From 67af031f49c0b187f264426cd05fb67fab777003 Mon Sep 17 00:00:00 2001 From: Davide Valleri <146823822+dvalleri@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:27:27 +0100 Subject: [PATCH] feat(ui): add percentage in model list (#173) * feat(ui): add percentage in model list * connect api * fix percentages * fix alert launchPad layout * replace toFixed with numberFormatter * fixies after api review * fix empty layout of launchPad --- ui/package.json | 2 +- ui/src/components/charts/line-chart/index.jsx | 1 + ui/src/components/charts/pie-chart/index.jsx | 67 ++++++-- ui/src/components/charts/pie-chart/options.js | 31 ++-- .../current-import-detail-modal/body.jsx | 12 +- .../launchpad/central-row/columns.jsx | 32 ++-- .../container/launchpad/central-row/index.jsx | 31 ++-- ui/src/container/launchpad/index.jsx | 12 +- .../launchpad/right-column/alerts.jsx | 147 ++++++++++++++++++ .../launchpad/right-column/index.jsx | 35 +++++ .../work-in-progress/columns.jsx | 1 - .../work-in-progress/index.jsx | 8 +- ui/src/container/launchpad/right/alerts.jsx | 73 --------- ui/src/container/launchpad/right/index.jsx | 14 -- .../binary-classification/current/index.jsx | 4 +- .../binary-classification/reference/index.jsx | 4 +- ui/src/container/models/Details/constants.js | 2 +- .../multi-classification/current/index.jsx | 4 +- .../multi-classification/reference/index.jsx | 4 +- .../Details/regression/current/index.jsx | 4 +- .../Details/regression/reference/index.jsx | 4 +- ui/src/container/models/List/columns.jsx | 57 ++++++- ui/src/container/models/List/index.jsx | 10 +- ui/src/store/apis.js | 2 + ui/src/store/state/alerts/api.js | 2 +- ui/src/store/state/models/api.js | 40 +++-- ui/src/store/state/models/polling-hook.js | 19 ++- ui/yarn.lock | 8 +- 28 files changed, 437 insertions(+), 193 deletions(-) create mode 100644 ui/src/container/launchpad/right-column/alerts.jsx create mode 100644 ui/src/container/launchpad/right-column/index.jsx rename ui/src/container/launchpad/{right => right-column}/work-in-progress/columns.jsx (96%) rename ui/src/container/launchpad/{right => right-column}/work-in-progress/index.jsx (94%) delete mode 100644 ui/src/container/launchpad/right/alerts.jsx delete mode 100644 ui/src/container/launchpad/right/index.jsx diff --git a/ui/package.json b/ui/package.json index 3cc225c2..5d9d92fc 100644 --- a/ui/package.json +++ b/ui/package.json @@ -17,7 +17,7 @@ "@fortawesome/free-solid-svg-icons": "^6.5.2", "@grafana/faro-react": "^1.8.2", "@radicalbit/formbit": "1.0.0", - "@radicalbit/radicalbit-design-system": "1.1.0", + "@radicalbit/radicalbit-design-system": "^1.2.0", "@reduxjs/toolkit": "^2.2.4", "@vitejs/plugin-react": "^4.2.1", "ace-builds": "^1.33.2", diff --git a/ui/src/components/charts/line-chart/index.jsx b/ui/src/components/charts/line-chart/index.jsx index bd14a723..a6d8fb41 100644 --- a/ui/src/components/charts/line-chart/index.jsx +++ b/ui/src/components/charts/line-chart/index.jsx @@ -27,6 +27,7 @@ function LineChart({ title, color, currentData, referenceData = [], }) { const handleOnChartReady = (echart) => { + console.debug('🚀 ~ handleOnChartReady ~ echart:', echart); // To handle the second opening of a modal when the rtkq hook read from cache // and the echart graph will render immediately. setTimeout(echart.resize); diff --git a/ui/src/components/charts/pie-chart/index.jsx b/ui/src/components/charts/pie-chart/index.jsx index f5e690dc..e41ded4e 100644 --- a/ui/src/components/charts/pie-chart/index.jsx +++ b/ui/src/components/charts/pie-chart/index.jsx @@ -9,6 +9,7 @@ import { import { PieChart as PieChartEchart } from 'echarts/charts'; import * as echarts from 'echarts/lib/echarts'; +import { numberFormatter } from '@Src/constants'; import pieChartOptions from './options'; echarts.use([ @@ -21,31 +22,69 @@ echarts.use([ ]); function PieChart({ title, data }) { + const splittedTitle = title.split(' '); + + if (data <= 0) { + return ( +
+ + +
+ ); + } + + return ( +
+ + +
+ ); +} + +function EmptyPieChart() { const handleOnChartReady = (echart) => { // To handle the second opening of a modal when the rtkq hook read from cache // and the echart graph will render immediately. setTimeout(echart.resize); }; - const splittedTitle = title.split(' '); + return ( + + ); +} - const currentData = data?.current; - const referenceData = data?.reference; +function EvaluatedPieChart({ data }) { + const handleOnChartReady = (echart) => { + // To handle the second opening of a modal when the rtkq hook read from cache + // and the echart graph will render immediately. + setTimeout(echart.resize); + }; + const currentData = numberFormatter({ maximumSignificantDigits: 4 }).format(data * 100); + const referenceData = numberFormatter({ maximumSignificantDigits: 4 }).format((1 - data) * 100); return ( -
- + + ); +} -
-

