index.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. import {Fragment, useCallback, useEffect, useState} from 'react';
  2. import {browserHistory} from 'react-router';
  3. import * as Sentry from '@sentry/react';
  4. import type {Location} from 'history';
  5. import {addErrorMessage} from 'sentry/actionCreators/indicator';
  6. import type {CursorHandler} from 'sentry/components/pagination';
  7. import type {AuditLog} from 'sentry/types';
  8. import {decodeScalar} from 'sentry/utils/queryString';
  9. import useApi from 'sentry/utils/useApi';
  10. import useOrganization from 'sentry/utils/useOrganization';
  11. import PermissionAlert from 'sentry/views/settings/organization/permissionAlert';
  12. import AuditLogList from './auditLogList';
  13. type Props = {
  14. location: Location;
  15. };
  16. type State = {
  17. entryList: AuditLog[] | null;
  18. entryListPageLinks: string | null;
  19. eventType: string | undefined;
  20. eventTypes: string[] | null;
  21. isLoading: boolean;
  22. currentCursor?: string;
  23. };
  24. function OrganizationAuditLog({location}: Props) {
  25. const [state, setState] = useState<State>({
  26. entryList: [],
  27. entryListPageLinks: null,
  28. eventType: decodeScalar(location.query.event),
  29. eventTypes: [],
  30. isLoading: true,
  31. });
  32. const organization = useOrganization();
  33. const api = useApi();
  34. const handleCursor: CursorHandler = resultsCursor => {
  35. setState(prevState => ({
  36. ...prevState,
  37. currentCursor: resultsCursor,
  38. }));
  39. };
  40. useEffect(() => {
  41. // Watch the location for changes so we can re-fetch data.
  42. const eventType = decodeScalar(location.query.event);
  43. setState(prevState => ({...prevState, eventType}));
  44. }, [location.query]);
  45. const fetchAuditLogData = useCallback(async () => {
  46. setState(prevState => ({...prevState, isLoading: true}));
  47. try {
  48. const payload = {cursor: state.currentCursor, event: state.eventType};
  49. if (!payload.cursor) {
  50. delete payload.cursor;
  51. }
  52. if (!payload.event) {
  53. delete payload.event;
  54. }
  55. setState(prevState => ({...prevState, isLoading: true}));
  56. const [data, _, response] = await api.requestPromise(
  57. `/organizations/${organization.slug}/audit-logs/`,
  58. {
  59. method: 'GET',
  60. includeAllArgs: true,
  61. query: payload,
  62. }
  63. );
  64. setState(prevState => ({
  65. ...prevState,
  66. entryList: data.rows,
  67. eventTypes: data.options,
  68. isLoading: false,
  69. entryListPageLinks: response?.getResponseHeader('Link') ?? null,
  70. }));
  71. } catch (err) {
  72. if (err.status !== 401 && err.status !== 403) {
  73. Sentry.captureException(err);
  74. }
  75. setState(prevState => ({
  76. ...prevState,
  77. isLoading: false,
  78. }));
  79. addErrorMessage('Unable to load audit logs.');
  80. }
  81. }, [api, organization.slug, state.currentCursor, state.eventType]);
  82. useEffect(() => {
  83. fetchAuditLogData();
  84. }, [fetchAuditLogData]);
  85. const handleEventSelect = (value: string) => {
  86. setState(prevState => ({
  87. ...prevState,
  88. eventType: value,
  89. }));
  90. browserHistory.push({
  91. pathname: location.pathname,
  92. query: {...location.query, event: value},
  93. });
  94. };
  95. return (
  96. <Fragment>
  97. {!organization.access.includes('org:write') ? (
  98. <PermissionAlert />
  99. ) : (
  100. <AuditLogList
  101. entries={state.entryList}
  102. pageLinks={state.entryListPageLinks}
  103. eventType={state.eventType}
  104. eventTypes={state.eventTypes}
  105. onEventSelect={handleEventSelect}
  106. isLoading={state.isLoading}
  107. onCursor={handleCursor}
  108. />
  109. )}
  110. </Fragment>
  111. );
  112. }
  113. export default OrganizationAuditLog;