Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add bias metrics configuration modal #1343

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions frontend/src/api/prometheus/serving.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import * as React from 'react';
import { ContextResourceData, PrometheusQueryRangeResultValue } from '~/types';
import {
ContextResourceData,
PrometheusQueryRangeResponseDataResult,
PrometheusQueryRangeResultValue,
} from '~/types';
import {
InferenceMetricType,
RuntimeMetricType,
Expand All @@ -16,7 +20,10 @@ export const useModelServingMetrics = (
lastUpdateTime: number,
setLastUpdateTime: (time: number) => void,
): {
data: Record<RuntimeMetricType, ContextResourceData<PrometheusQueryRangeResultValue>>;
data: Record<
RuntimeMetricType | InferenceMetricType,
ContextResourceData<PrometheusQueryRangeResultValue | PrometheusQueryRangeResponseDataResult>
>;
alexcreasy marked this conversation as resolved.
Show resolved Hide resolved
refresh: () => void;
} => {
const [end, setEnd] = React.useState(lastUpdateTime);
Expand Down Expand Up @@ -88,6 +95,8 @@ export const useModelServingMetrics = (
runtimeMemoryUtilization,
inferenceRequestSuccessCount,
inferenceRequestFailedCount,
inferenceTrustyAIDIR,
inferenceTrustyAISPD,
]);

const refreshAllMetrics = React.useCallback(() => {
Expand Down
48 changes: 48 additions & 0 deletions frontend/src/concepts/dashboard/DashboardModalFooter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {
ActionList,
ActionListItem,
Alert,
Button,
Stack,
StackItem,
} from '@patternfly/react-core';
import * as React from 'react';

type DashboardModalFooterProps = {
submitLabel: string;
onSubmit: () => void;
onCancel: () => void;
isSubmitDisabled: boolean;
alertTitle: string;
error?: Error;
};

const DashboardModalFooter: React.FC<DashboardModalFooterProps> = (
{ submitLabel, onSubmit, onCancel, isSubmitDisabled, error, alertTitle }, // make sure alert uses the full width
) => (
<Stack hasGutter style={{ flex: 'auto' }}>
{error && (
<StackItem>
<Alert isInline variant="danger" title={alertTitle}>
{error.message}
</Alert>
</StackItem>
)}
<StackItem>
<ActionList>
<ActionListItem>
<Button key="submit" variant="primary" isDisabled={isSubmitDisabled} onClick={onSubmit}>
{submitLabel}
</Button>
</ActionListItem>
<ActionListItem>
<Button key="cancel" variant="link" onClick={onCancel}>
Cancel
</Button>
</ActionListItem>
</ActionList>
</StackItem>
</Stack>
);

export default DashboardModalFooter;
2 changes: 2 additions & 0 deletions frontend/src/pages/modelServing/ModelServingRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const ModelServingRoutes: React.FC = () => {
<Route index element={<ModelServingGlobal />} />
{modelMetricsEnabled && (
<Route path="/metrics/:project" element={<ExplainabilityProvider />}>
<Route index element={<Navigate to=".." />} />
<Route path=":inferenceService" element={<GlobalInferenceMetricsWrapper />}>
<Route path=":tab?" element={<GlobalInferenceMetricsPage />} />
<Route path="configure" element={<BiasConfigurationBreadcrumbPage />} />
Expand All @@ -27,6 +28,7 @@ const ModelServingRoutes: React.FC = () => {
<Route path="*" element={<Navigate to="." />} />
</Route>
)}
<Route path="*" element={<Navigate to="." />} />
</Route>
</ProjectsRoutes>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as React from 'react';
import { Button, Icon, Popover } from '@patternfly/react-core';
import { InfoCircleIcon } from '@patternfly/react-icons';
import { EMPTY_BIAS_CONFIGURATION_DESC, EMPTY_BIAS_CONFIGURATION_TITLE } from './const';

type BiasConfigurationAlertPopoverProps = {
onConfigure: () => void;
};

const BiasConfigurationAlertPopover: React.FC<BiasConfigurationAlertPopoverProps> = ({
onConfigure,
}) => (
<Popover
aria-label={EMPTY_BIAS_CONFIGURATION_TITLE}
alertSeverityVariant="info"
headerContent={EMPTY_BIAS_CONFIGURATION_TITLE}
headerIcon={<InfoCircleIcon />}
bodyContent={EMPTY_BIAS_CONFIGURATION_DESC}
footerContent={
<Button variant="secondary" isSmall onClick={onConfigure}>
Configure
</Button>
}
>
<Icon status="info">
<InfoCircleIcon />
</Icon>
</Popover>
);

export default BiasConfigurationAlertPopover;
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const BiasConfigurationBreadcrumbPage: React.FC = () => {
},
{ label: 'Metric configuration', isActive: true },
]}
modelDisplayName={modelDisplayName}
inferenceService={inferenceService}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as React from 'react';
import { Button } from '@patternfly/react-core';
import { useExplainabilityModelData } from '~/concepts/explainability/useExplainabilityModelData';
import { InferenceServiceKind } from '~/k8sTypes';
import ManageBiasConfigurationModal from './biasConfigurationModal/ManageBiasConfigurationModal';

type BiasConfigurationButtonProps = {
inferenceService: InferenceServiceKind;
};

const BiasConfigurationButton: React.FC<BiasConfigurationButtonProps> = ({ inferenceService }) => {
const [isOpen, setOpen] = React.useState(false);
const { biasMetricConfigs, loaded, refresh } = useExplainabilityModelData();

React.useEffect(() => {
if (loaded && biasMetricConfigs.length === 0) {
setOpen(true);
}
}, [loaded, biasMetricConfigs]);

return (
<>
<Button onClick={() => setOpen(true)} variant="secondary">
Configure metric
</Button>
<ManageBiasConfigurationModal
isOpen={isOpen}
onClose={(submit) => {
if (submit) {
refresh();
}
setOpen(false);
}}
inferenceService={inferenceService}
/>
</>
);
};

export default BiasConfigurationButton;
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,22 @@ import { useNavigate } from 'react-router-dom';
import ApplicationsPage from '~/pages/ApplicationsPage';
import { BreadcrumbItemType } from '~/types';
import { useExplainabilityModelData } from '~/concepts/explainability/useExplainabilityModelData';
import { InferenceServiceKind } from '~/k8sTypes';
import { getInferenceServiceDisplayName } from '~/pages/modelServing/screens/global/utils';
import { MetricsTabKeys } from './types';
import BiasConfigurationTable from './BiasConfigurationTable';
import { getBreadcrumbItemComponents } from './utils';

type BiasConfigurationPageProps = {
breadcrumbItems: BreadcrumbItemType[];
modelDisplayName: string;
inferenceService: InferenceServiceKind;
};

const BiasConfigurationPage: React.FC<BiasConfigurationPageProps> = ({
breadcrumbItems,
modelDisplayName,
inferenceService,
}) => {
const { biasMetricConfigs, loaded } = useExplainabilityModelData();
const emptyConfiguration = biasMetricConfigs.length === 0;
const navigate = useNavigate();
return (
<ApplicationsPage
Expand All @@ -27,15 +28,17 @@ const BiasConfigurationPage: React.FC<BiasConfigurationPageProps> = ({
breadcrumb={<Breadcrumb>{getBreadcrumbItemComponents(breadcrumbItems)}</Breadcrumb>}
headerAction={
<Button onClick={() => navigate(`../${MetricsTabKeys.BIAS}`, { relative: 'path' })}>
{emptyConfiguration ? `Back to ${modelDisplayName}` : 'View metrics'}
{biasMetricConfigs.length === 0
? `Back to ${getInferenceServiceDisplayName(inferenceService)}`
: 'View metrics'}
</Button>
}
loaded={loaded}
provideChildrenPadding
// The page is not empty, we will handle the empty state in the table
empty={false}
>
<BiasConfigurationTable configurations={biasMetricConfigs} />
<BiasConfigurationTable inferenceService={inferenceService} />
</ApplicationsPage>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,36 @@ import { Button, 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 ManageBiasConfigurationModal from './biasConfigurationModal/ManageBiasConfigurationModal';
import BiasConfigurationTableRow from './BiasConfigurationTableRow';
import { columns } from './tableData';
import BiasConfigurationEmptyState from './BiasConfigurationEmptyState';
import BiasConfigurationButton from './BiasConfigurationButton';

type BiasConfigurationTableProps = {
configurations: BiasMetricConfig[];
inferenceService: InferenceServiceKind;
};

const BiasConfigurationTable: React.FC<BiasConfigurationTableProps> = ({ configurations }) => {
const BiasConfigurationTable: React.FC<BiasConfigurationTableProps> = ({ inferenceService }) => {
const { biasMetricConfigs, refresh } = useExplainabilityModelData();
const [searchType, setSearchType] = React.useState<SearchType>(SearchType.NAME);
const [search, setSearch] = React.useState('');
const filteredConfigurations = configurations.filter((configuration) => {
const [cloneConfiguration, setCloneConfiguration] = React.useState<BiasMetricConfig>();
const [deleteConfiguration, setDeleteConfiguration] = React.useState<BiasMetricConfig>();

const filteredConfigurations = biasMetricConfigs.filter((configuration) => {
if (!search) {
return true;
}

// TODO: add more search types
switch (searchType) {
case SearchType.NAME:
return configuration.name.toLowerCase().includes(search.toLowerCase());
case SearchType.METRIC:
return configuration.metricType.toLowerCase().includes(search.toLocaleLowerCase());
case SearchType.PROTECTED_ATTRIBUTE:
return configuration.protectedAttribute.toLowerCase().includes(search.toLowerCase());
case SearchType.OUTPUT:
Expand All @@ -43,53 +53,82 @@ const BiasConfigurationTable: React.FC<BiasConfigurationTableProps> = ({ configu
Object.keys(SearchType).filter(
(key) =>
SearchType[key] === SearchType.NAME ||
SearchType[key] === SearchType.METRIC ||
SearchType[key] === SearchType.PROTECTED_ATTRIBUTE ||
SearchType[key] === SearchType.OUTPUT,
),
[],
);
return (
<Table
data={filteredConfigurations}
columns={columns}
disableRowRenderSupport
rowRenderer={(configuration, i) => (
<BiasConfigurationTableRow key={configuration.id} obj={configuration} rowIndex={i} />
)}
emptyTableView={
search ? (
<>
<Table
data={filteredConfigurations}
columns={columns}
defaultSortColumn={1}
disableRowRenderSupport
rowRenderer={(configuration, i) => (
<BiasConfigurationTableRow
key={configuration.id}
obj={configuration}
rowIndex={i}
onCloneConfiguration={setCloneConfiguration}
onDeleteConfiguration={setDeleteConfiguration}
/>
)}
emptyTableView={
search ? (
<>
No metric configurations match your filters.{' '}
<Button variant="link" isInline onClick={resetFilters}>
Clear filters
</Button>
</>
) : (
<BiasConfigurationEmptyState />
)
}
toolbarContent={
<>
No metric configurations match your filters.{' '}
<Button variant="link" isInline onClick={resetFilters}>
Clear filters
</Button>
<ToolbarItem>
<DashboardSearchField
types={searchTypes}
searchType={searchType}
searchValue={search}
onSearchTypeChange={(searchType) => {
setSearchType(searchType);
}}
onSearchValueChange={(searchValue) => {
setSearch(searchValue);
}}
/>
</ToolbarItem>
<ToolbarItem>
<BiasConfigurationButton inferenceService={inferenceService} />
</ToolbarItem>
</>
) : (
<BiasConfigurationEmptyState />
)
}
toolbarContent={
<>
<ToolbarItem>
<DashboardSearchField
types={searchTypes}
searchType={searchType}
searchValue={search}
onSearchTypeChange={(searchType) => {
setSearchType(searchType);
}}
onSearchValueChange={(searchValue) => {
setSearch(searchValue);
}}
/>
</ToolbarItem>
<ToolbarItem>
{/* TODO: add configure metric action */}
<Button variant="secondary">Configure metric</Button>
</ToolbarItem>
</>
}
/>
}
/>
<ManageBiasConfigurationModal
existingConfiguration={cloneConfiguration}
isOpen={!!cloneConfiguration}
onClose={(submit) => {
if (submit) {
refresh();
}
setCloneConfiguration(undefined);
}}
inferenceService={inferenceService}
/>
<DeleteBiasConfigurationModal
configurationToDelete={deleteConfiguration}
onClose={(deleted) => {
if (deleted) {
refresh();
}
setDeleteConfiguration(undefined);
}}
/>
</>
);
};

Expand Down
Loading