Browse Source

ref(audit-log): Update Audit log to display project links and better msgs - (#38502)

Priscila Oliveira 2 years ago
parent
commit
d01d2bc449

+ 1 - 0
fixtures/js-stubs/types.tsx

@@ -16,6 +16,7 @@ type TestStubFixtures = {
   AsanaCreate: SimpleStub;
   AsanaPlugin: SimpleStub;
   AuditLogs: OverridableStubList;
+  AuditLogsApiEventNames: SimpleStub;
   AuthProvider: OverridableStub;
   AuthProviders: OverridableStubList;
   Authenticators: SimpleStub;

+ 141 - 36
static/app/views/settings/organizationAuditLog/auditLogList.tsx

@@ -5,14 +5,17 @@ import ActivityAvatar from 'sentry/components/activity/item/avatar';
 import UserAvatar from 'sentry/components/avatar/userAvatar';
 import DateTime from 'sentry/components/dateTime';
 import SelectControl from 'sentry/components/forms/selectControl';
+import Link from 'sentry/components/links/link';
 import Pagination, {CursorHandler} from 'sentry/components/pagination';
 import {PanelTable} from 'sentry/components/panels';
 import Tag from 'sentry/components/tag';
 import Tooltip from 'sentry/components/tooltip';
-import {t} from 'sentry/locale';
+import {t, tct} from 'sentry/locale';
 import space from 'sentry/styles/space';
-import {AuditLog, User} from 'sentry/types';
+import {AuditLog, Organization, User} from 'sentry/types';
 import {shouldUse24Hours} from 'sentry/utils/dates';
+import useOrganization from 'sentry/utils/useOrganization';
+import useProjects from 'sentry/utils/useProjects';
 import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
 
 const avatarStyle = {
@@ -51,6 +54,105 @@ const addUsernameDisplay = (logEntryUser: User | undefined) => {
   return null;
 };
 
+function AuditNote({entry, orgSlug}: {entry: AuditLog; orgSlug: Organization['slug']}) {
+  const {projects} = useProjects();
+  const project = projects.find(p => p.id === String(entry.data.id));
+
+  if (!project) {
+    return <Note>{entry.note}</Note>;
+  }
+
+  const projectSlug = (
+    <Link to={`/settings/${orgSlug}/projects/${project.slug}/`}>{entry.data.slug}</Link>
+  );
+
+  if (entry.event === 'project.create') {
+    return (
+      <Note>
+        {tct('Created project [project-slug]', {['project-slug']: projectSlug})}
+      </Note>
+    );
+  }
+
+  if (entry.event === 'project.edit') {
+    if (entry.data.old_slug && entry.data.new_slug) {
+      return (
+        <Note>
+          {tct('Renamed project slug from [old-slug] to [new-slug]', {
+            'old-slug': entry.data.old_slug,
+            'new-slug': (
+              <Link to={`/settings/${orgSlug}/projects/${entry.data.new_slug}/`}>
+                {entry.data.new_slug}
+              </Link>
+            ),
+          })}
+        </Note>
+      );
+    }
+
+    return (
+      <Note>
+        {tct('Edited project [project-slug] [note]', {
+          ['project-slug']: projectSlug,
+          note: entry.note.replace('edited project settings ', ''),
+        })}
+      </Note>
+    );
+  }
+
+  if (entry.event === 'sampling.add') {
+    return (
+      <Note>
+        {tct('Added server-side sampling rule in the project [project-slug]', {
+          ['project-slug']: projectSlug,
+        })}
+      </Note>
+    );
+  }
+
+  if (entry.event === 'sampling.remove') {
+    return (
+      <Note>
+        {tct('Deleted server-side sampling rule in the project [project-slug]', {
+          ['project-slug']: projectSlug,
+        })}
+      </Note>
+    );
+  }
+
+  if (entry.event === 'sampling.edit') {
+    return (
+      <Note>
+        {tct('Edited server-side sampling rule in the project [project-slug]', {
+          ['project-slug']: projectSlug,
+        })}
+      </Note>
+    );
+  }
+
+  if (entry.event === 'sampling.activate') {
+    return (
+      <Note>
+        {tct('Enabled server-side sampling rule in the project [project-slug]', {
+          ['project-slug']: projectSlug,
+        })}
+      </Note>
+    );
+  }
+
+  if (entry.event === 'sampling.deactivate') {
+    return (
+      <Note>
+        {tct('Disabled server-side sampling rule in the project [project-slug]', {
+          ['project-slug']: projectSlug,
+        })}
+      </Note>
+    );
+  }
+
+  return <Note>{entry.note}</Note>;
+}
+
 type Props = {
   entries: AuditLog[] | null;
   eventType: string | undefined;
@@ -71,6 +173,7 @@ const AuditLogList = ({
   onEventSelect,
 }: Props) => {
   const is24Hours = shouldUse24Hours();
+  const organization = useOrganization();
   const hasEntries = entries && entries.length > 0;
   const ipv4Length = 15;
 
@@ -102,40 +205,42 @@ const AuditLogList = ({
         emptyMessage={t('No audit entries available')}
         isLoading={isLoading}
       >
-        {entries?.map(entry => (
-          <Fragment key={entry.id}>
-            <UserInfo>
-              <div>{getAvatarDisplay(entry.actor)}</div>
-              <NameContainer>
-                {addUsernameDisplay(entry.actor)}
-                <Note>{entry.note}</Note>
-              </NameContainer>
-            </UserInfo>
-            <FlexCenter>
-              <MonoDetail>{entry.event}</MonoDetail>
-            </FlexCenter>
-            <FlexCenter>
-              {entry.ipAddress && (
-                <IpAddressOverflow>
-                  <Tooltip
-                    title={entry.ipAddress}
-                    disabled={entry.ipAddress.length <= ipv4Length}
-                  >
-                    <MonoDetail>{entry.ipAddress}</MonoDetail>
-                  </Tooltip>
-                </IpAddressOverflow>
-              )}
-            </FlexCenter>
-            <TimestampInfo>
-              <DateTime dateOnly date={entry.dateCreated} />
-              <DateTime
-                timeOnly
-                format={is24Hours ? 'HH:mm zz' : 'LT zz'}
-                date={entry.dateCreated}
-              />
-            </TimestampInfo>
-          </Fragment>
-        ))}
+        {entries?.map(entry => {
+          return (
+            <Fragment key={entry.id}>
+              <UserInfo>
+                <div>{getAvatarDisplay(entry.actor)}</div>
+                <NameContainer>
+                  {addUsernameDisplay(entry.actor)}
+                  <AuditNote entry={entry} orgSlug={organization.slug} />
+                </NameContainer>
+              </UserInfo>
+              <FlexCenter>
+                <MonoDetail>{entry.event}</MonoDetail>
+              </FlexCenter>
+              <FlexCenter>
+                {entry.ipAddress && (
+                  <IpAddressOverflow>
+                    <Tooltip
+                      title={entry.ipAddress}
+                      disabled={entry.ipAddress.length <= ipv4Length}
+                    >
+                      <MonoDetail>{entry.ipAddress}</MonoDetail>
+                    </Tooltip>
+                  </IpAddressOverflow>
+                )}
+              </FlexCenter>
+              <TimestampInfo>
+                <DateTime dateOnly date={entry.dateCreated} />
+                <DateTime
+                  timeOnly
+                  format={is24Hours ? 'HH:mm zz' : 'LT zz'}
+                  date={entry.dateCreated}
+                />
+              </TimestampInfo>
+            </Fragment>
+          );
+        })}
       </PanelTable>
       {pageLinks && <Pagination pageLinks={pageLinks} onCursor={onCursor} />}
     </div>

+ 20 - 26
static/app/views/settings/organizationAuditLog/auditLogView.spec.jsx → static/app/views/settings/organizationAuditLog/auditLogView.spec.tsx

@@ -1,34 +1,32 @@
 import {initializeOrg} from 'sentry-test/initializeOrg';
 import {render, screen} from 'sentry-test/reactTestingLibrary';
 
-import {Client} from 'sentry/api';
+import {OrganizationContext} from 'sentry/views/organizationContext';
 import OrganizationAuditLog from 'sentry/views/settings/organizationAuditLog';
 
-describe('OrganizationAuditLog', () => {
-  const {routerContext, org} = initializeOrg({
+describe('OrganizationAuditLog', function () {
+  const {routerContext, organization, router} = initializeOrg({
+    ...initializeOrg(),
     projects: [],
     router: {
       params: {orgId: 'org-slug'},
     },
   });
-  const ENDPOINT = `/organizations/${org.slug}/audit-logs/`;
-  const mockLocation = {query: {}};
+  const ENDPOINT = `/organizations/${organization.slug}/audit-logs/`;
 
   beforeEach(function () {
-    Client.clearMockResponses();
-    Client.addMockResponse({
+    MockApiClient.clearMockResponses();
+    MockApiClient.addMockResponse({
       url: ENDPOINT,
       body: {rows: TestStubs.AuditLogs(), options: TestStubs.AuditLogsApiEventNames()},
     });
   });
 
-  it('renders', async () => {
+  it('renders', async function () {
     render(
-      <OrganizationAuditLog
-        location={mockLocation}
-        organization={org}
-        params={{orgId: org.slug}}
-      />,
+      <OrganizationContext.Provider value={organization}>
+        <OrganizationAuditLog location={router.location} />
+      </OrganizationContext.Provider>,
       {
         context: routerContext,
       }
@@ -44,19 +42,17 @@ describe('OrganizationAuditLog', () => {
     expect(screen.getByText('edited project ludic-science')).toBeInTheDocument();
   });
 
-  it('renders empty', async () => {
-    Client.clearMockResponses();
-    Client.addMockResponse({
+  it('renders empty', async function () {
+    MockApiClient.clearMockResponses();
+    MockApiClient.addMockResponse({
       url: ENDPOINT,
       body: {rows: [], options: TestStubs.AuditLogsApiEventNames()},
     });
 
     render(
-      <OrganizationAuditLog
-        location={mockLocation}
-        organization={org}
-        params={{orgId: org.slug}}
-      />,
+      <OrganizationContext.Provider value={organization}>
+        <OrganizationAuditLog location={router.location} />
+      </OrganizationContext.Provider>,
       {
         context: routerContext,
       }
@@ -67,11 +63,9 @@ describe('OrganizationAuditLog', () => {
 
   it('displays whether an action was done by a superuser', async () => {
     render(
-      <OrganizationAuditLog
-        location={mockLocation}
-        organization={org}
-        params={{orgId: org.slug}}
-      />,
+      <OrganizationContext.Provider value={organization}>
+        <OrganizationAuditLog location={router.location} />
+      </OrganizationContext.Provider>,
       {
         context: routerContext,
       }

+ 5 - 5
static/app/views/settings/organizationAuditLog/index.tsx

@@ -5,16 +5,15 @@ import {Location} from 'history';
 
 import {addErrorMessage} from 'sentry/actionCreators/indicator';
 import {CursorHandler} from 'sentry/components/pagination';
-import {AuditLog, Organization} from 'sentry/types';
+import {AuditLog} from 'sentry/types';
 import {decodeScalar} from 'sentry/utils/queryString';
 import useApi from 'sentry/utils/useApi';
-import withOrganization from 'sentry/utils/withOrganization';
+import useOrganization from 'sentry/utils/useOrganization';
 
 import AuditLogList from './auditLogList';
 
 type Props = {
   location: Location;
-  organization: Organization;
 };
 
 type State = {
@@ -26,7 +25,7 @@ type State = {
   currentCursor?: string;
 };
 
-function OrganizationAuditLog({location, organization}: Props) {
+function OrganizationAuditLog({location}: Props) {
   const [state, setState] = useState<State>({
     entryList: [],
     entryListPageLinks: null,
@@ -34,6 +33,7 @@ function OrganizationAuditLog({location, organization}: Props) {
     eventTypes: [],
     isLoading: true,
   });
+  const organization = useOrganization();
   const api = useApi();
 
   const handleCursor: CursorHandler = resultsCursor => {
@@ -118,4 +118,4 @@ function OrganizationAuditLog({location, organization}: Props) {
   );
 }
 
-export default withOrganization(OrganizationAuditLog);
+export default OrganizationAuditLog;

+ 14 - 11
static/app/views/settings/organizationAuditLog/organizationAuditLog.spec.jsx → static/app/views/settings/organizationAuditLog/organizationAuditLog.spec.tsx

@@ -2,10 +2,12 @@ import {initializeOrg} from 'sentry-test/initializeOrg';
 import {render, screen} from 'sentry-test/reactTestingLibrary';
 
 import ConfigStore from 'sentry/stores/configStore';
+import {Config, User} from 'sentry/types';
+import {OrganizationContext} from 'sentry/views/organizationContext';
 import OrganizationAuditLog from 'sentry/views/settings/organizationAuditLog';
 
-describe('OrganizationAuditLog', () => {
-  const user = {
+describe('OrganizationAuditLog', function () {
+  const user: User = {
     ...TestStubs.User(),
     options: {
       clock24Hours: true,
@@ -13,11 +15,13 @@ describe('OrganizationAuditLog', () => {
     },
   };
 
+  const config: Config = {...ConfigStore.getConfig(), user};
+
   beforeEach(() => {
-    ConfigStore.loadInitialData({user});
+    ConfigStore.loadInitialData(config);
   });
 
-  it('renders', async () => {
+  it('renders', async function () {
     MockApiClient.addMockResponse({
       url: `/organizations/org-slug/audit-logs/`,
       method: 'GET',
@@ -50,19 +54,18 @@ describe('OrganizationAuditLog', () => {
       },
     });
 
-    const {routerContext, organization} = initializeOrg({
+    const {routerContext, organization, router} = initializeOrg({
+      ...initializeOrg(),
       projects: [],
       router: {
         params: {orgId: 'org-slug'},
       },
     });
-    const mockLocation = {query: {}};
+
     render(
-      <OrganizationAuditLog
-        organization={organization}
-        params={{orgId: organization.slug}}
-        location={mockLocation}
-      />,
+      <OrganizationContext.Provider value={organization}>
+        <OrganizationAuditLog location={router.location} />
+      </OrganizationContext.Provider>,
       {
         context: routerContext,
       }