diff --git a/front/assets/js/insights/components/custom_dashboards.tsx b/front/assets/js/insights/components/custom_dashboards.tsx index 1c2522d1e..14dcbdd0f 100644 --- a/front/assets/js/insights/components/custom_dashboards.tsx +++ b/front/assets/js/insights/components/custom_dashboards.tsx @@ -1,21 +1,21 @@ -import { useNavigate, useParams } from 'react-router-dom'; -import { useContext, useLayoutEffect, useState } from 'preact/hooks'; -import { Dashboard, DashboardItem } from '../types/dashboard'; -import { DashboardItemForm } from './forms/dashboard_item_form'; -import * as types from '../types'; -import * as util from '../util'; -import { useToggle } from '../util'; -import { CreateDashboardItem } from '../types/json_interface'; -import { Config } from '../app'; -import { State as DState } from '../stores/dashboards'; -import { State as DRState } from '../stores/metric_date_range'; +import { useNavigate, useParams } from "react-router-dom"; +import { useContext, useLayoutEffect, useState } from "preact/hooks"; +import { Dashboard, DashboardItem } from "../types/dashboard"; +import { DashboardItemForm } from "./forms/dashboard_item_form"; +import * as types from "../types"; +import * as util from "../util"; +import { useToggle } from "../util"; +import { CreateDashboardItem } from "../types/json_interface"; +import { Config } from "../app"; +import { State as DState } from "../stores/dashboards"; +import { State as DRState } from "../stores/metric_date_range"; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore -import { Notice } from '../../notice'; -import { DashboardItemCard } from './dashboard_item_card'; -import { InsightsType, typeByMetric } from '../types/insights_type'; -import { empty_custom_dashboard } from './zero_state/empty_custom_dashboard'; -import Tippy from '@tippyjs/react'; +import { Notice } from "../../notice"; +import { DashboardItemCard } from "./dashboard_item_card"; +import { InsightsType, typeByMetric } from "../types/insights_type"; +import { empty_custom_dashboard } from "./zero_state/empty_custom_dashboard"; +import Tippy from "@tippyjs/react"; import { handleMetricDatePickerChanged } from "../util/event_handlers"; import * as stores from "../stores"; @@ -26,12 +26,21 @@ interface Props { renameHandler: (id: string, name: string) => void; } -export const CustomDashboards = ({ state, dispatchDashboard, deleteHandler, renameHandler }: Props) => { - +export const CustomDashboards = ({ + state, + dispatchDashboard, + deleteHandler, + renameHandler, +}: Props) => { const { id } = useParams<`id`>(); - const dashboard = state.dashboards.find(d => d.id === id); + const dashboard = state.dashboards.find((d) => d.id === id); const navigate = useNavigate(); - const { dashboardsUrl, pipelineReliabilityUrl, pipelineFrequencyUrl, pipelinePerformanceUrl } = useContext(Config); + const { + dashboardsUrl, + pipelineReliabilityUrl, + pipelineFrequencyUrl, + pipelinePerformanceUrl, + } = useContext(Config); const dateRangeStore = useContext(stores.MetricDateRange.Context); const dateRangeState = dateRangeStore.state; @@ -41,6 +50,8 @@ export const CustomDashboards = ({ state, dispatchDashboard, deleteHandler, rena endpointUrls.frequency = pipelineFrequencyUrl; endpointUrls.dashboards = dashboardsUrl; + const [dashboardName, setDashboardName] = useState(dashboard.name); + const { show, toggle } = useToggle(); if (!dashboard) { @@ -51,11 +62,12 @@ export const CustomDashboards = ({ state, dispatchDashboard, deleteHandler, rena const scrollToForm = () => { toggle(); setTimeout(() => { - document.getElementById(`dashboard-item-form`)?.scrollIntoView({ behavior: `smooth` }); + document + .getElementById(`dashboard-item-form`) + ?.scrollIntoView({ behavior: `smooth` }); }, 100); }; - useLayoutEffect(() => { let items = dashboard.items; if (!items) { @@ -71,54 +83,59 @@ export const CustomDashboards = ({ state, dispatchDashboard, deleteHandler, rena }, [dashboard, dateRangeState.selectedMetricDateRangeLabel]); // --- rename dashboard item - const updateDashboardItem = async (url: string, dashboardId: string, id: string, name: string) => { + const updateDashboardItem = async ( + url: string, + dashboardId: string, + id: string, + name: string, + notes: string + ) => { try { const response = await fetch(`${url}/${dashboardId}/${id}`, { method: `PUT`, headers: util.Headers(), - body: `name=${name}` + body: `name=${name}&description=${notes}`, }); if (response.ok) { - dispatchDashboard({ type: `UPDATE_DASHBOARD_ITEM_NAME`, dashboardId: dashboard.id, itemId: id, name: name }); - } - } catch (e) { - Notice.error(`Failed to update Dashboard Item.`); - } - }; - - const updateDashboardItemHandler = (id: string, name: string) => { - updateDashboardItem(dashboardsUrl, dashboard.id, id, name).catch(() => { - return; - }); - }; - - // --- Update item description - const updateDashboardItemDescription = async (url: string, dashboardId: string, id: string, description: string) => { - try { - const response = await fetch(`${url}/${dashboardId}/${id}/description`, { - method: `PUT`, - headers: util.Headers(), - body: `description=${description}` - }); + dispatchDashboard({ + type: `UPDATE_DASHBOARD_ITEM_NAME`, + dashboardId: dashboard.id, + itemId: id, + name: name, + }); - if (response.ok) { - dispatchDashboard({ type: `UPDATE_DASHBOARD_ITEM_DESCRIPTION`, dashboardId: dashboard.id, itemId: id, description: description }); + dispatchDashboard({ + type: `UPDATE_DASHBOARD_ITEM_DESCRIPTION`, + dashboardId: dashboard.id, + itemId: id, + description: notes, + }); } } catch (e) { Notice.error(`Failed to update Dashboard Item.`); } }; - const updateDashboardItemDescriptionHandler = (id: string, description: string) => { - updateDashboardItemDescription(dashboardsUrl, dashboard.id, id, description).catch(() => { - return; - }); + const updateDashboardItemHandler = ( + id: string, + name: string, + notes: string + ) => { + updateDashboardItem(dashboardsUrl, dashboard.id, id, name, notes).catch( + () => { + return; + } + ); }; // --- delete dashboard item - const deleteDashboardItem = async (url: string, dashboardId: string, id: string) => { + const deleteDashboardItem = async ( + url: string, + dashboardId: string, + id: string + ) => { try { const response = await fetch(`${url}/${dashboardId}/${id}`, { method: `DELETE`, @@ -126,7 +143,11 @@ export const CustomDashboards = ({ state, dispatchDashboard, deleteHandler, rena }); if (response.ok) { - dispatchDashboard({ type: `DELETE_DASHBOARD_ITEM`, dashboardId: dashboard.id, itemId: id }); + dispatchDashboard({ + type: `DELETE_DASHBOARD_ITEM`, + dashboardId: dashboard.id, + itemId: id, + }); } } catch (e) { Notice.error(`Failed to delete Dashboard Item.`); @@ -140,24 +161,38 @@ export const CustomDashboards = ({ state, dispatchDashboard, deleteHandler, rena }; // --- create new dashboard item - const sendNewDashboardItem = async (url: string, dashboardId: string, item: object) => { + const sendNewDashboardItem = async ( + url: string, + dashboardId: string, + item: object + ) => { try { const response = await fetch(`${url}/${dashboardId}`, { method: `POST`, headers: util.Headers(`application/json`), - body: JSON.stringify(item) + body: JSON.stringify(item), }); const data: CreateDashboardItem = await response.json(); const dashboardItem = types.Dashboard.DashboardItem.fromJSON(data.item); //load metric - const routeItems = urlBuilder(endpointUrls, [dashboardItem], dateRangeState); + const routeItems = urlBuilder( + endpointUrls, + [dashboardItem], + dateRangeState + ); for (const [url, items] of routeItems) { - metricsFetcher(url, items, dashboard.id, dispatchDashboard).catch(() => { - return; - }); + metricsFetcher(url, items, dashboard.id, dispatchDashboard).catch( + () => { + return; + } + ); } - dispatchDashboard({ type: `ADD_DASHBOARD_ITEM`, id: dashboard.id, item: dashboardItem }); + dispatchDashboard({ + type: `ADD_DASHBOARD_ITEM`, + id: dashboard.id, + item: dashboardItem, + }); dashboard.items.push(dashboardItem); Notice.notice(`New Metric added to Dashboard.`); } catch (e) { @@ -169,17 +204,15 @@ export const CustomDashboards = ({ state, dispatchDashboard, deleteHandler, rena sendNewDashboardItem(dashboardsUrl, dashboard.id, item).catch(() => { return; }); - }; const [visible, setVisible] = useState(false); const showTippy = () => setVisible(true); const hideTippy = () => setVisible(false); - let dashboardName = dashboard.name; // -- event handlers const onInputNameChange = (e: any) => { - dashboardName = e.target.value; + setDashboardName(e.target.value as string); }; const onSubmit = (e: any) => { @@ -187,7 +220,6 @@ export const CustomDashboards = ({ state, dispatchDashboard, deleteHandler, rena renameHandler(dashboard.id, dashboardName); }; - // ----- render return (
@@ -208,58 +240,87 @@ export const CustomDashboards = ({ state, dispatchDashboard, deleteHandler, rena
Dashboard name
- +
- - + + onClick={hideTippy} + > + Cancel +
- + type="reset" + > + Delete +
- }> - + } + > +
- + {dateRangeState.dateRanges.map((d) => ( + + ))}
- - + ); }; @@ -268,12 +329,12 @@ const isEmpty = (d: Dashboard) => { return d.items === undefined || d.items.length === 0; }; - const fromJsonByInsightsType = (item: DashboardItem) => { switch (typeByMetric(item.settings.metric)) { case InsightsType.Performance: return (json: types.JSONInterface.PipelinePerformance) => { - const response = types.PipelinePerformance.DynamicMetrics.fromJSON(json); + const response = + types.PipelinePerformance.DynamicMetrics.fromJSON(json); return response.metrics; }; case InsightsType.Frequency: @@ -296,7 +357,11 @@ class EndpointUrls { reliability: string; } -const urlBuilder = (endpointUrls: EndpointUrls, items: DashboardItem[], state: DRState): Map => { +const urlBuilder = ( + endpointUrls: EndpointUrls, + items: DashboardItem[], + state: DRState +): Map => { const map = new Map(); const url = (type: InsightsType): string => { switch (type) { @@ -317,7 +382,9 @@ const urlBuilder = (endpointUrls: EndpointUrls, items: DashboardItem[], state: D const from = state.selectedMetricDateRange.from; const to = state.selectedMetricDateRange.to; const urlString = url(typeByMetric(item.settings.metric)) - .concat(`?custom_dashboards=true&branch=${item.branchName}&ppl_file_name=${item.pipelineFileName}`) + .concat( + `?custom_dashboards=true&branch=${item.branchName}&ppl_file_name=${item.pipelineFileName}` + ) .concat(`&from_date=${from}&to_date=${to}`); if (map.has(urlString)) { @@ -330,24 +397,33 @@ const urlBuilder = (endpointUrls: EndpointUrls, items: DashboardItem[], state: D return map; }; - -const metricsFetcher = async (url: string, items: DashboardItem[], dashboardId: string, dispatcher: any) => { +const metricsFetcher = async ( + url: string, + items: DashboardItem[], + dashboardId: string, + dispatcher: any +) => { const response = await fetch(url); const data: any = await response.json(); for (const item of items) { const fromJson = fromJsonByInsightsType(item); - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - dispatcher({ type: `ADD_ITEM_METRICS`, metrics: fromJson(data), itemId: item.id }); + dispatcher({ + type: `ADD_ITEM_METRICS`, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + metrics: fromJson(data), + itemId: item.id, + }); } }; - const isInvalid = (item: DashboardItem): boolean => { - return item.settings == null || - item.settings.metric == 0 || - item.branchName == null || - item.pipelineFileName == null; + return ( + item.settings == null || + item.settings.metric == 0 || + item.branchName == null || + item.pipelineFileName == null + ); }; function shouldHideEmptyPage(dashboard: Dashboard, show: boolean) { diff --git a/front/assets/js/insights/components/dashboard_item_card.tsx b/front/assets/js/insights/components/dashboard_item_card.tsx index 64348eb27..2f9a45386 100644 --- a/front/assets/js/insights/components/dashboard_item_card.tsx +++ b/front/assets/js/insights/components/dashboard_item_card.tsx @@ -1,67 +1,64 @@ -import { DashboardItem } from '../types/dashboard'; -import { InsightsType, typeByMetric } from '../types/insights_type'; -import * as customCharts from './custom_charts'; -import Tippy from '@tippyjs/react'; -import { useState } from 'preact/hooks'; +import { DashboardItem } from "../types/dashboard"; +import { InsightsType, typeByMetric } from "../types/insights_type"; +import * as customCharts from "./custom_charts"; +import Tippy from "@tippyjs/react"; +import { useState } from "preact/hooks"; import { metricFromNumber } from "../util/metric"; interface Props { item: DashboardItem; metrics: any; deleteHandler: (id: string) => void; - renameHandler: (id: string, name: string) => void; - updateDescriptionHandler: (id: string, description: string) => void; + updateHandler: (id: string, name: string, notes: string) => void; } -export const DashboardItemCard = ({ item, metrics, renameHandler, deleteHandler, updateDescriptionHandler }: Props) => { +export const DashboardItemCard = ({ + item, + metrics, + updateHandler, + deleteHandler, +}: Props) => { const insightType = typeByMetric(item.settings.metric); + const [name, setName] = useState(item.name); + const [notes, setNotes] = useState(item.notes); if (metrics == null) { metrics = []; } - let nameUpdated = false; - let descriptionUpdated = false; - - let itemName = item.name; - let description = item.notes; - const onInputNameChange = (e: any) => { - nameUpdated = true; - itemName = e.target.value; + setName(e.target.value as string); }; const onInputDescriptionChange = (e: any) => { - descriptionUpdated = true; - description = e.target.value; + setNotes(e.target.value as string); }; const onSubmit = (e: any) => { e.preventDefault(); - if (nameUpdated) { - renameHandler(item.id, itemName); - nameUpdated = false; - } - - if (descriptionUpdated) { - descriptionUpdated = false; - updateDescriptionHandler(item.id, description); - } + updateHandler(item.id, name, notes); }; const [visible, setVisible] = useState(false); const showTippy = () => setVisible(true); const hideTippy = () => setVisible(false); - const decideChart = (insightType: InsightsType, metrics: any) => { switch (insightType) { case InsightsType.Performance: return customCharts.Performance({ metrics, item }); case InsightsType.Frequency: - return customCharts.Frequency({ metrics, branchName: item.branchName, pipelineFileName: item.pipelineFileName }); + return customCharts.Frequency({ + metrics, + branchName: item.branchName, + pipelineFileName: item.pipelineFileName, + }); case InsightsType.Reliability: - return customCharts.Reliability({ metrics, branchName: item.branchName, pipelineFileName: item.pipelineFileName }); + return customCharts.Reliability({ + metrics, + branchName: item.branchName, + pipelineFileName: item.pipelineFileName, + }); } }; @@ -69,7 +66,9 @@ export const DashboardItemCard = ({ item, metrics, renameHandler, deleteHandler,
-

{item.name} — {metricFromNumber(item.settings.metric)}

+

+ {item.name} — {metricFromNumber(item.settings.metric)} +

Metric name
- +
Description
-