diff --git a/plugins/cicd-statistics/src/apis/types.ts b/plugins/cicd-statistics/src/apis/types.ts index b185a4a50e..19b4ac84b2 100644 --- a/plugins/cicd-statistics/src/apis/types.ts +++ b/plugins/cicd-statistics/src/apis/types.ts @@ -132,13 +132,14 @@ export interface CicdDefaults { filterStatus: Array; filterType: FilterBranchType | 'all'; - /** Default collapse the stages with a max-duration below this value */ - collapsedLimit: number; - /** Lower-case all stage names (to potentially merge stages with different cases) */ lowercaseNames: boolean; /** Normalize the from-to date range in all charts */ normalizeTimeRange: boolean; + /** Default collapse the stages with a max-duration below this value */ + collapsedLimit: number; + /** Default hide stages with a max-duration below this value */ + hideLimit: number; /** Chart types per status */ chartTypes: Record; } diff --git a/plugins/cicd-statistics/src/charts/stage-chart.tsx b/plugins/cicd-statistics/src/charts/stage-chart.tsx index 3e18bd17cd..f9b0312595 100644 --- a/plugins/cicd-statistics/src/charts/stage-chart.tsx +++ b/plugins/cicd-statistics/src/charts/stage-chart.tsx @@ -48,7 +48,7 @@ import { tickFormatterY, tooltipValueFormatter, formatDuration, -} from './utils'; +} from '../components/utils'; import { statusColorMap, fireColors, @@ -67,12 +67,18 @@ export interface StageChartProps { chartTypes: CicdDefaults['chartTypes']; defaultCollapsed?: number; + defaultHidden?: number; zeroYAxis?: boolean; } export function StageChart(props: StageChartProps) { const { stage, ...chartOptions } = props; - const { chartTypes, defaultCollapsed = 0, zeroYAxis = false } = chartOptions; + const { + chartTypes, + defaultCollapsed = 0, + defaultHidden = 0, + zeroYAxis = false, + } = chartOptions; const ticks = useMemo( () => pickElements(stage.values, 8).map(val => val.__epoch), @@ -97,7 +103,17 @@ export function StageChart(props: StageChartProps) { [statuses], ); - return ( + const subStages = useMemo( + () => + new Map( + [...stage.stages.entries()].filter( + ([_name, subStage]) => subStage.combinedAnalysis.max > defaultHidden, + ), + ), + [stage.stages, defaultHidden], + ); + + return stage.combinedAnalysis.max < defaultHidden ? null : ( defaultCollapsed} TransitionProps={transitionProps} @@ -209,18 +225,18 @@ export function StageChart(props: StageChartProps) { - {stage.stages.size === 0 ? null : ( + {subStages.size === 0 ? null : ( }> - Sub stages ({stage.stages.size}) + Sub stages ({subStages.size})
- {[...stage.stages.values()].map(subStage => ( + {[...subStages.values()].map(subStage => ( ( +export const useStyles = makeStyles( theme => ({ rootCard: { padding: theme.spacing(0, 0, 0, 0), @@ -81,7 +83,7 @@ const useStyles = makeStyles( }, }), { - name: 'CicdStatisticsChartFilters', + name: 'CicdStatistics', }, ); @@ -127,7 +129,11 @@ function isSameChartFilter(a: ChartFilter, b: ChartFilter): boolean { export type ViewOptions = Pick< CicdDefaults, - 'lowercaseNames' | 'normalizeTimeRange' | 'chartTypes' + | 'lowercaseNames' + | 'normalizeTimeRange' + | 'collapsedLimit' + | 'hideLimit' + | 'chartTypes' >; export function getDefaultViewOptions( @@ -136,6 +142,8 @@ export function getDefaultViewOptions( return { lowercaseNames: cicdConfiguration.defaults?.lowercaseNames ?? false, normalizeTimeRange: cicdConfiguration.defaults?.normalizeTimeRange ?? true, + collapsedLimit: 60 * 1000, // 1m + hideLimit: 20 * 1000, // 20s chartTypes: { succeeded: ['duration'], failed: ['count'], @@ -224,6 +232,20 @@ export function ChartFilters(props: ChartFiltersProps) { [setViewOptions], ); + const setHideLimit = useCallback( + (value: number) => { + setViewOptions(old => ({ ...old, hideLimit: value })); + }, + [setViewOptions], + ); + + const setCollapseLimit = useCallback( + (value: number) => { + setViewOptions(old => ({ ...old, collapsedLimit: value })); + }, + [setViewOptions], + ); + const setChartType = useCallback( (statusType: FilterStatusType, chartTypes: ChartTypes) => { setViewOptions(old => ({ @@ -351,7 +373,7 @@ export function ChartFilters(props: ChartFiltersProps) { onChange={toggleUseNowAsDate} /> } - label="To today" + label={} /> {useNowAsToDate ? null : ( - Lowercase names + - Normalize time range + + + ({ + value: index, + label: formatDurationFromSeconds(value), + seconds: value, +})); + +function findMarkIndex(seconds: number): number { + if (marks[0].seconds > seconds) { + return 0; + } else if (marks[marks.length - 1].seconds < seconds) { + return marks.length - 1; + } + for (let i = 0; i < marks.length - 1; ++i) { + const a = marks[i]; + const b = marks[i + 1]; + if (seconds === a.seconds) { + return i; + } else if (seconds === b.seconds) { + return i + 1; + } else if (a.seconds < seconds && b.seconds > seconds) { + return seconds - a.seconds < b.seconds - seconds ? i : i - 1; + } + } + return 0; // Won't happen +} + +function formatDurationFromIndex(index: number) { + return formatDurationFromSeconds(marks[index].seconds); +} + +export interface DurationSliderProps { + header: string; + value: number; + setValue: (value: number) => void; +} + +export function DurationSlider(props: DurationSliderProps) { + const { header, value, setValue } = props; + + const [curValue, setCurValue] = useState(value); + + const debouncedSetValue = useMemo(() => debounce(setValue, 1000), [setValue]); + + const onChange = useCallback( + (_: any, index: number | number[]) => { + const millis = marks[index as number].seconds * 1000; + setCurValue(millis); + debouncedSetValue(millis); + }, + [debouncedSetValue], + ); + + const indexValue = useMemo(() => findMarkIndex(curValue / 1000), [curValue]); + + return ( + <> + + + + ); +} diff --git a/plugins/cicd-statistics/src/components/label.tsx b/plugins/cicd-statistics/src/components/label.tsx new file mode 100644 index 0000000000..fe064d30a6 --- /dev/null +++ b/plugins/cicd-statistics/src/components/label.tsx @@ -0,0 +1,40 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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. + */ + +import React, { PropsWithChildren } from 'react'; +import { Typography, Theme, makeStyles } from '@material-ui/core'; + +export const useStyles = makeStyles( + theme => ({ + label: { + fontWeight: 'normal', + margin: theme.spacing(0), + }, + }), + { + name: 'CicdStatisticsLabel', + }, +); + +export function Label({ children }: PropsWithChildren<{}>) { + const classes = useStyles(); + + return ( + + {children} + + ); +} diff --git a/plugins/cicd-statistics/src/charts/utils.tsx b/plugins/cicd-statistics/src/components/utils.tsx similarity index 96% rename from plugins/cicd-statistics/src/charts/utils.tsx rename to plugins/cicd-statistics/src/components/utils.tsx index 6086e84ade..6eeb81df23 100644 --- a/plugins/cicd-statistics/src/charts/utils.tsx +++ b/plugins/cicd-statistics/src/components/utils.tsx @@ -117,3 +117,7 @@ export function formatDuration(millis: number) { return dur.toHuman({ unitDisplay: 'narrow' }).replace(/, /g, ''); } + +export function formatDurationFromSeconds(seconds: number) { + return formatDuration(seconds * 1000); +} diff --git a/plugins/cicd-statistics/src/entity-page.tsx b/plugins/cicd-statistics/src/entity-page.tsx index c007e355a9..87f5a9e8d4 100644 --- a/plugins/cicd-statistics/src/entity-page.tsx +++ b/plugins/cicd-statistics/src/entity-page.tsx @@ -152,8 +152,6 @@ function CicdCharts(props: CicdChartsProps) { errorApi.post(chartableStagesState.error); }, [errorApi, chartableStagesState.error]); - const collapsedLimit = cicdConfiguration.defaults.collapsedLimit ?? 60 * 1000; // 1m - return ( @@ -176,13 +174,15 @@ function CicdCharts(props: CicdChartsProps) { {[...chartableStages.stages.entries()].map(([name, stage]) => ( ))}