mirror of
https://github.com/valitydev/control-center.git
synced 2024-11-06 02:25:17 +00:00
TD-380: Create, edit, remove domain objects with forms (#128)
This commit is contained in:
parent
4e1b658e05
commit
1ebdc8b336
5
.gitignore
vendored
5
.gitignore
vendored
@ -47,6 +47,5 @@ Thumbs.db
|
|||||||
.angular
|
.angular
|
||||||
|
|
||||||
# Env and configs
|
# Env and configs
|
||||||
.env
|
.env*
|
||||||
/src/assets/appConfig.json
|
/src/assets/*Config*.json
|
||||||
/src/assets/authConfig.json
|
|
||||||
|
12
.run/App Stage Server.run.xml
Normal file
12
.run/App Stage Server.run.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="App Stage Server" type="js.build_tools.npm" activateToolWindowBeforeRun="false">
|
||||||
|
<package-json value="$PROJECT_DIR$/package.json" />
|
||||||
|
<command value="run" />
|
||||||
|
<scripts>
|
||||||
|
<script value="stage" />
|
||||||
|
</scripts>
|
||||||
|
<node-interpreter value="project" />
|
||||||
|
<envs />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
7
.run/Stage.run.xml
Normal file
7
.run/Stage.run.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Stage" type="CompoundRunConfigurationType">
|
||||||
|
<toRun name="Libs Dev Server" type="js.build_tools.npm" />
|
||||||
|
<toRun name="App Stage Server" type="js.build_tools.npm" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
@ -1,7 +1,7 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="Start" type="CompoundRunConfigurationType">
|
<configuration default="false" name="Start" type="CompoundRunConfigurationType">
|
||||||
<toRun name="App Dev Server" type="js.build_tools.npm" />
|
|
||||||
<toRun name="Libs Dev Server" type="js.build_tools.npm" />
|
<toRun name="Libs Dev Server" type="js.build_tools.npm" />
|
||||||
|
<toRun name="App Dev Server" type="js.build_tools.npm" />
|
||||||
<method v="2" />
|
<method v="2" />
|
||||||
</configuration>
|
</configuration>
|
||||||
</component>
|
</component>
|
@ -33,6 +33,14 @@
|
|||||||
npm ci
|
npm ci
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Stage
|
||||||
|
|
||||||
|
Running in stage mode needs files:
|
||||||
|
|
||||||
|
- `env.stage`
|
||||||
|
- `src/assets/appConfig.stage.json`
|
||||||
|
- `src/assets/authConfig.stage.json`
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
1. Start
|
1. Start
|
||||||
|
13
angular.json
13
angular.json
@ -26,7 +26,7 @@
|
|||||||
"short-uuid",
|
"short-uuid",
|
||||||
"js-sha256",
|
"js-sha256",
|
||||||
"jwt-decode",
|
"jwt-decode",
|
||||||
"element-resize-detector",
|
"css-element-queries",
|
||||||
"base64-js",
|
"base64-js",
|
||||||
"@vality/deanonimus-proto",
|
"@vality/deanonimus-proto",
|
||||||
"@vality/domain-proto",
|
"@vality/domain-proto",
|
||||||
@ -94,6 +94,14 @@
|
|||||||
"extractLicenses": false,
|
"extractLicenses": false,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"namedChunks": true
|
"namedChunks": true
|
||||||
|
},
|
||||||
|
"stage": {
|
||||||
|
"fileReplacements": [
|
||||||
|
{
|
||||||
|
"replace": "src/environments/environment.ts",
|
||||||
|
"with": "src/environments/environment.stage.ts"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"defaultConfiguration": "production"
|
"defaultConfiguration": "production"
|
||||||
@ -106,6 +114,9 @@
|
|||||||
},
|
},
|
||||||
"development": {
|
"development": {
|
||||||
"browserTarget": "control-center:build:development"
|
"browserTarget": "control-center:build:development"
|
||||||
|
},
|
||||||
|
"stage": {
|
||||||
|
"browserTarget": "control-center:build:development,stage"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"defaultConfiguration": "development"
|
"defaultConfiguration": "development"
|
||||||
|
141
package-lock.json
generated
141
package-lock.json
generated
@ -43,6 +43,7 @@
|
|||||||
"angular-file": "3.6.0",
|
"angular-file": "3.6.0",
|
||||||
"angular2-prettyjson": "3.0.1",
|
"angular2-prettyjson": "3.0.1",
|
||||||
"coerce-property": "0.3.2",
|
"coerce-property": "0.3.2",
|
||||||
|
"css-element-queries": "1.2.3",
|
||||||
"element-resize-detector": "1.2.4",
|
"element-resize-detector": "1.2.4",
|
||||||
"humanize-duration": "3.21.0",
|
"humanize-duration": "3.21.0",
|
||||||
"jsonc-parser": "2.0.2",
|
"jsonc-parser": "2.0.2",
|
||||||
@ -79,6 +80,7 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "^5.29.0",
|
"@typescript-eslint/eslint-plugin": "^5.29.0",
|
||||||
"@typescript-eslint/parser": "^5.29.0",
|
"@typescript-eslint/parser": "^5.29.0",
|
||||||
"concurrently": "7.3.0",
|
"concurrently": "7.3.0",
|
||||||
|
"cross-env": "7.0.3",
|
||||||
"dotenv": "16.0.0",
|
"dotenv": "16.0.0",
|
||||||
"eslint": "8.20.0",
|
"eslint": "8.20.0",
|
||||||
"eslint-config-prettier": "8.5.0",
|
"eslint-config-prettier": "8.5.0",
|
||||||
@ -9594,6 +9596,83 @@
|
|||||||
"pretty-bytes": "^5.3.0"
|
"pretty-bytes": "^5.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cross-env": {
|
||||||
|
"version": "7.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
|
||||||
|
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"cross-spawn": "^7.0.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"cross-env": "src/bin/cross-env.js",
|
||||||
|
"cross-env-shell": "src/bin/cross-env-shell.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.14",
|
||||||
|
"npm": ">=6",
|
||||||
|
"yarn": ">=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cross-env/node_modules/cross-spawn": {
|
||||||
|
"version": "7.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||||
|
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"path-key": "^3.1.0",
|
||||||
|
"shebang-command": "^2.0.0",
|
||||||
|
"which": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cross-env/node_modules/path-key": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cross-env/node_modules/shebang-command": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"shebang-regex": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cross-env/node_modules/shebang-regex": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cross-env/node_modules/which": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"isexe": "^2.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"node-which": "bin/node-which"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "6.0.5",
|
"version": "6.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
|
||||||
@ -9659,6 +9738,11 @@
|
|||||||
"postcss": "^8.4"
|
"postcss": "^8.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/css-element-queries": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-element-queries/-/css-element-queries-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-QK9uovYmKTsV2GXWQiMOByVNrLn2qz6m3P7vWpOR4IdD6I3iXoDw5qtgJEN3Xq7gIbdHVKvzHjdAtcl+4Arc4Q=="
|
||||||
|
},
|
||||||
"node_modules/css-has-pseudo": {
|
"node_modules/css-has-pseudo": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz",
|
||||||
@ -29773,6 +29857,58 @@
|
|||||||
"pretty-bytes": "^5.3.0"
|
"pretty-bytes": "^5.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"cross-env": {
|
||||||
|
"version": "7.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
|
||||||
|
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"cross-spawn": "^7.0.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"cross-spawn": {
|
||||||
|
"version": "7.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||||
|
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"path-key": "^3.1.0",
|
||||||
|
"shebang-command": "^2.0.0",
|
||||||
|
"which": "^2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"path-key": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"shebang-command": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"shebang-regex": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shebang-regex": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"which": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"isexe": "^2.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"cross-spawn": {
|
"cross-spawn": {
|
||||||
"version": "6.0.5",
|
"version": "6.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
|
||||||
@ -29847,6 +29983,11 @@
|
|||||||
"postcss-selector-parser": "^6.0.9"
|
"postcss-selector-parser": "^6.0.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"css-element-queries": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-element-queries/-/css-element-queries-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-QK9uovYmKTsV2GXWQiMOByVNrLn2qz6m3P7vWpOR4IdD6I3iXoDw5qtgJEN3Xq7gIbdHVKvzHjdAtcl+4Arc4Q=="
|
||||||
|
},
|
||||||
"css-has-pseudo": {
|
"css-has-pseudo": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz",
|
||||||
|
@ -6,12 +6,13 @@
|
|||||||
"postinstall": "ngcc",
|
"postinstall": "ngcc",
|
||||||
"start": "concurrently -n LIB,APP -c magenta,cyan npm:dev-libs \"sleep 0.5 && npm run dev\"",
|
"start": "concurrently -n LIB,APP -c magenta,cyan npm:dev-libs \"sleep 0.5 && npm run dev\"",
|
||||||
"dev": "ng serve --proxy-config proxy.conf.js --port 4200",
|
"dev": "ng serve --proxy-config proxy.conf.js --port 4200",
|
||||||
|
"stage": "cross-env NODE_ENV=stage npm run dev -- --configuration=stage",
|
||||||
"dev-libs": "ng build ng-core --watch",
|
"dev-libs": "ng build ng-core --watch",
|
||||||
"build-app": "ng build --extra-webpack-config webpack.extra.js",
|
"build-app": "ng build --extra-webpack-config webpack.extra.js",
|
||||||
"build-libs": "ng build ng-core",
|
"build-libs": "ng build ng-core",
|
||||||
"build": "npm run build-libs && npm run build-app",
|
"build": "npm run build-libs && npm run build-app",
|
||||||
"test": "ng test",
|
"test": "ng test",
|
||||||
"lint": "eslint \"src/**/*.{ts,js,html}\" --max-warnings 1033",
|
"lint": "eslint \"src/**/*.{ts,js,html}\" --max-warnings 886",
|
||||||
"lint-fix": "npm run lint -- --fix",
|
"lint-fix": "npm run lint -- --fix",
|
||||||
"lint-errors": "npm run lint -- --quiet",
|
"lint-errors": "npm run lint -- --quiet",
|
||||||
"lint-libs": "eslint \"projects/**/*.{ts,js,html}\" --max-warnings 0",
|
"lint-libs": "eslint \"projects/**/*.{ts,js,html}\" --max-warnings 0",
|
||||||
@ -56,6 +57,7 @@
|
|||||||
"angular-file": "3.6.0",
|
"angular-file": "3.6.0",
|
||||||
"angular2-prettyjson": "3.0.1",
|
"angular2-prettyjson": "3.0.1",
|
||||||
"coerce-property": "0.3.2",
|
"coerce-property": "0.3.2",
|
||||||
|
"css-element-queries": "1.2.3",
|
||||||
"element-resize-detector": "1.2.4",
|
"element-resize-detector": "1.2.4",
|
||||||
"humanize-duration": "3.21.0",
|
"humanize-duration": "3.21.0",
|
||||||
"jsonc-parser": "2.0.2",
|
"jsonc-parser": "2.0.2",
|
||||||
@ -92,6 +94,7 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "^5.29.0",
|
"@typescript-eslint/eslint-plugin": "^5.29.0",
|
||||||
"@typescript-eslint/parser": "^5.29.0",
|
"@typescript-eslint/parser": "^5.29.0",
|
||||||
"concurrently": "7.3.0",
|
"concurrently": "7.3.0",
|
||||||
|
"cross-env": "7.0.3",
|
||||||
"dotenv": "16.0.0",
|
"dotenv": "16.0.0",
|
||||||
"eslint": "8.20.0",
|
"eslint": "8.20.0",
|
||||||
"eslint-config-prettier": "8.5.0",
|
"eslint-config-prettier": "8.5.0",
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
fxFlex="grow"
|
fxFlex="grow"
|
||||||
fxLayout="column"
|
fxLayout="column"
|
||||||
fxLayout.gt-sm="row"
|
fxLayout.gt-sm="row"
|
||||||
fxLayoutAlign="space-between"
|
fxLayoutAlign="space-between center"
|
||||||
fxLayoutGap="24px"
|
fxLayoutGap="24px"
|
||||||
>
|
>
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
export * from './components';
|
export * from './components';
|
||||||
export * from './utils';
|
export * from './utils';
|
||||||
|
export * from './pipes';
|
||||||
|
2
projects/ng-core/src/lib/pipes/index.ts
Normal file
2
projects/ng-core/src/lib/pipes/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './pipes.module';
|
||||||
|
export * from './inline-json.pipe';
|
12
projects/ng-core/src/lib/pipes/inline-json.pipe.ts
Normal file
12
projects/ng-core/src/lib/pipes/inline-json.pipe.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
|
||||||
|
import { inlineJson } from '../utils';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'inlineJson',
|
||||||
|
})
|
||||||
|
export class InlineJsonPipe implements PipeTransform {
|
||||||
|
transform(value: unknown, maxReadableLever: number | false = 1): unknown {
|
||||||
|
return inlineJson(value, maxReadableLever === false ? Infinity : maxReadableLever);
|
||||||
|
}
|
||||||
|
}
|
11
projects/ng-core/src/lib/pipes/pipes.module.ts
Normal file
11
projects/ng-core/src/lib/pipes/pipes.module.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
|
import { InlineJsonPipe } from './inline-json.pipe';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [InlineJsonPipe],
|
||||||
|
exports: [InlineJsonPipe],
|
||||||
|
imports: [CommonModule],
|
||||||
|
})
|
||||||
|
export class PipesModule {}
|
@ -1 +1,2 @@
|
|||||||
export * from './clean';
|
export * from './clean';
|
||||||
|
export * from './inline-json';
|
||||||
|
32
projects/ng-core/src/lib/utils/inline-json.ts
Normal file
32
projects/ng-core/src/lib/utils/inline-json.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { isPrimitive } from 'utility-types';
|
||||||
|
|
||||||
|
export function inlineJson(value: unknown, maxReadableLever = 1): string {
|
||||||
|
if (value === '') {
|
||||||
|
return "''";
|
||||||
|
}
|
||||||
|
if (isPrimitive(value)) {
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
if (Array.isArray(value) || value instanceof Map || value instanceof Set) {
|
||||||
|
const content =
|
||||||
|
maxReadableLever > 0
|
||||||
|
? Array.from(value)
|
||||||
|
.map((v) => inlineJson(v, maxReadableLever))
|
||||||
|
.join(', ')
|
||||||
|
: Array.from(value).length
|
||||||
|
? '…'
|
||||||
|
: '';
|
||||||
|
if (value instanceof Set) return ['Set(', content, ')'].filter(Boolean).join('');
|
||||||
|
if (value instanceof Map) return ['Map(', content, ')'].filter(Boolean).join('');
|
||||||
|
return ['[', content, ']'].filter(Boolean).join('');
|
||||||
|
}
|
||||||
|
const content =
|
||||||
|
maxReadableLever > 0
|
||||||
|
? ' ' +
|
||||||
|
Object.entries(value as never)
|
||||||
|
.map(([k, v]) => `${k}: ${inlineJson(v, maxReadableLever - 1)}`)
|
||||||
|
.join(', ') +
|
||||||
|
' '
|
||||||
|
: '';
|
||||||
|
return ['{', content, '}'].filter(Boolean).join('');
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
require('dotenv').config();
|
require('dotenv').config({ path: ['.env', process.env.NODE_ENV].filter(Boolean).join('.') });
|
||||||
|
|
||||||
const THRIFT_PROXY_CONFIG = {
|
const THRIFT_PROXY_CONFIG = {
|
||||||
context: [
|
context: [
|
||||||
|
@ -6,8 +6,15 @@
|
|||||||
|
|
||||||
<mat-menu #userMenu="matMenu"> <button mat-menu-item (click)="logout()">Logout</button> </mat-menu>
|
<mat-menu #userMenu="matMenu"> <button mat-menu-item (click)="logout()">Logout</button> </mat-menu>
|
||||||
|
|
||||||
<mat-sidenav-container>
|
<mat-sidenav-container autosize>
|
||||||
<mat-sidenav #sidenav fixedInViewport="true" fixedTopGap="64" mode="side" role="navigation">
|
<mat-sidenav
|
||||||
|
#sidenav
|
||||||
|
[(opened)]="opened"
|
||||||
|
fixedInViewport="true"
|
||||||
|
fixedTopGap="64"
|
||||||
|
mode="side"
|
||||||
|
role="navigation"
|
||||||
|
>
|
||||||
<mat-nav-list>
|
<mat-nav-list>
|
||||||
<mat-list-item
|
<mat-list-item
|
||||||
*ngFor="let item of menuItems"
|
*ngFor="let item of menuItems"
|
||||||
|
@ -11,6 +11,8 @@ import {
|
|||||||
PayoutRole,
|
PayoutRole,
|
||||||
} from '@cc/app/shared/services';
|
} from '@cc/app/shared/services';
|
||||||
|
|
||||||
|
const SIDENAV_OPENED_KEY = 'sidenav-opened';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'cc-root',
|
selector: 'cc-root',
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
@ -18,9 +20,15 @@ import {
|
|||||||
})
|
})
|
||||||
export class AppComponent implements OnInit {
|
export class AppComponent implements OnInit {
|
||||||
username: string;
|
username: string;
|
||||||
|
|
||||||
menuItems: { name: string; route: string }[] = [];
|
menuItems: { name: string; route: string }[] = [];
|
||||||
|
|
||||||
|
get opened(): boolean {
|
||||||
|
return localStorage.getItem(SIDENAV_OPENED_KEY) === String(true);
|
||||||
|
}
|
||||||
|
set opened(opened: boolean) {
|
||||||
|
localStorage.setItem(SIDENAV_OPENED_KEY, String(opened));
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private keycloakService: KeycloakService,
|
private keycloakService: KeycloakService,
|
||||||
private appAuthGuardService: AppAuthGuardService
|
private appAuthGuardService: AppAuthGuardService
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
import { environment } from '../../environments/environment';
|
||||||
import { AppConfig } from './types/app-config';
|
import { AppConfig } from './types/app-config';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -11,7 +12,7 @@ export class ConfigService {
|
|||||||
|
|
||||||
load(): Promise<void> {
|
load(): Promise<void> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
this.http.get<AppConfig>('assets/appConfig.json').subscribe((config) => {
|
this.http.get<AppConfig>(environment.appConfigPath).subscribe((config) => {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
resolve(undefined);
|
resolve(undefined);
|
||||||
});
|
});
|
||||||
|
@ -3,6 +3,7 @@ import { HttpClientModule } from '@angular/common/http';
|
|||||||
import { APP_INITIALIZER, NgModule } from '@angular/core';
|
import { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||||
import { KeycloakAngularModule, KeycloakService } from 'keycloak-angular';
|
import { KeycloakAngularModule, KeycloakService } from 'keycloak-angular';
|
||||||
|
|
||||||
|
import { environment } from '../../environments/environment';
|
||||||
import { KeycloakTokenInfoService } from '../keycloak-token-info.service';
|
import { KeycloakTokenInfoService } from '../keycloak-token-info.service';
|
||||||
import { ConfigService } from './config.service';
|
import { ConfigService } from './config.service';
|
||||||
|
|
||||||
@ -18,7 +19,7 @@ const initializer =
|
|||||||
Promise.all([
|
Promise.all([
|
||||||
keycloak
|
keycloak
|
||||||
.init({
|
.init({
|
||||||
config: '/assets/authConfig.json',
|
config: environment.authConfigPath,
|
||||||
initOptions: {
|
initOptions: {
|
||||||
onLoad: 'login-required',
|
onLoad: 'login-required',
|
||||||
checkLoginIframe: true,
|
checkLoginIframe: true,
|
||||||
|
@ -1,234 +0,0 @@
|
|||||||
import Int64 from '@vality/thrift-ts/lib/int64';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ArrayASTNode,
|
|
||||||
ASTNode,
|
|
||||||
BooleanASTNode,
|
|
||||||
NumberASTNode,
|
|
||||||
ObjectASTNode,
|
|
||||||
PropertyASTNode,
|
|
||||||
StringASTNode,
|
|
||||||
} from '../jsonc';
|
|
||||||
import {
|
|
||||||
MetaCollection,
|
|
||||||
MetaEnum,
|
|
||||||
MetaField,
|
|
||||||
MetaMap,
|
|
||||||
MetaPrimitive,
|
|
||||||
MetaStruct,
|
|
||||||
MetaType,
|
|
||||||
MetaTyped,
|
|
||||||
MetaUnion,
|
|
||||||
PrimitiveType,
|
|
||||||
} from './model';
|
|
||||||
|
|
||||||
export interface ApplyPayload {
|
|
||||||
applied: MetaStruct | MetaUnion;
|
|
||||||
errors: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyToCollection(meta: MetaCollection, nodeValue: ASTNode): MetaCollection {
|
|
||||||
if (nodeValue.type !== 'array') {
|
|
||||||
throw new Error(
|
|
||||||
`Applied to collection node value should be array type. Current type is ${nodeValue.type}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const { items } = nodeValue as ArrayASTNode;
|
|
||||||
return {
|
|
||||||
...meta,
|
|
||||||
value: items.map((i) => applyToMeta(meta.itemMeta as MetaTyped, i)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyToMap(meta: MetaMap, nodeValue: ASTNode): MetaMap {
|
|
||||||
if (nodeValue.type !== 'array') {
|
|
||||||
throw new Error(
|
|
||||||
`Applied to map node value should be array type. Current type is ${nodeValue.type}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const { items } = nodeValue as ArrayASTNode;
|
|
||||||
const value = new Map();
|
|
||||||
for (const nodeItem of items) {
|
|
||||||
if (nodeItem.type !== 'object') {
|
|
||||||
throw new Error(
|
|
||||||
`Applied to map item node value should be object type. Current type is ${nodeItem.type}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const keyValueProps = (nodeItem as ObjectASTNode).properties;
|
|
||||||
if (keyValueProps.length !== 2) {
|
|
||||||
throw new Error(
|
|
||||||
`Applied to map object props should has length 2 (key, value). Current length is ${keyValueProps.length}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const [keyProp, valueProp] = keyValueProps;
|
|
||||||
const mapKey = applyToMeta(meta.keyMeta as MetaTyped, keyProp.value);
|
|
||||||
const mapValue = applyToMeta(meta.valueMeta as MetaTyped, valueProp.value);
|
|
||||||
value.set(mapKey, mapValue);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...meta,
|
|
||||||
value,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyToEnum(meta: MetaEnum, nodeValue: ASTNode): MetaEnum {
|
|
||||||
if (nodeValue.type !== 'number') {
|
|
||||||
throw new Error(
|
|
||||||
`Applied to enum node value should be number type. Current type is ${nodeValue.type}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...meta,
|
|
||||||
value: (nodeValue as NumberASTNode).value,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyToNumber({ primitiveType }: MetaPrimitive, { value }: NumberASTNode) {
|
|
||||||
switch (primitiveType) {
|
|
||||||
case PrimitiveType.i8:
|
|
||||||
case PrimitiveType.i16:
|
|
||||||
case PrimitiveType.i32:
|
|
||||||
return value;
|
|
||||||
case PrimitiveType.i64:
|
|
||||||
return new Int64(value);
|
|
||||||
default:
|
|
||||||
throw new Error(`Wrong primitiveType ${primitiveType} and number applied value`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyToString({ primitiveType }: MetaPrimitive, { value }: StringASTNode): string {
|
|
||||||
if (primitiveType !== PrimitiveType.string) {
|
|
||||||
throw new Error(`Wrong primitiveType ${primitiveType} and string applied value`);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyToBoolean({ primitiveType }: MetaPrimitive, nodeValue: BooleanASTNode) {
|
|
||||||
if (primitiveType !== PrimitiveType.bool) {
|
|
||||||
throw new Error(`Wrong primitiveType ${primitiveType} and boolean applied value`);
|
|
||||||
}
|
|
||||||
return nodeValue.getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyToPrimitive(meta: MetaPrimitive, nodeValue: ASTNode): MetaPrimitive {
|
|
||||||
if (
|
|
||||||
nodeValue.type !== 'number' &&
|
|
||||||
nodeValue.type !== 'string' &&
|
|
||||||
nodeValue.type !== 'boolean' &&
|
|
||||||
nodeValue.type !== 'null'
|
|
||||||
) {
|
|
||||||
throw new Error(
|
|
||||||
`Applied to primitive node value should be number, string, boolean or null type. Current type is ${nodeValue.type}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let value;
|
|
||||||
switch (nodeValue.type) {
|
|
||||||
case 'number':
|
|
||||||
value = applyToNumber(meta, nodeValue as NumberASTNode);
|
|
||||||
break;
|
|
||||||
case 'string':
|
|
||||||
value = applyToString(meta, nodeValue as StringASTNode);
|
|
||||||
break;
|
|
||||||
case 'boolean':
|
|
||||||
value = applyToBoolean(meta, nodeValue as BooleanASTNode);
|
|
||||||
break;
|
|
||||||
case 'null':
|
|
||||||
value = null;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...meta,
|
|
||||||
value,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyToMeta(meta: MetaTyped, value: ASTNode): MetaTyped {
|
|
||||||
switch (meta.type) {
|
|
||||||
case MetaType.struct:
|
|
||||||
return applyToStruct(meta as MetaStruct, value);
|
|
||||||
case MetaType.union:
|
|
||||||
return applyToUnion(meta as MetaUnion, value);
|
|
||||||
case MetaType.collection:
|
|
||||||
return applyToCollection(meta as MetaCollection, value);
|
|
||||||
case MetaType.map:
|
|
||||||
return applyToMap(meta as MetaMap, value);
|
|
||||||
case MetaType.enum:
|
|
||||||
return applyToEnum(meta as MetaEnum, value);
|
|
||||||
case MetaType.primitive:
|
|
||||||
return applyToPrimitive(meta as MetaPrimitive, value);
|
|
||||||
}
|
|
||||||
throw new Error(`Unsupported meta type ${meta.type}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyToField(field: MetaField, properties: PropertyASTNode[]): MetaField {
|
|
||||||
const found = properties.find(({ value: { location } }) => location === field.name);
|
|
||||||
return found
|
|
||||||
? {
|
|
||||||
...field,
|
|
||||||
meta: applyToMeta(field.meta as MetaTyped, found.value),
|
|
||||||
}
|
|
||||||
: field;
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyToFields(fields: MetaField[], values: PropertyASTNode[]): MetaField[] {
|
|
||||||
return fields.map((f) => applyToField(f, values));
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyToUnion(subject: MetaUnion, value: ASTNode): MetaUnion {
|
|
||||||
if (value.type !== 'object') {
|
|
||||||
throw new Error(
|
|
||||||
`Applied to union node value should be object type. Current type is ${value.type}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const properties = (value as ObjectASTNode).properties;
|
|
||||||
if (properties.length !== 1) {
|
|
||||||
throw new Error(
|
|
||||||
`Applied to union node properties length should be 1. Current properties length is ${properties.length}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...subject,
|
|
||||||
settedField: properties[0].location as string,
|
|
||||||
fields: applyToFields(subject.fields, properties),
|
|
||||||
virgin: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyToStruct(subject: MetaStruct, value: ASTNode): MetaStruct {
|
|
||||||
if (value.type !== 'object') {
|
|
||||||
throw new Error(
|
|
||||||
`Applied to struct node value should be object type. Current type is ${value.type}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...subject,
|
|
||||||
fields: applyToFields(subject.fields, (value as ObjectASTNode).properties),
|
|
||||||
virgin: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyValue(subject: MetaStruct | MetaUnion, value: ObjectASTNode): ApplyPayload {
|
|
||||||
const result = { applied: null, errors: [] };
|
|
||||||
try {
|
|
||||||
if (value.type !== 'object') {
|
|
||||||
throw new Error('Apply value must be ObjectASTNode');
|
|
||||||
}
|
|
||||||
let applied;
|
|
||||||
switch (subject.type) {
|
|
||||||
case MetaType.struct:
|
|
||||||
applied = applyToStruct(subject as MetaStruct, value);
|
|
||||||
break;
|
|
||||||
case MetaType.union:
|
|
||||||
applied = applyToUnion(subject as MetaUnion, value);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error(
|
|
||||||
`Applied meta type should be struct or union. Current meta type is ${subject.type}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return { ...result, applied };
|
|
||||||
} catch (ex) {
|
|
||||||
console.error(ex);
|
|
||||||
return { ...result, errors: [ex.message] };
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core';
|
|
||||||
|
|
||||||
import { DefinitionService } from './definition.service';
|
|
||||||
import { MetaApplicator } from './meta-applicator.service';
|
|
||||||
import { MetaBuilder } from './meta-builder.service';
|
|
||||||
import { ThriftBuilderService } from './thrift-builder.service';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
providers: [DefinitionService, MetaBuilder, MetaApplicator, ThriftBuilderService],
|
|
||||||
})
|
|
||||||
export class DamselMetaModule {}
|
|
@ -1,41 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { Reference } from '@vality/domain-proto';
|
|
||||||
import { Field } from '@vality/thrift-ts';
|
|
||||||
import { from, Observable, of } from 'rxjs';
|
|
||||||
import { map, shareReplay } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { ASTDefinition } from './model';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class DefinitionService {
|
|
||||||
private def$ = from(
|
|
||||||
import('@vality/domain-proto/lib/metadata.json').then((m) => m.default)
|
|
||||||
).pipe(shareReplay(1)) as Observable<ASTDefinition[]>;
|
|
||||||
|
|
||||||
get astDefinition(): Observable<ASTDefinition[]> {
|
|
||||||
return this.def$;
|
|
||||||
}
|
|
||||||
|
|
||||||
getDomainObjectType(ref: Reference): Observable<string | null> {
|
|
||||||
if (!ref) {
|
|
||||||
return of(null);
|
|
||||||
}
|
|
||||||
const keys = Object.keys(ref);
|
|
||||||
if (keys.length !== 1) {
|
|
||||||
return of(null);
|
|
||||||
}
|
|
||||||
const searchName = keys[0];
|
|
||||||
return this.getDomainDef().pipe(
|
|
||||||
map((d) => {
|
|
||||||
const found = d.find(({ name }) => name === searchName);
|
|
||||||
return found ? (found.type as string) : null;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getDomainDef(): Observable<Field[]> {
|
|
||||||
return this.def$.pipe(
|
|
||||||
map((m) => m.find(({ name }) => name === 'domain').ast.union.DomainObject)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
export * from './model';
|
|
||||||
export * from './meta-builder';
|
|
@ -1,43 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
|
|
||||||
import { IError, ObjectASTNode } from '../jsonc';
|
|
||||||
import { parse } from '../jsonc/json-parser';
|
|
||||||
import { applyValue } from './apply-value';
|
|
||||||
import { ErrorObservable, MetaErrorEmitter } from './meta-error-emitter';
|
|
||||||
import { MetaStruct, MetaUnion } from './model';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class MetaApplicator implements ErrorObservable {
|
|
||||||
private errorEmitter: MetaErrorEmitter;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.errorEmitter = new MetaErrorEmitter();
|
|
||||||
}
|
|
||||||
|
|
||||||
get errors(): Observable<string> {
|
|
||||||
return this.errorEmitter.errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
apply(meta: MetaStruct | MetaUnion, json: string): MetaStruct | MetaUnion | null {
|
|
||||||
if (!meta || !json) {
|
|
||||||
this.errorEmitter.emitErrors(['Meta or value is null']);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const document = parse(json);
|
|
||||||
if (document.errors.length > 0) {
|
|
||||||
this.emitMonacoErrors(document.errors);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const appliedResult = applyValue(meta, document.root as ObjectASTNode);
|
|
||||||
if (appliedResult.errors.length > 0) {
|
|
||||||
this.errorEmitter.emitErrors(appliedResult.errors);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return appliedResult.applied;
|
|
||||||
}
|
|
||||||
|
|
||||||
private emitMonacoErrors(errors: IError[]) {
|
|
||||||
this.errorEmitter.emitErrors(errors.map((e) => e.message));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { map } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { DefinitionService } from './definition.service';
|
|
||||||
import { buildInitialMeta, findMeta, MetaEnricher } from './meta-builder';
|
|
||||||
import { ErrorObservable, MetaErrorEmitter } from './meta-error-emitter';
|
|
||||||
import { MetaStruct, MetaUnion } from './model';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class MetaBuilder implements ErrorObservable {
|
|
||||||
private errorEmitter: MetaErrorEmitter;
|
|
||||||
|
|
||||||
constructor(private definitionService: DefinitionService) {
|
|
||||||
this.errorEmitter = new MetaErrorEmitter();
|
|
||||||
}
|
|
||||||
|
|
||||||
get errors(): Observable<string> {
|
|
||||||
return this.errorEmitter.errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
build(type: string, namespace: string): Observable<MetaStruct | MetaUnion | null> {
|
|
||||||
return this.definitionService.astDefinition.pipe(
|
|
||||||
map((astDef) => {
|
|
||||||
const initial = buildInitialMeta(astDef);
|
|
||||||
const target = findMeta<MetaStruct | MetaUnion>({ namespace, type }, initial);
|
|
||||||
if (!target) {
|
|
||||||
this.errorEmitter.emitErrors(['Target meta not found']);
|
|
||||||
}
|
|
||||||
const enricher = new MetaEnricher(namespace, initial);
|
|
||||||
const { errors, enriched } = enricher.enrich(target);
|
|
||||||
if (errors.length > 0) {
|
|
||||||
this.errorEmitter.emitErrors(errors);
|
|
||||||
}
|
|
||||||
return errors.length === 0 ? enriched : null;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,90 +0,0 @@
|
|||||||
import { Enums, Field, JsonAST, Structs, TypeDefs, Unions } from '@vality/thrift-ts';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ASTDefinition,
|
|
||||||
MetaEnum,
|
|
||||||
MetaField,
|
|
||||||
MetaStruct,
|
|
||||||
MetaType,
|
|
||||||
MetaTyped,
|
|
||||||
MetaTypedef,
|
|
||||||
MetaUnion,
|
|
||||||
} from '../model';
|
|
||||||
import { MetaGroup } from './model';
|
|
||||||
import { resolveAstValueType } from './resolve-ast-value-type';
|
|
||||||
import { isRef } from './utils';
|
|
||||||
|
|
||||||
const resolveAstField = ({ option, name, type }: Field): MetaField => ({
|
|
||||||
required: option ? option === 'required' : false,
|
|
||||||
name,
|
|
||||||
meta: resolveAstValueType(type),
|
|
||||||
});
|
|
||||||
|
|
||||||
const resolveAstFields = (fields: Field[]): MetaField[] => fields.map((f) => resolveAstField(f));
|
|
||||||
|
|
||||||
const resolveAstEnums = (ast: Enums): MetaEnum[] =>
|
|
||||||
Object.keys(ast).map((name) => ({
|
|
||||||
type: MetaType.enum,
|
|
||||||
name,
|
|
||||||
items: ast[name].items,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const resolveAstStructs = (ast: Structs, namespace: string): MetaStruct[] =>
|
|
||||||
Object.keys(ast).map((name) => ({
|
|
||||||
type: MetaType.struct,
|
|
||||||
name,
|
|
||||||
fields: resolveAstFields(ast[name]),
|
|
||||||
isRef: isRef(name),
|
|
||||||
namespace,
|
|
||||||
virgin: true,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const resolveAstUnion = (ast: Unions, namespace: string): MetaUnion[] =>
|
|
||||||
Object.keys(ast).map((name) => ({
|
|
||||||
type: MetaType.union,
|
|
||||||
name,
|
|
||||||
fields: resolveAstFields(ast[name]),
|
|
||||||
settedField: null,
|
|
||||||
namespace,
|
|
||||||
virgin: true,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const resolveAstTypedef = (ast: TypeDefs): MetaTypedef[] =>
|
|
||||||
Object.keys(ast).map((name) => ({
|
|
||||||
type: MetaType.typedef,
|
|
||||||
name,
|
|
||||||
meta: resolveAstValueType(ast[name].type),
|
|
||||||
}));
|
|
||||||
|
|
||||||
function resolveJsonAst(ast: JsonAST, namespace: string): MetaTyped[] {
|
|
||||||
let r = [];
|
|
||||||
if (ast.enum) {
|
|
||||||
r = [...r, ...resolveAstEnums(ast.enum)];
|
|
||||||
}
|
|
||||||
if (ast.struct) {
|
|
||||||
r = [...r, ...resolveAstStructs(ast.struct, namespace)];
|
|
||||||
}
|
|
||||||
if (ast.union) {
|
|
||||||
r = [...r, ...resolveAstUnion(ast.union, namespace)];
|
|
||||||
}
|
|
||||||
if (ast.typedef) {
|
|
||||||
r = [...r, ...resolveAstTypedef(ast.typedef)];
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildInitialMeta(astDef: ASTDefinition[]): MetaGroup[] {
|
|
||||||
if (!astDef || astDef.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return astDef.reduce(
|
|
||||||
(r, { name, ast }) => [
|
|
||||||
...r,
|
|
||||||
{
|
|
||||||
namespace: name,
|
|
||||||
meta: resolveJsonAst(ast, name),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export * from './meta-enricher';
|
|
@ -1,215 +0,0 @@
|
|||||||
import {
|
|
||||||
MetaCollection,
|
|
||||||
MetaMap,
|
|
||||||
MetaStruct,
|
|
||||||
MetaType,
|
|
||||||
MetaTyped,
|
|
||||||
MetaTypedef,
|
|
||||||
MetaTypeDefined,
|
|
||||||
MetaUnion,
|
|
||||||
PrimitiveType,
|
|
||||||
} from '../../model';
|
|
||||||
import { findMeta } from '../find-meta';
|
|
||||||
import { MetaGroup, MetaTypeCondition } from '../model';
|
|
||||||
import { resolvePrimitive } from '../resolve-ast-value-type';
|
|
||||||
import { isObjectRefType, isPrimitiveType, registerError } from '../utils';
|
|
||||||
import { MetaLoopResolver } from './meta-loop-resolver';
|
|
||||||
|
|
||||||
type MetaLoop = string;
|
|
||||||
type ObjectRef = string;
|
|
||||||
|
|
||||||
export interface EnrichResult {
|
|
||||||
enriched: MetaStruct | MetaUnion;
|
|
||||||
errors: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MetaEnricher {
|
|
||||||
private objectRefs: MetaTypeDefined[] = [];
|
|
||||||
private enrichedObjects: (MetaStruct | MetaUnion)[] = [];
|
|
||||||
private errors: string[] = [];
|
|
||||||
private hasLoop = false;
|
|
||||||
private externalNamespaces: string[] = [];
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private namespace: string,
|
|
||||||
private shallowMetaDef: MetaGroup[],
|
|
||||||
private loopSign = '$loop_'
|
|
||||||
) {}
|
|
||||||
|
|
||||||
enrich(target: MetaStruct | MetaUnion): EnrichResult {
|
|
||||||
if (!target) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
this.registerObjectRef(target);
|
|
||||||
const enriched = this.enrichStructUnion(target);
|
|
||||||
return this.preserveLoop(enriched);
|
|
||||||
}
|
|
||||||
|
|
||||||
private preserveLoop(enriched: MetaStruct | MetaUnion): EnrichResult {
|
|
||||||
if (this.hasLoop) {
|
|
||||||
const resolver = new MetaLoopResolver(this.enrichedObjects, this.loopSign);
|
|
||||||
const { resolved, errors } = resolver.resolve(enriched);
|
|
||||||
return {
|
|
||||||
enriched: resolved,
|
|
||||||
errors: [...this.errors, ...errors],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return { enriched, errors: this.errors };
|
|
||||||
}
|
|
||||||
|
|
||||||
private enrichStructUnion(meta: MetaStruct | MetaUnion): MetaStruct | MetaUnion {
|
|
||||||
const fields = meta.fields.map((f) => ({
|
|
||||||
...f,
|
|
||||||
meta: this.enrichObjectMeta(f.meta),
|
|
||||||
}));
|
|
||||||
const result = {
|
|
||||||
...meta,
|
|
||||||
fields,
|
|
||||||
};
|
|
||||||
this.enrichedObjects = [...this.enrichedObjects, result];
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private enrichTyped(meta: MetaTyped): MetaTyped {
|
|
||||||
switch (meta.type) {
|
|
||||||
case MetaType.struct:
|
|
||||||
case MetaType.union:
|
|
||||||
return this.enrichStructUnion(meta as MetaStruct | MetaUnion);
|
|
||||||
case MetaType.typedef:
|
|
||||||
return this.enrichTypedef(meta as MetaTypedef);
|
|
||||||
case MetaType.collection:
|
|
||||||
return this.enrichCollection(meta as MetaCollection);
|
|
||||||
case MetaType.map:
|
|
||||||
return this.enrichMap(meta as MetaMap);
|
|
||||||
case MetaType.primitive:
|
|
||||||
case MetaType.enum:
|
|
||||||
return meta;
|
|
||||||
}
|
|
||||||
this.registerError(`Unsupported enrichment MetaType: ${String(meta.type)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
private enrichTypedef({ meta }: MetaTypedef): MetaTypedef {
|
|
||||||
const result = this.enrichObjectMeta(meta);
|
|
||||||
if (result === this.loopSign) {
|
|
||||||
this.registerError(`Typedef enrichment should not be looped`);
|
|
||||||
}
|
|
||||||
return result as MetaTypedef;
|
|
||||||
}
|
|
||||||
|
|
||||||
private enrichCollection(meta: MetaCollection): MetaCollection {
|
|
||||||
return {
|
|
||||||
...meta,
|
|
||||||
itemMeta: this.enrichCollectionMapMeta(meta.itemMeta),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private enrichMap(meta: MetaMap): MetaMap {
|
|
||||||
return {
|
|
||||||
...meta,
|
|
||||||
keyMeta: this.enrichCollectionMapMeta(meta.keyMeta),
|
|
||||||
valueMeta: this.enrichCollectionMapMeta(meta.valueMeta),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private enrichCollectionMapMeta(
|
|
||||||
meta: MetaTyped | ObjectRef | PrimitiveType
|
|
||||||
): MetaTyped | MetaLoop {
|
|
||||||
if (isObjectRefType(meta)) {
|
|
||||||
return this.enrichObjectRefWithLoopCheck(meta as ObjectRef);
|
|
||||||
}
|
|
||||||
if (isPrimitiveType(meta)) {
|
|
||||||
return resolvePrimitive(meta as PrimitiveType);
|
|
||||||
}
|
|
||||||
return this.enrichTyped(meta as MetaTyped);
|
|
||||||
}
|
|
||||||
|
|
||||||
private enrichObjectMeta(meta: MetaTyped | ObjectRef): MetaTyped | MetaLoop {
|
|
||||||
if (isObjectRefType(meta)) {
|
|
||||||
return this.enrichObjectRefWithLoopCheck(meta as ObjectRef);
|
|
||||||
}
|
|
||||||
return this.enrichTyped(meta as MetaTyped);
|
|
||||||
}
|
|
||||||
|
|
||||||
private enrichObjectRefWithLoopCheck(meta: ObjectRef): MetaTyped | MetaLoop {
|
|
||||||
if (this.isObjectRefUsed(meta)) {
|
|
||||||
this.hasLoop = true;
|
|
||||||
return `${this.loopSign}${meta}`;
|
|
||||||
}
|
|
||||||
return this.enrichObjectRef(this.getCondition(meta));
|
|
||||||
}
|
|
||||||
|
|
||||||
private enrichObjectRef(condition: MetaTypeCondition): MetaTyped | ObjectRef {
|
|
||||||
const found = this.findMeta(condition);
|
|
||||||
const conditionState = `${condition.namespace}.${condition.type}`;
|
|
||||||
if (found === null) {
|
|
||||||
this.registerError(`Meta not found: ${conditionState}. Bump Control Center damsel!`);
|
|
||||||
return condition.type;
|
|
||||||
}
|
|
||||||
if (!found.type) {
|
|
||||||
this.registerError(`Meta should be typed: ${conditionState}`);
|
|
||||||
return condition.type;
|
|
||||||
}
|
|
||||||
if (!found.name) {
|
|
||||||
this.registerError(`Meta should be type defined: ${conditionState}`);
|
|
||||||
return condition.type;
|
|
||||||
}
|
|
||||||
this.registerObjectRef(found);
|
|
||||||
return this.enrichTyped(found);
|
|
||||||
}
|
|
||||||
|
|
||||||
private findMeta(condition: MetaTypeCondition): (MetaTyped & MetaTypeDefined) | null {
|
|
||||||
let found = findMeta<MetaTyped & MetaTypeDefined>(condition, this.shallowMetaDef);
|
|
||||||
if (found) {
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
for (const usedNamespace of this.externalNamespaces) {
|
|
||||||
found = findMeta<MetaTyped & MetaTypeDefined>(
|
|
||||||
{ ...condition, namespace: usedNamespace },
|
|
||||||
this.shallowMetaDef
|
|
||||||
);
|
|
||||||
if (found) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
private registerError(message: string, prefix = 'Enrichment error'): void {
|
|
||||||
this.errors = registerError(this.errors, message, prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
private registerObjectRef(meta: MetaTyped & MetaTypeDefined): void {
|
|
||||||
switch (meta.type) {
|
|
||||||
case MetaType.struct:
|
|
||||||
case MetaType.union:
|
|
||||||
this.objectRefs = [...this.objectRefs, meta];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private isObjectRefUsed(meta: ObjectRef): boolean {
|
|
||||||
return !!this.objectRefs.find(({ name }) => name === meta);
|
|
||||||
}
|
|
||||||
|
|
||||||
private getCondition(meta: ObjectRef): MetaTypeCondition {
|
|
||||||
const [first, second] = meta.split('.');
|
|
||||||
if (second) {
|
|
||||||
this.registerExternalNamespace(first);
|
|
||||||
}
|
|
||||||
return second
|
|
||||||
? {
|
|
||||||
namespace: first,
|
|
||||||
type: second,
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
namespace: this.namespace,
|
|
||||||
type: first,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private registerExternalNamespace(namespace: string): void {
|
|
||||||
const found = this.externalNamespaces.find((n) => n === namespace);
|
|
||||||
if (!found) {
|
|
||||||
this.externalNamespaces = this.externalNamespaces.concat(namespace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,104 +0,0 @@
|
|||||||
import cloneDeep from 'lodash-es/cloneDeep';
|
|
||||||
|
|
||||||
import { MetaLoopResolver } from './meta-loop-resolver';
|
|
||||||
|
|
||||||
describe('MetaLoopResolver', () => {
|
|
||||||
it('should resolve loop', () => {
|
|
||||||
const predicate = {
|
|
||||||
name: 'Predicate',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
required: false,
|
|
||||||
name: 'all_of',
|
|
||||||
meta: {
|
|
||||||
collectionType: 'set',
|
|
||||||
itemMeta: '$loop_Predicate',
|
|
||||||
type: 'collection',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
settedField: null,
|
|
||||||
type: 'union',
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
const resolver = new MetaLoopResolver([predicate], '$loop_');
|
|
||||||
const result = resolver.resolve(predicate);
|
|
||||||
|
|
||||||
const expected = cloneDeep(predicate);
|
|
||||||
expected.fields[0].meta.itemMeta = expected;
|
|
||||||
|
|
||||||
expect(result.errors.length).toEqual(0);
|
|
||||||
expect(result.resolved).toEqual(expected);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should resolve multi level loops', () => {
|
|
||||||
const resolveContainer = [
|
|
||||||
{
|
|
||||||
name: 'PaymentsProvisionTerms',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
meta: '$loop_CurrencySelector',
|
|
||||||
name: 'currencies',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
meta: '$loop_Predicate',
|
|
||||||
name: 'predicate',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
isRef: false,
|
|
||||||
type: 'struct',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'CurrencySelector',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
required: false,
|
|
||||||
name: 'value',
|
|
||||||
meta: {
|
|
||||||
collectionType: 'set',
|
|
||||||
itemMeta: '$loop_CurrencyRef',
|
|
||||||
type: 'collection',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
settedField: null,
|
|
||||||
type: 'union',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'CurrencyRef',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
meta: { type: 'primitive', primitiveType: 'string' },
|
|
||||||
name: 'symbolic_code',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
isRef: true,
|
|
||||||
type: 'struct',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Predicate',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
required: false,
|
|
||||||
name: 'all_of',
|
|
||||||
meta: {
|
|
||||||
collectionType: 'set',
|
|
||||||
itemMeta: '$loop_Predicate',
|
|
||||||
type: 'collection',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
settedField: null,
|
|
||||||
type: 'union',
|
|
||||||
},
|
|
||||||
] as any;
|
|
||||||
|
|
||||||
const resolver = new MetaLoopResolver(resolveContainer, '$loop_');
|
|
||||||
const result = resolver.resolve(resolveContainer[0]);
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log('result', result.resolved);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,116 +0,0 @@
|
|||||||
import {
|
|
||||||
MetaCollection,
|
|
||||||
MetaField,
|
|
||||||
MetaMap,
|
|
||||||
MetaStruct,
|
|
||||||
MetaType,
|
|
||||||
MetaTyped,
|
|
||||||
MetaUnion,
|
|
||||||
} from '../../model';
|
|
||||||
import { registerError } from '../utils';
|
|
||||||
|
|
||||||
export interface ResolveLoopResult {
|
|
||||||
resolved: MetaStruct | MetaUnion;
|
|
||||||
errors: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MetaLoopResolver {
|
|
||||||
private resolved: (MetaStruct | MetaUnion)[] = [];
|
|
||||||
private errors: string[] = [];
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private resolveContainer: (MetaStruct | MetaUnion)[],
|
|
||||||
private loopSign: string,
|
|
||||||
private deep = 0,
|
|
||||||
private deepLimit = 3
|
|
||||||
) {}
|
|
||||||
|
|
||||||
resolve(target: MetaStruct | MetaUnion): ResolveLoopResult {
|
|
||||||
const result = {
|
|
||||||
resolved: this.resolveObject(target),
|
|
||||||
errors: this.errors,
|
|
||||||
};
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private resolveMeta(meta: MetaTyped | string): MetaTyped | string {
|
|
||||||
if (this.isLoop(meta)) {
|
|
||||||
return this.findResolved(meta as string);
|
|
||||||
}
|
|
||||||
const metaTyped = meta as MetaTyped;
|
|
||||||
switch (metaTyped.type) {
|
|
||||||
case MetaType.struct:
|
|
||||||
case MetaType.union:
|
|
||||||
return this.resolveObject(meta as MetaUnion);
|
|
||||||
case MetaType.collection:
|
|
||||||
return this.resolveCollection(meta as MetaCollection);
|
|
||||||
case MetaType.map:
|
|
||||||
return this.resolveMap(meta as MetaMap);
|
|
||||||
case MetaType.typedef:
|
|
||||||
this.registerError('Unexpected meta type: typedef');
|
|
||||||
}
|
|
||||||
return meta;
|
|
||||||
}
|
|
||||||
|
|
||||||
private resolveObject(meta: MetaUnion | MetaStruct): MetaUnion | MetaStruct {
|
|
||||||
if (this.isResolved(meta)) {
|
|
||||||
return meta;
|
|
||||||
}
|
|
||||||
meta.fields = this.resolveFields(meta.fields);
|
|
||||||
return meta;
|
|
||||||
}
|
|
||||||
|
|
||||||
private resolveFields(fields: MetaField[]): MetaField[] {
|
|
||||||
for (const field of fields) {
|
|
||||||
field.meta = this.resolveMeta(field.meta);
|
|
||||||
}
|
|
||||||
return fields;
|
|
||||||
}
|
|
||||||
|
|
||||||
private resolveMap(meta: MetaMap): MetaMap {
|
|
||||||
meta.keyMeta = this.resolveMeta(meta.keyMeta);
|
|
||||||
meta.valueMeta = this.resolveMeta(meta.valueMeta);
|
|
||||||
return meta;
|
|
||||||
}
|
|
||||||
|
|
||||||
private resolveCollection(meta: MetaCollection): MetaCollection {
|
|
||||||
meta.itemMeta = this.resolveMeta(meta.itemMeta);
|
|
||||||
return meta;
|
|
||||||
}
|
|
||||||
|
|
||||||
private isLoop(meta: MetaTyped | string): boolean {
|
|
||||||
return typeof meta === 'string' && meta.startsWith(this.loopSign);
|
|
||||||
}
|
|
||||||
|
|
||||||
private isResolved(meta: MetaUnion | MetaStruct): boolean {
|
|
||||||
return !!this.resolved.find((i) => i.name === meta.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
private findResolved(looped: string): MetaUnion | MetaStruct {
|
|
||||||
const found = this.resolveContainer.find(
|
|
||||||
(i) => i.name === looped.replace(this.loopSign, '')
|
|
||||||
);
|
|
||||||
if (!found) {
|
|
||||||
this.registerError('Resolved meta not found');
|
|
||||||
}
|
|
||||||
if (this.deep < this.deepLimit) {
|
|
||||||
this.deep++;
|
|
||||||
const resolver = new MetaLoopResolver(
|
|
||||||
this.resolveContainer,
|
|
||||||
this.loopSign,
|
|
||||||
this.deep,
|
|
||||||
this.deepLimit
|
|
||||||
);
|
|
||||||
const { resolved, errors } = resolver.resolve(found);
|
|
||||||
this.errors = [...this.errors, ...errors];
|
|
||||||
this.resolved.push(resolved);
|
|
||||||
} else {
|
|
||||||
this.resolved.push(found);
|
|
||||||
}
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
private registerError(message: string, prefix = 'Resolve loop error'): void {
|
|
||||||
this.errors = registerError(this.errors, message, prefix);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
import { MetaObject, MetaTyped, MetaTypeDefined } from '../model';
|
|
||||||
import { MetaGroup, MetaTypeCondition } from './model';
|
|
||||||
|
|
||||||
export function findMeta<T extends MetaTypeDefined | MetaTyped | MetaObject>(
|
|
||||||
condition: MetaTypeCondition,
|
|
||||||
group: MetaGroup[]
|
|
||||||
): T | null {
|
|
||||||
if (!condition || !group) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const byNamespace = group.find(({ namespace }) => namespace === condition.namespace);
|
|
||||||
if (!byNamespace) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const found = byNamespace.meta.find(({ name }) => name === condition.type) as T;
|
|
||||||
return found ? found : null;
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
export * from './build-initial-meta';
|
|
||||||
export * from './enrichment/meta-enricher';
|
|
||||||
export * from './find-meta';
|
|
@ -1,11 +0,0 @@
|
|||||||
import { MetaTypeDefined } from '../model';
|
|
||||||
|
|
||||||
export interface MetaTypeCondition {
|
|
||||||
type: string;
|
|
||||||
namespace: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MetaGroup {
|
|
||||||
namespace: string;
|
|
||||||
meta: MetaTypeDefined[];
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
import { ListType, MapType, SetType, ValueType } from '@vality/thrift-ts';
|
|
||||||
|
|
||||||
import {
|
|
||||||
CollectionType,
|
|
||||||
MetaCollection,
|
|
||||||
MetaMap,
|
|
||||||
MetaPrimitive,
|
|
||||||
MetaType,
|
|
||||||
PrimitiveType,
|
|
||||||
} from '../model';
|
|
||||||
import { isComplexType, isPrimitiveType } from './utils';
|
|
||||||
|
|
||||||
const resolveCollection = (
|
|
||||||
collectionType: CollectionType,
|
|
||||||
itemType: ValueType
|
|
||||||
): MetaCollection => ({
|
|
||||||
type: MetaType.collection,
|
|
||||||
collectionType,
|
|
||||||
itemMeta: resolveAstValueType(itemType),
|
|
||||||
});
|
|
||||||
|
|
||||||
const resolveMap = (keyType: ValueType, valueType: ValueType): MetaMap => ({
|
|
||||||
type: MetaType.map,
|
|
||||||
keyMeta: resolveAstValueType(keyType),
|
|
||||||
valueMeta: resolveAstValueType(valueType),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const resolvePrimitive = (primitiveType: PrimitiveType): MetaPrimitive => ({
|
|
||||||
type: MetaType.primitive,
|
|
||||||
primitiveType,
|
|
||||||
});
|
|
||||||
|
|
||||||
export function resolveAstValueType(
|
|
||||||
type: ValueType
|
|
||||||
): MetaPrimitive | MetaCollection | MetaMap | string {
|
|
||||||
if (isPrimitiveType(type)) {
|
|
||||||
return resolvePrimitive(type as PrimitiveType);
|
|
||||||
}
|
|
||||||
if (isComplexType(type, 'set')) {
|
|
||||||
return resolveCollection(CollectionType.set, (type as SetType).valueType);
|
|
||||||
}
|
|
||||||
if (isComplexType(type, 'list')) {
|
|
||||||
return resolveCollection(CollectionType.list, (type as ListType).valueType);
|
|
||||||
}
|
|
||||||
if (isComplexType(type, 'map')) {
|
|
||||||
return resolveMap((type as MapType).keyType, (type as MapType).valueType);
|
|
||||||
}
|
|
||||||
if (typeof type === 'string') {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
throw Error('Unknown ast value type');
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
import { ValueType } from '@vality/thrift-ts';
|
|
||||||
import isObject from 'lodash-es/isObject';
|
|
||||||
|
|
||||||
import { PrimitiveType } from '../model';
|
|
||||||
|
|
||||||
export const isPrimitiveType = (type: any): boolean =>
|
|
||||||
typeof type === 'string' && Object.keys(PrimitiveType).includes(type);
|
|
||||||
|
|
||||||
export const isObjectRefType = (meta: any) => typeof meta === 'string' && !isPrimitiveType(meta);
|
|
||||||
|
|
||||||
export const isComplexType = (type: ValueType, typeName: 'map' | 'list' | 'set'): boolean => {
|
|
||||||
if (!isObject(type)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const { name } = type;
|
|
||||||
return name === typeName;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isRef = (name: string): boolean => name.endsWith('Ref');
|
|
||||||
|
|
||||||
export const registerError = (errContainer: string[], message: string, prefix: string) => {
|
|
||||||
const error = `${prefix}. ${message}`;
|
|
||||||
return [...errContainer, error];
|
|
||||||
};
|
|
@ -1,19 +0,0 @@
|
|||||||
import { Observable, Subject } from 'rxjs';
|
|
||||||
|
|
||||||
export interface ErrorObservable {
|
|
||||||
errors: Observable<string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MetaErrorEmitter {
|
|
||||||
private errors$: Subject<string> = new Subject();
|
|
||||||
|
|
||||||
get errors(): Observable<string> {
|
|
||||||
return this.errors$;
|
|
||||||
}
|
|
||||||
|
|
||||||
emitErrors(errors: string[]) {
|
|
||||||
for (const error of errors) {
|
|
||||||
this.errors$.next(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
import { JsonAST } from '@vality/thrift-ts';
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
||||||
export interface ASTDefinition {
|
|
||||||
path: string;
|
|
||||||
name: string;
|
|
||||||
ast: JsonAST;
|
|
||||||
}
|
|
@ -1,84 +0,0 @@
|
|||||||
export interface MetaTyped {
|
|
||||||
type: MetaType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MetaObject {
|
|
||||||
virgin: boolean;
|
|
||||||
fields: MetaField[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MetaTypeDefined {
|
|
||||||
namespace: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MetaField {
|
|
||||||
required: boolean;
|
|
||||||
name: string;
|
|
||||||
meta: MetaTyped | string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MetaStruct extends MetaTyped, MetaObject, MetaTypeDefined {
|
|
||||||
isRef: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MetaUnion extends MetaTyped, MetaObject, MetaTypeDefined {
|
|
||||||
settedField: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MetaPrimitive extends MetaTyped {
|
|
||||||
primitiveType: PrimitiveType;
|
|
||||||
value?: string | number | boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MetaEnum extends MetaTyped {
|
|
||||||
items: { name: string; value: string | number | boolean }[];
|
|
||||||
value?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MetaCollection extends MetaTyped {
|
|
||||||
collectionType: CollectionType;
|
|
||||||
itemMeta: MetaTyped | string;
|
|
||||||
value?: MetaTyped[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MetaMap extends MetaTyped {
|
|
||||||
keyMeta: MetaTyped | string;
|
|
||||||
valueMeta: MetaTyped | string;
|
|
||||||
value?: Map<MetaTyped, MetaTyped>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MetaTypedef extends MetaTyped {
|
|
||||||
meta: MetaTyped | string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
|
||||||
export enum MetaType {
|
|
||||||
struct = 'struct',
|
|
||||||
union = 'union',
|
|
||||||
primitive = 'primitive',
|
|
||||||
collection = 'collection',
|
|
||||||
map = 'map',
|
|
||||||
enum = 'enum',
|
|
||||||
typedef = 'typedef',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum PrimitiveType {
|
|
||||||
string = 'string',
|
|
||||||
i8 = 'i8',
|
|
||||||
i16 = 'i16',
|
|
||||||
i32 = 'i32',
|
|
||||||
i64 = 'i64',
|
|
||||||
bool = 'bool',
|
|
||||||
int = 'int',
|
|
||||||
double = 'double',
|
|
||||||
binary = 'binary',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum CollectionType {
|
|
||||||
set = 'set',
|
|
||||||
list = 'list',
|
|
||||||
}
|
|
||||||
/* eslint-enable @typescript-eslint/naming-convention */
|
|
||||||
|
|
||||||
export * from './ast-definition';
|
|
@ -1,29 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
|
|
||||||
import { ErrorObservable, MetaErrorEmitter } from './meta-error-emitter';
|
|
||||||
import { MetaStruct, MetaUnion } from './model';
|
|
||||||
import { buildStructUnion, ThriftType } from './thrift-builder';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class ThriftBuilderService implements ErrorObservable {
|
|
||||||
private errorEmitter: MetaErrorEmitter;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.errorEmitter = new MetaErrorEmitter();
|
|
||||||
}
|
|
||||||
|
|
||||||
get errors(): Observable<string> {
|
|
||||||
return this.errorEmitter.errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
build(meta: MetaStruct | MetaUnion): ThriftType | null {
|
|
||||||
try {
|
|
||||||
return buildStructUnion(meta);
|
|
||||||
} catch (ex) {
|
|
||||||
console.error(ex);
|
|
||||||
this.errorEmitter.emitErrors([ex.message]);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
import { getThriftInstance } from '../../thrift-services';
|
|
||||||
import {
|
|
||||||
MetaCollection,
|
|
||||||
MetaEnum,
|
|
||||||
MetaMap,
|
|
||||||
MetaPrimitive,
|
|
||||||
MetaStruct,
|
|
||||||
MetaType,
|
|
||||||
MetaTyped,
|
|
||||||
MetaUnion,
|
|
||||||
} from '../model';
|
|
||||||
|
|
||||||
export type ThriftType = any;
|
|
||||||
|
|
||||||
const buildPrimitive = ({ value }: MetaPrimitive): string | number | boolean => value;
|
|
||||||
|
|
||||||
const buildEnum = ({ value }: MetaEnum): number => value;
|
|
||||||
|
|
||||||
const buildCollection = ({ value }: MetaCollection): ThriftType[] => {
|
|
||||||
if (!value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return value.map(buildTyped);
|
|
||||||
};
|
|
||||||
|
|
||||||
function buildMap({ value }: MetaMap): Map<ThriftType, ThriftType> {
|
|
||||||
if (!value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const result = new Map();
|
|
||||||
value.forEach((v, k) => result.set(buildTyped(k), buildTyped(v)));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildTyped(meta: MetaTyped): ThriftType {
|
|
||||||
switch (meta.type) {
|
|
||||||
case MetaType.struct:
|
|
||||||
case MetaType.union:
|
|
||||||
return buildStructUnion(meta as MetaStruct);
|
|
||||||
case MetaType.primitive:
|
|
||||||
return buildPrimitive(meta as MetaPrimitive);
|
|
||||||
case MetaType.enum:
|
|
||||||
return buildEnum(meta as MetaEnum);
|
|
||||||
case MetaType.collection:
|
|
||||||
return buildCollection(meta as MetaCollection);
|
|
||||||
case MetaType.map:
|
|
||||||
return buildMap(meta as MetaMap);
|
|
||||||
}
|
|
||||||
throw new Error(`Unsupported meta type: ${meta.type}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildStructUnion({
|
|
||||||
namespace,
|
|
||||||
name,
|
|
||||||
fields,
|
|
||||||
virgin,
|
|
||||||
}: MetaStruct | MetaUnion): ThriftType {
|
|
||||||
if (virgin) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const instance = getThriftInstance(namespace, name);
|
|
||||||
for (const field of fields) {
|
|
||||||
instance[field.name] = buildTyped(field.meta as MetaTyped);
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export * from './build-thrift';
|
|
@ -1,23 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { MatSidenav } from '@angular/material/sidenav';
|
|
||||||
import { Subject } from 'rxjs';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class DetailsContainerService {
|
|
||||||
opened$: Subject<boolean> = new Subject();
|
|
||||||
|
|
||||||
private detailsContainer: MatSidenav;
|
|
||||||
|
|
||||||
set container(sidenav: MatSidenav) {
|
|
||||||
this.detailsContainer = sidenav;
|
|
||||||
this.detailsContainer.openedChange.subscribe((opened) => this.opened$.next(opened));
|
|
||||||
}
|
|
||||||
|
|
||||||
open() {
|
|
||||||
void this.detailsContainer.open();
|
|
||||||
}
|
|
||||||
|
|
||||||
close() {
|
|
||||||
void this.detailsContainer.close();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { Subject } from 'rxjs';
|
|
||||||
|
|
||||||
import { DomainPair } from './domain-group';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class DomainDetailsService {
|
|
||||||
domainPair$: Subject<DomainPair> = new Subject();
|
|
||||||
|
|
||||||
emit(p: DomainPair) {
|
|
||||||
this.domainPair$.next(p);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,61 @@
|
|||||||
<mat-card>
|
<div>
|
||||||
<mat-card-subtitle>Snapshot version: {{ version }} </mat-card-subtitle>
|
<div gdColumns="1fr 1fr" gdGap="24px">
|
||||||
<mat-card-content> <cc-group-table [group]="group"></cc-group-table> </mat-card-content>
|
<cc-select
|
||||||
</mat-card>
|
[formControl]="typesControl"
|
||||||
|
[options]="options$ | async"
|
||||||
|
label="Object types"
|
||||||
|
multiple
|
||||||
|
></cc-select>
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>RegExp patter</mat-label>
|
||||||
|
<input [formControl]="searchControl" matInput />
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<mat-card>
|
||||||
|
<mat-card-content>
|
||||||
|
<div class="table-wrapper">
|
||||||
|
<table
|
||||||
|
[dataSource]="dataSource$ | async"
|
||||||
|
mat-table
|
||||||
|
matSort
|
||||||
|
matSortActive="ref"
|
||||||
|
matSortDirection="asc"
|
||||||
|
matSortDisableClear
|
||||||
|
>
|
||||||
|
<ng-container [matColumnDef]="cols.def.type" sticky>
|
||||||
|
<th *matHeaderCellDef mat-header-cell mat-sort-header>Type</th>
|
||||||
|
<td *matCellDef="let c" mat-cell>{{ c.type }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container [matColumnDef]="cols.def.ref">
|
||||||
|
<th *matHeaderCellDef mat-header-cell mat-sort-header>Ref</th>
|
||||||
|
<td *matCellDef="let c" class="json-cell" mat-cell>
|
||||||
|
<cc-pretty-json [object]="c.ref | ccUnionValue" inline></cc-pretty-json>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container [matColumnDef]="cols.def.obj">
|
||||||
|
<th *matHeaderCellDef mat-header-cell mat-sort-header>Object</th>
|
||||||
|
<td *matCellDef="let c" class="json-cell" mat-cell>
|
||||||
|
<cc-pretty-json
|
||||||
|
[object]="(c.obj | ccUnionValue)?.data"
|
||||||
|
inline
|
||||||
|
></cc-pretty-json>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container [matColumnDef]="cols.def.actions" stickyEnd>
|
||||||
|
<th *matHeaderCellDef mat-header-cell></th>
|
||||||
|
<td *matCellDef="let c" mat-cell style="width: 0">
|
||||||
|
<button mat-icon-button (click)="openDetails(c)">
|
||||||
|
<mat-icon>visibility</mat-icon>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<cc-no-data-row></cc-no-data-row>
|
||||||
|
<tr *matHeaderRowDef="cols.list; sticky: true" mat-header-row></tr>
|
||||||
|
<tr *matRowDef="let row; columns: cols.list" mat-row></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<mat-paginator [pageSizeOptions]="[25, 100]" showFirstLastButtons></mat-paginator>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
$offset: 16px;
|
||||||
|
|
||||||
|
.table-wrapper {
|
||||||
|
$header-height: 64px;
|
||||||
|
$title-height: 40px;
|
||||||
|
$offsets: $offset * 4;
|
||||||
|
$internal-offsets: 16px * 2;
|
||||||
|
$paginator-height: 63.555px;
|
||||||
|
$filters-height: 57.555px;
|
||||||
|
|
||||||
|
overflow: scroll;
|
||||||
|
height: calc(
|
||||||
|
100vh -
|
||||||
|
(
|
||||||
|
$header-height + $title-height + $offsets + $internal-offsets + $paginator-height +
|
||||||
|
$filters-height
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.json-cell {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-table-sticky-border-elem-right {
|
||||||
|
border-left: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-table-sticky-border-elem-left {
|
||||||
|
border-right: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
}
|
@ -1,33 +1,112 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, ViewChildren, QueryList, Output, EventEmitter, OnInit } from '@angular/core';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { FormControl } from '@angular/forms';
|
||||||
import { filter } from 'rxjs/operators';
|
import { MatPaginator } from '@angular/material/paginator';
|
||||||
|
import { MatSort } from '@angular/material/sort';
|
||||||
|
import { MatTableDataSource } from '@angular/material/table';
|
||||||
|
import { UntilDestroy } from '@ngneat/until-destroy';
|
||||||
|
import { Reference, DomainObject } from '@vality/domain-proto/lib/domain';
|
||||||
|
import sortBy from 'lodash-es/sortBy';
|
||||||
|
import { combineLatest, Observable } from 'rxjs';
|
||||||
|
import { map, switchMap, startWith, shareReplay } from 'rxjs/operators';
|
||||||
|
|
||||||
import { DomainGroup } from './domain-group';
|
import { Columns } from '../../../../components/table';
|
||||||
import { DomainGroupService } from './domain-group.service';
|
import { objectToJSON } from '../../../api/utils';
|
||||||
|
import { QueryParamsService } from '../../../shared/services';
|
||||||
|
import { DomainStoreService } from '../../../thrift-services/damsel/domain-store.service';
|
||||||
|
import { MetadataService } from '../../services/metadata.service';
|
||||||
|
import { DataSourceItem } from './types/data-source-item';
|
||||||
|
import { filterPredicate } from './utils/filter-predicate';
|
||||||
|
import { sortData } from './utils/sort-table-data';
|
||||||
|
|
||||||
|
interface Params {
|
||||||
|
types?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
@UntilDestroy()
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'cc-domain-group',
|
selector: 'cc-domain-group',
|
||||||
templateUrl: './domain-group.component.html',
|
templateUrl: './domain-group.component.html',
|
||||||
providers: [DomainGroupService],
|
styleUrls: ['./domain-group.component.scss'],
|
||||||
})
|
})
|
||||||
export class DomainGroupComponent implements OnInit {
|
export class DomainGroupComponent implements OnInit {
|
||||||
group: DomainGroup[];
|
@Output() refChange = new EventEmitter<{ ref: Reference; obj: DomainObject }>();
|
||||||
version: number;
|
|
||||||
|
|
||||||
constructor(private groupService: DomainGroupService, private snackBar: MatSnackBar) {}
|
@ViewChildren(MatPaginator) paginator = new QueryList<MatPaginator>();
|
||||||
|
@ViewChildren(MatSort) sort = new QueryList<MatSort>();
|
||||||
|
|
||||||
|
searchControl = new FormControl('');
|
||||||
|
typesControl = new FormControl(this.queryParamsService.params.types || []);
|
||||||
|
dataSource$: Observable<MatTableDataSource<DataSourceItem>> =
|
||||||
|
this.domainStoreService.domain$.pipe(
|
||||||
|
map((domain) => Array.from(domain).map(([ref, obj]) => ({ ref, obj }))),
|
||||||
|
switchMap((data) =>
|
||||||
|
combineLatest(
|
||||||
|
data.map((d) => this.metadataService.getDomainObjectType(d.ref))
|
||||||
|
).pipe(
|
||||||
|
map((r) =>
|
||||||
|
r.map((type, idx) => ({
|
||||||
|
...data[idx],
|
||||||
|
type,
|
||||||
|
stringified: JSON.stringify(
|
||||||
|
objectToJSON([data[idx].obj, data[idx].ref, type])
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
switchMap((data: DataSourceItem[]) =>
|
||||||
|
combineLatest([
|
||||||
|
this.searchControl.valueChanges.pipe(startWith(this.searchControl.value)),
|
||||||
|
this.typesControl.valueChanges.pipe(startWith(this.typesControl.value)),
|
||||||
|
this.paginator.changes.pipe(startWith(this.paginator)),
|
||||||
|
this.sort.changes.pipe(startWith(this.sort)),
|
||||||
|
]).pipe(
|
||||||
|
map(([searchStr, selectedTypes]) =>
|
||||||
|
this.createMatTableDataSource(data, searchStr, selectedTypes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
shareReplay({ refCount: true, bufferSize: 1 })
|
||||||
|
);
|
||||||
|
cols = new Columns('type', 'ref', 'obj', 'actions');
|
||||||
|
fields$ = this.metadataService.getDomainFields().pipe(
|
||||||
|
map((fields) => sortBy(fields, 'type')),
|
||||||
|
shareReplay({ refCount: true, bufferSize: 1 })
|
||||||
|
);
|
||||||
|
options$ = this.fields$.pipe(
|
||||||
|
map((fields) => fields.map(({ type }) => ({ label: type, value: type })))
|
||||||
|
);
|
||||||
|
isLoading$ = this.domainStoreService.isLoading$;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private domainStoreService: DomainStoreService,
|
||||||
|
private metadataService: MetadataService,
|
||||||
|
private queryParamsService: QueryParamsService<Params>
|
||||||
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.groupService.initialize().subscribe(({ group, version }) => {
|
this.typesControl.valueChanges.subscribe((types) => {
|
||||||
this.group = group;
|
void this.queryParamsService.set({ types });
|
||||||
this.version = version;
|
|
||||||
});
|
});
|
||||||
this.groupService.undefDetectionStatus$
|
}
|
||||||
.pipe(filter((s) => s === 'detected'))
|
|
||||||
.subscribe(() =>
|
openDetails(item: DataSourceItem) {
|
||||||
this.snackBar.open(
|
this.refChange.emit({ ref: item.ref, obj: item.obj });
|
||||||
'Detected undefined domain types. Need to bump damsel version.',
|
}
|
||||||
'OK'
|
|
||||||
)
|
private createMatTableDataSource(
|
||||||
);
|
data: DataSourceItem[],
|
||||||
|
searchStr: string,
|
||||||
|
selectedTypes: string[]
|
||||||
|
) {
|
||||||
|
const dataSource = new MatTableDataSource(
|
||||||
|
data.filter((d) => selectedTypes.includes(d.type))
|
||||||
|
);
|
||||||
|
dataSource.paginator = this.paginator?.first;
|
||||||
|
dataSource.sort = this.sort?.first;
|
||||||
|
dataSource.sortData = sortData;
|
||||||
|
dataSource.filterPredicate = filterPredicate;
|
||||||
|
dataSource.filter = searchStr.trim();
|
||||||
|
return dataSource;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { CdkTableModule } from '@angular/cdk/table';
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
@ -10,24 +10,21 @@ import { MatExpansionModule } from '@angular/material/expansion';
|
|||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||||
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
import { MatSelectModule } from '@angular/material/select';
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
import { MatSortModule } from '@angular/material/sort';
|
import { MatSortModule } from '@angular/material/sort';
|
||||||
import { MatTableModule } from '@angular/material/table';
|
import { MatTableModule } from '@angular/material/table';
|
||||||
|
import { PipesModule } from '@vality/ng-core';
|
||||||
|
|
||||||
import { PrettyJsonModule } from '@cc/components/pretty-json';
|
import { PrettyJsonModule } from '@cc/components/pretty-json';
|
||||||
|
|
||||||
|
import { TableModule } from '../../../../components/table';
|
||||||
|
import { ThriftPipesModule } from '../../../shared';
|
||||||
|
import { SelectModule } from '../../../shared/components/select';
|
||||||
import { DomainGroupComponent } from './domain-group.component';
|
import { DomainGroupComponent } from './domain-group.component';
|
||||||
import { DomainObjectsTypeSelectorComponent } from './domain-objects-type-selector';
|
|
||||||
import { GroupControlComponent } from './group-control';
|
|
||||||
import { GroupTableComponent } from './group-table';
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [DomainGroupComponent],
|
||||||
DomainGroupComponent,
|
|
||||||
DomainObjectsTypeSelectorComponent,
|
|
||||||
GroupControlComponent,
|
|
||||||
GroupTableComponent,
|
|
||||||
],
|
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
@ -44,6 +41,12 @@ import { GroupTableComponent } from './group-table';
|
|||||||
MatSelectModule,
|
MatSelectModule,
|
||||||
MatSortModule,
|
MatSortModule,
|
||||||
PrettyJsonModule,
|
PrettyJsonModule,
|
||||||
|
PipesModule,
|
||||||
|
TableModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
SelectModule,
|
||||||
|
MatProgressSpinnerModule,
|
||||||
|
ThriftPipesModule,
|
||||||
],
|
],
|
||||||
exports: [DomainGroupComponent],
|
exports: [DomainGroupComponent],
|
||||||
})
|
})
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { Int64 } from '@vality/thrift-ts';
|
|
||||||
import { AsyncSubject, Observable } from 'rxjs';
|
|
||||||
import { map } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { DomainInfoService } from '../domain-info.service';
|
|
||||||
import { DomainGroup } from './domain-group';
|
|
||||||
import { group } from './group-domain-objects';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class DomainGroupService {
|
|
||||||
undefDetectionStatus$ = new AsyncSubject();
|
|
||||||
|
|
||||||
constructor(private domainInfoService: DomainInfoService) {}
|
|
||||||
|
|
||||||
initialize(): Observable<{ version: number; group: DomainGroup[] }> {
|
|
||||||
return this.domainInfoService.payload$.pipe(
|
|
||||||
map(({ shapshot: { version, domain }, domainDef }) => {
|
|
||||||
const domainGroup = group(domain, domainDef);
|
|
||||||
this.detectUndefGroup(domainGroup);
|
|
||||||
return {
|
|
||||||
version: (version as unknown as Int64).toNumber(),
|
|
||||||
group: this.filterUndef(domainGroup),
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private detectUndefGroup(domainGroup: DomainGroup[]) {
|
|
||||||
const undef = domainGroup.find((g) => g.name === 'undef');
|
|
||||||
if (undef) {
|
|
||||||
this.undefDetectionStatus$.next('detected');
|
|
||||||
}
|
|
||||||
this.undefDetectionStatus$.complete();
|
|
||||||
}
|
|
||||||
|
|
||||||
private filterUndef(domainGroup: DomainGroup[]) {
|
|
||||||
return domainGroup.filter((g) => g.name !== 'undef');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
import { Reference } from '@vality/domain-proto/lib/domain_config';
|
|
||||||
|
|
||||||
export interface AbstractDomainObject {
|
|
||||||
ref: any;
|
|
||||||
data: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DomainPair {
|
|
||||||
ref: Reference;
|
|
||||||
object: AbstractDomainObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DomainGroup {
|
|
||||||
name: string | 'undef';
|
|
||||||
pairs?: DomainPair[];
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
<div fxLayout>
|
|
||||||
<mat-form-field fxFlex>
|
|
||||||
<mat-select
|
|
||||||
#nameSelect="ngModel"
|
|
||||||
[(ngModel)]="selectedNames"
|
|
||||||
multiple
|
|
||||||
placeholder="Domain objects"
|
|
||||||
(selectionChange)="selectionChange($event)"
|
|
||||||
>
|
|
||||||
<mat-option class="filter-option" disabled="disabled">
|
|
||||||
<button mat-button (click)="selectAll(nameSelect)">SELECT ALL</button>
|
|
||||||
<button mat-button (click)="deselectAll(nameSelect)">DESELECT ALL</button>
|
|
||||||
</mat-option>
|
|
||||||
<mat-option *ngFor="let name of names" [value]="name"> {{ name }} </mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
@ -1,37 +0,0 @@
|
|||||||
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
|
|
||||||
import { NgModel } from '@angular/forms';
|
|
||||||
import { MatSelectChange } from '@angular/material/select';
|
|
||||||
|
|
||||||
import { DomainGroup } from '../domain-group';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'cc-domain-objects-type-selector',
|
|
||||||
templateUrl: './domain-objects-type-selector.component.html',
|
|
||||||
})
|
|
||||||
export class DomainObjectsTypeSelectorComponent implements OnChanges {
|
|
||||||
@Input() group: DomainGroup[];
|
|
||||||
@Output() typeSelectionChange: EventEmitter<string[]> = new EventEmitter();
|
|
||||||
|
|
||||||
names: string[];
|
|
||||||
selectedNames: string[];
|
|
||||||
|
|
||||||
ngOnChanges({ group }: SimpleChanges) {
|
|
||||||
if (group && group.currentValue) {
|
|
||||||
this.names = group.currentValue.map(({ name }) => name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selectAll(select: NgModel) {
|
|
||||||
select.update.emit(this.names);
|
|
||||||
this.typeSelectionChange.emit(this.names);
|
|
||||||
}
|
|
||||||
|
|
||||||
deselectAll(select: NgModel) {
|
|
||||||
select.update.emit([]);
|
|
||||||
this.typeSelectionChange.emit([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
selectionChange(change: MatSelectChange) {
|
|
||||||
this.typeSelectionChange.emit(change.value);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export * from './domain-objects-type-selector.component';
|
|
@ -1,27 +0,0 @@
|
|||||||
<form
|
|
||||||
fxLayout
|
|
||||||
fxLayout.sm="column"
|
|
||||||
fxLayout.xs="column"
|
|
||||||
fxLayoutGap="20px"
|
|
||||||
fxLayoutGap.sm="0"
|
|
||||||
fxLayoutGap.xs="0"
|
|
||||||
>
|
|
||||||
<cc-domain-objects-type-selector
|
|
||||||
[group]="group"
|
|
||||||
fxFlex="50"
|
|
||||||
(typeSelectionChange)="selectionChange($event)"
|
|
||||||
></cc-domain-objects-type-selector>
|
|
||||||
<mat-form-field fxFlex="50">
|
|
||||||
<span matPrefix>/</span>
|
|
||||||
<input
|
|
||||||
[value]="pattern"
|
|
||||||
matInput
|
|
||||||
placeholder="RegExp pattern"
|
|
||||||
(keyup)="patternChange($event.target.value)"
|
|
||||||
/>
|
|
||||||
<span matSuffix>/</span>
|
|
||||||
<button aria-label="Clear" mat-button mat-icon-button matSuffix (click)="clearPattern()">
|
|
||||||
<mat-icon>close</mat-icon>
|
|
||||||
</button>
|
|
||||||
</mat-form-field>
|
|
||||||
</form>
|
|
@ -1,29 +0,0 @@
|
|||||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
|
||||||
|
|
||||||
import { DomainGroup } from '../domain-group';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'cc-group-control',
|
|
||||||
templateUrl: './group-control.component.html',
|
|
||||||
})
|
|
||||||
export class GroupControlComponent {
|
|
||||||
@Input() group: DomainGroup[];
|
|
||||||
@Output() typeSelectionChange: EventEmitter<string[]> = new EventEmitter();
|
|
||||||
@Output() regExpPatternChange: EventEmitter<string> = new EventEmitter();
|
|
||||||
|
|
||||||
pattern = '';
|
|
||||||
|
|
||||||
selectionChange(selectedTypes: string[]) {
|
|
||||||
this.typeSelectionChange.emit(selectedTypes);
|
|
||||||
}
|
|
||||||
|
|
||||||
patternChange(pattern: string) {
|
|
||||||
this.pattern = pattern;
|
|
||||||
this.regExpPatternChange.emit(this.pattern);
|
|
||||||
}
|
|
||||||
|
|
||||||
clearPattern() {
|
|
||||||
this.pattern = '';
|
|
||||||
this.regExpPatternChange.emit(this.pattern);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export * from './group-control.component';
|
|
@ -1,13 +0,0 @@
|
|||||||
import { TableDataSource } from './model';
|
|
||||||
|
|
||||||
export function filterPredicate({ stringified }: TableDataSource, filter: string): boolean {
|
|
||||||
let regexp;
|
|
||||||
try {
|
|
||||||
regexp = new RegExp(filter, 'g');
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/prefer-regexp-exec
|
|
||||||
const matched = stringified.match(regexp);
|
|
||||||
return matched && matched.length > 0;
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
<cc-group-control
|
|
||||||
[group]="group"
|
|
||||||
(regExpPatternChange)="applyFilter($event)"
|
|
||||||
(typeSelectionChange)="setTableData($event)"
|
|
||||||
></cc-group-control>
|
|
||||||
<div class="table-container">
|
|
||||||
<table [dataSource]="dataSource" mat-table matSort>
|
|
||||||
<ng-container matColumnDef="name" sticky>
|
|
||||||
<th *matHeaderCellDef mat-header-cell mat-sort-header>Name</th>
|
|
||||||
<td *matCellDef="let object" mat-cell>{{ object.name }}</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="ref">
|
|
||||||
<th *matHeaderCellDef mat-header-cell mat-sort-header>Ref</th>
|
|
||||||
<td *matCellDef="let object" mat-cell>
|
|
||||||
<cc-pretty-json [object]="object.ref" inline="true"></cc-pretty-json>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="data">
|
|
||||||
<th *matHeaderCellDef mat-header-cell mat-sort-header>Data</th>
|
|
||||||
<td *matCellDef="let object" mat-cell>
|
|
||||||
<cc-pretty-json [object]="object.data" inline="true"></cc-pretty-json>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="details" stickyEnd>
|
|
||||||
<th *matHeaderCellDef mat-header-cell>Details</th>
|
|
||||||
<td *matCellDef="let object; let index = index" mat-cell>
|
|
||||||
<button mat-icon-button (click)="openDetails(object, index)">
|
|
||||||
<mat-icon>visibility</mat-icon>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<tr *matHeaderRowDef="cols" mat-header-row></tr>
|
|
||||||
<tr
|
|
||||||
*matRowDef="let object; columns: cols; let index = index"
|
|
||||||
[class.selected-row]="selectedIndex === index && detailsOpened"
|
|
||||||
mat-row
|
|
||||||
></tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<mat-paginator [pageSizeOptions]="[10, 20, 50, 100]" showFirstLastButtons></mat-paginator>
|
|
@ -1,45 +0,0 @@
|
|||||||
table {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-header-row {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-row {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-column-type {
|
|
||||||
padding-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-column-ref {
|
|
||||||
padding-left: 8px;
|
|
||||||
min-width: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-column-data {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-column-details {
|
|
||||||
padding-left: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-table-sticky:first-child {
|
|
||||||
border-right: 1px solid #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-table-sticky:last-child {
|
|
||||||
border-left: 1px solid #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-container {
|
|
||||||
width: 100%;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected-row {
|
|
||||||
background: #f5f5f5;
|
|
||||||
}
|
|
@ -1,62 +0,0 @@
|
|||||||
import { Component, Input, OnChanges, OnInit, SimpleChanges, ViewChild } from '@angular/core';
|
|
||||||
import { MatPaginator } from '@angular/material/paginator';
|
|
||||||
import { MatSort } from '@angular/material/sort';
|
|
||||||
import { MatTableDataSource } from '@angular/material/table';
|
|
||||||
|
|
||||||
import { DetailsContainerService } from '../../details-container.service';
|
|
||||||
import { DomainDetailsService } from '../../domain-details.service';
|
|
||||||
import { DomainGroup } from '../domain-group';
|
|
||||||
import { filterPredicate } from './filter-predicate';
|
|
||||||
import { TableDataSource, TableGroup } from './model';
|
|
||||||
import { sortData } from './sort-table-data';
|
|
||||||
import { toDataSource, toTableGroup } from './table-group';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'cc-group-table',
|
|
||||||
templateUrl: './group-table.component.html',
|
|
||||||
styleUrls: ['./group-table.component.scss'],
|
|
||||||
})
|
|
||||||
export class GroupTableComponent implements OnInit, OnChanges {
|
|
||||||
@Input() group: DomainGroup[];
|
|
||||||
|
|
||||||
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
|
|
||||||
@ViewChild(MatSort, { static: true }) sort: MatSort;
|
|
||||||
|
|
||||||
dataSource: MatTableDataSource<TableDataSource> = new MatTableDataSource();
|
|
||||||
cols = ['name', 'ref', 'data', 'details'];
|
|
||||||
selectedIndex: number;
|
|
||||||
detailsOpened: boolean;
|
|
||||||
private tableGroup: TableGroup[];
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private detailsService: DomainDetailsService,
|
|
||||||
private detailsContainerService: DetailsContainerService
|
|
||||||
) {}
|
|
||||||
|
|
||||||
ngOnChanges({ group }: SimpleChanges) {
|
|
||||||
if (group && group.currentValue) {
|
|
||||||
this.tableGroup = toTableGroup(group.currentValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.dataSource.paginator = this.paginator;
|
|
||||||
this.dataSource.sort = this.sort;
|
|
||||||
this.dataSource.filterPredicate = filterPredicate;
|
|
||||||
this.dataSource.sortData = sortData;
|
|
||||||
this.detailsContainerService.opened$.subscribe((opened) => (this.detailsOpened = opened));
|
|
||||||
}
|
|
||||||
|
|
||||||
openDetails({ pair }: TableDataSource, index: number) {
|
|
||||||
this.selectedIndex = index;
|
|
||||||
this.detailsService.emit(pair);
|
|
||||||
}
|
|
||||||
|
|
||||||
applyFilter(filterValue: string) {
|
|
||||||
this.dataSource.filter = filterValue.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
setTableData(selectedTypes: string[]) {
|
|
||||||
this.dataSource.data = toDataSource(this.tableGroup, selectedTypes);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export * from './group-table.component';
|
|
@ -1,25 +0,0 @@
|
|||||||
import { DomainPair } from '../domain-group';
|
|
||||||
|
|
||||||
interface ViewDomainObject {
|
|
||||||
ref: string;
|
|
||||||
data: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TableItem {
|
|
||||||
stringified: string;
|
|
||||||
pair: DomainPair;
|
|
||||||
view: ViewDomainObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TableGroup {
|
|
||||||
name: string;
|
|
||||||
tableItems: TableItem[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TableDataSource {
|
|
||||||
name: string;
|
|
||||||
ref: string;
|
|
||||||
data: string;
|
|
||||||
pair: DomainPair;
|
|
||||||
stringified: string;
|
|
||||||
}
|
|
@ -1,82 +0,0 @@
|
|||||||
import { MatSort } from '@angular/material/sort';
|
|
||||||
import isNumber from 'lodash-es/isNumber';
|
|
||||||
|
|
||||||
import { TableDataSource } from './model';
|
|
||||||
|
|
||||||
function strAsc(a: string, b: string): number {
|
|
||||||
if (a < b) {
|
|
||||||
return -1;
|
|
||||||
} else if (a > b) {
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function strDes(a: string, b: string): number {
|
|
||||||
if (a > b) {
|
|
||||||
return -1;
|
|
||||||
} else if (a < b) {
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function numberAsc(a: number, b: number): number {
|
|
||||||
return a - b;
|
|
||||||
}
|
|
||||||
|
|
||||||
function numberDes(a: number, b: number): number {
|
|
||||||
return b - a;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sortByStrField(
|
|
||||||
fieldName: string,
|
|
||||||
data: TableDataSource[],
|
|
||||||
sort: MatSort
|
|
||||||
): TableDataSource[] {
|
|
||||||
if (sort.direction === 'asc') {
|
|
||||||
return data.sort((a, b) => strAsc(a[fieldName], b[fieldName]));
|
|
||||||
}
|
|
||||||
if (sort.direction === 'desc') {
|
|
||||||
return data.sort((a, b) => strDes(a[fieldName], b[fieldName]));
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sortByRef(data: TableDataSource[], sort: MatSort) {
|
|
||||||
if (sort.direction === 'asc') {
|
|
||||||
return data.sort((a, b) => {
|
|
||||||
if (isNumber(a.pair.object.ref.id)) {
|
|
||||||
return numberAsc(a.pair.object.ref.id, b.pair.object.ref.id);
|
|
||||||
}
|
|
||||||
return strAsc(a.ref, b.ref);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (sort.direction === 'desc') {
|
|
||||||
return data.sort((a, b) => {
|
|
||||||
if (isNumber(a.pair.object.ref.id)) {
|
|
||||||
return numberDes(a.pair.object.ref.id, b.pair.object.ref.id);
|
|
||||||
}
|
|
||||||
return strDes(a.ref, b.ref);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sortData(data: TableDataSource[], sort: MatSort): TableDataSource[] {
|
|
||||||
if (!sort.active) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
if (sort.active === 'data') {
|
|
||||||
return sortByStrField('data', data, sort);
|
|
||||||
}
|
|
||||||
if (sort.active === 'name') {
|
|
||||||
return sortByStrField('name', data, sort);
|
|
||||||
}
|
|
||||||
if (sort.active === 'ref') {
|
|
||||||
return sortByRef(data, sort);
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
import { toJson } from '@cc/utils/thrift-json-converter';
|
|
||||||
|
|
||||||
import { DomainGroup } from '../domain-group';
|
|
||||||
import { TableDataSource, TableGroup } from './model';
|
|
||||||
|
|
||||||
function shorten(str: string, limit = 150): string {
|
|
||||||
return str.length > limit ? str.slice(0, limit) + '...' : str;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toTableGroup(domainGroup: DomainGroup[]): TableGroup[] {
|
|
||||||
return domainGroup.map(({ name, pairs }) => ({
|
|
||||||
name,
|
|
||||||
tableItems: pairs.map((p) => {
|
|
||||||
const pair = toJson(p);
|
|
||||||
const stringifiedRef = JSON.stringify(pair.object.ref);
|
|
||||||
const stringifiedData = JSON.stringify(pair.object.data);
|
|
||||||
const stringified = stringifiedRef + stringifiedData;
|
|
||||||
const view = {
|
|
||||||
ref: shorten(stringifiedRef),
|
|
||||||
data: shorten(stringifiedData),
|
|
||||||
};
|
|
||||||
return { stringified, pair, view };
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toDataSource(group: TableGroup[], selectedTypes: string[]): TableDataSource[] {
|
|
||||||
return group
|
|
||||||
.filter(({ name }) => selectedTypes.includes(name))
|
|
||||||
.reduce(
|
|
||||||
(acc, { name, tableItems }) =>
|
|
||||||
acc.concat(
|
|
||||||
tableItems.map(({ pair, view: { ref, data }, stringified }) => ({
|
|
||||||
name,
|
|
||||||
ref,
|
|
||||||
data,
|
|
||||||
pair,
|
|
||||||
stringified,
|
|
||||||
}))
|
|
||||||
),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,3 +1,2 @@
|
|||||||
export * from './domain-group.module';
|
export * from './domain-group.module';
|
||||||
export * from './domain-group.component';
|
export * from './domain-group.component';
|
||||||
export * from './domain-group';
|
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
import { Reference, DomainObject } from '@vality/domain-proto/lib/domain';
|
||||||
|
|
||||||
|
export interface DataSourceItem {
|
||||||
|
type: string;
|
||||||
|
ref: Reference;
|
||||||
|
obj: DomainObject;
|
||||||
|
stringified: string;
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
import { DataSourceItem } from '../types/data-source-item';
|
||||||
|
|
||||||
|
export function filterPredicate({ stringified }: DataSourceItem, filter: string): boolean {
|
||||||
|
let regexp;
|
||||||
|
try {
|
||||||
|
regexp = new RegExp(filter, 'g');
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const matched = stringified.match(regexp);
|
||||||
|
return matched && matched.length > 0;
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
import { MatSort } from '@angular/material/sort';
|
||||||
|
import sortBy from 'lodash-es/sortBy';
|
||||||
|
|
||||||
|
import { objectToJSON } from '../../../../api/utils';
|
||||||
|
import { DataSourceItem } from '../types/data-source-item';
|
||||||
|
|
||||||
|
export function sortData(data: DataSourceItem[], sort: MatSort): DataSourceItem[] {
|
||||||
|
switch (sort.active as keyof DataSourceItem) {
|
||||||
|
case 'type':
|
||||||
|
data = sortBy(data, 'type');
|
||||||
|
break;
|
||||||
|
case 'obj':
|
||||||
|
data = sortBy(data, [(o) => JSON.stringify(objectToJSON(o.obj))]);
|
||||||
|
break;
|
||||||
|
case 'ref':
|
||||||
|
data = sortBy(data, [
|
||||||
|
(o) => {
|
||||||
|
const id = o.ref?.['id'];
|
||||||
|
if (typeof id === 'number') return id;
|
||||||
|
return JSON.stringify(objectToJSON(o.ref));
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (sort.direction === 'desc') return data.reverse();
|
||||||
|
return data;
|
||||||
|
}
|
@ -1,25 +1,44 @@
|
|||||||
<mat-sidenav-container>
|
<mat-sidenav-container>
|
||||||
<mat-sidenav
|
<mat-sidenav
|
||||||
#domainObjDetails
|
#domainObjDetails
|
||||||
|
[opened]="!!objWithRef"
|
||||||
fixedInViewport="true"
|
fixedInViewport="true"
|
||||||
fixedTopGap="64"
|
fixedTopGap="64"
|
||||||
mode="side"
|
mode="side"
|
||||||
position="end"
|
position="end"
|
||||||
>
|
>
|
||||||
<div class="details-container" fxLayout="column" fxLayoutGap="10px">
|
<div class="details-container" fxLayout="column" fxLayoutGap="24px">
|
||||||
<cc-domain-obj-details fxFlex="95"></cc-domain-obj-details>
|
<cc-thrift-viewer
|
||||||
<div fxLayout fxLayoutAlign="space-between center">
|
[kind]="kind"
|
||||||
<button mat-button (click)="closeDetails()">CLOSE</button>
|
[value]="objWithRef?.obj"
|
||||||
<button mat-button (click)="editDomainObj()">EDIT</button>
|
class="viewer"
|
||||||
</div>
|
(changeKind)="kind = $event"
|
||||||
|
></cc-thrift-viewer>
|
||||||
|
<cc-actions>
|
||||||
|
<button mat-button (click)="objWithRef = null">CLOSE</button>
|
||||||
|
<button color="warn" mat-button (click)="delete()">DELETE</button>
|
||||||
|
<button color="primary" mat-button (click)="edit()">EDIT</button>
|
||||||
|
</cc-actions>
|
||||||
</div>
|
</div>
|
||||||
</mat-sidenav>
|
</mat-sidenav>
|
||||||
<mat-sidenav-content>
|
<mat-sidenav-content>
|
||||||
<cc-card-container>
|
<div fxLayout="column" fxLayoutGap="24px" style="margin: 24px 16px 4px">
|
||||||
<div *ngIf="isLoading" fxLayout fxLayoutAlign="center stretch">
|
<div fxLayout="row" fxLayoutAlign="space-between">
|
||||||
|
<h1 class="cc-display-1">
|
||||||
|
Domain config <span class="cc-secondary-text">#{{ version$ | async }}</span>
|
||||||
|
</h1>
|
||||||
|
<div>
|
||||||
|
<button color="primary" mat-raised-button routerLink="/domain/create">
|
||||||
|
CREATE OBJECT
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="progress$ | async; else content" fxLayout fxLayoutAlign="center stretch">
|
||||||
<mat-spinner></mat-spinner>
|
<mat-spinner></mat-spinner>
|
||||||
</div>
|
</div>
|
||||||
<cc-domain-group *ngIf="initialized"></cc-domain-group>
|
<ng-template #content>
|
||||||
</cc-card-container>
|
<cc-domain-group (refChange)="objWithRef = $event"></cc-domain-group>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
</mat-sidenav-content>
|
</mat-sidenav-content>
|
||||||
</mat-sidenav-container>
|
</mat-sidenav-container>
|
||||||
|
@ -5,4 +5,9 @@ mat-sidenav {
|
|||||||
.details-container {
|
.details-container {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
|
.viewer {
|
||||||
|
height: 100%;
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,63 +1,76 @@
|
|||||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
import { Component, ViewChild } from '@angular/core';
|
||||||
import { MatSidenav } from '@angular/material/sidenav';
|
import { MatSidenav } from '@angular/material/sidenav';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
import { untilDestroyed, UntilDestroy } from '@ngneat/until-destroy';
|
||||||
|
import { DomainObject, Reference } from '@vality/domain-proto/lib/domain';
|
||||||
|
import { BaseDialogService, BaseDialogResponseStatus } from '@vality/ng-core';
|
||||||
|
import { filter, switchMap } from 'rxjs/operators';
|
||||||
|
|
||||||
import { DetailsContainerService } from './details-container.service';
|
import { ConfirmActionDialogComponent } from '../../../components/confirm-action-dialog';
|
||||||
import { DomainDetailsService } from './domain-details.service';
|
import { enumHasValue } from '../../../utils';
|
||||||
import { DomainInfoService } from './domain-info.service';
|
import { ViewerKind } from '../../shared/components/thrift-viewer';
|
||||||
|
import { ErrorService } from '../../shared/services/error';
|
||||||
|
import { NotificationService } from '../../shared/services/notification';
|
||||||
|
import { DomainStoreService } from '../../thrift-services/damsel/domain-store.service';
|
||||||
|
|
||||||
|
const VIEWER_KIND = 'domain-info-kind';
|
||||||
|
|
||||||
|
@UntilDestroy()
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './domain-info.component.html',
|
templateUrl: './domain-info.component.html',
|
||||||
styleUrls: ['./domain-info.component.scss'],
|
styleUrls: ['./domain-info.component.scss'],
|
||||||
providers: [DomainInfoService, DomainDetailsService, DetailsContainerService],
|
|
||||||
})
|
})
|
||||||
export class DomainInfoComponent implements OnInit {
|
export class DomainInfoComponent {
|
||||||
initialized = false;
|
|
||||||
isLoading: boolean;
|
|
||||||
@ViewChild('domainObjDetails', { static: true }) detailsContainer: MatSidenav;
|
@ViewChild('domainObjDetails', { static: true }) detailsContainer: MatSidenav;
|
||||||
|
|
||||||
private detailedObjRef: any;
|
version$ = this.domainStoreService.version$;
|
||||||
|
progress$ = this.domainStoreService.isLoading$;
|
||||||
|
objWithRef: { obj: DomainObject; ref: Reference } = null;
|
||||||
|
|
||||||
|
get kind() {
|
||||||
|
const kind = localStorage.getItem(VIEWER_KIND);
|
||||||
|
if (!enumHasValue(ViewerKind, kind)) {
|
||||||
|
this.kind = ViewerKind.Editor;
|
||||||
|
return ViewerKind.Editor;
|
||||||
|
}
|
||||||
|
return kind;
|
||||||
|
}
|
||||||
|
set kind(kind: ViewerKind) {
|
||||||
|
localStorage.setItem(VIEWER_KIND, kind);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private snackBar: MatSnackBar,
|
private router: Router,
|
||||||
private detailsService: DomainDetailsService,
|
private domainStoreService: DomainStoreService,
|
||||||
private detailsContainerService: DetailsContainerService,
|
private baseDialogService: BaseDialogService,
|
||||||
private domainInfoService: DomainInfoService,
|
private notificationService: NotificationService,
|
||||||
private router: Router
|
private errorService: ErrorService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
edit() {
|
||||||
this.initialize();
|
void this.router.navigate(['domain', 'edit', JSON.stringify(this.objWithRef.ref)]);
|
||||||
this.detailsContainerService.container = this.detailsContainer;
|
|
||||||
this.detailsService.domainPair$.subscribe(({ ref }) => {
|
|
||||||
this.detailedObjRef = ref;
|
|
||||||
this.detailsContainerService.open();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
closeDetails() {
|
delete() {
|
||||||
this.detailsContainerService.close();
|
this.baseDialogService
|
||||||
}
|
.open(ConfirmActionDialogComponent, { title: 'Delete object' })
|
||||||
|
.afterClosed()
|
||||||
editDomainObj() {
|
.pipe(
|
||||||
void this.router.navigate(['domain', JSON.stringify(this.detailedObjRef)]);
|
untilDestroyed(this),
|
||||||
}
|
filter(({ status }) => status === BaseDialogResponseStatus.Success),
|
||||||
|
switchMap(() =>
|
||||||
private initialize() {
|
this.domainStoreService.commit({
|
||||||
this.isLoading = true;
|
ops: [{ remove: { object: this.objWithRef.obj } }],
|
||||||
this.domainInfoService.initialize().subscribe(
|
})
|
||||||
() => {
|
)
|
||||||
this.isLoading = false;
|
)
|
||||||
this.initialized = true;
|
.subscribe({
|
||||||
},
|
next: () => {
|
||||||
(err) => {
|
this.notificationService.success('Successfully removed');
|
||||||
this.isLoading = false;
|
},
|
||||||
this.snackBar
|
error: (err) => {
|
||||||
.open(`An error occurred while initializing: ${String(err)}`, 'RETRY')
|
this.errorService.error(err);
|
||||||
.onAction()
|
},
|
||||||
.subscribe(() => this.initialize());
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,17 +8,17 @@ import { MatProgressBarModule } from '@angular/material/progress-bar';
|
|||||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
import { MatSidenavModule } from '@angular/material/sidenav';
|
||||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
import { CardContainerModule } from '@cc/components/card-container/card-container.module';
|
import { PipesModule, ActionsModule } from '@vality/ng-core';
|
||||||
|
|
||||||
import { MonacoEditorModule } from '../../monaco-editor';
|
import { MonacoEditorModule } from '../../monaco-editor';
|
||||||
|
import { ThriftViewerModule } from '../../shared/components/thrift-viewer';
|
||||||
import { DamselModule } from '../../thrift-services/damsel';
|
import { DamselModule } from '../../thrift-services/damsel';
|
||||||
import { DomainGroupModule } from './domain-group';
|
import { DomainGroupModule } from './domain-group';
|
||||||
import { DomainInfoComponent } from './domain-info.component';
|
import { DomainInfoComponent } from './domain-info.component';
|
||||||
import { DomainObjDetailsComponent } from './domain-obj-details';
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [DomainInfoComponent, DomainObjDetailsComponent],
|
declarations: [DomainInfoComponent],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
DomainGroupModule,
|
DomainGroupModule,
|
||||||
@ -32,7 +32,10 @@ import { DomainObjDetailsComponent } from './domain-obj-details';
|
|||||||
MatProgressSpinnerModule,
|
MatProgressSpinnerModule,
|
||||||
MonacoEditorModule,
|
MonacoEditorModule,
|
||||||
DamselModule,
|
DamselModule,
|
||||||
CardContainerModule,
|
ThriftViewerModule,
|
||||||
|
PipesModule,
|
||||||
|
RouterModule,
|
||||||
|
ActionsModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class DomainInfoModule {}
|
export class DomainInfoModule {}
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { Snapshot } from '@vality/domain-proto/lib/domain_config';
|
|
||||||
import { Field } from '@vality/thrift-ts';
|
|
||||||
import { AsyncSubject, combineLatest, Observable, Subject } from 'rxjs';
|
|
||||||
import { map, tap } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { DomainService } from '../domain.service';
|
|
||||||
import { MetadataService } from '../metadata.service';
|
|
||||||
|
|
||||||
export interface Payload {
|
|
||||||
shapshot: Snapshot;
|
|
||||||
domainDef: Field[];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class DomainInfoService {
|
|
||||||
payload$: Subject<Payload> = new AsyncSubject();
|
|
||||||
|
|
||||||
constructor(private domainService: DomainService, private metadataService: MetadataService) {}
|
|
||||||
|
|
||||||
initialize(): Observable<void> {
|
|
||||||
return combineLatest([
|
|
||||||
this.domainService.shapshot,
|
|
||||||
this.metadataService.getDomainDef(),
|
|
||||||
]).pipe(
|
|
||||||
tap(([shapshot, domainDef]) => {
|
|
||||||
this.payload$.next({ shapshot, domainDef });
|
|
||||||
this.payload$.complete();
|
|
||||||
}),
|
|
||||||
map(() => null)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
<cc-monaco-editor [file]="file$ | async" [options]="options"></cc-monaco-editor>
|
|
@ -1,4 +0,0 @@
|
|||||||
cc-monaco-editor {
|
|
||||||
display: block;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { map } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { IEditorOptions, MonacoFile } from '../../../monaco-editor';
|
|
||||||
import { DomainDetailsService } from '../domain-details.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'cc-domain-obj-details',
|
|
||||||
templateUrl: './domain-obj-details.component.html',
|
|
||||||
styleUrls: ['./domain-obj-details.component.scss'],
|
|
||||||
})
|
|
||||||
export class DomainObjDetailsComponent implements OnInit {
|
|
||||||
file$: Observable<MonacoFile>;
|
|
||||||
options: IEditorOptions = {
|
|
||||||
readOnly: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(private detailsService: DomainDetailsService) {}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.file$ = this.detailsService.domainPair$.pipe(
|
|
||||||
map(({ object }) => ({
|
|
||||||
uri: 'index.json',
|
|
||||||
language: 'json',
|
|
||||||
content: JSON.stringify(object, null, 2),
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export * from './domain-obj-details.component';
|
|
@ -1,16 +0,0 @@
|
|||||||
import { DomainObject, Reference } from '@vality/domain-proto/lib/domain';
|
|
||||||
|
|
||||||
import { MetaStruct, MetaUnion } from '../damsel-meta';
|
|
||||||
|
|
||||||
export interface ModificationItem {
|
|
||||||
monacoContent: string;
|
|
||||||
meta: MetaStruct | MetaUnion;
|
|
||||||
domainObj: DomainObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DomainModificationModel {
|
|
||||||
ref: Reference;
|
|
||||||
objectType: string;
|
|
||||||
original: ModificationItem;
|
|
||||||
modified: Partial<ModificationItem>;
|
|
||||||
}
|
|
@ -0,0 +1,57 @@
|
|||||||
|
<div class="editor-container">
|
||||||
|
<div fxLayout="column" fxLayoutGap="24px">
|
||||||
|
<div class="cc-display-1">Create</div>
|
||||||
|
<mat-card>
|
||||||
|
<mat-card-content class="content">
|
||||||
|
<cc-thrift-editor
|
||||||
|
*ngIf="!review"
|
||||||
|
[extensions]="extensions$ | async"
|
||||||
|
[formControl]="control"
|
||||||
|
[kind]="kind"
|
||||||
|
[metadata]="metadata$ | async"
|
||||||
|
class="editor"
|
||||||
|
namespace="domain"
|
||||||
|
type="DomainObject"
|
||||||
|
(changeKind)="kind = $event"
|
||||||
|
></cc-thrift-editor>
|
||||||
|
<cc-thrift-viewer
|
||||||
|
*ngIf="review"
|
||||||
|
[kind]="reviewKind"
|
||||||
|
[value]="control.value"
|
||||||
|
class="editor"
|
||||||
|
(changeKind)="reviewKind = $event"
|
||||||
|
></cc-thrift-viewer>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
<cc-actions>
|
||||||
|
<button
|
||||||
|
*ngIf="!review"
|
||||||
|
[disabled]="!!(progress$ | async)"
|
||||||
|
mat-button
|
||||||
|
routerLink="/domain"
|
||||||
|
>
|
||||||
|
<mat-icon aria-label="Login">keyboard_arrow_left</mat-icon>
|
||||||
|
BACK TO DOMAIN
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
*ngIf="review"
|
||||||
|
[disabled]="!!(progress$ | async)"
|
||||||
|
mat-button
|
||||||
|
(click)="review = false"
|
||||||
|
>
|
||||||
|
<mat-icon aria-label="Login">keyboard_arrow_left</mat-icon>
|
||||||
|
BACK TO EDIT
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
[disabled]="control.invalid || !!(progress$ | async)"
|
||||||
|
color="primary"
|
||||||
|
mat-button
|
||||||
|
(click)="review ? commit() : reviewChanges()"
|
||||||
|
>
|
||||||
|
{{ review ? 'COMMIT' : 'REVIEW' }}
|
||||||
|
<mat-icon aria-label="Login">keyboard_arrow_right</mat-icon>
|
||||||
|
</button>
|
||||||
|
</cc-actions>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,93 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { FormControl, Validators } from '@angular/forms';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||||
|
import { DomainObject } from '@vality/domain-proto/lib/domain';
|
||||||
|
import { from, BehaviorSubject } from 'rxjs';
|
||||||
|
import { withLatestFrom } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { progressTo, getUnionKey, enumHasValue } from '../../../utils';
|
||||||
|
import { EditorKind } from '../../shared/components/thrift-editor';
|
||||||
|
import { ViewerKind } from '../../shared/components/thrift-viewer';
|
||||||
|
import { DomainMetadataFormExtensionsService } from '../../shared/services';
|
||||||
|
import { ErrorService } from '../../shared/services/error';
|
||||||
|
import { NotificationService } from '../../shared/services/notification';
|
||||||
|
import { DomainStoreService } from '../../thrift-services/damsel/domain-store.service';
|
||||||
|
import { DomainNavigateService } from '../services/domain-navigate.service';
|
||||||
|
import { MetadataService } from '../services/metadata.service';
|
||||||
|
|
||||||
|
const EDITOR_KIND = 'domain-obj-creation-editor-kind';
|
||||||
|
const REVIEW_KIND = 'domain-obj-creation-review-kind';
|
||||||
|
|
||||||
|
@UntilDestroy()
|
||||||
|
@Component({
|
||||||
|
templateUrl: './domain-obj-creation.component.html',
|
||||||
|
styleUrls: ['../editor-container.scss'],
|
||||||
|
})
|
||||||
|
export class DomainObjCreationComponent {
|
||||||
|
control = new FormControl<DomainObject>(null, Validators.required);
|
||||||
|
review = false;
|
||||||
|
|
||||||
|
metadata$ = from(import('@vality/domain-proto/lib/metadata.json').then((m) => m.default));
|
||||||
|
extensions$ = this.domainMetadataFormExtensionsService.extensions$;
|
||||||
|
progress$ = new BehaviorSubject(0);
|
||||||
|
|
||||||
|
get kind() {
|
||||||
|
const kind = localStorage.getItem(EDITOR_KIND);
|
||||||
|
if (!enumHasValue(EditorKind, kind)) {
|
||||||
|
this.kind = EditorKind.Form;
|
||||||
|
return EditorKind.Form;
|
||||||
|
}
|
||||||
|
return kind;
|
||||||
|
}
|
||||||
|
set kind(kind: EditorKind) {
|
||||||
|
localStorage.setItem(EDITOR_KIND, kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
get reviewKind() {
|
||||||
|
const kind = localStorage.getItem(REVIEW_KIND);
|
||||||
|
if (!enumHasValue(ViewerKind, kind)) {
|
||||||
|
this.reviewKind = ViewerKind.Editor;
|
||||||
|
return ViewerKind.Editor;
|
||||||
|
}
|
||||||
|
return kind;
|
||||||
|
}
|
||||||
|
set reviewKind(kind: ViewerKind) {
|
||||||
|
localStorage.setItem(REVIEW_KIND, kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private domainMetadataFormExtensionsService: DomainMetadataFormExtensionsService,
|
||||||
|
private domainStoreService: DomainStoreService,
|
||||||
|
private notificationService: NotificationService,
|
||||||
|
private errorService: ErrorService,
|
||||||
|
private router: Router,
|
||||||
|
private domainNavigateService: DomainNavigateService,
|
||||||
|
private metadataService: MetadataService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
reviewChanges() {
|
||||||
|
this.review = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
commit() {
|
||||||
|
this.domainStoreService
|
||||||
|
.commit({ ops: [{ insert: { object: this.control.value } }] })
|
||||||
|
.pipe(
|
||||||
|
withLatestFrom(
|
||||||
|
this.metadataService.getDomainFieldByFieldName(getUnionKey(this.control.value))
|
||||||
|
),
|
||||||
|
progressTo(this.progress$),
|
||||||
|
untilDestroyed(this)
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
|
next: ([, field]) => {
|
||||||
|
this.notificationService.success('Successfully created');
|
||||||
|
void this.domainNavigateService.toType(String(field.type));
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
this.errorService.error(err);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||||
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatCardModule } from '@angular/material/card';
|
||||||
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { ActionsModule } from '@vality/ng-core';
|
||||||
|
|
||||||
|
import { ThriftEditorModule } from '@cc/app/shared/components/thrift-editor';
|
||||||
|
|
||||||
|
import { MonacoEditorModule } from '../../monaco-editor';
|
||||||
|
import { ThriftViewerModule } from '../../shared/components/thrift-viewer';
|
||||||
|
import { DomainObjCreationComponent } from './domain-obj-creation.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [DomainObjCreationComponent],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FlexLayoutModule,
|
||||||
|
RouterModule,
|
||||||
|
MatProgressSpinnerModule,
|
||||||
|
MatCardModule,
|
||||||
|
MatButtonModule,
|
||||||
|
MatIconModule,
|
||||||
|
MonacoEditorModule,
|
||||||
|
MatDialogModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
ThriftEditorModule,
|
||||||
|
ActionsModule,
|
||||||
|
ThriftViewerModule,
|
||||||
|
],
|
||||||
|
exports: [DomainObjCreationComponent],
|
||||||
|
})
|
||||||
|
export class DomainObjCreationModule {}
|
2
src/app/domain/domain-obj-creation/index.ts
Normal file
2
src/app/domain/domain-obj-creation/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './domain-obj-creation.module';
|
||||||
|
export * from './domain-obj-creation.component';
|
@ -1,30 +1,42 @@
|
|||||||
<div class="editor-container">
|
<div class="editor-container">
|
||||||
<div *ngIf="isLoading" fxLayout fxLayoutAlign="center stretch"><mat-spinner></mat-spinner></div>
|
<div *ngIf="progress$ | async; else content" fxLayout fxLayoutAlign="center stretch">
|
||||||
<mat-card *ngIf="initialized">
|
<mat-spinner></mat-spinner>
|
||||||
<mat-card-header>
|
</div>
|
||||||
<mat-card-title>Edit {{ model.objectType }}</mat-card-title>
|
<ng-template #content>
|
||||||
</mat-card-header>
|
<div fxLayout="column" fxLayoutGap="24px">
|
||||||
<mat-card-content>
|
<div class="cc-display-1">Edit {{ type$ | async }}</div>
|
||||||
<cc-monaco-editor
|
<mat-card>
|
||||||
[codeLensProviders]="codeLensProviders"
|
<mat-card-content class="content">
|
||||||
[completionProviders]="completionProviders"
|
<cc-thrift-editor
|
||||||
[file]="modifiedFile"
|
[codeLensProviders]="codeLensProviders"
|
||||||
class="editor"
|
[completionProviders]="completionProviders"
|
||||||
(fileChange)="fileChange($event)"
|
[defaultValue]="object$ | async"
|
||||||
></cc-monaco-editor>
|
[extensions]="extensions$ | async"
|
||||||
</mat-card-content>
|
[formControl]="control"
|
||||||
<mat-card-actions>
|
[kind]="kind"
|
||||||
<div fxLayout="row" fxLayoutAlign="space-between center">
|
[metadata]="metadata$ | async"
|
||||||
<button mat-button routerLink="/domain">
|
[type]="type$ | async"
|
||||||
|
class="editor"
|
||||||
|
namespace="domain"
|
||||||
|
(changeKind)="kind = $event"
|
||||||
|
></cc-thrift-editor>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
<cc-actions>
|
||||||
|
<button mat-button (click)="backToDomain()">
|
||||||
<mat-icon aria-label="Login">keyboard_arrow_left</mat-icon>
|
<mat-icon aria-label="Login">keyboard_arrow_left</mat-icon>
|
||||||
BACK TO DOMAIN
|
BACK TO DOMAIN
|
||||||
</button>
|
</button>
|
||||||
<button color="warn" mat-button (click)="resetChanges()">RESET CHANGES</button>
|
<button
|
||||||
<button [disabled]="!valid" mat-button (click)="reviewChanges()">
|
[disabled]="control.invalid"
|
||||||
|
color="primary"
|
||||||
|
mat-button
|
||||||
|
(click)="reviewChanges()"
|
||||||
|
>
|
||||||
REVIEW CHANGES
|
REVIEW CHANGES
|
||||||
<mat-icon aria-label="Login">keyboard_arrow_right</mat-icon>
|
<mat-icon aria-label="Login">keyboard_arrow_right</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</cc-actions>
|
||||||
</mat-card-actions>
|
</div>
|
||||||
</mat-card>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,98 +1,79 @@
|
|||||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { FormControl } from '@angular/forms';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { Router } from '@angular/router';
|
import { Router, ActivatedRoute } from '@angular/router';
|
||||||
import { Subscription } from 'rxjs';
|
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||||
|
import { from } from 'rxjs';
|
||||||
|
import { first } from 'rxjs/operators';
|
||||||
|
|
||||||
import { CodeLensProvider, CompletionProvider, MonacoFile } from '../../monaco-editor';
|
import { enumHasValue } from '../../../utils';
|
||||||
import { DomainModificationModel } from '../domain-modification-model';
|
import { CodeLensProvider, CompletionProvider } from '../../monaco-editor';
|
||||||
import { DomainReviewService } from '../domain-review.service';
|
import { EditorKind } from '../../shared/components/thrift-editor';
|
||||||
import { toMonacoFile } from '../utils';
|
import { DomainMetadataFormExtensionsService } from '../../shared/services';
|
||||||
|
import { DomainNavigateService } from '../services/domain-navigate.service';
|
||||||
|
import { DomainObjModificationService } from '../services/domain-obj-modification.service';
|
||||||
|
import { ModifiedDomainObjectService } from '../services/modified-domain-object.service';
|
||||||
import { DomainObjCodeLensProvider } from './domain-obj-code-lens-provider';
|
import { DomainObjCodeLensProvider } from './domain-obj-code-lens-provider';
|
||||||
import { DomainObjCompletionProvider } from './domain-obj-completion-provider';
|
import { DomainObjCompletionProvider } from './domain-obj-completion-provider';
|
||||||
import { DomainObjModificationService } from './domain-obj-modification.service';
|
|
||||||
import { ResetConfirmDialogComponent } from './reset-confirm-dialog/reset-confirm-dialog.component';
|
|
||||||
|
|
||||||
|
const EDITOR_KIND = 'domain-obj-modification-kind';
|
||||||
|
|
||||||
|
@UntilDestroy()
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './domain-obj-modification.component.html',
|
templateUrl: './domain-obj-modification.component.html',
|
||||||
styleUrls: ['../editor-container.scss'],
|
styleUrls: ['../editor-container.scss'],
|
||||||
providers: [DomainObjModificationService],
|
providers: [DomainObjModificationService],
|
||||||
})
|
})
|
||||||
export class DomainObjModificationComponent implements OnInit, OnDestroy {
|
export class DomainObjModificationComponent implements OnInit {
|
||||||
initialized = false;
|
control = new FormControl();
|
||||||
isLoading: boolean;
|
|
||||||
valid = false;
|
|
||||||
codeLensProviders: CodeLensProvider[];
|
|
||||||
completionProviders: CompletionProvider[];
|
|
||||||
modifiedFile: MonacoFile;
|
|
||||||
model: DomainModificationModel;
|
|
||||||
|
|
||||||
private initSub: Subscription;
|
progress$ = this.domainObjModService.progress$;
|
||||||
|
codeLensProviders: CodeLensProvider[] = [new DomainObjCodeLensProvider()];
|
||||||
|
completionProviders: CompletionProvider[] = [new DomainObjCompletionProvider()];
|
||||||
|
metadata$ = from(import('@vality/domain-proto/lib/metadata.json').then((m) => m.default));
|
||||||
|
object$ = this.domainObjModService.object$;
|
||||||
|
type$ = this.domainObjModService.type$;
|
||||||
|
extensions$ = this.domainMetadataFormExtensionsService.extensions$;
|
||||||
|
|
||||||
|
get kind() {
|
||||||
|
const kind = localStorage.getItem(EDITOR_KIND);
|
||||||
|
if (!enumHasValue(EditorKind, kind)) {
|
||||||
|
this.kind = EditorKind.Editor;
|
||||||
|
return EditorKind.Editor;
|
||||||
|
}
|
||||||
|
return kind;
|
||||||
|
}
|
||||||
|
set kind(kind: EditorKind) {
|
||||||
|
localStorage.setItem(EDITOR_KIND, kind);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private router: Router,
|
private router: Router,
|
||||||
|
private route: ActivatedRoute,
|
||||||
private snackBar: MatSnackBar,
|
private snackBar: MatSnackBar,
|
||||||
private domainObjModService: DomainObjModificationService,
|
private domainObjModService: DomainObjModificationService,
|
||||||
private domainReviewService: DomainReviewService,
|
private modifiedDomainObjectService: ModifiedDomainObjectService,
|
||||||
private dialog: MatDialog
|
private domainMetadataFormExtensionsService: DomainMetadataFormExtensionsService,
|
||||||
|
private domainNavigateService: DomainNavigateService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.initSub = this.initialize();
|
this.domainObjModService.object$.pipe(first(), untilDestroyed(this)).subscribe((object) => {
|
||||||
this.codeLensProviders = [new DomainObjCodeLensProvider()];
|
if (
|
||||||
this.completionProviders = [new DomainObjCompletionProvider()];
|
this.modifiedDomainObjectService.domainObject &&
|
||||||
}
|
this.route.snapshot.params.ref === this.modifiedDomainObjectService.ref
|
||||||
|
)
|
||||||
ngOnDestroy() {
|
this.control.setValue(this.modifiedDomainObjectService.domainObject);
|
||||||
if (this.initSub) {
|
else this.control.setValue(object);
|
||||||
this.initSub.unsubscribe();
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fileChange({ content }: MonacoFile) {
|
|
||||||
const modified = this.domainObjModService.modify(this.model.original, content);
|
|
||||||
this.valid = !!modified;
|
|
||||||
this.model.modified = modified;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reviewChanges() {
|
reviewChanges() {
|
||||||
this.domainReviewService.addReviewModel(this.model);
|
this.modifiedDomainObjectService.update(this.control.value, this.route.snapshot.params.ref);
|
||||||
void this.router.navigate(['domain', JSON.stringify(this.model.ref), 'review']);
|
void this.router.navigate(['domain', 'edit', this.route.snapshot.params.ref, 'review']);
|
||||||
}
|
}
|
||||||
|
|
||||||
resetChanges() {
|
backToDomain() {
|
||||||
this.dialog
|
this.type$.pipe(first()).subscribe((type) => this.domainNavigateService.toType(type));
|
||||||
.open(ResetConfirmDialogComponent, {
|
|
||||||
width: '300px',
|
|
||||||
})
|
|
||||||
.afterClosed()
|
|
||||||
.subscribe((result) => {
|
|
||||||
if (!result) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const modified = this.domainObjModService.reset(this.model.original);
|
|
||||||
this.model.modified = modified;
|
|
||||||
this.modifiedFile = toMonacoFile(modified.monacoContent);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private initialize(): Subscription {
|
|
||||||
this.isLoading = true;
|
|
||||||
return this.domainObjModService.init().subscribe(
|
|
||||||
(model) => {
|
|
||||||
this.isLoading = false;
|
|
||||||
this.model = model;
|
|
||||||
this.modifiedFile = toMonacoFile(model.modified.monacoContent);
|
|
||||||
this.initialized = true;
|
|
||||||
if (this.initSub) {
|
|
||||||
this.initSub.unsubscribe();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
console.error(err);
|
|
||||||
this.isLoading = false;
|
|
||||||
this.snackBar.open(`An error occurred while initializing: ${String(err)}`, 'OK');
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,22 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||||
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
import { MatDialogModule } from '@angular/material/dialog';
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { ActionsModule } from '@vality/ng-core';
|
||||||
|
|
||||||
|
import { ThriftEditorModule } from '@cc/app/shared/components/thrift-editor';
|
||||||
|
|
||||||
import { MonacoEditorModule } from '../../monaco-editor';
|
import { MonacoEditorModule } from '../../monaco-editor';
|
||||||
import { DomainObjModificationComponent } from './domain-obj-modification.component';
|
import { DomainObjModificationComponent } from './domain-obj-modification.component';
|
||||||
import { ResetConfirmDialogComponent } from './reset-confirm-dialog/reset-confirm-dialog.component';
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [DomainObjModificationComponent, ResetConfirmDialogComponent],
|
declarations: [DomainObjModificationComponent],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
FlexLayoutModule,
|
FlexLayoutModule,
|
||||||
@ -24,6 +27,9 @@ import { ResetConfirmDialogComponent } from './reset-confirm-dialog/reset-confir
|
|||||||
MatIconModule,
|
MatIconModule,
|
||||||
MonacoEditorModule,
|
MonacoEditorModule,
|
||||||
MatDialogModule,
|
MatDialogModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
ThriftEditorModule,
|
||||||
|
ActionsModule,
|
||||||
],
|
],
|
||||||
exports: [DomainObjModificationComponent],
|
exports: [DomainObjModificationComponent],
|
||||||
})
|
})
|
||||||
|
@ -1,130 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { ActivatedRoute } from '@angular/router';
|
|
||||||
import { DomainObject, Reference } from '@vality/domain-proto/lib/domain';
|
|
||||||
import { combineLatest, Observable, of, Subject } from 'rxjs';
|
|
||||||
import { map, switchMap } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { MetaApplicator } from '../../damsel-meta/meta-applicator.service';
|
|
||||||
import { MetaBuilder } from '../../damsel-meta/meta-builder.service';
|
|
||||||
import { ThriftType } from '../../damsel-meta/thrift-builder';
|
|
||||||
import { ThriftBuilderService } from '../../damsel-meta/thrift-builder.service';
|
|
||||||
import { getThriftInstance } from '../../thrift-services';
|
|
||||||
import { DomainModificationModel, ModificationItem } from '../domain-modification-model';
|
|
||||||
import { DomainReviewService } from '../domain-review.service';
|
|
||||||
import { DomainService } from '../domain.service';
|
|
||||||
import { MetadataService } from '../metadata.service';
|
|
||||||
import { parseRef, toMonacoContent } from '../utils';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class DomainObjModificationService {
|
|
||||||
private errors$: Subject<string> = new Subject();
|
|
||||||
|
|
||||||
get errors(): Observable<string> {
|
|
||||||
return this.errors$;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private route: ActivatedRoute,
|
|
||||||
private domainService: DomainService,
|
|
||||||
private metadataService: MetadataService,
|
|
||||||
private metaBuilder: MetaBuilder,
|
|
||||||
private metaApplicator: MetaApplicator,
|
|
||||||
private domainReviewService: DomainReviewService,
|
|
||||||
private thriftBuilderService: ThriftBuilderService
|
|
||||||
) {
|
|
||||||
this.metaBuilder.errors.subscribe((e) => {
|
|
||||||
this.errors$.next(e);
|
|
||||||
console.error('Build meta error:', e);
|
|
||||||
});
|
|
||||||
this.metaApplicator.errors.subscribe((e) => console.error('Apply meta error:', e));
|
|
||||||
}
|
|
||||||
|
|
||||||
init(namespace = 'domain'): Observable<DomainModificationModel> {
|
|
||||||
return combineLatest([this.route.params, this.domainReviewService.reviewModel]).pipe(
|
|
||||||
switchMap(([routeParams, model]) => {
|
|
||||||
if (model && JSON.stringify(model.ref) === routeParams.ref) {
|
|
||||||
return of(model);
|
|
||||||
}
|
|
||||||
const ref = parseRef(routeParams.ref);
|
|
||||||
return combineLatest([
|
|
||||||
this.metadataService.getDomainObjectType(ref),
|
|
||||||
this.domainService.getDomainObject(ref),
|
|
||||||
]).pipe(
|
|
||||||
switchMap(([objectType, domainObj]) =>
|
|
||||||
this.build(ref, objectType, domainObj, namespace)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
modify(original: ModificationItem, modifiedContent: string): ModificationItem | null {
|
|
||||||
const modifiedMeta = this.metaApplicator.apply(original.meta, modifiedContent);
|
|
||||||
if (!modifiedMeta) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const thrift = this.thriftBuilderService.build(modifiedMeta);
|
|
||||||
if (!thrift) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
meta: modifiedMeta,
|
|
||||||
domainObj: this.formNewDomainObj(original.domainObj, thrift),
|
|
||||||
monacoContent: modifiedContent,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
reset({ monacoContent }: ModificationItem): Partial<ModificationItem> {
|
|
||||||
return {
|
|
||||||
monacoContent: monacoContent.slice(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private build(
|
|
||||||
ref: Reference,
|
|
||||||
objectType: string,
|
|
||||||
domainObj: DomainObject,
|
|
||||||
namespace: string
|
|
||||||
): Observable<DomainModificationModel> {
|
|
||||||
if (!objectType) {
|
|
||||||
throw new Error('Domain object type not found');
|
|
||||||
}
|
|
||||||
if (!domainObj) {
|
|
||||||
throw new Error('Domain object not found');
|
|
||||||
}
|
|
||||||
return this.metaBuilder.build(objectType, namespace).pipe(
|
|
||||||
map((initialMeta) => {
|
|
||||||
if (!initialMeta) {
|
|
||||||
throw new Error('Build initial meta failed');
|
|
||||||
}
|
|
||||||
const monacoContent = toMonacoContent(domainObj);
|
|
||||||
const applied = this.metaApplicator.apply(initialMeta, monacoContent);
|
|
||||||
if (!applied) {
|
|
||||||
throw new Error('Apply original value failed');
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
ref,
|
|
||||||
objectType,
|
|
||||||
original: {
|
|
||||||
monacoContent,
|
|
||||||
domainObj,
|
|
||||||
meta: applied,
|
|
||||||
},
|
|
||||||
modified: {
|
|
||||||
monacoContent: monacoContent.slice(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private formNewDomainObj(source: DomainObject, thrift: ThriftType): DomainObject {
|
|
||||||
const result = getThriftInstance('domain', 'DomainObject');
|
|
||||||
const filtered = Object.keys(source).filter((k) => !!source[k]);
|
|
||||||
if (filtered.length !== 1) {
|
|
||||||
throw new Error('Should be only one field in DomainObject');
|
|
||||||
}
|
|
||||||
result[filtered[0]] = thrift;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
<div mat-dialog-title>Confirm reset changes</div>
|
|
||||||
<div mat-dialog-actions>
|
|
||||||
<button [mat-dialog-close] mat-button>CANCEL</button>
|
|
||||||
<button [mat-dialog-close]="'true'" mat-button>CONFIRM</button>
|
|
||||||
</div>
|
|
@ -1,6 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
templateUrl: 'reset-confirm-dialog.component.html',
|
|
||||||
})
|
|
||||||
export class ResetConfirmDialogComponent {}
|
|
@ -1,33 +1,34 @@
|
|||||||
<div class="editor-container">
|
<div class="editor-container">
|
||||||
<mat-card *ngIf="initialized">
|
<div *ngIf="progress$ | async; else content" fxLayout fxLayoutAlign="center stretch">
|
||||||
<mat-card-header fxLayout="row" fxLayoutAlign="space-between stretch">
|
<mat-spinner></mat-spinner>
|
||||||
<mat-card-title>Review changes of {{ objectType }}</mat-card-title>
|
</div>
|
||||||
<div>
|
<ng-template #content>
|
||||||
<mat-checkbox
|
<div fxLayout="column" fxLayoutGap="24px">
|
||||||
[checked]="options.renderSideBySide"
|
<div class="cc-display-1">Review changes of {{ type$ | async }}</div>
|
||||||
(change)="renderSideBySide($event)"
|
<mat-card>
|
||||||
>Side by side</mat-checkbox
|
<mat-card-content class="content">
|
||||||
>
|
<cc-thrift-viewer
|
||||||
</div>
|
[compared]="modifiedObject"
|
||||||
</mat-card-header>
|
[value]="object$ | async"
|
||||||
<mat-card-content>
|
class="editor"
|
||||||
<cc-monaco-diff-editor
|
kind="diff"
|
||||||
[modified]="modified"
|
></cc-thrift-viewer>
|
||||||
[options]="options"
|
</mat-card-content>
|
||||||
[original]="original"
|
</mat-card>
|
||||||
class="editor"
|
<cc-actions>
|
||||||
></cc-monaco-diff-editor>
|
<button [disabled]="!!(progress$ | async)" mat-button (click)="back()">
|
||||||
</mat-card-content>
|
|
||||||
<mat-card-actions>
|
|
||||||
<div fxLayout="row" fxLayoutAlign="space-between center">
|
|
||||||
<button mat-button (click)="back()">
|
|
||||||
<mat-icon>keyboard_arrow_left</mat-icon>
|
<mat-icon>keyboard_arrow_left</mat-icon>
|
||||||
BACK TO EDIT
|
BACK TO EDIT
|
||||||
</button>
|
</button>
|
||||||
<button [disabled]="isLoading" color="primary" mat-button (click)="commit()">
|
<button
|
||||||
|
[disabled]="!!(progress$ | async)"
|
||||||
|
color="primary"
|
||||||
|
mat-button
|
||||||
|
(click)="commit()"
|
||||||
|
>
|
||||||
COMMIT
|
COMMIT
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</cc-actions>
|
||||||
</mat-card-actions>
|
</div>
|
||||||
</mat-card>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,90 +1,79 @@
|
|||||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { MatCheckboxChange } from '@angular/material/checkbox';
|
import { Router, ActivatedRoute } from '@angular/router';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { untilDestroyed, UntilDestroy } from '@ngneat/until-destroy';
|
||||||
import { Router } from '@angular/router';
|
import { switchMap } from 'rxjs';
|
||||||
import { Subscription } from 'rxjs';
|
import { first, withLatestFrom } from 'rxjs/operators';
|
||||||
|
|
||||||
import { IDiffEditorOptions, MonacoFile } from '../../monaco-editor';
|
import { getUnionKey } from '../../../utils';
|
||||||
import { DomainModificationModel } from '../domain-modification-model';
|
import { ErrorService } from '../../shared/services/error';
|
||||||
import { toMonacoFile } from '../utils';
|
import { NotificationService } from '../../shared/services/notification';
|
||||||
import { DomainObjReviewService } from './domain-obj-review.service';
|
import { DomainStoreService } from '../../thrift-services/damsel/domain-store.service';
|
||||||
|
import { DomainNavigateService } from '../services/domain-navigate.service';
|
||||||
|
import { DomainObjModificationService } from '../services/domain-obj-modification.service';
|
||||||
|
import { ModifiedDomainObjectService } from '../services/modified-domain-object.service';
|
||||||
|
|
||||||
|
@UntilDestroy()
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './domain-obj-review.component.html',
|
templateUrl: './domain-obj-review.component.html',
|
||||||
styleUrls: ['../editor-container.scss'],
|
styleUrls: ['../editor-container.scss'],
|
||||||
providers: [DomainObjReviewService],
|
providers: [DomainObjModificationService],
|
||||||
})
|
})
|
||||||
export class DomainObjReviewComponent implements OnInit, OnDestroy {
|
export class DomainObjReviewComponent {
|
||||||
initialized = false;
|
progress$ = this.domainObjModService.progress$;
|
||||||
original: MonacoFile;
|
object$ = this.domainObjModService.object$;
|
||||||
modified: MonacoFile;
|
type$ = this.domainObjModService.type$;
|
||||||
objectType: string;
|
modifiedObject = this.modifiedDomainObjectService.domainObject;
|
||||||
options: IDiffEditorOptions = {
|
|
||||||
renderSideBySide: true,
|
|
||||||
readOnly: true,
|
|
||||||
};
|
|
||||||
isLoading = false;
|
|
||||||
|
|
||||||
private reviewModelSub: Subscription;
|
|
||||||
private ref: string;
|
|
||||||
private model: DomainModificationModel;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private snackBar: MatSnackBar,
|
private route: ActivatedRoute,
|
||||||
private domainObjReviewService: DomainObjReviewService
|
private domainObjModService: DomainObjModificationService,
|
||||||
) {}
|
private modifiedDomainObjectService: ModifiedDomainObjectService,
|
||||||
|
private domainStoreService: DomainStoreService,
|
||||||
ngOnInit() {
|
private notificationService: NotificationService,
|
||||||
this.initialize();
|
private errorService: ErrorService,
|
||||||
}
|
private domainNavigateService: DomainNavigateService
|
||||||
|
) {
|
||||||
ngOnDestroy() {
|
if (!modifiedDomainObjectService.domainObject) {
|
||||||
if (this.reviewModelSub) {
|
this.back();
|
||||||
this.reviewModelSub.unsubscribe();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSideBySide({ checked }: MatCheckboxChange) {
|
commit() {
|
||||||
this.options = { ...this.options, renderSideBySide: checked };
|
this.domainObjModService.fullObject$
|
||||||
|
.pipe(
|
||||||
|
first(),
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
switchMap((old_object) =>
|
||||||
|
this.domainStoreService.commit({
|
||||||
|
ops: [
|
||||||
|
{
|
||||||
|
update: {
|
||||||
|
old_object,
|
||||||
|
new_object: {
|
||||||
|
[getUnionKey(old_object)]: this.modifiedObject,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
),
|
||||||
|
withLatestFrom(this.type$),
|
||||||
|
// progressTo(this.progress$),
|
||||||
|
untilDestroyed(this)
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
|
next: ([, type]) => {
|
||||||
|
this.notificationService.success('Successfully changed');
|
||||||
|
void this.domainNavigateService.toType(type);
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
this.errorService.error(err);
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
back() {
|
back() {
|
||||||
void this.router.navigate(['domain', this.ref]);
|
void this.router.navigate(['domain', 'edit', this.route.snapshot.params.ref]);
|
||||||
}
|
|
||||||
|
|
||||||
commit() {
|
|
||||||
this.isLoading = true;
|
|
||||||
this.domainObjReviewService.commit(this.model).subscribe(
|
|
||||||
() => {
|
|
||||||
this.isLoading = false;
|
|
||||||
this.snackBar.open('Commit successful', 'OK', {
|
|
||||||
duration: 2000,
|
|
||||||
});
|
|
||||||
void this.router.navigate(['domain', this.ref]);
|
|
||||||
},
|
|
||||||
(ex) => {
|
|
||||||
this.isLoading = false;
|
|
||||||
console.error(ex);
|
|
||||||
this.snackBar.open(`An error occured while commit: ${String(ex)}`, 'OK');
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private initialize() {
|
|
||||||
this.reviewModelSub = this.domainObjReviewService
|
|
||||||
.initialize()
|
|
||||||
.subscribe(([{ ref }, model]) => {
|
|
||||||
this.ref = ref;
|
|
||||||
if (!model) {
|
|
||||||
this.back();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.original = toMonacoFile(model.original.monacoContent);
|
|
||||||
this.modified = toMonacoFile(model.modified.monacoContent);
|
|
||||||
this.objectType = model.objectType;
|
|
||||||
this.model = model;
|
|
||||||
this.initialized = true;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||||
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { ActionsModule } from '@vality/ng-core';
|
||||||
|
|
||||||
import { MonacoEditorModule } from '../../monaco-editor';
|
import { MonacoEditorModule } from '../../monaco-editor';
|
||||||
|
import { ThriftEditorModule } from '../../shared/components/thrift-editor';
|
||||||
|
import { ThriftViewerModule } from '../../shared/components/thrift-viewer';
|
||||||
import { DomainObjReviewComponent } from './domain-obj-review.component';
|
import { DomainObjReviewComponent } from './domain-obj-review.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@ -21,6 +26,11 @@ import { DomainObjReviewComponent } from './domain-obj-review.component';
|
|||||||
MatCheckboxModule,
|
MatCheckboxModule,
|
||||||
MonacoEditorModule,
|
MonacoEditorModule,
|
||||||
MatIconModule,
|
MatIconModule,
|
||||||
|
ThriftEditorModule,
|
||||||
|
MatProgressSpinnerModule,
|
||||||
|
ActionsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
ThriftViewerModule,
|
||||||
],
|
],
|
||||||
exports: [DomainObjReviewComponent],
|
exports: [DomainObjReviewComponent],
|
||||||
})
|
})
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { ActivatedRoute } from '@angular/router';
|
|
||||||
import { Version } from '@vality/domain-proto/lib/domain_config';
|
|
||||||
import { combineLatest, Observable } from 'rxjs';
|
|
||||||
import { tap } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { DomainModificationModel } from '../domain-modification-model';
|
|
||||||
import { DomainReviewService } from '../domain-review.service';
|
|
||||||
import { DomainService } from '../domain.service';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class DomainObjReviewService {
|
|
||||||
constructor(
|
|
||||||
private route: ActivatedRoute,
|
|
||||||
private domainService: DomainService,
|
|
||||||
private domainReviewService: DomainReviewService
|
|
||||||
) {}
|
|
||||||
|
|
||||||
commit({ original, modified }: DomainModificationModel): Observable<Version> {
|
|
||||||
const commit = {
|
|
||||||
ops: [
|
|
||||||
{
|
|
||||||
update: {
|
|
||||||
old_object: original.domainObj,
|
|
||||||
new_object: modified.domainObj,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
return this.domainService
|
|
||||||
.commit(commit)
|
|
||||||
.pipe(tap(() => this.domainReviewService.resetModel()));
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize() {
|
|
||||||
return combineLatest([this.route.params, this.domainReviewService.reviewModel]);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { BehaviorSubject, Observable, Subject } from 'rxjs';
|
|
||||||
|
|
||||||
import { DomainModificationModel } from './domain-modification-model';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class DomainReviewService {
|
|
||||||
private reviewModel$: Subject<DomainModificationModel> = new BehaviorSubject(null);
|
|
||||||
|
|
||||||
get reviewModel(): Observable<DomainModificationModel> {
|
|
||||||
return this.reviewModel$;
|
|
||||||
}
|
|
||||||
|
|
||||||
addReviewModel(model: DomainModificationModel) {
|
|
||||||
if (!model) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.reviewModel$.next(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
resetModel() {
|
|
||||||
this.reviewModel$.next(null);
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,6 +4,7 @@ import { RouterModule } from '@angular/router';
|
|||||||
import { AppAuthGuardService, DomainConfigRole } from '@cc/app/shared/services';
|
import { AppAuthGuardService, DomainConfigRole } from '@cc/app/shared/services';
|
||||||
|
|
||||||
import { DomainInfoComponent } from './domain-info';
|
import { DomainInfoComponent } from './domain-info';
|
||||||
|
import { DomainObjCreationComponent } from './domain-obj-creation';
|
||||||
import { DomainObjModificationComponent } from './domain-obj-modification';
|
import { DomainObjModificationComponent } from './domain-obj-modification';
|
||||||
import { DomainObjReviewComponent } from './domain-obj-review';
|
import { DomainObjReviewComponent } from './domain-obj-review';
|
||||||
|
|
||||||
@ -19,7 +20,15 @@ import { DomainObjReviewComponent } from './domain-obj-review';
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'domain/:ref',
|
path: 'domain/create',
|
||||||
|
component: DomainObjCreationComponent,
|
||||||
|
canActivate: [AppAuthGuardService],
|
||||||
|
data: {
|
||||||
|
roles: [DomainConfigRole.Checkout],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'domain/edit/:ref',
|
||||||
component: DomainObjModificationComponent,
|
component: DomainObjModificationComponent,
|
||||||
canActivate: [AppAuthGuardService],
|
canActivate: [AppAuthGuardService],
|
||||||
data: {
|
data: {
|
||||||
@ -27,7 +36,7 @@ import { DomainObjReviewComponent } from './domain-obj-review';
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'domain/:ref/review',
|
path: 'domain/edit/:ref/review',
|
||||||
component: DomainObjReviewComponent,
|
component: DomainObjReviewComponent,
|
||||||
canActivate: [AppAuthGuardService],
|
canActivate: [AppAuthGuardService],
|
||||||
data: {
|
data: {
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
import { DamselMetaModule } from '../damsel-meta/damsel-meta.module';
|
|
||||||
import { DomainInfoModule } from './domain-info/domain-info.module';
|
import { DomainInfoModule } from './domain-info/domain-info.module';
|
||||||
import { DomainObjModificationModule } from './domain-obj-modification';
|
import { DomainObjModificationModule } from './domain-obj-modification';
|
||||||
import { DomainObjReviewModule } from './domain-obj-review';
|
import { DomainObjReviewModule } from './domain-obj-review';
|
||||||
import { DomainReviewService } from './domain-review.service';
|
|
||||||
import { DomainRoutingModule } from './domain-routing.module';
|
import { DomainRoutingModule } from './domain-routing.module';
|
||||||
import { DomainService } from './domain.service';
|
import { MetadataService } from './services/metadata.service';
|
||||||
import { MetadataService } from './metadata.service';
|
import { ModifiedDomainObjectService } from './services/modified-domain-object.service';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@ -15,8 +13,7 @@ import { MetadataService } from './metadata.service';
|
|||||||
DomainInfoModule,
|
DomainInfoModule,
|
||||||
DomainObjModificationModule,
|
DomainObjModificationModule,
|
||||||
DomainObjReviewModule,
|
DomainObjReviewModule,
|
||||||
DamselMetaModule,
|
|
||||||
],
|
],
|
||||||
providers: [DomainService, MetadataService, DomainReviewService],
|
providers: [MetadataService, ModifiedDomainObjectService],
|
||||||
})
|
})
|
||||||
export class DomainModule {}
|
export class DomainModule {}
|
||||||
|
@ -1,75 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { DomainObject, Reference } from '@vality/domain-proto/lib/domain';
|
|
||||||
import { Commit, Snapshot } from '@vality/domain-proto/lib/domain_config';
|
|
||||||
import { Int64 } from '@vality/thrift-ts';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { map, switchMap } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { toJson } from '@cc/utils/thrift-json-converter';
|
|
||||||
|
|
||||||
import { toGenCommit, toGenReference } from '../thrift-services/converters';
|
|
||||||
import { DomainService as ThriftDomainService } from '../thrift-services/damsel/domain.service';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated duplicates thrift-services/damsel/domain-cache.service
|
|
||||||
*/
|
|
||||||
@Injectable()
|
|
||||||
export class DomainService {
|
|
||||||
private shapshot$: Observable<Snapshot>;
|
|
||||||
|
|
||||||
constructor(private thriftDomainService: ThriftDomainService) {
|
|
||||||
this.updateSnapshot();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated use DomainCacheService -> snapshot$
|
|
||||||
*/
|
|
||||||
get shapshot() {
|
|
||||||
return this.shapshot$;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated use DomainCacheService -> version$
|
|
||||||
*/
|
|
||||||
get version$(): Observable<number> {
|
|
||||||
return this.shapshot$.pipe(
|
|
||||||
map(({ version }) => (version ? (version as unknown as Int64).toNumber() : undefined))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated use DomainCacheService -> getObjects or specific service from thrift-services/damsel
|
|
||||||
*/
|
|
||||||
getDomainObject(ref: Reference): Observable<DomainObject | null> {
|
|
||||||
return this.shapshot$.pipe(
|
|
||||||
map(({ domain }) => {
|
|
||||||
const searchRef = JSON.stringify(ref);
|
|
||||||
for (const [k, v] of domain) {
|
|
||||||
const domainRef = JSON.stringify(toJson(k));
|
|
||||||
if (domainRef === searchRef) {
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated use DomainCacheService -> forceReload()
|
|
||||||
*/
|
|
||||||
updateSnapshot() {
|
|
||||||
return (this.shapshot$ = this.thriftDomainService.checkout(toGenReference()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated use DomainCacheService -> commit()
|
|
||||||
*/
|
|
||||||
commit(commit: Commit) {
|
|
||||||
return this.shapshot$.pipe(
|
|
||||||
switchMap(({ version }) =>
|
|
||||||
this.thriftDomainService.commit(version, toGenCommit(commit))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +1,23 @@
|
|||||||
|
$offset: 24px;
|
||||||
|
|
||||||
.editor {
|
.editor {
|
||||||
display: block;
|
display: block;
|
||||||
height: calc(100vh - 200px);
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.editor-container {
|
.editor-container {
|
||||||
margin: 10px;
|
margin: $offset 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
$header-height: 64px;
|
||||||
|
$title-height: 40px;
|
||||||
|
$actions-height: 36px;
|
||||||
|
$offsets: $offset * 4;
|
||||||
|
$internal-offsets: 16px * 2;
|
||||||
|
|
||||||
|
height: calc(
|
||||||
|
100vh - ($header-height + $title-height + $actions-height + $offsets + $internal-offsets)
|
||||||
|
);
|
||||||
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1 @@
|
|||||||
export * from './domain.module';
|
export * from './domain.module';
|
||||||
export * from './domain.service';
|
|
||||||
|
13
src/app/domain/services/domain-navigate.service.ts
Normal file
13
src/app/domain/services/domain-navigate.service.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class DomainNavigateService {
|
||||||
|
constructor(private router: Router) {}
|
||||||
|
|
||||||
|
toType(type: string) {
|
||||||
|
return this.router.navigate(['domain'], { queryParams: { types: JSON.stringify([type]) } });
|
||||||
|
}
|
||||||
|
}
|
58
src/app/domain/services/domain-obj-modification.service.ts
Normal file
58
src/app/domain/services/domain-obj-modification.service.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { DomainObject, Reference } from '@vality/domain-proto/lib/domain';
|
||||||
|
import { Observable, switchMap, BehaviorSubject, defer } from 'rxjs';
|
||||||
|
import { map, shareReplay, first } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { ErrorService } from '@cc/app/shared/services/error';
|
||||||
|
import { DomainStoreService } from '@cc/app/thrift-services/damsel/domain-store.service';
|
||||||
|
import { toJson, getUnionValue, progressTo } from '@cc/utils';
|
||||||
|
|
||||||
|
import { MetadataService } from './metadata.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class DomainObjModificationService {
|
||||||
|
progress$ = new BehaviorSubject(0);
|
||||||
|
fullObject$ = defer(() => this.ref$).pipe(
|
||||||
|
switchMap((ref) => this.getDomainObject(ref).pipe(progressTo(this.progress$))),
|
||||||
|
shareReplay({ refCount: true, bufferSize: 1 })
|
||||||
|
);
|
||||||
|
object$ = this.fullObject$.pipe(
|
||||||
|
map((obj) => getUnionValue(obj)),
|
||||||
|
shareReplay({ refCount: true, bufferSize: 1 })
|
||||||
|
);
|
||||||
|
type$ = defer(() => this.ref$).pipe(
|
||||||
|
switchMap((ref) => this.metadataService.getDomainObjectType(ref)),
|
||||||
|
shareReplay({ refCount: true, bufferSize: 1 })
|
||||||
|
);
|
||||||
|
|
||||||
|
private ref$ = this.route.params.pipe(
|
||||||
|
map(({ ref }) => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(ref as string) as Reference;
|
||||||
|
} catch (err) {
|
||||||
|
this.errorService.error(err, 'Malformed domain object ref');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private domainStoreService: DomainStoreService,
|
||||||
|
private metadataService: MetadataService,
|
||||||
|
private errorService: ErrorService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
private getDomainObject(ref: Reference): Observable<DomainObject> {
|
||||||
|
return this.domainStoreService.domain$.pipe(
|
||||||
|
first(),
|
||||||
|
map((domain) => {
|
||||||
|
const searchRef = JSON.stringify(ref);
|
||||||
|
return domain.get(
|
||||||
|
Array.from(domain.keys()).find((k) => JSON.stringify(toJson(k)) === searchRef)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user