@things-factory/dataset 6.0.0-alpha.5 → 6.0.0-alpha.7

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.
Files changed (63) hide show
  1. package/README.md +23 -19
  2. package/assets/diagram.jpg +0 -0
  3. package/client/pages/data-archive/data-archive-list-page.js +290 -0
  4. package/client/pages/data-archive/data-archive-request-popup.js +180 -0
  5. package/client/pages/data-key-set/data-key-set-list-page.js +1 -1
  6. package/client/pages/data-report/data-report-list-page.js +2 -2
  7. package/client/pages/data-set/data-set-list-page.js +1 -1
  8. package/client/route.js +4 -0
  9. package/config/config.development.js +11 -0
  10. package/config/config.production.js +11 -0
  11. package/dist-server/controllers/jasper-report.js +4 -4
  12. package/dist-server/controllers/jasper-report.js.map +1 -1
  13. package/dist-server/controllers/shiny-report.js +4 -7
  14. package/dist-server/controllers/shiny-report.js.map +1 -1
  15. package/dist-server/routes.js.map +1 -1
  16. package/dist-server/service/data-archive/data-archive-mutation.js +227 -0
  17. package/dist-server/service/data-archive/data-archive-mutation.js.map +1 -0
  18. package/dist-server/service/data-archive/data-archive-query.js +83 -0
  19. package/dist-server/service/data-archive/data-archive-query.js.map +1 -0
  20. package/dist-server/service/data-archive/data-archive-type.js +74 -0
  21. package/dist-server/service/data-archive/data-archive-type.js.map +1 -0
  22. package/dist-server/service/data-archive/data-archive.js +79 -0
  23. package/dist-server/service/data-archive/data-archive.js.map +1 -0
  24. package/dist-server/service/data-archive/index.js +9 -0
  25. package/dist-server/service/data-archive/index.js.map +1 -0
  26. package/dist-server/service/data-set/data-set-mutation.js +1 -1
  27. package/dist-server/service/data-set/data-set-mutation.js.map +1 -1
  28. package/dist-server/service/index.js +5 -2
  29. package/dist-server/service/index.js.map +1 -1
  30. package/dist-server/tsconfig.tsbuildinfo +1 -1
  31. package/dist-server/utils/config-resolver.js +33 -0
  32. package/dist-server/utils/config-resolver.js.map +1 -0
  33. package/dist-server/utils/index.js +5 -0
  34. package/dist-server/utils/index.js.map +1 -0
  35. package/helps/dataset/data-archive.md +9 -0
  36. package/helps/dataset/data-entry-list.md +2 -0
  37. package/helps/dataset/data-key-set.md +10 -0
  38. package/helps/dataset/data-ooc.md +11 -0
  39. package/helps/dataset/data-report-list.md +4 -0
  40. package/helps/dataset/data-sample-search.md +2 -0
  41. package/helps/dataset/data-sample.md +11 -0
  42. package/helps/dataset/data-sensor.md +13 -0
  43. package/helps/dataset/data-set.md +28 -0
  44. package/helps/dataset/ui/data-item-list.md +19 -0
  45. package/helps/dataset/ui/data-key-item-list.md +2 -0
  46. package/package.json +8 -8
  47. package/server/controllers/jasper-report.ts +5 -4
  48. package/server/controllers/shiny-report.ts +4 -7
  49. package/server/routes.ts +1 -0
  50. package/server/service/data-archive/data-archive-mutation.ts +282 -0
  51. package/server/service/data-archive/data-archive-query.ts +55 -0
  52. package/server/service/data-archive/data-archive-type.ts +49 -0
  53. package/server/service/data-archive/data-archive.ts +69 -0
  54. package/server/service/data-archive/index.ts +6 -0
  55. package/server/service/data-set/data-set-mutation.ts +1 -1
  56. package/server/service/index.ts +5 -2
  57. package/server/utils/config-resolver.ts +29 -0
  58. package/server/utils/index.ts +1 -0
  59. package/things-factory.config.js +4 -0
  60. package/translations/en.json +18 -3
  61. package/translations/ko.json +15 -1
  62. package/translations/ms.json +1 -0
  63. package/translations/zh.json +1 -0
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getEndpointUrl = void 0;
4
+ function getEndpointUrl(endpoint, subpath = '') {
5
+ const { url = '', protocol = 'http', host = '', port = 80 } = endpoint;
6
+ let _url = '';
7
+ let _protocol = protocol;
8
+ if (url) {
9
+ _url = url;
10
+ }
11
+ else {
12
+ const hosts = host.split('://');
13
+ // host에 protocol을 포함한 경우
14
+ if (hosts.length == 2) {
15
+ const _host = hosts[1];
16
+ _protocol = hosts[0];
17
+ // host에 port를 포함한 경우
18
+ const _hosts = _host.split(':');
19
+ if (_hosts.length > 1) {
20
+ _url = `${_hosts[0]}:${_hosts[1]}`;
21
+ }
22
+ else {
23
+ _url = `${_host}:${port}`;
24
+ }
25
+ }
26
+ else if (hosts.length == 1) {
27
+ _url = `${host}:${port}`;
28
+ }
29
+ }
30
+ return _protocol + '://' + _url.split('/').concat(subpath.split('/')).filter(x => x).join('/');
31
+ }
32
+ exports.getEndpointUrl = getEndpointUrl;
33
+ //# sourceMappingURL=config-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-resolver.js","sourceRoot":"","sources":["../../server/utils/config-resolver.ts"],"names":[],"mappings":";;;AACA,SAAgB,cAAc,CAAC,QAAY,EAAE,UAAe,EAAE;IAC1D,MAAM,EAAE,GAAG,GAAC,EAAE,EAAE,QAAQ,GAAC,MAAM,EAAE,IAAI,GAAC,EAAE,EAAE,IAAI,GAAC,EAAE,EAAE,GAAG,QAAQ,CAAA;IAE9D,IAAI,IAAI,GAAG,EAAE,CAAA;IACb,IAAI,SAAS,GAAG,QAAQ,CAAA;IAExB,IAAI,GAAG,EAAE;QACL,IAAI,GAAG,GAAG,CAAA;KACb;SAAM;QACH,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QAC/B,yBAAyB;QACzB,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,EAAE;YACnB,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;YACtB,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;YACpB,qBAAqB;YACrB,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;YAC/B,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE;gBACnB,IAAI,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,CAAA;aACrC;iBAAM;gBACH,IAAI,GAAG,GAAG,KAAK,IAAI,IAAI,EAAE,CAAA;aAC5B;SACJ;aAAM,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,EAAE;YAC1B,IAAI,GAAG,GAAG,IAAI,IAAI,IAAI,EAAE,CAAA;SAC3B;KACJ;IAED,OAAO,SAAS,GAAG,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAA,EAAE,CAAA,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAChG,CAAC;AA3BD,wCA2BC","sourcesContent":["\nexport function getEndpointUrl(endpoint:any, subpath:string=''):string {\n const { url='', protocol='http', host='', port=80 } = endpoint\n \n let _url = ''\n let _protocol = protocol\n\n if (url) {\n _url = url\n } else {\n const hosts = host.split('://')\n // host에 protocol을 포함한 경우\n if (hosts.length == 2) {\n const _host = hosts[1]\n _protocol = hosts[0]\n // host에 port를 포함한 경우\n const _hosts = _host.split(':')\n if (_hosts.length > 1) {\n _url = `${_hosts[0]}:${_hosts[1]}`\n } else {\n _url = `${_host}:${port}`\n }\n } else if (hosts.length == 1) { \n _url = `${host}:${port}`\n }\n }\n\n return _protocol + '://' + _url.split('/').concat(subpath.split('/')).filter(x=>x).join('/')\n}\n"]}
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ tslib_1.__exportStar(require("./config-resolver"), exports);
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/utils/index.ts"],"names":[],"mappings":";;;AAAA,4DAAiC","sourcesContent":["export * from './config-resolver'"]}
@@ -0,0 +1,9 @@
1
+ # DataArchive
2
+ 데이터셋의 원형 데이터(raw data)를 csv.gz 파일로 다운로드할 수 있습니다. <br>
3
+ 요청 후 시간이 수 분 소요될 수 있으며, 정상적으로 요청이 수행되면 다운로드 가능한 링크를 알림으로 알려줍니다. 알림의 링크를 클릭하여 파일을 다운로드 할 수 있습니다. <br>
4
+ 조회 페이지에서도 다운로드 주소를 확인할 수 있습니다. <br>
5
+ 다운로드 링크는 유효기간이 있으며, 만료되면 더 이상 다운로드할 수 없습니다. <br>
6
+ 다음의 경우들은 관리자에게 문의하시기 바랍니다.
7
+ - 요청시간 초과(timeout)가 발생
8
+ - 10분 경과 후에도 상태가 requested에서 변경되지 않음
9
+ - 최종 상태가 error로 표기
@@ -0,0 +1,2 @@
1
+ # DataEntryList
2
+ 데이타 셋에 정의한 입력용 화면을 연결합니다. 데이타 셋에 입력담당 역할이 필요합니다.
@@ -0,0 +1,10 @@
1
+ # DataKeySet
2
+ 데이터 키셋은 데이터샘플 조회에 인덱싱을 하기 위한 장치입니다. 데이터샘플 레코드의 data컬럼에 저장되는 오브젝트 key값을 기준으로 정보를 추가합니다. 데이터 키셋은 데이터셋에 지정할 수 있습니다. <br>
3
+ - 이름: 데이타 키셋 이름을 설정합니다. <br>
4
+ - 설명: 데이타 키셋에 대한 설명을 설정합니다. <br>
5
+ - 활성화: 사용 여부를 설정합니다. <br>
6
+ - 리포트용 화면종류:
7
+ - Jasper: Jasper 서버 설정에 따른 jasper 페이지를 렌더링합니다.
8
+ - Shiny: shiny 서버 설정에 따른 shiny 페이지를 렌더링합니다.
9
+
10
+ - 리포트용 화면: 리포트용 화면종류에 따른 값을 설정합니다. Jasper나 Shiny의 경우, 해당 서버에서 유효한 suburl이 필요합니다.<br>
@@ -0,0 +1,11 @@
1
+ # DataOOC
2
+ 데이타 이탈 내역을 조회하고 조치합니다.
3
+
4
+ - 데이타 이탈점 상세: 데이타 이탈점 상세 팝업 화면을 띄웁니다.
5
+ - 데이타 셋 아이템 별 CCP, QC 이탈 여부를 조회합니다.
6
+ - 데이타 이탈점 생성 부터 조치 이력을 조회합니다.
7
+ - 조치, 조치완료 내용을 입력하고 저장합니다.
8
+ - 상태: 생성, 조치, 조치완료 상태를 보여줍니다.
9
+ - 조치 활동: 조치 내용을 보여줍니다.
10
+
11
+ 나머지는 항목들은 [데이타 샘플 조회](./data-sample.md)와 동일합니다.
@@ -0,0 +1,4 @@
1
+ # DataReportList
2
+ 데이타 리포트 조회가 가능한 목록입니다. 데이타 셋의 리포트용 화면이 설정되어 있어야 합니다.
3
+ - 데이타 셋의 리포트용 화면종류에 따라 해당 페이지로 이동합니다.
4
+ - 데이타 셋의 모니터용 화면종류에 따라 해당 페이지로 이동합니다.
@@ -0,0 +1,2 @@
1
+ # DataSampleSearch
2
+ 데이타 샘플 검색 페이지입니다. 데이타 키셋에 정의된 데이타키로 데이타 샘플을 조회할 수 있습니다.
@@ -0,0 +1,11 @@
1
+ # DataSample
2
+ 데이타 샘플을 조회합니다.
3
+ - 이름: 데이타 샘플이 사용하는 데이타 셋의 이름입니다.
4
+ - 설명: 데이타 샘플이 사용하는 데이타 셋의 설명입니다.
5
+ - 데이타키 #: 데이타 키셋에서 정의한 데이타 키 목록이 순서대로 저장된 값입니다. 데이타 샘플의 data 오브젝트에 해당 키, 값이 포함되어 있어야 합니다.
6
+ - 관리한계 이탈여부: 데이타 샘플이 사용하는 데이타 셋의 항목 중 CCP 명세를 벗어나는 것이 있으면 true로 저장됩니다.
7
+ - 허용한계 이탈여부: 데이타 샘플이 사용하는 데이타 셋의 항목 중 QC 명세를 벗어나는 것이 있으면 true로 저장됩니다.
8
+ - 데이타: 키, 값 쌍으로 된 json형태의 가공된 데이터입니다.
9
+ - 데이타 원본: 가공하기 전, 데이타 원본(Raw Data)이 저장됩니다.
10
+ - 작업기준일: 교대 작업조 기준의 작업일입니다.
11
+ - 교대근무조: 교대 작업조 기준 시간의 작업조 이름입니다.
@@ -0,0 +1,13 @@
1
+ # DataSensor
2
+ 데이타 센서를 등록하고 조회합니다.<br>
3
+ - 이름: 데이타 센서 이름을 설정합니다.<br>
4
+ - 설명: 데이타 센서에 대한 설명을 설정합니다.<br>
5
+ - 활성화: 사용 여부를 설정합니다. 비활성화된 센서 데이타는 데이타 샘플로 저장하지 않습니다.<br>
6
+ - 디바이스 아이디: 유일한 센서의 아이디를 설정합니다 <br>
7
+ - 태그이름: (deprecated 검토 중)<br>
8
+ - 어플라이언스: 데이타 등록 권한을 가진 어플라이언스를 설정합니다.<br>
9
+ - 데이터셋: 해당 센서 데이터에 반영할 데이터셋을 설정합니다. <br>
10
+ - 모델: 센서 모델 정보를 설정합니다.<br>
11
+ - 브랜드: 센서 브랜드를 설정합니다.<br>
12
+ - 시리얼번호: 센서 시리얼번호를 설정합니다.<br>
13
+ - 네트워크마스크: netmask 정보를 설정합니다.<br>
@@ -0,0 +1,28 @@
1
+ # DataSet
2
+ 데이타 셋을 정의합니다.<br>
3
+ - [`데이타 아이템 등록 및 조회`](./ui/data-key-item-list.md): 데이타 아이템 화면을 팝업으로 띄웁니다.
4
+ - 데이타 입력: 데이타 입력 화면을 팝업으로 띄웁니다.
5
+ - 이름: 데이터셋 이름을 설정합니다.<br>
6
+ - 설명: 데이터셋에 대한 설명을 설정합니다.<br>
7
+ - 유형: 데이터셋 유형을 설정합니다.<br>
8
+ - 수동 수집: 사용자가 입력하는 데이터셋인 경우
9
+ - 자동 수집: 센서처럼 자동으로 입력되는 데이터셋인 경우
10
+ - 데이타 키셋: 데이타 키셋 마스터 페이지에서 등록한 항목을 적용합니다.<br>
11
+ - 파티션 키: (deprecated 예정)<br>
12
+ - 스케줄: 입력 주기를 설정합니다.<br>
13
+ - 타임존: 수집일 타임존을 설정합니다.<br>
14
+ - 관리자 역할: 데이터셋 관리자 역할을 설정합니다.<br>
15
+ - 입력담당 역할: 입력 권한을 부여할 역할을 설정합니다.<br>
16
+ - 입력용 화면종류: 입력 화면종류를 설정합니다. 주로 Board가 사용됩니다.<br>
17
+ - Generated: 구현된 화면을 사용합니다.
18
+ - Board: Board 화면을 사용합니다.
19
+ - Page: 구현된 페이지로 이동합니다. suburl이 필요합니다.
20
+ - External URL: 외부 페이지로 이동합니다. 전체 Url이 필요합니다.
21
+ - 입력용 화면: 화면 종류에 따른 값을 설정합니다. Board 화면 종류는 Board를 선택할 수 있습니다.<br>
22
+ - 모니터용 화면종류: 모니터용 화면종류를 설정합니다. 선택 목록은 입력용 화면종류와 동일합니다.<br>
23
+ - 모니터용 화면: 모니터용 화면종류에 해당하는 값을 설정합니다. <br>
24
+ - 리포트용 화면종류: 리포트용 화면종류를 설정합니다. 선택 목록은 입력용 화면종류에 두 종류가 더 있습니다.
25
+ - Jasper: Jasper 서버 설정에 따른 jasper 페이지를 렌더링합니다.
26
+ - Shiny: shiny 서버 설정에 따른 shiny 페이지를 렌더링합니다.
27
+ - 리포트용 화면: 리포트용 화면종류에 맞는 값을 설정합니다. Jasper나 Shiny의 경우, 해당 서버에서 유효한 suburl이 필요합니다. <br>
28
+ - 리포트용 템플릿: 리포트용 화면에 필요한 템플릿 파일을 업로드합니다.
@@ -0,0 +1,19 @@
1
+ # DataItemList
2
+ 데이타 셋의 아이템 목록을 등록합니다.
3
+ - 이름: 데이타 셋 아이템 이름입니다.
4
+ - 설명: 데이타 셋 아아템 설명입니다.
5
+ - 활성화: 사용 여부를 설정합니다.
6
+ - 숨기기: 레포트 등 2차 가공 데이터로 사용시, 사용자에게 보여줄 지 여부를 설정합니다.
7
+ - 태그이름: 오브젝트 키를 설정합니다.
8
+ - 유형: 데이타 셋 아이템 값의 데이타 타입 입니다.
9
+ - number
10
+ - text
11
+ - select
12
+ - boolean
13
+ - file
14
+ - Options: select 유형인 경우, name과 value 쌍을 추가로 설정합니다.
15
+ - 단위: kg, cm, 등 의 입력 단위를 설정합니다.
16
+ - 샘플수: 해당 아이템 값을 몇 개 입력받을지 설정합니다.
17
+ - 명세: 명세를 정의하면 이탈값이 OOC 데이터로 추가됩니다. 또한 해당 데이터샘플에 이탈여부가 기록됩니다.
18
+ - CCP: Critical Control Point Data Spec을 정의합니다.
19
+ - QC: Quality Contol Data Spec을 정의합니다.
@@ -0,0 +1,2 @@
1
+ # DataKeyItemList
2
+ 데이타 키 상세에서는 데이터샘플에 저장되는 data컬럼의 key정보를 지정합니다. '번역 키'를 deprecated 시킬 예정이오니, '이름'을 활용해주세요. 데이터 키에는 data컬럼에 저장되는 오브젝트 key값을 지정해야 합니다.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/dataset",
3
- "version": "6.0.0-alpha.5",
3
+ "version": "6.0.0-alpha.7",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "client/index.js",
6
6
  "things-factory": true,
