feat(pipelines): 编辑和删除功能。
This commit is contained in:
parent
282366cd72
commit
611341a8ce
@ -5,6 +5,8 @@ export const LIST_PIPELINES = gql`
|
||||
pipelines: listPipelines(projectId: $projectId) {
|
||||
id
|
||||
name
|
||||
branch
|
||||
projectId
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -1,22 +1,45 @@
|
||||
.pipelineList {
|
||||
@apply bg-red-200;
|
||||
@apply bg-red-200 w-40;
|
||||
|
||||
& > header {
|
||||
@apply bg-white flex justify-between items-center;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
.list {
|
||||
@apply bg-gray-100 overflow-y-auto max-h-full;
|
||||
}
|
||||
|
||||
.item {
|
||||
@apply bg-gray-50;
|
||||
@apply bg-white text-gray-700;
|
||||
@apply border-l-4 border-white transition-colors;
|
||||
@apply px-2 py-1 my-px;
|
||||
@apply relative;
|
||||
small {
|
||||
@apply text-sm text-gray-500;
|
||||
svg {
|
||||
@apply mr-1 h-3 text-gray-300;
|
||||
}
|
||||
}
|
||||
}
|
||||
.activeItem {
|
||||
@apply border-red-500;
|
||||
}
|
||||
.addBtn {
|
||||
@apply bg-red-400 text-white;
|
||||
@apply py-1 px-2 m-2 rounded-lg;
|
||||
@apply py-1 px-2 m-1 rounded-lg mr-auto;
|
||||
@apply hover:bg-red-500;
|
||||
}
|
||||
.refetchBtn {
|
||||
.actionBtn {
|
||||
@apply text-red-400 flex items-center;
|
||||
@apply w-4 h-4 m-2 rounded-full;
|
||||
}
|
||||
@apply w-4 h-4 flex items-center justify-center overflow-hidden text-center m-1;
|
||||
svg {
|
||||
}
|
||||
}
|
||||
.editPanel {
|
||||
@apply absolute right-1 top-0 bottom-0;
|
||||
@apply flex flex-col items-center justify-evenly;
|
||||
@apply text-red-400 text-xs;
|
||||
button {
|
||||
@apply w-3;
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,13 @@
|
||||
// This file is automatically generated from your CSS. Any edits will be overwritten.
|
||||
declare namespace PipelineListScssNamespace {
|
||||
export interface IPipelineListScss {
|
||||
actionBtn: string;
|
||||
activeItem: string;
|
||||
addBtn: string;
|
||||
editPanel: string;
|
||||
item: string;
|
||||
list: string;
|
||||
pipelineList: string;
|
||||
refetchBtn: string;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,16 +1,29 @@
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { gql, useQuery, useMutation } from '@apollo/client';
|
||||
import { makeAutoObservable } from 'mobx';
|
||||
import { useLocalObservable, useObserver } from 'mobx-react-lite';
|
||||
import { useMemo } from 'preact/hooks';
|
||||
import { useLocalObservable } from 'mobx-react-lite';
|
||||
import { useCallback, useMemo } from 'preact/hooks';
|
||||
import { h } from 'preact';
|
||||
import { Pipeline } from '../../generated/graphql';
|
||||
import styles from './pipeline-list.scss';
|
||||
import classNames from 'classnames';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faRedoAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
import {
|
||||
faCodeBranch,
|
||||
faRedoAlt,
|
||||
faEdit,
|
||||
faEllipsisV,
|
||||
faTrash
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import { createOverlay } from '../commons/overlay/overlay';
|
||||
import { PipelineEditor } from '../../routes/pipelines/pipeline-editor';
|
||||
import { LIST_PIPELINES } from './pipeline-list.constants';
|
||||
import { Observer, observer } from 'mobx-react';
|
||||
|
||||
const DELETE_PIPELINE = gql`
|
||||
mutation DeletePipeline($id: String!) {
|
||||
deletePipeline(id: $id)
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
projectId: string;
|
||||
@ -19,36 +32,28 @@ interface Props {
|
||||
class Store {
|
||||
pipelines?: Pipeline[];
|
||||
currPipelineId?: string;
|
||||
editMode = false;
|
||||
constructor() {
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
|
||||
get items() {
|
||||
return this.pipelines?.map(pipeline => {
|
||||
const isActive = this.currPipelineId === pipeline.id;
|
||||
return (
|
||||
<li
|
||||
key={pipeline.id}
|
||||
className={classNames(
|
||||
{
|
||||
isActive
|
||||
},
|
||||
styles.item
|
||||
)}
|
||||
>
|
||||
<h3>{pipeline.name}</h3>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
}
|
||||
refetching = false;
|
||||
|
||||
setPipelines(pipelines: any[] | undefined) {
|
||||
this.pipelines = pipelines;
|
||||
}
|
||||
setRefetching(val: boolean) {
|
||||
this.refetching = val;
|
||||
}
|
||||
setCurrPipelineId(id: string) {
|
||||
this.currPipelineId = id;
|
||||
}
|
||||
toggleEditMode() {
|
||||
this.editMode = !this.editMode;
|
||||
}
|
||||
}
|
||||
|
||||
export const PipelineList = ({ projectId }: Props) => {
|
||||
const { data, refetch } = useQuery<{ pipelines: Pipeline[] }>(
|
||||
const { data, refetch, loading } = useQuery<{ pipelines: Pipeline[] }>(
|
||||
LIST_PIPELINES,
|
||||
{
|
||||
variables: {
|
||||
@ -63,25 +68,123 @@ export const PipelineList = ({ projectId }: Props) => {
|
||||
|
||||
useMemo(() => store.setPipelines(pipelines), [store, pipelines]);
|
||||
|
||||
const items = useObserver(() => store.items);
|
||||
|
||||
const addPipeline = () => {
|
||||
createOverlay({
|
||||
content: <PipelineEditor projectId={projectId} />
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<section className={styles.pipelineList}>
|
||||
const Header = observer(() => {
|
||||
const addPipeline = () => {
|
||||
createOverlay({
|
||||
content: <PipelineEditor projectId={projectId} />
|
||||
});
|
||||
};
|
||||
const onRefetchBtnClick = () => {
|
||||
store.setRefetching(true);
|
||||
refetch().finally(() => store.setRefetching(false));
|
||||
};
|
||||
return (
|
||||
<header>
|
||||
<button className={styles.addBtn} onClick={addPipeline}>
|
||||
Add
|
||||
</button>
|
||||
<button className={styles.refetchBtn} onClick={() => refetch()}>
|
||||
<FontAwesomeIcon icon={faRedoAlt} />
|
||||
<button className={styles.actionBtn} onClick={onRefetchBtnClick}>
|
||||
<FontAwesomeIcon
|
||||
className={classNames({
|
||||
'animate-spin': store.refetching
|
||||
})}
|
||||
icon={faRedoAlt}
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className={styles.actionBtn}
|
||||
onClick={() => store.toggleEditMode()}
|
||||
>
|
||||
<FontAwesomeIcon icon={faEllipsisV} />
|
||||
</button>
|
||||
</header>
|
||||
<ul>{items}</ul>
|
||||
);
|
||||
});
|
||||
const items = pipelines?.map(pipeline => (
|
||||
<Observer key={pipeline.id}>
|
||||
{(): any => <Item pipeline={pipeline} store={store} />}
|
||||
</Observer>
|
||||
));
|
||||
|
||||
return (
|
||||
<section className={styles.pipelineList}>
|
||||
<Header />
|
||||
<ul className={styles.list}>{items}</ul>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
const Item = observer(
|
||||
({ pipeline, store }: { pipeline: Pipeline; store: Store }) => {
|
||||
const [remove] = useMutation(DELETE_PIPELINE, {
|
||||
variables: { id: pipeline.id },
|
||||
update(cache) {
|
||||
const cacheData = cache.readQuery<{ pipelines: Pipeline[] }>({
|
||||
query: LIST_PIPELINES,
|
||||
variables: { projectId: pipeline.projectId }
|
||||
});
|
||||
const pipelines = cacheData?.pipelines?.slice();
|
||||
if (!pipelines) {
|
||||
return;
|
||||
}
|
||||
const index = pipelines.findIndex(
|
||||
(item: Pipeline) => item.id === pipeline.id
|
||||
);
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
pipelines.splice(index, 1);
|
||||
cache.writeQuery({
|
||||
query: LIST_PIPELINES,
|
||||
variables: { projectId: pipeline.projectId },
|
||||
data: {
|
||||
pipelines
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const isActive = store.currPipelineId === pipeline.id;
|
||||
const openModifyPanel = () => {
|
||||
createOverlay({
|
||||
content: (
|
||||
<PipelineEditor projectId={pipeline.projectId} id={pipeline.id} />
|
||||
)
|
||||
});
|
||||
};
|
||||
const onRemoveBtnClick = () => {
|
||||
remove();
|
||||
};
|
||||
const EditPanel = observer(() => {
|
||||
return store.editMode ? (
|
||||
<div className={styles.editPanel}>
|
||||
<button onClick={openModifyPanel}>
|
||||
<FontAwesomeIcon icon={faEdit} />
|
||||
</button>
|
||||
<button onClick={onRemoveBtnClick}>
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</button>
|
||||
</div>
|
||||
) : null;
|
||||
});
|
||||
return (
|
||||
<li
|
||||
key={pipeline.id}
|
||||
className={classNames(
|
||||
{
|
||||
[styles.activeItem]: isActive
|
||||
},
|
||||
styles.item
|
||||
)}
|
||||
onClick={() => store.setCurrPipelineId(pipeline.id)}
|
||||
>
|
||||
<h3>{pipeline.name}</h3>
|
||||
<small>
|
||||
<FontAwesomeIcon icon={faCodeBranch} />
|
||||
{pipeline.branch}
|
||||
</small>
|
||||
<Observer>{(): any => <EditPanel />}</Observer>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
@ -27,7 +27,6 @@ const FIND_PROJECTS = gql`
|
||||
sshUrl
|
||||
webUrl
|
||||
webHookSecret
|
||||
deletedAt
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -1,17 +1,18 @@
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import { Field, Form, Formik, useFormik } from 'formik';
|
||||
import { h } from 'preact';
|
||||
import { RoutableProps } from 'preact-router';
|
||||
import styles from './pipeline-editor.scss';
|
||||
import {
|
||||
CreatePipelineInput,
|
||||
Pipeline,
|
||||
UpdatePipelineInput
|
||||
} from '../../generated/graphql';
|
||||
import { useOverlay } from '../../components/commons/overlay/overlay';
|
||||
import { gql, useLazyQuery, useMutation } from '@apollo/client';
|
||||
import { useMemo } from 'preact/hooks';
|
||||
import classNames from 'classnames';
|
||||
import { WorkUnitMetadata, PipelineUnits } from '../../generated/graphql';
|
||||
import {
|
||||
CreatePipelineInput,
|
||||
UpdatePipelineInput,
|
||||
WorkUnitMetadata,
|
||||
PipelineUnits,
|
||||
Pipeline
|
||||
} from '../../generated/graphql';
|
||||
import { Message } from '../../components/commons/message/index';
|
||||
import { LIST_PIPELINES } from '../../components/pipelines/pipeline-list.constants';
|
||||
|
||||
@ -41,7 +42,7 @@ const FIND_PIPELINE = gql`
|
||||
query FindPipeline($id: String!) {
|
||||
pipeline: findPipeline(id: $id) {
|
||||
name
|
||||
id
|
||||
branch
|
||||
projectId
|
||||
workUnitMetadata {
|
||||
version
|
||||
@ -98,18 +99,28 @@ export const PipelineEditor = ({ projectId, id }: Props) => {
|
||||
};
|
||||
const isCreate = !id;
|
||||
|
||||
const [loadPipeline, { data: pipeline }] = useLazyQuery(FIND_PIPELINE);
|
||||
const [loadPipeline, { data }] = useLazyQuery(FIND_PIPELINE, {
|
||||
variables: { id }
|
||||
});
|
||||
useMemo(() => {
|
||||
if (!isCreate) {
|
||||
loadPipeline();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const formData: FormValues = pipeline ?? {
|
||||
name: 'test',
|
||||
projectId,
|
||||
workUnitMetadata: JSON.stringify(defaultWorkUnitMetadata, null, 2)
|
||||
};
|
||||
const formData: FormValues = useMemo(() => {
|
||||
if (data?.pipeline) {
|
||||
const fd = JSON.parse(JSON.stringify(data.pipeline));
|
||||
delete fd?.workUnitMetadata.__typename;
|
||||
fd.workUnitMetadata.units.forEach((item: any) => {
|
||||
delete item.__typename;
|
||||
});
|
||||
fd.workUnitMetadata = JSON.stringify(fd.workUnitMetadata, null, 2);
|
||||
delete fd.__typename;
|
||||
return fd;
|
||||
}
|
||||
return {};
|
||||
}, [data, data?.pipeline]);
|
||||
|
||||
const [createPipeline] = useMutation<{ pipeline: Pipeline }>(
|
||||
CREATE_PIPELINE,
|
||||
@ -167,10 +178,9 @@ export const PipelineEditor = ({ projectId, id }: Props) => {
|
||||
//
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section className={styles.editor}>
|
||||
<Formik initialValues={formData} onSubmit={submitForm}>
|
||||
<Formik initialValues={formData} onSubmit={submitForm} enableReinitialize>
|
||||
<Form className={styles.form}>
|
||||
<label>
|
||||
<span className={styles.label}>名称</span>
|
||||
|
@ -21,7 +21,6 @@ const FIND_PROJECT = gql`
|
||||
sshUrl
|
||||
webUrl
|
||||
webHookSecret
|
||||
deletedAt
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
Loading…
Reference in New Issue
Block a user