feat: codescene plugin at entity level
Signed-off-by: blbose <lillian.bose@philips.com>
This commit is contained in:
+135
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { CodeSceneEntityKPICard } from './CodeSceneEntityKPICard';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import {
|
||||
setupRequestMockHandlers,
|
||||
renderInTestApp,
|
||||
TestApiRegistry,
|
||||
} from '@backstage/test-utils';
|
||||
import { CodeSceneApi, codesceneApiRef } from '../../api/api';
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
import { Analysis } from '../../api/types';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { configApiRef } from '@backstage/core-plugin-api';
|
||||
import { EntityProvider } from '@backstage/plugin-catalog-react';
|
||||
|
||||
const entity = {
|
||||
metadata: {
|
||||
namespace: 'default',
|
||||
annotations: {
|
||||
'codescene.io/project-id': '12345',
|
||||
},
|
||||
name: 'codescene-test-project',
|
||||
title: 'codescene-test-project',
|
||||
description: 'A demo codescene-test repo',
|
||||
links: [
|
||||
{
|
||||
url: 'https://some-demo.com',
|
||||
title: 'Demo',
|
||||
icon: 'dashboard',
|
||||
},
|
||||
],
|
||||
},
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
spec: {
|
||||
type: 'website',
|
||||
owner: 'swcoe',
|
||||
lifecycle: 'experimental',
|
||||
},
|
||||
relations: [
|
||||
{
|
||||
type: 'ownedBy',
|
||||
targetRef: 'group:default/swcoe',
|
||||
target: {
|
||||
kind: 'group',
|
||||
namespace: 'default',
|
||||
name: 'swcoe',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describe('CodeSceneEntityKPICard', () => {
|
||||
const server = setupServer();
|
||||
// Enable same handlers for network requests
|
||||
setupRequestMockHandlers(server);
|
||||
let apis: TestApiRegistry;
|
||||
|
||||
// setup mock response
|
||||
beforeEach(() => {
|
||||
server.use(
|
||||
rest.get('/*', (_, res, ctx) => res(ctx.status(200), ctx.json({}))),
|
||||
);
|
||||
|
||||
const config = new ConfigReader({
|
||||
codescene: {
|
||||
baseUrl: 'www.fake-url.com',
|
||||
},
|
||||
});
|
||||
const analysis: Analysis = {
|
||||
id: 1,
|
||||
name: 'codescene-test-project',
|
||||
project_id: 12345,
|
||||
readable_analysis_time: '2022-03-22',
|
||||
summary: {
|
||||
unique_issue_ids: 0,
|
||||
issues_filtered_as_outliers: 0,
|
||||
entities: 0,
|
||||
commits_with_issue_ids: 0,
|
||||
authors_count: 0,
|
||||
active_authors_count: 0,
|
||||
issues_with_cycle_time: 0,
|
||||
commits: 0,
|
||||
issue_ids_matched_to_issues: 0,
|
||||
issues_classed_as_defects: 0,
|
||||
issues_with_cost: 0,
|
||||
},
|
||||
file_summary: [],
|
||||
high_level_metrics: {
|
||||
current_score: 0,
|
||||
month_score: 0,
|
||||
year_score: 0,
|
||||
active_developers: 0,
|
||||
lines_of_code: 0,
|
||||
system_mastery: 0,
|
||||
},
|
||||
};
|
||||
apis = TestApiRegistry.from(
|
||||
[
|
||||
codesceneApiRef,
|
||||
{
|
||||
fetchLatestAnalysis: jest.fn(() => analysis),
|
||||
} as unknown as CodeSceneApi,
|
||||
],
|
||||
[configApiRef, config],
|
||||
);
|
||||
});
|
||||
|
||||
it('should render', async () => {
|
||||
const rendered = await renderInTestApp(
|
||||
<EntityProvider entity={entity}>
|
||||
<ApiProvider apis={apis}>
|
||||
<CodeSceneEntityKPICard />
|
||||
</ApiProvider>
|
||||
</EntityProvider>,
|
||||
);
|
||||
expect(rendered.getByText(/Mar 22, 2022/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {
|
||||
Content,
|
||||
EmptyState,
|
||||
Progress,
|
||||
ContentHeader,
|
||||
} from '@backstage/core-components';
|
||||
import { configApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
import React from 'react';
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import { codesceneApiRef } from '../../api/api';
|
||||
import { Analysis } from '../../api/types';
|
||||
import { CodeHealthKpisCard } from '../CodeHealthKpisCard/CodeHealthKpisCard';
|
||||
import {
|
||||
getProjectAnnotation,
|
||||
CODESCENE_PROJECT_ANNOTATION,
|
||||
isCodeSceneAvailable,
|
||||
} from '../../utils/commonUtil';
|
||||
import { DateTime } from 'luxon';
|
||||
import { MissingAnnotationEmptyState } from '@backstage/plugin-catalog-react';
|
||||
|
||||
export const CodeSceneEntityKPICard = () => {
|
||||
const { entity } = useEntity();
|
||||
const { projectId } = getProjectAnnotation(entity);
|
||||
const codesceneApi = useApi(codesceneApiRef);
|
||||
const config = useApi(configApiRef);
|
||||
const codesceneHost = config.getString('codescene.baseUrl');
|
||||
|
||||
const {
|
||||
value: analysis,
|
||||
loading,
|
||||
error,
|
||||
} = useAsync(async (): Promise<Analysis> => {
|
||||
return await codesceneApi.fetchLatestAnalysis(projectId);
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return <Progress />;
|
||||
} else if (error) {
|
||||
return <Alert severity="error">{error.message}</Alert>;
|
||||
} else if (!analysis) {
|
||||
return (
|
||||
<EmptyState
|
||||
missing="content"
|
||||
title="CodeScene analysis"
|
||||
description={`No analysis found for project with id ${projectId}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return isCodeSceneAvailable(entity) ? (
|
||||
<Content>
|
||||
<ContentHeader title="CodeScene Dashboard">
|
||||
Last analyzed on{' '}
|
||||
{DateTime.fromISO(analysis.readable_analysis_time).toLocaleString(
|
||||
DateTime.DATETIME_MED,
|
||||
)}
|
||||
</ContentHeader>
|
||||
<CodeHealthKpisCard codesceneHost={codesceneHost} analysis={analysis} />
|
||||
</Content>
|
||||
) : (
|
||||
<MissingAnnotationEmptyState annotation={CODESCENE_PROJECT_ANNOTATION} />
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { CodeSceneEntityKPICard } from './CodeSceneEntityKPICard';
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { CodeSceneEntityPage } from './CodeSceneEntityPage';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import {
|
||||
setupRequestMockHandlers,
|
||||
renderInTestApp,
|
||||
TestApiRegistry,
|
||||
} from '@backstage/test-utils';
|
||||
import { CodeSceneApi, codesceneApiRef } from '../../api/api';
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
import { Analysis } from '../../api/types';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { configApiRef } from '@backstage/core-plugin-api';
|
||||
import { EntityProvider } from '@backstage/plugin-catalog-react';
|
||||
|
||||
const entity = {
|
||||
metadata: {
|
||||
namespace: 'default',
|
||||
annotations: {
|
||||
'codescene.io/project-id': '12345',
|
||||
},
|
||||
name: 'codescene-test-project',
|
||||
title: 'codescene-test-project',
|
||||
description: 'A demo codescene-test repo',
|
||||
links: [
|
||||
{
|
||||
url: 'https://some-demo.com',
|
||||
title: 'Demo',
|
||||
icon: 'dashboard',
|
||||
},
|
||||
],
|
||||
},
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
spec: {
|
||||
type: 'website',
|
||||
owner: 'swcoe',
|
||||
lifecycle: 'experimental',
|
||||
},
|
||||
relations: [
|
||||
{
|
||||
type: 'ownedBy',
|
||||
targetRef: 'group:default/swcoe',
|
||||
target: {
|
||||
kind: 'group',
|
||||
namespace: 'default',
|
||||
name: 'swcoe',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describe('CodeSceneEntityPage', () => {
|
||||
const server = setupServer();
|
||||
// Enable sane handlers for network requests
|
||||
setupRequestMockHandlers(server);
|
||||
let apis: TestApiRegistry;
|
||||
|
||||
// setup mock response
|
||||
beforeEach(() => {
|
||||
server.use(
|
||||
rest.get('/*', (_, res, ctx) => res(ctx.status(200), ctx.json({}))),
|
||||
);
|
||||
|
||||
const config = new ConfigReader({
|
||||
codescene: {
|
||||
baseUrl: 'www.fake-url.com',
|
||||
},
|
||||
});
|
||||
const analysis: Analysis = {
|
||||
id: 1,
|
||||
name: 'codescene-test-project',
|
||||
project_id: 12345,
|
||||
readable_analysis_time: '2022-03-22',
|
||||
summary: {
|
||||
unique_issue_ids: 0,
|
||||
issues_filtered_as_outliers: 0,
|
||||
entities: 0,
|
||||
commits_with_issue_ids: 0,
|
||||
authors_count: 0,
|
||||
active_authors_count: 0,
|
||||
issues_with_cycle_time: 0,
|
||||
commits: 0,
|
||||
issue_ids_matched_to_issues: 0,
|
||||
issues_classed_as_defects: 0,
|
||||
issues_with_cost: 0,
|
||||
},
|
||||
file_summary: [],
|
||||
high_level_metrics: {
|
||||
current_score: 0,
|
||||
month_score: 0,
|
||||
year_score: 0,
|
||||
active_developers: 0,
|
||||
lines_of_code: 0,
|
||||
system_mastery: 0,
|
||||
},
|
||||
};
|
||||
apis = TestApiRegistry.from(
|
||||
[
|
||||
codesceneApiRef,
|
||||
{
|
||||
fetchLatestAnalysis: jest.fn(() => analysis),
|
||||
} as unknown as CodeSceneApi,
|
||||
],
|
||||
[configApiRef, config],
|
||||
);
|
||||
});
|
||||
|
||||
it('should render', async () => {
|
||||
const rendered = await renderInTestApp(
|
||||
<EntityProvider entity={entity}>
|
||||
<ApiProvider apis={apis}>
|
||||
<CodeSceneEntityPage />
|
||||
</ApiProvider>
|
||||
</EntityProvider>,
|
||||
);
|
||||
expect(rendered.getByText(/codescene-test-project/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {
|
||||
Content,
|
||||
EmptyState,
|
||||
GaugeCard,
|
||||
Header,
|
||||
InfoCard,
|
||||
Page,
|
||||
Progress,
|
||||
SupportButton,
|
||||
Table,
|
||||
TableColumn,
|
||||
} from '@backstage/core-components';
|
||||
import { configApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import { Grid, Typography } from '@material-ui/core';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
import React from 'react';
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import { codesceneApiRef } from '../../api/api';
|
||||
import { Analysis } from '../../api/types';
|
||||
import { CodeHealthKpisCard } from '../CodeHealthKpisCard/CodeHealthKpisCard';
|
||||
import { MissingAnnotationEmptyState } from '@backstage/plugin-catalog-react';
|
||||
import {
|
||||
getProjectAnnotation,
|
||||
CODESCENE_PROJECT_ANNOTATION,
|
||||
isCodeSceneAvailable,
|
||||
} from '../../utils/commonUtil';
|
||||
|
||||
export const CodeSceneEntityPage = () => {
|
||||
const { entity } = useEntity();
|
||||
const { projectId } = getProjectAnnotation(entity);
|
||||
const codesceneApi = useApi(codesceneApiRef);
|
||||
const config = useApi(configApiRef);
|
||||
const {
|
||||
value: analysis,
|
||||
loading,
|
||||
error,
|
||||
} = useAsync(async (): Promise<Analysis> => {
|
||||
return await codesceneApi.fetchLatestAnalysis(projectId);
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return <Progress />;
|
||||
} else if (error) {
|
||||
return <Alert severity="error">{error.message}</Alert>;
|
||||
} else if (!analysis) {
|
||||
return (
|
||||
<EmptyState
|
||||
missing="content"
|
||||
title="CodeScene analysis"
|
||||
description={`No analysis found for project with id ${projectId}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const columns: TableColumn[] = [
|
||||
{
|
||||
title: 'Language',
|
||||
field: 'language',
|
||||
highlight: true,
|
||||
},
|
||||
{
|
||||
title: 'Files',
|
||||
field: 'number_of_files',
|
||||
type: 'numeric',
|
||||
},
|
||||
{
|
||||
title: 'Code',
|
||||
field: 'code',
|
||||
type: 'numeric',
|
||||
},
|
||||
{
|
||||
title: 'Comment',
|
||||
field: 'comment',
|
||||
type: 'numeric',
|
||||
},
|
||||
];
|
||||
|
||||
const fileSummaryTable = (
|
||||
<Table
|
||||
options={{ paging: false, padding: 'dense' }}
|
||||
data={analysis.file_summary.sort((a, b) => b.code - a.code)}
|
||||
columns={columns}
|
||||
title="File Summary"
|
||||
/>
|
||||
);
|
||||
|
||||
const codesceneHost = config.getString('codescene.baseUrl');
|
||||
const analysisPath = `${codesceneHost}/${projectId}/analyses/${analysis.id}`;
|
||||
const analysisLink = { title: 'Check the analysis', link: analysisPath };
|
||||
|
||||
const analysisBody = (
|
||||
<>
|
||||
<Typography variant="body1">
|
||||
<b>Active authors</b>: {analysis.summary.active_authors_count}
|
||||
</Typography>
|
||||
<Typography variant="body1">
|
||||
<b>Total authors</b>: {analysis.summary.authors_count}
|
||||
</Typography>
|
||||
<Typography variant="body1">
|
||||
<b>Commits</b>: {analysis.summary.commits}
|
||||
</Typography>
|
||||
</>
|
||||
);
|
||||
|
||||
return isCodeSceneAvailable(entity) ? (
|
||||
<Page themeId="tool">
|
||||
<Header
|
||||
title={`CodeScene: ${analysis.name}`}
|
||||
subtitle={`Last analysis: ${analysis.readable_analysis_time}`}
|
||||
>
|
||||
<SupportButton>
|
||||
See hidden risks and social patterns in your code. Prioritize and
|
||||
reduce technical debt.
|
||||
</SupportButton>
|
||||
</Header>
|
||||
<Content>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item md={4} xs={12}>
|
||||
<CodeHealthKpisCard
|
||||
codesceneHost={codesceneHost}
|
||||
analysis={analysis}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item>
|
||||
<GaugeCard
|
||||
title="System Mastery"
|
||||
progress={analysis.high_level_metrics.system_mastery / 100}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<GaugeCard
|
||||
title="Current Score"
|
||||
progress={analysis.high_level_metrics.current_score / 10}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<InfoCard title="Analysis" deepLink={analysisLink}>
|
||||
{analysisBody}
|
||||
</InfoCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container spacing={2} direction="column">
|
||||
<Grid item>{fileSummaryTable}</Grid>
|
||||
</Grid>
|
||||
</Content>
|
||||
</Page>
|
||||
) : (
|
||||
<MissingAnnotationEmptyState annotation={CODESCENE_PROJECT_ANNOTATION} />
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { CodeSceneEntityPage } from './CodeSceneEntityPage';
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,5 +17,11 @@ export {
|
||||
codescenePlugin,
|
||||
CodeScenePage,
|
||||
CodeSceneProjectDetailsPage,
|
||||
CodeSceneEntityPage,
|
||||
CodeSceneEntityKPICard,
|
||||
} from './plugin';
|
||||
export { CodeSceneIcon } from './CodeSceneIcon';
|
||||
export {
|
||||
isCodeSceneAvailable,
|
||||
CODESCENE_PROJECT_ANNOTATION,
|
||||
} from './utils/commonUtil';
|
||||
|
||||
@@ -67,3 +67,25 @@ export const CodeSceneProjectDetailsPage = codescenePlugin.provide(
|
||||
mountPoint: projectDetailsRouteRef,
|
||||
}),
|
||||
);
|
||||
|
||||
export const CodeSceneEntityKPICard = codescenePlugin.provide(
|
||||
createRoutableExtension({
|
||||
name: 'CodeSceneEntityKPICard',
|
||||
component: () =>
|
||||
import('./components/CodeSceneEntityKPICard').then(
|
||||
m => m.CodeSceneEntityKPICard,
|
||||
),
|
||||
mountPoint: rootRouteRef,
|
||||
}),
|
||||
);
|
||||
|
||||
export const CodeSceneEntityPage = codescenePlugin.provide(
|
||||
createRoutableExtension({
|
||||
name: 'CodeSceneEntityPage',
|
||||
component: () =>
|
||||
import('./components/CodeSceneEntityPage').then(
|
||||
m => m.CodeSceneEntityPage,
|
||||
),
|
||||
mountPoint: rootRouteRef,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
|
||||
export const CODESCENE_PROJECT_ANNOTATION = 'codescene.io/project-id';
|
||||
|
||||
export const getProjectAnnotation = (entity: Entity) => {
|
||||
const annotation =
|
||||
entity?.metadata.annotations?.[CODESCENE_PROJECT_ANNOTATION];
|
||||
const projectId = annotation?.split('/')[0];
|
||||
return { projectId: Number(projectId) };
|
||||
};
|
||||
|
||||
export const isCodeSceneAvailable = (entity: Entity) =>
|
||||
Boolean(getProjectAnnotation(entity).projectId);
|
||||
Reference in New Issue
Block a user