You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by li...@apache.org on 2023/05/29 02:07:19 UTC
[incubator-devlake] branch main updated: feat(config-ui): use new design for bp and project detail page (#5297)
This is an automated email from the ASF dual-hosted git repository.
likyh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git
The following commit(s) were added to refs/heads/main by this push:
new 376f0e873 feat(config-ui): use new design for bp and project detail page (#5297)
376f0e873 is described below
commit 376f0e8733f2eca4fe4395d5611300edf8e2dd42
Author: 青湛 <0x...@gmail.com>
AuthorDate: Mon May 29 10:07:14 2023 +0800
feat(config-ui): use new design for bp and project detail page (#5297)
* feat(config-ui): add new component form-item
* refactor(config-ui): remove blueprint-connection-add page
* feat(config-ui): add new component buttons
* refactor(config-ui): use data-scope-select to replace data-scope and data-scope-form
* refactor(config-ui): use new design for bp detail page
* refactor(config-ui): use new design for project detail page
---
config-ui/src/App.tsx | 8 -
.../styled.ts => components/buttons/index.tsx} | 22 ++-
.../step-3.tsx => components/form-item/index.tsx} | 32 ++--
.../components => components/form-item}/styled.ts | 16 +-
config-ui/src/components/index.ts | 2 +
config-ui/src/global.d.ts | 6 +-
.../src/pages/blueprint/connection-add/api.ts | 24 ---
.../blueprint/connection-add/components/index.ts | 21 ---
.../blueprint/connection-add/components/step-1.tsx | 69 -------
.../blueprint/connection-add/components/step-2.tsx | 44 -----
.../src/pages/blueprint/connection-add/context.tsx | 137 --------------
.../src/pages/blueprint/connection-add/index.tsx | 62 ------
.../pages/blueprint/connection-detail/index.tsx | 9 +-
config-ui/src/pages/blueprint/detail/api.ts | 4 +-
.../blueprint/detail/blueprint-detail-page.tsx | 15 +-
.../pages/blueprint/detail/blueprint-detail.tsx | 83 +++++---
.../components/add-connection-dialog/index.tsx | 71 +++++++
.../advanced-editor/example/customize.ts | 0
.../components/advanced-editor/example/dbt.ts | 0
.../components/advanced-editor/example/feishu.ts | 0
.../components/advanced-editor/example/general.ts | 0
.../advanced-editor/example/gitextractor.ts | 0
.../components/advanced-editor/example/github.ts | 0
.../components/advanced-editor/example/gitlab.ts | 0
.../components/advanced-editor/example/index.ts | 0
.../components/advanced-editor/example/jenkins.ts | 0
.../components/advanced-editor/example/jira.ts | 0
.../components/advanced-editor/example/refdiff.ts | 0
.../advanced-editor/example/starrocks.ts | 0
.../components/advanced-editor/example/tapd.ts | 0
.../components/advanced-editor/example/zentao.ts | 0
.../components/advanced-editor/index.tsx | 0
.../detail/components/connection-list/index.tsx | 75 --------
.../detail/components/connection-list/styled.ts | 55 ------
.../src/pages/blueprint/detail/components/index.ts | 3 +-
.../{ => detail}/components/sync-policy/index.tsx | 0
.../components/sync-policy/start-from-selector.tsx | 0
.../{ => detail}/components/sync-policy/styled.ts | 0
.../components/update-policy-dialog/index.tsx | 7 +-
.../pages/blueprint/detail/panel/configuration.tsx | 190 ++++++++++++-------
.../src/pages/blueprint/detail/panel/status.tsx | 28 ++-
config-ui/src/pages/blueprint/detail/styled.ts | 88 ++++-----
config-ui/src/pages/blueprint/detail/use-detail.ts | 90 ---------
config-ui/src/pages/blueprint/index.ts | 1 -
config-ui/src/pages/blueprint/types.ts | 7 +-
config-ui/src/pages/project/detail/api.ts | 16 +-
config-ui/src/pages/project/detail/index.tsx | 39 ++--
.../src/pages/project/detail/panel/blueprint.tsx | 50 -----
config-ui/src/pages/project/detail/panel/index.ts | 21 ---
.../src/pages/project/detail/panel/settings.tsx | 67 -------
.../src/pages/project/detail/settings-panel.tsx | 95 ++++++++++
config-ui/src/pages/project/detail/styled.ts | 14 --
config-ui/src/pages/project/detail/use-project.ts | 162 ----------------
.../incoming-webhooks.tsx => webhooks-panel.tsx} | 91 +++++++--
config-ui/src/pages/project/home/index.tsx | 4 +-
config-ui/src/pages/project/index.ts | 2 +
config-ui/src/pages/project/{detail => }/types.ts | 6 +-
config-ui/src/pages/project/utils.ts | 2 +
.../src/plugins/components/data-scope-form/api.ts | 34 ----
.../plugins/components/data-scope-form/index.tsx | 210 ---------------------
.../components/data-scope-select/api.ts} | 6 +-
.../plugins/components/data-scope-select/index.tsx | 96 ++++++++++
.../styled.ts | 14 +-
.../src/plugins/components/data-scope/index.tsx | 173 -----------------
.../src/plugins/components/data-scope/styled.ts | 63 -------
config-ui/src/plugins/components/index.ts | 3 +-
.../plugins/components/transformation/index.tsx | 6 +-
67 files changed, 679 insertions(+), 1664 deletions(-)
diff --git a/config-ui/src/App.tsx b/config-ui/src/App.tsx
index 3998e95c1..cc4b18aeb 100644
--- a/config-ui/src/App.tsx
+++ b/config-ui/src/App.tsx
@@ -28,7 +28,6 @@ import {
ProjectDetailPage,
BlueprintHomePage,
BlueprintDetailPage,
- BlueprintConnectioAddPage,
BlueprintConnectionDetailPage,
} from '@/pages';
import { history } from '@/utils';
@@ -69,15 +68,8 @@ function App() {
<Route exact path="/connections/:plugin/:id" component={() => <ConnectionDetailPage />} />
<Route exact path="/projects" component={() => <ProjectHomePage />} />
<Route exact path="/projects/:pname" component={() => <ProjectDetailPage />} />
- <Route
- exact
- path="/projects/:pname/:bid/connection-add"
- component={() => <BlueprintConnectioAddPage />}
- />
- <Route exact path="/projects/:pname/:bid/:unique" component={() => <BlueprintConnectionDetailPage />} />
<Route exact path="/blueprints" component={() => <BlueprintHomePage />} />
<Route exact path="/blueprints/:id" component={() => <BlueprintDetailPage />} />
- <Route exact path="/blueprints/:bid/connection-add" component={() => <BlueprintConnectioAddPage />} />
<Route exact path="/blueprints/:bid/:unique" component={() => <BlueprintConnectionDetailPage />} />
</Switch>
</BaseLayout>
diff --git a/config-ui/src/pages/blueprint/connection-add/components/styled.ts b/config-ui/src/components/buttons/index.tsx
similarity index 76%
copy from config-ui/src/pages/blueprint/connection-add/components/styled.ts
copy to config-ui/src/components/buttons/index.tsx
index 229370bff..8734aa563 100644
--- a/config-ui/src/pages/blueprint/connection-add/components/styled.ts
+++ b/config-ui/src/components/buttons/index.tsx
@@ -18,13 +18,21 @@
import styled from 'styled-components';
-export const Wrapper = styled.div`
- margin-top: 36px;
-`;
-
-export const Action = styled.div`
+const Wrapper = styled.div`
display: flex;
align-items: center;
- justify-content: space-between;
- margin-top: 36px;
+ justify-content: end;
+ margin-top: 24px;
+
+ .bp4-button + .bp4-button {
+ margin-left: 8px;
+ }
`;
+
+interface Props {
+ children: React.ReactNode;
+}
+
+export const Buttons = ({ children }: Props) => {
+ return <Wrapper>{children}</Wrapper>;
+};
diff --git a/config-ui/src/pages/blueprint/connection-add/components/step-3.tsx b/config-ui/src/components/form-item/index.tsx
similarity index 64%
rename from config-ui/src/pages/blueprint/connection-add/components/step-3.tsx
rename to config-ui/src/components/form-item/index.tsx
index d0c9aee5d..0ee800c6d 100644
--- a/config-ui/src/pages/blueprint/connection-add/components/step-3.tsx
+++ b/config-ui/src/components/form-item/index.tsx
@@ -16,27 +16,25 @@
*
*/
-import { Transformation } from '@/plugins';
-
-import { useConnectionAdd } from '../context';
+import { FormGroup } from '@blueprintjs/core';
import * as S from './styled';
-export const Step3 = () => {
- const { connection, onPrev, saving, onSave } = useConnectionAdd();
-
- if (!connection) {
- return null;
- }
+interface Props {
+ label?: React.ReactNode;
+ subLabel?: React.ReactNode;
+ required?: boolean;
+ children: React.ReactNode;
+}
+export const FormItem = ({ label, subLabel, required, children }: Props) => {
return (
- <S.Wrapper>
- <Transformation
- connections={[connection]}
- submitBtnProps={{ text: 'Save', loading: saving }}
- onCancel={onPrev}
- onNext={onSave}
- />
- </S.Wrapper>
+ <FormGroup
+ label={<S.Label>{label}</S.Label>}
+ subLabel={<S.subLabel>{subLabel}</S.subLabel>}
+ labelInfo={required ? <S.LabelInfo>*</S.LabelInfo> : null}
+ >
+ {children}
+ </FormGroup>
);
};
diff --git a/config-ui/src/pages/blueprint/connection-add/components/styled.ts b/config-ui/src/components/form-item/styled.ts
similarity index 82%
rename from config-ui/src/pages/blueprint/connection-add/components/styled.ts
rename to config-ui/src/components/form-item/styled.ts
index 229370bff..291e5b3e9 100644
--- a/config-ui/src/pages/blueprint/connection-add/components/styled.ts
+++ b/config-ui/src/components/form-item/styled.ts
@@ -18,13 +18,15 @@
import styled from 'styled-components';
-export const Wrapper = styled.div`
- margin-top: 36px;
+export const Label = styled.label`
+ font-size: 16px;
+ font-weight: 600;
`;
-export const Action = styled.div`
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-top: 36px;
+export const LabelInfo = styled.i`
+ color: #ff8b8b;
+`;
+
+export const subLabel = styled.p`
+ margin: 0;
`;
diff --git a/config-ui/src/components/index.ts b/config-ui/src/components/index.ts
index c5a8ae8e4..b7582018b 100644
--- a/config-ui/src/components/index.ts
+++ b/config-ui/src/components/index.ts
@@ -18,10 +18,12 @@
export * from './action';
export * from './alert';
+export * from './buttons';
export * from './card';
export * from './dialog';
export * from './divider';
export { default as ErrorBoundary } from './error-boundary';
+export * from './form-item';
export * from './inspector';
export * from './loading';
export * from './logo';
diff --git a/config-ui/src/global.d.ts b/config-ui/src/global.d.ts
index fbfabde2f..a1c271815 100644
--- a/config-ui/src/global.d.ts
+++ b/config-ui/src/global.d.ts
@@ -24,11 +24,7 @@ type MixConnection = {
connectionId: ID;
name: string;
icon: string;
- scope: Array<{
- id: string;
- entities: string[];
- }>;
- origin: Array<any>;
+ scope: Array<any>;
transformationType?: 'none' | 'for-connection' | 'for-scope';
};
diff --git a/config-ui/src/pages/blueprint/connection-add/api.ts b/config-ui/src/pages/blueprint/connection-add/api.ts
deleted file mode 100644
index 0abf6526e..000000000
--- a/config-ui/src/pages/blueprint/connection-add/api.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 { request } from '@/utils';
-
-export const getBlueprint = (id: ID) => request(`/blueprints/${id}`);
-
-export const updateBlueprint = (id: ID, payload: any) =>
- request(`/blueprints/${id}`, { method: 'patch', data: payload });
diff --git a/config-ui/src/pages/blueprint/connection-add/components/index.ts b/config-ui/src/pages/blueprint/connection-add/components/index.ts
deleted file mode 100644
index 6ffb0bdf8..000000000
--- a/config-ui/src/pages/blueprint/connection-add/components/index.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 * from './step-1';
-export * from './step-2';
-export * from './step-3';
diff --git a/config-ui/src/pages/blueprint/connection-add/components/step-1.tsx b/config-ui/src/pages/blueprint/connection-add/components/step-1.tsx
deleted file mode 100644
index 3a1bb2263..000000000
--- a/config-ui/src/pages/blueprint/connection-add/components/step-1.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 { Button, Intent } from '@blueprintjs/core';
-
-import { Table } from '@/components';
-import { useConnections } from '@/hooks';
-import { getPluginConfig } from '@/plugins';
-
-import { useConnectionAdd } from '../context';
-
-import * as S from './styled';
-
-export const Step1 = () => {
- const { filter, connection, onChangeConnection, onCancel, onNext } = useConnectionAdd();
- const { connections, onGet } = useConnections({ filter, filterBeta: true, filterPlugin: ['webhook'] });
-
- return (
- <S.Wrapper>
- <Table
- columns={[{ title: 'Data Connection', dataIndex: 'name', key: 'name' }]}
- dataSource={connections}
- rowSelection={{
- rowKey: 'unique',
- type: 'radio',
- selectedRowKeys: connection?.unique ? [connection?.unique] : [],
- onChange: (selectedRowKeys) => {
- const unique = selectedRowKeys[0];
- const connection = onGet(unique);
- const config = getPluginConfig(connection.plugin);
- onChangeConnection({
- unique: connection.unique,
- plugin: connection.plugin,
- connectionId: connection.id,
- name: connection.name,
- icon: connection.icon,
- scope: [],
- origin: [],
- transformationType: config.transformationType,
- });
- },
- }}
- />
- <S.Action>
- <Button outlined intent={Intent.PRIMARY} onClick={onCancel}>
- Cancel
- </Button>
- <Button intent={Intent.PRIMARY} disabled={!connection} onClick={onNext}>
- Next Step
- </Button>
- </S.Action>
- </S.Wrapper>
- );
-};
diff --git a/config-ui/src/pages/blueprint/connection-add/components/step-2.tsx b/config-ui/src/pages/blueprint/connection-add/components/step-2.tsx
deleted file mode 100644
index 6675e4483..000000000
--- a/config-ui/src/pages/blueprint/connection-add/components/step-2.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 { DataScope } from '@/plugins';
-
-import { useConnectionAdd } from '../context';
-
-import * as S from './styled';
-
-export const Step2 = () => {
- const { connection, onChangeConnection, onPrev, onNext } = useConnectionAdd();
-
- if (!connection) {
- return null;
- }
-
- return (
- <S.Wrapper>
- <DataScope
- connections={[connection]}
- cancelBtnProps={{ text: 'Previous Step' }}
- submitBtnProps={{ text: 'Next Step' }}
- onCancel={onPrev}
- onSubmit={(connections) => onChangeConnection(connections[0])}
- onNext={onNext}
- />
- </S.Wrapper>
- );
-};
diff --git a/config-ui/src/pages/blueprint/connection-add/context.tsx b/config-ui/src/pages/blueprint/connection-add/context.tsx
deleted file mode 100644
index 9c4ee3c45..000000000
--- a/config-ui/src/pages/blueprint/connection-add/context.tsx
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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, { useState, useContext } from 'react';
-import { useHistory } from 'react-router-dom';
-
-import { PageLoading } from '@/components';
-import { useRefreshData } from '@/hooks';
-import { operator } from '@/utils';
-
-import * as API from './api';
-
-type ContextType = {
- name: string;
- step: number;
- filter: string[];
- connection?: MixConnection;
- onChangeConnection: (connection: MixConnection) => void;
-
- onPrev: () => void;
- onNext: () => void;
- saving: boolean;
- onSave: () => void;
- onCancel: () => void;
-};
-
-export const Context = React.createContext<ContextType>({
- name: '',
- step: 1,
- filter: [],
-
- onChangeConnection: () => {},
- onPrev: () => {},
- onNext: () => {},
- saving: false,
- onSave: () => {},
- onCancel: () => {},
-});
-
-interface Props {
- pname?: string;
- id: string;
- children: React.ReactNode;
-}
-
-export const ContextProvider = ({ pname, id, children }: Props) => {
- const [step, setStep] = useState(1);
- const [connection, setConnection] = useState<MixConnection>();
- const [saving, setSaving] = useState(false);
-
- const history = useHistory();
-
- const { ready, data } = useRefreshData(() => API.getBlueprint(id), [id]);
-
- const handlePrev = () => {
- setStep(step - 1);
- };
-
- const handleNext = () => {
- setStep(step + 1);
- };
-
- const handleSave = async () => {
- if (!connection) return null;
- const { plugin, connectionId, scope } = connection;
- const payload = {
- ...data,
- settings: {
- ...data.settings,
- connections: [
- ...data.settings.connections,
- {
- plugin,
- connectionId,
- scopes: scope.map((sc) => ({
- id: `${sc.id}`,
- entities: sc.entities,
- })),
- },
- ],
- },
- };
-
- const [success] = await operator(() => API.updateBlueprint(data.id, payload), {
- setOperating: setSaving,
- });
-
- if (success) {
- history.push(pname ? `/projects/${pname}` : `/blueprints/${id}`);
- return;
- }
- };
-
- const handleCancel = () => {
- history.push(`/blueprints/${id}`);
- };
-
- if (!ready || !data) {
- return <PageLoading />;
- }
-
- return (
- <Context.Provider
- value={{
- name: data.name,
- step,
- filter: data.settings.connections.map((cs: any) => `${cs.plugin}-${cs.connectionId}`),
- connection,
- onChangeConnection: setConnection,
- onPrev: handlePrev,
- onNext: handleNext,
- saving,
- onSave: handleSave,
- onCancel: handleCancel,
- }}
- >
- {children}
- </Context.Provider>
- );
-};
-
-export const useConnectionAdd = () => useContext(Context);
diff --git a/config-ui/src/pages/blueprint/connection-add/index.tsx b/config-ui/src/pages/blueprint/connection-add/index.tsx
deleted file mode 100644
index 1ea739381..000000000
--- a/config-ui/src/pages/blueprint/connection-add/index.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 { useParams } from 'react-router-dom';
-
-import { PageHeader, Workflow } from '@/components';
-
-import { Step1, Step2, Step3 } from './components';
-import { ContextProvider, Context } from './context';
-
-export const BlueprintConnectioAddPage = () => {
- const { pname, bid } = useParams<{ pname?: string; bid: string }>();
-
- return (
- <ContextProvider pname={pname} id={bid}>
- <Context.Consumer>
- {({ name, step }) => (
- <PageHeader
- breadcrumbs={[
- ...(pname
- ? [
- {
- name: 'Projects',
- path: '/projects',
- },
- {
- name: pname,
- path: `/projects/${pname}`,
- },
- ]
- : [{ name: name, path: `/blueprints/${bid}` }]),
- { name: 'Add a New Connection', path: '' },
- ]}
- >
- <Workflow
- steps={['Select a Data Connection', 'Set Data Scope', 'Add Transformation (Optional)']}
- activeStep={step}
- />
- {step === 1 && <Step1 />}
- {step === 2 && <Step2 />}
- {step === 3 && <Step3 />}
- </PageHeader>
- )}
- </Context.Consumer>
- </ContextProvider>
- );
-};
diff --git a/config-ui/src/pages/blueprint/connection-detail/index.tsx b/config-ui/src/pages/blueprint/connection-detail/index.tsx
index 90e50deab..01929b4d6 100644
--- a/config-ui/src/pages/blueprint/connection-detail/index.tsx
+++ b/config-ui/src/pages/blueprint/connection-detail/index.tsx
@@ -24,7 +24,7 @@ import { Popover2 } from '@blueprintjs/popover2';
import { Dialog, PageHeader, PageLoading } from '@/components';
import { EntitiesLabel } from '@/config';
import { useRefreshData } from '@/hooks';
-import { DataScope, getPluginConfig, Transformation } from '@/plugins';
+import { DataScopeSelect, getPluginConfig, Transformation } from '@/plugins';
import * as API from './api';
import * as S from './styled';
@@ -169,12 +169,7 @@ export const BlueprintConnectionDetailPage = () => {
style={{ width: 820 }}
onCancel={handleHideDataScope}
>
- <DataScope
- connections={[connection]}
- onCancel={handleHideDataScope}
- onSubmit={handleChangeDataScope}
- onNext={handleHideDataScope}
- />
+ <DataScopeSelect plugin={connection.plugin} connectionId={connection.connectionId} />
</Dialog>
</PageHeader>
);
diff --git a/config-ui/src/pages/blueprint/detail/api.ts b/config-ui/src/pages/blueprint/detail/api.ts
index be4fb40e7..2fc8424eb 100644
--- a/config-ui/src/pages/blueprint/detail/api.ts
+++ b/config-ui/src/pages/blueprint/detail/api.ts
@@ -18,7 +18,9 @@
import { request } from '@/utils';
-export const getBlueprint = (id: ID) => request(`/blueprints/${id}`);
+import type { BlueprintType } from '../types';
+
+export const getBlueprint = (id: ID): Promise<BlueprintType> => request(`/blueprints/${id}`);
export const getBlueprintPipelines = (id: ID) => request(`/blueprints/${id}/pipelines`);
diff --git a/config-ui/src/pages/blueprint/detail/blueprint-detail-page.tsx b/config-ui/src/pages/blueprint/detail/blueprint-detail-page.tsx
index 52044acef..997fa3e66 100644
--- a/config-ui/src/pages/blueprint/detail/blueprint-detail-page.tsx
+++ b/config-ui/src/pages/blueprint/detail/blueprint-detail-page.tsx
@@ -18,30 +18,21 @@
import { useParams } from 'react-router-dom';
-import { PageHeader, PageLoading } from '@/components';
+import { PageHeader } from '@/components';
-import { FromEnum } from '../types';
-
-import { useDetail } from './use-detail';
import { BlueprintDetail } from './blueprint-detail';
export const BlueprintDetailPage = () => {
const { id } = useParams<{ id: string }>();
- const { loading, blueprint } = useDetail({ id });
-
- if (loading || !blueprint) {
- return <PageLoading />;
- }
-
return (
<PageHeader
breadcrumbs={[
{ name: 'Blueprints', path: '/blueprints' },
- { name: blueprint.name, path: `/blueprints/${blueprint.id}` },
+ // { name: blueprint.name, path: `/blueprints/${id}` },
]}
>
- <BlueprintDetail from={FromEnum.blueprint} id={blueprint.id} />
+ <BlueprintDetail id={id} />
</PageHeader>
);
};
diff --git a/config-ui/src/pages/blueprint/detail/blueprint-detail.tsx b/config-ui/src/pages/blueprint/detail/blueprint-detail.tsx
index 75d06b626..dc8faff31 100644
--- a/config-ui/src/pages/blueprint/detail/blueprint-detail.tsx
+++ b/config-ui/src/pages/blueprint/detail/blueprint-detail.tsx
@@ -16,59 +16,88 @@
*
*/
-import { useState, useMemo } from 'react';
+import { useState } from 'react';
import type { TabId } from '@blueprintjs/core';
-import { Tabs, Tab } from '@blueprintjs/core';
+import { Tabs, Tab, Switch } from '@blueprintjs/core';
import { PageLoading } from '@/components';
+import { useRefreshData } from '@/hooks';
+import { operator } from '@/utils';
-import { FromEnum } from '../types';
-
-import type { UseDetailProps } from './use-detail';
-import { useDetail } from './use-detail';
import { Configuration } from './panel/configuration';
import { Status } from './panel/status';
+import * as API from './api';
import * as S from './styled';
-interface Props extends UseDetailProps {
- from?: FromEnum;
- pname?: string;
+interface Props {
+ id: ID;
}
-export const BlueprintDetail = ({ from = FromEnum.project, pname, id }: Props) => {
- const [activeTab, setActiveTab] = useState<TabId>('status');
+export const BlueprintDetail = ({ id }: Props) => {
+ const [activeTab, setActiveTab] = useState<TabId>('configuration');
+ const [version, setVersion] = useState(1);
+ const [operating, setOperating] = useState(false);
- const paths = useMemo(
- () =>
- from === FromEnum.project
- ? [
- `/projects/${window.encodeURIComponent(pname ?? '')}/${id}/connection-add`,
- `/projects/${window.encodeURIComponent(pname ?? '')}/${id}/`,
- ]
- : [`/blueprints/${id}/connection-add`, `/blueprints/${id}/`],
- [from, pname],
+ const { ready, data } = useRefreshData(
+ async () => Promise.all([API.getBlueprint(id), API.getBlueprintPipelines(id)]),
+ [version],
);
- const { loading, blueprint, pipelineId, operating, onRun, onUpdate } = useDetail({
- id,
- });
-
- if (loading || !blueprint) {
+ if (!ready || !data) {
return <PageLoading />;
}
+ const [blueprint, pipelines] = data;
+
+ const handleUpdate = async (payload: any, callback?: () => void) => {
+ const [success] = await operator(
+ () =>
+ API.updateBlueprint(id, {
+ ...blueprint,
+ ...payload,
+ }),
+ {
+ setOperating,
+ },
+ );
+
+ if (success) {
+ setVersion((v) => v + 1);
+ callback?.();
+ }
+ };
+
+ const handleRun = async () => {
+ const [success] = await operator(() => API.runBlueprint(id), {
+ setOperating,
+ });
+
+ if (success) {
+ setVersion((v) => v + 1);
+ }
+ };
+
return (
<S.Wrapper>
<Tabs selectedTabId={activeTab} onChange={(at) => setActiveTab(at)}>
<Tab
id="status"
title="Status"
- panel={<Status blueprint={blueprint} pipelineId={pipelineId} operating={operating} onRun={onRun} />}
+ panel={
+ <Status blueprint={blueprint} pipelineId={pipelines?.[0]?.id} operating={operating} onRun={handleRun} />
+ }
/>
<Tab
id="configuration"
title="Configuration"
- panel={<Configuration paths={paths} blueprint={blueprint} operating={operating} onUpdate={onUpdate} />}
+ panel={<Configuration blueprint={blueprint} operating={operating} onUpdate={handleUpdate} />}
+ />
+ <Tabs.Expander />
+ <Switch
+ style={{ marginBottom: 0 }}
+ label="Blueprint Enabled"
+ checked={blueprint.enable}
+ onChange={(e) => handleUpdate({ enable: (e.target as HTMLInputElement).checked })}
/>
</Tabs>
</S.Wrapper>
diff --git a/config-ui/src/pages/blueprint/detail/components/add-connection-dialog/index.tsx b/config-ui/src/pages/blueprint/detail/components/add-connection-dialog/index.tsx
new file mode 100644
index 000000000..95982ba94
--- /dev/null
+++ b/config-ui/src/pages/blueprint/detail/components/add-connection-dialog/index.tsx
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 { useState } from 'react';
+import { Button, Intent } from '@blueprintjs/core';
+
+import { Dialog, FormItem, Selector, Buttons } from '@/components';
+import { useConnections } from '@/hooks';
+import { DataScopeSelect } from '@/plugins';
+import type { ConnectionItemType } from '@/store';
+
+interface Props {
+ onCancel: () => void;
+ onSubmit: (value: any) => void;
+}
+
+export const AddConnectionDialog = ({ onCancel, onSubmit }: Props) => {
+ const [step, setStep] = useState(1);
+ const [selectedConnection, setSelectedConnection] = useState<ConnectionItemType>();
+
+ const { connections } = useConnections();
+
+ const handleSubmit = (scope: any) => console.log(scope);
+
+ return (
+ <Dialog style={{ width: 820 }} isOpen title={`Add a Connection - Step ${step}`} footer={null}>
+ {step === 1 && (
+ <FormItem
+ label="Data Connections"
+ subLabel="Select from existing Data Connections. If you have not created any Data Connections yet, please create and manage Connections first."
+ required
+ >
+ <Selector
+ items={connections}
+ getKey={(it) => it.unique}
+ getName={(it) => it.name}
+ selectedItem={selectedConnection}
+ onChangeItem={(selectedItem) => setSelectedConnection(selectedItem)}
+ />
+ <Buttons>
+ <Button outlined intent={Intent.PRIMARY} text="Cancel" onClick={onCancel} />
+ <Button disabled={!selectedConnection} intent={Intent.PRIMARY} text="Next" onClick={() => setStep(2)} />
+ </Buttons>
+ </FormItem>
+ )}
+ {step === 2 && selectedConnection && (
+ <DataScopeSelect
+ plugin={selectedConnection.plugin}
+ connectionId={selectedConnection.id}
+ onCancel={onCancel}
+ onSubmit={handleSubmit}
+ />
+ )}
+ </Dialog>
+ );
+};
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/example/customize.ts b/config-ui/src/pages/blueprint/detail/components/advanced-editor/example/customize.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/example/customize.ts
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/example/customize.ts
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/example/dbt.ts b/config-ui/src/pages/blueprint/detail/components/advanced-editor/example/dbt.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/example/dbt.ts
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/example/dbt.ts
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/example/feishu.ts b/config-ui/src/pages/blueprint/detail/components/advanced-editor/example/feishu.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/example/feishu.ts
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/example/feishu.ts
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/example/general.ts b/config-ui/src/pages/blueprint/detail/components/advanced-editor/example/general.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/example/general.ts
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/example/general.ts
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/example/gitextractor.ts b/config-ui/src/pages/blueprint/detail/components/advanced-editor/example/gitextractor.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/example/gitextractor.ts
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/example/gitextractor.ts
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/example/github.ts b/config-ui/src/pages/blueprint/detail/components/advanced-editor/example/github.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/example/github.ts
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/example/github.ts
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/example/gitlab.ts b/config-ui/src/pages/blueprint/detail/components/advanced-editor/example/gitlab.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/example/gitlab.ts
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/example/gitlab.ts
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/example/index.ts b/config-ui/src/pages/blueprint/detail/components/advanced-editor/example/index.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/example/index.ts
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/example/index.ts
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/example/jenkins.ts b/config-ui/src/pages/blueprint/detail/components/advanced-editor/example/jenkins.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/example/jenkins.ts
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/example/jenkins.ts
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/example/jira.ts b/config-ui/src/pages/blueprint/detail/components/advanced-editor/example/jira.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/example/jira.ts
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/example/jira.ts
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/example/refdiff.ts b/config-ui/src/pages/blueprint/detail/components/advanced-editor/example/refdiff.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/example/refdiff.ts
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/example/refdiff.ts
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/example/starrocks.ts b/config-ui/src/pages/blueprint/detail/components/advanced-editor/example/starrocks.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/example/starrocks.ts
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/example/starrocks.ts
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/example/tapd.ts b/config-ui/src/pages/blueprint/detail/components/advanced-editor/example/tapd.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/example/tapd.ts
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/example/tapd.ts
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/example/zentao.ts b/config-ui/src/pages/blueprint/detail/components/advanced-editor/example/zentao.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/example/zentao.ts
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/example/zentao.ts
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/index.tsx b/config-ui/src/pages/blueprint/detail/components/advanced-editor/index.tsx
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/index.tsx
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/index.tsx
diff --git a/config-ui/src/pages/blueprint/detail/components/connection-list/index.tsx b/config-ui/src/pages/blueprint/detail/components/connection-list/index.tsx
deleted file mode 100644
index 91acde78a..000000000
--- a/config-ui/src/pages/blueprint/detail/components/connection-list/index.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 { useMemo } from 'react';
-import { Link } from 'react-router-dom';
-
-import { useConnections } from '@/hooks';
-import { getPluginConfig } from '@/plugins';
-
-import type { BlueprintType } from '../../../types';
-
-import * as S from './styled';
-
-interface Props {
- path: string;
- blueprint: BlueprintType;
-}
-
-export const ConnectionList = ({ path, blueprint }: Props) => {
- const { onGet } = useConnections();
-
- const list = useMemo(
- () =>
- blueprint.settings?.connections
- .filter((cs) => cs.plugin !== 'webhook')
- .map((cs: any) => {
- const unique = `${cs.plugin}-${cs.connectionId}`;
- const plugin = getPluginConfig(cs.plugin);
- const connection = onGet(unique);
-
- return {
- unique,
- icon: plugin.icon,
- name: connection.name,
- scope: cs.scopes,
- };
- })
- .filter(Boolean),
- [blueprint],
- );
-
- return (
- <S.List>
- {list.map((cs) => (
- <S.Item key={cs.unique}>
- <div className="title">
- <img src={cs.icon} alt="" />
- <span>{cs.name}</span>
- </div>
- <div className="count">
- <span>{cs.scope.length} data scope</span>
- </div>
- <div className="link">
- <Link to={`${path}${cs.unique}`}>View Detail</Link>
- </div>
- </S.Item>
- ))}
- </S.List>
- );
-};
diff --git a/config-ui/src/pages/blueprint/detail/components/connection-list/styled.ts b/config-ui/src/pages/blueprint/detail/components/connection-list/styled.ts
deleted file mode 100644
index da2ce28c2..000000000
--- a/config-ui/src/pages/blueprint/detail/components/connection-list/styled.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 styled from 'styled-components';
-
-export const List = styled.ul`
- display: flex;
- align-items: center;
-`;
-
-export const Item = styled.li`
- margin-right: 24px;
- padding: 12px 16px;
- width: 280px;
- background: #ffffff;
- box-shadow: 0px 2.4px 4.8px -0.8px rgba(0, 0, 0, 0.1), 0px 1.6px 8px rgba(0, 0, 0, 0.07);
- border-radius: 4px;
-
- &:last-child {
- margin-right: 0;
- }
-
- .title {
- display: flex;
- align-items: center;
-
- img {
- width: 24px;
- height: 24px;
- }
-
- span {
- margin-left: 8px;
- }
- }
-
- .count {
- margin: 24px 0;
- }
-`;
diff --git a/config-ui/src/pages/blueprint/detail/components/index.ts b/config-ui/src/pages/blueprint/detail/components/index.ts
index f892b941f..3d9b2044e 100644
--- a/config-ui/src/pages/blueprint/detail/components/index.ts
+++ b/config-ui/src/pages/blueprint/detail/components/index.ts
@@ -16,6 +16,7 @@
*
*/
+export * from './add-connection-dialog';
+export * from './advanced-editor';
export * from './update-name-dialog';
export * from './update-policy-dialog';
-export * from './connection-list';
diff --git a/config-ui/src/pages/blueprint/components/sync-policy/index.tsx b/config-ui/src/pages/blueprint/detail/components/sync-policy/index.tsx
similarity index 100%
rename from config-ui/src/pages/blueprint/components/sync-policy/index.tsx
rename to config-ui/src/pages/blueprint/detail/components/sync-policy/index.tsx
diff --git a/config-ui/src/pages/blueprint/components/sync-policy/start-from-selector.tsx b/config-ui/src/pages/blueprint/detail/components/sync-policy/start-from-selector.tsx
similarity index 100%
rename from config-ui/src/pages/blueprint/components/sync-policy/start-from-selector.tsx
rename to config-ui/src/pages/blueprint/detail/components/sync-policy/start-from-selector.tsx
diff --git a/config-ui/src/pages/blueprint/components/sync-policy/styled.ts b/config-ui/src/pages/blueprint/detail/components/sync-policy/styled.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/sync-policy/styled.ts
rename to config-ui/src/pages/blueprint/detail/components/sync-policy/styled.ts
diff --git a/config-ui/src/pages/blueprint/detail/components/update-policy-dialog/index.tsx b/config-ui/src/pages/blueprint/detail/components/update-policy-dialog/index.tsx
index ce3becbb0..c70118c9e 100644
--- a/config-ui/src/pages/blueprint/detail/components/update-policy-dialog/index.tsx
+++ b/config-ui/src/pages/blueprint/detail/components/update-policy-dialog/index.tsx
@@ -16,13 +16,14 @@
*
*/
-import React, { useState, useEffect } from 'react';
+import { useState, useEffect } from 'react';
import { Dialog } from '@/components';
import type { BlueprintType } from '../../../types';
import { ModeEnum } from '../../../types';
-import { SyncPolicy } from '../../../components';
+
+import { SyncPolicy } from '../sync-policy';
interface Props {
blueprint: BlueprintType;
@@ -32,7 +33,7 @@ interface Props {
timeAfter: string | null;
operating: boolean;
onCancel: () => void;
- onSubmit: (params: any) => Promise<void>;
+ onSubmit: (params: any) => void;
}
export const UpdatePolicyDialog = ({ blueprint, operating, onCancel, onSubmit, ...props }: Props) => {
diff --git a/config-ui/src/pages/blueprint/detail/panel/configuration.tsx b/config-ui/src/pages/blueprint/detail/panel/configuration.tsx
index 8038ae7fb..32c1b78c7 100644
--- a/config-ui/src/pages/blueprint/detail/panel/configuration.tsx
+++ b/config-ui/src/pages/blueprint/detail/panel/configuration.tsx
@@ -17,106 +17,167 @@
*/
import { useState, useEffect, useMemo } from 'react';
-import { useHistory } from 'react-router-dom';
-import { Icon, Button, Switch, Colors, Intent } from '@blueprintjs/core';
-import dayjs from 'dayjs';
+import { Link } from 'react-router-dom';
+import { Button, Intent } from '@blueprintjs/core';
-import { getCron } from '@/config';
+import { IconButton, Table, NoData } from '@/components';
+import { useConnections } from '@/hooks';
+import { getPluginConfig } from '@/plugins';
import type { BlueprintType } from '../../types';
import { ModeEnum } from '../../types';
import { validRawPlan } from '../../utils';
-import { AdvancedEditor } from '../../components';
-import { UpdateNameDialog, UpdatePolicyDialog, ConnectionList } from '../components';
+import { AdvancedEditor, UpdateNameDialog, UpdatePolicyDialog, AddConnectionDialog } from '../components';
import * as S from '../styled';
-type Type = 'name' | 'frequency' | 'scope' | 'transformation';
-
interface Props {
- paths: string[];
blueprint: BlueprintType;
operating: boolean;
- onUpdate: (bp: any) => void;
+ onUpdate: (payload: any, callback?: () => void) => void;
}
-export const Configuration = ({ paths, blueprint, operating, onUpdate }: Props) => {
- const [type, setType] = useState<Type>();
+export const Configuration = ({ blueprint, operating, onUpdate }: Props) => {
+ const [type, setType] = useState<'name' | 'policy' | 'add-connection'>();
const [rawPlan, setRawPlan] = useState('');
- const history = useHistory();
-
useEffect(() => {
setRawPlan(JSON.stringify(blueprint.plan, null, ' '));
}, [blueprint]);
- const cron = useMemo(() => getCron(blueprint.isManual, blueprint.cronConfig), [blueprint]);
+ const { onGet } = useConnections();
+
+ const connections = useMemo(
+ () =>
+ blueprint.settings?.connections
+ .filter((cs) => cs.plugin !== 'webhook')
+ .map((cs: any) => {
+ const unique = `${cs.plugin}-${cs.connectionId}`;
+ const plugin = getPluginConfig(cs.plugin);
+ const connection = onGet(unique);
+
+ return {
+ unique,
+ icon: plugin.icon,
+ name: connection.name,
+ scope: cs.scopes,
+ };
+ })
+ .filter(Boolean),
+ [blueprint],
+ );
const handleCancel = () => {
setType(undefined);
};
- const handleUpdateName = async (name: string) => {
- await onUpdate({ name });
- handleCancel();
+ const handleShowNameDialog = () => {
+ setType('name');
};
- const handleUpdatePolicy = async (policy: any) => {
- await onUpdate(policy);
- handleCancel();
+ const handleShowPolicyDialog = () => {
+ setType('policy');
};
- const handleToggleEnabled = (checked: boolean) => onUpdate({ enable: checked });
-
- const handleUpdatePlan = () =>
- onUpdate({
- plan: !validRawPlan(rawPlan) ? JSON.parse(rawPlan) : JSON.stringify([[]], null, ' '),
- });
+ const handleShowAddConnectionDialog = () => {
+ setType('add-connection');
+ };
return (
<S.ConfigurationPanel>
- <div className="top">
- <ul>
- <li>
- <h3>Name</h3>
- <div className="detail">
- <span>{blueprint.name}</span>
- <Icon icon="annotation" color={Colors.BLUE2} onClick={() => setType('name')} />
- </div>
- </li>
- <li>
- <h3>Sync Policy</h3>
- <div className="detail">
- <span>
- {cron.label} {cron.value !== 'manual' ? dayjs(cron.nextTime).format('HH:mm A') : null}
- </span>
- <Icon icon="annotation" color={Colors.BLUE2} onClick={() => setType('frequency')} />
- </div>
- </li>
- </ul>
- <Switch
- label="Blueprint Enabled"
- checked={blueprint.enable}
- onChange={(e) => handleToggleEnabled((e.target as HTMLInputElement).checked)}
+ <div className="block">
+ <h3>Blueprint Name</h3>
+ <div>
+ <span>{blueprint.name}</span>
+ <IconButton icon="annotation" tooltip="Edit" onClick={handleShowNameDialog} />
+ </div>
+ </div>
+ <div className="block">
+ <h3>
+ <span>Sync Policy</span>
+ <IconButton icon="annotation" tooltip="Edit" onClick={handleShowPolicyDialog} />
+ </h3>
+ <Table
+ columns={[
+ {
+ title: 'Data Time Range',
+ dataIndex: 'timeRange',
+ key: 'timeRange',
+ },
+ {
+ title: 'Sync Frequency',
+ dataIndex: 'frequency',
+ key: 'frequency',
+ },
+ {
+ title: 'Skip Failed Tasks',
+ dataIndex: 'skipFailed',
+ key: 'skipFailed',
+ },
+ ]}
+ dataSource={[
+ {
+ timeRange: blueprint.settings.timeAfter,
+ frequency: blueprint.cronConfig,
+ skipFailed: blueprint.skipOnFail,
+ },
+ ]}
/>
</div>
{blueprint.mode === ModeEnum.normal && (
- <div className="bottom">
- <h3>
- <span>Connections</span>
- <Button small intent={Intent.PRIMARY} onClick={() => history.push(paths[0])}>
- Add a Connection
- </Button>
- </h3>
- <ConnectionList path={paths[1]} blueprint={blueprint} />
+ <div className="block">
+ <h3>Data Connections</h3>
+ {!connections.length ? (
+ <NoData
+ text={
+ <>
+ If you have not created data connections yet, please <Link to="/connections">create connections</Link>{' '}
+ first and then add them to the project.
+ </>
+ }
+ action={
+ <Button
+ intent={Intent.PRIMARY}
+ icon="add"
+ text="Add a Connection"
+ onClick={handleShowAddConnectionDialog}
+ />
+ }
+ />
+ ) : (
+ <S.ConnectionList>
+ {connections.map((cs) => (
+ <S.ConnectionItem key={cs.unique}>
+ <div className="title">
+ <img src={cs.icon} alt="" />
+ <span>{cs.name}</span>
+ </div>
+ <div className="count">
+ <span>{cs.scope.length} data scope</span>
+ </div>
+ <div className="link">
+ <Link to={`${cs.unique}`}>View Detail</Link>
+ </div>
+ </S.ConnectionItem>
+ ))}
+ </S.ConnectionList>
+ )}
</div>
)}
{blueprint.mode === ModeEnum.advanced && (
- <div className="bottom">
+ <div className="block">
<h3>JSON Configuration</h3>
<AdvancedEditor value={rawPlan} onChange={setRawPlan} />
<div className="btns">
- <Button intent={Intent.PRIMARY} text="Save" onClick={handleUpdatePlan} />
+ <Button
+ intent={Intent.PRIMARY}
+ text="Save"
+ onClick={() =>
+ onUpdate({
+ plan: !validRawPlan(rawPlan) ? JSON.parse(rawPlan) : JSON.stringify([[]], null, ' '),
+ })
+ }
+ />
</div>
</div>
)}
@@ -125,10 +186,10 @@ export const Configuration = ({ paths, blueprint, operating, onUpdate }: Props)
name={blueprint.name}
operating={operating}
onCancel={handleCancel}
- onSubmit={handleUpdateName}
+ onSubmit={(name) => onUpdate({ name }, handleCancel)}
/>
)}
- {type === 'frequency' && (
+ {type === 'policy' && (
<UpdatePolicyDialog
blueprint={blueprint}
isManual={blueprint.isManual}
@@ -137,9 +198,12 @@ export const Configuration = ({ paths, blueprint, operating, onUpdate }: Props)
timeAfter={blueprint.settings?.timeAfter}
operating={operating}
onCancel={handleCancel}
- onSubmit={handleUpdatePolicy}
+ onSubmit={(payload) => onUpdate(payload, handleCancel)}
/>
)}
+ {type === 'add-connection' && (
+ <AddConnectionDialog onCancel={handleCancel} onSubmit={(value) => console.log(value)} />
+ )}
</S.ConfigurationPanel>
);
};
diff --git a/config-ui/src/pages/blueprint/detail/panel/status.tsx b/config-ui/src/pages/blueprint/detail/panel/status.tsx
index cb01d53ea..264fa28b3 100644
--- a/config-ui/src/pages/blueprint/detail/panel/status.tsx
+++ b/config-ui/src/pages/blueprint/detail/panel/status.tsx
@@ -16,11 +16,12 @@
*
*/
-import React, { useMemo } from 'react';
-import { Button, Intent } from '@blueprintjs/core';
+import { useMemo } from 'react';
+import { Button, Intent, Position } from '@blueprintjs/core';
+import { Tooltip2 } from '@blueprintjs/popover2';
-import { getCron } from '@/config';
import { Card } from '@/components';
+import { getCron } from '@/config';
import { PipelineContextProvider, PipelineInfo, PipelineTasks, PipelineHistorical } from '@/pages';
import { formatTime } from '@/utils';
@@ -38,22 +39,29 @@ interface Props {
export const Status = ({ blueprint, pipelineId, operating, onRun }: Props) => {
const cron = useMemo(() => getCron(blueprint.isManual, blueprint.cronConfig), [blueprint]);
- const handleRunNow = () => onRun();
-
return (
<S.StatusPanel>
<div className="info">
<span>{cron.value === 'manual' ? 'Manual' : `Next Run: ${formatTime(cron.nextTime, 'YYYY-MM-DD HH:mm')}`}</span>
- <span>
+ <Tooltip2
+ position={Position.TOP}
+ content="It is recommended to re-transform your data in this project if you have updated the transformation of the data scope in this project."
+ >
<Button
disabled={!blueprint.enable}
loading={operating}
- small
intent={Intent.PRIMARY}
- text="Run Now"
- onClick={handleRunNow}
+ text="Re-transform Data"
+ onClick={onRun}
/>
- </span>
+ </Tooltip2>
+ <Button
+ disabled={!blueprint.enable}
+ loading={operating}
+ intent={Intent.PRIMARY}
+ text="Collect All Data"
+ onClick={onRun}
+ />
</div>
<PipelineContextProvider>
<div className="block">
diff --git a/config-ui/src/pages/blueprint/detail/styled.ts b/config-ui/src/pages/blueprint/detail/styled.ts
index b865d3c8d..31af92f98 100644
--- a/config-ui/src/pages/blueprint/detail/styled.ts
+++ b/config-ui/src/pages/blueprint/detail/styled.ts
@@ -23,74 +23,54 @@ export const Wrapper = styled.div`
`;
export const ConfigurationPanel = styled.div`
+ .block + .block {
+ margin-top: 36px;
+ }
+
h3 {
margin-bottom: 16px;
}
- .top {
- display: flex;
- align-items: flex-start;
- justify-content: space-between;
-
- ul {
- display: flex;
- align-items: flex-start;
-
- li + li {
- margin-left: 48px;
- }
- }
-
- .detail {
- .bp4-icon {
- margin-left: 4px;
- cursor: pointer;
- }
- }
- }
-
- .bottom {
- margin-top: 24px;
-
- h3 {
- display: flex;
- align-items: center;
- justify-content: space-between;
- }
-
- .btns {
- margin-top: 16px;
- text-align: right;
- }
+ .btns {
+ margin-top: 16px;
+ text-align: right;
}
`;
-export const ConnectionColumn = styled.div`
+export const ConnectionList = styled.ul`
display: flex;
align-items: center;
-
- img {
- margin-right: 4px;
- width: 20px;
- }
`;
-export const ActionColumn = styled.div`
- display: inline-flex;
- flex-direction: column;
- align-items: flex-start;
+export const ConnectionItem = styled.li`
+ margin-right: 24px;
+ padding: 12px 16px;
+ width: 280px;
+ background: #ffffff;
+ box-shadow: 0px 2.4px 4.8px -0.8px rgba(0, 0, 0, 0.1), 0px 1.6px 8px rgba(0, 0, 0, 0.07);
+ border-radius: 4px;
- .item + .item {
- margin-top: 8px;
+ &:last-child {
+ margin-right: 0;
}
- .item {
- cursor: pointer;
+ .title {
+ display: flex;
+ align-items: center;
+
+ img {
+ width: 24px;
+ height: 24px;
+ }
- .bp4-icon {
- margin-right: 4px;
+ span {
+ margin-left: 8px;
}
}
+
+ .count {
+ margin: 24px 0;
+ }
`;
export const StatusPanel = styled.div`
@@ -103,13 +83,9 @@ export const StatusPanel = styled.div`
justify-content: flex-end;
align-items: center;
- & > span {
+ & > * {
margin-left: 16px;
}
-
- .bp4-switch {
- margin-bottom: 0;
- }
}
.block + .block {
diff --git a/config-ui/src/pages/blueprint/detail/use-detail.ts b/config-ui/src/pages/blueprint/detail/use-detail.ts
deleted file mode 100644
index 994989f5e..000000000
--- a/config-ui/src/pages/blueprint/detail/use-detail.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 { useState, useEffect, useMemo } from 'react';
-
-import { operator } from '@/utils';
-
-import type { BlueprintType } from '@/pages';
-import * as API from './api';
-
-export interface UseDetailProps {
- id: ID;
-}
-
-export const useDetail = ({ id }: UseDetailProps) => {
- const [loading, setLoading] = useState(false);
- const [operating, setOperating] = useState(false);
- const [blueprint, setBlueprint] = useState<BlueprintType>();
- const [pipelineId, setPipelineId] = useState<ID>();
-
- const getBlueprint = async () => {
- setLoading(true);
- try {
- const [bpRes, plRes] = await Promise.all([API.getBlueprint(id), API.getBlueprintPipelines(id)]);
-
- setBlueprint(bpRes);
- setPipelineId(plRes.pipelines?.[0]?.id);
- } finally {
- setLoading(false);
- }
- };
-
- useEffect(() => {
- getBlueprint();
- }, []);
-
- const handleRun = async () => {
- const [success] = await operator(() => API.runBlueprint(id), {
- setOperating,
- });
-
- if (success) {
- getBlueprint();
- }
- };
-
- const handleUpdate = async (payload: any) => {
- const [success] = await operator(
- () =>
- API.updateBlueprint(id, {
- ...blueprint,
- ...payload,
- }),
- {
- setOperating,
- },
- );
-
- if (success) {
- getBlueprint();
- }
- };
-
- return useMemo(
- () => ({
- loading,
- operating,
- blueprint,
- pipelineId,
- onRun: handleRun,
- onUpdate: handleUpdate,
- }),
- [loading, operating, blueprint, pipelineId],
- );
-};
diff --git a/config-ui/src/pages/blueprint/index.ts b/config-ui/src/pages/blueprint/index.ts
index 68aec9419..3633cb637 100644
--- a/config-ui/src/pages/blueprint/index.ts
+++ b/config-ui/src/pages/blueprint/index.ts
@@ -19,5 +19,4 @@
export * from './types';
export * from './home';
export * from './detail';
-export * from './connection-add';
export * from './connection-detail';
diff --git a/config-ui/src/pages/blueprint/types.ts b/config-ui/src/pages/blueprint/types.ts
index 362848031..eec9164d6 100644
--- a/config-ui/src/pages/blueprint/types.ts
+++ b/config-ui/src/pages/blueprint/types.ts
@@ -21,11 +21,6 @@ export enum ModeEnum {
normal = 'NORMAL',
}
-export enum FromEnum {
- project = 'project',
- blueprint = 'blueprint',
-}
-
export type BlueprintType = {
id: ID;
enable: boolean;
@@ -41,7 +36,7 @@ export type BlueprintType = {
connections: Array<{
plugin: string;
connectionId: ID;
- scopes: Array<{
+ scopes?: Array<{
id: string;
entities: string[];
}>;
diff --git a/config-ui/src/pages/project/detail/api.ts b/config-ui/src/pages/project/detail/api.ts
index 106498b85..2c7c65581 100644
--- a/config-ui/src/pages/project/detail/api.ts
+++ b/config-ui/src/pages/project/detail/api.ts
@@ -18,23 +18,15 @@
import { request } from '@/utils';
-export const getProject = (name: string) => request(`/projects/${name}`);
+import type { ProjectType, BlueprintType } from '@/pages';
-type UpdateProjectPayload = {
- name: string;
- description: string;
- metrics: Array<{
- pluginName: string;
- pluginOption: string;
- enable: boolean;
- }>;
-};
+export const getProject = (name: string): Promise<ProjectType> => request(`/projects/${name}`);
-export const updateProject = (name: string, payload: UpdateProjectPayload) =>
+export const updateProject = (name: string, payload: Omit<ProjectType, 'blueprint'>) =>
request(`/projects/${name}`, {
method: 'patch',
data: payload,
});
-export const updateBlueprint = (id: ID, payload: any) =>
+export const updateBlueprint = (id: ID, payload: BlueprintType) =>
request(`/blueprints/${id}`, { method: 'patch', data: payload });
diff --git a/config-ui/src/pages/project/detail/index.tsx b/config-ui/src/pages/project/detail/index.tsx
index 065352a08..4db19c51c 100644
--- a/config-ui/src/pages/project/detail/index.tsx
+++ b/config-ui/src/pages/project/detail/index.tsx
@@ -21,13 +21,17 @@ import { useParams, useLocation, useHistory } from 'react-router-dom';
import { Tabs, Tab } from '@blueprintjs/core';
import { PageHeader, PageLoading } from '@/components';
+import { useRefreshData } from '@/hooks';
+import { BlueprintDetail } from '@/pages';
-import { useProject } from './use-project';
-import { BlueprintPanel, IncomingWebhooksPanel, SettingsPanel } from './panel';
+import { WebhooksPanel } from './webhooks-panel';
+import { SettingsPanel } from './settings-panel';
+import * as API from './api';
import * as S from './styled';
export const ProjectDetailPage = () => {
- const [tabId, setTabId] = useState('bp');
+ const [tabId, setTabId] = useState('blueprint');
+ const [version, setVersion] = useState(1);
const { pname } = useParams<{ pname: string }>();
const { search } = useLocation();
@@ -37,10 +41,10 @@ export const ProjectDetailPage = () => {
const urlTabId = query.get('tabId');
useEffect(() => {
- setTabId(urlTabId ?? 'bp');
+ setTabId(urlTabId ?? 'blueprint');
}, [urlTabId]);
- const { loading, project, saving, onUpdate, onSelectWebhook, onCreateWebhook, onDeleteWebhook } = useProject(pname);
+ const { ready, data } = useRefreshData(() => Promise.all([API.getProject(pname)]), [version]);
const handleChangeTabId = (tabId: string) => {
query.delete('tabId');
@@ -48,10 +52,16 @@ export const ProjectDetailPage = () => {
history.push({ search: query.toString() });
};
- if (loading || !project) {
+ const handleRefresh = () => {
+ setVersion((v) => v + 1);
+ };
+
+ if (!ready || !data) {
return <PageLoading />;
}
+ const [project] = data;
+
return (
<PageHeader
breadcrumbs={[
@@ -61,22 +71,13 @@ export const ProjectDetailPage = () => {
>
<S.Wrapper>
<Tabs selectedTabId={tabId} onChange={handleChangeTabId}>
- <Tab id="bp" title="Blueprint" panel={<BlueprintPanel project={project} />} />
+ <Tab id="blueprint" title="Blueprint" panel={<BlueprintDetail id={project.blueprint.id} />} />
<Tab
- id="iw"
+ id="webhook"
title="Incoming Webhooks"
- disabled={!project.blueprint}
- panel={
- <IncomingWebhooksPanel
- project={project}
- saving={saving}
- onSelectWebhook={onSelectWebhook}
- onCreateWebhook={onCreateWebhook}
- onDeleteWebhook={onDeleteWebhook}
- />
- }
+ panel={<WebhooksPanel project={project} onRefresh={handleRefresh} />}
/>
- <Tab id="st" title="Settings" panel={<SettingsPanel project={project} onUpdate={onUpdate} />} />
+ <Tab id="settings" title="Settings" panel={<SettingsPanel project={project} />} />
</Tabs>
</S.Wrapper>
</PageHeader>
diff --git a/config-ui/src/pages/project/detail/panel/blueprint.tsx b/config-ui/src/pages/project/detail/panel/blueprint.tsx
deleted file mode 100644
index d818446ed..000000000
--- a/config-ui/src/pages/project/detail/panel/blueprint.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 from 'react';
-import { useHistory } from 'react-router-dom';
-import { Button, Intent } from '@blueprintjs/core';
-
-import { NoData } from '@/components';
-import { BlueprintDetail } from '@/pages';
-
-import type { ProjectType } from '../types';
-
-interface Props {
- project: ProjectType;
-}
-
-export const BlueprintPanel = ({ project }: Props) => {
- const history = useHistory();
-
- const handleGoCreateBlueprint = () =>
- history.push(`/projects/${window.encodeURIComponent(project.name)}/create-blueprint`);
-
- if (!project.blueprint) {
- return (
- <NoData
- text="Create a blueprint to collect data from data sources."
- action={
- <Button intent={Intent.PRIMARY} icon="plus" text="Create a Blueprint" onClick={handleGoCreateBlueprint} />
- }
- />
- );
- }
-
- return <BlueprintDetail pname={project.name} id={project.blueprint.id} />;
-};
diff --git a/config-ui/src/pages/project/detail/panel/index.ts b/config-ui/src/pages/project/detail/panel/index.ts
deleted file mode 100644
index 0642ffc62..000000000
--- a/config-ui/src/pages/project/detail/panel/index.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 * from './blueprint';
-export * from './incoming-webhooks';
-export * from './settings';
diff --git a/config-ui/src/pages/project/detail/panel/settings.tsx b/config-ui/src/pages/project/detail/panel/settings.tsx
deleted file mode 100644
index 807e504fb..000000000
--- a/config-ui/src/pages/project/detail/panel/settings.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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, { useEffect, useState } from 'react';
-import { InputGroup, Checkbox, ButtonGroup, Button, Intent } from '@blueprintjs/core';
-
-import { Card } from '@/components';
-
-import type { ProjectType } from '../types';
-import * as S from '../styled';
-
-interface Props {
- project: ProjectType;
- onUpdate: (name: string, enableDora: boolean) => void;
-}
-
-export const SettingsPanel = ({ project, onUpdate }: Props) => {
- const [name, setName] = useState('');
- const [enableDora, setEnableDora] = useState(false);
-
- useEffect(() => {
- if (project) {
- setName(project.name);
- setEnableDora(project.enableDora);
- }
- }, [project]);
-
- const handleSave = () => onUpdate(name, enableDora);
-
- return (
- <Card>
- <S.Settings>
- <div className="block">
- <h3>Project Name *</h3>
- <p>Edit your project name with letters, numbers, -, _ or /</p>
- <InputGroup value={name} onChange={(e) => setName(e.target.value)} />
- </div>
- <div className="block">
- <Checkbox
- label="Enable DORA Metrics"
- checked={enableDora}
- onChange={(e) => setEnableDora((e.target as HTMLInputElement).checked)}
- />
- <p>DORA metrics are four widely-adopted metrics for measuring software delivery performance.</p>
- </div>
- <ButtonGroup>
- <Button text="Save" disabled={!name} intent={Intent.PRIMARY} onClick={handleSave} />
- </ButtonGroup>
- </S.Settings>
- </Card>
- );
-};
diff --git a/config-ui/src/pages/project/detail/settings-panel.tsx b/config-ui/src/pages/project/detail/settings-panel.tsx
new file mode 100644
index 000000000..a271b05f7
--- /dev/null
+++ b/config-ui/src/pages/project/detail/settings-panel.tsx
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 { useEffect, useState } from 'react';
+import { useHistory } from 'react-router-dom';
+import { InputGroup, Checkbox, Button, Intent } from '@blueprintjs/core';
+
+import { Card, FormItem, Buttons, toast } from '@/components';
+import { operator } from '@/utils';
+
+import type { ProjectType } from '../types';
+import { validName } from '../utils';
+
+import * as API from './api';
+
+interface Props {
+ project: ProjectType;
+}
+
+export const SettingsPanel = ({ project }: Props) => {
+ const [name, setName] = useState('');
+ const [enableDora, setEnableDora] = useState(false);
+ const [operating, setOperating] = useState(false);
+
+ const history = useHistory();
+
+ useEffect(() => {
+ const doraMetrics = project.metrics.find((ms: any) => ms.pluginName === 'dora');
+
+ setName(project.name);
+ setEnableDora(doraMetrics?.enable ?? false);
+ }, [project]);
+
+ const handleUpdate = async () => {
+ if (!validName(name)) {
+ toast.error('Please enter alphanumeric or underscore');
+ return;
+ }
+
+ const [success] = await operator(
+ () =>
+ API.updateProject(project.name, {
+ name,
+ description: '',
+ metrics: [
+ {
+ pluginName: 'dora',
+ pluginOption: '',
+ enable: enableDora,
+ },
+ ],
+ }),
+ {
+ setOperating,
+ },
+ );
+
+ if (success) {
+ history.push(`/projects/${name}`);
+ }
+ };
+
+ return (
+ <Card>
+ <FormItem label="Project Name" subLabel="Edit your project name with letters, numbers, -, _ or /" required>
+ <InputGroup style={{ width: 386 }} value={name} onChange={(e) => setName(e.target.value)} />
+ </FormItem>
+ <FormItem subLabel="DORA metrics are four widely-adopted metrics for measuring software delivery performance.">
+ <Checkbox
+ label="Enable DORA Metrics"
+ checked={enableDora}
+ onChange={(e) => setEnableDora((e.target as HTMLInputElement).checked)}
+ />
+ </FormItem>
+ <Buttons>
+ <Button text="Save" loading={operating} disabled={!name} intent={Intent.PRIMARY} onClick={handleUpdate} />
+ </Buttons>
+ </Card>
+ );
+};
diff --git a/config-ui/src/pages/project/detail/styled.ts b/config-ui/src/pages/project/detail/styled.ts
index 0620323ab..248651df2 100644
--- a/config-ui/src/pages/project/detail/styled.ts
+++ b/config-ui/src/pages/project/detail/styled.ts
@@ -24,17 +24,3 @@ export const Wrapper = styled.div`
justify-content: center;
}
`;
-
-export const Settings = styled.div`
- .block + .block {
- margin-top: 24px;
- }
-
- .bp4-control {
- margin-bottom: 0;
- }
-
- .bp4-input-group {
- width: 386px;
- }
-`;
diff --git a/config-ui/src/pages/project/detail/use-project.ts b/config-ui/src/pages/project/detail/use-project.ts
deleted file mode 100644
index 227b1ec23..000000000
--- a/config-ui/src/pages/project/detail/use-project.ts
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 { useState, useEffect, useMemo } from 'react';
-import { useHistory } from 'react-router-dom';
-
-import { toast } from '@/components';
-import type { WebhookItemType } from '@/plugins/register/webook';
-import { operator } from '@/utils';
-
-import type { ProjectType } from './types';
-import * as API from './api';
-
-export const useProject = (name: string) => {
- const [loading, setLoading] = useState(false);
- const [project, setProject] = useState<ProjectType>();
- const [saving, setSaving] = useState(false);
-
- const history = useHistory();
-
- const getProject = async () => {
- setLoading(true);
- try {
- const res = await API.getProject(name);
- const doraMetrics = res.metrics.find((ms: any) => ms.pluginName === 'dora');
-
- setProject({
- name: res.name,
- description: res.description,
- blueprint: res.blueprint,
- enableDora: doraMetrics.enable,
- });
- } finally {
- setLoading(false);
- }
- };
-
- useEffect(() => {
- getProject();
- }, []);
-
- const handleUpdate = async (newName: string, enableDora: boolean) => {
- if (!/^(\w|-|\/)+$/.test(newName)) {
- toast.error('Please enter alphanumeric or underscore');
- return;
- }
-
- const payload = {
- name: newName,
- description: '',
- metrics: [
- {
- pluginName: 'dora',
- pluginOption: '',
- enable: enableDora,
- },
- ],
- };
-
- const [success] = await operator(() => API.updateProject(name, payload));
-
- if (success) {
- history.push(`/projects/${newName}`);
- }
- };
-
- const handleSelectWebhook = async (items: WebhookItemType[]) => {
- const payload = {
- ...project?.blueprint,
- settings: {
- version: '2.0.0',
- connections: [
- ...project?.blueprint.settings.connections,
- ...items.map((it) => ({
- plugin: 'webhook',
- connectionId: it.id,
- })),
- ],
- },
- };
-
- const [success] = await operator(() => API.updateBlueprint(project?.blueprint.id, payload), {
- setOperating: setSaving,
- });
-
- if (success) {
- getProject();
- }
- };
-
- const handleCreateWebhook = async (id: ID) => {
- const payload = {
- ...project?.blueprint,
- settings: {
- version: '2.0.0',
- connections: [
- ...project?.blueprint.settings.connections,
- {
- plugin: 'webhook',
- connectionId: id,
- },
- ],
- },
- };
-
- const [success] = await operator(() => API.updateBlueprint(project?.blueprint.id, payload), {
- setOperating: setSaving,
- });
-
- if (success) {
- getProject();
- }
- };
-
- const handleDeleteWebhook = async (id: ID) => {
- const payload = {
- ...project?.blueprint,
- settings: {
- version: '2.0.0',
- connections: project?.blueprint.settings.connections.filter(
- (cs: any) => !(cs.plugin === 'webhook' && cs.connectionId === id),
- ),
- },
- };
-
- const [success] = await operator(() => API.updateBlueprint(project?.blueprint.id, payload), {
- setOperating: setSaving,
- });
-
- if (success) {
- getProject();
- }
- };
-
- return useMemo(
- () => ({
- loading,
- project,
- saving,
- onUpdate: handleUpdate,
- onSelectWebhook: handleSelectWebhook,
- onCreateWebhook: handleCreateWebhook,
- onDeleteWebhook: handleDeleteWebhook,
- }),
- [loading, project, saving, name],
- );
-};
diff --git a/config-ui/src/pages/project/detail/panel/incoming-webhooks.tsx b/config-ui/src/pages/project/detail/webhooks-panel.tsx
similarity index 60%
rename from config-ui/src/pages/project/detail/panel/incoming-webhooks.tsx
rename to config-ui/src/pages/project/detail/webhooks-panel.tsx
index 27fde2ede..81413fbd9 100644
--- a/config-ui/src/pages/project/detail/panel/incoming-webhooks.tsx
+++ b/config-ui/src/pages/project/detail/webhooks-panel.tsx
@@ -22,25 +22,20 @@ import { Button, Intent } from '@blueprintjs/core';
import { Alert, NoData } from '@/components';
import type { WebhookItemType } from '@/plugins/register/webook';
import { WebhookCreateDialog, WebhookSelectorDialog, WebHookConnection } from '@/plugins/register/webook';
+import { operator } from '@/utils';
import type { ProjectType } from '../types';
+import * as API from './api';
+
interface Props {
project: ProjectType;
- saving: boolean;
- onSelectWebhook: (items: WebhookItemType[]) => void;
- onCreateWebhook: (id: ID) => any;
- onDeleteWebhook: (id: ID) => any;
+ onRefresh: () => void;
}
-export const IncomingWebhooksPanel = ({
- project,
- saving,
- onSelectWebhook,
- onCreateWebhook,
- onDeleteWebhook,
-}: Props) => {
+export const WebhooksPanel = ({ project, onRefresh }: Props) => {
const [type, setType] = useState<'selectExist' | 'create'>();
+ const [operating, setOperating] = useState(false);
const webhookIds = useMemo(
() =>
@@ -56,6 +51,74 @@ export const IncomingWebhooksPanel = ({
setType(undefined);
};
+ const handleCreate = async (id: ID) => {
+ const payload = {
+ ...project.blueprint,
+ settings: {
+ ...project.blueprint.settings,
+ connections: [
+ ...project.blueprint.settings.connections,
+ {
+ plugin: 'webhook',
+ connectionId: id,
+ },
+ ],
+ },
+ };
+
+ const [success] = await operator(() => API.updateBlueprint(project.blueprint.id, payload), {
+ setOperating,
+ });
+
+ if (success) {
+ onRefresh();
+ }
+ };
+
+ const handleSelect = async (items: WebhookItemType[]) => {
+ const payload = {
+ ...project.blueprint,
+ settings: {
+ ...project.blueprint.settings,
+ connections: [
+ ...project.blueprint.settings.connections,
+ ...items.map((it) => ({
+ plugin: 'webhook',
+ connectionId: it.id,
+ })),
+ ],
+ },
+ };
+
+ const [success] = await operator(() => API.updateBlueprint(project.blueprint.id, payload), {
+ setOperating,
+ });
+
+ if (success) {
+ onRefresh();
+ }
+ };
+
+ const handleDelete = async (id: ID) => {
+ const payload = {
+ ...project.blueprint,
+ settings: {
+ ...project.blueprint.settings,
+ connections: project.blueprint.settings.connections.filter(
+ (cs: any) => !(cs.plugin === 'webhook' && cs.connectionId === id),
+ ),
+ },
+ };
+
+ const [success] = await operator(() => API.updateBlueprint(project.blueprint.id, payload), {
+ setOperating,
+ });
+
+ if (success) {
+ onRefresh();
+ }
+ };
+
return (
<>
<Alert style={{ marginBottom: 24, color: '#3C5088' }}>
@@ -86,13 +149,13 @@ export const IncomingWebhooksPanel = ({
</>
}
/>
- {type === 'create' && <WebhookCreateDialog isOpen onCancel={handleCancel} onSubmitAfter={onCreateWebhook} />}
+ {type === 'create' && <WebhookCreateDialog isOpen onCancel={handleCancel} onSubmitAfter={handleCreate} />}
{type === 'selectExist' && (
- <WebhookSelectorDialog isOpen saving={saving} onCancel={handleCancel} onSubmit={onSelectWebhook} />
+ <WebhookSelectorDialog isOpen saving={operating} onCancel={handleCancel} onSubmit={handleSelect} />
)}
</>
) : (
- <WebHookConnection filterIds={webhookIds} onCreateAfter={onCreateWebhook} onDeleteAfter={onDeleteWebhook} />
+ <WebHookConnection filterIds={webhookIds} onCreateAfter={handleCreate} onDeleteAfter={handleDelete} />
)}
</>
);
diff --git a/config-ui/src/pages/project/home/index.tsx b/config-ui/src/pages/project/home/index.tsx
index ffc287fd4..ec8573252 100644
--- a/config-ui/src/pages/project/home/index.tsx
+++ b/config-ui/src/pages/project/home/index.tsx
@@ -26,7 +26,7 @@ import { cronPresets } from '@/config';
import { useRefreshData } from '@/hooks';
import { formatTime, operator } from '@/utils';
-import { encodeName } from '../utils';
+import { validName, encodeName } from '../utils';
import { ModeEnum } from '../../blueprint';
import * as API from './api';
@@ -54,7 +54,7 @@ export const ProjectHomePage = () => {
};
const handleCreate = async () => {
- if (!/^(\w|-|\/)+$/.test(name)) {
+ if (!validName(name)) {
toast.error('Please enter alphanumeric or underscore');
return;
}
diff --git a/config-ui/src/pages/project/index.ts b/config-ui/src/pages/project/index.ts
index d5131a681..40b545722 100644
--- a/config-ui/src/pages/project/index.ts
+++ b/config-ui/src/pages/project/index.ts
@@ -16,5 +16,7 @@
*
*/
+export * from './types';
+export * from './utils';
export * from './home';
export * from './detail';
diff --git a/config-ui/src/pages/project/detail/types.ts b/config-ui/src/pages/project/types.ts
similarity index 84%
rename from config-ui/src/pages/project/detail/types.ts
rename to config-ui/src/pages/project/types.ts
index 7568cf9b1..37d4573ce 100644
--- a/config-ui/src/pages/project/detail/types.ts
+++ b/config-ui/src/pages/project/types.ts
@@ -16,9 +16,11 @@
*
*/
+import type { BlueprintType } from '@/pages';
+
export type ProjectType = {
name: string;
description: string;
- blueprint: any | null;
- enableDora: boolean;
+ blueprint: BlueprintType;
+ metrics: Array<{ pluginName: string; pluginOption: string; enable: boolean }>;
};
diff --git a/config-ui/src/pages/project/utils.ts b/config-ui/src/pages/project/utils.ts
index 0dd911bd3..ac605cbb9 100644
--- a/config-ui/src/pages/project/utils.ts
+++ b/config-ui/src/pages/project/utils.ts
@@ -16,6 +16,8 @@
*
*/
+export const validName = (name: string) => /^(\w|-|\/)+$/.test(name);
+
export const decodeName = (name: string) => window.decodeURIComponent(name);
export const encodeName = (name: string) => window.encodeURIComponent(name);
diff --git a/config-ui/src/plugins/components/data-scope-form/api.ts b/config-ui/src/plugins/components/data-scope-form/api.ts
deleted file mode 100644
index 19abb110d..000000000
--- a/config-ui/src/plugins/components/data-scope-form/api.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 { request } from '@/utils';
-
-export const getDataScope = (plugin: string, connectionId: ID, scopeId: string) =>
- request(`/plugins/${plugin}/connections/${connectionId}/scopes/${scopeId}`);
-
-export const updateDataScope = (plugin: string, connectionId: ID, payload: any) =>
- request(`/plugins/${plugin}/connections/${connectionId}/scopes`, {
- method: 'put',
- data: payload,
- });
-
-export const updateDataScopeWithType = (plugin: string, connectionId: ID, type: string, payload: any) =>
- request(`/plugins/${plugin}/connections/${connectionId}/${type}/scopes`, {
- method: 'put',
- data: payload,
- });
diff --git a/config-ui/src/plugins/components/data-scope-form/index.tsx b/config-ui/src/plugins/components/data-scope-form/index.tsx
deleted file mode 100644
index 2e60b5436..000000000
--- a/config-ui/src/plugins/components/data-scope-form/index.tsx
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 { useEffect, useMemo, useState } from 'react';
-import { Button, Intent } from '@blueprintjs/core';
-
-import { Card, MultiSelector } from '@/components';
-import { transformEntities } from '@/config';
-import { getPluginConfig, getPluginId } from '@/plugins';
-
-import { GitHubDataScope } from '@/plugins/register/github';
-import { JiraDataScope } from '@/plugins/register/jira';
-import { GitLabDataScope } from '@/plugins/register/gitlab';
-import { JenkinsDataScope } from '@/plugins/register/jenkins';
-import { BitbucketDataScope } from '@/plugins/register/bitbucket';
-import { AzureDataScope } from '@/plugins/register/azure';
-import { SonarQubeDataScope } from '@/plugins/register/sonarqube';
-import { PagerDutyDataScope } from '@/plugins/register/pagerduty';
-import { ZentaoDataScope } from '@/plugins/register/zentao';
-
-import * as API from './api';
-import * as S from './styled';
-import { TapdDataScope } from '@/plugins/register/tapd';
-
-interface Props {
- plugin: string;
- connectionId: ID;
- initialScope?: any[];
- initialEntities?: string[];
- cancelBtnProps?: {
- text?: string;
- };
- submitBtnProps?: {
- text?: string;
- };
- onCancel?: () => void;
- onSubmit?: (scope: Array<{ id: string; entities: string[] }>, origin: any) => void;
-}
-
-export const DataScopeForm = ({
- plugin,
- connectionId,
- initialScope,
- initialEntities,
- onSubmit,
- onCancel,
- cancelBtnProps,
- submitBtnProps,
-}: Props) => {
- const [operating, setOperating] = useState(false);
- const [scope, setScope] = useState<any>([]);
- const [entities, setEntites] = useState<string[]>([]);
-
- const config = useMemo(() => getPluginConfig(plugin), []);
-
- const error = useMemo(
- () => (!scope.length || !entities.length ? 'No Data Scope is Selected' : ''),
- [scope, entities],
- );
-
- useEffect(() => {
- setScope(initialScope ?? []);
- setEntites(initialEntities ?? config.entities);
- }, []);
-
- const getDataScope = async (scope: any) => {
- try {
- const res = await API.getDataScope(plugin, connectionId, scope[getPluginId(plugin)]);
- return {
- ...scope,
- transformationRuleId: res.transformationRuleId,
- };
- } catch {
- return scope;
- }
- };
-
- const handleSubmit = async () => {
- setOperating(true);
- try {
- const data = await Promise.all(scope.map((sc: any) => getDataScope(sc)));
- const res =
- plugin === 'zentao'
- ? [
- ...(await API.updateDataScopeWithType(plugin, connectionId, 'product', {
- data: data.filter((s) => s.type !== 'project'),
- })),
- ...(await API.updateDataScopeWithType(plugin, connectionId, 'project', {
- data: data.filter((s) => s.type === 'project'),
- })),
- ]
- : await API.updateDataScope(plugin, connectionId, {
- data,
- });
-
- onSubmit?.(
- res.map((it: any) => ({
- id: plugin === 'zentao' ? `${it.type}/${it.id}` : it[getPluginId(plugin)],
- entities,
- })),
- res,
- );
- } finally {
- setOperating(false);
- }
- };
-
- return (
- <S.Wrapper>
- <Card>
- <div className="block">
- {plugin === 'github' && (
- <GitHubDataScope connectionId={connectionId} selectedItems={scope} onChangeItems={setScope} />
- )}
-
- {plugin === 'jira' && (
- <JiraDataScope connectionId={connectionId} selectedItems={scope} onChangeItems={setScope} />
- )}
-
- {plugin === 'gitlab' && (
- <GitLabDataScope connectionId={connectionId} selectedItems={scope} onChangeItems={setScope} />
- )}
-
- {plugin === 'jenkins' && (
- <JenkinsDataScope connectionId={connectionId} selectedItems={scope} onChangeItems={setScope} />
- )}
-
- {plugin === 'bitbucket' && (
- <BitbucketDataScope connectionId={connectionId} selectedItems={scope} onChangeItems={setScope} />
- )}
-
- {plugin === 'azuredevops' && (
- <AzureDataScope connectionId={connectionId} selectedItems={scope} onChangeItems={setScope} />
- )}
-
- {plugin === 'sonarqube' && (
- <SonarQubeDataScope connectionId={connectionId} selectedItems={scope} onChangeItems={setScope} />
- )}
-
- {plugin === 'pagerduty' && (
- <PagerDutyDataScope connectionId={connectionId} selectedItems={scope} onChangeItems={setScope} />
- )}
-
- {plugin === 'tapd' && (
- <TapdDataScope connectionId={connectionId} selectedItems={scope} onChangeItems={setScope} />
- )}
-
- {plugin === 'zentao' && (
- <ZentaoDataScope connectionId={connectionId} selectedItems={scope} onChangeItems={setScope} />
- )}
- </div>
-
- <div className="block">
- <h3>Data Entities</h3>
- <p>
- <span>Select the data entities you wish to collect for the projects.</span>{' '}
- <a
- href="https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema/#data-models"
- target="_blank"
- rel="noreferrer"
- >
- Learn about data entities
- </a>
- </p>
- <MultiSelector
- items={transformEntities(config.entities)}
- getKey={(item) => item.value}
- getName={(item) => item.label}
- selectedItems={transformEntities(entities)}
- onChangeItems={(items) => setEntites(items.map((it) => it.value))}
- />
- </div>
- </Card>
-
- <div className="action">
- <Button
- outlined
- intent={Intent.PRIMARY}
- text="Cancel"
- {...cancelBtnProps}
- disabled={operating}
- onClick={onCancel}
- />
- <Button
- intent={Intent.PRIMARY}
- text="Save"
- {...submitBtnProps}
- loading={operating}
- disabled={!!error}
- onClick={handleSubmit}
- />
- </div>
- </S.Wrapper>
- );
-};
diff --git a/config-ui/src/pages/blueprint/components/index.ts b/config-ui/src/plugins/components/data-scope-select/api.ts
similarity index 82%
rename from config-ui/src/pages/blueprint/components/index.ts
rename to config-ui/src/plugins/components/data-scope-select/api.ts
index 109693dd6..58fe70730 100644
--- a/config-ui/src/pages/blueprint/components/index.ts
+++ b/config-ui/src/plugins/components/data-scope-select/api.ts
@@ -16,5 +16,7 @@
*
*/
-export * from './advanced-editor';
-export * from './sync-policy';
+import { request } from '@/utils';
+
+export const getDataScope = (plugin: string, connectionId: ID) =>
+ request(`/plugins/${plugin}/connections/${connectionId}/scopes`);
diff --git a/config-ui/src/plugins/components/data-scope-select/index.tsx b/config-ui/src/plugins/components/data-scope-select/index.tsx
new file mode 100644
index 000000000..93cf8b353
--- /dev/null
+++ b/config-ui/src/plugins/components/data-scope-select/index.tsx
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 { useState, useEffect } from 'react';
+import { Button, InputGroup, Intent } from '@blueprintjs/core';
+
+import { FormItem, ExternalLink, Table, Buttons } from '@/components';
+import { useRefreshData } from '@/hooks';
+import { getPluginId } from '@/plugins';
+
+import * as API from './api';
+import * as S from './styled';
+
+interface Props {
+ plugin: string;
+ connectionId: ID;
+ initialScope?: any[];
+ onCancel?: () => void;
+ onSubmit?: (scope: any) => void;
+}
+
+export const DataScopeSelect = ({ plugin, connectionId, initialScope, onSubmit, onCancel }: Props) => {
+ const [scopeIds, setScopeIds] = useState<ID[]>([]);
+
+ const { ready, data } = useRefreshData(() => API.getDataScope(plugin, connectionId));
+
+ useEffect(() => {
+ setScopeIds((initialScope ?? data ?? []).map((sc: any) => sc[getPluginId(plugin)]) ?? []);
+ }, [data]);
+
+ const handleSubmit = () => {
+ const scope = data.filter((it: any) => scopeIds.includes(it[getPluginId(plugin)]));
+ onSubmit?.(scope);
+ };
+
+ return (
+ <FormItem
+ label="Select Data Scope"
+ subLabel={
+ <>
+ {' '}
+ Select the data scope in this Connection that you wish to associate with this Project. If you wish to add more
+ Data Scope to this Connection, please{' '}
+ <ExternalLink link={`/connections/${plugin}/${connectionId}`}>go to the Connection page</ExternalLink>.
+ </>
+ }
+ required
+ >
+ <S.Wrapper>
+ <div className="action">
+ <Button intent={Intent.PRIMARY} icon="refresh" text="Refresh Data Scope" />
+ </div>
+ <div className="search">
+ <InputGroup placeholder="Search for Data Scopes" />
+ </div>
+ <Table
+ noShadow
+ loading={!ready}
+ columns={[
+ {
+ title: 'Data Scope',
+ dataIndex: 'name',
+ key: 'name',
+ },
+ ]}
+ dataSource={data}
+ rowSelection={{
+ rowKey: getPluginId(plugin),
+ type: 'checkbox',
+ selectedRowKeys: scopeIds as string[],
+ onChange: (selectedRowKeys) => setScopeIds(selectedRowKeys),
+ }}
+ />
+ <Buttons>
+ <Button outlined intent={Intent.PRIMARY} text="Cancel" onClick={onCancel} />
+ <Button intent={Intent.PRIMARY} text="Save" onClick={handleSubmit} />
+ </Buttons>
+ </S.Wrapper>
+ </FormItem>
+ );
+};
diff --git a/config-ui/src/plugins/components/data-scope-form/styled.ts b/config-ui/src/plugins/components/data-scope-select/styled.ts
similarity index 88%
rename from config-ui/src/plugins/components/data-scope-form/styled.ts
rename to config-ui/src/plugins/components/data-scope-select/styled.ts
index e094ccea8..719705ec3 100644
--- a/config-ui/src/plugins/components/data-scope-form/styled.ts
+++ b/config-ui/src/plugins/components/data-scope-select/styled.ts
@@ -19,14 +19,16 @@
import styled from 'styled-components';
export const Wrapper = styled.div`
- .block {
- margin-bottom: 16px;
+ .action {
+ margin-top: 24px;
}
- .action {
+ .search {
+ margin-top: 24px;
+ margin-bottom: 24px;
+ }
+
+ .btns {
display: flex;
- align-items: center;
- justify-content: space-between;
- margin-top: 36px;
}
`;
diff --git a/config-ui/src/plugins/components/data-scope/index.tsx b/config-ui/src/plugins/components/data-scope/index.tsx
deleted file mode 100644
index 02bca7c65..000000000
--- a/config-ui/src/plugins/components/data-scope/index.tsx
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 { useState, useMemo } from 'react';
-import { Button, Intent } from '@blueprintjs/core';
-
-import { Table, Dialog } from '@/components';
-import { DataScopeForm } from '@/plugins';
-
-import * as S from './styled';
-
-interface Props {
- connections: MixConnection[];
- cancelBtnProps?: {
- text?: string;
- };
- submitBtnProps?: {
- text?: string;
- };
- onCancel?: () => void;
- onSubmit?: (connections: MixConnection[]) => void;
- onNext?: () => void;
-}
-
-export const DataScope = ({ connections, cancelBtnProps, submitBtnProps, onCancel, onSubmit, onNext }: Props) => {
- const [connection, setConnection] = useState<MixConnection>();
-
- const error = useMemo(() => (!connections.every((cs) => cs.scope.length) ? true : false), [connections]);
-
- const handleCancel = () => setConnection(undefined);
-
- const handleSubmit = (connection: MixConnection, scope: MixConnection['scope'], origin: MixConnection['origin']) => {
- onSubmit?.(
- connections.map((cs) => {
- if (cs.unique === connection.unique) {
- return {
- ...cs,
- scope,
- origin,
- };
- }
- return cs;
- }),
- );
- handleCancel();
- };
-
- if (connections.length === 1) {
- const [{ plugin, connectionId, scope, origin, ...props }] = connections;
- return (
- <DataScopeForm
- plugin={plugin}
- connectionId={connectionId}
- initialScope={origin}
- initialEntities={scope[0]?.entities}
- cancelBtnProps={cancelBtnProps}
- submitBtnProps={submitBtnProps}
- onCancel={onCancel}
- onSubmit={(scope: MixConnection['scope'], origin: MixConnection['origin']) => {
- onSubmit?.([
- {
- ...props,
- plugin,
- connectionId,
- scope,
- origin,
- },
- ]);
- onNext?.();
- }}
- />
- );
- }
-
- return (
- <S.Wrapper>
- <Table
- columns={[
- {
- title: 'Data Connections',
- dataIndex: ['icon', 'name'],
- key: 'connection',
- render: ({ icon, name }) => (
- <S.ConnectionColumn>
- <img src={icon} alt="" />
- <span>{name}</span>
- </S.ConnectionColumn>
- ),
- },
- {
- title: 'Data Scope',
- dataIndex: 'origin',
- key: 'scope',
- render: (scope: MixConnection['origin']) =>
- !scope.length ? (
- <span>No Data Scope Selected</span>
- ) : (
- <S.ScopeColumn>
- {scope.map((sc, i) => (
- <S.ScopeItem key={i}>
- <span>{sc.name}</span>
- </S.ScopeItem>
- ))}
- </S.ScopeColumn>
- ),
- },
- {
- title: '',
- dataIndex: 'id',
- key: 'action',
- align: 'center',
- render: (_, connection) => (
- <Button
- small
- minimal
- intent={Intent.PRIMARY}
- icon="cog"
- text="Set Data Scope"
- onClick={() => setConnection(connection)}
- />
- ),
- },
- ]}
- dataSource={connections}
- />
- <S.Btns>
- <Button outlined intent={Intent.PRIMARY} text="Cancel" {...cancelBtnProps} onClick={onCancel} />
- <Button intent={Intent.PRIMARY} text="Save" {...submitBtnProps} disabled={error} onClick={onNext} />
- </S.Btns>
- {connection && (
- <Dialog
- isOpen
- title={
- <S.DialogTitle>
- <img src={connection.icon} alt="" />
- <span>{connection.name}</span>
- <span>(Set Data Scope)</span>
- </S.DialogTitle>
- }
- footer={null}
- style={{ width: 820 }}
- onCancel={handleCancel}
- >
- <DataScopeForm
- plugin={connection.plugin}
- connectionId={connection.connectionId}
- initialScope={connection.origin}
- initialEntities={connection.scope[0]?.entities}
- onCancel={handleCancel}
- onSubmit={(scope: MixConnection['scope'], origin: MixConnection['origin']) =>
- handleSubmit(connection, scope, origin)
- }
- />
- </Dialog>
- )}
- </S.Wrapper>
- );
-};
diff --git a/config-ui/src/plugins/components/data-scope/styled.ts b/config-ui/src/plugins/components/data-scope/styled.ts
deleted file mode 100644
index 8ba790992..000000000
--- a/config-ui/src/plugins/components/data-scope/styled.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 styled from 'styled-components';
-
-export const Wrapper = styled.div``;
-
-export const ConnectionColumn = styled.div`
- display: flex;
- align-items: center;
-
- img {
- margin-right: 4px;
- width: 20px;
- }
-`;
-
-export const ScopeColumn = styled.ul``;
-
-export const ScopeItem = styled.li`
- margin-bottom: 4px;
-
- &:last-child {
- margin-bottom: 0;
- }
-
- .bp4-button {
- margin-left: 6px;
- }
-`;
-
-export const Btns = styled.div`
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-top: 36px;
-`;
-
-export const DialogTitle = styled.div`
- display: flex;
- align-items: center;
-
- img {
- margin-right: 4px;
- width: 24px;
- height: 24px;
- }
-`;
diff --git a/config-ui/src/plugins/components/index.ts b/config-ui/src/plugins/components/index.ts
index 9fa4e8fdd..6a81da1a4 100644
--- a/config-ui/src/plugins/components/index.ts
+++ b/config-ui/src/plugins/components/index.ts
@@ -19,11 +19,10 @@
export * from './connection-form';
export * from './connection-list';
export * from './connection-status';
-export * from './data-scope';
-export * from './data-scope-form';
export * from './data-scope-form-2';
export * from './data-scope-miller-columns';
export * from './data-scope-search';
+export * from './data-scope-select';
export * from './transformation';
export * from './transformation-form';
export * from './transformation-select';
diff --git a/config-ui/src/plugins/components/transformation/index.tsx b/config-ui/src/plugins/components/transformation/index.tsx
index 758892369..407cc275d 100644
--- a/config-ui/src/plugins/components/transformation/index.tsx
+++ b/config-ui/src/plugins/components/transformation/index.tsx
@@ -76,14 +76,14 @@ export const Transformation = ({
return cs;
}
- const origin = cs.origin.map((sc) => {
+ const scope = cs.scope.map((sc) => {
if (!scopeIds.includes(sc[getPluginId(cs.plugin)])) {
return sc;
}
return scopes.find((it) => it[getPluginId(cs.plugin)] === sc[getPluginId(cs.plugin)]);
});
- return { ...cs, origin };
+ return { ...cs, scope };
}),
);
setSelected({
@@ -145,7 +145,7 @@ export const Transformation = ({
),
},
]}
- dataSource={cs.origin}
+ dataSource={cs.scope}
rowSelection={
cs.transformationType === 'for-connection'
? {