Skip to content

Commit

Permalink
feat(ui): Project Settings (#1914)
Browse files Browse the repository at this point in the history
Signed-off-by: Rafal Pelczar <[email protected]>
  • Loading branch information
rpelczar authored May 1, 2024
1 parent 9c14a33 commit 384c85a
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { createConnectQueryKey, useMutation } from '@connectrpc/connect-query';
import { useQueryClient } from '@tanstack/react-query';
import { Alert, Form, Input, Modal } from 'antd';
import React from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import yaml from 'yaml';

import { paths } from '@ui/config/paths';
import { ModalComponentProps } from '@ui/features/common/modal/modal-context';
import {
deleteResource,
listProjects
} from '@ui/gen/service/v1alpha1/service-KargoService_connectquery';

import { projectYAMLExample } from '../../list/utils/project-yaml-example';

export const DeleteProjectModal = ({ visible, hide }: ModalComponentProps) => {
const { name } = useParams();
const queryClient = useQueryClient();
const navigate = useNavigate();
const [inputValue, setInputValue] = React.useState('');

const { mutate, isPending } = useMutation(deleteResource, {
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: createConnectQueryKey(listProjects) });
navigate(paths.projects);
}
});

const onDelete = () => {
const textEncoder = new TextEncoder();
const manifest = {
...projectYAMLExample,
metadata: {
name
}
};

mutate({ manifest: textEncoder.encode(yaml.stringify(manifest)) });
};

return (
<Modal
destroyOnClose
open={visible}
title='Danger Zone'
onCancel={hide}
onOk={onDelete}
okText='Delete'
okButtonProps={{ loading: isPending, danger: true, disabled: name !== inputValue }}
>
<Alert
type='error'
banner
message='Are you sure you want to delete Project?'
className='mb-4'
showIcon={false}
/>
<Form layout='vertical' component='div'>
<Form.Item label='If yes, please type the project name below:'>
<Input
onChange={(e) => setInputValue(e.target.value)}
value={inputValue}
placeholder={name}
/>
</Form.Item>
</Form>
</Modal>
);
};
77 changes: 77 additions & 0 deletions ui/src/features/project/settings/components/edit-project-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { useMutation, useQuery } from '@connectrpc/connect-query';
import { zodResolver } from '@hookform/resolvers/zod';
import { Modal } from 'antd';
import type { JSONSchema4 } from 'json-schema';
import { useForm } from 'react-hook-form';
import { useParams } from 'react-router-dom';
import yaml from 'yaml';
import { z } from 'zod';

import { YamlEditor } from '@ui/features/common/code-editor/yaml-editor';
import { FieldContainer } from '@ui/features/common/form/field-container';
import { ModalComponentProps } from '@ui/features/common/modal/modal-context';
import schema from '@ui/gen/schema/projects.kargo.akuity.io_v1alpha1.json';
import {
getProject,
updateResource
} from '@ui/gen/service/v1alpha1/service-KargoService_connectquery';
import { RawFormat } from '@ui/gen/service/v1alpha1/service_pb';
import { decodeRawData } from '@ui/utils/decode-raw-data';
import { zodValidators } from '@ui/utils/validators';

import { projectYAMLExample } from '../../list/utils/project-yaml-example';

const formSchema = z.object({
value: zodValidators.requiredString
});

export const EditProjectModal = ({ visible, hide }: ModalComponentProps) => {
const { name } = useParams();
const { data, isLoading } = useQuery(getProject, { name, format: RawFormat.YAML });

const { mutateAsync, isPending } = useMutation(updateResource, {
onSuccess: () => hide()
});

const { control, handleSubmit } = useForm({
values: {
value: decodeRawData(data)
},
resolver: zodResolver(formSchema)
});

const onSubmit = handleSubmit(async (data) => {
const textEncoder = new TextEncoder();
await mutateAsync({
manifest: textEncoder.encode(data.value)
});
});

return (
<Modal
destroyOnClose
open={visible}
title='Edit Project'
width={680}
onCancel={hide}
onOk={onSubmit}
okText='Update'
okButtonProps={{ loading: isPending }}
>
<FieldContainer name='value' control={control}>
{({ field: { value, onChange } }) => (
<YamlEditor
label='YAML'
value={value}
onChange={(e) => onChange(e || '')}
height='500px'
schema={schema as JSONSchema4}
placeholder={yaml.stringify(projectYAMLExample)}
isLoading={isLoading}
isHideManagedFieldsDisplayed
/>
)}
</FieldContainer>
</Modal>
);
};
50 changes: 50 additions & 0 deletions ui/src/features/project/settings/project-settings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { faChevronDown, faCog, faPencil, faTrash } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, Dropdown, Space } from 'antd';

import { useModal } from '@ui/features/common/modal/use-modal';

import { DeleteProjectModal } from './components/delete-project-modal';
import { EditProjectModal } from './components/edit-project-modal';

export const ProjectSettings = () => {
const { show: showEditModal } = useModal(EditProjectModal);
const { show: showDeleteModal } = useModal(DeleteProjectModal);

return (
<Dropdown
menu={{
items: [
{
key: '1',
label: (
<>
<FontAwesomeIcon icon={faPencil} size='xs' className='mr-2' /> Edit
</>
),
onClick: () => showEditModal()
},
{
key: '2',
danger: true,
label: (
<>
<FontAwesomeIcon icon={faTrash} size='xs' className='mr-2' /> Delete
</>
),
onClick: () => showDeleteModal()
}
]
}}
placement='bottomRight'
trigger={['click']}
>
<Button icon={<FontAwesomeIcon icon={faCog} size='1x' />}>
<Space>
Project Settings
<FontAwesomeIcon icon={faChevronDown} size='xs' />
</Space>
</Button>
</Dropdown>
);
};
2 changes: 2 additions & 0 deletions ui/src/pages/project.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { AnalysisTemplatesList } from '@ui/features/project/analysis-templates/a
import { CredentialsList } from '@ui/features/project/credentials/credentials-list';
import { Events } from '@ui/features/project/events/events';
import { Pipelines } from '@ui/features/project/pipelines/pipelines';
import { ProjectSettings } from '@ui/features/project/settings/project-settings';

const tabs = {
pipelines: {
Expand Down Expand Up @@ -67,6 +68,7 @@ export const Project = ({ tab = 'pipelines' }: { tab?: ProjectTab }) => {
<div className='font-semibold mb-1 text-xs text-gray-600'>PROJECT</div>
<div className='text-2xl font-semibold'>{name}</div>
</div>
<ProjectSettings />
</div>
</div>
<Tabs
Expand Down

0 comments on commit 384c85a

Please sign in to comment.