Просмотр исходного кода

feat(sourcemaps): Add date_modified field in UI (#51780)

Co-authored-by: Ogi <86684834+obostjancic@users.noreply.github.com>
Riccardo Busetti 1 год назад
Родитель
Сommit
c361f21d01

+ 1 - 0
fixtures/js-stubs/sourceMapsDebugIDBundles.ts

@@ -19,6 +19,7 @@ export function SourceMapsDebugIDBundles(
       release: null,
       dist: null,
       fileCount: 39,
+      dateModified: '2023-03-10T08:25:10Z',
       date: '2023-03-08T09:53:09Z',
       ...debugIdBundle,
     },

+ 1 - 0
static/app/types/sourceMaps.ts

@@ -7,6 +7,7 @@ export type DebugIdBundle = {
   associations: DebugIdBundleAssociation[];
   bundleId: string;
   date: string;
+  dateModified: string;
   // TODO(Pri): Remove this type once fully transitioned to associations.
   dist: string | null;
   fileCount: number;

+ 52 - 1
static/app/views/settings/projectSourceMaps/projectSourceMaps.spec.tsx

@@ -110,7 +110,7 @@ describe('ProjectSourceMaps', function () {
           '/projects/org-slug/project-slug/files/source-maps/',
           expect.objectContaining({
             query: expect.objectContaining({
-              sortBy: '-date_added',
+              sortBy: 'date_added',
             }),
           })
         );
@@ -232,9 +232,25 @@ describe('ProjectSourceMaps', function () {
       ).toBeInTheDocument();
 
       // Date Uploaded can be sorted
+      await userEvent.click(screen.getByTestId('date-uploaded-header'));
       await userEvent.hover(screen.getByTestId('icon-arrow'));
       expect(await screen.findByText('Switch to ascending order')).toBeInTheDocument();
       await userEvent.click(screen.getByTestId('icon-arrow'));
+      await waitFor(() => {
+        expect(mockRequests.artifactBundles).toHaveBeenLastCalledWith(
+          '/projects/org-slug/project-slug/files/artifact-bundles/',
+          expect.objectContaining({
+            query: expect.objectContaining({
+              sortBy: 'date_added',
+            }),
+          })
+        );
+      });
+
+      // Date Uploaded can be sorted in descending
+      await userEvent.hover(screen.getByTestId('icon-arrow'));
+      expect(await screen.findByText('Switch to descending order')).toBeInTheDocument();
+      await userEvent.click(screen.getByTestId('icon-arrow'));
       await waitFor(() => {
         expect(mockRequests.artifactBundles).toHaveBeenLastCalledWith(
           '/projects/org-slug/project-slug/files/artifact-bundles/',
@@ -246,8 +262,43 @@ describe('ProjectSourceMaps', function () {
         );
       });
 
+      // Date Modified can be sorted
+      await userEvent.click(screen.getByTestId('date-modified-header'));
+      await userEvent.hover(screen.getByTestId('icon-arrow-modified'));
+      expect(await screen.findByText('Switch to ascending order')).toBeInTheDocument();
+      await userEvent.click(screen.getByTestId('icon-arrow-modified'));
+
+      await waitFor(() => {
+        expect(mockRequests.artifactBundles).toHaveBeenLastCalledWith(
+          '/projects/org-slug/project-slug/files/artifact-bundles/',
+          expect.objectContaining({
+            query: expect.objectContaining({
+              sortBy: 'date_modified',
+            }),
+          })
+        );
+      });
+
+      // Date Modified can be sorted in descending
+      await userEvent.hover(screen.getByTestId('icon-arrow-modified'));
+      expect(await screen.findByText('Switch to descending order')).toBeInTheDocument();
+      await userEvent.click(screen.getByTestId('icon-arrow-modified'));
+
+      await waitFor(() => {
+        expect(mockRequests.artifactBundles).toHaveBeenLastCalledWith(
+          '/projects/org-slug/project-slug/files/artifact-bundles/',
+          expect.objectContaining({
+            query: expect.objectContaining({
+              sortBy: '-date_modified',
+            }),
+          })
+        );
+      });
+
       // Artifacts
       expect(screen.getByText('39')).toBeInTheDocument();
+      // Date Modified
+      expect(screen.getByText('Mar 10, 2023 8:25 AM UTC')).toBeInTheDocument();
       // Date Uploaded
       expect(screen.getByText('Mar 8, 2023 9:53 AM UTC')).toBeInTheDocument();
       // Delete button

+ 147 - 37
static/app/views/settings/projectSourceMaps/projectSourceMaps.tsx

@@ -1,4 +1,4 @@
-import {Fragment, useCallback, useEffect} from 'react';
+import {Fragment, useCallback, useEffect, useState} from 'react';
 import {RouteComponentProps} from 'react-router';
 import styled from '@emotion/styled';
 
@@ -36,8 +36,10 @@ import {Associations} from 'sentry/views/settings/projectSourceMaps/associations
 import {DebugIdBundlesTags} from 'sentry/views/settings/projectSourceMaps/debugIdBundlesTags';
 
 enum SortBy {
-  ASC = 'date_added',
-  DESC = '-date_added',
+  ASC_ADDED = 'date_added',
+  DESC_ADDED = '-date_added',
+  ASC_MODIFIED = 'date_modified',
+  DESC_MODIFIED = '-date_modified',
 }
 
 enum SourceMapsBundleType {
@@ -51,6 +53,7 @@ function SourceMapsTableRow({
   name,
   fileCount,
   link,
+  dateModified,
   date,
   idColumnDetails,
 }: {
@@ -60,10 +63,13 @@ function SourceMapsTableRow({
   link: string;
   name: string;
   onDelete: (name: string) => void;
+  dateModified?: string;
   idColumnDetails?: React.ReactNode;
 }) {
   const isEmptyReleaseBundle =
     bundleType === SourceMapsBundleType.RELEASE && fileCount === -1;
+  const showDateModified =
+    bundleType === SourceMapsBundleType.DEBUG_ID && dateModified !== undefined;
 
   return (
     <Fragment>
@@ -85,6 +91,11 @@ function SourceMapsTableRow({
           <Count value={fileCount} />
         )}
       </ArtifactsTotalColumn>
+      {showDateModified && (
+        <Column>
+          <DateTime date={dateModified} timeZone />
+        </Column>
+      )}
       <Column>
         {isEmptyReleaseBundle ? t('(no value)') : <DateTime date={date} timeZone />}
       </Column>
@@ -136,11 +147,6 @@ export function ProjectSourceMaps({location, router, project}: Props) {
   const api = useApi();
   const organization = useOrganization();
 
-  // query params
-  const query = decodeScalar(location.query.query);
-  const sortBy = location.query.sort ?? SortBy.DESC;
-  const cursor = location.query.cursor ?? '';
-
   // endpoints
   const sourceMapsEndpoint = `/projects/${organization.slug}/${project.slug}/files/source-maps/`;
   const debugIdBundlesEndpoint = `/projects/${organization.slug}/${project.slug}/files/artifact-bundles/`;
@@ -159,6 +165,17 @@ export function ProjectSourceMaps({location, router, project}: Props) {
 
   const tabDebugIdBundlesActive = location.pathname === debugIdsUrl;
 
+  // query params
+  const query = decodeScalar(location.query.query);
+
+  const [sortBy, setSortBy] = useState(
+    location.query.sort ?? tabDebugIdBundlesActive
+      ? SortBy.DESC_MODIFIED
+      : SortBy.DESC_ADDED
+  );
+  // The default sorting order changes based on the tab.
+  const cursor = location.query.cursor ?? '';
+
   useEffect(() => {
     if (location.pathname === sourceMapsUrl) {
       router.replace(debugIdsUrl);
@@ -213,13 +230,29 @@ export function ProjectSourceMaps({location, router, project}: Props) {
     [router, location]
   );
 
-  const handleSortChange = useCallback(() => {
+  const handleSortChangeForModified = useCallback(() => {
+    const newSortBy =
+      sortBy !== SortBy.DESC_MODIFIED ? SortBy.DESC_MODIFIED : SortBy.ASC_MODIFIED;
+    setSortBy(newSortBy);
+    router.push({
+      pathname: location.pathname,
+      query: {
+        ...location.query,
+        cursor: undefined,
+        sort: newSortBy,
+      },
+    });
+  }, [location, router, sortBy]);
+
+  const handleSortChangeForAdded = useCallback(() => {
+    const newSortBy = sortBy !== SortBy.DESC_ADDED ? SortBy.DESC_ADDED : SortBy.ASC_ADDED;
+    setSortBy(newSortBy);
     router.push({
       pathname: location.pathname,
       query: {
         ...location.query,
         cursor: undefined,
-        sort: sortBy === SortBy.ASC ? SortBy.DESC : SortBy.ASC,
+        sort: newSortBy,
       },
     });
   }, [location, router, sortBy]);
@@ -251,6 +284,87 @@ export function ProjectSourceMaps({location, router, project}: Props) {
     ]
   );
 
+  const currentBundleType = tabDebugIdBundlesActive
+    ? SourceMapsBundleType.DEBUG_ID
+    : SourceMapsBundleType.RELEASE;
+  const tableHeaders = [
+    {
+      component: tabDebugIdBundlesActive ? t('Bundle ID') : t('Name'),
+      enabledFor: [SourceMapsBundleType.RELEASE, SourceMapsBundleType.DEBUG_ID],
+    },
+    {
+      component: (
+        <ArtifactsTotalColumn key="artifacts-total">
+          {t('Artifacts')}
+        </ArtifactsTotalColumn>
+      ),
+      enabledFor: [SourceMapsBundleType.RELEASE, SourceMapsBundleType.DEBUG_ID],
+    },
+    {
+      component: (
+        <DateUploadedColumn
+          key="date-modified"
+          data-test-id="date-modified-header"
+          onClick={handleSortChangeForModified}
+        >
+          {t('Date Modified')}
+          {(sortBy === SortBy.ASC_MODIFIED || sortBy === SortBy.DESC_MODIFIED) && (
+            <Tooltip
+              containerDisplayMode="inline-flex"
+              title={
+                sortBy === SortBy.DESC_MODIFIED
+                  ? t('Switch to ascending order')
+                  : t('Switch to descending order')
+              }
+            >
+              <IconArrow
+                direction={sortBy === SortBy.DESC_MODIFIED ? 'down' : 'up'}
+                data-test-id="icon-arrow-modified"
+              />
+            </Tooltip>
+          )}
+        </DateUploadedColumn>
+      ),
+      enabledFor: [SourceMapsBundleType.DEBUG_ID],
+    },
+    {
+      component: (
+        <DateUploadedColumn
+          key="date-uploaded"
+          data-test-id="date-uploaded-header"
+          onClick={handleSortChangeForAdded}
+        >
+          {t('Date Uploaded')}
+          {(sortBy === SortBy.ASC_ADDED || sortBy === SortBy.DESC_ADDED) && (
+            <Tooltip
+              containerDisplayMode="inline-flex"
+              title={
+                sortBy === SortBy.DESC_ADDED
+                  ? t('Switch to ascending order')
+                  : t('Switch to descending order')
+              }
+            >
+              <IconArrow
+                direction={sortBy === SortBy.DESC_ADDED ? 'down' : 'up'}
+                data-test-id="icon-arrow"
+              />
+            </Tooltip>
+          )}
+        </DateUploadedColumn>
+      ),
+      enabledFor: [SourceMapsBundleType.RELEASE, SourceMapsBundleType.DEBUG_ID],
+    },
+    {
+      component: '',
+      enabledFor: [SourceMapsBundleType.RELEASE, SourceMapsBundleType.DEBUG_ID],
+    },
+  ];
+
+  const Table =
+    currentBundleType === SourceMapsBundleType.DEBUG_ID
+      ? ArtifactBundlesPanelTable
+      : ReleaseBundlesPanelTable;
+
   return (
     <Fragment>
       <SettingsPageHeader title={t('Source Maps')} />
@@ -281,30 +395,10 @@ export function ProjectSourceMaps({location, router, project}: Props) {
         onSearch={handleSearch}
         query={query}
       />
-      <StyledPanelTable
-        headers={[
-          tabDebugIdBundlesActive ? t('Bundle ID') : t('Name'),
-          <ArtifactsTotalColumn key="artifacts-total">
-            {t('Artifacts')}
-          </ArtifactsTotalColumn>,
-          <DateUploadedColumn key="date-uploaded" onClick={handleSortChange}>
-            {t('Date Uploaded')}
-            <Tooltip
-              containerDisplayMode="inline-flex"
-              title={
-                sortBy === SortBy.DESC
-                  ? t('Switch to ascending order')
-                  : t('Switch to descending order')
-              }
-            >
-              <IconArrow
-                direction={sortBy === SortBy.DESC ? 'down' : 'up'}
-                data-test-id="icon-arrow"
-              />
-            </Tooltip>
-          </DateUploadedColumn>,
-          '',
-        ]}
+      <Table
+        headers={tableHeaders
+          .filter(header => header.enabledFor.includes(currentBundleType))
+          .map(header => header.component)}
         emptyMessage={
           query
             ? tct('No [tabName] match your search query.', {
@@ -329,6 +423,7 @@ export function ProjectSourceMaps({location, router, project}: Props) {
               <SourceMapsTableRow
                 key={data.bundleId}
                 bundleType={SourceMapsBundleType.DEBUG_ID}
+                dateModified={data.dateModified}
                 date={data.date}
                 fileCount={data.fileCount}
                 name={data.bundleId}
@@ -360,7 +455,7 @@ export function ProjectSourceMaps({location, router, project}: Props) {
                 }/source-maps/release-bundles/${encodeURIComponent(data.name)}`}
               />
             ))}
-      </StyledPanelTable>
+      </Table>
       <Pagination
         pageLinks={
           tabDebugIdBundlesActive
@@ -372,11 +467,10 @@ export function ProjectSourceMaps({location, router, project}: Props) {
   );
 }
 
-const StyledPanelTable = styled(PanelTable)`
+const ReleaseBundlesPanelTable = styled(PanelTable)`
   grid-template-columns:
     minmax(120px, 1fr) minmax(120px, max-content) minmax(242px, max-content)
     minmax(74px, max-content);
-
   > * {
     :nth-child(-n + 4) {
       :nth-child(4n-1) {
@@ -386,6 +480,22 @@ const StyledPanelTable = styled(PanelTable)`
   }
 `;
 
+const ArtifactBundlesPanelTable = styled(PanelTable)`
+  grid-template-columns:
+    minmax(120px, 1fr) minmax(120px, max-content) minmax(242px, max-content) minmax(
+      242px,
+      max-content
+    )
+    minmax(74px, max-content);
+  > * {
+    :nth-child(-n + 5) {
+      :nth-child(5n-1) {
+        cursor: pointer;
+      }
+    }
+  }
+`;
+
 const ArtifactsTotalColumn = styled('div')`
   text-align: right;
   justify-content: flex-end;