Skip to content

Commit

Permalink
working summary scorecards
Browse files Browse the repository at this point in the history
  • Loading branch information
fokolo committed Oct 15, 2024
1 parent f82a6f5 commit 260fbc8
Show file tree
Hide file tree
Showing 10 changed files with 246 additions and 7 deletions.
18 changes: 18 additions & 0 deletions frontend-plugin/src/api/scorecards.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { PORT_PROXY_PATH } from "./consts";

export async function getAllScorecardDefinitions(
backendApiUrl: string
): Promise<Response> {
const response = await fetch(
`${backendApiUrl}${PORT_PROXY_PATH}/scorecards`,
{
method: "GET",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
}
);

return response;
}
9 changes: 7 additions & 2 deletions frontend-plugin/src/api/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@ import { PortEntity } from "./types";

export default async function search(
backendApiUrl: string,
searchQuery: object
searchQuery: object,
include?: string[]
): Promise<PortEntity[]> {
const response = await fetch(
`${backendApiUrl}${PORT_PROXY_PATH}/entities/search`,
`${backendApiUrl}${PORT_PROXY_PATH}/entities/search${
include
? `?${include.map((i) => `include=${encodeURIComponent(i)}`).join("&")}`
: ""
}`,
{
method: "POST",
credentials: "include",
Expand Down
4 changes: 3 additions & 1 deletion frontend-plugin/src/features/Scorecards/scorecards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ export type ScorecardProps = {
}[];
};

const getLevelEmoji = (level: "Bronze" | "Silver" | "Gold" | "Basic") => {
export const getLevelEmoji = (
level: "Bronze" | "Silver" | "Gold" | "Basic"
) => {
switch (level) {
case "Bronze":
return "🥉";
Expand Down
4 changes: 2 additions & 2 deletions frontend-plugin/src/hooks/api-hooks/useSearchQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useEffect, useMemo, useState } from "react";
import search from "../../api/search";
import { PortEntity } from "../../api/types";

function useSearchQuery(searchQuery: any) {
function useSearchQuery(searchQuery: any, include?: string[]) {
const [data, setData] = useState<PortEntity[]>([]);
const [error, setError] = useState<string | null>();
const [isLoading, setIsLoading] = useState(false);
Expand All @@ -13,7 +13,7 @@ function useSearchQuery(searchQuery: any) {

useEffect(() => {
setIsLoading(true);
search(backendUrl, searchQuery)
search(backendUrl, searchQuery, include)
.then((entities) => {
setData(entities);
setError(null);
Expand Down
3 changes: 2 additions & 1 deletion frontend-plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ export {
Actions,
EntityTabPortContent,
HomePage,
PortDependencyCard,
portPlugin,
ScorecardCard,
PortDependencyCard,
ScorecardsPage,
} from "./plugin";

export const isPortDataAvailable = (entity: Entity) => {
Expand Down
5 changes: 5 additions & 0 deletions frontend-plugin/src/pages/scorecards/initiatives.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from "react";

export const ScorecardsInitiativesPage = () => {
return <div>Initiatives</div>;
};
28 changes: 28 additions & 0 deletions frontend-plugin/src/pages/scorecards/scorecards.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Header, Page, RoutedTabs } from "@backstage/core-components";
import React from "react";
import { ScorecardsInitiativesPage } from "./initiatives";
import { ScorecardsSummaryPage } from "./summary";

type SubRoute = Parameters<typeof RoutedTabs>[0]["routes"];

export const ScorecardsPage = () => {
const routes: SubRoute = [
{
path: "/",
title: "Summary",
children: <ScorecardsSummaryPage />,
},
{
path: "/initiatives",
title: "Initiatives",
children: <ScorecardsInitiativesPage />,
},
];

return (
<Page themeId="home">
<Header title="Scorecards" />
<RoutedTabs routes={routes} />
</Page>
);
};
170 changes: 170 additions & 0 deletions frontend-plugin/src/pages/scorecards/summary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { Content, Progress } from "@backstage/core-components";
import {
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableRow,
} from "@material-ui/core";
import { makeStyles } from "@material-ui/core/styles";
import Cancel from "@material-ui/icons/Cancel";
import CheckCircle from "@material-ui/icons/CheckCircle";
import { Alert } from "@material-ui/lab";
import React, { useMemo } from "react";
import { getLevelEmoji } from "../../features/Scorecards/scorecards";
import useSearchQuery from "../../hooks/api-hooks/useSearchQuery";

const SERVICE_BLUEPRINT_ID = "service";
export type ScorecardRow = {
type: "scorecard";
scorecard: string;
level: string;
};
export type RuleRow = {
type: "rule";
rule: string;
status: "SUCCESS" | "FAILURE";
scorecard: string;
level: string;
};
export type OverviewRow = ScorecardRow | RuleRow;

const useStyles = makeStyles({
scorecardRow: {
backgroundColor: "#F0F0F0",
fontWeight: "bold",
fontSize: "1.1em",
color: "#333333",
padding: "12px 8px",
borderBottom: "2px solid #1976d2",
textTransform: "uppercase",
letterSpacing: "0.5px",
},
scorecardRowMedal: {
backgroundColor: "#F0F0F0",
fontSize: "1.5em",
},
nameRow: {
backgroundColor: "transparent",
},
nameCell: {
backgroundColor: "transparent",
fontStyle: "italic",
whiteSpace: "nowrap",
maxWidth: "50px",
},
});

export const ScorecardsSummaryPage = () => {
const { data, isLoading, error } = useSearchQuery({
combinator: "and",
rules: [
{
property: "$blueprint",
operator: "=",
value: SERVICE_BLUEPRINT_ID,
},
],
});

const classes = useStyles();

const transposedData: OverviewRow[] = useMemo(() => {
if (!data) {
return [];
}
return Object.entries(data[0]?.scorecards ?? {})
.map(([scorecardName, scorecard]) => {
return [
{
type: "scorecard",
scorecard: scorecardName,
level: scorecard.level,
},
...scorecard.rules.map((rule) => ({
type: "rule",
scorecard: scorecardName,
rule: rule.identifier,
status: rule.status,
level: rule.level,
})),
] as OverviewRow[];
})
?.flat();
}, [data]);

return (
<Content noPadding>
{isLoading && <Progress />}
{error && <Alert severity="error">{error}</Alert>}
{!isLoading && !error && data && (
<TableContainer component={Paper}>
<Table>
<TableBody>
<TableRow>
<TableCell className={classes.nameRow}>Services</TableCell>
{data.map((entity) => {
return (
<TableCell className={classes.nameCell}>
{entity.title}
</TableCell>
);
})}
</TableRow>

{transposedData.map((row) => {
if (row.type === "scorecard") {
return (
<TableRow
key={`scorecard-${row.scorecard}`}
className={classes.scorecardRow}
>
<TableCell className={classes.scorecardRow}>
{row.scorecard}
</TableCell>
{data.map((entity) => {
const entityLevel =
entity.scorecards?.[row.scorecard]?.level;
return (
<TableCell className={classes.scorecardRowMedal}>
{getLevelEmoji(entityLevel as any)}
</TableCell>
);
})}
</TableRow>
);
}

return (
<TableRow key={`rule-${row.rule}`}>
<TableCell>
<div>{row.rule}</div>
</TableCell>
{data.map((entity) => {
const entityStatus = entity.scorecards?.[
row.scorecard
]?.rules?.find(
(rule) => rule.identifier === row.rule
)?.status;

return (
<TableCell>
{entityStatus === "SUCCESS" ? (
<CheckCircle color="primary" />
) : (
<Cancel color="secondary" />
)}
</TableCell>
);
})}
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
)}
</Content>
);
};
10 changes: 10 additions & 0 deletions frontend-plugin/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,13 @@ export const HomePage = portPlugin.provide(
},
})
);

export const ScorecardsPage = portPlugin.provide(
createComponentExtension({
name: "ScorecardsPage",
component: {
lazy: () =>
import("./pages/scorecards/scorecards").then((m) => m.ScorecardsPage),
},
})
);
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2602,7 +2602,7 @@
zen-observable "^0.10.0"
zod "^3.22.4"

"@backstage/core-plugin-api@^1.9.4":
"@backstage/core-plugin-api@^1.9.3", "@backstage/core-plugin-api@^1.9.4":
version "1.9.4"
resolved "https://registry.yarnpkg.com/@backstage/core-plugin-api/-/core-plugin-api-1.9.4.tgz#3341207f67321f705462d77258ab5ee73e3e8da7"
integrity sha512-YFQKgGmN8cPsPyBpkELWGajVTfVV99IlcGgjggkGE6Qd9vKLyU1Wj76a8cJC8itcS8gw+BDJQ4AvdTKBuW707Q==
Expand Down

0 comments on commit 260fbc8

Please sign in to comment.