Skip to content

Commit

Permalink
feat(Groups): now we can create new group via UI [#634]
Browse files Browse the repository at this point in the history
  • Loading branch information
vrozaev authored and ma-efremoff committed Nov 22, 2024
1 parent 3a1fb71 commit 99766cf
Show file tree
Hide file tree
Showing 15 changed files with 472 additions and 155 deletions.
14 changes: 13 additions & 1 deletion packages/ui/src/shared/yt-types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,16 @@ export interface RemoveMaintenanceParams extends AddMaintenanceParams {
mine: boolean;
}

export interface AddMembersParams extends BaseBatchParams {
group: string;
member: string;
}

export interface RemoveMembersParams extends BaseBatchParams {
group: string;
member: string;
}

export type BatchSubRequest =
| SubRequest<'transfer_pool_resources', TransferPoolQuotaParams>
| SubRequest<'mount_table' | 'unmount_table' | 'freeze_table' | 'unfreeze_table', PathParams>
Expand All @@ -251,7 +261,9 @@ export type BatchSubRequest =
| SubRequest<'get_query', GetQueryParams>
| SubRequest<'get_query_result', GetQueryResultParams>
| SubRequest<'add_maintenance', AddMaintenanceParams>
| SubRequest<'remove_maintenance', RemoveMaintenanceParams>;
| SubRequest<'remove_maintenance', RemoveMaintenanceParams>
| SubRequest<'add_member', AddMembersParams>
| SubRequest<'remove_member', RemoveMembersParams>;

export type OutputFormat =
| {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Button from '../../../components/Button/Button';
import React, {useCallback} from 'react';
import {useDispatch} from 'react-redux';
import {openGroupEditorModal} from '../../../store/actions/groups';
import {isIdmAclAvailable} from '../../../config';

export const ShowCreateGroupModalButton: React.FC = () => {
const dispatch = useDispatch();
const onClick = useCallback(() => {
dispatch(openGroupEditorModal());
}, []);

if (isIdmAclAvailable()) {
return null;
}

return (
<Button view="action" onClick={onClick}>
Create new
</Button>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, {useCallback} from 'react';
import {useDispatch, useSelector} from 'react-redux';
import {RootState} from '../../../store/reducers';
import {Text} from '@gravity-ui/uikit';
import {closeGroupDeleteModal, deleteGroup, fetchGroups} from '../../../store/actions/groups';
import {YTDFDialog, makeErrorFields} from '../../../components/Dialog';
import {YTError} from '../../../types';

export const DeleteGroupModal: React.FC = () => {
const dispatch = useDispatch();
const [error, setError] = React.useState<YTError | undefined>(undefined);
const groupNameToDelete = useSelector(
(state: RootState) => state.groups.deleteGroup.groupNameToDelete,
);

const onClose = useCallback(() => {
dispatch(closeGroupDeleteModal());
}, [dispatch]);

const onAdd = useCallback(async () => {
try {
setError(undefined);

await deleteGroup({groupName: groupNameToDelete});

onClose();

// we don't need to wait for the end of the action
dispatch(fetchGroups());
} catch (error) {
setError(error as YTError);
}
}, [dispatch, groupNameToDelete, onClose]);

return (
<YTDFDialog
visible={Boolean(groupNameToDelete)}
headerProps={{title: `Delete group ${groupNameToDelete}`}}
pristineSubmittable={true}
onClose={onClose}
onAdd={onAdd}
fields={[
{
name: 'groupname',
type: 'block',
extras: {
children: (
<Text>
Are you sure you want to delete &quot;{groupNameToDelete}&quot;?
</Text>
),
},
},
...makeErrorFields([error]),
]}
/>
);
};
44 changes: 44 additions & 0 deletions packages/ui/src/ui/pages/groups/GroupActions/GroupActions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from 'react';

import {useDispatch} from 'react-redux';

import ClickableAttributesButton from '../../../components/AttributesButton/ClickableAttributesButton';

import Icon from '../../../components/Icon/Icon';
import Button from '../../../components/Button/Button';

import {openGroupEditorModal, showGroupDeleteModal} from '../../../store/actions/groups';
import {isIdmAclAvailable} from '../../../config';

type GroupActionsProps = {
className?: string;
groupname: string;
};

export function GroupActions({className, groupname}: GroupActionsProps) {
const dispatch = useDispatch();

const onEdit = React.useCallback(() => {
dispatch(openGroupEditorModal(groupname));
}, [groupname]);

const onRemove = React.useCallback(() => {
dispatch(showGroupDeleteModal(groupname));
}, [groupname]);

const allowDelete = !isIdmAclAvailable();

return (
<div className={className}>
<ClickableAttributesButton title={groupname} path={`//sys/groups/${groupname}`} />
<Button view="flat-secondary" size="m" onClick={onEdit}>
<Icon awesome="pencil-alt" />
</Button>
{allowDelete && (
<Button view="flat-secondary" size="m" onClick={onRemove}>
<Icon awesome="trash-bin" />
</Button>
)}
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import React from 'react';
import cn from 'bem-cn-lite';
import {ConnectedProps, connect} from 'react-redux';

import {closeGroupEditorModal, saveGroupData} from '../../../store/actions/groups';
import {closeGroupEditorModal, fetchGroups, saveGroupData} from '../../../store/actions/groups';
import {
getGroupEditorGroupIdm,
getGroupEditorGroupName,
Expand All @@ -19,6 +19,7 @@ import {
} from '../../../store/selectors/groups';
import type {RootState} from '../../../store/reducers';
import type {ResponsibleType, RoleConverted} from '../../../utils/acl/acl-types';
import {isIdmAclAvailable} from '../../../config';

import './GroupEditorDialog.scss';

Expand All @@ -27,6 +28,9 @@ const block = cn('group-editor-dialog');
interface GroupsPageTableProps extends ConnectedProps<typeof connector> {}

type FormValues = {
general: {
groupName: string;
};
details: {
idm: string;
size: string;
Expand All @@ -48,8 +52,8 @@ type FormValues = {
};

class GroupEditorDialog extends React.Component<GroupsPageTableProps> {
onSubmit = (form: FormApi<FormValues, Partial<FormValues>>) => {
const {groupName, saveGroupData} = this.props;
onSubmit = async (form: FormApi<FormValues, Partial<FormValues>>) => {
const {groupName: initialGroupName} = this.props;
const {values} = form.getState();
const {members, membersComment} = values.members;
const {added: membersToAdd, removed: membersToRemove} = extractChangedSubjects(members);
Expand All @@ -58,6 +62,8 @@ class GroupEditorDialog extends React.Component<GroupsPageTableProps> {
const {added: responsiblesToAdd, removed: responsiblesToRemove} =
extractChangedSubjects(responsibles);

const {groupName} = values.general;

let comment = '';
if (membersComment) {
comment += responsiblesComment ? '**COMMENT FOR MEMBERS**\n' : '';
Expand All @@ -68,14 +74,20 @@ class GroupEditorDialog extends React.Component<GroupsPageTableProps> {
comment += `${responsiblesComment}`;
}

return saveGroupData(
groupName,
membersToAdd,
membersToRemove,
responsiblesToAdd,
responsiblesToRemove,
comment,
).then(() => {});
return this.props
.saveGroupData({
initialGroupName,
groupName,
membersToAdd,
membersToRemove,
responsiblesToAdd,
responsiblesToRemove,
comment,
})
.then(() => {
return this.props.fetchGroups();
})
.then(() => {});
};

render() {
Expand All @@ -88,11 +100,14 @@ class GroupEditorDialog extends React.Component<GroupsPageTableProps> {
pristineSubmittable={false}
visible={visible}
headerProps={{
title: groupName,
title: this.isNewGroup() ? 'Create group' : `Group ${groupName}`,
}}
onClose={closeGroupEditorModal}
onAdd={this.onSubmit}
initialValues={{
general: {
groupName,
},
details: {
idm: String(idm || '-'),
size: String(members.length),
Expand All @@ -107,41 +122,58 @@ class GroupEditorDialog extends React.Component<GroupsPageTableProps> {
fields={[
{
type: 'tab-vertical',
name: 'details',
title: 'Details',
name: 'general',
title: 'General',
fields: [
{
name: 'idm',
type: 'plain',
caption: 'Idm managed',
},
{
name: 'size',
type: 'plain',
caption: 'Size',
},
],
},
{
type: 'tab-vertical',
name: 'responsibles',
title: 'Responsibles',
fields: [
{
name: 'responsibles',
type: 'acl-roles',
caption: 'Responsibles',
extras: {
placeholder: 'Enter login or name',
},
},
{
name: 'responsiblesComment',
type: 'textarea',
caption: 'Comment for IDM',
name: 'groupName',
type: 'text',
required: true,
caption: 'Group name',
},
],
},
...(isIdmAclAvailable()
? [
{
type: 'tab-vertical' as const,
name: 'details',
title: 'Details',
fields: [
{
name: 'idm',
type: 'plain' as const,
caption: 'Idm managed',
},
{
name: 'size',
type: 'plain' as const,
caption: 'Size',
},
],
},
{
type: 'tab-vertical' as const,
name: 'responsibles',
title: 'Responsibles',
fields: [
{
name: 'responsibles',
type: 'acl-roles' as const,
caption: 'Responsibles',
extras: {
placeholder: 'Enter login or name',
},
},
{
name: 'responsiblesComment',
type: 'textarea' as const,
caption: 'Comment for IDM',
},
],
},
]
: []),
{
type: 'tab-vertical',
name: 'members',
Expand All @@ -155,17 +187,26 @@ class GroupEditorDialog extends React.Component<GroupsPageTableProps> {
placeholder: 'Enter login or name',
},
},
{
name: 'membersComment',
type: 'textarea',
caption: 'Comment for IDM',
},

...(isIdmAclAvailable()
? [
{
name: 'membersComment',
type: 'textarea' as const,
caption: 'Comment for IDM',
},
]
: []),
],
},
]}
/>
);
}

private isNewGroup() {
return !this.props.groupName;
}
}

const mapStateToProps = (state: RootState) => {
Expand All @@ -184,6 +225,7 @@ const mapStateToProps = (state: RootState) => {
const mapDispatchToProps = {
closeGroupEditorModal,
saveGroupData,
fetchGroups,
};

const connector = connect(mapStateToProps, mapDispatchToProps);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
.groups-page-filters {
display: flex;
align-items: center;
justify-content: space-between;

&__item {
flex-grow: 1;
flex-shrink: 1;
Expand Down
Loading

0 comments on commit 99766cf

Please sign in to comment.