From de10f67af78833d07eee9261924bcc041f99bc05 Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Mon, 30 Dec 2024 21:33:29 +0400 Subject: [PATCH] Guardian analytics: labels and top down data way points (#4523) #4322: Guardian analytics: labels and top down data way points --- api-gateway/src/api/service/policy-labels.ts | 872 ++++++ api-gateway/src/app.module.ts | 2 + api-gateway/src/helpers/guardians.ts | 259 +- api-gateway/src/helpers/task-manager.ts | 3 +- .../middlewares/validation/schemas/index.ts | 3 +- .../validation/schemas/policy-labels.dto.ts | 337 +++ .../src/database-modules/database-server.ts | 96 +- common/src/entity/index.ts | 4 +- common/src/entity/policy-label-document.ts | 189 ++ common/src/entity/policy-label.ts | 111 + common/src/hedera-modules/message/index.ts | 4 +- .../message/label-document-message.ts | 244 ++ .../hedera-modules/message/label-message.ts | 202 ++ .../hedera-modules/message/message-action.ts | 6 +- .../message/message-body.interface.ts | 68 + .../hedera-modules/message/message-server.ts | 10 +- .../hedera-modules/message/message-type.ts | 3 +- common/src/import-export/index.ts | 3 +- common/src/import-export/policy-label.ts | 383 +++ common/src/import-export/policy-statistic.ts | 42 +- common/src/import-export/schema-rule.ts | 2 +- docker-compose-indexer-build.yml | 7 + docker-compose-indexer.yml | 7 + frontend/package.json | 2 +- frontend/src/app/app-routing.module.ts | 95 +- frontend/src/app/app.module.ts | 250 +- .../async-progress.component.ts | 9 + .../import-entity-dialog.component.ts | 141 +- .../app/modules/common/models/conditions.ts | 337 +++ .../common/models/field-rule-validator.ts | 406 --- .../app/modules/common/models/field-rule.ts | 340 +-- .../modules/common/models/schema-formulas.ts | 68 +- .../app/modules/common/models/schema-node.ts | 3 +- .../modules/common/models/schema-scores.ts | 1 + .../modules/common/models/schema-variables.ts | 12 + .../modules/common/tree-graph/tree-node.ts | 5 +- .../report-block/report-block.component.scss | 3 +- ...request-document-block-dialog.component.ts | 6 +- .../request-document-block.component.ts | 7 +- .../policy-statistics.module.ts | 65 - .../document-view/document-view.component.ts | 7 +- .../schema-form-view.component.ts | 3 +- .../schema-form/schema-form.component.ts | 36 +- .../schema-rules/schema-rules.module.ts | 59 - .../new-policy-label-dialog.component.html} | 0 .../new-policy-label-dialog.component.scss} | 0 .../new-policy-label-dialog.component.ts | 78 + ...policy-label-preview-dialog.component.html | 176 ++ ...policy-label-preview-dialog.component.scss | 395 +++ .../policy-label-preview-dialog.component.ts | 119 + .../search-label-dialog.component.html | 175 ++ .../search-label-dialog.component.scss | 259 ++ .../search-label-dialog.component.ts | 192 ++ .../components/label-config.ts | 200 ++ .../components/nav-item.ts | 501 ++++ .../components/rules-config.ts | 607 ++++ .../policy-label-configuration.component.html | 852 ++++++ .../policy-label-configuration.component.scss | 1576 ++++++++++ .../policy-label-configuration.component.ts | 318 ++ ...abel-document-configuration.component.html | 318 ++ ...abel-document-configuration.component.scss | 381 +++ ...-label-document-configuration.component.ts | 396 +++ .../policy-label-document-view.component.html | 339 +++ .../policy-label-document-view.component.scss | 459 +++ .../policy-label-document-view.component.ts | 317 ++ .../policy-label-documents.component.html | 77 + .../policy-label-documents.component.scss} | 0 .../policy-label-documents.component.ts | 177 ++ .../policy-labels.component.html | 204 ++ .../policy-labels.component.scss} | 0 .../policy-labels/policy-labels.component.ts | 410 +++ ...ew-policy-statistics-dialog.component.html | 0 ...w-policy-statistics-dialog.component.scss} | 0 .../new-policy-statistics-dialog.component.ts | 0 .../score-dialog/score-dialog.component.html | 0 .../score-dialog/score-dialog.component.scss | 0 .../score-dialog/score-dialog.component.ts | 0 .../statistic-preview-dialog.component.html | 0 .../statistic-preview-dialog.component.scss | 0 .../statistic-preview-dialog.component.ts | 7 +- ...ic-assessment-configuration.component.html | 0 ...ic-assessment-configuration.component.scss | 0 ...stic-assessment-configuration.component.ts | 9 +- .../statistic-assessment-view.component.html | 0 .../statistic-assessment-view.component.scss | 0 .../statistic-assessment-view.component.ts | 12 +- .../statistic-assessments.component.html | 0 .../statistic-assessments.component.scss | 23 + .../statistic-assessments.component.ts | 0 ...ic-definition-configuration.component.html | 103 +- ...ic-definition-configuration.component.scss | 83 +- ...stic-definition-configuration.component.ts | 85 +- .../statistic-definitions.component.html | 1 + .../statistic-definitions.component.scss} | 0 .../statistic-definitions.component.ts | 4 +- .../new-schema-rule-dialog.component.html | 70 + .../new-schema-rule-dialog.component.scss | 58 + .../new-schema-rule-dialog.component.ts | 0 .../schema-rule-config-dialog.component.html | 0 .../schema-rule-config-dialog.component.scss | 0 .../schema-rule-config-dialog.component.ts | 3 +- ...schema-rules-preview-dialog.component.html | 0 ...schema-rules-preview-dialog.component.scss | 0 .../schema-rules-preview-dialog.component.ts | 7 +- .../schema-rule-configuration.component.html | 0 .../schema-rule-configuration.component.scss | 0 .../schema-rule-configuration.component.ts | 17 +- .../schema-rules/schema-rules.component.html | 0 .../schema-rules/schema-rules.component.scss | 28 + .../schema-rules/schema-rules.component.ts | 4 +- .../modules/statistics/statistics.module.ts | 102 + .../src/app/services/policy-labels.service.ts | 147 + .../src/app/themes/guardian/dropdown.scss | 19 + frontend/src/app/themes/guardian/index.scss | 2 + .../src/app/themes/guardian/menu-button.scss | 148 + .../src/app/themes/guardian/separator.scss | 16 + frontend/src/app/themes/guardian/tree.scss | 11 + frontend/src/app/utils/formula.ts | 36 - frontend/src/app/utils/index.ts | 3 +- .../src/app/utils/permissions-interface.ts | 3 +- .../src/app/views/new-header/menu.model.ts | 6 + .../assets/images/icons/16/circle-check.svg | 3 + .../assets/images/icons/16/collapse-down.svg | 3 + .../assets/images/icons/16/collapse-left.svg | 3 + .../assets/images/icons/16/collapse-right.svg | 3 + .../assets/images/icons/16/collapse-up.svg | 3 + frontend/src/assets/images/icons/16/edit.svg | 3 + .../src/assets/images/icons/16/folder.svg | 3 + .../src/assets/images/icons/16/import.svg | 3 + frontend/src/assets/images/icons/16/order.svg | 3 + frontend/src/assets/images/icons/16/stats.svg | 3 + .../assets/images/icons/32/circle-check.svg | 3 + frontend/src/assets/images/icons/back.svg | 3 + .../src/assets/images/icons/circle-check.svg | 3 + .../src/assets/images/icons/collapse-down.svg | 3 + .../src/assets/images/icons/collapse-left.svg | 3 + .../assets/images/icons/collapse-right.svg | 3 + .../src/assets/images/icons/collapse-up.svg | 3 + frontend/src/assets/images/icons/file.svg | 3 + frontend/src/assets/images/icons/folder.svg | 3 + frontend/src/assets/images/icons/preview.svg | 3 + frontend/src/assets/images/icons/publish.svg | 3 + frontend/src/assets/images/icons/save.svg | 3 + frontend/src/assets/images/icons/stats.svg | 3 + frontend/src/main.ts | 28 +- guardian-service/package.json | 3 +- .../src/api/helpers/policy-labels-helpers.ts | 269 ++ .../api/helpers/policy-statistics-helpers.ts | 24 +- .../src/api/helpers/schema-publish-helper.ts | 35 + .../src/api/policy-labels.service.ts | 899 ++++++ .../src/api/policy-statistics.service.ts | 3 +- guardian-service/src/app.ts | 13 +- guardian-service/src/helpers/notifier.ts | 6 + guardian-service/src/utils/formula.ts | 25 + .../src/api/services/entities.ts | 373 ++- indexer-api-gateway/src/dto/details/index.ts | 4 +- .../src/dto/details/label.details.ts | 170 ++ .../src/dto/details/nft.details.ts | 7 + .../src/dto/details/statistic.details.ts | 122 + .../src/dto/details/token.details.ts | 6 + .../src/dto/details/vp.details.ts | 36 + indexer-common/src/entity/index.ts | 2 +- indexer-common/src/entity/message.ts | 14 + indexer-common/src/messages/message-api.ts | 8 + indexer-frontend/src/app/app.routes.ts | 15 + .../app/components/header/header.component.ts | 16 + .../overview-form.component.html | 23 +- .../overview-form/overview-form.component.ts | 47 +- .../src/app/services/entities.service.ts | 58 + .../label-documents.component.html | 33 + .../label-documents.component.scss | 7 + .../label-documents.component.ts | 175 ++ .../collections/labels/labels.component.html | 36 + .../collections/labels/labels.component.scss | 0 .../collections/labels/labels.component.ts | 139 + .../statistic-documents.component.html | 34 + .../statistic-documents.component.scss | 0 .../statistic-documents.component.ts | 167 ++ .../statistics/statistics.component.html | 26 + .../statistics/statistics.component.scss | 0 .../statistics/statistics.component.ts | 139 + .../vc-documents/vc-documents.component.html | 26 +- .../vp-documents/vp-documents.component.html | 26 +- .../vp-documents/vp-documents.component.ts | 2 +- .../base-details/base-details.component.ts | 34 +- .../label-details.component.html | 35 + .../label-details.component.scss | 0 .../label-details/label-details.component.ts | 134 + .../label-document-details.component.html | 212 ++ .../label-document-details.component.scss | 265 ++ .../label-document-details.component.ts | 251 ++ .../nft-details/nft-details.component.html | 55 +- .../nft-details/nft-details.component.ts | 48 +- .../statistic-details.component.html | 35 + .../statistic-details.component.scss | 0 .../statistic-details.component.ts | 133 + .../token-details.component.html | 45 +- .../token-details/token-details.component.ts | 51 +- .../vp-document-details.component.html | 121 +- .../vp-document-details.component.ts | 65 +- .../src/app/views/home/home.component.html | 8 +- .../app/views/search/search.component.html | 11 +- .../src/app/views/search/search.component.ts | 8 + indexer-frontend/src/assets/i18n/en.json | 60 +- indexer-interfaces/src/index.ts | 1 + .../src/interfaces/details/index.ts | 2 + .../src/interfaces/details/label.details.ts | 74 + .../src/interfaces/details/nft.details.ts | 6 +- .../interfaces/details/statistic.details.ts | 74 + .../src/interfaces/details/token.details.ts | 5 +- .../src/interfaces/details/vp.details.ts | 32 +- .../src/interfaces/message.interface.ts | 4 +- .../src/interfaces/relationships.interface.ts | 2 +- indexer-interfaces/src/types/index.ts | 5 +- ...ssage-action.ts => message-action.type.ts} | 8 + .../src/types/message-status.type.ts | 12 + .../{message-type.ts => message-type.type.ts} | 4 + indexer-interfaces/src/validators/index.ts | 21 + .../interfaces/field-rule-status.ts | 7 + .../src/validators/interfaces/formula-type.ts | 6 + .../src/validators/interfaces/index.ts | 13 + .../src/validators/interfaces/policy-label.ts | 90 + .../interfaces/schema-rule-result.ts | 13 + .../src/validators/interfaces/schema-rules.ts | 81 + .../src/validators/interfaces/statistic.ts | 67 + .../validators/interfaces/step-document.ts | 5 + .../src/validators/interfaces/sub-step.ts | 6 + .../validators/interfaces/validate-status.ts | 7 + .../validators/interfaces/validator-node.ts | 10 + .../validators/interfaces/validator-step.ts | 17 + .../src/validators/interfaces/validator.ts | 13 + .../interfaces/variable-rule-data.ts | 3 + .../label-validator/item-group-validator.ts | 144 + .../label-validator/item-label-validator.ts | 132 + .../label-validator/item-node-validator.ts | 141 + .../label-validator/item-rule-validator.ts | 360 +++ .../item-statistic-validator.ts | 339 +++ .../label-validator/label-validator.ts | 274 ++ .../validators/label-validator/namespace.ts | 88 + .../src/validators/label-validator/score.ts | 30 + .../label-validator/variable-validator.ts | 9 + .../document-field-validator.ts | 23 + .../document-field-validators.ts | 44 + .../rule-validator/document-validator.ts | 88 + .../rule-validator/document-validators.ts | 80 + .../rule-validator/field-validator.ts | 21 + .../rule-validator/rule-validator.ts | 133 + .../validators/statistic-validator/formula.ts | 39 + .../validators/statistic-validator/score.ts | 73 + .../statistic-validator/variables.ts | 52 + .../src/validators/utils/formula.ts | 37 + indexer-service/configs/.env.worker | 4 +- indexer-service/configs/.env.worker.develop | 2 + indexer-service/configs/.env.worker.template | 4 +- indexer-service/src/api/analytics.service.ts | 12 +- indexer-service/src/api/entities.service.ts | 360 ++- indexer-service/src/api/filters.service.ts | 9 + indexer-service/src/api/search.service.ts | 61 +- indexer-service/src/app.ts | 5 +- indexer-service/src/helpers/load-files.ts | 4 +- indexer-service/src/helpers/parsers/index.ts | 1 + .../src/helpers/parsers/label.parser.ts | 20 + .../src/helpers/parsers/module.parser.ts | 57 +- .../src/helpers/parsers/policy.parser.ts | 159 +- .../src/helpers/parsers/tool.parser.ts | 65 +- .../src/helpers/synchronizers/index.ts | 3 +- .../helpers/synchronizers/synchronize-all.ts | 4 + .../synchronizers/synchronize-labels.ts | 164 + .../synchronizers/synchronize-policy.ts | 3 + .../helpers/synchronizers/synchronize-vp.ts | 158 +- indexer-service/src/migrations/v3-0-0.1.ts | 37 + indexer-service/src/migrations/v3-0-0.2.ts | 73 + indexer-service/src/migrations/v3-0-0.3.ts | 48 + indexer-worker-service/configs/.env.worker | 5 + .../configs/.env.worker.develop | 7 +- .../configs/.env.worker.template | 4 + .../src/api/channel.service.ts | 20 +- indexer-worker-service/src/app.ts | 25 +- .../src/loaders/ipfs-service.ts | 42 +- .../src/loaders/ipfs/base-node.ts | 8 +- .../src/loaders/ipfs/http-node.ts | 65 +- .../src/services/file-service.ts | 65 + .../src/services/message-service.ts | 37 +- .../src/services/topic-service.ts | 13 +- indexer-worker-service/src/utils/parser.ts | 77 +- interfaces/src/helpers/permissions-helper.ts | 9 + interfaces/src/index.ts | 1 + interfaces/src/interface/index.ts | 3 +- .../src/interface/policy-label.interface.ts | 91 + .../src/interface/statistic.interface.ts | 5 + .../src/type/messages/message-api.type.ts | 18 + .../src/type/notification-action.type.ts | 1 + interfaces/src/type/permissions.type.ts | 21 +- interfaces/src/type/schema-category.type.ts | 3 +- interfaces/src/type/task-action.type.ts | 1 + interfaces/src/type/topic.type.ts | 3 +- interfaces/src/validators/index.ts | 28 + .../label-validator/interfaces/node.ts | 10 + .../label-validator/interfaces/status.ts | 7 + .../interfaces/step-document.ts | 5 + .../label-validator/interfaces/step.ts | 17 + .../label-validator/interfaces/sub-step.ts | 6 + .../label-validator/interfaces/validator.ts | 13 + .../label-validator/item-group-validator.ts | 145 + .../label-validator/item-label-validator.ts | 132 + .../label-validator/item-node-validator.ts | 142 + .../label-validator/item-rule-validator.ts | 356 +++ .../item-statistic-validator.ts | 347 +++ .../label-validator/label-validator.ts | 275 ++ .../validators/label-validator/namespace.ts | 88 + .../src/validators/label-validator/score.ts | 30 + .../label-validator/variable-validator.ts | 9 + .../document-field-validator.ts | 23 + .../document-field-validators.ts | 45 + .../rule-validator/document-validator.ts | 89 + .../rule-validator/document-validators.ts | 81 + .../rule-validator/field-validator.ts | 21 + .../rule-validator/interfaces/formula-type.ts | 6 + .../rule-validator/interfaces/rule.ts | 3 + .../rule-validator/interfaces/status.ts | 7 + .../interfaces/validate-result.ts | 13 + .../rule-validator/rule-validator.ts | 136 + .../validators/statistic-validator/formula.ts | 40 + .../validators/statistic-validator/score.ts | 73 + .../statistic-validator/variables.ts | 52 + interfaces/src/validators/utils/formula.ts | 27 + interfaces/tsconfig.json | 2 +- .../blocks/request-vc-document-block-addon.ts | 2 +- .../helpers/decorators/data-source-block.ts | 2 +- .../src/policy-engine/helpers/utils.ts | 13 +- swagger-indexer.yaml | 2630 ++++++++++++----- swagger.yaml | 938 +++++- yarn.lock | 80 +- 333 files changed, 26787 insertions(+), 2479 deletions(-) create mode 100644 api-gateway/src/api/service/policy-labels.ts create mode 100644 api-gateway/src/middlewares/validation/schemas/policy-labels.dto.ts create mode 100644 common/src/entity/policy-label-document.ts create mode 100644 common/src/entity/policy-label.ts create mode 100644 common/src/hedera-modules/message/label-document-message.ts create mode 100644 common/src/hedera-modules/message/label-message.ts create mode 100644 common/src/import-export/policy-label.ts create mode 100644 frontend/src/app/modules/common/models/conditions.ts delete mode 100644 frontend/src/app/modules/common/models/field-rule-validator.ts delete mode 100644 frontend/src/app/modules/policy-statistics/policy-statistics.module.ts delete mode 100644 frontend/src/app/modules/schema-rules/schema-rules.module.ts rename frontend/src/app/modules/{schema-rules/dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component.html => statistics/policy-labels/dialogs/new-policy-label-dialog/new-policy-label-dialog.component.html} (100%) rename frontend/src/app/modules/{policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.scss => statistics/policy-labels/dialogs/new-policy-label-dialog/new-policy-label-dialog.component.scss} (100%) create mode 100644 frontend/src/app/modules/statistics/policy-labels/dialogs/new-policy-label-dialog/new-policy-label-dialog.component.ts create mode 100644 frontend/src/app/modules/statistics/policy-labels/dialogs/policy-label-preview-dialog/policy-label-preview-dialog.component.html create mode 100644 frontend/src/app/modules/statistics/policy-labels/dialogs/policy-label-preview-dialog/policy-label-preview-dialog.component.scss create mode 100644 frontend/src/app/modules/statistics/policy-labels/dialogs/policy-label-preview-dialog/policy-label-preview-dialog.component.ts create mode 100644 frontend/src/app/modules/statistics/policy-labels/dialogs/search-label-dialog/search-label-dialog.component.html create mode 100644 frontend/src/app/modules/statistics/policy-labels/dialogs/search-label-dialog/search-label-dialog.component.scss create mode 100644 frontend/src/app/modules/statistics/policy-labels/dialogs/search-label-dialog/search-label-dialog.component.ts create mode 100644 frontend/src/app/modules/statistics/policy-labels/policy-label-configuration/components/label-config.ts create mode 100644 frontend/src/app/modules/statistics/policy-labels/policy-label-configuration/components/nav-item.ts create mode 100644 frontend/src/app/modules/statistics/policy-labels/policy-label-configuration/components/rules-config.ts create mode 100644 frontend/src/app/modules/statistics/policy-labels/policy-label-configuration/policy-label-configuration.component.html create mode 100644 frontend/src/app/modules/statistics/policy-labels/policy-label-configuration/policy-label-configuration.component.scss create mode 100644 frontend/src/app/modules/statistics/policy-labels/policy-label-configuration/policy-label-configuration.component.ts create mode 100644 frontend/src/app/modules/statistics/policy-labels/policy-label-document-configuration/policy-label-document-configuration.component.html create mode 100644 frontend/src/app/modules/statistics/policy-labels/policy-label-document-configuration/policy-label-document-configuration.component.scss create mode 100644 frontend/src/app/modules/statistics/policy-labels/policy-label-document-configuration/policy-label-document-configuration.component.ts create mode 100644 frontend/src/app/modules/statistics/policy-labels/policy-label-document-view/policy-label-document-view.component.html create mode 100644 frontend/src/app/modules/statistics/policy-labels/policy-label-document-view/policy-label-document-view.component.scss create mode 100644 frontend/src/app/modules/statistics/policy-labels/policy-label-document-view/policy-label-document-view.component.ts create mode 100644 frontend/src/app/modules/statistics/policy-labels/policy-label-documents/policy-label-documents.component.html rename frontend/src/app/modules/{policy-statistics/statistic-assessments/statistic-assessments.component.scss => statistics/policy-labels/policy-label-documents/policy-label-documents.component.scss} (100%) create mode 100644 frontend/src/app/modules/statistics/policy-labels/policy-label-documents/policy-label-documents.component.ts create mode 100644 frontend/src/app/modules/statistics/policy-labels/policy-labels/policy-labels.component.html rename frontend/src/app/modules/{policy-statistics/statistic-definitions/statistic-definitions.component.scss => statistics/policy-labels/policy-labels/policy-labels.component.scss} (100%) create mode 100644 frontend/src/app/modules/statistics/policy-labels/policy-labels/policy-labels.component.ts rename frontend/src/app/modules/{ => statistics}/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.html (100%) rename frontend/src/app/modules/{schema-rules/dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component.scss => statistics/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.scss} (100%) rename frontend/src/app/modules/{ => statistics}/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.ts (100%) rename frontend/src/app/modules/{ => statistics}/policy-statistics/dialogs/score-dialog/score-dialog.component.html (100%) rename frontend/src/app/modules/{ => statistics}/policy-statistics/dialogs/score-dialog/score-dialog.component.scss (100%) rename frontend/src/app/modules/{ => statistics}/policy-statistics/dialogs/score-dialog/score-dialog.component.ts (100%) rename frontend/src/app/modules/{ => statistics}/policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component.html (100%) rename frontend/src/app/modules/{ => statistics}/policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component.scss (100%) rename frontend/src/app/modules/{ => statistics}/policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component.ts (93%) rename frontend/src/app/modules/{ => statistics}/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.html (100%) rename frontend/src/app/modules/{ => statistics}/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.scss (100%) rename frontend/src/app/modules/{ => statistics}/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts (98%) rename frontend/src/app/modules/{ => statistics}/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.html (100%) rename frontend/src/app/modules/{ => statistics}/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.scss (100%) rename frontend/src/app/modules/{ => statistics}/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.ts (95%) rename frontend/src/app/modules/{ => statistics}/policy-statistics/statistic-assessments/statistic-assessments.component.html (100%) create mode 100644 frontend/src/app/modules/statistics/policy-statistics/statistic-assessments/statistic-assessments.component.scss rename frontend/src/app/modules/{ => statistics}/policy-statistics/statistic-assessments/statistic-assessments.component.ts (100%) rename frontend/src/app/modules/{ => statistics}/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html (91%) rename frontend/src/app/modules/{ => statistics}/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.scss (94%) rename frontend/src/app/modules/{ => statistics}/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.ts (86%) rename frontend/src/app/modules/{ => statistics}/policy-statistics/statistic-definitions/statistic-definitions.component.html (99%) rename frontend/src/app/modules/{schema-rules/schema-rules/schema-rules.component.scss => statistics/policy-statistics/statistic-definitions/statistic-definitions.component.scss} (100%) rename frontend/src/app/modules/{ => statistics}/policy-statistics/statistic-definitions/statistic-definitions.component.ts (98%) create mode 100644 frontend/src/app/modules/statistics/schema-rules/dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component.html create mode 100644 frontend/src/app/modules/statistics/schema-rules/dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component.scss rename frontend/src/app/modules/{ => statistics}/schema-rules/dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component.ts (100%) rename frontend/src/app/modules/{ => statistics}/schema-rules/dialogs/schema-rule-config-dialog/schema-rule-config-dialog.component.html (100%) rename frontend/src/app/modules/{ => statistics}/schema-rules/dialogs/schema-rule-config-dialog/schema-rule-config-dialog.component.scss (100%) rename frontend/src/app/modules/{ => statistics}/schema-rules/dialogs/schema-rule-config-dialog/schema-rule-config-dialog.component.ts (97%) rename frontend/src/app/modules/{ => statistics}/schema-rules/dialogs/schema-rules-preview-dialog/schema-rules-preview-dialog.component.html (100%) rename frontend/src/app/modules/{ => statistics}/schema-rules/dialogs/schema-rules-preview-dialog/schema-rules-preview-dialog.component.scss (100%) rename frontend/src/app/modules/{ => statistics}/schema-rules/dialogs/schema-rules-preview-dialog/schema-rules-preview-dialog.component.ts (86%) rename frontend/src/app/modules/{ => statistics}/schema-rules/schema-rule-configuration/schema-rule-configuration.component.html (100%) rename frontend/src/app/modules/{ => statistics}/schema-rules/schema-rule-configuration/schema-rule-configuration.component.scss (100%) rename frontend/src/app/modules/{ => statistics}/schema-rules/schema-rule-configuration/schema-rule-configuration.component.ts (96%) rename frontend/src/app/modules/{ => statistics}/schema-rules/schema-rules/schema-rules.component.html (100%) create mode 100644 frontend/src/app/modules/statistics/schema-rules/schema-rules/schema-rules.component.scss rename frontend/src/app/modules/{ => statistics}/schema-rules/schema-rules/schema-rules.component.ts (98%) create mode 100644 frontend/src/app/modules/statistics/statistics.module.ts create mode 100644 frontend/src/app/services/policy-labels.service.ts create mode 100644 frontend/src/app/themes/guardian/menu-button.scss create mode 100644 frontend/src/app/themes/guardian/tree.scss delete mode 100644 frontend/src/app/utils/formula.ts create mode 100644 frontend/src/assets/images/icons/16/circle-check.svg create mode 100644 frontend/src/assets/images/icons/16/collapse-down.svg create mode 100644 frontend/src/assets/images/icons/16/collapse-left.svg create mode 100644 frontend/src/assets/images/icons/16/collapse-right.svg create mode 100644 frontend/src/assets/images/icons/16/collapse-up.svg create mode 100644 frontend/src/assets/images/icons/16/edit.svg create mode 100644 frontend/src/assets/images/icons/16/folder.svg create mode 100644 frontend/src/assets/images/icons/16/import.svg create mode 100644 frontend/src/assets/images/icons/16/order.svg create mode 100644 frontend/src/assets/images/icons/16/stats.svg create mode 100644 frontend/src/assets/images/icons/32/circle-check.svg create mode 100644 frontend/src/assets/images/icons/back.svg create mode 100644 frontend/src/assets/images/icons/circle-check.svg create mode 100644 frontend/src/assets/images/icons/collapse-down.svg create mode 100644 frontend/src/assets/images/icons/collapse-left.svg create mode 100644 frontend/src/assets/images/icons/collapse-right.svg create mode 100644 frontend/src/assets/images/icons/collapse-up.svg create mode 100644 frontend/src/assets/images/icons/file.svg create mode 100644 frontend/src/assets/images/icons/folder.svg create mode 100644 frontend/src/assets/images/icons/preview.svg create mode 100644 frontend/src/assets/images/icons/publish.svg create mode 100644 frontend/src/assets/images/icons/save.svg create mode 100644 frontend/src/assets/images/icons/stats.svg create mode 100644 guardian-service/src/api/helpers/policy-labels-helpers.ts create mode 100644 guardian-service/src/api/policy-labels.service.ts create mode 100644 guardian-service/src/utils/formula.ts create mode 100644 indexer-api-gateway/src/dto/details/label.details.ts create mode 100644 indexer-api-gateway/src/dto/details/statistic.details.ts create mode 100644 indexer-frontend/src/app/views/collections/label-documents/label-documents.component.html create mode 100644 indexer-frontend/src/app/views/collections/label-documents/label-documents.component.scss create mode 100644 indexer-frontend/src/app/views/collections/label-documents/label-documents.component.ts create mode 100644 indexer-frontend/src/app/views/collections/labels/labels.component.html create mode 100644 indexer-frontend/src/app/views/collections/labels/labels.component.scss create mode 100644 indexer-frontend/src/app/views/collections/labels/labels.component.ts create mode 100644 indexer-frontend/src/app/views/collections/statistic-documents/statistic-documents.component.html create mode 100644 indexer-frontend/src/app/views/collections/statistic-documents/statistic-documents.component.scss create mode 100644 indexer-frontend/src/app/views/collections/statistic-documents/statistic-documents.component.ts create mode 100644 indexer-frontend/src/app/views/collections/statistics/statistics.component.html create mode 100644 indexer-frontend/src/app/views/collections/statistics/statistics.component.scss create mode 100644 indexer-frontend/src/app/views/collections/statistics/statistics.component.ts create mode 100644 indexer-frontend/src/app/views/details/label-details/label-details.component.html create mode 100644 indexer-frontend/src/app/views/details/label-details/label-details.component.scss create mode 100644 indexer-frontend/src/app/views/details/label-details/label-details.component.ts create mode 100644 indexer-frontend/src/app/views/details/label-document-details/label-document-details.component.html create mode 100644 indexer-frontend/src/app/views/details/label-document-details/label-document-details.component.scss create mode 100644 indexer-frontend/src/app/views/details/label-document-details/label-document-details.component.ts create mode 100644 indexer-frontend/src/app/views/details/statistic-details/statistic-details.component.html create mode 100644 indexer-frontend/src/app/views/details/statistic-details/statistic-details.component.scss create mode 100644 indexer-frontend/src/app/views/details/statistic-details/statistic-details.component.ts create mode 100644 indexer-interfaces/src/interfaces/details/label.details.ts create mode 100644 indexer-interfaces/src/interfaces/details/statistic.details.ts rename indexer-interfaces/src/types/{message-action.ts => message-action.type.ts} (77%) create mode 100644 indexer-interfaces/src/types/message-status.type.ts rename indexer-interfaces/src/types/{message-type.ts => message-type.type.ts} (77%) create mode 100644 indexer-interfaces/src/validators/index.ts create mode 100644 indexer-interfaces/src/validators/interfaces/field-rule-status.ts create mode 100644 indexer-interfaces/src/validators/interfaces/formula-type.ts create mode 100644 indexer-interfaces/src/validators/interfaces/index.ts create mode 100644 indexer-interfaces/src/validators/interfaces/policy-label.ts create mode 100644 indexer-interfaces/src/validators/interfaces/schema-rule-result.ts create mode 100644 indexer-interfaces/src/validators/interfaces/schema-rules.ts create mode 100644 indexer-interfaces/src/validators/interfaces/statistic.ts create mode 100644 indexer-interfaces/src/validators/interfaces/step-document.ts create mode 100644 indexer-interfaces/src/validators/interfaces/sub-step.ts create mode 100644 indexer-interfaces/src/validators/interfaces/validate-status.ts create mode 100644 indexer-interfaces/src/validators/interfaces/validator-node.ts create mode 100644 indexer-interfaces/src/validators/interfaces/validator-step.ts create mode 100644 indexer-interfaces/src/validators/interfaces/validator.ts create mode 100644 indexer-interfaces/src/validators/interfaces/variable-rule-data.ts create mode 100644 indexer-interfaces/src/validators/label-validator/item-group-validator.ts create mode 100644 indexer-interfaces/src/validators/label-validator/item-label-validator.ts create mode 100644 indexer-interfaces/src/validators/label-validator/item-node-validator.ts create mode 100644 indexer-interfaces/src/validators/label-validator/item-rule-validator.ts create mode 100644 indexer-interfaces/src/validators/label-validator/item-statistic-validator.ts create mode 100644 indexer-interfaces/src/validators/label-validator/label-validator.ts create mode 100644 indexer-interfaces/src/validators/label-validator/namespace.ts create mode 100644 indexer-interfaces/src/validators/label-validator/score.ts create mode 100644 indexer-interfaces/src/validators/label-validator/variable-validator.ts create mode 100644 indexer-interfaces/src/validators/rule-validator/document-field-validator.ts create mode 100644 indexer-interfaces/src/validators/rule-validator/document-field-validators.ts create mode 100644 indexer-interfaces/src/validators/rule-validator/document-validator.ts create mode 100644 indexer-interfaces/src/validators/rule-validator/document-validators.ts create mode 100644 indexer-interfaces/src/validators/rule-validator/field-validator.ts create mode 100644 indexer-interfaces/src/validators/rule-validator/rule-validator.ts create mode 100644 indexer-interfaces/src/validators/statistic-validator/formula.ts create mode 100644 indexer-interfaces/src/validators/statistic-validator/score.ts create mode 100644 indexer-interfaces/src/validators/statistic-validator/variables.ts create mode 100644 indexer-interfaces/src/validators/utils/formula.ts create mode 100644 indexer-service/src/helpers/parsers/label.parser.ts create mode 100644 indexer-service/src/helpers/synchronizers/synchronize-labels.ts create mode 100644 indexer-service/src/migrations/v3-0-0.1.ts create mode 100644 indexer-service/src/migrations/v3-0-0.2.ts create mode 100644 indexer-service/src/migrations/v3-0-0.3.ts create mode 100644 indexer-worker-service/src/services/file-service.ts create mode 100644 interfaces/src/interface/policy-label.interface.ts create mode 100644 interfaces/src/validators/index.ts create mode 100644 interfaces/src/validators/label-validator/interfaces/node.ts create mode 100644 interfaces/src/validators/label-validator/interfaces/status.ts create mode 100644 interfaces/src/validators/label-validator/interfaces/step-document.ts create mode 100644 interfaces/src/validators/label-validator/interfaces/step.ts create mode 100644 interfaces/src/validators/label-validator/interfaces/sub-step.ts create mode 100644 interfaces/src/validators/label-validator/interfaces/validator.ts create mode 100644 interfaces/src/validators/label-validator/item-group-validator.ts create mode 100644 interfaces/src/validators/label-validator/item-label-validator.ts create mode 100644 interfaces/src/validators/label-validator/item-node-validator.ts create mode 100644 interfaces/src/validators/label-validator/item-rule-validator.ts create mode 100644 interfaces/src/validators/label-validator/item-statistic-validator.ts create mode 100644 interfaces/src/validators/label-validator/label-validator.ts create mode 100644 interfaces/src/validators/label-validator/namespace.ts create mode 100644 interfaces/src/validators/label-validator/score.ts create mode 100644 interfaces/src/validators/label-validator/variable-validator.ts create mode 100644 interfaces/src/validators/rule-validator/document-field-validator.ts create mode 100644 interfaces/src/validators/rule-validator/document-field-validators.ts create mode 100644 interfaces/src/validators/rule-validator/document-validator.ts create mode 100644 interfaces/src/validators/rule-validator/document-validators.ts create mode 100644 interfaces/src/validators/rule-validator/field-validator.ts create mode 100644 interfaces/src/validators/rule-validator/interfaces/formula-type.ts create mode 100644 interfaces/src/validators/rule-validator/interfaces/rule.ts create mode 100644 interfaces/src/validators/rule-validator/interfaces/status.ts create mode 100644 interfaces/src/validators/rule-validator/interfaces/validate-result.ts create mode 100644 interfaces/src/validators/rule-validator/rule-validator.ts create mode 100644 interfaces/src/validators/statistic-validator/formula.ts create mode 100644 interfaces/src/validators/statistic-validator/score.ts create mode 100644 interfaces/src/validators/statistic-validator/variables.ts create mode 100644 interfaces/src/validators/utils/formula.ts diff --git a/api-gateway/src/api/service/policy-labels.ts b/api-gateway/src/api/service/policy-labels.ts new file mode 100644 index 0000000000..468a39d779 --- /dev/null +++ b/api-gateway/src/api/service/policy-labels.ts @@ -0,0 +1,872 @@ +import { IAuthUser, PinoLogger, RunFunctionAsync } from '@guardian/common'; +import { Body, Controller, Delete, Get, HttpCode, HttpException, HttpStatus, Param, Post, Put, Query, Response } from '@nestjs/common'; +import { Permissions, TaskAction } from '@guardian/interfaces'; +import { ApiBody, ApiInternalServerErrorResponse, ApiOkResponse, ApiOperation, ApiTags, ApiQuery, ApiExtraModels, ApiParam } from '@nestjs/swagger'; +import { Examples, InternalServerErrorDTO, PolicyLabelDocumentDTO, PolicyLabelDTO, PolicyLabelRelationshipsDTO, VcDocumentDTO, pageHeader, PolicyLabelDocumentRelationshipsDTO, PolicyLabelComponentsDTO, PolicyLabelFiltersDTO, TaskDTO } from '#middlewares'; +import { Guardians, InternalException, EntityOwner, TaskManager, ServiceError } from '#helpers'; +import { AuthUser, Auth } from '#auth'; + +@Controller('policy-labels') +@ApiTags('policy-labels') +export class PolicyLabelsApi { + constructor(private readonly logger: PinoLogger) { } + + /** + * Creates a new policy label + */ + @Post('/') + @Auth(Permissions.STATISTICS_LABEL_CREATE) + @ApiOperation({ + summary: 'Creates a new policy label.', + description: 'Creates a new policy label.', + }) + @ApiBody({ + description: 'Configuration.', + type: PolicyLabelDTO, + required: true + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: PolicyLabelDTO, + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(PolicyLabelDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.CREATED) + async createPolicyLabel( + @AuthUser() user: IAuthUser, + @Body() label: PolicyLabelDTO + ): Promise { + try { + if (!label) { + throw new HttpException('Invalid config.', HttpStatus.UNPROCESSABLE_ENTITY); + } + const owner = new EntityOwner(user); + const guardian = new Guardians(); + return await guardian.createPolicyLabel(label, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Get page + */ + @Get('/') + @Auth(Permissions.STATISTICS_LABEL_READ) + @ApiOperation({ + summary: 'Return a list of all policy labels.', + description: 'Returns all policy labels.', + }) + @ApiQuery({ + name: 'pageIndex', + type: Number, + description: 'The number of pages to skip before starting to collect the result set', + required: false, + example: 0 + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + description: 'The numbers of items to return', + required: false, + example: 20 + }) + @ApiQuery({ + name: 'policyInstanceTopicId', + type: String, + description: 'Policy Instance Topic Id', + required: false, + example: Examples.ACCOUNT_ID + }) + @ApiOkResponse({ + description: 'Successful operation.', + isArray: true, + headers: pageHeader, + type: PolicyLabelDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(PolicyLabelDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async getPolicyLabels( + @AuthUser() user: IAuthUser, + @Response() res: any, + @Query('pageIndex') pageIndex?: number, + @Query('pageSize') pageSize?: number, + @Query('policyInstanceTopicId') policyInstanceTopicId?: string + ): Promise { + try { + const owner = new EntityOwner(user); + const guardians = new Guardians(); + const { items, count } = await guardians.getPolicyLabels({ + policyInstanceTopicId, pageIndex, pageSize + }, owner); + return res.header('X-Total-Count', count).send(items); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Get policy label by id + */ + @Get('/:definitionId') + @Auth(Permissions.STATISTICS_LABEL_READ) + @ApiOperation({ + summary: 'Retrieves policy label.', + description: 'Retrieves policy label for the specified ID.' + }) + @ApiParam({ + name: 'definitionId', + type: String, + description: 'policy label Identifier', + required: true, + example: Examples.DB_ID + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: PolicyLabelDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(PolicyLabelDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async getPolicyLabelById( + @AuthUser() user: IAuthUser, + @Param('definitionId') definitionId: string + ): Promise { + try { + if (!definitionId) { + throw new HttpException('Invalid ID.', HttpStatus.UNPROCESSABLE_ENTITY); + } + const owner = new EntityOwner(user); + const guardian = new Guardians(); + return await guardian.getPolicyLabelById(definitionId, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Update policy label + */ + @Put('/:definitionId') + @Auth(Permissions.STATISTICS_LABEL_CREATE) + @ApiOperation({ + summary: 'Updates policy label.', + description: 'Updates policy label configuration for the specified label ID.', + }) + @ApiParam({ + name: 'definitionId', + type: 'string', + required: true, + description: 'policy label Identifier', + example: Examples.DB_ID, + }) + @ApiBody({ + description: 'Object that contains a configuration.', + required: true, + type: PolicyLabelDTO + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: PolicyLabelDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) + @ApiExtraModels(PolicyLabelDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async updatePolicyLabel( + @AuthUser() user: IAuthUser, + @Param('definitionId') definitionId: string, + @Body() item: PolicyLabelDTO + ): Promise { + try { + if (!definitionId) { + throw new HttpException('Invalid ID.', HttpStatus.UNPROCESSABLE_ENTITY); + } + const owner = new EntityOwner(user); + const guardians = new Guardians(); + const oldItem = await guardians.getPolicyLabelById(definitionId, owner); + if (!oldItem) { + throw new HttpException('Item not found.', HttpStatus.NOT_FOUND); + } + return await guardians.updatePolicyLabel(definitionId, item, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Delete policy label + */ + @Delete('/:definitionId') + @Auth(Permissions.STATISTICS_LABEL_CREATE) + @ApiOperation({ + summary: 'Deletes the policy label.', + description: 'Deletes the policy label with the provided ID.', + }) + @ApiParam({ + name: 'definitionId', + type: 'string', + required: true, + description: 'policy label Identifier', + example: Examples.DB_ID, + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: Boolean + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) + @ApiExtraModels(InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async deletePolicyLabel( + @AuthUser() user: IAuthUser, + @Param('definitionId') definitionId: string + ): Promise { + try { + if (!definitionId) { + throw new HttpException('Invalid ID.', HttpStatus.UNPROCESSABLE_ENTITY) + } + const owner = new EntityOwner(user); + const guardians = new Guardians(); + return await guardians.deletePolicyLabel(definitionId, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Publish policy label + */ + @Put('/:definitionId/publish') + @Auth(Permissions.STATISTICS_LABEL_CREATE) + @ApiOperation({ + summary: 'Publishes policy label.', + description: 'Publishes policy label for the specified label ID.', + }) + @ApiParam({ + name: 'definitionId', + type: 'string', + required: true, + description: 'policy label Identifier', + example: Examples.DB_ID, + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: PolicyLabelDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) + @ApiExtraModels(PolicyLabelDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async publishPolicyLabel( + @AuthUser() user: IAuthUser, + @Param('definitionId') definitionId: string + ): Promise { + try { + if (!definitionId) { + throw new HttpException('Invalid ID.', HttpStatus.UNPROCESSABLE_ENTITY); + } + const owner = new EntityOwner(user); + const guardians = new Guardians(); + const oldItem = await guardians.getPolicyLabelById(definitionId, owner); + if (!oldItem) { + throw new HttpException('Item not found.', HttpStatus.NOT_FOUND); + } + return await guardians.publishPolicyLabel(definitionId, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Publish policy label (Async) + */ + @Put('/push/:definitionId/publish') + @Auth(Permissions.STATISTICS_LABEL_CREATE) + @ApiOperation({ + summary: 'Publishes policy label.', + description: 'Publishes policy label for the specified label ID.', + }) + @ApiParam({ + name: 'definitionId', + type: 'string', + required: true, + description: 'policy label Identifier', + example: Examples.DB_ID, + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: PolicyLabelDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) + @ApiExtraModels(TaskDTO, PolicyLabelDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.ACCEPTED) + async publishPolicyLabelAsync( + @AuthUser() user: IAuthUser, + @Param('definitionId') definitionId: string + ): Promise { + try { + if (!definitionId) { + throw new HttpException('Invalid ID.', HttpStatus.UNPROCESSABLE_ENTITY); + } + const owner = new EntityOwner(user); + const guardians = new Guardians(); + const oldItem = await guardians.getPolicyLabelById(definitionId, owner); + if (!oldItem) { + throw new HttpException('Item not found.', HttpStatus.NOT_FOUND); + } + + const taskManager = new TaskManager(); + const task = taskManager.start(TaskAction.PUBLISH_POLICY_LABEL, user.id); + RunFunctionAsync(async () => { + await guardians.publishPolicyLabelAsync(definitionId, owner, task); + }, async (error) => { + await this.logger.error(error, ['API_GATEWAY']); + taskManager.addError(task.taskId, { code: 500, message: error.message || error }); + }); + + return task; + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Get relationships by id + */ + @Get('/:definitionId/relationships') + @Auth(Permissions.STATISTICS_LABEL_READ) + @ApiOperation({ + summary: 'Retrieves policy label relationships.', + description: 'Retrieves policy label relationships for the specified ID.' + }) + @ApiParam({ + name: 'definitionId', + type: String, + description: 'policy label Identifier', + required: true, + example: Examples.DB_ID + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: PolicyLabelRelationshipsDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(PolicyLabelRelationshipsDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async getPolicyLabelRelationships( + @AuthUser() user: IAuthUser, + @Param('definitionId') definitionId: string + ): Promise { + try { + if (!definitionId) { + throw new HttpException('Invalid ID.', HttpStatus.UNPROCESSABLE_ENTITY); + } + const owner = new EntityOwner(user); + const guardian = new Guardians(); + return await guardian.getPolicyLabelRelationships(definitionId, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Import labels + */ + @Post('/:policyId/import/file') + @Auth(Permissions.STATISTICS_LABEL_CREATE) + @ApiOperation({ + summary: 'Imports new labels from a zip file.', + description: 'Imports new labels from the provided zip file into the local DB.', + }) + @ApiParam({ + name: 'policyId', + type: String, + description: 'Policy Id', + required: true, + example: Examples.DB_ID + }) + @ApiBody({ + description: 'A zip file containing labels to be imported.', + required: true + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: PolicyLabelDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) + @ApiExtraModels(PolicyLabelDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.CREATED) + async importPolicyLabel( + @AuthUser() user: IAuthUser, + @Param('policyId') policyId: string, + @Body() zip: any + ): Promise { + const guardian = new Guardians(); + try { + const owner = new EntityOwner(user); + return await guardian.importPolicyLabel(zip, policyId, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Export labels + */ + @Get('/:definitionId/export/file') + @Auth(Permissions.STATISTICS_LABEL_READ) + @ApiOperation({ + summary: 'Returns a zip file containing labels.', + description: 'Returns a zip file containing labels.', + }) + @ApiParam({ + name: 'definitionId', + type: String, + description: 'policy label Identifier', + required: true, + example: Examples.DB_ID + }) + @ApiOkResponse({ + description: 'Successful operation. Response zip file.' + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) + @ApiExtraModels(InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async exportPolicyLabel( + @AuthUser() user: IAuthUser, + @Param('definitionId') definitionId: string, + @Response() res: any + ): Promise { + const guardian = new Guardians(); + try { + const owner = new EntityOwner(user); + const file: any = await guardian.exportPolicyLabel(definitionId, owner); + res.header('Content-disposition', `attachment; filename=theme_${Date.now()}`); + res.header('Content-type', 'application/zip'); + return res.send(file); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Preview policy label + */ + @Post('/import/file/preview') + @Auth(Permissions.STATISTICS_LABEL_CREATE) + @ApiOperation({ + summary: 'Imports a zip file containing labels.', + description: 'Imports a zip file containing labels.', + }) + @ApiBody({ + description: 'File.', + }) + @ApiOkResponse({ + description: 'policy label preview.', + type: PolicyLabelDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(PolicyLabelDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async previewPolicyLabel( + @AuthUser() user: IAuthUser, + @Body() body: any + ): Promise { + try { + const owner = new EntityOwner(user); + const guardian = new Guardians(); + return await guardian.previewPolicyLabel(body, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Search other labels ans statistics + */ + @Post('/components') + @Auth(Permissions.STATISTICS_LABEL_CREATE) + @ApiOperation({ + summary: 'Search labels ans statistics.', + description: 'Return a list of other labels ans statistics.', + }) + @ApiBody({ + description: 'Filters.', + required: true, + type: PolicyLabelFiltersDTO + }) + @ApiOkResponse({ + description: 'A list of labels ans statistics.', + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(PolicyLabelFiltersDTO, PolicyLabelComponentsDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async searchComponents( + @AuthUser() user: IAuthUser, + @Body() body: PolicyLabelFiltersDTO + ): Promise { + try { + const owner = new EntityOwner(user); + const guardian = new Guardians(); + return await guardian.searchComponents(body, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Get documents + */ + @Get('/:definitionId/tokens') + @Auth(Permissions.STATISTICS_LABEL_READ) + @ApiOperation({ + summary: 'Return a list of all documents.', + description: 'Returns all documents.', + }) + @ApiParam({ + name: 'definitionId', + type: String, + description: 'policy label Identifier', + required: true, + example: Examples.DB_ID + }) + @ApiQuery({ + name: 'pageIndex', + type: Number, + description: 'The number of pages to skip before starting to collect the result set', + required: false, + example: 0 + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + description: 'The numbers of items to return', + required: false, + example: 20 + }) + @ApiOkResponse({ + description: 'Successful operation.', + isArray: true, + headers: pageHeader, + type: VcDocumentDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(VcDocumentDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async getPolicyLabelTokens( + @AuthUser() user: IAuthUser, + @Response() res: any, + @Param('definitionId') definitionId: string, + @Query('pageIndex') pageIndex?: number, + @Query('pageSize') pageSize?: number + ): Promise { + try { + const owner = new EntityOwner(user); + const guardians = new Guardians(); + const { items, count } = await guardians.getPolicyLabelTokens(definitionId, owner, pageIndex, pageSize); + return res.header('X-Total-Count', count).send(items); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Get document + */ + @Get('/:definitionId/tokens/:documentId') + @Auth(Permissions.STATISTICS_LABEL_READ) + @ApiOperation({ + summary: 'Return a list of all documents.', + description: 'Returns all documents.', + }) + @ApiParam({ + name: 'definitionId', + type: String, + description: 'policy label Identifier', + required: true, + example: Examples.DB_ID + }) + @ApiParam({ + name: 'documentId', + type: String, + description: 'Document Identifier', + required: true, + example: Examples.DB_ID + }) + @ApiOkResponse({ + description: 'Successful operation.', + isArray: true, + headers: pageHeader, + type: VcDocumentDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(VcDocumentDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async getPolicyLabelDocument( + @AuthUser() user: IAuthUser, + @Param('definitionId') definitionId: string, + @Param('documentId') documentId: string, + ): Promise { + try { + const owner = new EntityOwner(user); + const guardians = new Guardians(); + return await guardians.getPolicyLabelTokenDocuments(documentId, definitionId, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Creates a new label document + */ + @Post('/:definitionId/documents') + @Auth(Permissions.STATISTICS_LABEL_CREATE) + @ApiOperation({ + summary: 'Creates a new label document.', + description: 'Creates a new label document.', + }) + @ApiParam({ + name: 'definitionId', + type: String, + description: 'policy label Identifier', + required: true, + example: Examples.DB_ID + }) + @ApiBody({ + description: 'Configuration.', + type: PolicyLabelDocumentDTO, + required: true + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: PolicyLabelDocumentDTO, + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(PolicyLabelDocumentDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.CREATED) + async createStatisticDocument( + @AuthUser() user: IAuthUser, + @Param('definitionId') definitionId: string, + @Body() document: PolicyLabelDocumentDTO + ): Promise { + try { + if (!definitionId) { + throw new HttpException('Invalid ID.', HttpStatus.UNPROCESSABLE_ENTITY); + } + if (!document) { + throw new HttpException('Invalid config.', HttpStatus.UNPROCESSABLE_ENTITY); + } + const owner = new EntityOwner(user); + const guardian = new Guardians(); + return await guardian.createLabelDocument(definitionId, document, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Get page + */ + @Get('/:definitionId/documents') + @Auth(Permissions.STATISTICS_LABEL_READ) + @ApiOperation({ + summary: 'Return a list of all label documents.', + description: 'Returns all label documents.', + }) + @ApiParam({ + name: 'definitionId', + type: String, + description: 'policy label Identifier', + required: true, + example: Examples.DB_ID + }) + @ApiQuery({ + name: 'pageIndex', + type: Number, + description: 'The number of pages to skip before starting to collect the result set', + required: false, + example: 0 + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + description: 'The numbers of items to return', + required: false, + example: 20 + }) + @ApiOkResponse({ + description: 'Successful operation.', + isArray: true, + headers: pageHeader, + type: PolicyLabelDocumentDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(PolicyLabelDocumentDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async getLabelDocuments( + @AuthUser() user: IAuthUser, + @Response() res: any, + @Param('definitionId') definitionId: string, + @Query('pageIndex') pageIndex?: number, + @Query('pageSize') pageSize?: number + ): Promise { + try { + if (!definitionId) { + throw new HttpException('Invalid ID.', HttpStatus.UNPROCESSABLE_ENTITY); + } + const owner = new EntityOwner(user); + const guardians = new Guardians(); + const { items, count } = await guardians.getLabelDocuments(definitionId, { pageIndex, pageSize }, owner); + return res.header('X-Total-Count', count).send(items); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Get label document by id + */ + @Get('/:definitionId/documents/:documentId') + @Auth(Permissions.STATISTICS_LABEL_READ) + @ApiOperation({ + summary: 'Retrieves label document.', + description: 'Retrieves label document for the specified ID.' + }) + @ApiParam({ + name: 'definitionId', + type: String, + description: 'Label Definition Identifier', + required: true, + example: Examples.DB_ID + }) + @ApiParam({ + name: 'documentId', + type: String, + description: 'Label Document Identifier', + required: true, + example: Examples.DB_ID + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: PolicyLabelDocumentDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(PolicyLabelDocumentDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async getLabelDocument( + @AuthUser() user: IAuthUser, + @Param('definitionId') definitionId: string, + @Param('documentId') documentId: string + ): Promise { + try { + if (!definitionId || !documentId) { + throw new HttpException('Invalid ID.', HttpStatus.UNPROCESSABLE_ENTITY); + } + const owner = new EntityOwner(user); + const guardian = new Guardians(); + return await guardian.getLabelDocument(definitionId, documentId, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Get label document relationships + */ + @Get('/:definitionId/documents/:documentId/relationships') + @Auth(Permissions.STATISTICS_STATISTIC_READ) + @ApiOperation({ + summary: 'Retrieves documents relationships.', + description: 'Retrieves documents relationships for the specified ID.' + }) + @ApiParam({ + name: 'definitionId', + type: String, + description: 'Statistic Definition Identifier', + required: true, + example: Examples.DB_ID + }) + @ApiParam({ + name: 'documentId', + type: String, + description: 'Label Document Identifier', + required: true, + example: Examples.DB_ID + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: PolicyLabelDocumentRelationshipsDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(PolicyLabelDocumentDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async getStatisticAssessmentRelationships( + @AuthUser() user: IAuthUser, + @Param('definitionId') definitionId: string, + @Param('documentId') documentId: string + ): Promise { + try { + if (!definitionId || !documentId) { + throw new HttpException('Invalid ID.', HttpStatus.UNPROCESSABLE_ENTITY); + } + const owner = new EntityOwner(user); + const guardian = new Guardians(); + return await guardian.getLabelDocumentRelationships(definitionId, documentId, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } +} diff --git a/api-gateway/src/app.module.ts b/api-gateway/src/app.module.ts index b5e21e9c59..163d17416c 100644 --- a/api-gateway/src/app.module.ts +++ b/api-gateway/src/app.module.ts @@ -43,6 +43,7 @@ import { WorkerTasksController } from './api/service/worker-tasks.js'; import { PolicyStatisticsApi } from './api/service/policy-statistics.js'; import { SchemaRulesApi } from './api/service/schema-rules.js'; import { loggerMongoProvider, pinoLoggerProvider } from './helpers/providers/index.js'; +import { PolicyLabelsApi } from './api/service/policy-labels.js'; // const JSON_REQUEST_LIMIT = process.env.JSON_REQUEST_LIMIT || '1mb'; // const RAW_REQUEST_LIMIT = process.env.RAW_REQUEST_LIMIT || '1gb'; @@ -96,6 +97,7 @@ import { loggerMongoProvider, pinoLoggerProvider } from './helpers/providers/ind PermissionsApi, PolicyStatisticsApi, SchemaRulesApi, + PolicyLabelsApi, WorkerTasksController ], providers: [ diff --git a/api-gateway/src/helpers/guardians.ts b/api-gateway/src/helpers/guardians.ts index f9e074354d..9d38d248e2 100644 --- a/api-gateway/src/helpers/guardians.ts +++ b/api-gateway/src/helpers/guardians.ts @@ -41,7 +41,13 @@ import { StatisticDefinitionRelationshipsDTO, SchemaRuleDTO, SchemaRuleRelationshipsDTO, - SchemaRuleDataDTO + SchemaRuleDataDTO, + PolicyLabelDTO, + PolicyLabelDocumentDTO, + PolicyLabelRelationshipsDTO, + PolicyLabelDocumentRelationshipsDTO, + PolicyLabelComponentsDTO, + PolicyLabelFiltersDTO } from '#middlewares'; /** @@ -3197,4 +3203,255 @@ export class Guardians extends NatsService { public async previewSchemaRule(zip: any, owner: IOwner) { return await this.sendMessage(MessageAPI.PREVIEW_SCHEMA_RULE_FILE, { zip, owner }); } + + /** + * Create policy label + * + * @param label + * @param owner + * @returns policy label + */ + public async createPolicyLabel(label: PolicyLabelDTO, owner: IOwner): Promise { + return await this.sendMessage(MessageAPI.CREATE_POLICY_LABEL, { label, owner }); + } + + /** + * Return policy labels + * + * @param filters + * @param owner + * + * @returns {ResponseAndCount} + */ + public async getPolicyLabels(filters: IFilter, owner: IOwner): Promise> { + return await this.sendMessage(MessageAPI.GET_POLICY_LABELS, { filters, owner }); + } + + /** + * Get policy label + * + * @param definitionId + * @param owner + * @returns Operation Success + */ + public async getPolicyLabelById(definitionId: string, owner: IOwner): Promise { + return await this.sendMessage(MessageAPI.GET_POLICY_LABEL, { definitionId, owner }); + } + + /** + * Get relationships + * + * @param definitionId + * @param owner + * + * @returns Relationships + */ + public async getPolicyLabelRelationships(definitionId: string, owner: IOwner): Promise { + return await this.sendMessage(MessageAPI.GET_POLICY_LABEL_RELATIONSHIPS, { definitionId, owner }); + } + + /** + * Update policy label + * + * @param definitionId + * @param label + * @param owner + * + * @returns theme + */ + public async updatePolicyLabel( + definitionId: string, + label: PolicyLabelDTO, + owner: IOwner + ): Promise { + return await this.sendMessage(MessageAPI.UPDATE_POLICY_LABEL, { definitionId, label, owner }); + } + + /** + * Delete policy label + * + * @param definitionId + * @param owner + * + * @returns Operation Success + */ + public async deletePolicyLabel(definitionId: string, owner: IOwner): Promise { + return await this.sendMessage(MessageAPI.DELETE_POLICY_LABEL, { definitionId, owner }); + } + + /** + * Publish policy label + * + * @param definitionId + * @param owner + * + * @returns Operation Success + */ + public async publishPolicyLabel(definitionId: string, owner: IOwner): Promise { + return await this.sendMessage(MessageAPI.PUBLISH_POLICY_LABEL, { definitionId, owner }); + } + + /** + * Async publish policy + * @param definitionId + * @param owner + * @param task + */ + public async publishPolicyLabelAsync( + definitionId: string, + owner: IOwner, + task: NewTask + ): Promise { + return await this.sendMessage(MessageAPI.PUBLISH_POLICY_LABEL_ASYNC, { definitionId, owner, task }); + } + + /** + * Load policy label file for import + * @param zip + * @param owner + */ + public async importPolicyLabel(zip: any, policyId: string, owner: IOwner): Promise { + return await this.sendMessage(MessageAPI.IMPORT_POLICY_LABEL_FILE, { zip, policyId, owner }); + } + + /** + * Get policy label export file + * @param definitionId + * @param owner + */ + public async exportPolicyLabel(definitionId: string, owner: IOwner) { + const file = await this.sendMessage(MessageAPI.EXPORT_POLICY_LABEL_FILE, { definitionId, owner }) as any; + return Buffer.from(file, 'base64'); + } + + /** + * Get policy label info from file + * @param zip + * @param owner + */ + public async previewPolicyLabel(zip: any, owner: IOwner): Promise { + return await this.sendMessage(MessageAPI.PREVIEW_POLICY_LABEL_FILE, { zip, owner }); + } + + /** + * Search labels and statistics + * @param options + * @param owner + */ + public async searchComponents( + options: PolicyLabelFiltersDTO, + owner: IOwner + ): Promise { + return await this.sendMessage(MessageAPI.SEARCH_POLICY_LABEL_COMPONENTS, { options, owner }); + } + + /** + * Return documents + * + * @param definitionId + * @param owner + * @param pageIndex + * @param pageSize + * + * @returns {ResponseAndCount} + */ + public async getPolicyLabelTokens( + definitionId: string, + owner: IOwner, + pageIndex?: number, + pageSize?: number + ): Promise> { + return await this.sendMessage(MessageAPI.GET_POLICY_LABEL_TOKENS, { definitionId, owner, pageIndex, pageSize }); + } + + /** + * Return documents + * + * @param documentId + * @param definitionId + * @param owner + * + * @returns {any} + */ + public async getPolicyLabelTokenDocuments( + documentId: string, + definitionId: string, + owner: IOwner, + ): Promise { + return await this.sendMessage(MessageAPI.GET_POLICY_LABEL_TOKEN_DOCUMENTS, { documentId, definitionId, owner }); + } + + /** + * Create label document + * + * @param definitionId + * @param data + * @param owner + * + * @returns report + */ + public async createLabelDocument( + definitionId: string, + data: PolicyLabelDocumentDTO, + owner: IOwner + ): Promise { + return await this.sendMessage(MessageAPI.CREATE_POLICY_LABEL_DOCUMENT, { definitionId, data, owner }); + } + + /** + * Return label documents + * + * @param definitionId + * @param filters + * @param owner + * + * @returns {ResponseAndCount} + */ + public async getLabelDocuments( + definitionId: string, + filters: IFilter, + owner: IOwner + ): Promise> { + return await this.sendMessage(MessageAPI.GET_POLICY_LABEL_DOCUMENTS, + { definitionId, filters, owner } + ); + } + + /** + * Get label document + * + * @param definitionId + * @param documentId + * @param owner + * + * @returns Operation Success + */ + public async getLabelDocument( + definitionId: string, + documentId: string, + owner: IOwner + ): Promise { + return await this.sendMessage(MessageAPI.GET_POLICY_LABEL_DOCUMENT, + { definitionId, documentId, owner } + ); + } + + /** + * Get statistic assessment relationships + * + * @param definitionId + * @param documentId + * @param owner + * + * @returns Operation Success + */ + public async getLabelDocumentRelationships( + definitionId: string, + documentId: string, + owner: IOwner + ): Promise { + return await this.sendMessage(MessageAPI.GET_POLICY_LABEL_DOCUMENT_RELATIONSHIPS, + { definitionId, documentId, owner } + ); + } } diff --git a/api-gateway/src/helpers/task-manager.ts b/api-gateway/src/helpers/task-manager.ts index 082e1f65c8..9c545ca418 100644 --- a/api-gateway/src/helpers/task-manager.ts +++ b/api-gateway/src/helpers/task-manager.ts @@ -81,7 +81,8 @@ export class TaskManager { [TaskAction.CREATE_TOOL, 8], [TaskAction.IMPORT_TOOL_FILE, 9], [TaskAction.IMPORT_TOOL_MESSAGE, 11], - [TaskAction.MIGRATE_DATA, 4] + [TaskAction.MIGRATE_DATA, 4], + [TaskAction.PUBLISH_POLICY_LABEL, 4] ]); /** diff --git a/api-gateway/src/middlewares/validation/schemas/index.ts b/api-gateway/src/middlewares/validation/schemas/index.ts index 1042cbd574..23aad8015f 100644 --- a/api-gateway/src/middlewares/validation/schemas/index.ts +++ b/api-gateway/src/middlewares/validation/schemas/index.ts @@ -30,4 +30,5 @@ export * from './policies.dto.js' export * from './profiles.dto.js' export * from './worker-tasks.dto.js' export * from './policy-statistics.dto.js' -export * from './schema-rules.dto.js' \ No newline at end of file +export * from './schema-rules.dto.js' +export * from './policy-labels.dto.js' \ No newline at end of file diff --git a/api-gateway/src/middlewares/validation/schemas/policy-labels.dto.ts b/api-gateway/src/middlewares/validation/schemas/policy-labels.dto.ts new file mode 100644 index 0000000000..b53cf9186d --- /dev/null +++ b/api-gateway/src/middlewares/validation/schemas/policy-labels.dto.ts @@ -0,0 +1,337 @@ +import { ApiExtraModels, ApiProperty } from '@nestjs/swagger'; +import { Examples } from '../examples.js'; +import { IsArray, IsObject, IsOptional, IsString } from 'class-validator'; +import { EntityStatus } from '@guardian/interfaces'; +import { SchemaDTO } from './schemas.dto.js'; +import { PolicyDTO } from './policies.dto.js'; +import { VcDocumentDTO, VpDocumentDTO } from './document.dto.js'; +import { StatisticDefinitionDTO } from './policy-statistics.dto.js'; + +export class PolicyLabelDTO { + @ApiProperty({ + type: 'string', + required: false, + example: Examples.DB_ID + }) + id?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.UUID + }) + @IsOptional() + @IsString() + uuid?: string; + + @ApiProperty({ + type: 'string', + required: true, + example: 'Tool name' + }) + @IsString() + name: string; + + @ApiProperty({ + type: 'string', + required: false, + example: 'Description' + }) + @IsOptional() + @IsString() + description?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.DID + }) + @IsOptional() + @IsString() + creator?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.DID + }) + @IsOptional() + @IsString() + owner?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.ACCOUNT_ID + }) + @IsOptional() + @IsString() + topicId?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.MESSAGE_ID + }) + @IsOptional() + @IsString() + messageId?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.DB_ID + }) + @IsOptional() + @IsString() + policyId?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.ACCOUNT_ID + }) + @IsOptional() + @IsString() + policyTopicId?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.ACCOUNT_ID + }) + @IsOptional() + @IsString() + policyInstanceTopicId?: string; + + @ApiProperty({ + type: 'string', + required: false, + enum: EntityStatus, + example: EntityStatus.DRAFT + }) + @IsOptional() + @IsString() + status?: EntityStatus; + + @ApiProperty({ + type: 'object', + nullable: true, + required: false + }) + @IsOptional() + @IsObject() + config?: any; +} + +@ApiExtraModels(PolicyDTO, SchemaDTO) +export class PolicyLabelRelationshipsDTO { + @ApiProperty({ + type: () => PolicyDTO, + required: false, + }) + @IsOptional() + @IsObject() + policy?: PolicyDTO; + + @ApiProperty({ + type: () => SchemaDTO, + required: false, + isArray: true, + }) + @IsOptional() + @IsArray() + policySchemas?: SchemaDTO[]; + + @ApiProperty({ + type: () => SchemaDTO, + required: false, + isArray: true, + }) + @IsOptional() + @IsArray() + documentsSchemas?: SchemaDTO[]; +} + +export class PolicyLabelDocumentDTO { + @ApiProperty({ + type: 'string', + required: false, + example: Examples.DB_ID + }) + @IsOptional() + @IsString() + id?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.DB_ID + }) + @IsOptional() + @IsString() + definitionId?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.DB_ID + }) + @IsOptional() + @IsString() + policyId?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.ACCOUNT_ID + }) + @IsOptional() + @IsString() + policyTopicId: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.ACCOUNT_ID + }) + @IsOptional() + @IsString() + policyInstanceTopicId: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.ACCOUNT_ID + }) + @IsOptional() + @IsString() + topicId: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.DID + }) + @IsOptional() + @IsString() + creator?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.DID + }) + @IsOptional() + @IsString() + owner?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.MESSAGE_ID + }) + @IsOptional() + @IsString() + messageId: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.MESSAGE_ID + }) + @IsOptional() + @IsString() + target: string; + + @ApiProperty({ + type: 'string', + required: false, + isArray: true, + example: [Examples.MESSAGE_ID] + }) + @IsOptional() + @IsArray() + relationships?: string[]; + + @ApiProperty({ + type: 'object', + nullable: true, + required: false + }) + @IsOptional() + @IsObject() + document?: any; +} + +@ApiExtraModels(VpDocumentDTO, VcDocumentDTO) +export class PolicyLabelDocumentRelationshipsDTO { + @ApiProperty({ + type: () => VpDocumentDTO, + required: false, + }) + @IsOptional() + @IsObject() + target?: VpDocumentDTO; + + @ApiProperty({ + type: () => VcDocumentDTO, + required: false, + isArray: true, + }) + @IsOptional() + @IsArray() + relationships?: VcDocumentDTO[]; +} + +@ApiExtraModels(VpDocumentDTO, StatisticDefinitionDTO) +export class PolicyLabelComponentsDTO { + @ApiProperty({ + type: () => StatisticDefinitionDTO, + required: false, + isArray: true, + }) + @IsOptional() + @IsArray() + statistics?: StatisticDefinitionDTO[]; + + @ApiProperty({ + type: () => PolicyLabelDTO, + required: false, + isArray: true, + }) + @IsOptional() + @IsArray() + labels?: PolicyLabelDTO[]; +} + +export class PolicyLabelFiltersDTO { + @ApiProperty({ + type: 'string', + required: false, + example: 'Name' + }) + @IsOptional() + @IsString() + text?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.DID + }) + @IsOptional() + @IsString() + owner?: string; + + @ApiProperty({ + type: 'string', + required: false, + description: 'Component type', + enum: ['all', 'label', 'statistic'], + example: 'all' + }) + @IsOptional() + @IsString() + components?: 'all' | 'label' | 'statistic'; +} \ No newline at end of file diff --git a/common/src/database-modules/database-server.ts b/common/src/database-modules/database-server.ts index d711484851..116de7d023 100644 --- a/common/src/database-modules/database-server.ts +++ b/common/src/database-modules/database-server.ts @@ -40,7 +40,9 @@ import { Artifact, PolicyStatistic, PolicyStatisticDocument, - SchemaRule + SchemaRule, + PolicyLabel, + PolicyLabelDocument } from '../entity/index.js'; import { Binary } from 'bson'; import { @@ -3982,4 +3984,96 @@ export class DatabaseServer extends AbstractDatabaseServer { public static async removeSchemaRule(rule: SchemaRule): Promise { return await new DataBaseHelper(SchemaRule).remove(rule); } + + /** + * Create Policy Label + * @param label + */ + public static async createPolicyLabel( + label: FilterObject + ): Promise { + const item = new DataBaseHelper(PolicyLabel).create(label); + return await new DataBaseHelper(PolicyLabel).save(item); + } + + /** + * Get Policy Label + * @param filters + * @param options + */ + public static async getPolicyLabelsAndCount( + filters?: FilterObject, + options?: FindOptions + ): Promise<[PolicyLabel[], number]> { + return await new DataBaseHelper(PolicyLabel).findAndCount(filters, options); + } + + /** + * Get Policy Label + * @param filters + * @param options + */ + public static async getPolicyLabels( + filters?: FilterObject, + options?: unknown + ): Promise { + return await new DataBaseHelper(PolicyLabel).find(filters, options); + } + + /** + * Get Policy Label By ID + * @param id + */ + public static async getPolicyLabelById(id: string): Promise { + return await new DataBaseHelper(PolicyLabel).findOne(id); + } + + /** + * Update Policy Label + * @param label + */ + public static async updatePolicyLabel(label: PolicyLabel): Promise { + return await new DataBaseHelper(PolicyLabel).update(label); + } + + /** + * Delete Policy Label + * @param label + */ + public static async removePolicyLabel(label: PolicyLabel): Promise { + return await new DataBaseHelper(PolicyLabel).remove(label); + } + + /** + * Create Label Document + * @param document + */ + public static async createLabelDocument( + document: FilterObject + ): Promise { + const item = new DataBaseHelper(PolicyLabelDocument).create(document); + return await new DataBaseHelper(PolicyLabelDocument).save(item); + } + + /** + * Get statistic assessments + * @param filters + * @param options + */ + public static async getLabelDocumentsAndCount( + filters?: FilterObject, + options?: FindOptions + ): Promise<[PolicyLabelDocument[], number]> { + return await new DataBaseHelper(PolicyLabelDocument).findAndCount(filters, options); + } + + /** + * Get statistic assessment + * @param filters + */ + public static async getLabelDocument( + filters: FilterQuery + ): Promise { + return await new DataBaseHelper(PolicyLabelDocument).findOne(filters); + } } diff --git a/common/src/entity/index.ts b/common/src/entity/index.ts index 880d72cb24..4665137b12 100644 --- a/common/src/entity/index.ts +++ b/common/src/entity/index.ts @@ -45,4 +45,6 @@ export * from './policy-test.js'; export * from './log.js'; export * from './policy-statistic.js'; export * from './policy-statistic-document.js'; -export * from './schema-rule.js'; \ No newline at end of file +export * from './schema-rule.js'; +export * from './policy-label.js'; +export * from './policy-label-document.js'; \ No newline at end of file diff --git a/common/src/entity/policy-label-document.ts b/common/src/entity/policy-label-document.ts new file mode 100644 index 0000000000..902e4852d8 --- /dev/null +++ b/common/src/entity/policy-label-document.ts @@ -0,0 +1,189 @@ + +import { BaseEntity } from '../models/index.js'; +import { GenerateUUIDv4, IStatistic, IVC } from '@guardian/interfaces'; +import { Entity, Property, BeforeCreate, OnLoad, BeforeUpdate, AfterDelete, AfterUpdate, AfterCreate } from '@mikro-orm/core'; +import { DataBaseHelper } from '../helpers/index.js'; +import { ObjectId } from '@mikro-orm/mongodb'; + +/** + * PolicyStatistic collection + */ +@Entity() +export class PolicyLabelDocument extends BaseEntity implements IStatistic { + /** + * ID + */ + @Property({ nullable: true }) + uuid?: string; + + /** + * Owner + */ + @Property({ + nullable: true, + index: true + }) + owner?: string; + + /** + * Creator + */ + @Property({ + nullable: true, + index: true + }) + creator?: string; + + /** + * Topic id + */ + @Property({ + nullable: true, + index: true + }) + topicId?: string; + + /** + * Policy id + */ + @Property({ + nullable: true, + index: true + }) + policyId?: string; + + /** + * Policy Topic id + */ + @Property({ + nullable: true, + index: true + }) + policyTopicId?: string; + + /** + * Policy Instance Topic id + */ + @Property({ + nullable: true, + index: true + }) + policyInstanceTopicId?: string; + + /** + * Statistic id + */ + @Property({ + nullable: true, + index: true + }) + definitionId?: string; + + /** + * Message id + */ + @Property({ nullable: true }) + messageId?: string; + + /** + * Message id + */ + @Property({ nullable: true }) + target?: string; + + /** + * Message id + */ + @Property({ nullable: true }) + relationships?: string[]; + + /** + * Document instance + */ + @Property({ persist: false, type: 'unknown' }) + document?: IVC; + + /** + * Document file id + */ + @Property({ nullable: true }) + documentFileId?: ObjectId; + + /** + * Set defaults + */ + @BeforeCreate() + setDefaults() { + this.uuid = this.uuid || GenerateUUIDv4(); + } + + /** + * Create document + */ + @BeforeCreate() + async createDocument() { + await new Promise((resolve, reject) => { + try { + if (this.document) { + const fileStream = DataBaseHelper.gridFS.openUploadStream( + GenerateUUIDv4() + ); + this.documentFileId = fileStream.id; + fileStream.write(JSON.stringify(this.document)); + fileStream.end(() => resolve()); + } else { + resolve(); + } + } catch (error) { + reject(error) + } + }); + } + + /** + * Update document + */ + @BeforeUpdate() + async updateDocument() { + if (this.document) { + if (this.documentFileId) { + DataBaseHelper.gridFS + .delete(this.documentFileId) + .catch(console.error); + } + await this.createDocument(); + } + } + + /** + * Load document + */ + @OnLoad() + @AfterUpdate() + @AfterCreate() + async loadDocument() { + if (this.documentFileId) { + const fileStream = DataBaseHelper.gridFS.openDownloadStream( + this.documentFileId + ); + const bufferArray = []; + for await (const data of fileStream) { + bufferArray.push(data); + } + const buffer = Buffer.concat(bufferArray); + this.document = JSON.parse(buffer.toString()); + } + } + + /** + * Delete document + */ + @AfterDelete() + deleteDocument() { + if (this.documentFileId) { + DataBaseHelper.gridFS + .delete(this.documentFileId) + .catch(console.error); + } + } +} diff --git a/common/src/entity/policy-label.ts b/common/src/entity/policy-label.ts new file mode 100644 index 0000000000..f42b83dde1 --- /dev/null +++ b/common/src/entity/policy-label.ts @@ -0,0 +1,111 @@ +import { BeforeCreate, Entity, Property } from '@mikro-orm/core'; +import { BaseEntity } from '../models/index.js'; +import { EntityStatus, GenerateUUIDv4, IPolicyLabel, IPolicyLabelConfig } from '@guardian/interfaces'; + +/** + * PolicyLabel collection + */ +@Entity() +export class PolicyLabel extends BaseEntity implements IPolicyLabel { + /** + * ID + */ + @Property({ nullable: true }) + uuid?: string; + + /** + * Label + */ + @Property({ nullable: true }) + name?: string; + + /** + * Description + */ + @Property({ nullable: true }) + description?: string; + + /** + * Owner + */ + @Property({ + nullable: true, + index: true + }) + owner?: string; + + /** + * Creator + */ + @Property({ nullable: true }) + creator?: string; + + /** + * Status + */ + @Property({ nullable: true }) + status?: EntityStatus; + + /** + * Topic id + */ + @Property({ + nullable: true, + index: true + }) + topicId?: string; + + /** + * Message id + */ + @Property({ nullable: true }) + messageId?: string; + + /** + * Policy id + */ + @Property({ + nullable: true, + index: true + }) + policyId?: string; + + /** + * Policy Topic id + */ + @Property({ + nullable: true, + index: true + }) + policyTopicId?: string; + + /** + * Policy Instance Topic id + */ + @Property({ + nullable: true, + index: true + }) + policyInstanceTopicId?: string; + + /** + * Config + */ + @Property({ nullable: true, type: 'unknown' }) + config?: IPolicyLabelConfig; + + /** + * Method + */ + @Property({ nullable: true }) + method?: string; + + /** + * Set defaults + */ + @BeforeCreate() + setDefaults() { + this.uuid = this.uuid || GenerateUUIDv4(); + this.status = this.status || EntityStatus.DRAFT; + } +} diff --git a/common/src/hedera-modules/message/index.ts b/common/src/hedera-modules/message/index.ts index c1be0ca04e..afbee0dacd 100644 --- a/common/src/hedera-modules/message/index.ts +++ b/common/src/hedera-modules/message/index.ts @@ -20,4 +20,6 @@ export { ContractMessage } from './contract-message.js'; export { GuardianRoleMessage } from './guardian-role-message.js'; export { UserPermissionsMessage } from './user-permissions-message.js'; export { StatisticMessage } from './statistic-message.js'; -export { StatisticAssessmentMessage } from './statistic-assessment-message.js'; \ No newline at end of file +export { StatisticAssessmentMessage } from './statistic-assessment-message.js'; +export { LabelMessage } from './label-message.js'; +export { LabelDocumentMessage } from './label-document-message.js'; \ No newline at end of file diff --git a/common/src/hedera-modules/message/label-document-message.ts b/common/src/hedera-modules/message/label-document-message.ts new file mode 100644 index 0000000000..1b9b912f87 --- /dev/null +++ b/common/src/hedera-modules/message/label-document-message.ts @@ -0,0 +1,244 @@ +import { Message } from './message.js'; +import { IURL, UrlType } from './url.interface.js'; +import { MessageAction } from './message-action.js'; +import { MessageType } from './message-type.js'; +import { LabelDocumentMessageBody } from './message-body.interface.js'; +import { IPFS } from '../../helpers/index.js'; +import { PolicyLabel } from '../../entity/index.js'; +import { VpDocument } from '../vcjs/vp-document.js'; + +/** + * VP message + */ +export class LabelDocumentMessage extends Message { + /** + * VC document + */ + public vpDocument: VpDocument; + /** + * Document + */ + public document: any; + /** + * Hash + */ + public hash: string; + /** + * Issuer + */ + public issuer: string; + /** + * Relationships + */ + public relationships: string[]; + /** + * Target + */ + public target: string; + /** + * Definition + */ + public definition: string; + + constructor( + action: MessageAction, + type: MessageType = MessageType.VPDocument + ) { + super(action, type); + } + + /** + * Set document + * @param document + */ + public setDocument(document: VpDocument): void { + this.vpDocument = document; + this.document = document.getDocument(); + this.hash = document.toCredentialHash(); + this.issuer = document.getIssuerDid(); + } + + /** + * Set target + * @param id + */ + public setTarget(id: string): void { + this.target = id; + } + + /** + * Set relationships + * @param ids + */ + public setRelationships(ids: string[]): void { + this.relationships = Array.from(new Set(ids)); + } + + /** + * Set target + * @param id + */ + public setDefinition(definition: PolicyLabel): void { + this.definition = definition.messageId; + } + + /** + * Get documents + */ + public getDocument(): any { + return this.document; + } + + /** + * To message object + */ + public override toMessageObject(): LabelDocumentMessageBody { + return { + id: null, + status: null, + type: this.type, + action: this.action, + lang: this.lang, + issuer: this.issuer, + relationships: this.relationships, + target: this.target, + definition: this.definition, + cid: this.getDocumentUrl(UrlType.cid), + uri: this.getDocumentUrl(UrlType.url), + }; + } + + /** + * To documents + */ + public async toDocuments(key: string): Promise { + const document = JSON.stringify(this.document); + const buffer = Buffer.from(document); + return [buffer]; + } + + /** + * Load documents + * @param documents + */ + public async loadDocuments(documents: string[]): Promise { + this.document = JSON.parse(documents[0]); + return this; + } + + /** + * From message + * @param message + */ + public static fromMessage(message: string): LabelDocumentMessage { + if (!message) { + throw new Error('Message Object is empty'); + } + + const json = JSON.parse(message); + return LabelDocumentMessage.fromMessageObject(json); + } + + /** + * From message object + * @param json + */ + public static fromMessageObject(json: LabelDocumentMessageBody): LabelDocumentMessage { + if (!json) { + throw new Error('JSON Object is empty'); + } + + let message = new LabelDocumentMessage(json.action, json.type); + message = LabelDocumentMessage._fromMessageObject(message, json); + return message; + } + + /** + * From message object + * @param message + * @param json + */ + protected static override _fromMessageObject( + message: T, json: LabelDocumentMessageBody + ): T { + const _message: LabelDocumentMessage = super._fromMessageObject(message, json) as any; + _message._id = json.id; + _message._status = json.status; + _message.issuer = json.issuer; + _message.relationships = json.relationships; + _message.target = json.target; + _message.definition = json.definition; + const urls = [ + { + cid: json.cid, + url: IPFS.IPFS_PROTOCOL + json.cid, + }, + ]; + _message.setUrls(urls); + return _message as any; + } + + /** + * Get URL + */ + public override getUrl(): IURL { + return this.getUrls()[0]; + } + + /** + * Validate + */ + public override validate(): boolean { + return true; + } + + /** + * Get document URL + * @param type + */ + public getDocumentUrl(type: UrlType): string | null { + return this.getUrlValue(0, type); + } + + /** + * Relationship message + */ + public getRelationships(): string[] { + return this.relationships || []; + } + + /** + * Target message + */ + public getTarget(): string { + return this.target; + } + + /** + * Definition message + */ + public getDefinition(): string { + return this.definition; + } + + /** + * To JSON + */ + public override toJson(): any { + const result = super.toJson(); + result.issuer = this.issuer; + result.hash = this.hash; + result.relationships = this.relationships; + result.target = this.target; + result.definition = this.definition; + result.document = this.document; + return result; + } + + /** + * Get User DID + */ + public override getOwner(): string { + return this.issuer; + } +} diff --git a/common/src/hedera-modules/message/label-message.ts b/common/src/hedera-modules/message/label-message.ts new file mode 100644 index 0000000000..bd18a3e1f6 --- /dev/null +++ b/common/src/hedera-modules/message/label-message.ts @@ -0,0 +1,202 @@ +import { Message } from './message.js'; +import { IURL, UrlType } from './url.interface.js'; +import { MessageAction } from './message-action.js'; +import { MessageType } from './message-type.js'; +import { LabelMessageBody } from './message-body.interface.js'; +import { PolicyLabel } from '../../entity/index.js'; +import { IPFS } from '../../helpers/index.js'; + +/** + * Schema message + */ +export class LabelMessage extends Message { + /** + * Name + */ + public name: string; + /** + * Description + */ + public description: string; + /** + * Owner + */ + public owner: string; + /** + * UUID + */ + public uuid: string; + /** + * Policy topic id + */ + public policyTopicId: string; + /** + * Policy Instance topic id + */ + public policyInstanceTopicId: string; + + /** + * Document + */ + public config: ArrayBuffer; + + constructor(action: MessageAction) { + super(action, MessageType.PolicyLabel); + } + + /** + * Set document + * @param item + */ + public setDocument(item: PolicyLabel, zip: ArrayBuffer): void { + this.name = item.name; + this.description = item.description; + this.owner = item.owner; + this.uuid = item.uuid; + this.policyTopicId = item.policyTopicId; + this.policyInstanceTopicId = item.policyInstanceTopicId; + this.config = zip; + } + + /** + * Get document + */ + public getDocument(): ArrayBuffer { + return this.config; + } + + /** + * To message object + */ + public override toMessageObject(): LabelMessageBody { + return { + id: null, + status: null, + type: this.type, + action: this.action, + lang: this.lang, + name: this.name, + description: this.description, + owner: this.owner, + uuid: this.uuid, + policyTopicId: this.policyTopicId, + policyInstanceTopicId: this.policyInstanceTopicId, + cid: this.getDocumentUrl(UrlType.cid), + uri: this.getDocumentUrl(UrlType.url), + }; + } + + /** + * To documents + */ + public async toDocuments(): Promise { + if (this.config) { + return [this.config]; + } + return []; + } + + /** + * Load documents + * @param documents + */ + public loadDocuments(documents: string[]): LabelMessage { + if (documents && documents.length === 1) { + this.config = Buffer.from(documents[0]); + } + return this; + } + + /** + * From message + * @param message + */ + public static fromMessage(message: string): LabelMessage { + if (!message) { + throw new Error('Message Object is empty'); + } + + const json = JSON.parse(message); + return LabelMessage.fromMessageObject(json); + } + + /** + * From message object + * @param json + */ + public static fromMessageObject(json: LabelMessageBody): LabelMessage { + if (!json) { + throw new Error('JSON Object is empty'); + } + + let message = new LabelMessage(json.action); + message = Message._fromMessageObject(message, json); + message._id = json.id; + message._status = json.status; + message.name = json.name; + message.description = json.description; + message.owner = json.owner; + message.uuid = json.uuid; + message.policyTopicId = json.policyTopicId; + message.policyInstanceTopicId = json.policyInstanceTopicId; + const urls = [ + { + cid: json.cid, + url: IPFS.IPFS_PROTOCOL + json.cid, + }, + ]; + message.setUrls(urls); + return message; + } + + /** + * Get URL + */ + public override getUrl(): IURL[] { + return this.getUrls(); + } + + /** + * Get document URL + * @param type + */ + public getDocumentUrl(type: UrlType): string | null { + return this.getUrlValue(0, type); + } + + /** + * Get context URL + * @param type + */ + public getContextUrl(type: UrlType): string | null { + return this.getUrlValue(1, type); + } + + /** + * Validate + */ + public override validate(): boolean { + return true; + } + + /** + * To JSON + */ + public override toJson(): any { + const result = super.toJson(); + result.name = this.name; + result.description = this.description; + result.owner = this.owner; + result.uuid = this.uuid; + result.policyTopicId = this.policyTopicId; + result.policyInstanceTopicId = this.policyInstanceTopicId; + result.config = this.config; + } + + /** + * Get User DID + */ + public override getOwner(): string { + return this.owner; + } +} diff --git a/common/src/hedera-modules/message/message-action.ts b/common/src/hedera-modules/message/message-action.ts index a596762b72..302737bff9 100644 --- a/common/src/hedera-modules/message/message-action.ts +++ b/common/src/hedera-modules/message/message-action.ts @@ -36,5 +36,7 @@ export enum MessageAction { DeleteRole = 'delete-role', SetRole = 'set-role', PublishPolicyStatistic = 'publish-policy-statistic', - CreateStatisticAssessment = 'create-assessment-document' -} + CreateStatisticAssessment = 'create-assessment-document', + PublishPolicyLabel = 'publish-policy-label', + CreateLabelDocument = 'create-label-document', +} \ No newline at end of file diff --git a/common/src/hedera-modules/message/message-body.interface.ts b/common/src/hedera-modules/message/message-body.interface.ts index 9e9b63e3d1..e74123af45 100644 --- a/common/src/hedera-modules/message/message-body.interface.ts +++ b/common/src/hedera-modules/message/message-body.interface.ts @@ -604,6 +604,44 @@ export interface StatisticMessageBody extends MessageBody { uri: string; } +/** + * Policy Label message body + */ +export interface LabelMessageBody extends MessageBody { + /** + * UUID + */ + uuid: string; + /** + * Name + */ + name: string; + /** + * Description + */ + description: string; + /** + * Owner + */ + owner: string; + /** + * Policy topic ID + */ + policyTopicId: string; + /** + * Policy instance topic ID + */ + policyInstanceTopicId: string; + /** + * CID + */ + cid: string; + /** + * URI + */ + uri: string; +} + /** * Statistic Assessment message body */ @@ -632,4 +670,34 @@ export interface StatisticAssessmentMessageBody extends MessageBody { * Definition */ definition: string; +} + +/** + * Label Assessment message body + */ +export interface LabelDocumentMessageBody extends MessageBody { + /** + * Issuer + */ + issuer: string; + /** + * CID + */ + cid: string; + /** + * URI + */ + uri: string; + /** + * Relationships + */ + relationships: string[]; + /** + * Target + */ + target: string; + /** + * Definition + */ + definition: string; } \ No newline at end of file diff --git a/common/src/hedera-modules/message/message-server.ts b/common/src/hedera-modules/message/message-server.ts index cdc40d90ba..f615ca0c5d 100644 --- a/common/src/hedera-modules/message/message-server.ts +++ b/common/src/hedera-modules/message/message-server.ts @@ -23,6 +23,8 @@ import { ToolMessage } from './tool-message.js'; import { RoleMessage } from './role-message.js'; import { GuardianRoleMessage } from './guardian-role-message.js'; import { UserPermissionsMessage } from './user-permissions-message.js'; +import { StatisticMessage } from './statistic-message.js'; +import { LabelMessage } from './label-message.js'; /** * Message server @@ -307,6 +309,12 @@ export class MessageServer { case MessageType.UserPermissions: message = UserPermissionsMessage.fromMessageObject(json); break; + case MessageType.PolicyStatistic: + message = StatisticMessage.fromMessageObject(json); + break; + case MessageType.PolicyLabel: + message = LabelMessage.fromMessageObject(json); + break; // Default schemas case 'schema-document': message = SchemaMessage.fromMessageObject(json); @@ -595,7 +603,7 @@ export class MessageServer { const timeStamp = messageId.trim(); const { operatorId, operatorKey, dryRun } = this.clientOptions; const workers = new Workers(); - const {topicId} = await workers.addNonRetryableTask({ + const { topicId } = await workers.addNonRetryableTask({ type: WorkerTaskType.GET_TOPIC_MESSAGE, data: { operatorId, diff --git a/common/src/hedera-modules/message/message-type.ts b/common/src/hedera-modules/message/message-type.ts index c7f159218b..53ea82402b 100644 --- a/common/src/hedera-modules/message/message-type.ts +++ b/common/src/hedera-modules/message/message-type.ts @@ -20,5 +20,6 @@ export enum MessageType { Tool = 'Tool', Contract = 'Contract', UserPermissions = 'User-Permissions', - PolicyStatistic = 'Policy-Statistic' + PolicyStatistic = 'Policy-Statistic', + PolicyLabel = 'Policy-Label' } diff --git a/common/src/import-export/index.ts b/common/src/import-export/index.ts index fbc9498811..e20ed2504a 100644 --- a/common/src/import-export/index.ts +++ b/common/src/import-export/index.ts @@ -6,4 +6,5 @@ export * from './theme.js'; export * from './record.js'; export * from './utils.js'; export * from './schema-rule.js'; -export * from './policy-statistic.js'; \ No newline at end of file +export * from './policy-statistic.js'; +export * from './policy-label.js'; \ No newline at end of file diff --git a/common/src/import-export/policy-label.ts b/common/src/import-export/policy-label.ts new file mode 100644 index 0000000000..0e0cb2b29f --- /dev/null +++ b/common/src/import-export/policy-label.ts @@ -0,0 +1,383 @@ +import JSZip from 'jszip'; +import { PolicyLabel, Policy, Schema as SchemaCollection } from '../entity/index.js'; +import { + IPolicyLabelConfig, + INavItemConfig, + INavImportsConfig, + NavItemType, + ILabelItemConfig, + IStatisticItemConfig, + IRulesItemConfig, + IGroupItemConfig, + INavLabelImportConfig, + INavStatisticImportConfig, + IStatisticConfig, + SchemaEntity, + SchemaStatus, + Schema +} from '@guardian/interfaces'; +import { PolicyStatisticImportExport } from './policy-statistic.js'; +import { PolicyImportExport } from './policy.js'; +import { DatabaseServer } from '../database-modules/index.js'; + +/** + * PolicyLabel components + */ +export interface IPolicyLabelComponents { + label: PolicyLabel; +} + +/** + * SchemaRule import export + */ +export class PolicyLabelImportExport { + /** + * SchemaRule filename + */ + public static readonly fileName = 'labels.json'; + + /** + * Load SchemaRule components + * @param rule SchemaRule + * + * @returns components + */ + public static async loadSchemaRuleComponents(label: PolicyLabel): Promise { + return { label }; + } + + /** + * Generate Zip File + * @param rule rule to pack + * + * @returns Zip file + */ + public static async generate(rule: PolicyLabel): Promise { + const components = await PolicyLabelImportExport.loadSchemaRuleComponents(rule); + const file = await PolicyLabelImportExport.generateZipFile(components); + return file; + } + + /** + * Generate Zip File + * @param components rule components + * + * @returns Zip file + */ + public static async generateZipFile(components: IPolicyLabelComponents): Promise { + const object = { ...components.label }; + delete object.id; + delete object._id; + delete object.owner; + delete object.createDate; + delete object.updateDate; + const zip = new JSZip(); + zip.file(PolicyLabelImportExport.fileName, JSON.stringify(object)); + return zip; + } + + /** + * Parse zip rule file + * @param zipFile Zip file + * @returns Parsed rule + */ + public static async parseZipFile(zipFile: any): Promise { + const zip = new JSZip(); + const content = await zip.loadAsync(zipFile); + if ( + !content.files[PolicyLabelImportExport.fileName] || + content.files[PolicyLabelImportExport.fileName].dir + ) { + throw new Error('Zip file is not a rule'); + } + const ruleString = await content.files[PolicyLabelImportExport.fileName].async('string'); + const label = JSON.parse(ruleString); + return { label }; + } + + /** + * Validate Config + * + * @param data config + */ + public static validateConfig(data?: IPolicyLabelConfig): IPolicyLabelConfig { + const config: IPolicyLabelConfig = { + imports: PolicyLabelImportExport.validateImports(data?.imports), + children: PolicyLabelImportExport.validateChildren(data?.children), + schemaId: PolicyLabelImportExport.validateString(data?.schemaId), + } + return config; + } + + /** + * Validate children + * + * @param data children + */ + private static validateChildren(data?: INavItemConfig[]): INavItemConfig[] { + const children: INavItemConfig[] = []; + if (Array.isArray(data)) { + for (const variable of data) { + const item = PolicyLabelImportExport.validateChild(variable); + if (item) { + children.push(item); + } + } + } + return children; + } + + /** + * Validate imports + * + * @param data imports + */ + private static validateImports(data?: INavImportsConfig[]): INavImportsConfig[] { + const imports: INavImportsConfig[] = []; + if (Array.isArray(data)) { + for (const variable of data) { + const item = PolicyLabelImportExport.validateImport(variable); + if (item) { + imports.push(item); + } + } + } + return imports; + } + + /** + * Validate child + * + * @param data child + */ + private static validateChild(data: INavItemConfig): INavItemConfig | null { + if (data?.type === NavItemType.Group) { + const child: IGroupItemConfig = { + id: PolicyLabelImportExport.validateString(data.id), + type: NavItemType.Group, + name: PolicyLabelImportExport.validateString(data.name), + title: PolicyLabelImportExport.validateString(data.title), + tag: PolicyLabelImportExport.validateTag(data.tag), + rule: PolicyLabelImportExport.validateString(data.rule) as any, + schemaId: PolicyLabelImportExport.validateString(data.schemaId), + children: PolicyLabelImportExport.validateChildren(data.children), + }; + return child; + } + if (data?.type === NavItemType.Label) { + const child: ILabelItemConfig = { + id: PolicyLabelImportExport.validateString(data.id), + type: NavItemType.Label, + name: PolicyLabelImportExport.validateString(data.name), + title: PolicyLabelImportExport.validateString(data.title), + tag: PolicyLabelImportExport.validateTag(data.tag), + description: PolicyLabelImportExport.validateString(data.description), + owner: PolicyLabelImportExport.validateString(data.owner), + schemaId: PolicyLabelImportExport.validateString(data.schemaId), + messageId: PolicyLabelImportExport.validateString(data.messageId), + config: PolicyLabelImportExport.validateConfig(data.config), + }; + return child; + } + if (data?.type === NavItemType.Rules) { + const child: IRulesItemConfig = { + id: PolicyLabelImportExport.validateString(data.id), + type: NavItemType.Rules, + name: PolicyLabelImportExport.validateString(data.name), + title: PolicyLabelImportExport.validateString(data.title), + tag: PolicyLabelImportExport.validateTag(data.tag), + schemaId: PolicyLabelImportExport.validateString(data.schemaId), + config: PolicyLabelImportExport.validateRulesConfig(data.config), + }; + return child; + } + if (data?.type === NavItemType.Statistic) { + const child: IStatisticItemConfig = { + id: PolicyLabelImportExport.validateString(data.id), + type: NavItemType.Statistic, + name: PolicyLabelImportExport.validateString(data.name), + title: PolicyLabelImportExport.validateString(data.title), + tag: PolicyLabelImportExport.validateTag(data.tag), + description: PolicyLabelImportExport.validateString(data.description), + owner: PolicyLabelImportExport.validateString(data.owner), + messageId: PolicyLabelImportExport.validateString(data.messageId), + schemaId: PolicyLabelImportExport.validateString(data.schemaId), + config: PolicyStatisticImportExport.validateConfig(data.config), + }; + return child; + } + return null; + } + + /** + * Validate import + * + * @param data import + */ + private static validateImport(data: INavImportsConfig): INavImportsConfig | null { + if (data?.type === NavItemType.Label) { + const child: INavLabelImportConfig = { + id: PolicyLabelImportExport.validateString(data.id), + type: NavItemType.Label, + name: PolicyLabelImportExport.validateString(data.name), + description: PolicyLabelImportExport.validateString(data.description), + owner: PolicyLabelImportExport.validateString(data.owner), + messageId: PolicyLabelImportExport.validateString(data.messageId), + config: PolicyLabelImportExport.validateConfig(data.config), + }; + return child; + } + if (data?.type === NavItemType.Statistic) { + const child: INavStatisticImportConfig = { + id: PolicyLabelImportExport.validateString(data.id), + type: NavItemType.Statistic, + name: PolicyLabelImportExport.validateString(data.name), + description: PolicyLabelImportExport.validateString(data.description), + owner: PolicyLabelImportExport.validateString(data.owner), + messageId: PolicyLabelImportExport.validateString(data.messageId), + config: PolicyStatisticImportExport.validateConfig(data.config), + }; + return child; + } + return null; + } + + /** + * Validate Config + * + * @param data config + */ + public static validateRulesConfig(data?: IStatisticConfig): IStatisticConfig { + const config: IStatisticConfig = { + variables: PolicyStatisticImportExport.validateVariables(data?.variables), + scores: PolicyStatisticImportExport.validateScores(data?.scores), + formulas: PolicyStatisticImportExport.validateFormulasWithRule(data?.formulas) + } + return config; + } + + /** + * Validate String + * + * @param data String + */ + private static validateString(data: string): string { + if (typeof data === 'string') { + return data; + } else { + return ''; + } + } + + /** + * Validate Tag + * + * @param data String + */ + private static validateTag(data: string): string { + if (typeof data === 'string') { + return data.trim().replace(/\s/ig, '_'); + } else { + return ''; + } + } + + /** + * Load policy schemas + * @param policy policy + * @returns policy schemas + */ + public static async getPolicySchemas(policy: Policy): Promise { + const { schemas, toolSchemas } = await PolicyImportExport.loadAllSchemas(policy); + const systemSchemas = await DatabaseServer.getSchemas({ + topicId: policy.topicId, + entity: { $in: [SchemaEntity.MINT_TOKEN, SchemaEntity.MINT_NFTOKEN] } + }); + + const all = [] + .concat(schemas, toolSchemas, systemSchemas) + .filter((s) => s.status === SchemaStatus.PUBLISHED && s.entity !== 'EVC'); + return all; + } + + /** + * Update schema uuid + * @param schemas policy schemas + * @param data config + * @returns new config + */ + public static updateSchemas( + schemas: SchemaCollection[], + data?: IPolicyLabelConfig + ): IPolicyLabelConfig | undefined { + if (!data) { + return; + } + + const fieldMap = new Map(); + const schemaObjects = schemas.map((s) => new Schema(s)); + for (const schema of schemaObjects) { + const allFields = schema.getFields(); + for (const field of allFields) { + const key = `${schema.name}|${field.path}|${field.description}|${field.type}|${field.isArray}|${field.isRef}`; + fieldMap.set(key, schema.iri); + } + } + + if (Array.isArray(data?.children)) { + for (const item of data.children) { + PolicyLabelImportExport._updateSchemas(fieldMap, item); + } + } + + return data; + } + + private static _updateSchemas( + fieldMap: Map, + data: INavItemConfig + ): void { + if (!data) { + return; + } + if (data.type === NavItemType.Group) { + if (Array.isArray(data.children)) { + for (const item of data.children) { + PolicyLabelImportExport._updateSchemas(fieldMap, item); + } + } + } + if (data.type === NavItemType.Label) { + if (Array.isArray(data.config?.children)) { + for (const item of data.config.children) { + PolicyLabelImportExport._updateSchemas(fieldMap, item); + } + } + } + if (data.type === NavItemType.Rules || data.type === NavItemType.Statistic) { + const config = data.config; + const variables = config.variables; + const rules = config.rules; + + const schemaMap = new Map(); + if (Array.isArray(variables)) { + for (const variable of variables) { + const key = `${variable.schemaName}|${variable.path}|${variable.fieldDescription}|${variable.fieldType}|${variable.fieldArray}|${variable.fieldRef}`; + schemaMap.set(variable.schemaId, fieldMap.get(key)); + } + } + + if (Array.isArray(variables)) { + for (const variable of variables) { + variable.schemaId = schemaMap.get(variable.schemaId); + } + } + + if (Array.isArray(rules)) { + for (const rule of rules) { + rule.schemaId = schemaMap.get(rule.schemaId); + } + } + } + } +} diff --git a/common/src/import-export/policy-statistic.ts b/common/src/import-export/policy-statistic.ts index 4c179f6043..2e64bcb1f9 100644 --- a/common/src/import-export/policy-statistic.ts +++ b/common/src/import-export/policy-statistic.ts @@ -1,8 +1,9 @@ import JSZip from 'jszip'; import { Policy, PolicyStatistic, Schema as SchemaCollection } from '../entity/index.js'; import { IFormulaData, IRuleData, IScoreData, IScoreOption, IStatisticConfig, IVariableData, Schema, SchemaEntity, SchemaStatus } from '@guardian/interfaces'; -import { DatabaseServer } from '../database-modules/index.js'; +import { SchemaRuleImportExport } from './schema-rule.js'; import { PolicyImportExport } from './policy.js'; +import { DatabaseServer } from '../database-modules/index.js'; /** * PolicyStatistic components @@ -164,7 +165,7 @@ export class PolicyStatisticImportExport { * * @param data Variables */ - private static validateVariables(data?: IVariableData[]): IVariableData[] { + public static validateVariables(data?: IVariableData[]): IVariableData[] { const variables: IVariableData[] = []; if (Array.isArray(data)) { for (const variable of data) { @@ -201,7 +202,7 @@ export class PolicyStatisticImportExport { * * @param data Scores */ - private static validateScores(data?: IScoreData[]): IScoreData[] { + public static validateScores(data?: IScoreData[]): IScoreData[] { const scores: IScoreData[] = []; if (Array.isArray(data)) { for (const score of data) { @@ -232,7 +233,7 @@ export class PolicyStatisticImportExport { * * @param data Formulas */ - private static validateFormulas(data?: IFormulaData[]): IFormulaData[] { + public static validateFormulas(data?: IFormulaData[]): IFormulaData[] { const formulas: IFormulaData[] = []; if (Array.isArray(data)) { for (const formula of data) { @@ -257,12 +258,43 @@ export class PolicyStatisticImportExport { return formula; } + /** + * Validate Formulas with rule + * + * @param data Formulas + */ + public static validateFormulasWithRule(data?: IFormulaData[]): IFormulaData[] { + const formulas: IFormulaData[] = []; + if (Array.isArray(data)) { + for (const formula of data) { + formulas.push(PolicyStatisticImportExport.validateFormulaWithRule(formula)); + } + } + return formulas; + } + + /** + * Validate Formula with rule + * + * @param data Formula + */ + private static validateFormulaWithRule(data?: IFormulaData): IFormulaData { + const formula: IFormulaData = { + id: PolicyStatisticImportExport.validateString(data.id), + type: PolicyStatisticImportExport.validateString(data.type), + description: PolicyStatisticImportExport.validateString(data.description), + formula: PolicyStatisticImportExport.validateString(data.formula), + rule: SchemaRuleImportExport.validateRule(data.rule), + } + return formula; + } + /** * Validate Rules * * @param data Rules */ - private static validateRules(data?: IRuleData[]): IRuleData[] { + public static validateRules(data?: IRuleData[]): IRuleData[] { const rules: IRuleData[] = []; if (Array.isArray(data)) { for (const rule of data) { diff --git a/common/src/import-export/schema-rule.ts b/common/src/import-export/schema-rule.ts index c28cfb7b62..0f31d71570 100644 --- a/common/src/import-export/schema-rule.ts +++ b/common/src/import-export/schema-rule.ts @@ -144,7 +144,7 @@ export class SchemaRuleImportExport { * * @param data variable */ - private static validateRule( + public static validateRule( data?: IFormulaRuleData | IConditionRuleData | IRangeRuleData ): IFormulaRuleData | IConditionRuleData | IRangeRuleData { if (data) { diff --git a/docker-compose-indexer-build.yml b/docker-compose-indexer-build.yml index a28e72b57c..be1750dc7a 100644 --- a/docker-compose-indexer-build.yml +++ b/docker-compose-indexer-build.yml @@ -28,6 +28,13 @@ services: ipfs-node: image: ipfs/kubo:v0.31.0 + ipfs-check: + image: ghcr.io/ipfs/ipfs-check:main-latest + expose: + - 3333 + ports: + - 3333:3333 + message-broker: image: nats:2.9.25 expose: diff --git a/docker-compose-indexer.yml b/docker-compose-indexer.yml index a801fcaf66..5078521825 100644 --- a/docker-compose-indexer.yml +++ b/docker-compose-indexer.yml @@ -28,6 +28,13 @@ services: ipfs-node: image: ipfs/kubo:v0.31.0 + ipfs-check: + image: ghcr.io/ipfs/ipfs-check:main-latest + expose: + - 3333 + ports: + - 3333:3333 + message-broker: image: nats:2.9.25 expose: diff --git a/frontend/package.json b/frontend/package.json index a973453aab..7dbb253cb4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,7 +13,7 @@ "@angular/router": "^18.2.12", "@ctrl/ngx-codemirror": "^5.1.1", "@enonic/global-polyfill": "^1.0.0", - "@formulajs/formulajs": "^4.4.6", + "@formulajs/formulajs": "4.4.6", "@guardian/interfaces": "file:../interfaces", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 97490d93f3..8ec1f0ac2f 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -45,13 +45,18 @@ import { UsersManagementComponent } from './views/user-management/user-managemen import { UsersManagementDetailComponent } from './views/user-management-detail/user-management-detail.component'; import { WorkerTasksComponent } from './views/worker-tasks/worker-tasks.component'; import { MapService } from './services/map.service'; -import { StatisticAssessmentViewComponent } from './modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component'; -import { StatisticAssessmentsComponent } from './modules/policy-statistics/statistic-assessments/statistic-assessments.component'; -import { StatisticAssessmentConfigurationComponent } from './modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component'; -import { StatisticDefinitionConfigurationComponent } from './modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component'; -import { StatisticDefinitionsComponent } from './modules/policy-statistics/statistic-definitions/statistic-definitions.component'; -import { SchemaRulesComponent } from './modules/schema-rules/schema-rules/schema-rules.component'; -import { SchemaRuleConfigurationComponent } from './modules/schema-rules/schema-rule-configuration/schema-rule-configuration.component'; +import { PolicyLabelsComponent } from './modules/statistics/policy-labels/policy-labels/policy-labels.component'; +import { PolicyLabelConfigurationComponent } from './modules/statistics/policy-labels/policy-label-configuration/policy-label-configuration.component'; +import { StatisticAssessmentConfigurationComponent } from './modules/statistics/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component'; +import { StatisticAssessmentViewComponent } from './modules/statistics/policy-statistics/statistic-assessment-view/statistic-assessment-view.component'; +import { StatisticAssessmentsComponent } from './modules/statistics/policy-statistics/statistic-assessments/statistic-assessments.component'; +import { StatisticDefinitionConfigurationComponent } from './modules/statistics/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component'; +import { StatisticDefinitionsComponent } from './modules/statistics/policy-statistics/statistic-definitions/statistic-definitions.component'; +import { SchemaRuleConfigurationComponent } from './modules/statistics/schema-rules/schema-rule-configuration/schema-rule-configuration.component'; +import { SchemaRulesComponent } from './modules/statistics/schema-rules/schema-rules/schema-rules.component'; +import { PolicyLabelDocumentConfigurationComponent } from './modules/statistics/policy-labels/policy-label-document-configuration/policy-label-document-configuration.component'; +import { PolicyLabelDocumentsComponent } from './modules/statistics/policy-labels/policy-label-documents/policy-label-documents.component'; +import { PolicyLabelDocumentViewComponent } from './modules/statistics/policy-labels/policy-label-document-view/policy-label-document-view.component'; @Injectable({ providedIn: 'root' @@ -598,6 +603,82 @@ const routes: Routes = [ } }, + + + { + path: 'policy-labels', + component: PolicyLabelsComponent, + canActivate: [PermissionsGuard], + data: { + roles: [ + UserRole.STANDARD_REGISTRY, + UserRole.USER + ], + permissions: [ + Permissions.STATISTICS_LABEL_READ + ] + } + }, + { + path: 'policy-labels/:definitionId', + component: PolicyLabelConfigurationComponent, + canActivate: [PermissionsGuard], + data: { + roles: [ + UserRole.STANDARD_REGISTRY, + UserRole.USER + ], + permissions: [ + Permissions.STATISTICS_LABEL_READ + ] + } + }, + { + path: 'policy-labels/:definitionId/document', + component: PolicyLabelDocumentConfigurationComponent, + canActivate: [PermissionsGuard], + data: { + roles: [ + UserRole.STANDARD_REGISTRY, + UserRole.USER + ], + permissions: [ + Permissions.STATISTICS_LABEL_READ + ] + } + }, + { + path: 'policy-labels/:definitionId/documents', + component: PolicyLabelDocumentsComponent, + canActivate: [PermissionsGuard], + data: { + roles: [ + UserRole.STANDARD_REGISTRY, + UserRole.USER + ], + permissions: [ + Permissions.STATISTICS_LABEL_READ + ] + } + }, + { + path: 'policy-labels/:definitionId/documents/:documentId', + component: PolicyLabelDocumentViewComponent, + canActivate: [PermissionsGuard], + data: { + roles: [ + UserRole.STANDARD_REGISTRY, + UserRole.USER + ], + permissions: [ + Permissions.STATISTICS_LABEL_READ + ] + } + }, + + + + { path: '', component: HomeComponent }, { path: 'info', component: InfoComponent }, { path: '**', redirectTo: '', pathMatch: 'full' }, diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index a3c73c04a2..b842a86463 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -1,137 +1,133 @@ -import {NgModule} from '@angular/core'; -import {FormsModule} from '@angular/forms'; -import {BrowserModule} from '@angular/platform-browser'; -import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; -import {HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi, withJsonpSupport} from '@angular/common/http'; -import {CommonModule} from '@angular/common'; -import {ToastrModule} from 'ngx-toastr'; -import {AppRoutingModule, PermissionsGuard} from './app-routing.module'; -import {AppComponent} from './app.component'; -import {SchemaHelper} from '@guardian/interfaces'; -import {CheckboxModule} from 'primeng/checkbox'; +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { BrowserModule } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi, withJsonpSupport } from '@angular/common/http'; +import { CommonModule } from '@angular/common'; +import { ToastrModule } from 'ngx-toastr'; +import { AppRoutingModule, PermissionsGuard } from './app-routing.module'; +import { AppComponent } from './app.component'; +import { SchemaHelper } from '@guardian/interfaces'; +import { CheckboxModule } from 'primeng/checkbox'; //Services -import {AuthInterceptor, AuthService} from './services/auth.service'; -import {ProfileService} from './services/profile.service'; -import {TokenService} from './services/token.service'; -import {SchemaService} from './services/schema.service'; -import {HandleErrorsService} from './services/handle-errors.service'; -import {AuditService} from './services/audit.service'; -import {PolicyEngineService} from './services/policy-engine.service'; -import {PolicyStatisticsService} from './services/policy-statistics.service'; -import {DemoService} from './services/demo.service'; -import {PolicyHelper} from './services/policy-helper.service'; -import {IPFSService} from './services/ipfs.service'; -import {SettingsService} from './services/settings.service'; -import {LoggerService} from './services/logger.service'; -import {TasksService} from './services/tasks.service'; -import {ArtifactService} from './services/artifact.service'; -import {ContractService} from './services/contract.service'; -import {WebSocketService} from './services/web-socket.service'; -import {MessageTranslationService} from './services/message-translation-service/message-translation-service'; -import {AnalyticsService} from './services/analytics.service'; -import {ModulesService} from './services/modules.service'; -import {TagsService} from './services/tag.service'; -import {MapService} from './services/map.service'; -import {WizardService} from './modules/policy-engine/services/wizard.service'; -import {NotificationService} from './services/notify.service'; -import {PermissionsService} from './services/permissions.service'; -import {WorkerTasksService} from './services/worker-tasks.service'; -import {SchemaRulesService} from './services/schema-rules.service'; +import { AuthInterceptor, AuthService } from './services/auth.service'; +import { ProfileService } from './services/profile.service'; +import { TokenService } from './services/token.service'; +import { SchemaService } from './services/schema.service'; +import { HandleErrorsService } from './services/handle-errors.service'; +import { AuditService } from './services/audit.service'; +import { PolicyEngineService } from './services/policy-engine.service'; +import { PolicyStatisticsService } from './services/policy-statistics.service'; +import { DemoService } from './services/demo.service'; +import { PolicyHelper } from './services/policy-helper.service'; +import { IPFSService } from './services/ipfs.service'; +import { SettingsService } from './services/settings.service'; +import { LoggerService } from './services/logger.service'; +import { TasksService } from './services/tasks.service'; +import { ArtifactService } from './services/artifact.service'; +import { ContractService } from './services/contract.service'; +import { WebSocketService } from './services/web-socket.service'; +import { MessageTranslationService } from './services/message-translation-service/message-translation-service'; +import { AnalyticsService } from './services/analytics.service'; +import { ModulesService } from './services/modules.service'; +import { TagsService } from './services/tag.service'; +import { MapService } from './services/map.service'; +import { WizardService } from './modules/policy-engine/services/wizard.service'; +import { NotificationService } from './services/notify.service'; +import { PermissionsService } from './services/permissions.service'; +import { WorkerTasksService } from './services/worker-tasks.service'; +import { SchemaRulesService } from './services/schema-rules.service'; +import { PolicyLabelsService } from './services/policy-labels.service'; //Views -import {UserProfileComponent} from './views/user-profile/user-profile.component'; -import {LoginComponent} from './views/login/login.component'; -import {ChangePasswordComponent} from './views/login/change-password/change-password.component'; -import {HomeComponent} from './views/home/home.component'; -import {HeaderComponent} from './views/header/header.component'; -import {RegisterComponent} from './views/register/register.component'; -import {RootProfileComponent} from './views/root-profile/root-profile.component'; -import {TokenConfigComponent} from './views/token-config/token-config.component'; -import {AuditComponent} from './views/audit/audit.component'; -import {TrustChainComponent} from './views/trust-chain/trust-chain.component'; -import {AdminHeaderComponent} from './views/admin/admin-header/admin-panel.component'; -import {LogsViewComponent} from './views/admin/logs-view/logs-view.component'; -import {SettingsViewComponent} from './views/admin/settings-view/settings-view.component'; -import {DetailsLogDialog} from './views/admin/details-log-dialog/details-log-dialog.component'; -import {ServiceStatusComponent} from './views/admin/service-status/service-status.component'; -import {SchemaConfigComponent} from './views/schemas/schemas.component'; -import {NotificationsComponent} from './views/notifications/notifications.component'; -import {RolesViewComponent} from './views/roles/roles-view.component'; -import {UsersManagementComponent} from './views/user-management/user-management.component'; -import {UsersManagementDetailComponent} from './views/user-management-detail/user-management-detail.component'; +import { UserProfileComponent } from './views/user-profile/user-profile.component'; +import { LoginComponent } from './views/login/login.component'; +import { ChangePasswordComponent } from './views/login/change-password/change-password.component'; +import { HomeComponent } from './views/home/home.component'; +import { HeaderComponent } from './views/header/header.component'; +import { RegisterComponent } from './views/register/register.component'; +import { RootProfileComponent } from './views/root-profile/root-profile.component'; +import { TokenConfigComponent } from './views/token-config/token-config.component'; +import { AuditComponent } from './views/audit/audit.component'; +import { TrustChainComponent } from './views/trust-chain/trust-chain.component'; +import { AdminHeaderComponent } from './views/admin/admin-header/admin-panel.component'; +import { LogsViewComponent } from './views/admin/logs-view/logs-view.component'; +import { SettingsViewComponent } from './views/admin/settings-view/settings-view.component'; +import { DetailsLogDialog } from './views/admin/details-log-dialog/details-log-dialog.component'; +import { ServiceStatusComponent } from './views/admin/service-status/service-status.component'; +import { SchemaConfigComponent } from './views/schemas/schemas.component'; +import { NotificationsComponent } from './views/notifications/notifications.component'; +import { RolesViewComponent } from './views/roles/roles-view.component'; +import { UsersManagementComponent } from './views/user-management/user-management.component'; +import { UsersManagementDetailComponent } from './views/user-management-detail/user-management-detail.component'; //Components -import {InfoComponent} from './components/info/info/info.component'; -import {BrandingComponent} from './views/branding/branding.component'; -import {StandardRegistryCardComponent} from './components/standard-registry-card/standard-registry-card.component'; -import {SuggestionsConfigurationComponent} from './views/suggestions-configuration/suggestions-configuration.component'; -import {NotificationComponent} from './components/notification/notification.component'; -import {TokenDialogComponent} from './components/token-dialog/token-dialog.component'; +import { InfoComponent } from './components/info/info/info.component'; +import { BrandingComponent } from './views/branding/branding.component'; +import { StandardRegistryCardComponent } from './components/standard-registry-card/standard-registry-card.component'; +import { SuggestionsConfigurationComponent } from './views/suggestions-configuration/suggestions-configuration.component'; +import { NotificationComponent } from './components/notification/notification.component'; +import { TokenDialogComponent } from './components/token-dialog/token-dialog.component'; //Modules -import {MaterialModule} from './modules/common/material.module'; -import {PolicyEngineModule} from './modules/policy-engine/policy-engine.module'; -import {CompareModule} from './modules/analytics/analytics.module'; -import {CommonComponentsModule} from './modules/common/common-components.module'; -import {TagEngineModule} from './modules/tag-engine/tag-engine.module'; -import {SchemaEngineModule} from './modules/schema-engine/schema-engine.module' -import {ThemeService} from './services/theme.service'; -import {RecordService} from './services/record.service'; -import {PolicyStatisticsModule} from './modules/policy-statistics/policy-statistics.module'; -import {SchemaRulesModule} from './modules/schema-rules/schema-rules.module'; +import { MaterialModule } from './modules/common/material.module'; +import { PolicyEngineModule } from './modules/policy-engine/policy-engine.module'; +import { CompareModule } from './modules/analytics/analytics.module'; +import { CommonComponentsModule } from './modules/common/common-components.module'; +import { TagEngineModule } from './modules/tag-engine/tag-engine.module'; +import { SchemaEngineModule } from './modules/schema-engine/schema-engine.module' +import { ThemeService } from './services/theme.service'; +import { RecordService } from './services/record.service'; +import { StatisticsModule } from './modules/statistics/statistics.module'; // Injectors -import {GET_SCHEMA_NAME} from './injectors/get-schema-name.injector'; -import {BLOCK_TYPE_TIPS, BLOCK_TYPE_TIPS_VALUE,} from './injectors/block-type-tips.injector'; -import {SuggestionsService} from './services/suggestions.service'; -import {QrCodeDialogComponent} from './components/qr-code-dialog/qr-code-dialog.component'; -import {QRCodeModule} from 'angularx-qrcode'; -import {MeecoVCSubmitDialogComponent} from './components/meeco-vc-submit-dialog/meeco-vc-submit-dialog.component'; -import {AboutViewComponent} from './views/admin/about-view/about-view.component'; -import {CompareStorage} from './services/compare-storage.service'; -import {ToolsService} from './services/tools.service'; -import {NewHeaderComponent} from './views/new-header/new-header.component'; -import {SearchResultCardComponent} from './components/search-result-card/search-result-card.component'; -import {PolicyAISearchComponent} from './views/policy-search/policy-ai-search/policy-ai-search.component'; -import {PolicyGuidedSearchComponent} from './views/policy-search/policy-guided-search/policy-guided-search.component'; -import {PolicySearchComponent} from './views/policy-search/policy-search.component'; -import {ListOfTokensUserComponent} from './views/list-of-tokens-user/list-of-tokens-user.component'; +import { GET_SCHEMA_NAME } from './injectors/get-schema-name.injector'; +import { BLOCK_TYPE_TIPS, BLOCK_TYPE_TIPS_VALUE, } from './injectors/block-type-tips.injector'; +import { SuggestionsService } from './services/suggestions.service'; +import { QrCodeDialogComponent } from './components/qr-code-dialog/qr-code-dialog.component'; +import { QRCodeModule } from 'angularx-qrcode'; +import { MeecoVCSubmitDialogComponent } from './components/meeco-vc-submit-dialog/meeco-vc-submit-dialog.component'; +import { AboutViewComponent } from './views/admin/about-view/about-view.component'; +import { CompareStorage } from './services/compare-storage.service'; +import { ToolsService } from './services/tools.service'; +import { NewHeaderComponent } from './views/new-header/new-header.component'; +import { SearchResultCardComponent } from './components/search-result-card/search-result-card.component'; +import { PolicyAISearchComponent } from './views/policy-search/policy-ai-search/policy-ai-search.component'; +import { PolicyGuidedSearchComponent } from './views/policy-search/policy-guided-search/policy-guided-search.component'; +import { PolicySearchComponent } from './views/policy-search/policy-search.component'; +import { ListOfTokensUserComponent } from './views/list-of-tokens-user/list-of-tokens-user.component'; // PrimeNG -import {InputTextModule} from 'primeng/inputtext'; -import {SelectButtonModule} from 'primeng/selectbutton'; -import {DropdownModule} from 'primeng/dropdown'; -import {TableModule} from 'primeng/table'; -import {DialogModule} from 'primeng/dialog'; -import {TagModule} from 'primeng/tag'; -import {ButtonModule} from 'primeng/button'; -import {TooltipModule} from 'primeng/tooltip'; -import {StepsModule} from 'primeng/steps'; -import {ProgressBarModule} from 'primeng/progressbar'; -import {TabViewModule} from 'primeng/tabview'; -import {DynamicDialogModule} from 'primeng/dynamicdialog'; -import {ColorPickerModule} from 'primeng/colorpicker'; -import {ProgressSpinnerModule} from 'primeng/progressspinner'; -import {AISearchService} from './services/ai-search.service'; -import {DndModule} from 'ngx-drag-drop'; -import {PasswordModule} from 'primeng/password'; -import {RegisterDialogComponent} from './views/login/register-dialogs/register-dialog/register-dialog.component'; -import { - TermsConditionsComponent -} from './views/login/register-dialogs/terms-conditions-dialog/terms-conditions.component'; -import { - AccountTypeSelectorDialogComponent -} from './views/login/register-dialogs/account-type-selector-dialog/account-type-selector-dialog.component'; -import {ForgotPasswordDialogComponent} from './views/login/forgot-password-dialog/forgot-password-dialog.component'; -import {MultiSelectModule} from 'primeng/multiselect'; -import {RadioButtonModule} from 'primeng/radiobutton'; -import {CalendarModule} from 'primeng/calendar'; -import {InputTextareaModule} from 'primeng/inputtextarea'; -import {ContractEngineModule} from './modules/contract-engine/contract-engine.module'; -import {ProjectComparisonService} from './services/project-comparison.service'; -import {ProjectComparisonModule} from './modules/project-comparison/project-comparison.module'; -import {AngularSvgIconModule} from 'angular-svg-icon'; +import { InputTextModule } from 'primeng/inputtext'; +import { SelectButtonModule } from 'primeng/selectbutton'; +import { DropdownModule } from 'primeng/dropdown'; +import { TableModule } from 'primeng/table'; +import { DialogModule } from 'primeng/dialog'; +import { TagModule } from 'primeng/tag'; +import { ButtonModule } from 'primeng/button'; +import { TooltipModule } from 'primeng/tooltip'; +import { StepsModule } from 'primeng/steps'; +import { ProgressBarModule } from 'primeng/progressbar'; +import { TabViewModule } from 'primeng/tabview'; +import { DynamicDialogModule } from 'primeng/dynamicdialog'; +import { ColorPickerModule } from 'primeng/colorpicker'; +import { ProgressSpinnerModule } from 'primeng/progressspinner'; +import { AISearchService } from './services/ai-search.service'; +import { DndModule } from 'ngx-drag-drop'; +import { PasswordModule } from 'primeng/password'; +import { RegisterDialogComponent } from './views/login/register-dialogs/register-dialog/register-dialog.component'; +import { TermsConditionsComponent } from './views/login/register-dialogs/terms-conditions-dialog/terms-conditions.component'; +import { AccountTypeSelectorDialogComponent } from './views/login/register-dialogs/account-type-selector-dialog/account-type-selector-dialog.component'; +import { ForgotPasswordDialogComponent } from './views/login/forgot-password-dialog/forgot-password-dialog.component'; +import { MultiSelectModule } from 'primeng/multiselect'; +import { RadioButtonModule } from 'primeng/radiobutton'; +import { CalendarModule } from 'primeng/calendar'; +import { InputTextareaModule } from 'primeng/inputtextarea'; +import { ContractEngineModule } from './modules/contract-engine/contract-engine.module'; +import { ProjectComparisonService } from './services/project-comparison.service'; +import { ProjectComparisonModule } from './modules/project-comparison/project-comparison.module'; +import { AngularSvgIconModule } from 'angular-svg-icon'; // Prototypes import '../prototypes/date-prototype'; -import {OnlyForDemoDirective} from './directives/onlyfordemo.directive'; -import {UseWithServiceDirective} from './directives/use-with-service.directive'; -import {WorkerTasksComponent} from './views/worker-tasks/worker-tasks.component'; +import { OnlyForDemoDirective } from './directives/onlyfordemo.directive'; +import { UseWithServiceDirective } from './directives/use-with-service.directive'; +import { WorkerTasksComponent } from './views/worker-tasks/worker-tasks.component'; @NgModule({ declarations: [ @@ -190,8 +186,7 @@ import {WorkerTasksComponent} from './views/worker-tasks/worker-tasks.component' FormsModule, SchemaEngineModule, PolicyEngineModule, - PolicyStatisticsModule, - SchemaRulesModule, + StatisticsModule, TagEngineModule, CompareModule, ToastrModule.forRoot(), @@ -232,6 +227,7 @@ import {WorkerTasksComponent} from './views/worker-tasks/worker-tasks.component' PolicyEngineService, PolicyStatisticsService, SchemaRulesService, + PolicyLabelsService, PolicyHelper, IPFSService, ArtifactService, diff --git a/frontend/src/app/modules/common/async-progress/async-progress.component.ts b/frontend/src/app/modules/common/async-progress/async-progress.component.ts index 1beecb06b9..9fed98aae5 100644 --- a/frontend/src/app/modules/common/async-progress/async-progress.component.ts +++ b/frontend/src/app/modules/common/async-progress/async-progress.component.ts @@ -322,6 +322,15 @@ export class AsyncProgressComponent implements OnInit, OnDestroy { replaceUrl: true, }); break; + case TaskAction.PUBLISH_POLICY_LABEL: + if (this.last) { + this.redirect(this.last); + return; + } + this.router.navigate(['policy-labels'], { + replaceUrl: true, + }); + break; } } diff --git a/frontend/src/app/modules/common/import-entity-dialog/import-entity-dialog.component.ts b/frontend/src/app/modules/common/import-entity-dialog/import-entity-dialog.component.ts index 850f7a9617..e0626b4440 100644 --- a/frontend/src/app/modules/common/import-entity-dialog/import-entity-dialog.component.ts +++ b/frontend/src/app/modules/common/import-entity-dialog/import-entity-dialog.component.ts @@ -10,6 +10,7 @@ import { MenuItem } from 'primeng/api'; import { ToolsService } from 'src/app/services/tools.service'; import { SchemaRulesService } from 'src/app/services/schema-rules.service'; import { PolicyStatisticsService } from 'src/app/services/policy-statistics.service'; +import { PolicyLabelsService } from 'src/app/services/policy-labels.service'; export enum ImportEntityType { Policy = 'policy', @@ -19,7 +20,8 @@ export enum ImportEntityType { Record = 'record', SchemaRule = 'schema-rule', Theme = 'theme', - Statistic = 'statistic' + Statistic = 'statistic', + PolicyLabel = 'policy-label' } export interface IImportEntityArray { @@ -31,6 +33,7 @@ export interface IImportEntityArray { xlsx?: any, rule?: any, statistic?: any, + label?: any, } export interface IImportEntityMessage { @@ -42,6 +45,7 @@ export interface IImportEntityMessage { xlsx?: any, rule?: any, statistic?: any, + label?: any, } export type IImportEntityResult = IImportEntityArray | IImportEntityMessage; @@ -93,7 +97,8 @@ export class ImportEntityDialog { private informService: InformService, private taskService: TasksService, private schemaRulesService: SchemaRulesService, - private policyStatisticsService: PolicyStatisticsService + private policyStatisticsService: PolicyStatisticsService, + private policyLabelsService: PolicyLabelsService ) { const _config = this.config.data || {}; @@ -162,6 +167,14 @@ export class ImportEntityDialog { this.fileExtension = 'statistic'; this.placeholder = 'Import Statistic .statistic file'; break; + case 'policy-label': + this.type = ImportEntityType.PolicyLabel; + this.canImportFile = true; + this.canImportMessage = false; + this.title = 'Import Label'; + this.fileExtension = 'label'; + this.placeholder = 'Import Label .label file'; + break; default: this.type = ImportEntityType.Policy; this.canImportFile = true; @@ -233,22 +246,46 @@ export class ImportEntityDialog { reader.readAsArrayBuffer(file); reader.addEventListener('load', (e: any) => { const arrayBuffer = e.target.result; - if (this.type === ImportEntityType.Xlsx) { - this.excelFromFile(arrayBuffer); - } else if (this.type === ImportEntityType.Module) { - this.moduleFromFile(arrayBuffer); - } else if (this.type === ImportEntityType.Tool) { - this.toolFromFile(arrayBuffer); - } else if (this.type === ImportEntityType.Policy) { - this.policyFromFile(arrayBuffer); - } else if (this.type === ImportEntityType.Record) { - this.recordFromFile(arrayBuffer); - } else if (this.type === ImportEntityType.SchemaRule) { - this.ruleFromFile(arrayBuffer); - } else if (this.type === ImportEntityType.Theme) { - this.themeFromFile(arrayBuffer); - } else if (this.type === ImportEntityType.Statistic) { - this.statisticFromFile(arrayBuffer); + switch (this.type) { + case ImportEntityType.Xlsx: { + this.excelFromFile(arrayBuffer); + break; + } + case ImportEntityType.Module: { + this.moduleFromFile(arrayBuffer); + break; + } + case ImportEntityType.Tool: { + this.toolFromFile(arrayBuffer); + break; + } + case ImportEntityType.Policy: { + this.policyFromFile(arrayBuffer); + break; + } + case ImportEntityType.Record: { + this.recordFromFile(arrayBuffer); + break; + } + case ImportEntityType.SchemaRule: { + this.ruleFromFile(arrayBuffer); + break; + } + case ImportEntityType.Theme: { + this.themeFromFile(arrayBuffer); + break; + } + case ImportEntityType.Statistic: { + this.statisticFromFile(arrayBuffer); + break; + } + case ImportEntityType.PolicyLabel: { + this.labelFromFile(arrayBuffer); + break; + } + default: { + break; + } } }); } @@ -259,22 +296,40 @@ export class ImportEntityDialog { return; } const messageId = this.dataForm.get('timestamp')?.value; - if (this.type === ImportEntityType.Module) { - this.moduleFromMessage(messageId); - } else if (this.type === ImportEntityType.Tool) { - this.toolFromMessage(messageId); - } else if (this.type === ImportEntityType.Policy) { - this.policyFromMessage(messageId); - } else if (this.type === ImportEntityType.Xlsx) { - return; - } else if (this.type === ImportEntityType.Record) { - return; - } else if (this.type === ImportEntityType.SchemaRule) { - return; - } else if (this.type === ImportEntityType.Theme) { - return; - } else if (this.type === ImportEntityType.Statistic) { - return; + switch (this.type) { + case ImportEntityType.Xlsx: { + return; + } + case ImportEntityType.Module: { + this.moduleFromMessage(messageId); + break; + } + case ImportEntityType.Tool: { + this.toolFromMessage(messageId); + break; + } + case ImportEntityType.Policy: { + this.policyFromMessage(messageId); + break; + } + case ImportEntityType.Record: { + return; + } + case ImportEntityType.SchemaRule: { + return; + } + case ImportEntityType.Theme: { + return; + } + case ImportEntityType.Statistic: { + return; + } + case ImportEntityType.PolicyLabel: { + return; + } + default: { + return; + } } } @@ -443,4 +498,22 @@ export class ImportEntityDialog { this.loading = false; }); } + + + //Label + private labelFromFile(arrayBuffer: any) { + this.loading = true; + this.policyLabelsService + .previewByFile(arrayBuffer) + .subscribe((result) => { + this.loading = false; + this.setResult({ + type: 'file', + data: arrayBuffer, + label: result + }); + }, (e) => { + this.loading = false; + }); + } } \ No newline at end of file diff --git a/frontend/src/app/modules/common/models/conditions.ts b/frontend/src/app/modules/common/models/conditions.ts new file mode 100644 index 0000000000..2285583009 --- /dev/null +++ b/frontend/src/app/modules/common/models/conditions.ts @@ -0,0 +1,337 @@ +import { + IConditionElseData, + IConditionEnum, + IConditionFormula, + IConditionIfData, + IConditionRange, + IConditionRuleData, + IConditionText, + IFormulaRuleData, + IRangeRuleData +} from "@guardian/interfaces"; +import { FieldRule } from "./field-rule"; +import { SchemaFormula } from "./schema-formulas"; + +export class ConditionValue { + public type: 'formula' | 'range' | 'text' | 'enum'; + public formula: string; + public min: string | number; + public max: string | number; + public text: string; + public enum: string[]; + + private _parentType: 'if' | 'then'; + private _parent: ConditionIf | ConditionElse; + private _variable: string; + + public get variable(): string { + if (this._parentType === 'then') { + return this._parent?.variable; + } else { + return this._variable; + } + } + + public set variable(value: string) { + this._variable = value; + } + + constructor( + parentType: 'if' | 'then', + parent: ConditionIf | ConditionElse + ) { + this._parentType = parentType; + this._parent = parent; + this.type = 'formula'; + this.max = 0; + this.min = 0; + this.text = ''; + this.enum = []; + this.formula = ''; + } + + public getJson(): (IConditionFormula | IConditionRange | IConditionText | IConditionEnum) { + if (this.type === 'formula') { + return { + type: this.type, + formula: this.formula, + } + } else if (this.type === 'range') { + return { + type: this.type, + variable: this.variable, + min: this.min, + max: this.max, + } + } else if (this.type === 'text') { + return { + type: this.type, + variable: this.variable, + value: this.text, + } + } else if (this.type === 'enum') { + return { + type: this.type, + variable: this.variable, + value: this.enum, + } + } else { + return { + type: 'formula', + formula: '', + } + } + } + + public static fromData( + parentType: 'if' | 'then', + parent: ConditionIf | ConditionElse, + data?: (IConditionFormula | IConditionRange | IConditionText | IConditionEnum) + ): ConditionValue { + const item = new ConditionValue(parentType, parent); + if (data) { + item.type = data.type; + if (data.type === 'formula') { + item.formula = data.formula; + return item; + } else if (data.type === 'range') { + item.variable = data.variable; + item.min = data.min; + item.max = data.max; + return item; + } else if (data.type === 'text') { + item.variable = data.variable; + item.text = data.value; + return item; + } else if (data.type === 'enum') { + item.variable = data.variable; + item.enum = data.value; + return item; + } + } + return item; + } +} + +export class ConditionIf { + public readonly type = 'if'; + public condition: ConditionValue; + public formula: ConditionValue; + + private _parent: ConditionRule; + + constructor(parent: ConditionRule) { + this._parent = parent; + this.condition = new ConditionValue('if', this); + this.formula = new ConditionValue('then', this); + } + + public get variable(): string { + return this._parent?.variable; + } + + public getJson(): IConditionIfData { + return { + type: this.type, + condition: this.condition.getJson(), + formula: this.formula.getJson(), + } + } + + public static fromData(parent: ConditionRule, data: IConditionIfData): ConditionIf { + const item = new ConditionIf(parent); + item.condition = ConditionValue.fromData('if', item, data.condition); + item.formula = ConditionValue.fromData('then', item, data.formula); + return item; + } + + public static fromConditions( + parent: ConditionRule, + data?: (IConditionIfData | IConditionElseData)[] + ): ConditionIf[] { + let result: ConditionIf[]; + if (Array.isArray(data)) { + const list = data.filter((e) => e.type === 'if'); + result = list.map((e) => ConditionIf.fromData(parent, e as IConditionIfData)); + } else { + result = []; + } + if (result.length === 0) { + result.push(new ConditionIf(parent)); + } + return result; + } +} + +export class ConditionElse { + public readonly type = 'else'; + public formula: ConditionValue; + + private _parent: ConditionRule; + + constructor(parent: ConditionRule) { + this._parent = parent; + this.formula = new ConditionValue('then', this); + } + + public get variable(): string { + return this._parent?.variable; + } + + public getJson(): IConditionElseData { + return { + type: this.type, + formula: this.formula.getJson(), + } + } + + public static fromData(parent: ConditionRule, data: IConditionElseData): ConditionElse { + const item = new ConditionElse(parent); + item.formula = ConditionValue.fromData('then', item, data.formula); + return item; + } + + public static fromConditions( + parent: ConditionRule, + data?: (IConditionIfData | IConditionElseData)[] + ): ConditionElse { + if (Array.isArray(data)) { + const item = data.find((e) => e.type === 'else'); + if (item) { + return ConditionElse.fromData(parent, item as IConditionElseData); + } + } + return new ConditionElse(parent); + } +} + +export class ConditionRule { + public readonly type = 'condition'; + public conditions: (IConditionIfData | IConditionElseData)[]; + public if: ConditionIf[]; + public else: ConditionElse; + + private _parent: FieldRule | SchemaFormula; + + constructor(parent: FieldRule | SchemaFormula) { + this._parent = parent; + this.conditions = []; + this.if = [new ConditionIf(this)]; + this.else = new ConditionElse(this); + } + + public get variable(): string { + return this._parent?.id; + } + + public setParent(parent: FieldRule | SchemaFormula) { + this._parent = parent; + } + + public getJson(): IConditionRuleData { + return { + type: this.type, + conditions: [ + ...(this.if.map((e) => e.getJson())), + this.else.getJson() + ] + } + } + + public static fromData( + parent: FieldRule | SchemaFormula, + data: IConditionRuleData + ): ConditionRule { + const item = new ConditionRule(parent); + item.conditions = data.conditions || []; + item.if = ConditionIf.fromConditions(item, data.conditions); + item.else = ConditionElse.fromConditions(item, data.conditions); + return item; + } + + public addCondition() { + this.if.push(new ConditionIf(this)); + } + + public deleteCondition(item: ConditionIf) { + if (this.if.length > 1) { + this.if = this.if.filter((e) => e !== item) + } + } +} + +export class FormulaRule { + public readonly type = 'formula'; + public formula: string; + + private _parent: FieldRule | SchemaFormula; + + constructor(parent: FieldRule | SchemaFormula) { + this._parent = parent; + this.formula = ''; + } + + public get variable(): string { + return this._parent?.id; + } + + public setParent(parent: FieldRule | SchemaFormula) { + this._parent = parent; + } + + public getJson(): IFormulaRuleData { + return { + type: this.type, + formula: this.formula + } + } + + public static fromData( + parent: FieldRule | SchemaFormula, + data: IFormulaRuleData + ): FormulaRule { + const item = new FormulaRule(parent); + item.formula = data.formula; + return item; + } +} + +export class RangeRule { + public readonly type = 'range'; + public min: string | number; + public max: string | number; + + private _parent: FieldRule | SchemaFormula; + + constructor(parent: FieldRule | SchemaFormula) { + this._parent = parent; + this.min = 0; + this.max = 0; + } + + public get variable(): string { + return this._parent?.id; + } + + public setParent(parent: FieldRule | SchemaFormula) { + this._parent = parent; + } + + public getJson(): IRangeRuleData { + return { + type: this.type, + min: this.min, + max: this.max + } + } + + public static fromData( + parent: FieldRule | SchemaFormula, + data: IRangeRuleData + ): RangeRule { + const item = new RangeRule(parent); + item.min = data.min; + item.max = data.max; + return item; + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/common/models/field-rule-validator.ts b/frontend/src/app/modules/common/models/field-rule-validator.ts deleted file mode 100644 index 16a520db6d..0000000000 --- a/frontend/src/app/modules/common/models/field-rule-validator.ts +++ /dev/null @@ -1,406 +0,0 @@ -import { Formula } from 'src/app/utils'; -import { - IConditionEnum, - IConditionFormula, - IConditionRange, - IConditionRuleData, - IConditionText, - IFormulaRuleData, - IRangeRuleData, - ISchemaRuleData, - IVC, - IVCDocument -} from '@guardian/interfaces'; - -enum FieldRuleResult { - None = 'None', - Error = 'Error', - Failure = 'Failure', - Success = 'Success', -} - -export interface SchemaRuleValidateResult { - [path: string]: { - status: FieldRuleResult; - tooltip: string; - rules: { - name: string; - description: string; - status: string; - }[] - } -} - -abstract class AbstractFieldRule { - public readonly id: string; - public readonly path: string; - public readonly schemaId: string; - public readonly rule: IFormulaRuleData | IConditionRuleData | IRangeRuleData | undefined; - - private type: 'formula' | 'range' | 'condition' | 'none'; - private formula: string; - private conditions: { - type: 'if' | 'else', - if: string; - then: string; - }[]; - - constructor(rule: ISchemaRuleData) { - this.id = rule.id; - this.path = rule.path; - this.schemaId = rule.schemaId; - this.rule = rule.rule; - this.parse(); - } - - abstract calculate(formula: string, scope: any): FieldRuleResult; - - private parse() { - if (!this.rule) { - return; - } - if (this.rule.type === 'formula') { - this.type = 'formula'; - this.formula = this.rule.formula; - } else if (this.rule.type === 'range') { - this.type = 'range'; - this.formula = `${this.rule.min} <= ${this.id} <= ${this.rule.max}`; - } else if (this.rule.type === 'condition') { - this.type = 'condition'; - this.conditions = []; - const conditions = this.rule.conditions || []; - for (const condition of conditions) { - if (condition.type === 'if') { - this.conditions.push({ - type: 'if', - if: this.parseCondition(condition.condition), - then: this.parseCondition(condition.formula) - }) - } else if (condition.type === 'else') { - this.conditions.push({ - type: 'else', - if: '', - then: this.parseCondition(condition.formula) - }) - } - } - } else { - this.type = 'none'; - } - } - - private parseCondition(condition: IConditionFormula | IConditionRange | IConditionText | IConditionEnum): string { - if (!condition) { - return ''; - } - if (condition.type === 'formula') { - return condition.formula; - } else if (condition.type === 'range') { - return `${condition.min} <= ${condition.variable} <= ${condition.max}`; - } else if (condition.type === 'text') { - return `${condition.variable} == '${condition.value}'`; - } else if (condition.type === 'enum') { - const items = []; - if (Array.isArray(condition.value)) { - for (const value of condition.value) { - items.push(`${condition.variable} == '${value}'`) - } - } - return items.join(' or '); - } else { - return ''; - } - } - - public checkField(path: string, schema?: string): boolean { - if (schema) { - return this.path === path && this.schemaId === schema; - } else { - return this.path === path; - } - } - - public validate(scope: any): FieldRuleResult { - if (this.type === 'none') { - return FieldRuleResult.None; - } - - if (this.type === 'formula') { - return this.calculate(this.formula, scope); - } - - if (this.type === 'range') { - return this.calculate(this.formula, scope); - } - - if (this.type === 'condition') { - for (const condition of this.conditions) { - const _if = condition.type === 'if' ? - this.calculate(condition.if, scope) : - FieldRuleResult.Success; - - if (_if === FieldRuleResult.Error) { - return FieldRuleResult.Error; - } - if (_if === FieldRuleResult.Success) { - return this.calculate(condition.then, scope); - } - } - return FieldRuleResult.None; - } - - return FieldRuleResult.None; - } -} - -export class FieldRuleValidator extends AbstractFieldRule { - public override calculate(formula: string, scope: any): FieldRuleResult { - try { - if (!formula) { - return FieldRuleResult.None; - } - const result: any = Formula.evaluate(formula, scope); - if (result === '' || result === 'Incorrect formula') { - return FieldRuleResult.Error; - } - if (result === 0 || result === false || result === '0' || result === 'false') { - return FieldRuleResult.Failure; - } - if (result) { - return FieldRuleResult.Success - } - return FieldRuleResult.Error; - } catch (error) { - return FieldRuleResult.Error; - } - } -} - -export class FieldVariable { - public readonly id: string; - public readonly schemaId: string; - public readonly path: string; - public readonly fullPah: string; - public readonly fieldRef: boolean; - public readonly fieldArray: boolean; - public readonly fieldDescription: string; - public readonly schemaName: string; - - constructor(variable: ISchemaRuleData) { - this.id = variable.id; - this.schemaId = variable.schemaId; - this.path = variable.path; - this.fullPah = variable.schemaId + '/' + variable.path; - this.fieldRef = variable.fieldRef; - this.fieldArray = variable.fieldArray; - this.fieldDescription = variable.fieldDescription; - this.schemaName = variable.schemaName; - } -} - -export class FieldRuleValidators { - public readonly rules: FieldRuleValidator[]; - public readonly variables: FieldVariable[]; - public readonly idToPath: Map; - public readonly pathToId: Map; - - constructor(rules?: ISchemaRuleData[]) { - const variables = rules || []; - - this.rules = []; - this.variables = []; - for (const variable of variables) { - this.rules.push(new FieldRuleValidator(variable)); - this.variables.push(new FieldVariable(variable)); - } - this.idToPath = new Map(); - this.pathToId = new Map(); - for (const variable of this.variables) { - this.idToPath.set(variable.id, variable.fullPah); - this.pathToId.set(variable.fullPah, variable.id); - } - } - - public validate(scope: any): { [x: string]: FieldRuleResult } { - const result: { [x: string]: FieldRuleResult } = {}; - for (const rule of this.rules) { - result[rule.id] = rule.validate(scope); - } - return result; - } - - public validateWithFullPath(scope: any): { [x: string]: FieldRuleResult } { - const result: { [x: string]: FieldRuleResult } = {}; - for (const rule of this.rules) { - const path = this.idToPath.get(rule.id) || rule.id; - result[path] = rule.validate(scope); - } - return result; - } -} - -export class SchemaRuleValidator { - public readonly name: string; - public readonly description: string; - public readonly schemas: Set; - public readonly validators: FieldRuleValidators; - public readonly relationships: Map; - - constructor(data: any) { - const item = data.rules || {}; - const configuration = item.config || {}; - const relationships = data.relationships || []; - - - this.relationships = new Map() - for (const document of relationships) { - SchemaRuleValidator.convertDocument( - SchemaRuleValidator.getCredentialSubject(document?.document), - document?.schema + '/', - this.relationships - ); - } - - this.name = item.name; - this.description = item.description; - this.validators = new FieldRuleValidators(configuration.fields); - this.schemas = new Set(); - for (const variable of this.validators.variables) { - this.schemas.add(variable.schemaId); - } - } - - public validate(iri: string | undefined, list: Map) { - if (!iri || !this.schemas.has(iri)) { - return null; - } - const score: { [id: string]: any } = {}; - for (const variable of this.validators.variables) { - if (list.has(variable.fullPah)) { - score[variable.id] = list.get(variable.fullPah); - } else if (this.relationships.has(variable.fullPah)) { - score[variable.id] = this.relationships.get(variable.fullPah); - } else { - score[variable.id] = null; - } - } - return this.validators.validateWithFullPath(score); - } - - public static getCredentialSubject(document?: IVC): any { - let credentialSubject: any = document?.credentialSubject; - if (Array.isArray(credentialSubject)) { - return credentialSubject[0]; - } else { - credentialSubject; - } - } - - public static convertDocument( - document: any, - path: string, - list: Map - ): Map { - if (!document) { - return list; - } - for (const [key, value] of Object.entries(document)) { - const currentPath = path + key; - switch (typeof value) { - case 'function': { - break; - } - case 'object': { - list.set(currentPath, value); - if (!Array.isArray(value)) { - SchemaRuleValidator.convertDocument(value, currentPath + '.', list); - } - break; - } - default: { - list.set(currentPath, value); - break; - } - } - } - return list; - } -} - -export class SchemaRuleValidators { - public schemas: Set; - public validators: SchemaRuleValidator[]; - - constructor(data: any[] | null) { - this.validators = (data || []).map((v) => new SchemaRuleValidator(v)); - this.schemas = new Set(); - for (const validator of this.validators) { - for (const iri of validator.schemas) { - this.schemas.add(iri); - } - } - } - - public validateVC(iri: string | undefined, vc: any): any { - if (this.validators.length === 0) { - return null; - } - if (!iri || !this.schemas.has(iri)) { - return null; - } - const data = SchemaRuleValidator.getCredentialSubject(vc); - const list = SchemaRuleValidator.convertDocument(data, iri + '/', new Map()); - return this.validate(iri, list); - } - - public validateForm(iri: string | undefined, data: any): any { - if (this.validators.length === 0) { - return null; - } - if (!iri || !this.schemas.has(iri)) { - return null; - } - const list = SchemaRuleValidator.convertDocument(data, iri + '/', new Map()); - return this.validate(iri, list); - } - - private validate(iri: string | undefined, list: Map): any { - const results = []; - for (const validator of this.validators) { - results.push(validator.validate(iri, list)); - } - - const statuses: SchemaRuleValidateResult = {}; - for (let i = 0; i < results.length; i++) { - const result = results[i]; - const validator = this.validators[i]; - if (result) { - for (const [path, status] of Object.entries(result)) { - if (status !== FieldRuleResult.None) { - if (statuses[path]) { - if (status === FieldRuleResult.Error || status === FieldRuleResult.Failure) { - statuses[path].status = status; - } - statuses[path].rules.push({ - name: validator.name, - description: validator.description, - status: status, - }) - } else { - statuses[path] = { - status: status, - tooltip: '', - rules: [{ - name: validator.name, - description: validator.description, - status: status, - }] - } - } - } - } - } - } - return statuses; - } -} \ No newline at end of file diff --git a/frontend/src/app/modules/common/models/field-rule.ts b/frontend/src/app/modules/common/models/field-rule.ts index 709ecb4609..374ec4c9e1 100644 --- a/frontend/src/app/modules/common/models/field-rule.ts +++ b/frontend/src/app/modules/common/models/field-rule.ts @@ -1,336 +1,7 @@ -import { - IConditionElseData, - IConditionEnum, - IConditionFormula, - IConditionIfData, - IConditionRange, - IConditionRuleData, - IConditionText, - IFormulaRuleData, - IRangeRuleData, - ISchemaRuleData, - Schema -} from "@guardian/interfaces"; -import { FieldData, SchemaNode } from "./schema-node"; +import { ISchemaRuleData, IFormulaRuleData, IConditionRuleData, IRangeRuleData, Schema } from "@guardian/interfaces"; import { TreeListItem } from "../tree-graph/tree-list"; - -export class ConditionValue { - public type: 'formula' | 'range' | 'text' | 'enum'; - public formula: string; - public min: string | number; - public max: string | number; - public text: string; - public enum: string[]; - - private _parentType: 'if' | 'then'; - private _parent: ConditionIf | ConditionElse; - private _variable: string; - - public get variable(): string { - if (this._parentType === 'then') { - return this._parent?.variable; - } else { - return this._variable; - } - } - - public set variable(value: string) { - this._variable = value; - } - - constructor( - parentType: 'if' | 'then', - parent: ConditionIf | ConditionElse - ) { - this._parentType = parentType; - this._parent = parent; - this.type = 'formula'; - this.max = 0; - this.min = 0; - this.text = ''; - this.enum = []; - this.formula = ''; - } - - public getJson(): (IConditionFormula | IConditionRange | IConditionText | IConditionEnum) { - if (this.type === 'formula') { - return { - type: this.type, - formula: this.formula, - } - } else if (this.type === 'range') { - return { - type: this.type, - variable: this.variable, - min: this.min, - max: this.max, - } - } else if (this.type === 'text') { - return { - type: this.type, - variable: this.variable, - value: this.text, - } - } else if (this.type === 'enum') { - return { - type: this.type, - variable: this.variable, - value: this.enum, - } - } else { - return { - type: 'formula', - formula: '', - } - } - } - - public static fromData( - parentType: 'if' | 'then', - parent: ConditionIf | ConditionElse, - data?: (IConditionFormula | IConditionRange | IConditionText | IConditionEnum) - ): ConditionValue { - const item = new ConditionValue(parentType, parent); - if (data) { - item.type = data.type; - if (data.type === 'formula') { - item.formula = data.formula; - return item; - } else if (data.type === 'range') { - item.variable = data.variable; - item.min = data.min; - item.max = data.max; - return item; - } else if (data.type === 'text') { - item.variable = data.variable; - item.text = data.value; - return item; - } else if (data.type === 'enum') { - item.variable = data.variable; - item.enum = data.value; - return item; - } - } - return item; - } -} - -export class ConditionIf { - public readonly type = 'if'; - public condition: ConditionValue; - public formula: ConditionValue; - - private _parent: ConditionRule; - - constructor(parent: ConditionRule) { - this._parent = parent; - this.condition = new ConditionValue('if', this); - this.formula = new ConditionValue('then', this); - } - - public get variable(): string { - return this._parent?.variable; - } - - public getJson(): IConditionIfData { - return { - type: this.type, - condition: this.condition.getJson(), - formula: this.formula.getJson(), - } - } - - public static fromData(parent: ConditionRule, data: IConditionIfData): ConditionIf { - const item = new ConditionIf(parent); - item.condition = ConditionValue.fromData('if', item, data.condition); - item.formula = ConditionValue.fromData('then', item, data.formula); - return item; - } - - public static fromConditions( - parent: ConditionRule, - data?: (IConditionIfData | IConditionElseData)[] - ): ConditionIf[] { - let result: ConditionIf[]; - if (Array.isArray(data)) { - const list = data.filter((e) => e.type === 'if'); - result = list.map((e) => ConditionIf.fromData(parent, e as IConditionIfData)); - } else { - result = []; - } - if (result.length === 0) { - result.push(new ConditionIf(parent)); - } - return result; - } -} - -export class ConditionElse { - public readonly type = 'else'; - public formula: ConditionValue; - - private _parent: ConditionRule; - - constructor(parent: ConditionRule) { - this._parent = parent; - this.formula = new ConditionValue('then', this); - } - - public get variable(): string { - return this._parent?.variable; - } - - public getJson(): IConditionElseData { - return { - type: this.type, - formula: this.formula.getJson(), - } - } - - public static fromData(parent: ConditionRule, data: IConditionElseData): ConditionElse { - const item = new ConditionElse(parent); - item.formula = ConditionValue.fromData('then', item, data.formula); - return item; - } - - public static fromConditions( - parent: ConditionRule, - data?: (IConditionIfData | IConditionElseData)[] - ): ConditionElse { - if (Array.isArray(data)) { - const item = data.find((e) => e.type === 'else'); - if (item) { - return ConditionElse.fromData(parent, item as IConditionElseData); - } - } - return new ConditionElse(parent); - } -} - -export class ConditionRule { - public readonly type = 'condition'; - public conditions: (IConditionIfData | IConditionElseData)[]; - public if: ConditionIf[]; - public else: ConditionElse; - - private _parent: FieldRule; - - constructor(parent: FieldRule) { - this._parent = parent; - this.conditions = []; - this.if = [new ConditionIf(this)]; - this.else = new ConditionElse(this); - } - - public get variable(): string { - return this._parent?.id; - } - - public setParent(parent: FieldRule) { - this._parent = parent; - } - - public getJson(): IConditionRuleData { - return { - type: this.type, - conditions: [ - ...(this.if.map((e) => e.getJson())), - this.else.getJson() - ] - } - } - - public static fromData( - parent: FieldRule, - data: IConditionRuleData - ): ConditionRule { - const item = new ConditionRule(parent); - item.conditions = data.conditions || []; - item.if = ConditionIf.fromConditions(item, data.conditions); - item.else = ConditionElse.fromConditions(item, data.conditions); - return item; - } - - public addCondition() { - this.if.push(new ConditionIf(this)); - } - - public deleteCondition(item: ConditionIf) { - if (this.if.length > 1) { - this.if = this.if.filter((e) => e !== item) - } - } -} - -export class FormulaRule { - public readonly type = 'formula'; - public formula: string; - - private _parent: FieldRule; - - constructor(parent: FieldRule) { - this._parent = parent; - this.formula = ''; - } - - public get variable(): string { - return this._parent?.id; - } - - public setParent(parent: FieldRule) { - this._parent = parent; - } - - public getJson(): IFormulaRuleData { - return { - type: this.type, - formula: this.formula - } - } - - public static fromData(parent: FieldRule, data: IFormulaRuleData): FormulaRule { - const item = new FormulaRule(parent); - item.formula = data.formula; - return item; - } -} - -export class RangeRule { - public readonly type = 'range'; - public min: string | number; - public max: string | number; - - private _parent: FieldRule; - - constructor(parent: FieldRule) { - this._parent = parent; - this.min = 0; - this.max = 0; - } - - public get variable(): string { - return this._parent?.id; - } - - public setParent(parent: FieldRule) { - this._parent = parent; - } - - public getJson(): IRangeRuleData { - return { - type: this.type, - min: this.min, - max: this.max - } - } - - public static fromData(parent: FieldRule, data: IRangeRuleData): RangeRule { - const item = new RangeRule(parent); - item.min = data.min; - item.max = data.max; - return item; - } -} +import { FormulaRule, ConditionRule, RangeRule } from "./conditions"; +import { SchemaNode, FieldData } from "./schema-node"; export class FieldRule { public id: string; @@ -376,7 +47,7 @@ export class FieldRule { fieldProperty: this.fieldProperty, fieldPropertyName: this.fieldPropertyName, rule: this.rule?.getJson() - } + }; } public static fromData(data: ISchemaRuleData): FieldRule { @@ -452,6 +123,7 @@ export class FieldRule { } } + export class FieldRules { private readonly symbol = 'A'; private startIndex: number = 1; @@ -591,4 +263,4 @@ export class FieldRules { variable.updateType(map); } } -} +} \ No newline at end of file diff --git a/frontend/src/app/modules/common/models/schema-formulas.ts b/frontend/src/app/modules/common/models/schema-formulas.ts index 28109b69bd..745f514f1c 100644 --- a/frontend/src/app/modules/common/models/schema-formulas.ts +++ b/frontend/src/app/modules/common/models/schema-formulas.ts @@ -1,4 +1,5 @@ -import { IFormulaData } from "@guardian/interfaces"; +import { IConditionRuleData, IFormulaData, IFormulaRuleData, IRangeRuleData } from "@guardian/interfaces"; +import { ConditionRule, FormulaRule, RangeRule } from "./conditions"; export class SchemaFormula implements IFormulaData { public id: string; @@ -7,16 +8,36 @@ export class SchemaFormula implements IFormulaData { public formula: string; public index: number; + public rule?: FormulaRule | ConditionRule | RangeRule; + constructor() { this.type = 'string'; } + public get ruleType(): string { + if (this.rule) { + return this.rule.type; + } else { + return ''; + } + } + public getJson(): IFormulaData { - return { - id: this.id, - type: this.type, - description: this.description, - formula: this.formula + if (this.rule) { + return { + id: this.id, + type: this.type, + description: this.description, + formula: this.formula, + rule: this.rule.getJson() + } + } else { + return { + id: this.id, + type: this.type, + description: this.description, + formula: this.formula + } } } @@ -26,8 +47,34 @@ export class SchemaFormula implements IFormulaData { formula.type = data.type || 'string'; formula.description = data.description; formula.formula = data.formula; + formula.rule = this.parsRule(formula, data.rule); return formula; } + + private static parsRule( + parent: SchemaFormula, + rule?: IFormulaRuleData | IConditionRuleData | IRangeRuleData + ) { + if (rule) { + if (rule.type === 'condition') { + return ConditionRule.fromData(parent, rule); + } else if (rule.type === 'formula') { + return FormulaRule.fromData(parent, rule); + } else if (rule.type === 'range') { + return RangeRule.fromData(parent, rule); + } + } + return undefined; + } + + public addRule(rule?: FormulaRule | ConditionRule | RangeRule) { + this.rule = rule; + this.rule?.setParent(this); + } + + public clone(): SchemaFormula { + return SchemaFormula.fromData(this.getJson()); + } } export class SchemaFormulas { @@ -60,6 +107,14 @@ export class SchemaFormulas { return name; } + public getNames(): string[] { + const names: Set = new Set(); + for (const score of this.formulas) { + names.add(score.id); + } + return Array.from(names); + } + public add() { const formula = new SchemaFormula(); formula.id = this.getName(); @@ -75,6 +130,7 @@ export class SchemaFormulas { } public fromData(data: IFormulaData[] | undefined) { + this.names.clear(); this.formulas = []; if (data) { for (let index = 0; index < data.length; index++) { diff --git a/frontend/src/app/modules/common/models/schema-node.ts b/frontend/src/app/modules/common/models/schema-node.ts index b4f04167a6..074b5d8b0e 100644 --- a/frontend/src/app/modules/common/models/schema-node.ts +++ b/frontend/src/app/modules/common/models/schema-node.ts @@ -1,5 +1,5 @@ import { Schema } from "@guardian/interfaces"; -import { TreeListView, TreeListData, TreeListItem } from "../tree-graph/tree-list"; +import { TreeListView, TreeListData } from "../tree-graph/tree-list"; import { TreeNode } from "../tree-graph/tree-node"; export interface SchemaData { @@ -76,6 +76,7 @@ export class DocumentNode extends TreeNode { const clone = new DocumentNode(this.id, this.type, this.data); clone.type = this.type; clone.data = this.data; + clone.entity = this.entity; clone.childIds = new Set(this.childIds); return clone; } diff --git a/frontend/src/app/modules/common/models/schema-scores.ts b/frontend/src/app/modules/common/models/schema-scores.ts index b9c1bcc03c..9b179a2d91 100644 --- a/frontend/src/app/modules/common/models/schema-scores.ts +++ b/frontend/src/app/modules/common/models/schema-scores.ts @@ -93,6 +93,7 @@ export class SchemaScores { } public fromData(data: IScoreData[] | undefined) { + this.names.clear(); this.scores = []; if (data) { for (let index = 0; index < data.length; index++) { diff --git a/frontend/src/app/modules/common/models/schema-variables.ts b/frontend/src/app/modules/common/models/schema-variables.ts index 9ce864e825..70b364ba5d 100644 --- a/frontend/src/app/modules/common/models/schema-variables.ts +++ b/frontend/src/app/modules/common/models/schema-variables.ts @@ -121,6 +121,17 @@ export class SchemaVariables { return Array.from(names); } + public getOptions(): any[] { + const options = []; + for (const variable of this.variables) { + options.push({ + label: variable.fieldDescription, + value: variable.id, + }) + } + return options; + } + public fromData(data: IVariableData[] | undefined) { const map = new Map(); if (data) { @@ -133,6 +144,7 @@ export class SchemaVariables { } } + this.names.clear(); this.variables = []; for (const item of map.values()) { this.variables.push(item); diff --git a/frontend/src/app/modules/common/tree-graph/tree-node.ts b/frontend/src/app/modules/common/tree-graph/tree-node.ts index 9805e16ce2..c9ebe85584 100644 --- a/frontend/src/app/modules/common/tree-graph/tree-node.ts +++ b/frontend/src/app/modules/common/tree-graph/tree-node.ts @@ -21,6 +21,7 @@ export class TreeNode { public lines: Line[]; public selected: SelectType; public parent: TreeNode | null; + public entity: string; constructor( id: string | null | undefined, @@ -44,6 +45,7 @@ export class TreeNode { this.lines = []; this.selected = SelectType.NONE; this.parent = null; + this.entity = 'default'; } public addId(id: string): void { @@ -59,6 +61,7 @@ export class TreeNode { const clone = new TreeNode(this.id, this.type, this.data); clone.type = this.type; clone.data = this.data; + clone.entity = this.entity; clone.childIds = new Set(this.childIds); return clone; } @@ -105,6 +108,6 @@ export class TreeNode { } public update(): void { - + } } diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/report-block/report-block.component.scss b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/report-block/report-block.component.scss index c536eed595..3115f358c7 100644 --- a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/report-block/report-block.component.scss +++ b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/report-block/report-block.component.scss @@ -122,8 +122,7 @@ a[disabled="true"] { height: 25px; } -.display-chain { -} +.display-chain {} .container { display: flex; diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/dialog/request-document-block-dialog.component.ts b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/dialog/request-document-block-dialog.component.ts index 857479d812..698f291439 100644 --- a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/dialog/request-document-block-dialog.component.ts +++ b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/dialog/request-document-block-dialog.component.ts @@ -4,11 +4,11 @@ import { RequestDocumentBlockComponent } from '../request-document-block.compone import { PolicyEngineService } from 'src/app/services/policy-engine.service'; import { RequestDocumentBlockAddonComponent } from '../../request-document-block-addon/request-document-block-addon.component'; import { SchemaRulesService } from 'src/app/services/schema-rules.service'; -import { SchemaRuleValidators } from 'src/app/modules/common/models/field-rule-validator'; import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { audit, takeUntil } from 'rxjs/operators'; import { interval, Subject } from 'rxjs'; import { prepareVcData } from 'src/app/modules/common/models/prepare-vc-data'; +import { DocumentValidators } from '@guardian/interfaces'; @Component({ selector: 'request-document-block-dialog', @@ -32,7 +32,7 @@ export class RequestDocumentBlockDialog { public get docRef() { return this.parent?.getRef(); } public buttons: any = []; - public rules: SchemaRuleValidators; + public rules: DocumentValidators; public dataForm: UntypedFormGroup; public destroy$: Subject = new Subject(); public rulesResults: any; @@ -80,7 +80,7 @@ export class RequestDocumentBlockDialog { parentId: this.docRef?.id }) .subscribe((rules: any[]) => { - this.rules = new SchemaRuleValidators(rules); + this.rules = new DocumentValidators(rules); setTimeout(() => { this.loading = false; }, 500); diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.ts b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.ts index e13e835446..6c2fbd2463 100644 --- a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.ts +++ b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectorRef, Component, Input, OnInit, TemplateRef, ViewChild, } from '@angular/core'; import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; -import { DocumentGenerator, ISchema, Schema } from '@guardian/interfaces'; +import { DocumentGenerator, DocumentValidators, ISchema, Schema } from '@guardian/interfaces'; import { PolicyEngineService } from 'src/app/services/policy-engine.service'; import { ProfileService } from 'src/app/services/profile.service'; import { WebSocketService } from 'src/app/services/web-socket.service'; @@ -10,7 +10,6 @@ import { AbstractUIBlockComponent } from '../models/abstract-ui-block.component' import { PolicyHelper } from 'src/app/services/policy-helper.service'; import { RequestDocumentBlockDialog } from './dialog/request-document-block-dialog.component'; import { SchemaRulesService } from 'src/app/services/schema-rules.service'; -import { SchemaRuleValidators } from 'src/app/modules/common/models/field-rule-validator'; import { audit, takeUntil } from 'rxjs/operators'; import { interval, Subject } from 'rxjs'; import { prepareVcData } from 'src/app/modules/common/models/prepare-vc-data'; @@ -73,7 +72,7 @@ export class RequestDocumentBlockComponent public dialogRef: any; public buttonClass: any; public restoreData: any; - public rules: SchemaRuleValidators; + public rules: DocumentValidators; public rulesResults: any; public destroy$: Subject = new Subject(); @@ -194,7 +193,7 @@ export class RequestDocumentBlockComponent parentId: this.ref?.id }) .subscribe((rules) => { - this.rules = new SchemaRuleValidators(rules); + this.rules = new DocumentValidators(rules); setTimeout(() => { this.loading = false; }, 500); diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts b/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts deleted file mode 100644 index 095051b007..0000000000 --- a/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { MaterialModule } from 'src/app/modules/common/material.module'; -import { FormsModule } from '@angular/forms'; -import { InputTextModule } from 'primeng/inputtext'; -import { AppRoutingModule } from 'src/app/app-routing.module'; -import { SchemaEngineModule } from '../schema-engine/schema-engine.module'; -import { CommonComponentsModule } from '../common/common-components.module'; -import { AngularSvgIconModule } from 'angular-svg-icon'; -import { DialogService, DynamicDialogModule } from 'primeng/dynamicdialog'; -import { TableModule } from 'primeng/table'; -import { TooltipModule } from 'primeng/tooltip'; -import { DropdownModule } from 'primeng/dropdown'; -import { NewPolicyStatisticsDialog } from './dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component'; -import { TabViewModule } from 'primeng/tabview'; -import { CheckboxModule } from 'primeng/checkbox'; -import { RadioButtonModule } from 'primeng/radiobutton'; -import { CodemirrorModule } from '@ctrl/ngx-codemirror'; -import { MultiSelectModule } from 'primeng/multiselect'; -import { ScoreDialog } from './dialogs/score-dialog/score-dialog.component'; -import { OverlayPanelModule } from 'primeng/overlaypanel'; -import { StatisticAssessmentConfigurationComponent } from './statistic-assessment-configuration/statistic-assessment-configuration.component'; -import { StatisticAssessmentViewComponent } from './statistic-assessment-view/statistic-assessment-view.component'; -import { StatisticAssessmentsComponent } from './statistic-assessments/statistic-assessments.component'; -import { StatisticDefinitionsComponent } from './statistic-definitions/statistic-definitions.component'; -import { StatisticDefinitionConfigurationComponent } from './statistic-definition-configuration/statistic-definition-configuration.component'; -import { StatisticPreviewDialog } from './dialogs/statistic-preview-dialog/statistic-preview-dialog.component'; - -@NgModule({ - declarations: [ - NewPolicyStatisticsDialog, - ScoreDialog, - StatisticPreviewDialog, - StatisticAssessmentConfigurationComponent, - StatisticAssessmentViewComponent, - StatisticAssessmentsComponent, - StatisticDefinitionConfigurationComponent, - StatisticDefinitionsComponent - ], - imports: [ - CommonModule, - FormsModule, - MaterialModule, - CommonComponentsModule, - SchemaEngineModule, - AppRoutingModule, - DynamicDialogModule, - TableModule, - TooltipModule, - InputTextModule, - DropdownModule, - TabViewModule, - CheckboxModule, - RadioButtonModule, - CodemirrorModule, - MultiSelectModule, - OverlayPanelModule, - AngularSvgIconModule.forRoot(), - ], - exports: [], - providers: [ - DialogService - ], -}) -export class PolicyStatisticsModule { } diff --git a/frontend/src/app/modules/schema-engine/document-view/document-view.component.ts b/frontend/src/app/modules/schema-engine/document-view/document-view.component.ts index 456548a6de..a04cc53aeb 100644 --- a/frontend/src/app/modules/schema-engine/document-view/document-view.component.ts +++ b/frontend/src/app/modules/schema-engine/document-view/document-view.component.ts @@ -1,10 +1,9 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit, } from '@angular/core'; -import { Schema } from '@guardian/interfaces'; +import { DocumentValidators, Schema, SchemaRuleValidateResult } from '@guardian/interfaces'; import { forkJoin, Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { SchemaRulesService } from 'src/app/services/schema-rules.service'; import { SchemaService } from 'src/app/services/schema.service'; -import { SchemaRuleValidateResult, SchemaRuleValidators } from '../../common/models/field-rule-validator'; /** * View document @@ -36,7 +35,7 @@ export class DocumentViewComponent implements OnInit { public pageIndex: number = 0; public pageSize: number = 5; public schemaMap: { [x: string]: Schema | null } = {}; - public rules: SchemaRuleValidators; + public rules: DocumentValidators; public rulesResults: SchemaRuleValidateResult; private destroy$: Subject = new Subject(); @@ -130,7 +129,7 @@ export class DocumentViewComponent implements OnInit { this.loading = true; forkJoin(requests).subscribe((results: any[]) => { const rules = results.pop(); - this.rules = new SchemaRuleValidators(rules); + this.rules = new DocumentValidators(rules); for (const result of results) { if (result) { try { diff --git a/frontend/src/app/modules/schema-engine/schema-form-view/schema-form-view.component.ts b/frontend/src/app/modules/schema-engine/schema-form-view/schema-form-view.component.ts index f36d37a47b..a49b4a3213 100644 --- a/frontend/src/app/modules/schema-engine/schema-form-view/schema-form-view.component.ts +++ b/frontend/src/app/modules/schema-engine/schema-form-view/schema-form-view.component.ts @@ -1,7 +1,6 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit, SimpleChanges } from '@angular/core'; -import { Schema, SchemaField, UnitSystem } from '@guardian/interfaces'; +import { Schema, SchemaField, SchemaRuleValidateResult, UnitSystem } from '@guardian/interfaces'; import { IPFSService } from 'src/app/services/ipfs.service'; -import { SchemaRuleValidateResult } from '../../common/models/field-rule-validator'; interface IFieldControl extends SchemaField { fullPath: string; diff --git a/frontend/src/app/modules/schema-engine/schema-form/schema-form.component.ts b/frontend/src/app/modules/schema-engine/schema-form/schema-form.component.ts index 984b8e7e0d..8ec8613a29 100644 --- a/frontend/src/app/modules/schema-engine/schema-form/schema-form.component.ts +++ b/frontend/src/app/modules/schema-engine/schema-form/schema-form.component.ts @@ -1,23 +1,14 @@ -import {ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, SimpleChanges} from '@angular/core'; -import { - AbstractControl, - UntypedFormArray, - UntypedFormControl, - UntypedFormGroup, - ValidationErrors, - ValidatorFn, - Validators -} from '@angular/forms'; -import {GenerateUUIDv4, Schema, SchemaField, UnitSystem} from '@guardian/interfaces'; -import {fullFormats} from 'ajv-formats/dist/formats'; +import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; +import { AbstractControl, UntypedFormArray, UntypedFormControl, UntypedFormGroup, ValidationErrors, ValidatorFn, Validators } from '@angular/forms'; +import { GenerateUUIDv4, Schema, SchemaField, SchemaRuleValidateResult, UnitSystem } from '@guardian/interfaces'; +import { fullFormats } from 'ajv-formats/dist/formats'; import moment from 'moment'; -import {Subject} from 'rxjs'; -import {takeUntil} from 'rxjs/operators'; -import {IPFSService} from 'src/app/services/ipfs.service'; -import {uriValidator} from 'src/app/validators/uri.validator'; -import {GUARDIAN_DATETIME_FORMAT} from '../../../utils/datetime-format'; -import {API_IPFS_GATEWAY_URL, IPFS_SCHEMA} from '../../../services/api'; -import {SchemaRuleValidateResult} from '../../common/models/field-rule-validator'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { IPFSService } from 'src/app/services/ipfs.service'; +import { uriValidator } from 'src/app/validators/uri.validator'; +import { GUARDIAN_DATETIME_FORMAT } from '../../../utils/datetime-format'; +import { API_IPFS_GATEWAY_URL, IPFS_SCHEMA } from '../../../services/api'; enum PlaceholderByFieldType { Email = "example@email.com", @@ -221,8 +212,7 @@ export class SchemaFormComponent implements OnInit { constructor( private ipfs: IPFSService, protected changeDetectorRef: ChangeDetectorRef - ) { - } + ) { } ngOnInit(): void { @@ -301,7 +291,7 @@ export class SchemaFormComponent implements OnInit { this.subscribeCondition(conditionForm); this.conditionFields.push(...cond.thenFields); this.conditionFields.push(...cond.elseFields); - return Object.assign({conditionForm}, cond); + return Object.assign({ conditionForm }, cond); }); } @@ -647,7 +637,7 @@ export class SchemaFormComponent implements OnInit { condition.conditionForm = new UntypedFormGroup({}); this.subscribeCondition(condition.conditionForm); fields.forEach(item => { - setTimeout(() => this.options?.removeControl(item.name, {emitEvent: false})); + setTimeout(() => this.options?.removeControl(item.name, { emitEvent: false })); }); } diff --git a/frontend/src/app/modules/schema-rules/schema-rules.module.ts b/frontend/src/app/modules/schema-rules/schema-rules.module.ts deleted file mode 100644 index f809c208af..0000000000 --- a/frontend/src/app/modules/schema-rules/schema-rules.module.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { MaterialModule } from 'src/app/modules/common/material.module'; -import { FormsModule } from '@angular/forms'; -import { InputTextModule } from 'primeng/inputtext'; -import { AppRoutingModule } from 'src/app/app-routing.module'; -import { SchemaEngineModule } from '../schema-engine/schema-engine.module'; -import { CommonComponentsModule } from '../common/common-components.module'; -import { AngularSvgIconModule } from 'angular-svg-icon'; -import { DialogService, DynamicDialogModule } from 'primeng/dynamicdialog'; -import { TableModule } from 'primeng/table'; -import { TooltipModule } from 'primeng/tooltip'; -import { DropdownModule } from 'primeng/dropdown'; -import { TabViewModule } from 'primeng/tabview'; -import { CheckboxModule } from 'primeng/checkbox'; -import { RadioButtonModule } from 'primeng/radiobutton'; -import { CodemirrorModule } from '@ctrl/ngx-codemirror'; -import { MultiSelectModule } from 'primeng/multiselect'; -import { OverlayPanelModule } from 'primeng/overlaypanel'; -import { SchemaRulesComponent } from './schema-rules/schema-rules.component'; -import { NewSchemaRuleDialog } from './dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component'; -import { SchemaRuleConfigurationComponent } from './schema-rule-configuration/schema-rule-configuration.component'; -import { SchemaRulesPreviewDialog } from './dialogs/schema-rules-preview-dialog/schema-rules-preview-dialog.component'; -import { SchemaRuleConfigDialog } from './dialogs/schema-rule-config-dialog/schema-rule-config-dialog.component'; - -@NgModule({ - declarations: [ - SchemaRulesComponent, - SchemaRuleConfigurationComponent, - NewSchemaRuleDialog, - SchemaRulesPreviewDialog, - SchemaRuleConfigDialog - ], - imports: [ - CommonModule, - FormsModule, - MaterialModule, - CommonComponentsModule, - SchemaEngineModule, - AppRoutingModule, - DynamicDialogModule, - TableModule, - TooltipModule, - InputTextModule, - DropdownModule, - TabViewModule, - CheckboxModule, - RadioButtonModule, - CodemirrorModule, - MultiSelectModule, - OverlayPanelModule, - AngularSvgIconModule.forRoot(), - ], - exports: [], - providers: [ - DialogService - ], -}) -export class SchemaRulesModule { } diff --git a/frontend/src/app/modules/schema-rules/dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component.html b/frontend/src/app/modules/statistics/policy-labels/dialogs/new-policy-label-dialog/new-policy-label-dialog.component.html similarity index 100% rename from frontend/src/app/modules/schema-rules/dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component.html rename to frontend/src/app/modules/statistics/policy-labels/dialogs/new-policy-label-dialog/new-policy-label-dialog.component.html diff --git a/frontend/src/app/modules/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.scss b/frontend/src/app/modules/statistics/policy-labels/dialogs/new-policy-label-dialog/new-policy-label-dialog.component.scss similarity index 100% rename from frontend/src/app/modules/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.scss rename to frontend/src/app/modules/statistics/policy-labels/dialogs/new-policy-label-dialog/new-policy-label-dialog.component.scss diff --git a/frontend/src/app/modules/statistics/policy-labels/dialogs/new-policy-label-dialog/new-policy-label-dialog.component.ts b/frontend/src/app/modules/statistics/policy-labels/dialogs/new-policy-label-dialog/new-policy-label-dialog.component.ts new file mode 100644 index 0000000000..cccb457862 --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/dialogs/new-policy-label-dialog/new-policy-label-dialog.component.ts @@ -0,0 +1,78 @@ +import { Component } from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { DialogService, DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; + +@Component({ + selector: 'new-policy-label-dialog', + templateUrl: './new-policy-label-dialog.component.html', + styleUrls: ['./new-policy-label-dialog.component.scss'], +}) +export class NewPolicyLabelDialog { + public loading = true; + public policy: any; + public policies: any[]; + public dataForm = new FormGroup({ + name: new FormControl('', Validators.required), + description: new FormControl(''), + policy: new FormControl(null, Validators.required) + }); + public title: string; + public action: string; + public readonly: boolean; + + constructor( + public ref: DynamicDialogRef, + public config: DynamicDialogConfig, + private dialogService: DialogService, + ) { + this.title = this.config.data?.title || ''; + this.action = this.config.data?.action || ''; + this.policies = this.config.data?.policies || []; + this.policies = this.policies.filter((p) => p.instanceTopicId); + const label = this.config.data?.label; + const instanceTopicId = this.config.data?.policy?.instanceTopicId; + this.policy = this.policies.find((p) => p.instanceTopicId === instanceTopicId) || null; + if (label) { + this.readonly = true; + this.dataForm.setValue({ + name: label.name || 'N\\A', + description: label.description || 'N\\A', + policy: this.policy + }) + } else { + this.readonly = false; + this.dataForm.setValue({ + name: '', + description: '', + policy: this.policy + }) + } + } + + public get currentPolicy(): any { + return this.dataForm.value.policy; + } + + ngOnInit() { + this.loading = false; + } + + ngOnDestroy(): void { + } + + public onClose(): void { + this.ref.close(null); + } + + public onSubmit(): void { + if (this.dataForm.valid) { + const { name, description, policy } = this.dataForm.value; + this.ref.close({ + name, + description, + policyId: policy?.id, + instanceTopicId: policy?.instanceTopicId, + }); + } + } +} diff --git a/frontend/src/app/modules/statistics/policy-labels/dialogs/policy-label-preview-dialog/policy-label-preview-dialog.component.html b/frontend/src/app/modules/statistics/policy-labels/dialogs/policy-label-preview-dialog/policy-label-preview-dialog.component.html new file mode 100644 index 0000000000..f66a2e52bb --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/dialogs/policy-label-preview-dialog/policy-label-preview-dialog.component.html @@ -0,0 +1,176 @@ +
+
+
Preview
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+ + + + +
+
{{item.name}}
+
+
+
+
+
{{current.title}}
+ +
+ +
{{item.index}}
+
{{item.name}}
+
+
+
+ + +
+
+ {{variable.fieldDescription}} +
+
+ +
+
+
+ + +
+
+
+
+ {{variable.fieldDescription}} +
+ +
+
+ {{getVariableValue(v)}} +
+
+
+ +
+ {{getVariableValue(variable.value)}} +
+
+
+
+
+ {{score.description}} +
+
+
+
+ +
+ +
+
+
+
+ +
+
+ {{formula.description}} +
+
+ {{formula.value}} +
+
+
+ +
+
+
+ + +
+
Label created successfully.
+
+
+
+ + +
+
Sorry, but your document does not meet the requirements.
+
+
+
+
+
+ +
+
+
+ \ No newline at end of file diff --git a/frontend/src/app/modules/statistics/policy-labels/dialogs/policy-label-preview-dialog/policy-label-preview-dialog.component.scss b/frontend/src/app/modules/statistics/policy-labels/dialogs/policy-label-preview-dialog/policy-label-preview-dialog.component.scss new file mode 100644 index 0000000000..df209b072b --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/dialogs/policy-label-preview-dialog/policy-label-preview-dialog.component.scss @@ -0,0 +1,395 @@ +.context { + position: relative; + overflow-y: auto; + padding: 14px 0 20px 0; + display: flex; + flex-direction: column; + height: 100%; + font-family: Inter, serif; + font-style: normal; +} + +.dialog-header { + .header-item { + padding: 6px 12px; + margin: 0px 16px; + border-radius: 6px; + font-size: var(--guardian-primary-font-size); + color: var(--guardian-dry-run-color); + background: var(--guardian-dry-run-background); + user-select: none; + cursor: default; + } +} + +form { + width: 100%; +} + +.guardian-input-container { + margin-bottom: 24px; +} + +.dialog-grid-body { + max-height: 256px; + overflow: auto; +} + +.dialog-body { + height: auto; +} + +.action-buttons { + display: flex; + justify-content: flex-end; + + &>div { + margin-left: 15px; + margin-right: 0px; + } +} + +.close-icon-color { + fill: #848FA9; + color: #848FA9; +} + +.option-actions { + padding-top: 24px; + + button { + height: 28px; + width: 120px; + } +} + +.grid-label { + font-family: Inter; + font-size: 12px; + font-weight: 500; + line-height: 14px; + text-align: left; + margin-bottom: 16px; +} + +.preview-container { + display: flex; + flex-direction: row; + overflow: auto; + max-height: calc(100vh - 500px); + min-height: calc(100vh - 500px); + padding-right: 16px; + position: relative; + + .preview-menu { + min-width: 250px; + width: 250px; + max-width: 250px; + border: 1px solid #E1E7EF; + border-radius: 8px; + padding: 8px 0px; + + .preview-menu-item { + width: 250px; + height: 56px; + display: flex; + flex-direction: row; + padding-left: 22px; + align-items: center; + position: relative; + + &[highlighted="true"] { + background: #F0F3FC; + + .preview-menu-item-name { + color: #4169E2; + } + } + + &[last="false"] { + margin-bottom: 24px; + + &::after { + content: ""; + display: block; + position: absolute; + left: 53px; + bottom: -22px; + width: 3px; + height: 20px; + border-left: 2px solid #C4D0E1; + pointer-events: none; + } + } + + .preview-menu-item-status { + width: 8px; + height: 8px; + overflow: hidden; + border-radius: 50%; + background: #AAB7C4; + margin-right: 12px; + + &[status="true"] { + background: #19BE47; + } + + &[status="false"] { + background: #FF432A; + } + } + + .preview-menu-item-icon { + width: 24px; + height: 24px; + overflow: hidden; + margin-right: 8px; + } + + .preview-menu-item-name { + width: 160px; + overflow: hidden; + font-family: Inter; + font-size: 16px; + font-weight: 500; + line-height: 16px; + text-align: left; + color: #848FA9; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } + } + + .preview-result { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + + .preview-result-item { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + } + + .preview-result-icon { + width: 48px; + height: 48px; + padding: 8px; + overflow: hidden; + } + + .preview-result-text { + font-family: Inter; + font-size: 16px; + font-weight: 600; + text-align: center; + color: #848FA9; + } + } + + .preview-node-container { + display: flex; + flex-direction: column; + position: absolute; + left: 250px; + right: 0; + top: 0; + bottom: 0; + + .preview-node-body { + padding: 16px 24px; + position: relative; + height: 100%; + + .preview-node-header { + font-size: 24px; + font-weight: 600; + line-height: 32px; + text-align: left; + color: var(--guardian-font-color, #23252E); + margin-bottom: 24px; + } + } + + .sub-indexes { + width: 100%; + height: 48px; + min-height: 48px; + max-height: 48px; + display: flex; + flex-direction: row; + justify-content: center; + background: #fff; + align-items: center; + margin-bottom: 16px; + + .sub-index { + width: 24px; + height: 24px; + min-width: 24px; + min-height: 24px; + background: var(--guardian-grey-color-3, #AAB7C4); + border-radius: 50%; + font-family: Inter; + font-size: 14px; + font-weight: 500; + line-height: 14px; + display: flex; + justify-content: center; + align-items: center; + color: #fff; + + &[action="true"] { + background: var(--guardian-primary-color, #4169E2); + } + } + + .sub-name { + font-family: Inter; + font-size: 14px; + font-weight: 500; + line-height: 14px; + text-align: left; + color: var(--guardian-grey-color-3, #AAB7C4); + padding-left: 8px; + + &[action="true"] { + color: var(--guardian-primary-color, #4169E2); + } + } + + .sub-index-separator { + width: 100%; + height: 12px; + position: relative; + + &::after { + content: ""; + display: block; + position: absolute; + left: 15px; + right: 16px; + top: 5px; + height: 3px; + border-top: 1px solid #C4D0E1; + pointer-events: none; + } + } + } + } + + +} + +.fields-container { + .fields-header { + color: #181818; + font-size: 16px; + font-weight: 600; + line-height: 20px; + text-align: left; + padding-bottom: 16px; + } + + .field-container { + margin-bottom: 16px; + + .field-name { + font-family: Inter; + font-size: 12px; + font-weight: 500; + line-height: 14px; + text-align: left; + color: #181818; + padding: 8px 0px; + margin-bottom: 6px; + } + + .field-value { + padding: 12px 16px 12px 16px; + border-radius: 8px; + border: 1px solid #E1E7EF; + background: #F9FAFC; + font-family: Inter; + font-size: 14px; + font-weight: 400; + line-height: 16px; + text-align: left; + color: #23252E; + width: 100%; + min-height: 42px; + } + + .field-input { + position: relative; + + .field-status { + position: absolute; + top: 8px; + right: 0; + } + } + + &.field-status-Error { + input { + border-color: var(--guardian-failure-color, #FF432A) !important; + } + } + + &.field-status-Failure { + input { + border-color: var(--guardian-warning-color, #DA9B22) !important; + } + } + + &.field-status-Success { + input { + border-color: var(--guardian-success-color, #19BE47) !important; + } + } + } + + .option-container { + display: flex; + flex-direction: row; + + border-radius: 6px; + + &:not([disabled]) { + cursor: pointer; + } + + &:not([disabled]):hover { + background: #f0f3fc; + } + + .option-checkbox { + cursor: pointer; + min-height: 40px; + width: 42px; + min-width: 42px; + display: flex; + justify-content: flex-start; + align-items: center; + padding-left: 4px; + } + + .option-name { + cursor: pointer; + min-height: 40px; + width: 100%; + display: flex; + justify-content: flex-start; + align-items: center; + } + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/statistics/policy-labels/dialogs/policy-label-preview-dialog/policy-label-preview-dialog.component.ts b/frontend/src/app/modules/statistics/policy-labels/dialogs/policy-label-preview-dialog/policy-label-preview-dialog.component.ts new file mode 100644 index 0000000000..30ad2df046 --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/dialogs/policy-label-preview-dialog/policy-label-preview-dialog.component.ts @@ -0,0 +1,119 @@ +import { Component } from '@angular/core'; +import { IValidateStatus, IValidatorNode, IValidatorStep, LabelValidators } from '@guardian/interfaces'; +import { DialogService, DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; + + +@Component({ + selector: 'policy-label-preview-dialog', + templateUrl: './policy-label-preview-dialog.component.html', + styleUrls: ['./policy-label-preview-dialog.component.scss'], +}) +export class PolicyLabelPreviewDialog { + public loading = true; + public item: any; + public validator: LabelValidators; + public tree: any; + public steps: any[]; + public current: IValidatorStep | null; + public menu: IValidatorNode[]; + public result: IValidateStatus | undefined; + + constructor( + public ref: DynamicDialogRef, + public config: DynamicDialogConfig, + private dialogService: DialogService, + ) { + this.item = this.config.data?.item || {}; + this.validator = new LabelValidators(this.item); + this.validator.setData([]); + + this.tree = this.validator.getTree(); + this.steps = this.validator.getSteps(); + this.current = this.validator.start(); + + this.tree.children.push({ + name: 'Result', + item: this.validator, + selectable: true, + children: [] + }) + + this.steps.push({ + name: 'Result', + title: 'Result', + item: this.validator, + type: 'result', + config: this.validator, + auto: false, + update: this.update.bind(this) + }) + + this.menu = [] + for (const child of this.tree.children) { + this.createMenu(child, this.menu); + } + } + + private createMenu(node: any, result: any[]) { + result.push(node); + for (const child of node.children) { + this.createMenu(child, result); + } + return result; + } + + private update() { + this.result = this.validator.getStatus(); + } + + ngOnInit() { + this.loading = false; + } + + ngOnDestroy(): void { + } + + public isSelected(menuItem: any): boolean { + return menuItem.item === this.current?.item; + } + + public getVariableValue(value: any): any { + if (value === undefined) { + return 'N/A'; + } else { + return value; + } + } + + public onPrev(): void { + this.current = this.validator.prev(); + this.updateStep(); + } + + public onNext(): void { + this.current = this.validator.next(); + this.updateStep(); + } + + public onClose(): void { + this.ref.close(null); + } + + public onSubmit() { + const result = this.validator.getStatus(); + this.ref.close(null); + } + + public updateStep() { + if (this.current?.type === 'scores') { + let valid = true; + if (Array.isArray(this.current.config)) { + for (const score of this.current.config) { + let validScore = score.value !== undefined; + valid = valid && validScore; + } + } + this.current.disabled = !valid; + } + } +} diff --git a/frontend/src/app/modules/statistics/policy-labels/dialogs/search-label-dialog/search-label-dialog.component.html b/frontend/src/app/modules/statistics/policy-labels/dialogs/search-label-dialog/search-label-dialog.component.html new file mode 100644 index 0000000000..8443ea1cfb --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/dialogs/search-label-dialog/search-label-dialog.component.html @@ -0,0 +1,175 @@ +
+
+
Search
+
+
+ +
+
+
+
+
+
+
+ +
+
+ + + + + + + + + + +
+
+
+
+ + + Type: + {{ filterSelectedProperty?.name }} + + +
+
+ + + + +
+
+
+ +
+
+
+
+
+
+ +
+
Type
+
Name
+
Description
+
Owner
+
+
+
+
+ +
+
+
+ + {{item._type}} +
+
+
{{item.name}}
+
{{item.description}}
+
{{item.creator}}
+
+
+
+
+
+ + +
+ +
Not available
+
{{error}}
+
+ +
There are no components
+
Please try to change or clear filters
+
+
+
+
+ \ No newline at end of file diff --git a/frontend/src/app/modules/statistics/policy-labels/dialogs/search-label-dialog/search-label-dialog.component.scss b/frontend/src/app/modules/statistics/policy-labels/dialogs/search-label-dialog/search-label-dialog.component.scss new file mode 100644 index 0000000000..7ed184259d --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/dialogs/search-label-dialog/search-label-dialog.component.scss @@ -0,0 +1,259 @@ +.context { + position: relative; + overflow-y: auto; + padding: 5px 3px 20px 3px; + display: flex; + flex-direction: column; + height: 100%; + + .filters { + display: flex; + flex-direction: column; + width: 100%; + + .filter-row { + height: 40px; + position: relative; + width: 100%; + display: flex; + flex-direction: row; + + .guardian-dropdown { + max-width: 260px; + min-width: 260px; + height: 40px; + margin-right: 16px; + } + } + + .filter-group { + margin-top: 16px; + width: 100%; + height: 132px; + padding: 16px; + border-radius: 8px; + border: 1px solid #C4D0E1; + background: #F9FAFC; + + .filter-row { + margin-bottom: 16px; + + .guardian-dropdown { + margin-right: 16px; + max-width: 100%; + min-width: 100%; + } + } + + .filter { + width: 50%; + display: flex; + flex-direction: row; + + &:first-of-type { + padding-right: 8px; + } + + &:last-of-type { + padding-left: 8px; + } + } + } + + .type-dropdown { + max-width: 260px; + width: 100%; + height: 40px; + margin-right: 16px; + + &::ng-deep { + .p-dropdown { + width: 100%; + height: 40px; + border: 1px solid #e1e7ef; + border-radius: 8px; + } + } + } + + .p-input-icon-right { + width: 100%; + } + + .search-policy-input { + width: 100%; + height: 40px; + border: 1px solid #e1e7ef; + border-radius: 8px; + padding-left: 16px; + position: relative; + + &:hover { + border-color: #2196F3; + } + + &:focus { + outline: 1px solid #2196F3; + } + } + + .guardian-button { + width: 150px; + min-width: 150px; + max-width: 150px; + margin-left: 12px; + padding: 6px 12px; + } + } + + .empty-grid { + margin-top: 16px; + display: flex; + flex-direction: column; + width: 100%; + overflow: auto; + height: 100%; + justify-content: center; + align-items: center; + color: #848FA9; + + .empty-grid-icon { + margin-bottom: 2px; + } + + .empty-grid-header { + font-weight: 500; + margin-bottom: 4px; + } + } + + .grid-container { + margin-top: 16px; + display: flex; + flex-direction: column; + width: 100%; + overflow: auto; + + .col-auto { + width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .col-250 { + width: 250px; + min-width: 250px; + max-width: 250px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .col-200 { + width: 200px; + min-width: 200px; + max-width: 200px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .col-120 { + width: 75px; + min-width: 75px; + max-width: 75px; + } + + .col-130 { + width: 130px; + min-width: 130px; + max-width: 130px; + } + + .col-80 { + width: 80px; + min-width: 80px; + max-width: 80px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .col-64 { + width: 64px; + min-width: 64px; + max-width: 64px; + overflow: hidden; + text-align: center; + } + + .grid-header { + display: flex; + flex-direction: row; + width: 100%; + height: 46px; + min-height: 46px; + max-height: 46px; + + &>div { + font-weight: 500; + padding: 12px 4px; + } + } + + .grid-body { + display: flex; + flex-direction: column; + width: 100%; + border: 1px solid #E1E7EF; + border-radius: 6px; + + .grid-row { + display: flex; + flex-direction: row; + width: 100%; + height: 64px; + min-height: 64px; + max-height: 64px; + border-bottom: 1px solid #E1E7EF; + cursor: pointer; + + &>div { + padding: 22px 6px; + } + + &:hover { + background: #f1f1f1; + } + } + } + } + + .checkbox-input { + width: 16px; + height: 16px; + cursor: pointer; + } + + .import-icon { + height: 24px; + display: flex; + align-items: center; + text-transform: capitalize; + + svg-icon { + margin-right: 8px; + } + } +} + +.dialog-body { + height: 45vh; + overflow-y: auto; +} + +.dropdown-type { + color: #848FA9; + padding: 0px 8px 0px 0px; +} \ No newline at end of file diff --git a/frontend/src/app/modules/statistics/policy-labels/dialogs/search-label-dialog/search-label-dialog.component.ts b/frontend/src/app/modules/statistics/policy-labels/dialogs/search-label-dialog/search-label-dialog.component.ts new file mode 100644 index 0000000000..c41c711af8 --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/dialogs/search-label-dialog/search-label-dialog.component.ts @@ -0,0 +1,192 @@ +import { Component } from '@angular/core'; +import { UntypedFormControl, UntypedFormGroup } from '@angular/forms'; +import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { PolicyLabelsService } from 'src/app/services/policy-labels.service'; + +/** + * Search policy dialog. + */ +@Component({ + selector: 'search-label-dialog', + templateUrl: './search-label-dialog.component.html', + styleUrls: ['./search-label-dialog.component.scss'] +}) +export class SearchLabelDialog { + public loading = false; + public filtersForm = new UntypedFormGroup({ + text: new UntypedFormControl(''), + type: new UntypedFormControl('local'), + components: new UntypedFormControl('all'), + owner: new UntypedFormControl(''), + }); + public types = [{ + name: 'Local Guardian search', + value: 'local' + }, { + name: 'Global search', + value: 'global' + }]; + public options = [{ + name: 'All', + value: 'all' + }, { + name: 'Label', + value: 'label' + }, { + name: 'Statistic', + value: 'statistic' + }]; + public showMoreFilters = false; + public list: any[] = []; + public selectedAll: boolean = false; + public count: number = 0; + public filtersCount: number = 0; + public error: string | null = null; + public ids: string[]; + + constructor( + public ref: DynamicDialogRef, + public config: DynamicDialogConfig, + private policyLabelsService: PolicyLabelsService + ) { + this.ids = this.config.data?.ids || []; + } + + ngOnInit() { + this.load(); + } + + public load() { + this.loading = true; + + this.filtersCount = 0; + const filters = this.filtersForm.value; + + const options: any = {}; + options.type = filters.type; + + if (filters.text) { + options.text = filters.text; + this.filtersCount++; + } + if (filters.owner) { + options.owner = filters.owner; + this.filtersCount++; + } + if (filters.components) { + options.components = filters.components; + if (filters.components !== 'all') { + this.filtersCount++; + } + } + + this.error = null; + this.policyLabelsService.searchComponents(options) + .subscribe((data) => { + this.loading = false; + if (!data) { + return; + } + const { labels, statistics } = data; + this.list = []; + if (Array.isArray(labels)) { + for (const item of labels) { + item._type = 'label'; + item._icon = 'circle-check'; + this.list.push(item); + } + } + if (Array.isArray(statistics)) { + for (const item of statistics) { + item._type = 'statistic'; + item._icon = 'stats'; + this.list.push(item); + } + } + for (const item of this.list ) { + if(item.messageId && this.ids.includes(item.messageId)) { + item._select = true; + } + } + + this.loading = false; + this.select(); + }, (error) => { + this.error = error?.error?.message; + this.list = []; + this.loading = false; + console.error(error); + }); + } + + public onClose(): void { + this.ref.close(null); + } + + public onCompare() { + const items = this.list.filter((e) => e._select); + this.ref.close(items); + } + + public changeType(): void { + this.loading = true; + setTimeout(() => { + this.selectedAll = false; + this.select(); + this.filtersForm.setValue({ + type: this.filtersForm.value.type, + text: '', + owner: '', + components: 'all', + }) + this.load(); + }, 0); + } + + public clearFilters(): void { + this.selectedAll = false; + this.filtersForm.setValue({ + type: this.filtersForm.value.type, + text: '', + owner: '', + components: 'all', + }) + this.select(); + this.load(); + } + + public showFilters(): void { + this.showMoreFilters = !this.showMoreFilters; + } + + public applyFilters(): void { + this.load(); + } + + public onSelectAll() { + this.selectedAll = !this.selectedAll; + if (this.list) { + for (const item of this.list) { + item._select = this.selectedAll; + } + } + this.select(); + } + + public onSelect(item: any) { + item._select = !item._select; + this.select(); + } + + public select() { + this.count = 0; + if (this.list) { + for (const item of this.list) { + if (item._select) { + this.count++; + } + } + } + this.selectedAll = this.count === this.list.length && this.list.length > 0; + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/statistics/policy-labels/policy-label-configuration/components/label-config.ts b/frontend/src/app/modules/statistics/policy-labels/policy-label-configuration/components/label-config.ts new file mode 100644 index 0000000000..084003ffde --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/policy-label-configuration/components/label-config.ts @@ -0,0 +1,200 @@ +import { FormGroup, FormControl, Validators } from "@angular/forms"; +import { IPolicyLabel, GenerateUUIDv4, IPolicyLabelConfig, IGroupItemConfig, NavItemType, IRulesItemConfig, ILabelItemConfig, IStatisticItemConfig } from "@guardian/interfaces"; +import { TreeDragDropService } from "primeng/api"; +import { DialogService } from "primeng/dynamicdialog"; +import { Subject } from "rxjs"; +import { CustomCustomDialogComponent } from "src/app/modules/common/custom-confirm-dialog/custom-confirm-dialog.component"; +import { NavMenu, NavItem, NavTree } from "./nav-item"; + +export class LabelConfig { + public show: boolean = false; + public readonly: boolean = false; + public stepper = [true, false, false]; + public policy: any; + + public readonly step = new Subject(); + + public groupTypes: any[] = [{ + label: 'At least one', + value: 'one' + }, { + label: 'Every', + value: 'every' + }]; + + constructor( + private dialogService: DialogService, + private dragDropService: TreeDragDropService + ) { + } + + public overviewForm = new FormGroup({ + name: new FormControl('', Validators.required), + description: new FormControl(''), + policy: new FormControl('', Validators.required), + }); + + public menu = new NavMenu(); + + public selectedNavItem: NavItem | null = null; + public draggedMenuItem: NavItem | null = null; + public navigationTree: NavTree = new NavTree(); + + public setData(item: IPolicyLabel) { + this.overviewForm.setValue({ + name: item.name || '', + description: item.description || '', + policy: this.policy?.name || '', + }); + + this.menu = NavMenu.from(item); + this.navigationTree = NavTree.from(item); + this.navigationTree.update(); + this.updateSelected(); + } + + public setPolicy(relationships: any) { + this.policy = relationships?.policy || {}; + } + + public isActionStep(index: number): boolean { + return this.stepper[index]; + } + + public async goToStep(index: number) { + for (let i = 0; i < this.stepper.length; i++) { + this.stepper[i] = (i == index); + } + return this.refreshView(); + } + + public async refreshView() { + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve(); + }, 400); + }); + } + + public onStep(index: number) { + this.step.next(index); + } + + public dragMenuStart(item: NavItem) { + this.draggedMenuItem = item.clone(); + this.draggedMenuItem.setId(GenerateUUIDv4()); + this.dragDropService.startDrag({ + tree: null, + node: this.draggedMenuItem, + subNodes: [this.draggedMenuItem], + index: 0, + scope: "navigationTree" + }); + } + + public dragMenuEnd() { + this.draggedMenuItem = null; + } + + public onDrop() { + if (this.draggedMenuItem) { + this.navigationTree.add(this.draggedMenuItem); + this.navigationTree.update(); + this.draggedMenuItem = null; + } + } + + public onDropValidator($event: any) { + if ($event.dropNode.freezed) { + return; + } + $event.accept(); + this.navigationTree.update(); + } + + public onClearNavItem() { + this.selectedNavItem = null; + } + + public onNavItemSelect(node: NavItem) { + this.selectedNavItem = node; + } + + public updateSelected() { + if (this.selectedNavItem) { + const key = this.selectedNavItem.key; + this.selectedNavItem = this.navigationTree.getItem(key); + } + } + + public ifNavSelected(node: NavItem) { + if (this.selectedNavItem) { + return this.selectedNavItem.key === node.key; + } + return false; + } + + public onDeleteNavItem(node: NavItem) { + const dialogRef = this.dialogService.open(CustomCustomDialogComponent, { + showHeader: false, + width: '640px', + styleClass: 'guardian-dialog', + data: { + header: 'Delete item', + text: 'Are you sure want to delete item?', + buttons: [{ + name: 'Close', + class: 'secondary' + }, { + name: 'Delete', + class: 'delete' + }] + }, + }); + dialogRef.onClose.subscribe((result: string) => { + if (result === 'Delete') { + this.selectedNavItem = null; + this.navigationTree.delete(node); + this.navigationTree.update(); + } + }); + } + + public toJson(): IPolicyLabelConfig { + const imports = this.menu.toJson(); + const children = this.navigationTree.toJson(); + const json: IPolicyLabelConfig = { + imports, + children + }; + return json; + } + + public getGroupConfig(selectedNavItem: NavItem | null): IGroupItemConfig | null { + if (selectedNavItem && selectedNavItem.blockType === NavItemType.Group) { + return selectedNavItem.config as IGroupItemConfig; + } + return null; + } + + public getRuleConfig(selectedNavItem: NavItem | null): IRulesItemConfig | null { + if (selectedNavItem && selectedNavItem.blockType === NavItemType.Rules) { + return selectedNavItem.config as IRulesItemConfig; + } + return null; + } + + public getLabelConfig(selectedNavItem: NavItem | null): ILabelItemConfig | null { + if (selectedNavItem && selectedNavItem.blockType === NavItemType.Label) { + return selectedNavItem.config as ILabelItemConfig; + } + return null; + } + + public getStatisticConfig(selectedNavItem: NavItem | null): IStatisticItemConfig | null { + if (selectedNavItem && selectedNavItem.blockType === NavItemType.Statistic) { + return selectedNavItem.config as IStatisticItemConfig; + } + return null; + } +} diff --git a/frontend/src/app/modules/statistics/policy-labels/policy-label-configuration/components/nav-item.ts b/frontend/src/app/modules/statistics/policy-labels/policy-label-configuration/components/nav-item.ts new file mode 100644 index 0000000000..8f77c6c25b --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/policy-label-configuration/components/nav-item.ts @@ -0,0 +1,501 @@ +import { GenerateUUIDv4, IGroupItemConfig, ILabelItemConfig, INavImportsConfig, INavItemConfig, IPolicyLabel, IRulesItemConfig, IStatistic, IStatisticItemConfig, NavItemType } from "@guardian/interfaces"; +import { TreeNode } from "primeng/api"; + +export const NavIcons: { [type: string]: string } = { + 'group': 'folder', + 'rules': 'file', + 'label': 'circle-check', + 'statistic': 'stats', + 'default': 'file' +} + +function getName(title: string | undefined): string { + return (title || '').toLowerCase().replace(/\s/ig, '_'); +} + +export class NavItem implements TreeNode { + public readonly config: INavItemConfig; + public readonly nodeType: string = 'default'; + public readonly nodeIcon: string = 'default'; + + public prefix: string = ''; + public get blockType(): string { + return this.config.type; + } + public get configurable(): boolean { + return ( + this.config.type === NavItemType.Rules || + this.config.type === NavItemType.Statistic + ); + } + public get messageId(): string { + return (this.config as any).messageId; + } + + public readonly readonly: boolean; + public freezed: boolean; + + //Tree Node + public get key(): string { + return this.config.id; + } + public get type(): string { + return this.nodeType; + } + public get label(): string { + return this.prefix + this.config.title; + } + public get droppable(): boolean { + return !this.freezed && this.config.type === NavItemType.Group; + } + public get draggable(): boolean { + return !this.freezed; + } + public get selectable(): boolean { + return this.freezed || this.config.type !== NavItemType.Group; + } + public children?: NavItem[] | undefined; + public parent?: NavItem | undefined; + public expanded?: boolean; + + constructor(type: NavItemType, config?: INavItemConfig) { + if (config) { + this.config = config; + } else { + this.config = { + id: GenerateUUIDv4(), + type: type, + name: type, + title: type, + tag: type + } + } + if (!this.config.id) { + this.config.id = GenerateUUIDv4(); + } + this.config.type = type; + this.nodeType = 'default'; + this.nodeIcon = NavIcons[this.config.type] || 'default'; + this.freezed = false; + if ( + this.config.type === NavItemType.Label || + this.config.type === NavItemType.Statistic + ) { + this.readonly = true; + } else { + this.readonly = false; + } + this.expanded = true; + } + + public setId(id: string) { + this.config.id = id; + } + + public clone(): NavItem { + const config = JSON.parse(JSON.stringify(this.config)); + const clone = new NavItem(config.type, config); + clone.freezed = this.freezed; + if (Array.isArray(this.children)) { + clone.children = []; + for (const child of this.children) { + clone.children.push(child.clone()); + } + } + return clone; + } + + public add(node: NavItem | null): void { + if (!node) { + return; + } + if (this.children) { + this.children.push(node); + } else { + this.children = [node]; + } + node.freeze(this.freezed || this.readonly); + } + + public freeze(freezed: boolean) { + this.freezed = freezed; + if (Array.isArray(this.children)) { + for (const child of this.children) { + child.freeze(this.freezed || this.readonly); + } + } + } + + public save(): void { + + } + + public toJson(): T | null { + switch (this.config.type) { + case NavItemType.Group: { + const children: INavItemConfig[] = []; + if (Array.isArray(this.children)) { + for (const item of this.children) { + const child = item.toJson(); + if (child) { + children.push(child); + } + } + } + const join: IGroupItemConfig = { + id: this.config.id, + type: NavItemType.Group, + tag: this.config.tag, + title: this.config.title, + name: this.config.name, + description: this.config.description, + rule: this.config.rule, + children + }; + return join as any; + } + case NavItemType.Label: { + const join: ILabelItemConfig = { + id: this.config.id, + type: NavItemType.Label, + tag: this.config.tag, + title: this.config.title, + name: this.config.name, + description: this.config.description, + owner: this.config.owner, + messageId: this.config.messageId, + config: this.config.config, + }; + return join as any; + } + case NavItemType.Rules: { + const join: IRulesItemConfig = { + id: this.config.id, + type: NavItemType.Rules, + tag: this.config.tag, + title: this.config.title, + name: this.config.name, + description: this.config.description, + owner: this.config.owner, + config: this.config.config, + }; + return join as any; + } + case NavItemType.Statistic: { + const join: IStatisticItemConfig = { + id: this.config.id, + type: NavItemType.Statistic, + tag: this.config.tag, + title: this.config.title, + name: this.config.name, + description: this.config.description, + owner: this.config.owner, + messageId: this.config.messageId, + config: this.config.config, + }; + return join as any; + } + } + } + + public static from(config: INavItemConfig): NavItem | null { + if (config?.type) { + const node = new NavItem(config.type, config); + const children = NavItem.getChildren(config); + for (const childConfig of children) { + node.add(NavItem.from(childConfig)); + } + return node; + } else { + return null; + } + } + + private static getChildren(config: INavItemConfig): INavItemConfig[] { + if (config?.type === NavItemType.Label) { + return config.config?.children || []; + } + if (config?.type === NavItemType.Group) { + return config.children || []; + } + return []; + } + + public static menu(type: NavItemType, label: string): NavItem { + const node = new NavItem(type, { + id: GenerateUUIDv4(), + type: type, + name: label, + title: label, + tag: getName(label), + }); + return node; + } + + public static updateOrder(tree: NavItem[], prefix: string = '') { + for (let i = 0; i < tree.length; i++) { + const item = tree[i]; + item.prefix = `${prefix}${i + 1}. `; + if (item.children) { + NavItem.updateOrder(item.children, `${prefix}${i + 1}.`); + } + } + } + + public static fromLabel(item: IPolicyLabel): NavItem | null { + const config: ILabelItemConfig = { + id: item.id || GenerateUUIDv4(), + type: NavItemType.Label, + tag: getName(item.name), + title: item.name || '', + name: item.name || '', + description: item.description || '', + owner: item.creator || '', + messageId: item.messageId, + config: item.config, + } + return NavItem.from(config); + } + + public static fromStatistic(item: IStatistic): NavItem | null { + const config: IStatisticItemConfig = { + id: item.id || GenerateUUIDv4(), + type: NavItemType.Statistic, + tag: getName(item.name), + title: item.name || '', + name: item.name || '', + description: item.description || '', + owner: item.creator || '', + messageId: item.messageId, + config: item.config, + } + return NavItem.from(config); + } + + public static fromImport(item: INavImportsConfig): NavItem | null { + if (item.type === NavItemType.Statistic) { + const config: IStatisticItemConfig = { + id: item.id || GenerateUUIDv4(), + type: NavItemType.Statistic, + tag: getName(item.name), + title: item.name || '', + name: item.name || '', + description: item.description || '', + owner: item.owner || '', + messageId: item.messageId, + config: item.config, + } + return NavItem.from(config); + } + if (item.type === NavItemType.Label) { + const config: ILabelItemConfig = { + id: item.id || GenerateUUIDv4(), + type: NavItemType.Label, + tag: getName(item.name), + title: item.name || '', + name: item.name || '', + description: item.description || '', + owner: item.owner || '', + messageId: item.messageId, + config: item.config, + } + return NavItem.from(config); + } + return null; + } + + public getItem(key: string): NavItem | null { + if (this.key === key) { + return this; + } + if (Array.isArray(this.children)) { + for (const node of this.children) { + const result = node.getItem(key); + if (result) { + return result; + } + } + } + return null; + } +} + +export class NavTree { + public mode: any = 'drag'; + public data: NavItem[] = []; + + public add(node: NavItem | null): void { + if (node) { + this.data.push(node); + } + } + + public update() { + NavItem.updateOrder(this.data); + } + + public delete(node: NavItem): void { + this._deleteNode(this.data, node); + } + + public getItem(key: string): NavItem | null { + for (const node of this.data) { + const result = node.getItem(key); + if (result) { + return result; + } + } + return null; + } + + private _deleteNode(nodes: NavItem[] | undefined, node: NavItem): void { + if (!Array.isArray(nodes)) { + return; + } + const index = nodes.indexOf(node); + if (index > -1) { + nodes.splice(index, 1); + } else { + for (const item of nodes) { + this._deleteNode(item.children, node); + } + } + } + + public toJson(): INavItemConfig[] { + const children: INavItemConfig[] = []; + for (const item of this.data) { + const child = item.toJson(); + if (child) { + children.push(child); + } + } + return children; + } + + public static fromTree(children?: INavItemConfig[]): NavTree { + const tree = new NavTree(); + if (Array.isArray(children)) { + for (const child of children) { + tree.add(NavItem.from(child)); + } + } + return tree; + } + + public static from(item?: IPolicyLabel): NavTree { + return this.fromTree(item?.config?.children); + } +} + +interface MenuItem { + title: string, + expanded: boolean, + items: NavItem[] +} + +export class NavMenu { + public readonly menu: MenuItem[]; + + public imports: NavItem[]; + + private readonly generalMenu: MenuItem; + private readonly statisticsMenu: MenuItem; + private readonly labelsMenu: MenuItem; + + public readonly map: Set; + + constructor() { + this.generalMenu = { + title: 'General', + expanded: true, + items: [ + NavItem.menu(NavItemType.Group, 'Group'), + NavItem.menu(NavItemType.Rules, 'Rules'), + ] + }; + this.statisticsMenu = { + title: 'Statistics', + expanded: true, + items: [] + }; + this.labelsMenu = { + title: 'Labels', + expanded: true, + items: [] + }; + this.menu = [ + this.generalMenu, + this.statisticsMenu, + this.labelsMenu, + ]; + this.map = new Set(); + this.imports = []; + } + + public addStatistic(item: IStatistic) { + const menuItem = NavItem.fromStatistic(item); + this.add(menuItem); + } + + public addLabel(item: IPolicyLabel) { + const menuItem = NavItem.fromLabel(item); + this.add(menuItem); + } + + public add(item: NavItem | null) { + if (!item) { + return; + } + if (this.map.has(item.messageId)) { + return; + } + if (item?.blockType === NavItemType.Statistic) { + this.statisticsMenu.items.push(item); + this.map.add(item.messageId); + this.imports.push(item); + } + if (item?.blockType === NavItemType.Label) { + this.labelsMenu.items.push(item); + this.map.add(item.messageId); + this.imports.push(item); + } + } + + public delete(item: NavItem) { + this.statisticsMenu.items = this.statisticsMenu.items.filter((e) => e !== item); + this.labelsMenu.items = this.labelsMenu.items.filter((e) => e !== item); + this.imports = this.imports.filter((e) => e !== item); + this.map.delete(item.messageId); + } + + public getIds(): string[] { + return this.imports.map((item) => item.messageId || ''); + } + + public toJson(): INavImportsConfig[] { + const imports: INavImportsConfig[] = []; + for (const item of this.imports) { + const child = item.toJson(); + if (child) { + imports.push(child); + } + } + return imports; + } + + public static from(item: IPolicyLabel): NavMenu { + return this.fromImports(item.config?.imports); + } + + public static fromImports(imports?: INavImportsConfig[]): NavMenu { + const menu = new NavMenu(); + if (Array.isArray(imports)) { + for (const item of imports) { + const menuItem = NavItem.fromImport(item); + menu.add(menuItem); + } + } + return menu; + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/statistics/policy-labels/policy-label-configuration/components/rules-config.ts b/frontend/src/app/modules/statistics/policy-labels/policy-label-configuration/components/rules-config.ts new file mode 100644 index 0000000000..31c42a5512 --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/policy-label-configuration/components/rules-config.ts @@ -0,0 +1,607 @@ +import { Schema, SchemaField, IRulesItemConfig, IStatisticItemConfig, LabelValidators } from "@guardian/interfaces"; +import { DialogService } from "primeng/dynamicdialog"; +import { Subject } from "rxjs"; +import { CustomCustomDialogComponent } from "src/app/modules/common/custom-confirm-dialog/custom-confirm-dialog.component"; +import { FormulaRule, ConditionRule, RangeRule } from "src/app/modules/common/models/conditions"; +import { createAutocomplete } from "src/app/modules/common/models/lang-modes/autocomplete"; +import { SchemaFormulas, SchemaFormula } from "src/app/modules/common/models/schema-formulas"; +import { SchemaNode, SchemaData } from "src/app/modules/common/models/schema-node"; +import { SchemaScores, SchemaScore } from "src/app/modules/common/models/schema-scores"; +import { SchemaVariables, SchemaVariable } from "src/app/modules/common/models/schema-variables"; +import { TreeGraphComponent } from "src/app/modules/common/tree-graph/tree-graph.component"; +import { TreeListItem } from "src/app/modules/common/tree-graph/tree-list"; +import { TreeNode } from "src/app/modules/common/tree-graph/tree-node"; +import { TreeSource } from "src/app/modules/common/tree-graph/tree-source"; +import { IPFSService } from "src/app/services/ipfs.service"; +import { ScoreDialog } from "../../../policy-statistics/dialogs/score-dialog/score-dialog.component"; +import { EnumValue, SchemaRuleConfigDialog } from "../../../schema-rules/dialogs/schema-rule-config-dialog/schema-rule-config-dialog.component"; +import { NavItem } from "./nav-item"; +import { PolicyLabelConfigurationComponent } from "../policy-label-configuration.component"; + + +export class RulesConfig { + public show: boolean = false; + public readonly: boolean = false; + public stepper = [true, false]; + + public searchField: string = ''; + public nodeLoading: boolean = true; + public selectedNode: SchemaNode | null = null; + public rootNode: SchemaNode | null = null; + public schemaFilterType: number = 1; + public schemas: Schema[]; + public policy: any; + + public formulas: SchemaFormulas = new SchemaFormulas(); + public variables: SchemaVariables = new SchemaVariables(); + public scores: SchemaScores = new SchemaScores(); + + public name: string; + public type: string; + + private _selectTimeout1: any; + private _selectTimeout2: any; + private _selectTimeout3: any; + + private tree: TreeGraphComponent; + private currentNode: NavItem | null; + + private nodes: SchemaNode[]; + private enumMap: Map>; + private source: TreeSource; + private properties: Map; + private namespace: string[]; + + private codeMirrorOptions: any = { + theme: 'default', + mode: 'formula-lang', + styleActiveLine: false, + lineNumbers: false, + lineWrapping: false, + foldGutter: true, + gutters: [ + 'CodeMirror-lint-markers' + ], + autoCloseBrackets: true, + matchBrackets: true, + lint: true, + readOnly: false, + viewportMargin: Infinity, + variables: [], + extraKeys: { "Ctrl-Space": "autocomplete" }, + scrollbarStyle: null, + singleLine: true + }; + private codeMirrorMap = new Map(); + + public formulaTypes: any[] = [{ + label: 'String', + value: 'string' + }, { + label: 'Number', + value: 'number' + }]; + + public get zoom(): number { + if (this.tree) { + return Math.round(this.tree.zoom * 100); + } else { + return 100; + } + } + + public get roots(): SchemaNode[] { + return this.source?.roots; + } + + public readonly step = new Subject(); + + constructor( + private parent: PolicyLabelConfigurationComponent, + private dialogService: DialogService, + private ipfs: IPFSService + ) { + } + + public setProperties(properties: any[]) { + this.properties = new Map(); + if (properties) { + for (const property of properties) { + this.properties.set(property.title, property.value); + } + } + } + + public setPolicy(relationships: any) { + this.policy = relationships?.policy || {}; + } + + public setSchemas(relationships: any) { + const schemas = relationships.policySchemas || []; + + this.nodes = []; + this.schemas = []; + for (const schema of schemas) { + try { + const item = new Schema(schema); + const node = SchemaNode.from(item, this.properties); + for (const field of item.fields) { + if (field.isRef && field.type) { + node.addId(field.type); + } + } + this.nodes.push(node); + this.schemas.push(item); + } catch (error) { + console.log(error); + } + } + + this.source = new TreeSource(this.nodes); + if (this.tree) { + this.tree.setData(this.source); + } + + this.enumMap = new Map>(); + for (const schema of this.schemas) { + const map = new Map(); + this.enumMap.set(schema.iri || '', map); + this.getFieldList(schema.fields, map); + } + } + + private getFieldList(fields: SchemaField[], map: Map) { + for (const field of fields) { + if (field.enum || field.remoteLink) { + map.set(field.path || '', new EnumValue(this.ipfs, field)); + } + if (Array.isArray(field.fields)) { + this.getFieldList(field.fields, map); + } + } + } + + public setData(node: NavItem, label: any) { + this.currentNode = node; + + const validators = new LabelValidators(label); + validators.setData([]); + const validator = validators.getValidator(node.key); + const namespace = validator?.getNamespace(); + const variables = namespace?.getNames(node.key); + this.namespace = variables || []; + + const clone = this.currentNode.clone(); + const item = clone.config as (IRulesItemConfig | IStatisticItemConfig); + const config = item.config; + + this.readonly = clone.readonly || clone.freezed; + this.name = clone.label; + this.type = clone.blockType === 'statistic' ? 'Statistic' : 'Rule'; + + this.variables.fromData(config?.variables); + this.scores.fromData(config?.scores); + this.formulas.fromData(config?.formulas); + + this.variables.updateType(this.schemas); + this.updateCodeMirror(); + + const map1 = this.variables.getMap(); + for (const root of this.source.roots) { + const rootView = root.fields; + const data = rootView.data; + const map2 = map1.get(root.data.iri); + if (map2) { + for (const field of data.list) { + const path = field.path.map((e) => e.data.name).join('.'); + field.selected = map2.has(path); + } + } else { + for (const field of data.list) { + field.selected = false; + } + } + rootView.updateHidden(); + rootView.updateSelected(); + } + } + + public onCancel() { + this.currentNode = null; + } + + public onSave() { + if (this.currentNode) { + const item = this.currentNode.config as (IRulesItemConfig | IStatisticItemConfig); + if (item.config) { + item.config.variables = this.variables.getJson(); + item.config.formulas = this.formulas.getJson(); + item.config.scores = this.scores.getJson(); + } else { + item.config = { + variables: this.variables.getJson(), + formulas: this.formulas.getJson(), + scores: this.scores.getJson(), + }; + } + } + } + + public initTree($event: TreeGraphComponent) { + this.tree = $event; + if (this.nodes) { + this.tree.setData(this.source); + } + } + + private getEnum(variable: SchemaVariable): EnumValue | undefined { + const map = this.enumMap.get(variable.schemaId); + if (map) { + return map.get(variable.path); + } + return undefined; + } + + private getEnums(): { [x: string]: EnumValue; } { + const enums: { [x: string]: EnumValue; } = {}; + for (const variable of this.variables.variables) { + const item = this.getEnum(variable); + if (item) { + enums[variable.id] = item; + } else { + enums[variable.id] = new EnumValue(this.ipfs); + } + } + return enums; + } + + public isActionStep(index: number): boolean { + return this.stepper[index]; + } + + public async goToStep(index: number) { + for (let i = 0; i < this.stepper.length; i++) { + this.stepper[i] = (i == index); + } + return this.refreshView(); + } + + public async refreshView() { + return new Promise((resolve, reject) => { + if (this.stepper[0]) { + setTimeout(() => { + this.tree?.move(18, 46); + setTimeout(() => { + this.tree?.refresh(); + resolve(); + }, 1500); + }, 100); + } else { + setTimeout(() => { + resolve(); + }, 400); + } + }); + } + + public onStep(index: number) { + this.step.next(index); + } + + public schemaConfigChange($event: any) { + if ($event.index === 1) { + this.schemaFilterType = 2; + } else { + this.schemaFilterType = 1; + } + this.onSchemaFilter(0); + } + + public createNodes($event: any) { + this.tree.move(18, 46); + } + + public onSelectNode(node: TreeNode | null) { + clearTimeout(this._selectTimeout1); + clearTimeout(this._selectTimeout2); + clearTimeout(this._selectTimeout3); + this.nodeLoading = true; + this.selectedNode = node as SchemaNode; + this._selectTimeout1 = setTimeout(() => { + this._updateSelectNode(); + }, 350); + } + + private _updateSelectNode() { + this.rootNode = (this.selectedNode?.getRoot() || null) as SchemaNode; + if (this.rootNode) { + const schemaId = this.selectedNode?.data?.iri; + const rootView = this.rootNode.fields; + rootView.collapseAll(true); + rootView.highlightAll(false); + const items = rootView.find((item) => item.type === schemaId); + for (const item of items) { + rootView.collapsePath(item, false); + rootView.highlight(item, true); + } + rootView.searchItems(this.searchField, this.schemaFilterType); + rootView.updateHidden(); + rootView.updateSelected(); + this._selectTimeout2 = setTimeout(() => { + this._updateSelectScroll(); + }, 200); + } + } + + private _updateSelectScroll() { + const first = (document as any) + .querySelector('.field-item[highlighted="true"]:not([search-highlighted="hidden"])'); + if (this.parent.fieldTree) { + if (first) { + this.parent.fieldTree.nativeElement.scrollTop = first.offsetTop; + } else { + this.parent.fieldTree.nativeElement.scrollTop = 0; + } + } + this._selectTimeout3 = setTimeout(() => { + this.nodeLoading = false; + }, 200); + } + + public onCollapseField(field: TreeListItem) { + if (this.rootNode) { + const rootView = this.rootNode.fields; + rootView.collapse(field, !field.collapsed); + rootView.updateHidden(); + } + } + + public onSelectField(field: TreeListItem) { + if (field.expandable) { + this.onCollapseField(field); + return; + } + if (this.readonly) { + return; + } + field.selected = !field.selected; + if (this.rootNode) { + const rootView = this.rootNode.fields; + rootView.updateHidden(); + rootView.updateSelected(); + } + this.updateVariables(); + } + + public onSchemaFilter(type: number) { + clearTimeout(this._selectTimeout3); + this.nodeLoading = true; + if (this.source) { + let highlighted: any; + + const roots = this.source.roots; + for (const root of roots) { + root.fields.searchItems(this.searchField, this.schemaFilterType); + } + + for (const node of this.source.nodes) { + node.fields.searchView(this.searchField); + if (!highlighted && node.fields.searchHighlighted) { + highlighted = node; + } + } + + if (this.rootNode) { + const rootView = this.rootNode.fields; + rootView.updateHidden(); + } + + if (type === 1 && highlighted) { + this.onNavTarget(highlighted); + } + } + this._selectTimeout3 = setTimeout(() => { + this.nodeLoading = false; + }, 250); + } + + public onNavTarget(highlighted: SchemaNode) { + const el = document.querySelector(`.tree-node[node-id="${highlighted.uuid}"]`); + const grid = el?.parentElement?.parentElement; + if (el && grid) { + const elCoord = el.getBoundingClientRect(); + const gridCoord = grid.getBoundingClientRect(); + const x = elCoord.left - gridCoord.left; + const y = elCoord.top - gridCoord.top; + this.tree?.move(-x + 50, -y + 56); + } + } + + public onNavRoot(root: SchemaNode) { + const el = document.querySelector(`.tree-node[node-id="${root.uuid}"]`); + const grid = el?.parentElement?.parentElement; + if (el && grid) { + const elCoord = el.getBoundingClientRect(); + const gridCoord = grid.getBoundingClientRect(); + const x = elCoord.left - gridCoord.left; + this.tree?.move(-x + 50, 56); + this.tree.onSelectNode(root); + } + } + + public onNavNext(dir: number) { + const el = this.parent.treeTabs.nativeElement; + const max = Math.floor((el.scrollWidth - el.offsetWidth) / 148); + let current = Math.floor(this.parent.treeTabs.nativeElement.scrollLeft / 148); + if (dir < 0) { + current--; + } else { + current++; + } + current = Math.min(Math.max(current, 0), max); + this.parent.treeTabs.nativeElement.scrollLeft = current * 148; + } + + public onClearNode() { + this.tree?.onSelectNode(null); + } + + public onZoom(d: number) { + if (this.tree) { + this.tree.onZoom(d); + if (d === 0) { + this.tree.move(18, 46); + } + } + } + + private updateVariables() { + this.variables.fromNodes(this.source.roots); + this.variables.updateType(this.schemas); + this.updateCodeMirror(); + } + + private updateCodeMirror() { + const map = new Set(); + for (const name of this.namespace) { + map.add(name); + } + for (const name of this.variables.getNames()) { + map.add(name); + } + for (const name of this.scores.getNames()) { + map.add(name); + } + + for (const name of this.formulas.getNames()) { + const variables = Array.from(map); + const codeMirrorOptions = { + ...this.codeMirrorOptions, + variables: variables, + hintOptions: { + hint: createAutocomplete(variables) + } + }; + this.codeMirrorMap.set(name, codeMirrorOptions); + map.add(name); + } + } + + public getCodeMirrorOptions(name: string): any { + return this.codeMirrorMap.get(name); + } + + public getRelationshipsName(item: any) { + if (item) { + const variable = this.variables.get(item.id); + if (variable) { + return `${variable.id} - ${variable.fieldDescription}`; + } else { + return item.id; + } + } + return item; + } + + public onAddScore() { + this.scores.add(); + this.updateCodeMirror(); + } + + public onRename() { + this.updateCodeMirror(); + } + + public onEditScore(score: SchemaScore) { + const dialogRef = this.dialogService.open(ScoreDialog, { + showHeader: false, + header: 'Create New', + width: '640px', + styleClass: 'guardian-dialog', + data: { + score: JSON.parse(JSON.stringify(score)) + } + }); + dialogRef.onClose.subscribe(async (result) => { + if (result) { + score.description = result.description; + score.options = result.options; + } + }); + } + + public onDeleteScore(score: SchemaScore) { + const dialogRef = this.dialogService.open(CustomCustomDialogComponent, { + showHeader: false, + width: '640px', + styleClass: 'guardian-dialog', + data: { + header: 'Delete score', + text: 'Are you sure want to delete score?', + buttons: [{ + name: 'Close', + class: 'secondary' + }, { + name: 'Delete', + class: 'delete' + }] + }, + }); + dialogRef.onClose.subscribe((result: string) => { + if (result === 'Delete') { + this.scores.delete(score); + this.updateCodeMirror(); + } + }); + } + + public onAddFormula() { + this.formulas.add(); + this.updateCodeMirror(); + } + + public onDeleteFormula(formula: SchemaFormula) { + const dialogRef = this.dialogService.open(CustomCustomDialogComponent, { + showHeader: false, + width: '640px', + styleClass: 'guardian-dialog', + data: { + header: 'Delete formula', + text: 'Are you sure want to delete formula?', + buttons: [{ + name: 'Close', + class: 'secondary' + }, { + name: 'Delete', + class: 'delete' + }] + }, + }); + dialogRef.onClose.subscribe((result: string) => { + if (result === 'Delete') { + this.formulas.delete(formula); + } + }); + } + + public onEditFormula(formula: SchemaFormula) { + const dialogRef = this.dialogService.open(SchemaRuleConfigDialog, { + showHeader: false, + header: 'Preview', + width: '800px', + styleClass: 'guardian-dialog', + data: { + variables: this.variables.getOptions(), + item: formula.clone(), + readonly: this.readonly, + enums: this.getEnums() + } + }); + dialogRef.onClose.subscribe(async (result) => { + if (result) { + const rule: FormulaRule | ConditionRule | RangeRule = result.rule; + formula.addRule(rule); + } + }); + } +} diff --git a/frontend/src/app/modules/statistics/policy-labels/policy-label-configuration/policy-label-configuration.component.html b/frontend/src/app/modules/statistics/policy-labels/policy-label-configuration/policy-label-configuration.component.html new file mode 100644 index 0000000000..b3a20a92b0 --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/policy-label-configuration/policy-label-configuration.component.html @@ -0,0 +1,852 @@ +
+
+
+
+ +
+ Before starting work you need to get DID + here +
+ +
+
+ Labels + > + {{item?.name || 'Label'}} +
+
+ {{item?.name}} +
+ Policy Name: {{policy.name}} + Version: {{policy.version}} +
+
+
+ +
+
+ Labels + > + {{item?.name || 'Label'}} + > + {{rulesConfig.name || rulesConfig.type}} +
+
+ {{rulesConfig.name}} +
+ Policy Name: {{policy.name}} + Version: {{policy.version}} +
+
+
+ +
+
+
+ +
+ +
Back
+
+ +
+ +
+ +
Save
+
+ +
+ +
+ +
Preview
+
+ +
+ + +
+ +
Published
+
+
+
+ +
+ +
Draft
+
+
+ + +
+ +
{{item.label}}
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
Overview
+
+
+
+ +
+
Imports
+
+
+
+ +
+
Navigation
+
+
+
+
+ +
+
+
+
+ + +
+
+ + +
+ +
+ + +
+
+
+ +
+
+ + +
+
+
+
+
TYPE
+
NAME
+
DESCRIPTION
+
OWNER
+
+
+
+
+
+
+ + {{item.blockType}} +
+
+
+ {{item.config.name}} +
+
+ {{item.config.description}} +
+
+ {{item.config.owner}} +
+
+ +
+
+
+
+
+
+ + +
+
There were no items imported yet.
+
+ +
+
+ + +
+
+ +
+
+
+
+
+
+ + +
+
Select Schemas
+
+
+
+
+ + +
+
Configure Rules
+
+
+
+
+
+
+
+ + +
+
+ {{node.data.name}} +
+
+
+ {{item.data.description}} +
+
+ + {{node.fields.selectedCount-4}} more +
+
+
+
+ Nothing selected +
+
+
+
+
+
+ +
+
+
+
+
+
+
+ {{root.data.name}} + + {{root.fields.selectedAllCount}} + +
+
+
+
+
+
+ +
+
+ +
+
+
{{rulesConfig.zoom}}%
+
+
+ +
+
+
+
+
+
+
+
+ {{rulesConfig.rootNode.data.name}} +
+ +
+
+
+ + +
+ +
+
+
+
+
+
+
+
+
+ +
+
+ {{item.data.description}} +
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+
+ {{item.data.propertyName || item.data.description}} +
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ Input Fields +
+
+
+
ID
+
PATH
+
PROPERTY
+
FIELD
+
TYPE
+
+
+
+
+ +
+
+
+ {{variable.schemaName}} / {{variable.schemaPath}} +
+
+ {{variable.fieldPropertyName}} +
+
+ {{variable.fieldDescription}} +
+
+ + {{variable.displayType}} + +
+
+
+
+
+
+ + +
+
There were no fields selected yet.
+
+
+
+
+ Scores +
+
+
+
ID
+ +
DESCRIPTION
+
RELATIONSHIPS
+
OPTIONS
+
+
+
+
+
+ +
+
+ +
+ +
+
+
+ + +
+ {{ rulesConfig.getRelationshipsName(option) }} +
+
+ +
{{ option.id }} - {{ + option.fieldDescription }}
+
+
+
+
+
+
+
+ {{option.description}} +
+
+
+
+ + +
+
+
+
+
+
+ + +
+
There were no scores created yet.
+
+
+ +
+
+
+
+ Output Fields +
+
+
+
ID
+
TYPE
+
DESCRIPTION
+
FORMULA
+
RULES
+
+
+
+
+
+ +
+
+
+ + +
+
+
+ +
+
+
+ +
+
+
+ {{formula.ruleType}} +
+
+
+ + +
+
+
+
+
+
+ + +
+
There were no formulas created yet.
+
+
+ +
+
+
+
+ +
+ + +
+
+
\ No newline at end of file diff --git a/frontend/src/app/modules/statistics/policy-labels/policy-label-configuration/policy-label-configuration.component.scss b/frontend/src/app/modules/statistics/policy-labels/policy-label-configuration/policy-label-configuration.component.scss new file mode 100644 index 0000000000..403a947692 --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/policy-label-configuration/policy-label-configuration.component.scss @@ -0,0 +1,1576 @@ +.guardian-page { + position: relative; + padding: 0px; + user-select: none; + + .header-container { + padding: 0px 48px 0px 48px; + background: var(--guardian-primary-color, #4169E2); + min-height: 132px; + height: 132px; + + .guardian-user-page-header { + font-size: 32px; + font-weight: 600; + height: 40px; + padding: 0; + position: relative; + color: var(--guardian-header-color, #000); + margin-bottom: 32px; + + span { + line-height: 40px; + } + } + + .guardian-user-page-path { + font-family: Inter; + font-size: 12px; + font-weight: 400; + line-height: 12px; + text-align: left; + color: var(--guardian-background, #FFFFFF); + margin-top: 36px; + margin-bottom: 8px; + height: 16px; + + span { + padding-right: 8px; + } + + .path-link { + cursor: pointer; + + &:hover { + text-decoration: underline; + } + } + } + + .guardian-user-page-header { + color: var(--guardian-background, #FFFFFF); + } + + .policy-name { + color: var(--guardian-background, #FFFFFF); + font-size: 14px; + font-weight: 500; + line-height: 16px; + position: absolute; + top: 12px; + right: 0; + + .policy-version { + padding-left: 16px; + } + } + } + + .actions-container { + min-height: 64px; + height: 64px; + width: 100%; + display: flex; + justify-content: flex-end; + padding: 12px 48px; + background: var(--guardian-background, #FFFFFF); + border-top: 1px solid var(--guardian-border-color, #E1E7EF); + position: relative; + overflow: hidden; + + button { + height: 40px; + width: 135px; + margin-left: 16px; + } + + .guardian-step-container { + height: 40px; + width: 620px; + } + } + + .body-container { + position: absolute; + left: 0; + right: 0; + top: 132px; + bottom: 0; + overflow: hidden; + display: flex; + flex-direction: column; + background: #f9fafc; + } + + .tab-container { + height: 40px; + width: 100%; + background: #E1E7EF; + border-bottom: 1px solid #C4D0E1; + padding: 0px 12px; + + .guardian-step { + margin-right: 8px; + margin-bottom: 0px; + background: #EFF3F7; + border: 1px solid #C4D0E1; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + border-bottom-width: 0; + + .guardian-step-name { + color: #848FA9; + } + + &:hover, + &[action="true"]:hover { + background: #f8f9fb !important; + } + + &[action="true"] { + background: #fff !important; + + .guardian-step-name { + color: var(--guardian-primary-color, #4169E2) !important; + } + } + } + } + + .step-container { + min-height: 40px; + height: 40px; + width: 100%; + display: flex; + justify-content: center; + padding: 0px; + background: var(--guardian-background, #FFFFFF); + border-bottom: 1px solid var(--guardian-border-color, #E1E7EF); + position: relative; + box-shadow: 0px 4px 8px 0px var(--guardian-shadow, #00000014); + } + + .step-body-container { + width: 100%; + height: 100%; + user-select: none; + position: relative; + + .overview-viewer { + padding: 24px 48px; + + form { + box-shadow: 0px 4px 8px 0px var(--guardian-shadow, #00000014); + background: var(--guardian-background, #FFFFFF); + padding: 24px 24px 2px 24px; + border-radius: 8px; + + .guardian-input-container { + margin-bottom: 24px; + } + } + } + + .imports-viewer { + padding: 24px 48px; + + .import-toolbar { + height: 40px; + margin-bottom: 16px; + display: flex; + justify-content: flex-end; + + button { + width: 110px; + height: 40px; + } + } + + .import-list { + box-shadow: 0px 4px 8px 0px var(--guardian-shadow, #00000014); + background: var(--guardian-background, #FFFFFF); + padding: 24px 24px 24px 24px; + border-radius: 8px; + } + + .import-icon { + height: 24px; + display: flex; + align-items: center; + text-transform: capitalize; + + svg-icon { + margin-right: 8px; + } + } + } + + .navigation-viewer { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + overflow: hidden; + + .navigation-toolbar { + position: absolute; + top: 0px; + bottom: 0px; + left: 0px; + width: 240px; + z-index: 2; + background: var(--guardian-background, #FFFFFF); + border-left: 1px solid var(--guardian-border-color, #E1E7EF); + box-shadow: 0px 0px 4px 0px var(--guardian-shadow, #00000014); + overflow: hidden; + + .navigation-toolbar-header { + font-family: Poppins; + font-size: 16px; + font-weight: 600; + line-height: 20px; + text-align: left; + text-underline-position: from-font; + text-decoration-skip-ink: none; + padding: 24px 0px 21px 24px; + } + + .navigation-toolbar-search {} + + .navigation-toolbar-group { + padding: 0px 24px 0px 24px; + + .navigation-toolbar-group-header { + font-family: Inter; + font-size: 12px; + font-weight: 400; + line-height: 16px; + text-align: left; + text-underline-position: from-font; + text-decoration-skip-ink: none; + padding: 5px 0px 5px 0px; + color: #848FA9; + text-transform: uppercase; + margin-bottom: 8px; + display: flex; + flex-direction: row; + height: 28px; + align-items: center; + cursor: pointer; + } + + .navigation-toolbar-group-expanded { + width: 20px; + height: 20px; + cursor: pointer; + margin-right: 8px; + border-radius: 50%; + padding: 2px; + + &:hover { + background: #e9ecef; + } + } + + .navigation-toolbar-group-item { + width: 100%; + height: 32px; + border-radius: 6px; + border: 1px solid var(--primary-primary, #4169E2); + display: flex; + overflow: hidden; + flex-direction: row; + cursor: pointer; + margin-bottom: 8px; + box-shadow: 0px 4px 4px 0px #00000014; + padding: 0px 8px; + align-items: center; + + .navigation-toolbar-group-item-icon { + width: 16px; + height: 16px; + overflow: hidden; + } + + .navigation-toolbar-group-item-label { + font-family: Inter; + font-size: 12px; + font-weight: 500; + line-height: 14px; + padding-left: 8px; + color: #23252E; + } + + &:last-child { + margin-bottom: 16px; + } + } + + &[expanded="false"] { + .navigation-toolbar-group-item { + display: none; + } + } + } + } + + .navigation-tree-container { + position: absolute; + left: 252px; + top: 12px; + bottom: 0px; + right: 410px; + z-index: 1; + user-select: none; + padding: 24px; + background-color: #f9fafc; + // background-image: linear-gradient(#e8e8e8 0.8px, transparent 0.8px), linear-gradient(90deg, #e8e8e8 0.8px, transparent 0.8px), linear-gradient(#e8e8e8 0.4px, transparent 0.4px), linear-gradient(90deg, #e8e8e8 0.4px, #f9fafc 0.4px); + // background-size: 20px 20px, 20px 20px, 4px 4px, 4px 4px; + // background-position: -0.8px -0.8px, -0.8px -0.8px, -0.4px -0.4px, -0.4px -0.4px; + background-image: radial-gradient(circle, #bfbfbf 1px, #f9fafc 1px); + background-size: 20px 20px; + + -webkit-transition: right 0.2s ease-in-out; + -moz-transition: right 0.2s ease-in-out; + -o-transition: right 0.2s ease-in-out; + transition: right 0.2s ease-in-out; + transform: translateZ(0); + will-change: transform, right; + + &[hidden-config="true"] { + right: 0px; + } + + .navigation-drop-area { + position: absolute; + left: 0px; + top: 0px; + right: 0px; + bottom: 0px; + z-index: -1; + + &::before { + content: ""; + display: block; + position: absolute; + left: 20px; + top: 20px; + right: 20px; + bottom: 20px; + border: 2px dashed var(--guardian-primary-color, #4169E2); + background: #f0f3fc75; + pointer-events: none; + opacity: 0; + } + + &.p-draggable-enter { + &::before { + opacity: 1; + transition-delay: 0.2s; + transition-property: opacity; + } + } + } + + .navigation-tree-area { + padding: 6px 0px; + width: fit-content; + + &[hidden-tree="true"] { + display: none; + } + } + + .navigation-tree { + width: 100%; + height: 100%; + + &::ng-deep { + // ul[role="tree"]>p-treenode>.p-treenode-droppoint { + // display: none !important; + // } + + .p-tree-toggler-icon { + height: 24px; + width: 24px; + } + + .p-tree-empty-message { + display: none; + } + + .p-treenode-droppoint { + height: 10px; + position: relative; + margin: 0px 34px 0px 38px; + + &.p-treenode-droppoint-active { + background-color: transparent !important; + + &::before { + content: ""; + display: block; + position: absolute; + left: 0; + top: 0; + right: 0px; + bottom: 3px; + border-bottom: 2px dashed var(--guardian-primary-color, #4169E2); + background: #f0f3fc; + } + } + } + + .p-treenode-content.p-treenode-selectable { + background: transparent !important; + color: #495057; + } + + .p-treenode-content.p-treenode-selectable+ul { + .p-treenode-droppoint { + pointer-events: none; + opacity: 0; + } + } + + .p-treenode-dragover { + .p-treenode-content.p-treenode-selectable+.p-treenode-children { + pointer-events: none; + } + } + + .p-tree .p-treenode-children { + padding: 0 0 0 34px; + } + + .p-treenode-content { + position: relative; + width: 242px; + cursor: pointer; + box-shadow: none; + padding: 2px 0px; + + button { + box-shadow: none; + outline: none; + } + + .navigation-item { + height: 32px; + width: 200px; + border: 1px solid var(--guardian-primary-color, #4169E2); + border-radius: 6px; + box-shadow: 0px 4px 4px 0px #00000014; + padding: 0px 8px; + display: flex; + flex-direction: row; + align-items: center; + background: #fff; + + .navigation-item-icon { + width: 16px; + height: 16px; + overflow: hidden; + } + + .navigation-item-label { + font-family: Inter; + font-size: 12px; + font-weight: 500; + line-height: 14px; + padding-left: 8px; + color: #23252E; + width: 160px; + } + + &[readonly="true"] { + .navigation-item-edit-icon { + display: none; + } + } + + &[freezed="true"] { + background: #EFF3F7; + + .navigation-item-edit-icon { + display: none; + } + } + + &[selected="true"] { + box-shadow: 0 0 0 2px #4169E2; + } + } + + &.p-treenode-dragover { + background: transparent; + position: relative; + + &::before { + content: ""; + display: block; + position: absolute; + left: 35px; + top: -2px; + right: 0px; + bottom: -2px; + border: 2px dashed var(--guardian-primary-color, #4169E2); + background: #f0f3fc; + z-index: -2; + } + + &+ul[role="group"] { + position: relative; + + &::before { + content: ""; + display: block; + position: absolute; + left: 35px; + top: -38px; + right: 0px; + bottom: -2px; + border: 2px dashed var(--guardian-primary-color, #4169E2); + background: #f0f3fc; + z-index: -1; + } + } + } + } + + // .p-treenode:focus>.p-treenode-content { + // .navigation-item { + // box-shadow: 0 0 0 2px #4169E2; + // } + // } + } + } + } + + .item-config { + position: absolute; + top: 0px; + bottom: 0px; + right: 0px; + width: 400px; + z-index: 3; + background: var(--guardian-background, #FFFFFF); + border-left: 1px solid var(--guardian-border-color, #E1E7EF); + box-shadow: 0px 0px 4px 0px var(--guardian-shadow, #00000014); + overflow: hidden; + -webkit-transition: width 0.2s ease-in-out; + -moz-transition: width 0.2s ease-in-out; + -o-transition: width 0.2s ease-in-out; + transition: width 0.2s ease-in-out; + transform: translateZ(0); + will-change: transform, width; + + &[hidden-config="true"] { + width: 0px; + } + + .config-close { + width: 32px; + height: 32px; + min-width: 32px; + max-width: 32px; + position: absolute; + padding: 4px; + border-radius: 6px; + cursor: pointer; + top: 17px; + right: 16px; + + &:hover { + background: var(--guardian-primary-background); + } + } + + .config-icon { + width: 32px; + height: 32px; + min-width: 32px; + max-width: 32px; + position: absolute; + padding: 4px; + top: 16px; + left: 16px; + } + + .item-config-container { + width: 400px; + height: 100%; + display: flex; + flex-direction: column; + + .item-name { + width: 400px; + padding: 24px 24px 24px 54px; + font-size: 16px; + font-weight: 600; + line-height: 18px; + text-align: left; + color: var(--guardian-font-color, #23252E); + position: relative; + } + + .item-options { + width: 100%; + height: 100%; + padding: 0px 24px 24px 24px; + + .guardian-input-container { + margin-bottom: 16px; + } + + .item-options-header { + font-family: Inter; + font-size: 14px; + font-weight: 700; + line-height: 18px; + color: #23252E; + margin-bottom: 24px; + margin-top: 24px; + } + } + } + } + + .item-actions { + display: flex; + flex-direction: column; + padding: 24px; + + button { + width: 100%; + height: 28px; + margin-bottom: 8px; + } + } + } + + .schema-viewer { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + overflow: hidden; + + .tree-container { + position: absolute; + left: 0px; + top: 0px; + bottom: 40px; + right: 0px; + z-index: 1; + user-select: none; + } + + .schema-search { + position: absolute; + left: 0px; + top: 0px; + right: 0px; + height: 56px; + z-index: 2; + background: transparent; + padding: 8px 416px 8px 16px; + + .guardian-search { + max-width: 500px; + + input { + height: 28px; + font-size: 12px; + } + } + } + + .schema-toolbar { + position: absolute; + left: 0px; + bottom: 0px; + right: 0px; + height: 40px; + z-index: 2; + background: var(--guardian-grey-background, #F9FAFC); + padding: 0px 16px 0px 16px; + display: flex; + flex-direction: row; + border-top: 1px solid var(--guardian-border-color, #E1E7EF); + user-select: none; + box-shadow: 0px 0px 4px 0px var(--guardian-shadow, #00000014); + + .tree-tabs { + display: flex; + flex-direction: row; + overflow: hidden; + } + + .tree-tabs-nav { + display: flex; + flex-direction: row; + width: 70px; + height: 30px; + min-width: 70px; + padding: 0 5px; + margin-top: 5px; + + .tree-tabs-nav-left { + width: 30px; + height: 30px; + cursor: pointer; + position: relative; + + &::before { + content: ""; + display: block; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + border: 6px solid var(--guardian-primary-color, #4169E2); + width: 0px; + height: 0px; + pointer-events: none; + border-top-color: transparent; + border-left-color: transparent; + border-bottom-color: transparent; + border-left-width: 0; + } + } + + .tree-tabs-nav-right { + width: 30px; + height: 30px; + cursor: pointer; + position: relative; + + &::before { + content: ""; + display: block; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + border: 6px solid var(--guardian-primary-color, #4169E2); + width: 0px; + height: 0px; + pointer-events: none; + border-top-color: transparent; + border-right-color: transparent; + border-bottom-color: transparent; + border-right-width: 0; + } + } + } + + .tree-tab { + padding: 8px 16px 8px 16px; + background: var(--guardian-background, #FFFFFF); + font-family: Inter; + font-size: 12px; + font-weight: 500; + line-height: 14px; + text-align: left; + color: var(--guardian-font-color, #23252E); + border: 1px solid #C4D0E1; + border-radius: 6px; + border-top-width: 0; + border-top-left-radius: 0; + border-top-right-radius: 0; + height: 30px; + cursor: pointer; + margin-right: 8px; + display: flex; + min-width: 140px; + max-width: 140px; + + &.selected-type-selected, + &.selected-type-sub { + color: var(--guardian-primary-color, #4169E2); + border-color: var(--guardian-primary-color, #4169E2); + background: #4169E214; + } + + .tree-tab-name { + display: block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .tree-tab-count { + margin-left: 8px; + background-color: var(--guardian-primary-color, #4169E2); + width: min-content; + display: flex; + align-items: center; + justify-content: center; + padding: 0px 8px; + border-radius: 16px; + cursor: pointer; + color: var(--guardian-background, #FFFFFF); + font-size: 10px; + height: 16px; + position: relative; + top: -1px; + } + } + + .tree-tab-last { + max-width: 600px; + min-width: 600px; + height: 30px; + } + } + + .zoom-toolbar { + width: 48px; + height: 160px; + position: absolute; + right: 400px; + top: 0px; + z-index: 3; + padding: 16px 16px 4px 4px; + overflow: hidden; + + -webkit-transition: right 0.2s ease-in-out; + -moz-transition: right 0.2s ease-in-out; + -o-transition: right 0.2s ease-in-out; + transition: right 0.2s ease-in-out; + + &[hidden-schema="true"] { + right: 0px; + } + + .zoom-button { + width: 28px; + height: 28px; + margin-bottom: 8px; + border-radius: 8px; + background: var(--guardian-background, #FFFFFF); + box-shadow: 0px 0px 1px 3px var(--guardian-grey-color, #EFF3F7); + + .zoom-label { + width: 28px; + height: 28px; + border-radius: 8px; + font-family: Inter; + font-size: 8px; + font-weight: 700; + color: var(--guardian-disabled-color, #848FA9); + border: 1px solid var(--guardian-border-color, #E1E7EF); + display: flex; + justify-content: center; + align-items: center; + } + + button { + width: 28px; + height: 28px; + border: 1px solid var(--guardian-primary-color, #4169E2); + border-radius: 8px; + } + } + } + + .schema-fields { + position: absolute; + top: 0px; + bottom: 0px; + right: 0px; + width: 400px; + z-index: 3; + background: var(--guardian-background, #FFFFFF); + border-left: 1px solid var(--guardian-border-color, #E1E7EF); + box-shadow: 0px 0px 4px 0px var(--guardian-shadow, #00000014); + overflow: hidden; + -webkit-transition: width 0.2s ease-in-out; + -moz-transition: width 0.2s ease-in-out; + -o-transition: width 0.2s ease-in-out; + transition: width 0.2s ease-in-out; + transform: translateZ(0); + will-change: transform, width; + + &[hidden-schema="true"] { + width: 0px; + } + + .schema-close { + width: 32px; + height: 32px; + min-width: 32px; + max-width: 32px; + position: absolute; + padding: 4px; + border-radius: 6px; + cursor: pointer; + top: 17px; + right: 16px; + + &:hover { + background: var(--guardian-primary-background); + } + } + + .fields-container { + width: 400px; + height: 100%; + display: flex; + flex-direction: column; + + .schema-name { + width: 400px; + padding: 24px 24px 24px 24px; + font-size: 16px; + font-weight: 600; + line-height: 18px; + text-align: left; + color: var(--guardian-font-color, #23252E); + position: relative; + } + + .schema-config { + width: 100%; + height: 100%; + padding: 0px 24px 24px 24px; + } + + .field-list { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + + .field-search { + padding: 24px 0px; + width: 100%; + height: 88px; + min-height: 88px; + } + + .field-tree-container { + width: 100%; + height: 100%; + position: relative; + + .field-tree { + position: absolute; + overflow: auto; + top: 0; + bottom: 0; + left: 0; + right: 0; + transform: translateZ(0); + transform-origin: 0% 0%; + + .field-item { + width: 100%; + padding: 8px 0px; + font-family: Inter; + font-size: 14px; + font-weight: 500; + line-height: 18px; + text-align: left; + display: flex; + justify-content: flex-start; + align-items: center; + + .field-offset { + width: 0px; + height: 24px; + overflow: hidden; + } + + .field-collapse { + width: 24px; + min-width: 24px; + height: 24px; + overflow: hidden; + } + + .field-select { + width: 24px; + min-width: 24px; + height: 24px; + overflow: hidden; + } + + .field-name { + padding-left: 8px; + min-width: 125px; + cursor: pointer; + + &[readonly="true"] { + cursor: default; + } + } + + &[property="false"] { + opacity: 0.4; + } + + &[highlighted="true"] .field-name { + color: var(--guardian-primary-color, #4169E2); + font-weight: 600; + } + + &[search-highlighted="hidden"] { + display: none; + } + + &[search-highlighted="sub"] .field-name { + opacity: 0.5; + } + + &[search-highlighted="highlighted"] .field-name { + color: var(--guardian-success-color, #19BE47); + font-weight: 600; + } + + &[search-highlighted="highlighted"][highlighted="true"] .field-name { + color: var(--guardian-primary-color, #4169E2); + font-weight: 600; + } + } + } + } + } + + .rules-list { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + padding: 24px 0px; + + .schema-rule { + .schema-rule-name { + font-family: Inter; + font-size: 14px; + font-weight: 700; + line-height: 18px; + text-align: left; + color: var(--guardian-font-color, #23252E); + width: 100%; + padding-bottom: 16px; + } + + .schema-rule-values { + display: flex; + flex-direction: column; + width: 100%; + + .schema-rule-value { + display: flex; + flex-direction: row; + width: 100%; + height: 40px; + padding-bottom: 16px; + align-items: center; + cursor: pointer; + + .schema-rule-value-select { + width: 24px; + height: 24px; + } + + .schema-rule-value-name { + padding: 4px 0 4px 8px; + height: 24px; + font-family: Inter; + font-size: 14px; + font-weight: 500; + line-height: 16px; + text-align: left; + color: var(--guardian-font-color, #23252E); + cursor: pointer; + } + } + } + } + } + } + } + } + + .config-viewer { + padding: 32px 48px; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + overflow: auto; + + .variables-container { + box-shadow: 0px 4px 8px 0px var(--guardian-shadow, #00000014); + background: var(--guardian-background, #FFFFFF); + padding: 24px; + border-radius: 8px; + margin-bottom: 16px; + min-width: 1300px; + + .variables-header { + color: var(--guardian-font-color, #23252E); + font-size: 16px; + font-weight: 600; + line-height: 20px; + text-align: left; + padding-bottom: 16px; + } + + .variables-actions { + padding-top: 24px; + + button { + height: 28px; + width: 120px; + } + } + } + + .cell-option-values { + .cell-option-value { + width: 270px; + height: 30px; + padding: 7px 8px 7px 8px; + border-radius: 6px; + background: var(--guardian-grey-color, #EFF3F7); + font-family: Inter; + font-size: 14px; + font-weight: 400; + line-height: 16px; + text-align: left; + color: var(--guardian-disabled-color, #848FA9); + margin-bottom: 8px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + + &:last-child { + margin-bottom: 0px; + } + } + } + } + + .variables-empty-grid { + font-family: Inter; + font-size: 14px; + font-weight: 400; + color: var(--guardian-disabled-color, #848FA9); + height: 87px; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + } + + .variables-grid { + overflow: auto; + display: grid; + + .cell-48 { + min-width: 48px; + width: 48px; + max-width: 48px; + } + + .cell-56 { + min-width: 56px; + width: 56px; + max-width: 56px; + } + + .cell-64 { + min-width: 64px; + width: 64px; + max-width: 64px; + } + + .cell-84 { + min-width: 84px; + width: 84px; + max-width: 84px; + } + + .cell-90 { + min-width: 90px; + width: 90px; + max-width: 90px; + } + + .cell-125 { + min-width: 115px; + width: 115px; + max-width: 115px; + } + + .cell-130 { + min-width: 130px; + width: 130px; + max-width: 130px; + } + + .cell-150 { + min-width: 150px; + width: 150px; + max-width: 150px; + } + + .cell-200 { + min-width: 200px; + width: 200px; + max-width: 200px; + } + + .cell-250 { + min-width: 250px; + width: 250px; + max-width: 250px; + } + + .cell-300 { + min-width: 300px; + width: 300px; + max-width: 300px; + } + + .cell-56 { + min-width: 56px !important; + width: 56px !important; + max-width: 56px !important; + } + + .cell-max { + min-width: 250px; + width: auto; + } + + .cell-btn { + padding: 6px 0px 6px 16px !important; + display: flex; + justify-content: flex-end; + padding: 6px 6px 6px 16px !important; + + button { + width: 24px; + height: 24px; + margin-left: 16px; + + &:first-child { + margin-left: 0px; + } + } + } + + .cell-multiselect { + padding: 1px 10px !important; + overflow: hidden; + } + + .cell-select { + padding: 1px 1px !important; + + &::ng-deep { + .p-dropdown-label { + padding-left: 16px; + } + } + } + + .variables-grid-cell { + min-height: 40px; + padding: 6px 16px; + display: flex; + align-items: center; + width: 100%; + position: relative; + font-family: Inter; + font-size: 14px; + font-weight: 400; + text-align: left; + user-select: text; + + span { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + .cell-focus { + display: none; + position: absolute; + top: -1px; + left: -1px; + right: -1px; + bottom: -1px; + border: 1px solid var(--guardian-primary-color, #4169E2); + } + + &:first-child { + .cell-focus { + left: -9px; + } + } + + .p-inputwrapper-focus+.cell-focus, + .guardian-input:focus+.cell-focus { + display: block; + } + } + + .variables-grid-cell-transparent { + border-right: 1px solid transparent !important; + } + + .variables-grid-row { + &:first-child .variables-grid-cell:first-child .cell-focus { + border-top-left-radius: 8px; + } + + &:last-child .variables-grid-cell:first-child .cell-focus { + border-bottom-left-radius: 8px; + } + } + + .variables-grid-header { + display: flex; + flex-direction: row; + padding: 0px 8px; + width: 100%; + min-width: 100%; + + &>div { + color: var(--guardian-disabled-color, #848FA9); + font-family: Inter; + font-size: 12px; + font-weight: 400; + text-align: left; + } + } + + .variables-grid-body { + width: 100%; + min-width: 100%; + border: 1px solid var(--guardian-border-color, #E1E7EF); + border-radius: 8px; + + .variables-grid-row { + min-height: 40px; + display: flex; + flex-direction: row; + border-bottom: 1px solid var(--guardian-border-color, #E1E7EF); + padding: 0px 8px; + + &>div { + border-right: 1px solid var(--guardian-border-color, #E1E7EF); + + &:last-child { + border-right: none; + } + } + + &:last-child { + border-bottom: none; + } + } + + .rule-type-name { + background: #E1E7EF; + height: 28px; + padding: 4px 10px 4px 10px; + border-radius: 6px; + font-family: Inter; + font-size: 14px; + font-weight: 500; + line-height: 16px; + text-align: left; + color: #848FA9; + text-transform: capitalize; + display: flex; + justify-content: center; + align-items: center; + } + } + } + } +} + +.guardian-dropdown-cell { + &::ng-deep .p-dropdown { + width: 100%; + + .p-dropdown-label { + padding-left: 0px; + } + } +} + +.guardian-multiselect { + height: 100%; + + &::ng-deep .p-multiselect { + width: 100%; + height: 100%; + + .p-multiselect-label { + padding: 6px 16px 6px 8px; + } + } +} + +.tree-container { + display: flex; + + .tree-node { + background: var(--guardian-background, #FFFFFF); + border: 1px solid #bac0ce; + box-shadow: 0px 4px 4px 0px var(--guardian-shadow, #00000014); + border-radius: 6px; + width: 150px; + cursor: pointer; + overflow: hidden; + user-select: none; + + &.root-node { + .node-header { + background: #CAFDD9; + } + } + + &:hover { + border: 1px solid var(--guardian-primary-color, #4169E2); + } + + * { + pointer-events: none; + } + + &.selected-type-selected { + border: 1px solid var(--guardian-primary-color, #4169E2); + box-shadow: 0px 0px 0px 3px var(--guardian-primary-color, #4169E2), 0px 6px 6px 0px #00000021; + } + + &.selected-type-sub { + border: 1px solid var(--guardian-primary-color, #4169E2); + } + + &.selected-type-hidden { + opacity: 0.4; + } + + &[search-highlighted="true"] { + border: 1px solid var(--guardian-success-color, #19BE47); + box-shadow: 0px 0px 0px 3px var(--guardian-success-color, #19BE47), 0px 6px 6px 0px #00000021; + } + + &[search-highlighted="true"].selected-type-selected { + border: 1px solid var(--guardian-primary-color, #4169E2); + box-shadow: 0px 0px 0px 3px var(--guardian-primary-color, #4169E2), 0px 6px 6px 0px #00000021; + } + + .node-header { + width: 100%; + padding: 9px 8px 9px 16px; + background: var(--guardian-grey-background, #F9FAFC); + font-family: Inter; + font-size: 12px; + font-weight: 600; + line-height: 14px; + text-align: left; + color: #23252E; + } + + .node-fields { + border-top: 1px solid var(--guardian-border-color, #E1E7EF); + font-family: Inter; + font-size: 12px; + font-weight: 500; + line-height: 14px; + text-align: left; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + .node-field { + color: #23252E; + border-bottom: 1px solid var(--guardian-border-color, #E1E7EF); + padding: 8px 8px 8px 16px; + overflow: hidden; + text-overflow: ellipsis; + font-weight: 400; + } + + .node-not-fields { + color: var(--guardian-disabled-color, #848FA9); + border-bottom: 1px solid var(--guardian-border-color, #E1E7EF); + padding: 8px 8px 8px 16px; + overflow: hidden; + text-overflow: ellipsis; + font-weight: 400; + } + } + } +} + +.config-viewer ngx-codemirror { + width: 100%; +} + +.config-viewer ngx-codemirror::ng-deep { + .CodeMirror-gutters { + display: none; + } + + .cm-number { + color: #15ab3f; + font-weight: 600; + } + + .cm-formula-variable { + color: #4267df; + font-weight: 600; + } + + .cm-formula-function { + color: #FF9800; + font-weight: 600; + } +} + +.multiselect-option-value { + max-width: 300px; + white-space: normal; + padding-left: 8px; +} + +.multiselect-selected-value { + width: 210px; + height: 30px; + padding: 7px 8px 7px 8px; + border-radius: 6px; + background: var(--guardian-grey-color, #EFF3F7); + font-family: Inter; + font-size: 14px; + font-weight: 400; + line-height: 16px; + text-align: left; + color: var(--guardian-disabled-color, #848FA9); + margin-bottom: 8px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.multiselect-selected-value:last-child { + margin-bottom: 0px; +} + +*[codemirror-readonly="true"] { + pointer-events: none; +} \ No newline at end of file diff --git a/frontend/src/app/modules/statistics/policy-labels/policy-label-configuration/policy-label-configuration.component.ts b/frontend/src/app/modules/statistics/policy-labels/policy-label-configuration/policy-label-configuration.component.ts new file mode 100644 index 0000000000..7f02f5f185 --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/policy-label-configuration/policy-label-configuration.component.ts @@ -0,0 +1,318 @@ +import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { EntityStatus, IPolicyLabelConfig, UserPermissions } from '@guardian/interfaces'; +import { forkJoin, Subscription } from 'rxjs'; +import { ProfileService } from 'src/app/services/profile.service'; +import { SchemaService } from 'src/app/services/schema.service'; +import { DialogService } from 'primeng/dynamicdialog'; +import { IPFSService } from 'src/app/services/ipfs.service'; +import { PolicyLabelsService } from 'src/app/services/policy-labels.service'; +import { TreeDragDropService } from 'primeng/api'; +import { NavItem } from './components/nav-item'; +import { SearchLabelDialog } from '../dialogs/search-label-dialog/search-label-dialog.component'; +import { PolicyLabelPreviewDialog } from '../dialogs/policy-label-preview-dialog/policy-label-preview-dialog.component'; +import { LabelConfig } from './components/label-config'; +import { RulesConfig } from './components/rules-config'; +import { CustomCustomDialogComponent } from 'src/app/modules/common/custom-confirm-dialog/custom-confirm-dialog.component'; + +@Component({ + selector: 'app-policy-label-configuration', + templateUrl: './policy-label-configuration.component.html', + styleUrls: ['./policy-label-configuration.component.scss'], +}) +export class PolicyLabelConfigurationComponent implements OnInit { + public readonly title: string = 'Configuration'; + + public loading: boolean = true; + public isConfirmed: boolean = false; + public user: UserPermissions = new UserPermissions(); + public owner: string; + public definitionId: string; + public item: any | undefined; + public policy: any; + public readonly: boolean = false; + + private subscription = new Subscription(); + + public readonly labelConfig: LabelConfig; + public readonly rulesConfig: RulesConfig; + public readonly statusMenuItems = [{ + label: 'Publish', + icon: 'publish', + callback: ($event: any) => { + this.publish(); + } + }] + + @ViewChild('fieldTree', { static: false }) fieldTree: ElementRef; + @ViewChild('treeTabs', { static: false }) treeTabs: ElementRef; + + constructor( + private profileService: ProfileService, + private schemaService: SchemaService, + private policyLabelsService: PolicyLabelsService, + private router: Router, + private route: ActivatedRoute, + private ipfs: IPFSService, + private dialogService: DialogService, + private dragDropService: TreeDragDropService + ) { + this.labelConfig = new LabelConfig(dialogService, dragDropService); + this.rulesConfig = new RulesConfig(this, dialogService, ipfs); + } + + ngOnInit() { + this.subscription.add( + this.route.params.subscribe((queryParams) => { + this.loadProfile(); + }) + ); + this.subscription.add( + this.route.queryParams.subscribe((queryParams) => { + const index = queryParams.tab || 0; + this.labelConfig.goToStep(index).then(); + }) + ); + this.subscription.add( + this.labelConfig.step.subscribe((index) => { + this.loading = true; + this.router.navigate([], { + relativeTo: this.route, + queryParams: { + tab: String(index), + }, + queryParamsHandling: 'merge', + }); + setTimeout(() => { + this.labelConfig.goToStep(index).then(() => { + this.loading = false; + }) + }, 100); + }) + ); + this.subscription.add( + this.rulesConfig.step.subscribe((index) => { + this.loading = true; + setTimeout(() => { + this.rulesConfig.goToStep(index).then(() => { + this.loading = false; + }) + }, 100); + }) + ); + } + + ngOnDestroy(): void { + this.subscription.unsubscribe(); + } + + private loadProfile() { + this.isConfirmed = false; + this.loading = true; + this.profileService + .getProfile() + .subscribe((profile) => { + this.isConfirmed = !!(profile && profile.confirmed); + this.user = new UserPermissions(profile); + this.owner = this.user.did; + + if (this.isConfirmed) { + this.loadData(); + } else { + setTimeout(() => { + this.loading = false; + }, 500); + } + }, (e) => { + this.loading = false; + }); + } + + private loadData() { + this.definitionId = this.route.snapshot.params['definitionId']; + this.loading = true; + forkJoin([ + this.schemaService.properties(), + this.policyLabelsService.getLabel(this.definitionId), + this.policyLabelsService.getRelationships(this.definitionId), + ]).subscribe(([properties, item, relationships]) => { + this.item = item; + this.readonly = this.item?.status === EntityStatus.PUBLISHED; + this.policy = relationships?.policy || {}; + + this.rulesConfig.setPolicy(relationships); + this.rulesConfig.setProperties(properties); + this.rulesConfig.setSchemas(relationships); + + this.labelConfig.setPolicy(relationships); + this.labelConfig.setData(this.item); + this.labelConfig.show = true; + + setTimeout(() => { + this.loading = false; + }, 1000); + }, (e) => { + this.loading = false; + }); + } + + public onBack() { + this.router.navigate(['/policy-labels']); + } + + private getItem(): any { + const value = this.labelConfig.overviewForm.value; + const config: IPolicyLabelConfig = this.labelConfig.toJson(); + const item = { + ...this.item, + name: value.name, + description: value.description, + config + }; + return item; + } + + public onSave() { + this.loading = true; + const item = this.getItem(); + this.policyLabelsService + .updateLabel(item) + .subscribe((item) => { + this.item = item; + this.labelConfig.setData(this.item); + setTimeout(() => { + this.loading = false; + }, 1000); + }, (e) => { + this.loading = false; + }); + } + + public onPreview() { + const item = this.getItem(); + const dialogRef = this.dialogService.open(PolicyLabelPreviewDialog, { + showHeader: false, + header: 'Preview', + width: '1100px', + styleClass: 'guardian-dialog', + data: { + item + } + }); + dialogRef.onClose.subscribe(async (result) => { }); + } + + public onImport() { + const dialogRef = this.dialogService.open(SearchLabelDialog, { + showHeader: false, + width: '1100px', + styleClass: 'guardian-dialog', + data: { + ids: this.labelConfig.menu.getIds() + }, + }); + dialogRef.onClose.subscribe((result: any[]) => { + if (result) { + for (const item of result) { + if (item._type === 'label') { + this.labelConfig.menu.addLabel(item) + } + if (item._type === 'statistic') { + this.labelConfig.menu.addStatistic(item) + } + } + } + }); + } + + public onDeleteImport(item: NavItem) { + this.labelConfig.menu.delete(item); + } + + public onEditNavItem(node: NavItem) { + this.loading = true; + const item = this.getItem(); + this.rulesConfig.show = true; + this.rulesConfig.setData(node, item); + setTimeout(() => { + this.rulesConfig.goToStep(0).then(() => { + this.loading = false; + }) + }, 100); + } + + public onCancelNavItem() { + this.loading = true; + this.rulesConfig.show = false; + this.rulesConfig.onCancel(); + setTimeout(() => { + this.labelConfig.goToStep(2).then(() => { + this.loading = false; + }) + }, 100); + } + + public onSaveNavItem() { + this.loading = true; + this.rulesConfig.show = false; + this.rulesConfig.onSave(); + + const item = this.getItem(); + this.policyLabelsService + .updateLabel(item) + .subscribe((item) => { + this.item = item; + this.labelConfig.setData(this.item); + setTimeout(() => { + this.labelConfig.goToStep(2).then(() => { + this.loading = false; + }) + }, 1000); + }, (e) => { + this.loading = false; + }); + + // setTimeout(() => { + // this.labelConfig.goToStep(2).then(() => { + // this.loading = false; + // }) + // }, 100); + } + + private publish() { + const dialogRef = this.dialogService.open(CustomCustomDialogComponent, { + showHeader: false, + width: '640px', + styleClass: 'guardian-dialog', + data: { + header: 'Publish Label', + text: `Are you sure want to publish label (${this.item.name})?`, + buttons: [{ + name: 'Close', + class: 'secondary' + }, { + name: 'Publish', + class: 'primary' + }] + }, + }); + dialogRef.onClose.subscribe((result: string) => { + if (result === 'Publish') { + this.loading = true; + this.policyLabelsService.pushPublish(this.item).subscribe( + (result) => { + const { taskId, expectation } = result; + this.router.navigate(['task', taskId], { + queryParams: { + last: btoa(location.href), + }, + }); + }, + (e) => { + this.loading = false; + } + ); + } + }); + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/statistics/policy-labels/policy-label-document-configuration/policy-label-document-configuration.component.html b/frontend/src/app/modules/statistics/policy-labels/policy-label-document-configuration/policy-label-document-configuration.component.html new file mode 100644 index 0000000000..422c2b24db --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/policy-label-document-configuration/policy-label-document-configuration.component.html @@ -0,0 +1,318 @@ +
+
+
+
+ +
+ Before starting work you need to get DID + here +
+ +
+
+ +
+ +
+ {{item?.name}} +
+ Policy Name: {{policy.name}} + Version: {{policy.version}} +
+
+
+ +
+ +
+
+
+
+
+ + +
+
{{item.name}}
+
+
+
+
+
+
+ {{current.prefix}} + {{current.title}} +
+ +
+ +
{{item.index}}
+
{{item.name}}
+
+
+
+ + +
+ +
+ + + + + + {{column.title}} + + + + + {{column.title}} + + + + + + + + + + + + + + {{row.__schemaName}} + + + + {{row.__id}} + + + + {{getCellValue(row, column)}} + + + + + + + + {{getCellValue(row, column)}} + + + + + + +
+ +
+
+
+ +
+ + No Documents +
+
+
+ +
+ + +
+
+ {{variable.fieldDescription}} +
+ +
+
+ {{getVariableValue(v)}} +
+
+
+ +
+ {{getVariableValue(variable.value)}} +
+
+ +
+
+ + +
+
+
+
+ {{variable.fieldDescription}} +
+ +
+
+ {{getVariableValue(v)}} +
+
+
+ +
+ {{getVariableValue(variable.value)}} +
+
+
+
+
+ {{score.description}} +
+
+
+
+ +
+ +
+
+
+
+ + +
+
+ {{formula.description}} +
+
+ {{formula.value}} +
+
+
+ + + +
+
+
+ + +
+
+ Label created successfully. +
+
+
+
+
+
+ + +
+
+ Sorry, but your document does not meet the requirements. +
+
+
+
+
+
+ +
+
+ + + +
+
\ No newline at end of file diff --git a/frontend/src/app/modules/statistics/policy-labels/policy-label-document-configuration/policy-label-document-configuration.component.scss b/frontend/src/app/modules/statistics/policy-labels/policy-label-document-configuration/policy-label-document-configuration.component.scss new file mode 100644 index 0000000000..e5000e3ed3 --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/policy-label-document-configuration/policy-label-document-configuration.component.scss @@ -0,0 +1,381 @@ +.guardian-page { + position: relative; + padding: 0px; + user-select: none; + background: var(--guardian-grey-background, #F9FAFC); + + .header-container { + padding: 56px 48px 10px 48px; + background: var(--guardian-primary-color, #4169E2); + min-height: 178px; + height: 178px; + + .guardian-user-back-button { + button { + border-color: var(--guardian-background, #FFFFFF); + } + } + + .guardian-user-page-header { + color: var(--guardian-background, #FFFFFF); + } + + .policy-name { + color: var(--guardian-background, #FFFFFF); + font-size: 14px; + font-weight: 500; + line-height: 16px; + position: absolute; + top: 34px; + right: 0; + + .policy-version { + padding-left: 16px; + } + } + } + + .actions-container { + min-height: 64px; + height: 64px; + width: 100%; + display: flex; + justify-content: flex-end; + padding: 12px 48px; + background: var(--guardian-background, #FFFFFF); + border-top: 1px solid var(--guardian-border-color, #E1E7EF); + position: relative; + + button { + height: 40px; + width: 135px; + margin-left: 16px; + } + } + + .body-container { + display: flex; + width: 100%; + height: 100%; + overflow: auto; + position: relative; + padding: 24px 48px; + + .left-side { + min-width: 260px; + width: 260px; + max-width: 260px; + height: 100%; + margin-right: 24px; + } + + .config-menu { + border: 1px solid #E1E7EF; + border-radius: 8px; + padding: 8px 0px; + background: #FFFFFF; + box-shadow: 0px 4px 8px 0px #00000014; + overflow: hidden; + + .config-menu-item { + width: 258px; + height: 56px; + display: flex; + flex-direction: row; + padding-left: 22px; + align-items: center; + position: relative; + + &[highlighted="true"] { + background: #F0F3FC; + + .config-menu-item-name { + color: #4169E2; + } + } + + &[last="false"] { + margin-bottom: 24px; + + &::after { + content: ""; + display: block; + position: absolute; + left: 53px; + bottom: -22px; + width: 3px; + height: 20px; + border-left: 2px solid #C4D0E1; + pointer-events: none; + } + } + + .config-menu-item-status { + width: 8px; + height: 8px; + overflow: hidden; + border-radius: 50%; + background: #AAB7C4; + margin-right: 12px; + + &[status="true"] { + background: #19BE47; + } + + &[status="false"] { + background: #FF432A; + } + } + + .config-menu-item-icon { + width: 24px; + height: 24px; + overflow: hidden; + margin-right: 8px; + } + + .config-menu-item-name { + width: 160px; + overflow: hidden; + font-family: Inter; + font-size: 16px; + font-weight: 500; + line-height: 16px; + text-align: left; + color: #848FA9; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } + } + + .config-result { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + min-height: 250px; + + .config-result-item { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + } + + .config-result-icon { + width: 48px; + height: 48px; + padding: 8px; + overflow: hidden; + } + + .config-result-text { + font-family: Inter; + font-size: 16px; + font-weight: 600; + text-align: center; + color: #848FA9; + } + + &.config-result-valid { + .config-result-text { + color: #19BE47; + } + } + + &.config-result-invalid { + .config-result-text { + color: #FF432A; + } + } + } + + .config-node-container { + width: 100%; + + .guardian-grid-container { + &::before { + display: none !important; + } + } + + .config-node-body { + padding: 24px; + position: relative; + border-radius: 8px; + background: #FFFFFF; + box-shadow: 0px 4px 8px 0px #00000014; + border: 1px solid #E1E7EF; + + .config-node-header { + font-size: 24px; + font-weight: 600; + line-height: 32px; + text-align: left; + color: var(--guardian-font-color, #23252E); + margin-bottom: 24px; + + .config-node-prefix { + margin-right: 12px; + } + } + } + + .sub-indexes { + width: 100%; + height: 48px; + min-height: 48px; + max-height: 48px; + display: flex; + flex-direction: row; + justify-content: center; + background: #fff; + align-items: center; + margin-bottom: 16px; + + .sub-index { + width: 24px; + height: 24px; + min-width: 24px; + min-height: 24px; + background: var(--guardian-grey-color-3, #AAB7C4); + border-radius: 50%; + font-family: Inter; + font-size: 14px; + font-weight: 500; + line-height: 14px; + display: flex; + justify-content: center; + align-items: center; + color: #fff; + + &[action="true"] { + background: var(--guardian-primary-color, #4169E2); + } + } + + .sub-name { + font-family: Inter; + font-size: 14px; + font-weight: 500; + line-height: 14px; + text-align: left; + color: var(--guardian-grey-color-3, #AAB7C4); + padding-left: 8px; + + &[action="true"] { + color: var(--guardian-primary-color, #4169E2); + } + } + + .sub-index-separator { + width: 100%; + height: 12px; + position: relative; + + &::after { + content: ""; + display: block; + position: absolute; + left: 15px; + right: 16px; + top: 5px; + height: 3px; + border-top: 1px solid #C4D0E1; + pointer-events: none; + } + } + } + + .field-container { + margin-bottom: 16px; + + .field-name { + font-family: Inter; + font-size: 12px; + font-weight: 500; + line-height: 14px; + color: #181818; + margin-bottom: 6px; + } + + .field-value { + width: 100%; + height: 40px; + border-radius: 8px; + border: 1px solid #E1E7EF; + background: #F9FAFC; + padding: 12px 16px; + font-family: Inter; + font-size: 14px; + font-weight: 400; + line-height: 14px; + color: #23252E; + margin-bottom: 8px; + } + + .field-value-array {} + } + + .score-container { + border: 1px solid #AAB7C4; + border-radius: 8px; + padding: 24px; + margin-bottom: 16px; + + .fields-container { + margin-bottom: 24px; + } + + .score-name { + font-family: Inter; + font-size: 14px; + font-weight: 700; + line-height: 18px; + text-align: left; + color: #000000; + margin-bottom: 24px; + } + + .options-container { + .option-container { + display: flex; + flex-direction: row; + + border-radius: 6px; + + &:not([disabled]) { + cursor: pointer; + } + + &:not([disabled]):hover { + background: var(--guardian-hover, #F0F3FC); + } + + .option-checkbox { + cursor: pointer; + min-height: 40px; + width: 42px; + min-width: 42px; + display: flex; + justify-content: flex-start; + align-items: center; + padding-left: 4px; + } + + .option-name { + cursor: pointer; + min-height: 40px; + width: 100%; + display: flex; + justify-content: flex-start; + align-items: center; + } + } + } + } + } + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/statistics/policy-labels/policy-label-document-configuration/policy-label-document-configuration.component.ts b/frontend/src/app/modules/statistics/policy-labels/policy-label-document-configuration/policy-label-document-configuration.component.ts new file mode 100644 index 0000000000..d35e7c1801 --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/policy-label-document-configuration/policy-label-document-configuration.component.ts @@ -0,0 +1,396 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { IValidateStatus, IValidatorNode, IValidatorStep, LabelValidators, Schema, UserPermissions } from '@guardian/interfaces'; +import { forkJoin, Subscription } from 'rxjs'; +import { ProfileService } from 'src/app/services/profile.service'; +import { SchemaService } from 'src/app/services/schema.service'; +import { DialogService } from 'primeng/dynamicdialog'; +import { IDocument } from '../../../common/models/assessment'; +import { IColumn } from '../../../common/models/grid'; +import { PolicyLabelsService } from 'src/app/services/policy-labels.service'; + +@Component({ + selector: 'app-policy-label-document-configuration', + templateUrl: './policy-label-document-configuration.component.html', + styleUrls: ['./policy-label-document-configuration.component.scss'], +}) +export class PolicyLabelDocumentConfigurationComponent implements OnInit { + public readonly title: string = 'Configuration'; + + public loading: boolean = true; + public isConfirmed: boolean = false; + public user: UserPermissions = new UserPermissions(); + public owner: string; + public policy: any; + public definitionId: string; + public item: any; + + public documents: any[]; + public documentsCount: number; + public document: any | null; + public relationships: any; + public pageIndex: number; + public pageSize: number; + + public validator: LabelValidators; + public tree: any; + public steps: any[]; + public current: IValidatorStep | null; + public menu: IValidatorNode[]; + public result: IValidateStatus | undefined; + + public status: boolean | undefined = undefined; + + public defaultColumns: IColumn[] = [{ + id: 'checkbox', + title: '', + type: 'text', + size: '56', + minSize: '56', + tooltip: false + }, { + id: 'id', + title: 'ID', + type: 'text', + size: 'auto', + minSize: '150', + tooltip: false + }]; + public columns: IColumn[] = [{ + id: 'tokenId', + title: 'Token ID', + type: 'text', + size: 'auto', + minSize: '150', + tooltip: false + }, { + id: 'date', + title: 'Date', + type: 'text', + size: 'auto', + minSize: '150', + tooltip: false + }, { + id: 'amount', + title: 'Amount', + type: 'text', + size: 'auto', + minSize: '150', + tooltip: false + }]; + public userColumns: any[] = []; + public schemas = new Map(); + + private subscription = new Subscription(); + + constructor( + private profileService: ProfileService, + private policyLabelsService: PolicyLabelsService, + private router: Router, + private route: ActivatedRoute + ) { + } + + ngOnInit() { + this.documents = []; + this.pageIndex = 0; + this.pageSize = 10; + this.documentsCount = 0; + this.subscription.add( + this.route.queryParams.subscribe((queryParams) => { + this.loadProfile(); + }) + ); + } + + ngOnDestroy(): void { + this.subscription.unsubscribe(); + } + + private loadProfile() { + this.isConfirmed = false; + this.loading = true; + this.profileService + .getProfile() + .subscribe((profile) => { + this.isConfirmed = !!(profile && profile.confirmed); + this.user = new UserPermissions(profile); + this.owner = this.user.did; + + if (this.isConfirmed) { + this.loadData(); + } else { + setTimeout(() => { + this.loading = false; + }, 500); + } + }, (e) => { + this.loading = false; + }); + } + + private loadData() { + this.definitionId = this.route.snapshot.params['definitionId']; + this.loading = true; + forkJoin([ + this.policyLabelsService.getLabel(this.definitionId), + this.policyLabelsService.getRelationships(this.definitionId), + ]).subscribe(([item, relationships]) => { + this.item = item; + this.policy = relationships?.policy || {}; + this.loadDocuments(); + }, (e) => { + this.loading = false; + }); + } + + private loadDocuments() { + this.loading = true; + this.policyLabelsService + .getTokens(this.definitionId) + .subscribe((documents) => { + const { page, count } = this.policyLabelsService.parsePage(documents); + this.documents = page; + this.documentsCount = count; + this.document = null; + this.updateDocuments(); + this.updateMetadata(); + setTimeout(() => { + this.loading = false; + }, 1000); + }, (e) => { + this.loading = false; + }); + } + + public updateDocuments() { + for (const doc of this.documents) { + doc.__id = doc.messageId; + doc.__cols = new Map(); + } + } + + private updateMetadata() { + this.validator = new LabelValidators(this.item); + this.tree = this.validator.getTree(); + this.steps = this.validator.getSteps(); + + this.addDefaultSteps(); + + this.menu = [] + for (const child of this.tree.children) { + this.createMenu(child, this.menu); + } + this.current = this.validator.start(); + } + + private addDefaultSteps() { + this.tree.children.unshift({ + name: 'Target', + item: this, + selectable: true, + type: 'target', + children: [] + }) + this.steps.unshift({ + name: 'Target', + title: 'Target', + item: this, + type: 'target', + config: null, + auto: false, + disabled: true, + update: this.onTarget.bind(this), + validate: this.onTarget.bind(this), + }) + + this.tree.children.push({ + name: 'Result', + item: this.validator, + selectable: true, + type: 'result', + children: [] + }) + this.steps.push({ + name: 'Result', + title: 'Result', + item: this.validator, + type: 'result', + config: this.validator, + auto: false, + update: this.onResult.bind(this), + validate: this.onResult.bind(this), + }) + } + + private createMenu(node: IValidatorNode, result: any[]) { + if (node.type === 'group') { + node.icon = 'folder'; + } else if (node.type === 'rules') { + node.icon = 'file'; + } else if (node.type === 'label') { + node.icon = 'folder'; + } else if (node.type === 'statistic') { + node.icon = 'file'; + } else if (node.type === 'target') { + node.icon = 'list'; + } else if (node.type === 'result') { + node.icon = 'publish'; + } + result.push(node); + for (const child of node.children) { + this.createMenu(child, result); + } + return result; + } + + private onTarget() { + return; + } + + private onResult() { + this.result = this.validator.getStatus(); + } + + public isSelected(menuItem: any): boolean { + return menuItem.item === this.current?.item; + } + + public onPrev(): void { + this.current = this.validator.prev(); + this.updateStep(); + } + + public onNext(): void { + if (this.current?.type === 'target') { + this.loading = true; + this.policyLabelsService + .getTokenDocuments(this.document.id, this.definitionId) + .subscribe((documents) => { + this.relationships = documents?.relatedDocuments || []; + this.validator.setData(documents?.relatedDocuments || []); + this.current = this.validator.next(); + setTimeout(() => { + this.loading = false; + }, 1000); + }, (e) => { + this.loading = false; + }); + } else { + this.current = this.validator.next(); + } + this.updateStep(); + } + + public onSubmit() { + const result = this.validator.getResult(); + const item = { + target: this.document.id, + documents: result + } + this.loading = true; + this.policyLabelsService + .createLabelDocument(this.definitionId, item) + .subscribe((row) => { + this.router.navigate([ + '/policy-labels', + this.definitionId, + 'documents', + row.id + ]); + setTimeout(() => { + this.loading = false; + }, 1000); + }, (e) => { + this.loading = false; + }); + } + + public onSelectDocument(item: IDocument) { + this.document = item; + this.steps[0].disabled = !this.document; + this.status = this.document ? true : undefined; + } + + public onPage(event: any): void { + if (this.pageSize != event.pageSize) { + this.pageIndex = 0; + this.pageSize = event.pageSize; + } else { + this.pageIndex = event.pageIndex; + this.pageSize = event.pageSize; + } + this.loadDocuments(); + } + + public getCellValue(row: IDocument, column: IColumn): any { + if (row.__cols.has(column)) { + return row.__cols.get(column); + } else { + let value: any; + if (typeof column.id === 'string') { + value = this.getFieldValue(row, [column.id]); + } else { + value = this.getFieldValue(row, column.id); + } + if (Array.isArray(value)) { + value = `[${value.join(',')}]`; + } + row.__cols.set(column, value); + return value; + } + } + + private getFieldValue(document: any, fullPath: string[]): any { + if (!document) { + return null; + } + + let vc = document?.document?.verifiableCredential; + if (Array.isArray(vc)) { + vc = vc[vc.length - 1]; + } + let cs: any = vc?.credentialSubject; + if (Array.isArray(cs)) { + cs = cs[0]; + } + let value: any = cs; + for (let i = 0; i < fullPath.length; i++) { + if (value) { + value = value[fullPath[i]] + } else { + return undefined; + } + } + return value; + } + + public onScore() { + this.updateStep(); + } + + public updateStep() { + if (this.current?.type === 'scores') { + let valid = true; + if (Array.isArray(this.current.config)) { + for (const score of this.current.config) { + let validScore = score.value !== undefined; + valid = valid && validScore; + } + } + this.current.disabled = !valid; + } + } + + public getVariableValue(value: any): any { + if (value === undefined) { + return 'N/A'; + } else { + return value; + } + } + + public onBack() { + this.router.navigate(['/policy-labels']); + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/statistics/policy-labels/policy-label-document-view/policy-label-document-view.component.html b/frontend/src/app/modules/statistics/policy-labels/policy-label-document-view/policy-label-document-view.component.html new file mode 100644 index 0000000000..dfc4f7a11a --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/policy-label-document-view/policy-label-document-view.component.html @@ -0,0 +1,339 @@ +
+
+
+
+ +
+ Before starting work you need to get DID + here +
+ +
+
+ +
+ +
+ {{definition?.name}} +
+ Policy Name: {{policy.name}} + Version: {{policy.version}} +
+
+
+ +
+
+
+
+
+ + +
+
Overview
+
+
+
+
+ + +
+
Document
+
+
+
+
+ + +
+
Relationships
+
+
+
+
+ +
+ +
+
+
+ + + + + +
+
\ No newline at end of file diff --git a/frontend/src/app/modules/statistics/policy-labels/policy-label-document-view/policy-label-document-view.component.scss b/frontend/src/app/modules/statistics/policy-labels/policy-label-document-view/policy-label-document-view.component.scss new file mode 100644 index 0000000000..8fee50754a --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/policy-label-document-view/policy-label-document-view.component.scss @@ -0,0 +1,459 @@ +.guardian-page { + position: relative; + padding: 0px; + user-select: none; + background: var(--guardian-grey-background, #F9FAFC); + + .header-container { + padding: 56px 48px 10px 48px; + background: var(--guardian-primary-color, #4169E2); + min-height: 178px; + height: 178px; + + .guardian-user-back-button { + width: 200px; + + button { + border-color: var(--guardian-background, #FFFFFF); + } + } + + .guardian-user-page-header { + color: var(--guardian-background, #FFFFFF); + } + + + .policy-name { + color: var(--guardian-background, #FFFFFF); + font-size: 14px; + font-weight: 500; + line-height: 16px; + position: absolute; + top: 34px; + right: 0; + + .policy-version { + padding-left: 16px; + } + } + } + + .step-container { + min-height: 40px; + height: 40px; + width: 100%; + display: flex; + justify-content: center; + padding: 0px; + background: var(--guardian-background, #FFFFFF); + border-bottom: 1px solid var(--guardian-border-color, #E1E7EF); + position: relative; + box-shadow: 0px 4px 8px 0px var(--guardian-shadow, #00000014); + } + + .body-container { + display: flex; + width: 100%; + height: 100%; + overflow: hidden; + position: relative; + } + + .nav-body-container { + display: flex; + width: 100%; + height: 100%; + padding: 24px 48px 24px 48px; + overflow: auto; + position: relative; + + .step-body-container { + display: flex; + width: 100%; + min-height: 100px; + height: fit-content; + border-radius: 8px; + box-shadow: 0px 4px 8px 0px var(--guardian-shadow, #00000014); + background: var(--guardian-background, #FFFFFF); + padding: 24px; + flex-direction: column; + margin-bottom: 16px; + + .step-body-header { + font-size: 24px; + font-weight: 600; + line-height: 32px; + text-align: left; + color: var(--guardian-font-color, #23252E); + margin-bottom: 24px; + } + } + } + + .node-container { + .node-header { + font-size: 24px; + font-weight: 600; + line-height: 32px; + text-align: left; + color: var(--guardian-font-color, #23252E); + margin-bottom: 4px; + + .node-prefix { + margin-right: 12px; + } + } + + .node-sub-header { + font-size: 20px; + font-weight: 600; + line-height: 32px; + text-align: left; + color: var(--guardian-font-color, #23252E); + margin-bottom: 4px; + } + + .node-status { + width: 8px; + height: 8px; + overflow: hidden; + border-radius: 50%; + background: #AAB7C4; + display: inline-block; + margin: 3px 12px 3px 0px; + + &[status="true"] { + background: #19BE47; + } + + &[status="false"] { + background: #FF432A; + } + } + + .node-body { + padding-top: 12px; + padding-bottom: 16px; + } + + .field-container { + margin-bottom: 16px; + + .field-name { + font-family: Inter; + font-size: 12px; + font-weight: 500; + line-height: 14px; + color: #181818; + margin-bottom: 6px; + } + + .field-value { + width: 100%; + height: 40px; + border-radius: 8px; + border: 1px solid #E1E7EF; + background: #F9FAFC; + padding: 12px 16px; + font-family: Inter; + font-size: 14px; + font-weight: 400; + line-height: 14px; + color: #23252E; + margin-bottom: 8px; + } + } + } + + .fields-container { + .field-container { + margin-bottom: 16px; + + .field-name { + font-family: Inter; + font-size: 12px; + font-weight: 500; + line-height: 14px; + text-align: left; + color: var(--guardian-font-color, #23252E); + padding: 8px 0px; + } + + .field-value { + padding: 12px 16px 12px 16px; + border-radius: 8px; + border: 1px solid var(--guardian-border-color, #E1E7EF); + background: var(--guardian-grey-background, #F9FAFC); + font-family: Inter; + font-size: 14px; + font-weight: 400; + line-height: 16px; + text-align: left; + color: var(--guardian-font-color, #23252E); + width: 100%; + min-height: 42px; + overflow: hidden; + text-overflow: ellipsis; + user-select: text; + } + + .field-value-array { + .field-value { + margin-bottom: 16px; + + &:last-child { + margin-bottom: 0px; + } + } + } + } + } + + .scores-container { + .score-container { + border: 1px solid var(--guardian-grey-color-3, #AAB7C4); + padding: 24px; + border-radius: 8px; + margin-bottom: 24px; + + .score-name { + font-family: Inter; + font-size: 14px; + font-weight: 700; + line-height: 18px; + text-align: left; + color: var(--guardian-font-color, #23252E); + margin-bottom: 16px; + padding: 8px 0px; + } + } + } + + .options-container { + .option-container { + display: flex; + flex-direction: row; + + border-radius: 6px; + + &:not([disabled]) { + cursor: pointer; + } + + &:not([disabled]):hover { + background: var(--guardian-hover, #F0F3FC); + } + + .option-checkbox { + cursor: pointer; + min-height: 40px; + width: 42px; + min-width: 42px; + display: flex; + justify-content: flex-start; + align-items: center; + padding-left: 4px; + } + + .option-name { + cursor: pointer; + min-height: 40px; + width: 100%; + display: flex; + justify-content: flex-start; + align-items: center; + } + } + } +} + +.tree-container { + position: absolute; + left: 0px; + top: 0px; + bottom: 0px; + right: 0px; + z-index: 1; + user-select: none; + display: flex; + + .tree-node { + background: var(--guardian-background, #FFFFFF); + border: 1px solid #bac0ce; + box-shadow: 0px 4px 4px 0px var(--guardian-shadow, #00000014); + border-radius: 6px; + width: 150px; + cursor: pointer; + overflow: hidden; + user-select: none; + + &.root-node { + .node-header { + background: #CAFDD9; + } + } + + &:hover { + border: 1px solid var(--guardian-primary-color, #4169E2); + } + + * { + pointer-events: none; + } + + &.selected-type-selected { + border: 1px solid var(--guardian-primary-color, #4169E2); + box-shadow: 0px 0px 0px 3px var(--guardian-primary-color, #4169E2), 0px 6px 6px 0px #00000021; + } + + &.selected-type-sub { + border: 1px solid var(--guardian-primary-color, #4169E2); + } + + &.selected-type-hidden { + opacity: 0.4; + } + + &[search-highlighted="true"] { + border: 1px solid var(--guardian-success-color, #19BE47); + box-shadow: 0px 0px 0px 3px var(--guardian-success-color, #19BE47), 0px 6px 6px 0px #00000021; + } + + &[search-highlighted="true"].selected-type-selected { + border: 1px solid var(--guardian-primary-color, #4169E2); + box-shadow: 0px 0px 0px 3px var(--guardian-primary-color, #4169E2), 0px 6px 6px 0px #00000021; + } + + .node-header { + width: 100%; + padding: 9px 8px 9px 16px; + background: var(--guardian-grey-background, #F9FAFC); + font-family: Inter; + font-size: 12px; + font-weight: 600; + line-height: 14px; + text-align: left; + color: var(--guardian-font-color, #23252E); + } + } +} + +.zoom-toolbar { + width: 48px; + height: 160px; + position: absolute; + right: 400px; + top: 0px; + z-index: 3; + padding: 16px 16px 4px 4px; + overflow: hidden; + + -webkit-transition: right 0.2s ease-in-out; + -moz-transition: right 0.2s ease-in-out; + -o-transition: right 0.2s ease-in-out; + transition: right 0.2s ease-in-out; + + &[hidden-schema="true"] { + right: 0px; + } + + .zoom-button { + width: 28px; + height: 28px; + margin-bottom: 8px; + border-radius: 8px; + background: var(--guardian-background, #FFFFFF); + box-shadow: 0px 0px 1px 3px var(--guardian-grey-color, #EFF3F7); + + .zoom-label { + width: 28px; + height: 28px; + border-radius: 8px; + font-family: Inter; + font-size: 8px; + font-weight: 700; + color: var(--guardian-disabled-color, #848FA9); + border: 1px solid var(--guardian-border-color, #E1E7EF); + display: flex; + justify-content: center; + align-items: center; + } + + button { + width: 28px; + height: 28px; + border: 1px solid var(--guardian-primary-color, #4169E2); + border-radius: 8px; + } + } +} + +.schema-fields { + position: absolute; + top: 0px; + bottom: 0px; + right: 0px; + width: 400px; + z-index: 3; + background: var(--guardian-background, #FFFFFF); + border-left: 1px solid var(--guardian-border-color, #E1E7EF); + box-shadow: 0px 0px 4px 0px var(--guardian-shadow, #00000014); + overflow: hidden; + -webkit-transition: width 0.2s ease-in-out; + -moz-transition: width 0.2s ease-in-out; + -o-transition: width 0.2s ease-in-out; + transition: width 0.2s ease-in-out; + transform: translateZ(0); + will-change: transform, width; + + &[hidden-schema="true"] { + width: 0px; + } + + .schema-close { + width: 32px; + height: 32px; + min-width: 32px; + max-width: 32px; + position: absolute; + padding: 4px; + border-radius: 6px; + cursor: pointer; + top: 17px; + right: 16px; + + &:hover { + background: var(--guardian-primary-background); + } + } + + .schema-fields-container { + width: 400px; + height: 100%; + display: flex; + flex-direction: column; + + .schema-name { + width: 400px; + padding: 24px 24px 24px 24px; + font-size: 16px; + font-weight: 600; + line-height: 18px; + text-align: left; + color: var(--guardian-font-color, #23252E); + position: relative; + } + + .schema-config { + width: 100%; + height: 100%; + padding: 0px 24px 24px 24px; + } + + .guardian-button { + height: 32px; + width: 155px; + margin-top: 10px; + } + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/statistics/policy-labels/policy-label-document-view/policy-label-document-view.component.ts b/frontend/src/app/modules/statistics/policy-labels/policy-label-document-view/policy-label-document-view.component.ts new file mode 100644 index 0000000000..256a360f75 --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/policy-label-document-view/policy-label-document-view.component.ts @@ -0,0 +1,317 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { GenerateUUIDv4, IValidatorStep, LabelValidators, Schema, UserPermissions } from '@guardian/interfaces'; +import { forkJoin, Subscription } from 'rxjs'; +import { ProfileService } from 'src/app/services/profile.service'; +import { DialogService } from 'primeng/dynamicdialog'; +import { IFormula, IOption, IScore, IVariable } from '../../../common/models/assessment'; +import { TreeSource } from '../../../common/tree-graph/tree-source'; +import { TreeGraphComponent } from '../../../common/tree-graph/tree-graph.component'; +import { DocumentNode, SchemaData } from '../../../common/models/schema-node'; +import { TreeNode } from '../../../common/tree-graph/tree-node'; +import { VCViewerDialog } from '../../../schema-engine/vc-dialog/vc-dialog.component'; +import { PolicyLabelsService } from 'src/app/services/policy-labels.service'; + +@Component({ + selector: 'app-policy-label-document-view', + templateUrl: './policy-label-document-view.component.html', + styleUrls: ['./policy-label-document-view.component.scss'], +}) +export class PolicyLabelDocumentViewComponent implements OnInit { + public readonly title: string = 'Document'; + + public loading: boolean = true; + public navLoading: boolean = false; + public nodeLoading: boolean = true; + public isConfirmed: boolean = false; + public user: UserPermissions = new UserPermissions(); + public owner: string; + public definitionId: string; + public documentId: string; + public definition: any; + public policy: any; + public policySchemas: any[]; + public documentsSchemas: any[]; + public document: any; + public target: any; + public relationships: any; + public schemasMap: Map; + public stepper = [true, false, false]; + + public token: any; + + public preview: IVariable[]; + public scores: IScore[]; + public formulas: IFormula[]; + + public tree: TreeGraphComponent; + public nodes: DocumentNode[]; + public source: TreeSource; + public selectedNode: DocumentNode; + + public steps: IValidatorStep[]; + + private subscription = new Subscription(); + + public get zoom(): number { + if (this.tree) { + return Math.round(this.tree.zoom * 100); + } else { + return 100; + } + } + + private validator: LabelValidators; + + constructor( + private profileService: ProfileService, + private policyLabelsService: PolicyLabelsService, + private dialogService: DialogService, + private router: Router, + private route: ActivatedRoute + ) { + + } + + ngOnInit() { + this.subscription.add( + this.route.queryParams.subscribe((queryParams) => { + this.loadProfile(); + }) + ); + } + + ngOnDestroy(): void { + this.subscription.unsubscribe(); + } + + private loadProfile() { + this.isConfirmed = false; + this.loading = true; + this.profileService + .getProfile() + .subscribe((profile) => { + this.isConfirmed = !!(profile && profile.confirmed); + this.user = new UserPermissions(profile); + this.owner = this.user.did; + + if (this.isConfirmed) { + this.loadData(); + } else { + setTimeout(() => { + this.loading = false; + }, 500); + } + }, (e) => { + this.loading = false; + }); + } + + private loadData() { + this.definitionId = this.route.snapshot.params['definitionId']; + this.documentId = this.route.snapshot.params['documentId']; + this.loading = true; + forkJoin([ + this.policyLabelsService.getLabel(this.definitionId), + this.policyLabelsService.getRelationships(this.definitionId), + this.policyLabelsService.getLabelDocument(this.definitionId, this.documentId), + this.policyLabelsService.getLabelDocumentRelationships(this.definitionId, this.documentId) + ]).subscribe(([ + definition, + definitionRelationships, + document, + documentRelationships + ]) => { + this.definition = definition; + this.policy = definitionRelationships?.policy || {}; + this.policySchemas = definitionRelationships?.policySchemas || []; + this.documentsSchemas = definitionRelationships?.documentsSchemas || []; + this.document = document || {}; + this.target = documentRelationships?.target || {}; + this.relationships = documentRelationships?.relationships || []; + this.updateMetadata(); + setTimeout(() => { + this.loading = false; + }, 500); + }, (e) => { + this.loading = false; + }); + } + + public onBack() { + this.router.navigate([ + '/policy-labels', + this.definitionId, + 'documents' + ]); + } + + private updateMetadata() { + this.token = this.getMintVc(); + + this.validator = new LabelValidators(this.definition); + this.steps = this.validator.getDocument(); + this.validator.setData(this.relationships); + this.validator.setVp(this.document); + + // + this.schemasMap = new Map(); + for (const schema of this.policySchemas) { + try { + const item = new Schema(schema); + this.schemasMap.set(item.iri || item.id, item) + } catch (error) { + console.log(error); + } + } + for (const schema of this.documentsSchemas) { + try { + const item = new Schema(schema); + this.schemasMap.set(item.iri || item.id, item) + } catch (error) { + console.log(error); + } + } + + // + let root: DocumentNode | null = null; + let target: DocumentNode | null = null; + this.nodes = []; + + if (this.document) { + this.document.schemaName = 'Label'; + this.document.document.issuer = this.document.document?.proof?.verificationMethod?.split('#')?.[0]; + root = DocumentNode.from(this.document, 'root'); + root.entity = 'vp'; + this.nodes.push(root); + } + if (root && this.target) { + this.target.schemaName = 'Token' + this.target.document.issuer = this.target.document?.proof?.verificationMethod?.split('#')?.[0]; + target = DocumentNode.from(this.target, 'sub'); + target.entity = 'vp'; + this.nodes.push(target); + root.addId(target.id); + } + if (target && this.relationships) { + for (const item of this.relationships) { + item.schemaName = this.schemasMap.get(item.schema)?.name || item.schema; + const node = DocumentNode.from(item, 'sub'); + if (node.id !== target.id) { + this.nodes.push(node); + target.addId(node.id); + } + } + } + this.source = new TreeSource(this.nodes); + if (this.tree) { + this.tree.setData(this.source); + this.tree.move(18, 46); + } + } + + public getVariableValue(value: any): any { + if (value === undefined) { + return 'N/A'; + } else { + return value; + } + } + + private getMintVc() { + let data: any = this.target?.document?.verifiableCredential; + if (Array.isArray(data)) { + data = data[data.length - 1]; + } + data = data?.credentialSubject; + if (Array.isArray(data)) { + data = data[0]; + } + return data + } + + public onStep(index: number) { + this.navLoading = true; + for (let i = 0; i < this.stepper.length; i++) { + this.stepper[i] = false; + } + this.stepper[index] = true; + setTimeout(() => { + this.tree?.move(18, 46); + setTimeout(() => { + this.navLoading = false; + }, 200); + }, 200); + } + + public initTree($event: TreeGraphComponent) { + this.tree = $event; + if (this.nodes) { + this.tree.setData(this.source); + this.tree.move(18, 46); + } + } + + public createNodes($event: any) { + this.tree.move(18, 46); + } + + public onSelectNode(node: TreeNode | null) { + this.nodeLoading = true; + this.selectedNode = node as DocumentNode; + setTimeout(() => { + this.nodeLoading = false; + }, 350); + } + + public onZoom(d: number) { + if (this.tree) { + this.tree.onZoom(d); + if (d === 0) { + this.tree.move(18, 46); + } + } + } + + public onClearNode() { + this.tree?.onSelectNode(null); + } + + public openVCDocument(document: any) { + const dialogRef = this.dialogService.open(VCViewerDialog, { + showHeader: false, + width: '1000px', + styleClass: 'guardian-dialog', + data: { + id: document.id, + row: document, + dryRun: false, + document: document.document, + title: document.schemaName || 'VC Document', + type: 'VC', + viewDocument: true, + // schema: this.schema + } + }); + dialogRef.onClose.subscribe(async (result) => { }); + } + + public openVPDocument(document: any) { + const dialogRef = this.dialogService.open(VCViewerDialog, { + showHeader: false, + width: '1000px', + styleClass: 'guardian-dialog', + data: { + id: document.id, + row: document, + dryRun: false, + document: document.document, + title: 'VP Document', + type: 'VP', + viewDocument: true, + // schema: this.schema + } + }); + dialogRef.onClose.subscribe(async (result) => { }); + } +} diff --git a/frontend/src/app/modules/statistics/policy-labels/policy-label-documents/policy-label-documents.component.html b/frontend/src/app/modules/statistics/policy-labels/policy-label-documents/policy-label-documents.component.html new file mode 100644 index 0000000000..359e2024b0 --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/policy-label-documents/policy-label-documents.component.html @@ -0,0 +1,77 @@ +
+
+
+
+ +
+ Before starting work you need to get DID + here +
+ +
+ +
+ +
+ {{title}} +
+ Policy Name: {{policy.name}} + Version: {{policy.version}} +
+
+ +
+ +
+ + + + + {{column.title}} + + + + + + + + + + + + {{row[column.id]}} + + + + + + +
+ +
+
+
+ +
+ + There were no label created yet + Please create new label to see the data +
+
+
+
\ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessments/statistic-assessments.component.scss b/frontend/src/app/modules/statistics/policy-labels/policy-label-documents/policy-label-documents.component.scss similarity index 100% rename from frontend/src/app/modules/policy-statistics/statistic-assessments/statistic-assessments.component.scss rename to frontend/src/app/modules/statistics/policy-labels/policy-label-documents/policy-label-documents.component.scss diff --git a/frontend/src/app/modules/statistics/policy-labels/policy-label-documents/policy-label-documents.component.ts b/frontend/src/app/modules/statistics/policy-labels/policy-label-documents/policy-label-documents.component.ts new file mode 100644 index 0000000000..43bab0a188 --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/policy-label-documents/policy-label-documents.component.ts @@ -0,0 +1,177 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { UserPermissions } from '@guardian/interfaces'; +import { forkJoin, Subscription } from 'rxjs'; +import { ProfileService } from 'src/app/services/profile.service'; +import { PolicyLabelsService } from 'src/app/services/policy-labels.service'; + +interface IColumn { + id: string; + title: string; + type: string; + size: string; + tooltip: boolean; + permissions?: (user: UserPermissions) => boolean; + canDisplay?: () => boolean; +} + +@Component({ + selector: 'app-policy-label-documents', + templateUrl: './policy-label-documents.component.html', + styleUrls: ['./policy-label-documents.component.scss'], +}) +export class PolicyLabelDocumentsComponent implements OnInit { + public readonly title: string = 'Documents'; + + public loading: boolean = true; + public isConfirmed: boolean = false; + public user: UserPermissions = new UserPermissions(); + public owner: string; + public page: any[]; + public pageIndex: number; + public pageSize: number; + public pageCount: number; + public definitionId: string; + public definition: any; + public columns: IColumn[]; + public policy: any; + + private subscription = new Subscription(); + + constructor( + private profileService: ProfileService, + private policyLabelsService: PolicyLabelsService, + private router: Router, + private route: ActivatedRoute + ) { + this.columns = [{ + id: 'definition', + title: 'Definition', + type: 'text', + size: 'auto', + tooltip: false + }, { + id: 'policy', + title: 'Policy', + type: 'text', + size: 'auto', + tooltip: false + }, { + id: 'topicId', + title: 'Topic', + type: 'text', + size: '150', + tooltip: false + }, { + id: 'target', + title: 'Target', + type: 'text', + size: '220', + tooltip: false + }, { + id: 'messageId', + title: 'Message ID', + type: 'text', + size: '220', + tooltip: false + }, { + id: 'options', + title: '', + type: 'text', + size: '135', + tooltip: false + }] + } + + ngOnInit() { + this.page = []; + this.pageIndex = 0; + this.pageSize = 10; + this.pageCount = 0; + this.subscription.add( + this.route.queryParams.subscribe((queryParams) => { + this.loadProfile(); + }) + ); + } + + ngOnDestroy(): void { + this.subscription.unsubscribe(); + } + + private loadProfile() { + this.definitionId = this.route.snapshot.params['definitionId']; + this.isConfirmed = false; + this.loading = true; + forkJoin([ + this.profileService.getProfile(), + this.policyLabelsService.getLabel(this.definitionId), + this.policyLabelsService.getRelationships(this.definitionId) + ]).subscribe(([profile, definition, relationships]) => { + this.isConfirmed = !!(profile && profile.confirmed); + this.user = new UserPermissions(profile); + this.owner = this.user.did; + this.definition = definition; + this.policy = relationships?.policy || {}; + if (this.isConfirmed) { + this.loadData(); + } else { + setTimeout(() => { + this.loading = false; + }, 500); + } + }, (e) => { + this.loading = false; + }); + } + + private loadData() { + const filters: any = {}; + this.loading = true; + this.policyLabelsService + .getLabelDocuments( + this.definitionId, + this.pageIndex, + this.pageSize, + filters + ) + .subscribe((response) => { + const { page, count } = this.policyLabelsService.parsePage(response); + this.page = page; + this.pageCount = count; + for (const item of this.page) { + item.definition = this.definition?.name; + item.policy = this.policy?.name; + } + setTimeout(() => { + this.loading = false; + }, 500); + }, (e) => { + this.loading = false; + }); + } + + public onPage(event: any): void { + if (this.pageSize != event.pageSize) { + this.pageIndex = 0; + this.pageSize = event.pageSize; + } else { + this.pageIndex = event.pageIndex; + this.pageSize = event.pageSize; + } + this.loadData(); + } + + public onBack() { + this.router.navigate(['/policy-labels']); + } + + public onOpen(row: any) { + this.router.navigate([ + '/policy-labels', + this.definitionId, + 'documents', + row.id + ]); + } +} diff --git a/frontend/src/app/modules/statistics/policy-labels/policy-labels/policy-labels.component.html b/frontend/src/app/modules/statistics/policy-labels/policy-labels/policy-labels.component.html new file mode 100644 index 0000000000..14d02fe45b --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/policy-labels/policy-labels.component.html @@ -0,0 +1,204 @@ +
+
+
+
+ +
+ Before starting work you need to get DID + here +
+ +
+ +
+ +
+ {{title}} +
+ +
+
+ + +
+ Policy + {{ currentPolicy.name }} +
+
+ Policy + All +
+
+ +
+ {{policy.name}} + ({{policy.version}}) +
+
+ All +
+
+
+
+
+ + + +
+
+ +
+ +
+ + + + + {{column.title}} + + + + + + + + + + + +
+ + +
+
+ +
+ + +
+
+ + + + + +
+ + +
+
+ + {{row[column.id]}} + + +
+ +
+
+
+ +
+
+
+ +
+ + There were no rule created yet + Please create new rule to see the data +
+
+
+
\ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.scss b/frontend/src/app/modules/statistics/policy-labels/policy-labels/policy-labels.component.scss similarity index 100% rename from frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.scss rename to frontend/src/app/modules/statistics/policy-labels/policy-labels/policy-labels.component.scss diff --git a/frontend/src/app/modules/statistics/policy-labels/policy-labels/policy-labels.component.ts b/frontend/src/app/modules/statistics/policy-labels/policy-labels/policy-labels.component.ts new file mode 100644 index 0000000000..7341016f21 --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/policy-labels/policy-labels.component.ts @@ -0,0 +1,410 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { EntityStatus, PolicyType, UserPermissions } from '@guardian/interfaces'; +import { forkJoin, Subscription } from 'rxjs'; +import { PolicyEngineService } from 'src/app/services/policy-engine.service'; +import { ProfileService } from 'src/app/services/profile.service'; +import { DialogService } from 'primeng/dynamicdialog'; +import { CustomCustomDialogComponent } from '../../../common/custom-confirm-dialog/custom-confirm-dialog.component'; +import { IImportEntityResult, ImportEntityDialog, ImportEntityType } from '../../../common/import-entity-dialog/import-entity-dialog.component'; +import { NewPolicyLabelDialog } from '../dialogs/new-policy-label-dialog/new-policy-label-dialog.component'; +import { PolicyLabelsService } from 'src/app/services/policy-labels.service'; + +interface IColumn { + id: string; + title: string; + type: string; + size: string; + tooltip: boolean; + permissions?: (user: UserPermissions) => boolean; + canDisplay?: () => boolean; +} + +@Component({ + selector: 'app-policy-labels', + templateUrl: './policy-labels.component.html', + styleUrls: ['./policy-labels.component.scss'], +}) +export class PolicyLabelsComponent implements OnInit { + public readonly title: string = 'Policy Labels'; + + public loading: boolean = true; + public isConfirmed: boolean = false; + public user: UserPermissions = new UserPermissions(); + public owner: string; + public page: any[]; + public pageIndex: number; + public pageSize: number; + public pageCount: number; + public columns: IColumn[]; + public allPolicies: any[] = []; + public currentPolicy: any = null; + + public statuses = [{ + label: 'Draft', + value: EntityStatus.DRAFT, + description: 'Return to editing.', + disable: true + }, { + label: 'Published', + value: EntityStatus.PUBLISHED, + description: 'Release version into public domain.', + disable: (value: string): boolean => { + return !(value === EntityStatus.DRAFT || value === EntityStatus.ERROR); + } + }, { + label: 'Error', + value: EntityStatus.ERROR, + description: '', + disable: true + }] + + private subscription = new Subscription(); + + constructor( + private profileService: ProfileService, + private policyLabelsService: PolicyLabelsService, + private policyEngineService: PolicyEngineService, + private dialogService: DialogService, + private router: Router, + private route: ActivatedRoute + ) { + this.columns = [{ + id: 'name', + title: 'Name', + type: 'text', + size: 'auto', + tooltip: true + }, { + id: 'policy', + title: 'Policy', + type: 'text', + size: 'auto', + tooltip: false + }, { + id: 'topicId', + title: 'Topic', + type: 'text', + size: '135', + tooltip: false + }, { + id: 'status', + title: 'Status', + type: 'text', + size: '180', + tooltip: false + }, { + id: 'edit', + title: '', + type: 'text', + size: '56', + tooltip: false + }, { + id: 'export', + title: '', + type: 'text', + size: '56', + tooltip: false + }, { + id: 'options', + title: '', + type: 'text', + size: '210', + tooltip: false + }, { + id: 'delete', + title: '', + type: 'text', + size: '64', + tooltip: false + }] + } + + ngOnInit() { + this.page = []; + this.pageIndex = 0; + this.pageSize = 10; + this.pageCount = 0; + this.subscription.add( + this.route.queryParams.subscribe((queryParams) => { + this.loadProfile(); + }) + ); + } + + ngOnDestroy(): void { + this.subscription.unsubscribe(); + } + + private loadProfile() { + this.isConfirmed = false; + this.loading = true; + forkJoin([ + this.profileService.getProfile(), + this.policyEngineService.all(), + ]).subscribe(([profile, policies]) => { + this.isConfirmed = !!(profile && profile.confirmed); + this.user = new UserPermissions(profile); + this.owner = this.user.did; + this.allPolicies = policies || []; + this.allPolicies = this.allPolicies.filter((p) => p.status === PolicyType.PUBLISH); + this.allPolicies.unshift({ + name: 'All', + instanceTopicId: null + }); + this.allPolicies.forEach((p: any) => p.label = p.name); + + const topic = this.route.snapshot.queryParams['topic']; + this.currentPolicy = + this.allPolicies.find((p) => p.instanceTopicId === topic) || + this.allPolicies[0]; + + if (this.isConfirmed) { + this.loadData(); + } else { + setTimeout(() => { + this.loading = false; + }, 500); + } + }, (e) => { + this.loading = false; + }); + } + + private loadData() { + const filters: any = {}; + if (this.currentPolicy?.instanceTopicId) { + filters.policyInstanceTopicId = this.currentPolicy?.instanceTopicId; + } + this.loading = true; + this.policyLabelsService + .getLabels( + this.pageIndex, + this.pageSize, + filters + ) + .subscribe((response) => { + const { page, count } = this.policyLabelsService.parsePage(response); + this.page = page; + this.pageCount = count; + for (const item of this.page) { + item.policy = this.allPolicies.find((p) => p.id && p.id === item.policyId)?.name; + } + setTimeout(() => { + this.loading = false; + }, 500); + }, (e) => { + this.loading = false; + }); + } + + public onBack() { + this.router.navigate(['/policy-viewer']); + } + + public onPage(event: any): void { + if (this.pageSize != event.pageSize) { + this.pageIndex = 0; + this.pageSize = event.pageSize; + } else { + this.pageIndex = event.pageIndex; + this.pageSize = event.pageSize; + } + this.loadData(); + } + + public onFilter(event: any) { + if (event.value === null) { + this.currentPolicy = this.allPolicies[0]; + } + this.pageIndex = 0; + const topic = this.currentPolicy?.instanceTopicId || 'all' + this.router.navigate(['/policy-labels'], { queryParams: { topic } }); + this.loadData(); + } + + public onCreate() { + const dialogRef = this.dialogService.open(NewPolicyLabelDialog, { + showHeader: false, + width: '720px', + styleClass: 'guardian-dialog', + data: { + title: 'Create New', + policies: this.allPolicies, + policy: this.currentPolicy, + action: 'Create' + } + }); + dialogRef.onClose.subscribe(async (result) => { + if (result) { + this.loading = true; + this.policyLabelsService + .createLabel(result) + .subscribe((newItem) => { + this.loadData(); + }, (e) => { + this.loading = false; + }); + } + }); + } + + public onEdit(item: any) { + this.router.navigate(['/policy-labels', item.id]); + } + + public onImport() { + const dialogRef = this.dialogService.open(ImportEntityDialog, { + showHeader: false, + width: '720px', + styleClass: 'guardian-dialog', + data: { + type: ImportEntityType.PolicyLabel, + } + }); + dialogRef.onClose.subscribe(async (result: IImportEntityResult | null) => { + if (result) { + this.importDetails(result); + } + }); + } + + private importDetails(result: IImportEntityResult) { + const { type, data, label } = result; + const dialogRef = this.dialogService.open(NewPolicyLabelDialog, { + showHeader: false, + width: '720px', + styleClass: 'guardian-dialog', + data: { + title: 'Preview', + action: 'Import', + policies: this.allPolicies, + policy: this.currentPolicy, + label + } + }); + dialogRef.onClose.subscribe(async (result) => { + if (result && result.policyId) { + this.loading = true; + this.policyLabelsService + .import(result.policyId, data) + .subscribe((newItem) => { + this.loadData(); + }, (e) => { + this.loading = false; + }); + } + }); + } + + public onExport(item: any) { + this.loading = true; + this.policyLabelsService.export(item.id) + .subscribe((fileBuffer) => { + const downloadLink = document.createElement('a'); + downloadLink.href = window.URL.createObjectURL( + new Blob([new Uint8Array(fileBuffer)], { + type: 'application/guardian-label' + }) + ); + downloadLink.setAttribute('download', `${item.name}_${Date.now()}.label`); + document.body.appendChild(downloadLink); + downloadLink.click(); + downloadLink.remove(); + setTimeout(() => { + this.loading = false; + }, 500); + }, (error) => { + this.loading = false; + }); + } + + public onDelete(item: any) { + if (item.status === EntityStatus.ACTIVE) { + return; + } + const dialogRef = this.dialogService.open(CustomCustomDialogComponent, { + showHeader: false, + width: '640px', + styleClass: 'guardian-dialog', + data: { + header: 'Delete Label', + text: `Are you sure want to delete label (${item.name})?`, + buttons: [{ + name: 'Close', + class: 'secondary' + }, { + name: 'Delete', + class: 'delete' + }] + }, + }); + dialogRef.onClose.subscribe((result: string) => { + if (result === 'Delete') { + this.loading = true; + this.policyLabelsService + .deleteLabel(item.id) + .subscribe((result) => { + this.loadData(); + }, (e) => { + this.loading = false; + }); + } + }); + } + + public onChangeStatus($event: string, row: any): void { + this.publish(row) + } + + private publish(row: any) { + const dialogRef = this.dialogService.open(CustomCustomDialogComponent, { + showHeader: false, + width: '640px', + styleClass: 'guardian-dialog', + data: { + header: 'Publish Label', + text: `Are you sure want to publish label (${row.name})?`, + buttons: [{ + name: 'Close', + class: 'secondary' + }, { + name: 'Publish', + class: 'primary' + }] + }, + }); + dialogRef.onClose.subscribe((result: string) => { + if (result === 'Publish') { + this.loading = true; + this.policyLabelsService.pushPublish(row).subscribe( + (result) => { + const { taskId, expectation } = result; + this.router.navigate(['task', taskId], { + queryParams: { + last: btoa(location.href), + }, + }); + }, + (e) => { + this.loading = false; + } + ); + } + }); + } + + public onCreateInstance(item: any): void { + if (item.status !== 'PUBLISHED') { + return; + } + this.router.navigate(['/policy-labels', item.id, 'document']); + } + + public onOpenInstances(item: any): void { + if (item.status !== 'PUBLISHED') { + return; + } + this.router.navigate(['/policy-labels', item.id, 'documents']); + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.html b/frontend/src/app/modules/statistics/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.html similarity index 100% rename from frontend/src/app/modules/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.html rename to frontend/src/app/modules/statistics/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.html diff --git a/frontend/src/app/modules/schema-rules/dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component.scss b/frontend/src/app/modules/statistics/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.scss similarity index 100% rename from frontend/src/app/modules/schema-rules/dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component.scss rename to frontend/src/app/modules/statistics/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.scss diff --git a/frontend/src/app/modules/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.ts b/frontend/src/app/modules/statistics/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.ts similarity index 100% rename from frontend/src/app/modules/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.ts rename to frontend/src/app/modules/statistics/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.ts diff --git a/frontend/src/app/modules/policy-statistics/dialogs/score-dialog/score-dialog.component.html b/frontend/src/app/modules/statistics/policy-statistics/dialogs/score-dialog/score-dialog.component.html similarity index 100% rename from frontend/src/app/modules/policy-statistics/dialogs/score-dialog/score-dialog.component.html rename to frontend/src/app/modules/statistics/policy-statistics/dialogs/score-dialog/score-dialog.component.html diff --git a/frontend/src/app/modules/policy-statistics/dialogs/score-dialog/score-dialog.component.scss b/frontend/src/app/modules/statistics/policy-statistics/dialogs/score-dialog/score-dialog.component.scss similarity index 100% rename from frontend/src/app/modules/policy-statistics/dialogs/score-dialog/score-dialog.component.scss rename to frontend/src/app/modules/statistics/policy-statistics/dialogs/score-dialog/score-dialog.component.scss diff --git a/frontend/src/app/modules/policy-statistics/dialogs/score-dialog/score-dialog.component.ts b/frontend/src/app/modules/statistics/policy-statistics/dialogs/score-dialog/score-dialog.component.ts similarity index 100% rename from frontend/src/app/modules/policy-statistics/dialogs/score-dialog/score-dialog.component.ts rename to frontend/src/app/modules/statistics/policy-statistics/dialogs/score-dialog/score-dialog.component.ts diff --git a/frontend/src/app/modules/policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component.html b/frontend/src/app/modules/statistics/policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component.html similarity index 100% rename from frontend/src/app/modules/policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component.html rename to frontend/src/app/modules/statistics/policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component.html diff --git a/frontend/src/app/modules/policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component.scss b/frontend/src/app/modules/statistics/policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component.scss similarity index 100% rename from frontend/src/app/modules/policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component.scss rename to frontend/src/app/modules/statistics/policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component.scss diff --git a/frontend/src/app/modules/policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component.ts b/frontend/src/app/modules/statistics/policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component.ts similarity index 93% rename from frontend/src/app/modules/policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component.ts rename to frontend/src/app/modules/statistics/policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component.ts index 55a3273370..fd70cd0003 100644 --- a/frontend/src/app/modules/policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component.ts +++ b/frontend/src/app/modules/statistics/policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component.ts @@ -1,8 +1,7 @@ import { Component } from '@angular/core'; import { DialogService, DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; -import { IFormula, IVariable } from '../../../common/models/assessment'; -import { GenerateUUIDv4 } from '@guardian/interfaces'; -import { Formula } from 'src/app/utils'; +import { IFormula, IVariable } from '../../../../common/models/assessment'; +import { FormulaEngine, GenerateUUIDv4 } from '@guardian/interfaces'; @Component({ selector: 'statistic-preview-dialog', @@ -105,7 +104,7 @@ export class StatisticPreviewDialog { private calcFormula(item: IFormula, scope: any): any { try { - return Formula.evaluate(item.formula, scope); + return FormulaEngine.evaluate(item.formula, scope); } catch (error) { return NaN; } diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.html b/frontend/src/app/modules/statistics/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.html similarity index 100% rename from frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.html rename to frontend/src/app/modules/statistics/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.html diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.scss b/frontend/src/app/modules/statistics/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.scss similarity index 100% rename from frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.scss rename to frontend/src/app/modules/statistics/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.scss diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts b/frontend/src/app/modules/statistics/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts similarity index 98% rename from frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts rename to frontend/src/app/modules/statistics/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts index 88158762ca..4abe6de1e9 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts +++ b/frontend/src/app/modules/statistics/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts @@ -1,14 +1,13 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { GenerateUUIDv4, IStatistic, IVCDocument, Schema, UserPermissions } from '@guardian/interfaces'; +import { FormulaEngine, GenerateUUIDv4, IStatistic, IVCDocument, Schema, UserPermissions } from '@guardian/interfaces'; import { forkJoin, Subscription } from 'rxjs'; import { PolicyStatisticsService } from 'src/app/services/policy-statistics.service'; import { ProfileService } from 'src/app/services/profile.service'; import { SchemaService } from 'src/app/services/schema.service'; import { DialogService } from 'primeng/dynamicdialog'; -import { Formula } from 'src/app/utils'; -import { IDocument, IFormula, IOption, IScore, IVariable } from '../../common/models/assessment'; -import { IColumn } from '../../common/models/grid'; +import { IDocument, IFormula, IOption, IScore, IVariable } from '../../../common/models/assessment'; +import { IColumn } from '../../../common/models/grid'; @Component({ selector: 'app-statistic-assessment-configuration', @@ -431,7 +430,7 @@ export class StatisticAssessmentConfigurationComponent implements OnInit { private calcFormula(item: IFormula, scope: any): any { try { - return Formula.evaluate(item.formula, scope); + return FormulaEngine.evaluate(item.formula, scope); } catch (error) { return NaN; } diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.html b/frontend/src/app/modules/statistics/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.html similarity index 100% rename from frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.html rename to frontend/src/app/modules/statistics/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.html diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.scss b/frontend/src/app/modules/statistics/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.scss similarity index 100% rename from frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.scss rename to frontend/src/app/modules/statistics/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.scss diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.ts b/frontend/src/app/modules/statistics/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.ts similarity index 95% rename from frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.ts rename to frontend/src/app/modules/statistics/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.ts index 37c3b17f77..5060dde2ca 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.ts +++ b/frontend/src/app/modules/statistics/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.ts @@ -5,12 +5,12 @@ import { forkJoin, Subscription } from 'rxjs'; import { PolicyStatisticsService } from 'src/app/services/policy-statistics.service'; import { ProfileService } from 'src/app/services/profile.service'; import { DialogService } from 'primeng/dynamicdialog'; -import { IFormula, IOption, IScore, IVariable } from '../../common/models/assessment'; -import { TreeSource } from '../../common/tree-graph/tree-source'; -import { TreeGraphComponent } from '../../common/tree-graph/tree-graph.component'; -import { DocumentNode, SchemaData } from '../../common/models/schema-node'; -import { TreeNode } from '../../common/tree-graph/tree-node'; -import { VCViewerDialog } from '../../schema-engine/vc-dialog/vc-dialog.component'; +import { IFormula, IOption, IScore, IVariable } from '../../../common/models/assessment'; +import { TreeSource } from '../../../common/tree-graph/tree-source'; +import { TreeGraphComponent } from '../../../common/tree-graph/tree-graph.component'; +import { DocumentNode, SchemaData } from '../../../common/models/schema-node'; +import { TreeNode } from '../../../common/tree-graph/tree-node'; +import { VCViewerDialog } from '../../../schema-engine/vc-dialog/vc-dialog.component'; @Component({ selector: 'app-statistic-assessment-view', diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessments/statistic-assessments.component.html b/frontend/src/app/modules/statistics/policy-statistics/statistic-assessments/statistic-assessments.component.html similarity index 100% rename from frontend/src/app/modules/policy-statistics/statistic-assessments/statistic-assessments.component.html rename to frontend/src/app/modules/statistics/policy-statistics/statistic-assessments/statistic-assessments.component.html diff --git a/frontend/src/app/modules/statistics/policy-statistics/statistic-assessments/statistic-assessments.component.scss b/frontend/src/app/modules/statistics/policy-statistics/statistic-assessments/statistic-assessments.component.scss new file mode 100644 index 0000000000..e99f4ea49c --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-statistics/statistic-assessments/statistic-assessments.component.scss @@ -0,0 +1,23 @@ +.policy-name { + color: var(--guardian-disabled-color, #848FA9); + font-size: 14px; + font-weight: 500; + line-height: 16px; + position: absolute; + top: 34px; + right: 0; + + .policy-version { + padding-left: 16px; + } +} + +.grid-btn { + width: 80px; + height: 30px; + margin-right: 16px; + + &:last-child { + margin-right: 0px; + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessments/statistic-assessments.component.ts b/frontend/src/app/modules/statistics/policy-statistics/statistic-assessments/statistic-assessments.component.ts similarity index 100% rename from frontend/src/app/modules/policy-statistics/statistic-assessments/statistic-assessments.component.ts rename to frontend/src/app/modules/statistics/policy-statistics/statistic-assessments/statistic-assessments.component.ts diff --git a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html b/frontend/src/app/modules/statistics/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html similarity index 91% rename from frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html rename to frontend/src/app/modules/statistics/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html index 3ec5dc8dff..65ec219ac6 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html +++ b/frontend/src/app/modules/statistics/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html @@ -9,20 +9,11 @@
-
- +
+ Statistics + > + {{item?.name || 'Statistic'}}
-
{{item?.name}}
@@ -31,33 +22,89 @@
-
+ +
+
+ +
+ +
Back
+
+ +
+ +
+ +
Save
+
+ +
+ +
+ +
Preview
+
+ +
+ + +
+ +
Published
+
+
+
+ +
+ +
Draft
+
+
+ + +
+ +
{{item.label}}
+
+
+
+
+
+
+ +
-
+
-
Overview
-
-
+
-
Select Schemas
-
-
+
-
@@ -66,6 +113,7 @@
+
@@ -685,9 +733,4 @@
- -
- - -
\ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.scss b/frontend/src/app/modules/statistics/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.scss similarity index 94% rename from frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.scss rename to frontend/src/app/modules/statistics/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.scss index 0ec9d2411a..b0cb2a2b12 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.scss +++ b/frontend/src/app/modules/statistics/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.scss @@ -4,21 +4,52 @@ user-select: none; .header-container { - padding: 56px 48px 10px 48px; + padding: 0px 48px 0px 48px; background: var(--guardian-primary-color, #4169E2); - min-height: 178px; - height: 178px; + min-height: 132px; + height: 132px; - .guardian-user-back-button { - button { - border-color: var(--guardian-background, #FFFFFF); + .guardian-user-page-header { + font-size: 32px; + font-weight: 600; + height: 40px; + padding: 0; + position: relative; + color: var(--guardian-header-color, #000); + margin-bottom: 32px; + + span { + line-height: 40px; } } - .guardian-user-page-header { + .guardian-user-page-path { + font-family: Inter; + font-size: 12px; + font-weight: 400; + line-height: 12px; + text-align: left; color: var(--guardian-background, #FFFFFF); + margin-top: 36px; + margin-bottom: 8px; + height: 16px; + + span { + padding-right: 8px; + } + + .path-link { + cursor: pointer; + + &:hover { + text-decoration: underline; + } + } } + .guardian-user-page-header { + color: var(--guardian-background, #FFFFFF); + } .policy-name { color: var(--guardian-background, #FFFFFF); @@ -26,7 +57,7 @@ font-weight: 500; line-height: 16px; position: absolute; - top: 34px; + top: 12px; right: 0; .policy-version { @@ -35,6 +66,42 @@ } } + .tab-container { + height: 40px; + min-height: 40px; + width: 100%; + background: #E1E7EF; + border-bottom: 1px solid #C4D0E1; + padding: 0px 12px; + + .guardian-step { + margin-right: 8px; + margin-bottom: 0px; + background: #EFF3F7; + border: 1px solid #C4D0E1; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + border-bottom-width: 0; + + .guardian-step-name { + color: #848FA9; + } + + &:hover, + &[action="true"]:hover { + background: #f8f9fb !important; + } + + &[action="true"] { + background: #fff !important; + + .guardian-step-name { + color: var(--guardian-primary-color, #4169E2) !important; + } + } + } + } + .actions-container { min-height: 64px; height: 64px; diff --git a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.ts b/frontend/src/app/modules/statistics/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.ts similarity index 86% rename from frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.ts rename to frontend/src/app/modules/statistics/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.ts index 18a4d302bc..ebac3bd688 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.ts +++ b/frontend/src/app/modules/statistics/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.ts @@ -4,23 +4,22 @@ import { EntityStatus, IStatistic, Schema, UserPermissions } from '@guardian/int import { forkJoin, Subscription } from 'rxjs'; import { PolicyStatisticsService } from 'src/app/services/policy-statistics.service'; import { ProfileService } from 'src/app/services/profile.service'; -import { TreeGraphComponent } from '../../common/tree-graph/tree-graph.component'; -import { TreeNode } from '../../common/tree-graph/tree-node'; -import { TreeListItem } from '../../common/tree-graph/tree-list'; -import { SchemaData, SchemaNode } from '../../common/models/schema-node'; -import { SchemaVariables } from "../../common/models/schema-variables"; -import { SchemaFormulas } from "../../common/models/schema-formulas"; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { SchemaService } from 'src/app/services/schema.service'; -import { TreeSource } from '../../common/tree-graph/tree-source'; -import { createAutocomplete } from '../../common/models/lang-modes/autocomplete'; -import { SchemaScore, SchemaScores } from '../../common/models/schema-scores'; import { DialogService } from 'primeng/dynamicdialog'; import { ScoreDialog } from '../dialogs/score-dialog/score-dialog.component'; -import { SchemaRule, SchemaRules } from '../../common/models/schema-rules'; import { StatisticPreviewDialog } from '../dialogs/statistic-preview-dialog/statistic-preview-dialog.component'; -import { CustomCustomDialogComponent } from '../../common/custom-confirm-dialog/custom-confirm-dialog.component'; - +import { TreeGraphComponent } from '../../../common/tree-graph/tree-graph.component'; +import { TreeNode } from '../../../common/tree-graph/tree-node'; +import { TreeListItem } from '../../../common/tree-graph/tree-list'; +import { SchemaData, SchemaNode } from '../../../common/models/schema-node'; +import { SchemaVariables } from '../../../common/models/schema-variables'; +import { SchemaFormulas } from '../../../common/models/schema-formulas'; +import { TreeSource } from '../../../common/tree-graph/tree-source'; +import { createAutocomplete } from '../../../common/models/lang-modes/autocomplete'; +import { SchemaScore, SchemaScores } from '../../../common/models/schema-scores'; +import { SchemaRule, SchemaRules } from '../../../common/models/schema-rules'; +import { CustomCustomDialogComponent } from '../../../common/custom-confirm-dialog/custom-confirm-dialog.component'; @Component({ selector: 'app-statistic-definition-configuration', @@ -73,6 +72,14 @@ export class StatisticDefinitionConfigurationComponent implements OnInit { policy: new FormControl('', Validators.required), }); + public readonly statusMenuItems = [{ + label: 'Publish', + icon: 'publish', + callback: ($event: any) => { + this.publish(); + } + }] + public schemaFilterType: number = 1; // public methods: any[] = [{ @@ -644,4 +651,58 @@ export class StatisticDefinitionConfigurationComponent implements OnInit { }); dialogRef.onClose.subscribe(async (result) => { }); } + + private publish() { + const rules = this.item?.config?.rules || []; + const main = rules.find((r) => r.type === 'main'); + if (!main) { + const dialogRef = this.dialogService.open(CustomCustomDialogComponent, { + showHeader: false, + width: '640px', + styleClass: 'guardian-dialog', + data: { + header: 'Publish Statistic', + text: 'Statistics cannot be published. Please select main schema.', + buttons: [{ + name: 'Close', + class: 'secondary' + }] + }, + }); + dialogRef.onClose.subscribe((result) => { }); + } else { + const dialogRef = this.dialogService.open(CustomCustomDialogComponent, { + showHeader: false, + width: '640px', + styleClass: 'guardian-dialog', + data: { + header: 'Publish Statistic', + text: `Are you sure want to publish statistic (${this.item?.name})?`, + buttons: [{ + name: 'Close', + class: 'secondary' + }, { + name: 'Publish', + class: 'primary' + }] + }, + }); + dialogRef.onClose.subscribe((result: string) => { + if (result === 'Publish') { + this.loading = true; + this.policyStatisticsService + .publishDefinition(this.item) + .subscribe((response) => { + this.loadData(); + }, (e) => { + this.loading = false; + }); + } + }); + } + } + + public isActionStep(index: number): boolean { + return this.stepper[index]; + } } \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.html b/frontend/src/app/modules/statistics/policy-statistics/statistic-definitions/statistic-definitions.component.html similarity index 99% rename from frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.html rename to frontend/src/app/modules/statistics/policy-statistics/statistic-definitions/statistic-definitions.component.html index 8fc8628a28..f69ed0f42f 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.html +++ b/frontend/src/app/modules/statistics/policy-statistics/statistic-definitions/statistic-definitions.component.html @@ -117,6 +117,7 @@ pTooltip="{{row[column.id]}}" [tooltipDisabled]="!column.tooltip" tooltipPosition="top" + [showDelay]="1000" [ngSwitch]="column.id" > diff --git a/frontend/src/app/modules/schema-rules/schema-rules/schema-rules.component.scss b/frontend/src/app/modules/statistics/policy-statistics/statistic-definitions/statistic-definitions.component.scss similarity index 100% rename from frontend/src/app/modules/schema-rules/schema-rules/schema-rules.component.scss rename to frontend/src/app/modules/statistics/policy-statistics/statistic-definitions/statistic-definitions.component.scss diff --git a/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.ts b/frontend/src/app/modules/statistics/policy-statistics/statistic-definitions/statistic-definitions.component.ts similarity index 98% rename from frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.ts rename to frontend/src/app/modules/statistics/policy-statistics/statistic-definitions/statistic-definitions.component.ts index f2103404be..a9445860cb 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.ts +++ b/frontend/src/app/modules/statistics/policy-statistics/statistic-definitions/statistic-definitions.component.ts @@ -7,8 +7,8 @@ import { PolicyStatisticsService } from 'src/app/services/policy-statistics.serv import { ProfileService } from 'src/app/services/profile.service'; import { DialogService } from 'primeng/dynamicdialog'; import { NewPolicyStatisticsDialog } from '../dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component'; -import { CustomCustomDialogComponent } from '../../common/custom-confirm-dialog/custom-confirm-dialog.component'; -import { IImportEntityResult, ImportEntityDialog, ImportEntityType } from '../../common/import-entity-dialog/import-entity-dialog.component'; +import { CustomCustomDialogComponent } from '../../../common/custom-confirm-dialog/custom-confirm-dialog.component'; +import { IImportEntityResult, ImportEntityDialog, ImportEntityType } from '../../../common/import-entity-dialog/import-entity-dialog.component'; interface IColumn { diff --git a/frontend/src/app/modules/statistics/schema-rules/dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component.html b/frontend/src/app/modules/statistics/schema-rules/dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component.html new file mode 100644 index 0000000000..aadf6c5cf3 --- /dev/null +++ b/frontend/src/app/modules/statistics/schema-rules/dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component.html @@ -0,0 +1,70 @@ +
+
+
{{title}}
+
+
+ +
+
+
+
+
+
+
+ + +
+ + +
+
+ + +
+ +
+ + + +
+ Policy + {{ currentPolicy.name }} +
+
+ +
+ {{policy.name}} + ({{policy.version}}) +
+
+ +
+
+
+
+ + +
+
+ \ No newline at end of file diff --git a/frontend/src/app/modules/statistics/schema-rules/dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component.scss b/frontend/src/app/modules/statistics/schema-rules/dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component.scss new file mode 100644 index 0000000000..2da16109a5 --- /dev/null +++ b/frontend/src/app/modules/statistics/schema-rules/dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component.scss @@ -0,0 +1,58 @@ +.context { + position: relative; + overflow-y: auto; + padding: 14px 0 20px 0; + display: flex; + flex-direction: row; + height: 100%; + font-family: Inter, serif; + font-style: normal; +} + +.dialog-header { + .header-item { + padding: 6px 12px; + margin: 0px 16px; + border-radius: 6px; + font-size: var(--guardian-primary-font-size); + color: var(--guardian-dry-run-color); + background: var(--guardian-dry-run-background); + user-select: none; + cursor: default; + } +} + +form { + width: 100%; + + .guardian-input-container { + input[readonly] { + border: none; + padding-left: 0; + } + } +} + +.guardian-input-container { + margin-bottom: 24px; +} + +.dialog-body { + height: 300px; +} + +.action-buttons { + display: flex; + justify-content: flex-end; + user-select: none; + + &>div { + margin-left: 15px; + margin-right: 0px; + } +} + +.close-icon-color { + fill: #848FA9; + color: #848FA9; +} \ No newline at end of file diff --git a/frontend/src/app/modules/schema-rules/dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component.ts b/frontend/src/app/modules/statistics/schema-rules/dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component.ts similarity index 100% rename from frontend/src/app/modules/schema-rules/dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component.ts rename to frontend/src/app/modules/statistics/schema-rules/dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component.ts diff --git a/frontend/src/app/modules/schema-rules/dialogs/schema-rule-config-dialog/schema-rule-config-dialog.component.html b/frontend/src/app/modules/statistics/schema-rules/dialogs/schema-rule-config-dialog/schema-rule-config-dialog.component.html similarity index 100% rename from frontend/src/app/modules/schema-rules/dialogs/schema-rule-config-dialog/schema-rule-config-dialog.component.html rename to frontend/src/app/modules/statistics/schema-rules/dialogs/schema-rule-config-dialog/schema-rule-config-dialog.component.html diff --git a/frontend/src/app/modules/schema-rules/dialogs/schema-rule-config-dialog/schema-rule-config-dialog.component.scss b/frontend/src/app/modules/statistics/schema-rules/dialogs/schema-rule-config-dialog/schema-rule-config-dialog.component.scss similarity index 100% rename from frontend/src/app/modules/schema-rules/dialogs/schema-rule-config-dialog/schema-rule-config-dialog.component.scss rename to frontend/src/app/modules/statistics/schema-rules/dialogs/schema-rule-config-dialog/schema-rule-config-dialog.component.scss diff --git a/frontend/src/app/modules/schema-rules/dialogs/schema-rule-config-dialog/schema-rule-config-dialog.component.ts b/frontend/src/app/modules/statistics/schema-rules/dialogs/schema-rule-config-dialog/schema-rule-config-dialog.component.ts similarity index 97% rename from frontend/src/app/modules/schema-rules/dialogs/schema-rule-config-dialog/schema-rule-config-dialog.component.ts rename to frontend/src/app/modules/statistics/schema-rules/dialogs/schema-rule-config-dialog/schema-rule-config-dialog.component.ts index df889e802e..35897f7e77 100644 --- a/frontend/src/app/modules/schema-rules/dialogs/schema-rule-config-dialog/schema-rule-config-dialog.component.ts +++ b/frontend/src/app/modules/statistics/schema-rules/dialogs/schema-rule-config-dialog/schema-rule-config-dialog.component.ts @@ -1,7 +1,8 @@ import { Component } from '@angular/core'; import { SchemaField } from '@guardian/interfaces'; import { DialogService, DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; -import { ConditionIf, ConditionRule, FieldRule, FormulaRule, RangeRule } from 'src/app/modules/common/models/field-rule'; +import { ConditionIf, ConditionRule, FormulaRule, RangeRule } from 'src/app/modules/common/models/conditions'; +import { FieldRule } from "src/app/modules/common/models/field-rule"; import { createAutocomplete } from 'src/app/modules/common/models/lang-modes/autocomplete'; import { IPFSService } from 'src/app/services/ipfs.service'; diff --git a/frontend/src/app/modules/schema-rules/dialogs/schema-rules-preview-dialog/schema-rules-preview-dialog.component.html b/frontend/src/app/modules/statistics/schema-rules/dialogs/schema-rules-preview-dialog/schema-rules-preview-dialog.component.html similarity index 100% rename from frontend/src/app/modules/schema-rules/dialogs/schema-rules-preview-dialog/schema-rules-preview-dialog.component.html rename to frontend/src/app/modules/statistics/schema-rules/dialogs/schema-rules-preview-dialog/schema-rules-preview-dialog.component.html diff --git a/frontend/src/app/modules/schema-rules/dialogs/schema-rules-preview-dialog/schema-rules-preview-dialog.component.scss b/frontend/src/app/modules/statistics/schema-rules/dialogs/schema-rules-preview-dialog/schema-rules-preview-dialog.component.scss similarity index 100% rename from frontend/src/app/modules/schema-rules/dialogs/schema-rules-preview-dialog/schema-rules-preview-dialog.component.scss rename to frontend/src/app/modules/statistics/schema-rules/dialogs/schema-rules-preview-dialog/schema-rules-preview-dialog.component.scss diff --git a/frontend/src/app/modules/schema-rules/dialogs/schema-rules-preview-dialog/schema-rules-preview-dialog.component.ts b/frontend/src/app/modules/statistics/schema-rules/dialogs/schema-rules-preview-dialog/schema-rules-preview-dialog.component.ts similarity index 86% rename from frontend/src/app/modules/schema-rules/dialogs/schema-rules-preview-dialog/schema-rules-preview-dialog.component.ts rename to frontend/src/app/modules/statistics/schema-rules/dialogs/schema-rules-preview-dialog/schema-rules-preview-dialog.component.ts index 9ac5b93a00..9ea1f17ade 100644 --- a/frontend/src/app/modules/schema-rules/dialogs/schema-rules-preview-dialog/schema-rules-preview-dialog.component.ts +++ b/frontend/src/app/modules/statistics/schema-rules/dialogs/schema-rules-preview-dialog/schema-rules-preview-dialog.component.ts @@ -1,7 +1,6 @@ import { Component } from '@angular/core'; import { DialogService, DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; -import { ISchemaRuleData } from '@guardian/interfaces'; -import { FieldRuleValidators } from 'src/app/modules/common/models/field-rule-validator'; +import { DocumentFieldValidators, ISchemaRuleData } from '@guardian/interfaces'; @Component({ @@ -13,7 +12,7 @@ export class SchemaRulesPreviewDialog { public loading = true; public item: any; public preview: any[]; - public rules: FieldRuleValidators; + public rules: DocumentFieldValidators; constructor( public ref: DynamicDialogRef, @@ -35,7 +34,7 @@ export class SchemaRulesPreviewDialog { }); } - this.rules = new FieldRuleValidators(variables); + this.rules = new DocumentFieldValidators(variables); } ngOnInit() { diff --git a/frontend/src/app/modules/schema-rules/schema-rule-configuration/schema-rule-configuration.component.html b/frontend/src/app/modules/statistics/schema-rules/schema-rule-configuration/schema-rule-configuration.component.html similarity index 100% rename from frontend/src/app/modules/schema-rules/schema-rule-configuration/schema-rule-configuration.component.html rename to frontend/src/app/modules/statistics/schema-rules/schema-rule-configuration/schema-rule-configuration.component.html diff --git a/frontend/src/app/modules/schema-rules/schema-rule-configuration/schema-rule-configuration.component.scss b/frontend/src/app/modules/statistics/schema-rules/schema-rule-configuration/schema-rule-configuration.component.scss similarity index 100% rename from frontend/src/app/modules/schema-rules/schema-rule-configuration/schema-rule-configuration.component.scss rename to frontend/src/app/modules/statistics/schema-rules/schema-rule-configuration/schema-rule-configuration.component.scss diff --git a/frontend/src/app/modules/schema-rules/schema-rule-configuration/schema-rule-configuration.component.ts b/frontend/src/app/modules/statistics/schema-rules/schema-rule-configuration/schema-rule-configuration.component.ts similarity index 96% rename from frontend/src/app/modules/schema-rules/schema-rule-configuration/schema-rule-configuration.component.ts rename to frontend/src/app/modules/statistics/schema-rules/schema-rule-configuration/schema-rule-configuration.component.ts index 106fc9480a..c46e252ca2 100644 --- a/frontend/src/app/modules/schema-rules/schema-rule-configuration/schema-rule-configuration.component.ts +++ b/frontend/src/app/modules/statistics/schema-rules/schema-rule-configuration/schema-rule-configuration.component.ts @@ -3,20 +3,21 @@ import { ActivatedRoute, Router } from '@angular/router'; import { EntityStatus, ISchemaRules, ISchemaRulesConfig, Schema, SchemaField, UserPermissions } from '@guardian/interfaces'; import { forkJoin, Subscription } from 'rxjs'; import { ProfileService } from 'src/app/services/profile.service'; -import { TreeGraphComponent } from '../../common/tree-graph/tree-graph.component'; -import { TreeNode } from '../../common/tree-graph/tree-node'; -import { TreeListItem } from '../../common/tree-graph/tree-list'; -import { SchemaData, SchemaNode } from '../../common/models/schema-node'; +import { TreeGraphComponent } from '../../../common/tree-graph/tree-graph.component'; +import { TreeNode } from '../../../common/tree-graph/tree-node'; +import { TreeListItem } from '../../../common/tree-graph/tree-list'; +import { SchemaData, SchemaNode } from '../../../common/models/schema-node'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { SchemaService } from 'src/app/services/schema.service'; -import { TreeSource } from '../../common/tree-graph/tree-source'; -import { createAutocomplete } from '../../common/models/lang-modes/autocomplete'; +import { TreeSource } from '../../../common/tree-graph/tree-source'; +import { createAutocomplete } from '../../../common/models/lang-modes/autocomplete'; import { DialogService } from 'primeng/dynamicdialog'; import { SchemaRulesService } from 'src/app/services/schema-rules.service'; import { SchemaRulesPreviewDialog } from '../dialogs/schema-rules-preview-dialog/schema-rules-preview-dialog.component'; -import { ConditionRule, FieldRule, FieldRules, FormulaRule, RangeRule } from '../../common/models/field-rule'; +import { ConditionRule, FormulaRule, RangeRule } from '../../../common/models/conditions'; +import { FieldRule, FieldRules } from "src/app/modules/common/models/field-rule"; import { EnumValue, SchemaRuleConfigDialog } from '../dialogs/schema-rule-config-dialog/schema-rule-config-dialog.component'; -import { CustomCustomDialogComponent } from '../../common/custom-confirm-dialog/custom-confirm-dialog.component'; +import { CustomCustomDialogComponent } from '../../../common/custom-confirm-dialog/custom-confirm-dialog.component'; import { IPFSService } from 'src/app/services/ipfs.service'; @Component({ diff --git a/frontend/src/app/modules/schema-rules/schema-rules/schema-rules.component.html b/frontend/src/app/modules/statistics/schema-rules/schema-rules/schema-rules.component.html similarity index 100% rename from frontend/src/app/modules/schema-rules/schema-rules/schema-rules.component.html rename to frontend/src/app/modules/statistics/schema-rules/schema-rules/schema-rules.component.html diff --git a/frontend/src/app/modules/statistics/schema-rules/schema-rules/schema-rules.component.scss b/frontend/src/app/modules/statistics/schema-rules/schema-rules/schema-rules.component.scss new file mode 100644 index 0000000000..0443d14a4b --- /dev/null +++ b/frontend/src/app/modules/statistics/schema-rules/schema-rules/schema-rules.component.scss @@ -0,0 +1,28 @@ +.policy-name { + color: var(--guardian-disabled-color, #848FA9); + font-size: 14px; + font-weight: 500; + line-height: 16px; + position: absolute; + top: 34px; + right: 0; + + .policy-version { + padding-left: 16px; + } +} +.guardian-dropdown { + &::ng-deep .p-dropdown { + width: 250px; + } +} + +.grid-btn { + width: 80px; + height: 30px; + margin-right: 16px; + + &:last-child { + margin-right: 0px; + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/schema-rules/schema-rules/schema-rules.component.ts b/frontend/src/app/modules/statistics/schema-rules/schema-rules/schema-rules.component.ts similarity index 98% rename from frontend/src/app/modules/schema-rules/schema-rules/schema-rules.component.ts rename to frontend/src/app/modules/statistics/schema-rules/schema-rules/schema-rules.component.ts index bc333fc727..ad9e09899f 100644 --- a/frontend/src/app/modules/schema-rules/schema-rules/schema-rules.component.ts +++ b/frontend/src/app/modules/statistics/schema-rules/schema-rules/schema-rules.component.ts @@ -6,9 +6,9 @@ import { PolicyEngineService } from 'src/app/services/policy-engine.service'; import { ProfileService } from 'src/app/services/profile.service'; import { DialogService } from 'primeng/dynamicdialog'; import { SchemaRulesService } from 'src/app/services/schema-rules.service'; -import { CustomCustomDialogComponent } from '../../common/custom-confirm-dialog/custom-confirm-dialog.component'; +import { CustomCustomDialogComponent } from '../../../common/custom-confirm-dialog/custom-confirm-dialog.component'; import { NewSchemaRuleDialog } from '../dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component'; -import { IImportEntityResult, ImportEntityDialog, ImportEntityType } from '../../common/import-entity-dialog/import-entity-dialog.component'; +import { IImportEntityResult, ImportEntityDialog, ImportEntityType } from '../../../common/import-entity-dialog/import-entity-dialog.component'; interface IColumn { id: string; diff --git a/frontend/src/app/modules/statistics/statistics.module.ts b/frontend/src/app/modules/statistics/statistics.module.ts new file mode 100644 index 0000000000..89782912e0 --- /dev/null +++ b/frontend/src/app/modules/statistics/statistics.module.ts @@ -0,0 +1,102 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MaterialModule } from 'src/app/modules/common/material.module'; +import { FormsModule } from '@angular/forms'; +import { InputTextModule } from 'primeng/inputtext'; +import { AppRoutingModule } from 'src/app/app-routing.module'; +import { SchemaEngineModule } from '../schema-engine/schema-engine.module'; +import { CommonComponentsModule } from '../common/common-components.module'; +import { AngularSvgIconModule } from 'angular-svg-icon'; +import { DialogService, DynamicDialogModule } from 'primeng/dynamicdialog'; +import { TableModule } from 'primeng/table'; +import { TooltipModule } from 'primeng/tooltip'; +import { DropdownModule } from 'primeng/dropdown'; +import { NewPolicyStatisticsDialog } from './policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component'; +import { TabViewModule } from 'primeng/tabview'; +import { CheckboxModule } from 'primeng/checkbox'; +import { RadioButtonModule } from 'primeng/radiobutton'; +import { CodemirrorModule } from '@ctrl/ngx-codemirror'; +import { MultiSelectModule } from 'primeng/multiselect'; +import { ScoreDialog } from './policy-statistics/dialogs/score-dialog/score-dialog.component'; +import { OverlayPanelModule } from 'primeng/overlaypanel'; +import { DragDropModule } from 'primeng/dragdrop'; +import { TreeModule } from 'primeng/tree'; +import { TreeDragDropService } from 'primeng/api'; +import { TieredMenuModule } from 'primeng/tieredmenu'; +import { StatisticAssessmentConfigurationComponent } from './policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component'; +import { StatisticAssessmentViewComponent } from './policy-statistics/statistic-assessment-view/statistic-assessment-view.component'; +import { StatisticAssessmentsComponent } from './policy-statistics/statistic-assessments/statistic-assessments.component'; +import { StatisticDefinitionsComponent } from './policy-statistics/statistic-definitions/statistic-definitions.component'; +import { StatisticDefinitionConfigurationComponent } from './policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component'; +import { StatisticPreviewDialog } from './policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component'; +import { PolicyLabelsComponent } from './policy-labels/policy-labels/policy-labels.component'; +import { PolicyLabelConfigurationComponent } from './policy-labels/policy-label-configuration/policy-label-configuration.component'; +import { NewPolicyLabelDialog } from './policy-labels/dialogs/new-policy-label-dialog/new-policy-label-dialog.component'; +import { PolicyLabelPreviewDialog } from './policy-labels/dialogs/policy-label-preview-dialog/policy-label-preview-dialog.component'; +import { SchemaRulesComponent } from './schema-rules/schema-rules/schema-rules.component'; +import { NewSchemaRuleDialog } from './schema-rules/dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component'; +import { SchemaRuleConfigurationComponent } from './schema-rules/schema-rule-configuration/schema-rule-configuration.component'; +import { SchemaRulesPreviewDialog } from './schema-rules/dialogs/schema-rules-preview-dialog/schema-rules-preview-dialog.component'; +import { SchemaRuleConfigDialog } from './schema-rules/dialogs/schema-rule-config-dialog/schema-rule-config-dialog.component'; +import { SearchLabelDialog } from './policy-labels/dialogs/search-label-dialog/search-label-dialog.component'; +import { PolicyLabelDocumentsComponent } from './policy-labels/policy-label-documents/policy-label-documents.component'; +import { PolicyLabelDocumentViewComponent } from './policy-labels/policy-label-document-view/policy-label-document-view.component'; +import { PolicyLabelDocumentConfigurationComponent } from './policy-labels/policy-label-document-configuration/policy-label-document-configuration.component'; + +@NgModule({ + declarations: [ + //policy-statistics + NewPolicyStatisticsDialog, + ScoreDialog, + StatisticPreviewDialog, + StatisticAssessmentConfigurationComponent, + StatisticAssessmentViewComponent, + StatisticAssessmentsComponent, + StatisticDefinitionConfigurationComponent, + StatisticDefinitionsComponent, + //policy-labels + PolicyLabelsComponent, + PolicyLabelConfigurationComponent, + PolicyLabelDocumentsComponent, + PolicyLabelDocumentConfigurationComponent, + PolicyLabelDocumentViewComponent, + NewPolicyLabelDialog, + PolicyLabelPreviewDialog, + SearchLabelDialog, + //schema-rules + SchemaRulesComponent, + SchemaRuleConfigurationComponent, + NewSchemaRuleDialog, + SchemaRulesPreviewDialog, + SchemaRuleConfigDialog + ], + imports: [ + CommonModule, + FormsModule, + MaterialModule, + CommonComponentsModule, + SchemaEngineModule, + AppRoutingModule, + DynamicDialogModule, + TableModule, + TooltipModule, + InputTextModule, + DropdownModule, + TabViewModule, + CheckboxModule, + RadioButtonModule, + CodemirrorModule, + MultiSelectModule, + OverlayPanelModule, + DragDropModule, + TreeModule, + TieredMenuModule, + AngularSvgIconModule.forRoot(), + ], + exports: [], + providers: [ + DialogService, + TreeDragDropService + ], +}) +export class StatisticsModule { } diff --git a/frontend/src/app/services/policy-labels.service.ts b/frontend/src/app/services/policy-labels.service.ts new file mode 100644 index 0000000000..c08c60f8b4 --- /dev/null +++ b/frontend/src/app/services/policy-labels.service.ts @@ -0,0 +1,147 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { API_BASE_URL } from './api'; + +/** + * Services for working from labels and separate blocks. + */ +@Injectable() +export class PolicyLabelsService { + private readonly url: string = `${API_BASE_URL}/policy-labels`; + + constructor(private http: HttpClient) { + } + + public static getOptions( + filters: any, + pageIndex?: number, + pageSize?: number + ): HttpParams { + let params = new HttpParams(); + if (filters && typeof filters === 'object') { + for (const key of Object.keys(filters)) { + if (filters[key]) { + params = params.set(key, filters[key]); + } + } + } + if (Number.isInteger(pageIndex) && Number.isInteger(pageSize)) { + params = params.set('pageIndex', String(pageIndex)); + params = params.set('pageSize', String(pageSize)); + } + return params; + } + + + public parsePage(response: HttpResponse) { + const page = response.body || []; + const count = Number(response.headers.get('X-Total-Count')) || page.length; + return { page, count }; + } + + public getLabels( + pageIndex?: number, + pageSize?: number, + filters?: any + ): Observable> { + const params = PolicyLabelsService.getOptions(filters, pageIndex, pageSize); + return this.http.get(`${this.url}`, { observe: 'response', params }); + } + + public createLabel(item: any): Observable { + return this.http.post(`${this.url}/`, item); + } + + public getLabel(definitionId: string): Observable { + return this.http.get(`${this.url}/${definitionId}`); + } + + public deleteLabel(definitionId: any): Observable { + return this.http.delete(`${this.url}/${definitionId}`); + } + + public updateLabel(item: any): Observable { + return this.http.put(`${this.url}/${item.id}`, item); + } + + public publish(item: any): Observable { + return this.http.put(`${this.url}/${item.id}/publish`, item); + } + + public pushPublish(item: any): Observable<{ taskId: string, expectation: number }> { + return this.http.put<{ taskId: string, expectation: number }>(`${this.url}/push/${item.id}/publish`, item); + } + + public getRelationships(definitionId: string): Observable<{ + policy: any, + policySchemas: any[], + documentsSchemas: any[] + }> { + return this.http.get(`${this.url}/${definitionId}/relationships`); + } + + public export(definitionId: string): Observable { + return this.http.get(`${this.url}/${definitionId}/export/file`, { + responseType: 'arraybuffer' + }); + } + + public import(policyId: string, file: any): Observable { + return this.http.post(`${this.url}/${policyId}/import/file`, file, { + headers: { + 'Content-Type': 'binary/octet-stream' + } + }); + } + + public previewByFile(policyFile: any): Observable { + return this.http.post(`${this.url}/import/file/preview`, policyFile, { + headers: { + 'Content-Type': 'binary/octet-stream' + } + }); + } + + public searchComponents(options: any): Observable { + return this.http.post(`${this.url}/components`, options); + } + + public getTokens( + definitionId: string, + pageIndex?: number, + pageSize?: number, + ): Observable> { + const params = PolicyLabelsService.getOptions({}, pageIndex, pageSize); + return this.http.get(`${this.url}/${definitionId}/tokens`, { observe: 'response', params }); + } + + public getTokenDocuments( + documentId: string, + definitionId: string, + ): Observable { + return this.http.get(`${this.url}/${definitionId}/tokens/${documentId}`); + } + + public createLabelDocument(definitionId: string, item: any): Observable { + return this.http.post(`${this.url}/${definitionId}/documents`, item); + } + + public getLabelDocuments( + definitionId: string, + pageIndex?: number, + pageSize?: number, + filters?: any + ): Observable> { + const params = PolicyLabelsService.getOptions(filters, pageIndex, pageSize); + return this.http.get(`${this.url}/${definitionId}/documents`, { observe: 'response', params }); + } + + public getLabelDocument(definitionId: string, documentId: any): Observable { + return this.http.get(`${this.url}/${definitionId}/documents/${documentId}`); + } + + public getLabelDocumentRelationships(definitionId: string, documentId: any): Observable { + return this.http.get(`${this.url}/${definitionId}/documents/${documentId}/relationships`); + } +} diff --git a/frontend/src/app/themes/guardian/dropdown.scss b/frontend/src/app/themes/guardian/dropdown.scss index 7c8ee75522..11e394c090 100644 --- a/frontend/src/app/themes/guardian/dropdown.scss +++ b/frontend/src/app/themes/guardian/dropdown.scss @@ -7,6 +7,7 @@ border-radius: 8px; outline: 0 none; height: 40px; + width: 100%; &:not(.p-disabled):hover { border-color: var(--guardian-primary-color, #4169E2); @@ -141,4 +142,22 @@ pointer-events: none; } } + + .p-dropdown-item:not(.p-highlight):not(.p-disabled).p-focus { + position: relative; + background: none !important; + + &::before { + content: ""; + display: block; + position: absolute; + background: var(--guardian-primary-color, #4169E2); + opacity: 0.08; + left: 0; + top: 0; + right: 0; + bottom: 0; + pointer-events: none; + } + } } \ No newline at end of file diff --git a/frontend/src/app/themes/guardian/index.scss b/frontend/src/app/themes/guardian/index.scss index 06b43b7b8e..45987dd44f 100644 --- a/frontend/src/app/themes/guardian/index.scss +++ b/frontend/src/app/themes/guardian/index.scss @@ -22,4 +22,6 @@ @import 'textarea.scss'; @import 'select-button.scss'; @import 'accordion.scss'; + @import 'tree.scss'; + @import 'menu-button.scss'; } \ No newline at end of file diff --git a/frontend/src/app/themes/guardian/menu-button.scss b/frontend/src/app/themes/guardian/menu-button.scss new file mode 100644 index 0000000000..30642b3001 --- /dev/null +++ b/frontend/src/app/themes/guardian/menu-button.scss @@ -0,0 +1,148 @@ +.guardian-menu-container { + width: 100%; + height: 70px; + min-height: 70px; + overflow: hidden; + background: #fff; + border-bottom: 1px solid #E1E7EF; + display: flex; + flex-direction: row; + padding-left: 12px; +} + +.guardian-menu-label, +.guardian-menu-button { + width: 70px; + height: 70px; + min-width: 70px; + min-height: 70px; + overflow: hidden; + display: flex; + flex-direction: column; + cursor: pointer; + justify-content: center; + align-items: center; + position: relative; + + &>* { + pointer-events: none; + } + + &.guardian-menu-select { + width: 86px; + min-width: 86px; + padding-right: 16px; + + .guardian-menu-select-icon { + position: absolute; + top: 0; + bottom: 0; + right: 0; + width: 16px; + background: #e1e6fa; + + &::before { + content: ""; + display: block; + position: absolute; + width: 5px; + height: 5px; + left: 4px; + top: 28px; + border-left: 2px solid var(--primary-primary, #4169E2); + border-bottom: 2px solid var(--primary-primary, #4169E2); + pointer-events: none; + transform: rotate(-45deg); + } + } + } + + &:hover { + background: #f0f3fc; + + .guardian-menu-select-icon { + background: #dde3f6; + } + } + + &[open="true"] { + background: #e1e7fa !important; + + .guardian-menu-select-icon { + background: #c4d0f3 !important; + } + } + + div { + font-family: Inter; + font-size: 12px; + font-weight: 500; + line-height: 24px; + color: var(--guardian-primary-color); + } + + svg-icon svg { + fill: var(--guardian-primary-color); + color: var(--guardian-primary-color); + } + + &[disabled="true"] { + pointer-events: none; + + div { + color: var(--guardian-disabled-icon); + } + + svg-icon svg { + fill: var(--guardian-disabled-icon); + color: var(--guardian-disabled-icon); + } + } + + &.success-color { + div { + color: var(--guardian-success-color); + } + + svg-icon svg { + fill: var(--guardian-success-color); + color: var(--guardian-success-color); + } + } +} + +.guardian-menu-label { + pointer-events: none; +} + +.guardian-menu-separator { + width: 5px; + height: 70px; + position: relative; + + &::before { + content: ""; + display: block; + position: absolute; + left: 2px; + top: 0; + bottom: 0; + width: 3px; + border-left: 1px solid #E1E7EF; + pointer-events: none; + } + + &.guardian-menu-separator-last { + &::before { + left: 0px; + } + } +} + +.guardian-menu-popup { + width: 70px; + + .p-menuitem-content { + background: #fff !important; + } +} \ No newline at end of file diff --git a/frontend/src/app/themes/guardian/separator.scss b/frontend/src/app/themes/guardian/separator.scss index 3337c48679..60f94215ae 100644 --- a/frontend/src/app/themes/guardian/separator.scss +++ b/frontend/src/app/themes/guardian/separator.scss @@ -99,6 +99,17 @@ } } + .guardian-step-template { + width: 24px; + height: 24px; + border: 1px solid var(--guardian-grey-color-3, #AAB7C4); + background: var(--guardian-grey-color, #EFF3F7); + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; + } + .guardian-step-name { height: 24px; padding: 2px 8px; @@ -135,6 +146,11 @@ } } + .guardian-step-template { + background: var(--guardian-primary-color, #4169E2); + border: 1px solid var(--guardian-primary-color, #4169E2); + } + .guardian-step-name { color: var(--guardian-primary-color, #4169E2); } diff --git a/frontend/src/app/themes/guardian/tree.scss b/frontend/src/app/themes/guardian/tree.scss new file mode 100644 index 0000000000..ecf337272a --- /dev/null +++ b/frontend/src/app/themes/guardian/tree.scss @@ -0,0 +1,11 @@ +.guardian-tree { + .p-tree { + border: none; + background: none; + padding: 0; + } + + .p-tree-container { + width: fit-content; + } +} \ No newline at end of file diff --git a/frontend/src/app/utils/formula.ts b/frontend/src/app/utils/formula.ts deleted file mode 100644 index 6ef3813538..0000000000 --- a/frontend/src/app/utils/formula.ts +++ /dev/null @@ -1,36 +0,0 @@ -import * as formulajs from '@formulajs/formulajs' -import { create, all, ImportObject } from 'mathjs'; - -const mathjs = create(all); - -const exclude = new Set(['PI']) -const customFunctions: ImportObject = {}; -for (const [name, f] of Object.entries(formulajs)) { - if (typeof f === 'function' && !exclude.has(name)) { - customFunctions[name] = function (...args: any) { - return (f as any).apply(null, args); - } - } -} -mathjs.import(customFunctions); -mathjs.import({ - equal: function (a: any, b: any) { return a == b } -}, { override: true }) - -export abstract class Formula { - /** - * Evaluate expressions - * @param formula - * @param scope - */ - public static evaluate(formula: string, scope: any): any { - const ex = formula.trim().trim().replace(/^=/, ''); - return (function (mathjs: any, _formula: string, _scope: any) { - try { - return mathjs.evaluate(_formula, _scope); - } catch (error) { - return 'Incorrect formula'; - } - }).call(null, mathjs, ex, scope); - } -} \ No newline at end of file diff --git a/frontend/src/app/utils/index.ts b/frontend/src/app/utils/index.ts index cef58f04d8..d239c21f73 100644 --- a/frontend/src/app/utils/index.ts +++ b/frontend/src/app/utils/index.ts @@ -1,5 +1,4 @@ export { ActionGroup } from "./permissions-action"; export { CategoryAccess, CategoryDetails, CategoryGroup } from "./permissions-category"; export { EntityAccess, EntityGroup } from "./permissions-entity"; -export { PermissionsGroup } from "./permissions"; -export { Formula } from "./formula"; \ No newline at end of file +export { PermissionsGroup } from "./permissions"; \ No newline at end of file diff --git a/frontend/src/app/utils/permissions-interface.ts b/frontend/src/app/utils/permissions-interface.ts index 1eaf075441..7ec5cf6a05 100644 --- a/frontend/src/app/utils/permissions-interface.ts +++ b/frontend/src/app/utils/permissions-interface.ts @@ -122,7 +122,8 @@ export const entityNames = new Map([ [PermissionEntities.TOKEN, 'Token'], [PermissionEntities.TRUST_CHAIN, 'Trust Chain'], [PermissionEntities.ROLE, 'Role'], - [PermissionEntities.STATISTIC, 'Statistic'] + [PermissionEntities.STATISTIC, 'Statistic'], + [PermissionEntities.LABEL, 'Label'] ]) export const actionIndexes = new Map([ diff --git a/frontend/src/app/views/new-header/menu.model.ts b/frontend/src/app/views/new-header/menu.model.ts index 82f3f5e13a..90ecc11531 100644 --- a/frontend/src/app/views/new-header/menu.model.ts +++ b/frontend/src/app/views/new-header/menu.model.ts @@ -159,6 +159,12 @@ function customMenu(user: UserPermissions): NavbarMenuItem[] { routerLink: '/schema-rules' }); } + if (user.STATISTICS_LABEL_READ) { + childItems.push({ + title: 'Policy Labels', + routerLink: '/policy-labels' + }); + } } if ( user.SCHEMAS_SCHEMA_READ || diff --git a/frontend/src/assets/images/icons/16/circle-check.svg b/frontend/src/assets/images/icons/16/circle-check.svg new file mode 100644 index 0000000000..ed4b27235d --- /dev/null +++ b/frontend/src/assets/images/icons/16/circle-check.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/src/assets/images/icons/16/collapse-down.svg b/frontend/src/assets/images/icons/16/collapse-down.svg new file mode 100644 index 0000000000..b4fae0712a --- /dev/null +++ b/frontend/src/assets/images/icons/16/collapse-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/images/icons/16/collapse-left.svg b/frontend/src/assets/images/icons/16/collapse-left.svg new file mode 100644 index 0000000000..b1eb7ed776 --- /dev/null +++ b/frontend/src/assets/images/icons/16/collapse-left.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/images/icons/16/collapse-right.svg b/frontend/src/assets/images/icons/16/collapse-right.svg new file mode 100644 index 0000000000..56fec13302 --- /dev/null +++ b/frontend/src/assets/images/icons/16/collapse-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/images/icons/16/collapse-up.svg b/frontend/src/assets/images/icons/16/collapse-up.svg new file mode 100644 index 0000000000..f631171a9b --- /dev/null +++ b/frontend/src/assets/images/icons/16/collapse-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/images/icons/16/edit.svg b/frontend/src/assets/images/icons/16/edit.svg new file mode 100644 index 0000000000..95864843dd --- /dev/null +++ b/frontend/src/assets/images/icons/16/edit.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/src/assets/images/icons/16/folder.svg b/frontend/src/assets/images/icons/16/folder.svg new file mode 100644 index 0000000000..4a198286cb --- /dev/null +++ b/frontend/src/assets/images/icons/16/folder.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/src/assets/images/icons/16/import.svg b/frontend/src/assets/images/icons/16/import.svg new file mode 100644 index 0000000000..25c9bed794 --- /dev/null +++ b/frontend/src/assets/images/icons/16/import.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/src/assets/images/icons/16/order.svg b/frontend/src/assets/images/icons/16/order.svg new file mode 100644 index 0000000000..ba85831197 --- /dev/null +++ b/frontend/src/assets/images/icons/16/order.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/src/assets/images/icons/16/stats.svg b/frontend/src/assets/images/icons/16/stats.svg new file mode 100644 index 0000000000..a9cf287764 --- /dev/null +++ b/frontend/src/assets/images/icons/16/stats.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/images/icons/32/circle-check.svg b/frontend/src/assets/images/icons/32/circle-check.svg new file mode 100644 index 0000000000..1752c14ebb --- /dev/null +++ b/frontend/src/assets/images/icons/32/circle-check.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/images/icons/back.svg b/frontend/src/assets/images/icons/back.svg new file mode 100644 index 0000000000..3c0d3afe01 --- /dev/null +++ b/frontend/src/assets/images/icons/back.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/images/icons/circle-check.svg b/frontend/src/assets/images/icons/circle-check.svg new file mode 100644 index 0000000000..87efbb0a5a --- /dev/null +++ b/frontend/src/assets/images/icons/circle-check.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/images/icons/collapse-down.svg b/frontend/src/assets/images/icons/collapse-down.svg new file mode 100644 index 0000000000..099edf00c4 --- /dev/null +++ b/frontend/src/assets/images/icons/collapse-down.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/src/assets/images/icons/collapse-left.svg b/frontend/src/assets/images/icons/collapse-left.svg new file mode 100644 index 0000000000..595beaf9f4 --- /dev/null +++ b/frontend/src/assets/images/icons/collapse-left.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/src/assets/images/icons/collapse-right.svg b/frontend/src/assets/images/icons/collapse-right.svg new file mode 100644 index 0000000000..2c777e1bc0 --- /dev/null +++ b/frontend/src/assets/images/icons/collapse-right.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/src/assets/images/icons/collapse-up.svg b/frontend/src/assets/images/icons/collapse-up.svg new file mode 100644 index 0000000000..bda58a2b15 --- /dev/null +++ b/frontend/src/assets/images/icons/collapse-up.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/src/assets/images/icons/file.svg b/frontend/src/assets/images/icons/file.svg new file mode 100644 index 0000000000..25f2fbaecf --- /dev/null +++ b/frontend/src/assets/images/icons/file.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/images/icons/folder.svg b/frontend/src/assets/images/icons/folder.svg new file mode 100644 index 0000000000..84e047c9f4 --- /dev/null +++ b/frontend/src/assets/images/icons/folder.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/images/icons/preview.svg b/frontend/src/assets/images/icons/preview.svg new file mode 100644 index 0000000000..c82839ffce --- /dev/null +++ b/frontend/src/assets/images/icons/preview.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/images/icons/publish.svg b/frontend/src/assets/images/icons/publish.svg new file mode 100644 index 0000000000..7f76dcb740 --- /dev/null +++ b/frontend/src/assets/images/icons/publish.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/images/icons/save.svg b/frontend/src/assets/images/icons/save.svg new file mode 100644 index 0000000000..bf76efd4b7 --- /dev/null +++ b/frontend/src/assets/images/icons/save.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/images/icons/stats.svg b/frontend/src/assets/images/icons/stats.svg new file mode 100644 index 0000000000..a549dd5a8e --- /dev/null +++ b/frontend/src/assets/images/icons/stats.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/main.ts b/frontend/src/main.ts index 39ca242efe..1166226251 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -17,9 +17,33 @@ import 'codemirror/addon/fold/indent-fold'; import 'codemirror/addon/edit/closebrackets'; import 'codemirror/addon/edit/matchbrackets'; +import { FormulaEngine } from '@guardian/interfaces'; +import * as formulajs from '@formulajs/formulajs' +import { create, all, ImportObject } from 'mathjs'; + +function createMathjs() { + const mathjs = create(all); + const exclude = new Set(['PI']) + const customFunctions: ImportObject = {}; + for (const [name, f] of Object.entries(formulajs)) { + if (typeof f === 'function' && !exclude.has(name)) { + customFunctions[name] = function (...args: any) { + return (f as any).apply(null, args); + } + } + } + mathjs.import(customFunctions); + mathjs.import({ + equal: function (a: any, b: any) { return a == b } + }, { override: true }); + return mathjs; +} + +FormulaEngine.setMathEngine(createMathjs()) + if (environment.production) { - enableProdMode(); + enableProdMode(); } platformBrowserDynamic().bootstrapModule(AppModule) - .catch(err => console.error(err)); + .catch(err => console.error(err)); diff --git a/guardian-service/package.json b/guardian-service/package.json index e38a0c2358..1301db527a 100644 --- a/guardian-service/package.json +++ b/guardian-service/package.json @@ -33,6 +33,8 @@ "@transmute/jsonld-schema": "0.7.0-unstable.80", "@transmute/security-context": "0.7.0-unstable.80", "@transmute/vc.js": "0.7.0-unstable.80", + "@formulajs/formulajs": "4.4.6", + "mathjs": "^13.1.1", "ajv": "^8.10.0", "ajv-formats": "^2.1.1", "axios": "^1.3.6", @@ -47,7 +49,6 @@ "imurmurhash": "^0.1.4", "jszip": "^3.7.1", "lodash.get": "^4.4.2", - "mathjs": "^10.1.0", "module-alias": "^2.2.2", "moment": "^2.29.2", "mongodb": "6.5.0", diff --git a/guardian-service/src/api/helpers/policy-labels-helpers.ts b/guardian-service/src/api/helpers/policy-labels-helpers.ts new file mode 100644 index 0000000000..f8e3d06cca --- /dev/null +++ b/guardian-service/src/api/helpers/policy-labels-helpers.ts @@ -0,0 +1,269 @@ +import { + DatabaseServer, + HederaDidDocument, + PolicyLabel, + VcHelper, + TopicConfig, + TopicHelper, + Users, + Schema as SchemaCollection, + VcDocument as VcDocumentCollection, + VpDocument as VpDocumentCollection, + SchemaConverterUtils, +} from '@guardian/common'; +import { + GenerateUUIDv4, + INavItemConfig, + IOwner, + IPolicyLabelConfig, + IStepDocument, + NavItemType, + PolicyType, + Schema, + SchemaCategory, + SchemaHelper, + SchemaStatus, + TopicType +} from '@guardian/interfaces'; +import { generateSchema as generateStatisticSchema } from './policy-statistics-helpers.js'; +import { generateSchemaContext } from './schema-publish-helper.js'; + +export function publishLabelConfig(data?: IPolicyLabelConfig): IPolicyLabelConfig { + return data; +} + +export async function getOrCreateTopic(item: PolicyLabel): Promise { + let topic: TopicConfig; + if (item.topicId) { + topic = await TopicConfig.fromObject(await DatabaseServer.getTopicById(item.topicId), true); + if (topic) { + return topic; + } + } + + const policy = await DatabaseServer.getPolicyById(item.policyId); + if (!policy || policy.status !== PolicyType.PUBLISH) { + throw Error('Item does not exist.'); + } + + const rootTopic = await TopicConfig.fromObject(await DatabaseServer.getTopicById(policy.instanceTopicId), true); + const root = await (new Users()).getHederaAccount(item.owner); + const topicHelper = new TopicHelper(root.hederaAccountId, root.hederaAccountKey, root.signOptions); + topic = await topicHelper.create({ + type: TopicType.LabelTopic, + owner: policy.owner, + name: 'POLICY_LABELS', + description: 'POLICY_LABELS', + policyId: policy.id, + policyUUID: policy.uuid + }, { admin: true, submit: false }); + await topic.saveKeys(); + await topicHelper.twoWayLink(topic, rootTopic, null); + await DatabaseServer.saveTopic(topic.toObject()); + return topic; +} + +export async function generateSchema( + topicId: string, + config: IPolicyLabelConfig, + owner: IOwner +): Promise<{ + node: any, + schema: SchemaCollection +}[]> { + if (!config) { + return []; + } + const items = convertConfigToList([], config.children); + const schemas: any[] = []; + const groupSchema = await generateGroupSchema(topicId, 'Group', owner); + schemas.push({ node: config, schema: groupSchema }); + for (const node of items) { + if (node.type === NavItemType.Statistic) { + const schema = await generateStatisticSchema(topicId, node.config, owner, false); + schemas.push({ node, schema }); + } + if (node.type === NavItemType.Rules) { + const schema = await generateStatisticSchema(topicId, node.config, owner, true); + schemas.push({ node, schema }); + } + if (node.type === NavItemType.Group) { + schemas.push({ node, schema: groupSchema }); + } + if (node.type === NavItemType.Label) { + schemas.push({ node, schema: groupSchema }); + } + } + return schemas; +} + +function convertConfigToList( + result: INavItemConfig[], + items?: INavItemConfig[] +): INavItemConfig[] { + if (Array.isArray(items)) { + for (const item of items) { + result.push(item); + if (item.type === NavItemType.Group) { + convertConfigToList(result, item.children); + } + if (item.type === NavItemType.Label) { + convertConfigToList(result, item.config?.children); + } + } + } + return result; +} + +export async function findRelationships( + target: VcDocumentCollection | VpDocumentCollection +): Promise { + if (!target) { + return []; + } + + const messageIds = new Set(); + messageIds.add(target.messageId); + + const result: VcDocumentCollection[] = []; + if (Array.isArray(target.relationships)) { + for (const relationship of target.relationships) { + await findRelationshipsById(relationship, messageIds, result); + } + } + + return result; +} + +export async function findRelationshipsById( + messageId: string | undefined, + map: Set, + result: VcDocumentCollection[] +): Promise { + if (!messageId || map.has(messageId)) { + return result; + } + map.add(messageId); + const doc = await DatabaseServer.getStatisticDocument({ messageId }); + if (doc) { + result.push(doc); + if (Array.isArray(doc.relationships)) { + for (const relationship of doc.relationships) { + await findRelationshipsById(relationship, map, result); + } + } + } + return result; +} + +export async function generateVpDocument( + documents: IStepDocument[], + schemas: Schema[], + owner: IOwner +) { + const uuid = GenerateUUIDv4(); + const vcHelper = new VcHelper(); + const didDocument = await vcHelper.loadDidDocument(owner.creator); + + const vcObjects: any[] = []; + for (const vc of documents) { + const schemaObject = schemas.find((s) => s.iri === vc.schema); + const vcObject = await generateVcDocument(vc.document, schemaObject, didDocument, vcHelper); + vcObjects.push(vcObject); + } + + const vpObject = await vcHelper.createVerifiablePresentation( + vcObjects, + didDocument, + null, + { uuid } + ); + return vpObject; +} + +export async function generateVcDocument( + document: any, + schema: Schema, + didDocument: HederaDidDocument, + vcHelper: VcHelper +) { + document.id = GenerateUUIDv4(); + if (schema) { + document = SchemaHelper.updateObjectContext(schema, document); + } + + const res = await vcHelper.verifySubject(document); + if (!res.ok) { + throw Error(JSON.stringify(res.error)); + } + + const vcObject = await vcHelper.createVerifiableCredential(document, didDocument, null, null); + return vcObject; +} + +export async function generateGroupSchema(topicId: string, type: string, owner: IOwner) { + const uuid = type; + const document: any = { + $id: `#${uuid}`, + $comment: `{ "term": "${uuid}", "@id": "#${uuid}" }`, + title: `${uuid}`, + description: `${uuid}`, + type: 'object', + properties: { + '@context': { + oneOf: [{ + type: 'string' + }, { + type: 'array', + items: { + type: 'string' + } + }], + readOnly: true + }, + type: { + oneOf: [{ + type: 'string' + }, { + type: 'array', + items: { + type: 'string' + } + }], + readOnly: true + }, + id: { + type: 'string', + readOnly: true + }, + status: { + $comment: `{"term": "status", "@id": "https://www.schema.org/text"}`, + title: 'status', + description: 'Status', + type: 'boolean', + readOnly: false + } + }, + required: [], + additionalProperties: false + } + const newSchema: any = {}; + newSchema.category = SchemaCategory.STATISTIC; + newSchema.readonly = true; + newSchema.system = false; + newSchema.uuid = uuid + newSchema.status = SchemaStatus.PUBLISHED; + newSchema.document = document; + newSchema.context = generateSchemaContext(newSchema); + newSchema.iri = `${uuid}`; + newSchema.codeVersion = SchemaConverterUtils.VERSION; + newSchema.documentURL = `schema:${uuid}`; + newSchema.contextURL = `schema:${uuid}`; + newSchema.topicId = topicId; + newSchema.creator = owner.creator; + newSchema.owner = owner.owner; + const schemaObject = DatabaseServer.createSchema(newSchema); + SchemaHelper.setVersion(schemaObject, '1.0.0', null); + SchemaHelper.updateIRI(schemaObject); + return schemaObject; +} diff --git a/guardian-service/src/api/helpers/policy-statistics-helpers.ts b/guardian-service/src/api/helpers/policy-statistics-helpers.ts index 1a9cf46c4b..874bcccb89 100644 --- a/guardian-service/src/api/helpers/policy-statistics-helpers.ts +++ b/guardian-service/src/api/helpers/policy-statistics-helpers.ts @@ -58,11 +58,16 @@ export async function findRelationships( return subDocs.filter((doc) => prevRelationships.has(doc.messageId) || nextRelationships.has(doc.messageId)); } -export async function generateSchema(config: PolicyStatistic, owner: IOwner) { +export async function generateSchema( + topicId: string, + config: IStatisticConfig, + owner: IOwner, + rules: boolean = false +) { const uuid = GenerateUUIDv4(); - const variables = config.config?.variables || []; - const scores = config.config?.scores || []; - const formulas = config.config?.formulas || []; + const variables = config?.variables || []; + const scores = config?.scores || []; + const formulas = config?.formulas || []; const properties: any = {} for (const variable of variables) { properties[variable.id] = { @@ -112,6 +117,15 @@ export async function generateSchema(config: PolicyStatistic, owner: IOwner) { readOnly: false } } + if (rules) { + properties.status = { + $comment: `{"term": "status", "@id": "https://www.schema.org/text"}`, + title: 'status', + description: 'Status', + type: 'boolean', + readOnly: false + } + } const document: any = { $id: `#${uuid}`, $comment: `{ "term": "${uuid}", "@id": "#${uuid}" }`, @@ -162,7 +176,7 @@ export async function generateSchema(config: PolicyStatistic, owner: IOwner) { newSchema.codeVersion = SchemaConverterUtils.VERSION; newSchema.documentURL = `schema:${uuid}`; newSchema.contextURL = `schema:${uuid}`; - newSchema.topicId = config.topicId; + newSchema.topicId = topicId; newSchema.creator = owner.creator; newSchema.owner = owner.owner; const schemaObject = DatabaseServer.createSchema(newSchema); diff --git a/guardian-service/src/api/helpers/schema-publish-helper.ts b/guardian-service/src/api/helpers/schema-publish-helper.ts index 8f787e2b68..7f74dccb2f 100644 --- a/guardian-service/src/api/helpers/schema-publish-helper.ts +++ b/guardian-service/src/api/helpers/schema-publish-helper.ts @@ -111,6 +111,41 @@ export async function publishSchema( return item; } +/** + * Publish schemas + * @param schemas + * @param messageServer + * @param owner + * @param notifier + */ +export async function publishSchemas( + schemas: Iterable, + owner: IOwner, + messageServer: MessageServer, + type: MessageAction +): Promise { + const tasks = []; + for (const schema of schemas) { + tasks.push(publishSchema(schema, owner, messageServer, type)); + } + await Promise.all(tasks); +} + +/** + * Save schemas + * @param schemas + * @param messageServer + * @param owner + * @param notifier + */ +export async function saveSchemas( + schemas: Iterable +): Promise { + for (const schema of schemas) { + await DatabaseServer.createAndSaveSchema(schema); + } +} + /** * Publishing schemas in defs * @param defs Definitions diff --git a/guardian-service/src/api/policy-labels.service.ts b/guardian-service/src/api/policy-labels.service.ts new file mode 100644 index 0000000000..db76997a24 --- /dev/null +++ b/guardian-service/src/api/policy-labels.service.ts @@ -0,0 +1,899 @@ +import { ApiResponse } from './helpers/api-response.js'; +import { + BinaryMessageResponse, + DatabaseServer, + LabelDocumentMessage, + LabelMessage, + MessageAction, + MessageError, + MessageResponse, + MessageServer, + PinoLogger, + PolicyImportExport, + PolicyLabel, + PolicyLabelImportExport, + Users, + Schema as SchemaCollection, + RunFunctionAsync, +} from '@guardian/common'; +import { EntityStatus, IOwner, LabelValidators, MessageAPI, PolicyType, Schema, SchemaStatus } from '@guardian/interfaces'; +import { findRelationships, generateSchema, generateVpDocument, getOrCreateTopic, publishLabelConfig } from './helpers/policy-labels-helpers.js'; +import { publishSchemas, saveSchemas } from './helpers/index.js'; +import { emptyNotifier, initNotifier, INotifier } from '../helpers/notifier.js'; + +async function publishPolicyLabel( + item: PolicyLabel, + owner: IOwner, + notifier: INotifier, + logger: PinoLogger +): Promise { + item.status = EntityStatus.PUBLISHED; + item.config = PolicyLabelImportExport.validateConfig(item.config); + item.config = publishLabelConfig(item.config); + + notifier.start('Create topic'); + const topic = await getOrCreateTopic(item); + const user = await (new Users()).getHederaAccount(owner.creator); + const messageServer = new MessageServer(user.hederaAccountId, user.hederaAccountKey, user.signOptions); + messageServer.setTopicObject(topic); + + notifier.completedAndStart('Generate schemas'); + const schemas = await generateSchema(topic.topicId, item.config, owner); + const schemaList = new Set(); + for (const { schema } of schemas) { + schemaList.add(schema); + } + + notifier.completedAndStart('Publish schemas'); + await publishSchemas(schemaList, owner, messageServer, MessageAction.PublishSchema); + await saveSchemas(schemaList); + for (const { node, schema } of schemas) { + node.schemaId = schema.iri; + } + + notifier.completedAndStart('Publish label'); + const zip = await PolicyLabelImportExport.generate(item); + const buffer = await zip.generateAsync({ + type: 'arraybuffer', + compression: 'DEFLATE', + compressionOptions: { + level: 3 + } + }); + + const statMessage = new LabelMessage(MessageAction.PublishPolicyLabel); + statMessage.setDocument(item, buffer); + const statMessageResult = await messageServer + .sendMessage(statMessage); + + item.topicId = topic.topicId; + item.messageId = statMessageResult.getId(); + + const result = await DatabaseServer.updatePolicyLabel(item); + return result; +} + +/** + * Connect to the message broker methods of working with policy labels. + */ +export async function policyLabelsAPI(logger: PinoLogger): Promise { + /** + * Create new policy label + * + * @param payload - policy label + * + * @returns {any} new policy label + */ + ApiResponse(MessageAPI.CREATE_POLICY_LABEL, + async (msg: { label: PolicyLabel, owner: IOwner }) => { + try { + if (!msg) { + return new MessageError('Invalid parameters.'); + } + const { label, owner } = msg; + + if (!label) { + return new MessageError('Invalid object.'); + } + + const policyId = label.policyId; + const policy = await DatabaseServer.getPolicyById(policyId); + if (!policy || policy.status !== PolicyType.PUBLISH) { + return new MessageError('Item does not exist.'); + } + + delete label._id; + delete label.id; + delete label.status; + delete label.owner; + label.creator = owner.creator; + label.owner = owner.owner; + label.policyTopicId = policy.topicId; + label.policyInstanceTopicId = policy.instanceTopicId; + label.status = EntityStatus.DRAFT; + label.config = PolicyLabelImportExport.validateConfig(label.config); + const row = await DatabaseServer.createPolicyLabel(label); + return new MessageResponse(row); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Get policy labels + * + * @param {any} msg - filters + * + * @returns {any} - policy labels + */ + ApiResponse(MessageAPI.GET_POLICY_LABELS, + async (msg: { filters: any, owner: IOwner }) => { + try { + if (!msg) { + return new MessageError('Invalid parameters.'); + } + const { filters, owner } = msg; + const { policyInstanceTopicId, pageIndex, pageSize } = filters; + + const otherOptions: any = {}; + const _pageSize = parseInt(pageSize, 10); + const _pageIndex = parseInt(pageIndex, 10); + if (Number.isInteger(_pageSize) && Number.isInteger(_pageIndex)) { + otherOptions.orderBy = { createDate: 'DESC' }; + otherOptions.limit = _pageSize; + otherOptions.offset = _pageIndex * _pageSize; + } else { + otherOptions.orderBy = { createDate: 'DESC' }; + otherOptions.limit = 100; + } + otherOptions.fields = [ + 'id', + 'creator', + 'owner', + 'name', + 'description', + 'status', + 'policyId', + 'config', + 'topicId', + 'messageId' + ]; + const query: any = { + owner: owner.owner + }; + if (policyInstanceTopicId) { + query.policyInstanceTopicId = policyInstanceTopicId; + } + const [items, count] = await DatabaseServer.getPolicyLabelsAndCount(query, otherOptions); + return new MessageResponse({ items, count }); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Get policy label + * + * @param {any} msg - policy label id + * + * @returns {any} - policy label + */ + ApiResponse(MessageAPI.GET_POLICY_LABEL, + async (msg: { definitionId: string, owner: IOwner }) => { + try { + if (!msg) { + return new MessageError('Invalid parameters.'); + } + const { definitionId, owner } = msg; + const item = await DatabaseServer.getPolicyLabelById(definitionId); + if (!(item && item.owner === owner.owner)) { + return new MessageError('Item does not exist.'); + } + return new MessageResponse(item); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Get relationships + * + * @param {any} msg - policy label id + * + * @returns {any} - relationships + */ + ApiResponse(MessageAPI.GET_POLICY_LABEL_RELATIONSHIPS, + async (msg: { definitionId: string, owner: IOwner }) => { + try { + if (!msg) { + return new MessageError('Invalid parameters.'); + } + const { definitionId, owner } = msg; + const item = await DatabaseServer.getPolicyLabelById(definitionId); + if (!(item && item.owner === owner.owner)) { + return new MessageError('Item does not exist.'); + } + const policyId = item.policyId; + const policy = await DatabaseServer.getPolicyById(policyId); + if (!policy || policy.status !== PolicyType.PUBLISH) { + return new MessageError('Item does not exist.'); + } + const { schemas, toolSchemas } = await PolicyImportExport.loadAllSchemas(policy); + const all = [] + .concat(schemas, toolSchemas) + .filter((s) => s.status === SchemaStatus.PUBLISHED && s.entity !== 'EVC'); + + const documentsSchemas = await DatabaseServer.getSchemas({ topicId: item.topicId }); + + return new MessageResponse({ + policy, + policySchemas: all, + documentsSchemas + }); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Update policy label + * + * @param payload - policy label + * + * @returns policy label + */ + ApiResponse(MessageAPI.UPDATE_POLICY_LABEL, + async (msg: { + definitionId: string, + label: PolicyLabel, + owner: IOwner + }) => { + try { + if (!msg) { + return new MessageError('Invalid parameters.'); + } + const { definitionId, label, owner } = msg; + + const item = await DatabaseServer.getPolicyLabelById(definitionId); + if (!item || item.owner !== owner.owner) { + return new MessageError('Item does not exist.'); + } + if (item.status === EntityStatus.ACTIVE) { + return new MessageError('Item is active.'); + } + + item.name = label.name; + item.description = label.description; + item.config = PolicyLabelImportExport.validateConfig(label.config); + const result = await DatabaseServer.updatePolicyLabel(item); + return new MessageResponse(result); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Delete policy label + * + * @param {any} msg - policy label id + * + * @returns {boolean} - Operation success + */ + ApiResponse(MessageAPI.DELETE_POLICY_LABEL, + async (msg: { definitionId: string, owner: IOwner }) => { + try { + if (!msg) { + return new MessageError('Invalid parameters.'); + } + const { definitionId, owner } = msg; + const item = await DatabaseServer.getPolicyLabelById(definitionId); + if (!item || item.owner !== owner.owner) { + return new MessageError('Item does not exist.'); + } + if (item.status === EntityStatus.ACTIVE) { + return new MessageError('Item is active.'); + } + await DatabaseServer.removePolicyLabel(item); + return new MessageResponse(true); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Publish policy label + * + * @param {any} msg - policy label id + * + * @returns {any} - policy label + */ + ApiResponse(MessageAPI.PUBLISH_POLICY_LABEL, + async (msg: { definitionId: string, owner: IOwner }) => { + try { + if (!msg) { + return new MessageError('Invalid parameters.'); + } + const { definitionId, owner } = msg; + + const item = await DatabaseServer.getPolicyLabelById(definitionId); + if (!item || item.owner !== owner.owner) { + return new MessageError('Item does not exist.'); + } + if (item.status === EntityStatus.PUBLISHED) { + return new MessageError(`Item is already active.`); + } + + const result = await publishPolicyLabel(item, owner, emptyNotifier(), logger); + return new MessageResponse(result); + + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Publish policy label + * + * @param {any} msg - policy label id + * + * @returns {any} - policy label + */ + ApiResponse(MessageAPI.PUBLISH_POLICY_LABEL_ASYNC, + async (msg: { definitionId: string, owner: IOwner, task: any }) => { + try { + if (!msg) { + return new MessageError('Invalid parameters.'); + } + const { definitionId, owner, task } = msg; + + const item = await DatabaseServer.getPolicyLabelById(definitionId); + if (!item || item.owner !== owner.owner) { + return new MessageError('Item does not exist.'); + } + if (item.status === EntityStatus.PUBLISHED) { + return new MessageError(`Item is already active.`); + } + + const notifier = await initNotifier(task); + RunFunctionAsync(async () => { + const result = await publishPolicyLabel(item, owner, notifier, logger); + notifier.result(result); + }, async (error) => { + await logger.error(error, ['GUARDIAN_SERVICE']); + notifier.error(error); + }); + + return new MessageResponse(task); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Export policy label + * + * @param {any} msg - Export policy label parameters + * + * @returns {any} - zip file + */ + ApiResponse(MessageAPI.EXPORT_POLICY_LABEL_FILE, + async (msg: { definitionId: string, owner: IOwner }) => { + try { + if (!msg) { + return new MessageError('Invalid export theme parameters'); + } + const { definitionId, owner } = msg; + + const item = await DatabaseServer.getPolicyLabelById(definitionId); + if (!(item && item.owner === owner.owner)) { + return new MessageError('Item does not exist.'); + } + + const zip = await PolicyLabelImportExport.generate(item); + const file = await zip.generateAsync({ + type: 'arraybuffer', + compression: 'DEFLATE', + compressionOptions: { + level: 3, + }, + }); + + return new BinaryMessageResponse(file); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Import policy label + * + * @param {any} msg - Import policy label parameters + * + * @returns {any} - new policy label + */ + ApiResponse(MessageAPI.IMPORT_POLICY_LABEL_FILE, + async (msg: { zip: any, policyId: string, owner: IOwner }) => { + try { + const { zip, policyId, owner } = msg; + if (!zip) { + throw new Error('file in body is empty'); + } + + const policy = await DatabaseServer.getPolicyById(policyId); + if (!policy || policy.status !== PolicyType.PUBLISH) { + return new MessageError('Item does not exist.'); + } + + const schemas = await PolicyLabelImportExport.getPolicySchemas(policy); + + const preview = await PolicyLabelImportExport.parseZipFile(Buffer.from(zip.data)); + const { label } = preview; + + delete label._id; + delete label.id; + delete label.status; + delete label.owner; + delete label.topicId; + label.creator = owner.creator; + label.owner = owner.owner; + label.policyId = policyId; + label.policyTopicId = policy.topicId; + label.policyInstanceTopicId = policy.instanceTopicId; + label.status = EntityStatus.DRAFT; + label.config = PolicyLabelImportExport.updateSchemas(schemas, label.config); + label.config = PolicyLabelImportExport.validateConfig(label.config); + const row = await DatabaseServer.createPolicyLabel(label); + + return new MessageResponse(row); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Preview policy label + * + * @param {any} msg - zip file + * + * @returns {any} Preview + */ + ApiResponse(MessageAPI.PREVIEW_POLICY_LABEL_FILE, + async (msg: { zip: any, owner: IOwner }) => { + try { + const { zip } = msg; + if (!zip) { + throw new Error('file in body is empty'); + } + const preview = await PolicyLabelImportExport.parseZipFile(Buffer.from(zip.data)); + const { label } = preview; + return new MessageResponse(label); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Get components + * + * @param {any} msg - filters + * + * @returns {any} components + */ + ApiResponse(MessageAPI.SEARCH_POLICY_LABEL_COMPONENTS, + async (msg: { + options: { + text?: string, + owner?: string, + components?: string, + }, owner: IOwner + }) => { + try { + const { options } = msg; + + const filter: any = { + $and: [ + { + status: EntityStatus.PUBLISHED + } + ] + }; + if (options.text) { + const keywords = options.text.split(' '); + for (const keyword of keywords) { + filter.$and.push({ + 'name': { + $regex: `.*${keyword.trim()}.*`, + $options: 'si', + }, + }); + } + } + if (options.owner) { + filter.$and.push({ + $or: [ + { + owner: options.owner + }, + { + creator: options.owner + } + ] + }); + } + + if (options.components === 'all') { + const labels = await DatabaseServer.getPolicyLabels(filter); + const statistics = await DatabaseServer.getStatistics(filter); + return new MessageResponse({ labels, statistics }); + } else if (options.components === 'label') { + const labels = await DatabaseServer.getPolicyLabels(filter); + return new MessageResponse({ labels, statistics: [] }); + } else if (options.components === 'statistic') { + const statistics = await DatabaseServer.getStatistics(filter); + return new MessageResponse({ labels: [], statistics }); + } else { + return new MessageResponse({ labels: [], statistics: [] }); + } + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Get documents + * + * @param {any} msg - filters + * + * @returns {any} - documents + */ + ApiResponse(MessageAPI.GET_POLICY_LABEL_TOKENS, + async (msg: { + definitionId: string, + owner: IOwner, + pageIndex?: string, + pageSize?: string + }) => { + try { + + if (!msg) { + return new MessageError('Invalid parameters.'); + } + const { definitionId, owner, pageIndex, pageSize } = msg; + + const otherOptions: any = {}; + const _pageSize = parseInt(pageSize, 10); + const _pageIndex = parseInt(pageIndex, 10); + if (Number.isInteger(_pageSize) && Number.isInteger(_pageIndex)) { + otherOptions.orderBy = { createDate: 'DESC' }; + otherOptions.limit = _pageSize; + otherOptions.offset = _pageIndex * _pageSize; + } else { + otherOptions.orderBy = { createDate: 'DESC' }; + otherOptions.limit = 100; + otherOptions.offset = 0; + } + + const item = await DatabaseServer.getPolicyLabelById(definitionId); + if (!(item && (item.creator === owner.creator || item.status === EntityStatus.PUBLISHED))) { + return new MessageError('Item does not exist.'); + } + + const policyId: string = item.policyId; + + const vps = await DatabaseServer.getVPs({ + type: 'mint', + policyId, + owner: owner.creator, + }, otherOptions); + + return new MessageResponse({ + items: vps, + count: vps.length + }); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Get documents + * + * @param {any} msg - filters + * + * @returns {any} - documents + */ + ApiResponse(MessageAPI.GET_POLICY_LABEL_TOKEN_DOCUMENTS, + async (msg: { + documentId: string, + definitionId: string, + owner: IOwner + }) => { + try { + if (!msg) { + return new MessageError('Invalid parameters.'); + } + const { documentId, definitionId, owner } = msg; + + const item = await DatabaseServer.getPolicyLabelById(definitionId); + if (!(item && (item.creator === owner.creator || item.status === EntityStatus.PUBLISHED))) { + return new MessageError('Item does not exist.'); + } + + const policyId: string = item.policyId; + + const vp = await DatabaseServer.getVP({ + id: documentId, + type: 'mint', + policyId, + owner: owner.creator, + }); + + if (!vp) { + return new MessageError('Item does not exist.'); + } + + const relationships = await findRelationships(vp); + + return new MessageResponse({ + targetDocument: vp, + relatedDocuments: relationships, + unrelatedDocuments: [] + }); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Create label document + * + * @param payload - label document + * + * @returns {any} new label document + */ + ApiResponse(MessageAPI.CREATE_POLICY_LABEL_DOCUMENT, + async (msg: { + definitionId: string, + data: { + target: string, + documents: any[] + }, + owner: IOwner + }) => { + try { + if (!msg) { + return new MessageError('Invalid parameters'); + } + const { definitionId, data, owner } = msg; + + if (!data || !Array.isArray(data.documents)) { + return new MessageError('Invalid object.'); + } + const item = await DatabaseServer.getPolicyLabelById(definitionId); + if (!(item && (item.creator === owner.creator || item.status === EntityStatus.PUBLISHED))) { + return new MessageError('Item does not exist.'); + } + if (item.status !== EntityStatus.PUBLISHED) { + return new MessageError('Item is not published.'); + } + + const vp = await DatabaseServer.getVP({ + id: data.target, + type: 'mint', + policyId: item.policyId, + owner: owner.creator, + }); + if (!vp) { + return new MessageError('Target does not exist.'); + } + + const relationships = await findRelationships(vp); + const ids = relationships.map((doc) => doc.messageId); + + const schemas = await DatabaseServer.getSchemas({ topicId: item.topicId }); + const schemaObjects = schemas.map((schema) => new Schema(schema)); + + const validator = new LabelValidators(item); + validator.setData(relationships); + validator.setResult(data.documents); + const status = validator.validate(); + + if (!status.valid) { + return new MessageError('Invalid item.'); + } + + const vcs = validator.getVCs(); + const vpObject = await generateVpDocument(vcs, schemaObjects, owner); + + const topic = await getOrCreateTopic(item); + const user = await (new Users()).getHederaAccount(owner.creator); + const messageServer = new MessageServer(user.hederaAccountId, user.hederaAccountKey, user.signOptions); + + const vpMessage = new LabelDocumentMessage(MessageAction.CreateLabelDocument); + vpMessage.setDefinition(item); + vpMessage.setDocument(vpObject); + vpMessage.setTarget(vp.messageId); + vpMessage.setRelationships(ids); + const vpMessageResult = await messageServer + .setTopicObject(topic) + .sendMessage(vpMessage); + + const row = await DatabaseServer.createLabelDocument({ + definitionId: item.id, + policyId: item.policyId, + policyTopicId: item.policyTopicId, + policyInstanceTopicId: item.policyInstanceTopicId, + creator: owner.creator, + owner: owner.owner, + messageId: vpMessageResult.getId(), + topicId: vpMessageResult.getTopicId(), + target: vpMessageResult.getTarget(), + relationships: vpMessageResult.getRelationships(), + document: vpMessageResult.getDocument() + }); + return new MessageResponse(row); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Get label document + * + * @param {any} msg - filters + * + * @returns {any} - Operation success + */ + ApiResponse(MessageAPI.GET_POLICY_LABEL_DOCUMENTS, + async (msg: { + definitionId: string, + filters: any, + owner: IOwner + }) => { + try { + if (!msg) { + return new MessageError('Invalid parameters.'); + } + const { definitionId, filters, owner } = msg; + const { pageIndex, pageSize } = filters; + + const item = await DatabaseServer.getPolicyLabelById(definitionId); + if (!(item && (item.creator === owner.creator || item.status === EntityStatus.PUBLISHED))) { + return new MessageError('Item does not exist.'); + } + if (item.status !== EntityStatus.PUBLISHED) { + return new MessageError('Item is not published.'); + } + + const otherOptions: any = {}; + const _pageSize = parseInt(pageSize, 10); + const _pageIndex = parseInt(pageIndex, 10); + if (Number.isInteger(_pageSize) && Number.isInteger(_pageIndex)) { + otherOptions.orderBy = { createDate: 'DESC' }; + otherOptions.limit = _pageSize; + otherOptions.offset = _pageIndex * _pageSize; + } else { + otherOptions.orderBy = { createDate: 'DESC' }; + otherOptions.limit = 100; + } + otherOptions.fields = [ + 'id', + 'definitionId', + 'policyId', + 'creator', + 'owner', + 'target', + 'relationships', + 'topicId', + 'messageId', + 'document' + ]; + + const [items, count] = await DatabaseServer.getLabelDocumentsAndCount( + { + definitionId + }, + otherOptions + ); + + return new MessageResponse({ items, count }); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Get label document + * + * @param {any} msg - label id + * + * @returns {any} - Operation success + */ + ApiResponse(MessageAPI.GET_POLICY_LABEL_DOCUMENT, + async (msg: { + definitionId: string, + documentId: string, + owner: IOwner + }) => { + try { + if (!msg) { + return new MessageError('Invalid parameters.'); + } + const { definitionId, documentId, owner } = msg; + const document = await DatabaseServer.getLabelDocument({ + id: documentId, + definitionId, + owner: owner.owner + }); + if (!document) { + return new MessageError('Item does not exist.'); + } + + return new MessageResponse(document); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Get statistic assessment relationships + * + * @param {any} msg - statistic id + * + * @returns {any} - Operation success + */ + ApiResponse(MessageAPI.GET_POLICY_LABEL_DOCUMENT_RELATIONSHIPS, + async (msg: { + definitionId: string, + documentId: string, + owner: IOwner + }) => { + try { + if (!msg) { + return new MessageError('Invalid parameters.'); + } + const { definitionId, documentId, owner } = msg; + const document = await DatabaseServer.getLabelDocument({ + id: documentId, + definitionId, + owner: owner.owner + }); + if (!document) { + return new MessageError('Item does not exist.'); + } + + const relationships = await DatabaseServer.getStatisticDocuments({ + messageId: { $in: document?.relationships } + }); + + const target = await DatabaseServer.getVP({ + messageId: document?.target + }); + + return new MessageResponse({ + target, + relationships + }); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); +} \ No newline at end of file diff --git a/guardian-service/src/api/policy-statistics.service.ts b/guardian-service/src/api/policy-statistics.service.ts index bad5832cfb..e7305d7310 100644 --- a/guardian-service/src/api/policy-statistics.service.ts +++ b/guardian-service/src/api/policy-statistics.service.ts @@ -285,7 +285,7 @@ export async function statisticsAPI(logger: PinoLogger): Promise { item.topicId = topic.topicId; item.messageId = statMessageResult.getId(); - const schema = await generateSchema(item, owner); + const schema = await generateSchema(item.topicId, item.config, owner); await publishSchema(schema, owner, messageServer, MessageAction.PublishSchema); await DatabaseServer.createAndSaveSchema(schema); @@ -673,6 +673,7 @@ export async function statisticsAPI(logger: PinoLogger): Promise { delete definition.status; delete definition.owner; delete definition.messageId; + delete definition.topicId; definition.creator = owner.creator; definition.owner = owner.owner; definition.policyId = policyId; diff --git a/guardian-service/src/app.ts b/guardian-service/src/app.ts index f649c469bb..c9cdd6dc30 100644 --- a/guardian-service/src/app.ts +++ b/guardian-service/src/app.ts @@ -52,6 +52,7 @@ import { PolicyStatistic, PolicyStatisticDocument, SchemaRule, + PolicyLabel, PolicyTest, PolicyTool, Record, @@ -76,7 +77,8 @@ import { VpDocument, Wallet, WiperRequest, - Workers + Workers, + PolicyLabelDocument } from '@guardian/common'; import { ApplicationStates, PolicyEvents, PolicyType, WorkerTaskType } from '@guardian/interfaces'; import { AccountId, PrivateKey, TopicId } from '@hashgraph/sdk'; @@ -111,6 +113,8 @@ import { AISuggestionsService } from './helpers/ai-suggestions.js'; import { AssignedEntityAPI } from './api/assigned-entity.service.js'; import { permissionAPI } from './api/permission.service.js'; import { setDefaultSchema } from './api/helpers/default-schemas.js'; +import { policyLabelsAPI } from './api/policy-labels.service.js'; +import { initMathjs } from './utils/formula.js'; export const obj = {}; @@ -161,7 +165,9 @@ const necessaryEntity = [ PolicyTest, PolicyStatistic, PolicyStatisticDocument, - SchemaRule + SchemaRule, + PolicyLabel, + PolicyLabelDocument ] Promise.all([ @@ -266,6 +272,7 @@ Promise.all([ await permissionAPI(logger); await statisticsAPI(logger); await schemaRulesAPI(logger); + await policyLabelsAPI(logger); } catch (error) { console.error(error.message); process.exit(0); @@ -489,6 +496,8 @@ Promise.all([ ); clearPolicyCache.start(true); + initMathjs(); + startMetricsServer(); }, (reason) => { console.log(reason); diff --git a/guardian-service/src/helpers/notifier.ts b/guardian-service/src/helpers/notifier.ts index de99e036d1..8c28a3f210 100644 --- a/guardian-service/src/helpers/notifier.ts +++ b/guardian-service/src/helpers/notifier.ts @@ -85,6 +85,7 @@ const notificationActionMap = new Map([ [TaskAction.DELETE_TOKEN, NotificationAction.TOKENS_PAGE], [TaskAction.DELETE_POLICY, NotificationAction.POLICIES_PAGE], [TaskAction.CLONE_POLICY, NotificationAction.POLICY_CONFIGURATION], + [TaskAction.PUBLISH_POLICY_LABEL, NotificationAction.POLICY_LABEL_PAGE], ]); const taskResultTitleMap = new Map([ [TaskAction.CREATE_POLICY, 'Policy created'], @@ -114,6 +115,7 @@ const taskResultTitleMap = new Map([ [TaskAction.DISSOCIATE_TOKEN, 'Token dissociated'], [TaskAction.RESTORE_USER_PROFILE, 'Profile restored'], [TaskAction.MIGRATE_DATA, 'Data migrated'], + [TaskAction.PUBLISH_POLICY_LABEL, 'Label published'], ]); function getNotificationResultMessage(action: TaskAction, result: any) { @@ -122,6 +124,8 @@ function getNotificationResultMessage(action: TaskAction, result: any) { return `Policy ${result} created`; case TaskAction.PUBLISH_POLICY: return `Policy ${result.policyId} published`; + case TaskAction.PUBLISH_POLICY_LABEL: + return `Policy ${result.id} published`; case TaskAction.IMPORT_POLICY_FILE: case TaskAction.IMPORT_POLICY_MESSAGE: return `Policy ${result.policyId} imported`; @@ -178,6 +182,8 @@ function getNotificationResult(action: TaskAction, result: any) { case TaskAction.IMPORT_TOOL_FILE: case TaskAction.IMPORT_TOOL_MESSAGE: return result.toolId; + case TaskAction.PUBLISH_POLICY_LABEL: + return result.id; default: return result; } diff --git a/guardian-service/src/utils/formula.ts b/guardian-service/src/utils/formula.ts new file mode 100644 index 0000000000..ec2345ec66 --- /dev/null +++ b/guardian-service/src/utils/formula.ts @@ -0,0 +1,25 @@ +import { FormulaEngine } from '@guardian/interfaces'; +import * as formulajs from '@formulajs/formulajs' +import { create, all, ImportObject } from 'mathjs'; + +export function initMathjs() { + FormulaEngine.setMathEngine((() => { + const mathjs = create(all); + const exclude = new Set(['PI']) + const customFunctions: ImportObject = {}; + for (const [name, f] of Object.entries(formulajs)) { + if (typeof f === 'function' && !exclude.has(name)) { + // tslint:disable-next-line:only-arrow-functions + customFunctions[name] = function (...args: any) { + return (f as any).apply(null, args); + } + } + } + mathjs.import(customFunctions); + mathjs.import({ + // tslint:disable-next-line:only-arrow-functions object-literal-shorthand triple-equals + equal: function (a: any, b: any) { return a == b } + }, { override: true }) + return mathjs; + })()) +} \ No newline at end of file diff --git a/indexer-api-gateway/src/api/services/entities.ts b/indexer-api-gateway/src/api/services/entities.ts index ec3cfb6f76..27e3855444 100644 --- a/indexer-api-gateway/src/api/services/entities.ts +++ b/indexer-api-gateway/src/api/services/entities.ts @@ -53,7 +53,12 @@ import { SchemaTreeDTO, InternalServerErrorDTO, DetailsDTO, - UpdateFileDTO + UpdateFileDTO, + StatisticDTO, + StatisticDetailsDTO, + LabelDTO, + LabelDetailsDTO, + LabelDocumentDetailsDTO } from '#dto'; @Controller('entities') @@ -754,6 +759,367 @@ export class EntityApi extends ApiClient { }); } //#endregion + //#region STATISTICS + @Get('/statistics') + @ApiOperation({ + summary: 'Get statistics', + description: 'Returns statistics', + }) + @ApiPaginatedRequest + @ApiPaginatedResponse('Statistics', StatisticDTO) + @ApiInternalServerErrorResponse({ + description: 'Internal server error', + type: InternalServerErrorDTO + }) + @ApiQuery({ + name: 'keywords', + description: 'Keywords to search', + examples: { + '0.0.1960': { + description: + 'Search statistics, which are related to specific topic identifier', + value: '["0.0.1960"]', + }, + }, + required: false, + }) + @ApiQuery({ + name: 'topicId', + description: 'Statistic topic identifier', + example: '0.0.1960', + required: false, + }) + @ApiQuery({ + name: 'options.owner', + description: 'Statistic owner', + example: + 'did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265', + required: false, + }) + @ApiQuery({ + name: 'options.policy', + description: 'Policy', + example: '1706823227.586179534', + required: false, + }) + @HttpCode(HttpStatus.OK) + async getStatistics( + @Query('pageIndex') pageIndex?: string, + @Query('pageSize') pageSize?: string, + @Query('orderField') orderField?: string, + @Query('orderDir') orderDir?: string, + @Query('keywords') keywords?: string, + @Query('topicId') topicId?: string, + @Query('options.owner') owner?: string, + @Query('options.policy') policy?: string + ) { + return await this.send(IndexerMessageAPI.GET_STATISTICS, { + pageIndex, + pageSize, + orderField, + orderDir, + keywords, + topicId, + 'options.owner': owner, + 'options.policy': policy, + }); + } + + @Get('/statistics/:messageId') + @ApiOperation({ + summary: 'Get statistic', + description: 'Returns statistic', + }) + @ApiOkResponse({ + description: 'Statistic details', + type: StatisticDetailsDTO, + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error', + type: InternalServerErrorDTO + }) + @ApiParam({ + name: 'messageId', + description: 'Message identifier', + example: '1706823227.586179534', + }) + @HttpCode(HttpStatus.OK) + async getStatistic(@Param('messageId') messageId: string) { + return await this.send(IndexerMessageAPI.GET_STATISTIC, { + messageId, + }); + } + + @Get('/statistic-documents') + @ApiOperation({ + summary: 'Get VCs', + description: 'Returns VCs', + }) + @ApiPaginatedRequest + @ApiPaginatedResponse('VCs', VCGridDTO) + @ApiInternalServerErrorResponse({ + description: 'Internal server error', + type: InternalServerErrorDTO + }) + @ApiQuery({ + name: 'keywords', + description: 'Keywords to search', + examples: { + '0.0.1960': { + description: + 'Search VCs, which are related to specific topic identifier', + value: '["0.0.1960"]', + }, + }, + required: false, + }) + @ApiQuery({ + name: 'topicId', + description: 'Topic identifier', + example: '0.0.1960', + required: false, + }) + @ApiQuery({ + name: 'options.issuer', + description: 'Issuer', + example: + 'did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265', + required: false, + }) + @ApiQuery({ + name: 'analytics.policyId', + description: 'Policy identifier', + example: '1706823227.586179534', + required: false, + }) + @ApiQuery({ + name: 'analytics.schemaId', + description: 'Schema identifier', + example: '1706823227.586179534', + required: false, + }) + @ApiQuery({ + name: 'options.relationships', + description: 'Relationships', + example: '1706823227.586179534', + required: false, + }) + @HttpCode(HttpStatus.OK) + async getStatisticDocuments( + @Query('pageIndex') pageIndex?: number, + @Query('pageSize') pageSize?: number, + @Query('orderField') orderField?: string, + @Query('orderDir') orderDir?: string, + @Query('keywords') keywords?: string, + @Query('topicId') topicId?: string, + @Query('options.issuer') issuer?: string, + @Query('analytics.policyId') policyId?: string, + @Query('analytics.schemaId') schemaId?: string, + @Query('options.relationships') relationship?: string + ) { + return await this.send(IndexerMessageAPI.GET_STATISTIC_DOCUMENTS, { + pageIndex, + pageSize, + orderField, + orderDir, + keywords, + topicId, + 'options.issuer': issuer, + 'analytics.policyId': policyId, + 'analytics.schemaId': schemaId, + 'options.relationships': relationship, + }); + } + //#endregion + //#region LABELS + @Get('/labels') + @ApiOperation({ + summary: 'Get labels', + description: 'Returns labels', + }) + @ApiPaginatedRequest + @ApiPaginatedResponse('Labels', LabelDTO) + @ApiInternalServerErrorResponse({ + description: 'Internal server error', + type: InternalServerErrorDTO + }) + @ApiQuery({ + name: 'keywords', + description: 'Keywords to search', + examples: { + '0.0.1960': { + description: + 'Search labels, which are related to specific topic identifier', + value: '["0.0.1960"]', + }, + }, + required: false, + }) + @ApiQuery({ + name: 'topicId', + description: 'Label topic identifier', + example: '0.0.1960', + required: false, + }) + @ApiQuery({ + name: 'options.owner', + description: 'Label owner', + example: + 'did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265', + required: false, + }) + @ApiQuery({ + name: 'options.policy', + description: 'Policy', + example: '1706823227.586179534', + required: false, + }) + @HttpCode(HttpStatus.OK) + async getLabels( + @Query('pageIndex') pageIndex?: string, + @Query('pageSize') pageSize?: string, + @Query('orderField') orderField?: string, + @Query('orderDir') orderDir?: string, + @Query('keywords') keywords?: string, + @Query('topicId') topicId?: string, + @Query('options.owner') owner?: string, + @Query('options.policy') policy?: string + ) { + return await this.send(IndexerMessageAPI.GET_LABELS, { + pageIndex, + pageSize, + orderField, + orderDir, + keywords, + topicId, + 'options.owner': owner, + 'options.policy': policy, + }); + } + + @Get('/labels/:messageId') + @ApiOperation({ + summary: 'Get label', + description: 'Returns label', + }) + @ApiOkResponse({ + description: 'Label details', + type: LabelDetailsDTO, + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error', + type: InternalServerErrorDTO + }) + @ApiParam({ + name: 'messageId', + description: 'Message identifier', + example: '1706823227.586179534', + }) + @HttpCode(HttpStatus.OK) + async getLabel(@Param('messageId') messageId: string) { + return await this.send(IndexerMessageAPI.GET_LABEL, { + messageId, + }); + } + + @Get('/label-documents/:messageId') + @ApiOperation({ + summary: 'Get label document', + description: 'Returns label document', + }) + @ApiOkResponse({ + description: 'Label document details', + type: LabelDocumentDetailsDTO, + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error', + type: InternalServerErrorDTO + }) + @ApiParam({ + name: 'messageId', + description: 'Message identifier', + example: '1706823227.586179534', + }) + @HttpCode(HttpStatus.OK) + async getLabelDocument(@Param('messageId') messageId: string) { + return await this.send(IndexerMessageAPI.GET_LABEL_DOCUMENT, { + messageId, + }); + } + + @Get('/label-documents') + @ApiOperation({ + summary: 'Get Label Documents', + description: 'Returns Label Documents', + }) + @ApiPaginatedRequest + @ApiPaginatedResponse('Label Documents', VPGridDTO) + @ApiInternalServerErrorResponse({ + description: 'Internal server error', + type: InternalServerErrorDTO + }) + @ApiQuery({ + name: 'keywords', + description: 'Keywords to search', + examples: { + '0.0.1960': { + description: + 'Search VPs, which are related to specific topic identifier', + value: '["0.0.1960"]', + }, + }, + required: false, + }) + @ApiQuery({ + name: 'topicId', + description: 'Topic identifier', + example: '0.0.1960', + required: false, + }) + @ApiQuery({ + name: 'options.issuer', + description: 'Issuer', + example: + 'did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265', + required: false, + }) + @ApiQuery({ + name: 'analytics.policyId', + description: 'Policy identifier', + example: '1706823227.586179534', + required: false, + }) + @ApiQuery({ + name: 'analytics.schemaIds', + description: 'Schema identifier', + example: '1706823227.586179534', + required: false, + }) + @HttpCode(HttpStatus.OK) + async getLabelDocuments( + @Query('pageIndex') pageIndex?: number, + @Query('pageSize') pageSize?: number, + @Query('orderField') orderField?: string, + @Query('orderDir') orderDir?: string, + @Query('keywords') keywords?: string, + @Query('topicId') topicId?: string, + @Query('options.issuer') issuer?: string, + @Query('analytics.policyId') policyId?: string, + @Query('analytics.schemaIds') schemaId?: string + ) { + return await this.send(IndexerMessageAPI.GET_LABEL_DOCUMENTS, { + pageIndex, + pageSize, + orderField, + orderDir, + keywords, + topicId, + 'options.issuer': issuer, + 'analytics.policyId': policyId, + 'analytics.schemaIds': schemaId, + }); + } + //#endregion //#endregion //#region DOCUMENTS @@ -1349,8 +1715,7 @@ export class EntityApi extends ApiClient { }); } //#endregion - //#endregion - + //#region FILES @Post('/update-files') @ApiOperation({ summary: 'Try load ipfs files', @@ -1375,4 +1740,6 @@ export class EntityApi extends ApiClient { body ); } + //#endregion + //#endregion } diff --git a/indexer-api-gateway/src/dto/details/index.ts b/indexer-api-gateway/src/dto/details/index.ts index ad38f2c51c..2c13f1cca8 100644 --- a/indexer-api-gateway/src/dto/details/index.ts +++ b/indexer-api-gateway/src/dto/details/index.ts @@ -13,4 +13,6 @@ export * from './contract.details.js'; export * from './topic.details.js'; export * from './nft.details.js'; export * from './token.details.js'; -export * from './update-file.js'; \ No newline at end of file +export * from './update-file.js'; +export * from './label.details.js'; +export * from './statistic.details.js'; \ No newline at end of file diff --git a/indexer-api-gateway/src/dto/details/label.details.ts b/indexer-api-gateway/src/dto/details/label.details.ts new file mode 100644 index 0000000000..1412b60e2e --- /dev/null +++ b/indexer-api-gateway/src/dto/details/label.details.ts @@ -0,0 +1,170 @@ +import { + MessageAction, + MessageType, + Label, + LabelActivity, + LabelAnalytics, + LabelDetails, + LabelOptions, + VPDetails, +} from '@indexer/interfaces'; +import { ApiProperty } from '@nestjs/swagger'; +import { MessageDTO } from '../message.dto.js'; +import { DetailsActivityDTO, DetailsHistoryDTO } from './details.interface.js'; +import { RawMessageDTO } from '../raw-message.dto.js'; +import { VPDetailsItemDTO } from './vp.details.js'; + +export class LabelOptionsDTO implements LabelOptions { + @ApiProperty({ + description: 'UUID', + example: '93938a10-d032-4a9b-9425-092e58bffbf7', + }) + uuid: string; + + @ApiProperty({ + description: 'Name', + example: 'Label Name', + }) + name: string; + + @ApiProperty({ + description: 'Description', + example: 'Label Description', + }) + description: string; + + @ApiProperty({ + description: 'Owner', + example: + 'did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265', + }) + owner: string; + + @ApiProperty({ + description: 'Policy topic identifier', + example: '0.0.4481265', + }) + policyTopicId: string; + + @ApiProperty({ + description: 'Policy instance topic identifier', + example: '0.0.4481265', + }) + policyInstanceTopicId: string; +} + +export class LabelAnalyticsDTO implements LabelAnalytics { + @ApiProperty({ + description: 'Text search', + }) + textSearch: string; + + @ApiProperty({ + description: 'Owner', + example: 'did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265', + }) + owner?: string; + + @ApiProperty({ + description: 'Label Config', + type: 'object', + }) + config?: any; +} + +export class LabelActivityDTO implements LabelActivity { + @ApiProperty({ + description: 'Schemas', + example: 10, + }) + schemas: number; + + @ApiProperty({ + description: 'VPs', + example: 10, + }) + vps: number; +} + +export class LabelDTO + extends MessageDTO + implements Label { + @ApiProperty({ + description: 'Type', + enum: MessageType, + example: MessageType.POLICY_LABEL + }) + declare type: MessageType; + + @ApiProperty({ + description: 'Action', + enum: MessageAction, + example: MessageAction.PublishPolicyLabel + }) + declare action: MessageAction; + + @ApiProperty({ + type: LabelOptionsDTO, + }) + declare options: LabelOptionsDTO; + + @ApiProperty({ + type: LabelAnalyticsDTO, + }) + declare analytics?: LabelAnalyticsDTO; +} + +export class LabelDetailsDTO + extends DetailsActivityDTO + implements LabelDetails { + @ApiProperty({ + description: 'UUID', + example: '93938a10-d032-4a9b-9425-092e58bffbf7', + }) + declare uuid?: string; + + @ApiProperty({ + type: LabelDTO, + }) + declare item?: LabelDTO; + + @ApiProperty({ + type: RawMessageDTO, + }) + declare row?: RawMessageDTO; + + @ApiProperty({ + type: LabelActivityDTO, + }) + declare activity?: LabelActivityDTO; +} + +export class LabelDocumentDetailsDTO + extends DetailsHistoryDTO + implements VPDetails { + @ApiProperty({ + description: 'UUID', + example: '93938a10-d032-4a9b-9425-092e58bffbf7', + }) + declare uuid?: string; + + @ApiProperty({ + type: VPDetailsItemDTO, + }) + declare item?: VPDetailsItemDTO; + + @ApiProperty({ + type: RawMessageDTO, + }) + declare row?: RawMessageDTO; + + @ApiProperty({ + type: [VPDetailsItemDTO], + }) + declare history?: VPDetailsItemDTO[]; + + @ApiProperty({ + type: [VPDetailsItemDTO], + }) + declare label?: VPDetailsItemDTO; +} \ No newline at end of file diff --git a/indexer-api-gateway/src/dto/details/nft.details.ts b/indexer-api-gateway/src/dto/details/nft.details.ts index 759930e6ad..b83bbfbc6e 100644 --- a/indexer-api-gateway/src/dto/details/nft.details.ts +++ b/indexer-api-gateway/src/dto/details/nft.details.ts @@ -2,6 +2,7 @@ import { NFT, NFTDetails } from '@indexer/interfaces'; import { DetailsDTO } from './details.interface.js'; import { RawNFTDTO } from '../raw-nft.dto.js'; import { ApiProperty } from '@nestjs/swagger'; +import { VPDetailsItemDTO } from './vp.details.js'; export class NFTDTO extends RawNFTDTO implements NFT {} @@ -13,6 +14,7 @@ export class NFTDetailsDTO type: NFTDTO, }) declare row?: NFTDTO; + @ApiProperty({ description: 'NFT transaction history', type: 'array', @@ -41,4 +43,9 @@ export class NFTDetailsDTO ], }) history: any[]; + + @ApiProperty({ + type: [VPDetailsItemDTO], + }) + declare labels?: VPDetailsItemDTO[]; } diff --git a/indexer-api-gateway/src/dto/details/statistic.details.ts b/indexer-api-gateway/src/dto/details/statistic.details.ts new file mode 100644 index 0000000000..afc70c4b85 --- /dev/null +++ b/indexer-api-gateway/src/dto/details/statistic.details.ts @@ -0,0 +1,122 @@ +import { + MessageAction, + MessageType, + Statistic, + StatisticActivity, + StatisticAnalytics, + StatisticDetails, + StatisticOptions, +} from '@indexer/interfaces'; +import { ApiProperty } from '@nestjs/swagger'; +import { MessageDTO } from '../message.dto.js'; +import { DetailsActivityDTO } from './details.interface.js'; +import { RawMessageDTO } from '../raw-message.dto.js'; + +export class StatisticOptionsDTO implements StatisticOptions { + @ApiProperty({ + description: 'UUID', + example: '93938a10-d032-4a9b-9425-092e58bffbf7', + }) + uuid: string; + + @ApiProperty({ + description: 'Name', + example: 'Label Name', + }) + name: string; + + @ApiProperty({ + description: 'Description', + example: 'Label Description', + }) + description: string; + + @ApiProperty({ + description: 'Owner', + example: + 'did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265', + }) + owner: string; + + @ApiProperty({ + description: 'Policy topic identifier', + example: '0.0.4481265', + }) + policyTopicId: string; + + @ApiProperty({ + description: 'Policy instance topic identifier', + example: '0.0.4481265', + }) + policyInstanceTopicId: string; +} + +export class StatisticAnalyticsDTO implements StatisticAnalytics { +} + +export class StatisticActivityDTO implements StatisticActivity { + @ApiProperty({ + description: 'Schemas', + example: 10, + }) + schemas: number; + + @ApiProperty({ + description: 'VCs', + example: 10, + }) + vcs: number; +} + +export class StatisticDTO + extends MessageDTO + implements Statistic { + @ApiProperty({ + description: 'Type', + enum: MessageType, + example: MessageType.POLICY_LABEL + }) + declare type: MessageType; + + @ApiProperty({ + description: 'Action', + enum: MessageAction, + example: MessageAction.PublishPolicyLabel + }) + declare action: MessageAction; + + @ApiProperty({ + type: StatisticOptionsDTO, + }) + declare options: StatisticOptionsDTO; + + @ApiProperty({ + type: StatisticAnalyticsDTO, + }) + declare analytics?: StatisticAnalyticsDTO; +} + +export class StatisticDetailsDTO + extends DetailsActivityDTO + implements StatisticDetails { + @ApiProperty({ + description: 'UUID', + example: '93938a10-d032-4a9b-9425-092e58bffbf7', + }) + declare uuid?: string; + + @ApiProperty({ + type: StatisticDTO, + }) + declare item?: StatisticDTO; + + @ApiProperty({ + type: RawMessageDTO, + }) + declare row?: RawMessageDTO; + + @ApiProperty({ + type: StatisticActivityDTO, + }) + declare activity?: StatisticActivityDTO; +} diff --git a/indexer-api-gateway/src/dto/details/token.details.ts b/indexer-api-gateway/src/dto/details/token.details.ts index 27a16faddf..0eda4ebce9 100644 --- a/indexer-api-gateway/src/dto/details/token.details.ts +++ b/indexer-api-gateway/src/dto/details/token.details.ts @@ -2,6 +2,7 @@ import { Token, TokenDetails } from '@indexer/interfaces'; import { DetailsDTO } from './details.interface.js'; import { RawTokenDTO } from '../raw-token.dto.js'; import { ApiProperty } from '@nestjs/swagger'; +import { VPDetailsItemDTO } from './vp.details.js'; export class TokenDTO extends RawTokenDTO implements Token {} @@ -13,4 +14,9 @@ export class TokenDetailsDTO type: TokenDTO, }) declare row?: TokenDTO; + + @ApiProperty({ + type: [VPDetailsItemDTO], + }) + declare labels?: VPDetailsItemDTO[]; } diff --git a/indexer-api-gateway/src/dto/details/vp.details.ts b/indexer-api-gateway/src/dto/details/vp.details.ts index 76415845e4..be01c46c28 100644 --- a/indexer-api-gateway/src/dto/details/vp.details.ts +++ b/indexer-api-gateway/src/dto/details/vp.details.ts @@ -31,20 +31,48 @@ export class VPAnalyticsDTO implements VPAnalytics { example: ['1706823227.586179534'], }) schemaIds: string[]; + @ApiProperty({ description: 'Schema names', example: ['Monitoring Report'], }) schemaNames: string[]; + @ApiProperty({ description: 'Policy message identifier', example: '1706823227.586179534', }) policyId: string; + @ApiProperty({ description: 'Text search', }) textSearch: string; + + @ApiProperty({ + description: 'Document issuer', + }) + issuer?: string; + + @ApiProperty({ + description: 'Token ID', + }) + tokenId?: string; + + @ApiProperty({ + description: 'Token amount', + }) + tokenAmount?: string; + + @ApiProperty({ + description: 'Label name', + }) + labelName?: string; + + @ApiProperty({ + description: 'Label IDs', + }) + labels?: string[]; } export class VPGridDTO @@ -115,16 +143,24 @@ export class VPDetailsDTO example: '93938a10-d032-4a9b-9425-092e58bffbf7', }) declare uuid?: string; + @ApiProperty({ type: VPDetailsItemDTO, }) declare item?: VPDetailsItemDTO; + @ApiProperty({ type: RawMessageDTO, }) declare row?: RawMessageDTO; + @ApiProperty({ type: [VPDetailsItemDTO], }) declare history?: VPDetailsItemDTO[]; + + @ApiProperty({ + type: [VPDetailsItemDTO], + }) + declare labels?: VPDetailsItemDTO[]; } diff --git a/indexer-common/src/entity/index.ts b/indexer-common/src/entity/index.ts index 0bfaffae00..360b931e9f 100644 --- a/indexer-common/src/entity/index.ts +++ b/indexer-common/src/entity/index.ts @@ -6,4 +6,4 @@ export * from './token-cache.js'; export * from './nft-cache.js'; export * from './analytics.js'; export * from './project-coordinates.js'; -export * from './synchronization-task.js'; +export * from './synchronization-task.js'; \ No newline at end of file diff --git a/indexer-common/src/entity/message.ts b/indexer-common/src/entity/message.ts index 27a834b1ad..5e9ef605dc 100644 --- a/indexer-common/src/entity/message.ts +++ b/indexer-common/src/entity/message.ts @@ -16,6 +16,8 @@ import { Message as IMessage, MessageAction, MessageType } from '@indexer/interf @Index({ name: 'status', properties: ['status'] }) @Index({ name: 'type', properties: ['type'] }) @Index({ name: 'files', properties: ['files'] }) +@Index({ name: 'last_update', properties: ['lastUpdate'] }) +@Index({ name: 'loaded', properties: ['loaded'] }) export class Message implements IMessage { @PrimaryKey() _id: ObjectId; @@ -23,12 +25,18 @@ export class Message implements IMessage { @SerializedPrimaryKey() id!: string; + @Property() + lastUpdate: number; + @Property() topicId: string; @Property() consensusTimestamp: string; + @Property() + sequenceNumber: number; + @Property() owner: string; @@ -77,6 +85,9 @@ export class Message implements IMessage { hash?: string; hashMap?: any; properties?: string[]; + tokenId?: string, + labels?: string[]; + labelName?: string; }; @Property({ nullable: true }) @@ -90,4 +101,7 @@ export class Message implements IMessage { @Property({ nullable: true }) tokens: string[]; + + @Property({ nullable: true }) + loaded: boolean; } diff --git a/indexer-common/src/messages/message-api.ts b/indexer-common/src/messages/message-api.ts index 39ec90bae7..3e94de677d 100644 --- a/indexer-common/src/messages/message-api.ts +++ b/indexer-common/src/messages/message-api.ts @@ -40,6 +40,10 @@ export enum IndexerMessageAPI { GET_TOKEN = 'INDEXER_API_GET_TOKEN', GET_ROLES = 'INDEXER_API_GET_ROLES', GET_ROLE = 'INDEXER_API_GET_ROLE', + GET_STATISTICS = 'INDEXER_API_GET_STATISTICS', + GET_STATISTIC = 'INDEXER_API_GET_STATISTIC', + GET_LABELS = 'INDEXER_API_GET_LABELS', + GET_LABEL = 'INDEXER_API_GET_LABEL', // #endregion // #region DOCUMENTS @@ -57,6 +61,10 @@ export enum IndexerMessageAPI { GET_DID_DOCUMENT = 'INDEXER_API_GET_DID_DOCUMENT', GET_DID_RELATIONSHIPS = 'INDEXER_API_GET_DID_RELATIONSHIPS', GET_DID_FILTERS = 'INDEXER_API_GET_DID_FILTERS', + + GET_LABEL_DOCUMENTS = 'INDEXER_API_GET_LABEL_DOCUMENTS', + GET_LABEL_DOCUMENT = 'INDEXER_API_GET_LABEL_DOCUMENT', + GET_STATISTIC_DOCUMENTS = 'INDEXER_API_GET_STATISTIC_DOCUMENTS', // #endregion // #region OTHERS diff --git a/indexer-frontend/src/app/app.routes.ts b/indexer-frontend/src/app/app.routes.ts index 039c82d8ca..1a957837b2 100644 --- a/indexer-frontend/src/app/app.routes.ts +++ b/indexer-frontend/src/app/app.routes.ts @@ -40,6 +40,13 @@ import { NFTsComponent } from '@views/collections/nfts/nfts.component'; import { NFTDetailsComponent } from '@views/details/nft-details/nft-details.component'; import { DidDocumentDetailsComponent } from '@views/details/did-document-details/did-document-details.component'; import { ContractDetailsComponent } from '@views/details/contract-details/contract-details.component'; +import { StatisticsComponent } from '@views/collections/statistics/statistics.component'; +import { LabelsComponent } from '@views/collections/labels/labels.component'; +import { StatisticDetailsComponent } from '@views/details/statistic-details/statistic-details.component'; +import { LabelDetailsComponent } from '@views/details/label-details/label-details.component'; +import { LabelDocumentsComponent } from '@views/collections/label-documents/label-documents.component'; +import { StatisticDocumentsComponent } from '@views/collections/statistic-documents/statistic-documents.component'; +import { LabelDocumentDetailsComponent } from '@views/details/label-document-details/label-document-details.component'; export const routes: Routes = [ // _DEV @@ -68,6 +75,10 @@ export const routes: Routes = [ { path: 'nfts', component: NFTsComponent }, { path: 'topics', component: TopicsComponent }, { path: 'contracts', component: ContractsComponent }, + { path: 'statistics', component: StatisticsComponent }, + { path: 'labels', component: LabelsComponent }, + { path: 'label-documents', component: LabelDocumentsComponent }, + { path: 'statistic-documents', component: StatisticDocumentsComponent }, //Details { path: 'registries/:id', component: RegistryDetailsComponent }, @@ -84,4 +95,8 @@ export const routes: Routes = [ { path: 'vc-documents/:id', component: VcDocumentDetailsComponent }, { path: 'vp-documents/:id', component: VpDocumentDetailsComponent }, { path: 'contracts/:id', component: ContractDetailsComponent }, + { path: 'statistics/:id', component: StatisticDetailsComponent }, + { path: 'labels/:id', component: LabelDetailsComponent }, + { path: 'label-documents/:id', component: LabelDocumentDetailsComponent }, + { path: 'statistic-documents/:id', component: VcDocumentDetailsComponent }, ]; diff --git a/indexer-frontend/src/app/components/header/header.component.ts b/indexer-frontend/src/app/components/header/header.component.ts index 50ff6705c5..1a7d8de291 100644 --- a/indexer-frontend/src/app/components/header/header.component.ts +++ b/indexer-frontend/src/app/components/header/header.component.ts @@ -82,6 +82,14 @@ export class HeaderComponent { label: 'header.roles', routerLink: '/roles', }, + { + label: 'header.statistics', + routerLink: '/statistics', + }, + { + label: 'header.labels', + routerLink: '/labels', + }, ]; public documentsMenu: MenuItem[] = [ @@ -97,6 +105,14 @@ export class HeaderComponent { label: 'header.vps', routerLink: '/vp-documents', }, + { + label: 'header.statistic_documents', + routerLink: '/statistic-documents', + }, + { + label: 'header.label_documents', + routerLink: '/label-documents', + }, ]; public othersMenu: MenuItem[] = [ diff --git a/indexer-frontend/src/app/components/overview-form/overview-form.component.html b/indexer-frontend/src/app/components/overview-form/overview-form.component.html index 4435ec9eb5..15d9e54c17 100644 --- a/indexer-frontend/src/app/components/overview-form/overview-form.component.html +++ b/indexer-frontend/src/app/components/overview-form/overview-form.component.html @@ -1,13 +1,18 @@
@for (field of fields; track $index) { -
- {{ field.label | transloco }} - @if (field.link) { - {{getFieldValue(target, - field.path)}} - } @else { - {{getFieldValue(target, field.path)}} - } -
+ @if (field.value) { +
+ {{ field.label | transloco }} + + @if (field._link) { + {{field.value}} + } @else { + {{field.value}} + } + +
+ } }
diff --git a/indexer-frontend/src/app/components/overview-form/overview-form.component.ts b/indexer-frontend/src/app/components/overview-form/overview-form.component.ts index 5012485cff..35a11843bf 100644 --- a/indexer-frontend/src/app/components/overview-form/overview-form.component.ts +++ b/indexer-frontend/src/app/components/overview-form/overview-form.component.ts @@ -7,22 +7,33 @@ export interface OverviewFormField { path: string, link?: string, direct?: boolean, - queryParams?: any + queryParams?: any, + value?: any, + pattern?: any, + _link?: any } @Component({ - selector: 'app-overview-form', - standalone: true, - imports: [TranslocoModule, RouterModule], - templateUrl: './overview-form.component.html', - styleUrl: './overview-form.component.scss' + selector: 'app-overview-form', + standalone: true, + imports: [TranslocoModule, RouterModule], + templateUrl: './overview-form.component.html', + styleUrl: './overview-form.component.scss' }) export class OverviewFormComponent { - @Input() target: any; @Input() fields!: OverviewFormField[]; - getFieldValue(obj: any, paths: string) { + ngOnChanges() { + if (Array.isArray(this.fields)) { + for (const field of this.fields) { + field.value = this.getFieldValue(this.target, field.path); + field._link = this.getLink(field); + } + } + } + + private getFieldValue(obj: any, paths: string) { const pathList = paths.split('.'); let result = obj[pathList[0]]; for (let i = 1; i < pathList.length; i++) { @@ -33,4 +44,24 @@ export class OverviewFormComponent { } return result; } + + private getLink(field: OverviewFormField) { + if (field.link) { + if (field.direct) { + return [field.link]; + } else { + if (field.pattern) { + const reg = new RegExp(field.pattern); + if (reg.test(field.value)) { + return [field.link, field.value]; + } else { + return undefined; + } + } else { + return [field.link, field.value]; + } + } + } + return undefined; + } } diff --git a/indexer-frontend/src/app/services/entities.service.ts b/indexer-frontend/src/app/services/entities.service.ts index af57617b21..06b1b9c72c 100644 --- a/indexer-frontend/src/app/services/entities.service.ts +++ b/indexer-frontend/src/app/services/entities.service.ts @@ -36,6 +36,10 @@ import { VP, VPDetails, Relationships, + Statistic, + StatisticDetails, + Label, + LabelDetails, } from '@indexer/interfaces'; /** @@ -319,6 +323,60 @@ export class EntitiesService { ) as any; } //#endregion + //#region LABELS + public getLabels(filters: PageFilters): Observable> { + const entity = 'labels'; + const options = ApiUtils.getOptions(filters); + return this.http.get>( + `${this.url}/${entity}`, + options + ) as any; + } + + public getLabel(messageId: string): Observable { + const entity = 'labels'; + return this.http.get( + `${this.url}/${entity}/${messageId}` + ) as any; + } + + public getLabelDocuments(filters: PageFilters): Observable> { + const entity = 'label-documents'; + const options = ApiUtils.getOptions(filters); + return this.http.get>(`${this.url}/${entity}`, options) as any; + } + + public getLabelDocument(messageId: string): Observable { + const entity = 'label-documents'; + return this.http.get( + `${this.url}/${entity}/${messageId}` + ) as any; + } + + //#endregion + //#region STATISTICS + public getStatistics(filters: PageFilters): Observable> { + const entity = 'statistics'; + const options = ApiUtils.getOptions(filters); + return this.http.get>( + `${this.url}/${entity}`, + options + ) as any; + } + + public getStatistic(messageId: string): Observable { + const entity = 'statistics'; + return this.http.get( + `${this.url}/${entity}/${messageId}` + ) as any; + } + + public getStatisticDocuments(filters: PageFilters): Observable> { + const entity = 'statistic-documents'; + const options = ApiUtils.getOptions(filters); + return this.http.get>(`${this.url}/${entity}`, options) as any; + } + //#endregion //#endregion public updateFiles(messageId: string): Observable { diff --git a/indexer-frontend/src/app/views/collections/label-documents/label-documents.component.html b/indexer-frontend/src/app/views/collections/label-documents/label-documents.component.html new file mode 100644 index 0000000000..2f5ee4ae90 --- /dev/null +++ b/indexer-frontend/src/app/views/collections/label-documents/label-documents.component.html @@ -0,0 +1,33 @@ +
+
+

{{ 'header.label_documents' | transloco }}

+
+ +
+ +
+ @for (filter of filters; track $index) { + @if (filter.type === 'input') { + + {{ filter.label | transloco }} + + + } + } +
+
+
+
+
+
diff --git a/indexer-frontend/src/app/views/collections/label-documents/label-documents.component.scss b/indexer-frontend/src/app/views/collections/label-documents/label-documents.component.scss new file mode 100644 index 0000000000..2d9f31d065 --- /dev/null +++ b/indexer-frontend/src/app/views/collections/label-documents/label-documents.component.scss @@ -0,0 +1,7 @@ +.table-body { + .mat-column-topicId { + width: 200px; + min-width: 200px; + max-width: 200px; + } +} \ No newline at end of file diff --git a/indexer-frontend/src/app/views/collections/label-documents/label-documents.component.ts b/indexer-frontend/src/app/views/collections/label-documents/label-documents.component.ts new file mode 100644 index 0000000000..616e20f1c5 --- /dev/null +++ b/indexer-frontend/src/app/views/collections/label-documents/label-documents.component.ts @@ -0,0 +1,175 @@ +import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ActivatedRoute, Router } from '@angular/router'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatSortModule } from '@angular/material/sort'; +import { MatPaginatorModule } from '@angular/material/paginator'; +import { MatTableModule } from '@angular/material/table'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatSelectModule } from '@angular/material/select'; +import { MatInputModule } from '@angular/material/input'; +import { MatButtonModule } from '@angular/material/button'; +import { LoadingComponent } from '@components/loading/loading.component'; +import { BaseGridComponent, Filter } from '../base-grid/base-grid.component'; +import { TranslocoModule } from '@jsverse/transloco'; +import { SelectFilterComponent } from '@components/select-filter/select-filter.component'; +import { EntitiesService } from '@services/entities.service'; +import { FiltersService } from '@services/filters.service'; +import { ColumnType, TableComponent } from '@components/table/table.component'; +import { InputGroupModule } from 'primeng/inputgroup'; +import { InputGroupAddonModule } from 'primeng/inputgroupaddon'; +import { ChipsModule } from 'primeng/chips'; + +@Component({ + selector: 'label-documents', + templateUrl: './label-documents.component.html', + styleUrls: [ + '../base-grid/base-grid.component.scss', + './label-documents.component.scss', + ], + standalone: true, + imports: [ + CommonModule, + MatPaginatorModule, + MatTableModule, + MatSortModule, + MatFormFieldModule, + MatSelectModule, + MatInputModule, + FormsModule, + MatButtonModule, + LoadingComponent, + TranslocoModule, + ReactiveFormsModule, + SelectFilterComponent, + TableComponent, + InputGroupModule, + InputGroupAddonModule, + ChipsModule + ] +}) +export class LabelDocumentsComponent extends BaseGridComponent { + columns: any[] = [ + { + type: ColumnType.TEXT, + field: 'consensusTimestamp', + title: 'grid.consensus_timestamp', + width: '250px', + sort: true + }, + { + type: ColumnType.TEXT, + field: 'topicId', + title: 'grid.topic_id', + width: '125px', + link: { + field: 'topicId', + url: '/topics', + }, + }, + { + type: ColumnType.TEXT, + field: 'analytics.tokenId', + title: 'grid.token_id', + width: '125px', + link: { + field: 'analytics.tokenId', + url: '/tokens', + }, + }, + { + type: ColumnType.TEXT, + field: 'options.target', + title: 'grid.target', + width: '250px', + link: { + field: 'options.target', + url: '/vp-documents', + }, + }, + { + type: ColumnType.TEXT, + field: 'analytics.labelName', + title: 'grid.name', + width: '500px', + }, + { + type: ColumnType.CHIP, + field: 'status', + title: 'grid.status', + width: '100px', + sort: true + }, + { + type: ColumnType.BUTTON, + title: 'grid.open', + btn_label: 'grid.open', + width: '100px', + callback: this.onOpen.bind(this), + }, + ]; + + constructor( + private entitiesService: EntitiesService, + private filtersService: FiltersService, + route: ActivatedRoute, + router: Router + ) { + super(route, router); + this.filters.push( + new Filter({ + label: 'grid.filter.topic_id', + type: 'input', + field: 'topicId', + }), + new Filter({ + type: 'input', + field: 'analytics.policyId', + label: 'grid.filter.policy_id', + }), + new Filter({ + type: 'input', + field: 'analytics.tokenId', + label: 'grid.filter.token_id', + }), + new Filter({ + type: 'input', + field: 'options.target', + label: 'grid.filter.target', + }), + ); + } + + protected loadData(): void { + const filters = this.getFilters(); + this.loadingData = true; + this.entitiesService.getLabelDocuments(filters).subscribe({ + next: (result) => { + this.setResult(result); + setTimeout(() => { + this.loadingData = false; + }, 500); + }, + error: ({ message }) => { + this.loadingData = false; + console.error(message); + } + }); + } + + protected loadFilters(): void { + this.loadingFilters = true; + this.filtersService.getVpFilters().subscribe({ + next: (result) => { + this.setFilters(result); + setTimeout(() => { + this.loadingFilters = false; + }, 500); + }, + error: ({ message }) => { + this.loadingFilters = false; + console.error(message); + } + }); + } +} diff --git a/indexer-frontend/src/app/views/collections/labels/labels.component.html b/indexer-frontend/src/app/views/collections/labels/labels.component.html new file mode 100644 index 0000000000..9595a1c6e3 --- /dev/null +++ b/indexer-frontend/src/app/views/collections/labels/labels.component.html @@ -0,0 +1,36 @@ +
+
+

{{ 'header.labels' | transloco }}

+
+ +
+ +
+ @for (filter of filters; track $index) { + @if (filter.type === 'input') { + + {{ filter.label | transloco }} + + + } + } +
+
+
+
+
+ +
diff --git a/indexer-frontend/src/app/views/collections/labels/labels.component.scss b/indexer-frontend/src/app/views/collections/labels/labels.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/indexer-frontend/src/app/views/collections/labels/labels.component.ts b/indexer-frontend/src/app/views/collections/labels/labels.component.ts new file mode 100644 index 0000000000..8a2029b9df --- /dev/null +++ b/indexer-frontend/src/app/views/collections/labels/labels.component.ts @@ -0,0 +1,139 @@ +import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ActivatedRoute, Router } from '@angular/router'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatSortModule } from '@angular/material/sort'; +import { MatPaginatorModule } from '@angular/material/paginator'; +import { MatTableModule } from '@angular/material/table'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatSelectModule } from '@angular/material/select'; +import { MatInputModule } from '@angular/material/input'; +import { MatButtonModule } from '@angular/material/button'; +import { LoadingComponent } from '@components/loading/loading.component'; +import { BaseGridComponent, Filter } from '../base-grid/base-grid.component'; +import { TranslocoModule } from '@jsverse/transloco'; +import { EntitiesService } from '@services/entities.service'; +import { FiltersService } from '@services/filters.service'; +import { PaginatorModule } from 'primeng/paginator'; +import { ChipsModule } from 'primeng/chips'; +import { ColumnType, TableComponent } from '@components/table/table.component'; +import { InputGroupModule } from 'primeng/inputgroup'; +import { InputGroupAddonModule } from 'primeng/inputgroupaddon'; +import { InputTextModule } from 'primeng/inputtext'; + +@Component({ + selector: 'labels', + templateUrl: './labels.component.html', + styleUrls: [ + '../base-grid/base-grid.component.scss', + './labels.component.scss', + ], + standalone: true, + imports: [ + CommonModule, + MatPaginatorModule, + MatTableModule, + MatSortModule, + MatFormFieldModule, + MatSelectModule, + MatInputModule, + FormsModule, + MatButtonModule, + LoadingComponent, + TranslocoModule, + TableComponent, + PaginatorModule, + ChipsModule, + ReactiveFormsModule, + InputTextModule, + InputGroupModule, + InputGroupAddonModule, + ], +}) +export class LabelsComponent extends BaseGridComponent { + columns: any[] = [ + { + type: ColumnType.TEXT, + field: 'consensusTimestamp', + title: 'grid.consensus_timestamp', + width: '250px', + sort: true, + }, + { + type: ColumnType.TEXT, + field: 'topicId', + title: 'grid.topic_id', + width: '150px', + link: { + field: 'topicId', + url: '/topics', + }, + }, + { + type: ColumnType.TEXT, + field: 'options.name', + title: 'grid.name', + width: '200px', + }, + { + type: ColumnType.TEXT, + field: 'options.owner', + title: 'grid.owner', + width: '650px', + }, + { + type: ColumnType.BUTTON, + title: 'grid.open', + btn_label: 'grid.open', + width: '100px', + callback: this.onOpen.bind(this), + }, + ]; + + constructor( + private entitiesService: EntitiesService, + private filtersService: FiltersService, + route: ActivatedRoute, + router: Router + ) { + super(route, router); + this.filters.push( + new Filter({ + label: 'grid.filter.topic_id', + type: 'input', + field: 'topicId', + }), + new Filter({ + type: 'input', + field: 'options.owner', + label: 'grid.owner', + }), + new Filter({ + type: 'input', + field: 'options.policy', + label: 'grid.filter.policy', + }) + ); + } + + protected loadData(): void { + const filters = this.getFilters(); + this.loadingData = true; + this.entitiesService.getLabels(filters).subscribe({ + next: (result) => { + this.setResult(result); + setTimeout(() => { + this.loadingData = false; + }, 500); + }, + error: ({ message }) => { + this.loadingData = false; + console.error(message); + }, + }); + } + + protected loadFilters(): void { + this.loadingFilters = false; + } +} diff --git a/indexer-frontend/src/app/views/collections/statistic-documents/statistic-documents.component.html b/indexer-frontend/src/app/views/collections/statistic-documents/statistic-documents.component.html new file mode 100644 index 0000000000..bf924c311b --- /dev/null +++ b/indexer-frontend/src/app/views/collections/statistic-documents/statistic-documents.component.html @@ -0,0 +1,34 @@ +
+
+

{{ 'header.statistic_documents' | transloco }}

+
+ +
+ +
+ @for (filter of filters; track $index) { + @if (filter.type === 'input') { + + {{ filter.label | transloco }} + + + } + } +
+
+
+
+
+ +
diff --git a/indexer-frontend/src/app/views/collections/statistic-documents/statistic-documents.component.scss b/indexer-frontend/src/app/views/collections/statistic-documents/statistic-documents.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/indexer-frontend/src/app/views/collections/statistic-documents/statistic-documents.component.ts b/indexer-frontend/src/app/views/collections/statistic-documents/statistic-documents.component.ts new file mode 100644 index 0000000000..589dcfeefa --- /dev/null +++ b/indexer-frontend/src/app/views/collections/statistic-documents/statistic-documents.component.ts @@ -0,0 +1,167 @@ +import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ActivatedRoute, Router } from '@angular/router'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatSortModule } from '@angular/material/sort'; +import { MatPaginatorModule } from '@angular/material/paginator'; +import { MatTableModule } from '@angular/material/table'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatSelectModule } from '@angular/material/select'; +import { MatInputModule } from '@angular/material/input'; +import { MatButtonModule } from '@angular/material/button'; +import { LoadingComponent } from '@components/loading/loading.component'; +import { BaseGridComponent, Filter } from '../base-grid/base-grid.component'; +import { TranslocoModule } from '@jsverse/transloco'; +import { EntitiesService } from '@services/entities.service'; +import { FiltersService } from '@services/filters.service'; +import { PaginatorModule } from 'primeng/paginator'; +import { ColumnType, TableComponent } from '@components/table/table.component'; +import { InputGroupModule } from 'primeng/inputgroup'; +import { InputGroupAddonModule } from 'primeng/inputgroupaddon'; +import { InputTextModule } from 'primeng/inputtext'; +import { ChipsModule } from 'primeng/chips'; + +@Component({ + selector: 'statistic-documents', + templateUrl: './statistic-documents.component.html', + styleUrls: [ + '../base-grid/base-grid.component.scss', + './statistic-documents.component.scss', + ], + standalone: true, + imports: [ + CommonModule, + MatPaginatorModule, + MatTableModule, + MatSortModule, + MatFormFieldModule, + MatSelectModule, + MatInputModule, + FormsModule, + MatButtonModule, + LoadingComponent, + TranslocoModule, + TableComponent, + PaginatorModule, + InputGroupModule, + InputGroupAddonModule, + InputTextModule, + ReactiveFormsModule, + ChipsModule, + ], +}) +export class StatisticDocumentsComponent extends BaseGridComponent { + columns: any[] = [ + { + type: ColumnType.TEXT, + field: 'consensusTimestamp', + title: 'grid.consensus_timestamp', + width: '250px', + sort: true, + }, + { + type: ColumnType.TEXT, + field: 'topicId', + title: 'grid.topic_id', + width: '150px', + link: { + field: 'topicId', + url: '/topics', + }, + }, + { + type: ColumnType.CHIP, + field: 'status', + title: 'grid.status', + width: '100px', + sort: true, + }, + { + type: ColumnType.TEXT, + field: 'analytics.schemaName', + title: 'grid.schema', + width: '200px', + }, + { + type: ColumnType.TEXT, + field: 'options.issuer', + title: 'grid.issuer', + width: '650px', + }, + { + type: ColumnType.BUTTON, + title: 'grid.open', + btn_label: 'grid.open', + width: '100px', + callback: this.onOpen.bind(this), + }, + ]; + + constructor( + private entitiesService: EntitiesService, + private filtersService: FiltersService, + route: ActivatedRoute, + router: Router + ) { + super(route, router); + this.filters.push( + new Filter({ + label: 'grid.filter.topic_id', + type: 'input', + field: 'topicId', + }), + new Filter({ + type: 'input', + field: 'options.issuer', + label: 'grid.issuer', + }), + new Filter({ + type: 'input', + field: 'analytics.policyId', + label: 'grid.filter.policy_id', + }), + new Filter({ + type: 'input', + field: 'analytics.schemaId', + label: 'grid.filter.schema_id', + }), + new Filter({ + type: 'input', + field: 'options.relationships', + label: 'grid.filter.relationship', + }) + ); + } + + protected loadData(): void { + const filters = this.getFilters(); + this.loadingData = true; + this.entitiesService.getStatisticDocuments(filters).subscribe({ + next: (result) => { + this.setResult(result); + setTimeout(() => { + this.loadingData = false; + }, 500); + }, + error: ({ message }) => { + this.loadingData = false; + console.error(message); + }, + }); + } + + protected loadFilters(): void { + this.loadingFilters = true; + this.filtersService.getVcFilters().subscribe({ + next: (result) => { + setTimeout(() => { + this.loadingFilters = false; + }, 500); + }, + error: ({ message }) => { + this.loadingFilters = false; + console.error(message); + }, + }); + } +} diff --git a/indexer-frontend/src/app/views/collections/statistics/statistics.component.html b/indexer-frontend/src/app/views/collections/statistics/statistics.component.html new file mode 100644 index 0000000000..2b233ad0b9 --- /dev/null +++ b/indexer-frontend/src/app/views/collections/statistics/statistics.component.html @@ -0,0 +1,26 @@ +
+
+

{{ 'header.statistic' | transloco }}

+
+ +
+ +
+ @for (filter of filters; track $index) { + @if (filter.type === 'input') { + + {{ filter.label | transloco }} + + + } + } +
+
+
+
+
+ +
diff --git a/indexer-frontend/src/app/views/collections/statistics/statistics.component.scss b/indexer-frontend/src/app/views/collections/statistics/statistics.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/indexer-frontend/src/app/views/collections/statistics/statistics.component.ts b/indexer-frontend/src/app/views/collections/statistics/statistics.component.ts new file mode 100644 index 0000000000..e84a07cfb1 --- /dev/null +++ b/indexer-frontend/src/app/views/collections/statistics/statistics.component.ts @@ -0,0 +1,139 @@ +import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ActivatedRoute, Router } from '@angular/router'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatSortModule } from '@angular/material/sort'; +import { MatPaginatorModule } from '@angular/material/paginator'; +import { MatTableModule } from '@angular/material/table'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatSelectModule } from '@angular/material/select'; +import { MatInputModule } from '@angular/material/input'; +import { MatButtonModule } from '@angular/material/button'; +import { LoadingComponent } from '@components/loading/loading.component'; +import { BaseGridComponent, Filter } from '../base-grid/base-grid.component'; +import { TranslocoModule } from '@jsverse/transloco'; +import { EntitiesService } from '@services/entities.service'; +import { FiltersService } from '@services/filters.service'; +import { PaginatorModule } from 'primeng/paginator'; +import { ChipsModule } from 'primeng/chips'; +import { ColumnType, TableComponent } from '@components/table/table.component'; +import { InputGroupModule } from 'primeng/inputgroup'; +import { InputGroupAddonModule } from 'primeng/inputgroupaddon'; +import { InputTextModule } from 'primeng/inputtext'; + +@Component({ + selector: 'statistics', + templateUrl: './statistics.component.html', + styleUrls: [ + '../base-grid/base-grid.component.scss', + './statistics.component.scss', + ], + standalone: true, + imports: [ + CommonModule, + MatPaginatorModule, + MatTableModule, + MatSortModule, + MatFormFieldModule, + MatSelectModule, + MatInputModule, + FormsModule, + MatButtonModule, + LoadingComponent, + TranslocoModule, + TableComponent, + PaginatorModule, + ChipsModule, + ReactiveFormsModule, + InputTextModule, + InputGroupModule, + InputGroupAddonModule, + ], +}) +export class StatisticsComponent extends BaseGridComponent { + columns: any[] = [ + { + type: ColumnType.TEXT, + field: 'consensusTimestamp', + title: 'grid.consensus_timestamp', + width: '250px', + sort: true, + }, + { + type: ColumnType.TEXT, + field: 'topicId', + title: 'grid.topic_id', + width: '150px', + link: { + field: 'topicId', + url: '/topics', + }, + }, + { + type: ColumnType.TEXT, + field: 'options.name', + title: 'grid.name', + width: '200px', + }, + { + type: ColumnType.TEXT, + field: 'options.owner', + title: 'grid.owner', + width: '650px', + }, + { + type: ColumnType.BUTTON, + title: 'grid.open', + btn_label: 'grid.open', + width: '100px', + callback: this.onOpen.bind(this), + }, + ]; + + constructor( + private entitiesService: EntitiesService, + private filtersService: FiltersService, + route: ActivatedRoute, + router: Router + ) { + super(route, router); + this.filters.push( + new Filter({ + label: 'grid.filter.topic_id', + type: 'input', + field: 'topicId', + }), + new Filter({ + type: 'input', + field: 'options.owner', + label: 'grid.owner', + }), + new Filter({ + type: 'input', + field: 'analytics.tools', + label: 'grid.filter.tool_id', + }) + ); + } + + protected loadData(): void { + const filters = this.getFilters(); + this.loadingData = true; + this.entitiesService.getStatistics(filters).subscribe({ + next: (result) => { + this.setResult(result); + setTimeout(() => { + this.loadingData = false; + }, 500); + }, + error: ({ message }) => { + this.loadingData = false; + console.error(message); + }, + }); + } + + protected loadFilters(): void { + this.loadingFilters = false; + } +} diff --git a/indexer-frontend/src/app/views/collections/vc-documents/vc-documents.component.html b/indexer-frontend/src/app/views/collections/vc-documents/vc-documents.component.html index 361f56c5e4..ed5639025f 100644 --- a/indexer-frontend/src/app/views/collections/vc-documents/vc-documents.component.html +++ b/indexer-frontend/src/app/views/collections/vc-documents/vc-documents.component.html @@ -2,20 +2,28 @@

{{ 'header.vcs' | transloco }}

- +
@for (filter of filters; track $index) { - @if (filter.type === 'input') { - - {{ filter.label | transloco }} - - - } + @if (filter.type === 'input') { + + {{ filter.label | transloco }} + + + } }
diff --git a/indexer-frontend/src/app/views/collections/vp-documents/vp-documents.component.html b/indexer-frontend/src/app/views/collections/vp-documents/vp-documents.component.html index a197212c6d..19452606f7 100644 --- a/indexer-frontend/src/app/views/collections/vp-documents/vp-documents.component.html +++ b/indexer-frontend/src/app/views/collections/vp-documents/vp-documents.component.html @@ -2,20 +2,28 @@

{{ 'header.vps' | transloco }}

- +
@for (filter of filters; track $index) { - @if (filter.type === 'input') { - - {{ filter.label | transloco }} - - - } + @if (filter.type === 'input') { + + {{ filter.label | transloco }} + + + } }
diff --git a/indexer-frontend/src/app/views/collections/vp-documents/vp-documents.component.ts b/indexer-frontend/src/app/views/collections/vp-documents/vp-documents.component.ts index eb4c793b7a..39d3d5edc8 100644 --- a/indexer-frontend/src/app/views/collections/vp-documents/vp-documents.component.ts +++ b/indexer-frontend/src/app/views/collections/vp-documents/vp-documents.component.ts @@ -82,7 +82,7 @@ export class VpDocumentsComponent extends BaseGridComponent { }, { type: ColumnType.TEXT, - field: 'options.issuer', + field: 'analytics.issuer', title: 'grid.issuer', width: '500px', }, diff --git a/indexer-frontend/src/app/views/details/base-details/base-details.component.ts b/indexer-frontend/src/app/views/details/base-details/base-details.component.ts index 11c59f396f..f5e74e8af7 100644 --- a/indexer-frontend/src/app/views/details/base-details/base-details.component.ts +++ b/indexer-frontend/src/app/views/details/base-details/base-details.component.ts @@ -187,7 +187,7 @@ export abstract class BaseDetailsComponent { .updateFiles(first.consensusTimestamp) .subscribe({ next: (result) => { - if(result) { + if (result) { this.first = result; this.setFiles(this.first); } @@ -239,7 +239,9 @@ export abstract class BaseDetailsComponent { const url = item.files[i]; const document = item.documents?.[i]; const json = this.getDocument(document); - const credentialSubject = this.getCredentialSubject(document); + const documentObject = this.getDocumentObject(document); + const credentialSubject = this.getCredentialSubject(documentObject); + const verifiableCredential = this.getVerifiableCredential(documentObject); const cid = new CID(url); const ipfs = { version: cid.version, @@ -247,7 +249,9 @@ export abstract class BaseDetailsComponent { global: cid.toV1().toString('base32'), document, json, - credentialSubject + documentObject, + credentialSubject, + verifiableCredential } if (!document) { item._ipfsStatus = false; @@ -267,15 +271,35 @@ export abstract class BaseDetailsComponent { } } - protected getCredentialSubject(item: any): any { + protected getDocumentObject(item: any): any { try { - return JSON.parse(item).credentialSubject[0]; + return JSON.parse(item); } catch (error) { console.log(error); + return null; + } + } + + protected getCredentialSubject(item: any): any { + try { + return item.credentialSubject[0]; + } catch (error) { return {}; } } + protected getVerifiableCredential(item: any): any[] { + try { + return item.verifiableCredential; + } catch (error) { + return []; + } + } + + protected getFirstDocument() { + return this.first._ipfs[0]; + } + protected setRelationships(result: Relationships): void { this.relationships = result; } diff --git a/indexer-frontend/src/app/views/details/label-details/label-details.component.html b/indexer-frontend/src/app/views/details/label-details/label-details.component.html new file mode 100644 index 0000000000..e914f0df4e --- /dev/null +++ b/indexer-frontend/src/app/views/details/label-details/label-details.component.html @@ -0,0 +1,35 @@ +
+
+
+

{{ 'details.label.header' | transloco }} {{id}}

+
+ @if (loading) { +
+ +
+ } @else { + @if (row) { + + @if (target) { + + + + + + + + } + +
+ +
+
+ +
+ } @else { +
{{ 'details.not_found' | transloco }}
+ } + } +
+
diff --git a/indexer-frontend/src/app/views/details/label-details/label-details.component.scss b/indexer-frontend/src/app/views/details/label-details/label-details.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/indexer-frontend/src/app/views/details/label-details/label-details.component.ts b/indexer-frontend/src/app/views/details/label-details/label-details.component.ts new file mode 100644 index 0000000000..334bf6578d --- /dev/null +++ b/indexer-frontend/src/app/views/details/label-details/label-details.component.ts @@ -0,0 +1,134 @@ +import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ActivatedRoute, Router } from '@angular/router'; +import { LoadingComponent } from '@components/loading/loading.component'; +import { MatTabsModule } from '@angular/material/tabs'; +import { NgxEchartsDirective } from 'ngx-echarts'; +import { MatInputModule } from '@angular/material/input'; +import { BaseDetailsComponent } from '../base-details/base-details.component'; +import { TranslocoModule } from '@jsverse/transloco'; +import { EntitiesService } from '@services/entities.service'; +import { TabViewModule } from 'primeng/tabview'; +import { ProgressSpinnerModule } from 'primeng/progressspinner'; +import { ButtonModule } from 'primeng/button'; +import { + OverviewFormComponent, + OverviewFormField, +} from '@components/overview-form/overview-form.component'; +import { ActivityComponent } from '@components/activity/activity.component'; +import { InputTextareaModule } from 'primeng/inputtextarea'; + +@Component({ + selector: 'label-details', + templateUrl: './label-details.component.html', + styleUrls: [ + '../base-details/base-details.component.scss', + './label-details.component.scss', + ], + standalone: true, + imports: [ + CommonModule, + LoadingComponent, + MatTabsModule, + NgxEchartsDirective, + MatInputModule, + TranslocoModule, + TabViewModule, + ProgressSpinnerModule, + ButtonModule, + OverviewFormComponent, + ActivityComponent, + InputTextareaModule, + ], +}) +export class LabelDetailsComponent extends BaseDetailsComponent { + tabs: any[] = ['overview', 'activity', 'raw']; + overviewFields: OverviewFormField[] = [ + { + label: 'details.label.overview.topic_id', + path: 'topicId', + link: '/topics', + }, + { + label: 'details.label.overview.policy_topic_id', + path: 'options.policyTopicId', + link: '/topics', + }, + { + label: 'details.label.overview.owner', + path: 'options.owner', + }, + { + label: 'details.label.overview.name', + path: 'options.name', + }, + { + label: 'details.label.overview.description', + path: 'options.description', + } + ]; + + constructor( + entitiesService: EntitiesService, + route: ActivatedRoute, + router: Router + ) { + super(entitiesService, route, router); + } + + protected override loadData(): void { + if (this.id) { + this.loading = true; + this.entitiesService.getLabel(this.id).subscribe({ + next: (result) => { + this.setResult(result); + setTimeout(() => { + this.loading = false; + }, 500); + }, + error: ({ message }) => { + this.loading = false; + console.error(message); + }, + }); + } else { + this.setResult(); + } + } + + protected override onNavigate(): void {} + + protected override getTabIndex(name: string): number { + if (this.target) { + const tabIndex = this.tabs.findIndex((item) => item === name); + return tabIndex >= 0 ? tabIndex : 0; + } else { + return 0; + } + } + + protected override getTabName(index: number): string { + if (this.target) { + return this.tabs[index] || 'raw'; + } else { + return 'raw'; + } + } + + public override onOpenSchemas() { + this.router.navigate(['/schemas'], { + queryParams: { + topicId: this.row.topicId, + }, + }); + } + + + public override onOpenVPs() { + this.router.navigate(['/vp-documents'], { + queryParams: { + topicId: this.row.topicId + }, + }); + } +} diff --git a/indexer-frontend/src/app/views/details/label-document-details/label-document-details.component.html b/indexer-frontend/src/app/views/details/label-document-details/label-document-details.component.html new file mode 100644 index 0000000000..259daa4ba2 --- /dev/null +++ b/indexer-frontend/src/app/views/details/label-document-details/label-document-details.component.html @@ -0,0 +1,212 @@ +
+
+
+
+

{{ 'details.header.label' | transloco }}

+
+ @if (loading) { +
+ +
+ } @else { + @if (row) { + + @if (target) { + + + + + + @if (first._ipfs) { +
+
+
+ {{ 'details.cid' | transloco }}: +
+
+ {{first._ipfs[0].cid}} +
+
+ @if (first._ipfsStatus) { +
+
+
+ +
+
+
+ } @else { +
+
+ @if (first._ipfs.length<2) { +
+ {{ 'details.document_not_loaded_header' | transloco }} +
+
+ {{ 'details.document_not_loaded_message' | transloco }} +
+ } @else { +
+ {{ 'details.documents_not_loaded_header' | transloco }} +
+
+ {{ 'details.documents_not_loaded_message' | transloco }} +
+ } + +
+
+ } +
+ } +
+ + + @if (label) { +
+
+ @for (current of steps; track current) { +
+ @if (current.type === 'validate') { +
+ + @if (current.prefix) { + {{current.prefix}} + } + {{current.title}} +
+ } @else { +
+ @if (current.prefix) { +
+ {{current.name}} +
+ } + + @if (current.type === 'variables') { + @for (variable of current.config; track variable) { +
+
+ {{variable.fieldDescription}} +
+ @if (variable.isArray) { +
+ @for (v of variable.value; track v) { +
+ {{getVariableValue(v)}} +
+ } +
+ } @else { +
+ {{getVariableValue(variable.value)}} +
+ } +
+ } + } + @if (current.type === 'scores') { + @for (score of current.config; track score) { +
+ @if (score.relationships) { +
+ @for (variable of score._relationships; track variable) { +
+
+ {{variable.fieldDescription}} +
+ @if (variable.isArray) { +
+ @for (v of variable.value; track v) { +
+ {{getVariableValue(v)}} +
+ } +
+ } @else { +
+ {{getVariableValue(variable.value)}} +
+ } +
+ } +
+ } +
+ {{score.description}} +
+ @if (score._options) { +
+ @for (option of score._options; track option) { +
+
+ +
+ +
+ } +
+ } +
+ } + } + @if (current.type === 'formulas') { + @for (formula of current.config; track formula) { +
+
+ {{formula.description}} +
+
+ {{formula.value}} +
+
+ } + } +
+ } +
+ } +
+
+ } +
+ + + + + + +
+
+
+ } + +
+ +
+
+
+ } @else { +
{{ 'details.not_found' | transloco }}
+ } + } +
+
diff --git a/indexer-frontend/src/app/views/details/label-document-details/label-document-details.component.scss b/indexer-frontend/src/app/views/details/label-document-details/label-document-details.component.scss new file mode 100644 index 0000000000..305d531c93 --- /dev/null +++ b/indexer-frontend/src/app/views/details/label-document-details/label-document-details.component.scss @@ -0,0 +1,265 @@ +.step-container { + display: flex; + width: 100%; + height: 100%; + padding: 16px; + overflow: auto; +} + +.step-body-container { + display: flex; + width: 100%; + height: fit-content; + border-radius: 8px; + box-shadow: 0px 4px 8px 0px var(--guardian-shadow, #00000014); + background: var(--guardian-background, #FFFFFF); + padding: 24px; + flex-direction: column; + + .step-body-header { + font-size: 24px; + font-weight: 600; + line-height: 32px; + text-align: left; + color: var(--guardian-font-color, #23252E); + margin-bottom: 24px; + } +} + +.node-container { + .node-header { + font-size: 24px; + font-weight: 600; + line-height: 32px; + text-align: left; + color: var(--guardian-font-color, #23252E); + margin-bottom: 4px; + + .node-prefix { + margin-right: 12px; + } + } + + .node-sub-header { + font-size: 20px; + font-weight: 600; + line-height: 32px; + text-align: left; + color: var(--guardian-font-color, #23252E); + margin-bottom: 4px; + } + + .node-status { + width: 8px; + height: 8px; + overflow: hidden; + border-radius: 50%; + background: #AAB7C4; + display: inline-block; + margin: 3px 12px 3px 0px; + + &[status="true"] { + background: #19BE47; + } + + &[status="false"] { + background: #FF432A; + } + } + + .node-body { + padding-top: 12px; + padding-bottom: 16px; + } + + .field-container { + margin-bottom: 16px; + + .field-name { + font-size: 12px; + font-weight: 500; + line-height: 14px; + color: #181818; + margin-bottom: 6px; + } + + .field-value { + width: 100%; + border-radius: 8px; + border: 1px solid #E1E7EF; + background: #F9FAFC; + padding: 12px 16px; + font-size: 14px; + font-weight: 400; + line-height: 14px; + color: #23252E; + margin-bottom: 8px; + } + } +} + +.fields-container { + .field-container { + margin-bottom: 16px; + + .field-name { + font-size: 12px; + font-weight: 500; + line-height: 14px; + text-align: left; + color: var(--guardian-font-color, #23252E); + padding: 8px 0px; + } + + .field-value { + padding: 12px 16px 12px 16px; + border-radius: 8px; + border: 1px solid var(--guardian-border-color, #E1E7EF); + background: var(--guardian-grey-background, #F9FAFC); + font-size: 14px; + font-weight: 400; + line-height: 16px; + text-align: left; + color: var(--guardian-font-color, #23252E); + width: 100%; + min-height: 42px; + overflow: hidden; + text-overflow: ellipsis; + user-select: text; + } + + .field-value-array { + .field-value { + margin-bottom: 16px; + + &:last-child { + margin-bottom: 0px; + } + } + } + } +} + +.score-container { + .score-name { + font-size: 14px; + font-weight: 700; + line-height: 18px; + text-align: left; + color: var(--guardian-font-color, #23252E); + margin-bottom: 0px; + padding: 8px 0px; + margin-top: 24px; + } +} + +.options-container { + .option-container { + display: flex; + flex-direction: row; + + border-radius: 6px; + + &:not([disabled]) { + cursor: pointer; + } + + &:not([disabled]):hover { + background: var(--guardian-hover, #F0F3FC); + } + + .option-checkbox { + cursor: pointer; + min-height: 40px; + width: 42px; + min-width: 42px; + display: flex; + justify-content: flex-start; + align-items: center; + padding-left: 4px; + } + + .option-name { + cursor: pointer; + min-height: 40px; + width: 100%; + display: flex; + justify-content: flex-start; + align-items: center; + } + } +} + +.content::ng-deep { + .guardian-radio-button { + .p-disabled, .p-component:disabled { + opacity: 1; + filter: grayscale(1); + } + + .p-radiobutton:not(.p-radiobutton-disabled) .p-radiobutton-box.p-focus { + outline: 0 none; + outline-offset: 0; + box-shadow: none; + border-color: var(--guardian-primary-color, #4169E2); + } + + .p-radiobutton .p-radiobutton-box:not(.p-disabled):not(.p-highlight):hover { + border-color: var(--guardian-primary-color, #4169E2); + } + + .p-radiobutton { + .p-radiobutton-box { + border-color: var(--guardian-primary-color, #4169E2); + background: #ffffff; + + .p-radiobutton-icon { + background: var(--guardian-primary-color, #4169E2); + box-shadow: 0px 0px 0px 2px #ffffff; + } + + &.p-highlight { + background: var(--guardian-primary-color, #4169E2); + } + } + } + + &.radio-button-24 { + .p-radiobutton { + width: 24px; + height: 24px; + line-height: 24px; + padding: 4px; + + .p-radiobutton-box { + width: 16px; + height: 16px; + + .p-radiobutton-icon { + width: 8px; + height: 8px; + } + } + } + } + + &.radio-button-32 { + .p-radiobutton { + width: 32px; + height: 32px; + line-height: 32px; + padding: 4px; + + .p-radiobutton-box { + width: 16px; + height: 16px; + + .p-radiobutton-icon { + width: 8px; + height: 8px; + } + } + } + } + } +} \ No newline at end of file diff --git a/indexer-frontend/src/app/views/details/label-document-details/label-document-details.component.ts b/indexer-frontend/src/app/views/details/label-document-details/label-document-details.component.ts new file mode 100644 index 0000000000..3d87804003 --- /dev/null +++ b/indexer-frontend/src/app/views/details/label-document-details/label-document-details.component.ts @@ -0,0 +1,251 @@ +import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ActivatedRoute, Router } from '@angular/router'; +import { LoadingComponent } from '@components/loading/loading.component'; +import { MatTabsModule } from '@angular/material/tabs'; +import { ECElementEvent, EChartsOption } from 'echarts'; +import { NgxEchartsDirective } from 'ngx-echarts'; +import { MatInputModule } from '@angular/material/input'; +import { BaseDetailsComponent } from '../base-details/base-details.component'; +import { TranslocoModule } from '@jsverse/transloco'; +import { createChart } from '../base-details/relationships-chart.config'; +import { EntitiesService } from '@services/entities.service'; +import { OverviewFormComponent, OverviewFormField } from '@components/overview-form/overview-form.component'; +import { TabViewModule } from 'primeng/tabview'; +import { ColumnType, TableComponent } from '@components/table/table.component'; +import { ProgressSpinnerModule } from 'primeng/progressspinner'; +import { InputTextareaModule } from 'primeng/inputtextarea'; +import { ButtonModule } from 'primeng/button'; +import { IValidatorStep, LabelValidators } from '@indexer/interfaces'; +import { RadioButtonModule } from 'primeng/radiobutton'; +import { FormsModule } from '@angular/forms'; + +@Component({ + selector: 'label-document-details', + templateUrl: './label-document-details.component.html', + styleUrls: [ + '../base-details/base-details.component.scss', + './label-document-details.component.scss', + ], + standalone: true, + imports: [ + CommonModule, + LoadingComponent, + MatTabsModule, + NgxEchartsDirective, + MatInputModule, + TranslocoModule, + OverviewFormComponent, + TabViewModule, + TableComponent, + ProgressSpinnerModule, + InputTextareaModule, + ButtonModule, + RadioButtonModule, + FormsModule + ] +}) +export class LabelDocumentDetailsComponent extends BaseDetailsComponent { + public chartOption: EChartsOption = createChart(); + public label: any = null; + public steps?: IValidatorStep[]; + + overviewFields: OverviewFormField[] = [{ + label: 'details.hedera.topic_id', + path: 'topicId', + link: '/topics' + }, { + label: 'details.hedera.consensus_timestamp', + path: 'consensusTimestamp' + }, { + label: 'details.hedera.uuid', + path: 'uuid' + }, { + label: 'details.hedera.type', + path: 'type' + }, { + label: 'details.hedera.action', + path: 'action' + }, { + label: 'details.hedera.status', + path: 'status' + }, { + label: 'details.hedera.status_reason', + path: 'statusReason' + }, { + label: 'details.hedera.issuer', + path: 'options.issuer' + }, { + label: 'details.hedera.token_id', + path: 'analytics.tokenId', + link: '/tokens' + }, { + label: 'details.hedera.token_amount', + path: 'analytics.tokenAmount', + }] + + historyColumns: any[] = [ + { + title: 'details.hedera.consensus_timestamp', + field: 'consensusTimestamp', + type: ColumnType.TEXT, + width: '250px' + }, + { + title: 'details.hedera.topic_id', + field: 'topicId', + type: ColumnType.TEXT, + width: '100px' + }, + { + title: 'details.hedera.action', + field: 'action', + type: ColumnType.TEXT, + width: '200px' + }, + { + title: 'details.hedera.status', + field: 'status', + type: ColumnType.TEXT, + width: '100px' + }, + { + title: 'details.hedera.status_reason', + field: 'statusReason', + type: ColumnType.TEXT, + width: '100px' + } + ] + + constructor( + entitiesService: EntitiesService, + route: ActivatedRoute, + router: Router + ) { + super(entitiesService, route, router); + } + + protected override loadData(): void { + if (this.id) { + this.loading = true; + this.entitiesService.getLabelDocument(this.id).subscribe({ + next: (result) => { + this.setResult(result); + setTimeout(() => { + this.loading = false; + }, 500); + }, + error: ({ message }) => { + this.loading = false; + console.error(message); + } + }); + } else { + this.setResult(); + } + } + + protected override setResult(result?: any) { + super.setResult(result); + if (result) { + this.label = result.label; + } else { + this.label = null; + } + const vp = this.getVpDocument(); + if (vp) { + const labelConfig = this.label?.analytics?.config; + const validator = new LabelValidators(labelConfig); + this.steps = validator.getDocument(); + validator.setData([]); + validator.setVp(vp); + } else { + this.steps = []; + } + } + + protected override onNavigate(): void { + if (this.id && this.tab === 'relationships') { + this.loading = true; + this.entitiesService.getVpRelationships(this.id).subscribe({ + next: (result) => { + this.setRelationships(result); + this.setChartData(); + setTimeout(() => { + this.loading = false; + }, 500); + }, + error: ({ message }) => { + this.loading = false; + console.error(message); + } + }); + } + } + + protected override getTabIndex(name: string): number { + if (this.target) { + switch (name) { + case 'overview': return 0; + case 'documents': return 1; + case 'history': return 2; + case 'relationships': return 3; + case 'raw': return 4; + default: return 0; + } + } else { + return 0; + } + } + + protected override getTabName(index: number): string { + if (this.target) { + switch (index) { + case 0: return 'overview'; + case 1: return 'documents'; + case 2: return 'history'; + case 3: return 'relationships'; + case 4: return 'raw'; + default: return 'raw'; + } + } else { + return 'raw'; + } + } + + private setChartData() { + this.chartOption = createChart(this.relationships); + } + + public onSelect(event: any) { + if (event.dataType === 'node') { + this.toEntity( + String(event.data?.entityType), + event.name, + 'relationships' + ); + } + } + + public getJson(item: any): string { + return JSON.stringify(item, null, 4); + } + + public getVariableValue(value: any): any { + if (value === undefined) { + return 'N/A'; + } else { + return value; + } + } + + public getVpDocument() { + const file = this.getFirstDocument(); + if (file && file.documentObject) { + return { + document: file.documentObject + } + } + return null; + } +} diff --git a/indexer-frontend/src/app/views/details/nft-details/nft-details.component.html b/indexer-frontend/src/app/views/details/nft-details/nft-details.component.html index 77e467d4b8..f40a373fa3 100644 --- a/indexer-frontend/src/app/views/details/nft-details/nft-details.component.html +++ b/indexer-frontend/src/app/views/details/nft-details/nft-details.component.html @@ -5,31 +5,38 @@

{{ 'details.nft.header' | transloco }}

@if (loading) { -
- -
- } @else { - @if (row) { - - - - - - - - -
-
- -
-
-
-
+
+ +
} @else { -
{{ 'details.not_found' | transloco }}
- } + @if (row) { + + + + + + + + + + + +
+
+ +
+
+
+
+ } @else { +
{{ 'details.not_found' | transloco }}
+ } }
diff --git a/indexer-frontend/src/app/views/details/nft-details/nft-details.component.ts b/indexer-frontend/src/app/views/details/nft-details/nft-details.component.ts index 56b5855d35..60685219fa 100644 --- a/indexer-frontend/src/app/views/details/nft-details/nft-details.component.ts +++ b/indexer-frontend/src/app/views/details/nft-details/nft-details.component.ts @@ -3,12 +3,10 @@ import { CommonModule } from '@angular/common'; import { ActivatedRoute, Router } from '@angular/router'; import { LoadingComponent } from '@components/loading/loading.component'; import { MatTabsModule } from '@angular/material/tabs'; -import { ECElementEvent, EChartsOption } from 'echarts'; import { NgxEchartsDirective } from 'ngx-echarts'; import { MatInputModule } from '@angular/material/input'; import { BaseDetailsComponent } from '../base-details/base-details.component'; import { TranslocoModule } from '@jsverse/transloco'; -import { createChart } from '../base-details/relationships-chart.config'; import { EntitiesService } from '@services/entities.service'; import { OverviewFormComponent, OverviewFormField } from '@components/overview-form/overview-form.component'; import { TabViewModule } from 'primeng/tabview'; @@ -39,6 +37,7 @@ import { InputTextareaModule } from 'primeng/inputtextarea'; ] }) export class NFTDetailsComponent extends BaseDetailsComponent { + public labels: any[] = []; overviewFields: OverviewFormField[] = [ { @@ -53,9 +52,11 @@ export class NFTDetailsComponent extends BaseDetailsComponent { { label: 'details.nft.overview.metadata', path: 'metadata', + link: '/vp-documents', + pattern: '^\\d{10}\\.\\d{9}$' } ] - tabs: any[] = ['overview', 'history', 'raw']; + tabs: any[] = ['overview', 'history', 'labels', 'raw']; historyColumns: any[] = [ { title: 'details.hedera.transaction_id', @@ -82,6 +83,36 @@ export class NFTDetailsComponent extends BaseDetailsComponent { width: '200px' }, ] + labelColumns: any[] = [ + { + title: 'details.hedera.consensus_timestamp', + field: 'consensusTimestamp', + type: ColumnType.TEXT, + width: '250px', + link: { + field: 'consensusTimestamp', + url: '/label-documents', + }, + }, + { + title: 'details.hedera.topic_id', + field: 'topicId', + type: ColumnType.TEXT, + width: '100px' + }, + { + title: 'details.hedera.name', + field: 'analytics.labelName', + type: ColumnType.TEXT, + width: '200px' + }, + { + title: 'details.hedera.issuer', + field: 'analytics.issuer', + type: ColumnType.TEXT, + width: '300px' + } + ] constructor( entitiesService: EntitiesService, @@ -111,7 +142,16 @@ export class NFTDetailsComponent extends BaseDetailsComponent { } } - protected override onNavigate(): void {} + protected override setResult(result?: any) { + super.setResult(result); + if (result) { + this.labels = result.labels || []; + } else { + this.labels = []; + } + } + + protected override onNavigate(): void { } protected override getTabIndex(name: string): number { if (this.target) { diff --git a/indexer-frontend/src/app/views/details/statistic-details/statistic-details.component.html b/indexer-frontend/src/app/views/details/statistic-details/statistic-details.component.html new file mode 100644 index 0000000000..151afa09bf --- /dev/null +++ b/indexer-frontend/src/app/views/details/statistic-details/statistic-details.component.html @@ -0,0 +1,35 @@ +
+
+
+

{{ 'details.statistic.header' | transloco }} {{id}}

+
+ @if (loading) { +
+ +
+ } @else { + @if (row) { + + @if (target) { + + + + + + + + } + +
+ +
+
+ +
+ } @else { +
{{ 'details.not_found' | transloco }}
+ } + } +
+
diff --git a/indexer-frontend/src/app/views/details/statistic-details/statistic-details.component.scss b/indexer-frontend/src/app/views/details/statistic-details/statistic-details.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/indexer-frontend/src/app/views/details/statistic-details/statistic-details.component.ts b/indexer-frontend/src/app/views/details/statistic-details/statistic-details.component.ts new file mode 100644 index 0000000000..a94f7b3524 --- /dev/null +++ b/indexer-frontend/src/app/views/details/statistic-details/statistic-details.component.ts @@ -0,0 +1,133 @@ +import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ActivatedRoute, Router } from '@angular/router'; +import { LoadingComponent } from '@components/loading/loading.component'; +import { MatTabsModule } from '@angular/material/tabs'; +import { NgxEchartsDirective } from 'ngx-echarts'; +import { MatInputModule } from '@angular/material/input'; +import { BaseDetailsComponent } from '../base-details/base-details.component'; +import { TranslocoModule } from '@jsverse/transloco'; +import { EntitiesService } from '@services/entities.service'; +import { TabViewModule } from 'primeng/tabview'; +import { ProgressSpinnerModule } from 'primeng/progressspinner'; +import { ButtonModule } from 'primeng/button'; +import { + OverviewFormComponent, + OverviewFormField, +} from '@components/overview-form/overview-form.component'; +import { ActivityComponent } from '@components/activity/activity.component'; +import { InputTextareaModule } from 'primeng/inputtextarea'; + +@Component({ + selector: 'statistic-details', + templateUrl: './statistic-details.component.html', + styleUrls: [ + '../base-details/base-details.component.scss', + './statistic-details.component.scss', + ], + standalone: true, + imports: [ + CommonModule, + LoadingComponent, + MatTabsModule, + NgxEchartsDirective, + MatInputModule, + TranslocoModule, + TabViewModule, + ProgressSpinnerModule, + ButtonModule, + OverviewFormComponent, + ActivityComponent, + InputTextareaModule, + ], +}) +export class StatisticDetailsComponent extends BaseDetailsComponent { + tabs: any[] = ['overview', 'activity', 'raw']; + overviewFields: OverviewFormField[] = [ + { + label: 'details.statistic.overview.topic_id', + path: 'topicId', + link: '/topics', + }, + { + label: 'details.statistic.overview.policy_topic_id', + path: 'options.policyTopicId', + link: '/topics', + }, + { + label: 'details.statistic.overview.owner', + path: 'options.owner', + }, + { + label: 'details.statistic.overview.name', + path: 'options.name', + }, + { + label: 'details.statistic.overview.description', + path: 'options.description', + }, + ]; + + constructor( + entitiesService: EntitiesService, + route: ActivatedRoute, + router: Router + ) { + super(entitiesService, route, router); + } + + protected override loadData(): void { + if (this.id) { + this.loading = true; + this.entitiesService.getStatistic(this.id).subscribe({ + next: (result) => { + this.setResult(result); + setTimeout(() => { + this.loading = false; + }, 500); + }, + error: ({ message }) => { + this.loading = false; + console.error(message); + }, + }); + } else { + this.setResult(); + } + } + + protected override onNavigate(): void {} + + protected override getTabIndex(name: string): number { + if (this.target) { + const tabIndex = this.tabs.findIndex((item) => item === name); + return tabIndex >= 0 ? tabIndex : 0; + } else { + return 0; + } + } + + protected override getTabName(index: number): string { + if (this.target) { + return this.tabs[index] || 'raw'; + } else { + return 'raw'; + } + } + + public override onOpenSchemas() { + this.router.navigate(['/schemas'], { + queryParams: { + topicId: this.row.topicId, + }, + }); + } + + public override onOpenVCs() { + this.router.navigate(['/vc-documents'], { + queryParams: { + topicId: this.row.topicId + }, + }); + } +} diff --git a/indexer-frontend/src/app/views/details/token-details/token-details.component.html b/indexer-frontend/src/app/views/details/token-details/token-details.component.html index a71983e51a..b20c5fe0c1 100644 --- a/indexer-frontend/src/app/views/details/token-details/token-details.component.html +++ b/indexer-frontend/src/app/views/details/token-details/token-details.component.html @@ -4,26 +4,33 @@

{{ 'details.token.header' | transloco }} {{id}}

@if (loading) { -
- -
- } @else { - @if (row) { - - - - - -
- -
-
-
+
+ +
} @else { -
{{ 'details.not_found' | transloco }}
- } + @if (row) { + + + + + + + + +
+ +
+
+
+ } @else { +
{{ 'details.not_found' | transloco }}
+ } }
diff --git a/indexer-frontend/src/app/views/details/token-details/token-details.component.ts b/indexer-frontend/src/app/views/details/token-details/token-details.component.ts index ed3c3946cd..5d6acfbede 100644 --- a/indexer-frontend/src/app/views/details/token-details/token-details.component.ts +++ b/indexer-frontend/src/app/views/details/token-details/token-details.component.ts @@ -19,6 +19,7 @@ import { import { TagModule } from 'primeng/tag'; import { ActivityComponent } from '@components/activity/activity.component'; import { InputTextareaModule } from 'primeng/inputtextarea'; +import { ColumnType, TableComponent } from '@components/table/table.component'; export enum TokenType { FT = 'FUNGIBLE_COMMON', @@ -46,11 +47,15 @@ export enum TokenType { OverviewFormComponent, ActivityComponent, TagModule, - InputTextareaModule + InputTextareaModule, + TableComponent ], }) export class TokenDetailsComponent extends BaseDetailsComponent { - tabs: any[] = ['overview', 'raw']; + public labels: any[] = []; + + tabs: any[] = ['overview', 'labels', 'raw']; + overviewFields: OverviewFormField[] = [ { label: 'details.token.overview.token_id', @@ -74,6 +79,37 @@ export class TokenDetailsComponent extends BaseDetailsComponent { }, ]; + labelColumns: any[] = [ + { + title: 'details.hedera.consensus_timestamp', + field: 'consensusTimestamp', + type: ColumnType.TEXT, + width: '250px', + link: { + field: 'consensusTimestamp', + url: '/label-documents', + }, + }, + { + title: 'details.hedera.topic_id', + field: 'topicId', + type: ColumnType.TEXT, + width: '100px' + }, + { + title: 'details.hedera.name', + field: 'analytics.labelName', + type: ColumnType.TEXT, + width: '200px' + }, + { + title: 'details.hedera.issuer', + field: 'analytics.issuer', + type: ColumnType.TEXT, + width: '300px' + } + ] + additionalOveriviewFormFields: OverviewFormField[] = []; constructor( @@ -124,7 +160,16 @@ export class TokenDetailsComponent extends BaseDetailsComponent { } } - protected override onNavigate(): void {} + protected override setResult(result?: any) { + super.setResult(result); + if (result) { + this.labels = result.labels || []; + } else { + this.labels = []; + } + } + + protected override onNavigate(): void { } protected override getTabIndex(name: string): number { if (this.target) { diff --git a/indexer-frontend/src/app/views/details/vp-document-details/vp-document-details.component.html b/indexer-frontend/src/app/views/details/vp-document-details/vp-document-details.component.html index eeeeb53af4..59d86a8ec4 100644 --- a/indexer-frontend/src/app/views/details/vp-document-details/vp-document-details.component.html +++ b/indexer-frontend/src/app/views/details/vp-document-details/vp-document-details.component.html @@ -12,68 +12,79 @@

{{ 'details.header.vp' | transloco }}

@if (row) { @if (target) { - - - + + + - - @if (first._ipfs) { -
-
-
- {{ 'details.cid' | transloco }}: -
-
- {{first._ipfs[0].cid}} -
-
- @if (first._ipfsStatus) { -
-
-
- -
+ + @if (first._ipfs) { +
+
+
+ {{ 'details.cid' | transloco }}: +
+
+ {{first._ipfs[0].cid}}
- } @else { -
-
- @if (first._ipfs.length<2) { -
- {{ 'details.document_not_loaded_header' | transloco }} -
-
- {{ 'details.document_not_loaded_message' | transloco }} -
- } @else { -
- {{ 'details.documents_not_loaded_header' | transloco }} + @if (first._ipfsStatus) { +
+
+
+
-
- {{ 'details.documents_not_loaded_message' | transloco }} -
- } - +
-
- } -
- } - + } @else { +
+
+ @if (first._ipfs.length<2) { +
+ {{ 'details.document_not_loaded_header' | transloco }} +
+
+ {{ 'details.document_not_loaded_message' | transloco }} +
+ } @else { +
+ {{ 'details.documents_not_loaded_header' | transloco }} +
+
+ {{ 'details.documents_not_loaded_message' | transloco }} +
+ } + +
+
+ } +
+ } + - - - + + + - -
-
-
+ +
+
+
+ + + + }
diff --git a/indexer-frontend/src/app/views/details/vp-document-details/vp-document-details.component.ts b/indexer-frontend/src/app/views/details/vp-document-details/vp-document-details.component.ts index 0042fd6da8..5933f943ea 100644 --- a/indexer-frontend/src/app/views/details/vp-document-details/vp-document-details.component.ts +++ b/indexer-frontend/src/app/views/details/vp-document-details/vp-document-details.component.ts @@ -41,6 +41,8 @@ import { ButtonModule } from 'primeng/button'; ] }) export class VpDocumentDetailsComponent extends BaseDetailsComponent { + public labels: any[] = []; + public chartOption: EChartsOption = createChart(); overviewFields: OverviewFormField[] = [{ @@ -50,24 +52,31 @@ export class VpDocumentDetailsComponent extends BaseDetailsComponent { }, { label: 'details.hedera.consensus_timestamp', path: 'consensusTimestamp' - },{ + }, { label: 'details.hedera.uuid', path: 'uuid' - },{ + }, { label: 'details.hedera.type', path: 'type' - },{ + }, { label: 'details.hedera.action', path: 'action' - },{ + }, { label: 'details.hedera.status', path: 'status' - },{ + }, { label: 'details.hedera.status_reason', path: 'statusReason' }, { label: 'details.hedera.issuer', path: 'options.issuer' + }, { + label: 'details.hedera.token_id', + path: 'analytics.tokenId', + link: '/tokens' + }, { + label: 'details.hedera.token_amount', + path: 'analytics.tokenAmount', }] historyColumns: any[] = [ @@ -103,6 +112,37 @@ export class VpDocumentDetailsComponent extends BaseDetailsComponent { } ] + labelColumns: any[] = [ + { + title: 'details.hedera.consensus_timestamp', + field: 'consensusTimestamp', + type: ColumnType.TEXT, + width: '250px', + link: { + field: 'consensusTimestamp', + url: '/label-documents', + }, + }, + { + title: 'details.hedera.topic_id', + field: 'topicId', + type: ColumnType.TEXT, + width: '100px' + }, + { + title: 'details.hedera.name', + field: 'analytics.labelName', + type: ColumnType.TEXT, + width: '200px' + }, + { + title: 'details.hedera.issuer', + field: 'analytics.issuer', + type: ColumnType.TEXT, + width: '300px' + } + ] + constructor( entitiesService: EntitiesService, route: ActivatedRoute, @@ -131,6 +171,15 @@ export class VpDocumentDetailsComponent extends BaseDetailsComponent { } } + protected override setResult(result?: any) { + super.setResult(result); + if (result) { + this.labels = result.labels || []; + } else { + this.labels = []; + } + } + protected override onNavigate(): void { if (this.id && this.tab === 'relationships') { this.loading = true; @@ -157,7 +206,8 @@ export class VpDocumentDetailsComponent extends BaseDetailsComponent { case 'documents': return 1; case 'history': return 2; case 'relationships': return 3; - case 'raw': return 4; + case 'labels': return 4; + case 'raw': return 5; default: return 0; } } else { @@ -172,7 +222,8 @@ export class VpDocumentDetailsComponent extends BaseDetailsComponent { case 1: return 'documents'; case 2: return 'history'; case 3: return 'relationships'; - case 4: return 'raw'; + case 4: return 'labels'; + case 5: return 'raw'; default: return 'raw'; } } else { diff --git a/indexer-frontend/src/app/views/home/home.component.html b/indexer-frontend/src/app/views/home/home.component.html index 9f6712704f..8f87e62a8b 100644 --- a/indexer-frontend/src/app/views/home/home.component.html +++ b/indexer-frontend/src/app/views/home/home.component.html @@ -24,8 +24,12 @@

{{ 'landing.overview' | transloco }}

@for (stat of stats; track $index) { - + }
diff --git a/indexer-frontend/src/app/views/search/search.component.html b/indexer-frontend/src/app/views/search/search.component.html index 02f9922da9..2d8cb9bd4a 100644 --- a/indexer-frontend/src/app/views/search/search.component.html +++ b/indexer-frontend/src/app/views/search/search.component.html @@ -40,8 +40,15 @@
- +
diff --git a/indexer-frontend/src/app/views/search/search.component.ts b/indexer-frontend/src/app/views/search/search.component.ts index 758c5cf7bd..6339df7cd1 100644 --- a/indexer-frontend/src/app/views/search/search.component.ts +++ b/indexer-frontend/src/app/views/search/search.component.ts @@ -56,6 +56,12 @@ export class SearchViewComponent { field: 'type', width: '200px', }, + { + type: ColumnType.TEXT, + title: 'grid.topicId', + field: 'topicId', + width: '200px', + }, { type: ColumnType.TEXT, title: 'grid.consensus_timestamp', @@ -193,6 +199,8 @@ export class SearchViewComponent { ]); break; } + case 'NON_FUNGIBLE_UNIQUE': + case 'FUNGIBLE_COMMON': case 'Token': { this.router.navigate([`/tokens/${item.tokenId}`]); break; diff --git a/indexer-frontend/src/assets/i18n/en.json b/indexer-frontend/src/assets/i18n/en.json index 68efb68ccb..a4ae8a34f7 100644 --- a/indexer-frontend/src/assets/i18n/en.json +++ b/indexer-frontend/src/assets/i18n/en.json @@ -35,9 +35,13 @@ "nfts": "NFTs", "vps": "Verifiable Presentation (VPs)", "vcs": "Verifiable Credentials (VCs)", + "statistic_documents": "Statistic Documents (VCs)", + "label_documents": "Label Documents (VPs)", "dids": "Decentralized Identifiers (DIDs)", "others": "Others", - "search_placeholder": "Search by keyword" + "search_placeholder": "Search by keyword", + "statistics": "Statistics", + "labels": "Labels" }, "home": { "header": "Indexer" @@ -74,8 +78,10 @@ "issuer": "Issuer", "account": "Account", "did": "DID", + "topicId": "Topic Id", "registrantTopicId": "User Topic Id", "open": "Open", + "target": "Target", "filter": { "policy": "Policy", "schema": "Schema", @@ -85,7 +91,9 @@ "topic_id": "Topic Id", "policy_id": "Policy Id", "schema_id": "Schema Id", - "relationship": "Relationship" + "relationship": "Relationship", + "target": "Target", + "token_id": "Token Id" }, "paginator": { "items_per_page": "Items per page", @@ -100,8 +108,10 @@ "overview": "Overview", "documents": "Document", "history": "History", + "view": "View", "relationships": "Relationships", "raw_data": "Raw data", + "labels": "Labels", "tabs": { "activity": "Activity" }, @@ -128,17 +138,21 @@ "action": "Action", "status": "Status", "status_reason": "Reason", + "name": "Name", "issuer": "Issuer", "transaction_id": "Transaction Id", "sender_account_id": "Sender", - "receiver_account_id": "Receiver" + "receiver_account_id": "Receiver", + "token_id": "Token Id", + "token_amount": "Token Amount" }, "ipfs": "IPFS", "header": { "registry": "Standard Registry", "registry_user": "Registry User", "vc": "Verifiable Credential", - "vp": "Verifiable Presentation" + "vp": "Verifiable Presentation", + "label": "Label (Verifiable Presentation)" }, "topic": { "header": "Topic", @@ -178,7 +192,7 @@ "overview": { "topic_id": "Topic Id", "name": "Name", - "description": "Desription", + "description": "Description", "owner": "Owner" } }, @@ -192,7 +206,7 @@ "overview": { "topic_id": "Topic Id", "name": "Name", - "description": "Desription", + "description": "Description", "owner": "Owner", "hash": "Hash" } @@ -207,13 +221,41 @@ "topic_id": "Topic Id", "instance_topic_id": "Instance Topic Id", "name": "Name", - "description": "Desription", + "description": "Description", "owner": "Owner", "version": "Version", "policy_tag": "Policy Tag", "registry": "Standard Registry" } }, + "label": { + "header": "Label", + "tabs": { + "overview": "Overview", + "raw_data": "Raw data" + }, + "overview": { + "topic_id": "Topic Id", + "policy_topic_id": "Policy Topic Id", + "name": "Name", + "description": "Description", + "owner": "Owner" + } + }, + "statistic": { + "header": "Statistic", + "tabs": { + "overview": "Overview", + "raw_data": "Raw data" + }, + "overview": { + "topic_id": "Topic Id", + "policy_topic_id": "Policy Topic Id", + "name": "Name", + "description": "Description", + "owner": "Owner" + } + }, "schema": { "header": "Schema", "tabs": { @@ -226,7 +268,7 @@ "overview": { "topic_id": "Topic Id", "name": "Name", - "description": "Desription", + "description": "Description", "owner": "Owner", "version": "Version", "policy": "Policy" @@ -300,7 +342,7 @@ "contract_id": "Contract Id", "topic_id": "Topic Id", "type": "Type", - "description": "Desription", + "description": "Description", "owner": "Owner", "owner_did": "Owner DID" } diff --git a/indexer-interfaces/src/index.ts b/indexer-interfaces/src/index.ts index b4fe45f93b..0b2d0d58a7 100644 --- a/indexer-interfaces/src/index.ts +++ b/indexer-interfaces/src/index.ts @@ -3,3 +3,4 @@ export * from './interfaces/index.js'; export * from './models/index.js'; export * from './types/index.js'; export * from './constants/index.js'; +export * from './validators/index.js'; \ No newline at end of file diff --git a/indexer-interfaces/src/interfaces/details/index.ts b/indexer-interfaces/src/interfaces/details/index.ts index 7b73e3a06f..eaeef4f11c 100644 --- a/indexer-interfaces/src/interfaces/details/index.ts +++ b/indexer-interfaces/src/interfaces/details/index.ts @@ -13,3 +13,5 @@ export * from './contract.details.js'; export * from './topic.details.js'; export * from './token.details.js'; export * from './nft.details.js'; +export * from './statistic.details.js'; +export * from './label.details.js'; \ No newline at end of file diff --git a/indexer-interfaces/src/interfaces/details/label.details.ts b/indexer-interfaces/src/interfaces/details/label.details.ts new file mode 100644 index 0000000000..11c0011b24 --- /dev/null +++ b/indexer-interfaces/src/interfaces/details/label.details.ts @@ -0,0 +1,74 @@ +import { DetailsActivity } from './details.interface.js'; +import { Message } from '../message.interface.js'; + +/** + * Label options + */ +export interface LabelOptions { + /** + * UUID + */ + uuid: string; + /** + * Name + */ + name: string; + /** + * Description + */ + description: string; + /** + * Owner + */ + owner: string; + /** + * Policy topic identifier + */ + policyTopicId: string; + /** + * Instance topic identifier + */ + policyInstanceTopicId: string; +} + +/** + * Label analytics + */ +export interface LabelAnalytics { + /** + * Text search + */ + textSearch?: string; + /** + * Owner + */ + owner?: string; + /** + * Config + */ + config?: any; +} + +/** + * Label activity + */ +export interface LabelActivity { + /** + * Schemas + */ + schemas: number; + /** + * VPs + */ + vps: number; +} + +/** + * Label + */ +export type Label = Message; + +/** + * Label details + */ +export type LabelDetails = DetailsActivity; diff --git a/indexer-interfaces/src/interfaces/details/nft.details.ts b/indexer-interfaces/src/interfaces/details/nft.details.ts index 7bfcd799f5..2ce75d077d 100644 --- a/indexer-interfaces/src/interfaces/details/nft.details.ts +++ b/indexer-interfaces/src/interfaces/details/nft.details.ts @@ -1,5 +1,6 @@ import { Details } from './details.interface.js'; import { RawNFT } from '../raw-nft.interface.js'; +import { VP } from './vp.details.js'; /** * NFT @@ -9,4 +10,7 @@ export type NFT = RawNFT; /** * NFT details */ -export type NFTDetails = Details & { history: any[] }; +export interface NFTDetails extends Details { + labels?: VP[]; + history: any[]; +} \ No newline at end of file diff --git a/indexer-interfaces/src/interfaces/details/statistic.details.ts b/indexer-interfaces/src/interfaces/details/statistic.details.ts new file mode 100644 index 0000000000..289d95b968 --- /dev/null +++ b/indexer-interfaces/src/interfaces/details/statistic.details.ts @@ -0,0 +1,74 @@ +import { DetailsActivity } from './details.interface.js'; +import { Message } from '../message.interface.js'; + +/** + * Statistic options + */ +export interface StatisticOptions { + /** + * UUID + */ + uuid: string; + /** + * Name + */ + name: string; + /** + * Description + */ + description: string; + /** + * Owner + */ + owner: string; + /** + * Policy topic identifier + */ + policyTopicId: string; + /** + * Instance topic identifier + */ + policyInstanceTopicId: string; +} + +/** + * Statistic analytics + */ +export interface StatisticAnalytics { + /** + * Text search + */ + textSearch?: string; + /** + * Owner + */ + owner?: string; + /** + * Config + */ + config?: any; +} + +/** + * Statistic activity + */ +export interface StatisticActivity { + /** + * Schemas + */ + schemas: number; + /** + * VCs + */ + vcs: number; +} + +/** + * Statistic + */ +export type Statistic = Message; + +/** + * Statistic details + */ +export type StatisticDetails = DetailsActivity; diff --git a/indexer-interfaces/src/interfaces/details/token.details.ts b/indexer-interfaces/src/interfaces/details/token.details.ts index 5247b6c173..6968ca069a 100644 --- a/indexer-interfaces/src/interfaces/details/token.details.ts +++ b/indexer-interfaces/src/interfaces/details/token.details.ts @@ -1,5 +1,6 @@ import { Details } from './details.interface.js'; import { RawToken } from '../raw-token.interface.js'; +import { VP } from './vp.details.js'; /** * Token @@ -9,4 +10,6 @@ export type Token = RawToken; /** * Token details */ -export type TokenDetails = Details; +export interface TokenDetails extends Details { + labels?: VP[]; +} \ No newline at end of file diff --git a/indexer-interfaces/src/interfaces/details/vp.details.ts b/indexer-interfaces/src/interfaces/details/vp.details.ts index 7317c6e1e8..be7b021082 100644 --- a/indexer-interfaces/src/interfaces/details/vp.details.ts +++ b/indexer-interfaces/src/interfaces/details/vp.details.ts @@ -1,5 +1,6 @@ import { DetailsHistory } from './details.interface.js'; import { Message } from '../message.interface.js'; +import { Label } from './label.details.js'; /** * VP options @@ -35,6 +36,26 @@ export interface VPAnalytics { * Schema names */ schemaNames?: string[]; + /** + * Issuer + */ + issuer?: string; + /** + * Token + */ + tokenId?: string; + /** + * Label + */ + labelName?: string; + /** + * Token Amount + */ + tokenAmount?: string; + /** + * Labels + */ + labels?: string[]; } /** @@ -45,4 +66,13 @@ export type VP = Message; /** * VP details */ -export type VPDetails = DetailsHistory; +export interface VPDetails extends DetailsHistory { + labels?: VP[]; +} + +/** + * Label Document details + */ +export interface LabelDocumentDetails extends DetailsHistory { + label?: Label; +} \ No newline at end of file diff --git a/indexer-interfaces/src/interfaces/message.interface.ts b/indexer-interfaces/src/interfaces/message.interface.ts index da9aadbfc7..12fe00cbba 100644 --- a/indexer-interfaces/src/interfaces/message.interface.ts +++ b/indexer-interfaces/src/interfaces/message.interface.ts @@ -1,5 +1,5 @@ -import { MessageAction } from '../types/message-action.js'; -import { MessageType } from '../types/message-type.js'; +import { MessageAction } from '../types/message-action.type.js'; +import { MessageType } from '../types/message-type.type.js'; /** * Parsed message diff --git a/indexer-interfaces/src/interfaces/relationships.interface.ts b/indexer-interfaces/src/interfaces/relationships.interface.ts index 3581b5b222..5a7298be5b 100644 --- a/indexer-interfaces/src/interfaces/relationships.interface.ts +++ b/indexer-interfaces/src/interfaces/relationships.interface.ts @@ -1,4 +1,4 @@ -import { MessageType } from '../types/message-type'; +import { MessageType } from '../types/message-type.type'; import { Message } from './message.interface'; /** diff --git a/indexer-interfaces/src/types/index.ts b/indexer-interfaces/src/types/index.ts index 51f6d877bd..509a0623de 100644 --- a/indexer-interfaces/src/types/index.ts +++ b/indexer-interfaces/src/types/index.ts @@ -1,4 +1,5 @@ -export * from './message-type.js'; -export * from './message-action.js'; +export * from './message-type.type.js'; +export * from './message-action.type.js'; export * from './topic.type.js'; export * from './contract.type.js'; +export * from './message-status.type.js'; \ No newline at end of file diff --git a/indexer-interfaces/src/types/message-action.ts b/indexer-interfaces/src/types/message-action.type.ts similarity index 77% rename from indexer-interfaces/src/types/message-action.ts rename to indexer-interfaces/src/types/message-action.type.ts index 1ef9935c3e..06ec996060 100644 --- a/indexer-interfaces/src/types/message-action.ts +++ b/indexer-interfaces/src/types/message-action.type.ts @@ -31,4 +31,12 @@ export enum MessageAction { DeferredDiscontinuePolicy = 'deferred-discontinue-policy', MigrateVC = 'migrate-vc-document', MigrateVP = 'migrate-vp-document', + CreateRole = 'create-role', + UpdateRole = 'update-role', + DeleteRole = 'delete-role', + SetRole = 'set-role', + PublishPolicyStatistic = 'publish-policy-statistic', + CreateStatisticAssessment = 'create-assessment-document', + PublishPolicyLabel = 'publish-policy-label', + CreateLabelDocument = 'create-label-document', } diff --git a/indexer-interfaces/src/types/message-status.type.ts b/indexer-interfaces/src/types/message-status.type.ts new file mode 100644 index 0000000000..7d68a39c75 --- /dev/null +++ b/indexer-interfaces/src/types/message-status.type.ts @@ -0,0 +1,12 @@ +/** + * Message Status + */ +export enum MessageStatus { + NONE = '', + COMPRESSING = 'COMPRESSING', + COMPRESSED = 'COMPRESSED', + LOADING = 'LOADING', + LOADED = 'LOADED', + ERROR = 'ERROR', + UNSUPPORTED = 'UNSUPPORTED' +} \ No newline at end of file diff --git a/indexer-interfaces/src/types/message-type.ts b/indexer-interfaces/src/types/message-type.type.ts similarity index 77% rename from indexer-interfaces/src/types/message-type.ts rename to indexer-interfaces/src/types/message-type.type.ts index 15a7708901..c623d7eb3c 100644 --- a/indexer-interfaces/src/types/message-type.ts +++ b/indexer-interfaces/src/types/message-type.type.ts @@ -19,4 +19,8 @@ export enum MessageType { ROLE_DOCUMENT = 'Role-Document', SYNCHRONIZATION_EVENT = 'Synchronization Event', CONTRACT = 'Contract', + GUARDIAN_ROLE = 'Guardian-Role-Document', + USER_PERMISSIONS = 'User-Permissions', + POLICY_STATISTIC = 'Policy-Statistic', + POLICY_LABEL = 'Policy-Label' } \ No newline at end of file diff --git a/indexer-interfaces/src/validators/index.ts b/indexer-interfaces/src/validators/index.ts new file mode 100644 index 0000000000..68d3ef16a6 --- /dev/null +++ b/indexer-interfaces/src/validators/index.ts @@ -0,0 +1,21 @@ + +export * from './interfaces/index.js'; +export * from './rule-validator/document-field-validator.js'; +export * from './rule-validator/document-field-validators.js'; +export * from './rule-validator/document-validator.js'; +export * from './rule-validator/document-validators.js'; +export * from './rule-validator/field-validator.js'; +export * from './rule-validator/rule-validator.js'; +export * from './label-validator/item-group-validator.js'; +export * from './label-validator/item-label-validator.js'; +export * from './label-validator/item-node-validator.js'; +export * from './label-validator/item-rule-validator.js'; +export * from './label-validator/item-statistic-validator.js'; +export * from './label-validator/label-validator.js'; +export * from './label-validator/namespace.js'; +export * from './label-validator/score.js'; +export * from './label-validator/variable-validator.js'; +export * from './statistic-validator/variables.js'; +export * from './statistic-validator/formula.js'; +export * from './statistic-validator/score.js'; +export * from './utils/formula.js'; \ No newline at end of file diff --git a/indexer-interfaces/src/validators/interfaces/field-rule-status.ts b/indexer-interfaces/src/validators/interfaces/field-rule-status.ts new file mode 100644 index 0000000000..21cc4d8551 --- /dev/null +++ b/indexer-interfaces/src/validators/interfaces/field-rule-status.ts @@ -0,0 +1,7 @@ + +export enum FieldRuleResult { + None = 'None', + Error = 'Error', + Failure = 'Failure', + Success = 'Success' +} diff --git a/indexer-interfaces/src/validators/interfaces/formula-type.ts b/indexer-interfaces/src/validators/interfaces/formula-type.ts new file mode 100644 index 0000000000..7990444b7e --- /dev/null +++ b/indexer-interfaces/src/validators/interfaces/formula-type.ts @@ -0,0 +1,6 @@ +export enum FormulaType { + None = 'none', + Formula = 'formula', + Range = 'range', + Condition = 'condition' +} \ No newline at end of file diff --git a/indexer-interfaces/src/validators/interfaces/index.ts b/indexer-interfaces/src/validators/interfaces/index.ts new file mode 100644 index 0000000000..cc34dbaba2 --- /dev/null +++ b/indexer-interfaces/src/validators/interfaces/index.ts @@ -0,0 +1,13 @@ +export * from './field-rule-status.js' +export * from './formula-type.js' +export * from './policy-label.js' +export * from './schema-rule-result.js' +export * from './schema-rules.js' +export * from './statistic.js' +export * from './step-document.js' +export * from './sub-step.js' +export * from './validate-status.js' +export * from './validator-node.js' +export * from './validator-step.js' +export * from './validator.js' +export * from './variable-rule-data.js' \ No newline at end of file diff --git a/indexer-interfaces/src/validators/interfaces/policy-label.ts b/indexer-interfaces/src/validators/interfaces/policy-label.ts new file mode 100644 index 0000000000..015a09f939 --- /dev/null +++ b/indexer-interfaces/src/validators/interfaces/policy-label.ts @@ -0,0 +1,90 @@ +import { IStatisticConfig } from './statistic'; + +export enum NavItemType { + Group = 'group', + Rules = 'rules', + Label = 'label', + Statistic = 'statistic', +} + +export enum GroupType { + One = 'one', + Every = 'every', +} + +//children +export interface IItemConfig { + id: string; + tag?: string; + title?: string; + name?: string; + description?: string; + owner?: string; + schemaId?:string; +} + +export interface IGroupItemConfig extends IItemConfig { + type: NavItemType.Group; + rule?: GroupType; + children?: INavItemConfig[]; +} + +export interface ILabelItemConfig extends IItemConfig { + type: NavItemType.Label; + messageId?: string; + config?: IPolicyLabelConfig; +} + +export interface IRulesItemConfig extends IItemConfig { + type: NavItemType.Rules; + config?: IStatisticConfig; +} + +export interface IStatisticItemConfig extends IItemConfig { + type: NavItemType.Statistic; + messageId?: string; + config?: IStatisticConfig; +} + +export type INavItemConfig = IGroupItemConfig | IRulesItemConfig | ILabelItemConfig | IStatisticItemConfig; + +//imports +export interface INavStatisticImportConfig { + id: string; + type: NavItemType.Statistic; + name?: string; + description?: string; + messageId?: string; + owner?: string; + config?: IStatisticConfig; +} + +export interface INavLabelImportConfig { + id: string; + type: NavItemType.Label; + name?: string; + description?: string; + messageId?: string; + owner?: string; + config?: IPolicyLabelConfig; +} + +export type INavImportsConfig = INavStatisticImportConfig | INavLabelImportConfig + +export interface IPolicyLabelConfig { + schemaId?:string; + imports?: INavImportsConfig[]; + children?: INavItemConfig[]; +} + +export interface IPolicyLabel { + id?: string; + name?: string; + description?: string; + instanceTopicId?: string; + policyId?: string; + messageId?: string; + owner?: string; + status?: string; + config?: IPolicyLabelConfig; +} \ No newline at end of file diff --git a/indexer-interfaces/src/validators/interfaces/schema-rule-result.ts b/indexer-interfaces/src/validators/interfaces/schema-rule-result.ts new file mode 100644 index 0000000000..3f3b40fb99 --- /dev/null +++ b/indexer-interfaces/src/validators/interfaces/schema-rule-result.ts @@ -0,0 +1,13 @@ +import { FieldRuleResult } from './field-rule-status.js'; + +export interface SchemaRuleValidateResult { + [path: string]: { + status: FieldRuleResult; + tooltip: string; + rules: { + name: string; + description: string; + status: string; + }[]; + }; +} diff --git a/indexer-interfaces/src/validators/interfaces/schema-rules.ts b/indexer-interfaces/src/validators/interfaces/schema-rules.ts new file mode 100644 index 0000000000..632998c75f --- /dev/null +++ b/indexer-interfaces/src/validators/interfaces/schema-rules.ts @@ -0,0 +1,81 @@ +export interface IConditionFormula { + type: 'formula'; + formula: string; +} + +export interface IConditionRange { + type: 'range'; + variable: string; + min: string | number; + max: string | number; +} + +export interface IConditionText { + type: 'text'; + variable: string; + value: string +} + +export interface IConditionEnum { + type: 'enum'; + variable: string; + value: string[] +} + +export interface IConditionIfData { + type: 'if'; + condition: (IConditionFormula | IConditionRange | IConditionText | IConditionEnum); + formula: (IConditionFormula | IConditionRange | IConditionText | IConditionEnum); +} + +export interface IConditionElseData { + type: 'else'; + formula: (IConditionFormula | IConditionRange | IConditionText | IConditionEnum); +} + +export interface IConditionRuleData { + type: 'condition'; + conditions: (IConditionIfData | IConditionElseData)[] +} + +export interface IFormulaRuleData { + type: 'formula'; + formula: string; +} + +export interface IRangeRuleData { + type: 'range'; + min: string | number; + max: string | number; +} + +export interface ISchemaRuleData { + id: string; + schemaId: string; + path: string; + schemaName: string; + schemaPath: string; + fieldType: string; + fieldRef: boolean; + fieldArray: boolean; + fieldDescription: string; + fieldProperty: string; + fieldPropertyName: string; + rule?: IFormulaRuleData | IConditionRuleData | IRangeRuleData; +} + +export interface ISchemaRulesConfig { + fields?: ISchemaRuleData[]; + schemas?: string[]; +} + +export interface ISchemaRules { + id?: string; + name?: string; + description?: string; + instanceTopicId?: string; + policyId?: string; + owner?: string; + status?: string; + config?: ISchemaRulesConfig; +} \ No newline at end of file diff --git a/indexer-interfaces/src/validators/interfaces/statistic.ts b/indexer-interfaces/src/validators/interfaces/statistic.ts new file mode 100644 index 0000000000..a212fc91df --- /dev/null +++ b/indexer-interfaces/src/validators/interfaces/statistic.ts @@ -0,0 +1,67 @@ +import { IConditionRuleData, IFormulaRuleData, IRangeRuleData } from './schema-rules.js'; + +export interface IFormulaData { + id: string; + type: string; + description: string; + formula: string; + rule?: IFormulaRuleData | IConditionRuleData | IRangeRuleData; +} + +export interface IVariableData { + id: string; + schemaId: string; + path: string; + schemaName: string; + schemaPath: string; + fieldType: string; + fieldRef: boolean; + fieldArray: boolean; + fieldDescription: string; + fieldProperty: string; + fieldPropertyName: string; +} + +export interface IScoreOption { + description: string; + value: number | string; +} + +export interface IScoreData { + id: string; + type: string; + description: string; + relationships: string[]; + options: IScoreOption[]; +} + +export enum RuleType { + Main = 'main', + Related = 'related', + Unrelated = 'unrelated' +} + +export interface IRuleData { + schemaId: string; + type: RuleType; + unique: boolean | string; +} + +export interface IStatisticConfig { + variables?: IVariableData[], + scores?: IScoreData[], + formulas?: IFormulaData[], + rules?: IRuleData[] +} + +export interface IStatistic { + id?: string; + name?: string; + description?: string; + instanceTopicId?: string; + policyId?: string; + messageId?: string; + owner?: string; + status?: string; + config?: IStatisticConfig; +} \ No newline at end of file diff --git a/indexer-interfaces/src/validators/interfaces/step-document.ts b/indexer-interfaces/src/validators/interfaces/step-document.ts new file mode 100644 index 0000000000..ad65e74841 --- /dev/null +++ b/indexer-interfaces/src/validators/interfaces/step-document.ts @@ -0,0 +1,5 @@ +export interface IStepDocument { + id: string; + schema: string; + document: any; +} \ No newline at end of file diff --git a/indexer-interfaces/src/validators/interfaces/sub-step.ts b/indexer-interfaces/src/validators/interfaces/sub-step.ts new file mode 100644 index 0000000000..91d3ba3ee0 --- /dev/null +++ b/indexer-interfaces/src/validators/interfaces/sub-step.ts @@ -0,0 +1,6 @@ + +export interface ISubStep { + index: number; + name: string; + selected: boolean; +} diff --git a/indexer-interfaces/src/validators/interfaces/validate-status.ts b/indexer-interfaces/src/validators/interfaces/validate-status.ts new file mode 100644 index 0000000000..609e904793 --- /dev/null +++ b/indexer-interfaces/src/validators/interfaces/validate-status.ts @@ -0,0 +1,7 @@ + +export interface IValidateStatus { + id: string; + valid: boolean; + error?: any; + children?: IValidateStatus[]; +} diff --git a/indexer-interfaces/src/validators/interfaces/validator-node.ts b/indexer-interfaces/src/validators/interfaces/validator-node.ts new file mode 100644 index 0000000000..453bbcec67 --- /dev/null +++ b/indexer-interfaces/src/validators/interfaces/validator-node.ts @@ -0,0 +1,10 @@ +import { IValidator } from './validator.js'; + +export interface IValidatorNode { + name: string; + item: IValidator; + selectable: boolean; + type: string | null; + icon?: string; + children: IValidatorNode[]; +} \ No newline at end of file diff --git a/indexer-interfaces/src/validators/interfaces/validator-step.ts b/indexer-interfaces/src/validators/interfaces/validator-step.ts new file mode 100644 index 0000000000..6c0c5bae9e --- /dev/null +++ b/indexer-interfaces/src/validators/interfaces/validator-step.ts @@ -0,0 +1,17 @@ +import { IValidator } from './validator.js'; +import { ISubStep } from './sub-step.js'; +import { IValidateStatus } from './validate-status.js'; + +export interface IValidatorStep { + name: string; + title: string; + prefix?: string; + item: IValidator; + type: string; + config: any; + auto: boolean; + disabled?: boolean; + subIndexes?: ISubStep[]; + update: () => void; + validate: () => IValidateStatus; +} \ No newline at end of file diff --git a/indexer-interfaces/src/validators/interfaces/validator.ts b/indexer-interfaces/src/validators/interfaces/validator.ts new file mode 100644 index 0000000000..d31d9a4687 --- /dev/null +++ b/indexer-interfaces/src/validators/interfaces/validator.ts @@ -0,0 +1,13 @@ +import { GroupItemValidator } from '../label-validator/item-group-validator.js'; +import { LabelItemValidator } from '../label-validator/item-label-validator.js'; +import { NodeItemValidator } from '../label-validator/item-node-validator.js'; +import { RuleItemValidator } from '../label-validator/item-rule-validator.js'; +import { StatisticItemValidator } from '../label-validator/item-statistic-validator.js'; + +export type IValidator = ( + GroupItemValidator | + LabelItemValidator | + RuleItemValidator | + StatisticItemValidator | + NodeItemValidator +); \ No newline at end of file diff --git a/indexer-interfaces/src/validators/interfaces/variable-rule-data.ts b/indexer-interfaces/src/validators/interfaces/variable-rule-data.ts new file mode 100644 index 0000000000..5e32413886 --- /dev/null +++ b/indexer-interfaces/src/validators/interfaces/variable-rule-data.ts @@ -0,0 +1,3 @@ +import { IFormulaRuleData, IConditionRuleData, IRangeRuleData } from './schema-rules.js'; + +export type IVariableRuleData = IFormulaRuleData | IConditionRuleData | IRangeRuleData | undefined; \ No newline at end of file diff --git a/indexer-interfaces/src/validators/label-validator/item-group-validator.ts b/indexer-interfaces/src/validators/label-validator/item-group-validator.ts new file mode 100644 index 0000000000..cabfde0a9e --- /dev/null +++ b/indexer-interfaces/src/validators/label-validator/item-group-validator.ts @@ -0,0 +1,144 @@ +import { IValidator } from '../interfaces/validator.js'; +import { IValidateStatus } from '../interfaces/validate-status.js'; +import { IValidatorStep } from '../interfaces/validator-step.js'; +import { ValidateScore } from './score.js'; +import { ValidateNamespace } from './namespace.js'; +import { NodeItemValidator } from './item-node-validator.js'; +import { GroupType, IGroupItemConfig, IStepDocument, NavItemType } from '../interfaces/index.js'; + +export class GroupItemValidator { + public readonly type: NavItemType | null = NavItemType.Group; + + public readonly id: string; + public readonly name: string; + public readonly title: string; + public readonly tag: string; + public readonly children: IValidator[]; + public readonly steps: number = 0; + public readonly rule: GroupType; + public readonly schema: string; + + public isRoot: boolean; + + private namespace: ValidateNamespace; + private scope: ValidateScore; + private valid: IValidateStatus | undefined; + + public prefix: string; + + constructor(item: IGroupItemConfig) { + this.id = item.id; + this.name = item.name || ''; + this.title = item.title || ''; + this.tag = item.tag || ''; + this.schema = item.schemaId || ''; + this.rule = item.rule || GroupType.Every; + this.children = NodeItemValidator.fromArray(item.children); + this.isRoot = false; + } + + public get status(): boolean | undefined { + return this.valid ? this.valid.valid : undefined; + } + + public setData(namespace: ValidateNamespace) { + this.namespace = namespace; + this.scope = this.namespace.createScore(this.id, this.tag); + for (const child of this.children) { + child.setData(namespace); + } + } + + public validate(): IValidateStatus { + this.valid = { + id: this.id, + valid: true, + children: [] + }; + + if (!this.children.length) { + return this.valid; + } + + let count: number = 0; + for (const child of this.children) { + const childResult = child.validate(); + this.valid.children?.push(childResult); + if (childResult.valid) { + count++; + } + } + + if (this.rule === GroupType.Every) { + this.valid.valid = this.children.length === count; + } else { + this.valid.valid = count > 0; + } + + return this.valid; + } + + public getSteps(): IValidatorStep[] { + return [{ + item: this, + name: this.title, + title: this.title, + prefix: this.prefix, + auto: true, + type: 'validate', + config: null, + update: this.validate.bind(this), + validate: this.validate.bind(this), + }]; + } + + public getNamespace(): ValidateNamespace { + return this.namespace; + } + + public getScope(): ValidateScore { + return this.scope; + } + + public getStatus(): IValidateStatus | undefined { + return this.valid; + } + + public getResult(): any { + return { + status: this.status + } + } + + public setResult(document: any): void { + if (!document) { + this.valid = { + id: this.id, + valid: false, + error: 'Invalid document' + }; + return; + } + this.valid = { + id: this.id, + valid: !!document.status + }; + } + + public clear(): void { + this.valid = undefined; + } + + public getVC(): IStepDocument | null { + return { + id: this.id, + schema: this.schema, + document: this.getResult() + }; + } + + public setVC(vc: any): boolean { + this.setResult(vc); + return true; + } +} diff --git a/indexer-interfaces/src/validators/label-validator/item-label-validator.ts b/indexer-interfaces/src/validators/label-validator/item-label-validator.ts new file mode 100644 index 0000000000..5f114318d6 --- /dev/null +++ b/indexer-interfaces/src/validators/label-validator/item-label-validator.ts @@ -0,0 +1,132 @@ +import { IValidateStatus } from '../interfaces/validate-status.js'; +import { IValidatorStep } from '../interfaces/validator-step.js'; +import { ValidateScore } from './score.js'; +import { ValidateNamespace } from './namespace.js'; +import { GroupItemValidator } from './item-group-validator.js'; +import { IStepDocument } from '../interfaces/step-document.js'; +import { NavItemType, INavImportsConfig, INavItemConfig, ILabelItemConfig, IPolicyLabelConfig, GroupType } from '../interfaces/index.js'; + +export class LabelItemValidator { + public readonly type: NavItemType | null = NavItemType.Label; + + public readonly id: string; + public readonly name: string; + public readonly title: string; + public readonly tag: string; + public readonly steps: number = 0; + public readonly root: GroupItemValidator; + public readonly schema: string; + + public isRoot: boolean; + + private namespace: ValidateNamespace; + private scope: ValidateScore; + private valid: IValidateStatus | undefined; + + // tslint:disable-next-line:no-unused-variable + private readonly imports: INavImportsConfig[]; + // tslint:disable-next-line:no-unused-variable + private readonly children: INavItemConfig[]; + + public prefix: string; + + constructor(item: ILabelItemConfig) { + this.id = item.id; + this.name = item.name || ''; + this.title = item.title || ''; + this.tag = item.tag || ''; + this.isRoot = false; + + const label: IPolicyLabelConfig = item.config || {}; + this.imports = label.imports || []; + this.children = label.children || []; + this.schema = item.schemaId || label.schemaId || ''; + + this.root = new GroupItemValidator({ + id: item.id, + type: NavItemType.Group, + name: item.name, + schemaId: this.schema, + rule: GroupType.Every, + children: this.children + }); + this.root.isRoot = true; + } + + public get status(): boolean | undefined { + return this.valid ? this.valid.valid : undefined; + } + + public setData(namespace: ValidateNamespace) { + this.namespace = namespace; + this.scope = this.namespace.createScore(this.id, this.tag); + this.root.setData(namespace); + } + + public validate(): IValidateStatus { + this.valid = this.root.validate(); + return this.valid; + } + + public getSteps(): IValidatorStep[] { + return [{ + item: this, + name: this.title, + title: this.title, + prefix: this.prefix, + auto: true, + type: 'validate', + config: null, + update: this.validate.bind(this), + validate: this.validate.bind(this) + }]; + } + + public getNamespace(): ValidateNamespace { + return this.namespace; + } + + public getScope(): ValidateScore { + return this.scope; + } + + public getStatus(): IValidateStatus | undefined { + return this.valid; + } + + public getResult(): any { + return { + status: this.status + } + } + + public setResult(document: any): void { + if (!document) { + this.valid = { + id: this.id, + valid: false, + error: 'Invalid document' + }; + return; + } + this.root.setResult(document); + this.valid = this.root.getStatus(); + } + + public clear(): void { + this.valid = undefined; + } + + public getVC(): IStepDocument | null { + return { + id: this.id, + schema: this.schema, + document: this.getResult() + }; + } + + public setVC(vc: any): boolean { + this.setResult(vc); + return true; + } +} diff --git a/indexer-interfaces/src/validators/label-validator/item-node-validator.ts b/indexer-interfaces/src/validators/label-validator/item-node-validator.ts new file mode 100644 index 0000000000..c82ca0728e --- /dev/null +++ b/indexer-interfaces/src/validators/label-validator/item-node-validator.ts @@ -0,0 +1,141 @@ +import { GroupItemValidator } from './item-group-validator.js'; +import { LabelItemValidator } from './item-label-validator.js'; +import { RuleItemValidator } from './item-rule-validator.js'; +import { StatisticItemValidator } from './item-statistic-validator.js'; +import { IValidator } from '../interfaces/validator.js'; +import { IValidateStatus } from '../interfaces/validate-status.js'; +import { IValidatorStep } from '../interfaces/validator-step.js'; +import { ValidateScore } from './score.js'; +import { ValidateNamespace } from './namespace.js'; +import { FormulaEngine } from '../utils/formula.js'; +import { IFormulaData, INavItemConfig, IStepDocument, NavItemType } from '../interfaces/index.js'; + +export class NodeItemValidator { + public readonly type: NavItemType | null = null; + + public readonly id: string; + public readonly name: string; + public readonly title: string; + public readonly tag: string; + public readonly steps: number = 0; + public readonly isRoot: boolean = false; + + private namespace: ValidateNamespace; + private scope: ValidateScore; + private valid: IValidateStatus | undefined; + + public prefix: string; + + constructor(item: any) { + this.id = item.id; + this.name = item.name; + this.title = item.title; + this.tag = item.tag; + } + + public setData(namespace: ValidateNamespace) { + this.namespace = namespace; + this.scope = this.namespace.createScore(this.id, this.tag); + } + + public get status(): boolean | undefined { + return this.valid ? this.valid.valid : undefined; + } + + public validate(): IValidateStatus { + this.valid = { + id: this.id, + valid: false, + error: 'Unidentified item' + }; + return this.valid; + } + + public getSteps(): IValidatorStep[] { + return [{ + item: this, + name: this.name, + title: this.title, + prefix: this.prefix, + auto: true, + type: 'validate', + config: null, + update: this.validate.bind(this), + validate: this.validate.bind(this) + }]; + } + + public getStatus(): IValidateStatus | undefined { + return this.valid; + } + + public getNamespace(): ValidateNamespace { + return this.namespace; + } + + public getScope(): ValidateScore { + return this.scope; + } + + public static calculateFormula(item: IFormulaData, scope: any): any { + let value = FormulaEngine.evaluate(item.formula, scope); + if (value) { + if (item.type === 'string') { + value = String(value); + } else { + value = Number(value); + } + } + return value; + } + + public static from(item: INavItemConfig): IValidator { + switch (item.type) { + case NavItemType.Group: { + return new GroupItemValidator(item); + } + case NavItemType.Label: { + return new LabelItemValidator(item); + } + case NavItemType.Rules: { + return new RuleItemValidator(item); + } + case NavItemType.Statistic: { + return new StatisticItemValidator(item); + } + default: { + return new NodeItemValidator(item); + } + } + } + + public static fromArray(items?: INavItemConfig[]): IValidator[] { + const validators: IValidator[] = []; + if (Array.isArray(items)) { + for (const item of items) { + validators.push(NodeItemValidator.from(item)); + } + } + return validators; + } + + public getResult(): any { + return null; + } + + public setResult(result: any): void { + return; + } + + public clear(): void { + this.valid = undefined; + } + + public getVC(): IStepDocument | null { + return null; + } + + public setVC(vc: any): boolean { + return false; + } +} diff --git a/indexer-interfaces/src/validators/label-validator/item-rule-validator.ts b/indexer-interfaces/src/validators/label-validator/item-rule-validator.ts new file mode 100644 index 0000000000..da991ae1e2 --- /dev/null +++ b/indexer-interfaces/src/validators/label-validator/item-rule-validator.ts @@ -0,0 +1,360 @@ +import { IValidateStatus } from '../interfaces/validate-status.js'; +import { ISubStep } from '../interfaces/sub-step.js'; +import { IValidatorStep } from '../interfaces/validator-step.js'; +import { ValidateScore } from './score.js'; +import { ValidateNamespace } from './namespace.js'; +import { NodeItemValidator } from './item-node-validator.js'; +import { FormulaValidator } from './variable-validator.js'; +import { FieldRuleResult } from '../interfaces/field-rule-status.js'; +import { VariableData } from '../statistic-validator/variables.js'; +import { ScoreData } from '../statistic-validator/score.js'; +import { FormulaData } from '../statistic-validator/formula.js'; +import { IRulesItemConfig, IStepDocument, NavItemType } from '../interfaces/index.js'; + +export class RuleItemValidator { + public readonly type: NavItemType | null = NavItemType.Rules; + + public readonly id: string; + public readonly name: string; + public readonly title: string; + public readonly tag: string; + public readonly steps: number = 3; + public readonly schema: string; + public readonly isRoot: boolean = false; + + private namespace: ValidateNamespace; + private scope: ValidateScore; + private valid: IValidateStatus | undefined; + + private readonly variables: VariableData[]; + private readonly scores: ScoreData[]; + private readonly formulas: FormulaData[]; + + public prefix: string; + + constructor(item: IRulesItemConfig) { + this.id = item.id; + this.name = item.name || ''; + this.title = item.title || ''; + this.tag = item.tag || ''; + this.schema = item.schemaId || ''; + + this.variables = VariableData.from(item.config?.variables); + this.scores = ScoreData.from(item.config?.scores); + this.formulas = FormulaData.from(item.config?.formulas); + + for (const score of this.scores) { + score.setRelationships(this.variables); + } + } + + public get status(): boolean | undefined { + return this.valid ? this.valid.valid : undefined; + } + + public setData(namespace: ValidateNamespace) { + this.namespace = namespace; + this.scope = this.namespace.createScore(this.id, this.tag); + for (const item of this.variables) { + this.scope.setName(item.id); + } + for (const item of this.scores) { + this.scope.setName(item.id); + } + for (const item of this.formulas) { + this.scope.setName(item.id); + } + } + + public updateVariables() { + for (const variable of this.variables) { + const value = this.namespace.getField(variable.schemaId, variable.path); + variable.setValue(value); + this.scope.setVariable(variable.id, variable.value); + } + } + + public updateScores() { + for (const variable of this.variables) { + this.scope.setVariable(variable.id, variable.value); + } + } + + public updateFormulas() { + for (const variable of this.variables) { + this.scope.setVariable(variable.id, variable.value); + } + for (const score of this.scores) { + this.scope.setVariable(score.id, score.value); + } + for (const formula of this.formulas) { + const scope = this.getScore(); + const value = NodeItemValidator.calculateFormula(formula, scope); + formula.setValue(value); + this.scope.setVariable(formula.id, value); + } + } + + public validateVariables(): IValidateStatus { + if (this.valid) { + if (this.valid.valid === false) { + return this.valid; + } + } else { + this.valid = { + id: this.id, + valid: true + }; + } + + for (const variable of this.variables) { + const value = this.namespace.getField(variable.schemaId, variable.path); + const status = variable.validate(value); + this.scope.setVariable(variable.id, variable.value); + if (!status) { + this.valid.valid = false; + this.valid.error = 'Invalid variable'; + return this.valid; + } + } + return this.valid; + } + + public validateScores(): IValidateStatus { + if (this.valid) { + if (this.valid.valid === false) { + return this.valid; + } + } else { + this.valid = { + id: this.id, + valid: true + }; + } + + for (const score of this.scores) { + const status = score.validate(score.value); + if (!status) { + this.valid.valid = false; + this.valid.error = 'Invalid scores'; + return this.valid; + } + } + return this.valid; + } + + public validateFormulas(): IValidateStatus { + if (this.valid) { + if (this.valid.valid === false) { + return this.valid; + } + } else { + this.valid = { + id: this.id, + valid: true + }; + } + + for (const score of this.scores) { + this.scope.setVariable(score.id, score.value); + } + for (const formula of this.formulas) { + const scope = this.getScore(); + const value = NodeItemValidator.calculateFormula(formula, scope); + const status = formula.validate(value); + this.scope.setVariable(formula.id, value); + if (!status) { + this.valid.valid = false; + this.valid.error = 'Invalid formula'; + return this.valid; + } + } + return this.valid; + } + + public validate(): IValidateStatus { + if (this.valid) { + if (this.valid.valid === false) { + return this.valid; + } + } else { + this.valid = { + id: this.id, + valid: true + }; + } + + const scope = this.getScore(); + for (const formula of this.formulas) { + const validator = new FormulaValidator(formula); + const status = validator.validate(scope); + formula.status = status; + if (status === FieldRuleResult.Failure || status === FieldRuleResult.Error) { + this.valid.valid = false; + this.valid.error = 'Invalid condition'; + return this.valid; + } + } + + return this.valid; + } + + private getScore(): any { + const namespace = this.namespace.getNamespace(); + const scope = this.scope.getScore(); + return Object.assign(namespace, scope); + } + + public getSteps(): IValidatorStep[] { + const steps: IValidatorStep[] = []; + const subIndex: ISubStep[] = []; + + if (this.variables?.length) { + subIndex.push({ + index: subIndex.length + 1, + name: 'Overview', + selected: false + }); + } + if (this.scores?.length) { + subIndex.push({ + index: subIndex.length + 1, + name: 'Scores', + selected: false + }); + } + if (this.formulas?.length) { + subIndex.push({ + index: subIndex.length + 1, + name: 'Statistics', + selected: false + }); + } + + if (this.variables?.length) { + steps.push({ + item: this, + name: 'Overview', + title: this.title, + prefix: this.prefix, + auto: false, + type: 'variables', + config: this.variables, + subIndexes: subIndex.map(e => { return { ...e, selected: e.name === 'Overview' }; }), + update: this.updateVariables.bind(this), + validate: this.validateVariables.bind(this) + }); + } + if (this.scores?.length) { + steps.push({ + item: this, + name: 'Scores', + title: this.title, + prefix: this.prefix, + auto: false, + type: 'scores', + config: this.scores, + subIndexes: subIndex.map(e => { return { ...e, selected: e.name === 'Scores' }; }), + update: this.updateScores.bind(this), + validate: this.validateScores.bind(this) + }); + } + if (this.formulas?.length) { + steps.push({ + item: this, + name: 'Statistics', + title: this.title, + prefix: this.prefix, + auto: false, + type: 'formulas', + config: this.formulas, + subIndexes: subIndex.map(e => { return { ...e, selected: e.name === 'Statistics' }; }), + update: this.updateFormulas.bind(this), + validate: this.validateFormulas.bind(this) + }); + } + steps.push({ + item: this, + name: this.title, + title: this.title, + prefix: this.prefix, + auto: true, + type: 'validate', + config: null, + update: this.validate.bind(this), + validate: this.validate.bind(this) + }); + return steps; + } + + public getNamespace(): ValidateNamespace { + return this.namespace; + } + + public getScope(): ValidateScore { + return this.scope; + } + + public getStatus(): IValidateStatus | undefined { + return this.valid; + } + + public getResult(): any { + const document: any = { + status: this.status + }; + for (const field of this.variables) { + if (field.value !== undefined) { + document[field.id] = field.getValue(); + } + } + for (const score of this.scores) { + document[score.id] = score.getValue(); + } + for (const formula of this.formulas) { + document[formula.id] = formula.getValue(); + } + return document; + } + + public setResult(document: any): void { + if(!document) { + this.valid = { + id: this.id, + valid: false, + error: 'Invalid document' + }; + return; + } + for (const field of this.variables) { + field.setValue(document[field.id]); + } + for (const score of this.scores) { + score.setValue(document[score.id]); + } + for (const formula of this.formulas) { + formula.setValue(document[formula.id]); + } + this.valid = { + id: this.id, + valid: !!document.status + }; + } + + public clear(): void { + this.valid = undefined; + } + + public getVC(): IStepDocument | null { + return { + id: this.id, + schema: this.schema, + document: this.getResult() + }; + } + + public setVC(vc: any): boolean { + this.setResult(vc); + return true; + } +} \ No newline at end of file diff --git a/indexer-interfaces/src/validators/label-validator/item-statistic-validator.ts b/indexer-interfaces/src/validators/label-validator/item-statistic-validator.ts new file mode 100644 index 0000000000..7d4f42e042 --- /dev/null +++ b/indexer-interfaces/src/validators/label-validator/item-statistic-validator.ts @@ -0,0 +1,339 @@ +import { IValidateStatus } from '../interfaces/validate-status.js'; +import { ISubStep } from '../interfaces/sub-step.js'; +import { IValidatorStep } from '../interfaces/validator-step.js'; +import { ValidateScore } from './score.js'; +import { ValidateNamespace } from './namespace.js'; +import { NodeItemValidator } from './item-node-validator.js'; +import { VariableData } from '../statistic-validator/variables.js'; +import { ScoreData } from '../statistic-validator/score.js'; +import { FormulaData } from '../statistic-validator/formula.js'; +import { IStepDocument, IStatisticItemConfig, NavItemType } from '../interfaces/index.js'; + +export class StatisticItemValidator { + public readonly type: NavItemType | null = NavItemType.Statistic; + + public readonly id: string; + public readonly name: string; + public readonly title: string; + public readonly tag: string; + public readonly steps: number = 3; + public readonly schema: string; + public readonly isRoot: boolean = false; + + private namespace: ValidateNamespace; + private scope: ValidateScore; + private valid: IValidateStatus | undefined; + + private readonly variables: VariableData[]; + private readonly scores: ScoreData[]; + private readonly formulas: FormulaData[]; + + public prefix: string; + + constructor(item: IStatisticItemConfig) { + this.id = item.id; + this.name = item.name || ''; + this.title = item.title || ''; + this.tag = item.tag || ''; + this.schema = item.schemaId || ''; + + this.variables = VariableData.from(item.config?.variables); + this.scores = ScoreData.from(item.config?.scores); + this.formulas = FormulaData.from(item.config?.formulas); + + for (const score of this.scores) { + score.setRelationships(this.variables); + } + } + + public get status(): boolean | undefined { + return this.valid ? this.valid.valid : undefined; + } + + public setData(namespace: ValidateNamespace) { + this.namespace = namespace; + this.scope = this.namespace.createScore(this.id, this.tag); + for (const item of this.variables) { + this.scope.setName(item.id); + } + for (const item of this.scores) { + this.scope.setName(item.id); + } + for (const item of this.formulas) { + this.scope.setName(item.id); + } + } + + public updateVariables() { + for (const variable of this.variables) { + const value = this.namespace.getField(variable.schemaId, variable.path); + variable.setValue(value); + this.scope.setVariable(variable.id, variable.value); + } + } + + public updateScores() { + for (const variable of this.variables) { + this.scope.setVariable(variable.id, variable.value); + } + } + + public updateFormulas() { + for (const variable of this.variables) { + this.scope.setVariable(variable.id, variable.value); + } + for (const score of this.scores) { + this.scope.setVariable(score.id, score.value); + } + for (const formula of this.formulas) { + const scope = this.getScore(); + const value = NodeItemValidator.calculateFormula(formula, scope); + formula.setValue(value); + this.scope.setVariable(formula.id, value); + } + } + + public validateVariables(): IValidateStatus { + if (this.valid) { + if (this.valid.valid === false) { + return this.valid; + } + } else { + this.valid = { + id: this.id, + valid: true + }; + } + + for (const variable of this.variables) { + const value = this.namespace.getField(variable.schemaId, variable.path); + const status = variable.validate(value); + this.scope.setVariable(variable.id, variable.value); + if (!status) { + this.valid.valid = false; + this.valid.error = 'Invalid variable'; + return this.valid; + } + } + return this.valid; + } + + public validateScores(): IValidateStatus { + if (this.valid) { + if (this.valid.valid === false) { + return this.valid; + } + } else { + this.valid = { + id: this.id, + valid: true + }; + } + + for (const score of this.scores) { + const status = score.validate(score.value); + if (!status) { + this.valid.valid = false; + this.valid.error = 'Invalid scores'; + return this.valid; + } + } + return this.valid; + } + + public validateFormulas(): IValidateStatus { + if (this.valid) { + if (this.valid.valid === false) { + return this.valid; + } + } else { + this.valid = { + id: this.id, + valid: true + }; + } + + for (const score of this.scores) { + this.scope.setVariable(score.id, score.value); + } + for (const formula of this.formulas) { + const scope = this.getScore(); + const value = NodeItemValidator.calculateFormula(formula, scope); + const status = formula.validate(value); + this.scope.setVariable(formula.id, value); + if (!status) { + this.valid.valid = false; + this.valid.error = 'Invalid formula'; + return this.valid; + } + } + return this.valid; + } + + public validate(): IValidateStatus { + if (!this.valid) { + this.valid = { + id: this.id, + valid: true, + }; + } + return this.valid; + } + + private getScore(): any { + const namespace = this.namespace.getNamespace(); + const scope = this.scope.getScore(); + return Object.assign(namespace, scope); + } + + public getSteps(): IValidatorStep[] { + const steps: IValidatorStep[] = []; + const subIndex: ISubStep[] = []; + + if (this.variables?.length) { + subIndex.push({ + index: subIndex.length + 1, + name: 'Overview', + selected: false + }); + } + if (this.scores?.length) { + subIndex.push({ + index: subIndex.length + 1, + name: 'Scores', + selected: false + }); + } + if (this.formulas?.length) { + subIndex.push({ + index: subIndex.length + 1, + name: 'Statistics', + selected: false + }); + } + + if (this.variables?.length) { + steps.push({ + item: this, + name: 'Overview', + title: this.title, + prefix: this.prefix, + auto: false, + type: 'variables', + config: this.variables, + subIndexes: subIndex.map(e => { return { ...e, selected: e.name === 'Overview' }; }), + update: this.updateVariables.bind(this), + validate: this.validateVariables.bind(this) + }); + } + if (this.scores?.length) { + steps.push({ + item: this, + name: 'Scores', + title: this.title, + prefix: this.prefix, + auto: false, + type: 'scores', + config: this.scores, + subIndexes: subIndex.map(e => { return { ...e, selected: e.name === 'Scores' }; }), + update: this.updateScores.bind(this), + validate: this.validateScores.bind(this) + }); + } + if (this.formulas?.length) { + steps.push({ + item: this, + name: 'Statistics', + title: this.title, + prefix: this.prefix, + auto: false, + type: 'formulas', + config: this.formulas, + subIndexes: subIndex.map(e => { return { ...e, selected: e.name === 'Statistics' }; }), + update: this.updateFormulas.bind(this), + validate: this.validateFormulas.bind(this) + }); + } + steps.push({ + item: this, + name: this.title, + title: this.title, + prefix: this.prefix, + auto: true, + type: 'validate', + config: null, + update: this.validate.bind(this), + validate: this.validate.bind(this) + }); + return steps; + } + + public getNamespace(): ValidateNamespace { + return this.namespace; + } + + public getScope(): ValidateScore { + return this.scope; + } + + public getStatus(): IValidateStatus | undefined { + return this.valid; + } + + public getResult(): any { + const document: any = {}; + for (const field of this.variables) { + if (field.value !== undefined) { + document[field.id] = field.getValue(); + } + } + for (const score of this.scores) { + document[score.id] = score.getValue(); + } + for (const formula of this.formulas) { + document[formula.id] = formula.getValue(); + } + return document; + } + + public setResult(document: any): void { + if (!document) { + this.valid = { + id: this.id, + valid: false, + error: 'Invalid document' + }; + return; + } + for (const field of this.variables) { + field.setValue(document[field.id]); + } + for (const score of this.scores) { + score.setValue(document[score.id]); + } + for (const formula of this.formulas) { + formula.setValue(document[formula.id]); + } + this.valid = { + id: this.id, + valid: true, + }; + } + + public clear(): void { + this.valid = undefined; + } + + public getVC(): IStepDocument | null { + return { + id: this.id, + schema: this.schema, + document: this.getResult() + }; + } + + public setVC(vc: any): boolean { + this.setResult(vc); + return true; + } +} diff --git a/indexer-interfaces/src/validators/label-validator/label-validator.ts b/indexer-interfaces/src/validators/label-validator/label-validator.ts new file mode 100644 index 0000000000..2db47740b4 --- /dev/null +++ b/indexer-interfaces/src/validators/label-validator/label-validator.ts @@ -0,0 +1,274 @@ + +import { LabelItemValidator } from './item-label-validator.js'; +import { GroupItemValidator } from './item-group-validator.js'; +import { ValidateNamespace } from './namespace.js'; +import { IValidatorStep } from '../interfaces/validator-step.js'; +import { IValidatorNode } from '../interfaces/validator-node.js'; +import { IValidateStatus } from '../interfaces/validate-status.js'; +import { IValidator } from '../interfaces/validator.js'; +import { IStepDocument, INavImportsConfig, INavItemConfig, IPolicyLabel, IPolicyLabelConfig, NavItemType } from '../interfaces/index.js'; + +export class LabelValidators { + // tslint:disable-next-line:no-unused-variable + private readonly imports: INavImportsConfig[]; + // tslint:disable-next-line:no-unused-variable + private readonly children: INavItemConfig[]; + private readonly root: LabelItemValidator; + private readonly steps: IValidatorStep[]; + private readonly tree: IValidatorNode; + private readonly list: IValidator[]; + private readonly document: IValidatorStep[]; + + private index: number = 0; + + constructor(label: IPolicyLabel) { + const config: IPolicyLabelConfig = label.config || {}; + this.root = new LabelItemValidator({ + id: 'root', + type: NavItemType.Label, + name: 'root', + title: label.name, + config + }); + this.root.isRoot = true; + this.tree = this.createTree(this.root); + this.steps = this.createSteps(this.root, []); + this.list = this.createList(this.root, []); + this.document = this.createDocument(this.root, []); + } + + public get status(): boolean | undefined { + return this.root ? this.root.status : undefined; + } + + private createList(node: IValidator, result: IValidator[]): IValidator[] { + result.push(node); + if (node.type === NavItemType.Group) { + for (const child of (node as GroupItemValidator).children) { + this.createList(child, result); + } + } else if (node.type === NavItemType.Label) { + this.createList((node as LabelItemValidator).root, result); + } + return result; + } + + private createSteps(node: IValidator, result: IValidatorStep[]): IValidatorStep[] { + if (node.type === NavItemType.Rules) { + this.addSteps(node, result); + } else if (node.type === NavItemType.Statistic) { + this.addSteps(node, result); + } else if (node.type === NavItemType.Group) { + for (const child of (node as GroupItemValidator).children) { + this.createSteps(child, result); + } + this.addSteps(node, result); + } else if (node.type === NavItemType.Label) { + this.createSteps((node as LabelItemValidator).root, result); + this.addSteps(node, result); + } + return result; + } + + private addSteps(node: IValidator, result: IValidatorStep[]): IValidatorStep[] { + const steps = node.getSteps(); + for (const step of steps) { + result.push(step); + } + return result; + } + + private createDocument(node: IValidator, result: IValidatorStep[]): IValidatorStep[] { + if (node.type === NavItemType.Rules) { + this.addDocument(node, result); + } else if (node.type === NavItemType.Statistic) { + this.addDocument(node, result); + } else if (node.type === NavItemType.Group) { + this.addDocument(node, result); + for (const child of (node as GroupItemValidator).children) { + this.createDocument(child, result); + } + } else if (node.type === NavItemType.Label) { + this.addDocument(node, result); + this.createDocument((node as LabelItemValidator).root, result); + } + return result; + } + + private addDocument(node: IValidator, result: IValidatorStep[]): IValidatorStep[] { + if (node.isRoot) { + return result; + } + const steps = node.getSteps(); + steps.unshift(steps.pop()); + for (const step of steps) { + result.push(step); + } + return result; + } + + private createTree(node: IValidator, prefix: string = ''): IValidatorNode { + const item: IValidatorNode = { + name: prefix ? `${prefix} ${node.title}` : node.title, + item: node, + type: node.type, + selectable: ( + node.type === NavItemType.Rules || + node.type === NavItemType.Statistic + ), + children: [] + } + node.prefix = prefix; + if (node.type === NavItemType.Group) { + const childrenNode = (node as GroupItemValidator).children; + for (let i = 0; i < childrenNode.length; i++) { + const childNode = childrenNode[i]; + const child = this.createTree(childNode, `${prefix}${i + 1}.`); + item.children.push(child); + } + } else if (node.type === NavItemType.Label) { + const childrenNode = (node as LabelItemValidator).root.children; + for (let i = 0; i < childrenNode.length; i++) { + const childNode = childrenNode[i]; + const child = this.createTree(childNode, `${prefix}${i + 1}.`); + item.children.push(child); + } + } + return item; + } + + public getValidator(id: string): IValidator | undefined { + return this.list.find((v) => v.id === id); + } + + public setData(documents: any[]) { + const namespaces = new ValidateNamespace('root', documents); + this.root.setData(namespaces); + } + + public getStatus(): IValidateStatus | undefined { + return this.root.getStatus(); + } + + public getTree(): IValidatorNode { + return this.tree; + } + + public getSteps(): IValidatorStep[] { + return this.steps; + } + + public getDocument(): IValidatorStep[] { + return this.document; + } + + public next(): IValidatorStep | null { + this.index++; + this.index = Math.max(Math.min(this.index, this.steps.length), -1); + const step = this.steps[this.index]; + if (step) { + step.update(); + if (step.auto) { + return this.next(); + } else { + return step; + } + } else { + return null; + } + } + + public prev(): IValidatorStep | null { + this.index--; + this.index = Math.max(Math.min(this.index, this.steps.length), -1); + const step = this.steps[this.index]; + if (step) { + if (step.auto) { + return this.prev(); + } else { + step.update(); + return step; + } + } else { + return null; + } + } + + public current(): IValidatorStep | null { + return this.steps[this.index]; + } + + public isNext(): boolean { + return this.index < (this.steps.length - 1); + } + + public isPrev(): boolean { + return this.index > 0; + } + + public start(): IValidatorStep | null { + this.index = -1; + return this.next(); + } + + public getResult(): any[] { + const documents: any[] = []; + for (const item of this.list) { + documents.push(item.getResult()); + } + return documents; + } + + public setResult(result: any[]): void { + for (let i = 0; i < this.list.length; i++) { + const item = this.list[i]; + const document = result[i]; + item.setResult(document); + } + } + + public validate(): IValidateStatus | undefined { + for (const step of this.steps) { + step.validate(); + } + return this.root.getStatus(); + } + + public clear(): void { + for (const item of this.list) { + item.clear(); + } + } + + public getVCs(): IStepDocument[] { + const vcs: IStepDocument[] = []; + for (const node of this.list) { + const vc = node.getVC(); + if (vc) { + vcs.push(vc); + } + } + return vcs; + } + + public setVp(vp: any): void { + const result: any[] = []; + const vcs: any = vp.document.verifiableCredential; + if (Array.isArray(vcs)) { + for (const vc of vcs) { + let cs = vc?.credentialSubject; + if (Array.isArray(cs)) { + cs = cs[0]; + } + result.push(cs); + } + } + + let index = 0; + for (const node of this.list) { + if (node.setVC(result[index])) { + index++; + } + } + } +} \ No newline at end of file diff --git a/indexer-interfaces/src/validators/label-validator/namespace.ts b/indexer-interfaces/src/validators/label-validator/namespace.ts new file mode 100644 index 0000000000..d0767f8493 --- /dev/null +++ b/indexer-interfaces/src/validators/label-validator/namespace.ts @@ -0,0 +1,88 @@ +import { ValidateScore } from './score.js'; + +function isObject(item: any): boolean { + return typeof item === 'object' && !Array.isArray(item) && item !== null; +} + +export class ValidateNamespace { + public readonly name: string; + + private readonly documents: any[]; + private readonly children: ValidateNamespace[]; + private readonly scores: ValidateScore[]; + + constructor(name: string, documents: any[]) { + this.name = name; + this.documents = documents; + this.children = []; + this.scores = []; + } + + public createNamespaces(name: string): ValidateNamespace { + const namespace = new ValidateNamespace(name, this.documents); + this.children.push(namespace); + return namespace; + } + + public createScore(id: string, name: string): any { + const score = new ValidateScore(id, name); + this.scores.push(score); + return score; + } + + public getNamespace(id?: string): any { + const namespace: any = {}; + for (const item of this.scores) { + if (item.id === id) { + return namespace; + } + const values = item.getScore(); + const keys = Object.keys(values); + for (const key of keys) { + if (!isObject(namespace[item.name])) { + namespace[item.name] = {}; + } + namespace[item.name][key] = values[key]; + } + } + return namespace; + } + + public getNames(id?: string): string[] { + const names = new Set(); + for (const item of this.scores) { + if (item.id === id) { + return Array.from(names); + } + const keys = item.getName(); + for (const key of keys) { + names.add(`${item.name}.${key}`); + } + } + return Array.from(names); + } + + public getField(schema: string, path: string): any { + const fullPath = [...(path || '').split('.')]; + const document = this.documents?.find((doc) => doc.schema === schema); + if (!document) { + return undefined; + } + return this.getFieldValue(document, fullPath); + } + + private getFieldValue(document: any, fullPath: string[]): any { + let value: any = document?.document?.credentialSubject; + if (Array.isArray(value)) { + value = value[0]; + } + for (const key of fullPath) { + if (value) { + value = value[key]; + } else { + return undefined; + } + } + return value; + } +} diff --git a/indexer-interfaces/src/validators/label-validator/score.ts b/indexer-interfaces/src/validators/label-validator/score.ts new file mode 100644 index 0000000000..c949a35b9c --- /dev/null +++ b/indexer-interfaces/src/validators/label-validator/score.ts @@ -0,0 +1,30 @@ +export class ValidateScore { + public readonly id: string; + public readonly name: string; + + private readonly score: any; + private readonly names: string[]; + + constructor(id: string, name: string) { + this.id = id; + this.name = name; + this.score = {}; + this.names = []; + } + + public setVariable(name: string, value: any) { + this.score[name] = value; + } + + public getScore(): any { + return this.score; + } + + public setName(name: string): void { + this.names.push(name); + } + + public getName(): string[] { + return this.names; + } +} \ No newline at end of file diff --git a/indexer-interfaces/src/validators/label-validator/variable-validator.ts b/indexer-interfaces/src/validators/label-validator/variable-validator.ts new file mode 100644 index 0000000000..d30b45d05a --- /dev/null +++ b/indexer-interfaces/src/validators/label-validator/variable-validator.ts @@ -0,0 +1,9 @@ + +import { IFormulaData } from '../interfaces/index.js'; +import { RuleValidator } from '../rule-validator/rule-validator.js'; + +export class FormulaValidator extends RuleValidator { + constructor(formula: IFormulaData) { + super(formula.id, formula.rule); + } +} \ No newline at end of file diff --git a/indexer-interfaces/src/validators/rule-validator/document-field-validator.ts b/indexer-interfaces/src/validators/rule-validator/document-field-validator.ts new file mode 100644 index 0000000000..e6e887e584 --- /dev/null +++ b/indexer-interfaces/src/validators/rule-validator/document-field-validator.ts @@ -0,0 +1,23 @@ +import { ISchemaRuleData } from '../interfaces/index.js'; + +export class DocumentFieldVariable { + public readonly id: string; + public readonly schemaId: string; + public readonly path: string; + public readonly fullPah: string; + public readonly fieldRef: boolean; + public readonly fieldArray: boolean; + public readonly fieldDescription: string; + public readonly schemaName: string; + + constructor(variable: ISchemaRuleData) { + this.id = variable.id; + this.schemaId = variable.schemaId; + this.path = variable.path; + this.fullPah = variable.schemaId + '/' + variable.path; + this.fieldRef = variable.fieldRef; + this.fieldArray = variable.fieldArray; + this.fieldDescription = variable.fieldDescription; + this.schemaName = variable.schemaName; + } +} \ No newline at end of file diff --git a/indexer-interfaces/src/validators/rule-validator/document-field-validators.ts b/indexer-interfaces/src/validators/rule-validator/document-field-validators.ts new file mode 100644 index 0000000000..5f22ba7914 --- /dev/null +++ b/indexer-interfaces/src/validators/rule-validator/document-field-validators.ts @@ -0,0 +1,44 @@ +import { DocumentFieldVariable } from './document-field-validator.js'; +import { FieldValidator } from './field-validator.js'; +import { FieldRuleResult, ISchemaRuleData } from '../interfaces/index.js'; + +export class DocumentFieldValidators { + public readonly rules: FieldValidator[]; + public readonly variables: DocumentFieldVariable[]; + public readonly idToPath: Map; + public readonly pathToId: Map; + + constructor(rules?: ISchemaRuleData[]) { + const variables = rules || []; + + this.rules = []; + this.variables = []; + for (const variable of variables) { + this.rules.push(new FieldValidator(variable)); + this.variables.push(new DocumentFieldVariable(variable)); + } + this.idToPath = new Map(); + this.pathToId = new Map(); + for (const variable of this.variables) { + this.idToPath.set(variable.id, variable.fullPah); + this.pathToId.set(variable.fullPah, variable.id); + } + } + + public validate(scope: any): { [x: string]: FieldRuleResult; } { + const result: { [x: string]: FieldRuleResult; } = {}; + for (const rule of this.rules) { + result[rule.id] = rule.validate(scope); + } + return result; + } + + public validateWithFullPath(scope: any): { [x: string]: FieldRuleResult; } { + const result: { [x: string]: FieldRuleResult; } = {}; + for (const rule of this.rules) { + const path = this.idToPath.get(rule.id) || rule.id; + result[path] = rule.validate(scope); + } + return result; + } +} \ No newline at end of file diff --git a/indexer-interfaces/src/validators/rule-validator/document-validator.ts b/indexer-interfaces/src/validators/rule-validator/document-validator.ts new file mode 100644 index 0000000000..ec4c144f68 --- /dev/null +++ b/indexer-interfaces/src/validators/rule-validator/document-validator.ts @@ -0,0 +1,88 @@ +import { DocumentFieldValidators } from './document-field-validators.js'; + +export class DocumentValidator { + public readonly name: string; + public readonly description: string; + public readonly schemas: Set; + public readonly validators: DocumentFieldValidators; + public readonly relationships: Map; + + constructor(data: any) { + const item = data.rules || {}; + const configuration = item.config || {}; + const relationships = data.relationships || []; + + this.relationships = new Map(); + for (const document of relationships) { + DocumentValidator.convertDocument( + DocumentValidator.getCredentialSubject(document?.document), + document?.schema + '/', + this.relationships + ); + } + + this.name = item.name; + this.description = item.description; + this.validators = new DocumentFieldValidators(configuration.fields); + this.schemas = new Set(); + for (const variable of this.validators.variables) { + this.schemas.add(variable.schemaId); + } + } + + public validate(iri: string | undefined, list: Map) { + if (!iri || !this.schemas.has(iri)) { + return null; + } + const score: { [id: string]: any; } = {}; + for (const variable of this.validators.variables) { + if (list.has(variable.fullPah)) { + score[variable.id] = list.get(variable.fullPah); + } else if (this.relationships.has(variable.fullPah)) { + score[variable.id] = this.relationships.get(variable.fullPah); + } else { + score[variable.id] = null; + } + } + return this.validators.validateWithFullPath(score); + } + + public static getCredentialSubject(document?: any): any { + const credentialSubject: any = document?.credentialSubject; + if (Array.isArray(credentialSubject)) { + return credentialSubject[0]; + } else { + return credentialSubject; + } + } + + public static convertDocument( + document: any, + path: string, + list: Map + ): Map { + if (!document) { + return list; + } + for (const [key, value] of Object.entries(document)) { + const currentPath = path + key; + switch (typeof value) { + case 'function': { + break; + } + case 'object': { + list.set(currentPath, value); + if (!Array.isArray(value)) { + DocumentValidator.convertDocument(value, currentPath + '.', list); + } + break; + } + default: { + list.set(currentPath, value); + break; + } + } + } + return list; + } +} diff --git a/indexer-interfaces/src/validators/rule-validator/document-validators.ts b/indexer-interfaces/src/validators/rule-validator/document-validators.ts new file mode 100644 index 0000000000..23419af5a7 --- /dev/null +++ b/indexer-interfaces/src/validators/rule-validator/document-validators.ts @@ -0,0 +1,80 @@ +import { SchemaRuleValidateResult, FieldRuleResult } from '../interfaces/index.js'; +import { DocumentValidator } from './document-validator.js'; + +export class DocumentValidators { + public schemas: Set; + public validators: DocumentValidator[]; + + constructor(data: any[] | null) { + this.validators = (data || []).map((v) => new DocumentValidator(v)); + this.schemas = new Set(); + for (const validator of this.validators) { + for (const iri of validator.schemas) { + this.schemas.add(iri); + } + } + } + + public validateVC(iri: string | undefined, vc: any): any { + if (this.validators.length === 0) { + return null; + } + if (!iri || !this.schemas.has(iri)) { + return null; + } + const data = DocumentValidator.getCredentialSubject(vc); + const list = DocumentValidator.convertDocument(data, iri + '/', new Map()); + return this.validate(iri, list); + } + + public validateForm(iri: string | undefined, data: any): any { + if (this.validators.length === 0) { + return null; + } + if (!iri || !this.schemas.has(iri)) { + return null; + } + const list = DocumentValidator.convertDocument(data, iri + '/', new Map()); + return this.validate(iri, list); + } + + private validate(iri: string | undefined, list: Map): any { + const results: { [x: string]: FieldRuleResult; }[] = []; + for (const validator of this.validators) { + results.push(validator.validate(iri, list)); + } + + const statuses: SchemaRuleValidateResult = {}; + for (let i = 0; i < results.length; i++) { + const result = results[i]; + const validator = this.validators[i]; + if (result) { + for (const [path, status] of Object.entries(result)) { + if (status !== FieldRuleResult.None) { + if (statuses[path]) { + if (status === FieldRuleResult.Error || status === FieldRuleResult.Failure) { + statuses[path].status = status; + } + statuses[path].rules.push({ + name: validator.name, + description: validator.description, + status, + }); + } else { + statuses[path] = { + status, + tooltip: '', + rules: [{ + name: validator.name, + description: validator.description, + status, + }] + }; + } + } + } + } + } + return statuses; + } +} diff --git a/indexer-interfaces/src/validators/rule-validator/field-validator.ts b/indexer-interfaces/src/validators/rule-validator/field-validator.ts new file mode 100644 index 0000000000..ed5594ccae --- /dev/null +++ b/indexer-interfaces/src/validators/rule-validator/field-validator.ts @@ -0,0 +1,21 @@ +import { ISchemaRuleData } from '../interfaces/index.js'; +import { RuleValidator } from './rule-validator.js'; + +export class FieldValidator extends RuleValidator { + public readonly path: string; + public readonly schemaId: string; + + constructor(rule: ISchemaRuleData) { + super(rule.id, rule.rule); + this.path = rule.path; + this.schemaId = rule.schemaId; + } + + public checkField(path: string, schema?: string): boolean { + if (schema) { + return this.path === path && this.schemaId === schema; + } else { + return this.path === path; + } + } +} diff --git a/indexer-interfaces/src/validators/rule-validator/rule-validator.ts b/indexer-interfaces/src/validators/rule-validator/rule-validator.ts new file mode 100644 index 0000000000..bfba48cd44 --- /dev/null +++ b/indexer-interfaces/src/validators/rule-validator/rule-validator.ts @@ -0,0 +1,133 @@ +import { FieldRuleResult, FormulaType, IVariableRuleData, IConditionEnum, IConditionFormula, IConditionRange, IConditionText } from '../interfaces/index.js'; +import { FormulaEngine } from '../utils/formula.js'; + +export class RuleValidator { + public readonly id: string; + public readonly rule: IVariableRuleData; + + private type: FormulaType; + private formula: string; + private conditions: { + type: 'if' | 'else'; + if: string; + then: string; + }[]; + + constructor(id: string, rule: IVariableRuleData) { + this.id = id; + this.rule = rule; + this.parse(); + } + + private parse() { + if (!this.rule) { + return; + } + if (this.rule.type === 'formula') { + this.type = FormulaType.Formula; + this.formula = this.rule.formula; + } else if (this.rule.type === 'range') { + this.type = FormulaType.Range; + this.formula = `${this.rule.min} <= ${this.id} <= ${this.rule.max}`; + } else if (this.rule.type === 'condition') { + this.type = FormulaType.Condition; + this.conditions = []; + const conditions = this.rule.conditions || []; + for (const condition of conditions) { + if (condition.type === 'if') { + this.conditions.push({ + type: 'if', + if: this.parseCondition(condition.condition), + then: this.parseCondition(condition.formula) + }); + } else if (condition.type === 'else') { + this.conditions.push({ + type: 'else', + if: '', + then: this.parseCondition(condition.formula) + }); + } + } + } else { + this.type = FormulaType.None; + } + } + + private parseCondition( + condition: IConditionFormula | IConditionRange | IConditionText | IConditionEnum + ): string { + if (!condition) { + return ''; + } + if (condition.type === 'formula') { + return condition.formula; + } else if (condition.type === 'range') { + return `${condition.min} <= ${condition.variable} <= ${condition.max}`; + } else if (condition.type === 'text') { + return `${condition.variable} == '${condition.value}'`; + } else if (condition.type === 'enum') { + const items = []; + if (Array.isArray(condition.value)) { + for (const value of condition.value) { + items.push(`${condition.variable} == '${value}'`); + } + } + return items.join(' or '); + } else { + return ''; + } + } + + public validate(scope: any): FieldRuleResult { + if (this.type === FormulaType.None) { + return FieldRuleResult.None; + } + + if (this.type === FormulaType.Formula) { + return this.calculate(this.formula, scope); + } + + if (this.type === FormulaType.Range) { + return this.calculate(this.formula, scope); + } + + if (this.type === FormulaType.Condition) { + for (const condition of this.conditions) { + const _if = condition.type === 'if' ? + this.calculate(condition.if, scope) : + FieldRuleResult.Success; + + if (_if === FieldRuleResult.Error) { + return FieldRuleResult.Error; + } + if (_if === FieldRuleResult.Success) { + return this.calculate(condition.then, scope); + } + } + return FieldRuleResult.None; + } + + return FieldRuleResult.None; + } + + private calculate(formula: string, scope: any): FieldRuleResult { + try { + if (!formula) { + return FieldRuleResult.None; + } + const result: any = FormulaEngine.evaluate(formula, scope); + if (result === '' || result === 'Incorrect formula') { + return FieldRuleResult.Error; + } + if (result === 0 || result === false || result === '0' || result === 'false') { + return FieldRuleResult.Failure; + } + if (result) { + return FieldRuleResult.Success; + } + return FieldRuleResult.Error; + } catch (error) { + return FieldRuleResult.Error; + } + } +} \ No newline at end of file diff --git a/indexer-interfaces/src/validators/statistic-validator/formula.ts b/indexer-interfaces/src/validators/statistic-validator/formula.ts new file mode 100644 index 0000000000..cc36af66dc --- /dev/null +++ b/indexer-interfaces/src/validators/statistic-validator/formula.ts @@ -0,0 +1,39 @@ +import { FieldRuleResult, IConditionRuleData, IFormulaData, IFormulaRuleData, IRangeRuleData } from '../interfaces/index.js'; + +export class FormulaData implements IFormulaData { + public id: string; + public type: string; + public description: string; + public formula: string; + public rule?: IFormulaRuleData | IConditionRuleData | IRangeRuleData; + + public value: any; + public status: FieldRuleResult; + + constructor(item: IFormulaData) { + this.id = item.id; + this.type = item.type; + this.description = item.description; + this.formula = item.formula; + this.rule = item.rule; + } + + public setValue(value: any): void { + this.value = value; + } + + public getValue(): any { + return this.value; + } + + public validate(value: any): boolean { + return this.value === value; + } + + public static from(data?: IFormulaData[]): FormulaData[] { + if (Array.isArray(data)) { + return data.map((e) => new FormulaData(e)); + } + return []; + } +} diff --git a/indexer-interfaces/src/validators/statistic-validator/score.ts b/indexer-interfaces/src/validators/statistic-validator/score.ts new file mode 100644 index 0000000000..9d9bdc311e --- /dev/null +++ b/indexer-interfaces/src/validators/statistic-validator/score.ts @@ -0,0 +1,73 @@ +import { IScoreData, IScoreOption } from '../interfaces/index.js'; +import { FormulaEngine } from '../utils/formula.js'; +import { VariableData } from './variables.js'; + +export class ScoreData implements IScoreData { + public id: string; + public type: string; + public description: string; + public relationships: string[]; + public options: IScoreOption[]; + + public value: any; + public _relationships: VariableData[]; + public _options: { + id: string; + description: string; + value: string | number; + }[]; + + constructor(item: IScoreData) { + this.id = item.id; + this.type = item.type; + this.description = item.description; + this.relationships = item.relationships || []; + this.options = item.options || []; + } + + public setRelationships(variables: VariableData[]) { + if (Array.isArray(variables)) { + this._relationships = this.relationships + .map((id) => { + return variables.find((v) => v.id === id); + }) + .filter((v) => v !== undefined) as VariableData[]; + } else { + this._relationships = []; + } + if (Array.isArray(this.options)) { + this._options = this.options + .map((option) => { + return { + id: FormulaEngine.GenerateUUIDv4(), + description: option.description, + value: option.value + }; + }); + } else { + this._options = []; + } + } + + public setValue(value: any): void { + const option = this.options.find((o) => o.description === value); + this.value = option?.value || value; + } + + public getValue(): any { + const option = this.options.find((o) => o.value === this.value); + return option?.description || String(this.value); + } + + public validate(value: any): boolean { + const option = this.options.find((o) => o.value === value); + return this.value === value && !!option; + } + + public static from(data?: IScoreData[]): ScoreData[] { + if (Array.isArray(data)) { + return data.map((e) => new ScoreData(e)); + } + return []; + } +} diff --git a/indexer-interfaces/src/validators/statistic-validator/variables.ts b/indexer-interfaces/src/validators/statistic-validator/variables.ts new file mode 100644 index 0000000000..5a7313391a --- /dev/null +++ b/indexer-interfaces/src/validators/statistic-validator/variables.ts @@ -0,0 +1,52 @@ +import { IVariableData } from '../interfaces/index.js'; + +export class VariableData implements IVariableData { + public id: string; + public schemaId: string; + public path: string; + public schemaName: string; + public schemaPath: string; + public fieldType: string; + public fieldRef: boolean; + public fieldArray: boolean; + public fieldDescription: string; + public fieldProperty: string; + public fieldPropertyName: string; + + public value: any; + public isArray: boolean; + + constructor(item: IVariableData) { + this.id = item.id; + this.schemaId = item.schemaId; + this.path = item.path; + this.schemaName = item.schemaName; + this.schemaPath = item.schemaPath; + this.fieldType = item.fieldType; + this.fieldRef = item.fieldRef; + this.fieldArray = item.fieldArray; + this.fieldDescription = item.fieldDescription; + this.fieldProperty = item.fieldProperty; + this.fieldPropertyName = item.fieldPropertyName; + } + + public setValue(value: any): void { + this.value = value; + this.isArray = Array.isArray(value); + } + + public getValue(): any { + return this.value; + } + + public validate(value: any): boolean { + return this.value === value; + } + + public static from(data?: IVariableData[]): VariableData[] { + if (Array.isArray(data)) { + return data.map((e) => new VariableData(e)); + } + return []; + } +} \ No newline at end of file diff --git a/indexer-interfaces/src/validators/utils/formula.ts b/indexer-interfaces/src/validators/utils/formula.ts new file mode 100644 index 0000000000..07233502b3 --- /dev/null +++ b/indexer-interfaces/src/validators/utils/formula.ts @@ -0,0 +1,37 @@ +export abstract class FormulaEngine { + private static mathjs: any; + + public static setMathEngine(math: any): void { + FormulaEngine.mathjs = math + } + + /** + * Evaluate expressions + * @param formula + * @param scope + */ + public static evaluate(formula: string, scope: any): any { + if (!FormulaEngine.mathjs) { + throw new Error('Math engine is not defined'); + } + const ex = formula.trim().trim().replace(/^=/, ''); + // tslint:disable-next-line:only-arrow-functions + return (function (_mathjs: any, _formula: string, _scope: any) { + try { + return _mathjs.evaluate(_formula, _scope); + } catch (error) { + return 'Incorrect formula'; + } + }).call(null, FormulaEngine.mathjs, ex, scope); + } + + public static GenerateUUIDv4() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { + // tslint:disable-next-line:no-bitwise + const r = Math.random() * 16 | 0; + // tslint:disable-next-line:no-bitwise + const v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + } +} \ No newline at end of file diff --git a/indexer-service/configs/.env.worker b/indexer-service/configs/.env.worker index 45ccccdf8c..ef60530ece 100644 --- a/indexer-service/configs/.env.worker +++ b/indexer-service/configs/.env.worker @@ -5,4 +5,6 @@ DB_HOST="localhost" #DB_HOST="mongo" DB_DATABASE="indexer_db" HEDERA_NET="testnet" # "mainnet" | "testnet" | "previewnet" | "localnode" PREUSED_HEDERA_NET="testnet" -IPFS_GATEWAY="https://ipfs.io/ipfs/${cid}" \ No newline at end of file +IPFS_GATEWAY="https://ipfs.io/ipfs/${cid}" +SYNC_ALL_MASK= "0 * * * *" +START_SYNC_ALL= "true" \ No newline at end of file diff --git a/indexer-service/configs/.env.worker.develop b/indexer-service/configs/.env.worker.develop index edb1b50dc0..ef60530ece 100644 --- a/indexer-service/configs/.env.worker.develop +++ b/indexer-service/configs/.env.worker.develop @@ -6,3 +6,5 @@ DB_DATABASE="indexer_db" HEDERA_NET="testnet" # "mainnet" | "testnet" | "previewnet" | "localnode" PREUSED_HEDERA_NET="testnet" IPFS_GATEWAY="https://ipfs.io/ipfs/${cid}" +SYNC_ALL_MASK= "0 * * * *" +START_SYNC_ALL= "true" \ No newline at end of file diff --git a/indexer-service/configs/.env.worker.template b/indexer-service/configs/.env.worker.template index 17162285ca..914f073e8e 100644 --- a/indexer-service/configs/.env.worker.template +++ b/indexer-service/configs/.env.worker.template @@ -4,4 +4,6 @@ MQ_ADDRESS="localhost" DB_HOST="localhost" #DB_HOST="mongo" DB_DATABASE="indexer_db" HEDERA_NET="testnet" # "mainnet" | "testnet" | "previewnet" | "localnode" -IPFS_GATEWAY="https://ipfs.io/ipfs/${cid}" \ No newline at end of file +IPFS_GATEWAY="https://ipfs.io/ipfs/${cid}" +SYNC_ALL_MASK= "0 * * * *" +START_SYNC_ALL= "true" \ No newline at end of file diff --git a/indexer-service/src/api/analytics.service.ts b/indexer-service/src/api/analytics.service.ts index 8d9fe2e7a9..fc2ca20985 100644 --- a/indexer-service/src/api/analytics.service.ts +++ b/indexer-service/src/api/analytics.service.ts @@ -12,6 +12,13 @@ import escapeStringRegexp from 'escape-string-regexp'; import { MessageAction, MessageType, SearchPolicyParams, SearchPolicyResult } from '@indexer/interfaces'; import { HashComparator } from '../analytics/index.js'; +function createRegex(text: string) { + return { + $regex: `.*${escapeStringRegexp(text).trim()}.*`, + $options: 'si', + } +} + @Controller() export class AnalyticsService { @MessagePattern(IndexerMessageAPI.GET_ANALYTICS_SEARCH_POLICY) @@ -40,10 +47,7 @@ export class AnalyticsService { const keywords = text.split(' '); for (const keyword of keywords) { filter.$and.push({ - 'analytics.textSearch': { - $regex: `.*${escapeStringRegexp(keyword).trim()}.*`, - $options: 'si', - }, + 'analytics.textSearch': createRegex(keyword) }); } } diff --git a/indexer-service/src/api/entities.service.ts b/indexer-service/src/api/entities.service.ts index 45fe194d9e..672dfcf3fa 100644 --- a/indexer-service/src/api/entities.service.ts +++ b/indexer-service/src/api/entities.service.ts @@ -50,13 +50,19 @@ import { NFT, SchemaTree, Relationships as IRelationships, - IPFS_CID_PATTERN + IPFS_CID_PATTERN, + Statistic, + StatisticDetails, + Label, + LabelDetails, + LabelDocumentDetails } from '@indexer/interfaces'; import { parsePageParams } from '../utils/parse-page-params.js'; import axios from 'axios'; import { SchemaTreeNode } from '../utils/schema-tree.js'; import { IPFSService } from '../helpers/ipfs-service.js'; +//#region UTILS const pageOptions = new Set([ 'pageSize', 'pageIndex', @@ -65,14 +71,22 @@ const pageOptions = new Set([ 'keywords', ]); -function parsePageFilters(msg: PageFilters) { +function createRegex(text: string) { + return { + $regex: `.*${escapeStringRegexp(text).trim()}.*`, + $options: 'si', + } +} + +function parsePageFilters(msg: PageFilters, exactFields?: Set) { let filters: any = {}; const keys = Object.keys(msg).filter((name) => !pageOptions.has(name)); for (const key of keys) { - filters[key] = { - $regex: `.*${escapeStringRegexp(msg[key]).trim()}.*`, - $options: 'si', - }; + if (exactFields && exactFields.has(key)) { + filters[key] = msg[key]; + } else { + filters[key] = createRegex(msg[key]); + } } if (msg.keywords) { filters = Object.assign(filters, parseKeywordFilter(msg.keywords)); @@ -81,7 +95,7 @@ function parsePageFilters(msg: PageFilters) { } function parseKeywordFilter(keywordsString: string) { - let keywords; + let keywords: any; try { keywords = JSON.parse(keywordsString); } catch { @@ -92,10 +106,7 @@ function parseKeywordFilter(keywordsString: string) { }; for (const keyword of keywords) { filter.$and.push({ - 'analytics.textSearch': { - $regex: `.*${escapeStringRegexp(keyword).trim()}.*`, - $options: 'si', - }, + 'analytics.textSearch': createRegex(keyword) }); } return filter; @@ -216,6 +227,8 @@ async function loadFiles(cid: string, timeout: number): Promise { async function saveDocuments(row: Message): Promise { const em = DataBaseHelper.getEntityManager(); const collection = em.getCollection('message'); + const links = row.files?.length || 0; + const files = row.documents?.length || 0; await collection.updateOne( { _id: row._id, @@ -223,6 +236,8 @@ async function saveDocuments(row: Message): Promise { { $set: { documents: row.documents, + loaded: links === files, + lastUpdate: Date.now() }, }, { @@ -251,6 +266,7 @@ function getContext(file: string): any { return null; } } +//#endregion @Controller() export class EntityService { @@ -927,9 +943,17 @@ export class EntityService { const row = await em.findOne(TokenCache, { tokenId, }); + + const labels = (await em.find(Message, { + type: MessageType.VP_DOCUMENT, + action: MessageAction.CreateLabelDocument, + 'analytics.tokenId': tokenId + } as any)); + return new MessageResponse({ id: tokenId, row, + labels }); } catch (error) { return new MessageError(error, error.code); @@ -1010,6 +1034,203 @@ export class EntityService { } } //#endregion + //#region STATISTICS + @MessagePattern(IndexerMessageAPI.GET_STATISTICS) + async getStatistics( + @Payload() msg: PageFilters + ): Promise>> { + try { + const options = parsePageParams(msg); + const filters = parsePageFilters(msg); + filters.type = MessageType.POLICY_STATISTIC; + filters.action = MessageAction.PublishPolicyStatistic; + const em = DataBaseHelper.getEntityManager(); + const [rows, count] = (await em.findAndCount( + Message, + filters, + options + )) as [Statistic[], number]; + const result = { + items: rows, + pageIndex: options.offset / options.limit, + pageSize: options.limit, + total: count, + order: options.orderBy, + }; + return new MessageResponse>(result); + } catch (error) { + return new MessageError(error, error.code); + } + } + + @MessagePattern(IndexerMessageAPI.GET_STATISTIC) + async getStatistic( + @Payload() msg: { messageId: string } + ): Promise> { + try { + const { messageId } = msg; + const em = DataBaseHelper.getEntityManager(); + const item = (await em.findOne(Message, { + consensusTimestamp: messageId, + type: MessageType.POLICY_STATISTIC, + action: MessageAction.PublishPolicyStatistic, + } as any)) as Statistic; + const row = await em.findOne(MessageCache, { + consensusTimestamp: messageId, + }); + const schemas = await em.count(Message, { + type: MessageType.SCHEMA, + action: { + $in: [ + MessageAction.PublishSchema, + MessageAction.PublishSystemSchema, + ], + }, + topicId: row.topicId, + } as any); + const vcs = await em.count(Message, { + type: MessageType.VC_DOCUMENT, + topicId: row.topicId, + } as any); + const activity: any = { + schemas, + vcs + }; + if (!item) { + return new MessageResponse({ + id: messageId, + row, + activity, + }); + } + return new MessageResponse({ + id: messageId, + uuid: item.uuid, + item, + row, + activity, + }); + } catch (error) { + return new MessageError(error, error.code); + } + } + @MessagePattern(IndexerMessageAPI.GET_STATISTIC_DOCUMENTS) + async getStatisticDocuments( + @Payload() msg: PageFilters + ): Promise>> { + try { + const options = parsePageParams(msg); + const filters = parsePageFilters(msg); + filters.type = MessageType.VC_DOCUMENT; + filters.action = MessageAction.CreateStatisticAssessment; + const em = DataBaseHelper.getEntityManager(); + const [rows, count] = (await em.findAndCount( + Message, + filters, + options + )) as [VC[], number]; + const result = { + items: rows.map((item) => { + if (item.analytics) { + item.analytics = Object.assign(item.analytics, { + schemaName: item.analytics.schemaName, + }); + } + return item; + }), + pageIndex: options.offset / options.limit, + pageSize: options.limit, + total: count, + order: options.orderBy, + }; + return new MessageResponse>(result); + } catch (error) { + return new MessageError(error, error.code); + } + } + //#endregion + //#region LABELS + @MessagePattern(IndexerMessageAPI.GET_LABELS) + async getLabels( + @Payload() msg: PageFilters + ): Promise>> { + try { + const options = parsePageParams(msg); + const filters = parsePageFilters(msg); + filters.type = MessageType.POLICY_LABEL; + filters.action = MessageAction.PublishPolicyLabel; + const em = DataBaseHelper.getEntityManager(); + + const [rows, count] = (await em.findAndCount( + Message, + filters, + options + )) as [Label[], number]; + const result = { + items: rows, + pageIndex: options.offset / options.limit, + pageSize: options.limit, + total: count, + order: options.orderBy, + }; + return new MessageResponse>(result); + } catch (error) { + return new MessageError(error, error.code); + } + } + + @MessagePattern(IndexerMessageAPI.GET_LABEL) + async getLabel( + @Payload() msg: { messageId: string } + ): Promise> { + try { + const { messageId } = msg; + const em = DataBaseHelper.getEntityManager(); + const item = (await em.findOne(Message, { + consensusTimestamp: messageId, + type: MessageType.POLICY_LABEL, + action: MessageAction.PublishPolicyLabel, + } as any)) as Label; + const row = await em.findOne(MessageCache, { + consensusTimestamp: messageId, + }); + const schemas = await em.count(Message, { + type: MessageType.SCHEMA, + action: { + $in: [ + MessageAction.PublishSchema, + MessageAction.PublishSystemSchema, + ], + }, + topicId: row.topicId, + } as any); + const vps = await em.count(Message, { + type: MessageType.VP_DOCUMENT, + topicId: row.topicId, + } as any); + const activity: any = { + schemas, + vps + }; + if (!item) { + return new MessageResponse({ + id: messageId, + row, + activity, + }); + } + return new MessageResponse({ + id: messageId, + uuid: item.uuid, + item, + row, + activity, + }); + } catch (error) { + return new MessageError(error, error.code); + } + } + //#endregion //#endregion //#region DOCUMENTS @@ -1138,10 +1359,7 @@ export class EntityService { options )) as [VP[], number]; const result = { - items: rows.map((item) => { - delete item.analytics; - return item; - }), + items: rows, pageIndex: options.offset / options.limit, pageSize: options.limit, total: count, @@ -1152,6 +1370,7 @@ export class EntityService { return new MessageError(error, error.code); } } + @MessagePattern(IndexerMessageAPI.GET_VP_DOCUMENT) async getVpDocument( @Payload() msg: { messageId: string } @@ -1190,17 +1409,28 @@ export class EntityService { for (const historyItem of history) { await loadDocuments(historyItem, false); } + + const labels = (await em.find(Message, { + type: MessageType.VP_DOCUMENT, + action: MessageAction.CreateLabelDocument, + 'options.target': messageId + } as any)); + + console.log() + return new MessageResponse({ id: messageId, uuid: item.uuid, item, history, + labels, row, }); } catch (error) { return new MessageError(error, error.code); } } + @MessagePattern(IndexerMessageAPI.GET_VP_RELATIONSHIPS) async getVpRelationships( @Payload() msg: { messageId: string } @@ -1358,6 +1588,93 @@ export class EntityService { } } //#endregion + //#region LABELS DOCUMENTS + @MessagePattern(IndexerMessageAPI.GET_LABEL_DOCUMENTS) + async getLabelDocuments( + @Payload() msg: PageFilters + ): Promise>> { + try { + const options = parsePageParams(msg); + const filters = parsePageFilters(msg); + filters.type = MessageType.VP_DOCUMENT; + filters.action = MessageAction.CreateLabelDocument; + const em = DataBaseHelper.getEntityManager(); + const [rows, count] = (await em.findAndCount( + Message, + filters, + options + )) as [VP[], number]; + const result = { + items: rows, + pageIndex: options.offset / options.limit, + pageSize: options.limit, + total: count, + order: options.orderBy, + }; + return new MessageResponse>(result); + } catch (error) { + return new MessageError(error, error.code); + } + } + + @MessagePattern(IndexerMessageAPI.GET_LABEL_DOCUMENT) + async getLabelDocument( + @Payload() msg: { messageId: string } + ): Promise> { + try { + const { messageId } = msg; + const em = DataBaseHelper.getEntityManager(); + const item = await em.findOne(Message, { + consensusTimestamp: messageId, + type: MessageType.VP_DOCUMENT, + }); + const row = await em.findOne(MessageCache, { + consensusTimestamp: messageId, + }); + + if (!item) { + return new MessageResponse({ + id: messageId, + row, + }); + } + + await loadDocuments(item, true); + const history = await em.find( + Message, + { + uuid: item.uuid, + type: MessageType.VP_DOCUMENT, + }, + { + orderBy: { + consensusTimestamp: 'ASC', + }, + } + ); + for (const historyItem of history) { + await loadDocuments(historyItem, false); + } + + const label = (await em.findOne(Message, { + type: MessageType.POLICY_LABEL, + action: MessageAction.PublishPolicyLabel, + consensusTimestamp: item.options?.definition + } as any)) as Label; + + return new MessageResponse({ + id: messageId, + uuid: item.uuid, + item, + history, + label, + row, + }); + } catch (error) { + return new MessageError(error, error.code); + } + } + //#endregion //#endregion //#region OTHERS @@ -1368,7 +1685,7 @@ export class EntityService { ): Promise>> { try { const options = parsePageParams(msg); - const filters = parsePageFilters(msg); + const filters = parsePageFilters(msg, new Set(['tokenId'])); const em = DataBaseHelper.getEntityManager(); const [rows, count] = await em.findAndCount( NftCache, @@ -1403,9 +1720,15 @@ export class EntityService { const nftHistory: any = await axios.get( `https://${process.env.HEDERA_NET}.mirrornode.hedera.com/api/v1/tokens/${tokenId}/nfts/${serialNumber}/transactions?limit=100` ); + const labels = (await em.find(Message, { + type: MessageType.VP_DOCUMENT, + action: MessageAction.CreateLabelDocument, + 'options.target': row?.metadata + } as any)); return new MessageResponse({ id: tokenId, row, + labels, history: nftHistory.data?.transactions || [], }); } catch (error) { @@ -1612,8 +1935,7 @@ export class EntityService { } } //#endregion - //#endregion - + //#region FILES @MessagePattern(IndexerMessageAPI.UPDATE_FILES) async updateFiles( @Payload() msg: { messageId: string } @@ -1637,4 +1959,6 @@ export class EntityService { return new MessageError(error, error.code); } } + //#endregion + //#endregion } diff --git a/indexer-service/src/api/filters.service.ts b/indexer-service/src/api/filters.service.ts index 1e4cc461d3..509503bc5b 100644 --- a/indexer-service/src/api/filters.service.ts +++ b/indexer-service/src/api/filters.service.ts @@ -16,4 +16,13 @@ export class FiltersService { return new MessageError(error); } } + + @MessagePattern(IndexerMessageAPI.GET_VP_FILTERS) + async getVpFilters() { + try { + return new MessageResponse(null); + } catch (error) { + return new MessageError(error); + } + } } diff --git a/indexer-service/src/api/search.service.ts b/indexer-service/src/api/search.service.ts index 45d1d97ed6..09a6211959 100644 --- a/indexer-service/src/api/search.service.ts +++ b/indexer-service/src/api/search.service.ts @@ -6,16 +6,29 @@ import { MessageError, DataBaseHelper, Message, + TokenCache, } from '@indexer/common'; import { parsePageParams } from '../utils/parse-page-params.js'; import { Page, SearchItem } from '@indexer/interfaces'; +import escapeStringRegexp from 'escape-string-regexp'; + +function createRegex(text: string) { + return { + $regex: `.*${escapeStringRegexp(text).trim()}.*`, + $options: 'si', + } +} @Controller() export class SearchService { @MessagePattern(IndexerMessageAPI.GET_SEARCH_API) async search( @Payload() - msg: { pageIndex: number, pageSize: number, search: string } + msg: { + pageIndex: number, + pageSize: number, + search: string + } ) { try { if (!msg.pageIndex || !msg.pageSize) { @@ -25,21 +38,53 @@ export class SearchService { const { search } = msg; const em = DataBaseHelper.getEntityManager(); - const [results, count] = (await em.findAndCount( - Message, + + const [tokens, tokensCount] = (await em.findAndCount( + TokenCache, { - $text: { - $search: search, - }, + 'tokenId': search } as any, options )) as any as [SearchItem[], number]; + const [messages, messagesCount] = (await em.findAndCount( + Message, + { + $or: [ + { + 'analytics.textSearch': createRegex(search) + }, + { + 'topicId': search + }, + { + 'tokenId': search + }, + { + 'consensusTimestamp': search + }, + { + 'owner': search + }, + { + 'type': search + }, + { + 'action': search + }, + ] + } as any, + { + ...options, + offset: Math.max(options.offset - tokensCount, 0), + limit: Math.max(options.limit - tokens.length, 0), + } + )) as any as [SearchItem[], number]; const result = { - items: results, + items: [...tokens, ...messages], pageIndex: options.offset / options.limit, pageSize: options.limit, - total: count, + total: tokensCount + messagesCount, order: options.orderBy, }; diff --git a/indexer-service/src/app.ts b/indexer-service/src/app.ts index 6f692ec950..c915e8ae7a 100644 --- a/indexer-service/src/app.ts +++ b/indexer-service/src/app.ts @@ -24,6 +24,7 @@ import { SynchronizationTopics, SynchronizationVCs, SynchronizationVPs, + SynchronizationLabels, SynchronizationAll } from './helpers/synchronizers/index.js'; import { fixtures } from './helpers/fixtures.js'; @@ -150,7 +151,6 @@ Promise.all([ * Listen */ app.listen(); - /** * Sync tasks */ @@ -196,6 +196,9 @@ Promise.all([ (new SynchronizationContracts(getMask(process.env.SYNC_CONTRACTS_MASK))) .start(getBoolean(process.env.START_SYNC_CONTRACTS)); + + (new SynchronizationLabels(getMask(process.env.SYNC_LABELS_MASK))) + .start(getBoolean(process.env.START_SYNC_LABELS)); } }, (reason) => { diff --git a/indexer-service/src/helpers/load-files.ts b/indexer-service/src/helpers/load-files.ts index 73262e84e0..1afc99f097 100644 --- a/indexer-service/src/helpers/load-files.ts +++ b/indexer-service/src/helpers/load-files.ts @@ -36,7 +36,7 @@ export async function loadFiles(ids: Set, buffer: boolean): Promise, buffer: boolean): Promise { + try { + const zip = new JSZip(); + const content = await zip.loadAsync(zipFile); + if (!content.files[LABEL_FILE_NAME] || content.files[LABEL_FILE_NAME].dir) { + throw new Error('Zip file is not a labels'); + } + const labelString = await content.files[LABEL_FILE_NAME].async('string'); + const label = JSON.parse(labelString); + return { label }; + } catch (error) { + console.log('Failed to parse label') + return null; + } +} diff --git a/indexer-service/src/helpers/parsers/module.parser.ts b/indexer-service/src/helpers/parsers/module.parser.ts index c75a60ffdd..1c27f7f46b 100644 --- a/indexer-service/src/helpers/parsers/module.parser.ts +++ b/indexer-service/src/helpers/parsers/module.parser.ts @@ -5,32 +5,37 @@ export interface IModuleComponents { tags: any[]; } export const MODULE_FILE_NAME = 'module.json'; -export async function parseModuleFile(zipFile: any): Promise { - const zip = new JSZip(); - const content = await zip.loadAsync(zipFile); - if ( - !content.files[MODULE_FILE_NAME] || - content.files[MODULE_FILE_NAME].dir - ) { - throw new Error('Zip file is not a module'); - } - const moduleString = await content.files[MODULE_FILE_NAME].async('string'); - const tagsStringArray = await Promise.all( - Object.entries(content.files) - .filter((file) => !file[1].dir) - .filter((file) => /^tags\/.+/.test(file[0])) - .map((file) => file[1].async('string')) - ); - const schemasStringArray = await Promise.all( - Object.entries(content.files) - .filter((file) => !file[1].dir) - .filter((file) => /^schemas\/.+/.test(file[0])) - .map((file) => file[1].async('string')) - ); +export async function parseModuleFile(zipFile: any): Promise { + try { + const zip = new JSZip(); + const content = await zip.loadAsync(zipFile); + if ( + !content.files[MODULE_FILE_NAME] || + content.files[MODULE_FILE_NAME].dir + ) { + throw new Error('Zip file is not a module'); + } + const moduleString = await content.files[MODULE_FILE_NAME].async('string'); + const tagsStringArray = await Promise.all( + Object.entries(content.files) + .filter((file) => !file[1].dir) + .filter((file) => /^tags\/.+/.test(file[0])) + .map((file) => file[1].async('string')) + ); + const schemasStringArray = await Promise.all( + Object.entries(content.files) + .filter((file) => !file[1].dir) + .filter((file) => /^schemas\/.+/.test(file[0])) + .map((file) => file[1].async('string')) + ); - const module = JSON.parse(moduleString); - const tags = tagsStringArray.map((item) => JSON.parse(item)) || []; - const schemas = schemasStringArray.map((item) => JSON.parse(item)); + const module = JSON.parse(moduleString); + const tags = tagsStringArray.map((item) => JSON.parse(item)) || []; + const schemas = schemasStringArray.map((item) => JSON.parse(item)); - return { module, tags, schemas }; + return { module, tags, schemas }; + } catch (error) { + console.log('Failed to parse module') + return null; + } } diff --git a/indexer-service/src/helpers/parsers/policy.parser.ts b/indexer-service/src/helpers/parsers/policy.parser.ts index 8b18e8a660..d8d08398be 100644 --- a/indexer-service/src/helpers/parsers/policy.parser.ts +++ b/indexer-service/src/helpers/parsers/policy.parser.ts @@ -11,92 +11,97 @@ export const POLICY_FILE_NAME = 'policy.json'; export async function parsePolicyFile( zipFile: any, includeArtifactsData: boolean = false -): Promise { - const zip = new JSZip(); - const content = await zip.loadAsync(zipFile); - if ( - !content.files[POLICY_FILE_NAME] || - content.files[POLICY_FILE_NAME].dir - ) { - throw new Error('Zip file is not a policy'); - } - const policyString = await content.files[POLICY_FILE_NAME].async('string'); - const policy = JSON.parse(policyString); +): Promise { + try { + const zip = new JSZip(); + const content = await zip.loadAsync(zipFile); + if ( + !content.files[POLICY_FILE_NAME] || + content.files[POLICY_FILE_NAME].dir + ) { + throw new Error('Zip file is not a policy'); + } + const policyString = await content.files[POLICY_FILE_NAME].async('string'); + const policy = JSON.parse(policyString); - const fileEntries = Object.entries(content.files).filter( - (file) => !file[1].dir - ); - const [ - tokensStringArray, - schemasStringArray, - toolsStringArray, - tagsStringArray, - ] = await Promise.all([ - Promise.all( - fileEntries - .filter((file) => /^tokens\/.+/.test(file[0])) - .map((file) => file[1].async('string')) - ), - Promise.all( - fileEntries - .filter((file) => /^schem[a,e]s\/.+/.test(file[0])) - .map((file) => file[1].async('string')) - ), - Promise.all( - fileEntries - .filter((file) => /^tools\/.+/.test(file[0])) - .map((file) => file[1].async('string')) - ), - Promise.all( - fileEntries - .filter((file) => /^tags\/.+/.test(file[0])) - .map((file) => file[1].async('string')) - ), - ]); + const fileEntries = Object.entries(content.files).filter( + (file) => !file[1].dir + ); + const [ + tokensStringArray, + schemasStringArray, + toolsStringArray, + tagsStringArray, + ] = await Promise.all([ + Promise.all( + fileEntries + .filter((file) => /^tokens\/.+/.test(file[0])) + .map((file) => file[1].async('string')) + ), + Promise.all( + fileEntries + .filter((file) => /^schem[a,e]s\/.+/.test(file[0])) + .map((file) => file[1].async('string')) + ), + Promise.all( + fileEntries + .filter((file) => /^tools\/.+/.test(file[0])) + .map((file) => file[1].async('string')) + ), + Promise.all( + fileEntries + .filter((file) => /^tags\/.+/.test(file[0])) + .map((file) => file[1].async('string')) + ), + ]); - const tokens = tokensStringArray.map((item) => JSON.parse(item)); - const schemas = schemasStringArray.map((item) => JSON.parse(item)); - const tools = toolsStringArray.map((item) => JSON.parse(item)); - const tags = tagsStringArray.map((item) => JSON.parse(item)); + const tokens = tokensStringArray.map((item) => JSON.parse(item)); + const schemas = schemasStringArray.map((item) => JSON.parse(item)); + const tools = toolsStringArray.map((item) => JSON.parse(item)); + const tags = tagsStringArray.map((item) => JSON.parse(item)); - const metaDataFile = Object.entries(content.files).find( - (file) => file[0] === 'artifacts/metadata.json' - ); - const metaDataString = - (metaDataFile && (await metaDataFile[1].async('string'))) || '[]'; - const metaDataBody: any[] = JSON.parse(metaDataString); + const metaDataFile = Object.entries(content.files).find( + (file) => file[0] === 'artifacts/metadata.json' + ); + const metaDataString = + (metaDataFile && (await metaDataFile[1].async('string'))) || '[]'; + const metaDataBody: any[] = JSON.parse(metaDataString); - let artifacts: any; - if (includeArtifactsData) { - const data = fileEntries - .filter( - (file) => - /^artifacts\/.+/.test(file[0]) && - file[0] !== 'artifacts/metadata.json' - ) - .map(async (file) => { - const uuid = file[0].split('/')[1]; - const artifactMetaData = metaDataBody.find( - (item) => item.uuid === uuid - ); + let artifacts: any; + if (includeArtifactsData) { + const data = fileEntries + .filter( + (file) => + /^artifacts\/.+/.test(file[0]) && + file[0] !== 'artifacts/metadata.json' + ) + .map(async (file) => { + const uuid = file[0].split('/')[1]; + const artifactMetaData = metaDataBody.find( + (item) => item.uuid === uuid + ); + return { + name: artifactMetaData.name, + extention: artifactMetaData.extention, + uuid: artifactMetaData.uuid, + data: await file[1].async('nodebuffer'), + }; + }); + artifacts = await Promise.all(data); + } else { + artifacts = metaDataBody.map((artifactMetaData) => { return { name: artifactMetaData.name, extention: artifactMetaData.extention, uuid: artifactMetaData.uuid, - data: await file[1].async('nodebuffer'), + data: null, }; }); - artifacts = await Promise.all(data); - } else { - artifacts = metaDataBody.map((artifactMetaData) => { - return { - name: artifactMetaData.name, - extention: artifactMetaData.extention, - uuid: artifactMetaData.uuid, - data: null, - }; - }); - } + } - return { policy, tokens, schemas, artifacts, tags, tools }; + return { policy, tokens, schemas, artifacts, tags, tools }; + } catch (error) { + console.log('Failed to parse policy') + return null; + } } diff --git a/indexer-service/src/helpers/parsers/tool.parser.ts b/indexer-service/src/helpers/parsers/tool.parser.ts index a2e2ea6343..ad8dddb617 100644 --- a/indexer-service/src/helpers/parsers/tool.parser.ts +++ b/indexer-service/src/helpers/parsers/tool.parser.ts @@ -6,35 +6,40 @@ export interface IToolComponents { tools: any[]; } export const TOOL_FILE_NAME = 'tool.json'; -export async function parseToolFile(zipFile: any): Promise { - const zip = new JSZip(); - const content = await zip.loadAsync(zipFile); - if (!content.files[TOOL_FILE_NAME] || content.files[TOOL_FILE_NAME].dir) { - throw new Error('Zip file is not a tool'); - } - const toolString = await content.files[TOOL_FILE_NAME].async('string'); - const tagsStringArray = await Promise.all( - Object.entries(content.files) - .filter((file) => !file[1].dir) - .filter((file) => /^tags\/.+/.test(file[0])) - .map((file) => file[1].async('string')) - ); - const schemasStringArray = await Promise.all( - Object.entries(content.files) - .filter((file) => !file[1].dir) - .filter((file) => /^schemas\/.+/.test(file[0])) - .map((file) => file[1].async('string')) - ); - const toolsStringArray = await Promise.all( - Object.entries(content.files) - .filter((file) => !file[1].dir) - .filter((file) => /^tools\/.+/.test(file[0])) - .map((file) => file[1].async('string')) - ); +export async function parseToolFile(zipFile: any): Promise { + try { + const zip = new JSZip(); + const content = await zip.loadAsync(zipFile); + if (!content.files[TOOL_FILE_NAME] || content.files[TOOL_FILE_NAME].dir) { + throw new Error('Zip file is not a tool'); + } + const toolString = await content.files[TOOL_FILE_NAME].async('string'); + const tagsStringArray = await Promise.all( + Object.entries(content.files) + .filter((file) => !file[1].dir) + .filter((file) => /^tags\/.+/.test(file[0])) + .map((file) => file[1].async('string')) + ); + const schemasStringArray = await Promise.all( + Object.entries(content.files) + .filter((file) => !file[1].dir) + .filter((file) => /^schemas\/.+/.test(file[0])) + .map((file) => file[1].async('string')) + ); + const toolsStringArray = await Promise.all( + Object.entries(content.files) + .filter((file) => !file[1].dir) + .filter((file) => /^tools\/.+/.test(file[0])) + .map((file) => file[1].async('string')) + ); - const tool = JSON.parse(toolString); - const tags = tagsStringArray.map((item) => JSON.parse(item)) || []; - const schemas = schemasStringArray.map((item) => JSON.parse(item)); - const tools = toolsStringArray.map((item) => JSON.parse(item)); - return { tool, tags, schemas, tools }; + const tool = JSON.parse(toolString); + const tags = tagsStringArray.map((item) => JSON.parse(item)) || []; + const schemas = schemasStringArray.map((item) => JSON.parse(item)); + const tools = toolsStringArray.map((item) => JSON.parse(item)); + return { tool, tags, schemas, tools }; + } catch (error) { + console.log('Failed to parse tool') + return null; + } } diff --git a/indexer-service/src/helpers/synchronizers/index.ts b/indexer-service/src/helpers/synchronizers/index.ts index 45c50e6b5b..b0cd6a2580 100644 --- a/indexer-service/src/helpers/synchronizers/index.ts +++ b/indexer-service/src/helpers/synchronizers/index.ts @@ -12,4 +12,5 @@ export * from './synchronize-topic.js'; export * from './synchronize-vp.js'; export * from './synchronize-contracts.js'; export * from './synchronize-projects.js'; -export * from './synchronize-all.js'; \ No newline at end of file +export * from './synchronize-all.js'; +export * from './synchronize-labels.js'; \ No newline at end of file diff --git a/indexer-service/src/helpers/synchronizers/synchronize-all.ts b/indexer-service/src/helpers/synchronizers/synchronize-all.ts index 3c82bfd8b0..e3c5a34d15 100644 --- a/indexer-service/src/helpers/synchronizers/synchronize-all.ts +++ b/indexer-service/src/helpers/synchronizers/synchronize-all.ts @@ -2,6 +2,7 @@ import { SynchronizationTask } from '../synchronization-task.js'; import { SynchronizationAnalytics } from './synchronize-analytics.js'; import { SynchronizationContracts } from './synchronize-contracts.js'; import { SynchronizationDid } from './synchronize-dids.js'; +import { SynchronizationLabels } from './synchronize-labels.js'; import { SynchronizationModules } from './synchronize-module.js'; import { SynchronizationPolicy } from './synchronize-policy.js'; import { SynchronizationProjects } from './synchronize-projects.js'; @@ -29,6 +30,7 @@ export class SynchronizationAll extends SynchronizationTask { private readonly synchronizationVPs: SynchronizationVPs; private readonly synchronizationPolicy: SynchronizationPolicy; private readonly synchronizationContracts: SynchronizationContracts; + private readonly synchronizationLabels: SynchronizationLabels; constructor(mask: string) { super('all', mask); @@ -46,6 +48,7 @@ export class SynchronizationAll extends SynchronizationTask { this.synchronizationVPs = (new SynchronizationVPs(this.getMask(process.env.SYNC_VP_DOCUMENTS_MASK))); this.synchronizationPolicy = (new SynchronizationPolicy(this.getMask(process.env.SYNC_POLICIES_MASK))); this.synchronizationContracts = (new SynchronizationContracts(this.getMask(process.env.SYNC_CONTRACTS_MASK))); + this.synchronizationLabels = (new SynchronizationLabels(this.getMask(process.env.SYNC_LABELS_MASK))); } public override async sync(): Promise { @@ -62,6 +65,7 @@ export class SynchronizationAll extends SynchronizationTask { await this.runTask(this.synchronizationVPs); await this.runTask(this.synchronizationPolicy); await this.runTask(this.synchronizationContracts); + await this.runTask(this.synchronizationLabels); } private async runTask(task: SynchronizationTask) { diff --git a/indexer-service/src/helpers/synchronizers/synchronize-labels.ts b/indexer-service/src/helpers/synchronizers/synchronize-labels.ts new file mode 100644 index 0000000000..1fd370810c --- /dev/null +++ b/indexer-service/src/helpers/synchronizers/synchronize-labels.ts @@ -0,0 +1,164 @@ +import { DataBaseHelper, Message } from '@indexer/common'; +import { MessageType, MessageAction, LabelAnalytics, VPAnalytics } from '@indexer/interfaces'; +import { SynchronizationTask } from '../synchronization-task.js'; +import { Collection } from 'mongodb'; +import { textSearch } from '../text-search-options.js'; +import { parseLabelFile } from '../parsers/index.js'; +import { loadFiles } from '../load-files.js'; + +export class SynchronizationLabels extends SynchronizationTask { + public readonly name: string = 'labels'; + + constructor(mask: string) { + super('labels', mask); + } + + private async loadAllLabels(collection: Collection) { + console.log(`Sync labels: load all labels`) + const labelMap = new Map(); + const labels = collection.find({ + type: MessageType.POLICY_LABEL, + action: MessageAction.PublishPolicyLabel + }); + while (await labels.hasNext()) { + const label = await labels.next(); + labelMap.set(label.consensusTimestamp, label); + } + return labelMap; + } + + private async loadTargets(collection: Collection) { + console.log(`Sync labels: load targets`) + const targetMap = new Map(); + const targets = collection.find({ + type: MessageType.VP_DOCUMENT + }); + while (await targets.hasNext()) { + const target = await targets.next(); + targetMap.set(target.consensusTimestamp, target); + } + return targetMap; + } + + private async loadLabels( + collection: Collection, + fileIds: Set + ) { + console.log(`Sync labels: load labels`) + const labels = collection.find({ + type: MessageType.POLICY_LABEL, + action: MessageAction.PublishPolicyLabel, + ...this.filter(), + }); + const allDocuments: Message[] = []; + while (await labels.hasNext()) { + const label = await labels.next(); + allDocuments.push(label); + fileIds.add(label.files?.[0]); + } + return allDocuments; + } + + private async loadDocuments(collection: Collection) { + console.log(`Sync labels: load documents`) + const documents = collection.find({ + action: MessageAction.CreateLabelDocument, + ...this.filter(), + }); + const allDocuments: Message[] = []; + while (await documents.hasNext()) { + const document = await documents.next(); + allDocuments.push(document); + } + return allDocuments; + } + + public override async sync(): Promise { + const em = DataBaseHelper.getEntityManager(); + const collection = em.getCollection('message'); + + const fileIds: Set = new Set(); + const labelMap = await this.loadAllLabels(collection); + const targetMap = await this.loadTargets(collection); + const needUpdate1 = await this.loadLabels(collection, fileIds); + const needUpdate2 = await this.loadDocuments(collection); + + console.log(`Sync labels: load files`) + const fileMap = await loadFiles(fileIds, true); + + console.log(`Sync labels: update data`) + for (const document of needUpdate1) { + const row = em.getReference(Message, document._id); + row.analytics = await this.createAnalytics1(document, fileMap); + em.persist(row); + } + + console.log(`Sync label documents: update data`) + for (const document of needUpdate2) { + const row = em.getReference(Message, document._id); + row.analytics = this.createAnalytics2(document, labelMap, targetMap); + em.persist(row); + } + console.log(`Sync labels: flush`) + await em.flush(); + } + + private async createAnalytics1( + document: Message, + fileMap: Map, + ): Promise { + const analytics: LabelAnalytics = { + textSearch: textSearch(document), + config: null + }; + const labelFileId = document.files[0]; + const labelFileBuffer = fileMap.get(labelFileId); + if (!labelFileBuffer) { + return analytics; + } + const labelData = await parseLabelFile(labelFileBuffer); + if (!labelData) { + return analytics; + } + analytics.config = labelData.label; + return analytics; + } + + private createAnalytics2( + document: Message, + labelMap: Map, + targetMap: Map, + ): VPAnalytics { + const analytics: VPAnalytics = document.analytics; + if (!analytics) { + return; + } + const target = targetMap.get(document.options?.target); + if (target) { + analytics.tokenId = target.analytics?.tokenId; + } + + const definition = labelMap.get(document.options?.definition); + if (definition) { + analytics.labelName = definition.options?.name; + } + + return analytics; + } + + private filter() { + return { + $or: [ + { + analytics: { $exists: false }, + }, + { + analytics: null, + }, + { + analytics: undefined, + }, + ], + }; + } +} \ No newline at end of file diff --git a/indexer-service/src/helpers/synchronizers/synchronize-policy.ts b/indexer-service/src/helpers/synchronizers/synchronize-policy.ts index bd8f3673f0..1c13d99581 100644 --- a/indexer-service/src/helpers/synchronizers/synchronize-policy.ts +++ b/indexer-service/src/helpers/synchronizers/synchronize-policy.ts @@ -152,6 +152,9 @@ export class SynchronizationPolicy extends SynchronizationTask { return; } const policyData = await parsePolicyFile(policyFileBuffer, false); + if (!policyData) { + return; + } analytics.tools = policyData.tools?.map((tool: any) => tool.messageId) || []; for (const tool of analytics.tools) { analytics.textSearch += `|${tool}`; diff --git a/indexer-service/src/helpers/synchronizers/synchronize-vp.ts b/indexer-service/src/helpers/synchronizers/synchronize-vp.ts index 4ffbfbf50e..9be70e2a48 100644 --- a/indexer-service/src/helpers/synchronizers/synchronize-vp.ts +++ b/indexer-service/src/helpers/synchronizers/synchronize-vp.ts @@ -1,8 +1,9 @@ import { DataBaseHelper, Message } from '@indexer/common'; -import { MessageType, MessageAction, IPFS_CID_PATTERN } from '@indexer/interfaces'; +import { MessageType, MessageAction, IPFS_CID_PATTERN, VPAnalytics } from '@indexer/interfaces'; import { textSearch } from '../text-search-options.js'; import { SynchronizationTask } from '../synchronization-task.js'; import { loadFiles } from '../load-files.js'; +import { Collection } from 'mongodb'; export class SynchronizationVPs extends SynchronizationTask { public readonly name: string = 'vps'; @@ -11,10 +12,7 @@ export class SynchronizationVPs extends SynchronizationTask { super('vps', mask); } - public override async sync(): Promise { - const em = DataBaseHelper.getEntityManager(); - const collection = em.getCollection('message'); - + private async loadPolicies(collection: Collection) { console.log(`Sync VPs: load policies`) const policyMap = new Map(); const policies = collection.find({ type: MessageType.INSTANCE_POLICY }); @@ -24,7 +22,10 @@ export class SynchronizationVPs extends SynchronizationTask { policyMap.set(policy.options.instanceTopicId, policy); } } + return policyMap; + } + private async loadTopics(collection: Collection) { console.log(`Sync VPs: load topics`) const topicMap = new Map(); const topics = collection.find({ @@ -37,20 +38,10 @@ export class SynchronizationVPs extends SynchronizationTask { topicMap.set(topic.topicId, topic); } } + return topicMap; + } - console.log(`Sync VPs: load documents`) - const documents = collection.find({ - type: { $in: [MessageType.VP_DOCUMENT] }, - ...this.filter(), - }); - const allDocuments: Message[] = []; - const fileIds: Set = new Set(); - while (await documents.hasNext()) { - const document = await documents.next(); - allDocuments.push(document); - fileIds.add(document.files?.[0]); - } - + private async loadSchemas(collection: Collection, fileIds: Set) { console.log(`Sync VPs: load schemas`) const schemaMap = new Map(); const schemas = collection.find({ type: MessageType.SCHEMA }); @@ -68,14 +59,68 @@ export class SynchronizationVPs extends SynchronizationTask { } } } + return schemaMap; + } + private async loadLabelDocuments(collection: Collection) { + console.log(`Sync VPs: load label documents`) + const labels = collection.find({ + action: MessageAction.CreateLabelDocument + }); + const labelDocumentMap = new Map(); + while (await labels.hasNext()) { + const label = await labels.next(); + const refs = labelDocumentMap.get(label.options?.target) || []; + refs.push(label.consensusTimestamp); + labelDocumentMap.set(label.options?.target, refs); + } + return labelDocumentMap; + } + + private async loadDocuments(collection: Collection, fileIds: Set) { + console.log(`Sync VPs: load documents`) + const documents = collection.find({ + type: { $in: [MessageType.VP_DOCUMENT] }, + ...this.filter(), + }); + const allDocuments: Message[] = []; + while (await documents.hasNext()) { + const document = await documents.next(); + allDocuments.push(document); + fileIds.add(document.files?.[0]); + } + return allDocuments; + } + + private async loadFiles(fileIds: Set) { console.log(`Sync VPs: load files`) const fileMap = await loadFiles(fileIds, false); + return fileMap; + } + + public override async sync(): Promise { + const em = DataBaseHelper.getEntityManager(); + const collection = em.getCollection('message'); + + const fileIds: Set = new Set(); + const policyMap = await this.loadPolicies(collection); + const topicMap = await this.loadTopics(collection); + const schemaMap = await this.loadSchemas(collection, fileIds); + const labelDocumentMap = await this.loadLabelDocuments(collection); + const needUpdate = await this.loadDocuments(collection, fileIds); + const fileMap = await this.loadFiles(fileIds); console.log(`Sync VPs: update data`) - for (const document of allDocuments) { + for (const document of needUpdate) { const row = em.getReference(Message, document._id); - row.analytics = this.createAnalytics(document, policyMap, topicMap, schemaMap, fileMap); + row.analytics = this.createAnalytics( + document, + policyMap, + topicMap, + schemaMap, + fileMap, + labelDocumentMap + ); em.persist(row); } console.log(`Sync VPs: flush`) @@ -87,9 +132,10 @@ export class SynchronizationVPs extends SynchronizationTask { policyMap: Map, topicMap: Map, schemaMap: Map, - fileMap: Map - ): any { - const documentAnalytics: any = { + fileMap: Map, + labelDocumentMap: Map + ): VPAnalytics { + const documentAnalytics: VPAnalytics = { textSearch: textSearch(document), }; let policyMessage = policyMap.get(document.topicId); @@ -108,43 +154,55 @@ export class SynchronizationVPs extends SynchronizationTask { const documentFileString = fileMap.get(documentFileId); const documentFile = this.parseFile(documentFileString); const vcs = this.getVcs(documentFile); - if (!vcs) { - return documentAnalytics; - } - for (const vc of vcs) { - const subject = this.getSubject(vc); - if (!subject) { - return documentAnalytics; - } - const documentFields = new Set(); - this.parseDocumentFields(subject, documentFields); - if (documentFields.size > 0) { - documentAnalytics.textSearch += `|${[...documentFields].join('|')}`; - } - const schemaContextCID = this.getContext(vc); - if (schemaContextCID) { - const schemaMessage = schemaMap.get(schemaContextCID); - if (schemaMessage) { - if (!documentAnalytics.schemaIds) { - documentAnalytics.schemaIds = []; + if (vcs) { + for (const vc of vcs) { + const subject = this.getSubject(vc); + if (subject) { + if (subject.type === 'MintToken') { + documentAnalytics.tokenId = subject.tokenId; + documentAnalytics.tokenAmount = subject.amount; } - documentAnalytics.schemaIds.push(schemaMessage.consensusTimestamp); - const schemaDocumentFileString = fileMap.get(schemaMessage.files?.[0]); - const schemaDocumentFile = this.parseFile(schemaDocumentFileString); - if (schemaDocumentFile?.title) { - documentAnalytics.textSearch += `|${schemaDocumentFile.title}`; - if (!documentAnalytics.schemaNames) { - documentAnalytics.schemaNames = []; + const documentFields = new Set(); + this.parseDocumentFields(subject, documentFields); + if (documentFields.size > 0) { + documentAnalytics.textSearch += `|${[...documentFields].join('|')}`; + } + const schemaContextCID = this.getContext(vc); + if (schemaContextCID) { + const schemaMessage = schemaMap.get(schemaContextCID); + if (schemaMessage) { + if (!documentAnalytics.schemaIds) { + documentAnalytics.schemaIds = []; + } + documentAnalytics.schemaIds.push(schemaMessage.consensusTimestamp); + const schemaDocumentFileString = fileMap.get(schemaMessage.files?.[0]); + const schemaDocumentFile = this.parseFile(schemaDocumentFileString); + if (schemaDocumentFile?.title) { + documentAnalytics.textSearch += `|${schemaDocumentFile.title}`; + if (!documentAnalytics.schemaNames) { + documentAnalytics.schemaNames = []; + } + documentAnalytics.schemaNames.push(schemaDocumentFile.title); + } } - documentAnalytics.schemaNames.push(schemaDocumentFile.title); } } } } + documentAnalytics.issuer = this.getIssuer(documentFile); } + documentAnalytics.labels = labelDocumentMap.get(document.consensusTimestamp); + return documentAnalytics; } + private getIssuer(documentFile: any) { + if (documentFile && documentFile.proof && typeof documentFile.proof.verificationMethod === 'string') { + return documentFile.proof.verificationMethod.split('#')[0]; + } + return null; + } + private parseFile(file: string | undefined): any | null { try { if (file) { diff --git a/indexer-service/src/migrations/v3-0-0.1.ts b/indexer-service/src/migrations/v3-0-0.1.ts new file mode 100644 index 0000000000..ceb28e0eb8 --- /dev/null +++ b/indexer-service/src/migrations/v3-0-0.1.ts @@ -0,0 +1,37 @@ +import { MessageCache } from '@indexer/common'; +import { MessageStatus } from '@indexer/interfaces'; +import { Migration } from '@mikro-orm/migrations-mongodb'; + +/** + * Migration to version 3.0.0 + */ +export class ReleaseMigration extends Migration { + /** + * Up migration + */ + async up(): Promise { + await this.updateStatus(); + } + + /** + * Update status + */ + async updateStatus() { + const cacheCollection = this.getCollection('MessageCache'); + const cacheRequests = cacheCollection.find({ + status: MessageStatus.UNSUPPORTED + }, { session: this.ctx }); + while (await cacheRequests.hasNext()) { + const cacheRequest = await cacheRequests.next(); + await cacheCollection.updateOne( + { _id: cacheRequest._id }, + { + $set: { + status: MessageStatus.COMPRESSED, + }, + }, + { session: this.ctx, upsert: false } + ); + } + } +} diff --git a/indexer-service/src/migrations/v3-0-0.2.ts b/indexer-service/src/migrations/v3-0-0.2.ts new file mode 100644 index 0000000000..5290a049fe --- /dev/null +++ b/indexer-service/src/migrations/v3-0-0.2.ts @@ -0,0 +1,73 @@ +import { Message, MessageCache } from '@indexer/common'; +import { MessageAction, MessageStatus, MessageType } from '@indexer/interfaces'; +import { Migration } from '@mikro-orm/migrations-mongodb'; + +/** + * Migration to version 3.0.0 + */ +export class ReleaseMigration extends Migration { + /** + * Up migration + */ + async up(): Promise { + await this.updateVP(); + } + + /** + * Update status + */ + async updateVP() { + const messageCollection = this.getCollection('Message'); + const cacheCollection = this.getCollection('MessageCache'); + + const vps = messageCollection.find({ + type: MessageType.VP_DOCUMENT + }, { session: this.ctx }); + + while (await vps.hasNext()) { + const vp = await vps.next(); + if (vp.action === MessageAction.CreateLabelDocument) { + //Label VP + await messageCollection.deleteOne({ + _id: vp._id + }, { session: this.ctx }) + await cacheCollection.updateOne( + { consensusTimestamp: vp.consensusTimestamp }, + { + $set: { + status: MessageStatus.COMPRESSED, + }, + }, + { session: this.ctx, upsert: false } + ); + } else { + //Other VP + await messageCollection.updateOne( + { _id: vp._id }, + { + $set: { + analytics: null, + }, + }, + { session: this.ctx, upsert: false } + ); + } + } + + const labels = messageCollection.find({ + type: MessageType.POLICY_LABEL + }, { session: this.ctx }); + while (await labels.hasNext()) { + const label = await labels.next(); + await messageCollection.updateOne( + { _id: label._id }, + { + $set: { + analytics: null, + }, + }, + { session: this.ctx, upsert: false } + ); + } + } +} diff --git a/indexer-service/src/migrations/v3-0-0.3.ts b/indexer-service/src/migrations/v3-0-0.3.ts new file mode 100644 index 0000000000..eca663d4c4 --- /dev/null +++ b/indexer-service/src/migrations/v3-0-0.3.ts @@ -0,0 +1,48 @@ +import { Message, MessageCache } from '@indexer/common'; +import { Migration } from '@mikro-orm/migrations-mongodb'; + +/** + * Migration to version 3.0.0 + */ +export class ReleaseMigration extends Migration { + /** + * Up migration + */ + async up(): Promise { + await this.updateMessageIndexes(); + } + + /** + * Update indexes + */ + async updateMessageIndexes() { + const messageCollection = this.getCollection('Message'); + const cacheCollection = this.getCollection('MessageCache'); + + const indexMap = new Map(); + + const cacheRequests = cacheCollection.find({}, { session: this.ctx }); + while (await cacheRequests.hasNext()) { + const cacheRequest = await cacheRequests.next(); + indexMap.set(cacheRequest.consensusTimestamp, cacheRequest.sequenceNumber) + } + + const messageRequests = messageCollection.find({}, { session: this.ctx }); + while (await messageRequests.hasNext()) { + const messageRequest = await messageRequests.next(); + const links = messageRequest.files?.length || 0; + const files = messageRequest.documents?.length || 0; + await messageCollection.updateOne( + { _id: messageRequest._id }, + { + $set: { + sequenceNumber: indexMap.get(messageRequest.consensusTimestamp), + loaded: links === files, + lastUpdate: 0, + }, + }, + { session: this.ctx, upsert: false } + ); + } + } +} diff --git a/indexer-worker-service/configs/.env.worker b/indexer-worker-service/configs/.env.worker index 7883f908b7..e7045bbf47 100644 --- a/indexer-worker-service/configs/.env.worker +++ b/indexer-worker-service/configs/.env.worker @@ -27,4 +27,9 @@ MESSAGE_READ_DELAY="1000" MESSAGE_READ_TIMEOUT="60000" MESSAGE_JOB_REFRESH_TIME="60000" MESSAGE_JOB_COUNT="5" +FILE_READ_DELAY="5000" +FILE_READ_TIMEOUT="60000" +FILE_JOB_REFRESH_TIME="60000" +FILE_JOB_COUNT="1" IPFS_GATEWAY="http://127.0.0.1:8080/ipfs/${cid}" +IPFS_CHECK_GATEWAY="http://127.0.0.1:3333/check?cid=${cid}&timeoutSeconds=5" \ No newline at end of file diff --git a/indexer-worker-service/configs/.env.worker.develop b/indexer-worker-service/configs/.env.worker.develop index ed0126179f..e7045bbf47 100644 --- a/indexer-worker-service/configs/.env.worker.develop +++ b/indexer-worker-service/configs/.env.worker.develop @@ -27,4 +27,9 @@ MESSAGE_READ_DELAY="1000" MESSAGE_READ_TIMEOUT="60000" MESSAGE_JOB_REFRESH_TIME="60000" MESSAGE_JOB_COUNT="5" -IPFS_GATEWAY="http://127.0.0.1:8080/ipfs/${cid}" \ No newline at end of file +FILE_READ_DELAY="5000" +FILE_READ_TIMEOUT="60000" +FILE_JOB_REFRESH_TIME="60000" +FILE_JOB_COUNT="1" +IPFS_GATEWAY="http://127.0.0.1:8080/ipfs/${cid}" +IPFS_CHECK_GATEWAY="http://127.0.0.1:3333/check?cid=${cid}&timeoutSeconds=5" \ No newline at end of file diff --git a/indexer-worker-service/configs/.env.worker.template b/indexer-worker-service/configs/.env.worker.template index 4616c23784..d415b81b10 100644 --- a/indexer-worker-service/configs/.env.worker.template +++ b/indexer-worker-service/configs/.env.worker.template @@ -23,6 +23,10 @@ TOKEN_READ_DELAY="1000" TOKEN_READ_TIMEOUT="60000" TOKEN_JOB_REFRESH_TIME="60000" TOKEN_JOB_COUNT="2" +FILE_READ_DELAY="5000" +FILE_READ_TIMEOUT="60000" +FILE_JOB_REFRESH_TIME="60000" +FILE_JOB_COUNT="1" MESSAGE_READ_DELAY="1000" MESSAGE_READ_TIMEOUT="60000" MESSAGE_JOB_REFRESH_TIME="60000" diff --git a/indexer-worker-service/src/api/channel.service.ts b/indexer-worker-service/src/api/channel.service.ts index 04722a1f2f..b44d56215f 100644 --- a/indexer-worker-service/src/api/channel.service.ts +++ b/indexer-worker-service/src/api/channel.service.ts @@ -4,6 +4,7 @@ import { IndexerMessageAPI, Singleton, Jobs, Utils } from '@indexer/common'; import { TopicService } from '../services/topic-service.js'; import { MessageService } from '../services/message-service.js'; import { TokenService } from '../services/token-service.js'; +import { FileService } from '../services/file-service.js'; interface IOptions { NAME: string; @@ -20,6 +21,11 @@ interface IOptions { TOKEN_READ_TIMEOUT: number; TOKEN_JOB_REFRESH_TIME: number; TOKEN_JOB_COUNT: number; + FILE_CYCLE_TIME: number; + FILE_READ_DELAY: number; + FILE_READ_TIMEOUT: number; + FILE_JOB_REFRESH_TIME: number; + FILE_JOB_COUNT: number; } @Controller() @@ -60,6 +66,7 @@ export class Worker { public topics: Jobs; public messages: Jobs; public tokens: Jobs; + public files: Jobs; /** * Initialize worker @@ -71,6 +78,7 @@ export class Worker { TopicService.CYCLE_TIME = option.CYCLE_TIME; MessageService.CYCLE_TIME = option.CYCLE_TIME; TokenService.CYCLE_TIME = option.CYCLE_TIME; + FileService.CYCLE_TIME = option.FILE_CYCLE_TIME || option.CYCLE_TIME; this.topics = new Jobs({ delay: option.TOPIC_READ_DELAY, timeout: option.TOPIC_READ_TIMEOUT, @@ -92,6 +100,13 @@ export class Worker { count: option.TOKEN_JOB_COUNT, callback: TokenService.updateToken }); + this.files = new Jobs({ + delay: option.FILE_READ_DELAY, + timeout: option.FILE_READ_TIMEOUT, + refresh: option.FILE_JOB_REFRESH_TIME, + count: option.FILE_JOB_COUNT, + callback: FileService.updateFile + }); return this; } @@ -103,6 +118,7 @@ export class Worker { await this.topics.start(); await this.messages.start(); await this.tokens.start(); + await this.files.start(); this.status = 'STARTED'; return this; } @@ -114,6 +130,7 @@ export class Worker { await this.topics.stop(); await this.messages.stop(); await this.tokens.stop(); + await this.files.stop(); this.status = 'STOPPED'; return this; } @@ -128,7 +145,8 @@ export class Worker { status: this.status, topics: this.topics?.getStatuses(), messages: this.messages?.getStatuses(), - tokens: this.tokens?.getStatuses() + tokens: this.tokens?.getStatuses(), + files: this.files?.getStatuses() }; } } diff --git a/indexer-worker-service/src/app.ts b/indexer-worker-service/src/app.ts index c65cf16199..99d1ec5452 100644 --- a/indexer-worker-service/src/app.ts +++ b/indexer-worker-service/src/app.ts @@ -93,18 +93,27 @@ Promise.all([ await worker.init({ NAME: channelName, CYCLE_TIME: Utils.getIntParm(process.env.CYCLE_TIME, 60 * 60 * 1000), + //MESSAGE MESSAGE_READ_DELAY: Utils.getIntParm(process.env.MESSAGE_READ_DELAY, 1000), - MESSAGE_READ_TIMEOUT: Utils.getIntParm(process.env.MESSAGE_READ_TIMEOUT, 60000), - MESSAGE_JOB_REFRESH_TIME: Utils.getIntParm(process.env.MESSAGE_JOB_REFRESH_TIME, 60000), + MESSAGE_READ_TIMEOUT: Utils.getIntParm(process.env.MESSAGE_READ_TIMEOUT, 60 * 1000), + MESSAGE_JOB_REFRESH_TIME: Utils.getIntParm(process.env.MESSAGE_JOB_REFRESH_TIME, 60 * 1000), MESSAGE_JOB_COUNT: Utils.getIntParm(process.env.MESSAGE_JOB_COUNT, 10), + //TOPIC TOPIC_READ_DELAY: Utils.getIntParm(process.env.TOPIC_READ_DELAY, 1000), - TOPIC_READ_TIMEOUT: Utils.getIntParm(process.env.TOPIC_READ_TIMEOUT, 60000), - TOPIC_JOB_REFRESH_TIME: Utils.getIntParm(process.env.TOPIC_JOB_REFRESH_TIME, 60000), + TOPIC_READ_TIMEOUT: Utils.getIntParm(process.env.TOPIC_READ_TIMEOUT, 60 * 1000), + TOPIC_JOB_REFRESH_TIME: Utils.getIntParm(process.env.TOPIC_JOB_REFRESH_TIME, 60 * 1000), TOPIC_JOB_COUNT: Utils.getIntParm(process.env.TOPIC_JOB_COUNT, 5), - TOKEN_READ_DELAY: Utils.getIntParm(process.env.TOKEN__READ_DELAY, 1000), - TOKEN_READ_TIMEOUT: Utils.getIntParm(process.env.TOKEN__READ_TIMEOUT, 60000), - TOKEN_JOB_REFRESH_TIME: Utils.getIntParm(process.env.TOKEN__JOB_REFRESH_TIME, 60000), - TOKEN_JOB_COUNT: Utils.getIntParm(process.env.TOKEN__JOB_COUNT, 2), + //TOKEN + TOKEN_READ_DELAY: Utils.getIntParm(process.env.TOKEN_READ_DELAY, 1000), + TOKEN_READ_TIMEOUT: Utils.getIntParm(process.env.TOKEN_READ_TIMEOUT, 60 * 1000), + TOKEN_JOB_REFRESH_TIME: Utils.getIntParm(process.env.TOKEN_JOB_REFRESH_TIME, 60 * 1000), + TOKEN_JOB_COUNT: Utils.getIntParm(process.env.TOKEN_JOB_COUNT, 2), + //FILE + FILE_CYCLE_TIME: Utils.getIntParm(process.env.FILE_CYCLE_TIME, 24 * 60 * 60 * 1000), + FILE_READ_DELAY: Utils.getIntParm(process.env.FILE_READ_DELAY, 5 * 1000), + FILE_READ_TIMEOUT: Utils.getIntParm(process.env.FILE_READ_TIMEOUT, 60 * 1000), + FILE_JOB_REFRESH_TIME: Utils.getIntParm(process.env.FILE_JOB_REFRESH_TIME, 60 * 1000), + FILE_JOB_COUNT: Utils.getIntParm(process.env.FILE_JOB_COUNT, 1), }).start(); // await state.updateState(ApplicationStates.READY); diff --git a/indexer-worker-service/src/loaders/ipfs-service.ts b/indexer-worker-service/src/loaders/ipfs-service.ts index b563581b73..1fce42dbd8 100644 --- a/indexer-worker-service/src/loaders/ipfs-service.ts +++ b/indexer-worker-service/src/loaders/ipfs-service.ts @@ -1,12 +1,13 @@ -import { CID } from 'multiformats/cid' import { BaseNode } from './ipfs/base-node.js'; - +import { HttpNode } from './ipfs/http-node.js'; // import { IPFSNode } from './ipfs/ipfs-node.js' // import { HeliaNode } from './ipfs/helia-node.js' // import { KudoNode } from './ipfs/kudo-node.js'; -import { HttpNode } from './ipfs/http-node.js'; +import CID from 'cids'; export class IPFSService { + private static readonly LOAD_TIMEOUT: number = 5 * 60 * 1000; + public static node: BaseNode; public static async init() { @@ -17,7 +18,7 @@ export class IPFSService { public static parseCID(file: string): CID | null { try { if (file && typeof file === 'string') { - return CID.parse(file); + return new CID(file); } else { return null; } @@ -26,18 +27,35 @@ export class IPFSService { } } - public static async getFile(cid: string): Promise { + private static async loadFile(cid: string): Promise { + const check = await this.node.check(cid); + if (check.check === true) { + const file = await this.node.get(cid); + console.log(`IPFS loaded: ${cid}`); + return file; + } else if (check.check === undefined) { + console.log(`IPFS check: ${cid}`, check.error); + const file = await this.node.get(cid); + console.log(`IPFS loaded: ${cid}`); + return file; + } else { + console.log(`IPFS error: ${cid}`, check.error); + return undefined; + } + } + + public static async getFile(cid: string): Promise { try { - const timeoutPromise = new Promise((resolve, reject) => { + console.log(`IPFS loading: ${cid}`); + const timeoutPromise = new Promise((resolve, reject) => { setTimeout(() => { - reject(new Error('IPFS timeout exceeded')); - }, 60 * 1000); + reject(new Error('Timeout exceeded')); + }, IPFSService.LOAD_TIMEOUT); }); - return Promise.race([this.node.get(cid), timeoutPromise]); - // return await this.node.get(cid);; + return await Promise.race([this.loadFile(cid), timeoutPromise]); } catch (error) { - console.log('IPFS ', cid, error.message); - throw error; + console.log(`IPFS error: ${cid}`, error.message); + return undefined; } } } diff --git a/indexer-worker-service/src/loaders/ipfs/base-node.ts b/indexer-worker-service/src/loaders/ipfs/base-node.ts index cee75ebbef..6cc4c3feed 100644 --- a/indexer-worker-service/src/loaders/ipfs/base-node.ts +++ b/indexer-worker-service/src/loaders/ipfs/base-node.ts @@ -1,5 +1,11 @@ export interface BaseNode { start(): Promise; stop(): Promise; - get(cid: string): Promise; + get(cid: string, timeout?: number): Promise; + check(cid: string, timeout?: number): Promise; } + +export type CheckFileResponse = { + check?: boolean, + error?: string +} \ No newline at end of file diff --git a/indexer-worker-service/src/loaders/ipfs/http-node.ts b/indexer-worker-service/src/loaders/ipfs/http-node.ts index 898e2644a0..19eebded30 100644 --- a/indexer-worker-service/src/loaders/ipfs/http-node.ts +++ b/indexer-worker-service/src/loaders/ipfs/http-node.ts @@ -1,26 +1,73 @@ import axios from 'axios'; +import CID from 'cids'; +import { BaseNode, CheckFileResponse } from './base-node'; -export class HttpNode { - constructor() {} +export class HttpNode implements BaseNode { + private readonly LOAD_TIMEOUT: number = 60 * 1000; + private readonly CHECK_TIMEOUT: number = 15 * 1000; - public async start() {} + constructor() { - public async stop() {} + } + + private parseCID(cid: string): string { + return new CID(cid).toV1().toString('base32'); + } + + public async start() { + if (!process.env.IPFS_GATEWAY) { + console.error('IPFS_GATEWAY not configured'); + } + if (!process.env.IPFS_CHECK_GATEWAY) { + console.error('IPFS_CHECK_GATEWAY not configured'); + } + } + + public async stop() { + + } - public async get(cid: string): Promise { + public async get(cid: string, timeout?: number): Promise { try { const items = await axios.get( - process.env.IPFS_GATEWAY?.replace('${cid}', cid), + process.env.IPFS_GATEWAY?.replace('${cid}', this.parseCID(cid)), { responseType: 'arraybuffer', - timeout: 60 * 1000, + timeout: timeout || this.LOAD_TIMEOUT, } ); - console.log('loaded: ' + cid); return items.data; } catch (error) { - console.log(error); throw error; } } + + public async check(cid: string, timeout?: number): Promise { + try { + if (!process.env.IPFS_CHECK_GATEWAY) { + return { + check: undefined, + error: 'IPFS_CHECK_GATEWAY not configured' + }; + } + const result = await axios.get( + process.env.IPFS_CHECK_GATEWAY.replace('${cid}', this.parseCID(cid)), + { + responseType: 'arraybuffer', + timeout: timeout || this.CHECK_TIMEOUT, + } + ); + const peers = JSON.parse(result.data.toString())?.length; + if (peers > 0) { + return { check: true }; + } else { + return { check: false, error: 'file does not exist' }; + } + } catch (error) { + return { + check: undefined, + error: error.message + }; + } + } } diff --git a/indexer-worker-service/src/services/file-service.ts b/indexer-worker-service/src/services/file-service.ts new file mode 100644 index 0000000000..94818c522c --- /dev/null +++ b/indexer-worker-service/src/services/file-service.ts @@ -0,0 +1,65 @@ +import { MongoDriver, MongoEntityManager, ObjectId } from '@mikro-orm/mongodb'; +import { DataBaseHelper, Job, Message } from '@indexer/common'; +import { LogService } from './log-service.js'; +import { MessageService } from './message-service.js'; + +export class FileService { + public static CYCLE_TIME: number = 0; + public static InProgress: ObjectId[] = []; + + public static async updateFile(job: Job) { + try { + const em = DataBaseHelper.getEntityManager(); + const row = await FileService.randomMessage(em); + if (!row) { + job.sleep(); + return; + } + const documents = await MessageService.loadDocuments(row); + if (documents) { + row.documents = documents; + row.loaded = MessageService.checkFiles(row); + await em.nativeUpdate(Message, { + _id: row._id, + }, { + documents + }); + } + } catch (error) { + await LogService.error(error, 'update message'); + } + } + + private static async randomMessage(em: MongoEntityManager): Promise { + const delay = Date.now() - FileService.CYCLE_TIME; + const rows = await em.find(Message, + { + lastUpdate: { $lt: delay }, + loaded: false + }, + { + orderBy: { lastUpdate: 'ASC' }, + limit: 50, + } + ) + const index = Math.min(Math.floor(Math.random() * rows.length), rows.length - 1); + const row = rows[index]; + + if (!row) { + return null; + } + + const count = await em.nativeUpdate(Message, { + _id: row._id, + lastUpdate: { $lt: delay } + }, { + lastUpdate: Date.now() + }); + + if (count) { + return row; + } else { + return null; + } + } +} \ No newline at end of file diff --git a/indexer-worker-service/src/services/message-service.ts b/indexer-worker-service/src/services/message-service.ts index 9ae47bd6ca..f523b5a868 100644 --- a/indexer-worker-service/src/services/message-service.ts +++ b/indexer-worker-service/src/services/message-service.ts @@ -4,6 +4,7 @@ import { Parser } from '../utils/parser.js'; import { IPFSService } from '../loaders/ipfs-service.js'; import { LogService } from './log-service.js'; import { DataBaseHelper, Job, MessageCache, Message } from '@indexer/common'; +import { MessageStatus } from '@indexer/interfaces'; export interface IFile { id?: ObjectId; @@ -27,14 +28,16 @@ export class MessageService { if (json) { const documents = await MessageService.loadDocuments(json); json.documents = documents; + json.loaded = MessageService.checkFiles(json); + json.lastUpdate = Date.now(); const messageRow = await MessageService.insertMessage(json, em); if (messageRow) { - row.status = 'LOADED'; + row.status = MessageStatus.LOADED; } else { - row.status = 'ERROR'; + row.status = MessageStatus.ERROR; } } else { - row.status = 'UNSUPPORTED'; + row.status = MessageStatus.UNSUPPORTED; } await em.flush(); } catch (error) { @@ -46,10 +49,10 @@ export class MessageService { const delay = Date.now() - MessageService.CYCLE_TIME; const rows = await em.find(MessageCache, { - type: "Message", + type: 'Message', $or: [ - { status: 'LOADING', lastUpdate: { $lt: delay } }, - { status: 'COMPRESSED' } + { status: MessageStatus.LOADING, lastUpdate: { $lt: delay } }, + { status: MessageStatus.COMPRESSED } ] }, { @@ -67,12 +70,12 @@ export class MessageService { const count = await em.nativeUpdate(MessageCache, { _id: row._id, $or: [ - { status: 'LOADING', lastUpdate: { $lt: delay } }, - { status: 'COMPRESSED' } + { status: MessageStatus.LOADING, lastUpdate: { $lt: delay } }, + { status: MessageStatus.COMPRESSED } ] }, { lastUpdate: Date.now(), - status: 'LOADING' + status: MessageStatus.LOADING }); if (count) { @@ -106,12 +109,14 @@ export class MessageService { if (!json.files || !json.files.length) { const row = em.create(Message, json); row.documents = []; + row.loaded = MessageService.checkFiles(row); + row.lastUpdate = Date.now(); em.persist(row); - ref.status = 'LOADED'; + ref.status = MessageStatus.LOADED; await em.flush(); } } else { - ref.status = 'UNSUPPORTED'; + ref.status = MessageStatus.UNSUPPORTED; await em.flush(); } } catch (error) { @@ -147,8 +152,8 @@ export class MessageService { if (Array.isArray(message.files)) { for (const file of message.files) { const cid = IPFSService.parseCID(file); - if (cid && cid.version === 1) { - cids.push(cid.toString()); + if (cid) { + cids.push(file); } else { return null; } @@ -182,4 +187,10 @@ export class MessageService { } }); } + + public static checkFiles(message: Message): boolean { + const links = message.files?.length || 0; + const files = message.documents?.length || 0; + return links === files; + } } diff --git a/indexer-worker-service/src/services/topic-service.ts b/indexer-worker-service/src/services/topic-service.ts index 940cf973f9..b294a6e82c 100644 --- a/indexer-worker-service/src/services/topic-service.ts +++ b/indexer-worker-service/src/services/topic-service.ts @@ -6,6 +6,7 @@ import { Parser } from '../utils/parser.js'; import { HederaService } from '../loaders/hedera-service.js'; import { DataBaseHelper, Job, MessageCache, TopicCache, TopicMessage, Utils } from '@indexer/common'; import { TokenService } from './token-service.js'; +import { MessageStatus } from '@indexer/interfaces'; export class TopicService { public static CYCLE_TIME: number = 0; @@ -47,7 +48,7 @@ export class TopicService { if (!old) { await em.persistAndFlush(em.create(TopicCache, { topicId, - status: '', + status: MessageStatus.NONE, lastUpdate: 0, messages: 0, hasNext: false @@ -200,9 +201,9 @@ export class TopicService { item.data = null; if (item.chunkId && item.chunkTotal > 1) { - item.status = 'COMPRESSING'; + item.status = MessageStatus.COMPRESSING; } else { - item.status = 'COMPRESSED'; + item.status = MessageStatus.COMPRESSED; item.data = TopicService.compressData(item.message); } return item; @@ -214,9 +215,9 @@ export class TopicService { const compressing = new Set(); const compressed = []; for (const message of messages) { - if (message.status === 'COMPRESSING') { + if (message.status === MessageStatus.COMPRESSING) { compressing.add(message.chunkId); - } else if (message.status === 'COMPRESSED') { + } else if (message.status === MessageStatus.COMPRESSED) { compressed.push(message); } } @@ -244,7 +245,7 @@ export class TopicService { const buffers: string[] = new Array(chunks.length); for (const row of chunks) { - row.status = 'COMPRESSED'; + row.status = MessageStatus.COMPRESSED; buffers[row.chunkNumber - 1] = row.message; } diff --git a/indexer-worker-service/src/utils/parser.ts b/indexer-worker-service/src/utils/parser.ts index 7790d14411..4ae291a91e 100644 --- a/indexer-worker-service/src/utils/parser.ts +++ b/indexer-worker-service/src/utils/parser.ts @@ -1,5 +1,5 @@ import { MessageCache, Message } from '@indexer/common'; -import { MessageType } from '@indexer/interfaces'; +import { MessageAction, MessageType } from '@indexer/interfaces'; export class Parser { public static parseMassage(row: MessageCache): Message | null { @@ -17,6 +17,7 @@ export class Parser { message.topicId = row.topicId; message.consensusTimestamp = row.consensusTimestamp; message.owner = row.owner; + message.sequenceNumber = row.sequenceNumber; message.uuid = json.id; message.status = json.status || 'ISSUE'; @@ -103,6 +104,16 @@ export class Parser { if (json.cid) { message.files.push(json.cid); } + //Label + if (json.action === MessageAction.CreateLabelDocument) { + message.options.target = json.target; + message.options.definition = json.definition; + } + //Statistic + if (json.action === MessageAction.CreateStatisticAssessment) { + message.options.target = json.target; + message.options.definition = json.definition; + } break; case MessageType.STANDARD_REGISTRY: message.options.did = json.did; @@ -167,14 +178,6 @@ export class Parser { message.files.push(json.cid); } break; - case MessageType.ROLE_DOCUMENT: - message.options.issuer = json.issuer; - message.options.role = json.role; - message.options.group = json.group; - if (json.cid) { - message.files.push(json.cid); - } - break; case MessageType.SYNCHRONIZATION_EVENT: message.options.user = json.user; message.options.policy = json.policy; @@ -192,6 +195,62 @@ export class Parser { message.options.contractType = json.contractType; message.options.owner = json.owner; break; + case MessageType.ROLE_DOCUMENT: + message.options.issuer = json.issuer; + message.options.role = json.role; + message.options.group = json.group; + message.options.issuer = json.issuer; + message.options.relationships = json.relationships; + message.options.documentStatus = json.documentStatus; + message.options.encodedData = false; + if (json.cid) { + message.files.push(json.cid); + } + break; + case MessageType.GUARDIAN_ROLE: + message.options.uuid = json.uuid; + message.options.name = json.name; + message.options.description = json.description; + message.options.issuer = json.issuer; + message.options.relationships = json.relationships; + message.options.documentStatus = json.documentStatus; + message.options.encodedData = false; + if (json.cid) { + message.files.push(json.cid); + } + break; + case MessageType.USER_PERMISSIONS: + message.options.user = json.user; + message.options.issuer = json.issuer; + message.options.relationships = json.relationships; + message.options.documentStatus = json.documentStatus; + message.options.encodedData = false; + if (json.cid) { + message.files.push(json.cid); + } + break; + case MessageType.POLICY_STATISTIC: + message.options.uuid = json.uuid; + message.options.name = json.name; + message.options.description = json.description; + message.options.owner = json.owner; + message.options.policyTopicId = json.policyTopicId; + message.options.policyInstanceTopicId = json.policyInstanceTopicId; + if (json.cid) { + message.files.push(json.cid); + } + break; + case MessageType.POLICY_LABEL: + message.options.uuid = json.uuid; + message.options.name = json.name; + message.options.description = json.description; + message.options.owner = json.owner; + message.options.policyTopicId = json.policyTopicId; + message.options.policyInstanceTopicId = json.policyInstanceTopicId; + if (json.cid) { + message.files.push(json.cid); + } + break; default: return null; } diff --git a/interfaces/src/helpers/permissions-helper.ts b/interfaces/src/helpers/permissions-helper.ts index 87ed1c726c..4388ffdf47 100644 --- a/interfaces/src/helpers/permissions-helper.ts +++ b/interfaces/src/helpers/permissions-helper.ts @@ -520,6 +520,15 @@ export class UserPermissions { return this.check(Permissions.SCHEMAS_RULE_EXECUTE); } + //SCHEMA RULES + public get STATISTICS_LABEL_CREATE(): boolean { + return this.check(Permissions.STATISTICS_LABEL_CREATE); + } + + public get STATISTICS_LABEL_READ(): boolean { + return this.check(Permissions.STATISTICS_LABEL_READ); + } + public static isPolicyAdmin(user: any): boolean { return ( UserPermissions.has(user, Permissions.POLICIES_MIGRATION_CREATE) || diff --git a/interfaces/src/index.ts b/interfaces/src/index.ts index 887614ab82..0661cc9803 100644 --- a/interfaces/src/index.ts +++ b/interfaces/src/index.ts @@ -46,3 +46,4 @@ export * from './interface/index.js'; export * from './helpers/index.js'; export * from './models/index.js'; export * from './errors/index.js'; +export * from './validators/index.js'; diff --git a/interfaces/src/interface/index.ts b/interfaces/src/interface/index.ts index d158738003..9f2a24d943 100644 --- a/interfaces/src/interface/index.ts +++ b/interfaces/src/interface/index.ts @@ -42,4 +42,5 @@ export * from './policy-tool-metadata.interface.js'; export * from './sign-options.interface.js' export * from './owner.interface.js' export * from './statistic.interface.js' -export * from './schema-rules.interface.js' \ No newline at end of file +export * from './schema-rules.interface.js' +export * from './policy-label.interface.js' \ No newline at end of file diff --git a/interfaces/src/interface/policy-label.interface.ts b/interfaces/src/interface/policy-label.interface.ts new file mode 100644 index 0000000000..e72a745bc4 --- /dev/null +++ b/interfaces/src/interface/policy-label.interface.ts @@ -0,0 +1,91 @@ +import { IStatisticConfig } from './statistic.interface.js'; + +export enum NavItemType { + Group = 'group', + Rules = 'rules', + Label = 'label', + Statistic = 'statistic', +} + +export enum GroupType { + One = 'one', + Every = 'every', +} + +//children +export interface IItemConfig { + id: string; + tag?: string; + title?: string; + name?: string; + description?: string; + owner?: string; + schemaId?:string; +} + +export interface IGroupItemConfig extends IItemConfig { + type: NavItemType.Group; + rule?: GroupType; + children?: INavItemConfig[]; +} + +export interface ILabelItemConfig extends IItemConfig { + type: NavItemType.Label; + messageId?: string; + config?: IPolicyLabelConfig; +} + +export interface IRulesItemConfig extends IItemConfig { + type: NavItemType.Rules; + config?: IStatisticConfig; +} + +export interface IStatisticItemConfig extends IItemConfig { + type: NavItemType.Statistic; + messageId?: string; + config?: IStatisticConfig; +} + +export type INavItemConfig = IGroupItemConfig | IRulesItemConfig | ILabelItemConfig | IStatisticItemConfig; + +//imports +export interface INavStatisticImportConfig { + id: string; + type: NavItemType.Statistic; + name?: string; + description?: string; + messageId?: string; + owner?: string; + config?: IStatisticConfig; +} + +export interface INavLabelImportConfig { + id: string; + type: NavItemType.Label; + name?: string; + description?: string; + messageId?: string; + owner?: string; + config?: IPolicyLabelConfig; +} + +export type INavImportsConfig = INavStatisticImportConfig | INavLabelImportConfig + +export interface IPolicyLabelConfig { + schemaId?:string; + imports?: INavImportsConfig[]; + children?: INavItemConfig[]; +} + +export interface IPolicyLabel { + id?: string; + name?: string; + description?: string; + instanceTopicId?: string; + policyId?: string; + messageId?: string; + owner?: string; + creator?: string; + status?: string; + config?: IPolicyLabelConfig; +} \ No newline at end of file diff --git a/interfaces/src/interface/statistic.interface.ts b/interfaces/src/interface/statistic.interface.ts index 813b29c275..dadf5c6a10 100644 --- a/interfaces/src/interface/statistic.interface.ts +++ b/interfaces/src/interface/statistic.interface.ts @@ -1,8 +1,11 @@ +import { IConditionRuleData, IFormulaRuleData, IRangeRuleData } from './schema-rules.interface.js'; + export interface IFormulaData { id: string; type: string; description: string; formula: string; + rule?: IFormulaRuleData | IConditionRuleData | IRangeRuleData; } export interface IVariableData { @@ -57,7 +60,9 @@ export interface IStatistic { description?: string; instanceTopicId?: string; policyId?: string; + messageId?: string; owner?: string; + creator?: string; status?: string; config?: IStatisticConfig; } \ No newline at end of file diff --git a/interfaces/src/type/messages/message-api.type.ts b/interfaces/src/type/messages/message-api.type.ts index 105b6c8f14..b335cacd9d 100644 --- a/interfaces/src/type/messages/message-api.type.ts +++ b/interfaces/src/type/messages/message-api.type.ts @@ -244,6 +244,24 @@ export enum MessageAPI { IMPORT_SCHEMA_RULE_FILE = 'IMPORT_SCHEMA_RULE_FILE', EXPORT_SCHEMA_RULE_FILE = 'EXPORT_SCHEMA_RULE_FILE', PREVIEW_SCHEMA_RULE_FILE = 'PREVIEW_SCHEMA_RULE_FILE', + CREATE_POLICY_LABEL = 'CREATE_POLICY_LABEL', + GET_POLICY_LABELS = 'GET_POLICY_LABELS', + GET_POLICY_LABEL = 'GET_POLICY_LABEL', + GET_POLICY_LABEL_RELATIONSHIPS = 'GET_POLICY_LABEL_RELATIONSHIPS', + UPDATE_POLICY_LABEL = 'UPDATE_POLICY_LABEL', + DELETE_POLICY_LABEL = 'DELETE_POLICY_LABEL', + PUBLISH_POLICY_LABEL = 'PUBLISH_POLICY_LABEL', + PUBLISH_POLICY_LABEL_ASYNC = 'PUBLISH_POLICY_LABEL_ASYNC', + EXPORT_POLICY_LABEL_FILE = 'EXPORT_POLICY_LABEL_FILE', + IMPORT_POLICY_LABEL_FILE = 'IMPORT_POLICY_LABEL_FILE', + PREVIEW_POLICY_LABEL_FILE = 'PREVIEW_POLICY_LABEL_FILE', + SEARCH_POLICY_LABEL_COMPONENTS = 'SEARCH_POLICY_LABEL_COMPONENTS', + GET_POLICY_LABEL_TOKENS = 'GET_POLICY_LABEL_TOKENS', + GET_POLICY_LABEL_TOKEN_DOCUMENTS = 'GET_POLICY_LABEL_TOKEN_DOCUMENTS', + CREATE_POLICY_LABEL_DOCUMENT = 'CREATE_POLICY_LABEL_DOCUMENT', + GET_POLICY_LABEL_DOCUMENTS = 'GET_POLICY_LABEL_DOCUMENTS', + GET_POLICY_LABEL_DOCUMENT = 'GET_POLICY_LABEL_DOCUMENT', + GET_POLICY_LABEL_DOCUMENT_RELATIONSHIPS = 'GET_POLICY_LABEL_DOCUMENT_RELATIONSHIPS', } /** diff --git a/interfaces/src/type/notification-action.type.ts b/interfaces/src/type/notification-action.type.ts index b11b29a75f..1b343a8165 100644 --- a/interfaces/src/type/notification-action.type.ts +++ b/interfaces/src/type/notification-action.type.ts @@ -5,4 +5,5 @@ export enum NotificationAction { SCHEMAS_PAGE = 'SCHEMAS_PAGE', TOKENS_PAGE = 'TOKENS_PAGE', PROFILE_PAGE = 'PROFILE_PAGE', + POLICY_LABEL_PAGE= 'POLICY_LABEL_PAGE' } diff --git a/interfaces/src/type/permissions.type.ts b/interfaces/src/type/permissions.type.ts index 04e644eae1..ee5016eb03 100644 --- a/interfaces/src/type/permissions.type.ts +++ b/interfaces/src/type/permissions.type.ts @@ -65,7 +65,8 @@ export enum PermissionEntities { TRUST_CHAIN = 'TRUST_CHAIN', ROLE = 'ROLE', STATISTIC = 'STATISTIC', - RULE = 'RULE' + RULE = 'RULE', + LABEL = 'LABEL' } /** @@ -221,6 +222,8 @@ export enum Permissions { //STATISTICS STATISTICS_STATISTIC_CREATE = 'STATISTICS_STATISTIC_CREATE', STATISTICS_STATISTIC_READ = 'STATISTICS_STATISTIC_READ', + STATISTICS_LABEL_CREATE = 'STATISTICS_LABEL_CREATE', + STATISTICS_LABEL_READ = 'STATISTICS_LABEL_READ', //SCHEMA RULES SCHEMAS_RULE_CREATE = 'SCHEMAS_RULE_CREATE', SCHEMAS_RULE_READ = 'SCHEMAS_RULE_READ', @@ -1145,6 +1148,20 @@ export const PermissionsArray: { action: PermissionActions.CREATE, disabled: false }, + { + name: Permissions.STATISTICS_LABEL_READ, + category: PermissionCategories.STATISTICS, + entity: PermissionEntities.LABEL, + action: PermissionActions.READ, + disabled: false + }, + { + name: Permissions.STATISTICS_LABEL_CREATE, + category: PermissionCategories.STATISTICS, + entity: PermissionEntities.LABEL, + action: PermissionActions.CREATE, + disabled: false + }, //SCHEMA RULE { name: Permissions.SCHEMAS_RULE_READ, @@ -1334,6 +1351,8 @@ export const DefaultRoles: Permissions[] = [ Permissions.ACCESS_POLICY_ASSIGNED_AND_PUBLISHED, Permissions.STATISTICS_STATISTIC_READ, Permissions.STATISTICS_STATISTIC_CREATE, + Permissions.STATISTICS_LABEL_READ, + Permissions.STATISTICS_LABEL_CREATE, Permissions.SCHEMAS_RULE_EXECUTE, ]; diff --git a/interfaces/src/type/schema-category.type.ts b/interfaces/src/type/schema-category.type.ts index 0b62da7dd4..d494e9c76e 100644 --- a/interfaces/src/type/schema-category.type.ts +++ b/interfaces/src/type/schema-category.type.ts @@ -7,5 +7,6 @@ export enum SchemaCategory { SYSTEM = 'SYSTEM', TAG = 'TAG', TOOL = 'TOOL', - STATISTIC = 'STATISTIC' + STATISTIC = 'STATISTIC', + LABEL = 'LABEL' } diff --git a/interfaces/src/type/task-action.type.ts b/interfaces/src/type/task-action.type.ts index 9e52169872..2b3eda28a6 100644 --- a/interfaces/src/type/task-action.type.ts +++ b/interfaces/src/type/task-action.type.ts @@ -30,4 +30,5 @@ export enum TaskAction { IMPORT_TOOL_FILE = 'Import tool file', IMPORT_TOOL_MESSAGE = 'Import tool message', MIGRATE_DATA = 'Migrate data', + PUBLISH_POLICY_LABEL = 'Publish policy label' } diff --git a/interfaces/src/type/topic.type.ts b/interfaces/src/type/topic.type.ts index 5dba645f78..7eba5238d3 100644 --- a/interfaces/src/type/topic.type.ts +++ b/interfaces/src/type/topic.type.ts @@ -14,5 +14,6 @@ export enum TopicType { ContractTopic = 'CONTRACT_TOPIC', ToolTopic = 'TOOL_TOPIC', TagsTopic = 'TAGS_TOPIC', - StatisticTopic = 'STATISTIC_TOPIC' + StatisticTopic = 'STATISTIC_TOPIC', + LabelTopic = 'LABEL_TOPIC' } diff --git a/interfaces/src/validators/index.ts b/interfaces/src/validators/index.ts new file mode 100644 index 0000000000..4c0f62e1e0 --- /dev/null +++ b/interfaces/src/validators/index.ts @@ -0,0 +1,28 @@ +export * from './rule-validator/interfaces/rule.js'; +export * from './rule-validator/interfaces/status.js'; +export * from './rule-validator/interfaces/validate-result.js'; +export * from './rule-validator/document-field-validator.js'; +export * from './rule-validator/document-field-validators.js'; +export * from './rule-validator/document-validator.js'; +export * from './rule-validator/document-validators.js'; +export * from './rule-validator/field-validator.js'; +export * from './rule-validator/rule-validator.js'; +export * from './label-validator/interfaces/step-document.js'; +export * from './label-validator/interfaces/node.js'; +export * from './label-validator/interfaces/status.js'; +export * from './label-validator/interfaces/step.js'; +export * from './label-validator/interfaces/sub-step.js'; +export * from './label-validator/interfaces/validator.js'; +export * from './label-validator/item-group-validator.js'; +export * from './label-validator/item-label-validator.js'; +export * from './label-validator/item-node-validator.js'; +export * from './label-validator/item-rule-validator.js'; +export * from './label-validator/item-statistic-validator.js'; +export * from './label-validator/label-validator.js'; +export * from './label-validator/namespace.js'; +export * from './label-validator/score.js'; +export * from './label-validator/variable-validator.js'; +export * from './statistic-validator/variables.js'; +export * from './statistic-validator/formula.js'; +export * from './statistic-validator/score.js'; +export * from './utils/formula.js'; \ No newline at end of file diff --git a/interfaces/src/validators/label-validator/interfaces/node.ts b/interfaces/src/validators/label-validator/interfaces/node.ts new file mode 100644 index 0000000000..453bbcec67 --- /dev/null +++ b/interfaces/src/validators/label-validator/interfaces/node.ts @@ -0,0 +1,10 @@ +import { IValidator } from './validator.js'; + +export interface IValidatorNode { + name: string; + item: IValidator; + selectable: boolean; + type: string | null; + icon?: string; + children: IValidatorNode[]; +} \ No newline at end of file diff --git a/interfaces/src/validators/label-validator/interfaces/status.ts b/interfaces/src/validators/label-validator/interfaces/status.ts new file mode 100644 index 0000000000..609e904793 --- /dev/null +++ b/interfaces/src/validators/label-validator/interfaces/status.ts @@ -0,0 +1,7 @@ + +export interface IValidateStatus { + id: string; + valid: boolean; + error?: any; + children?: IValidateStatus[]; +} diff --git a/interfaces/src/validators/label-validator/interfaces/step-document.ts b/interfaces/src/validators/label-validator/interfaces/step-document.ts new file mode 100644 index 0000000000..ad65e74841 --- /dev/null +++ b/interfaces/src/validators/label-validator/interfaces/step-document.ts @@ -0,0 +1,5 @@ +export interface IStepDocument { + id: string; + schema: string; + document: any; +} \ No newline at end of file diff --git a/interfaces/src/validators/label-validator/interfaces/step.ts b/interfaces/src/validators/label-validator/interfaces/step.ts new file mode 100644 index 0000000000..5ebdcc78da --- /dev/null +++ b/interfaces/src/validators/label-validator/interfaces/step.ts @@ -0,0 +1,17 @@ +import { IValidator } from './validator.js'; +import { ISubStep } from './sub-step.js'; +import { IValidateStatus } from './status.js'; + +export interface IValidatorStep { + name: string; + title: string; + prefix?: string; + item: IValidator; + type: string; + config: any; + auto: boolean; + disabled?: boolean; + subIndexes?: ISubStep[]; + update: () => void; + validate: () => IValidateStatus; +} \ No newline at end of file diff --git a/interfaces/src/validators/label-validator/interfaces/sub-step.ts b/interfaces/src/validators/label-validator/interfaces/sub-step.ts new file mode 100644 index 0000000000..91d3ba3ee0 --- /dev/null +++ b/interfaces/src/validators/label-validator/interfaces/sub-step.ts @@ -0,0 +1,6 @@ + +export interface ISubStep { + index: number; + name: string; + selected: boolean; +} diff --git a/interfaces/src/validators/label-validator/interfaces/validator.ts b/interfaces/src/validators/label-validator/interfaces/validator.ts new file mode 100644 index 0000000000..22019ea508 --- /dev/null +++ b/interfaces/src/validators/label-validator/interfaces/validator.ts @@ -0,0 +1,13 @@ +import { GroupItemValidator } from '../item-group-validator.js'; +import { LabelItemValidator } from '../item-label-validator.js'; +import { NodeItemValidator } from '../item-node-validator.js'; +import { RuleItemValidator } from '../item-rule-validator.js'; +import { StatisticItemValidator } from '../item-statistic-validator.js'; + +export type IValidator = ( + GroupItemValidator | + LabelItemValidator | + RuleItemValidator | + StatisticItemValidator | + NodeItemValidator +); \ No newline at end of file diff --git a/interfaces/src/validators/label-validator/item-group-validator.ts b/interfaces/src/validators/label-validator/item-group-validator.ts new file mode 100644 index 0000000000..dc4fe0a264 --- /dev/null +++ b/interfaces/src/validators/label-validator/item-group-validator.ts @@ -0,0 +1,145 @@ +import { IValidator } from './interfaces/validator.js'; +import { IValidateStatus } from './interfaces/status.js'; +import { IValidatorStep } from './interfaces/step.js'; +import { ValidateScore } from './score.js'; +import { ValidateNamespace } from './namespace.js'; +import { NodeItemValidator } from './item-node-validator.js'; +import { GroupType, IGroupItemConfig, NavItemType } from '../../interface/index.js'; +import { IStepDocument } from './interfaces/step-document.js'; + +export class GroupItemValidator { + public readonly type: NavItemType | null = NavItemType.Group; + + public readonly id: string; + public readonly name: string; + public readonly title: string; + public readonly tag: string; + public readonly children: IValidator[]; + public readonly steps: number = 0; + public readonly rule: GroupType; + public readonly schema: string; + + public isRoot: boolean; + + private namespace: ValidateNamespace; + private scope: ValidateScore; + private valid: IValidateStatus | undefined; + + public prefix: string; + + constructor(item: IGroupItemConfig) { + this.id = item.id; + this.name = item.name || ''; + this.title = item.title || ''; + this.tag = item.tag || ''; + this.schema = item.schemaId || ''; + this.rule = item.rule || GroupType.Every; + this.children = NodeItemValidator.fromArray(item.children); + this.isRoot = false; + } + + public get status(): boolean | undefined { + return this.valid ? this.valid.valid : undefined; + } + + public setData(namespace: ValidateNamespace) { + this.namespace = namespace; + this.scope = this.namespace.createScore(this.id, this.tag); + for (const child of this.children) { + child.setData(namespace); + } + } + + public validate(): IValidateStatus { + this.valid = { + id: this.id, + valid: true, + children: [] + }; + + if (!this.children.length) { + return this.valid; + } + + let count: number = 0; + for (const child of this.children) { + const childResult = child.validate(); + this.valid.children?.push(childResult); + if (childResult.valid) { + count++; + } + } + + if (this.rule === GroupType.Every) { + this.valid.valid = this.children.length === count; + } else { + this.valid.valid = count > 0; + } + + return this.valid; + } + + public getSteps(): IValidatorStep[] { + return [{ + item: this, + name: this.title, + title: this.title, + prefix: this.prefix, + auto: true, + type: 'validate', + config: null, + update: this.validate.bind(this), + validate: this.validate.bind(this), + }]; + } + + public getNamespace(): ValidateNamespace { + return this.namespace; + } + + public getScope(): ValidateScore { + return this.scope; + } + + public getStatus(): IValidateStatus | undefined { + return this.valid; + } + + public getResult(): any { + return { + status: this.status + } + } + + public setResult(document: any): void { + if (!document) { + this.valid = { + id: this.id, + valid: false, + error: 'Invalid document' + }; + return; + } + this.valid = { + id: this.id, + valid: !!document.status + }; + } + + public clear(): void { + this.valid = undefined; + } + + public getVC(): IStepDocument | null { + return { + id: this.id, + schema: this.schema, + document: this.getResult() + }; + } + + public setVC(vc: any): boolean { + this.setResult(vc); + return true; + } +} diff --git a/interfaces/src/validators/label-validator/item-label-validator.ts b/interfaces/src/validators/label-validator/item-label-validator.ts new file mode 100644 index 0000000000..a8a000c33f --- /dev/null +++ b/interfaces/src/validators/label-validator/item-label-validator.ts @@ -0,0 +1,132 @@ +import { IValidateStatus } from './interfaces/status.js'; +import { IValidatorStep } from './interfaces/step.js'; +import { ValidateScore } from './score.js'; +import { ValidateNamespace } from './namespace.js'; +import { GroupItemValidator } from './item-group-validator.js'; +import { GroupType, ILabelItemConfig, INavImportsConfig, INavItemConfig, IPolicyLabelConfig, NavItemType } from '../../interface/index.js'; +import { IStepDocument } from './interfaces/step-document.js'; + +export class LabelItemValidator { + public readonly type: NavItemType | null = NavItemType.Label; + + public readonly id: string; + public readonly name: string; + public readonly title: string; + public readonly tag: string; + public readonly steps: number = 0; + public readonly root: GroupItemValidator; + public readonly schema: string; + + public isRoot: boolean; + + private namespace: ValidateNamespace; + private scope: ValidateScore; + private valid: IValidateStatus | undefined; + + // tslint:disable-next-line:no-unused-variable + private readonly imports: INavImportsConfig[]; + // tslint:disable-next-line:no-unused-variable + private readonly children: INavItemConfig[]; + + public prefix: string; + + constructor(item: ILabelItemConfig) { + this.id = item.id; + this.name = item.name || ''; + this.title = item.title || ''; + this.tag = item.tag || ''; + this.isRoot = false; + + const label: IPolicyLabelConfig = item.config || {}; + this.imports = label.imports || []; + this.children = label.children || []; + this.schema = item.schemaId || label.schemaId || ''; + + this.root = new GroupItemValidator({ + id: item.id, + type: NavItemType.Group, + name: item.name, + schemaId: this.schema, + rule: GroupType.Every, + children: this.children + }); + this.root.isRoot = true; + } + + public get status(): boolean | undefined { + return this.valid ? this.valid.valid : undefined; + } + + public setData(namespace: ValidateNamespace) { + this.namespace = namespace; + this.scope = this.namespace.createScore(this.id, this.tag); + this.root.setData(namespace); + } + + public validate(): IValidateStatus { + this.valid = this.root.validate(); + return this.valid; + } + + public getSteps(): IValidatorStep[] { + return [{ + item: this, + name: this.title, + title: this.title, + prefix: this.prefix, + auto: true, + type: 'validate', + config: null, + update: this.validate.bind(this), + validate: this.validate.bind(this) + }]; + } + + public getNamespace(): ValidateNamespace { + return this.namespace; + } + + public getScope(): ValidateScore { + return this.scope; + } + + public getStatus(): IValidateStatus | undefined { + return this.valid; + } + + public getResult(): any { + return { + status: this.status + } + } + + public setResult(document: any): void { + if (!document) { + this.valid = { + id: this.id, + valid: false, + error: 'Invalid document' + }; + return; + } + this.root.setResult(document); + this.valid = this.root.getStatus(); + } + + public clear(): void { + this.valid = undefined; + } + + public getVC(): IStepDocument | null { + return { + id: this.id, + schema: this.schema, + document: this.getResult() + }; + } + + public setVC(vc: any): boolean { + this.setResult(vc); + return true; + } +} diff --git a/interfaces/src/validators/label-validator/item-node-validator.ts b/interfaces/src/validators/label-validator/item-node-validator.ts new file mode 100644 index 0000000000..d28d746f1a --- /dev/null +++ b/interfaces/src/validators/label-validator/item-node-validator.ts @@ -0,0 +1,142 @@ +import { GroupItemValidator } from './item-group-validator.js'; +import { LabelItemValidator } from './item-label-validator.js'; +import { RuleItemValidator } from './item-rule-validator.js'; +import { StatisticItemValidator } from './item-statistic-validator.js'; +import { IValidator } from './interfaces/validator.js'; +import { IValidateStatus } from './interfaces/status.js'; +import { IValidatorStep } from './interfaces/step.js'; +import { ValidateScore } from './score.js'; +import { ValidateNamespace } from './namespace.js'; +import { FormulaEngine } from '../utils/formula.js'; +import { IFormulaData, INavItemConfig, NavItemType } from '../../interface/index.js'; +import { IStepDocument } from './interfaces/step-document.js'; + +export class NodeItemValidator { + public readonly type: NavItemType | null = null; + + public readonly id: string; + public readonly name: string; + public readonly title: string; + public readonly tag: string; + public readonly steps: number = 0; + public readonly isRoot: boolean = false; + + private namespace: ValidateNamespace; + private scope: ValidateScore; + private valid: IValidateStatus | undefined; + + public prefix: string; + + constructor(item: any) { + this.id = item.id; + this.name = item.name; + this.title = item.title; + this.tag = item.tag; + } + + public setData(namespace: ValidateNamespace) { + this.namespace = namespace; + this.scope = this.namespace.createScore(this.id, this.tag); + } + + public get status(): boolean | undefined { + return this.valid ? this.valid.valid : undefined; + } + + public validate(): IValidateStatus { + this.valid = { + id: this.id, + valid: false, + error: 'Unidentified item' + }; + return this.valid; + } + + public getSteps(): IValidatorStep[] { + return [{ + item: this, + name: this.name, + title: this.title, + prefix: this.prefix, + auto: true, + type: 'validate', + config: null, + update: this.validate.bind(this), + validate: this.validate.bind(this) + }]; + } + + public getStatus(): IValidateStatus | undefined { + return this.valid; + } + + public getNamespace(): ValidateNamespace { + return this.namespace; + } + + public getScope(): ValidateScore { + return this.scope; + } + + public static calculateFormula(item: IFormulaData, scope: any): any { + let value = FormulaEngine.evaluate(item.formula, scope); + if (value) { + if (item.type === 'string') { + value = String(value); + } else { + value = Number(value); + } + } + return value; + } + + public static from(item: INavItemConfig): IValidator { + switch (item.type) { + case NavItemType.Group: { + return new GroupItemValidator(item); + } + case NavItemType.Label: { + return new LabelItemValidator(item); + } + case NavItemType.Rules: { + return new RuleItemValidator(item); + } + case NavItemType.Statistic: { + return new StatisticItemValidator(item); + } + default: { + return new NodeItemValidator(item); + } + } + } + + public static fromArray(items?: INavItemConfig[]): IValidator[] { + const validators: IValidator[] = []; + if (Array.isArray(items)) { + for (const item of items) { + validators.push(NodeItemValidator.from(item)); + } + } + return validators; + } + + public getResult(): any { + return null; + } + + public setResult(result: any): void { + return; + } + + public clear(): void { + this.valid = undefined; + } + + public getVC(): IStepDocument | null { + return null; + } + + public setVC(vc: any): boolean { + return false; + } +} diff --git a/interfaces/src/validators/label-validator/item-rule-validator.ts b/interfaces/src/validators/label-validator/item-rule-validator.ts new file mode 100644 index 0000000000..a756619f65 --- /dev/null +++ b/interfaces/src/validators/label-validator/item-rule-validator.ts @@ -0,0 +1,356 @@ +import { IValidateStatus } from './interfaces/status.js'; +import { ISubStep } from './interfaces/sub-step.js'; +import { IValidatorStep } from './interfaces/step.js'; +import { ValidateScore } from './score.js'; +import { ValidateNamespace } from './namespace.js'; +import { NodeItemValidator } from './item-node-validator.js'; +import { FormulaValidator } from './variable-validator.js'; +import { FieldRuleResult } from '../rule-validator/interfaces/status.js'; +import { VariableData } from '../statistic-validator/variables.js'; +import { ScoreData } from '../statistic-validator/score.js'; +import { FormulaData } from '../statistic-validator/formula.js'; +import { IRulesItemConfig, NavItemType } from '../../interface/index.js'; +import { IStepDocument } from './interfaces/step-document.js'; + +export class RuleItemValidator { + public readonly type: NavItemType | null = NavItemType.Rules; + + public readonly id: string; + public readonly name: string; + public readonly title: string; + public readonly tag: string; + public readonly steps: number = 3; + public readonly schema: string; + public readonly isRoot: boolean = false; + + private namespace: ValidateNamespace; + private scope: ValidateScore; + private valid: IValidateStatus | undefined; + + private readonly variables: VariableData[]; + private readonly scores: ScoreData[]; + private readonly formulas: FormulaData[]; + + public prefix: string; + + constructor(item: IRulesItemConfig) { + this.id = item.id; + this.name = item.name || ''; + this.title = item.title || ''; + this.tag = item.tag || ''; + this.schema = item.schemaId || ''; + + this.variables = VariableData.from(item.config?.variables); + this.scores = ScoreData.from(item.config?.scores); + this.formulas = FormulaData.from(item.config?.formulas); + + for (const score of this.scores) { + score.setRelationships(this.variables); + } + } + + public get status(): boolean | undefined { + return this.valid ? this.valid.valid : undefined; + } + + public setData(namespace: ValidateNamespace) { + this.namespace = namespace; + this.scope = this.namespace.createScore(this.id, this.tag); + for (const item of this.variables) { + this.scope.setName(item.id); + } + for (const item of this.scores) { + this.scope.setName(item.id); + } + for (const item of this.formulas) { + this.scope.setName(item.id); + } + } + + public updateVariables() { + for (const variable of this.variables) { + const value = this.namespace.getField(variable.schemaId, variable.path); + variable.setValue(value); + this.scope.setVariable(variable.id, variable.value); + } + } + + public updateScores() { + for (const variable of this.variables) { + this.scope.setVariable(variable.id, variable.value); + } + } + + public updateFormulas() { + for (const variable of this.variables) { + this.scope.setVariable(variable.id, variable.value); + } + for (const score of this.scores) { + this.scope.setVariable(score.id, score.value); + } + for (const formula of this.formulas) { + const scope = this.getScore(); + const value = NodeItemValidator.calculateFormula(formula, scope); + formula.setValue(value); + this.scope.setVariable(formula.id, value); + } + } + + public validateVariables(): IValidateStatus { + if (this.valid && this.valid.error === 'Invalid document') { + return this.valid; + } + this.valid = { + id: this.id, + valid: true + }; + + for (const variable of this.variables) { + const value = this.namespace.getField(variable.schemaId, variable.path); + const status = variable.validate(value); + this.scope.setVariable(variable.id, variable.value); + if (!status) { + this.valid.valid = false; + this.valid.error = 'Invalid variable'; + return this.valid; + } + } + return this.valid; + } + + public validateScores(): IValidateStatus { + if (this.valid && this.valid.error === 'Invalid document') { + return this.valid; + } + this.valid = { + id: this.id, + valid: true + }; + + for (const score of this.scores) { + const status = score.validate(score.value); + if (!status) { + this.valid.valid = false; + this.valid.error = 'Invalid scores'; + return this.valid; + } + } + return this.valid; + } + + public validateFormulas(): IValidateStatus { + if (this.valid && this.valid.error === 'Invalid document') { + return this.valid; + } + this.valid = { + id: this.id, + valid: true + }; + + for (const score of this.scores) { + this.scope.setVariable(score.id, score.value); + } + for (const formula of this.formulas) { + const scope = this.getScore(); + const value = NodeItemValidator.calculateFormula(formula, scope); + const status = formula.validate(value); + this.scope.setVariable(formula.id, value); + if (!status) { + this.valid.valid = false; + this.valid.error = 'Invalid formula'; + return this.valid; + } + } + return this.valid; + } + + public validate(): IValidateStatus { + if (this.valid && this.valid.error === 'Invalid document') { + return this.valid; + } + this.valid = { + id: this.id, + valid: true + }; + + const scope = this.getScore(); + for (const formula of this.formulas) { + const validator = new FormulaValidator(formula); + const status = validator.validate(scope); + formula.status = status; + if (status === FieldRuleResult.Failure || status === FieldRuleResult.Error) { + this.valid.valid = false; + this.valid.error = 'Invalid condition'; + return this.valid; + } + } + + return this.valid; + } + + private getScore(): any { + const namespace = this.namespace.getNamespace(); + const scope = this.scope.getScore(); + return Object.assign(namespace, scope); + } + + public getSteps(): IValidatorStep[] { + const steps: IValidatorStep[] = []; + const subIndex: ISubStep[] = []; + + if (this.variables?.length) { + subIndex.push({ + index: subIndex.length + 1, + name: 'Overview', + selected: false + }); + } + if (this.scores?.length) { + subIndex.push({ + index: subIndex.length + 1, + name: 'Scores', + selected: false + }); + } + if (this.formulas?.length) { + subIndex.push({ + index: subIndex.length + 1, + name: 'Statistics', + selected: false + }); + } + + if (this.variables?.length) { + steps.push({ + item: this, + name: 'Overview', + title: this.title, + prefix: this.prefix, + auto: false, + type: 'variables', + config: this.variables, + subIndexes: subIndex.map(e => { return { ...e, selected: e.name === 'Overview' }; }), + update: this.updateVariables.bind(this), + validate: this.validateVariables.bind(this) + }); + } + if (this.scores?.length) { + steps.push({ + item: this, + name: 'Scores', + title: this.title, + prefix: this.prefix, + auto: false, + type: 'scores', + config: this.scores, + subIndexes: subIndex.map(e => { return { ...e, selected: e.name === 'Scores' }; }), + update: this.updateScores.bind(this), + validate: this.validateScores.bind(this) + }); + } + if (this.formulas?.length) { + steps.push({ + item: this, + name: 'Statistics', + title: this.title, + prefix: this.prefix, + auto: false, + type: 'formulas', + config: this.formulas, + subIndexes: subIndex.map(e => { return { ...e, selected: e.name === 'Statistics' }; }), + update: this.updateFormulas.bind(this), + validate: this.validateFormulas.bind(this) + }); + } + steps.push({ + item: this, + name: this.title, + title: this.title, + prefix: this.prefix, + auto: true, + type: 'validate', + config: null, + update: this.validate.bind(this), + validate: this.validate.bind(this) + }); + return steps; + } + + public getNamespace(): ValidateNamespace { + return this.namespace; + } + + public getScope(): ValidateScore { + return this.scope; + } + + public getStatus(): IValidateStatus | undefined { + return this.valid; + } + + public getResult(): any { + const document: any = { + status: this.status + }; + for (const field of this.variables) { + const value = field.getValue(); + if (value !== undefined) { + document[field.id] = value; + } + } + for (const score of this.scores) { + const value = score.getValue(); + if (value !== undefined) { + document[score.id] = value; + } + } + for (const formula of this.formulas) { + const value = formula.getValue(); + if (value !== undefined) { + document[formula.id] = value; + } + } + return document; + } + + public setResult(document: any): void { + if (!document) { + this.valid = { + id: this.id, + valid: false, + error: 'Invalid document' + }; + return; + } + for (const field of this.variables) { + field.setValue(document[field.id]); + } + for (const score of this.scores) { + score.setValue(document[score.id]); + } + for (const formula of this.formulas) { + formula.setValue(document[formula.id]); + } + this.valid = { + id: this.id, + valid: !!document.status + }; + } + + public clear(): void { + this.valid = undefined; + } + + public getVC(): IStepDocument | null { + return { + id: this.id, + schema: this.schema, + document: this.getResult() + }; + } + + public setVC(vc: any): boolean { + this.setResult(vc); + return true; + } +} \ No newline at end of file diff --git a/interfaces/src/validators/label-validator/item-statistic-validator.ts b/interfaces/src/validators/label-validator/item-statistic-validator.ts new file mode 100644 index 0000000000..0b1fdf02d9 --- /dev/null +++ b/interfaces/src/validators/label-validator/item-statistic-validator.ts @@ -0,0 +1,347 @@ +import { IValidateStatus } from './interfaces/status.js'; +import { ISubStep } from './interfaces/sub-step.js'; +import { IValidatorStep } from './interfaces/step.js'; +import { ValidateScore } from './score.js'; +import { ValidateNamespace } from './namespace.js'; +import { NodeItemValidator } from './item-node-validator.js'; +import { VariableData } from '../statistic-validator/variables.js'; +import { ScoreData } from '../statistic-validator/score.js'; +import { FormulaData } from '../statistic-validator/formula.js'; +import { IStatisticItemConfig, NavItemType } from '../../interface/index.js'; +import { IStepDocument } from './interfaces/step-document.js'; + +export class StatisticItemValidator { + public readonly type: NavItemType | null = NavItemType.Statistic; + + public readonly id: string; + public readonly name: string; + public readonly title: string; + public readonly tag: string; + public readonly steps: number = 3; + public readonly schema: string; + public readonly isRoot: boolean = false; + + private namespace: ValidateNamespace; + private scope: ValidateScore; + private valid: IValidateStatus | undefined; + + private readonly variables: VariableData[]; + private readonly scores: ScoreData[]; + private readonly formulas: FormulaData[]; + + public prefix: string; + + constructor(item: IStatisticItemConfig) { + this.id = item.id; + this.name = item.name || ''; + this.title = item.title || ''; + this.tag = item.tag || ''; + this.schema = item.schemaId || ''; + + this.variables = VariableData.from(item.config?.variables); + this.scores = ScoreData.from(item.config?.scores); + this.formulas = FormulaData.from(item.config?.formulas); + + for (const score of this.scores) { + score.setRelationships(this.variables); + } + } + + public get status(): boolean | undefined { + return this.valid ? this.valid.valid : undefined; + } + + public setData(namespace: ValidateNamespace) { + this.namespace = namespace; + this.scope = this.namespace.createScore(this.id, this.tag); + for (const item of this.variables) { + this.scope.setName(item.id); + } + for (const item of this.scores) { + this.scope.setName(item.id); + } + for (const item of this.formulas) { + this.scope.setName(item.id); + } + } + + public updateVariables() { + for (const variable of this.variables) { + const value = this.namespace.getField(variable.schemaId, variable.path); + variable.setValue(value); + this.scope.setVariable(variable.id, variable.value); + } + } + + public updateScores() { + for (const variable of this.variables) { + this.scope.setVariable(variable.id, variable.value); + } + } + + public updateFormulas() { + for (const variable of this.variables) { + this.scope.setVariable(variable.id, variable.value); + } + for (const score of this.scores) { + this.scope.setVariable(score.id, score.value); + } + for (const formula of this.formulas) { + const scope = this.getScore(); + const value = NodeItemValidator.calculateFormula(formula, scope); + formula.setValue(value); + this.scope.setVariable(formula.id, value); + } + } + + public validateVariables(): IValidateStatus { + if (this.valid) { + if (this.valid.valid === false) { + return this.valid; + } + } else { + this.valid = { + id: this.id, + valid: true + }; + } + + for (const variable of this.variables) { + const value = this.namespace.getField(variable.schemaId, variable.path); + const status = variable.validate(value); + this.scope.setVariable(variable.id, variable.value); + if (!status) { + this.valid.valid = false; + this.valid.error = 'Invalid variable'; + return this.valid; + } + } + return this.valid; + } + + public validateScores(): IValidateStatus { + if (this.valid) { + if (this.valid.valid === false) { + return this.valid; + } + } else { + this.valid = { + id: this.id, + valid: true + }; + } + + for (const score of this.scores) { + const status = score.validate(score.value); + if (!status) { + this.valid.valid = false; + this.valid.error = 'Invalid scores'; + return this.valid; + } + } + return this.valid; + } + + public validateFormulas(): IValidateStatus { + if (this.valid) { + if (this.valid.valid === false) { + return this.valid; + } + } else { + this.valid = { + id: this.id, + valid: true + }; + } + + for (const score of this.scores) { + this.scope.setVariable(score.id, score.value); + } + for (const formula of this.formulas) { + const scope = this.getScore(); + const value = NodeItemValidator.calculateFormula(formula, scope); + const status = formula.validate(value); + this.scope.setVariable(formula.id, value); + if (!status) { + this.valid.valid = false; + this.valid.error = 'Invalid formula'; + return this.valid; + } + } + return this.valid; + } + + public validate(): IValidateStatus { + if (!this.valid) { + this.valid = { + id: this.id, + valid: true, + }; + } + return this.valid; + } + + private getScore(): any { + const namespace = this.namespace.getNamespace(); + const scope = this.scope.getScore(); + return Object.assign(namespace, scope); + } + + public getSteps(): IValidatorStep[] { + const steps: IValidatorStep[] = []; + const subIndex: ISubStep[] = []; + + if (this.variables?.length) { + subIndex.push({ + index: subIndex.length + 1, + name: 'Overview', + selected: false + }); + } + if (this.scores?.length) { + subIndex.push({ + index: subIndex.length + 1, + name: 'Scores', + selected: false + }); + } + if (this.formulas?.length) { + subIndex.push({ + index: subIndex.length + 1, + name: 'Statistics', + selected: false + }); + } + + if (this.variables?.length) { + steps.push({ + item: this, + name: 'Overview', + title: this.title, + prefix: this.prefix, + auto: false, + type: 'variables', + config: this.variables, + subIndexes: subIndex.map(e => { return { ...e, selected: e.name === 'Overview' }; }), + update: this.updateVariables.bind(this), + validate: this.validateVariables.bind(this) + }); + } + if (this.scores?.length) { + steps.push({ + item: this, + name: 'Scores', + title: this.title, + prefix: this.prefix, + auto: false, + type: 'scores', + config: this.scores, + subIndexes: subIndex.map(e => { return { ...e, selected: e.name === 'Scores' }; }), + update: this.updateScores.bind(this), + validate: this.validateScores.bind(this) + }); + } + if (this.formulas?.length) { + steps.push({ + item: this, + name: 'Statistics', + title: this.title, + prefix: this.prefix, + auto: false, + type: 'formulas', + config: this.formulas, + subIndexes: subIndex.map(e => { return { ...e, selected: e.name === 'Statistics' }; }), + update: this.updateFormulas.bind(this), + validate: this.validateFormulas.bind(this) + }); + } + steps.push({ + item: this, + name: this.title, + title: this.title, + prefix: this.prefix, + auto: true, + type: 'validate', + config: null, + update: this.validate.bind(this), + validate: this.validate.bind(this) + }); + return steps; + } + + public getNamespace(): ValidateNamespace { + return this.namespace; + } + + public getScope(): ValidateScore { + return this.scope; + } + + public getStatus(): IValidateStatus | undefined { + return this.valid; + } + + public getResult(): any { + const document: any = {}; + for (const field of this.variables) { + const value = field.getValue(); + if (value !== undefined) { + document[field.id] = value; + } + } + for (const score of this.scores) { + const value = score.getValue(); + if (value !== undefined) { + document[score.id] = value; + } + } + for (const formula of this.formulas) { + const value = formula.getValue(); + if (value !== undefined) { + document[formula.id] = value; + } + } + return document; + } + + public setResult(document: any): void { + if (!document) { + this.valid = { + id: this.id, + valid: false, + error: 'Invalid document' + }; + return; + } + for (const field of this.variables) { + field.setValue(document[field.id]); + } + for (const score of this.scores) { + score.setValue(document[score.id]); + } + for (const formula of this.formulas) { + formula.setValue(document[formula.id]); + } + this.valid = { + id: this.id, + valid: true, + }; + } + + public clear(): void { + this.valid = undefined; + } + + public getVC(): IStepDocument | null { + return { + id: this.id, + schema: this.schema, + document: this.getResult() + }; + } + + public setVC(vc: any): boolean { + this.setResult(vc); + return true; + } +} diff --git a/interfaces/src/validators/label-validator/label-validator.ts b/interfaces/src/validators/label-validator/label-validator.ts new file mode 100644 index 0000000000..2e6cf78e15 --- /dev/null +++ b/interfaces/src/validators/label-validator/label-validator.ts @@ -0,0 +1,275 @@ + +import { LabelItemValidator } from './item-label-validator.js'; +import { GroupItemValidator } from './item-group-validator.js'; +import { ValidateNamespace } from './namespace.js'; +import { IValidatorStep } from './interfaces/step.js'; +import { IValidatorNode } from './interfaces/node.js'; +import { IValidateStatus } from './interfaces/status.js'; +import { IValidator } from './interfaces/validator.js'; +import { INavImportsConfig, INavItemConfig, IPolicyLabel, IPolicyLabelConfig, IVPDocument, NavItemType } from '../../interface/index.js'; +import { IStepDocument } from './interfaces/step-document.js'; + +export class LabelValidators { + // tslint:disable-next-line:no-unused-variable + private readonly imports: INavImportsConfig[]; + // tslint:disable-next-line:no-unused-variable + private readonly children: INavItemConfig[]; + private readonly root: LabelItemValidator; + private readonly steps: IValidatorStep[]; + private readonly tree: IValidatorNode; + private readonly list: IValidator[]; + private readonly document: IValidatorStep[]; + + private index: number = 0; + + constructor(label: IPolicyLabel) { + const config: IPolicyLabelConfig = label.config || {}; + this.root = new LabelItemValidator({ + id: 'root', + type: NavItemType.Label, + name: 'root', + title: label.name, + config + }); + this.root.isRoot = true; + this.tree = this.createTree(this.root); + this.steps = this.createSteps(this.root, []); + this.list = this.createList(this.root, []); + this.document = this.createDocument(this.root, []); + } + + public get status(): boolean | undefined { + return this.root ? this.root.status : undefined; + } + + private createList(node: IValidator, result: IValidator[]): IValidator[] { + result.push(node); + if (node.type === NavItemType.Group) { + for (const child of (node as GroupItemValidator).children) { + this.createList(child, result); + } + } else if (node.type === NavItemType.Label) { + this.createList((node as LabelItemValidator).root, result); + } + return result; + } + + private createSteps(node: IValidator, result: IValidatorStep[]): IValidatorStep[] { + if (node.type === NavItemType.Rules) { + this.addSteps(node, result); + } else if (node.type === NavItemType.Statistic) { + this.addSteps(node, result); + } else if (node.type === NavItemType.Group) { + for (const child of (node as GroupItemValidator).children) { + this.createSteps(child, result); + } + this.addSteps(node, result); + } else if (node.type === NavItemType.Label) { + this.createSteps((node as LabelItemValidator).root, result); + this.addSteps(node, result); + } + return result; + } + + private addSteps(node: IValidator, result: IValidatorStep[]): IValidatorStep[] { + const steps = node.getSteps(); + for (const step of steps) { + result.push(step); + } + return result; + } + + private createDocument(node: IValidator, result: IValidatorStep[]): IValidatorStep[] { + if (node.type === NavItemType.Rules) { + this.addDocument(node, result); + } else if (node.type === NavItemType.Statistic) { + this.addDocument(node, result); + } else if (node.type === NavItemType.Group) { + this.addDocument(node, result); + for (const child of (node as GroupItemValidator).children) { + this.createDocument(child, result); + } + } else if (node.type === NavItemType.Label) { + this.addDocument(node, result); + this.createDocument((node as LabelItemValidator).root, result); + } + return result; + } + + private addDocument(node: IValidator, result: IValidatorStep[]): IValidatorStep[] { + if (node.isRoot) { + return result; + } + const steps = node.getSteps(); + steps.unshift(steps.pop()); + for (const step of steps) { + result.push(step); + } + return result; + } + + private createTree(node: IValidator, prefix: string = ''): IValidatorNode { + const item: IValidatorNode = { + name: prefix ? `${prefix} ${node.title}` : node.title, + item: node, + type: node.type, + selectable: ( + node.type === NavItemType.Rules || + node.type === NavItemType.Statistic + ), + children: [] + } + node.prefix = prefix; + if (node.type === NavItemType.Group) { + const childrenNode = (node as GroupItemValidator).children; + for (let i = 0; i < childrenNode.length; i++) { + const childNode = childrenNode[i]; + const child = this.createTree(childNode, `${prefix}${i + 1}.`); + item.children.push(child); + } + } else if (node.type === NavItemType.Label) { + const childrenNode = (node as LabelItemValidator).root.children; + for (let i = 0; i < childrenNode.length; i++) { + const childNode = childrenNode[i]; + const child = this.createTree(childNode, `${prefix}${i + 1}.`); + item.children.push(child); + } + } + return item; + } + + public getValidator(id: string): IValidator | undefined { + return this.list.find((v) => v.id === id); + } + + public setData(documents: any[]) { + const namespaces = new ValidateNamespace('root', documents); + this.root.setData(namespaces); + } + + public getStatus(): IValidateStatus | undefined { + return this.root.getStatus(); + } + + public getTree(): IValidatorNode { + return this.tree; + } + + public getSteps(): IValidatorStep[] { + return this.steps; + } + + public getDocument(): IValidatorStep[] { + return this.document; + } + + public next(): IValidatorStep | null { + this.index++; + this.index = Math.max(Math.min(this.index, this.steps.length), -1); + const step = this.steps[this.index]; + if (step) { + step.update(); + if (step.auto) { + return this.next(); + } else { + return step; + } + } else { + return null; + } + } + + public prev(): IValidatorStep | null { + this.index--; + this.index = Math.max(Math.min(this.index, this.steps.length), -1); + const step = this.steps[this.index]; + if (step) { + if (step.auto) { + return this.prev(); + } else { + step.update(); + return step; + } + } else { + return null; + } + } + + public current(): IValidatorStep | null { + return this.steps[this.index]; + } + + public isNext(): boolean { + return this.index < (this.steps.length - 1); + } + + public isPrev(): boolean { + return this.index > 0; + } + + public start(): IValidatorStep | null { + this.index = -1; + return this.next(); + } + + public getResult(): any[] { + const documents: any[] = []; + for (const item of this.list) { + documents.push(item.getResult()); + } + return documents; + } + + public setResult(result: any[]): void { + for (let i = 0; i < this.list.length; i++) { + const item = this.list[i]; + const document = result[i]; + item.setResult(document); + } + } + + public validate(): IValidateStatus | undefined { + for (const step of this.steps) { + step.validate(); + } + return this.root.getStatus(); + } + + public clear(): void { + for (const item of this.list) { + item.clear(); + } + } + + public getVCs(): IStepDocument[] { + const vcs: IStepDocument[] = []; + for (const node of this.list) { + const vc = node.getVC(); + if (vc) { + vcs.push(vc); + } + } + return vcs; + } + + public setVp(vp: IVPDocument): void { + const result: any[] = []; + const vcs: any = vp.document.verifiableCredential; + if (Array.isArray(vcs)) { + for (const vc of vcs) { + let cs = vc?.credentialSubject; + if (Array.isArray(cs)) { + cs = cs[0]; + } + result.push(cs); + } + } + + let index = 0; + for (const node of this.list) { + if (node.setVC(result[index])) { + index++; + } + } + } +} diff --git a/interfaces/src/validators/label-validator/namespace.ts b/interfaces/src/validators/label-validator/namespace.ts new file mode 100644 index 0000000000..d0767f8493 --- /dev/null +++ b/interfaces/src/validators/label-validator/namespace.ts @@ -0,0 +1,88 @@ +import { ValidateScore } from './score.js'; + +function isObject(item: any): boolean { + return typeof item === 'object' && !Array.isArray(item) && item !== null; +} + +export class ValidateNamespace { + public readonly name: string; + + private readonly documents: any[]; + private readonly children: ValidateNamespace[]; + private readonly scores: ValidateScore[]; + + constructor(name: string, documents: any[]) { + this.name = name; + this.documents = documents; + this.children = []; + this.scores = []; + } + + public createNamespaces(name: string): ValidateNamespace { + const namespace = new ValidateNamespace(name, this.documents); + this.children.push(namespace); + return namespace; + } + + public createScore(id: string, name: string): any { + const score = new ValidateScore(id, name); + this.scores.push(score); + return score; + } + + public getNamespace(id?: string): any { + const namespace: any = {}; + for (const item of this.scores) { + if (item.id === id) { + return namespace; + } + const values = item.getScore(); + const keys = Object.keys(values); + for (const key of keys) { + if (!isObject(namespace[item.name])) { + namespace[item.name] = {}; + } + namespace[item.name][key] = values[key]; + } + } + return namespace; + } + + public getNames(id?: string): string[] { + const names = new Set(); + for (const item of this.scores) { + if (item.id === id) { + return Array.from(names); + } + const keys = item.getName(); + for (const key of keys) { + names.add(`${item.name}.${key}`); + } + } + return Array.from(names); + } + + public getField(schema: string, path: string): any { + const fullPath = [...(path || '').split('.')]; + const document = this.documents?.find((doc) => doc.schema === schema); + if (!document) { + return undefined; + } + return this.getFieldValue(document, fullPath); + } + + private getFieldValue(document: any, fullPath: string[]): any { + let value: any = document?.document?.credentialSubject; + if (Array.isArray(value)) { + value = value[0]; + } + for (const key of fullPath) { + if (value) { + value = value[key]; + } else { + return undefined; + } + } + return value; + } +} diff --git a/interfaces/src/validators/label-validator/score.ts b/interfaces/src/validators/label-validator/score.ts new file mode 100644 index 0000000000..c949a35b9c --- /dev/null +++ b/interfaces/src/validators/label-validator/score.ts @@ -0,0 +1,30 @@ +export class ValidateScore { + public readonly id: string; + public readonly name: string; + + private readonly score: any; + private readonly names: string[]; + + constructor(id: string, name: string) { + this.id = id; + this.name = name; + this.score = {}; + this.names = []; + } + + public setVariable(name: string, value: any) { + this.score[name] = value; + } + + public getScore(): any { + return this.score; + } + + public setName(name: string): void { + this.names.push(name); + } + + public getName(): string[] { + return this.names; + } +} \ No newline at end of file diff --git a/interfaces/src/validators/label-validator/variable-validator.ts b/interfaces/src/validators/label-validator/variable-validator.ts new file mode 100644 index 0000000000..add323a60a --- /dev/null +++ b/interfaces/src/validators/label-validator/variable-validator.ts @@ -0,0 +1,9 @@ + +import { IFormulaData } from '../../interface/index.js'; +import { RuleValidator } from '../rule-validator/rule-validator.js'; + +export class FormulaValidator extends RuleValidator { + constructor(formula: IFormulaData) { + super(formula.id, formula.rule); + } +} \ No newline at end of file diff --git a/interfaces/src/validators/rule-validator/document-field-validator.ts b/interfaces/src/validators/rule-validator/document-field-validator.ts new file mode 100644 index 0000000000..6eff86a7a5 --- /dev/null +++ b/interfaces/src/validators/rule-validator/document-field-validator.ts @@ -0,0 +1,23 @@ +import { ISchemaRuleData } from '../../interface/index.js'; + +export class DocumentFieldVariable { + public readonly id: string; + public readonly schemaId: string; + public readonly path: string; + public readonly fullPah: string; + public readonly fieldRef: boolean; + public readonly fieldArray: boolean; + public readonly fieldDescription: string; + public readonly schemaName: string; + + constructor(variable: ISchemaRuleData) { + this.id = variable.id; + this.schemaId = variable.schemaId; + this.path = variable.path; + this.fullPah = variable.schemaId + '/' + variable.path; + this.fieldRef = variable.fieldRef; + this.fieldArray = variable.fieldArray; + this.fieldDescription = variable.fieldDescription; + this.schemaName = variable.schemaName; + } +} \ No newline at end of file diff --git a/interfaces/src/validators/rule-validator/document-field-validators.ts b/interfaces/src/validators/rule-validator/document-field-validators.ts new file mode 100644 index 0000000000..e5aa81abd6 --- /dev/null +++ b/interfaces/src/validators/rule-validator/document-field-validators.ts @@ -0,0 +1,45 @@ +import { DocumentFieldVariable } from './document-field-validator.js'; +import { FieldRuleResult } from './interfaces/status.js'; +import { FieldValidator } from './field-validator.js'; +import { ISchemaRuleData } from '../../interface/index.js'; + +export class DocumentFieldValidators { + public readonly rules: FieldValidator[]; + public readonly variables: DocumentFieldVariable[]; + public readonly idToPath: Map; + public readonly pathToId: Map; + + constructor(rules?: ISchemaRuleData[]) { + const variables = rules || []; + + this.rules = []; + this.variables = []; + for (const variable of variables) { + this.rules.push(new FieldValidator(variable)); + this.variables.push(new DocumentFieldVariable(variable)); + } + this.idToPath = new Map(); + this.pathToId = new Map(); + for (const variable of this.variables) { + this.idToPath.set(variable.id, variable.fullPah); + this.pathToId.set(variable.fullPah, variable.id); + } + } + + public validate(scope: any): { [x: string]: FieldRuleResult; } { + const result: { [x: string]: FieldRuleResult; } = {}; + for (const rule of this.rules) { + result[rule.id] = rule.validate(scope); + } + return result; + } + + public validateWithFullPath(scope: any): { [x: string]: FieldRuleResult; } { + const result: { [x: string]: FieldRuleResult; } = {}; + for (const rule of this.rules) { + const path = this.idToPath.get(rule.id) || rule.id; + result[path] = rule.validate(scope); + } + return result; + } +} \ No newline at end of file diff --git a/interfaces/src/validators/rule-validator/document-validator.ts b/interfaces/src/validators/rule-validator/document-validator.ts new file mode 100644 index 0000000000..28a053cbf0 --- /dev/null +++ b/interfaces/src/validators/rule-validator/document-validator.ts @@ -0,0 +1,89 @@ +import { IVC, IVCDocument } from '../../interface/index.js'; +import { DocumentFieldValidators } from './document-field-validators.js'; + +export class DocumentValidator { + public readonly name: string; + public readonly description: string; + public readonly schemas: Set; + public readonly validators: DocumentFieldValidators; + public readonly relationships: Map; + + constructor(data: any) { + const item = data.rules || {}; + const configuration = item.config || {}; + const relationships = data.relationships || []; + + this.relationships = new Map(); + for (const document of relationships) { + DocumentValidator.convertDocument( + DocumentValidator.getCredentialSubject(document?.document), + document?.schema + '/', + this.relationships + ); + } + + this.name = item.name; + this.description = item.description; + this.validators = new DocumentFieldValidators(configuration.fields); + this.schemas = new Set(); + for (const variable of this.validators.variables) { + this.schemas.add(variable.schemaId); + } + } + + public validate(iri: string | undefined, list: Map) { + if (!iri || !this.schemas.has(iri)) { + return null; + } + const score: { [id: string]: any; } = {}; + for (const variable of this.validators.variables) { + if (list.has(variable.fullPah)) { + score[variable.id] = list.get(variable.fullPah); + } else if (this.relationships.has(variable.fullPah)) { + score[variable.id] = this.relationships.get(variable.fullPah); + } else { + score[variable.id] = null; + } + } + return this.validators.validateWithFullPath(score); + } + + public static getCredentialSubject(document?: IVC): any { + const credentialSubject: any = document?.credentialSubject; + if (Array.isArray(credentialSubject)) { + return credentialSubject[0]; + } else { + return credentialSubject; + } + } + + public static convertDocument( + document: any, + path: string, + list: Map + ): Map { + if (!document) { + return list; + } + for (const [key, value] of Object.entries(document)) { + const currentPath = path + key; + switch (typeof value) { + case 'function': { + break; + } + case 'object': { + list.set(currentPath, value); + if (!Array.isArray(value)) { + DocumentValidator.convertDocument(value, currentPath + '.', list); + } + break; + } + default: { + list.set(currentPath, value); + break; + } + } + } + return list; + } +} diff --git a/interfaces/src/validators/rule-validator/document-validators.ts b/interfaces/src/validators/rule-validator/document-validators.ts new file mode 100644 index 0000000000..6b7e7c098a --- /dev/null +++ b/interfaces/src/validators/rule-validator/document-validators.ts @@ -0,0 +1,81 @@ +import { SchemaRuleValidateResult } from './interfaces/validate-result.js'; +import { FieldRuleResult } from './interfaces/status.js'; +import { DocumentValidator } from './document-validator.js'; + +export class DocumentValidators { + public schemas: Set; + public validators: DocumentValidator[]; + + constructor(data: any[] | null) { + this.validators = (data || []).map((v) => new DocumentValidator(v)); + this.schemas = new Set(); + for (const validator of this.validators) { + for (const iri of validator.schemas) { + this.schemas.add(iri); + } + } + } + + public validateVC(iri: string | undefined, vc: any): any { + if (this.validators.length === 0) { + return null; + } + if (!iri || !this.schemas.has(iri)) { + return null; + } + const data = DocumentValidator.getCredentialSubject(vc); + const list = DocumentValidator.convertDocument(data, iri + '/', new Map()); + return this.validate(iri, list); + } + + public validateForm(iri: string | undefined, data: any): any { + if (this.validators.length === 0) { + return null; + } + if (!iri || !this.schemas.has(iri)) { + return null; + } + const list = DocumentValidator.convertDocument(data, iri + '/', new Map()); + return this.validate(iri, list); + } + + private validate(iri: string | undefined, list: Map): any { + const results: { [x: string]: FieldRuleResult; }[] = []; + for (const validator of this.validators) { + results.push(validator.validate(iri, list)); + } + + const statuses: SchemaRuleValidateResult = {}; + for (let i = 0; i < results.length; i++) { + const result = results[i]; + const validator = this.validators[i]; + if (result) { + for (const [path, status] of Object.entries(result)) { + if (status !== FieldRuleResult.None) { + if (statuses[path]) { + if (status === FieldRuleResult.Error || status === FieldRuleResult.Failure) { + statuses[path].status = status; + } + statuses[path].rules.push({ + name: validator.name, + description: validator.description, + status, + }); + } else { + statuses[path] = { + status, + tooltip: '', + rules: [{ + name: validator.name, + description: validator.description, + status, + }] + }; + } + } + } + } + } + return statuses; + } +} diff --git a/interfaces/src/validators/rule-validator/field-validator.ts b/interfaces/src/validators/rule-validator/field-validator.ts new file mode 100644 index 0000000000..3a15b3bd35 --- /dev/null +++ b/interfaces/src/validators/rule-validator/field-validator.ts @@ -0,0 +1,21 @@ +import { ISchemaRuleData } from '../../interface/index.js'; +import { RuleValidator } from './rule-validator.js'; + +export class FieldValidator extends RuleValidator { + public readonly path: string; + public readonly schemaId: string; + + constructor(rule: ISchemaRuleData) { + super(rule.id, rule.rule); + this.path = rule.path; + this.schemaId = rule.schemaId; + } + + public checkField(path: string, schema?: string): boolean { + if (schema) { + return this.path === path && this.schemaId === schema; + } else { + return this.path === path; + } + } +} diff --git a/interfaces/src/validators/rule-validator/interfaces/formula-type.ts b/interfaces/src/validators/rule-validator/interfaces/formula-type.ts new file mode 100644 index 0000000000..7990444b7e --- /dev/null +++ b/interfaces/src/validators/rule-validator/interfaces/formula-type.ts @@ -0,0 +1,6 @@ +export enum FormulaType { + None = 'none', + Formula = 'formula', + Range = 'range', + Condition = 'condition' +} \ No newline at end of file diff --git a/interfaces/src/validators/rule-validator/interfaces/rule.ts b/interfaces/src/validators/rule-validator/interfaces/rule.ts new file mode 100644 index 0000000000..403e63da4f --- /dev/null +++ b/interfaces/src/validators/rule-validator/interfaces/rule.ts @@ -0,0 +1,3 @@ +import { IConditionRuleData, IFormulaRuleData, IRangeRuleData } from '../../../interface/index.js'; + +export type IVariableRuleData = IFormulaRuleData | IConditionRuleData | IRangeRuleData | undefined; \ No newline at end of file diff --git a/interfaces/src/validators/rule-validator/interfaces/status.ts b/interfaces/src/validators/rule-validator/interfaces/status.ts new file mode 100644 index 0000000000..21cc4d8551 --- /dev/null +++ b/interfaces/src/validators/rule-validator/interfaces/status.ts @@ -0,0 +1,7 @@ + +export enum FieldRuleResult { + None = 'None', + Error = 'Error', + Failure = 'Failure', + Success = 'Success' +} diff --git a/interfaces/src/validators/rule-validator/interfaces/validate-result.ts b/interfaces/src/validators/rule-validator/interfaces/validate-result.ts new file mode 100644 index 0000000000..7981803d4b --- /dev/null +++ b/interfaces/src/validators/rule-validator/interfaces/validate-result.ts @@ -0,0 +1,13 @@ +import { FieldRuleResult } from './status.js'; + +export interface SchemaRuleValidateResult { + [path: string]: { + status: FieldRuleResult; + tooltip: string; + rules: { + name: string; + description: string; + status: string; + }[]; + }; +} diff --git a/interfaces/src/validators/rule-validator/rule-validator.ts b/interfaces/src/validators/rule-validator/rule-validator.ts new file mode 100644 index 0000000000..8d19329cd1 --- /dev/null +++ b/interfaces/src/validators/rule-validator/rule-validator.ts @@ -0,0 +1,136 @@ +import { IVariableRuleData } from './interfaces/rule.js'; +import { FieldRuleResult } from './interfaces/status.js'; +import { FormulaEngine } from '../utils/formula.js'; +import { FormulaType } from './interfaces/formula-type.js'; +import { IConditionEnum, IConditionFormula, IConditionRange, IConditionText } from '../../interface/index.js'; + +export class RuleValidator { + public readonly id: string; + public readonly rule: IVariableRuleData; + + private type: FormulaType; + private formula: string; + private conditions: { + type: 'if' | 'else'; + if: string; + then: string; + }[]; + + constructor(id: string, rule: IVariableRuleData) { + this.id = id; + this.rule = rule; + this.parse(); + } + + private parse() { + if (!this.rule) { + return; + } + if (this.rule.type === 'formula') { + this.type = FormulaType.Formula; + this.formula = this.rule.formula; + } else if (this.rule.type === 'range') { + this.type = FormulaType.Range; + this.formula = `${this.rule.min} <= ${this.id} <= ${this.rule.max}`; + } else if (this.rule.type === 'condition') { + this.type = FormulaType.Condition; + this.conditions = []; + const conditions = this.rule.conditions || []; + for (const condition of conditions) { + if (condition.type === 'if') { + this.conditions.push({ + type: 'if', + if: this.parseCondition(condition.condition), + then: this.parseCondition(condition.formula) + }); + } else if (condition.type === 'else') { + this.conditions.push({ + type: 'else', + if: '', + then: this.parseCondition(condition.formula) + }); + } + } + } else { + this.type = FormulaType.None; + } + } + + private parseCondition( + condition: IConditionFormula | IConditionRange | IConditionText | IConditionEnum + ): string { + if (!condition) { + return ''; + } + if (condition.type === 'formula') { + return condition.formula; + } else if (condition.type === 'range') { + return `${condition.min} <= ${condition.variable} <= ${condition.max}`; + } else if (condition.type === 'text') { + return `${condition.variable} == '${condition.value}'`; + } else if (condition.type === 'enum') { + const items = []; + if (Array.isArray(condition.value)) { + for (const value of condition.value) { + items.push(`${condition.variable} == '${value}'`); + } + } + return items.join(' or '); + } else { + return ''; + } + } + + public validate(scope: any): FieldRuleResult { + if (this.type === FormulaType.None) { + return FieldRuleResult.None; + } + + if (this.type === FormulaType.Formula) { + return this.calculate(this.formula, scope); + } + + if (this.type === FormulaType.Range) { + return this.calculate(this.formula, scope); + } + + if (this.type === FormulaType.Condition) { + for (const condition of this.conditions) { + const _if = condition.type === 'if' ? + this.calculate(condition.if, scope) : + FieldRuleResult.Success; + + if (_if === FieldRuleResult.Error) { + return FieldRuleResult.Error; + } + if (_if === FieldRuleResult.Success) { + return this.calculate(condition.then, scope); + } + } + return FieldRuleResult.None; + } + + return FieldRuleResult.None; + } + + private calculate(formula: string, scope: any): FieldRuleResult { + try { + if (!formula) { + return FieldRuleResult.None; + } + const result: any = FormulaEngine.evaluate(formula, scope); + if (result === '' || result === 'Incorrect formula') { + return FieldRuleResult.Error; + } + if (result === 0 || result === false || result === '0' || result === 'false') { + return FieldRuleResult.Failure; + } + if (result) { + return FieldRuleResult.Success; + } + return FieldRuleResult.Error; + } catch (error) { + return FieldRuleResult.Error; + } + } +} \ No newline at end of file diff --git a/interfaces/src/validators/statistic-validator/formula.ts b/interfaces/src/validators/statistic-validator/formula.ts new file mode 100644 index 0000000000..b5f7d98687 --- /dev/null +++ b/interfaces/src/validators/statistic-validator/formula.ts @@ -0,0 +1,40 @@ +import { IConditionRuleData, IFormulaData, IFormulaRuleData, IRangeRuleData } from '../../interface/index.js'; +import { FieldRuleResult } from '../rule-validator/interfaces/status.js'; + +export class FormulaData implements IFormulaData { + public id: string; + public type: string; + public description: string; + public formula: string; + public rule?: IFormulaRuleData | IConditionRuleData | IRangeRuleData; + + public value: any; + public status: FieldRuleResult; + + constructor(item: IFormulaData) { + this.id = item.id; + this.type = item.type; + this.description = item.description; + this.formula = item.formula; + this.rule = item.rule; + } + + public setValue(value: any): void { + this.value = value; + } + + public getValue(): any { + return this.value; + } + + public validate(value: any): boolean { + return this.value === value; + } + + public static from(data?: IFormulaData[]): FormulaData[] { + if (Array.isArray(data)) { + return data.map((e) => new FormulaData(e)); + } + return []; + } +} diff --git a/interfaces/src/validators/statistic-validator/score.ts b/interfaces/src/validators/statistic-validator/score.ts new file mode 100644 index 0000000000..d60f256496 --- /dev/null +++ b/interfaces/src/validators/statistic-validator/score.ts @@ -0,0 +1,73 @@ +import { GenerateUUIDv4 } from '../../helpers/index.js'; +import { IScoreData, IScoreOption } from '../../interface/index.js'; +import { VariableData } from './variables.js'; + +export class ScoreData implements IScoreData { + public id: string; + public type: string; + public description: string; + public relationships: string[]; + public options: IScoreOption[]; + + public value: any; + public _relationships: VariableData[]; + public _options: { + id: string; + description: string; + value: string | number; + }[]; + + constructor(item: IScoreData) { + this.id = item.id; + this.type = item.type; + this.description = item.description; + this.relationships = item.relationships || []; + this.options = item.options || []; + } + + public setRelationships(variables: VariableData[]) { + if (Array.isArray(variables)) { + this._relationships = this.relationships + .map((id) => { + return variables.find((v) => v.id === id); + }) + .filter((v) => v !== undefined) as VariableData[]; + } else { + this._relationships = []; + } + if (Array.isArray(this.options)) { + this._options = this.options + .map((option) => { + return { + id: GenerateUUIDv4(), + description: option.description, + value: option.value + }; + }); + } else { + this._options = []; + } + } + + public setValue(value: any): void { + const option = this.options.find((o) => o.description === value); + this.value = option?.value || value; + } + + public getValue(): any { + const option = this.options.find((o) => o.value === this.value); + return option?.description || String(this.value); + } + + public validate(value: any): boolean { + const option = this.options.find((o) => o.value === value); + return this.value === value && !!option; + } + + public static from(data?: IScoreData[]): ScoreData[] { + if (Array.isArray(data)) { + return data.map((e) => new ScoreData(e)); + } + return []; + } +} diff --git a/interfaces/src/validators/statistic-validator/variables.ts b/interfaces/src/validators/statistic-validator/variables.ts new file mode 100644 index 0000000000..8988dbcd94 --- /dev/null +++ b/interfaces/src/validators/statistic-validator/variables.ts @@ -0,0 +1,52 @@ +import { IVariableData } from '../../interface/index.js'; + +export class VariableData implements IVariableData { + public id: string; + public schemaId: string; + public path: string; + public schemaName: string; + public schemaPath: string; + public fieldType: string; + public fieldRef: boolean; + public fieldArray: boolean; + public fieldDescription: string; + public fieldProperty: string; + public fieldPropertyName: string; + + public value: any; + public isArray: boolean; + + constructor(item: IVariableData) { + this.id = item.id; + this.schemaId = item.schemaId; + this.path = item.path; + this.schemaName = item.schemaName; + this.schemaPath = item.schemaPath; + this.fieldType = item.fieldType; + this.fieldRef = item.fieldRef; + this.fieldArray = item.fieldArray; + this.fieldDescription = item.fieldDescription; + this.fieldProperty = item.fieldProperty; + this.fieldPropertyName = item.fieldPropertyName; + } + + public setValue(value: any): void { + this.value = value; + this.isArray = Array.isArray(value); + } + + public getValue(): any { + return this.value; + } + + public validate(value: any): boolean { + return this.value === value; + } + + public static from(data?: IVariableData[]): VariableData[] { + if (Array.isArray(data)) { + return data.map((e) => new VariableData(e)); + } + return []; + } +} \ No newline at end of file diff --git a/interfaces/src/validators/utils/formula.ts b/interfaces/src/validators/utils/formula.ts new file mode 100644 index 0000000000..ff5ec6cc4d --- /dev/null +++ b/interfaces/src/validators/utils/formula.ts @@ -0,0 +1,27 @@ +export abstract class FormulaEngine { + private static mathjs: any; + + public static setMathEngine(math: any): void { + FormulaEngine.mathjs = math + } + + /** + * Evaluate expressions + * @param formula + * @param scope + */ + public static evaluate(formula: string, scope: any): any { + if (!FormulaEngine.mathjs) { + throw new Error('Math engine is not defined'); + } + const ex = formula.trim().trim().replace(/^=/, ''); + // tslint:disable-next-line:only-arrow-functions + return (function (_mathjs: any, _formula: string, _scope: any) { + try { + return _mathjs.evaluate(_formula, _scope); + } catch (error) { + return 'Incorrect formula'; + } + }).call(null, FormulaEngine.mathjs, ex, scope); + } +} \ No newline at end of file diff --git a/interfaces/tsconfig.json b/interfaces/tsconfig.json index 0a20c54ada..6235926735 100644 --- a/interfaces/tsconfig.json +++ b/interfaces/tsconfig.json @@ -22,4 +22,4 @@ "node_modules" ], "compileOnSave": true -} +} \ No newline at end of file diff --git a/policy-service/src/policy-engine/blocks/request-vc-document-block-addon.ts b/policy-service/src/policy-engine/blocks/request-vc-document-block-addon.ts index 412e89570a..a0c550b8a6 100644 --- a/policy-service/src/policy-engine/blocks/request-vc-document-block-addon.ts +++ b/policy-service/src/policy-engine/blocks/request-vc-document-block-addon.ts @@ -247,7 +247,7 @@ export class RequestVcDocumentBlockAddon { item.schema = schemaIRI; item.accounts = accounts; item = PolicyUtils.setDocumentRef(item, documentRef); - + const state: IPolicyEventState = { data: item }; const error = await this.validateDocuments(user, state); if (error) { diff --git a/policy-service/src/policy-engine/helpers/decorators/data-source-block.ts b/policy-service/src/policy-engine/helpers/decorators/data-source-block.ts index 580cf83b63..88eef5a29e 100644 --- a/policy-service/src/policy-engine/helpers/decorators/data-source-block.ts +++ b/policy-service/src/policy-engine/helpers/decorators/data-source-block.ts @@ -214,7 +214,7 @@ export function DataSourceBlock(options: Partial) { $set: { firstCredentialSubject: { $ifNull: [{ - $arrayElemAt: ["$document.credentialSubject", 0] + $arrayElemAt: ['$document.credentialSubject', 0] }, null] } } diff --git a/policy-service/src/policy-engine/helpers/utils.ts b/policy-service/src/policy-engine/helpers/utils.ts index 7a004cba04..e9bd90211c 100644 --- a/policy-service/src/policy-engine/helpers/utils.ts +++ b/policy-service/src/policy-engine/helpers/utils.ts @@ -1340,11 +1340,11 @@ export class PolicyUtils { //Check number value const numberValue = PolicyUtils.parseQueryNumberValue(queryValue); if (numberValue) { - if(queryOperation === '$nin') { + if (queryOperation === '$nin') { return { $and: [ - { $not: { $in: [`\$${queryKey}`, numberValue[0]] }}, - { $not: { $in: [`\$${queryKey}`, numberValue[1]] }} + { $not: { $in: [`\$${queryKey}`, numberValue[0]] } }, + { $not: { $in: [`\$${queryKey}`, numberValue[1]] } } ] } } else if (queryOperation === '$ne') { @@ -1363,10 +1363,11 @@ export class PolicyUtils { } } } else { - if(queryOperation === '$nin') { - return { $not: { $in: [`\$${queryKey}`, queryValue] }} - } else + if (queryOperation === '$nin') { + return { $not: { $in: [`\$${queryKey}`, queryValue] } } + } else { return { [`${queryOperation}`]: [`\$${queryKey}`, queryValue] }; + } } } diff --git a/swagger-indexer.yaml b/swagger-indexer.yaml index 34b4b755d2..b16bffc87b 100644 --- a/swagger-indexer.yaml +++ b/swagger-indexer.yaml @@ -987,11 +987,11 @@ paths: schema: $ref: '#/components/schemas/InternalServerErrorDTO' tags: *ref_0 - /entities/did-documents: + /entities/statistics: get: - operationId: EntityApi_getDidDocuments - summary: Get DIDs - description: Returns DIDs + operationId: EntityApi_getStatistics + summary: Get statistics + description: Returns statistics parameters: - name: pageIndex required: false @@ -1034,28 +1034,37 @@ paths: description: Keywords to search examples: 0.0.1960: - description: Search DIDs, which are related to specific topic identifier + description: >- + Search statistics, which are related to specific topic + identifier value: '["0.0.1960"]' schema: type: string - name: topicId required: false in: query - description: Topic identifier + description: Statistic topic identifier example: 0.0.1960 schema: type: string - - name: options.did + - name: options.owner required: false in: query - description: DID + description: Statistic owner example: >- did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265 schema: type: string + - name: options.policy + required: false + in: query + description: Policy + example: '1706823227.586179534' + schema: + type: string responses: '200': - description: DIDs + description: Statistics content: application/json: schema: @@ -1065,34 +1074,7 @@ paths: items: type: array items: - $ref: '#/components/schemas/DIDGridDTO' - '500': - description: Internal server error - content: - application/json: - schema: - $ref: '#/components/schemas/InternalServerErrorDTO' - tags: *ref_0 - /entities/did-documents/{messageId}: - get: - operationId: EntityApi_getDidDocument - summary: Get DID - description: Returns DID - parameters: - - name: messageId - required: true - in: path - description: Message identifier - example: '1706823227.586179534' - schema: - type: string - responses: - '200': - description: DID details - content: - application/json: - schema: - $ref: '#/components/schemas/DIDDetailsDTO' + $ref: '#/components/schemas/StatisticDTO' '500': description: Internal server error content: @@ -1100,11 +1082,11 @@ paths: schema: $ref: '#/components/schemas/InternalServerErrorDTO' tags: *ref_0 - /entities/did-documents/{messageId}/relationships: + /entities/statistics/{messageId}: get: - operationId: EntityApi_getDidRelationships - summary: Get DID relationships - description: Returns DID relationships + operationId: EntityApi_getStatistic + summary: Get statistic + description: Returns statistic parameters: - name: messageId required: true @@ -1115,11 +1097,11 @@ paths: type: string responses: '200': - description: DID relationships + description: Statistic details content: application/json: schema: - $ref: '#/components/schemas/RelationshipsDTO' + $ref: '#/components/schemas/StatisticDetailsDTO' '500': description: Internal server error content: @@ -1127,11 +1109,11 @@ paths: schema: $ref: '#/components/schemas/InternalServerErrorDTO' tags: *ref_0 - /entities/vp-documents: + /entities/statistic-documents: get: - operationId: EntityApi_getVpDocuments - summary: Get VPs - description: Returns VPs + operationId: EntityApi_getStatisticDocuments + summary: Get VCs + description: Returns VCs parameters: - name: pageIndex required: false @@ -1174,7 +1156,7 @@ paths: description: Keywords to search examples: 0.0.1960: - description: Search VPs, which are related to specific topic identifier + description: Search VCs, which are related to specific topic identifier value: '["0.0.1960"]' schema: type: string @@ -1200,16 +1182,23 @@ paths: example: '1706823227.586179534' schema: type: string - - name: analytics.schemaIds + - name: analytics.schemaId required: false in: query description: Schema identifier example: '1706823227.586179534' schema: type: string + - name: options.relationships + required: false + in: query + description: Relationships + example: '1706823227.586179534' + schema: + type: string responses: '200': - description: VPs + description: VCs content: application/json: schema: @@ -1219,61 +1208,7 @@ paths: items: type: array items: - $ref: '#/components/schemas/VPGridDTO' - '500': - description: Internal server error - content: - application/json: - schema: - $ref: '#/components/schemas/InternalServerErrorDTO' - tags: *ref_0 - /entities/vp-documents/{messageId}: - get: - operationId: EntityApi_getVpDocument - summary: Get VP - description: Returns VP - parameters: - - name: messageId - required: true - in: path - description: Message identifier - example: '1706823227.586179534' - schema: - type: string - responses: - '200': - description: VP details - content: - application/json: - schema: - $ref: '#/components/schemas/VPDetailsDTO' - '500': - description: Internal server error - content: - application/json: - schema: - $ref: '#/components/schemas/InternalServerErrorDTO' - tags: *ref_0 - /entities/vp-documents/{messageId}/relationships: - get: - operationId: EntityApi_getVpRelationships - summary: Get VP relationships - description: Returns VP relationships - parameters: - - name: messageId - required: true - in: path - description: Message identifier - example: '1706823227.586179534' - schema: - type: string - responses: - '200': - description: VP relationships - content: - application/json: - schema: - $ref: '#/components/schemas/RelationshipsDTO' + $ref: '#/components/schemas/VCGridDTO' '500': description: Internal server error content: @@ -1281,11 +1216,11 @@ paths: schema: $ref: '#/components/schemas/InternalServerErrorDTO' tags: *ref_0 - /entities/vc-documents: + /entities/labels: get: - operationId: EntityApi_getVcDocuments - summary: Get VCs - description: Returns VCs + operationId: EntityApi_getLabels + summary: Get labels + description: Returns labels parameters: - name: pageIndex required: false @@ -1328,49 +1263,35 @@ paths: description: Keywords to search examples: 0.0.1960: - description: Search VCs, which are related to specific topic identifier + description: Search labels, which are related to specific topic identifier value: '["0.0.1960"]' schema: type: string - name: topicId required: false in: query - description: Topic identifier + description: Label topic identifier example: 0.0.1960 schema: type: string - - name: options.issuer + - name: options.owner required: false in: query - description: Issuer + description: Label owner example: >- did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265 schema: type: string - - name: analytics.policyId - required: false - in: query - description: Policy identifier - example: '1706823227.586179534' - schema: - type: string - - name: analytics.schemaId - required: false - in: query - description: Schema identifier - example: '1706823227.586179534' - schema: - type: string - - name: options.relationships + - name: options.policy required: false in: query - description: Relationships + description: Policy example: '1706823227.586179534' schema: type: string responses: '200': - description: VCs + description: Labels content: application/json: schema: @@ -1380,7 +1301,7 @@ paths: items: type: array items: - $ref: '#/components/schemas/VCGridDTO' + $ref: '#/components/schemas/LabelDTO' '500': description: Internal server error content: @@ -1388,11 +1309,11 @@ paths: schema: $ref: '#/components/schemas/InternalServerErrorDTO' tags: *ref_0 - /entities/vc-documents/{messageId}: + /entities/labels/{messageId}: get: - operationId: EntityApi_getVcDocument - summary: Get VC - description: Returns VC + operationId: EntityApi_getLabel + summary: Get label + description: Returns label parameters: - name: messageId required: true @@ -1403,11 +1324,11 @@ paths: type: string responses: '200': - description: VC details + description: Label details content: application/json: schema: - $ref: '#/components/schemas/VCDetailsDTO' + $ref: '#/components/schemas/LabelDetailsDTO' '500': description: Internal server error content: @@ -1415,11 +1336,11 @@ paths: schema: $ref: '#/components/schemas/InternalServerErrorDTO' tags: *ref_0 - /entities/vc-documents/{messageId}/relationships: + /entities/label-documents/{messageId}: get: - operationId: EntityApi_getVcRelationships - summary: Get VC relationships - description: Returns VC relationships + operationId: EntityApi_getLabelDocument + summary: Get label document + description: Returns label document parameters: - name: messageId required: true @@ -1430,11 +1351,11 @@ paths: type: string responses: '200': - description: VC relationships + description: Label document details content: application/json: schema: - $ref: '#/components/schemas/RelationshipsDTO' + $ref: '#/components/schemas/LabelDocumentDetailsDTO' '500': description: Internal server error content: @@ -1442,11 +1363,11 @@ paths: schema: $ref: '#/components/schemas/InternalServerErrorDTO' tags: *ref_0 - /entities/nfts: + /entities/label-documents: get: - operationId: EntityApi_getNFTs - summary: Get NFTs - description: Returns NFTs + operationId: EntityApi_getLabelDocuments + summary: Get Label Documents + description: Returns Label Documents parameters: - name: pageIndex required: false @@ -1483,16 +1404,48 @@ paths: description: Descending ordering schema: type: string - - name: tokenId + - name: keywords required: false in: query - description: Token identifier + description: Keywords to search + examples: + 0.0.1960: + description: Search VPs, which are related to specific topic identifier + value: '["0.0.1960"]' + schema: + type: string + - name: topicId + required: false + in: query + description: Topic identifier example: 0.0.1960 schema: type: string + - name: options.issuer + required: false + in: query + description: Issuer + example: >- + did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265 + schema: + type: string + - name: analytics.policyId + required: false + in: query + description: Policy identifier + example: '1706823227.586179534' + schema: + type: string + - name: analytics.schemaIds + required: false + in: query + description: Schema identifier + example: '1706823227.586179534' + schema: + type: string responses: '200': - description: NFTs + description: Label Documents content: application/json: schema: @@ -1502,41 +1455,7 @@ paths: items: type: array items: - $ref: '#/components/schemas/NFTDTO' - '500': - description: Internal server error - content: - application/json: - schema: - $ref: '#/components/schemas/InternalServerErrorDTO' - tags: *ref_0 - /entities/nfts/{tokenId}/{serialNumber}: - get: - operationId: EntityApi_getNFT - summary: Get NFT - description: Returns NFT - parameters: - - name: tokenId - required: true - in: path - description: Token identifier - example: 0.0.1960 - schema: - type: string - - name: serialNumber - required: true - in: path - description: Serial number - example: '1' - schema: - type: string - responses: - '200': - description: NFT details - content: - application/json: - schema: - $ref: '#/components/schemas/NFTDetailsDTO' + $ref: '#/components/schemas/VPGridDTO' '500': description: Internal server error content: @@ -1544,11 +1463,11 @@ paths: schema: $ref: '#/components/schemas/InternalServerErrorDTO' tags: *ref_0 - /entities/topics: + /entities/did-documents: get: - operationId: EntityApi_getTopics - summary: Get topics - description: Returns topics + operationId: EntityApi_getDidDocuments + summary: Get DIDs + description: Returns DIDs parameters: - name: pageIndex required: false @@ -1591,14 +1510,571 @@ paths: description: Keywords to search examples: 0.0.1960: - description: Search topics, which are related to specific topic identifier + description: Search DIDs, which are related to specific topic identifier value: '["0.0.1960"]' schema: type: string - - name: options.parentId + - name: topicId required: false in: query - description: Parent topic identifier + description: Topic identifier + example: 0.0.1960 + schema: + type: string + - name: options.did + required: false + in: query + description: DID + example: >- + did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265 + schema: + type: string + responses: + '200': + description: DIDs + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PageDTO' + - properties: + items: + type: array + items: + $ref: '#/components/schemas/DIDGridDTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_0 + /entities/did-documents/{messageId}: + get: + operationId: EntityApi_getDidDocument + summary: Get DID + description: Returns DID + parameters: + - name: messageId + required: true + in: path + description: Message identifier + example: '1706823227.586179534' + schema: + type: string + responses: + '200': + description: DID details + content: + application/json: + schema: + $ref: '#/components/schemas/DIDDetailsDTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_0 + /entities/did-documents/{messageId}/relationships: + get: + operationId: EntityApi_getDidRelationships + summary: Get DID relationships + description: Returns DID relationships + parameters: + - name: messageId + required: true + in: path + description: Message identifier + example: '1706823227.586179534' + schema: + type: string + responses: + '200': + description: DID relationships + content: + application/json: + schema: + $ref: '#/components/schemas/RelationshipsDTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_0 + /entities/vp-documents: + get: + operationId: EntityApi_getVpDocuments + summary: Get VPs + description: Returns VPs + parameters: + - name: pageIndex + required: false + in: query + description: Page index + example: 0 + schema: + type: number + - name: pageSize + required: false + in: query + description: Page size + example: 10 + schema: + type: number + maximum: 100 + - name: orderField + required: false + in: query + description: Order field + example: consensusTimestamp + schema: + type: string + - name: orderDir + required: false + in: query + description: Order direction + examples: + ASC: + value: ASC + description: Ascending ordering + DESC: + value: DESC + description: Descending ordering + schema: + type: string + - name: keywords + required: false + in: query + description: Keywords to search + examples: + 0.0.1960: + description: Search VPs, which are related to specific topic identifier + value: '["0.0.1960"]' + schema: + type: string + - name: topicId + required: false + in: query + description: Topic identifier + example: 0.0.1960 + schema: + type: string + - name: options.issuer + required: false + in: query + description: Issuer + example: >- + did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265 + schema: + type: string + - name: analytics.policyId + required: false + in: query + description: Policy identifier + example: '1706823227.586179534' + schema: + type: string + - name: analytics.schemaIds + required: false + in: query + description: Schema identifier + example: '1706823227.586179534' + schema: + type: string + responses: + '200': + description: VPs + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PageDTO' + - properties: + items: + type: array + items: + $ref: '#/components/schemas/VPGridDTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_0 + /entities/vp-documents/{messageId}: + get: + operationId: EntityApi_getVpDocument + summary: Get VP + description: Returns VP + parameters: + - name: messageId + required: true + in: path + description: Message identifier + example: '1706823227.586179534' + schema: + type: string + responses: + '200': + description: VP details + content: + application/json: + schema: + $ref: '#/components/schemas/VPDetailsDTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_0 + /entities/vp-documents/{messageId}/relationships: + get: + operationId: EntityApi_getVpRelationships + summary: Get VP relationships + description: Returns VP relationships + parameters: + - name: messageId + required: true + in: path + description: Message identifier + example: '1706823227.586179534' + schema: + type: string + responses: + '200': + description: VP relationships + content: + application/json: + schema: + $ref: '#/components/schemas/RelationshipsDTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_0 + /entities/vc-documents: + get: + operationId: EntityApi_getVcDocuments + summary: Get VCs + description: Returns VCs + parameters: + - name: pageIndex + required: false + in: query + description: Page index + example: 0 + schema: + type: number + - name: pageSize + required: false + in: query + description: Page size + example: 10 + schema: + type: number + maximum: 100 + - name: orderField + required: false + in: query + description: Order field + example: consensusTimestamp + schema: + type: string + - name: orderDir + required: false + in: query + description: Order direction + examples: + ASC: + value: ASC + description: Ascending ordering + DESC: + value: DESC + description: Descending ordering + schema: + type: string + - name: keywords + required: false + in: query + description: Keywords to search + examples: + 0.0.1960: + description: Search VCs, which are related to specific topic identifier + value: '["0.0.1960"]' + schema: + type: string + - name: topicId + required: false + in: query + description: Topic identifier + example: 0.0.1960 + schema: + type: string + - name: options.issuer + required: false + in: query + description: Issuer + example: >- + did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265 + schema: + type: string + - name: analytics.policyId + required: false + in: query + description: Policy identifier + example: '1706823227.586179534' + schema: + type: string + - name: analytics.schemaId + required: false + in: query + description: Schema identifier + example: '1706823227.586179534' + schema: + type: string + - name: options.relationships + required: false + in: query + description: Relationships + example: '1706823227.586179534' + schema: + type: string + responses: + '200': + description: VCs + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PageDTO' + - properties: + items: + type: array + items: + $ref: '#/components/schemas/VCGridDTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_0 + /entities/vc-documents/{messageId}: + get: + operationId: EntityApi_getVcDocument + summary: Get VC + description: Returns VC + parameters: + - name: messageId + required: true + in: path + description: Message identifier + example: '1706823227.586179534' + schema: + type: string + responses: + '200': + description: VC details + content: + application/json: + schema: + $ref: '#/components/schemas/VCDetailsDTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_0 + /entities/vc-documents/{messageId}/relationships: + get: + operationId: EntityApi_getVcRelationships + summary: Get VC relationships + description: Returns VC relationships + parameters: + - name: messageId + required: true + in: path + description: Message identifier + example: '1706823227.586179534' + schema: + type: string + responses: + '200': + description: VC relationships + content: + application/json: + schema: + $ref: '#/components/schemas/RelationshipsDTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_0 + /entities/nfts: + get: + operationId: EntityApi_getNFTs + summary: Get NFTs + description: Returns NFTs + parameters: + - name: pageIndex + required: false + in: query + description: Page index + example: 0 + schema: + type: number + - name: pageSize + required: false + in: query + description: Page size + example: 10 + schema: + type: number + maximum: 100 + - name: orderField + required: false + in: query + description: Order field + example: consensusTimestamp + schema: + type: string + - name: orderDir + required: false + in: query + description: Order direction + examples: + ASC: + value: ASC + description: Ascending ordering + DESC: + value: DESC + description: Descending ordering + schema: + type: string + - name: tokenId + required: false + in: query + description: Token identifier + example: 0.0.1960 + schema: + type: string + responses: + '200': + description: NFTs + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PageDTO' + - properties: + items: + type: array + items: + $ref: '#/components/schemas/NFTDTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_0 + /entities/nfts/{tokenId}/{serialNumber}: + get: + operationId: EntityApi_getNFT + summary: Get NFT + description: Returns NFT + parameters: + - name: tokenId + required: true + in: path + description: Token identifier + example: 0.0.1960 + schema: + type: string + - name: serialNumber + required: true + in: path + description: Serial number + example: '1' + schema: + type: string + responses: + '200': + description: NFT details + content: + application/json: + schema: + $ref: '#/components/schemas/NFTDetailsDTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_0 + /entities/topics: + get: + operationId: EntityApi_getTopics + summary: Get topics + description: Returns topics + parameters: + - name: pageIndex + required: false + in: query + description: Page index + example: 0 + schema: + type: number + - name: pageSize + required: false + in: query + description: Page size + example: 10 + schema: + type: number + maximum: 100 + - name: orderField + required: false + in: query + description: Order field + example: consensusTimestamp + schema: + type: string + - name: orderDir + required: false + in: query + description: Order direction + examples: + ASC: + value: ASC + description: Ascending ordering + DESC: + value: DESC + description: Descending ordering + schema: + type: string + - name: keywords + required: false + in: query + description: Keywords to search + examples: + 0.0.1960: + description: Search topics, which are related to specific topic identifier + value: '["0.0.1960"]' + schema: + type: string + - name: options.parentId + required: false + in: query + description: Parent topic identifier example: 0.0.1960 schema: type: string @@ -2057,6 +2533,10 @@ components: - Role-Document - Synchronization Event - Contract + - Guardian-Role-Document + - User-Permissions + - Policy-Statistic + - Policy-Label example: Standard Registry action: type: string @@ -2091,6 +2571,14 @@ components: - deferred-discontinue-policy - migrate-vc-document - migrate-vp-document + - create-role + - update-role + - delete-role + - set-role + - publish-policy-statistic + - create-assessment-document + - publish-policy-label + - create-label-document example: Init options: $ref: '#/components/schemas/RegistryOptionsDTO' @@ -2357,6 +2845,10 @@ components: - Role-Document - Synchronization Event - Contract + - Guardian-Role-Document + - User-Permissions + - Policy-Statistic + - Policy-Label example: DID-Document action: type: string @@ -2391,6 +2883,14 @@ components: - deferred-discontinue-policy - migrate-vc-document - migrate-vp-document + - create-role + - update-role + - delete-role + - set-role + - publish-policy-statistic + - create-assessment-document + - publish-policy-label + - create-label-document example: create-did-document options: $ref: '#/components/schemas/RegistryUserOptionsDTO' @@ -2495,6 +2995,10 @@ components: - Role-Document - Synchronization Event - Contract + - Guardian-Role-Document + - User-Permissions + - Policy-Statistic + - Policy-Label example: DID-Document action: type: string @@ -2529,6 +3033,14 @@ components: - deferred-discontinue-policy - migrate-vc-document - migrate-vp-document + - create-role + - update-role + - delete-role + - set-role + - publish-policy-statistic + - create-assessment-document + - publish-policy-label + - create-label-document example: create-did-document options: $ref: '#/components/schemas/RegistryUserOptionsDTO' @@ -2811,6 +3323,10 @@ components: - Role-Document - Synchronization Event - Contract + - Guardian-Role-Document + - User-Permissions + - Policy-Statistic + - Policy-Label example: Policy action: type: string @@ -2845,6 +3361,14 @@ components: - deferred-discontinue-policy - migrate-vc-document - migrate-vp-document + - create-role + - update-role + - delete-role + - set-role + - publish-policy-statistic + - create-assessment-document + - publish-policy-label + - create-label-document example: publish-policy options: $ref: '#/components/schemas/PolicyOptionsDTO' @@ -3044,6 +3568,10 @@ components: - Role-Document - Synchronization Event - Contract + - Guardian-Role-Document + - User-Permissions + - Policy-Statistic + - Policy-Label example: Tool action: type: string @@ -3078,6 +3606,14 @@ components: - deferred-discontinue-policy - migrate-vc-document - migrate-vp-document + - create-role + - update-role + - delete-role + - set-role + - publish-policy-statistic + - create-assessment-document + - publish-policy-label + - create-label-document example: publish-tool options: $ref: '#/components/schemas/ToolOptionsDTO' @@ -3257,6 +3793,10 @@ components: - Role-Document - Synchronization Event - Contract + - Guardian-Role-Document + - User-Permissions + - Policy-Statistic + - Policy-Label example: Module action: type: string @@ -3291,11 +3831,235 @@ components: - deferred-discontinue-policy - migrate-vc-document - migrate-vp-document + - create-role + - update-role + - delete-role + - set-role + - publish-policy-statistic + - create-assessment-document + - publish-policy-label + - create-label-document example: publish-module options: - $ref: '#/components/schemas/ModuleOptionsDTO' - analytics: - $ref: '#/components/schemas/ModuleAnalyticsDTO' + $ref: '#/components/schemas/ModuleOptionsDTO' + analytics: + $ref: '#/components/schemas/ModuleAnalyticsDTO' + required: + - id + - topicId + - consensusTimestamp + - owner + - uuid + - status + - statusReason + - lang + - responseType + - statusMessage + - files + - topics + - tokens + - type + - action + - options + - analytics + ModuleDetailsDTO: + type: object + properties: + id: + type: string + description: Message identifier + example: '1706823227.586179534' + uuid: + type: string + description: UUID + example: 93938a10-d032-4a9b-9425-092e58bffbf7 + item: + $ref: '#/components/schemas/ModuleDTO' + row: + $ref: '#/components/schemas/RawMessageDTO' + required: + - id + - uuid + - item + - row + SchemaOptionsDTO: + type: object + properties: + name: + type: string + description: Name + example: Monitoring report + description: + type: string + description: Description + example: Monitoring report schema + entity: + type: string + description: Entity + example: VC + owner: + type: string + description: Owner + example: >- + did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265 + uuid: + type: string + description: UUID + example: 93938a10-d032-4a9b-9425-092e58bffbf7 + version: + type: string + description: Version + example: 1.0.0 + codeVersion: + type: string + description: Code version + example: 1.0.0 + relationships: + description: Relationships + example: + - '1706823227.586179534' + type: array + items: + type: string + required: + - name + - description + - entity + - owner + - uuid + - version + - codeVersion + - relationships + SchemaGridDTO: + type: object + properties: + id: + type: string + description: Identifier + example: 667c240639282050117a1985 + topicId: + type: string + description: Topic identifier + example: 0.0.4481265 + consensusTimestamp: + type: string + description: Message identifier + example: '1706823227.586179534' + owner: + type: string + description: Owner + example: 0.0.1 + uuid: + type: string + description: UUID + example: 93938a10-d032-4a9b-9425-092e58bffbf7 + status: + type: string + description: Status + example: NEW + statusReason: + type: string + description: Status + example: Revoked + lang: + type: string + description: Lang + example: en-US + responseType: + type: string + description: Response type + example: str + statusMessage: + type: string + description: Status message + files: + description: Files + example: *ref_2 + type: array + items: + type: string + topics: + description: Topics + example: *ref_3 + type: array + items: + type: string + tokens: + description: Tokens + example: *ref_4 + type: array + items: + type: string + type: + type: string + description: Type + enum: + - EVC-Document + - VC-Document + - DID-Document + - Schema + - schema-document + - Policy + - Instance-Policy + - VP-Document + - Standard Registry + - Topic + - Token + - Module + - Tool + - Tag + - Role-Document + - Synchronization Event + - Contract + - Guardian-Role-Document + - User-Permissions + - Policy-Statistic + - Policy-Label + example: Schema + action: + type: string + description: Action + enum: + - create-did-document + - create-vc-document + - create-policy + - publish-policy + - delete-policy + - create-schema + - publish-schema + - delete-schema + - create-topic + - create-vp-document + - publish-system-schema + - Init + - change-message-status + - revoke-document + - delete-document + - token-issue + - create-token + - create-multi-policy + - mint + - publish-module + - publish-tag + - delete-tag + - publish-tool + - create-tool + - create-contract + - discontinue-policy + - deferred-discontinue-policy + - migrate-vc-document + - migrate-vp-document + - create-role + - update-role + - delete-role + - set-role + - publish-policy-statistic + - create-assessment-document + - publish-policy-label + - create-label-document + example: publish-schema + options: + $ref: '#/components/schemas/SchemaOptionsDTO' required: - id - topicId @@ -3313,76 +4077,48 @@ components: - type - action - options - - analytics - ModuleDetailsDTO: + ChildSchemaDTO: type: object properties: id: type: string description: Message identifier example: '1706823227.586179534' - uuid: + name: type: string - description: UUID - example: 93938a10-d032-4a9b-9425-092e58bffbf7 - item: - $ref: '#/components/schemas/ModuleDTO' - row: - $ref: '#/components/schemas/RawMessageDTO' + description: Name + example: Project Details required: - id - - uuid - - item - - row - SchemaOptionsDTO: + - name + SchemaAnalyticsDTO: type: object properties: - name: - type: string - description: Name - example: Monitoring report - description: - type: string - description: Description - example: Monitoring report schema - entity: - type: string - description: Entity - example: VC - owner: - type: string - description: Owner - example: >- - did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265 - uuid: - type: string - description: UUID - example: 93938a10-d032-4a9b-9425-092e58bffbf7 - version: - type: string - description: Version - example: 1.0.0 - codeVersion: - type: string - description: Code version - example: 1.0.0 - relationships: - description: Relationships + policyIds: + description: Policy message identifiers example: - '1706823227.586179534' type: array items: type: string + childSchemas: + $ref: '#/components/schemas/ChildSchemaDTO' + properties: + description: Schema properties + example: + - ActivityImpactModule.projectScope + type: array + items: + type: string + textSearch: + type: string + description: Text search required: - - name - - description - - entity - - owner - - uuid - - version - - codeVersion - - relationships - SchemaGridDTO: + - policyIds + - childSchemas + - properties + - textSearch + SchemaDetailsItemDTO: type: object properties: id: @@ -3463,6 +4199,10 @@ components: - Role-Document - Synchronization Event - Contract + - Guardian-Role-Document + - User-Permissions + - Policy-Statistic + - Policy-Label example: Schema action: type: string @@ -3497,9 +4237,32 @@ components: - deferred-discontinue-policy - migrate-vc-document - migrate-vp-document + - create-role + - update-role + - delete-role + - set-role + - publish-policy-statistic + - create-assessment-document + - publish-policy-label + - create-label-document example: publish-schema options: $ref: '#/components/schemas/SchemaOptionsDTO' + analytics: + $ref: '#/components/schemas/SchemaAnalyticsDTO' + documents: + type: array + description: Documents + items: + type: string + example: + - >- + {"$id":"#d0e99e70-3511-486668e-bf6f-10041e9a0cb7669080&1.0.0","$comment":"{ + \"@id\": \"#d0e99e70-3511-486668e-bf6f-10041e9a0cb7669080&1.0.0\", + \"term\": \"d0e99e70-3511-486668e-bf6f-10041e9a0cb7669080&1.0.0\" + }","title":"tagSchemaAPI339404","description":"tagSchemaAPI339404","type":"object","properties":{"@context":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"readOnly":true},"type":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"readOnly":true},"id":{"type":"string","readOnly":true}},"required":["@context","type"],"additionalProperties":false,"$defs":{}} + - >- + {"@context":{"@version":1.1,"@vocab":"https://w3id.org/traceability/#undefinedTerm","id":"@id","type":"@type","d0e99e70-3511-486668e-bf6f-10041e9a0cb7669080&1.0.0":{"@id":"#d0e99e70-3511-486668e-bf6f-10041e9a0cb7669080&1.0.0","@context":{}}}} required: - id - topicId @@ -3517,48 +4280,236 @@ components: - type - action - options - ChildSchemaDTO: + - analytics + - documents + SchemaActivityDTO: + type: object + properties: + vcs: + type: number + description: VCs + example: 10 + vps: + type: number + description: VPs + example: 10 + required: + - vcs + - vps + SchemaDetailsDTO: + type: object + properties: + id: + type: string + description: Message identifier + example: '1706823227.586179534' + uuid: + type: string + description: UUID + example: 93938a10-d032-4a9b-9425-092e58bffbf7 + item: + $ref: '#/components/schemas/SchemaDetailsItemDTO' + row: + $ref: '#/components/schemas/RawMessageDTO' + activity: + $ref: '#/components/schemas/SchemaActivityDTO' + required: + - id + - uuid + - item + - row + - activity + SchemaTreeNodeDataDTO: + type: object + properties: + id: + type: string + description: Message identifier + example: '1706823227.586179534' + color: + type: string + description: Color + example: '#FFFFFF' + required: + - id + - color + SchemaTreeNodeDTO: + type: object + properties: + label: + type: string + description: Label + example: Monitoring Report + expanded: + type: boolean + description: Expanded + example: true + data: + $ref: '#/components/schemas/SchemaTreeNodeDataDTO' + children: + type: object + description: Schema tree node children + required: + - label + - expanded + - data + - children + SchemaTreeDTO: type: object properties: id: type: string description: Message identifier example: '1706823227.586179534' + item: + $ref: '#/components/schemas/SchemaGridDTO' + root: + $ref: '#/components/schemas/SchemaTreeNodeDTO' + required: + - id + - item + - root + TokenDTO: + type: object + properties: + id: + type: string + description: Identifier + example: 667c240639282050117a1985 + tokenId: + type: string + description: Token identifier + example: 0.0.4481265 + status: + type: string + description: Status + example: UPDATED + lastUpdate: + type: number + description: Last update + example: 1716755852055 + serialNumber: + type: number + description: Serial number + example: 1 + hasNext: + type: boolean + description: Has next + example: false name: type: string description: Name - example: Project Details + example: iRec Token + symbol: + type: string + description: Symbol + example: iRec + type: + type: string + description: Symbol + enum: + - NON_FUNGIBLE_UNIQUE + - FUNGIBLE_COMMON + treasury: + type: string + description: Treasury + example: 0.0.1 + memo: + type: string + description: Memo + example: 0.0.2952745 + totalSupply: + type: object + description: Total supply + example: '77' + decimals: + type: string + description: Decimals + example: '2' + required: + - id + - tokenId + - status + - lastUpdate + - serialNumber + - hasNext + - name + - symbol + - type + - treasury + - memo + - totalSupply + - decimals + VPOptionsDTO: + type: object + properties: + issuer: + type: string + description: Issuer + example: >- + did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265 + relationships: + description: Relationships + example: + - '1706823227.586179534' + type: array + items: + type: string required: - - id - - name - SchemaAnalyticsDTO: + - issuer + - relationships + VPAnalyticsDTO: type: object properties: - policyIds: - description: Policy message identifiers + schemaIds: + description: Schema message identifiers example: - '1706823227.586179534' type: array items: type: string - childSchemas: - $ref: '#/components/schemas/ChildSchemaDTO' - properties: - description: Schema properties + schemaNames: + description: Schema names example: - - ActivityImpactModule.projectScope + - Monitoring Report type: array items: type: string + policyId: + type: string + description: Policy message identifier + example: '1706823227.586179534' textSearch: type: string description: Text search + issuer: + type: string + description: Document issuer + tokenId: + type: string + description: Token ID + tokenAmount: + type: string + description: Token amount + labelName: + type: string + description: Label name + labels: + description: Label IDs + type: array + items: + type: string required: - - policyIds - - childSchemas - - properties + - schemaIds + - schemaNames + - policyId - textSearch - SchemaDetailsItemDTO: + - issuer + - tokenId + - tokenAmount + - labelName + - labels + VPDetailsItemDTO: type: object properties: id: @@ -3639,7 +4590,11 @@ components: - Role-Document - Synchronization Event - Contract - example: Schema + - Guardian-Role-Document + - User-Permissions + - Policy-Statistic + - Policy-Label + example: VP-Document action: type: string description: Action @@ -3673,11 +4628,19 @@ components: - deferred-discontinue-policy - migrate-vc-document - migrate-vp-document - example: publish-schema + - create-role + - update-role + - delete-role + - set-role + - publish-policy-statistic + - create-assessment-document + - publish-policy-label + - create-label-document + example: create-vp-document options: - $ref: '#/components/schemas/SchemaOptionsDTO' + $ref: '#/components/schemas/VPOptionsDTO' analytics: - $ref: '#/components/schemas/SchemaAnalyticsDTO' + $ref: '#/components/schemas/VPAnalyticsDTO' documents: type: array description: Documents @@ -3685,12 +4648,7 @@ components: type: string example: - >- - {"$id":"#d0e99e70-3511-486668e-bf6f-10041e9a0cb7669080&1.0.0","$comment":"{ - \"@id\": \"#d0e99e70-3511-486668e-bf6f-10041e9a0cb7669080&1.0.0\", - \"term\": \"d0e99e70-3511-486668e-bf6f-10041e9a0cb7669080&1.0.0\" - }","title":"tagSchemaAPI339404","description":"tagSchemaAPI339404","type":"object","properties":{"@context":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"readOnly":true},"type":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"readOnly":true},"id":{"type":"string","readOnly":true}},"required":["@context","type"],"additionalProperties":false,"$defs":{}} - - >- - {"@context":{"@version":1.1,"@vocab":"https://w3id.org/traceability/#undefinedTerm","id":"@id","type":"@type","d0e99e70-3511-486668e-bf6f-10041e9a0cb7669080&1.0.0":{"@id":"#d0e99e70-3511-486668e-bf6f-10041e9a0cb7669080&1.0.0","@context":{}}}} + "{"id":"urn:uuid:2c374b67-fda5-4023-97c2-c0782624573f","type":["VerifiablePresentation"],"@context":["https://www.w3.org/2018/credentials/v1"],"verifiableCredential":[{"id":"urn:uuid:ff0aecbd-d358-4e5b-b99b-7a87ba38a3b2","type":["VerifiableCredential"],"issuer":"did:hedera:testnet:C5YaWT128KGmtivag99VbSeKrzxP8P8H7FbL2KQ9VQEB_0.0.1533438","issuanceDate":"2024-02-06T05:40:37.795Z","@context":["https://www.w3.org/2018/credentials/v1","ipfs://bafkreib6arvz7hltf2yqoyb7iqlkrojur7lqqcsuuhvcfvyrtkncm6pqhi"],"credentialSubject":[{"finalMintAmount":5,"policyId":"65bc691d2ae9d0f1ef2db3bc","ref":"urn:uuid:11b1ad6f-8b4f-4d61-a63a-cc9e6532625f","@context":["ipfs://bafkreib6arvz7hltf2yqoyb7iqlkrojur7lqqcsuuhvcfvyrtkncm6pqhi"],"id":"urn:uuid:5d253a1d-456a-4fb1-8b45-257e1db2e668","type":"601a68c4-66c3-407c-bc88-1b5841e6d1da&1.0.0"}],"proof":{"type":"Ed25519Signature2018","created":"2024-02-06T05:40:37Z","verificationMethod":"did:hedera:testnet:C5YaWT128KGmtivag99VbSeKrzxP8P8H7FbL2KQ9VQEB_0.0.1533438#did-root-key","proofPurpose":"assertionMethod","jws":"eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..UvTeKVUVUxH1SFNNoyu_VXf4kFqDIFzFPJaaq5adiSHrBePLQMQv7dM_Fq23z7UGHSmXlodBen1Ujcdi-am5DQ"}},{"id":"urn:uuid:b76fbd72-48b7-45fb-b152-7ec13d11eafb","type":["VerifiableCredential"],"issuer":"did:hedera:testnet:C5YaWT128KGmtivag99VbSeKrzxP8P8H7FbL2KQ9VQEB_0.0.1533438","issuanceDate":"2024-02-06T05:40:45.066Z","@context":["https://www.w3.org/2018/credentials/v1","ipfs://bafkreigd6nhj5auxobzu4qzlakzcaizh6wux2gq43qft4rwpri7msn2geu"],"credentialSubject":[{"date":"2024-02-06T05:40:45.021Z","tokenId":"0.0.1621155","amount":"5","@context":["ipfs://bafkreigd6nhj5auxobzu4qzlakzcaizh6wux2gq43qft4rwpri7msn2geu"],"type":"MintToken"}],"proof":{"type":"Ed25519Signature2018","created":"2024-02-06T05:40:45Z","verificationMethod":"did:hedera:testnet:C5YaWT128KGmtivag99VbSeKrzxP8P8H7FbL2KQ9VQEB_0.0.1533438#did-root-key","proofPurpose":"assertionMethod","jws":"eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..5hWYO3NA0Q9zI0oS1lLOpofQI-DTQVM0sd4GUQV-UUSlBug3EgYYBm7247LCzlCRt9VpYsUh7SxIrsgHzsSRDA"}}],"proof":{"type":"Ed25519Signature2018","created":"2024-02-06T05:40:45Z","verificationMethod":"did:hedera:testnet:C5YaWT128KGmtivag99VbSeKrzxP8P8H7FbL2KQ9VQEB_0.0.1533438#did-root-key","proofPurpose":"authentication","challenge":"123","jws":"eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..Mu5BaQ34idnqG6d-aMqufQOXcuWHMkv6N9Z2zhBi9Yfd7jU9FFkwi-Xjyf-Kastr7vVWBNLwGxB-bPRf4UEHAg"}}" required: - id - topicId @@ -3710,210 +4668,277 @@ components: - options - analytics - documents - SchemaActivityDTO: - type: object - properties: - vcs: - type: number - description: VCs - example: 10 - vps: - type: number - description: VPs - example: 10 - required: - - vcs - - vps - SchemaDetailsDTO: + TokenDetailsDTO: type: object properties: id: type: string description: Message identifier example: '1706823227.586179534' - uuid: - type: string - description: UUID - example: 93938a10-d032-4a9b-9425-092e58bffbf7 - item: - $ref: '#/components/schemas/SchemaDetailsItemDTO' row: - $ref: '#/components/schemas/RawMessageDTO' - activity: - $ref: '#/components/schemas/SchemaActivityDTO' + $ref: '#/components/schemas/TokenDTO' + labels: + type: array + items: + $ref: '#/components/schemas/VPDetailsItemDTO' required: - id - - uuid - - item - row - - activity - SchemaTreeNodeDataDTO: + - labels + RoleOptionsDTO: type: object properties: - id: + role: type: string - description: Message identifier - example: '1706823227.586179534' - color: + description: Role + example: Registrant + group: type: string - description: Color - example: '#FFFFFF' - required: - - id - - color - SchemaTreeNodeDTO: - type: object - properties: - label: + description: Role + example: Registrants + issuer: type: string - description: Label - example: Monitoring Report - expanded: - type: boolean - description: Expanded - example: true - data: - $ref: '#/components/schemas/SchemaTreeNodeDataDTO' - children: - type: object - description: Schema tree node children + description: Issuer + example: >- + did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265 required: - - label - - expanded - - data - - children - SchemaTreeDTO: + - role + - group + - issuer + RoleAnalyticsDTO: type: object properties: - id: + policyId: type: string - description: Message identifier + description: Policy message identifier example: '1706823227.586179534' - item: - $ref: '#/components/schemas/SchemaGridDTO' - root: - $ref: '#/components/schemas/SchemaTreeNodeDTO' + textSearch: + type: string + description: Text search required: - - id - - item - - root - TokenDTO: + - policyId + - textSearch + RoleDTO: type: object properties: id: type: string description: Identifier example: 667c240639282050117a1985 - tokenId: + topicId: type: string - description: Token identifier + description: Topic identifier example: 0.0.4481265 + consensusTimestamp: + type: string + description: Message identifier + example: '1706823227.586179534' + owner: + type: string + description: Owner + example: 0.0.1 + uuid: + type: string + description: UUID + example: 93938a10-d032-4a9b-9425-092e58bffbf7 status: type: string description: Status - example: UPDATED - lastUpdate: - type: number - description: Last update - example: 1716755852055 - serialNumber: - type: number - description: Serial number - example: 1 - hasNext: - type: boolean - description: Has next - example: false - name: + example: NEW + statusReason: type: string - description: Name - example: iRec Token - symbol: + description: Status + example: Revoked + lang: type: string - description: Symbol - example: iRec - type: + description: Lang + example: en-US + responseType: type: string - description: Symbol - enum: - - NON_FUNGIBLE_UNIQUE - - FUNGIBLE_COMMON - treasury: + description: Response type + example: str + statusMessage: type: string - description: Treasury - example: 0.0.1 - memo: + description: Status message + files: + description: Files + example: *ref_2 + type: array + items: + type: string + topics: + description: Topics + example: *ref_3 + type: array + items: + type: string + tokens: + description: Tokens + example: *ref_4 + type: array + items: + type: string + type: type: string - description: Memo - example: 0.0.2952745 - totalSupply: - type: object - description: Total supply - example: '77' - decimals: + description: Type + enum: + - EVC-Document + - VC-Document + - DID-Document + - Schema + - schema-document + - Policy + - Instance-Policy + - VP-Document + - Standard Registry + - Topic + - Token + - Module + - Tool + - Tag + - Role-Document + - Synchronization Event + - Contract + - Guardian-Role-Document + - User-Permissions + - Policy-Statistic + - Policy-Label + example: Role-Document + action: type: string - description: Decimals - example: '2' + description: Action + enum: + - create-did-document + - create-vc-document + - create-policy + - publish-policy + - delete-policy + - create-schema + - publish-schema + - delete-schema + - create-topic + - create-vp-document + - publish-system-schema + - Init + - change-message-status + - revoke-document + - delete-document + - token-issue + - create-token + - create-multi-policy + - mint + - publish-module + - publish-tag + - delete-tag + - publish-tool + - create-tool + - create-contract + - discontinue-policy + - deferred-discontinue-policy + - migrate-vc-document + - migrate-vp-document + - create-role + - update-role + - delete-role + - set-role + - publish-policy-statistic + - create-assessment-document + - publish-policy-label + - create-label-document + example: create-vc-document + options: + $ref: '#/components/schemas/RoleOptionsDTO' + analytics: + $ref: '#/components/schemas/RoleAnalyticsDTO' required: - id - - tokenId + - topicId + - consensusTimestamp + - owner + - uuid - status - - lastUpdate - - serialNumber - - hasNext - - name - - symbol + - statusReason + - lang + - responseType + - statusMessage + - files + - topics + - tokens - type - - treasury - - memo - - totalSupply - - decimals - TokenDetailsDTO: + - action + - options + - analytics + RoleActivityDTO: + type: object + properties: + vcs: + type: number + description: VCs + example: 10 + required: + - vcs + RoleDetailsDTO: type: object properties: id: type: string description: Message identifier example: '1706823227.586179534' + uuid: + type: string + description: UUID + example: 93938a10-d032-4a9b-9425-092e58bffbf7 + item: + $ref: '#/components/schemas/RoleDTO' row: - $ref: '#/components/schemas/TokenDTO' + $ref: '#/components/schemas/RawMessageDTO' + activity: + $ref: '#/components/schemas/RoleActivityDTO' required: - id + - uuid + - item - row - RoleOptionsDTO: + - activity + StatisticOptionsDTO: type: object properties: - role: + uuid: type: string - description: Role - example: Registrant - group: + description: UUID + example: 93938a10-d032-4a9b-9425-092e58bffbf7 + name: type: string - description: Role - example: Registrants - issuer: + description: Name + example: Label Name + description: type: string - description: Issuer + description: Description + example: Label Description + owner: + type: string + description: Owner example: >- did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265 - required: - - role - - group - - issuer - RoleAnalyticsDTO: - type: object - properties: - policyId: + policyTopicId: type: string - description: Policy message identifier - example: '1706823227.586179534' - textSearch: + description: Policy topic identifier + example: 0.0.4481265 + policyInstanceTopicId: type: string - description: Text search + description: Policy instance topic identifier + example: 0.0.4481265 required: - - policyId - - textSearch - RoleDTO: + - uuid + - name + - description + - owner + - policyTopicId + - policyInstanceTopicId + StatisticAnalyticsDTO: + type: object + properties: {} + StatisticDTO: type: object properties: id: @@ -3994,7 +5019,11 @@ components: - Role-Document - Synchronization Event - Contract - example: Role-Document + - Guardian-Role-Document + - User-Permissions + - Policy-Statistic + - Policy-Label + example: Policy-Label action: type: string description: Action @@ -4028,11 +5057,19 @@ components: - deferred-discontinue-policy - migrate-vc-document - migrate-vp-document - example: create-vc-document + - create-role + - update-role + - delete-role + - set-role + - publish-policy-statistic + - create-assessment-document + - publish-policy-label + - create-label-document + example: publish-policy-label options: - $ref: '#/components/schemas/RoleOptionsDTO' + $ref: '#/components/schemas/StatisticOptionsDTO' analytics: - $ref: '#/components/schemas/RoleAnalyticsDTO' + $ref: '#/components/schemas/StatisticAnalyticsDTO' required: - id - topicId @@ -4051,16 +5088,21 @@ components: - action - options - analytics - RoleActivityDTO: + StatisticActivityDTO: type: object properties: + schemas: + type: number + description: Schemas + example: 10 vcs: type: number description: VCs example: 10 required: + - schemas - vcs - RoleDetailsDTO: + StatisticDetailsDTO: type: object properties: id: @@ -4072,20 +5114,25 @@ components: description: UUID example: 93938a10-d032-4a9b-9425-092e58bffbf7 item: - $ref: '#/components/schemas/RoleDTO' + $ref: '#/components/schemas/StatisticDTO' row: $ref: '#/components/schemas/RawMessageDTO' activity: - $ref: '#/components/schemas/RoleActivityDTO' + $ref: '#/components/schemas/StatisticActivityDTO' required: - id - uuid - item - row - activity - DIDOptionsDTO: + VCOptionsDTO: type: object properties: + issuer: + type: string + description: Issuer + example: >- + did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265 relationships: description: Relationships example: @@ -4093,23 +5140,42 @@ components: type: array items: type: string - did: + documentStatus: type: string - description: DID - example: >- - did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265 + description: Document status + example: Approved + encodedData: + type: boolean + description: Encoded EVC data required: + - issuer - relationships - - did - DIDAnalyticsDTO: + - documentStatus + - encodedData + VCAnalyticsDTO: type: object properties: + policyId: + type: string + description: Policy message identifier + example: '1706823227.586179534' + schemaId: + type: string + description: Schema message identifier + example: '1706823227.586179534' + schemaName: + type: string + description: Schema name + example: Monitoring Report textSearch: type: string description: Text search required: + - policyId + - schemaId + - schemaName - textSearch - DIDGridDTO: + VCGridDTO: type: object properties: id: @@ -4190,7 +5256,11 @@ components: - Role-Document - Synchronization Event - Contract - example: DID-Document + - Guardian-Role-Document + - User-Permissions + - Policy-Statistic + - Policy-Label + example: VC-Document action: type: string description: Action @@ -4224,11 +5294,19 @@ components: - deferred-discontinue-policy - migrate-vc-document - migrate-vp-document - example: create-did-document + - create-role + - update-role + - delete-role + - set-role + - publish-policy-statistic + - create-assessment-document + - publish-policy-label + - create-label-document + example: create-vc-document options: - $ref: '#/components/schemas/DIDOptionsDTO' + $ref: '#/components/schemas/VCOptionsDTO' analytics: - $ref: '#/components/schemas/DIDAnalyticsDTO' + $ref: '#/components/schemas/VCAnalyticsDTO' required: - id - topicId @@ -4247,7 +5325,60 @@ components: - action - options - analytics - DIDDetailsItemDTO: + LabelOptionsDTO: + type: object + properties: + uuid: + type: string + description: UUID + example: 93938a10-d032-4a9b-9425-092e58bffbf7 + name: + type: string + description: Name + example: Label Name + description: + type: string + description: Description + example: Label Description + owner: + type: string + description: Owner + example: >- + did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265 + policyTopicId: + type: string + description: Policy topic identifier + example: 0.0.4481265 + policyInstanceTopicId: + type: string + description: Policy instance topic identifier + example: 0.0.4481265 + required: + - uuid + - name + - description + - owner + - policyTopicId + - policyInstanceTopicId + LabelAnalyticsDTO: + type: object + properties: + textSearch: + type: string + description: Text search + owner: + type: string + description: Owner + example: >- + did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265 + config: + type: object + description: Label Config + required: + - textSearch + - owner + - config + LabelDTO: type: object properties: id: @@ -4328,7 +5459,11 @@ components: - Role-Document - Synchronization Event - Contract - example: DID-Document + - Guardian-Role-Document + - User-Permissions + - Policy-Statistic + - Policy-Label + example: Policy-Label action: type: string description: Action @@ -4362,19 +5497,19 @@ components: - deferred-discontinue-policy - migrate-vc-document - migrate-vp-document - example: create-did-document + - create-role + - update-role + - delete-role + - set-role + - publish-policy-statistic + - create-assessment-document + - publish-policy-label + - create-label-document + example: publish-policy-label options: - $ref: '#/components/schemas/DIDOptionsDTO' + $ref: '#/components/schemas/LabelOptionsDTO' analytics: - $ref: '#/components/schemas/DIDAnalyticsDTO' - documents: - type: array - description: Documents - items: - type: string - example: - - >- - "{"@context":"https://www.w3.org/ns/did/v1","id":"did:hedera:testnet:C37cfiAMHeToXMiy1V5rAVJdhd182QJRGxwsWQpu2dN2_0.0.1533438","verificationMethod":[{"id":"did:hedera:testnet:C37cfiAMHeToXMiy1V5rAVJdhd182QJRGxwsWQpu2dN2_0.0.1533438#did-root-key","type":"Ed25519VerificationKey2018","controller":"did:hedera:testnet:C37cfiAMHeToXMiy1V5rAVJdhd182QJRGxwsWQpu2dN2_0.0.1533438","publicKeyBase58":"8WkE4uKLN7i9RnzeoUJfxSH9Jw8M1yTzKk6rtwVa6uGP"},{"id":"did:hedera:testnet:C37cfiAMHeToXMiy1V5rAVJdhd182QJRGxwsWQpu2dN2_0.0.1533438#did-root-key-bbs","type":"Bls12381G2Key2020","controller":"did:hedera:testnet:C37cfiAMHeToXMiy1V5rAVJdhd182QJRGxwsWQpu2dN2_0.0.1533438","publicKeyBase58":"237NDsUq7LAmSMzE6CEBFyuz9s2sscSz2M6cn4zUKPmJ5Q6rMh6SLRGC3EDdna7vSKwHMCGjhCiLKM6qYU7ZeYKRPNnRMcadoJbSQ44SGAAiyrpmhX8aaoTZpMdHmGFVXdqC"}],"authentication":["did:hedera:testnet:C37cfiAMHeToXMiy1V5rAVJdhd182QJRGxwsWQpu2dN2_0.0.1533438#did-root-key"],"assertionMethod":["#did-root-key","#did-root-key-bbs"]}" + $ref: '#/components/schemas/LabelAnalyticsDTO' required: - id - topicId @@ -4393,8 +5528,21 @@ components: - action - options - analytics - - documents - DIDDetailsDTO: + LabelActivityDTO: + type: object + properties: + schemas: + type: number + description: Schemas + example: 10 + vps: + type: number + description: VPs + example: 10 + required: + - schemas + - vps + LabelDetailsDTO: type: object properties: id: @@ -4406,20 +5554,48 @@ components: description: UUID example: 93938a10-d032-4a9b-9425-092e58bffbf7 item: - $ref: '#/components/schemas/DIDDetailsItemDTO' + $ref: '#/components/schemas/LabelDTO' + row: + $ref: '#/components/schemas/RawMessageDTO' + activity: + $ref: '#/components/schemas/LabelActivityDTO' + required: + - id + - uuid + - item + - row + - activity + LabelDocumentDetailsDTO: + type: object + properties: + id: + type: string + description: Message identifier + example: '1706823227.586179534' + uuid: + type: string + description: UUID + example: 93938a10-d032-4a9b-9425-092e58bffbf7 + item: + $ref: '#/components/schemas/VPDetailsItemDTO' row: $ref: '#/components/schemas/RawMessageDTO' history: type: array items: - $ref: '#/components/schemas/DIDDetailsItemDTO' + $ref: '#/components/schemas/VPDetailsItemDTO' + label: + type: array + items: + $ref: '#/components/schemas/VPDetailsItemDTO' required: - id - uuid - item - row - history - MessageDTO: + - label + VPGridDTO: type: object properties: id: @@ -4479,31 +5655,6 @@ components: type: array items: type: string - required: - - id - - topicId - - consensusTimestamp - - owner - - uuid - - status - - statusReason - - lang - - responseType - - statusMessage - - files - - topics - - tokens - RelationshipDTO: - type: object - properties: - id: - type: string - description: Message identifier - example: '1706823227.586179534' - uuid: - type: string - description: UUID - example: 93938a10-d032-4a9b-9425-092e58bffbf7 type: type: string description: Type @@ -4525,91 +5676,99 @@ components: - Role-Document - Synchronization Event - Contract - category: - type: number - description: Category - example: 1 - name: + - Guardian-Role-Document + - User-Permissions + - Policy-Statistic + - Policy-Label + example: VP-Document + action: type: string - description: Name - example: Monitoring Report Document + description: Action + enum: + - create-did-document + - create-vc-document + - create-policy + - publish-policy + - delete-policy + - create-schema + - publish-schema + - delete-schema + - create-topic + - create-vp-document + - publish-system-schema + - Init + - change-message-status + - revoke-document + - delete-document + - token-issue + - create-token + - create-multi-policy + - mint + - publish-module + - publish-tag + - delete-tag + - publish-tool + - create-tool + - create-contract + - discontinue-policy + - deferred-discontinue-policy + - migrate-vc-document + - migrate-vp-document + - create-role + - update-role + - delete-role + - set-role + - publish-policy-statistic + - create-assessment-document + - publish-policy-label + - create-label-document + example: create-vp-document + options: + $ref: '#/components/schemas/VPOptionsDTO' required: - id + - topicId + - consensusTimestamp + - owner - uuid + - status + - statusReason + - lang + - responseType + - statusMessage + - files + - topics + - tokens - type - - category - - name - RelationshipLinkDTO: - type: object - properties: - source: - type: string - description: Source message identifier - example: '1706823227.586179534' - target: - type: string - description: Target message identifier - example: '1706823227.586179534' - required: - - source - - target - RelationshipsDTO: + - action + - options + DIDOptionsDTO: type: object properties: - id: - type: string - description: Message identifier - example: '1706823227.586179534' - item: - $ref: '#/components/schemas/MessageDTO' - target: - $ref: '#/components/schemas/RelationshipDTO' relationships: - type: array - items: - $ref: '#/components/schemas/RelationshipDTO' - links: - type: array - items: - $ref: '#/components/schemas/RelationshipLinkDTO' - categories: - description: Categories + description: Relationships example: - - name: Registry - - name: Policy - - name: Schema - - name: Role - - name: VC - - name: VP + - '1706823227.586179534' type: array items: type: string + did: + type: string + description: DID + example: >- + did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265 required: - - id - - item - - target - relationships - - links - - categories - VPOptionsDTO: + - did + DIDAnalyticsDTO: type: object properties: - issuer: + textSearch: type: string - description: Issuer - example: >- - did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265 - relationships: - description: Relationships - example: - - '1706823227.586179534' - type: array - items: - type: string + description: Text search required: - - issuer - - relationships - VPGridDTO: + - textSearch + DIDGridDTO: type: object properties: id: @@ -4690,7 +5849,11 @@ components: - Role-Document - Synchronization Event - Contract - example: VP-Document + - Guardian-Role-Document + - User-Permissions + - Policy-Statistic + - Policy-Label + example: DID-Document action: type: string description: Action @@ -4724,56 +5887,38 @@ components: - deferred-discontinue-policy - migrate-vc-document - migrate-vp-document - example: create-vp-document + - create-role + - update-role + - delete-role + - set-role + - publish-policy-statistic + - create-assessment-document + - publish-policy-label + - create-label-document + example: create-did-document options: - $ref: '#/components/schemas/VPOptionsDTO' - required: - - id - - topicId - - consensusTimestamp - - owner - - uuid - - status - - statusReason - - lang - - responseType - - statusMessage - - files - - topics - - tokens - - type - - action - - options - VPAnalyticsDTO: - type: object - properties: - schemaIds: - description: Schema message identifiers - example: - - '1706823227.586179534' - type: array - items: - type: string - schemaNames: - description: Schema names - example: - - Monitoring Report - type: array - items: - type: string - policyId: - type: string - description: Policy message identifier - example: '1706823227.586179534' - textSearch: - type: string - description: Text search - required: - - schemaIds - - schemaNames - - policyId - - textSearch - VPDetailsItemDTO: + $ref: '#/components/schemas/DIDOptionsDTO' + analytics: + $ref: '#/components/schemas/DIDAnalyticsDTO' + required: + - id + - topicId + - consensusTimestamp + - owner + - uuid + - status + - statusReason + - lang + - responseType + - statusMessage + - files + - topics + - tokens + - type + - action + - options + - analytics + DIDDetailsItemDTO: type: object properties: id: @@ -4854,7 +5999,11 @@ components: - Role-Document - Synchronization Event - Contract - example: VP-Document + - Guardian-Role-Document + - User-Permissions + - Policy-Statistic + - Policy-Label + example: DID-Document action: type: string description: Action @@ -4888,11 +6037,19 @@ components: - deferred-discontinue-policy - migrate-vc-document - migrate-vp-document - example: create-vp-document + - create-role + - update-role + - delete-role + - set-role + - publish-policy-statistic + - create-assessment-document + - publish-policy-label + - create-label-document + example: create-did-document options: - $ref: '#/components/schemas/VPOptionsDTO' + $ref: '#/components/schemas/DIDOptionsDTO' analytics: - $ref: '#/components/schemas/VPAnalyticsDTO' + $ref: '#/components/schemas/DIDAnalyticsDTO' documents: type: array description: Documents @@ -4900,7 +6057,7 @@ components: type: string example: - >- - "{"id":"urn:uuid:2c374b67-fda5-4023-97c2-c0782624573f","type":["VerifiablePresentation"],"@context":["https://www.w3.org/2018/credentials/v1"],"verifiableCredential":[{"id":"urn:uuid:ff0aecbd-d358-4e5b-b99b-7a87ba38a3b2","type":["VerifiableCredential"],"issuer":"did:hedera:testnet:C5YaWT128KGmtivag99VbSeKrzxP8P8H7FbL2KQ9VQEB_0.0.1533438","issuanceDate":"2024-02-06T05:40:37.795Z","@context":["https://www.w3.org/2018/credentials/v1","ipfs://bafkreib6arvz7hltf2yqoyb7iqlkrojur7lqqcsuuhvcfvyrtkncm6pqhi"],"credentialSubject":[{"finalMintAmount":5,"policyId":"65bc691d2ae9d0f1ef2db3bc","ref":"urn:uuid:11b1ad6f-8b4f-4d61-a63a-cc9e6532625f","@context":["ipfs://bafkreib6arvz7hltf2yqoyb7iqlkrojur7lqqcsuuhvcfvyrtkncm6pqhi"],"id":"urn:uuid:5d253a1d-456a-4fb1-8b45-257e1db2e668","type":"601a68c4-66c3-407c-bc88-1b5841e6d1da&1.0.0"}],"proof":{"type":"Ed25519Signature2018","created":"2024-02-06T05:40:37Z","verificationMethod":"did:hedera:testnet:C5YaWT128KGmtivag99VbSeKrzxP8P8H7FbL2KQ9VQEB_0.0.1533438#did-root-key","proofPurpose":"assertionMethod","jws":"eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..UvTeKVUVUxH1SFNNoyu_VXf4kFqDIFzFPJaaq5adiSHrBePLQMQv7dM_Fq23z7UGHSmXlodBen1Ujcdi-am5DQ"}},{"id":"urn:uuid:b76fbd72-48b7-45fb-b152-7ec13d11eafb","type":["VerifiableCredential"],"issuer":"did:hedera:testnet:C5YaWT128KGmtivag99VbSeKrzxP8P8H7FbL2KQ9VQEB_0.0.1533438","issuanceDate":"2024-02-06T05:40:45.066Z","@context":["https://www.w3.org/2018/credentials/v1","ipfs://bafkreigd6nhj5auxobzu4qzlakzcaizh6wux2gq43qft4rwpri7msn2geu"],"credentialSubject":[{"date":"2024-02-06T05:40:45.021Z","tokenId":"0.0.1621155","amount":"5","@context":["ipfs://bafkreigd6nhj5auxobzu4qzlakzcaizh6wux2gq43qft4rwpri7msn2geu"],"type":"MintToken"}],"proof":{"type":"Ed25519Signature2018","created":"2024-02-06T05:40:45Z","verificationMethod":"did:hedera:testnet:C5YaWT128KGmtivag99VbSeKrzxP8P8H7FbL2KQ9VQEB_0.0.1533438#did-root-key","proofPurpose":"assertionMethod","jws":"eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..5hWYO3NA0Q9zI0oS1lLOpofQI-DTQVM0sd4GUQV-UUSlBug3EgYYBm7247LCzlCRt9VpYsUh7SxIrsgHzsSRDA"}}],"proof":{"type":"Ed25519Signature2018","created":"2024-02-06T05:40:45Z","verificationMethod":"did:hedera:testnet:C5YaWT128KGmtivag99VbSeKrzxP8P8H7FbL2KQ9VQEB_0.0.1533438#did-root-key","proofPurpose":"authentication","challenge":"123","jws":"eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..Mu5BaQ34idnqG6d-aMqufQOXcuWHMkv6N9Z2zhBi9Yfd7jU9FFkwi-Xjyf-Kastr7vVWBNLwGxB-bPRf4UEHAg"}}" + "{"@context":"https://www.w3.org/ns/did/v1","id":"did:hedera:testnet:C37cfiAMHeToXMiy1V5rAVJdhd182QJRGxwsWQpu2dN2_0.0.1533438","verificationMethod":[{"id":"did:hedera:testnet:C37cfiAMHeToXMiy1V5rAVJdhd182QJRGxwsWQpu2dN2_0.0.1533438#did-root-key","type":"Ed25519VerificationKey2018","controller":"did:hedera:testnet:C37cfiAMHeToXMiy1V5rAVJdhd182QJRGxwsWQpu2dN2_0.0.1533438","publicKeyBase58":"8WkE4uKLN7i9RnzeoUJfxSH9Jw8M1yTzKk6rtwVa6uGP"},{"id":"did:hedera:testnet:C37cfiAMHeToXMiy1V5rAVJdhd182QJRGxwsWQpu2dN2_0.0.1533438#did-root-key-bbs","type":"Bls12381G2Key2020","controller":"did:hedera:testnet:C37cfiAMHeToXMiy1V5rAVJdhd182QJRGxwsWQpu2dN2_0.0.1533438","publicKeyBase58":"237NDsUq7LAmSMzE6CEBFyuz9s2sscSz2M6cn4zUKPmJ5Q6rMh6SLRGC3EDdna7vSKwHMCGjhCiLKM6qYU7ZeYKRPNnRMcadoJbSQ44SGAAiyrpmhX8aaoTZpMdHmGFVXdqC"}],"authentication":["did:hedera:testnet:C37cfiAMHeToXMiy1V5rAVJdhd182QJRGxwsWQpu2dN2_0.0.1533438#did-root-key"],"assertionMethod":["#did-root-key","#did-root-key-bbs"]}" required: - id - topicId @@ -4920,7 +6077,7 @@ components: - options - analytics - documents - VPDetailsDTO: + DIDDetailsDTO: type: object properties: id: @@ -4932,70 +6089,20 @@ components: description: UUID example: 93938a10-d032-4a9b-9425-092e58bffbf7 item: - $ref: '#/components/schemas/VPDetailsItemDTO' + $ref: '#/components/schemas/DIDDetailsItemDTO' row: $ref: '#/components/schemas/RawMessageDTO' history: type: array items: - $ref: '#/components/schemas/VPDetailsItemDTO' + $ref: '#/components/schemas/DIDDetailsItemDTO' required: - id - uuid - item - row - history - VCOptionsDTO: - type: object - properties: - issuer: - type: string - description: Issuer - example: >- - did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265 - relationships: - description: Relationships - example: - - '1706823227.586179534' - type: array - items: - type: string - documentStatus: - type: string - description: Document status - example: Approved - encodedData: - type: boolean - description: Encoded EVC data - required: - - issuer - - relationships - - documentStatus - - encodedData - VCAnalyticsDTO: - type: object - properties: - policyId: - type: string - description: Policy message identifier - example: '1706823227.586179534' - schemaId: - type: string - description: Schema message identifier - example: '1706823227.586179534' - schemaName: - type: string - description: Schema name - example: Monitoring Report - textSearch: - type: string - description: Text search - required: - - policyId - - schemaId - - schemaName - - textSearch - VCGridDTO: + MessageDTO: type: object properties: id: @@ -5055,6 +6162,31 @@ components: type: array items: type: string + required: + - id + - topicId + - consensusTimestamp + - owner + - uuid + - status + - statusReason + - lang + - responseType + - statusMessage + - files + - topics + - tokens + RelationshipDTO: + type: object + properties: + id: + type: string + description: Message identifier + example: '1706823227.586179534' + uuid: + type: string + description: UUID + example: 93938a10-d032-4a9b-9425-092e58bffbf7 type: type: string description: Type @@ -5076,63 +6208,106 @@ components: - Role-Document - Synchronization Event - Contract - example: VC-Document - action: + - Guardian-Role-Document + - User-Permissions + - Policy-Statistic + - Policy-Label + category: + type: number + description: Category + example: 1 + name: type: string - description: Action - enum: - - create-did-document - - create-vc-document - - create-policy - - publish-policy - - delete-policy - - create-schema - - publish-schema - - delete-schema - - create-topic - - create-vp-document - - publish-system-schema - - Init - - change-message-status - - revoke-document - - delete-document - - token-issue - - create-token - - create-multi-policy - - mint - - publish-module - - publish-tag - - delete-tag - - publish-tool - - create-tool - - create-contract - - discontinue-policy - - deferred-discontinue-policy - - migrate-vc-document - - migrate-vp-document - example: create-vc-document - options: - $ref: '#/components/schemas/VCOptionsDTO' - analytics: - $ref: '#/components/schemas/VCAnalyticsDTO' + description: Name + example: Monitoring Report Document required: - id - - topicId - - consensusTimestamp - - owner - uuid - - status - - statusReason - - lang - - responseType - - statusMessage - - files - - topics - - tokens - type - - action - - options - - analytics + - category + - name + RelationshipLinkDTO: + type: object + properties: + source: + type: string + description: Source message identifier + example: '1706823227.586179534' + target: + type: string + description: Target message identifier + example: '1706823227.586179534' + required: + - source + - target + RelationshipsDTO: + type: object + properties: + id: + type: string + description: Message identifier + example: '1706823227.586179534' + item: + $ref: '#/components/schemas/MessageDTO' + target: + $ref: '#/components/schemas/RelationshipDTO' + relationships: + type: array + items: + $ref: '#/components/schemas/RelationshipDTO' + links: + type: array + items: + $ref: '#/components/schemas/RelationshipLinkDTO' + categories: + description: Categories + example: + - name: Registry + - name: Policy + - name: Schema + - name: Role + - name: VC + - name: VP + type: array + items: + type: string + required: + - id + - item + - target + - relationships + - links + - categories + VPDetailsDTO: + type: object + properties: + id: + type: string + description: Message identifier + example: '1706823227.586179534' + uuid: + type: string + description: UUID + example: 93938a10-d032-4a9b-9425-092e58bffbf7 + item: + $ref: '#/components/schemas/VPDetailsItemDTO' + row: + $ref: '#/components/schemas/RawMessageDTO' + history: + type: array + items: + $ref: '#/components/schemas/VPDetailsItemDTO' + labels: + type: array + items: + $ref: '#/components/schemas/VPDetailsItemDTO' + required: + - id + - uuid + - item + - row + - history + - labels VCDetailsItemDTO: type: object properties: @@ -5214,6 +6389,10 @@ components: - Role-Document - Synchronization Event - Contract + - Guardian-Role-Document + - User-Permissions + - Policy-Statistic + - Policy-Label example: VC-Document action: type: string @@ -5248,6 +6427,14 @@ components: - deferred-discontinue-policy - migrate-vc-document - migrate-vp-document + - create-role + - update-role + - delete-role + - set-role + - publish-policy-statistic + - create-assessment-document + - publish-policy-label + - create-label-document example: create-vc-document options: $ref: '#/components/schemas/VCOptionsDTO' @@ -5420,10 +6607,15 @@ components: is_approval: false receiver_account_id: 0.0.1533323 sender_account_id: null + labels: + type: array + items: + $ref: '#/components/schemas/VPDetailsItemDTO' required: - id - row - history + - labels TopicOptionsDTO: type: object properties: @@ -5565,6 +6757,10 @@ components: - Role-Document - Synchronization Event - Contract + - Guardian-Role-Document + - User-Permissions + - Policy-Statistic + - Policy-Label example: Topic action: type: string @@ -5599,6 +6795,14 @@ components: - deferred-discontinue-policy - migrate-vc-document - migrate-vp-document + - create-role + - update-role + - delete-role + - set-role + - publish-policy-statistic + - create-assessment-document + - publish-policy-label + - create-label-document example: create-topic options: $ref: '#/components/schemas/TopicOptionsDTO' @@ -5854,6 +7058,10 @@ components: - Role-Document - Synchronization Event - Contract + - Guardian-Role-Document + - User-Permissions + - Policy-Statistic + - Policy-Label example: Contract action: type: string @@ -5888,6 +7096,14 @@ components: - deferred-discontinue-policy - migrate-vc-document - migrate-vp-document + - create-role + - update-role + - delete-role + - set-role + - publish-policy-statistic + - create-assessment-document + - publish-policy-label + - create-label-document example: create-contract options: $ref: '#/components/schemas/ContractOptionsDTO' diff --git a/swagger.yaml b/swagger.yaml index c3bbcec0cf..12a53a9be2 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -13033,6 +13033,707 @@ paths: tags: *ref_28 security: - bearer: [] + /policy-labels: + post: + operationId: PolicyLabelsApi_createPolicyLabel + summary: Creates a new policy label. + description: Creates a new policy label. + parameters: [] + requestBody: + required: true + description: Configuration. + content: + application/json: + schema: + $ref: '#/components/schemas/PolicyLabelDTO' + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/PolicyLabelDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: &ref_29 + - policy-labels + security: + - bearer: [] + get: + operationId: PolicyLabelsApi_getPolicyLabels + summary: Return a list of all policy labels. + description: Returns all policy labels. + parameters: + - name: pageIndex + required: false + in: query + description: >- + The number of pages to skip before starting to collect the result + set + example: 0 + schema: + type: number + - name: pageSize + required: false + in: query + description: The numbers of items to return + example: 20 + schema: + type: number + - name: policyInstanceTopicId + required: false + in: query + description: Policy Instance Topic Id + example: 0.0.1 + schema: + type: string + responses: + '200': + description: Successful operation. + headers: + X-Total-Count: + schema: + type: integer + description: Total items in the collection. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PolicyLabelDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_29 + security: + - bearer: [] + /policy-labels/{definitionId}: + get: + operationId: PolicyLabelsApi_getPolicyLabelById + summary: Retrieves policy label. + description: Retrieves policy label for the specified ID. + parameters: + - name: definitionId + required: true + in: path + description: policy label Identifier + example: '000000000000000000000001' + schema: + type: string + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/PolicyLabelDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_29 + security: + - bearer: [] + put: + operationId: PolicyLabelsApi_updatePolicyLabel + summary: Updates policy label. + description: Updates policy label configuration for the specified label ID. + parameters: + - name: definitionId + required: true + in: path + description: policy label Identifier + example: '000000000000000000000001' + schema: + type: string + requestBody: + required: true + description: Object that contains a configuration. + content: + application/json: + schema: + $ref: '#/components/schemas/PolicyLabelDTO' + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/PolicyLabelDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_29 + security: + - bearer: [] + delete: + operationId: PolicyLabelsApi_deletePolicyLabel + summary: Deletes the policy label. + description: Deletes the policy label with the provided ID. + parameters: + - name: definitionId + required: true + in: path + description: policy label Identifier + example: '000000000000000000000001' + schema: + type: string + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + type: boolean + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_29 + security: + - bearer: [] + /policy-labels/{definitionId}/publish: + put: + operationId: PolicyLabelsApi_publishPolicyLabel + summary: Publishes policy label. + description: Publishes policy label for the specified label ID. + parameters: + - name: definitionId + required: true + in: path + description: policy label Identifier + example: '000000000000000000000001' + schema: + type: string + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/PolicyLabelDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_29 + security: + - bearer: [] + /policy-labels/push/{definitionId}/publish: + put: + operationId: PolicyLabelsApi_publishPolicyLabelAsync + summary: Publishes policy label. + description: Publishes policy label for the specified label ID. + parameters: + - name: definitionId + required: true + in: path + description: policy label Identifier + example: '000000000000000000000001' + schema: + type: string + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/PolicyLabelDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_29 + security: + - bearer: [] + /policy-labels/{definitionId}/relationships: + get: + operationId: PolicyLabelsApi_getPolicyLabelRelationships + summary: Retrieves policy label relationships. + description: Retrieves policy label relationships for the specified ID. + parameters: + - name: definitionId + required: true + in: path + description: policy label Identifier + example: '000000000000000000000001' + schema: + type: string + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/PolicyLabelRelationshipsDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_29 + security: + - bearer: [] + /policy-labels/{policyId}/import/file: + post: + operationId: PolicyLabelsApi_importPolicyLabel + summary: Imports new labels from a zip file. + description: Imports new labels from the provided zip file into the local DB. + parameters: + - name: policyId + required: true + in: path + description: Policy Id + example: '000000000000000000000001' + schema: + type: string + requestBody: + required: true + description: A zip file containing labels to be imported. + content: + application/json: + schema: + type: string + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/PolicyLabelDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_29 + security: + - bearer: [] + /policy-labels/{definitionId}/export/file: + get: + operationId: PolicyLabelsApi_exportPolicyLabel + summary: Returns a zip file containing labels. + description: Returns a zip file containing labels. + parameters: + - name: definitionId + required: true + in: path + description: policy label Identifier + example: '000000000000000000000001' + schema: + type: string + responses: + '200': + description: Successful operation. Response zip file. + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_29 + security: + - bearer: [] + /policy-labels/import/file/preview: + post: + operationId: PolicyLabelsApi_previewPolicyLabel + summary: Imports a zip file containing labels. + description: Imports a zip file containing labels. + parameters: [] + requestBody: + required: true + description: File. + content: + application/json: + schema: + type: string + responses: + '200': + description: policy label preview. + content: + application/json: + schema: + $ref: '#/components/schemas/PolicyLabelDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_29 + security: + - bearer: [] + /policy-labels/components: + post: + operationId: PolicyLabelsApi_searchComponents + summary: Search labels ans statistics. + description: Return a list of other labels ans statistics. + parameters: [] + requestBody: + required: true + description: Filters. + content: + application/json: + schema: + $ref: '#/components/schemas/PolicyLabelFiltersDTO' + responses: + '200': + description: A list of labels ans statistics. + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_29 + security: + - bearer: [] + /policy-labels/{definitionId}/tokens: + get: + operationId: PolicyLabelsApi_getPolicyLabelTokens + summary: Return a list of all documents. + description: Returns all documents. + parameters: + - name: definitionId + required: true + in: path + description: policy label Identifier + example: '000000000000000000000001' + schema: + type: string + - name: pageIndex + required: false + in: query + description: >- + The number of pages to skip before starting to collect the result + set + example: 0 + schema: + type: number + - name: pageSize + required: false + in: query + description: The numbers of items to return + example: 20 + schema: + type: number + responses: + '200': + description: Successful operation. + headers: + X-Total-Count: + schema: + type: integer + description: Total items in the collection. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/VcDocumentDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_29 + security: + - bearer: [] + /policy-labels/{definitionId}/tokens/{documentId}: + get: + operationId: PolicyLabelsApi_getPolicyLabelDocument + summary: Return a list of all documents. + description: Returns all documents. + parameters: + - name: definitionId + required: true + in: path + description: policy label Identifier + example: '000000000000000000000001' + schema: + type: string + - name: documentId + required: true + in: path + description: Document Identifier + example: '000000000000000000000001' + schema: + type: string + responses: + '200': + description: Successful operation. + headers: + X-Total-Count: + schema: + type: integer + description: Total items in the collection. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/VcDocumentDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_29 + security: + - bearer: [] + /policy-labels/{definitionId}/documents: + post: + operationId: PolicyLabelsApi_createStatisticDocument + summary: Creates a new label document. + description: Creates a new label document. + parameters: + - name: definitionId + required: true + in: path + description: policy label Identifier + example: '000000000000000000000001' + schema: + type: string + requestBody: + required: true + description: Configuration. + content: + application/json: + schema: + $ref: '#/components/schemas/PolicyLabelDocumentDTO' + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/PolicyLabelDocumentDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_29 + security: + - bearer: [] + get: + operationId: PolicyLabelsApi_getLabelDocuments + summary: Return a list of all label documents. + description: Returns all label documents. + parameters: + - name: definitionId + required: true + in: path + description: policy label Identifier + example: '000000000000000000000001' + schema: + type: string + - name: pageIndex + required: false + in: query + description: >- + The number of pages to skip before starting to collect the result + set + example: 0 + schema: + type: number + - name: pageSize + required: false + in: query + description: The numbers of items to return + example: 20 + schema: + type: number + responses: + '200': + description: Successful operation. + headers: + X-Total-Count: + schema: + type: integer + description: Total items in the collection. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PolicyLabelDocumentDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_29 + security: + - bearer: [] + /policy-labels/{definitionId}/documents/{documentId}: + get: + operationId: PolicyLabelsApi_getLabelDocument + summary: Retrieves label document. + description: Retrieves label document for the specified ID. + parameters: + - name: definitionId + required: true + in: path + description: Label Definition Identifier + example: '000000000000000000000001' + schema: + type: string + - name: documentId + required: true + in: path + description: Label Document Identifier + example: '000000000000000000000001' + schema: + type: string + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/PolicyLabelDocumentDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_29 + security: + - bearer: [] + /policy-labels/{definitionId}/documents/{documentId}/relationships: + get: + operationId: PolicyLabelsApi_getStatisticAssessmentRelationships + summary: Retrieves documents relationships. + description: Retrieves documents relationships for the specified ID. + parameters: + - name: definitionId + required: true + in: path + description: Statistic Definition Identifier + example: '000000000000000000000001' + schema: + type: string + - name: documentId + required: true + in: path + description: Label Document Identifier + example: '000000000000000000000001' + schema: + type: string + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/PolicyLabelDocumentRelationshipsDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_29 + security: + - bearer: [] /worker-tasks: get: operationId: WorkerTasksController_getAllWorkerTasks @@ -13079,7 +13780,7 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerErrorDTO' - tags: &ref_29 + tags: &ref_30 - worker-tasks security: - bearer: [] @@ -13102,7 +13803,7 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerErrorDTO' - tags: *ref_29 + tags: *ref_30 security: - bearer: [] /worker-tasks/delete/{taskId}: @@ -13131,7 +13832,7 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerErrorDTO' - tags: *ref_29 + tags: *ref_30 security: - bearer: [] info: @@ -13616,19 +14317,19 @@ components: example: username role: type: string - enum: &ref_38 + enum: &ref_39 - STANDARD_REGISTRY - USER - AUDITOR example: USER permissionsGroup: - example: &ref_39 + example: &ref_40 - {} type: array items: type: string permissions: - example: &ref_40 + example: &ref_41 - POLICIES_POLICY_READ type: array items: @@ -13813,35 +14514,35 @@ components: type: object properties: idLvl: - oneOf: &ref_30 + oneOf: &ref_31 - type: string - type: number - enum: &ref_31 + enum: &ref_32 - 0 - 1 example: 0 eventsLvl: - oneOf: &ref_32 + oneOf: &ref_33 - type: string - type: number - enum: &ref_33 + enum: &ref_34 - 0 - 1 example: 0 propLvl: - oneOf: &ref_34 + oneOf: &ref_35 - type: string - type: number - enum: &ref_35 + enum: &ref_36 - 0 - 1 - 2 example: 0 childrenLvl: - oneOf: &ref_36 + oneOf: &ref_37 - type: string - type: number - enum: &ref_37 + enum: &ref_38 - 0 - 1 - 2 @@ -13905,20 +14606,20 @@ components: type: object properties: idLvl: - oneOf: *ref_30 - enum: *ref_31 + oneOf: *ref_31 + enum: *ref_32 example: 0 eventsLvl: - oneOf: *ref_32 - enum: *ref_33 + oneOf: *ref_33 + enum: *ref_34 example: 0 propLvl: - oneOf: *ref_34 - enum: *ref_35 + oneOf: *ref_35 + enum: *ref_36 example: 0 childrenLvl: - oneOf: *ref_36 - enum: *ref_37 + oneOf: *ref_37 + enum: *ref_38 example: 0 moduleId1: type: string @@ -14032,20 +14733,20 @@ components: type: object properties: idLvl: - oneOf: *ref_30 - enum: *ref_31 + oneOf: *ref_31 + enum: *ref_32 example: 0 eventsLvl: - oneOf: *ref_32 - enum: *ref_33 + oneOf: *ref_33 + enum: *ref_34 example: 0 propLvl: - oneOf: *ref_34 - enum: *ref_35 + oneOf: *ref_35 + enum: *ref_36 example: 0 childrenLvl: - oneOf: *ref_36 - enum: *ref_37 + oneOf: *ref_37 + enum: *ref_38 example: 0 documentId1: type: string @@ -14080,20 +14781,20 @@ components: type: object properties: idLvl: - oneOf: *ref_30 - enum: *ref_31 + oneOf: *ref_31 + enum: *ref_32 example: 0 eventsLvl: - oneOf: *ref_32 - enum: *ref_33 + oneOf: *ref_33 + enum: *ref_34 example: 0 propLvl: - oneOf: *ref_34 - enum: *ref_35 + oneOf: *ref_35 + enum: *ref_36 example: 0 childrenLvl: - oneOf: *ref_36 - enum: *ref_37 + oneOf: *ref_37 + enum: *ref_38 example: 0 toolId1: type: string @@ -14597,6 +15298,7 @@ components: - TAG - TOOL - STATISTIC + - LABEL example: POLICY documentURL: type: string @@ -14843,15 +15545,15 @@ components: example: username role: type: string - enum: *ref_38 + enum: *ref_39 example: USER permissionsGroup: - example: *ref_39 + example: *ref_40 type: array items: type: string permissions: - example: *ref_40 + example: *ref_41 type: array items: type: string @@ -15955,6 +16657,7 @@ components: - SCHEMAS_PAGE - TOKENS_PAGE - PROFILE_PAGE + - POLICY_LABEL_PAGE result: type: object read: @@ -16139,7 +16842,7 @@ components: #did:hedera:testnet:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA_0.0.0000001 permissions: type: string - enum: &ref_41 + enum: &ref_42 - ANALYTIC_POLICY_READ - ANALYTIC_MODULE_READ - ANALYTIC_TOOL_READ @@ -16189,6 +16892,8 @@ components: - PERMISSIONS_ROLE_MANAGE - STATISTICS_STATISTIC_READ - STATISTICS_STATISTIC_CREATE + - STATISTICS_LABEL_READ + - STATISTICS_LABEL_CREATE - SCHEMAS_RULE_READ - SCHEMAS_RULE_CREATE - SCHEMAS_RULE_EXECUTE @@ -16210,7 +16915,7 @@ components: properties: name: type: string - enum: *ref_41 + enum: *ref_42 example: ANALYTIC_POLICY_READ category: type: string @@ -16277,6 +16982,7 @@ components: - ROLE - STATISTIC - RULE + - LABEL example: POLICY action: type: string @@ -16505,6 +17211,152 @@ components: type: array items: $ref: '#/components/schemas/VcDocumentDTO' + PolicyLabelDTO: + type: object + properties: + id: + type: string + example: '000000000000000000000001' + uuid: + type: string + example: 00000000-0000-0000-0000-000000000000 + name: + type: string + example: Tool name + description: + type: string + example: Description + creator: + type: string + example: >- + #did:hedera:testnet:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA_0.0.0000001 + owner: + type: string + example: >- + #did:hedera:testnet:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA_0.0.0000001 + topicId: + type: string + example: 0.0.1 + messageId: + type: string + example: '0000000000.000000001' + policyId: + type: string + example: '000000000000000000000001' + policyTopicId: + type: string + example: 0.0.1 + policyInstanceTopicId: + type: string + example: 0.0.1 + status: + type: string + enum: + - DRAFT + - PUBLISHED + - ERROR + - ACTIVE + example: DRAFT + config: + type: object + nullable: true + required: + - name + PolicyLabelRelationshipsDTO: + type: object + properties: + policy: + $ref: '#/components/schemas/PolicyDTO' + policySchemas: + type: array + items: + $ref: '#/components/schemas/SchemaDTO' + documentsSchemas: + type: array + items: + $ref: '#/components/schemas/SchemaDTO' + PolicyLabelFiltersDTO: + type: object + properties: + text: + type: string + example: Name + owner: + type: string + example: >- + #did:hedera:testnet:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA_0.0.0000001 + components: + type: string + description: Component type + enum: + - all + - label + - statistic + example: all + PolicyLabelComponentsDTO: + type: object + properties: + statistics: + type: array + items: + $ref: '#/components/schemas/StatisticDefinitionDTO' + labels: + type: array + items: + $ref: '#/components/schemas/PolicyLabelDTO' + PolicyLabelDocumentDTO: + type: object + properties: + id: + type: string + example: '000000000000000000000001' + definitionId: + type: string + example: '000000000000000000000001' + policyId: + type: string + example: '000000000000000000000001' + policyTopicId: + type: string + example: 0.0.1 + policyInstanceTopicId: + type: string + example: 0.0.1 + topicId: + type: string + example: 0.0.1 + creator: + type: string + example: >- + #did:hedera:testnet:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA_0.0.0000001 + owner: + type: string + example: >- + #did:hedera:testnet:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA_0.0.0000001 + messageId: + type: string + example: '0000000000.000000001' + target: + type: string + example: '0000000000.000000001' + relationships: + example: + - '0000000000.000000001' + type: array + items: + type: string + document: + type: object + nullable: true + PolicyLabelDocumentRelationshipsDTO: + type: object + properties: + target: + $ref: '#/components/schemas/VpDocumentDTO' + relationships: + type: array + items: + $ref: '#/components/schemas/VcDocumentDTO' WorkersTasksDTO: type: object properties: diff --git a/yarn.lock b/yarn.lock index 9d8734948f..d7f233866e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1299,6 +1299,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.25.7": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1" + integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== + dependencies: + regenerator-runtime "^0.14.0" + "@bitauth/libauth@^1.18.1": version "1.19.1" resolved "https://registry.yarnpkg.com/@bitauth/libauth/-/libauth-1.19.1.tgz#713751bbc09815b667f8fe00a1cc5b0f3bf45dd1" @@ -1695,6 +1702,14 @@ bessel "^1.0.2" jstat "^1.9.6" +"@formulajs/formulajs@^4.4.6": + version "4.4.8" + resolved "https://registry.yarnpkg.com/@formulajs/formulajs/-/formulajs-4.4.8.tgz#69475c5b00c95d2e53d4a791d24f542f74585b73" + integrity sha512-U1xG4thAqdCVCrJgjNA/I4Bn8WvhMPIGidbm9zO1jowcXEX03eDIaYReWQIRxxTRPJeSl5nTVlzxQYDn1nL10Q== + dependencies: + bessel "^1.0.2" + jstat "^1.9.6" + "@gar/promisify@^1.1.3": version "1.1.3" resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" @@ -6343,6 +6358,11 @@ complex.js@^2.1.1: resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.1.1.tgz#0675dac8e464ec431fb2ab7d30f41d889fb25c31" integrity sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg== +complex.js@^2.2.5: + version "2.4.2" + resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.4.2.tgz#76f260a9e7e232d8ad26348484a9b128c13fcc9a" + integrity sha512-qtx7HRhPGSCBtGiST4/WGHuW+zeaND/6Ld+db6PbrulIB1i2Ev/2UPiqcmpQNPSyfBKraC0EOvOKCB5dGZKt3g== + component-emitter@^1.2.1: version "1.3.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.1.tgz#ef1d5796f7d93f135ee6fb684340b26403c97d17" @@ -6705,7 +6725,7 @@ decamelize@^4.0.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== -decimal.js@^10.3.1: +decimal.js@^10.3.1, decimal.js@^10.4.3: version "10.4.3" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== @@ -8205,7 +8225,7 @@ fr32-sha2-256-trunc254-padded-binary-tree-multihash@^3.3.0: resolved "https://registry.yarnpkg.com/fr32-sha2-256-trunc254-padded-binary-tree-multihash/-/fr32-sha2-256-trunc254-padded-binary-tree-multihash-3.3.0.tgz#30e0aaa3594ea781a2d53505570604bfcd9c7085" integrity sha512-O11VDxPmPvbQj5eac2BJXyieNacyd+RCMhwOzXQQM/NCI25x3c32YWB4/JwgOWPCpKnNXF6lpK/j0lj7GWOnYQ== -fraction.js@^4.2.0: +fraction.js@^4.2.0, fraction.js@^4.3.7: version "4.3.7" resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== @@ -11104,6 +11124,21 @@ mathjs@^10.1.0: tiny-emitter "^2.1.0" typed-function "^2.1.0" +mathjs@^13.1.1: + version "13.2.3" + resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-13.2.3.tgz#775a030684ee15a36ad29138f28707667bb623ed" + integrity sha512-I67Op0JU7gGykFK64bJexkSAmX498x0oybxfVXn1rroEMZTmfxppORhnk8mEUnPrbTfabDKCqvm18vJKMk2UJQ== + dependencies: + "@babel/runtime" "^7.25.7" + complex.js "^2.2.5" + decimal.js "^10.4.3" + escape-latex "^1.2.0" + fraction.js "^4.3.7" + javascript-natural-sort "^0.7.1" + seedrandom "^3.0.5" + tiny-emitter "^2.1.0" + typed-function "^4.2.1" + md5@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" @@ -14524,7 +14559,7 @@ string-argv@~0.3.1: resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6" integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -14542,15 +14577,6 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" @@ -14606,7 +14632,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -14620,13 +14646,6 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1: dependencies: ansi-regex "^2.0.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -15242,6 +15261,11 @@ typed-function@^2.1.0: resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-2.1.0.tgz#ded6f8a442ba8749ff3fe75bc41419c8d46ccc3f" integrity sha512-bctQIOqx2iVbWGDGPWwIm18QScpu2XRmkC19D8rQGFsjKSgteq/o1hTZvIG/wuDq8fanpBDrLkLq+aEN/6y5XQ== +typed-function@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-4.2.1.tgz#19aa51847aa2dea9ef5e7fb7641c060179a74426" + integrity sha512-EGjWssW7Tsk4DGfE+5yluuljS1OGYWiI1J6e8puZz9nTMM51Oug8CD5Zo4gWMsOhq5BI+1bF+rWTm4Vbj3ivRA== + typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" @@ -15396,6 +15420,11 @@ undici@^5.12.0: dependencies: "@fastify/busboy" "^2.0.0" +undici@^7.1.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/undici/-/undici-7.1.1.tgz#f11eceeaaaa34ff8a28da31b68b0b4a8d75562f0" + integrity sha512-WZkQ6eH9f5ZT93gaIffsbUaDpBwjbpvmMbfaEhOnbdUneurTESeRxwPGwjI28mRFESH3W3e8Togijh37ptOQqA== + union-value@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" @@ -15843,7 +15872,7 @@ workerpool@6.2.0: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.0.tgz#827d93c9ba23ee2019c3ffaff5c27fccea289e8b" integrity sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -15860,15 +15889,6 @@ wrap-ansi@^2.0.0: string-width "^1.0.1" strip-ansi "^3.0.1" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"