diff --git a/frontend/src/component/application/ApplicationChart.tsx b/frontend/src/component/application/ApplicationChart.tsx index 6a88e16c2e..9d3a26a7cb 100644 --- a/frontend/src/component/application/ApplicationChart.tsx +++ b/frontend/src/component/application/ApplicationChart.tsx @@ -1,14 +1,17 @@ import { Box, Divider, styled, Typography, useTheme } from '@mui/material'; import { ArcherContainer, ArcherElement } from 'react-archer'; -import { ConditionallyRender } from '../common/ConditionallyRender/ConditionallyRender'; import { useNavigate } from 'react-router-dom'; import { FC, useLayoutEffect, useRef, useState } from 'react'; -import { ApplicationOverviewSchema } from '../../openapi'; +import { + ApplicationOverviewEnvironmentSchema, + ApplicationOverviewSchema, +} from '../../openapi'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { HelpIcon } from '../common/HelpIcon/HelpIcon'; import { CloudCircle, Flag, WarningAmberRounded } from '@mui/icons-material'; import TimeAgo from 'react-timeago'; import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; +import { getApplicationIssueMode } from './ApplicationIssues/ApplicationIssues'; const StyledTable = styled('table')(({ theme }) => ({ fontSize: theme.fontSizes.smallerBody, @@ -100,6 +103,15 @@ const StyledText = styled(Box)(({ theme }) => ({ alignItems: 'center', })); +interface IApplicationChartProps { + data: ApplicationOverviewSchema; +} + +interface IApplicationCountersProps { + environmentCount: number; + featureCount: number; +} + const useElementWidth = () => { const elementRef = useRef(null); const [width, setWidth] = useState('100%'); @@ -135,15 +147,6 @@ const WarningStatus: FC = ({ children }) => ( ); -interface IApplicationChartProps { - data: ApplicationOverviewSchema; -} - -interface IApplicationCountersProps { - environmentCount: number; - featureCount: number; -} - const ApplicationCounters = ({ environmentCount, featureCount, @@ -173,6 +176,16 @@ const useTracking = () => { }; }; +const getEnvironmentMode = ( + environment: ApplicationOverviewEnvironmentSchema, +) => { + return environment.issues.missingFeatures.length + + environment.issues.outdatedSdks.length === + 0 + ? 'success' + : 'warning'; +}; + export const ApplicationChart = ({ data }: IApplicationChartProps) => { const trackClick = useTracking(); const applicationName = useRequiredPathParam('name'); @@ -180,8 +193,7 @@ export const ApplicationChart = ({ data }: IApplicationChartProps) => { const navigate = useNavigate(); const theme = useTheme(); - const mode: 'success' | 'warning' = - data.issues.length === 0 ? 'success' : 'warning'; + const mode = getApplicationIssueMode(data); return ( @@ -198,13 +210,14 @@ export const ApplicationChart = ({ data }: IApplicationChartProps) => { sourceAnchor: 'bottom', style: { strokeColor: - mode === 'success' + getEnvironmentMode(environment) === + 'success' ? theme.palette.secondary.border : theme.palette.warning.border, }, }))} > - + ({ fontSize: theme.fontSizes.smallerBody, @@ -225,18 +238,14 @@ export const ApplicationChart = ({ data }: IApplicationChartProps) => { environmentCount={data.environments.length} featureCount={data.featureCount} /> - - - } - elseShow={ - - {data.issues.length} issues detected - - } - /> + {mode.applicationMode === 'success' ? ( + + ) : ( + + {mode.issueCount} issues detected + + )} @@ -248,7 +257,7 @@ export const ApplicationChart = ({ data }: IApplicationChartProps) => { key={environment.name} > { diff --git a/frontend/src/component/application/ApplicationIssues/ApplicationIssues.test.tsx b/frontend/src/component/application/ApplicationIssues/ApplicationIssues.test.tsx index debcede200..4ca41fed2b 100644 --- a/frontend/src/component/application/ApplicationIssues/ApplicationIssues.test.tsx +++ b/frontend/src/component/application/ApplicationIssues/ApplicationIssues.test.tsx @@ -1,24 +1,29 @@ import { screen } from '@testing-library/react'; import { render } from 'utils/testRenderer'; import { ApplicationIssues } from './ApplicationIssues'; -import { ApplicationOverviewIssuesSchema } from 'openapi'; +import { ApplicationOverviewSchema } from 'openapi'; test('Display all application issues', async () => { - const issues: ApplicationOverviewIssuesSchema[] = [ - { - type: 'missingFeatures', - items: ['my-app'], + const application: ApplicationOverviewSchema = { + projects: ['default'], + featureCount: 0, + environments: [ + { + issues: { + missingFeatures: ['my-app'], + outdatedSdks: ['unleash-client-php:1.13.0'], + }, + sdks: [], + instanceCount: 0, + lastSeen: new Date().toISOString(), + name: 'development', + }, + ], + issues: { + missingStrategies: ['defaultStrategy', 'mainStrategy'], }, - { - type: 'missingStrategies', - items: ['defaultStrategy', 'mainStrategy'], - }, - { - type: 'outdatedSdks', - items: ['unleash-client-php:1.13.0'], - }, - ]; - render(); + }; + render(); await screen.findByText('my-app'); await screen.findByText('mainStrategy'); diff --git a/frontend/src/component/application/ApplicationIssues/ApplicationIssues.tsx b/frontend/src/component/application/ApplicationIssues/ApplicationIssues.tsx index ec81bd69ec..1210cdff2e 100644 --- a/frontend/src/component/application/ApplicationIssues/ApplicationIssues.tsx +++ b/frontend/src/component/application/ApplicationIssues/ApplicationIssues.tsx @@ -1,7 +1,7 @@ import { Box, styled } from '@mui/material'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { WarningAmberRounded } from '@mui/icons-material'; -import { ApplicationOverviewIssuesSchema } from 'openapi'; +import { ApplicationOverviewSchema } from 'openapi'; import { Link } from 'react-router-dom'; import { CREATE_FEATURE, @@ -80,93 +80,173 @@ const StyledLink = styled(Link)(() => ({ })); export interface IApplicationIssuesProps { - issues: ApplicationOverviewIssuesSchema[]; + application: ApplicationOverviewSchema; } -const resolveIssueText = (issue: ApplicationOverviewIssuesSchema) => { - const issueCount = issue.items.length; - let issueText = ''; +export interface IFeaturesMissingProps { + features: string[]; +} - if (issue.type === 'outdatedSdks') { - return 'We detected the following outdated SDKs'; - } +interface IStrategiesMissingProps { + strategies: string[]; +} - switch (issue.type) { - case 'missingFeatures': - issueText = `feature flag${issueCount !== 1 ? 's' : ''}`; - break; - case 'missingStrategies': - issueText = `strategy type${issueCount !== 1 ? 's' : ''}`; - break; - } +interface IOutdatedSDKsProps { + sdks: string[]; +} - return `We detected ${issueCount} ${issueText} defined in the SDK that ${ - issueCount !== 1 ? 'do' : 'does' - } not exist in Unleash`; -}; - -export const ApplicationIssues = ({ issues }: IApplicationIssuesProps) => { +const FeaturesMissing = ({ features }: IFeaturesMissingProps) => { const { hasAccess } = useContext(AccessContext); + const length = features.length; + + if (length === 0) { + return null; + } + return ( - 0} - show={ - - - - - We detected {issues.length} issue - {issues.length !== 1 ? 's' : ''} in this application - - - - {issues.map((issue) => ( - - {resolveIssueText(issue)} - - {issue.items.map((item) => ( - - - {item} - - - Create feature flag - - } - /> - - Create strategy type - - } - /> - - ))} - - - ))} - - - } - /> + + {`We detected ${length} feature flag${ + length !== 1 ? 's' : '' + } defined in the SDK that ${ + length !== 1 ? 'do' : 'does' + } not exist in Unleash`} + + {features.map((feature) => ( + + {feature} + + Create feature flag + + } + /> + + ))} + + + ); +}; + +const StrategiesMissing = ({ strategies }: IStrategiesMissingProps) => { + const { hasAccess } = useContext(AccessContext); + const length = strategies.length; + + if (length === 0) { + return null; + } + return ( + + {`We detected ${length} strategy type${ + length !== 1 ? 's' : '' + } defined in the SDK that ${ + length !== 1 ? 'do' : 'does' + } not exist in Unleash`} + + {strategies.map((strategy) => ( + + {strategy} + + Create strategy type + + } + /> + + ))} + + + ); +}; + +const OutdatedSDKs = ({ sdks }: IOutdatedSDKsProps) => { + if (sdks.length === 0) { + return null; + } + return ( + + We detected the following outdated SDKs + + {sdks.map((sdk) => ( + + {sdk} + + ))} + + + ); +}; + +export const getApplicationIssueMode = ( + application: ApplicationOverviewSchema, +): + | { + applicationMode: 'success'; + } + | { + applicationMode: 'warning'; + issueCount: number; + } => { + const issueCount = + application.issues.missingStrategies.length + + application.environments + .map( + (env) => + env.issues.missingFeatures.length + + env.issues.outdatedSdks.length, + ) + .reduce((sum, num) => sum + num, 0); + + return { + issueCount, + applicationMode: issueCount > 0 ? 'warning' : 'success', + }; +}; + +export const ApplicationIssues = ({ application }: IApplicationIssuesProps) => { + const mode = getApplicationIssueMode(application); + + if (mode.applicationMode === 'success') { + return null; + } + const outdatedSdks = [ + ...new Set( + application.environments.flatMap((env) => env.issues.outdatedSdks), + ), + ]; + const missingFeatures = [ + ...new Set( + application.environments.flatMap( + (env) => env.issues.missingFeatures, + ), + ), + ]; + const issueCount = mode.issueCount; + return ( + + + + + We detected {issueCount} issue + {issueCount !== 1 ? 's' : ''} in this application + + + + + + + + ); }; diff --git a/frontend/src/component/application/ApplicationOverview.test.tsx b/frontend/src/component/application/ApplicationOverview.test.tsx index c20a41cb65..8d3032c190 100644 --- a/frontend/src/component/application/ApplicationOverview.test.tsx +++ b/frontend/src/component/application/ApplicationOverview.test.tsx @@ -24,9 +24,13 @@ test('Display application overview with environments', async () => { instanceCount: 999, lastSeen: new Date().toISOString(), sdks: ['unleash-client-node:5.5.0-beta.0'], + issues: { + missingFeatures: [], + outdatedSdks: [], + }, }, ], - issues: [], + issues: { missingStrategies: [] }, featureCount: 1, projects: ['default'], }); @@ -54,7 +58,9 @@ test('Display application overview without environments', async () => { setupApi({ environments: [], featureCount: 0, - issues: [], + issues: { + missingStrategies: [], + }, projects: ['default'], }); render( @@ -81,18 +87,15 @@ test('Display application with issues', async () => { instanceCount: 999, lastSeen: new Date().toISOString(), sdks: ['unleash-client-node:5.5.0-beta.0'], + issues: { + missingFeatures: ['feature1'], + outdatedSdks: [], + }, }, ], - issues: [ - { - type: 'missingFeatures', - items: ['feature1'], - }, - { - type: 'missingStrategies', - items: ['strategy1'], - }, - ], + issues: { + missingStrategies: ['strategy1'], + }, featureCount: 1, projects: ['default'], }); diff --git a/frontend/src/component/application/ApplicationOverview.tsx b/frontend/src/component/application/ApplicationOverview.tsx index 6dea4c9034..693cce55c4 100644 --- a/frontend/src/component/application/ApplicationOverview.tsx +++ b/frontend/src/component/application/ApplicationOverview.tsx @@ -75,7 +75,7 @@ const ApplicationOverview = () => { ))} - + } diff --git a/frontend/src/hooks/api/getters/useApplicationOverview/useApplicationOverview.ts b/frontend/src/hooks/api/getters/useApplicationOverview/useApplicationOverview.ts index 27a2681d0d..2e317e4f33 100644 --- a/frontend/src/hooks/api/getters/useApplicationOverview/useApplicationOverview.ts +++ b/frontend/src/hooks/api/getters/useApplicationOverview/useApplicationOverview.ts @@ -7,7 +7,9 @@ const placeHolderApplication: ApplicationOverviewSchema = { environments: [], featureCount: 0, projects: [], - issues: [], + issues: { + missingStrategies: [], + }, }; export const useApplicationOverview = ( application: string, diff --git a/frontend/src/openapi/models/applicationEnvironmentIssuesSchema.ts b/frontend/src/openapi/models/applicationEnvironmentIssuesSchema.ts new file mode 100644 index 0000000000..c0cd470b88 --- /dev/null +++ b/frontend/src/openapi/models/applicationEnvironmentIssuesSchema.ts @@ -0,0 +1,15 @@ +/** + * Generated by Orval + * Do not edit manually. + * See `gen:api` script in package.json + */ + +/** + * This list of issues that might be wrong with the application + */ +export interface ApplicationEnvironmentIssuesSchema { + /** The list of features that are missing in Unleash */ + missingFeatures: string[]; + /** The list of used SDKs that are outdated */ + outdatedSdks: string[]; +} diff --git a/frontend/src/openapi/models/applicationOverviewEnvironmentSchema.ts b/frontend/src/openapi/models/applicationOverviewEnvironmentSchema.ts index ea1483e32e..9ae6078c9c 100644 --- a/frontend/src/openapi/models/applicationOverviewEnvironmentSchema.ts +++ b/frontend/src/openapi/models/applicationOverviewEnvironmentSchema.ts @@ -3,6 +3,7 @@ * Do not edit manually. * See `gen:api` script in package.json */ +import type { ApplicationEnvironmentIssuesSchema } from './applicationEnvironmentIssuesSchema'; /** * Data about an application environment @@ -10,6 +11,8 @@ export interface ApplicationOverviewEnvironmentSchema { /** The number of instances of the application environment */ instanceCount: number; + /** This list of issues that might be wrong with the application */ + issues: ApplicationEnvironmentIssuesSchema; /** The last time the application environment was seen */ lastSeen: string | null; /** Name of the application environment */ diff --git a/frontend/src/openapi/models/applicationOverviewIssuesSchema.ts b/frontend/src/openapi/models/applicationOverviewIssuesSchema.ts index 8e6a21ca99..b73927a377 100644 --- a/frontend/src/openapi/models/applicationOverviewIssuesSchema.ts +++ b/frontend/src/openapi/models/applicationOverviewIssuesSchema.ts @@ -3,14 +3,11 @@ * Do not edit manually. * See `gen:api` script in package.json */ -import type { ApplicationOverviewIssuesSchemaType } from './applicationOverviewIssuesSchemaType'; /** * This list of issues that might be wrong with the application */ export interface ApplicationOverviewIssuesSchema { - /** The list of issues that might be wrong with the application */ - items: string[]; - /** The name of this action. */ - type: ApplicationOverviewIssuesSchemaType; + /** The list of strategies that are missing from Unleash */ + missingStrategies: string[]; } diff --git a/frontend/src/openapi/models/applicationOverviewIssuesSchemaType.ts b/frontend/src/openapi/models/applicationOverviewIssuesSchemaType.ts deleted file mode 100644 index 53e233f491..0000000000 --- a/frontend/src/openapi/models/applicationOverviewIssuesSchemaType.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Generated by Orval - * Do not edit manually. - * See `gen:api` script in package.json - */ - -/** - * The name of this action. - */ -export type ApplicationOverviewIssuesSchemaType = - (typeof ApplicationOverviewIssuesSchemaType)[keyof typeof ApplicationOverviewIssuesSchemaType]; - -// eslint-disable-next-line @typescript-eslint/no-redeclare -export const ApplicationOverviewIssuesSchemaType = { - missingFeatures: 'missingFeatures', - missingStrategies: 'missingStrategies', - outdatedSdks: 'outdatedSdks', -} as const; diff --git a/frontend/src/openapi/models/applicationOverviewSchema.ts b/frontend/src/openapi/models/applicationOverviewSchema.ts index f8b7c6483c..915080d844 100644 --- a/frontend/src/openapi/models/applicationOverviewSchema.ts +++ b/frontend/src/openapi/models/applicationOverviewSchema.ts @@ -15,7 +15,7 @@ export interface ApplicationOverviewSchema { /** The number of features the application has been using. */ featureCount: number; /** This list of issues that might be wrong with the application */ - issues: ApplicationOverviewIssuesSchema[]; + issues: ApplicationOverviewIssuesSchema; /** The list of projects the application has been using. */ projects: string[]; } diff --git a/frontend/src/openapi/models/index.ts b/frontend/src/openapi/models/index.ts index 815614a68e..70d33954a3 100644 --- a/frontend/src/openapi/models/index.ts +++ b/frontend/src/openapi/models/index.ts @@ -92,9 +92,9 @@ export * from './apiTokenSchemaType'; export * from './apiTokensSchema'; export * from './applicationEnvironmentInstancesSchema'; export * from './applicationEnvironmentInstancesSchemaInstancesItem'; +export * from './applicationEnvironmentIssuesSchema'; export * from './applicationOverviewEnvironmentSchema'; export * from './applicationOverviewIssuesSchema'; -export * from './applicationOverviewIssuesSchemaType'; export * from './applicationOverviewSchema'; export * from './applicationSchema'; export * from './applicationUsageSchema'; diff --git a/src/lib/db/client-applications-store.ts b/src/lib/db/client-applications-store.ts index 7b705ed935..571f919e8d 100644 --- a/src/lib/db/client-applications-store.ts +++ b/src/lib/db/client-applications-store.ts @@ -10,7 +10,6 @@ import { Logger, LogProvider } from '../logger'; import { Db } from './db'; import { IApplicationOverview } from '../features/metrics/instance/models'; import { applySearchFilters } from '../features/feature-search/search-utils'; -import { ApplicationOverviewIssuesSchema } from '../openapi/spec/application-overview-issues-schema'; const COLUMNS = [ 'app_name', @@ -331,7 +330,6 @@ export default class ClientApplicationsStore existingStrategies: string[], ): IApplicationOverview { const featureCount = new Set(rows.map((row) => row.feature_name)).size; - const missingFeatures: Set = new Set(); const missingStrategies: Set = new Set(); const environments = rows.reduce((acc, row) => { @@ -347,10 +345,6 @@ export default class ClientApplicationsStore if (!environment) return acc; - if (!project && feature_name) { - missingFeatures.add(feature_name); - } - strategies.forEach((strategy) => { if ( !DEPRECATED_STRATEGIES.includes(strategy) && @@ -360,6 +354,8 @@ export default class ClientApplicationsStore } }); + const featureDoesNotExist = !project && feature_name; + let env = acc.find((e) => e.name === environment); if (!env) { env = { @@ -370,13 +366,24 @@ export default class ClientApplicationsStore uniqueInstanceIds: new Set( instance_id ? [instance_id] : [], ), + issues: { + missingFeatures: featureDoesNotExist + ? [feature_name] + : [], + }, }; acc.push(env); } else { - if (instance_id && !env.uniqueInstanceIds.has(instance_id)) { + if (instance_id) { env.uniqueInstanceIds.add(instance_id); env.instanceCount = env.uniqueInstanceIds.size; } + if ( + featureDoesNotExist && + !env.issues.missingFeatures.includes(feature_name) + ) { + env.issues.missingFeatures.push(feature_name); + } if (sdk_version && !env.sdks.includes(sdk_version)) { env.sdks.push(sdk_version); } @@ -392,20 +399,6 @@ export default class ClientApplicationsStore env.sdks.sort(); }); - const issues: ApplicationOverviewIssuesSchema[] = []; - if (missingFeatures.size > 0) { - issues.push({ - type: 'missingFeatures', - items: [...missingFeatures], - }); - } - if (missingStrategies.size > 0) { - issues.push({ - type: 'missingStrategies', - items: [...missingStrategies], - }); - } - return { projects: [ ...new Set( @@ -416,7 +409,9 @@ export default class ClientApplicationsStore ], featureCount, environments, - issues, + issues: { + missingStrategies: [...missingStrategies], + }, }; } } diff --git a/src/lib/features/metrics/instance/findOutdatedSdks.ts b/src/lib/features/metrics/instance/findOutdatedSdks.ts index 93a7b2e961..b5769ab64b 100644 --- a/src/lib/features/metrics/instance/findOutdatedSdks.ts +++ b/src/lib/features/metrics/instance/findOutdatedSdks.ts @@ -16,7 +16,6 @@ const config: SDKConfig = { export function findOutdatedSDKs(sdkVersions: string[]): string[] { const uniqueSdkVersions = Array.from(new Set(sdkVersions)); - const outdatedSDKs: string[] = []; return uniqueSdkVersions.filter((sdkVersion) => { const result = sdkVersion.split(':'); diff --git a/src/lib/features/metrics/instance/instance-service.ts b/src/lib/features/metrics/instance/instance-service.ts index 4e8e91aa88..4ef842faf8 100644 --- a/src/lib/features/metrics/instance/instance-service.ts +++ b/src/lib/features/metrics/instance/instance-service.ts @@ -222,15 +222,11 @@ export default class ClientInstanceService { ): Promise { const result = await this.clientApplicationsStore.getApplicationOverview(appName); - - const sdks = result.environments.flatMap( - (environment) => environment.sdks, - ); - const outdatedSdks = findOutdatedSDKs(sdks); - if (outdatedSdks.length > 0) { - result.issues.push({ type: 'outdatedSdks', items: outdatedSdks }); - } - + result.environments.forEach((environment) => { + environment.issues.outdatedSdks = findOutdatedSDKs( + environment.sdks, + ); + }); return result; } diff --git a/src/lib/features/metrics/instance/models.ts b/src/lib/features/metrics/instance/models.ts index 03bafde72d..0df13c66f0 100644 --- a/src/lib/features/metrics/instance/models.ts +++ b/src/lib/features/metrics/instance/models.ts @@ -32,7 +32,7 @@ export interface IApplication { links?: Record; } -type IApplicationOverviewEnvironment = Omit< +export type IApplicationOverviewEnvironment = Omit< ApplicationOverviewEnvironmentSchema, 'lastSeen' > & { diff --git a/src/lib/openapi/index.ts b/src/lib/openapi/index.ts index 191601fc7f..0c83f66f16 100644 --- a/src/lib/openapi/index.ts +++ b/src/lib/openapi/index.ts @@ -202,6 +202,7 @@ import { applicationOverviewSchema } from './spec/application-overview-schema'; import { applicationOverviewEnvironmentSchema } from './spec/application-overview-environment-schema'; import { applicationOverviewIssuesSchema } from './spec/application-overview-issues-schema'; import { applicationEnvironmentInstancesSchema } from './spec/application-environment-instances-schema'; +import { applicationEnvironmentIssuesSchema } from './spec/application-environment-issues-schema'; // Schemas must have an $id property on the form "#/components/schemas/mySchema". export type SchemaId = (typeof schemas)[keyof typeof schemas]['$id']; @@ -253,6 +254,7 @@ export const schemas: UnleashSchemas = { applicationOverviewIssuesSchema, applicationOverviewEnvironmentSchema, applicationEnvironmentInstancesSchema, + applicationEnvironmentIssuesSchema, applicationUsageSchema, applicationsSchema, batchFeaturesSchema, diff --git a/src/lib/openapi/spec/application-environment-issues-schema.ts b/src/lib/openapi/spec/application-environment-issues-schema.ts new file mode 100644 index 0000000000..d906d2a454 --- /dev/null +++ b/src/lib/openapi/spec/application-environment-issues-schema.ts @@ -0,0 +1,32 @@ +import { FromSchema } from 'json-schema-to-ts'; + +export const applicationEnvironmentIssuesSchema = { + $id: '#/components/schemas/applicationEnvironmentIssuesSchema', + type: 'object', + description: 'This list of issues that might be wrong with the application', + additionalProperties: false, + required: ['missingFeatures', 'outdatedSdks'], + properties: { + missingFeatures: { + type: 'array', + items: { + type: 'string', + }, + description: 'The list of features that are missing in Unleash', + example: ['feature1', 'feature2'], + }, + outdatedSdks: { + type: 'array', + items: { + type: 'string', + }, + description: 'The list of used SDKs that are outdated', + example: ['unleash-client-node:5.4.0', 'unleash-client-node:5.3.0'], + }, + }, + components: {}, +} as const; + +export type ApplicationEnvironmentIssuesSchema = FromSchema< + typeof applicationEnvironmentIssuesSchema +>; diff --git a/src/lib/openapi/spec/application-overview-environment-schema.ts b/src/lib/openapi/spec/application-overview-environment-schema.ts index c8602b1acc..af7cfa1aaa 100644 --- a/src/lib/openapi/spec/application-overview-environment-schema.ts +++ b/src/lib/openapi/spec/application-overview-environment-schema.ts @@ -1,11 +1,12 @@ import { FromSchema } from 'json-schema-to-ts'; +import { applicationEnvironmentIssuesSchema } from './application-environment-issues-schema'; export const applicationOverviewEnvironmentSchema = { $id: '#/components/schemas/applicationOverviewEnvironmentSchema', type: 'object', description: 'Data about an application environment', additionalProperties: false, - required: ['name', 'instanceCount', 'sdks', 'lastSeen'], + required: ['name', 'instanceCount', 'sdks', 'lastSeen', 'issues'], properties: { name: { description: 'Name of the application environment', @@ -33,8 +34,17 @@ export const applicationOverviewEnvironmentSchema = { example: '2023-04-19T08:15:14.000Z', description: 'The last time the application environment was seen', }, + issues: { + description: + 'This list of issues that might be wrong with the application', + $ref: '#/components/schemas/applicationEnvironmentIssuesSchema', + }, + }, + components: { + schemas: { + applicationEnvironmentIssuesSchema, + }, }, - components: {}, } as const; export type ApplicationOverviewEnvironmentSchema = FromSchema< diff --git a/src/lib/openapi/spec/application-overview-issues-schema.ts b/src/lib/openapi/spec/application-overview-issues-schema.ts index 0fd9a33372..a6e26a99d2 100644 --- a/src/lib/openapi/spec/application-overview-issues-schema.ts +++ b/src/lib/openapi/spec/application-overview-issues-schema.ts @@ -5,20 +5,14 @@ export const applicationOverviewIssuesSchema = { type: 'object', description: 'This list of issues that might be wrong with the application', additionalProperties: false, - required: ['type', 'items'], + required: ['missingStrategies'], properties: { - type: { - type: 'string', - enum: ['missingFeatures', 'missingStrategies', 'outdatedSdks'], - description: 'The name of this action.', - }, - items: { + missingStrategies: { type: 'array', items: { type: 'string', }, - description: - 'The list of issues that might be wrong with the application', + description: 'The list of strategies that are missing from Unleash', example: ['feature1', 'feature2'], }, }, diff --git a/src/lib/openapi/spec/application-overview-schema.test.ts b/src/lib/openapi/spec/application-overview-schema.test.ts index 6e5b959a93..c3483b3d4c 100644 --- a/src/lib/openapi/spec/application-overview-schema.test.ts +++ b/src/lib/openapi/spec/application-overview-schema.test.ts @@ -4,22 +4,29 @@ test('applicationOverviewSchema', () => { const app = { projects: ['default', 'dx'], featureCount: 12, - issues: [ - { type: 'missingFeatures', items: ['feature1'] }, - { type: 'missingStrategies', items: ['strategy1'] }, - ], + issues: { + missingStrategies: [], + }, environments: [ { name: 'production', instanceCount: 34, sdks: ['unleash-client-node:5.4.0'], lastSeen: '2021-10-01T12:00:00Z', + issues: { + missingFeatures: ['feature1'], + outdatedSdks: ['node-unleash-client:5.3.0'], + }, }, { name: 'development', instanceCount: 16, sdks: ['unleash-client-java:5.4.0'], lastSeen: '2021-10-01T12:00:00Z', + issues: { + missingFeatures: [], + outdatedSdks: [], + }, }, ], }; diff --git a/src/lib/openapi/spec/application-overview-schema.ts b/src/lib/openapi/spec/application-overview-schema.ts index 6b11d7679e..f62231d73e 100644 --- a/src/lib/openapi/spec/application-overview-schema.ts +++ b/src/lib/openapi/spec/application-overview-schema.ts @@ -1,6 +1,7 @@ import { FromSchema } from 'json-schema-to-ts'; import { applicationOverviewEnvironmentSchema } from './application-overview-environment-schema'; import { applicationOverviewIssuesSchema } from './application-overview-issues-schema'; +import { applicationEnvironmentIssuesSchema } from './application-environment-issues-schema'; export const applicationOverviewSchema = { $id: '#/components/schemas/applicationOverviewSchema', @@ -35,16 +36,14 @@ export const applicationOverviewSchema = { issues: { description: 'This list of issues that might be wrong with the application', - type: 'array', - items: { - $ref: '#/components/schemas/applicationOverviewIssuesSchema', - }, + $ref: '#/components/schemas/applicationOverviewIssuesSchema', }, }, components: { schemas: { applicationOverviewEnvironmentSchema, applicationOverviewIssuesSchema, + applicationEnvironmentIssuesSchema, }, }, } as const; diff --git a/src/test/e2e/api/admin/applications.e2e.test.ts b/src/test/e2e/api/admin/applications.e2e.test.ts index 326817fa1c..fa207c6d13 100644 --- a/src/test/e2e/api/admin/applications.e2e.test.ts +++ b/src/test/e2e/api/admin/applications.e2e.test.ts @@ -122,7 +122,9 @@ test('should show correct number of total', async () => { const expected = { projects: ['default'], - issues: [], + issues: { + missingStrategies: [], + }, environments: [ { instanceCount: 2, @@ -179,27 +181,20 @@ test('should show missing features and strategies', async () => { const expected = { projects: ['default'], - issues: [ - { - type: 'missingFeatures', - items: ['toggle-name-2', 'toggle-name-3'], - }, - { - type: 'missingStrategies', - items: ['my-special-strategy'], - }, - { - type: 'outdatedSdks', - items: ['unleash-client-node:1.0.0'], - }, - ], environments: [ { instanceCount: 1, name: 'default', sdks: ['unleash-client-node:1.0.0'], + issues: { + missingFeatures: ['toggle-name-2', 'toggle-name-3'], + outdatedSdks: ['unleash-client-node:1.0.0'], + }, }, ], + issues: { + missingStrategies: ['my-special-strategy'], + }, featureCount: 3, };