Skip to content

Commit

Permalink
Create a way to execute the stored scorecards from the database and s…
Browse files Browse the repository at this point in the history
…tore the result in the db as well janus-idp#17

kubesmarts/akrivis#17

Create a way to define scorecards and put them inside a database #16
kubesmarts/akrivis#16
  • Loading branch information
Rikkola committed Dec 19, 2024
1 parent 2a5b6d1 commit 5970d71
Show file tree
Hide file tree
Showing 34 changed files with 1,227 additions and 728 deletions.
2 changes: 1 addition & 1 deletion app-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ proxy:
# Change to "false" in case of using self hosted quay instance with a self-signed certificate
secure: true
'/ingestor/api':
target: 'http://localhost:8082'
target: 'http://localhost:8083'
headers:
X-Requested-With: 'XMLHttpRequest'
changeOrigin: true
Expand Down
18 changes: 2 additions & 16 deletions packages/app/src/components/catalog/EntityPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,7 @@ import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
import Button from '@mui/material/Button';
import Grid from '@mui/material/Grid';

import {
IngestorComponent,
RulesFetchComponent,
} from '@janus-idp/backstage-plugin-rules';
import { ScoreCardsView } from '@janus-idp/backstage-plugin-rules';

const techdocsContent = (
<EntityTechdocsContent>
Expand Down Expand Up @@ -200,18 +197,7 @@ const websiteEntityPage = (
{techdocsContent}
</EntityLayout.Route>
<EntityLayout.Route path="/rules" title="Scorecard">
<Grid container spacing={5} alignItems="stretch">
<Grid item xs="auto">
<RulesFetchComponent />
</Grid>
</Grid>
</EntityLayout.Route>
<EntityLayout.Route path="/ingestor" title="Ingestor">
<Grid container spacing={5} alignItems="stretch">
<Grid item xs="auto">
<IngestorComponent />
</Grid>
</Grid>
<ScoreCardsView />
</EntityLayout.Route>
</EntityLayout>
);
Expand Down
18 changes: 11 additions & 7 deletions plugins/rules/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,23 @@
"@backstage/core-components": "^0.14.6",
"@backstage/core-plugin-api": "^1.9.2",
"@backstage/theme": "^0.5.3",
"@kie-tools/yard-editor": "^0.32.0",
"@material-ui/core": "^4.9.13",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "^4.0.0-alpha.61",
"@patternfly/patternfly": "^5.1.0",
"@patternfly/react-charts": "^7.1.1",
"@patternfly/react-code-editor": "^5.3.3",
"@patternfly/react-core": "^5.1.2",
"@patternfly/react-icons": "^5.1.1",
"@patternfly/react-tokens": "^5.1.2",
"@patternfly/patternfly": "^5.4.0",
"@patternfly/react-charts": "^7.4.0",
"@patternfly/react-code-editor": "^5.4.0",
"@patternfly/react-core": "^5.4.0",
"@patternfly/react-icons": "^5.4.0",
"@patternfly/react-styles": "^5.4.0",
"@patternfly/react-table": "^5.4.0",
"@patternfly/react-tokens": "^5.4.0",
"react-use": "^17.2.4"
},
"peerDependencies": {
"react": "16.13.1 || ^17.0.0 || ^18.0.0"
"react": "16.13.1 || ^17.0.0 || ^18.0.0",
"react-router-dom": "^6.23.0"
},
"devDependencies": {
"@backstage/cli": "0.26.4",
Expand Down
98 changes: 86 additions & 12 deletions plugins/rules/src/api/RulesClient.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
import {ConfigApi, DiscoveryApi} from '@backstage/core-plugin-api';
import { ConfigApi, DiscoveryApi } from '@backstage/core-plugin-api';

import {data} from 'msw';
import { data } from 'msw';

import {ScoreCardApi} from './api';
import {Job, RawData, RawDataDetail, ScoreCard} from './types';
import { Response } from '@janus-idp/backstage-plugin-kiali/src/services/Api';

import { ScoreCardApi } from './api';
import {
Card,
CardConfig,
CardData,
Job,
RawData,
RawDataDetail,
ScoreCardResult,
} from './types';

export class ScoreCardBackendClient implements ScoreCardApi {
private readonly configApi: ConfigApi;

static readonly DEFAULT_INGESTOR_PROXY_PATH = '/ingestor/api';
static readonly DEFAULT_PROCESSOR_PROXY_PATH = '/processor/api';

private readonly discoveryApi: DiscoveryApi;
constructor(options: { discoveryApi: DiscoveryApi, configApi: ConfigApi}) {
constructor(options: { discoveryApi: DiscoveryApi; configApi: ConfigApi }) {
this.discoveryApi = options.discoveryApi;
this.configApi = options.configApi;
}
Expand All @@ -28,15 +37,68 @@ export class ScoreCardBackendClient implements ScoreCardApi {
}

private async getIngestorBaseUrl() {
return await this.getBaseUrl(ScoreCardBackendClient.DEFAULT_INGESTOR_PROXY_PATH);
return await this.getBaseUrl(
ScoreCardBackendClient.DEFAULT_INGESTOR_PROXY_PATH,
);
}

async createCard(card: Card, config: CardConfig): Promise<Response> {
const url = `${await this.getIngestorBaseUrl()}/card/add`;
const createJobRequest = {
card: card,
configuration: config,
};
return await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(createJobRequest),
});
}

async deleteCard(cardId: number): Promise<Response> {
const url = `${await this.getIngestorBaseUrl()}/card/${cardId}`;
return await fetch(url, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
}

async getCardData(cardId: number): Promise<CardData> {
const url = `${await this.getIngestorBaseUrl()}/card/${cardId}/data`;
const response = await fetch(url, {
method: 'GET',
});

return await this.handleResponse(response);
}

private async getProcessorBaseUrl() {
return this.getBaseUrl(ScoreCardBackendClient.DEFAULT_PROCESSOR_PROXY_PATH)
async listCardResults(): Promise<{ results: ScoreCardResult[] }> {
const url = `${await this.getIngestorBaseUrl()}/card/results`;
const response = await fetch(url, {
method: 'GET',
});

return await this.handleResponse(response);
}

async listScoreCards(): Promise<{ results: ScoreCard[] }> {
const url = `${await this.getProcessorBaseUrl()}/scorecards/run`;
async listCardResultHistory(
cardId: number,
): Promise<{ results: ScoreCardResult[] }> {
const url = `${await this.getIngestorBaseUrl()}/card/${cardId}/history`;
const response = await fetch(url, {
method: 'GET',
});

return await this.handleResponse(response);
}

async listScoreCards(): Promise<{ results: Card[] }> {
const url = `${await this.getIngestorBaseUrl()}/card/list`;
const response = await fetch(url, {
method: 'GET',
});
Expand Down Expand Up @@ -66,7 +128,7 @@ export class ScoreCardBackendClient implements ScoreCardApi {
jobId: number,
rawDataId: number,
): Promise<{ results: RawDataDetail }> {
const url = `${(await this.getIngestorBaseUrl())}/job/${jobId}/data/${rawDataId}`;
const url = `${await this.getIngestorBaseUrl()}/job/${jobId}/data/${rawDataId}`;
const response = await fetch(url, {
method: 'GET',
});
Expand All @@ -86,6 +148,18 @@ export class ScoreCardBackendClient implements ScoreCardApi {
return response;
}

async activate(jobId: number): Promise<Response> {
const url = `${await this.getIngestorBaseUrl()}/job/${jobId}/activate`;
const response: Response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
return response;
}

async deleteJob(jobId: number): Promise<Response> {
const url = `${await this.getIngestorBaseUrl()}/job/${jobId}`;
return await fetch(url, {
Expand Down
26 changes: 24 additions & 2 deletions plugins/rules/src/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
import { createApiRef } from '@backstage/core-plugin-api';

import { Job, RawData, RawDataDetail, ScoreCard } from './types';
import {
Card,
CardConfig,
CardData,
Job,
RawData,
RawDataDetail,
ScoreCardResult,
} from './types';

export interface ScoreCardApi {
listScoreCards(): Promise<{ results: ScoreCard[] }>;
createCard(card: Card, config: CardConfig): Promise<Response>;

deleteCard(id: number): Promise<Response>;

getCardData(cardId: number): Promise<CardData>;

listCardResults(): Promise<{ results: ScoreCardResult[] }>;

listCardResultHistory(
cardId: number,
): Promise<{ results: ScoreCardResult[] }>;

listScoreCards(): Promise<{ results: Card[] }>;

getJobs(): Promise<{ results: Job[] }>;

Expand All @@ -16,6 +36,8 @@ export interface ScoreCardApi {

testJob(jobId: number): Promise<Response>;

activate(jobId: number): Promise<Response>;

deleteJob(jobId: number): Promise<Response>;

createJob(cron: string, type: string, endpoint: string): Promise<Response>;
Expand Down
2 changes: 1 addition & 1 deletion plugins/rules/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type { ScoreCard } from './types';
export type { ScoreCardResult } from './types';
export type { ScoreCardBackendClient } from './RulesClient';
export { scoreCardApiRef } from './api';
export type { ScoreCardApi } from './api';
23 changes: 21 additions & 2 deletions plugins/rules/src/api/types.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,39 @@
export type ScoreCard = {
export type ScoreCardResult = {
cardId: number;
runTime: number;
status: string;
measureValue: number;
measureName: string;
maxValue: number;
yaml: string;
thresholds: Threshold[];
};

export type Card = {
id?: number;
definition: string;
};

export type CardConfig = {
definition: string;
};

export type CardData = {
cardDefinition: string;
configurationDefinition: string;
};

export type Threshold = {
name: string;
value: number;
limit: number;
};

export type Job = {
id: number;
type: string;
endpoint: string;
status: string; // TODO make enum
cron: string;
};

export type RawData = {
Expand Down
102 changes: 102 additions & 0 deletions plugins/rules/src/components/Cards/CardForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import React, { useCallback } from 'react';

import { useApi } from '@backstage/core-plugin-api';

import {
Modal,
ModalVariant,
Wizard,
WizardStep,
} from '@patternfly/react-core';

import { ScoreCardApi, scoreCardApiRef } from '../../api';
import { YamlEditor } from './YamlEditor';

type Props = {
isModalOpen: boolean;
cardId: number | undefined;
onAfterCreate: any;
};

export const CardForm = ({
isModalOpen = false,
cardId = undefined,
onAfterCreate = () => {},
}: Props) => {
const scoreCardApi: ScoreCardApi = useApi(scoreCardApiRef);

const [modalOpenState, setModalOpen] = React.useState(isModalOpen);
const [cardValue, setCardValue] = React.useState('');
const [configValue, setConfigValue] = React.useState('');

const load = useCallback(() => {
const id = cardId ?? -1;
if (id >= 0) {
scoreCardApi.getCardData(id).then(cardData => {
setCardValue(cardData.cardDefinition);
setConfigValue(cardData.configurationDefinition);
setModalOpen(isModalOpen);
});
} else {
setModalOpen(isModalOpen);
}
}, [scoreCardApi, isModalOpen, cardId]);

React.useEffect(() => {
load();
}, [load]);

const handleCardInputChange = (value: string | undefined) => {
if (value !== undefined) {
setCardValue(value);
}
};

const handleConfigInputChange = (value: string | undefined) => {
if (value !== undefined) {
setConfigValue(value);
}
};

const handleModalToggle = () => {
setModalOpen(!isModalOpen);
};

const createCard = () => {
scoreCardApi
.createCard({ definition: cardValue }, { definition: configValue })
.then(() => {
setModalOpen(false);
onAfterCreate();
});
};

return (
<Modal
className="pf-v5-theme-dark"
variant={ModalVariant.large}
title="Create a Card"
description="Card something"
isOpen={modalOpenState}
onClose={handleModalToggle}
>
<Wizard
height={400}
title="Basic wizard"
onSave={createCard}
onClose={handleModalToggle}
>
<WizardStep name="Configuration" id="config-step">
<YamlEditor value={configValue} onChange={handleConfigInputChange} />
</WizardStep>
<WizardStep
name="Card"
id="card-step"
footer={{ nextButtonText: 'Finish' }}
>
<YamlEditor value={cardValue} onChange={handleCardInputChange} />
</WizardStep>
</Wizard>
</Modal>
);
};
Loading

0 comments on commit 5970d71

Please sign in to comment.