mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-14 01:16:17 +02:00
feat: fetch change request overview in project overview (#6683)
This commit is contained in:
parent
9ecd81ebb4
commit
501da974d6
@ -4,12 +4,10 @@ import { PageContent } from 'component/common/PageContent/PageContent';
|
|||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
import { DateCell } from 'component/common/Table/cells/DateCell/DateCell';
|
import { DateCell } from 'component/common/Table/cells/DateCell/DateCell';
|
||||||
import { FeatureTypeCell } from 'component/common/Table/cells/FeatureTypeCell/FeatureTypeCell';
|
import { FeatureTypeCell } from 'component/common/Table/cells/FeatureTypeCell/FeatureTypeCell';
|
||||||
import type { IProject } from 'interfaces/project';
|
|
||||||
import { PaginatedTable } from 'component/common/Table';
|
import { PaginatedTable } from 'component/common/Table';
|
||||||
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
||||||
import { FavoriteIconHeader } from 'component/common/Table/FavoriteIconHeader/FavoriteIconHeader';
|
import { FavoriteIconHeader } from 'component/common/Table/FavoriteIconHeader/FavoriteIconHeader';
|
||||||
import { FavoriteIconCell } from 'component/common/Table/cells/FavoriteIconCell/FavoriteIconCell';
|
import { FavoriteIconCell } from 'component/common/Table/cells/FavoriteIconCell/FavoriteIconCell';
|
||||||
import type { ProjectEnvironmentType } from '../ProjectFeatureToggles/hooks/useEnvironmentsRef';
|
|
||||||
import { ActionsCell } from '../ProjectFeatureToggles/ActionsCell/ActionsCell';
|
import { ActionsCell } from '../ProjectFeatureToggles/ActionsCell/ActionsCell';
|
||||||
import { ExperimentalColumnsMenu as ColumnsMenu } from './ExperimentalColumnsMenu/ExperimentalColumnsMenu';
|
import { ExperimentalColumnsMenu as ColumnsMenu } from './ExperimentalColumnsMenu/ExperimentalColumnsMenu';
|
||||||
import { useFavoriteFeaturesApi } from 'hooks/api/actions/useFavoriteFeaturesApi/useFavoriteFeaturesApi';
|
import { useFavoriteFeaturesApi } from 'hooks/api/actions/useFavoriteFeaturesApi/useFavoriteFeaturesApi';
|
||||||
@ -53,7 +51,7 @@ import { FeatureTagCell } from 'component/common/Table/cells/FeatureTagCell/Feat
|
|||||||
import { useSelectedData } from './hooks/useSelectedData';
|
import { useSelectedData } from './hooks/useSelectedData';
|
||||||
|
|
||||||
interface IPaginatedProjectFeatureTogglesProps {
|
interface IPaginatedProjectFeatureTogglesProps {
|
||||||
environments: IProject['environments'];
|
environments: string[];
|
||||||
refreshInterval?: number;
|
refreshInterval?: number;
|
||||||
storageKey?: string;
|
storageKey?: string;
|
||||||
}
|
}
|
||||||
@ -226,11 +224,8 @@ export const ProjectFeatureToggles = ({
|
|||||||
header: 'Created',
|
header: 'Created',
|
||||||
cell: DateCell,
|
cell: DateCell,
|
||||||
}),
|
}),
|
||||||
...environments.map(
|
...environments.map((name: string) => {
|
||||||
(projectEnvironment: ProjectEnvironmentType) => {
|
const isChangeRequestEnabled = isChangeRequestConfigured(name);
|
||||||
const name = projectEnvironment.environment;
|
|
||||||
const isChangeRequestEnabled =
|
|
||||||
isChangeRequestConfigured(name);
|
|
||||||
|
|
||||||
return columnHelper.accessor(
|
return columnHelper.accessor(
|
||||||
(row) => ({
|
(row) => ({
|
||||||
@ -281,8 +276,7 @@ export const ProjectFeatureToggles = ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
}),
|
||||||
),
|
|
||||||
columnHelper.display({
|
columnHelper.display({
|
||||||
id: 'actions',
|
id: 'actions',
|
||||||
header: '',
|
header: '',
|
||||||
@ -391,9 +385,7 @@ export const ProjectFeatureToggles = ({
|
|||||||
setTableState({ query });
|
setTableState({ query });
|
||||||
}}
|
}}
|
||||||
dataToExport={data}
|
dataToExport={data}
|
||||||
environmentsToExport={environments.map(
|
environmentsToExport={environments}
|
||||||
({ environment }) => environment,
|
|
||||||
)}
|
|
||||||
actions={
|
actions={
|
||||||
<ColumnsMenu
|
<ColumnsMenu
|
||||||
columns={[
|
columns={[
|
||||||
@ -426,7 +418,7 @@ export const ProjectFeatureToggles = ({
|
|||||||
{
|
{
|
||||||
id: 'divider',
|
id: 'divider',
|
||||||
},
|
},
|
||||||
...environments.map(({ environment }) => ({
|
...environments.map((environment) => ({
|
||||||
header: environment,
|
header: environment,
|
||||||
id: formatEnvironmentColumnId(
|
id: formatEnvironmentColumnId(
|
||||||
environment,
|
environment,
|
||||||
@ -478,9 +470,7 @@ export const ProjectFeatureToggles = ({
|
|||||||
showExportDialog={showExportDialog}
|
showExportDialog={showExportDialog}
|
||||||
data={data}
|
data={data}
|
||||||
onClose={() => setShowExportDialog(false)}
|
onClose={() => setShowExportDialog(false)}
|
||||||
environments={environments.map(
|
environments={environments}
|
||||||
({ environment }) => environment,
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -10,7 +10,7 @@ import useProjectOverview, {
|
|||||||
import { usePageTitle } from 'hooks/usePageTitle';
|
import { usePageTitle } from 'hooks/usePageTitle';
|
||||||
import { useLastViewedProject } from 'hooks/useLastViewedProject';
|
import { useLastViewedProject } from 'hooks/useLastViewedProject';
|
||||||
import { useUiFlag } from 'hooks/useUiFlag';
|
import { useUiFlag } from 'hooks/useUiFlag';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ProjectOverviewChangeRequests } from './ProjectOverviewChangeRequests';
|
||||||
|
|
||||||
const refreshInterval = 15 * 1000;
|
const refreshInterval = 15 * 1000;
|
||||||
|
|
||||||
@ -39,6 +39,17 @@ const ProjectOverview: FC<{
|
|||||||
storageKey?: string;
|
storageKey?: string;
|
||||||
}> = ({ storageKey = 'project-overview-v2' }) => {
|
}> = ({ storageKey = 'project-overview-v2' }) => {
|
||||||
const projectOverviewRefactor = useUiFlag('projectOverviewRefactor');
|
const projectOverviewRefactor = useUiFlag('projectOverviewRefactor');
|
||||||
|
|
||||||
|
if (projectOverviewRefactor) {
|
||||||
|
return <NewProjectOverview storageKey={storageKey} />;
|
||||||
|
} else {
|
||||||
|
return <OldProjectOverview storageKey={storageKey} />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const OldProjectOverview: FC<{
|
||||||
|
storageKey?: string;
|
||||||
|
}> = ({ storageKey = 'project-overview-v2' }) => {
|
||||||
const projectId = useRequiredPathParam('projectId');
|
const projectId = useRequiredPathParam('projectId');
|
||||||
const projectName = useProjectOverviewNameOrId(projectId);
|
const projectName = useProjectOverviewNameOrId(projectId);
|
||||||
const { project } = useProjectOverview(projectId, {
|
const { project } = useProjectOverview(projectId, {
|
||||||
@ -61,9 +72,6 @@ const ProjectOverview: FC<{
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer key={projectId}>
|
<StyledContainer key={projectId}>
|
||||||
<ConditionallyRender
|
|
||||||
condition={!projectOverviewRefactor}
|
|
||||||
show={
|
|
||||||
<ProjectInfo
|
<ProjectInfo
|
||||||
id={projectId}
|
id={projectId}
|
||||||
description={description}
|
description={description}
|
||||||
@ -72,18 +80,50 @@ const ProjectOverview: FC<{
|
|||||||
featureTypeCounts={featureTypeCounts}
|
featureTypeCounts={featureTypeCounts}
|
||||||
stats={stats}
|
stats={stats}
|
||||||
/>
|
/>
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<StyledContentContainer>
|
<StyledContentContainer>
|
||||||
<ConditionallyRender
|
<ProjectStats stats={project.stats} />
|
||||||
condition={!projectOverviewRefactor}
|
|
||||||
show={<ProjectStats stats={project.stats} />}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<StyledProjectToggles>
|
<StyledProjectToggles>
|
||||||
<ProjectFeatureToggles
|
<ProjectFeatureToggles
|
||||||
environments={environments}
|
environments={environments.map(
|
||||||
|
(environment) => environment.environment,
|
||||||
|
)}
|
||||||
|
refreshInterval={refreshInterval}
|
||||||
|
storageKey={storageKey}
|
||||||
|
/>
|
||||||
|
</StyledProjectToggles>
|
||||||
|
</StyledContentContainer>
|
||||||
|
</StyledContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const NewProjectOverview: FC<{
|
||||||
|
storageKey?: string;
|
||||||
|
}> = ({ storageKey = 'project-overview-v2' }) => {
|
||||||
|
const projectId = useRequiredPathParam('projectId');
|
||||||
|
const projectName = useProjectOverviewNameOrId(projectId);
|
||||||
|
|
||||||
|
const { project } = useProjectOverview(projectId, {
|
||||||
|
refreshInterval,
|
||||||
|
});
|
||||||
|
|
||||||
|
usePageTitle(`Project overview – ${projectName}`);
|
||||||
|
const { setLastViewed } = useLastViewedProject();
|
||||||
|
useEffect(() => {
|
||||||
|
setLastViewed(projectId);
|
||||||
|
}, [projectId, setLastViewed]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledContainer key={projectId}>
|
||||||
|
<StyledContentContainer>
|
||||||
|
<ProjectOverviewChangeRequests project={projectId} />
|
||||||
|
|
||||||
|
<StyledProjectToggles>
|
||||||
|
<ProjectFeatureToggles
|
||||||
|
environments={project.environments.map(
|
||||||
|
(environment) => environment.environment,
|
||||||
|
)}
|
||||||
refreshInterval={refreshInterval}
|
refreshInterval={refreshInterval}
|
||||||
storageKey={storageKey}
|
storageKey={storageKey}
|
||||||
/>
|
/>
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
import { screen } from '@testing-library/react';
|
||||||
|
import { render } from 'utils/testRenderer';
|
||||||
|
import { testServerRoute, testServerSetup } from 'utils/testServer';
|
||||||
|
import { ProjectOverviewChangeRequests } from './ProjectOverviewChangeRequests';
|
||||||
|
|
||||||
|
const server = testServerSetup();
|
||||||
|
|
||||||
|
const setupEnterpriseApi = () => {
|
||||||
|
testServerRoute(server, '/api/admin/ui-config', {
|
||||||
|
versionInfo: {
|
||||||
|
current: { enterprise: 'present' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
testServerRoute(
|
||||||
|
server,
|
||||||
|
'/api/admin/projects/default/change-requests/config',
|
||||||
|
[
|
||||||
|
{
|
||||||
|
environment: 'default',
|
||||||
|
changeRequestEnabled: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
testServerRoute(
|
||||||
|
server,
|
||||||
|
'/api/admin/projects/default/change-requests/count',
|
||||||
|
{
|
||||||
|
total: 14,
|
||||||
|
approved: 2,
|
||||||
|
applied: 0,
|
||||||
|
rejected: 0,
|
||||||
|
reviewRequired: 10,
|
||||||
|
scheduled: 2,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
test('Show change requests count', async () => {
|
||||||
|
setupEnterpriseApi();
|
||||||
|
render(<ProjectOverviewChangeRequests project='default' />);
|
||||||
|
|
||||||
|
await screen.findByText('4');
|
||||||
|
await screen.findByText('10');
|
||||||
|
await screen.findByText('View change requests');
|
||||||
|
});
|
@ -1,6 +1,8 @@
|
|||||||
import { Box, styled, Typography } from '@mui/material';
|
import { Box, styled, Typography } from '@mui/material';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
|
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
||||||
|
import { useChangeRequestsCount } from 'hooks/api/getters/useChangeRequestsCount/useChangeRequestsCount';
|
||||||
|
|
||||||
export const ChangeRequestContainer = styled(Box)(({ theme }) => ({
|
export const ChangeRequestContainer = styled(Box)(({ theme }) => ({
|
||||||
margin: '0',
|
margin: '0',
|
||||||
@ -42,16 +44,27 @@ const ChangeRequestCount = styled(Typography)(({ theme }) => ({
|
|||||||
export const ProjectOverviewChangeRequests: FC<{ project: string }> = ({
|
export const ProjectOverviewChangeRequests: FC<{ project: string }> = ({
|
||||||
project,
|
project,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { isChangeRequestConfiguredInAnyEnv } =
|
||||||
|
useChangeRequestsEnabled(project);
|
||||||
|
const { data } = useChangeRequestsCount(project);
|
||||||
|
|
||||||
|
if (!isChangeRequestConfiguredInAnyEnv) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const toBeApplied = data.scheduled + data.approved;
|
||||||
|
const toBeReviewed = data.reviewRequired;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChangeRequestContainer>
|
<ChangeRequestContainer>
|
||||||
<Box>Open change requests</Box>
|
<Box>Open change requests</Box>
|
||||||
<ApplyBox>
|
<ApplyBox>
|
||||||
<span>To be applied</span>
|
<span>To be applied</span>
|
||||||
<ChangeRequestCount>10</ChangeRequestCount>
|
<ChangeRequestCount>{toBeApplied}</ChangeRequestCount>
|
||||||
</ApplyBox>
|
</ApplyBox>
|
||||||
<ReviewBox>
|
<ReviewBox>
|
||||||
<span>To be reviewed</span>
|
<span>To be reviewed</span>
|
||||||
<ChangeRequestCount>20</ChangeRequestCount>
|
<ChangeRequestCount>{toBeReviewed}</ChangeRequestCount>
|
||||||
</ReviewBox>
|
</ReviewBox>
|
||||||
<Link to={`/projects/${project}/change-requests`}>
|
<Link to={`/projects/${project}/change-requests`}>
|
||||||
View change requests
|
View change requests
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
import { formatApiPath } from 'utils/formatPath';
|
||||||
|
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||||
|
import { useConditionalSWR } from '../useConditionalSWR/useConditionalSWR';
|
||||||
|
import useUiConfig from '../useUiConfig/useUiConfig';
|
||||||
|
import type { ChangeRequestsCountSchema } from '../../../../openapi';
|
||||||
|
|
||||||
|
const fallback: ChangeRequestsCountSchema = {
|
||||||
|
applied: 0,
|
||||||
|
approved: 0,
|
||||||
|
rejected: 0,
|
||||||
|
scheduled: 0,
|
||||||
|
reviewRequired: 0,
|
||||||
|
total: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useChangeRequestsCount = (projectId: string) => {
|
||||||
|
const { isEnterprise } = useUiConfig();
|
||||||
|
const { data, error, mutate } =
|
||||||
|
useConditionalSWR<ChangeRequestsCountSchema>(
|
||||||
|
Boolean(projectId) && isEnterprise(),
|
||||||
|
fallback,
|
||||||
|
formatApiPath(
|
||||||
|
`api/admin/projects/${projectId}/change-requests/count`,
|
||||||
|
),
|
||||||
|
fetcher,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
data: data || fallback,
|
||||||
|
loading: !error && !data,
|
||||||
|
refetchChangeRequestConfig: mutate,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetcher = (path: string) => {
|
||||||
|
return fetch(path)
|
||||||
|
.then(handleErrorResponses('Request changes'))
|
||||||
|
.then((res) => res.json());
|
||||||
|
};
|
23
frontend/src/openapi/models/changeRequestsCountSchema.ts
Normal file
23
frontend/src/openapi/models/changeRequestsCountSchema.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Generated by Orval
|
||||||
|
* Do not edit manually.
|
||||||
|
* See `gen:api` script in package.json
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count of change requests in different stages of the [process](https://docs.getunleash.io/reference/change-requests#change-request-flow).
|
||||||
|
*/
|
||||||
|
export interface ChangeRequestsCountSchema {
|
||||||
|
/** The number of applied change requests */
|
||||||
|
applied: number;
|
||||||
|
/** The number of approved change requests */
|
||||||
|
approved: number;
|
||||||
|
/** The number of rejected change requests */
|
||||||
|
rejected: number;
|
||||||
|
/** The number of change requests awaiting the review */
|
||||||
|
reviewRequired: number;
|
||||||
|
/** The number of scheduled change requests */
|
||||||
|
scheduled: number;
|
||||||
|
/** The number of total change requests in this project */
|
||||||
|
total: number;
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* Generated by Orval
|
||||||
|
* Do not edit manually.
|
||||||
|
* See `gen:api` script in package.json
|
||||||
|
*/
|
||||||
|
import type { FeatureStrategySchema } from './featureStrategySchema';
|
||||||
|
import type { VariantSchema } from './variantSchema';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A detailed description of the feature environment
|
||||||
|
*/
|
||||||
|
export interface FeatureSearchEnvironmentSchema {
|
||||||
|
/** `true` if the feature is enabled for the environment, otherwise `false`. */
|
||||||
|
enabled: boolean;
|
||||||
|
/** The name of the environment */
|
||||||
|
environment?: string;
|
||||||
|
/** The name of the feature */
|
||||||
|
featureName?: string;
|
||||||
|
/** Whether the feature has any enabled strategies defined. */
|
||||||
|
hasEnabledStrategies?: boolean;
|
||||||
|
/** Whether the feature has any strategies defined. */
|
||||||
|
hasStrategies?: boolean;
|
||||||
|
/** The date when metrics where last collected for the feature environment */
|
||||||
|
lastSeenAt?: string | null;
|
||||||
|
/** The name of the environment */
|
||||||
|
name: string;
|
||||||
|
/** How many times the toggle evaluated to false in last hour bucket */
|
||||||
|
no?: number;
|
||||||
|
/** The sort order of the feature environment in the feature environments list */
|
||||||
|
sortOrder?: number;
|
||||||
|
/** A list of activation strategies for the feature environment */
|
||||||
|
strategies?: FeatureStrategySchema[];
|
||||||
|
/** The type of the environment */
|
||||||
|
type?: string;
|
||||||
|
/** The number of defined variants */
|
||||||
|
variantCount?: number;
|
||||||
|
/** A list of variants for the feature environment */
|
||||||
|
variants?: VariantSchema[];
|
||||||
|
/** How many times the toggle evaluated to true in last hour bucket */
|
||||||
|
yes?: number;
|
||||||
|
}
|
@ -4,7 +4,7 @@
|
|||||||
* See `gen:api` script in package.json
|
* See `gen:api` script in package.json
|
||||||
*/
|
*/
|
||||||
import type { FeatureSearchResponseSchemaDependenciesItem } from './featureSearchResponseSchemaDependenciesItem';
|
import type { FeatureSearchResponseSchemaDependenciesItem } from './featureSearchResponseSchemaDependenciesItem';
|
||||||
import type { FeatureEnvironmentSchema } from './featureEnvironmentSchema';
|
import type { FeatureSearchEnvironmentSchema } from './featureSearchEnvironmentSchema';
|
||||||
import type { FeatureSearchResponseSchemaStrategiesItem } from './featureSearchResponseSchemaStrategiesItem';
|
import type { FeatureSearchResponseSchemaStrategiesItem } from './featureSearchResponseSchemaStrategiesItem';
|
||||||
import type { TagSchema } from './tagSchema';
|
import type { TagSchema } from './tagSchema';
|
||||||
import type { VariantSchema } from './variantSchema';
|
import type { VariantSchema } from './variantSchema';
|
||||||
@ -28,7 +28,7 @@ export interface FeatureSearchResponseSchema {
|
|||||||
/** `true` if the feature is enabled, otherwise `false`. */
|
/** `true` if the feature is enabled, otherwise `false`. */
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
/** The list of environments where the feature can be used */
|
/** The list of environments where the feature can be used */
|
||||||
environments?: FeatureEnvironmentSchema[];
|
environments?: FeatureSearchEnvironmentSchema[];
|
||||||
/** `true` if the feature was favorited, otherwise `false`. */
|
/** `true` if the feature was favorited, otherwise `false`. */
|
||||||
favorite?: boolean;
|
favorite?: boolean;
|
||||||
/** `true` if the impression data collection is enabled for the feature, otherwise `false`. */
|
/** `true` if the impression data collection is enabled for the feature, otherwise `false`. */
|
||||||
|
@ -271,6 +271,7 @@ export * from './changeRequestStateSchemaOneOfState';
|
|||||||
export * from './changeRequestStateSchemaOneOfThree';
|
export * from './changeRequestStateSchemaOneOfThree';
|
||||||
export * from './changeRequestStateSchemaOneOfThreeState';
|
export * from './changeRequestStateSchemaOneOfThreeState';
|
||||||
export * from './changeRequestUpdateTitleSchema';
|
export * from './changeRequestUpdateTitleSchema';
|
||||||
|
export * from './changeRequestsCountSchema';
|
||||||
export * from './changeRequestsSchema';
|
export * from './changeRequestsSchema';
|
||||||
export * from './changeRoleForGroup401';
|
export * from './changeRoleForGroup401';
|
||||||
export * from './changeRoleForGroup403';
|
export * from './changeRoleForGroup403';
|
||||||
@ -538,6 +539,7 @@ export * from './featureMetricsSchema';
|
|||||||
export * from './featureSchema';
|
export * from './featureSchema';
|
||||||
export * from './featureSchemaDependenciesItem';
|
export * from './featureSchemaDependenciesItem';
|
||||||
export * from './featureSchemaStrategiesItem';
|
export * from './featureSchemaStrategiesItem';
|
||||||
|
export * from './featureSearchEnvironmentSchema';
|
||||||
export * from './featureSearchResponseSchema';
|
export * from './featureSearchResponseSchema';
|
||||||
export * from './featureSearchResponseSchemaDependenciesItem';
|
export * from './featureSearchResponseSchemaDependenciesItem';
|
||||||
export * from './featureSearchResponseSchemaStrategiesItem';
|
export * from './featureSearchResponseSchemaStrategiesItem';
|
||||||
|
Loading…
Reference in New Issue
Block a user