{splittedTitle[0]}

+function Label({ splittedTitle }) { + return ( +
+

{splittedTitle[0]}

-

{splittedTitle[1]}

-
+

{splittedTitle[1]}

); } diff --git a/ui/src/components/charts/pie-chart/options.js b/ui/src/components/charts/pie-chart/options.js index 0c42f27a..fc0b14b8 100644 --- a/ui/src/components/charts/pie-chart/options.js +++ b/ui/src/components/charts/pie-chart/options.js @@ -2,27 +2,36 @@ import { CHART_COLOR } from '@Helpers/common-chart-options'; export default function pieChartOptions({ currentData, referenceData }) { const options = { - series: [ { percentPrecision: 2, type: 'pie', radius: ['50%', '80%'], - label: { - show: true, - fontSize: '14', - fontWeight: 'bold', - color: CHART_COLOR.WHITE, - position: 'center', - formatter: ({ data: { value } }) => `${value}%`, - }, labelLine: { show: false, }, emphasis: { disabled: true }, data: [ - { value: currentData, name: 'Current', itemStyle: { color: CHART_COLOR.CURRENT } }, - { value: referenceData, name: 'Reference', itemStyle: { color: '#f60000' } }, + { + value: currentData, + name: 'Current', + itemStyle: { color: CHART_COLOR.CURRENT }, + selected: true, + label: { + show: true, + fontSize: '14', + fontWeight: 'bold', + color: CHART_COLOR.WHITE, + position: 'center', + formatter: ({ data: { value } }) => value === 0 ? '--' : `${value}%`, + }, + }, + { + value: referenceData, + name: 'Reference', + itemStyle: { color: referenceData === 100 ? '#1e2b43' : '#f60000' }, + label: { show: false }, + }, ], }, diff --git a/ui/src/components/modals/current-import-detail-modal/body.jsx b/ui/src/components/modals/current-import-detail-modal/body.jsx index 7f326191..56eeb8b7 100644 --- a/ui/src/components/modals/current-import-detail-modal/body.jsx +++ b/ui/src/components/modals/current-import-detail-modal/body.jsx @@ -55,8 +55,8 @@ function useGetTabs() { case ModelTypeEnum.BINARY_CLASSIFICATION: return [ { - label: METRICS_TABS.DATA_QUALITIY, - key: METRICS_TABS.DATA_QUALITIY, + label: METRICS_TABS.DATA_QUALITY, + key: METRICS_TABS.DATA_QUALITY, children: , }, { @@ -74,8 +74,8 @@ function useGetTabs() { case ModelTypeEnum.MULTI_CLASSIFICATION: return [ { - label: METRICS_TABS.DATA_QUALITIY, - key: METRICS_TABS.DATA_QUALITIY, + label: METRICS_TABS.DATA_QUALITY, + key: METRICS_TABS.DATA_QUALITY, children: , }, { @@ -93,8 +93,8 @@ function useGetTabs() { case ModelTypeEnum.REGRESSION: return [ { - label: METRICS_TABS.DATA_QUALITIY, - key: METRICS_TABS.DATA_QUALITIY, + label: METRICS_TABS.DATA_QUALITY, + key: METRICS_TABS.DATA_QUALITY, children: , }, { diff --git a/ui/src/container/launchpad/central-row/columns.jsx b/ui/src/container/launchpad/central-row/columns.jsx index a5deb651..2b0cd658 100644 --- a/ui/src/container/launchpad/central-row/columns.jsx +++ b/ui/src/container/launchpad/central-row/columns.jsx @@ -1,6 +1,6 @@ import JobStatusPin from '@Components/JobStatus/job-status-pin'; import { columnFactory } from '@Src/components/smart-table/utils'; -import { JOB_STATUS } from '@Src/constants'; +import { JOB_STATUS, numberFormatter } from '@Src/constants'; import { ModelTypeEnumLabel } from '@Src/store/state/models/constants'; import { RelativeDateTime, Truncate } from '@radicalbit/radicalbit-design-system'; @@ -39,8 +39,8 @@ export const getColumns = ( activeFilters, activeSorter, align: 'left', - width: '23%', - render: (type) =>
{ModelTypeEnumLabel[type]}
, + width: '21%', + render: (type) =>
{ModelTypeEnumLabel[type]}
, }), columnFactory({ @@ -50,10 +50,12 @@ export const getColumns = ( activeSorter, align: 'left', width: '10%', - render: ({ dataQuality: { current } }) => { - const data = (current) ? `${current}%` : '--'; + render: ({ percentages }) => { + const value = percentages?.dataQuality.value; + const data = (value && value !== -1) ? `${numberFormatter({ maximumSignificantDigits: 4 }).format(parseFloat(value) * 100)}%` : '--'; + return ( -
+
{data}
); @@ -67,10 +69,12 @@ export const getColumns = ( activeSorter, align: 'left', width: '10%', - render: ({ modelQuality: { current } }) => { - const data = (current) ? `${current}%` : '--'; + render: ({ percentages }) => { + const value = percentages?.modelQuality.value; + const data = (value && value !== -1) ? `${numberFormatter({ maximumSignificantDigits: 4 }).format(parseFloat(value) * 100)}%` : '--'; + return ( -
+
{data}
); @@ -84,10 +88,12 @@ export const getColumns = ( activeSorter, align: 'left', width: '10%', - render: ({ dataDrift: { current } }) => { - const data = (current) ? `${current}%` : '--'; + render: ({ percentages }) => { + const value = percentages?.drift.value; + const data = (value && value !== -1) ? `${numberFormatter({ maximumSignificantDigits: 4 }).format(parseFloat(value) * 100)}%` : '--'; + return ( -
+
{data}
); @@ -102,7 +108,7 @@ export const getColumns = ( activeSorter, sorter: true, align: 'right', - width: '10%', + width: '13%', render: (date) => date && , }), ]; diff --git a/ui/src/container/launchpad/central-row/index.jsx b/ui/src/container/launchpad/central-row/index.jsx index 0b0e544e..e8f27591 100644 --- a/ui/src/container/launchpad/central-row/index.jsx +++ b/ui/src/container/launchpad/central-row/index.jsx @@ -1,9 +1,10 @@ import PieChart from '@Components/charts/pie-chart'; import SmartTable from '@Components/smart-table'; import useModals from '@Hooks/use-modals'; -import { Button, Spinner } from '@radicalbit/radicalbit-design-system'; +import { Button, Spinner, Void } from '@radicalbit/radicalbit-design-system'; import { ModalsEnum, NamespaceEnum } from '@Src/constants'; import { modelsApiSlice } from '@Src/store/state/models/api'; +import { useGetOverallModelListQueryWithPolling } from '@Src/store/state/models/polling-hook'; import { memo } from 'react'; import { useNavigate } from 'react-router'; import { useSearchParams } from 'react-router-dom'; @@ -13,6 +14,8 @@ const { useGetOverallStatsQuery } = modelsApiSlice; function ModelStatsList() { const { isLoading } = useGetOverallStatsQuery(); + const { data } = useGetOverallModelListQueryWithPolling(); + const count = data?.length; if (isLoading) { ; @@ -23,7 +26,7 @@ function ModelStatsList() {
- + {count > 0 && }
@@ -34,10 +37,9 @@ function ModelStatsList() { function OverallCharts() { const { data } = useGetOverallStatsQuery(); - - const dataQualityStats = data?.overallStats.dataQuality; - const modelQualityStats = data?.overallStats.modelQuality; - const dataDriftStats = data?.overallStats.dataDrift; + const dataQualityStats = data?.dataQuality || 0; + const modelQualityStats = data?.modelQuality || 0; + const dataDriftStats = data?.drift || 0; return (
@@ -55,20 +57,27 @@ function OverallList() { const { search } = useSearchParams(); const navigate = useNavigate(); - const { data } = useGetOverallStatsQuery(); - - const modelStats = data?.modelStats.items; - const count = data?.modelStats.count; + const { data } = useGetOverallModelListQueryWithPolling(); + const count = data?.length; const handleOnClick = ({ uuid }) => { navigate({ pathname: `/models/${uuid}`, search }); }; + if (count === 0) { + return ( + } + description="No models are available." + /> + ); + } + return ( ({ diff --git a/ui/src/container/launchpad/index.jsx b/ui/src/container/launchpad/index.jsx index e3bf6eb3..de7fcef2 100644 --- a/ui/src/container/launchpad/index.jsx +++ b/ui/src/container/launchpad/index.jsx @@ -1,10 +1,10 @@ -import Right from './right'; -import TopRow from './top-row'; import ModelStatsList from './central-row'; +import RightColumn from './right-column'; +import TopRow from './top-row'; export default function Launchpad() { return ( -
+
@@ -17,9 +17,3 @@ export default function Launchpad() {
); } - -function RightColumn() { - return ( - - ); -} diff --git a/ui/src/container/launchpad/right-column/alerts.jsx b/ui/src/container/launchpad/right-column/alerts.jsx new file mode 100644 index 00000000..4731d036 --- /dev/null +++ b/ui/src/container/launchpad/right-column/alerts.jsx @@ -0,0 +1,147 @@ +import SomethingWentWrong from '@Components/ErrorPage/something-went-wrong'; +import { columnFactory } from '@Components/smart-table/utils'; +import { METRICS_TABS, MODEL_TABS_ENUM } from '@Container/models/Details/constants'; +import { + Board, DataTable, SectionTitle, Skeleton, +} from '@radicalbit/radicalbit-design-system'; +import { alertsApiSlice } from '@State/alerts/api'; +import { useNavigate } from 'react-router'; + +const { useGetAlertsQuery } = alertsApiSlice; + +function Alerts() { + const { data = [], isLoading, isError } = useGetAlertsQuery(); + + if (isLoading) { + return ( + } + main={( +
+ + + + + +
+ )} + /> + ); + } + + if (isError) { + return ( + } + main={} + size="small" + /> + ); + } + + if (data.length === 0) { + return (false); + } + + return ( + } + main={( + + + )} + modifier="h-full" + size="small" + /> + ); +} + +function Main({ + alert: { + anomalyType, anomalyFeatures, modelUuid, modelName, + }, +}) { + const navigate = useNavigate(); + + const modelNametest = modelName ?? 'MODEL_NAME'; + const anomalyFeaturesJoined = anomalyFeatures.join(', '); + const anomalyTypeLabel = METRICS_TABS[`${anomalyType}`] ?? 'SECTION'; + + const handleOnClick = () => { + navigate(`/models/${modelUuid}?tab=${MODEL_TABS_ENUM.CURRENT_DASHBOARD}&tab-metrics=${anomalyTypeLabel}`); + }; + + if (anomalyFeaturesJoined.length > 0) { + return ( + + {anomalyTypeLabel} + : + + {' '} + + {modelNametest} + + {' '} + + model reports a problem on + + {' '} + + {anomalyFeaturesJoined} + + {' '} + + features. + + )} + modifier="min-h-fit" + onClick={handleOnClick} + size="small" + type="error" + /> + ); + } + + return ( + + + {anomalyTypeLabel} + + : + + {' '} + + {modelNametest} + + {' '} + reports a problem + + )} + modifier="min-h-fit" + onClick={handleOnClick} + size="small" + type="error" + /> + ); +} + +const columns = [ + columnFactory({ + key: 'name', + render: (data) =>
, + }), + +]; + +export default Alerts; diff --git a/ui/src/container/launchpad/right-column/index.jsx b/ui/src/container/launchpad/right-column/index.jsx new file mode 100644 index 00000000..53c66688 --- /dev/null +++ b/ui/src/container/launchpad/right-column/index.jsx @@ -0,0 +1,35 @@ +import { alertsApiSlice } from '@Src/store/state/alerts/api'; +import Alerts from './alerts'; +import WorkInProgress from './work-in-progress'; + +const { useGetAlertsQuery } = alertsApiSlice; + +function RightColumn() { + const { data = [] } = useGetAlertsQuery(); + const count = data?.length; + + if (count === 0) { + return ( +
+ +
+ ); + } + + return ( +
+
+ + +
+ +
+ + +
+ +
+ ); +} + +export default RightColumn; diff --git a/ui/src/container/launchpad/right/work-in-progress/columns.jsx b/ui/src/container/launchpad/right-column/work-in-progress/columns.jsx similarity index 96% rename from ui/src/container/launchpad/right/work-in-progress/columns.jsx rename to ui/src/container/launchpad/right-column/work-in-progress/columns.jsx index 829e1b3c..ec7b0bca 100644 --- a/ui/src/container/launchpad/right/work-in-progress/columns.jsx +++ b/ui/src/container/launchpad/right-column/work-in-progress/columns.jsx @@ -13,7 +13,6 @@ const columns = [ ? latestCurrentJobStatus : latestReferenceJobStatus; - console.debug(jobStatus); return ( ); diff --git a/ui/src/container/launchpad/right/work-in-progress/index.jsx b/ui/src/container/launchpad/right-column/work-in-progress/index.jsx similarity index 94% rename from ui/src/container/launchpad/right/work-in-progress/index.jsx rename to ui/src/container/launchpad/right-column/work-in-progress/index.jsx index c537bc73..ce95aca5 100644 --- a/ui/src/container/launchpad/right/work-in-progress/index.jsx +++ b/ui/src/container/launchpad/right-column/work-in-progress/index.jsx @@ -29,6 +29,7 @@ function WorkInProgress() {
)} + modifier="h-full" /> ); } @@ -38,6 +39,7 @@ function WorkInProgress() { } main={} + modifier="h-full" size="small" /> ); @@ -47,8 +49,8 @@ function WorkInProgress() { return ( } - height="250px" main={()} + modifier="h-full" size="small" /> ); @@ -57,7 +59,6 @@ function WorkInProgress() { return ( } - height="300px" main={( uuid} - scroll={{ y: '16rem' }} + scroll={{ y: '8rem' }} size="small" /> )} + modifier="h-full" size="small" /> ); diff --git a/ui/src/container/launchpad/right/alerts.jsx b/ui/src/container/launchpad/right/alerts.jsx deleted file mode 100644 index 5a2897a2..00000000 --- a/ui/src/container/launchpad/right/alerts.jsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Board, SectionTitle, Skeleton } from '@radicalbit/radicalbit-design-system'; -import SomethingWentWrong from '@Components/ErrorPage/something-went-wrong'; -import { alertsApiSlice } from '@State/alerts/api'; - -const { useGetAlertsQuery } = alertsApiSlice; - -function Alerts() { - const { data = [undefined, undefined], isLoading, isError } = useGetAlertsQuery(); - console.debug(data); - - if (isLoading) { - return ( - } - main={( -
- - - - - -
- )} - /> - ); - } - - if (isError) { - return ( - } - main={} - size="small" - /> - ); - } - - return ( - } - main={( -
- {data.map((alert) => ( -
- ))} -
- )} - size="small" - /> - ); -} - -function Main({ alert }) { - const alertType = alert?.alertType || 'Foo'; - const fieldsInError = alert?.fieldsInError || 'Bar'; - - return ( - - {alertType} - - {fieldsInError} -
- )} - size="small" - type="error" - /> - ); -} - -export default Alerts; diff --git a/ui/src/container/launchpad/right/index.jsx b/ui/src/container/launchpad/right/index.jsx deleted file mode 100644 index a72cbe34..00000000 --- a/ui/src/container/launchpad/right/index.jsx +++ /dev/null @@ -1,14 +0,0 @@ -import Alerts from './alerts'; -import WorkInProgress from './work-in-progress'; - -function Right() { - return ( -
- - - -
- ); -} - -export default Right; diff --git a/ui/src/container/models/Details/binary-classification/current/index.jsx b/ui/src/container/models/Details/binary-classification/current/index.jsx index 07c64ab8..3c6d6c8e 100644 --- a/ui/src/container/models/Details/binary-classification/current/index.jsx +++ b/ui/src/container/models/Details/binary-classification/current/index.jsx @@ -11,8 +11,8 @@ import BinaryClassificationDataDriftMetrics from './data-drift'; const tabs = [ { - label: METRICS_TABS.DATA_QUALITIY, - key: METRICS_TABS.DATA_QUALITIY, + label: METRICS_TABS.DATA_QUALITY, + key: METRICS_TABS.DATA_QUALITY, children: , }, { diff --git a/ui/src/container/models/Details/binary-classification/reference/index.jsx b/ui/src/container/models/Details/binary-classification/reference/index.jsx index d9c74308..dcc7435e 100644 --- a/ui/src/container/models/Details/binary-classification/reference/index.jsx +++ b/ui/src/container/models/Details/binary-classification/reference/index.jsx @@ -10,8 +10,8 @@ import ModelQualityMetrics from './model-quality'; const tabs = [ { - label: METRICS_TABS.DATA_QUALITIY, - key: METRICS_TABS.DATA_QUALITIY, + label: METRICS_TABS.DATA_QUALITY, + key: METRICS_TABS.DATA_QUALITY, children: , }, { diff --git a/ui/src/container/models/Details/constants.js b/ui/src/container/models/Details/constants.js index 8f31e7c3..223d5e40 100644 --- a/ui/src/container/models/Details/constants.js +++ b/ui/src/container/models/Details/constants.js @@ -5,7 +5,7 @@ export const MODEL_TABS_ENUM = { }; export const METRICS_TABS = { - DATA_QUALITIY: 'Data Quality', + DATA_QUALITY: 'Data Quality', MODEL_QUALITY: 'Model Quality', DATA_DRIFT: 'Data Drift', IMPORT: 'Import', diff --git a/ui/src/container/models/Details/multi-classification/current/index.jsx b/ui/src/container/models/Details/multi-classification/current/index.jsx index 8af3a11a..f2a3e9fd 100644 --- a/ui/src/container/models/Details/multi-classification/current/index.jsx +++ b/ui/src/container/models/Details/multi-classification/current/index.jsx @@ -11,8 +11,8 @@ import MultiClassificationDataDriftMetrics from './data-drift'; const tabs = [ { - label: METRICS_TABS.DATA_QUALITIY, - key: METRICS_TABS.DATA_QUALITIY, + label: METRICS_TABS.DATA_QUALITY, + key: METRICS_TABS.DATA_QUALITY, children: , }, { diff --git a/ui/src/container/models/Details/multi-classification/reference/index.jsx b/ui/src/container/models/Details/multi-classification/reference/index.jsx index d9c74308..dcc7435e 100644 --- a/ui/src/container/models/Details/multi-classification/reference/index.jsx +++ b/ui/src/container/models/Details/multi-classification/reference/index.jsx @@ -10,8 +10,8 @@ import ModelQualityMetrics from './model-quality'; const tabs = [ { - label: METRICS_TABS.DATA_QUALITIY, - key: METRICS_TABS.DATA_QUALITIY, + label: METRICS_TABS.DATA_QUALITY, + key: METRICS_TABS.DATA_QUALITY, children: , }, { diff --git a/ui/src/container/models/Details/regression/current/index.jsx b/ui/src/container/models/Details/regression/current/index.jsx index 66029b5d..7df5e049 100644 --- a/ui/src/container/models/Details/regression/current/index.jsx +++ b/ui/src/container/models/Details/regression/current/index.jsx @@ -11,8 +11,8 @@ import RegressionDataDriftMetrics from './data-drift'; const tabs = [ { - label: METRICS_TABS.DATA_QUALITIY, - key: METRICS_TABS.DATA_QUALITIY, + label: METRICS_TABS.DATA_QUALITY, + key: METRICS_TABS.DATA_QUALITY, children: , }, { diff --git a/ui/src/container/models/Details/regression/reference/index.jsx b/ui/src/container/models/Details/regression/reference/index.jsx index d9c74308..dcc7435e 100644 --- a/ui/src/container/models/Details/regression/reference/index.jsx +++ b/ui/src/container/models/Details/regression/reference/index.jsx @@ -10,8 +10,8 @@ import ModelQualityMetrics from './model-quality'; const tabs = [ { - label: METRICS_TABS.DATA_QUALITIY, - key: METRICS_TABS.DATA_QUALITIY, + label: METRICS_TABS.DATA_QUALITY, + key: METRICS_TABS.DATA_QUALITY, children: , }, { diff --git a/ui/src/container/models/List/columns.jsx b/ui/src/container/models/List/columns.jsx index 9841423a..dd84aaeb 100644 --- a/ui/src/container/models/List/columns.jsx +++ b/ui/src/container/models/List/columns.jsx @@ -1,6 +1,6 @@ import JobStatusPin from '@Components/JobStatus/job-status-pin'; import { columnFactory } from '@Src/components/smart-table/utils'; -import { JOB_STATUS } from '@Src/constants'; +import { JOB_STATUS, numberFormatter } from '@Src/constants'; import { DataTypeEnumLabel, ModelTypeEnumLabel } from '@State/models/constants'; import { RelativeDateTime } from '@radicalbit/radicalbit-design-system'; @@ -37,7 +37,7 @@ export const getColumns = ( activeSorter, align: 'left', width: 200, - render: (type) =>
{ModelTypeEnumLabel[type]}
, + render: (type) =>
{ModelTypeEnumLabel[type]}
, }), columnFactory({ @@ -48,7 +48,58 @@ export const getColumns = ( activeSorter, align: 'left', width: 200, - render: (type) =>
{DataTypeEnumLabel[type]}
, + render: (type) =>
{DataTypeEnumLabel[type]}
, + }), + + columnFactory({ + title: 'Data Quality', + key: 'dataQuality', + activeFilters, + activeSorter, + align: 'left', + width: '10%', + render: ({ percentages }) => { + const value = percentages?.dataQuality.value; + const data = (value && value !== -1) ? `${numberFormatter({ maximumSignificantDigits: 4 }).format(parseFloat(value).toFixed(4) * 100)}%` : '--'; + + return ( +
{data}
+ ); + }, + }), + + columnFactory({ + title: 'Model Quality', + key: 'modelQuality', + activeFilters, + activeSorter, + align: 'left', + width: '10%', + render: ({ percentages }) => { + const value = percentages?.modelQuality.value; + const data = (value && value !== -1) ? `${numberFormatter({ maximumSignificantDigits: 4 }).format(parseFloat(value).toFixed(4) * 100)}%` : '--'; + + return ( +
{data}
+ ); + }, + }), + + columnFactory({ + title: 'Drift Quality', + key: 'driftQuality', + activeFilters, + activeSorter, + align: 'left', + width: '10%', + render: ({ percentages }) => { + const value = percentages?.drift.value; + const data = (value && value !== -1) ? `${numberFormatter({ maximumSignificantDigits: 4 }).format(parseFloat(value).toFixed(4) * 100)}%` : '--'; + + return ( +
{data}
+ ); + }, }), columnFactory({ diff --git a/ui/src/container/models/List/index.jsx b/ui/src/container/models/List/index.jsx index e4056aa8..30a02be7 100644 --- a/ui/src/container/models/List/index.jsx +++ b/ui/src/container/models/List/index.jsx @@ -17,6 +17,14 @@ export function ModelsList() { const models = data?.items || []; const count = data?.total || 0; + // TODO we use the following object only for mock purpose. Must be removed when BE are ready + const modelsWithMock = models.map(((m) => ({ + ...m, + dataQuality: { current: null, reference: null }, + modelQuality: { current: null, reference: null }, + dataDrift: { current: null, reference: null }, + }))); + const modifier = models?.length ? '' : 'c-spinner--centered'; if (isError) { @@ -43,7 +51,7 @@ export function ModelsList() { ({ onClick: () => navigate({ diff --git a/ui/src/store/apis.js b/ui/src/store/apis.js index 303581b3..49185c42 100644 --- a/ui/src/store/apis.js +++ b/ui/src/store/apis.js @@ -7,6 +7,8 @@ export const API_TAGS = { MODEL: 'Model', REFERENCE_IMPORT: 'REFERENCE_IMPORT', CURRENT_IMPORT: 'CURRENT_IMPORT', + OVERALL_MODELS: 'OVERALL_MODELS', + OVERALL_STATS: 'OVERALL_STATS', }; export const apiService = createApi({ diff --git a/ui/src/store/state/alerts/api.js b/ui/src/store/state/alerts/api.js index 5f5d8c23..072e167d 100644 --- a/ui/src/store/state/alerts/api.js +++ b/ui/src/store/state/alerts/api.js @@ -6,7 +6,7 @@ export const alertsApiSlice = apiService.injectEndpoints({ providesTags: () => [{ type: API_TAGS.ALERTS }], query: () => ({ baseUrl: import.meta.env.VITE_BASE_URL, - url: '/alerts', + url: '/models/last_n_alerts?n_alerts=5', method: 'get', }), }), diff --git a/ui/src/store/state/models/api.js b/ui/src/store/state/models/api.js index 986343d3..0abcec1a 100644 --- a/ui/src/store/state/models/api.js +++ b/ui/src/store/state/models/api.js @@ -1,5 +1,5 @@ +import { JOB_STATUS } from '@Src/constants'; import { API_TAGS, apiService } from '@Src/store/apis'; -import overallStats from './mock/overall_stats.json'; export const modelsApiSlice = apiService.injectEndpoints({ endpoints: (builder) => ({ @@ -56,7 +56,7 @@ export const modelsApiSlice = apiService.injectEndpoints({ }), deleteModel: builder.mutation({ - invalidatesTags: [API_TAGS.MODELS], + invalidatesTags: [API_TAGS.MODELS, API_TAGS.OVERALL_STATS, API_TAGS.ALERTS, API_TAGS.OVERALL_MODELS], query: ({ uuid }) => ({ baseUrl: import.meta.env.VITE_BASE_URL, url: `/models/${uuid}`, @@ -193,6 +193,7 @@ export const modelsApiSlice = apiService.injectEndpoints({ { type: API_TAGS.CURRENT_IMPORT, id: modelUUID }, { type: API_TAGS.MODEL, id: modelUUID }, { type: API_TAGS.MODELS }, + { type: API_TAGS.OVERALL_MODELS }, ]; } return []; @@ -208,20 +209,37 @@ export const modelsApiSlice = apiService.injectEndpoints({ }), }), - // TODO replace with correct apiUrl getOverallStats: builder.query({ - /* providesTags: (_, __, { uuid }) => [{ type: API_TAGS.CURRENT_IMPORT, id: uuid }], - query: ({ uuid, currentUUID }) => ({ + providesTags: () => [{ type: API_TAGS.OVERALL_STATS }], + query: () => ({ baseUrl: import.meta.env.VITE_BASE_URL, - url: `/models/${uuid}/current/${currentUUID}/drift`, + url: '/models/tot_percentages', method: 'get', - }), */ - queryFn: () => { - console.debug(overallStats); - return { data: overallStats }; - }, + }), }), + getOverallModelList: builder.query({ + providesTags: () => [API_TAGS.OVERALL_MODELS], + query: ({ limit }) => ({ + baseUrl: import.meta.env.VITE_BASE_URL, + url: `/models/last_n?n_models=${limit}`, + method: 'get', + }), + onQueryStarted: (async (arg, { queryFulfilled, dispatch }) => { + const response = await queryFulfilled; + + if (response.data) { + const isReferencePending = response.data.some((d) => d.latestReferenceJobStatus === JOB_STATUS.IMPORTING); + const isCurrentPending = response.data.some((d) => d.latestCurrentJobStatus === JOB_STATUS.IMPORTING); + const isPending = isReferencePending || isCurrentPending; + + if (!isPending) { + dispatch(apiService.util.invalidateTags([API_TAGS.OVERALL_STATS, API_TAGS.ALERTS])); + } + } + }), + + }), }), }); diff --git a/ui/src/store/state/models/polling-hook.js b/ui/src/store/state/models/polling-hook.js index d4e9d789..1b721f91 100644 --- a/ui/src/store/state/models/polling-hook.js +++ b/ui/src/store/state/models/polling-hook.js @@ -1,8 +1,8 @@ +import useModals from '@Hooks/use-modals'; import { DEFAULT_POLLING_INTERVAL, JOB_STATUS, NamespaceEnum } from '@Src/constants'; import { selectors as contextConfigurationSelectors } from '@State/context-configuration'; import { useSelector } from 'react-redux'; import { useParams } from 'react-router'; -import useModals from '@Hooks/use-modals'; import { modelsApiSlice } from './api'; const { selectQueryParamsSelector } = contextConfigurationSelectors; @@ -19,6 +19,7 @@ const { useGetCurrentDriftQuery, useGetModelByUUIDQuery, useGetModelsQuery, + useGetOverallModelListQuery, } = modelsApiSlice; const useGetReferenceImportsQueryWithPolling = () => { @@ -169,15 +170,25 @@ const useGetModelQueryWithPolling = () => { return result; }; +const useGetOverallModelListQueryWithPolling = () => { + const result = useGetOverallModelListQuery({ limit: 10 }); + + const isReferencePending = result.data?.some((d) => d.latestReferenceJobStatus === JOB_STATUS.IMPORTING); + const isCurrentPending = result.data?.some((d) => d.latestCurrentJobStatus === JOB_STATUS.IMPORTING); + const isPending = isReferencePending || isCurrentPending; + + useGetOverallModelListQuery({ limit: 10 }, { pollingInterval: DEFAULT_POLLING_INTERVAL, skip: !isPending }); + return result; +}; + export { useGetCurrentDataQualityQueryWithPolling, useGetCurrentDriftQueryWithPolling, useGetCurrentImportsQueryWithPolling, useGetCurrentModelQualityQueryWithPolling, - useGetCurrentStatisticsQueryWithPolling, - useGetReferenceDataQualityQueryWithPolling, + useGetCurrentStatisticsQueryWithPolling, useGetModelQueryWithPolling, + useGetOverallModelListQueryWithPolling, useGetReferenceDataQualityQueryWithPolling, useGetReferenceImportsQueryWithPolling, useGetReferenceModelQualityQueryWithPolling, useGetReferenceStatisticsQueryWithPolling, - useGetModelQueryWithPolling, }; diff --git a/ui/yarn.lock b/ui/yarn.lock index d66e356b..611a9df4 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -814,10 +814,10 @@ dependencies: yup "^1.4.0" -"@radicalbit/radicalbit-design-system@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@radicalbit/radicalbit-design-system/-/radicalbit-design-system-1.1.0.tgz#2506608ee3b4bfe7cbf6c81da6ea4fba0e0ee76a" - integrity sha512-GBX5ch7gXpFU20ZM4YpLjCwhV/e0boRxl5cgXiWuBh+NR6rh7HC2rOfsPPRu/1tuaTUU8vwBD8KImQc9SvVBJQ== +"@radicalbit/radicalbit-design-system@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@radicalbit/radicalbit-design-system/-/radicalbit-design-system-1.2.0.tgz#11ccee8f19c487240a76afdb02c547a510626e41" + integrity sha512-35dJkz/D6Ed8h/IX8QARvjL5lQQta7MM0+OoPIqCY0RI36sE4p6Sgd11UR8kszyu6eLxjWnb5fCYsklHBrsUqQ== dependencies: "@babel/polyfill" "7.12.1" "@fortawesome/fontawesome-svg-core" "^6.5.1"