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/03/27 02:10:31 UTC
[incubator-devlake] branch main updated: feat(config-ui): about the new design of transformation (#4741)
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 5b5f497ea feat(config-ui): about the new design of transformation (#4741)
5b5f497ea is described below
commit 5b5f497ead42d76a2a11bebf8d7cc10cf05dce90
Author: 青湛 <0x...@gmail.com>
AuthorDate: Mon Mar 27 10:10:24 2023 +0800
feat(config-ui): about the new design of transformation (#4741)
* feat(config-ui): updated the jsx react to react-jsx
* refactor(config-ui): remove content about transformation
* refactor(config-ui): enhance the feature of the table component
* fix(config-ui): github transformation form missed default value
* fix(config-ui): the datasource judgment error in the table component
* feat(config-ui): add new component workflow
* refactor(config-ui): the transformation in plugins
* feat(config-ui): add plugin component transformation-select
* feat(config-ui): add global type MixConnection
* refactor(config-ui): the plugin transformation rename to transformation-form
* feat(config-ui): new plugin tranformation
* chore(config-ui): remove plugin data scope list
* refactor(config-ui): the plugin data-scope rename to data-scope-form
* feat(config-ui): new plugin data-scope
* refactor(config-ui): the plugin other content
* fix(config-ui): adjust the transformation form
* fix(config-ui): adjust the data scope form
* fix(config-ui): missed initial value in data scope form
* feat(config-ui): add new prop noFooter in transformation
* feat(config-ui): add new page bp-connection-add and bp-connection-detail
* refactor(config-ui): the bp create page
* refactor(config-ui): adjust the bp detail page
* refactor(config-ui): adjust the props for transformation
* refactor(config-ui): the plugin data-scope-form
* refactor(config-ui): adjust the props for data-scope
* refactor(config-ui): adjust the props for dialog
* feat(config-ui): add new page about project connection
* refactor(config-ui): adjust the style for github and gitlab data-scope
* refactor(config-ui): the data-scope-form
* fix(config-ui): the origin error in transformation
* refactor(config-ui): adjust the style for data-scope
* feat(config-ui): add props from in bp connection page
* fix(config-ui): type defined error
* fix(config-ui): some bugs for new transformation and data-scope
---
config-ui/src/App.tsx | 12 +-
config-ui/src/components/dialog/index.tsx | 2 +-
config-ui/src/components/index.ts | 15 +-
.../table/components}/index.ts | 4 +-
.../table/components/loading.tsx} | 12 +-
.../table/components/no-data.tsx} | 55 ++--
.../table/hooks}/index.ts | 3 +-
.../components/table/hooks/use-row-selection.ts | 91 +++++++
config-ui/src/components/table/styled.ts | 49 ++--
config-ui/src/components/table/table.tsx | 135 +++++-----
.../types.ts => components/workflow/index.tsx} | 27 +-
.../create => }/components/workflow/styled.ts | 19 +-
config-ui/src/global.d.ts | 13 +
config-ui/src/hooks/use-operator.ts | 6 +-
config-ui/src/layouts/base/use-menu.ts | 6 -
.../blueprint/connection-add}/api.ts | 9 +-
.../{create => connection-add}/components/index.ts | 5 +-
.../blueprint/connection-add/components/step-1.tsx | 72 ++++++
.../connection-add/components/step-2.tsx} | 49 ++--
.../connection-add/components/step-3.tsx} | 38 +--
.../action => connection-add/components}/styled.ts | 10 +-
.../src/pages/blueprint/connection-add/context.tsx | 134 ++++++++++
.../src/pages/blueprint/connection-add/index.tsx | 62 +++++
.../blueprint/connection-detail}/api.ts | 12 +-
.../pages/blueprint/connection-detail/index.tsx | 181 ++++++++++++++
.../{create => connection-detail}/styled.ts | 65 +++--
.../blueprint/create/components/action/index.tsx | 77 ------
.../src/pages/blueprint/create/components/index.ts | 6 +-
.../{step-one/index.tsx => components/step-1.tsx} | 70 ++++--
.../blueprint/create/components/step-2.tsx} | 28 ++-
.../blueprint/create/components/step-3.tsx} | 21 +-
.../{step-four/index.tsx => components/step-4.tsx} | 53 ++--
.../create/{step-one => components}/styled.ts | 43 ++++
.../blueprint/create/components/workflow/index.tsx | 47 ----
.../create/{bp-context.tsx => context.tsx} | 119 +++------
config-ui/src/pages/blueprint/create/index.tsx | 53 ++--
.../pages/blueprint/create/step-three/index.tsx | 81 ------
.../blueprint/create/step-three/use-columns.tsx | 77 ------
.../src/pages/blueprint/create/step-two/index.tsx | 96 -------
.../blueprint/create/step-two/use-columns.tsx | 74 ------
config-ui/src/pages/blueprint/create/types.ts | 33 +--
.../blueprint/detail/blueprint-detail-page.tsx | 5 +-
.../pages/blueprint/detail/blueprint-detail.tsx | 23 +-
.../detail/components/add-scope-dialog/index.tsx | 59 -----
.../detail/components/connection-list/index.tsx | 69 +++++
.../components/connection-list}/styled.ts | 43 ++--
.../src/pages/blueprint/detail/components/index.ts | 3 +-
.../update-transformation-dialog/index.tsx | 60 -----
.../pages/blueprint/detail/panel/configuration.tsx | 187 +++-----------
config-ui/src/pages/blueprint/detail/styled.ts | 20 +-
config-ui/src/pages/blueprint/detail/use-detail.ts | 1 -
config-ui/src/pages/blueprint/index.ts | 2 +
config-ui/src/pages/index.ts | 1 -
.../src/pages/project/detail/panel/blueprint.tsx | 2 +-
.../src/pages/transformation/detail/index.tsx | 103 --------
.../src/pages/transformation/detail/use-detail.ts | 89 -------
config-ui/src/pages/transformation/home/index.tsx | 136 ----------
.../{data-scope => data-scope-form}/api.ts | 4 +-
.../plugins/components/data-scope-form/index.tsx | 196 +++++++++++++++
.../components/data-scope-form}/styled.ts | 19 +-
.../plugins/components/data-scope-list/index.tsx | 74 ------
.../plugins/components/data-scope-list/styled.ts | 68 -----
.../data-scope-list/use-data-scope-list.ts | 105 --------
.../src/plugins/components/data-scope/index.tsx | 232 +++++++++++------
.../src/plugins/components/data-scope/styled.ts | 47 +++-
.../components/data-scope/use-data-scope.ts | 135 ----------
config-ui/src/plugins/components/index.ts | 4 +-
.../components/transformation-form}/api.ts | 15 +-
.../components/transformation-form/index.tsx | 126 ++++++++++
.../misc.ts | 0
.../components/transformation-form}/styled.ts | 18 +-
.../api.ts | 4 +-
.../components/transformation-select/index.tsx | 117 +++++++++
.../components/transformation-select}/styled.ts | 15 +-
.../src/plugins/components/transformation/api.ts | 27 +-
.../plugins/components/transformation/index.tsx | 277 ++++++++++-----------
.../plugins/components/transformation/styled.ts | 52 ++--
.../transformation/use-transformation.ts | 158 ------------
config-ui/src/plugins/index.ts | 3 +-
.../src/plugins/register/github/data-scope.tsx | 8 +-
config-ui/src/plugins/register/github/styled.ts | 6 +
.../src/plugins/register/github/transformation.tsx | 26 +-
.../src/plugins/register/gitlab/data-scope.tsx | 9 +-
config-ui/src/plugins/register/gitlab/styled.ts | 6 +
.../step-three/styled.ts => plugins/utils.ts} | 27 +-
.../src/store/connections/use-context-value.ts | 5 +-
config-ui/src/store/index.ts | 1 -
config-ui/src/store/transformations/context.tsx | 60 -----
.../src/store/transformations/use-context-value.ts | 75 ------
config-ui/tsconfig.json | 2 +-
90 files changed, 2103 insertions(+), 2585 deletions(-)
diff --git a/config-ui/src/App.tsx b/config-ui/src/App.tsx
index edfa54c09..ea9b4e969 100644
--- a/config-ui/src/App.tsx
+++ b/config-ui/src/App.tsx
@@ -16,7 +16,6 @@
*
*/
-import React from 'react';
import { Switch, Route, Redirect } from 'react-router-dom';
import { BaseLayout } from '@/layouts';
@@ -30,8 +29,8 @@ import {
BlueprintHomePage,
BlueprintCreatePage,
BlueprintDetailPage,
- TransformationHomePage,
- TransformationDetailPage,
+ BlueprintConnectioAddPage,
+ BlueprintConnectionDetailPage,
} from '@/pages';
function App() {
@@ -41,6 +40,8 @@ function App() {
<Route path="/" exact component={() => <Redirect to="/projects" />} />
<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="/projects/:pname/create-blueprint"
@@ -53,9 +54,8 @@ function App() {
<Route exact path="/blueprints" component={() => <BlueprintHomePage />} />
<Route exact path="/blueprints/create" component={() => <BlueprintCreatePage from={FromEnum.blueprint} />} />
<Route exact path="/blueprints/:id" component={() => <BlueprintDetailPage />} />
- <Route exact path="/transformations" component={() => <TransformationHomePage />} />
- <Route exact path="/transformations/:plugin/create" component={() => <TransformationDetailPage />} />
- <Route exact path="/transformations/:plugin/:tid" component={() => <TransformationDetailPage />} />
+ <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/components/dialog/index.tsx b/config-ui/src/components/dialog/index.tsx
index 85d4b89a3..290b96cfd 100644
--- a/config-ui/src/components/dialog/index.tsx
+++ b/config-ui/src/components/dialog/index.tsx
@@ -25,7 +25,7 @@ interface Props {
isOpen: boolean;
children?: React.ReactNode;
style?: React.CSSProperties;
- title?: string;
+ title?: React.ReactNode;
footer?: React.ReactNode | null;
cancelText?: string;
okText?: string;
diff --git a/config-ui/src/components/index.ts b/config-ui/src/components/index.ts
index 3291883ae..a9948ea4c 100644
--- a/config-ui/src/components/index.ts
+++ b/config-ui/src/components/index.ts
@@ -16,16 +16,17 @@
*
*/
-export * from './loading';
+export * from './action';
+export * from './card';
+export * from './dialog';
export * from './divider';
+export * from './inspector';
+export * from './loading';
+export * from './logo';
+export * from './no-data';
export * from './page-header';
export * from './selector';
-export * from './dialog';
export * from './table';
export * from './toast';
-export * from './logo';
-export * from './card';
-export * from './inspector';
-export * from './action';
export * from './tooltip';
-export * from './no-data';
+export * from './workflow';
diff --git a/config-ui/src/store/transformations/index.ts b/config-ui/src/components/table/components/index.ts
similarity index 93%
rename from config-ui/src/store/transformations/index.ts
rename to config-ui/src/components/table/components/index.ts
index 34ae5c1d0..ef68e2564 100644
--- a/config-ui/src/store/transformations/index.ts
+++ b/config-ui/src/components/table/components/index.ts
@@ -16,5 +16,5 @@
*
*/
-export * from './types';
-export * from './context';
+export * from './loading';
+export * from './no-data';
diff --git a/config-ui/src/store/transformations/types.ts b/config-ui/src/components/table/components/loading.tsx
similarity index 84%
rename from config-ui/src/store/transformations/types.ts
rename to config-ui/src/components/table/components/loading.tsx
index 48a344e90..c0d73a28b 100644
--- a/config-ui/src/store/transformations/types.ts
+++ b/config-ui/src/components/table/components/loading.tsx
@@ -16,8 +16,12 @@
*
*/
-export type TransformationItemType = {
- id: ID;
- name: string;
- plugin: string;
+import { Card, PageLoading } from '@/components';
+
+export const TableLoading = () => {
+ return (
+ <Card>
+ <PageLoading />
+ </Card>
+ );
};
diff --git a/config-ui/src/plugins/register/gitlab/styled.ts b/config-ui/src/components/table/components/no-data.tsx
similarity index 62%
copy from config-ui/src/plugins/register/gitlab/styled.ts
copy to config-ui/src/components/table/components/no-data.tsx
index 8c64ba989..6dbe94070 100644
--- a/config-ui/src/plugins/register/gitlab/styled.ts
+++ b/config-ui/src/components/table/components/no-data.tsx
@@ -16,36 +16,27 @@
*
*/
-import styled from 'styled-components';
-
-export const TransformationWrapper = styled.div`
- h3 {
- margin-top: 16px;
-
- .bp4-tag {
- margin-left: 4px;
- }
- }
-
- .radio {
- padding-left: 20px;
- margin-bottom: 16px;
-
- .input {
- display: flex;
- align-items: center;
-
- & + .input {
- margin-top: 8px;
- }
-
- p {
- color: #292b3f;
- }
-
- .bp4-input-group {
- margin: 0 4px;
+import { Button, Intent } from '@blueprintjs/core';
+
+import { NoData } from '@/components';
+
+interface Props {
+ text?: React.ReactNode;
+ btnText?: string;
+ onCreate?: () => void;
+}
+
+export const TableNoData = ({ text, btnText, onCreate }: Props) => {
+ return (
+ <NoData
+ text={text}
+ action={
+ onCreate && (
+ <Button intent={Intent.PRIMARY} icon="plus" onClick={onCreate}>
+ {btnText ?? 'Create'}
+ </Button>
+ )
}
- }
- }
-`;
+ />
+ );
+};
diff --git a/config-ui/src/pages/transformation/index.ts b/config-ui/src/components/table/hooks/index.ts
similarity index 94%
rename from config-ui/src/pages/transformation/index.ts
rename to config-ui/src/components/table/hooks/index.ts
index d5131a681..97fc28721 100644
--- a/config-ui/src/pages/transformation/index.ts
+++ b/config-ui/src/components/table/hooks/index.ts
@@ -16,5 +16,4 @@
*
*/
-export * from './home';
-export * from './detail';
+export * from './use-row-selection';
diff --git a/config-ui/src/components/table/hooks/use-row-selection.ts b/config-ui/src/components/table/hooks/use-row-selection.ts
new file mode 100644
index 000000000..a0a686f5e
--- /dev/null
+++ b/config-ui/src/components/table/hooks/use-row-selection.ts
@@ -0,0 +1,91 @@
+/*
+ * 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';
+
+export interface UseRowSelectionProps<T> {
+ dataSource: T[];
+ rowSelection?: {
+ rowKey: string;
+ type?: 'checkbox' | 'radio';
+ selectedRowKeys?: ID[];
+ onChange?: (selectedRowKeys: ID[]) => void;
+ };
+}
+
+export const useRowSelection = <T>({ dataSource, rowSelection }: UseRowSelectionProps<T>) => {
+ const [selectedKeys, setSelectedKeys] = useState<ID[]>([]);
+
+ const {
+ rowKey = 'key',
+ type = 'checkbox',
+ selectedRowKeys,
+ onChange,
+ } = {
+ rowKey: 'key',
+ type: 'checkbox',
+ ...rowSelection,
+ };
+
+ useEffect(() => {
+ setSelectedKeys(selectedRowKeys ?? []);
+ }, [selectedRowKeys]);
+
+ const handleChecked = (data: T) => {
+ const key = (data as any)[rowKey];
+ let result: ID[] = selectedKeys;
+
+ switch (true) {
+ case !selectedKeys.includes(key) && type === 'radio':
+ result = [key];
+ break;
+ case !selectedKeys.includes(key) && type === 'checkbox':
+ result = [...selectedKeys, key];
+ break;
+ case selectedKeys.includes(key) && type === 'checkbox':
+ result = selectedKeys.filter((k) => k !== key);
+ break;
+ }
+
+ onChange ? onChange(result) : setSelectedKeys(result);
+ };
+
+ const handleCheckedAll = () => {
+ let result: ID[] = [];
+
+ if (selectedKeys.length !== dataSource.length) {
+ result = dataSource.map((data: any) => data[rowKey]);
+ }
+
+ onChange ? onChange(result) : setSelectedKeys(result);
+ };
+
+ return useMemo(
+ () => ({
+ canSelection: !!rowSelection,
+ selectionType: type,
+ getCheckedAll: () => dataSource.length === selectedKeys.length,
+ onCheckedAll: handleCheckedAll,
+ getChecked: (data: T) => {
+ return selectedKeys.includes((data as any)[rowKey]);
+ },
+ onChecked: handleChecked,
+ }),
+ [selectedKeys],
+ );
+};
diff --git a/config-ui/src/components/table/styled.ts b/config-ui/src/components/table/styled.ts
index 9ef1169b1..59a76225c 100644
--- a/config-ui/src/components/table/styled.ts
+++ b/config-ui/src/components/table/styled.ts
@@ -18,32 +18,17 @@
import styled from 'styled-components';
-export const Container = styled.div`
- position: relative;
-`;
-
-export const Loading = styled.div`
- text-align: center;
-`;
-
-export const NoData = styled.div`
- text-align: center;
-
- img {
- display: inline-block;
- }
-`;
-
export const Table = styled.table`
table-layout: fixed;
width: 100%;
background-color: #fff;
- 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: 8px;
+ border-radius: 4px;
border-spacing: 0;
`;
-export const THeader = styled.thead``;
+export const THeader = styled.thead`
+ background-color: #f0f4fe;
+`;
export const TBody = styled.tbody``;
@@ -57,26 +42,20 @@ export const TR = styled.tr`
export const TH = styled.th`
padding: 12px 16px;
- border-bottom: 1px solid #dbe4fd;
+ font-weight: 400;
+ border-bottom: 1px solid #dbdcdf;
+
+ label.bp4-control {
+ margin-bottom: 0;
+ }
`;
export const TD = styled.td`
padding: 12px 16px;
- border-bottom: 1px solid #dbe4fd;
- word-break: break-word;
-`;
-
-export const TDEllipsis = styled.td`
+ border-bottom: 1px solid #dbdcdf;
word-break: break-word;
-`;
-export const Mask = styled.div`
- position: absolute;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- display: flex;
- align-items: center;
- justify-content: center;
+ label.bp4-control {
+ margin-bottom: 0;
+ }
`;
diff --git a/config-ui/src/components/table/table.tsx b/config-ui/src/components/table/table.tsx
index 9dfc585bf..bace2e740 100644
--- a/config-ui/src/components/table/table.tsx
+++ b/config-ui/src/components/table/table.tsx
@@ -17,14 +17,16 @@
*/
import React from 'react';
-import { Button, Intent } from '@blueprintjs/core';
+import { Checkbox, Radio } from '@blueprintjs/core';
-import { Loading, Card, NoData, TextTooltip } from '@/components';
+import { TextTooltip } from '@/components';
import { ColumnType } from './types';
+import { TableLoading, TableNoData } from './components';
+import { useRowSelection, UseRowSelectionProps } from './hooks';
import * as S from './styled';
-interface Props<T> {
+interface Props<T> extends UseRowSelectionProps<T> {
loading?: boolean;
columns: ColumnType<T>;
dataSource: T[];
@@ -33,68 +35,77 @@ interface Props<T> {
btnText?: string;
onCreate?: () => void;
};
+ noShadow?: boolean;
}
-export const Table = <T extends Record<string, any>>({ loading, columns, dataSource, noData = {} }: Props<T>) => {
- const { text, btnText, onCreate } = noData;
+export const Table = <T extends Record<string, any>>({
+ loading,
+ columns,
+ dataSource,
+ noData = {},
+ rowSelection,
+ noShadow = false,
+}: Props<T>) => {
+ const { canSelection, selectionType, getCheckedAll, onCheckedAll, getChecked, onChecked } = useRowSelection<T>({
+ dataSource,
+ rowSelection,
+ });
+
+ if (loading) {
+ return <TableLoading />;
+ }
+
+ if (!dataSource.length) {
+ return <TableNoData {...noData} />;
+ }
return (
- <S.Container>
- {loading ? (
- <Card>
- <S.Loading>
- <Loading />
- </S.Loading>
- </Card>
- ) : !dataSource.length ? (
- <NoData
- text={text}
- action={
- onCreate && (
- <Button intent={Intent.PRIMARY} icon="plus" onClick={onCreate}>
- {btnText ?? 'Create'}
- </Button>
- )
- }
- />
- ) : (
- <S.Table>
- <S.THeader>
- <S.TR>
- {columns.map(({ key, width, align = 'left', title }) => (
- <S.TH key={key} style={{ width, textAlign: align }}>
- {title}
- </S.TH>
- ))}
- </S.TR>
- </S.THeader>
- <S.TBody>
- {dataSource.map((data, i) => (
- <S.TR key={i}>
- {columns.map(({ key, width, align = 'left', ellipsis, dataIndex, render }) => {
- const value = Array.isArray(dataIndex)
- ? dataIndex.reduce((acc, cur) => {
- acc[cur] = data[cur];
- return acc;
- }, {} as any)
- : data[dataIndex];
- return (
- <S.TD key={key} style={{ width, textAlign: align }}>
- {render ? (
- render(value, data)
- ) : ellipsis ? (
- <TextTooltip content={value}>{value}</TextTooltip>
- ) : (
- value
- )}
- </S.TD>
- );
- })}
- </S.TR>
- ))}
- </S.TBody>
- </S.Table>
- )}
- </S.Container>
+ <S.Table
+ style={{
+ boxShadow: noShadow ? 'none' : '0px 2.4px 4.8px -0.8px rgba(0, 0, 0, 0.1), 0px 1.6px 8px rgba(0, 0, 0, 0.07)',
+ }}
+ >
+ <S.THeader>
+ <S.TR>
+ {canSelection && (
+ <S.TH style={{ width: 40, textAlign: 'center' }}>
+ {selectionType === 'checkbox' && <Checkbox checked={getCheckedAll()} onChange={() => onCheckedAll()} />}
+ </S.TH>
+ )}
+ {columns.map(({ key, width, align = 'left', title }) => (
+ <S.TH key={key} style={{ width, textAlign: align }}>
+ {title}
+ </S.TH>
+ ))}
+ </S.TR>
+ </S.THeader>
+ <S.TBody>
+ {dataSource.map((data, i) => (
+ <S.TR key={i}>
+ {canSelection && (
+ <S.TD style={{ width: 40, textAlign: 'center' }}>
+ {selectionType === 'checkbox' && (
+ <Checkbox checked={getChecked(data)} onChange={() => onChecked(data)} />
+ )}
+ {selectionType === 'radio' && <Radio checked={getChecked(data)} onChange={() => onChecked(data)} />}
+ </S.TD>
+ )}
+ {columns.map(({ key, width, align = 'left', ellipsis, dataIndex, render }) => {
+ const value = Array.isArray(dataIndex)
+ ? dataIndex.reduce((acc, cur) => {
+ acc[cur] = data[cur];
+ return acc;
+ }, {} as any)
+ : data[dataIndex];
+ return (
+ <S.TD key={key} style={{ width, textAlign: align }}>
+ {render ? render(value, data) : ellipsis ? <TextTooltip content={value}>{value}</TextTooltip> : value}
+ </S.TD>
+ );
+ })}
+ </S.TR>
+ ))}
+ </S.TBody>
+ </S.Table>
);
};
diff --git a/config-ui/src/plugins/components/transformation/types.ts b/config-ui/src/components/workflow/index.tsx
similarity index 60%
copy from config-ui/src/plugins/components/transformation/types.ts
copy to config-ui/src/components/workflow/index.tsx
index 6749e9f3d..f44b45de4 100644
--- a/config-ui/src/plugins/components/transformation/types.ts
+++ b/config-ui/src/components/workflow/index.tsx
@@ -16,13 +16,24 @@
*
*/
-export type TransformationType = 'create' | 'createByExist' | 'selectExist';
+import { Icon } from '@blueprintjs/core';
-export type RuleItem = {
- id: ID;
- name: string;
-};
+import * as S from './styled';
+
+interface Props {
+ steps: string[];
+ activeStep: number;
+}
-export type ScopeItem = {
- name: string;
-} & any;
+export const Workflow = ({ steps, activeStep }: Props) => {
+ return (
+ <S.List>
+ {steps.map((step, i) => (
+ <S.Item key={i} actived={i + 1 === activeStep} passed={activeStep > i + 1}>
+ <span className="step">{activeStep > i + 1 ? <Icon icon="tick" /> : i + 1}</span>
+ <span className="name">{step}</span>
+ </S.Item>
+ ))}
+ </S.List>
+ );
+};
diff --git a/config-ui/src/pages/blueprint/create/components/workflow/styled.ts b/config-ui/src/components/workflow/styled.ts
similarity index 88%
rename from config-ui/src/pages/blueprint/create/components/workflow/styled.ts
rename to config-ui/src/components/workflow/styled.ts
index f99272fa4..1c88a5e15 100644
--- a/config-ui/src/pages/blueprint/create/components/workflow/styled.ts
+++ b/config-ui/src/components/workflow/styled.ts
@@ -23,7 +23,7 @@ export const List = styled.ul`
align-items: center;
`;
-export const Item = styled.li<{ active?: boolean }>`
+export const Item = styled.li<{ actived: boolean; passed: boolean }>`
position: relative;
flex: 1;
display: flex;
@@ -71,16 +71,21 @@ export const Item = styled.li<{ active?: boolean }>`
background-color: #f0f4fe;
border-radius: 50%;
- ${({ active }) =>
- active
+ ${({ actived }) =>
+ actived
? `
color: #fff;
background-color: #7497f7;
`
- : `
- color: #7497f7;
- background-color: #f0f4fe;
- `}
+ : ''}
+
+ ${({ passed }) =>
+ passed
+ ? `
+ color: #fff;
+ background-color: #4DB764;
+ `
+ : ''}
}
span.name {
diff --git a/config-ui/src/global.d.ts b/config-ui/src/global.d.ts
index 0f3a050aa..2c326fff9 100644
--- a/config-ui/src/global.d.ts
+++ b/config-ui/src/global.d.ts
@@ -18,6 +18,19 @@
type ID = string | number;
+type MixConnection = {
+ unique: string;
+ plugin: string;
+ connectionId: ID;
+ name: string;
+ icon: string;
+ scope: Array<{
+ id: string;
+ entities: string[];
+ }>;
+ origin: Array<any>;
+};
+
declare module '*.svg' {
const content: any;
export default content;
diff --git a/config-ui/src/hooks/use-operator.ts b/config-ui/src/hooks/use-operator.ts
index 58e917457..d961d40e2 100644
--- a/config-ui/src/hooks/use-operator.ts
+++ b/config-ui/src/hooks/use-operator.ts
@@ -23,7 +23,7 @@ import { operator } from '@/utils';
export const useOperator = <T>(
request: (paylod?: any) => Promise<T>,
options?: {
- callback?: () => void;
+ callback?: (res?: any) => void;
formatReason?: (err: unknown) => string;
formatMessage?: () => string;
},
@@ -31,14 +31,14 @@ export const useOperator = <T>(
const [operating, setOperating] = useState(false);
const handleSubmit = async (paylod?: any) => {
- const [success] = await operator(() => request(paylod), {
+ const [success, res] = await operator(() => request(paylod), {
setOperating,
formatReason: options?.formatReason ? options?.formatMessage : (err) => (err as any).response?.data?.message,
formatMessage: options?.formatMessage,
});
if (success) {
- options?.callback?.();
+ options?.callback?.(res);
}
};
diff --git a/config-ui/src/layouts/base/use-menu.ts b/config-ui/src/layouts/base/use-menu.ts
index 6e282ca0d..637cc0c6b 100644
--- a/config-ui/src/layouts/base/use-menu.ts
+++ b/config-ui/src/layouts/base/use-menu.ts
@@ -58,12 +58,6 @@ export const useMenu = () => {
isBeta: it.isBeta,
})),
},
- {
- key: 'transformation',
- title: 'Transformation',
- icon: 'function',
- path: '/transformations',
- },
{
key: 'advanced',
title: 'Advanced',
diff --git a/config-ui/src/store/transformations/api.ts b/config-ui/src/pages/blueprint/connection-add/api.ts
similarity index 78%
rename from config-ui/src/store/transformations/api.ts
rename to config-ui/src/pages/blueprint/connection-add/api.ts
index 92ab91588..0abf6526e 100644
--- a/config-ui/src/store/transformations/api.ts
+++ b/config-ui/src/pages/blueprint/connection-add/api.ts
@@ -18,10 +18,7 @@
import { request } from '@/utils';
-type GetTransformationParams = {
- page: number;
- pageSize: number;
-};
+export const getBlueprint = (id: ID) => request(`/blueprints/${id}`);
-export const getTransformation = (plugin: string, params: GetTransformationParams) =>
- request(`/plugins/${plugin}/transformation_rules`, { data: params });
+export const updateBlueprint = (id: ID, payload: any) =>
+ request(`/blueprints/${id}`, { method: 'patch', data: payload });
diff --git a/config-ui/src/pages/blueprint/create/components/index.ts b/config-ui/src/pages/blueprint/connection-add/components/index.ts
similarity index 91%
copy from config-ui/src/pages/blueprint/create/components/index.ts
copy to config-ui/src/pages/blueprint/connection-add/components/index.ts
index b85355f10..6ffb0bdf8 100644
--- a/config-ui/src/pages/blueprint/create/components/index.ts
+++ b/config-ui/src/pages/blueprint/connection-add/components/index.ts
@@ -16,5 +16,6 @@
*
*/
-export * from './workflow';
-export * from './action';
+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
new file mode 100644
index 000000000..1188c448d
--- /dev/null
+++ b/config-ui/src/pages/blueprint/connection-add/components/step-1.tsx
@@ -0,0 +1,72 @@
+/*
+ * 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 type { ConnectionItemType } from '@/store';
+import { ConnectionContextProvider, ConnectionContextConsumer } from '@/store';
+
+import { useConnectionAdd } from '../context';
+
+import * as S from './styled';
+
+export const Step1 = () => {
+ const { filter, connection, onChangeConnection, onCancel, onNext } = useConnectionAdd();
+
+ return (
+ <ConnectionContextProvider filterBeta filter={filter}>
+ <ConnectionContextConsumer>
+ {({ connections }) => (
+ <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 = connections.find((cs) => cs.unique === unique) as ConnectionItemType;
+ onChangeConnection({
+ unique: connection.unique,
+ plugin: connection.plugin,
+ connectionId: connection.id,
+ name: connection.name,
+ icon: connection.icon,
+ scope: [],
+ origin: [],
+ });
+ },
+ }}
+ />
+ <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>
+ )}
+ </ConnectionContextConsumer>
+ </ConnectionContextProvider>
+ );
+};
diff --git a/config-ui/src/plugins/register/gitlab/styled.ts b/config-ui/src/pages/blueprint/connection-add/components/step-2.tsx
similarity index 57%
copy from config-ui/src/plugins/register/gitlab/styled.ts
copy to config-ui/src/pages/blueprint/connection-add/components/step-2.tsx
index 8c64ba989..6675e4483 100644
--- a/config-ui/src/plugins/register/gitlab/styled.ts
+++ b/config-ui/src/pages/blueprint/connection-add/components/step-2.tsx
@@ -16,36 +16,29 @@
*
*/
-import styled from 'styled-components';
+import { DataScope } from '@/plugins';
-export const TransformationWrapper = styled.div`
- h3 {
- margin-top: 16px;
+import { useConnectionAdd } from '../context';
- .bp4-tag {
- margin-left: 4px;
- }
- }
-
- .radio {
- padding-left: 20px;
- margin-bottom: 16px;
-
- .input {
- display: flex;
- align-items: center;
+import * as S from './styled';
- & + .input {
- margin-top: 8px;
- }
+export const Step2 = () => {
+ const { connection, onChangeConnection, onPrev, onNext } = useConnectionAdd();
- p {
- color: #292b3f;
- }
-
- .bp4-input-group {
- margin: 0 4px;
- }
- }
+ 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/transformation/home/styled.ts b/config-ui/src/pages/blueprint/connection-add/components/step-3.tsx
similarity index 61%
rename from config-ui/src/pages/transformation/home/styled.ts
rename to config-ui/src/pages/blueprint/connection-add/components/step-3.tsx
index 32c534180..45afa12f8 100644
--- a/config-ui/src/pages/transformation/home/styled.ts
+++ b/config-ui/src/pages/blueprint/connection-add/components/step-3.tsx
@@ -16,25 +16,27 @@
*
*/
-import { Colors } from '@blueprintjs/core';
-import styled from 'styled-components';
+import { Transformation } from '@/plugins';
-export const Wrapper = styled.div`
- & > .action {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 16px;
- }
-`;
+import { useConnectionAdd } from '../context';
+
+import * as S from './styled';
-export const DialogWrapper = styled.div`
- .warning {
- margin-top: 16px;
+export const Step3 = () => {
+ const { connection, onPrev, operating, onSubmit } = useConnectionAdd();
- .bp4-icon {
- color: ${Colors.ORANGE3};
- margin-right: 4px;
- }
+ if (!connection) {
+ return null;
}
-`;
+
+ return (
+ <S.Wrapper>
+ <Transformation
+ connections={[connection]}
+ submitBtnProps={{ text: 'Save', loading: operating }}
+ onCancel={onPrev}
+ onNext={() => onSubmit(connection)}
+ />
+ </S.Wrapper>
+ );
+};
diff --git a/config-ui/src/pages/blueprint/create/components/action/styled.ts b/config-ui/src/pages/blueprint/connection-add/components/styled.ts
similarity index 91%
rename from config-ui/src/pages/blueprint/create/components/action/styled.ts
rename to config-ui/src/pages/blueprint/connection-add/components/styled.ts
index 0660328ac..229370bff 100644
--- a/config-ui/src/pages/blueprint/create/components/action/styled.ts
+++ b/config-ui/src/pages/blueprint/connection-add/components/styled.ts
@@ -18,13 +18,13 @@
import styled from 'styled-components';
-export const Container = styled.div`
+export const Wrapper = styled.div`
+ margin-top: 36px;
+`;
+
+export const Action = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 36px;
-
- .bp4-button + .bp4-button {
- margin-left: 8px;
- }
`;
diff --git a/config-ui/src/pages/blueprint/connection-add/context.tsx b/config-ui/src/pages/blueprint/connection-add/context.tsx
new file mode 100644
index 000000000..1f6e7874a
--- /dev/null
+++ b/config-ui/src/pages/blueprint/connection-add/context.tsx
@@ -0,0 +1,134 @@
+/*
+ * 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, useOperator } from '@/hooks';
+
+import * as API from './api';
+
+type ContextType = {
+ name: string;
+ step: number;
+ filter: string[];
+ connection?: MixConnection;
+ onChangeConnection: (connection: MixConnection) => void;
+
+ onPrev: () => void;
+ onNext: () => void;
+ onCancel: () => void;
+ operating: boolean;
+ onSubmit: (connection: MixConnection) => void;
+};
+
+export const Context = React.createContext<ContextType>({
+ name: '',
+ step: 1,
+ filter: [],
+
+ onChangeConnection: () => {},
+ onPrev: () => {},
+ onNext: () => {},
+ onCancel: () => {},
+ operating: false,
+ onSubmit: () => {},
+});
+
+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 history = useHistory();
+
+ const { ready, data } = useRefreshData(() => API.getBlueprint(id), [id]);
+
+ const { operating, onSubmit } = useOperator(
+ async (connection: MixConnection) => {
+ if (!connection) return;
+ 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,
+ })),
+ },
+ ],
+ },
+ };
+
+ await API.updateBlueprint(data.id, payload);
+ },
+ {
+ callback: () => history.push(pname ? `/projects/${pname}` : `/blueprints/${id}`),
+ },
+ );
+
+ const handlePrev = () => {
+ setStep(step - 1);
+ };
+
+ const handleNext = () => {
+ setStep(step + 1);
+ };
+
+ 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,
+ onCancel: handleCancel,
+ operating,
+ onSubmit,
+ }}
+ >
+ {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
new file mode 100644
index 000000000..1ea739381
--- /dev/null
+++ b/config-ui/src/pages/blueprint/connection-add/index.tsx
@@ -0,0 +1,62 @@
+/*
+ * 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/plugins/components/data-scope-list/api.ts b/config-ui/src/pages/blueprint/connection-detail/api.ts
similarity index 68%
copy from config-ui/src/plugins/components/data-scope-list/api.ts
copy to config-ui/src/pages/blueprint/connection-detail/api.ts
index 9035ede94..7686757f3 100644
--- a/config-ui/src/plugins/components/data-scope-list/api.ts
+++ b/config-ui/src/pages/blueprint/connection-detail/api.ts
@@ -18,5 +18,13 @@
import { request } from '@/utils';
-export const getDataScopeRepo = (plugin: string, connectionId: ID, repoId: ID) =>
- request(`/plugins/${plugin}/connections/${connectionId}/scopes/${repoId}`);
+export const getBlueprint = (id: ID) => request(`/blueprints/${id}`);
+
+export const updateBlueprint = (id: ID, payload: any) =>
+ request(`/blueprints/${id}`, { method: 'patch', data: payload });
+
+export const getConnection = (plugin: string, connectionId: ID) =>
+ request(`/plugins/${plugin}/connections/${connectionId}`);
+
+export const getDataScope = (plugin: string, connectionId: ID, scopeId: ID) =>
+ request(`/plugins/${plugin}/connections/${connectionId}/scopes/${scopeId}`);
diff --git a/config-ui/src/pages/blueprint/connection-detail/index.tsx b/config-ui/src/pages/blueprint/connection-detail/index.tsx
new file mode 100644
index 000000000..966706e49
--- /dev/null
+++ b/config-ui/src/pages/blueprint/connection-detail/index.tsx
@@ -0,0 +1,181 @@
+/*
+ * 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 { useParams, useHistory } from 'react-router-dom';
+import { Button, Intent, Position } from '@blueprintjs/core';
+import { Popover2 } from '@blueprintjs/popover2';
+
+import { PageHeader, PageLoading, Dialog } from '@/components';
+import { EntitiesLabel } from '@/config';
+import { useRefreshData } from '@/hooks';
+import type { PluginConfigType } from '@/plugins';
+import { PluginConfig, DataScope, Transformation } from '@/plugins';
+
+import * as API from './api';
+import * as S from './styled';
+
+export const BlueprintConnectionDetailPage = () => {
+ const [version, setVersion] = useState(1);
+ const [isOpen, setIsOpen] = useState(false);
+
+ const { pname, bid, unique } = useParams<{ pname?: string; bid: string; unique: string }>();
+ const history = useHistory();
+
+ const { ready, data } = useRefreshData(async () => {
+ const [plugin, connectionId] = unique.split('-');
+ const blueprint = await API.getBlueprint(bid);
+ const connection = await API.getConnection(plugin, connectionId);
+ const scope = blueprint.settings.connections.find(
+ (cs: any) => cs.plugin === plugin && cs.connectionId === +connectionId,
+ ).scopes;
+ const config = PluginConfig.find((p) => p.plugin === plugin) as PluginConfigType;
+ const origin = await Promise.all(scope.map((sc: any) => API.getDataScope(plugin, connectionId, sc.id)));
+
+ return {
+ blueprint,
+ bpName: blueprint.name,
+ csName: connection.name,
+ entities: scope[0].entities,
+ connection: {
+ unique,
+ plugin,
+ connectionId: +connectionId,
+ name: connection.name,
+ icon: config.icon,
+ scope,
+ origin,
+ },
+ };
+ }, [version]);
+
+ if (!ready || !data) {
+ return <PageLoading />;
+ }
+
+ const { blueprint, bpName, csName, entities, connection } = data;
+
+ const handleShowDataScope = () => setIsOpen(true);
+ const handleHideDataScope = () => setIsOpen(false);
+
+ const handleChangeDataScope = async () => {
+ await API.updateBlueprint(blueprint.id, {
+ ...blueprint,
+ settings: {
+ ...blueprint.settings,
+ connections: blueprint.settings.connections.map((cs: any) => {
+ if (cs.plugin === connection.plugin && cs.connectionId === connection.connectionId) {
+ return {
+ ...cs,
+ scopes: connection.scope.map((sc: any) => ({
+ id: `${sc.id}`,
+ entities: sc.entities,
+ })),
+ };
+ }
+ return cs;
+ }),
+ },
+ });
+ setVersion((v) => v + 1);
+ };
+
+ const handleChangeTransformation = () => {
+ setVersion((v) => v + 1);
+ };
+
+ const handleRemoveConnection = async () => {
+ await API.updateBlueprint(blueprint.id, {
+ ...blueprint,
+ settings: {
+ ...blueprint.settings,
+ connections: blueprint.settings.connections.filter(
+ (cs: any) => !(cs.plugin === connection.plugin && cs.connectionId === connection.connectionId),
+ ),
+ },
+ });
+ history.push(pname ? `/projects/:${pname}` : `/blueprints/${blueprint.id}`);
+ };
+
+ return (
+ <PageHeader
+ breadcrumbs={[
+ ...(pname
+ ? [
+ {
+ name: 'Projects',
+ path: '/projects',
+ },
+ {
+ name: pname,
+ path: `/projects/${pname}`,
+ },
+ ]
+ : [{ name: bpName, path: `/blueprints/${bid}` }]),
+ { name: `Connection - ${csName}`, path: '' },
+ ]}
+ >
+ <S.Action>
+ <span>
+ <Button intent={Intent.PRIMARY} icon="annotation" onClick={handleShowDataScope}>
+ Edit Data Scope
+ </Button>
+ </span>
+ <Popover2
+ position={Position.BOTTOM}
+ content={
+ <S.ActionDelete>
+ <div className="content">Are you sure you want to delete this connection?</div>
+ <div className="btns" onClick={handleRemoveConnection}>
+ <Button intent={Intent.PRIMARY} text="Confirm" />
+ </div>
+ </S.ActionDelete>
+ }
+ >
+ <Button intent={Intent.DANGER} icon="trash">
+ Remove this Connection
+ </Button>
+ </Popover2>
+ </S.Action>
+ <S.Entities>
+ <h4>Data Entities</h4>
+ <ul>
+ {entities.map((it: string) => (
+ <li key={it}>{EntitiesLabel[it]}</li>
+ ))}
+ </ul>
+ </S.Entities>
+ <Transformation connections={[connection]} noFooter onSubmit={handleChangeTransformation} />
+ <Dialog
+ isOpen={isOpen}
+ title="Change Data Scope"
+ footer={null}
+ style={{ width: 820 }}
+ onCancel={handleHideDataScope}
+ >
+ <DataScope
+ connections={[connection]}
+ initialScope={connection.origin}
+ onCancel={handleHideDataScope}
+ onSubmit={handleChangeDataScope}
+ onNext={handleHideDataScope}
+ />
+ </Dialog>
+ </PageHeader>
+ );
+};
diff --git a/config-ui/src/pages/blueprint/create/styled.ts b/config-ui/src/pages/blueprint/connection-detail/styled.ts
similarity index 56%
copy from config-ui/src/pages/blueprint/create/styled.ts
copy to config-ui/src/pages/blueprint/connection-detail/styled.ts
index 378109925..92de69ebe 100644
--- a/config-ui/src/pages/blueprint/create/styled.ts
+++ b/config-ui/src/pages/blueprint/connection-detail/styled.ts
@@ -18,39 +18,66 @@
import styled from 'styled-components';
-export const Container = styled.div``;
+export const Action = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 24px;
+
+ .bp4-button + .bp4-button {
+ margin-left: 8px;
+ }
+`;
+
+export const ActionDelete = styled.div`
+ padding: 16px 24px;
+
+ .btns {
+ display: flex;
+ align-items: center;
+ justify-content: end;
+ margin-top: 16px;
+ }
+`;
-export const Content = styled.div`
- margin-top: 36px;
+export const Entities = styled.div`
margin-bottom: 24px;
- .card + .card {
- margin-top: 24px;
+ h4 {
+ margin-bottom: 16px;
}
- .back {
+ ul {
display: flex;
align-items: center;
- margin-bottom: 12px;
- color: #7497f7;
- cursor: pointer;
- span.bp4-icon {
- margin-right: 4px;
- cursor: pointer;
+ li::after {
+ content: ',';
+ }
+
+ li:last-child::after {
+ content: '';
+ }
+
+ li + li {
+ margin-left: 4px;
}
}
+`;
+
+export const SelectTransformationWrapper = styled.div`
+ .action {
+ margin-bottom: 24px;
+ }
- .connection {
+ .btns {
display: flex;
align-items: center;
- margin-bottom: 12px;
+ justify-content: end;
+ margin-top: 24px;
- span {
- margin-left: 8px;
- font-size: 14px;
- color: #292b3f;
- font-weight: 600;
+ .bp4-button + .bp4-button {
+ margin-left: 4px;
}
}
`;
diff --git a/config-ui/src/pages/blueprint/create/components/action/index.tsx b/config-ui/src/pages/blueprint/create/components/action/index.tsx
deleted file mode 100644
index e2a6eb2d1..000000000
--- a/config-ui/src/pages/blueprint/create/components/action/index.tsx
+++ /dev/null
@@ -1,77 +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, { useMemo } from 'react';
-import { ButtonGroup, Button, Icon, Intent, Position, Colors } from '@blueprintjs/core';
-import { Tooltip2 } from '@blueprintjs/popover2';
-
-import { ModeEnum } from '../../../types';
-import { useCreateBP } from '../../bp-context';
-
-import * as S from './styled';
-
-export const Action = () => {
- const { step, mode, error, showDetail, onChangeStep, onChangeShowInspector, onSave, onSaveAndRun } = useCreateBP();
-
- const [isFirst, isLast] = useMemo(() => {
- return [step === 1, (mode === ModeEnum.normal && step === 4) || (mode === ModeEnum.advanced && step === 2)];
- }, [step, mode]);
-
- if (showDetail) {
- return null;
- }
-
- return (
- <S.Container>
- <ButtonGroup>
- {!isFirst && (
- <Button outlined intent={Intent.PRIMARY} text="Previous Step" onClick={() => onChangeStep(step - 1)} />
- )}
- </ButtonGroup>
- <ButtonGroup>
- <Button
- minimal
- intent={Intent.PRIMARY}
- icon="code"
- text="Inspect"
- onClick={() => onChangeShowInspector(true)}
- />
- {isLast ? (
- <>
- <Button intent={Intent.PRIMARY} text="Save Blueprint" onClick={onSave} />
- <Button outlined intent={Intent.PRIMARY} text="Save and Run Now" onClick={onSaveAndRun} />
- </>
- ) : (
- <Button
- intent={Intent.PRIMARY}
- disabled={!!error}
- icon={
- error ? (
- <Tooltip2 defaultIsOpen placement={Position.TOP} content={error}>
- <Icon icon="warning-sign" color={Colors.ORANGE5} style={{ margin: 0 }} />
- </Tooltip2>
- ) : null
- }
- text="Next Step"
- onClick={() => onChangeStep(step + 1)}
- />
- )}
- </ButtonGroup>
- </S.Container>
- );
-};
diff --git a/config-ui/src/pages/blueprint/create/components/index.ts b/config-ui/src/pages/blueprint/create/components/index.ts
index b85355f10..6e5963478 100644
--- a/config-ui/src/pages/blueprint/create/components/index.ts
+++ b/config-ui/src/pages/blueprint/create/components/index.ts
@@ -16,5 +16,7 @@
*
*/
-export * from './workflow';
-export * from './action';
+export * from './step-1';
+export * from './step-2';
+export * from './step-3';
+export * from './step-4';
diff --git a/config-ui/src/pages/blueprint/create/step-one/index.tsx b/config-ui/src/pages/blueprint/create/components/step-1.tsx
similarity index 71%
rename from config-ui/src/pages/blueprint/create/step-one/index.tsx
rename to config-ui/src/pages/blueprint/create/components/step-1.tsx
index 4f5c0daec..72732196a 100644
--- a/config-ui/src/pages/blueprint/create/step-one/index.tsx
+++ b/config-ui/src/pages/blueprint/create/components/step-1.tsx
@@ -16,16 +16,17 @@
*
*/
-import React, { useMemo } from 'react';
-import { pick } from 'lodash';
-import { InputGroup, Icon } from '@blueprintjs/core';
+import { useMemo } from 'react';
+import { InputGroup, Icon, Button, Intent } from '@blueprintjs/core';
import { useConnection, ConnectionStatusEnum } from '@/store';
import { Card, Divider, MultiSelector, Loading } from '@/components';
import { ModeEnum, FromEnum } from '../../types';
import { AdvancedEditor } from '../../components';
-import { useCreateBP } from '../bp-context';
+import { validRawPlan } from '../../utils';
+
+import { useCreate } from '../context';
import * as S from './styled';
@@ -33,26 +34,36 @@ interface Props {
from: FromEnum;
}
-export const StepOne = ({ from }: Props) => {
+export const Step1 = ({ from }: Props) => {
const { connections, onTest } = useConnection();
-
- const {
- mode,
- name,
- rawPlan,
- uniqueList,
- scopeMap,
- onChangeMode,
- onChangeName,
- onChangeRawPlan,
- onChangeUniqueList,
- onChangeScopeMap,
- } = useCreateBP();
+ const { mode, name, rawPlan, onChangeMode, onChangeName, onChangeConnections, onChangeRawPlan, onNext, ...props } =
+ useCreate();
const fromProject = useMemo(() => from === FromEnum.project, [from]);
+ const uniqueList = useMemo(() => props.connections.map((sc) => sc.unique), [props.connections]);
+
+ const error = useMemo(() => {
+ switch (true) {
+ case !name:
+ return true;
+ case name.length < 3:
+ return true;
+ case mode === ModeEnum.advanced && validRawPlan(rawPlan):
+ return true;
+ case mode === ModeEnum.normal && !uniqueList.length:
+ return true;
+ case mode === ModeEnum.normal &&
+ !connections
+ .filter((cs) => uniqueList.includes(cs.unique))
+ .every((cs) => cs.status === ConnectionStatusEnum.ONLINE):
+ return true;
+ default:
+ return false;
+ }
+ }, [mode, name, connections, props.connections, rawPlan]);
return (
- <>
+ <S.Wrapper>
<Card className="card">
<h2>Blueprint Name</h2>
<Divider />
@@ -79,9 +90,17 @@ export const StepOne = ({ from }: Props) => {
if (lastItem) {
onTest(lastItem);
}
- const uniqueList = selectedItems.map((sc) => sc.unique);
- onChangeUniqueList(uniqueList);
- onChangeScopeMap(pick(scopeMap, uniqueList));
+ onChangeConnections(
+ selectedItems.map((sc) => ({
+ unique: sc.unique,
+ plugin: sc.plugin,
+ connectionId: sc.id,
+ name: sc.name,
+ icon: sc.icon,
+ scope: [],
+ origin: [],
+ })),
+ );
}}
/>
<S.ConnectionList>
@@ -128,6 +147,11 @@ export const StepOne = ({ from }: Props) => {
</S.Tips>
</>
)}
- </>
+
+ <S.Btns>
+ <span></span>
+ <Button intent={Intent.PRIMARY} disabled={error} text="Next Step" onClick={onNext} />
+ </S.Btns>
+ </S.Wrapper>
);
};
diff --git a/config-ui/src/plugins/components/transformation/types.ts b/config-ui/src/pages/blueprint/create/components/step-2.tsx
similarity index 61%
copy from config-ui/src/plugins/components/transformation/types.ts
copy to config-ui/src/pages/blueprint/create/components/step-2.tsx
index 6749e9f3d..e3dbff814 100644
--- a/config-ui/src/plugins/components/transformation/types.ts
+++ b/config-ui/src/pages/blueprint/create/components/step-2.tsx
@@ -16,13 +16,25 @@
*
*/
-export type TransformationType = 'create' | 'createByExist' | 'selectExist';
+import { DataScope } from '@/plugins';
-export type RuleItem = {
- id: ID;
- name: string;
-};
+import { useCreate } from '../context';
+
+import * as S from './styled';
-export type ScopeItem = {
- name: string;
-} & any;
+export const Step2 = () => {
+ const { connections, onChangeConnections, onPrev, onNext } = useCreate();
+
+ return (
+ <S.Wrapper>
+ <DataScope
+ connections={connections}
+ cancelBtnProps={{ text: 'Previous Step' }}
+ submitBtnProps={{ text: 'Next Step' }}
+ onCancel={onPrev}
+ onSubmit={onChangeConnections}
+ onNext={onNext}
+ />
+ </S.Wrapper>
+ );
+};
diff --git a/config-ui/src/plugins/components/transformation/types.ts b/config-ui/src/pages/blueprint/create/components/step-3.tsx
similarity index 67%
rename from config-ui/src/plugins/components/transformation/types.ts
rename to config-ui/src/pages/blueprint/create/components/step-3.tsx
index 6749e9f3d..be178feb2 100644
--- a/config-ui/src/plugins/components/transformation/types.ts
+++ b/config-ui/src/pages/blueprint/create/components/step-3.tsx
@@ -16,13 +16,18 @@
*
*/
-export type TransformationType = 'create' | 'createByExist' | 'selectExist';
+import { Transformation } from '@/plugins';
-export type RuleItem = {
- id: ID;
- name: string;
-};
+import { useCreate } from '../context';
+
+import * as S from './styled';
-export type ScopeItem = {
- name: string;
-} & any;
+export const Step3 = () => {
+ const { connections, onChangeConnections, onPrev, onNext } = useCreate();
+
+ return (
+ <S.Wrapper>
+ <Transformation connections={connections} onCancel={onPrev} onSubmit={onChangeConnections} onNext={onNext} />
+ </S.Wrapper>
+ );
+};
diff --git a/config-ui/src/pages/blueprint/create/step-four/index.tsx b/config-ui/src/pages/blueprint/create/components/step-4.tsx
similarity index 51%
rename from config-ui/src/pages/blueprint/create/step-four/index.tsx
rename to config-ui/src/pages/blueprint/create/components/step-4.tsx
index 03bcc546a..6ed6370e6 100644
--- a/config-ui/src/pages/blueprint/create/step-four/index.tsx
+++ b/config-ui/src/pages/blueprint/create/components/step-4.tsx
@@ -16,15 +16,18 @@
*
*/
-import React from 'react';
+import { Button, Intent } from '@blueprintjs/core';
import { Card, Divider } from '@/components';
import { ModeEnum } from '../../types';
-import { useCreateBP } from '../bp-context';
import { SyncPolicy } from '../../components';
-export const StepFour = () => {
+import { useCreate } from '../context';
+
+import * as S from './styled';
+
+export const Step4 = () => {
const {
mode,
isManual,
@@ -35,23 +38,35 @@ export const StepFour = () => {
onChangeCronConfig,
onChangeSkipOnFail,
onChangeTimeAfter,
- } = useCreateBP();
+ onPrev,
+ onSave,
+ onSaveAndRun,
+ } = useCreate();
return (
- <Card>
- <h2>Set Sync Policy</h2>
- <Divider />
- <SyncPolicy
- isManual={isManual}
- cronConfig={cronConfig}
- skipOnFail={skipOnFail}
- showTimeFilter={mode === ModeEnum.normal}
- timeAfter={timeAfter}
- onChangeIsManual={onChangeIsManual}
- onChangeCronConfig={onChangeCronConfig}
- onChangeSkipOnFail={onChangeSkipOnFail}
- onChangeTimeAfter={onChangeTimeAfter}
- />
- </Card>
+ <S.Wrapper>
+ <Card>
+ <h2>Set Sync Policy</h2>
+ <Divider />
+ <SyncPolicy
+ isManual={isManual}
+ cronConfig={cronConfig}
+ skipOnFail={skipOnFail}
+ showTimeFilter={mode === ModeEnum.normal}
+ timeAfter={timeAfter}
+ onChangeIsManual={onChangeIsManual}
+ onChangeCronConfig={onChangeCronConfig}
+ onChangeSkipOnFail={onChangeSkipOnFail}
+ onChangeTimeAfter={onChangeTimeAfter}
+ />
+ </Card>
+ <S.Btns>
+ <Button intent={Intent.PRIMARY} outlined text="Previous Step" onClick={onPrev} />
+ <div>
+ <Button intent={Intent.PRIMARY} text="Save Blueprint" onClick={onSave} />
+ <Button intent={Intent.PRIMARY} outlined text="Save and Run Now" onClick={onSaveAndRun} />
+ </div>
+ </S.Btns>
+ </S.Wrapper>
);
};
diff --git a/config-ui/src/pages/blueprint/create/step-one/styled.ts b/config-ui/src/pages/blueprint/create/components/styled.ts
similarity index 69%
rename from config-ui/src/pages/blueprint/create/step-one/styled.ts
rename to config-ui/src/pages/blueprint/create/components/styled.ts
index b7219fc88..67066091d 100644
--- a/config-ui/src/pages/blueprint/create/step-one/styled.ts
+++ b/config-ui/src/pages/blueprint/create/components/styled.ts
@@ -19,6 +19,14 @@
import styled from 'styled-components';
import { Colors } from '@blueprintjs/core';
+export const Wrapper = styled.div`
+ margin-top: 36px;
+
+ .card + .card {
+ margin-top: 16px;
+ }
+`;
+
export const ConnectionList = styled.ul`
padding: 12px;
@@ -56,3 +64,38 @@ export const Tips = styled.p`
cursor: pointer;
}
`;
+
+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;
+
+ .bp4-button + .bp4-button {
+ margin-left: 8px;
+ }
+`;
diff --git a/config-ui/src/pages/blueprint/create/components/workflow/index.tsx b/config-ui/src/pages/blueprint/create/components/workflow/index.tsx
deleted file mode 100644
index 221bc81ae..000000000
--- a/config-ui/src/pages/blueprint/create/components/workflow/index.tsx
+++ /dev/null
@@ -1,47 +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, { useMemo } from 'react';
-
-import { ModeEnum } from '../../../types';
-import { useCreateBP } from '../../bp-context';
-
-import * as S from './styled';
-
-export const WorkFlow = () => {
- const { step, mode } = useCreateBP();
-
- const steps = useMemo(
- () =>
- mode === ModeEnum.normal
- ? ['Add Data Connections', 'Set Data Scope', 'Add Transformation (Optional)', 'Set Sync Policy']
- : ['Create Advanced Configuration', 'Set Sync Policy'],
- [mode],
- );
-
- return (
- <S.List>
- {steps.map((it, i) => (
- <S.Item key={it} active={i + 1 === step}>
- <span className="step">{i + 1}</span>
- <span className="name">{it}</span>
- </S.Item>
- ))}
- </S.List>
- );
-};
diff --git a/config-ui/src/pages/blueprint/create/bp-context.tsx b/config-ui/src/pages/blueprint/create/context.tsx
similarity index 60%
rename from config-ui/src/pages/blueprint/create/bp-context.tsx
rename to config-ui/src/pages/blueprint/create/context.tsx
index ea5d22b14..f2af24ded 100644
--- a/config-ui/src/pages/blueprint/create/bp-context.tsx
+++ b/config-ui/src/pages/blueprint/create/context.tsx
@@ -20,49 +20,39 @@ import React, { useState, useMemo, useContext } from 'react';
import { useHistory } from 'react-router-dom';
import dayjs from 'dayjs';
-import type { ConnectionItemType } from '@/store';
-import { useConnection, ConnectionStatusEnum } from '@/store';
import { operator, formatTime } from '@/utils';
import { ModeEnum, FromEnum } from '../types';
import { validRawPlan } from '../utils';
-import type { BPContextType } from './types';
+import type { ContextType } from './types';
import * as API from './api';
-export const BPContext = React.createContext<BPContextType>({
+export const Context = React.createContext<ContextType>({
step: 1,
- error: '',
- showInspector: false,
- showDetail: false,
- payload: {},
name: 'MY BLUEPRINT',
mode: ModeEnum.normal,
+ connections: [],
rawPlan: JSON.stringify([[]], null, ' '),
- uniqueList: [],
- scopeMap: {},
cronConfig: '0 0 * * *',
isManual: false,
skipOnFail: false,
timeAfter: null,
- onChangeStep: () => {},
- onChangeShowInspector: () => {},
- onChangeShowDetail: () => {},
+ onPrev: () => {},
+ onNext: () => {},
+ onSave: () => {},
+ onSaveAndRun: () => {},
onChangeMode: () => {},
onChangeName: () => {},
+ onChangeConnections: () => {},
onChangeRawPlan: () => {},
- onChangeUniqueList: () => {},
- onChangeScopeMap: () => {},
onChangeCronConfig: () => {},
onChangeIsManual: () => {},
onChangeSkipOnFail: () => {},
onChangeTimeAfter: () => {},
-
- onSave: () => {},
- onSaveAndRun: () => {},
});
interface Props {
@@ -71,18 +61,15 @@ interface Props {
children: React.ReactNode;
}
-export const BPContextProvider = ({ from, projectName, children }: Props) => {
+export const ContextProvider = ({ from, projectName, children }: Props) => {
const [step, setStep] = useState(1);
- const [showInspector, setShowInspector] = useState(false);
- const [showDetail, setShowDetail] = useState(false);
const [name, setName] = useState(
from === FromEnum.project ? `${window.decodeURIComponent(projectName)}-BLUEPRINT` : 'MY BLUEPRINT',
);
const [mode, setMode] = useState<ModeEnum>(ModeEnum.normal);
+ const [connections, setConnections] = useState<MixConnection[]>([]);
const [rawPlan, setRawPlan] = useState(JSON.stringify([[]], null, ' '));
- const [uniqueList, setUniqueList] = useState<string[]>([]);
- const [scopeMap, setScopeMap] = useState<Record<string, any>>({});
const [cronConfig, setCronConfig] = useState('0 0 * * *');
const [isManual, setIsManual] = useState(false);
const [skipOnFail, setSkipOnFail] = useState(true);
@@ -92,30 +79,6 @@ export const BPContextProvider = ({ from, projectName, children }: Props) => {
const history = useHistory();
- const { connections } = useConnection();
-
- const error = useMemo(() => {
- switch (true) {
- case !name:
- return 'Blueprint Name: Enter a valid Name';
- case name.length < 3:
- return 'Blueprint Name: Name too short, 3 chars minimum.';
- case mode === ModeEnum.advanced && validRawPlan(rawPlan):
- return 'Advanced Mode: Invalid/Empty Configuration';
- case mode === ModeEnum.normal && !uniqueList.length:
- return 'Normal Mode: No Data Connections selected.';
- case mode === ModeEnum.normal &&
- !connections
- .filter((cs) => uniqueList.includes(cs.unique))
- .every((cs) => cs.status === ConnectionStatusEnum.ONLINE):
- return 'Normal Mode: Has some offline connections';
- case step === 2 && Object.keys(scopeMap).filter((key) => scopeMap[key].length).length !== uniqueList.length:
- return 'No Data Scope is Selected';
- default:
- return '';
- }
- }, [name, mode, rawPlan, uniqueList, connections, step, scopeMap]);
-
const payload = useMemo(() => {
const params: any = {
name,
@@ -131,13 +94,14 @@ export const BPContextProvider = ({ from, projectName, children }: Props) => {
params.settings = {
version: '2.0.0',
timeAfter,
- connections: uniqueList.map((unique) => {
- const connection = connections.find((cs) => cs.unique === unique) as ConnectionItemType;
- const scope = scopeMap[unique] ?? [];
+ connections: connections.map((cs) => {
return {
- plugin: connection.plugin,
- connectionId: connection.id,
- scopes: scope,
+ plugin: cs.plugin,
+ connectionId: cs.connectionId,
+ scopes: cs.scope.map((sc) => ({
+ id: `${sc.id}`,
+ entities: sc.entities,
+ })),
};
}),
};
@@ -149,19 +113,7 @@ export const BPContextProvider = ({ from, projectName, children }: Props) => {
}
return params;
- }, [
- name,
- projectName,
- mode,
- cronConfig,
- isManual,
- skipOnFail,
- timeAfter,
- rawPlan,
- uniqueList,
- scopeMap,
- connections,
- ]);
+ }, [projectName, name, mode, connections, rawPlan, cronConfig, isManual, skipOnFail, timeAfter]);
const handleSaveAfter = (id: ID) => {
const path =
@@ -189,48 +141,43 @@ export const BPContextProvider = ({ from, projectName, children }: Props) => {
}
};
+ const handlePrev = () => setStep(step - 1);
+ const handleNext = () => setStep(step + 1);
+
return (
- <BPContext.Provider
+ <Context.Provider
value={{
step,
- error,
- showInspector,
- showDetail,
- payload,
- name,
mode,
+ name,
+ connections,
rawPlan,
- uniqueList,
- scopeMap,
cronConfig,
isManual,
skipOnFail,
timeAfter,
- onChangeStep: setStep,
- onChangeShowInspector: setShowInspector,
- onChangeShowDetail: setShowDetail,
+ onPrev: handlePrev,
+ onNext: handleNext,
+ onSave: handleSave,
+ onSaveAndRun: hanldeSaveAndRun,
- onChangeName: setName,
onChangeMode: setMode,
+ onChangeName: setName,
+ onChangeConnections: setConnections,
onChangeRawPlan: setRawPlan,
- onChangeUniqueList: setUniqueList,
- onChangeScopeMap: setScopeMap,
onChangeCronConfig: setCronConfig,
onChangeIsManual: setIsManual,
onChangeSkipOnFail: setSkipOnFail,
onChangeTimeAfter: setTimeAfter,
-
- onSave: handleSave,
- onSaveAndRun: hanldeSaveAndRun,
}}
>
{children}
- </BPContext.Provider>
+ </Context.Provider>
);
};
-export const useCreateBP = () => {
- return useContext(BPContext);
+export const useCreate = () => {
+ return useContext(Context);
};
diff --git a/config-ui/src/pages/blueprint/create/index.tsx b/config-ui/src/pages/blueprint/create/index.tsx
index 6f2b3b8ed..8cab63e30 100644
--- a/config-ui/src/pages/blueprint/create/index.tsx
+++ b/config-ui/src/pages/blueprint/create/index.tsx
@@ -16,21 +16,16 @@
*
*/
-import React, { useMemo } from 'react';
+import { useMemo } from 'react';
import { useParams } from 'react-router-dom';
-import { PageHeader, Inspector } from '@/components';
+import { PageHeader, Workflow } from '@/components';
import { ConnectionContextProvider } from '@/store';
import { ModeEnum, FromEnum } from '../types';
-import { BPContext, BPContextProvider } from './bp-context';
-import { WorkFlow, Action } from './components';
-import { StepOne } from './step-one';
-import { StepTwo } from './step-two';
-import { StepThree } from './step-three';
-import { StepFour } from './step-four';
-import * as S from './styled';
+import { ContextProvider, Context } from './context';
+import { Step1, Step2, Step3, Step4 } from './components';
interface Props {
from: FromEnum;
@@ -59,32 +54,26 @@ export const BlueprintCreatePage = ({ from }: Props) => {
return (
<ConnectionContextProvider filterBeta>
- <BPContextProvider from={from} projectName={pname}>
- <BPContext.Consumer>
- {({ step, mode, name, payload, showInspector, onChangeShowInspector }) => (
+ <ContextProvider from={from} projectName={pname}>
+ <Context.Consumer>
+ {({ step, mode }) => (
<PageHeader breadcrumbs={breadcrumbs}>
- <S.Container>
- <WorkFlow />
- <S.Content>
- {step === 1 && <StepOne from={from} />}
- {mode === ModeEnum.normal && step === 2 && <StepTwo />}
- {step === 3 && <StepThree />}
- {((mode === ModeEnum.normal && step === 4) || (mode === ModeEnum.advanced && step === 2)) && (
- <StepFour />
- )}
- </S.Content>
- <Action />
- <Inspector
- isOpen={showInspector}
- title={name}
- data={payload}
- onClose={() => onChangeShowInspector(false)}
- />
- </S.Container>
+ <Workflow
+ steps={
+ mode === ModeEnum.normal
+ ? ['Add Data Connections', 'Set Data Scope', 'Add Transformation (Optional)', 'Set Sync Policy']
+ : ['Create Advanced Configuration', 'Set Sync Policy']
+ }
+ activeStep={step}
+ />
+ {step === 1 && <Step1 from={from} />}
+ {mode === ModeEnum.normal && step === 2 && <Step2 />}
+ {step === 3 && <Step3 />}
+ {((mode === ModeEnum.normal && step === 4) || (mode === ModeEnum.advanced && step === 2)) && <Step4 />}
</PageHeader>
)}
- </BPContext.Consumer>
- </BPContextProvider>
+ </Context.Consumer>
+ </ContextProvider>
</ConnectionContextProvider>
);
};
diff --git a/config-ui/src/pages/blueprint/create/step-three/index.tsx b/config-ui/src/pages/blueprint/create/step-three/index.tsx
deleted file mode 100644
index 5e2396cb3..000000000
--- a/config-ui/src/pages/blueprint/create/step-three/index.tsx
+++ /dev/null
@@ -1,81 +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, useMemo } from 'react';
-import { Icon } from '@blueprintjs/core';
-
-import { Card, Table, Divider } from '@/components';
-import { useConnection } from '@/store';
-import { Transformation } from '@/plugins';
-
-import type { BPConnectionItemType } from '../types';
-import { useCreateBP } from '../bp-context';
-
-import { useColumns } from './use-columns';
-
-export const StepThree = () => {
- const [connection, setConnection] = useState<BPConnectionItemType>();
-
- const { connections } = useConnection();
- const { uniqueList, scopeMap, onChangeShowDetail } = useCreateBP();
-
- const handleGoDetail = (c: BPConnectionItemType) => {
- setConnection(c);
- onChangeShowDetail(true);
- };
-
- const handleBack = () => {
- setConnection(undefined);
- onChangeShowDetail(false);
- };
-
- const columns = useColumns({ onDetail: handleGoDetail });
- const dataSource = useMemo(
- () =>
- uniqueList.map((unique) => {
- const connection = connections.find((cs) => cs.unique === unique) as BPConnectionItemType;
- const scope = scopeMap[unique] ?? [];
- return {
- ...connection,
- scopeIds: scope.map((sc: any) => sc.id),
- };
- }),
- [uniqueList, connections, scopeMap],
- );
-
- return !connection ? (
- <Table columns={columns} dataSource={dataSource} />
- ) : (
- <Card>
- <div className="back" onClick={handleBack}>
- <Icon icon="arrow-left" size={14} />
- <span>Cancel and Go Back</span>
- </div>
- <h2>Add Transformation</h2>
- <Divider />
- <Transformation
- from="create"
- plugin={connection.plugin}
- connectionId={connection.id}
- scopeIds={connection.scopeIds}
- onCancel={handleBack}
- onSave={handleBack}
- />
- </Card>
- );
-};
diff --git a/config-ui/src/pages/blueprint/create/step-three/use-columns.tsx b/config-ui/src/pages/blueprint/create/step-three/use-columns.tsx
deleted file mode 100644
index 22bbff89e..000000000
--- a/config-ui/src/pages/blueprint/create/step-three/use-columns.tsx
+++ /dev/null
@@ -1,77 +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, { useMemo } from 'react';
-import { Button, Intent } from '@blueprintjs/core';
-
-import type { ColumnType } from '@/components';
-import { DataScopeList } from '@/plugins';
-
-import type { BPConnectionItemType } from '../types';
-
-import * as S from './styled';
-
-interface Props {
- onDetail: (connection: BPConnectionItemType) => void;
-}
-
-export const useColumns = ({ onDetail }: Props) => {
- return useMemo(
- () =>
- [
- {
- title: 'Data Connections',
- dataIndex: ['icon', 'name'],
- key: 'connection',
- render: ({ icon, name }: Pick<BPConnectionItemType, 'icon' | 'name'>) => (
- <S.ConnectionColumn>
- <img src={icon} alt="" />
- <span>{name}</span>
- </S.ConnectionColumn>
- ),
- },
- {
- title: 'Data Scope and Transformation',
- dataIndex: ['plugin', 'id', 'scopeIds'],
- key: 'unique',
- render: ({ plugin, id, scopeIds }: Pick<BPConnectionItemType, 'plugin' | 'id' | 'scopeIds'>) => (
- <DataScopeList groupByTs plugin={plugin} connectionId={id} scopeIds={scopeIds} />
- ),
- },
- {
- title: '',
- key: 'action',
- align: 'center',
- render: (_: any, connection: BPConnectionItemType) =>
- connection.plugin === 'sonarqube' || connection.plugin === 'zentao' ? (
- 'No Transformation Required'
- ) : (
- <Button
- small
- minimal
- intent={Intent.PRIMARY}
- icon="add"
- text="Add Transformation"
- onClick={() => onDetail(connection)}
- />
- ),
- },
- ] as ColumnType<BPConnectionItemType>,
- [],
- );
-};
diff --git a/config-ui/src/pages/blueprint/create/step-two/index.tsx b/config-ui/src/pages/blueprint/create/step-two/index.tsx
deleted file mode 100644
index 1d0389cfd..000000000
--- a/config-ui/src/pages/blueprint/create/step-two/index.tsx
+++ /dev/null
@@ -1,96 +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, useMemo } from 'react';
-import { Icon } from '@blueprintjs/core';
-
-import { Card, Table, Divider } from '@/components';
-import { useConnection } from '@/store';
-import { DataScope } from '@/plugins';
-
-import type { BPConnectionItemType } from '../types';
-import { useCreateBP } from '../bp-context';
-
-import { useColumns } from './use-columns';
-
-export const StepTwo = () => {
- const [connection, setConnection] = useState<BPConnectionItemType>();
-
- const { connections } = useConnection();
- const { uniqueList, scopeMap, onChangeScopeMap, onChangeShowDetail } = useCreateBP();
-
- const handleGoDetail = (c: BPConnectionItemType) => {
- setConnection(c);
- onChangeShowDetail(true);
- };
-
- const handleBack = () => {
- setConnection(undefined);
- onChangeShowDetail(false);
- };
-
- const handleSave = (scope: any) => {
- if (!connection) return;
- onChangeScopeMap({
- ...scopeMap,
- [`${connection.unique}`]: scope,
- });
- handleBack();
- };
-
- const columns = useColumns({ onDetail: handleGoDetail });
- const dataSource = useMemo(
- () =>
- uniqueList.map((unique) => {
- const connection = connections.find((cs) => cs.unique === unique) as BPConnectionItemType;
- const scope = scopeMap[unique] ?? [];
- return {
- ...connection,
- scopeIds: scope.map((sc: any) => sc.id),
- };
- }),
- [uniqueList, connections, scopeMap],
- );
-
- return !connection ? (
- <Table columns={columns} dataSource={dataSource} />
- ) : (
- <Card>
- <div className="back" onClick={handleBack}>
- <Icon icon="arrow-left" size={14} />
- <span>Cancel and Go Back to the Data Scope List</span>
- </div>
- <h2>Set Data Scope</h2>
- <Divider />
- <div className="connection">
- <img src={connection.icon} width={24} alt="" />
- <span>{connection.name}</span>
- </div>
- <DataScope
- plugin={connection.plugin}
- connectionId={connection.id}
- entities={connection.entities}
- initialValues={{
- scope: connection.scopeIds.map((id) => ({ id })),
- }}
- onCancel={handleBack}
- onSave={handleSave}
- />
- </Card>
- );
-};
diff --git a/config-ui/src/pages/blueprint/create/step-two/use-columns.tsx b/config-ui/src/pages/blueprint/create/step-two/use-columns.tsx
deleted file mode 100644
index 087fa3b0a..000000000
--- a/config-ui/src/pages/blueprint/create/step-two/use-columns.tsx
+++ /dev/null
@@ -1,74 +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, { useMemo } from 'react';
-import { Button, Intent } from '@blueprintjs/core';
-
-import type { ColumnType } from '@/components';
-import { DataScopeList } from '@/plugins';
-
-import type { BPConnectionItemType } from '../types';
-
-import * as S from './styled';
-
-interface Props {
- onDetail: (connection: BPConnectionItemType) => void;
-}
-
-export const useColumns = ({ onDetail }: Props) => {
- return useMemo(
- () =>
- [
- {
- title: 'Data Connections',
- dataIndex: ['icon', 'name'],
- key: 'connection',
- render: ({ icon, name }: Pick<BPConnectionItemType, 'icon' | 'name'>) => (
- <S.ConnectionColumn>
- <img src={icon} alt="" />
- <span>{name}</span>
- </S.ConnectionColumn>
- ),
- },
- {
- title: 'Data Scope',
- dataIndex: ['plugin', 'id', 'scopeIds'],
- key: 'unique',
- render: ({ plugin, id, scopeIds }: Pick<BPConnectionItemType, 'plugin' | 'id' | 'scopeIds'>) => (
- <DataScopeList groupByTs={false} plugin={plugin} connectionId={id} scopeIds={scopeIds} />
- ),
- },
- {
- title: '',
- key: 'action',
- align: 'center',
- render: (_: any, connection: BPConnectionItemType) => (
- <Button
- small
- minimal
- intent={Intent.PRIMARY}
- icon="cog"
- text="Set Data Scope"
- onClick={() => onDetail(connection)}
- />
- ),
- },
- ] as ColumnType<BPConnectionItemType>,
- [onDetail],
- );
-};
diff --git a/config-ui/src/pages/blueprint/create/types.ts b/config-ui/src/pages/blueprint/create/types.ts
index a3bd35d22..9717371d3 100644
--- a/config-ui/src/pages/blueprint/create/types.ts
+++ b/config-ui/src/pages/blueprint/create/types.ts
@@ -16,45 +16,32 @@
*
*/
-import type { ConnectionItemType } from '@/store';
-
import { ModeEnum } from '../types';
-export type BPConnectionItemType = ConnectionItemType & {
- scopeIds: string[];
-};
-
-export type BPContextType = {
+export type ContextType = {
step: number;
- error: string;
- showInspector: boolean;
- showDetail: boolean;
- payload: any;
- name: string;
mode: ModeEnum;
+ name: string;
+ connections: MixConnection[];
rawPlan: string;
- uniqueList: string[];
- scopeMap: Record<string, any>;
cronConfig: string;
isManual: boolean;
skipOnFail: boolean;
timeAfter: string | null;
- onChangeStep: React.Dispatch<React.SetStateAction<number>>;
- onChangeShowInspector: React.Dispatch<React.SetStateAction<boolean>>;
- onChangeShowDetail: React.Dispatch<React.SetStateAction<boolean>>;
+ onPrev: () => void;
+ onNext: () => void;
+
+ onSave: () => void;
+ onSaveAndRun: () => void;
- onChangeName: React.Dispatch<React.SetStateAction<string>>;
onChangeMode: (mode: ModeEnum) => void;
+ onChangeName: React.Dispatch<React.SetStateAction<string>>;
+ onChangeConnections: React.Dispatch<React.SetStateAction<MixConnection[]>>;
onChangeRawPlan: React.Dispatch<React.SetStateAction<string>>;
- onChangeUniqueList: React.Dispatch<React.SetStateAction<string[]>>;
- onChangeScopeMap: React.Dispatch<React.SetStateAction<Record<string, any>>>;
onChangeCronConfig: React.Dispatch<React.SetStateAction<string>>;
onChangeIsManual: React.Dispatch<React.SetStateAction<boolean>>;
onChangeSkipOnFail: React.Dispatch<React.SetStateAction<boolean>>;
onChangeTimeAfter: React.Dispatch<React.SetStateAction<string | null>>;
-
- onSave: () => void;
- onSaveAndRun: () => void;
};
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 bfbde9404..52044acef 100644
--- a/config-ui/src/pages/blueprint/detail/blueprint-detail-page.tsx
+++ b/config-ui/src/pages/blueprint/detail/blueprint-detail-page.tsx
@@ -16,11 +16,12 @@
*
*/
-import React from 'react';
import { useParams } from 'react-router-dom';
import { PageHeader, PageLoading } from '@/components';
+import { FromEnum } from '../types';
+
import { useDetail } from './use-detail';
import { BlueprintDetail } from './blueprint-detail';
@@ -40,7 +41,7 @@ export const BlueprintDetailPage = () => {
{ name: blueprint.name, path: `/blueprints/${blueprint.id}` },
]}
>
- <BlueprintDetail id={blueprint.id} />
+ <BlueprintDetail from={FromEnum.blueprint} id={blueprint.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 9690500cc..c3cc981d9 100644
--- a/config-ui/src/pages/blueprint/detail/blueprint-detail.tsx
+++ b/config-ui/src/pages/blueprint/detail/blueprint-detail.tsx
@@ -22,18 +22,31 @@ import { Tabs, Tab } from '@blueprintjs/core';
import { PageLoading } from '@/components';
+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 S from './styled';
-interface Props extends UseDetailProps {}
+interface Props extends UseDetailProps {
+ from?: FromEnum;
+ pname?: string;
+}
-export const BlueprintDetail = ({ id }: Props) => {
+export const BlueprintDetail = ({ from = FromEnum.project, pname, id }: Props) => {
const [activeTab, setActiveTab] = useState<TabId>('status');
- const { loading, blueprint, pipelineId, operating, onRun, onUpdate, onRefresh } = useDetail({
+ const paths = useMemo(
+ () =>
+ from === FromEnum.project
+ ? [`/projects/${pname}/${id}/connection-add`, `/projects/${pname}/${id}/`]
+ : [`/blueprints/${id}/connection-add`, `blueprints/${id}/`],
+ [from, pname],
+ );
+
+ const { loading, blueprint, pipelineId, operating, onRun, onUpdate } = useDetail({
id,
});
@@ -57,9 +70,7 @@ export const BlueprintDetail = ({ id }: Props) => {
<Tab
id="configuration"
title="Configuration"
- panel={
- <Configuration blueprint={blueprint} operating={operating} onUpdate={onUpdate} onRefresh={onRefresh} />
- }
+ panel={<Configuration paths={paths} blueprint={blueprint} operating={operating} onUpdate={onUpdate} />}
/>
</Tabs>
{showJenkinsTips && (
diff --git a/config-ui/src/pages/blueprint/detail/components/add-scope-dialog/index.tsx b/config-ui/src/pages/blueprint/detail/components/add-scope-dialog/index.tsx
deleted file mode 100644
index fdb3b07ae..000000000
--- a/config-ui/src/pages/blueprint/detail/components/add-scope-dialog/index.tsx
+++ /dev/null
@@ -1,59 +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 { Dialog } from '@/components';
-import { DataScope } from '@/plugins';
-
-import type { ConfigConnectionItemType } from '../../types';
-
-interface Props {
- connection?: ConfigConnectionItemType;
- onCancel: () => void;
- onSubmit: (connection: any) => void;
-}
-
-export const AddScopeDialog = ({ connection, onCancel, onSubmit }: Props) => {
- if (!connection) return null;
-
- const { plugin, connectionId, entities, scope } = connection;
-
- const handleSaveScope = (scope: any) => {
- onSubmit({
- plugin,
- connectionId,
- scopes: scope,
- });
- };
-
- return (
- <Dialog isOpen title="Change Data Scope" footer={null} style={{ width: 900 }} onCancel={onCancel}>
- <DataScope
- plugin={plugin}
- connectionId={connectionId}
- entities={entities}
- initialValues={{
- scope,
- }}
- onCancel={onCancel}
- onSave={handleSaveScope}
- />
- </Dialog>
- );
-};
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
new file mode 100644
index 000000000..2dd24bb42
--- /dev/null
+++ b/config-ui/src/pages/blueprint/detail/components/connection-list/index.tsx
@@ -0,0 +1,69 @@
+/*
+ * 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 { PluginConfig } 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 connections = useMemo(
+ () =>
+ blueprint.settings?.connections
+ .filter((cs) => cs.plugin !== 'webhook')
+ .map((cs: any) => {
+ const plugin = PluginConfig.find((p) => p.plugin === cs.plugin) as any;
+ return {
+ unique: `${cs.plugin}-${cs.connectionId}`,
+ icon: plugin.icon,
+ name: plugin.name,
+ scope: cs.scopes,
+ };
+ })
+ .filter(Boolean),
+ [blueprint],
+ );
+
+ return (
+ <S.List>
+ {connections.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/create/styled.ts b/config-ui/src/pages/blueprint/detail/components/connection-list/styled.ts
similarity index 67%
rename from config-ui/src/pages/blueprint/create/styled.ts
rename to config-ui/src/pages/blueprint/detail/components/connection-list/styled.ts
index 378109925..da2ce28c2 100644
--- a/config-ui/src/pages/blueprint/create/styled.ts
+++ b/config-ui/src/pages/blueprint/detail/components/connection-list/styled.ts
@@ -18,39 +18,38 @@
import styled from 'styled-components';
-export const Container = styled.div``;
+export const List = styled.ul`
+ display: flex;
+ align-items: center;
+`;
-export const Content = styled.div`
- margin-top: 36px;
- margin-bottom: 24px;
+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;
- .card + .card {
- margin-top: 24px;
+ &:last-child {
+ margin-right: 0;
}
- .back {
+ .title {
display: flex;
align-items: center;
- margin-bottom: 12px;
- color: #7497f7;
- cursor: pointer;
- span.bp4-icon {
- margin-right: 4px;
- cursor: pointer;
+ img {
+ width: 24px;
+ height: 24px;
}
- }
-
- .connection {
- display: flex;
- align-items: center;
- margin-bottom: 12px;
span {
margin-left: 8px;
- font-size: 14px;
- color: #292b3f;
- font-weight: 600;
}
}
+
+ .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 611aa2ae3..f892b941f 100644
--- a/config-ui/src/pages/blueprint/detail/components/index.ts
+++ b/config-ui/src/pages/blueprint/detail/components/index.ts
@@ -18,5 +18,4 @@
export * from './update-name-dialog';
export * from './update-policy-dialog';
-export * from './add-scope-dialog';
-export * from './update-transformation-dialog';
+export * from './connection-list';
diff --git a/config-ui/src/pages/blueprint/detail/components/update-transformation-dialog/index.tsx b/config-ui/src/pages/blueprint/detail/components/update-transformation-dialog/index.tsx
deleted file mode 100644
index 7eea90f0c..000000000
--- a/config-ui/src/pages/blueprint/detail/components/update-transformation-dialog/index.tsx
+++ /dev/null
@@ -1,60 +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 { Dialog } from '@/components';
-import { Transformation } from '@/plugins';
-
-import type { ConfigConnectionItemType } from '../../types';
-
-interface Props {
- connection?: ConfigConnectionItemType;
- onCancel: () => void;
- onRefresh: () => void;
-}
-
-export const UpdateTransformationDialog = ({ connection, onCancel, onRefresh }: Props) => {
- if (!connection) return null;
-
- const { plugin, connectionId, scope } = connection;
-
- const handleSaveAfter = () => {
- onRefresh();
- onCancel();
- };
-
- return (
- <Dialog
- isOpen
- title="Assign a different transformation by"
- footer={null}
- style={{ width: 900 }}
- onCancel={onCancel}
- >
- <Transformation
- from="update"
- plugin={plugin}
- connectionId={connectionId}
- scopeIds={scope.map((sc) => sc.id)}
- onCancel={onCancel}
- onSave={handleSaveAfter}
- />
- </Dialog>
- );
-};
diff --git a/config-ui/src/pages/blueprint/detail/panel/configuration.tsx b/config-ui/src/pages/blueprint/detail/panel/configuration.tsx
index d7b042769..8038ae7fb 100644
--- a/config-ui/src/pages/blueprint/detail/panel/configuration.tsx
+++ b/config-ui/src/pages/blueprint/detail/panel/configuration.tsx
@@ -16,36 +16,32 @@
*
*/
-import React, { useState, useEffect, useMemo } from 'react';
+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 { Table, ColumnType } from '@/components';
-import { getCron, transformEntities } from '@/config';
-import { PluginConfig, DataScopeList } from '@/plugins';
+import { getCron } from '@/config';
import type { BlueprintType } from '../../types';
import { ModeEnum } from '../../types';
import { validRawPlan } from '../../utils';
import { AdvancedEditor } from '../../components';
-import type { ConfigConnectionItemType } from '../types';
-import { UpdateNameDialog, UpdatePolicyDialog, AddScopeDialog, UpdateTransformationDialog } from '../components';
+import { UpdateNameDialog, UpdatePolicyDialog, ConnectionList } 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;
- onRefresh: () => void;
}
-export const Configuration = ({ blueprint, operating, onUpdate, onRefresh }: Props) => {
+export const Configuration = ({ paths, blueprint, operating, onUpdate }: Props) => {
const [type, setType] = useState<Type>();
- const [curConnection, setCurConnection] = useState<ConfigConnectionItemType>();
const [rawPlan, setRawPlan] = useState('');
const history = useHistory();
@@ -56,27 +52,6 @@ export const Configuration = ({ blueprint, operating, onUpdate, onRefresh }: Pro
const cron = useMemo(() => getCron(blueprint.isManual, blueprint.cronConfig), [blueprint]);
- const connections = useMemo(
- () =>
- blueprint.settings?.connections
- .filter((cs) => cs.plugin !== 'webhook')
- .map((cs: any) => {
- const plugin = PluginConfig.find((p) => p.plugin === cs.plugin) as any;
- return {
- icon: plugin.icon,
- name: plugin.name,
- connectionId: cs.connectionId,
- entities: plugin.entities,
- selectedEntites: cs.scopes?.[0]?.entities ?? [],
- plugin: cs.plugin,
- scope: cs.scopes,
- scopeIds: cs.scopes.map((sc: any) => sc.id),
- };
- })
- .filter(Boolean),
- [blueprint],
- );
-
const handleCancel = () => {
setType(undefined);
};
@@ -93,133 +68,47 @@ export const Configuration = ({ blueprint, operating, onUpdate, onRefresh }: Pro
const handleToggleEnabled = (checked: boolean) => onUpdate({ enable: checked });
- const handleUpdateConnection = (updated: any) =>
- onUpdate({
- settings: {
- ...blueprint.settings,
- connections: blueprint.settings.connections.map((cs) =>
- cs.plugin === updated.plugin && cs.connectionId === updated.connectionId ? updated : cs,
- ),
- },
- });
-
const handleUpdatePlan = () =>
onUpdate({
plan: !validRawPlan(rawPlan) ? JSON.parse(rawPlan) : JSON.stringify([[]], null, ' '),
});
- const columns = useMemo(
- () =>
- [
- {
- title: 'Data Connections',
- dataIndex: ['icon', 'name'],
- key: 'connection',
- render: ({ icon, name }: Pick<ConfigConnectionItemType, 'icon' | 'name'>) => (
- <S.ConnectionColumn>
- <img src={icon} alt="" />
- <span>{name}</span>
- </S.ConnectionColumn>
- ),
- },
- {
- title: 'Data Entities',
- dataIndex: 'selectedEntites',
- key: 'selectedEntites',
- render: (val: string[]) => (
- <>
- {transformEntities(val).map(({ label, value }) => (
- <div key={value}>{label}</div>
- ))}
- </>
- ),
- },
- {
- title: 'Data Scope and Transformation',
- dataIndex: ['plugin', 'connectionId', 'scopeIds'],
- key: 'sopce',
- render: ({
- plugin,
- connectionId,
- scopeIds,
- }: Pick<ConfigConnectionItemType, 'plugin' | 'connectionId' | 'scopeIds'>) => (
- <DataScopeList groupByTs plugin={plugin} connectionId={connectionId} scopeIds={scopeIds} />
- ),
- },
- {
- title: '',
- key: 'action',
- align: 'center',
- render: (_, row: ConfigConnectionItemType) => (
- <S.ActionColumn>
- <div
- className="item"
- onClick={() => {
- setType('scope');
- setCurConnection(row);
- }}
- >
- <Icon icon="annotation" color={Colors.BLUE2} />
- <span>Change Data Scope</span>
- </div>
- {row.plugin !== 'sonarqube' && row.plugin !== 'zentao' && (
- <>
- <div
- className="item"
- onClick={() => {
- setType('transformation');
- setCurConnection(row);
- }}
- >
- <Icon icon="annotation" color={Colors.BLUE2} />
- <span>Re-apply Transformation</span>
- </div>
- <div className="item" onClick={() => history.push('/transformations')}>
- <Icon icon="cog" color={Colors.BLUE2} />
- <span>Manage Transformations</span>
- </div>
- </>
- )}
- </S.ActionColumn>
- ),
- },
- ] as ColumnType<ConfigConnectionItemType>,
- [],
- );
-
return (
<S.ConfigurationPanel>
<div className="top">
- <div className="block">
- <h3>Name</h3>
- <div className="detail">
- <span>{blueprint.name}</span>
- <Icon icon="annotation" color={Colors.BLUE2} onClick={() => setType('name')} />
- </div>
- </div>
- <div className="block">
- <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>
- </div>
- <div className="block">
- <h3>Enabled</h3>
- <div className="detail">
- <Switch
- checked={blueprint.enable}
- onChange={(e) => handleToggleEnabled((e.target as HTMLInputElement).checked)}
- />
- </div>
- </div>
+ <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>
{blueprint.mode === ModeEnum.normal && (
<div className="bottom">
- <h3>Data Scope and Transformation</h3>
- <Table columns={columns} dataSource={connections} />
+ <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>
)}
{blueprint.mode === ModeEnum.advanced && (
@@ -251,12 +140,6 @@ export const Configuration = ({ blueprint, operating, onUpdate, onRefresh }: Pro
onSubmit={handleUpdatePolicy}
/>
)}
- {type === 'scope' && (
- <AddScopeDialog connection={curConnection} onCancel={handleCancel} onSubmit={handleUpdateConnection} />
- )}
- {type === 'transformation' && (
- <UpdateTransformationDialog connection={curConnection} onCancel={handleCancel} onRefresh={onRefresh} />
- )}
</S.ConfigurationPanel>
);
};
diff --git a/config-ui/src/pages/blueprint/detail/styled.ts b/config-ui/src/pages/blueprint/detail/styled.ts
index 0a772d8a1..b1a6d8453 100644
--- a/config-ui/src/pages/blueprint/detail/styled.ts
+++ b/config-ui/src/pages/blueprint/detail/styled.ts
@@ -26,13 +26,15 @@ export const ConfigurationPanel = styled.div`
.top {
display: flex;
align-items: flex-start;
+ justify-content: space-between;
- .block + .block {
- margin-left: 32px;
- }
+ ul {
+ display: flex;
+ align-items: flex-start;
- h3 {
- margin: 0 0 8px;
+ li + li {
+ margin-left: 48px;
+ }
}
.detail {
@@ -44,7 +46,13 @@ export const ConfigurationPanel = styled.div`
}
.bottom {
- margin-top: 32px;
+ margin-top: 24px;
+
+ h3 {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ }
.btns {
margin-top: 16px;
diff --git a/config-ui/src/pages/blueprint/detail/use-detail.ts b/config-ui/src/pages/blueprint/detail/use-detail.ts
index 344ffc522..8c7987eca 100644
--- a/config-ui/src/pages/blueprint/detail/use-detail.ts
+++ b/config-ui/src/pages/blueprint/detail/use-detail.ts
@@ -94,7 +94,6 @@ export const useDetail = ({ id }: UseDetailProps) => {
pipelineId,
onRun: handleRun,
onUpdate: handleUpdate,
- onRefresh: getBlueprint,
}),
[loading, operating, blueprint, pipelineId],
);
diff --git a/config-ui/src/pages/blueprint/index.ts b/config-ui/src/pages/blueprint/index.ts
index 2511a1467..cd8a30e1f 100644
--- a/config-ui/src/pages/blueprint/index.ts
+++ b/config-ui/src/pages/blueprint/index.ts
@@ -20,3 +20,5 @@ export * from './types';
export * from './home';
export * from './create';
export * from './detail';
+export * from './connection-add';
+export * from './connection-detail';
diff --git a/config-ui/src/pages/index.ts b/config-ui/src/pages/index.ts
index 3002d2306..307e9f433 100644
--- a/config-ui/src/pages/index.ts
+++ b/config-ui/src/pages/index.ts
@@ -20,4 +20,3 @@ export * from './project';
export * from './connection';
export * from './blueprint';
export * from './pipeline';
-export * from './transformation';
diff --git a/config-ui/src/pages/project/detail/panel/blueprint.tsx b/config-ui/src/pages/project/detail/panel/blueprint.tsx
index 0c99dcc5f..d818446ed 100644
--- a/config-ui/src/pages/project/detail/panel/blueprint.tsx
+++ b/config-ui/src/pages/project/detail/panel/blueprint.tsx
@@ -46,5 +46,5 @@ export const BlueprintPanel = ({ project }: Props) => {
);
}
- return <BlueprintDetail id={project.blueprint.id} />;
+ return <BlueprintDetail pname={project.name} id={project.blueprint.id} />;
};
diff --git a/config-ui/src/pages/transformation/detail/index.tsx b/config-ui/src/pages/transformation/detail/index.tsx
deleted file mode 100644
index 47d7fb3d1..000000000
--- a/config-ui/src/pages/transformation/detail/index.tsx
+++ /dev/null
@@ -1,103 +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 { useParams, useHistory } from 'react-router-dom';
-import { InputGroup, ButtonGroup, Button, Intent } from '@blueprintjs/core';
-
-import { PageLoading, PageHeader, Card } from '@/components';
-import { GitHubTransformation } from '@/plugins/register/github';
-import { GitLabTransformation } from '@/plugins/register/gitlab';
-import { JenkinsTransformation } from '@/plugins/register/jenkins';
-import { BitbucketTransformation } from '@/plugins/register/bitbucket';
-
-import { useDetail } from './use-detail';
-import * as S from './styled';
-
-export const TransformationDetailPage = () => {
- const { plugin, tid } = useParams<{ plugin: string; tid?: string }>();
- const history = useHistory();
-
- const { loading, operating, name, transformation, onChangeName, onChangeTransformation, onSave } = useDetail({
- plugin,
- id: tid,
- });
-
- if (loading) {
- return <PageLoading />;
- }
-
- return (
- <PageHeader
- breadcrumbs={[
- { name: 'Transformations', path: '/transformations' },
- {
- name: plugin,
- path: '/transformations',
- },
- {
- name: 'Create',
- path: `/transformations/${plugin}/${tid ? tid : 'Create'}`,
- },
- ]}
- >
- <S.Wrapper>
- <Card className="name card">
- <h3>Transformation Name *</h3>
- <p>Give this set of transformation rules a unique name so that you can identify it in the future.</p>
- <InputGroup
- placeholder="Enter Transformation Name"
- value={name}
- onChange={(e) => onChangeName(e.target.value)}
- />
- </Card>
- <Card className="card">
- {plugin === 'github' && (
- <GitHubTransformation transformation={transformation} setTransformation={onChangeTransformation} />
- )}
-
- {plugin === 'gitlab' && (
- <GitLabTransformation transformation={transformation} setTransformation={onChangeTransformation} />
- )}
-
- {plugin === 'jenkins' && (
- <JenkinsTransformation transformation={transformation} setTransformation={onChangeTransformation} />
- )}
-
- {plugin === 'bitbucket' && (
- <BitbucketTransformation transformation={transformation} setTransformation={onChangeTransformation} />
- )}
-
- <div className="action">
- <ButtonGroup>
- <Button disabled={operating} outlined text="Cancel" onClick={() => history.push('/transformations')} />
- <Button
- disabled={!name}
- loading={operating}
- outlined
- intent={Intent.PRIMARY}
- text="Save"
- onClick={onSave}
- />
- </ButtonGroup>
- </div>
- </Card>
- </S.Wrapper>
- </PageHeader>
- );
-};
diff --git a/config-ui/src/pages/transformation/detail/use-detail.ts b/config-ui/src/pages/transformation/detail/use-detail.ts
deleted file mode 100644
index 37705aabb..000000000
--- a/config-ui/src/pages/transformation/detail/use-detail.ts
+++ /dev/null
@@ -1,89 +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 type { PluginConfigType } from '@/plugins';
-import { PluginConfig } from '@/plugins';
-import { operator } from '@/utils';
-
-import * as API from './api';
-
-interface Props {
- plugin: string;
- id?: ID;
-}
-
-export const useDetail = ({ plugin, id }: Props) => {
- const [loading, setLoading] = useState(false);
- const [operating, setOperating] = useState(false);
- const [name, setName] = useState('');
- const [transformation, setTransformation] = useState<any>(
- (PluginConfig.find((pc) => pc.plugin === plugin) as PluginConfigType)?.transformation,
- );
-
- const history = useHistory();
-
- const getTransformation = async () => {
- if (!id) return;
- setLoading(true);
- try {
- const res = await API.getTransformation(plugin, id);
- setName(res.name);
- setTransformation(res);
- } finally {
- setLoading(false);
- }
- };
-
- useEffect(() => {
- getTransformation();
- }, []);
-
- const handleSave = async () => {
- const payload = {
- ...transformation,
- name,
- };
-
- const [success] = await operator(
- () => (id ? API.updateTransformation(plugin, id, payload) : API.createTransformation(plugin, payload)),
- {
- setOperating,
- },
- );
-
- if (success) {
- history.push('/transformations');
- }
- };
-
- return useMemo(
- () => ({
- loading,
- operating,
- name,
- transformation,
- onChangeName: setName,
- onChangeTransformation: setTransformation,
- onSave: handleSave,
- }),
- [loading, operating, name, transformation],
- );
-};
diff --git a/config-ui/src/pages/transformation/home/index.tsx b/config-ui/src/pages/transformation/home/index.tsx
deleted file mode 100644
index 35dae32f2..000000000
--- a/config-ui/src/pages/transformation/home/index.tsx
+++ /dev/null
@@ -1,136 +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, useMemo } from 'react';
-import { useHistory } from 'react-router-dom';
-import { ButtonGroup, Button, Icon, Intent } from '@blueprintjs/core';
-
-import { PageHeader, Table, ColumnType, Dialog, Selector, IconButton } from '@/components';
-import type { PluginConfigType } from '@/plugins';
-import { TransformationContextProvider, TransformationContextConsumer, TransformationItemType } from '@/store';
-
-import * as S from './styled';
-
-export const TransformationHomePage = () => {
- const [active, setActive] = useState('All');
- const [isOpen, setIsOpen] = useState(false);
- const [selectedPlugin, setSelectedPlugin] = useState<PluginConfigType>();
-
- const history = useHistory();
-
- const handleCreate = () => setIsOpen(true);
-
- const columns = useMemo(
- () =>
- [
- {
- title: 'Name',
- dataIndex: 'name',
- key: 'name',
- },
- {
- title: 'Data Source',
- dataIndex: 'plugin',
- key: 'plugin',
- },
- {
- title: '',
- key: 'action',
- width: 100,
- align: 'center',
- render: (_, row) =>
- row.plugin !== 'jira' && (
- <IconButton
- icon="cog"
- tooltip="Detail"
- onClick={() => history.push(`/transformations/${row.plugin}/${row.id}`)}
- />
- ),
- },
- ] as ColumnType<TransformationItemType>,
- [],
- );
-
- return (
- <TransformationContextProvider>
- <TransformationContextConsumer>
- {({ plugins, transformations }) => (
- <PageHeader breadcrumbs={[{ name: 'Transformations', path: '/transformations' }]}>
- <S.Wrapper>
- <div className="action">
- <ButtonGroup>
- <Button
- intent={active === 'All' ? Intent.PRIMARY : Intent.NONE}
- text="All"
- onClick={() => setActive('All')}
- />
- {plugins.map((p) => (
- <Button
- key={p.plugin}
- intent={active === p.plugin ? Intent.PRIMARY : Intent.NONE}
- text={p.name}
- onClick={() => setActive(p.plugin)}
- />
- ))}
- </ButtonGroup>
- <Button icon="plus" intent={Intent.PRIMARY} text="New Transformation" onClick={handleCreate} />
- </div>
- <Table
- columns={columns}
- dataSource={transformations.filter((ts) => (active === 'All' ? true : ts.plugin === active))}
- noData={{
- text: 'There is no transformation yet. Please add a new transformation.',
- btnText: 'New Transformation',
- onCreate: handleCreate,
- }}
- />
- <Dialog
- isOpen={isOpen}
- title="Select a Data Source"
- okText="Continue"
- okDisabled={!selectedPlugin || selectedPlugin.plugin === 'jira'}
- onOk={() => history.push(`/transformations/${selectedPlugin?.plugin}/create`)}
- onCancel={() => setIsOpen(false)}
- >
- <S.DialogWrapper>
- <p>Select from the supported data sources</p>
- <Selector
- items={plugins}
- getKey={(it) => it.plugin}
- getName={(it) => it.name}
- selectedItem={selectedPlugin}
- onChangeItem={(selectedItem) => setSelectedPlugin(selectedItem)}
- />
- {selectedPlugin?.plugin === 'jira' && (
- <div className="warning">
- <Icon icon="error" />
- <span>
- Because Jira transformation is specific to every Jira connection, you can only add a
- Transformation in Blueprints.
- </span>
- </div>
- )}
- </S.DialogWrapper>
- </Dialog>
- </S.Wrapper>
- </PageHeader>
- )}
- </TransformationContextConsumer>
- </TransformationContextProvider>
- );
-};
diff --git a/config-ui/src/plugins/components/data-scope/api.ts b/config-ui/src/plugins/components/data-scope-form/api.ts
similarity index 93%
rename from config-ui/src/plugins/components/data-scope/api.ts
rename to config-ui/src/plugins/components/data-scope-form/api.ts
index dc16c2005..19abb110d 100644
--- a/config-ui/src/plugins/components/data-scope/api.ts
+++ b/config-ui/src/plugins/components/data-scope-form/api.ts
@@ -18,8 +18,8 @@
import { request } from '@/utils';
-export const getDataScope = (plugin: string, connectionId: ID, repoId: ID) =>
- request(`/plugins/${plugin}/connections/${connectionId}/scopes/${repoId}`);
+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`, {
diff --git a/config-ui/src/plugins/components/data-scope-form/index.tsx b/config-ui/src/plugins/components/data-scope-form/index.tsx
new file mode 100644
index 000000000..57de5ba1d
--- /dev/null
+++ b/config-ui/src/plugins/components/data-scope-form/index.tsx
@@ -0,0 +1,196 @@
+/*
+ * 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 { Button, Intent } from '@blueprintjs/core';
+
+import { Card, MultiSelector } from '@/components';
+import { transformEntities } from '@/config';
+import type { PluginConfigType } from '@/plugins';
+import { PluginConfig, 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 { SonarQubeDataScope } from '@/plugins/register/sonarqube';
+import { ZentaoDataScope } from '@/plugins/register/zentao';
+
+import * as API from './api';
+import * as S from './styled';
+
+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(() => PluginConfig.find((p) => p.plugin === plugin) as PluginConfigType, []);
+
+ 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 Promise.all([
+ API.updateDataScopeWithType(plugin, connectionId, 'product', {
+ data: data.filter((s) => s.type !== 'project'),
+ }),
+ API.updateDataScopeWithType(plugin, connectionId, 'project', {
+ data: data.filter((s) => s.type === 'project'),
+ }),
+ ])
+ : await API.updateDataScope(plugin, connectionId, {
+ data,
+ });
+
+ onSubmit?.(
+ res.map((it: any) => ({
+ 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 === 'sonarqube' && (
+ <SonarQubeDataScope 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/transformation/detail/styled.ts b/config-ui/src/plugins/components/data-scope-form/styled.ts
similarity index 83%
rename from config-ui/src/pages/transformation/detail/styled.ts
rename to config-ui/src/plugins/components/data-scope-form/styled.ts
index ec7ce2278..e094ccea8 100644
--- a/config-ui/src/pages/transformation/detail/styled.ts
+++ b/config-ui/src/plugins/components/data-scope-form/styled.ts
@@ -19,23 +19,14 @@
import styled from 'styled-components';
export const Wrapper = styled.div`
- .card + .card {
- margin-top: 32px;
- }
-
- .name {
- h3 {
- margin: 0 0 8px;
- }
-
- p {
- margin: 0 0 8px;
- }
+ .block {
+ margin-bottom: 16px;
}
.action {
display: flex;
- justify-content: flex-end;
- margin-top: 16px;
+ align-items: center;
+ justify-content: space-between;
+ margin-top: 36px;
}
`;
diff --git a/config-ui/src/plugins/components/data-scope-list/index.tsx b/config-ui/src/plugins/components/data-scope-list/index.tsx
deleted file mode 100644
index aad93209e..000000000
--- a/config-ui/src/plugins/components/data-scope-list/index.tsx
+++ /dev/null
@@ -1,74 +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 { Icon, Position } from '@blueprintjs/core';
-import { Tooltip2 } from '@blueprintjs/popover2';
-
-import { Loading } from '@/components';
-
-import type { UseDataScopeList } from './use-data-scope-list';
-import { useDataScopeList } from './use-data-scope-list';
-import * as S from './styled';
-
-interface Props extends UseDataScopeList {
- groupByTs: boolean;
- scopeIds: string[];
-}
-
-export const DataScopeList = ({ groupByTs, scopeIds, ...props }: Props) => {
- const { loading, scope, scopeTsMap } = useDataScopeList({ scopeIds, ...props });
-
- if (!scope.length) {
- return <span>No Data Scope Selected</span>;
- }
-
- if (loading) {
- return <Loading />;
- }
-
- return (
- <S.ScopeList>
- {!groupByTs &&
- scope.map((sc) => (
- <S.ScopeItem key={sc.id}>
- <span>{sc.name}</span>
- </S.ScopeItem>
- ))}
-
- {groupByTs &&
- Object.keys(scopeTsMap).map((name) => (
- <S.ScopeItemMap key={name}>
- <div className="title">
- <Icon icon="function" />
- <span>{name}</span>
- </div>
- <ul>
- {scopeTsMap[name].map((sc) => (
- <li key={sc.id}>
- <Tooltip2 className="name" content={sc.name} position={Position.TOP}>
- <span>{sc.name}</span>
- </Tooltip2>
- </li>
- ))}
- </ul>
- </S.ScopeItemMap>
- ))}
- </S.ScopeList>
- );
-};
diff --git a/config-ui/src/plugins/components/data-scope-list/styled.ts b/config-ui/src/plugins/components/data-scope-list/styled.ts
deleted file mode 100644
index 2ab16088d..000000000
--- a/config-ui/src/plugins/components/data-scope-list/styled.ts
+++ /dev/null
@@ -1,68 +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 ScopeList = styled.ul``;
-
-export const ScopeItem = styled.li`
- margin-bottom: 4px;
-
- &:last-child {
- margin-bottom: 0;
- }
-
- .bp4-button {
- margin-left: 6px;
- }
-`;
-
-export const ScopeItemMap = styled(ScopeItem)`
- margin-bottom: 12px;
-
- .title {
- display: flex;
- align-items: center;
- font-size: 12px;
-
- span {
- font-weight: 600;
- }
-
- span.bp4-icon {
- margin-right: 6px;
- }
- }
-
- & > ul {
- padding-left: 24px;
- margin-top: 4px;
-
- li {
- display: flex;
- align-items: center;
- justify-content: space-between;
-
- span.name {
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- }
- }
-`;
diff --git a/config-ui/src/plugins/components/data-scope-list/use-data-scope-list.ts b/config-ui/src/plugins/components/data-scope-list/use-data-scope-list.ts
deleted file mode 100644
index 5cf3be985..000000000
--- a/config-ui/src/plugins/components/data-scope-list/use-data-scope-list.ts
+++ /dev/null
@@ -1,105 +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 { groupBy } from 'lodash';
-
-import * as API from './api';
-
-type ScopeItem = {
- id: string;
- name: string;
- transformationRuleName?: string;
-};
-
-export interface UseDataScopeList {
- plugin: string;
- connectionId: ID;
- scopeIds: string[];
-}
-
-export const useDataScopeList = ({ plugin, connectionId, scopeIds }: UseDataScopeList) => {
- const [loading, setLoading] = useState(false);
- const [scope, setScope] = useState<ScopeItem[]>([]);
-
- const formatScope = (scope: any) => {
- return scope.map((sc: any) => {
- switch (true) {
- case plugin === 'github':
- return {
- id: `${sc.githubId}`,
- name: sc.name,
- transformationRuleName: sc.transformationRuleName,
- };
- case plugin === 'jira':
- return {
- id: `${sc.boardId}`,
- name: sc.name,
- transformationRuleName: sc.transformationRuleName,
- };
- case plugin === 'gitlab':
- return {
- id: `${sc.gitlabId}`,
- name: sc.name,
- transformationRuleName: sc.transformationRuleName,
- };
- case plugin === 'jenkins':
- return {
- id: sc.jobFullName,
- name: sc.jobFullName,
- transformationRuleName: sc.transformationRuleName,
- };
- case plugin === 'bitbucket':
- return {
- id: sc.bitbucketId,
- name: sc.name,
- transformationRuleName: sc.transformationRuleName,
- };
- default:
- return {
- id: sc.id,
- name: sc.name,
- transformationRuleName: sc.transformationRuleName,
- };
- }
- });
- };
-
- const getScopeDetail = async () => {
- setLoading(true);
- try {
- const res = await Promise.all(scopeIds.map((id) => API.getDataScopeRepo(plugin, connectionId, id)));
- setScope(formatScope(res));
- } finally {
- setLoading(false);
- }
- };
-
- useEffect(() => {
- getScopeDetail();
- }, [scopeIds]);
-
- return useMemo(
- () => ({
- loading,
- scope,
- scopeTsMap: groupBy(scope, (it) => it.transformationRuleName ?? 'No Transformation'),
- }),
- [loading, scope],
- );
-};
diff --git a/config-ui/src/plugins/components/data-scope/index.tsx b/config-ui/src/plugins/components/data-scope/index.tsx
index 7beed0529..c7a76a2ce 100644
--- a/config-ui/src/plugins/components/data-scope/index.tsx
+++ b/config-ui/src/plugins/components/data-scope/index.tsx
@@ -16,99 +16,169 @@
*
*/
-import React from 'react';
-import { ButtonGroup, Button, Intent } from '@blueprintjs/core';
+import { useState, useMemo } from 'react';
+import { Button, Intent } from '@blueprintjs/core';
-import { transformEntities } from '@/config';
-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 { SonarQubeDataScope } from '@/plugins/register/sonarqube';
-import { MultiSelector } from '@/components';
+import { Table, Dialog } from '@/components';
+import { DataScopeForm } from '@/plugins';
-import type { UseDataScope } from './use-data-scope';
-import { useDataScope } from './use-data-scope';
import * as S from './styled';
-import {ZentaoDataScope} from "@/plugins/register/zentao";
-interface Props extends UseDataScope {
+interface Props {
+ connections: MixConnection[];
+ initialScope?: any[];
+ initialEntities?: string[];
+ cancelBtnProps?: {
+ text?: string;
+ };
+ submitBtnProps?: {
+ text?: string;
+ };
onCancel?: () => void;
+ onSubmit?: (connections: MixConnection[]) => void;
+ onNext?: () => void;
}
-export const DataScope = ({ plugin, connectionId, entities, onCancel, ...props }: Props) => {
- const { saving, selectedScope, selectedEntities, onChangeScope, onChangeEntites, onSave } = useDataScope({
- ...props,
- plugin,
- connectionId,
- entities,
- });
+export const DataScope = ({
+ connections,
+ initialScope,
+ initialEntities,
+ cancelBtnProps,
+ submitBtnProps,
+ onCancel,
+ onSubmit,
+ onNext,
+}: Props) => {
+ const [connection, setConnection] = useState<MixConnection>();
- return (
- <S.Wrapper>
- <div className="block">
- {plugin === 'github' && (
- <GitHubDataScope connectionId={connectionId} selectedItems={selectedScope} onChangeItems={onChangeScope} />
- )}
-
- {plugin === 'jira' && (
- <JiraDataScope connectionId={connectionId} selectedItems={selectedScope} onChangeItems={onChangeScope} />
- )}
-
- {plugin === 'gitlab' && (
- <GitLabDataScope connectionId={connectionId} selectedItems={selectedScope} onChangeItems={onChangeScope} />
- )}
+ const error = useMemo(() => (!connections.every((cs) => cs.scope.length) ? true : false), [connections]);
- {plugin === 'jenkins' && (
- <JenkinsDataScope connectionId={connectionId} selectedItems={selectedScope} onChangeItems={onChangeScope} />
- )}
+ const handleCancel = () => setConnection(undefined);
- {plugin === 'bitbucket' && (
- <BitbucketDataScope connectionId={connectionId} selectedItems={selectedScope} onChangeItems={onChangeScope} />
- )}
+ 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();
+ };
- {plugin === 'sonarqube' && (
- <SonarQubeDataScope connectionId={connectionId} selectedItems={selectedScope} onChangeItems={onChangeScope} />
- )}
+ if (connections.length === 1) {
+ const [{ plugin, connectionId, ...props }] = connections;
+ return (
+ <DataScopeForm
+ plugin={plugin}
+ connectionId={connectionId}
+ initialScope={initialScope}
+ initialEntities={initialEntities}
+ cancelBtnProps={cancelBtnProps}
+ submitBtnProps={submitBtnProps}
+ onCancel={onCancel}
+ onSubmit={(scope: MixConnection['scope'], origin: MixConnection['origin']) => {
+ onSubmit?.([
+ {
+ plugin,
+ connectionId,
+ ...props,
+ scope,
+ origin,
+ },
+ ]);
+ onNext?.();
+ }}
+ />
+ );
+ }
- {plugin === 'zentao' && (
- <ZentaoDataScope connectionId={connectionId} selectedItems={selectedScope} onChangeItems={onChangeScope} />
- )}
- </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(entities)}
- getKey={(item) => item.value}
- getName={(item) => item.label}
- selectedItems={selectedEntities}
- onChangeItems={onChangeEntites}
- />
- </div>
-
- <ButtonGroup>
- <Button outlined disabled={saving} text="Cancel" onClick={onCancel} />
- <Button
- outlined
- intent={Intent.PRIMARY}
- loading={saving}
- disabled={!selectedScope.length || !selectedEntities.length}
- text="Save"
- onClick={onSave}
- />
- </ButtonGroup>
+ 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={initialScope}
+ initialEntities={initialEntities}
+ 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
index 5a9d85fae..8ba790992 100644
--- a/config-ui/src/plugins/components/data-scope/styled.ts
+++ b/config-ui/src/plugins/components/data-scope/styled.ts
@@ -18,25 +18,46 @@
import styled from 'styled-components';
-export const Wrapper = styled.div`
- .block {
- margin-bottom: 16px;
- }
+export const Wrapper = styled.div``;
+
+export const ConnectionColumn = styled.div`
+ display: flex;
+ align-items: center;
- h3 {
- margin: 0 0 8px;
+ img {
+ margin-right: 4px;
+ width: 20px;
}
+`;
+
+export const ScopeColumn = styled.ul``;
+
+export const ScopeItem = styled.li`
+ margin-bottom: 4px;
- h4 {
- margin: 8px 0;
+ &:last-child {
+ margin-bottom: 0;
}
- p {
- margin: 0 0 8px;
+ .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;
- .bp4-button-group {
- display: flex;
- justify-content: flex-end;
+ img {
+ margin-right: 4px;
+ width: 24px;
+ height: 24px;
}
`;
diff --git a/config-ui/src/plugins/components/data-scope/use-data-scope.ts b/config-ui/src/plugins/components/data-scope/use-data-scope.ts
deleted file mode 100644
index 136f76dcf..000000000
--- a/config-ui/src/plugins/components/data-scope/use-data-scope.ts
+++ /dev/null
@@ -1,135 +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 { omit } from 'lodash';
-
-import { transformEntities } from '@/config';
-import { operator } from '@/utils';
-
-import * as API from './api';
-
-export interface UseDataScope {
- plugin: string;
- connectionId: ID;
- entities: string[];
- initialValues?: {
- scope?: any;
- entites?: string[];
- };
- onSave?: (scope: any) => void;
-}
-
-export const useDataScope = ({ plugin, connectionId, entities, initialValues, onSave }: UseDataScope) => {
- const [saving, setSaving] = useState(false);
- const [selectedScope, setSelectedScope] = useState<any>([]);
- const [selectedEntities, setSelectedEntities] = useState<any>([]);
-
- useEffect(() => {
- (async () => {
- const scope = await Promise.all(
- (initialValues?.scope ?? []).map((sc: any) => API.getDataScope(plugin, connectionId, sc.id)),
- );
- setSelectedScope(scope);
- })();
- }, [initialValues?.scope]);
-
- useEffect(() => {
- setSelectedEntities(transformEntities(initialValues?.entites ?? entities));
- }, [entities, initialValues?.entites]);
-
- const getPluginId = (scope: any) => {
- switch (true) {
- case plugin === 'github':
- return scope.githubId;
- case plugin === 'jira':
- return scope.boardId;
- case plugin === 'gitlab':
- return scope.gitlabId;
- case plugin === 'jenkins':
- return scope.jobFullName;
- case plugin === 'bitbucket':
- return scope.bitbucketId;
- case plugin === 'zentao':
- return scope.type === 'project' ? `project/${scope.id}` : `product/${scope.id}`;
- case plugin === 'sonarqube':
- return scope.projectKey;
- }
- };
-
- const getDataScope = async (scope: any) => {
- try {
- const res = await API.getDataScope(plugin, connectionId, getPluginId(scope));
- return {
- ...scope,
- transformationRuleId: res.transformationRuleId,
- };
- } catch {
- return scope;
- }
- };
-
- const handleSave = async () => {
- const scope = await Promise.all(selectedScope.map((sc: any) => getDataScope(sc)));
-
- let request: () => Promise<any>;
- if (plugin === 'zentao') {
- request = async () => {
- return [
- ...(await API.updateDataScopeWithType(plugin, connectionId, 'product', {
- data: scope.filter((s) => s.type !== 'project').map((sc: any) => omit(sc, 'from')),
- })),
- ...(await API.updateDataScopeWithType(plugin, connectionId, 'project', {
- data: scope.filter((s) => s.type === 'project').map((sc: any) => omit(sc, 'from')),
- })),
- ];
- };
- } else {
- request = () =>
- API.updateDataScope(plugin, connectionId, {
- data: scope.map((sc: any) => omit(sc, 'from')),
- });
- }
-
- const [success, res] = await operator(request, {
- setOperating: setSaving,
- hideToast: true,
- });
-
- if (success) {
- onSave?.(
- res.map((it: any) => ({
- id: `${getPluginId(it)}`,
- entities: selectedEntities.map((it: any) => it.value),
- })),
- );
- }
- };
-
- return useMemo(
- () => ({
- saving,
- selectedScope,
- selectedEntities,
- onChangeScope: setSelectedScope,
- onChangeEntites: setSelectedEntities,
- onSave: handleSave,
- }),
- [saving, selectedScope, selectedEntities],
- );
-};
diff --git a/config-ui/src/plugins/components/index.ts b/config-ui/src/plugins/components/index.ts
index b98f87654..307482460 100644
--- a/config-ui/src/plugins/components/index.ts
+++ b/config-ui/src/plugins/components/index.ts
@@ -17,8 +17,10 @@
*/
export * from './connection-form';
-export * from './data-scope-list';
export * from './data-scope';
+export * from './data-scope-form';
export * from './data-scope-miller-columns';
export * from './data-scope-search';
export * from './transformation';
+export * from './transformation-form';
+export * from './transformation-select';
diff --git a/config-ui/src/pages/transformation/detail/api.ts b/config-ui/src/plugins/components/transformation-form/api.ts
similarity index 69%
rename from config-ui/src/pages/transformation/detail/api.ts
rename to config-ui/src/plugins/components/transformation-form/api.ts
index ee2c2900a..1744a8c48 100644
--- a/config-ui/src/pages/transformation/detail/api.ts
+++ b/config-ui/src/plugins/components/transformation-form/api.ts
@@ -18,16 +18,19 @@
import { request } from '@/utils';
-export const createTransformation = (plugin: string, payload: any) =>
+export const getTransformation = (plugin: string, connectionId: ID, tid: ID) =>
+ request(`/plugins/${plugin}/transformation_rules/${tid}`, {
+ method: 'get',
+ });
+
+export const createTransformation = (plugin: string, connectionId: ID, paylod: any) =>
request(`/plugins/${plugin}/transformation_rules`, {
method: 'post',
- data: payload,
+ data: paylod,
});
-export const getTransformation = (plugin: string, id: ID) => request(`/plugins/${plugin}/transformation_rules/${id}`);
-
-export const updateTransformation = (plugin: string, id: ID, payload: any) =>
- request(`/plugins/${plugin}/transformation_rules/${id}`, {
+export const updateTransformation = (plugin: string, connectionId: ID, tid: ID, payload: any) =>
+ request(`/plugins/${plugin}/transformation_rules/${tid}`, {
method: 'patch',
data: payload,
});
diff --git a/config-ui/src/plugins/components/transformation-form/index.tsx b/config-ui/src/plugins/components/transformation-form/index.tsx
new file mode 100644
index 000000000..acb1b0027
--- /dev/null
+++ b/config-ui/src/plugins/components/transformation-form/index.tsx
@@ -0,0 +1,126 @@
+/*
+ * 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 { InputGroup, Button, Intent } from '@blueprintjs/core';
+
+import { PageLoading, ExternalLink, Card } from '@/components';
+import { useRefreshData, useOperator } from '@/hooks';
+import type { PluginConfigType } from '@/plugins';
+import { PluginConfig } from '@/plugins';
+import { GitHubTransformation } from '@/plugins/register/github';
+import { JiraTransformation } from '@/plugins/register/jira';
+import { GitLabTransformation } from '@/plugins/register/gitlab';
+import { JenkinsTransformation } from '@/plugins/register/jenkins';
+import { BitbucketTransformation } from '@/plugins/register/bitbucket';
+
+import { TIPS_MAP } from './misc';
+import * as API from './api';
+import * as S from './styled';
+
+interface Props {
+ plugin: string;
+ connectionId: ID;
+ id?: ID;
+ onCancel?: () => void;
+}
+
+export const TransformationForm = ({ plugin, connectionId, id, onCancel }: Props) => {
+ const [name, setName] = useState('');
+ const [transformation, setTransformation] = useState({});
+
+ const config = useMemo(() => PluginConfig.find((p) => p.plugin === plugin) as PluginConfigType, []);
+
+ const { ready, data } = useRefreshData(async () => {
+ if (!id) return null;
+ return API.getTransformation(plugin, connectionId, id);
+ }, [id]);
+
+ const { operating, onSubmit } = useOperator(
+ (payload) =>
+ id
+ ? API.updateTransformation(plugin, connectionId, id, payload)
+ : API.createTransformation(plugin, connectionId, payload),
+ {
+ callback: onCancel,
+ formatMessage: () => 'Transformation created successfully',
+ },
+ );
+
+ useEffect(() => {
+ setTransformation(data ?? config.transformation);
+ }, [data, config.transformation]);
+
+ if (!ready) {
+ return <PageLoading />;
+ }
+
+ return (
+ <S.Wrapper>
+ {TIPS_MAP[plugin] && (
+ <S.Tips>
+ To learn about how {TIPS_MAP[plugin].name} transformation is used in DevLake,{' '}
+ <ExternalLink link={TIPS_MAP[plugin].link}>check out this doc</ExternalLink>.
+ </S.Tips>
+ )}
+
+ <Card style={{ marginTop: 24 }}>
+ <h3>Transformation Name *</h3>
+ <p>Give this set of transformation rules a unique name so that you can identify it in the future.</p>
+ <InputGroup placeholder="Enter Transformation Name" value={name} onChange={(e) => setName(e.target.value)} />
+ </Card>
+
+ <Card style={{ marginTop: 24 }}>
+ {plugin === 'github' && (
+ <GitHubTransformation transformation={transformation} setTransformation={setTransformation} />
+ )}
+
+ {plugin === 'jira' && (
+ <JiraTransformation
+ connectionId={connectionId}
+ transformation={transformation}
+ setTransformation={setTransformation}
+ />
+ )}
+
+ {plugin === 'gitlab' && (
+ <GitLabTransformation transformation={transformation} setTransformation={setTransformation} />
+ )}
+
+ {plugin === 'jenkins' && (
+ <JenkinsTransformation transformation={transformation} setTransformation={setTransformation} />
+ )}
+
+ {plugin === 'bitbucket' && (
+ <BitbucketTransformation transformation={transformation} setTransformation={setTransformation} />
+ )}
+ </Card>
+
+ <S.Btns>
+ <Button outlined intent={Intent.PRIMARY} text="Cancel" onClick={onCancel} />
+ <Button
+ intent={Intent.PRIMARY}
+ disabled={!name}
+ loading={operating}
+ text="Save"
+ onClick={() => onSubmit({ ...transformation, name })}
+ />
+ </S.Btns>
+ </S.Wrapper>
+ );
+};
diff --git a/config-ui/src/plugins/components/transformation/misc.ts b/config-ui/src/plugins/components/transformation-form/misc.ts
similarity index 100%
rename from config-ui/src/plugins/components/transformation/misc.ts
rename to config-ui/src/plugins/components/transformation-form/misc.ts
diff --git a/config-ui/src/pages/blueprint/create/step-three/styled.ts b/config-ui/src/plugins/components/transformation-form/styled.ts
similarity index 75%
copy from config-ui/src/pages/blueprint/create/step-three/styled.ts
copy to config-ui/src/plugins/components/transformation-form/styled.ts
index 5b4d47a02..cc7bea2fe 100644
--- a/config-ui/src/pages/blueprint/create/step-three/styled.ts
+++ b/config-ui/src/plugins/components/transformation-form/styled.ts
@@ -18,12 +18,22 @@
import styled from 'styled-components';
-export const ConnectionColumn = styled.div`
+export const Wrapper = styled.div``;
+
+export const Tips = styled.div`
+ padding: 24px;
+ background: #f0f4fe;
+ border: 1px solid #bdcefb;
+ border-radius: 4px;
+`;
+
+export const Btns = styled.div`
display: flex;
align-items: center;
+ justify-content: end;
+ margin-top: 24px;
- img {
- margin-right: 4px;
- width: 20px;
+ .bp4-button + .bp4-button {
+ margin-left: 4px;
}
`;
diff --git a/config-ui/src/plugins/components/data-scope-list/api.ts b/config-ui/src/plugins/components/transformation-select/api.ts
similarity index 84%
rename from config-ui/src/plugins/components/data-scope-list/api.ts
rename to config-ui/src/plugins/components/transformation-select/api.ts
index 9035ede94..807617a46 100644
--- a/config-ui/src/plugins/components/data-scope-list/api.ts
+++ b/config-ui/src/plugins/components/transformation-select/api.ts
@@ -18,5 +18,5 @@
import { request } from '@/utils';
-export const getDataScopeRepo = (plugin: string, connectionId: ID, repoId: ID) =>
- request(`/plugins/${plugin}/connections/${connectionId}/scopes/${repoId}`);
+export const getTransformations = (plugin: string, connectionId: ID) =>
+ request(`/plugins/${plugin}/transformation_rules`);
diff --git a/config-ui/src/plugins/components/transformation-select/index.tsx b/config-ui/src/plugins/components/transformation-select/index.tsx
new file mode 100644
index 000000000..eaa8b8401
--- /dev/null
+++ b/config-ui/src/plugins/components/transformation-select/index.tsx
@@ -0,0 +1,117 @@
+/*
+ * 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 { Dialog, PageLoading, Table, IconButton } from '@/components';
+import { useRefreshData } from '@/hooks';
+import { TransformationForm } from '@/plugins';
+
+import * as API from './api';
+import * as S from './styled';
+
+interface Props {
+ plugin: string;
+ connectionId: ID;
+ onCancel: () => void;
+ onSubmit: (tid: ID) => void;
+}
+
+export const TransformationSelect = ({ plugin, connectionId, onCancel, onSubmit }: Props) => {
+ const [step, setStep] = useState(1);
+ const [type, setType] = useState<'add' | 'edit'>('add');
+ const [selectedId, setSelectedId] = useState<ID>();
+ const [updatedId, setUpdatedId] = useState<ID>();
+
+ const { ready, data } = useRefreshData(() => API.getTransformations(plugin, connectionId), [step]);
+
+ const title = useMemo(() => {
+ switch (true) {
+ case step === 1:
+ return 'Select Transformation';
+ case type === 'add':
+ return 'Add New Transformation';
+ case type === 'edit':
+ return 'Edit Transformation';
+ }
+ }, [step, type]);
+
+ const handleNewTransformation = () => {
+ setStep(2);
+ setType('add');
+ };
+
+ const handleEditTransformation = (id: ID) => {
+ setStep(2);
+ setType('edit');
+ setUpdatedId(id);
+ };
+
+ const handleReset = () => {
+ setStep(1);
+ setUpdatedId(undefined);
+ };
+
+ const handleSubmit = () => !!selectedId && onSubmit(selectedId);
+
+ return (
+ <Dialog isOpen title={title} footer={null} style={{ width: 820 }} onCancel={onCancel}>
+ {!ready || !data ? (
+ <PageLoading />
+ ) : step === 1 ? (
+ <S.Wrapper>
+ <S.Aciton>
+ <Button intent={Intent.PRIMARY} icon="add" onClick={handleNewTransformation}>
+ Add New Transformation
+ </Button>
+ </S.Aciton>
+ <Table
+ columns={[
+ { title: 'Transformation', dataIndex: 'name', key: 'name' },
+ {
+ title: '',
+ dataIndex: 'id',
+ key: 'id',
+ width: 100,
+ align: 'right',
+ render: (id) => (
+ <IconButton icon="annotation" tooltip="Edit" onClick={() => handleEditTransformation(id)} />
+ ),
+ },
+ ]}
+ dataSource={data}
+ rowSelection={{
+ rowKey: 'id',
+ type: 'radio',
+ selectedRowKeys: selectedId ? [selectedId] : [],
+ onChange: (selectedRowKeys) => setSelectedId(selectedRowKeys[0]),
+ }}
+ noShadow
+ />
+ <S.Btns>
+ <Button outlined intent={Intent.PRIMARY} text="Cancel" onClick={onCancel} />
+ <Button disabled={!selectedId} intent={Intent.PRIMARY} text="Save" onClick={handleSubmit} />
+ </S.Btns>
+ </S.Wrapper>
+ ) : (
+ <TransformationForm plugin={plugin} connectionId={connectionId} id={updatedId} onCancel={handleReset} />
+ )}
+ </Dialog>
+ );
+};
diff --git a/config-ui/src/pages/blueprint/create/step-two/styled.ts b/config-ui/src/plugins/components/transformation-select/styled.ts
similarity index 79%
rename from config-ui/src/pages/blueprint/create/step-two/styled.ts
rename to config-ui/src/plugins/components/transformation-select/styled.ts
index 5b4d47a02..c4c1bee23 100644
--- a/config-ui/src/pages/blueprint/create/step-two/styled.ts
+++ b/config-ui/src/plugins/components/transformation-select/styled.ts
@@ -18,12 +18,19 @@
import styled from 'styled-components';
-export const ConnectionColumn = styled.div`
+export const Wrapper = styled.div``;
+
+export const Aciton = styled.div`
+ margin-bottom: 16px;
+`;
+
+export const Btns = styled.div`
display: flex;
align-items: center;
+ justify-content: end;
+ margin-top: 24px;
- img {
- margin-right: 4px;
- width: 20px;
+ .bp4-button + .bp4-button {
+ margin-left: 4px;
}
`;
diff --git a/config-ui/src/plugins/components/transformation/api.ts b/config-ui/src/plugins/components/transformation/api.ts
index 907e943f0..ef0f79387 100644
--- a/config-ui/src/plugins/components/transformation/api.ts
+++ b/config-ui/src/plugins/components/transformation/api.ts
@@ -18,28 +18,11 @@
import { request } from '@/utils';
-type GetRulesParams = {
- page: number;
- pageSize: number;
-};
+export const getDataScope = (plugin: string, connectionId: ID, scopeId: ID) =>
+ request(`/plugins/${plugin}/connections/${connectionId}/scopes/${scopeId}`);
-export const getRules = (plugin: string, params?: GetRulesParams) =>
- request(`/plugins/${plugin}/transformation_rules`, {
- method: 'get',
- data: params,
- });
-
-export const getDataScopeRepo = (plugin: string, connectionId: ID, repoId: ID) =>
- request(`/plugins/${plugin}/connections/${connectionId}/scopes/${repoId}`);
-
-export const updateDataScope = (plugin: string, connectionId: ID, payload: any) =>
- request(`/plugins/${plugin}/connections/${connectionId}/scopes`, {
- method: 'put',
+export const updateDataScope = (plugin: string, connectionId: ID, scopeId: ID, payload: any) =>
+ request(`/plugins/${plugin}/connections/${connectionId}/scopes/${scopeId}`, {
+ method: 'patch',
data: payload,
});
-
-export const createTransformation = (plugin: string, paylod: any) =>
- request(`/plugins/${plugin}/transformation_rules`, {
- method: 'POST',
- data: paylod,
- });
diff --git a/config-ui/src/plugins/components/transformation/index.tsx b/config-ui/src/plugins/components/transformation/index.tsx
index f9a988a16..73c97be03 100644
--- a/config-ui/src/plugins/components/transformation/index.tsx
+++ b/config-ui/src/plugins/components/transformation/index.tsx
@@ -16,167 +16,148 @@
*
*/
-import React, { useState } from 'react';
-import { RadioGroup, Radio, InputGroup, ButtonGroup, Button, Intent } from '@blueprintjs/core';
+import { useState } from 'react';
+import { Button, Intent } from '@blueprintjs/core';
-import { ExternalLink, Divider, Selector, MultiSelector } from '@/components';
+import { Table, IconButton } from '@/components';
+import { TransformationSelect, getPluginId } from '@/plugins';
-import { GitHubTransformation } from '@/plugins/register/github';
-import { JiraTransformation } from '@/plugins/register/jira';
-import { GitLabTransformation } from '@/plugins/register/gitlab';
-import { JenkinsTransformation } from '@/plugins/register/jenkins';
-import { BitbucketTransformation } from '@/plugins/register/bitbucket';
-
-import type { TransformationType, RuleItem } from './types';
-import { TIPS_MAP } from './misc';
-import type { UseTransformationProps } from './use-transformation';
-import { useTransformation } from './use-transformation';
+import * as API from './api';
import * as S from './styled';
-interface Props extends Omit<UseTransformationProps, 'name' | 'selectedRule' | 'setSelectedScope'> {
- from: 'create' | 'update';
+interface Props {
+ connections: MixConnection[];
+ cancelBtnProps?: {
+ text?: string;
+ };
+ submitBtnProps?: {
+ text?: string;
+ loading?: boolean;
+ };
+ noFooter?: boolean;
onCancel?: () => void;
+ onSubmit?: (connections: MixConnection[]) => void;
+ onNext?: () => void;
}
-export const Transformation = ({ from, plugin, connectionId, onCancel, ...props }: Props) => {
- const [type, setType] = useState<TransformationType>();
- const [name, setName] = useState('');
- const [selectedRule, setSelectedRule] = useState<RuleItem>();
- const [selectedScope, setSelectedScope] = useState<any>([]);
-
- const { loading, rules, scope, saving, transformation, getScopeKey, onSave, onUpdateScope, onChangeTransformation } =
- useTransformation({
- ...props,
- plugin,
- connectionId,
- name,
- selectedRule,
- selectedScope,
+export const Transformation = ({
+ connections,
+ cancelBtnProps,
+ submitBtnProps,
+ noFooter,
+ onCancel,
+ onSubmit,
+ onNext,
+}: Props) => {
+ const [selected, setSelected] = useState<Record<string, ID[]>>({});
+ const [connection, setConnection] = useState<MixConnection>();
+
+ const handleCancel = () => setConnection(undefined);
+
+ const handleSubmit = async (tid: ID, connection: MixConnection, connections: MixConnection[]) => {
+ const { unique, plugin, connectionId } = connection;
+ const scopeIds = selected[unique];
+ const scopes = await Promise.all(
+ scopeIds.map(async (scopeId) => {
+ const scope = await API.getDataScope(plugin, connectionId, scopeId);
+ return await API.updateDataScope(plugin, connectionId, scopeId, {
+ ...scope,
+ transformationRuleId: tid,
+ });
+ }),
+ );
+ onSubmit?.(
+ connections.map((cs) => {
+ if (cs.unique !== unique) {
+ return cs;
+ }
+
+ const origin = cs.origin.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 };
+ }),
+ );
+ setSelected({
+ ...selected,
+ [`${unique}`]: [],
});
-
- const handleChangeType = (e: React.FormEvent<HTMLInputElement>) => {
- setType((e.target as HTMLInputElement).value as TransformationType);
- setSelectedRule(undefined);
+ handleCancel();
};
return (
- <S.Wrapper>
- {TIPS_MAP[plugin] && (
- <div className="tips">
- To learn about how {TIPS_MAP[plugin].name} transformation is used in DevLake,{' '}
- <ExternalLink link={TIPS_MAP[plugin].link}>check out this doc</ExternalLink>.
- </div>
- )}
-
- <div className="block">
- <RadioGroup selectedValue={type} onChange={handleChangeType}>
- <Radio label="Create a new transformation" value="create" />
- <Radio
- label={
- from === 'create'
- ? 'Create a new transformation by duplicating an exisitng transformation'
- : 'Duplicating an existing transformation'
- }
- value="createByExist"
+ <S.List>
+ {connections.map((cs) => (
+ <S.Item key={cs.unique}>
+ {connections.length !== 1 && (
+ <S.Title>
+ <img src={cs.icon} alt="" />
+ <span>{cs.name}</span>
+ </S.Title>
+ )}
+ <S.Action>
+ <Button
+ intent={Intent.PRIMARY}
+ icon="annotation"
+ disabled={!selected[cs.unique] || !selected[cs.unique].length}
+ onClick={() => setConnection(cs)}
+ >
+ Select Transformation
+ </Button>
+ </S.Action>
+ <Table
+ columns={[
+ { title: 'Data Scope', dataIndex: 'name', key: 'name' },
+ {
+ title: 'Transformation',
+ dataIndex: 'transformationRuleName',
+ key: 'transformation',
+ align: 'center',
+ render: (val, row) => (
+ <div>
+ <span>{val ?? 'N/A'}</span>
+ <IconButton
+ icon="annotation"
+ tooltip="Select Transformation"
+ onClick={() => {
+ setSelected({
+ ...selected,
+ [`${cs.unique}`]: [row[getPluginId(cs.plugin)]],
+ });
+ setConnection(cs);
+ }}
+ />
+ </div>
+ ),
+ },
+ ]}
+ dataSource={cs.origin}
+ rowSelection={{
+ rowKey: getPluginId(cs.plugin),
+ selectedRowKeys: selected[cs.unique],
+ onChange: (selectedRowKeys) => setSelected({ ...selected, [`${cs.unique}`]: selectedRowKeys }),
+ }}
/>
- <Radio label="Select an existing transformation" value="selectExist" />
- </RadioGroup>
- </div>
-
- {type && (
- <>
- <Divider />
- <div className="block">
- {(type === 'createByExist' || type === 'selectExist') && (
- <div className="item">
- <h3>Select a Transformation to Duplicate *</h3>
- <p>
- The selected transformation will be used as template that you can tweak and save as a new
- transformation.
- </p>
- <Selector
- items={rules}
- getKey={(it) => it.id}
- getName={(it) => it.name}
- selectedItem={selectedRule}
- onChangeItem={setSelectedRule}
- />
- </div>
- )}
- {type !== 'selectExist' && (
- <div className="item">
- <h3>Transformation Name *</h3>
- <p>Give this set of transformation rules a unique name so that you can identify it in the future.</p>
- <InputGroup
- placeholder="Enter Transformation Name"
- value={name}
- onChange={(e) => setName(e.target.value)}
- />
- </div>
- )}
- <div className="item">
- <h3>Applied Data Scope</h3>
- <p>Select the data scope for which you want to apply this transformation for.</p>
- <MultiSelector
- loading={loading}
- items={scope}
- getKey={getScopeKey}
- getName={(sc) => sc.name}
- selectedItems={selectedScope}
- onChangeItems={setSelectedScope}
- />
- {type === 'selectExist' && (
- <ButtonGroup>
- <Button outlined intent={Intent.PRIMARY} text="Cancel and Go Back" onClick={onCancel} />
- <Button
- outlined
- disabled={!selectedRule || !selectedScope.length}
- intent={Intent.PRIMARY}
- text="Save"
- onClick={() => onUpdateScope(selectedRule?.id)}
- />
- </ButtonGroup>
- )}
- </div>
- </div>
- </>
+ </S.Item>
+ ))}
+ {!noFooter && (
+ <S.Btns>
+ <Button outlined intent={Intent.PRIMARY} text="Previous Step" onClick={onCancel} {...cancelBtnProps} />
+ <Button intent={Intent.PRIMARY} text="Next Step" onClick={onNext} {...submitBtnProps} />
+ </S.Btns>
)}
-
- {(type === 'create' || (type === 'createByExist' && selectedRule)) && (
- <>
- <Divider />
- <div className="block">
- {plugin === 'github' && (
- <GitHubTransformation transformation={transformation} setTransformation={onChangeTransformation} />
- )}
-
- {plugin === 'jira' && (
- <JiraTransformation
- connectionId={connectionId}
- transformation={transformation}
- setTransformation={onChangeTransformation}
- />
- )}
-
- {plugin === 'gitlab' && (
- <GitLabTransformation transformation={transformation} setTransformation={onChangeTransformation} />
- )}
-
- {plugin === 'jenkins' && (
- <JenkinsTransformation transformation={transformation} setTransformation={onChangeTransformation} />
- )}
-
- {plugin === 'bitbucket' && (
- <BitbucketTransformation transformation={transformation} setTransformation={onChangeTransformation} />
- )}
-
- <ButtonGroup>
- <Button outlined intent={Intent.PRIMARY} text="Cancel and Go Back" onClick={onCancel} />
- <Button outlined disabled={!name} loading={saving} intent={Intent.PRIMARY} text="Save" onClick={onSave} />
- </ButtonGroup>
- </div>
- </>
+ {connection && (
+ <TransformationSelect
+ plugin={connection.plugin}
+ connectionId={connection.connectionId}
+ onCancel={handleCancel}
+ onSubmit={(tid) => handleSubmit(tid, connection, connections)}
+ />
)}
- </S.Wrapper>
+ </S.List>
);
};
diff --git a/config-ui/src/plugins/components/transformation/styled.ts b/config-ui/src/plugins/components/transformation/styled.ts
index dc39bf265..7062ec37e 100644
--- a/config-ui/src/plugins/components/transformation/styled.ts
+++ b/config-ui/src/plugins/components/transformation/styled.ts
@@ -18,39 +18,35 @@
import styled from 'styled-components';
-export const Wrapper = styled.div`
- .tips {
- margin-bottom: 16px;
- padding: 12px 24px;
- background: #f0f4fe;
- border: 1px solid #bdcefb;
- border-radius: 4px;
- }
+export const List = styled.div``;
- .block {
- padding: 8px 16px;
- }
+export const Item = styled.div`
+ margin-bottom: 36px;
- .item + .item {
- margin-top: 16px;
+ &:last-child {
+ margin-bottom: 0;
}
+`;
- .bp4-form-group {
- display: flex;
- align-items: center;
-
- .bp4-label {
- flex: 0 0 150px;
- }
+export const Title = styled.div`
+ display: flex;
+ align-items: center;
+ margin-bottom: 16px;
- .bp4-form-content {
- flex: auto;
- }
+ img {
+ margin-right: 4px;
+ width: 24px;
+ height: 24px;
}
+`;
- .bp4-button-group {
- display: flex;
- justify-content: flex-end;
- margin-top: 16px;
- }
+export const Action = styled.div`
+ margin-bottom: 16px;
+`;
+
+export const Btns = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-top: 36px;
`;
diff --git a/config-ui/src/plugins/components/transformation/use-transformation.ts b/config-ui/src/plugins/components/transformation/use-transformation.ts
deleted file mode 100644
index 9c694536e..000000000
--- a/config-ui/src/plugins/components/transformation/use-transformation.ts
+++ /dev/null
@@ -1,158 +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 type { PluginConfigType } from '@/plugins';
-import { PluginConfig } from '@/plugins';
-import { operator } from '@/utils';
-
-import type { RuleItem, ScopeItem } from './types';
-import * as API from './api';
-
-export interface UseTransformationProps {
- plugin: string;
- connectionId: ID;
- scopeIds: ID[];
- name: string;
- selectedRule?: RuleItem;
- selectedScope?: ScopeItem[];
- onSave?: () => void;
-}
-
-export const useTransformation = ({
- plugin,
- connectionId,
- scopeIds,
- name,
- selectedRule,
- selectedScope,
- onSave,
-}: UseTransformationProps) => {
- const [loading, setLoading] = useState(false);
- const [rules, setRules] = useState<RuleItem[]>([]);
- const [scope, setScope] = useState<ScopeItem[]>([]);
- const [saving, setSaving] = useState(false);
- const [transformation, setTransformation] = useState({});
-
- const config = useMemo(() => PluginConfig.find((p) => p.plugin === plugin) as PluginConfigType, []);
-
- useEffect(() => {
- setTransformation(selectedRule ? selectedRule : config.transformation);
- }, [selectedRule]);
-
- const getRules = async () => {
- const res = await API.getRules(plugin);
- setRules(res);
- };
-
- const getScope = async () => {
- const res = await Promise.all(scopeIds.map((id) => API.getDataScopeRepo(plugin, connectionId, id)));
- setScope(res);
- };
-
- const init = async () => {
- setLoading(true);
- try {
- await getRules();
- await getScope();
- } finally {
- setLoading(false);
- }
- };
-
- useEffect(() => {
- init();
- }, []);
-
- const handleUpdateScope = async (tid?: ID) => {
- if (!tid) return;
-
- const payload = (selectedScope ?? []).map((sc: any) => ({
- ...sc,
- transformationRuleId: tid,
- }));
-
- const [success] = await operator(() =>
- API.updateDataScope(plugin, connectionId, {
- data: payload,
- }),
- );
-
- if (success) {
- onSave?.();
- }
- };
-
- const handleSave = async () => {
- const [success, res] = await operator(
- () =>
- API.createTransformation(plugin, {
- ...transformation,
- name,
- }),
- {
- setOperating: setSaving,
- formatReason: (err) => (err as any).response?.data?.message,
- },
- );
-
- if (success) {
- const payload = (selectedScope ?? []).map((sc: any) => ({
- ...sc,
- transformationRuleId: res.id,
- }));
-
- if (payload.length) {
- await API.updateDataScope(plugin, connectionId, {
- data: payload,
- });
- }
-
- onSave?.();
- }
- };
-
- return useMemo(
- () => ({
- loading,
- rules,
- scope,
- saving,
- transformation,
- getScopeKey(sc: any) {
- switch (true) {
- case plugin === 'github':
- return sc.githubId;
- case plugin === 'jira':
- return sc.boardId;
- case plugin === 'gitlab':
- return sc.gitlabId;
- case plugin === 'jenkins':
- return sc.jobFullName;
- case plugin === 'bitbucket':
- return sc.bitbucketId;
- }
- },
- onSave: handleSave,
- onUpdateScope: handleUpdateScope,
- onChangeTransformation: setTransformation,
- }),
- [loading, rules, scope, saving, transformation, plugin, selectedScope, name, onSave],
- );
-};
diff --git a/config-ui/src/plugins/index.ts b/config-ui/src/plugins/index.ts
index 1dfd94a8b..1bf045890 100644
--- a/config-ui/src/plugins/index.ts
+++ b/config-ui/src/plugins/index.ts
@@ -17,5 +17,6 @@
*/
export * from './types';
-export * from './config';
+export * from './utils';
export * from './components';
+export * from './config';
diff --git a/config-ui/src/plugins/register/github/data-scope.tsx b/config-ui/src/plugins/register/github/data-scope.tsx
index bebc03c96..d63c71bad 100644
--- a/config-ui/src/plugins/register/github/data-scope.tsx
+++ b/config-ui/src/plugins/register/github/data-scope.tsx
@@ -16,11 +16,9 @@
*
*/
-import React from 'react';
-
import type { ScopeItemType } from './types';
-
import { MillerColumns, RepoSelector } from './components';
+import * as S from './styled';
interface Props {
connectionId: ID;
@@ -30,13 +28,13 @@ interface Props {
export const GitHubDataScope = ({ connectionId, selectedItems, onChangeItems }: Props) => {
return (
- <>
+ <S.DataScope>
<h3>Repositories *</h3>
<p>Select the repositories you would like to sync.</p>
<MillerColumns connectionId={connectionId} selectedItems={selectedItems} onChangeItems={onChangeItems} />
<h4>Add repositories outside of your organizations</h4>
<p>Search for repositories and add to them</p>
<RepoSelector connectionId={connectionId} selectedItems={selectedItems} onChangeItems={onChangeItems} />
- </>
+ </S.DataScope>
);
};
diff --git a/config-ui/src/plugins/register/github/styled.ts b/config-ui/src/plugins/register/github/styled.ts
index e8358d9ea..ae2b38e37 100644
--- a/config-ui/src/plugins/register/github/styled.ts
+++ b/config-ui/src/plugins/register/github/styled.ts
@@ -18,6 +18,12 @@
import styled from 'styled-components';
+export const DataScope = styled.div`
+ h4 {
+ margin-top: 16px;
+ }
+`;
+
export const TransformationWrapper = styled.div`
.issue-tracking {
.issue-type {
diff --git a/config-ui/src/plugins/register/github/transformation.tsx b/config-ui/src/plugins/register/github/transformation.tsx
index 0c24b6897..65d104b9f 100644
--- a/config-ui/src/plugins/register/github/transformation.tsx
+++ b/config-ui/src/plugins/register/github/transformation.tsx
@@ -98,7 +98,7 @@ export const GitHubTransformation = ({ transformation, setTransformation }: Prop
<FormGroup inline label="Feature">
<InputGroup
placeholder="(feat|feature|proposal|requirement)"
- value={transformation.issueTypeRequirement}
+ value={transformation.issueTypeRequirement ?? ''}
onChange={(e) =>
setTransformation({
...transformation,
@@ -110,7 +110,7 @@ export const GitHubTransformation = ({ transformation, setTransformation }: Prop
<FormGroup inline label="Bug">
<InputGroup
placeholder="(bug|broken)"
- value={transformation.issueTypeBug}
+ value={transformation.issueTypeBug ?? ''}
onChange={(e) =>
setTransformation({
...transformation,
@@ -132,7 +132,7 @@ export const GitHubTransformation = ({ transformation, setTransformation }: Prop
>
<InputGroup
placeholder="(incident|failure)"
- value={transformation.issueTypeIncident}
+ value={transformation.issueTypeIncident ?? ''}
onChange={(e) =>
setTransformation({
...transformation,
@@ -154,7 +154,7 @@ export const GitHubTransformation = ({ transformation, setTransformation }: Prop
>
<InputGroup
placeholder="(highest|high|medium|low|p0|p1|p2|p3)"
- value={transformation.issuePriority}
+ value={transformation.issuePriority ?? ''}
onChange={(e) =>
setTransformation({
...transformation,
@@ -174,7 +174,7 @@ export const GitHubTransformation = ({ transformation, setTransformation }: Prop
>
<InputGroup
placeholder="component(.*)"
- value={transformation.issueComponent}
+ value={transformation.issueComponent ?? ''}
onChange={(e) =>
setTransformation({
...transformation,
@@ -194,7 +194,7 @@ export const GitHubTransformation = ({ transformation, setTransformation }: Prop
>
<InputGroup
placeholder="severity(.*)"
- value={transformation.issueSeverity}
+ value={transformation.issueSeverity ?? ''}
onChange={(e) =>
setTransformation({
...transformation,
@@ -233,7 +233,7 @@ export const GitHubTransformation = ({ transformation, setTransformation }: Prop
<p>The Job name that matches</p>
<InputGroup
placeholder="(deploy|push-image)"
- value={transformation.deploymentPattern}
+ value={transformation.deploymentPattern ?? ''}
onChange={(e) =>
setTransformation({
...transformation,
@@ -250,7 +250,7 @@ export const GitHubTransformation = ({ transformation, setTransformation }: Prop
<InputGroup
disabled={!transformation.deploymentPattern}
placeholder="production"
- value={transformation.productionPattern}
+ value={transformation.productionPattern ?? ''}
onChange={(e) =>
setTransformation({
...transformation,
@@ -290,7 +290,7 @@ export const GitHubTransformation = ({ transformation, setTransformation }: Prop
>
<InputGroup
placeholder="type(.*)$"
- value={transformation.prType}
+ value={transformation.prType ?? ''}
onChange={(e) => setTransformation({ ...transformation, prType: e.target.value })}
/>
</FormGroup>
@@ -305,7 +305,7 @@ export const GitHubTransformation = ({ transformation, setTransformation }: Prop
>
<InputGroup
placeholder="component(.*)$"
- value={transformation.prComponent}
+ value={transformation.prComponent ?? ''}
onChange={(e) =>
setTransformation({
...transformation,
@@ -351,7 +351,7 @@ export const GitHubTransformation = ({ transformation, setTransformation }: Prop
}
>
<TextArea
- value={transformation.prBodyClosePattern}
+ value={transformation.prBodyClosePattern ?? ''}
placeholder="(?mi)(fix|close|resolve|fixes|closes|resolves|fixed|closed|resolved)[s]*.*(((and )?(#|https://github.com/%s/%s/issues/)d+[ ]*)+)"
onChange={(e) =>
setTransformation({
@@ -384,7 +384,7 @@ export const GitHubTransformation = ({ transformation, setTransformation }: Prop
Compare the last
<InputGroup
style={{ width: 60 }}
- value={transformation.refdiff?.tagsOrder}
+ value={transformation.refdiff?.tagsOrder ?? ''}
onChange={(e) =>
setTransformation({
...transformation,
@@ -399,7 +399,7 @@ export const GitHubTransformation = ({ transformation, setTransformation }: Prop
<InputGroup
style={{ width: 200 }}
placeholder="(regex)$"
- value={transformation.refdiff?.tagsPattern}
+ value={transformation.refdiff?.tagsPattern ?? ''}
onChange={(e) =>
setTransformation({
...transformation,
diff --git a/config-ui/src/plugins/register/gitlab/data-scope.tsx b/config-ui/src/plugins/register/gitlab/data-scope.tsx
index c7f46e11e..f3a8b63e5 100644
--- a/config-ui/src/plugins/register/gitlab/data-scope.tsx
+++ b/config-ui/src/plugins/register/gitlab/data-scope.tsx
@@ -16,11 +16,12 @@
*
*/
-import React, { useMemo } from 'react';
+import { useMemo } from 'react';
import { DataScopeMillerColumns, DataScopeSearch } from '@/plugins';
import type { ScopeItemType } from './types';
+import * as S from './styled';
interface Props {
connectionId: ID;
@@ -35,8 +36,8 @@ export const GitLabDataScope = ({ connectionId, onChangeItems, ...props }: Props
);
return (
- <>
- <h4>Projects *</h4>
+ <S.DataScope>
+ <h3>Projects *</h3>
<p>Select the project you would like to sync.</p>
<DataScopeMillerColumns
title="Subgroups/Projects"
@@ -53,6 +54,6 @@ export const GitLabDataScope = ({ connectionId, onChangeItems, ...props }: Props
selectedItems={selectedItems}
onChangeItems={onChangeItems}
/>
- </>
+ </S.DataScope>
);
};
diff --git a/config-ui/src/plugins/register/gitlab/styled.ts b/config-ui/src/plugins/register/gitlab/styled.ts
index 8c64ba989..78de5577d 100644
--- a/config-ui/src/plugins/register/gitlab/styled.ts
+++ b/config-ui/src/plugins/register/gitlab/styled.ts
@@ -18,6 +18,12 @@
import styled from 'styled-components';
+export const DataScope = styled.div`
+ h4 {
+ margin-top: 16px;
+ }
+`;
+
export const TransformationWrapper = styled.div`
h3 {
margin-top: 16px;
diff --git a/config-ui/src/pages/blueprint/create/step-three/styled.ts b/config-ui/src/plugins/utils.ts
similarity index 68%
rename from config-ui/src/pages/blueprint/create/step-three/styled.ts
rename to config-ui/src/plugins/utils.ts
index 5b4d47a02..ac93c7e02 100644
--- a/config-ui/src/pages/blueprint/create/step-three/styled.ts
+++ b/config-ui/src/plugins/utils.ts
@@ -16,14 +16,21 @@
*
*/
-import styled from 'styled-components';
-
-export const ConnectionColumn = styled.div`
- display: flex;
- align-items: center;
-
- img {
- margin-right: 4px;
- width: 20px;
+export const getPluginId = (plugin: string) => {
+ switch (plugin) {
+ case 'github':
+ return 'githubId';
+ case 'jira':
+ return 'boardId';
+ case 'gitlab':
+ return 'gitlabId';
+ case 'jenkins':
+ return 'jobFullName';
+ case 'bitbucket':
+ return 'bitbucketId';
+ case 'sonarqube':
+ return 'projectKey';
+ default:
+ return 'id';
}
-`;
+};
diff --git a/config-ui/src/store/connections/use-context-value.ts b/config-ui/src/store/connections/use-context-value.ts
index 804c9937f..036790d54 100644
--- a/config-ui/src/store/connections/use-context-value.ts
+++ b/config-ui/src/store/connections/use-context-value.ts
@@ -27,9 +27,10 @@ import * as API from './api';
export interface UseContextValueProps {
plugin?: string;
filterBeta?: boolean;
+ filter?: string[];
}
-export const useContextValue = ({ plugin, filterBeta = false }: UseContextValueProps) => {
+export const useContextValue = ({ plugin, filterBeta = false, filter }: UseContextValueProps) => {
const [loading, setLoading] = useState(false);
const [connections, setConnections] = useState<ConnectionItemType[]>([]);
@@ -140,7 +141,7 @@ export const useContextValue = ({ plugin, filterBeta = false }: UseContextValueP
return useMemo(
() => ({
loading,
- connections,
+ connections: filter ? connections.filter((cs) => !filter.includes(cs.unique)) : connections,
onRefresh: handleRefresh,
onTest: handleTest,
}),
diff --git a/config-ui/src/store/index.ts b/config-ui/src/store/index.ts
index e3b1a81c6..4fbdb691e 100644
--- a/config-ui/src/store/index.ts
+++ b/config-ui/src/store/index.ts
@@ -18,4 +18,3 @@
export * from './version';
export * from './connections';
-export * from './transformations';
diff --git a/config-ui/src/store/transformations/context.tsx b/config-ui/src/store/transformations/context.tsx
deleted file mode 100644
index efbfb735d..000000000
--- a/config-ui/src/store/transformations/context.tsx
+++ /dev/null
@@ -1,60 +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, { useContext } from 'react';
-
-import { PageLoading } from '@/components';
-import type { PluginConfigType } from '@/plugins';
-
-import type { TransformationItemType } from './types';
-import { useContextValue } from './use-context-value';
-
-const TransformationContext = React.createContext<{
- plugins: PluginConfigType[];
- transformations: TransformationItemType[];
-}>({
- plugins: [],
- transformations: [],
-});
-
-interface Props {
- children?: React.ReactNode;
-}
-
-export const TransformationContextProvider = ({ children }: Props) => {
- const { loading, plugins, transformations } = useContextValue();
-
- if (loading) {
- return <PageLoading />;
- }
-
- return (
- <TransformationContext.Provider
- value={{
- plugins,
- transformations,
- }}
- >
- {children}
- </TransformationContext.Provider>
- );
-};
-
-export const TransformationContextConsumer = TransformationContext.Consumer;
-
-export const useTransformation = () => useContext(TransformationContext);
diff --git a/config-ui/src/store/transformations/use-context-value.ts b/config-ui/src/store/transformations/use-context-value.ts
deleted file mode 100644
index 6cf03ff84..000000000
--- a/config-ui/src/store/transformations/use-context-value.ts
+++ /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 { useState, useEffect, useCallback, useMemo } from 'react';
-
-import { PluginConfig, PluginType } from '@/plugins';
-
-import type { TransformationItemType } from './types';
-import * as API from './api';
-
-export const useContextValue = () => {
- const [loading, setLoading] = useState(false);
- const [transformations, setTransformations] = useState<TransformationItemType[]>([]);
-
- const allConnections = useMemo(
- () => PluginConfig.filter((p) => p.type === PluginType.Connection && !p.isBeta && p.transformation),
- [],
- );
-
- const getTransformation = async (plugin: string) => {
- try {
- return await API.getTransformation(plugin, { page: 1, pageSize: 200 });
- } catch {
- return [];
- }
- };
-
- const handleRefresh = useCallback(async () => {
- setLoading(true);
-
- const res = await Promise.all(allConnections.map((cs) => getTransformation(cs.plugin)));
-
- const resWithPlugin = res.map((ts, i) =>
- ts.map((it: any) => {
- const { plugin } = allConnections[i];
-
- return {
- ...it,
- plugin,
- };
- }),
- );
-
- setTransformations(resWithPlugin.flat());
- setLoading(false);
- }, [allConnections]);
-
- useEffect(() => {
- handleRefresh();
- }, []);
-
- return useMemo(
- () => ({
- loading,
- plugins: allConnections,
- transformations,
- }),
- [loading, allConnections, transformations],
- );
-};
diff --git a/config-ui/tsconfig.json b/config-ui/tsconfig.json
index 3a78cfd06..c0c4a9dae 100644
--- a/config-ui/tsconfig.json
+++ b/config-ui/tsconfig.json
@@ -18,7 +18,7 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
- "jsx": "react"
+ "jsx": "react-jsx"
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]