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 d3ca563b0a..4bc2cce4b0 100644 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx +++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx @@ -30,7 +30,7 @@ import { FeatureStaleCell } from './FeatureStaleCell/FeatureStaleCell'; import { useStyles } from './styles'; import { useSearch } from 'hooks/useSearch'; -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: '-', @@ -38,7 +38,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]) }),