release v1.11

This commit is contained in:
shyallegro
2023-05-23 14:03:10 +03:00
committed by Shay Halsband
parent 29c68abeb4
commit 28d1702075
540 changed files with 9858 additions and 7401 deletions

View File

@@ -2,11 +2,15 @@
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# For the full list of supported browsers by the Angular framework, please see:
# https://angular.io/guide/browser-support
# You can see what browsers were selected by your queries by running:
# npx browserslist
> 1%
last 2 versions
not dead
not ie <= 11
last 2 Chrome versions
last 2 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR

View File

@@ -20,6 +20,8 @@
"plugin:@angular-eslint/template/process-inline-templates"
],
"rules": {
"no-console": "error",
"no-debugger": "error",
"@angular-eslint/component-selector": [
"error",
{
@@ -88,7 +90,9 @@
"extends": [
"plugin:@angular-eslint/template/recommended"
],
"rules": {}
"rules": {
"@angular-eslint/template/use-track-by-function": "warn"
}
}
]
}

2
.gitignore vendored
View File

@@ -7,7 +7,6 @@
/out-tsc
/gen-code
/src/__ngcc_entry_points__.json
.angular/
# dependencies
/node_modules
@@ -30,6 +29,7 @@
!.vscode/extensions.json
# misc
/.angular/cache
/.sass-cache
/connect.lock
/coverage

View File

@@ -47,7 +47,6 @@
"node_modules/ngx-markdown-editor/assets/marked.min.js"
],
"allowedCommonJsDependencies": [
"lodash/fp",
"ansi-to-html",
"has-ansi",
"fabric/dist/fabric.min",
@@ -66,7 +65,10 @@
"britecharts/dist/umd/line.min",
"britecharts/dist/umd/tooltip.min",
"britecharts/dist/umd/miniTooltip.min",
"britecharts/dist/umd/scatterPlot.min"
"britecharts/dist/umd/scatterPlot.min",
"localforage",
"dom-to-image",
"ace-builds"
],
"vendorChunk": true,
"extractLicenses": false,
@@ -128,7 +130,10 @@
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "trains-webapp:build"
"browserTarget": "trains-webapp:build",
"proxyConfig": "proxy.config.js",
"liveReload": false,
"port": 4300
},
"configurations": {
"appdev": {
@@ -292,7 +297,6 @@
"src/app/webapp-common/clearml-applications/report-widgets/src/app/webapp-common/assets",
"src/app/webapp-common/clearml-applications/report-widgets/src/app/webapp-common/assets/fonts/trains.ttf"
],
"styles": [
"src/app/webapp-common/clearml-applications/report-widgets/src/styles.scss",
{
@@ -311,6 +315,12 @@
"@schematics/angular:component": {
"prefix": "sm",
"style": "scss"
},
"@angular-eslint/schematics:application": {
"setParserOptionsProject": true
},
"@angular-eslint/schematics:library": {
"setParserOptionsProject": true
}
}
}

5941
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "clearml-webapp",
"version": "1.10.0",
"version": "1.11.0",
"license": "",
"scripts": {
"ng": "ng",
@@ -19,54 +19,54 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^15.1.3",
"@angular/cdk": "^15.1.3",
"@angular/common": "^15.1.3",
"@angular/compiler": "^15.1.3",
"@angular/core": "^15.1.3",
"@angular/forms": "^15.1.3",
"@angular/material": "^15.1.3",
"@angular/platform-browser": "^15.1.3",
"@angular/platform-browser-dynamic": "^15.1.3",
"@angular/platform-server": "^15.1.3",
"@angular/router": "^15.1.3",
"@angular/service-worker": "^15.1.3",
"@angular/youtube-player": "^15.1.3",
"@aws-sdk/client-s3": "^3.266.1",
"@aws-sdk/s3-request-presigner": "^3.266.1",
"@angular/animations": "^15.2.8",
"@angular/cdk": "^15.2.8",
"@angular/common": "^15.2.8",
"@angular/compiler": "^15.2.8",
"@angular/core": "^15.2.8",
"@angular/forms": "^15.2.8",
"@angular/material": "^15.2.8",
"@angular/platform-browser": "^15.2.8",
"@angular/platform-browser-dynamic": "^15.2.8",
"@angular/platform-server": "^15.2.8",
"@angular/router": "^15.2.8",
"@angular/service-worker": "^15.2.8",
"@angular/youtube-player": "^15.2.8",
"@aws-sdk/client-s3": "^3.317.0",
"@aws-sdk/s3-request-presigner": "^3.317.0",
"@ctrl/ngx-github-buttons": "^8.0.0",
"@ngneat/dag": "^2.0.0",
"@ngrx/effects": "^15.2.1",
"@ngrx/entity": "^15.2.1",
"@ngrx/router-store": "^15.2.1",
"@ngrx/store": "^15.2.1",
"ace-builds": "^1.15.0",
"@ngrx/effects": "^15.4.0",
"@ngrx/entity": "^15.4.0",
"@ngrx/router-store": "^15.4.0",
"@ngrx/store": "^15.4.0",
"ace-builds": "^1.18.0",
"angular-google-tag-manager": "^1.7.0",
"angular-resizable-element": "^7.0.2",
"angular-split": "^14.1.0",
"angular-split": "^15.0.0",
"ansi-to-html": "^0.7.2",
"bootstrap": "^4.6.2",
"bootstrap": "^5.2.3",
"britecharts": "^2.18.0",
"curved-arrows": "^0.1.0",
"d3-selection": "^3.0.0",
"diff": "^5.1.0",
"dom-to-image": "^2.6.0",
"filesize": "^10.0.6",
"filesize": "^10.0.7",
"has-ansi": "^5.0.1",
"hocon-parser": "^1.0.1",
"jwt-decode": "^3.1.2",
"localforage": "^1.10.0",
"lodash-es": "^4.17.21",
"lucene": "^2.1.1",
"ngx-clipboard": "^15.1.0",
"ngx-color-picker": "^13.0.0",
"ngx-clipboard": "^16.0.0",
"ngx-color-picker": "^14.0.0",
"ngx-device-detector": "^5.0.1",
"ngx-markdown-editor": "^5.1.0",
"ngx-markdown-editor": "^5.3.0",
"ngx-print": "^1.3.1",
"ngx-window-token": "^6.0.0",
"ngx-window-token": "^7.0.0",
"object-hash": "^3.0.0",
"primeicons": "^6.0.1",
"primeng": "^15.2.0",
"primeng": "^15.4.1",
"process": "^0.11.10",
"rxjs": "^7.8.0",
"string-to-color": "^2.2.2",
@@ -74,36 +74,36 @@
"tslib": "^2.5.0",
"url": "^0.11.0",
"uuid": "^9.0.0",
"zone.js": "~0.12.0"
"zone.js": "^0.12.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "^15.1.4",
"@angular-devkit/core": "^15.1.4",
"@angular-devkit/schematics": "^15.1.4",
"@angular-devkit/schematics-cli": "^15.1.4",
"@angular-eslint/builder": "^15.2.0",
"@angular-eslint/eslint-plugin": "^15.2.0",
"@angular-eslint/eslint-plugin-template": "^15.2.0",
"@angular-eslint/schematics": "15.2.0",
"@angular-eslint/template-parser": "^15.2.0",
"@angular/cli": "^15.1.4",
"@angular/compiler-cli": "^15.1.3",
"@angular/language-service": "^15.1.3",
"@fortawesome/fontawesome-free": "^6.3.0",
"@ngrx/schematics": "^15.2.1",
"@ngrx/store-devtools": "^15.2.1",
"@types/d3-selection": "^3.0.4",
"@types/lodash-es": "^4.17.6",
"@types/node": "^18.13.0",
"@types/plotly.js": "^2.12.13",
"@types/uuid": "^9.0.0",
"@typescript-eslint/eslint-plugin": "^5.51.0",
"@typescript-eslint/parser": "^5.51.0",
"@angular-devkit/build-angular": "^15.2.6",
"@angular-devkit/core": "^15.2.6",
"@angular-devkit/schematics": "^15.2.6",
"@angular-devkit/schematics-cli": "^15.2.5",
"@angular-eslint/builder": "^15.2.1",
"@angular-eslint/eslint-plugin": "^15.2.1",
"@angular-eslint/eslint-plugin-template": "^15.2.1",
"@angular-eslint/schematics": "15.2.1",
"@angular-eslint/template-parser": "^15.2.1",
"@angular/cli": "^15.2.6",
"@angular/compiler-cli": "^15.2.8",
"@angular/language-service": "^15.2.8",
"@fortawesome/fontawesome-free": "^6.4.0",
"@ngrx/schematics": "^15.4.0",
"@ngrx/store-devtools": "^15.4.0",
"@types/d3-selection": "^3.0.5",
"@types/lodash-es": "^4.17.7",
"@types/node": "^18.16.0",
"@types/plotly.js": "^2.12.18",
"@types/uuid": "^9.0.1",
"@typescript-eslint/eslint-plugin": "^5.59.1",
"@typescript-eslint/parser": "^5.59.1",
"codelyzer": "^6.0.2",
"eslint": "^8.33.0",
"eslint": "^8.39.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-jsdoc": "39.8.0",
"eslint-plugin-jsdoc": "41.1.2",
"eslint-plugin-prefer-arrow": "1.2.3",
"typescript": "~4.9.5"
}
}
}

View File

@@ -3,22 +3,19 @@ import {selectCurrentUser} from '@common/core/reducers/users-reducer';
import {Component, OnDestroy, OnInit, ViewEncapsulation, HostListener, Renderer2, Injector} from '@angular/core';
import {ActivatedRoute, NavigationEnd, Router, Params, RouterEvent} from '@angular/router';
import {Title} from '@angular/platform-browser';
import {selectLoggedOut} from '@common/core/reducers/view.reducer';
import {selectBreadcrumbs, selectLoggedOut} from '@common/core/reducers/view.reducer';
import {Store} from '@ngrx/store';
import {selectRouterConfig, selectRouterParams, selectRouterUrl} from '@common/core/reducers/router-reducer';
import {selectRouterParams, selectRouterUrl} from '@common/core/reducers/router-reducer';
import {ApiProjectsService} from './business-logic/api-services/projects.service';
import {Project} from './business-logic/model/projects/project';
import {getAllSystemProjects, setSelectedProjectId, updateProject} from '@common/core/actions/projects.actions';
import {selectSelectedProject} from '@common/core/reducers/projects.reducer';
import {MatLegacyDialog as MatDialog} from '@angular/material/legacy-dialog';
import {MatDialog} from '@angular/material/dialog';
import {getTutorialBucketCredentials} from '@common/core/actions/common-auth.actions';
import {termsOfUseAccepted} from '@common/core/actions/users.actions';
import {distinctUntilChanged, filter, map, tap, withLatestFrom} from 'rxjs/operators';
import {distinctUntilChanged, filter, map, tap} from 'rxjs/operators';
import * as routerActions from './webapp-common/core/actions/router.actions';
import {combineLatest, Observable, Subscription} from 'rxjs';
import {selectBreadcrumbsStrings} from '@common/layout/layout.reducer';
import {NestedProjectTypeUrlEnum, prepareNames} from './layout/breadcrumbs/breadcrumbs.utils';
import {formatStaticCrumb} from '@common/layout/breadcrumbs/breadcrumbs-common.utils';
import {ServerUpdatesService} from '@common/shared/services/server-updates.service';
import {selectAvailableUpdates} from './core/reducers/view.reducer';
import {UPDATE_SERVER_PATH} from './app.constants';
@@ -53,10 +50,8 @@ export class AppComponent implements OnInit, OnDestroy {
private selectedProjectFromUrl$: Observable<string>;
private breadcrumbsSubscription: Subscription;
private selectedCurrentUserSubscription: Subscription;
private breadcrumbsStrings;
private selectedCurrentUser$: Observable<any>;
public showNotification: boolean = true;
public showSurvey$: Observable<boolean>;
public demo = ConfigurationService.globalEnvironment.demo;
public isLoginContext: boolean;
public currentUser: User;
@@ -67,6 +62,7 @@ export class AppComponent implements OnInit, OnDestroy {
public showSurvey: boolean;
private plotlyURL: string;
private environment: Environment;
private title = 'ClearML';
@HostListener('document:visibilitychange') onVisibilityChange() {
this.store.dispatch(visibilityChanged({visible: !document.hidden}));
@@ -140,7 +136,6 @@ export class AppComponent implements OnInit, OnDestroy {
};
this.gtmService?.pushTag(gtmTag);
this.store.dispatch(new routerActions.NavigationEnd());
this.updateTitle();
});
this.selectedCurrentUserSubscription = this.selectedCurrentUser$.pipe(
@@ -181,16 +176,10 @@ export class AppComponent implements OnInit, OnDestroy {
}
});
this.breadcrumbsSubscription = this.store.select(selectBreadcrumbsStrings).pipe(
filter(names => !!names),
withLatestFrom(this.store.select(selectRouterConfig))
).subscribe(
([names, routeConf]) => {
const projectType = `${routeConf?.[0]}${routeConf?.[1] === 'simple' ? '/' + routeConf?.[1] : ''}`;
this.breadcrumbsStrings = prepareNames(names, projectType as NestedProjectTypeUrlEnum);
this.updateTitle();
}
);
this.breadcrumbsSubscription = this.store.select(selectBreadcrumbs).subscribe(breadcrumbs => {
const crumbs = breadcrumbs.flat().map(breadcrumb=> breadcrumb.name);
this.titleService.setTitle(`${this.title ? this.title + '-' : ''} ${crumbs.join(' / ')}`);
});
if (window.localStorage.getItem('disableHidpi') !== 'true') {
this.setScale();
@@ -228,26 +217,6 @@ export class AppComponent implements OnInit, OnDestroy {
return this.router.navigateByUrl('projects');
}
updateTitle() {
let route = this.route.snapshot.firstChild;
let routeConfig = [];
while (route) {
const path = route.routeConfig.path.split('/').filter((item) => !!item);
routeConfig = routeConfig.concat(path);
route = route.firstChild;
}
const crumbs = routeConfig
.reduce((acc, config) => {
const dynamicCrumb = this.breadcrumbsStrings[config];
let crumb = dynamicCrumb ? dynamicCrumb : formatStaticCrumb(config);
crumb = Array.isArray(crumb) ? crumb.at(-1) : crumb;
return acc.concat(crumb.name);
}, [''])
.filter(name => !!name && name !== ':project');
this.titleService.setTitle(`ClearML - ${crumbs.join(' / ')}`);
}
versionDismissed(version: string) {
this.serverUpdatesService.setDismissedVersion(version);
}

View File

@@ -20,7 +20,7 @@ import {ColorHashService} from '@common/shared/services/color-hash/color-hash.se
import {SharedModule} from './shared/shared.module';
import {ConfigurationService} from '@common/shared/services/configuration.service';
import {ProjectsSharedModule} from './features/projects/shared/projects-shared.module';
import {MAT_LEGACY_FORM_FIELD_DEFAULT_OPTIONS as MAT_FORM_FIELD_DEFAULT_OPTIONS} from '@angular/material/legacy-form-field';
import {MAT_FORM_FIELD_DEFAULT_OPTIONS} from '@angular/material/form-field';
import {LoginService} from '~/shared/services/login.service';
import {ExperimentSharedModule} from '~/features/experiments/shared/experiment-shared.module';

View File

@@ -1,7 +1,4 @@
import {Routes} from '@angular/router';
/*
import {AdminComponent} from '@common/settings/admin/admin.component';
*/
import {ProjectRedirectGuardGuard} from '@common/shared/guards/project-redirect.guard';
import {EntityTypeEnum} from '~/shared/constants/non-common-consts';
@@ -45,6 +42,11 @@ export const routes: Routes = [
loadChildren: () => import('./webapp-common/experiments-compare/experiments-compare.module').then(m => m.ExperimentsCompareModule),
data: {entityType: EntityTypeEnum.experiment},
},
{
path: 'compare-models',
data: {entityType: EntityTypeEnum.model},
loadChildren: () => import('./webapp-common/experiments-compare/experiments-compare.module').then(m => m.ExperimentsCompareModule)
},
]
},
]

View File

@@ -26,4 +26,5 @@ export interface EventsGetMultiTaskPlotsRequest {
*/
scroll_id?: string;
no_scroll?: boolean;
model_events?: boolean;
}

View File

@@ -23,4 +23,5 @@ export interface EventsMultiTaskScalarMetricsIterHistogramRequest {
*/
samples?: number;
key?: ScalarKeyEnum;
model_events?: boolean;
}

View File

@@ -23,7 +23,7 @@ export interface MetricsPlotEvent {
/**
* \'plot\'
*/
type: object;
type: any;
/**
* Task ID (required)
*/
@@ -41,7 +41,7 @@ export interface MetricsPlotEvent {
*/
variant?: string;
/**
* An entire plot (not single datapoint) and it\'s layout. Used for plotting ROC curves, confidence matrices, etc. when evaluating the net.
* An entire plot (not single datapoint) and it\'s layout. Used for plotting ROC curves, confidence matrices, etc. when evaluating the net.
*/
plot_str?: string;
/**

View File

@@ -0,0 +1,44 @@
/**
* models
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* OpenAPI spec version: 999.0
*
*
* NOTE: This class is auto generated by the swagger code generator program.
* https://github.com/swagger-api/swagger-codegen.git
* Do not edit the class manually.
*/
export interface LastMetricsEvent {
/**
* Metric name
*/
metric?: string;
/**
* Variant name
*/
variant?: string;
/**
* Last value reported
*/
value?: number;
/**
* Minimum value reported
*/
min_value?: number;
/**
* The iteration at which the minimum value was reported
*/
min_value_iteration?: number;
/**
* Maximum value reported
*/
max_value?: number;
/**
* The iteration at which the maximum value was reported
*/
max_value_iteration?: number;
}

View File

@@ -10,6 +10,8 @@
* Do not edit the class manually.
*/
import { ModelStats } from '././modelStats';
import { LastMetricsEvent } from '././lastMetricsEvent';
import { MetadataItem } from '././metadataItem';
@@ -55,11 +57,11 @@ export interface Model {
*/
comment?: string;
/**
* User-defined tags list
* User-defined tags
*/
tags?: Array<string>;
/**
* System tags list. This field is reserved for system use, please don\'t use it.
* System tags. This field is reserved for system use, please don\'t use it.
*/
system_tags?: Array<string>;
/**
@@ -89,5 +91,14 @@ export interface Model {
/**
* Model metadata
*/
metadata?: Array<MetadataItem>;
metadata?: { [key: string]: MetadataItem; };
/**
* Last iteration reported for this model
*/
last_iteration?: number;
/**
* Last metric variants (hash to events), one for each metric hash
*/
last_metrics?: { [key: string]: { [key: string]: LastMetricsEvent; }; };
stats?: ModelStats;
}

View File

@@ -0,0 +1,23 @@
/**
* models
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* OpenAPI spec version: 999.0
*
*
* NOTE: This class is auto generated by the swagger code generator program.
* https://github.com/swagger-api/swagger-codegen.git
* Do not edit the class manually.
*/
/**
* Model statistics
*/
export interface ModelStats {
/**
* Number of the model labels
*/
labels_count?: number;
}

View File

@@ -21,4 +21,8 @@ export interface ProjectsGetUniqueMetricVariantsRequest {
* If set to \'true\' and the project field is set then the result includes metrics/variants from the subproject tasks
*/
include_subprojects?: boolean;
/**
* If set to Truethen bring unique metric and variant names from the project models otherwise from the project tasks
*/
model_metrics?: boolean;
}

View File

@@ -12,20 +12,25 @@
import { Task } from '././task';
import { DebugImagesResponseTaskMetrics } from '././debugImagesResponseTaskMetrics';
import { SingleValueTaskMetrics } from '././singleValueTaskMetrics';
export interface ReportsGetTaskDataResponse {
/**
* List of tasks
*/
tasks?: Array<Task>;
/**
* Plot events grouped by tasks and iterations
*/
plots?: object;
/**
* Debug image events grouped by tasks and iterations
*/
debug_images?: Array<DebugImagesResponseTaskMetrics>;
scalar_metrics_iter_histogram?: object;
/**
* List of tasks
*/
tasks?: Array<Task>;
/**
* Plots mapped by metric, variant, task and iteration
*/
plots?: object;
/**
* Debug image events grouped by tasks and iterations
*/
debug_images?: Array<DebugImagesResponseTaskMetrics>;
scalar_metrics_iter_histogram?: object;
/**
* Single value metrics grouped by task
*/
single_value_metrics?: Array<SingleValueTaskMetrics>;
}

View File

@@ -0,0 +1,22 @@
/**
* reports
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* OpenAPI spec version: 999.0
*
*
* NOTE: This class is auto generated by the swagger code generator program.
* https://github.com/swagger-api/swagger-codegen.git
* Do not edit the class manually.
*/
import { SingleValueTaskMetricsValues } from '././singleValueTaskMetricsValues';
export interface SingleValueTaskMetrics {
/**
* Task ID
*/
task?: string;
values?: Array<SingleValueTaskMetricsValues>;
}

View File

@@ -0,0 +1,20 @@
/**
* reports
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* OpenAPI spec version: 999.0
*
*
* NOTE: This class is auto generated by the swagger code generator program.
* https://github.com/swagger-api/swagger-codegen.git
* Do not edit the class manually.
*/
export interface SingleValueTaskMetricsValues {
metric?: string;
variant?: string;
value?: number;
timestamp?: number;
}

View File

@@ -25,4 +25,5 @@ export interface TasksDequeueManyRequest {
* Extra information regarding status change
*/
status_message?: string;
remove_from_all_queues?: boolean;
}

View File

@@ -25,4 +25,5 @@ export interface TasksDequeueRequest {
* Extra information regarding status change
*/
status_message?: string;
remove_from_all_queues?: boolean;
}

View File

@@ -60,6 +60,7 @@ const syncedKeys = [
'projects.selectedProject',
'rootProjects.showHidden',
'rootProjects.hideExamples',
'rootProjects.defaultNestedModeForFeature',
'views.availableUpdates',
'views.showSurvey',
'views.neverShowPopupAgain'
@@ -94,7 +95,7 @@ const userPrefMetaFactory = (userPreferences: UserPreferences): MetaReducer<any>
(reducer: ActionReducer<any>) =>
createUserPrefReducer('users', ['activeWorkspace', 'showOnlyUserWork'], [USERS_PREFIX], userPreferences, reducer),
(reducer: ActionReducer<any>) =>
createUserPrefReducer('rootProjects', ['tagsColors', 'graphVariant', 'showHidden', 'hideExamples', 'aa'] as (keyof RootProjects)[], [ROOT_PROJECTS_PREFIX], userPreferences, reducer),
createUserPrefReducer('rootProjects', ['tagsColors', 'graphVariant', 'showHidden', 'hideExamples', 'defaultNestedModeForFeature'], [ROOT_PROJECTS_PREFIX], userPreferences, reducer),
(reducer: ActionReducer<any>) =>
createUserPrefReducer('views', ['autoRefresh', 'neverShowPopupAgain', 'redactedArguments', 'hideRedactedArguments'], [VIEW_PREFIX], userPreferences, reducer),
localStorageReducer,

View File

@@ -18,7 +18,7 @@ export const usersReducer = createReducer<UsersState>(initUsers,
}))
);
export const selectFeatures = createSelector(users, (state) => []);
export const selectFeatures = createSelector(users, (state) => null);
// eslint-disable-next-line @typescript-eslint/naming-convention
export const selectTermsOfUse = createSelector(users, state => ({accept_required: null}));
export const selectInvitesPending = createSelector(users, state => []);

