diff --git a/.changeset/silent-jars-complain.md b/.changeset/silent-jars-complain.md
new file mode 100644
index 0000000000..b5027f1815
--- /dev/null
+++ b/.changeset/silent-jars-complain.md
@@ -0,0 +1,5 @@
+---
+'@oriflame/backstage-plugin-score-card': minor
+---
+
+Added config options to customize reviewer and review date display behavior
diff --git a/plugins/score-card/config.d.ts b/plugins/score-card/config.d.ts
index 8704db2e17..a47cb2d63d 100644
--- a/plugins/score-card/config.d.ts
+++ b/plugins/score-card/config.d.ts
@@ -24,6 +24,18 @@ export interface Config {
* @visibility frontend
*/
jsonDataUrl?: string;
+ display?: {
+ /**
+ * Whether to display the reviewer column in the score card table.
+ * @visibility frontend
+ */
+ reviewer?: string;
+ /**
+ * Whether to display the review date column in the score card table.
+ * @visibility frontend
+ */
+ reviewDate?: string;
+ };
/**
* The template for the link to the wiki, e.g. "https://TBD/XXX/_wiki/wikis/XXX.wiki/{id}"
* @visibility frontend
diff --git a/plugins/score-card/dev/app-config.yaml b/plugins/score-card/dev/app-config.yaml
index dd03505bf2..5707982605 100644
--- a/plugins/score-card/dev/app-config.yaml
+++ b/plugins/score-card/dev/app-config.yaml
@@ -12,3 +12,6 @@ backend:
scorecards:
jsonDataUrl: http://127.0.0.1:8090/sample-data/ #this needs to be served via http-server as we get http 200 with WDS server instead of http 404 for nonExistent entity
+ display:
+ reviewer: always
+ reviewDate: always
diff --git a/plugins/score-card/src/components/ScoreCard/ScoreCard.tsx b/plugins/score-card/src/components/ScoreCard/ScoreCard.tsx
index dc144d18ef..9d67932b11 100644
--- a/plugins/score-card/src/components/ScoreCard/ScoreCard.tsx
+++ b/plugins/score-card/src/components/ScoreCard/ScoreCard.tsx
@@ -42,6 +42,7 @@ import { scorePercentColumn } from './columns/scorePercentColumn';
import { titleColumn } from './columns/titleColumn';
import { getReviewerLink } from './sub-components/getReviewerLink';
import { scoringDataApiRef } from '../../api';
+import { useDisplayConfig } from '../../config/DisplayConfig';
// lets prepare some styles
const useStyles = makeStyles(theme => ({
@@ -122,6 +123,8 @@ export const ScoreCard = ({
const allEntries = getScoreTableEntries(data);
+ const displayPolicies = useDisplayConfig().getDisplayPolicies();
+
return (
- {getReviewerLink(data)}
+ {getReviewerLink(data, displayPolicies)}
)}
diff --git a/plugins/score-card/src/components/ScoreCard/sub-components/getReviewerLink.tsx b/plugins/score-card/src/components/ScoreCard/sub-components/getReviewerLink.tsx
index 58ed0b579f..cdcef0f32b 100644
--- a/plugins/score-card/src/components/ScoreCard/sub-components/getReviewerLink.tsx
+++ b/plugins/score-card/src/components/ScoreCard/sub-components/getReviewerLink.tsx
@@ -16,18 +16,35 @@
import { EntityRefLink } from '@backstage/plugin-catalog-react';
import React from 'react';
import { EntityScoreExtended } from '../../../api/types';
+import { DisplayPolicies, DisplayPolicy } from '../../../config/types';
+
+export function getReviewerLink(
+ value: EntityScoreExtended,
+ displayPolicies: DisplayPolicies,
+) {
+ const displayReviewer = displayPolicies.reviewer !== DisplayPolicy.Never;
+ const displayReviewDate = displayPolicies.reviewDate !== DisplayPolicy.Never;
+
+ if (!displayReviewer && !displayReviewDate) {
+ return null;
+ }
-export function getReviewerLink(value: EntityScoreExtended) {
return (
{value.reviewer ? (
<>
- Review done by
-
- {value.reviewer?.name}
-
- at
- {value.reviewDate ? value.reviewDate.toLocaleDateString() : 'unknown'}
+ Review done
+ {displayReviewer && ' by '}
+ {displayReviewer && (
+
+ {value.reviewer?.name}
+
+ )}
+ {displayReviewDate &&
+ ` at
+ ${(value.reviewDate
+ ? value.reviewDate.toLocaleDateString()
+ : 'unknown')}`}
>
) : (
<>Not yet reviewed.>
diff --git a/plugins/score-card/src/components/ScoreCardTable/ScoreCardTable.test.tsx b/plugins/score-card/src/components/ScoreCardTable/ScoreCardTable.test.tsx
index 0207537e4f..0f12c64983 100644
--- a/plugins/score-card/src/components/ScoreCardTable/ScoreCardTable.test.tsx
+++ b/plugins/score-card/src/components/ScoreCardTable/ScoreCardTable.test.tsx
@@ -16,11 +16,11 @@
import React from 'react';
import { act, render } from '@testing-library/react';
import { ScoreCardTable } from './ScoreCardTable';
-import { TestApiProvider } from '@backstage/test-utils';
+import { MockConfigApi, TestApiProvider } from '@backstage/test-utils';
import { ScoringDataApi, scoringDataApiRef } from '../../api';
import { Entity } from '@backstage/catalog-model';
import { EntityScoreExtended } from '../../api/types';
-import { errorApiRef } from '@backstage/core-plugin-api';
+import { configApiRef, errorApiRef } from '@backstage/core-plugin-api';
import { lightTheme } from '@backstage/theme';
import { ThemeProvider } from '@material-ui/core';
import { MemoryRouter as Router } from 'react-router-dom';
@@ -53,6 +53,7 @@ describe('ScoreBoardPage-EmptyData', () => {
apis={[
[errorApiRef, errorApi],
[scoringDataApiRef, mockClient],
+ [configApiRef, new MockConfigApi({})],
]}
>
@@ -68,6 +69,68 @@ describe('ScoreBoardPage-EmptyData', () => {
await findByTestId('score-board-table');
jest.useRealTimers();
});
+
+ it.each([
+ ['always', 1],
+ ['if-data-present', 0],
+ ['never', 0],
+ ])('should apply reviewer column display policy', async (displayPolicy, isPresent) => {
+ const errorApi = { post: () => {} };
+ const displayPolicies = { reviewer: displayPolicy };
+ const configApi = new MockConfigApi({ scorecards: {display: displayPolicies} });
+ const { findByTestId, queryAllByText } = render(
+
+
+
+
+
+
+ ,
+ );
+
+ await findByTestId('score-board-table');
+
+ const reviewerColumn = await queryAllByText('Reviewer')
+
+ expect(reviewerColumn).toHaveLength(isPresent)
+ });
+
+ it.each([
+ ['always', 1],
+ ['if-data-present', 0],
+ ['never', 0],
+ ])('should apply review date column display policy', async (displayPolicy, isPresent) => {
+ const errorApi = { post: () => {} };
+ const displayPolicies = { reviewDate: displayPolicy };
+ const configApi = new MockConfigApi({ scorecards: {display: displayPolicies} });
+ const { findByTestId, queryAllByText } = render(
+
+
+
+
+
+
+ ,
+ );
+
+ await findByTestId('score-board-table');
+
+ const reviewerColumn = await queryAllByText('Date')
+
+ expect(reviewerColumn).toHaveLength(isPresent)
+ });
});
describe('ScoreCard-TestWithData', () => {
@@ -119,6 +182,7 @@ describe('ScoreCard-TestWithData', () => {
apis={[
[errorApiRef, errorApi],
[scoringDataApiRef, mockClient],
+ [configApiRef, new MockConfigApi({})],
]}
>
@@ -135,4 +199,66 @@ describe('ScoreCard-TestWithData', () => {
expect(podcastRow).toHaveTextContent('podcastsystemAB+DFFC');
});
+
+ it.each([
+ ['always', 1],
+ ['if-data-present', 1],
+ ['never', 0],
+ ])('should apply reviewer column display policy', async (displayPolicy, isPresent) => {
+ const errorApi = { post: () => {} };
+ const displayPolicies = { reviewer: displayPolicy };
+ const configApi = new MockConfigApi({ scorecards: {display: displayPolicies} });
+ const { findByTestId, queryAllByText } = render(
+
+
+
+
+
+
+ ,
+ );
+
+ await findByTestId('score-board-table');
+
+ const reviewerColumn = await queryAllByText('Reviewer')
+
+ expect(reviewerColumn).toHaveLength(isPresent)
+ });
+
+ it.each([
+ ['always', 1],
+ ['if-data-present', 1],
+ ['never', 0],
+ ])('should apply review date column display policy', async (displayPolicy, isPresent) => {
+ const errorApi = { post: () => {} };
+ const displayPolicies = { reviewDate: displayPolicy };
+ const configApi = new MockConfigApi({ scorecards: {display: displayPolicies} });
+ const { findByTestId, queryAllByText } = render(
+
+
+
+
+
+
+ ,
+ );
+
+ await findByTestId('score-board-table');
+
+ const reviewerColumn = await queryAllByText('Date')
+
+ expect(reviewerColumn).toHaveLength(isPresent)
+ });
});
diff --git a/plugins/score-card/src/components/ScoreCardTable/ScoreCardTable.tsx b/plugins/score-card/src/components/ScoreCardTable/ScoreCardTable.tsx
index 1c61419528..98d96e7a73 100644
--- a/plugins/score-card/src/components/ScoreCardTable/ScoreCardTable.tsx
+++ b/plugins/score-card/src/components/ScoreCardTable/ScoreCardTable.tsx
@@ -24,6 +24,8 @@ import { scoringDataApiRef } from '../../api';
import { EntityScoreExtended } from '../../api/types';
import { EntityRefLink } from '@backstage/plugin-catalog-react';
import { DEFAULT_NAMESPACE } from '@backstage/catalog-model';
+import { useDisplayConfig } from '../../config/DisplayConfig';
+import { DisplayPolicy } from '../../config/types';
const useScoringAllDataLoader = (entityKindFilter?: string[]) => {
const errorApi = useApi(errorApiRef);
@@ -49,6 +51,8 @@ type ScoreTableProps = {
};
export const ScoreTable = ({ title, scores }: ScoreTableProps) => {
+ const displayPolicies = useDisplayConfig().getDisplayPolicies();
+
const columns: TableColumn[] = [
{
title: 'Name',
@@ -85,7 +89,14 @@ export const ScoreTable = ({ title, scores }: ScoreTableProps) => {
>
) : null,
},
- {
+ ];
+
+ if (
+ displayPolicies.reviewer === DisplayPolicy.Always ||
+ (displayPolicies.reviewer === DisplayPolicy.IfDataPresent &&
+ scores.some(s => !!s.scoringReviewer))
+ ) {
+ columns.push({
title: 'Reviewer',
field: 'scoringReviewer',
render: entityScore =>
@@ -96,16 +107,24 @@ export const ScoreTable = ({ title, scores }: ScoreTableProps) => {
>
) : null,
- },
- {
+ });
+ }
+
+ if (
+ displayPolicies.reviewDate === DisplayPolicy.Always ||
+ (displayPolicies.reviewDate === DisplayPolicy.IfDataPresent &&
+ scores.some(s => !!s.scoringReviewDate))
+ ) {
+ columns.push({
title: 'Date',
field: 'scoringReviewDate',
render: entityScore =>
entityScore.reviewDate ? (
<>{entityScore.reviewDate.toLocaleDateString()}>
) : null,
- },
- ];
+ });
+ }
+
scores
.flatMap(s => {
return s.areaScores ?? [];
diff --git a/plugins/score-card/src/config/DisplayConfig.test.ts b/plugins/score-card/src/config/DisplayConfig.test.ts
new file mode 100644
index 0000000000..2d143a4328
--- /dev/null
+++ b/plugins/score-card/src/config/DisplayConfig.test.ts
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2022 Oriflame
+ *
+ * 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 { MockConfigApi } from '@backstage/test-utils';
+import { DisplayConfig } from './DisplayConfig';
+import { DisplayPolicy } from './types';
+
+describe('display config', () => {
+ it.each([
+ [
+ { reviewer: 'always', reviewDate: 'always' },
+ { reviewer: DisplayPolicy.Always, reviewDate: DisplayPolicy.Always },
+ ],
+ [
+ { reviewer: 'never', reviewDate: 'never' },
+ { reviewer: DisplayPolicy.Never, reviewDate: DisplayPolicy.Never },
+ ],
+ [
+ { reviewer: 'if-data-present', reviewDate: 'if-data-present' },
+ {
+ reviewer: DisplayPolicy.IfDataPresent,
+ reviewDate: DisplayPolicy.IfDataPresent,
+ },
+ ],
+ [
+ { reviewDate: 'if-data-present' },
+ {
+ reviewer: DisplayPolicy.Always,
+ reviewDate: DisplayPolicy.IfDataPresent,
+ },
+ ],
+ [
+ { reviewer: 'never' },
+ { reviewer: DisplayPolicy.Never, reviewDate: DisplayPolicy.Always },
+ ],
+ [{}, { reviewer: DisplayPolicy.Always, reviewDate: DisplayPolicy.Always }],
+ ])('gets expected display policies from config', (policies, expected) => {
+ const mockConfig = new MockConfigApi({ scorecards: { display: policies } });
+
+ const config = new DisplayConfig({ configApi: mockConfig });
+
+ const displayPolicies = config.getDisplayPolicies();
+ expect(displayPolicies).toEqual(expected);
+ });
+});
diff --git a/plugins/score-card/src/config/DisplayConfig.ts b/plugins/score-card/src/config/DisplayConfig.ts
new file mode 100644
index 0000000000..608e561316
--- /dev/null
+++ b/plugins/score-card/src/config/DisplayConfig.ts
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2022 Oriflame
+ *
+ * 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 { ConfigApi, configApiRef, useApi } from '@backstage/core-plugin-api';
+import { DisplayPolicies, DisplayPolicy } from './types';
+
+export class DisplayConfig {
+ configApi: ConfigApi;
+
+ constructor({ configApi }: { configApi: ConfigApi }) {
+ this.configApi = configApi;
+ }
+
+ public getDisplayPolicies(): DisplayPolicies {
+ const displayConfig =
+ this.configApi.getOptionalConfig('scorecards.display');
+ return {
+ reviewer:
+ (displayConfig?.getOptionalString('reviewer') as DisplayPolicy) ??
+ DisplayPolicy.Always,
+ reviewDate:
+ (displayConfig?.getOptionalString('reviewDate') as DisplayPolicy) ??
+ DisplayPolicy.Always,
+ };
+ }
+}
+
+export const useDisplayConfig = () => {
+ const configApi = useApi(configApiRef);
+ return new DisplayConfig({ configApi });
+};
diff --git a/plugins/score-card/src/config/types.ts b/plugins/score-card/src/config/types.ts
new file mode 100644
index 0000000000..a20b47f26c
--- /dev/null
+++ b/plugins/score-card/src/config/types.ts
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2022 Oriflame
+ *
+ * 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 enum DisplayPolicy {
+ IfDataPresent = 'if-data-present',
+ Never = 'never',
+ Always = 'always',
+}
+
+export interface DisplayPolicies {
+ reviewer: DisplayPolicy;
+ reviewDate: DisplayPolicy;
+}