peng.peng 1 year ago
parent
commit
35054f3e90
  1. 26
      api/app/lib/controllers/dataQuality/index.js
  2. 62
      api/app/lib/models/quality_check_result.js
  3. 3
      api/app/lib/routes/dataQuality/index.js
  4. 24
      api/app/lib/routes/latestMetadata/index.js
  5. 14
      scripts/0.0.9/03.alter_t_quality_check_result.sql
  6. 2
      web/client/src/sections/dataQuality/actions/index.js
  7. 18
      web/client/src/sections/dataQuality/actions/qualityMonitor.js
  8. 191
      web/client/src/sections/dataQuality/containers/qualityMonitor.js
  9. 5
      web/client/src/sections/dataService/actions/serviceManagement.js
  10. 12
      web/client/src/sections/dataService/components/resourceModal.js
  11. 6
      web/client/src/sections/dataService/containers/serviceManagement.js
  12. 3
      web/client/src/utils/webapi.js

26
api/app/lib/controllers/dataQuality/index.js

@ -1,6 +1,6 @@
'use strict';
const moment = require('moment')
const { sequelize, Sequelize } = require('sequelize');
function getStandardDocFolders (opts) {
return async function (ctx, next) {
@ -404,7 +404,7 @@ function fetchFiles (opts) {
try {
const models = ctx.fs.dc.models;
const { folderId } = ctx.request.body;
let fileAll = await models.StandardDoc.findAll({})
let folderAll = await models.StandardDocFolder.findAll({})
@ -433,6 +433,25 @@ function fetchFiles (opts) {
}
}
function getQualityInspection (opts) {
return async function (ctx, next) {
const models = ctx.fs.dc.models;
const { } = ctx.query;
try {
let res = await models.QualityCheckResult.findAll() || []
ctx.status = 200;
ctx.body = res || [];
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = { message: '查询数据质检详情失败' }
}
}
}
module.exports = {
getStandardDocFolders,
postStandardDocFolders,
@ -443,5 +462,6 @@ module.exports = {
delBusinessRules,
getRegularBasis,
postFolderFile,
fetchFiles
fetchFiles,
getQualityInspection
}

62
api/app/lib/models/quality_check_result.js

@ -0,0 +1,62 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const QualityCheckResult = sequelize.define("qualityCheckResult", {
id: {
type: DataTypes.BIGINT,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: true,
field: "id",
autoIncrement: true,
unique: "t_quality_check_result_id_uindex"
},
database: {
type: DataTypes.TEXT,
allowNull: true,
defaultValue: null,
comment: null,
primaryKey: false,
field: "database",
autoIncrement: false,
},
mode: {
type: DataTypes.TEXT,
allowNull: true,
defaultValue: null,
comment: null,
primaryKey: false,
field: "mode",
autoIncrement: false
},
total: {
type: DataTypes.BIGINT,
allowNull: true,
defaultValue: null,
comment: null,
primaryKey: false,
field: "total",
autoIncrement: false
},
unexpected: {
type: DataTypes.BIGINT,
allowNull: true,
defaultValue: null,
comment: null,
primaryKey: false,
field: "unexpected",
autoIncrement: false
}
}, {
tableName: "t_quality_check_result",
comment: "",
indexes: []
});
dc.models.QualityCheckResult = QualityCheckResult;
return QualityCheckResult;
};

3
api/app/lib/routes/dataQuality/index.js

@ -34,4 +34,7 @@ module.exports = function (app, router, opts, AuthCode) {
app.fs.api.logAttr['POST/fetchFiles'] = { content: '获取文件夹下文件', visible: true };
router.post('/fetchFiles', model.fetchFiles(opts));
app.fs.api.logAttr['GET/quality-inspection'] = { content: '查询数据质检详情', visible: true };
router.get('/quality-inspection', model.getQualityInspection(opts));
};

24
api/app/lib/routes/latestMetadata/index.js

@ -175,19 +175,14 @@ module.exports = function (app, router, opts) {
}
});
async function release (apps, opts) {
const models = apps.fs.dc.models;
const list = await models.RestfulApi.findAll({
order: [["id", "desc"]],
include: [{
model: models.ResourceConsumption,
}],
distinct: true
}) || []
const list = await models.RestfulApi.findAll() || []
list.map(v => {
opts.exclude.push({ p: v.url, o: 'GET' });
router.get(v.url, async (ctx) => {
let message = "获取库表元数据列表失败"
@ -199,13 +194,22 @@ module.exports = function (app, router, opts) {
throw ''
} else {
let tokens
v.resourceConsumptions.map(s => {
let findOne = await models.RestfulApi.findOne({
where: { url: v.url, method: v.method },
order: [["id", "desc"]],
include: [{
model: models.ResourceConsumption,
}],
distinct: true
});
findOne && findOne.resourceConsumptions.map(s => {
if (!tokens && s.token) {
tokens = s.token
}
})
if (tokens && tokens == token) {
if (v.enabled) {
if (findOne.enabled) {
const pool = new Pool({
user: ctx.fs.dc.orm.config.username,
host: ctx.fs.dc.orm.config.host,

14
scripts/0.0.9/03.alter_t_quality_check_result.sql

@ -0,0 +1,14 @@
create table t_quality_check_result
(
id bigserial not null
constraint t_quality_check_result_pkey
primary key,
database text,
mode text,
total bigint,
unexpected bigint
);
create unique index idx_unique_db_mode
on t_quality_check_result (database, mode);

2
web/client/src/sections/dataQuality/actions/index.js

@ -3,9 +3,11 @@
import * as example from './example'
import * as documentLibrary from './documentLibrary'
import * as ruleLibrary from './ruleLibrary'
import * as qualityMonitor from './qualityMonitor'
export default {
...example,
...documentLibrary,
...ruleLibrary,
...qualityMonitor
}

18
web/client/src/sections/dataQuality/actions/qualityMonitor.js

@ -0,0 +1,18 @@
'use strict';
import { basicAction } from '@peace/utils'
import { ApiTable } from '$utils'
export function getQualityInspection (query = {}) {
return dispatch => basicAction({
type: 'get',
query,
dispatch: dispatch,
actionType: 'GET_QUALITY_INSPECT',
url: `${ApiTable.getQualityInspection}`,
msg: { error: '查询数据质检详情' },
reducer: { name: '' }
});
}

191
web/client/src/sections/dataQuality/containers/qualityMonitor.js

@ -13,15 +13,10 @@ function MyApplication ({ loading, clientHeight, actions, dispatch, user }) {
const { dataQuality } = actions
const [query, setQuery] = useState({ page: 0, limit: 10 });
const [proTableList, setProTableList] = useState({ rows: [], count: 0 });
const [massData, setMassData] = useState();
const [bistribution, setBistribution] = useState({})
useEffect(() => {
resourceData()
}, [])
let resourceData = () => {
dispatch(dataQuality.getBusinessRules({})).then(res => {
if (res.success) {
let data = res.payload.data?.rows
@ -34,7 +29,43 @@ function MyApplication ({ loading, clientHeight, actions, dispatch, user }) {
}
})
}
dispatch(dataQuality.getQualityInspection({})).then(res => {
if (res.success) {
let data = {
Consistency: [], //一致性
Accuracy: [], //准确性
Completeness: [], //完整性
Validity: [], //有效性
Timeliness: [], //及时性
Conformity: [], //规范性
}
res.payload.data?.map(s => {
data[s.mode].push({ total: Number(s.total), unexpected: Number(s.unexpected) })
})
let list = {
Consistency: {}, //一致性
Accuracy: {}, //准确性
Completeness: {}, //完整性
Validity: {}, //有效性
Timeliness: {}, //及时性
Conformity: {}, //规范性
}
for (let key in data) {
let total = 0
let unexpected = 0
data[key]?.map(d => {
total += d.total
unexpected += d.unexpected
})
list[key] = { total: total, unexpected: unexpected }
}
setMassData(list)
}
})
}, [])
// console.log(massData);
const option = {
@ -77,16 +108,16 @@ function MyApplication ({ loading, clientHeight, actions, dispatch, user }) {
const [count, setCount] = useState(0);
function onChartReady (echarts) {
console.log('echarts is ready', echarts);
// console.log('echarts is ready', echarts);
}
function onChartClick (param, echarts) {
console.log(param, echarts);
// console.log(param, echarts);
setCount(count + 1);
};
function onChartLegendselectchanged (param, echarts) {
console.log(param, echarts);
// console.log(param, echarts);
};
return <div style={{ width: '100%', display: 'flex', justifyContent: 'center' }}>
@ -94,11 +125,11 @@ function MyApplication ({ loading, clientHeight, actions, dispatch, user }) {
<div style={{ fontSize: 24, fontWeight: 600, }}>业务规则情况</div>
<div style={{ border: '1px solid #38333326', margin: '20px 0' }}></div>
<div style={{ display: 'flex' }}>
<div style={{ width: 200, h: 400, display: 'flex', alignItems: 'center', justifyContent: 'flex-end' }}>
业务规则个数<span style={{fontSize: 24, fontWeight: 600,}}>{bistribution?.count || 0} </span> </div>
<div style={{ width: '29%', h: 400, display: 'flex', alignItems: 'center', justifyContent: 'flex-end' }}>
业务规则个数<span style={{ fontSize: 24, fontWeight: 600, }}>{bistribution?.count || 0} </span> </div>
<ReactECharts
option={option}
style={{ height: 400, width: 600 }}
style={{ height: 400, width: '70%' }}
onChartReady={onChartReady}
onEvents={{
'click': onChartClick,
@ -106,6 +137,140 @@ function MyApplication ({ loading, clientHeight, actions, dispatch, user }) {
}}
/>
</div>
<div style={{ fontSize: 24, fontWeight: 600, }}>数据质检情况</div>
<div style={{ border: '1px solid #38333326', margin: '20px 0' }}></div>
<ReactECharts
option={{
backgroundColor: 'white',
tooltip: {
trigger: 'axis',
backgroundColor: "rgba(1, 13, 19, 0.5)",
borderWidth: 0,
axisPointer: { // 坐标轴指示器,坐标轴触发有效
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
},
textStyle: {
color: "rgba(212, 232, 254, 1)",
// fontSize: fontChart(0.24),
},
confine: true
},
grid: {
top: '25%',
left: '5%',
right: '5%',
bottom: '8%',
containLabel: true,
},
legend: {
data: ['数据量', '错误数'],
left: "center",
top: 30,
itemWidth: 15,
itemHeight: 10,
itemGap: 15,
borderRadius: 4,
textStyle: {
color: "#000",
fontFamily: "Alibaba PuHuiTi",
fontSize: 14,
fontWeight: 400,
},
},
xAxis: {
type: 'category',
data: ['一致性', '准确性', '完整性', '有效性', '及时性', '规范性'],
axisLine: {
show: false,
lineStyle: {
color: '#ECECEC'
}
},
axisTick: {
show: false,
},
axisLabel: {
// interval: 0,
// rotate: 40,
show: true,
textStyle: {
fontFamily: 'Microsoft YaHei',
color: '#666666'
}
},
},
yAxis: {
nameGap: 25, // 表现为上下位置
type: 'value',
//max:'5000',
axisLine: {
show: false,
lineStyle: {
color: '#666666',
}
},
splitLine: {
show: true,
lineStyle: {
color: '#ECECEC'
}
},
axisLabel: {
textStyle: {
fontFamily: 'Microsoft YaHei',
color: '#393939',
fontSize: 12
}
}
},
series: [{
name: '数据量',
type: 'bar',
barWidth: '25',
itemStyle: {
normal: {
color: '#40cf11'
},
},
data: [
massData?.Consistency?.total || 0,
massData?.Accuracy.total || 0,
massData?.Completeness?.total || 0,
massData?.Validity?.total || 0,
massData?.Timeliness?.total || 0,
massData?.Conformity?.total || 0,
]
},
{
name: '错误数',
type: 'bar',
barWidth: '25',
itemStyle: {
color: '#f2de06'
},
data: [
massData?.Consistency?.unexpected || 0,
massData?.Accuracy.unexpected || 0,
massData?.Completeness?.unexpected || 0,
massData?.Validity?.unexpected || 0,
massData?.Timeliness?.unexpected || 0,
massData?.Conformity?.unexpected || 0,
]
}
]
}}
style={{ height: 400, width: '100%' }}
onChartReady={onChartReady}
// onEvents={{
// 'click': onChartClick,
// 'legendselectchanged': onChartLegendselectchanged
// }}
/>
</div>

5
web/client/src/sections/dataService/actions/serviceManagement.js

@ -16,14 +16,15 @@ export function getServiceManagement (query = {}) {
}
export function postServiceManagement (data = {}) {
export function postServiceManagement (data = {}, distinguish) {
let option = distinguish ? data?.enabled ? 'REST服务启用' : 'REST服务禁用' : '编辑REST服务'
return dispatch => basicAction({
type: 'post',
data,
dispatch: dispatch,
actionType: 'POST_SERVICE_MANAGEMENT',
url: `${ApiTable.serviceManagement}`,
msg: { option: '编辑REST服务' },
msg: { option: option },
reducer: { name: '' }
});
}

12
web/client/src/sections/dataService/components/resourceModal.js

@ -38,19 +38,21 @@ const ResourceModal = (props) => {
<Modal title={'申请资源'} open={true} destroyOnClose
okText='确定' width={800}
onOk={() => handleOk(null)}
onCancel={()=>close()}>
onCancel={() => close()}>
<Form form={form} labelCol={{ span: 6 }} wrapperCol={{ span: 18 }} initialValues={editData.record || {}}>
<Form.Item
label='资源名称'
label='名称'
name='resourceName'
rules={[{ required: true, message: '资源名称不可空' }]}>
<Input style={{ width: '90%' }} />
initialValue={editData?.name || ''}
rules={[{ required: true, message: '名称不可空' }]}>
<Input disabled style={{ width: '90%' }} />
</Form.Item>
<Form.Item
label='申请人'
name='applyByName'
initialValue={user?.name || ''}
rules={[{ required: true, message: '申请人不可空' }]}>
<Input style={{ width: '90%' }} />
<Input disabled style={{ width: '90%' }} />
</Form.Item>
<Form.Item
label='需求描述'

6
web/client/src/sections/dataService/containers/serviceManagement.js

@ -79,14 +79,14 @@ function ServiceManagement ({ loading, clientHeight, actions, dispatch, }) {
<a >删除</a>
</Popconfirm>
{/* // } */}
{/* // } */}
{record?.enabled ?
<Popconfirm
title="禁用后该服务将不可用"
onConfirm={() => {
dispatch(dataService.postServiceManagement({
id: record.id, name: record?.name, enabled: false
})).then(res => {
}, true)).then(res => {
if (res.success) {
resourceData({ keyword })
}
@ -98,7 +98,7 @@ function ServiceManagement ({ loading, clientHeight, actions, dispatch, }) {
: <a onClick={() => {
dispatch(dataService.postServiceManagement({
id: record.id, name: record?.name, enabled: true
})).then(res => {
}, true)).then(res => {
if (res.success) {
resourceData({ keyword })
}

3
web/client/src/utils/webapi.js

@ -96,6 +96,7 @@ export const ApiTable = {
delBusinessRules: 'business-rules/{id}',
regularBasis: 'regular-basis',
fetchFiles: 'fetchFiles',
getQualityInspection: 'quality-inspection',
//数据安全规范上传
specifications: 'data-security/specifications',
@ -113,7 +114,7 @@ export const ApiTable = {
//REST服务
serviceManagement: 'service-management',
delServiceManagement: 'service-management/{id}',
lookField:'lookField',
lookField: 'lookField',
};

Loading…
Cancel
Save