From fc2f59d66f96e8355fbbc4759b57d7042c799e7c Mon Sep 17 00:00:00 2001 From: Abel Rodriguez Date: Thu, 25 May 2023 10:05:35 -0500 Subject: [PATCH 01/14] [CN-2385] apply date filter when searching pipeline executions [CN-2385] set date filter if undefined [CN-2385] add logs to debug undefined error [CN-2385] add check for undefined date filter --- .../src/components/PluginContainer.tsx | 11 +++++++---- .../src/components/date-picker/date-picker.tsx | 5 +++++ .../src/components/pipelines/PipelineExecutions.tsx | 8 +++++++- .../src/components/pipelines/constants.ts | 2 ++ .../src/services/gateService.ts | 7 +++++-- 5 files changed, 26 insertions(+), 7 deletions(-) diff --git a/spin-observatory-plugin-deck/src/components/PluginContainer.tsx b/spin-observatory-plugin-deck/src/components/PluginContainer.tsx index 2f4af29..f312ce3 100644 --- a/spin-observatory-plugin-deck/src/components/PluginContainer.tsx +++ b/spin-observatory-plugin-deck/src/components/PluginContainer.tsx @@ -4,9 +4,9 @@ import React, { useEffect, useState } from 'react'; import type { Application, IPipeline } from '@spinnaker/core'; import { ReactSelectInput, useDataSource } from '@spinnaker/core'; -import { DatePicker } from './date-picker/date-picker'; +import { DatePicker, IDateRange } from './date-picker/date-picker'; import { ParameterSelect } from './parameters'; -import { PipelineExecutions, STATUSES } from './pipelines'; +import { PipelineExecutions, STATUSES, MAX_DATE_RANGE } from './pipelines'; interface IPluginContainerProps { app: Application; @@ -17,6 +17,7 @@ export function PluginContainer({ app }: IPluginContainerProps) { const { data: pipelines } = useDataSource(dataSource); const [selectedPipeline, setSelectedPipeline] = useState(); const [selectedParams, setSelectedParams] = useState([]); + const [selectedDateRange, setSelectedDateRange] = useState({ start: 0, end: MAX_DATE_RANGE }); useEffect(() => { dataSource.activate(); @@ -29,8 +30,7 @@ export function PluginContainer({ app }: IPluginContainerProps) { }; const handleDateFilterChange = ({ start, end }: { start: number, end: number }) => { - // eslint-disable-next-line no-console - console.log("filter executions ", { start, end }); + setSelectedDateRange({ start, end }); }; return ( @@ -64,18 +64,21 @@ export function PluginContainer({ app }: IPluginContainerProps) { pipeline={selectedPipeline} parameters={selectedParams} status={STATUSES.SUCCESSFUL} + dateRange={selectedDateRange} /> diff --git a/spin-observatory-plugin-deck/src/components/date-picker/date-picker.tsx b/spin-observatory-plugin-deck/src/components/date-picker/date-picker.tsx index 81abedc..876fdfc 100644 --- a/spin-observatory-plugin-deck/src/components/date-picker/date-picker.tsx +++ b/spin-observatory-plugin-deck/src/components/date-picker/date-picker.tsx @@ -14,6 +14,11 @@ import { } from "@material-ui/pickers"; import DateFnsUtils from "@date-io/date-fns"; +export interface IDateRange { + start: number; + end: number; +} + const START_AND_END_BY_HOURS = ({ hours = 1 }) => { const now = new Date(); const minusHours = new Date(now.getTime() - hours * 60 * 60 * 1000); diff --git a/spin-observatory-plugin-deck/src/components/pipelines/PipelineExecutions.tsx b/spin-observatory-plugin-deck/src/components/pipelines/PipelineExecutions.tsx index c5c9f4a..328035f 100644 --- a/spin-observatory-plugin-deck/src/components/pipelines/PipelineExecutions.tsx +++ b/spin-observatory-plugin-deck/src/components/pipelines/PipelineExecutions.tsx @@ -4,15 +4,17 @@ import { IStatus, POLL_DELAY_MS, REQUEST_PAGE_SIZE } from './constants'; import { getExecutions } from '../../services/gateService'; import { ExecutionsContainer } from './ExecutionsContainer'; import { ExecutionsTable } from './ExecutionsTable'; +import { IDateRange } from '../date-picker/date-picker'; interface IPipelineExecutionsProps { appName: string; pipeline?: IPipeline; parameters: string[]; status: IStatus; + dateRange: IDateRange; } -export const PipelineExecutions = ({ appName, pipeline, parameters, status }: IPipelineExecutionsProps) => { +export const PipelineExecutions = ({ appName, pipeline, parameters, status, dateRange }: IPipelineExecutionsProps) => { const [executions, setExecutions] = useState([]); useEffect(() => { @@ -25,6 +27,8 @@ export const PipelineExecutions = ({ appName, pipeline, parameters, status }: IP pipelineName: pipeline.name, pageSize: REQUEST_PAGE_SIZE, statuses: status.values, + startDate: dateRange.start, + endDate: dateRange.end, }; getExecutions(appName, requestParams).then((resp) => setExecutions(resp)); @@ -36,6 +40,8 @@ export const PipelineExecutions = ({ appName, pipeline, parameters, status }: IP pipelineName: pipeline.name, statuses: status.values, pageSize: REQUEST_PAGE_SIZE, + startDate: dateRange.start, + endDate: dateRange.end, }); setExecutions(resp); }, POLL_DELAY_MS); diff --git a/spin-observatory-plugin-deck/src/components/pipelines/constants.ts b/spin-observatory-plugin-deck/src/components/pipelines/constants.ts index 1d63a80..6a6a6c5 100644 --- a/spin-observatory-plugin-deck/src/components/pipelines/constants.ts +++ b/spin-observatory-plugin-deck/src/components/pipelines/constants.ts @@ -23,3 +23,5 @@ export const REQUEST_PAGE_SIZE = 5000; export const DEFAULT_ROWS_PER_PAGE = 10; export const POLL_DELAY_MS = 10000; + +export const MAX_DATE_RANGE = 9007199254740991; diff --git a/spin-observatory-plugin-deck/src/services/gateService.ts b/spin-observatory-plugin-deck/src/services/gateService.ts index 0534434..d8bbb82 100644 --- a/spin-observatory-plugin-deck/src/services/gateService.ts +++ b/spin-observatory-plugin-deck/src/services/gateService.ts @@ -5,16 +5,19 @@ 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, firstItemIdx = 0 } = params; + const { pipelineName, pageSize, statuses, 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(',') }) + .query({ pipelineName, size: pageSize, startIndex: firstItemIdx, statuses: statuses.join(','), triggerTimeStartBoundary: startDate, triggerTimeEndBoundary: endDate }) .get(); return data; }; From b9214e385d3e9f0212deeb1bdfafff8877b864d2 Mon Sep 17 00:00:00 2001 From: Abel Rodriguez Date: Wed, 7 Jun 2023 13:00:33 -0500 Subject: [PATCH 02/14] [CN-2386] combine executions into single block --- .../src/components/PluginContainer.tsx | 17 +------- .../src/components/pipelines/ExecutionRow.tsx | 15 +++---- .../pipelines/ExecutionsContainer.tsx | 40 ------------------- .../components/pipelines/ExecutionsTable.tsx | 11 ++--- .../pipelines/PipelineExecutions.tsx | 13 +++--- .../src/components/pipelines/constants.ts | 33 +++++++-------- 6 files changed, 32 insertions(+), 97 deletions(-) delete mode 100644 spin-observatory-plugin-deck/src/components/pipelines/ExecutionsContainer.tsx diff --git a/spin-observatory-plugin-deck/src/components/PluginContainer.tsx b/spin-observatory-plugin-deck/src/components/PluginContainer.tsx index f312ce3..f2cbebf 100644 --- a/spin-observatory-plugin-deck/src/components/PluginContainer.tsx +++ b/spin-observatory-plugin-deck/src/components/PluginContainer.tsx @@ -18,6 +18,7 @@ export function PluginContainer({ app }: IPluginContainerProps) { const [selectedPipeline, setSelectedPipeline] = useState(); const [selectedParams, setSelectedParams] = useState([]); const [selectedDateRange, setSelectedDateRange] = useState({ start: 0, end: MAX_DATE_RANGE }); + const [selectedStatus, setSelectedStatus] = useState(STATUSES); useEffect(() => { dataSource.activate(); @@ -63,21 +64,7 @@ export function PluginContainer({ app }: IPluginContainerProps) { appName={app.name} pipeline={selectedPipeline} parameters={selectedParams} - status={STATUSES.SUCCESSFUL} - dateRange={selectedDateRange} - /> - - diff --git a/spin-observatory-plugin-deck/src/components/pipelines/ExecutionRow.tsx b/spin-observatory-plugin-deck/src/components/pipelines/ExecutionRow.tsx index 826b5b9..f87559c 100644 --- a/spin-observatory-plugin-deck/src/components/pipelines/ExecutionRow.tsx +++ b/spin-observatory-plugin-deck/src/components/pipelines/ExecutionRow.tsx @@ -17,7 +17,6 @@ interface IExecutionRowProps { parameters: string[]; onSelectOne: MouseEventHandler; isSelected: boolean; - inProgress: boolean; } const goToExecutionDetails = (executionId: string) => () => { @@ -27,6 +26,10 @@ const goToExecutionDetails = (executionId: string) => () => { }; const convertTimestamp = (ts: number) => { + if (!ts) { + return '' + } + return new Intl.DateTimeFormat('en-US', { year: '2-digit', month: 'numeric', @@ -37,7 +40,7 @@ const convertTimestamp = (ts: number) => { }).format(ts); }; -export const ExecutionRow = ({ execution, parameters, onSelectOne, isSelected, inProgress }: IExecutionRowProps) => { +export const ExecutionRow = ({ execution, parameters, onSelectOne, isSelected }: IExecutionRowProps) => { const styles = useStyles(); return ( {convertTimestamp(execution.startTime)} - {!inProgress && ( - - {convertTimestamp(execution.endTime)} - - )} + + {convertTimestamp(execution.endTime)} + {parameters.map((p) => ( {execution.trigger.parameters![p]} diff --git a/spin-observatory-plugin-deck/src/components/pipelines/ExecutionsContainer.tsx b/spin-observatory-plugin-deck/src/components/pipelines/ExecutionsContainer.tsx deleted file mode 100644 index 33730fa..0000000 --- a/spin-observatory-plugin-deck/src/components/pipelines/ExecutionsContainer.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React, { useState, ReactNode } from 'react'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; -import { Accordion, AccordionSummary, Typography, AccordionDetails } from '@material-ui/core'; -import Skeleton from '@material-ui/lab/Skeleton'; -import { makeStyles } from '@material-ui/core'; - -const useStyles = makeStyles({ - skeleton: { padding: '3rem', marginLeft: '2rem', marginRight: '2rem' }, - accordion: { marginBottom: '1rem' }, -}); - -interface IExecutionContainerProps { - loading: boolean; - heading: string; - children: NonNullable; -} - -export const ExecutionsContainer = ({ loading, heading, children }: IExecutionContainerProps) => { - const [expanded, setExpanded] = useState(false); - const styles = useStyles(); - - const onAccordionClick = () => { - setExpanded(!expanded); - }; - - return ( - - } onClick={onAccordionClick}> - {heading} - - {loading ? ( - [...Array(4).keys()].map((key) => ( - - )) - ) : ( - {children} - )} - - ); -}; diff --git a/spin-observatory-plugin-deck/src/components/pipelines/ExecutionsTable.tsx b/spin-observatory-plugin-deck/src/components/pipelines/ExecutionsTable.tsx index 2d6fdc1..25c3480 100644 --- a/spin-observatory-plugin-deck/src/components/pipelines/ExecutionsTable.tsx +++ b/spin-observatory-plugin-deck/src/components/pipelines/ExecutionsTable.tsx @@ -12,7 +12,7 @@ import React, { ChangeEvent, useState } from 'react'; import { IExecution } from '@spinnaker/core'; import { TableHeaders } from './TableHeaders'; import { ExecutionRow } from './ExecutionRow'; -import { IStatus, STATUSES, DEFAULT_ROWS_PER_PAGE } from './constants'; +import { DEFAULT_ROWS_PER_PAGE } from './constants'; import { PaginationActions } from './PaginationActions'; import { makeStyles } from '@material-ui/core'; @@ -24,19 +24,15 @@ const useStyles = makeStyles({ interface IExecutionsTableProps { executions: IExecution[]; parameters: string[]; - status: IStatus; } -export const ExecutionsTable = ({ executions, parameters, status }: IExecutionsTableProps) => { +export const ExecutionsTable = ({ executions, parameters }: IExecutionsTableProps) => { const [selectedExecutions, setSelectedExecutions] = useState([]); const [currentPage, setPage] = useState(0); const [rowsPerPage, setRowsPerPage] = useState(DEFAULT_ROWS_PER_PAGE); const styles = useStyles(); - const headers = - status === STATUSES.TRIGGERED - ? ['ID', 'Status', 'Start Time', ...parameters] - : ['ID', 'Status', 'Start Time', 'End Time', ...parameters]; + const headers = ['ID', 'Status', 'Start Time', 'End Time', ...parameters]; const handlePageChange = (_: any, newPage: number) => { setPage(newPage); @@ -86,7 +82,6 @@ export const ExecutionsTable = ({ executions, parameters, status }: IExecutionsT isSelected={isSelected(e.id)} execution={e} parameters={parameters} - inProgress={status === STATUSES.TRIGGERED} onSelectOne={handleSelectOne(e.id)} /> ))} diff --git a/spin-observatory-plugin-deck/src/components/pipelines/PipelineExecutions.tsx b/spin-observatory-plugin-deck/src/components/pipelines/PipelineExecutions.tsx index 328035f..6b09067 100644 --- a/spin-observatory-plugin-deck/src/components/pipelines/PipelineExecutions.tsx +++ b/spin-observatory-plugin-deck/src/components/pipelines/PipelineExecutions.tsx @@ -1,8 +1,7 @@ import React, { useEffect, useState } from 'react'; import { IExecution, IPipeline, useInterval } from '@spinnaker/core'; -import { IStatus, POLL_DELAY_MS, REQUEST_PAGE_SIZE } from './constants'; +import { POLL_DELAY_MS, REQUEST_PAGE_SIZE } from './constants'; import { getExecutions } from '../../services/gateService'; -import { ExecutionsContainer } from './ExecutionsContainer'; import { ExecutionsTable } from './ExecutionsTable'; import { IDateRange } from '../date-picker/date-picker'; @@ -10,7 +9,7 @@ interface IPipelineExecutionsProps { appName: string; pipeline?: IPipeline; parameters: string[]; - status: IStatus; + status: string[]; dateRange: IDateRange; } @@ -26,7 +25,7 @@ export const PipelineExecutions = ({ appName, pipeline, parameters, status, date const requestParams = { pipelineName: pipeline.name, pageSize: REQUEST_PAGE_SIZE, - statuses: status.values, + statuses: status, startDate: dateRange.start, endDate: dateRange.end, }; @@ -38,7 +37,7 @@ export const PipelineExecutions = ({ appName, pipeline, parameters, status, date if (!pipeline) return; const resp = await getExecutions(appName, { pipelineName: pipeline.name, - statuses: status.values, + statuses: status, pageSize: REQUEST_PAGE_SIZE, startDate: dateRange.start, endDate: dateRange.end, @@ -47,8 +46,6 @@ export const PipelineExecutions = ({ appName, pipeline, parameters, status, date }, POLL_DELAY_MS); return ( - - - + ); }; diff --git a/spin-observatory-plugin-deck/src/components/pipelines/constants.ts b/spin-observatory-plugin-deck/src/components/pipelines/constants.ts index 6a6a6c5..fbc2500 100644 --- a/spin-observatory-plugin-deck/src/components/pipelines/constants.ts +++ b/spin-observatory-plugin-deck/src/components/pipelines/constants.ts @@ -1,22 +1,17 @@ -export interface IStatus { - text: string; - values: string[]; -} - -export const STATUSES = { - SUCCESSFUL: { - text: 'Successful', - values: ['SUCCEEDED'], - }, - FAILED: { - text: 'Failed', - values: ['FAILED_CONTINUE', 'TERMINAL', 'CANCELED'], - }, - TRIGGERED: { - text: 'Triggered', - values: ['NOT_STARTED', 'RUNNING', 'PAUSED', 'SUSPENDED', 'BUFFERED', 'STOPPED', 'SKIPPED'], - }, -}; +export const STATUSES = [ + 'SUCCEEDED', + 'FAILED_CONTINUE', + 'TERMINAL', + 'CANCELED', + 'NOT_STARTED', + 'RUNNING', + 'PAUSED', + 'SUSPENDED', + 'BUFFERED', + 'STOPPED', + 'SKIPPED', + 'REDIRECT', +]; export const REQUEST_PAGE_SIZE = 5000; From 9e983c8cf60c17f64c663b49b12d0655ec6b853a Mon Sep 17 00:00:00 2001 From: Abel Rodriguez Date: Wed, 7 Jun 2023 14:09:41 -0500 Subject: [PATCH 03/14] [CN-2386] remove trailing comma --- .../src/components/pipelines/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spin-observatory-plugin-deck/src/components/pipelines/constants.ts b/spin-observatory-plugin-deck/src/components/pipelines/constants.ts index fbc2500..52928fa 100644 --- a/spin-observatory-plugin-deck/src/components/pipelines/constants.ts +++ b/spin-observatory-plugin-deck/src/components/pipelines/constants.ts @@ -10,7 +10,7 @@ export const STATUSES = [ 'BUFFERED', 'STOPPED', 'SKIPPED', - 'REDIRECT', + 'REDIRECT' ]; export const REQUEST_PAGE_SIZE = 5000; From d59a615d898a93bf37c0fafd2d8cc2e00506bf91 Mon Sep 17 00:00:00 2001 From: Abel Rodriguez Date: Wed, 7 Jun 2023 15:20:11 -0500 Subject: [PATCH 04/14] [CN-2386] display message if no executions found --- .../src/components/PluginContainer.tsx | 24 +++++++++++++------ .../components/pipelines/ExecutionsTable.tsx | 4 ++++ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/spin-observatory-plugin-deck/src/components/PluginContainer.tsx b/spin-observatory-plugin-deck/src/components/PluginContainer.tsx index f2cbebf..1e4f68b 100644 --- a/spin-observatory-plugin-deck/src/components/PluginContainer.tsx +++ b/spin-observatory-plugin-deck/src/components/PluginContainer.tsx @@ -34,6 +34,22 @@ export function PluginContainer({ app }: IPluginContainerProps) { setSelectedDateRange({ start, end }); }; + const pipelineExecutions = () => { + if (!selectedPipeline) { + return

Select a pipeline..

+ } + + return ( + + ) + } + return (
@@ -60,13 +76,7 @@ export function PluginContainer({ app }: IPluginContainerProps) {
- + {pipelineExecutions}
); diff --git a/spin-observatory-plugin-deck/src/components/pipelines/ExecutionsTable.tsx b/spin-observatory-plugin-deck/src/components/pipelines/ExecutionsTable.tsx index 25c3480..9e210b8 100644 --- a/spin-observatory-plugin-deck/src/components/pipelines/ExecutionsTable.tsx +++ b/spin-observatory-plugin-deck/src/components/pipelines/ExecutionsTable.tsx @@ -65,6 +65,10 @@ export const ExecutionsTable = ({ executions, parameters }: IExecutionsTableProp }; const isSelected = (name: string) => selectedExecutions.indexOf(name) !== -1; + + if (executions.length == 0) { + return

No pipeline executions found.

+ } return ( From 81d77eb5eba1739f1ea423fc2d74ad37f4a509e1 Mon Sep 17 00:00:00 2001 From: Abel Rodriguez Date: Wed, 7 Jun 2023 15:44:01 -0500 Subject: [PATCH 05/14] [CN-2386] refactor conditional render of pipeline executions --- .../src/components/PluginContainer.tsx | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/spin-observatory-plugin-deck/src/components/PluginContainer.tsx b/spin-observatory-plugin-deck/src/components/PluginContainer.tsx index 1e4f68b..3c7b4e9 100644 --- a/spin-observatory-plugin-deck/src/components/PluginContainer.tsx +++ b/spin-observatory-plugin-deck/src/components/PluginContainer.tsx @@ -34,22 +34,6 @@ export function PluginContainer({ app }: IPluginContainerProps) { setSelectedDateRange({ start, end }); }; - const pipelineExecutions = () => { - if (!selectedPipeline) { - return

Select a pipeline..

- } - - return ( - - ) - } - return (
@@ -76,7 +60,17 @@ export function PluginContainer({ app }: IPluginContainerProps) {
- {pipelineExecutions} + {!selectedPipeline ? ( +

Select a pipeline..

+ ) : ( + + )}
); From f21d465560b93e8eea5b08f5b6992bfa27bb7c2a Mon Sep 17 00:00:00 2001 From: Abel Rodriguez Date: Wed, 7 Jun 2023 16:01:24 -0500 Subject: [PATCH 06/14] [CN-2386] remove conditional render --- .../src/components/PluginContainer.tsx | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/spin-observatory-plugin-deck/src/components/PluginContainer.tsx b/spin-observatory-plugin-deck/src/components/PluginContainer.tsx index 3c7b4e9..f2cbebf 100644 --- a/spin-observatory-plugin-deck/src/components/PluginContainer.tsx +++ b/spin-observatory-plugin-deck/src/components/PluginContainer.tsx @@ -60,17 +60,13 @@ export function PluginContainer({ app }: IPluginContainerProps) {
- {!selectedPipeline ? ( -

Select a pipeline..

- ) : ( - - )} +
); From dc79190aa448330954ada7118581c1acba832366 Mon Sep 17 00:00:00 2001 From: Abel Rodriguez Date: Wed, 7 Jun 2023 16:10:42 -0500 Subject: [PATCH 07/14] [CN-2386] add conditional render when landing on page --- .../src/components/PluginContainer.tsx | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/spin-observatory-plugin-deck/src/components/PluginContainer.tsx b/spin-observatory-plugin-deck/src/components/PluginContainer.tsx index f2cbebf..3c7b4e9 100644 --- a/spin-observatory-plugin-deck/src/components/PluginContainer.tsx +++ b/spin-observatory-plugin-deck/src/components/PluginContainer.tsx @@ -60,13 +60,17 @@ export function PluginContainer({ app }: IPluginContainerProps) {
- + {!selectedPipeline ? ( +

Select a pipeline..

+ ) : ( + + )}
); From d87739d8a89892299b9b1444691674f18aca3198 Mon Sep 17 00:00:00 2001 From: Abel Rodriguez Date: Wed, 7 Jun 2023 16:23:53 -0500 Subject: [PATCH 08/14] [CN-2386] change message on landing page --- spin-observatory-plugin-deck/src/components/PluginContainer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spin-observatory-plugin-deck/src/components/PluginContainer.tsx b/spin-observatory-plugin-deck/src/components/PluginContainer.tsx index 3c7b4e9..1911ed1 100644 --- a/spin-observatory-plugin-deck/src/components/PluginContainer.tsx +++ b/spin-observatory-plugin-deck/src/components/PluginContainer.tsx @@ -61,7 +61,7 @@ export function PluginContainer({ app }: IPluginContainerProps) {
{!selectedPipeline ? ( -

Select a pipeline..

+

Please select a pipeline to view executions.

) : ( Date: Wed, 7 Jun 2023 16:37:41 -0500 Subject: [PATCH 09/14] [CN-2386] move executions check to pipeline executions component --- .../src/components/pipelines/ExecutionsTable.tsx | 4 ---- .../src/components/pipelines/PipelineExecutions.tsx | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spin-observatory-plugin-deck/src/components/pipelines/ExecutionsTable.tsx b/spin-observatory-plugin-deck/src/components/pipelines/ExecutionsTable.tsx index 9e210b8..25c3480 100644 --- a/spin-observatory-plugin-deck/src/components/pipelines/ExecutionsTable.tsx +++ b/spin-observatory-plugin-deck/src/components/pipelines/ExecutionsTable.tsx @@ -65,10 +65,6 @@ export const ExecutionsTable = ({ executions, parameters }: IExecutionsTableProp }; const isSelected = (name: string) => selectedExecutions.indexOf(name) !== -1; - - if (executions.length == 0) { - return

No pipeline executions found.

- } return ( diff --git a/spin-observatory-plugin-deck/src/components/pipelines/PipelineExecutions.tsx b/spin-observatory-plugin-deck/src/components/pipelines/PipelineExecutions.tsx index 6b09067..b45bc1f 100644 --- a/spin-observatory-plugin-deck/src/components/pipelines/PipelineExecutions.tsx +++ b/spin-observatory-plugin-deck/src/components/pipelines/PipelineExecutions.tsx @@ -45,6 +45,10 @@ export const PipelineExecutions = ({ appName, pipeline, parameters, status, date setExecutions(resp); }, POLL_DELAY_MS); + if (executions.length == 0) { + return

No pipeline executions found.

+ } + return ( ); From 4c4be43c1e22ca310e4ce9132c779bb2886959b4 Mon Sep 17 00:00:00 2001 From: Abel Rodriguez Date: Wed, 7 Jun 2023 17:17:43 -0500 Subject: [PATCH 10/14] [CN-2386] do not show message until response if received --- .../src/components/pipelines/PipelineExecutions.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/spin-observatory-plugin-deck/src/components/pipelines/PipelineExecutions.tsx b/spin-observatory-plugin-deck/src/components/pipelines/PipelineExecutions.tsx index b45bc1f..4aa4069 100644 --- a/spin-observatory-plugin-deck/src/components/pipelines/PipelineExecutions.tsx +++ b/spin-observatory-plugin-deck/src/components/pipelines/PipelineExecutions.tsx @@ -15,10 +15,12 @@ interface IPipelineExecutionsProps { export const PipelineExecutions = ({ appName, pipeline, parameters, status, dateRange }: IPipelineExecutionsProps) => { const [executions, setExecutions] = useState([]); + const [isLoading, setIsLoading] = useState(true); useEffect(() => { if (!pipeline) { setExecutions([]); + setIsLoading(false); return; } @@ -30,7 +32,10 @@ export const PipelineExecutions = ({ appName, pipeline, parameters, status, date endDate: dateRange.end, }; - getExecutions(appName, requestParams).then((resp) => setExecutions(resp)); + getExecutions(appName, requestParams).then((resp) => { + setExecutions(resp); + setIsLoading(false); + }); }, [pipeline]); useInterval(async () => { @@ -43,8 +48,13 @@ export const PipelineExecutions = ({ appName, pipeline, parameters, status, date endDate: dateRange.end, }); setExecutions(resp); + setIsLoading(false); }, POLL_DELAY_MS); + if (isLoading) { + return null; + } + if (executions.length == 0) { return

No pipeline executions found.

} From c9f5c7ccd34ae05720aa57c2fc726e5c51f10de1 Mon Sep 17 00:00:00 2001 From: Abel Rodriguez Date: Wed, 7 Jun 2023 17:39:23 -0500 Subject: [PATCH 11/14] [CN-2386] add skeleton when loading executions --- .../components/pipelines/PipelineExecutions.tsx | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/spin-observatory-plugin-deck/src/components/pipelines/PipelineExecutions.tsx b/spin-observatory-plugin-deck/src/components/pipelines/PipelineExecutions.tsx index 4aa4069..ce2b7ae 100644 --- a/spin-observatory-plugin-deck/src/components/pipelines/PipelineExecutions.tsx +++ b/spin-observatory-plugin-deck/src/components/pipelines/PipelineExecutions.tsx @@ -4,6 +4,12 @@ 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'; + +const useStyles = makeStyles({ + skeleton: { padding: '3rem', marginLeft: '2rem', marginRight: '2rem' }, +}); interface IPipelineExecutionsProps { appName: string; @@ -16,6 +22,7 @@ interface IPipelineExecutionsProps { export const PipelineExecutions = ({ appName, pipeline, parameters, status, dateRange }: IPipelineExecutionsProps) => { const [executions, setExecutions] = useState([]); const [isLoading, setIsLoading] = useState(true); + const styles = useStyles(); useEffect(() => { if (!pipeline) { @@ -52,14 +59,16 @@ export const PipelineExecutions = ({ appName, pipeline, parameters, status, date }, POLL_DELAY_MS); if (isLoading) { - return null; + return ( + [...Array(3).keys()].map((key) => ( + + )) + ); } if (executions.length == 0) { return

No pipeline executions found.

} - return ( - - ); + return }; From 2980de081dced8d59daf850593fd8284badd813e Mon Sep 17 00:00:00 2001 From: Jeff Billimek Date: Thu, 8 Jun 2023 09:04:24 -0400 Subject: [PATCH 12/14] Create LICENSE Fixes #13 --- LICENSE | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..938f8ca --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2023 The Home Depot + + 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. From d34b3471f8a1d0c23400f78168305604c401756e Mon Sep 17 00:00:00 2001 From: Abe Garcia <44239562+abe21412@users.noreply.github.com> Date: Fri, 16 Jun 2023 13:50:50 -0400 Subject: [PATCH 13/14] add codeowners file (#17) Co-authored-by: abe garcia --- .github/CODEOWNERS | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..82961d7 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,15 @@ +# Lines starting with '#' are comments. +# Each line is a file pattern followed by one or more owners. + +# More details are here: https://help.github.com/articles/about-codeowners/ + +# The '*' pattern is global owners. + +# Order is important. The last matching pattern has the most precedence. +# The folders are ordered as follows: + +# In each subsection folders are ordered first by depth, then alphabetically. +# This should make it easy to add new rules without breaking existing ones. + +# Global rule: +* @abe21412 @rodrig67 From 834a9d8f249ede3a76a2ba5b29f6c56e8bd62cee Mon Sep 17 00:00:00 2001 From: Abel Rodriguez Date: Tue, 20 Jun 2023 07:52:49 -0500 Subject: [PATCH 14/14] [CN-2388] add status filter (#16) * [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 --- .../src/components/PluginContainer.tsx | 34 ++++++++--- .../pipelines/PipelineExecutions.tsx | 52 ++++++++++++++--- .../src/components/pipelines/constants.ts | 15 ----- .../src/components/status/StatusSelect.tsx | 57 +++++++++++++++++++ .../src/components/status/index.ts | 1 + .../src/services/gateService.ts | 5 +- 6 files changed, 131 insertions(+), 33 deletions(-) create mode 100644 spin-observatory-plugin-deck/src/components/status/StatusSelect.tsx create mode 100644 spin-observatory-plugin-deck/src/components/status/index.ts diff --git a/spin-observatory-plugin-deck/src/components/PluginContainer.tsx b/spin-observatory-plugin-deck/src/components/PluginContainer.tsx index 1911ed1..a405abc 100644 --- a/spin-observatory-plugin-deck/src/components/PluginContainer.tsx +++ b/spin-observatory-plugin-deck/src/components/PluginContainer.tsx @@ -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; @@ -18,7 +19,8 @@ export function PluginContainer({ app }: IPluginContainerProps) { const [selectedPipeline, setSelectedPipeline] = useState(); const [selectedParams, setSelectedParams] = useState([]); const [selectedDateRange, setSelectedDateRange] = useState({ start: 0, end: MAX_DATE_RANGE }); - const [selectedStatus, setSelectedStatus] = useState(STATUSES); + const [selectedStatus, setSelectedStatus] = useState([]); + const [statusCount, setStatusCount] = useState>(new Map()); useEffect(() => { dataSource.activate(); @@ -27,6 +29,8 @@ export function PluginContainer({ app }: IPluginContainerProps) { const onPipelineSelect = async (e: ChangeEvent) => { const pipelineConfig = pipelines.find((p) => p.name === e.target.value); setSelectedParams([]); + setSelectedStatus([]); + setStatusCount(new Map()); setSelectedPipeline(pipelineConfig); }; @@ -34,6 +38,10 @@ export function PluginContainer({ app }: IPluginContainerProps) { setSelectedDateRange({ start, end }); }; + const handleStatusCountChange = (statusCount: Map) => { + setStatusCount(statusCount); + }; + return (
@@ -47,14 +55,23 @@ export function PluginContainer({ app }: IPluginContainerProps) { options={pipelines.map((p) => ({ label: p.name, value: p.name }))} />
-
-
+
+
- + +
+
@@ -64,10 +81,11 @@ export function PluginContainer({ app }: IPluginContainerProps) {

Please select a pipeline to view executions.

) : ( )} diff --git a/spin-observatory-plugin-deck/src/components/pipelines/PipelineExecutions.tsx b/spin-observatory-plugin-deck/src/components/pipelines/PipelineExecutions.tsx index ce2b7ae..f585e82 100644 --- a/spin-observatory-plugin-deck/src/components/pipelines/PipelineExecutions.tsx +++ b/spin-observatory-plugin-deck/src/components/pipelines/PipelineExecutions.tsx @@ -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' }, @@ -15,18 +18,23 @@ interface IPipelineExecutionsProps { appName: string; pipeline?: IPipeline; parameters: string[]; - status: string[]; + statuses: string[]; dateRange: IDateRange; + onStatusChange: (statusCount: Map) => void; } -export const PipelineExecutions = ({ appName, pipeline, parameters, status, dateRange }: IPipelineExecutionsProps) => { +export const PipelineExecutions = ({ appName, pipeline, parameters, statuses, dateRange, onStatusChange }: IPipelineExecutionsProps) => { const [executions, setExecutions] = useState([]); + const [filteredExecutions, setFilteredExecutions] = useState([]); + const [statusCount, setStatusCount] = useState>(new Map()); const [isLoading, setIsLoading] = useState(true); const styles = useStyles(); useEffect(() => { if (!pipeline) { setExecutions([]); + setFilteredExecutions([]); + setStatusCount(new Map()); setIsLoading(false); return; } @@ -34,30 +42,60 @@ export const PipelineExecutions = ({ appName, pipeline, parameters, status, date 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(); + 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) => ( @@ -70,5 +108,5 @@ export const PipelineExecutions = ({ appName, pipeline, parameters, status, date return

No pipeline executions found.

} - return + return }; diff --git a/spin-observatory-plugin-deck/src/components/pipelines/constants.ts b/spin-observatory-plugin-deck/src/components/pipelines/constants.ts index 52928fa..ded7c25 100644 --- a/spin-observatory-plugin-deck/src/components/pipelines/constants.ts +++ b/spin-observatory-plugin-deck/src/components/pipelines/constants.ts @@ -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; diff --git a/spin-observatory-plugin-deck/src/components/status/StatusSelect.tsx b/spin-observatory-plugin-deck/src/components/status/StatusSelect.tsx new file mode 100644 index 0000000..8832613 --- /dev/null +++ b/spin-observatory-plugin-deck/src/components/status/StatusSelect.tsx @@ -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; +} + +export const StatusSelect = ({className, pipeline, selectedStatus, setSelectedStatus, statusCount }: IStatusSelectProps) => { + const onStatusSelect = (options: Array>) => { + setSelectedStatus(options.map((o) => o.value)); + }; + + const extractStatus = (statusCount: Map): Array> => { + let options = []; + statusCount.forEach((value, key) => { + options.push({ label: `${key} (${value})`, value: key }); + }); + + return options; + }; + + return ( +