@things-factory/kpi 9.1.19 → 10.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/pages/kpi/kpi-list-page.ts +339 -525
- package/client/pages/kpi/kpi-tree-page.ts +135 -207
- package/client/pages/kpi-metric/kpi-metric-list-page.ts +146 -226
- package/client/pages/kpi-metric-value/kpi-metric-value-editor-page.ts +187 -295
- package/client/pages/kpi-metric-value/kpi-metric-value-list-page.ts +123 -194
- package/client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.ts +57 -91
- package/client/pages/kpi-statistic/kpi-statistic-editor-page.ts +180 -278
- package/client/pages/kpi-statistic/kpi-statistic-list-page.ts +186 -286
- package/client/pages/kpi-value/kpi-value-editor-page.ts +189 -292
- package/client/pages/kpi-value/kpi-value-list-page.ts +170 -264
- package/dist-client/pages/kpi/kpi-list-page.d.ts +0 -6
- package/dist-client/pages/kpi/kpi-list-page.js +150 -282
- package/dist-client/pages/kpi/kpi-list-page.js.map +1 -1
- package/dist-client/pages/kpi/kpi-tree-page.d.ts +1 -7
- package/dist-client/pages/kpi/kpi-tree-page.js +76 -127
- package/dist-client/pages/kpi/kpi-tree-page.js.map +1 -1
- package/dist-client/pages/kpi-metric/kpi-metric-list-page.d.ts +0 -6
- package/dist-client/pages/kpi-metric/kpi-metric-list-page.js +62 -116
- package/dist-client/pages/kpi-metric/kpi-metric-list-page.js.map +1 -1
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.d.ts +1 -7
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js +82 -140
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js.map +1 -1
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.d.ts +0 -6
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js +54 -98
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js.map +1 -1
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.d.ts +1 -7
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js +30 -57
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js.map +1 -1
- package/dist-client/pages/kpi-statistic/kpi-statistic-editor-page.d.ts +1 -7
- package/dist-client/pages/kpi-statistic/kpi-statistic-editor-page.js +91 -153
- package/dist-client/pages/kpi-statistic/kpi-statistic-editor-page.js.map +1 -1
- package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.d.ts +0 -6
- package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.js +81 -155
- package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.js.map +1 -1
- package/dist-client/pages/kpi-value/kpi-value-editor-page.d.ts +1 -7
- package/dist-client/pages/kpi-value/kpi-value-editor-page.js +80 -136
- package/dist-client/pages/kpi-value/kpi-value-editor-page.js.map +1 -1
- package/dist-client/pages/kpi-value/kpi-value-list-page.d.ts +0 -6
- package/dist-client/pages/kpi-value/kpi-value-list-page.js +73 -134
- package/dist-client/pages/kpi-value/kpi-value-list-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/service/index.d.ts +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +18 -18
- package/client/tsconfig.json +0 -11
- package/dist-server/tsconfig.json +0 -10
- package/server/@types/index.d.ts +0 -11
- package/server/calculator/evaluator.ts +0 -45
- package/server/calculator/functions.ts +0 -67
- package/server/calculator/index.ts +0 -4
- package/server/calculator/parser.ts +0 -137
- package/server/calculator/provider.ts +0 -10
- package/server/controllers/index.ts +0 -2
- package/server/controllers/kpi-metric-value-provider.ts +0 -79
- package/server/controllers/kpi-value-provider.ts +0 -51
- package/server/index.ts +0 -6
- package/server/migrations/1752190849680-seed-kpi-metrics.ts +0 -124
- package/server/migrations/1752190849681-seed-kpi.ts +0 -356
- package/server/migrations/1752192090123-add-grades-to-kpi.ts +0 -67
- package/server/migrations/1752192090124-add-kpi-statistics.ts +0 -719
- package/server/migrations/1752192090128-seed-kpi-org-scope.ts +0 -132
- package/server/migrations/1752192090129-seed-kpi-values.ts +0 -207
- package/server/migrations/grade-data/x11-performance-table.json +0 -962
- package/server/migrations/grade-data/x12-performance-table.json +0 -611
- package/server/migrations/grade-data/x14-performance-table.json +0 -42
- package/server/migrations/grade-data/x21-performance-table.json +0 -889
- package/server/migrations/grade-data/x22-performance-table.json +0 -1064
- package/server/migrations/grade-data/x23-performance-table.json +0 -42
- package/server/migrations/grade-data/x31-performance-table.json +0 -644
- package/server/migrations/grade-data/x32-performance-table.json +0 -993
- package/server/migrations/grade-data/x33-performance-table.json +0 -195
- package/server/migrations/grade-data/x34-performance-table.json +0 -12
- package/server/migrations/grade-data/x35-performance-table.json +0 -42
- package/server/migrations/grade-data/x41-performance-table.json +0 -825
- package/server/migrations/grade-data/x42-performance-table.json +0 -786
- package/server/migrations/grade-data/x43-performance-table.json +0 -12
- package/server/migrations/grade-data/x44-performance-table.json +0 -42
- package/server/migrations/grade-data/x51-performance-table.json +0 -924
- package/server/migrations/grade-data/x52-performance-table.json +0 -42
- package/server/migrations/grade-data/x61-performance-table.json +0 -261
- package/server/migrations/grade-data/x62-performance-table.json +0 -42
- package/server/migrations/index.ts +0 -9
- package/server/migrations/seed-data/kpi-metrics-seed.json +0 -454
- package/server/migrations/seed-data/kpi-org-scope-seed.json +0 -1676
- package/server/migrations/seed-data/kpi-scopes-seed.json +0 -121
- package/server/migrations/seed-data/kpi-values-seed.json +0 -402
- package/server/migrations/seed-data/kpis-seed.json +0 -488
- package/server/migrations/seed-data/scope-definitions-seed.json +0 -90
- package/server/routes.ts +0 -81
- package/server/service/index.ts +0 -51
- package/server/service/kpi/aggregate-kpi.ts +0 -103
- package/server/service/kpi/event-subscriber.ts +0 -29
- package/server/service/kpi/index.ts +0 -9
- package/server/service/kpi/kpi-formula.service.ts +0 -164
- package/server/service/kpi/kpi-grade.types.ts +0 -28
- package/server/service/kpi/kpi-history.ts +0 -126
- package/server/service/kpi/kpi-mutation.ts +0 -553
- package/server/service/kpi/kpi-query.ts +0 -224
- package/server/service/kpi/kpi-type.ts +0 -151
- package/server/service/kpi/kpi.ts +0 -254
- package/server/service/kpi-alert/index.ts +0 -3
- package/server/service/kpi-alert/kpi-alert-query.ts +0 -59
- package/server/service/kpi-alert/kpi-alert-type.ts +0 -20
- package/server/service/kpi-metric/aggregate-kpi-metric.ts +0 -132
- package/server/service/kpi-metric/index.ts +0 -7
- package/server/service/kpi-metric/kpi-metric-mutation.ts +0 -309
- package/server/service/kpi-metric/kpi-metric-query.ts +0 -70
- package/server/service/kpi-metric/kpi-metric-type.ts +0 -111
- package/server/service/kpi-metric/kpi-metric.ts +0 -134
- package/server/service/kpi-metric-value/index.ts +0 -7
- package/server/service/kpi-metric-value/kpi-metric-value-mutation.ts +0 -270
- package/server/service/kpi-metric-value/kpi-metric-value-query.ts +0 -62
- package/server/service/kpi-metric-value/kpi-metric-value-type.ts +0 -82
- package/server/service/kpi-metric-value/kpi-metric-value.ts +0 -93
- package/server/service/kpi-org-scope/index.ts +0 -6
- package/server/service/kpi-org-scope/kpi-org-scope-mutation.ts +0 -173
- package/server/service/kpi-org-scope/kpi-org-scope-query.ts +0 -127
- package/server/service/kpi-org-scope/kpi-org-scope-type.ts +0 -68
- package/server/service/kpi-org-scope/kpi-org-scope.ts +0 -123
- package/server/service/kpi-scope/index.ts +0 -11
- package/server/service/kpi-scope/kpi-scope-mutation.ts +0 -129
- package/server/service/kpi-scope/kpi-scope-query.ts +0 -63
- package/server/service/kpi-scope/kpi-scope-type.ts +0 -96
- package/server/service/kpi-scope/kpi-scope.ts +0 -143
- package/server/service/kpi-statistic/index.ts +0 -7
- package/server/service/kpi-statistic/kpi-statistic-batch.service.ts +0 -231
- package/server/service/kpi-statistic/kpi-statistic-calculation.service.ts +0 -410
- package/server/service/kpi-statistic/kpi-statistic-mutation.ts +0 -291
- package/server/service/kpi-statistic/kpi-statistic-query.ts +0 -146
- package/server/service/kpi-statistic/kpi-statistic-type.ts +0 -152
- package/server/service/kpi-statistic/kpi-statistic.ts +0 -199
- package/server/service/kpi-value/index.ts +0 -7
- package/server/service/kpi-value/kpi-value-mutation.ts +0 -432
- package/server/service/kpi-value/kpi-value-query.ts +0 -61
- package/server/service/kpi-value/kpi-value-score.service.ts +0 -106
- package/server/service/kpi-value/kpi-value-type.ts +0 -122
- package/server/service/kpi-value/kpi-value.ts +0 -160
- package/server/service/utils/value-date-util.ts +0 -119
- package/server/tsconfig.json +0 -10
- package/server/types/global.d.ts +0 -8
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { ObjectType, Field, ID } from 'type-graphql'
|
|
2
|
-
import { Kpi } from '../kpi/kpi'
|
|
3
|
-
|
|
4
|
-
@ObjectType({ description: 'KPI 경고/알림 엔티티. KPI 실적, 등급, 목표 미달 등 경고/알림 정보를 제공.' })
|
|
5
|
-
export class KpiAlert {
|
|
6
|
-
@Field(type => ID)
|
|
7
|
-
id: string
|
|
8
|
-
|
|
9
|
-
@Field(type => Kpi, { nullable: true, description: '경고/알림이 발생한 KPI.' })
|
|
10
|
-
kpi?: Kpi
|
|
11
|
-
|
|
12
|
-
@Field({ description: '경고/알림 메시지.' })
|
|
13
|
-
message: string
|
|
14
|
-
|
|
15
|
-
@Field({ description: '경고 레벨(예: warning, critical, info 등).' })
|
|
16
|
-
level: string
|
|
17
|
-
|
|
18
|
-
@Field({ description: '경고/알림 발생 시각.' })
|
|
19
|
-
createdAt: Date
|
|
20
|
-
}
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { getRepository } from '@things-factory/shell'
|
|
2
|
-
import type ResolverContext from '@things-factory/auth-base'
|
|
3
|
-
import { DataSummary } from '@things-factory/dataset'
|
|
4
|
-
import { finalizeLatestDataCollection } from '@things-factory/dataset/dist-server/controllers/finalize-data-collection'
|
|
5
|
-
import { KpiMetric } from './kpi-metric'
|
|
6
|
-
import { KpiValue, KpiValueInputType } from '../kpi-value/kpi-value'
|
|
7
|
-
import { Kpi } from '../kpi/kpi'
|
|
8
|
-
import { KpiOrgScope } from '../kpi-org-scope/kpi-org-scope'
|
|
9
|
-
import { KpiFormulaService } from '../kpi/kpi-formula.service'
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* KPI-Metric 집계/자동화 서비스 함수 (formula 지원, KPI Value 저장)
|
|
13
|
-
* @param metricId KPI-Metric ID
|
|
14
|
-
* @param domainId 도메인 ID
|
|
15
|
-
* @param context ResolverContext (state에 domain, user, tx 등 포함)
|
|
16
|
-
* @returns 저장된 KPI Value 배열
|
|
17
|
-
*/
|
|
18
|
-
export async function aggregateKpiMetricValue(metricId: string, domainId: string, context: ResolverContext) {
|
|
19
|
-
const tx = context.state?.tx || getRepository(KpiMetric).manager
|
|
20
|
-
|
|
21
|
-
// 1. metric 정보 조회
|
|
22
|
-
const metric = await getRepository(KpiMetric).findOne({
|
|
23
|
-
where: { id: metricId, domain: { id: domainId } }
|
|
24
|
-
})
|
|
25
|
-
if (!metric) throw new Error('Metric 정보 없음')
|
|
26
|
-
if (!metric.active) throw new Error('비활성화된 Metric')
|
|
27
|
-
|
|
28
|
-
// formula 분기 제거: metric은 항상 dataset 집계만 수행
|
|
29
|
-
let values: any[] = []
|
|
30
|
-
if (!metric.dataSetId || !metric.fieldName) throw new Error('Metric 매핑 정보 누락')
|
|
31
|
-
await finalizeLatestDataCollection(metric.dataSetId, context)
|
|
32
|
-
const summaries = await getRepository(DataSummary).find({
|
|
33
|
-
where: { dataSet: { id: metric.dataSetId }, domain: { id: domainId } },
|
|
34
|
-
order: { date: 'DESC', period: 'DESC' }
|
|
35
|
-
})
|
|
36
|
-
values = summaries
|
|
37
|
-
.map(summary => {
|
|
38
|
-
if (summary.summary && metric.fieldName in summary.summary) {
|
|
39
|
-
return {
|
|
40
|
-
date: summary.date,
|
|
41
|
-
period: summary.period,
|
|
42
|
-
value: summary.summary[metric.fieldName],
|
|
43
|
-
org: {
|
|
44
|
-
key01: summary.key01,
|
|
45
|
-
key02: summary.key02,
|
|
46
|
-
key03: summary.key03,
|
|
47
|
-
key04: summary.key04,
|
|
48
|
-
key05: summary.key05
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return null
|
|
53
|
-
})
|
|
54
|
-
.filter(Boolean)
|
|
55
|
-
|
|
56
|
-
// 5. KPI Value로 저장 (metric 단위, KPI 단위 formula는 후속)
|
|
57
|
-
// metric이 속한 KPI 정보 조회 (여기서는 metric.name == kpi.name인 KPI를 임시로 매핑, 실제 연동 구조에 맞게 보완 필요)
|
|
58
|
-
const kpi = await getRepository(Kpi).findOne({ where: { name: metric.name, domain: { id: domainId } } })
|
|
59
|
-
if (!kpi) throw new Error('KPI 정보 없음 (metric.name과 동일한 KPI name 기준, 실제 연동 구조에 맞게 보완 필요)')
|
|
60
|
-
|
|
61
|
-
// KPI Value version, valueDate, org 등 매핑
|
|
62
|
-
const savedValues = []
|
|
63
|
-
const kpiOrgScopeRepo = getRepository(KpiOrgScope, context.state?.tx)
|
|
64
|
-
const kpiValueRepo = getRepository(KpiValue, context.state?.tx)
|
|
65
|
-
|
|
66
|
-
for (const v of values) {
|
|
67
|
-
const valueDate = v.date
|
|
68
|
-
const orgData = v.org
|
|
69
|
-
const version = kpi.version || 1
|
|
70
|
-
const value = v.value
|
|
71
|
-
if (value == null || isNaN(value)) continue
|
|
72
|
-
|
|
73
|
-
// KpiOrgScope 찾기 또는 생성
|
|
74
|
-
let kpiOrgScope: KpiOrgScope | null = null
|
|
75
|
-
|
|
76
|
-
// key들로 조합된 org 식별자 생성
|
|
77
|
-
const orgIdentifier =
|
|
78
|
-
[orgData.key01, orgData.key02, orgData.key03, orgData.key04, orgData.key05].filter(Boolean).join('-') ||
|
|
79
|
-
'unknown-org'
|
|
80
|
-
|
|
81
|
-
// 기존 KpiOrgScope 조회 (entityName 기준)
|
|
82
|
-
kpiOrgScope = await kpiOrgScopeRepo.findOne({
|
|
83
|
-
where: { entityName: orgIdentifier, domain: { id: domainId } }
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
// 없으면 새로 생성
|
|
87
|
-
if (!kpiOrgScope) {
|
|
88
|
-
kpiOrgScope = await kpiOrgScopeRepo.save({
|
|
89
|
-
entityType: 'DataSummary', // DataSummary에서 온 데이터임을 표시
|
|
90
|
-
entityId: `datasummary-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
91
|
-
entityName: orgIdentifier,
|
|
92
|
-
org: orgIdentifier, // legacy field
|
|
93
|
-
scope01: orgData.key01,
|
|
94
|
-
scope02: orgData.key02,
|
|
95
|
-
scope03: orgData.key03,
|
|
96
|
-
scope04: orgData.key04,
|
|
97
|
-
scope05: orgData.key05,
|
|
98
|
-
domain: kpi.domain,
|
|
99
|
-
creator: context.state?.user,
|
|
100
|
-
updater: context.state?.user
|
|
101
|
-
})
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// upsert KpiValue (동일 KPI, valueDate, kpiOrgScope, version 기준)
|
|
105
|
-
const existing = await kpiValueRepo.findOne({
|
|
106
|
-
where: {
|
|
107
|
-
kpi: { id: kpi.id },
|
|
108
|
-
valueDate,
|
|
109
|
-
kpiOrgScope: { id: kpiOrgScope.id },
|
|
110
|
-
version,
|
|
111
|
-
domain: { id: domainId }
|
|
112
|
-
}
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
let entity = existing || kpiValueRepo.create()
|
|
116
|
-
entity.kpi = kpi
|
|
117
|
-
entity.kpiId = kpi.id
|
|
118
|
-
entity.version = version
|
|
119
|
-
entity.valueDate = valueDate
|
|
120
|
-
entity.value = value
|
|
121
|
-
entity.kpiOrgScope = kpiOrgScope
|
|
122
|
-
entity.kpiOrgScopeId = kpiOrgScope.id
|
|
123
|
-
entity.inputType = KpiValueInputType.AUTO
|
|
124
|
-
entity.source = 'AUTO-AGGREGATE'
|
|
125
|
-
entity.domain = kpi.domain
|
|
126
|
-
entity.creator = context.state?.user
|
|
127
|
-
entity.updater = context.state?.user
|
|
128
|
-
entity = await kpiValueRepo.save(entity)
|
|
129
|
-
savedValues.push(entity)
|
|
130
|
-
}
|
|
131
|
-
return savedValues
|
|
132
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { KpiMetric } from './kpi-metric'
|
|
2
|
-
import { KpiMetricQuery } from './kpi-metric-query'
|
|
3
|
-
import { KpiMetricMutation } from './kpi-metric-mutation'
|
|
4
|
-
|
|
5
|
-
export const entities = [KpiMetric]
|
|
6
|
-
export const resolvers = [KpiMetricQuery, KpiMetricMutation]
|
|
7
|
-
export const subscribers = []
|
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
import { Resolver, Mutation, Arg, Ctx, Directive } from 'type-graphql'
|
|
2
|
-
import { In } from 'typeorm'
|
|
3
|
-
import { getRepository } from '@things-factory/shell'
|
|
4
|
-
import { DataSet } from '@things-factory/dataset'
|
|
5
|
-
|
|
6
|
-
import { createAttachment, deleteAttachmentsByRef } from '@things-factory/attachment-base'
|
|
7
|
-
import { KpiMetric } from './kpi-metric'
|
|
8
|
-
import { NewKpiMetric, KpiMetricPatch } from './kpi-metric-type'
|
|
9
|
-
import { KpiFormulaService } from '../kpi/kpi-formula.service'
|
|
10
|
-
import { registerSchedule, unregisterSchedule, CallbackBase, Application } from '@things-factory/scheduler-client'
|
|
11
|
-
|
|
12
|
-
@Resolver(KpiMetric)
|
|
13
|
-
export class KpiMetricMutation {
|
|
14
|
-
@Directive('@transaction')
|
|
15
|
-
@Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
16
|
-
@Mutation(returns => KpiMetric, { description: 'Create a new KPI metric with the provided details.' })
|
|
17
|
-
async createKpiMetric(
|
|
18
|
-
@Arg('kpiMetric', { description: 'Input object containing details for the new KPI metric.' })
|
|
19
|
-
kpiMetric: NewKpiMetric,
|
|
20
|
-
@Ctx() context: ResolverContext
|
|
21
|
-
): Promise<KpiMetric> {
|
|
22
|
-
const { domain, user, tx } = context.state
|
|
23
|
-
|
|
24
|
-
let dataSet = kpiMetric.dataSetId
|
|
25
|
-
? await getRepository(DataSet).findOne({ where: { id: kpiMetric.dataSetId } })
|
|
26
|
-
: undefined
|
|
27
|
-
|
|
28
|
-
// formula 관련 로직 완전 제거
|
|
29
|
-
|
|
30
|
-
const result = await getRepository(KpiMetric, tx).save({
|
|
31
|
-
...kpiMetric,
|
|
32
|
-
dataSet,
|
|
33
|
-
domain,
|
|
34
|
-
creator: user,
|
|
35
|
-
updater: user
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
// 스케줄러 등록
|
|
39
|
-
if (kpiMetric.schedule) {
|
|
40
|
-
const handle = await registerSchedule({
|
|
41
|
-
name: kpiMetric.name,
|
|
42
|
-
client: {
|
|
43
|
-
application: Application,
|
|
44
|
-
group: `${domain.id}`,
|
|
45
|
-
type: 'kpi-metric',
|
|
46
|
-
key: result.id,
|
|
47
|
-
operation: 'aggregate'
|
|
48
|
-
},
|
|
49
|
-
type: 'cron',
|
|
50
|
-
schedule: kpiMetric.schedule,
|
|
51
|
-
timezone: kpiMetric.timezone,
|
|
52
|
-
task: {
|
|
53
|
-
type: 'rest',
|
|
54
|
-
connection: {
|
|
55
|
-
host: `${CallbackBase}/callback-schedule-for-kpi-metric`,
|
|
56
|
-
headers: {
|
|
57
|
-
'Content-Type': 'application/json',
|
|
58
|
-
accept: '*/*'
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
|
-
data: {
|
|
62
|
-
domainId: domain.id,
|
|
63
|
-
metricId: result.id
|
|
64
|
-
},
|
|
65
|
-
history_check: true,
|
|
66
|
-
failed_policy: 'retry_dlq',
|
|
67
|
-
max_retry_count: 3,
|
|
68
|
-
retry_period: 60
|
|
69
|
-
}
|
|
70
|
-
})
|
|
71
|
-
result.scheduleId = handle
|
|
72
|
-
result.timezone = kpiMetric.timezone
|
|
73
|
-
await getRepository(KpiMetric, tx).save(result)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (kpiMetric.thumbnail) {
|
|
77
|
-
await createAttachment(
|
|
78
|
-
null,
|
|
79
|
-
{
|
|
80
|
-
attachment: {
|
|
81
|
-
file: kpiMetric.thumbnail,
|
|
82
|
-
refType: KpiMetric.name,
|
|
83
|
-
refBy: result.id
|
|
84
|
-
}
|
|
85
|
-
},
|
|
86
|
-
context
|
|
87
|
-
)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return result
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
@Directive('@transaction')
|
|
94
|
-
@Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
95
|
-
@Mutation(returns => KpiMetric, { description: 'To modify KpiMetric information' })
|
|
96
|
-
async updateKpiMetric(
|
|
97
|
-
@Arg('id') id: string,
|
|
98
|
-
@Arg('patch') patch: KpiMetricPatch,
|
|
99
|
-
@Ctx() context: ResolverContext
|
|
100
|
-
): Promise<KpiMetric> {
|
|
101
|
-
const { domain, user, tx } = context.state
|
|
102
|
-
|
|
103
|
-
const repository = getRepository(KpiMetric, tx)
|
|
104
|
-
const kpiMetric = await repository.findOne({
|
|
105
|
-
where: { domain: { id: domain.id }, id }
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
let dataSet = patch.dataSetId
|
|
109
|
-
? await getRepository(DataSet).findOne({ where: { id: patch.dataSetId } })
|
|
110
|
-
: kpiMetric.dataSet
|
|
111
|
-
|
|
112
|
-
// formula 관련 로직 완전 제거
|
|
113
|
-
|
|
114
|
-
const result = await repository.save({
|
|
115
|
-
...kpiMetric,
|
|
116
|
-
...patch,
|
|
117
|
-
dataSet,
|
|
118
|
-
updater: user
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
// 스케줄러 해제/등록 (변경 시)
|
|
122
|
-
if (kpiMetric.scheduleId && (patch.scheduleId !== kpiMetric.scheduleId || !patch.scheduleId)) {
|
|
123
|
-
await unregisterSchedule(kpiMetric.scheduleId)
|
|
124
|
-
result.scheduleId = null
|
|
125
|
-
}
|
|
126
|
-
if (patch.schedule) {
|
|
127
|
-
const handle = await registerSchedule({
|
|
128
|
-
name: patch.name || kpiMetric.name,
|
|
129
|
-
client: {
|
|
130
|
-
application: Application,
|
|
131
|
-
group: `${domain.id}`,
|
|
132
|
-
type: 'kpi-metric',
|
|
133
|
-
key: result.id,
|
|
134
|
-
operation: 'aggregate'
|
|
135
|
-
},
|
|
136
|
-
type: 'cron',
|
|
137
|
-
schedule: patch.schedule,
|
|
138
|
-
timezone: patch.timezone,
|
|
139
|
-
task: {
|
|
140
|
-
type: 'rest',
|
|
141
|
-
connection: {
|
|
142
|
-
host: `${CallbackBase}/callback-schedule-for-kpi-metric`,
|
|
143
|
-
headers: {
|
|
144
|
-
'Content-Type': 'application/json',
|
|
145
|
-
accept: '*/*'
|
|
146
|
-
}
|
|
147
|
-
},
|
|
148
|
-
data: {
|
|
149
|
-
domainId: domain.id,
|
|
150
|
-
metricId: result.id
|
|
151
|
-
},
|
|
152
|
-
history_check: true,
|
|
153
|
-
failed_policy: 'retry_dlq',
|
|
154
|
-
max_retry_count: 3,
|
|
155
|
-
retry_period: 60
|
|
156
|
-
}
|
|
157
|
-
})
|
|
158
|
-
result.scheduleId = handle
|
|
159
|
-
result.timezone = patch.timezone
|
|
160
|
-
await repository.save(result)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (patch.thumbnail) {
|
|
164
|
-
await deleteAttachmentsByRef(null, { refBys: [result.id] }, context)
|
|
165
|
-
await createAttachment(
|
|
166
|
-
null,
|
|
167
|
-
{
|
|
168
|
-
attachment: {
|
|
169
|
-
file: patch.thumbnail,
|
|
170
|
-
refType: KpiMetric.name,
|
|
171
|
-
refBy: result.id
|
|
172
|
-
}
|
|
173
|
-
},
|
|
174
|
-
context
|
|
175
|
-
)
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return result
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
@Directive('@transaction')
|
|
182
|
-
@Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
183
|
-
@Mutation(returns => [KpiMetric], { description: "To modify multiple KpiMetrics' information" })
|
|
184
|
-
async updateMultipleKpiMetric(
|
|
185
|
-
@Arg('patches', type => [KpiMetricPatch]) patches: KpiMetricPatch[],
|
|
186
|
-
@Ctx() context: ResolverContext
|
|
187
|
-
): Promise<KpiMetric[]> {
|
|
188
|
-
const { domain, user, tx } = context.state
|
|
189
|
-
|
|
190
|
-
let results = []
|
|
191
|
-
const _createRecords = patches.filter((patch: any) => patch.cuFlag.toUpperCase() === '+')
|
|
192
|
-
const _updateRecords = patches.filter((patch: any) => patch.cuFlag.toUpperCase() === 'M')
|
|
193
|
-
const kpiMetricRepo = getRepository(KpiMetric, tx)
|
|
194
|
-
|
|
195
|
-
if (_createRecords.length > 0) {
|
|
196
|
-
for (let i = 0; i < _createRecords.length; i++) {
|
|
197
|
-
const newRecord = _createRecords[i]
|
|
198
|
-
|
|
199
|
-
const result = await kpiMetricRepo.save({
|
|
200
|
-
...newRecord,
|
|
201
|
-
domain,
|
|
202
|
-
creator: user,
|
|
203
|
-
updater: user
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
if (newRecord.thumbnail) {
|
|
207
|
-
await createAttachment(
|
|
208
|
-
null,
|
|
209
|
-
{
|
|
210
|
-
attachment: {
|
|
211
|
-
file: newRecord.thumbnail,
|
|
212
|
-
refType: KpiMetric.name,
|
|
213
|
-
refBy: result.id
|
|
214
|
-
}
|
|
215
|
-
},
|
|
216
|
-
context
|
|
217
|
-
)
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
results.push({ ...result, cuFlag: '+' })
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
if (_updateRecords.length > 0) {
|
|
225
|
-
for (let i = 0; i < _updateRecords.length; i++) {
|
|
226
|
-
const updateRecord = _updateRecords[i]
|
|
227
|
-
const kpiMetric = await kpiMetricRepo.findOneBy({ id: updateRecord.id })
|
|
228
|
-
|
|
229
|
-
const result = await kpiMetricRepo.save({
|
|
230
|
-
...kpiMetric,
|
|
231
|
-
...updateRecord,
|
|
232
|
-
updater: user
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
if (updateRecord.thumbnail) {
|
|
236
|
-
await deleteAttachmentsByRef(null, { refBys: [result.id] }, context)
|
|
237
|
-
await createAttachment(
|
|
238
|
-
null,
|
|
239
|
-
{
|
|
240
|
-
attachment: {
|
|
241
|
-
file: updateRecord.thumbnail,
|
|
242
|
-
refType: KpiMetric.name,
|
|
243
|
-
refBy: result.id
|
|
244
|
-
}
|
|
245
|
-
},
|
|
246
|
-
context
|
|
247
|
-
)
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
results.push({ ...result, cuFlag: 'M' })
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
return results
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
@Directive('@transaction')
|
|
258
|
-
@Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
259
|
-
@Mutation(returns => Boolean, { description: 'To delete KpiMetric' })
|
|
260
|
-
async deleteKpiMetric(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<boolean> {
|
|
261
|
-
const { domain, tx } = context.state
|
|
262
|
-
const repo = getRepository(KpiMetric, tx)
|
|
263
|
-
const metric = await repo.findOne({ where: { domain: { id: domain.id }, id } })
|
|
264
|
-
if (metric?.scheduleId) {
|
|
265
|
-
await unregisterSchedule(metric.scheduleId)
|
|
266
|
-
}
|
|
267
|
-
await repo.delete({ domain: { id: domain.id }, id })
|
|
268
|
-
await deleteAttachmentsByRef(null, { refBys: [id] }, context)
|
|
269
|
-
|
|
270
|
-
return true
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
@Directive('@transaction')
|
|
274
|
-
@Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
275
|
-
@Mutation(returns => Boolean, { description: 'To delete multiple KpiMetrics' })
|
|
276
|
-
async deleteKpiMetrics(
|
|
277
|
-
@Arg('ids', type => [String]) ids: string[],
|
|
278
|
-
@Ctx() context: ResolverContext
|
|
279
|
-
): Promise<boolean> {
|
|
280
|
-
const { domain, tx } = context.state
|
|
281
|
-
|
|
282
|
-
await getRepository(KpiMetric, tx).delete({
|
|
283
|
-
domain: { id: domain.id },
|
|
284
|
-
id: In(ids)
|
|
285
|
-
})
|
|
286
|
-
|
|
287
|
-
await deleteAttachmentsByRef(null, { refBys: ids }, context)
|
|
288
|
-
|
|
289
|
-
return true
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
@Directive('@transaction')
|
|
293
|
-
@Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
294
|
-
@Mutation(returns => Boolean, { description: 'To import multiple KpiMetrics' })
|
|
295
|
-
async importKpiMetrics(
|
|
296
|
-
@Arg('kpiMetrics', type => [KpiMetricPatch]) kpiMetrics: KpiMetricPatch[],
|
|
297
|
-
@Ctx() context: ResolverContext
|
|
298
|
-
): Promise<boolean> {
|
|
299
|
-
const { domain, tx } = context.state
|
|
300
|
-
|
|
301
|
-
await Promise.all(
|
|
302
|
-
kpiMetrics.map(async (kpiMetric: KpiMetricPatch) => {
|
|
303
|
-
const createdKpiMetric: KpiMetric = await getRepository(KpiMetric, tx).save({ domain, ...kpiMetric })
|
|
304
|
-
})
|
|
305
|
-
)
|
|
306
|
-
|
|
307
|
-
return true
|
|
308
|
-
}
|
|
309
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { Resolver, Query, FieldResolver, Root, Args, Arg, Ctx, Directive } from 'type-graphql'
|
|
2
|
-
import { Attachment } from '@things-factory/attachment-base'
|
|
3
|
-
import { Domain, getQueryBuilderFromListParams, getRepository, ListParam } from '@things-factory/shell'
|
|
4
|
-
import { User } from '@things-factory/auth-base'
|
|
5
|
-
import { KpiMetric } from './kpi-metric'
|
|
6
|
-
import { KpiMetricList } from './kpi-metric-type'
|
|
7
|
-
|
|
8
|
-
@Resolver(KpiMetric)
|
|
9
|
-
export class KpiMetricQuery {
|
|
10
|
-
@Directive('@privilege(category: "kpi", privilege: "query", domainOwnerGranted: true, superUserGranted: true)')
|
|
11
|
-
@Query(returns => KpiMetric!, { nullable: true, description: 'Fetch a single KPI metric by its unique identifier.' })
|
|
12
|
-
async kpiMetric(
|
|
13
|
-
@Arg('id', { description: 'Unique identifier of the KPI metric to fetch.' }) id: string,
|
|
14
|
-
@Ctx() context: ResolverContext
|
|
15
|
-
): Promise<KpiMetric> {
|
|
16
|
-
const { domain } = context.state
|
|
17
|
-
|
|
18
|
-
return await getRepository(KpiMetric).findOne({
|
|
19
|
-
where: { domain: { id: domain.id }, id }
|
|
20
|
-
})
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
@Directive('@privilege(category: "kpi", privilege: "query", domainOwnerGranted: true, superUserGranted: true)')
|
|
24
|
-
@Query(returns => KpiMetricList, { description: 'To fetch multiple KpiMetrics' })
|
|
25
|
-
async kpiMetrics(
|
|
26
|
-
@Args(type => ListParam) params: ListParam,
|
|
27
|
-
@Ctx() context: ResolverContext
|
|
28
|
-
): Promise<KpiMetricList> {
|
|
29
|
-
const { domain } = context.state
|
|
30
|
-
|
|
31
|
-
const queryBuilder = getQueryBuilderFromListParams({
|
|
32
|
-
domain,
|
|
33
|
-
params,
|
|
34
|
-
repository: await getRepository(KpiMetric),
|
|
35
|
-
searchables: ['name', 'description']
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
const [items, total] = await queryBuilder.getManyAndCount()
|
|
39
|
-
|
|
40
|
-
return { items, total }
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
@FieldResolver(type => String)
|
|
44
|
-
async thumbnail(@Root() kpiMetric: KpiMetric): Promise<string | undefined> {
|
|
45
|
-
const attachment: Attachment = await getRepository(Attachment).findOne({
|
|
46
|
-
where: {
|
|
47
|
-
domain: { id: kpiMetric.domainId },
|
|
48
|
-
refType: KpiMetric.name,
|
|
49
|
-
refBy: kpiMetric.id
|
|
50
|
-
}
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
return attachment?.fullpath
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
@FieldResolver(type => Domain)
|
|
57
|
-
async domain(@Root() kpiMetric: KpiMetric): Promise<Domain> {
|
|
58
|
-
return kpiMetric.domainId && (await getRepository(Domain).findOneBy({ id: kpiMetric.domainId }))
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
@FieldResolver(type => User)
|
|
62
|
-
async updater(@Root() kpiMetric: KpiMetric): Promise<User> {
|
|
63
|
-
return kpiMetric.updaterId && (await getRepository(User).findOneBy({ id: kpiMetric.updaterId }))
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
@FieldResolver(type => User)
|
|
67
|
-
async creator(@Root() kpiMetric: KpiMetric): Promise<User> {
|
|
68
|
-
return kpiMetric.creatorId && (await getRepository(User).findOneBy({ id: kpiMetric.creatorId }))
|
|
69
|
-
}
|
|
70
|
-
}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import type { FileUpload } from 'graphql-upload/GraphQLUpload.js'
|
|
2
|
-
import GraphQLUpload from 'graphql-upload/GraphQLUpload.js'
|
|
3
|
-
import { ObjectType, Field, InputType, Int, ID, registerEnumType } from 'type-graphql'
|
|
4
|
-
|
|
5
|
-
import { KpiPeriodType } from '../kpi/kpi'
|
|
6
|
-
import { KpiMetricCollectType } from './kpi-metric'
|
|
7
|
-
import { KpiMetric } from './kpi-metric'
|
|
8
|
-
|
|
9
|
-
@InputType({ description: 'Input type for creating a new KPI metric. Used in mutations to provide metric details.' })
|
|
10
|
-
export class NewKpiMetric {
|
|
11
|
-
@Field({ description: 'Metric code, unique within the domain, used in KPI formulas.' })
|
|
12
|
-
name: string
|
|
13
|
-
|
|
14
|
-
@Field({ nullable: true, description: 'User-friendly name or description of the metric.' })
|
|
15
|
-
description?: string
|
|
16
|
-
|
|
17
|
-
@Field({ nullable: true, description: 'Unit of measurement for this metric (e.g., %, count, hours).' })
|
|
18
|
-
unit?: string
|
|
19
|
-
|
|
20
|
-
@Field({ nullable: true, description: 'Source of the metric data (e.g., system, method, dataset).' })
|
|
21
|
-
source?: string
|
|
22
|
-
|
|
23
|
-
@Field(type => ID, { nullable: true, description: 'ID of the source dataset for this metric.' })
|
|
24
|
-
dataSetId?: string
|
|
25
|
-
|
|
26
|
-
@Field({ nullable: true, description: 'Name of the field in the dataset this metric maps to.' })
|
|
27
|
-
fieldName?: string
|
|
28
|
-
|
|
29
|
-
@Field({ nullable: true, description: 'Indicates whether this metric is active and usable.' })
|
|
30
|
-
active?: boolean
|
|
31
|
-
|
|
32
|
-
@Field({
|
|
33
|
-
nullable: true,
|
|
34
|
-
description: 'Cron schedule string for periodic KPI value aggregation (e.g., "0 0 * * *" for daily).'
|
|
35
|
-
})
|
|
36
|
-
schedule?: string
|
|
37
|
-
|
|
38
|
-
@Field(type => GraphQLUpload, { nullable: true, description: 'Thumbnail image or file for this metric.' })
|
|
39
|
-
thumbnail?: FileUpload
|
|
40
|
-
|
|
41
|
-
@Field({ nullable: true, description: 'Timezone for the schedule.' })
|
|
42
|
-
timezone?: string
|
|
43
|
-
@Field({ nullable: true, description: 'Schedule ID for the registered cron job.' })
|
|
44
|
-
scheduleId?: string
|
|
45
|
-
|
|
46
|
-
@Field(type => KpiPeriodType, { nullable: true, description: 'Aggregation period type for this metric.' })
|
|
47
|
-
periodType?: KpiPeriodType
|
|
48
|
-
|
|
49
|
-
@Field(type => KpiMetricCollectType, { nullable: true, description: '데이터 수집 방식' })
|
|
50
|
-
collectType?: KpiMetricCollectType
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
@InputType()
|
|
54
|
-
export class KpiMetricPatch {
|
|
55
|
-
@Field(type => ID, { nullable: true })
|
|
56
|
-
id?: string
|
|
57
|
-
|
|
58
|
-
@Field({ nullable: true })
|
|
59
|
-
name?: string
|
|
60
|
-
|
|
61
|
-
@Field({ nullable: true })
|
|
62
|
-
description?: string
|
|
63
|
-
|
|
64
|
-
@Field({ nullable: true })
|
|
65
|
-
unit?: string
|
|
66
|
-
|
|
67
|
-
@Field({ nullable: true })
|
|
68
|
-
source?: string
|
|
69
|
-
|
|
70
|
-
@Field(type => ID, { nullable: true })
|
|
71
|
-
dataSetId?: string
|
|
72
|
-
|
|
73
|
-
@Field({ nullable: true })
|
|
74
|
-
fieldName?: string
|
|
75
|
-
|
|
76
|
-
@Field({ nullable: true })
|
|
77
|
-
active?: boolean
|
|
78
|
-
|
|
79
|
-
@Field({
|
|
80
|
-
nullable: true,
|
|
81
|
-
description: 'Cron schedule string for periodic KPI value aggregation (e.g., "0 0 * * *" for daily).'
|
|
82
|
-
})
|
|
83
|
-
schedule?: string
|
|
84
|
-
|
|
85
|
-
@Field(type => GraphQLUpload, { nullable: true })
|
|
86
|
-
thumbnail?: FileUpload
|
|
87
|
-
|
|
88
|
-
@Field({ nullable: true, description: 'Timezone for the schedule.' })
|
|
89
|
-
timezone?: string
|
|
90
|
-
|
|
91
|
-
@Field({ nullable: true, description: 'Schedule ID for the registered cron job.' })
|
|
92
|
-
scheduleId?: string
|
|
93
|
-
|
|
94
|
-
@Field(type => KpiPeriodType, { nullable: true, description: 'Aggregation period type for this metric.' })
|
|
95
|
-
periodType?: KpiPeriodType
|
|
96
|
-
|
|
97
|
-
@Field({ nullable: true })
|
|
98
|
-
cuFlag?: string
|
|
99
|
-
|
|
100
|
-
@Field(type => KpiMetricCollectType, { nullable: true, description: '데이터 수집 방식' })
|
|
101
|
-
collectType?: KpiMetricCollectType
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
@ObjectType()
|
|
105
|
-
export class KpiMetricList {
|
|
106
|
-
@Field(type => [KpiMetric])
|
|
107
|
-
items: KpiMetric[]
|
|
108
|
-
|
|
109
|
-
@Field(type => Int)
|
|
110
|
-
total: number
|
|
111
|
-
}
|