feat(pipelines): 编辑和删除功能。
This commit is contained in:
parent
282366cd72
commit
611341a8ce
@ -5,6 +5,8 @@ export const LIST_PIPELINES = gql`
|
|||||||
pipelines: listPipelines(projectId: $projectId) {
|
pipelines: listPipelines(projectId: $projectId) {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
branch
|
||||||
|
projectId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -1,22 +1,45 @@
|
|||||||
.pipelineList {
|
.pipelineList {
|
||||||
@apply bg-red-200;
|
@apply bg-red-200 w-40;
|
||||||
|
|
||||||
& > header {
|
& > header {
|
||||||
@apply bg-white flex justify-between items-center;
|
@apply bg-white flex justify-between items-center;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.list {
|
||||||
|
@apply bg-gray-100 overflow-y-auto max-h-full;
|
||||||
|
}
|
||||||
|
|
||||||
.item {
|
.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 {
|
.addBtn {
|
||||||
@apply bg-red-400 text-white;
|
@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;
|
@apply hover:bg-red-500;
|
||||||
}
|
}
|
||||||
.refetchBtn {
|
.actionBtn {
|
||||||
@apply text-red-400 flex items-center;
|
@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.
|
// This file is automatically generated from your CSS. Any edits will be overwritten.
|
||||||
declare namespace PipelineListScssNamespace {
|
declare namespace PipelineListScssNamespace {
|
||||||
export interface IPipelineListScss {
|
export interface IPipelineListScss {
|
||||||
|
actionBtn: string;
|
||||||
|
activeItem: string;
|
||||||
addBtn: string;
|
addBtn: string;
|
||||||
|
editPanel: string;
|
||||||
item: string;
|
item: string;
|
||||||
|
list: string;
|
||||||
pipelineList: 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 { makeAutoObservable } from 'mobx';
|
||||||
import { useLocalObservable, useObserver } from 'mobx-react-lite';
|
import { useLocalObservable } from 'mobx-react-lite';
|
||||||
import { useMemo } from 'preact/hooks';
|
import { useCallback, useMemo } from 'preact/hooks';
|
||||||
import { h } from 'preact';
|
import { h } from 'preact';
|
||||||
import { Pipeline } from '../../generated/graphql';
|
import { Pipeline } from '../../generated/graphql';
|
||||||
import styles from './pipeline-list.scss';
|
import styles from './pipeline-list.scss';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
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 { createOverlay } from '../commons/overlay/overlay';
|
||||||
import { PipelineEditor } from '../../routes/pipelines/pipeline-editor';
|
import { PipelineEditor } from '../../routes/pipelines/pipeline-editor';
|
||||||
import { LIST_PIPELINES } from './pipeline-list.constants';
|
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 {
|
interface Props {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
@ -19,36 +32,28 @@ interface Props {
|
|||||||
class Store {
|
class Store {
|
||||||
pipelines?: Pipeline[];
|
pipelines?: Pipeline[];
|
||||||
currPipelineId?: string;
|
currPipelineId?: string;
|
||||||
|
editMode = false;
|
||||||
constructor() {
|
constructor() {
|
||||||
makeAutoObservable(this);
|
makeAutoObservable(this);
|
||||||
}
|
}
|
||||||
|
refetching = false;
|
||||||
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>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setPipelines(pipelines: any[] | undefined) {
|
setPipelines(pipelines: any[] | undefined) {
|
||||||
this.pipelines = pipelines;
|
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) => {
|
export const PipelineList = ({ projectId }: Props) => {
|
||||||
const { data, refetch } = useQuery<{ pipelines: Pipeline[] }>(
|
const { data, refetch, loading } = useQuery<{ pipelines: Pipeline[] }>(
|
||||||
LIST_PIPELINES,
|
LIST_PIPELINES,
|
||||||
{
|
{
|
||||||
variables: {
|
variables: {
|
||||||
@ -63,25 +68,123 @@ export const PipelineList = ({ projectId }: Props) => {
|
|||||||
|
|
||||||
useMemo(() => store.setPipelines(pipelines), [store, pipelines]);
|
useMemo(() => store.setPipelines(pipelines), [store, pipelines]);
|
||||||
|
|
||||||
const items = useObserver(() => store.items);
|
const Header = observer(() => {
|
||||||
|
const addPipeline = () => {
|
||||||
const addPipeline = () => {
|
createOverlay({
|
||||||
createOverlay({
|
content: <PipelineEditor projectId={projectId} />
|
||||||
content: <PipelineEditor projectId={projectId} />
|
});
|
||||||
});
|
};
|
||||||
};
|
const onRefetchBtnClick = () => {
|
||||||
|
store.setRefetching(true);
|
||||||
return (
|
refetch().finally(() => store.setRefetching(false));
|
||||||
<section className={styles.pipelineList}>
|
};
|
||||||
|
return (
|
||||||
<header>
|
<header>
|
||||||
<button className={styles.addBtn} onClick={addPipeline}>
|
<button className={styles.addBtn} onClick={addPipeline}>
|
||||||
Add
|
Add
|
||||||
</button>
|
</button>
|
||||||
<button className={styles.refetchBtn} onClick={() => refetch()}>
|
<button className={styles.actionBtn} onClick={onRefetchBtnClick}>
|
||||||
<FontAwesomeIcon icon={faRedoAlt} />
|
<FontAwesomeIcon
|
||||||
|
className={classNames({
|
||||||
|
'animate-spin': store.refetching
|
||||||
|
})}
|
||||||
|
icon={faRedoAlt}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={styles.actionBtn}
|
||||||
|
onClick={() => store.toggleEditMode()}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faEllipsisV} />
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</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>
|
</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
|
sshUrl
|
||||||
webUrl
|
webUrl
|
||||||
webHookSecret
|
webHookSecret
|
||||||
deletedAt
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
import { Field, Form, Formik } from 'formik';
|
import { Field, Form, Formik, useFormik } from 'formik';
|
||||||
import { h } from 'preact';
|
import { h } from 'preact';
|
||||||
import { RoutableProps } from 'preact-router';
|
import { RoutableProps } from 'preact-router';
|
||||||
import styles from './pipeline-editor.scss';
|
import styles from './pipeline-editor.scss';
|
||||||
import {
|
|
||||||
CreatePipelineInput,
|
|
||||||
Pipeline,
|
|
||||||
UpdatePipelineInput
|
|
||||||
} from '../../generated/graphql';
|
|
||||||
import { useOverlay } from '../../components/commons/overlay/overlay';
|
import { useOverlay } from '../../components/commons/overlay/overlay';
|
||||||
import { gql, useLazyQuery, useMutation } from '@apollo/client';
|
import { gql, useLazyQuery, useMutation } from '@apollo/client';
|
||||||
import { useMemo } from 'preact/hooks';
|
import { useMemo } from 'preact/hooks';
|
||||||
import classNames from 'classnames';
|
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 { Message } from '../../components/commons/message/index';
|
||||||
import { LIST_PIPELINES } from '../../components/pipelines/pipeline-list.constants';
|
import { LIST_PIPELINES } from '../../components/pipelines/pipeline-list.constants';
|
||||||
|
|
||||||
@ -41,7 +42,7 @@ const FIND_PIPELINE = gql`
|
|||||||
query FindPipeline($id: String!) {
|
query FindPipeline($id: String!) {
|
||||||
pipeline: findPipeline(id: $id) {
|
pipeline: findPipeline(id: $id) {
|
||||||
name
|
name
|
||||||
id
|
branch
|
||||||
projectId
|
projectId
|
||||||
workUnitMetadata {
|
workUnitMetadata {
|
||||||
version
|
version
|
||||||
@ -98,18 +99,28 @@ export const PipelineEditor = ({ projectId, id }: Props) => {
|
|||||||
};
|
};
|
||||||
const isCreate = !id;
|
const isCreate = !id;
|
||||||
|
|
||||||
const [loadPipeline, { data: pipeline }] = useLazyQuery(FIND_PIPELINE);
|
const [loadPipeline, { data }] = useLazyQuery(FIND_PIPELINE, {
|
||||||
|
variables: { id }
|
||||||
|
});
|
||||||
useMemo(() => {
|
useMemo(() => {
|
||||||
if (!isCreate) {
|
if (!isCreate) {
|
||||||
loadPipeline();
|
loadPipeline();
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const formData: FormValues = pipeline ?? {
|
const formData: FormValues = useMemo(() => {
|
||||||
name: 'test',
|
if (data?.pipeline) {
|
||||||
projectId,
|
const fd = JSON.parse(JSON.stringify(data.pipeline));
|
||||||
workUnitMetadata: JSON.stringify(defaultWorkUnitMetadata, null, 2)
|
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 }>(
|
const [createPipeline] = useMutation<{ pipeline: Pipeline }>(
|
||||||
CREATE_PIPELINE,
|
CREATE_PIPELINE,
|
||||||
@ -167,10 +178,9 @@ export const PipelineEditor = ({ projectId, id }: Props) => {
|
|||||||
//
|
//
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.editor}>
|
<section className={styles.editor}>
|
||||||
<Formik initialValues={formData} onSubmit={submitForm}>
|
<Formik initialValues={formData} onSubmit={submitForm} enableReinitialize>
|
||||||
<Form className={styles.form}>
|
<Form className={styles.form}>
|
||||||
<label>
|
<label>
|
||||||
<span className={styles.label}>名称</span>
|
<span className={styles.label}>名称</span>
|
||||||
|
@ -21,7 +21,6 @@ const FIND_PROJECT = gql`
|
|||||||
sshUrl
|
sshUrl
|
||||||
webUrl
|
webUrl
|
||||||
webHookSecret
|
webHookSecret
|
||||||
deletedAt
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
Loading…
Reference in New Issue
Block a user