mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-10 16:25:37 +00:00
feat: Refactor theme creation and integrate global styles
- Updated createMuiTheme.ts to improve theme structure and organization. - Added GlobalStyles to App.tsx for consistent font sizing across the application. - Introduced InlineTestCaseIncidentStatus component for better handling of test case statuses. - Modified TestCaseIncidentManagerStatus to conditionally render InlineTestCaseIncidentStatus based on props. - Enhanced DataQualityTab to include a new column for failed/aborted reasons and last run timestamps. - Updated QualityTab to streamline pagination handling and improve layout. - Added isInline prop to TestCaseStatusIncidentManagerStatus interface for inline rendering.
This commit is contained in:
parent
3f8f89be96
commit
98ec6b5f51
@ -11,17 +11,17 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Shadows } from '@mui/material/styles';
|
import type { Shadows } from "@mui/material/styles";
|
||||||
import { createTheme } from '@mui/material/styles';
|
import { createTheme } from "@mui/material/styles";
|
||||||
import { buttonTheme } from './button-theme';
|
import { defaultColors } from "../colors/defaultColors";
|
||||||
import { dataDisplayTheme } from './data-display-theme';
|
import { generateAllMuiPalettes } from "../colors/generateMuiPalettes";
|
||||||
import { defaultColors } from '../colors/defaultColors';
|
import type { CustomColors, ThemeColors } from "../types";
|
||||||
import { formTheme } from './form-theme';
|
import { buttonTheme } from "./button-theme";
|
||||||
import { generateAllMuiPalettes } from '../colors/generateMuiPalettes';
|
import { dataDisplayTheme } from "./data-display-theme";
|
||||||
import { navigationTheme } from './navigation-theme';
|
import { formTheme } from "./form-theme";
|
||||||
import { shadows } from './shadows';
|
import "./mui-theme-types";
|
||||||
import type { CustomColors, ThemeColors } from '../types';
|
import { navigationTheme } from "./navigation-theme";
|
||||||
import './mui-theme-types';
|
import { shadows } from "./shadows";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates dynamic MUI theme with user customizations or default colors
|
* Creates dynamic MUI theme with user customizations or default colors
|
||||||
@ -127,88 +127,90 @@ export const createMuiTheme = (
|
|||||||
allShades: themeColors,
|
allShades: themeColors,
|
||||||
},
|
},
|
||||||
typography: {
|
typography: {
|
||||||
|
fontSize: 14,
|
||||||
|
htmlFontSize: 14,
|
||||||
fontFamily:
|
fontFamily:
|
||||||
'var(--font-inter, "Inter"), -apple-system, "Segoe UI", Roboto, Arial, sans-serif',
|
'var(--font-inter, "Inter"), -apple-system, "Segoe UI", Roboto, Arial, sans-serif',
|
||||||
h1: {
|
h1: {
|
||||||
fontSize: '3.75rem',
|
fontSize: "3.75rem",
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
lineHeight: '4.5rem',
|
lineHeight: "4.5rem",
|
||||||
letterSpacing: '-1.2px',
|
letterSpacing: "-1.2px",
|
||||||
color: 'var(--color-text-primary)',
|
color: "var(--color-text-primary)",
|
||||||
},
|
},
|
||||||
h2: {
|
h2: {
|
||||||
fontSize: '3rem',
|
fontSize: "3rem",
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
lineHeight: '3.75rem',
|
lineHeight: "3.75rem",
|
||||||
letterSpacing: '-0.96px',
|
letterSpacing: "-0.96px",
|
||||||
color: 'var(--color-text-primary)',
|
color: "var(--color-text-primary)",
|
||||||
},
|
},
|
||||||
h3: {
|
h3: {
|
||||||
fontSize: '2.25rem',
|
fontSize: "2.25rem",
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
lineHeight: '2.75rem',
|
lineHeight: "2.75rem",
|
||||||
letterSpacing: '-0.72px',
|
letterSpacing: "-0.72px",
|
||||||
color: 'var(--color-text-primary)',
|
color: "var(--color-text-primary)",
|
||||||
},
|
},
|
||||||
h4: {
|
h4: {
|
||||||
fontSize: '1.875rem',
|
fontSize: "1.875rem",
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
lineHeight: '2.375rem',
|
lineHeight: "2.375rem",
|
||||||
color: 'var(--color-text-primary)',
|
color: "var(--color-text-primary)",
|
||||||
},
|
},
|
||||||
h5: {
|
h5: {
|
||||||
fontSize: '1.5rem',
|
fontSize: "1.5rem",
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
lineHeight: '2rem',
|
lineHeight: "2rem",
|
||||||
color: 'var(--color-text-primary)',
|
color: "var(--color-text-primary)",
|
||||||
},
|
},
|
||||||
h6: {
|
h6: {
|
||||||
fontSize: '1.25rem',
|
fontSize: "1.25rem",
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
lineHeight: '1.875rem',
|
lineHeight: "1.875rem",
|
||||||
color: 'var(--color-text-primary)',
|
color: "var(--color-text-primary)",
|
||||||
},
|
},
|
||||||
subtitle1: {
|
subtitle1: {
|
||||||
fontSize: '1.125rem',
|
fontSize: "1.125rem",
|
||||||
lineHeight: '1.75rem',
|
lineHeight: "1.75rem",
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
color: 'var(--color-text-secondary)',
|
color: "var(--color-text-secondary)",
|
||||||
},
|
},
|
||||||
subtitle2: {
|
subtitle2: {
|
||||||
fontSize: '1rem',
|
fontSize: "1rem",
|
||||||
lineHeight: '1.5rem',
|
lineHeight: "1.5rem",
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
color: 'var(--color-text-secondary)',
|
color: "var(--color-text-secondary)",
|
||||||
},
|
},
|
||||||
body1: {
|
body1: {
|
||||||
fontSize: '1rem',
|
fontSize: "1rem",
|
||||||
lineHeight: '1.5rem',
|
lineHeight: "1.5rem",
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
color: 'var(--color-text-tertiary)',
|
color: "var(--color-text-tertiary)",
|
||||||
},
|
},
|
||||||
body2: {
|
body2: {
|
||||||
fontSize: '0.875rem',
|
fontSize: "0.875rem",
|
||||||
lineHeight: '1.25rem',
|
lineHeight: "1.25rem",
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
color: 'var(--color-text-tertiary)',
|
color: "var(--color-text-tertiary)",
|
||||||
},
|
},
|
||||||
caption: {
|
caption: {
|
||||||
fontSize: '0.75rem',
|
fontSize: "0.75rem",
|
||||||
lineHeight: '1.125rem',
|
lineHeight: "1.125rem",
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
color: 'var(--color-text-quaternary)',
|
color: "var(--color-text-quaternary)",
|
||||||
},
|
},
|
||||||
overline: {
|
overline: {
|
||||||
fontSize: '0.75rem',
|
fontSize: "0.75rem",
|
||||||
lineHeight: '1.125rem',
|
lineHeight: "1.125rem",
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
textTransform: 'uppercase' as const,
|
textTransform: "uppercase" as const,
|
||||||
letterSpacing: '0.5px',
|
letterSpacing: "0.5px",
|
||||||
color: 'var(--color-text-quaternary)',
|
color: "var(--color-text-quaternary)",
|
||||||
},
|
},
|
||||||
button: {
|
button: {
|
||||||
fontSize: '0.875rem',
|
fontSize: "0.875rem",
|
||||||
textTransform: 'none' as const,
|
textTransform: "none" as const,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -217,32 +219,32 @@ export const createMuiTheme = (
|
|||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
},
|
},
|
||||||
shadows: [
|
shadows: [
|
||||||
'none',
|
"none",
|
||||||
shadows.xs,
|
shadows.xs,
|
||||||
shadows.sm,
|
shadows.sm,
|
||||||
shadows.md,
|
shadows.md,
|
||||||
shadows.lg,
|
shadows.lg,
|
||||||
shadows.xl,
|
shadows.xl,
|
||||||
shadows['2xl'],
|
shadows["2xl"],
|
||||||
'0px 32px 64px -12px rgba(10, 13, 18, 0.14), 0px 5px 5px -2.5px rgba(10, 13, 18, 0.04)',
|
"0px 32px 64px -12px rgba(10, 13, 18, 0.14), 0px 5px 5px -2.5px rgba(10, 13, 18, 0.04)",
|
||||||
// Additional shadows for MUI's 25-shadow requirement
|
// Additional shadows for MUI's 25-shadow requirement
|
||||||
'0px 1px 3px rgba(10, 13, 18, 0.1), 0px 1px 2px -1px rgba(10, 13, 18, 0.1)',
|
"0px 1px 3px rgba(10, 13, 18, 0.1), 0px 1px 2px -1px rgba(10, 13, 18, 0.1)",
|
||||||
'0px 4px 6px -1px rgba(10, 13, 18, 0.1), 0px 2px 4px -2px rgba(10, 13, 18, 0.06)',
|
"0px 4px 6px -1px rgba(10, 13, 18, 0.1), 0px 2px 4px -2px rgba(10, 13, 18, 0.06)",
|
||||||
'0px 12px 16px -4px rgba(10, 13, 18, 0.08), 0px 4px 6px -2px rgba(10, 13, 18, 0.03), 0px 2px 2px -1px rgba(10, 13, 18, 0.04)',
|
"0px 12px 16px -4px rgba(10, 13, 18, 0.08), 0px 4px 6px -2px rgba(10, 13, 18, 0.03), 0px 2px 2px -1px rgba(10, 13, 18, 0.04)",
|
||||||
'0px 12px 16px -4px rgba(10, 13, 18, 0.08), 0px 4px 6px -2px rgba(10, 13, 18, 0.03), 0px 2px 2px -1px rgba(10, 13, 18, 0.04)',
|
"0px 12px 16px -4px rgba(10, 13, 18, 0.08), 0px 4px 6px -2px rgba(10, 13, 18, 0.03), 0px 2px 2px -1px rgba(10, 13, 18, 0.04)",
|
||||||
'0px 20px 24px -4px rgba(10, 13, 18, 0.08), 0px 8px 8px -4px rgba(10, 13, 18, 0.03), 0px 3px 3px -1.5px rgba(10, 13, 18, 0.04)',
|
"0px 20px 24px -4px rgba(10, 13, 18, 0.08), 0px 8px 8px -4px rgba(10, 13, 18, 0.03), 0px 3px 3px -1.5px rgba(10, 13, 18, 0.04)",
|
||||||
'0px 20px 24px -4px rgba(10, 13, 18, 0.08), 0px 8px 8px -4px rgba(10, 13, 18, 0.03), 0px 3px 3px -1.5px rgba(10, 13, 18, 0.04)',
|
"0px 20px 24px -4px rgba(10, 13, 18, 0.08), 0px 8px 8px -4px rgba(10, 13, 18, 0.03), 0px 3px 3px -1.5px rgba(10, 13, 18, 0.04)",
|
||||||
'0px 24px 48px -12px rgba(10, 13, 18, 0.18), 0px 4px 4px -2px rgba(10, 13, 18, 0.04)',
|
"0px 24px 48px -12px rgba(10, 13, 18, 0.18), 0px 4px 4px -2px rgba(10, 13, 18, 0.04)",
|
||||||
'0px 24px 48px -12px rgba(10, 13, 18, 0.18), 0px 4px 4px -2px rgba(10, 13, 18, 0.04)',
|
"0px 24px 48px -12px rgba(10, 13, 18, 0.18), 0px 4px 4px -2px rgba(10, 13, 18, 0.04)",
|
||||||
'0px 32px 64px -12px rgba(10, 13, 18, 0.14), 0px 5px 5px -2.5px rgba(10, 13, 18, 0.04)',
|
"0px 32px 64px -12px rgba(10, 13, 18, 0.14), 0px 5px 5px -2.5px rgba(10, 13, 18, 0.04)",
|
||||||
'0px 32px 64px -12px rgba(10, 13, 18, 0.14), 0px 5px 5px -2.5px rgba(10, 13, 18, 0.04)',
|
"0px 32px 64px -12px rgba(10, 13, 18, 0.14), 0px 5px 5px -2.5px rgba(10, 13, 18, 0.04)",
|
||||||
'0px 32px 64px -12px rgba(10, 13, 18, 0.14), 0px 5px 5px -2.5px rgba(10, 13, 18, 0.04)',
|
"0px 32px 64px -12px rgba(10, 13, 18, 0.14), 0px 5px 5px -2.5px rgba(10, 13, 18, 0.04)",
|
||||||
'0px 32px 64px -12px rgba(10, 13, 18, 0.14), 0px 5px 5px -2.5px rgba(10, 13, 18, 0.04)',
|
"0px 32px 64px -12px rgba(10, 13, 18, 0.14), 0px 5px 5px -2.5px rgba(10, 13, 18, 0.04)",
|
||||||
'0px 32px 64px -12px rgba(10, 13, 18, 0.14), 0px 5px 5px -2.5px rgba(10, 13, 18, 0.04)',
|
"0px 32px 64px -12px rgba(10, 13, 18, 0.14), 0px 5px 5px -2.5px rgba(10, 13, 18, 0.04)",
|
||||||
'0px 32px 64px -12px rgba(10, 13, 18, 0.14), 0px 5px 5px -2.5px rgba(10, 13, 18, 0.04)',
|
"0px 32px 64px -12px rgba(10, 13, 18, 0.14), 0px 5px 5px -2.5px rgba(10, 13, 18, 0.04)",
|
||||||
'0px 32px 64px -12px rgba(10, 13, 18, 0.14), 0px 5px 5px -2.5px rgba(10, 13, 18, 0.04)',
|
"0px 32px 64px -12px rgba(10, 13, 18, 0.14), 0px 5px 5px -2.5px rgba(10, 13, 18, 0.04)",
|
||||||
'0px 32px 64px -12px rgba(10, 13, 18, 0.14), 0px 5px 5px -2.5px rgba(10, 13, 18, 0.04)',
|
"0px 32px 64px -12px rgba(10, 13, 18, 0.14), 0px 5px 5px -2.5px rgba(10, 13, 18, 0.04)",
|
||||||
'0px 32px 64px -12px rgba(10, 13, 18, 0.14), 0px 5px 5px -2.5px rgba(10, 13, 18, 0.04)',
|
"0px 32px 64px -12px rgba(10, 13, 18, 0.14), 0px 5px 5px -2.5px rgba(10, 13, 18, 0.04)",
|
||||||
] as Shadows,
|
] as Shadows,
|
||||||
components: componentThemes,
|
components: componentThemes,
|
||||||
});
|
});
|
||||||
|
@ -35,6 +35,7 @@ import {
|
|||||||
} from './rest/settingConfigAPI';
|
} from './rest/settingConfigAPI';
|
||||||
import { getBasePath } from './utils/HistoryUtils';
|
import { getBasePath } from './utils/HistoryUtils';
|
||||||
|
|
||||||
|
import GlobalStyles from '@mui/material/GlobalStyles';
|
||||||
import { ThemeProvider } from '@mui/material/styles';
|
import { ThemeProvider } from '@mui/material/styles';
|
||||||
import {
|
import {
|
||||||
createMuiTheme,
|
createMuiTheme,
|
||||||
@ -106,6 +107,7 @@ const App: FC = () => {
|
|||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<AntDConfigProvider>
|
<AntDConfigProvider>
|
||||||
<ThemeProvider theme={muiTheme}>
|
<ThemeProvider theme={muiTheme}>
|
||||||
|
<GlobalStyles styles={{ html: { fontSize: '14px' } }} />
|
||||||
<SnackbarProvider
|
<SnackbarProvider
|
||||||
Components={{
|
Components={{
|
||||||
default: SnackbarContent,
|
default: SnackbarContent,
|
||||||
|
@ -0,0 +1,581 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 Collate.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
ArrowBack as ArrowBackIcon,
|
||||||
|
KeyboardArrowDown as ArrowDownIcon,
|
||||||
|
KeyboardArrowUp as ArrowUpIcon,
|
||||||
|
Check as CheckIcon,
|
||||||
|
Close as CloseIcon,
|
||||||
|
} from '@mui/icons-material';
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
Box,
|
||||||
|
Chip,
|
||||||
|
IconButton,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
ListItemAvatar,
|
||||||
|
ListItemButton,
|
||||||
|
ListItemText,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
Popover,
|
||||||
|
TextField,
|
||||||
|
Typography,
|
||||||
|
} from '@mui/material';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import { debounce, isEmpty, startCase } from 'lodash';
|
||||||
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { EntityType } from '../../../../enums/entity.enum';
|
||||||
|
import { CreateTestCaseResolutionStatus } from '../../../../generated/api/tests/createTestCaseResolutionStatus';
|
||||||
|
import {
|
||||||
|
EntityReference,
|
||||||
|
TestCaseFailureReasonType,
|
||||||
|
TestCaseResolutionStatusTypes,
|
||||||
|
} from '../../../../generated/tests/testCaseResolutionStatus';
|
||||||
|
import { useApplicationStore } from '../../../../hooks/useApplicationStore';
|
||||||
|
import { Option } from '../../../../pages/TasksPage/TasksPage.interface';
|
||||||
|
import { postTestCaseIncidentStatus } from '../../../../rest/incidentManagerAPI';
|
||||||
|
import { getUserAndTeamSearch } from '../../../../rest/miscAPI';
|
||||||
|
import {
|
||||||
|
getEntityName,
|
||||||
|
getEntityReferenceFromEntity,
|
||||||
|
} from '../../../../utils/EntityUtils';
|
||||||
|
import { showErrorToast } from '../../../../utils/ToastUtils';
|
||||||
|
import { TestCaseStatusIncidentManagerProps } from './TestCaseIncidentManagerStatus.interface';
|
||||||
|
|
||||||
|
interface InlineTestCaseIncidentStatusProps {
|
||||||
|
data: TestCaseStatusIncidentManagerProps['data'];
|
||||||
|
hasEditPermission: boolean;
|
||||||
|
onSubmit: TestCaseStatusIncidentManagerProps['onSubmit'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const STATUS_COLORS: Record<
|
||||||
|
string,
|
||||||
|
{ bg: string; color: string; border: string }
|
||||||
|
> = {
|
||||||
|
New: { bg: '#E1D3FF', color: '#7147E8', border: '#7147E8' },
|
||||||
|
Ack: { bg: '#EBF6FE', color: '#3DA2F3', border: '#3DA2F3' },
|
||||||
|
Assigned: { bg: '#FFF6E1', color: '#D99601', border: '#D99601' },
|
||||||
|
Resolved: { bg: '#E8F5E9', color: '#4CAF50', border: '#81C784' },
|
||||||
|
};
|
||||||
|
|
||||||
|
const InlineTestCaseIncidentStatus = ({
|
||||||
|
data,
|
||||||
|
hasEditPermission,
|
||||||
|
onSubmit,
|
||||||
|
}: InlineTestCaseIncidentStatusProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { currentUser } = useApplicationStore();
|
||||||
|
const chipRef = React.useRef<HTMLDivElement>(null);
|
||||||
|
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
|
||||||
|
const [showStatusMenu, setShowStatusMenu] = useState(false);
|
||||||
|
const [showAssigneePopover, setShowAssigneePopover] = useState(false);
|
||||||
|
const [showResolvedPopover, setShowResolvedPopover] = useState(false);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [userOptions, setUserOptions] = useState<Option[]>([]);
|
||||||
|
const [selectedAssignee, setSelectedAssignee] =
|
||||||
|
useState<EntityReference | null>(
|
||||||
|
data?.testCaseResolutionStatusDetails?.assignee ?? null
|
||||||
|
);
|
||||||
|
const [selectedReason, setSelectedReason] =
|
||||||
|
useState<TestCaseFailureReasonType | null>(null);
|
||||||
|
const [comment, setComment] = useState('');
|
||||||
|
|
||||||
|
const statusType = data.testCaseResolutionStatusType;
|
||||||
|
|
||||||
|
const initialOptions = useMemo(() => {
|
||||||
|
const assignee = data?.testCaseResolutionStatusDetails?.assignee;
|
||||||
|
if (assignee) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: getEntityName(assignee),
|
||||||
|
value: assignee.id || '',
|
||||||
|
type: assignee.type,
|
||||||
|
name: assignee.name,
|
||||||
|
displayName: assignee.displayName,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}, [data?.testCaseResolutionStatusDetails?.assignee]);
|
||||||
|
|
||||||
|
const searchUsers = useCallback(
|
||||||
|
async (query: string) => {
|
||||||
|
try {
|
||||||
|
const res = await getUserAndTeamSearch(query, true);
|
||||||
|
const hits = res.data.hits.hits;
|
||||||
|
const suggestOptions: Option[] = hits.map((hit) => ({
|
||||||
|
label: getEntityName(hit._source),
|
||||||
|
value: hit._id ?? '',
|
||||||
|
type: hit._source.entityType,
|
||||||
|
name: hit._source.name,
|
||||||
|
displayName: hit._source.displayName,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// If there's an assigned user and it's not in the results, add it at the top
|
||||||
|
if (initialOptions.length > 0) {
|
||||||
|
const assigneeId = initialOptions[0].value;
|
||||||
|
const isAssigneeInResults = suggestOptions.some(
|
||||||
|
(opt) => opt.value === assigneeId
|
||||||
|
);
|
||||||
|
if (!isAssigneeInResults) {
|
||||||
|
setUserOptions([initialOptions[0], ...suggestOptions]);
|
||||||
|
} else {
|
||||||
|
// Move assignee to top
|
||||||
|
const filteredOptions = suggestOptions.filter(
|
||||||
|
(opt) => opt.value !== assigneeId
|
||||||
|
);
|
||||||
|
setUserOptions([initialOptions[0], ...filteredOptions]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setUserOptions(suggestOptions);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
showErrorToast(err as AxiosError);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[initialOptions]
|
||||||
|
);
|
||||||
|
|
||||||
|
const debouncedSearch = useMemo(
|
||||||
|
() => debounce(searchUsers, 300),
|
||||||
|
[searchUsers]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSearchUsers = useCallback(
|
||||||
|
(query: string) => {
|
||||||
|
if (isEmpty(query)) {
|
||||||
|
// When search is cleared, trigger search with empty query to get default results
|
||||||
|
searchUsers('');
|
||||||
|
} else {
|
||||||
|
debouncedSearch(query);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[debouncedSearch, searchUsers]
|
||||||
|
);
|
||||||
|
|
||||||
|
const submitStatusChange = useCallback(
|
||||||
|
async (
|
||||||
|
status: TestCaseResolutionStatusTypes,
|
||||||
|
additionalData?: {
|
||||||
|
assignee?: EntityReference;
|
||||||
|
reason?: TestCaseFailureReasonType;
|
||||||
|
comment?: string;
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
setIsLoading(true);
|
||||||
|
const updatedData: CreateTestCaseResolutionStatus = {
|
||||||
|
testCaseResolutionStatusType: status,
|
||||||
|
testCaseReference: data.testCaseReference?.fullyQualifiedName ?? '',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
status === TestCaseResolutionStatusTypes.Assigned &&
|
||||||
|
additionalData?.assignee
|
||||||
|
) {
|
||||||
|
updatedData.testCaseResolutionStatusDetails = {
|
||||||
|
assignee: {
|
||||||
|
name: additionalData.assignee.name,
|
||||||
|
displayName: additionalData.assignee.displayName,
|
||||||
|
id: additionalData.assignee.id,
|
||||||
|
type: EntityType.USER,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (status === TestCaseResolutionStatusTypes.Resolved) {
|
||||||
|
updatedData.testCaseResolutionStatusDetails = {
|
||||||
|
testCaseFailureReason: additionalData?.reason,
|
||||||
|
testCaseFailureComment: additionalData?.comment ?? '',
|
||||||
|
resolvedBy: currentUser
|
||||||
|
? getEntityReferenceFromEntity(currentUser, EntityType.USER)
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const responseData = await postTestCaseIncidentStatus(updatedData);
|
||||||
|
onSubmit(responseData);
|
||||||
|
setShowAssigneePopover(false);
|
||||||
|
setShowResolvedPopover(false);
|
||||||
|
setSelectedAssignee(null);
|
||||||
|
setSelectedReason(null);
|
||||||
|
setComment('');
|
||||||
|
} catch (error) {
|
||||||
|
showErrorToast(error as AxiosError);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[currentUser, data.testCaseReference?.fullyQualifiedName, onSubmit]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleStatusClick = (event: React.MouseEvent<HTMLElement>) => {
|
||||||
|
if (!hasEditPermission) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (chipRef.current) {
|
||||||
|
setAnchorEl(chipRef.current);
|
||||||
|
|
||||||
|
// Open directly to the current status detail screen
|
||||||
|
if (statusType === TestCaseResolutionStatusTypes.Assigned) {
|
||||||
|
// Load initial user list with empty search
|
||||||
|
searchUsers('');
|
||||||
|
setShowAssigneePopover(true);
|
||||||
|
} else if (statusType === TestCaseResolutionStatusTypes.Resolved) {
|
||||||
|
// Pre-populate with existing values
|
||||||
|
setSelectedReason(
|
||||||
|
data?.testCaseResolutionStatusDetails?.testCaseFailureReason ?? null
|
||||||
|
);
|
||||||
|
setComment(
|
||||||
|
data?.testCaseResolutionStatusDetails?.testCaseFailureComment ?? ''
|
||||||
|
);
|
||||||
|
setShowResolvedPopover(true);
|
||||||
|
} else {
|
||||||
|
// For New/Ack, show the status menu
|
||||||
|
setShowStatusMenu(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseStatusMenu = useCallback(() => {
|
||||||
|
setShowStatusMenu(false);
|
||||||
|
setAnchorEl(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleStatusChange = useCallback(
|
||||||
|
async (newStatus: TestCaseResolutionStatusTypes) => {
|
||||||
|
setShowStatusMenu(false);
|
||||||
|
|
||||||
|
if (newStatus === TestCaseResolutionStatusTypes.Assigned) {
|
||||||
|
// Load initial user list with empty search
|
||||||
|
searchUsers('');
|
||||||
|
setShowAssigneePopover(true);
|
||||||
|
} else if (newStatus === TestCaseResolutionStatusTypes.Resolved) {
|
||||||
|
setShowResolvedPopover(true);
|
||||||
|
} else {
|
||||||
|
setAnchorEl(null);
|
||||||
|
await submitStatusChange(newStatus);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[searchUsers, submitStatusChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleBackToStatusMenu = useCallback(() => {
|
||||||
|
setShowAssigneePopover(false);
|
||||||
|
setShowResolvedPopover(false);
|
||||||
|
setSelectedAssignee(
|
||||||
|
data?.testCaseResolutionStatusDetails?.assignee ?? null
|
||||||
|
);
|
||||||
|
setUserOptions([]);
|
||||||
|
setSelectedReason(null);
|
||||||
|
setComment('');
|
||||||
|
setShowStatusMenu(true);
|
||||||
|
}, [data?.testCaseResolutionStatusDetails?.assignee]);
|
||||||
|
|
||||||
|
const handleCloseAllPopovers = useCallback(() => {
|
||||||
|
setShowAssigneePopover(false);
|
||||||
|
setShowResolvedPopover(false);
|
||||||
|
setShowStatusMenu(false);
|
||||||
|
setAnchorEl(null);
|
||||||
|
setSelectedAssignee(
|
||||||
|
data?.testCaseResolutionStatusDetails?.assignee ?? null
|
||||||
|
);
|
||||||
|
setUserOptions([]);
|
||||||
|
setSelectedReason(null);
|
||||||
|
setComment('');
|
||||||
|
}, [data?.testCaseResolutionStatusDetails?.assignee]);
|
||||||
|
|
||||||
|
const handleAssigneeSelect = (user: EntityReference) => {
|
||||||
|
setSelectedAssignee(user);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAssigneeSubmit = () => {
|
||||||
|
if (selectedAssignee) {
|
||||||
|
submitStatusChange(TestCaseResolutionStatusTypes.Assigned, {
|
||||||
|
assignee: selectedAssignee,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResolvedSubmit = () => {
|
||||||
|
if (selectedReason && comment) {
|
||||||
|
submitStatusChange(TestCaseResolutionStatusTypes.Resolved, {
|
||||||
|
reason: selectedReason,
|
||||||
|
comment,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getInitials = (name: string) => {
|
||||||
|
return name
|
||||||
|
.split(' ')
|
||||||
|
.map((n) => n[0])
|
||||||
|
.join('')
|
||||||
|
.toUpperCase()
|
||||||
|
.slice(0, 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
const statusColor = STATUS_COLORS[statusType] || STATUS_COLORS.New;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box ref={chipRef} sx={{ display: 'inline-flex', alignItems: 'center' }}>
|
||||||
|
<Chip
|
||||||
|
deleteIcon={
|
||||||
|
hasEditPermission ? (
|
||||||
|
showStatusMenu || showAssigneePopover || showResolvedPopover ? (
|
||||||
|
<ArrowUpIcon />
|
||||||
|
) : (
|
||||||
|
<ArrowDownIcon />
|
||||||
|
)
|
||||||
|
) : undefined
|
||||||
|
}
|
||||||
|
disabled={!hasEditPermission}
|
||||||
|
label={statusType}
|
||||||
|
sx={{
|
||||||
|
px: 1,
|
||||||
|
backgroundColor: statusColor.bg,
|
||||||
|
color: statusColor.color,
|
||||||
|
border: `1px solid ${statusColor.border}`,
|
||||||
|
borderRadius: '16px',
|
||||||
|
fontWeight: 500,
|
||||||
|
fontSize: '12px',
|
||||||
|
cursor: hasEditPermission ? 'pointer' : 'default',
|
||||||
|
'& .MuiChip-label': {
|
||||||
|
px: 1,
|
||||||
|
},
|
||||||
|
'& .MuiChip-deleteIcon': {
|
||||||
|
color: statusColor.color,
|
||||||
|
fontSize: '16px',
|
||||||
|
margin: '0 4px 0 -4px',
|
||||||
|
},
|
||||||
|
'&:hover': hasEditPermission
|
||||||
|
? {
|
||||||
|
backgroundColor: statusColor.bg,
|
||||||
|
opacity: 0.8,
|
||||||
|
}
|
||||||
|
: {},
|
||||||
|
}}
|
||||||
|
onClick={handleStatusClick}
|
||||||
|
onDelete={handleStatusClick}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Menu
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: 'bottom',
|
||||||
|
horizontal: 'left',
|
||||||
|
}}
|
||||||
|
open={showStatusMenu}
|
||||||
|
transformOrigin={{
|
||||||
|
vertical: 'top',
|
||||||
|
horizontal: 'left',
|
||||||
|
}}
|
||||||
|
onClose={handleCloseStatusMenu}>
|
||||||
|
{Object.values(TestCaseResolutionStatusTypes).map((status) => (
|
||||||
|
<MenuItem
|
||||||
|
key={status}
|
||||||
|
selected={status === statusType}
|
||||||
|
sx={{
|
||||||
|
minWidth: 150,
|
||||||
|
fontWeight: status === statusType ? 600 : 400,
|
||||||
|
}}
|
||||||
|
onClick={() => handleStatusChange(status)}>
|
||||||
|
{status}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
|
||||||
|
<Popover
|
||||||
|
PaperProps={{
|
||||||
|
sx: { width: 400, maxHeight: 500 },
|
||||||
|
}}
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: 'bottom',
|
||||||
|
horizontal: 'left',
|
||||||
|
}}
|
||||||
|
open={showAssigneePopover}
|
||||||
|
transformOrigin={{
|
||||||
|
vertical: 'top',
|
||||||
|
horizontal: 'left',
|
||||||
|
}}
|
||||||
|
onClose={handleCloseAllPopovers}>
|
||||||
|
<Box sx={{ p: 2 }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
mb: 2,
|
||||||
|
gap: 1,
|
||||||
|
}}>
|
||||||
|
<IconButton size="small" onClick={handleBackToStatusMenu}>
|
||||||
|
<ArrowBackIcon />
|
||||||
|
</IconButton>
|
||||||
|
<Typography sx={{ fontWeight: 600, fontSize: 16 }}>
|
||||||
|
{t('label.assigned')}
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ flex: 1 }} />
|
||||||
|
<IconButton size="small" onClick={handleCloseAllPopovers}>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
color="primary"
|
||||||
|
disabled={!selectedAssignee || isLoading}
|
||||||
|
size="small"
|
||||||
|
onClick={handleAssigneeSubmit}>
|
||||||
|
<CheckIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
placeholder={t('label.search')}
|
||||||
|
size="small"
|
||||||
|
sx={{ mb: 2 }}
|
||||||
|
onChange={(e) => handleSearchUsers(e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<List sx={{ maxHeight: 350, overflow: 'auto' }}>
|
||||||
|
{userOptions.map((option) => {
|
||||||
|
const user: EntityReference = {
|
||||||
|
id: option.value,
|
||||||
|
name: option.name,
|
||||||
|
displayName: option.displayName,
|
||||||
|
type: option.type || EntityType.USER,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ListItem disablePadding key={option.value}>
|
||||||
|
<ListItemButton
|
||||||
|
selected={selectedAssignee?.id === option.value}
|
||||||
|
onClick={() => handleAssigneeSelect(user)}>
|
||||||
|
<ListItemAvatar>
|
||||||
|
<Avatar
|
||||||
|
sx={{
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
fontSize: 14,
|
||||||
|
bgcolor: '#FFE7BA',
|
||||||
|
color: '#000',
|
||||||
|
}}>
|
||||||
|
{getInitials(option.displayName || option.name || 'U')}
|
||||||
|
</Avatar>
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText
|
||||||
|
primary={option.label}
|
||||||
|
primaryTypographyProps={{
|
||||||
|
fontSize: 14,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</List>
|
||||||
|
</Box>
|
||||||
|
</Popover>
|
||||||
|
|
||||||
|
<Popover
|
||||||
|
PaperProps={{
|
||||||
|
sx: { width: 400 },
|
||||||
|
}}
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: 'bottom',
|
||||||
|
horizontal: 'left',
|
||||||
|
}}
|
||||||
|
open={showResolvedPopover}
|
||||||
|
transformOrigin={{
|
||||||
|
vertical: 'top',
|
||||||
|
horizontal: 'left',
|
||||||
|
}}
|
||||||
|
onClose={handleCloseAllPopovers}>
|
||||||
|
<Box sx={{ p: 2 }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
mb: 2,
|
||||||
|
gap: 1,
|
||||||
|
}}>
|
||||||
|
<IconButton size="small" onClick={handleBackToStatusMenu}>
|
||||||
|
<ArrowBackIcon />
|
||||||
|
</IconButton>
|
||||||
|
<Typography sx={{ fontWeight: 600, fontSize: 16 }}>
|
||||||
|
{t('label.resolved')}
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ flex: 1 }} />
|
||||||
|
<IconButton size="small" onClick={handleCloseAllPopovers}>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
color="primary"
|
||||||
|
disabled={!selectedReason || !comment || isLoading}
|
||||||
|
size="small"
|
||||||
|
onClick={handleResolvedSubmit}>
|
||||||
|
<CheckIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 500,
|
||||||
|
mb: 1,
|
||||||
|
'&::after': { content: '" *"', color: 'error.main' },
|
||||||
|
}}>
|
||||||
|
{t('label.reason')}
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, mb: 2 }}>
|
||||||
|
{Object.values(TestCaseFailureReasonType).map((reason) => (
|
||||||
|
<Chip
|
||||||
|
color={selectedReason === reason ? 'primary' : 'default'}
|
||||||
|
key={reason}
|
||||||
|
label={startCase(reason)}
|
||||||
|
sx={{ cursor: 'pointer' }}
|
||||||
|
variant={selectedReason === reason ? 'filled' : 'outlined'}
|
||||||
|
onClick={() => setSelectedReason(reason)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 500,
|
||||||
|
mb: 1,
|
||||||
|
'&::after': { content: '" *"', color: 'error.main' },
|
||||||
|
}}>
|
||||||
|
{t('label.comment')}
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
multiline
|
||||||
|
placeholder="Enter your comment"
|
||||||
|
rows={4}
|
||||||
|
value={comment}
|
||||||
|
onChange={(e) => setComment(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Popover>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InlineTestCaseIncidentStatus;
|
@ -27,13 +27,16 @@ import AppBadge from '../../../common/Badge/Badge.component';
|
|||||||
import { EditIconButton } from '../../../common/IconButtons/EditIconButton';
|
import { EditIconButton } from '../../../common/IconButtons/EditIconButton';
|
||||||
import { TestCaseStatusModal } from '../../TestCaseStatusModal/TestCaseStatusModal.component';
|
import { TestCaseStatusModal } from '../../TestCaseStatusModal/TestCaseStatusModal.component';
|
||||||
import '../incident-manager.style.less';
|
import '../incident-manager.style.less';
|
||||||
|
import InlineTestCaseIncidentStatus from './InlineTestCaseIncidentStatus.component';
|
||||||
import { TestCaseStatusIncidentManagerProps } from './TestCaseIncidentManagerStatus.interface';
|
import { TestCaseStatusIncidentManagerProps } from './TestCaseIncidentManagerStatus.interface';
|
||||||
|
|
||||||
const TestCaseIncidentManagerStatus = ({
|
const TestCaseIncidentManagerStatus = ({
|
||||||
data,
|
data,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
hasPermission,
|
hasPermission,
|
||||||
newLook = false,
|
newLook = false,
|
||||||
headerName,
|
headerName,
|
||||||
|
isInline = false,
|
||||||
}: TestCaseStatusIncidentManagerProps) => {
|
}: TestCaseStatusIncidentManagerProps) => {
|
||||||
const [isEditStatus, setIsEditStatus] = useState<boolean>(false);
|
const [isEditStatus, setIsEditStatus] = useState<boolean>(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -107,6 +110,16 @@ const TestCaseIncidentManagerStatus = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isInline) {
|
||||||
|
return (
|
||||||
|
<InlineTestCaseIncidentStatus
|
||||||
|
data={data}
|
||||||
|
hasEditPermission={hasEditPermission}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Space
|
<Space
|
||||||
|
@ -19,4 +19,5 @@ export interface TestCaseStatusIncidentManagerProps {
|
|||||||
hasPermission?: boolean;
|
hasPermission?: boolean;
|
||||||
newLook?: boolean;
|
newLook?: boolean;
|
||||||
headerName?: string;
|
headerName?: string;
|
||||||
|
isInline?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { Typography as MuiTypography } from '@mui/material';
|
||||||
import { Col, Row, Skeleton, Typography } from 'antd';
|
import { Col, Row, Skeleton, Typography } from 'antd';
|
||||||
import { ColumnsType, TablePaginationConfig } from 'antd/lib/table';
|
import { ColumnsType, TablePaginationConfig } from 'antd/lib/table';
|
||||||
import { FilterValue, SorterResult } from 'antd/lib/table/interface';
|
import { FilterValue, SorterResult } from 'antd/lib/table/interface';
|
||||||
@ -173,6 +174,39 @@ const DataQualityTab: React.FC<DataQualityTabProps> = ({
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Failed/aborted Reason',
|
||||||
|
dataIndex: 'testCaseResult',
|
||||||
|
key: 'Reason',
|
||||||
|
width: 200,
|
||||||
|
render: (result: TestCaseResult) => {
|
||||||
|
return result?.result ? (
|
||||||
|
<MuiTypography
|
||||||
|
sx={{
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
display: '-webkit-box',
|
||||||
|
WebkitLineClamp: 2,
|
||||||
|
WebkitBoxOrient: 'vertical',
|
||||||
|
}}>
|
||||||
|
{result.result}
|
||||||
|
</MuiTypography>
|
||||||
|
) : (
|
||||||
|
'--'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('label.last-run'),
|
||||||
|
dataIndex: 'testCaseResult',
|
||||||
|
key: 'lastRun',
|
||||||
|
width: 150,
|
||||||
|
sorter: true,
|
||||||
|
render: (result: TestCaseResult) => {
|
||||||
|
return <DateTimeDisplay timestamp={result?.timestamp} />;
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: t('label.name'),
|
title: t('label.name'),
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
@ -244,7 +278,7 @@ const DataQualityTab: React.FC<DataQualityTabProps> = ({
|
|||||||
title: t('label.column'),
|
title: t('label.column'),
|
||||||
dataIndex: 'entityLink',
|
dataIndex: 'entityLink',
|
||||||
key: 'column',
|
key: 'column',
|
||||||
width: 150,
|
width: 120,
|
||||||
render: (entityLink) => {
|
render: (entityLink) => {
|
||||||
const isColumn = entityLink.includes('::columns::');
|
const isColumn = entityLink.includes('::columns::');
|
||||||
if (isColumn) {
|
if (isColumn) {
|
||||||
@ -256,7 +290,7 @@ const DataQualityTab: React.FC<DataQualityTabProps> = ({
|
|||||||
<Typography.Paragraph
|
<Typography.Paragraph
|
||||||
className="m-0"
|
className="m-0"
|
||||||
data-testid={name}
|
data-testid={name}
|
||||||
style={{ maxWidth: 150 }}>
|
style={{ maxWidth: 120 }}>
|
||||||
{name}
|
{name}
|
||||||
</Typography.Paragraph>
|
</Typography.Paragraph>
|
||||||
);
|
);
|
||||||
@ -279,13 +313,41 @@ const DataQualityTab: React.FC<DataQualityTabProps> = ({
|
|||||||
sortDirections: ['ascend', 'descend'],
|
sortDirections: ['ascend', 'descend'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('label.last-run'),
|
title: t('label.incident'),
|
||||||
dataIndex: 'testCaseResult',
|
dataIndex: 'testCaseResult',
|
||||||
key: 'lastRun',
|
key: 'incident',
|
||||||
width: 150,
|
width: 120,
|
||||||
sorter: true,
|
render: (_, record) => {
|
||||||
render: (result: TestCaseResult) => {
|
const testCaseResult = testCaseStatus.find(
|
||||||
return <DateTimeDisplay timestamp={result?.timestamp} />;
|
(status) =>
|
||||||
|
status.testCaseReference?.fullyQualifiedName ===
|
||||||
|
record.fullyQualifiedName
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isStatusLoading) {
|
||||||
|
return <Skeleton.Input size="small" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!testCaseResult) {
|
||||||
|
return '--';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user has permission to edit incident status
|
||||||
|
const testCasePermission = testCasePermissions.find(
|
||||||
|
(permission) =>
|
||||||
|
permission.fullyQualifiedName === record.fullyQualifiedName
|
||||||
|
);
|
||||||
|
const hasEditPermission =
|
||||||
|
isEditAllowed || testCasePermission?.EditAll;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TestCaseIncidentManagerStatus
|
||||||
|
isInline
|
||||||
|
data={testCaseResult}
|
||||||
|
hasPermission={hasEditPermission}
|
||||||
|
onSubmit={handleStatusSubmit}
|
||||||
|
/>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import { Box, Grid, Stack, Tab, Tabs, useTheme } from '@mui/material';
|
import { Box, Grid, Stack, Tab, Tabs, useTheme } from '@mui/material';
|
||||||
import { Col, Form, Row, Select, Space } from 'antd';
|
import { Form, Select, Space } from 'antd';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import QueryString from 'qs';
|
import QueryString from 'qs';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
@ -45,7 +45,6 @@ import {
|
|||||||
import { getPrioritizedEditPermission } from '../../../../../utils/PermissionsUtils';
|
import { getPrioritizedEditPermission } from '../../../../../utils/PermissionsUtils';
|
||||||
import { getEntityDetailsPath } from '../../../../../utils/RouterUtils';
|
import { getEntityDetailsPath } from '../../../../../utils/RouterUtils';
|
||||||
import ErrorPlaceHolder from '../../../../common/ErrorWithPlaceholder/ErrorPlaceHolder';
|
import ErrorPlaceHolder from '../../../../common/ErrorWithPlaceholder/ErrorPlaceHolder';
|
||||||
import NextPrevious from '../../../../common/NextPrevious/NextPrevious';
|
|
||||||
import { NextPreviousProps } from '../../../../common/NextPrevious/NextPrevious.interface';
|
import { NextPreviousProps } from '../../../../common/NextPrevious/NextPrevious.interface';
|
||||||
import Searchbar from '../../../../common/SearchBarComponent/SearchBar.component';
|
import Searchbar from '../../../../common/SearchBarComponent/SearchBar.component';
|
||||||
import SummaryCardV1 from '../../../../common/SummaryCard/SummaryCardV1';
|
import SummaryCardV1 from '../../../../common/SummaryCard/SummaryCardV1';
|
||||||
@ -75,7 +74,6 @@ export const QualityTab = () => {
|
|||||||
paging,
|
paging,
|
||||||
handlePageChange,
|
handlePageChange,
|
||||||
handlePageSizeChange,
|
handlePageSizeChange,
|
||||||
showPagination,
|
|
||||||
} = testCasePaging;
|
} = testCasePaging;
|
||||||
|
|
||||||
const { editTest } = useMemo(() => {
|
const { editTest } = useMemo(() => {
|
||||||
@ -282,6 +280,25 @@ export const QualityTab = () => {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const pagingData = useMemo(() => {
|
||||||
|
return {
|
||||||
|
isNumberBased: true,
|
||||||
|
currentPage,
|
||||||
|
isLoading: isTestsLoading,
|
||||||
|
pageSize,
|
||||||
|
paging,
|
||||||
|
pagingHandler: handleTestCasePageChange,
|
||||||
|
onShowSizeChange: handlePageSizeChange,
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
currentPage,
|
||||||
|
isTestsLoading,
|
||||||
|
pageSize,
|
||||||
|
paging,
|
||||||
|
handleTestCasePageChange,
|
||||||
|
handlePageSizeChange,
|
||||||
|
]);
|
||||||
|
|
||||||
const handleTabChange = (_: React.SyntheticEvent, tab: string) => {
|
const handleTabChange = (_: React.SyntheticEvent, tab: string) => {
|
||||||
navigate(
|
navigate(
|
||||||
{
|
{
|
||||||
@ -405,39 +422,23 @@ export const QualityTab = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{isTestCaseTab && (
|
{isTestCaseTab && (
|
||||||
<Row>
|
<DataQualityTab
|
||||||
<Col span={24}>
|
removeTableBorder
|
||||||
<DataQualityTab
|
afterDeleteAction={async (...params) => {
|
||||||
removeTableBorder
|
await fetchAllTests(...params); // Update current count when Create / Delete operation performed
|
||||||
afterDeleteAction={async (...params) => {
|
params?.length &&
|
||||||
await fetchAllTests(...params); // Update current count when Create / Delete operation performed
|
(await getResourceLimit('dataQuality', true, true));
|
||||||
params?.length &&
|
}}
|
||||||
(await getResourceLimit('dataQuality', true, true));
|
breadcrumbData={tableBreadcrumb}
|
||||||
}}
|
fetchTestCases={handleSortTestCase}
|
||||||
breadcrumbData={tableBreadcrumb}
|
isEditAllowed={editTest}
|
||||||
fetchTestCases={handleSortTestCase}
|
isLoading={isTestsLoading}
|
||||||
isEditAllowed={editTest}
|
pagingData={pagingData}
|
||||||
isLoading={isTestsLoading}
|
showTableColumn={false}
|
||||||
showTableColumn={false}
|
testCases={allTestCases}
|
||||||
testCases={allTestCases}
|
onTestCaseResultUpdate={onTestCaseUpdate}
|
||||||
onTestCaseResultUpdate={onTestCaseUpdate}
|
onTestUpdate={onTestCaseUpdate}
|
||||||
onTestUpdate={onTestCaseUpdate}
|
/>
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
<Col span={24}>
|
|
||||||
{showPagination && (
|
|
||||||
<NextPrevious
|
|
||||||
isNumberBased
|
|
||||||
currentPage={currentPage}
|
|
||||||
isLoading={isTestsLoading}
|
|
||||||
pageSize={pageSize}
|
|
||||||
paging={paging}
|
|
||||||
pagingHandler={handleTestCasePageChange}
|
|
||||||
onShowSizeChange={handlePageSizeChange}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{qualityTab === EntityTabs.PIPELINE && (
|
{qualityTab === EntityTabs.PIPELINE && (
|
||||||
|
Loading…
x
Reference in New Issue
Block a user