UI :- Added functionality to drag and drop teams in another team (#9152)

* Added functionality to drag and drop teams in another team

* remove immutability package

* changes as per comments

* Added unit test

* changes as per comments

* minor changes

* added expandable config

* changes as per comments

* changes as per comments

Co-authored-by: Sachin Chaurasiya <sachinchaurasiyachotey87@gmail.com>
This commit is contained in:
Ashish Gupta 2022-12-12 18:02:51 +05:30 committed by GitHub
parent 775016488d
commit 2303aede92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 735 additions and 76 deletions

View File

@ -74,6 +74,8 @@
"react-codemirror2": "^7.2.1",
"react-context-mutex": "^2.0.0",
"react-copy-to-clipboard": "^5.0.4",
"react-dnd": "14.0.2",
"react-dnd-html5-backend": "14.0.2",
"react-dom": "^16.14.0",
"react-error-boundary": "^3.1.4",
"react-i18next": "^11.18.6",
@ -181,6 +183,7 @@
"connect-api-mocker": "^1.10.0",
"copy-webpack-plugin": "^7.0.0",
"css-loader": "^6.7.2",
"cypress-postgresql": "^1.0.8",
"dotenv": "^16.0.0",
"eslint": "^6.6.0",
"eslint-config-prettier": "^6.11.0",
@ -219,7 +222,6 @@
"webpack-bundle-analyzer": "^4.4.0",
"webpack-cli": "^4.3.1",
"webpack-dev-server": "^4.11.1",
"webpackbar": "^5.0.0-3",
"cypress-postgresql": "^1.0.8"
"webpackbar": "^5.0.0-3"
}
}

View File

