Release v1.4 (#23)

Co-authored-by: shyallegro <support@allegro.ai>
This commit is contained in:
shyallegro
2022-04-24 12:30:51 +03:00
committed by GitHub
parent d7de4d3bc6
commit ab12845bd1
284 changed files with 5523 additions and 6276 deletions

1
.gitignore vendored
View File

@@ -7,6 +7,7 @@
/out-tsc
/gen-code
/src/__ngcc_entry_points__.json
.angular/
# dependencies
/node_modules

View File

@@ -26,6 +26,7 @@
"assets": [
"src/assets",
"src/favicon.ico",
"src/env.js",
"src/app/webapp-common/assets",
{
"glob": "**/*",
@@ -54,8 +55,9 @@
"fast-xml-parser",
"url",
"@aws-crypto/sha256-browser",
"@aws-crypto/crc32"
"@aws-crypto/crc32",
"@aws-crypto/sha1-browser",
"@aws-crypto/crc32c"
],
"vendorChunk": true,
"extractLicenses": false,

4943
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.3.0",
"version": "1.4.0",
"license": "",
"scripts": {
"ng": "ng",
@@ -20,26 +20,27 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^13.1.1",
"@angular/cdk": "^13.1.1",
"@angular/common": "^13.1.1",
"@angular/compiler": "^13.1.1",
"@angular/core": "^13.1.1",
"@angular/forms": "^13.1.1",
"@angular/material": "^13.1.1",
"@angular/platform-browser": "^13.1.1",
"@angular/platform-browser-dynamic": "^13.1.1",
"@angular/platform-server": "^13.1.1",
"@angular/router": "^13.1.1",
"@angular/service-worker": "^13.1.1",
"@aws-sdk/client-s3": "^3.45.0",
"@aws-sdk/s3-request-presigner": "^3.45.0",
"@angular/animations": "^13.2.2",
"@angular/cdk": "^13.2.2",
"@angular/common": "^13.2.2",
"@angular/compiler": "^13.2.2",
"@angular/core": "^13.2.2",
"@angular/forms": "^13.2.2",
"@angular/material": "^13.2.2",
"@angular/platform-browser": "^13.2.2",
"@angular/platform-browser-dynamic": "^13.2.2",
"@angular/platform-server": "^13.2.2",
"@angular/router": "^13.2.2",
"@angular/service-worker": "^13.2.2",
"@angular/youtube-player": "^13.2.2",
"@aws-sdk/client-s3": "^3.53.1",
"@aws-sdk/s3-request-presigner": "^3.53.1",
"@ngneat/dag": "^1.1.0",
"@ngrx/effects": "^13.0.2",
"@ngrx/entity": "^13.0.2",
"@ngrx/router-store": "^13.0.2",
"@ngrx/store": "^13.0.2",
"ace-builds": "^1.4.13",
"ace-builds": "^1.4.14",
"angular-google-tag-manager": "^1.5.0",
"angular-resizable-element": "^5.0.0",
"angular-split": "^13.1.0",
@@ -49,7 +50,7 @@
"curved-arrows": "^0.1.0",
"d3-selection": "^1.4.2",
"diff": "^5.0.0",
"filesize": "^8.0.6",
"filesize": "^8.0.7",
"has-ansi": "^5.0.1",
"hocon-parser": "^1.0.1",
"jwt-decode": "^3.1.2",
@@ -63,7 +64,7 @@
"primeicons": "^5.0.0",
"primeng": "^13.0.4",
"process": "^0.11.10",
"rxjs": "^7.5.1",
"rxjs": "^7.5.5",
"string-to-color": "^2.2.2",
"tslib": "^2.3.1",
"url": "^0.11.0",
@@ -71,19 +72,19 @@
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "^13.1.2",
"@angular-devkit/core": "^13.1.2",
"@angular-devkit/schematics": "^13.1.2",
"@angular-devkit/schematics-cli": "^13.1.2",
"@angular-eslint/builder": "^13.0.1",
"@angular-eslint/eslint-plugin": "^13.0.1",
"@angular-eslint/eslint-plugin-template": "^13.0.1",
"@angular-eslint/schematics": "13.0.1",
"@angular-eslint/template-parser": "^13.0.1",
"@angular/cli": "^13.1.2",
"@angular/compiler-cli": "^13.1.1",
"@angular/language-service": "^13.1.1",
"@fortawesome/fontawesome-free": "^5.15.4",
"@angular-devkit/build-angular": "^13.2.3",
"@angular-devkit/core": "^13.2.3",
"@angular-devkit/schematics": "^13.2.3",
"@angular-devkit/schematics-cli": "^13.2.3",
"@angular-eslint/builder": "^13.1.0",
"@angular-eslint/eslint-plugin": "^13.1.0",
"@angular-eslint/eslint-plugin-template": "^13.1.0",
"@angular-eslint/schematics": "13.1.0",
"@angular-eslint/template-parser": "^13.1.0",
"@angular/cli": "^13.2.3",
"@angular/compiler-cli": "^13.2.2",
"@angular/language-service": "^13.2.2",
"@fortawesome/fontawesome-free": "^6.0.0",
"@ngrx/schematics": "^13.0.2",
"@ngrx/store-devtools": "^13.0.2",
"@types/d3-selection": "^1.4.3",
@@ -94,10 +95,10 @@
"@typescript-eslint/eslint-plugin": "5.9.0",
"@typescript-eslint/parser": "5.9.0",
"codelyzer": "^6.0.2",
"eslint": "^8.6.0",
"eslint": "^8.9.0",
"eslint-plugin-import": "2.25.4",
"eslint-plugin-jsdoc": "37.5.1",
"eslint-plugin-jsdoc": "37.9.1",
"eslint-plugin-prefer-arrow": "1.2.3",
"typescript": "^4.5.4"
"typescript": "^4.5.5"
}
}

View File

@@ -183,7 +183,7 @@ export class AppComponent implements OnInit, OnDestroy {
}
loadExternalLibrary(this.store, this.environment.plotlyURL, plotlyReady);
loadExternalLibrary(this.store, '/assets/ace-builds/ace.js', aceReady);
loadExternalLibrary(this.store, 'assets/ace-builds/ace.js', aceReady);
}
private setScale() {

View File

@@ -1,6 +1,5 @@
import {Action} from '@ngrx/store';
import {ConfigurationService} from './webapp-common/shared/services/configuration.service';
const environment = ConfigurationService.globalEnvironment;
import {Environment} from '../environments/base';
export const NA = 'N/A';
export const ALLEGRO_TUTORIAL_BUCKET = 'allegro-tutorials';
@@ -62,7 +61,7 @@ export const RECENT_TASKS_ACTIONS = {
export const VIEW_PREFIX = 'VIEW_';
export type MediaContentTypeEnum = 'image/bmp' | 'image/jpeg' | 'image/png' | 'video/mp4' | 'image/jpeg';
export type MediaContentTypeEnum = 'image/bmp' | 'image/jpeg' | 'image/png' | 'video/mp4';
export const MEDIA_VIDEO_EXTENSIONS = ['flv', 'avi', 'mp4', 'mov', 'mpg', 'wmv', '3gp', 'mkv'];
@@ -94,7 +93,7 @@ export const NAVIGATION_ACTIONS = {
};
export function guessAPIServerURL() {
export const guessAPIServerURL = () => {
const url = window.location.origin;
if (/https?:\/\/(demo|)app\./.test(url)) {
return url.replace(/(https?):\/\/(demo|)app/, '$1://$2api');
@@ -102,39 +101,48 @@ export function guessAPIServerURL() {
return url.replace(/:\d+/, '') + ':30008';
}
return url.replace(/:\d+/, '') + ':8008';
}
};
export const ENVIRONMENT = {API_VERSION: '/v999.0'};
const url = window.location.origin;
let apiBaseUrl: string;
if (environment.apiBaseUrl) {
apiBaseUrl = environment.apiBaseUrl;
} else {
apiBaseUrl = guessAPIServerURL();
}
const apiBaseUrlNoVersion = apiBaseUrl;
let fileBaseUrl;
if (environment.fileBaseUrl) {
fileBaseUrl = environment.fileBaseUrl;
} else if (/https?:\/\/(demo|)app\./.test(url)) {
fileBaseUrl = url.replace(/(https?):\/\/(demo|)app/, '$1://$2files');
} else if (window.location.port === '30080') {
fileBaseUrl = url.replace(/:\d+/, '') + ':30081';
} else if (window.location.port === '8080') {
fileBaseUrl = url.replace(/:\d+/, '') + ':8081';
}
export let HTTP = {
API_BASE_URL: '',
API_BASE_URL_NO_VERSION : '',
FILE_BASE_URL: '',
ALT_FILES: null
};
export const updateHttpUrlBaseConstant = (_environment: Environment) => {
let apiBaseUrl: string;
if (_environment.apiBaseUrl) {
apiBaseUrl = _environment.apiBaseUrl;
} else {
apiBaseUrl = guessAPIServerURL();
}
const apiBaseUrlNoVersion = apiBaseUrl;
apiBaseUrl += ENVIRONMENT.API_VERSION;
const url = window.location.origin;
let fileBaseUrl;
if (_environment.fileBaseUrl) {
fileBaseUrl = _environment.fileBaseUrl;
} else if (/https?:\/\/(demo|)app\./.test(url)) {
fileBaseUrl = url.replace(/(https?):\/\/(demo|)app/, '$1://$2files');
} else if (window.location.port === '30080') {
fileBaseUrl = url.replace(/:\d+/, '') + ':30081';
} else if (window.location.port === '8080') {
fileBaseUrl = url.replace(/:\d+/, '') + ':8081';
}
HTTP.API_BASE_URL = apiBaseUrl; // <-- DIRECT CALL DOESN'T WORK
HTTP.API_BASE_URL_NO_VERSION = apiBaseUrlNoVersion;
HTTP.FILE_BASE_URL = fileBaseUrl;
};
apiBaseUrl += ENVIRONMENT.API_VERSION;
export const HTTP_PREFIX = 'HTTP_';
export const HTTP = {
API_BASE_URL : apiBaseUrl, // <-- DIRECT CALL DOESN'T WORK
API_BASE_URL_NO_VERSION: apiBaseUrlNoVersion,
FILE_BASE_URL: fileBaseUrl,
};
export class EmptyAction implements Action {
readonly type = 'EMPTY_ACTION';
}

View File

@@ -22,7 +22,6 @@ import {ConfigurationService} from '@common/shared/services/configuration.servic
import {ProjectsSharedModule} from './features/projects/shared/projects-shared.module';
import {MAT_FORM_FIELD_DEFAULT_OPTIONS} from '@angular/material/form-field';
import {LoginService} from '~/shared/services/login.service';
import {SettingsModule} from '~/features/settings/settings.module';
@NgModule({
declarations : [AppComponent],
@@ -42,7 +41,6 @@ import {SettingsModule} from '~/features/settings/settings.module';
onSameUrlNavigation: 'reload',
relativeLinkResolution: 'legacy'
}),
SettingsModule,
NotifierModule.withConfig({
theme: 'material',
behaviour: {
@@ -74,7 +72,7 @@ import {SettingsModule} from '~/features/settings/settings.module';
deps: [ConfigurationService],
useFactory: (confService: ConfigurationService) =>
confService.getStaticEnvironment().GTM_ID
}
},
],
bootstrap : [AppComponent],
exports : []

View File

@@ -7,7 +7,6 @@ import {ProjectRedirectGuardGuard} from '@common/shared/guards/project-redirect.
export const routes: Routes = [
{path: '', redirectTo: 'dashboard', pathMatch: 'full'},
{path: 'admin', redirectTo: 'settings', pathMatch: 'full'},
{
path: 'dashboard',
loadChildren: () => import('./features/dashboard/dashboard.module').then(m => m.DashboardModule),

View File

@@ -63,6 +63,8 @@ import {ProjectsGetModelMetadataKeysResponse} from '~/business-logic/model/proje
import {ProjectsGetModelMetadataKeysRequest} from '~/business-logic/model/projects/projectsGetModelMetadataKeysRequest';
import {ProjectsGetProjectTagsResponse} from '~/business-logic/model/projects/projectsGetProjectTagsResponse';
import {ProjectsGetProjectTagsRequest} from '~/business-logic/model/projects/projectsGetProjectTagsRequest';
import {ProjectsGetModelMetadataValuesRequest} from '~/business-logic/model/projects/projectsGetModelMetadataValuesRequest';
import {ProjectsGetModelMetadataValuesResponse} from '~/business-logic/model/projects/projectsGetModelMetadataValuesResponse';
@Injectable()
@@ -98,7 +100,7 @@ export class ApiProjectsService {
/**
*
*
* Create a new project
* @param request request body
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
@@ -143,7 +145,7 @@ export class ApiProjectsService {
}
/**
*
*
* Deletes a project
* @param request request body
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
@@ -188,7 +190,7 @@ export class ApiProjectsService {
}
/**
*
*
* Get all the company\&#39;s projects and all public projects
* @param request request body
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
@@ -233,7 +235,7 @@ export class ApiProjectsService {
}
/**
*
*
* Get all the company\&#39;s projects and all public projects
* @param request request body
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
@@ -278,8 +280,8 @@ export class ApiProjectsService {
}
/**
*
*
*
*
* @param request request body
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
@@ -323,7 +325,7 @@ export class ApiProjectsService {
}
/**
*
*
* Get a list of all hyper parameter sections and names used in tasks within the given project.
* @param request request body
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
@@ -368,7 +370,7 @@ export class ApiProjectsService {
}
/**
*
*
* Get a list of distinct values for the chosen hyperparameter
* @param request request body
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
@@ -458,12 +460,57 @@ export class ApiProjectsService {
);
}
/**
*
* Get a list of distinct values for the chosen model metadata key
* @param request request body
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public projectsGetModelMetadataValues(request: ProjectsGetModelMetadataValuesRequest, options?: any, observe: any = 'body', reportProgress: boolean = false ): Observable<any> {
if (request === null || request === undefined) {
throw new Error('Required parameter request was null or undefined when calling projectsGetModelMetadataValues.');
}
/**
*
* Get user and system tags used for the models under the specified projects
* @param request request body
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
let headers = this.defaultHeaders;
if (options && options.async_enable) {
headers = headers.set(this.configuration.asyncHeader, '1');
}
// to determine the Accept header
const httpHeaderAccepts: string[] = [
'application/json'
];
const httpHeaderAcceptSelected: string | undefined = this.configuration.selectHeaderAccept(httpHeaderAccepts);
if (httpHeaderAcceptSelected != undefined) {
headers = headers.set("Accept", httpHeaderAcceptSelected);
}
// to determine the Content-Type header
const consumes: string[] = [
];
const httpContentTypeSelected:string | undefined = this.configuration.selectHeaderContentType(consumes);
if (httpContentTypeSelected != undefined) {
headers = headers.set("Content-Type", httpContentTypeSelected);
}
return this.apiRequest.post<ProjectsGetModelMetadataValuesResponse>(`${this.basePath}/projects.get_model_metadata_values`,
request,
{
withCredentials: this.configuration.withCredentials,
headers: headers,
observe: observe,
reportProgress: reportProgress
}
);
}
/**
*
* Get user and system tags used for the models under the specified projects
* @param request request body
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public projectsGetModelTags(request: ProjectsGetModelTagsRequest, options?: any, observe: any = 'body', reportProgress: boolean = false ): Observable<any> {
@@ -550,7 +597,7 @@ export class ApiProjectsService {
}
/**
*
*
* Get unique parent tasks for the tasks in the specified projects
* @param request request body
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
@@ -595,7 +642,7 @@ export class ApiProjectsService {
}
/**
*
*
* Get user and system tags used for the tasks under the specified projects
* @param request request body
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
@@ -640,7 +687,7 @@ export class ApiProjectsService {
}
/**
*
*
* Get all metric/variant pairs reported for tasks in a specific project. If no project is specified, metrics/variant paris reported for all tasks will be returned. If the project does not exist, an empty list will be returned.
* @param request request body
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
@@ -685,7 +732,7 @@ export class ApiProjectsService {
}
/**
*
*
* Convert public projects to private
* @param request request body
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
@@ -730,7 +777,7 @@ export class ApiProjectsService {
}
/**
*
*
* Convert company projects to public
* @param request request body
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
@@ -775,7 +822,7 @@ export class ApiProjectsService {
}
/**
*
*
* Moves all the source project\&#39;s contents to the destination project and remove the source project
* @param request request body
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
@@ -820,7 +867,7 @@ export class ApiProjectsService {
}
/**
*
*
* Moves a project and all of its subprojects under the different location
* @param request request body
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
@@ -865,7 +912,7 @@ export class ApiProjectsService {
}
/**
*
*
* Update project information
* @param request request body
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
@@ -910,7 +957,7 @@ export class ApiProjectsService {
}
/**
*
*
* Validates that the project existis and can be deleted
* @param request request body
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.

View File

@@ -0,0 +1,32 @@
/**
* projects
* 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 ProjectsGetModelMetadataValuesRequest {
/**
* Project IDs
*/
projects?: Array<string>;
/**
* Metadata key
*/
key: string;
/**
* If set to \'true\' then collect values from both company and public models otherwise company modeels only. The default is \'true\'
*/
allow_public?: boolean;
/**
* If set to \'true\' and the project field is set then the result includes metadata values from the subproject models
*/
include_subprojects?: boolean;
}

View File

@@ -0,0 +1,24 @@
/**
* projects
* 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 ProjectsGetModelMetadataValuesResponse {
/**
* Total number of distinct values
*/
total?: number;
/**
* The list of the unique values
*/
values?: Array<string>;
}

View File

@@ -2,17 +2,17 @@ import {Component, OnDestroy, OnInit} from '@angular/core';
import {Observable, Subscription} from 'rxjs';
import {Store} from '@ngrx/store';
import {ActivatedRoute, Router} from '@angular/router';
import {selectShowOnlyUserWork} from '../../webapp-common/core/reducers/users-reducer';
import {GetCurrentUserResponseUserObjectCompany} from '../../business-logic/model/users/getCurrentUserResponseUserObjectCompany';
import {selectShowOnlyUserWork} from '@common/core/reducers/users-reducer';
import {GetCurrentUserResponseUserObjectCompany} from '~/business-logic/model/users/getCurrentUserResponseUserObjectCompany';
import {filter, skip, take} from 'rxjs/operators';
import {setDeep} from '../../webapp-common/core/actions/projects.actions';
import {getRecentProjects, getRecentExperiments} from '../../webapp-common/dashboard/common-dashboard.actions';
import {selectActiveSearch} from '../../webapp-common/common-search/common-search.reducer';
import {selectFirstLogin} from '../../webapp-common/core/reducers/view.reducer';
import {setDeep} from '@common/core/actions/projects.actions';
import {getRecentProjects, getRecentExperiments} from '@common/dashboard/common-dashboard.actions';
import {selectActiveSearch} from '@common/common-search/common-search.reducer';
import {selectFirstLogin} from '@common/core/reducers/view.reducer';
import {MatDialog} from '@angular/material/dialog';
import {WelcomeMessageComponent} from '../../webapp-common/dashboard/dumb/welcome-message/welcome-message.component';
import {firstLogin} from '../../webapp-common/core/actions/layout.actions';
import {IRecentTask, selectRecentTasks} from '../../webapp-common/dashboard/common-dashboard.reducer';
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';
@Component({

View File

@@ -4,18 +4,19 @@ import {Store} from '@ngrx/store';
import {get} from 'lodash/fp';
import {Observable, Subscription} from 'rxjs';
import {debounceTime, distinctUntilChanged, filter, map, tap} from 'rxjs/operators';
import {MESSAGES_SEVERITY} from '../../../../app.constants';
import {MESSAGES_SEVERITY} from '~/app.constants';
import {IExperimentInfoState} from '../../reducers/experiment-info.reducer';
import {selectExperimentInfoData, selectIsExperimentEditable, selectSelectedExperiment} from '../../reducers';
import {selectBackdropActive} from '../../../../webapp-common/core/reducers/view.reducer';
import {isReadOnly} from '../../../../webapp-common/shared/utils/shared-utils';
import {selectRouterConfig, selectRouterParams, selectRouterQueryParams} from '../../../../webapp-common/core/reducers/router-reducer';
import {selectBackdropActive} from '@common/core/reducers/view.reducer';
import {isReadOnly} from '@common/shared/utils/shared-utils';
import {selectRouterConfig, selectRouterParams, selectRouterQueryParams} from '@common/core/reducers/router-reducer';
import * as commonInfoActions from '../../../../webapp-common/experiments/actions/common-experiments-info.actions';
import {ExperimentDetailsUpdated} from '../../../../webapp-common/experiments/actions/common-experiments-info.actions';
import {addMessage} from '../../../../webapp-common/core/actions/layout.actions';
import {ExperimentDetailsUpdated} from '@common/experiments/actions/common-experiments-info.actions';
import {addMessage} from '@common/core/actions/layout.actions';
import {IExperimentInfo} from '../../shared/experiment-info.model';
import {selectSelectedTableExperiment} from '../../../../webapp-common/experiments/reducers';
import {ITableExperiment} from '../../../../webapp-common/experiments/shared/common-experiment-model.model';
import {selectSelectedTableExperiment} from '@common/experiments/reducers';
import {ITableExperiment} from '@common/experiments/shared/common-experiment-model.model';
import {setTableMode} from '@common/experiments/actions/common-experiments-view.actions';
@Component({
@@ -97,7 +98,7 @@ export class ExperimentInfoComponent implements OnInit, OnDestroy {
updateExperimentName(name) {
if (name.trim().length > 2) {
this.store.dispatch(new ExperimentDetailsUpdated({id: this.selectedExperiment.id, changes: {name: name}}));
this.store.dispatch(new ExperimentDetailsUpdated({id: this.selectedExperiment.id, changes: {name}}));
} else {
this.store.dispatch(addMessage(MESSAGES_SEVERITY.ERROR, 'Name must be more than three letters long'));
}
@@ -108,6 +109,7 @@ export class ExperimentInfoComponent implements OnInit, OnDestroy {
}
navigateAfterExperimentSelectionChanged() {
this.store.dispatch(setTableMode({mode: 'table'}));
this.router.navigate([`projects/${this.projectId}/experiments`], {queryParamsHandling: 'merge'});
}

View File

@@ -63,8 +63,6 @@
<sm-refresh-button
*ngIf=" ! minimized"
class="light-theme"
[autoRefreshState]="autoRefreshState$ | async"
(refreshList)="refresh(false)"
(setAutoRefresh)="setAutoRefresh($event)"
>
</sm-refresh-button>

View File

@@ -1,32 +1,34 @@
import {NgModule} from '@angular/core';
import {InjectionToken, NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {ExperimentSharedModule} from './shared/experiment-shared.module';
import {SMSharedModule} from '../../webapp-common/shared/shared.module';
import {SMSharedModule} from '@common/shared/shared.module';
import {ExperimentRouterModule} from './experiments-routing.module';
import {ExperimentsComponent} from './experiments.component';
import {EffectsModule} from '@ngrx/effects';
import {StoreModule} from '@ngrx/store';
import {experimentsReducers} from './reducers';
import {StoreConfig, StoreModule} from '@ngrx/store';
import {experimentsReducers, ExperimentState} from './reducers';
import {AdminService} from '~/shared/services/admin.service';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {SelectModelModule} from '../../webapp-common/select-model/select-model.module';
import {SmSyncStateSelectorService} from '../../webapp-common/core/services/sync-state-selector.service';
import {SelectModelModule} from '@common/select-model/select-model.module';
import {SmSyncStateSelectorService} from '@common/core/services/sync-state-selector.service';
import {ExperimentOutputEffects} from './effects/experiment-output.effects';
import {ExperimentsMenuEffects} from './effects/experiments-menu.effects';
import {LayoutModule} from '../../layout/layout.module';
import {ExperimentGraphsModule} from '../../webapp-common/shared/experiment-graphs/experiment-graphs.module';
import {ExperimentCompareSharedModule} from '../../webapp-common/experiments-compare/shared/experiment-compare-shared.module';
import {LayoutModule} from '~/layout/layout.module';
import {ExperimentGraphsModule} from '@common/shared/experiment-graphs/experiment-graphs.module';
import {ExperimentCompareSharedModule} from '@common/experiments-compare/shared/experiment-compare-shared.module';
import {AngularSplitModule} from 'angular-split';
import {SMMaterialModule} from '../../webapp-common/shared/material/material.module';
import {ExperimentsCommonModule} from '../../webapp-common/experiments/common-experiments.module';
import {CommonLayoutModule} from '../../webapp-common/layout/layout.module';
import {EXPERIMENTS_STORE_KEY} from '../../webapp-common/experiments/shared/common-experiments.const';
import {SMMaterialModule} from '@common/shared/material/material.module';
import {ExperimentsCommonModule} from '@common/experiments/common-experiments.module';
import {CommonLayoutModule} from '@common/layout/layout.module';
import {EXPERIMENTS_STORE_KEY} from '@common/experiments/shared/common-experiments.const';
import {ExperimentInfoComponent} from './containers/experiment-info/experiment-info.component';
import {DebugImagesModule} from '../../webapp-common/debug-images/debug-images.module';
import {ExperimentInfoExecutionComponent} from '../../webapp-common/experiments/containers/experiment-info-execution/experiment-info-execution.component';
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 {MatListModule} from '@angular/material/list';
import {ExperimentOutputComponent} from './containers/experiment-ouptut/experiment-output.component';
import {merge, pick} from 'lodash/fp';
import {EXPERIMENTS_PREFIX} from '@common/experiments/actions/common-experiments-view.actions';
export const experimentSyncedKeys = [
@@ -40,6 +42,30 @@ export const experimentSyncedKeys = [
'output.settingsList',
];
export const EXPERIMENT_CONFIG_TOKEN =
new InjectionToken<StoreConfig<ExperimentState, any>>('ExperimentConfigToken');
const localStorageKey = '_saved_experiment_state_';
const getExperimentsConfig = () => ({
metaReducers: [reducer => {
let onInit = true;
return (state, action) => {
const nextState = reducer(state, action);
if (onInit) {
onInit = false;
const savedState = JSON.parse(localStorage.getItem(localStorageKey));
return merge(nextState, savedState);
}
if (action.type.startsWith(EXPERIMENTS_PREFIX)) {
localStorage.setItem(localStorageKey, JSON.stringify(pick(['view.tableMode'], nextState)));
}
return nextState;
};
}]
});
@NgModule({
imports: [
SMMaterialModule,
@@ -59,7 +85,7 @@ export const experimentSyncedKeys = [
MatSidenavModule,
MatListModule,
AngularSplitModule,
StoreModule.forFeature(EXPERIMENTS_STORE_KEY, experimentsReducers),
StoreModule.forFeature(EXPERIMENTS_STORE_KEY, experimentsReducers, EXPERIMENT_CONFIG_TOKEN),
EffectsModule.forFeature([ExperimentOutputEffects, ExperimentsMenuEffects]),
],
declarations: [
@@ -70,7 +96,8 @@ export const experimentSyncedKeys = [
],
providers: [
AdminService,
SmSyncStateSelectorService
SmSyncStateSelectorService,
{provide: EXPERIMENT_CONFIG_TOKEN, useFactory: getExperimentsConfig},
]
})
export class ExperimentsModule {

View File

@@ -1,46 +1,43 @@
import {ActionReducerMap, createSelector} from '@ngrx/store';
import {experimentsViewReducer, IExperimentsViewState} from './experiments-view.reducer';
import {experimentInfoReducer, IExperimentInfoState} from './experiment-info.reducer';
import {experimentOutputReducer} from './experiment-output.reducer';
import {experimentsViewReducer, IExperimentsViewState, initialState as viewInitialState} from './experiments-view.reducer';
import {experimentInfoReducer, IExperimentInfoState, initialState as infoInitialState} from './experiment-info.reducer';
import {experimentOutputReducer, ExperimentOutputState, initialState as outputInitialState} from './experiment-output.reducer';
import {IExperimentInfo} from '../shared/experiment-info.model';
import {TaskStatusEnum} from '../../../business-logic/model/tasks/taskStatusEnum';
import {isReadOnly, isSharedAndNotOwner} from '../../../webapp-common/shared/utils/shared-utils';
import {EXPERIMENTS_STORE_KEY} from '../../../webapp-common/experiments/shared/common-experiments.const';
import {CommonExperimentOutputState} from '../../../webapp-common/experiments/reducers/common-experiment-output.reducer';
import {selectSelectedModel} from "../../../webapp-common/models/reducers";
import {TaskStatusEnum} from '~/business-logic/model/tasks/taskStatusEnum';
import {isReadOnly, isSharedAndNotOwner} from '@common/shared/utils/shared-utils';
import {selectSelectedModel} from '@common/models/reducers';
import {selectCurrentUser} from '@common/core/reducers/users-reducer';
export const experimentsReducers: ActionReducerMap<any, any> = {
export interface ExperimentState {
view: IExperimentsViewState;
info: IExperimentInfoState;
output: ExperimentOutputState;
}
export const experimentsReducers: ActionReducerMap<ExperimentState, any> = {
view: experimentsViewReducer,
info: experimentInfoReducer,
output: experimentOutputReducer,
};
/**
* The createFeatureSelector function selects a piece of state from the root of the state object.
* This is used for selecting feature states that are loaded eagerly or lazily.
*/
export function experiments(state) {
return state[EXPERIMENTS_STORE_KEY];
}
export const experiments = state => state.experiments ?? {} as ExperimentState;
// view selectors.
export const experimentsView = createSelector(experiments, (state): IExperimentsViewState => state ? state.view : {});
export const experimentsView = createSelector(experiments, state => (state?.view ?? viewInitialState) as IExperimentsViewState);
export const selectExperimentsMetricsCols = createSelector(experimentsView, state => state.metricsCols);
export const selectMetricVariants = createSelector(experimentsView, state => state.metricVariants);
export const selectMetricsLoading = createSelector(experimentsView, state => state.metricsLoading);
// info selectors
export const experimentInfo = createSelector(experiments, (state): IExperimentInfoState => state ? state.info : {});
export const selectSelectedExperiment = createSelector(experimentInfo, state => state.selectedExperiment);
export const experimentInfo = createSelector(experiments, state => (state?.info ?? infoInitialState) as IExperimentInfoState);
export const selectSelectedExperiment = createSelector(experimentInfo, state => state?.selectedExperiment);
export const selectExperimentInfoData = createSelector(experimentInfo, state => state.infoData);
export const selectShowExtraDataSpinner = createSelector(experimentInfo, state => state.showExtraDataSpinner);
// output selectors
export const experimentOutput = createSelector(experiments, (state): CommonExperimentOutputState => state ? state.output : {});
export const experimentOutput = createSelector(experiments, state => (state.output ?? outputInitialState) as ExperimentOutputState);
export const selectIsExperimentEditable = createSelector(selectSelectedExperiment, selectCurrentUser,
(experiment, user): boolean => experiment && experiment.status === TaskStatusEnum.Created && !isReadOnly(experiment) && !isSharedAndNotOwner(experiment, user.company));
export const selectIsSharedAndNotOwner = createSelector(selectSelectedExperiment, selectSelectedModel, selectCurrentUser,

View File

@@ -1,33 +0,0 @@
<div class="d-flex justify-content-between header-container align-items-center"
[ngClass]="{'archive-mode': isArchived}">
<sm-toggle-archive *ngIf="!hideArchiveToggle"
[showArchived]="isArchived"
[class.hide-item]="sharedView"
(toggleArchived)="archivedChanged($event)"></sm-toggle-archive>
<sm-project-context-navbar *ngIf="!hideCreateNewButton"
[class.hide-item]="sharedView"
activeFeature="models" [archivedMode]="isArchived"></sm-project-context-navbar>
<div class="d-flex justify-content-end align-items-center right-buttons">
<sm-clear-filters-button
[tableFilters]="tableFilters"
(clearTableFilters)="clearTableFilters.emit(tableFilters)"
></sm-clear-filters-button>
<sm-model-custom-cols-menu
[disabled]="minimizedView || sharedView"
[isLoading]="isLoadingMetadataKeys"
[metadataKeys]="metadataKeys"
[tableCols]="tableCols"
(removeColFromList)="removeColFromList.emit($event)"
(selectedTableColsChanged)="selectedTableColsChanged.emit($event)"
(selectMetadataKeysActiveChanged)="selectMetadataKeysActiveChanged.emit($event)"
(addOrRemoveMetadataKeyFromColumns)="addOrRemoveMetadataKeyFromColumns.emit($event)"
></sm-model-custom-cols-menu>
<sm-refresh-button
[autoRefreshState]="autoRefreshState"
[allowAutoRefresh]="true"
(refreshList)="refreshListClicked.emit()"
(setAutoRefresh)="setAutoRefresh.emit($event)"
>
</sm-refresh-button>
</div>
</div>

View File

@@ -1,36 +1,34 @@
import {Actions, createEffect, ofType} from '@ngrx/effects';
import {CheckProjectForDeletion, SetProjectReadyForDeletion} from '../../webapp-common/projects/common-projects.actions';
import {PROJECTS_ACTIONS} from '../../webapp-common/projects/common-projects.consts';
import {checkProjectForDeletion, setProjectReadyForDeletion} from '@common/projects/common-projects.actions';
import {mergeMap, switchMap} from 'rxjs/operators';
import {ProjectsValidateDeleteResponse} from '../../business-logic/model/projects/projectsValidateDeleteResponse';
import {ProjectsValidateDeleteResponse} from '~/business-logic/model/projects/projectsValidateDeleteResponse';
import {Injectable} from '@angular/core';
import {ApiProjectsService} from '../../business-logic/api-services/projects.service';
import {ApiTasksService} from '../../business-logic/api-services/tasks.service';
import {ApiModelsService} from '../../business-logic/api-services/models.service';
import {Store} from '@ngrx/store';
import {ApiProjectsService} from '~/business-logic/api-services/projects.service';
import {ApiModelsService} from '~/business-logic/api-services/models.service';
@Injectable()
export class ProjectsEffects {
constructor(
private actions: Actions, public projectsApi: ApiProjectsService,
public experimentsApi: ApiTasksService, public modelsApi: ApiModelsService,
private store: Store<any>
public modelsApi: ApiModelsService,
) {}
checkIfProjectExperiments = createEffect(() => this.actions.pipe(
ofType<CheckProjectForDeletion>(PROJECTS_ACTIONS.CHECK_PROJECT_FOR_DELETION),
switchMap((action) => this.projectsApi.projectsValidateDelete({project: action.payload.project.id})),
ofType(checkProjectForDeletion),
switchMap((action) => this.projectsApi.projectsValidateDelete({project: action.project.id})),
mergeMap((projectsValidateDeleteResponse: ProjectsValidateDeleteResponse) => [
new SetProjectReadyForDeletion({
experiments: {
total: projectsValidateDeleteResponse.tasks,
archived: projectsValidateDeleteResponse.tasks - projectsValidateDeleteResponse.non_archived_tasks,
unarchived: projectsValidateDeleteResponse.non_archived_tasks
},
models: {
total: projectsValidateDeleteResponse.models,
archived: projectsValidateDeleteResponse.models - projectsValidateDeleteResponse.non_archived_models,
unarchived: projectsValidateDeleteResponse.non_archived_models
setProjectReadyForDeletion({
readyForDeletion: {
experiments: {
total: projectsValidateDeleteResponse.tasks,
archived: projectsValidateDeleteResponse.tasks - projectsValidateDeleteResponse.non_archived_tasks,
unarchived: projectsValidateDeleteResponse.non_archived_tasks
},
models: {
total: projectsValidateDeleteResponse.models,
archived: projectsValidateDeleteResponse.models - projectsValidateDeleteResponse.non_archived_models,
unarchived: projectsValidateDeleteResponse.non_archived_models
}
}
})
])

View File

@@ -7,7 +7,7 @@ import {projectsReducer} from './projects.reducer';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {CommonProjectsModule} from '../../webapp-common/projects/common-projects.module';
export const projectSyncedKeys = ['showHidden'];
export const projectSyncedKeys = ['showHidden', 'tableModeAwareness'];
@NgModule({
imports : [

View File

@@ -1,12 +1,11 @@
import {createFeatureSelector, createSelector} from '@ngrx/store';
import {on, createReducer, createSelector} from '@ngrx/store';
import {
CommonProjectReadyForDeletion,
commonProjectsInitState,
commonProjectsReducer,
commonProjectsReducers,
ICommonProjectsState
} from '@common/projects/common-projects.reducer';
import {PROJECTS_ACTIONS} from '@common/projects/common-projects.consts';
import {selectSelectedProject} from '@common/core/reducers/projects.reducer';
import {checkProjectForDeletion, resetReadyToDelete, setProjectReadyForDeletion} from '@common/projects/common-projects.actions';
export type IProjectReadyForDeletion = CommonProjectReadyForDeletion;
@@ -16,25 +15,33 @@ export interface IProjectsState extends ICommonProjectsState {
}
const projectsInitState: IProjectsState = {
...commonProjectsInitState,
projectReadyForDeletion: {
project: null, experiments: null, models: null
}
};
export const projectsReducer = (state: IProjectsState = projectsInitState, action): IProjectsState => {
switch (action.type) {
case PROJECTS_ACTIONS.CHECK_PROJECT_FOR_DELETION:
return {...state, projectReadyForDeletion: {...projectsInitState.projectReadyForDeletion, project: action.payload.project}};
case PROJECTS_ACTIONS.RESET_READY_TO_DELETE:
return {...state, projectReadyForDeletion: projectsInitState.projectReadyForDeletion};
case PROJECTS_ACTIONS.SET_PROJECT_READY_FOR_DELETION:
return {...state, projectReadyForDeletion: {...state.projectReadyForDeletion, ...action.payload.readyForDeletion}};
default:
return commonProjectsReducer(state, action);
...commonProjectsInitState,
projectReadyForDeletion: {
project: null, experiments: null, models: null
}
};
export const projectsReducer = createReducer(
projectsInitState,
on(checkProjectForDeletion, (state, action) => ({
...state,
projectReadyForDeletion: {
...projectsInitState.projectReadyForDeletion,
project: action.project
}
})),
on(resetReadyToDelete, state => ({...state, projectReadyForDeletion: projectsInitState.projectReadyForDeletion})),
on(setProjectReadyForDeletion, (state, action) => ({
...state,
projectReadyForDeletion: {
...state.projectReadyForDeletion,
...action.readyForDeletion
}
})),
...commonProjectsReducers
);
export const projects = state => state.projects as IProjectsState;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const selectShowHidden = createSelector(projects, (state) => false);

View File

@@ -1,8 +1,8 @@
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {ProfileNameComponent} from '../../webapp-common/settings/admin/profile-name/profile-name.component';
import {WebappConfigurationComponent} from '../../webapp-common/settings/webapp-configuration/webapp-configuration.component';
import {WorkspaceConfigurationComponent} from '../../webapp-common/settings/workspace-configuration/workspace-configuration.component';
import {ProfileNameComponent} from '@common/settings/admin/profile-name/profile-name.component';
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';
const routes: Routes = [
@@ -34,5 +34,5 @@ const routes: Routes = [
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class SettingsRoutingModule { }
export class SettingsRoutingModule {}

View File

@@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SettingsRoutingModule } from './settings-routing.module';
import { SettingsComponent } from '../settings/settings.component';
import {SMMaterialModule} from '../../webapp-common/shared/material/material.module';
import {SMMaterialModule} from '@common/shared/material/material.module';
import {SMSharedModule} from '@common/shared/shared.module';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {SharedModule} from '~/shared/shared.module';

View File

@@ -21,7 +21,7 @@ export const selectBreadcrumbsStringsBase = createSelector(
(project, experiment, model, projects) =>
({project, experiment, model, projects}) as IBreadcrumbs);
export function prepareNames(data: IBreadcrumbs, noSubProjects?: boolean) {
export const prepareNames = (data: IBreadcrumbs, noSubProjects?: boolean) => {
const project = prepareLinkData(data.project, true);
if (data.project) {
const subProjects = [];
@@ -39,42 +39,33 @@ export function prepareNames(data: IBreadcrumbs, noSubProjects?: boolean) {
].find(proj => currentName === proj.name);
subProjects.push(foundProject);
});
const subProjectsLinks = subProjects.map(proj => ({
name: proj?.name.substring(proj?.name.lastIndexOf('/') + 1),
url: (noSubProjects || (proj?.name === data.project?.name && data.project?.sub_projects?.length===0)) ? '' : `projects/${proj?.id}/projects`
const subProjectsLinks = subProjects.map(subProject => ({
name: subProject?.name.substring(subProject?.name.lastIndexOf('/') + 1),
url: (noSubProjects || (subProject?.name === data.project?.name && data.project?.sub_projects?.length === 0)) ?
`projects/${subProject?.id}` :
`projects/${subProject?.id}/projects`
})) as { name: string; url: string }[];
project.name = project?.name.substring(project.name.lastIndexOf('/') + 1);
project.subCrumbs = subProjectsLinks;
}
const task = prepareLinkData(data.task);
const experiment = (data.experiment) ? prepareLinkData(data.experiment, true) : {};
const model = prepareLinkData(data.model, true);
const overview = formatStaticCrumb('overview');
const output = formatStaticCrumb('');
const accountAdministration = formatStaticCrumb('account-administration');
const experiments = formatStaticCrumb('experiments');
const models = formatStaticCrumb('models');
const compare = formatStaticCrumb('compare-experiments');
return {
':projectId' : project,
':experimentId' : experiment,
':modelId' : model,
...(project.url !=='*' && {':projectId': project}),
':taskId' : task,
':controllerId': experiment,
'compare-experiments': compare,
output,
experiments,
overview,
models,
execution: formatStaticCrumb('execution'),
'hyper-params' : formatStaticCrumb('hyper-params'),
artifacts: formatStaticCrumb('artifacts'),
general: formatStaticCrumb('general'),
log: formatStaticCrumb('logs'),
scalar: formatStaticCrumb('scalars'),
plots: formatStaticCrumb('plots'),
accountAdministration,
debugImages: formatStaticCrumb('Debug Samples'),
profile: {url: 'profile', name: 'Profile'},
'webapp-configuration': {url: 'webapp-configuration', name: 'Configuration'},
'workspace-configuration': {url: 'workspace-configuration', name: 'Workspace'},
};
};

View File

@@ -3,7 +3,7 @@
<div class="side-nav">
<div class="item logo">
<div class="item-icon">
<img src="../../../assets/c-logo.svg?v=1" class="logo-a">
<img src="assets/c-logo.svg?v=1" class="logo-a" alt="logo">
</div>
<div class="caption">
<!-- <img src="../../../assets/logo-white.png" class="logo-full">-->

View File

@@ -2,7 +2,7 @@
@font-face {
font-family: '#{$icomoon-font-family}';
src: url('./#{$icomoon-font-family}.ttf?dn2wcr') format('truetype');
src: url('./#{$icomoon-font-family}.ttf?medlz4') format('truetype');
font-weight: normal;
font-style: normal;
font-display: block;
@@ -413,11 +413,6 @@
content: $al-ico-zoom-to-fit;
}
}
.al-ico-kibana-code {
&:before {
content: $al-ico-kibana-code;
}
}
.al-ico-reset {
&:before {
content: $al-ico-reset;
@@ -1136,6 +1131,46 @@
content: $al-ico-run;
}
}
.al-ico-table-view {
&:before {
content: $al-ico-table-view;
}
}
.al-ico-experiment-view {
&:before {
content: $al-ico-experiment-view;
}
}
.al-ico-code-file {
&:before {
content: $al-ico-code-file;
}
}
.al-ico-code-square {
&:before {
content: $al-ico-code-square;
}
}
.al-ico-video {
&:before {
content: $al-ico-video;
}
}
.al-ico-less-than {
&:before {
content: $al-ico-less-than;
}
}
.al-ico-greater-than {
&:before {
content: $al-ico-greater-than;
}
}
.al-ico-toogle-graph {
&:before {
content: $al-ico-toogle-graph;
}
}
.al-ico-status-draft {
&:before {
content: $al-ico-status-draft;

View File

@@ -79,7 +79,6 @@ $al-ico-zoom-in: "\ea26";
$al-ico-zoom-out: "\ea27";
$al-ico-search: "\e9e1";
$al-ico-zoom-to-fit: "\ea28";
$al-ico-kibana-code: "\e9a4";
$al-ico-reset: "\e9dd";
$al-ico-import: "\e99e";
$al-ico-export: "\e9b6";
@@ -219,6 +218,14 @@ $al-ico-console: "\e9bc";
$al-ico-link-arrow: "\e9bf";
$al-ico-broken-file: "\e9c0";
$al-ico-run: "\e9c1";
$al-ico-table-view: "\e9c2";
$al-ico-experiment-view: "\e9c3";
$al-ico-code-file: "\e9a4";
$al-ico-code-square: "\e9c4";
$al-ico-video: "\e9c5";
$al-ico-less-than: "\e9c6";
$al-ico-greater-than: "\e9c7";
$al-ico-toogle-graph: "\e9c8";
$al-ico-status-draft: "\e902";
$al-ico-status-published: "\e906";
$al-ico-status-aborted-sec: "\e918";

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="10" height="10" viewBox="0 0 10 10">
<path id="checkers-2" d="M8,10V8h2v2ZM4,10V8H6v2ZM0,10V8H2v2ZM6,8V6H8V8ZM2,8V6H4V8ZM8,6V4H6V2H8V4h2V6ZM4,6V4H6V6ZM0,6V4H2V6ZM2,4V2H0V0H2V2H4V4ZM8,2V0h2V2ZM4,2V0H6V2Z" fill="#ccc"/>
</svg>

After

Width:  |  Height:  |  Size: 319 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -1,28 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="88" height="88" viewBox="0 0 88 88">
<defs>
<style>
.cls-11,.cls-4,.cls-7{fill:#4d66ff}.cls-4{opacity:.692}.cls-12{fill:#5a658e}.cls-7{opacity:.246}.cls-10{fill:#d3ff00}
</style>
</defs>
<g id="icon-researcher" transform="translate(-.456)">
<path id="icon-rec" fill="none" d="M0 0H88V88H0z" transform="translate(.456)"/>
<circle id="e" cx="1.5" cy="1.5" r="1.5" fill="#4d66ff" opacity="0.56" transform="translate(64.456 46)"/>
<circle id="e-2" cx="2.5" cy="2.5" r="2.5" fill="#4d66ff" opacity="0.584" transform="translate(60.456 39)"/>
<circle id="e-3" cx="1" cy="1" r="1" class="cls-4" transform="translate(4.456 27)"/>
<circle id="e-4" cx="1.5" cy="1.5" r="1.5" fill="#4d66ff" opacity="0.913" transform="translate(66.456 34)"/>
<path id="r" fill="#5a658e" d="M0 0H3V1H0z" opacity="0.796" transform="scale(-1) rotate(79 -8.284 -23.223)"/>
<circle id="e-5" cx="1" cy="1" r="1" class="cls-7" transform="translate(38.456 9)"/>
<path id="p" fill="#4d66ff" d="M1 0l1 5H0z" opacity="0.717" transform="scale(-1) rotate(-57 -91.262 49.5)"/>
<path id="p-2" fill="#5a658e" d="M1.5 0L3 3H0z" opacity="0.228" transform="rotate(-144 18.803 29.311)"/>
<path id="p-3" d="M2 0l2 2H0z" class="cls-10" transform="rotate(-49.98 30.225 -10.01)"/>
<path id="p-4" d="M1 0l1 4H0z" class="cls-10" transform="rotate(21 8.147 160.502)"/>
<circle id="e-6" cx=".5" cy=".5" r=".5" class="cls-11" transform="translate(58.456 2)"/>
<circle id="e-7" cx="1" cy="1" r="1" class="cls-4" transform="translate(18.456 40)"/>
<circle id="e-8" cx="1" cy="1" r="1" class="cls-7" transform="translate(10.456 41)"/>
<g id="researcher" transform="translate(20.456 17)">
<path id="researcher-2" d="M34.828 10.422a9.217 9.217 0 0 0 1.489-2.195 1 1 0 0 0 0-.882L34.982 4.63a1.631 1.631 0 0 0-.239-.316C30.541 0 29.19 0 28.747 0a2.043 2.043 0 0 0-1.338.687 3.171 3.171 0 0 1-3.68.723c-3.3-1.116-8.283 1.524-10.707 3.261C10.4 6.555 10.4 7.45 10.4 7.786c0 1.078.008 2.922 2.11 3.519-.866 1.541-2.054 4.09.352 6.59a8.672 8.672 0 0 1 .7 1.087c.065 5.754 4.575 12.378 10.375 12.378 4.828 0 11.047-6.5 11.131-13.45.057-.057.145-.133.208-.188.119-.1.234-.2.33-.3 1.673-1.749.483-5.406-.77-7M12.4 7.9c.716-1.319 7.443-5.7 10.692-4.6a5.134 5.134 0 0 0 5.645-1.119c.06-.052.113-.1.164-.146.524.191 1.894 1.1 4.361 3.616L34.3 7.767a5.7 5.7 0 0 1-1.3 1.62 1.491 1.491 0 0 0-.48.112c-1.226 0-3.41-.063-4.388-1.057a5.84 5.84 0 0 0-4.717-1.656 2.2 2.2 0 0 0-2.129 1.375.921.921 0 0 1-.43.482l-1.5.763a.877.877 0 0 1-.392.094h-4.899c-1.62 0-1.669-.342-1.67-1.6m2.837 9.881V15.43a1.038 1.038 0 0 1 1.037-1.037h5.81a.164.164 0 0 1 .161.161v3.23a1.038 1.038 0 0 1-1.04 1.037h-4.931a1.022 1.022 0 0 1-.782-.376 2.973 2.973 0 0 0-.226-.514 1.194 1.194 0 0 1-.029-.147m8.7 11.576c-3.608 0-7.922-4.242-8.335-9.663a2.02 2.02 0 0 0 .675.124H21.2a2.04 2.04 0 0 0 2.037-2.037v-1.356h1.67v1.356a2.04 2.04 0 0 0 2.037 2.037h4.934a2.011 2.011 0 0 0 1.013-.281c-.893 5.191-5.458 9.82-8.965 9.82m8.989-11.576a1.038 1.038 0 0 1-1.037 1.037h-4.931a1.038 1.038 0 0 1-1.037-1.037v-3.23a.164.164 0 0 1 .161-.161h5.81a1.038 1.038 0 0 1 1.037 1.037zm1.235-1.749c-.055.058-.123.114-.19.172l-.045.041v-.818a2.039 2.039 0 0 0-2.037-2.037h-5.81a1.162 1.162 0 0 0-1.161 1.161v.874H23.24v-.874a1.163 1.163 0 0 0-1.161-1.161h-5.81a2.039 2.039 0 0 0-2.037 2.037v.992c-1.336-1.461-.766-2.78.2-4.464.093-.162.176-.311.257-.458h4.267a2.879 2.879 0 0 0 1.3-.312l1.5-.762a2.944 2.944 0 0 0 1.369-1.494c.037-.088.06-.146.283-.146A3.91 3.91 0 0 1 26.7 9.844c1.628 1.656 4.615 1.656 6.051 1.656a2.011 2.011 0 0 0 .348-.032 5.874 5.874 0 0 1 1.243 2.756 2.314 2.314 0 0 1-.189 1.811" class="cls-12"/>
<path id="researcher-3" d="M36.274 33.916a23.283 23.283 0 0 0-4.488-2.209c-1.8-.656-3.428.718-4.872 1.927-1.039.87-2.114 1.77-2.984 1.77s-1.947-.9-2.987-1.77c-1.446-1.209-3.086-2.583-4.875-1.925C6.457 35.214 0 44.742 0 55.419a1 1 0 0 0 1 1h32.5a1 1 0 0 0 0-2H2.021a23.053 23.053 0 0 1 13.937-20.5l7.1 12.511a1 1 0 0 0 1.739 0l7.052-12.418a14.293 14.293 0 0 1 3.354 1.6 1 1 0 1 0 1.069-1.689M23.93 43.9l-5.525-9.732c.406.288.833.642 1.254.995 1.315 1.1 2.673 2.236 4.271 2.236s2.955-1.138 4.269-2.237c.426-.358.857-.718 1.267-1.01z" class="cls-12"/>
<path id="bottle" d="M47.569 43.024V36.6h.723a1 1 0 0 0 1-1V34a1 1 0 0 0-1-1h-8.536a1 1 0 0 0-1 1v1.6a1 1 0 0 0 1 1h.722v6.373l-.039.089c-2.538 2.184-4.647 6.169-4.647 8.832a3.793 3.793 0 0 0 3.073 3.831 39.854 39.854 0 0 0 10.317 0 3.794 3.794 0 0 0 3.074-3.831c0-2.664-2.159-6.709-4.687-8.866m-5.676 1.425a1.017 1.017 0 0 0 .283-.373l.217-.495a.993.993 0 0 0 .085-.4V36.6h3.091v6.583a1.079 1.079 0 0 0 .025.226l.091.392a1.007 1.007 0 0 0 .357.561 11.942 11.942 0 0 1 2.612 3.092 9.635 9.635 0 0 0-4.539 1.383c-1.553.981-2.187.8-3.724.355-.48-.138-1.046-.295-1.7-.433a13.159 13.159 0 0 1 3.2-4.306m6.72 9.355a17.259 17.259 0 0 1-4.5.368h-.17a17.548 17.548 0 0 1-4.505-.368c-1.167-.347-1.642-.9-1.642-1.914a5.254 5.254 0 0 1 .193-1.241c.715.134 1.311.305 1.853.461a8.186 8.186 0 0 0 2.3.432 5.6 5.6 0 0 0 3.049-1.017 8.177 8.177 0 0 1 4.033-1.129c.134-.013.276-.031.412-.044a7.076 7.076 0 0 1 .622 2.531c0 1.013-.476 1.567-1.643 1.914" class="cls-11"/>
</g>
</g>
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
<circle cx="47.43" cy="34.51" r="1.5" fill="#4d66ff" opacity="0.56"/>
<circle cx="45.15" cy="30.22" r="2.5" fill="#4d66ff" opacity="0.58"/>
<circle cx="48.87" cy="25.9" r="1.5" fill="#4d66ff" opacity="0.91"/>
<path d="M7.91,60.5a1,1,0,0,1-1-1c0-10.68,6.45-20.2,16.07-23.71,1.78-.66,3.43.72,4.87,1.92,1,.87,2.12,1.77,3,1.77s1.94-.9,3-1.77c1.45-1.2,3.08-2.58,4.87-1.92A22.83,22.83,0,0,1,43.18,38a1,1,0,0,1,.31,1.38h0a1,1,0,0,1-1.38.31h0a13.94,13.94,0,0,0-3.35-1.6l-7,12.41a1,1,0,0,1-1.74,0L22.87,38A23.06,23.06,0,0,0,8.93,58.5H40.41a1,1,0,0,1,0,2ZM30.84,48l5.53-9.75c-.41.29-.84.65-1.26,1-1.32,1.1-2.67,2.23-4.27,2.23s-3-1.13-4.27-2.23c-.42-.35-.85-.71-1.26-1ZM20.46,23.06A8.61,8.61,0,0,0,19.77,22c-2.41-2.5-1.22-5.05-.36-6.59-2.1-.6-2.11-2.44-2.11-3.52,0-.34,0-1.23,2.63-3.12C22.36,7,27.33,4.37,30.64,5.49a3.17,3.17,0,0,0,3.68-.72,2,2,0,0,1,1.34-.69c.44,0,1.79,0,6,4.31a1.72,1.72,0,0,1,.23.32l1.34,2.71a1,1,0,0,1,0,.89,9.11,9.11,0,0,1-1.49,2.19c1.25,1.59,2.44,5.25.77,7l-.33.31L42,22c-.08,7-6.3,13.45-11.13,13.45-5.8,0-10.31-6.62-10.38-12.38ZM30.84,33.44c3.5,0,8.07-4.63,9-9.82a2,2,0,0,1-1,.28H33.86a2,2,0,0,1-2-2h0V20.5H30.15v1.36a2,2,0,0,1-2,2H23.17a2,2,0,0,1-.67-.13c.42,5.43,4.73,9.67,8.34,9.67Zm2-14.8v3.23a1,1,0,0,0,1,1h4.93a1,1,0,0,0,1-1V19.51a1,1,0,0,0-1-1H33a.17.17,0,0,0-.16.17Zm-10.68.87v2.36l0,.14a3,3,0,0,1,.23.52,1,1,0,0,0,.78.37h4.93a1,1,0,0,0,1-1h0V18.64a.17.17,0,0,0-.16-.17H23.18a1,1,0,0,0-1,1ZM21.34,16c-1,1.68-1.54,3-.2,4.46v-1a2,2,0,0,1,2-2H29a1.18,1.18,0,0,1,1.16,1.17v.87h1.67v-.87A1.16,1.16,0,0,1,33,17.47h5.81a2.06,2.06,0,0,1,2,2v.82l0,0,.19-.17a2.29,2.29,0,0,0,.19-1.81A5.88,5.88,0,0,0,40,15.55a2.07,2.07,0,0,1-.35,0c-1.44,0-4.42,0-6-1.65a3.89,3.89,0,0,0-3.29-1.06c-.23,0-.25.06-.29.14a2.93,2.93,0,0,1-1.36,1.5l-1.5.76a2.84,2.84,0,0,1-1.3.32H21.6l-.26.45ZM35,12.52c1,1,3.17,1.06,4.39,1.06a1.6,1.6,0,0,1,.48-.11,5.8,5.8,0,0,0,1.3-1.62l-1-2.11C37.7,7.22,36.33,6.31,35.8,6.12l-.16.15A5.15,5.15,0,0,1,30,7.39C26.74,6.29,20,10.66,19.3,12c0,1.25.05,1.6,1.67,1.6h4.89a1,1,0,0,0,.39-.1l1.5-.76a.94.94,0,0,0,.43-.49,2.2,2.2,0,0,1,2.13-1.37A5.81,5.81,0,0,1,35,12.52Z" fill="#5a658e"/>
<path d="M54.48,47.1V40.68h.72a1,1,0,0,0,1-1v-1.6a1,1,0,0,0-1-1H46.66a1,1,0,0,0-1,1v1.6a1,1,0,0,0,1,1h.73v6.37l0,.09C44.81,49.33,42.7,53.31,42.7,56a3.81,3.81,0,0,0,3.07,3.83,40.51,40.51,0,0,0,10.32,0A3.8,3.8,0,0,0,59.16,56c0-2.67-2.15-6.71-4.68-8.87M48.8,48.53a.93.93,0,0,0,.28-.37l.22-.49a1,1,0,0,0,.09-.4V40.68h3.09v6.58a1.64,1.64,0,0,0,0,.23l.09.39a1.05,1.05,0,0,0,.36.56,12.08,12.08,0,0,1,2.61,3.09A9.76,9.76,0,0,0,51,52.92c-1.55,1-2.18.8-3.72.35-.48-.14-1-.29-1.7-.43a13.19,13.19,0,0,1,3.2-4.31m6.72,9.36a17.45,17.45,0,0,1-4.5.37h-.17a17.52,17.52,0,0,1-4.51-.37C45.18,57.54,44.7,57,44.7,56a5.34,5.34,0,0,1,.2-1.24c.71.14,1.31.31,1.85.46a8.15,8.15,0,0,0,2.3.44,5.68,5.68,0,0,0,3.05-1,8.16,8.16,0,0,1,4-1.13l.41,0A6.87,6.87,0,0,1,57.16,56c0,1-.47,1.57-1.64,1.91" fill="#4d66ff"/>
</svg>

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -173,6 +173,7 @@ span.highlight-text {
}
hr {
border: none;
border-top: 1px solid rgba(0, 0, 0, .1);
}

View File

@@ -47,7 +47,8 @@ export const ICONS = {
PLUGIN: 'al-ico-plugin',
ADD: 'fa-plus',
TREE: 'fa-code-branch',
TABLE: 'fa-table',
TABLE: 'al-ico-table-view',
DETAILS: 'al-ico-experiment-view',
SELECTED: 'fa-check-square-o',
PROJECT: 'fa-list-alt',
FOCUS: 'fa-crosshairs',

View File

@@ -79,6 +79,6 @@ export const neverShowPopupAgain = createAction(VIEW_PREFIX + 'NEVER_SHOW_POPUP_
export const plotlyReady = createAction(VIEW_PREFIX + '[plotly ready]');
export const aceReady = createAction(VIEW_PREFIX + '[ace ready]');
export const openAppsAwarenessDialog = createAction(VIEW_PREFIX + '[apps awareness dialog]',
props<{appsYouTubeIntroLink}>()
props<{appsYouTubeIntroVideoId}>()
);

View File

@@ -15,13 +15,6 @@ import {MetricColumn} from '@common/shared/utils/tableParamEncode';
import {ProjectStatsGraphData} from '@common/core/reducers/projects.reducer';
export const PROJECTS_PREFIX = '[ROOT_PROJECTS] ';
export const SET_PROJECTS = PROJECTS_PREFIX + 'SET_PROJECTS';
export const RESET_PROJECTS = PROJECTS_PREFIX + 'RESET_PROJECTS';
export const REFETCH_PROJECTS = PROJECTS_PREFIX + 'REFETCH_PROJECTS';
export const SET_LAST_UPDATE = PROJECTS_PREFIX + 'SET_LAST_UPDATE';
export const RESET_SELECTED_PROJECT = PROJECTS_PREFIX + 'RESET_SELECTED_PROJECT';
export const RESET_PROJECT_SELECTION = PROJECTS_PREFIX + 'RESET_PROJECT_SELECTION';
export const UPDATE_PROJECT = PROJECTS_PREFIX + 'UPDATE_PROJECT';
export interface TagColor {
foreground: string;
@@ -35,20 +28,20 @@ export const getAllSystemProjects = createAction(
export const updateProject = createAction(
UPDATE_PROJECT,
PROJECTS_PREFIX + 'UPDATE_PROJECT',
props<{ id: string; changes: Partial<ProjectsUpdateRequest> }>()
);
export const setAllProjects = createAction(
SET_PROJECTS,
PROJECTS_PREFIX + 'SET_PROJECTS',
props<{ projects: Project[]; updating?: boolean }>()
);
export const resetProjects = createAction(RESET_PROJECTS);
export const refetchProjects = createAction(REFETCH_PROJECTS);
export const resetProjects = createAction(PROJECTS_PREFIX + 'RESET_PROJECTS');
export const refetchProjects = createAction(PROJECTS_PREFIX + 'REFETCH_PROJECTS');
export const setLastUpdate = createAction(
SET_LAST_UPDATE,
PROJECTS_PREFIX + 'SET_LAST_UPDATE',
props<{ lastUpdate: string }>());
export const updateProjectCompleted = createAction(
@@ -114,12 +107,7 @@ export const setCompanyTags = createAction(
props<{ tags: string[]; systemTags: string[] }>()
);
export const setAllProjectTags = createAction(
PROJECTS_PREFIX + '[set all projects tags]',
props<{ tags: string[]; systemTags: string[] }>()
);
export const addAllProjectTags = createAction(
export const addProjectTags = createAction(
PROJECTS_PREFIX + '[add all projects tags]',
props<{ tags: string[]; systemTags: string[] }>()
);

View File

@@ -11,7 +11,7 @@ import {
openTagColorsMenu, refetchProjects,
resetProjects, resetProjectSelection,
setCompanyTags,
setGraphData, setLastUpdate, setAllProjectTags,
setGraphData, setLastUpdate,
setTags
} from '../actions/projects.actions';
@@ -140,7 +140,7 @@ export class ProjectsEffects {
// eslint-disable-next-line @typescript-eslint/naming-convention
switchMap(() => this.projectsApi.projectsGetProjectTags({filter: {system_tags: ['pipeline']}})
.pipe(
map((res: OrganizationGetTagsResponse) => setAllProjectTags({tags: res.tags, systemTags: res.system_tags})),
map((res: OrganizationGetTagsResponse) => setTags({tags: res.tags})),
catchError(error => [requestFailed(error)])
)
)

View File

@@ -1,17 +1,6 @@
import {createSelector} from '@ngrx/store';
import {on, createReducer, createSelector} from '@ngrx/store';
import * as projectsActions from '../actions/projects.actions';
import {
addAllProjectTags,
setAllProjects,
setCompanyTags,
setGraphData,
setLastUpdate,
setMetricVariant, setAllProjectTags,
setTagColors,
setTags,
setTagsFilterByProject,
TagColor
} from '../actions/projects.actions';
import {TagColor} from '../actions/projects.actions';
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';
@@ -36,7 +25,6 @@ export interface RootProjects {
archive: boolean;
deep: boolean;
projectTags: string[];
allProjectsTags: string[];
companyTags: string[];
systemTags: string[];
tagsColors: { [tag: string]: TagColor };
@@ -52,7 +40,6 @@ const initRootProjects: RootProjects = {
archive: false,
deep: false,
projectTags: [],
allProjectsTags: [],
companyTags: [],
systemTags: [],
tagsColors: {},
@@ -72,7 +59,6 @@ export const selectIsDeepMode = createSelector(projects, state => state.deep);
export const selectTagsFilterByProject = createSelector(projects, state => state.tagsFilterByProject);
export const selectProjectTags = createSelector(projects, state => state.projectTags);
export const selectCompanyTags = createSelector(projects, state => state.companyTags);
export const selectAllProjectsTagsTags = createSelector(projects, state => state.allProjectsTags);
// eslint-disable-next-line @typescript-eslint/naming-convention
export const selectProjectSystemTags = createSelector(projects, state => getSystemTags({system_tags: state.systemTags} as ITableExperiment));
export const selectTagsColors = createSelector(projects, state => state.tagsColors);
@@ -80,85 +66,62 @@ export const selectLastUpdate = createSelector(projects, (state): string => stat
export const selectTagColors = createSelector(selectTagsColors,
(tagsColors, props: { tag: string }) => tagsColors[props.tag]);
const selectSelectedProjectsMetricVariant = createSelector(projects, state => state.graphVariant);
export const selectSelectedMetricVariant = createSelector(selectSelectedProjectsMetricVariant,
(projectsVariant, projectId: string) => projectsVariant[projectId]);
export const selectSelectedMetricVariantForCurrProject = createSelector(
selectSelectedProjectsMetricVariant, selectSelectedProjectId,
(projectsVariant, projectId) => projectsVariant[projectId]);
export const selectGraphData = createSelector(projects, state => state.graphData);
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')};
export const projectsReducer = (state: RootProjects = initRootProjects, action) => {
switch (action.type) {
case projectsActions.resetProjects.type:
return {...state, projects: [], lastUpdate: null};
case projectsActions.setAllProjects.type:
const payload = action as ReturnType<typeof setAllProjects>;
let newProjects = state.projects;
if (payload.updating) {
payload.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, ...payload.projects];
}
return {...state, projects: sortByField(newProjects, 'name')};
case projectsActions.setSelectedProjectId.type: {
const projectId = (action as ReturnType<typeof projectsActions.setSelectedProjectId>).projectId;
return {
...state,
...(state.selectedProject?.id !== projectId && {archive: initRootProjects.archive}),
graphData: initRootProjects.graphData,
};
}
case projectsActions.setSelectedProject.type:
return {...state, selectedProject: action.project};
case projectsActions.deletedProjectFromRoot.type:
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))};
case projectsActions.resetSelectedProject.type:
return {...state, selectedProject: initRootProjects.selectedProject};
case projectsActions.updateProjectCompleted.type: {
const payload = action as ReturnType<typeof projectsActions.updateProjectCompleted>;
return {
...state,
selectedProject: {...state.selectedProject, ...payload.changes},
projects: state.projects.map(project => project.id === payload.id ? project : {...project, ...payload.changes})
};
}
case projectsActions.setArchive.type:
return {...state, archive: action.archive};
case projectsActions.setDeep.type:
return {...state, deep: action.deep};
case setTags.type:
return {...state, projectTags: action.tags};
case setTagsFilterByProject.type:
return {...state, tagsFilterByProject: action.tagsFilterByProject};
case setCompanyTags.type:
return {...state, companyTags: action.tags, systemTags: action.systemTags};
case setAllProjectTags.type:
return {...state, allProjectsTags: action.tags};
case addAllProjectTags.type:
return {...state, allProjectsTags: Array.from(new Set(state.allProjectsTags.concat(action.tags))).sort()};
case setTagColors.type:
return {...state, tagsColors: {...state.tagsColors, [action.tag]: action.colors}};
case setMetricVariant.type: {
const payLoad = action as ReturnType<typeof setMetricVariant>;
return {...state, graphVariant: {...state.graphVariant, [payLoad.projectId]: payLoad.col}};
}
case setGraphData.type:
return {...state, graphData: (action as ReturnType<typeof setGraphData>).stats};
case setLastUpdate.type:
return {...state, lastUpdate: (action as ReturnType<typeof setLastUpdate>).lastUpdate};
default:
return state;
}
};
}),
on(projectsActions.setSelectedProjectId, (state, action) => {
const projectId = action.projectId;
return {
...state,
...(state.selectedProject?.id !== projectId && {archive: initRootProjects.archive}),
graphData: initRootProjects.graphData,
};
}),
on(projectsActions.setSelectedProject, (state, action) => ({...state, selectedProject: action.project})),
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})),
on(projectsActions.updateProjectCompleted, (state, action) => ({
...state,
selectedProject: {...state.selectedProject, ...action.changes},
projects: state.projects.map(project => project.id === action.id ? project : {...project, ...action.changes})
})),
on(projectsActions.setArchive, (state, action) => ({...state, archive: action.archive})),
on(projectsActions.setDeep, (state, action) => ({...state, deep: action.deep})),
on(projectsActions.setTags, (state, action) => ({...state, projectTags: action.tags})),
on(projectsActions.setTagsFilterByProject, (state, action) => ({...state, tagsFilterByProject: action.tagsFilterByProject})),
on(projectsActions.setCompanyTags, (state, action) => ({...state, companyTags: action.tags, systemTags: action.systemTags})),
on(projectsActions.addProjectTags, (state, action) => ({...state, projectTags: Array.from(new Set(state.projectTags.concat(action.tags))).sort()})),
on(projectsActions.setTagColors, (state, action) => ({...state, tagsColors: {...state.tagsColors, [action.tag]: action.colors}})),
on(projectsActions.setMetricVariant, (state, action) => ({
...state, graphVariant: {...state.graphVariant, [action.projectId]: action.col}
})),
on(projectsActions.setGraphData, (state, action) => ({...state, graphData: action.stats})),
on(projectsActions.setLastUpdate, (state, action) => ({...state, lastUpdate: action.lastUpdate})),
);

View File

@@ -0,0 +1,33 @@
import { Injectable } from '@angular/core';
import {Store} from '@ngrx/store';
import {selectAppVisible, selectAutoRefresh} from '@common/core/reducers/view.reducer';
import {interval, Subject} from 'rxjs';
import {AUTO_REFRESH_INTERVAL} from '~/app.constants';
import {filter, withLatestFrom} from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class RefreshService {
private _tick = new Subject<boolean>();
get tick() {
return this._tick;
}
constructor(private store: Store) {
interval(AUTO_REFRESH_INTERVAL)
.pipe(
withLatestFrom(
this.store.select(selectAutoRefresh),
this.store.select(selectAppVisible)
),
filter(([, auto, visible]) => auto && visible)
)
.subscribe(() => this._tick.next(null))
}
trigger(auto = false) {
this._tick.next(auto);
}
}

View File

@@ -14,12 +14,11 @@ import {ProjectsSharedModule} from '../../features/projects/shared/projects-shar
import {CommonExperimentSharedModule} from '../experiments/shared/common-experiment-shared.module';
import {CommonProjectsModule} from '../projects/common-projects.module';
import {SharedModule} from '../../shared/shared.module';
import { WelcomeMessageComponent } from './dumb/welcome-message/welcome-message.component';
import {FormsModule} from '@angular/forms';
@NgModule({
declarations: [DashboardProjectsComponent, DashboardExperimentsComponent, RecentExperimentTableComponent, WelcomeMessageComponent],
exports : [DashboardProjectsComponent, DashboardExperimentsComponent],
declarations: [DashboardProjectsComponent, DashboardExperimentsComponent, RecentExperimentTableComponent],
exports: [DashboardProjectsComponent, DashboardExperimentsComponent],
imports: [
CommonModule,
SMSharedModule,

View File

@@ -5,7 +5,7 @@
</div>
<div>
<button *ngIf="(recentProjectsList$ | async).length >= cardsInRow"
class="btn btn-primary d-flex align-items-center"
class="btn btn-cml-primary d-flex align-items-center"
(click)="openCreateProjectDialog()">
<i class="al-icon al-color sm blue-400 al-ico-add mr-2"></i>NEW PROJECT
</button>

View File

@@ -1,105 +0,0 @@
<sm-dialog-template [displayX]="true"
[header]="queue? 'NO WORKERS ASSIGNED TO QUEUE': (step === 1 ? 'Welcome to ClearML' : 'GETTING STARTED')"
(xClicked)="closeDialog()" [closeOnX]="false"
[iconClass]="queue?'al-ico-queues': 'i-welcome-researcher'">
<div *ngIf="step === 1; else configStep" class="welcome-content">
<div class="body">
<div class="info">
<span class="position-relative">
<i class="al-ico-card-example foreground"></i>
<span class="background"></span>
</span>
<div class="mt-2">See the pre-loaded examples to quickly get familiar with ClearMLs various capabilities.</div>
</div>
<div class="info">
<i class="al-ico-help-outlined"></i>
<div class="mt-2">Browse "Pro Tips" in the Help menu to jump start your work flow.</div>
</div>
<div class="info">
<i class="al-ico-documentation"></i>
<div class="mt-2">Check out the <a target="_blank" [href]="docsLink">ClearML docs</a> for advanced information and in depth how-tos.
</div>
</div>
</div>
<div class="separator"></div>
<div class="d-flex justify-content-center">
<button class="btn btn-neon" (click)="nextSteps($event)">GET STARTED</button>
</div>
</div>
<ng-template #configStep>
<div class="steps-content">
<div class="text" *ngIf="queue">
Tasks have been enqueued on the <b>{{queue?.name}}</b> queue, which is currently not serviced by any worker. They will remain in the 'pending' state until a ClearML worker services this queue.
</div>
<div *ngFor="let step of steps" class="step-container">
<div class="step-header">{{step.header}}</div>
<div class="step">{{step.title}}</div>
<div class="code">
<div class="content" #stepCode>{{step.code}}</div>
<sm-copy-clipboard
[hideBackground]="true"
[label]="''"
[copyIcon]="'far fa-lg fa-copy'"
[clipboardText]="stepCode.innerHTML"></sm-copy-clipboard>
</div>
<div *ngIf="step.subNote" class="sub-note"><i class="mr-1 fas fa-info-circle info"></i>{{step.subNote}}</div>
</div>
<div class="step-container cred-step" [class.first-step]="!credentialsCreated" [class.has-label]="credentialsLabel">
<div class="step-header">Complete the clearml configuration information as prompted.</div>
<div *ngIf="!credentialsCreated" class="d-flex align-items-end">
<mat-form-field appearance="outline"
class="label-input"
floatLabel="always">
<mat-label>Label (optional)</mat-label>
<input matInput [(ngModel)]="credentialsLabel" [disabled]="credentialsCreated" placeholder="Credentials label" name="credentials">
</mat-form-field>
<button class="mb-2 btn btn-neon create-cred-button" (click)="createCredentials()">CREATE NEW
CREDENTIALS
</button>
</div>
<div class="cred-visible" [class.invisible]="!accessKey">
<div class="code">
<div #content class="content"><span class="variable">api</span> {{ '{' }}<ng-container *ngIf="community && workspace.name">
<span class="">{{'# ' + workspace.name}}</span></ng-container>
<span class="variable">web_server</span><span class="operation">:</span> <span class="string">{{WEB_SERVER_URL}}</span>
<span class="variable">api_server</span><span class="operation">:</span> <span class="string">{{API_BASE_URL}}</span>
<ng-container *ngIf="fileBaseUrl"> <span class="variable">files_server</span><span class="operation">:</span> <span class="string">{{fileBaseUrl}}</span><ng-container *ngIf="credentialsLabel">
<span>{{'# ' + credentialsLabel}}</span></ng-container>
</ng-container> <span class="variable">credentials</span> {{ '{' }}
<span class="string">"access_key"</span> <span class="operation">=</span> <span class="string">"{{accessKey}}"</span>
<span class="string">"secret_key"</span> <span class="operation">=</span> <span class="string">"{{secretKey}}"</span>
}
}</div>
<sm-copy-clipboard
[hideBackground]="true"
[label]="''"
[copyIcon]="'far fa-lg fa-copy'"
[clipboardText]="content.textContent"></sm-copy-clipboard>
</div>
<div class="sub-note"><i class="mr-1 fas fa-info-circle info"></i>Manage your app credentials in the <a target="_blank" href="settings/workspace-configuration">workspace settings page</a></div>
</div>
</div>
<div class="step-container" *ngIf="!queue">
<div class="step">3. Integrate</div>
<div class="step sub-note">Add the following lines to your code</div>
<div class="code">
<div #content class="content"><span class="variable">from</span> {{gettingStartedContext?.packageName || 'clearml'}} <span class="variable">import</span> Task
task <span class="operation">=</span> Task.<span class="variable">init</span>(project_name<span class="operation">=</span>"my project", task_name<span class="operation">=</span>"my task")</div>
<sm-copy-clipboard
[hideBackground]="true"
[label]="''"
[copyIcon]="'far fa-lg fa-copy'"
[clipboardText]="content.textContent"></sm-copy-clipboard>
</div>
</div>
<div *ngIf="queue" class="text">
<sm-checkbox-control
fieldName="orphanedQueue"
[formData]="doNotShowAgain"
(formDataChanged)="doNotShowThisAgain($event)"
label="Dont show again"></sm-checkbox-control>
</div>
</div>
</ng-template>
</sm-dialog-template>

View File

@@ -1,8 +1,8 @@
import {createAction, props} from '@ngrx/store';
import {Task} from '../../business-logic/model/tasks/task';
import {TaskMetric} from '../../business-logic/model/events/taskMetric';
import {EventsDebugImagesResponse} from '../../business-logic/model/events/eventsDebugImagesResponse';
import {EventsGetDebugImageIterationsResponse} from '../../business-logic/model/events/eventsGetDebugImageIterationsResponse';
import {Task} from '~/business-logic/model/tasks/task';
import {TaskMetric} from '~/business-logic/model/events/taskMetric';
import {EventsDebugImagesResponse} from '~/business-logic/model/events/eventsDebugImagesResponse';
import {EventsGetDebugImageIterationsResponse} from '~/business-logic/model/events/eventsGetDebugImageIterationsResponse';
export const DEBUG_IMAGES_PREFIX = 'DEBUG_IMAGES_';
@@ -20,7 +20,7 @@ export const getDebugImagesMetrics = createAction(
export const refreshDebugImagesMetrics = createAction(
DEBUG_IMAGES_PREFIX + 'REFRESH_DEBUG_IMAGES_METRICS',
props<{ tasks: string[] }>()
props<{ tasks: string[], autoRefresh?: boolean }>()
);
export const fetchExperiments = createAction(
@@ -45,7 +45,7 @@ export const setSelectedMetric = createAction(
export const refreshMetric = createAction(
DEBUG_IMAGES_PREFIX + 'REFRESH_IMAGES_SELECTED_METRIC',
props<{ payload: TaskMetric }>()
props<{ payload: TaskMetric; autoRefresh?: boolean }>()
);
export const getNextBatch= createAction(

View File

@@ -1,13 +1,12 @@
import {Injectable} from '@angular/core';
import {Actions, createEffect, ofType} from '@ngrx/effects';
import {catchError, mergeMap, map, switchMap, withLatestFrom} from 'rxjs/operators';
import {catchError, mergeMap, map, switchMap, withLatestFrom, filter} from 'rxjs/operators';
import * as debugActions from './debug-images-actions';
import {activeLoader, deactivateLoader} from '../core/actions/layout.actions';
import {ApiTasksService} from '../../business-logic/api-services/tasks.service';
import {ApiEventsService} from '../../business-logic/api-services/events.service';
import {ApiTasksService} from '~/business-logic/api-services/tasks.service';
import {ApiEventsService} from '~/business-logic/api-services/events.service';
import {requestFailed} from '../core/actions/http.actions';
import {refreshExperiments} from '../experiments/actions/common-experiments-view.actions';
import {setRefreshing} from '../experiments-compare/actions/compare-header.actions';
import {Action, Store} from '@ngrx/store';
import {selectDebugImages, selectImageViewerScrollId} from './debug-images-reducer';
import {
@@ -16,8 +15,9 @@ import {
setDebugImageViewerScrollId,
setDisplayerBeginningOfTime, setDisplayerEndOfTime
} from './debug-images-actions';
import {EventsDebugImagesResponse} from '../../business-logic/model/events/eventsDebugImagesResponse';
import {EventsGetTaskMetricsResponse} from '../../business-logic/model/events/eventsGetTaskMetricsResponse';
import {EventsDebugImagesResponse} from '~/business-logic/model/events/eventsDebugImagesResponse';
import {EventsGetTaskMetricsResponse} from '~/business-logic/model/events/eventsGetTaskMetricsResponse';
import {COMPARE_DEBUG_IMAGES_ONLY_FIELDS} from '../experiments-compare/experiments-compare.constants';
export const ALL_IMAGES = '-- All --';
@@ -48,7 +48,8 @@ export class DebugImagesEffects {
) {}
activeLoader = createEffect(() => this.actions$.pipe(
ofType(debugActions.fetchExperiments),
ofType(debugActions.fetchExperiments, debugActions.refreshMetric, debugActions.refreshDebugImagesMetrics),
filter(action => !(action as any).autoRefresh),
map(action => activeLoader(action.type))
));
@@ -68,7 +69,7 @@ export class DebugImagesEffects {
})
.pipe(
mergeMap((res: EventsDebugImagesResponse ) => {
const actionsToShoot = [deactivateLoader(action.type), setRefreshing({payload: false})] as Action[];
const actionsToShoot = [deactivateLoader(action.type)] as Action[];
if (res.metrics[0].iterations && res.metrics[0].iterations.length > 0) {
actionsToShoot.push(debugActions.setDebugImages({res, task: action.payload.task}));
switch (action.type) {
@@ -102,7 +103,6 @@ export class DebugImagesEffects {
}),
catchError(error => [
requestFailed(error),
setRefreshing({payload: false}),
deactivateLoader(action.type),
deactivateLoader(refreshExperiments.type)
])
@@ -113,7 +113,7 @@ export class DebugImagesEffects {
fetchExperiments$ = createEffect(() => this.actions$.pipe(
ofType(debugActions.fetchExperiments),
// eslint-disable-next-line @typescript-eslint/naming-convention
switchMap((action) => this.apiTasks.tasksGetAllEx({id: action.tasks, only_fields: ['id', 'name', 'status']})
switchMap((action) => this.apiTasks.tasksGetAllEx({id: action.tasks, only_fields: COMPARE_DEBUG_IMAGES_ONLY_FIELDS})
.pipe(
mergeMap(res => [debugActions.setExperimentsNames({tasks: res.tasks}), deactivateLoader(action.type)]),
catchError(error => [requestFailed(error), deactivateLoader(action.type)])

View File

@@ -9,7 +9,7 @@ import {EventEmitter} from '@angular/core';
export class DebugImagesViewComponent {
public trackKey = (index: number, item: any) => item.iter;
public trackFrame = (index: number, item: any) => item.key;
public trackFrame = (index: number, item: any) => `${item?.key} ${item?.timestamp}`;
public iterationEvents;
@Input() experimentId;

View File

@@ -1,21 +1,26 @@
<div class="p-3 images-container">
<div class="single-debug-images-container" *ngFor="let experimentId of experimentIds; trackBy: trackExperiment">
<div class="single-debug-images-container" *ngFor="let experimentId of experimentIds; trackBy: trackExperiment" [class.separator]="experimentIds?.length > 1">
<header *ngIf="experimentIds?.length > 1">
<div
class="experiment-name">
<span [smTooltip]="experimentNames[experimentId]" matTooltipPosition="above">{{modifiedExperimentsNames[experimentId]}}</span>
</div>
<sm-experiment-compare-general-data
*ngIf="(experiments | itemById: experimentId).name"
[experiment]="experiments | itemById: experimentId"
[tags]="(experiments | itemById: experimentId)?.tags"
(copyIdClicked)="copyIdToClipboard()"
>
</sm-experiment-compare-general-data>
</header>
<div class="d-flex">
<div class="d-flex" >
<div class="metric-bar" [class.minimized]="minimized" *ngIf="!thereAreNoMetrics(experimentId)">
<label>Metric:</label>
<mat-form-field appearance="outline" [ngClass]="{'dark thin': isDarkTheme}">
<mat-select #metricSelect (selectionChange)="selectMetric($event, experimentId)" [panelClass]="isDarkTheme ? 'dark black dark-theme': 'light-theme'"
[value]="selectedMetrics[experimentId]" name="selectedMetric">
<mat-select
#metricSelect
(selectionChange)="selectMetric($event, experimentId)"
[panelClass]="isDarkTheme ? 'dark black dark-theme': 'light-theme'"
[value]="selectedMetrics[experimentId]"
>
<mat-option *ngIf="selectedMetrics[experimentId]" [value]="allImages">{{allImages}}</mat-option>
<mat-option *ngFor="let metric of optionalMetrics[experimentId]" [value]="metric">
{{metric}}
</mat-option>
<mat-option *ngFor="let metric of optionalMetrics[experimentId]" [value]="metric">{{metric}}</mat-option>
</mat-select>
</mat-form-field>
<label>Iterations:</label>
@@ -25,12 +30,9 @@
class="al-icon al-ico-next-batch al-color light-grey-blue"
smTooltip="Older images"></div>
<b
class="text-right">{{debugImages && debugImages[experimentId] && debugImages[experimentId][0][debugImages[experimentId][0].length - 1].iter}}</b>
<b class="text-right">{{debugImages?.[experimentId]?.data.slice(-1)[0].iter}}</b>
<div class="al-icon al-ico-between al-color light-blue-grey"></div>
<b>{{debugImages && debugImages[experimentId] && debugImages[experimentId][0][0].iter}}</b>
<b>{{debugImages?.[experimentId]?.data?.[0].iter}}</b>
<div [ngClass]="{'disabled': (timeIsNow$| async)[experimentId]}"
(click)="(!timeIsNow[experimentId]) && previousBatch({task: experimentId, metric: metricSelect.value})"
@@ -51,8 +53,8 @@
<h3>NO DEBUG SAMPLES</h3>
</div>
<sm-debug-images-view
*ngFor="let debugImages of debugImages[experimentId]"
[iterations]="debugImages"
*ngIf="debugImages?.[experimentId]?.data"
[iterations]="debugImages[experimentId].data"
[experimentId]="experimentId"
[title]="experimentNames && experimentNames[experimentId]"
[isMergeIterations]="mergeIterations"
@@ -62,5 +64,5 @@
>
</sm-debug-images-view>
</div>
</div>
</div>

View File

@@ -105,6 +105,10 @@ sm-debug-images-view {
min-height: 100%;
color: $blue-500;
margin: 0 12px;
&.separator:not(:last-child){
border-right: 1px solid #f2f4fc;
}
}
.bordered-experiments {

View File

@@ -1,7 +1,7 @@
import {ChangeDetectorRef, Component, ElementRef, Input, OnDestroy, OnInit} from '@angular/core';
import {ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output} from '@angular/core';
import {combineLatest, Observable, Subscription} from 'rxjs';
import {select, Store} from '@ngrx/store';
import {IExperimentInfoState} from '../../features/experiments/reducers/experiment-info.reducer';
import {IExperimentInfoState} from '~/features/experiments/reducers/experiment-info.reducer';
import {AdminService} from '~/shared/services/admin.service';
import {selectS3BucketCredentials} from '../core/reducers/common-auth-reducer';
import {MatDialog} from '@angular/material/dialog';
@@ -17,17 +17,43 @@ import {
} from './debug-images-reducer';
import {selectRouterParams} from '../core/reducers/router-reducer';
import {distinctUntilChanged, filter, map, withLatestFrom} from 'rxjs/operators';
import {Task} from '../../business-logic/model/tasks/task';
import {Task} from '~/business-logic/model/tasks/task';
import {ActivatedRoute} from '@angular/router';
import {TaskStatusEnum} from '../../business-logic/model/tasks/taskStatusEnum';
import {TaskStatusEnum} from '~/business-logic/model/tasks/taskStatusEnum';
import {ImageDisplayerComponent} from '../experiments/dumb/image-displayer/image-displayer.component';
import {selectSelectedExperiment} from '../../features/experiments/reducers';
import {selectRefreshing} from '../experiments-compare/reducers';
import {TaskMetric} from '../../business-logic/model/events/taskMetric';
import {selectSelectedExperiment} from '~/features/experiments/reducers';
import {TaskMetric} from '~/business-logic/model/events/taskMetric';
import {get, isEqual} from 'lodash/fp';
import {ALL_IMAGES} from './debug-images-effects';
import {MatSelectChange} from '@angular/material/select';
import {getSignedUrl} from '../core/actions/common-auth.actions';
import {addMessage} from '../core/actions/layout.actions';
import {RefreshService} from '@common/core/services/refresh.service';
interface Event {
timestamp: number;
type?: string;
task?: string;
iter?: number;
metric?: string;
variant?: string;
key?: string;
url?: string;
'@timestamp'?: string;
worker?: string;
}
interface Iteration {
events: Event[];
iter: number;
}
interface DebugSamples {
metrics: string[];
metric: string;
scrollId: string;
data: Iteration[]
}
@Component({
selector: 'sm-debug-images',
@@ -37,6 +63,8 @@ import {getSignedUrl} from '../core/actions/common-auth.actions';
export class DebugImagesComponent implements OnInit, OnDestroy {
@Input() isDarkTheme = false;
@Output() copyIdClicked = new EventEmitter();
private debugImagesSubscription: Subscription;
private taskNamesSubscription: Subscription;
private selectedExperimentSubscription: Subscription;
@@ -51,7 +79,7 @@ export class DebugImagesComponent implements OnInit, OnDestroy {
public beginningOfTime$: Observable<any>;
public mergeIterations: boolean;
public debugImages: { [experimentId: string]: any };
public debugImages: { [experimentId: string]: DebugSamples };
public experimentNames: { [id: string]: string } = {};
public experimentIds: string[];
public allowAutorefresh: boolean = false;
@@ -59,7 +87,7 @@ export class DebugImagesComponent implements OnInit, OnDestroy {
public noMoreData$: Observable<boolean>;
public optionalMetrics$: Observable<any>;
public optionalMetrics: any;
public selectedMetrics: {[taskId: string] : string} = {};
public selectedMetrics: { [taskId: string]: string } = {};
public beginningOfTime: any;
private beginningOfTimeSubscription: Subscription;
public timeIsNow: any;
@@ -68,6 +96,7 @@ export class DebugImagesComponent implements OnInit, OnDestroy {
readonly allImages = ALL_IMAGES;
private selectedMetric: string;
public modifiedExperimentsNames: { [id: string]: string } = {};
public experiments: Partial<Task>[];
constructor(
private store: Store<IExperimentInfoState>,
@@ -75,7 +104,8 @@ export class DebugImagesComponent implements OnInit, OnDestroy {
private dialog: MatDialog,
private changeDetection: ChangeDetectorRef,
private activeRoute: ActivatedRoute,
private elRef: ElementRef
private elRef: ElementRef,
private refresh: RefreshService
) {
this.tasks$ = this.store.select(selectTaskNames);
this.optionalMetrics$ = this.store.select(selectOptionalMetrics);
@@ -87,20 +117,22 @@ export class DebugImagesComponent implements OnInit, OnDestroy {
store.pipe(select(selectS3BucketCredentials)),
store.pipe(select(selectDebugImages))]).pipe(
map(([, debugImages]) => Object.entries(debugImages).reduce(((acc, val: any) => {
acc[val[0]] = val[1].metrics.map(metric => metric.iterations.map(iteration => {
const events = iteration.events.map(event => {
this.store.dispatch(getSignedUrl({url: event.url, config: {disableCache: event.timestamp}}));
return {
...event,
url: event.url,
variantAndMetric: this.selectedMetric === ALL_IMAGES ? `${event.metric}/${event.variant}` : ''
};
});
return {...iteration, events};
}));
acc[val[0]].metrics = val[1].metrics.map(metric => metric.metric || metric.iterations[0].events[0].metric);
acc[val[0]].metric = acc[val[0]].metrics[0];
acc[val[0]].scrollId = val[1].scroll_id;
const id = val[0];
const iterations = val[1].metrics.find(m => m.task === id).iterations;
acc[id] = {data: iterations.map(iteration => ({
iter: iteration.iter,
events: iteration.events.map(event => {
this.store.dispatch(getSignedUrl({url: event.url, config: {disableCache: event.timestamp}}));
return {
...event,
url: event.url,
variantAndMetric: this.selectedMetric === ALL_IMAGES ? `${event.metric}/${event.variant}` : ''
};
})
}))};
acc[id].metrics = val[1].metrics.map(metric => metric.metric || metric.iterations[0].events[0].metric);
acc[id].metric = acc[id].metrics[0];
acc[id].scrollId = val[1].scroll_id;
return acc;
}), {}))
).subscribe(debugImages => {
@@ -133,7 +165,6 @@ export class DebugImagesComponent implements OnInit, OnDestroy {
this.timeIsNow = timeIsNow;
});
let currentExperiment: string;
if (multipleExperiments) {
this.routerParamsSubscription = this.routerParams$
@@ -155,49 +186,55 @@ export class DebugImagesComponent implements OnInit, OnDestroy {
this.store.dispatch(getDebugImagesMetrics({tasks: this.experimentIds}));
}
this.experiments = tasks;
this.experimentNames = tasks.reduce((acc, task) => ({
...acc,
[task.id]: task.name
}), {}) as { [id: string]: string };
tasks.forEach(task => {
this.modifiedExperimentsNames[task.id] = Object.values(this.experimentNames).filter(name => name === task.name).length > 1 ? `${task.name}.${task.id.substr(0, 6)}` : task.name;
this.modifiedExperimentsNames[task.id] = Object.values(this.experimentNames).filter(name => name === task.name).length > 1 ? `${task.name}.${task.id.slice(0, 6)}` : task.name;
}
);
this.changeDetection.detectChanges();
});
// auto refresh subscription for compare only.
this.refreshingSubscription = this.store.select(selectRefreshing).pipe(
filter(({refreshing}) => refreshing),
withLatestFrom(
this.store.select(selectTimeIsNow),
)
).subscribe(([, timeIsNow]) => {
this.store.dispatch(debugActions.refreshDebugImagesMetrics({tasks: this.experimentIds}));
this.experimentIds.forEach(experiment => {
this.refresh(experiment, timeIsNow, this.debugImages);
});
}
);
} else {
this.selectedExperimentSubscription = this.store.select(selectSelectedExperiment)
.pipe(
filter(experiment => !!experiment),
withLatestFrom(
this.store.select(selectTimeIsNow),
),
).subscribe(([experiment, timeIsNow]) => {
if (currentExperiment === experiment.id && Object.keys(this.debugImages || {}).length > 0) {
this.refresh(experiment.id, timeIsNow, this.debugImages);
} else {
currentExperiment = experiment.id;
this.experimentNames = {[experiment.id]: experiment.name};
this.experimentIds = [experiment.id];
this.store.dispatch(getDebugImagesMetrics({tasks: this.experimentIds}));
}
distinctUntilChanged((previous, current) => previous?.id === current?.id)
).subscribe(experiment => {
this.experimentNames = {[experiment.id]: experiment.name};
this.experimentIds = [experiment.id];
this.store.dispatch(getDebugImagesMetrics({tasks: this.experimentIds}));
});
}
// auto refresh subscription for compare only.
this.refreshingSubscription = this.refresh.tick
.pipe(
filter(auto => !multipleExperiments || auto !== null),
withLatestFrom(
this.store.select(selectTimeIsNow),
)
)
.subscribe(([auto, timeIsNow]) => {
if (multipleExperiments) {
this.store.dispatch(debugActions.refreshDebugImagesMetrics({tasks: this.experimentIds, autoRefresh: auto}));
}
this.experimentIds.forEach(experimentId => {
if (experimentId && timeIsNow?.[experimentId] && this.debugImages[experimentId] && this.elRef.nativeElement.scrollTop < 40) {
this.store.dispatch(debugActions.refreshMetric({
payload: {
task: experimentId,
metric: this.debugImages[experimentId]?.metric,
},
autoRefresh: auto
}));
}
});
});
this.optionalMetricsSubscription = this.optionalMetrics$.subscribe(optionalMetrics => {
const optionalMetricsDic = {};
optionalMetrics.forEach(experimentMetrics => optionalMetricsDic[experimentMetrics.task] = experimentMetrics.metrics);
@@ -250,17 +287,6 @@ export class DebugImagesComponent implements OnInit, OnDestroy {
});
}
refresh(experimentId: string, timeIsNow, debugImages) {
if (experimentId && timeIsNow?.[experimentId] && debugImages[experimentId] && this.elRef.nativeElement.scrollTop < 40) {
this.store.dispatch(debugActions.refreshMetric({
payload: {
task: experimentId,
metric: debugImages[experimentId].metric
}
}));
}
}
private isTaskRunning(tasks: Partial<Task>[]) {
return tasks.some(task => [TaskStatusEnum.InProgress, TaskStatusEnum.Queued].includes(task.status));
}
@@ -293,11 +319,19 @@ export class DebugImagesComponent implements OnInit, OnDestroy {
}
thereAreNoDebugImages(experiment) {
return !(this.debugImages && this.debugImages[experiment] && this.debugImages[experiment].length > 0);
return !(this.debugImages && this.debugImages[experiment] && this.debugImages[experiment].data.length > 0);
}
shouldShowNoImagesForExperiment(experiment: string) {
return (this.thereAreNoMetrics(experiment) && this.optionalMetrics && this.optionalMetrics[experiment]) || (this.thereAreNoDebugImages(experiment) && this.debugImages && this.debugImages[experiment]);
}
// buildUrl() {
// return ['../../', 'experiments', ];
// }
copyIdToClipboard() {
this.store.dispatch(addMessage('success', 'Copied to clipboard'));
}
}

View File

@@ -1,17 +1,17 @@
import { ScrollingModule } from '@angular/cdk/scrolling';
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { EffectsModule } from '@ngrx/effects';
import { StoreModule } from '@ngrx/store';
import { ExperimentCompareSharedModule } from '../experiments-compare/shared/experiment-compare-shared.module';
import { ImageDisplayerComponent } from '../experiments/dumb/image-displayer/image-displayer.component';
import { SMSharedModule } from '../shared/shared.module';
import { UiComponentsModule } from '../shared/ui-components/ui-components.module';
import { DebugImageSnippetComponent } from './debug-image-snippet/debug-image-snippet.component';
import { DebugImagesEffects } from './debug-images-effects';
import {ScrollingModule} from '@angular/cdk/scrolling';
import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
import {EffectsModule} from '@ngrx/effects';
import {StoreModule} from '@ngrx/store';
import {ExperimentCompareSharedModule} from '../experiments-compare/shared/experiment-compare-shared.module';
import {ImageDisplayerComponent} from '../experiments/dumb/image-displayer/image-displayer.component';
import {SMSharedModule} from '../shared/shared.module';
import {UiComponentsModule} from '../shared/ui-components/ui-components.module';
import {DebugImageSnippetComponent} from './debug-image-snippet/debug-image-snippet.component';
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 {DebugImagesViewComponent} from './debug-images-view/debug-images-view.component';
import {DebugImagesComponent} from './debug-images.component';
import {MatSliderModule} from '@angular/material/slider';
import {ExperimentGraphsModule} from '../shared/experiment-graphs/experiment-graphs.module';

View File

@@ -1,10 +1,9 @@
import {createAction, props} from '@ngrx/store';
import {Task} from '../../../business-logic/model/tasks/task';
import {Task} from '~/business-logic/model/tasks/task';
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 {EXPERIMENTS_PREFIX} from '../../experiments/actions/common-experiments-view.actions';
export const EXPERIMENTS_COMPARE_SELECT_EXPERIMENT_ = 'EXPERIMENTS_COMPARE_SELECT_EXPERIMENT_';
@@ -22,7 +21,6 @@ export const SET_NAVIGATION_PREFERENCES = EXPERIMENTS_COMPARE_SELECT_EXPERIMENT_
export const setHideIdenticalFields = createAction(SET_HIDE_IDENTICAL_ROWS, props<{payload: boolean}>());
export const setRefreshing = createAction(SET_REFRESHING, props<{ payload: boolean; autoRefresh?: 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 toggleShowScalarOptions = createAction(TOGGLE_SHOW_SACLARS_OPTIONS);

View File

@@ -1,6 +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/fp';
import {treeBuilderService} from '../services/tree-builder.service';
import {isArrayOrderNotImportant} from '../jsonToDiffConvertor';
@@ -8,20 +8,21 @@ import {ExperimentParams, TreeNode, TreeNodeMetadata} from '../shared/experiment
import {MatTreeFlatDataSource, MatTreeFlattener} from '@angular/material/tree';
import {activeLoader, addMessage, deactivateLoader} from '../../core/actions/layout.actions';
import {ChangeDetectorRef, OnDestroy, QueryList, ViewChildren, Directive} from '@angular/core';
import {ExperimentCompareDetailsBase} from '../../../features/experiments-compare/experiments-compare-details.base';
import {ExperimentCompareDetailsBase} from '~/features/experiments-compare/experiments-compare-details.base';
import {ActivatedRoute, Router} from '@angular/router';
import {select, Store} from '@ngrx/store';
import {IExperimentInfoState} from '../../../features/experiments/reducers/experiment-info.reducer';
import {IExperimentInfoState} from '~/features/experiments/reducers/experiment-info.reducer';
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, tap} from 'rxjs/operators';
import {selectHideIdenticalFields, selectRefreshing} from '../reducers';
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 {selectHasDataFeature} from '~/core/reducers/users.reducer';
import {ListRange} from '@angular/cdk/collections';
import {RefreshService} from '@common/core/services/refresh.service';
export type NextDiffDirectionEnum = 'down' | 'up';
@@ -78,10 +79,13 @@ export abstract class ExperimentCompareBase extends ExperimentCompareDetailsBase
@ViewChildren('virtualScrollRef') virtualScrollRef: QueryList<CdkVirtualScrollViewport>;
constructor(public router: Router,
public store: Store<IExperimentInfoState>,
public changeDetection: ChangeDetectorRef,
public activeRoute: ActivatedRoute) {
constructor (
public router: Router,
public store: Store<IExperimentInfoState>,
public changeDetection: ChangeDetectorRef,
public activeRoute: ActivatedRoute,
public refresh: RefreshService
) {
super();
this.hasDataFeature$ = this.store.pipe(select(selectHasDataFeature));
@@ -111,9 +115,9 @@ export abstract class ExperimentCompareBase extends ExperimentCompareDetailsBase
this.find(this.searchText);
});
this.refreshingSubscription = this.store.pipe(select(selectRefreshing))
.pipe(filter(({refreshing}) => refreshing))
.subscribe(({autoRefresh}) => this.store.dispatch(refetchExperimentRequested({autoRefresh})));
this.refreshingSubscription = this.refresh.tick
.pipe(filter(auto => auto !== null))
.subscribe(auto => this.store.dispatch(refetchExperimentRequested({autoRefresh: auto})));
this.hideIdenticalFieldsSub.add(this.hasDataFeature$.subscribe(hasData => this.hasDataFeature = hasData));
@@ -395,7 +399,7 @@ export abstract class ExperimentCompareBase extends ExperimentCompareDetailsBase
let tooltip;
if (this.compareTabPage === 'hyper-params') {
path[path.length - 1] = path[path.length - 1].trim();
const hypeParamObject = get(path.join('.'), this.originalExperiments[comparedExperiment.id]);
const hypeParamObject = get(path.join('.'), this.originalExperiments[comparedExperiment?.id]);
if (hypeParamObject && has('name', hypeParamObject) && has('value', hypeParamObject) && hypeParamObject.type !== 'legacy') {
tooltip = (hypeParamObject.type ? `Type: ${hypeParamObject.type}\n` : '') + (hypeParamObject.description || '');
}
@@ -415,7 +419,7 @@ export abstract class ExperimentCompareBase extends ExperimentCompareDetailsBase
};
};
metaDataTransformer = (data, key, path, extraParams): TreeNodeMetadata => {
metaDataTransformer = (): TreeNodeMetadata => {
return null;
};

View File

@@ -22,6 +22,7 @@ import {ExperimentCompareBase} from '../experiment-compare-base';
import {ActivatedRoute, Router} from '@angular/router';
import {IExperimentInfoState} from '../../../../features/experiments/reducers/experiment-info.reducer';
import {ConfigurationItem} from '../../../../business-logic/model/tasks/configurationItem';
import {RefreshService} from '@common/core/services/refresh.service';
@Component({
selector: 'sm-experiment-compare-details',
@@ -49,9 +50,10 @@ export class ExperimentCompareDetailsComponent extends ExperimentCompareBase imp
public store: Store<IExperimentInfoState>,
public changeDetection: ChangeDetectorRef,
public activeRoute: ActivatedRoute,
private cdr: ChangeDetectorRef
private cdr: ChangeDetectorRef,
public refresh: RefreshService
) {
super(router, store, changeDetection, activeRoute);
super(router, store, changeDetection, activeRoute, refresh);
}
experiments$ = this.store.pipe(select(selectExperimentsDetails));

View File

@@ -35,7 +35,7 @@
.metrics-container {
padding: 20px 12px 0 32px;
padding: 20px 0 0 32px;
// dashboard-search icon
i.fa {
position: absolute;

View File

@@ -6,12 +6,13 @@ import {distinctUntilChanged, filter, map} from 'rxjs/operators';
import {selectRouterParams} from '@common/core/reducers/router-reducer';
import {get, has} from 'lodash/fp';
import {SetExperimentSettings, SetSelectedExperiments} from '../../actions/experiments-compare-charts.actions';
import {selectRefreshing, selectScalarsGraphHyperParams, selectScalarsGraphMetrics, selectScalarsGraphShowIdenticalHyperParams, selectScalarsGraphTasks, selectMetricValueType, selectSelectedSettingsHyperParams, selectSelectedSettingsMetric} from '../../reducers';
import {selectScalarsGraphHyperParams, selectScalarsGraphMetrics, selectScalarsGraphShowIdenticalHyperParams, selectScalarsGraphTasks, selectMetricValueType, selectSelectedSettingsHyperParams, selectSelectedSettingsMetric} from '../../reducers';
import {getExperimentsHyperParams, setShowIdenticalHyperParams, setvalueType} from '../../actions/experiments-compare-scalars-graph.actions';
import {GroupedHyperParams, MetricOption, MetricValueType, SelectedMetric, VariantOption} from '../../reducers/experiments-compare-charts.reducer';
import {MatRadioChange} from '@angular/material/radio';
import {selectPlotlyReady} from '@common/core/reducers/view.reducer';
import {ExtFrame} from '@common/shared/experiment-graphs/single-graph/plotly-graph-base';
import {RefreshService} from '@common/core/services/refresh.service';
export const _filter = (opt: VariantOption[], value: string): VariantOption[] => {
@@ -37,7 +38,6 @@ export class ExperimentCompareHyperParamsGraphComponent implements OnInit, OnDes
public metrics$: Observable<MetricOption[]>;
public selectedHyperParams$: Observable<string[]>;
private selectedMetric$: Observable<SelectedMetric>;
private selectRefreshing$: Observable<{ refreshing: boolean, autoRefresh: boolean }>;
public experiments$: Observable<any[]>;
public graphs: { [key: string]: ExtFrame };
@@ -66,13 +66,12 @@ export class ExperimentCompareHyperParamsGraphComponent implements OnInit, OnDes
}
}
constructor(private store: Store<IExperimentInfoState>) {
constructor(private store: Store<IExperimentInfoState>, private refresh: RefreshService) {
this.metrics$ = this.store.pipe(select(selectScalarsGraphMetrics));
this.hyperParams$ = this.store.pipe(select(selectScalarsGraphHyperParams));
this.selectedHyperParams$ = this.store.pipe(select(selectSelectedSettingsHyperParams));
this.selectedMetric$ = this.store.pipe(select(selectSelectedSettingsMetric));
this.selectShowIdenticalHyperParams$ = this.store.pipe(select(selectScalarsGraphShowIdenticalHyperParams));
this.selectRefreshing$ = this.store.select(selectRefreshing);
this.experiments$ = this.store.pipe(select(selectScalarsGraphTasks));
this.metricValueType$ = this.store.pipe(select(selectMetricValueType));
@@ -128,8 +127,11 @@ export class ExperimentCompareHyperParamsGraphComponent implements OnInit, OnDes
this.store.dispatch(getExperimentsHyperParams({experimentsIds: this.taskIds}));
});
this.refreshingSubscription = this.selectRefreshing$.pipe(filter(({refreshing}) => refreshing)).subscribe(({autoRefresh}) =>
this.store.dispatch(getExperimentsHyperParams({experimentsIds: this.taskIds, autoRefresh})));
this.refreshingSubscription = this.refresh.tick
.pipe(filter(auto => auto !== null))
.subscribe(autoRefresh =>
this.store.dispatch(getExperimentsHyperParams({experimentsIds: this.taskIds, autoRefresh}))
);
this.listOpen = true;
window.setTimeout(() => {

View File

@@ -1,19 +1,20 @@
import {ChangeDetectorRef, Component, OnDestroy, OnInit} from '@angular/core';
import {Observable, Subscription} from 'rxjs';
import {select, Store} from '@ngrx/store';
import {IExperimentInfoState} from '../../../../features/experiments/reducers/experiment-info.reducer';
import {distinctUntilChanged, filter, map, tap} from 'rxjs/operators';
import {selectRouterParams} from '../../../core/reducers/router-reducer';
import {IExperimentInfoState} from '~/features/experiments/reducers/experiment-info.reducer';
import {distinctUntilChanged, filter, map} from 'rxjs/operators';
import {isEqual} from 'lodash/fp';
import {mergeMultiMetrics, mergeMultiMetricsGroupedVariant} from '../../../tasks/tasks.utils';
import {scrollToElement} from '../../../shared/utils/shared-utils';
import {mergeMultiMetrics, mergeMultiMetricsGroupedVariant} from '@common/tasks/tasks.utils';
import {scrollToElement} from '@common/shared/utils/shared-utils';
import {GetMultiScalarCharts, ResetExperimentMetrics, SetExperimentMetricsSearchTerm, SetExperimentSettings, SetSelectedExperiments} from '../../actions/experiments-compare-charts.actions';
import {selectCompareSelectedSettingsGroupBy, selectCompareSelectedSettingsSmoothWeight, selectCompareSelectedSettingsxAxisType, selectCompareTasksScalarCharts, selectExperimentMetricsSearchTerm, selectRefreshing, selectSelectedExperimentSettings, selectSelectedSettingsHiddenScalar, selectShowScalarsOptions} from '../../reducers';
import {ScalarKeyEnum} from '../../../../business-logic/model/events/scalarKeyEnum';
import {selectCompareSelectedSettingsGroupBy, selectCompareSelectedSettingsSmoothWeight, selectCompareSelectedSettingsxAxisType, selectCompareTasksScalarCharts, selectExperimentMetricsSearchTerm, selectSelectedExperimentSettings, selectSelectedSettingsHiddenScalar} from '../../reducers';
import {ScalarKeyEnum} from '~/business-logic/model/events/scalarKeyEnum';
import {toggleShowScalarOptions} from '../../actions/compare-header.actions';
import {GroupByCharts} from '../../../experiments/reducers/common-experiment-output.reducer';
import {GroupedList} from '../../../shared/ui-components/data/selectable-grouped-filter-list/selectable-grouped-filter-list.component';
import {ExtFrame} from '../../../shared/experiment-graphs/single-graph/plotly-graph-base';
import {GroupByCharts} from '@common/experiments/reducers/common-experiment-output.reducer';
import {GroupedList} from '@common/shared/ui-components/data/selectable-grouped-filter-list/selectable-grouped-filter-list.component';
import {ExtFrame} from '@common/shared/experiment-graphs/single-graph/plotly-graph-base';
import {RefreshService} from '@common/core/services/refresh.service';
import {selectRouterParams} from '@common/core/reducers/router-reducer';
@Component({
@@ -26,8 +27,6 @@ export class ExperimentCompareScalarChartsComponent implements OnInit, OnDestroy
public smoothWeight$: Observable<number>;
public xAxisType$: Observable<ScalarKeyEnum>;
public groupBy$: Observable<GroupByCharts>;
public showSettingsBar$: Observable<boolean>;
private selectRefreshing$: Observable<{ refreshing: boolean, autoRefresh: boolean }>;
private routerParams$: Observable<any>;
public metrics$: Observable<any>;
public experimentSettings$: Observable<any>;
@@ -45,8 +44,6 @@ export class ExperimentCompareScalarChartsComponent implements OnInit, OnDestroy
public selectedGraph: string = null;
private taskIds: Array<string>;
public graphs: { [key: string]: ExtFrame[] };
public refreshDisabled = false;
public showSettingsBar: boolean = false;
public groupBy: GroupByCharts;
private metrics: GroupedList;
@@ -61,14 +58,16 @@ export class ExperimentCompareScalarChartsComponent implements OnInit, OnDestroy
}
];
constructor(private store: Store<IExperimentInfoState>, private changeDetection: ChangeDetectorRef) {
constructor(
private store: Store<IExperimentInfoState>,
private changeDetection: ChangeDetectorRef,
private refresh: RefreshService
) {
this.listOfHidden = this.store.pipe(select(selectSelectedSettingsHiddenScalar));
this.searchTerm$ = this.store.pipe(select(selectExperimentMetricsSearchTerm));
this.showSettingsBar$ = this.store.pipe(select(selectShowScalarsOptions));
this.smoothWeight$ = this.store.select(selectCompareSelectedSettingsSmoothWeight);
this.xAxisType$ = this.store.select(selectCompareSelectedSettingsxAxisType);
this.groupBy$ = this.store.select(selectCompareSelectedSettingsGroupBy);
this.selectRefreshing$ = this.store.select(selectRefreshing);
this.metrics$ = this.store.pipe(
select(selectCompareTasksScalarCharts),
filter(metrics => !!metrics),
@@ -84,13 +83,12 @@ export class ExperimentCompareScalarChartsComponent implements OnInit, OnDestroy
this.routerParams$ = this.store.pipe(
select(selectRouterParams),
filter(params => !!params.ids),
distinctUntilChanged(),
tap(() => this.refreshDisabled = true)
distinctUntilChanged()
);
this.xAxisSub = this.xAxisType$
.pipe(filter((axis) => !!axis))
.subscribe((axis) => this.store.dispatch(new GetMultiScalarCharts({taskIds: this.taskIds, cached: true})));
.pipe(filter(axis => !!axis && this.taskIds?.length > 0))
.subscribe(() => this.store.dispatch(new GetMultiScalarCharts({taskIds: this.taskIds, cached: true})));
this.groupBySub = this.groupBy$
.subscribe(groupBy => {
@@ -102,7 +100,6 @@ export class ExperimentCompareScalarChartsComponent implements OnInit, OnDestroy
ngOnInit() {
this.metricsSubscription = this.metrics$
.subscribe((metricsWrapped) => {
this.refreshDisabled = false;
const metrics = metricsWrapped.metrics || {};
this.metrics = metrics;
this.prepareGraphsAndUpdate(metrics);
@@ -123,8 +120,9 @@ export class ExperimentCompareScalarChartsComponent implements OnInit, OnDestroy
}
});
this.refreshingSubscription = this.selectRefreshing$.pipe(filter(({refreshing}) => refreshing))
.subscribe(({autoRefresh}) => this.store.dispatch(new GetMultiScalarCharts({taskIds: this.taskIds, autoRefresh})));
this.refreshingSubscription = this.refresh.tick
.pipe(filter(auto => auto !== null && this.graphs !== null))
.subscribe(autoRefresh => this.store.dispatch(new GetMultiScalarCharts({taskIds: this.taskIds, autoRefresh})));
}
private prepareGraphsAndUpdate(metrics) {
@@ -146,11 +144,11 @@ export class ExperimentCompareScalarChartsComponent implements OnInit, OnDestroy
}
ngOnDestroy() {
this.metricsSubscription.unsubscribe();
this.settingsSubscription.unsubscribe();
this.routerParamsSubscription.unsubscribe();
this.xAxisSub.unsubscribe();
this.refreshingSubscription.unsubscribe();
this.metricsSubscription?.unsubscribe();
this.settingsSubscription?.unsubscribe();
this.routerParamsSubscription?.unsubscribe();
this.xAxisSub?.unsubscribe();
this.refreshingSubscription?.unsubscribe();
this.resetMetrics();
}

View File

@@ -1,15 +1,16 @@
import {ChangeDetectorRef, Component, OnDestroy, OnInit} from '@angular/core';
import {selectRouterParams, selectRouterQueryParams} from '../../../core/reducers/router-reducer';
import {selectRouterParams, selectRouterQueryParams} from '@common/core/reducers/router-reducer';
import {select, Store} from '@ngrx/store';
import {distinctUntilChanged, filter, map, tap} from 'rxjs/operators';
import {get, has} from 'lodash/fp';
import {Observable, Subscription} from 'rxjs';
import * as metricsValuesActions from '../../actions/experiments-compare-metrics-values.actions';
import {selectCompareMetricsValuesExperiments, selectCompareMetricsValuesSortConfig, selectRefreshing} from '../../reducers';
import {selectCompareMetricsValuesExperiments, selectCompareMetricsValuesSortConfig} from '../../reducers';
import {Router} from '@angular/router';
import {addMessage} from '../../../core/actions/layout.actions';
import {addMessage} from '@common/core/actions/layout.actions';
import {TreeNode} from '../../shared/experiments-compare-details.model';
import {createDiffObjectScalars, getAllKeysEmptyObject} from '../../jsonToDiffConvertor';
import {RefreshService} from '@common/core/services/refresh.service';
interface ValueMode {
key: string;
@@ -39,7 +40,6 @@ const VALUE_MODES: { [mode: string]: ValueMode } = {
export class ExperimentCompareMetricValuesComponent implements OnInit, OnDestroy {
public sortOrder$: Observable<any>;
public comparedTasks$: Observable<any>;
private selectRefreshing$: Observable<{ refreshing: boolean; autoRefresh: boolean }>;
private comparedTasksSubscription: Subscription;
private refreshingSubscription: Subscription;
@@ -54,10 +54,14 @@ export class ExperimentCompareMetricValuesComponent implements OnInit, OnDestroy
private taskIds: string;
public valuesMode: ValueMode;
constructor(private router: Router, public store: Store<any>, private changeDetection: ChangeDetectorRef) {
constructor(
private router: Router,
public store: Store<any>,
private changeDetection: ChangeDetectorRef,
private refresh: RefreshService
) {
this.comparedTasks$ = this.store.pipe(select(selectCompareMetricsValuesExperiments));
this.sortOrder$ = this.store.pipe(select(selectCompareMetricsValuesSortConfig));
this.selectRefreshing$ = this.store.select(selectRefreshing);
}
ngOnInit() {
@@ -92,8 +96,9 @@ export class ExperimentCompareMetricValuesComponent implements OnInit, OnDestroy
this.changeDetection.detectChanges();
});
this.refreshingSubscription = this.selectRefreshing$.pipe(filter(({refreshing}) => refreshing))
.subscribe(({autoRefresh}) => this.store.dispatch(
this.refreshingSubscription = this.refresh.tick
.pipe(filter(auto => auto !== null))
.subscribe((autoRefresh) => this.store.dispatch(
new metricsValuesActions.GetComparedExperimentsMetricsValues({taskIds: this.taskIds.split(','), autoRefresh})
));
}

View File

@@ -13,6 +13,7 @@ import {ExperimentCompareBase} from '../experiment-compare-base';
import {ActivatedRoute, Router} from '@angular/router';
import {IExperimentInfoState} from '../../../../features/experiments/reducers/experiment-info.reducer';
import {experimentListUpdated, setExperiments} from '../../actions/experiments-compare-params.actions';
import {RefreshService} from '@common/core/services/refresh.service';
@Component({
selector: 'sm-experiment-compare-params',
@@ -23,11 +24,14 @@ import {experimentListUpdated, setExperiments} from '../../actions/experiments-c
export class ExperimentCompareParamsComponent extends ExperimentCompareBase implements OnInit {
public showEllipsis: boolean = true;
constructor(public router: Router,
public store: Store<IExperimentInfoState>,
public changeDetection: ChangeDetectorRef,
public activeRoute: ActivatedRoute) {
super(router, store, changeDetection, activeRoute);
constructor(
public router: Router,
public store: Store<IExperimentInfoState>,
public changeDetection: ChangeDetectorRef,
public activeRoute: ActivatedRoute,
public refresh: RefreshService
) {
super(router, store, changeDetection, activeRoute, refresh);
}
experiments$ = this.store.pipe(select(selectExperimentsParams));

View File

@@ -17,7 +17,7 @@
.graphs-container {
padding: 0 !important;
width: calc(100% - 420px);
width: calc(100% - 344px);
}
}

View File

@@ -1,16 +1,17 @@
import {ChangeDetectorRef, Component, OnDestroy, OnInit} from '@angular/core';
import {SelectableListItem} from '../../../shared/ui-components/data/selectable-list/selectable-list.model';
import {SelectableListItem} from '@common/shared/ui-components/data/selectable-list/selectable-list.model';
import {Observable, Subscription} from 'rxjs';
import {select, Store} from '@ngrx/store';
import {IExperimentInfoState} from '../../../../features/experiments/reducers/experiment-info.reducer';
import {IExperimentInfoState} from '~/features/experiments/reducers/experiment-info.reducer';
import {distinctUntilChanged, filter, map, tap} from 'rxjs/operators';
import {selectRouterParams} from '../../../core/reducers/router-reducer';
import {convertMultiPlots, prepareMultiPlots, sortMetricsList} from '../../../tasks/tasks.utils';
import {selectRouterParams} from '@common/core/reducers/router-reducer';
import {convertMultiPlots, prepareMultiPlots, sortMetricsList} from '@common/tasks/tasks.utils';
import {isEqual} from 'lodash/fp';
import {scrollToElement} from '../../../shared/utils/shared-utils';
import {scrollToElement} from '@common/shared/utils/shared-utils';
import {GetMultiPlotCharts, ResetExperimentMetrics, SetExperimentMetricsSearchTerm, SetExperimentSettings, SetSelectedExperiments} from '../../actions/experiments-compare-charts.actions';
import {selectCompareTasksPlotCharts, selectExperimentMetricsSearchTerm, selectRefreshing, selectSelectedExperimentSettings, selectSelectedSettingsHiddenPlot} from '../../reducers';
import {ExtFrame} from '../../../shared/experiment-graphs/single-graph/plotly-graph-base';
import {selectCompareTasksPlotCharts, selectExperimentMetricsSearchTerm, selectSelectedExperimentSettings, selectSelectedSettingsHiddenPlot} from '../../reducers';
import {ExtFrame} from '@common/shared/experiment-graphs/single-graph/plotly-graph-base';
import {RefreshService} from '@common/core/services/refresh.service';
@Component({
selector: 'sm-experiment-compare-plots',
@@ -24,7 +25,6 @@ export class ExperimentComparePlotsComponent implements OnInit, OnDestroy {
public plots$: Observable<any>;
public experimentSettings$: Observable<any>;
public searchTerm$: Observable<string>;
private selectRefreshing$: Observable<{refreshing: boolean, autoRefresh: boolean}>;
private plotsSubscription: Subscription;
private settingsSubscription: Subscription;
@@ -33,16 +33,14 @@ export class ExperimentComparePlotsComponent implements OnInit, OnDestroy {
public graphList: SelectableListItem[] = [];
public selectedGraph: string = null;
private experimentId: string;
private taskIds: Array<string>;
public graphs: { [key: string]: ExtFrame[] };
public refreshDisabled: boolean;
constructor(private store: Store<IExperimentInfoState>, private changeDetection: ChangeDetectorRef) {
constructor(private store: Store<IExperimentInfoState>, private changeDetection: ChangeDetectorRef, private refresh: RefreshService) {
this.listOfHidden = this.store.pipe(select(selectSelectedSettingsHiddenPlot));
this.searchTerm$ = this.store.pipe(select(selectExperimentMetricsSearchTerm));
this.selectRefreshing$ = this.store.select(selectRefreshing);
this.plots$ = this.store.pipe(
select(selectCompareTasksPlotCharts),
filter(metrics => !!metrics),
@@ -92,8 +90,11 @@ export class ExperimentComparePlotsComponent implements OnInit, OnDestroy {
}
});
this.refreshingSubscription = this.selectRefreshing$.pipe(filter(({refreshing}) => refreshing))
.subscribe(({autoRefresh}) => this.store.dispatch(new GetMultiPlotCharts({taskIds: this.taskIds, autoRefresh})));
this.refreshingSubscription = this.refresh.tick
.pipe(filter(auto => auto !== null))
.subscribe(autoRefresh =>
this.store.dispatch(new GetMultiPlotCharts({taskIds: this.taskIds, autoRefresh}))
);
}
ngOnDestroy() {

View File

@@ -23,7 +23,6 @@
class="align-self-stretch"
[selectionReachedLimit]="reachedCompareLimit"
[selectionMode]="null"
[disableContextMenu]="true"
[reorderableColumns]="false"
[minimizedView]="false"
[colsOrder]="(tableColsOrder$ | async)"

View File

@@ -1,4 +1,4 @@
import {ChangeDetectorRef, Component, ElementRef, OnDestroy, OnInit, ViewChild} from '@angular/core';
import {ChangeDetectorRef, Component, ElementRef, EventEmitter, OnDestroy, OnInit, ViewChild} from '@angular/core';
import {select, Store} from '@ngrx/store';
import {
compareAddDialogTableSortChanged,
@@ -10,7 +10,6 @@ import {
setShowSearchExperimentsForCompare
} from '../../actions/compare-header.actions';
import {
selectCompareAddTableSortFields,
selectExperimentsForCompareSearchTerm,
selectSelectedExperimentsForCompareAdd
} from '../../reducers';
@@ -52,6 +51,7 @@ import {ProjectsGetTaskParentsResponseParents} from '~/business-logic/model/proj
import {FilterMetadata} from 'primeng/api/filtermetadata';
import {INITIAL_EXPERIMENT_TABLE_COLS} from '@common/experiments/experiment.consts';
import {EntityTypeEnum} from '~/shared/constants/non-common-consts';
import {ExperimentsTableComponent} from '@common/experiments/dumb/experiments-table/experiments-table.component';
export const allowAddExperiment$ = (selectRouterParams$: Observable<Params>) => selectRouterParams$.pipe(
distinctUntilKeyChanged('ids'),
@@ -73,7 +73,6 @@ export class SelectExperimentsForCompareComponent implements OnInit, OnDestroy {
public selectedExperimentsIds: string[] = [];
private paramsSubscription: Subscription;
public searchTerm$: Observable<string>;
@ViewChild('searchExperiments', {static: true}) searchExperiments;
public allowAddExperiment$: Observable<boolean>;
public tableColsOrder$: Observable<string[]>;
public tableSortOrder$: Observable<TableSortOrderEnum>;
@@ -98,6 +97,8 @@ export class SelectExperimentsForCompareComponent implements OnInit, OnDestroy {
public reachedCompareLimit: boolean;
private _resizedCols = {} as { [colId: string]: string };
private resizedCols$ = new BehaviorSubject<{[colId: string]: string }>(this._resizedCols);
@ViewChild('searchExperiments', {static: true}) searchExperiments;
@ViewChild(ExperimentsTableComponent) table: ExperimentsTableComponent;
constructor(
private store: Store<any>,
@@ -183,6 +184,7 @@ export class SelectExperimentsForCompareComponent implements OnInit, OnDestroy {
ngOnInit() {
window.setTimeout(() => this.table.table.rowRightClick = new EventEmitter());
this.paramsSubscription = this.store.pipe(
select(selectRouterParams),
map(params => [params && params['ids'], get('projectId', params)]),

View File

@@ -38,6 +38,7 @@
sm-tag-list {
margin-top: 4px;
height: 18px;
max-width: 530px;
}
.general-info {
@@ -60,10 +61,10 @@
}
.bw.i-completed.icon {
background-image: url('/app/webapp-common/assets/icons/completed-dark.svg') !important;
background-image: url('../../../assets/icons/completed-dark.svg') !important;
}
.bw.i-stopped.icon {
background-image: url('/app/webapp-common/assets/icons/completed-dark.svg') !important;
background-image: url('../../../assets/icons/completed-dark.svg') !important;
}
}
}

View File

@@ -61,9 +61,6 @@
<div class="settings">
<sm-refresh-button
[autoRefreshState]="autoRefreshState$ | async"
[allowAutoRefresh]="true"
(refreshList)="refreshList($event)"
(setAutoRefresh)="setAutoRefresh($event)"
class="light-theme"
>

View File

@@ -9,8 +9,8 @@ import {
} from '@angular/core';
import {MatSelectChange} from '@angular/material/select';
import {Store} from '@ngrx/store';
import {selectHideIdenticalFields, selectRefreshing} from '../../reducers';
import {interval, Observable, Subscription} from 'rxjs';
import {selectHideIdenticalFields} from '../../reducers';
import {Observable, Subscription} from 'rxjs';
import {
refreshIfNeeded,
setHideIdenticalFields,
@@ -19,19 +19,18 @@ import {
toggleShowScalarOptions
} from '../../actions/compare-header.actions';
import {ActivatedRoute, Router} from '@angular/router';
import {selectRouterParams, selectRouterQueryParams, selectRouterUrl} from '../../../core/reducers/router-reducer';
import {selectRouterParams, selectRouterQueryParams, selectRouterUrl} from '@common/core/reducers/router-reducer';
import {get} from 'lodash/fp';
import {selectAppVisible, selectAutoRefresh} from '../../../core/reducers/view.reducer';
import {setAutoRefresh} from '../../../core/actions/layout.actions';
import {AUTO_REFRESH_INTERVAL} from '../../../../app.constants';
import {filter, withLatestFrom} from 'rxjs/operators';
import {setAutoRefresh} from '@common/core/actions/layout.actions';
import {filter} from 'rxjs/operators';
import {MatSlideToggleChange} from '@angular/material/slide-toggle';
import {compareLimitations} from '../../../shared/entity-page/footer-items/compare-footer-item';
import {compareLimitations} from '@common/shared/entity-page/footer-items/compare-footer-item';
import {
allowAddExperiment$,
SelectExperimentsForCompareComponent
} from '../../containers/select-experiments-for-compare/select-experiments-for-compare.component';
import {MatDialog} from "@angular/material/dialog";
import {RefreshService} from '@common/core/services/refresh.service';
@Component({
selector: 'sm-experiment-compare-header',
@@ -44,34 +43,32 @@ export class ExperimentCompareHeaderComponent implements OnInit, OnDestroy {
private queryParamsSubscription: Subscription;
public selectHideIdenticalFields$: Observable<boolean>;
public selectRefreshing$: Observable<{ refreshing: boolean; autoRefresh: boolean }>;
public viewMode: string;
public currentPage: string;
public queryParamsViewMode: string;
public compareLimitations = compareLimitations;
public autoRefreshState$: Observable<boolean>;
public allowAddExperiment$: Observable<boolean>;
private autorRefreshSub: Subscription;
private showMenuSub: Subscription;
private isAppVisible$: Observable<boolean>;
@Output() selectionChanged = new EventEmitter<string[]>();
constructor(private store: Store<any>, private route: ActivatedRoute, private router: Router, private cdr: ChangeDetectorRef, private dialog: MatDialog) {
constructor(
private store: Store<any>,
private route: ActivatedRoute,
private router: Router,
private cdr: ChangeDetectorRef,
private dialog: MatDialog,
private refresh: RefreshService
) {
this.selectHideIdenticalFields$ = this.store.select(selectHideIdenticalFields);
this.selectRefreshing$ = this.store.select(selectRefreshing);
this.autoRefreshState$ = this.store.select(selectAutoRefresh);
this.isAppVisible$ = this.store.select(selectAppVisible);
}
ngOnInit() {
this.autorRefreshSub = interval(AUTO_REFRESH_INTERVAL).pipe(
withLatestFrom(this.autoRefreshState$, this.isAppVisible$),
filter(([, autoRefreshState, isAppVisible]) => autoRefreshState && isAppVisible)
).subscribe(() => {
this.refreshList(true);
});
this.autorRefreshSub = this.refresh.tick
.pipe(filter(auto => auto === null))
.subscribe(() => this.store.dispatch(refreshIfNeeded({payload: true, autoRefresh: true})));
this.routerSubscription = this.store.select(selectRouterUrl).subscribe(() => {
this.currentPage = get('snapshot.firstChild.url[0].path', this.route);
this.viewMode = get('snapshot.firstChild.url[1].path', this.route);
@@ -90,10 +87,6 @@ export class ExperimentCompareHeaderComponent implements OnInit, OnDestroy {
this.showMenuSub?.unsubscribe();
}
refresh({isAutoRefresh}: { isAutoRefresh: boolean }) {
this.store.dispatch(refreshIfNeeded({payload: true, autoRefresh: isAutoRefresh}));
}
changeView($event: MatSelectChange) {
const queryParam = {[this.currentPage]: $event.value};
const page = $event.value.replace(/.*_/, '');
@@ -121,10 +114,6 @@ export class ExperimentCompareHeaderComponent implements OnInit, OnDestroy {
this.store.dispatch(toggleShowScalarOptions());
}
refreshList(isAutorefresh: boolean) {
this.store.dispatch(refreshIfNeeded({payload: true, autoRefresh: isAutorefresh}));
}
setAutoRefresh($event: boolean) {
this.store.dispatch(setAutoRefresh({autoRefresh: $event}));
}

View File

@@ -292,8 +292,8 @@ export class ParallelCoordinatesGraphComponent extends PlotlyGraphBase implement
graph.selectAll('.axis-title').text((d: any) => this.wrap(d.key)).append('title').text(d => (d as any).key);
graph.selectAll('.axis .tick text').text((d: string) => this.wrap(d)).append('title').text((d: string) => d);
graph.selectAll('.axis .tick text').style('pointer-events', 'auto');
graph.selectAll('.tick').on('mouseover', (d, i, e) => {
const tick = e[i] as unknown as SVGGElement;
graph.selectAll('.tick').on('mouseover', (event: MouseEvent) => {
const tick = event.currentTarget as SVGGElement;
const axis = tick.parentNode as SVGGElement;
if (axis && axis.lastChild !== tick) {
axis.removeChild(tick);

View File

@@ -5,14 +5,13 @@ import {IExperimentCompareChartsState} from '../reducers/experiments-compare-cha
import * as chartActions from '../actions/experiments-compare-charts.actions';
import {GetMultiPlotCharts, GetMultiScalarCharts} from '../actions/experiments-compare-charts.actions';
import {activeLoader, deactivateLoader, setServerError} from '../../core/actions/layout.actions';
import {catchError, debounceTime, mergeMap, map, withLatestFrom} from 'rxjs/operators';
import {catchError, debounceTime, mergeMap, map, withLatestFrom, filter} from 'rxjs/operators';
import {ApiTasksService} from '~/business-logic/api-services/tasks.service';
import {ApiAuthService} from '~/business-logic/api-services/auth.service';
import {BlTasksService} from '~/business-logic/services/tasks.service';
import {ApiEventsService} from '~/business-logic/api-services/events.service';
import {requestFailed} from '../../core/actions/http.actions';
import {selectCompareHistogramCacheAxisType, selectCompareSelectedSettingsxAxisType} from '../reducers';
import {setRefreshing} from '../actions/compare-header.actions';
import {ScalarKeyEnum} from '~/business-logic/model/events/scalarKeyEnum';
@@ -25,6 +24,7 @@ export class ExperimentsCompareChartsEffects {
activeLoader = createEffect(() => this.actions$.pipe(
ofType(chartActions.GET_MULTI_SCALAR_CHARTS, chartActions.GET_MULTI_PLOT_CHARTS),
filter(action => !(action as any).payload?.autoRefresh),
map(action => activeLoader(action.type))
));
@@ -37,7 +37,7 @@ export class ExperimentsCompareChartsEffects {
[ScalarKeyEnum.IsoTime, ScalarKeyEnum.Timestamp].includes(axisType) &&
prevAxisType !== axisType
) {
return [setRefreshing({payload: false}), deactivateLoader(action.type)];
return [deactivateLoader(action.type)];
}
return this.eventsApi.eventsMultiTaskScalarMetricsIterHistogram({
tasks: action.payload.taskIds,
@@ -46,11 +46,10 @@ export class ExperimentsCompareChartsEffects {
mergeMap(res => [
// also here
new chartActions.SetExperimentHistogram(res, axisType),
setRefreshing({payload: false}),
deactivateLoader(action.type)]
),
catchError(error => [
requestFailed(error), deactivateLoader(action.type), setRefreshing({payload: false}),
requestFailed(error), deactivateLoader(action.type),
setServerError(error, null, 'Failed to get Scalar Charts', action.payload.autoRefresh)
])
);
@@ -66,10 +65,9 @@ export class ExperimentsCompareChartsEffects {
map(res => res.plots),
mergeMap(res => [
new chartActions.SetExperimentPlots(res),
setRefreshing({payload: false}),
deactivateLoader(action.type)]),
catchError(error => [
requestFailed(error), deactivateLoader(action.type), setRefreshing({payload: false}),
requestFailed(error), deactivateLoader(action.type),
setServerError(error, null, 'Failed to get Plot Charts', action.payload.autoRefresh)
])
)

View File

@@ -3,17 +3,17 @@ import {Actions, createEffect, ofType} from '@ngrx/effects';
import {select, Store} from '@ngrx/store';
import {activeLoader, deactivateLoader, setServerError} from '../../core/actions/layout.actions';
import {catchError, mergeMap, map, switchMap, withLatestFrom} from 'rxjs/operators';
import {ApiTasksService} from '../../../business-logic/api-services/tasks.service';
import {ApiTasksService} from '~/business-logic/api-services/tasks.service';
import {ExperimentDetailsReverterService} from '../services/experiment-details-reverter.service';
import {requestFailed} from '../../core/actions/http.actions';
import {selectExperimentIdsDetails, selectExperimentsDetails} from '../reducers';
import {Observable, of} from 'rxjs';
import {IExperimentDetail} from '../../../features/experiments-compare/experiments-compare-models';
import {REFETCH_EXPERIMENT_REQUESTED, refetchExperimentRequested, setRefreshing} from '../actions/compare-header.actions';
import {IExperimentDetail} from '~/features/experiments-compare/experiments-compare-models';
import {REFETCH_EXPERIMENT_REQUESTED, refetchExperimentRequested} from '../actions/compare-header.actions';
import {ExperimentCompareDetailsState} from '../reducers/experiments-compare-details.reducer';
import {experimentListUpdated, setExperiments} from '../actions/experiments-compare-details.actions';
import {getCompareDetailsOnlyFields} from '../../../features/experiments-compare/experiments-compare-consts';
import {selectHasDataFeature} from '../../../core/reducers/users.reducer';
import {getCompareDetailsOnlyFields} from '~/features/experiments-compare/experiments-compare-consts';
import {selectHasDataFeature} from '~/core/reducers/users.reducer';
@Injectable()
export class ExperimentsCompareDetailsEffects {
@@ -61,13 +61,11 @@ export class ExperimentsCompareDetailsEffects {
switchMap(([action, newExperimentIds, hasDataFeature]) => this.fetchExperimentDetails$(newExperimentIds, hasDataFeature).pipe(
mergeMap(experiments => [
deactivateLoader(action.type),
setRefreshing({payload: false}),
setExperiments({experiments})
]),
catchError(error => [
requestFailed(error),
deactivateLoader(action.type),
setRefreshing({payload: false}),
setServerError(
error, null,
'The attempt to retrieve your experiment data failed. Refresh your browser and try again.',

View File

@@ -3,11 +3,10 @@ import {Actions, Effect, ofType} from '@ngrx/effects';
import {Store} from '@ngrx/store';
import {IExperimentCompareMetricsValuesState} from '../reducers/experiments-compare-metrics-values.reducer';
import * as metricsValuesActions from '../actions/experiments-compare-metrics-values.actions';
import {activeLoader, deactivateLoader, setServerError} from '../../../webapp-common/core/actions/layout.actions';
import {activeLoader, deactivateLoader, setServerError} from '@common/core/actions/layout.actions';
import {catchError, mergeMap, map} from 'rxjs/operators';
import {requestFailed} from '../../core/actions/http.actions';
import {ApiTasksService} from '../../../business-logic/api-services/tasks.service';
import {setRefreshing} from '../actions/compare-header.actions';
import {ApiTasksService} from '~/business-logic/api-services/tasks.service';
@Injectable()
@@ -30,10 +29,9 @@ export class ExperimentsCompareMetricsValuesEffects {
map(res => action.payload.taskIds.map(id => res.tasks.find(ex => ex.id === id))),
mergeMap(experiments => [
new metricsValuesActions.SetComparedExperiments(experiments),
setRefreshing({payload: false}),
deactivateLoader(action.type)]),
catchError(error => [
requestFailed(error), deactivateLoader(action.type), setRefreshing({payload: false}),
requestFailed(error), deactivateLoader(action.type),
setServerError(error, null, 'Failed to get Compared Experiments', action.payload.autoRefresh)
])
)

View File

@@ -4,14 +4,14 @@ import {select, Store} from '@ngrx/store';
import * as paramsActions from '../actions/experiments-compare-params.actions';
import {activeLoader, deactivateLoader, setServerError} from '../../core/actions/layout.actions';
import {catchError, map, mergeMap, switchMap, withLatestFrom} from 'rxjs/operators';
import {ApiTasksService} from '../../../business-logic/api-services/tasks.service';
import {ApiTasksService} from '~/business-logic/api-services/tasks.service';
import {ExperimentParamsReverterService} from '../services/experiment-params-reverter.service';
import {requestFailed} from '../../core/actions/http.actions';
import {selectExperimentIdsParams, selectExperimentsParams} from '../reducers';
import {Observable, of} from 'rxjs';
import {COMPARE_PARAMS_ONLY_FIELDS} from '../../../features/experiments-compare/experiments-compare-consts';
import {IExperimentDetail} from '../../../features/experiments-compare/experiments-compare-models';
import {REFETCH_EXPERIMENT_REQUESTED, refetchExperimentRequested, setRefreshing} from '../actions/compare-header.actions';
import {COMPARE_PARAMS_ONLY_FIELDS} from '~/features/experiments-compare/experiments-compare-consts';
import {IExperimentDetail} from '~/features/experiments-compare/experiments-compare-models';
import {REFETCH_EXPERIMENT_REQUESTED, refetchExperimentRequested} from '../actions/compare-header.actions';
import {ExperimentCompareParamsState} from '../reducers/experiments-compare-params.reducer';
import {setExperiments} from '../actions/experiments-compare-params.actions';
import {ExperimentDetailBase, ExperimentParams} from '../shared/experiments-compare-details.model';
@@ -65,13 +65,11 @@ export class ExperimentsCompareParamsEffects {
this.fetchExperimentParams$(newExperimentIds).pipe(
mergeMap(experiments => [
deactivateLoader(action.type),
setRefreshing({payload: false}),
setExperiments({experiments : experiments as ExperimentParams[]})
]),
catchError(error => [
requestFailed(error),
deactivateLoader(action.type),
setRefreshing({payload: false}),
setServerError(
error, null,
'The attempt to retrieve your experiment data failed. Refresh your browser and try again.',

View File

@@ -1,19 +1,16 @@
import {Injectable} from '@angular/core';
import {Actions, createEffect, Effect, ofType} from '@ngrx/effects';
import {Store} from '@ngrx/store';
import {IExperimentCompareMetricsValuesState} from '../reducers/experiments-compare-metrics-values.reducer';
import {activeLoader, deactivateLoader, setServerError} from '../../core/actions/layout.actions';
import {catchError, mergeMap, map} from 'rxjs/operators';
import {requestFailed} from '../../core/actions/http.actions';
import {ApiTasksService} from '../../../business-logic/api-services/tasks.service';
import {ApiTasksService} from '~/business-logic/api-services/tasks.service';
import {getExperimentsHyperParams, setHyperParamsList, setMetricsList, setTasks} from '../actions/experiments-compare-scalars-graph.actions';
import {setRefreshing} from '../actions/compare-header.actions';
import {GroupedHyperParams, HyperParams} from '../reducers/experiments-compare-charts.reducer';
@Injectable()
export class ExperimentsCompareScalarsGraphEffects {
constructor(private actions$: Actions, private store: Store<IExperimentCompareMetricsValuesState>, public tasksApiService: ApiTasksService) {
constructor(private actions$: Actions, public tasksApiService: ApiTasksService) {
}
@Effect()
@@ -38,11 +35,10 @@ export class ExperimentsCompareScalarsGraphEffects {
setTasks({tasks: res.tasks}),
setMetricsList({metricsList: metricsList}),
setHyperParamsList({hyperParams: paramsHasDiffs}),
setRefreshing({payload: false}),
deactivateLoader(action.type)];
}),
catchError(error => [
requestFailed(error), deactivateLoader(action.type), setRefreshing({payload: false}),
requestFailed(error), deactivateLoader(action.type),
setServerError(error, null, 'Failed to get Compared Experiments')
])
)
@@ -65,14 +61,13 @@ export class ExperimentsCompareScalarsGraphEffects {
}
}
}
const metricsList = Object.keys(metrics).sort((a, b) => a.toLowerCase() > b.toLowerCase() ? 1 : -1).map(metricName => ({
return Object.keys(metrics).sort((a, b) => a.toLowerCase() > b.toLowerCase() ? 1 : -1).map(metricName => ({
metricName,
variants: Object.keys(metrics[metricName]).sort().map(variant => ({
name: variant,
value: metrics[metricName][variant]
}))
}));
return metricsList;
}
private getParametersHasDiffs(tasks): GroupedHyperParams {

View File

@@ -2,7 +2,7 @@ import {Injectable} from '@angular/core';
import {Actions, createEffect, Effect, ofType} from '@ngrx/effects';
import {debounceTime, filter, map, mergeMap, switchMap, withLatestFrom} from 'rxjs/operators';
import {activeLoader, deactivateLoader} from '../../core/actions/layout.actions';
import {ApiTasksService} from '../../../business-logic/api-services/tasks.service';
import {ApiTasksService} from '~/business-logic/api-services/tasks.service';
import {
compareAddDialogSetTableSort,
compareAddDialogTableSortChanged,
@@ -10,13 +10,11 @@ import {
getSelectedExperimentsForCompareAddDialog,
refreshIfNeeded,
setExperimentsUpdateTime,
setRefreshing,
setSearchExperimentsForCompareResults
setSearchExperimentsForCompareResults,
} from '../actions/compare-header.actions';
import {select, Store} from '@ngrx/store';
import {flatten, get, isEmpty} from 'lodash/fp';
import {selectCompareAddTableSortFields, selectExperimentsUpdateTime} from '../reducers';
import {EmptyAction} from '../../../app.constants';
import {selectExperimentsUpdateTime} from '../reducers';
import {selectRouterParams} from '../../core/reducers/router-reducer';
import {selectAppVisible} from '../../core/reducers/view.reducer';
import {MINIMUM_ONLY_FIELDS} from '../../experiments/experiment.consts';
@@ -24,12 +22,17 @@ import * as exSelectors from '../../experiments/reducers';
import {selectExperimentsMetricsCols, selectExperimentsTableCols, selectTableSortFields} from '../../experiments/reducers';
import {selectSelectedProjectId} from '../../core/reducers/projects.reducer';
import {addMultipleSortColumns} from '../../shared/utils/shared-utils';
import {RefreshService} from '@common/core/services/refresh.service';
@Injectable()
export class SelectCompareHeaderEffects {
constructor(private actions: Actions, public experimentsApi: ApiTasksService, private store: Store<any>) {
}
constructor(
private actions: Actions,
public experimentsApi: ApiTasksService,
private store: Store,
private refresh: RefreshService
) {}
@Effect()
activeLoader = this.actions.pipe(
@@ -48,25 +51,24 @@ export class SelectCompareHeaderEffects {
filter(([, isAppVisible, ,]) => isAppVisible),
switchMap(([action, , experimentsIds, experimentsUpdateTime]) =>
// eslint-disable-next-line @typescript-eslint/naming-convention
this.experimentsApi.tasksGetAllEx({id: experimentsIds, only_fields: ['last_update']}).pipe(
this.experimentsApi.tasksGetAllEx({id: experimentsIds, only_fields: ['last_change']}).pipe(
mergeMap((res) => {
const updatedExperimentsUpdateTime: { [key: string]: Date } = {};
res.tasks.forEach(task => {
updatedExperimentsUpdateTime[task.id] = task.last_update;
updatedExperimentsUpdateTime[task.id] = task.last_change;
});
const experimentsWhereUpdated = !!(experimentsIds.find((id) =>
(new Date(experimentsUpdateTime[id]).getTime()) < new Date(updatedExperimentsUpdateTime[id]).getTime()
));
const shouldUpdate = ((!action.payload) || (!action.autoRefresh) || experimentsWhereUpdated) && !(isEmpty(experimentsUpdateTime));
const experimentsWhereUpdated = experimentsIds.some(id =>
new Date(experimentsUpdateTime[id]) < new Date(updatedExperimentsUpdateTime[id])
);
if (((!action.payload) || (!action.autoRefresh) || experimentsWhereUpdated) && !(isEmpty(experimentsUpdateTime))) {
this.refresh.trigger(true);
}
return [
setExperimentsUpdateTime({payload: updatedExperimentsUpdateTime}),
(shouldUpdate) ? setRefreshing({
payload: action.payload,
autoRefresh: action.autoRefresh
}) : new EmptyAction()];
setExperimentsUpdateTime({payload: updatedExperimentsUpdateTime})];
}))
)
);
tableSortChange = createEffect(() => this.actions.pipe(
ofType(compareAddDialogTableSortChanged),
withLatestFrom(

View File

@@ -1,6 +1,3 @@
import { EXPERIMENTS_TABLE_COL_FIELDS } from "../../features/experiments/shared/experiments.const";
import {ColHeaderFilterTypeEnum, ColHeaderTypeEnum, ISmCol} from "../shared/ui-components/data/table/table.consts";
export const RENAME_MAP = {
'network_design': 'Network Design',
'uncommitted_changes': 'Uncommitted Changes',
@@ -49,138 +46,15 @@ export const COMPARE_DETAILS_ONLY_FIELDS_BASE = [
'last_iteration',
'configuration'
];
export const ADD_EXPERIMENT_FOR_COMPARE_TABLE_COLS: ISmCol[] = [
{
id : EXPERIMENTS_TABLE_COL_FIELDS.SELECTED,
sortable : false,
filterable : false,
headerType : ColHeaderTypeEnum.checkBox,
header : '',
hidden : false,
static : true,
bodyStyleClass : 'selected-col-body type-col',
headerStyleClass: 'selected-col-header',
style : {width: '50px'},
disableDrag : true,
disablePointerEvents: true
},
{
id : EXPERIMENTS_TABLE_COL_FIELDS.TYPE,
headerType : ColHeaderTypeEnum.sortFilter,
sortable : false,
filterable : false,
static : true,
header : 'TYPE',
bodyStyleClass: 'type-col',
style : {width: '115px'},
},
{
id : EXPERIMENTS_TABLE_COL_FIELDS.NAME,
headerType : ColHeaderTypeEnum.sortFilter,
sortable : false,
static : true,
header : 'NAME',
style : {width: '400px'},
},
{
id: EXPERIMENTS_TABLE_COL_FIELDS.TAGS,
headerType : ColHeaderTypeEnum.sortFilter,
filterable: false,
searchableFilter: false,
sortable: false,
static: true,
header: 'TAGS',
style: {width: '300px'},
andFilter: true
},
{
id : EXPERIMENTS_TABLE_COL_FIELDS.STATUS,
headerType : ColHeaderTypeEnum.sortFilter,
filterable : false,
static : false,
header : 'STATUS',
style : {width: '115px'},
},
{
id : EXPERIMENTS_TABLE_COL_FIELDS.PROJECT,
headerType : ColHeaderTypeEnum.title,
static : true,
header : 'PROJECT',
style : {width: '150px'},
},
{
id : EXPERIMENTS_TABLE_COL_FIELDS.USER,
getter : 'user.name',
headerType : ColHeaderTypeEnum.sortFilter,
searchableFilter: false,
filterable : false,
sortable : false,
static : true,
header : 'USER',
style : {width: '115px'},
},
{
id : EXPERIMENTS_TABLE_COL_FIELDS.STARTED,
headerType : ColHeaderTypeEnum.sortFilter,
sortable : false,
filterType : ColHeaderFilterTypeEnum.durationDate,
filterable: false,
searchableFilter: false,
static : true,
header : 'STARTED',
style : {width: '150px'},
},
{
id : EXPERIMENTS_TABLE_COL_FIELDS.LAST_UPDATE,
headerType : ColHeaderTypeEnum.sortFilter,
sortable : false,
filterType : ColHeaderFilterTypeEnum.durationDate,
filterable: false,
searchableFilter: false,
static : true,
header : 'UPDATED',
label : 'Updated',
style : {width: '150px'},
},
{
id : EXPERIMENTS_TABLE_COL_FIELDS.LAST_ITERATION,
headerType : ColHeaderTypeEnum.sortFilter,
sortable : false,
filterType : ColHeaderFilterTypeEnum.durationNumeric,
filterable : false,
searchableFilter: false,
static : true,
header : 'ITERATION',
label : 'Iterations:',
style : {width: '115px'},
},
{
id : EXPERIMENTS_TABLE_COL_FIELDS.COMMENT,
headerType: ColHeaderTypeEnum.sortFilter,
sortable : false,
header : 'DESCRIPTION',
style : {width: '300px'}
},
{
id : EXPERIMENTS_TABLE_COL_FIELDS.ACTIVE_DURATION,
headerType: ColHeaderTypeEnum.sortFilter,
sortable : false,
filterable: false,
filterType : ColHeaderFilterTypeEnum.duration,
searchableFilter: false,
static : true,
bodyStyleClass: 'type-col',
header : 'RUN TIME',
style : {width: '150px'}
},
{
id : EXPERIMENTS_TABLE_COL_FIELDS.PARENT,
getter : [EXPERIMENTS_TABLE_COL_FIELDS.PARENT, 'parent.project.id', 'parent.project.name'],
headerType: ColHeaderTypeEnum.sortFilter,
searchableFilter: false,
filterable : false,
sortable : false,
header : 'PARENT TASK',
style : {width: '200px'}
}
export const COMPARE_DEBUG_IMAGES_ONLY_FIELDS = [
'id',
'name',
'type',
'status',
'last_update',
'project.name',
'tags',
'published',
'last_iteration',
];

View File

@@ -20,7 +20,6 @@ import {ExperimentGraphsModule} from '../shared/experiment-graphs/experiment-gra
import {DebugImagesModule} from '../debug-images/debug-images.module';
import {ExperimentCompareSharedModule} from './shared/experiment-compare-shared.module';
import {ExperimentCompareGeneralDataComponent} from './dumbs/experiment-compare-general-data/experiment-compare-general-data.component';
import {GetKeyValueArrayPipePipe} from './get-key-value-array-pipe.pipe';
import {SelectCompareHeaderEffects} from './effects/select-experiment-for-compare-effects.service';
import {SelectExperimentsForCompareComponent} from './containers/select-experiments-for-compare/select-experiments-for-compare.component';
@@ -59,7 +58,6 @@ export const compareSyncedKeys = [
ExperimentCompareScalarChartsComponent,
ExperimentComparePlotsComponent,
ExperimentCompareHeaderComponent,
ExperimentCompareGeneralDataComponent,
GetKeyValueArrayPipePipe,
SelectExperimentsForCompareComponent,
CompareCardListComponent,

View File

@@ -4,7 +4,6 @@ import {
setExperimentsUpdateTime,
setHideIdenticalFields,
setNavigationPreferences,
setRefreshing,
setSearchExperimentsForCompareResults,
setShowSearchExperimentsForCompare,
toggleShowScalarOptions
@@ -22,7 +21,6 @@ export interface CompareHeaderState {
hideIdenticalRows: boolean;
viewMode: string;
showScalarOptions: boolean;
refreshing: boolean;
autoRefresh: boolean;
navigationPreferences: Params;
experimentsUpdateTime: { [key: string]: Date };
@@ -38,7 +36,6 @@ export const initialState: CompareHeaderState = {
hideIdenticalRows: false,
viewMode: 'values',
showScalarOptions: false,
refreshing: false,
autoRefresh: false,
navigationPreferences: {},
experimentsUpdateTime: {},
@@ -55,11 +52,6 @@ const _compareHeader = createReducer(initialState,
on(setExperimentsUpdateTime, (state: CompareHeaderState, {payload}) => ({...state, experimentsUpdateTime: payload})),
on(setShowSearchExperimentsForCompare, (state: CompareHeaderState, {payload}) => ({...state, showSearch: payload})),
on(toggleShowScalarOptions, (state: CompareHeaderState) => ({...state, showScalarOptions: !state.showScalarOptions})),
on(setRefreshing, (state: CompareHeaderState, {payload, autoRefresh}) => ({
...state,
refreshing: payload,
autoRefresh
})),
on(setNavigationPreferences, (state: CompareHeaderState, {navigationPreferences}) => ({
...state,
navigationPreferences: {...state.navigationPreferences, ...navigationPreferences}

View File

@@ -53,7 +53,6 @@ export const selectExperimentsForCompareSearchTerm = createSelector(selectCompar
export const selectShowAddExperimentsForCompare = createSelector(selectCompareHeader, state => state?.showSearch);
export const selectHideIdenticalFields = createSelector(selectCompareHeader, state => state?.hideIdenticalRows);
export const selectShowScalarsOptions = createSelector(selectCompareHeader, state => state?.showScalarOptions);
export const selectRefreshing = createSelector(selectCompareHeader, state => state ? {refreshing: state.refreshing, autoRefresh: state.autoRefresh} : {refreshing: false, autoRefresh: false});
export const selectExperimentsUpdateTime = createSelector(selectCompareHeader, state => state ? state.experimentsUpdateTime : {});
export const selectNavigationPreferences = createSelector(selectCompareHeader, state => state ? state.navigationPreferences : {});

View File

@@ -1,7 +1,7 @@
import {Action, createAction, props} from '@ngrx/store';
import {Task} from '~/business-logic/model/tasks/task';
import {IExperimentInfo, ISelectedExperiment} from '~/features/experiments/shared/experiment-info.model';
import {ITableExperiment} from '../shared/common-experiment-model.model';
import {IExperimentModelInfo, ITableExperiment} from '../shared/common-experiment-model.model';
import {ParamsItem} from '~/business-logic/model/tasks/paramsItem';
import {ConfigurationItem} from '~/business-logic/model/tasks/configurationItem';
import {IExperimentInfoState} from '~/features/experiments/reducers/experiment-info.reducer';
@@ -82,10 +82,20 @@ export const setExperimentUncommittedChanges = createAction(
props<{ diff: string }>()
);
export const getExperimentArtifacts= createAction(
EXPERIMENTS_INFO_PREFIX + '[get artifacts]',
props<{ experimentId: string; autoRefresh?: boolean }>()
);
export const setExperimentArtifacts = createAction(
EXPERIMENTS_INFO_PREFIX + '[set artifacts]',
props<{model: IExperimentModelInfo; experimentId: string}>()
);
export class UpdateExperimentInfoData implements Action {
readonly type = UPDATE_EXPERIMENT_INFO_DATA;
constructor(public payload: { id: ITableExperiment['id']; changes: Partial<IExperimentInfo> }) {
constructor(public payload: { id?: ITableExperiment['id']; changes: Partial<IExperimentInfo> }) {
}
}
export const saveExperimentInputModel = createAction(

View File

@@ -80,6 +80,7 @@ export const experimentSelectionChanged = createAction(
props<{experiment: {id?: string}; project?: string}>()
);
export const selectAllExperiments = createAction(
EXPERIMENTS_PREFIX + ' [select all experiments]',
props<{filtered: boolean}>()
@@ -104,6 +105,7 @@ export const setUsers = createAction(
props<{ users: User[] }>()
);
export const setParents = createAction(
EXPERIMENTS_PREFIX + '[set project experiment parents]',
props<{ parents: ProjectsGetTaskParentsResponseParents[]}>()
@@ -232,3 +234,7 @@ export const setSelectedExperimentsDisableAvailable = createAction(
EXPERIMENTS_PREFIX + 'setSelectedExperimentsDisableAvailable',
props<{ selectedExperimentsDisableAvailable: Record<string, CountAvailableAndIsDisableSelectedFiltered> }>()
);
export const setTableMode = createAction(
EXPERIMENTS_PREFIX + '[set table view mode]',
props<{mode: 'info' | 'table'}>()
)

View File

@@ -117,7 +117,7 @@ import { GetVariantWithoutRoundPipe } from './dumb/experiments-table/hyper-param
MatProgressSpinnerModule,
SharedModule,
ExperimentOutputLogModule,
MatRadioModule,
MatRadioModule
],
providers: [ExperimentTableCardComponent, NoUnderscorePipe, TitleCasePipe]
})

View File

@@ -1,16 +1,23 @@
import {Component, OnDestroy} from '@angular/core';
import {Store} from '@ngrx/store';
import {selectBackdropActive} from '../../../core/reducers/view.reducer';
import {selectBackdropActive} from '@common/core/reducers/view.reducer';
import {combineLatest, Observable, Subscription} from 'rxjs';
import {IExperimentInfoState} from '../../../../features/experiments/reducers/experiment-info.reducer';
import {selectExperimentModelInfoData} from '../../reducers';
import {selectExperimentInfoData, selectIsExperimentEditable} from '../../../../features/experiments/reducers';
import {IExperimentInfo} from '../../../../features/experiments/shared/experiment-info.model';
import {selectRouterConfig, selectRouterParams} from '../../../core/reducers/router-reducer';
import {distinctUntilChanged, filter, map} from 'rxjs/operators';
import {get, getOr} from 'lodash/fp';
import {IExperimentInfoState} from '~/features/experiments/reducers/experiment-info.reducer';
import {selectCurrentArtifactExperimentId, selectExperimentModelInfoData} from '../../reducers';
import {
selectExperimentInfoData,
selectIsExperimentEditable
} from '~/features/experiments/reducers';
import {IExperimentInfo} from '~/features/experiments/shared/experiment-info.model';
import {selectRouterConfig, selectRouterParams} from '@common/core/reducers/router-reducer';
import {debounceTime, distinctUntilChanged, filter, map} from 'rxjs/operators';
import {ActivatedRoute, Router} from '@angular/router';
import {IExperimentModelInfo} from '../../shared/common-experiment-model.model';
import {
getExperimentArtifacts,
setExperimentArtifacts
} from '@common/experiments/actions/common-experiments-info.actions';
import {selectSelectedProject} from '@common/core/reducers/projects.reducer';
@Component({
selector: 'sm-experiment-info-artifacts-model',
@@ -22,52 +29,66 @@ export class ExperimentInfoArtifactsComponent implements OnDestroy {
public modelInfo$: Observable<IExperimentModelInfo>;
public ExperimentInfo$: Observable<IExperimentInfo>;
public activeSection: any;
private selectedRouterConfigSubs: Subscription;
public selectedId$: Observable<string>;
private artifactSubscription: Subscription;
private onOutputModel$: Observable<boolean>;
private onInputModel$: Observable<boolean>;
private experimentKey$: Observable<string>;
public routerConfig$: Observable<string[]>;
public editable$: Observable<boolean>;
public minimized: boolean;
private previousTarget: string;
private sub = new Subscription();
constructor(private store: Store<IExperimentInfoState>, public router: Router, private route: ActivatedRoute
) {
this.minimized = getOr(false, 'data.minimized', this.route.snapshot.routeConfig);
this.minimized = !!this.route.snapshot?.routeConfig?.data?.minimized;
this.backdropActive$ = this.store.select(selectBackdropActive);
this.editable$ = this.store.select(selectIsExperimentEditable);
this.modelInfo$ = this.store.select(selectExperimentModelInfoData);
this.ExperimentInfo$ = this.store.select(selectExperimentInfoData);
this.routerConfig$ = this.store.select(selectRouterConfig);
this.selectedRouterConfigSubs = this.store.select(selectRouterConfig)
this.selectedId$ = this.store.select(selectRouterParams).pipe(map(params => params?.artifactId || params?.modelId));
this.experimentKey$ = this.store.select(selectRouterParams).pipe(map(params => params?.experimentId));
this.sub.add(this.store.select(selectRouterConfig)
.pipe(filter(rc => !!rc))
.subscribe((routerConfig: string[]) => {
this.activeSection = this.minimized ? routerConfig[5] : routerConfig[6];
});
})
);
this.selectedId$ = this.store.select(selectRouterParams)
this.sub.add(combineLatest([
this.experimentKey$,
this.store.select(selectSelectedProject)
])
.pipe(
map(params => get('artifactId', params) || get('modelId', params)),
);
filter(([id, project]) => !!id && !!project?.id),
map(([id]) => id),
distinctUntilChanged()
)
.subscribe(experimentId => {
this.store.dispatch(setExperimentArtifacts({model: null, experimentId: null}));
this.store.dispatch(getExperimentArtifacts({experimentId}));
})
);
this.experimentKey$ = this.store.select(selectRouterParams)
.pipe(
map(params => get('experimentId', params)),
);
this.artifactSubscription = combineLatest([this.selectedId$, this.modelInfo$, this.experimentKey$, this.ExperimentInfo$])
this.sub.add(combineLatest([
this.selectedId$,
this.modelInfo$,
this.experimentKey$,
this.ExperimentInfo$,
this.store.select(selectCurrentArtifactExperimentId)
])
.pipe(
debounceTime(0),
distinctUntilChanged(),
filter(([selectedId, modelInfo, experimentKey, experimentInfo]) =>
!!modelInfo && experimentInfo && experimentKey && experimentInfo.id === experimentKey))
filter(([, modelInfo, experimentKey, experimentInfo, artifactsExperiment]) =>
!!modelInfo && experimentInfo && experimentKey && artifactsExperiment === experimentKey))
.subscribe(([selectedId, modelInfo]) => {
const onOutputModel = this.route.snapshot.firstChild?.data?.outputModel;
const onInputModel = this.route.snapshot.firstChild?.data?.outputModel === false;
if (selectedId) {
const selectedArtifact = modelInfo.artifacts.find(artifact => artifact.key === selectedId);
const selectedInputModel = modelInfo.input.find(model => model.id === selectedId);
const selectedOutputModel = modelInfo.output.find(model => model.id === selectedId);
const selectedArtifact = modelInfo.artifacts?.find(artifact => artifact.key === selectedId);
const selectedInputModel = modelInfo.input?.find(model => model.id === selectedId);
const selectedOutputModel = modelInfo.output?.find(model => model.id === selectedId);
const onArtifact = !onInputModel && !onOutputModel;
if ((onOutputModel && !selectedOutputModel) || (onInputModel && !selectedInputModel) || (onArtifact && !selectedArtifact)) {
this.resetSelection(modelInfo);
@@ -75,7 +96,8 @@ export class ExperimentInfoArtifactsComponent implements OnDestroy {
} else {
this.resetSelection(modelInfo);
}
});
})
);
}
private navigateToTarget(target: string) {
@@ -101,8 +123,7 @@ export class ExperimentInfoArtifactsComponent implements OnDestroy {
}
ngOnDestroy(): void {
this.selectedRouterConfigSubs.unsubscribe();
this.artifactSubscription.unsubscribe();
this.sub.unsubscribe();
}
}

View File

@@ -148,6 +148,7 @@ export class ExperimentInfoExecutionComponent implements OnInit, OnDestroy {
.pipe(filter( bool => !isUndefined(bool)))
.subscribe( setup_shell_script => {
if (this.formData.container.setup_shell_script !== setup_shell_script) {
smEditableSection.saveSection();
this.store.dispatch(commonInfoActions.saveExperimentSection({container: {...this.formData.container, setup_shell_script}}));
} else {
smEditableSection.cancelClickedEvent();
@@ -164,6 +165,7 @@ export class ExperimentInfoExecutionComponent implements OnInit, OnDestroy {
if (data === undefined) {
this.requirementsSection.cancelClickedEvent();
} else {
this.requirementsSection.saveSection();
this.store.dispatch(commonInfoActions.saveExperimentSection({script: {requirements: {...this.formData.requirements, pip: data}}}));
}
});
@@ -171,11 +173,14 @@ export class ExperimentInfoExecutionComponent implements OnInit, OnDestroy {
editDiff() {
this.openEditJsonDialog({textData: this.formData?.diff, readOnly: false, title: 'EDIT UNCOMMITTED CHANGES', typeJson: false}, this.diffSection)
.afterClosed().pipe(take(1)).subscribe((data) => {
if (!isUndefined(data)) {
this.store.dispatch(commonInfoActions.saveExperimentSection({script: {diff: data}}));
}
});
.afterClosed()
.pipe(take(1))
.subscribe((data) => {
this.diffSection.unsubscribeToEventListener();
if (!isUndefined(data)) {
this.store.dispatch(commonInfoActions.saveExperimentSection({script: {diff: data}}));
}
});
}
clearInstalledPackages() {

View File

@@ -22,7 +22,7 @@
[modelLabels]="modelLabels"
[source]="source"
[showCreatedExperiment]="!outputMode"
(modelSelected)="onModelSelected($event)">
(modelSelected)="onModelSelected($event); modelSection.unsubscribeToEventListener()">
</sm-experiment-models-form-view>
</sm-editable-section>
<sm-editable-section class="editable-design"

View File

@@ -2,19 +2,19 @@ import {Component, OnDestroy, OnInit, ViewChild} from '@angular/core';
import {Store} from '@ngrx/store';
import {selectExperimentModelInfoData, selectExperimentUserKnowledge, selectIsExperimentSaving} from '../../reducers';
import {IExperimentModelInfo, IModelInfo, IModelInfoSource} from '../../shared/common-experiment-model.model';
import {Model} from '../../../../business-logic/model/models/model';
import {Model} from '~/business-logic/model/models/model';
import {combineLatest, Observable, Subject} from 'rxjs';
import {IExperimentInfoState} from '../../../../features/experiments/reducers/experiment-info.reducer';
import {experimentSectionsEnum} from '../../../../features/experiments/shared/experiments.const';
import {selectIsExperimentEditable, selectSelectedExperiment} from '../../../../features/experiments/reducers';
import {IExperimentInfoState} from '~/features/experiments/reducers/experiment-info.reducer';
import {experimentSectionsEnum} from '~/features/experiments/shared/experiments.const';
import {selectIsExperimentEditable, selectSelectedExperiment} from '~/features/experiments/reducers';
import * as commonInfoActions from '../../actions/common-experiments-info.actions';
import {ActivateEdit, CancelExperimentEdit, DeactivateEdit} from '../../actions/common-experiments-info.actions';
import {ExperimentModelsFormViewComponent} from '../../dumb/experiment-models-form-view/experiment-models-form-view.component';
import {getModelDesign} from '../../../tasks/tasks.utils';
import {distinctUntilKeyChanged, filter, map, takeUntil} from 'rxjs/operators';
import {getModelDesign} from '@common/tasks/tasks.utils';
import {distinctUntilChanged, filter, map, takeUntil} from 'rxjs/operators';
import {ActivatedRoute, Router} from '@angular/router';
import {IExperimentInfo} from '../../../../features/experiments/shared/experiment-info.model';
import {addMessage} from '../../../core/actions/layout.actions';
import {IExperimentInfo} from '~/features/experiments/shared/experiment-info.model';
import {addMessage} from '@common/core/actions/layout.actions';
@Component({
@@ -48,16 +48,15 @@ export class ExperimentInfoModelComponent implements OnInit, OnDestroy {
this.saving$ = this.store.select(selectIsExperimentSaving);
this.selectedExperiment$ = this.store.select(selectSelectedExperiment);
this.routerModelId$ = this.route.params.pipe(
takeUntil(this.unsubscribe$),
filter(params => params.modelId),
distinctUntilKeyChanged('modelId'),
map(params => params.modelId));
map(params => params?.modelId),
filter(params => !!params),
distinctUntilChanged()
);
}
ngOnInit() {
combineLatest([this.routerModelId$, this.modelInfo$]).pipe(takeUntil(this.unsubscribe$))
combineLatest([this.routerModelId$, this.modelInfo$])
.pipe(takeUntil(this.unsubscribe$))
.subscribe(([modelId, formData]) => {
this.modelId = modelId;
this.outputMode = this.route.snapshot.data?.outputModel;
@@ -72,7 +71,6 @@ export class ExperimentInfoModelComponent implements OnInit, OnDestroy {
this.inputDesign = (design.value === undefined || design.key === undefined && Object.keys(design.value).length === 0) ? null : design.value;
this.modelProjectId = this.model?.project?.id ? this.model.project.id : '*';
});
}
ngOnDestroy(): void {
@@ -91,9 +89,9 @@ export class ExperimentInfoModelComponent implements OnInit, OnDestroy {
} else {
newModels = [...this.models.map(model => ({model: model.id, name: model.name})), {model: selectedModel.id, name: selectedModel.name}];
}
this.store.dispatch(new commonInfoActions.SetExperimentInfoData({models: {input: newModels as any}}));
this.store.dispatch(new commonInfoActions.UpdateExperimentInfoData({changes: {model: {input: newModels as any}}}));
this.store.dispatch(commonInfoActions.saveExperimentSection({models: {input: newModels as any}}));
this.router.navigate(['..', selectedModel.id || ''], {relativeTo: this.route, queryParamsHandling: 'preserve'});
return this.router.navigate([{modelId: selectedModel.id || ''}], {relativeTo: this.route, replaceUrl: true})
}
}

View File

@@ -1,26 +1,27 @@
import {Component, OnDestroy, OnInit} from '@angular/core';
import {selectRouterConfig, selectRouterParams} from '../../../core/reducers/router-reducer';
import {selectRouterConfig, selectRouterParams} from '@common/core/reducers/router-reducer';
import {get, getOr} from 'lodash/fp';
import {select, Store} from '@ngrx/store';
import {interval, Observable, Subscription} from 'rxjs';
import {Observable, Subscription} from 'rxjs';
import {ActivatedRoute, Router} from '@angular/router';
import {distinctUntilChanged, filter, map, tap, withLatestFrom} from 'rxjs/operators';
import {Project} from '../../../../business-logic/model/projects/project';
import {IExperimentInfo} from '../../../../features/experiments/shared/experiment-info.model';
import {ExperimentOutputState} from '../../../../features/experiments/reducers/experiment-output.reducer';
import {Project} from '~/business-logic/model/projects/project';
import {IExperimentInfo} from '~/features/experiments/shared/experiment-info.model';
import {ExperimentOutputState} from '~/features/experiments/reducers/experiment-output.reducer';
import {
selectExperimentInfoData,
selectIsSharedAndNotOwner,
selectSelectedExperiment
} from '../../../../features/experiments/reducers';
} from '~/features/experiments/reducers';
import {ResetExperimentMetrics, toggleSettings} from '../../actions/common-experiment-output.actions';
import * as infoActions from '../../actions/common-experiments-info.actions';
import {selectAppVisible, selectAutoRefresh, selectBackdropActive} from '../../../core/reducers/view.reducer';
import {addMessage, setAutoRefresh} from '../../../core/actions/layout.actions';
import {AUTO_REFRESH_INTERVAL, MESSAGES_SEVERITY} from '../../../../app.constants';
import {selectAppVisible, selectBackdropActive} from '@common/core/reducers/view.reducer';
import {addMessage, setAutoRefresh} from '@common/core/actions/layout.actions';
import {MESSAGES_SEVERITY} from '~/app.constants';
import {selectIsExperimentInEditMode, selectSelectedExperiments} from '../../reducers';
import {isReadOnly} from '../../../shared/utils/shared-utils';
import {isReadOnly} from '@common/shared/utils/shared-utils';
import {ExperimentDetailsUpdated} from '../../actions/common-experiments-info.actions';
import {RefreshService} from '@common/core/services/refresh.service';
@Component({
selector: 'sm-base-experiment-output',
@@ -35,7 +36,6 @@ export abstract class BaseExperimentOutputComponent implements OnInit, OnDestroy
public currentComponent: string;
public currentMetric: string;
public minimized: boolean;
public autoRefreshState$;
private isExperimentInEditMode$: Observable<boolean>;
private projectId: Project['id'];
public experimentId: string;
@@ -44,10 +44,14 @@ export abstract class BaseExperimentOutputComponent implements OnInit, OnDestroy
isSharedAndNotOwner$: Observable<boolean>;
public isExample: boolean;
constructor(private store: Store<ExperimentOutputState>, private router: Router, private route: ActivatedRoute) {
constructor(
private store: Store<ExperimentOutputState>,
private router: Router,
private route: ActivatedRoute,
private refresh: RefreshService
) {
this.infoData$ = this.store.select(selectExperimentInfoData);
this.isSharedAndNotOwner$ = this.store.select((selectIsSharedAndNotOwner));
this.autoRefreshState$ = this.store.select(selectAutoRefresh);
this.isExperimentInEditMode$ = this.store.select(selectIsExperimentInEditMode);
this.isAppVisible$ = this.store.select(selectAppVisible);
this.backdropActive$ = this.store.select(selectBackdropActive);
@@ -76,13 +80,20 @@ export abstract class BaseExperimentOutputComponent implements OnInit, OnDestroy
this.store.dispatch(new infoActions.GetExperimentInfo(experimentId));
})
);
this.subs.add(interval(AUTO_REFRESH_INTERVAL).pipe(
withLatestFrom(this.autoRefreshState$, this.isAppVisible$, this.isExperimentInEditMode$),
filter(([, autoRefreshState, isVisible, isExperimentInEditMode]) => isVisible && autoRefreshState && !isExperimentInEditMode &&!this.minimized)
).subscribe(() => {
this.refresh(true);
this.subs.add(this.refresh.tick
.pipe(
withLatestFrom(this.isExperimentInEditMode$),
filter(([, isExperimentInEditMode]) => !isExperimentInEditMode && !this.minimized)
).subscribe(([auto]) => {
if (auto === null) {
this.store.dispatch(new infoActions.AutoRefreshExperimentInfo(this.experimentId));
} else {
this.store.dispatch(new infoActions.GetExperimentInfo(this.experimentId));
}
})
);
this.subs.add(this.store.pipe(select(selectSelectedExperiment),
filter(experiment => experiment?.id === this.experimentId))
.subscribe(experiment => {
@@ -92,33 +103,20 @@ export abstract class BaseExperimentOutputComponent implements OnInit, OnDestroy
);
}
ngOnDestroy() {
this.subs.unsubscribe();
}
returnToInfo(experiment) {
this.router.navigateByUrl(`projects/${this.projectId}/experiments/${experiment.id}`);
}
setAutoRefresh($event: boolean) {
this.store.dispatch(setAutoRefresh({autoRefresh: $event}));
}
refresh(isAutorefresh) {
if (isAutorefresh) {
this.store.dispatch(new infoActions.AutoRefreshExperimentInfo(this.experimentId));
} else {
this.store.dispatch(new infoActions.GetExperimentInfo(this.experimentId));
}
}
minimizeView() {
const part = this.route.firstChild.routeConfig.path;
if (['log', 'metrics/scalar', 'metrics/plots', 'debugImages'].includes(part)) {
this.router.navigateByUrl(`projects/${this.projectId}/experiments/${this.experimentId}/info-output/${part}`);
} else {
const parts = this.router ? this.router.url.split('/') : window.location.pathname.split('/');;
const parts = this.router ? this.router.url.split('/') : window.location.pathname.split('/');
parts.splice(5, 1);
this.router.navigateByUrl(parts.join('/'));
}

View File

@@ -4,7 +4,7 @@
<b>Hostname:</b> {{creator}}
</span>
</div>
<button class="btn btn-primary mr-5" (click)="downloadLog()">
<button class="btn btn-cml-primary mr-5" (click)="downloadLog()">
<i class="fa fa-download"></i>
Download full log
</button>

View File

@@ -24,8 +24,8 @@ import {
SetLogFilter
} from '../../actions/common-experiment-output.actions';
import {ExperimentLogInfoComponent} from '../../dumb/experiment-log-info/experiment-log-info.component';
import {selectRefreshing} from '@common/experiments-compare/reducers';
import {ITableExperiment} from '@common/experiments/shared/common-experiment-model.model';
import {RefreshService} from '@common/core/services/refresh.service';
@Component({
selector: 'sm-experiment-output-log',
@@ -52,7 +52,7 @@ export class ExperimentOutputLogComponent implements OnInit, AfterViewInit, OnDe
private experiment$ = new BehaviorSubject<ITableExperiment>(null);
@ViewChildren(ExperimentLogInfoComponent) private logRefs: QueryList<ExperimentLogInfoComponent>;
constructor(private store: Store<IExperimentInfoState>, private cdr: ChangeDetectorRef) {
constructor(private store: Store<IExperimentInfoState>, private cdr: ChangeDetectorRef, private refresh: RefreshService) {
this.log$ = this.store.select(selectExperimentLog);
this.logBeginning$ = this.store.select(selectExperimentBeginningOfLog);
this.filter$ = this.store.select(selectLogFilter);
@@ -77,13 +77,14 @@ export class ExperimentOutputLogComponent implements OnInit, AfterViewInit, OnDe
id: this.currExperiment.id,
direction: null
}));
} else if (!this.logRef?.lines?.length || this.logRef?.canRefresh) {
this.store.dispatch(getExperimentLog({
id: this.currExperiment.id,
direction: !this.logRef?.orgLogs ? 'prev' : 'next',
from: last(this.logRef?.orgLogs)?.timestamp
}));
}
// else if (!this.logRef?.lines?.length || this.logRef?.canRefresh) {
// this.store.dispatch(getExperimentLog({
// id: this.currExperiment.id,
// direction: !this.logRef?.orgLogs ? 'prev' : 'next',
// from: last(this.logRef?.orgLogs)?.timestamp
// }));
// }
})
);
}
@@ -102,9 +103,8 @@ export class ExperimentOutputLogComponent implements OnInit, AfterViewInit, OnDe
}
}));
this.subs.add(this.store.select(selectRefreshing)
.pipe(filter(({refreshing}) => refreshing))
.subscribe(({autoRefresh}) => this.store.dispatch(getExperimentLog({
this.subs.add(this.refresh.tick
.subscribe((autoRefresh) => this.store.dispatch(getExperimentLog({
id: this.currExperiment.id,
direction: autoRefresh ? 'prev' : 'next',
from: last(this.logRef?.orgLogs)?.timestamp

View File

@@ -27,7 +27,7 @@
</sm-selectable-grouped-filter-list>
</div>
</mat-drawer>
<mat-drawer-content>
<mat-drawer-content class="overflow-hidden">
<sm-graph-settings-bar
class="ribbon-setting-bar"
[class.showSettings]="showSettingsBar && minimized"
@@ -42,8 +42,13 @@
></sm-graph-settings-bar>
<div class="graphs-container" [class.maximized]="!minimized">
<div class="hover-button" *ngIf="minimized">
<button class="btn btn-secondary" (click)="drawer.open()">
Toggle Graphs
<button
class="btn btn-secondary"
(click)="drawer.open()"
smTooltip="Toggle Graphs"
matTooltipPosition="above"
>
<i class="al-icon al-ico-toogle-graph"></i>
</button>
</div>
<sm-experiment-graphs
@@ -52,6 +57,7 @@
[isGroupGraphs]="false"
[metrics]="graphs"
[hiddenList]="listOfHidden | async"
[smoothWeight]="smoothWeight$ | async"
[legendStringLength]="minimized? 14 : undefined"
[minimized]="minimized"
[xAxisType]="xAxisType$ | async"

View File

@@ -51,6 +51,11 @@ $list-width: 300px;
top: 16px;
left: 16px;
z-index: 12;
button {
padding: 6px;
display: flex;
align-items: center;
}
}
.graphs-container {

View File

@@ -4,7 +4,7 @@
buttonTooltip="Customize table"
[showButton]="false"
(click)="!disabled && getMetricsToDisplay.emit()"
(onMenuClosed)="selectMetricActiveChanged.emit(null)"
(menuClosed)="setMode(CustomColumnMode.Standard)"
[style.pointer-events]="disabled ? 'none' : 'initial'"
>
<div *ngIf="!customColumnMode" (click)="$event.stopPropagation()">
@@ -22,13 +22,13 @@
<div class="add-button metrics-button"
smClickStopPropagation
[ngClass]="{disabled: !metricVariants.length}"
(click)="metricVariants.length && selectMetricActiveChanged.emit(CustomColumnMode.Metrics); $event.stopPropagation()"
(click)="$event.stopPropagation(); metricVariants.length && setMode(CustomColumnMode.Metrics)"
><i class="al-icon al-ico-add sm mr-1"></i><span class="caption">METRIC</span>
</div>
<div class="add-button metrics-button"
smClickStopPropagation
[ngClass]="{disabled: !hasHyperParams}"
(click)="hasHyperParams && selectMetricActiveChanged.emit(CustomColumnMode.HyperParams); $event.stopPropagation()"
(click)="$event.stopPropagation(); hasHyperParams && setMode(CustomColumnMode.HyperParams)"
><i class="al-icon al-ico-add sm mr-1"></i><span class="caption">HYPER PARAMETERS</span>
</div>
</div>
@@ -37,13 +37,13 @@
<sm-select-metric-for-custom-col *ngIf="customColumnMode === CustomColumnMode.Metrics"
[tableCols]="tableCols"
[metricVariants]="metricVariants"
(goBack)="selectMetricActiveChanged.emit(null)"
(goBack)="setMode(CustomColumnMode.Standard)"
(selectedMetricToShow)="selectedMetricToShow.emit($event)">
</sm-select-metric-for-custom-col>
<sm-select-hyper-params-for-custom-col *ngIf="customColumnMode === CustomColumnMode.HyperParams"
[tableCols]="tableCols"
[hyperParams]="hyperParams"
(goBack)="selectMetricActiveChanged.emit(null)"
(goBack)="setMode(CustomColumnMode.Standard)"
(selectedHyperParamToShow)="selectedHyperParamToShow.emit($event)"
(clearSelection)="clearSelection.emit()">
</sm-select-hyper-params-for-custom-col>

View File

@@ -11,13 +11,10 @@ import {MetricValueType} from '@common/experiments-compare/reducers/experiments-
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ExperimentCustomColsMenuComponent {
public hasHyperParams: boolean;
private _hyperParams: { [section: string]: any[] };
@Input() metricVariants;
@Input() customColumnMode: CustomColumnMode;
@Input() tableCols;
@Input() disabled: boolean;
@@ -31,7 +28,6 @@ export class ExperimentCustomColsMenuComponent {
@Input() isLoading: boolean;
@Output() selectMetricActiveChanged = new EventEmitter<CustomColumnMode>();
@Output() getMetricsToDisplay = new EventEmitter();
@Output() removeColFromList = new EventEmitter<ISmCol['id']>();
@Output() selectedTableColsChanged = new EventEmitter<ISmCol>();
@@ -43,5 +39,10 @@ export class ExperimentCustomColsMenuComponent {
@Output() selectedHyperParamToShow = new EventEmitter<{param: string; addCol: boolean}>();
@Output() clearSelection = new EventEmitter();
customColumnMode = CustomColumnMode.Standard as CustomColumnMode;
public CustomColumnMode = CustomColumnMode;
setMode(mode: CustomColumnMode) {
this.customColumnMode = mode;
}
}

View File

@@ -23,7 +23,7 @@
<mat-error *ngIf="parameterKey.invalid && parameterKey?.errors?.smNotAllowedStringsValidator">
.(dot) $(dollar) and space are not allowed in parameter key.
</mat-error>
<mat-error *ngIf="parameterKey.invalid && parameterKey?.errors?.uniqueName">
<mat-error *ngIf="!parameterKey?.errors?.required && parameterKey.invalid && parameterKey?.errors?.uniqueName">
key already exists
</mat-error>
</mat-form-field>

View File

@@ -1,12 +1,21 @@
<div class="d-flex justify-content-between header-container align-items-center"
[ngClass]="{'archive-mode': isArchived}">
<div class="d-flex-center">
<ng-content></ng-content>
<ng-container *ngTemplateOutlet="addButtonTemplate; context: {smallScreen: (isSmallScreen$ | async).matches}">
</ng-container>
<sm-toggle-archive
[class.hide-item]="sharedView"
[showArchived]="isArchived"
[minimize]="(isSmallScreen$ | async).matches"
(toggleArchived)="onIsArchivedChanged($event)"
></sm-toggle-archive>
<sm-button-toggle
[disabled]="!tableMode"
class="ml-3"
[options]="[{label: 'Table view', value: 'table', icon: 'al-ico-table-view'}, {label: 'Details view', value: 'info', icon: 'al-ico-experiment-view', ripple: true}]"
[value]="tableMode"
[rippleEffect]="rippleEffect"
(valueChanged)="tableModeChanged.emit($event)"></sm-button-toggle>
</div>
<sm-project-context-navbar
*ngIf="showNavbarLinks"
@@ -26,20 +35,16 @@
[metricVariants]="metricVariants"
[hyperParams]="hyperParams"
[tableCols]="tableCols"
[customColumnMode]="selectMetricActive"
[isLoading]="isMetricsLoading"
(selectedMetricToShow)="selectedMetricToShow.emit($event)"
(selectedHyperParamToShow)="selectedHyperParamToShow.emit($event)"
(selectedTableColsChanged)="selectedTableColsChanged.emit($event)"
(getMetricsToDisplay)="getMetricsToDisplay.emit($event)"
(removeColFromList)="removeColFromList.emit($event)"
(selectMetricActiveChanged)="setCustomColumnMode($event)"
(clearSelection)="clearSelection.emit()"
></sm-experiment-custom-cols-menu>
<sm-refresh-button
[autoRefreshState]="autoRefreshState"
[allowAutoRefresh]="true"
(refreshList)="refreshListClicked.emit($event)"
(setAutoRefresh)="setAutoRefresh.emit($event)"
>
</sm-refresh-button>

View File

@@ -33,10 +33,6 @@
}
}
.btn-primary {
border-color: $blue-600;
}
sm-experiment-custom-cols-menu {
margin-right: 25px;
}

View File

@@ -1,29 +1,29 @@
import {Component, EventEmitter, Input, Output} from '@angular/core';
import {Component, EventEmitter, Input, Output, TemplateRef} from '@angular/core';
import {MetricVariantResult} from '~/business-logic/model/projects/metricVariantResult';
import {CustomColumnMode} from '../../shared/common-experiments.const';
import {ISmCol} from '@common/shared/ui-components/data/table/table.consts';
import {MetricValueType} from '@common/experiments-compare/reducers/experiments-compare-charts.reducer';
import {FilterMetadata} from 'primeng/api/filtermetadata';
import {BaseEntityHeaderComponent} from '@common/shared/entity-page/base-entity-header/base-entity-header.component';
@Component({
selector : 'sm-experiment-header',
templateUrl: './experiment-header.component.html',
styleUrls : ['./experiment-header.component.scss']
})
export class ExperimentHeaderComponent {
public selectMetricActive: CustomColumnMode;
export class ExperimentHeaderComponent extends BaseEntityHeaderComponent {
private _tableCols: any;
@Input() isArchived: boolean;
@Input() metricVariants: Array<MetricVariantResult>;
@Input() hyperParams: { [section: string]: any[] };
@Input() minimizedView: boolean;
@Input() isMetricsLoading: boolean;
@Input() autoRefreshState: boolean;
@Input() tableFilters: { [s: string]: FilterMetadata };
@Input() sharedView: boolean;
@Input() showNavbarLinks: boolean;
@Input() tableMode: string;
@Input() rippleEffect: boolean;
@Input() addButtonTemplate: TemplateRef<any>;
@Input() set tableCols(tableCols) {
this._tableCols = tableCols.filter(col => col.header !== '');
@@ -43,25 +43,13 @@ export class ExperimentHeaderComponent {
valueType: MetricValueType;
}>();
@Output() selectedHyperParamToShow = new EventEmitter<{param: string; addCol: boolean}>();
@Output() refreshListClicked = new EventEmitter<boolean>();
@Output() setAutoRefresh = new EventEmitter<boolean>();
@Output() clearSelection = new EventEmitter();
@Output() clearTableFilters = new EventEmitter<{ [s: string]: FilterMetadata }>();
@Output() setAutoRefresh = new EventEmitter<boolean>();
@Output() clearSelection = new EventEmitter();
@Output() clearTableFilters = new EventEmitter<{ [s: string]: FilterMetadata }>();
@Output() tableModeChanged = new EventEmitter<'table' | 'info'>();
onIsArchivedChanged(value: boolean) {
this.isArchivedChanged.emit(value);
}
onRefreshListClicked() {
this.refreshListClicked.emit(false);
}
setCustomColumnMode(mode: CustomColumnMode) {
this.selectMetricActive = mode;
}
newRun() {
}
}

View File

@@ -8,7 +8,6 @@
class="edit-name"
[originalText]="infoData?.name || experiment?.name"
[editable]="editable"
[minWidth]="250"
(textChanged)="onNameChanged($event)"
(inlineActiveStateChanged)="editExperimentName($event)"
[warning]="isDev && 'Renaming a DEV experiment without changing the code to reflect the rename, will create a new experiment the next time the code is executed.'"

View File

@@ -9,4 +9,4 @@
</div>
</cdk-virtual-scroll-viewport>
<mat-spinner *ngIf="fetching" [diameter]="80" [strokeWidth]="8"></mat-spinner>
<button *ngIf="!fetching" class="btn btn-primary get-last" [class.at-end]="canRefresh" (click)="getLast()">Jump to end</button>
<button *ngIf="!fetching" class="btn btn-cml-primary get-last" [class.at-end]="canRefresh" (click)="getLast()">Jump to end</button>

View File

@@ -175,20 +175,28 @@ export class ExperimentLogInfoComponent implements OnDestroy, AfterViewInit {
calcLines() {
this.lines = [];
this.orgLogs.filter((row) => !this.hasFilter || this.regex.test(row.msg))
this.orgLogs
.filter((row) => !this.hasFilter || this.regex.test(row?.msg ?? ''))
.forEach(logItem => {
let first = true;
logItem.msg.split('\n').filter(msg => !!msg).forEach((msg: string) => {
const hasAnsi = this.hasAnsi(msg);
const converted = msg ? (hasAnsi ? this.convert.toHtml(msg) :
msg) : '';
if (first) {
this.lines.push({timestamp: logItem['timestamp'] || logItem['@timestamp'], entry: converted, hasAnsi: hasAnsi});
first = false;
} else {
this.lines.push({entry: converted, hasAnsi: hasAnsi});
}
});
if (!logItem.msg) {
this.lines.push({timestamp: logItem['timestamp'] || logItem['@timestamp'], entry: '', hasAnsi: false, separator: true});
return;
}
logItem.msg
.split('\n')
.filter(msg => !!msg)
.forEach((msg: string) => {
const hasAnsi = this.hasAnsi(msg);
const converted = msg ? (hasAnsi ? this.convert.toHtml(msg) :
msg) : '';
if (first) {
this.lines.push({timestamp: logItem['timestamp'] || logItem['@timestamp'], entry: converted, hasAnsi: hasAnsi});
first = false;
} else {
this.lines.push({entry: converted, hasAnsi: hasAnsi});
}
});
this.lines[this.lines.length - 1].separator = true;
});
}

View File

@@ -1,4 +1,3 @@
<script src="../../../models/shared/models-table/models-table.component.ts"></script>
<ng-container *ngTemplateOutlet="contextMenuTemplate; context: { $implicit: contextExperiment}"></ng-container>
<div class="table-container" [class.card-view]="minimizedView" #tableContainer>
<sm-table
@@ -11,7 +10,7 @@
[columns]="tableCols"
[lazyLoading]="true"
[minimizedView]="minimizedView"
minimizedTableHeader="{{entityType.replace('pipeline ', '')}}S LIST"
minimizedTableHeader="{{entityType.replace('ripeline ', '')}}S LIST"
[noMoreData]="noMoreExperiments"
[selection]="selectedExperiment"
[activeContextRow]="contextExperiment"
@@ -19,9 +18,10 @@
[checkedItems]="selectedExperiments"
[keyboardControl]="true"
[sortFields]="tableSortFields"
(rowSelectionChanged)="onRowSelectionChanged($event)"
(rowSelectionChanged)="experimentSelectionChanged.emit($any($event).data)"
(rowClicked)="tableRowClicked($event)"
(loadMoreClicked)="onLoadMoreClicked()"
(onRowRightClick)="onContextMenu($event)"
(rowRightClick)="onContextMenu($event)"
(colReordered)="columnsReordered.emit($event)"
(sortChanged)="onSortChanged($event.isShift, $event.field)"
(columnResized)="columnResized.emit($event)"
@@ -42,7 +42,7 @@
</ng-template>
<ng-template let-col pTemplate="checkbox">
<div class="d-flex">
<div class="d-flex align-items-center">
<sm-checkbox-control
*ngIf="col.headerType === colHeaderTypeEnum.checkBox"
class="checkbox-col header"
@@ -55,7 +55,7 @@
<div class="al-icon al-ico-dropdown-arrow sm drop-down" [matMenuTriggerFor]="selectionMenu"></div>
</div>
<mat-menu class="light-theme" #selectionMenu="matMenu">
<div class="menu-title">Select from project</div>
<div *ngIf="entityType !== entityTypes.controller" class="menu-title">Select from project</div>
<button mat-menu-item (click)="selectAll()">All</button>
<button mat-menu-item (click)="emitSelection([])">None</button>
<button mat-menu-item (click)="selectAll(true)">All matching filter</button>
@@ -97,6 +97,7 @@
class="experiment-name"
[class.italic]="isDevelopment(experiment)"
matTooltipPosition="above"
smShowTooltipIfEllipsis
[smTooltip]="experiment.name">
{{experiment.name}}
</div>
@@ -107,11 +108,14 @@
<sm-tag-list [tags]="experiment.tags" [sysTags]="getSystemTags(experiment)"></sm-tag-list>
</ng-container>
<ng-container *ngSwitchCase="EXPERIMENTS_TABLE_COL_FIELDS.USER">
<span class="ellipsis">{{experiment.user?.name ? experiment.user?.name : 'Unknown User'}}</span>
<span class="ellipsis" matTooltipPosition="above"
smShowTooltipIfEllipsis
[smTooltip]="experiment.user?.name ? experiment.user?.name : 'Unknown User'">{{experiment.user?.name ? experiment.user?.name : 'Unknown User'}}</span>
</ng-container>
<span *ngSwitchCase="EXPERIMENTS_TABLE_COL_FIELDS.PROJECT"
class="ellipsis"
matTooltipPosition="above"
smShowTooltipIfEllipsis
[smTooltip]="experiment.project?.name">
{{experiment.project?.name}}
</span>
@@ -129,7 +133,7 @@
<span class="ellipsis">{{experiment.active_duration | duration}}</span>
</ng-container>
<ng-container *ngSwitchCase="EXPERIMENTS_TABLE_COL_FIELDS.COMMENT">
<span class="ellipsis" matTooltipPosition="above" [smTooltip]="experiment.comment">{{experiment.comment}}</span>
<span class="ellipsis" matTooltipPosition="above" smShowTooltipIfEllipsis [smTooltip]="experiment.comment">{{experiment.comment}}</span>
</ng-container>
<ng-container *ngSwitchCase="EXPERIMENTS_TABLE_COL_FIELDS.SELECTED">
<sm-checkbox-control
@@ -150,6 +154,7 @@
class="parent-name"
*ngIf="experiment?.parent?.id"
matTooltipPosition="above"
smShowTooltipIfEllipsis
[smTooltip]="experiment.parent.project?.name? experiment.parent.project.name + ' / ' + experiment.parent.name : experiment.parent.name"
>{{experiment.parent.name}}</span>
</ng-container>
@@ -165,14 +170,17 @@
</ng-template>
<ng-template let-experiment="rowData" let-selected="selected" let-rowNumber="rowNumber" pTemplate="card">
<sm-table-card class="flex-grow-1" [selected]="selected"
[cardName]="experiment.name"
[columns]="tableCols"
[rowData]="experiment"
[checked]="isRowSelected(experiment)"
[activeContextRow]="contextExperiment"
[contextMenuOpen]="contextMenuActive"
[entityType]="entityType"
<sm-table-card
class="flex-grow-1"
[selected]="selected"
[cardName]="experiment.name"
[columns]="tableCols"
[rowData]="experiment"
[checked]="isRowSelected(experiment)"
[activeContextRow]="contextExperiment"
[contextMenuOpen]="contextMenuActive"
[entityType]="entityType"
(click)="selected && onContextMenu({e: $event, rowData: experiment, backdrop: true})"
>
<div sm-name-icon *ngIf="experiment?.system_tags.includes('shared')"><i class="al-icon al-ico-link sm-md ml-2"></i></div>
<div sm-name-version *ngIf="experiment?.hyperparams?.properties?.version?.value" class="mr-5">

View File

@@ -41,10 +41,15 @@
.checkbox-col {
display: block;
float: left;
padding-left: 18px;
padding: 13px 0 13px 26px;
margin-left: -8px;
&:not(.header) {
padding-right: 24px;
}
&.header.minimised {
padding-left: 16px;
padding-left: 24px;
}
}

View File

@@ -5,7 +5,8 @@ import {
EventEmitter,
Input,
OnDestroy,
Output, TemplateRef,
Output,
TemplateRef,
} from '@angular/core';
import {ICONS, TIME_FORMAT_STRING} from '@common/constants';
import {ColHeaderTypeEnum, ISmCol} from '@common/shared/ui-components/data/table/table.consts';
@@ -22,15 +23,12 @@ import {Store} from '@ngrx/store';
import {NoUnderscorePipe} from '@common/shared/pipes/no-underscore.pipe';
import {TitleCasePipe} from '@angular/common';
import {INITIAL_EXPERIMENT_TABLE_COLS} from '../../experiment.consts';
import {
ProjectsGetTaskParentsResponseParents
} from '~/business-logic/model/projects/projectsGetTaskParentsResponseParents';
import {ProjectsGetTaskParentsResponseParents} from '~/business-logic/model/projects/projectsGetTaskParentsResponseParents';
import {Router} from '@angular/router';
import {IOption} from '@common/shared/ui-components/inputs/select-autocomplete-for-template-forms/select-autocomplete-for-template-forms.component';
import {createFiltersFromStore} from '../../effects/common-experiments-view.effects';
import {CountAvailableAndIsDisableSelectedFiltered} from '@common/shared/entity-page/items.utils';
import {hyperParamSelectedExperiments, selectAllExperiments} from '../../actions/common-experiments-view.actions';
import {excludedKey, uniqueFilterValueAndExcluded} from '@common/shared/utils/tableParamEncode';
import {createFiltersFromStore, excludedKey, uniqueFilterValueAndExcluded} from '@common/shared/utils/tableParamEncode';
import {getRoundedNumber} from '../../shared/common-experiments.utils';
@Component({
@@ -56,7 +54,6 @@ export class ExperimentsTableComponent extends BaseTableView implements OnDestro
private _selectedExperiments: ITableExperiment[] = [];
readonly colHeaderTypeEnum = ColHeaderTypeEnum;
@Input() initialColumns = INITIAL_EXPERIMENT_TABLE_COLS;
@Input() disableContextMenu = false;
@Input() contextMenuTemplate: TemplateRef<any> = null;
@Input() tableCols: ISmCol[];
@@ -94,7 +91,7 @@ export class ExperimentsTableComponent extends BaseTableView implements OnDestro
label: user.name ? user.name : 'Unknown User',
value: user.id
}));
this.sortOptionalUsersList();
this.sortOptionsList(EXPERIMENTS_TABLE_COL_FIELDS.USER);
}
@Input() set hyperParamsOptions(hyperParamsOptions: Record<ISmCol['id'], string[]>) {
@@ -109,7 +106,6 @@ export class ExperimentsTableComponent extends BaseTableView implements OnDestro
@Input() activeParentsFilter: ProjectsGetTaskParentsResponseParents[];
@Input() set parents(parents: ProjectsGetTaskParentsResponseParents[]) {
const parentsAndActiveFilter = Array.from(new Set(parents.concat(this.activeParentsFilter || [])));
this.filtersOptions[EXPERIMENTS_TABLE_COL_FIELDS.PARENT] = parentsAndActiveFilter.map(parent => ({
@@ -117,7 +113,7 @@ export class ExperimentsTableComponent extends BaseTableView implements OnDestro
value: parent.id,
tooltip: `${parent.project?.name} / ${parent.name}`
}));
this.sortOptionalParentsList();
this.sortOptionsList(EXPERIMENTS_TABLE_COL_FIELDS.PARENT);
}
@Input() set selectedExperiments(experiments: ITableExperiment[]) {
@@ -148,7 +144,7 @@ export class ExperimentsTableComponent extends BaseTableView implements OnDestro
label: tag === null ? '(No tags)' : tag,
value: tag
}) as IOption);
this.sortOptionalTagsList();
this.sortOptionalTagsList()
}
@Input() set experimentTypes(types: string[]) {
@@ -162,6 +158,15 @@ export class ExperimentsTableComponent extends BaseTableView implements OnDestro
);
}
@Input() set projects(projects) {
this.filtersOptions[EXPERIMENTS_TABLE_COL_FIELDS.PROJECT] = projects.map(project => ({
label: project.name,
value: project.id,
tooltip: `${project.name}`
}));
this.sortOptionsList(EXPERIMENTS_TABLE_COL_FIELDS.PROJECT);
}
@Input() systemTags = [] as string[];
@Input() reorderableColumns: boolean = true;
@Input() selectionReachedLimit: boolean;
@@ -183,20 +188,12 @@ export class ExperimentsTableComponent extends BaseTableView implements OnDestro
this.filtersValues[EXPERIMENTS_TABLE_COL_FIELDS.VERSION] = get([EXPERIMENTS_TABLE_COL_FIELDS.VERSION, 'value'], filters) || [];
this.filtersValues[EXPERIMENTS_TABLE_COL_FIELDS.LAST_UPDATE] = get([EXPERIMENTS_TABLE_COL_FIELDS.LAST_UPDATE, 'value'], filters) || [];
this.filtersValues[EXPERIMENTS_TABLE_COL_FIELDS.STARTED] = get([EXPERIMENTS_TABLE_COL_FIELDS.STARTED, 'value'], filters) || [];
this.sortOptionalProjectsList();
// handle dynamic filters;
const filtersValues = createFiltersFromStore(filters || {}, false);
this.filtersValues = Object.assign({}, {...this.filtersValues}, {...filtersValues});
}
@Input() set projects(projects) {
this.filtersOptions[EXPERIMENTS_TABLE_COL_FIELDS.PROJECT] = projects.map(project => ({
label: project.name,
value: project.id,
tooltip: `${project.name}`
}));
this.sortOptionalProjectsList();
}
@Output() experimentSelectionChanged = new EventEmitter<ITableExperiment>();
@Output() experimentsSelectionChanged = new EventEmitter<Array<ITableExperiment>>();
@@ -205,8 +202,8 @@ export class ExperimentsTableComponent extends BaseTableView implements OnDestro
@Output() tagsMenuOpened = new EventEmitter();
@Output() typesMenuOpened = new EventEmitter();
@Output() columnResized = new EventEmitter<{ columnId: string; widthPx: number }>();
@Output() openContextMenu = new EventEmitter<{ x: number; y: number }>();
@Output() removeTag = new EventEmitter<{experiment: ITableExperiment; tag: string}>();
@Output() openContextMenu = new EventEmitter<{ x: number; y: number; single?: boolean; backdrop?: boolean }>();
@Output() removeTag = new EventEmitter<{ experiment: ITableExperiment; tag: string }>();
TIME_FORMAT_STRING = TIME_FORMAT_STRING;
constructor(
@@ -225,25 +222,6 @@ export class ExperimentsTableComponent extends BaseTableView implements OnDestro
super.ngOnDestroy();
}
onRowSelectionChanged(event) {
this.experimentSelectionChanged.emit(event.data);
}
sortOptionalUsersList() {
this.filtersOptions[EXPERIMENTS_TABLE_COL_FIELDS.USER]
.sort((a, b) => sortByArr(a.value, b.value, this.filtersValues[EXPERIMENTS_TABLE_COL_FIELDS.USER]));
}
sortOptionalParentsList() {
this.filtersOptions[EXPERIMENTS_TABLE_COL_FIELDS.PARENT]
.sort((a, b) => sortByArr(a.value, b.value, this.filtersValues[EXPERIMENTS_TABLE_COL_FIELDS.PARENT]));
}
sortOptionalProjectsList() {
this.filtersOptions[EXPERIMENTS_TABLE_COL_FIELDS.PROJECT]
.sort((a, b) => sortByArr(a.value, b.value, this.filtersValues[EXPERIMENTS_TABLE_COL_FIELDS.PROJECT]));
}
sortOptionalTagsList() {
const selectedTags = (this.filtersValues[EXPERIMENTS_TABLE_COL_FIELDS.TAGS] || [])
.map(tag => typeof tag === 'string' ? tag.replace(excludedKey, '') : tag);
@@ -277,26 +255,32 @@ export class ExperimentsTableComponent extends BaseTableView implements OnDestro
}
}
tableRowClicked(event: { e: MouseEvent; data: ITableExperiment }) {
if (this._selectedExperiments.some(exp => exp.id === event.data.id)) {
this.onContextMenu({e: event.e, rowData: event.data, backdrop: true});
} else {
this.experimentsSelectionChanged.emit([event.data]);
}
}
emitSelection(selection: any[]) {
this.experimentsSelectionChanged.emit(selection);
}
searchValueChanged($event: string, colId) {
this.searchValues[colId] = $event;
if (colId === EXPERIMENTS_TABLE_COL_FIELDS.TAGS) {
this.sortOptionalTagsList();
}
}
onContextMenu(data) {
this.contextExperiment = this._experiments.find(experiment => experiment.id === data.rowData.id);
if (!this.selectedExperiments.map(exp => exp.id).includes(this.contextExperiment.id)) {
this.prevSelected = this.contextExperiment;
this.emitSelection([this.contextExperiment]);
onContextMenu(data: { e: MouseEvent, rowData; single?: boolean; backdrop?: boolean }) {
if (!data?.single) {
this.contextExperiment = this._experiments.find(experiment => experiment.id === data.rowData.id);
if (!this.selectedExperiments.map(exp => exp.id).includes(this.contextExperiment.id)) {
this.prevSelected = this.contextExperiment;
this.emitSelection([this.contextExperiment]);
}
} else {
this.contextExperiment = data.rowData;
}
const event = data.e as MouseEvent;
event.preventDefault();
this.openContextMenu.emit({x: event.clientX, y: event.clientY});
this.openContextMenu.emit({x: event.clientX, y: event.clientY, single: data?.single, backdrop: data?.backdrop});
}
@@ -322,19 +306,6 @@ export class ExperimentsTableComponent extends BaseTableView implements OnDestro
}
}
columnFilterClosed(col: ISmCol) {
switch (col.id) {
case EXPERIMENTS_TABLE_COL_FIELDS.TAGS:
this.sortOptionalTagsList();
break;
case EXPERIMENTS_TABLE_COL_FIELDS.USER:
this.sortOptionalUsersList();
break;
case EXPERIMENTS_TABLE_COL_FIELDS.PROJECT:
this.sortOptionalProjectsList();
break;
}
}
selectAll(filtered?: boolean) {
this.store.dispatch(selectAllExperiments({filtered}));

View File

@@ -18,22 +18,22 @@ import {
resetDisplayer,
setDebugImageViewerScrollId,
setDisplayerEndOfTime
} from '../../../debug-images/debug-images-actions';
} from '@common/debug-images/debug-images-actions';
import {
selectCurrentImageViewerDebugImage,
selectDisplayerBeginningOfTime,
selectDisplayerEndOfTime,
selectMinMaxIterations
} from '../../../debug-images/debug-images-reducer';
} from '@common/debug-images/debug-images-reducer';
import {interval, Observable, Subscription} from 'rxjs';
import {EventsGetDebugImageIterationsResponse} from '../../../../business-logic/model/events/eventsGetDebugImageIterationsResponse';
import {EventsGetDebugImageIterationsResponse} from '~/business-logic/model/events/eventsGetDebugImageIterationsResponse';
import {filter, map, switchMap, tap, withLatestFrom} from 'rxjs/operators';
import {selectAppVisible, selectAutoRefresh} from '../../../core/reducers/view.reducer';
import {isFileserverUrl} from '../../../../shared/utils/url';
import {getSignedUrl} from '../../../core/actions/common-auth.actions';
import {getSignedUrlOrOrigin$} from '../../../core/reducers/common-auth-reducer';
import {IsVideoPipe} from '../../../shared/pipes/is-video.pipe';
import {IsAudioPipe} from '../../../shared/pipes/is-audio.pipe';
import {selectAppVisible, selectAutoRefresh} from '@common/core/reducers/view.reducer';
import {isFileserverUrl} from '~/shared/utils/url';
import {getSignedUrl} from '@common/core/actions/common-auth.actions';
import {getSignedUrlOrOrigin$} from '@common/core/reducers/common-auth-reducer';
import {IsVideoPipe} from '@common/shared/pipes/is-video.pipe';
import {IsAudioPipe} from '@common/shared/pipes/is-audio.pipe';
const DISPLAYER_AUTO_REFRESH_INTERVAL = 60 * 1000;
@@ -292,7 +292,7 @@ export class ImageDisplayerComponent implements OnInit, OnDestroy {
this.currentDebugImageSubscription.unsubscribe();
this.begOfTimeSub.unsubscribe();
this.endOfTimeSub.unsubscribe();
this.autoRefreshSub.unsubscribe();
this.autoRefreshSub?.unsubscribe();
}
showImage() {

View File

@@ -10,5 +10,4 @@
[selectedItemsList]="metricsCols"
(selectedItems)="toggleParamToDisplay($event)"
(clearSelection)="clearSelection.emit()"
>
</sm-grouped-checked-filter-list>
></sm-grouped-checked-filter-list>

View File

@@ -1,13 +1,15 @@
@import "../../../shared/ui-components/styles/variables";
:host {
display: block;
height: 640px;
max-height: calc(100vh - 120px);
width: 370px;
min-height: 470px;
}
sm-grouped-checked-filter-list {
padding: 15px 15px 0 15px;
max-height: 419px;
height: calc(100% - 52px);
padding: 15px 0 0 15px;
::ng-deep .actions {
padding-top: 10px;
}

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