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 44c9247a5 feat(config-ui): improve the content for the new design
(#5341)
44c9247a5 is described below
commit 44c9247a520d538c15e16066c942b87c912e26a0
Author: 青湛 <[email protected]>
AuthorDate: Fri Jun 2 09:27:05 2023 +0800
feat(config-ui): improve the content for the new design (#5341)
* feat(config-ui): default select all plugin entities
* feat(config-ui): link data entities and transformation
* feat(config-ui): add new component message
* feat(config-ui): add warning message for data scopo form
* feat(config-ui): disabled button when no scope selected
* feat(config-ui): support params disabled in icon-button
* feat(config-ui): distinguish between project and blueprint details
* feat(config-ui): improve the connection detail
* feat(config-ui): adjust the connection detail page
* feat(config-ui): improve the content for connection
* refactor(config-ui): adjust the wording and style
---
config-ui/src/App.tsx | 1 +
.../src/components/action/icon-button/index.tsx | 6 +-
config-ui/src/components/index.ts | 1 +
.../styled.ts => components/message/index.tsx} | 35 +-
config-ui/src/config/cron.ts | 4 +-
.../src/pages/blueprint/connection-detail/api.ts | 2 +
.../pages/blueprint/connection-detail/index.tsx | 43 +-
.../blueprint/detail/blueprint-detail-page.tsx | 7 +-
.../pages/blueprint/detail/blueprint-detail.tsx | 103 +---
.../pages/blueprint/detail/configuration-panel.tsx | 58 ++-
.../src/pages/blueprint/detail/status-panel.tsx | 149 +++++-
config-ui/src/pages/blueprint/detail/styled.ts | 28 +-
config-ui/src/pages/blueprint/types.ts | 6 +
config-ui/src/pages/connection/detail/index.tsx | 35 +-
config-ui/src/pages/connection/detail/styled.ts | 7 +
config-ui/src/pages/connection/home/index.tsx | 22 +-
config-ui/src/pages/project/detail/api.ts | 5 +-
config-ui/src/pages/project/detail/index.tsx | 8 +-
.../plugins/components/connection-form/index.tsx | 6 +-
.../plugins/components/connection-list/index.tsx | 1 +
.../plugins/components/data-scope-select/index.tsx | 37 +-
.../plugins/components/scope-config-form/index.tsx | 76 ++-
.../components/scope-config-select/index.tsx | 10 +-
.../src/plugins/register/azure/transformation.tsx | 123 ++---
.../plugins/register/bitbucket/transformation.tsx | 257 +++++-----
.../src/plugins/register/github/transformation.tsx | 556 +++++++++++----------
.../src/plugins/register/gitlab/transformation.tsx | 126 ++---
.../plugins/register/jenkins/transformation.tsx | 123 ++---
.../jira/transformation-fields/cross-domain.tsx | 2 +-
.../src/plugins/register/jira/transformation.tsx | 253 +++++-----
.../src/plugins/register/tapd/transformation.tsx | 324 ++++++------
31 files changed, 1318 insertions(+), 1096 deletions(-)
diff --git a/config-ui/src/App.tsx b/config-ui/src/App.tsx
index cc4b18aeb..1e723a1e6 100644
--- a/config-ui/src/App.tsx
+++ b/config-ui/src/App.tsx
@@ -68,6 +68,7 @@ function App() {
<Route exact path="/connections/:plugin/:id" component={() =>
<ConnectionDetailPage />} />
<Route exact path="/projects" component={() =>
<ProjectHomePage />} />
<Route exact path="/projects/:pname" component={() =>
<ProjectDetailPage />} />
+ <Route exact path="/projects/:pname/:unique" component={() =>
<BlueprintConnectionDetailPage />} />
<Route exact path="/blueprints" component={() =>
<BlueprintHomePage />} />
<Route exact path="/blueprints/:id" component={() =>
<BlueprintDetailPage />} />
<Route exact path="/blueprints/:bid/:unique" component={() =>
<BlueprintConnectionDetailPage />} />
diff --git a/config-ui/src/components/action/icon-button/index.tsx
b/config-ui/src/components/action/icon-button/index.tsx
index 7a40dc80d..45441018a 100644
--- a/config-ui/src/components/action/icon-button/index.tsx
+++ b/config-ui/src/components/action/icon-button/index.tsx
@@ -16,7 +16,6 @@
*
*/
-import React from 'react';
import { Button, Intent, Position, IconName } from '@blueprintjs/core';
import { Tooltip2 } from '@blueprintjs/popover2';
@@ -24,13 +23,14 @@ interface Props {
icon: IconName;
tooltip: string;
loading?: boolean;
+ disabled?: boolean;
onClick?: () => void;
}
-export const IconButton = ({ icon, tooltip, loading, onClick }: Props) => {
+export const IconButton = ({ icon, tooltip, loading, disabled, onClick }:
Props) => {
return (
<Tooltip2 intent={Intent.PRIMARY} position={Position.TOP}
content={tooltip}>
- <Button loading={loading} minimal intent={Intent.PRIMARY} icon={icon}
onClick={onClick} />
+ <Button loading={loading} disabled={disabled} minimal
intent={Intent.PRIMARY} icon={icon} onClick={onClick} />
</Tooltip2>
);
};
diff --git a/config-ui/src/components/index.ts
b/config-ui/src/components/index.ts
index b7582018b..bad168aef 100644
--- a/config-ui/src/components/index.ts
+++ b/config-ui/src/components/index.ts
@@ -27,6 +27,7 @@ export * from './form-item';
export * from './inspector';
export * from './loading';
export * from './logo';
+export * from './message';
export * from './no-data';
export * from './page-header';
export * from './selector';
diff --git a/config-ui/src/pages/connection/detail/styled.ts
b/config-ui/src/components/message/index.tsx
similarity index 70%
copy from config-ui/src/pages/connection/detail/styled.ts
copy to config-ui/src/components/message/index.tsx
index 8a166f523..bc803d9d6 100644
--- a/config-ui/src/pages/connection/detail/styled.ts
+++ b/config-ui/src/components/message/index.tsx
@@ -16,32 +16,29 @@
*
*/
+import { Icon } from '@blueprintjs/core';
import styled from 'styled-components';
-export const Wrapper = styled.div`
- .authentication {
- display: flex;
- align-items: center;
- justify-content: flex-end;
- }
-`;
-
-export const DialogTitle = styled.div`
+const Wrapper = styled.div`
display: flex;
align-items: center;
+ font-size: 12px;
- img {
+ & > .bp4-icon {
margin-right: 8px;
- width: 24px;
}
`;
-export const DialogBody = styled.div`
- display: flex;
- align-items: center;
+interface Props {
+ style?: React.CSSProperties;
+ content: React.ReactNode;
+}
- .bp4-icon {
- margin-right: 8px;
- color: #f4be55;
- }
-`;
+export const Message = ({ style, content }: Props) => {
+ return (
+ <Wrapper style={style}>
+ <Icon icon="warning-sign" size={24} color="#f4be55" />
+ <span>{content}</span>
+ </Wrapper>
+ );
+};
diff --git a/config-ui/src/config/cron.ts b/config-ui/src/config/cron.ts
index 3b5f5f0b9..ddcdbdd8c 100644
--- a/config-ui/src/config/cron.ts
+++ b/config-ui/src/config/cron.ts
@@ -49,7 +49,7 @@ export const getCron = (isManual: boolean, config: string) =>
{
return {
label: 'Manual',
value: 'manual',
- description: 'Manual',
+ description: '',
config: '',
nextTime: '',
};
@@ -65,7 +65,7 @@ export const getCron = (isManual: boolean, config: string) =>
{
}
: {
label: 'Custom',
- value: 'custom',
+ value: '',
description: 'Custom',
config,
nextTime: getNextTime(config),
diff --git a/config-ui/src/pages/blueprint/connection-detail/api.ts
b/config-ui/src/pages/blueprint/connection-detail/api.ts
index dac107063..6e6109e21 100644
--- a/config-ui/src/pages/blueprint/connection-detail/api.ts
+++ b/config-ui/src/pages/blueprint/connection-detail/api.ts
@@ -18,6 +18,8 @@
import { request } from '@/utils';
+export const getProject = (pname: string) => request(`/projects/${pname}`);
+
export const getBlueprint = (id: ID) => request(`/blueprints/${id}`);
export const updateBlueprint = (id: ID, payload: any) =>
diff --git a/config-ui/src/pages/blueprint/connection-detail/index.tsx
b/config-ui/src/pages/blueprint/connection-detail/index.tsx
index 39dc7cf44..6ab122f9e 100644
--- a/config-ui/src/pages/blueprint/connection-detail/index.tsx
+++ b/config-ui/src/pages/blueprint/connection-detail/index.tsx
@@ -33,13 +33,22 @@ export const BlueprintConnectionDetailPage = () => {
const [version, setVersion] = useState(1);
const [isOpen, setIsOpen] = useState(false);
- const { bid, unique } = useParams<{ bid: string; unique: string }>();
+ const { pname, bid, unique } = useParams<{ pname?: string; bid?: string;
unique: string }>();
const history = useHistory();
+ const getBlueprint = async (pname?: string, bid?: string) => {
+ if (pname) {
+ const res = await API.getProject(pname);
+ return res.blueprint;
+ }
+
+ return API.getBlueprint(bid as any);
+ };
+
const { ready, data } = useRefreshData(async () => {
const [plugin, connectionId] = unique.split('-');
const [blueprint, connection, scopes] = await Promise.all([
- API.getBlueprint(bid),
+ getBlueprint(pname, bid),
API.getConnection(plugin, connectionId),
API.getDataScopes(plugin, connectionId),
]);
@@ -58,7 +67,7 @@ export const BlueprintConnectionDetailPage = () => {
},
scopes: scopes.filter((sc: any) =>
scopeIds.includes(sc[getPluginId(plugin)])),
};
- }, [version]);
+ }, [version, pname, bid]);
if (!ready || !data) {
return <PageLoading />;
@@ -83,7 +92,7 @@ export const BlueprintConnectionDetailPage = () => {
);
if (success) {
- history.push(`/blueprints/${blueprint.id}`);
+ history.push(pname ? `/projects/${pname}` :
`/blueprints/${blueprint.id}`);
}
};
@@ -118,14 +127,24 @@ export const BlueprintConnectionDetailPage = () => {
return (
<PageHeader
- breadcrumbs={[
- { name: blueprint.name, path: `/blueprints/${bid}` },
- { name: `Connection - ${connection.name}`, path: '' },
- ]}
+ breadcrumbs={
+ pname
+ ? [
+ { name: 'Projects', path: '/projects' },
+ { name: pname, path: `/projects/${pname}` },
+ { name: `Connection - ${connection.name}`, path: '' },
+ ]
+ : [
+ { name: 'Advanced', path: '/blueprints' },
+ { name: 'Blueprints', path: '/blueprints' },
+ { name: bid as any, path: `/blueprints/${bid}` },
+ { name: `Connection - ${connection.name}`, path: '' },
+ ]
+ }
>
<S.Top>
<span>
- If you would like to manage Data Entities and Data Scope of this
Connection, please{' '}
+ If you would like to edit the Data Scope or Scope Config of this
Connection, please{' '}
<ExternalLink
link={`/connections/${connection.plugin}/${connection.id}`}>
go to the Connection detail page
</ExternalLink>
@@ -149,11 +168,14 @@ export const BlueprintConnectionDetailPage = () => {
</S.Top>
<Buttons position="top" align="left">
<Button intent={Intent.PRIMARY} icon="annotation" text="Manage Data
Scope" onClick={handleShowDataScope} />
+ <ExternalLink style={{ marginLeft: 8 }}
link={`/connections/${connection.plugin}/${connection.id}`}>
+ <Button intent={Intent.PRIMARY} icon="annotation" text="Edit Scope
Config" />
+ </ExternalLink>
</Buttons>
<Table columns={[{ title: 'Data Scope', dataIndex: 'name', key: 'name'
}]} dataSource={scopes} />
<Dialog
isOpen={isOpen}
- title="Change Data Scope"
+ title="Manage Data Scope"
footer={null}
style={{ width: 820 }}
onCancel={handleHideDataScope}
@@ -161,6 +183,7 @@ export const BlueprintConnectionDetailPage = () => {
<DataScopeSelect
plugin={connection.plugin}
connectionId={connection.id}
+ showWarning
initialScope={scopes}
onCancel={handleHideDataScope}
onSubmit={handleChangeDataScope}
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 997fa3e66..67a439d35 100644
--- a/config-ui/src/pages/blueprint/detail/blueprint-detail-page.tsx
+++ b/config-ui/src/pages/blueprint/detail/blueprint-detail-page.tsx
@@ -20,6 +20,8 @@ import { useParams } from 'react-router-dom';
import { PageHeader } from '@/components';
+import { FromEnum } from '../types';
+
import { BlueprintDetail } from './blueprint-detail';
export const BlueprintDetailPage = () => {
@@ -28,11 +30,12 @@ export const BlueprintDetailPage = () => {
return (
<PageHeader
breadcrumbs={[
+ { name: 'Advanced', path: '/blueprints' },
{ name: 'Blueprints', path: '/blueprints' },
- // { name: blueprint.name, path: `/blueprints/${id}` },
+ { name: id, path: `/blueprints/${id}` },
]}
>
- <BlueprintDetail id={id} />
+ <BlueprintDetail id={id} from={FromEnum.blueprint} />
</PageHeader>
);
};
diff --git a/config-ui/src/pages/blueprint/detail/blueprint-detail.tsx
b/config-ui/src/pages/blueprint/detail/blueprint-detail.tsx
index f3daee155..eb975bd67 100644
--- a/config-ui/src/pages/blueprint/detail/blueprint-detail.tsx
+++ b/config-ui/src/pages/blueprint/detail/blueprint-detail.tsx
@@ -17,13 +17,13 @@
*/
import { useState } from 'react';
-import { useHistory } from 'react-router-dom';
import type { TabId } from '@blueprintjs/core';
-import { Tabs, Tab, Switch, Button, Icon, Intent } from '@blueprintjs/core';
+import { Tabs, Tab } from '@blueprintjs/core';
-import { PageLoading, Dialog } from '@/components';
+import { PageLoading } from '@/components';
import { useRefreshData } from '@/hooks';
-import { operator } from '@/utils';
+
+import { FromEnum } from '../types';
import { ConfigurationPanel } from './configuration-panel';
import { StatusPanel } from './status-panel';
@@ -32,76 +32,26 @@ import * as S from './styled';
interface Props {
id: ID;
+ from: FromEnum;
}
-export const BlueprintDetail = ({ id }: Props) => {
- const [activeTab, setActiveTab] = useState<TabId>('configuration');
+export const BlueprintDetail = ({ id, from }: Props) => {
+ const [activeTab, setActiveTab] = useState<TabId>(from === FromEnum.project
? 'configuration' : 'status');
const [version, setVersion] = useState(1);
- const [operating, setOperating] = useState(false);
- const [isOpen, setIsOpen] = useState(false);
-
- const history = useHistory();
const { ready, data } = useRefreshData(
async () => Promise.all([API.getBlueprint(id),
API.getBlueprintPipelines(id)]),
[version],
);
+ const handleRefresh = () => setVersion((v) => v + 1);
+
if (!ready || !data) {
return <PageLoading />;
}
const [blueprint, pipelines] = data;
- const handleUpdate = async (payload: any, callback?: () => void) => {
- const [success] = await operator(
- () =>
- API.updateBlueprint(id, {
- ...blueprint,
- ...payload,
- }),
- {
- setOperating,
- formatMessage: () => 'Update blueprint successful.',
- },
- );
-
- if (success) {
- setVersion((v) => v + 1);
- callback?.();
- }
- };
-
- const handleRun = async (skipCollectors: boolean) => {
- const [success] = await operator(() => API.runBlueprint(id,
skipCollectors), {
- setOperating,
- formatMessage: () => 'Trigger blueprint successful.',
- });
-
- if (success) {
- setVersion((v) => v + 1);
- }
- };
-
- const handleShowDeleteDialog = () => {
- setIsOpen(true);
- };
-
- const handleHideDeleteDialog = () => {
- setIsOpen(false);
- };
-
- const handleDelete = async () => {
- const [success] = await operator(() => API.deleteBluprint(id), {
- setOperating,
- formatMessage: () => 'Delete blueprint successful.',
- });
-
- if (success) {
- history.push('/blueprints');
- }
- };
-
return (
<S.Wrapper>
<Tabs selectedTabId={activeTab} onChange={(at) => setActiveTab(at)}>
@@ -109,46 +59,15 @@ export const BlueprintDetail = ({ id }: Props) => {
id="status"
title="Status"
panel={
- <StatusPanel
- blueprint={blueprint}
- pipelineId={pipelines?.[0]?.id}
- operating={operating}
- onRun={handleRun}
- />
+ <StatusPanel from={from} blueprint={blueprint}
pipelineId={pipelines?.[0]?.id} onRefresh={handleRefresh} />
}
/>
<Tab
id="configuration"
title="Configuration"
- panel={<ConfigurationPanel blueprint={blueprint}
operating={operating} onUpdate={handleUpdate} />}
- />
- <Tabs.Expander />
- <Switch
- style={{ marginBottom: 0 }}
- label="Blueprint Enabled"
- checked={blueprint.enable}
- onChange={(e) => handleUpdate({ enable: (e.target as
HTMLInputElement).checked })}
+ panel={<ConfigurationPanel from={from} blueprint={blueprint}
onRefresh={handleRefresh} />}
/>
- <Button intent={Intent.DANGER} text="Delete Blueprint"
onClick={handleShowDeleteDialog} />
</Tabs>
- <Dialog
- isOpen={isOpen}
- style={{ width: 820 }}
- title="Are you sure you want to delete this Blueprint?"
- okText="Confirm"
- okLoading={operating}
- onCancel={handleHideDeleteDialog}
- onOk={handleDelete}
- >
- <S.DialogBody>
- <Icon icon="warning-sign" />
- <span>
- Please note: deleting the Blueprint will not delete the historical
data of the Data Scopes in this
- Blueprint. If you would like to delete the historical data of Data
Scopes, please visit the Connection page
- and do so.
- </span>
- </S.DialogBody>
- </Dialog>
</S.Wrapper>
);
};
diff --git a/config-ui/src/pages/blueprint/detail/configuration-panel.tsx
b/config-ui/src/pages/blueprint/detail/configuration-panel.tsx
index a76f95655..ef15ae14a 100644
--- a/config-ui/src/pages/blueprint/detail/configuration-panel.tsx
+++ b/config-ui/src/pages/blueprint/detail/configuration-panel.tsx
@@ -21,25 +21,29 @@ import { Link } from 'react-router-dom';
import { Button, Intent } from '@blueprintjs/core';
import { IconButton, Table, NoData, Buttons } from '@/components';
+import { getCron } from '@/config';
import { useConnections } from '@/hooks';
import { getPluginConfig } from '@/plugins';
+import { formatTime, operator } from '@/utils';
-import type { BlueprintType } from '../types';
+import { BlueprintType, FromEnum } from '../types';
import { ModeEnum } from '../types';
import { validRawPlan } from '../utils';
import { AdvancedEditor, UpdateNameDialog, UpdatePolicyDialog,
AddConnectionDialog } from './components';
+import * as API from './api';
import * as S from './styled';
interface Props {
+ from: FromEnum;
blueprint: BlueprintType;
- operating: boolean;
- onUpdate: (payload: any, callback?: () => void) => void;
+ onRefresh: () => void;
}
-export const ConfigurationPanel = ({ blueprint, operating, onUpdate }: Props)
=> {
+export const ConfigurationPanel = ({ from, blueprint, onRefresh }: Props) => {
const [type, setType] = useState<'name' | 'policy' | 'add-connection'>();
const [rawPlan, setRawPlan] = useState('');
+ const [operating, setOperating] = useState(false);
useEffect(() => {
setRawPlan(JSON.stringify(blueprint.plan, null, ' '));
@@ -83,6 +87,25 @@ export const ConfigurationPanel = ({ blueprint, operating,
onUpdate }: Props) =>
setType('add-connection');
};
+ const handleUpdate = async (payload: any) => {
+ const [success] = await operator(
+ () =>
+ API.updateBlueprint(blueprint.id, {
+ ...blueprint,
+ ...payload,
+ }),
+ {
+ setOperating,
+ formatMessage: () => 'Update blueprint successful.',
+ },
+ );
+
+ if (success) {
+ onRefresh();
+ handleCancel();
+ }
+ };
+
return (
<S.ConfigurationPanel>
<div className="block">
@@ -103,22 +126,31 @@ export const ConfigurationPanel = ({ blueprint,
operating, onUpdate }: Props) =>
title: 'Data Time Range',
dataIndex: 'timeRange',
key: 'timeRange',
+ render: (val) => `${formatTime(val)} to Now`,
},
{
title: 'Sync Frequency',
dataIndex: 'frequency',
key: 'frequency',
+ align: 'center',
+ render: (val, row) => {
+ const cron = getCron(row.isManual, val);
+ return `${cron.label}${cron.description}`;
+ },
},
{
title: 'Skip Failed Tasks',
dataIndex: 'skipFailed',
key: 'skipFailed',
+ align: 'center',
+ render: (val) => (val ? 'Enabled' : 'Disabled'),
},
]}
dataSource={[
{
timeRange: blueprint.settings.timeAfter,
frequency: blueprint.cronConfig,
+ isManual: blueprint.isManual,
skipFailed: blueprint.skipOnFail,
},
]}
@@ -165,7 +197,15 @@ export const ConfigurationPanel = ({ blueprint, operating,
onUpdate }: Props) =>
<span>{cs.scope.length} data scope</span>
</div>
<div className="link">
- <Link
to={`/blueprints/${blueprint.id}/${cs.unique}`}>Edit Data Scope and Scope
Config</Link>
+ <Link
+ to={
+ from === FromEnum.blueprint
+ ? `/blueprints/${blueprint.id}/${cs.unique}`
+ : `/projects/${blueprint.projectName}/${cs.unique}`
+ }
+ >
+ Edit Data Scope and Scope Config
+ </Link>
</div>
</S.ConnectionItem>
))}
@@ -183,7 +223,7 @@ export const ConfigurationPanel = ({ blueprint, operating,
onUpdate }: Props) =>
intent={Intent.PRIMARY}
text="Save"
onClick={() =>
- onUpdate({
+ handleUpdate({
plan: !validRawPlan(rawPlan) ? JSON.parse(rawPlan) :
JSON.stringify([[]], null, ' '),
})
}
@@ -196,7 +236,7 @@ export const ConfigurationPanel = ({ blueprint, operating,
onUpdate }: Props) =>
name={blueprint.name}
operating={operating}
onCancel={handleCancel}
- onSubmit={(name) => onUpdate({ name }, handleCancel)}
+ onSubmit={(name) => handleUpdate({ name })}
/>
)}
{type === 'policy' && (
@@ -208,7 +248,7 @@ export const ConfigurationPanel = ({ blueprint, operating,
onUpdate }: Props) =>
timeAfter={blueprint.settings?.timeAfter}
operating={operating}
onCancel={handleCancel}
- onSubmit={(payload) => onUpdate(payload, handleCancel)}
+ onSubmit={(payload) => handleUpdate(payload)}
/>
)}
{type === 'add-connection' && (
@@ -216,7 +256,7 @@ export const ConfigurationPanel = ({ blueprint, operating,
onUpdate }: Props) =>
disabled={connections.map((cs) => cs.unique)}
onCancel={handleCancel}
onSubmit={(connection) =>
- onUpdate({
+ handleUpdate({
settings: { ...blueprint.settings, connections:
[...blueprint.settings.connections, connection] },
})
}
diff --git a/config-ui/src/pages/blueprint/detail/status-panel.tsx
b/config-ui/src/pages/blueprint/detail/status-panel.tsx
index 756e4b3f1..bf672b2b2 100644
--- a/config-ui/src/pages/blueprint/detail/status-panel.tsx
+++ b/config-ui/src/pages/blueprint/detail/status-panel.tsx
@@ -16,53 +16,133 @@
*
*/
-import { useMemo } from 'react';
-import { Button, Intent, Position } from '@blueprintjs/core';
+import { useState, useMemo } from 'react';
+import { useHistory } from 'react-router-dom';
+import { Button, Switch, Icon, Intent, Position } from '@blueprintjs/core';
import { Tooltip2 } from '@blueprintjs/popover2';
-import { Card } from '@/components';
+import { Card, IconButton, Dialog } from '@/components';
import { getCron } from '@/config';
import { PipelineContextProvider, PipelineInfo, PipelineTasks,
PipelineHistorical } from '@/pages';
-import { formatTime } from '@/utils';
+import { formatTime, operator } from '@/utils';
-import type { BlueprintType } from '../types';
+import { BlueprintType, FromEnum } from '../types';
+import * as API from './api';
import * as S from './styled';
interface Props {
+ from: FromEnum;
blueprint: BlueprintType;
pipelineId?: ID;
- operating: boolean;
- onRun: (skipCollectors: boolean) => void;
+ onRefresh: () => void;
}
-export const StatusPanel = ({ blueprint, pipelineId, operating, onRun }:
Props) => {
+export const StatusPanel = ({ from, blueprint, pipelineId, onRefresh }: Props)
=> {
+ const [isOpen, setIsOpen] = useState(false);
+ const [operating, setOperating] = useState(false);
+
+ const history = useHistory();
+
const cron = useMemo(() => getCron(blueprint.isManual,
blueprint.cronConfig), [blueprint]);
+ const handleShowDeleteDialog = () => {
+ setIsOpen(true);
+ };
+
+ const handleHideDeleteDialog = () => {
+ setIsOpen(false);
+ };
+
+ const handleRun = async (skipCollectors: boolean) => {
+ const [success] = await operator(() => API.runBlueprint(blueprint.id,
skipCollectors), {
+ setOperating,
+ formatMessage: () => 'Trigger blueprint successful.',
+ });
+
+ if (success) {
+ onRefresh();
+ }
+ };
+
+ const handleUpdate = async (payload: any) => {
+ const [success] = await operator(
+ () =>
+ API.updateBlueprint(blueprint.id, {
+ ...blueprint,
+ ...payload,
+ }),
+ {
+ setOperating,
+ formatMessage: () => 'Update blueprint successful.',
+ },
+ );
+
+ if (success) {
+ onRefresh();
+ }
+ };
+
+ const handleDelete = async () => {
+ const [success] = await operator(() => API.deleteBluprint(blueprint.id), {
+ setOperating,
+ formatMessage: () => 'Delete blueprint successful.',
+ });
+
+ if (success) {
+ history.push('/blueprints');
+ }
+ };
+
return (
<S.StatusPanel>
- <div className="info">
- <span>{cron.value === 'manual' ? 'Manual' : `Next Run:
${formatTime(cron.nextTime, 'YYYY-MM-DD HH:mm')}`}</span>
- <Tooltip2
- position={Position.TOP}
- content="It is recommended to re-transform your data in this project
if you have updated the transformation of the data scope in this project."
- >
+ {from === FromEnum.project && (
+ <S.ProjectACtion>
+ <span>
+ {cron.value === 'manual' ? 'Manual' : `Next Run:
${formatTime(cron.nextTime, 'YYYY-MM-DD HH:mm')}`}
+ </span>
+ <Tooltip2
+ position={Position.TOP}
+ content="It is recommended to re-transform your data in this
project if you have updated the transformation of the data scope in this
project."
+ >
+ <Button
+ disabled={!blueprint.enable}
+ loading={operating}
+ intent={Intent.PRIMARY}
+ text="Re-transform Data"
+ onClick={() => handleRun(true)}
+ />
+ </Tooltip2>
<Button
disabled={!blueprint.enable}
loading={operating}
intent={Intent.PRIMARY}
- text="Re-transform Data"
- onClick={() => onRun(true)}
+ text="Collect All Data"
+ onClick={() => handleRun(false)}
+ />
+ </S.ProjectACtion>
+ )}
+
+ {from === FromEnum.blueprint && (
+ <S.BlueprintAction>
+ <Button text="Run Now" onClick={() => handleRun(false)} />
+ <Switch
+ style={{ marginBottom: 0 }}
+ label="Blueprint Enabled"
+ disabled={!!blueprint.projectName}
+ checked={blueprint.enable}
+ onChange={(e) => handleUpdate({ enable: (e.target as
HTMLInputElement).checked })}
/>
- </Tooltip2>
- <Button
- disabled={!blueprint.enable}
- loading={operating}
- intent={Intent.PRIMARY}
- text="Collect All Data"
- onClick={() => onRun(false)}
- />
- </div>
+ <IconButton
+ loading={operating}
+ disabled={!!blueprint.projectName}
+ icon="trash"
+ tooltip="Delete Blueprint"
+ onClick={handleShowDeleteDialog}
+ />
+ </S.BlueprintAction>
+ )}
+
<PipelineContextProvider>
<div className="block">
<h3>Current Pipeline</h3>
@@ -82,6 +162,25 @@ export const StatusPanel = ({ blueprint, pipelineId,
operating, onRun }: Props)
<PipelineHistorical blueprintId={blueprint.id} />
</div>
</PipelineContextProvider>
+
+ <Dialog
+ isOpen={isOpen}
+ style={{ width: 820 }}
+ title="Are you sure you want to delete this Blueprint?"
+ okText="Confirm"
+ okLoading={operating}
+ onCancel={handleHideDeleteDialog}
+ onOk={handleDelete}
+ >
+ <S.DialogBody>
+ <Icon icon="warning-sign" />
+ <span>
+ Please note: deleting the Blueprint will not delete the historical
data of the Data Scopes in this
+ Blueprint. If you would like to delete the historical data of Data
Scopes, please visit the Connection page
+ and do so.
+ </span>
+ </S.DialogBody>
+ </Dialog>
</S.StatusPanel>
);
};
diff --git a/config-ui/src/pages/blueprint/detail/styled.ts
b/config-ui/src/pages/blueprint/detail/styled.ts
index a7caedb95..cc28fb174 100644
--- a/config-ui/src/pages/blueprint/detail/styled.ts
+++ b/config-ui/src/pages/blueprint/detail/styled.ts
@@ -88,17 +88,27 @@ export const StatusPanel = styled.div`
margin-bottom: 16px;
}
- & > .info {
- display: flex;
- justify-content: flex-end;
- align-items: center;
+ .block + .block {
+ margin-top: 32px;
+ }
+`;
- & > * {
- margin-left: 16px;
- }
+export const ProjectACtion = styled.div`
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+
+ & > * {
+ margin-left: 16px;
}
+`;
- .block + .block {
- margin-top: 32px;
+export const BlueprintAction = styled.div`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ & > .bp4-switch {
+ margin: 0 8px;
}
`;
diff --git a/config-ui/src/pages/blueprint/types.ts
b/config-ui/src/pages/blueprint/types.ts
index ca87e5d6a..2c498e9c4 100644
--- a/config-ui/src/pages/blueprint/types.ts
+++ b/config-ui/src/pages/blueprint/types.ts
@@ -21,7 +21,13 @@ export enum ModeEnum {
normal = 'NORMAL',
}
+export enum FromEnum {
+ project = 'PROJECT',
+ blueprint = 'BLUEPRINT',
+}
+
export type BlueprintType = {
+ projectName: string;
id: ID;
enable: boolean;
name: string;
diff --git a/config-ui/src/pages/connection/detail/index.tsx
b/config-ui/src/pages/connection/detail/index.tsx
index 12be3e8c3..a7c096ddb 100644
--- a/config-ui/src/pages/connection/detail/index.tsx
+++ b/config-ui/src/pages/connection/detail/index.tsx
@@ -16,7 +16,7 @@
*
*/
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
import { useParams, useHistory } from 'react-router-dom';
import { Button, Icon, Intent } from '@blueprintjs/core';
@@ -35,12 +35,7 @@ import { operator } from '@/utils';
import * as API from './api';
import * as S from './styled';
-interface Props {
- plugin: string;
- id: ID;
-}
-
-const ConnectionDetail = ({ plugin, id }: Props) => {
+export const ConnectionDetailPage = () => {
const [type, setType] = useState<
| 'deleteConnection'
| 'updateConnection'
@@ -55,12 +50,17 @@ const ConnectionDetail = ({ plugin, id }: Props) => {
const [scopeIds, setScopeIds] = useState<ID[]>([]);
const [scopeConfigId, setScopeConfigId] = useState<ID>();
+ const { plugin, id } = useParams<{ plugin: string; id: string }>();
const history = useHistory();
const { onGet, onTest, onRefresh } = useConnections();
const { setTips } = useTips();
const { ready, data } = useRefreshData(() => API.getDataScopes(plugin, id),
[version]);
- const { unique, status, name, icon } = onGet(`${plugin}-${id}`);
+ const { unique, status, name, icon } = onGet(`${plugin}-${id}`) || {};
+
+ useEffect(() => {
+ onTest(`${plugin}-${id}`);
+ }, [plugin, id]);
const handleHideDialog = () => {
setType(undefined);
@@ -89,6 +89,7 @@ const ConnectionDetail = ({ plugin, id }: Props) => {
});
if (success) {
+ onRefresh(plugin);
history.push('/connections');
}
};
@@ -175,15 +176,19 @@ const ConnectionDetail = ({ plugin, id }: Props) => {
extra={<Button intent={Intent.DANGER} icon="trash" text="Delete
Connection" onClick={handleShowDeleteDialog} />}
>
<S.Wrapper>
- <div className="authentication">
- <span style={{ marginRight: 4 }}>Authentication Status:</span>
- <ConnectionStatus status={status} unique={unique} onTest={onTest} />
- <IconButton icon="annotation" tooltip="Edit Connection"
onClick={handleShowUpdateDialog} />
+ <div className="top">
+ <div>Please note: In order to view DORA metrics, you will need to
add Scope Configs.</div>
+ <div className="authentication">
+ <span style={{ marginRight: 4 }}>Authentication Status:</span>
+ <ConnectionStatus status={status} unique={unique} onTest={onTest}
/>
+ <IconButton icon="annotation" tooltip="Edit Connection"
onClick={handleShowUpdateDialog} />
+ </div>
</div>
<Buttons position="top" align="left">
<Button intent={Intent.PRIMARY} icon="add" text="Add Data Scope"
onClick={handleShowCreateDataScopeDialog} />
{plugin !== 'tapd' && (
<Button
+ disabled={!scopeIds.length}
intent={Intent.PRIMARY}
icon="many-to-one"
text="Associate Scope Config"
@@ -370,9 +375,3 @@ const ConnectionDetail = ({ plugin, id }: Props) => {
</PageHeader>
);
};
-
-export const ConnectionDetailPage = () => {
- const { plugin, id } = useParams<{ plugin: string; id: string }>();
-
- return <ConnectionDetail plugin={plugin} id={+id} />;
-};
diff --git a/config-ui/src/pages/connection/detail/styled.ts
b/config-ui/src/pages/connection/detail/styled.ts
index 8a166f523..8d79884eb 100644
--- a/config-ui/src/pages/connection/detail/styled.ts
+++ b/config-ui/src/pages/connection/detail/styled.ts
@@ -19,6 +19,13 @@
import styled from 'styled-components';
export const Wrapper = styled.div`
+ .top {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 24px;
+ }
+
.authentication {
display: flex;
align-items: center;
diff --git a/config-ui/src/pages/connection/home/index.tsx
b/config-ui/src/pages/connection/home/index.tsx
index 9613bfca5..2bc644ca9 100644
--- a/config-ui/src/pages/connection/home/index.tsx
+++ b/config-ui/src/pages/connection/home/index.tsx
@@ -17,21 +17,22 @@
*/
import { useState, useMemo } from 'react';
+import { useHistory } from 'react-router-dom';
import { Tag, Intent } from '@blueprintjs/core';
import { Dialog } from '@/components';
import { useConnections } from '@/hooks';
import type { PluginConfigType } from '@/plugins';
import { PluginConfig, PluginType, ConnectionList, ConnectionForm } from
'@/plugins';
-import { ConnectionContextProvider } from '@/store';
import * as S from './styled';
-export const ConnectionHome = () => {
+export const ConnectionHomePage = () => {
const [type, setType] = useState<'list' | 'form'>();
const [pluginConfig, setPluginConfig] = useState<PluginConfigType>();
const { connections, onRefresh } = useConnections();
+ const history = useHistory();
const [plugins, webhook] = useMemo(
() => [
@@ -61,9 +62,9 @@ export const ConnectionHome = () => {
setPluginConfig(undefined);
};
- const handleCreateSuccess = async (plugin: string) => {
+ const handleCreateSuccess = async (plugin: string, id: ID) => {
onRefresh(plugin);
- setType('list');
+ history.push(`/connections/${plugin}/${id}`);
};
return (
@@ -138,17 +139,12 @@ export const ConnectionHome = () => {
footer={null}
onCancel={handleHideDialog}
>
- <ConnectionForm plugin={pluginConfig.plugin} onSuccess={() =>
handleCreateSuccess(pluginConfig.plugin)} />
+ <ConnectionForm
+ plugin={pluginConfig.plugin}
+ onSuccess={(id) => handleCreateSuccess(pluginConfig.plugin, id)}
+ />
</Dialog>
)}
</S.Wrapper>
);
};
-
-export const ConnectionHomePage = () => {
- return (
- <ConnectionContextProvider>
- <ConnectionHome />
- </ConnectionContextProvider>
- );
-};
diff --git a/config-ui/src/pages/project/detail/api.ts
b/config-ui/src/pages/project/detail/api.ts
index a222dda7f..bc8fdaca4 100644
--- a/config-ui/src/pages/project/detail/api.ts
+++ b/config-ui/src/pages/project/detail/api.ts
@@ -29,7 +29,10 @@ export const updateProject = (name: string, payload:
Omit<ProjectType, 'blueprin
});
export const updateBlueprint = (id: ID, payload: BlueprintType) =>
- request(`/blueprints/${id}`, { method: 'patch', data: payload });
+ request(`/blueprints/${id}`, {
+ method: 'patch',
+ data: payload,
+ });
export const deleteProject = (name: string) =>
request(`/projects/${name}`, {
diff --git a/config-ui/src/pages/project/detail/index.tsx
b/config-ui/src/pages/project/detail/index.tsx
index 4db19c51c..6a98974ff 100644
--- a/config-ui/src/pages/project/detail/index.tsx
+++ b/config-ui/src/pages/project/detail/index.tsx
@@ -22,7 +22,7 @@ import { Tabs, Tab } from '@blueprintjs/core';
import { PageHeader, PageLoading } from '@/components';
import { useRefreshData } from '@/hooks';
-import { BlueprintDetail } from '@/pages';
+import { BlueprintDetail, FromEnum } from '@/pages';
import { WebhooksPanel } from './webhooks-panel';
import { SettingsPanel } from './settings-panel';
@@ -71,7 +71,11 @@ export const ProjectDetailPage = () => {
>
<S.Wrapper>
<Tabs selectedTabId={tabId} onChange={handleChangeTabId}>
- <Tab id="blueprint" title="Blueprint" panel={<BlueprintDetail
id={project.blueprint.id} />} />
+ <Tab
+ id="blueprint"
+ title="Blueprint"
+ panel={<BlueprintDetail id={project.blueprint.id}
from={FromEnum.project} />}
+ />
<Tab
id="webhook"
title="Incoming Webhooks"
diff --git a/config-ui/src/plugins/components/connection-form/index.tsx
b/config-ui/src/plugins/components/connection-form/index.tsx
index 6ea9d89c6..bfb594fef 100644
--- a/config-ui/src/plugins/components/connection-form/index.tsx
+++ b/config-ui/src/plugins/components/connection-form/index.tsx
@@ -32,7 +32,7 @@ import * as S from './styled';
interface Props {
plugin: string;
connectionId?: ID;
- onSuccess?: () => void;
+ onSuccess?: (id: ID) => void;
}
export const ConnectionForm = ({ plugin, connectionId, onSuccess }: Props) => {
@@ -83,7 +83,7 @@ export const ConnectionForm = ({ plugin, connectionId,
onSuccess }: Props) => {
};
const handleSave = async () => {
- const [success] = await operator(
+ const [success, res] = await operator(
() => (!connectionId ? API.createConnection(plugin, values) :
API.updateConnection(plugin, connectionId, values)),
{
setOperating,
@@ -92,7 +92,7 @@ export const ConnectionForm = ({ plugin, connectionId,
onSuccess }: Props) => {
);
if (success) {
- onSuccess?.();
+ onSuccess?.(res.id);
}
};
diff --git a/config-ui/src/plugins/components/connection-list/index.tsx
b/config-ui/src/plugins/components/connection-list/index.tsx
index 6e5fe4042..aa439d8db 100644
--- a/config-ui/src/plugins/components/connection-list/index.tsx
+++ b/config-ui/src/plugins/components/connection-list/index.tsx
@@ -55,6 +55,7 @@ const BaseList = ({ plugin, onCreate }: Props) => {
title: 'Status',
dataIndex: ['status', 'unique'],
key: 'status',
+ width: 150,
render: ({ status, unique }) => <ConnectionStatus status={status}
unique={unique} onTest={onTest} />,
},
{
diff --git a/config-ui/src/plugins/components/data-scope-select/index.tsx
b/config-ui/src/plugins/components/data-scope-select/index.tsx
index 4a3730972..b1005a787 100644
--- a/config-ui/src/plugins/components/data-scope-select/index.tsx
+++ b/config-ui/src/plugins/components/data-scope-select/index.tsx
@@ -19,7 +19,7 @@
import { useState, useEffect } from 'react';
import { Button, Intent } from '@blueprintjs/core';
-import { PageLoading, FormItem, ExternalLink, Buttons, Table } from
'@/components';
+import { PageLoading, FormItem, ExternalLink, Message, Buttons, Table } from
'@/components';
import { useRefreshData } from '@/hooks';
import { getPluginId } from '@/plugins';
@@ -29,20 +29,31 @@ import * as S from './styled';
interface Props {
plugin: string;
connectionId: ID;
+ showWarning?: boolean;
initialScope?: any[];
onCancel?: () => void;
onSubmit?: (scope: any) => void;
}
-export const DataScopeSelect = ({ plugin, connectionId, initialScope,
onSubmit, onCancel }: Props) => {
+export const DataScopeSelect = ({
+ plugin,
+ connectionId,
+ showWarning = false,
+ initialScope,
+ onSubmit,
+ onCancel,
+}: Props) => {
+ const [version, setVersion] = useState(1);
const [scopeIds, setScopeIds] = useState<ID[]>([]);
- const { ready, data } = useRefreshData(() => API.getDataScope(plugin,
connectionId));
+ const { ready, data } = useRefreshData(() => API.getDataScope(plugin,
connectionId), [version]);
useEffect(() => {
setScopeIds((initialScope ?? data ?? []).map((sc: any) =>
sc[getPluginId(plugin)]) ?? []);
}, [data]);
+ const handleRefresh = () => setVersion((v) => v + 1);
+
const handleSubmit = () => {
const scope = data.filter((it: any) =>
scopeIds.includes(it[getPluginId(plugin)]));
onSubmit?.(scope);
@@ -76,9 +87,23 @@ export const DataScopeSelect = ({ plugin, connectionId,
initialScope, onSubmit,
>
{data.length ? (
<S.Wrapper>
- <Buttons position="top" align="left">
- <Button intent={Intent.PRIMARY} icon="refresh" text="Refresh Data
Scope" />
- </Buttons>
+ {showWarning ? (
+ <Message
+ style={{ marginBottom: 24 }}
+ content={
+ <>
+ Unchecking Data Scope below will only remove it from the
current Project and will not delete the
+ historical data. If you would like to delete the data of
Data Scope, please{' '}
+ <ExternalLink
link={`/connections/${plugin}/${connectionId}`}>go to the Connection
page</ExternalLink>
+ .
+ </>
+ }
+ />
+ ) : (
+ <Buttons position="top" align="left">
+ <Button intent={Intent.PRIMARY} icon="refresh" text="Refresh
Data Scope" onClick={handleRefresh} />
+ </Buttons>
+ )}
<Table
noShadow
loading={!ready}
diff --git a/config-ui/src/plugins/components/scope-config-form/index.tsx
b/config-ui/src/plugins/components/scope-config-form/index.tsx
index 0637d2ee9..e46daf1da 100644
--- a/config-ui/src/plugins/components/scope-config-form/index.tsx
+++ b/config-ui/src/plugins/components/scope-config-form/index.tsx
@@ -20,7 +20,7 @@ import { useState, useEffect, useMemo } from 'react';
import { omit } from 'lodash';
import { InputGroup, Button, Intent } from '@blueprintjs/core';
-import { Alert, ExternalLink, Card, FormItem, MultiSelector, Buttons, Divider
} from '@/components';
+import { Alert, ExternalLink, Card, FormItem, MultiSelector, Message, Buttons,
Divider } from '@/components';
import { transformEntities, EntitiesLabel } from '@/config';
import { getPluginConfig } from '@/plugins';
import { GitHubTransformation } from '@/plugins/register/github';
@@ -40,13 +40,22 @@ import * as S from './styled';
interface Props {
plugin: string;
connectionId: ID;
+ showWarning?: boolean;
scopeId?: ID;
scopeConfigId?: ID;
onCancel?: () => void;
onSubmit?: (trId: string) => void;
}
-export const ScopeConfigForm = ({ plugin, connectionId, scopeId,
scopeConfigId, onCancel, onSubmit }: Props) => {
+export const ScopeConfigForm = ({
+ plugin,
+ connectionId,
+ showWarning = false,
+ scopeId,
+ scopeConfigId,
+ onCancel,
+ onSubmit,
+}: Props) => {
const [step, setStep] = useState(1);
const [name, setName] = useState('');
const [entities, setEntities] = useState<string[]>([]);
@@ -60,6 +69,10 @@ export const ScopeConfigForm = ({ plugin, connectionId,
scopeId, scopeConfigId,
setHasRefDiff(!!config.transformation.refdiff);
}, [config.transformation]);
+ useEffect(() => {
+ setEntities(config.entities);
+ }, [config.entities]);
+
useEffect(() => {
if (!scopeConfigId) return;
@@ -73,6 +86,10 @@ export const ScopeConfigForm = ({ plugin, connectionId,
scopeId, scopeConfigId,
})();
}, [scopeConfigId]);
+ const handlePrevStep = () => {
+ setStep(1);
+ };
+
const handleNextStep = () => {
setStep(2);
};
@@ -133,6 +150,13 @@ export const ScopeConfigForm = ({ plugin, connectionId,
scopeId, scopeConfigId,
onChangeItems={(its) => setEntities(its.map((it) => it.value))}
/>
</FormItem>
+ {showWarning && (
+ <Message
+ content="Please note: if you edit Data Entities and expect to
see the Dashboards updated, you will need to visit
+ the Project page of the Data Scope that has been associated
with this Scope Config and click on “Collect
+ All Data”."
+ />
+ )}
</Card>
<Buttons>
<Button outlined intent={Intent.PRIMARY} text="Cancel"
onClick={onCancel} />
@@ -143,12 +167,24 @@ export const ScopeConfigForm = ({ plugin, connectionId,
scopeId, scopeConfigId,
{step === 2 && (
<>
<Card>
+ {showWarning && (
+ <>
+ <Message content="Please note: if you only edit the following
Scope Configs without editing Data Entities in the previous step, you will only
need to re-transform data on the Project page to see the Dashboard updated." />
+ <Divider />
+ </>
+ )}
+
{plugin === 'github' && (
- <GitHubTransformation transformation={transformation}
setTransformation={setTransformation} />
+ <GitHubTransformation
+ entities={entities}
+ transformation={transformation}
+ setTransformation={setTransformation}
+ />
)}
{plugin === 'jira' && (
<JiraTransformation
+ entities={entities}
connectionId={connectionId}
transformation={transformation}
setTransformation={setTransformation}
@@ -156,23 +192,40 @@ export const ScopeConfigForm = ({ plugin, connectionId,
scopeId, scopeConfigId,
)}
{plugin === 'gitlab' && (
- <GitLabTransformation transformation={transformation}
setTransformation={setTransformation} />
+ <GitLabTransformation
+ entities={entities}
+ transformation={transformation}
+ setTransformation={setTransformation}
+ />
)}
{plugin === 'jenkins' && (
- <JenkinsTransformation transformation={transformation}
setTransformation={setTransformation} />
+ <JenkinsTransformation
+ entities={entities}
+ transformation={transformation}
+ setTransformation={setTransformation}
+ />
)}
{plugin === 'bitbucket' && (
- <BitbucketTransformation transformation={transformation}
setTransformation={setTransformation} />
+ <BitbucketTransformation
+ entities={entities}
+ transformation={transformation}
+ setTransformation={setTransformation}
+ />
)}
{plugin === 'azuredevops' && (
- <AzureTransformation transformation={transformation}
setTransformation={setTransformation} />
+ <AzureTransformation
+ entities={entities}
+ transformation={transformation}
+ setTransformation={setTransformation}
+ />
)}
{plugin === 'tapd' && scopeId && (
<TapdTransformation
+ entities={entities}
connectionId={connectionId}
scopeId={scopeId}
transformation={transformation}
@@ -180,15 +233,10 @@ export const ScopeConfigForm = ({ plugin, connectionId,
scopeId, scopeConfigId,
/>
)}
- {hasRefDiff && (
- <>
- <Divider />
- <AdditionalSettings transformation={transformation}
setTransformation={setTransformation} />
- </>
- )}
+ {hasRefDiff && <AdditionalSettings transformation={transformation}
setTransformation={setTransformation} />}
</Card>
<Buttons>
- <Button outlined intent={Intent.PRIMARY} text="Cancel"
onClick={onCancel} />
+ <Button outlined intent={Intent.PRIMARY} text="Prev"
onClick={handlePrevStep} />
<Button loading={operating} intent={Intent.PRIMARY} text="Save"
onClick={handleSubmit} />
</Buttons>
</>
diff --git a/config-ui/src/plugins/components/scope-config-select/index.tsx
b/config-ui/src/plugins/components/scope-config-select/index.tsx
index 18fbcb6bd..3cba0b5bf 100644
--- a/config-ui/src/plugins/components/scope-config-select/index.tsx
+++ b/config-ui/src/plugins/components/scope-config-select/index.tsx
@@ -50,6 +50,7 @@ export const ScopeConfigSelect = ({ plugin, connectionId,
onCancel, onSubmit }:
const handleHideDialog = () => {
setIsOpen(false);
+ setUpdatedId(undefined);
};
const handleUpdate = async (id: ID) => {
@@ -92,10 +93,17 @@ export const ScopeConfigSelect = ({ plugin, connectionId,
onCancel, onSubmit }:
<Button outlined intent={Intent.PRIMARY} text="Cancel"
onClick={onCancel} />
<Button disabled={!trId} intent={Intent.PRIMARY} text="Save"
onClick={() => trId && onSubmit?.(trId)} />
</Buttons>
- <Dialog style={{ width: 820 }} footer={null} isOpen={isOpen} title="Add
Scope Config" onCancel={handleHideDialog}>
+ <Dialog
+ style={{ width: 820 }}
+ footer={null}
+ isOpen={isOpen}
+ title={!updatedId ? 'Add Scope Config' : 'Edit Scope Config'}
+ onCancel={handleHideDialog}
+ >
<ScopeConfigForm
plugin={plugin}
connectionId={connectionId}
+ showWarning={!!updatedId}
scopeConfigId={updatedId}
onCancel={onCancel}
onSubmit={handleSubmit}
diff --git a/config-ui/src/plugins/register/azure/transformation.tsx
b/config-ui/src/plugins/register/azure/transformation.tsx
index 47205e82e..e5bebbd2b 100644
--- a/config-ui/src/plugins/register/azure/transformation.tsx
+++ b/config-ui/src/plugins/register/azure/transformation.tsx
@@ -24,11 +24,12 @@ import { ExternalLink, HelpTooltip } from '@/components';
import * as S from './styled';
interface Props {
+ entities: string[];
transformation: any;
setTransformation: React.Dispatch<React.SetStateAction<any>>;
}
-export const AzureTransformation = ({ transformation, setTransformation }:
Props) => {
+export const AzureTransformation = ({ entities, transformation,
setTransformation }: Props) => {
const [enableCICD, setEnableCICD] = useState(true);
useEffect(() => {
@@ -53,66 +54,68 @@ export const AzureTransformation = ({ transformation,
setTransformation }: Props
return (
<S.Transfromation>
- <S.CICD>
- <h2>CI/CD</h2>
- <h3>
- <span>Deployment</span>
- <Tag minimal intent={Intent.PRIMARY} style={{ marginLeft: 8 }}>
- DORA
- </Tag>
- <div className="switch">
- <span>Enable</span>
- <Switch alignIndicator="right" inline checked={enableCICD}
onChange={handleChangeCICDEnable} />
- </div>
- </h3>
- {enableCICD && (
- <>
- <p>
- Use Regular Expression to define Deployments in DevLake in order
to measure DORA metrics.{' '}
- <ExternalLink
link="https://devlake.apache.org/docs/Configuration/GitHub#step-3---adding-transformation-rules-optional">
- Learn more
- </ExternalLink>
- </p>
- <div style={{ marginTop: 16 }}>Convert a Azure Pipeline Run as a
DevLake Deployment when: </div>
- <div className="text">
- <span>
- The name of the <strong>Azure pipeline</strong> or <strong>one
of its jobs</strong> matches
- </span>
- <InputGroup
- style={{ width: 200, margin: '0 8px' }}
- placeholder="(deploy|push-image)"
- value={transformation.deploymentPattern ?? ''}
- onChange={(e) =>
- setTransformation({
- ...transformation,
- deploymentPattern: e.target.value,
- productionPattern: !e.target.value ? '' :
transformation.productionPattern,
- })
- }
- />
- <i style={{ color: '#E34040' }}>*</i>
- <HelpTooltip content="Azure Pipelines:
https://learn.microsoft.com/en-us/azure/devops/pipelines/get-started/what-is-azure-pipelines?view=azure-devops#continuous-testing"
/>
+ {entities.includes('CICD') && (
+ <S.CICD>
+ <h2>CI/CD</h2>
+ <h3>
+ <span>Deployment</span>
+ <Tag minimal intent={Intent.PRIMARY} style={{ marginLeft: 8 }}>
+ DORA
+ </Tag>
+ <div className="switch">
+ <span>Enable</span>
+ <Switch alignIndicator="right" inline checked={enableCICD}
onChange={handleChangeCICDEnable} />
</div>
- <div className="text">
- <span>If the name also matches</span>
- <InputGroup
- style={{ width: 200, margin: '0 8px' }}
- disabled={!transformation.deploymentPattern}
- placeholder="prod(.*)"
- value={transformation.productionPattern ?? ''}
- onChange={(e) =>
- setTransformation({
- ...transformation,
- productionPattern: e.target.value,
- })
- }
- />
- <span>, this Deployment is a ‘Production Deployment’</span>
- <HelpTooltip content="If you leave this field empty, all DevLake
Deployments will be tagged as in the Production environment. " />
- </div>
- </>
- )}
- </S.CICD>
+ </h3>
+ {enableCICD && (
+ <>
+ <p>
+ Use Regular Expression to define Deployments in DevLake in
order to measure DORA metrics.{' '}
+ <ExternalLink
link="https://devlake.apache.org/docs/Configuration/GitHub#step-3---adding-transformation-rules-optional">
+ Learn more
+ </ExternalLink>
+ </p>
+ <div style={{ marginTop: 16 }}>Convert a Azure Pipeline Run as a
DevLake Deployment when: </div>
+ <div className="text">
+ <span>
+ The name of the <strong>Azure pipeline</strong> or
<strong>one of its jobs</strong> matches
+ </span>
+ <InputGroup
+ style={{ width: 200, margin: '0 8px' }}
+ placeholder="(deploy|push-image)"
+ value={transformation.deploymentPattern ?? ''}
+ onChange={(e) =>
+ setTransformation({
+ ...transformation,
+ deploymentPattern: e.target.value,
+ productionPattern: !e.target.value ? '' :
transformation.productionPattern,
+ })
+ }
+ />
+ <i style={{ color: '#E34040' }}>*</i>
+ <HelpTooltip content="Azure Pipelines:
https://learn.microsoft.com/en-us/azure/devops/pipelines/get-started/what-is-azure-pipelines?view=azure-devops#continuous-testing"
/>
+ </div>
+ <div className="text">
+ <span>If the name also matches</span>
+ <InputGroup
+ style={{ width: 200, margin: '0 8px' }}
+ disabled={!transformation.deploymentPattern}
+ placeholder="prod(.*)"
+ value={transformation.productionPattern ?? ''}
+ onChange={(e) =>
+ setTransformation({
+ ...transformation,
+ productionPattern: e.target.value,
+ })
+ }
+ />
+ <span>, this Deployment is a ‘Production Deployment’</span>
+ <HelpTooltip content="If you leave this field empty, all
DevLake Deployments will be tagged as in the Production environment. " />
+ </div>
+ </>
+ )}
+ </S.CICD>
+ )}
</S.Transfromation>
);
};
diff --git a/config-ui/src/plugins/register/bitbucket/transformation.tsx
b/config-ui/src/plugins/register/bitbucket/transformation.tsx
index 2a13cb554..0dc647035 100644
--- a/config-ui/src/plugins/register/bitbucket/transformation.tsx
+++ b/config-ui/src/plugins/register/bitbucket/transformation.tsx
@@ -25,13 +25,14 @@ import ExampleJpg from './assets/bitbucket-example.jpg';
import * as S from './styled';
interface Props {
+ entities: string[];
transformation: any;
setTransformation: React.Dispatch<React.SetStateAction<any>>;
}
const ALL_STATES = ['new', 'open', 'resolved', 'closed', 'on hold', 'wontfix',
'duplicate', 'invalid'];
-export const BitbucketTransformation = ({ transformation, setTransformation }:
Props) => {
+export const BitbucketTransformation = ({ entities, transformation,
setTransformation }: Props) => {
const [useCustom, setUseCustom] = useState(false);
useEffect(() => {
@@ -68,134 +69,136 @@ export const BitbucketTransformation = ({ transformation,
setTransformation }: P
return (
<S.Transformation>
- {/* Issue Tracking */}
- <div className="issue-tracking">
- <h2>Issue Tracking</h2>
- <div className="issue-type">
- <div className="title">
- <span>Issue Status Mapping</span>
- <HelpTooltip content="Standardize your issue statuses to the
following issue statuses to view metrics such as `Requirement Delivery Rate` in
built-in dashboards." />
+ {entities.includes('TICKET') && (
+ <div className="issue-tracking">
+ <h2>Issue Tracking</h2>
+ <div className="issue-type">
+ <div className="title">
+ <span>Issue Status Mapping</span>
+ <HelpTooltip content="Standardize your issue statuses to the
following issue statuses to view metrics such as `Requirement Delivery Rate` in
built-in dashboards." />
+ </div>
+ <div className="list">
+ <FormGroup inline label="TODO">
+ <MultiSelector
+ items={ALL_STATES}
+ disabledItems={selectedStates}
+ selectedItems={transformation.issueStatusTodo ?
transformation.issueStatusTodo.split(',') : []}
+ onChangeItems={(selectedItems) =>
+ setTransformation({
+ ...transformation,
+ issueStatusTodo: selectedItems.join(','),
+ })
+ }
+ />
+ </FormGroup>
+ <FormGroup inline label="IN-PROGRESS">
+ <MultiSelector
+ items={ALL_STATES}
+ disabledItems={selectedStates}
+ selectedItems={
+ transformation.issueStatusInProgress ?
transformation.issueStatusInProgress.split(',') : []
+ }
+ onChangeItems={(selectedItems) =>
+ setTransformation({
+ ...transformation,
+ issueStatusInProgress: selectedItems.join(','),
+ })
+ }
+ />
+ </FormGroup>
+ <FormGroup inline label="DONE">
+ <MultiSelector
+ items={ALL_STATES}
+ disabledItems={selectedStates}
+ selectedItems={transformation.issueStatusDone ?
transformation.issueStatusDone.split(',') : []}
+ onChangeItems={(selectedItems) =>
+ setTransformation({
+ ...transformation,
+ issueStatusDone: selectedItems.join(','),
+ })
+ }
+ />
+ </FormGroup>
+ <FormGroup inline label="OTHER">
+ <MultiSelector
+ items={ALL_STATES}
+ disabledItems={selectedStates}
+ selectedItems={transformation.issueStatusOther ?
transformation.issueStatusOther.split(',') : []}
+ onChangeItems={(selectedItems) =>
+ setTransformation({
+ ...transformation,
+ issueStatusOther: selectedItems.join(','),
+ })
+ }
+ />
+ </FormGroup>
+ </div>
</div>
- <div className="list">
- <FormGroup inline label="TODO">
- <MultiSelector
- items={ALL_STATES}
- disabledItems={selectedStates}
- selectedItems={transformation.issueStatusTodo ?
transformation.issueStatusTodo.split(',') : []}
- onChangeItems={(selectedItems) =>
- setTransformation({
- ...transformation,
- issueStatusTodo: selectedItems.join(','),
- })
- }
- />
- </FormGroup>
- <FormGroup inline label="IN-PROGRESS">
- <MultiSelector
- items={ALL_STATES}
- disabledItems={selectedStates}
- selectedItems={
- transformation.issueStatusInProgress ?
transformation.issueStatusInProgress.split(',') : []
- }
- onChangeItems={(selectedItems) =>
- setTransformation({
- ...transformation,
- issueStatusInProgress: selectedItems.join(','),
- })
- }
- />
- </FormGroup>
- <FormGroup inline label="DONE">
- <MultiSelector
- items={ALL_STATES}
- disabledItems={selectedStates}
- selectedItems={transformation.issueStatusDone ?
transformation.issueStatusDone.split(',') : []}
- onChangeItems={(selectedItems) =>
- setTransformation({
- ...transformation,
- issueStatusDone: selectedItems.join(','),
- })
- }
- />
- </FormGroup>
- <FormGroup inline label="OTHER">
- <MultiSelector
- items={ALL_STATES}
- disabledItems={selectedStates}
- selectedItems={transformation.issueStatusOther ?
transformation.issueStatusOther.split(',') : []}
- onChangeItems={(selectedItems) =>
- setTransformation({
- ...transformation,
- issueStatusOther: selectedItems.join(','),
- })
- }
- />
- </FormGroup>
- </div>
- </div>
- </div>
- <Divider />
- {/* CI/CD */}
- <S.CICD>
- <h2>CI/CD</h2>
- <h3>
- <span>Deployment</span>
- <Tag minimal intent={Intent.PRIMARY} style={{ marginLeft: 8 }}>
- DORA
- </Tag>
- </h3>
- <p style={{ marginBottom: 16 }}>
- Use Regular Expression to define Deployments in DevLake in order to
measure DORA metrics.{' '}
- <ExternalLink
link="https://devlake.apache.org/docs/Configuration/GitHub#step-3---adding-transformation-rules-optional">
- Learn more
- </ExternalLink>
- </p>
- <div className="text">
- <Checkbox disabled checked />
- <span>Convert a BitBucket Deployment to a DevLake Deployment </span>
- <HelpTooltip content={<img src={ExampleJpg} alt="" width={400} />} />
- </div>
- <div className="text">
- <Checkbox checked={useCustom} onChange={handleChangeUseCustom} />
- <span>
- Convert a BitBucket Pipeline to a DevLake Deployment when its
branch/tag name or one of its pipeline steps’
- names
- </span>
- </div>
- <div className="sub-text">
- <span>matches</span>
- <InputGroup
- style={{ width: 200, margin: '0 8px' }}
- placeholder="(deploy|push-image)"
- value={transformation.deploymentPattern ?? ''}
- onChange={(e) =>
- setTransformation({
- ...transformation,
- deploymentPattern: e.target.value,
- productionPattern: !e.target.value ? '' :
transformation.productionPattern,
- })
- }
- />
- <span>.</span>
- <HelpTooltip content="View your BitBucket Pipelines:
https://support.atlassian.com/bitbucket-cloud/docs/view-your-pipeline/" />
- </div>
- <div className="sub-text">
- <span>If the name also matches</span>
- <InputGroup
- style={{ width: 200, margin: '0 8px' }}
- placeholder="prod(.*)"
- value={transformation.productionPattern ?? ''}
- onChange={(e) =>
- setTransformation({
- ...transformation,
- productionPattern: e.target.value,
- })
- }
- />
- <span>, this Deployment is a ‘Production Deployment’</span>
- <HelpTooltip content="If you leave this field empty, all Deployments
will be tagged as in the Production environment. " />
+ <Divider />
</div>
- </S.CICD>
+ )}
+ {entities.includes('CICD') && (
+ <S.CICD>
+ <h2>CI/CD</h2>
+ <h3>
+ <span>Deployment</span>
+ <Tag minimal intent={Intent.PRIMARY} style={{ marginLeft: 8 }}>
+ DORA
+ </Tag>
+ </h3>
+ <p style={{ marginBottom: 16 }}>
+ Use Regular Expression to define Deployments in DevLake in order
to measure DORA metrics.{' '}
+ <ExternalLink
link="https://devlake.apache.org/docs/Configuration/GitHub#step-3---adding-transformation-rules-optional">
+ Learn more
+ </ExternalLink>
+ </p>
+ <div className="text">
+ <Checkbox disabled checked />
+ <span>Convert a BitBucket Deployment to a DevLake Deployment
</span>
+ <HelpTooltip content={<img src={ExampleJpg} alt="" width={400} />}
/>
+ </div>
+ <div className="text">
+ <Checkbox checked={useCustom} onChange={handleChangeUseCustom} />
+ <span>
+ Convert a BitBucket Pipeline to a DevLake Deployment when its
branch/tag name or one of its pipeline
+ steps’ names
+ </span>
+ </div>
+ <div className="sub-text">
+ <span>matches</span>
+ <InputGroup
+ style={{ width: 200, margin: '0 8px' }}
+ placeholder="(deploy|push-image)"
+ value={transformation.deploymentPattern ?? ''}
+ onChange={(e) =>
+ setTransformation({
+ ...transformation,
+ deploymentPattern: e.target.value,
+ productionPattern: !e.target.value ? '' :
transformation.productionPattern,
+ })
+ }
+ />
+ <span>.</span>
+ <HelpTooltip content="View your BitBucket Pipelines:
https://support.atlassian.com/bitbucket-cloud/docs/view-your-pipeline/" />
+ </div>
+ <div className="sub-text">
+ <span>If the name also matches</span>
+ <InputGroup
+ style={{ width: 200, margin: '0 8px' }}
+ placeholder="prod(.*)"
+ value={transformation.productionPattern ?? ''}
+ onChange={(e) =>
+ setTransformation({
+ ...transformation,
+ productionPattern: e.target.value,
+ })
+ }
+ />
+ <span>, this Deployment is a ‘Production Deployment’</span>
+ <HelpTooltip content="If you leave this field empty, all
Deployments will be tagged as in the Production environment. " />
+ </div>
+ </S.CICD>
+ )}
</S.Transformation>
);
};
diff --git a/config-ui/src/plugins/register/github/transformation.tsx
b/config-ui/src/plugins/register/github/transformation.tsx
index e8762e588..082725be3 100644
--- a/config-ui/src/plugins/register/github/transformation.tsx
+++ b/config-ui/src/plugins/register/github/transformation.tsx
@@ -24,11 +24,12 @@ import { ExternalLink, HelpTooltip, Divider } from
'@/components';
import * as S from './styled';
interface Props {
+ entities: string[];
transformation: any;
setTransformation: React.Dispatch<React.SetStateAction<any>>;
}
-export const GitHubTransformation = ({ transformation, setTransformation }:
Props) => {
+export const GitHubTransformation = ({ entities, transformation,
setTransformation }: Props) => {
const [enableCICD, setEnableCICD] = useState(true);
useEffect(() => {
@@ -53,290 +54,295 @@ export const GitHubTransformation = ({ transformation,
setTransformation }: Prop
return (
<S.Transformation>
- {/* Issue Tracking */}
- <div className="issue-tracking">
- <h2>Issue Tracking</h2>
- <p>
- Tell DevLake what your issue labels mean to view metrics such as{' '}
- <ExternalLink
link="https://devlake.apache.org/docs/Metrics/BugAge">Bug Age</ExternalLink>,{'
'}
- <ExternalLink link="https://devlake.apache.org/docs/Metrics/MTTR">
- DORA - Median Time to Restore Service
- </ExternalLink>
- , etc.
- </p>
- <div className="issue-type">
- <div className="title">
- <span>Issue Type</span>
- <HelpTooltip content="DevLake defines three standard types of
issues: FEATURE, BUG and INCIDENT. Set your issues to these three types with
issue labels that match the RegEx." />
- </div>
- <div className="list">
- <FormGroup inline label="Requirement">
- <InputGroup
- placeholder="(feat|feature|proposal|requirement)"
- value={transformation.issueTypeRequirement ?? ''}
- onChange={(e) =>
- setTransformation({
- ...transformation,
- issueTypeRequirement: e.target.value,
- })
- }
- />
- </FormGroup>
- <FormGroup inline label="Bug">
- <InputGroup
- placeholder="(bug|broken)"
- value={transformation.issueTypeBug ?? ''}
- onChange={(e) =>
- setTransformation({
- ...transformation,
- issueTypeBug: e.target.value,
- })
- }
- />
- </FormGroup>
- <FormGroup
- inline
- label={
- <span>
- Incident
- <Tag minimal intent={Intent.PRIMARY} style={{ marginLeft: 4
}}>
- DORA
- </Tag>
- </span>
- }
- >
- <InputGroup
- placeholder="(incident|failure)"
- value={transformation.issueTypeIncident ?? ''}
- onChange={(e) =>
- setTransformation({
- ...transformation,
- issueTypeIncident: e.target.value,
- })
+ {entities.includes('TICKET') && (
+ <div className="issue-tracking">
+ <h2>Issue Tracking</h2>
+ <p>
+ Tell DevLake what your issue labels mean to view metrics such as{'
'}
+ <ExternalLink
link="https://devlake.apache.org/docs/Metrics/BugAge">Bug Age</ExternalLink>,{'
'}
+ <ExternalLink link="https://devlake.apache.org/docs/Metrics/MTTR">
+ DORA - Median Time to Restore Service
+ </ExternalLink>
+ , etc.
+ </p>
+ <div className="issue-type">
+ <div className="title">
+ <span>Issue Type</span>
+ <HelpTooltip content="DevLake defines three standard types of
issues: FEATURE, BUG and INCIDENT. Set your issues to these three types with
issue labels that match the RegEx." />
+ </div>
+ <div className="list">
+ <FormGroup inline label="Requirement">
+ <InputGroup
+ placeholder="(feat|feature|proposal|requirement)"
+ value={transformation.issueTypeRequirement ?? ''}
+ onChange={(e) =>
+ setTransformation({
+ ...transformation,
+ issueTypeRequirement: e.target.value,
+ })
+ }
+ />
+ </FormGroup>
+ <FormGroup inline label="Bug">
+ <InputGroup
+ placeholder="(bug|broken)"
+ value={transformation.issueTypeBug ?? ''}
+ onChange={(e) =>
+ setTransformation({
+ ...transformation,
+ issueTypeBug: e.target.value,
+ })
+ }
+ />
+ </FormGroup>
+ <FormGroup
+ inline
+ label={
+ <span>
+ Incident
+ <Tag minimal intent={Intent.PRIMARY} style={{ marginLeft:
4 }}>
+ DORA
+ </Tag>
+ </span>
}
- />
- </FormGroup>
+ >
+ <InputGroup
+ placeholder="(incident|failure)"
+ value={transformation.issueTypeIncident ?? ''}
+ onChange={(e) =>
+ setTransformation({
+ ...transformation,
+ issueTypeIncident: e.target.value,
+ })
+ }
+ />
+ </FormGroup>
+ </div>
</div>
- </div>
- <FormGroup
- inline
- label={
- <>
- <span>Issue Priority</span>
- <HelpTooltip content="Labels that match the RegEx will be set as
the priority of an issue." />
- </>
- }
- >
- <InputGroup
- placeholder="(highest|high|medium|low|p0|p1|p2|p3)"
- value={transformation.issuePriority ?? ''}
- onChange={(e) =>
- setTransformation({
- ...transformation,
- issuePriority: e.target.value,
- })
+ <FormGroup
+ inline
+ label={
+ <>
+ <span>Issue Priority</span>
+ <HelpTooltip content="Labels that match the RegEx will be set
as the priority of an issue." />
+ </>
}
- />
- </FormGroup>
- <FormGroup
- inline
- label={
- <>
- <span>Issue Component</span>
- <HelpTooltip content="Labels that match the RegEx will be set as
the component of an issue." />
- </>
- }
- >
- <InputGroup
- placeholder="component(.*)"
- value={transformation.issueComponent ?? ''}
- onChange={(e) =>
- setTransformation({
- ...transformation,
- issueComponent: e.target.value,
- })
+ >
+ <InputGroup
+ placeholder="(highest|high|medium|low|p0|p1|p2|p3)"
+ value={transformation.issuePriority ?? ''}
+ onChange={(e) =>
+ setTransformation({
+ ...transformation,
+ issuePriority: e.target.value,
+ })
+ }
+ />
+ </FormGroup>
+ <FormGroup
+ inline
+ label={
+ <>
+ <span>Issue Component</span>
+ <HelpTooltip content="Labels that match the RegEx will be set
as the component of an issue." />
+ </>
}
- />
- </FormGroup>
- <FormGroup
- inline
- label={
- <>
- <span>Issue Severity</span>
- <HelpTooltip content="Labels that match the RegEx will be set as
the serverity of an issue." />
- </>
- }
- >
- <InputGroup
- placeholder="severity(.*)"
- value={transformation.issueSeverity ?? ''}
- onChange={(e) =>
- setTransformation({
- ...transformation,
- issueSeverity: e.target.value,
- })
+ >
+ <InputGroup
+ placeholder="component(.*)"
+ value={transformation.issueComponent ?? ''}
+ onChange={(e) =>
+ setTransformation({
+ ...transformation,
+ issueComponent: e.target.value,
+ })
+ }
+ />
+ </FormGroup>
+ <FormGroup
+ inline
+ label={
+ <>
+ <span>Issue Severity</span>
+ <HelpTooltip content="Labels that match the RegEx will be set
as the serverity of an issue." />
+ </>
}
- />
- </FormGroup>
- </div>
- <Divider />
- {/* CI/CD */}
- <S.CICD>
- <h2>CI/CD</h2>
- <h3>
- <span>Deployment</span>
- <Tag minimal intent={Intent.PRIMARY} style={{ marginLeft: 8 }}>
- DORA
- </Tag>
- <div className="switch">
- <span>Enable</span>
- <Switch alignIndicator="right" inline checked={enableCICD}
onChange={handleChangeEnableCICD} />
- </div>
- </h3>
- {enableCICD && (
- <>
- <p>
- Use Regular Expression to define Deployments in DevLake in order
to measure DORA metrics.{' '}
- <ExternalLink
link="https://devlake.apache.org/docs/Configuration/GitHub#step-3---adding-transformation-rules-optional">
- Learn more
- </ExternalLink>
- </p>
- <div style={{ marginTop: 16 }}>Convert a GitHub Workflow run as a
DevLake Deployment when: </div>
- <div className="text">
- <span>
- The name of the <strong>GitHub workflow run</strong> or
<strong>one of its jobs</strong> matches
- </span>
- <InputGroup
- style={{ width: 200, margin: '0 8px' }}
- placeholder="(deploy|push-image)"
- value={transformation.deploymentPattern ?? ''}
- onChange={(e) =>
- setTransformation({
- ...transformation,
- deploymentPattern: e.target.value,
- productionPattern: !e.target.value ? '' :
transformation.productionPattern,
- })
- }
- />
- <i style={{ color: '#E34040' }}>*</i>
- <HelpTooltip content="GitHub Workflow Runs:
https://docs.github.com/en/actions/managing-workflow-runs/manually-running-a-workflow"
/>
- </div>
- <div className="text">
- <span>If the name also matches</span>
- <InputGroup
- style={{ width: 200, margin: '0 8px' }}
- disabled={!transformation.deploymentPattern}
- placeholder="prod(.*)"
- value={transformation.productionPattern ?? ''}
- onChange={(e) =>
- setTransformation({
- ...transformation,
- productionPattern: e.target.value,
- })
- }
- />
- <span>, this Deployment is a ‘Production Deployment’</span>
- <HelpTooltip content="If you leave this field empty, all DevLake
Deployments will be tagged as in the Production environment. " />
+ >
+ <InputGroup
+ placeholder="severity(.*)"
+ value={transformation.issueSeverity ?? ''}
+ onChange={(e) =>
+ setTransformation({
+ ...transformation,
+ issueSeverity: e.target.value,
+ })
+ }
+ />
+ </FormGroup>
+ <Divider />
+ </div>
+ )}
+ {entities.includes('CICD') && (
+ <S.CICD>
+ <h2>CI/CD</h2>
+ <h3>
+ <span>Deployment</span>
+ <Tag minimal intent={Intent.PRIMARY} style={{ marginLeft: 8 }}>
+ DORA
+ </Tag>
+ <div className="switch">
+ <span>Enable</span>
+ <Switch alignIndicator="right" inline checked={enableCICD}
onChange={handleChangeEnableCICD} />
</div>
- </>
- )}
- </S.CICD>
- <Divider />
- {/* Code Review */}
- <div>
- <h2>Code Review</h2>
- <p>
- If you use labels to identify types and components of pull requests,
use the following RegExes to extract them
- into corresponding columns.{' '}
- <ExternalLink
link="https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema#pull_requests">
- Learn More
- </ExternalLink>
- </p>
- <FormGroup
- inline
- label={
- <>
- <span>PR Type</span>
- <HelpTooltip content="Labels that match the RegEx will be set as
the type of a pull request." />
- </>
- }
- >
- <InputGroup
- placeholder="type(.*)$"
- value={transformation.prType ?? ''}
- onChange={(e) => setTransformation({ ...transformation, prType:
e.target.value })}
- />
- </FormGroup>
- <FormGroup
- inline
- label={
+ </h3>
+ {enableCICD && (
<>
- <span>PR Component</span>
- <HelpTooltip content="Labels that match the RegEx will be set as
the component of a pull request." />
+ <p>
+ Use Regular Expression to define Deployments in DevLake in
order to measure DORA metrics.{' '}
+ <ExternalLink
link="https://devlake.apache.org/docs/Configuration/GitHub#step-3---adding-transformation-rules-optional">
+ Learn more
+ </ExternalLink>
+ </p>
+ <div style={{ marginTop: 16 }}>Convert a GitHub Workflow run as
a DevLake Deployment when: </div>
+ <div className="text">
+ <span>
+ The name of the <strong>GitHub workflow run</strong> or
<strong>one of its jobs</strong> matches
+ </span>
+ <InputGroup
+ style={{ width: 200, margin: '0 8px' }}
+ placeholder="(deploy|push-image)"
+ value={transformation.deploymentPattern ?? ''}
+ onChange={(e) =>
+ setTransformation({
+ ...transformation,
+ deploymentPattern: e.target.value,
+ productionPattern: !e.target.value ? '' :
transformation.productionPattern,
+ })
+ }
+ />
+ <i style={{ color: '#E34040' }}>*</i>
+ <HelpTooltip content="GitHub Workflow Runs:
https://docs.github.com/en/actions/managing-workflow-runs/manually-running-a-workflow"
/>
+ </div>
+ <div className="text">
+ <span>If the name also matches</span>
+ <InputGroup
+ style={{ width: 200, margin: '0 8px' }}
+ disabled={!transformation.deploymentPattern}
+ placeholder="prod(.*)"
+ value={transformation.productionPattern ?? ''}
+ onChange={(e) =>
+ setTransformation({
+ ...transformation,
+ productionPattern: e.target.value,
+ })
+ }
+ />
+ <span>, this Deployment is a ‘Production Deployment’</span>
+ <HelpTooltip content="If you leave this field empty, all
DevLake Deployments will be tagged as in the Production environment. " />
+ </div>
</>
- }
- >
- <InputGroup
- placeholder="component(.*)$"
- value={transformation.prComponent ?? ''}
- onChange={(e) =>
- setTransformation({
- ...transformation,
- prComponent: e.target.value,
- })
+ )}
+ <Divider />
+ </S.CICD>
+ )}
+ {entities.includes('CODEREVIEW') && (
+ <div>
+ <h2>Code Review</h2>
+ <p>
+ If you use labels to identify types and components of pull
requests, use the following RegExes to extract
+ them into corresponding columns.{' '}
+ <ExternalLink
link="https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema#pull_requests">
+ Learn More
+ </ExternalLink>
+ </p>
+ <FormGroup
+ inline
+ label={
+ <>
+ <span>PR Type</span>
+ <HelpTooltip content="Labels that match the RegEx will be set
as the type of a pull request." />
+ </>
}
- />
- </FormGroup>
- </div>
- <Divider />
- {/* Cross-domain */}
- <div>
- <h2>Cross-domain</h2>
- <p>
- Connect entities across domains to measure metrics such as{' '}
- <ExternalLink
link="https://devlake.apache.org/docs/Metrics/BugCountPer1kLinesOfCode">
- Bug Count per 1k Lines of Code
- </ExternalLink>
- .
- </p>
- <FormGroup
- inline
- label={
- <div className="label">
- <span>Connect PRs and Issues</span>
- <HelpTooltip
- content={
- <>
- <div>
- <Icon icon="tick-circle" size={12} color={Colors.GREEN4}
style={{ marginRight: '4px' }} />
- Example 1: PR #321 body contains "<strong>Closes
#1234</strong>" (PR #321 and issue #1234 will be
- mapped by the following RegEx)
- </div>
- <div>
- <Icon icon="delete" size={12} color={Colors.RED4}
style={{ marginRight: '4px' }} />
- Example 2: PR #321 body contains "<strong>Related to
#1234</strong>" (PR #321 and issue #1234 will
- NOT be mapped by the following RegEx)
- </div>
- </>
- }
- />
- </div>
- }
- >
- <TextArea
- 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({
- ...transformation,
- prBodyClosePattern: e.target.value,
- })
+ >
+ <InputGroup
+ placeholder="type(.*)$"
+ value={transformation.prType ?? ''}
+ onChange={(e) => setTransformation({ ...transformation, prType:
e.target.value })}
+ />
+ </FormGroup>
+ <FormGroup
+ inline
+ label={
+ <>
+ <span>PR Component</span>
+ <HelpTooltip content="Labels that match the RegEx will be set
as the component of a pull request." />
+ </>
+ }
+ >
+ <InputGroup
+ placeholder="component(.*)$"
+ value={transformation.prComponent ?? ''}
+ onChange={(e) =>
+ setTransformation({
+ ...transformation,
+ prComponent: e.target.value,
+ })
+ }
+ />
+ </FormGroup>
+ <Divider />
+ </div>
+ )}
+ {entities.includes('CROSS') && (
+ <div>
+ <h2>Cross-domain</h2>
+ <p>
+ Connect entities across domains to measure metrics such as{' '}
+ <ExternalLink
link="https://devlake.apache.org/docs/Metrics/BugCountPer1kLinesOfCode">
+ Bug Count per 1k Lines of Code
+ </ExternalLink>
+ .
+ </p>
+ <FormGroup
+ inline
+ label={
+ <div className="label">
+ <span>Connect PRs and Issues</span>
+ <HelpTooltip
+ content={
+ <>
+ <div>
+ <Icon icon="tick-circle" size={12}
color={Colors.GREEN4} style={{ marginRight: '4px' }} />
+ Example 1: PR #321 body contains "<strong>Closes
#1234</strong>" (PR #321 and issue #1234 will
+ be mapped by the following RegEx)
+ </div>
+ <div>
+ <Icon icon="delete" size={12} color={Colors.RED4}
style={{ marginRight: '4px' }} />
+ Example 2: PR #321 body contains "<strong>Related to
#1234</strong>" (PR #321 and issue #1234
+ will NOT be mapped by the following RegEx)
+ </div>
+ </>
+ }
+ />
+ </div>
}
- fill
- rows={2}
- />
- </FormGroup>
- </div>
+ >
+ <TextArea
+ 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({
+ ...transformation,
+ prBodyClosePattern: e.target.value,
+ })
+ }
+ fill
+ rows={2}
+ />
+ </FormGroup>
+ <Divider />
+ </div>
+ )}
</S.Transformation>
);
};
diff --git a/config-ui/src/plugins/register/gitlab/transformation.tsx
b/config-ui/src/plugins/register/gitlab/transformation.tsx
index 78a1ecd31..864f380bf 100644
--- a/config-ui/src/plugins/register/gitlab/transformation.tsx
+++ b/config-ui/src/plugins/register/gitlab/transformation.tsx
@@ -19,16 +19,17 @@
import React, { useState, useEffect } from 'react';
import { Tag, Intent, Switch, InputGroup } from '@blueprintjs/core';
-import { ExternalLink, HelpTooltip } from '@/components';
+import { ExternalLink, HelpTooltip, Divider } from '@/components';
import * as S from './styled';
interface Props {
+ entities: string[];
transformation: any;
setTransformation: React.Dispatch<React.SetStateAction<any>>;
}
-export const GitLabTransformation = ({ transformation, setTransformation }:
Props) => {
+export const GitLabTransformation = ({ entities, transformation,
setTransformation }: Props) => {
const [enableCICD, setEnableCICD] = useState(true);
useEffect(() => {
@@ -53,66 +54,69 @@ export const GitLabTransformation = ({ transformation,
setTransformation }: Prop
return (
<S.Transformation>
- <S.CICD>
- <h2>CI/CD</h2>
- <h3>
- <span>Deployment</span>
- <Tag minimal intent={Intent.PRIMARY} style={{ marginLeft: 8 }}>
- DORA
- </Tag>
- <div className="switch">
- <span>Enable</span>
- <Switch alignIndicator="right" inline checked={enableCICD}
onChange={handleChangeCICDEnable} />
- </div>
- </h3>
- {enableCICD && (
- <>
- <p>
- Use Regular Expression to define Deployments in DevLake in order
to measure DORA metrics.{' '}
- <ExternalLink
link="https://devlake.apache.org/docs/Configuration/GitHub#step-3---adding-transformation-rules-optional">
- Learn more
- </ExternalLink>
- </p>
- <div style={{ marginTop: 16 }}>Convert a GitLab Pipeline as a
DevLake Deployment when: </div>
- <div className="text">
- <span>
- The name of the <strong>GitLab pipeline</strong> or
<strong>one of its jobs</strong> matches
- </span>
- <InputGroup
- style={{ width: 200, margin: '0 8px' }}
- placeholder="(deploy|push-image)"
- value={transformation.deploymentPattern ?? ''}
- onChange={(e) =>
- setTransformation({
- ...transformation,
- deploymentPattern: e.target.value,
- productionPattern: !e.target.value ? '' :
transformation.productionPattern,
- })
- }
- />
- <i style={{ color: '#E34040' }}>*</i>
- <HelpTooltip content="GitLab Pipelines:
https://docs.gitlab.com/ee/ci/pipelines/" />
+ {entities.includes('CICD') && (
+ <S.CICD>
+ <h2>CI/CD</h2>
+ <h3>
+ <span>Deployment</span>
+ <Tag minimal intent={Intent.PRIMARY} style={{ marginLeft: 8 }}>
+ DORA
+ </Tag>
+ <div className="switch">
+ <span>Enable</span>
+ <Switch alignIndicator="right" inline checked={enableCICD}
onChange={handleChangeCICDEnable} />
</div>
- <div className="text">
- <span>If the name also matches</span>
- <InputGroup
- style={{ width: 200, margin: '0 8px' }}
- disabled={!transformation.deploymentPattern}
- placeholder="prod(.*)"
- value={transformation.productionPattern ?? ''}
- onChange={(e) =>
- setTransformation({
- ...transformation,
- productionPattern: e.target.value,
- })
- }
- />
- <span>, this Deployment is a ‘Production Deployment’</span>
- <HelpTooltip content="If you leave this field empty, all DevLake
Deployments will be tagged as in the Production environment. " />
- </div>
- </>
- )}
- </S.CICD>
+ </h3>
+ {enableCICD && (
+ <>
+ <p>
+ Use Regular Expression to define Deployments in DevLake in
order to measure DORA metrics.{' '}
+ <ExternalLink
link="https://devlake.apache.org/docs/Configuration/GitHub#step-3---adding-transformation-rules-optional">
+ Learn more
+ </ExternalLink>
+ </p>
+ <div style={{ marginTop: 16 }}>Convert a GitLab Pipeline as a
DevLake Deployment when: </div>
+ <div className="text">
+ <span>
+ The name of the <strong>GitLab pipeline</strong> or
<strong>one of its jobs</strong> matches
+ </span>
+ <InputGroup
+ style={{ width: 200, margin: '0 8px' }}
+ placeholder="(deploy|push-image)"
+ value={transformation.deploymentPattern ?? ''}
+ onChange={(e) =>
+ setTransformation({
+ ...transformation,
+ deploymentPattern: e.target.value,
+ productionPattern: !e.target.value ? '' :
transformation.productionPattern,
+ })
+ }
+ />
+ <i style={{ color: '#E34040' }}>*</i>
+ <HelpTooltip content="GitLab Pipelines:
https://docs.gitlab.com/ee/ci/pipelines/" />
+ </div>
+ <div className="text">
+ <span>If the name also matches</span>
+ <InputGroup
+ style={{ width: 200, margin: '0 8px' }}
+ disabled={!transformation.deploymentPattern}
+ placeholder="prod(.*)"
+ value={transformation.productionPattern ?? ''}
+ onChange={(e) =>
+ setTransformation({
+ ...transformation,
+ productionPattern: e.target.value,
+ })
+ }
+ />
+ <span>, this Deployment is a ‘Production Deployment’</span>
+ <HelpTooltip content="If you leave this field empty, all
DevLake Deployments will be tagged as in the Production environment. " />
+ </div>
+ </>
+ )}
+ <Divider />
+ </S.CICD>
+ )}
</S.Transformation>
);
};
diff --git a/config-ui/src/plugins/register/jenkins/transformation.tsx
b/config-ui/src/plugins/register/jenkins/transformation.tsx
index 01c9e0999..5ec2980ed 100644
--- a/config-ui/src/plugins/register/jenkins/transformation.tsx
+++ b/config-ui/src/plugins/register/jenkins/transformation.tsx
@@ -23,11 +23,12 @@ import * as S from './styled';
import { ExternalLink, HelpTooltip } from '@/components';
interface Props {
+ entities: string[];
transformation: any;
setTransformation: React.Dispatch<React.SetStateAction<any>>;
}
-export const JenkinsTransformation = ({ transformation, setTransformation }:
Props) => {
+export const JenkinsTransformation = ({ entities, transformation,
setTransformation }: Props) => {
const [enableCICD, setEnableCICD] = useState(true);
useEffect(() => {
@@ -52,66 +53,68 @@ export const JenkinsTransformation = ({ transformation,
setTransformation }: Pro
return (
<S.Transformation>
- <S.CICD>
- <h2>CI/CD</h2>
- <h3>
- <span>Deployment</span>
- <Tag minimal intent={Intent.PRIMARY} style={{ marginLeft: 8 }}>
- DORA
- </Tag>
- <div className="switch">
- <span>Enable</span>
- <Switch alignIndicator="right" inline checked={enableCICD}
onChange={handleChangeCICDEnable} />
- </div>
- </h3>
- {enableCICD && (
- <>
- <p>
- Use Regular Expression to define Deployments in DevLake in order
to measure DORA metrics.{' '}
- <ExternalLink
link="https://devlake.apache.org/docs/Configuration/GitHub#step-3---adding-transformation-rules-optional">
- Learn more
- </ExternalLink>
- </p>
- <div style={{ marginTop: 16 }}>Convert a Jenkins Build as a
DevLake Deployment when: </div>
- <div className="text">
- <span>
- The name of the <strong>Jenkins job</strong> or <strong>one of
its stages</strong> matches
- </span>
- <InputGroup
- style={{ width: 200, margin: '0 8px' }}
- placeholder="(deploy|push-image)"
- value={transformation.deploymentPattern ?? ''}
- onChange={(e) =>
- setTransformation({
- ...transformation,
- deploymentPattern: e.target.value,
- productionPattern: !e.target.value ? '' :
transformation.productionPattern,
- })
- }
- />
- <i style={{ color: '#E34040' }}>*</i>
- <HelpTooltip content="Jenkins Builds:
https://www.jenkins.io/doc/pipeline/steps/pipeline-build-step/" />
+ {entities.includes('CICD') && (
+ <S.CICD>
+ <h2>CI/CD</h2>
+ <h3>
+ <span>Deployment</span>
+ <Tag minimal intent={Intent.PRIMARY} style={{ marginLeft: 8 }}>
+ DORA
+ </Tag>
+ <div className="switch">
+ <span>Enable</span>
+ <Switch alignIndicator="right" inline checked={enableCICD}
onChange={handleChangeCICDEnable} />
</div>
- <div className="text">
- <span>If the name also matches</span>
- <InputGroup
- style={{ width: 120, margin: '0 8px' }}
- disabled={!transformation.deploymentPattern}
- placeholder="prod(.*)"
- value={transformation.productionPattern ?? ''}
- onChange={(e) =>
- setTransformation({
- ...transformation,
- productionPattern: e.target.value,
- })
- }
- />
- <span>, this Deployment is a ‘Production Deployment’</span>
- <HelpTooltip content="If you leave this field empty, all DevLake
Deployments will be tagged as in the Production environment. " />
- </div>
- </>
- )}
- </S.CICD>
+ </h3>
+ {enableCICD && (
+ <>
+ <p>
+ Use Regular Expression to define Deployments in DevLake in
order to measure DORA metrics.{' '}
+ <ExternalLink
link="https://devlake.apache.org/docs/Configuration/GitHub#step-3---adding-transformation-rules-optional">
+ Learn more
+ </ExternalLink>
+ </p>
+ <div style={{ marginTop: 16 }}>Convert a Jenkins Build as a
DevLake Deployment when: </div>
+ <div className="text">
+ <span>
+ The name of the <strong>Jenkins job</strong> or <strong>one
of its stages</strong> matches
+ </span>
+ <InputGroup
+ style={{ width: 200, margin: '0 8px' }}
+ placeholder="(deploy|push-image)"
+ value={transformation.deploymentPattern ?? ''}
+ onChange={(e) =>
+ setTransformation({
+ ...transformation,
+ deploymentPattern: e.target.value,
+ productionPattern: !e.target.value ? '' :
transformation.productionPattern,
+ })
+ }
+ />
+ <i style={{ color: '#E34040' }}>*</i>
+ <HelpTooltip content="Jenkins Builds:
https://www.jenkins.io/doc/pipeline/steps/pipeline-build-step/" />
+ </div>
+ <div className="text">
+ <span>If the name also matches</span>
+ <InputGroup
+ style={{ width: 120, margin: '0 8px' }}
+ disabled={!transformation.deploymentPattern}
+ placeholder="prod(.*)"
+ value={transformation.productionPattern ?? ''}
+ onChange={(e) =>
+ setTransformation({
+ ...transformation,
+ productionPattern: e.target.value,
+ })
+ }
+ />
+ <span>, this Deployment is a ‘Production Deployment’</span>
+ <HelpTooltip content="If you leave this field empty, all
DevLake Deployments will be tagged as in the Production environment. " />
+ </div>
+ </>
+ )}
+ </S.CICD>
+ )}
</S.Transformation>
);
};
diff --git
a/config-ui/src/plugins/register/jira/transformation-fields/cross-domain.tsx
b/config-ui/src/plugins/register/jira/transformation-fields/cross-domain.tsx
index f27065ab8..6db672bd7 100644
--- a/config-ui/src/plugins/register/jira/transformation-fields/cross-domain.tsx
+++ b/config-ui/src/plugins/register/jira/transformation-fields/cross-domain.tsx
@@ -19,7 +19,7 @@
import { useState, useEffect } from 'react';
import { Radio, Icon, Collapse, InputGroup, Button, Intent } from
'@blueprintjs/core';
-import { ExternalLink, IconButton } from '@/components';
+import { ExternalLink, IconButton, Divider } from '@/components';
import JiraIssueTipsImg from '@/images/jira-issue-tips.png';
import * as S from './styled';
diff --git a/config-ui/src/plugins/register/jira/transformation.tsx
b/config-ui/src/plugins/register/jira/transformation.tsx
index 75cba06f3..1b264e4f3 100644
--- a/config-ui/src/plugins/register/jira/transformation.tsx
+++ b/config-ui/src/plugins/register/jira/transformation.tsx
@@ -35,12 +35,13 @@ enum StandardType {
}
interface Props {
+ entities: string[];
connectionId: ID;
transformation: any;
setTransformation: React.Dispatch<React.SetStateAction<any>>;
}
-export const JiraTransformation = ({ connectionId, transformation,
setTransformation }: Props) => {
+export const JiraTransformation = ({ entities, connectionId, transformation,
setTransformation }: Props) => {
const [requirements, setRequirements] = useState<string[]>([]);
const [bugs, setBugs] = useState<string[]>([]);
const [incidents, setIncidents] = useState<string[]>([]);
@@ -115,133 +116,137 @@ export const JiraTransformation = ({ connectionId,
transformation, setTransforma
return (
<S.TransformationWrapper>
- <div className="issue-tracking">
- <h2>Issue Tracking</h2>
- <p>
- Tell DevLake what types of Jira issues you are using as features,
bugs and incidents, and what field as `Epic
- Link` or `Story Points`.
- </p>
- <div className="issue-type">
- <div className="title">
- <span>Issue Type</span>
- <Popover2
- position="top"
- content={
- <div style={{ padding: '8px 12px', color: '#ffffff',
backgroundColor: 'rgba(0,0,0,.8)' }}>
- DevLake defines three standard types of issues: FEATURE, BUG
and INCIDENT. Standardize your Jira issue
- types to these three types so that DevLake can calculate
metrics such as{' '}
- <ExternalLink
link="https://devlake.apache.org/docs/Metrics/RequirementLeadTime">
- Requirement Lead Time
- </ExternalLink>
- , <ExternalLink
link="https://devlake.apache.org/docs/Metrics/BugAge">Bug Age</ExternalLink>,
- <ExternalLink
link="https://devlake.apache.org/docs/Metrics/MTTR">
- DORA - Median Time to Restore Service
- </ExternalLink>
- , etc.
- </div>
- }
- >
- <Icon icon="help" size={12} color="#94959f" style={{ marginLeft:
4, cursor: 'pointer' }} />
- </Popover2>
- </div>
- <div className="list">
- <FormGroup inline label="Requirement">
- <MultiSelector
- items={issueTypes}
- disabledItems={[...bugItems, ...incidentItems]}
- getKey={(it) => it.id}
- getName={(it) => it.name}
- getIcon={(it) => it.iconUrl}
- selectedItems={requirementItems}
- onChangeItems={(selectedItems) =>
- setTransformation({
- ...transformation,
- typeMappings: {
- ...transformaType(selectedItems,
StandardType.Requirement),
- ...transformaType(bugItems, StandardType.Bug),
- ...transformaType(incidentItems, StandardType.Incident),
- },
- })
+ {entities.includes('TICKET') && (
+ <div className="issue-tracking">
+ <h2>Issue Tracking</h2>
+ <p>
+ Tell DevLake what types of Jira issues you are using as features,
bugs and incidents, and what field as
+ `Epic Link` or `Story Points`.
+ </p>
+ <div className="issue-type">
+ <div className="title">
+ <span>Issue Type</span>
+ <Popover2
+ position="top"
+ content={
+ <div style={{ padding: '8px 12px', color: '#ffffff',
backgroundColor: 'rgba(0,0,0,.8)' }}>
+ DevLake defines three standard types of issues: FEATURE,
BUG and INCIDENT. Standardize your Jira
+ issue types to these three types so that DevLake can
calculate metrics such as{' '}
+ <ExternalLink
link="https://devlake.apache.org/docs/Metrics/RequirementLeadTime">
+ Requirement Lead Time
+ </ExternalLink>
+ , <ExternalLink
link="https://devlake.apache.org/docs/Metrics/BugAge">Bug Age</ExternalLink>,
+ <ExternalLink
link="https://devlake.apache.org/docs/Metrics/MTTR">
+ DORA - Median Time to Restore Service
+ </ExternalLink>
+ , etc.
+ </div>
}
- />
- </FormGroup>
- <FormGroup inline label="Bug">
- <MultiSelector
- items={issueTypes}
- disabledItems={[...requirementItems, ...incidentItems]}
- getKey={(it) => it.id}
- getName={(it) => it.name}
- getIcon={(it) => it.iconUrl}
- selectedItems={bugItems}
- onChangeItems={(selectedItems) =>
- setTransformation({
- ...transformation,
- typeMappings: {
- ...transformaType(requirementItems,
StandardType.Requirement),
- ...transformaType(selectedItems, StandardType.Bug),
- ...transformaType(incidentItems, StandardType.Incident),
- },
- })
- }
- />
- </FormGroup>
- <FormGroup
- inline
- label={
- <>
- <span>Incident</span>
- <Tag intent={Intent.PRIMARY} style={{ marginLeft: 4 }}>
- DORA
- </Tag>
- </>
- }
- >
- <MultiSelector
- items={issueTypes}
- disabledItems={[...requirementItems, ...bugItems]}
- getKey={(it) => it.id}
- getName={(it) => it.name}
- getIcon={(it) => it.iconUrl}
- selectedItems={incidentItems}
- onChangeItems={(selectedItems) =>
- setTransformation({
- ...transformation,
- typeMappings: {
- ...transformaType(requirementItems,
StandardType.Requirement),
- ...transformaType(bugItems, StandardType.Bug),
- ...transformaType(selectedItems, StandardType.Incident),
- },
- })
+ >
+ <Icon icon="help" size={12} color="#94959f" style={{
marginLeft: 4, cursor: 'pointer' }} />
+ </Popover2>
+ </div>
+ <div className="list">
+ <FormGroup inline label="Requirement">
+ <MultiSelector
+ items={issueTypes}
+ disabledItems={[...bugItems, ...incidentItems]}
+ getKey={(it) => it.id}
+ getName={(it) => it.name}
+ getIcon={(it) => it.iconUrl}
+ selectedItems={requirementItems}
+ onChangeItems={(selectedItems) =>
+ setTransformation({
+ ...transformation,
+ typeMappings: {
+ ...transformaType(selectedItems,
StandardType.Requirement),
+ ...transformaType(bugItems, StandardType.Bug),
+ ...transformaType(incidentItems,
StandardType.Incident),
+ },
+ })
+ }
+ />
+ </FormGroup>
+ <FormGroup inline label="Bug">
+ <MultiSelector
+ items={issueTypes}
+ disabledItems={[...requirementItems, ...incidentItems]}
+ getKey={(it) => it.id}
+ getName={(it) => it.name}
+ getIcon={(it) => it.iconUrl}
+ selectedItems={bugItems}
+ onChangeItems={(selectedItems) =>
+ setTransformation({
+ ...transformation,
+ typeMappings: {
+ ...transformaType(requirementItems,
StandardType.Requirement),
+ ...transformaType(selectedItems, StandardType.Bug),
+ ...transformaType(incidentItems,
StandardType.Incident),
+ },
+ })
+ }
+ />
+ </FormGroup>
+ <FormGroup
+ inline
+ label={
+ <>
+ <span>Incident</span>
+ <Tag intent={Intent.PRIMARY} style={{ marginLeft: 4 }}>
+ DORA
+ </Tag>
+ </>
}
- />
- </FormGroup>
+ >
+ <MultiSelector
+ items={issueTypes}
+ disabledItems={[...requirementItems, ...bugItems]}
+ getKey={(it) => it.id}
+ getName={(it) => it.name}
+ getIcon={(it) => it.iconUrl}
+ selectedItems={incidentItems}
+ onChangeItems={(selectedItems) =>
+ setTransformation({
+ ...transformation,
+ typeMappings: {
+ ...transformaType(requirementItems,
StandardType.Requirement),
+ ...transformaType(bugItems, StandardType.Bug),
+ ...transformaType(selectedItems,
StandardType.Incident),
+ },
+ })
+ }
+ />
+ </FormGroup>
+ </div>
</div>
- </div>
- <FormGroup
- inline
- label={
- <>
- <span>Story Points</span>
- <HelpTooltip content="Choose the issue field you are using as
`Story Points`." />
- </>
- }
- >
- <Selector
- items={fields}
- getKey={(it) => it.id}
- getName={(it) => it.name}
- selectedItem={fields.find((it) => it.id ===
transformation.storyPointField)}
- onChangeItem={(selectedItem) =>
- setTransformation({
- ...transformation,
- storyPointField: selectedItem.id,
- })
+ <FormGroup
+ inline
+ label={
+ <>
+ <span>Story Points</span>
+ <HelpTooltip content="Choose the issue field you are using as
`Story Points`." />
+ </>
}
- />
- </FormGroup>
- </div>
- <Divider />
- <CrossDomain transformation={transformation}
setTransformation={setTransformation} />
+ >
+ <Selector
+ items={fields}
+ getKey={(it) => it.id}
+ getName={(it) => it.name}
+ selectedItem={fields.find((it) => it.id ===
transformation.storyPointField)}
+ onChangeItem={(selectedItem) =>
+ setTransformation({
+ ...transformation,
+ storyPointField: selectedItem.id,
+ })
+ }
+ />
+ </FormGroup>
+ <Divider />
+ </div>
+ )}
+ {entities.includes('CROSS') && (
+ <CrossDomain transformation={transformation}
setTransformation={setTransformation} />
+ )}
</S.TransformationWrapper>
);
};
diff --git a/config-ui/src/plugins/register/tapd/transformation.tsx
b/config-ui/src/plugins/register/tapd/transformation.tsx
index f488e5585..eb1bab91b 100644
--- a/config-ui/src/plugins/register/tapd/transformation.tsx
+++ b/config-ui/src/plugins/register/tapd/transformation.tsx
@@ -39,13 +39,14 @@ enum StandardStatus {
}
interface Props {
+ entities: string[];
connectionId: ID;
scopeId: ID;
transformation: any;
setTransformation: React.Dispatch<React.SetStateAction<any>>;
}
-export const TapdTransformation = ({ connectionId, scopeId, transformation,
setTransformation }: Props) => {
+export const TapdTransformation = ({ entities, connectionId, scopeId,
transformation, setTransformation }: Props) => {
const [featureTypeList, setFeatureTypeList] = useState<string[]>([]);
const [bugTypeList, setBugTypeList] = useState<string[]>([]);
const [incidentTypeList, setIncidentTypeList] = useState<string[]>([]);
@@ -130,168 +131,173 @@ export const TapdTransformation = ({ connectionId,
scopeId, transformation, setT
};
return (
<S.TransformationWrapper>
- {/* Issue Tracking */}
- <div className="issue-tracking">
- <h2>Issue Tracking</h2>
- <div className="issue-type">
- <div className="title">
- <span>Issue Type Mapping</span>
- <HelpTooltip content="Standardize your issue types to the
following issue types to view metrics such as `Requirement lead time` and `Bug
age` in built-in dashboards." />
- </div>
- <div className="list">
- <FormGroup inline label="Requirement">
- <MultiSelector
- items={typeList}
- disabledItems={typeList.filter((v) => [...bugTypeList,
...incidentTypeList].includes(v.id))}
- getKey={(it) => it.id}
- getName={(it) => it.name}
- selectedItems={typeList.filter((v) =>
featureTypeList.includes(v.id))}
- onChangeItems={(selectedItems) =>
- setTransformation({
- ...transformation,
- typeMappings: {
- ...transformaType(
- selectedItems.map((v) => v.id),
- StandardType.Requirement,
- ),
- ...transformaType(bugTypeList, StandardType.Bug),
- ...transformaType(incidentTypeList,
StandardType.Incident),
- },
- })
- }
- />
- </FormGroup>
- <FormGroup inline label="Bug">
- <MultiSelector
- items={typeList}
- disabledItems={typeList.filter((v) => [...featureTypeList,
...incidentTypeList].includes(v.id))}
- getKey={(it) => it.id}
- getName={(it) => it.name}
- selectedItems={typeList.filter((v) =>
bugTypeList.includes(v.id))}
- onChangeItems={(selectedItems) =>
- setTransformation({
- ...transformation,
- typeMappings: {
- ...transformaType(featureTypeList,
StandardType.Requirement),
- ...transformaType(
- selectedItems.map((v) => v.id),
- StandardType.Bug,
- ),
- ...transformaType(incidentTypeList,
StandardType.Incident),
- },
- })
+ {entities.includes('TICKET') && (
+ <div className="issue-tracking">
+ <h2>Issue Tracking</h2>
+ <div className="issue-type">
+ <div className="title">
+ <span>Issue Type Mapping</span>
+ <HelpTooltip content="Standardize your issue types to the
following issue types to view metrics such as `Requirement lead time` and `Bug
age` in built-in dashboards." />
+ </div>
+ <div className="list">
+ <FormGroup inline label="Requirement">
+ <MultiSelector
+ items={typeList}
+ disabledItems={typeList.filter((v) => [...bugTypeList,
...incidentTypeList].includes(v.id))}
+ getKey={(it) => it.id}
+ getName={(it) => it.name}
+ selectedItems={typeList.filter((v) =>
featureTypeList.includes(v.id))}
+ onChangeItems={(selectedItems) =>
+ setTransformation({
+ ...transformation,
+ typeMappings: {
+ ...transformaType(
+ selectedItems.map((v) => v.id),
+ StandardType.Requirement,
+ ),
+ ...transformaType(bugTypeList, StandardType.Bug),
+ ...transformaType(incidentTypeList,
StandardType.Incident),
+ },
+ })
+ }
+ />
+ </FormGroup>
+ <FormGroup inline label="Bug">
+ <MultiSelector
+ items={typeList}
+ disabledItems={typeList.filter((v) => [...featureTypeList,
...incidentTypeList].includes(v.id))}
+ getKey={(it) => it.id}
+ getName={(it) => it.name}
+ selectedItems={typeList.filter((v) =>
bugTypeList.includes(v.id))}
+ onChangeItems={(selectedItems) =>
+ setTransformation({
+ ...transformation,
+ typeMappings: {
+ ...transformaType(featureTypeList,
StandardType.Requirement),
+ ...transformaType(
+ selectedItems.map((v) => v.id),
+ StandardType.Bug,
+ ),
+ ...transformaType(incidentTypeList,
StandardType.Incident),
+ },
+ })
+ }
+ />
+ </FormGroup>
+ <FormGroup
+ inline
+ label={
+ <>
+ <span>Incident</span>
+ <Tag intent={Intent.PRIMARY} style={{ marginLeft: 4 }}>
+ DORA
+ </Tag>
+ </>
}
- />
- </FormGroup>
- <FormGroup
- inline
- label={
- <>
- <span>Incident</span>
- <Tag intent={Intent.PRIMARY} style={{ marginLeft: 4 }}>
- DORA
- </Tag>
- </>
- }
- >
- <MultiSelector
- items={typeList}
- disabledItems={typeList.filter((v) => [...featureTypeList,
...bugTypeList].includes(v.id))}
- getKey={(it) => it.id}
- getName={(it) => it.name}
- selectedItems={typeList.filter((v) =>
incidentTypeList.includes(v.id))}
- onChangeItems={(selectedItems) =>
- setTransformation({
- ...transformation,
- typeMappings: {
- ...transformaType(featureTypeList,
StandardType.Requirement),
- ...transformaType(bugTypeList, StandardType.Bug),
- ...transformaType(
- selectedItems.map((v) => v.id),
- StandardType.Incident,
- ),
- },
- })
- }
- />
- </FormGroup>
- </div>
- </div>
- <div className="issue-status">
- <div className="title">
- <span>Issue Status Mapping</span>
- <HelpTooltip content="Standardize your issue statuses to the
following issue statuses to view metrics such as `Requirement Delivery Rate` in
built-in dashboards." />
+ >
+ <MultiSelector
+ items={typeList}
+ disabledItems={typeList.filter((v) => [...featureTypeList,
...bugTypeList].includes(v.id))}
+ getKey={(it) => it.id}
+ getName={(it) => it.name}
+ selectedItems={typeList.filter((v) =>
incidentTypeList.includes(v.id))}
+ onChangeItems={(selectedItems) =>
+ setTransformation({
+ ...transformation,
+ typeMappings: {
+ ...transformaType(featureTypeList,
StandardType.Requirement),
+ ...transformaType(bugTypeList, StandardType.Bug),
+ ...transformaType(
+ selectedItems.map((v) => v.id),
+ StandardType.Incident,
+ ),
+ },
+ })
+ }
+ />
+ </FormGroup>
+ </div>
</div>
- <div className="list">
- <FormGroup inline label="TODO">
- <MultiSelector
- items={statusList}
- disabledItems={statusList.filter((v) =>
[...inProgressStatusList, ...doneStatusList].includes(v.name))}
- getKey={(it) => it.id}
- getName={(it) => it.name}
- selectedItems={statusList.filter((v) =>
todoStatusList.includes(v.name))}
- onChangeItems={(selectedItems) =>
- setTransformation({
- ...transformation,
- statusMappings: {
- ...transformaType(
- selectedItems.map((v) => v.name),
- StandardStatus.Todo,
- ),
- ...transformaType(inProgressStatusList,
StandardStatus.InProgress),
- ...transformaType(doneStatusList, StandardStatus.Done),
- },
- })
- }
- />
- </FormGroup>
- <FormGroup inline label="IN-PROGRESS">
- <MultiSelector
- items={statusList}
- disabledItems={statusList.filter((v) => [...todoStatusList,
...doneStatusList].includes(v.name))}
- getKey={(it) => it.id}
- getName={(it) => it.name}
- selectedItems={statusList.filter((v) =>
inProgressStatusList.includes(v.name))}
- onChangeItems={(selectedItems) =>
- setTransformation({
- ...transformation,
- statusMappings: {
- ...transformaType(todoStatusList, StandardStatus.Todo),
- ...transformaType(
- selectedItems.map((v) => v.name),
- StandardStatus.InProgress,
- ),
- ...transformaType(doneStatusList, StandardStatus.Done),
- },
- })
- }
- />
- </FormGroup>
- <FormGroup inline label="DONE">
- <MultiSelector
- items={statusList}
- disabledItems={statusList.filter((v) => [...todoStatusList,
...inProgressStatusList].includes(v.name))}
- getKey={(it) => it.id}
- getName={(it) => it.name}
- selectedItems={statusList.filter((v) =>
doneStatusList.includes(v.name))}
- onChangeItems={(selectedItems) =>
- setTransformation({
- ...transformation,
- statusMappings: {
- ...transformaType(todoStatusList, StandardStatus.Todo),
- ...transformaType(inProgressStatusList,
StandardStatus.InProgress),
- ...transformaType(
- selectedItems.map((v) => v.name),
- StandardStatus.Done,
- ),
- },
- })
- }
- />
- </FormGroup>
+ <div className="issue-status">
+ <div className="title">
+ <span>Issue Status Mapping</span>
+ <HelpTooltip content="Standardize your issue statuses to the
following issue statuses to view metrics such as `Requirement Delivery Rate` in
built-in dashboards." />
+ </div>
+ <div className="list">
+ <FormGroup inline label="TODO">
+ <MultiSelector
+ items={statusList}
+ disabledItems={statusList.filter((v) =>
+ [...inProgressStatusList,
...doneStatusList].includes(v.name),
+ )}
+ getKey={(it) => it.id}
+ getName={(it) => it.name}
+ selectedItems={statusList.filter((v) =>
todoStatusList.includes(v.name))}
+ onChangeItems={(selectedItems) =>
+ setTransformation({
+ ...transformation,
+ statusMappings: {
+ ...transformaType(
+ selectedItems.map((v) => v.name),
+ StandardStatus.Todo,
+ ),
+ ...transformaType(inProgressStatusList,
StandardStatus.InProgress),
+ ...transformaType(doneStatusList, StandardStatus.Done),
+ },
+ })
+ }
+ />
+ </FormGroup>
+ <FormGroup inline label="IN-PROGRESS">
+ <MultiSelector
+ items={statusList}
+ disabledItems={statusList.filter((v) => [...todoStatusList,
...doneStatusList].includes(v.name))}
+ getKey={(it) => it.id}
+ getName={(it) => it.name}
+ selectedItems={statusList.filter((v) =>
inProgressStatusList.includes(v.name))}
+ onChangeItems={(selectedItems) =>
+ setTransformation({
+ ...transformation,
+ statusMappings: {
+ ...transformaType(todoStatusList, StandardStatus.Todo),
+ ...transformaType(
+ selectedItems.map((v) => v.name),
+ StandardStatus.InProgress,
+ ),
+ ...transformaType(doneStatusList, StandardStatus.Done),
+ },
+ })
+ }
+ />
+ </FormGroup>
+ <FormGroup inline label="DONE">
+ <MultiSelector
+ items={statusList}
+ disabledItems={statusList.filter((v) =>
+ [...todoStatusList,
...inProgressStatusList].includes(v.name),
+ )}
+ getKey={(it) => it.id}
+ getName={(it) => it.name}
+ selectedItems={statusList.filter((v) =>
doneStatusList.includes(v.name))}
+ onChangeItems={(selectedItems) =>
+ setTransformation({
+ ...transformation,
+ statusMappings: {
+ ...transformaType(todoStatusList, StandardStatus.Todo),
+ ...transformaType(inProgressStatusList,
StandardStatus.InProgress),
+ ...transformaType(
+ selectedItems.map((v) => v.name),
+ StandardStatus.Done,
+ ),
+ },
+ })
+ }
+ />
+ </FormGroup>
+ </div>
</div>
</div>
- </div>
+ )}
</S.TransformationWrapper>
);
};