Skip to content

Commit

Permalink
[CN-2388] add status filter (#16)
Browse files Browse the repository at this point in the history
* [CN-2388] add status filter

* [CN-2388] test child sending status data to parent component

* [CN-2388] add hooks for status count change

* [CN-2388] fix parameter type mismatch

* [CN-2388] apply filter when response is received

* [CN-2388] filter executions on status change

* [CN-2388] fix issue with status select

* [CN-2388] revert change to status select

* [CN-2388] fix status select pt2

* [CN-2388] update style for select element

* [CN-2388] remove console logs

* [CN-2388] use map type for status count and use filter method

* [CN-2388] set status count default value

* [CN-2388] set status count default value pt2

---------

Co-authored-by: Abel Rodriguez <[email protected]>
  • Loading branch information
arodski and Abel Rodriguez committed Jun 20, 2023
1 parent d34b347 commit 834a9d8
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 33 deletions.
34 changes: 26 additions & 8 deletions spin-observatory-plugin-deck/src/components/PluginContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { ReactSelectInput, useDataSource } from '@spinnaker/core';

import { DatePicker, IDateRange } from './date-picker/date-picker';
import { ParameterSelect } from './parameters';
import { PipelineExecutions, STATUSES, MAX_DATE_RANGE } from './pipelines';
import { StatusSelect } from './status';
import { PipelineExecutions, MAX_DATE_RANGE } from './pipelines';

interface IPluginContainerProps {
app: Application;
Expand All @@ -18,7 +19,8 @@ export function PluginContainer({ app }: IPluginContainerProps) {
const [selectedPipeline, setSelectedPipeline] = useState<IPipeline>();
const [selectedParams, setSelectedParams] = useState<string[]>([]);
const [selectedDateRange, setSelectedDateRange] = useState<IDateRange>({ start: 0, end: MAX_DATE_RANGE });
const [selectedStatus, setSelectedStatus] = useState<string[]>(STATUSES);
const [selectedStatus, setSelectedStatus] = useState<string[]>([]);
const [statusCount, setStatusCount] = useState<Map<string, number>>(new Map<string, number>());

useEffect(() => {
dataSource.activate();
Expand All @@ -27,13 +29,19 @@ export function PluginContainer({ app }: IPluginContainerProps) {
const onPipelineSelect = async (e: ChangeEvent<HTMLSelectElement>) => {
const pipelineConfig = pipelines.find((p) => p.name === e.target.value);
setSelectedParams([]);
setSelectedStatus([]);
setStatusCount(new Map<string, number>());
setSelectedPipeline(pipelineConfig);
};

const handleDateFilterChange = ({ start, end }: { start: number, end: number }) => {
setSelectedDateRange({ start, end });
};

const handleStatusCountChange = (statusCount: Map<string, number>) => {
setStatusCount(statusCount);
};

return (
<div className="flex-container-v" style={{ margin: '3rem', width: '100%', rowGap: '2rem' }}>
<div className="flex-container-h" style={{ flexGrow: 1 }}>
Expand All @@ -47,14 +55,23 @@ export function PluginContainer({ app }: IPluginContainerProps) {
options={pipelines.map((p) => ({ label: p.name, value: p.name }))}
/>
</div>
<div className="flex-pull-right" style={{ width: '40rem' }}>
<div className="horizontal middle right">
<div className="flex-pull-right" style={{ width: '60rem' }}>
<div className="horizontal right">
<DatePicker onChange={handleDateFilterChange} disabled={!selectedPipeline} />
<ParameterSelect
<div className="flex-1" style={{ margin: '0 5px' }}>
<ParameterSelect
className="flex-1"
pipeline={selectedPipeline}
selectedParams={selectedParams}
setSelectedParams={setSelectedParams}
/>
</div>
<StatusSelect
className="flex-1"
pipeline={selectedPipeline}
selectedParams={selectedParams}
setSelectedParams={setSelectedParams}
selectedStatus={selectedStatus}
setSelectedStatus={setSelectedStatus}
statusCount={statusCount}
/>
</div>
</div>
Expand All @@ -64,10 +81,11 @@ export function PluginContainer({ app }: IPluginContainerProps) {
<h4 style={{ textAlign: 'center' }}>Please select a pipeline to view executions.</h4>
) : (
<PipelineExecutions
onStatusChange={handleStatusCountChange}
appName={app.name}
pipeline={selectedPipeline}
parameters={selectedParams}
status={selectedStatus}
statuses={selectedStatus}
dateRange={selectedDateRange}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import React, { useEffect, useState } from 'react';

import { IExecution, IPipeline, useInterval } from '@spinnaker/core';
import Skeleton from '@material-ui/lab/Skeleton';
import { makeStyles } from '@material-ui/core';

import { POLL_DELAY_MS, REQUEST_PAGE_SIZE } from './constants';
import { getExecutions } from '../../services/gateService';
import { ExecutionsTable } from './ExecutionsTable';
import { IDateRange } from '../date-picker/date-picker';
import Skeleton from '@material-ui/lab/Skeleton';
import { makeStyles } from '@material-ui/core';
import { STATUSES } from '../status';

const useStyles = makeStyles({
skeleton: { padding: '3rem', marginLeft: '2rem', marginRight: '2rem' },
Expand All @@ -15,49 +18,84 @@ interface IPipelineExecutionsProps {
appName: string;
pipeline?: IPipeline;
parameters: string[];
status: string[];
statuses: string[];
dateRange: IDateRange;
onStatusChange: (statusCount: Map<string, number>) => void;
}

export const PipelineExecutions = ({ appName, pipeline, parameters, status, dateRange }: IPipelineExecutionsProps) => {
export const PipelineExecutions = ({ appName, pipeline, parameters, statuses, dateRange, onStatusChange }: IPipelineExecutionsProps) => {
const [executions, setExecutions] = useState<IExecution[]>([]);
const [filteredExecutions, setFilteredExecutions] = useState<IExecution[]>([]);
const [statusCount, setStatusCount] = useState<Map<string, number>>(new Map<string, number>());
const [isLoading, setIsLoading] = useState<boolean>(true);
const styles = useStyles();

useEffect(() => {
if (!pipeline) {
setExecutions([]);
setFilteredExecutions([]);
setStatusCount(new Map<string, number>());
setIsLoading(false);
return;
}

const requestParams = {
pipelineName: pipeline.name,
pageSize: REQUEST_PAGE_SIZE,
statuses: status,
startDate: dateRange.start,
endDate: dateRange.end,
};

getExecutions(appName, requestParams).then((resp) => {
setExecutions(resp);
setFilteredExecutions(filterExecutions(resp));
setStatusCount(getStatusCount(resp));
setIsLoading(false);
});
}, [pipeline]);

useEffect(() => {
onStatusChange(statusCount);
}, [statusCount]);

useEffect(() => {
setFilteredExecutions(filterExecutions(executions));
}, [statuses]);

useInterval(async () => {
if (!pipeline) return;
const resp = await getExecutions(appName, {
pipelineName: pipeline.name,
statuses: status,
pageSize: REQUEST_PAGE_SIZE,
startDate: dateRange.start,
endDate: dateRange.end,
});

setExecutions(resp);
setFilteredExecutions(filterExecutions(resp));
setStatusCount(getStatusCount(resp));
setIsLoading(false);
}, POLL_DELAY_MS);

const filterExecutions = (ex: IExecution[]) => {
const statusArr = statuses.length === 0 ? STATUSES : statuses;

return ex.filter(e => statusArr.includes(e.status));
};

const getStatusCount = (ex: IExecution[]) => {
let statusCount = new Map<string, number>();
for (const e of ex) {
if (!statusCount.has(e.status)) {
statusCount.set(e.status, 1);
} else {
statusCount.set(e.status, statusCount.get(e.status) + 1);
}
}

return statusCount;
};

if (isLoading) {
return (
[...Array(3).keys()].map((key) => (
Expand All @@ -70,5 +108,5 @@ export const PipelineExecutions = ({ appName, pipeline, parameters, status, date
return <h4 style={{ textAlign: 'center' }}>No pipeline executions found.</h4>
}

return <ExecutionsTable executions={executions} parameters={parameters} />
return <ExecutionsTable executions={filteredExecutions} parameters={parameters} />
};
15 changes: 0 additions & 15 deletions spin-observatory-plugin-deck/src/components/pipelines/constants.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,3 @@
export const STATUSES = [
'SUCCEEDED',
'FAILED_CONTINUE',
'TERMINAL',
'CANCELED',
'NOT_STARTED',
'RUNNING',
'PAUSED',
'SUSPENDED',
'BUFFERED',
'STOPPED',
'SKIPPED',
'REDIRECT'
];

export const REQUEST_PAGE_SIZE = 5000;

export const DEFAULT_ROWS_PER_PAGE = 10;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';
import type { Option } from 'react-select';
import Select from 'react-select';

import type { IPipeline } from '@spinnaker/core';

export const STATUSES = [
'SUCCEEDED',
'FAILED_CONTINUE',
'TERMINAL',
'CANCELED',
'NOT_STARTED',
'RUNNING',
'PAUSED',
'SUSPENDED',
'BUFFERED',
'STOPPED',
'SKIPPED',
'REDIRECT'
];

interface IStatusSelectProps {
className?: string;
pipeline?: IPipeline;
selectedStatus: string[];
setSelectedStatus(params: string[]): void;
statusCount: Map<string, number>;
}

export const StatusSelect = ({className, pipeline, selectedStatus, setSelectedStatus, statusCount }: IStatusSelectProps) => {
const onStatusSelect = (options: Array<Option<string>>) => {
setSelectedStatus(options.map((o) => o.value));
};

const extractStatus = (statusCount: Map<string, number>): Array<Option<string>> => {
let options = [];
statusCount.forEach((value, key) => {
options.push({ label: `${key} (${value})`, value: key });
});

return options;
};

return (
<Select
className={className}
onChange={onStatusSelect}
value={selectedStatus}
disabled={!pipeline}
placeholder="Select Status..."
clearable={true}
noResultsText="No status"
options={!pipeline ? [] : extractStatus(statusCount)}
multi
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './StatusSelect';
5 changes: 2 additions & 3 deletions spin-observatory-plugin-deck/src/services/gateService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,19 @@ import { REST } from '@spinnaker/core';
interface IExecutionsParams {
pipelineName: string;
pageSize: number;
statuses: string[];
startDate: number;
endDate: number;
firstItemIdx?: number;
}

export const getExecutions = async (appName: string, params: IExecutionsParams) => {
const { pipelineName, pageSize, statuses, startDate, endDate, firstItemIdx = 0 } = params;
const { pipelineName, pageSize, startDate, endDate, firstItemIdx = 0 } = params;

const data = await REST('/applications')
.path(appName)
.path('executions')
.path('search')
.query({ pipelineName, size: pageSize, startIndex: firstItemIdx, statuses: statuses.join(','), triggerTimeStartBoundary: startDate, triggerTimeEndBoundary: endDate })
.query({ pipelineName, size: pageSize, startIndex: firstItemIdx, triggerTimeStartBoundary: startDate, triggerTimeEndBoundary: endDate })
.get<IExecution[]>();
return data;
};

0 comments on commit 834a9d8

Please sign in to comment.