From 66f43e818031fa69e9ed8c4116e8c4775381858a Mon Sep 17 00:00:00 2001 From: Niklas Granander Date: Thu, 29 Jul 2021 18:51:16 +0200 Subject: [PATCH] Implement filtering on project name of builds Signed-off-by: Niklas Granander --- plugins/xcmetrics/src/api/XcmetricsClient.ts | 24 ++-- .../src/api/__mocks__/XcmetricsClient.ts | 13 ++- plugins/xcmetrics/src/api/types.ts | 20 ++-- .../BuildListComponent/BuildListComponent.tsx | 41 +++---- .../BuildListFilterComponent.tsx | 104 +++++++++++------- .../DatePickerComponent.tsx | 3 +- 6 files changed, 119 insertions(+), 86 deletions(-) diff --git a/plugins/xcmetrics/src/api/XcmetricsClient.ts b/plugins/xcmetrics/src/api/XcmetricsClient.ts index 19e4043e5e..81b3e78a31 100644 --- a/plugins/xcmetrics/src/api/XcmetricsClient.ts +++ b/plugins/xcmetrics/src/api/XcmetricsClient.ts @@ -20,7 +20,7 @@ import { DateTime } from 'luxon'; import { Build, BuildCount, - BuildStatus, + BuildFilters, BuildStatusResult, BuildTime, PaginationResult, @@ -61,9 +61,7 @@ export class XcmetricsClient implements XcmetricsApi { } async getFilteredBuilds( - from: string, - to: string, - status?: BuildStatus, + filters: BuildFilters, page?: number, perPage?: number, ): Promise> { @@ -72,14 +70,15 @@ export class XcmetricsClient implements XcmetricsApi { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ - from: DateTime.fromISO(from) + from: DateTime.fromISO(filters.from) .startOf('day') .toISO({ suppressMilliseconds: true }), - to: DateTime.fromISO(to) + to: DateTime.fromISO(filters.to) .endOf('day') .startOf('second') .toISO({ suppressMilliseconds: true }), - status, + status: filters.buildStatus, + projectName: filters.project, page, per: perPage, }), @@ -131,4 +130,15 @@ export class XcmetricsClient implements XcmetricsApi { return ((await response.json()) as PaginationResult) .items; } + + async getProjects(): Promise { + const baseUrl = `${await this.discoveryApi.getBaseUrl('proxy')}/xcmetrics`; + const response = await fetch(`${baseUrl}/build/project`); + + if (!response.ok) { + throw await ResponseError.fromResponse(response); + } + + return (await response.json()) as string[]; + } } diff --git a/plugins/xcmetrics/src/api/__mocks__/XcmetricsClient.ts b/plugins/xcmetrics/src/api/__mocks__/XcmetricsClient.ts index 869ffe96f0..cacb8fb2b1 100644 --- a/plugins/xcmetrics/src/api/__mocks__/XcmetricsClient.ts +++ b/plugins/xcmetrics/src/api/__mocks__/XcmetricsClient.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Build, BuildStatus, XcmetricsApi } from '../types'; +import { Build, BuildFilters, XcmetricsApi } from '../types'; export const mockBuild = { userid: 'userid1', @@ -62,11 +62,9 @@ export const XcmetricsClient: XcmetricsApi = { ]); }, getFilteredBuilds: ( - from: string, - to: string, - status?: BuildStatus, - page?: number, - perPage?: number, + _filters: BuildFilters, + _page?: number, + _perPage?: number, ) => { return Promise.resolve({ items: [mockBuild], @@ -86,4 +84,7 @@ export const XcmetricsClient: XcmetricsApi = { getBuildTimes: (days: number) => { return Promise.resolve([mockBuildTime, mockBuildTime].slice(0, days)); }, + getProjects: () => { + return Promise.resolve([mockBuild.projectName]); + }, }; diff --git a/plugins/xcmetrics/src/api/types.ts b/plugins/xcmetrics/src/api/types.ts index 640951da7e..c018a67056 100644 --- a/plugins/xcmetrics/src/api/types.ts +++ b/plugins/xcmetrics/src/api/types.ts @@ -68,27 +68,25 @@ export type PaginationResult = { }; }; +export type BuildFilters = { + from: string; // ISO Date (e.g. "2021-01-01") + to: string; // ISO Date (e.g. "2021-01-02") + buildStatus?: BuildStatus; + project?: string; +}; + export interface XcmetricsApi { getBuild(id: string): Promise; getBuilds(limit?: number): Promise; - - /** - * Get builds filtered by the provided parameters - * - * @param from Builds after this date. An ISO date in a string (e.g. "2020-01-01") - * @param to Builds before this date. An ISO date in a string (e.g. "2021-01-01") - * @param status Builds with this status - */ getFilteredBuilds( - from: string, - to: string, - status?: BuildStatus, + filters: BuildFilters, page?: number, perPage?: number, ): Promise>; getBuildCounts(days: number): Promise; getBuildTimes(days: number): Promise; getBuildStatuses(limit: number): Promise; + getProjects(): Promise; } export const xcmetricsApiRef = createApiRef({ diff --git a/plugins/xcmetrics/src/components/BuildListComponent/BuildListComponent.tsx b/plugins/xcmetrics/src/components/BuildListComponent/BuildListComponent.tsx index c54bd7d6cc..0c1337ea6e 100644 --- a/plugins/xcmetrics/src/components/BuildListComponent/BuildListComponent.tsx +++ b/plugins/xcmetrics/src/components/BuildListComponent/BuildListComponent.tsx @@ -13,16 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useRef, useState } from 'react'; import { Table, TableColumn } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; -import { Build, xcmetricsApiRef } from '../../api'; +import { Build, BuildFilters, xcmetricsApiRef } from '../../api'; import { formatDuration, formatTime } from '../../utils'; import { Chip, Grid } from '@material-ui/core'; -import { - ActiveFilters, - BuildListFilterComponent as Filters, -} from '../BuildListFilterComponent'; +import { BuildListFilterComponent as Filters } from '../BuildListFilterComponent'; import { DateTime } from 'luxon'; const columns: TableColumn[] = [ @@ -62,31 +59,39 @@ const columns: TableColumn[] = [ ]; export const BuildListComponent = () => { - const initDates = { + const client = useApi(xcmetricsApiRef); + const tableRef = useRef(); + + const initialFilters = { from: DateTime.now().minus({ year: 1 }).toISODate(), to: DateTime.now().toISODate(), }; - const client = useApi(xcmetricsApiRef); - const tableRef = useRef(); - const [filters, setFilters] = useState(initDates); - useEffect(() => tableRef.current?.onQueryChange(), [filters]); + const [filters, setFilters] = useState(initialFilters); + + const handleFilterChange = (values: BuildFilters) => { + setFilters(values); + tableRef.current?.onQueryChange(); + }; return ( - + { return new Promise((resolve, reject) => { if (!query) return; client .getFilteredBuilds( - filters.from, - filters.to, - filters.buildStatus, - query.page + 1, // Page starts at 1 in API + filters, + query.page + 1, // Page is 0-indexed in Table query.pageSize, ) .then(result => { @@ -99,8 +104,6 @@ export const BuildListComponent = () => { .catch(reason => reject(reason)); }); }} - columns={columns} - title="Builds" /> ); diff --git a/plugins/xcmetrics/src/components/BuildListFilterComponent/BuildListFilterComponent.tsx b/plugins/xcmetrics/src/components/BuildListFilterComponent/BuildListFilterComponent.tsx index 6fc2094a70..08cb82c8ca 100644 --- a/plugins/xcmetrics/src/components/BuildListFilterComponent/BuildListFilterComponent.tsx +++ b/plugins/xcmetrics/src/components/BuildListFilterComponent/BuildListFilterComponent.tsx @@ -14,24 +14,18 @@ * limitations under the License. */ -import { - makeStyles, - useTheme, - IconButton, - Grid, - Button, -} from '@material-ui/core'; -import FilterList from '@material-ui/icons/FilterList'; import React, { useEffect, useState } from 'react'; +import { makeStyles, IconButton, Grid, Button } from '@material-ui/core'; +import FilterList from '@material-ui/icons/FilterList'; import { InfoCard, Select } from '@backstage/core-components'; import { BackstageTheme } from '@backstage/theme'; -import { BuildStatus } from '../../api'; +import { useApi } from '@backstage/core-plugin-api'; +import { useAsync } from 'react-use'; +import { BuildFilters, BuildStatus, xcmetricsApiRef } from '../../api'; import { DatePickerComponent } from '../DatePickerComponent'; -export type ActiveFilters = { - from: string; - to: string; - buildStatus?: BuildStatus; +const toSelectItems = (strings: string[]) => { + return strings.map(str => ({ label: str, value: str })); }; const useStyles = makeStyles(theme => ({ @@ -43,37 +37,26 @@ const useStyles = makeStyles(theme => ({ type FilterOption = T | 'all'; interface FiltersProps { - initDates: { from: string; to: string }; - onFilterChange: (filters: ActiveFilters) => void; + initialValues: BuildFilters; + onFilterChange: (filters: BuildFilters) => void; } export const BuildListFilterComponent = ({ onFilterChange, - initDates, + initialValues, }: FiltersProps) => { - const classes = useStyles(useTheme()); - const [status, setStatus] = useState(); - const [from, setFrom] = useState(initDates.from); - const [to, setTo] = useState(initDates.to); + const client = useApi(xcmetricsApiRef); + const classes = useStyles(); const [open, setOpen] = useState(false); + const [values, setValues] = useState(initialValues); - useEffect(() => onFilterChange({ from, to, buildStatus: status }), [ - onFilterChange, - from, - to, - status, - ]); + useEffect(() => onFilterChange(values), [onFilterChange, values]); const numFilters = - Number(!!status) + - Number(from !== initDates.from) + - Number(to !== initDates.to); - - const clear = () => { - setStatus(undefined); - setFrom(initDates.from); - setTo(initDates.to); - }; + Number(values.from !== initialValues.from) + + Number(values.to !== initialValues.to) + + Number(!!values.buildStatus) + + Number(!!values.project); const title = ( <> @@ -82,7 +65,7 @@ export const BuildListFilterComponent = ({ Filters ({numFilters}) {!!numFilters && ( - )} @@ -96,6 +79,10 @@ export const BuildListFilterComponent = ({ { label: 'Stopped', value: 'stopped' }, ]; + const { value: projects, loading } = useAsync(async () => { + return client.getProjects(); + }, []); + const content = ( - + setValues({ ...values, from: date })} + /> - + setValues({ ...values, to: date })} + /> undefined} + /> + ) : ( +