Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions plugins/openchoreo-ci-backend/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,27 @@ export async function createRouter({
);
});

router.delete('/workflow-run', requireAuth, async (req, res) => {
const { componentName, projectName, namespaceName, runName } = req.query;

if (!componentName || !projectName || !namespaceName || !runName) {
throw new InputError(
'componentName, projectName, namespaceName and runName are required query parameters',
);
}

const userToken = getUserTokenFromRequest(req);

await workflowService.deleteWorkflowRun(
namespaceName as string,
projectName as string,
componentName as string,
runName as string,
userToken,
);
res.status(204).end();
});

router.get('/workflow-run', async (req, res) => {
const { componentName, projectName, namespaceName, runName } = req.query;

Expand Down
38 changes: 38 additions & 0 deletions plugins/openchoreo-ci-backend/src/services/WorkflowService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,44 @@ export class WorkflowService {
}
}

async deleteWorkflowRun(
namespaceName: string,
projectName: string,
componentName: string,
runName: string,
token?: string,
): Promise<void> {
this.logger.info(
`Deleting workflow run: ${runName} for component: ${componentName} in project: ${projectName}, namespace: ${namespaceName}`,
);

try {
const client = createOpenChoreoApiClient({
baseUrl: this.baseUrl,
token,
logger: this.logger,
});

const { data, error, response } = await client.DELETE(
'/api/v1/namespaces/{namespaceName}/workflowruns/{runName}',
{
params: {
path: { namespaceName, runName },
},
},
);

assertApiResponse({ data, error, response }, 'delete workflow run');

this.logger.debug(`Successfully deleted workflow run: ${runName}`);
} catch (error) {
this.logger.error(
`Failed to delete workflow run ${runName} in namespace ${namespaceName}: ${error}`,
);
throw error;
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/**
* Fetch JSONSchema for a specific component workflow
*/
Expand Down
12 changes: 12 additions & 0 deletions plugins/openchoreo-ci/src/api/OpenChoreoCiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,4 +237,16 @@ export class OpenChoreoCiClient implements OpenChoreoCiClientApi {
const entries = (await response.json()) as WorkflowRunEventEntry[];
return entries;
}

async deleteWorkflowRun(
namespaceName: string,
projectName: string,
componentName: string,
runName: string,
): Promise<void> {
return this.apiFetch<void>('/workflow-run', {
method: 'DELETE',
params: { namespaceName, projectName, componentName, runName },
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}
8 changes: 8 additions & 0 deletions plugins/openchoreo-ci/src/api/OpenChoreoCiClientApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,14 @@ export interface OpenChoreoCiClientApi {
step: string,
hasLiveObservability: boolean,
): Promise<WorkflowRunEventEntry[]>;

/** Delete a specific workflow run */
deleteWorkflowRun(
namespaceName: string,
projectName: string,
componentName: string,
runName: string,
): Promise<void>;
}

// ============================================
Expand Down
12 changes: 11 additions & 1 deletion plugins/openchoreo-ci/src/components/RunsTab/RunsTab.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { RunsTab } from './RunsTab';
import type { ModelsBuild } from '@openchoreo/backstage-plugin-common';
import { TestApiProvider } from '@backstage/test-utils';
import { openChoreoCiClientApiRef } from '../../api/OpenChoreoCiClientApi';

// ---- Mocks ----

Expand Down Expand Up @@ -78,6 +80,10 @@ const builds: ModelsBuild[] = [
},
];

const mockCiClient = {
deleteWorkflowRun: jest.fn(),
};

Comment thread
coderabbitai[bot] marked this conversation as resolved.
function renderTab(
overrides: Partial<React.ComponentProps<typeof RunsTab>> = {},
) {
Expand All @@ -90,7 +96,11 @@ function renderTab(
};

return {
...render(<RunsTab {...defaultProps} {...overrides} />),
...render(
<TestApiProvider apis={[[openChoreoCiClientApiRef, mockCiClient]]}>
<RunsTab {...defaultProps} {...overrides} />
</TestApiProvider>
),
props: { ...defaultProps, ...overrides },
};
}
Expand Down
32 changes: 32 additions & 0 deletions plugins/openchoreo-ci/src/components/RunsTab/RunsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { Table, TableColumn } from '@backstage/core-components';
import { Typography, Box, IconButton, Tooltip } from '@material-ui/core';
import Refresh from '@material-ui/icons/Refresh';
import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined';
import DeleteIcon from '@material-ui/icons/Delete';
import { useApi } from '@backstage/core-plugin-api';
import { BuildStatusChip } from '../BuildStatusChip';
import { openChoreoCiClientApiRef } from '../../api/OpenChoreoCiClientApi';
import type { ModelsBuild } from '@openchoreo/backstage-plugin-common';
import { formatRelativeTime } from '@openchoreo/backstage-plugin-react';
import { extractGitFieldValues } from '../../utils/schemaExtensions';
Expand Down Expand Up @@ -30,6 +33,7 @@ export const RunsTab = ({
retentionTtl,
}: RunsTabProps) => {
const classes = useStyles();
const client = useApi(openChoreoCiClientApiRef);

const columns: TableColumn[] = [
{
Expand Down Expand Up @@ -122,6 +126,34 @@ export const RunsTab = ({
sorting: true,
}}
columns={columns}
actions={[
{
icon: () => <DeleteIcon />,
tooltip: 'Delete Run',
onClick: async (_event, rowData) => {
const run = rowData as ModelsBuild;
if (
// eslint-disable-next-line no-alert
window.confirm(
`Are you sure you want to delete workflow run "${run.name}"?`,
)
) {
try {
await client.deleteWorkflowRun(
run.namespaceName!,
run.projectName!,
run.componentName!,
run.name!,
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
onRefresh();
} catch (err) {
// eslint-disable-next-line no-alert
window.alert(`Failed to delete run: ${err}`);
}
}
},
},
]}
data={sortedBuilds}
onRowClick={(_, rowData) => {
onRowClick(rowData as ModelsBuild);
Expand Down
19 changes: 19 additions & 0 deletions plugins/openchoreo-workflows-backend/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,25 @@ export async function createRouter({
);
});

// DELETE /workflow-runs/:runName - Delete a workflow run
router.delete('/workflow-runs/:runName', requireAuth, async (req, res) => {
const { runName } = req.params;
const { namespaceName } = req.query;

if (!namespaceName) {
throw new InputError('namespaceName is required query parameter');
}

const userToken = getUserTokenFromRequest(req);

await workflowService.deleteWorkflowRun(
namespaceName as string,
runName,
userToken,
);
res.status(204).end();
});

// GET /workflow-runs/:runName/logs - Get workflow run logs
router.get('/workflow-runs/:runName/logs', async (req, res) => {
const { runName } = req.params;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -722,4 +722,43 @@ export class GenericWorkflowService {
throw error;
}
}

/**
* Delete a specific workflow run
*/
async deleteWorkflowRun(
namespaceName: string,
runName: string,
token?: string,
): Promise<void> {
this.logger.info(
`Deleting workflow run: ${runName} in namespace: ${namespaceName}`,
);

try {
const client = createOpenChoreoApiClient({
baseUrl: this.baseUrl,
token,
logger: this.logger,
});

const { data, error, response } = await client.DELETE(
'/api/v1/namespaces/{namespaceName}/workflowruns/{runName}',
{
params: {
path: { namespaceName, runName },
},
},
);

assertApiResponse({ data, error, response }, 'delete workflow run');

this.logger.debug(`Successfully deleted workflow run: ${runName}`);
} catch (error) {
this.logger.error(
`Failed to delete workflow run ${runName} in namespace ${namespaceName}: ${error}`,
);
throw error;
}
}
}
13 changes: 13 additions & 0 deletions plugins/openchoreo-workflows/src/api/GenericWorkflowsClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,4 +166,17 @@ export class GenericWorkflowsClient implements GenericWorkflowsClientApi {
{ params },
);
}

async deleteWorkflowRun(
namespaceName: string,
runName: string,
): Promise<void> {
return this.apiFetch<void>(
`/workflow-runs/${encodeURIComponent(runName)}`,
{
method: 'DELETE',
params: { namespaceName },
},
);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ export interface GenericWorkflowsClientApi {
runName: string,
task?: string,
): Promise<WorkflowRunEventEntry[]>;

/** Delete a specific workflow run */
deleteWorkflowRun(namespaceName: string, runName: string): Promise<void>;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { makeStyles } from '@material-ui/core/styles';
import RefreshIcon from '@material-ui/icons/Refresh';
import PlayArrowIcon from '@material-ui/icons/PlayArrow';
import CloseIcon from '@material-ui/icons/Close';
import DeleteIcon from '@material-ui/icons/Delete';
import DescriptionOutlinedIcon from '@material-ui/icons/DescriptionOutlined';
import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined';
import EventNoteOutlinedIcon from '@material-ui/icons/EventNoteOutlined';
Expand Down Expand Up @@ -728,6 +729,7 @@ export const WorkflowRunsContent = () => {
const { entity } = useEntity();
const [searchParams, setSearchParams] = useSearchParams();
const [showTriggerForm, setShowTriggerForm] = useState(false);
const client = useApi(genericWorkflowsClientApiRef);

const workflowName = entity.metadata.name;
const workflowKind: 'Workflow' | 'ClusterWorkflow' =
Expand Down Expand Up @@ -915,6 +917,29 @@ export const WorkflowRunsContent = () => {
<Table
data={runs}
columns={columns}
actions={[
{
icon: () => <DeleteIcon />,
tooltip: 'Delete Run',
onClick: async (_event, rowData) => {
const run = rowData as WorkflowRun;
if (
// eslint-disable-next-line no-alert
window.confirm(
`Are you sure you want to delete workflow run "${run.name}"?`,
)
) {
try {
await client.deleteWorkflowRun(runsNamespace, run.name);
refetch();
} catch (err) {
// eslint-disable-next-line no-alert
window.alert(`Failed to delete run: ${err}`);
}
}
},
},
]}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
options={{
search: true,
paging: true,
Expand Down
Loading