View File

@@ -3,7 +3,7 @@ import {Store} from '@ngrx/store';
import {filter} from 'rxjs/operators';
import {updateUsageStats} from '../actions/usage-stats.actions';
import {selectPromptUser} from '../reducers/usage-stats.reducer';
import {MatLegacyDialog as MatDialog} from '@angular/material/legacy-dialog';
import {MatDialog} from '@angular/material/dialog';
import {ConfirmDialogComponent} from '@common/shared/ui-components/overlay/confirm-dialog/confirm-dialog.component';
import {ConfigurationService} from '@common/shared/services/configuration.service';

View File

@@ -1,9 +1,14 @@
import {RouterModule, Routes} from '@angular/router';
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}
{path: '', component: DashboardComponent, data:{staticBreadcrumb:[[{
name: 'DASHBOARD',
type: CrumbTypeEnum.Feature
}]]}
}
];
@NgModule({

View File

@@ -9,9 +9,9 @@
<div header-buttons>
<button
*smCheckPermission="true"
class="btn btn-primary d-flex align-items-center"
class="btn btn-cml-primary d-flex align-items-center"
(click)="redirectToWorkers()"
><i class="al-icon al-ico-queues al-color light-grey-blue sm mr-2"></i>MANAGE WORKERS AND QUEUES</button>
><i class="al-icon al-ico-queues al-color light-grey-blue sm me-2"></i>MANAGE WORKERS AND QUEUES</button>
</div>
</sm-dashboard-experiments>
</div>

View File

@@ -8,7 +8,7 @@ import {filter, skip, take} from 'rxjs/operators';
import {setDeep} from '@common/core/actions/projects.actions';
import {getRecentProjects, getRecentExperiments} from '@common/dashboard/common-dashboard.actions';
import {selectFirstLogin} from '@common/core/reducers/view.reducer';
import {MatLegacyDialog as MatDialog} from '@angular/material/legacy-dialog';
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';

View File

@@ -4,13 +4,17 @@ import {SimpleDatasetsComponent} from '@common/datasets/simple-datasets/simple-d
import {EntityTypeEnum} from '~/shared/constants/non-common-consts';
import {
NestedProjectViewPageComponent
} from "@common/nested-project-view/nested-project-view-page/nested-project-view-page.component";
} from '@common/nested-project-view/nested-project-view-page/nested-project-view-page.component';
import {CrumbTypeEnum} from '@common/layout/breadcrumbs/breadcrumbs.component';
const routes: Routes = [
{
path : '',
component: SimpleDatasetsComponent,
data : {search: true}
data: {search: true, staticBreadcrumb:[[{
name: 'DATASETS',
type: CrumbTypeEnum.Feature
}]]}
},
{
path: 'simple/:projectId',

View File

@@ -6,7 +6,8 @@ import {DatasetsRoutingModule} from '~/features/datasets/datasets-routing.module
import {DatasetsSharedModule} from '~/features/datasets/shared/datasets-shared.module';
import {SharedPipesModule} from '@common/shared/pipes/shared-pipes.module';
import {SMSharedModule} from '@common/shared/shared.module';
import {FeatureNestedProjectViewModule} from "~/features/nested-project-view/feature-nested-project-view.module";
import {FeatureNestedProjectViewModule} from '~/features/nested-project-view/feature-nested-project-view.module';
import {LabeledFormFieldDirective} from '@common/shared/directive/labeled-form-field.directive';
@NgModule({
@@ -18,6 +19,7 @@ import {FeatureNestedProjectViewModule} from "~/features/nested-project-view/fea
DatasetsSharedModule,
SharedPipesModule,
FeatureNestedProjectViewModule,
LabeledFormFieldDirective,
],
declarations: [
SimpleDatasetsComponent,

View File

@@ -1,58 +1,8 @@
<nav [overflowTrigger]="splitSize" (smOverflows)="navbarOverflowed($event)" [overflowDelay]="800" [class.minimized]="minimized">
<span [routerLink]="['execution']" routerLinkActive #rlaExecution="routerLinkActive" queryParamsHandling="preserve">
<sm-navbar-item header="execution" [active]="rlaExecution.isActive" class="small-nav"></sm-navbar-item>
</span>
<span [routerLink]="['hyper-params/hyper-param/_empty_']" queryParamsHandling="merge">
<sm-navbar-item header="configuration"
class="small-nav"
[active]="(routerConfig$| async)?.includes('hyper-params')"></sm-navbar-item>
</span>
<span [routerLink]="['artifacts']" routerLinkActive #rlaModel="routerLinkActive" queryParamsHandling="preserve">
<sm-navbar-item header="artifacts"
class="small-nav"
[active]="rlaModel.isActive"></sm-navbar-item>
</span>
<span [routerLink]="['general']" routerLinkActive #rlaGeneral="routerLinkActive" queryParamsHandling="preserve">
<sm-navbar-item header="info"
class="small-nav"
[active]="rlaGeneral.isActive"></sm-navbar-item>
</span>
<span [matMenuTriggerFor]="results" *ngIf="overflow">
<sm-navbar-item header="results"
class="small-nav"
[multi]="true"
[active]="rlaDebug.isActive || rlaPlots.isActive || rlaScalars.isActive || rlaLog.isActive"></sm-navbar-item>
</span>
<div class="d-inline-block" [style.visibility]="overflow ? 'hidden' : 'visible'">
<span [routerLink]="baseInfoRoute.concat(['log'])" routerLinkActive queryParamsHandling="preserve"
#rlaLog="routerLinkActive">
<sm-navbar-item class="small-nav" header="console" [active]="rlaLog.isActive"></sm-navbar-item>
</span>
<span [routerLink]="baseInfoRoute.concat(['metrics','scalar'])" routerLinkActive queryParamsHandling="preserve"
#rlaScalars="routerLinkActive">
<sm-navbar-item class="small-nav" header="Scalars" [active]="rlaScalars.isActive"></sm-navbar-item>
</span>
<span [routerLink]="baseInfoRoute.concat(['metrics','plots'])" routerLinkActive queryParamsHandling="preserve"
#rlaPlots="routerLinkActive">
<sm-navbar-item class="small-nav" header="PLOTS" [active]="rlaPlots.isActive"></sm-navbar-item>
</span>
<span [routerLink]="baseInfoRoute.concat(['debugImages'])" routerLinkActive queryParamsHandling="preserve"
#rlaDebug="routerLinkActive">
<sm-navbar-item class="small-nav" header="DEBUG SAMPLES" [active]="rlaDebug.isActive"></sm-navbar-item>
</span>
</div>
<mat-menu #results="matMenu">
<button mat-menu-item [routerLink]="baseInfoRoute.concat(['log'])" [class.active]="rlaLog.isActive">CONSOLE</button>
<button mat-menu-item [routerLink]="baseInfoRoute.concat(['metrics','scalar'])"
[class.active]="rlaScalars.isActive">SCALARS
</button>
<button mat-menu-item [routerLink]="baseInfoRoute.concat(['metrics','plots'])" [class.active]="rlaPlots.isActive">
PLOTS
</button>
<button mat-menu-item [routerLink]="baseInfoRoute.concat(['debugImages'])" [class.active]="rlaDebug.isActive">DEBUG
SAMPLES
</button>
</mat-menu>
<div class="tab-nav" [class.minimized]="minimized">
<span></span>
<sm-router-tab-nav-bar
[links]="links"
[splitSize]="splitSize"
></sm-router-tab-nav-bar>
<ng-content select="[refresh]"></ng-content>
</nav>
</div>

View File

@@ -1,31 +1,17 @@
@import "variables";
$output-tabs-height: 40px;
nav {
height: $output-tabs-height;
position: relative;
text-align: center;
.tab-nav {
display: grid;
grid-template-columns: 200px 1fr 200px;
border-bottom: 1px solid #efefef;
padding: 0 48px 0 24px;
.refresh-position {
position: absolute;
right: 16px;
top: 6px;
display: flex;
align-items: center;
&.minimized {
grid-template-columns: 0 1fr 60px;
}
.refreshIcon{
margin-right: 10px;
}
span.disabled {
pointer-events: none;
}
}
.mat-menu-item {
padding-left: 22px;
&.active {
border-left: 6px solid $purple;
padding-left: 16px;
@media(max-width: 1200px) {
grid-template-columns: 0 1fr 200px;
}
}
sm-router-tab-nav-bar {
overflow: hidden;
}

View File

@@ -1,38 +1,38 @@
import {Component, Input} from '@angular/core';
import {FeaturesEnum} from '~/business-logic/model/users/featuresEnum';
import {selectRouterConfig} from '@common/core/reducers/router-reducer';
import {Store} from '@ngrx/store';
import {ExperimentInfoState} from '../../reducers/experiment-info.reducer';
import {Observable} from 'rxjs';
import {ChangeDetectionStrategy, Component, Input} from '@angular/core';
import {Link} from '@common/shared/components/router-tab-nav-bar/router-tab-nav-bar.component';
@Component({
selector: 'sm-experiment-info-navbar',
templateUrl: './experiment-info-navbar.component.html',
styleUrls: ['./experiment-info-navbar.component.scss']
styleUrls: ['./experiment-info-navbar.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ExperimentInfoNavbarComponent {
public featuresEnum = FeaturesEnum;
public routerConfig$: Observable<string[]>;
public baseInfoRoute: string[];
public overflow: boolean;
private _minimized: boolean;
links = [
{name: 'execution', url: ['execution']},
{name: 'configuration', url: ['hyper-params', 'hyper-param', '_empty_'], activeBy: 'hyper-params'},
{name: 'artifacts', url: ['artifacts']},
{name: 'info', url: ['general']},
{name: 'console', url: ['log'], output: true},
{name: 'scalars', url: ['metrics','scalar'], output: true},
{name: 'plots', url: ['metrics','plots'], output: true},
{name: 'debug samples', url: ['debugImages'], output: true},
] as Link[];
@Input() set minimized(minimized: boolean) {
this.baseInfoRoute = minimized ? ['info-output'] : [];
this.links = this.links.map(link => ({
...link,
url: (link as any).output ? this.baseInfoRoute.concat(link.url) : link.url
}));
this._minimized = minimized;
}
get minimized() {
get minimized(){
return this._minimized;
}
@Input() splitSize: number;
constructor(private store: Store<ExperimentInfoState>) {
this.routerConfig$ = this.store.select(selectRouterConfig);
}
navbarOverflowed($event: boolean) {
this.overflow = $event;
}
}

View File

@@ -16,7 +16,7 @@ import {CommonLayoutModule} from '@common/layout/layout.module';
import {DebugImagesModule} from '@common/debug-images/debug-images.module';
import {ExperimentInfoExecutionComponent} from '@common/experiments/containers/experiment-info-execution/experiment-info-execution.component';
import {MatSidenavModule} from '@angular/material/sidenav';
import {MatLegacyListModule as MatListModule} from '@angular/material/legacy-list';
import {MatListModule} from '@angular/material/list';
import {ExperimentOutputComponent} from './containers/experiment-ouptut/experiment-output.component';
import {ExperimentInfoNavbarComponent} from './containers/experiment-info-navbar/experiment-info-navbar.component';
import {ExperimentInfoHyperParametersComponent} from '@common/experiments/containers/experiment-info-hyper-parameters/experiment-info-hyper-parameters.component';
@@ -34,7 +34,6 @@ import {ExperimentArtifactsNavbarComponent} from '@common/experiments/dumb/exper
import {ExperimentInfoArtifactsComponent} from '@common/experiments/containers/experiment-info-aritfacts/experiment-info-artifacts.component';
import {ExperimentInfoHeaderComponent} from '@common/experiments/dumb/experiment-info-header/experiment-info-header.component';
import {ExperimentInfoTaskModelComponent} from '@common/experiments/containers/experiment-info-task-model/experiment-info-task-model.component';
import {ModelAutoPopulateDialogComponent} from '@common/experiments/dumb/model-auto-populate-dialog/model-auto-populate-dialog.component';
import {ExperimentOutputScalarsComponent} from '@common/experiments/containers/experiment-output-scalars/experiment-output-scalars.component';
import {ExperimentInfoModelComponent} from '@common/experiments/containers/experiment-info-model/experiment-info-model.component';
import {ExperimentInfoHyperParametersFormContainerComponent} from '@common/experiments/containers/experiment-info-hyper-parameters-form-container/experiment-info-hyper-parameters-form-container.component';
@@ -50,6 +49,8 @@ import {MAT_AUTOCOMPLETE_SCROLL_STRATEGY} from '@angular/material/autocomplete';
import {scrollFactory} from '@common/shared/utils/scroll-factory';
import {Overlay} from '@angular/cdk/overlay';
import {ExperimentsComponent} from '@common/experiments/experiments.component';
import {RouterTabNavBarComponent} from '@common/shared/components/router-tab-nav-bar/router-tab-nav-bar.component';
import {MatTabsModule} from '@angular/material/tabs';
@NgModule({
@@ -80,6 +81,9 @@ import {ExperimentsComponent} from '@common/experiments/experiments.component';
MatProgressSpinnerModule,
MatRadioModule,
ExperimentSharedModule,
RouterTabNavBarComponent,
MatTabsModule,
RouterTabNavBarComponent,
],
declarations: [
ExperimentsComponent,
@@ -97,7 +101,6 @@ import {ExperimentsComponent} from '@common/experiments/experiments.component';
ExperimentOutputModelViewComponent,
ExperimentExecutionSourceCodeComponent,
ExperimentOutputScalarsComponent,
ModelAutoPopulateDialogComponent,
ExperimentInfoHyperParametersComponent,
ExperimentInfoHyperParametersFormContainerComponent,
ExperimentArtifactsNavbarComponent,

View File

@@ -1,6 +1,6 @@
import {ActionReducerMap, createSelector} from '@ngrx/store';
import {experimentInfoReducer, ExperimentInfoState, initialState as infoInitialState} from './experiment-info.reducer';
import {experimentOutputReducer, ExperimentOutputState, experimentOutputInitState} from '@common/experiments/reducers/experiment-output.reducer';
import {experimentOutputReducer, ExperimentOutputState, experimentOutputInitState, ExperimentSettings} from '@common/experiments/reducers/experiment-output.reducer';
import {experimentsViewReducer, ExperimentsViewState, experimentsViewInitialState} from '@common/experiments/reducers/experiments-view.reducer';
import {IExperimentInfo} from '../shared/experiment-info.model';
import {TaskStatusEnum} from '~/business-logic/model/tasks/taskStatusEnum';
@@ -59,3 +59,7 @@ export const selectExperimentFormValidity = createSelector(selectExperimentInfoD
return !error;
});
export const selectSelectedModelSettings = createSelector(experimentOutput, selectSelectedModel,
(output, currentModel): ExperimentSettings =>
output.settingsList && output.settingsList.find((setting) => currentModel && setting.id === currentModel.id));

View File

@@ -4,16 +4,13 @@ import {SMSharedModule} from '@common/shared/shared.module';
import {ExperimentConverterService} from './services/experiment-converter.service';
import { ExperimentMenuComponent } from '@common/experiments/shared/components/experiment-menu/experiment-menu.component';
import {ExperimentMenuExtendedComponent} from '../containers/experiment-menu-extended/experiment-menu-extended.component';
import {GetParamMetricValuePipe} from '@common/experiments/dumb/experiments-table/hyper-param-metric-column/get-param-metric-value.pipe';
import {ExperimentHeaderComponent} from '@common/experiments/dumb/experiment-header/experiment-header.component';
import {SelectHyperParamsForCustomColComponent} from '@common/experiments/dumb/select-hyper-params-for-custom-col/select-hyper-params-for-custom-col.component';
import {ExperimentExecutionParametersComponent} from '@common/experiments/dumb/experiment-execution-parameters/experiment-execution-parameters.component';
import {CloneDialogComponent} from '@common/experiments/shared/components/clone-dialog/clone-dialog.component';
import {HyperParamMetricColumnComponent} from '@common/experiments/dumb/experiments-table/hyper-param-metric-column/hyper-param-metric-column.component';
import {ExperimentSystemTagsComponent} from '@common/experiments/shared/components/experiments-system-tags/experiment-system-tags.component';
import {AbortAllChildrenDialogComponent} from '@common/experiments/shared/components/abort-all-children-dialog/abort-all-children-dialog.component';
import {ExperimentsTableComponent} from '@common/experiments/dumb/experiments-table/experiments-table.component';
import {GetVariantWithoutRoundPipe} from '@common/experiments/dumb/experiments-table/hyper-param-metric-column/get-variant-without-round.pipe';
import {ChangeProjectDialogComponent} from '@common/experiments/shared/components/change-project-dialog/change-project-dialog.component';
import {ExperimentOutputPlotsComponent} from '@common/experiments/containers/experiment-output-plots/experiment-output-plots.component';
import {ExperimentCustomColsMenuComponent} from '@common/experiments/dumb/experiment-custom-cols-menu/experiment-custom-cols-menu.component';
@@ -41,6 +38,8 @@ import {EXPERIMENTS_OUTPUT_PREFIX} from '@common/experiments/actions/common-expe
import {EXPERIMENTS_INFO_PREFIX} from '@common/experiments/actions/common-experiments-info.actions';
import {experimentsReducers} from '~/features/experiments/reducers';
import {CommonExperimentConverterService} from '@common/experiments/shared/services/common-experiment-converter.service';
import {HyperParamMetricColumnComponent} from '@common/experiments/shared/components/hyper-param-metric-column/hyper-param-metric-column.component';
import {LabeledFormFieldDirective} from '@common/shared/directive/labeled-form-field.directive';
export const experimentSyncedKeys = [
'view.projectColumnsSortOrder',
@@ -89,9 +88,6 @@ const DECLARATIONS = [
AbortAllChildrenDialogComponent,
ExperimentExecutionParametersComponent,
ExperimentsTableComponent,
HyperParamMetricColumnComponent,
GetParamMetricValuePipe,
GetVariantWithoutRoundPipe,
ExperimentHeaderComponent,
ExperimentCustomColsMenuComponent,
SelectHyperParamsForCustomColComponent,
@@ -117,6 +113,8 @@ const DECLARATIONS = [
MatProgressSpinnerModule,
ScrollingModule,
CommonLayoutModule,
HyperParamMetricColumnComponent,
LabeledFormFieldDirective,
],
declarations : [...DECLARATIONS],
providers : [

View File

@@ -5,14 +5,14 @@ import { CommonModule } from '@angular/common';
import { LoginRoutingModule } from './login-routing.module';
import { MatLegacyAutocompleteModule as MatAutocompleteModule } from '@angular/material/legacy-autocomplete';
import {MatLegacyProgressSpinnerModule as MatProgressSpinnerModule} from '@angular/material/legacy-progress-spinner';
import {MatLegacyCheckboxModule as MatCheckboxModule} from '@angular/material/legacy-checkbox';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
import {MatCheckboxModule} from '@angular/material/checkbox';
import {SignupComponent} from './signup/signup.component';
import {MatLegacyFormFieldModule as MatFormFieldModule} from '@angular/material/legacy-form-field';
import {MatLegacySelectModule as MatSelectModule} from '@angular/material/legacy-select';
import {MatLegacyInputModule as MatInputModule} from '@angular/material/legacy-input';
import {MatLegacyRadioModule as MatRadioModule} from '@angular/material/legacy-radio';
import {MatFormFieldModule} from '@angular/material/form-field';
import {MatSelectModule} from '@angular/material/select';
import {MatInputModule} from '@angular/material/input';
import {MatRadioModule} from '@angular/material/radio';
import {LoginComponent} from '@common/login/login/login.component';
import {SharedPipesModule} from '@common/shared/pipes/shared-pipes.module';
import {NtkmeButtonModule} from '@ctrl/ngx-github-buttons';

View File

@@ -3,7 +3,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NestedProjectViewPageExtendedComponent } from './nested-project-view-page-extended.component';
import {StoreModule} from '@ngrx/store';
import {RouterTestingModule} from '@angular/router/testing';
import {MatLegacyDialogModule as MatDialogModule} from '@angular/material/legacy-dialog';
import {MatDialogModule} from '@angular/material/dialog';
describe('PipelinesPageComponent', () => {
let component: NestedProjectViewPageExtendedComponent;

View File

@@ -1,4 +1,4 @@
import {EntityTypeEnum} from "~/shared/constants/non-common-consts";
import {EntityTypeEnum} from '~/shared/constants/non-common-consts';
export enum EntityTypePluralEnum {
pipelines = 'pipelines',
@@ -6,13 +6,13 @@ export enum EntityTypePluralEnum {
reports = 'reports',
}
export const getEntityTypeFromUrlConf = (conf: string[]): string => {
return conf[0];
};
export const getEntityTypeFromUrlConf = (conf: string[]) => conf[0] as EntityTypePluralEnum;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const getDatasetUrlPrefix = (entityType)=> 'simple';
export const getNestedEntityBaseUrl = (entityType) => entityType;
export const isDatasetType = (entityType) => entityType === EntityTypePluralEnum.datasets ;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const datasetLabel = (entityType) => 'DATASETS' ;
export const getNestedEntityName = (entityType: string): EntityTypeEnum => {

View File

@@ -1,9 +1,15 @@
import {RouterModule, Routes} from '@angular/router';
import {NgModule} from '@angular/core';
import {CommonProjectsPageComponent} from '../../webapp-common/projects/containers/projects-page/common-projects-page.component';
import {CommonProjectsPageComponent} from '@common/projects/containers/projects-page/common-projects-page.component';
import {CrumbTypeEnum} from '@common/layout/breadcrumbs/breadcrumbs.component';
export const routes: Routes = [
{path: '', component: CommonProjectsPageComponent}
{path: '', component: CommonProjectsPageComponent, data: {
staticBreadcrumb: [[{
name: 'PROJECTS',
type: CrumbTypeEnum.Feature
}]]
}}
];

View File

@@ -1,5 +1,5 @@
import {Component, Inject} from '@angular/core';
import { MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA } from '@angular/material/legacy-dialog';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
import {Store} from '@ngrx/store';
import {updateCredentialLabel} from '@common/core/actions/common-auth.actions';
import {OrganizationGetUserCompaniesResponseCompanies} from '~/business-logic/model/organization/organizationGetUserCompaniesResponseCompanies';

View File

@@ -1,5 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { MatLegacySlideToggleChange as MatSlideToggleChange } from '@angular/material/legacy-slide-toggle';
import { MatSlideToggleChange } from '@angular/material/slide-toggle';
import {Store} from '@ngrx/store';
import {selectAllowed} from '~/core/reducers/usage-stats.reducer';
import {Observable} from 'rxjs';

View File

@@ -6,7 +6,7 @@ import {filter, take} from 'rxjs/operators';
import {createCredential, credentialRevoked, getAllCredentials, resetCredential, updateCredentialLabel} from '@common/core/actions/common-auth.actions';
import {Store} from '@ngrx/store';
import {GetCurrentUserResponseUserObject} from '~/business-logic/model/users/getCurrentUserResponseUserObject';
import {MatLegacyDialog as MatDialog} from '@angular/material/legacy-dialog';
import {MatDialog} from '@angular/material/dialog';
import {CreateCredentialDialogComponent} from '~/features/settings/containers/admin/create-credential-dialog/create-credential-dialog.component';
@Component({

View File

@@ -4,6 +4,13 @@ import {ProfileNameComponent} from '@common/settings/admin/profile-name/profile-
import {WebappConfigurationComponent} from '@common/settings/webapp-configuration/webapp-configuration.component';
import {WorkspaceConfigurationComponent} from '@common/settings/workspace-configuration/workspace-configuration.component';
import {SettingsComponent} from './settings.component';
import {CrumbTypeEnum} from '@common/layout/breadcrumbs/breadcrumbs.component';
const settingsBreadcrumb = {
name: 'Settings',
url: 'settings',
type: CrumbTypeEnum.Feature
};
const routes: Routes = [
{
@@ -17,14 +24,27 @@ const routes: Routes = [
},
{path: 'profile',
component: ProfileNameComponent,
data: {
staticBreadcrumb:[[settingsBreadcrumb, {
name: 'Profile',
type: CrumbTypeEnum.SubFeature
}]]},
},
{
path: 'webapp-configuration',
component: WebappConfigurationComponent,
data: {workspaceNeutral: true, staticBreadcrumb:[[settingsBreadcrumb, {
name: 'Configuration',
type: CrumbTypeEnum.SubFeature
}]]},
},
{
path: 'workspace-configuration',
component: WorkspaceConfigurationComponent,
data: {workspaceNeutral: true, staticBreadcrumb:[[settingsBreadcrumb, {
name: 'Workspace',
type: CrumbTypeEnum.SubFeature
}]]},
}
]
}

View File

@@ -24,6 +24,7 @@ import {CreateCredentialDialogComponent} from '~/features/settings/containers/ad
import {RedactedArgumentsDialogComponent} from '@common/settings/admin/redacted-arguments-dialog/redacted-arguments-dialog.component';
import {LayoutModule} from '~/layout/layout.module';
import {SharedPipesModule} from '@common/shared/pipes/shared-pipes.module';
import {LabeledFormFieldDirective} from '@common/shared/directive/labeled-form-field.directive';
@@ -56,6 +57,7 @@ import {SharedPipesModule} from '@common/shared/pipes/shared-pipes.module';
MatExpansionModule,
FormsModule,
LayoutModule,
LabeledFormFieldDirective,
SharedPipesModule
],
exports: [

View File

@@ -1,10 +1,10 @@
<div class="navbar-header-container">
<div class="header-container">
<div class="nav-bar-items-container">
<span [routerLink]="['workers']" routerLinkActive #rlaWorkers="routerLinkActive">
<span [routerLink]="['workers']" routerLinkActive="active" #rlaWorkers="routerLinkActive">
<sm-navbar-item direction="top" header="workers" [active]="rlaWorkers.isActive"></sm-navbar-item>
</span>
<span [routerLink]="['queues']" routerLinkActive #rlaQueues="routerLinkActive">
<span [routerLink]="['queues']" routerLinkActive="active" #rlaQueues="routerLinkActive">
<sm-navbar-item direction="top" header="queues" [active]="rlaQueues.isActive"></sm-navbar-item>
</span>
</div>
@@ -16,4 +16,4 @@
</div>
</div>
</div>
<router-outlet></router-outlet>
<router-outlet class="outlet"></router-outlet>

View File

@@ -0,0 +1,39 @@
import {RouterModule, Routes} from '@angular/router';
import {NgModule} from '@angular/core';
import {OrchestrationComponent} from '@common/workers-and-queues/orchestration.component';
import {WorkersComponent} from '@common/workers-and-queues/containers/workers/workers.component';
import {QueuesComponent} from '@common/workers-and-queues/containers/queues/queues.component';
import {WorkersAndQueuesResolver} from '~/shared/resolvers/workers-and-queues.resolver';
import {CrumbTypeEnum} from '@common/layout/breadcrumbs/breadcrumbs.component';
const wQBreadcrumb = [[{
name: 'WORKERS AND QUEUES',
type: CrumbTypeEnum.Feature
}]];
export const routes: Routes = [
{
path: '',
component: OrchestrationComponent,
resolve: {
queuesManager: WorkersAndQueuesResolver
},
children: [
{path: '', redirectTo: 'workers', pathMatch: 'full'},
{path: 'workers', component: WorkersComponent, data: {staticBreadcrumb: wQBreadcrumb}},
{
path: 'queues',
component: QueuesComponent,
data: {staticBreadcrumb: wQBreadcrumb, queuesManager: true}
},
]
}
];
@NgModule({
imports: [
RouterModule.forChild(routes)
],
exports: [RouterModule]
})
export class WorkersAndQueuesRoutingModule {
}

View File

@@ -1,8 +1,8 @@
import {NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {CommonModule} from '@angular/common';
import {WorkersAndQueuesRoutingModule} from '@common/workers-and-queues/workers-and-queues-routing.module';
import {WorkersAndQueuesComponent} from '@common/workers-and-queues/workers-and-queues.component';
import {WorkersAndQueuesRoutingModule} from './workers-and-queues-routing.module';
import {OrchestrationComponent} from '@common/workers-and-queues/orchestration.component';
import {WorkersComponent} from '@common/workers-and-queues/containers/workers/workers.component';
import {QueuesComponent} from '@common/workers-and-queues/containers/queues/queues.component';
import {SMSharedModule} from '@common/shared/shared.module';
@@ -41,7 +41,7 @@ import {QueuesMenuExtendedComponent} from './queues-menu-extended/queues-menu-ex
FormsModule,
],
declarations: [
WorkersAndQueuesComponent,
OrchestrationComponent,
WorkersComponent,
WorkersTableComponent,
WorkersStatsComponent,

View File

@@ -20,22 +20,22 @@
routerLinkActive="active">
</a>
<a class="item al-ico-datasets"
routerLink="/datasets"
routerLinkActive="active"
[routerLink]="(defaultNestedModeForFeature$ | async)?.['datasets']? '/datasets/simple/*/projects' : '/datasets'"
[class.active]="(baseRouteConfig$ | async) ==='datasets'"
smTooltip="DATASETS"
[matTooltipShowDelay]="0"
matTooltipPosition="right">
</a>
<a class="item al-ico-pipelines"
routerLink="/pipelines"
routerLinkActive="active"
[routerLink]="(defaultNestedModeForFeature$ | async)?.['pipelines']? '/pipelines/*/projects' : '/pipelines'"
[class.active]="(baseRouteConfig$ | async) ==='pipelines'"
smTooltip="PIPELINES"
[matTooltipShowDelay]="0"
matTooltipPosition="right">
</a>
<a class="item al-ico-reports"
routerLink="/reports"
routerLinkActive="active"
[routerLink]="(defaultNestedModeForFeature$ | async)?.['reports']? '/reports/*/projects' : '/reports'"
[class.active]="(baseRouteConfig$ | async) ==='reports'"
smTooltip="REPORTS"
[matTooltipShowDelay]="0"
matTooltipPosition="right">

View File

@@ -1,10 +1,12 @@
import {selectCurrentUser} from '@common/core/reducers/users-reducer';
import {AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, ViewChild} from '@angular/core';
import {Store} from '@ngrx/store';
import {selectSelectedProjectId} from '@common/core/reducers/projects.reducer';
import {selectDefaultNestedModeForFeature, selectSelectedProjectId} from '@common/core/reducers/projects.reducer';
import {fromEvent, Observable} from 'rxjs';
import {ConfigurationService} from '@common/shared/services/configuration.service';
import {searchDeactivate} from '@common/dashboard-search/dashboard-search.actions';
import {selectRouterConfig} from '@common/core/reducers/router-reducer';
import {map} from 'rxjs/operators';
@Component({
selector : 'sm-side-nav',
@@ -17,13 +19,17 @@ export class SideNavComponent implements AfterViewInit {
currentUser: any;
environment = ConfigurationService.globalEnvironment;
public scrolling: boolean;
public defaultNestedModeForFeature$: Observable<{ [p: string]: boolean }>;
public baseRouteConfig$: Observable<string>;
@ViewChild('container') container: ElementRef<HTMLDivElement>;
constructor(public store: Store<any>, private cdr: ChangeDetectorRef) {
this.selectedProjectId$ = this.store.select(selectSelectedProjectId);
this.defaultNestedModeForFeature$ = this.store.select(selectDefaultNestedModeForFeature);
this.store.select(selectCurrentUser).subscribe((res) => this.currentUser = res);
this.baseRouteConfig$ = this.store.select(selectRouterConfig).pipe(map(conf => conf?.[0]));
fromEvent(window, 'resize').subscribe(() => {
const scrolling = this.container.nativeElement.scrollHeight > this.container.nativeElement.clientHeight;

View File

@@ -43,3 +43,5 @@ export const EXPERIMENTS_STATUS_LABELS = {
[TaskTypeEnum.Qc] : 'Qc',
[TaskTypeEnum.Custom] : 'Custom'
};
export const hideDeleteArtifactsEntities = [EntityTypeEnum.model];

View File

@@ -3,4 +3,15 @@ import {HTTP} from '~/app.constants';
export const isFileserverUrl = (url: string) =>
url.startsWith(HTTP.FILE_BASE_URL);
export const convertToReverseProxy = (url: string) => url;
export const convertToReverseProxy = (url: string) => {
try {
const u = new URL(url);
if (!u.pathname.startsWith('/files')) {
u.protocol = window.location.protocol;
u.host = window.location.host;
u.pathname = 'files' + u.pathname;
return u.toString();
}
} catch {}
return url;
};

View File

@@ -12,11 +12,10 @@ import {NotifierConfigToken, NotifierService} from './services/notifier.service'
/**
* Injection Token for notifier options
*/
export const NotifierOptionsToken: InjectionToken<NotifierOptions>
export const notifierOptionsToken: InjectionToken<NotifierOptions>
= new InjectionToken<NotifierOptions>('[angular-notifier] Notifier Options');
/**
* Factory for a notifier configuration with custom options
*
@@ -26,9 +25,7 @@ export const NotifierOptionsToken: InjectionToken<NotifierOptions>
* @param options - Custom notifier options
* @returns - Notifier configuration as result
*/
export function notifierCustomConfigFactory(options: NotifierOptions): NotifierConfig {
return new NotifierConfig(options);
}
export const notifierCustomConfigFactory = (options: NotifierOptions): NotifierConfig => new NotifierConfig(options);
/**
* Factory for a notifier configuration with default options
@@ -38,9 +35,7 @@ export function notifierCustomConfigFactory(options: NotifierOptions): NotifierC
*
* @returns - Notifier configuration as result
*/
export function notifierDefaultConfigFactory(): NotifierConfig {
return new NotifierConfig({});
}
export const notifierDefaultConfigFactory = (): NotifierConfig => new NotifierConfig({});
/**
* Notifier module
@@ -84,14 +79,14 @@ export class NotifierModule {
// Provide the options itself upfront (as we need to inject them as dependencies -- see below)
{
provide: NotifierOptionsToken,
provide: notifierOptionsToken,
useValue: options
},
// Provide a custom notifier configuration, based on the given notifier options
{
deps: [
NotifierOptionsToken
notifierOptionsToken
],
provide: NotifierConfigToken,
useFactory: notifierCustomConfigFactory

View File

@@ -19,7 +19,6 @@ $notifier-shadow-color: rgba(0, 0, 0, .2) !default;
}
.notifier__notification {
&-message {
display: inline-block;
margin: { // Reset paragraph default styles
@@ -31,6 +30,8 @@ $notifier-shadow-color: rgba(0, 0, 0, .2) !default;
font-size: 15px;
.message{
white-space: pre-line;
overflow: hidden;
text-overflow: ellipsis;
}
.user-action {

View File

@@ -3,7 +3,7 @@
@font-face {
font-family: '#{$icomoon-font-family}';
src: url('./#{$icomoon-font-family}.ttf?i5mliw') format('truetype');
src: url('./#{$icomoon-font-family}.ttf?s0lnuq') format('truetype');
font-weight: normal;
font-style: normal;
font-display: block;
@@ -24,6 +24,11 @@
-moz-osx-font-smoothing: grayscale;
}
.al-ico-info-circle-outline {
&:before {
content: $al-ico-info-circle-outline;
}
}
.al-ico-ghost {
&:before {
content: $al-ico-ghost;

View File

@@ -1,6 +1,7 @@
$icomoon-font-family: "trains" !default;
$icomoon-font-path: "fonts" !default;
$al-ico-info-circle-outline: "\e9f0";
$al-ico-ghost: "\e9ef";
$al-ico-flat-view: "\e9ee";
$al-ico-camera: "\e9ed";

View File

@@ -1,14 +1,3 @@
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg">
<g>
<title>background</title>
<rect fill="none" id="canvas_background" height="26" width="26" y="-1" x="-1"/>
</g>
<g>
<title>Layer 1</title>
<g id="svg_1" fill-rule="evenodd" fill="none">
<path id="svg_2" d="m5.874997,0.125l18,0l0,18l-18,0l0,-18z"/>
<path id="svg_3" opacity="0.3" d="m17.874997,6.125c-0.148,0.11 -0.315,0 -0.75,0l-10.5,0c-0.435,0 -0.602,0.11 -0.75,0c-0.045,0.565 -0.006,0.764 0,0.75l4.5,4.5l0,3.75c-0.134,0.42 -0.08,0.548 0,0.75l2.25,2.25c0.058,-0.054 0.186,0 0,0c0.407,0 0.478,-0.014 0.75,0c0.023,-0.14 0.134,-0.307 0,-0.75l0,-6l4.5,-4.5c0.006,0.014 0.045,-0.185 0,-0.75z" fill-rule="nonzero" fill="#8492C2"/>
</g>
</g>
<path d="m17.83,6.85l-4.33,4.57v6.06c0,.42-.48.65-.81.39l-2.19-1.75v-4.7l-4.33-4.57c-.36-.31-.1-.85.4-.85h10.86c.5,0,.76.54.4.85Z" fill="#8492c2"/>
</svg>

Before

Width:  |  Height:  |  Size: 730 B

After

Width:  |  Height:  |  Size: 221 B

View File

@@ -3,6 +3,7 @@ import {ExtFrame} from '@common/shared/single-graph/plotly-graph-base';
import {DebugSample} from '@common/shared/debug-sample/debug-sample.reducer';
import {ReportsApiMultiplotsResponse} from '@common/clearml-applications/report-widgets/src/app/app.reducer';
import {Task} from '~/business-logic/model/tasks/task';
import {SingleValueTaskMetrics} from '~/business-logic/model/reports/singleValueTaskMetrics';
export const getPlot = createAction('[App] getPlot', props<{
tasks: string[];
@@ -10,6 +11,7 @@ export const getPlot = createAction('[App] getPlot', props<{
metrics: string[];
variants: string[];
company: string;
models: boolean;
otherSearchParams?: URLSearchParams;
}>());
@@ -19,6 +21,7 @@ export const getScalar = createAction('[App] getScalar', props<{
metrics: string[];
variants: string[];
company: string;
models: boolean;
otherSearchParams?: URLSearchParams;
}>());
export const getSample = createAction('[App] getSample', props<{
@@ -37,11 +40,23 @@ export const getParcoords = createAction('[App] getParcoords', props<{
company: string;
otherSearchParams?: URLSearchParams;
}>());
export const getSingleValues = createAction('[App] getSingleValues', props<{
tasks: string[];
company: string;
models: boolean;
otherSearchParams?: URLSearchParams;
}>());
export const setPlotData = createAction('[App] setPlot', props<{ data: ReportsApiMultiplotsResponse }>());
export const setScalarData = createAction('[App] setScalar', props<{ data: ExtFrame[] }>());
export const setSampleData = createAction('[App] setSample', props<{ data: DebugSample }>());
export const setSingleValues = createAction('[App] setSingleValues', props<{ data: SingleValueTaskMetrics }>());
export const setParallelCoordinateExperiments = createAction('[App] setParcoor', props<{ data: Task[] }>());
export const reportsPlotlyReady = createAction('[App] plotly ready');
export const setSignIsNeeded = createAction('[App] set sign is needed');
export const setNoPermissions = createAction('[App] set no permissions');
export const setTaskData = createAction('[App] set task data', props<{
sourceProject: string;
sourceTasks: string[];
appId?: string;
}>());

View File

@@ -2,6 +2,12 @@
<ng-container *ngIf="(signIsNeeded$ | async) === false; else signIsNeededTemplate">
<ng-container *ngIf="(noPermissions$ | async) === false; else noPermissionsTemplate">
<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="#8693be"/>
</svg>
<span class="webapp-link_tooltip">View original resource</span>
</a>
<ng-container [ngSwitch]="type">
<ng-container *ngSwitchCase="type === 'plot' || type === 'scalar' ? type : ''">
<sm-single-graph
@@ -33,6 +39,7 @@
(imageClicked)="hideMaximize === 'show' && sampleClicked($event)">
</sm-debug-image-snippet>
</ng-container>
<ng-container *ngSwitchCase="'parcoords'">
<sm-parallel-coordinates-graph
*ngIf="parcoordsData"
@@ -44,6 +51,15 @@
[reportMode]="true"
></sm-parallel-coordinates-graph>
</ng-container>
<ng-container *ngSwitchCase="'single'">
<sm-single-value-summary-table
*ngIf="singleValueData && singleValueData[0]"
[data]="singleValueData"
[experimentName]="singleValueData[0]?.metric"
[darkTheme]="isDarkTheme"
class="single-value-summary-table"></sm-single-value-summary-table>
</ng-container>
</ng-container>
</ng-container>

View File

@@ -67,3 +67,59 @@
padding: 24px;
// min-height: 150px;
}
.webapp-link {
position: absolute;
display: block;
width: 16px;
height: 16px;
top: 26px;
left: 21px;
cursor: pointer;
z-index: 2;
visibility: hidden;
&:hover {
svg path {
fill: $blue-100;
}
}
}
:host {
&:hover .webapp-link {
visibility: visible;
}
}
.webapp-link_tooltip {
display:none;
position: absolute;
left: 0;
top: 24px;
z-index: 1;
padding: 6px 8px;
font-size: 12px;
text-align: left;
color: white;
background-color: #6B7488;
text-decoration: none;
border-radius: 3px;
white-space: nowrap;
}
a.webapp-link:hover .webapp-link_tooltip {
display: block;
}
.webapp-link.dark-theme {
svg path {
fill: $blue-300;
}
&:hover {
svg path {
fill: $blue-400;
}
}
}

View File

@@ -1,10 +1,10 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, OnInit, ViewChild} from '@angular/core';
import {Store} from '@ngrx/store';
import {MatLegacyDialog as MatDialog} from '@angular/material/legacy-dialog';
import {Observable} from 'rxjs';
import {MatDialog} from '@angular/material/dialog';
import {Observable, withLatestFrom} from 'rxjs';
import {filter, map, switchMap, take} from 'rxjs/operators';
import {Environment} from '../environments/base';
import {getParcoords, getPlot, getSample, getScalar, reportsPlotlyReady} from './app.actions';
import {getParcoords, getPlot, getSample, getScalar, getSingleValues, reportsPlotlyReady} from './app.actions';
import {
ReportsApiMultiplotsResponse,
selectNoPermissions,
@@ -13,6 +13,8 @@ import {
selectReportsPlotlyReady,
selectSampleData,
selectSignIsNeeded,
selectSingleValuesData,
selectTaskData,
State
} from './app.reducer';
import {ExtFrame} from '@common/shared/single-graph/plotly-graph-base';
@@ -30,9 +32,10 @@ import {setCurrentDebugImage} from '@common/shared/debug-sample/debug-sample.act
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 {EventsGetTaskSingleValueMetricsResponseValues} from '~/business-logic/model/events/eventsGetTaskSingleValueMetricsResponseValues';
type WidgetTypes = 'plot' | 'scalar' | 'sample' | 'parcoords';
type WidgetTypes = 'plot' | 'scalar' | 'sample' | 'parcoords' | 'single';
@Component({
selector: 'sm-app-root',
@@ -59,6 +62,8 @@ export class AppComponent implements OnInit {
public externalTool: boolean = false;
public parcoordsData: { experiments: ExtraTask[]; params: string[]; metric: SelectedMetric; valueType: MetricValueType };
@ViewChild(SingleGraphComponent) 'singleGraph': SingleGraphComponent;
public singleValueData: EventsGetTaskSingleValueMetricsResponseValues[];
public webappLink: string;
@HostListener('window:resize')
onResize() {
@@ -77,6 +82,7 @@ export class AppComponent implements OnInit {
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);
this.singleGraphHeight = window.innerHeight;
this.otherSearchParams = this.getOtherSearchParams();
this.isDarkTheme = !this.searchParams.get('light');
@@ -91,7 +97,7 @@ export class AppComponent implements OnInit {
}
private getOtherSearchParams() {
const paramsToRemove = ['light', 'type', 'tasks', 'metrics', 'variants', 'iterations', 'company', 'value_type'];
const paramsToRemove = ['light', 'type', 'tasks', 'models', 'objects', 'objectType', 'metrics', 'variants', 'iterations', 'company', 'value_type'];
const otherSearchParams = new URLSearchParams(window.location.search);
paramsToRemove.forEach(key => {
otherSearchParams.delete(key);
@@ -123,6 +129,9 @@ export class AppComponent implements OnInit {
break;
case 'parcoords':
this.getParallelCoordinate();
break;
case 'single':
this.getSingleValues();
}
window.addEventListener('message', (e) => {
@@ -134,6 +143,15 @@ export class AppComponent implements OnInit {
this.hideMaximize = 'disabled';
}
});
this.store.select(selectTaskData)
.pipe(
filter(taskData => !!taskData),
take(1))
.subscribe(({sourceProject, sourceTasks, appId}) => {
this.webappLink = appId ? this.buildAppLink(sourceTasks, appId) : this.buildSourceLink(this.searchParams, sourceProject, sourceTasks);
this.cdr.detectChanges();
});
}
/// Merging all variants of same metric to same graph. (single experiment)
@@ -169,7 +187,7 @@ export class AppComponent implements OnInit {
const merged = this.mergeVariants(metricsPlots as ReportsApiMultiplotsResponse);
this.plotData = Object.values(merged)[0].plotParsed;
} else {
const {merged, } = prepareMultiPlots(metricsPlots);
const {merged,} = prepareMultiPlots(metricsPlots);
const newGraphs = convertMultiPlots(merged);
this.plotData = Object.values(newGraphs)[0]?.[0];
}
@@ -264,14 +282,31 @@ export class AppComponent implements OnInit {
});
}
activate = () => {
this.type !== 'sample' && loadExternalLibrary(this.store, this.environment.plotlyURL, reportsPlotlyReady);
private getSingleValues() {
this.store.select(selectSingleValuesData)
.pipe(
filter(singleValueData => !!singleValueData),
take(1)
).subscribe(singleValueData => {
this.singleValueData = singleValueData.values;
this.cdr.detectChanges();
});
}
activate = async () => {
this.activated = true;
await this.waitForVisibility();
this.singleGraphHeight = window.innerHeight;
!['sample', 'single'].includes(this.type) && loadExternalLibrary(this.store, this.environment.plotlyURL, reportsPlotlyReady);
const models = this.searchParams.has('models') || this.searchParams.get('objectType') === 'model';
const objects = this.searchParams.getAll('objects');
const queryParams = {
tasks: this.searchParams.getAll('tasks'),
tasks: objects.length > 0 ? objects : this.searchParams.getAll('tasks'),
metrics: this.searchParams.getAll('metrics'),
variants: this.searchParams.getAll('variants'),
iterations: this.searchParams.getAll('iterations').map(iteration => parseInt(iteration, 10)),
company: this.searchParams.get('company') || '',
models
};
switch (this.type) {
@@ -286,8 +321,11 @@ export class AppComponent implements OnInit {
break;
case 'parcoords':
this.store.dispatch(getParcoords({...queryParams, otherSearchParams: this.otherSearchParams}));
break;
case 'single':
this.store.dispatch(getSingleValues({...queryParams, otherSearchParams: this.otherSearchParams}));
break;
}
this.activated = true;
};
@@ -339,4 +377,80 @@ export class AppComponent implements OnInit {
const lastMetric = get(experimentWithCurrentMetric.last_metrics, metric) as ExtraTask['last_metrics'];
return `${lastMetric.metric}/${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');
let entityIds = objects.length > 0? objects : searchParams.getAll(isModels ? 'models' : 'tasks');
if (entityIds.length === 0 && tasks?.length > 0) {
entityIds = tasks;
}
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.join(',')}/${this.getComparePath(this.type)}`;
} else {
url += `${isModels ? 'models/' : 'experiments/'}${entityIds}/${this.getOutputPath(isModels, this.type)}`;
}
return url;
}
private getOutputPath(isModels: boolean, type: WidgetTypes) {
if (isModels) {
switch (type) {
case 'single':
case 'scalar':
return 'scalars';
case 'plot':
return 'plots';
}
} else {
switch (type) {
case 'single':
case 'scalar':
return 'info-output/metrics/scalar';
case 'plot':
return 'info-output/metrics/plots';
case 'sample':
return 'info-output/debugImages';
}
}
}
private getComparePath(type: WidgetTypes) {
switch (type) {
case 'single':
case 'scalar':
return 'scalars/graph';
case 'plot':
return 'metrics-plots';
case 'parcoords':
return 'hyper-params/graph';
case 'sample':
return 'debug-images';
}
}
private buildAppLink(sourceTasks: string[], appId) {
const isAutoscaler = appId.includes('autoscaler');
return `${window.location.origin.replace('4201', '4200')}/${isAutoscaler ? 'workers-and-queues/autoscalers' : 'applications'}/${appId}/info;experimentId=${sourceTasks[0]}?instancesFilter=All`;
}
private waitForVisibility(): Promise<boolean> {
return new Promise(resolve => {
if (window.innerHeight > 0) {
return resolve(true);
}
const observer = new IntersectionObserver(entities => {
if (entities[0].intersectionRatio > 0) {
resolve(true);
observer.disconnect();
}
});
observer.observe(document.body);
});
}
}

View File

@@ -4,11 +4,11 @@ import {
getParcoords,
getPlot,
getSample,
getScalar,
getScalar, getSingleValues,
setNoPermissions, setParallelCoordinateExperiments,
setPlotData,
setSampleData,
setSignIsNeeded
setSignIsNeeded, setSingleValues, setTaskData
} from './app.actions';
import {EMPTY, mergeMap, of, switchMap} from 'rxjs';
import {Store} from '@ngrx/store';
@@ -50,6 +50,8 @@ export class AppEffects {
switchMap(action => this.httpClient.post<{ data: ReportsGetTaskDataResponse }>(`${this.basePath}/reports.get_task_data?${action.otherSearchParams.toString()}`,
{
id: action.tasks,
// eslint-disable-next-line @typescript-eslint/naming-convention
model_events: action.models,
plots: {
iters: 1,
metrics: action.metrics.map(metric => ({metric, variants: action.variants}))
@@ -57,7 +59,13 @@ export class AppEffects {
},
{headers: this.getHeaders(action.company)}
)),
mergeMap((res) => [setPlotData({data: res.data.plots as unknown as ReportsApiMultiplotsResponse})]),
mergeMap((res) => [
setPlotData({data: res.data.plots as unknown as ReportsApiMultiplotsResponse}),
setTaskData({
sourceProject: (res.data.tasks[0]?.project as any).id,
sourceTasks: res.data.tasks.map(t => t.id),
appId: (res.data.tasks[0] as any)?.application?.app_id?.id
})]),
catchError(error => [requestFailed(error), ...(error.status === 403 ? [setNoPermissions()] : [])])
));
@@ -67,6 +75,8 @@ export class AppEffects {
{
id: action.tasks,
// eslint-disable-next-line @typescript-eslint/naming-convention
model_events: action.models,
// eslint-disable-next-line @typescript-eslint/naming-convention
scalar_metrics_iter_histogram: {
metrics: action.metrics.map(metric => ({metric, variants: action.variants}))
}
@@ -74,7 +84,12 @@ export class AppEffects {
{headers: this.getHeaders(action.company)}
).pipe(
mergeMap(res => [
setPlotData({data: res.data.scalar_metrics_iter_histogram as ReportsApiMultiplotsResponse})]
setPlotData({data: res.data.scalar_metrics_iter_histogram as ReportsApiMultiplotsResponse}),
setTaskData({
sourceProject: (res.data.tasks[0]?.project as any).id,
sourceTasks: res.data.tasks.map(t => t.id),
appId: (res.data.tasks[0] as any)?.application?.app_id?.id
})]
), catchError(error => [requestFailed(error), ...(error.status === 403 ? [setNoPermissions()] : [])])
)
)
@@ -94,26 +109,46 @@ export class AppEffects {
{headers: this.getHeaders(action.company)}
).pipe(
mergeMap(res => [
setSampleData({data: res.data.debug_images?.[0]?.iterations?.[0]?.events[0] as DebugSample})
]),
setSampleData({data: res.data.debug_images?.[0]?.iterations?.[0]?.events[0] as DebugSample}),
setTaskData({sourceProject: (res.data.tasks[0]?.project as any).id, sourceTasks: res.data.tasks.map(t => t.id)})]),
catchError(error => [requestFailed(error), ...(error.status === 403 ? [setNoPermissions()] : [])])
)
))
);
getExperiments$ = createEffect(() => this.actions$.pipe(
getParcoords$ = createEffect(() => this.actions$.pipe(
ofType(getParcoords),
mergeMap((action) => this.httpClient.post<{data: ReportsGetTaskDataResponse}>(`${this.basePath}/reports.get_task_data?${action.otherSearchParams.toString()}`, {
mergeMap((action) => this.httpClient.post<{ data: ReportsGetTaskDataResponse }>(`${this.basePath}/reports.get_task_data?${action.otherSearchParams.toString()}`, {
id: action.tasks,
// eslint-disable-next-line @typescript-eslint/naming-convention
only_fields: ['last_metrics', 'name', 'last_iteration', ...action.variants.map(variant => `hyperparams.${variant}`)]
})
.pipe(
mergeMap(res => [setParallelCoordinateExperiments({data: res.data.tasks as unknown as Task[]})])
mergeMap(res => [
setParallelCoordinateExperiments({data: res.data.tasks as unknown as Task[]}),
setTaskData({sourceProject: (res.data.tasks[0]?.project as any).id, sourceTasks: res.data.tasks.map(t => t.id)})
])
)
))
);
getScalarSingleValue$ = createEffect(() => this.actions$.pipe(
ofType(getSingleValues),
switchMap((action) => this.httpClient.post<{ data: ReportsGetTaskDataResponse }>(`${this.basePath}/reports.get_task_data?${action.otherSearchParams.toString()}`, {
id: action.tasks,
// eslint-disable-next-line @typescript-eslint/naming-convention
model_events: action.models,
// eslint-disable-next-line @typescript-eslint/naming-convention
single_value_metrics: {}
})
),
mergeMap((res: { data: ReportsGetTaskDataResponse }) => [
setSingleValues({data: res.data.single_value_metrics[0]}),
setTaskData({sourceProject: (res.data.tasks[0]?.project as any).id, sourceTasks: res.data.tasks.map(t => t.id)})
]
)
));
signUrl = createEffect(() => this.actions$.pipe(
ofType(getSignedUrl),
filter(action => !!action.url),

View File

@@ -8,7 +8,7 @@ import {AppEffects} from './app.effects';
import {StoreModule} from '@ngrx/store';
import {appReducer} from './app.reducer';
import {HttpClientModule} from '@angular/common/http';
import {MatLegacyDialogModule as MatDialogModule} from '@angular/material/legacy-dialog';
import {MatDialogModule} from '@angular/material/dialog';
import {ChooseColorModule} from '@common/shared/ui-components/directives/choose-color/choose-color.module';
import {SingleGraphModule} from '@common/shared/single-graph/single-graph.module';
import {DebugSampleModule} from '@common/shared/debug-sample/debug-sample.module';
@@ -20,6 +20,7 @@ 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_', '{}');
@@ -29,19 +30,20 @@ if (!localStorage.getItem('_saved_state_')) {
declarations: [
AppComponent
],
imports: [
BrowserAnimationsModule,
BrowserModule,
HttpClientModule,
MatDialogModule,
ChooseColorModule,
SingleGraphModule,
DebugSampleModule,
ParallelCoordinatesGraphComponent,
StoreModule.forRoot({appReducer, auth: authReducer}),
EffectsModule.forRoot([AppEffects]),
...extCoreModules
],
imports: [
BrowserAnimationsModule,
BrowserModule,
HttpClientModule,
MatDialogModule,
ChooseColorModule,
SingleGraphModule,
DebugSampleModule,
ParallelCoordinatesGraphComponent,
StoreModule.forRoot({appReducer, auth: authReducer}),
EffectsModule.forRoot([AppEffects]),
...extCoreModules,
SingleValueSummaryTableComponent
],
providers: [ApiEventsService, ApiReportsService, SmApiRequestsService, ColorHashService, BaseAdminService],
bootstrap: [AppComponent]
})

View File

@@ -1,11 +1,17 @@
import {createReducer, createSelector, on} from '@ngrx/store';
import {reportsPlotlyReady, setNoPermissions, setParallelCoordinateExperiments, setPlotData, setSampleData, setSignIsNeeded} from './app.actions';
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';
import {MetricValueType, SelectedMetric} from '@common/experiments-compare/experiments-compare.constants';
import {Task} from '~/business-logic/model/tasks/task';
import {SingleValueTaskMetrics} from '~/business-logic/model/reports/singleValueTaskMetrics';
export interface ParCoords {
metric: SelectedMetric;
valueType: MetricValueType;
parameters: string[];
}
export interface ParCoords {metric: SelectedMetric; valueType: MetricValueType; parameters: string[]}
export interface ReportsApiMultiplotsResponse {
[metric: string]: {
[variant: string]: {
@@ -25,21 +31,29 @@ export const appFeatureKey = 'app';
export interface State {
plotData: MetricsPlotEvent[] | ReportsApiMultiplotsResponse;
sampleData: DebugSample;
singleValuesData: SingleValueTaskMetrics;
parallelCoordinateData: Task[];
scaleFactor: number;
plotlyReady: boolean;
signIsNeeded: boolean;
noPermissions: boolean;
taskData: {
sourceProject: string;
sourceTasks: string[];
appId: string;
};
}
export const initialState: State = {
plotData: null,
sampleData: null,
singleValuesData: null,
parallelCoordinateData: null,
scaleFactor: 100,
plotlyReady: false,
signIsNeeded: false,
noPermissions: false
noPermissions: false,
taskData: null
};
export const appReducer = createReducer(
@@ -47,9 +61,13 @@ export const appReducer = createReducer(
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 selectFeature = state => state.appReducer as State;
@@ -58,6 +76,12 @@ export const selectScaleFactor = createSelector(selectFeature, state => state.sc
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);

View File

@@ -2,7 +2,40 @@
// For more information: https://material.angular.io/guide/theming
@use '../../../../../../node_modules/@angular/material/index' as mat;
// Plus imports for other components in your app.
$neon-yellow: #d3ff00;
$white-primary-text: rgba(white, 0.87);
$sm-neon: (
50: lighten($neon-yellow, 30%),
100: lighten($neon-yellow, 25%),
200: lighten($neon-yellow, 20%),
300: lighten($neon-yellow, 15%),
400: lighten($neon-yellow, 10%),
500: $neon-yellow,
600: darken($neon-yellow, 10%),
700: darken($neon-yellow, 15%),
800: darken($neon-yellow, 20%),
900: darken($neon-yellow, 25%),
A100: $neon-yellow,
A200: darken($neon-yellow, 5%),
A400: darken($neon-yellow, 10%),
A700: darken($neon-yellow, 15%),
contrast: (
50: $white-primary-text,
100: $white-primary-text,
200: $white-primary-text,
300: $white-primary-text,
400: white,
500: white,
600: white,
700: white,
800: white,
900: white,
A100: white,
A200: white,
A400: white,
A700: white,
)
);
// Include the common styles for Angular Material. We include this here so that you only
// have to load a single css file for Angular Material in your app.
// Be sure that you only ever include this mixin once!
@@ -14,9 +47,14 @@
$theme-primary: mat.define-palette(mat.$indigo-palette);
$theme-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);
$sm-neon-palette-primary: mat.define-palette($sm-neon);
$sm-neon-palette-accent: mat.define-palette($sm-neon, A200, A100, A400);
// The warn palette is optional (defaults to red).
$theme-warn: mat.define-palette(mat.$red-palette);
$font-family-base: 'Heebo', sans-serif; // Assumes the browser default, typically `16px`
$custom-typography: mat.define-typography-config(
$font-family: $font-family-base
);
// Create the theme object. A theme consists of configurations for individual
// theming systems such as "color" or "typography".
$theme: mat.define-light-theme((
@@ -24,13 +62,26 @@ $theme: mat.define-light-theme((
primary: $theme-primary,
accent: $theme-accent,
warn: $theme-warn,
)
),
typography: $custom-typography,
density: -2
));
$sm-neon-theme: mat.define-dark-theme((
color: (
primary: $sm-neon-palette-primary,
accent: $sm-neon-palette-accent,
),
typography: $custom-typography,
density: -2
));
// Include theme styles for core and each component used in your app.
// Alternatively, you can import and @include the theme mixins for each component
// that you are using.
@include mat.dialog-theme($theme);
@include mat.slider-theme($sm-neon-theme);
@include mat.form-field-theme($theme);
@import "src/app/webapp-common/shared/ui-components/styles/variables";
@@ -68,7 +119,7 @@ body {
}
}
.mat-dialog-container {
.mat-mdc-dialog-container {
padding: 0 !important;
}
@@ -76,6 +127,10 @@ body {
display: flex;
}
.align-items-center {
align-items: center;
}
.h-100 {
height: 100%;
}
@@ -182,6 +237,107 @@ body {
cursor: default !important;
}
sm-debug-image-snippet {
.image-var {
top: 8px;
bottom: unset;
}
}
.dark-theme .mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field__input {
color: rgba(255, 255, 255, 0.87);
}
.dark-theme {
.mdc-text-field--outlined:not(.mdc-text-field--disabled) {
.mdc-notched-outline__leading, .mdc-notched-outline__notch, .mdc-notched-outline__trailing {
border-color: $blue-500;
border-width: 1px;
}
&:not(.mdc-text-field--focused):hover .mdc-notched-outline {
.mdc-notched-outline__leading, .mdc-notched-outline__notch, .mdc-notched-outline__trailing {
border-color: $blue-450;
}
}
&.mdc-text-field--focused {
.mdc-notched-outline__leading, .mdc-notched-outline__notch, .mdc-notched-outline__trailing {
border-color: $blue-400;
}
}
}
}
.dark-theme, .light-theme {
.mat-mdc-form-field {
--mdc-typography-body1-font-size: 14px;
--mdc-typography-body1-line-height: 16px;
&.mat-form-field-appearance-outline {
&.black {
background-color: #000;
.mat-mdc-select-value, .mat-mdc-select-arrow {
color: $blue-200;
}
}
.mat-mdc-text-field-wrapper {
.mat-mdc-form-field-flex {
height: 100%;
.mat-mdc-form-field-infix {
min-height: 100%;
padding: 5px 0;
.mat-mdc-input-element {
height: 26px;
}
}
.mat-mdc-form-field-icon-suffix {
line-height: 24px;
}
}
}
}
&.mat-form-field-appearance-fill {
.mdc-text-field--filled:not(.mdc-text-field--disabled) {
background-color: transparent;
.mat-mdc-input-element {
background-color: transparent;
}
}
&.mat-focused {
.mat-mdc-form-field-focus-overlay {
opacity: 5%;
}
}
}
}
}
.mat-mdc-form-field.smooth-input {
height: 36px;
}
.label-text.smoothing-text {
color: $blue-200;
}
// hide arrows for number inputs
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
-webkit-appearance: none !important;
margin: 0 !important;
}
// hide arrows for number inputs (FireFox)
input[type=number] {
-moz-appearance: textfield !important;
}
/* width */
::-webkit-scrollbar {

View File

@@ -12,94 +12,104 @@
@import "shared/ui-components/styles/material-palette";
@import "assets/fonts/trains-icons.scss";
@import "layout/layout";
@import "shared/ui-components/styles/overrides/viewer-iterations-slider";
// TODO(v15): As of v15 mat.legacy-core no longer includes default typography styles.
// The following line adds:
// 1. Default typography styles for all components
// 2. Styles for typography hierarchy classes (e.g. .mat-headline-1)
// If you specify typography styles for the components you use elsewhere, you should delete this line.
// If you don't need the default component typographies but still want the hierarchy styles,
// you can delete this line and instead use:
// `@include mat.legacy-typography-hierarchy(mat.define-legacy-typography-config());`
@include mat.all-legacy-component-typographies();
@include mat.legacy-core();
//@import "../webapp-common/shared/ui-components/styles/material-theme.scss";
$custom-typography: mat.define-legacy-typography-config(
$font-family: $font-family-base
@include mat.core();
$custom-typography: mat.define-typography-config(
$font-family: $font-family-base
);
@include mat.all-legacy-component-typographies($custom-typography);
@include mat.typography-hierarchy($custom-typography);
@include mat.all-component-typographies($custom-typography);
$allegro-theme-primary: mat.define-palette($mat-allegro);
$allegro-theme-accent: mat.define-palette($mat-allegro, A400, A100, A400);
$custom-theme-primary: mat.define-palette(mat.$green-palette);
$custom-theme-accent: mat.define-palette(mat.$lime-palette, A400, A100, A400);
$custom-theme-warn: mat.define-palette(mat.$purple-palette);
$allegro-palette-primary: mat.define-palette($mat-allegro);
$allegro-palette-accent: mat.define-palette($mat-allegro, A400, A100, A400);
$sm-theme-primary: mat.define-palette($sm-purple);
$sm-theme-accent: mat.define-palette($sm-purple, A400, A100, A400);
$sm-theme-warn: mat.define-palette(mat.$purple-palette);
$sm-palette-primary: mat.define-palette($sm-purple);
$sm-palette-accent: mat.define-palette($sm-purple, A100, A200, A400);
$sm-palette-warn: mat.define-palette(mat.$purple-palette);
$green-theme: mat.define-light-theme((color: (
primary: $custom-theme-primary,
accent: $custom-theme-accent,
warn: $custom-theme-warn
)));
$dark-theme: mat.define-dark-theme((color: (
primary: $allegro-theme-primary,
accent: $allegro-theme-accent
)));
$light-theme: mat.define-light-theme((
$sm-neon-palette-primary: mat.define-palette($sm-neon);
$sm-neon-palette-accent: mat.define-palette($sm-neon, A200, A100, A400);
$dark-theme: mat.define-dark-theme((
color: (
primary: $allegro-theme-primary,
accent: $allegro-theme-accent
primary: $allegro-palette-primary,
accent: $allegro-palette-accent
),
typography: $custom-typography,
density: -2
));
$sm-theme: mat.define-light-theme((color: (
primary: $sm-theme-primary,
accent: $sm-theme-accent,
warn: $sm-theme-warn
)));
$light-theme: mat.define-light-theme((
color: (
primary: $allegro-palette-primary,
accent: $allegro-palette-accent
),
typography: $custom-typography,
));
@include mat.legacy-form-field-color($light-theme);
@include mat.legacy-progress-spinner-theme($light-theme);
@include mat.legacy-progress-bar-theme($light-theme);
$sm-theme: mat.define-light-theme((
color: (
primary: $sm-palette-primary,
accent: $sm-palette-accent,
warn: $sm-palette-warn
),
typography: $custom-typography
));
$sm-neon-theme: mat.define-dark-theme((
color: (
primary: $sm-neon-palette-primary,
accent: $sm-neon-palette-accent,
),
typography: $custom-typography
));
//@include mat.form-field-theme($light-theme);
@include mat.progress-spinner-theme($light-theme);
@include mat.progress-bar-theme($light-theme);
@include mat.datepicker-color($light-theme);
@include mat.tooltip-theme($sm-theme);
@include mat.menu-theme($light-theme);
@include mat.autocomplete-theme($light-theme);
@include mat.select-theme($light-theme);
.dark-outline {
@include mat.menu-color($dark-theme);
@include mat.autocomplete-color($dark-theme);
@include mat.select-color($dark-theme);
}
.dark-theme {
@include mat.legacy-core-theme($dark-theme);
@include mat.legacy-button-theme($dark-theme);
@include mat.legacy-slide-toggle-color($green-theme);
@include mat.legacy-radio-color($green-theme);
@include mat.legacy-select-color($dark-theme);
@include mat.legacy-menu-color($light-theme);
@include mat.legacy-autocomplete-color($light-theme);
@include mat.pseudo-checkbox-color($light-theme);
@include mat.divider-color($light-theme);
// @include mat.all-component-themes($dark-theme);
@include mat.core-theme($dark-theme);
@include mat.form-field-theme($dark-theme);
@include mat.input-theme($dark-theme);
@include mat.dialog-theme($dark-theme);
@include mat.chips-theme($dark-theme);
@include mat.button-theme($dark-theme);
@include mat.fab-theme($dark-theme);
@include mat.icon-button-theme($dark-theme);
@include mat.slide-toggle-theme($sm-neon-theme);
@include mat.slider-theme($sm-neon-theme);
@include mat.radio-color($dark-theme);
@include mat.divider-color($dark-theme);
@include mat.checkbox-theme($dark-theme);
@include mat.list-theme($dark-theme);
--mdc-typography-body1-letter-spacing: 0;
--mdc-typography-button-letter-spacing: 0;
.mat-checkbox-frame,
.mat-radio-outer-circle {
border-color: $blue-400;
.mat-mdc-checkbox .mdc-checkbox {
--mdc-checkbox-selected-icon-color: #{$purple};
--mdc-checkbox-selected-hover-icon-color: #{$purple};
--mdc-checkbox-selected-focus-icon-color: #{$purple};
}
.mat-expansion-panel-header-description, .mat-expansion-indicator:after {
color: $white;
}
.light-theme .mat-radio-button.mat-radio-disabled .mat-radio-outer-circle,
.light-theme .mat-radio-button.mat-radio-disabled .mat-radio-label-content {
border-color: $blue-200 !important;
color: $blue-200 !important;
}
.light-theme .mat-radio-button.mat-radio-disabled .mat-radio-inner-circle {
background-color: transparent !important;
}
.link {
&:hover {
text-decoration: underline;
@@ -109,50 +119,34 @@ $sm-theme: mat.define-light-theme((color: (
}
.light-theme {
@include mat.legacy-core-color($light-theme);
@include mat.legacy-button-color($light-theme);
@include mat.legacy-slide-toggle-color($sm-theme);
// @include mat.radio-color($sm-theme);
@include mat.legacy-radio-theme($sm-theme);
// @include mat.select-color($light-theme);
@include mat.legacy-select-theme($light-theme);
@include mat.legacy-menu-color($light-theme);
@include mat.legacy-autocomplete-color($light-theme);
@include mat.pseudo-checkbox-color($light-theme);
@include mat.core-color($light-theme);
@include mat.form-field-color($light-theme);
@include mat.input-color($light-theme);
@include mat.dialog-color($light-theme);
@include mat.chips-color($light-theme);
@include mat.button-color($light-theme);
@include mat.fab-color($light-theme);
@include mat.icon-button-color($light-theme);
@include mat.slide-toggle-color($sm-theme);
@include mat.slider-color($sm-theme);
@include mat.radio-color($sm-theme);
@include mat.divider-color($light-theme);
@include mat.checkbox-color($sm-theme);
@include mat.list-color($light-theme);
--mdc-typography-body1-letter-spacing: 0;
--mdc-typography-button-letter-spacing: 0;
mat-progress-bar {
border-radius: 4px;
box-shadow: 0 0 0 1px $white, 0 0 0 3px lighten($purple, 30%);
--mdc-linear-progress-active-indicator-color: #{lighten($purple, 10%)};
--mdc-linear-progress-track-height: 12px;
}
.mat-progress-bar-fill::after {
background-color: lighten($purple, 10%);
}
.mat-checkbox-frame,
.mat-radio-outer-circle {
border-color: $purple;
}
.mat-radio-button.mat-accent.disabled, .mat-radio-button.mat-accent.disabled.mat-radio-checked {
.mat-radio-outer-circle, .mat-radio-inner-circle {
border-color: $blue-300;
}
.mat-radio-inner-circle {
background-color: $blue-300;
}
}
.light-theme .mat-radio-button.mat-radio-disabled .mat-radio-outer-circle,
.light-theme .mat-radio-button.mat-radio-disabled .mat-radio-label-content {
border-color: $blue-200 !important;
color: $blue-200 !important;
}
.light-theme .mat-radio-button.mat-radio-disabled .mat-radio-inner-circle {
background-color: transparent !important;
.mat-mdc-radio-button, .mat-mdc-radio-button.mat-accent {
--mdc-radio-disabled-selected-icon-color: #{$blue-300};
--mdc-radio-disabled-unselected-icon-color: #{$blue-300};
}
.mat-drawer {
@@ -163,6 +157,18 @@ $sm-theme: mat.define-light-theme((color: (
color: $blue-300;
}
.mat-mdc-nav-list {
--mdc-list-list-item-label-text-size: 13px;
--mdc-list-list-item-label-text-weight: 500;
--mdc-list-list-item-label-text-color: #{$blue-400};
--mdc-list-list-item-selected-label-text-color: white;
.mat-mdc-list-item.selected {
.mdc-list-item__primary-text {
color: white;
}
}
}
}
* {
@@ -249,33 +255,33 @@ hr {
}
}
// fix for calendar v15 mix with v14
.dark-theme, .light-theme {
.mat-datepicker-content {
.mat-mdc-icon-button.mat-mdc-button-base {
width: 40px;
height: 40px;
padding: 8px;
}
.mat-calendar-body-selected {
background-color: $blue-200;
}
.mat-mdc-button:not(:disabled) {
color: $blue-700;
}
.mat-calendar-body-label, .mat-calendar-period-button {
font-size: 14px;
font-weight: 500;
}
.mat-calendar-hidden-label {
display: none;
}
}
}
//// fix for calendar v15 mix with v14
//.dark-theme, .light-theme {
// .mat-datepicker-content {
// .mat-mdc-icon-button.mat-mdc-button-base {
// width: 40px;
// height: 40px;
// padding: 8px;
// }
//
// .mat-calendar-body-selected {
// background-color: $blue-200;
// }
//
// .mat-mdc-button:not(:disabled) {
// color: $blue-700;
// }
//
// .mat-calendar-body-label, .mat-calendar-period-button {
// font-size: 14px;
// font-weight: 500;
// }
//
// .mat-calendar-hidden-label {
// display: none;
// }
// }
//}
mat-expansion-panel {
box-shadow: unset;
@@ -309,31 +315,28 @@ mat-expansion-panel {
background: $faint-gray !important;
}
.mat-slide-toggle-label {
display: flex !important;
}
.mat-mdc-radio-button.sm {
--mdc-radio-state-layer-size: 16px;
// Cancel bootstrap css on material component
mat-radio-group mat-radio-button label.mat-radio-label {
display: inline-flex;
margin-bottom: 0;
}
mat-radio-button.sm {
.mat-radio-container,
.mat-radio-outer-circle,
.mat-radio-inner-circle {
width: 16px;
.mdc-radio {
height: 16px;
}
.mat-radio-input {
height: auto;
.mdc-radio__background {
width: 16px;
height: 16px;
.mdc-radio__inner-circle {
top: -2px;
left: -2px;
}
}
.mat-radio-ripple{
.mat-radio-ripple {
height: 32px;
width: 32px;
left: calc(50% - 16px);
top: calc(50% - 16px);
left: calc(50% - 18px);
top: calc(50% - 18px);
border-radius: 100%;
}
}
@@ -357,12 +360,10 @@ button {
.background-neon-green {
background-color: $neon-green !important;
background: $neon-green !important;
}
.background-neon-yellow {
background-color: $neon-yellow !important;
background: $neon-yellow !important;
}
.color-neon-green {
@@ -388,6 +389,7 @@ button {
justify-content: center;
height: 100%;
width: 100%;
margin-top: 50px;
}
.empty-menu {
@@ -510,24 +512,27 @@ body .clean-list {
list-style-type: none;
}
.mat-tooltip {
.mat-mdc-tooltip {
&.sm-tooltip {
background-color: $purple;
box-shadow: 0 -2px 8px 0 rgba(0, 0, 0, 0.2);
font-family: 'Heebo', sans-serif;
font-size: 11px;
line-height: 1.55;
letter-spacing: 0.3px;
color: #ffffff;
word-break: break-word;
max-width: 400px;
--mdc-plain-tooltip-container-color: #{$purple};
.mdc-tooltip__surface {
max-width: 400px;
font-size: 11px;
line-height: 1.55;
letter-spacing: 0.3px;
}
&.validation {
background-color: #ff001f;
.mdc-tooltip__surface {
--mdc-plain-tooltip-container-color: #{$strong-red};
}
}
&.break-line {
white-space: pre-line;
.mdc-tooltip__surface {
white-space: pre-line;
}
}
}
@@ -539,7 +544,7 @@ body .clean-list {
// --------------------------------old------------------------------------------
@import "shared/ui-components/styles/index";
.mat-dialog-container {
.mat-mdc-dialog-container {
box-shadow: none !important;
background-color: transparent !important;
}
@@ -561,15 +566,21 @@ html {
overflow: hidden;
}
.mat-menu-panel {
max-width: none;
.mat-mdc-menu-panel.mat-mdc-menu-panel {
max-width: 450px;
min-width: 114px;
min-height: 32px;
&.custom-columns {
width: 370px;
}
sm-checkbox-three-state-list {
min-width: 160px;
display: block;
}
}
.ico-chk {
width: 24px;
display: inline-block;
@@ -583,24 +594,55 @@ html {
background: $blue-25;
color: $blue-400;
border-bottom: 1px solid $blue-200;
font-size: 14px;
}
.mat-menu-content {
.mat-menu-item {
.light-theme {
.mat-mdc-menu-content {
.mat-mdc-menu-item {
.mdc-list-item__primary-text {
--mdc-list-list-item-label-text-color: rgba(0, 0, 0, 0.87);
}
}
}
}
.mat-mdc-menu-content {
padding: 4px 0 !important;
.mat-mdc-menu-item {
margin: 0 4px;
width: calc(100% - 8px);
height: 40px;
min-height: 40px;
--mdc-typography-body1-line-height: 20px;
font-size: 14px;
--mdc-typography-body1-font-size: 14px;
padding: 0 32px 0 12px;
border-radius: 4px;
> .al-icon {
margin-right: 12px;
.mdc-list-item__primary-text {
--mdc-typography-body1-line-height: 20px;
display: flex;
align-items: center;
gap: 0 12px;
flex-grow: 1;
overflow: hidden;
text-overflow: ellipsis;
}
}
hr {
margin: 4px -4px;
margin: 4px 0;
}
}
.mat-mdc-list-item {
--mdc-list-list-item-label-text-size: 14px;
--mdc-list-list-item-label-text-line-height: 40px;
--mdc-list-list-item-one-line-container-height: 40px;
}
// hide arrows for number inputs
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
@@ -641,6 +683,21 @@ as-split {
}
}
.dark-theme .light-theme .as-horizontal > .as-split-gutter {
background-color: transparent !important;
.as-split-gutter-icon {
background-color: transparent !important;
border-left: solid 1px #DEE1E9;
border-right: none;
background-image: none !important;
&:hover {
border-left: $purple solid 2px;
}
}
}
.notifier__container {
ul {
margin: 0;
@@ -648,10 +705,10 @@ as-split {
}
$type-colors: (
string: #ff8400,
number: $neon-yellow,
boolean: #b938a4,
date: #05668D,
string: #ff8400,
number: $neon-yellow,
boolean: #b938a4,
date: #05668D,
);
@@ -688,25 +745,17 @@ $type-colors: (
}
}
.mat-checkbox label {
margin: 0;
display: inline-flex;
}
.image-viewer-dialog {
.mat-dialog-container {
.mat-mdc-dialog-container {
padding: 0;
border-radius: 0;
}
}
//material menu
body .mat-menu-content:not(:empty) {
padding: 4px;
body .mat-mdc-menu-content:not(:empty) {
.search-results {
overflow: auto;
max-width: 222px;
}
.fixed-options-subheader {
@@ -717,29 +766,6 @@ body .mat-menu-content:not(:empty) {
}
}
body .mat-menu-panel .mat-form-field.tags-menu-input {
.mat-form-field-wrapper {
padding: 0;
.mat-form-field-flex {
padding: 0 14px;
align-items: center;
.mat-form-field-infix, .mat-form-field-suffix {
display: flex;
align-items: center;
padding: 10px 0;
border: 0;
font-size: 14px;
}
}
.mat-form-field-underline {
bottom: unset;
}
}
}
.hyper-parameters-tooltip {
white-space: pre-line;
text-align: left !important;
@@ -823,7 +849,7 @@ button.btn.button-outline-dark {
.cdk-drag-preview.form-group-drag {
padding: 8px 16px 32px 16px;
border-radius: 4px;
border: solid 1px #d4d6e0;
border: solid 1px $cloudy-blue;
background-color: white;
}
@@ -885,3 +911,22 @@ button.btn.button-outline-dark {
padding: 0 !important;
}
}
.cml-dialog {
border-radius: 4px;
.mdc-dialog .mdc-dialog__content {
padding: 0 32px;
max-height: 90vh;
}
.mdc-dialog__actions {
padding: 24px 0;
justify-content: center;
}
}
.select-panel-width {
min-width: fit-content;
max-width: 50vw !important;
}

View File

@@ -108,3 +108,5 @@ export const MESSAGES_SEVERITY = {
INFO: 'info' as MessageSeverityEnum,
WARN: 'warn' as MessageSeverityEnum
};
export const rootProjectsPageSize = 50;

View File

@@ -4,7 +4,7 @@ import {ProjectsUpdateRequest} from '~/business-logic/model/projects/projectsUpd
import {ModelsPublishManyResponse} from '~/business-logic/model/models/modelsPublishManyResponse';
import {ModelsArchiveManyResponse} from '~/business-logic/model/models/modelsArchiveManyResponse';
import {ModelsDeleteManyResponse} from '~/business-logic/model/models/modelsDeleteManyResponse';
import {archivedSelectedModels} from '@common/models/actions/models-menu.actions';
import {archiveSelectedModels} from '@common/models/actions/models-menu.actions';
import {TasksResetManyResponse} from '~/business-logic/model/tasks/tasksResetManyResponse';
import {TasksEnqueueManyResponse} from '~/business-logic/model/tasks/tasksEnqueueManyResponse';
import {TasksArchiveManyResponse} from '~/business-logic/model/tasks/tasksArchiveManyResponse';
@@ -14,6 +14,8 @@ import {EntityTypeEnum} from '~/shared/constants/non-common-consts';
import {MetricColumn} from '@common/shared/utils/tableParamEncode';
import {ProjectStatsGraphData} from '@common/core/reducers/projects.reducer';
import {User} from '~/business-logic/model/users/user';
import {ProjectsGetAllResponseSingle} from '~/business-logic/model/projects/projectsGetAllResponseSingle';
import {TaskStatusEnum} from '~/business-logic/model/tasks/taskStatusEnum';
export const PROJECTS_PREFIX = '[ROOT_PROJECTS] ';
@@ -31,11 +33,6 @@ export const updateProject = createAction(
props<{ id: string; changes: Partial<ProjectsUpdateRequest> }>()
);
export const setAllProjects = createAction(
PROJECTS_PREFIX + 'SET_PROJECTS',
props<{ projects: Project[]; updating?: boolean }>()
);
export const resetProjects = createAction(PROJECTS_PREFIX + 'RESET_PROJECTS');
export const refetchProjects = createAction(PROJECTS_PREFIX + 'REFETCH_PROJECTS');
@@ -56,15 +53,16 @@ export const setSelectedProjectId = createAction(
PROJECTS_PREFIX + 'SET_SELECTED_PROJECT_ID',
props<{ projectId: string; example?: boolean }>()
);
export const deletedProjectFromRoot = createAction(
PROJECTS_PREFIX + 'DELETE_PROJECT_FROM_ROOT',
props<{ project: Project }>()
);
export const setSelectedProject = createAction(
PROJECTS_PREFIX + 'SET_SELECTED_PROJECT',
props<{ project: Project }>()
);
export const setProjectAncestors = createAction(
PROJECTS_PREFIX + 'SET_PROJECT_ANCESTORS',
props<{ projects: Project[] }>()
);
export const setSelectedProjectStats = createAction(
PROJECTS_PREFIX + '[set selected project statistics]',
props<{ project: Project }>()
@@ -99,7 +97,7 @@ export const getCompanyTags = createAction(
export const getProjectsTags = createAction(
PROJECTS_PREFIX + '[get projects tags]',
props<{entity: string}>()
props<{ entity: string }>()
);
export const setTagsFilterByProject = createAction(
@@ -119,7 +117,7 @@ export const setCompanyTags = createAction(
export const setMainPageTagsFilter = createAction(
PROJECTS_PREFIX + '[set main page tags filters]',
props<{ tags: string[] }>()
props<{ tags: string[]; feature: string }>()
);
export const setMainPageTagsFilterMatchMode = createAction(
@@ -134,7 +132,7 @@ export const addProjectTags = createAction(
export const openTagColorsMenu = createAction(
PROJECTS_PREFIX + '[open tag colors]',
props<{tags: string[]}>()
props<{ tags: string[] }>()
);
export const setTagColors = createAction(
@@ -144,7 +142,12 @@ export const setTagColors = createAction(
export const openMoreInfoPopup = createAction(
PROJECTS_PREFIX + '[open more info popup]',
props<{ parentAction: ReturnType<typeof archivedSelectedModels>; operationName: string; entityType: EntityTypeEnum; res: ModelsPublishManyResponse | ModelsArchiveManyResponse | ModelsDeleteManyResponse | TasksResetManyResponse | TasksEnqueueManyResponse | TasksArchiveManyResponse | TasksPublishManyResponse | TasksStopManyResponse }>()
props<{
parentAction: ReturnType<typeof archiveSelectedModels>;
operationName: string;
entityType: EntityTypeEnum;
res: ModelsPublishManyResponse | ModelsArchiveManyResponse | ModelsDeleteManyResponse | TasksResetManyResponse | TasksEnqueueManyResponse | TasksArchiveManyResponse | TasksPublishManyResponse | TasksStopManyResponse
}>()
);
export const setMetricVariant = createAction(
@@ -153,6 +156,11 @@ export const setMetricVariant = createAction(
);
export const fetchGraphData = createAction(PROJECTS_PREFIX + '[fetch stats for project graph]');
export const toggleState = createAction(
PROJECTS_PREFIX + '[toggle state]',
props<{ state: TaskStatusEnum }>()
);
export const setGraphData = createAction(
PROJECTS_PREFIX + '[set project stats]',
props<{ stats: ProjectStatsGraphData[] }>()
@@ -193,4 +201,16 @@ export const setDefaultNestedModeForFeature = createAction(
props<{ feature: string; isNested: boolean }>()
);
export const resetTablesFilterProjectsOptions = createAction(
PROJECTS_PREFIX + ' [reset tables filter projects options]'
);
export const getTablesFilterProjectsOptions = createAction(
PROJECTS_PREFIX + ' [get tables filter projects options]',
props<{ searchString: string; loadMore: boolean }>()
);
export const setTablesFilterProjectsOptions = createAction(
PROJECTS_PREFIX + ' [set tables filter projects options]',
props<{ projects: Partial<ProjectsGetAllResponseSingle>[]; scrollId: string; loadMore?: boolean }>()
);

View File

@@ -1,9 +1,12 @@
import {ISmAction} from '../models/actions';
import {NAVIGATION_PREFIX, NAVIGATION_ACTIONS} from '~/app.constants';
import {NAVIGATION_ACTIONS, NAVIGATION_PREFIX} from '~/app.constants';
import {Action, createAction, props} from '@ngrx/store';
import {Params} from '@angular/router';
import {FilterMetadata} from 'primeng/api/filtermetadata';
import {SortMeta} from 'primeng/api';
import {CrumbTypeEnum, IBreadcrumbsLink} from "@common/layout/breadcrumbs/breadcrumbs.component";
export const BREADCRUMBS_PREFIX = 'BREADCRUMBS_';
// TODO: remove this action...
@@ -14,23 +17,21 @@ export class NavigateTo implements ISmAction {
url: string;
params?: Params;
unGuard?: boolean;
}) {}
}) {
}
}
export class NavigationEnd implements Action {
readonly type = NAVIGATION_ACTIONS.NAVIGATION_END;
}
export class SetRouterSegments implements Action {
readonly type = NAVIGATION_ACTIONS.SET_ROUTER_SEGMENT;
constructor(public payload: {
export const setRouterSegments = createAction(
NAVIGATION_ACTIONS.SET_ROUTER_SEGMENT, props<{
url: string;
params: Params;
queryParams: Params;
config: string[];
}) {}
}
}>());
export const setURLParams = createAction(
NAVIGATION_PREFIX + 'SET_URL_PARAMS',
@@ -44,3 +45,14 @@ export const setURLParams = createAction(
version?: string;
}>()
);
export const setBreadcrumbs = createAction(
BREADCRUMBS_PREFIX + 'SET_BREADCRUMBS',
props<{ breadcrumbs: IBreadcrumbsLink[][]}>()
);
export const setTypeBreadcrumbs = createAction(
BREADCRUMBS_PREFIX + 'SET_TYPE_BREADCRUMBS',
props<{ breadcrumb: IBreadcrumbsLink; type?: CrumbTypeEnum }>()
);

View File

@@ -13,7 +13,7 @@ import {AdminService} from '~/shared/services/admin.service';
import {selectDontShowAgainForBucketEndpoint, selectS3BucketCredentialsBucketCredentials, selectSignedUrl} from '@common/core/reducers/common-auth-reducer';
import {EMPTY, of} from 'rxjs';
import {S3AccessResolverComponent} from '@common/layout/s3-access-resolver/s3-access-resolver.component';
import {MatLegacyDialog as MatDialog} from '@angular/material/legacy-dialog';
import {MatDialog} from '@angular/material/dialog';
import {setCredentialLabel} from '../actions/common-auth.actions';
import {SignResponse} from '@common/settings/admin/base-admin-utils';

View File

@@ -7,7 +7,7 @@ import {bufferTime, filter, map, mergeMap, switchMap, take} from 'rxjs/operators
import {ApiTasksService} from '~/business-logic/api-services/tasks.service';
import {EMPTY, Observable, of} from 'rxjs';
import {ApiModelsService} from '~/business-logic/api-services/models.service';
import {MatLegacyDialog as MatDialog, MatLegacyDialogRef as MatDialogRef} from '@angular/material/legacy-dialog';
import {MatDialog, MatDialogRef} from '@angular/material/dialog';
import {AlertDialogComponent} from '../../shared/ui-components/overlay/alert-dialog/alert-dialog.component';
import {NotifierService} from '../../angular-notifier';
import {requestFailed} from '@common/core/actions/http.actions';

View File

@@ -3,26 +3,23 @@ import {Store} from '@ngrx/store';
import {Actions, createEffect, ofType} from '@ngrx/effects';
import {ApiProjectsService} from '~/business-logic/api-services/projects.service';
import * as actions from '../actions/projects.actions';
import {setShowHidden} from '../actions/projects.actions';
import {catchError, expand, filter, map, mergeMap, reduce, switchMap, withLatestFrom} from 'rxjs/operators';
import {setProjectAncestors, setShowHidden, setTablesFilterProjectsOptions} from '../actions/projects.actions';
import {catchError, debounceTime, filter, map, mergeMap, switchMap, withLatestFrom} from 'rxjs/operators';
import {requestFailed} from '../actions/http.actions';
import {activeLoader, deactivateLoader, setServerError} from '../actions/layout.actions';
import {setSelectedModels} from '../../models/actions/models-view.actions';
import {TagColorMenuComponent} from '../../shared/ui-components/tags/tag-color-menu/tag-color-menu.component';
import {MatLegacyDialog as MatDialog} from '@angular/material/legacy-dialog';
import {MatDialog} from '@angular/material/dialog';
import {ApiOrganizationService} from '~/business-logic/api-services/organization.service';
import {OrganizationGetTagsResponse} from '~/business-logic/model/organization/organizationGetTagsResponse';
import {selectRouterParams} from '../reducers/router-reducer';
import {EMPTY, forkJoin, from, of} from 'rxjs';
import {EMPTY, forkJoin, of} from 'rxjs';
import {ProjectsGetTaskTagsResponse} from '~/business-logic/model/projects/projectsGetTaskTagsResponse';
import {ProjectsGetModelTagsResponse} from '~/business-logic/model/projects/projectsGetModelTagsResponse';
import {
selectAllProjectsUsers,
selectLastUpdate,
selectRootProjects,
selectAllProjectsUsers, selectProjectsOptionsScrollId,
selectSelectedMetricVariantForCurrProject,
selectSelectedProjectId,
selectShowHidden
selectSelectedProjectId, selectShowHidden,
} from '../reducers/projects.reducer';
import {
OperationErrorDialogComponent
@@ -33,30 +30,25 @@ import {ITask} from '~/business-logic/model/al-task';
import {TasksGetAllExRequest} from '~/business-logic/model/tasks/tasksGetAllExRequest';
import {setSelectedExperiments} from '../../experiments/actions/common-experiments-view.actions';
import {setActiveWorkspace} from '@common/core/actions/users.actions';
import {ProjectsGetAllExResponse} from '~/business-logic/model/projects/projectsGetAllExResponse';
import {Project} from '~/business-logic/model/projects/project';
import {ApiUsersService} from '~/business-logic/api-services/users.service';
import {get} from 'lodash-es';
import {escapeRegExp, get} from 'lodash-es';
import {escapeRegex} from '@common/shared/utils/escape-regex';
import {ProjectsGetAllExRequest} from '~/business-logic/model/projects/projectsGetAllExRequest';
import localForage from 'localforage';
import {TIME_IN_MILLI} from '@common/shared/utils/time-util';
import {ProjectsGetAllResponseSingle} from '~/business-logic/model/projects/projectsGetAllResponseSingle';
import {rootProjectsPageSize} from '@common/constants';
export const ALL_PROJECTS_OBJECT = {id: '*', name: 'All Experiments'};
interface RootCache {
time: string;
hidden: boolean;
projects: Project[];
}
@Injectable()
export class ProjectsEffects {
private pageSize: number = 1000;
constructor(
private actions$: Actions, private projectsApi: ApiProjectsService, private orgApi: ApiOrganizationService,
private store: Store<any>, private dialog: MatDialog, private tasksApi: ApiTasksService,
private usersApi: ApiUsersService,
) {}
) {
}
activeLoader = createEffect(() => this.actions$.pipe(
ofType(actions.setSelectedProjectId),
@@ -64,83 +56,92 @@ export class ProjectsEffects {
map(action => activeLoader(action.type))
));
getProjects$ = createEffect(() => this.actions$.pipe(
ofType(actions.getAllSystemProjects),
withLatestFrom(
this.store.select(selectShowHidden),
this.store.select(selectLastUpdate),
),
switchMap(([, showHidden, lastUpdate]) => !lastUpdate ? from(localForage.getItem<RootCache>('rootProjects'))
.pipe(
map((cache: RootCache) =>
[showHidden, lastUpdate,
(new Date(cache?.time)).getTime() > (new Date()).getTime() - TIME_IN_MILLI.ONE_HOUR &&
cache.projects?.length > 0 &&
showHidden === cache.hidden ? cache : null
])
) : of([showHidden, lastUpdate, null])
),
switchMap(([showHidden, lastUpdate, cache]: [boolean, string, RootCache]) => {
if (cache) {
return [
actions.setAllProjects({projects: cache.projects, updating: false}),
actions.setLastUpdate({lastUpdate: cache.time}),
actions.getAllSystemProjects()
];
}
const cacheTime = (new Date()).toISOString();
const query = {
/* eslint-disable @typescript-eslint/naming-convention */
scroll_id: null,
size: this.pageSize,
order_by: ['last_update'],
...(lastUpdate && {last_update: [lastUpdate, null]}),
only_fields: ['name', 'company', 'last_update'],
search_hidden: showHidden
/* eslint-enable @typescript-eslint/naming-convention */
} as ProjectsGetAllExRequest;
return this.projectsApi.projectsGetAllEx(query)
.pipe(
expand((res: ProjectsGetAllExResponse) => res.scroll_id && res.projects.length >= this.pageSize ?
this.projectsApi.projectsGetAllEx({
...query,
// eslint-disable-next-line @typescript-eslint/naming-convention
scroll_id: res.scroll_id,
}) :
EMPTY
),
reduce((acc, res: ProjectsGetAllExResponse) => acc.concat(res.projects), []),
withLatestFrom(this.store.select(selectRootProjects)),
mergeMap(([projects, rootProjects]) => [
actions.setLastUpdate({lastUpdate: cacheTime}),
actions.setAllProjects({
projects: projects as Project[],
updating: rootProjects?.length > 0
}),
])
);
}),
));
updateProjectsCache$ = createEffect(() => this.actions$.pipe(
ofType(actions.setAllProjects, actions.deletedProjectFromRoot, actions.updateProjectCompleted),
withLatestFrom(
this.store.select(selectRootProjects),
this.store.select(selectLastUpdate),
this.store.select(selectShowHidden),
),
map(([, projects, lastUpdate, hidden]) => localForage.setItem<RootCache>('rootProjects', {
time: lastUpdate,
hidden,
projects
}))
), {dispatch: false});
getTablesFilterProjectsOptions$ = createEffect(() => this.actions$.pipe(
ofType(actions.getTablesFilterProjectsOptions),
debounceTime(300),
withLatestFrom(
this.store.select(selectShowHidden),
this.store.select(selectProjectsOptionsScrollId),
),
switchMap(([action, showHidden, scrollId]) => forkJoin([
this.projectsApi.projectsGetAllEx({
/* eslint-disable @typescript-eslint/naming-convention */
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
} 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([])
])
.pipe(map(([allProjects, specificProjects]) => ({
projects: [
...(specificProjects.length > 0 && allProjects.projects.some(project => project.id === specificProjects[0]?.id) ? [] : specificProjects),
...allProjects.projects
],
scrollId: allProjects.scroll_id,
loadMore: action.loadMore
})
))
),
mergeMap((projects: { projects: ProjectsGetAllResponseSingle[]; scrollId: string }) => [setTablesFilterProjectsOptions({...projects})])
)
);
resetProjects$ = createEffect(() => this.actions$.pipe(
ofType(actions.resetSelectedProject),
mergeMap(() => [actions.resetProjectSelection()])
));
resetAncestorProjects$ = createEffect(() => this.actions$.pipe(
ofType(actions.setSelectedProjectId),
withLatestFrom(this.store.select(selectSelectedProjectId)),
filter(([action, prevProjectId]) => action.projectId !== prevProjectId),
mergeMap(() => [setProjectAncestors({projects: null})])
));
getAncestorProjects$ = createEffect(() => this.actions$.pipe(
ofType(actions.setSelectedProject),
filter(action => !!action.project),
switchMap(action => {
const parts = action.project.name?.split('/');
if (!action.project.id || action.project.id === ALL_PROJECTS_OBJECT.id || parts.length === 1) {
return of([{projects: []}, []]);
}
parts.pop();
const escapedParts = parts.map(escapeRegExp);
const [simpleProjectNames, projectsNames] = parts.reduce(
([simpleNames, names], part, index) => [
[...simpleNames, parts.slice(0, index + 1).join('/')],
[...names, escapedParts.slice(0, index + 1).join('\\/')],
],
[[], []]
);
return this.projectsApi.projectsGetAllEx({
/* eslint-disable @typescript-eslint/naming-convention */
_any_: {fields: ['name'], pattern: projectsNames.map(name => `^${name}$`).join('|')},
search_hidden: true
/* eslint-enable @typescript-eslint/naming-convention */
}).pipe(map(res => [res, simpleProjectNames]));
}),
switchMap(([res, projectsNames]) => [actions.setProjectAncestors({
projects: res?.projects?.filter(project => projectsNames.includes(project.name))
.sort((projectA, projectB) => (projectA.name?.split('/').length >= projectB.name?.split('/').length) ? 1 : -1)
})]
)));
resetProjectSelections$ = createEffect(() => this.actions$.pipe(
ofType(actions.resetProjectSelection),
mergeMap(() => [setSelectedExperiments({experiments: []}), setSelectedModels({models: []})])
@@ -176,13 +177,16 @@ export class ProjectsEffects {
// eslint-disable-next-line @typescript-eslint/naming-convention
switchMap(() => this.orgApi.organizationGetTags({include_system: true})
.pipe(
map((res: OrganizationGetTagsResponse) => actions.setCompanyTags({tags: res.tags, systemTags: res.system_tags})),
map((res: OrganizationGetTagsResponse) => actions.setCompanyTags({
tags: res.tags,
systemTags: res.system_tags
})),
catchError(error => [requestFailed(error)])
)
)
));
getProjectsTags = createEffect(() => this.actions$.pipe(
getProjectsTags = createEffect(() => this.actions$.pipe(
ofType(actions.getProjectsTags),
// eslint-disable-next-line @typescript-eslint/naming-convention
switchMap(action => this.projectsApi.projectsGetProjectTags({filter: {system_tags: [action.entity]}})
@@ -197,7 +201,7 @@ export class ProjectsEffects {
ofType(actions.getTags),
withLatestFrom(this.store.select(selectRouterParams).pipe(
map(params => (params === null || params?.projectId === '*') ? [] : [params.projectId]))),
mergeMap(([action, projects]) => {
mergeMap(([action, projects]) => {
const ids = action?.projectId ? [action.projectId] : projects;
if (ids.length === 0 || !ids[0]) {
return EMPTY;
@@ -237,7 +241,7 @@ export class ProjectsEffects {
ofType(actions.fetchGraphData),
withLatestFrom(
this.store.select(selectSelectedProjectId),
this.store.select(selectSelectedMetricVariantForCurrProject)
this.store.select(selectSelectedMetricVariantForCurrProject),
),
filter(([, , variant]) => !!variant),
switchMap(([, projectId, variant]) => {
@@ -278,7 +282,6 @@ export class ProjectsEffects {
resetRootProjects = createEffect(() => this.actions$.pipe(
ofType(setActiveWorkspace, actions.refetchProjects, setShowHidden),
switchMap(() => from(localForage.removeItem('rootProjects'))),
mergeMap(() => [
actions.resetProjects(),
actions.getAllSystemProjects()

View File

@@ -5,7 +5,7 @@ import {uniq} from 'lodash-es';
import {map, tap} from 'rxjs/operators';
import {NAVIGATION_ACTIONS} from '~/app.constants';
import {encodeFilters, encodeOrder} from '../../shared/utils/tableParamEncode';
import {NavigateTo, NavigationEnd, SetRouterSegments, setURLParams} from '../actions/router.actions';
import {NavigateTo, NavigationEnd, setRouterSegments, setURLParams} from '../actions/router.actions';
@Injectable()
@@ -29,7 +29,7 @@ export class RouterEffects {
routerNavigationEnd = createEffect(() => this.actions$.pipe(
ofType<NavigationEnd>(NAVIGATION_ACTIONS.NAVIGATION_END),
map(() => new SetRouterSegments({url: this.getRouterUrl(), params: this.getRouterParams(), config: this.getRouterConfig(), queryParams: this.route.snapshot.queryParams}))
map(() => setRouterSegments({url: this.getRouterUrl(), params: this.getRouterParams(), config: this.getRouterConfig(), queryParams: this.route.snapshot.queryParams}))
));
setTableParams = createEffect(() => this.actions$.pipe(

View File

@@ -5,9 +5,9 @@ import {Project} from '~/business-logic/model/projects/project';
import {getSystemTags} from '~/features/experiments/shared/experiments.utils';
import {ITableExperiment} from '../../experiments/shared/common-experiment-model.model';
import {MetricColumn} from '@common/shared/utils/tableParamEncode';
import {sortByField} from '@common/tasks/tasks.utils';
import {ProjectsGetAllResponseSingle} from '~/business-logic/model/projects/projectsGetAllResponseSingle';
import {User} from '~/business-logic/model/users/user';
import {ProjectsGetAllResponseSingle} from '~/business-logic/model/projects/projectsGetAllResponseSingle';
import {selectRouterConfig} from "@common/core/reducers/router-reducer";
export interface ProjectStatsGraphData {
@@ -23,6 +23,7 @@ export interface ProjectStatsGraphData {
export interface RootProjects {
projects: Project[];
selectedProject: Project;
projectAncestors: Project[];
archive: boolean;
deep: boolean;
projectTags: string[];
@@ -32,22 +33,26 @@ export interface RootProjects {
tagsFilterByProject: boolean;
graphVariant: { [project: string]: MetricColumn };
graphData: ProjectStatsGraphData[];
hiddenStates: { [state: string]: boolean };
lastUpdate: string;
users: User[];
allUsers: User[];
extraUsers: User[];
showHidden: boolean;
hideExamples: boolean;
mainPageTagsFilter: string[];
mainPageTagsFilter: { [Feature: string]: string[] };
mainPageTagsFilterMatchMode: string;
defaultNestedModeForFeature: { [feature: string]: boolean };
tablesFilterProjectsOptions: Partial<ProjectsGetAllResponseSingle>[];
projectsOptionsScrollId: string;
}
const initRootProjects: RootProjects = {
mainPageTagsFilter: [],
mainPageTagsFilter: {},
mainPageTagsFilterMatchMode: 'AND',
projects: null,
selectedProject: null,
projectAncestors: null,
archive: false,
deep: false,
projectTags: [],
@@ -57,25 +62,30 @@ const initRootProjects: RootProjects = {
tagsFilterByProject: true,
graphVariant: {},
graphData: null,
hiddenStates: {},
lastUpdate: null,
users: [],
allUsers: [],
extraUsers: [],
showHidden: false,
hideExamples: false,
defaultNestedModeForFeature: {}
defaultNestedModeForFeature: {},
tablesFilterProjectsOptions: null,
projectsOptionsScrollId: null
};
export const projects = state => state.rootProjects as RootProjects;
export const selectRootProjects = createSelector(projects, (state): Project[] => state.projects);
export const selectSelectedProject = createSelector(projects, (state): ProjectsGetAllResponseSingle => state.selectedProject);
export const selectSelectedProject = createSelector(projects, state => state.selectedProject);
export const selectProjectAncestors = createSelector(projects, state => state.projectAncestors);
export const selectSelectedProjectDescription = createSelector(projects, state => state.selectedProject?.description);
export const selectSelectedProjectId = createSelector(selectSelectedProject, (selectedProject): string => selectedProject ? selectedProject.id : '');
export const selectIsArchivedMode = createSelector(projects, state => state.archive);
export const selectIsDeepMode = createSelector(projects, state => state.deep);
export const selectTagsFilterByProject = createSelector(projects, state => state.tagsFilterByProject);
export const selectTagsFilterByProject = createSelector(projects, selectSelectedProjectId,
(state, projectId) => projectId !== '*' && state.tagsFilterByProject);
export const selectProjectTags = createSelector(projects, state => state.projectTags);
export const selectMainPageTagsFilter = createSelector(projects, state => state.mainPageTagsFilter);
export const selectMainPageTagsFilter = createSelector(projects, selectRouterConfig, (state, config) => config?.[0] ? state.mainPageTagsFilter[config?.[0]] : []);
export const selectMainPageTagsFilterMatchMode = createSelector(projects, state => state.mainPageTagsFilterMatchMode);
export const selectCompanyTags = createSelector(projects, state => state.companyTags);
// eslint-disable-next-line @typescript-eslint/naming-convention
@@ -91,32 +101,17 @@ export const selectSelectedMetricVariantForCurrProject = createSelector(
selectSelectedProjectsMetricVariant, selectSelectedProjectId,
(projectsVariant, projectId) => projectsVariant[projectId]);
export const selectGraphData = createSelector(projects, state => state.graphData);
export const selectGraphHiddenStates = createSelector(projects, state => state.hiddenStates);
export const selectProjectUsers = createSelector(projects, state => state.extraUsers.length ?
Array.from(new Set([...state.users, ...state.extraUsers])) :
state.users
);
export const selectAllProjectsUsers = createSelector(projects, state => state.allUsers);
export const selectTablesFilterProjectsOptions = createSelector(projects, state => state.tablesFilterProjectsOptions);
export const projectsReducer = createReducer(
initRootProjects,
on(projectsActions.resetProjects, state => ({...state, projects: [], lastUpdate: null})),
on(projectsActions.setAllProjects, (state, action) => {
let newProjects = state.projects;
if (action.updating) {
action.projects.forEach(proj => {
const index = state.projects.findIndex(stateProject => stateProject.id === proj.id);
if (index > -1) {
newProjects = [...newProjects.slice(0, index), proj, ...newProjects.slice(index + 1)];
} else {
newProjects = [...newProjects, proj];
}
});
} else {
newProjects = [...(newProjects || []), ...action.projects];
}
return {...state, projects: sortByField(newProjects, 'name')};
}),
on(projectsActions.setSelectedProjectId, (state, action) => {
const projectId = action.projectId;
return {
@@ -130,6 +125,7 @@ export const projectsReducer = createReducer(
selectedProject: action.project,
extraUsers: []
})),
on(projectsActions.setProjectAncestors, (state, action) => ({...state, projectAncestors: action.projects})),
on(projectsActions.setSelectedProjectStats, (state, action) => ({
...state,
selectedProject: {
@@ -137,10 +133,6 @@ export const projectsReducer = createReducer(
stats: action.project?.stats
}
})),
on(projectsActions.deletedProjectFromRoot, (state, action) => {
const projectIdsToDelete = [action.project.id].concat(action.project.sub_projects.map(project => project.id));
return {...state, projects: state.projects.filter(project => !projectIdsToDelete.includes(project.id))};
}),
on(projectsActions.resetSelectedProject, state => ({
...state,
selectedProject: initRootProjects.selectedProject,
@@ -168,7 +160,9 @@ export const projectsReducer = createReducer(
...state,
projectTags: Array.from(new Set(state.projectTags.concat(action.tags))).sort()
})),
on(projectsActions.setMainPageTagsFilter, (state, action) => ({...state, mainPageTagsFilter: action.tags})),
on(projectsActions.setMainPageTagsFilter, (state, action) => ({
...state,
mainPageTagsFilter: {...state.mainPageTagsFilter, [action.feature] : action.tags }})),
on(projectsActions.setMainPageTagsFilterMatchMode, (state, action) => ({
...state,
mainPageTagsFilterMatchMode: action.matchMode
@@ -181,13 +175,26 @@ export const projectsReducer = createReducer(
...state, graphVariant: {...state.graphVariant, [action.projectId]: action.col}
})),
on(projectsActions.setGraphData, (state, action) => ({...state, graphData: action.stats})),
on(projectsActions.toggleState, (state, action) => ({
...state,
hiddenStates: {...state.hiddenStates, [action.state]: !state.hiddenStates?.[action.state]}
})),
on(projectsActions.setLastUpdate, (state, action) => ({...state, lastUpdate: action.lastUpdate})),
on(projectsActions.setProjectUsers, (state, action) => ({...state, users: action.users, extraUsers: []})),
on(projectsActions.setAllProjectUsers, (state, action) => ({...state, allUsers: action.users})),
on(projectsActions.setProjectExtraUsers, (state, action) => ({...state, extraUsers: action.users})),
on(projectsActions.setShowHidden, (state, action) => ({...state, showHidden: action.show})),
on(projectsActions.setHideExamples, (state, action) => ({...state, hideExamples: action.hide})),
on(projectsActions.setDefaultNestedModeForFeature, (state, action) => ({...state, defaultNestedModeForFeature: {...state.defaultNestedModeForFeature, [action.feature]:action.isNested}}))
on(projectsActions.setDefaultNestedModeForFeature, (state, action) => ({
...state,
defaultNestedModeForFeature: {...state.defaultNestedModeForFeature, [action.feature]: action.isNested}
})),
on(projectsActions.resetTablesFilterProjectsOptions, (state) => ({...state, tablesFilterProjectsOptions: null})),
on(projectsActions.setTablesFilterProjectsOptions, (state, action) => ({
...state,
tablesFilterProjectsOptions: action.loadMore ? (state.tablesFilterProjectsOptions || []).concat(action.projects) : action.projects,
projectsOptionsScrollId: action.scrollId
}))
);
export const selectShowHiddenUserSelection = createSelector(projects, state => state.showHidden);
export const selectShowHidden = createSelector(projects, selectSelectedProject,
@@ -195,3 +202,4 @@ export const selectShowHidden = createSelector(projects, selectSelectedProject,
export const selectHideExamples = createSelector(projects, state => state?.hideExamples);
export const selectDefaultNestedModeForFeature = createSelector(projects, state => state?.defaultNestedModeForFeature);
export const selectProjectsOptionsScrollId = createSelector(projects, state => state?.projectsOptionsScrollId);

View File

@@ -1,7 +1,6 @@
import {createSelector, Action} from '@ngrx/store';
import {NAVIGATION_ACTIONS} from '../../../app.constants';
import {SetRouterSegments} from '../actions/router.actions';
import {createReducer, createSelector, on} from '@ngrx/store';
import {Params} from '@angular/router';
import {setRouterSegments} from '@common/core/actions/router.actions';
export interface RouterState {
url: string;
@@ -11,29 +10,23 @@ export interface RouterState {
skipNextNavigation: boolean;
}
const initRouter = {
url : window.location.pathname,
params : null,
queryParams : null,
config : null,
skipNextNavigation: false
const initRouter: RouterState = {
url: window.location.pathname,
params: null,
queryParams: null,
config: null,
skipNextNavigation: false,
};
export const selectRouter = state => state.router as RouterState;
export const selectRouterUrl = createSelector(selectRouter, router => router && router.url);
export const selectRouterParams = createSelector(selectRouter, router => router && router.params);
export const selectRouter = state => state.router as RouterState;
export const selectRouterUrl = createSelector(selectRouter, router => router && router.url);
export const selectRouterParams = createSelector(selectRouter, router => router && router?.params);
export const selectRouterQueryParams = createSelector(selectRouter, router => router && router.queryParams);
export const selectRouterConfig = createSelector(selectRouter, router => router && router.config);
export function routerReducer(state: RouterState = initRouter, action: Action): RouterState {
switch (action.type) {
case NAVIGATION_ACTIONS.SET_ROUTER_SEGMENT: {
const payload = (action as SetRouterSegments).payload;
return {...state, params: payload.params, queryParams: payload.queryParams,
url: payload.url, config: payload.config};
}
default:
return state;
}
}
export const routerReducer = createReducer(initRouter,
on(setRouterSegments, (state, action) => ({
...state, params: action.params, queryParams: action.queryParams,
url: action.url, config: action.config
})),
);

View File

@@ -2,6 +2,8 @@ import {createReducer, createSelector, on, ReducerTypes} from '@ngrx/store';
import * as layoutActions from '../actions/layout.actions';
import {apiRequest, requestFailed} from '@common/core/actions/http.actions';
import {Ace} from 'ace-builds';
import {IBreadcrumbsLink} from '@common/layout/breadcrumbs/breadcrumbs.component';
import {setBreadcrumbs, setTypeBreadcrumbs} from '@common/core/actions/router.actions';
export interface ViewState {
loading: { [endpoint: string]: boolean };
@@ -24,6 +26,7 @@ export interface ViewState {
redactedArguments: { key: string }[];
hideRedactedArguments: boolean;
showEmbedReportMenu: { show: boolean; position: { x: number; y: number } };
breadcrumbs: IBreadcrumbsLink[][];
}
export const initViewState: ViewState = {
@@ -49,7 +52,8 @@ export const initViewState: ViewState = {
{key: 'AWS_SECRET_ACCESS_KEY'},
{key: 'AZURE_STORAGE_KEY'}],
hideRedactedArguments: false,
showEmbedReportMenu: {show: null, position: null}
showEmbedReportMenu: {show: null, position: null},
breadcrumbs: [[{}]],
};
export const views = state => state.views as ViewState;
@@ -75,6 +79,8 @@ export const selectNeverShowPopups = createSelector(views, (state): string[] =>
export const selectRedactedArguments = createSelector(views, (state): { key: string }[] => state.redactedArguments);
export const selectHideRedactedArguments = createSelector(views, (state): { key: string }[] => state.hideRedactedArguments ? state.redactedArguments : null);
export const selectShowEmbedReportMenu = createSelector(views, state => state.showEmbedReportMenu);
export const selectBreadcrumbs = createSelector(views, state => state && state.breadcrumbs);
export const viewReducers = [
@@ -124,6 +130,14 @@ export const viewReducers = [
...state,
neverShowPopupAgain: action.reset ? state.neverShowPopupAgain.filter(popups => popups !== action.popupId) : Array.from(new Set([...state.neverShowPopupAgain, action.popupId]))
})),
on(setBreadcrumbs, (state, action) => ({
...state, breadcrumbs: action.breadcrumbs
})),
on(setTypeBreadcrumbs, (state, action) => ({
...state,
breadcrumbs: [...state.breadcrumbs?.map(breadcrumbGroup => [...breadcrumbGroup?.map(breadcrumb => breadcrumb.type === action.type ? action.breadcrumb : breadcrumb)
])]
})),
] as ReducerTypes<ViewState, any>[];
export const viewReducer = createReducer(

View File

@@ -8,7 +8,7 @@
class="btn btn-cml-primary d-flex align-items-center"
data-id="New Project"
(click)="openCreateProjectDialog()">
<i class="al-icon sm al-ico-add mr-2"></i>NEW PROJECT
<i class="al-icon sm al-ico-add me-2"></i>NEW PROJECT
</button>
</div>
</div>

View File

@@ -1,6 +1,6 @@
import {Component, Output, EventEmitter, AfterViewInit, ViewChild, ElementRef, OnDestroy} from '@angular/core';
import {Component, OnInit, Output, EventEmitter, AfterViewInit, ViewChild, ElementRef, OnDestroy} from '@angular/core';
import {Router} from '@angular/router';
import {MatLegacyDialog as MatDialog, MatLegacyDialogRef as MatDialogRef} from '@angular/material/legacy-dialog';
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';
@@ -19,7 +19,7 @@ import {trackById} from '@common/shared/utils/forms-track-by';
templateUrl: './dashboard-projects.component.html',
styleUrls : ['./dashboard-projects.component.scss']
})
export class DashboardProjectsComponent implements AfterViewInit, OnDestroy {
export class DashboardProjectsComponent implements OnInit, AfterViewInit, OnDestroy {
public recentProjectsList$: Observable<Array<Project>>;
private dialog: MatDialogRef<ProjectDialogComponent>;
private sub: Subscription;
@@ -34,15 +34,18 @@ export class DashboardProjectsComponent implements AfterViewInit, OnDestroy {
public router: Router,
private matDialog: MatDialog
) {
this.store.dispatch(resetSelectedProject());
this.store.select(selectCurrentUser)
.pipe(filter(user => !!user), take(1))
.subscribe(() => this.store.dispatch(getRecentProjects()));
this.recentProjectsList$ = this.store.select(selectRecentProjects);
}
@ViewChild('header') header: ElementRef<HTMLDivElement>;
ngOnInit() {
this.store.dispatch(resetSelectedProject());
this.store.select(selectCurrentUser)
.pipe(filter(user => !!user), take(1))
.subscribe(() => this.store.dispatch(getRecentProjects()));
}
ngAfterViewInit() {
window.setTimeout(() => this.width.emit(this.header.nativeElement.getBoundingClientRect().width));
this.sub = fromEvent(window, 'resize')
@@ -56,6 +59,7 @@ export class DashboardProjectsComponent implements AfterViewInit, OnDestroy {
public openCreateProjectDialog() {
this.dialog = this.matDialog.open(ProjectDialogComponent, {
panelClass: 'light-theme',
data: {
mode: 'create',
}

View File

@@ -12,7 +12,7 @@
<div class="step-part step-footer" [class]="step?.data?.status">
<div *ngIf="step?.data?.job_size">{{step.data.job_size | filesize: fileSizeConfigStorage}}</div>
<div class="d-flex-center" *ngIf="step?.data?.last_update">
<i class="al-icon al-ico-upload sm mr-1"></i>{{step.data.last_update * 1000 | timeAgo}}
<i class="al-icon al-ico-upload sm me-1"></i>{{step.data.last_update * 1000 | timeAgo}}
</div>
</div>
</div>

View File

@@ -50,10 +50,10 @@ export const routes: Routes = [
SimpleDatasetVersionInfoComponent,
SimpleDatasetVersionDetailsComponent,
SimpleDatasetVersionContentComponent,
SimpleDatasetVersionPreviewComponent,
DatasetVersionStepComponent,
],
imports: [
SimpleDatasetVersionPreviewComponent,
CommonModule,
SMSharedModule,
AngularSplitModule,
@@ -63,6 +63,9 @@ export const routes: Routes = [
ExperimentOutputLogModule,
DebugImagesModule,
RouterModule.forChild(routes),
],
exports: [
SimpleDatasetVersionPreviewComponent,
]
})
export class DatasetVersionModule { }

View File

@@ -13,7 +13,7 @@
ngxClipboard
[cbContent]="command"
(cbOnSuccess)="$event.event.stopPropagation(); copied()"
>Copy command</div><i class="al-icon al-ico-success sm mr-1" [class.visible]="copySuccess"></i>
>Copy command</div><i class="al-icon al-ico-success sm me-1" [class.visible]="copySuccess"></i>
</div>
</div>
</mat-menu>

View File

@@ -1,5 +1,5 @@
import {ChangeDetectionStrategy, Component, Input, ViewChild} from '@angular/core';
import {MatLegacyMenuTrigger as MatMenuTrigger} from '@angular/material/legacy-menu';
import {MatMenuTrigger} from '@angular/material/menu';
import {ISmCol} from '@common/shared/ui-components/data/table/table.consts';
import {fileSizeConfigStorage, FileSizePipe} from '@common/shared/pipes/filesize.pipe';

View File

@@ -20,6 +20,21 @@
></i>
</div>
</div>
<div class="param">
<div class="key">Parent</div>
<div class="value" [smTooltip]="entity?.parent?.name" smShowTooltipIfEllipsis>
<a
*ngIf="entity?.parent?.id; else: empty"
class="arr-link"
target="_blank"
[href]="'/projects/' + entity?.parent?.project.id + '/experiments/' + entity?.parent?.id + '/output/execution'">
<div class="d-flex-center">
<span class="flex-shrink-1 ellipsis">{{entity?.parent?.name}}</span><i *ngIf="entity?.parent?.id" class="al-icon al-ico-export xs"></i>
</div>
</a>
<ng-template #empty>-</ng-template>
</div>
</div>
<div class="param continue">
<div class="key">Size</div>
<div class="value" [smTooltip]="entity?.runtime?.ds_total_size + ' (original)'" smShowTooltipIfEllipsis>{{($any(entity?.runtime?.ds_total_size) | filesize : fileSizeConfigStorage) || '-'}}<span class="comment">(original)</span></div>

View File

@@ -6,7 +6,7 @@
></sm-simple-dataset-version-details>
<div class="console-button">
<button class="btn btn-cml-primary d-flex align-items-center" (click)="toggleDetails()">
<i class="al-icon al-ico-console sm mr-3"></i>DETAILS
<i class="al-icon al-ico-console sm me-3"></i>DETAILS
</button>
</div>
<div
@@ -48,7 +48,7 @@
<ng-container *ngIf="showLog">
<div class="header toggle">
<div class="log-name">
<i class="al-icon al-ico-console mr-2"></i>
<i class="al-icon al-ico-console me-2"></i>
<span *ngIf="(selected$ | async) as selected">
{{selected?.name}}<ng-container *ngIf="selected?.runtime?.version"> v{{selected.runtime.version}}</ng-container>
</span>
@@ -64,7 +64,7 @@
></sm-button-toggle>
<div class="close">
<i class="al-icon pointer" [class]="maximizeResults ? 'al-ico-min-panel' : 'al-ico-max-panel'" (click)="toggleResultSize()"></i>
<i class="al-icon al-ico-dialog-x pointer ml-4" (click)="openLog(false)"></i>
<i class="al-icon al-ico-dialog-x pointer ms-4" (click)="openLog(false)"></i>
</div>
</div>
<div [ngSwitch]="detailsPanelMode" class="content">

View File

@@ -4,8 +4,8 @@ import {DagManagerUnsortedService} from '@common/shared/services/dag-manager-uns
import {experimentDetailsUpdated, getSelectedPipelineStep, setSelectedPipelineStep} from '@common/experiments/actions/common-experiments-info.actions';
import {last} from 'lodash-es';
import {Store} from '@ngrx/store';
import {MatLegacyDialog as MatDialog} from '@angular/material/legacy-dialog';
import {EditJsonComponent} from '@common/shared/ui-components/overlay/edit-json/edit-json.component';
import {MatDialog} from '@angular/material/dialog';
import {EditJsonComponent, EditJsonData} from '@common/shared/ui-components/overlay/edit-json/edit-json.component';
import {Task} from '~/business-logic/model/tasks/task';
import {CommonExperimentsInfoEffects} from '@common/experiments/effects/common-experiments-info.effects';
import {tap} from 'rxjs/operators';
@@ -93,8 +93,7 @@ export class SimpleDatasetVersionInfoComponent extends PipelineControllerInfoCom
data: {
textData: dataset.comment,
title: 'EDIT DESCRIPTION',
typeJson: false,
}
} as EditJsonData
});
editJsonComponent.afterClosed().subscribe(res => {
if (res !== undefined) {

View File

@@ -1,9 +1,18 @@
import {Component, Input} from '@angular/core';
import {ExperimentSharedModule} from "~/features/experiments/shared/experiment-shared.module";
import {DebugImagesModule} from "@common/debug-images/debug-images.module";
import {NgIf} from "@angular/common";
@Component({
selector: 'sm-simple-dataset-version-preview',
templateUrl: './simple-dataset-version-preview.component.html',
styleUrls: ['./simple-dataset-version-preview.component.scss']
styleUrls: ['./simple-dataset-version-preview.component.scss'],
imports: [
ExperimentSharedModule,
DebugImagesModule,
NgIf
],
standalone: true
})
export class SimpleDatasetVersionPreviewComponent {
@Input() selected;

View File

@@ -47,7 +47,7 @@
[users]="users$ | async"
[hyperParamsOptions]="hyperParamsOptions$ | async"
[activeParentsFilter]="activeParentsFilter$ | async"
[parents]="parent$ | async"
[parents]="parents$ | async"
[experimentTypes]="types$ | async"
[tags]="tags$ | async"
[systemTags]="systemTags$ | async"
@@ -107,7 +107,7 @@
[selectedDisableAvailable]="singleRowContext ? getSingleSelectedDisableAvailable(contextExperiment) : (selectedExperimentsDisableAvailable$ | async)"
[numSelected]="singleRowContext ? 1 : selectedExperiments.length"
[tagsFilterByProject]="tagsFilterByProject$ | async"
[projectTags]="projectTags$ | async"
[projectTags]="tags$ | async"
[companyTags]="companyTags$ | async"
[activateFromMenuButton]="false"
[minimizedView]="true"

View File

@@ -9,7 +9,7 @@ import {EXPERIMENTS_TABLE_COL_FIELDS} from '~/features/experiments/shared/experi
import {Store} from '@ngrx/store';
import {SmSyncStateSelectorService} from '@common/core/services/sync-state-selector.service';
import {ActivatedRoute, Router} from '@angular/router';
import {MatLegacyDialog as MatDialog} from '@angular/material/legacy-dialog';
import {MatDialog} from '@angular/material/dialog';
import {RefreshService} from '@common/core/services/refresh.service';
import {take} from 'rxjs/operators';
import {ExperimentsViewState} from '@common/experiments/reducers/experiments-view.reducer';
@@ -50,7 +50,7 @@ export class SimpleDatasetVersionsComponent extends ControllersComponent impleme
if (!this.route.snapshot.firstChild?.params.versionId) {
this.store.dispatch(experimentsActions.experimentSelectionChanged({
experiment: this.firstExperiment,
project: this.selectedProject,
project: this.selectedProjectId,
replaceURL: true
}));
}

View File

@@ -12,7 +12,7 @@
<div>
Use ClearML Data from CLI or in your Python code.<br>
Run these code snippets for a quick example (Requires ClearML 1.7 or above).<br>
For more details see the <a [class.dark]="showButton" href="https://clear.ml/docs/latest/docs/clearml_data/clearml_data" target="_blank">documentation</a>
For more details see the <a [class.dark]="showButton" href="https://clear.ml/docs/latest/docs/clearml_data" target="_blank">documentation</a>
</div>
</div>
<div class="navbar">
@@ -38,4 +38,4 @@
</div>
<div class="diagram"><i class="i-datasets-empty-state"></i></div>
</div>
</ng-template>
</ng-template>

View File

@@ -24,7 +24,7 @@
>{{project.name | shortProjectName}}</span>
<i *ngIf="!hideProjectPathIcon && (project.name | cleanProjectPath: false)" [smTooltip]="project.name |
cleanProjectPath:false"
class="al-icon al-ico-project-path sm ml-2"></i></span>
class="al-icon al-ico-project-path sm ms-2"></i></span>
</sm-inline-edit>
<sm-pipeline-card-menu
class="menu-wrapper"

View File

@@ -9,7 +9,7 @@
>
<sm-button-toggle
left-items
class="ml-3"
class="ms-3"
[value]="!!projectId"
[options]="[
{value: false, icon: 'al-ico-flat-view', label: 'List view'},
@@ -20,7 +20,7 @@
<button
class="btn btn-cml-primary d-flex align-items-center"
(click)="createDataset()">
<i class="al-icon al-ico-add sm mr-2"></i>NEW DATASET
<i class="al-icon al-ico-add sm me-2" data-id="NewDatasetButton"></i>NEW DATASET
</button>
</sm-projects-header>
<ng-container

View File

@@ -21,7 +21,7 @@
<div class="metric-bar" [class.minimized]="minimized"
*ngIf="!thereAreNoMetrics(experimentId) && !disableStatusRefreshFilter">
<label>Metric:</label>
<mat-form-field appearance="outline" [ngClass]="{'dark thin': isDarkTheme}">
<mat-form-field appearance="outline" class="no-bottom" [ngClass]="{'dark thin': isDarkTheme}">
<mat-select
#metricSelect
(selectionChange)="selectMetric($event, experimentId)"

View File

@@ -15,7 +15,7 @@ import {select, Store} from '@ngrx/store';
import {ExperimentInfoState} from '~/features/experiments/reducers/experiment-info.reducer';
import {AdminService} from '~/shared/services/admin.service';
import {selectS3BucketCredentials} from '../core/reducers/common-auth-reducer';
import {MatLegacyDialog as MatDialog} from '@angular/material/legacy-dialog';
import {MatDialog} from '@angular/material/dialog';
import * as debugActions from './debug-images-actions';
import {fetchExperiments, getDebugImagesMetrics, resetDebugImages} from './debug-images-actions';
import {
@@ -316,7 +316,7 @@ export class DebugImagesComponent implements OnInit, OnDestroy, OnChanges {
this.createEmbedCode({metrics: [metric], variants: [variant], domRect: rect}, experimentId),
imageSources: sources, index, snippetsMetaData: iterationSnippets, isAllMetrics
},
panelClass: ['image-viewer-dialog'],
panelClass: ['image-viewer-dialog', 'light-theme'],
height: '100%',
maxHeight: 'auto',
width: '100%',
@@ -418,7 +418,8 @@ export class DebugImagesComponent implements OnInit, OnDestroy, OnChanges {
createEmbedCode(event: { metrics?: string[]; variants?: string[]; domRect: DOMRect }, experimentId: string) {
this.reportEmbed.createCode({
type: 'sample',
tasks: [experimentId],
objects: [experimentId],
objectType: 'task',
...event
});
}

View File

@@ -10,7 +10,7 @@ import {DebugImagesEffects} from './debug-images-effects';
import {debugSamplesReducer} from './debug-images-reducer';
import {DebugImagesViewComponent} from './debug-images-view/debug-images-view.component';
import {DebugImagesComponent} from './debug-images.component';
import {MatLegacySliderModule as MatSliderModule} from '@angular/material/legacy-slider';
import {MatSliderModule} from '@angular/material/slider';
import {ExperimentGraphsModule} from '../shared/experiment-graphs/experiment-graphs.module';
import {DebugSampleModule} from '@common/shared/debug-sample/debug-sample.module';
import {SharedPipesModule} from '@common/shared/pipes/shared-pipes.module';

View File

@@ -4,6 +4,7 @@ import {Params} from '@angular/router';
import {ISmCol} from '../../shared/ui-components/data/table/table.consts';
import {SortMeta} from 'primeng/api';
import {TableFilter} from '../../shared/utils/tableParamEncode';
import {EntityTypeEnum} from '~/shared/constants/non-common-consts';
export const EXPERIMENTS_COMPARE_SELECT_EXPERIMENT_ = 'EXPERIMENTS_COMPARE_SELECT_EXPERIMENT_';
@@ -22,7 +23,7 @@ export const SET_NAVIGATION_PREFERENCES = EXPERIMENTS_COMPARE_SELECT_EXPERIMENT_
export const setHideIdenticalFields = createAction(SET_HIDE_IDENTICAL_ROWS, props<{payload: boolean}>());
export const setExperimentsUpdateTime = createAction(SET_EXPERIMENTS_UPDATE_TIME, props<{ payload: {[key: string]: Date}}>());
export const refreshIfNeeded = createAction(REFRESH_IF_NEEDED, props<{ payload: boolean; autoRefresh?: boolean }>());
export const refreshIfNeeded = createAction(REFRESH_IF_NEEDED, props<{ payload: boolean; autoRefresh?: boolean; entityType: string }>());
export const toggleShowScalarOptions = createAction(TOGGLE_SHOW_SACLARS_OPTIONS);
export const setSearchExperimentsForCompareResults = createAction(SET_SELECT_EXPERIMENTS_FOR_COMPARE, props<{ payload: Array<Task> }>());
export const setShowSearchExperimentsForCompare = createAction(SET_SHOW_SEARCH_EXPERIMENTS_FOR_COMPARE, props<{ payload: boolean }>());
@@ -50,7 +51,7 @@ export const compareAddDialogSetTableSort = createAction(
EXPERIMENTS_COMPARE_SELECT_EXPERIMENT_ + ' [set table sort]',
props<{ orders: SortMeta[]; projectId: string; colIds: string[] }>()
);
export const refetchExperimentRequested = createAction(REFETCH_EXPERIMENT_REQUESTED, props<{ autoRefresh: boolean }>());
export const refetchExperimentRequested = createAction(REFETCH_EXPERIMENT_REQUESTED, props<{ autoRefresh: boolean; entity: EntityTypeEnum }>());
export const setNavigationPreferences = createAction(SET_NAVIGATION_PREFERENCES, props<{ navigationPreferences: Params }>());
export const setAddTableViewArchived = createAction(
EXPERIMENTS_COMPARE_SELECT_EXPERIMENT_ + '[show archived in add table]',

View File

@@ -2,6 +2,7 @@ import {createAction, props} from '@ngrx/store';
import {ScalarKeyEnum} from '~/business-logic/model/events/scalarKeyEnum';
import {EventsGetMultiTaskPlotsResponse} from '~/business-logic/model/events/eventsGetMultiTaskPlotsResponse';
import {ExperimentCompareSettings} from '@common/experiments-compare/reducers/experiments-compare-charts.reducer';
import {EntityTypeEnum} from '~/shared/constants/non-common-consts';
export const EXPERIMENTS_COMPARE_METRICS_CHARTS_ = 'EXPERIMENTS_COMPARE_METRICS_CHARTS_';
@@ -14,12 +15,12 @@ export const SET_EXPERIMENT_PLOTS = EXPERIMENTS_COMPARE_METRICS_CH
export const getMultiScalarCharts = createAction(
EXPERIMENTS_COMPARE_METRICS_CHARTS_ + 'GET_MULTI_SCALAR_CHARTS',
props<{ taskIds: string[]; autoRefresh?: boolean; cached?: boolean }>()
props<{ taskIds: string[]; entity: EntityTypeEnum; autoRefresh?: boolean; cached?: boolean }>()
);
export const getMultiPlotCharts = createAction(
EXPERIMENTS_COMPARE_METRICS_CHARTS_ + 'GET_MULTI_PLOT_CHARTS',
props<{ taskIds: Array<string>; autoRefresh?: boolean }>()
props<{ taskIds: Array<string>; entity: EntityTypeEnum; autoRefresh?: boolean }>()
);
export const setSelectedExperiments = createAction(

View File

@@ -1,6 +1,9 @@
import {createAction, props} from '@ngrx/store';
import {IExperimentDetail} from '~/features/experiments-compare/experiments-compare-models';
import {EntityTypeEnum} from '~/shared/constants/non-common-consts';
import {ModelDetail, ModelDetails} from '@common/experiments-compare/shared/experiments-compare-details.model';
export const resetState = createAction('[experiment compare details] RESET_STATE');
export const setExperiments = createAction('[experiment compare details] SET_EXPERIMENTS', props<{experiments: IExperimentDetail[]}>());
export const experimentListUpdated = createAction('[experiment compare details] EXPERIMENT_LIST_UPDATED', props<{ids: string[]}>());
export const setModels = createAction('[experiment compare details] SET_MODELS', props<{models: ModelDetail[]}>());
export const experimentListUpdated = createAction('[experiment compare details] EXPERIMENT_LIST_UPDATED', props<{ids: string[]; entity: EntityTypeEnum}>());

View File

@@ -1,11 +1,12 @@
import {createAction, props} from '@ngrx/store';
import {Task} from '~/business-logic/model/tasks/task';
import {EntityTypeEnum} from '~/shared/constants/non-common-consts';
export const EXPERIMENTS_COMPARE_METRICS_VALUES_ = 'EXPERIMENTS_COMPARE_METRICS_VALUES_';
export const getComparedExperimentsMetricsValues = createAction(
EXPERIMENTS_COMPARE_METRICS_VALUES_ + 'GET_COMPARED_EXPERIMENTS_METRICS_VALUES',
props<{ taskIds: string[]; autoRefresh?: boolean }>()
props<{ taskIds: string[]; entity: EntityTypeEnum; autoRefresh?: boolean }>()
);
export const setComparedExperiments = createAction(
EXPERIMENTS_COMPARE_METRICS_VALUES_ + 'SET_COMPARED_EXPERIMENTS',

View File

@@ -1,6 +1,7 @@
import {createAction, props} from '@ngrx/store';
import {ExperimentParams} from '../shared/experiments-compare-details.model';
import {EntityTypeEnum} from '~/shared/constants/non-common-consts';
export const resetState = createAction('[experiment compare params] RESET_STATE');
export const setExperiments = createAction('[experiment compare params] SET_EXPERIMENTS', props<{experiments: ExperimentParams[]}>());
export const experimentListUpdated = createAction('[experiment compare params] EXPERIMENT_LIST_UPDATED', props<{ids: string[]}>());
export const experimentListUpdated = createAction('[experiment compare params] EXPERIMENT_LIST_UPDATED', props<{ids: string[]; entity: EntityTypeEnum}>());

View File

@@ -1,10 +1,6 @@
import * as detailsActions from '../actions/experiments-compare-details.actions';
import * as paramsActions from '../actions/experiments-compare-params.actions';
import {
ExperimentCompareTree,
ExperimentCompareTreeSection,
IExperimentDetail
} from '~/features/experiments-compare/experiments-compare-models';
import {ExperimentCompareTree, ExperimentCompareTreeSection, IExperimentDetail} from '~/features/experiments-compare/experiments-compare-models';
import {get, has, isEmpty, isEqual} from 'lodash-es';
import {treeBuilderService} from '../services/tree-builder.service';
import {isArrayOrderNotImportant} from '../jsonToDiffConvertor';
@@ -20,13 +16,14 @@ import {Observable, Subscription} from 'rxjs';
import {FlatTreeControl} from '@angular/cdk/tree';
import {CdkVirtualScrollViewport} from '@angular/cdk/scrolling';
import {selectRouterParams} from '../../core/reducers/router-reducer';
import {distinctUntilChanged, filter, map, take, tap} from 'rxjs/operators';
import {distinctUntilChanged, filter, map, take} from 'rxjs/operators';
import {selectHideIdenticalFields} from '../reducers';
import {refetchExperimentRequested} from '../actions/compare-header.actions';
import {RENAME_MAP} from '../experiments-compare.constants';
import {selectHasDataFeature} from '~/core/reducers/users.reducer';
import {ListRange} from '@angular/cdk/collections';
import {RefreshService} from '@common/core/services/refresh.service';
import {EntityTypeEnum} from '~/shared/constants/non-common-consts';
export type NextDiffDirectionEnum = 'down' | 'up';
@@ -76,8 +73,8 @@ export abstract class ExperimentCompareBase extends ExperimentCompareDetailsBase
public experimentTags: { [experimentId: string]: string[] } = {};
private timeoutIndex: number;
private originalScrolledElement: EventTarget;
private taskIds: string;
private treeCardBody: HTMLDivElement;
protected entityType = EntityTypeEnum.experiment;
@ViewChildren('treeCardBody') treeCardBodies: QueryList<ElementRef<HTMLDivElement>>;
get baseExperiment(): IExperimentDetail {
@@ -89,7 +86,7 @@ export abstract class ExperimentCompareBase extends ExperimentCompareDetailsBase
@HostListener('window:resize')
afterResize() {
window.setTimeout(() => {
this.nativeWidth = Math.max(this.treeCardBody.getBoundingClientRect().width, 410);
this.nativeWidth = Math.max(this.treeCardBody?.getBoundingClientRect().width, 410);
this.cdr.detectChanges();
});
}
@@ -130,7 +127,7 @@ export abstract class ExperimentCompareBase extends ExperimentCompareDetailsBase
this.refreshingSubscription = this.refresh.tick
.pipe(filter(auto => auto !== null))
.subscribe(auto => this.store.dispatch(refetchExperimentRequested({autoRefresh: auto})));
.subscribe(auto => this.store.dispatch(refetchExperimentRequested({autoRefresh: auto, entity: this.entityType})));
this.hideIdenticalFieldsSub.add(this.hasDataFeature$.subscribe(hasData => this.hasDataFeature = hasData));
@@ -141,7 +138,7 @@ export abstract class ExperimentCompareBase extends ExperimentCompareDetailsBase
.pipe(filter(list => list.first), take(1))
.subscribe((list: QueryList<ElementRef<HTMLDivElement>>) => {
this.treeCardBody = list.first.nativeElement;
this.nativeWidth = Math.max(this.treeCardBody.getBoundingClientRect().width, 410);
this.afterResize();
});
}

Some files were not shown because too many files have changed in this diff Show More