[improvement][semantic-fe] Adding batch operations for indicators/dimensions/models (#313)

* [improvement][semantic-fe] Add model alias setting & Add view permission restrictions to the model permission management tab.
[improvement][semantic-fe] Add permission control to the action buttons for the main domain; apply high sensitivity filtering to the authorization of metrics/dimensions.
[improvement][semantic-fe] Optimize the editing mode in the dimension/metric/datasource components to use the modelId stored in the database for data, instead of relying on the data from the state manager.

* [improvement][semantic-fe] Add time granularity setting in the data source configuration.

* [improvement][semantic-fe] Dictionary import for dimension values supported in Q&A visibility

* [improvement][semantic-fe] Modification of data source creation prompt wording"

* [improvement][semantic-fe] metric market experience optimization

* [improvement][semantic-fe] enhance the analysis of metric trends

* [improvement][semantic-fe] optimize the presentation of metric trend permissions

* [improvement][semantic-fe] add metric trend download functionality

* [improvement][semantic-fe] fix the dimension initialization issue in metric correlation

* [improvement][semantic-fe] Fix the issue of database changes not taking effect when creating based on an SQL data source.

* [improvement][semantic-fe] Optimizing pagination logic and some CSS styles

* [improvement][semantic-fe] Fixing the API for the indicator list by changing "current" to "pageNum"

* [improvement][semantic-fe] Fixing the default value setting for the indicator list

* [improvement][semantic-fe] Adding batch operations for indicators/dimensions/models
This commit is contained in:
tristanliu
2023-11-02 06:11:12 -05:00
committed by GitHub
parent 70784598e1
commit 9f813ca1c0
16 changed files with 1232 additions and 133 deletions

View File

@@ -145,7 +145,7 @@ const TrendChart: React.FC<Props> = ({
},
tooltip: {
trigger: 'axis',
formatter: function (params: any[]) {
formatter: function (params: any) {
const param = params[0];
const valueLabels = params
.map(

View File

@@ -0,0 +1,88 @@
import { Form, Input, Select, Space, Row, Col, Switch, Tag } from 'antd';
import StandardFormRow from '@/components/StandardFormRow';
import TagSelect from '@/components/TagSelect';
import React, { useEffect, useState, useRef } from 'react';
import { SENSITIVE_LEVEL_OPTIONS } from '../../constant';
import { SearchOutlined } from '@ant-design/icons';
import RemoteSelect, { RemoteSelectImperativeHandle } from '@/components/RemoteSelect';
import { queryDimValue } from '@/pages/SemanticModel/service';
import styles from '../style.less';
const FormItem = Form.Item;
type Props = {
dimensionOptions: { value: string; label: string }[];
modelId: number;
initFilterValues?: any;
onFiltersChange: (_: any, values: any) => void;
};
const MetricTrendDimensionFilter: React.FC<Props> = ({
dimensionOptions,
modelId,
initFilterValues = {},
onFiltersChange,
}) => {
// const [form] = Form.useForm();
const dimensionValueSearchRef = useRef<RemoteSelectImperativeHandle>();
const queryParams = useRef<{ dimensionBizName?: string }>({});
const [dimensionValue, setDimensionValue] = useState<string>('');
// const [queryParams, setQueryParams] = useState<any>({});
const loadSiteName = async (searchValue: string) => {
if (!queryParams.current?.dimensionBizName) {
// return [];
return;
}
const { dimensionBizName } = queryParams.current;
const { code, data } = await queryDimValue({
...queryParams.current,
value: searchValue,
modelId,
limit: 50,
});
if (code === 200 && Array.isArray(data?.resultList)) {
return data.resultList.slice(0, 50).map((item: any) => ({
value: item[dimensionBizName],
label: item[dimensionBizName],
}));
}
return [];
};
return (
<Space>
<Select
style={{ minWidth: 150 }}
options={dimensionOptions}
showSearch
filterOption={(input, option) =>
((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
}
allowClear
placeholder="请选择筛选维度"
onChange={(value) => {
queryParams.current = { dimensionBizName: value };
if (value) {
dimensionValueSearchRef.current?.emitSearch('');
}
}}
/>
<Tag color="processing" style={{ margin: 0 }}>
=
</Tag>
<RemoteSelect
value={dimensionValue}
onChange={(value) => {
setDimensionValue(value);
}}
ref={dimensionValueSearchRef}
style={{ minWidth: 150 }}
mode={undefined}
placeholder="维度值搜索"
fetchOptions={loadSiteName}
/>
</Space>
);
};
export default MetricTrendDimensionFilter;

View File

@@ -1,14 +1,25 @@
import React, { useState, useEffect, useRef } from 'react';
import { SemanticNodeType } from '../../enum';
import moment from 'moment';
import { message, Row, Col, Button } from 'antd';
import { queryStruct, downloadCosFile } from '@/pages/SemanticModel/service';
import { message, Row, Col, Button, Space, Select, Form } from 'antd';
import {
queryStruct,
downloadCosFile,
getDrillDownDimension,
getDimensionList,
} from '@/pages/SemanticModel/service';
import TrendChart from '@/pages/SemanticModel/Metric/components/MetricTrend';
import MetricTrendDimensionFilter from './MetricTrendDimensionFilter';
import MDatePicker from '@/components/MDatePicker';
import { useModel } from 'umi';
import { DateRangeType, DateSettingType } from '@/components/MDatePicker/type';
import StandardFormRow from '@/components/StandardFormRow';
import { ISemantic } from '../../data';
const FormItem = Form.Item;
type Props = {
nodeData: any;
[key: string]: any;
@@ -26,6 +37,10 @@ const MetricTrendSection: React.FC<Props> = ({ nodeData }) => {
const [metricColumnConfig, setMetricColumnConfig] = useState<ISemantic.IMetricTrendColumn>();
const [authMessage, setAuthMessage] = useState<string>('');
const [downloadLoding, setDownloadLoding] = useState<boolean>(false);
const [relationDimensionOptions, setRelationDimensionOptions] = useState<
{ value: string; label: string }[]
>([]);
const [queryParams, setQueryParams] = useState<any>({});
const [downloadBtnDisabledState, setDownloadBtnDisabledState] = useState<boolean>(true);
const [periodDate, setPeriodDate] = useState<{
startDate: string;
@@ -37,7 +52,8 @@ const MetricTrendSection: React.FC<Props> = ({ nodeData }) => {
dateField: dateFieldMap[DateRangeType.DAY],
});
const getMetricTrendData = async (download = false) => {
const getMetricTrendData = async (params: any = { download: false }) => {
const { download, dimensionGroup, dimensionFilters } = params;
if (download) {
setDownloadLoding(true);
} else {
@@ -49,6 +65,8 @@ const MetricTrendSection: React.FC<Props> = ({ nodeData }) => {
const res = await queryStruct({
modelId,
bizName,
groups: dimensionGroup,
dimensionFilters,
dateField: periodDate.dateField,
startDate: periodDate.startDate,
endDate: periodDate.endDate,
@@ -86,15 +104,103 @@ const MetricTrendSection: React.FC<Props> = ({ nodeData }) => {
}
};
const queryDimensionList = async (modelId: number) => {
const { code, data, msg } = await getDimensionList({ modelId });
if (code === 200 && Array.isArray(data?.list)) {
return data.list;
}
message.error(msg);
return [];
};
const queryDrillDownDimension = async (metricId: number) => {
const { code, data, msg } = await getDrillDownDimension(metricId);
if (code === 200 && Array.isArray(data)) {
return data;
}
message.error(msg);
return [];
};
const initDimensionData = async (metricItem: ISemantic.IMetricItem) => {
const dimensionList = await queryDimensionList(metricItem.modelId);
const drillDownDimension = await queryDrillDownDimension(metricItem.id);
const drillDownDimensionIds = drillDownDimension.map(
(item: ISemantic.IDrillDownDimensionItem) => item.dimensionId,
);
const drillDownDimensionList = dimensionList.filter((metricItem: ISemantic.IMetricItem) => {
return drillDownDimensionIds.includes(metricItem.id);
});
setRelationDimensionOptions(
drillDownDimensionList.map((item: ISemantic.IMetricItem) => {
return { label: item.name, value: item.bizName };
}),
);
};
useEffect(() => {
if (nodeData.id && nodeData?.nodeType === SemanticNodeType.METRIC) {
if (nodeData?.id && nodeData?.nodeType === SemanticNodeType.METRIC) {
getMetricTrendData();
initDimensionData(nodeData);
}
}, [nodeData, periodDate]);
return (
<>
<div style={{ marginBottom: 5 }}>
<div style={{ marginBottom: 5, display: 'grid', gap: 10 }}>
{/* <StandardFormRow key="showType" title="维度下钻" block>
<FormItem name="showType" valuePropName="checked">
<Select
style={{ minWidth: 150 }}
options={relationDimensionOptions}
showSearch
filterOption={(input, option) =>
((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
}
mode="multiple"
placeholder="请选择下钻维度"
onChange={(value) => {
const params = { ...queryParams, dimensionGroup: value || [] };
setQueryParams(params);
getMetricTrendData({ ...params });
}}
/>
</FormItem>
</StandardFormRow> */}
{/* <Row>
<Col flex="1 1 200px">
<Space>
<span>维度下钻: </span>
<Select
style={{ minWidth: 150 }}
options={relationDimensionOptions}
showSearch
filterOption={(input, option) =>
((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
}
mode="multiple"
placeholder="请选择下钻维度"
onChange={(value) => {
const params = { ...queryParams, dimensionGroup: value || [] };
setQueryParams(params);
getMetricTrendData({ ...params });
}}
/>
</Space>
</Col>
</Row>
<Row>
<Col flex="1 1 200px">
<Space>
<span>维度筛选: </span>
<MetricTrendDimensionFilter
modelId={nodeData.modelId}
dimensionOptions={relationDimensionOptions}
onFiltersChange={() => {}}
/>
</Space>
</Col>
</Row> */}
<Row>
<Col flex="1 1 200px">
<MDatePicker
@@ -126,6 +232,36 @@ const MetricTrendSection: React.FC<Props> = ({ nodeData }) => {
}}
disabledAdvanceSetting={true}
/>
{/* <Select
style={{ minWidth: 150 }}
options={relationDimensionOptions}
showSearch
filterOption={(input, option) =>
((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
}
mode="multiple"
placeholder="请选择下钻维度"
onChange={(value) => {
const params = { ...queryParams, dimensionGroup: value || [] };
setQueryParams(params);
getMetricTrendData({ ...params });
}}
/>
<Select
style={{ minWidth: 150 }}
options={relationDimensionOptions}
showSearch
filterOption={(input, option) =>
((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
}
mode="multiple"
placeholder="请选择筛选维度"
onChange={(value) => {
const params = { ...queryParams, dimensionFilters: value || [] };
setQueryParams(params);
getMetricTrendData({ ...params });
}}
/> */}
</Col>
<Col flex="0 1">
<Button
@@ -133,7 +269,7 @@ const MetricTrendSection: React.FC<Props> = ({ nodeData }) => {
loading={downloadLoding}
disabled={downloadBtnDisabledState}
onClick={() => {
getMetricTrendData(true);
getMetricTrendData({ download: true, ...queryParams });
}}
>

View File

@@ -1,17 +1,17 @@
import type { ActionType, ProColumns } from '@ant-design/pro-table';
import ProTable from '@ant-design/pro-table';
import { message, Space, Popconfirm, Tag, Spin } from 'antd';
import { message, Space, Popconfirm, Tag, Spin, Dropdown } from 'antd';
import React, { useRef, useState, useEffect } from 'react';
import type { Dispatch } from 'umi';
import { connect, history, useModel } from 'umi';
import type { StateType } from '../model';
import { SENSITIVE_LEVEL_ENUM } from '../constant';
import { queryMetric, deleteMetric } from '../service';
import { queryMetric, deleteMetric, batchUpdateMetricStatus } from '../service';
import MetricFilter from './components/MetricFilter';
import MetricInfoCreateForm from '../components/MetricInfoCreateForm';
import MetricCardList from './components/MetricCardList';
import NodeInfoDrawer from '../SemanticGraph/components/NodeInfoDrawer';
import { SemanticNodeType } from '../enum';
import { SemanticNodeType, StatusEnum } from '../enum';
import moment from 'moment';
import styles from './style.less';
import { ISemantic } from '../data';
@@ -46,6 +46,7 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
const [loading, setLoading] = useState<boolean>(false);
const [dataSource, setDataSource] = useState<ISemantic.IMetricItem[]>([]);
const [metricItem, setMetricItem] = useState<ISemantic.IMetricItem>();
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [filterParams, setFilterParams] = useState<Record<string, any>>({
showType: localStorage.getItem('metricMarketShowType') === '1' ? true : false,
});
@@ -56,6 +57,21 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
queryMetricList(filterParams);
}, []);
const queryBatchUpdateStatus = async (ids: React.Key[], status: StatusEnum) => {
if (Array.isArray(ids) && ids.length === 0) {
return;
}
const { code, msg } = await batchUpdateMetricStatus({
ids,
status,
});
if (code === 200) {
queryMetricList(filterParams);
return;
}
message.error(msg);
};
const queryMetricList = async (params: QueryMetricListParams = {}, disabledLoading = false) => {
if (!disabledLoading) {
setLoading(true);
@@ -155,6 +171,26 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
title: '敏感度',
valueEnum: SENSITIVE_LEVEL_ENUM,
},
{
dataIndex: 'status',
title: '状态',
width: 80,
search: false,
render: (status) => {
switch (status) {
case StatusEnum.ONLINE:
return <Tag color="success"></Tag>;
case StatusEnum.OFFLINE:
return <Tag color="warning"></Tag>;
case StatusEnum.INITIALIZED:
return <Tag color="processing"></Tag>;
case StatusEnum.DELETED:
return <Tag color="default"></Tag>;
default:
return <Tag color="default"></Tag>;
}
},
},
{
dataIndex: 'createdBy',
title: '创建人',
@@ -209,7 +245,14 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
</a>
<Popconfirm title="确认删除?" okText="是" cancelText="否" onConfirm={() => {}}>
<Popconfirm
title="确认删除?"
okText="是"
cancelText="否"
onConfirm={async () => {
deleteMetricQuery(record.id);
}}
>
<a
key="metricDeleteBtn"
onClick={() => {
@@ -244,6 +287,52 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
await queryMetricList(params, filterParams.key ? false : true);
};
const rowSelection = {
onChange: (selectedRowKeys: React.Key[]) => {
setSelectedRowKeys(selectedRowKeys);
},
getCheckboxProps: (record: ISemantic.IMetricItem) => ({
disabled: !record.hasAdminRes,
}),
};
const dropdownButtonItems = [
{
key: 'batchStart',
label: '批量启用',
},
{
key: 'batchStop',
label: '批量禁用',
},
{
key: 'batchDelete',
label: (
<Popconfirm
title="确定批量删除吗?"
onConfirm={() => {
queryBatchUpdateStatus(selectedRowKeys, StatusEnum.DELETED);
}}
>
<a></a>
</Popconfirm>
),
},
];
const onMenuClick = ({ key }: { key: string }) => {
switch (key) {
case 'batchStart':
queryBatchUpdateStatus(selectedRowKeys, StatusEnum.ONLINE);
break;
case 'batchStop':
queryBatchUpdateStatus(selectedRowKeys, StatusEnum.OFFLINE);
break;
default:
break;
}
};
return (
<>
<div className={styles.metricFilterWrapper}>
@@ -264,14 +353,14 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
<MetricCardList
metricList={dataSource}
disabledEdit={true}
onMetricChange={(metricItem) => {
onMetricChange={(metricItem: ISemantic.IMetricItem) => {
setInfoDrawerVisible(true);
setMetricItem(metricItem);
}}
onDeleteBtnClick={(metricItem) => {
onDeleteBtnClick={(metricItem: ISemantic.IMetricItem) => {
deleteMetricQuery(metricItem.id);
}}
onEditBtnClick={(metricItem) => {
onEditBtnClick={(metricItem: ISemantic.IMetricItem) => {
setMetricItem(metricItem);
setCreateModalVisible(true);
}}
@@ -289,6 +378,18 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
tableAlertRender={() => {
return false;
}}
rowSelection={{
type: 'checkbox',
...rowSelection,
}}
toolBarRender={() => [
<Dropdown.Button
key="ctrlBtnList"
menu={{ items: dropdownButtonItems, onClick: onMenuClick }}
>
</Dropdown.Button>,
]}
loading={loading}
onChange={(data: any) => {
const { current, pageSize, total } = data;