@ -0,0 +1,3 @@
<svg width="8" height="12" viewBox="0 0 8 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.83464 11.3334C1.51241 11.3334 1.23741 11.2195 1.00964 10.9917C0.781858 10.764 0.667969 10.489 0.667969 10.1667C0.667969 9.84453 0.781858 9.56953 1.00964 9.34175C1.23741 9.11397 1.51241 9.00008 1.83464 9.00008C2.15686 9.00008 2.43186 9.11397 2.65964 9.34175C2.88741 9.56953 3.0013 9.84453 3.0013 10.1667C3.0013 10.489 2.88741 10.764 2.65964 10.9917C2.43186 11.2195 2.15686 11.3334 1.83464 11.3334ZM6.16797 11.3334C5.84575 11.3334 5.57075 11.2195 5.34297 10.9917C5.11519 10.764 5.0013 10.489 5.0013 10.1667C5.0013 9.84453 5.11519 9.56953 5.34297 9.34175C5.57075 9.11397 5.84575 9.00008 6.16797 9.00008C6.49019 9.00008 6.76519 9.11397 6.99297 9.34175C7.22075 9.56953 7.33463 9.84453 7.33463 10.1667C7.33463 10.489 7.22075 10.764 6.99297 10.9917C6.76519 11.2195 6.49019 11.3334 6.16797 11.3334ZM1.83464 7.16675C1.51241 7.16675 1.23741 7.05286 1.00964 6.82508C0.781858 6.5973 0.667969 6.3223 0.667969 6.00008C0.667969 5.67786 0.781858 5.40286 1.00964 5.17508C1.23741 4.9473 1.51241 4.83342 1.83464 4.83342C2.15686 4.83342 2.43186 4.9473 2.65964 5.17508C2.88741 5.40286 3.0013 5.67786 3.0013 6.00008C3.0013 6.3223 2.88741 6.5973 2.65964 6.82508C2.43186 7.05286 2.15686 7.16675 1.83464 7.16675ZM6.16797 7.16675C5.84575 7.16675 5.57075 7.05286 5.34297 6.82508C5.11519 6.5973 5.0013 6.3223 5.0013 6.00008C5.0013 5.67786 5.11519 5.40286 5.34297 5.17508C5.57075 4.9473 5.84575 4.83342 6.16797 4.83342C6.49019 4.83342 6.76519 4.9473 6.99297 5.17508C7.22075 5.40286 7.33463 5.67786 7.33463 6.00008C7.33463 6.3223 7.22075 6.5973 6.99297 6.82508C6.76519 7.05286 6.49019 7.16675 6.16797 7.16675ZM1.83464 3.00008C1.51241 3.00008 1.23741 2.88619 1.00964 2.65841C0.781858 2.43064 0.667969 2.15564 0.667969 1.83341C0.667969 1.51119 0.781858 1.23619 1.00964 1.00841C1.23741 0.780637 1.51241 0.666748 1.83464 0.666748C2.15686 0.666748 2.43186 0.780637 2.65964 1.00841C2.88741 1.23619 3.0013 1.51119 3.0013 1.83341C3.0013 2.15564 2.88741 2.43064 2.65964 2.65841C2.43186 2.88619 2.15686 3.00008 1.83464 3.00008ZM6.16797 3.00008C5.84575 3.00008 5.57075 2.88619 5.34297 2.65841C5.11519 2.43064 5.0013 2.15564 5.0013 1.83341C5.0013 1.51119 5.11519 1.23619 5.34297 1.00841C5.57075 0.780637 5.84575 0.666748 6.16797 0.666748C6.49019 0.666748 6.76519 0.780637 6.99297 1.00841C7.22075 1.23619 7.33463 1.51119 7.33463 1.83341C7.33463 2.15564 7.22075 2.43064 6.99297 2.65841C6.76519 2.88619 6.49019 3.00008 6.16797 3.00008Z" fill="#76746F"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -13,7 +13,7 @@
import { AxiosResponse } from 'axios';
import { Operation } from 'fast-json-patch';
import { RestoreEntitiesRequestType } from 'Models';
import { RestoreRequestType } from 'Models';
import { Dashboard } from '../generated/entity/data/dashboard';
import { EntityHistory } from '../generated/type/entityHistory';
import { EntityReference } from '../generated/type/entityReference';
@ -122,7 +122,7 @@ export const patchDashboardDetails = async (id: string, data: Operation[]) => {
export const restoreDashboard = async (id: string) => {
const response = await APIClient.put<
RestoreEntitiesRequestType,
RestoreRequestType,
AxiosResponse<Dashboard>
>('/dashboards/restore', { id });

View File

@ -13,7 +13,7 @@
import { AxiosResponse } from 'axios';
import { Operation } from 'fast-json-patch';
import { PagingResponse, RestoreEntitiesRequestType } from 'Models';
import { PagingResponse, RestoreRequestType } from 'Models';
import { Pipeline, PipelineStatus } from '../generated/entity/data/pipeline';
import { EntityHistory } from '../generated/type/entityHistory';
import { EntityReference } from '../generated/type/entityReference';
@ -140,7 +140,7 @@ export const getPipelineStatus = async (
export const restorePipeline = async (id: string) => {
const response = await APIClient.put<
RestoreEntitiesRequestType,
RestoreRequestType,
AxiosResponse<Pipeline>
>('/pipelines/restore', {
id,

View File

@ -13,7 +13,7 @@
import { AxiosResponse } from 'axios';
import { Operation } from 'fast-json-patch';
import { RestoreEntitiesRequestType } from 'Models';
import { RestoreRequestType } from 'Models';
import {
ColumnProfile,
Table,
@ -98,7 +98,7 @@ export const patchTableDetails = async (id: string, data: Operation[]) => {
export const restoreTable = async (id: string) => {
const response = await APIClient.put<
RestoreEntitiesRequestType,
RestoreRequestType,
AxiosResponse<Table>
>('/tables/restore', { id });

View File

@ -14,6 +14,7 @@
import { AxiosResponse } from 'axios';
import { Operation } from 'fast-json-patch';
import { isString } from 'lodash';
import { RestoreRequestType } from 'Models';
import { CreateTeam } from '../generated/api/teams/createTeam';
import { Team } from '../generated/entity/teams/team';
import { TeamHierarchy } from '../generated/entity/teams/teamHierarchy';
@ -102,7 +103,7 @@ export const deleteTeam = async (id: string) => {
return response.data;
};
export const reactivateTeam = async (data: CreateTeam) => {
export const updateTeam = async (data: CreateTeam) => {
const response = await APIClient.put<CreateTeam, AxiosResponse<Team>>(
'/teams',
data
@ -110,3 +111,12 @@ export const reactivateTeam = async (data: CreateTeam) => {
return response.data;
};
export const restoreTeam = async (id: string) => {
const response = await APIClient.put<RestoreRequestType, AxiosResponse<Team>>(
'/teams/restore',
{ id }
);
return response.data;
};

View File

@ -13,7 +13,7 @@
import { AxiosResponse } from 'axios';
import { Operation } from 'fast-json-patch';
import { RestoreEntitiesRequestType } from 'Models';
import { RestoreRequestType } from 'Models';
import { TabSpecificField } from '../enums/entity.enum';
import { Topic } from '../generated/entity/data/topic';
import { EntityHistory } from '../generated/type/entityHistory';
@ -128,7 +128,7 @@ export const patchTopicDetails = async (id: string, data: Operation[]) => {
export const restoreTopic = async (id: string) => {
const response = await APIClient.put<
RestoreEntitiesRequestType,
RestoreRequestType,
AxiosResponse<Topic>
>('/topics/restore', { id });

View File

@ -0,0 +1,68 @@
/*
* Copyright 2022 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 React, { useRef } from 'react';
import { useDrag, useDrop } from 'react-dnd';
import { DRAGGABLE_BODY_ROW } from '../../constants/Teams.constants';
import { Team } from '../../generated/entity/teams/team';
import { DragCollectProps, DraggableBodyRowProps } from './team.interface';
const DraggableBodyRow = ({
index,
handleMoveRow,
className,
record,
style,
...restProps
}: DraggableBodyRowProps) => {
const ref = useRef<HTMLTableRowElement>(null);
const [{ isOver, dropClassName }, drop] = useDrop({
accept: DRAGGABLE_BODY_ROW,
collect: (monitor: DragCollectProps) => {
const { index: dragIndex } = monitor?.getItem() || {};
if (dragIndex === index) {
return {};
}
return {
isOver: monitor.isOver(),
dropClassName:
dragIndex < index ? ' drop-over-downward' : ' drop-over-upward',
};
},
// this will going to return the drag and drop object of a table
drop: ({ record: dragRecord }: { record: Team }) => {
handleMoveRow(dragRecord, record);
},
});
// here we are passing the drag record
const [, drag] = useDrag({
type: DRAGGABLE_BODY_ROW,
item: { record },
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
});
drop(drag(ref));
return (
<tr
className={`${className}${isOver ? dropClassName : ''}`}
ref={ref}
style={{ cursor: 'move', ...style }}
{...restProps}
/>
);
};
export default DraggableBodyRow;

View File

@ -36,7 +36,7 @@ import React, { Fragment, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import AppState from '../../AppState';
import { reactivateTeam } from '../../axiosAPIs/teamsAPI';
import { restoreTeam } from '../../axiosAPIs/teamsAPI';
import {
getTeamAndUserDetailsPath,
getUserPath,
@ -76,7 +76,6 @@ import SVGIcons, { Icons } from '../../utils/SvgUtils';
import {
filterChildTeams,
getDeleteMessagePostFix,
getRestoreTeamData,
} from '../../utils/TeamUtils';
import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils';
import { Button } from '../buttons/Button/Button';
@ -514,9 +513,7 @@ const TeamDetailsV1 = ({
const handleReactiveTeam = async () => {
try {
const res = await reactivateTeam(
getRestoreTeamData(currentTeam, childTeams)
);
const res = await restoreTeam(currentTeam.id);
if (res) {
afterDeleteAction();
showSuccessToast(
@ -1166,7 +1163,7 @@ const TeamDetailsV1 = ({
) : (
<Row
className="team-list-container"
gutter={[8, 8]}
gutter={[8, 16]}
justify="space-between">
<Col span={8}>
<Searchbar
@ -1195,6 +1192,7 @@ const TeamDetailsV1 = ({
</Col>
<Col span={24}>
<TeamHierarchy
currentTeam={currentTeam}
data={table as Team[]}
onTeamExpand={onTeamExpand}
/>

View File

@ -0,0 +1,119 @@
/*
* Copyright 2022 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 { act, fireEvent, render, screen } from '@testing-library/react';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { MOCK_CURRENT_TEAM, MOCK_TABLE_DATA } from '../../mocks/Teams.mock';
import { TeamHierarchyProps } from './team.interface';
import TeamHierarchy from './TeamHierarchy';
const teamHierarchyPropsData: TeamHierarchyProps = {
data: MOCK_TABLE_DATA,
currentTeam: MOCK_CURRENT_TEAM,
onTeamExpand: jest.fn(),
};
const mockShowErrorToast = jest.fn();
// mock library imports
jest.mock('react-router-dom', () => ({
Link: jest
.fn()
.mockImplementation(({ children }) => <a href="#">{children}</a>),
}));
jest.mock('../../utils/TeamUtils', () => ({
getMovedTeamData: jest.fn().mockReturnValue([]),
}));
jest.mock('../../axiosAPIs/teamsAPI', () => ({
updateTeam: jest
.fn()
.mockImplementation(() => Promise.resolve(MOCK_CURRENT_TEAM)),
getTeamByName: jest
.fn()
.mockImplementation(() => Promise.resolve(MOCK_CURRENT_TEAM)),
}));
jest.mock('../../utils/CommonUtils', () => ({
getEntityName: jest.fn().mockReturnValue('entityName'),
}));
jest.mock('../../utils/RouterUtils', () => ({
getTeamsWithFqnPath: jest.fn().mockReturnValue([]),
}));
jest.mock('../../utils/ToastUtils', () => ({
showErrorToast: jest.fn().mockImplementation(() => mockShowErrorToast),
}));
describe('Team Hierarchy page', () => {
it('Initially, Table should load', async () => {
await act(async () => {
render(<TeamHierarchy {...teamHierarchyPropsData} />, {
wrapper: MemoryRouter,
});
});
const table = await screen.findByTestId('team-hierarchy-table');
expect(table).toBeInTheDocument();
});
it('Should render all table columns', async () => {
await act(async () => {
render(<TeamHierarchy {...teamHierarchyPropsData} />, {
wrapper: MemoryRouter,
});
});
const table = await screen.findByTestId('team-hierarchy-table');
const teamsColumn = await screen.findByText('Teams');
const typeColumn = await screen.findByText('Type');
const subTeamsColumn = await screen.findByText('Sub Teams');
const usersColumn = await screen.findByText('Users');
const assetCountColumn = await screen.findByText('Asset Count');
const descriptionColumn = await screen.findByText('Description');
const rows = await screen.findAllByRole('row');
expect(table).toBeInTheDocument();
expect(teamsColumn).toBeInTheDocument();
expect(typeColumn).toBeInTheDocument();
expect(subTeamsColumn).toBeInTheDocument();
expect(usersColumn).toBeInTheDocument();
expect(assetCountColumn).toBeInTheDocument();
expect(descriptionColumn).toBeInTheDocument();
expect(rows).toHaveLength(MOCK_TABLE_DATA.length + 1);
});
it('Should render child row in table', async () => {
await act(async () => {
render(<TeamHierarchy {...teamHierarchyPropsData} />, {
wrapper: MemoryRouter,
});
});
const table = await screen.findByTestId('team-hierarchy-table');
expect(table).toBeInTheDocument();
const expandableTableRow = await screen.getAllByTestId('expand-table-row');
fireEvent.click(expandableTableRow[0]);
const totalRows = await screen.findAllByText('entityName');
expect(totalRows).toHaveLength(5);
});
});

View File

@ -11,27 +11,42 @@
* limitations under the License.
*/
import { Table } from 'antd';
import { Modal, Table, Typography } from 'antd';
import { ColumnsType } from 'antd/lib/table';
import { isEmpty } from 'lodash';
import React, { FC, useMemo } from 'react';
import { ExpandableConfig } from 'antd/lib/table/interface';
import { AxiosError } from 'axios';
import { isArray, isEmpty } from 'lodash';
import React, { FC, useCallback, useMemo, useState } from 'react';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { getTeamByName, updateTeam } from '../../axiosAPIs/teamsAPI';
import { TABLE_CONSTANTS } from '../../constants/Teams.constants';
import { Team } from '../../generated/entity/teams/team';
import { getEntityName } from '../../utils/CommonUtils';
import { getTeamsWithFqnPath } from '../../utils/RouterUtils';
import SVGIcons, { Icons } from '../../utils/SvgUtils';
import { getMovedTeamData } from '../../utils/TeamUtils';
import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils';
import {
DraggableBodyRowProps,
MovedTeamProps,
TableExpandableDataProps,
TeamHierarchyProps,
} from './team.interface';
import './teams.less';
interface TeamHierarchyProps {
data: Team[];
onTeamExpand: (
loading?: boolean,
parentTeam?: string,
updateChildNode?: boolean
) => void;
}
const TeamHierarchy: FC<TeamHierarchyProps> = ({
currentTeam,
data,
onTeamExpand,
}) => {
const { t } = useTranslation();
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
const [isTableLoading, setIsTableLoading] = useState<boolean>(false);
const [movedTeam, setMovedTeam] = useState<MovedTeamProps>();
const TeamHierarchy: FC<TeamHierarchyProps> = ({ data, onTeamExpand }) => {
const columns: ColumnsType<Team> = useMemo(() => {
return [
{
@ -78,42 +93,137 @@ const TeamHierarchy: FC<TeamHierarchyProps> = ({ data, onTeamExpand }) => {
];
}, [data, onTeamExpand]);
return (
<Table
bordered
className="teams-list-table"
columns={columns}
dataSource={data}
expandable={{
expandIcon: ({ expanded, onExpand, expandable, record }) =>
expandable ? (
<span
className="m-r-xs cursor-pointer"
onClick={(e) =>
onExpand(
record,
e as unknown as React.MouseEvent<HTMLElement, MouseEvent>
)
}>
<SVGIcons
icon={
expanded ? Icons.ARROW_DOWN_LIGHT : Icons.ARROW_RIGHT_LIGHT
}
/>
</span>
) : (
<div className="expand-cell-icon-container" />
),
}}
pagination={false}
rowKey="name"
size="small"
onExpand={(isOpen, record) => {
const handleMoveRow = useCallback(
async (dragRecord: Team, dropRecord: Team) => {
if (dragRecord.id === dropRecord.id) {
return;
}
let dropTeam: Team = dropRecord;
if (!isArray(dropTeam.children)) {
const res = await getTeamByName(dropTeam.name, ['parents'], 'all');
dropTeam = (res.parents?.[0] as Team) || currentTeam;
}
setMovedTeam({
from: dragRecord,
to: dropTeam,
});
setIsModalOpen(true);
},
[]
);
const handleChangeTeam = async () => {
if (movedTeam) {
setIsTableLoading(true);
try {
const data = await getTeamByName(
movedTeam.from.name,
['users', 'defaultRoles', 'policies', 'owner', 'parents', 'children'],
'all'
);
await updateTeam(getMovedTeamData(data, [movedTeam.to.id]));
onTeamExpand(true, currentTeam?.name);
showSuccessToast(t('message.team-moved-success'));
} catch (error) {
showErrorToast(error as AxiosError, t('server.team-moved-error'));
} finally {
setIsTableLoading(false);
setIsModalOpen(false);
}
}
};
const tableExpandableIconData = useMemo(
() =>
({ expanded, onExpand, expandable, record }: TableExpandableDataProps) =>
expandable ? (
<div
draggable
className="expand-cell-icon-container"
data-testid="expand-table-row"
onClick={(e) =>
onExpand(
record,
e as unknown as React.MouseEvent<HTMLElement, MouseEvent>
)
}>
<SVGIcons
className="drag-icon"
draggable="true"
icon={Icons.DRAG}
/>
<SVGIcons
className="expand-icon"
icon={expanded ? Icons.ARROW_DOWN_LIGHT : Icons.ARROW_RIGHT_LIGHT}
/>
</div>
) : (
<>
<SVGIcons className="drag-icon" icon={Icons.DRAG} />
<div className="expand-cell-empty-icon-container" />
</>
),
[]
);
const expandableConfig: ExpandableConfig<Team> = useMemo(
() => ({
onExpand: (isOpen, record) => {
if (isOpen && isEmpty(record.children)) {
onTeamExpand(false, record.fullyQualifiedName, true);
}
}}
/>
},
expandIcon: ({ expanded, onExpand, expandable, record }) =>
tableExpandableIconData({ expanded, onExpand, expandable, record }),
}),
[onTeamExpand, tableExpandableIconData]
);
return (
<>
<DndProvider backend={HTML5Backend}>
<Table
bordered
className="teams-list-table"
columns={columns}
components={TABLE_CONSTANTS}
data-testid="team-hierarchy-table"
dataSource={data}
expandable={expandableConfig}
loading={isTableLoading}
pagination={false}
rowKey="name"
size="small"
onRow={(record, index) => {
const attr = {
index,
handleMoveRow,
record,
};
return attr as DraggableBodyRowProps;
}}
/>
</DndProvider>
<Modal
centered
destroyOnClose
closable={false}
data-testid="confirmation-modal"
okText={t('label.confirm')}
title={t('label.move-the-team')}
visible={isModalOpen}
onCancel={() => setIsModalOpen(false)}
onOk={handleChangeTeam}>
<Typography.Text>
{t('message.team-transfer-message', {
from: movedTeam?.from?.name,
to: movedTeam?.to?.name,
})}
</Typography.Text>
</Modal>
</>
);
};

View File

@ -0,0 +1,48 @@
/*
* Copyright 2022 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 { Team } from '../../generated/entity/teams/team';
export interface TeamHierarchyProps {
currentTeam?: Team;
data: Team[];
onTeamExpand: (
loading?: boolean,
parentTeam?: string,
updateChildNode?: boolean
) => void;
}
export interface DraggableBodyRowProps
extends React.HTMLAttributes<HTMLTableRowElement> {
index: number;
handleMoveRow: (dragRecord: Team, dropRecord: Team) => void;
record: Team;
}
export interface MovedTeamProps {
from: Team;
to: Team;
}
export interface DragCollectProps {
getItem: () => { index: number };
isOver: (options?: { shallow?: boolean }) => boolean;
}
export interface TableExpandableDataProps {
expanded: boolean;
onExpand: (record: Team, event: React.MouseEvent<HTMLElement>) => void;
expandable: boolean;
record: Team;
}

View File

@ -54,16 +54,40 @@
td {
border-right: none;
background: @white;
margin-right: 10px;
.drag-icon {
width: 12px;
height: 12px;
margin-right: 6px;
display: inline-flex;
visibility: hidden;
}
.expand-icon {
cursor: pointer;
}
.expand-cell-icon-container {
display: inline-flex;
margin-right: 8px;
}
.expand-cell-empty-icon-container {
width: 16px;
height: 16px;
display: inline-flex;
}
}
.ant-table-cell-with-append {
display: flex;
align-items: center;
}
.ant-table-cell-row-hover {
background: @body-dark-bg-color;
.drag-icon {
visibility: initial;
}
}
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright 2022 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 DraggableBodyRow from '../components/TeamDetails/DraggableBodyRow';
export const DRAGGABLE_BODY_ROW = 'DraggableBodyRow';
export const TABLE_CONSTANTS = {
body: {
row: DraggableBodyRow,
},
};

View File

@ -17,7 +17,7 @@ declare module 'Models' {
import { TagLabel } from '../generated/type/tagLabel';
import { Paging } from './../generated/type/paging';
export interface RestoreEntitiesRequestType {
export interface RestoreRequestType {
id: string;
}

View File

@ -381,6 +381,7 @@
"hide": "Hide",
"restore-team": "Restore Team",
"remove": "Remove",
"move-the-team": "Move the Team",
"data-insight-plural": "Data Insights",
"configure-entity": "Configure {{entity}}",
"name-lowercase": "name",
@ -536,6 +537,8 @@
"delete-message-question-mark": "Delete Message?",
"view-deleted-teams": "View all the Deleted Teams, which come under this Team.",
"restore-deleted-team": " Restoring the Team will add all the metadata back to OpenMetadata",
"team-moved-success": "Team moved successfully",
"team-transfer-message": "Click on Confirm if youd like to move {{from}} team under {{to}} team.",
"create-new-glossary-guide": "A Glossary is a controlled vocabulary used to define the concepts and terminology in an organization. Glossaries can be specific to a certain domain (for e.g., Business Glossary, Technical Glossary). In the glossary, the standard terms and concepts can be defined along with the synonyms, and related terms. Control can be established over how and who can add the terms in the glossary.",
"no-notification-found": "No notifications Found",
"enables-end-to-end-metadata-management": "Enables end-to-end metadata management with data discovery, data duality, observability, and people collaboration",
@ -573,7 +576,8 @@
"leave-team-error": "Error while leaving the team!",
"no-query-available": "No query available",
"unexpected-response": "Unexpected response from server!",
"ingestion-workflow-operation-error": "Error while {{operation}} ingestion workflow {{displayName}}"
"ingestion-workflow-operation-error": "Error while {{operation}} ingestion workflow {{displayName}}",
"team-moved-error": "Error while moving team"
},
"url": {}
}

View File

@ -0,0 +1,191 @@
/* Copyright 2022 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.
*/
export const MOCK_CURRENT_TEAM = {
childrenCount: 22,
defaultRoles: [
{
deleted: false,
description:
'Users with Data Consumer role use different data assets for their day to day work.',
displayName: 'Data Consumer',
fullyQualifiedName: 'DataConsumer',
href: 'http://sandbox-beta.open-metadata.org/api/v1/roles/1497b0cf-cb5f-42c2-8e13-3ab68b90bfa0',
id: '1497b0cf-cb5f-42c2-8e13-3ab68b90bfa0',
name: 'DataConsumer',
type: 'role',
},
],
deleted: false,
description:
'Organization under which all the other team hierarchy is created',
displayName: 'Organization',
fullyQualifiedName: 'Organization',
href: 'http://sandbox-beta.open-metadata.org/api/v1/teams/f9578f16-363a-4788-80fb-d05816c9e169',
id: 'f9578f16-363a-4788-80fb-d05816c9e169',
inheritedRoles: [],
isJoinable: false,
name: 'Organization',
owns: [],
parents: [],
policies: [
{
deleted: false,
description: 'Policy for all the users of an organization.',
displayName: 'Organization Policy',
fullyQualifiedName: 'OrganizationPolicy',
href: 'http://sandbox-beta.open-metadata.org/api/v1/policies/09f4480c-ef57-4239-b2aa-c87053ad4f46',
id: '09f4480c-ef57-4239-b2aa-c87053ad4f46',
name: 'OrganizationPolicy',
type: 'policy',
},
],
teamType: undefined,
updatedAt: 1669719624263,
updatedBy: 'ag939431',
users: [],
version: 2.4,
};
export const MOCK_TABLE_DATA = [
{
children: [
{
children: undefined,
childrenCount: 0,
defaultRoles: [],
deleted: false,
fullyQualifiedName: 'Applications',
href: 'http://localhost:8585/api/v1/teams/eb4b1b74-d30e-4bfa-8409-dac15db3cc32',
id: 'eb4b1b74-d30e-4bfa-8409-dac15db3cc32',
inheritedRoles: [],
isJoinable: true,
key: 'Applications',
name: 'Applications',
owns: [],
teamType: 'Group',
updatedAt: 1670390160760,
updatedBy: 'admin',
userCount: 12,
version: 0.1,
type: 'BusinessUnit',
},
{
children: undefined,
childrenCount: 3,
defaultRoles: [],
deleted: false,
fullyQualifiedName: 'Infrastructure',
href: 'http://localhost:8585/api/v1/teams/c8cc8922-8917-4d33-94e3-d9d257dd8830',
id: 'c8cc8922-8917-4d33-94e3-d9d257dd8830',
inheritedRoles: [],
isJoinable: true,
key: 'Infrastructure',
name: 'Infrastructure',
owns: [],
teamType: 'BusinessUnit',
type: 'BusinessUnit',
updatedAt: 1670390159742,
updatedBy: 'admin',
userCount: 20,
version: 0.1,
},
],
childrenCount: 4,
defaultRoles: [],
deleted: false,
fullyQualifiedName: 'Engineering',
href: 'http://sandbox-beta.open-metadata.org/api/v1/teams/49d060a2-ad14-48a7-840a-836cd99aaffb',
id: '49d060a2-ad14-48a7-840a-836cd99aaffb',
inheritedRoles: [
{
deleted: false,
description:
'Users with Data Consumer role use different data assets for their day to day work.',
displayName: 'Data Consumer',
fullyQualifiedName: 'DataConsumer',
href: 'http://sandbox-beta.open-metadata.org/api/v1/roles/1497b0cf-cb5f-42c2-8e13-3ab68b90bfa0',
id: '1497b0cf-cb5f-42c2-8e13-3ab68b90bfa0',
name: 'DataConsumer',
type: 'role',
},
],
isJoinable: true,
key: 'Engineering',
name: 'Engineering',
owns: [],
teamType: undefined,
updatedAt: 1670312015218,
updatedBy: 'ingestion-bot',
userCount: 50,
},
{
children: [],
childrenCount: 3,
defaultRoles: [],
deleted: false,
fullyQualifiedName: 'Finance',
href: 'http://sandbox-beta.open-metadata.org/api/v1/teams/b201a5b2-b0e8-461d-9fa1-cd5212d09eee',
id: 'b201a5b2-b0e8-461d-9fa1-cd5212d09eee',
inheritedRoles: [
{
deleted: false,
description:
'Users with Data Consumer role use different data assets for their day to day work.',
displayName: 'Data Consumer',
fullyQualifiedName: 'DataConsumer',
href: 'http://sandbox-beta.open-metadata.org/api/v1/roles/1497b0cf-cb5f-42c2-8e13-3ab68b90bfa0',
id: '1497b0cf-cb5f-42c2-8e13-3ab68b90bfa0',
name: 'DataConsumer',
type: 'role',
},
],
isJoinable: true,
key: 'Finance',
name: 'Finance',
owns: [],
teamType: undefined,
updatedAt: 1670312016093,
updatedBy: 'ingestion-bot',
userCount: 2,
},
{
children: [],
childrenCount: 2,
defaultRoles: [],
deleted: false,
fullyQualifiedName: 'Legal',
href: 'http://sandbox-beta.open-metadata.org/api/v1/teams/e64afbd0-aab5-4aed-952d-c5a5b8ba06bb',
id: 'e64afbd0-aab5-4aed-952d-c5a5b8ba06bb',
inheritedRoles: [
{
deleted: false,
description:
'Users with Data Consumer role use different data assets for their day to day work.',
displayName: 'Legal',
fullyQualifiedName: 'Legal',
href: 'http://sandbox-beta.open-metadata.org/api/v1/roles/1497b0cf-cb5f-42c2-8e13-3ab68b90bfa0',
id: '1497b0cf-cb5f-42c2-8e13-3ab68b90bfa0',
name: 'Legal',
type: 'role',
},
],
isJoinable: true,
key: 'Marketing',
name: 'Marketing',
owns: [],
teamType: undefined,
updatedAt: 1670312016516,
updatedBy: 'ingestion-bot',
userCount: 3,
},
];

View File

@ -32,6 +32,9 @@
.d-flex {
display: flex;
}
.d-inline-flex {
display: inline-flex;
}
.inline {
display: inline;
}

View File

@ -63,6 +63,7 @@ import IconDeployIngestion from '../assets/svg/deploy-ingestion.svg';
import IconDocPrimary from '../assets/svg/doc-primary.svg';
import IconDocWhite from '../assets/svg/doc-white.svg';
import IconDoc from '../assets/svg/doc.svg';
import IconDrag from '../assets/svg/drag.svg';
import IconEditBlack from '../assets/svg/edit-black.svg';
import IconEditOutlinePrimary from '../assets/svg/edit-outline-primery.svg';
import IconEditPrimary from '../assets/svg/edit-primary.svg';
@ -397,6 +398,7 @@ export const Icons = {
HIDE_PASSWORD: 'hide-password',
ARROW_RIGHT_LIGHT: 'arrow-right-light',
ARROW_DOWN_LIGHT: 'arrow-down-light',
DRAG: 'drag',
};
const SVGIcons: FunctionComponent<Props> = ({ icon, ...props }: Props) => {
@ -509,6 +511,11 @@ const SVGIcons: FunctionComponent<Props> = ({ icon, ...props }: Props) => {
case Icons.FEED:
IconComponent = IconFeed;
break;
case Icons.DRAG:
IconComponent = IconDrag;
break;
case Icons.THUMBSUP:
IconComponent = IconThumbsUp;

View File

@ -12,7 +12,7 @@
*/
import { t } from 'i18next';
import { cloneDeep, isEmpty, isNil, isUndefined, omit } from 'lodash';
import { cloneDeep, isNil, isUndefined, omit } from 'lodash';
import { CreateTeam } from '../generated/api/teams/createTeam';
import {
EntityReference,
@ -53,10 +53,7 @@ const getEntityValue = (value: EntityReference[] | undefined) => {
return undefined;
};
export const getRestoreTeamData = (
team: Team,
childTeams: Team[]
): CreateTeam => {
export const getMovedTeamData = (team: Team, parents: string[]): CreateTeam => {
const userDetails = omit(cloneDeep(team), [
'id',
'fullyQualifiedName',
@ -70,18 +67,20 @@ export const getRestoreTeamData = (
'changeDescription',
'deleted',
'inheritedRoles',
'key',
]) as Team;
const { parents, policies, users, defaultRoles } = userDetails;
const { policies, users, defaultRoles, children } = userDetails;
return {
...userDetails,
teamType: userDetails.teamType as TeamType,
defaultRoles: getEntityValue(defaultRoles),
children: isEmpty(childTeams)
? undefined
: getEntityIdArray(childTeams as EntityReference[]),
parents: getEntityValue(parents),
children:
userDetails.teamType == TeamType.Group
? undefined
: getEntityValue(children),
parents: parents,
policies: getEntityValue(policies),
users: getEntityValue(users),
};

View File

@ -2856,6 +2856,21 @@
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.15.tgz#6a9d143f7f4f49db2d782f9e1c8839a29b43ae23"
integrity sha512-15spi3V28QdevleWBNXE4pIls3nFZmBbUGrW9IVPwiQczuSb9n76TCB4bsk8TSel+I1OkHEdPhu5QKMfY6rQHA==
"@react-dnd/asap@^4.0.0":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@react-dnd/asap/-/asap-4.0.1.tgz#5291850a6b58ce6f2da25352a64f1b0674871aab"
integrity sha512-kLy0PJDDwvwwTXxqTFNAAllPHD73AycE9ypWeln/IguoGBEbvFcPDbCV03G52bEcC5E+YgupBE0VzHGdC8SIXg==
"@react-dnd/invariant@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@react-dnd/invariant/-/invariant-2.0.0.tgz#09d2e81cd39e0e767d7da62df9325860f24e517e"
integrity sha512-xL4RCQBCBDJ+GRwKTFhGUW8GXa4yoDfJrPbLblc3U09ciS+9ZJXJ3Qrcs/x2IODOdIE5kQxvMmE2UKyqUictUw==
"@react-dnd/shallowequal@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@react-dnd/shallowequal/-/shallowequal-2.0.0.tgz#a3031eb54129f2c66b2753f8404266ec7bf67f0a"
integrity sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg==
"@rc-component/portal@^1.0.0-6", "@rc-component/portal@^1.0.0-8":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@rc-component/portal/-/portal-1.0.3.tgz#3aa2c229a7a20ac2412d864e8977e6377973416e"
@ -6511,6 +6526,24 @@ dlv@^1.1.3:
resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79"
integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==
dnd-core@14.0.0:
version "14.0.0"
resolved "https://registry.yarnpkg.com/dnd-core/-/dnd-core-14.0.0.tgz#973ab3470d0a9ac5a0fa9021c4feba93ad12347d"
integrity sha512-wTDYKyjSqWuYw3ZG0GJ7k+UIfzxTNoZLjDrut37PbcPGNfwhlKYlPUqjAKUjOOv80izshUiqusaKgJPItXSevA==
dependencies:
"@react-dnd/asap" "^4.0.0"
"@react-dnd/invariant" "^2.0.0"
redux "^4.0.5"
dnd-core@14.0.1:
version "14.0.1"
resolved "https://registry.yarnpkg.com/dnd-core/-/dnd-core-14.0.1.tgz#76d000e41c494983210fb20a48b835f81a203c2e"
integrity sha512-+PVS2VPTgKFPYWo3vAFEA8WPbTf7/xo43TifH9G8S1KqnrQu0o77A3unrF5yOugy4mIz7K5wAVFHUcha7wsz6A==
dependencies:
"@react-dnd/asap" "^4.0.0"
"@react-dnd/invariant" "^2.0.0"
redux "^4.1.1"
dns-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"
@ -12417,6 +12450,24 @@ react-copy-to-clipboard@^5.0.4:
copy-to-clipboard "^3"
prop-types "^15.5.8"
react-dnd-html5-backend@14.0.2:
version "14.0.2"
resolved "https://registry.yarnpkg.com/react-dnd-html5-backend/-/react-dnd-html5-backend-14.0.2.tgz#25019388f6abdeeda3a6fea835dff155abb2085c"
integrity sha512-QgN6rYrOm4UUj6tIvN8ovImu6uP48xBXF2rzVsp6tvj6d5XQ7OjHI4SJ/ZgGobOneRAU3WCX4f8DGCYx0tuhlw==
dependencies:
dnd-core "14.0.1"
react-dnd@14.0.2:
version "14.0.2"
resolved "https://registry.yarnpkg.com/react-dnd/-/react-dnd-14.0.2.tgz#57266baec92b887301f81fa3b77f87168d159733"
integrity sha512-JoEL78sBCg8SzjOKMlkR70GWaPORudhWuTNqJ56lb2P8Vq0eM2+er3ZrMGiSDhOmzaRPuA9SNBz46nHCrjn11A==
dependencies:
"@react-dnd/invariant" "^2.0.0"
"@react-dnd/shallowequal" "^2.0.0"
dnd-core "14.0.0"
fast-deep-equal "^3.1.3"
hoist-non-react-statics "^3.3.2"
react-dom@^16.14.0:
version "16.14.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.14.0.tgz#7ad838ec29a777fb3c75c3a190f661cf92ab8b89"
@ -12823,7 +12874,7 @@ reduce-css-calc@^2.1.8:
css-unit-converter "^1.1.1"
postcss-value-parser "^3.3.0"
redux@^4.0.0, redux@^4.1.0:
redux@^4.0.0, redux@^4.0.5, redux@^4.1.0, redux@^4.1.1:
version "4.2.0"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.0.tgz#46f10d6e29b6666df758780437651eeb2b969f13"
integrity sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==