@@ -34,14 +34,14 @@
34
34
  "@operato/shell": "^1.0.1",
35
35
  "@operato/styles": "^1.0.0",
36
36
  "@operato/utils": "^1.0.1",
37
- "@things-factory/auth-base": "^6.0.0-alpha.5",
38
- "@things-factory/aws-base": "^6.0.0-alpha.5",
39
- "@things-factory/board-service": "^6.0.0-alpha.5",
40
- "@things-factory/env": "^6.0.0-alpha.3",
41
- "@things-factory/shell": "^6.0.0-alpha.3",
42
- "@things-factory/work-shift": "^6.0.0-alpha.5",
37
+ "@things-factory/auth-base": "^6.0.0-alpha.7",
38
+ "@things-factory/aws-base": "^6.0.0-alpha.7",
39
+ "@things-factory/board-service": "^6.0.0-alpha.7",
40
+ "@things-factory/env": "^6.0.0-alpha.6",
41
+ "@things-factory/shell": "^6.0.0-alpha.7",
42
+ "@things-factory/work-shift": "^6.0.0-alpha.7",
43
43
  "cron-parser": "^4.3.0",
44
44
  "moment-timezone": "^0.5.34"
45
45
  },
46
- "gitHead": "78f7675c7abfc36b61155ddc8f2a0f86c1416742"
46
+ "gitHead": "3f2bc58876ef158759040c977b96b554c1220859"
47
47
  }
