From 28cfe4d470587778175c18b5eea3f1945ababe61 Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Fri, 3 Jun 2022 16:21:12 +0300 Subject: [PATCH 01/33] Archive table new sort parameter --- .../archive/ArchiveTable/ArchiveTable.tsx | 287 ++++++++++++++++ .../ReviveArchivedFeatureCell.tsx | 20 ++ .../FeatureToggleListTable.tsx | 6 +- frontend/src/component/menu/routes.ts | 5 +- frontend/src/hooks/useFeaturesSort.ts | 13 + frontend/src/openapi/apis/AdminApi.ts | 143 +++----- .../src/openapi/models/ChangeProjectSchema.ts | 56 ---- .../src/openapi/models/ConstraintSchema.ts | 42 +-- frontend/src/openapi/models/FeatureSchema.ts | 8 + .../openapi/models/PatchOperationSchema.ts | 22 +- frontend/src/openapi/models/index.ts | 1 - frontend/src/openapi/runtime.ts | 309 ++++++++++-------- 12 files changed, 593 insertions(+), 319 deletions(-) create mode 100644 frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx create mode 100644 frontend/src/component/common/Table/cells/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx delete mode 100644 frontend/src/openapi/models/ChangeProjectSchema.ts diff --git a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx new file mode 100644 index 0000000000..2f3139da61 --- /dev/null +++ b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx @@ -0,0 +1,287 @@ +import { PageContent } from 'component/common/PageContent/PageContent'; +import { PageHeader } from 'component/common/PageHeader/PageHeader'; +import { + SortableTableHeader, + Table, + TableBody, + TableCell, + TablePlaceholder, + TableRow, + TableSearch, +} from 'component/common/Table'; +import { + SortingRule, + useFlexLayout, + useGlobalFilter, + useSortBy, + useTable, +} from 'react-table'; +import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; +import { useMediaQuery } from '@mui/material'; +import { sortTypes } from 'utils/sortTypes'; +import { useEffect, useMemo, useState } from 'react'; +import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell'; +import { DateCell } from 'component/common/Table/cells/DateCell/DateCell'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import { useFeaturesArchive } from '../../../hooks/api/getters/useFeaturesArchive/useFeaturesArchive'; +import { FeatureTypeCell } from '../../common/Table/cells/FeatureTypeCell/FeatureTypeCell'; +import { FeatureSeenCell } from '../../common/Table/cells/FeatureSeenCell/FeatureSeenCell'; +import { LinkCell } from '../../common/Table/cells/LinkCell/LinkCell'; +import { FeatureStaleCell } from '../../feature/FeatureToggleList/FeatureStaleCell/FeatureStaleCell'; +import { TimeAgoCell } from '../../common/Table/cells/TimeAgoCell/TimeAgoCell'; +import { ReviveArchivedFeatureCell } from 'component/common/Table/cells/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell'; +import { useStyles } from '../../feature/FeatureToggleList/styles'; +import { useSearchParams } from 'react-router-dom'; +import { useLocalStorage } from '../../../hooks/useLocalStorage'; +import { useVirtualizedRange } from '../../../hooks/useVirtualizedRange'; +import { + featuresPlaceholder, + PageQueryType, +} from '../../feature/FeatureToggleList/FeatureToggleListTable'; +import theme from 'themes/theme'; + +const defaultSort: SortingRule = { id: 'createdAt', desc: true }; + +export const ArchiveTable = () => { + const rowHeight = theme.shape.tableRowHeight; + const { classes } = useStyles(); + const isSmallScreen = useMediaQuery(theme.breakpoints.down('md')); + const isMediumScreen = useMediaQuery(theme.breakpoints.down('lg')); + const [searchParams, setSearchParams] = useSearchParams(); + const [storedParams, setStoredParams] = useLocalStorage( + 'ArchiveTable:v1', + defaultSort + ); + const { archivedFeatures = [], loading } = useFeaturesArchive(); + const data = useMemo( + () => + archivedFeatures?.length === 0 && loading + ? featuresPlaceholder + : archivedFeatures, + [archivedFeatures, loading] + ); + + const [initialState] = useState(() => ({ + sortBy: [ + { + id: searchParams.get('sort') || storedParams.id, + desc: searchParams.has('order') + ? searchParams.get('order') === 'desc' + : storedParams.desc, + }, + ], + hiddenColumns: ['description'], + globalFilter: searchParams.get('search') || '', + })); + + const { + getTableProps, + getTableBodyProps, + headerGroups, + rows, + prepareRow, + state: { globalFilter, sortBy }, + setGlobalFilter, + setHiddenColumns, + } = useTable( + { + columns: COLUMNS as any, + data: data as any, + initialState, + sortTypes, + autoResetGlobalFilter: false, + autoResetSortBy: false, + disableSortRemove: true, + disableMultiSort: true, + }, + useGlobalFilter, + useSortBy, + useFlexLayout + ); + + useEffect(() => { + const hiddenColumns = ['description']; + if (isMediumScreen) { + hiddenColumns.push('lastSeenAt', 'status'); + } + if (isSmallScreen) { + hiddenColumns.push('type', 'createdAt'); + } + setHiddenColumns(hiddenColumns); + }, [setHiddenColumns, isSmallScreen, isMediumScreen]); + + useEffect(() => { + const tableState: PageQueryType = {}; + tableState.sort = sortBy[0].id; + if (sortBy[0].desc) { + tableState.order = 'desc'; + } + if (globalFilter) { + tableState.search = globalFilter; + } + + setSearchParams(tableState, { + replace: true, + }); + setStoredParams({ id: sortBy[0].id, desc: sortBy[0].desc || false }); + }, [sortBy, globalFilter, setSearchParams, setStoredParams]); + + const [firstRenderedIndex, lastRenderedIndex] = + useVirtualizedRange(rowHeight); + + return ( + + + + } + /> + } + > + } + elseShow={() => ( + <> + + + + + {rows.map((row, index) => { + const isVirtual = + index < firstRenderedIndex || + index > lastRenderedIndex; + + if (isVirtual) { + return null; + } + + prepareRow(row); + return ( + + {row.cells.map(cell => ( + + {cell.render('Cell')} + + ))} + + ); + })} + +
+
+ 0 + } + show={ + + No features found matching “ + {globalFilter}” + + } + /> + + )} + /> +
+ ); +}; + +const COLUMNS = [ + { + id: 'Seen', + Header: 'Seen', + maxWidth: 85, + canSort: true, + Cell: FeatureSeenCell, + disableGlobalFilter: true, + }, + { + id: 'Type', + Header: 'Type', + maxWidth: 85, + canSort: true, + Cell: FeatureTypeCell, + disableGlobalFilter: true, + }, + { + Header: 'Feature toggle Name', + accessor: 'name', + maxWidth: 150, + Cell: ({ value, row: { original } }: any) => ( + + ), + sortType: 'alphanumeric', + }, + { + Header: 'Created', + accessor: 'createdAt', + maxWidth: 150, + Cell: DateCell, + sortType: 'date', + disableGlobalFilter: true, + }, + { + Header: 'Archived', + accessor: 'archivedAt', + maxWidth: 150, + Cell: TimeAgoCell, + sortType: 'date', + disableGlobalFilter: true, + }, + { + Header: 'Project ID', + accessor: 'project', + sortType: 'alphanumeric', + maxWidth: 150, + Cell: ({ value }: any) => ( + + ), + }, + { + Header: 'Status', + accessor: 'stale', + Cell: FeatureStaleCell, + sortType: 'boolean', + maxWidth: 120, + disableGlobalFilter: true, + }, + { + Header: 'Actions', + id: 'Actions', + align: 'center', + maxWidth: 85, + canSort: false, + disableGlobalFilter: true, + Cell: ReviveArchivedFeatureCell, + }, +]; diff --git a/frontend/src/component/common/Table/cells/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx b/frontend/src/component/common/Table/cells/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx new file mode 100644 index 0000000000..4c062d0e03 --- /dev/null +++ b/frontend/src/component/common/Table/cells/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx @@ -0,0 +1,20 @@ +import { VFC } from 'react'; +import { ActionCell } from '../ActionCell/ActionCell'; +import { Undo } from '@mui/icons-material'; +import { IconButton } from '@mui/material'; + +interface IReviveArchivedFeatureCell { + onRevive: any; +} + +export const ReviveArchivedFeatureCell: VFC = ({ + onRevive, +}) => { + return ( + + + + + + ); +}; diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx index 12bdb65519..1086da1b40 100644 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx +++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx @@ -35,7 +35,7 @@ import { CreateFeatureButton } from '../CreateFeatureButton/CreateFeatureButton' import { FeatureStaleCell } from './FeatureStaleCell/FeatureStaleCell'; import { useStyles } from './styles'; -const featuresPlaceholder: FeatureSchema[] = Array(15).fill({ +export const featuresPlaceholder: FeatureSchema[] = Array(15).fill({ name: 'Name of the feature', description: 'Short description of the feature', type: '-', @@ -43,7 +43,9 @@ const featuresPlaceholder: FeatureSchema[] = Array(15).fill({ project: 'projectID', }); -type PageQueryType = Partial>; +export type PageQueryType = Partial< + Record<'sort' | 'order' | 'search', string> +>; const columns = [ { diff --git a/frontend/src/component/menu/routes.ts b/frontend/src/component/menu/routes.ts index e6710aa056..848754739a 100644 --- a/frontend/src/component/menu/routes.ts +++ b/frontend/src/component/menu/routes.ts @@ -1,8 +1,6 @@ import { FeatureToggleListTable } from 'component/feature/FeatureToggleList/FeatureToggleListTable'; import { StrategyView } from 'component/strategies/StrategyView/StrategyView'; import { StrategiesList } from 'component/strategies/StrategiesList/StrategiesList'; - -import { ArchiveListContainer } from 'component/archive/ArchiveListContainer'; import { TagTypeList } from 'component/tags/TagTypeList/TagTypeList'; import { AddonList } from 'component/addons/AddonList/AddonList'; import Admin from 'component/admin'; @@ -53,6 +51,7 @@ import { IRoute } from 'interfaces/route'; import { EnvironmentTable } from 'component/environments/EnvironmentTable/EnvironmentTable'; import { SegmentTable } from 'component/segments/SegmentTable/SegmentTable'; import RedirectAdminInvoices from 'component/admin/billing/RedirectAdminInvoices/RedirectAdminInvoices'; +import { ArchiveTable } from '../archive/ArchiveTable/ArchiveTable'; export const routes: IRoute[] = [ // Splash @@ -376,7 +375,7 @@ export const routes: IRoute[] = [ { path: '/archive', title: 'Archived toggles', - component: ArchiveListContainer, + component: ArchiveTable, type: 'protected', menu: {}, }, diff --git a/frontend/src/hooks/useFeaturesSort.ts b/frontend/src/hooks/useFeaturesSort.ts index 0615036317..0758a02b68 100644 --- a/frontend/src/hooks/useFeaturesSort.ts +++ b/frontend/src/hooks/useFeaturesSort.ts @@ -15,6 +15,7 @@ export type FeaturesSortType = | 'enabled' | 'stale' | 'created' + | 'archived' | 'last-seen' | 'status' | 'project'; @@ -65,7 +66,9 @@ export const createFeaturesFilterSortOptions = { type: 'type', name: 'Type' }, { type: 'enabled', name: 'Enabled' }, { type: 'stale', name: 'Stale' }, + { type: 'status', name: 'Status' }, { type: 'created', name: 'Created' }, + { type: 'archived', name: 'Archived' }, { type: 'last-seen', name: 'Last seen' }, { type: 'project', name: 'Project' }, ]; @@ -82,6 +85,8 @@ const sortAscendingFeatures = ( return sortByStale(features); case 'created': return sortByCreated(features); + case 'archived': + return sortByArchived(features); case 'last-seen': return sortByLastSeen(features); case 'name': @@ -149,6 +154,14 @@ const sortByCreated = ( ); }; +const sortByArchived = ( + features: Readonly +): FeatureSchema[] => { + return [...features].sort((a, b) => + compareNullableDates(b.archivedAt, a.archivedAt) + ); +}; + const sortByName = (features: Readonly): FeatureSchema[] => { return [...features].sort((a, b) => a.name.localeCompare(b.name)); }; diff --git a/frontend/src/openapi/apis/AdminApi.ts b/frontend/src/openapi/apis/AdminApi.ts index 2391d854f4..008454d860 100644 --- a/frontend/src/openapi/apis/AdminApi.ts +++ b/frontend/src/openapi/apis/AdminApi.ts @@ -15,9 +15,6 @@ import * as runtime from '../runtime'; import { - ChangeProjectSchema, - ChangeProjectSchemaFromJSON, - ChangeProjectSchemaToJSON, CloneFeatureSchema, CloneFeatureSchemaFromJSON, CloneFeatureSchemaToJSON, @@ -75,12 +72,6 @@ export interface ApiAdminArchiveFeaturesProjectIdGetRequest { projectId: string; } -export interface ApiAdminProjectsProjectIdFeaturesFeatureNameChangeProjectPostRequest { - projectId: string; - featureName: string; - changeProjectSchema: ChangeProjectSchema; -} - export interface ArchiveFeatureRequest { projectId: string; featureName: string; @@ -189,7 +180,7 @@ export class AdminApi extends runtime.BaseAPI { /** */ - async addStrategyRaw(requestParameters: AddStrategyRequest, initOverrides?: RequestInit): Promise> { + async addStrategyRaw(requestParameters: AddStrategyRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise> { if (requestParameters.projectId === null || requestParameters.projectId === undefined) { throw new runtime.RequiredError('projectId','Required parameter requestParameters.projectId was null or undefined when calling addStrategy.'); } @@ -229,14 +220,14 @@ export class AdminApi extends runtime.BaseAPI { /** */ - async addStrategy(requestParameters: AddStrategyRequest, initOverrides?: RequestInit): Promise { + async addStrategy(requestParameters: AddStrategyRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise { const response = await this.addStrategyRaw(requestParameters, initOverrides); return await response.value(); } /** */ - async addTagRaw(requestParameters: AddTagRequest, initOverrides?: RequestInit): Promise> { + async addTagRaw(requestParameters: AddTagRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise> { if (requestParameters.featureName === null || requestParameters.featureName === undefined) { throw new runtime.RequiredError('featureName','Required parameter requestParameters.featureName was null or undefined when calling addTag.'); } @@ -268,14 +259,14 @@ export class AdminApi extends runtime.BaseAPI { /** */ - async addTag(requestParameters: AddTagRequest, initOverrides?: RequestInit): Promise { + async addTag(requestParameters: AddTagRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise { const response = await this.addTagRaw(requestParameters, initOverrides); return await response.value(); } /** */ - async apiAdminArchiveFeaturesGetRaw(initOverrides?: RequestInit): Promise> { + async apiAdminArchiveFeaturesGetRaw(initOverrides?: RequestInit | runtime.InitOverideFunction): Promise> { const queryParameters: any = {}; const headerParameters: runtime.HTTPHeaders = {}; @@ -296,14 +287,14 @@ export class AdminApi extends runtime.BaseAPI { /** */ - async apiAdminArchiveFeaturesGet(initOverrides?: RequestInit): Promise { + async apiAdminArchiveFeaturesGet(initOverrides?: RequestInit | runtime.InitOverideFunction): Promise { const response = await this.apiAdminArchiveFeaturesGetRaw(initOverrides); return await response.value(); } /** */ - async apiAdminArchiveFeaturesProjectIdGetRaw(requestParameters: ApiAdminArchiveFeaturesProjectIdGetRequest, initOverrides?: RequestInit): Promise> { + async apiAdminArchiveFeaturesProjectIdGetRaw(requestParameters: ApiAdminArchiveFeaturesProjectIdGetRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise> { if (requestParameters.projectId === null || requestParameters.projectId === undefined) { throw new runtime.RequiredError('projectId','Required parameter requestParameters.projectId was null or undefined when calling apiAdminArchiveFeaturesProjectIdGet.'); } @@ -328,56 +319,14 @@ export class AdminApi extends runtime.BaseAPI { /** */ - async apiAdminArchiveFeaturesProjectIdGet(requestParameters: ApiAdminArchiveFeaturesProjectIdGetRequest, initOverrides?: RequestInit): Promise { + async apiAdminArchiveFeaturesProjectIdGet(requestParameters: ApiAdminArchiveFeaturesProjectIdGetRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise { const response = await this.apiAdminArchiveFeaturesProjectIdGetRaw(requestParameters, initOverrides); return await response.value(); } /** */ - async apiAdminProjectsProjectIdFeaturesFeatureNameChangeProjectPostRaw(requestParameters: ApiAdminProjectsProjectIdFeaturesFeatureNameChangeProjectPostRequest, initOverrides?: RequestInit): Promise> { - if (requestParameters.projectId === null || requestParameters.projectId === undefined) { - throw new runtime.RequiredError('projectId','Required parameter requestParameters.projectId was null or undefined when calling apiAdminProjectsProjectIdFeaturesFeatureNameChangeProjectPost.'); - } - - if (requestParameters.featureName === null || requestParameters.featureName === undefined) { - throw new runtime.RequiredError('featureName','Required parameter requestParameters.featureName was null or undefined when calling apiAdminProjectsProjectIdFeaturesFeatureNameChangeProjectPost.'); - } - - if (requestParameters.changeProjectSchema === null || requestParameters.changeProjectSchema === undefined) { - throw new runtime.RequiredError('changeProjectSchema','Required parameter requestParameters.changeProjectSchema was null or undefined when calling apiAdminProjectsProjectIdFeaturesFeatureNameChangeProjectPost.'); - } - - const queryParameters: any = {}; - - const headerParameters: runtime.HTTPHeaders = {}; - - headerParameters['Content-Type'] = 'application/json'; - - if (this.configuration && this.configuration.apiKey) { - headerParameters["Authorization"] = this.configuration.apiKey("Authorization"); // apiKey authentication - } - - const response = await this.request({ - path: `/api/admin/projects/{projectId}/features/{featureName}/changeProject`.replace(`{${"projectId"}}`, encodeURIComponent(String(requestParameters.projectId))).replace(`{${"featureName"}}`, encodeURIComponent(String(requestParameters.featureName))), - method: 'POST', - headers: headerParameters, - query: queryParameters, - body: ChangeProjectSchemaToJSON(requestParameters.changeProjectSchema), - }, initOverrides); - - return new runtime.VoidApiResponse(response); - } - - /** - */ - async apiAdminProjectsProjectIdFeaturesFeatureNameChangeProjectPost(requestParameters: ApiAdminProjectsProjectIdFeaturesFeatureNameChangeProjectPostRequest, initOverrides?: RequestInit): Promise { - await this.apiAdminProjectsProjectIdFeaturesFeatureNameChangeProjectPostRaw(requestParameters, initOverrides); - } - - /** - */ - async archiveFeatureRaw(requestParameters: ArchiveFeatureRequest, initOverrides?: RequestInit): Promise> { + async archiveFeatureRaw(requestParameters: ArchiveFeatureRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise> { if (requestParameters.projectId === null || requestParameters.projectId === undefined) { throw new runtime.RequiredError('projectId','Required parameter requestParameters.projectId was null or undefined when calling archiveFeature.'); } @@ -406,14 +355,14 @@ export class AdminApi extends runtime.BaseAPI { /** */ - async archiveFeature(requestParameters: ArchiveFeatureRequest, initOverrides?: RequestInit): Promise { + async archiveFeature(requestParameters: ArchiveFeatureRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise { const response = await this.archiveFeatureRaw(requestParameters, initOverrides); return await response.value(); } /** */ - async cloneFeatureRaw(requestParameters: CloneFeatureRequest, initOverrides?: RequestInit): Promise> { + async cloneFeatureRaw(requestParameters: CloneFeatureRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise> { if (requestParameters.projectId === null || requestParameters.projectId === undefined) { throw new runtime.RequiredError('projectId','Required parameter requestParameters.projectId was null or undefined when calling cloneFeature.'); } @@ -449,14 +398,14 @@ export class AdminApi extends runtime.BaseAPI { /** */ - async cloneFeature(requestParameters: CloneFeatureRequest, initOverrides?: RequestInit): Promise { + async cloneFeature(requestParameters: CloneFeatureRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise { const response = await this.cloneFeatureRaw(requestParameters, initOverrides); return await response.value(); } /** */ - async createFeatureRaw(requestParameters: CreateFeatureRequest, initOverrides?: RequestInit): Promise> { + async createFeatureRaw(requestParameters: CreateFeatureRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise> { if (requestParameters.projectId === null || requestParameters.projectId === undefined) { throw new runtime.RequiredError('projectId','Required parameter requestParameters.projectId was null or undefined when calling createFeature.'); } @@ -488,14 +437,14 @@ export class AdminApi extends runtime.BaseAPI { /** */ - async createFeature(requestParameters: CreateFeatureRequest, initOverrides?: RequestInit): Promise { + async createFeature(requestParameters: CreateFeatureRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise { const response = await this.createFeatureRaw(requestParameters, initOverrides); return await response.value(); } /** */ - async deleteStrategyRaw(requestParameters: DeleteStrategyRequest, initOverrides?: RequestInit): Promise> { + async deleteStrategyRaw(requestParameters: DeleteStrategyRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise> { if (requestParameters.projectId === null || requestParameters.projectId === undefined) { throw new runtime.RequiredError('projectId','Required parameter requestParameters.projectId was null or undefined when calling deleteStrategy.'); } @@ -532,14 +481,14 @@ export class AdminApi extends runtime.BaseAPI { /** */ - async deleteStrategy(requestParameters: DeleteStrategyRequest, initOverrides?: RequestInit): Promise { + async deleteStrategy(requestParameters: DeleteStrategyRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise { const response = await this.deleteStrategyRaw(requestParameters, initOverrides); return await response.value(); } /** */ - async getAllTogglesRaw(initOverrides?: RequestInit): Promise> { + async getAllTogglesRaw(initOverrides?: RequestInit | runtime.InitOverideFunction): Promise> { const queryParameters: any = {}; const headerParameters: runtime.HTTPHeaders = {}; @@ -560,14 +509,14 @@ export class AdminApi extends runtime.BaseAPI { /** */ - async getAllToggles(initOverrides?: RequestInit): Promise { + async getAllToggles(initOverrides?: RequestInit | runtime.InitOverideFunction): Promise { const response = await this.getAllTogglesRaw(initOverrides); return await response.value(); } /** */ - async getEnvironmentRaw(requestParameters: GetEnvironmentRequest, initOverrides?: RequestInit): Promise> { + async getEnvironmentRaw(requestParameters: GetEnvironmentRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise> { if (requestParameters.projectId === null || requestParameters.projectId === undefined) { throw new runtime.RequiredError('projectId','Required parameter requestParameters.projectId was null or undefined when calling getEnvironment.'); } @@ -600,14 +549,14 @@ export class AdminApi extends runtime.BaseAPI { /** */ - async getEnvironment(requestParameters: GetEnvironmentRequest, initOverrides?: RequestInit): Promise { + async getEnvironment(requestParameters: GetEnvironmentRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise { const response = await this.getEnvironmentRaw(requestParameters, initOverrides); return await response.value(); } /** */ - async getFeatureRaw(requestParameters: GetFeatureRequest, initOverrides?: RequestInit): Promise> { + async getFeatureRaw(requestParameters: GetFeatureRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise> { if (requestParameters.projectId === null || requestParameters.projectId === undefined) { throw new runtime.RequiredError('projectId','Required parameter requestParameters.projectId was null or undefined when calling getFeature.'); } @@ -636,14 +585,14 @@ export class AdminApi extends runtime.BaseAPI { /** */ - async getFeature(requestParameters: GetFeatureRequest, initOverrides?: RequestInit): Promise { + async getFeature(requestParameters: GetFeatureRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise { const response = await this.getFeatureRaw(requestParameters, initOverrides); return await response.value(); } /** */ - async getFeaturesRaw(requestParameters: GetFeaturesRequest, initOverrides?: RequestInit): Promise> { + async getFeaturesRaw(requestParameters: GetFeaturesRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise> { if (requestParameters.projectId === null || requestParameters.projectId === undefined) { throw new runtime.RequiredError('projectId','Required parameter requestParameters.projectId was null or undefined when calling getFeatures.'); } @@ -668,14 +617,14 @@ export class AdminApi extends runtime.BaseAPI { /** */ - async getFeatures(requestParameters: GetFeaturesRequest, initOverrides?: RequestInit): Promise { + async getFeatures(requestParameters: GetFeaturesRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise { const response = await this.getFeaturesRaw(requestParameters, initOverrides); return await response.value(); } /** */ - async getStrategiesRaw(requestParameters: GetStrategiesRequest, initOverrides?: RequestInit): Promise>> { + async getStrategiesRaw(requestParameters: GetStrategiesRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise>> { if (requestParameters.projectId === null || requestParameters.projectId === undefined) { throw new runtime.RequiredError('projectId','Required parameter requestParameters.projectId was null or undefined when calling getStrategies.'); } @@ -708,14 +657,14 @@ export class AdminApi extends runtime.BaseAPI { /** */ - async getStrategies(requestParameters: GetStrategiesRequest, initOverrides?: RequestInit): Promise> { + async getStrategies(requestParameters: GetStrategiesRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise> { const response = await this.getStrategiesRaw(requestParameters, initOverrides); return await response.value(); } /** */ - async getStrategyRaw(requestParameters: GetStrategyRequest, initOverrides?: RequestInit): Promise> { + async getStrategyRaw(requestParameters: GetStrategyRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise> { if (requestParameters.projectId === null || requestParameters.projectId === undefined) { throw new runtime.RequiredError('projectId','Required parameter requestParameters.projectId was null or undefined when calling getStrategy.'); } @@ -752,14 +701,14 @@ export class AdminApi extends runtime.BaseAPI { /** */ - async getStrategy(requestParameters: GetStrategyRequest, initOverrides?: RequestInit): Promise { + async getStrategy(requestParameters: GetStrategyRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise { const response = await this.getStrategyRaw(requestParameters, initOverrides); return await response.value(); } /** */ - async listTagsRaw(requestParameters: ListTagsRequest, initOverrides?: RequestInit): Promise> { + async listTagsRaw(requestParameters: ListTagsRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise> { if (requestParameters.featureName === null || requestParameters.featureName === undefined) { throw new runtime.RequiredError('featureName','Required parameter requestParameters.featureName was null or undefined when calling listTags.'); } @@ -784,14 +733,14 @@ export class AdminApi extends runtime.BaseAPI { /** */ - async listTags(requestParameters: ListTagsRequest, initOverrides?: RequestInit): Promise { + async listTags(requestParameters: ListTagsRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise { const response = await this.listTagsRaw(requestParameters, initOverrides); return await response.value(); } /** */ - async patchFeatureRaw(requestParameters: PatchFeatureRequest, initOverrides?: RequestInit): Promise> { + async patchFeatureRaw(requestParameters: PatchFeatureRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise> { if (requestParameters.projectId === null || requestParameters.projectId === undefined) { throw new runtime.RequiredError('projectId','Required parameter requestParameters.projectId was null or undefined when calling patchFeature.'); } @@ -827,14 +776,14 @@ export class AdminApi extends runtime.BaseAPI { /** */ - async patchFeature(requestParameters: PatchFeatureRequest, initOverrides?: RequestInit): Promise { + async patchFeature(requestParameters: PatchFeatureRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise { const response = await this.patchFeatureRaw(requestParameters, initOverrides); return await response.value(); } /** */ - async patchStrategyRaw(requestParameters: PatchStrategyRequest, initOverrides?: RequestInit): Promise> { + async patchStrategyRaw(requestParameters: PatchStrategyRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise> { if (requestParameters.projectId === null || requestParameters.projectId === undefined) { throw new runtime.RequiredError('projectId','Required parameter requestParameters.projectId was null or undefined when calling patchStrategy.'); } @@ -878,14 +827,14 @@ export class AdminApi extends runtime.BaseAPI { /** */ - async patchStrategy(requestParameters: PatchStrategyRequest, initOverrides?: RequestInit): Promise { + async patchStrategy(requestParameters: PatchStrategyRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise { const response = await this.patchStrategyRaw(requestParameters, initOverrides); return await response.value(); } /** */ - async removeTagRaw(requestParameters: RemoveTagRequest, initOverrides?: RequestInit): Promise> { + async removeTagRaw(requestParameters: RemoveTagRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise> { if (requestParameters.featureName === null || requestParameters.featureName === undefined) { throw new runtime.RequiredError('featureName','Required parameter requestParameters.featureName was null or undefined when calling removeTag.'); } @@ -918,14 +867,14 @@ export class AdminApi extends runtime.BaseAPI { /** */ - async removeTag(requestParameters: RemoveTagRequest, initOverrides?: RequestInit): Promise { + async removeTag(requestParameters: RemoveTagRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise { const response = await this.removeTagRaw(requestParameters, initOverrides); return await response.value(); } /** */ - async toggleEnvironmentOffRaw(requestParameters: ToggleEnvironmentOffRequest, initOverrides?: RequestInit): Promise> { + async toggleEnvironmentOffRaw(requestParameters: ToggleEnvironmentOffRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise> { if (requestParameters.projectId === null || requestParameters.projectId === undefined) { throw new runtime.RequiredError('projectId','Required parameter requestParameters.projectId was null or undefined when calling toggleEnvironmentOff.'); } @@ -958,14 +907,14 @@ export class AdminApi extends runtime.BaseAPI { /** */ - async toggleEnvironmentOff(requestParameters: ToggleEnvironmentOffRequest, initOverrides?: RequestInit): Promise { + async toggleEnvironmentOff(requestParameters: ToggleEnvironmentOffRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise { const response = await this.toggleEnvironmentOffRaw(requestParameters, initOverrides); return await response.value(); } /** */ - async toggleEnvironmentOnRaw(requestParameters: ToggleEnvironmentOnRequest, initOverrides?: RequestInit): Promise> { + async toggleEnvironmentOnRaw(requestParameters: ToggleEnvironmentOnRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise> { if (requestParameters.projectId === null || requestParameters.projectId === undefined) { throw new runtime.RequiredError('projectId','Required parameter requestParameters.projectId was null or undefined when calling toggleEnvironmentOn.'); } @@ -998,14 +947,14 @@ export class AdminApi extends runtime.BaseAPI { /** */ - async toggleEnvironmentOn(requestParameters: ToggleEnvironmentOnRequest, initOverrides?: RequestInit): Promise { + async toggleEnvironmentOn(requestParameters: ToggleEnvironmentOnRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise { const response = await this.toggleEnvironmentOnRaw(requestParameters, initOverrides); return await response.value(); } /** */ - async updateFeatureRaw(requestParameters: UpdateFeatureRequest, initOverrides?: RequestInit): Promise> { + async updateFeatureRaw(requestParameters: UpdateFeatureRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise> { if (requestParameters.projectId === null || requestParameters.projectId === undefined) { throw new runtime.RequiredError('projectId','Required parameter requestParameters.projectId was null or undefined when calling updateFeature.'); } @@ -1041,14 +990,14 @@ export class AdminApi extends runtime.BaseAPI { /** */ - async updateFeature(requestParameters: UpdateFeatureRequest, initOverrides?: RequestInit): Promise { + async updateFeature(requestParameters: UpdateFeatureRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise { const response = await this.updateFeatureRaw(requestParameters, initOverrides); return await response.value(); } /** */ - async updateStrategyRaw(requestParameters: UpdateStrategyRequest, initOverrides?: RequestInit): Promise> { + async updateStrategyRaw(requestParameters: UpdateStrategyRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise> { if (requestParameters.projectId === null || requestParameters.projectId === undefined) { throw new runtime.RequiredError('projectId','Required parameter requestParameters.projectId was null or undefined when calling updateStrategy.'); } @@ -1092,14 +1041,14 @@ export class AdminApi extends runtime.BaseAPI { /** */ - async updateStrategy(requestParameters: UpdateStrategyRequest, initOverrides?: RequestInit): Promise { + async updateStrategy(requestParameters: UpdateStrategyRequest, initOverrides?: RequestInit | runtime.InitOverideFunction): Promise { const response = await this.updateStrategyRaw(requestParameters, initOverrides); return await response.value(); } /** */ - async validateFeatureRaw(initOverrides?: RequestInit): Promise> { + async validateFeatureRaw(initOverrides?: RequestInit | runtime.InitOverideFunction): Promise> { const queryParameters: any = {}; const headerParameters: runtime.HTTPHeaders = {}; @@ -1120,7 +1069,7 @@ export class AdminApi extends runtime.BaseAPI { /** */ - async validateFeature(initOverrides?: RequestInit): Promise { + async validateFeature(initOverrides?: RequestInit | runtime.InitOverideFunction): Promise { const response = await this.validateFeatureRaw(initOverrides); return await response.value(); } diff --git a/frontend/src/openapi/models/ChangeProjectSchema.ts b/frontend/src/openapi/models/ChangeProjectSchema.ts deleted file mode 100644 index 808d15eb7d..0000000000 --- a/frontend/src/openapi/models/ChangeProjectSchema.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * Unleash API - * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) - * - * The version of the OpenAPI document: 4.11.0-beta.2 - * - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - -import { exists, mapValues } from '../runtime'; -/** - * - * @export - * @interface ChangeProjectSchema - */ -export interface ChangeProjectSchema { - /** - * - * @type {string} - * @memberof ChangeProjectSchema - */ - newProjectId: string; -} - -export function ChangeProjectSchemaFromJSON(json: any): ChangeProjectSchema { - return ChangeProjectSchemaFromJSONTyped(json, false); -} - -export function ChangeProjectSchemaFromJSONTyped(json: any, ignoreDiscriminator: boolean): ChangeProjectSchema { - if ((json === undefined) || (json === null)) { - return json; - } - return { - - 'newProjectId': json['newProjectId'], - }; -} - -export function ChangeProjectSchemaToJSON(value?: ChangeProjectSchema | null): any { - if (value === undefined) { - return undefined; - } - if (value === null) { - return null; - } - return { - - 'newProjectId': value.newProjectId, - }; -} - diff --git a/frontend/src/openapi/models/ConstraintSchema.ts b/frontend/src/openapi/models/ConstraintSchema.ts index bdd882f095..7dac0d61c5 100644 --- a/frontend/src/openapi/models/ConstraintSchema.ts +++ b/frontend/src/openapi/models/ConstraintSchema.ts @@ -57,27 +57,29 @@ export interface ConstraintSchema { value?: string; } + /** -* @export -* @enum {string} -*/ -export enum ConstraintSchemaOperatorEnum { - NotIn = 'NOT_IN', - In = 'IN', - StrEndsWith = 'STR_ENDS_WITH', - StrStartsWith = 'STR_STARTS_WITH', - StrContains = 'STR_CONTAINS', - NumEq = 'NUM_EQ', - NumGt = 'NUM_GT', - NumGte = 'NUM_GTE', - NumLt = 'NUM_LT', - NumLte = 'NUM_LTE', - DateAfter = 'DATE_AFTER', - DateBefore = 'DATE_BEFORE', - SemverEq = 'SEMVER_EQ', - SemverGt = 'SEMVER_GT', - SemverLt = 'SEMVER_LT' -} + * @export + */ +export const ConstraintSchemaOperatorEnum = { + NotIn: 'NOT_IN', + In: 'IN', + StrEndsWith: 'STR_ENDS_WITH', + StrStartsWith: 'STR_STARTS_WITH', + StrContains: 'STR_CONTAINS', + NumEq: 'NUM_EQ', + NumGt: 'NUM_GT', + NumGte: 'NUM_GTE', + NumLt: 'NUM_LT', + NumLte: 'NUM_LTE', + DateAfter: 'DATE_AFTER', + DateBefore: 'DATE_BEFORE', + SemverEq: 'SEMVER_EQ', + SemverGt: 'SEMVER_GT', + SemverLt: 'SEMVER_LT' +} as const; +export type ConstraintSchemaOperatorEnum = typeof ConstraintSchemaOperatorEnum[keyof typeof ConstraintSchemaOperatorEnum]; + export function ConstraintSchemaFromJSON(json: any): ConstraintSchema { return ConstraintSchemaFromJSONTyped(json, false); diff --git a/frontend/src/openapi/models/FeatureSchema.ts b/frontend/src/openapi/models/FeatureSchema.ts index 259c087a74..afe005b86a 100644 --- a/frontend/src/openapi/models/FeatureSchema.ts +++ b/frontend/src/openapi/models/FeatureSchema.ts @@ -92,6 +92,12 @@ export interface FeatureSchema { * @memberof FeatureSchema */ createdAt?: Date | null; + /** + * + * @type {Date} + * @memberof FeatureSchema + */ + archivedAt?: Date | null; /** * * @type {Date} @@ -137,6 +143,7 @@ export function FeatureSchemaFromJSONTyped(json: any, ignoreDiscriminator: boole 'stale': !exists(json, 'stale') ? undefined : json['stale'], 'impressionData': !exists(json, 'impressionData') ? undefined : json['impressionData'], 'createdAt': !exists(json, 'createdAt') ? undefined : (json['createdAt'] === null ? null : new Date(json['createdAt'])), + 'archivedAt': !exists(json, 'archivedAt') ? undefined : (json['archivedAt'] === null ? null : new Date(json['archivedAt'])), 'lastSeenAt': !exists(json, 'lastSeenAt') ? undefined : (json['lastSeenAt'] === null ? null : new Date(json['lastSeenAt'])), 'environments': !exists(json, 'environments') ? undefined : ((json['environments'] as Array).map(FeatureEnvironmentSchemaFromJSON)), 'strategies': !exists(json, 'strategies') ? undefined : ((json['strategies'] as Array).map(StrategySchemaFromJSON)), @@ -162,6 +169,7 @@ export function FeatureSchemaToJSON(value?: FeatureSchema | null): any { 'stale': value.stale, 'impressionData': value.impressionData, 'createdAt': value.createdAt === undefined ? undefined : (value.createdAt === null ? null : value.createdAt.toISOString().substr(0,10)), + 'archivedAt': value.archivedAt === undefined ? undefined : (value.archivedAt === null ? null : value.archivedAt.toISOString().substr(0,10)), 'lastSeenAt': value.lastSeenAt === undefined ? undefined : (value.lastSeenAt === null ? null : value.lastSeenAt.toISOString().substr(0,10)), 'environments': value.environments === undefined ? undefined : ((value.environments as Array).map(FeatureEnvironmentSchemaToJSON)), 'strategies': value.strategies === undefined ? undefined : ((value.strategies as Array).map(StrategySchemaToJSON)), diff --git a/frontend/src/openapi/models/PatchOperationSchema.ts b/frontend/src/openapi/models/PatchOperationSchema.ts index 9b52f34d63..44c916f534 100644 --- a/frontend/src/openapi/models/PatchOperationSchema.ts +++ b/frontend/src/openapi/models/PatchOperationSchema.ts @@ -45,17 +45,19 @@ export interface PatchOperationSchema { value?: any | null; } + /** -* @export -* @enum {string} -*/ -export enum PatchOperationSchemaOpEnum { - Add = 'add', - Remove = 'remove', - Replace = 'replace', - Copy = 'copy', - Move = 'move' -} + * @export + */ +export const PatchOperationSchemaOpEnum = { + Add: 'add', + Remove: 'remove', + Replace: 'replace', + Copy: 'copy', + Move: 'move' +} as const; +export type PatchOperationSchemaOpEnum = typeof PatchOperationSchemaOpEnum[keyof typeof PatchOperationSchemaOpEnum]; + export function PatchOperationSchemaFromJSON(json: any): PatchOperationSchema { return PatchOperationSchemaFromJSONTyped(json, false); diff --git a/frontend/src/openapi/models/index.ts b/frontend/src/openapi/models/index.ts index 3acb13a748..81fe38e624 100644 --- a/frontend/src/openapi/models/index.ts +++ b/frontend/src/openapi/models/index.ts @@ -1,6 +1,5 @@ /* tslint:disable */ /* eslint-disable */ -export * from './ChangeProjectSchema'; export * from './CloneFeatureSchema'; export * from './ConstraintSchema'; export * from './CreateFeatureSchema'; diff --git a/frontend/src/openapi/runtime.ts b/frontend/src/openapi/runtime.ts index 2be7bb9eb9..73fce340b5 100644 --- a/frontend/src/openapi/runtime.ts +++ b/frontend/src/openapi/runtime.ts @@ -15,119 +15,6 @@ export const BASE_PATH = "http://localhost:4242".replace(/\/+$/, ""); -const isBlob = (value: any) => typeof Blob !== 'undefined' && value instanceof Blob; - -/** - * This is the base class for all generated API classes. - */ -export class BaseAPI { - - private middleware: Middleware[]; - - constructor(protected configuration = new Configuration()) { - this.middleware = configuration.middleware; - } - - withMiddleware(this: T, ...middlewares: Middleware[]) { - const next = this.clone(); - next.middleware = next.middleware.concat(...middlewares); - return next; - } - - withPreMiddleware(this: T, ...preMiddlewares: Array) { - const middlewares = preMiddlewares.map((pre) => ({ pre })); - return this.withMiddleware(...middlewares); - } - - withPostMiddleware(this: T, ...postMiddlewares: Array) { - const middlewares = postMiddlewares.map((post) => ({ post })); - return this.withMiddleware(...middlewares); - } - - protected async request(context: RequestOpts, initOverrides?: RequestInit): Promise { - const { url, init } = this.createFetchParams(context, initOverrides); - const response = await this.fetchApi(url, init); - if (response.status >= 200 && response.status < 300) { - return response; - } - throw response; - } - - private createFetchParams(context: RequestOpts, initOverrides?: RequestInit) { - let url = this.configuration.basePath + context.path; - if (context.query !== undefined && Object.keys(context.query).length !== 0) { - // only add the querystring to the URL if there are query parameters. - // this is done to avoid urls ending with a "?" character which buggy webservers - // do not handle correctly sometimes. - url += '?' + this.configuration.queryParamsStringify(context.query); - } - const body = ((typeof FormData !== "undefined" && context.body instanceof FormData) || context.body instanceof URLSearchParams || isBlob(context.body)) - ? context.body - : JSON.stringify(context.body); - - const headers = Object.assign({}, this.configuration.headers, context.headers); - const init = { - method: context.method, - headers: headers, - body, - credentials: this.configuration.credentials, - ...initOverrides - }; - return { url, init }; - } - - private fetchApi = async (url: string, init: RequestInit) => { - let fetchParams = { url, init }; - for (const middleware of this.middleware) { - if (middleware.pre) { - fetchParams = await middleware.pre({ - fetch: this.fetchApi, - ...fetchParams, - }) || fetchParams; - } - } - let response = await (this.configuration.fetchApi || fetch)(fetchParams.url, fetchParams.init); - for (const middleware of this.middleware) { - if (middleware.post) { - response = await middleware.post({ - fetch: this.fetchApi, - url: fetchParams.url, - init: fetchParams.init, - response: response.clone(), - }) || response; - } - } - return response; - } - - /** - * Create a shallow clone of `this` by constructing a new instance - * and then shallow cloning data members. - */ - private clone(this: T): T { - const constructor = this.constructor as any; - const next = new constructor(this.configuration); - next.middleware = this.middleware.slice(); - return next; - } -}; - -export class RequiredError extends Error { - name: "RequiredError" = "RequiredError"; - constructor(public field: string, msg?: string) { - super(msg); - } -} - -export const COLLECTION_FORMATS = { - csv: ",", - ssv: " ", - tsv: "\t", - pipes: "|", -}; - -export type FetchAPI = GlobalFetch['fetch']; - export interface ConfigurationParameters { basePath?: string; // override base path fetchApi?: FetchAPI; // override for fetch implementation @@ -144,6 +31,10 @@ export interface ConfigurationParameters { export class Configuration { constructor(private configuration: ConfigurationParameters = {}) {} + set config(configuration: Configuration) { + this.configuration = configuration; + } + get basePath(): string { return this.configuration.basePath != null ? this.configuration.basePath : BASE_PATH; } @@ -193,13 +84,166 @@ export class Configuration { } } +export const DefaultConfig = new Configuration(); + +/** + * This is the base class for all generated API classes. + */ +export class BaseAPI { + + private middleware: Middleware[]; + + constructor(protected configuration = DefaultConfig) { + this.middleware = configuration.middleware; + } + + withMiddleware(this: T, ...middlewares: Middleware[]) { + const next = this.clone(); + next.middleware = next.middleware.concat(...middlewares); + return next; + } + + withPreMiddleware(this: T, ...preMiddlewares: Array) { + const middlewares = preMiddlewares.map((pre) => ({ pre })); + return this.withMiddleware(...middlewares); + } + + withPostMiddleware(this: T, ...postMiddlewares: Array) { + const middlewares = postMiddlewares.map((post) => ({ post })); + return this.withMiddleware(...middlewares); + } + + protected async request(context: RequestOpts, initOverrides?: RequestInit | InitOverideFunction): Promise { + const { url, init } = await this.createFetchParams(context, initOverrides); + const response = await this.fetchApi(url, init); + if (response.status >= 200 && response.status < 300) { + return response; + } + throw new ResponseError(response, 'Response returned an error code'); + } + + private async createFetchParams(context: RequestOpts, initOverrides?: RequestInit | InitOverideFunction) { + let url = this.configuration.basePath + context.path; + if (context.query !== undefined && Object.keys(context.query).length !== 0) { + // only add the querystring to the URL if there are query parameters. + // this is done to avoid urls ending with a "?" character which buggy webservers + // do not handle correctly sometimes. + url += '?' + this.configuration.queryParamsStringify(context.query); + } + + const headers = Object.assign({}, this.configuration.headers, context.headers); + Object.keys(headers).forEach(key => headers[key] === undefined ? delete headers[key] : {}); + + const initOverrideFn = + typeof initOverrides === "function" + ? initOverrides + : async () => initOverrides; + + const initParams = { + method: context.method, + headers, + body: context.body, + credentials: this.configuration.credentials, + }; + + const overridedInit: RequestInit = { + ...initParams, + ...(await initOverrideFn({ + init: initParams, + context, + })) + } + + const init: RequestInit = { + ...overridedInit, + body: + isFormData(overridedInit.body) || + overridedInit.body instanceof URLSearchParams || + isBlob(overridedInit.body) + ? overridedInit.body + : JSON.stringify(overridedInit.body), + }; + + return { url, init }; + } + + private fetchApi = async (url: string, init: RequestInit) => { + let fetchParams = { url, init }; + for (const middleware of this.middleware) { + if (middleware.pre) { + fetchParams = await middleware.pre({ + fetch: this.fetchApi, + ...fetchParams, + }) || fetchParams; + } + } + let response = await (this.configuration.fetchApi || fetch)(fetchParams.url, fetchParams.init); + for (const middleware of this.middleware) { + if (middleware.post) { + response = await middleware.post({ + fetch: this.fetchApi, + url: fetchParams.url, + init: fetchParams.init, + response: response.clone(), + }) || response; + } + } + return response; + } + + /** + * Create a shallow clone of `this` by constructing a new instance + * and then shallow cloning data members. + */ + private clone(this: T): T { + const constructor = this.constructor as any; + const next = new constructor(this.configuration); + next.middleware = this.middleware.slice(); + return next; + } +}; + +function isBlob(value: any): value is Blob { + return typeof Blob !== 'undefined' && value instanceof Blob +} + +function isFormData(value: any): value is FormData { + return typeof FormData !== "undefined" && value instanceof FormData +} + +export class ResponseError extends Error { + name: "ResponseError" = "ResponseError"; + constructor(public response: Response, msg?: string) { + super(msg); + } +} + +export class RequiredError extends Error { + name: "RequiredError" = "RequiredError"; + constructor(public field: string, msg?: string) { + super(msg); + } +} + +export const COLLECTION_FORMATS = { + csv: ",", + ssv: " ", + tsv: "\t", + pipes: "|", +}; + +export type FetchAPI = WindowOrWorkerGlobalScope['fetch']; + export type Json = any; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; -export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | HTTPQuery }; +export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; export type HTTPBody = Json | FormData | URLSearchParams; +export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody } export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; +export type InitOverideFunction = (requestContext: { init: HTTPRequestInit, context: RequestOpts }) => Promise + export interface FetchParams { url: string; init: RequestInit; @@ -220,26 +264,31 @@ export function exists(json: any, key: string) { export function querystring(params: HTTPQuery, prefix: string = ''): string { return Object.keys(params) - .map((key) => { - const fullKey = prefix + (prefix.length ? `[${key}]` : key); - const value = params[key]; - if (value instanceof Array) { - const multiValue = value.map(singleValue => encodeURIComponent(String(singleValue))) - .join(`&${encodeURIComponent(fullKey)}=`); - return `${encodeURIComponent(fullKey)}=${multiValue}`; - } - if (value instanceof Date) { - return `${encodeURIComponent(fullKey)}=${encodeURIComponent(value.toISOString())}`; - } - if (value instanceof Object) { - return querystring(value as HTTPQuery, fullKey); - } - return `${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`; - }) + .map(key => querystringSingleKey(key, params[key], prefix)) .filter(part => part.length > 0) .join('&'); } +function querystringSingleKey(key: string, value: string | number | null | boolean | Array | Set | HTTPQuery, keyPrefix: string = ''): string { + const fullKey = keyPrefix + (keyPrefix.length ? `[${key}]` : key); + if (value instanceof Array) { + const multiValue = value.map(singleValue => encodeURIComponent(String(singleValue))) + .join(`&${encodeURIComponent(fullKey)}=`); + return `${encodeURIComponent(fullKey)}=${multiValue}`; + } + if (value instanceof Set) { + const valueAsArray = Array.from(value); + return querystringSingleKey(key, valueAsArray, keyPrefix); + } + if (value instanceof Date) { + return `${encodeURIComponent(fullKey)}=${encodeURIComponent(value.toISOString())}`; + } + if (value instanceof Object) { + return querystring(value as HTTPQuery, fullKey); + } + return `${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`; +} + export function mapValues(data: any, fn: (item: any) => any) { return Object.keys(data).reduce( (acc, key) => ({ ...acc, [key]: fn(data[key]) }), From 79af4c39e00f2106023aff3ba058566fb5b3a6e3 Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Mon, 6 Jun 2022 09:54:45 +0300 Subject: [PATCH 02/33] Archive table new sort parameter --- .../component/archive/{ArchiveTable => Table}/ArchiveTable.tsx | 0 frontend/src/component/menu/routes.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename frontend/src/component/archive/{ArchiveTable => Table}/ArchiveTable.tsx (100%) diff --git a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx b/frontend/src/component/archive/Table/ArchiveTable.tsx similarity index 100% rename from frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx rename to frontend/src/component/archive/Table/ArchiveTable.tsx diff --git a/frontend/src/component/menu/routes.ts b/frontend/src/component/menu/routes.ts index 848754739a..e417a5bc77 100644 --- a/frontend/src/component/menu/routes.ts +++ b/frontend/src/component/menu/routes.ts @@ -51,7 +51,7 @@ import { IRoute } from 'interfaces/route'; import { EnvironmentTable } from 'component/environments/EnvironmentTable/EnvironmentTable'; import { SegmentTable } from 'component/segments/SegmentTable/SegmentTable'; import RedirectAdminInvoices from 'component/admin/billing/RedirectAdminInvoices/RedirectAdminInvoices'; -import { ArchiveTable } from '../archive/ArchiveTable/ArchiveTable'; +import { ArchiveTable } from '../archive/Table/ArchiveTable'; export const routes: IRoute[] = [ // Splash From dcad6d2b40d94368d78a041a991a32ab11caf601 Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Mon, 6 Jun 2022 10:21:40 +0300 Subject: [PATCH 03/33] Archive table new sort parameter --- .../component/archive/{Table => ArchiveTable}/ArchiveTable.tsx | 0 frontend/src/component/menu/routes.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename frontend/src/component/archive/{Table => ArchiveTable}/ArchiveTable.tsx (100%) diff --git a/frontend/src/component/archive/Table/ArchiveTable.tsx b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx similarity index 100% rename from frontend/src/component/archive/Table/ArchiveTable.tsx rename to frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx diff --git a/frontend/src/component/menu/routes.ts b/frontend/src/component/menu/routes.ts index e417a5bc77..848754739a 100644 --- a/frontend/src/component/menu/routes.ts +++ b/frontend/src/component/menu/routes.ts @@ -51,7 +51,7 @@ import { IRoute } from 'interfaces/route'; import { EnvironmentTable } from 'component/environments/EnvironmentTable/EnvironmentTable'; import { SegmentTable } from 'component/segments/SegmentTable/SegmentTable'; import RedirectAdminInvoices from 'component/admin/billing/RedirectAdminInvoices/RedirectAdminInvoices'; -import { ArchiveTable } from '../archive/Table/ArchiveTable'; +import { ArchiveTable } from '../archive/ArchiveTable/ArchiveTable'; export const routes: IRoute[] = [ // Splash From 406c1873728d3a0dca12f8129774d1688bf1e0fc Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Mon, 6 Jun 2022 12:12:28 +0300 Subject: [PATCH 04/33] Project Archive --- .../archive/ArchiveTable/ArchiveTable.tsx | 205 +++++++++++------- .../archive/FeaturesArchiveTable.tsx | 34 +++ .../archive/ProjectFeaturesArchiveTable.tsx | 40 ++++ frontend/src/component/menu/routes.ts | 4 +- .../ProjectFeaturesArchive.tsx | 7 +- 5 files changed, 201 insertions(+), 89 deletions(-) create mode 100644 frontend/src/component/archive/FeaturesArchiveTable.tsx create mode 100644 frontend/src/component/archive/ProjectFeaturesArchiveTable.tsx diff --git a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx index 2f3139da61..a4fa2cf9c7 100644 --- a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx +++ b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx @@ -10,7 +10,6 @@ import { TableSearch, } from 'component/common/Table'; import { - SortingRule, useFlexLayout, useGlobalFilter, useSortBy, @@ -23,7 +22,6 @@ import { useEffect, useMemo, useState } from 'react'; import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell'; import { DateCell } from 'component/common/Table/cells/DateCell/DateCell'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { useFeaturesArchive } from '../../../hooks/api/getters/useFeaturesArchive/useFeaturesArchive'; import { FeatureTypeCell } from '../../common/Table/cells/FeatureTypeCell/FeatureTypeCell'; import { FeatureSeenCell } from '../../common/Table/cells/FeatureSeenCell/FeatureSeenCell'; import { LinkCell } from '../../common/Table/cells/LinkCell/LinkCell'; @@ -31,28 +29,61 @@ import { FeatureStaleCell } from '../../feature/FeatureToggleList/FeatureStaleCe import { TimeAgoCell } from '../../common/Table/cells/TimeAgoCell/TimeAgoCell'; import { ReviveArchivedFeatureCell } from 'component/common/Table/cells/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell'; import { useStyles } from '../../feature/FeatureToggleList/styles'; -import { useSearchParams } from 'react-router-dom'; -import { useLocalStorage } from '../../../hooks/useLocalStorage'; import { useVirtualizedRange } from '../../../hooks/useVirtualizedRange'; import { featuresPlaceholder, PageQueryType, } from '../../feature/FeatureToggleList/FeatureToggleListTable'; import theme from 'themes/theme'; +import { FeatureSchema } from '../../../openapi'; +import { useFeatureArchiveApi } from '../../../hooks/api/actions/useFeatureArchiveApi/useReviveFeatureApi'; +import useToast from '../../../hooks/useToast'; -const defaultSort: SortingRule = { id: 'createdAt', desc: true }; +export interface IFeaturesArchiveTableProps { + archivedFeatures: FeatureSchema[]; + refetch: any; + loading: boolean; + inProject: boolean; + storedParams: any; + setStoredParams: any; + searchParams: any; + setSearchParams: any; +} -export const ArchiveTable = () => { +export const ArchiveTable = ({ + archivedFeatures = [], + loading, + inProject, + refetch, + storedParams, + setStoredParams, + searchParams, + setSearchParams, +}: IFeaturesArchiveTableProps) => { const rowHeight = theme.shape.tableRowHeight; const { classes } = useStyles(); const isSmallScreen = useMediaQuery(theme.breakpoints.down('md')); const isMediumScreen = useMediaQuery(theme.breakpoints.down('lg')); - const [searchParams, setSearchParams] = useSearchParams(); - const [storedParams, setStoredParams] = useLocalStorage( - 'ArchiveTable:v1', - defaultSort - ); - const { archivedFeatures = [], loading } = useFeaturesArchive(); + const { setToastData, setToastApiError } = useToast(); + + const { reviveFeature } = useFeatureArchiveApi(); + + const onRevive = (feature: string) => { + reviveFeature(feature) + .then(refetch) + .then(() => + setToastData({ + type: 'success', + title: "And we're back!", + text: 'The feature toggle has been revived.', + confetti: true, + }) + ) + .catch(e => setToastApiError(e.toString())); + }; + + const columns = useColumns(onRevive); + const data = useMemo( () => archivedFeatures?.length === 0 && loading @@ -85,7 +116,7 @@ export const ArchiveTable = () => { setHiddenColumns, } = useTable( { - columns: COLUMNS as any, + columns: columns as any, data: data as any, initialState, sortTypes, @@ -134,7 +165,9 @@ export const ArchiveTable = () => { isLoading={loading} header={ { ); }; -const COLUMNS = [ - { - id: 'Seen', - Header: 'Seen', - maxWidth: 85, - canSort: true, - Cell: FeatureSeenCell, - disableGlobalFilter: true, - }, - { - id: 'Type', - Header: 'Type', - maxWidth: 85, - canSort: true, - Cell: FeatureTypeCell, - disableGlobalFilter: true, - }, - { - Header: 'Feature toggle Name', - accessor: 'name', - maxWidth: 150, - Cell: ({ value, row: { original } }: any) => ( - - ), - sortType: 'alphanumeric', - }, - { - Header: 'Created', - accessor: 'createdAt', - maxWidth: 150, - Cell: DateCell, - sortType: 'date', - disableGlobalFilter: true, - }, - { - Header: 'Archived', - accessor: 'archivedAt', - maxWidth: 150, - Cell: TimeAgoCell, - sortType: 'date', - disableGlobalFilter: true, - }, - { - Header: 'Project ID', - accessor: 'project', - sortType: 'alphanumeric', - maxWidth: 150, - Cell: ({ value }: any) => ( - - ), - }, - { - Header: 'Status', - accessor: 'stale', - Cell: FeatureStaleCell, - sortType: 'boolean', - maxWidth: 120, - disableGlobalFilter: true, - }, - { - Header: 'Actions', - id: 'Actions', - align: 'center', - maxWidth: 85, - canSort: false, - disableGlobalFilter: true, - Cell: ReviveArchivedFeatureCell, - }, -]; +const useColumns = (onRevive: any) => { + return [ + { + id: 'Seen', + Header: 'Seen', + maxWidth: 85, + canSort: true, + Cell: FeatureSeenCell, + disableGlobalFilter: true, + }, + { + id: 'Type', + Header: 'Type', + maxWidth: 85, + canSort: true, + Cell: FeatureTypeCell, + disableGlobalFilter: true, + }, + { + Header: 'Feature toggle Name', + accessor: 'name', + maxWidth: 150, + Cell: ({ value, row: { original } }: any) => ( + + ), + sortType: 'alphanumeric', + }, + { + Header: 'Created', + accessor: 'createdAt', + maxWidth: 150, + Cell: DateCell, + sortType: 'date', + disableGlobalFilter: true, + }, + { + Header: 'Archived', + accessor: 'archivedAt', + maxWidth: 150, + Cell: TimeAgoCell, + sortType: 'date', + disableGlobalFilter: true, + }, + { + Header: 'Project ID', + accessor: 'project', + sortType: 'alphanumeric', + maxWidth: 150, + Cell: ({ value }: any) => ( + + ), + }, + { + Header: 'Status', + accessor: 'stale', + Cell: FeatureStaleCell, + sortType: 'boolean', + maxWidth: 120, + disableGlobalFilter: true, + }, + { + Header: 'Actions', + id: 'Actions', + align: 'center', + maxWidth: 85, + canSort: false, + disableGlobalFilter: true, + Cell: ({ row: { original } }: any) => ( + onRevive(original.name)} + /> + ), + }, + ]; +}; diff --git a/frontend/src/component/archive/FeaturesArchiveTable.tsx b/frontend/src/component/archive/FeaturesArchiveTable.tsx new file mode 100644 index 0000000000..d728ca9c70 --- /dev/null +++ b/frontend/src/component/archive/FeaturesArchiveTable.tsx @@ -0,0 +1,34 @@ +import { useFeaturesArchive } from '../../hooks/api/getters/useFeaturesArchive/useFeaturesArchive'; +import { ArchiveTable } from './ArchiveTable/ArchiveTable'; +import { useSearchParams } from 'react-router-dom'; +import { useLocalStorage } from '../../hooks/useLocalStorage'; +import { SortingRule } from 'react-table'; + +const defaultSort: SortingRule = { id: 'createdAt', desc: true }; + +export const FeaturesArchiveTable = () => { + const { + archivedFeatures = [], + loading, + refetchArchived, + } = useFeaturesArchive(); + + const [searchParams, setSearchParams] = useSearchParams(); + const [storedParams, setStoredParams] = useLocalStorage( + 'FeaturesArchiveTable:v1', + defaultSort + ); + + return ( + + ); +}; diff --git a/frontend/src/component/archive/ProjectFeaturesArchiveTable.tsx b/frontend/src/component/archive/ProjectFeaturesArchiveTable.tsx new file mode 100644 index 0000000000..9b196d3c26 --- /dev/null +++ b/frontend/src/component/archive/ProjectFeaturesArchiveTable.tsx @@ -0,0 +1,40 @@ +import { ArchiveTable } from './ArchiveTable/ArchiveTable'; +import { useSearchParams } from 'react-router-dom'; +import { useLocalStorage } from '../../hooks/useLocalStorage'; +import { SortingRule } from 'react-table'; +import { useProjectFeaturesArchive } from '../../hooks/api/getters/useProjectFeaturesArchive/useProjectFeaturesArchive'; + +const defaultSort: SortingRule = { id: 'archivedAt', desc: true }; + +interface IProjectFeaturesTable { + projectId: string; +} + +export const ProjectFeaturesArchiveTable = ({ + projectId, +}: IProjectFeaturesTable) => { + const { + archivedFeatures = [], + refetchArchived, + loading, + } = useProjectFeaturesArchive(projectId); + + const [searchParams, setSearchParams] = useSearchParams(); + const [storedParams, setStoredParams] = useLocalStorage( + 'ProjectFeaturesArchiveTable:v1', + defaultSort + ); + + return ( + + ); +}; diff --git a/frontend/src/component/menu/routes.ts b/frontend/src/component/menu/routes.ts index 848754739a..eda5e0027b 100644 --- a/frontend/src/component/menu/routes.ts +++ b/frontend/src/component/menu/routes.ts @@ -51,7 +51,7 @@ import { IRoute } from 'interfaces/route'; import { EnvironmentTable } from 'component/environments/EnvironmentTable/EnvironmentTable'; import { SegmentTable } from 'component/segments/SegmentTable/SegmentTable'; import RedirectAdminInvoices from 'component/admin/billing/RedirectAdminInvoices/RedirectAdminInvoices'; -import { ArchiveTable } from '../archive/ArchiveTable/ArchiveTable'; +import { FeaturesArchiveTable } from '../archive/FeaturesArchiveTable'; export const routes: IRoute[] = [ // Splash @@ -375,7 +375,7 @@ export const routes: IRoute[] = [ { path: '/archive', title: 'Archived toggles', - component: ArchiveTable, + component: FeaturesArchiveTable, type: 'protected', menu: {}, }, diff --git a/frontend/src/component/project/Project/ProjectFeaturesArchive/ProjectFeaturesArchive.tsx b/frontend/src/component/project/Project/ProjectFeaturesArchive/ProjectFeaturesArchive.tsx index 7603c9445e..7685944eea 100644 --- a/frontend/src/component/project/Project/ProjectFeaturesArchive/ProjectFeaturesArchive.tsx +++ b/frontend/src/component/project/Project/ProjectFeaturesArchive/ProjectFeaturesArchive.tsx @@ -1,5 +1,4 @@ -import { ProjectFeaturesArchiveList } from 'component/archive/ProjectFeaturesArchiveList'; -import { usePageTitle } from 'hooks/usePageTitle'; +import { ProjectFeaturesArchiveTable } from '../../../archive/ProjectFeaturesArchiveTable'; interface IProjectFeaturesArchiveProps { projectId: string; @@ -8,7 +7,7 @@ interface IProjectFeaturesArchiveProps { export const ProjectFeaturesArchive = ({ projectId, }: IProjectFeaturesArchiveProps) => { - usePageTitle('Project Archived Features'); + // usePageTitle('Project Archived Features'); - return ; + return ; }; From dd9a291e090decf25b0fd31c064681c67081d369 Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Tue, 7 Jun 2022 11:54:16 +0300 Subject: [PATCH 05/33] Fix PR comments --- .../archive/ArchiveTable/ArchiveTable.tsx | 103 +++++++++--------- .../ReviveArchivedFeatureCell.tsx | 2 +- .../archive/FeaturesArchiveTable.tsx | 2 + .../ProjectFeaturesArchive.tsx | 3 +- 4 files changed, 58 insertions(+), 52 deletions(-) rename frontend/src/component/{common/Table/cells => archive/ArchiveTable}/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx (84%) diff --git a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx index a4fa2cf9c7..8506d4a7ec 100644 --- a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx +++ b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx @@ -27,7 +27,7 @@ import { FeatureSeenCell } from '../../common/Table/cells/FeatureSeenCell/Featur import { LinkCell } from '../../common/Table/cells/LinkCell/LinkCell'; import { FeatureStaleCell } from '../../feature/FeatureToggleList/FeatureStaleCell/FeatureStaleCell'; import { TimeAgoCell } from '../../common/Table/cells/TimeAgoCell/TimeAgoCell'; -import { ReviveArchivedFeatureCell } from 'component/common/Table/cells/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell'; +import { ReviveArchivedFeatureCell } from 'component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell'; import { useStyles } from '../../feature/FeatureToggleList/styles'; import { useVirtualizedRange } from '../../../hooks/useVirtualizedRange'; import { @@ -68,21 +68,22 @@ export const ArchiveTable = ({ const { reviveFeature } = useFeatureArchiveApi(); - const onRevive = (feature: string) => { - reviveFeature(feature) - .then(refetch) - .then(() => - setToastData({ - type: 'success', - title: "And we're back!", - text: 'The feature toggle has been revived.', - confetti: true, - }) - ) - .catch(e => setToastApiError(e.toString())); + const onRevive = async (feature: string) => { + try { + await reviveFeature(feature); + await refetch(); + setToastData({ + type: 'success', + title: "And we're back!", + text: 'The feature toggle has been revived.', + confetti: true, + }); + } catch (e: any) { + setToastApiError(e.toString()); + } }; - const columns = useColumns(onRevive); + const columns = getColumns(onRevive); const data = useMemo( () => @@ -160,6 +161,41 @@ export const ArchiveTable = ({ const [firstRenderedIndex, lastRenderedIndex] = useVirtualizedRange(rowHeight); + const renderRows = () => { + return ( + <> + {rows.map((row, index) => { + const isVirtual = + index < firstRenderedIndex || index > lastRenderedIndex; + + if (isVirtual) { + return null; + } + + prepareRow(row); + return ( + + {row.cells.map(cell => ( + + {cell.render('Cell')} + + ))} + + ); + })} + + ); + }; + return ( - {rows.map((row, index) => { - const isVirtual = - index < firstRenderedIndex || - index > lastRenderedIndex; - - if (isVirtual) { - return null; - } - - prepareRow(row); - return ( - - {row.cells.map(cell => ( - - {cell.render('Cell')} - - ))} - - ); - })} + {renderRows()} @@ -249,7 +252,7 @@ export const ArchiveTable = ({ ); }; -const useColumns = (onRevive: any) => { +function getColumns(onRevive: (feature: string) => Promise) { return [ { id: 'Seen', @@ -323,4 +326,4 @@ const useColumns = (onRevive: any) => { ), }, ]; -}; +} diff --git a/frontend/src/component/common/Table/cells/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx b/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx similarity index 84% rename from frontend/src/component/common/Table/cells/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx rename to frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx index 4c062d0e03..e8271a1c77 100644 --- a/frontend/src/component/common/Table/cells/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx +++ b/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx @@ -1,5 +1,5 @@ import { VFC } from 'react'; -import { ActionCell } from '../ActionCell/ActionCell'; +import { ActionCell } from '../../../common/Table/cells/ActionCell/ActionCell'; import { Undo } from '@mui/icons-material'; import { IconButton } from '@mui/material'; diff --git a/frontend/src/component/archive/FeaturesArchiveTable.tsx b/frontend/src/component/archive/FeaturesArchiveTable.tsx index d728ca9c70..e7a4d55bae 100644 --- a/frontend/src/component/archive/FeaturesArchiveTable.tsx +++ b/frontend/src/component/archive/FeaturesArchiveTable.tsx @@ -3,10 +3,12 @@ import { ArchiveTable } from './ArchiveTable/ArchiveTable'; import { useSearchParams } from 'react-router-dom'; import { useLocalStorage } from '../../hooks/useLocalStorage'; import { SortingRule } from 'react-table'; +import { usePageTitle } from '../../hooks/usePageTitle'; const defaultSort: SortingRule = { id: 'createdAt', desc: true }; export const FeaturesArchiveTable = () => { + usePageTitle('Archived'); const { archivedFeatures = [], loading, diff --git a/frontend/src/component/project/Project/ProjectFeaturesArchive/ProjectFeaturesArchive.tsx b/frontend/src/component/project/Project/ProjectFeaturesArchive/ProjectFeaturesArchive.tsx index 7685944eea..4dff9aedcd 100644 --- a/frontend/src/component/project/Project/ProjectFeaturesArchive/ProjectFeaturesArchive.tsx +++ b/frontend/src/component/project/Project/ProjectFeaturesArchive/ProjectFeaturesArchive.tsx @@ -1,4 +1,5 @@ import { ProjectFeaturesArchiveTable } from '../../../archive/ProjectFeaturesArchiveTable'; +import { usePageTitle } from '../../../../hooks/usePageTitle'; interface IProjectFeaturesArchiveProps { projectId: string; @@ -7,7 +8,7 @@ interface IProjectFeaturesArchiveProps { export const ProjectFeaturesArchive = ({ projectId, }: IProjectFeaturesArchiveProps) => { - // usePageTitle('Project Archived Features'); + usePageTitle('Project Archived Features'); return ; }; From 63f147b49237d56134b8f933deaa50e78d5d27e4 Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Tue, 7 Jun 2022 13:23:15 +0300 Subject: [PATCH 06/33] Fix PR comments --- .../component/archive/ArchiveTable/ArchiveTable.tsx | 9 ++++----- .../ReviveArchivedFeatureCell.tsx | 13 ++++++++++--- .../src/component/archive/FeaturesArchiveTable.tsx | 2 +- .../archive/ProjectFeaturesArchiveTable.tsx | 2 +- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx index 8506d4a7ec..b76f016a0a 100644 --- a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx +++ b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx @@ -43,22 +43,22 @@ export interface IFeaturesArchiveTableProps { archivedFeatures: FeatureSchema[]; refetch: any; loading: boolean; - inProject: boolean; storedParams: any; setStoredParams: any; searchParams: any; setSearchParams: any; + title: string; } export const ArchiveTable = ({ archivedFeatures = [], loading, - inProject, refetch, storedParams, setStoredParams, searchParams, setSearchParams, + title, }: IFeaturesArchiveTableProps) => { const rowHeight = theme.shape.tableRowHeight; const { classes } = useStyles(); @@ -201,9 +201,7 @@ export const ArchiveTable = ({ isLoading={loading} header={ Promise) { disableGlobalFilter: true, Cell: ({ row: { original } }: any) => ( onRevive(original.name)} /> ), diff --git a/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx b/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx index e8271a1c77..ed6f6f4f58 100644 --- a/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx +++ b/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx @@ -1,20 +1,27 @@ import { VFC } from 'react'; import { ActionCell } from '../../../common/Table/cells/ActionCell/ActionCell'; import { Undo } from '@mui/icons-material'; -import { IconButton } from '@mui/material'; +import PermissionIconButton from '../../../common/PermissionIconButton/PermissionIconButton'; +import { ADMIN } from '../../../providers/AccessProvider/permissions'; interface IReviveArchivedFeatureCell { onRevive: any; + project: string; } export const ReviveArchivedFeatureCell: VFC = ({ onRevive, + project, }) => { return ( - + - + ); }; diff --git a/frontend/src/component/archive/FeaturesArchiveTable.tsx b/frontend/src/component/archive/FeaturesArchiveTable.tsx index e7a4d55bae..00ec6242a9 100644 --- a/frontend/src/component/archive/FeaturesArchiveTable.tsx +++ b/frontend/src/component/archive/FeaturesArchiveTable.tsx @@ -23,6 +23,7 @@ export const FeaturesArchiveTable = () => { return ( { storedParams={storedParams} setStoredParams={setStoredParams} refetch={refetchArchived} - inProject={false} /> ); }; diff --git a/frontend/src/component/archive/ProjectFeaturesArchiveTable.tsx b/frontend/src/component/archive/ProjectFeaturesArchiveTable.tsx index 9b196d3c26..12e5fc0bef 100644 --- a/frontend/src/component/archive/ProjectFeaturesArchiveTable.tsx +++ b/frontend/src/component/archive/ProjectFeaturesArchiveTable.tsx @@ -27,6 +27,7 @@ export const ProjectFeaturesArchiveTable = ({ return ( ); }; From fa0c32a478c8399c97213fc7669f02b1559127e6 Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Tue, 7 Jun 2022 13:24:58 +0300 Subject: [PATCH 07/33] Fix PR comments --- frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx index b76f016a0a..43b3ac47b6 100644 --- a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx +++ b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx @@ -38,6 +38,7 @@ import theme from 'themes/theme'; import { FeatureSchema } from '../../../openapi'; import { useFeatureArchiveApi } from '../../../hooks/api/actions/useFeatureArchiveApi/useReviveFeatureApi'; import useToast from '../../../hooks/useToast'; +import {formatUnknownError} from "../../../utils/formatUnknownError"; export interface IFeaturesArchiveTableProps { archivedFeatures: FeatureSchema[]; @@ -78,8 +79,8 @@ export const ArchiveTable = ({ text: 'The feature toggle has been revived.', confetti: true, }); - } catch (e: any) { - setToastApiError(e.toString()); + } catch (e: unknown) { + setToastApiError(formatUnknownError(e)); } }; From fdf7ef8fb2ae21972f88fe17c6f2a6c184ed77ff Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Tue, 7 Jun 2022 13:27:05 +0300 Subject: [PATCH 08/33] Fix PR comments --- frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx index 43b3ac47b6..4908017b7b 100644 --- a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx +++ b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx @@ -251,7 +251,7 @@ export const ArchiveTable = ({ ); }; -function getColumns(onRevive: (feature: string) => Promise) { +const getColumns = (onRevive: (feature: string) => Promise) => { return [ { id: 'Seen', From de2a3037f577da33cc85245538eb7e564fbf17d1 Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Tue, 7 Jun 2022 13:28:22 +0300 Subject: [PATCH 09/33] Fix PR comments --- .../ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx b/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx index ed6f6f4f58..44c16442b5 100644 --- a/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx +++ b/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx @@ -5,7 +5,7 @@ import PermissionIconButton from '../../../common/PermissionIconButton/Permissio import { ADMIN } from '../../../providers/AccessProvider/permissions'; interface IReviveArchivedFeatureCell { - onRevive: any; + onRevive: (featureName: string) => Promise; project: string; } From 1922c26f1144427fd64bbc97ef4011d4022b33d9 Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Tue, 7 Jun 2022 14:06:58 +0300 Subject: [PATCH 10/33] bug fix --- .../ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx b/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx index 44c16442b5..54e1b7a48e 100644 --- a/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx +++ b/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx @@ -1,11 +1,11 @@ -import { VFC } from 'react'; +import {SyntheticEvent, VFC} from 'react'; import { ActionCell } from '../../../common/Table/cells/ActionCell/ActionCell'; import { Undo } from '@mui/icons-material'; import PermissionIconButton from '../../../common/PermissionIconButton/PermissionIconButton'; import { ADMIN } from '../../../providers/AccessProvider/permissions'; interface IReviveArchivedFeatureCell { - onRevive: (featureName: string) => Promise; + onRevive: (event: SyntheticEvent) => Promise; project: string; } From 1b1f21caebaa25874ac7f1725034b85b37323e65 Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Tue, 7 Jun 2022 14:16:56 +0300 Subject: [PATCH 11/33] bug fix --- .../ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx b/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx index 54e1b7a48e..7aafafd5e2 100644 --- a/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx +++ b/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx @@ -5,7 +5,7 @@ import PermissionIconButton from '../../../common/PermissionIconButton/Permissio import { ADMIN } from '../../../providers/AccessProvider/permissions'; interface IReviveArchivedFeatureCell { - onRevive: (event: SyntheticEvent) => Promise; + onRevive: (event: SyntheticEvent) => void; project: string; } From 539aa89639f01af771ea72dca8f4112cc155f3fe Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Tue, 7 Jun 2022 14:20:25 +0300 Subject: [PATCH 12/33] lint fix --- frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx index 4908017b7b..eb6f08e75c 100644 --- a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx +++ b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx @@ -38,7 +38,7 @@ import theme from 'themes/theme'; import { FeatureSchema } from '../../../openapi'; import { useFeatureArchiveApi } from '../../../hooks/api/actions/useFeatureArchiveApi/useReviveFeatureApi'; import useToast from '../../../hooks/useToast'; -import {formatUnknownError} from "../../../utils/formatUnknownError"; +import { formatUnknownError } from '../../../utils/formatUnknownError'; export interface IFeaturesArchiveTableProps { archivedFeatures: FeatureSchema[]; @@ -326,4 +326,4 @@ const getColumns = (onRevive: (feature: string) => Promise) => { ), }, ]; -} +}; From de51478e192175b35aa01165902784046490d8f3 Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Tue, 7 Jun 2022 14:21:36 +0300 Subject: [PATCH 13/33] PR comment --- .../ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx b/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx index 7aafafd5e2..0f46b1da3a 100644 --- a/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx +++ b/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx @@ -1,4 +1,4 @@ -import {SyntheticEvent, VFC} from 'react'; +import { SyntheticEvent, VFC } from 'react'; import { ActionCell } from '../../../common/Table/cells/ActionCell/ActionCell'; import { Undo } from '@mui/icons-material'; import PermissionIconButton from '../../../common/PermissionIconButton/PermissionIconButton'; @@ -19,6 +19,7 @@ export const ReviveArchivedFeatureCell: VFC = ({ onClick={onRevive} projectId={project} permission={ADMIN} + tooltipProps={{ title: 'Revive feature' }} > From c26bfcf9dafef54b48c75ee2931562af225aae8c Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Tue, 7 Jun 2022 14:22:51 +0300 Subject: [PATCH 14/33] PR comment --- .../ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx b/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx index 0f46b1da3a..9221018389 100644 --- a/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx +++ b/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx @@ -2,7 +2,7 @@ import { SyntheticEvent, VFC } from 'react'; import { ActionCell } from '../../../common/Table/cells/ActionCell/ActionCell'; import { Undo } from '@mui/icons-material'; import PermissionIconButton from '../../../common/PermissionIconButton/PermissionIconButton'; -import { ADMIN } from '../../../providers/AccessProvider/permissions'; +import { UPDATE_FEATURE } from '../../../providers/AccessProvider/permissions'; interface IReviveArchivedFeatureCell { onRevive: (event: SyntheticEvent) => void; @@ -18,7 +18,7 @@ export const ReviveArchivedFeatureCell: VFC = ({ From 854ffaf0b2f3a82808278e65997ba6c029edefbd Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Wed, 8 Jun 2022 13:26:41 +0300 Subject: [PATCH 15/33] PR comments and tidying up --- .../archive/ArchiveTable/ArchiveTable.tsx | 258 +++++++++--------- .../ReviveArchivedFeatureCell.tsx | 9 +- .../archive/ProjectFeaturesArchiveTable.tsx | 2 +- .../FeatureArchivedCell.tsx | 33 +++ 4 files changed, 165 insertions(+), 137 deletions(-) create mode 100644 frontend/src/component/common/Table/cells/FeatureArchivedCell/FeatureArchivedCell.tsx diff --git a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx index eb6f08e75c..e05aa3b5b9 100644 --- a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx +++ b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx @@ -9,16 +9,11 @@ import { TableRow, TableSearch, } from 'component/common/Table'; -import { - useFlexLayout, - useGlobalFilter, - useSortBy, - useTable, -} from 'react-table'; +import { useFlexLayout, useSortBy, useTable } from 'react-table'; import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; import { useMediaQuery } from '@mui/material'; import { sortTypes } from 'utils/sortTypes'; -import { useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell'; import { DateCell } from 'component/common/Table/cells/DateCell/DateCell'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; @@ -26,29 +21,26 @@ import { FeatureTypeCell } from '../../common/Table/cells/FeatureTypeCell/Featur import { FeatureSeenCell } from '../../common/Table/cells/FeatureSeenCell/FeatureSeenCell'; import { LinkCell } from '../../common/Table/cells/LinkCell/LinkCell'; import { FeatureStaleCell } from '../../feature/FeatureToggleList/FeatureStaleCell/FeatureStaleCell'; -import { TimeAgoCell } from '../../common/Table/cells/TimeAgoCell/TimeAgoCell'; import { ReviveArchivedFeatureCell } from 'component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell'; import { useStyles } from '../../feature/FeatureToggleList/styles'; -import { useVirtualizedRange } from '../../../hooks/useVirtualizedRange'; -import { - featuresPlaceholder, - PageQueryType, -} from '../../feature/FeatureToggleList/FeatureToggleListTable'; +import { featuresPlaceholder } from '../../feature/FeatureToggleList/FeatureToggleListTable'; import theme from 'themes/theme'; import { FeatureSchema } from '../../../openapi'; import { useFeatureArchiveApi } from '../../../hooks/api/actions/useFeatureArchiveApi/useReviveFeatureApi'; import useToast from '../../../hooks/useToast'; import { formatUnknownError } from '../../../utils/formatUnknownError'; +import { useSearch } from '../../../hooks/useSearch'; +import { FeatureArchivedCell } from '../../common/Table/cells/FeatureArchivedCell/FeatureArchivedCell'; export interface IFeaturesArchiveTableProps { archivedFeatures: FeatureSchema[]; + title: string; refetch: any; loading: boolean; storedParams: any; setStoredParams: any; searchParams: any; setSearchParams: any; - title: string; } export const ArchiveTable = ({ @@ -69,7 +61,11 @@ export const ArchiveTable = ({ const { reviveFeature } = useFeatureArchiveApi(); - const onRevive = async (feature: string) => { + const [searchValue, setSearchValue] = useState( + searchParams.get('search') || '' + ); + + const onRevive = useCallback(async (feature: string) => { try { await reviveFeature(feature); await refetch(); @@ -82,16 +78,95 @@ export const ArchiveTable = ({ } catch (e: unknown) { setToastApiError(formatUnknownError(e)); } - }; + }, []); - const columns = getColumns(onRevive); + const columns = useMemo( + () => [ + { + id: 'Seen', + Header: 'Seen', + maxWidth: 85, + canSort: true, + Cell: FeatureSeenCell, + accessor: 'lastSeenAt', + }, + { + id: 'Type', + Header: 'Type', + maxWidth: 80, + canSort: true, + Cell: FeatureTypeCell, + }, + { + Header: 'Feature toggle Name', + accessor: 'name', + minWidth: 100, + Cell: ({ value, row: { original } }: any) => ( + + ), + sortType: 'alphanumeric', + }, + { + Header: 'Created', + accessor: 'createdAt', + minWidth: 120, + Cell: DateCell, + sortType: 'date', + }, + { + Header: 'Archived', + accessor: 'archivedAt', + minWidth: 120, + Cell: FeatureArchivedCell, + sortType: 'date', + }, + { + Header: 'Project ID', + accessor: 'project', + sortType: 'alphanumeric', + maxWidth: 150, + Cell: ({ value }: any) => ( + + ), + }, + { + Header: 'Status', + accessor: 'stale', + Cell: FeatureStaleCell, + sortType: 'boolean', + maxWidth: 120, + disableGlobalFilter: true, + }, + { + Header: 'Actions', + id: 'Actions', + align: 'center', + maxWidth: 85, + canSort: false, + disableGlobalFilter: true, + Cell: ({ row: { original } }: any) => ( + onRevive(original.name)} + /> + ), + }, + ], + [] + ); + + const { + data: searchedData, + getSearchText, + getSearchContext, + } = useSearch(columns, searchValue, archivedFeatures); const data = useMemo( - () => - archivedFeatures?.length === 0 && loading - ? featuresPlaceholder - : archivedFeatures, - [archivedFeatures, loading] + () => (loading ? featuresPlaceholder : searchedData), + [searchedData, loading] ); const [initialState] = useState(() => ({ @@ -104,32 +179,27 @@ export const ArchiveTable = ({ }, ], hiddenColumns: ['description'], - globalFilter: searchParams.get('search') || '', })); const { - getTableProps, - getTableBodyProps, headerGroups, rows, + state: { sortBy }, + getTableBodyProps, + getTableProps, prepareRow, - state: { globalFilter, sortBy }, - setGlobalFilter, setHiddenColumns, } = useTable( { - columns: columns as any, - data: data as any, + columns: columns as any[], // TODO: fix after `react-table` v8 update + data, initialState, sortTypes, - autoResetGlobalFilter: false, - autoResetSortBy: false, disableSortRemove: true, - disableMultiSort: true, + autoResetSortBy: false, }, - useGlobalFilter, - useSortBy, - useFlexLayout + useFlexLayout, + useSortBy ); useEffect(() => { @@ -144,35 +214,28 @@ export const ArchiveTable = ({ }, [setHiddenColumns, isSmallScreen, isMediumScreen]); useEffect(() => { - const tableState: PageQueryType = {}; + if (loading) { + return; + } + const tableState: Record = {}; tableState.sort = sortBy[0].id; if (sortBy[0].desc) { tableState.order = 'desc'; } - if (globalFilter) { - tableState.search = globalFilter; + if (searchValue) { + tableState.search = searchValue; } setSearchParams(tableState, { replace: true, }); setStoredParams({ id: sortBy[0].id, desc: sortBy[0].desc || false }); - }, [sortBy, globalFilter, setSearchParams, setStoredParams]); - - const [firstRenderedIndex, lastRenderedIndex] = - useVirtualizedRange(rowHeight); + }, [loading, sortBy, searchValue, setSearchParams, setStoredParams]); const renderRows = () => { return ( <> - {rows.map((row, index) => { - const isVirtual = - index < firstRenderedIndex || index > lastRenderedIndex; - - if (isVirtual) { - return null; - } - + {rows.map((row) => { prepareRow(row); return ( @@ -210,8 +273,10 @@ export const ArchiveTable = ({ actions={ <> } @@ -223,8 +288,10 @@ export const ArchiveTable = ({ show={} elseShow={() => ( <> - - + +
@@ -235,12 +302,12 @@ export const ArchiveTable = ({ 0 + rows.length === 0 && searchValue?.length > 0 } show={ - No features found matching “ - {globalFilter}” + No feature toggles found matching “ + {searchValue}” } /> @@ -250,80 +317,3 @@ export const ArchiveTable = ({ ); }; - -const getColumns = (onRevive: (feature: string) => Promise) => { - return [ - { - id: 'Seen', - Header: 'Seen', - maxWidth: 85, - canSort: true, - Cell: FeatureSeenCell, - disableGlobalFilter: true, - }, - { - id: 'Type', - Header: 'Type', - maxWidth: 85, - canSort: true, - Cell: FeatureTypeCell, - disableGlobalFilter: true, - }, - { - Header: 'Feature toggle Name', - accessor: 'name', - maxWidth: 150, - Cell: ({ value, row: { original } }: any) => ( - - ), - sortType: 'alphanumeric', - }, - { - Header: 'Created', - accessor: 'createdAt', - maxWidth: 150, - Cell: DateCell, - sortType: 'date', - disableGlobalFilter: true, - }, - { - Header: 'Archived', - accessor: 'archivedAt', - maxWidth: 150, - Cell: TimeAgoCell, - sortType: 'date', - disableGlobalFilter: true, - }, - { - Header: 'Project ID', - accessor: 'project', - sortType: 'alphanumeric', - maxWidth: 150, - Cell: ({ value }: any) => ( - - ), - }, - { - Header: 'Status', - accessor: 'stale', - Cell: FeatureStaleCell, - sortType: 'boolean', - maxWidth: 120, - disableGlobalFilter: true, - }, - { - Header: 'Actions', - id: 'Actions', - align: 'center', - maxWidth: 85, - canSort: false, - disableGlobalFilter: true, - Cell: ({ row: { original } }: any) => ( - onRevive(original.name)} - /> - ), - }, - ]; -}; diff --git a/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx b/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx index 9221018389..9c7d99de9f 100644 --- a/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx +++ b/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx @@ -5,7 +5,7 @@ import PermissionIconButton from '../../../common/PermissionIconButton/Permissio import { UPDATE_FEATURE } from '../../../providers/AccessProvider/permissions'; interface IReviveArchivedFeatureCell { - onRevive: (event: SyntheticEvent) => void; + onRevive: () => void; project: string; } @@ -13,10 +13,15 @@ export const ReviveArchivedFeatureCell: VFC = ({ onRevive, project, }) => { + const handleClick = (e: SyntheticEvent) => { + e.preventDefault(); + onRevive(); + }; + return ( = ({ + value: archivedAt, +}) => { + const { locationSettings } = useLocationSettings(); + + if (!archivedAt) return ; + + return ( + + {archivedAt && ( + + + + + + )} + + ); +}; From b6beae95cc8e6556eabac1e0f32e8b3e982dfcef Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Wed, 8 Jun 2022 13:54:12 +0300 Subject: [PATCH 16/33] added filterName to column --- frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx index e05aa3b5b9..0c5a256444 100644 --- a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx +++ b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx @@ -100,6 +100,7 @@ export const ArchiveTable = ({ { Header: 'Feature toggle Name', accessor: 'name', + filterName: 'name', minWidth: 100, Cell: ({ value, row: { original } }: any) => ( (loading ? featuresPlaceholder : searchedData), [searchedData, loading] @@ -260,6 +263,7 @@ export const ArchiveTable = ({ ); }; + return ( Date: Wed, 8 Jun 2022 14:07:55 +0300 Subject: [PATCH 17/33] removed unused code --- .../archive/ArchiveListContainer.tsx | 50 ---------------- .../archive/ArchiveTable/ArchiveTable.tsx | 5 +- .../archive/ProjectFeaturesArchiveList.tsx | 58 ------------------- .../FeatureArchivedCell.tsx | 15 +++-- 4 files changed, 10 insertions(+), 118 deletions(-) delete mode 100644 frontend/src/component/archive/ArchiveListContainer.tsx delete mode 100644 frontend/src/component/archive/ProjectFeaturesArchiveList.tsx diff --git a/frontend/src/component/archive/ArchiveListContainer.tsx b/frontend/src/component/archive/ArchiveListContainer.tsx deleted file mode 100644 index fa0dd84c9b..0000000000 --- a/frontend/src/component/archive/ArchiveListContainer.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { useFeaturesArchive } from 'hooks/api/getters/useFeaturesArchive/useFeaturesArchive'; -import { FeatureToggleList } from '../feature/FeatureToggleList/FeatureToggleArchiveList'; -import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; -import { useFeaturesFilter } from 'hooks/useFeaturesFilter'; -import { useFeatureArchiveApi } from 'hooks/api/actions/useFeatureArchiveApi/useReviveFeatureApi'; -import useToast from 'hooks/useToast'; -import { useFeaturesSort } from 'hooks/useFeaturesSort'; - -export const ArchiveListContainer = () => { - const { setToastData, setToastApiError } = useToast(); - const { uiConfig } = useUiConfig(); - const { reviveFeature } = useFeatureArchiveApi(); - - const { - archivedFeatures = [], - refetchArchived, - loading, - } = useFeaturesArchive(); - - const { filtered, filter, setFilter } = useFeaturesFilter(archivedFeatures); - const { sorted, sort, setSort } = useFeaturesSort(filtered); - - const onRevive = (feature: string) => { - reviveFeature(feature) - .then(refetchArchived) - .then(() => - setToastData({ - type: 'success', - title: "And we're back!", - text: 'The feature toggle has been revived.', - confetti: true, - }) - ) - .catch(e => setToastApiError(e.toString())); - }; - - return ( - - ); -}; diff --git a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx index 0c5a256444..9c5e60ce93 100644 --- a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx +++ b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx @@ -165,8 +165,6 @@ export const ArchiveTable = ({ getSearchContext, } = useSearch(columns, searchValue, archivedFeatures); - debugger; - const data = useMemo( () => (loading ? featuresPlaceholder : searchedData), [searchedData, loading] @@ -238,7 +236,7 @@ export const ArchiveTable = ({ const renderRows = () => { return ( <> - {rows.map((row) => { + {rows.map(row => { prepareRow(row); return ( @@ -263,7 +261,6 @@ export const ArchiveTable = ({ ); }; - return ( = ({ - projectId, -}) => { - const { setToastData, setToastApiError } = useToast(); - const { uiConfig } = useUiConfig(); - const { reviveFeature } = useFeatureArchiveApi(); - - const { - archivedFeatures = [], - refetchArchived, - loading, - } = useProjectFeaturesArchive(projectId); - - const { filtered, filter, setFilter } = useFeaturesFilter(archivedFeatures); - const { sorted, sort, setSort } = useFeaturesSort(filtered); - - const onRevive = (feature: string) => { - reviveFeature(feature) - .then(refetchArchived) - .then(() => - setToastData({ - type: 'success', - title: "And we're back!", - text: 'The feature toggle has been revived.', - confetti: true, - }) - ) - .catch(e => setToastApiError(e.toString())); - }; - - return ( - - ); -}; diff --git a/frontend/src/component/common/Table/cells/FeatureArchivedCell/FeatureArchivedCell.tsx b/frontend/src/component/common/Table/cells/FeatureArchivedCell/FeatureArchivedCell.tsx index 66c6038f2d..1765daa5e2 100644 --- a/frontend/src/component/common/Table/cells/FeatureArchivedCell/FeatureArchivedCell.tsx +++ b/frontend/src/component/common/Table/cells/FeatureArchivedCell/FeatureArchivedCell.tsx @@ -1,9 +1,9 @@ -import React, {VFC} from 'react'; +import React, { VFC } from 'react'; import TimeAgo from 'react-timeago'; -import {Tooltip, Typography} from '@mui/material'; -import {formatDateYMD} from '../../../../../utils/formatDate'; -import {TextCell} from '../TextCell/TextCell'; -import {useLocationSettings} from "../../../../../hooks/useLocationSettings"; +import { Tooltip, Typography } from '@mui/material'; +import { formatDateYMD } from '../../../../../utils/formatDate'; +import { TextCell } from '../TextCell/TextCell'; +import { useLocationSettings } from '../../../../../hooks/useLocationSettings'; interface IFeatureArchivedCellProps { value?: string | Date | null; @@ -20,7 +20,10 @@ export const FeatureArchivedCell: VFC = ({ {archivedAt && ( From 65c252a20c56151a5e8fcbabd003a1392186a421 Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Wed, 8 Jun 2022 14:36:29 +0300 Subject: [PATCH 18/33] add search and filters --- .../archive/ArchiveTable/ArchiveTable.tsx | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx index 9c5e60ce93..ebaf861a81 100644 --- a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx +++ b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx @@ -31,6 +31,7 @@ import useToast from '../../../hooks/useToast'; import { formatUnknownError } from '../../../utils/formatUnknownError'; import { useSearch } from '../../../hooks/useSearch'; import { FeatureArchivedCell } from '../../common/Table/cells/FeatureArchivedCell/FeatureArchivedCell'; +import { useVirtualizedRange } from '../../../hooks/useVirtualizedRange'; export interface IFeaturesArchiveTableProps { archivedFeatures: FeatureSchema[]; @@ -100,7 +101,7 @@ export const ArchiveTable = ({ { Header: 'Feature toggle Name', accessor: 'name', - filterName: 'name', + searchable: true, minWidth: 100, Cell: ({ value, row: { original } }: any) => ( ( @@ -139,7 +142,8 @@ export const ArchiveTable = ({ Cell: FeatureStaleCell, sortType: 'boolean', maxWidth: 120, - disableGlobalFilter: true, + filterName: 'state', + filterParsing: (value: any) => (value ? 'stale' : 'active'), }, { Header: 'Actions', @@ -155,6 +159,10 @@ export const ArchiveTable = ({ /> ), }, + // Always hidden -- for search + { + accessor: 'description', + }, ], [] ); @@ -233,10 +241,20 @@ export const ArchiveTable = ({ setStoredParams({ id: sortBy[0].id, desc: sortBy[0].desc || false }); }, [loading, sortBy, searchValue, setSearchParams, setStoredParams]); + const [firstRenderedIndex, lastRenderedIndex] = + useVirtualizedRange(rowHeight); + const renderRows = () => { return ( <> - {rows.map(row => { + {rows.map((row, index) => { + const isVirtual = + index < firstRenderedIndex || index > lastRenderedIndex; + + if (isVirtual) { + return null; + } + prepareRow(row); return ( From d50edfa2947c98a4ba8c130ee49cc4c20a614031 Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Wed, 8 Jun 2022 14:47:44 +0300 Subject: [PATCH 19/33] remove dead code --- .../FeatureToggleArchiveList.tsx | 178 ------------- .../FeatureToggleListActions.tsx | 99 -------- .../FeatureToggleListActions/styles.ts | 12 - frontend/src/hooks/useFeaturesSort.ts | 239 ------------------ 4 files changed, 528 deletions(-) delete mode 100644 frontend/src/component/feature/FeatureToggleList/FeatureToggleArchiveList.tsx delete mode 100644 frontend/src/component/feature/FeatureToggleList/FeatureToggleListActions/FeatureToggleListActions.tsx delete mode 100644 frontend/src/component/feature/FeatureToggleList/FeatureToggleListActions/styles.ts delete mode 100644 frontend/src/hooks/useFeaturesSort.ts diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleArchiveList.tsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleArchiveList.tsx deleted file mode 100644 index c0bf892afe..0000000000 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleArchiveList.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import { Dispatch, SetStateAction, useContext, VFC } from 'react'; -import classnames from 'classnames'; -import { Link } from 'react-router-dom'; -import { List, ListItem } from '@mui/material'; -import useMediaQuery from '@mui/material/useMediaQuery'; -import { IFlags } from 'interfaces/uiConfig'; -import { SearchField } from 'component/common/SearchField/SearchField'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { PageContent } from 'component/common/PageContent/PageContent'; -import { PageHeader } from 'component/common/PageHeader/PageHeader'; -import AccessContext from 'contexts/AccessContext'; -import { IFeaturesFilter } from 'hooks/useFeaturesFilter'; -import { FeatureToggleListItem } from './FeatureToggleListItem/FeatureToggleListItem'; -import { FeatureToggleListActions } from './FeatureToggleListActions/FeatureToggleListActions'; -import { CreateFeatureButton } from '../CreateFeatureButton/CreateFeatureButton'; -import { IFeaturesSort } from 'hooks/useFeaturesSort'; -import { FeatureSchema } from 'openapi'; -import { useStyles } from './styles'; - -interface IFeatureToggleListProps { - features: FeatureSchema[]; - loading?: boolean; - flags?: IFlags; - filter: IFeaturesFilter; - setFilter: Dispatch>; - sort: IFeaturesSort; - setSort: Dispatch>; - onRevive?: (feature: string) => void; - inProject?: boolean; - isArchive?: boolean; -} - -const loadingFeaturesPlaceholder: FeatureSchema[] = Array(10) - .fill({ - createdAt: '2021-03-19T09:16:21.329Z', - description: ' ', - enabled: true, - lastSeenAt: '2021-03-24T10:46:38.036Z', - name: '', - project: 'default', - stale: true, - strategies: [], - variants: [], - type: 'release', - archived: false, - environments: [], - impressionData: false, - }) - .map((feature, index) => ({ ...feature, name: `${index}` })); // ID for React key - -export const FeatureToggleList: VFC = ({ - features, - onRevive, - inProject, - isArchive, - loading, - flags, - filter, - setFilter, - sort, - setSort, -}) => { - const { hasAccess } = useContext(AccessContext); - const { classes: styles } = useStyles(); - const smallScreen = useMediaQuery('(max-width:800px)'); - const mobileView = useMediaQuery('(max-width:600px)'); - - const setFilterQuery = (v: string) => { - const query = v && typeof v === 'string' ? v.trim() : ''; - setFilter(prev => ({ ...prev, query })); - }; - - const renderFeatures = () => { - if (loading) { - return loadingFeaturesPlaceholder.map(feature => ( - - )); - } - - return ( - 0} - show={features.map(feature => ( - - ))} - elseShow={ - - No archived features. - - } - /> - ); - }; - - const searchResultsHeader = filter.query - ? ` (${features.length} matches)` - : ''; - - const headerTitle = isArchive - ? inProject - ? `Project Archived Features ${searchResultsHeader}` - : `Archived Features ${searchResultsHeader}` - : `Features ${searchResultsHeader}`; - - return ( -
-
- - Archive} - /> -
- - - - } - /> - - } - /> -
- } - /> - } - > - {renderFeatures()} -
- - ); -}; diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListActions/FeatureToggleListActions.tsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListActions/FeatureToggleListActions.tsx deleted file mode 100644 index 0ee0f1511f..0000000000 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListActions/FeatureToggleListActions.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { Dispatch, MouseEventHandler, SetStateAction, VFC } from 'react'; -import { MenuItem, Typography } from '@mui/material'; -import DropdownMenu from 'component/common/DropdownMenu/DropdownMenu'; -import ProjectSelect from 'component/common/ProjectSelect/ProjectSelect'; -import useLoading from 'hooks/useLoading'; -import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { - createFeaturesFilterSortOptions, - FeaturesSortType, - IFeaturesSort, -} from 'hooks/useFeaturesSort'; -import { useStyles } from './styles'; -import { IFeaturesFilter } from 'hooks/useFeaturesFilter'; - -let sortOptions = createFeaturesFilterSortOptions(); - -interface IFeatureToggleListActionsProps { - filter: IFeaturesFilter; - setFilter: Dispatch>; - sort: IFeaturesSort; - setSort: Dispatch>; - loading?: boolean; - inProject?: boolean; -} - -export const FeatureToggleListActions: VFC = ({ - filter, - setFilter, - sort, - setSort, - loading = false, - inProject, -}) => { - const { classes: styles } = useStyles(); - const { uiConfig } = useUiConfig(); - const ref = useLoading(loading); - - const handleSort: MouseEventHandler = e => { - const type = (e.target as Element) - .getAttribute('data-target') - ?.trim() as FeaturesSortType; - if (type) { - setSort(prev => ({ ...prev, type })); - } - }; - - const selectedOption = - sortOptions.find(o => o.type === sort.type) || sortOptions[0]; - - if (inProject) { - sortOptions = sortOptions.filter(option => option.type !== 'project'); - } - - const renderSortingOptions = () => - sortOptions.map(option => ( - - {option.name} - - )); - - return ( -
- - Sorted by: - - - - setFilter(prev => ({ ...prev, project })) - } - style={{ - textTransform: 'lowercase', - fontWeight: 'normal', - }} - data-loading - /> - } - /> -
- ); -}; diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListActions/styles.ts b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListActions/styles.ts deleted file mode 100644 index c61a280123..0000000000 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListActions/styles.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { makeStyles } from 'tss-react/mui'; - -export const useStyles = makeStyles()({ - actions: { - '& > *': { - margin: '0 0.25rem', - }, - marginRight: '0.25rem', - display: 'flex', - alignItems: 'center', - }, -}); diff --git a/frontend/src/hooks/useFeaturesSort.ts b/frontend/src/hooks/useFeaturesSort.ts deleted file mode 100644 index 0758a02b68..0000000000 --- a/frontend/src/hooks/useFeaturesSort.ts +++ /dev/null @@ -1,239 +0,0 @@ -import React, { useMemo } from 'react'; -import { basePath } from 'utils/formatPath'; -import { createPersistentGlobalStateHook } from './usePersistentGlobalState'; -import { - expired, - getDiffInDays, - toggleExpiryByTypeMap, -} from 'component/Reporting/utils'; -import { FeatureSchema } from 'openapi'; - -export type FeaturesSortType = - | 'name' - | 'expired' - | 'type' - | 'enabled' - | 'stale' - | 'created' - | 'archived' - | 'last-seen' - | 'status' - | 'project'; - -export interface IFeaturesSort { - type: FeaturesSortType; - desc?: boolean; -} - -export interface IFeaturesSortOutput { - sort: IFeaturesSort; - sorted: FeatureSchema[]; - setSort: React.Dispatch>; -} - -export interface IFeaturesFilterSortOption { - type: FeaturesSortType; - name: string; -} - -// Store the features sort state globally, and in localStorage. -// When changing the format of IFeaturesSort, change the version as well. -const useFeaturesSortState = createPersistentGlobalStateHook( - `${basePath}:useFeaturesSort:v1`, - { type: 'name' } -); - -export const useFeaturesSort = ( - features: FeatureSchema[] -): IFeaturesSortOutput => { - const [sort, setSort] = useFeaturesSortState(); - - const sorted = useMemo(() => { - return sortFeatures(features, sort); - }, [features, sort]); - - return { - setSort, - sort, - sorted, - }; -}; - -export const createFeaturesFilterSortOptions = - (): IFeaturesFilterSortOption[] => { - return [ - { type: 'name', name: 'Name' }, - { type: 'type', name: 'Type' }, - { type: 'enabled', name: 'Enabled' }, - { type: 'stale', name: 'Stale' }, - { type: 'status', name: 'Status' }, - { type: 'created', name: 'Created' }, - { type: 'archived', name: 'Archived' }, - { type: 'last-seen', name: 'Last seen' }, - { type: 'project', name: 'Project' }, - ]; - }; - -const sortAscendingFeatures = ( - features: FeatureSchema[], - sort: IFeaturesSort -): FeatureSchema[] => { - switch (sort.type) { - case 'enabled': - return sortByEnabled(features); - case 'stale': - return sortByStale(features); - case 'created': - return sortByCreated(features); - case 'archived': - return sortByArchived(features); - case 'last-seen': - return sortByLastSeen(features); - case 'name': - return sortByName(features); - case 'project': - return sortByProject(features); - case 'type': - return sortByType(features); - case 'expired': - return sortByExpired(features); - case 'status': - return sortByStatus(features); - default: - console.error(`Unknown feature sort type: ${sort.type}`); - return features; - } -}; - -const sortFeatures = ( - features: FeatureSchema[], - sort: IFeaturesSort -): FeatureSchema[] => { - const sorted = sortAscendingFeatures(features, sort); - - if (sort.desc) { - return [...sorted].reverse(); - } - - return sorted; -}; - -const sortByEnabled = ( - features: Readonly -): FeatureSchema[] => { - return [...features].sort((a, b) => - a.enabled === b.enabled ? 0 : a.enabled ? -1 : 1 - ); -}; - -const sortByStale = (features: Readonly): FeatureSchema[] => { - return [...features].sort((a, b) => - a.stale === b.stale ? 0 : a.stale ? -1 : 1 - ); -}; - -const sortByLastSeen = ( - features: Readonly -): FeatureSchema[] => { - return [...features].sort((a, b) => - a.lastSeenAt && b.lastSeenAt - ? compareNullableDates(b.lastSeenAt, a.lastSeenAt) - : a.lastSeenAt - ? -1 - : b.lastSeenAt - ? 1 - : compareNullableDates(b.createdAt, a.createdAt) - ); -}; - -const sortByCreated = ( - features: Readonly -): FeatureSchema[] => { - return [...features].sort((a, b) => - compareNullableDates(b.createdAt, a.createdAt) - ); -}; - -const sortByArchived = ( - features: Readonly -): FeatureSchema[] => { - return [...features].sort((a, b) => - compareNullableDates(b.archivedAt, a.archivedAt) - ); -}; - -const sortByName = (features: Readonly): FeatureSchema[] => { - return [...features].sort((a, b) => a.name.localeCompare(b.name)); -}; - -const sortByProject = ( - features: Readonly -): FeatureSchema[] => { - return [...features].sort((a, b) => - a.project && b.project - ? a.project.localeCompare(b.project) - : a.project - ? 1 - : b.project - ? -1 - : 0 - ); -}; - -const sortByType = (features: Readonly): FeatureSchema[] => { - return [...features].sort((a, b) => - a.type && b.type - ? a.type.localeCompare(b.type) - : a.type - ? 1 - : b.type - ? -1 - : 0 - ); -}; - -const compareNullableDates = ( - a: Date | null | undefined, - b: Date | null | undefined -): number => { - return a && b ? a?.getTime?.() - b?.getTime?.() : a ? 1 : b ? -1 : 0; -}; - -const sortByExpired = ( - features: Readonly -): FeatureSchema[] => { - return [...features].sort((a, b) => { - const now = new Date(); - const dateA = a.createdAt!; - const dateB = b.createdAt!; - - const diffA = getDiffInDays(dateA, now); - const diffB = getDiffInDays(dateB, now); - - if (!expired(diffA, a.type!) && expired(diffB, b.type!)) { - return 1; - } - - if (expired(diffA, a.type!) && !expired(diffB, b.type!)) { - return -1; - } - - const expiration = toggleExpiryByTypeMap as Record; - const expiredByA = a.type ? diffA - expiration[a.type] : 0; - const expiredByB = b.type ? diffB - expiration[b.type] : 0; - - return expiredByB - expiredByA; - }); -}; - -const sortByStatus = (features: Readonly): FeatureSchema[] => { - return [...features].sort((a, b) => { - if (a.stale) { - return 1; - } else if (b.stale) { - return -1; - } else { - return 0; - } - }); -}; From 0e9d19ee7c9777d28be78b513aec0f3fc6ab0147 Mon Sep 17 00:00:00 2001 From: andreas-unleash <104830839+andreas-unleash@users.noreply.github.com> Date: Thu, 9 Jun 2022 12:46:04 +0300 Subject: [PATCH 20/33] PR comment --- .../archive/ArchiveTable/ArchiveTable.tsx | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx index ebaf861a81..573c744b04 100644 --- a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx +++ b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx @@ -17,21 +17,21 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell'; import { DateCell } from 'component/common/Table/cells/DateCell/DateCell'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { FeatureTypeCell } from '../../common/Table/cells/FeatureTypeCell/FeatureTypeCell'; -import { FeatureSeenCell } from '../../common/Table/cells/FeatureSeenCell/FeatureSeenCell'; -import { LinkCell } from '../../common/Table/cells/LinkCell/LinkCell'; -import { FeatureStaleCell } from '../../feature/FeatureToggleList/FeatureStaleCell/FeatureStaleCell'; +import { FeatureTypeCell } from 'common/Table/cells/FeatureTypeCell/FeatureTypeCell'; +import { FeatureSeenCell } from 'common/Table/cells/FeatureSeenCell/FeatureSeenCell'; +import { LinkCell } from 'common/Table/cells/LinkCell/LinkCell'; +import { FeatureStaleCell } from 'feature/FeatureToggleList/FeatureStaleCell/FeatureStaleCell'; import { ReviveArchivedFeatureCell } from 'component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell'; -import { useStyles } from '../../feature/FeatureToggleList/styles'; -import { featuresPlaceholder } from '../../feature/FeatureToggleList/FeatureToggleListTable'; +import { useStyles } from 'feature/FeatureToggleList/styles'; +import { featuresPlaceholder } from 'feature/FeatureToggleList/FeatureToggleListTable'; import theme from 'themes/theme'; import { FeatureSchema } from '../../../openapi'; -import { useFeatureArchiveApi } from '../../../hooks/api/actions/useFeatureArchiveApi/useReviveFeatureApi'; -import useToast from '../../../hooks/useToast'; -import { formatUnknownError } from '../../../utils/formatUnknownError'; -import { useSearch } from '../../../hooks/useSearch'; -import { FeatureArchivedCell } from '../../common/Table/cells/FeatureArchivedCell/FeatureArchivedCell'; -import { useVirtualizedRange } from '../../../hooks/useVirtualizedRange'; +import { useFeatureArchiveApi } from 'hooks/api/actions/useFeatureArchiveApi/useReviveFeatureApi'; +import useToast from 'hooks/useToast'; +import { formatUnknownError } from 'utils/formatUnknownError'; +import { useSearch } from 'hooks/useSearch'; +import { FeatureArchivedCell } from 'common/Table/cells/FeatureArchivedCell/FeatureArchivedCell'; +import { useVirtualizedRange } from 'hooks/useVirtualizedRange'; export interface IFeaturesArchiveTableProps { archivedFeatures: FeatureSchema[]; From 055e7bebff444cebf8cfcde2dd9a35a0b048c1a8 Mon Sep 17 00:00:00 2001 From: andreas-unleash <104830839+andreas-unleash@users.noreply.github.com> Date: Thu, 9 Jun 2022 12:47:13 +0300 Subject: [PATCH 21/33] PR comment --- .../archive/ArchiveTable/ArchiveTable.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx index 573c744b04..ec5709509c 100644 --- a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx +++ b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx @@ -17,20 +17,20 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell'; import { DateCell } from 'component/common/Table/cells/DateCell/DateCell'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { FeatureTypeCell } from 'common/Table/cells/FeatureTypeCell/FeatureTypeCell'; -import { FeatureSeenCell } from 'common/Table/cells/FeatureSeenCell/FeatureSeenCell'; -import { LinkCell } from 'common/Table/cells/LinkCell/LinkCell'; -import { FeatureStaleCell } from 'feature/FeatureToggleList/FeatureStaleCell/FeatureStaleCell'; +import { FeatureTypeCell } from '../../common/Table/cells/FeatureTypeCell/FeatureTypeCell'; +import { FeatureSeenCell } from '../../common/Table/cells/FeatureSeenCell/FeatureSeenCell'; +import { LinkCell } from '../../common/Table/cells/LinkCell/LinkCell'; +import { FeatureStaleCell } from '../../feature/FeatureToggleList/FeatureStaleCell/FeatureStaleCell'; import { ReviveArchivedFeatureCell } from 'component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell'; -import { useStyles } from 'feature/FeatureToggleList/styles'; -import { featuresPlaceholder } from 'feature/FeatureToggleList/FeatureToggleListTable'; +import { useStyles } from '../../feature/FeatureToggleList/styles'; +import { featuresPlaceholder } from '../../feature/FeatureToggleList/FeatureToggleListTable'; import theme from 'themes/theme'; import { FeatureSchema } from '../../../openapi'; import { useFeatureArchiveApi } from 'hooks/api/actions/useFeatureArchiveApi/useReviveFeatureApi'; import useToast from 'hooks/useToast'; import { formatUnknownError } from 'utils/formatUnknownError'; import { useSearch } from 'hooks/useSearch'; -import { FeatureArchivedCell } from 'common/Table/cells/FeatureArchivedCell/FeatureArchivedCell'; +import { FeatureArchivedCell } from '../../common/Table/cells/FeatureArchivedCell/FeatureArchivedCell'; import { useVirtualizedRange } from 'hooks/useVirtualizedRange'; export interface IFeaturesArchiveTableProps { From a5094b1fc0a97b6bd94f9a48bbeb174c6a03cc12 Mon Sep 17 00:00:00 2001 From: andreas-unleash <104830839+andreas-unleash@users.noreply.github.com> Date: Fri, 10 Jun 2022 09:34:42 +0300 Subject: [PATCH 22/33] replace useLocalStorage --- .../src/component/archive/FeaturesArchiveTable.tsx | 10 +++++----- .../component/archive/ProjectFeaturesArchiveTable.tsx | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/frontend/src/component/archive/FeaturesArchiveTable.tsx b/frontend/src/component/archive/FeaturesArchiveTable.tsx index 00ec6242a9..56d35b4070 100644 --- a/frontend/src/component/archive/FeaturesArchiveTable.tsx +++ b/frontend/src/component/archive/FeaturesArchiveTable.tsx @@ -1,9 +1,9 @@ import { useFeaturesArchive } from '../../hooks/api/getters/useFeaturesArchive/useFeaturesArchive'; import { ArchiveTable } from './ArchiveTable/ArchiveTable'; import { useSearchParams } from 'react-router-dom'; -import { useLocalStorage } from '../../hooks/useLocalStorage'; import { SortingRule } from 'react-table'; -import { usePageTitle } from '../../hooks/usePageTitle'; +import { usePageTitle } from 'hooks/usePageTitle'; +import { createLocalStorage } from 'utils/createLocalStorage'; const defaultSort: SortingRule = { id: 'createdAt', desc: true }; @@ -16,7 +16,7 @@ export const FeaturesArchiveTable = () => { } = useFeaturesArchive(); const [searchParams, setSearchParams] = useSearchParams(); - const [storedParams, setStoredParams] = useLocalStorage( + const { value, setValue } = createLocalStorage( 'FeaturesArchiveTable:v1', defaultSort ); @@ -28,8 +28,8 @@ export const FeaturesArchiveTable = () => { loading={loading} searchParams={searchParams} setSearchParams={setSearchParams} - storedParams={storedParams} - setStoredParams={setStoredParams} + storedParams={value} + setStoredParams={setValue} refetch={refetchArchived} /> ); diff --git a/frontend/src/component/archive/ProjectFeaturesArchiveTable.tsx b/frontend/src/component/archive/ProjectFeaturesArchiveTable.tsx index 25bcc50f35..2431a03e5f 100644 --- a/frontend/src/component/archive/ProjectFeaturesArchiveTable.tsx +++ b/frontend/src/component/archive/ProjectFeaturesArchiveTable.tsx @@ -1,8 +1,8 @@ import { ArchiveTable } from './ArchiveTable/ArchiveTable'; import { useSearchParams } from 'react-router-dom'; -import { useLocalStorage } from '../../hooks/useLocalStorage'; import { SortingRule } from 'react-table'; import { useProjectFeaturesArchive } from '../../hooks/api/getters/useProjectFeaturesArchive/useProjectFeaturesArchive'; +import { createLocalStorage } from 'utils/createLocalStorage'; const defaultSort: SortingRule = { id: 'archivedAt', desc: true }; @@ -20,7 +20,7 @@ export const ProjectFeaturesArchiveTable = ({ } = useProjectFeaturesArchive(projectId); const [searchParams, setSearchParams] = useSearchParams(); - const [storedParams, setStoredParams] = useLocalStorage( + const { value, setValue } = createLocalStorage( `${projectId}:ProjectFeaturesArchiveTable`, defaultSort ); @@ -32,8 +32,8 @@ export const ProjectFeaturesArchiveTable = ({ loading={loading} searchParams={searchParams} setSearchParams={setSearchParams} - storedParams={storedParams} - setStoredParams={setStoredParams} + storedParams={value} + setStoredParams={setValue} refetch={refetchArchived} /> ); From fb2005a2dfb44684811d3580c6d27a3431d0c54c Mon Sep 17 00:00:00 2001 From: andreas-unleash <104830839+andreas-unleash@users.noreply.github.com> Date: Fri, 10 Jun 2022 15:47:42 +0300 Subject: [PATCH 23/33] PR comments --- .../archive/ArchiveTable/ArchiveTable.tsx | 79 +++++++++---------- .../ReviveArchivedFeatureCell.tsx | 6 +- .../archive/FeaturesArchiveTable.tsx | 2 +- .../archive/ProjectFeaturesArchiveTable.tsx | 2 +- .../FeatureArchivedCell.tsx | 2 +- 5 files changed, 42 insertions(+), 49 deletions(-) diff --git a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx index ec5709509c..c74f97d8cf 100644 --- a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx +++ b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx @@ -9,7 +9,7 @@ import { TableRow, TableSearch, } from 'component/common/Table'; -import { useFlexLayout, useSortBy, useTable } from 'react-table'; +import {SortingRule, useFlexLayout, useSortBy, useTable} from 'react-table'; import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; import { useMediaQuery } from '@mui/material'; import { sortTypes } from 'utils/sortTypes'; @@ -32,16 +32,17 @@ import { formatUnknownError } from 'utils/formatUnknownError'; import { useSearch } from 'hooks/useSearch'; import { FeatureArchivedCell } from '../../common/Table/cells/FeatureArchivedCell/FeatureArchivedCell'; import { useVirtualizedRange } from 'hooks/useVirtualizedRange'; +import { URLSearchParamsInit } from 'react-router-dom'; export interface IFeaturesArchiveTableProps { archivedFeatures: FeatureSchema[]; title: string; - refetch: any; + refetch: () => void; loading: boolean; - storedParams: any; - setStoredParams: any; - searchParams: any; - setSearchParams: any; + storedParams: SortingRule; + setStoredParams: (newValue: (SortingRule | ((prev: SortingRule) => SortingRule))) => SortingRule; + searchParams: URLSearchParams; + setSearchParams: (nextInit: URLSearchParamsInit, navigateOptions?: ({replace?: boolean | undefined, state?: any} | undefined)) => void; } export const ArchiveTable = ({ @@ -74,7 +75,6 @@ export const ArchiveTable = ({ type: 'success', title: "And we're back!", text: 'The feature toggle has been revived.', - confetti: true, }); } catch (e: unknown) { setToastApiError(formatUnknownError(e)); @@ -164,6 +164,7 @@ export const ArchiveTable = ({ accessor: 'description', }, ], + //eslint-disable-next-line [] ); @@ -244,41 +245,6 @@ export const ArchiveTable = ({ const [firstRenderedIndex, lastRenderedIndex] = useVirtualizedRange(rowHeight); - const renderRows = () => { - return ( - <> - {rows.map((row, index) => { - const isVirtual = - index < firstRenderedIndex || index > lastRenderedIndex; - - if (isVirtual) { - return null; - } - - prepareRow(row); - return ( - - {row.cells.map(cell => ( - - {cell.render('Cell')} - - ))} - - ); - })} - - ); - }; - return ( - {renderRows()} + {rows.map((row, index) => { + const isVirtual = + index < firstRenderedIndex || index > lastRenderedIndex; + + if (isVirtual) { + return null; + } + + prepareRow(row); + return ( + + {row.cells.map(cell => ( + + {cell.render('Cell')} + + ))} + + ); + })}
diff --git a/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx b/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx index 9c7d99de9f..aa22d9a8f2 100644 --- a/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx +++ b/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx @@ -1,8 +1,8 @@ import { SyntheticEvent, VFC } from 'react'; -import { ActionCell } from '../../../common/Table/cells/ActionCell/ActionCell'; +import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell'; import { Undo } from '@mui/icons-material'; -import PermissionIconButton from '../../../common/PermissionIconButton/PermissionIconButton'; -import { UPDATE_FEATURE } from '../../../providers/AccessProvider/permissions'; +import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; +import { UPDATE_FEATURE } from 'component/providers/AccessProvider/permissions'; interface IReviveArchivedFeatureCell { onRevive: () => void; diff --git a/frontend/src/component/archive/FeaturesArchiveTable.tsx b/frontend/src/component/archive/FeaturesArchiveTable.tsx index 56d35b4070..beb6cc5a1e 100644 --- a/frontend/src/component/archive/FeaturesArchiveTable.tsx +++ b/frontend/src/component/archive/FeaturesArchiveTable.tsx @@ -23,7 +23,7 @@ export const FeaturesArchiveTable = () => { return ( = ({ arrow > - + )} From f44bc9a849d54247b9f4dcdf40a55fe519cfe1f6 Mon Sep 17 00:00:00 2001 From: andreas-unleash <104830839+andreas-unleash@users.noreply.github.com> Date: Fri, 10 Jun 2022 15:55:54 +0300 Subject: [PATCH 24/33] PR comments --- .../Project/ProjectFeaturesArchive/ProjectFeaturesArchive.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/component/project/Project/ProjectFeaturesArchive/ProjectFeaturesArchive.tsx b/frontend/src/component/project/Project/ProjectFeaturesArchive/ProjectFeaturesArchive.tsx index 4dff9aedcd..876281076b 100644 --- a/frontend/src/component/project/Project/ProjectFeaturesArchive/ProjectFeaturesArchive.tsx +++ b/frontend/src/component/project/Project/ProjectFeaturesArchive/ProjectFeaturesArchive.tsx @@ -1,5 +1,5 @@ -import { ProjectFeaturesArchiveTable } from '../../../archive/ProjectFeaturesArchiveTable'; -import { usePageTitle } from '../../../../hooks/usePageTitle'; +import { ProjectFeaturesArchiveTable } from 'component/archive/ProjectFeaturesArchiveTable'; +import { usePageTitle } from 'hooks/usePageTitle'; interface IProjectFeaturesArchiveProps { projectId: string; From 9536a1b78ff06f2343b39c734aa0debcf00ae7b9 Mon Sep 17 00:00:00 2001 From: andreas-unleash <104830839+andreas-unleash@users.noreply.github.com> Date: Fri, 10 Jun 2022 16:04:06 +0300 Subject: [PATCH 25/33] formatting --- .../archive/ArchiveTable/ArchiveTable.tsx | 27 ++++++++++++++----- .../archive/FeaturesArchiveTable.tsx | 2 +- .../archive/ProjectFeaturesArchiveTable.tsx | 2 +- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx index c74f97d8cf..4d85be411d 100644 --- a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx +++ b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx @@ -9,7 +9,7 @@ import { TableRow, TableSearch, } from 'component/common/Table'; -import {SortingRule, useFlexLayout, useSortBy, useTable} from 'react-table'; +import { SortingRule, useFlexLayout, useSortBy, useTable } from 'react-table'; import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; import { useMediaQuery } from '@mui/material'; import { sortTypes } from 'utils/sortTypes'; @@ -40,9 +40,18 @@ export interface IFeaturesArchiveTableProps { refetch: () => void; loading: boolean; storedParams: SortingRule; - setStoredParams: (newValue: (SortingRule | ((prev: SortingRule) => SortingRule))) => SortingRule; + setStoredParams: ( + newValue: + | SortingRule + | ((prev: SortingRule) => SortingRule) + ) => SortingRule; searchParams: URLSearchParams; - setSearchParams: (nextInit: URLSearchParamsInit, navigateOptions?: ({replace?: boolean | undefined, state?: any} | undefined)) => void; + setSearchParams: ( + nextInit: URLSearchParamsInit, + navigateOptions?: + | { replace?: boolean | undefined; state?: any } + | undefined + ) => void; } export const ArchiveTable = ({ @@ -283,7 +292,8 @@ export const ArchiveTable = ({ {rows.map((row, index) => { const isVirtual = - index < firstRenderedIndex || index > lastRenderedIndex; + index < firstRenderedIndex || + index > lastRenderedIndex; if (isVirtual) { return null; @@ -291,12 +301,17 @@ export const ArchiveTable = ({ prepareRow(row); return ( - + {row.cells.map(cell => ( { return ( Date: Mon, 13 Jun 2022 15:22:27 +0200 Subject: [PATCH 26/33] Fix/archive table (#1086) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: upgrade search to use the new search component (#1073) * feat: upgrade project list search to use the new search field * cleanup unused imports * feat: add upgraded search to projects and applications, polish search UX * refactor: TableSearch to new Search common component Co-authored-by: Fredrik Strand Oseberg * fix: resolve issues with project edit/delete button conditions (#1084) * fix: fix UPDATE_PROJECT permission checks for editors * fix: disable delete button for the default project * fix: warn about access on edit project page * fix: hide broken project edit/delete buttons for OSS * refactor: avoid project card clicks when closing modals * refactor: improve default project deletion message * refactor: improve project access error text * Update src/component/project/ProjectCard/ProjectCard.tsx Co-authored-by: Thomas Heartman * refactor: fix string quotes * refactor: improve disabled menu item contrast * refactor: remove Enterprise routes for OSS Co-authored-by: Thomas Heartman * fix: archive table small adjustments * refactor archive table params Co-authored-by: Nuno Góis Co-authored-by: Fredrik Strand Oseberg Co-authored-by: olav Co-authored-by: Thomas Heartman --- .../archive/ArchiveTable/ArchiveTable.tsx | 21 +++++++------------ .../FeatureArchivedCell.tsx | 8 +++---- .../ReviveArchivedFeatureCell.tsx | 9 ++------ .../archive/FeaturesArchiveTable.tsx | 13 ++++-------- .../archive/ProjectFeaturesArchiveTable.tsx | 4 ---- 5 files changed, 17 insertions(+), 38 deletions(-) rename frontend/src/component/{common/Table/cells => archive/ArchiveTable}/FeatureArchivedCell/FeatureArchivedCell.tsx (79%) diff --git a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx index 4d85be411d..1db49013a8 100644 --- a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx +++ b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx @@ -25,14 +25,15 @@ import { ReviveArchivedFeatureCell } from 'component/archive/ArchiveTable/Revive import { useStyles } from '../../feature/FeatureToggleList/styles'; import { featuresPlaceholder } from '../../feature/FeatureToggleList/FeatureToggleListTable'; import theme from 'themes/theme'; -import { FeatureSchema } from '../../../openapi'; +import { FeatureSchema } from 'openapi'; import { useFeatureArchiveApi } from 'hooks/api/actions/useFeatureArchiveApi/useReviveFeatureApi'; import useToast from 'hooks/useToast'; import { formatUnknownError } from 'utils/formatUnknownError'; import { useSearch } from 'hooks/useSearch'; -import { FeatureArchivedCell } from '../../common/Table/cells/FeatureArchivedCell/FeatureArchivedCell'; +import { FeatureArchivedCell } from './FeatureArchivedCell/FeatureArchivedCell'; import { useVirtualizedRange } from 'hooks/useVirtualizedRange'; -import { URLSearchParamsInit } from 'react-router-dom'; +import { useSearchParams } from 'react-router-dom'; +import { useWindowVirtualizer } from '@tanstack/react-virtual'; export interface IFeaturesArchiveTableProps { archivedFeatures: FeatureSchema[]; @@ -45,13 +46,6 @@ export interface IFeaturesArchiveTableProps { | SortingRule | ((prev: SortingRule) => SortingRule) ) => SortingRule; - searchParams: URLSearchParams; - setSearchParams: ( - nextInit: URLSearchParamsInit, - navigateOptions?: - | { replace?: boolean | undefined; state?: any } - | undefined - ) => void; } export const ArchiveTable = ({ @@ -60,8 +54,6 @@ export const ArchiveTable = ({ refetch, storedParams, setStoredParams, - searchParams, - setSearchParams, title, }: IFeaturesArchiveTableProps) => { const rowHeight = theme.shape.tableRowHeight; @@ -70,6 +62,7 @@ export const ArchiveTable = ({ const isMediumScreen = useMediaQuery(theme.breakpoints.down('lg')); const { setToastData, setToastApiError } = useToast(); + const [searchParams, setSearchParams] = useSearchParams(); const { reviveFeature } = useFeatureArchiveApi(); const [searchValue, setSearchValue] = useState( @@ -108,7 +101,7 @@ export const ArchiveTable = ({ Cell: FeatureTypeCell, }, { - Header: 'Feature toggle Name', + Header: 'Feature toggle name', accessor: 'name', searchable: true, minWidth: 100, @@ -249,7 +242,7 @@ export const ArchiveTable = ({ replace: true, }); setStoredParams({ id: sortBy[0].id, desc: sortBy[0].desc || false }); - }, [loading, sortBy, searchValue, setSearchParams, setStoredParams]); + }, [loading, sortBy, searchValue]); const [firstRenderedIndex, lastRenderedIndex] = useVirtualizedRange(rowHeight); diff --git a/frontend/src/component/common/Table/cells/FeatureArchivedCell/FeatureArchivedCell.tsx b/frontend/src/component/archive/ArchiveTable/FeatureArchivedCell/FeatureArchivedCell.tsx similarity index 79% rename from frontend/src/component/common/Table/cells/FeatureArchivedCell/FeatureArchivedCell.tsx rename to frontend/src/component/archive/ArchiveTable/FeatureArchivedCell/FeatureArchivedCell.tsx index 3afbd92403..75c94812f0 100644 --- a/frontend/src/component/common/Table/cells/FeatureArchivedCell/FeatureArchivedCell.tsx +++ b/frontend/src/component/archive/ArchiveTable/FeatureArchivedCell/FeatureArchivedCell.tsx @@ -1,9 +1,9 @@ -import React, { VFC } from 'react'; +import { VFC } from 'react'; import TimeAgo from 'react-timeago'; import { Tooltip, Typography } from '@mui/material'; -import { formatDateYMD } from '../../../../../utils/formatDate'; -import { TextCell } from '../TextCell/TextCell'; -import { useLocationSettings } from '../../../../../hooks/useLocationSettings'; +import { formatDateYMD } from 'utils/formatDate'; +import { TextCell } from 'component/common/Table/cells/TextCell/TextCell'; +import { useLocationSettings } from 'hooks/useLocationSettings'; interface IFeatureArchivedCellProps { value?: string | Date | null; diff --git a/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx b/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx index aa22d9a8f2..995810c38c 100644 --- a/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx +++ b/frontend/src/component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell.tsx @@ -1,4 +1,4 @@ -import { SyntheticEvent, VFC } from 'react'; +import { VFC } from 'react'; import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell'; import { Undo } from '@mui/icons-material'; import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; @@ -13,15 +13,10 @@ export const ReviveArchivedFeatureCell: VFC = ({ onRevive, project, }) => { - const handleClick = (e: SyntheticEvent) => { - e.preventDefault(); - onRevive(); - }; - return ( = { id: 'createdAt', desc: true }; +const { value, setValue } = createLocalStorage( + 'FeaturesArchiveTable:v1', + defaultSort +); export const FeaturesArchiveTable = () => { usePageTitle('Archived'); @@ -15,19 +18,11 @@ export const FeaturesArchiveTable = () => { refetchArchived, } = useFeaturesArchive(); - const [searchParams, setSearchParams] = useSearchParams(); - const { value, setValue } = createLocalStorage( - 'FeaturesArchiveTable:v1', - defaultSort - ); - return ( Date: Tue, 14 Jun 2022 09:16:27 +0200 Subject: [PATCH 27/33] fix archive row virtualization --- .../archive/ArchiveTable/ArchiveTable.tsx | 50 +++++++++++++------ 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx index 1db49013a8..e1a7a747ac 100644 --- a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx +++ b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx @@ -33,7 +33,6 @@ import { useSearch } from 'hooks/useSearch'; import { FeatureArchivedCell } from './FeatureArchivedCell/FeatureArchivedCell'; import { useVirtualizedRange } from 'hooks/useVirtualizedRange'; import { useSearchParams } from 'react-router-dom'; -import { useWindowVirtualizer } from '@tanstack/react-virtual'; export interface IFeaturesArchiveTableProps { archivedFeatures: FeatureSchema[]; @@ -69,19 +68,22 @@ export const ArchiveTable = ({ searchParams.get('search') || '' ); - const onRevive = useCallback(async (feature: string) => { - try { - await reviveFeature(feature); - await refetch(); - setToastData({ - type: 'success', - title: "And we're back!", - text: 'The feature toggle has been revived.', - }); - } catch (e: unknown) { - setToastApiError(formatUnknownError(e)); - } - }, []); + const onRevive = useCallback( + async (feature: string) => { + try { + await reviveFeature(feature); + await refetch(); + setToastData({ + type: 'success', + title: "And we're back!", + text: 'The feature toggle has been revived.', + }); + } catch (e: unknown) { + setToastApiError(formatUnknownError(e)); + } + }, + [refetch, reviveFeature, setToastApiError, setToastData] + ); const columns = useMemo( () => [ @@ -135,7 +137,7 @@ export const ArchiveTable = ({ searchable: true, maxWidth: 150, Cell: ({ value }: any) => ( - + ), }, { @@ -278,7 +280,15 @@ export const ArchiveTable = ({ - +
@@ -297,6 +307,14 @@ export const ArchiveTable = ({ {row.cells.map(cell => ( Date: Tue, 14 Jun 2022 09:19:41 +0200 Subject: [PATCH 28/33] hide project column on project archive --- .../archive/ArchiveTable/ArchiveTable.tsx | 33 ++++++++++++------- .../archive/ProjectFeaturesArchiveTable.tsx | 1 + 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx index e1a7a747ac..6e28a78080 100644 --- a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx +++ b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx @@ -45,6 +45,7 @@ export interface IFeaturesArchiveTableProps { | SortingRule | ((prev: SortingRule) => SortingRule) ) => SortingRule; + projectId?: string; } export const ArchiveTable = ({ @@ -54,6 +55,7 @@ export const ArchiveTable = ({ storedParams, setStoredParams, title, + projectId, }: IFeaturesArchiveTableProps) => { const rowHeight = theme.shape.tableRowHeight; const { classes } = useStyles(); @@ -129,17 +131,24 @@ export const ArchiveTable = ({ Cell: FeatureArchivedCell, sortType: 'date', }, - { - Header: 'Project ID', - accessor: 'project', - sortType: 'alphanumeric', - filterName: 'project', - searchable: true, - maxWidth: 150, - Cell: ({ value }: any) => ( - - ), - }, + ...(!projectId + ? [ + { + Header: 'Project ID', + accessor: 'project', + sortType: 'alphanumeric', + filterName: 'project', + searchable: true, + maxWidth: 150, + Cell: ({ value }: any) => ( + + ), + }, + ] + : []), { Header: 'Status', accessor: 'stale', @@ -169,7 +178,7 @@ export const ArchiveTable = ({ }, ], //eslint-disable-next-line - [] + [projectId] ); const { diff --git a/frontend/src/component/archive/ProjectFeaturesArchiveTable.tsx b/frontend/src/component/archive/ProjectFeaturesArchiveTable.tsx index a28553c53a..82cfd045e0 100644 --- a/frontend/src/component/archive/ProjectFeaturesArchiveTable.tsx +++ b/frontend/src/component/archive/ProjectFeaturesArchiveTable.tsx @@ -31,6 +31,7 @@ export const ProjectFeaturesArchiveTable = ({ storedParams={value} setStoredParams={setValue} refetch={refetchArchived} + projectId={projectId} /> ); }; From f35d3a488140f0fb6cf4051a736b7ebb27e47431 Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech Date: Tue, 14 Jun 2022 09:32:04 +0200 Subject: [PATCH 29/33] update table search and columns width --- .../archive/ArchiveTable/ArchiveTable.tsx | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx index 6e28a78080..26e1fbb5c2 100644 --- a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx +++ b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx @@ -7,7 +7,6 @@ import { TableCell, TablePlaceholder, TableRow, - TableSearch, } from 'component/common/Table'; import { SortingRule, useFlexLayout, useSortBy, useTable } from 'react-table'; import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; @@ -17,6 +16,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell'; import { DateCell } from 'component/common/Table/cells/DateCell/DateCell'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import { Search } from 'component/common/Search/Search'; import { FeatureTypeCell } from '../../common/Table/cells/FeatureTypeCell/FeatureTypeCell'; import { FeatureSeenCell } from '../../common/Table/cells/FeatureSeenCell/FeatureSeenCell'; import { LinkCell } from '../../common/Table/cells/LinkCell/LinkCell'; @@ -92,17 +92,19 @@ export const ArchiveTable = ({ { id: 'Seen', Header: 'Seen', - maxWidth: 85, + width: 85, canSort: true, Cell: FeatureSeenCell, accessor: 'lastSeenAt', + align: 'center', }, { id: 'Type', Header: 'Type', - maxWidth: 80, + width: 80, canSort: true, Cell: FeatureTypeCell, + align: 'center', }, { Header: 'Feature toggle name', @@ -120,14 +122,14 @@ export const ArchiveTable = ({ { Header: 'Created', accessor: 'createdAt', - minWidth: 120, + width: 150, Cell: DateCell, sortType: 'date', }, { Header: 'Archived', accessor: 'archivedAt', - minWidth: 120, + width: 150, Cell: FeatureArchivedCell, sortType: 'date', }, @@ -139,7 +141,7 @@ export const ArchiveTable = ({ sortType: 'alphanumeric', filterName: 'project', searchable: true, - maxWidth: 150, + maxWidth: 170, Cell: ({ value }: any) => ( - {rows.map((row, index) => { From 1d8c286c79c4635a9b7dd5c8053785533db34e99 Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech Date: Tue, 14 Jun 2022 09:36:33 +0200 Subject: [PATCH 30/33] fix archive type column --- frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx index 26e1fbb5c2..15862ac99c 100644 --- a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx +++ b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx @@ -99,9 +99,9 @@ export const ArchiveTable = ({ align: 'center', }, { - id: 'Type', Header: 'Type', - width: 80, + accessor: 'type', + width: 85, canSort: true, Cell: FeatureTypeCell, align: 'center', From 349106a2be596bc0240565812dae381d4f2c8ea0 Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech Date: Tue, 14 Jun 2022 11:14:56 +0200 Subject: [PATCH 31/33] fix: sort order across the app discussed with Nicolae and Nuno --- frontend/src/component/Reporting/ReportTable/ReportTable.tsx | 2 +- frontend/src/component/admin/users/UsersList/UsersList.tsx | 4 +++- .../feature/FeatureToggleList/FeatureToggleListTable.tsx | 2 +- .../FeatureMetricsTable/FeatureMetricsTable.tsx | 5 +---- .../Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx | 5 +---- .../src/component/segments/SegmentTable/SegmentTable.tsx | 2 +- frontend/src/utils/sortTypes.ts | 2 +- 7 files changed, 9 insertions(+), 13 deletions(-) diff --git a/frontend/src/component/Reporting/ReportTable/ReportTable.tsx b/frontend/src/component/Reporting/ReportTable/ReportTable.tsx index e692454fb2..ab35369951 100644 --- a/frontend/src/component/Reporting/ReportTable/ReportTable.tsx +++ b/frontend/src/component/Reporting/ReportTable/ReportTable.tsx @@ -55,7 +55,7 @@ export const ReportTable = ({ projectId, features }: IReportTableProps) => { const initialState = useMemo( () => ({ hiddenColumns: [], - sortBy: [{ id: 'createdAt', desc: true }], + sortBy: [{ id: 'createdAt' }], }), [] ); diff --git a/frontend/src/component/admin/users/UsersList/UsersList.tsx b/frontend/src/component/admin/users/UsersList/UsersList.tsx index 5c658955cd..a8e61b6335 100644 --- a/frontend/src/component/admin/users/UsersList/UsersList.tsx +++ b/frontend/src/component/admin/users/UsersList/UsersList.tsx @@ -119,6 +119,7 @@ const UsersList = () => { Cell: DateCell, disableGlobalFilter: true, sortType: 'date', + minWidth: 120, }, { Header: 'Avatar', @@ -169,6 +170,7 @@ const UsersList = () => { ), disableGlobalFilter: true, sortType: 'date', + minWidth: 150, }, { Header: 'Actions', @@ -193,7 +195,7 @@ const UsersList = () => { const initialState = useMemo(() => { return { - sortBy: [{ id: 'createdAt', desc: false }], + sortBy: [{ id: 'createdAt' }], hiddenColumns: isBillingUsers ? [] : ['type'], }; }, [isBillingUsers]); diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx index 4fb113786d..177a3e3e6a 100644 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx +++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx @@ -99,7 +99,7 @@ const columns = [ }, ]; -const defaultSort: SortingRule = { id: 'createdAt', desc: true }; +const defaultSort: SortingRule = { id: 'createdAt' }; const { value: storedParams, setValue: setStoredParams } = createLocalStorage( 'FeatureToggleListTable:v1', diff --git a/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetricsTable/FeatureMetricsTable.tsx b/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetricsTable/FeatureMetricsTable.tsx index c7a827caf9..912a7cc7ca 100644 --- a/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetricsTable/FeatureMetricsTable.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetricsTable/FeatureMetricsTable.tsx @@ -20,10 +20,7 @@ export const FeatureMetricsTable = ({ }: IFeatureMetricsTableProps) => { const isMediumScreen = useMediaQuery(theme.breakpoints.down('md')); - const initialState = useMemo( - () => ({ sortBy: [{ id: 'timestamp', desc: true }] }), - [] - ); + const initialState = useMemo(() => ({ sortBy: [{ id: 'timestamp' }] }), []); const { getTableProps, diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx index 2283f794d1..d20971c284 100644 --- a/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx +++ b/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx @@ -67,10 +67,7 @@ const staticColumns = ['Actions', 'name']; const defaultSort: SortingRule & { columns?: string[]; -} = { - id: 'createdAt', - desc: true, -}; +} = { id: 'createdAt' }; export const ProjectFeatureToggles = ({ features, diff --git a/frontend/src/component/segments/SegmentTable/SegmentTable.tsx b/frontend/src/component/segments/SegmentTable/SegmentTable.tsx index e6e0b5eb38..e59cd7e9bd 100644 --- a/frontend/src/component/segments/SegmentTable/SegmentTable.tsx +++ b/frontend/src/component/segments/SegmentTable/SegmentTable.tsx @@ -30,7 +30,7 @@ export const SegmentTable = () => { const { segments, loading } = useSegments(); const isSmallScreen = useMediaQuery(theme.breakpoints.down('md')); const [initialState] = useState({ - sortBy: [{ id: 'createdAt', desc: false }], + sortBy: [{ id: 'createdAt' }], hiddenColumns: ['description'], }); diff --git a/frontend/src/utils/sortTypes.ts b/frontend/src/utils/sortTypes.ts index fc8ba2badb..2fc43e3702 100644 --- a/frontend/src/utils/sortTypes.ts +++ b/frontend/src/utils/sortTypes.ts @@ -7,7 +7,7 @@ export const sortTypes = { date: (v1: any, v2: any, id: string) => { const a = new Date(v1?.values?.[id] || 0); const b = new Date(v2?.values?.[id] || 0); - return a?.getTime() - b?.getTime(); + return b?.getTime() - a?.getTime(); // newest first by default }, boolean: (v1: any, v2: any, id: string) => { const a = v1?.values?.[id]; From 7bfe7201c0d47ff139c9d6fab804de157ef91287 Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech Date: Tue, 14 Jun 2022 11:28:40 +0200 Subject: [PATCH 32/33] fix: simplified empty archive list page --- .../archive/ArchiveTable/ArchiveTable.tsx | 151 +++++++++--------- 1 file changed, 72 insertions(+), 79 deletions(-) diff --git a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx index 15862ac99c..5c5628faf1 100644 --- a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx +++ b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx @@ -283,86 +283,79 @@ export const ArchiveTable = ({ /> } > + +
+ + + {rows.map((row, index) => { + const isVirtual = + index < firstRenderedIndex || + index > lastRenderedIndex; + + if (isVirtual) { + return null; + } + + prepareRow(row); + return ( + + {row.cells.map(cell => ( + + {cell.render('Cell')} + + ))} + + ); + })} + +
+
} - elseShow={() => ( - <> - - - - - {rows.map((row, index) => { - const isVirtual = - index < firstRenderedIndex || - index > lastRenderedIndex; - - if (isVirtual) { - return null; - } - - prepareRow(row); - return ( - - {row.cells.map(cell => ( - - {cell.render('Cell')} - - ))} - - ); - })} - -
-
- 0 - } - show={ - - No feature toggles found matching “ - {searchValue}” - - } - /> - - )} + condition={rows.length === 0 && searchValue?.length > 0} + show={ + + No feature toggles found matching “ + {searchValue}” + + } + /> + + None of the feature toggles where archived yet. +
+ } />
); From ee2d852ef2c984318106add4584f0ec6c20756e1 Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech Date: Tue, 14 Jun 2022 11:48:49 +0200 Subject: [PATCH 33/33] fix: search clear bug --- .../archive/ArchiveTable/ArchiveTable.tsx | 14 ++++++-------- frontend/src/component/common/Search/Search.tsx | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx index 5c5628faf1..1978526139 100644 --- a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx +++ b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx @@ -271,14 +271,12 @@ export const ArchiveTable = ({ : data.length })`} actions={ - <> - - + } /> } diff --git a/frontend/src/component/common/Search/Search.tsx b/frontend/src/component/common/Search/Search.tsx index 2d7247b46c..24c2421fcb 100644 --- a/frontend/src/component/common/Search/Search.tsx +++ b/frontend/src/component/common/Search/Search.tsx @@ -93,7 +93,7 @@ export const Search = ({ { - onChange(''); + onSearchChange(''); ref.current?.focus(); }} >