diff --git a/backend/src/types.ts b/backend/src/types.ts index 34944890fe..53c8186591 100644 --- a/backend/src/types.ts +++ b/backend/src/types.ts @@ -29,6 +29,7 @@ export type DashboardConfig = K8sResourceCommon & { disableCustomServingRuntimes: boolean; modelMetricsNamespace: string; disablePipelines: boolean; + disableBiasMetrics: boolean; }; groupsConfig?: { adminGroups: string; diff --git a/backend/src/utils/constants.ts b/backend/src/utils/constants.ts index a04c37cb6d..26cdad3714 100644 --- a/backend/src/utils/constants.ts +++ b/backend/src/utils/constants.ts @@ -53,6 +53,7 @@ export const blankDashboardCR: DashboardConfig = { disableCustomServingRuntimes: false, modelMetricsNamespace: '', disablePipelines: true, + disableBiasMetrics: false, }, notebookController: { enabled: true, diff --git a/docs/dashboard_config.md b/docs/dashboard_config.md index 88f0a405b8..0505f5561e 100644 --- a/docs/dashboard_config.md +++ b/docs/dashboard_config.md @@ -23,6 +23,7 @@ The following are a list of features that are supported, along with there defaul | disableProjectSharing | false | Disables Project Sharing from Data Science Projects. | | disableCustomServingRuntimes | false | Disables Custom Serving Runtimes from the Admin Panel. | | modelMetricsNamespace | false | Enables the namespace in which the Model Serving Metrics' Prometheus Operator is installed. | +| disableBiasMetrics | false | Disables Model Bias from Model Serving metrics. | ## Defaults @@ -46,6 +47,7 @@ spec: disableProjectSharing: false disableCustomServingRuntimes: false modelMetricsNamespace: '' + disableBiasMetrics: false ``` ## Additional fields @@ -136,6 +138,7 @@ spec: disableProjectSharing: true disableCustomServingRuntimes: false modelMetricsNamespace: '' + disableBiasMetrics: false notebookController: enabled: true notebookSizes: diff --git a/frontend/src/__mocks__/mockDashboardConfig.ts b/frontend/src/__mocks__/mockDashboardConfig.ts index d360c4f8c9..927b38486d 100644 --- a/frontend/src/__mocks__/mockDashboardConfig.ts +++ b/frontend/src/__mocks__/mockDashboardConfig.ts @@ -54,6 +54,7 @@ export const mockDashboardConfig = ({ modelMetricsNamespace: 'test-project', disablePipelines: false, disableProjectSharing: false, + disableBiasMetrics: false, }, notebookController: { enabled: true, diff --git a/frontend/src/api/prometheus/serving.ts b/frontend/src/api/prometheus/serving.ts index c2dd132a2d..71df8fe728 100644 --- a/frontend/src/api/prometheus/serving.ts +++ b/frontend/src/api/prometheus/serving.ts @@ -13,6 +13,7 @@ import { RefreshIntervalTitle, TimeframeTitle, } from '~/pages/modelServing/screens/types'; +import useBiasMetricsEnabled from '~/concepts/explainability/useBiasMetricsEnabled'; import useQueryRangeResourceData, { useQueryRangeResourceDataTrusty, } from './useQueryRangeResourceData'; @@ -32,6 +33,7 @@ export const useModelServingMetrics = ( refresh: () => void; } => { const [end, setEnd] = React.useState(lastUpdateTime); + const [biasMetricsEnabled] = useBiasMetricsEnabled(); const runtimeRequestCount = useQueryRangeResourceData( type === 'runtime', @@ -82,7 +84,7 @@ export const useModelServingMetrics = ( ); const inferenceTrustyAISPD = useQueryRangeResourceDataTrusty( - type === 'inference', + biasMetricsEnabled && type === 'inference', queries[InferenceMetricType.TRUSTY_AI_SPD], end, timeframe, @@ -90,7 +92,7 @@ export const useModelServingMetrics = ( ); const inferenceTrustyAIDIR = useQueryRangeResourceDataTrusty( - type === 'inference', + biasMetricsEnabled && type === 'inference', queries[InferenceMetricType.TRUSTY_AI_DIR], end, timeframe, diff --git a/frontend/src/api/prometheus/usePrometheusQueryRange.ts b/frontend/src/api/prometheus/usePrometheusQueryRange.ts index 76d9b3f9f2..ac538d2937 100644 --- a/frontend/src/api/prometheus/usePrometheusQueryRange.ts +++ b/frontend/src/api/prometheus/usePrometheusQueryRange.ts @@ -1,7 +1,11 @@ import * as React from 'react'; import axios from 'axios'; -import useFetchState, { FetchState, FetchStateCallbackPromise } from '~/utilities/useFetchState'; +import useFetchState, { + FetchState, + FetchStateCallbackPromise, + NotReadyError, +} from '~/utilities/useFetchState'; import { PrometheusQueryRangeResponse, PrometheusQueryRangeResponseData, @@ -25,13 +29,17 @@ const usePrometheusQueryRange = ( const endInS = endInMs / 1000; const start = endInS - span; + if (!active) { + return Promise.reject(new NotReadyError('Prometheus query is not active')); + } + return axios .post<{ response: PrometheusQueryRangeResponse }>(apiPath, { query: `query=${queryLang}&start=${start}&end=${endInS}&step=${step}`, }) .then((response) => responsePredicate(response.data?.response.data)); - }, [endInMs, span, apiPath, queryLang, step, responsePredicate]); + }, [endInMs, span, apiPath, queryLang, step, responsePredicate, active]); return useFetchState(fetchData, []); }; diff --git a/frontend/src/concepts/pipelines/content/tables/EmptyTableView.tsx b/frontend/src/concepts/dashboard/DashboardEmptyTableView.tsx similarity index 80% rename from frontend/src/concepts/pipelines/content/tables/EmptyTableView.tsx rename to frontend/src/concepts/dashboard/DashboardEmptyTableView.tsx index 3e37bfbdaf..5d0480fc38 100644 --- a/frontend/src/concepts/pipelines/content/tables/EmptyTableView.tsx +++ b/frontend/src/concepts/dashboard/DashboardEmptyTableView.tsx @@ -10,11 +10,11 @@ import { } from '@patternfly/react-core'; import { SearchIcon } from '@patternfly/react-icons'; -type EmptyTableViewProps = { +type DashboardEmptyTableViewProps = { onClearFilters: () => void; }; -const EmptyTableView: React.FC = ({ onClearFilters }) => ( +const DashboardEmptyTableView: React.FC = ({ onClearFilters }) => ( @@ -33,4 +33,4 @@ const EmptyTableView: React.FC = ({ onClearFilters }) => ( ); -export default EmptyTableView; +export default DashboardEmptyTableView; diff --git a/frontend/src/concepts/dashboard/DashboardHelpTooltip.tsx b/frontend/src/concepts/dashboard/DashboardHelpTooltip.tsx new file mode 100644 index 0000000000..b844e9f83a --- /dev/null +++ b/frontend/src/concepts/dashboard/DashboardHelpTooltip.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; +import { Tooltip } from '@patternfly/react-core'; +import { HelpIcon } from '@patternfly/react-icons'; + +type DashboardHelpTooltipProps = { + content: string; +}; + +const DashboardHelpTooltip: React.FC = ({ content }) => ( + + + +); + +export default DashboardHelpTooltip; diff --git a/frontend/src/concepts/explainability/ExplainabilityContext.tsx b/frontend/src/concepts/explainability/ExplainabilityContext.tsx index 6ce7c43db2..1bc07df11e 100644 --- a/frontend/src/concepts/explainability/ExplainabilityContext.tsx +++ b/frontend/src/concepts/explainability/ExplainabilityContext.tsx @@ -11,6 +11,7 @@ import useFetchState, { FetchStateCallbackPromise, NotReadyError, } from '~/utilities/useFetchState'; +import useBiasMetricsEnabled from './useBiasMetricsEnabled'; // TODO create component for ensuring API availability, see pipelines for example. @@ -113,8 +114,12 @@ const useFetchContextData = (apiState: TrustyAPIState): ExplainabilityContextDat }; const useFetchBiasMetricConfigs = (apiState: TrustyAPIState): FetchState => { + const [biasMetricsEnabled] = useBiasMetricsEnabled(); const callback = React.useCallback>( (opts) => { + if (!biasMetricsEnabled) { + return Promise.reject(new NotReadyError('Bias metrics is not enabled')); + } if (!apiState.apiAvailable) { return Promise.reject(new NotReadyError('API not yet available')); } @@ -125,7 +130,7 @@ const useFetchBiasMetricConfigs = (apiState: TrustyAPIState): FetchState { + const { + dashboardConfig: { + spec: { + dashboardConfig: { disableBiasMetrics }, + }, + }, + } = useAppContext(); + + return [featureFlagEnabled(disableBiasMetrics)]; +}; + +export default useBiasMetricsEnabled; diff --git a/frontend/src/concepts/explainability/useTrustyAPIRoute.ts b/frontend/src/concepts/explainability/useTrustyAPIRoute.ts index 5d27dae623..79949fb092 100644 --- a/frontend/src/concepts/explainability/useTrustyAPIRoute.ts +++ b/frontend/src/concepts/explainability/useTrustyAPIRoute.ts @@ -7,11 +7,16 @@ import useFetchState, { import { getTrustyAIAPIRoute } from '~/api/'; import { RouteKind } from '~/k8sTypes'; import { FAST_POLL_INTERVAL } from '~/utilities/const'; +import useBiasMetricsEnabled from './useBiasMetricsEnabled'; type State = string | null; const useTrustyAPIRoute = (hasCR: boolean, namespace: string): FetchState => { + const [biasMetricsEnabled] = useBiasMetricsEnabled(); const callback = React.useCallback>( (opts) => { + if (!biasMetricsEnabled) { + return Promise.reject(new NotReadyError('Bias metrics is not enabled')); + } if (!hasCR) { return Promise.reject(new NotReadyError('CR not created')); } @@ -27,7 +32,7 @@ const useTrustyAPIRoute = (hasCR: boolean, namespace: string): FetchState throw e; }); }, - [hasCR, namespace], + [hasCR, namespace, biasMetricsEnabled], ); // TODO: add duplicate functionality to useFetchState. diff --git a/frontend/src/concepts/explainability/useTrustyAiNamespaceCR.ts b/frontend/src/concepts/explainability/useTrustyAiNamespaceCR.ts index d3e2ac702e..d6045573a4 100644 --- a/frontend/src/concepts/explainability/useTrustyAiNamespaceCR.ts +++ b/frontend/src/concepts/explainability/useTrustyAiNamespaceCR.ts @@ -1,13 +1,24 @@ import React from 'react'; -import useFetchState, { FetchState, FetchStateCallbackPromise } from '~/utilities/useFetchState'; +import useFetchState, { + FetchState, + FetchStateCallbackPromise, + NotReadyError, +} from '~/utilities/useFetchState'; import { TrustyAiKind } from '~/k8sTypes'; +import useBiasMetricsEnabled from './useBiasMetricsEnabled'; type State = TrustyAiKind | null; const useTrustyAiNamespaceCR = (namespace: string): FetchState => { + const [biasMetricsEnabled] = useBiasMetricsEnabled(); // TODO: the logic needs to be fleshed out once the TrustyAI operator is complete. const callback = React.useCallback>( - (opts) => (namespace && opts ? Promise.resolve(null) : Promise.reject()), - [namespace], + (opts) => { + if (!biasMetricsEnabled) { + return Promise.reject(new NotReadyError('Bias metrics is not enabled')); + } + return namespace && opts ? Promise.resolve(null) : Promise.reject(); + }, + [namespace, biasMetricsEnabled], ); const state = useFetchState(callback, null, { diff --git a/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTable.tsx b/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTable.tsx index e323982143..fd1cc66fae 100644 --- a/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTable.tsx +++ b/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTable.tsx @@ -5,7 +5,7 @@ import { PipelineCoreResourceKF, PipelineRunKF } from '~/concepts/pipelines/kfTy import { pipelineRunColumns } from '~/concepts/pipelines/content/tables/columns'; import PipelineRunTableRow from '~/concepts/pipelines/content/tables/pipelineRun/PipelineRunTableRow'; import useCheckboxTable from '~/components/table/useCheckboxTable'; -import EmptyTableView from '~/concepts/pipelines/content/tables/EmptyTableView'; +import DashboardEmptyTableView from '~/concepts/dashboard/DashboardEmptyTableView'; import usePipelineRunFilter from '~/concepts/pipelines/content/tables/pipelineRun/usePipelineRunFilter'; import PipelineRunTableToolbar from '~/concepts/pipelines/content/tables/pipelineRun/PipelineRunTableToolbar'; import DeletePipelineCoreResourceModal from '~/concepts/pipelines/content/DeletePipelineCoreResourceModal'; @@ -32,7 +32,7 @@ const PipelineRunTable: React.FC = ({ runs }) => { data={filteredRuns} columns={pipelineRunColumns} enablePagination - emptyTableView={} + emptyTableView={} toolbarContent={ = ({ jobs }) => { data={filterJobs} columns={pipelineRunJobColumns} enablePagination - emptyTableView={} + emptyTableView={} toolbarContent={ { const [modelMetricsEnabled] = useModelMetricsEnabled(); + const [biasMetricsEnabled] = useBiasMetricsEnabled(); //TODO: Split route to project and mount provider here. This will allow you to load data when model switching is later implemented. return ( @@ -22,7 +24,9 @@ const ModelServingRoutes: React.FC = () => { } /> }> } /> - } /> + {biasMetricsEnabled && ( + } /> + )} {/* TODO: Global Runtime metrics?? */} } /> diff --git a/frontend/src/pages/modelServing/screens/global/InferenceServiceTable.tsx b/frontend/src/pages/modelServing/screens/global/InferenceServiceTable.tsx index d2926ae9cb..7d00c9d78b 100644 --- a/frontend/src/pages/modelServing/screens/global/InferenceServiceTable.tsx +++ b/frontend/src/pages/modelServing/screens/global/InferenceServiceTable.tsx @@ -1,10 +1,9 @@ import * as React from 'react'; -import { Button } from '@patternfly/react-core'; import ManageInferenceServiceModal from '~/pages/modelServing/screens/projects/InferenceServiceModal/ManageInferenceServiceModal'; import Table from '~/components/table/Table'; - import { InferenceServiceKind, ServingRuntimeKind } from '~/k8sTypes'; import { ProjectsContext } from '~/concepts/projects/ProjectsContext'; +import DashboardEmptyTableView from '~/concepts/dashboard/DashboardEmptyTableView'; import InferenceServiceTableRow from './InferenceServiceTableRow'; import { getGlobalInferenceServiceColumns, getProjectInferenceServiceColumns } from './data'; import DeleteInferenceServiceModal from './DeleteInferenceServiceModal'; @@ -41,14 +40,7 @@ const InferenceServiceTable: React.FC = ({ toolbarContent={toolbarContent} enablePagination={enablePagination} emptyTableView={ - isGlobal ? ( - <> - No projects match your filters.{' '} - - - ) : undefined + isGlobal ? : undefined } rowRenderer={(is) => ( ( - - +type BiasConfigurationEmptyStateProps = { + actionButton: React.ReactNode; + variant: EmptyStateVariant; +}; + +const BiasConfigurationEmptyState: React.FC = ({ + actionButton, + variant, +}) => ( + + - No bias metrics configured + {EMPTY_BIAS_CONFIGURATION_TITLE} - - No bias metrics for this model have been configured. To monitor model bias, you must first - configure metrics - + {EMPTY_BIAS_CONFIGURATION_DESC} + {actionButton} ); diff --git a/frontend/src/pages/modelServing/screens/metrics/BiasConfigurationPage.tsx b/frontend/src/pages/modelServing/screens/metrics/BiasConfigurationPage.tsx index 79b46a87e4..d4fb10bf6b 100644 --- a/frontend/src/pages/modelServing/screens/metrics/BiasConfigurationPage.tsx +++ b/frontend/src/pages/modelServing/screens/metrics/BiasConfigurationPage.tsx @@ -1,5 +1,11 @@ import * as React from 'react'; -import { Breadcrumb, Button } from '@patternfly/react-core'; +import { + Breadcrumb, + Button, + EmptyStateVariant, + PageSection, + PageSectionVariants, +} from '@patternfly/react-core'; import { useNavigate } from 'react-router-dom'; import ApplicationsPage from '~/pages/ApplicationsPage'; import { BreadcrumbItemType } from '~/types'; @@ -9,6 +15,8 @@ import { getInferenceServiceDisplayName } from '~/pages/modelServing/screens/glo import { MetricsTabKeys } from './types'; import BiasConfigurationTable from './BiasConfigurationTable'; import { getBreadcrumbItemComponents } from './utils'; +import BiasConfigurationEmptyState from './BiasConfigurationEmptyState'; +import ManageBiasConfigurationModal from './biasConfigurationModal/ManageBiasConfigurationModal'; type BiasConfigurationPageProps = { breadcrumbItems: BreadcrumbItemType[]; @@ -19,27 +27,63 @@ const BiasConfigurationPage: React.FC = ({ breadcrumbItems, inferenceService, }) => { - const { biasMetricConfigs, loaded } = useExplainabilityModelData(); + const { biasMetricConfigs, loaded, loadError, refresh } = useExplainabilityModelData(); const navigate = useNavigate(); - return ( - {getBreadcrumbItemComponents(breadcrumbItems)}} - headerAction={ - + const firstRender = React.useRef(true); + const [isOpen, setOpen] = React.useState(false); + + React.useEffect(() => { + if (loaded && !loadError) { + if (firstRender.current) { + firstRender.current = false; + if (biasMetricConfigs.length === 0) { + setOpen(true); + } } - loaded={loaded} - provideChildrenPadding - // The page is not empty, we will handle the empty state in the table - empty={false} - > - - + } + }, [loaded, biasMetricConfigs, loadError]); + + return ( + <> + {getBreadcrumbItemComponents(breadcrumbItems)}} + headerAction={ + + } + loaded={loaded} + provideChildrenPadding + empty={biasMetricConfigs.length === 0} + emptyStatePage={ + + setOpen(true)}>Configure metric} + variant={EmptyStateVariant.large} + /> + + } + > + setOpen(true)} + /> + + { + if (submit) { + refresh(); + } + setOpen(false); + }} + inferenceService={inferenceService} + /> + ); }; diff --git a/frontend/src/pages/modelServing/screens/metrics/BiasConfigurationTable.tsx b/frontend/src/pages/modelServing/screens/metrics/BiasConfigurationTable.tsx index 78a848eefb..98e53b4ea9 100644 --- a/frontend/src/pages/modelServing/screens/metrics/BiasConfigurationTable.tsx +++ b/frontend/src/pages/modelServing/screens/metrics/BiasConfigurationTable.tsx @@ -1,22 +1,25 @@ import * as React from 'react'; -import { Button, ToolbarItem } from '@patternfly/react-core'; +import { Button, ButtonVariant, ToolbarItem } from '@patternfly/react-core'; import Table from '~/components/table/Table'; import DashboardSearchField, { SearchType } from '~/concepts/dashboard/DashboardSearchField'; import { BiasMetricConfig } from '~/concepts/explainability/types'; import { useExplainabilityModelData } from '~/concepts/explainability/useExplainabilityModelData'; import { InferenceServiceKind } from '~/k8sTypes'; import DeleteBiasConfigurationModal from '~/pages/modelServing/screens/metrics/biasConfigurationModal/DeleteBiasConfigurationModal'; +import DashboardEmptyTableView from '~/concepts/dashboard/DashboardEmptyTableView'; import ManageBiasConfigurationModal from './biasConfigurationModal/ManageBiasConfigurationModal'; import BiasConfigurationTableRow from './BiasConfigurationTableRow'; import { columns } from './tableData'; -import BiasConfigurationEmptyState from './BiasConfigurationEmptyState'; -import BiasConfigurationButton from './BiasConfigurationButton'; type BiasConfigurationTableProps = { inferenceService: InferenceServiceKind; + onConfigure: () => void; }; -const BiasConfigurationTable: React.FC = ({ inferenceService }) => { +const BiasConfigurationTable: React.FC = ({ + inferenceService, + onConfigure, +}) => { const { biasMetricConfigs, refresh } = useExplainabilityModelData(); const [searchType, setSearchType] = React.useState(SearchType.NAME); const [search, setSearch] = React.useState(''); @@ -75,18 +78,7 @@ const BiasConfigurationTable: React.FC = ({ inferen onDeleteConfiguration={setDeleteConfiguration} /> )} - emptyTableView={ - search ? ( - <> - No metric configurations match your filters.{' '} - - - ) : ( - - ) - } + emptyTableView={} toolbarContent={ <> @@ -103,7 +95,9 @@ const BiasConfigurationTable: React.FC = ({ inferen /> - + } diff --git a/frontend/src/pages/modelServing/screens/metrics/EmptyBiasConfigurationCard.tsx b/frontend/src/pages/modelServing/screens/metrics/EmptyBiasConfigurationCard.tsx index 05d6d5d9ef..e43feefffc 100644 --- a/frontend/src/pages/modelServing/screens/metrics/EmptyBiasConfigurationCard.tsx +++ b/frontend/src/pages/modelServing/screens/metrics/EmptyBiasConfigurationCard.tsx @@ -1,36 +1,25 @@ import * as React from 'react'; -import { - Button, - Card, - CardBody, - EmptyState, - EmptyStateBody, - EmptyStateIcon, - Title, -} from '@patternfly/react-core'; -import { WrenchIcon } from '@patternfly/react-icons'; +import { Button, Card, CardBody, EmptyStateVariant } from '@patternfly/react-core'; import { useNavigate } from 'react-router-dom'; -import { EMPTY_BIAS_CONFIGURATION_DESC, EMPTY_BIAS_CONFIGURATION_TITLE } from './const'; +import BiasConfigurationEmptyState from './BiasConfigurationEmptyState'; const EmptyBiasConfigurationCard: React.FC = () => { const navigate = useNavigate(); return ( - - - - {EMPTY_BIAS_CONFIGURATION_TITLE} - - {EMPTY_BIAS_CONFIGURATION_DESC} - - + { + navigate('../configure', { relative: 'path' }); + }} + > + Configure + + } + variant={EmptyStateVariant.full} + /> ); diff --git a/frontend/src/pages/modelServing/screens/metrics/MetricsPageTabs.tsx b/frontend/src/pages/modelServing/screens/metrics/MetricsPageTabs.tsx index 392516754f..bcccd9be86 100644 --- a/frontend/src/pages/modelServing/screens/metrics/MetricsPageTabs.tsx +++ b/frontend/src/pages/modelServing/screens/metrics/MetricsPageTabs.tsx @@ -3,26 +3,33 @@ import { useParams, useNavigate } from 'react-router-dom'; import { Tabs, Tab, TabTitleText, TabAction } from '@patternfly/react-core'; import { MetricsTabKeys } from '~/pages/modelServing/screens/metrics/types'; import { useExplainabilityModelData } from '~/concepts/explainability/useExplainabilityModelData'; +import useBiasMetricsEnabled from '~/concepts/explainability/useBiasMetricsEnabled'; +import NotFound from '~/pages/NotFound'; import PerformanceTab from './PerformanceTab'; import BiasTab from './BiasTab'; import BiasConfigurationAlertPopover from './BiasConfigurationAlertPopover'; +import useMetricsPageEnabledTabs from './useMetricsPageEnabledTabs'; import './MetricsPageTabs.scss'; const MetricsPageTabs: React.FC = () => { - const DEFAULT_TAB = MetricsTabKeys.PERFORMANCE; + const enabledTabs = useMetricsPageEnabledTabs(); const { biasMetricConfigs, loaded } = useExplainabilityModelData(); - + const [biasMetricsEnabled] = useBiasMetricsEnabled(); const { tab } = useParams<{ tab: MetricsTabKeys }>(); const navigate = useNavigate(); React.useEffect(() => { if (!tab) { - navigate(`./${DEFAULT_TAB}`, { replace: true }); - } else if (!Object.values(MetricsTabKeys).includes(tab)) { - navigate(`../${DEFAULT_TAB}`, { replace: true }); + navigate(`./${enabledTabs[0]}`, { replace: true }); + } else if (!enabledTabs.includes(tab)) { + navigate(`../${enabledTabs[0]}`, { replace: true }); } - }, [DEFAULT_TAB, navigate, tab]); + }, [enabledTabs, navigate, tab]); + + if (enabledTabs.length === 0) { + return ; + } return ( { > - Model Bias} - aria-label="Bias tab" - className="odh-tabcontent-fix" - actions={ - loaded && - biasMetricConfigs.length === 0 && ( - - { - navigate('../configure', { relative: 'path' }); - }} - /> - - ) - } - > - - + {biasMetricsEnabled && ( + Model Bias} + aria-label="Bias tab" + className="odh-tabcontent-fix" + actions={ + loaded && + biasMetricConfigs.length === 0 && ( + + { + navigate('../configure', { relative: 'path' }); + }} + /> + + ) + } + > + + + )} ); }; diff --git a/frontend/src/pages/modelServing/screens/metrics/biasConfigurationModal/ManageBiasConfigurationModal.tsx b/frontend/src/pages/modelServing/screens/metrics/biasConfigurationModal/ManageBiasConfigurationModal.tsx index 21d45e8934..29a79cd426 100644 --- a/frontend/src/pages/modelServing/screens/metrics/biasConfigurationModal/ManageBiasConfigurationModal.tsx +++ b/frontend/src/pages/modelServing/screens/metrics/biasConfigurationModal/ManageBiasConfigurationModal.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; -import { Form, FormGroup, Modal, TextInput, Tooltip } from '@patternfly/react-core'; -import { HelpIcon } from '@patternfly/react-icons'; +import { Form, FormGroup, Modal, TextInput } from '@patternfly/react-core'; import { BiasMetricConfig } from '~/concepts/explainability/types'; import { MetricTypes } from '~/api'; import { InferenceServiceKind } from '~/k8sTypes'; @@ -9,7 +8,9 @@ import DashboardModalFooter from '~/concepts/dashboard/DashboardModalFooter'; import { checkConfigurationFieldsValid, convertConfigurationRequestType, + getThresholdDefaultDelta, } from '~/pages/modelServing/screens/metrics/utils'; +import DashboardHelpTooltip from '~/concepts/dashboard/DashboardHelpTooltip'; import useBiasConfigurationObject from './useBiasConfigurationObject'; import MetricTypeField from './MetricTypeField'; @@ -29,13 +30,14 @@ const ManageBiasConfigurationModal: React.FC const { apiState: { api }, } = React.useContext(ExplainabilityContext); - const [configuration, setConfiguration] = useBiasConfigurationObject( - inferenceService.metadata.name, - existingConfiguration, - ); const [actionInProgress, setActionInProgress] = React.useState(false); const [error, setError] = React.useState(); const [metricType, setMetricType] = React.useState(); + const [configuration, setConfiguration, resetData] = useBiasConfigurationObject( + inferenceService.metadata.name, + metricType, + existingConfiguration, + ); React.useEffect(() => { setMetricType(existingConfiguration?.metricType); @@ -45,6 +47,8 @@ const ManageBiasConfigurationModal: React.FC onClose(submitted); setError(undefined); setActionInProgress(false); + resetData(); + setMetricType(undefined); }; const onCreateConfiguration = () => { @@ -90,36 +94,73 @@ const ManageBiasConfigurationModal: React.FC onChange={(value) => setConfiguration('requestName', value)} /> - - + { + setMetricType(value); + setConfiguration('thresholdDelta', getThresholdDefaultDelta(value)); + }} + /> + + } + > setConfiguration('protectedAttribute', value)} /> - + + } + > setConfiguration('privilegedAttribute', value)} /> - + + } + > setConfiguration('unprivilegedAttribute', value)} /> - + + } + > setConfiguration('outcomeName', value)} /> - + + } + > label="Violation threshold" fieldId="violation-threshold" labelIcon={ - - - + } > label="Metric batch size" fieldId="metric-batch-size" labelIcon={ - - - + } > void; + onChange: (value: MetricTypes) => void; }; -const MetricTypeField: React.FC = ({ fieldId, value, setValue }) => { +const MetricTypeField: React.FC = ({ fieldId, value, onChange }) => { const [isOpen, setOpen] = React.useState(false); return ( - // TODO: decide what to show in the helper tooltip - - - - } - > +