@@ -1,6 +1,8 @@
1
1
  import FormData from 'form-data'
2
2
  import fetch from 'node-fetch'
3
3
 
4
+ import { getEndpointUrl } from '../utils/config-resolver'
5
+
4
6
  import { STORAGE } from '@things-factory/attachment-base'
5
7
  import { AthenaController } from '@things-factory/aws-base'
6
8
  import { config } from '@things-factory/env'
@@ -8,7 +10,7 @@ import { config } from '@things-factory/env'
8
10
  const dataReportConfig = config.get('dataReport')
9
11
  const {
10
12
  jasper: {
11
- endpoint: { protocol: PROTOCOL, host: HOST, port: PORT },
13
+ endpoint: ENDPOINT,
12
14
  datasource: { database: DATABASE }
13
15
  }
14
16
  } = dataReportConfig || {
@@ -173,9 +175,8 @@ export async function renderJasperReport(context: any) {
173
175
  formData.append('parameters', JSON.stringify(parameters))
174
176
 
175
177
  const { reportView } = query
176
- const subpath = reportView ? reportView.split('/').filter(x => x).join('/') : ''
177
- const reportUrl = `${PROTOCOL || 'http'}://${HOST}:${PORT}/${subpath}`
178
- const response = await fetch(reportUrl, {
178
+ const url = getEndpointUrl(ENDPOINT, reportView)
179
+ const response = await fetch(url, {
179
180
  method: 'POST',
180
181
  body: formData
181
182
  })
@@ -1,13 +1,14 @@
1
1
  import FormData from 'form-data'
2
2
  import fetch from 'node-fetch'
3
3
  import { config } from '@things-factory/env'
4
+ import { getEndpointUrl } from '../utils/config-resolver'
4
5
 
5
6
  const querystring = require('querystring')
6
7
  const debug = require('debug')('things-factory:dataset:shiny-report')
7
8
  const dataReportConfig = config.get('dataReport')
8
9
  const {
9
10
  shiny: {
10
- endpoint: { protocol: PROTOCOL, host: HOST, port: PORT },
11
+ endpoint: ENDPOINT,
11
12
  datasource: { database: DATABASE }
12
13
  }
13
14
  } = Object.assign({
@@ -41,19 +42,15 @@ export async function renderShinyReport(context: any) {
41
42
  // domain: domain?.subdomain }
42
43
  // })
43
44
 
44
- const protocol = `${PROTOCOL || 'http'}://`
45
- const host = `${HOST}:${PORT}/`
46
45
  const subpath = query?.reportView || ''
47
46
 
48
47
  if (!subpath) {
49
48
  return context.body = ''
50
49
  }
51
50
 
52
- // filter not empty and join '/'
53
- const url = host.split('/').concat(subpath.split('/'))
54
- .filter(x => x).join('/')
51
+ const url = getEndpointUrl(ENDPOINT, subpath)
55
52
  // const reportUrl = `${protocol}${url}/?params=${jsonParams}`
56
- const reportUrl = `${protocol}${url}/?${urlParams}`
53
+ const reportUrl = `${url}/?${urlParams}`
57
54
 
58
55
  response.redirect(reportUrl)
59
56
  } catch(ex) {
package/server/routes.ts CHANGED
@@ -99,4 +99,5 @@ process.on('bootstrap-module-domain-private-route' as any, (app, domainPrivateRo
99
99
  let { reportType } = context.params
100
100
  context.body = `${reportType} Not Implemented.`
101
101
  })
102
+
102
103
  })
@@ -0,0 +1,282 @@
1
+ import i18next from 'i18next'
2
+ import fetch from 'node-fetch'
3
+ import {
4
+ Arg,
5
+ Ctx,
6
+ Directive,
7
+ Mutation,
8
+ Resolver
9
+ } from 'type-graphql'
10
+ import { In } from 'typeorm'
11
+
12
+ import { getEndpointUrl } from '../../utils/config-resolver'
13
+ import { DataArchive } from './data-archive'
14
+ import {
15
+ DataArchivePatch,
16
+ NewDataArchive
17
+ } from './data-archive-type'
18
+
19
+ const { config } = require('@things-factory/env')
20
+ const crypto = require('crypto')
21
+
22
+ @Resolver(DataArchive)
23
+ export class DataArchiveMutation {
24
+ /**
25
+ * It may use 'func-[dev]-data-set-download' in https://github.com/operatochef/serverless
26
+ * This function requests athena query and save the result as csv.gz in s3
27
+ */
28
+ @Directive('@privilege(category: "data-archive", privilege: "mutation", domainOwnerGranted: true)')
29
+ @Directive('@transaction')
30
+ @Mutation(returns => DataArchive, { description: 'To create new DataArchive' })
31
+ async generatePresignedUrl(
32
+ @Arg('patch') patch: DataArchivePatch,
33
+ @Ctx() context: ResolverContext
34
+ ): Promise<DataArchive> {
35
+ const { domain, user, tx, notify, lng } = context.state
36
+ // const dataArchiveRepo = tx.getRepository(DataArchive)
37
+ const t = i18next.getFixedT(lng, 'translations')
38
+ const {
39
+ dataset: {
40
+ endpoint,
41
+ datasource: { database }
42
+ }
43
+ } = config.get('dataArchive')
44
+
45
+ const body = JSON.stringify({
46
+ domain: domain?.subdomain,
47
+ database,
48
+ ...patch?.requestParams
49
+ })
50
+
51
+ const url = getEndpointUrl(endpoint)
52
+
53
+ const fetched = await fetch(`${url}`, {
54
+ method: 'POST',
55
+ headers: { 'Content-Type': 'application/json' },
56
+ body
57
+ })
58
+
59
+ const message = {
60
+ receivers: [user.email],
61
+ mode: 'in-app',
62
+ title: '',
63
+ body: '',
64
+ url: ''
65
+ }
66
+
67
+ if (fetched.ok) {
68
+ const response = await fetched.json()
69
+ patch.downloadUrl = response['presignedUrl']
70
+ patch.status = 'done'
71
+
72
+ message['title'] = t('title.data-archive downloads ready')
73
+ message['url'] = patch.downloadUrl
74
+ } else {
75
+ const error = await fetched.text()
76
+ console.log(error)
77
+ patch.status = 'error'
78
+
79
+ message['title'] = t('title.data-archive request failed')
80
+ // may not user-friendly message
81
+ message['body'] = error
82
+ }
83
+
84
+ const result = await this.updateDataArchive(patch.id, patch, context)
85
+
86
+ notify && notify(message)
87
+
88
+ return result
89
+ }
90
+
91
+ @Directive('@privilege(category: "data-archive", privilege: "mutation", domainOwnerGranted: true)')
92
+ @Directive('@transaction')
93
+ @Mutation(returns => DataArchive, { description: 'To create new DataArchive' })
94
+ async createDataArchive(
95
+ @Arg('dataArchive') dataArchive: NewDataArchive,
96
+ @Ctx() context: ResolverContext
97
+ ): Promise<DataArchive> {
98
+ const { domain, user, tx } = context.state
99
+ const dataArchiveRepo = tx.getRepository(DataArchive)
100
+
101
+ const result = await dataArchiveRepo.save({
102
+ ...dataArchive,
103
+ domain,
104
+ creator: user,
105
+ updater: user
106
+ })
107
+
108
+ return result
109
+ }
110
+
111
+ @Directive('@privilege(category: "data-archive", privilege: "mutation", domainOwnerGranted: true)')
112
+ @Directive('@transaction')
113
+ @Mutation(returns => DataArchive, { description: 'To modify DataArchive information' })
114
+ async updateDataArchive(
115
+ @Arg('id') id: string,
116
+ @Arg('patch') patch: DataArchivePatch,
117
+ @Ctx() context: ResolverContext
118
+ ): Promise<DataArchive> {
119
+ const { domain, user, tx } = context.state
120
+ const dataArchiveRepo = tx.getRepository(DataArchive)
121
+
122
+ const dataArchive = await dataArchiveRepo.findOne({
123
+ where: { domain: { id: domain.id }, id },
124
+ relations: ['domain', 'creator', 'updater']
125
+ })
126
+
127
+ const result = await dataArchiveRepo.save({
128
+ ...dataArchive,
129
+ ...patch,
130
+ updater: user
131
+ })
132
+
133
+ return result
134
+ }
135
+
136
+ @Directive('@privilege(category: "data-archive", privilege: "mutation", domainOwnerGranted: true)')
137
+ @Directive('@transaction')
138
+ @Mutation(returns => [DataArchive], { description: "To modify multiple DataArchives' information" })
139
+ async updateMultipleDataArchive(
140
+ @Arg('patches', type => [DataArchivePatch]) patches: DataArchivePatch[],
141
+ @Ctx() context: any
142
+ ): Promise<DataArchive[]> {
143
+ const { domain, user, tx } = context.state
144
+ const dataArchiveRepo = tx.getRepository(DataArchive)
145
+
146
+ let results = []
147
+ const _createRecords = patches.filter((patch: any) => patch.cuFlag.toUpperCase() === '+')
148
+ const _updateRecords = patches.filter((patch: any) => patch.cuFlag.toUpperCase() === 'M')
149
+
150
+ if (_createRecords.length > 0) {
151
+ const cuFlag = '+'
152
+ for (let i = 0; i < _createRecords.length; i++) {
153
+ const newRecord = _createRecords[i]
154
+
155
+ const result = await dataArchiveRepo.save({
156
+ ...newRecord,
157
+ domain,
158
+ creator: user,
159
+ updater: user
160
+ })
161
+
162
+ results.push({
163
+ ...result,
164
+ cuFlag
165
+ })
166
+ }
167
+ }
168
+
169
+ if (_updateRecords.length > 0) {
170
+ const cuFlag = 'M'
171
+ for (let i = 0; i < _updateRecords.length; i++) {
172
+ const newRecord = _updateRecords[i]
173
+ const dataArchive = await dataArchiveRepo.findOne(newRecord.id, {
174
+ relations: ['domain', 'creator', 'updater']
175
+ })
176
+
177
+ const result = await dataArchiveRepo.save({
178
+ ...dataArchive,
179
+ ...newRecord,
180
+ updater: user
181
+ })
182
+
183
+ results.push({
184
+ ...result,
185
+ cuFlag
186
+ })
187
+ }
188
+ }
189
+
190
+ return results
191
+ }
192
+
193
+ @Directive('@privilege(category: "data-archive", privilege: "mutation", domainOwnerGranted: true)')
194
+ @Directive('@transaction')
195
+ @Mutation(returns => Boolean, { description: 'To delete DataArchive' })
196
+ async deleteDataArchive(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<boolean> {
197
+ const { domain, tx } = context.state
198
+
199
+ await tx.getRepository(DataArchive).delete({ domain, id })
200
+ return true
201
+ }
202
+
203
+ @Directive('@privilege(category: "data-archive", privilege: "mutation", domainOwnerGranted: true)')
204
+ @Directive('@transaction')
205
+ @Mutation(returns => Boolean, { description: 'To delete multiple dataArchives' })
206
+ async deleteDataArchives(
207
+ @Arg('ids', type => [String]) ids: string[],
208
+ @Ctx() context: ResolverContext
209
+ ): Promise<boolean> {
210
+ const { domain, tx } = context.state
211
+
212
+ await tx.getRepository(DataArchive).delete({
213
+ domain,
214
+ id: In(ids)
215
+ })
216
+
217
+ return true
218
+ }
219
+
220
+ @Directive('@privilege(category: "data-archive", privilege: "mutation", domainOwnerGranted: true)')
221
+ @Directive('@transaction')
222
+ @Mutation(returns => Boolean, { description: 'To import multiple data-archives' })
223
+ async importDataArchives(
224
+ @Arg('dataArchives', type => [DataArchivePatch]) dataArchives: DataArchive[],
225
+ @Ctx() context: ResolverContext
226
+ ): Promise<boolean> {
227
+ const { domain, tx } = context.state
228
+ const dataArchiveRepo = tx.getRepository(DataArchive)
229
+
230
+ await Promise.all(
231
+ dataArchives.map(async (dataArchive: DataArchive) => {
232
+ const createdDataArchive: DataArchive = await dataArchiveRepo.save({
233
+ domain,
234
+ ...dataArchive
235
+ })
236
+ })
237
+ )
238
+
239
+ return true
240
+ }
241
+
242
+ @Directive('@privilege(category: "data-archive", privilege: "mutation", domainOwnerGranted: true)')
243
+ @Directive('@transaction')
244
+ @Mutation(returns => [DataArchive], { description: 'To copy multiple data-archives' })
245
+ async copyDataArchives(
246
+ @Arg('ids', type => [String]) ids: string[],
247
+ @Ctx() context: ResolverContext
248
+ ): Promise<DataArchive[]> {
249
+ const { domain, user, tx } = context.state
250
+ const dataArchiveRepo = tx.getRepository(DataArchive)
251
+
252
+ const originals = await dataArchiveRepo.find({
253
+ where: {
254
+ id: In(ids),
255
+ domain: { id: domain.id }
256
+ },
257
+ relations: ['domain']
258
+ })
259
+
260
+ if (originals.length == 0) {
261
+ return []
262
+ }
263
+
264
+ var newCopys = originals.map(dataArchive => {
265
+ let dataArchiveId = crypto.randomUUID()
266
+
267
+ return {
268
+ ...dataArchive,
269
+ id: dataArchiveId,
270
+ domain,
271
+ creator: user,
272
+ updater: user,
273
+ updatedAt: undefined,
274
+ createdAt: undefined
275
+ }
276
+ })
277
+
278
+ var copiedDataArchives = await dataArchiveRepo.save(newCopys)
279
+
280
+ return copiedDataArchives
281
+ }
282
+ }
@@ -0,0 +1,55 @@
1
+ import { Arg, Args, Ctx, Directive, FieldResolver, Query, Resolver, Root } from 'type-graphql'
2
+
3
+ import { Role, User } from '@things-factory/auth-base'
4
+ import { Domain, getQueryBuilderFromListParams, ListParam, getRepository } from '@things-factory/shell'
5
+
6
+ import { DataArchive } from './data-archive'
7
+ import { DataArchiveList } from './data-archive-type'
8
+
9
+ var parser = require('cron-parser')
10
+
11
+ @Resolver(DataArchive)
12
+ export class DataArchiveQuery {
13
+ @Directive('@privilege(category: "data-archive", privilege: "query", domainOwnerGranted: true)')
14
+ @Query(returns => DataArchive, { description: 'To fetch a DataArchive' })
15
+ async dataArchive(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<DataArchive> {
16
+ const { domain } = context.state
17
+
18
+ return await getRepository(DataArchive).findOne({
19
+ where: { domain: { id: domain.id }, id }
20
+ })
21
+ }
22
+
23
+ @Directive('@privilege(category: "data-archive", privilege: "query", domainOwnerGranted: true)')
24
+ @Query(returns => DataArchiveList, { description: 'To fetch multiple DataArchives' })
25
+ async dataArchives(@Args() params: ListParam, @Ctx() context: ResolverContext): Promise<DataArchiveList> {
26
+ const { domain } = context.state
27
+
28
+ const queryBuilder = getQueryBuilderFromListParams({
29
+ repository: getRepository(DataArchive),
30
+ params,
31
+ domain,
32
+ alias: 'dataArchive',
33
+ searchables: []
34
+ })
35
+
36
+ const [items, total] = await queryBuilder.getManyAndCount()
37
+
38
+ return { items, total }
39
+ }
40
+
41
+ @FieldResolver(type => Domain)
42
+ async domain(@Root() dataArchive: DataArchive): Promise<Domain> {
43
+ return await getRepository(Domain).findOneBy({ id: dataArchive.domainId })
44
+ }
45
+
46
+ @FieldResolver(type => User)
47
+ async updater(@Root() dataArchive: DataArchive): Promise<User> {
48
+ return await getRepository(User).findOneBy({ id: dataArchive.updaterId })
49
+ }
50
+
51
+ @FieldResolver(type => User)
52
+ async creator(@Root() dataArchive: DataArchive): Promise<User> {
53
+ return await getRepository(User).findOneBy({ id: dataArchive.creatorId })
54
+ }
55
+ }