mirror of
https://github.com/clearml/clearml-web
synced 2025-06-26 18:27:02 +00:00
@@ -13,7 +13,7 @@
|
||||
|
||||
### Development
|
||||
During development, the development server will need to proxy an API server. to achieve that:
|
||||
* in [proxy.config.js](proxy.config.js) update the list of targets in line 3 with a working API server URI.
|
||||
* in [proxy.config.mjs](proxy.config.mjs) update the list of targets in line 3 with a working API server URI.
|
||||
* Angular is already configured to use this proxy configuration
|
||||
* If more than 1 API server is configured `apiBaseUrl` should be updated with the server enumeration in [environment.ts](src%2Fenvironments%2Fenvironment.ts)
|
||||
|
||||
|
||||
68
angular.json
68
angular.json
@@ -10,14 +10,16 @@
|
||||
"projectType": "application",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"builder": "@angular-devkit/build-angular:browser-esbuild",
|
||||
"options": {
|
||||
"preserveSymlinks": true,
|
||||
"outputPath": "build",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"tsConfig": "src/tsconfig.app.json",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"polyfills": [
|
||||
"zone.js"
|
||||
],
|
||||
"stylePreprocessorOptions": {
|
||||
"includePaths": [
|
||||
"src/app/webapp-common/shared/ui-components/styles/"
|
||||
@@ -57,9 +59,9 @@
|
||||
"@aws-crypto/crc32",
|
||||
"@aws-crypto/sha1-browser",
|
||||
"@aws-crypto/crc32c",
|
||||
"bowser",
|
||||
"filesize/lib/filesize.es6",
|
||||
"hex-rgb",
|
||||
"britecharts",
|
||||
"localforage",
|
||||
"dom-to-image",
|
||||
"ace-builds",
|
||||
@@ -67,9 +69,9 @@
|
||||
"taira",
|
||||
"base-64",
|
||||
"export-to-csv",
|
||||
"dompurify"
|
||||
"dompurify",
|
||||
"hammerjs"
|
||||
],
|
||||
"vendorChunk": true,
|
||||
"extractLicenses": false,
|
||||
"buildOptimizer": false,
|
||||
"sourceMap": true,
|
||||
@@ -89,7 +91,6 @@
|
||||
"sourceMap": false,
|
||||
"namedChunks": false,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
"fileReplacements": [
|
||||
{
|
||||
@@ -110,7 +111,6 @@
|
||||
"sourceMap": false,
|
||||
"namedChunks": false,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
"fileReplacements": [
|
||||
{
|
||||
@@ -129,27 +129,27 @@
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "trains-webapp:build",
|
||||
"proxyConfig": "proxy.config.js",
|
||||
"proxyConfig": "./proxy.config.mjs",
|
||||
"liveReload": false,
|
||||
"port": 4300
|
||||
"port": 4300,
|
||||
"buildTarget": "trains-webapp:build"
|
||||
},
|
||||
"configurations": {
|
||||
"appdev": {
|
||||
"browserTarget": "trains-webapp:build:appdev"
|
||||
"buildTarget": "trains-webapp:build:appdev"
|
||||
},
|
||||
"staging": {
|
||||
"browserTarget": "trains-webapp:build:demo"
|
||||
"buildTarget": "trains-webapp:build:demo"
|
||||
},
|
||||
"production": {
|
||||
"browserTarget": "trains-webapp:build:production"
|
||||
"buildTarget": "trains-webapp:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "trains-webapp:build"
|
||||
"buildTarget": "trains-webapp:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
@@ -157,7 +157,10 @@
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"karmaConfig": "./karma.conf.js",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"polyfills": [
|
||||
"zone.js",
|
||||
"zone.js/testing"
|
||||
],
|
||||
"stylePreprocessorOptions": {
|
||||
"includePaths": [
|
||||
"src/app/webapp-common/shared/ui-components/styles/"
|
||||
@@ -198,14 +201,16 @@
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"builder": "@angular-devkit/build-angular:browser-esbuild",
|
||||
"options": {
|
||||
"preserveSymlinks": true,
|
||||
"outputPath": "dist/report-widgets",
|
||||
"baseHref": "widgets",
|
||||
"index": "src/app/webapp-common/clearml-applications/report-widgets/src/index.html",
|
||||
"main": "src/app/webapp-common/clearml-applications/report-widgets/src/main.ts",
|
||||
"polyfills": "src/app/webapp-common/clearml-applications/report-widgets/src/polyfills.ts",
|
||||
"polyfills": [
|
||||
"zone.js"
|
||||
],
|
||||
"tsConfig": "src/app/webapp-common/clearml-applications/report-widgets/tsconfig.app.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
@@ -226,7 +231,20 @@
|
||||
"inject": false
|
||||
}
|
||||
],
|
||||
"scripts": []
|
||||
"scripts": [],
|
||||
"allowedCommonJsDependencies": [
|
||||
"string-to-color",
|
||||
"dom-to-image",
|
||||
"dompurify",
|
||||
"url",
|
||||
"taira",
|
||||
"@aws-crypto/crc32",
|
||||
"@aws-crypto/crc32c",
|
||||
"@aws-crypto/sha1-browser",
|
||||
"@aws-crypto/sha256-browser",
|
||||
"fast-xml-parser",
|
||||
"bowser"
|
||||
]
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
@@ -253,7 +271,6 @@
|
||||
"development": {
|
||||
"buildOptimizer": false,
|
||||
"optimization": false,
|
||||
"vendorChunk": true,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
"namedChunks": true
|
||||
@@ -265,13 +282,13 @@
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "report-widgets:build:production",
|
||||
"headers": {
|
||||
"Content-Security-Policy": "frame-ancestors *"
|
||||
}
|
||||
},
|
||||
"buildTarget": "report-widgets:build:production"
|
||||
},
|
||||
"development": {
|
||||
"browserTarget": "report-widgets:build:development"
|
||||
"buildTarget": "report-widgets:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
@@ -279,14 +296,17 @@
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "report-widgets:build"
|
||||
"buildTarget": "report-widgets:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "src/app/webapp-common/clearml-applications/report-widgets/src/test.ts",
|
||||
"polyfills": "src/app/webapp-common/clearml-applications/report-widgets/src/polyfills.ts",
|
||||
"polyfills": [
|
||||
"zone.js",
|
||||
"zone.js/testing"
|
||||
],
|
||||
"tsConfig": "src/app/webapp-common/clearml-applications/report-widgets/tsconfig.spec.json",
|
||||
"karmaConfig": "src/app/webapp-common/clearml-applications/report-widgets/karma.conf.js",
|
||||
"inlineStyleLanguage": "scss",
|
||||
|
||||
8323
package-lock.json
generated
8323
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
136
package.json
136
package.json
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "clearml-webapp",
|
||||
"version": "1.14.0",
|
||||
"version": "1.15.0",
|
||||
"license": "",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "npx ng serve",
|
||||
"start-widgets": "npx ng serve --port 4201 --project report-widgets --proxy-config proxy.config.js --live-reload false",
|
||||
"hmr": "npx ng serve --proxy-config proxy.config.js --hmr --port 4300",
|
||||
"build": "npx ng build --configuration production --source-map --vendor-chunk",
|
||||
"start-widgets": "npx ng serve --port 4201 --project report-widgets --proxy-config proxy.config.mjs --live-reload false",
|
||||
"hmr": "npx ng serve --live-reload true",
|
||||
"build": "npx ng build --configuration production",
|
||||
"build-dev": "node ./node_modules/.bin/ng build --extract-css=false",
|
||||
"build-widgets": "npx ng build --project report-widgets --configuration production",
|
||||
"fetch": "./scripts/get-remote-build.sh",
|
||||
@@ -19,100 +19,102 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^16.2.12",
|
||||
"@angular/cdk": "^16.2.11",
|
||||
"@angular/common": "^16.2.12",
|
||||
"@angular/compiler": "^16.2.12",
|
||||
"@angular/core": "^16.2.12",
|
||||
"@angular/forms": "^16.2.12",
|
||||
"@angular/material": "^16.2.11",
|
||||
"@angular/platform-browser": "^16.2.12",
|
||||
"@angular/platform-browser-dynamic": "^16.2.12",
|
||||
"@angular/platform-server": "^16.2.12",
|
||||
"@angular/router": "^16.2.12",
|
||||
"@angular/service-worker": "^16.2.12",
|
||||
"@angular/youtube-player": "^16.2.11",
|
||||
"@aws-sdk/client-s3": "^3.441.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.441.0",
|
||||
"@angular/animations": "^17.1.1",
|
||||
"@angular/cdk": "^17.1.1",
|
||||
"@angular/common": "^17.1.1",
|
||||
"@angular/compiler": "^17.1.1",
|
||||
"@angular/core": "^17.1.1",
|
||||
"@angular/forms": "^17.1.1",
|
||||
"@angular/material": "^17.1.1",
|
||||
"@angular/platform-browser": "^17.1.1",
|
||||
"@angular/platform-browser-dynamic": "^17.1.1",
|
||||
"@angular/platform-server": "^17.1.1",
|
||||
"@angular/router": "^17.1.1",
|
||||
"@angular/service-worker": "^17.1.1",
|
||||
"@angular/youtube-player": "^17.1.1",
|
||||
"@aws-sdk/client-s3": "^3.499.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.499.0",
|
||||
"@ctrl/ngx-github-buttons": "^9.0.0",
|
||||
"@ctrl/tinycolor": "^4.0.2",
|
||||
"@ctrl/tinycolor": "^4.0.3",
|
||||
"@ngneat/dag": "^2.0.0",
|
||||
"@ngrx/effects": "^16.3.0",
|
||||
"@ngrx/entity": "^16.3.0",
|
||||
"@ngrx/router-store": "^16.3.0",
|
||||
"@ngrx/store": "^16.3.0",
|
||||
"ace-builds": "^1.31.1",
|
||||
"angular-google-tag-manager": "^1.8.0",
|
||||
"@ngrx/effects": "^17.1.0",
|
||||
"@ngrx/entity": "^17.1.0",
|
||||
"@ngrx/router-store": "^17.1.0",
|
||||
"@ngrx/store": "^17.1.0",
|
||||
"ace-builds": "^1.32.3",
|
||||
"angular-google-tag-manager": "^1.9.0",
|
||||
"angular-resizable-element": "^7.0.2",
|
||||
"angular-split": "^16.2.1",
|
||||
"angular-split": "^17.1.1",
|
||||
"ansi-to-html": "^0.7.2",
|
||||
"bootstrap": "^5.3.2",
|
||||
"chart.js": "^4.4.0",
|
||||
"chart.js": "^4.4.1",
|
||||
"chartjs-adapter-date-fns": "^3.0.0",
|
||||
"chartjs-plugin-annotation": "^3.0.1",
|
||||
"chartjs-plugin-zoom": "^2.0.1",
|
||||
"curved-arrows": "^0.1.0",
|
||||
"d3-selection": "^3.0.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"date-fns": "^3.3.1",
|
||||
"diff": "^5.1.0",
|
||||
"dom-to-image": "^2.6.0",
|
||||
"dompurify": "^3.0.6",
|
||||
"export-to-csv": "^1.2.1",
|
||||
"export-to-csv": "^1.2.2",
|
||||
"filesize": "^10.1.0",
|
||||
"dompurify": "^3.0.8",
|
||||
"has-ansi": "^5.0.1",
|
||||
"hocon-parser": "^1.0.1",
|
||||
"localforage": "^1.10.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lucene": "^2.1.1",
|
||||
"marked": "^7.0.5",
|
||||
"ng2-charts": "^5.0.3",
|
||||
"marked": "^11.1.1",
|
||||
"ng2-charts": "^5.0.4",
|
||||
"ngx-clipboard": "^16.0.0",
|
||||
"ngx-color-picker": "^15.0.0",
|
||||
"ngx-device-detector": "^6.0.2",
|
||||
"ngx-color-picker": "^16.0.0",
|
||||
"ngx-device-detector": "^7.0.0",
|
||||
"ngx-markdown-editor": "^5.3.4",
|
||||
"ngx-print": "^1.3.1",
|
||||
"ngx-print": "^1.5.1",
|
||||
"ngx-window-token": "^7.0.0",
|
||||
"object-hash": "^3.0.0",
|
||||
"primeicons": "^6.0.1",
|
||||
"primeng": "^16.7.1",
|
||||
"primeng": "^17.4.0",
|
||||
"rxjs": "^7.8.1",
|
||||
"string-to-color": "^2.2.2",
|
||||
"taira": "^3.2.2",
|
||||
"tslib": "^2.6.2",
|
||||
"url": "^0.11.3",
|
||||
"uuid": "^9.0.1",
|
||||
"zone.js": "~0.14.2"
|
||||
"zone.js": "~0.14.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^16.2.9",
|
||||
"@angular-devkit/core": "^16.2.9",
|
||||
"@angular-devkit/schematics": "^16.2.9",
|
||||
"@angular-devkit/schematics-cli": "^16.2.9",
|
||||
"@angular-eslint/eslint-plugin": "16.2.0",
|
||||
"@angular-eslint/eslint-plugin-template": "16.2.0",
|
||||
"@angular-eslint/template-parser": "16.2.0",
|
||||
"@angular/cli": "^16.2.8",
|
||||
"@angular/compiler-cli": "^16.2.11",
|
||||
"@angular/language-service": "^16.2.11",
|
||||
"@fortawesome/fontawesome-free": "^6.4.2",
|
||||
"@ngrx/eslint-plugin": "^16.3.0",
|
||||
"@ngrx/schematics": "^16.3.0",
|
||||
"@ngrx/store-devtools": "^16.3.0",
|
||||
"@types/d3-selection": "^3.0.8",
|
||||
"@types/dom-to-image": "^2.6.6",
|
||||
"@types/has-ansi": "^5.0.1",
|
||||
"@types/jasmine": "^5.1.1",
|
||||
"@types/lodash-es": "^4.17.10",
|
||||
"@types/node": "^18.18.8",
|
||||
"@types/plotly.js": "^2.12.29",
|
||||
"@types/tinycolor2": "^1.4.5",
|
||||
"@types/uuid": "^9.0.6",
|
||||
"@typescript-eslint/eslint-plugin": "6.9.1",
|
||||
"@typescript-eslint/parser": "6.9.1",
|
||||
"eslint": "^8.53.0",
|
||||
"eslint-plugin-import": "^2.29.0",
|
||||
"eslint-plugin-jsdoc": "^46.8.2",
|
||||
"@angular-devkit/build-angular": "^17.1.1",
|
||||
"@angular-devkit/core": "^17.1.1",
|
||||
"@angular-devkit/schematics": "^17.1.1",
|
||||
"@angular-devkit/schematics-cli": "^17.1.1",
|
||||
"@angular-eslint/builder": "17.2.1",
|
||||
"@angular-eslint/eslint-plugin": "17.2.1",
|
||||
"@angular-eslint/eslint-plugin-template": "17.2.1",
|
||||
"@angular-eslint/schematics": "17.2.1",
|
||||
"@angular-eslint/template-parser": "17.2.1",
|
||||
"@angular/cli": "^17.1.1",
|
||||
"@angular/compiler-cli": "^17.1.1",
|
||||
"@angular/language-service": "^17.1.1",
|
||||
"@fortawesome/fontawesome-free": "^6.5.1",
|
||||
"@ngrx/eslint-plugin": "^17.1.0",
|
||||
"@ngrx/schematics": "^17.1.0",
|
||||
"@ngrx/store-devtools": "^17.1.0",
|
||||
"@types/d3-selection": "^3.0.10",
|
||||
"@types/dom-to-image": "^2.6.7",
|
||||
"@types/has-ansi": "^5.0.2",
|
||||
"@types/jasmine": "^5.1.4",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^20.11.6",
|
||||
"@types/plotly.js": "^2.12.32",
|
||||
"@types/tinycolor2": "^1.4.6",
|
||||
"@types/uuid": "^9.0.7",
|
||||
"@typescript-eslint/eslint-plugin": "^6.19.1",
|
||||
"@typescript-eslint/parser": "^6.19.1",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jsdoc": "^48.0.3",
|
||||
"eslint-plugin-prefer-arrow": "^1.2.3",
|
||||
"typescript": "~5.1.6"
|
||||
"typescript": "~5.3.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const fs = require('fs');
|
||||
import * as fs from 'fs';
|
||||
|
||||
const targets = [
|
||||
'https://api.trains-master.hosted.allegro.ai', // 1
|
||||
'http://localhost:8081', // 1
|
||||
];
|
||||
|
||||
const PROXY_CONFIG = {
|
||||
@@ -30,15 +30,17 @@ const PROXY_CONFIG = {
|
||||
};
|
||||
|
||||
targets.forEach((target, i) => {
|
||||
const path = `/service/${i+1}/api`;
|
||||
PROXY_CONFIG[path + '/*'] = {
|
||||
const path = `/service/${i + 1}/api`;
|
||||
PROXY_CONFIG[path] = {
|
||||
target: target,
|
||||
secure: false,
|
||||
secure: true,
|
||||
changeOrigin: true,
|
||||
cookieDomainRewrite: 'localhost',
|
||||
logLevel: 'debug',
|
||||
pathRewrite: {
|
||||
[path]: ''
|
||||
[`^${path}`]: ''
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
module.exports = PROXY_CONFIG;
|
||||
export default PROXY_CONFIG;
|
||||
@@ -66,13 +66,11 @@ export const routes: Routes = [
|
||||
},
|
||||
{
|
||||
path: 'pipelines',
|
||||
// canActivate: [RolePermissionsGuard],
|
||||
data: {search: true},
|
||||
loadChildren: () => import('@common/pipelines/pipelines.module').then(m => m.PipelinesModule),
|
||||
},
|
||||
{
|
||||
path: 'pipelines',
|
||||
// canActivate: [RolePermissionsGuard],
|
||||
data: {search: true},
|
||||
children: [
|
||||
{
|
||||
|
||||
@@ -3,5 +3,5 @@ import { StoreDevtoolsModule } from '@ngrx/store-devtools';
|
||||
export const extCoreModules = [
|
||||
StoreDevtoolsModule.instrument({
|
||||
maxAge: 50
|
||||
})
|
||||
, connectInZone: true})
|
||||
];
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<sm-search-results-page
|
||||
(projectSelected)="projectCardClicked($event)"
|
||||
(experimentSelected)="taskSelected($event)"
|
||||
(modelSelected)="modelSelected($event)"
|
||||
(pipelineSelected)="pipelineSelected($event)"
|
||||
(reportSelected)="reportSelected($event)"
|
||||
(activeLinkChanged)="changeActiveLink($event)"
|
||||
(openDatasetSelected)="openDatasetCardClicked($event)"
|
||||
(loadMoreClicked)="loadMore()"
|
||||
[projectsList]="projectsResults$ | async"
|
||||
[experimentsList]="experimentsResults$ | async"
|
||||
[modelsList]="modelsResults$ | async"
|
||||
[datasetsList]="datasetsResults$ | async"
|
||||
[pipelinesList]="pipelinesResults$ | async"
|
||||
[reportsList]="reportsResults$ | async"
|
||||
[activeLink]="activeLink"
|
||||
[resultsCount]="resultsCount$ | async"
|
||||
></sm-search-results-page>
|
||||
@@ -0,0 +1,24 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {DashboardSearchBaseComponent} from '@common/dashboard/dashboard-search.component.base';
|
||||
import {selectIsSearching} from '@common/common-search/common-search.reducer';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
import {debounceTime, filter} from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'sm-dashboard-search',
|
||||
templateUrl: './dashboard-search.component.html',
|
||||
styleUrls: ['./dashboard-search.component.scss'],
|
||||
})
|
||||
export class DashboardSearchComponent extends DashboardSearchBaseComponent {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.store.select(selectIsSearching)
|
||||
.pipe(
|
||||
debounceTime(200),
|
||||
takeUntilDestroyed(),
|
||||
filter(active => !active)
|
||||
)
|
||||
.subscribe(() => this.router.navigate(['dashboard'], ));
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,15 @@
|
||||
<div class="search-container">
|
||||
<div class="d-flex-center tabs">
|
||||
<div class="ps-3 py-3">
|
||||
<ng-container *ngFor="let searchTab of activeLinksList">
|
||||
<span [class.active]="activeLink === searchTab.name" class="pointer category-link"
|
||||
(click)="activeLinkChanged.emit(searchTab.name)">{{searchTab.label}} ({{resultsCount?.[searchTab.name]}}) </span>
|
||||
</ng-container>
|
||||
@for (searchTab of activeLinksList; track searchTab.name) {
|
||||
<span
|
||||
class="pointer category-link"
|
||||
[class.active]="activeLink === searchTab.name"
|
||||
[tabindex]="$count"
|
||||
(click)="activeLinkChanged.emit(searchTab.name)"
|
||||
(keyup)="activeLinkChanged.emit(searchTab.name)"
|
||||
>{{searchTab.label}} ({{resultsCount?.[searchTab.name]}})</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-container">
|
||||
@@ -4,7 +4,7 @@ import {Task} from '~/business-logic/model/tasks/task';
|
||||
import {ITask} from '~/business-logic/model/al-task';
|
||||
import {Model} from '~/business-logic/model/models/model';
|
||||
import {activeLinksList, ActiveSearchLink, activeSearchLink} from '~/features/dashboard-search/dashboard-search.consts';
|
||||
import {IReport} from '../../../../webapp-common/reports/reports.consts';
|
||||
import {IReport} from '@common/reports/reports.consts';
|
||||
|
||||
@Component({
|
||||
selector: 'sm-search-results-page',
|
||||
@@ -0,0 +1,22 @@
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CrumbTypeEnum} from '@common/layout/breadcrumbs/breadcrumbs.component';
|
||||
import {DashboardSearchComponent} from '~/features/dashboard-search/containers/dashboard-search/dashboard-search.component';
|
||||
|
||||
const staticBreadcrumb = [[{
|
||||
name: 'DASHBOARD',
|
||||
type: CrumbTypeEnum.Feature
|
||||
}]];
|
||||
|
||||
export const routes: Routes = [
|
||||
{path: '', component: DashboardSearchComponent, data: {staticBreadcrumb}},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes)
|
||||
],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class DashboardSearchRoutingModule {
|
||||
}
|
||||
@@ -1,37 +1,39 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {AsyncPipe} from '@angular/common';
|
||||
import {StoreModule} from '@ngrx/store';
|
||||
import {EffectsModule} from '@ngrx/effects';
|
||||
import {DashboardSearchEffects as commonDashboardSearchEffects } from '@common/dashboard-search/dashboard-search.effects';
|
||||
import {DashboardSearchEffects} from '~/features/dashboard/dashboard-search/dashboard-search.effects';
|
||||
import {ProjectsSharedModule} from '../../projects/shared/projects-shared.module';
|
||||
import {SharedModule} from '~/shared/shared.module';
|
||||
import {DashboardSearchEffects} from '~/features/dashboard-search/dashboard-search.effects';
|
||||
import {dashboardSearchReducer} from '@common/dashboard-search/dashboard-search.reducer';
|
||||
import {ReportsSharedModule} from '../../../webapp-common/reports/reports-shared.module';
|
||||
import {ScrollingModule} from '@angular/cdk/scrolling';
|
||||
import {ModelCardComponent} from '@common/shared/ui-components/panel/model-card/model-card.component';
|
||||
import {ExperimentCardComponent} from '@common/shared/ui-components/panel/experiment-card/experiment-card.component';
|
||||
import {CheckPermissionDirective} from '~/shared/directives/check-permission.directive';
|
||||
import {ProjectCardComponent} from '@common/shared/ui-components/panel/project-card/project-card.component';
|
||||
import {PipelineCardComponent} from '@common/pipelines/pipeline-card/pipeline-card.component';
|
||||
import {VirtualGridComponent} from '@common/shared/components/virtual-grid/virtual-grid.component';
|
||||
import {SearchResultsPageComponent} from '~/features/dashboard-search/containers/search-results-page/search-results-page.component';
|
||||
import {DashboardSearchComponent} from '~/features/dashboard-search/containers/dashboard-search/dashboard-search.component';
|
||||
import {DatasetsSharedModule} from '~/features/datasets/shared/datasets-shared.module';
|
||||
import {ReportCardComponent} from '@common/reports/report-card/report-card.component';
|
||||
import {DashboardSearchRoutingModule} from '~/features/dashboard-search/dashboard-search-routing.module';
|
||||
|
||||
@NgModule({
|
||||
imports : [
|
||||
CommonModule,
|
||||
ProjectsSharedModule,
|
||||
ReportsSharedModule,
|
||||
imports: [
|
||||
StoreModule.forFeature('search', dashboardSearchReducer),
|
||||
EffectsModule.forFeature([DashboardSearchEffects, commonDashboardSearchEffects]),
|
||||
SharedModule,
|
||||
ScrollingModule,
|
||||
ModelCardComponent,
|
||||
ExperimentCardComponent,
|
||||
DashboardSearchRoutingModule,
|
||||
AsyncPipe,
|
||||
VirtualGridComponent,
|
||||
CheckPermissionDirective,
|
||||
ProjectCardComponent,
|
||||
ExperimentCardComponent,
|
||||
ModelCardComponent,
|
||||
PipelineCardComponent,
|
||||
VirtualGridComponent
|
||||
DatasetsSharedModule,
|
||||
ReportCardComponent,
|
||||
],
|
||||
declarations:[
|
||||
SearchResultsPageComponent, DashboardSearchComponent
|
||||
]
|
||||
})
|
||||
export class DashboardSearchModule {
|
||||
}
|
||||
export class DashboardSearchModule {}
|
||||
@@ -3,12 +3,17 @@ import {NgModule} from '@angular/core';
|
||||
import {DashboardComponent} from './dashboard.component';
|
||||
import {CrumbTypeEnum} from '@common/layout/breadcrumbs/breadcrumbs.component';
|
||||
|
||||
export const routes: Routes = [
|
||||
{path: '', component: DashboardComponent, data:{staticBreadcrumb:[[{
|
||||
const staticBreadcrumb = [[{
|
||||
name: 'DASHBOARD',
|
||||
type: CrumbTypeEnum.Feature
|
||||
}]]}
|
||||
}
|
||||
}]];
|
||||
|
||||
export const routes: Routes = [
|
||||
{path: '', component: DashboardComponent, data: {staticBreadcrumb}},
|
||||
{
|
||||
path: 'search',
|
||||
loadChildren: () => import('~/features/dashboard-search/dashboard-search.module').then(m => m.DashboardSearchModule),
|
||||
data: {staticBreadcrumb}}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<sm-dashboard-search-base [class.dashboard-search]="activeSearch$ | async"></sm-dashboard-search-base>
|
||||
<div *ngIf="(activeSearch$ | async) !== true" class="dashboard-body">
|
||||
<div class="dashboard-body">
|
||||
<div class="recent">
|
||||
<sm-dashboard-projects (width)="setWidth($event)"></sm-dashboard-projects>
|
||||
<sm-dashboard-experiments
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import {Component, OnDestroy, OnInit} from '@angular/core';
|
||||
import {Observable, Subscription} from 'rxjs';
|
||||
import {Component, inject, OnDestroy, OnInit} from '@angular/core';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {selectShowOnlyUserWork} from '@common/core/reducers/users-reducer';
|
||||
import {GetCurrentUserResponseUserObjectCompany} from '~/business-logic/model/users/getCurrentUserResponseUserObjectCompany';
|
||||
import {filter, skip, take} from 'rxjs/operators';
|
||||
import {setDeep} from '@common/core/actions/projects.actions';
|
||||
import {getRecentProjects, getRecentExperiments} from '@common/dashboard/common-dashboard.actions';
|
||||
@@ -11,8 +10,10 @@ import {selectFirstLogin} from '@common/core/reducers/view.reducer';
|
||||
import {MatDialog} from '@angular/material/dialog';
|
||||
import {WelcomeMessageComponent} from '@common/layout/welcome-message/welcome-message.component';
|
||||
import {firstLogin} from '@common/core/actions/layout.actions';
|
||||
import {IRecentTask, selectRecentTasks} from '@common/dashboard/common-dashboard.reducer';
|
||||
import {selectActiveSearch} from '@common/dashboard-search/dashboard-search.reducer';
|
||||
import {selectRecentTasks} from '@common/dashboard/common-dashboard.reducer';
|
||||
import {initSearch} from '@common/common-search/common-search.actions';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
import {selectActiveSearch} from '@common/common-search/common-search.reducer';
|
||||
|
||||
|
||||
@Component({
|
||||
@@ -21,26 +22,31 @@ import {selectActiveSearch} from '@common/dashboard-search/dashboard-search.redu
|
||||
styleUrls: ['./dashboard.component.scss']
|
||||
})
|
||||
export class DashboardComponent implements OnInit, OnDestroy {
|
||||
public activeSearch$: Observable<boolean>;
|
||||
public recentTasks$: Observable<Array<IRecentTask>>;
|
||||
public workspace: GetCurrentUserResponseUserObjectCompany;
|
||||
private store = inject(Store);
|
||||
private router = inject(Router);
|
||||
private activatedRoute = inject(ActivatedRoute);
|
||||
private dialog = inject(MatDialog);
|
||||
public recentTasks$ = this.store.select(selectRecentTasks);
|
||||
public width: number;
|
||||
private welcomeSub: Subscription;
|
||||
private showOnlyUserWorkSub: Subscription;
|
||||
private subscriptions = new Subscription();
|
||||
|
||||
constructor(
|
||||
private store: Store<any>,
|
||||
private router: Router,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private dialog: MatDialog,
|
||||
) {
|
||||
this.activeSearch$ = this.store.select(selectActiveSearch);
|
||||
this.recentTasks$ = this.store.select(selectRecentTasks);
|
||||
constructor() {
|
||||
this.store.dispatch(initSearch({payload: 'Search for all'}));
|
||||
|
||||
this.showOnlyUserWorkSub = this.store.select(selectShowOnlyUserWork).pipe(skip(1)).subscribe(() => {
|
||||
this.store.select(selectActiveSearch)
|
||||
.pipe(
|
||||
takeUntilDestroyed(),
|
||||
filter(active => active)
|
||||
)
|
||||
.subscribe(() => this.router.navigate(['search'], {relativeTo: this.activatedRoute, queryParamsHandling: 'preserve'}));
|
||||
|
||||
this.subscriptions.add(this.store.select(selectShowOnlyUserWork).pipe(
|
||||
skip(1),
|
||||
).subscribe(() => {
|
||||
this.store.dispatch(getRecentProjects());
|
||||
this.store.dispatch(getRecentExperiments());
|
||||
});
|
||||
}));
|
||||
|
||||
this.welcomeSub = this.store.select(selectFirstLogin)
|
||||
.pipe(
|
||||
|
||||
@@ -7,19 +7,15 @@ import {StoreModule} from '@ngrx/store';
|
||||
import {GettingStartedCardComponent} from './dumb/getting-started-card/getting-started-card.component';
|
||||
import {CommonDashboardModule} from '@common/dashboard/common-dashboard.module';
|
||||
import {commonDashboardReducer} from '@common/dashboard/common-dashboard.reducer';
|
||||
import {SearchResultsPageComponent} from './dumb/search-results-page/search-results-page.component';
|
||||
import {SharedModule} from '~/shared/shared.module';
|
||||
import {DashboardSearchModule} from './dashboard-search/dashboard-search.module';
|
||||
import {ProjectDialogModule} from '@common/shared/project-dialog/project-dialog.module';
|
||||
import {ProjectsSharedModule} from '../projects/shared/projects-shared.module';
|
||||
import {DashboardSearchBaseComponent} from '@common/dashboard/dashboard-search.component.base';
|
||||
import {DatasetsSharedModule} from '~/features/datasets/shared/datasets-shared.module';
|
||||
import {ScrollingModule} from '@angular/cdk/scrolling';
|
||||
import {CheckPermissionDirective} from '~/shared/directives/check-permission.directive';
|
||||
import {PlusCardComponent} from '@common/shared/ui-components/panel/plus-card/plus-card.component';
|
||||
import {NeonButtonComponent} from '@common/shared/ui-components/buttons/neon-button/neon-button.component';
|
||||
import {OverflowsDirective} from '@common/shared/ui-components/directives/overflows.directive';
|
||||
import {ReportsSharedModule} from '@common/reports/reports-shared.module';
|
||||
import {PipelineCardComponent} from '@common/pipelines/pipeline-card/pipeline-card.component';
|
||||
import {ModelCardComponent} from '@common/shared/ui-components/panel/model-card/model-card.component';
|
||||
import {ExperimentCardComponent} from '@common/shared/ui-components/panel/experiment-card/experiment-card.component';
|
||||
@@ -36,21 +32,19 @@ import {VirtualGridComponent} from '@common/shared/components/virtual-grid/virtu
|
||||
StoreModule.forFeature('dashboard', commonDashboardReducer),
|
||||
CommonDashboardModule,
|
||||
SharedModule,
|
||||
DashboardSearchModule,
|
||||
DatasetsSharedModule,
|
||||
ScrollingModule,
|
||||
CheckPermissionDirective,
|
||||
PlusCardComponent,
|
||||
NeonButtonComponent,
|
||||
OverflowsDirective,
|
||||
ReportsSharedModule,
|
||||
PipelineCardComponent,
|
||||
ModelCardComponent,
|
||||
ExperimentCardComponent,
|
||||
ProjectCardComponent,
|
||||
VirtualGridComponent
|
||||
],
|
||||
declarations : [DashboardComponent, GettingStartedCardComponent, DashboardSearchBaseComponent, SearchResultsPageComponent]
|
||||
declarations : [DashboardComponent, GettingStartedCardComponent]
|
||||
})
|
||||
export class DashboardModule {
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ import {ClickStopPropagationDirective} from '@common/shared/ui-components/direct
|
||||
import {FilterPipe} from '@common/shared/pipes/filter.pipe';
|
||||
import {ShowTooltipIfEllipsisDirective} from '@common/shared/ui-components/indicators/tooltip/show-tooltip-if-ellipsis.directive';
|
||||
import {MatCheckboxModule} from '@angular/material/checkbox';
|
||||
import {DotsLoadMoreComponent} from '@common/shared/ui-components/indicators/dots-load-more/dots-load-more.component';
|
||||
|
||||
export const experimentSyncedKeys = [
|
||||
'view.projectColumnsSortOrder',
|
||||
@@ -107,7 +108,12 @@ export const getExperimentsConfig = (userPreferences: UserPreferences) => ({
|
||||
return merge({}, nextState, savedState);
|
||||
}
|
||||
if (action.type.startsWith(EXPERIMENTS_PREFIX)) {
|
||||
localStorage.setItem(localStorageKey, JSON.stringify(pick(nextState, ['view.tableMode', 'view.compareSelectedMetrics', 'view.compareSelectedMetricsPlots'])));
|
||||
localStorage.setItem(localStorageKey, JSON.stringify(pick(nextState, [
|
||||
'view.tableMode',
|
||||
'view.tableCompareView',
|
||||
'view.compareSelectedMetrics',
|
||||
'view.compareSelectedMetricsPlots'
|
||||
])));
|
||||
}
|
||||
return nextState;
|
||||
};
|
||||
@@ -189,6 +195,7 @@ const DECLARATIONS = [
|
||||
FilterPipe,
|
||||
ShowTooltipIfEllipsisDirective,
|
||||
MatCheckboxModule,
|
||||
DotsLoadMoreComponent,
|
||||
],
|
||||
declarations : [...DECLARATIONS],
|
||||
providers : [
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
export {PROJECT_ROUTES} from '@common/projects/common-projects.consts';
|
||||
import {HeaderNavbarTabConfig} from '@common/layout/header-navbar-tabs/header-navbar-tabs-config.types';
|
||||
|
||||
export const PROJECTS_FEATURES = ['models',' experiments', 'overview'];
|
||||
|
||||
export const PROJECT_ROUTES = [
|
||||
{header: 'overview', subHeader: '', id: 'overviewTab'},
|
||||
{header: 'experiments', subHeader: '(ARCHIVED)', id: 'experimentsTab'},
|
||||
{header: 'models', subHeader: '(ARCHIVED)', id: 'modelsTab'}
|
||||
] as HeaderNavbarTabConfig[];
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<div class="title">APP CREDENTIALS <i
|
||||
<header><div class="title">APP CREDENTIALS <i
|
||||
class="fas fa-info-circle info"
|
||||
smTooltip="Credentials providing API access to this workspace"
|
||||
matTooltipPosition="above"></i>
|
||||
</div>
|
||||
</div></header>
|
||||
<div class="section mb-4" *ngFor="let cred of credentials$ | async | keyvalue">
|
||||
<sm-admin-credential-table
|
||||
[credentials]="cred?.value"
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
@import "variables";
|
||||
|
||||
:host {
|
||||
max-width: 900px;
|
||||
|
||||
header {
|
||||
padding: 20px 24px;
|
||||
}
|
||||
.title {
|
||||
color: $white;
|
||||
font-size: 16px;
|
||||
@@ -15,7 +17,7 @@
|
||||
}
|
||||
|
||||
.section {
|
||||
margin: 6px 0 0 24px;
|
||||
//margin: 6px 0 0 24px;
|
||||
}
|
||||
|
||||
.info {
|
||||
@@ -28,7 +30,7 @@
|
||||
}
|
||||
|
||||
.add-button {
|
||||
margin-top: 18px;
|
||||
margin: 24px 0 48px 24px;
|
||||
color: $blue-280;
|
||||
|
||||
&:hover {
|
||||
|
||||
@@ -27,7 +27,7 @@ export class UserCredentialsComponent implements OnInit, OnDestroy {
|
||||
.pipe(filter(user => !!user), take(1))
|
||||
.subscribe(user => {
|
||||
this.user = user;
|
||||
this.store.dispatch(getAllCredentials());
|
||||
this.store.dispatch(getAllCredentials({userId: ''}));
|
||||
});
|
||||
this.credentials$ = this.store.select(selectCredentials);
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import {ProfilePreferencesComponent} from '@common/settings/admin/profile-prefer
|
||||
import {ProfileNameComponent} from '@common/settings/admin/profile-name/profile-name.component';
|
||||
import {AdminFooterComponent} from '@common/settings/admin/admin-footer/admin-footer.component';
|
||||
import {S3AccessComponent} from '@common/settings/admin/s3-access/s3-access.component';
|
||||
import {AdminDialogTemplateComponent} from '@common/settings/admin/admin-dialog-template/admin-dialog-template.component';
|
||||
import {AdminCredentialTableComponent} from '@common/settings/admin/admin-credential-table/admin-credential-table.component';
|
||||
import {AdminFooterActionsComponent} from '~/features/settings/containers/admin/admin-footer-actions/admin-footer-actions.component';
|
||||
import {UserCredentialsComponent} from '~/features/settings/containers/admin/user-credentials/user-credentials.component';
|
||||
@@ -37,6 +36,9 @@ import {MatButtonToggleModule} from '@angular/material/button-toggle';
|
||||
import {KeyValuePipe} from '@common/shared/pipes/key-value.pipe';
|
||||
import {ButtonToggleComponent} from '@common/shared/ui-components/inputs/button-toggle/button-toggle.component';
|
||||
import {LabelValuePipe} from '@common/shared/pipes/label-value.pipe';
|
||||
import {AdminDialogTemplateComponent} from '@common/settings/admin/admin-dialog-template/admin-dialog-template.component';
|
||||
import {TimeAgoPipe} from '@common/shared/pipes/timeAgo';
|
||||
import {ShowTooltipIfEllipsisDirective} from '@common/shared/ui-components/indicators/tooltip/show-tooltip-if-ellipsis.directive';
|
||||
|
||||
|
||||
|
||||
@@ -48,7 +50,6 @@ import {LabelValuePipe} from '@common/shared/pipes/label-value.pipe';
|
||||
UserCredentialsComponent,
|
||||
AdminFooterActionsComponent,
|
||||
AdminCredentialTableComponent,
|
||||
AdminDialogTemplateComponent,
|
||||
S3AccessComponent,
|
||||
CreateCredentialDialogComponent,
|
||||
AdminFooterComponent,
|
||||
@@ -82,7 +83,10 @@ import {LabelValuePipe} from '@common/shared/pipes/label-value.pipe';
|
||||
MatButtonToggleModule,
|
||||
KeyValuePipe,
|
||||
ButtonToggleComponent,
|
||||
LabelValuePipe
|
||||
LabelValuePipe,
|
||||
AdminDialogTemplateComponent,
|
||||
TimeAgoPipe,
|
||||
ShowTooltipIfEllipsisDirective,
|
||||
],
|
||||
exports: [
|
||||
UserCredentialsComponent,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
@import "variables";
|
||||
@import "src/app/webapp-common/shared/ui-components/styles/variables.scss";
|
||||
@import "../../shared/ui-components/styles/variables";
|
||||
@import "./variables";
|
||||
|
||||
@font-face {
|
||||
font-family: '#{$icomoon-font-family}';
|
||||
src: url('./#{$icomoon-font-family}.ttf?1skk4q') format('truetype');
|
||||
src: url('./#{$icomoon-font-family}.ttf?hr04a1') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
@@ -24,6 +24,65 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.al-ico-queue {
|
||||
&:before {
|
||||
content: $al-ico-queue;
|
||||
}
|
||||
}
|
||||
.al-ico-weight {
|
||||
&:before {
|
||||
content: $al-ico-weight;
|
||||
}
|
||||
}
|
||||
.al-ico-link-plus {
|
||||
&:before {
|
||||
content: $al-ico-link-plus;
|
||||
}
|
||||
}
|
||||
.al-ico-drag-vertical {
|
||||
&:before {
|
||||
content: $al-ico-drag-vertical;
|
||||
}
|
||||
}
|
||||
.al-ico-drag-horizontal {
|
||||
&:before {
|
||||
content: $al-ico-drag-horizontal;
|
||||
}
|
||||
}
|
||||
.al-ico-admin-support {
|
||||
&:before {
|
||||
content: $al-ico-admin-support;
|
||||
}
|
||||
}
|
||||
.al-ico-scatter-view {
|
||||
&:before {
|
||||
content: $al-ico-scatter-view;
|
||||
}
|
||||
}.al-ico-scatter-view {
|
||||
&:before {
|
||||
content: $al-ico-scatter-view;
|
||||
}
|
||||
}
|
||||
.al-ico-schedulers {
|
||||
&:before {
|
||||
content: $al-ico-schedulers;
|
||||
}
|
||||
}
|
||||
.al-ico-triggers {
|
||||
&:before {
|
||||
content: $al-ico-triggers;
|
||||
}
|
||||
}
|
||||
.al-ico-autoscalers {
|
||||
&:before {
|
||||
content: $al-ico-autoscalers;
|
||||
}
|
||||
}
|
||||
.al-ico-automation {
|
||||
&:before {
|
||||
content: $al-ico-automation;
|
||||
}
|
||||
}
|
||||
.al-ico-charts-view:before {
|
||||
content: "\e9f9";
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -1,6 +1,17 @@
|
||||
$icomoon-font-family: "trains" !default;
|
||||
$icomoon-font-path: "fonts" !default;
|
||||
|
||||
$al-ico-queue: "\ea01";
|
||||
$al-ico-weight: "\ea05";
|
||||
$al-ico-link-plus: "\ea02";
|
||||
$al-ico-drag-vertical: "\ea03";
|
||||
$al-ico-drag-horizontal: "\ea04";
|
||||
$al-ico-admin-support: "\ea00";
|
||||
$al-ico-scatter-view: "\e9ff";
|
||||
$al-ico-schedulers: "\e9fb";
|
||||
$al-ico-triggers: "\e9fc";
|
||||
$al-ico-autoscalers: "\e9fd";
|
||||
$al-ico-automation: "\e9fe";
|
||||
$al-ico-charts-view: "\e9f9";
|
||||
$al-ico-compact-view: "\e9fa";
|
||||
$al-ico-tune: "\e9f8";
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
<ng-container *ngIf="activated; else placeHolder" [ngSwitch]="type">
|
||||
<ng-container *ngIf="(signIsNeeded$ | async) === false; else signIsNeededTemplate">
|
||||
<ng-container *ngIf="(noPermissions$ | async) === false; else noPermissionsTemplate">
|
||||
|
||||
@if (activated) {
|
||||
@if (signIsNeeded$() === false) {
|
||||
@if (noPermissions$() === false) {
|
||||
<a class="webapp-link" [href]="webappLink" target="_blank" [class.dark-theme]="isDarkTheme">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<path d="m2.67,14.66c-.74,0-1.33-.6-1.33-1.33h0V2.67c0-.74.6-1.33,1.33-1.33h5.33v1.33H2.67v10.67h10.67v-5.33h1.34v5.33c0,.74-.6,1.33-1.34,1.33H2.67Zm4.86-7.14l4.86-4.86h-1.72v-1.33h4v4h-1.33v-1.72l-4.86,4.86-.95-.94Z" fill="#8492c2"/>
|
||||
</svg>
|
||||
<span class="webapp-link_tooltip">View original resource</span>
|
||||
</a>
|
||||
<ng-container [ngSwitch]="type">
|
||||
<ng-container *ngSwitchCase="type === 'plot' || type === 'scalar' ? type : ''">
|
||||
@switch (type) {
|
||||
@case (type === 'plot' || type === 'scalar' ? type : '') {
|
||||
<sm-single-graph
|
||||
[hideDownloadButtons]="externalTool"
|
||||
[class.less-padding]="false"
|
||||
@@ -17,10 +16,10 @@
|
||||
[graphsNumber]="1"
|
||||
[height]="singleGraphHeight"
|
||||
[chart]="plotData"
|
||||
[id]="'lala'"
|
||||
[id]="'report-widget'"
|
||||
[isDarkTheme]="isDarkTheme"
|
||||
[showLoaderOnDraw]="false"
|
||||
[identifier]="'lala'"
|
||||
[identifier]="'report-widget'"
|
||||
[width]="400"
|
||||
[xAxisType]="xaxis"
|
||||
[isCompare]="true"
|
||||
@@ -29,63 +28,60 @@
|
||||
[hideMaximize]="hideMaximize"
|
||||
(maximizeClicked)="maximize()">
|
||||
</sm-single-graph>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchCase="'sample'">
|
||||
<sm-debug-image-snippet
|
||||
*ngIf="frame?.url"
|
||||
class="d-flex-center h-100"
|
||||
[frame]="frame"
|
||||
[noHoverEffects]="true"
|
||||
(imageClicked)="hideMaximize === 'show' && sampleClicked($event)">
|
||||
</sm-debug-image-snippet>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchCase="'parcoords'">
|
||||
<sm-parallel-coordinates-graph
|
||||
*ngIf="parcoordsData"
|
||||
[experiments]="parcoordsData.experiments"
|
||||
[parameters]="parcoordsData.params"
|
||||
[metric]="parcoordsData.metric"
|
||||
[metricValueType]="parcoordsData.valueType"
|
||||
[darkTheme]="isDarkTheme"
|
||||
[reportMode]="true"
|
||||
></sm-parallel-coordinates-graph>
|
||||
</ng-container>
|
||||
|
||||
<div *ngSwitchCase="'single'" class="single-value-summary-section">
|
||||
<sm-single-value-summary-table
|
||||
*ngIf="singleValueData && singleValueData[0]"
|
||||
[data]="singleValueData"
|
||||
[experimentName]="singleValueData[0]?.metric"
|
||||
[darkTheme]="isDarkTheme"
|
||||
></sm-single-value-summary-table>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-template #placeHolder>
|
||||
}
|
||||
@case ('sample') {
|
||||
@if (frame?.url) {
|
||||
<sm-debug-image-snippet
|
||||
class="d-flex-center h-100"
|
||||
[frame]="frame"
|
||||
[noHoverEffects]="true"
|
||||
(imageClicked)="hideMaximize === 'show' && sampleClicked($event)">
|
||||
</sm-debug-image-snippet>
|
||||
}
|
||||
}
|
||||
@case ('parcoords') {
|
||||
@if (parcoordsData) {
|
||||
<sm-parallel-coordinates-graph
|
||||
[experiments]="parcoordsData.experiments"
|
||||
[parameters]="parcoordsData.params"
|
||||
[metrics]="parcoordsData.metrics"
|
||||
[darkTheme]="isDarkTheme"
|
||||
[reportMode]="true"
|
||||
></sm-parallel-coordinates-graph>
|
||||
}
|
||||
}
|
||||
@case ('single') {
|
||||
<div class="single-value-summary-section">
|
||||
@if (singleValueData && singleValueData[0]) {
|
||||
<sm-single-value-summary-table
|
||||
[data]="singleValueData"
|
||||
[experimentName]="singleValueData[0]?.metric"
|
||||
[darkTheme]="isDarkTheme"
|
||||
></sm-single-value-summary-table>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
} @else {
|
||||
<div class="placeholder">Missing permissions to view this item.
|
||||
</div>
|
||||
}
|
||||
} @else {
|
||||
<div class="container" [class.dark-theme]="isDarkTheme">
|
||||
<div class="s3message">
|
||||
Missing S3 credentials. Please verify credentials in <a target="_blank" href="/settings/webapp-configuration">WEB APP CLOUD ACCESS</a> in clearml app.
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
} @else {
|
||||
<div class="placeholder">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48">
|
||||
<path
|
||||
d="M43.29,43.65H5.29c-.55,0-1-.45-1-1h0c0-.55,.45-1,1-1H43.29c.55,0,1,.45,1,1h0c0,.55-.45,1-1,1h0Zm1-27v-2h-3.5V5.65h-1V14.79l-8.5,5.04-4.5-5.12V5.65h-1V14.49l-4.7,8.48h-4.3V5.65h-1V22.97h-2.42l-4.58-12.22V5.65h-1v5.1h-3.5v2h3.12l.38,1.02v9.2h-3.5v2h3.5v14.68h1v-14.68h3.18l3.82,10.2v4.48h1v-4.84l5.5-9.84h3.54v14.68h1v-14.82l4-2.42,8.92,10.18v7.06h1v-7h3.54v-2h-3.5v-14h3.5ZM8.79,22.97v-6.52l2.44,6.52h-2.44Zm5.32,2h1.68v4.48l-1.68-4.48Zm2.68,5.72v-5.72h3.18l-3.18,5.72Zm6.58-7.72l2.42-4.36v4.36h-2.42Zm3.42-.46v-4.76l2.74,3.12-2.74,1.64Zm5.84-1.14l7.16-4.26v12.54l-7.16-8.28Z"
|
||||
fill="#5a658e"/>
|
||||
</svg>
|
||||
<span class="show-text" (click)="activate()">Show preview</span>
|
||||
<span class="show-text" tabindex="1" (click)="activate()" (keyup)="activate()">Show preview</span>
|
||||
</div>
|
||||
</ng-template>
|
||||
}
|
||||
|
||||
<ng-template #signIsNeededTemplate>
|
||||
<div class="container" [class.dark-theme]="isDarkTheme">
|
||||
<div class="s3message">
|
||||
Missing S3 credentials. Please verify credentials in <a target="_blank" href="/settings/webapp-configuration">WEB APP CLOUD ACCESS</a> in clearml app.
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #noPermissionsTemplate>
|
||||
<div class="placeholder">Missing permissions to view this item.
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "src/app/webapp-common/shared/ui-components/styles/variables";
|
||||
@import "variables";
|
||||
|
||||
.placeholder {
|
||||
height: 100%;
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, OnInit, ViewChild} from '@angular/core';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
HostListener,
|
||||
inject,
|
||||
OnInit,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {MatDialog} from '@angular/material/dialog';
|
||||
import {Observable} from 'rxjs';
|
||||
import {filter, map, switchMap, take} from 'rxjs/operators';
|
||||
import {Environment} from '../environments/base';
|
||||
import {getParcoords, getPlot, getSample, getScalar, getSingleValues, reportsPlotlyReady} from './app.actions';
|
||||
import {
|
||||
selectNoPermissions,
|
||||
selectParallelCoordinateExperiments,
|
||||
selectPlotData,
|
||||
selectReportsPlotlyReady,
|
||||
selectSampleData,
|
||||
selectSignIsNeeded,
|
||||
selectSingleValuesData,
|
||||
} from './app.reducer';
|
||||
import {appFeature} from './app.reducer';
|
||||
import {ExtFrame} from '@common/shared/single-graph/plotly-graph-base';
|
||||
import {DebugSample} from '@common/shared/debug-sample/debug-sample.reducer';
|
||||
import {getSignedUrl, setS3Credentials} from '@common/core/actions/common-auth.actions';
|
||||
@@ -28,22 +27,48 @@ import {SingleGraphComponent} from '@common/shared/single-graph/single-graph.com
|
||||
import {setCurrentDebugImage} from '@common/shared/debug-sample/debug-sample.actions';
|
||||
import {isFileserverUrl} from '~/shared/utils/url';
|
||||
import {MetricValueType, SelectedMetric} from '@common/experiments-compare/experiments-compare.constants';
|
||||
import {ExtraTask} from '@common/experiments-compare/dumbs/parallel-coordinates-graph/parallel-coordinates-graph.component';
|
||||
import {
|
||||
ExtraTask,
|
||||
ParallelCoordinatesGraphComponent
|
||||
} from '@common/experiments-compare/dumbs/parallel-coordinates-graph/parallel-coordinates-graph.component';
|
||||
import {EventsGetTaskSingleValueMetricsResponseValues} from '~/business-logic/model/events/eventsGetTaskSingleValueMetricsResponseValues';
|
||||
import {ScalarKeyEnum} from '~/business-logic/model/reports/scalarKeyEnum';
|
||||
import {ReportsApiMultiplotsResponse} from '@common/constants';
|
||||
import {SingleGraphModule} from '@common/shared/single-graph/single-graph.module';
|
||||
import {DebugSampleModule} from '@common/shared/debug-sample/debug-sample.module';
|
||||
import {
|
||||
SingleValueSummaryTableComponent
|
||||
} from '@common/shared/single-value-summary-table/single-value-summary-table.component';
|
||||
import {MetricVariantResult} from '~/business-logic/model/projects/metricVariantResult';
|
||||
import {SingleGraphStateModule} from '@common/shared/single-graph/single-graph-state.module';
|
||||
|
||||
|
||||
type WidgetTypes = 'plot' | 'scalar' | 'sample' | 'parcoords' | 'single';
|
||||
|
||||
export interface SelectedMetricVariant extends MetricVariantResult {
|
||||
valueType?: 'min_value' | 'max_value' | 'value';
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'sm-app-root',
|
||||
selector: 'sm-widget-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [
|
||||
SingleGraphStateModule,
|
||||
SingleGraphModule,
|
||||
DebugSampleModule,
|
||||
ParallelCoordinatesGraphComponent,
|
||||
SingleValueSummaryTableComponent
|
||||
]
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
title = 'report-widgets';
|
||||
private store = inject(Store);
|
||||
private configService = inject(ConfigurationService);
|
||||
private dialog = inject(MatDialog);
|
||||
private cdr = inject(ChangeDetectorRef);
|
||||
protected title = 'report-widgets';
|
||||
public plotData: ExtFrame;
|
||||
public frame: DebugSample;
|
||||
public plotLoaded: boolean;
|
||||
@@ -54,11 +79,11 @@ export class AppComponent implements OnInit {
|
||||
public type: WidgetTypes;
|
||||
public singleGraphHeight;
|
||||
public hideMaximize: 'show' | 'hide' | 'disabled' = 'show';
|
||||
public signIsNeeded$: Observable<boolean>;
|
||||
public noPermissions$: Observable<boolean>;
|
||||
protected signIsNeeded$ = this.store.selectSignal(appFeature.selectSignIsNeeded);
|
||||
protected noPermissions$ = this.store.selectSignal(appFeature.selectNoPermissions);
|
||||
public isDarkTheme: boolean;
|
||||
public externalTool: boolean = false;
|
||||
public parcoordsData: { experiments: ExtraTask[]; params: string[]; metric: SelectedMetric; valueType: MetricValueType };
|
||||
public parcoordsData: { experiments: ExtraTask[]; params: string[]; metrics: SelectedMetricVariant[] };
|
||||
@ViewChild(SingleGraphComponent) 'singleGraph': SingleGraphComponent;
|
||||
public singleValueData: EventsGetTaskSingleValueMetricsResponseValues[];
|
||||
public webappLink: string;
|
||||
@@ -69,16 +94,10 @@ export class AppComponent implements OnInit {
|
||||
this.singleGraph?.redrawPlot();
|
||||
}
|
||||
|
||||
constructor(
|
||||
private store: Store,
|
||||
private configService: ConfigurationService,
|
||||
private dialog: MatDialog,
|
||||
private cdr: ChangeDetectorRef) {
|
||||
constructor() {
|
||||
this.configService.globalEnvironmentObservable.subscribe(env => {
|
||||
this.environment = env;
|
||||
});
|
||||
this.signIsNeeded$ = store.select(selectSignIsNeeded);
|
||||
this.noPermissions$ = store.select(selectNoPermissions);
|
||||
this.searchParams = new URLSearchParams(window.location.search);
|
||||
this.type = this.searchParams.get('type') as WidgetTypes;
|
||||
this.webappLink = this.buildSourceLink(this.searchParams, '*', null);
|
||||
@@ -154,6 +173,9 @@ export class AppComponent implements OnInit {
|
||||
const metric = plot.metric;
|
||||
groupedPlots[metric] = cloneDeep(groupedPlots[metric]) || null;
|
||||
const plotParsed = tryParseJson(plot.plot_str);
|
||||
if (plotParsed.data[0] && !plotParsed.data[0].name) {
|
||||
plotParsed.data[0].name = plot.variant;
|
||||
}
|
||||
if (groupedPlots[metric] && ['scatter', 'bar'].includes(plotParsed?.data?.[0]?.type) && previousPlotIsMergable) {
|
||||
groupedPlots[metric].plotParsed = {...groupedPlots[metric].plotParsed, data: _mergeVariants(groupedPlots[metric].plotParsed.data, plotParsed.data)};
|
||||
} else {
|
||||
@@ -167,18 +189,18 @@ export class AppComponent implements OnInit {
|
||||
};
|
||||
|
||||
private getPlotData() {
|
||||
this.store.select(selectReportsPlotlyReady).pipe(
|
||||
this.store.select(appFeature.selectPlotlyReady).pipe(
|
||||
filter(ready => !!ready),
|
||||
switchMap(() => this.store.select(selectPlotData)),
|
||||
switchMap(() => this.store.select(appFeature.selectPlotData)),
|
||||
filter(plot => !!plot),
|
||||
take(1))
|
||||
.subscribe((metricsPlots) => {
|
||||
this.plotLoaded = true;
|
||||
if (this.isSingleExperiment(metricsPlots)) {
|
||||
const merged = this.mergeVariants(metricsPlots as ReportsApiMultiplotsResponse);
|
||||
this.plotData = Object.values(merged)[0].plotParsed;
|
||||
this.plotData = {...Object.values(merged)[0], ...Object.values(merged)[0].plotParsed};
|
||||
} else {
|
||||
const {merged,} = prepareMultiPlots(metricsPlots as ReportsApiMultiplotsResponse);
|
||||
const {merged} = prepareMultiPlots(metricsPlots as ReportsApiMultiplotsResponse);
|
||||
const newGraphs = convertMultiPlots(merged);
|
||||
const originalObject = this.searchParams.get('objects');
|
||||
const series = this.searchParams.get('series');
|
||||
@@ -213,7 +235,7 @@ export class AppComponent implements OnInit {
|
||||
|
||||
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
private isSingleExperiment(metricsPlots: any) {
|
||||
try {
|
||||
@@ -224,9 +246,9 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
|
||||
private getScalars() {
|
||||
this.store.select(selectReportsPlotlyReady).pipe(
|
||||
this.store.select(appFeature.selectPlotlyReady).pipe(
|
||||
filter(ready => !!ready),
|
||||
switchMap(() => this.store.select(selectPlotData)),
|
||||
switchMap(() => this.store.select(appFeature.selectPlotData)),
|
||||
filter(plot => !!plot),
|
||||
take(1))
|
||||
.subscribe(metrics => {
|
||||
@@ -240,16 +262,24 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
|
||||
private getParallelCoordinate() {
|
||||
this.store.select(selectReportsPlotlyReady).pipe(
|
||||
this.store.select(appFeature.selectPlotlyReady).pipe(
|
||||
filter(ready => !!ready),
|
||||
switchMap(() => this.store.select(selectParallelCoordinateExperiments)),
|
||||
switchMap(() => this.store.select(appFeature.selectParallelCoordinateData)),
|
||||
filter(experiments => !!experiments),
|
||||
take(1))
|
||||
.subscribe(experiments => {
|
||||
this.parcoordsData = {
|
||||
experiments,
|
||||
valueType: this.searchParams.get('value_type') as MetricValueType,
|
||||
metric: {path: this.searchParams.get('metrics'), name: this.findMetricName(this.searchParams.get('metrics'), experiments)},
|
||||
metrics: this.searchParams.getAll('metrics').map(metric => {
|
||||
const [metricHash, variantHash, valueType] = metric.split('.');
|
||||
const path = `${metricHash}.${variantHash}`;
|
||||
return {
|
||||
metric_hash: metricHash,
|
||||
variant_hash: variantHash,
|
||||
...this.findMetricName(path, experiments),
|
||||
valueType: valueType ?? this.searchParams.get('value_type')
|
||||
} as SelectedMetricVariant;
|
||||
}),
|
||||
params: this.searchParams.getAll('variants')
|
||||
};
|
||||
this.cdr.detectChanges();
|
||||
@@ -258,7 +288,7 @@ export class AppComponent implements OnInit {
|
||||
|
||||
|
||||
private getSample() {
|
||||
this.store.select(selectSampleData)
|
||||
this.store.select(appFeature.selectSampleData)
|
||||
.pipe(filter(sample => !!sample))
|
||||
.subscribe(sample => {
|
||||
const url = new URL(sample.url);
|
||||
@@ -281,7 +311,7 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
|
||||
private getSingleValues() {
|
||||
this.store.select(selectSingleValuesData)
|
||||
this.store.select(appFeature.selectSingleValuesData)
|
||||
.pipe(
|
||||
filter(singleValueData => !!singleValueData),
|
||||
take(1)
|
||||
@@ -383,14 +413,14 @@ export class AppComponent implements OnInit {
|
||||
private findMetricName(metric: string, experiments: ExtraTask[]) {
|
||||
const experimentWithCurrentMetric = experiments.find(exp => get(exp.last_metrics, metric));
|
||||
const lastMetric = get(experimentWithCurrentMetric.last_metrics, metric) as ExtraTask['last_metrics'];
|
||||
return `${lastMetric.metric}/${lastMetric.variant}`;
|
||||
return {metric: lastMetric.metric, variant: lastMetric.variant};
|
||||
}
|
||||
|
||||
private buildSourceLink(searchParams: URLSearchParams, project: string, tasks: string[]): string {
|
||||
const isModels = searchParams.has('models') || this.searchParams.get('objectType') === 'model';
|
||||
const objects = searchParams.getAll('objects');
|
||||
const variants = searchParams.getAll('variants');
|
||||
const metricPath = searchParams.get('metrics') || '';
|
||||
const metricsPath = searchParams.getAll('metrics');
|
||||
let entityIds = objects.length > 0 ? objects : searchParams.getAll(isModels ? 'models' : 'tasks');
|
||||
if (entityIds.length === 0 && tasks?.length > 0) {
|
||||
entityIds = tasks;
|
||||
@@ -398,8 +428,8 @@ export class AppComponent implements OnInit {
|
||||
const isCompare = entityIds.length > 1;
|
||||
let url = `${window.location.origin.replace('4201', '4200')}/projects/${project ?? '*'}/`;
|
||||
if (isCompare) {
|
||||
url += `${isModels ? 'compare-models;ids=' : 'compare-experiments;ids='}${entityIds.filter(id => !!id).join(',')}/
|
||||
${this.getComparePath(this.type)}?metricPath=${metricPath}&metricName=lala${variants.map(par => `¶ms=${par}`).join('')}`;
|
||||
url += `${isModels ? 'compare-models;ids=' : 'compare-experiments;ids='}${entityIds.filter(id => !!id).join(',')}/${
|
||||
this.getComparePath(this.type)}?metricVariants=${encodeURIComponent(metricsPath.join(','))}&metricName=${variants.map(par => `¶ms=${encodeURIComponent(par)}`).join('')}`;
|
||||
} else {
|
||||
url += `${isModels ? 'models/' : 'experiments/'}${entityIds}/${this.getOutputPath(isModels, this.type)}`;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import {ApplicationConfig, importProvidersFrom} from '@angular/core';
|
||||
import {StoreModule} from '@ngrx/store';
|
||||
import {EffectsModule} from '@ngrx/effects';
|
||||
import {appFeature} from '@common/clearml-applications/report-widgets/src/app/app.reducer';
|
||||
import {authReducer} from '~/features/settings/containers/admin/auth.reducers';
|
||||
import {AppEffects} from '@common/clearml-applications/report-widgets/src/app/app.effects';
|
||||
import {extCoreConfig} from '@common/clearml-applications/report-widgets/src/build';
|
||||
import {provideHttpClient} from '@angular/common/http';
|
||||
import {BaseAdminService} from '@common/settings/admin/base-admin.service';
|
||||
import {ApiEventsService} from '~/business-logic/api-services/events.service';
|
||||
import {SmApiRequestsService} from '~/business-logic/api-services/api-requests.service';
|
||||
import {ColorHashService} from '@common/shared/services/color-hash/color-hash.service';
|
||||
import {provideAnimations} from '@angular/platform-browser/animations';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
importProvidersFrom(
|
||||
StoreModule.forRoot({}),
|
||||
StoreModule.forFeature(appFeature),
|
||||
StoreModule.forFeature({name: 'auth', reducer: authReducer}),
|
||||
EffectsModule.forRoot([AppEffects])
|
||||
),
|
||||
...extCoreConfig,
|
||||
provideAnimations(),
|
||||
provideHttpClient(),
|
||||
BaseAdminService,
|
||||
ApiEventsService,
|
||||
SmApiRequestsService,
|
||||
ColorHashService
|
||||
]
|
||||
};
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
import {EMPTY, mergeMap, of, switchMap} from 'rxjs';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {catchError, filter} from 'rxjs/operators';
|
||||
import {ApiReportsService} from '~/business-logic/api-services/reports.service';
|
||||
import {BaseAdminService} from '@common/settings/admin/base-admin.service';
|
||||
import {ReportsGetTaskDataResponse} from '~/business-logic/model/reports/reportsGetTaskDataResponse';
|
||||
import {getSignedUrl, setSignedUrl} from '@common/core/actions/common-auth.actions';
|
||||
@@ -37,7 +36,6 @@ export class AppEffects {
|
||||
private httpClient: HttpClient,
|
||||
private store: Store,
|
||||
private actions$: Actions,
|
||||
private reportsApi: ApiReportsService,
|
||||
private adminService: BaseAdminService) {
|
||||
}
|
||||
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
|
||||
import {AppComponent} from './app.component';
|
||||
import {EffectsModule} from '@ngrx/effects';
|
||||
import {AppEffects} from './app.effects';
|
||||
import {StoreModule} from '@ngrx/store';
|
||||
import {appReducer} from './app.reducer';
|
||||
import {HttpClientModule} from '@angular/common/http';
|
||||
import {MatDialogModule} from '@angular/material/dialog';
|
||||
import {SingleGraphModule} from '@common/shared/single-graph/single-graph.module';
|
||||
import {DebugSampleModule} from '@common/shared/debug-sample/debug-sample.module';
|
||||
import {ApiEventsService} from '~/business-logic/api-services/events.service';
|
||||
import {ApiReportsService} from '~/business-logic/api-services/reports.service';
|
||||
import {BaseAdminService} from '@common/settings/admin/base-admin.service';
|
||||
import {ColorHashService} from '@common/shared/services/color-hash/color-hash.service';
|
||||
import {authReducer} from '~/features/settings/containers/admin/auth.reducers';
|
||||
import {extCoreModules} from '~/build-specifics';
|
||||
import {SmApiRequestsService} from '~/business-logic/api-services/api-requests.service';
|
||||
import {ParallelCoordinatesGraphComponent} from '@common/experiments-compare/dumbs/parallel-coordinates-graph/parallel-coordinates-graph.component';
|
||||
import {SingleValueSummaryTableComponent} from '@common/shared/single-value-summary-table/single-value-summary-table.component';
|
||||
|
||||
if (!localStorage.getItem('_saved_state_')) {
|
||||
localStorage.setItem('_saved_state_', '{}');
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserAnimationsModule,
|
||||
BrowserModule,
|
||||
HttpClientModule,
|
||||
MatDialogModule,
|
||||
SingleGraphModule,
|
||||
DebugSampleModule,
|
||||
ParallelCoordinatesGraphComponent,
|
||||
StoreModule.forRoot({appReducer, auth: authReducer}),
|
||||
EffectsModule.forRoot([AppEffects]),
|
||||
...extCoreModules,
|
||||
SingleValueSummaryTableComponent
|
||||
],
|
||||
providers: [ApiEventsService, ApiReportsService, SmApiRequestsService, ColorHashService, BaseAdminService],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule {
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import {createReducer, createSelector, on} from '@ngrx/store';
|
||||
import {createFeature, createReducer, on} from '@ngrx/store';
|
||||
import {reportsPlotlyReady, setNoPermissions, setParallelCoordinateExperiments, setPlotData, setSampleData, setSignIsNeeded, setSingleValues, setTaskData} from './app.actions';
|
||||
import {DebugSample} from '@common/shared/debug-sample/debug-sample.reducer';
|
||||
import {MetricsPlotEvent} from '~/business-logic/model/events/metricsPlotEvent';
|
||||
@@ -44,32 +44,21 @@ export const initialState: State = {
|
||||
taskData: null
|
||||
};
|
||||
|
||||
export const appReducer = createReducer(
|
||||
initialState,
|
||||
on(reportsPlotlyReady, (state) => ({...state, plotlyReady: true})),
|
||||
on(setPlotData, (state, action) => ({...state, plotData: action.data as ReportsApiMultiplotsResponse})),
|
||||
on(setSampleData, (state, action) => ({...state, sampleData: action.data})),
|
||||
on(setSingleValues, (state, action) => ({...state, singleValuesData: action.data})),
|
||||
on(setParallelCoordinateExperiments, (state, action) => ({...state, parallelCoordinateData: action.data})),
|
||||
on(setSignIsNeeded, (state) => ({...state, signIsNeeded: true})),
|
||||
on(setNoPermissions, (state) => ({...state, noPermissions: true})),
|
||||
on(setTaskData, (state, action) => ({...state, taskData:
|
||||
{appId: action.appId, sourceTasks: action.sourceTasks, sourceProject: action.sourceProject}})
|
||||
export const appFeature = createFeature({
|
||||
name: 'app',
|
||||
reducer: createReducer(
|
||||
initialState,
|
||||
on(reportsPlotlyReady, (state): State => ({...state, plotlyReady: true})),
|
||||
on(setPlotData, (state, action): State => ({...state, plotData: action.data as ReportsApiMultiplotsResponse})),
|
||||
on(setSampleData, (state, action): State => ({...state, sampleData: action.data})),
|
||||
on(setSingleValues, (state, action): State => ({...state, singleValuesData: action.data})),
|
||||
on(setParallelCoordinateExperiments, (state, action): State => ({...state, parallelCoordinateData: action.data})),
|
||||
on(setSignIsNeeded, (state): State => ({...state, signIsNeeded: true})),
|
||||
on(setNoPermissions, (state): State => ({...state, noPermissions: true})),
|
||||
on(setTaskData, (state, action): State => ({
|
||||
...state, taskData:
|
||||
{appId: action.appId, sourceTasks: action.sourceTasks, sourceProject: action.sourceProject}
|
||||
})
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
export const selectFeature = state => state.appReducer as State;
|
||||
|
||||
export const selectScaleFactor = createSelector(selectFeature, state => state.scaleFactor);
|
||||
export const selectReportsPlotlyReady = createSelector(selectFeature, state => state.plotlyReady);
|
||||
export const selectPlotData = createSelector(selectFeature, state => state.plotData);
|
||||
export const selectSampleData = createSelector(selectFeature, state => state.sampleData);
|
||||
export const selectSingleValuesData = createSelector(selectFeature, state => state.singleValuesData);
|
||||
export const selectParallelCoordinateExperiments = createSelector(selectFeature, state => state.parallelCoordinateData);
|
||||
export const selectSignIsNeeded = createSelector(selectFeature, state => state.signIsNeeded);
|
||||
export const selectNoPermissions = createSelector(selectFeature, state => state.noPermissions);
|
||||
export const selectTaskData = createSelector(selectFeature, (state): {
|
||||
sourceProject: string;
|
||||
sourceTasks: string[];
|
||||
appId: string;
|
||||
} => state.taskData);
|
||||
});
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const extCoreModules = [];
|
||||
@@ -0,0 +1,10 @@
|
||||
import {provideStoreDevtools} from '@ngrx/store-devtools';
|
||||
|
||||
export const extCoreConfig = [
|
||||
provideStoreDevtools({
|
||||
maxAge: 75,
|
||||
trace: true,
|
||||
traceLimit: 50,
|
||||
connectInZone: true
|
||||
})
|
||||
];
|
||||
@@ -17,7 +17,7 @@ export const environment = {
|
||||
production: false,
|
||||
baseUrl: 'localhost:4200',
|
||||
autoLogin: false,
|
||||
apiBaseUrl: 'service/1/api',
|
||||
apiBaseUrl: '../service/1/api',
|
||||
// communityServer: true,
|
||||
accountAdministration: true,
|
||||
fileBaseUrl: 'https://files.allegro.ai',
|
||||
|
||||
@@ -8,6 +8,6 @@
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
<sm-app-root style="width: 100%; height: 100%"></sm-app-root>
|
||||
<sm-widget-root style="width: 100%; height: 100%"></sm-widget-root>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
import {APP_BASE_HREF} from '@angular/common';
|
||||
import {Environment} from '../../../../../environments/base';
|
||||
import {bootstrapApplication} from '@angular/platform-browser';
|
||||
import {environment} from './environments/environment';
|
||||
import {updateHttpUrlBaseConstant} from '~/app.constants';
|
||||
import {appConfig} from '@common/clearml-applications/report-widgets/src/app/app.config';
|
||||
import {Environment} from '../../../../../environments/base';
|
||||
import {AppComponent} from './app/app.component';
|
||||
|
||||
// bootstrapApplication(AppComponent, appConfig)
|
||||
// .catch((err) => console.error(err));
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const configData = {baseHref: ''} as Environment;
|
||||
@@ -18,8 +17,9 @@ if (environment.production) {
|
||||
(window as any).configuration = {};
|
||||
} finally {
|
||||
updateHttpUrlBaseConstant({...environment, ...configData});
|
||||
await platformBrowserDynamic([
|
||||
{provide: APP_BASE_HREF, useValue: configData.baseHref}
|
||||
]).bootstrapModule(AppModule);
|
||||
await bootstrapApplication(AppComponent, {providers: [
|
||||
...appConfig.providers,
|
||||
{provide: APP_BASE_HREF, useValue: configData.baseHref}
|
||||
]});
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
/**
|
||||
* This file includes polyfills needed by Angular and is loaded before the app.
|
||||
* You can add your own extra polyfills to this file.
|
||||
*
|
||||
* This file is divided into 2 sections:
|
||||
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
|
||||
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
|
||||
* file.
|
||||
*
|
||||
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
|
||||
* automatically update themselves. This includes recent versions of Safari, Chrome (including
|
||||
* Opera), Edge on the desktop, and iOS and Chrome on mobile.
|
||||
*
|
||||
* Learn more in https://angular.io/guide/browser-support
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* BROWSER POLYFILLS
|
||||
*/
|
||||
|
||||
/**
|
||||
* By default, zone.js will patch all possible macroTask and DomEvents
|
||||
* user can disable parts of macroTask/DomEvents patch by setting following flags
|
||||
* because those flags need to be set before `zone.js` being loaded, and webpack
|
||||
* will put import in the top of bundle, so user need to create a separate file
|
||||
* in this directory (for example: zone-flags.ts), and put the following flags
|
||||
* into that file, and then add the following code before importing zone.js.
|
||||
* import './zone-flags';
|
||||
*
|
||||
* The flags allowed in zone-flags.ts are listed here.
|
||||
*
|
||||
* The following flags will work for all browsers.
|
||||
*
|
||||
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
|
||||
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
|
||||
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
|
||||
*
|
||||
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
|
||||
* with the following flag, it will bypass `zone.js` patch for IE/Edge
|
||||
*
|
||||
* (window as any).__Zone_enable_cross_context_check = true;
|
||||
*
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Zone JS is required by default for Angular itself.
|
||||
*/
|
||||
import 'zone.js'; // Included with Angular CLI.
|
||||
|
||||
|
||||
/***************************************************************************************************
|
||||
* APPLICATION IMPORTS
|
||||
*/
|
||||
@@ -132,7 +132,7 @@ $sm-dark-theme: mat.define-dark-theme((
|
||||
|
||||
@include mat.select-theme($sm-dark-theme);
|
||||
|
||||
@import "src/app/webapp-common/shared/ui-components/styles/variables";
|
||||
@import "variables";
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
@@ -191,6 +191,11 @@ body {
|
||||
.h-100 {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.w-100 {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.dark-theme .plot-container .hoverlayer {
|
||||
line[stroke-width="1"] {
|
||||
stroke: $blue-300;
|
||||
@@ -276,11 +281,6 @@ body {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
||||
.modebar {
|
||||
top: 20px !important;
|
||||
}
|
||||
|
||||
.modebar-btn[data-attr="plotly-disabled-maximize"] {
|
||||
cursor: default !important;
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
},
|
||||
"files": [
|
||||
"src/main.ts",
|
||||
"src/polyfills.ts"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.d.ts"
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
},
|
||||
"files": [
|
||||
"src/test.ts",
|
||||
"src/polyfills.ts"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.spec.ts",
|
||||
|
||||
@@ -31,8 +31,9 @@ export const searchReducer = createReducer(
|
||||
);
|
||||
|
||||
export const selectCommonSearch = createFeatureSelector<SearchState>('commonSearch');
|
||||
export const selectIsSearching = createSelector(selectCommonSearch, (state: SearchState): boolean => state ? state.isSearching : false);
|
||||
export const selectSearchQuery = createSelector(selectCommonSearch, (state: SearchState) => state ? state.searchQuery : searchInitState.searchQuery);
|
||||
export const selectPlaceholder = createSelector(selectCommonSearch, (state: SearchState) => state ? state.placeholder : '');
|
||||
export const selectIsSearching = createSelector(selectCommonSearch, state => state ? state.isSearching : false);
|
||||
export const selectSearchQuery = createSelector(selectCommonSearch, state => state ? state.searchQuery : searchInitState.searchQuery);
|
||||
export const selectPlaceholder = createSelector(selectCommonSearch, state => state ? state.placeholder : '');
|
||||
export const selectActiveSearch = createSelector(selectSearchQuery, state => state?.query?.length >= 1);
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,23 @@
|
||||
@import "layout/layout";
|
||||
|
||||
@include mat.core();
|
||||
|
||||
// The following mixins include base theme styles that are only needed once per application. These
|
||||
// theme styles do not depend on the color, typography, or density settings in your theme. However,
|
||||
// these styles may differ depending on the theme's design system. Currently all themes use the
|
||||
// Material 2 design system, but in the future it may be possible to create theme based on other
|
||||
// design systems, such as Material 3.
|
||||
//
|
||||
// Please note: you do not need to include the 'base' mixins, if you include the corresponding
|
||||
// 'theme' mixin elsewhere in your Sass. The full 'theme' mixins already include the base styles.
|
||||
//
|
||||
// To learn more about "base" theme styles visit our theming guide:
|
||||
// https://material.angular.io/guide/theming#theming-dimensions
|
||||
//
|
||||
// TODO(v17): Please move these @include statements to the preferred place in your Sass, and pass
|
||||
// your theme to them. This will ensure the correct values for your app are included.
|
||||
//@include mat.all-component-bases(/* TODO(v17): pass $your-theme here */);
|
||||
|
||||
$custom-typography: mat.define-typography-config(
|
||||
$font-family: $font-family-base
|
||||
);
|
||||
@@ -169,6 +186,15 @@ $sm-neon-theme: mat.define-dark-theme((
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.inline-code {
|
||||
background-color: $blue-50;
|
||||
border: 1px solid $blue-100;
|
||||
border-radius: 4px;
|
||||
padding: 2px 6px;
|
||||
font-size: 12px;
|
||||
font-family: $font-family-monospace;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
@@ -319,11 +345,12 @@ mat-expansion-panel {
|
||||
background: $faint-gray !important;
|
||||
}
|
||||
|
||||
.mat-mdc-radio-button.sm {
|
||||
.mat-mdc-radio-button {
|
||||
--mdc-radio-state-layer-size: 16px;
|
||||
|
||||
.mdc-radio {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.mdc-radio__background {
|
||||
@@ -331,17 +358,15 @@ mat-expansion-panel {
|
||||
height: 16px;
|
||||
|
||||
.mdc-radio__inner-circle {
|
||||
top: -2px;
|
||||
left: -2px;
|
||||
border-width: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.mat-radio-ripple {
|
||||
.mat-ripple.mat-radio-ripple {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
left: calc(50% - 18px);
|
||||
top: calc(50% - 18px);
|
||||
border-radius: 100%;
|
||||
left: calc(50% - 16px);
|
||||
top: calc(50% - 16px);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -637,6 +662,11 @@ html {
|
||||
padding: 0 32px 0 12px;
|
||||
border-radius: 4px;
|
||||
|
||||
.mat-icon {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
|
||||
.mat-mdc-menu-item-text, .mdc-list-item__primary-text {
|
||||
--mat-menu-item-label-text-tracking: 0;
|
||||
display: flex;
|
||||
|
||||
@@ -76,6 +76,8 @@ export const ICONS = {
|
||||
ARROW_UP: 'al-ico-ico-chevron-up',
|
||||
RUN: 'al-ico-run',
|
||||
METADATA: 'al-ico-metadata',
|
||||
ID: 'al-ico-id',
|
||||
CHECK: 'al-ico-success'
|
||||
};
|
||||
|
||||
export type IconNames = keyof typeof ICONS;
|
||||
|
||||
@@ -16,7 +16,7 @@ export const updateS3Credential = createAction(
|
||||
);
|
||||
export const createCredential = createAction(
|
||||
AUTH_PREFIX + 'CREATE_CREDENTIAL (API)',
|
||||
props<{workspace: GetCurrentUserResponseUserObjectCompany; openCredentialsPopup?: boolean; label?: string}>()
|
||||
props<{workspace: GetCurrentUserResponseUserObjectCompany; openCredentialsPopup?: boolean; label?: string; userId?: string}>()
|
||||
);
|
||||
|
||||
export const updateCredentialLabel = createAction(
|
||||
@@ -33,6 +33,7 @@ export const addCredential = createAction(
|
||||
props<{ newCredential: CredentialKeyExt; workspaceId: string }>()
|
||||
);
|
||||
export const resetCredential = createAction(AUTH_PREFIX + 'RESET_CREDENTIAL');
|
||||
export const resetCredentials = createAction(AUTH_PREFIX + 'RESET_CREDENTIALS');
|
||||
export const removeCredential = createAction(
|
||||
AUTH_PREFIX + '[remove credentials]',
|
||||
props<{ accessKey: string; workspaceId: string }>()
|
||||
@@ -55,7 +56,9 @@ export const showLocalFilePopUp = createAction(
|
||||
AUTH_PREFIX + 'SHOW_LOCAL_FILE_POPUP',
|
||||
props<{ url: string }>()
|
||||
);
|
||||
export const getAllCredentials = createAction(AUTH_PREFIX + 'GET_ALL_CREDENTIALS');
|
||||
export const getAllCredentials = createAction(
|
||||
AUTH_PREFIX + 'GET_ALL_CREDENTIALS',
|
||||
props<{userId?: string}>());
|
||||
export const credentialRevoked = createAction(
|
||||
AUTH_PREFIX + 'REVOKE_CREDENTIAL (API)',
|
||||
props<{ accessKey: string; workspaceId: string }>()
|
||||
|
||||
@@ -33,8 +33,6 @@ export const setFilterByUser = createAction(
|
||||
);
|
||||
|
||||
export const setUserWorkspacesFromUser = createAction(USERS_PREFIX + ' set user workspaces from current user');
|
||||
|
||||
export const setAccountAdministrationPage = createAction(`${USERS_PREFIX} route to account-administration` );
|
||||
export const getApiVersion = createAction(`${USERS_PREFIX} get api version` );
|
||||
export const setApiVersion = createAction(`${USERS_PREFIX} set api version`, props<{serverVersions: {server: string; api: string}}>());
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import {Injectable} from '@angular/core';
|
||||
import {Actions, concatLatestFrom, createEffect, ofType} from '@ngrx/effects';
|
||||
import {ApiAuthService} from '~/business-logic/api-services/auth.service';
|
||||
import * as authActions from '../actions/common-auth.actions';
|
||||
import {setCredentialLabel} from '../actions/common-auth.actions';
|
||||
import {requestFailed} from '../actions/http.actions';
|
||||
import {activeLoader, deactivateLoader, setServerError} from '../actions/layout.actions';
|
||||
import {catchError, filter, finalize, map, mergeMap, switchMap, throttleTime} from 'rxjs/operators';
|
||||
@@ -12,14 +13,11 @@ import {GetCurrentUserResponseUserObject} from '~/business-logic/model/users/get
|
||||
import {AdminService} from '~/shared/services/admin.service';
|
||||
import {selectDontShowAgainForBucketEndpoint, selectS3BucketCredentialsBucketCredentials, selectSignedUrl} from '@common/core/reducers/common-auth-reducer';
|
||||
import {EMPTY, of} from 'rxjs';
|
||||
import {
|
||||
S3AccessDialogData,
|
||||
S3AccessResolverComponent
|
||||
} from '@common/layout/s3-access-resolver/s3-access-resolver.component';
|
||||
import {S3AccessDialogData, S3AccessResolverComponent} from '@common/layout/s3-access-resolver/s3-access-resolver.component';
|
||||
import {MatDialog} from '@angular/material/dialog';
|
||||
import {setCredentialLabel} from '../actions/common-auth.actions';
|
||||
import {isGoogleCloudUrl, SignResponse} from '@common/settings/admin/base-admin-utils';
|
||||
import {isFileserverUrl} from '~/shared/utils/url';
|
||||
import {selectRouterQueryParams} from '@common/core/reducers/router-reducer';
|
||||
|
||||
@Injectable()
|
||||
export class CommonAuthEffects {
|
||||
@@ -32,7 +30,8 @@ export class CommonAuthEffects {
|
||||
private store: Store,
|
||||
private adminService: AdminService,
|
||||
private matDialog: MatDialog
|
||||
) {}
|
||||
) {
|
||||
}
|
||||
|
||||
activeLoader = createEffect(() => this.actions.pipe(
|
||||
ofType(authActions.getAllCredentials, authActions.createCredential),
|
||||
@@ -41,7 +40,8 @@ export class CommonAuthEffects {
|
||||
|
||||
getAllCredentialsEffect = createEffect(() => this.actions.pipe(
|
||||
ofType(authActions.getAllCredentials),
|
||||
switchMap(action => this.credentialsApi.authGetCredentials({}).pipe(
|
||||
switchMap(action => this.credentialsApi.authGetCredentials({},
|
||||
{userId: action.userId}).pipe(
|
||||
concatLatestFrom(() => this.store.select(selectCurrentUser)),
|
||||
mergeMap(([res, user]: [AuthGetCredentialsResponse, GetCurrentUserResponseUserObject]) => [
|
||||
authActions.updateAllCredentials({credentials: res.credentials, extra: res?.['additional_credentials'], workspace: user.company.id}),
|
||||
@@ -53,9 +53,15 @@ export class CommonAuthEffects {
|
||||
|
||||
revokeCredential = createEffect(() => this.actions.pipe(
|
||||
ofType(authActions.credentialRevoked),
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
mergeMap(action => this.credentialsApi.authRevokeCredentials({access_key: action.accessKey}).pipe(
|
||||
mergeMap(() => [authActions.removeCredential(action), deactivateLoader(action.type)]),
|
||||
concatLatestFrom(() => [
|
||||
this.store.select(selectRouterQueryParams).pipe(map(params => params.userId)),
|
||||
]),
|
||||
mergeMap(([action, userId]) => this.credentialsApi.authRevokeCredentials(
|
||||
{access_key: action.accessKey}, {userId}).pipe(
|
||||
mergeMap(() => [
|
||||
authActions.removeCredential(action),
|
||||
deactivateLoader(action.type)
|
||||
]),
|
||||
catchError(error => [
|
||||
requestFailed(error),
|
||||
deactivateLoader(action.type),
|
||||
@@ -66,32 +72,40 @@ export class CommonAuthEffects {
|
||||
|
||||
createCredential = createEffect(() => this.actions.pipe(
|
||||
ofType(authActions.createCredential),
|
||||
mergeMap(action => this.credentialsApi.authCreateCredentials({label: action.label}).pipe(
|
||||
mergeMap(({credentials}) => [
|
||||
authActions.addCredential({newCredential: credentials, workspaceId: action.workspace?.id}),
|
||||
deactivateLoader(action.type)
|
||||
]),
|
||||
catchError(error => [
|
||||
requestFailed(error),
|
||||
setServerError(error, null, 'Unable to create credentials'),
|
||||
authActions.addCredential({newCredential: {}, workspaceId: action.workspace?.id}),
|
||||
deactivateLoader(action.type)])
|
||||
))
|
||||
concatLatestFrom(() => [
|
||||
this.store.select(selectRouterQueryParams).pipe(map(params => params.userId)),
|
||||
]),
|
||||
mergeMap(([action, userId]) =>
|
||||
this.credentialsApi.authCreateCredentials({label: action.label},
|
||||
{userId}).pipe(
|
||||
mergeMap(({credentials}) => [
|
||||
authActions.addCredential({newCredential: credentials, workspaceId: action.workspace?.id}),
|
||||
deactivateLoader(action.type)
|
||||
]),
|
||||
catchError(error => [
|
||||
requestFailed(error),
|
||||
setServerError(error, null, 'Unable to create credentials'),
|
||||
authActions.addCredential({newCredential: {}, workspaceId: action.workspace?.id}),
|
||||
deactivateLoader(action.type)])
|
||||
))
|
||||
));
|
||||
|
||||
updateCredentialLabel = createEffect(() => this.actions.pipe(
|
||||
ofType(authActions.updateCredentialLabel),
|
||||
concatLatestFrom(() => this.store.select(selectRouterQueryParams).pipe(map(params => params.userId))),
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
mergeMap(action => this.credentialsApi.authEditCredentials({access_key: action.credential.access_key, label: action.label}).pipe(
|
||||
mergeMap(() => [
|
||||
setCredentialLabel({credential: action.credential, label: action.label}),
|
||||
deactivateLoader(action.type)
|
||||
]),
|
||||
catchError(error => [
|
||||
requestFailed(error),
|
||||
setServerError(error, null, 'Unable to update credentials'),
|
||||
deactivateLoader(action.type)])
|
||||
))
|
||||
mergeMap(([action, userId]) =>
|
||||
this.credentialsApi.authEditCredentials({access_key: action.credential.access_key, label: action.label},
|
||||
{userId}).pipe(
|
||||
mergeMap(() => [
|
||||
setCredentialLabel({credential: action.credential, label: action.label}),
|
||||
deactivateLoader(action.type)
|
||||
]),
|
||||
catchError(error => [
|
||||
requestFailed(error),
|
||||
setServerError(error, null, 'Unable to update credentials'),
|
||||
deactivateLoader(action.type)])
|
||||
))
|
||||
));
|
||||
|
||||
refresh = createEffect(() => this.actions.pipe(
|
||||
@@ -129,8 +143,8 @@ export class CommonAuthEffects {
|
||||
return EMPTY;
|
||||
}
|
||||
}
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
|
||||
@@ -96,59 +96,58 @@ export class ProjectsEffects {
|
||||
|
||||
|
||||
getTablesFilterProjectsOptions$ = createEffect(() => this.actions$.pipe(
|
||||
ofType(actions.getTablesFilterProjectsOptions),
|
||||
debounceTime(300),
|
||||
concatLatestFrom(() => [
|
||||
this.store.select(selectShowHidden),
|
||||
this.store.select(selectProjectsOptionsScrollId),
|
||||
this.getRelevantTableFilters(this.store.select(selectRouterConfig))
|
||||
]),
|
||||
switchMap(([action, showHidden, scrollId, filters]) => forkJoin([
|
||||
ofType(actions.getTablesFilterProjectsOptions),
|
||||
debounceTime(300),
|
||||
concatLatestFrom(() => [
|
||||
this.store.select(selectShowHidden),
|
||||
this.store.select(selectProjectsOptionsScrollId),
|
||||
this.getRelevantTableFilters(this.store.select(selectRouterConfig))
|
||||
]),
|
||||
switchMap(([action, showHidden, scrollId, filters]) => forkJoin([
|
||||
this.projectsApi.projectsGetAllEx({
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
allow_public: action.allowPublic,
|
||||
page_size: rootProjectsPageSize,
|
||||
size: rootProjectsPageSize,
|
||||
order_by: ['name'],
|
||||
only_fields: ['name', 'company'],
|
||||
search_hidden: showHidden,
|
||||
_any_: {pattern: escapeRegex(action.searchString), fields: ['name']},
|
||||
scroll_id: !!action.loadMore && scrollId ? scrollId : null
|
||||
} as ProjectsGetAllExRequest),
|
||||
!action.loadMore && action.searchString?.length > 2 ?
|
||||
this.projectsApi.projectsGetAllEx({
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
allow_public: action.allowPublic,
|
||||
page_size: rootProjectsPageSize,
|
||||
size: rootProjectsPageSize,
|
||||
order_by: ['name'],
|
||||
page_size: 1,
|
||||
only_fields: ['name', 'company'],
|
||||
search_hidden: showHidden,
|
||||
_any_: {pattern: escapeRegex(action.searchString), fields: ['name']},
|
||||
scroll_id: !!action.loadMore && scrollId
|
||||
} as ProjectsGetAllExRequest),
|
||||
!action.loadMore && action.searchString?.length > 2 ?
|
||||
this.projectsApi.projectsGetAllEx({
|
||||
page_size: 1,
|
||||
only_fields: ['name', 'company'],
|
||||
search_hidden: showHidden,
|
||||
_any_: {pattern: `^${escapeRegex(action.searchString)}$`, fields: ['name', 'id']},
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
} as ProjectsGetAllExRequest).pipe(map(res => res.projects)) :
|
||||
of([]),
|
||||
!action.loadMore && filters['project.name']?.value.length ?
|
||||
this.projectsApi.projectsGetAllEx({
|
||||
id: filters['project.name']?.value,
|
||||
only_fields: ['name', 'company'],
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
} as ProjectsGetAllExRequest).pipe(map(res => res.projects)) :
|
||||
of([]),
|
||||
])
|
||||
.pipe(map(([allProjects, specificProjects, selectedProjects]) => ({
|
||||
projects: [
|
||||
...(specificProjects.length > 0 && allProjects.projects.some(project => project.id === specificProjects[0]?.id) ? [] : specificProjects),
|
||||
...allProjects.projects,
|
||||
...selectedProjects
|
||||
],
|
||||
scrollId: allProjects.scroll_id,
|
||||
loadMore: action.loadMore
|
||||
})
|
||||
))
|
||||
),
|
||||
mergeMap((projects: {
|
||||
projects: ProjectsGetAllResponseSingle[];
|
||||
scrollId: string;
|
||||
}) => [setTablesFilterProjectsOptions({...projects})])
|
||||
)
|
||||
);
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
} as ProjectsGetAllExRequest).pipe(map(res => res.projects)) :
|
||||
of([]),
|
||||
!action.loadMore && filters['project.name']?.value.length ?
|
||||
this.projectsApi.projectsGetAllEx({
|
||||
id: filters['project.name']?.value,
|
||||
only_fields: ['name', 'company'],
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
} as ProjectsGetAllExRequest).pipe(map(res => res.projects)) :
|
||||
of([]),
|
||||
])
|
||||
.pipe(map(([allProjects, specificProjects, selectedProjects]) => ({
|
||||
projects: [
|
||||
...(specificProjects.length > 0 && allProjects.projects.some(project => project.id === specificProjects[0]?.id) ? [] : specificProjects),
|
||||
...allProjects.projects,
|
||||
...selectedProjects
|
||||
],
|
||||
scrollId: allProjects.scroll_id,
|
||||
loadMore: action.loadMore
|
||||
})
|
||||
))
|
||||
),
|
||||
mergeMap((projects: {
|
||||
projects: ProjectsGetAllResponseSingle[];
|
||||
scrollId: string;
|
||||
}) => [setTablesFilterProjectsOptions({...projects})])
|
||||
));
|
||||
|
||||
|
||||
resetProjects$ = createEffect(() => this.actions$.pipe(
|
||||
@@ -362,7 +361,7 @@ export class ProjectsEffects {
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
include_subprojects: true
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
}, null, 'body', true).pipe(
|
||||
}, {adminQuery: true}).pipe(
|
||||
mergeMap(res => [actions.setAllProjectUsers(res)]),
|
||||
catchError(error => [
|
||||
requestFailed(error),
|
||||
@@ -381,7 +380,7 @@ export class ProjectsEffects {
|
||||
projects: [action.projectId],
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
include_subprojects: isDeep
|
||||
}, null, 'body', true)).pipe(
|
||||
}, {adminQuery: true})).pipe(
|
||||
mergeMap(res => [actions.setProjectUsers(res)]),
|
||||
catchError(error => [
|
||||
requestFailed(error),
|
||||
@@ -398,7 +397,7 @@ export class ProjectsEffects {
|
||||
only_fields: ['name'],
|
||||
id: action.filteredUsers || []
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
}, null, 'body', true).pipe(
|
||||
}, {adminQuery: true}).pipe(
|
||||
mergeMap(res => [
|
||||
actions.setProjectExtraUsers(res),
|
||||
deactivateLoader(action.type)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {LocationStrategy} from '@angular/common';
|
||||
import {inject, Injectable} from '@angular/core';
|
||||
import {Router} from '@angular/router';
|
||||
import {Actions, createEffect, ofType} from '@ngrx/effects';
|
||||
import {ApiUsersService} from '~/business-logic/api-services/users.service';
|
||||
@@ -9,7 +10,7 @@ import {
|
||||
setApiVersion, setCurrentUserName,
|
||||
setUserWorkspacesFromUser, updateCurrentUser
|
||||
} from '../actions/users.actions';
|
||||
import {catchError, map, mergeMap} from 'rxjs/operators';
|
||||
import {catchError, filter, map, mergeMap} from 'rxjs/operators';
|
||||
import {addMessage} from '../actions/layout.actions';
|
||||
import {ApiLoginService} from '~/business-logic/api-services/login.service';
|
||||
import {LoginLogoutResponse} from '~/business-logic/model/login/loginLogoutResponse';
|
||||
@@ -21,6 +22,7 @@ import {MESSAGES_SEVERITY} from '@common/constants';
|
||||
|
||||
@Injectable()
|
||||
export class CommonUserEffects {
|
||||
private locationStrategy = inject(LocationStrategy);
|
||||
|
||||
constructor(
|
||||
private actions: Actions, private userService: ApiUsersService,
|
||||
@@ -34,7 +36,7 @@ export class CommonUserEffects {
|
||||
ofType(logout),
|
||||
mergeMap(action => this.loginApi.loginLogout({
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
redirect_url: window.location.origin + '/login',
|
||||
redirect_url: window.location.origin + (this.locationStrategy.getBaseHref() === '/' ? '' : this.locationStrategy.getBaseHref()) + '/login',
|
||||
...(action.provider && {provider: action.provider})
|
||||
}).pipe(
|
||||
map((res: LoginLogoutResponse) => {
|
||||
@@ -62,7 +64,8 @@ ${this.errorService.getErrorMsg(err?.error)}`)])
|
||||
updateCurrentUser = createEffect(() => this.actions.pipe(
|
||||
ofType(updateCurrentUser),
|
||||
mergeMap(({user}) => this.userService.usersUpdate({...user}).pipe(
|
||||
mergeMap((res: UsersUpdateResponse) => res.updated ? [setCurrentUserName({name: user.name})] : [])
|
||||
filter((res: UsersUpdateResponse) => res.updated > 0),
|
||||
map(() => setCurrentUserName({name: user.name}))
|
||||
)),
|
||||
catchError(err => [addMessage(MESSAGES_SEVERITY.ERROR, `Update User Failed ${this.errorService.getErrorMsg(err?.error)}`)])
|
||||
));
|
||||
|
||||
@@ -4,7 +4,7 @@ import {isEqual} from 'lodash-es';
|
||||
import {
|
||||
addCredential,
|
||||
cancelS3Credentials,
|
||||
removeCredential, removeSignedUrl, resetCredential,
|
||||
removeCredential, removeSignedUrl, resetCredential, resetCredentials,
|
||||
resetDontShowAgainForBucketEndpoint,
|
||||
saveS3Credentials, setCredentialLabel, setS3Credentials, setSignedUrl,
|
||||
showLocalFilePopUp,
|
||||
@@ -118,6 +118,7 @@ export const commonAuthReducer = [
|
||||
}
|
||||
}),
|
||||
on(resetCredential, state => ({...state, newCredential: initAuth.newCredential})),
|
||||
on(resetCredentials, state => ({...state, credentials: initAuth.credentials})),
|
||||
on(addCredential, (state, action) => ({
|
||||
...state,
|
||||
newCredential: {...action.newCredential, company: action.workspaceId},
|
||||
|
||||
@@ -37,6 +37,7 @@ export interface ScatterPlotSeries {
|
||||
y: number;
|
||||
name: string;
|
||||
description?: string;
|
||||
extraParamsHoverInfo?: string[];
|
||||
}[]
|
||||
}
|
||||
|
||||
@@ -115,7 +116,7 @@ export const selectMainPageTagsFilterMatchMode = createSelector(projects, select
|
||||
export const selectCompanyTags = createSelector(projects, state => state.companyTags);
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export const selectProjectSystemTags = createSelector(projects, state => getSystemTags({system_tags: state.systemTags} as ITableExperiment));
|
||||
export const selectTagsColors = createSelector(projects, state => state.tagsColors);
|
||||
export const selectTagsColors = createSelector(projects, state => state?.tagsColors);
|
||||
export const selectLastUpdate = createSelector(projects, state => state.lastUpdate);
|
||||
export const selectTagColors = createSelector(selectTagsColors,
|
||||
(tagsColors, props: { tag: string }) => tagsColors[props.tag]);
|
||||
|
||||
@@ -15,6 +15,8 @@ import {
|
||||
} from '~/business-logic/model/organization/organizationGetUserCompaniesResponseCompanies';
|
||||
import {GettingStarted} from '~/core/actions/users.action';
|
||||
import {UsersGetCurrentUserResponseSettings} from '~/business-logic/model/users/usersGetCurrentUserResponseSettings';
|
||||
import {AuthEditUserRequest} from '~/business-logic/model/auth/authEditUserRequest';
|
||||
import RoleEnum = AuthEditUserRequest.RoleEnum;
|
||||
|
||||
export interface UsersState {
|
||||
currentUser: GetCurrentUserResponseUserObject;
|
||||
@@ -44,6 +46,7 @@ export const users = state => state.users as UsersState;
|
||||
export const selectSettings = createSelector(users, (state) => state?.settings);
|
||||
export const selectMaxDownloadItems = createSelector(selectSettings, (state): number => state?.max_download_items ?? 1000);
|
||||
export const selectCurrentUser = createSelector(users, state => state.currentUser);
|
||||
export const selectIsAdmin = createSelector(users, state => state.currentUser.role === RoleEnum.Admin);
|
||||
export const selectActiveWorkspace = createSelector(users, state => state.activeWorkspace);
|
||||
export const selectActiveWorkspaceTier = createSelector(selectActiveWorkspace, workspace => workspace?.tier);
|
||||
export const selectUserWorkspaces = createSelector(users, state => state.userWorkspaces);
|
||||
|
||||
@@ -5,13 +5,11 @@ import {
|
||||
getCurrentPageResults,
|
||||
getResultsCount,
|
||||
searchActivate,
|
||||
searchClear,
|
||||
searchExperiments,
|
||||
searchModels,
|
||||
searchOpenDatasets,
|
||||
searchPipelines,
|
||||
searchProjects, searchReports,
|
||||
searchSetTerm,
|
||||
searchStart,
|
||||
setExperimentsResults,
|
||||
setModelsResults, setOpenDatasetsResults,
|
||||
@@ -27,7 +25,6 @@ import {ProjectsGetAllExRequest} from '~/business-logic/model/projects/projectsG
|
||||
import {ApiTasksService} from '~/business-logic/api-services/tasks.service';
|
||||
import {ApiModelsService} from '~/business-logic/api-services/models.service';
|
||||
import {catchError, mergeMap, map, switchMap} from 'rxjs/operators';
|
||||
import {isEqual} from 'lodash-es';
|
||||
import {activeSearchLink} from '~/features/dashboard-search/dashboard-search.consts';
|
||||
import {emptyAction} from '~/app.constants';
|
||||
import {escapeRegex} from '@common/shared/utils/escape-regex';
|
||||
@@ -119,10 +116,10 @@ export class DashboardSearchEffects {
|
||||
this.store.select(selectActiveSearch),
|
||||
this.store.select(selectSearchTerm)
|
||||
]),
|
||||
mergeMap(([action, active, term]) => {
|
||||
mergeMap(([, active, term]) => {
|
||||
const actionsToFire = [];
|
||||
if (!active) {
|
||||
actionsToFire.push(searchClear());
|
||||
// actionsToFire.push(searchClear());
|
||||
actionsToFire.push(searchActivate());
|
||||
}
|
||||
actionsToFire.push(getResultsCount(term));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {createFeatureSelector, createSelector, ReducerTypes, on, createReducer} from '@ngrx/store';
|
||||
import {createFeatureSelector, createSelector, ReducerTypes, on, createReducer, ActionCreator} from '@ngrx/store';
|
||||
import {Project} from '~/business-logic/model/projects/project';
|
||||
import {User} from '~/business-logic/model/users/user';
|
||||
import {Task} from '~/business-logic/model/tasks/task';
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
import {SearchState} from '../common-search/common-search.reducer';
|
||||
import {ActiveSearchLink, activeSearchLink} from '~/features/dashboard-search/dashboard-search.consts';
|
||||
import {IReport} from '@common/reports/reports.consts';
|
||||
import {setFilterByUser} from '@common/core/actions/users.actions';
|
||||
|
||||
export interface DashboardSearchState {
|
||||
projects: Project[];
|
||||
@@ -48,8 +49,8 @@ export const searchInitialState: DashboardSearchState = {
|
||||
};
|
||||
|
||||
export const dashboardSearchReducers = [
|
||||
on(searchActivate, (state) => ({...state, active: true})),
|
||||
on(searchDeactivate, (state) => ({
|
||||
on(searchActivate, (state): DashboardSearchState => ({...state, active: true})),
|
||||
on(searchDeactivate, (state): DashboardSearchState => ({
|
||||
...state,
|
||||
active: false,
|
||||
term: searchInitialState.term,
|
||||
@@ -57,47 +58,49 @@ export const dashboardSearchReducers = [
|
||||
scrollIds: null,
|
||||
resultsCount: null
|
||||
})),
|
||||
on(searchSetTerm, (state, action) => ({...state, term: action, forceSearch: action.force, scrollIds: null})),
|
||||
on(setProjectsResults, (state, action) => ({
|
||||
on(searchSetTerm, (state, action): DashboardSearchState => ({...state, term: action, forceSearch: action.force, scrollIds: null})),
|
||||
on(setFilterByUser, (state): DashboardSearchState => ({...state, scrollIds: null})),
|
||||
|
||||
on(setProjectsResults, (state, action): DashboardSearchState => ({
|
||||
...state,
|
||||
projects: action.scrollId === state.scrollIds?.[activeSearchLink.projects] ? state.projects.concat(action.projects) : action.projects,
|
||||
scrollIds: {...state.scrollIds, [activeSearchLink.projects]: action.scrollId}
|
||||
})),
|
||||
on(setPipelinesResults, (state, action) => ({
|
||||
on(setPipelinesResults, (state, action): DashboardSearchState => ({
|
||||
...state,
|
||||
pipelines: action.scrollId === state.scrollIds?.[activeSearchLink.pipelines] ? state.pipelines.concat(action.pipelines) : action.pipelines,
|
||||
scrollIds: {...state.scrollIds, [activeSearchLink.pipelines]: action.scrollId}
|
||||
})),
|
||||
on(setOpenDatasetsResults, (state, action) => ({
|
||||
on(setOpenDatasetsResults, (state, action): DashboardSearchState => ({
|
||||
...state,
|
||||
openDatasets: action.scrollId === state.scrollIds?.[activeSearchLink.openDatasets] ? state.openDatasets.concat(action.openDatasets) : action.openDatasets,
|
||||
scrollIds: {...state.scrollIds, [activeSearchLink.openDatasets]: action.scrollId}
|
||||
})),
|
||||
on(setExperimentsResults, (state, action) => ({
|
||||
on(setExperimentsResults, (state, action): DashboardSearchState => ({
|
||||
...state,
|
||||
experiments: action.scrollId === state.scrollIds?.[activeSearchLink.experiments] ? state.experiments.concat(action.experiments) : action.experiments,
|
||||
scrollIds: {...state.scrollIds, [activeSearchLink.experiments]: action.scrollId}
|
||||
})),
|
||||
on(setModelsResults, (state, action) => ({
|
||||
on(setModelsResults, (state, action): DashboardSearchState => ({
|
||||
...state,
|
||||
models: action.scrollId === state.scrollIds?.[activeSearchLink.models] ? state.models.concat(action.models) : action.models,
|
||||
scrollIds: {...state.scrollIds, [activeSearchLink.models]: action.scrollId}
|
||||
})),
|
||||
on(setReportsResults, (state, action) => ({
|
||||
on(setReportsResults, (state, action): DashboardSearchState => ({
|
||||
...state,
|
||||
reports: action.scrollId === state.scrollIds?.[activeSearchLink.reports] ? state.reports.concat(action.reports) : action.reports,
|
||||
scrollIds: {...state.scrollIds, [activeSearchLink.reports]: action.scrollId}
|
||||
})),
|
||||
on(setResultsCount, (state, action) => ({...state, resultsCount: action.counts})),
|
||||
on(clearSearchResults, (state) => ({
|
||||
on(setResultsCount, (state, action): DashboardSearchState => ({...state, resultsCount: action.counts})),
|
||||
on(clearSearchResults, (state): DashboardSearchState => ({
|
||||
...state,
|
||||
[activeSearchLink.models]: [],
|
||||
[activeSearchLink.experiments]: [],
|
||||
[activeSearchLink.pipelines]: [],
|
||||
[activeSearchLink.projects]: [],
|
||||
})),
|
||||
on(searchClear, (state) => ({...state, ...searchInitialState})),
|
||||
] as ReducerTypes<DashboardSearchState, any>[];
|
||||
on(searchClear, (state): DashboardSearchState => ({...state, ...searchInitialState})),
|
||||
] as ReducerTypes<DashboardSearchState, ActionCreator[]>[];
|
||||
|
||||
export const dashboardSearchReducer = createReducer(
|
||||
searchInitialState,
|
||||
|
||||
@@ -5,11 +5,9 @@ import {DashboardExperimentsComponent} from './containers/dashboard-experiments/
|
||||
import {RecentExperimentTableComponent} from './dumb/recent-experiment-table/recent-experiment-table.component';
|
||||
import {CommonDashboardEffects} from './common-dashboard.effects';
|
||||
import {CommonSearchModule} from '../common-search/common-search.module';
|
||||
import {CommonLayoutModule} from '../layout/layout.module';
|
||||
import {EffectsModule} from '@ngrx/effects';
|
||||
import {ProjectsSharedModule} from '~/features/projects/shared/projects-shared.module';
|
||||
import {CommonProjectsModule} from '../projects/common-projects.module';
|
||||
import {SharedModule} from '~/shared/shared.module';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {ExperimentCompareSharedModule} from '@common/experiments-compare/shared/experiment-compare-shared.module';
|
||||
import {ProjectCardComponent} from '@common/shared/ui-components/panel/project-card/project-card.component';
|
||||
@@ -32,9 +30,7 @@ import {ShowTooltipIfEllipsisDirective} from '@common/shared/ui-components/indic
|
||||
CommonSearchModule,
|
||||
ProjectsSharedModule,
|
||||
EffectsModule.forFeature([CommonDashboardEffects]),
|
||||
CommonLayoutModule,
|
||||
CommonProjectsModule,
|
||||
SharedModule,
|
||||
FormsModule,
|
||||
ExperimentCompareSharedModule,
|
||||
ProjectCardComponent,
|
||||
|
||||
@@ -4,15 +4,8 @@ import {Task} from '~/business-logic/model/tasks/task';
|
||||
import {User} from '~/business-logic/model/users/user';
|
||||
import {setRecentExperiments, setRecentProjects} from './common-dashboard.actions';
|
||||
|
||||
export interface IRecentTask {
|
||||
id: Task['id'];
|
||||
name?: Task['name'];
|
||||
export interface IRecentTask extends Omit<Task, 'user' | 'project'> {
|
||||
user?: User;
|
||||
type?: Task['type'];
|
||||
status?: Task['status'];
|
||||
created?: Task['created'];
|
||||
started?: Task['started'];
|
||||
completed?: Task['completed'];
|
||||
project?: Project;
|
||||
}
|
||||
|
||||
@@ -23,8 +16,8 @@ export interface DashboardState {
|
||||
|
||||
// Todo remove selectedProjectId
|
||||
export const dashboardInitState: DashboardState = {
|
||||
recentProjects: [],
|
||||
recentTasks : [],
|
||||
recentProjects: null,
|
||||
recentTasks : null,
|
||||
};
|
||||
|
||||
export const commonDashboardReducers = [
|
||||
@@ -38,5 +31,6 @@ export const commonDashboardReducer = createReducer(
|
||||
);
|
||||
|
||||
export const selectDashboard = createFeatureSelector<DashboardState>('dashboard');
|
||||
export const selectRecentProjects = createSelector(selectDashboard, (state: DashboardState): Array<Project> => state ? state.recentProjects : []);
|
||||
export const selectRecentTasks = createSelector(selectDashboard, (state: DashboardState): Array<IRecentTask> => state ? state.recentTasks : []);
|
||||
export const selectRecentProjects = createSelector(selectDashboard, state => state.recentProjects);
|
||||
export const selectRecentProjectsCount = createSelector(selectRecentProjects , projects => projects?.length);
|
||||
export const selectRecentTasks = createSelector(selectDashboard, state => state.recentTasks);
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
<ng-content select="[header-buttons]"></ng-content>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<sm-recent-tasks-table [tasks]="recentTasks"
|
||||
(taskSelected)="taskSelected($event)">
|
||||
</sm-recent-tasks-table>
|
||||
@if (recentTasks) {
|
||||
<sm-recent-tasks-table [tasks]="recentTasks"
|
||||
(taskSelected)="taskSelected($event)">
|
||||
</sm-recent-tasks-table>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {Component, inject, Input} from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { IRecentTask} from '../../common-dashboard.reducer';
|
||||
import {getRecentExperiments} from '../../common-dashboard.actions';
|
||||
import { ITask } from '../../../../business-logic/model/al-task';
|
||||
import {selectCurrentUser} from '../../../core/reducers/users-reducer';
|
||||
import { ITask } from '~/business-logic/model/al-task';
|
||||
import {selectCurrentUser} from '@common/core/reducers/users-reducer';
|
||||
import {filter, take} from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
@@ -12,22 +12,20 @@ import {filter, take} from 'rxjs/operators';
|
||||
templateUrl: './dashboard-experiments.component.html',
|
||||
styleUrls: ['./dashboard-experiments.component.scss']
|
||||
})
|
||||
export class DashboardExperimentsComponent implements OnInit {
|
||||
export class DashboardExperimentsComponent {
|
||||
private store = inject(Store);
|
||||
private router = inject(Router);
|
||||
@Input() recentTasks: IRecentTask[];
|
||||
|
||||
constructor(private store: Store, private router: Router) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
constructor() {
|
||||
this.store.select(selectCurrentUser)
|
||||
.pipe(filter(user => !!user), take(1))
|
||||
.subscribe(() => this.store.dispatch((getRecentExperiments())));
|
||||
}
|
||||
|
||||
public taskSelected(task: IRecentTask | ITask) {
|
||||
// TODO ADD task.id to route
|
||||
const projectId = task.project ? task.project.id : '*';
|
||||
return this.router.navigateByUrl('projects/' + projectId + '/experiments/' + task.id);
|
||||
return this.router.navigate(['projects', projectId, 'experiments', task.id]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,22 +4,28 @@
|
||||
<button class="btn btn-link view-all" (click)="router.navigateByUrl('/projects')">VIEW ALL</button>
|
||||
</div>
|
||||
<div>
|
||||
<button *ngIf="(recentProjectsList$ | async).length >= cardsInRow || overflow"
|
||||
class="btn btn-cml-primary d-flex align-items-center"
|
||||
data-id="New Project"
|
||||
(click)="openCreateProjectDialog()">
|
||||
<i class="al-icon sm al-ico-add me-2"></i>NEW PROJECT
|
||||
</button>
|
||||
@if (recentProjectsListCount$() >= cardsInRow || overflow) {
|
||||
<button
|
||||
class="btn btn-cml-primary d-flex align-items-center"
|
||||
data-id="New Project"
|
||||
(click)="openCreateProjectDialog()">
|
||||
<i class="al-icon sm al-ico-add me-2"></i>NEW PROJECT
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<sm-project-card
|
||||
*ngFor="let project of recentProjectsList$ | async; trackBy: trackById"
|
||||
[project]="project" (projectCardClicked)="projectCardClicked($event)"
|
||||
[hideMenu]="true"
|
||||
></sm-project-card>
|
||||
<sm-plus-card
|
||||
*ngIf="(recentProjectsList$ | async).length < cardsInRow"
|
||||
[folder]="true"
|
||||
(plusCardClick)="openCreateProjectDialog()"
|
||||
></sm-plus-card>
|
||||
@if (recentProjectsList$(); as recentProjectsList) {
|
||||
@for(project of recentProjectsList; track project.id) {
|
||||
<sm-project-card
|
||||
[project]="project" (projectCardClicked)="projectCardClicked($event)"
|
||||
[hideMenu]="true"
|
||||
></sm-project-card>
|
||||
}
|
||||
@if (recentProjectsList.length < cardsInRow) {
|
||||
<sm-plus-card
|
||||
[folder]="true"
|
||||
(plusCardClick)="openCreateProjectDialog()"
|
||||
></sm-plus-card>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
import {Component, OnInit, Output, EventEmitter, AfterViewInit, ViewChild, ElementRef, OnDestroy} from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
Output,
|
||||
EventEmitter,
|
||||
AfterViewInit,
|
||||
ViewChild,
|
||||
ElementRef,
|
||||
OnDestroy,
|
||||
inject
|
||||
} from '@angular/core';
|
||||
import {Router} from '@angular/router';
|
||||
import {MatDialog, MatDialogRef} from '@angular/material/dialog';
|
||||
import {fromEvent, Observable, Subscription} from 'rxjs';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {Project} from '~/business-logic/model/projects/project';
|
||||
import {selectRecentProjects} from '../../common-dashboard.reducer';
|
||||
import {selectRecentProjects, selectRecentProjectsCount} from '../../common-dashboard.reducer';
|
||||
import {getRecentProjects} from '../../common-dashboard.actions';
|
||||
import {ProjectDialogComponent} from '@common/shared/project-dialog/project-dialog.component';
|
||||
import {resetSelectedProject, setSelectedProjectId} from '@common/core/actions/projects.actions';
|
||||
@@ -12,40 +22,34 @@ import {selectCurrentUser} from '@common/core/reducers/users-reducer';
|
||||
import {filter, take, throttleTime} from 'rxjs/operators';
|
||||
import {isExample} from '@common/shared/utils/shared-utils';
|
||||
import { CARDS_IN_ROW } from '../../common-dashboard.const';
|
||||
import {trackById} from '@common/shared/utils/forms-track-by';
|
||||
|
||||
@Component({
|
||||
selector : 'sm-dashboard-projects',
|
||||
templateUrl: './dashboard-projects.component.html',
|
||||
styleUrls : ['./dashboard-projects.component.scss']
|
||||
})
|
||||
export class DashboardProjectsComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
public recentProjectsList$: Observable<Array<Project>>;
|
||||
export class DashboardProjectsComponent implements AfterViewInit, OnDestroy {
|
||||
private store = inject(Store);
|
||||
protected router = inject(Router);
|
||||
private matDialog = inject(MatDialog);
|
||||
public recentProjectsList$ = this.store.selectSignal(selectRecentProjects);
|
||||
public recentProjectsListCount$ = this.store.selectSignal(selectRecentProjectsCount);
|
||||
private dialog: MatDialogRef<ProjectDialogComponent>;
|
||||
private sub: Subscription;
|
||||
readonly cardsInRow = CARDS_IN_ROW;
|
||||
overflow: boolean;
|
||||
trackById = trackById;
|
||||
|
||||
@Output() width = new EventEmitter<number>();
|
||||
|
||||
constructor(
|
||||
private store: Store,
|
||||
public router: Router,
|
||||
private matDialog: MatDialog
|
||||
) {
|
||||
this.recentProjectsList$ = this.store.select(selectRecentProjects);
|
||||
}
|
||||
|
||||
@ViewChild('header') header: ElementRef<HTMLDivElement>;
|
||||
|
||||
ngOnInit() {
|
||||
constructor() {
|
||||
this.store.dispatch(resetSelectedProject());
|
||||
this.store.select(selectCurrentUser)
|
||||
.pipe(filter(user => !!user), take(1))
|
||||
.subscribe(() => this.store.dispatch(getRecentProjects()));
|
||||
}
|
||||
|
||||
@ViewChild('header') header: ElementRef<HTMLDivElement>;
|
||||
|
||||
ngAfterViewInit() {
|
||||
window.setTimeout(() => this.width.emit(this.header.nativeElement.getBoundingClientRect().width));
|
||||
this.sub = fromEvent(window, 'resize')
|
||||
|
||||
@@ -8,7 +8,6 @@ import {combineLatest, Observable, Subscription} from 'rxjs';
|
||||
import {SearchState, selectSearchQuery} from '../common-search/common-search.reducer';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {
|
||||
selectActiveSearch,
|
||||
selectDatasetsResults,
|
||||
selectExperimentsResults,
|
||||
selectModelsResults,
|
||||
@@ -28,37 +27,19 @@ import {ActivatedRoute, Router} from '@angular/router';
|
||||
import { selectShowOnlyUserWork } from '@common/core/reducers/users-reducer';
|
||||
import {IReport} from '@common/reports/reports.consts';
|
||||
import {isEqual} from 'lodash-es';
|
||||
import { Task } from '~/business-logic/model/tasks/task';
|
||||
|
||||
@Component({
|
||||
selector: 'sm-dashboard-search-base',
|
||||
template: `<sm-search-results-page
|
||||
*ngIf="activeSearch$ | async"
|
||||
(projectSelected)="projectCardClicked($event)"
|
||||
(experimentSelected)="taskSelected($event)"
|
||||
(modelSelected)="modelSelected($event)"
|
||||
(pipelineSelected)="pipelineSelected($event)"
|
||||
(activeLinkChanged)="changeActiveLink($event)"
|
||||
(reportSelected)="reportSelected($event)"
|
||||
(openDatasetSelected)="openDatasetCardClicked($event)"
|
||||
(loadMoreClicked)="loadMore()"
|
||||
[projectsList]="projectsResults$ | async"
|
||||
[pipelinesList]="pipelinesResults$ | async"
|
||||
[datasetsList]="datasetsResults$ | async"
|
||||
[experimentsList]="experimentsResults$ | async"
|
||||
[modelsList]="modelsResults$ | async"
|
||||
[reportsList]="reportsResults$ | async"
|
||||
[activeLink]="activeLink"
|
||||
[resultsCount]="resultsCount$ | async">
|
||||
</sm-search-results-page>`,
|
||||
template: '',
|
||||
})
|
||||
export class DashboardSearchBaseComponent implements OnInit, OnDestroy{
|
||||
export abstract class DashboardSearchBaseComponent implements OnInit, OnDestroy{
|
||||
public activeLink = 'projects' as ActiveSearchLink;
|
||||
private searchSubs;
|
||||
public searchQuery$: Observable<SearchState['searchQuery']>;
|
||||
public activeSearch$: Observable<boolean>;
|
||||
public modelsResults$: Observable<Array<Model>>;
|
||||
public projectsResults$: Observable<Array<Project>>;
|
||||
public experimentsResults$: Observable<any>;
|
||||
public experimentsResults$: Observable<Task[]>;
|
||||
public searchTerm$: Observable<SearchState['searchQuery']>;
|
||||
public pipelinesResults$: Observable<Project[]>;
|
||||
public datasetsResults$: Observable<Project[]>;
|
||||
@@ -73,7 +54,6 @@ export class DashboardSearchBaseComponent implements OnInit, OnDestroy{
|
||||
constructor() {
|
||||
this.cdr = inject(ChangeDetectorRef);
|
||||
this.searchQuery$ = this.store.select(selectSearchQuery);
|
||||
this.activeSearch$ = this.store.select(selectActiveSearch);
|
||||
this.modelsResults$ = this.store.select(selectModelsResults);
|
||||
this.reportsResults$ = this.store.select(selectReportsResults);
|
||||
this.pipelinesResults$ = this.store.select(selectPipelinesResults);
|
||||
|
||||
@@ -36,7 +36,7 @@ export class SimpleDatasetVersionContentComponent {
|
||||
@Input() set data(csv: string) {
|
||||
const lines = csv?.trimEnd().split('\n') ?? [];
|
||||
const header = lines.splice(0, 1)[0] ?? '';
|
||||
const colWidth = (this.ref.nativeElement.getBoundingClientRect().width - 150) / 2 ?? 300;
|
||||
const colWidth = (this.ref.nativeElement.getBoundingClientRect().width - 150) / 2;
|
||||
this.columns = header.split(/, ?/)
|
||||
.map((caption, index) => {
|
||||
const width = this.colSizes?.[columnIds[index]] ? `${this.colSizes[columnIds[index]]}px` : null;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "src/app/webapp-common/shared/ui-components/styles/variables";
|
||||
@import "variables";
|
||||
|
||||
:host {
|
||||
.header {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {createAction, props} from '@ngrx/store';
|
||||
import {GroupedHyperParams, MetricOption} from '../reducers/experiments-compare-charts.reducer';
|
||||
import {MetricValueType} from '@common/experiments-compare/experiments-compare.constants';
|
||||
import {MetricValueType, SelectedMetricVariant} from '@common/experiments-compare/experiments-compare.constants';
|
||||
import {MetricVariantResult} from '~/business-logic/model/projects/metricVariantResult';
|
||||
|
||||
export const EXPERIMENTS_COMPARE_SCALARS_GRAPH = 'EXPERIMENTS_COMPARE_SCALARS_GRAPH_';
|
||||
|
||||
@@ -13,14 +14,29 @@ export const setMetricsList = createAction(
|
||||
EXPERIMENTS_COMPARE_SCALARS_GRAPH + 'SET_METRICS_LIST',
|
||||
props<{ metricsList: MetricOption[] }>()
|
||||
);
|
||||
|
||||
export const setMetricsResults = createAction(
|
||||
EXPERIMENTS_COMPARE_SCALARS_GRAPH + 'SET_METRICS_RESULTS',
|
||||
props<{ metricVariantsResults: Array<MetricVariantResult> }>()
|
||||
);
|
||||
export const setTasks = createAction(
|
||||
EXPERIMENTS_COMPARE_SCALARS_GRAPH + 'SET_TASKS',
|
||||
props<{ tasks: any }>()
|
||||
);
|
||||
export const setvalueType = createAction(
|
||||
export const setValueType = createAction(
|
||||
EXPERIMENTS_COMPARE_SCALARS_GRAPH + 'SET_VALUE_TYPE',
|
||||
props<{ valueType: MetricValueType }>()
|
||||
);
|
||||
|
||||
export const setParamsHoverInfo = createAction(
|
||||
EXPERIMENTS_COMPARE_SCALARS_GRAPH + 'SET_PARAMS_HOVER_INFO',
|
||||
props<{ paramsHoverInfo: string[] }>()
|
||||
);
|
||||
|
||||
export const setMetricsHoverInfo = createAction(
|
||||
EXPERIMENTS_COMPARE_SCALARS_GRAPH + 'SET_METRICS_HOVER_INFO',
|
||||
props<{ metricsHoverInfo: SelectedMetricVariant[] }>()
|
||||
);
|
||||
export const setHyperParamsList = createAction(
|
||||
EXPERIMENTS_COMPARE_SCALARS_GRAPH + 'SET_PARAMS_LIST',
|
||||
props<{ hyperParams: GroupedHyperParams }>()
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<div *ngIf="graphData?.length > 0" class="buttons">
|
||||
<button class="btn btn-icon" (click)="downloadImage()"><i class="al-icon al-ico-download"></i></button>
|
||||
<button class="btn btn-icon" smTooltip="Download" (click)="downloadImage()"><i class="al-icon al-ico-download"></i></button>
|
||||
</div>
|
||||
<sm-scatter-plot
|
||||
[xAxisType]="scalar ? 'linear' : 'category'"
|
||||
[xAxisLabel]="params?.[0] ?? $any(params)"
|
||||
[yAxisLabel]="metricName"
|
||||
[extraHoverInfoParams]="extraHoverInfoParams"
|
||||
[data]="graphData"
|
||||
></sm-scatter-plot>
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import {Component, ElementRef, inject, Input, OnChanges} from '@angular/core';
|
||||
import {Component, ElementRef, inject, Input, OnChanges, SimpleChanges} from '@angular/core';
|
||||
import {ScatterPlotSeries} from '@common/core/reducers/projects.reducer';
|
||||
import {
|
||||
ExtraTask
|
||||
} from '@common/experiments-compare/dumbs/parallel-coordinates-graph/parallel-coordinates-graph.component';
|
||||
import {get} from 'lodash-es';
|
||||
import {get, isEqual} from 'lodash-es';
|
||||
import {from} from 'rxjs';
|
||||
import domtoimage from 'dom-to-image';
|
||||
import {take} from 'rxjs/operators';
|
||||
import {SelectedMetricVariant} from '@common/experiments-compare/experiments-compare.constants';
|
||||
import {MetricVariantToPathPipe} from '@common/shared/pipes/metric-variant-to-path.pipe';
|
||||
|
||||
|
||||
@Component({
|
||||
@@ -15,22 +17,24 @@ import {take} from 'rxjs/operators';
|
||||
styleUrls: ['./compare-scatter-plot.component.scss']
|
||||
})
|
||||
export class CompareScatterPlotComponent implements OnChanges {
|
||||
|
||||
public metricVariantToPathPipe = new MetricVariantToPathPipe;
|
||||
public graphData: ScatterPlotSeries[];
|
||||
public scalar: boolean;
|
||||
|
||||
@Input() metric: string;
|
||||
@Input() metricName!: string;
|
||||
@Input() params: string | string[];
|
||||
@Input() extraHoverInfoParams: string[] = [];
|
||||
@Input() extraHoverInfoMetrics: SelectedMetricVariant[] = [];
|
||||
@Input() experiments: ExtraTask[];
|
||||
|
||||
private ref = inject(ElementRef);
|
||||
|
||||
|
||||
ngOnChanges(): void {
|
||||
ngOnChanges(changes:SimpleChanges): void {
|
||||
this.scalar = true;
|
||||
if (this.experiments && this.params && this.metric) {
|
||||
this.graphData = [{
|
||||
const newGraphData = [{
|
||||
label: '',
|
||||
backgroundColor: '#14aa8c',
|
||||
data: this.experiments
|
||||
@@ -46,9 +50,18 @@ export class CompareScatterPlotComponent implements OnChanges {
|
||||
y: get(point.last_metrics, this.metric),
|
||||
id: point.id,
|
||||
name: point.name,
|
||||
extraParamsHoverInfo: this.extraHoverInfoParams.map(param => `${param}: ${get(point.hyperparams, param)?.value}`).concat(
|
||||
this.extraHoverInfoMetrics.map(metric => {
|
||||
const metricVar = get(point.last_metrics, this.metricVariantToPathPipe.transform(metric));
|
||||
return `${metric?.metric}/${metric?.variant}: value: ${metricVar?.value}, min: ${metricVar?.min_value}, max: ${metricVar?.max_value}`;
|
||||
})
|
||||
)
|
||||
};
|
||||
}),
|
||||
} as ScatterPlotSeries];
|
||||
if (!isEqual(newGraphData, this.graphData) || (changes.metricName?.currentValue!== changes.metricName?.previousValue) || (changes.params?.currentValue!== changes.params?.previousValue)) {
|
||||
this.graphData = newGraphData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ $extra-header-min-height: 50px;
|
||||
cursor: default;
|
||||
|
||||
.fas {
|
||||
width: 3px;
|
||||
width: 6px;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ export abstract class ExperimentCompareBase extends ExperimentCompareDetailsBase
|
||||
public experimentTags: { [experimentId: string]: string[] } = {};
|
||||
private timeoutIndex: number;
|
||||
private originalScrolledElement: EventTarget;
|
||||
private treeCardBody: HTMLDivElement;
|
||||
protected treeCardBody: HTMLDivElement;
|
||||
protected entityType = EntityTypeEnum.experiment;
|
||||
protected router: Router;
|
||||
protected store: Store;
|
||||
@@ -112,7 +112,7 @@ export abstract class ExperimentCompareBase extends ExperimentCompareDetailsBase
|
||||
afterResize() {
|
||||
window.setTimeout(() => {
|
||||
this.nativeWidth = Math.max(this.treeCardBody?.getBoundingClientRect().width, 410);
|
||||
this.cdr.detectChanges();
|
||||
this.cdr.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -64,10 +64,10 @@
|
||||
'hide-identical-mode': hideIdenticalFields
|
||||
}" data-id="selectedRowHighlighter">
|
||||
<div>
|
||||
<pre *ngIf="(node.data.value !== undefined) || (node.data.existOnOrigin && node.data.existOnCompared)"
|
||||
[class.no-ellipsis]="((node.data.key | hideHash) + node.data.value).length < 45"
|
||||
<pre #row *ngIf="(node.data.value !== undefined) || (node.data.existOnOrigin && node.data.existOnCompared)"
|
||||
[class.no-ellipsis]="(nativeWidth -2 > row.scrollWidth) && (row.scrollWidth === row.clientWidth)"
|
||||
[class.with-ellipsis]="showEllipsis"
|
||||
[style.width.px]="showEllipsis ? nativeWidth - 55 - node.level * 20 : null" data-id="diffDataRow"
|
||||
[style.width.px]="showEllipsis ? nativeWidth - 45 - node.level * 20 : null" data-id="diffDataRow"
|
||||
><ng-container
|
||||
*ngIf="!!node.data.value?.dataDictionary && !!node.data.value?.link; else simple">{{node.data.key |
|
||||
hideHash}}<span
|
||||
|
||||
@@ -51,6 +51,7 @@ export class ExperimentCompareDetailsComponent extends ExperimentCompareBase imp
|
||||
|
||||
this.resetComponentState(experiments);
|
||||
this.calculateTree(experiments);
|
||||
this.nativeWidth = Math.max(this.treeCardBody?.getBoundingClientRect().width, 410);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,103 +1,83 @@
|
||||
<div class="list-container light-theme">
|
||||
|
||||
<!--####### Metrics Autocomplete ######-->
|
||||
<div smClickStopPropagation class="metrics-container" tabindex="1" (blur)="listOpen = false">
|
||||
<div class="metric-title">Performance Metric</div>
|
||||
<div class="metrics-search" (click)="openList()">
|
||||
<input #searchMetric
|
||||
type="text"
|
||||
(keydown.escape)="clearMetricSearch(); searchMetric.value= ''"
|
||||
placeholder="Search metric"
|
||||
data-id="searchField"
|
||||
[smTooltip]="selectedMetric?.name"
|
||||
[matTooltipShowDelay]="500"
|
||||
[value]="(!metrics || listOpen) ? '' : selectedMetric?.name"
|
||||
(input)="updateMetricsList($event)"
|
||||
>
|
||||
<i *ngIf="searchMetric.value.length === 0" class="fa fa-search pe-2"></i>
|
||||
<i *ngIf="searchMetric.value.length > 0" class="fa fa-times pointer pe-2" (click)="clearMetricSearchAndSelected(); searchMetric.value= ''"></i>
|
||||
</div>
|
||||
<mat-radio-group
|
||||
*ngIf="!listOpen"
|
||||
class="value-types"
|
||||
[value]="valueType"
|
||||
[disabled]="searchMetric.value=== ''"
|
||||
(change)="valueTypeChange($event)">
|
||||
<mat-radio-button class="sm" [value]="'value'">LAST</mat-radio-button>
|
||||
<mat-radio-button class="sm" [value]="'min_value'">MIN</mat-radio-button>
|
||||
<mat-radio-button class="sm" [value]="'max_value'">MAX</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
<div class="metric-list" [ngClass]="{'metric-list--show': listOpen}">
|
||||
<mat-expansion-panel *ngFor="let metricGroup of metricsOptions; trackBy: trackMetricByFn"
|
||||
class="metric-list__panel"
|
||||
[expanded]="listOpen && searchMetric.value.length > 0"
|
||||
togglePosition="before">
|
||||
<mat-expansion-panel-header (click)="searchMetric.focus()" class="metric-list__header" expandedHeight="40px" collapsedHeight="40px">
|
||||
<mat-panel-title class="metric-list__title">
|
||||
{{metricGroup.metricName}}
|
||||
</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<ng-template matExpansionPanelContent>
|
||||
<div *ngFor="let variant of metricGroup.variants; trackBy: trackVariantByFn"
|
||||
class="metric-list__item"
|
||||
[class.selected]="$any(variant).value.name === selectedMetric?.name"
|
||||
(click)="metricSelected($any(variant))">
|
||||
<span class="ellipsis">{{$any(variant).name}}</span>
|
||||
</div>
|
||||
</ng-template>
|
||||
</mat-expansion-panel>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<hr class="separate-margins">
|
||||
|
||||
<!--####### Hyper Params Checked List ######-->
|
||||
<sm-grouped-checked-filter-list
|
||||
titleText="Parameters"
|
||||
[itemsList]="hyperParams"
|
||||
[selectedItemsList]="selectedHyperParams"
|
||||
[selectFilteredItems]="selectShowIdenticalHyperParams$ | async"
|
||||
[selectedItemsListMapper]="selectedItemsListMapper"
|
||||
selectedItemsListPrefix=""
|
||||
[limitSelection]="50"
|
||||
[single]="scatter"
|
||||
(selectedItems)="selectedParamsChanged($event)"
|
||||
(clearSelection)="clearSelection()"
|
||||
>
|
||||
<mat-slide-toggle
|
||||
(change)="showIdenticalParamsToggled()"
|
||||
[checked]="!showIdenticalParamsActive">Hide identical fields
|
||||
</mat-slide-toggle>
|
||||
</sm-grouped-checked-filter-list>
|
||||
|
||||
@if (scatter) {
|
||||
<div class="metric-title">Plot axes</div>
|
||||
<div class="label">Y-axis</div>
|
||||
<sm-metric-variant-selector [title]="scatter? 'Metric' :'Select Performance Metric'" class="param-selector"
|
||||
[selectedMetricVariants]="selectedMetric? [selectedMetric]: []"
|
||||
[metricVariants]="metricsResults$ | async"
|
||||
(selectMetricVariant)="metricVariantSelected($event)"
|
||||
(clearSelection)="clearMetricsSelection()"
|
||||
></sm-metric-variant-selector>
|
||||
} @else {
|
||||
<div class="metric-title">Coordinates</div>
|
||||
<sm-metric-variant-selector class="param-selector" [title]="'Performance Metrics'"
|
||||
[selectedMetricVariants]="selectedMetrics" [multiSelect]="true" [skipValueType]="false"
|
||||
[metricVariants]="metricsResults$ | async"
|
||||
(selectMetricVariant)="multiMetricVariantSelected($event)"
|
||||
(removeMetric)="multiMetricVariantSelected({addCol: false, variant: $event, valueType:$event.valueType})"
|
||||
(clearSelection)="clearMetricsSelection()"
|
||||
></sm-metric-variant-selector>
|
||||
}
|
||||
@if (scatter) {
|
||||
<div class="label">X-axis</div>
|
||||
}
|
||||
<sm-param-selector class="param-selector"
|
||||
[itemsList]="hyperParams"
|
||||
[title]="'Select Parameters'"
|
||||
[selectedHyperParams]="selectedHyperParams"
|
||||
[single]="scatter"
|
||||
[selectFilteredItems]="selectHideIdenticalHyperParams$ | async"
|
||||
[selectedItemsListMapper]="selectedItemsListMapper"
|
||||
(selectedItems)="selectedParamsChanged($event)"
|
||||
(clearSelection)="clearParamsSelection()"></sm-param-selector>
|
||||
@if (scatter) {
|
||||
<hr class="separate-margins">
|
||||
<div class="metric-title">Additional data point information</div>
|
||||
<sm-metric-variant-selector class="param-selector"
|
||||
[title]="'Select Metrics'"
|
||||
[selectedMetricVariants]="(selectedMetricsHoverInfo$ | async)"
|
||||
[multiSelect]="true"
|
||||
[skipValueType]="true"
|
||||
[metricVariants]="metricsResults$ | async"
|
||||
(selectMetricVariant)="metricVariantForHoverSelected($event)"
|
||||
(removeMetric)="metricVariantForHoverSelected({addCol: false, variant: $event, valueType:null})"
|
||||
(clearSelection)="clearMetricsSelectionForHover()"
|
||||
></sm-metric-variant-selector>
|
||||
<sm-param-selector class="param-selector"
|
||||
[title]="'Select Parameters'"
|
||||
[itemsList]="hyperParams"
|
||||
[selectedHyperParams]="selectedParamsHoverInfo$ | async"
|
||||
[single]="false"
|
||||
[selectFilteredItems]="selectHideIdenticalHyperParams$ | async"
|
||||
[selectedItemsListMapper]="selectedItemsListMapper"
|
||||
(selectedItems)="selectedParamsForHoverChanged($event)"
|
||||
(clearSelection)="clearParamsForHoverSelection()"></sm-param-selector>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="graphs-container h-100">
|
||||
<ng-container
|
||||
*ngIf="(experiments$ | async).length > 1 && selectedHyperParams?.length > 0 && !!selectedMetric; else no_data"
|
||||
>
|
||||
<sm-compare-scatter-plot
|
||||
*ngIf="scatter; else parallel"
|
||||
[params]="selectedHyperParams"
|
||||
[metric]="selectedMetric.path + '.' + valueType"
|
||||
[metricName]="selectedMetric.name"
|
||||
[experiments]="experiments$ | async"
|
||||
></sm-compare-scatter-plot>
|
||||
<ng-template #parallel>
|
||||
<sm-parallel-coordinates-graph
|
||||
*ngIf="plotlyReady$ | async"
|
||||
[experiments]="experiments$ | async"
|
||||
[parameters]="selectedHyperParams"
|
||||
[metric]="selectedMetric"
|
||||
[metricValueType]="valueType"
|
||||
(createEmbedCode)="createEmbedCode($event)"
|
||||
></sm-parallel-coordinates-graph>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<ng-template #no_data>
|
||||
<div class="d-flex align-items-center justify-content-center flex-column h-100 no-data">
|
||||
<div class="al-icon al-ico-no-data-graph"></div>
|
||||
<h4 class="no-data-title">NO DATA TO SHOW</h4>
|
||||
<div>Please select parameters & metric</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
@if ((experiments$ | async).length > 1) {
|
||||
@if (scatter && !!selectedMetric && selectedHyperParams?.length > 0) {
|
||||
<sm-compare-scatter-plot [params]="selectedHyperParams"
|
||||
[metric]="selectedMetric | metricVariantToPath: true"
|
||||
[metricName]="selectedMetric | metricVariantToName: true"
|
||||
[experiments]="experiments$ | async"
|
||||
[extraHoverInfoParams]="selectedParamsHoverInfo$ |async"
|
||||
[extraHoverInfoMetrics]="selectedMetricsHoverInfo$ | async"></sm-compare-scatter-plot>
|
||||
}
|
||||
@else if (!scatter && selectedMetrics.length > 0 && selectedHyperParams?.length > 0) {
|
||||
@if (plotlyReady$ | async) {
|
||||
<sm-parallel-coordinates-graph [experiments]="experiments$ | async"
|
||||
[metrics]="selectedMetrics"
|
||||
[parameters]="selectedHyperParams"
|
||||
(createEmbedCode)="createEmbedCode($event)"></sm-parallel-coordinates-graph>
|
||||
}
|
||||
} @else {
|
||||
<div class="d-flex align-items-center justify-content-center flex-column h-100 no-data">
|
||||
<div class="al-icon al-ico-no-data-graph"></div>
|
||||
<h4 class="no-data-title">NO DATA TO SHOW</h4>
|
||||
<div>Please select parameters & metrics</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
height: 50%;
|
||||
flex-grow: 1;
|
||||
|
||||
|
||||
::ng-deep .mat-expansion-panel-header.mat-expansion-toggle-indicator-before {
|
||||
.mat-expansion-indicator {
|
||||
margin: 0 12px 0 2px;
|
||||
@@ -19,6 +20,8 @@
|
||||
width: 360px;
|
||||
height: 100%;
|
||||
border-right: 1px solid #efefef;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
input {
|
||||
padding-left: 0;
|
||||
@@ -28,6 +31,7 @@
|
||||
::ng-deep .mat-expansion-panel-body {
|
||||
padding: 0 12px 0 24px;
|
||||
}
|
||||
|
||||
::ng-deep sm-search {
|
||||
margin-top: 12px;
|
||||
padding-right: 24px;
|
||||
@@ -76,9 +80,17 @@
|
||||
}
|
||||
|
||||
.metric-title {
|
||||
font-size: 16px;
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: $blue-400;
|
||||
padding: 12px 24px 6px 0;
|
||||
border-bottom: 1px solid $blue-100;
|
||||
margin: 0 24px 12px;
|
||||
}
|
||||
.label{
|
||||
padding-left: 24px;
|
||||
color: $blue-400;
|
||||
}
|
||||
|
||||
.metrics-search {
|
||||
@@ -100,7 +112,7 @@
|
||||
}
|
||||
|
||||
.separate-margins {
|
||||
margin: 24px 0 20px 0;
|
||||
margin: 12px 0 6px 0;
|
||||
}
|
||||
|
||||
.metric-list {
|
||||
@@ -189,9 +201,13 @@
|
||||
font-size: 140px;
|
||||
width: 140px;
|
||||
height: auto;
|
||||
color: rgba(0,0,0,0.1);
|
||||
color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
sm-grouped-checked-filter-list {
|
||||
margin: 0 0 0 24px;
|
||||
}
|
||||
|
||||
.param-selector {
|
||||
padding: 0 24px 12px;
|
||||
}
|
||||
|
||||
@@ -1,46 +1,49 @@
|
||||
import {ChangeDetectorRef, Component, ElementRef, HostListener, OnDestroy, OnInit, ViewChild} from '@angular/core';
|
||||
import {combineLatest, Observable, Subscription} from 'rxjs';
|
||||
import { Store} from '@ngrx/store';
|
||||
import {debounceTime, distinctUntilChanged, filter, map, take, withLatestFrom} from 'rxjs/operators';
|
||||
import {selectRouterParams, selectRouterQueryParams} from '@common/core/reducers/router-reducer';
|
||||
import {has} from 'lodash-es';
|
||||
import {combineLatest, Observable, Subscription, take} from 'rxjs';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {debounceTime, distinctUntilChanged, filter} from 'rxjs/operators';
|
||||
import {selectRouterQueryParams} from '@common/core/reducers/router-reducer';
|
||||
import {flatten, has, isArray, isEqual} from 'lodash-es';
|
||||
import {setExperimentSettings, setSelectedExperiments} from '../../actions/experiments-compare-charts.actions';
|
||||
import {
|
||||
selectCompareIdsFromRoute,
|
||||
selectHideIdenticalFields,
|
||||
selectScalarsGraphHyperParams,
|
||||
selectScalarsGraphMetrics,
|
||||
selectScalarsGraphShowIdenticalHyperParams,
|
||||
selectScalarsGraphMetricsResults,
|
||||
selectScalarsGraphTasks,
|
||||
selectScalarsMetricsHoverInfo,
|
||||
selectScalarsParamsHoverInfo,
|
||||
selectSelectedSettingsHyperParams,
|
||||
selectSelectedSettingsHyperParamsHoverInfo,
|
||||
selectSelectedSettingsMetric,
|
||||
selectSelectedSettingsValueType
|
||||
selectSelectedSettingsMetrics,
|
||||
selectSelectedSettingsMetricsHoverInfo
|
||||
} from '../../reducers';
|
||||
import {
|
||||
getExperimentsHyperParams,
|
||||
setMetricsHoverInfo,
|
||||
setParamsHoverInfo,
|
||||
setShowIdenticalHyperParams,
|
||||
} from '../../actions/experiments-compare-scalars-graph.actions';
|
||||
import {
|
||||
GroupedHyperParams,
|
||||
MetricOption,
|
||||
VariantOption
|
||||
} from '../../reducers/experiments-compare-charts.reducer';
|
||||
import {MatRadioChange} from '@angular/material/radio';
|
||||
import {GroupedHyperParams, MetricOption} from '../../reducers/experiments-compare-charts.reducer';
|
||||
import {selectPlotlyReady} from '@common/core/reducers/view.reducer';
|
||||
import {ExtFrame} from '@common/shared/single-graph/plotly-graph-base';
|
||||
import {RefreshService} from '@common/core/services/refresh.service';
|
||||
import {MetricValueType, SelectedMetric} from '@common/experiments-compare/experiments-compare.constants';
|
||||
import {MetricValueType, SelectedMetricVariant} from '@common/experiments-compare/experiments-compare.constants';
|
||||
import {ReportCodeEmbedService} from '~/shared/services/report-code-embed.service';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {
|
||||
ExtraTask
|
||||
} from '@common/experiments-compare/dumbs/parallel-coordinates-graph/parallel-coordinates-graph.component';
|
||||
import {MetricVariantResult} from '~/business-logic/model/projects/metricVariantResult';
|
||||
import {
|
||||
SelectionEvent
|
||||
} from '@common/experiments/dumb/select-metric-for-custom-col/select-metric-for-custom-col.component';
|
||||
import {MetricResultToSelectedMetricPipe} from '@common/shared/pipes/metric-result-to-selected-metric.pipe';
|
||||
import {MetricVariantToPathPipe} from '@common/shared/pipes/metric-variant-to-path.pipe';
|
||||
|
||||
|
||||
export const _filter = (opt: VariantOption[], value: string): VariantOption[] => {
|
||||
const filterValue = value.toLowerCase();
|
||||
|
||||
return opt.filter(item => item.name.toLowerCase().includes(filterValue));
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 'sm-experiment-compare-hyper-params-graph',
|
||||
templateUrl: './experiment-compare-hyper-params-graph.component.html',
|
||||
@@ -49,36 +52,47 @@ export const _filter = (opt: VariantOption[], value: string): VariantOption[] =>
|
||||
export class ExperimentCompareHyperParamsGraphComponent implements OnInit, OnDestroy {
|
||||
private subs = new Subscription();
|
||||
|
||||
public selectShowIdenticalHyperParams$: Observable<boolean>;
|
||||
public selectHideIdenticalHyperParams$: Observable<boolean>;
|
||||
public hyperParams$: Observable<GroupedHyperParams>;
|
||||
public metrics$: Observable<MetricOption[]>;
|
||||
public selectedHyperParams$: Observable<string[]>;
|
||||
private selectedMetric$: Observable<SelectedMetric>;
|
||||
public metricsOptions$: Observable<MetricOption[]>;
|
||||
public selectedHyperParamsSettings$: Observable<string[]>;
|
||||
public experiments$: Observable<ExtraTask[]>;
|
||||
|
||||
public graphs: { [key: string]: ExtFrame };
|
||||
public selectedHyperParams: string[];
|
||||
public selectedMetric: SelectedMetric;
|
||||
public selectedHyperParams: string[] =[];
|
||||
public selectedMetric: SelectedMetricVariant;
|
||||
public hyperParams: { [section: string]: any };
|
||||
public showIdenticalParamsActive: boolean;
|
||||
public plotlyReady$ = this.store.select(selectPlotlyReady);
|
||||
|
||||
public metricResultToSelectedMetricPipe = new MetricResultToSelectedMetricPipe;
|
||||
public metricVariantToPathPipe = new MetricVariantToPathPipe;
|
||||
public metrics: MetricOption[];
|
||||
public metricsOptions: MetricOption[];
|
||||
public listOpen = true;
|
||||
private initView = true;
|
||||
private taskIds: string[];
|
||||
|
||||
public metricValueType$: Observable<MetricValueType>;
|
||||
public valueType: 'min_value' | 'max_value' | 'value';
|
||||
public scatter: boolean;
|
||||
private settingsKey: string;
|
||||
public metricsResults$: Observable<MetricVariantResult[]>;
|
||||
|
||||
public selectedMetricSettings$: Observable<SelectedMetricVariant>;
|
||||
public selectedParamsHoverInfo$: Observable<string[]>;
|
||||
public selectedMetricsHoverInfo$: Observable<SelectedMetricVariant[]>;
|
||||
private: string[] = [];
|
||||
private selectedParamsHoverInfo: string[];
|
||||
private selectedMetricsHoverInfo: SelectedMetricVariant[];
|
||||
private compareIdsFromRoute$: Observable<string>;
|
||||
private selectedHyperParamsHoverInfoSettings$: Observable<Array<string>>;
|
||||
private selectedMetricsHoverInfoSettings$: Observable<SelectedMetricVariant[]>;
|
||||
private selectedMetricsSettings$: Observable<SelectedMetricVariant[]>;
|
||||
private routeWasLoaded: boolean;
|
||||
private settingsLoaded: boolean;
|
||||
|
||||
public selectedItemsListMapper(data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
@ViewChild('searchMetric') searchMetricRef: ElementRef;
|
||||
selectedMetrics: SelectedMetricVariant[] = [];
|
||||
|
||||
@HostListener('document:click', [])
|
||||
clickOut() {
|
||||
@@ -92,43 +106,40 @@ export class ExperimentCompareHyperParamsGraphComponent implements OnInit, OnDes
|
||||
}
|
||||
|
||||
constructor(private store: Store,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private refresh: RefreshService,
|
||||
private reportEmbed: ReportCodeEmbedService,
|
||||
private cdr: ChangeDetectorRef) {
|
||||
this.metrics$ = this.store.select(selectScalarsGraphMetrics);
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private refresh: RefreshService,
|
||||
private reportEmbed: ReportCodeEmbedService,
|
||||
private cdr: ChangeDetectorRef) {
|
||||
this.metricsOptions$ = this.store.select(selectScalarsGraphMetrics);
|
||||
this.metricsResults$ = this.store.select(selectScalarsGraphMetricsResults);
|
||||
this.hyperParams$ = this.store.select(selectScalarsGraphHyperParams);
|
||||
this.selectedHyperParams$ = this.store.select(selectSelectedSettingsHyperParams);
|
||||
this.selectedMetric$ = this.store.select(selectSelectedSettingsMetric);
|
||||
this.selectShowIdenticalHyperParams$ = this.store.select(selectScalarsGraphShowIdenticalHyperParams);
|
||||
this.selectedHyperParamsSettings$ = this.store.select(selectSelectedSettingsHyperParams);
|
||||
this.selectedHyperParamsHoverInfoSettings$ = this.store.select(selectSelectedSettingsHyperParamsHoverInfo);
|
||||
this.selectedParamsHoverInfo$ = this.store.select(selectScalarsParamsHoverInfo);
|
||||
this.selectedMetricsHoverInfo$ = this.store.select(selectScalarsMetricsHoverInfo);
|
||||
this.selectedMetricSettings$ = this.store.select(selectSelectedSettingsMetric);
|
||||
this.selectedMetricsSettings$ = this.store.select(selectSelectedSettingsMetrics);
|
||||
this.selectedMetricsHoverInfoSettings$ = this.store.select(selectSelectedSettingsMetricsHoverInfo);
|
||||
this.selectHideIdenticalHyperParams$ = this.store.select(selectHideIdenticalFields);
|
||||
this.experiments$ = this.store.select(selectScalarsGraphTasks);
|
||||
this.metricValueType$ = this.store.select(selectSelectedSettingsValueType);
|
||||
|
||||
this.scatter = this.route.snapshot.data?.scatter;
|
||||
this.settingsKey = this.scatter ? 'scatter-param-graph' : 'hyper-param-graph';
|
||||
this.compareIdsFromRoute$ = this.store.select(selectCompareIdsFromRoute);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.subs.add(this.metrics$.pipe(
|
||||
filter(metrics => !!metrics)
|
||||
).subscribe((metrics) => {
|
||||
this.metrics = metrics;
|
||||
this.metricsOptions = [...metrics];
|
||||
}));
|
||||
|
||||
this.subs.add(combineLatest([this.hyperParams$, this.selectShowIdenticalHyperParams$])
|
||||
this.scatter = this.route.snapshot.data?.scatter;
|
||||
this.subs.add(combineLatest([this.hyperParams$, this.selectHideIdenticalHyperParams$])
|
||||
.pipe(
|
||||
filter(([allParams]) => !!allParams),
|
||||
)
|
||||
.subscribe(([allParams, showIdentical]) => {
|
||||
this.showIdenticalParamsActive = showIdentical;
|
||||
.subscribe(([allParams, hideIdentical]) => {
|
||||
this.showIdenticalParamsActive = !hideIdentical;
|
||||
this.hyperParams = Object.entries(allParams)
|
||||
.reduce((acc, [sectionKey, params]) => {
|
||||
const section = Object.keys(params)
|
||||
.sort((a, b) => a.toLowerCase() > b.toLowerCase() ? 1 : -1)
|
||||
.reduce((acc2, paramKey) => {
|
||||
if (showIdentical || params[paramKey]) {
|
||||
if (!hideIdentical || params[paramKey]) {
|
||||
acc2[paramKey] = true;
|
||||
}
|
||||
return acc2;
|
||||
@@ -139,146 +150,144 @@ export class ExperimentCompareHyperParamsGraphComponent implements OnInit, OnDes
|
||||
return acc;
|
||||
}, {});
|
||||
const selectedHyperParams = this.selectedHyperParams?.filter(selectedParam => has(this.hyperParams, selectedParam));
|
||||
selectedHyperParams && this.updateServer(this.selectedMetric, selectedHyperParams, this.valueType)
|
||||
selectedHyperParams && this.updateServer(this.selectedMetric, selectedHyperParams);
|
||||
this.cdr.detectChanges();
|
||||
}));
|
||||
|
||||
this.subs.add(combineLatest([this.metrics$, this.hyperParams$]).pipe(
|
||||
this.subs.add(combineLatest([this.metricsOptions$, this.hyperParams$, this.store.select(selectRouterQueryParams)]).pipe(
|
||||
debounceTime(0),
|
||||
filter(([metircs, hyperparams]) => metircs?.length > 0 && Object.keys(hyperparams || {})?.length > 0),
|
||||
withLatestFrom(this.store.select(selectRouterQueryParams))
|
||||
).subscribe(([[metircs], queryParams]) => {
|
||||
filter(([metrics, hyperparams,]) => ((metrics?.length > 0 || Object.keys(hyperparams || {})?.length > 0) && this.settingsLoaded)),
|
||||
).subscribe(([metrics, , queryParams]) => {
|
||||
this.routeWasLoaded = true;
|
||||
const flatVariants = flatten(metrics.map(m => m.variants)).map(mv => mv.value);
|
||||
if (queryParams.metricPath) {
|
||||
const selectedMetric = metircs.map(a => a.variants).flat(2).find(variant => variant.value.path === queryParams.metricPath)?.value ?? null;
|
||||
const params = Array.isArray(queryParams.params) ? queryParams.params : [queryParams.params];
|
||||
this.updateServer(selectedMetric, params, queryParams.valueType, true);
|
||||
this.listOpen = false;
|
||||
this.cdr.detectChanges();
|
||||
if (this.scatter) {
|
||||
this.selectedMetric = {
|
||||
metric: queryParams.metricName.split('/')[0],
|
||||
variant: queryParams.metricName.split('/')[1],
|
||||
metric_hash: queryParams.metricPath.split('.')[0],
|
||||
variant_hash: queryParams.metricPath.split('.')[1],
|
||||
valueType: queryParams.valueType ?? 'value'
|
||||
};
|
||||
} else {
|
||||
// backwards compatibility 3.21-> 3.20
|
||||
this.selectedMetrics= [{
|
||||
metric: queryParams.metricName.split('/')[0],
|
||||
variant: queryParams.metricName.split('/')[1],
|
||||
metric_hash: queryParams.metricPath.split('.')[0],
|
||||
variant_hash: queryParams.metricPath.split('.')[1],
|
||||
valueType: queryParams.valueType ?? 'value'
|
||||
}]
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.selectedMetric = undefined;
|
||||
}
|
||||
if (queryParams.metricVariants !== undefined) {
|
||||
this.selectedMetrics = !queryParams.metricVariants ? [] : queryParams.metricVariants?.split(',').map(path => {
|
||||
const variant = flatVariants.find(m => path.startsWith(m.path));
|
||||
return {
|
||||
metric: variant.name.split('/')[0],
|
||||
variant: variant.name.split('/')[1],
|
||||
metric_hash: variant.path.split('.')[0],
|
||||
variant_hash: variant.path.split('.')[1],
|
||||
valueType: path.split('.')[2] ?? 'value'
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (queryParams.params) {
|
||||
this.selectedHyperParams = Array.isArray(queryParams.params) ? queryParams.params : [queryParams.params];
|
||||
}
|
||||
this.cdr.detectChanges();
|
||||
}));
|
||||
|
||||
this.subs.add(this.store.select(selectRouterParams).pipe(
|
||||
|
||||
map(params => params?.ids),
|
||||
distinctUntilChanged(),
|
||||
filter(ids => !!ids),
|
||||
this.subs.add(this.compareIdsFromRoute$.pipe(
|
||||
filter((ids) => !!ids),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
.subscribe((ids) => {
|
||||
this.taskIds = ids.split(',');
|
||||
this.store.dispatch(setSelectedExperiments({selectedExperiments: [this.settingsKey].concat(this.taskIds)}));
|
||||
this.store.dispatch(setSelectedExperiments({selectedExperiments: [this.scatter ? 'scatter-param-graph' : 'hyper-param-graph'].concat(this.taskIds)}));
|
||||
this.store.dispatch(getExperimentsHyperParams({experimentsIds: this.taskIds, scatter: this.scatter}));
|
||||
}));
|
||||
|
||||
|
||||
this.subs.add(this.refresh.tick
|
||||
.pipe(filter(auto => auto !== null))
|
||||
.subscribe(autoRefresh =>
|
||||
this.store.dispatch(getExperimentsHyperParams({experimentsIds: this.taskIds, autoRefresh}))
|
||||
));
|
||||
|
||||
this.subs.add(this.metricValueType$.pipe(take(1))
|
||||
.subscribe(valueType => this.valueType = valueType));
|
||||
this.subs.add(this.selectedHyperParams$.pipe(take(1))
|
||||
.subscribe(p => this.selectedHyperParams = p));
|
||||
this.subs.add(this.selectedMetric$.pipe(take(1))
|
||||
.subscribe(selectedMetric => this.selectedMetric = selectedMetric?.path ? {...selectedMetric} : null));
|
||||
|
||||
this.listOpen = false;
|
||||
if (!this.selectedMetric) {
|
||||
this.listOpen = true;
|
||||
window.setTimeout(() => {
|
||||
this.searchMetricRef.nativeElement.focus();
|
||||
this.initView = false;
|
||||
this.cdr.detectChanges();
|
||||
}, 200);
|
||||
}
|
||||
this.subs.add(combineLatest([this.selectedMetricSettings$, this.selectedHyperParamsSettings$,
|
||||
this.selectedMetricsHoverInfoSettings$, this.selectedHyperParamsHoverInfoSettings$, this.selectedMetricsSettings$]).pipe(
|
||||
take(1)
|
||||
)
|
||||
.subscribe(([selectedMetric, selectedParams, selectedMetricsHoverInfo, selectedParamsHoverInfo, selectedMetrics]) => {
|
||||
selectedMetricsHoverInfo?.length > 0 && this.store.dispatch(setMetricsHoverInfo({metricsHoverInfo: selectedMetricsHoverInfo as SelectedMetricVariant[]}));
|
||||
selectedParamsHoverInfo?.length > 0 && this.store.dispatch(setParamsHoverInfo({paramsHoverInfo: selectedParamsHoverInfo}));
|
||||
this.updateServer(selectedMetric, selectedParams, false, null, selectedMetrics, true);
|
||||
this.settingsLoaded = true;
|
||||
}));
|
||||
this.subs.add(this.selectedParamsHoverInfo$
|
||||
.subscribe(p => this.selectedParamsHoverInfo = p));
|
||||
this.subs.add(this.selectedMetricsHoverInfo$
|
||||
.subscribe(p => this.selectedMetricsHoverInfo = p));
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.saveSettingsState();
|
||||
this.subs.unsubscribe();
|
||||
this.saveSettingsState();
|
||||
this.clearParamsForHoverSelection();
|
||||
this.store.dispatch(setMetricsHoverInfo({metricsHoverInfo: []}));
|
||||
}
|
||||
|
||||
private _filterGroup(value: string): MetricOption[] {
|
||||
if (value) {
|
||||
return this.metrics
|
||||
.map(group => ({metricName: group.metricName, variants: _filter(group.variants, value)}))
|
||||
.filter(group => group.variants.length > 0);
|
||||
}
|
||||
|
||||
return this.metrics;
|
||||
}
|
||||
|
||||
metricSelected(metric: VariantOption) {
|
||||
this.updateServer(metric.value, this.selectedHyperParams, this.valueType);
|
||||
this.listOpen = false;
|
||||
metricVariantSelected($event?: SelectionEvent) {
|
||||
this.updateServer({...$event.variant, valueType: $event.valueType}, this.selectedHyperParams);
|
||||
}
|
||||
|
||||
selectedParamsChanged({param}) {
|
||||
if(this.scatter) {
|
||||
this.updateServer(this.selectedMetric, [param], this.valueType);
|
||||
if (this.scatter) {
|
||||
this.updateServer(this.selectedMetric, this.selectedHyperParams.includes(param) ? [] : [param]);
|
||||
} else {
|
||||
const newSelectedParamsList = this.selectedHyperParams.includes(param) ? this.selectedHyperParams.filter(i => i !== param) : [...this.selectedHyperParams, param];
|
||||
this.updateServer(this.selectedMetric, newSelectedParamsList, this.valueType);
|
||||
this.updateServer(this.selectedMetric, newSelectedParamsList);
|
||||
}
|
||||
}
|
||||
|
||||
clearSelection() {
|
||||
this.updateServer(this.selectedMetric, [], this.valueType);
|
||||
this.updateServer(this.selectedMetric, []);
|
||||
}
|
||||
|
||||
showIdenticalParamsToggled() {
|
||||
this.store.dispatch(setShowIdenticalHyperParams());
|
||||
}
|
||||
|
||||
updateServer(selectedMetric: SelectedMetric, selectedParams: string[], valueType, skipNavigation?: boolean) {
|
||||
!skipNavigation && this.router.navigate([], {
|
||||
|
||||
updateServer(selectedMetric?: SelectedMetricVariant, selectedParams?: string[], skipNavigation?: boolean, valueType?: SelectedMetricVariant['valueType'], selectedMetrics?: SelectedMetricVariant[], force?: boolean) {
|
||||
(this.routeWasLoaded || force) && !skipNavigation && this.router.navigate([], {
|
||||
queryParams: {
|
||||
metricPath: selectedMetric?.path || undefined,
|
||||
metricName: selectedMetric?.name || undefined,
|
||||
params: selectedParams,
|
||||
valueType: valueType || 'value'
|
||||
metricPath: selectedMetric ? `${selectedMetric?.metric_hash}.${selectedMetric?.variant_hash}` : undefined,
|
||||
...(isArray(selectedMetrics) && {metricVariants: selectedMetrics.map(mv => this.metricVariantToPathPipe.transform(mv, true)).toString()}),
|
||||
metricName: selectedMetric ? `${selectedMetric?.metric}/${selectedMetric?.variant}` : undefined,
|
||||
...(selectedParams && {params: selectedParams}),
|
||||
valueType: selectedMetric ? selectedMetric?.valueType || valueType || 'value' : undefined
|
||||
},
|
||||
queryParamsHandling: 'merge',
|
||||
replaceUrl: true
|
||||
});
|
||||
this.selectedMetric = selectedMetric;
|
||||
this.selectedHyperParams = selectedParams;
|
||||
this.valueType = valueType;
|
||||
}
|
||||
|
||||
updateMetricsList(event: Event) {
|
||||
this.metricsOptions = this._filterGroup((event.target as HTMLInputElement).value);
|
||||
}
|
||||
|
||||
clearMetricSearchAndSelected() {
|
||||
this.updateServer(null, this.selectedHyperParams, this.valueType);
|
||||
this.selectedMetric = null;
|
||||
this.metricsOptions = this._filterGroup('');
|
||||
}
|
||||
|
||||
clearMetricSearch() {
|
||||
this.metricsOptions = this._filterGroup('');
|
||||
}
|
||||
|
||||
openList() {
|
||||
this.listOpen = true;
|
||||
}
|
||||
|
||||
trackMetricByFn(index: number, item: MetricOption): string {
|
||||
return item.metricName;
|
||||
}
|
||||
|
||||
trackVariantByFn(index: number, item: VariantOption): string {
|
||||
// TODO: validate with @nirla
|
||||
return item.value.path;
|
||||
}
|
||||
|
||||
valueTypeChange($event: MatRadioChange) {
|
||||
this.valueType = $event.value;
|
||||
this.updateServer(this.selectedMetric, this.selectedHyperParams, $event.value, false);
|
||||
}
|
||||
|
||||
createEmbedCode(event: { tasks: string[]; valueType: MetricValueType; metrics?: string[]; variants?: string[]; domRect: DOMRect }) {
|
||||
createEmbedCode(event: {
|
||||
tasks: string[];
|
||||
valueType: MetricValueType;
|
||||
metrics?: string[];
|
||||
variants?: string[];
|
||||
domRect: DOMRect
|
||||
}) {
|
||||
this.reportEmbed.createCode({
|
||||
type: 'parcoords',
|
||||
objects: event.tasks,
|
||||
@@ -289,12 +298,51 @@ export class ExperimentCompareHyperParamsGraphComponent implements OnInit, OnDes
|
||||
|
||||
saveSettingsState() {
|
||||
this.store.dispatch(setExperimentSettings({
|
||||
id: [this.settingsKey].concat(this.taskIds),
|
||||
id: [this.scatter ? 'scatter-param-graph' : 'hyper-param-graph'].concat(this.taskIds),
|
||||
changes: {
|
||||
selectedMetric: this.selectedMetric,
|
||||
selectedMetrics: this.selectedMetrics,
|
||||
selectedHyperParams: this.selectedHyperParams,
|
||||
valueType: this.valueType
|
||||
selectedParamsHoverInfo: this.selectedParamsHoverInfo,
|
||||
selectedMetricsHoverInfo: this.selectedMetricsHoverInfo
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
clearParamsSelection() {
|
||||
this.updateServer(this.selectedMetric, []);
|
||||
}
|
||||
clearMetricsSelection() {
|
||||
this.updateServer(null, undefined,false, undefined, [] );
|
||||
}
|
||||
|
||||
selectedParamsForHoverChanged({param}) {
|
||||
const newSelectedParamsList = this.selectedParamsHoverInfo.includes(param) ? this.selectedParamsHoverInfo.filter(i => i !== param) : [...this.selectedParamsHoverInfo, param];
|
||||
this.store.dispatch(setParamsHoverInfo({paramsHoverInfo: newSelectedParamsList}));
|
||||
|
||||
}
|
||||
|
||||
clearParamsForHoverSelection() {
|
||||
this.store.dispatch(setParamsHoverInfo({paramsHoverInfo: []}));
|
||||
}
|
||||
|
||||
metricVariantForHoverSelected($event: SelectionEvent) {
|
||||
const newSelectedVariantList = $event.addCol ? [...this.selectedMetricsHoverInfo, $event.variant] : [...this.selectedMetricsHoverInfo.filter(metricVar => !isEqual(metricVar, $event.variant))];
|
||||
this.store.dispatch(setMetricsHoverInfo({metricsHoverInfo: newSelectedVariantList as SelectedMetricVariant[]}));
|
||||
}
|
||||
|
||||
multiMetricVariantSelected($event: SelectionEvent) {
|
||||
if ($event.valueType) {
|
||||
const selectedVariant = {...$event.variant, valueType: $event.valueType};
|
||||
const newSelectedVariantList = $event.addCol ? [...this.selectedMetrics, selectedVariant] :
|
||||
[...this.selectedMetrics.filter(metricVar => !isEqual(metricVar, selectedVariant))];
|
||||
this.updateServer(null, this.selectedHyperParams, false, undefined, newSelectedVariantList);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
clearMetricsSelectionForHover() {
|
||||
this.store.dispatch(setMetricsHoverInfo({metricsHoverInfo: [] as SelectedMetricVariant[]}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {createMultiSingleValuesChart, mergeMultiMetrics, mergeMultiMetricsGroupe
|
||||
import {getMultiScalarCharts, getMultiSingleScalars, resetExperimentMetrics, setExperimentMetricsSearchTerm, setExperimentSettings, setScalarsHoverMode, setSelectedExperiments} from '../../actions/experiments-compare-charts.actions';
|
||||
import {selectCompareTasksScalarCharts, selectExperimentMetricsSearchTerm, selectMultiSingleValues, selectScalarsHoverMode, selectSelectedExperiments, selectSelectedExperimentSettings} from '../../reducers';
|
||||
import {ScalarKeyEnum} from '~/business-logic/model/events/scalarKeyEnum';
|
||||
import {GroupByCharts, groupByCharts} from '@common/experiments/reducers/experiment-output.reducer';
|
||||
import {GroupByCharts, groupByCharts} from '@common/experiments/actions/common-experiment-output.actions';
|
||||
import {ExtFrame} from '@common/shared/single-graph/plotly-graph-base';
|
||||
import {RefreshService} from '@common/core/services/refresh.service';
|
||||
import {selectRouterParams} from '@common/core/reducers/router-reducer';
|
||||
@@ -81,7 +81,7 @@ export class ExperimentCompareScalarChartsComponent implements OnInit, OnDestroy
|
||||
smoothType: smoothTypeEnum.exponential,
|
||||
xAxisType: ScalarKeyEnum.Iter,
|
||||
selectedMetricsScalar: []
|
||||
}
|
||||
};
|
||||
private originalSettings: ExperimentCompareSettings;
|
||||
public minimized = false;
|
||||
public splitSize$: Observable<number>;
|
||||
@@ -115,7 +115,7 @@ export class ExperimentCompareScalarChartsComponent implements OnInit, OnDestroy
|
||||
distinctUntilChanged()
|
||||
);
|
||||
this.routerParams$ = this.store.select(selectRouterParams).pipe(
|
||||
filter(params => !!params.ids),
|
||||
filter(params => params.ids !== undefined),
|
||||
distinctUntilChanged()
|
||||
);
|
||||
|
||||
@@ -135,7 +135,7 @@ export class ExperimentCompareScalarChartsComponent implements OnInit, OnDestroy
|
||||
|
||||
if (singleValues) {
|
||||
const visibles = this.graphsComponent?.singleValueGraph.first?.chart.data.reduce((curr, data) => {
|
||||
curr[data.task] = data.visible
|
||||
curr[data.task] = data.visible;
|
||||
return curr;
|
||||
}, {}) ?? {};
|
||||
const singleValuesData = createMultiSingleValuesChart(singleValues, visibles);
|
||||
@@ -150,15 +150,15 @@ export class ExperimentCompareScalarChartsComponent implements OnInit, OnDestroy
|
||||
this.store.select(selectMetricVariants),
|
||||
this.store.select(selectSelectedExperiments)))
|
||||
.subscribe(([params, metrics, selectedExperiments]) => {
|
||||
if (!this.taskIds || this.taskIds.join(',') !== params.ids) {
|
||||
const previousTaskIds = this.taskIds;
|
||||
this.taskIds = params.ids.split(',').sort();
|
||||
this.store.dispatch(setSelectedExperiments({selectedExperiments: this.taskIds}));
|
||||
if (metrics.length === 0 || (metrics.length > 0 && previousTaskIds !== undefined) || !isEqual(selectedExperiments, this.taskIds)) {
|
||||
this.store.dispatch(getCustomMetricsPerType({ids: this.taskIds, metricsType: EventTypeEnum.TrainingStatsScalar, isModel: this.entityType === EntityTypeEnum.model}));
|
||||
if (!this.taskIds || this.taskIds.join(',') !== params.ids) {
|
||||
const previousTaskIds = this.taskIds;
|
||||
this.taskIds = params.ids.split(',').sort().filter(id => !!id);
|
||||
this.store.dispatch(setSelectedExperiments({selectedExperiments: this.taskIds}));
|
||||
if ((metrics.length === 0 || (metrics.length > 0 && previousTaskIds !== undefined) || !isEqual(selectedExperiments, this.taskIds))) {
|
||||
this.store.dispatch(getCustomMetricsPerType({ids: this.taskIds, metricsType: EventTypeEnum.TrainingStatsScalar, isModel: this.entityType === EntityTypeEnum.model}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
}));
|
||||
|
||||
this.subs.add(this.store.select(selectCompareSelectedMetrics('scalars'))
|
||||
.pipe(
|
||||
@@ -178,7 +178,7 @@ export class ExperimentCompareScalarChartsComponent implements OnInit, OnDestroy
|
||||
const newSelectedMetricsScalar = selectedMetrics.filter(m => !m.hidden).map(m => m.metricName + m.variantName);
|
||||
const VariantWasAdded = newSelectedMetricsScalar?.length > this.settings.selectedMetricsScalar?.length;
|
||||
this.settings.selectedMetricsScalar = newSelectedMetricsScalar;
|
||||
const variants = Object.entries(metricsVariants).map(([metricName, variants]) => ({metric: metricName, variants}))
|
||||
const variants = Object.entries(metricsVariants).map(([metricName, variants]) => ({metric: metricName, variants}));
|
||||
this.selectedVariants = variants;
|
||||
if (variants.length > 0) {
|
||||
if (this.firstTime || VariantWasAdded && this.missingVariantGraphInStore()) {
|
||||
@@ -218,8 +218,8 @@ export class ExperimentCompareScalarChartsComponent implements OnInit, OnDestroy
|
||||
selectedMetricsCols = metrics.filter(metric => this.settings.selectedMetricsScalar.includes(metric.metric + metric.variant) || this.settings.selectedMetricsScalar.includes(metric.metric));
|
||||
} else {
|
||||
if (this.settings.groupBy === 'metric') {
|
||||
const uniqueMetrics = Array.from( new Set(metrics.map(a => a.metric)))
|
||||
const FifthMetric = uniqueMetrics[5] ?? uniqueMetrics.at(-1)
|
||||
const uniqueMetrics = Array.from(new Set(metrics.map(a => a.metric)));
|
||||
const FifthMetric = uniqueMetrics[5] ?? uniqueMetrics.at(-1);
|
||||
selectedMetricsCols = metrics.slice(0, metrics.findIndex(metric => metric.metric === FifthMetric) ?? 5);
|
||||
|
||||
} else {
|
||||
@@ -250,8 +250,8 @@ export class ExperimentCompareScalarChartsComponent implements OnInit, OnDestroy
|
||||
return acc;
|
||||
}, {} as { [metric: string]: string[] });
|
||||
|
||||
return Object.entries(selectedMetricsVariants).map(([metricName, variants]) => ({metric: metricName, variants}))
|
||||
}
|
||||
return Object.entries(selectedMetricsVariants).map(([metricName, variants]) => ({metric: metricName, variants}));
|
||||
};
|
||||
|
||||
private prepareGraphsAndUpdate(metrics, singleValues) {
|
||||
if (metrics || singleValues) {
|
||||
@@ -298,7 +298,7 @@ export class ExperimentCompareScalarChartsComponent implements OnInit, OnDestroy
|
||||
}
|
||||
this.settings = {...this.settings, selectedMetricsScalar: selectedList ?? []};
|
||||
if (!this.minimized) {
|
||||
const selectedMetricsCols = this.originMetrics.filter(metric => this.settings.selectedMetricsScalar.includes(metric.metric + metric.variant) || this.settings.selectedMetricsScalar.includes(metric.metric))
|
||||
const selectedMetricsCols = this.originMetrics.filter(metric => this.settings.selectedMetricsScalar.includes(metric.metric + metric.variant) || this.settings.selectedMetricsScalar.includes(metric.metric));
|
||||
this.selectedVariants = this.buildMetricVariants(selectedMetricsCols);
|
||||
if (this.selectedVariants.length > 0) {
|
||||
this.store.dispatch(getMultiScalarCharts({taskIds: this.taskIds, entity: this.entityType, metrics: this.selectedVariants, xAxisType: this.settings.xAxisType}));
|
||||
@@ -372,6 +372,6 @@ export class ExperimentCompareScalarChartsComponent implements OnInit, OnDestroy
|
||||
|
||||
private missingVariantGraphInStore() {
|
||||
const graphs = this.graphs ? Object.keys(this.graphs) : [];
|
||||
return this.settings.selectedMetricsScalar.filter(metric => !metric.startsWith(' Summary')).some( graph => !graphs.includes(graph));
|
||||
return this.settings.selectedMetricsScalar.filter(metric => !metric.startsWith(' Summary')).some(graph => !graphs.includes(graph));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
<mat-drawer-container autosize class="h-100">
|
||||
<mat-drawer #drawer [opened]="filterOpen" mode="over" (closedStart)="filterOpen = false">
|
||||
<i class="close al-icon al-ico-dialog-x" (click)="drawer.close()" data-id="closeToggleGraph"></i>
|
||||
<div class="list-container mt-3">
|
||||
<mat-drawer #drawer [opened]="filterOpen" mode="over" (closedStart)="filterOpen = false; filterValue = ''" class="mat-elevation-z0">
|
||||
<div class="head">
|
||||
<i class="close al-icon al-ico-dialog-x" (click)="drawer.close(); filterValue = ''" data-id="closeToggleGraph"></i>
|
||||
</div>
|
||||
<div class="list-container">
|
||||
<sm-selectable-grouped-filter-list
|
||||
[list]="metricVariantList"
|
||||
[checkedList]="settings.selectedMetricsScalar"
|
||||
[searchTerm]="listSearchTerm"
|
||||
(hiddenChanged)="selectedMetricsChanged($event)"
|
||||
(searchTermChanged)="searchTermChanged($event)"
|
||||
[list]="metricVariantList"
|
||||
[checkedList]="settings.selectedMetricsScalar"
|
||||
[searchTerm]="listSearchTerm"
|
||||
(hiddenChanged)="selectedMetricsChanged($event)"
|
||||
(searchTermChanged)="searchTermChanged($event)"
|
||||
>
|
||||
</sm-selectable-grouped-filter-list>
|
||||
</div>
|
||||
@@ -15,7 +17,7 @@
|
||||
|
||||
<mat-drawer-content>
|
||||
|
||||
<div *ngIf="dataTable?.length === 0" class="no-output" >
|
||||
<div *ngIf="dataTable?.length === 0" class="no-output">
|
||||
<i class="i-no-table-data"></i>
|
||||
<h4>NO METRICS</h4>
|
||||
</div>
|
||||
@@ -25,23 +27,29 @@
|
||||
|
||||
</div>
|
||||
|
||||
<p-table *ngIf="experiments.length > 0"
|
||||
[value]="dataTableFiltered"
|
||||
[scrollable]="true"
|
||||
[virtualScroll]="true"
|
||||
[virtualScrollItemSize]="50"
|
||||
[rowTrackBy]="trackByFunction"
|
||||
styleClass="p-mt-3"
|
||||
scrollHeight="flex"
|
||||
[class.empty-state]="dataTable?.length < 1"
|
||||
[class.no-rows]="dataTableFiltered?.length < 1">
|
||||
<p-table
|
||||
[value]="dataTableFiltered"
|
||||
[scrollable]="true"
|
||||
[virtualScroll]="true"
|
||||
[virtualScrollItemSize]="50"
|
||||
[rowTrackBy]="trackByFunction"
|
||||
styleClass="p-mt-3"
|
||||
scrollHeight="flex"
|
||||
[class.empty-state]="dataTable?.length < 1"
|
||||
[class.no-rows]="dataTableFiltered?.length < 1">
|
||||
<ng-template pTemplate="header">
|
||||
<tr>
|
||||
<th class="filter-header-cell" [class.freeze-divider]="scrolled" pFrozenColumn [colSpan]="2">
|
||||
<div class="filter-header">
|
||||
<i [class]="'al-icon pointer ' + (settings.hiddenMetricsScalar?.length > 0 ? 'al-ico-filter-on':'al-ico-filter-off')" (click)="filterOpen = !filterOpen">
|
||||
<span class="path1"></span><span class="path2"></span>
|
||||
</i>
|
||||
<button class="btn btn-icon" [class.is-filtered]="dataTable?.length > dataTableFiltered?.length">
|
||||
<i [class]="'al-icon pointer ' + (dataTable?.length > dataTableFiltered?.length ? 'al-ico-filter-on':'al-ico-filter-off')"
|
||||
[smTooltip]="dataTableFiltered?.length + '/' + dataTable?.length + ' filtered'"
|
||||
[matTooltipDisabled]="dataTableFiltered?.length === dataTable?.length"
|
||||
[showTooltip]="showFilterTooltip"
|
||||
(click)="filterOpen = !filterOpen">
|
||||
<span class="path1"></span><span class="path2"></span>
|
||||
</i>
|
||||
</button>
|
||||
<mat-form-field appearance="outline" hideRequiredMarker class="light-theme mat-light no-bottom">
|
||||
<input #filterRef
|
||||
name="filter"
|
||||
@@ -49,7 +57,7 @@
|
||||
placeholder="Search metric"
|
||||
matInput
|
||||
autocomplete="off">
|
||||
<i matSuffix class="al-icon sm-md search-icon me-2" [ngClass]="variantFilter.value? 'al-ico-dialog-x pointer' : 'al-ico-search'"
|
||||
<i matSuffix class="al-icon sm-md search-icon me-2" [class]="variantFilter.value? 'al-ico-dialog-x pointer' : 'al-ico-search'"
|
||||
(click)="variantFilter.value && clear(); filterRef.focus()"
|
||||
smClickStopPropagation></i>
|
||||
</mat-form-field>
|
||||
@@ -66,18 +74,18 @@
|
||||
[smTooltip]="exp.name"
|
||||
[delay]="0"
|
||||
[triggerEllipsis]="experiments.length"
|
||||
>{{exp.name}}</span>
|
||||
>{{ exp.name }}</span>
|
||||
</th>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="body" let-metricVariant>
|
||||
<tr [class.first-row]="metricVariant.firstMetricRow">
|
||||
<td class="metrics-column" pFrozenColumn>
|
||||
<span class="ellipsis" [smTooltip]="metricVariant.firstMetricRow ? metricVariant.metric : ''" smShowTooltipIfEllipsis>{{metricVariant.firstMetricRow ? metricVariant.metric : ''}}</span>
|
||||
<span class="ellipsis" [smTooltip]="metricVariant.firstMetricRow ? metricVariant.metric : ''" smShowTooltipIfEllipsis>{{ metricVariant.firstMetricRow ? metricVariant.metric : '' }}</span>
|
||||
</td>
|
||||
<td class="variants-column" [class.freeze-divider]="scrolled" pFrozenColumn>
|
||||
<div class="variant-cell">
|
||||
<span class="ellipsis variant-name" [smTooltip]="metricVariant.variant" smShowTooltipIfEllipsis>{{metricVariant.variant}}</span>
|
||||
<span class="ellipsis variant-name" [smTooltip]="metricVariant.variant" smShowTooltipIfEllipsis>{{ metricVariant.variant }}</span>
|
||||
<i *ngIf="metricVariant.min === metricVariant.max"
|
||||
class="al-icon al-ico-equal-outline sm row-info-icon"
|
||||
smTooltip="All scalars have the same value in this row"></i>
|
||||
@@ -85,7 +93,7 @@
|
||||
</td>
|
||||
<td class="value-cell" *ngFor="let exp of experiments; trackBy: trackById">
|
||||
<div class="value">
|
||||
<span class="value-text" smShowTooltipIfEllipsis [smTooltip]="metricVariant.values[exp.id]?.[valuesMode.key]">{{metricVariant.values[exp.id]?.[valuesMode.key]}}</span>
|
||||
<span class="value-text" smShowTooltipIfEllipsis [smTooltip]="metricVariant.values[exp.id]?.[valuesMode.key]">{{ metricVariant.values[exp.id]?.[valuesMode.key] }}</span>
|
||||
<ng-container *ngIf="(selectShowRowExtremes$ | async) && metricVariant.min !== metricVariant.max">
|
||||
<span *ngIf="metricVariant.values[exp.id]?.[valuesMode.key] === metricVariant.max" class="tag tag-max">MAX</span>
|
||||
<span *ngIf="metricVariant.values[exp.id]?.[valuesMode.key] === metricVariant.min" class="tag tag-min">MIN</span>
|
||||
|
||||
@@ -91,7 +91,6 @@ $tableBottomPaginatorBorderWidth: 0 0 1px 0 !default;
|
||||
|
||||
i {
|
||||
color: $blue-400;
|
||||
margin-left: 8px;
|
||||
|
||||
&:hover {
|
||||
color: $blue-300;
|
||||
@@ -103,7 +102,24 @@ $tableBottomPaginatorBorderWidth: 0 0 1px 0 !default;
|
||||
display: flex;
|
||||
width: 401px;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
gap: 24px;
|
||||
padding-left: 8px;
|
||||
|
||||
.btn-icon {
|
||||
border: 1px solid transparent;
|
||||
background-color: transparent;
|
||||
padding: 5px;
|
||||
|
||||
&:hover {
|
||||
border-color: $blue-250;
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.is-filtered {
|
||||
border-color: $cloudy-blue;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
mat-form-field {
|
||||
flex-grow: 1;
|
||||
@@ -216,11 +232,15 @@ tr.first-row {
|
||||
}
|
||||
}
|
||||
|
||||
.head {
|
||||
padding: 12px 24px 0;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
.close {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import {setExportTable} from '@common/experiments-compare/actions/compare-header
|
||||
import {Table} from 'primeng/table';
|
||||
import {ExperimentCompareSettings} from '@common/experiments-compare/reducers/experiments-compare-charts.reducer';
|
||||
import {GroupedList} from '@common/tasks/tasks.model';
|
||||
import {sortMetricsList} from '@common/tasks/tasks.utils';
|
||||
|
||||
interface ValueMode {
|
||||
key: string;
|
||||
@@ -90,11 +91,12 @@ export class ExperimentCompareMetricValuesComponent implements OnInit, OnDestroy
|
||||
|
||||
private subs = new Subscription();
|
||||
private selectExportTable$: Observable<boolean>;
|
||||
public showFilterTooltip = false;
|
||||
|
||||
@ViewChildren(Table) public tableComp: QueryList<Table>;
|
||||
private scrollContainer: HTMLDivElement;
|
||||
public scrolled: boolean;
|
||||
private filterValue: string;
|
||||
public filterValue: string;
|
||||
public settings: ExperimentCompareSettings = {} as ExperimentCompareSettings;
|
||||
private initialSettings = {
|
||||
selectedMetricsScalar: []
|
||||
@@ -220,7 +222,7 @@ export class ExperimentCompareMetricValuesComponent implements OnInit, OnDestroy
|
||||
allMetricsVariants = mergeWith(allMetricsVariants, ...lastMetrics);
|
||||
|
||||
this.metricVariantList = {};
|
||||
const dataTable = Object.entries(allMetricsVariants).map(([metricId, metricsVar]) => Object.keys(metricsVar).map(variantId => {
|
||||
this.dataTable = sortMetricsList(Object.keys(allMetricsVariants)).map((metricId) => Object.keys(allMetricsVariants[metricId]).map(variantId => {
|
||||
let metric, variant;
|
||||
return {
|
||||
metricId,
|
||||
@@ -249,8 +251,6 @@ export class ExperimentCompareMetricValuesComponent implements OnInit, OnDestroy
|
||||
}, {values: {}} as { metric, variant, firstMetricRow: boolean, min: number, max: number, values: { [expId: string]: Task['last_metrics'] } })
|
||||
};
|
||||
})).flat(1);
|
||||
dataTable.sort((a) => a.metric.startsWith(':') ? 1 : -1);
|
||||
this.dataTable = dataTable.sort((a) => a.metric === ' Summary' ? -1 : 1);
|
||||
if (!this.settings.selectedMetricsScalar || this.settings.selectedMetricsScalar?.length === 0) {
|
||||
this.settings.selectedMetricsScalar = this.getFirstMetrics(10);
|
||||
}
|
||||
@@ -263,6 +263,7 @@ export class ExperimentCompareMetricValuesComponent implements OnInit, OnDestroy
|
||||
return;
|
||||
}
|
||||
this.dataTableFiltered = this.dataTable.filter(row => this.settings.selectedMetricsScalar?.includes(`${row.metric}${row.variant}`)).filter(row => row.metric.includes(value) || row.variant.includes(value));
|
||||
this.showFilterTooltip = true;
|
||||
let previousMetric: string;
|
||||
this.dataTableFiltered.forEach(row => {
|
||||
row.firstMetricRow = row.metric !== previousMetric;
|
||||
|
||||
@@ -79,6 +79,7 @@ export class ExperimentComparePlotsComponent implements OnInit, OnDestroy {
|
||||
public splitSize$: Observable<number>;
|
||||
private originMetrics: Array<MetricVariantResult>;
|
||||
private firstTime = true;
|
||||
private previousTaskIds: Array<string>;
|
||||
|
||||
@HostListener('window:beforeunload', ['$event']) unloadHandler() {
|
||||
this.saveSettingsState();
|
||||
@@ -101,7 +102,7 @@ export class ExperimentComparePlotsComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
|
||||
this.routerParams$ = this.store.select(selectRouterParams).pipe(
|
||||
filter(params => !!params.ids),
|
||||
filter(params => params.ids!== undefined),
|
||||
distinctUntilChanged(),
|
||||
tap(() => this.refreshDisabled = true)
|
||||
);
|
||||
@@ -135,7 +136,7 @@ export class ExperimentComparePlotsComponent implements OnInit, OnDestroy {
|
||||
.subscribe(([params, metrics, selectedExperiments]) => {
|
||||
if (!this.taskIds || this.taskIds.join(',') !== params.ids) {
|
||||
const previousTaskIds = this.taskIds;
|
||||
this.taskIds = params.ids.split(',').sort();
|
||||
this.taskIds = params.ids.split(',').sort().filter(id => !!id);
|
||||
this.store.dispatch(setSelectedExperiments({selectedExperiments: this.taskIds}));
|
||||
if (metrics.length === 0 || (metrics.length > 0 && previousTaskIds !== undefined) || !isEqual(selectedExperiments, this.taskIds)) {
|
||||
this.store.dispatch(getCustomMetricsPerType({ids: this.taskIds, metricsType: EventTypeEnum.Plot, isModel: this.entityType === EntityTypeEnum.model}));
|
||||
@@ -146,7 +147,8 @@ export class ExperimentComparePlotsComponent implements OnInit, OnDestroy {
|
||||
this.subs.add(this.store.select(selectCompareSelectedMetrics('plots'))
|
||||
.pipe(
|
||||
filter(metrics => !!metrics && this.minimized),
|
||||
distinctUntilChanged((prev, curr) => isEqual(prev, curr)))
|
||||
// distinctUntilChanged((prev, curr) => isEqual(prev, curr))
|
||||
)
|
||||
.subscribe(selectedMetrics => {
|
||||
const metricsVariants = selectedMetrics.filter(m => !m.hidden).reduce((acc, curr) => {
|
||||
if (acc[curr.metricName]) {
|
||||
@@ -159,11 +161,12 @@ export class ExperimentComparePlotsComponent implements OnInit, OnDestroy {
|
||||
this.settings.selectedMetricsPlot = selectedMetrics.filter(m => !m.hidden).map(m => `${m.metricName} - ${m.variantName}`);
|
||||
const variants = Object.entries(metricsVariants).map(([metricName, variants]) => ({metric: metricName, variants}))
|
||||
this.selectedVariants = variants;
|
||||
if (this.firstTime || this.previousSelectedMetrics?.length !== selectedMetrics?.length) {
|
||||
if (this.firstTime || this.previousSelectedMetrics?.length !== selectedMetrics?.length || this.taskIds.length !== this.previousTaskIds.length) {
|
||||
this.firstTime = false;
|
||||
this.store.dispatch(getMultiPlotCharts({taskIds: this.taskIds, entity: this.entityType, metrics: variants}));
|
||||
}
|
||||
this.previousSelectedMetrics = selectedMetrics;
|
||||
this.previousTaskIds = this.taskIds;
|
||||
}));
|
||||
|
||||
this.subs.add(this.refresh.tick
|
||||
|
||||
@@ -64,10 +64,10 @@
|
||||
'hide-identical-mode': hideIdenticalFields
|
||||
}">
|
||||
<div>
|
||||
<pre *ngIf="(node.data.value !== undefined) || (node.data.existOnOrigin && node.data.existOnCompared)"
|
||||
[class.no-ellipsis]="((node.data.key | hideHash) + node.data.value).length < 45"
|
||||
<pre #row *ngIf="(node.data.value !== undefined) || (node.data.existOnOrigin && node.data.existOnCompared)"
|
||||
[class.no-ellipsis]="(nativeWidth -2 > row.scrollWidth) && (row.scrollWidth === row.clientWidth)"
|
||||
[class.with-ellipsis]="showEllipsis"
|
||||
[style.width.px]="showEllipsis ? nativeWidth - 55 - node.level * 20 : null"
|
||||
[style.width.px]="showEllipsis ? nativeWidth - 45 - node.level * 20 : null"
|
||||
><ng-container
|
||||
*ngIf="!!node.data.value?.dataDictionary && !!node.data.value?.link; else simple">{{node.data.key |
|
||||
hideHash}}<span
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import {AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit,} from '@angular/core';
|
||||
import {select, Store} from '@ngrx/store';
|
||||
import {AfterViewInit, ChangeDetectionStrategy, Component, OnInit} from '@angular/core';
|
||||
import {experimentListUpdated} from '../../actions/experiments-compare-details.actions';
|
||||
import {selectModelsDetails} from '../../reducers';
|
||||
import {filter, tap} from 'rxjs/operators';
|
||||
import {ExperimentCompareTree,} from '~/features/experiments-compare/experiments-compare-models';
|
||||
import {ExperimentCompareTree} from '~/features/experiments-compare/experiments-compare-models';
|
||||
import {convertmodelsArrays, getAllKeysEmptyObject, isDetailsConverted} from '../../jsonToDiffConvertor';
|
||||
import {ExperimentCompareBase} from '../experiment-compare-base';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {ConfigurationItem} from '~/business-logic/model/tasks/configurationItem';
|
||||
import {RefreshService} from '@common/core/services/refresh.service';
|
||||
import {LIMITED_VIEW_LIMIT} from '@common/experiments-compare/experiments-compare.constants';
|
||||
import {EntityTypeEnum} from '~/shared/constants/non-common-consts';
|
||||
import {ModelDetail} from '@common/experiments-compare/shared/experiments-compare-details.model';
|
||||
@@ -50,6 +47,7 @@ export class ModelCompareDetailsComponent extends ExperimentCompareBase implemen
|
||||
|
||||
this.resetComponentState(models);
|
||||
this.calculateTree(models);
|
||||
this.nativeWidth = Math.max(this.treeCardBody?.getBoundingClientRect().width, 410);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -72,6 +70,7 @@ export class ModelCompareDetailsComponent extends ExperimentCompareBase implemen
|
||||
general: this.buildSectionTree(experiment, 'general', mergedExperiment),
|
||||
labels: this.buildSectionTree(experiment, 'labels', mergedExperiment),
|
||||
metadata: this.buildSectionTree(experiment, 'metadata', mergedExperiment),
|
||||
lineage: this.buildSectionTree(experiment, 'lineage', mergedExperiment),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="actions-container">
|
||||
<span class="d-flex">
|
||||
<button class="btn btn-secondary btn-add-experiment" (click)="showGlobalLegend()">
|
||||
<span>{{entityType | uppercase}}S</span>
|
||||
<span>{{ entityType | uppercase }}S</span>
|
||||
</button>
|
||||
<button [smTooltip]="(allowAddExperiment$ | async) ?
|
||||
'Add/Remove ' + entityType + 's to comparison' :
|
||||
@@ -19,37 +19,37 @@
|
||||
(selectionChange)="changeView($event)"
|
||||
>
|
||||
<mat-select-trigger>
|
||||
<i data-id="viewTypeMenuOption" class="fas me-2"
|
||||
[ngClass]="{'fa-align-left': viewMode.endsWith('values'), 'fa-chart-area': viewMode === 'graph'}"></i>
|
||||
<i data-id="viewTypeMenuOption" class="al-icon sm-md me-2"
|
||||
[ngClass]="{'al-ico-description': viewMode.endsWith('values'), 'al-ico-charts-view': viewMode === 'graph', 'al-ico-scatter-view': viewMode === 'scatter'}"></i>
|
||||
<ng-container [ngSwitch]="true">
|
||||
<span *ngSwitchCase="currentPage === 'hyper-params' && viewMode === 'graph'">Parallel Coordinates</span>
|
||||
<span *ngSwitchCase="currentPage === 'hyper-params' && viewMode === 'scatter'"><i class="al-icon al-ico-no-scatter-graph sm me-2"></i>Scatter Plot</span>
|
||||
<span *ngSwitchCase="currentPage === 'hyper-params' && viewMode === 'scatter'">Scatter Plot</span>
|
||||
<span *ngSwitchCase="currentPage === 'scalars' && viewMode === 'graph'">Graph</span>
|
||||
<span *ngSwitchCase="currentPage === 'scalars' && viewMode === 'values'">Last Values</span>
|
||||
<span *ngSwitchDefault>{{(queryParamsViewMode$ | async) || viewMode | noUnderscore | titlecase}}</span>
|
||||
<span *ngSwitchDefault>{{ (queryParamsViewMode$ | async) || viewMode | noUnderscore | titlecase }}</span>
|
||||
</ng-container>
|
||||
</mat-select-trigger>
|
||||
<ng-container *ngIf="currentPage === 'scalars'; else: otherOptions">
|
||||
<mat-option value="values" class="compare-mat-option">
|
||||
<i class="fas fa-align-left me-2"></i>Last Values
|
||||
<i class="al-icon al-ico-description sm-md me-2"></i>Last Values
|
||||
</mat-option>
|
||||
<mat-option *ngIf="currentPage === 'scalars'" value="min-values" class="compare-mat-option">
|
||||
<i class="fas fa-align-left me-2"></i>Min Values
|
||||
<i class="al-icon al-ico-description sm-md me-2"></i>Min Values
|
||||
</mat-option>
|
||||
<mat-option *ngIf="currentPage === 'scalars'" value="max-values" class="compare-mat-option">
|
||||
<i class="fas fa-align-left me-2"></i>Max Values
|
||||
<i class="al-icon al-ico-description sm-md me-2"></i>Max Values
|
||||
</mat-option>
|
||||
</ng-container>
|
||||
<ng-template #otherOptions>
|
||||
<mat-option value="values" class="compare-mat-option">
|
||||
<i class="fas fa-align-left me-2"></i>Values
|
||||
<i class="al-icon al-ico-description sm-md me-2"></i>Values
|
||||
</mat-option>
|
||||
</ng-template>
|
||||
<mat-option value="graph" class="compare-mat-option">
|
||||
<i class="fas fa-chart-area me-2"></i>{{currentPage === 'hyper-params' ? 'Parallel Coordinates' : 'Graph'}}
|
||||
<i class="al-icon al-ico-charts-view sm-md me-2"></i>{{ currentPage === 'hyper-params' ? 'Parallel Coordinates' : 'Graph' }}
|
||||
</mat-option>
|
||||
<mat-option *ngIf="currentPage === 'hyper-params'" value="scatter" class="compare-mat-option">
|
||||
<i class="al-icon al-ico-no-scatter-graph sm me-2"></i>Scatter Plot
|
||||
<i class="al-icon al-ico-scatter-view sm-md me-2"></i>Scatter Plot
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
@@ -57,9 +57,9 @@
|
||||
<div id="nextDiff" class="next-diff"></div>
|
||||
|
||||
<mat-slide-toggle
|
||||
*ngIf="['hyper-params', 'details', 'models-details', 'network'].includes(currentPage) && viewMode !== 'graph' && viewMode !== 'scatter'"
|
||||
*ngIf="['hyper-params', 'details', 'models-details', 'network'].includes(currentPage)"
|
||||
(change)="hideIdenticalFieldsToggled($event)"
|
||||
[checked]="selectHideIdenticalFields$ | async">Hide Identical Fields
|
||||
[checked]="selectHideIdenticalFields$ | async">Hide Identical {{ (viewMode === 'graph' || viewMode === 'scatter') ? 'Parameters' : 'Fields' }}
|
||||
</mat-slide-toggle>
|
||||
|
||||
<mat-slide-toggle color="primary"
|
||||
|
||||
@@ -102,8 +102,13 @@
|
||||
|
||||
}
|
||||
|
||||
|
||||
::ng-deep .light-theme .mat-mdc-select-value-text {
|
||||
i {vertical-align: middle;}
|
||||
}
|
||||
::ng-deep .light-theme .mat-mdc-option .mdc-list-item__primary-text {
|
||||
white-space: nowrap;
|
||||
i {vertical-align: middle;}
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-menu-content {
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
@if (multiSelect) {
|
||||
<div class="d-flex title-container align-items-center" (click)="metricSelect.openMenu()">
|
||||
<span class="param-selector-title pointer">{{ title }}</span>
|
||||
<sm-menu #metricSelect iconClass="al-icon al-ico-dropdown-arrow pointer" buttonTooltip="Select metric" smMenuClass="light-theme custom-columns" (menuClosed)="selectMetric.resetSearch()">
|
||||
<sm-select-metric-for-custom-col #selectMetric class="normal-size"
|
||||
[metricVariants]="metricVariantsSet"
|
||||
[tableCols]="selectedMetricVariants ? selectedMetricVariants : []"
|
||||
[multiSelect]="multiSelect"
|
||||
[skipValueType]="skipValueType"
|
||||
[enableClearSelection]="true"
|
||||
(selectedMetricToShow)="selectMetricVariant.emit($event)"
|
||||
(clearSelection)="clearSelection.emit()"></sm-select-metric-for-custom-col>
|
||||
</sm-menu>
|
||||
</div>
|
||||
} @else {
|
||||
<button class="btn btn-outline d-flex justify-content-between align-items-center" (click)="metricSelect.openMenu()">
|
||||
<span class="selected-parameter ellipsis" smShowTooltipIfEllipsis
|
||||
[smTooltip]="(selectedMetricVariants[0] |metricVariantToName) + ' ' + (MetricValueTypeStrings[selectedMetricVariants[0]?.valueType] ?? '')">{{ selectedMetricVariants[0] ? (selectedMetricVariants[0] |metricVariantToName) : 'Select Metric' }} {{ (MetricValueTypeStrings[selectedMetricVariants[0]?.valueType] ?? '') }}</span>
|
||||
<sm-menu #metricSelect iconClass="al-icon al-ico-dropdown-arrow pointer al-color blue-400" buttonTooltip="Select metric" smMenuClass="light-theme custom-columns" (menuClosed)="selectMetric.resetSearch()">
|
||||
<sm-select-metric-for-custom-col #selectMetric class="normal-size"
|
||||
[metricVariants]="metricVariantsSet"
|
||||
[tableCols]="selectedMetricVariants ? selectedMetricVariants : []"
|
||||
[multiSelect]="multiSelect"
|
||||
[skipValueType]="skipValueType"
|
||||
[enableClearSelection]="true"
|
||||
(selectedMetricToShow)="selectMetricVariant.emit($event)"
|
||||
(clearSelection)="clearSelection.emit()"></sm-select-metric-for-custom-col>
|
||||
</sm-menu>
|
||||
</button>
|
||||
}
|
||||
@if (multiSelect) {
|
||||
@if (selectedMetricVariants.length > 0) {
|
||||
@for (selectedMetricVariant of selectedMetricVariants; track trackByIndex($index)) {
|
||||
<div class="multi-selected-parameter" smShowTooltipIfEllipsis [smTooltip]="(selectedMetricVariant |metricVariantToName) + ' ' + (MetricValueTypeStrings[selectedMetricVariant?.valueType] ?? '')">
|
||||
<span class="multi-selected-parameter-name ellipsis">{{ selectedMetricVariant |metricVariantToName }} {{ (MetricValueTypeStrings[selectedMetricVariant?.valueType] ?? '') }}</span>
|
||||
<i class="al-icon sm al-ico-dialog-x pointer" (click)="removeMetric.emit(selectedMetricVariant)"></i></div>
|
||||
}
|
||||
} @else {
|
||||
<div class="no-data">No metrics selected</div>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
@import "variables";
|
||||
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.btn.btn-outline {
|
||||
color: $blue-500;
|
||||
border: 1px solid $cloudy-blue;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
&:hover {
|
||||
border-color: $blue-250;
|
||||
}
|
||||
}
|
||||
|
||||
sm-menu {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.multi-selected-parameter{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
background-color: transparent;
|
||||
&:hover {
|
||||
background-color: $blue-50;
|
||||
}
|
||||
|
||||
.multi-selected-parameter-name{
|
||||
max-width: 280px;
|
||||
}
|
||||
.al-ico-dialog-x {
|
||||
visibility: hidden
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.al-ico-dialog-x {
|
||||
visibility: visible;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.selected-parameter {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
max-width: 260px;
|
||||
}
|
||||
}
|
||||
|
||||
.title-container {
|
||||
margin-bottom: 12px;
|
||||
border-bottom: 1px solid $cloudy-blue;
|
||||
padding: 6px 12px 6px 0;
|
||||
&:hover {
|
||||
border-color: $blue-250;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.param-selector-title {
|
||||
color: $blue-500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
sm-menu {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
.no-data{
|
||||
text-align: center;
|
||||
color: $blue-300;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
||||
import {MenuComponent} from '@common/shared/ui-components/panel/menu/menu.component';
|
||||
import {ClickStopPropagationDirective} from '@common/shared/ui-components/directives/click-stop-propagation.directive';
|
||||
import {CustomColumnsListComponent} from '@common/shared/components/custom-columns-list/custom-columns-list.component';
|
||||
import {ExperimentCompareSharedModule} from '@common/experiments-compare/shared/experiment-compare-shared.module';
|
||||
|
||||
import {
|
||||
SelectMetadataKeysCustomColsComponent
|
||||
} from '@common/shared/components/select-metadata-keys-custom-cols/select-metadata-keys-custom-cols.component';
|
||||
import {MetricVariantResult} from '~/business-logic/model/projects/metricVariantResult';
|
||||
import {uniqBy} from 'lodash-es';
|
||||
import {
|
||||
SelectionEvent
|
||||
} from '@common/experiments/dumb/select-metric-for-custom-col/select-metric-for-custom-col.component';
|
||||
import {SelectedMetricVariant} from '@common/experiments-compare/experiments-compare.constants';
|
||||
import {MetricVariantToNamePipe} from '@common/shared/pipes/metric-variant-to-name.pipe';
|
||||
import {trackByIndex} from '@common/shared/utils/forms-track-by';
|
||||
import {TooltipDirective} from '@common/shared/ui-components/indicators/tooltip/tooltip.directive';
|
||||
import {
|
||||
ShowTooltipIfEllipsisDirective
|
||||
} from '@common/shared/ui-components/indicators/tooltip/show-tooltip-if-ellipsis.directive';
|
||||
import {MetricValueTypeStrings} from '@common/shared/utils/tableParamEncode';
|
||||
|
||||
@Component({
|
||||
selector: 'sm-metric-variant-selector',
|
||||
standalone: true,
|
||||
imports: [
|
||||
MenuComponent,
|
||||
ClickStopPropagationDirective,
|
||||
CustomColumnsListComponent,
|
||||
ExperimentCompareSharedModule,
|
||||
SelectMetadataKeysCustomColsComponent,
|
||||
MetricVariantToNamePipe,
|
||||
TooltipDirective,
|
||||
ShowTooltipIfEllipsisDirective
|
||||
],
|
||||
templateUrl: './metric-variant-selector.component.html',
|
||||
styleUrl: './metric-variant-selector.component.scss'
|
||||
})
|
||||
export class MetricVariantSelectorComponent {
|
||||
public metricVariantsSet: Array<MetricVariantResult>;
|
||||
@Input() selectedMetricVariants: SelectedMetricVariant[];
|
||||
@Input() multiSelect: boolean = false;
|
||||
@Input() skipValueType: boolean = false;
|
||||
@Input() title: string;
|
||||
@Input() set metricVariants(metricVariants: Array<MetricVariantResult>) {
|
||||
this.metricVariantsSet = uniqBy(metricVariants?.map(metricVariant => {
|
||||
return {...metricVariant, key: metricVariant.metric_hash + metricVariant.variant_hash};
|
||||
}), 'key')?.map(({key, ...metricVariant}) => metricVariant);
|
||||
}
|
||||
|
||||
@Output() selectMetricVariant = new EventEmitter<SelectionEvent>();
|
||||
@Output() removeMetric = new EventEmitter<SelectedMetricVariant>();
|
||||
@Output() clearSelection = new EventEmitter();
|
||||
protected readonly trackByIndex = trackByIndex;
|
||||
|
||||
protected readonly MetricValueTypeStrings = MetricValueTypeStrings;
|
||||
searchText: string;
|
||||
}
|
||||
@@ -1,42 +1,45 @@
|
||||
<div class="actions" *ngIf="!darkTheme">
|
||||
<button
|
||||
(click)="maximize()"
|
||||
class="btn btn-icon"
|
||||
smTooltip="Maximize"
|
||||
><i class="al-icon al-ico-fit sm-md"></i></button>
|
||||
<button
|
||||
(click)="creatingEmbedCode($any($event.target).getBoundingClientRect())"
|
||||
class="btn btn-icon"
|
||||
smTooltip="Copy embed code"
|
||||
><i class="al-icon al-ico-code sm-md"></i></button>
|
||||
<button
|
||||
(click)="downloadImage()"
|
||||
class="btn btn-icon"
|
||||
smTooltip="Download as PNG"
|
||||
><i class="al-icon al-ico-download sm-md"></i></button>
|
||||
</div>
|
||||
<div #container class="graph-container" [class.dark-theme]="darkTheme">
|
||||
<div class="graph-title">Hyperparameters Impact on {{metric?.name}}</div>
|
||||
<div #parallelGraph>
|
||||
</div>
|
||||
<div #legend class="d-flex legend-container">
|
||||
<div *ngFor="let experiment of experiments; trackBy: trackById" class="experiment-name">
|
||||
<span class="dot-container">
|
||||
<span #dot class="dot pallete-cursor"
|
||||
[style.background-color]="experimentsColors[experiment.id]"
|
||||
[colorButtonRef]="dot"
|
||||
[smChooseColor]="experimentsColors[getExperimentNameForColor(experiment)]"
|
||||
[stringToColor]="getExperimentNameForColor(experiment)">
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="task-name pointer"
|
||||
(click)="toggleHideExperiment(experiment.id)"
|
||||
(mouseover)="highlightExperiment(experiment)"
|
||||
(mouseout)="removeHighlightExperiment()"
|
||||
[class.hide]="experiment.hidden">
|
||||
{{experiment.name + (experiment.duplicateName ? ('.' + (experiment.id|slice:0:5)) : '')}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (!darkTheme && !reportMode) {
|
||||
<div class="actions">
|
||||
<button
|
||||
(click)="maximize()"
|
||||
class="btn btn-icon"
|
||||
smTooltip="Maximize"
|
||||
><i class="al-icon al-ico-fit sm-md"></i></button>
|
||||
<button
|
||||
(click)="creatingEmbedCode($any($event.target).getBoundingClientRect())"
|
||||
class="btn btn-icon"
|
||||
smTooltip="Copy embed code"
|
||||
><i class="al-icon al-ico-code sm-md"></i></button>
|
||||
<button
|
||||
(click)="downloadImage()"
|
||||
class="btn btn-icon"
|
||||
smTooltip="Download as PNG"
|
||||
><i class="al-icon al-ico-download sm-md"></i></button>
|
||||
</div>
|
||||
}
|
||||
<div #container class="graph-container" [class.dark-theme]="darkTheme">
|
||||
<div #parallelGraph>
|
||||
</div>
|
||||
<div #legend class="d-flex legend-container">
|
||||
@for (experiment of experiments; track experiment.id) {
|
||||
<div class="experiment-name">
|
||||
<span class="dot-container">
|
||||
<span #dot class="dot pallete-cursor"
|
||||
[style.background-color]="experimentsColors[experiment.id]"
|
||||
[colorButtonRef]="dot"
|
||||
[smChooseColor]="experimentsColors[getExperimentNameForColor(experiment)]"
|
||||
[stringToColor]="getExperimentNameForColor(experiment)">
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="task-name pointer"
|
||||
(click)="toggleHideExperiment(experiment.id)"
|
||||
(mouseover)="highlightExperiment(experiment)"
|
||||
(mouseout)="removeHighlightExperiment()"
|
||||
[class.hide]="experiment.hidden">
|
||||
{{experiment.name + (experiment.duplicateName ? ('.' + (experiment.id|slice:0:5)) : '')}}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ChangeDetectorRef, Component, ElementRef, EventEmitter, HostListener, Input, OnInit, Output, ViewChild} from '@angular/core';
|
||||
import {ChangeDetectorRef, Component, ElementRef, EventEmitter, HostListener, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild} from '@angular/core';
|
||||
import {
|
||||
DARK_THEME_GRAPH_LINES_COLOR,
|
||||
DARK_THEME_GRAPH_TICK_COLOR, ExtData,
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
ExtLayout,
|
||||
PlotlyGraphBaseComponent
|
||||
} from '@common/shared/single-graph/plotly-graph-base';
|
||||
import {NgForOf, NgIf, SlicePipe} from '@angular/common';
|
||||
import { SlicePipe } from '@angular/common';
|
||||
import {debounceTime, filter, take} from 'rxjs/operators';
|
||||
import {from} from 'rxjs';
|
||||
import {cloneDeep, get, isEqual, max, min, uniq} from 'lodash-es';
|
||||
@@ -16,12 +16,13 @@ import {select} from 'd3-selection';
|
||||
import {ColorHashService} from '@common/shared/services/color-hash/color-hash.service';
|
||||
import {Task} from '~/business-logic/model/tasks/task';
|
||||
import {sortCol} from '@common/shared/utils/sortCol';
|
||||
import {MetricValueType, SelectedMetric} from '@common/experiments-compare/experiments-compare.constants';
|
||||
import {MetricValueType, SelectedMetricVariant} from '@common/experiments-compare/experiments-compare.constants';
|
||||
import {TooltipDirective} from '@common/shared/ui-components/indicators/tooltip/tooltip.directive';
|
||||
import {trackById} from '@common/shared/utils/forms-track-by';
|
||||
import {GraphViewerComponent, GraphViewerData} from '@common/shared/single-graph/graph-viewer/graph-viewer.component';
|
||||
import {MatDialog} from '@angular/material/dialog';
|
||||
import {ChooseColorModule} from '@common/shared/ui-components/directives/choose-color/choose-color.module';
|
||||
import {MetricVariantToPathPipe} from '@common/shared/pipes/metric-variant-to-path.pipe';
|
||||
import {MetricVariantToNamePipe} from '@common/shared/pipes/metric-variant-to-name.pipe';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
declare let Plotly;
|
||||
@@ -49,6 +50,7 @@ interface ParaPlotData {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'sm-parallel-coordinates-graph',
|
||||
templateUrl: './parallel-coordinates-graph.component.html',
|
||||
@@ -56,18 +58,16 @@ interface ParaPlotData {
|
||||
imports: [
|
||||
SlicePipe,
|
||||
TooltipDirective,
|
||||
NgForOf,
|
||||
NgIf,
|
||||
ChooseColorModule
|
||||
],
|
||||
ChooseColorModule,
|
||||
MetricVariantToNamePipe
|
||||
],
|
||||
standalone: true
|
||||
})
|
||||
export class ParallelCoordinatesGraphComponent extends PlotlyGraphBaseComponent implements OnInit {
|
||||
|
||||
public trackById = trackById;
|
||||
export class ParallelCoordinatesGraphComponent extends PlotlyGraphBaseComponent implements OnInit, OnChanges {
|
||||
private metricVariantToPathPipe = new MetricVariantToPathPipe();
|
||||
private metricVariantToNamePipe = new MetricVariantToNamePipe();
|
||||
private data: ParaPlotData[];
|
||||
private _experiments: ExtraTask[];
|
||||
private _metric: SelectedMetric;
|
||||
public experimentsColors = {};
|
||||
public filteredExperiments = [];
|
||||
private timer: number;
|
||||
@@ -99,20 +99,6 @@ export class ParallelCoordinatesGraphComponent extends PlotlyGraphBaseComponent
|
||||
return this._metricValueType;
|
||||
}
|
||||
|
||||
@Input() set metric(metric) {
|
||||
if (metric != this.metric) {
|
||||
this._metric = metric;
|
||||
if (this.experiments) {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = window.setTimeout(() => this.prepareGraph(), 200);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get metric(): SelectedMetric {
|
||||
return this._metric;
|
||||
}
|
||||
|
||||
@Input() set parameters(parameters) {
|
||||
if (!isEqual(parameters, this.parameters)) {
|
||||
this._parameters = parameters;
|
||||
@@ -127,6 +113,7 @@ export class ParallelCoordinatesGraphComponent extends PlotlyGraphBaseComponent
|
||||
return this._parameters;
|
||||
}
|
||||
|
||||
@Input() metrics: SelectedMetricVariant[];
|
||||
|
||||
@Input() set experiments(experiments) {
|
||||
let experimentsCopy = cloneDeep(experiments) ?? [];
|
||||
@@ -163,6 +150,13 @@ export class ParallelCoordinatesGraphComponent extends PlotlyGraphBaseComponent
|
||||
super();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes.metrics && this.experiments) {
|
||||
this.prepareGraph();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.initColorSubscription();
|
||||
}
|
||||
@@ -224,7 +218,7 @@ export class ParallelCoordinatesGraphComponent extends PlotlyGraphBaseComponent
|
||||
parameter = `${parameter}.value`;
|
||||
const allValuesIncludingNull = this.experiments.map(experiment => get(experiment.hyperparams, parameter));
|
||||
const allValues = allValuesIncludingNull.filter(value => (value !== undefined)).filter(value => (value !== ''));
|
||||
const textVal = {} as {[key: string]: number};
|
||||
const textVal = {} as { [key: string]: number };
|
||||
let ticktext = this.naturalCompare(uniq(allValues).filter(text => text !== ''));
|
||||
(allValuesIncludingNull.length > allValues.length) && (ticktext = ['N/A'].concat(ticktext));
|
||||
const tickvals = ticktext.map((text, index) => {
|
||||
@@ -244,7 +238,7 @@ export class ParallelCoordinatesGraphComponent extends PlotlyGraphBaseComponent
|
||||
tickvals,
|
||||
values: filteredExperiments.map((experiment) => (textVal[['', undefined].includes(get(experiment.hyperparams, parameter)) ? 'N/A' : get(experiment.hyperparams, parameter)])),
|
||||
range: [0, max(tickvals)],
|
||||
constraintrange,
|
||||
constraintrange
|
||||
|
||||
};
|
||||
})
|
||||
@@ -253,7 +247,7 @@ export class ParallelCoordinatesGraphComponent extends PlotlyGraphBaseComponent
|
||||
trace.line = {
|
||||
color: this.getColorsArray(filteredExperiments),
|
||||
colorscale: filteredExperiments.map((experiment, index) =>
|
||||
[index / (filteredExperiments.length - 1), this.getStringColor(experiment)] as [number, string]),
|
||||
[index / (filteredExperiments.length - 1), this.getStringColor(experiment)] as [number, string])
|
||||
};
|
||||
} else {
|
||||
trace.line = {
|
||||
@@ -265,40 +259,43 @@ export class ParallelCoordinatesGraphComponent extends PlotlyGraphBaseComponent
|
||||
// this.data = [trace];
|
||||
// this.drawChart();
|
||||
|
||||
if (this.metric) {
|
||||
const allValuesIncludingNull = this.experiments.map(experiment => get(experiment.last_metrics, `${this.metric.path}.${this.metricValueType}`));
|
||||
const allValues = allValuesIncludingNull.filter(value => value !== undefined);
|
||||
const naVal = this.getNAValue(allValues);
|
||||
const ticktext = uniq(allValuesIncludingNull.map(value => value !== undefined ? value : 'N/A'));
|
||||
const tickvals = ticktext.map(text => text === 'N/A' ? naVal : text);
|
||||
let constraintrange;
|
||||
if (this.parallelGraph.nativeElement?.data?.[0]?.dimensions) {
|
||||
const currDimention = this.parallelGraph.nativeElement.data[0].dimensions.find(d => d.label === this.metric.name);
|
||||
if (currDimention?.constraintrange) {
|
||||
constraintrange = currDimention.constraintrange;
|
||||
if (this.metrics) {
|
||||
this.metrics.map(metric => {
|
||||
const allValuesIncludingNull = this.experiments.map(experiment => get(experiment.last_metrics, this.metricVariantToPathPipe.transform(metric, true)));
|
||||
const allValues = allValuesIncludingNull.filter(value => value !== undefined);
|
||||
const naVal = this.getNAValue(allValues);
|
||||
const ticktext = uniq(allValuesIncludingNull.map(value => value !== undefined ? value : 'N/A'));
|
||||
const tickvals = ticktext.map(text => text === 'N/A' ? naVal : text);
|
||||
let constraintrange;
|
||||
if (this.parallelGraph.nativeElement?.data?.[0]?.dimensions) {
|
||||
const currDimention = this.parallelGraph.nativeElement.data[0].dimensions.find(d => d.label === this.metricVariantToNamePipe.transform(metric, true));
|
||||
if (currDimention?.constraintrange) {
|
||||
constraintrange = currDimention.constraintrange;
|
||||
}
|
||||
}
|
||||
}
|
||||
trace.dimensions.push({
|
||||
label: this.metric.name,
|
||||
|
||||
ticktext,
|
||||
tickvals,
|
||||
values: filteredExperiments.map((experiment) =>
|
||||
parseFloat(get(experiment.last_metrics, `${this.metric.path}.${this.metricValueType}`, naVal))
|
||||
),
|
||||
range: [min(tickvals), max(tickvals)],
|
||||
constraintrange,
|
||||
color: 'red',
|
||||
gridcolor: 'red',
|
||||
linecolor: 'green',
|
||||
tickcolor: 'red',
|
||||
dividercolor: 'green',
|
||||
spikecolor: 'yellow',
|
||||
dividerwidth: 3,
|
||||
tickwidth: 2,
|
||||
linewidth: 4,
|
||||
trace.dimensions.push({
|
||||
label: this.metricVariantToNamePipe.transform(metric, true),
|
||||
ticktext,
|
||||
tickvals,
|
||||
values: filteredExperiments.map((experiment) =>
|
||||
parseFloat(get(experiment.last_metrics, this.metricVariantToPathPipe.transform(metric, true), naVal))
|
||||
),
|
||||
range: [min(tickvals), max(tickvals)],
|
||||
constraintrange,
|
||||
color: 'red',
|
||||
gridcolor: 'red',
|
||||
linecolor: 'green',
|
||||
tickcolor: 'red',
|
||||
dividercolor: 'green',
|
||||
spikecolor: 'yellow',
|
||||
dividerwidth: 3,
|
||||
tickwidth: 2,
|
||||
linewidth: 4
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
this.data = [trace];
|
||||
if (this.dimensionsOrder) {
|
||||
this.data[0].dimensions.sort((a, b) => sortCol(a.label, b.label, this.dimensionsOrder));
|
||||
@@ -321,7 +318,7 @@ export class ParallelCoordinatesGraphComponent extends PlotlyGraphBaseComponent
|
||||
margin: {l: 120, r: 120},
|
||||
...(setDimentiosn && {
|
||||
height: 500,
|
||||
width: this.parallelGraph.nativeElement.offsetWidth,
|
||||
width: this.parallelGraph.nativeElement.offsetWidth
|
||||
}),
|
||||
...(this.darkTheme ? {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
@@ -330,7 +327,7 @@ export class ParallelCoordinatesGraphComponent extends PlotlyGraphBaseComponent
|
||||
margin: {l: 120, r: 120, b: 20},
|
||||
font: {
|
||||
color: DARK_THEME_GRAPH_TICK_COLOR
|
||||
},
|
||||
}
|
||||
} : {})
|
||||
} as ExtLayout;
|
||||
}
|
||||
@@ -349,12 +346,12 @@ export class ParallelCoordinatesGraphComponent extends PlotlyGraphBaseComponent
|
||||
const graph = select(this.parallelGraph.nativeElement);
|
||||
this.graphWidth = graph.node().getBoundingClientRect().width;
|
||||
graph.selectAll('.y-axis')
|
||||
.filter((d: {key: string}) => d.key === this.metric.name)
|
||||
.filter((d: { key: string }) => this.metrics?.map(mv => this.metricVariantToNamePipe.transform(mv, true)).includes(d.key))
|
||||
.classed('metric-column', true);
|
||||
graph.selectAll('.axis-title')
|
||||
.text((d: {key: string}) => this.wrap(d.key))
|
||||
.text((d: { key: string }) => this.wrap(d.key))
|
||||
.append('title')
|
||||
.text(d => (d as {key: string}).key);
|
||||
.text(d => (d as { key: string }).key);
|
||||
graph.selectAll('.axis .tick text').text((d: string) => this.wrap(d)).append('title').text((d: string) => d);
|
||||
graph.selectAll('.axis .tick text').style('pointer-events', 'auto');
|
||||
this.darkTheme && graph.selectAll('.dark-theme .axis .domain').style('stroke', DARK_THEME_GRAPH_LINES_COLOR).style('stroke-opacity', 1);
|
||||
@@ -419,13 +416,18 @@ export class ParallelCoordinatesGraphComponent extends PlotlyGraphBaseComponent
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.target = '_blank';
|
||||
a.download = `Hyperparameters ${this._metric.name}.png`;
|
||||
a.download = `Hyperparameters ${this.metrics[0].metric} ${this.metrics[0].variant}.png`;
|
||||
a.click();
|
||||
});
|
||||
}
|
||||
|
||||
creatingEmbedCode(domRect: DOMRect) {
|
||||
this.createEmbedCode.emit({tasks: this.experiments.map(exp => exp.id), valueType: this.metricValueType, metrics: [this.metric.path], variants: this.parameters, domRect});
|
||||
this.createEmbedCode.emit({
|
||||
tasks: this.experiments.map(exp => exp.id),
|
||||
valueType: this.metricValueType,
|
||||
metrics: this.metrics.map(mv => this.metricVariantToPathPipe.transform(mv, true)),
|
||||
variants: this.parameters, domRect
|
||||
});
|
||||
}
|
||||
|
||||
maximize() {
|
||||
@@ -435,15 +437,18 @@ export class ParallelCoordinatesGraphComponent extends PlotlyGraphBaseComponent
|
||||
// signed url are updated after originChart was cloned - need to update images urls!
|
||||
chart: cloneDeep({
|
||||
data: this.data as unknown as ExtData[],
|
||||
layout: {...this.getLayout(false), title: this.metric?.name || ''},
|
||||
layout: {
|
||||
...this.getLayout(false),
|
||||
title: ''
|
||||
},
|
||||
config: {
|
||||
displaylogo: false,
|
||||
displayModeBar: false,
|
||||
displayModeBar: false
|
||||
}
|
||||
} as ExtFrame),
|
||||
id: `compare params ${this.experiments?.map(e => e.id).join(',')}`,
|
||||
darkTheme: false,
|
||||
isCompare: true,
|
||||
isCompare: true
|
||||
} as GraphViewerData,
|
||||
panelClass: ['image-viewer-dialog', 'light-theme'],
|
||||
height: '100%',
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
@if (!single) {
|
||||
<div class="d-flex align-items-center title-container" (click)="paramSelect.openMenu()">
|
||||
<span class="param-selector-title pointer">{{ title }}</span>
|
||||
<sm-menu #paramSelect iconClass="al-icon al-ico-dropdown-arrow pointer" (menuClosed)="checklist.searchQ('')"
|
||||
buttonTooltip="Select parameter" smMenuClass="light-theme custom-columns">
|
||||
<sm-grouped-checked-filter-list
|
||||
#checklist
|
||||
smClickStopPropagation class="filtered-list"
|
||||
[itemsList]="itemsList"
|
||||
[selectedItemsList]="selectedHyperParams"
|
||||
[selectFilteredItems]="selectFilteredItems"
|
||||
[selectedItemsListMapper]="selectedItemsListMapper"
|
||||
selectedItemsListPrefix=""
|
||||
[limitSelection]="50"
|
||||
[single]="false"
|
||||
(selectedItems)="selectedItems.emit($event)"
|
||||
(clearSelection)="clearSelection.emit()"></sm-grouped-checked-filter-list>
|
||||
</sm-menu>
|
||||
</div>
|
||||
@if (selectedHyperParams?.length > 0) {
|
||||
<div>
|
||||
@for (hyperParam of selectedHyperParams; track trackByIndex($index)) {
|
||||
<div class="selected-parameter">
|
||||
<span class="ellipsis parameter-name" smShowTooltipIfEllipsis [smTooltip]="hyperParam">{{ hyperParam }}</span>
|
||||
<i class="al-icon sm al-ico-dialog-x pointer" (click)="removeHyperParam(hyperParam)"></i></div>
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<div class="no-data">No parameters selected</div>
|
||||
}
|
||||
} @else {
|
||||
<button class="btn btn-outline d-flex justify-content-between align-items-center" (click)="paramSelect.openMenu()">
|
||||
<span class="ellipsis select" smShowTooltipIfEllipsis [smTooltip]="selectedHyperParams?.[0] ?? ''">{{ selectedHyperParams?.[0] ?? 'Select Parameter' }}</span>
|
||||
<sm-menu #paramSelect
|
||||
(menuClosed)="checklist.searchQ('')"
|
||||
iconClass="al-icon al-ico-dropdown-arrow pointer al-color blue-400"
|
||||
buttonTooltip="Select parameter"
|
||||
smMenuClass="light-theme custom-columns">
|
||||
<sm-grouped-checked-filter-list #checklist
|
||||
smClickStopPropagation
|
||||
class="filtered-list"
|
||||
[itemsList]="itemsList"
|
||||
[selectedItemsList]="selectedHyperParams"
|
||||
[selectFilteredItems]="selectFilteredItems"
|
||||
[selectedItemsListMapper]="selectedItemsListMapper"
|
||||
selectedItemsListPrefix=""
|
||||
[single]="true"
|
||||
(selectedItems)="selectedItems.emit($event)"
|
||||
(clearSelection)="clearSelection.emit()"></sm-grouped-checked-filter-list>
|
||||
</sm-menu>
|
||||
</button>
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
@import "variables";
|
||||
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.no-data {
|
||||
text-align: center;
|
||||
color: $blue-300;
|
||||
}
|
||||
|
||||
.btn.btn-outline {
|
||||
color: $blue-500;
|
||||
border: 1px solid $cloudy-blue;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
|
||||
&:hover {
|
||||
border-color: $blue-250;
|
||||
}
|
||||
}
|
||||
|
||||
sm-menu {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.filtered-list {
|
||||
padding: 6px;
|
||||
height: 480px;
|
||||
|
||||
::ng-deep .actions {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.selected-parameter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
background-color: transparent;
|
||||
|
||||
&:hover {
|
||||
background-color: $blue-50;
|
||||
}
|
||||
|
||||
span {
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
.al-ico-dialog-x {
|
||||
visibility: hidden
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.al-ico-dialog-x {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title-container {
|
||||
margin-bottom: 12px;
|
||||
border-bottom: 1px solid $cloudy-blue;
|
||||
padding: 6px 12px 6px 0;
|
||||
|
||||
&:hover {
|
||||
border-color: $blue-250;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.param-selector-title {
|
||||
color: $blue-500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
sm-menu {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
||||
import {MenuComponent} from '@common/shared/ui-components/panel/menu/menu.component';
|
||||
import {ExperimentSharedModule} from '~/features/experiments/shared/experiment-shared.module';
|
||||
import {ClickStopPropagationDirective} from '@common/shared/ui-components/directives/click-stop-propagation.directive';
|
||||
import {
|
||||
GroupedCheckedFilterListComponent
|
||||
} from '@common/shared/ui-components/data/grouped-checked-filter-list/grouped-checked-filter-list.component';
|
||||
import { AsyncPipe } from '@angular/common';
|
||||
import { trackByIndex } from '@common/shared/utils/forms-track-by';
|
||||
import {TooltipDirective} from '@common/shared/ui-components/indicators/tooltip/tooltip.directive';
|
||||
import {
|
||||
ShowTooltipIfEllipsisDirective
|
||||
} from '@common/shared/ui-components/indicators/tooltip/show-tooltip-if-ellipsis.directive';
|
||||
|
||||
@Component({
|
||||
selector: 'sm-param-selector',
|
||||
standalone: true,
|
||||
imports: [
|
||||
MenuComponent,
|
||||
ExperimentSharedModule,
|
||||
ClickStopPropagationDirective,
|
||||
GroupedCheckedFilterListComponent,
|
||||
AsyncPipe,
|
||||
TooltipDirective,
|
||||
ShowTooltipIfEllipsisDirective
|
||||
],
|
||||
templateUrl: './param-selector.component.html',
|
||||
styleUrl: './param-selector.component.scss'
|
||||
})
|
||||
export class ParamSelectorComponent {
|
||||
public trackByIndex = trackByIndex;
|
||||
|
||||
@Input() selectedHyperParams: string[];
|
||||
@Input() title: string;
|
||||
@Input() itemsList: { [section: string]: any };
|
||||
@Input() single: boolean;
|
||||
@Input() selectFilteredItems: boolean;
|
||||
|
||||
@Input() selectedItemsListMapper: (data) => string;
|
||||
@Output() selectedItems = new EventEmitter<{ param: string }>();
|
||||
@Output() clearSelection = new EventEmitter();
|
||||
|
||||
removeHyperParam(hyperParam: string) {
|
||||
this.selectedItems.emit({param:hyperParam})
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import {selectCompareHistogramCacheAxisType} from '../reducers';
|
||||
import {ScalarKeyEnum} from '~/business-logic/model/events/scalarKeyEnum';
|
||||
import {selectActiveWorkspaceReady} from '~/core/reducers/view.reducer';
|
||||
import {EntityTypeEnum} from '~/shared/constants/non-common-consts';
|
||||
import {EMPTY, iif} from 'rxjs';
|
||||
import {EMPTY, iif, of} from 'rxjs';
|
||||
import {merge} from 'lodash-es';
|
||||
import {ApiModelsService} from '~/business-logic/api-services/models.service';
|
||||
import {setAxisCache, setGlobalLegendData} from '../actions/experiments-compare-charts.actions';
|
||||
@@ -117,33 +117,38 @@ export class ExperimentsCompareChartsEffects {
|
||||
getMultiPlotCharts = createEffect(() => this.actions$.pipe(
|
||||
ofType(chartActions.getMultiPlotCharts),
|
||||
debounceTime(200),
|
||||
switchMap(action => this.eventsApi.eventsGetMultiTaskPlots({
|
||||
tasks: action.taskIds,
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
model_events: action.entity === EntityTypeEnum.model,
|
||||
metrics: action.metrics,
|
||||
last_iters_per_task_metric: true,
|
||||
iters: 1
|
||||
}).pipe(
|
||||
map((res: EventsGetMultiTaskPlotsResponse) => [res.returned, res] as [number, EventsGetMultiTaskPlotsResponse]),
|
||||
expand(([plotsLength, data]) => (data.total < 10000 && data.returned > 0)
|
||||
switchMap(action => { if(action.taskIds.length > 0 ){
|
||||
return this.eventsApi.eventsGetMultiTaskPlots({
|
||||
tasks: action.taskIds,
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
? this.eventsApi.eventsGetMultiTaskPlots({
|
||||
tasks: action.taskIds,
|
||||
model_events: action.entity === EntityTypeEnum.model,
|
||||
metrics: action.metrics,
|
||||
last_iters_per_task_metric: true,
|
||||
iters: 1
|
||||
}).pipe(
|
||||
map((res: EventsGetMultiTaskPlotsResponse) => [res.returned, res] as [number, EventsGetMultiTaskPlotsResponse]),
|
||||
expand(([plotsLength, data]) => (data.total < 10000 && data.returned > 0)
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
model_events: action.entity === EntityTypeEnum.model,
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
scroll_id: data.scroll_id,
|
||||
metrics: action.metrics,
|
||||
last_iters_per_task_metric: true,
|
||||
iters: 1
|
||||
}).pipe(
|
||||
map((res: EventsGetMultiTaskPlotsResponse) => [plotsLength + res.returned, res] as [number, EventsGetTaskPlotsResponse])
|
||||
)
|
||||
: EMPTY
|
||||
),
|
||||
reduce((acc, [, data]) => merge(acc, data.plots), {})
|
||||
)),
|
||||
? this.eventsApi.eventsGetMultiTaskPlots({
|
||||
tasks: action.taskIds,
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
model_events: action.entity === EntityTypeEnum.model,
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
scroll_id: data.scroll_id,
|
||||
metrics: action.metrics,
|
||||
last_iters_per_task_metric: true,
|
||||
iters: 1
|
||||
}).pipe(
|
||||
map((res: EventsGetMultiTaskPlotsResponse) => [plotsLength + res.returned, res] as [number, EventsGetTaskPlotsResponse])
|
||||
)
|
||||
: EMPTY
|
||||
),
|
||||
reduce((acc, [, data]) => merge(acc, data.plots), {})
|
||||
)
|
||||
} else {
|
||||
return of({plots:[]})
|
||||
}
|
||||
}),
|
||||
mergeMap(plots => [
|
||||
chartActions.setExperimentPlots({plots}),
|
||||
deactivateLoader(chartActions.getMultiPlotCharts.type)]),
|
||||
|
||||
@@ -7,10 +7,9 @@ import {ApiTasksService} from '~/business-logic/api-services/tasks.service';
|
||||
import {ExperimentDetailsReverterService} from '../services/experiment-details-reverter.service';
|
||||
import {requestFailed} from '../../core/actions/http.actions';
|
||||
import {selectExperimentIdsDetails, selectExperimentsDetails, selectModelIdsDetails} from '../reducers';
|
||||
import {Observable, of} from 'rxjs';
|
||||
import {forkJoin, Observable, of} from 'rxjs';
|
||||
import {IExperimentDetail} from '~/features/experiments-compare/experiments-compare-models';
|
||||
import {REFETCH_EXPERIMENT_REQUESTED, refetchExperimentRequested} from '../actions/compare-header.actions';
|
||||
import {ExperimentCompareDetailsState} from '../reducers/experiments-compare-details.reducer';
|
||||
import {experimentListUpdated, setExperiments, setModels} from '../actions/experiments-compare-details.actions';
|
||||
import {getCompareDetailsOnlyFields} from '~/features/experiments-compare/experiments-compare-consts';
|
||||
import {selectHasDataFeature} from '~/core/reducers/users.reducer';
|
||||
@@ -105,14 +104,19 @@ export class ExperimentsCompareDetailsEffects {
|
||||
: of([]);
|
||||
}
|
||||
|
||||
fetchModelDetails$(ids): Observable<Array<IExperimentDetail>> {
|
||||
fetchModelDetails$(ids: string[]): Observable<Array<IExperimentDetail>> {
|
||||
return ids.length > 0 ?
|
||||
this.modelsApi.modelsGetAllEx({
|
||||
forkJoin([
|
||||
this.modelsApi.modelsGetAllEx({
|
||||
id: ids,
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
only_fields: ['company', 'created', 'last_update', 'last_iteration', 'framework', 'id', 'labels', 'name', 'ready', 'tags', 'system_tags', 'task.name', 'task.project', 'uri', 'user.name', 'parent', 'project.name', 'metadata']
|
||||
}).pipe(
|
||||
map(res => this.modelDetailsReverter.revertModels(ids, res.models))
|
||||
}),
|
||||
this.tasksApi.tasksGetAllEx({
|
||||
'models.input.model': ids,
|
||||
only_fields: ['name', 'models.input.model']} as any)
|
||||
]).pipe(
|
||||
map(([modelsRes, tasksRes]) => this.modelDetailsReverter.revertModels(ids, modelsRes.models, tasksRes.tasks))
|
||||
)
|
||||
: of([]);
|
||||
}
|
||||
|
||||
@@ -4,10 +4,17 @@ import {activeLoader, deactivateLoader, setServerError} from '../../core/actions
|
||||
import {catchError, mergeMap, map} from 'rxjs/operators';
|
||||
import {requestFailed} from '../../core/actions/http.actions';
|
||||
import {ApiTasksService} from '~/business-logic/api-services/tasks.service';
|
||||
import {getExperimentsHyperParams, setHyperParamsList, setMetricsList, setTasks} from '../actions/experiments-compare-scalars-graph.actions';
|
||||
import {
|
||||
getExperimentsHyperParams,
|
||||
setHyperParamsList,
|
||||
setMetricsList,
|
||||
setMetricsResults,
|
||||
setTasks
|
||||
} from '../actions/experiments-compare-scalars-graph.actions';
|
||||
import {GroupedHyperParams, HyperParams} from '../reducers/experiments-compare-charts.reducer';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {selectSelectedSettingsMetric} from '@common/experiments-compare/reducers';
|
||||
import {MetricVariantResult} from '~/business-logic/model/projects/metricVariantResult';
|
||||
|
||||
@Injectable()
|
||||
export class ExperimentsCompareScalarsGraphEffects {
|
||||
@@ -20,34 +27,69 @@ export class ExperimentsCompareScalarsGraphEffects {
|
||||
map(action => activeLoader(action.type))
|
||||
));
|
||||
|
||||
loadMovies$ = createEffect(() => this.actions$.pipe(
|
||||
getExperimentsHyperParams$ = createEffect(() => this.actions$.pipe(
|
||||
ofType(getExperimentsHyperParams),
|
||||
concatLatestFrom(() => this.store.select(selectSelectedSettingsMetric)),
|
||||
mergeMap(([action, /*metric*/]) => this.tasksApiService.tasksGetAllEx({
|
||||
id: action.experimentsIds,
|
||||
id: action.experimentsIds,
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
only_fields: ['last_metrics', 'name', 'last_iteration', 'hyperparams'],
|
||||
// ...(action.scatter && metric && {[`last_metrics.${metric.path}.value`]: [Number.MIN_VALUE, null]})
|
||||
})
|
||||
.pipe(
|
||||
// map(res => res.tasks)),
|
||||
mergeMap(res => {
|
||||
const metricsList = this.getMetricOptions(res.tasks);
|
||||
const paramsHasDiffs = this.getParametersHasDiffs(res.tasks);
|
||||
return [
|
||||
setTasks({tasks: res.tasks}),
|
||||
setMetricsList({metricsList}),
|
||||
setHyperParamsList({hyperParams: paramsHasDiffs}),
|
||||
deactivateLoader(action.type)];
|
||||
}),
|
||||
catchError(error => [
|
||||
requestFailed(error), deactivateLoader(action.type),
|
||||
setServerError(error, null, 'Failed to get Compared Experiments')
|
||||
])
|
||||
)
|
||||
only_fields: ['last_metrics', 'name', 'last_iteration', 'hyperparams'],
|
||||
// ...(action.scatter && metric && {[`last_metrics.${metric.path}.value`]: [Number.MIN_VALUE, null]})
|
||||
})
|
||||
.pipe(
|
||||
// map(res => res.tasks)),
|
||||
mergeMap(res => {
|
||||
const metricsList = this.getMetricOptions(res.tasks);
|
||||
const metricVariantsResults = this.getMetricResults(res.tasks);
|
||||
const paramsHasDiffs = this.getParametersHasDiffs(res.tasks);
|
||||
return [
|
||||
setTasks({tasks: res.tasks}),
|
||||
setMetricsList({metricsList}),
|
||||
setMetricsResults({metricVariantsResults: metricVariantsResults}),
|
||||
setHyperParamsList({hyperParams: paramsHasDiffs}),
|
||||
deactivateLoader(action.type)];
|
||||
}),
|
||||
catchError(error => [
|
||||
requestFailed(error), deactivateLoader(action.type),
|
||||
setServerError(error, null, 'Failed to get Compared Experiments')
|
||||
])
|
||||
)
|
||||
))
|
||||
);
|
||||
|
||||
getMetricResults(tasks): Array<MetricVariantResult> {
|
||||
const metrics = [];
|
||||
for (const task of tasks) {
|
||||
for (const metric in task.last_metrics) {
|
||||
for (const variant in task.last_metrics[metric]) {
|
||||
const metricName = task.last_metrics[metric][variant].metric;
|
||||
const variantName = task.last_metrics[metric][variant].variant;
|
||||
metrics.push({
|
||||
metric: metricName,
|
||||
metric_hash: metric,
|
||||
variant: variantName,
|
||||
variant_hash: variant
|
||||
});
|
||||
|
||||
// !metrics[metricName] && (metrics[metricName] = {});
|
||||
// if (!metrics[metricName][variantName]) {
|
||||
// metrics[metricName][variantName] = {};
|
||||
// metrics[metricName][variantName]['name'] = `${metricName}/${variantName}`;
|
||||
// metrics[metricName][variantName]['path'] = `${metric}.${variant}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return Object.keys(metrics).sort((a, b) => a.toLowerCase() > b.toLowerCase() ? 1 : -1).map(metricName => ({
|
||||
// metricName,
|
||||
// variants: Object.keys(metrics[metricName]).sort().map(variant => ({
|
||||
// name: variant,
|
||||
// value: metrics[metricName][variant]
|
||||
// }))
|
||||
// }));
|
||||
return metrics;
|
||||
}
|
||||
|
||||
getMetricOptions(tasks) {
|
||||
const metrics = {};
|
||||
for (const task of tasks) {
|
||||
|
||||
@@ -15,7 +15,7 @@ import {selectProjectType} from '~/core/reducers/view.reducer';
|
||||
import {ALL_PROJECTS_OBJECT} from '@common/core/effects/projects.effects';
|
||||
import {trackById} from '@common/shared/utils/forms-track-by';
|
||||
import {selectRouterConfig, selectRouterParams} from '@common/core/reducers/router-reducer';
|
||||
import {getGlobalLegendData} from '@common/experiments-compare/actions/experiments-compare-charts.actions';
|
||||
import {getGlobalLegendData, setGlobalLegendData} from '@common/experiments-compare/actions/experiments-compare-charts.actions';
|
||||
import {rgbList2Hex} from '@common/shared/services/color-hash/color-hash.utils';
|
||||
import {ColorHashService} from '@common/shared/services/color-hash/color-hash.service';
|
||||
import {SelectModelComponent} from '@common/select-model/select-model.component';
|
||||
@@ -61,11 +61,11 @@ export class ExperimentsCompareComponent implements OnInit, OnDestroy {
|
||||
private ids: string[];
|
||||
public duplicateNamesObject: { [name: string]: boolean };
|
||||
private routeConfig$: Observable<string[]>;
|
||||
private titleCasePipe = new TitleCasePipe();
|
||||
|
||||
constructor(private store: Store,
|
||||
private router: Router,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private titleCasePipe: TitleCasePipe,
|
||||
private colorHash: ColorHashService,
|
||||
private dialog: MatDialog,
|
||||
private cdr: ChangeDetectorRef,
|
||||
@@ -86,6 +86,7 @@ export class ExperimentsCompareComponent implements OnInit, OnDestroy {
|
||||
ngOnDestroy(): void {
|
||||
this.subs.unsubscribe();
|
||||
this.store.dispatch(resetSelectCompareHeader({fullReset: true}));
|
||||
this.store.dispatch(setGlobalLegendData({data: null}));
|
||||
this.store.dispatch(setContextMenu({contextMenu: null}));
|
||||
this.store.dispatch(resetSelectModelState({fullReset: true}));
|
||||
}
|
||||
@@ -97,7 +98,7 @@ export class ExperimentsCompareComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.subs.add(combineLatest([this.routeConfig$,
|
||||
this.store.select(selectRouterParams),
|
||||
this.store.select(selectSelectedProject)]).pipe(filter(([conf, params, project]) => !!params.ids && !!project?.id)).subscribe(([conf, params, project]) => {
|
||||
this.store.select(selectSelectedProject)]).pipe(filter(([, params, project]) => !!params.ids && !!project?.id)).subscribe(([conf, params, project]) => {
|
||||
this.setupCompareContextMenu(toCompareEntityType[this.entityType] ?? this.entityType, conf[conf[0] === 'datasets' ? 4 : 3], project?.id, params.ids, conf[0]);
|
||||
}));
|
||||
|
||||
@@ -216,7 +217,7 @@ export class ExperimentsCompareComponent implements OnInit, OnDestroy {
|
||||
|
||||
updateUrl(ids: string[]) {
|
||||
this.router.navigate(
|
||||
[{ids}, ...this.activatedRoute.firstChild?.snapshot.url.map(segment => segment.path)],
|
||||
[{ids}, ...(this.activatedRoute.firstChild?.snapshot.url.map(segment => segment.path) ?? [])],
|
||||
{
|
||||
queryParamsHandling: 'preserve',
|
||||
relativeTo: this.activatedRoute,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {HeaderNavbarTabConfig} from '@common/layout/header-navbar-tabs/header-navbar-tabs-config.types';
|
||||
import {MetricVariantResult} from '~/business-logic/model/projects/metricVariantResult';
|
||||
|
||||
export type MetricValueType = 'min_value' | 'max_value' | 'value';
|
||||
|
||||
@@ -8,6 +9,10 @@ export interface SelectedMetric {
|
||||
valueType?: 'min_value' | 'max_value' | 'value';
|
||||
}
|
||||
|
||||
export interface SelectedMetricVariant extends MetricVariantResult{
|
||||
valueType?: 'min_value' | 'max_value' | 'value';
|
||||
}
|
||||
|
||||
export interface DataDictionary {
|
||||
dataDictionary: boolean;
|
||||
link: string;
|
||||
|
||||
@@ -32,7 +32,6 @@ import {CompareCardExtraHeaderDirective} from './dumbs/compare-card-extra-header
|
||||
import {CompareCardHeaderDirective} from './dumbs/compare-card-header.directive';
|
||||
import {TableDiffModule} from '../shared/ui-components/data/table-diff/table-diff.module';
|
||||
import {CardModule} from '../shared/ui-components/panel/card2';
|
||||
import {DrawerModule} from '../shared/ui-components/panel/drawer';
|
||||
import {
|
||||
ParallelCoordinatesGraphComponent
|
||||
} from './dumbs/parallel-coordinates-graph/parallel-coordinates-graph.component';
|
||||
@@ -47,16 +46,20 @@ import {
|
||||
} from './containers/experiment-compare-params/experiment-compare-params.component';
|
||||
import {ExperimentsCompareParamsEffects} from './effects/experiments-compare-params.effects';
|
||||
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||
import {ModelCompareDetailsComponent} from '@common/experiments-compare/containers/model-compare-details/model-compare-details.component';
|
||||
import {
|
||||
ModelCompareDetailsComponent
|
||||
} from '@common/experiments-compare/containers/model-compare-details/model-compare-details.component';
|
||||
import {IExperimentCompareChartsState} from '@common/experiments-compare/reducers/experiments-compare-charts.reducer';
|
||||
import {UserPreferences} from '@common/user-preferences';
|
||||
import {merge, pick} from 'lodash-es';
|
||||
import {createUserPrefFeatureReducer} from '@common/core/meta-reducers/user-pref-reducer';
|
||||
import {EXPERIMENTS_COMPARE_METRICS_CHARTS_} from '@common/experiments-compare/actions/experiments-compare-charts.actions';
|
||||
import {
|
||||
EXPERIMENTS_COMPARE_METRICS_CHARTS_
|
||||
} from '@common/experiments-compare/actions/experiments-compare-charts.actions';
|
||||
import {EXPERIMENTS_COMPARE_SELECT_EXPERIMENT_} from '@common/experiments-compare/actions/compare-header.actions';
|
||||
import {LabeledFormFieldDirective} from '@common/shared/directive/labeled-form-field.directive';
|
||||
import {EllipsisMiddleDirective} from '@common/shared/ui-components/directives/ellipsis-middle.directive';
|
||||
import { CompareScatterPlotComponent } from './containers/compare-scatter-plot/compare-scatter-plot.component';
|
||||
import {CompareScatterPlotComponent} from './containers/compare-scatter-plot/compare-scatter-plot.component';
|
||||
import {ScatterPlotComponent} from '@common/shared/components/charts/scatter-plot/scatter-plot.component';
|
||||
import {NoUnderscorePipe} from '@common/shared/pipes/no-underscore.pipe';
|
||||
import {HideHashPipe} from '@common/shared/pipes/hide-hash.pipe';
|
||||
@@ -92,7 +95,17 @@ import {MatSelectModule} from '@angular/material/select';
|
||||
import {MatSidenavModule} from '@angular/material/sidenav';
|
||||
import {MatExpansionModule} from '@angular/material/expansion';
|
||||
import {MatInputModule} from '@angular/material/input';
|
||||
import {ShowTooltipIfEllipsisDirective} from '@common/shared/ui-components/indicators/tooltip/show-tooltip-if-ellipsis.directive';
|
||||
import {
|
||||
MetricVariantSelectorComponent
|
||||
} from '@common/experiments-compare/dumbs/metric-param-selector/metric-variant-selector.component';
|
||||
import {ParamSelectorComponent} from '@common/experiments-compare/dumbs/param-selector/param-selector.component';
|
||||
import {
|
||||
ShowTooltipIfEllipsisDirective
|
||||
} from '@common/shared/ui-components/indicators/tooltip/show-tooltip-if-ellipsis.directive';
|
||||
import {MetricVariantToPathPipe} from '@common/shared/pipes/metric-variant-to-path.pipe';
|
||||
import {MetricResultToSelectedMetricPipe} from '@common/shared/pipes/metric-result-to-selected-metric.pipe';
|
||||
import {MetricVariantToNamePipe} from '@common/shared/pipes/metric-variant-to-name.pipe';
|
||||
import {DrawerComponent} from '@common/shared/ui-components/panel/drawer/drawer.component';
|
||||
|
||||
export const COMPARE_STORE_KEY = 'experimentsCompare';
|
||||
export const COMPARE_CONFIG_TOKEN =
|
||||
@@ -115,7 +128,7 @@ export const getCompareConfig = (userPreferences: UserPreferences) => ({
|
||||
return merge({}, nextState, savedState);
|
||||
}
|
||||
if (action.type.startsWith('EXPERIMENTS_COMPARE_')) {
|
||||
localStorage.setItem(localStorageKey, JSON.stringify(pick(nextState, ['charts.settingsList', 'charts.scalarsHoverMode'])));
|
||||
localStorage.setItem(localStorageKey, JSON.stringify(pick(nextState, ['charts.settingsList', 'charts.scalarsHoverMode', 'compareHeader.hideIdenticalRows'])));
|
||||
}
|
||||
return nextState;
|
||||
};
|
||||
@@ -152,7 +165,6 @@ export const getCompareConfig = (userPreferences: UserPreferences) => ({
|
||||
TableDiffModule,
|
||||
ScrollingModule,
|
||||
CardModule,
|
||||
DrawerModule,
|
||||
ExperimentSharedModule,
|
||||
ExperimentsCompareRoutingModule,
|
||||
ExperimentGraphsModule,
|
||||
@@ -197,7 +209,14 @@ export const getCompareConfig = (userPreferences: UserPreferences) => ({
|
||||
MatSidenavModule,
|
||||
MatExpansionModule,
|
||||
MatInputModule,
|
||||
ShowTooltipIfEllipsisDirective
|
||||
MetricVariantSelectorComponent,
|
||||
ParamSelectorComponent,
|
||||
MatInputModule,
|
||||
ShowTooltipIfEllipsisDirective,
|
||||
MetricVariantToPathPipe,
|
||||
MetricResultToSelectedMetricPipe,
|
||||
MetricVariantToNamePipe,
|
||||
DrawerComponent,
|
||||
],
|
||||
providers: [
|
||||
{provide: COMPARE_CONFIG_TOKEN, useFactory: getCompareConfig, deps: [UserPreferences]},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user