Feat: Support for column customization for Glossary term table (#18584)

This commit is contained in:
Shrushti Polekar 2024-11-14 10:22:11 +05:30 committed by GitHub
parent fda0e542e4
commit 626464f443
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 907 additions and 17 deletions

View File

@ -43,18 +43,27 @@ import {
approveTagsTask,
assignTagToGlossaryTerm,
changeTermHierarchyFromModal,
clickSaveButton,
confirmationDragAndDropGlossary,
createDescriptionTaskForGlossary,
createGlossary,
createGlossaryTerms,
createTagTaskForGlossary,
deleteGlossaryOrGlossaryTerm,
deselectColumns,
dragAndDropColumn,
dragAndDropTerm,
filterStatus,
goToAssetsTab,
openColumnDropdown,
renameGlossaryTerm,
selectActiveGlossary,
selectActiveGlossaryTerm,
selectColumns,
toggleAllColumnsSelection,
validateGlossaryTerm,
verifyAllColumns,
verifyColumnsVisibility,
verifyGlossaryDetails,
verifyGlossaryTermAssets,
} from '../../utils/glossary';
@ -924,6 +933,141 @@ test.describe('Glossary tests', () => {
}
});
test('Column selection and visibility for Glossary Terms table', async ({
browser,
}) => {
const { page, afterAction, apiContext } = await performAdminLogin(browser);
const glossary1 = new Glossary();
const glossaryTerm1 = new GlossaryTerm(glossary1);
const glossaryTerm2 = new GlossaryTerm(glossary1);
glossary1.data.terms = [glossaryTerm1, glossaryTerm2];
try {
await glossary1.create(apiContext);
await glossaryTerm1.create(apiContext);
await glossaryTerm2.create(apiContext);
await sidebarClick(page, SidebarItem.GLOSSARY);
await selectActiveGlossary(page, glossary1.data.displayName);
await test.step(
'Open column dropdown and select columns and check if they are visible',
async () => {
await openColumnDropdown(page);
const checkboxLabels = ['Reviewer', 'Synonyms'];
await selectColumns(page, checkboxLabels);
await clickSaveButton(page);
await verifyColumnsVisibility(page, checkboxLabels, true);
}
);
await test.step(
'Open column dropdown and deselect columns and check if they are hidden',
async () => {
await openColumnDropdown(page);
const checkboxLabels = ['Reviewer', 'Owners'];
await deselectColumns(page, checkboxLabels);
await clickSaveButton(page);
await verifyColumnsVisibility(page, checkboxLabels, false);
}
);
await test.step('All columns selection', async () => {
await toggleAllColumnsSelection(page, true);
const tableColumns = [
'TERMS',
'DESCRIPTION',
'REVIEWER',
'SYNONYMS',
'OWNERS',
'STATUS',
'ACTIONS',
];
await verifyAllColumns(page, tableColumns, true);
});
} finally {
await glossaryTerm1.delete(apiContext);
await glossaryTerm2.delete(apiContext);
await glossary1.delete(apiContext);
await afterAction();
}
});
test('Glossary Terms Table Status filtering', async ({ browser }) => {
const { page, afterAction, apiContext } = await performAdminLogin(browser);
const glossary1 = new Glossary();
const glossaryTerm1 = new GlossaryTerm(glossary1);
const glossaryTerm2 = new GlossaryTerm(glossary1);
glossary1.data.terms = [glossaryTerm1, glossaryTerm2];
try {
await glossary1.create(apiContext);
await glossaryTerm1.create(apiContext);
await glossaryTerm2.create(apiContext);
await sidebarClick(page, SidebarItem.GLOSSARY);
await selectActiveGlossary(page, glossary1.data.displayName);
await test.step(
'Deselect status and check if the table has filtered rows',
async () => {
await filterStatus(page, ['Draft'], ['Approved']);
}
);
await test.step(
'Re-select the status and check if it appears again',
async () => {
await filterStatus(page, ['Draft'], ['Draft', 'Approved']);
}
);
} finally {
await glossaryTerm1.delete(apiContext);
await glossaryTerm2.delete(apiContext);
await glossary1.delete(apiContext);
await afterAction();
}
});
test('Column dropdown drag-and-drop functionality for Glossary Terms table', async ({
browser,
}) => {
const { page, afterAction, apiContext } = await performAdminLogin(browser);
const glossary1 = new Glossary();
const glossaryTerm1 = new GlossaryTerm(glossary1);
const glossaryTerm2 = new GlossaryTerm(glossary1);
glossary1.data.terms = [glossaryTerm1, glossaryTerm2];
try {
await glossary1.create(apiContext);
await glossaryTerm1.create(apiContext);
await glossaryTerm2.create(apiContext);
await sidebarClick(page, SidebarItem.GLOSSARY);
await selectActiveGlossary(page, glossary1.data.displayName);
await openColumnDropdown(page);
const dragColumn = 'Owners';
const dropColumn = 'Status';
await dragAndDropColumn(page, dragColumn, dropColumn);
const saveButton = page.locator('.ant-btn-primary', {
hasText: 'Save',
});
await saveButton.click();
const columnHeaders = await page.locator('thead th');
const columnText = await columnHeaders.allTextContents();
expect(columnText).toEqual([
'Terms',
'Description',
'Status',
'Owners',
'Actions',
]);
} finally {
await glossaryTerm1.delete(apiContext);
await glossaryTerm2.delete(apiContext);
await glossary1.delete(apiContext);
await afterAction();
}
});
test.afterAll(async ({ browser }) => {
const { afterAction, apiContext } = await performAdminLogin(browser);
await user1.delete(apiContext);

View File

@ -1004,3 +1004,152 @@ export const approveTagsTask = async (
await expect(tagVisibility).toBe(true);
};
export async function openColumnDropdown(page: Page): Promise<void> {
const dropdownButton = page.getByTestId('glossary-column-dropdown');
await expect(dropdownButton).toBeVisible();
await dropdownButton.click();
}
export async function selectColumns(
page: Page,
checkboxLabels: string[]
): Promise<void> {
for (const label of checkboxLabels) {
const checkbox = page.locator('.glossary-dropdown-label', {
hasText: label,
});
await checkbox.click();
}
}
export async function deselectColumns(
page: Page,
checkboxLabels: string[]
): Promise<void> {
for (const label of checkboxLabels) {
const checkbox = page.locator('.glossary-dropdown-label', {
hasText: label,
});
await checkbox.click();
}
}
export async function clickSaveButton(page: Page): Promise<void> {
const saveButton = page.locator('.ant-btn-primary', {
hasText: 'Save',
});
await saveButton.click();
}
export async function verifyColumnsVisibility(
page: Page,
checkboxLabels: string[],
shouldBeVisible: boolean
): Promise<void> {
const glossaryTermsTable = page.getByTestId('glossary-terms-table');
await expect(glossaryTermsTable).toBeVisible();
for (const label of checkboxLabels) {
const termsColumnHeader = glossaryTermsTable.locator('th', {
hasText: label,
});
if (shouldBeVisible) {
await expect(termsColumnHeader).toBeVisible();
} else {
await expect(termsColumnHeader).toBeHidden();
}
}
}
export async function toggleAllColumnsSelection(
page: Page,
isSelected: boolean
): Promise<void> {
const dropdownButton = page.getByTestId('glossary-column-dropdown');
await expect(dropdownButton).toBeVisible();
await dropdownButton.click();
const checkboxLabel = 'All';
const checkbox = page.locator('.custom-glossary-col-sel-checkbox', {
hasText: checkboxLabel,
});
if (isSelected) {
await checkbox.click();
}
await clickSaveButton(page);
}
export async function verifyAllColumns(
page: Page,
tableColumns: string[],
shouldBeVisible: boolean
): Promise<void> {
const glossaryTermsTable = page.getByTestId('glossary-terms-table');
await expect(glossaryTermsTable).toBeVisible();
for (const columnHeader of tableColumns) {
const termsColumnHeader = glossaryTermsTable.locator('th', {
hasText: columnHeader,
});
if (shouldBeVisible) {
await expect(termsColumnHeader).toBeVisible();
} else {
if (columnHeader !== 'TERMS') {
await expect(termsColumnHeader).not.toBeVisible();
} else {
await expect(termsColumnHeader).toBeVisible();
}
}
}
}
export const filterStatus = async (
page: Page,
statusLabels: string[],
expectedStatus: string[]
): Promise<void> => {
const dropdownButton = page.getByTestId('glossary-status-dropdown');
await dropdownButton.click();
for (const label of statusLabels) {
const checkbox = page.locator('.glossary-dropdown-label', {
hasText: label,
});
await checkbox.click();
}
const saveButton = page.locator('.ant-btn-primary', {
hasText: 'Save',
});
await saveButton.click();
const glossaryTermsTable = page.getByTestId('glossary-terms-table');
const rows = glossaryTermsTable.locator('tbody tr');
const statusColumnIndex = 3;
for (let i = 0; i < (await rows.count()); i++) {
const statusCell = rows
.nth(i)
.locator(`td:nth-child(${statusColumnIndex + 1})`);
const statusText = await statusCell.textContent();
expect(expectedStatus).toContain(statusText);
}
};
export const dragAndDropColumn = async (
page: Page,
dragColumn: string,
dropColumn: string
) => {
await page
.locator('.draggable-menu-item', { hasText: dragColumn })
.dragTo(page.locator('.draggable-menu-item', { hasText: dropColumn }));
};

View File

@ -0,0 +1,3 @@
<svg width="14" height="7" viewBox="0 0 14 7" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13 2.16671H0.99998C0.823169 2.16671 0.653599 2.09647 0.528575 1.97144C0.403551 1.84642 0.333313 1.67685 0.333313 1.50004C0.333313 1.32323 0.403551 1.15366 0.528575 1.02864C0.653599 0.903612 0.823169 0.833374 0.99998 0.833374H13C13.1768 0.833374 13.3464 0.903612 13.4714 1.02864C13.5964 1.15366 13.6667 1.32323 13.6667 1.50004C13.6667 1.67685 13.5964 1.84642 13.4714 1.97144C13.3464 2.09647 13.1768 2.16671 13 2.16671ZM13.6667 5.50004C13.6667 5.32323 13.5964 5.15366 13.4714 5.02863C13.3464 4.90361 13.1768 4.83337 13 4.83337H0.99998C0.823169 4.83337 0.653599 4.90361 0.528575 5.02863C0.403551 5.15366 0.333313 5.32323 0.333313 5.50004C0.333313 5.67685 0.403551 5.84642 0.528575 5.97144C0.653599 6.09647 0.823169 6.16671 0.99998 6.16671H13C13.1768 6.16671 13.3464 6.09647 13.4714 5.97144C13.5964 5.84642 13.6667 5.67685 13.6667 5.50004Z" fill="#757575"/>
</svg>

After

Width:  |  Height:  |  Size: 965 B

View File

@ -0,0 +1,84 @@
/*
* Copyright 2024 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 { Checkbox } from 'antd';
import React, { useCallback } from 'react';
import { useDrag, useDrop } from 'react-dnd';
import { ReactComponent as ColumnDragIcon } from '../../../assets/svg/menu-duo.svg';
interface DraggableMenuItemProps {
option: { value: string; label: string };
index: number;
options: { value: string; label: string }[];
onMoveItem: (updatedList: { value: string; label: string }[]) => void;
selectedOptions: string[];
onSelect: (key: string, checked: boolean, type: 'columns' | 'status') => void;
}
const DraggableMenuItem: React.FC<DraggableMenuItemProps> = ({
option,
index,
options,
onMoveItem,
selectedOptions,
onSelect,
}) => {
const moveDropdownMenuItem = useCallback(
(fromIndex: number, toIndex: number) => {
const updatedList = [...options];
const [movedItem] = updatedList.splice(fromIndex, 1);
updatedList.splice(toIndex, 0, movedItem);
onMoveItem(updatedList);
},
[options, onMoveItem]
);
const [{ isDragging }, drag] = useDrag({
type: 'CHECKBOX',
item: { index },
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
});
const [, drop] = useDrop({
accept: 'CHECKBOX',
hover: (draggedItem: any) => {
if (draggedItem.index !== index) {
moveDropdownMenuItem(draggedItem.index, index);
draggedItem.index = index;
}
},
});
return (
<div
className={`draggable-menu-item ${isDragging ? 'dragging' : ''}`}
ref={(node) => {
drag(drop(node));
}}>
<ColumnDragIcon
className="glossary-col-dropdown-drag-icon m-l-xs m-t-xs"
height={16}
width={16}
/>
<Checkbox
checked={selectedOptions.includes(option.value)}
className="custom-glossary-col-sel-checkbox m-l-sm"
key={option.value}
value={option.value}
onChange={(e) => onSelect(option.value, e.target.checked, 'columns')}>
<p className="glossary-dropdown-label">{option.label}</p>
</Checkbox>
</div>
);
};
export default DraggableMenuItem;

View File

@ -11,10 +11,24 @@
* limitations under the License.
*/
import { FilterOutlined } from '@ant-design/icons';
import { DownOutlined } from '@ant-design/icons';
import Icon from '@ant-design/icons/lib/components/Icon';
import { Button, Col, Modal, Row, Space, TableProps, Tooltip } from 'antd';
import { ColumnsType, ExpandableConfig } from 'antd/lib/table/interface';
import {
Button,
Checkbox,
Col,
Dropdown,
Modal,
Row,
Space,
TableProps,
Tooltip,
} from 'antd';
import {
ColumnsType,
ColumnType,
ExpandableConfig,
} from 'antd/lib/table/interface';
import { AxiosError } from 'axios';
import classNames from 'classnames';
import { compare } from 'fast-json-patch';
@ -38,6 +52,7 @@ import StatusBadge from '../../../components/common/StatusBadge/StatusBadge.comp
import {
API_RES_MAX_SIZE,
DE_ACTIVE_COLOR,
NO_DATA_PLACEHOLDER,
TEXT_BODY_COLOR,
} from '../../../constants/constants';
import { GLOSSARIES_DOCS } from '../../../constants/docs.constants';
@ -49,7 +64,6 @@ import {
GlossaryTerm,
Status,
} from '../../../generated/entity/data/glossaryTerm';
import { useApplicationStore } from '../../../hooks/useApplicationStore';
import {
getFirstLevelGlossaryTerms,
getGlossaryTerms,
@ -64,13 +78,14 @@ import {
findExpandableKeysForArray,
findGlossaryTermByFqn,
StatusClass,
StatusFilters,
} from '../../../utils/GlossaryUtils';
import { getGlossaryPath } from '../../../utils/RouterUtils';
import { showErrorToast } from '../../../utils/ToastUtils';
import { DraggableBodyRowProps } from '../../common/Draggable/DraggableBodyRowProps.interface';
import Loader from '../../common/Loader/Loader';
import Table from '../../common/Table/Table';
import TagButton from '../../common/TagButton/TagButton.component';
import DraggableMenuItem from '../GlossaryColumnsSelectionDropdown/DraggableMenuItem.component';
import { ModifiedGlossary, useGlossaryStore } from '../useGlossary.store';
import {
GlossaryTermTabProps,
@ -89,7 +104,6 @@ const GlossaryTermTab = ({
}: GlossaryTermTabProps) => {
const { activeGlossary, glossaryChildTerms, setGlossaryChildTerms } =
useGlossaryStore();
const { theme } = useApplicationStore();
const { t } = useTranslation();
const glossaryTerms = (glossaryChildTerms as ModifiedGlossaryTerm[]) ?? [];
@ -100,6 +114,19 @@ const GlossaryTermTab = ({
const [isTableLoading, setIsTableLoading] = useState(false);
const [isTableHovered, setIsTableHovered] = useState(false);
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
const [isStatusDropdownVisible, setIsStatusDropdownVisible] =
useState<boolean>(false);
const statusOptions = useMemo(
() =>
Object.values(Status).map((status) => ({ value: status, label: status })),
[]
);
const [statusDropdownSelection, setStatusDropdownSelections] = useState<
string[]
>(['Approved', 'Draft']);
const [selectedStatus, setSelectedStatus] = useState<string[]>([
...statusDropdownSelection,
]);
const glossaryTermStatus: Status | null = useMemo(() => {
if (!isGlossary) {
@ -162,6 +189,36 @@ const GlossaryTermTab = ({
<span className="text-grey-muted">{t('label.no-description')}</span>
),
},
{
title: t('label.reviewer'),
dataIndex: 'reviewers',
key: 'reviewers',
width: '33%',
render: (reviewers: EntityReference[]) => (
<OwnerLabel owners={reviewers} />
),
},
{
title: t('label.synonym-plural'),
dataIndex: 'synonyms',
key: 'synonyms',
width: '33%',
render: (synonyms: string[]) => {
return isEmpty(synonyms) ? (
<div>{NO_DATA_PLACEHOLDER}</div>
) : (
<div className="d-flex flex-wrap">
{synonyms.map((synonym: string) => (
<TagButton
className="glossary-synonym-tag"
key={synonym}
label={synonym}
/>
))}
</div>
);
},
},
{
title: t('label.owner-plural'),
dataIndex: 'owners',
@ -174,14 +231,6 @@ const GlossaryTermTab = ({
dataIndex: 'status',
key: 'status',
width: '12%',
filterIcon: (filtered) => (
<FilterOutlined
style={{
color: filtered ? theme.primaryColor : undefined,
}}
/>
),
filters: StatusFilters,
render: (_, record) => {
const status = record.status ?? Status.Approved;
@ -249,6 +298,303 @@ const GlossaryTermTab = ({
return data;
}, [glossaryTerms, permissions]);
const listOfVisibleColumns = useMemo(() => {
return ['name', 'description', 'owners', 'status', 'new-term'];
}, []);
const defaultCheckedList = useMemo(
() =>
columns.reduce<string[]>(
(acc, column) =>
listOfVisibleColumns.includes(column.key as string)
? [...acc, column.key as string]
: acc,
[]
),
[columns, listOfVisibleColumns]
);
const [selectedColumns, setSelectedColumns] =
useState<string[]>(defaultCheckedList);
const [columnDropdownSelections, setColumnDropdownSelections] = useState<
string[]
>([...selectedColumns]);
const [isDropdownVisible, setIsDropdownVisible] = useState<boolean>(false);
const [options, setOptions] = useState<{ value: string; label: string }[]>(
columns.reduce<{ value: string; label: string }[]>(
(acc, { key, title }) =>
key !== 'name'
? [...acc, { label: title as string, value: key as string }]
: acc,
[]
)
);
const newColumns = useMemo(() => {
return columns.map((item) => ({
...item,
hidden: !selectedColumns.includes(item.key as string),
}));
}, [columns, selectedColumns]);
const rearrangedColumns = useMemo(
() =>
newColumns
.filter((column) => !column.hidden)
.sort((a, b) => {
const aIndex = options.findIndex(
(option: { value: string; label: string }) => option.value === a.key
);
const bIndex = options.findIndex(
(option: { value: string; label: string }) => option.value === b.key
);
return aIndex - bIndex;
}),
[newColumns]
);
const handleColumnSelectionDropdownSave = useCallback(() => {
setSelectedColumns(columnDropdownSelections);
setIsDropdownVisible(false);
}, [columnDropdownSelections]);
const handleColumnSelectionDropdownCancel = useCallback(() => {
setColumnDropdownSelections(selectedColumns);
setIsDropdownVisible(false);
}, [selectedColumns]);
const handleMoveItem = (updatedList: { value: string; label: string }[]) => {
setOptions(updatedList);
setColumnDropdownSelections((prevCheckedList) => {
const updatedCheckedList = prevCheckedList.map((item) => {
const index = updatedList.findIndex((option) => option.value === item);
return updatedList[index]?.value || item;
});
return updatedCheckedList;
});
};
const handleCheckboxChange = useCallback(
(key: string, checked: boolean, type: 'columns' | 'status') => {
const setCheckedList =
type === 'columns'
? setColumnDropdownSelections
: setStatusDropdownSelections;
const optionsToUse =
type === 'columns'
? (columns as ColumnType<ModifiedGlossaryTerm>[])
: (statusOptions as { value: string }[]);
if (key === 'all') {
if (checked) {
const newCheckedList = [
'all',
...optionsToUse.map((option) => {
return type === 'columns'
? String((option as ColumnType<ModifiedGlossaryTerm>).key)
: (option as { value: string }).value ?? '';
}),
];
setCheckedList(newCheckedList);
} else {
setCheckedList([type === 'columns' ? 'name' : 'Draft']);
}
} else {
setCheckedList((prev: string[]) => {
const newCheckedList = checked
? [...prev, key]
: prev.filter((item) => item !== key);
const allChecked =
type === 'columns'
? (optionsToUse as ColumnType<ModifiedGlossaryTerm>[]).every(
(opt) => newCheckedList.includes(String(opt.key))
)
: (optionsToUse as { value: string }[]).every((opt) =>
newCheckedList.includes(opt.value ?? '')
);
if (allChecked) {
return ['all', ...newCheckedList];
}
return newCheckedList.filter((item) => item !== 'all');
});
}
},
[
columns,
statusOptions,
setColumnDropdownSelections,
setStatusDropdownSelections,
]
);
const menu = useMemo(
() => ({
items: [
{
key: 'addColumn',
label: (
<div className="glossary-col-sel-dropdown-title">
<p className="m-l-md">{t('label.add-column')}</p>
<Checkbox.Group
className="glossary-col-sel-checkbox-group"
value={columnDropdownSelections}>
<Checkbox
checked={columns
.filter((col) => col.key === 'name')
.every(({ key }) =>
columnDropdownSelections.includes(key as string)
)}
className="custom-glossary-col-sel-checkbox m-l-lg p-l-md"
key="all"
value="all"
onChange={(e) =>
handleCheckboxChange('all', e.target.checked, 'columns')
}>
{t('label.all')}
</Checkbox>
{options.map(
(option: { value: string; label: string }, index: number) => (
<div
className="d-flex justify-start items-center"
key={option.value}>
<DraggableMenuItem
index={index}
option={option}
options={options}
selectedOptions={columnDropdownSelections}
onMoveItem={handleMoveItem}
onSelect={handleCheckboxChange}
/>
</div>
)
)}
</Checkbox.Group>
</div>
),
},
{
key: 'divider',
type: 'divider',
},
{
key: 'actions',
label: (
<div className="flex-center">
<Space>
<Button
type="primary"
onClick={handleColumnSelectionDropdownSave}>
{t('label.save')}
</Button>
<Button
type="default"
onClick={handleColumnSelectionDropdownCancel}>
{t('label.cancel')}
</Button>
</Space>
</div>
),
},
],
}),
[
columnDropdownSelections,
columns,
options,
handleCheckboxChange,
handleColumnSelectionDropdownSave,
handleColumnSelectionDropdownCancel,
]
);
const handleStatusSelectionDropdownSave = () => {
setSelectedStatus(statusDropdownSelection);
setIsStatusDropdownVisible(false);
};
const handleStatusSelectionDropdownCancel = () => {
setStatusDropdownSelections(selectedStatus);
setIsStatusDropdownVisible(false);
};
const statusDropdownMenu = useMemo(
() => ({
items: [
{
key: 'statusSelection',
label: (
<div className="status-selection-dropdown">
<Checkbox.Group
className="glossary-col-sel-checkbox-group"
value={statusDropdownSelection}>
<Checkbox
className="custom-glossary-col-sel-checkbox"
key="all"
value="all"
onChange={(e) =>
handleCheckboxChange('all', e.target.checked, 'status')
}>
<p className="glossary-dropdown-label">{t('label.all')}</p>
</Checkbox>
{statusOptions.map((option) => (
<div key={option.value}>
<Checkbox
className="custom-glossary-col-sel-checkbox"
value={option.value}
onChange={(e) =>
handleCheckboxChange(
option.value,
e.target.checked,
'status'
)
}>
<p className="glossary-dropdown-label">{option.label}</p>
</Checkbox>
</div>
))}
</Checkbox.Group>
</div>
),
},
{
key: 'divider',
type: 'divider',
},
{
key: 'actions',
label: (
<div className="flex-center">
<Space>
<Button
type="primary"
onClick={handleStatusSelectionDropdownSave}>
{t('label.save')}
</Button>
<Button
type="default"
onClick={handleStatusSelectionDropdownCancel}>
{t('label.cancel')}
</Button>
</Space>
</div>
),
},
],
}),
[
statusDropdownSelection,
statusOptions,
handleStatusSelectionDropdownSave,
handleStatusSelectionDropdownCancel,
]
);
const handleAddGlossaryTermClick = () => {
onAddGlossaryTerm(
!isGlossary ? (activeGlossary as GlossaryTerm) : undefined
@ -468,6 +814,52 @@ const GlossaryTermTab = ({
{isAllExpanded ? t('label.collapse-all') : t('label.expand-all')}
</Space>
</Button>
<Dropdown
className="custom-glossary-dropdown-menu status-dropdown"
getPopupContainer={(trigger) => {
const customContainer = trigger.closest(
'.custom-glossary-dropdown-menu.status-dropdown'
);
return customContainer as HTMLElement;
}}
menu={statusDropdownMenu}
open={isStatusDropdownVisible}
trigger={['click']}
onOpenChange={setIsStatusDropdownVisible}>
<Button
className="custom-status-dropdown-btn m-r-sm"
data-testid="glossary-status-dropdown">
<Space>
{t('label.status')}
<DownOutlined />
</Space>
</Button>
</Dropdown>
<DndProvider backend={HTML5Backend}>
<Dropdown
className="mb-4 custom-glossary-dropdown-menu"
getPopupContainer={(trigger) => {
const customContainer = trigger.closest(
'.custom-glossary-dropdown-menu'
);
return customContainer as HTMLElement;
}}
menu={menu}
open={isDropdownVisible}
trigger={['click']}
onOpenChange={setIsDropdownVisible}>
<Button
className="custom-status-dropdown-btn m-r-xs"
data-testid="glossary-column-dropdown">
<Space>
{t('label.column-plural')}
<DownOutlined />
</Space>
</Button>
</Dropdown>
</DndProvider>
</div>
{glossaryTerms.length > 0 ? (
@ -477,9 +869,12 @@ const GlossaryTermTab = ({
className={classNames('drop-over-background', {
'drop-over-table': isTableHovered,
})}
columns={columns}
columns={rearrangedColumns.filter((col) => !col.hidden)}
components={TABLE_CONSTANTS}
dataSource={glossaryTerms}
data-testid="glossary-terms-table"
dataSource={glossaryTerms.filter((term) =>
selectedStatus.includes(term.status as string)
)}
expandable={expandableConfig}
loading={isTableLoading}
pagination={false}

View File

@ -90,3 +90,102 @@
display: inline-block;
}
}
.custom-glossary-col-sel-checkbox {
font-size: 16px;
color: @grey-3;
width: 100%;
.ant-checkbox-inner {
width: 20px;
height: 20px;
background-color: @white;
border-color: @grey-4;
&::after {
width: 6px;
height: 10px;
border-color: @purple-2;
border-width: 0 2px 2px 0;
}
}
.ant-checkbox-checked .ant-checkbox-inner {
background-color: @white;
border-color: @grey-4;
}
.ant-checkbox-wrapper:hover .ant-checkbox-inner,
.ant-checkbox:hover .ant-checkbox-inner,
.ant-checkbox-input:focus + .ant-checkbox-inner {
border-color: @grey-4;
}
}
.custom-glossary-dropdown-menu {
.ant-dropdown-menu {
background-color: @white;
border-color: @grey-4;
}
.ant-dropdown-menu-item {
padding-left: 0px;
padding-right: 0px;
}
.ant-dropdown-menu-item:hover {
background-color: @white;
color: inherit;
}
}
.glossary-col-sel-dropdown-title {
font-size: 14px;
line-height: 21px;
color: @grey-4;
}
.glossary-col-sel-checkbox-group {
display: flex;
flex-direction: column;
width: 100%;
background-color: @white;
border-color: @grey-3;
}
.status-dropdown {
.ant-dropdown-menu-item {
padding-left: 8px;
padding-right: 8px;
}
}
.draggable-menu-item {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
transition: background-color 0.3s ease, opacity 0.3s ease;
width: 100%;
box-sizing: border-box;
padding: 0px 8px;
box-sizing: border-box;
}
.draggable-menu-item.dragging {
background-color: #f0f0f0;
opacity: 0.8;
z-index: 9999;
}
.glossary-dropdown-label {
font-size: 14px;
line-height: 21px;
color: @grey-4;
}
.custom-status-dropdown-btn {
background-color: @white;
outline: 1px solid @grey-2;
color: @black;
}
.glossary-col-dropdown-drag-icon {
margin-left: 8px;
}

View File

@ -28,6 +28,7 @@
"add": "Hinzufügen",
"add-a-new-service": "Neuen Service hinzufügen",
"add-an-image": "Add an image",
"add-column": "Add Column",
"add-custom-entity-property": "Benutzerdefinierte Eigenschaft zu {{entity}} hinzufügen",
"add-deploy": "Hinzufügen und Bereitstellen",
"add-entity": "{{entity}} hinzufügen",

View File

@ -28,6 +28,7 @@
"add": "Add",
"add-a-new-service": "Add a New Service",
"add-an-image": "Add an image",
"add-column": "Add Column",
"add-custom-entity-property": "Add Custom {{entity}} Property",
"add-deploy": "Add & Deploy",
"add-entity": "Add {{entity}}",

View File

@ -28,6 +28,7 @@
"add": "Añadir",
"add-a-new-service": "Añadir un nuevo servicio",
"add-an-image": "Añadir una imagen",
"add-column": "Add Column",
"add-custom-entity-property": "Añadir propiedad personalizada de {{entity}}",
"add-deploy": "Añadir y desplegar",
"add-entity": "Añadir {{entity}}",

View File

@ -28,6 +28,7 @@
"add": "Ajouter",
"add-a-new-service": "Ajouter un Nouveau Service",
"add-an-image": "Ajouter une Image",
"add-column": "Add Column",
"add-custom-entity-property": "Ajouter une Propriété Personnalisée à {{entity}}",
"add-deploy": "Ajouter et Déployer",
"add-entity": "Ajouter {{entity}}",

View File

@ -28,6 +28,7 @@
"add": "Engadir",
"add-a-new-service": "Engadir un novo servizo",
"add-an-image": "Engadir unha imaxe",
"add-column": "Add Column",
"add-custom-entity-property": "Engadir unha propiedade personalizada de {{entity}}",
"add-deploy": "Engadir e despregar",
"add-entity": "Engadir {{entity}}",

View File

@ -28,6 +28,7 @@
"add": "הוסף",
"add-a-new-service": "הוסף שירות חדש",
"add-an-image": "הוסף תמונה",
"add-column": "Add Column",
"add-custom-entity-property": "הוסף נכס מותאם אישית ל {{entity}}",
"add-deploy": "הוסף והפעל",
"add-entity": "הוסף {{entity}}",

View File

@ -28,6 +28,7 @@
"add": "追加",
"add-a-new-service": "新しいサービスを追加",
"add-an-image": "Add an image",
"add-column": "Add Column",
"add-custom-entity-property": "Add Custom {{entity}} Property",
"add-deploy": "追加&デプロイ",
"add-entity": "{{entity}}を追加",

View File

@ -28,6 +28,7 @@
"add": "Toevoegen",
"add-a-new-service": "Nieuwe service toevoegen",
"add-an-image": "Afbeelding toevoegen",
"add-column": "Add Column",
"add-custom-entity-property": "Aangepaste {{entity}} eigenschap toevoegen",
"add-deploy": "Toevoegen en deployen",
"add-entity": "{{entity}} toevoegen",

View File

@ -28,6 +28,7 @@
"add": "اضافه کردن",
"add-a-new-service": "اضافه کردن یک سرویس جدید",
"add-an-image": "اضافه کردن تصویر",
"add-column": "Add Column",
"add-custom-entity-property": "اضافه کردن خصوصیت سفارشی {{entity}}",
"add-deploy": "اضافه کردن و استقرار",
"add-entity": "اضافه کردن {{entity}}",

View File

@ -28,6 +28,7 @@
"add": "Adicionar",
"add-a-new-service": "Adicionar um Novo Serviço",
"add-an-image": "Adicionar uma imagem",
"add-column": "Add Column",
"add-custom-entity-property": "Adicionar Propriedade Personalizada {{entity}}",
"add-deploy": "Adicionar & Implementar",
"add-entity": "Adicionar {{entity}}",

View File

@ -28,6 +28,7 @@
"add": "Добавить",
"add-a-new-service": "Добавить новый сервис",
"add-an-image": "Add an image",
"add-column": "Add Column",
"add-custom-entity-property": "Добавить пользовательское свойство {{entity}}",
"add-deploy": "Добавить & Развернуть",
"add-entity": "Добавить {{entity}}",

View File

@ -28,6 +28,7 @@
"add": "添加",
"add-a-new-service": "添加新服务",
"add-an-image": "添加图片",
"add-column": "Add Column",
"add-custom-entity-property": "添加自定义{{entity}}属性",
"add-deploy": "添加部署",
"add-entity": "添加{{entity}}",

View File

@ -335,7 +335,11 @@ export const getFirstLevelGlossaryTerms = async (parentFQN: string) => {
>(apiUrl, {
params: {
directChildrenOf: parentFQN,
fields: [TabSpecificField.CHILDREN_COUNT, TabSpecificField.OWNERS],
fields: [
TabSpecificField.CHILDREN_COUNT,
TabSpecificField.OWNERS,
TabSpecificField.REVIEWERS,
],
limit: 100000,
},
});