mirror of
https://github.com/clearml/clearml-web
synced 2025-06-26 18:27:02 +00:00
Release v1.5 (#27)
This commit is contained in:
4475
package-lock.json
generated
4475
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ClearML-webapp",
|
||||
"version": "1.4.0",
|
||||
"version": "1.5.0",
|
||||
"license": "",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
@@ -48,7 +48,7 @@
|
||||
"bootstrap": "^4.6.1",
|
||||
"britecharts": "^2.18.0",
|
||||
"curved-arrows": "^0.1.0",
|
||||
"d3-selection": "^1.4.2",
|
||||
"d3-selection": "^3.0.0",
|
||||
"diff": "^5.0.0",
|
||||
"filesize": "^8.0.7",
|
||||
"has-ansi": "^5.0.1",
|
||||
@@ -87,7 +87,7 @@
|
||||
"@fortawesome/fontawesome-free": "^6.0.0",
|
||||
"@ngrx/schematics": "^13.0.2",
|
||||
"@ngrx/store-devtools": "^13.0.2",
|
||||
"@types/d3-selection": "^1.4.3",
|
||||
"@types/d3-selection": "^3.0.2",
|
||||
"@types/lodash": "^4.14.178",
|
||||
"@types/node": "^16.11.19",
|
||||
"@types/plotly.js": "^1.54.20",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* auth
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* OpenAPI spec version: 2.14
|
||||
* OpenAPI spec version: 2.18
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
@@ -22,6 +22,7 @@ import { CustomHttpUrlEncodingCodec } from '../encoder';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { AuthCreateCredentialsRequest } from '../model/auth/authCreateCredentialsRequest';
|
||||
import { AuthCreateCredentialsResponse } from '../model/auth/authCreateCredentialsResponse';
|
||||
import { AuthCreateUserRequest } from '../model/auth/authCreateUserRequest';
|
||||
import { AuthCreateUserResponse } from '../model/auth/authCreateUserResponse';
|
||||
@@ -40,6 +41,8 @@ import { AuthValidateTokenResponse } from '../model/auth/authValidateTokenRespon
|
||||
|
||||
import { BASE_PATH, COLLECTION_FORMATS } from '../variables';
|
||||
import { Configuration } from '../configuration';
|
||||
import {AuthEditCredentialsRequest} from '~/business-logic/model/auth/authEditCredentialsRequest';
|
||||
import {AuthEditCredentialsResponse} from '~/business-logic/model/auth/authEditCredentialsResponse';
|
||||
|
||||
|
||||
@Injectable()
|
||||
@@ -75,13 +78,13 @@ export class ApiAuthService {
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Internal. Creates a new set of credentials for the authenticated user. New key/secret is returned. Note: Secret will never be returned in any other API call. If a secret is lost or compromised, the key should be revoked and a new set of credentials can be created.
|
||||
* @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 authCreateCredentials(request: object, options?: any, observe: any = 'body', reportProgress: boolean = false ): Observable<any> {
|
||||
public authCreateCredentials(request: AuthCreateCredentialsRequest, 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 authCreateCredentials.');
|
||||
}
|
||||
@@ -120,7 +123,7 @@ export class ApiAuthService {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Internal. Creates a new user auth entry. Intended for internal use.
|
||||
* @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.
|
||||
@@ -165,7 +168,7 @@ export class ApiAuthService {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Internal. Edit a users\' auth data properties
|
||||
* @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.
|
||||
@@ -210,7 +213,7 @@ export class ApiAuthService {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Internal. Return fixed users mode status
|
||||
* @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.
|
||||
@@ -255,7 +258,7 @@ export class ApiAuthService {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Internal. Returns all existing credential keys for the authenticated user. Note: Only credential keys are 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.
|
||||
@@ -299,11 +302,56 @@ export class ApiAuthService {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Internal. Get a token for the specified user. Intended for internal use.
|
||||
* @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.
|
||||
/**
|
||||
*
|
||||
* Internal. Updates the label of the existing credentials for the authenticated user.
|
||||
* @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 authEditCredentials(request: AuthEditCredentialsRequest, 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 authEditCredentials.');
|
||||
}
|
||||
|
||||
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<AuthEditCredentialsResponse>(`${this.basePath}/auth.edit_credentials`,
|
||||
request,
|
||||
{
|
||||
withCredentials: this.configuration.withCredentials,
|
||||
headers: headers,
|
||||
observe: observe,
|
||||
reportProgress: reportProgress
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Internal. Get a token for the specified user. Intended for internal use.
|
||||
* @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 authGetTokenForUser(request: AuthGetTokenForUserRequest, options?: any, observe: any = 'body', reportProgress: boolean = false ): Observable<any> {
|
||||
@@ -345,7 +393,7 @@ export class ApiAuthService {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Internal. Get a token based on supplied credentials (key/secret). Intended for use by users with key/secret credentials that wish to obtain a token for use with other services.
|
||||
* @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.
|
||||
@@ -390,7 +438,7 @@ export class ApiAuthService {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Internal. Removes the authentication cookie from the current session
|
||||
* @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.
|
||||
@@ -435,7 +483,7 @@ export class ApiAuthService {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Internal. Revokes (and deletes) a set (key, secret) of credentials for the authenticated user.
|
||||
* @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.
|
||||
@@ -480,7 +528,7 @@ export class ApiAuthService {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Internal. Validate a token and return user identity if valid. Intended for internal use.
|
||||
* @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.
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* auth
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* OpenAPI spec version: 2.18
|
||||
*
|
||||
*
|
||||
* 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 AuthCreateCredentialsRequest {
|
||||
/**
|
||||
* Optional credentials label
|
||||
*/
|
||||
label?: string;
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
* auth
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* OpenAPI spec version: 2.14
|
||||
* OpenAPI spec version: 2.18
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* auth
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* OpenAPI spec version: 2.14
|
||||
* OpenAPI spec version: 2.18
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* auth
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* OpenAPI spec version: 2.14
|
||||
* OpenAPI spec version: 2.18
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* auth
|
||||
* 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 AuthEditCredentialsRequest {
|
||||
/**
|
||||
* Existing credentials key
|
||||
*/
|
||||
access_key: string;
|
||||
/**
|
||||
* New credentials label
|
||||
*/
|
||||
label?: string;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* auth
|
||||
* 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 AuthEditCredentialsResponse {
|
||||
/**
|
||||
* Number of credentials updated
|
||||
*/
|
||||
updated?: number;
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
* auth
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* OpenAPI spec version: 2.14
|
||||
* OpenAPI spec version: 2.18
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* auth
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* OpenAPI spec version: 2.14
|
||||
* OpenAPI spec version: 2.18
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* auth
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* OpenAPI spec version: 2.14
|
||||
* OpenAPI spec version: 2.18
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* auth
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* OpenAPI spec version: 2.14
|
||||
* OpenAPI spec version: 2.18
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* auth
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* OpenAPI spec version: 2.14
|
||||
* OpenAPI spec version: 2.18
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* auth
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* OpenAPI spec version: 2.14
|
||||
* OpenAPI spec version: 2.18
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* auth
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* OpenAPI spec version: 2.14
|
||||
* OpenAPI spec version: 2.18
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* auth
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* OpenAPI spec version: 2.14
|
||||
* OpenAPI spec version: 2.18
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* auth
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* OpenAPI spec version: 2.14
|
||||
* OpenAPI spec version: 2.18
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* auth
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* OpenAPI spec version: 2.14
|
||||
* OpenAPI spec version: 2.18
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* auth
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* OpenAPI spec version: 2.14
|
||||
* OpenAPI spec version: 2.18
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* auth
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* OpenAPI spec version: 2.14
|
||||
* OpenAPI spec version: 2.18
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* auth
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* OpenAPI spec version: 2.14
|
||||
* OpenAPI spec version: 2.18
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
@@ -14,16 +14,19 @@
|
||||
|
||||
export interface CredentialKey {
|
||||
/**
|
||||
*
|
||||
*
|
||||
*/
|
||||
access_key?: string;
|
||||
/**
|
||||
*
|
||||
* Optional credentials label
|
||||
*/
|
||||
label?: string;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
last_used?: string;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
last_used_from?: string;
|
||||
label?: string; // (nir) until BE will implement
|
||||
/**
|
||||
*
|
||||
*/
|
||||
last_used_from?: string;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* auth
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* OpenAPI spec version: 2.14
|
||||
* OpenAPI spec version: 2.18
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
@@ -21,4 +21,8 @@ export interface Credentials {
|
||||
* Credentials secret key
|
||||
*/
|
||||
secret_key?: string;
|
||||
/**
|
||||
* Optional credentials label
|
||||
*/
|
||||
label?: string;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from '././authCreateCredentialsRequest';
|
||||
export * from '././authCreateCredentialsResponse';
|
||||
export * from '././authCreateUserRequest';
|
||||
export * from '././authCreateUserResponse';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* auth
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* OpenAPI spec version: 2.14
|
||||
* OpenAPI spec version: 2.18
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
|
||||
@@ -2,12 +2,12 @@ import { Injectable } from '@angular/core';
|
||||
import {Actions, createEffect, ofType} from '@ngrx/effects';
|
||||
import * as actions from '../../webapp-common/core/actions/projects.actions';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {selectSelectedProjectId} from '../../webapp-common/core/reducers/projects.reducer';
|
||||
import {selectSelectedProjectId} from '@common/core/reducers/projects.reducer';
|
||||
import {catchError, finalize, mergeMap, switchMap, withLatestFrom} from 'rxjs/operators';
|
||||
import {deactivateLoader} from '../../webapp-common/core/actions/layout.actions';
|
||||
import {ALL_PROJECTS_OBJECT} from '../../webapp-common/core/effects/projects.effects';
|
||||
import {requestFailed} from '../../webapp-common/core/actions/http.actions';
|
||||
import {ApiProjectsService} from '../../business-logic/api-services/projects.service';
|
||||
import {deactivateLoader} from '@common/core/actions/layout.actions';
|
||||
import {ALL_PROJECTS_OBJECT} from '@common/core/effects/projects.effects';
|
||||
import {requestFailed} from '@common/core/actions/http.actions';
|
||||
import {ApiProjectsService} from '~/business-logic/api-services/projects.service';
|
||||
|
||||
|
||||
|
||||
@@ -15,14 +15,21 @@ import {ApiProjectsService} from '../../business-logic/api-services/projects.ser
|
||||
export class ProjectsEffects {
|
||||
private fetchingExampleExperiment: string = null;
|
||||
|
||||
constructor(private actions$: Actions, private store: Store, private projectsApi: ApiProjectsService) {}
|
||||
constructor(
|
||||
private actions$: Actions,
|
||||
private store: Store,
|
||||
private projectsApi: ApiProjectsService
|
||||
) {}
|
||||
|
||||
getSelectedProject = createEffect(() => this.actions$.pipe(
|
||||
ofType(actions.setSelectedProjectId),
|
||||
withLatestFrom(this.store.select(selectSelectedProjectId)),
|
||||
switchMap(([action, selectedProjectId]) => {
|
||||
if (!action.projectId) {
|
||||
return [actions.setSelectedProject({project: null})];
|
||||
return [
|
||||
deactivateLoader(action.type),
|
||||
actions.setSelectedProject({project: null}),
|
||||
];
|
||||
}
|
||||
if (action.projectId === selectedProjectId) {
|
||||
return [deactivateLoader(action.type)];
|
||||
@@ -30,8 +37,8 @@ export class ProjectsEffects {
|
||||
if (action.projectId === '*') {
|
||||
return [
|
||||
actions.setSelectedProject({project: ALL_PROJECTS_OBJECT}),
|
||||
deactivateLoader(action.type)
|
||||
];
|
||||
actions.getProjectUsers(action),
|
||||
deactivateLoader(action.type)];
|
||||
} else {
|
||||
this.fetchingExampleExperiment = action.example && action.projectId;
|
||||
return this.projectsApi.projectsGetAllEx({
|
||||
@@ -45,6 +52,7 @@ export class ProjectsEffects {
|
||||
finalize(() => this.fetchingExampleExperiment = null),
|
||||
mergeMap(({projects}) => [
|
||||
actions.setSelectedProject({project: projects[0]}),
|
||||
actions.getProjectUsers(action),
|
||||
deactivateLoader(action.type),
|
||||
]
|
||||
),
|
||||
|
||||
@@ -2,6 +2,7 @@ import {createReducer, createSelector, on} from '@ngrx/store';
|
||||
|
||||
import {initUsers, users, usersReducerFunctions, UsersState} from '@common/core/reducers/users-reducer';
|
||||
import {setCurrentUser} from '../actions/users.action';
|
||||
import {of} from 'rxjs';
|
||||
|
||||
export const selectHasDataFeature = createSelector(users, state => false);
|
||||
export const selectHasUserManagement = createSelector(users, () => false);
|
||||
@@ -20,3 +21,4 @@ export const selectFeatures = createSelector(users, (state) => []);
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export const selectTermsOfUse = createSelector(users, state => ({accept_required: null}));
|
||||
export const selectInvitesPending = createSelector(users, state => []);
|
||||
export const userAllowedToCreateQueue$ = store => of(true);
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
(projectSelected)="projectCardClicked($event)"
|
||||
(experimentSelected)="taskSelected($event)"
|
||||
(modelSelected)="modelSelected($event)"
|
||||
(pipelineSelected)="pipelineSelected($event)"
|
||||
(activeLinkChanged)="activeLinkChanged($event)"
|
||||
[projectsList]="projectsResults$ | async"
|
||||
[pipelinesList]="pipelinesResults$ | async"
|
||||
[experimentsList]="experimentsResults$ | async"
|
||||
[modelsList]="modelsResults$ | async"
|
||||
[activeLink]="activeLink"></sm-search-results-page>
|
||||
[activeLink]="activeLink">
|
||||
</sm-search-results-page>
|
||||
|
||||
@@ -3,10 +3,13 @@ import {Router} from '@angular/router';
|
||||
import {combineLatest, Observable, ObservedValueOf, Subscription} from 'rxjs';
|
||||
import {filter, skip} from 'rxjs/operators';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {Project} from '../../../../business-logic/model/projects/project';
|
||||
import {Model} from '../../../../business-logic/model/models/model';
|
||||
import {DashboardSearchComponentBase} from '../../../../webapp-common/dashboard/dashboard-search.component.base';
|
||||
import {SearchClear} from '../../../../webapp-common/dashboard-search/dashboard-search.actions';
|
||||
import {Project} from '~/business-logic/model/projects/project';
|
||||
import {Model} from '~/business-logic/model/models/model';
|
||||
import {DashboardSearchComponentBase} from '@common/dashboard/dashboard-search.component.base';
|
||||
import {SearchClear} from '@common/dashboard-search/dashboard-search.actions';
|
||||
|
||||
export type ActiveSearchLink = 'projects' | 'experiments' | 'models' | 'pipelines';
|
||||
|
||||
|
||||
@Component({
|
||||
selector : ' sm-dashboard-search',
|
||||
|
||||
@@ -1,21 +1,12 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {SMSharedModule} from '../../../webapp-common/shared/shared.module';
|
||||
import {SMSharedModule} from '@common/shared/shared.module';
|
||||
import {StoreModule} from '@ngrx/store';
|
||||
import {EffectsModule} from '@ngrx/effects';
|
||||
import {DashboardSearchEffects} from '../../../webapp-common/dashboard-search/dashboard-search.effects';
|
||||
import {ExperimentsSearchResultsComponent} from '../../../webapp-common/dashboard-search/dumb/experiments-search-results/experiments-search-results.component';
|
||||
import {ModelsSearchResultsComponent} from '../../../webapp-common/dashboard-search/dumb/models-search-results/models-search-results.component';
|
||||
import {ProjectsSearchResultsComponent} from '../../../webapp-common/dashboard-search/dumb/projects-search-results/projects-search-results.component';
|
||||
import {DashboardSearchEffects} from '@common/dashboard-search/dashboard-search.effects';
|
||||
import {ProjectsSharedModule} from '../../projects/shared/projects-shared.module';
|
||||
import {SharedModule} from '../../../shared/shared.module';
|
||||
import {dashboardSearchReducer} from '../../../webapp-common/dashboard-search/dashboard-search.reducer';
|
||||
|
||||
const declarations = [
|
||||
ModelsSearchResultsComponent,
|
||||
ProjectsSearchResultsComponent,
|
||||
ExperimentsSearchResultsComponent,
|
||||
];
|
||||
import {SharedModule} from '~/shared/shared.module';
|
||||
import {dashboardSearchReducer} from '@common/dashboard-search/dashboard-search.reducer';
|
||||
|
||||
@NgModule({
|
||||
imports : [
|
||||
@@ -26,8 +17,6 @@ const declarations = [
|
||||
EffectsModule.forFeature([DashboardSearchEffects]),
|
||||
SharedModule
|
||||
],
|
||||
declarations: [declarations],
|
||||
exports : [...declarations]
|
||||
})
|
||||
export class DashboardSearchModule {
|
||||
}
|
||||
|
||||
@@ -5,15 +5,16 @@ import {ExperimentSharedModule} from '../experiments/shared/experiment-shared.mo
|
||||
import {DashboardRoutingModule} from './dashboard-routing.module';
|
||||
import {StoreModule} from '@ngrx/store';
|
||||
import {GettingStartedCardComponent} from './dumb/getting-started-card/getting-started-card.component';
|
||||
import {SMSharedModule} from '../../webapp-common/shared/shared.module';
|
||||
import {CommonDashboardModule} from '../../webapp-common/dashboard/common-dashboard.module';
|
||||
import {commonDashboardReducer} from '../../webapp-common/dashboard/common-dashboard.reducer';
|
||||
import {SMSharedModule} from '@common/shared/shared.module';
|
||||
import {CommonDashboardModule} from '@common/dashboard/common-dashboard.module';
|
||||
import {commonDashboardReducer} from '@common/dashboard/common-dashboard.reducer';
|
||||
import {DashboardSearchComponent} from './containers/dashboard-search/dashboard-search.component';
|
||||
import {SearchResultsPageComponent} from './dumb/search-results-page/search-results-page.component';
|
||||
import {SharedModule} from '../../shared/shared.module';
|
||||
import {SharedModule} from '~/shared/shared.module';
|
||||
import {DashboardSearchModule} from './dashboard-search/dashboard-search.module';
|
||||
import {ProjectDialogModule} from '../../webapp-common/shared/project-dialog/project-dialog.module';
|
||||
import {ProjectDialogModule} from '@common/shared/project-dialog/project-dialog.module';
|
||||
import {ProjectsSharedModule} from '../projects/shared/projects-shared.module';
|
||||
import {SearchResultsComponent} from '@common/dashboard-search/dumb/search-results/search-results.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -28,7 +29,7 @@ import {ProjectsSharedModule} from '../projects/shared/projects-shared.module';
|
||||
SharedModule,
|
||||
DashboardSearchModule
|
||||
],
|
||||
declarations : [DashboardComponent, GettingStartedCardComponent, DashboardSearchComponent, SearchResultsPageComponent]
|
||||
declarations : [DashboardComponent, GettingStartedCardComponent, DashboardSearchComponent, SearchResultsPageComponent, SearchResultsComponent]
|
||||
})
|
||||
export class DashboardModule {
|
||||
}
|
||||
|
||||
@@ -7,22 +7,51 @@
|
||||
(click)="activeLinkChanged.emit('experiments')">EXPERIMENTS {{'(' + experimentsList.length + ')'}} </span>
|
||||
<span [ngClass]="{'active': activeLink === 'models'}" class="pointer category-link"
|
||||
(click)="activeLinkChanged.emit('models')">MODELS {{'(' + modelsList.length + ')'}} </span>
|
||||
<span [ngClass]="{'active': activeLink === 'pipelines'}" class="pointer category-link"
|
||||
(click)="activeLinkChanged.emit('pipelines')">PIPELINES ({{pipelinesList?.length}}) </span>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="activeLink === 'projects'" class="page-container">
|
||||
<sm-projects-search-results [projectsList]="projectsList"
|
||||
(projectClicked)="projectClicked($event)"></sm-projects-search-results>
|
||||
<div class="page-container">
|
||||
<sm-search-results
|
||||
[cardTemplate]="
|
||||
activeLink === 'projects' ? ProjectTemplate :
|
||||
activeLink === 'experiments' ? ExperimentTemplate :
|
||||
activeLink === 'models' ? ModelsTemplate :
|
||||
activeLink === 'pipelines' ? PipelineTemplate :
|
||||
ProjectTemplate"
|
||||
[results]="getResults()"
|
||||
[cardHeight]="getCardHeight()"
|
||||
(resultClicked)="projectClicked($event)">
|
||||
</sm-search-results>
|
||||
</div>
|
||||
|
||||
<ng-template #ProjectTemplate let-project="result">
|
||||
<sm-project-card
|
||||
[project]="project"
|
||||
(projectCardClicked)="projectClicked($event)"
|
||||
[hideMenu]="true"
|
||||
></sm-project-card>
|
||||
</ng-template>
|
||||
|
||||
<div *ngIf="activeLink === 'experiments'" class="page-container">
|
||||
<sm-experiments-search-results [experimentsList]="experimentsList"
|
||||
(experimentClicked)="experimentClicked($event)"></sm-experiments-search-results>
|
||||
</div>
|
||||
<ng-template #ExperimentTemplate let-experiment="result">
|
||||
<sm-experiment-card
|
||||
[experiment]="experiment"
|
||||
(experimentCardClicked)="experimentClicked($event)"
|
||||
></sm-experiment-card>
|
||||
</ng-template>
|
||||
|
||||
<div *ngIf="activeLink === 'models'" class="page-container">
|
||||
<sm-models-search-results [modelsList]="modelsList"
|
||||
(modelClicked)="modelClicked($event)"></sm-models-search-results>
|
||||
</div>
|
||||
<ng-template #ModelsTemplate let-model="result">
|
||||
<sm-model-card
|
||||
[model]="model"
|
||||
(modelCardClicked)="modelClicked($event)"
|
||||
></sm-model-card>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #PipelineTemplate let-pipeline="result">
|
||||
<sm-pipeline-card
|
||||
[project]="pipeline"
|
||||
[hideMenu]="true"
|
||||
(projectCardClicked)="pipelineClicked($event)"
|
||||
></sm-pipeline-card>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
||||
import {Project} from '../../../../business-logic/model/projects/project';
|
||||
import {Task} from '../../../../business-logic/model/tasks/task';
|
||||
import {ITask} from '../../../../business-logic/model/al-task';
|
||||
import {Model} from '../../../../business-logic/model/models/model';
|
||||
import {Project} from '~/business-logic/model/projects/project';
|
||||
import {Task} from '~/business-logic/model/tasks/task';
|
||||
import {ITask} from '~/business-logic/model/al-task';
|
||||
import {Model} from '~/business-logic/model/models/model';
|
||||
import {ActiveSearchLink} from '~/features/dashboard/containers/dashboard-search/dashboard-search.component';
|
||||
|
||||
@Component({
|
||||
selector : 'sm-search-results-page',
|
||||
@@ -13,12 +14,14 @@ export class SearchResultsPageComponent {
|
||||
@Input() projectsList: Array<Project> = [];
|
||||
@Input() experimentsList: Array<Task> = [];
|
||||
@Input() modelsList: Array<Model> = [];
|
||||
@Input() activeLink = 'projects';
|
||||
@Input() pipelinesList: Array<Project> = [];
|
||||
@Input() activeLink: ActiveSearchLink;
|
||||
|
||||
@Output() projectSelected = new EventEmitter<Project>();
|
||||
@Output() activeLinkChanged = new EventEmitter<string>();
|
||||
@Output() experimentSelected = new EventEmitter<ITask>();
|
||||
@Output() modelSelected = new EventEmitter<Model>();
|
||||
@Output() pipelineSelected = new EventEmitter<Project>();
|
||||
|
||||
public projectClicked(project: Project) {
|
||||
this.projectSelected.emit(project);
|
||||
@@ -32,4 +35,25 @@ export class SearchResultsPageComponent {
|
||||
this.modelSelected.emit(model);
|
||||
}
|
||||
|
||||
public pipelineClicked(pipeline: Project) {
|
||||
this.pipelineSelected.emit(pipeline);
|
||||
}
|
||||
|
||||
getResults() {
|
||||
return this[`${this.activeLink}List`];
|
||||
}
|
||||
|
||||
getCardHeight() {
|
||||
switch (this.activeLink) {
|
||||
case 'projects':
|
||||
return 246;
|
||||
case 'experiments':
|
||||
case 'models':
|
||||
return 264;
|
||||
case 'pipelines':
|
||||
return 226;
|
||||
default:
|
||||
return 250;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<nav [smOverflows]="splitSize" (onOverflows)="navbarOverflowed($event)" [class.minimized]="minimized">
|
||||
<span [routerLink]="['execution']" routerLinkActive #rlaExecution="routerLinkActive" queryParamsHandling="preserve">
|
||||
<sm-navbar-item header="execution" [active]="rlaExecution.isActive" class="small-nav"></sm-navbar-item>
|
||||
</span>
|
||||
<span [routerLink]="['hyper-params/hyper-param/_empty_']" queryParamsHandling="merge">
|
||||
<sm-navbar-item header="configuration"
|
||||
class="small-nav"
|
||||
[active]="(routerConfig$| async)?.includes('hyper-params')"></sm-navbar-item>
|
||||
</span>
|
||||
<span [routerLink]="['artifacts']" routerLinkActive #rlaModel="routerLinkActive" queryParamsHandling="preserve">
|
||||
<sm-navbar-item header="artifacts"
|
||||
class="small-nav"
|
||||
[active]="rlaModel.isActive"></sm-navbar-item>
|
||||
</span>
|
||||
<span [routerLink]="['general']" routerLinkActive #rlaGeneral="routerLinkActive" queryParamsHandling="preserve">
|
||||
<sm-navbar-item header="info"
|
||||
class="small-nav"
|
||||
[active]="rlaGeneral.isActive"></sm-navbar-item>
|
||||
</span>
|
||||
|
||||
<span [matMenuTriggerFor]="results" *ngIf="overflow">
|
||||
<sm-navbar-item header="results"
|
||||
class="small-nav"
|
||||
[multi]="true"
|
||||
[active]="rlaDebug.isActive || rlaPlots.isActive || rlaScalars.isActive || rlaLog.isActive"></sm-navbar-item>
|
||||
</span>
|
||||
<div class="d-inline-block" [style.visibility]="overflow ? 'hidden' : 'visible'">
|
||||
<span [routerLink]="baseInfoRoute.concat(['log'])" routerLinkActive queryParamsHandling="preserve"
|
||||
#rlaLog="routerLinkActive">
|
||||
<sm-navbar-item class="small-nav" header="console" [active]="rlaLog.isActive"></sm-navbar-item>
|
||||
</span>
|
||||
<span [routerLink]="baseInfoRoute.concat(['metrics','scalar'])" routerLinkActive queryParamsHandling="preserve"
|
||||
#rlaScalars="routerLinkActive">
|
||||
<sm-navbar-item class="small-nav" header="Scalars" [active]="rlaScalars.isActive"></sm-navbar-item>
|
||||
</span>
|
||||
<span [routerLink]="baseInfoRoute.concat(['metrics','plots'])" routerLinkActive queryParamsHandling="preserve"
|
||||
#rlaPlots="routerLinkActive">
|
||||
<sm-navbar-item class="small-nav" header="PLOTS" [active]="rlaPlots.isActive"></sm-navbar-item>
|
||||
</span>
|
||||
<span [routerLink]="baseInfoRoute.concat(['debugImages'])" routerLinkActive queryParamsHandling="preserve"
|
||||
#rlaDebug="routerLinkActive">
|
||||
<sm-navbar-item class="small-nav" header="DEBUG SAMPLES" [active]="rlaDebug.isActive"></sm-navbar-item>
|
||||
</span>
|
||||
</div>
|
||||
<mat-menu #results="matMenu">
|
||||
<button mat-menu-item [routerLink]="baseInfoRoute.concat(['log'])" [class.active]="rlaLog.isActive">CONSOLE</button>
|
||||
<button mat-menu-item [routerLink]="baseInfoRoute.concat(['metrics','scalar'])"
|
||||
[class.active]="rlaScalars.isActive">SCALARS
|
||||
</button>
|
||||
<button mat-menu-item [routerLink]="baseInfoRoute.concat(['metrics','plots'])" [class.active]="rlaPlots.isActive">
|
||||
PLOTS
|
||||
</button>
|
||||
<button mat-menu-item [routerLink]="baseInfoRoute.concat(['debugImages'])" [class.active]="rlaDebug.isActive">DEBUG
|
||||
SAMPLES
|
||||
</button>
|
||||
</mat-menu>
|
||||
<ng-content select="[refresh]"></ng-content>
|
||||
</nav>
|
||||
@@ -0,0 +1,32 @@
|
||||
@import "variables";
|
||||
$output-tabs-height: 40px;
|
||||
|
||||
nav {
|
||||
height: $output-tabs-height;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #efefef;
|
||||
//overflow: hidden;
|
||||
padding: 0 24px;
|
||||
|
||||
.refresh-position {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.refreshIcon{
|
||||
margin-right: 10px;
|
||||
}
|
||||
span.disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
.mat-menu-item {
|
||||
padding-left: 22px;
|
||||
&.active {
|
||||
border-left: 6px solid $purple;
|
||||
padding-left: 16px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import {Component, Input} from '@angular/core';
|
||||
import {FeaturesEnum} from '../../../../business-logic/model/users/featuresEnum';
|
||||
import {selectRouterConfig} from '../../../../webapp-common/core/reducers/router-reducer';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {IExperimentInfoState} from '../../reducers/experiment-info.reducer';
|
||||
import {Observable} from 'rxjs';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'sm-experiment-info-navbar',
|
||||
templateUrl: './experiment-info-navbar.component.html',
|
||||
styleUrls: ['./experiment-info-navbar.component.scss']
|
||||
})
|
||||
export class ExperimentInfoNavbarComponent {
|
||||
public featuresEnum = FeaturesEnum;
|
||||
public routerConfig$: Observable<string[]>;
|
||||
public baseInfoRoute: string[];
|
||||
public overflow: boolean;
|
||||
private _minimized: boolean;
|
||||
|
||||
@Input() set minimized(minimized: boolean) {
|
||||
this.baseInfoRoute = minimized ? ['info-output'] : [];
|
||||
this._minimized = minimized;
|
||||
}
|
||||
get minimized(){
|
||||
return this._minimized;
|
||||
}
|
||||
|
||||
@Input() splitSize: number;
|
||||
|
||||
|
||||
constructor(private store: Store<IExperimentInfoState>,) {
|
||||
this.routerConfig$ = this.store.select(selectRouterConfig);
|
||||
}
|
||||
navbarOverflowed($event: boolean) {
|
||||
this.overflow = $event;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
<sm-experiment-info-header-status-progress-bar
|
||||
[status]="(tableSelectedExperiment$| async)?.status || selectedExperiment?.status"
|
||||
[editable]="editable$ | async"
|
||||
[showMaximize]="true"
|
||||
(closeInfoClicked)="deselectExperiment()"
|
||||
(maximizedClicked)="maximize()">
|
||||
</sm-experiment-info-header-status-progress-bar>
|
||||
|
||||
<div class="experiment-info-container light-theme">
|
||||
<sm-experiment-info-header
|
||||
[experiment]="(tableSelectedExperiment$| async) || selectedExperiment"
|
||||
[infoData]="infoData$ | async"
|
||||
[editable]="!isExample"
|
||||
[showMenu]="true"
|
||||
[backdropActive]="backdropActive$|async"
|
||||
(experimentNameChanged)="updateExperimentName($event)"
|
||||
>
|
||||
</sm-experiment-info-header>
|
||||
<nav>
|
||||
<span routerLink="execution" [routerLinkActive]="'disabled'" queryParamsHandling="merge">
|
||||
<sm-navbar-item header="execution" [active]="routerConfig.includes('execution')"></sm-navbar-item>
|
||||
</span>
|
||||
|
||||
<span [routerLink]="['hyper-params/hyper-param/_empty_']" [class.disabled]="routerConfig.includes('hyper-params')" queryParamsHandling="merge">
|
||||
<sm-navbar-item header="configuration" [active]="routerConfig.includes('hyper-params')"></sm-navbar-item>
|
||||
</span>
|
||||
<span [routerLink]="['artifacts']" [routerLinkActive]="'disabled'" queryParamsHandling="preserve">
|
||||
<sm-navbar-item header="artifacts" [active]="routerConfig.includes('artifacts')"></sm-navbar-item>
|
||||
</span>
|
||||
<span [routerLink]="['general']" [routerLinkActive]="'disabled'" queryParamsHandling="preserve">
|
||||
<sm-navbar-item header="info" [active]="routerConfig.includes('general')"></sm-navbar-item>
|
||||
</span>
|
||||
<span [routerLink]="['info-output']" [routerLinkActive]="'disabled'" queryParamsHandling="preserve">
|
||||
<sm-navbar-item header="results" [active]="routerConfig.includes('info-output')"></sm-navbar-item>
|
||||
</span>
|
||||
</nav>
|
||||
<div class="experiment-info-body" #scrollContainer>
|
||||
<router-outlet (activate)="onActivate($event, scrollContainer)"></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
@import "../../../../webapp-common/shared/ui-components/styles/variables";
|
||||
@import "../../../../webapp-common/layout/layout.scss";
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
height: 100%;
|
||||
|
||||
.experiment-info-body {
|
||||
height: calc(100% - #{$experiment-info-header-height + $experiment-info-tabs-height});
|
||||
flex-grow: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.experiment-info-container {
|
||||
height: calc(100% - #{$project-info-progress-height});
|
||||
padding: 15px 0 0 5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
nav {
|
||||
height: $experiment-info-tabs-height;
|
||||
border-bottom: 1px solid rgba(2, 2, 2, 0.07);
|
||||
|
||||
span.disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep nav {
|
||||
border-bottom: 1px solid rgba(2, 2, 2, 0.07);
|
||||
}
|
||||
|
||||
::ng-deep sm-simple-table-2 .headers {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
import {Component, OnDestroy, OnInit} from '@angular/core';
|
||||
import {ActivatedRoute, Params, Router} from '@angular/router';
|
||||
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 {IExperimentInfoState} from '../../reducers/experiment-info.reducer';
|
||||
import {selectExperimentInfoData, selectIsExperimentEditable, selectSelectedExperiment} from '../../reducers';
|
||||
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 '@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 '@common/experiments/reducers';
|
||||
import {ITableExperiment} from '@common/experiments/shared/common-experiment-model.model';
|
||||
import {setTableMode} from '@common/experiments/actions/common-experiments-view.actions';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'sm-experiment-info',
|
||||
templateUrl: './experiment-info.component.html',
|
||||
styleUrls: ['./experiment-info.component.scss']
|
||||
})
|
||||
export class ExperimentInfoComponent implements OnInit, OnDestroy {
|
||||
|
||||
private paramsSubscription: Subscription;
|
||||
public selectedExperiment: IExperimentInfo;
|
||||
private selectedExperimentSubscription: Subscription;
|
||||
public editable$: Observable<boolean>;
|
||||
public infoData$: Observable<IExperimentInfo>;
|
||||
public backdropActive$: Observable<any>;
|
||||
public isExample: boolean;
|
||||
private projectId: string;
|
||||
private experimentId: string;
|
||||
public resultsTab: boolean;
|
||||
public queryParams$: Observable<Params>;
|
||||
public routerConfig: string[];
|
||||
private routerConfigSubscription: Subscription;
|
||||
public tableSelectedExperiment$: Observable<ITableExperiment>;
|
||||
private toMaximize = false;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private store: Store<IExperimentInfoState>,
|
||||
private route: ActivatedRoute
|
||||
) {
|
||||
this.editable$ = this.store.select(selectIsExperimentEditable);
|
||||
this.infoData$ = this.store.select(selectExperimentInfoData);
|
||||
this.backdropActive$ = this.store.select(selectBackdropActive);
|
||||
this.queryParams$ = this.store.select(selectRouterQueryParams);
|
||||
this.tableSelectedExperiment$ = this.store.select(selectSelectedTableExperiment);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.selectedExperimentSubscription = this.store.select(selectSelectedExperiment)
|
||||
.subscribe(experiment => {
|
||||
this.selectedExperiment = experiment;
|
||||
this.isExample = isReadOnly(experiment);
|
||||
});
|
||||
this.routerConfigSubscription = this.store.select(selectRouterConfig).subscribe(routerConfig => {
|
||||
this.routerConfig = routerConfig;
|
||||
});
|
||||
|
||||
this.paramsSubscription = this.store.select(selectRouterParams)
|
||||
.pipe(
|
||||
tap((params) => {
|
||||
this.projectId = get('projectId', params);
|
||||
this.resultsTab = 'info-output' === this.route.firstChild.routeConfig.path;
|
||||
}),
|
||||
debounceTime(150),
|
||||
map(params => get('experimentId', params)),
|
||||
filter(experimentId => !!experimentId),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
.subscribe(experimentId => {
|
||||
this.experimentId = experimentId;
|
||||
|
||||
// We already have GetExperimentInfo in output (results) component
|
||||
if (!this.resultsTab) {
|
||||
this.store.dispatch(new commonInfoActions.ResetExperimentInfo());
|
||||
this.store.dispatch(new commonInfoActions.GetExperimentInfo(experimentId));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.paramsSubscription.unsubscribe();
|
||||
this.selectedExperimentSubscription.unsubscribe();
|
||||
this.routerConfigSubscription.unsubscribe();
|
||||
if (!this.toMaximize) {
|
||||
this.store.dispatch(new commonInfoActions.SetExperiment(null));
|
||||
this.store.dispatch(new commonInfoActions.ResetExperimentInfo());
|
||||
}
|
||||
}
|
||||
|
||||
updateExperimentName(name) {
|
||||
if (name.trim().length > 2) {
|
||||
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'));
|
||||
}
|
||||
}
|
||||
|
||||
deselectExperiment() {
|
||||
this.navigateAfterExperimentSelectionChanged();
|
||||
}
|
||||
|
||||
navigateAfterExperimentSelectionChanged() {
|
||||
this.store.dispatch(setTableMode({mode: 'table'}));
|
||||
this.router.navigate([`projects/${this.projectId}/experiments`], {queryParamsHandling: 'merge'});
|
||||
}
|
||||
|
||||
onActivate(e, scrollContainer) {
|
||||
scrollContainer.scrollTop = 0;
|
||||
}
|
||||
|
||||
maximize() {
|
||||
if (window.location.pathname.includes('info-output')) {
|
||||
const resultsPath = this.route.firstChild?.firstChild?.routeConfig?.path || this.route.firstChild.routeConfig.path;
|
||||
this.router.navigateByUrl(`projects/${this.projectId}/experiments/${this.experimentId}/output/${resultsPath}`);
|
||||
} else {
|
||||
const parts = window.location.pathname.split('/');
|
||||
parts.splice(5, 0, 'output');
|
||||
this.router.navigateByUrl(parts.join('/'));
|
||||
}
|
||||
this.toMaximize = true;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component} from '@angular/core';
|
||||
import {Component, Input} from '@angular/core';
|
||||
import {ExperimentMenuComponent} from '@common/experiments/shared/components/experiment-menu/experiment-menu.component';
|
||||
|
||||
@Component({
|
||||
@@ -7,7 +7,6 @@ import {ExperimentMenuComponent} from '@common/experiments/shared/components/exp
|
||||
styleUrls: ['../../../../webapp-common/experiments/shared/components/experiment-menu/experiment-menu.component.scss']
|
||||
})
|
||||
export class ExperimentMenuExtendedComponent extends ExperimentMenuComponent{
|
||||
|
||||
set contextMenu(data) {}
|
||||
get contextMenu() {
|
||||
return this;
|
||||
|
||||
@@ -1,60 +1,24 @@
|
||||
<sm-overlay [backdropActive]="backdropActive$|async"></sm-overlay>
|
||||
<div class="experiment-output-container light-theme">
|
||||
<sm-experiment-info-header-status-icon-label
|
||||
[status]=" (selectedExperiment)?.status || selectedExperiment?.status"
|
||||
[development]="isDevelopment"
|
||||
></sm-experiment-info-header-status-icon-label>
|
||||
<div class="experiment-output-container light-theme" [class.minimized]="minimized">
|
||||
<sm-experiment-info-header
|
||||
*ngIf="!minimized"
|
||||
(minimizeClicked)="minimizeView()"
|
||||
[experiment]="selectedExperiment"
|
||||
[infoData]="infoData$ | async"
|
||||
[editable]="!isExample"
|
||||
[showMenu]="true"
|
||||
[showMinimize]="true"
|
||||
[minimized]="minimized"
|
||||
[isSharedAndNotOwner]="isSharedAndNotOwner$ | async"
|
||||
(experimentNameChanged)="updateExperimentName($event)"
|
||||
|
||||
(closeInfoClicked)="closePanel()"
|
||||
(minimizeClicked)="minimizeView()"
|
||||
(maximizedClicked)="maximize()"
|
||||
>
|
||||
</sm-experiment-info-header>
|
||||
<nav [class.minimized]="minimized" [smOverflows]="'nav'" (onOverflows)="overflow = $event">
|
||||
<ng-container *ngIf="!minimized">
|
||||
<span [routerLink]="['execution']" queryParamsHandling="preserve">
|
||||
<sm-navbar-item header="execution" [active]="routerConfig.includes('execution')"></sm-navbar-item>
|
||||
</span>
|
||||
<span [routerLink]="['hyper-params/hyper-param/_empty_']" [class.disabled]="routerConfig.includes('hyper-params')" queryParamsHandling="merge">
|
||||
<sm-navbar-item header="Configuration" [active]="routerConfig.includes('hyper-params')"></sm-navbar-item>
|
||||
</span>
|
||||
<span [routerLink]="['artifacts']" [routerLinkActive]="'disabled'">
|
||||
<sm-navbar-item header="artifacts" [active]="routerConfig.includes('artifacts')"></sm-navbar-item>
|
||||
</span>
|
||||
<span [routerLink]="['general']" queryParamsHandling="preserve">
|
||||
<sm-navbar-item header="info" [active]="routerConfig.includes('general')"></sm-navbar-item>
|
||||
</span>
|
||||
</ng-container>
|
||||
|
||||
<span [matMenuTriggerFor]="results" *ngIf="!minimized && overflow">
|
||||
<sm-navbar-item header="results" [multi]="true" [active]="console.active || scalar.active || plots.active || samples.active"></sm-navbar-item>
|
||||
</span>
|
||||
|
||||
<mat-menu #results="matMenu">
|
||||
<button mat-menu-item [routerLink]="['log']" [class.active]="routerConfig.includes('log')">CONSOLE</button>
|
||||
<button mat-menu-item [routerLink]="['metrics','scalar']" [class.active]="routerConfig.includes('metrics') && routerConfig.includes('scalar')">SCALARS</button>
|
||||
<button mat-menu-item [routerLink]="['metrics','plots']" [class.active]="routerConfig.includes('metrics') && routerConfig.includes('plots')">PLOTS</button>
|
||||
<button mat-menu-item [routerLink]="['debugImages']" [class.active]="routerConfig.includes('debugImages')">DEBUG SAMPLES</button>
|
||||
</mat-menu>
|
||||
|
||||
<div class="d-inline-block" [style.visibility]="overflow && !minimized ? 'hidden' : 'visible'">
|
||||
<span [routerLink]="['log']" queryParamsHandling="preserve">
|
||||
<sm-navbar-item #console header="console" [active]="routerConfig.includes('log')"></sm-navbar-item>
|
||||
</span>
|
||||
<span [routerLink]="['metrics','scalar']" queryParamsHandling="preserve">
|
||||
<sm-navbar-item #scalar header="Scalars" [active]="routerConfig.includes('metrics') && routerConfig.includes('scalar')"></sm-navbar-item>
|
||||
</span>
|
||||
<span [routerLink]="['metrics','plots']" queryParamsHandling="preserve">
|
||||
<sm-navbar-item #plots header="PLOTS" [active]="routerConfig.includes('metrics') && routerConfig.includes('plots')"></sm-navbar-item>
|
||||
</span>
|
||||
<span [routerLink]="['debugImages']" queryParamsHandling="preserve">
|
||||
<sm-navbar-item #samples header="DEBUG SAMPLES" [active]="routerConfig.includes('debugImages')"></sm-navbar-item>
|
||||
</span>
|
||||
</div>
|
||||
<span class="refresh-position">
|
||||
<sm-experiment-info-navbar [minimized]="minimized" [splitSize]="selectSplitSize$ | async">
|
||||
<span class="refresh-position" refresh>
|
||||
<sm-experiment-settings
|
||||
[class.maximized]="!minimized"
|
||||
[showSettings]="routerConfig.includes('scalar') && minimized"
|
||||
@@ -67,8 +31,8 @@
|
||||
>
|
||||
</sm-refresh-button>
|
||||
</span>
|
||||
</nav>
|
||||
<div class="output-body" [class.minimized]="minimized">
|
||||
<router-outlet class="output-outlet"></router-outlet>
|
||||
</sm-experiment-info-navbar>
|
||||
<div class="output-body" [class.minimized]="minimized" #scrollContainer>
|
||||
<router-outlet class="output-outlet" (activate)="onActivate($event, scrollContainer)"></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,6 @@ export const routes: Routes = [
|
||||
imports: [
|
||||
SMSharedModule,
|
||||
RouterModule.forChild(routes),
|
||||
|
||||
],
|
||||
exports: [RouterModule, SelectableListComponent, SelectableFilterListComponent]
|
||||
})
|
||||
|
||||
@@ -21,7 +21,6 @@ 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 '@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';
|
||||
@@ -29,6 +28,7 @@ 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';
|
||||
import {ExperimentInfoNavbarComponent} from './containers/experiment-info-navbar/experiment-info-navbar.component';
|
||||
|
||||
|
||||
export const experimentSyncedKeys = [
|
||||
@@ -90,9 +90,9 @@ const getExperimentsConfig = () => ({
|
||||
],
|
||||
declarations: [
|
||||
ExperimentsComponent,
|
||||
ExperimentInfoComponent,
|
||||
ExperimentInfoExecutionComponent,
|
||||
ExperimentOutputComponent,
|
||||
ExperimentInfoNavbarComponent
|
||||
],
|
||||
providers: [
|
||||
AdminService,
|
||||
|
||||
@@ -1,16 +1,8 @@
|
||||
export function isDeletableProject(readyForDeletion) {
|
||||
return (readyForDeletion.experiments.unarchived + readyForDeletion.models.unarchived) === 0;
|
||||
}
|
||||
export const isDeletableProject = readyForDeletion => (readyForDeletion.experiments.unarchived + readyForDeletion.models.unarchived) === 0;
|
||||
|
||||
export function getDeletePopupEntitiesList(): string {
|
||||
return 'experiments or models';
|
||||
}
|
||||
export const getDeletePopupEntitiesList = (): string => 'experiments or models';
|
||||
|
||||
export function getDeleteProjectPopupStatsBreakdown(readyForDeletion, statsSubset: 'archived' | 'unarchived' | 'total'): string {
|
||||
return `${readyForDeletion.experiments[statsSubset] > 0 ? readyForDeletion.experiments[statsSubset] + ' experiments ' : ''}
|
||||
export const getDeleteProjectPopupStatsBreakdown = (readyForDeletion, statsSubset: 'archived' | 'unarchived' | 'total', experimentCaption): string => `${readyForDeletion.experiments[statsSubset] > 0 ? `${readyForDeletion.experiments[statsSubset]} ${experimentCaption} ` : ''}
|
||||
${readyForDeletion.models[statsSubset] > 0 ? readyForDeletion.models[statsSubset] + ' models ' : ''}`;
|
||||
}
|
||||
|
||||
export function readyForDeletionFilter(readyForDeletion) {
|
||||
return !(readyForDeletion.experiments === null || readyForDeletion.models === null);
|
||||
}
|
||||
export const readyForDeletionFilter = readyForDeletion => !(readyForDeletion.experiments === null || readyForDeletion.models === null);
|
||||
|
||||
@@ -5,22 +5,26 @@ import {SMSharedModule} from '@common/shared/shared.module';
|
||||
import {ProjectCardComponent} from '@common/shared/ui-components/panel/project-card/project-card.component';
|
||||
import {ProjectCardMenuExtendedComponent} from '~/features/projects/containers/project-card-menu-extended/project-card-menu-extended.component';
|
||||
import {ProjectCardMenuComponent} from '@common/shared/ui-components/panel/project-card-menu/project-card-menu.component';
|
||||
import {PipelineCardComponent} from '../../../webapp-common/pipelines/pipeline-card/pipeline-card.component';
|
||||
import {PipelineCardMenuComponent} from '../../../webapp-common/pipelines/pipeline-card-menu/pipeline-card-menu.component';
|
||||
|
||||
const _declarations = [
|
||||
ProjectCardComponent,
|
||||
ProjectCardMenuComponent,
|
||||
ProjectCardMenuExtendedComponent
|
||||
ProjectCardMenuExtendedComponent,
|
||||
PipelineCardComponent,
|
||||
PipelineCardMenuComponent,
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports : [
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
SMSharedModule
|
||||
],
|
||||
declarations : [..._declarations],
|
||||
exports : [..._declarations]
|
||||
declarations: [..._declarations],
|
||||
exports: [..._declarations]
|
||||
})
|
||||
export class ProjectsSharedModule {
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<sm-dialog-template>
|
||||
<sm-dialog-template header="CREATE CREDENTIALS" iconClass="al-ico-access-key">
|
||||
<sm-admin-dialog-template
|
||||
[newCredential]="(newCredential$ | async) || {}"
|
||||
(onCreateCredentials)="onCreateCredentials($event)"
|
||||
[newCredential]="(newCredential$ | async)"
|
||||
(updateLabel)="updateLabel($event)"
|
||||
></sm-admin-dialog-template>
|
||||
</sm-dialog-template>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {Component, Inject} from '@angular/core';
|
||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {createCredential} from '@common/core/actions/common-auth.actions';
|
||||
import {updateCredentialLabel} from '@common/core/actions/common-auth.actions';
|
||||
import {OrganizationGetUserCompaniesResponseCompanies} from '~/business-logic/model/organization/organizationGetUserCompaniesResponseCompanies';
|
||||
import {Observable} from 'rxjs';
|
||||
import {CredentialKeyExt, selectNewCredential} from '@common/core/reducers/common-auth-reducer';
|
||||
@@ -21,7 +21,7 @@ import {CredentialKeyExt, selectNewCredential} from '@common/core/reducers/commo
|
||||
this.newCredential$ = this.store.select(selectNewCredential);
|
||||
}
|
||||
|
||||
onCreateCredentials({label}) {
|
||||
this.store.dispatch(createCredential({workspace: this.data.workspace, label}));
|
||||
updateLabel({credential, label}) {
|
||||
this.store.dispatch(updateCredentialLabel({credential, label}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
matTooltipPosition="above"></i>
|
||||
</div>
|
||||
<div class="section mb-4" *ngFor="let cred of credentials$ | async | keyvalue">
|
||||
<sm-admin-credential-table [credentials]="cred?.value"
|
||||
(credentialRevoked)="onCredentialRevoked($event)">
|
||||
</sm-admin-credential-table>
|
||||
<sm-admin-credential-table
|
||||
[credentials]="cred?.value"
|
||||
(updateCredentialLabel)="updateLabel($event)"
|
||||
(credentialRevoked)="onCredentialRevoked($event)"
|
||||
></sm-admin-credential-table>
|
||||
<span
|
||||
class="add-button d-flex align-items-center pointer"
|
||||
[class.disabled]="creatingCredentials"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
@import "../../../../../webapp-common/shared/ui-components/styles/variables";
|
||||
@import "variables";
|
||||
|
||||
:host {
|
||||
max-width: 900px;
|
||||
|
||||
.title {
|
||||
color: $white;
|
||||
font-size: 16px;
|
||||
|
||||
@@ -3,7 +3,7 @@ import {Observable, Subscription} from 'rxjs';
|
||||
import {CredentialKeyExt, selectCredentials, selectNewCredential} from '@common/core/reducers/common-auth-reducer';
|
||||
import {selectCurrentUser} from '@common/core/reducers/users-reducer';
|
||||
import {filter, take} from 'rxjs/operators';
|
||||
import {credentialRevoked, getAllCredentials, resetCredential} from '@common/core/actions/common-auth.actions';
|
||||
import {createCredential, credentialRevoked, getAllCredentials, resetCredential, updateCredentialLabel} from '@common/core/actions/common-auth.actions';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {GetCurrentUserResponseUserObject} from '~/business-logic/model/users/getCurrentUserResponseUserObject';
|
||||
import {MatDialog} from '@angular/material/dialog';
|
||||
@@ -41,19 +41,23 @@ export class UserCredentialsComponent implements OnInit, OnDestroy {
|
||||
this.creatingCredentials = true;
|
||||
this.dialog.open(CreateCredentialDialogComponent, {
|
||||
data: {workspace : this.user.company},
|
||||
width: '816px'
|
||||
width: '788px'
|
||||
}
|
||||
).afterClosed().subscribe(() => {
|
||||
this.creatingCredentials = false;
|
||||
this.store.dispatch(resetCredential());
|
||||
});
|
||||
// this.store.dispatch(createCredential({workspace: this.user.company, openCredentialsPopup: true}));
|
||||
this.store.dispatch(createCredential({workspace: this.user.company, openCredentialsPopup: true}));
|
||||
}
|
||||
|
||||
onCredentialRevoked(accessKey) {
|
||||
this.store.dispatch(credentialRevoked({accessKey, workspaceId: this.user.company.id}));
|
||||
}
|
||||
|
||||
updateLabel({credential, label}) {
|
||||
this.store.dispatch(updateCredentialLabel({credential: {...credential, company: this.user.company.id}, label}));
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.newCredentialSub.unsubscribe();
|
||||
}
|
||||
|
||||
@@ -21,12 +21,12 @@ export const selectBreadcrumbsStringsBase = createSelector(
|
||||
(project, experiment, model, projects) =>
|
||||
({project, experiment, model, projects}) as IBreadcrumbs);
|
||||
|
||||
export const prepareNames = (data: IBreadcrumbs, noSubProjects?: boolean) => {
|
||||
export const prepareNames = (data: IBreadcrumbs, isPipeline?: boolean, fullScreen = false) => {
|
||||
const project = prepareLinkData(data.project, true);
|
||||
if (data.project) {
|
||||
const subProjects = [];
|
||||
let subProjectsNames = [data.project?.name];
|
||||
if (!noSubProjects) {
|
||||
if (!isPipeline) {
|
||||
subProjectsNames = data.project?.name?.split('/');
|
||||
}
|
||||
let currentName = '';
|
||||
@@ -41,9 +41,11 @@ export const prepareNames = (data: IBreadcrumbs, noSubProjects?: boolean) => {
|
||||
});
|
||||
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`
|
||||
url: isPipeline ? `pipelines/${subProject?.id}/experiments` :
|
||||
fullScreen ? `projects/${subProject?.id}/experiments/${data.experiment.id}` :
|
||||
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;
|
||||
@@ -56,7 +58,7 @@ export const prepareNames = (data: IBreadcrumbs, noSubProjects?: boolean) => {
|
||||
const models = formatStaticCrumb('models');
|
||||
const compare = formatStaticCrumb('compare-experiments');
|
||||
return {
|
||||
...(project.url !=='*' && {':projectId': project}),
|
||||
...(project.url !== '*' && {':projectId': project}),
|
||||
':taskId' : task,
|
||||
':controllerId': experiment,
|
||||
'compare-experiments': compare,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
@font-face {
|
||||
font-family: '#{$icomoon-font-family}';
|
||||
src: url('./#{$icomoon-font-family}.ttf?medlz4') format('truetype');
|
||||
src: url('./#{$icomoon-font-family}.ttf?2i0eh5') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
@@ -1166,9 +1166,9 @@
|
||||
content: $al-ico-greater-than;
|
||||
}
|
||||
}
|
||||
.al-ico-toogle-graph {
|
||||
.al-ico-eye-outline {
|
||||
&:before {
|
||||
content: $al-ico-toogle-graph;
|
||||
content: $al-ico-eye-outline;
|
||||
}
|
||||
}
|
||||
.al-ico-status-draft {
|
||||
|
||||
Binary file not shown.
@@ -225,7 +225,7 @@ $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-eye-outline: "\e9c8";
|
||||
$al-ico-status-draft: "\e902";
|
||||
$al-ico-status-published: "\e906";
|
||||
$al-ico-status-aborted-sec: "\e918";
|
||||
|
||||
@@ -449,7 +449,7 @@ body .clean-list {
|
||||
box-shadow: 0 -2px 8px 0 rgba(0, 0, 0, 0.2);
|
||||
font-family: 'Heebo', sans-serif;
|
||||
font-size: 11px;
|
||||
line-height: 1.45;
|
||||
line-height: 1.55;
|
||||
letter-spacing: 0.3px;
|
||||
color: #ffffff;
|
||||
word-break: break-word;
|
||||
@@ -465,11 +465,6 @@ body .clean-list {
|
||||
}
|
||||
}
|
||||
|
||||
.mat-button-toggle-appearance-standard .mat-button-toggle-label-content {
|
||||
line-height: 30px !important;
|
||||
font-size: 11px !important;
|
||||
}
|
||||
|
||||
// --------------------------------old------------------------------------------
|
||||
@import "shared/ui-components/styles/index";
|
||||
|
||||
|
||||
@@ -12,6 +12,16 @@ export const createCredential = createAction(
|
||||
AUTH_PREFIX + 'CREATE_CREDENTIAL (API)',
|
||||
props<{workspace: GetCurrentUserResponseUserObjectCompany; openCredentialsPopup?: boolean; label?: string}>()
|
||||
);
|
||||
|
||||
export const updateCredentialLabel = createAction(
|
||||
AUTH_PREFIX + 'UPDATE_CREDENTIAL_LABEL',
|
||||
props<{credential: CredentialKeyExt; label?: string}>()
|
||||
);
|
||||
|
||||
export const setCredentialLabel = createAction(
|
||||
AUTH_PREFIX + 'SET_CREDENTIAL_LABEL',
|
||||
props<{credential: CredentialKeyExt; label?: string}>()
|
||||
);
|
||||
export const addCredential = createAction(
|
||||
AUTH_PREFIX + 'ADD_CREDENTIAL',
|
||||
props<{ newCredential: CredentialKeyExt; workspaceId: string }>()
|
||||
@@ -57,3 +67,7 @@ export const setSignedUrl = createAction(
|
||||
props<{url: string; signed: string; expires: number}>()
|
||||
);
|
||||
|
||||
export const removeSignedUrl = createAction(
|
||||
AUTH_PREFIX + '[remove signed url]',
|
||||
props<{url: string}>()
|
||||
);
|
||||
|
||||
@@ -82,3 +82,7 @@ export const openAppsAwarenessDialog = createAction(VIEW_PREFIX + '[apps awarene
|
||||
props<{appsYouTubeIntroVideoId}>()
|
||||
);
|
||||
|
||||
export const toggleUserFocus = createAction(
|
||||
VIEW_PREFIX + '[toggle user focus in header',
|
||||
props<{show: boolean}>()
|
||||
);
|
||||
|
||||
@@ -13,6 +13,7 @@ import {TasksStopManyResponse} from '~/business-logic/model/tasks/tasksStopManyR
|
||||
import {EntityTypeEnum} from '~/shared/constants/non-common-consts';
|
||||
import {MetricColumn} from '@common/shared/utils/tableParamEncode';
|
||||
import {ProjectStatsGraphData} from '@common/core/reducers/projects.reducer';
|
||||
import {User} from '~/business-logic/model/users/user';
|
||||
|
||||
export const PROJECTS_PREFIX = '[ROOT_PROJECTS] ';
|
||||
|
||||
@@ -25,8 +26,6 @@ export const getAllSystemProjects = createAction(
|
||||
PROJECTS_PREFIX + 'GET_PROJECTS'
|
||||
);
|
||||
|
||||
|
||||
|
||||
export const updateProject = createAction(
|
||||
PROJECTS_PREFIX + 'UPDATE_PROJECT',
|
||||
props<{ id: string; changes: Partial<ProjectsUpdateRequest> }>()
|
||||
@@ -136,3 +135,24 @@ export const setGraphData = createAction(
|
||||
PROJECTS_PREFIX + '[set project stats]',
|
||||
props<{ stats: ProjectStatsGraphData[] }>()
|
||||
);
|
||||
|
||||
export const getProjectUsers = createAction(
|
||||
PROJECTS_PREFIX + '[get current project users]',
|
||||
props<{projectId: string}>()
|
||||
);
|
||||
export const setProjectUsers = createAction(
|
||||
PROJECTS_PREFIX + '[set current project users]',
|
||||
props<{users: User[]}>()
|
||||
);
|
||||
export const setAllProjectUsers = createAction(
|
||||
PROJECTS_PREFIX + '[set all projects users]',
|
||||
props<{users: User[]}>()
|
||||
);
|
||||
export const setProjectExtraUsers = createAction(
|
||||
PROJECTS_PREFIX + '[set extra users]',
|
||||
props<{users: User[]}>()
|
||||
);
|
||||
export const getFilteredUsers = createAction(
|
||||
PROJECTS_PREFIX + 'GET_FILTERED_USERS',
|
||||
props<{filteredUsers: string[]}>()
|
||||
);
|
||||
|
||||
@@ -15,6 +15,7 @@ import {EMPTY, of} from 'rxjs';
|
||||
import {SignResponse} from '@common/settings/admin/base-admin.service';
|
||||
import {S3AccessResolverComponent} from '@common/layout/s3-access-resolver/s3-access-resolver.component';
|
||||
import {MatDialog} from '@angular/material/dialog';
|
||||
import {setCredentialLabel} from '../actions/common-auth.actions';
|
||||
|
||||
@Injectable()
|
||||
export class CommonAuthEffects {
|
||||
@@ -74,6 +75,20 @@ export class CommonAuthEffects {
|
||||
))
|
||||
));
|
||||
|
||||
updateCredentialLabel = createEffect(() => this.actions.pipe(
|
||||
ofType(authActions.updateCredentialLabel),
|
||||
mergeMap(action => this.credentialsApi.authEditCredentials({access_key: action.credential.access_key, label: action.label}).pipe(
|
||||
mergeMap(() => [
|
||||
setCredentialLabel({credential: action.credential, label: action.label}),
|
||||
deactivateLoader(action.type)
|
||||
]),
|
||||
catchError(error => [
|
||||
requestFailed(error),
|
||||
setServerError(error, null, 'Unable to update credentials'),
|
||||
deactivateLoader(action.type)])
|
||||
))
|
||||
));
|
||||
|
||||
signUrl = createEffect(() => this.actions.pipe(
|
||||
ofType(authActions.getSignedUrl),
|
||||
filter(action => !!action.url),
|
||||
@@ -83,7 +98,7 @@ export class CommonAuthEffects {
|
||||
this.store.select(state => selectSignedUrl(action.url)(state))
|
||||
),
|
||||
switchMap(([, signedUrl]) =>
|
||||
(!signedUrl?.expires || signedUrl.expires < (new Date()).getTime()) ?
|
||||
(!signedUrl?.expires || signedUrl.expires < (new Date()).getTime() || action.config?.disableCache) ?
|
||||
this.adminService.signUrlIfNeeded(action.url, action.config) : of({type: 'none'})
|
||||
),
|
||||
filter(res => !!res),
|
||||
|
||||
@@ -3,18 +3,6 @@ import {Action, Store} from '@ngrx/store';
|
||||
import {Actions, createEffect, ofType} from '@ngrx/effects';
|
||||
import {ApiProjectsService} from '~/business-logic/api-services/projects.service';
|
||||
import * as actions from '../actions/projects.actions';
|
||||
import {
|
||||
fetchGraphData, getAllSystemProjects,
|
||||
getCompanyTags, getProjectsTags,
|
||||
getTags,
|
||||
openMoreInfoPopup,
|
||||
openTagColorsMenu, refetchProjects,
|
||||
resetProjects, resetProjectSelection,
|
||||
setCompanyTags,
|
||||
setGraphData, setLastUpdate,
|
||||
setTags
|
||||
} from '../actions/projects.actions';
|
||||
|
||||
import {catchError, filter, map, mergeMap, switchMap, withLatestFrom} from 'rxjs/operators';
|
||||
import {requestFailed} from '../actions/http.actions';
|
||||
import {activeLoader, deactivateLoader, setServerError} from '../actions/layout.actions';
|
||||
@@ -24,14 +12,18 @@ import {MatDialog} from '@angular/material/dialog';
|
||||
import {ApiOrganizationService} from '~/business-logic/api-services/organization.service';
|
||||
import {OrganizationGetTagsResponse} from '~/business-logic/model/organization/organizationGetTagsResponse';
|
||||
import {selectRouterParams} from '../reducers/router-reducer';
|
||||
import {forkJoin} from 'rxjs';
|
||||
import {forkJoin, of} from 'rxjs';
|
||||
import {ProjectsGetTaskTagsResponse} from '~/business-logic/model/projects/projectsGetTaskTagsResponse';
|
||||
import {ProjectsGetModelTagsResponse} from '~/business-logic/model/projects/projectsGetModelTagsResponse';
|
||||
import {selectLastUpdate, selectSelectedMetricVariantForCurrProject, selectSelectedProjectId} from '../reducers/projects.reducer';
|
||||
import {
|
||||
selectAllProjectsUsers,
|
||||
selectLastUpdate,
|
||||
selectSelectedMetricVariantForCurrProject,
|
||||
selectSelectedProjectId
|
||||
} from '../reducers/projects.reducer';
|
||||
import {OperationErrorDialogComponent} from '@common/shared/ui-components/overlay/operation-error-dialog/operation-error-dialog.component';
|
||||
import {ApiTasksService} from '~/business-logic/api-services/tasks.service';
|
||||
import {createMetricColumn} from '@common/shared/utils/tableParamEncode';
|
||||
import {get} from 'lodash/fp';
|
||||
import {ITask} from '~/business-logic/model/al-task';
|
||||
import {TasksGetAllExRequest} from '~/business-logic/model/tasks/tasksGetAllExRequest';
|
||||
import {setSelectedExperiments} from '../../experiments/actions/common-experiments-view.actions';
|
||||
@@ -39,6 +31,8 @@ import {selectShowHidden} from '~/features/projects/projects.reducer';
|
||||
import {setActiveWorkspace} from '@common/core/actions/users.actions';
|
||||
import {ProjectsGetAllExResponse} from '~/business-logic/model/projects/projectsGetAllExResponse';
|
||||
import {Project} from '~/business-logic/model/projects/project';
|
||||
import {ApiUsersService} from '~/business-logic/api-services/users.service';
|
||||
import { get } from 'lodash/fp';
|
||||
|
||||
export const ALL_PROJECTS_OBJECT = {id: '*', name: 'All Experiments'};
|
||||
|
||||
@@ -50,9 +44,9 @@ export class ProjectsEffects {
|
||||
|
||||
constructor(
|
||||
private actions$: Actions, private projectsApi: ApiProjectsService, private orgApi: ApiOrganizationService,
|
||||
private store: Store<any>, private dialog: MatDialog, private tasksApi: ApiTasksService
|
||||
) {
|
||||
}
|
||||
private store: Store<any>, private dialog: MatDialog, private tasksApi: ApiTasksService,
|
||||
private usersApi: ApiUsersService,
|
||||
) {}
|
||||
|
||||
activeLoader = createEffect(() => this.actions$.pipe(
|
||||
ofType(actions.setSelectedProjectId),
|
||||
@@ -77,9 +71,9 @@ export class ProjectsEffects {
|
||||
if (res.projects.length >= this.pageSize) {
|
||||
this.scrollId = res.scroll_id;
|
||||
this.lastUpdateSoFar = res.projects[res.projects.length - 1].last_update;
|
||||
resultsActions.push(getAllSystemProjects());
|
||||
resultsActions.push(actions.getAllSystemProjects());
|
||||
} else {
|
||||
resultsActions.push(setLastUpdate({lastUpdate: res.projects[res.projects.length - 1]?.last_update || this.lastUpdateSoFar || lastUpdate}));
|
||||
resultsActions.push(actions.setLastUpdate({lastUpdate: res.projects[res.projects.length - 1]?.last_update || this.lastUpdateSoFar || lastUpdate}));
|
||||
this.scrollId = null;
|
||||
this.lastUpdateSoFar = null;
|
||||
}
|
||||
@@ -91,11 +85,11 @@ export class ProjectsEffects {
|
||||
|
||||
resetProjects$ = createEffect(() => this.actions$.pipe(
|
||||
ofType(actions.resetSelectedProject),
|
||||
mergeMap(() => [resetProjectSelection()])
|
||||
mergeMap(() => [actions.resetProjectSelection()])
|
||||
));
|
||||
|
||||
resetProjectSelections$ = createEffect(() => this.actions$.pipe(
|
||||
ofType(resetProjectSelection),
|
||||
ofType(actions.resetProjectSelection),
|
||||
mergeMap(() => [setSelectedExperiments({experiments: []}), setSelectedModels({models: []})])
|
||||
));
|
||||
|
||||
@@ -117,7 +111,7 @@ export class ProjectsEffects {
|
||||
));
|
||||
|
||||
openTagColor = createEffect(() => this.actions$.pipe(
|
||||
ofType(openTagColorsMenu),
|
||||
ofType(actions.openTagColorsMenu),
|
||||
map(() => {
|
||||
this.dialog.open(TagColorMenuComponent);
|
||||
})
|
||||
@@ -125,29 +119,29 @@ export class ProjectsEffects {
|
||||
|
||||
//getAll but not projects'
|
||||
getAllTags = createEffect(() => this.actions$.pipe(
|
||||
ofType(getCompanyTags),
|
||||
ofType(actions.getCompanyTags),
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
switchMap(() => this.orgApi.organizationGetTags({include_system: true})
|
||||
.pipe(
|
||||
map((res: OrganizationGetTagsResponse) => setCompanyTags({tags: res.tags, systemTags: res.system_tags})),
|
||||
map((res: OrganizationGetTagsResponse) => actions.setCompanyTags({tags: res.tags, systemTags: res.system_tags})),
|
||||
catchError(error => [requestFailed(error)])
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
getProjectsTags = createEffect(() => this.actions$.pipe(
|
||||
ofType(getProjectsTags),
|
||||
ofType(actions.getProjectsTags),
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
switchMap(() => this.projectsApi.projectsGetProjectTags({filter: {system_tags: ['pipeline']}})
|
||||
.pipe(
|
||||
map((res: OrganizationGetTagsResponse) => setTags({tags: res.tags})),
|
||||
map((res: OrganizationGetTagsResponse) => actions.setTags({tags: res.tags})),
|
||||
catchError(error => [requestFailed(error)])
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
getTagsEffect = createEffect(() => this.actions$.pipe(
|
||||
ofType(getTags),
|
||||
ofType(actions.getTags),
|
||||
withLatestFrom(this.store.select(selectRouterParams).pipe(
|
||||
map(params => (params === null || params?.projectId === '*') ? [] : [params.projectId]))),
|
||||
switchMap(([action, projects]) => forkJoin([
|
||||
@@ -157,7 +151,7 @@ export class ProjectsEffects {
|
||||
map((res: [ProjectsGetTaskTagsResponse, ProjectsGetModelTagsResponse]) =>
|
||||
Array.from(new Set(res[0].tags.concat(res[1].tags))).sort()),
|
||||
mergeMap((tags: string[]) => [
|
||||
setTags({tags}),
|
||||
actions.setTags({tags}),
|
||||
deactivateLoader(action.type)
|
||||
]),
|
||||
catchError(error => [
|
||||
@@ -169,7 +163,7 @@ export class ProjectsEffects {
|
||||
));
|
||||
|
||||
openMoreInfoPopupEffect = createEffect(() => this.actions$.pipe(
|
||||
ofType(openMoreInfoPopup),
|
||||
ofType(actions.openMoreInfoPopup),
|
||||
switchMap(action => this.dialog.open(OperationErrorDialogComponent, {
|
||||
data: {
|
||||
title: `${action.operationName} ${action.entityType}`,
|
||||
@@ -181,7 +175,7 @@ export class ProjectsEffects {
|
||||
), {dispatch: false});
|
||||
|
||||
fetchProjectStats = createEffect(() => this.actions$.pipe(
|
||||
ofType(fetchGraphData),
|
||||
ofType(actions.fetchGraphData),
|
||||
withLatestFrom(
|
||||
this.store.select(selectSelectedProjectId),
|
||||
this.store.select(selectSelectedMetricVariantForCurrProject)
|
||||
@@ -204,7 +198,7 @@ export class ProjectsEffects {
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
} as unknown as TasksGetAllExRequest).pipe(
|
||||
map((res) =>
|
||||
setGraphData({
|
||||
actions.setGraphData({
|
||||
stats: res.tasks.map((task: ITask) => {
|
||||
const started = new Date(task.started).getTime();
|
||||
const end = started + (task.active_duration ?? 0) * 1000;
|
||||
@@ -224,12 +218,66 @@ export class ProjectsEffects {
|
||||
));
|
||||
|
||||
resetRootProjects = createEffect(() => this.actions$.pipe(
|
||||
ofType(setActiveWorkspace, refetchProjects),
|
||||
ofType(setActiveWorkspace, actions.refetchProjects),
|
||||
mergeMap(() => [
|
||||
resetProjects(),
|
||||
getAllSystemProjects()
|
||||
actions.resetProjects(),
|
||||
actions.getAllSystemProjects()
|
||||
])
|
||||
));
|
||||
|
||||
getAllProjectsUsersEffect = createEffect(() => this.actions$.pipe(
|
||||
ofType(actions.getAllSystemProjects),
|
||||
switchMap(() => this.usersApi.usersGetAllEx({
|
||||
order_by: ['name'],
|
||||
only_fields: ['name'],
|
||||
}, null, 'body', true).pipe(
|
||||
mergeMap(res => [actions.setAllProjectUsers(res)]),
|
||||
catchError(error => [
|
||||
requestFailed(error),
|
||||
setServerError(error, null, 'Fetch all projects users failed')]
|
||||
)
|
||||
))
|
||||
));
|
||||
|
||||
getUsersEffect = createEffect(() => this.actions$.pipe(
|
||||
ofType(actions.getProjectUsers),
|
||||
withLatestFrom(
|
||||
this.store.select(selectAllProjectsUsers)
|
||||
),
|
||||
switchMap(([action, all]) => (!action.projectId || action.projectId === '*' ?
|
||||
of({users: all}) :
|
||||
this.usersApi.usersGetAllEx({
|
||||
order_by: ['name'],
|
||||
only_fields: ['name'],
|
||||
active_in_projects: [action.projectId]
|
||||
}, null, 'body', true)).pipe(
|
||||
mergeMap(res => [actions.setProjectUsers(res)]),
|
||||
catchError(error => [
|
||||
requestFailed(error),
|
||||
setServerError(error, null, 'Fetch users failed')]
|
||||
)
|
||||
))
|
||||
));
|
||||
|
||||
getExtraUsersEffect = createEffect(() => this.actions$.pipe(
|
||||
ofType(actions.getFilteredUsers),
|
||||
switchMap(action => this.usersApi.usersGetAllEx({
|
||||
order_by: ['name'],
|
||||
only_fields: ['name'],
|
||||
id: action.filteredUsers || []
|
||||
}, null, 'body', true).pipe(
|
||||
mergeMap(res => [
|
||||
actions.setProjectExtraUsers(res),
|
||||
deactivateLoader(action.type)
|
||||
]),
|
||||
catchError(error => [
|
||||
requestFailed(error),
|
||||
deactivateLoader(action.type),
|
||||
setServerError(error, null, 'Fetch users failed')]
|
||||
)
|
||||
))
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,16 +2,16 @@ import {createSelector, on, ReducerTypes, select, Store} from '@ngrx/store';
|
||||
import {
|
||||
addCredential,
|
||||
cancelS3Credentials,
|
||||
removeCredential, resetCredential,
|
||||
removeCredential, removeSignedUrl, resetCredential,
|
||||
resetDontShowAgainForBucketEndpoint,
|
||||
saveS3Credentials, setSignedUrl,
|
||||
saveS3Credentials, setCredentialLabel, setSignedUrl,
|
||||
showLocalFilePopUp,
|
||||
updateAllCredentials,
|
||||
updateS3Credential
|
||||
} from '../actions/common-auth.actions';
|
||||
import {CredentialKey} from '~/business-logic/model/auth/credentialKey';
|
||||
import {inBucket} from '@common/settings/admin/base-admin.service';
|
||||
import {filter, map, timeoutWith} from 'rxjs/operators';
|
||||
import {filter, map, takeWhile, timeoutWith} from 'rxjs/operators';
|
||||
|
||||
export interface Credentials {
|
||||
Bucket?: string;
|
||||
@@ -68,7 +68,10 @@ export const getSignedUrlOrOrigin$ = (url: string, store: Store) => store.pipe(
|
||||
filter(signed => !!signed?.signed),
|
||||
map(signed => signed?.signed),
|
||||
timeoutWith(900, store.select(selectSignedUrl(url))
|
||||
.pipe(map(signed => signed?.signed || url))
|
||||
.pipe(
|
||||
takeWhile( signed => signed !== null),
|
||||
map(signed => signed?.signed || url)
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
@@ -103,7 +106,8 @@ export const commonAuthReducer = [
|
||||
}
|
||||
}),
|
||||
on(resetCredential, state => ({...state, newCredential: initAuth.newCredential})),
|
||||
on(addCredential, (state, action) => ({ ...state,
|
||||
on(addCredential, (state, action) => ({
|
||||
...state,
|
||||
newCredential: {...action.newCredential, company: action.workspaceId},
|
||||
credentials: {
|
||||
...state.credentials,
|
||||
@@ -111,6 +115,14 @@ export const commonAuthReducer = [
|
||||
...(state.credentials[action.workspaceId] || []),
|
||||
...(Object.keys(action.newCredential).length > 0 ? [{...action.newCredential, company: action.workspaceId}] : [])
|
||||
]}})),
|
||||
on(setCredentialLabel, (state, action) => ({
|
||||
...state,
|
||||
newCredential: {...state.newCredential, label: action.label},
|
||||
credentials: {
|
||||
...state.credentials,
|
||||
[action.credential.company]: state.credentials[action.credential.company]?.map(cred =>
|
||||
cred.access_key === action.credential.access_key ? {...action.credential, label: action.label} : cred)
|
||||
}})),
|
||||
on(removeCredential, (state, action) => ({ ...state, credentials: {
|
||||
...state.credentials,
|
||||
[action.workspaceId]: state.credentials[action.workspaceId].filter((cred => cred.access_key !== action.accessKey))
|
||||
@@ -119,4 +131,5 @@ export const commonAuthReducer = [
|
||||
...state,
|
||||
credentials: {[action.credentials[0]?.company || action.workspace]: action.credentials, ...action.extra}, revokeSucceed: false})),
|
||||
on(setSignedUrl, (state, action) => ({...state, signedUrls: {...state.signedUrls, [action.url]: {signed: action.signed, expires: action.expires}}})),
|
||||
on(removeSignedUrl, (state, action) => ({...state, signedUrls: {...state.signedUrls, [action.url]: null}})),
|
||||
] as ReducerTypes<AuthState, any>[];
|
||||
|
||||
@@ -7,6 +7,7 @@ import {ITableExperiment} from '../../experiments/shared/common-experiment-model
|
||||
import {MetricColumn} from '@common/shared/utils/tableParamEncode';
|
||||
import {sortByField} from '@common/tasks/tasks.utils';
|
||||
import {ProjectsGetAllResponseSingle} from '~/business-logic/model/projects/projectsGetAllResponseSingle';
|
||||
import {User} from '~/business-logic/model/users/user';
|
||||
|
||||
|
||||
export interface ProjectStatsGraphData {
|
||||
@@ -32,6 +33,9 @@ export interface RootProjects {
|
||||
graphVariant: { [project: string]: MetricColumn };
|
||||
graphData: ProjectStatsGraphData[];
|
||||
lastUpdate: string;
|
||||
users: User[];
|
||||
allUsers: User[];
|
||||
extraUsers: User[];
|
||||
}
|
||||
|
||||
const initRootProjects: RootProjects = {
|
||||
@@ -46,7 +50,10 @@ const initRootProjects: RootProjects = {
|
||||
tagsFilterByProject: true,
|
||||
graphVariant: {},
|
||||
graphData: null,
|
||||
lastUpdate: null
|
||||
lastUpdate: null,
|
||||
users: [],
|
||||
allUsers: [],
|
||||
extraUsers: [],
|
||||
};
|
||||
|
||||
export const projects = state => state.rootProjects as RootProjects;
|
||||
@@ -72,6 +79,11 @@ export const selectSelectedMetricVariantForCurrProject = createSelector(
|
||||
selectSelectedProjectsMetricVariant, selectSelectedProjectId,
|
||||
(projectsVariant, projectId) => projectsVariant[projectId]);
|
||||
export const selectGraphData = createSelector(projects, state => state.graphData);
|
||||
export const selectProjectUsers = createSelector(projects, state => state.extraUsers.length ?
|
||||
Array.from(new Set([...state.users, ...state.extraUsers])) :
|
||||
state.users
|
||||
);
|
||||
export const selectAllProjectsUsers = createSelector(projects, state => state.allUsers);
|
||||
|
||||
export const projectsReducer = createReducer(
|
||||
initRootProjects,
|
||||
@@ -101,12 +113,12 @@ export const projectsReducer = createReducer(
|
||||
graphData: initRootProjects.graphData,
|
||||
};
|
||||
}),
|
||||
on(projectsActions.setSelectedProject, (state, action) => ({...state, selectedProject: action.project})),
|
||||
on(projectsActions.setSelectedProject, (state, action) => ({...state, selectedProject: action.project, extraUsers: []})),
|
||||
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.resetSelectedProject, state => ({...state, selectedProject: initRootProjects.selectedProject, users: [], extraUsers: []})),
|
||||
on(projectsActions.updateProjectCompleted, (state, action) => ({
|
||||
...state,
|
||||
selectedProject: {...state.selectedProject, ...action.changes},
|
||||
@@ -124,4 +136,7 @@ export const projectsReducer = createReducer(
|
||||
})),
|
||||
on(projectsActions.setGraphData, (state, action) => ({...state, graphData: action.stats})),
|
||||
on(projectsActions.setLastUpdate, (state, action) => ({...state, lastUpdate: action.lastUpdate})),
|
||||
on(projectsActions.setProjectUsers, (state, action) => ({...state, users: action.users, extraUsers: []})),
|
||||
on(projectsActions.setAllProjectUsers, (state, action) => ({...state, allUsers: action.users})),
|
||||
on(projectsActions.setProjectExtraUsers, (state, action) => ({...state, extraUsers: action.users})),
|
||||
);
|
||||
|
||||
@@ -18,6 +18,7 @@ export interface ViewState {
|
||||
plotlyReady: boolean;
|
||||
aceReady: boolean;
|
||||
preferencesReady: boolean;
|
||||
showUserFocus: boolean;
|
||||
}
|
||||
|
||||
export const initViewState: ViewState = {
|
||||
@@ -36,7 +37,8 @@ export const initViewState: ViewState = {
|
||||
neverShowPopupAgain: [],
|
||||
plotlyReady: false,
|
||||
aceReady: false,
|
||||
preferencesReady: false
|
||||
preferencesReady: false,
|
||||
showUserFocus: false,
|
||||
};
|
||||
|
||||
export const views = state => state.views as ViewState;
|
||||
@@ -56,6 +58,7 @@ export const selectFirstLoginAt = createSelector(views, state => state.firstLogi
|
||||
export const selectPlotlyReady = createSelector(views, state => state.plotlyReady);
|
||||
export const selectAceReady = createSelector(views, state => state.aceReady);
|
||||
export const selectNeverShowPopups = createSelector(views, (state): string[] => state.neverShowPopupAgain);
|
||||
export const selectShowUserFocus = createSelector(views, state => state.showUserFocus);
|
||||
|
||||
|
||||
export const viewReducers = [
|
||||
@@ -90,6 +93,7 @@ export const viewReducers = [
|
||||
...state,
|
||||
neverShowPopupAgain: action.reset ? state.neverShowPopupAgain.filter(popups => popups !== action.popupId) : Array.from(new Set([...state.neverShowPopupAgain, action.popupId]))
|
||||
})),
|
||||
on(layoutActions.toggleUserFocus, (state, action) => ({...state, showUserFocus: action.show}))
|
||||
] as ReducerTypes<ViewState, any>[];
|
||||
|
||||
export const viewReducer = createReducer(
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {Action, createAction, props} from '@ngrx/store';
|
||||
import {ISmAction} from '../core/models/actions';
|
||||
import {SEARCH_ACTIONS} from './dashboard-search.consts';
|
||||
import {Project} from '../../business-logic/model/projects/project';
|
||||
import {Task} from '../../business-logic/model/tasks/task';
|
||||
import {Model} from '../../business-logic/model/models/model';
|
||||
import {Project} from '~/business-logic/model/projects/project';
|
||||
import {Task} from '~/business-logic/model/tasks/task';
|
||||
import {Model} from '~/business-logic/model/models/model';
|
||||
|
||||
|
||||
export const searchSetTerm = createAction(
|
||||
@@ -37,6 +37,16 @@ export const searchProjects = createAction(
|
||||
props<{query: string; regExp?: boolean}>()
|
||||
);
|
||||
|
||||
export const searchPipelines = createAction(
|
||||
SEARCH_ACTIONS.SEARCH_PIPELINES,
|
||||
props<{query: string; regExp?: boolean}>()
|
||||
);
|
||||
|
||||
export const setPipelinesResults = createAction(
|
||||
'Set Pipelines Results',
|
||||
props<{pipelines: Project[]}>()
|
||||
);
|
||||
|
||||
export class SetProjectsResults implements ISmAction {
|
||||
public type = SEARCH_ACTIONS.SET_PROJECTS;
|
||||
public payload: { projects: Array<Project> };
|
||||
|
||||
@@ -5,6 +5,7 @@ export const SEARCH_ACTIONS = {
|
||||
SET_TERM : SEARCH_PREFIX + 'SET_TERM',
|
||||
SEARCH_START : SEARCH_PREFIX + 'SEARCH_START',
|
||||
SEARCH_PROJECTS : SEARCH_PREFIX + 'SEARCH_PROJECTS',
|
||||
SEARCH_PIPELINES : SEARCH_PREFIX + 'SEARCH_PIPELINES',
|
||||
SEARCH_EXPERIMENTS: SEARCH_PREFIX + 'SEARCH_EXPERIMENTS',
|
||||
SEARCH_MODELS : SEARCH_PREFIX + 'SEARCH_MODELS',
|
||||
SEARCH_COMPLETE : SEARCH_PREFIX + 'SEARCH_COMPLETE',
|
||||
|
||||
@@ -2,7 +2,17 @@ import {Injectable} from '@angular/core';
|
||||
import {Actions, createEffect, ofType} from '@ngrx/effects';
|
||||
import {activeLoader, deactivateLoader} from '../core/actions/layout.actions';
|
||||
import {
|
||||
SearchActivate, SearchClear, searchExperiments, searchModels, searchProjects, searchSetTerm, searchStart, SetExperimentsResults, SetModelsResults, SetProjectsResults
|
||||
SearchActivate,
|
||||
SearchClear,
|
||||
searchExperiments,
|
||||
searchModels,
|
||||
searchPipelines,
|
||||
searchProjects,
|
||||
searchSetTerm,
|
||||
searchStart,
|
||||
SetExperimentsResults,
|
||||
SetModelsResults, setPipelinesResults,
|
||||
SetProjectsResults
|
||||
} from './dashboard-search.actions';
|
||||
import {EXPERIMENT_SEARCH_ONLY_FIELDS, SEARCH_ACTIONS, SEARCH_PAGE_SIZE} from './dashboard-search.consts';
|
||||
import {ApiProjectsService} from '~/business-logic/api-services/projects.service';
|
||||
@@ -28,7 +38,7 @@ export class DashboardSearchEffects {
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
activeLoader = createEffect(() => this.actions.pipe(
|
||||
ofType(SEARCH_ACTIONS.SEARCH_PROJECTS, SEARCH_ACTIONS.SEARCH_MODELS, SEARCH_ACTIONS.SEARCH_EXPERIMENTS),
|
||||
ofType(SEARCH_ACTIONS.SEARCH_PROJECTS, SEARCH_ACTIONS.SEARCH_MODELS, SEARCH_ACTIONS.SEARCH_EXPERIMENTS, SEARCH_ACTIONS.SEARCH_PIPELINES),
|
||||
map(action => activeLoader(action.type))
|
||||
));
|
||||
// add actions for each search
|
||||
@@ -43,6 +53,7 @@ export class DashboardSearchEffects {
|
||||
}
|
||||
actionsToFire.push(searchSetTerm(action));
|
||||
actionsToFire.push(searchProjects(action));
|
||||
actionsToFire.push(searchPipelines(action));
|
||||
actionsToFire.push(searchExperiments(action));
|
||||
actionsToFire.push(searchModels(action));
|
||||
return actionsToFire;
|
||||
@@ -56,16 +67,37 @@ export class DashboardSearchEffects {
|
||||
...(action.query && {pattern: action.regExp ? action.query : escapeRegex(action.query) + '[^/]*$'}),
|
||||
fields: ['name', 'id']
|
||||
},
|
||||
include_stats_filter: {system_tags: ['-pipeline']},
|
||||
stats_for_state: ProjectsGetAllExRequest.StatsForStateEnum.Active,
|
||||
scroll_id: null,
|
||||
size: SEARCH_PAGE_SIZE,
|
||||
include_stats: true,
|
||||
only_fields: ['name', 'company', 'user', 'created', 'default_output_destination']
|
||||
}).pipe(
|
||||
} as ProjectsGetAllExRequest).pipe(
|
||||
mergeMap(res => [new SetProjectsResults(res.projects), deactivateLoader(action.type)]),
|
||||
catchError(error => [deactivateLoader(action.type), requestFailed(error)])))
|
||||
));
|
||||
|
||||
searchPipelines = createEffect(() => this.actions.pipe(
|
||||
ofType(searchPipelines.type),
|
||||
switchMap((action: ReturnType<typeof searchPipelines>) => this.projectsApi.projectsGetAllEx({
|
||||
_any_: {
|
||||
...(action.query && {pattern: action.regExp ? action.query : escapeRegex(action.query) + '[^/]*$'}),
|
||||
fields: ['name', 'id']
|
||||
},
|
||||
search_hidden: true,
|
||||
shallow_search: false,
|
||||
system_tags: ['pipeline'],
|
||||
stats_for_state: ProjectsGetAllExRequest.StatsForStateEnum.Active,
|
||||
scroll_id: null,
|
||||
size: SEARCH_PAGE_SIZE,
|
||||
include_stats: true,
|
||||
only_fields: ['name', 'company', 'user', 'created', 'default_output_destination', 'tags', 'system_tags']
|
||||
} as ProjectsGetAllExRequest).pipe(
|
||||
mergeMap(res => [setPipelinesResults({pipelines:res.projects}), deactivateLoader(action.type)]),
|
||||
catchError(error => [deactivateLoader(action.type), requestFailed(error)])))
|
||||
));
|
||||
|
||||
searchModels = createEffect(() => this.actions.pipe(
|
||||
ofType(searchModels.type),
|
||||
switchMap((action: ReturnType<typeof searchModels>) => this.modelsApi.modelsGetAllEx({
|
||||
|
||||
@@ -4,13 +4,14 @@ import {Task} from '../../business-logic/model/tasks/task';
|
||||
import {createFeatureSelector, createSelector} from '@ngrx/store';
|
||||
import {SEARCH_ACTIONS} from './dashboard-search.consts';
|
||||
import {Model} from '../../business-logic/model/models/model';
|
||||
import {searchSetTerm} from './dashboard-search.actions';
|
||||
import {searchSetTerm, setPipelinesResults} from './dashboard-search.actions';
|
||||
import {ICommonSearchState} from '../common-search/common-search.reducer';
|
||||
|
||||
export interface ISearchState {
|
||||
projects: Project[];
|
||||
experiments: Task[];
|
||||
models: Model[];
|
||||
pipelines: Project[];
|
||||
users: User[];
|
||||
resultsCounter: number;
|
||||
term: ICommonSearchState['searchQuery'];
|
||||
@@ -20,14 +21,15 @@ export interface ISearchState {
|
||||
|
||||
|
||||
export const searchInitialState: ISearchState = {
|
||||
term : null,
|
||||
term: null,
|
||||
forceSearch: false,
|
||||
projects : [],
|
||||
users : [],
|
||||
experiments : [],
|
||||
models : [],
|
||||
projects: [],
|
||||
pipelines: [],
|
||||
users: [],
|
||||
experiments: [],
|
||||
models: [],
|
||||
resultsCounter: 0,
|
||||
active : false
|
||||
active: false
|
||||
};
|
||||
|
||||
export function dashboardSearchReducer<ActionReducer>(state: ISearchState = searchInitialState, action) {
|
||||
@@ -42,6 +44,8 @@ export function dashboardSearchReducer<ActionReducer>(state: ISearchState = sear
|
||||
}
|
||||
case SEARCH_ACTIONS.SET_PROJECTS:
|
||||
return {...state, projects: action.payload.projects, resultsCounter: state.resultsCounter + 1};
|
||||
case setPipelinesResults.type:
|
||||
return {...state, pipelines: action.pipelines, resultsCounter: state.resultsCounter + 1};
|
||||
case SEARCH_ACTIONS.SET_EXPERIMENTS:
|
||||
return {...state, experiments: action.payload.experiments, resultsCounter: state.resultsCounter + 1};
|
||||
case SEARCH_ACTIONS.SET_MODELS:
|
||||
@@ -60,10 +64,11 @@ export function dashboardSearchReducer<ActionReducer>(state: ISearchState = sear
|
||||
}
|
||||
|
||||
|
||||
export const selectSearch = createFeatureSelector<ISearchState>('search');
|
||||
export const selectProjectsResults = createSelector(selectSearch, (state: ISearchState): Array<Project> => state.projects);
|
||||
export const selectSearch = createFeatureSelector<ISearchState>('search');
|
||||
export const selectProjectsResults = createSelector(selectSearch, (state: ISearchState): Array<Project> => state.projects);
|
||||
export const selectExperimentsResults = createSelector(selectSearch, (state: ISearchState): Array<Task> => state.experiments);
|
||||
export const selectModelsResults = createSelector(selectSearch, (state: ISearchState): Array<Model> => state.models);
|
||||
export const selectActiveSearch = createSelector(selectSearch, (state: ISearchState): boolean => state.term?.query?.length >= 3 || state.forceSearch);
|
||||
export const selectSearchTerm = createSelector(selectSearch, (state: ISearchState) => state.term);
|
||||
export const selectResultsCounter = createSelector(selectSearch, (state: ISearchState): number => state.resultsCounter);
|
||||
export const selectModelsResults = createSelector(selectSearch, (state: ISearchState): Array<Model> => state.models);
|
||||
export const selectPipelinesResults = createSelector(selectSearch, (state: ISearchState): Array<Project> => state.pipelines);
|
||||
export const selectActiveSearch = createSelector(selectSearch, (state: ISearchState): boolean => state.term?.query?.length >= 3 || state.forceSearch);
|
||||
export const selectSearchTerm = createSelector(selectSearch, (state: ISearchState) => state.term);
|
||||
export const selectResultsCounter = createSelector(selectSearch, (state: ISearchState): number => state.resultsCounter);
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<div class="sm-card-list-layout">
|
||||
<sm-experiment-card
|
||||
*ngFor="let experiment of experimentsList"
|
||||
[experiment]="experiment"
|
||||
(experimentCardClicked)="experimentCardClicked($event)"
|
||||
></sm-experiment-card>
|
||||
</div>
|
||||
@@ -1,18 +0,0 @@
|
||||
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
||||
import {Task} from '../../../../business-logic/model/tasks/task';
|
||||
import {ITask} from '../../../../business-logic/model/al-task';
|
||||
|
||||
@Component({
|
||||
selector : 'sm-experiments-search-results',
|
||||
templateUrl: './experiments-search-results.component.html',
|
||||
styleUrls : ['./experiments-search-results.component.scss']
|
||||
})
|
||||
export class ExperimentsSearchResultsComponent {
|
||||
@Input() experimentsList: Array<Task>;
|
||||
@Output() experimentClicked = new EventEmitter<ITask>();
|
||||
|
||||
public experimentCardClicked(experiment: ITask) {
|
||||
this.experimentClicked.emit(experiment);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
<div class="sm-card-list-layout">
|
||||
<sm-model-card
|
||||
*ngFor="let model of modelsList"
|
||||
[model]="model"
|
||||
(modelCardClicked)="modelCardClicked($event)"
|
||||
></sm-model-card>
|
||||
</div>
|
||||
@@ -1,17 +0,0 @@
|
||||
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
||||
import {Model} from '../../../../business-logic/model/models/model';
|
||||
|
||||
@Component({
|
||||
selector : 'sm-models-search-results',
|
||||
templateUrl: './models-search-results.component.html',
|
||||
styleUrls : ['./models-search-results.component.scss']
|
||||
})
|
||||
export class ModelsSearchResultsComponent {
|
||||
@Input() modelsList: Array<Model>;
|
||||
@Output() modelClicked = new EventEmitter<Model>();
|
||||
|
||||
public modelCardClicked(model: Model) {
|
||||
this.modelClicked.emit(model);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
<div class="sm-card-list-layout">
|
||||
<sm-project-card
|
||||
*ngFor="let project of projectsList"
|
||||
[project]="project"
|
||||
(projectCardClicked)="projectCardClicked($event)"
|
||||
[hideMenu]="true"
|
||||
></sm-project-card>
|
||||
</div>
|
||||
@@ -1,17 +0,0 @@
|
||||
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
||||
import {Project} from '../../../../business-logic/model/projects/project';
|
||||
|
||||
@Component({
|
||||
selector : 'sm-projects-search-results',
|
||||
templateUrl: './projects-search-results.component.html',
|
||||
styleUrls : ['./projects-search-results.component.scss']
|
||||
})
|
||||
export class ProjectsSearchResultsComponent {
|
||||
@Input() projectsList: Array<Project>;
|
||||
@Output() projectClicked = new EventEmitter<Project>();
|
||||
|
||||
public projectCardClicked(project: Project) {
|
||||
this.projectClicked.emit(project);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<cdk-virtual-scroll-viewport class="card-container" [itemSize]="cardHeight + 32">
|
||||
<div class="card-row" [style.width.px]="rowWidth" *cdkVirtualFor="let row of resultRows$ | async">
|
||||
<ng-container *ngFor="let result of row; trackBy: trackById">
|
||||
<ng-container *ngTemplateOutlet="cardTemplate; context: {result}"></ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
@@ -0,0 +1,15 @@
|
||||
:host {
|
||||
.card-container {
|
||||
height: 100%;
|
||||
padding: 0 24px 24px;
|
||||
|
||||
.card-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, 352px);
|
||||
grid-gap: 32px 24px;
|
||||
margin: 0 auto;
|
||||
|
||||
padding: 16px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import {Component, EventEmitter, Input, Output, TemplateRef, ViewChild} from '@angular/core';
|
||||
import {BehaviorSubject, combineLatest, Observable} from 'rxjs';
|
||||
import {BreakpointObserver, BreakpointState} from '@angular/cdk/layout';
|
||||
import {map, take} from 'rxjs/operators';
|
||||
import {chunk} from 'lodash/fp';
|
||||
import {trackById} from '@common/shared/utils/forms-track-by';
|
||||
import {CdkVirtualScrollViewport} from '@angular/cdk/scrolling';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {selectScaleFactor} from '@common/core/reducers/view.reducer';
|
||||
|
||||
const SIDE_NAV_PLUS_PAD = 64 + 24 + 24;
|
||||
const CARD_WIDTH = 352;
|
||||
|
||||
@Component({
|
||||
selector: 'sm-search-results',
|
||||
templateUrl: './search-results.component.html',
|
||||
styleUrls: ['./search-results.component.scss']
|
||||
})
|
||||
export class SearchResultsComponent {
|
||||
private cardLayoutChange$: Observable<BreakpointState>;
|
||||
private results$ = new BehaviorSubject<any[]>(null);
|
||||
public resultRows$: Observable<any[][]>;
|
||||
public trackById = trackById;
|
||||
public rowWidth = 300;
|
||||
|
||||
@Input() cardTemplate: TemplateRef<any>;
|
||||
@Input() set results(results: any[]) {
|
||||
this.results$.next(results);
|
||||
this.viewPort?.scrollToIndex(0);
|
||||
}
|
||||
@Input() cardHeight = 246;
|
||||
@Output() resultClicked = new EventEmitter<any>();
|
||||
@ViewChild(CdkVirtualScrollViewport) viewPort : CdkVirtualScrollViewport;
|
||||
|
||||
constructor(private store: Store, private breakpointObserver: BreakpointObserver) {
|
||||
this.store.select(selectScaleFactor)
|
||||
.pipe(take(1), map(factor => 100 / factor))
|
||||
.subscribe(factor => {
|
||||
const points = {} as {[point: string]: number};
|
||||
[2,3,4,5,6].forEach(num =>
|
||||
points[`(min-width: ${num === 2 ? 0 : ((num - 2) * 24 + (num - 1) * CARD_WIDTH + SIDE_NAV_PLUS_PAD) * factor}px) and ` +
|
||||
`(max-width: ${num === 6 ? 20000 : ((num - 1) * 24 + num * CARD_WIDTH + SIDE_NAV_PLUS_PAD) * factor}px)`] = num);
|
||||
this.cardLayoutChange$ = breakpointObserver.observe(Object.keys(points));
|
||||
|
||||
this.resultRows$ = combineLatest([this.cardLayoutChange$, this.results$])
|
||||
.pipe(map(([match, results]) => {
|
||||
const point = Object.entries(match.breakpoints).find(([, val]) => val);
|
||||
const cards = point ? points[point[0]] - 1 : 3;
|
||||
this.rowWidth = cards * CARD_WIDTH + (cards - 1) * 24
|
||||
return chunk(cards, results);
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -38,12 +38,13 @@ export class CommonDashboardEffects {
|
||||
this.projectsApi.projectsGetAllEx({
|
||||
stats_for_state: ProjectsGetAllExRequest.StatsForStateEnum.Active,
|
||||
include_stats: true,
|
||||
include_stats_filter: {system_tags: ['-pipeline']},
|
||||
order_by: ['featured', '-last_update'],
|
||||
page: 0,
|
||||
page_size: CARDS_IN_ROW,
|
||||
active_users: (showOnlyUserWork ? [user.id] : null),
|
||||
only_fields: ['name', 'company', 'user', 'created', 'default_output_destination']
|
||||
}).pipe(
|
||||
} as ProjectsGetAllExRequest).pipe(
|
||||
mergeMap(({projects}) => [setRecentProjects({projects}), deactivateLoader(action.type)]),
|
||||
catchError(error => [deactivateLoader(action.type), requestFailed(error)])
|
||||
)
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import {InitSearch, ResetSearch} from '../common-search/common-search.actions';
|
||||
import {skip} from 'rxjs/operators';
|
||||
import {Model} from '../../business-logic/model/models/model';
|
||||
import {Model} from '~/business-logic/model/models/model';
|
||||
import {SearchDeactivate, searchStart} from '../dashboard-search/dashboard-search.actions';
|
||||
import {IRecentTask} from './common-dashboard.reducer';
|
||||
import {ITask} from '../../business-logic/model/al-task';
|
||||
import {ITask} from '~/business-logic/model/al-task';
|
||||
import {Observable} from 'rxjs';
|
||||
import {ICommonSearchState, selectSearchQuery} from '../common-search/common-search.reducer';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {
|
||||
selectActiveSearch, selectExperimentsResults, selectModelsResults, selectProjectsResults,
|
||||
selectActiveSearch, selectExperimentsResults, selectModelsResults, selectPipelinesResults, selectProjectsResults,
|
||||
selectResultsCounter,
|
||||
selectSearchTerm
|
||||
} from '../dashboard-search/dashboard-search.reducer';
|
||||
import {Project} from '../../business-logic/model/projects/project';
|
||||
import {Project} from '~/business-logic/model/projects/project';
|
||||
import {setSelectedProjectId} from '../core/actions/projects.actions';
|
||||
import {isExample} from '../shared/utils/shared-utils';
|
||||
import {ActiveSearchLink} from '~/features/dashboard/containers/dashboard-search/dashboard-search.component';
|
||||
|
||||
export abstract class DashboardSearchComponentBase {
|
||||
|
||||
abstract store;
|
||||
abstract router;
|
||||
public activeLink: string = 'projects';
|
||||
public activeLink = 'projects' as ActiveSearchLink;
|
||||
private searchSubs;
|
||||
public searchQuery$: Observable<ICommonSearchState['searchQuery']>;
|
||||
public activeSearch$: Observable<boolean>;
|
||||
@@ -29,12 +29,14 @@ export abstract class DashboardSearchComponentBase {
|
||||
public projectsResults$: Observable<Array<Project>>;
|
||||
public experimentsResults$: Observable<any>;
|
||||
public searchTerm$: Observable<ICommonSearchState['searchQuery']>;
|
||||
public pipelinesResults$: Observable<Project[]>;
|
||||
|
||||
constructor(store: Store<any>){
|
||||
this.searchQuery$ = store.select(selectSearchQuery);
|
||||
this.activeSearch$ = store.select(selectActiveSearch);
|
||||
this.resultsCounter$ = store.select(selectResultsCounter);
|
||||
this.modelsResults$ = store.select(selectModelsResults);
|
||||
this.pipelinesResults$ = store.select(selectPipelinesResults);
|
||||
this.projectsResults$ = store.select(selectProjectsResults);
|
||||
this.experimentsResults$ = store.select(selectExperimentsResults);
|
||||
this.searchTerm$ = store.select(selectSearchTerm);
|
||||
@@ -73,6 +75,10 @@ export abstract class DashboardSearchComponentBase {
|
||||
this.router.navigateByUrl(`projects/${project.id}`);
|
||||
this.store.dispatch(setSelectedProjectId({projectId: project.id, example: isExample(project)}));
|
||||
}
|
||||
pipelineSelected(project: Project) {
|
||||
this.router.navigateByUrl(`pipelines/${project.id}/experiments`);
|
||||
this.store.dispatch(setSelectedProjectId({projectId: project.id, example: isExample(project)}));
|
||||
}
|
||||
|
||||
public taskSelected(task: IRecentTask | ITask) {
|
||||
// TODO ADD task.id to route
|
||||
@@ -85,7 +91,7 @@ export abstract class DashboardSearchComponentBase {
|
||||
this.activeLink = activeLink;
|
||||
}
|
||||
|
||||
setFirstActiveLink(allResults, tabsIndexes: string[]) {
|
||||
setFirstActiveLink(allResults, tabsIndexes) {
|
||||
if (!(allResults[tabsIndexes.indexOf(this.activeLink)].length > 0)) {
|
||||
const firstTabIndex = allResults.findIndex(list => list.length > 0);
|
||||
if (firstTabIndex > -1) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<sm-table [tableData]="tasks"
|
||||
[columns]="cols"
|
||||
[rowHeight]="52"
|
||||
[selectionMode]="null"
|
||||
selectionMode="single"
|
||||
[scrollable]="true"
|
||||
(rowClicked)="onExperimentSelected($event)"
|
||||
>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<ng-container *ngIf="(source$ | async) as source">
|
||||
<div *ngIf="!isFailed" class="item snippet" [ngClass]="{'loading' : isLoading}">
|
||||
<div *ngIf="!isFailed" class="item-snippet" [ngClass]="{'loading' : isLoading}">
|
||||
<ng-container [ngSwitch]="type">
|
||||
<img
|
||||
*ngSwitchCase="'image'"
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
@import "../../shared/ui-components/styles/variables";
|
||||
|
||||
.item {
|
||||
.item-snippet {
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
margin-top: 8px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.snippet {
|
||||
border-radius: 3px;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
background: #efefef;
|
||||
@@ -35,13 +30,12 @@
|
||||
|
||||
.image-var {
|
||||
position: absolute;
|
||||
color: $blue-300;
|
||||
padding: 1px 5px 0px 5px;
|
||||
bottom: 1px;
|
||||
color: $blue-500;
|
||||
bottom: 2px;
|
||||
left: 50%;
|
||||
height: 20px;
|
||||
transform: translateX(-50%);
|
||||
width: 100%;
|
||||
width: calc(100% - 18px);
|
||||
}
|
||||
|
||||
.html-wrap {
|
||||
@@ -59,6 +53,9 @@
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
.html-snippet {
|
||||
border: 1px solid $blue-200;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
.toolbar {
|
||||
opacity: 1;
|
||||
@@ -70,7 +67,8 @@
|
||||
display: flex;
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
left: 16px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.5s, visibility 0.5s;
|
||||
@@ -78,8 +76,9 @@
|
||||
}
|
||||
|
||||
.clickable-icon {
|
||||
background-color: #efefef;
|
||||
padding: 4px 24px;
|
||||
background-color: $blue-400;
|
||||
color: $blue-50;
|
||||
padding: 6px 18px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import {isHtmlPage, isTextFileURL} from '../../shared/utils/shared-utils';
|
||||
import {IsAudioPipe} from '../../shared/pipes/is-audio.pipe';
|
||||
import {IsVideoPipe} from '../../shared/pipes/is-video.pipe';
|
||||
import {addMessage} from '../../core/actions/layout.actions';
|
||||
import {MESSAGES_SEVERITY} from '../../../app.constants';
|
||||
import {MESSAGES_SEVERITY} from '~/app.constants';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {ThemeEnum} from '../../experiments/shared/common-experiments.const';
|
||||
import {getSignedUrlOrOrigin$} from '../../core/reducers/common-auth-reducer';
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
<mat-panel-title> {{iteration.iter}}</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<ng-template matExpansionPanelContent>
|
||||
<div class="d-flex justify-content flex-wrap">
|
||||
<div class="d-flex justify-content flex-wrap sample-row">
|
||||
<sm-debug-image-snippet
|
||||
*ngFor="let frame of iteration.events; trackBy:trackFrame"
|
||||
[frame]="frame"
|
||||
(imageError)="imageUrlError({frame: frame, experimentId: experimentId})"
|
||||
(imageClicked)="imageClicked.emit({frame: frame, snippetKey: frame.key, frames: iterationEvents})">
|
||||
(imageError)="imageUrlError({frame, experimentId})"
|
||||
(imageClicked)="imageClicked.emit({frame})">
|
||||
</sm-debug-image-snippet>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
.mat-expansion-panel {
|
||||
width: 100%;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: unset;
|
||||
&:hover{
|
||||
background:transparent;
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
::ng-deep .mat-expansion-panel-content {
|
||||
.mat-expansion-panel-body {
|
||||
padding: 0 12px 48px 12px;
|
||||
padding: 0 0 12px 28px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,4 +100,8 @@
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.sample-row {
|
||||
gap: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {Component, Input, Output} from '@angular/core';
|
||||
import {EventEmitter} from '@angular/core';
|
||||
import {Iteration, Event} from '@common/debug-images/debug-images.component';
|
||||
|
||||
@Component({
|
||||
selector: 'sm-debug-images-view',
|
||||
@@ -10,26 +11,17 @@ export class DebugImagesViewComponent {
|
||||
|
||||
public trackKey = (index: number, item: any) => item.iter;
|
||||
public trackFrame = (index: number, item: any) => `${item?.key} ${item?.timestamp}`;
|
||||
public iterationEvents;
|
||||
|
||||
@Input() experimentId;
|
||||
@Input() isMergeIterations;
|
||||
@Input() title;
|
||||
private _iterations;
|
||||
@Input() set iterations(iters) {
|
||||
this._iterations = iters;
|
||||
this.iterationEvents = [];
|
||||
iters.forEach(iteration => this.iterationEvents.push(iteration.events));
|
||||
}
|
||||
get iterations() {
|
||||
return this._iterations;
|
||||
}
|
||||
@Input() iterations: Iteration[];
|
||||
@Input() isDarkTheme = false;
|
||||
@Output() imageClicked = new EventEmitter();
|
||||
@Output() refreshClicked = new EventEmitter();
|
||||
@Output() urlError = new EventEmitter();
|
||||
|
||||
public imageUrlError(data: { frame: string; experimentId: string }) {
|
||||
public imageUrlError(data: { frame: Event; experimentId: string }) {
|
||||
this.urlError.emit(data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,10 +59,10 @@
|
||||
[title]="experimentNames && experimentNames[experimentId]"
|
||||
[isMergeIterations]="mergeIterations"
|
||||
[isDarkTheme]="isDarkTheme"
|
||||
(imageClicked)="imageClicked($event)"
|
||||
(imageClicked)="imageClicked($event, experimentId)"
|
||||
(urlError)="urlError()"
|
||||
>
|
||||
</sm-debug-images-view>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {MatDialog} from '@angular/material/dialog';
|
||||
import * as debugActions from './debug-images-actions';
|
||||
import {fetchExperiments, getDebugImagesMetrics, resetDebugImages} from './debug-images-actions';
|
||||
import {
|
||||
ITaskOptionalMetrics,
|
||||
selectBeginningOfTime,
|
||||
selectDebugImages,
|
||||
selectNoMore,
|
||||
@@ -30,7 +31,7 @@ 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 {
|
||||
export interface Event {
|
||||
timestamp: number;
|
||||
type?: string;
|
||||
task?: string;
|
||||
@@ -43,7 +44,7 @@ interface Event {
|
||||
worker?: string;
|
||||
}
|
||||
|
||||
interface Iteration {
|
||||
export interface Iteration {
|
||||
events: Event[];
|
||||
iter: number;
|
||||
}
|
||||
@@ -85,8 +86,8 @@ export class DebugImagesComponent implements OnInit, OnDestroy {
|
||||
public allowAutorefresh: boolean = false;
|
||||
|
||||
public noMoreData$: Observable<boolean>;
|
||||
public optionalMetrics$: Observable<any>;
|
||||
public optionalMetrics: any;
|
||||
public optionalMetrics$: Observable<ITaskOptionalMetrics[]>;
|
||||
public optionalMetrics: {[experimentId: string]: string};
|
||||
public selectedMetrics: { [taskId: string]: string } = {};
|
||||
public beginningOfTime: any;
|
||||
private beginningOfTimeSubscription: Subscription;
|
||||
@@ -270,11 +271,8 @@ export class DebugImagesComponent implements OnInit, OnDestroy {
|
||||
// this.adminService.checkImgUrl(frame.oldSrc || frame.src);
|
||||
}
|
||||
|
||||
imageClicked({frame, frames}) {
|
||||
let iterationSnippets = [];
|
||||
Object.entries(frames).map(iteration => {
|
||||
iterationSnippets = iterationSnippets.concat(iteration[1]);
|
||||
});
|
||||
imageClicked({frame}, experimentId: string) {
|
||||
const iterationSnippets = this.debugImages?.[experimentId]?.data.map(iter => iter.events).flat();
|
||||
const sources = iterationSnippets.map(img => img.url);
|
||||
const index = iterationSnippets.findIndex(img => img.url === frame.url);
|
||||
this.dialog.open(ImageDisplayerComponent, {
|
||||
@@ -326,10 +324,6 @@ export class DebugImagesComponent implements OnInit, OnDestroy {
|
||||
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'));
|
||||
}
|
||||
|
||||
@@ -102,15 +102,15 @@ $extra-header-min-height: 50px;
|
||||
pre {
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
padding-right: 20px;
|
||||
padding-right: 30px;
|
||||
padding-left: 6px;
|
||||
|
||||
.extend-toggle {
|
||||
visibility: hidden;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 5px;
|
||||
right: 6px;
|
||||
top: 6px;
|
||||
color: $blue-500;
|
||||
}
|
||||
}
|
||||
@@ -144,6 +144,14 @@ $extra-header-min-height: 50px;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
&.hovered {
|
||||
box-shadow: 0 0 0 1px $blue-250 inset;
|
||||
|
||||
&.diff-row {
|
||||
box-shadow: 0 0 0 1px lighten($strong-red, 20%) inset, 0 0 0 2px white inset;
|
||||
}
|
||||
}
|
||||
|
||||
&.selected-diff {
|
||||
background-color: $faint-gray;
|
||||
position: relative;
|
||||
@@ -197,6 +205,14 @@ $extra-header-min-height: 50px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&.hovered {
|
||||
box-shadow: 0 0 0 1px $blue-250 inset;
|
||||
|
||||
&.al-danger {
|
||||
box-shadow: 0 0 0 1px lighten($strong-red, 20%) inset, 0 0 0 2px white inset;
|
||||
}
|
||||
}
|
||||
|
||||
&.selected-diff {
|
||||
background-color: $faint-gray;
|
||||
position: relative;
|
||||
|
||||
@@ -54,6 +54,7 @@ export abstract class ExperimentCompareBase extends ExperimentCompareDetailsBase
|
||||
public originalExperiments: { [id: string]: IExperimentDetail | ExperimentParams } = {};
|
||||
public allPathsDiffs: any = {};
|
||||
public selectedPath: string = null;
|
||||
public hoveredPath: string = null;
|
||||
private selectedPathIndex: number = -1;
|
||||
public onlyDiffsPaths: string[];
|
||||
|
||||
@@ -462,12 +463,17 @@ export abstract class ExperimentCompareBase extends ExperimentCompareDetailsBase
|
||||
}
|
||||
}
|
||||
|
||||
rowHovered(path) {
|
||||
this.hoveredPath = path;
|
||||
}
|
||||
|
||||
keyClicked(data) {
|
||||
const path = data.path;
|
||||
this.selectedPathClicked(path);
|
||||
}
|
||||
|
||||
checkIfSelectedPath = (data: any) => this.selectedPath === (data.path);
|
||||
checkIfSelectedPath = (data: any) => this.selectedPath === data.path;
|
||||
checkIfHoveredPath = (data: any) => this.hoveredPath === data.path;
|
||||
|
||||
// checkIfFoundPathPath = (data: any) => this.foundPath === (data.path);
|
||||
|
||||
|
||||
@@ -23,26 +23,32 @@
|
||||
<cdk-virtual-scroll-viewport #virtualScrollRef class="virtual-scroll-container" [class.is-not-origin]="i > 0" itemSize="28" minBufferPx="280" maxBufferPx="560">
|
||||
<ng-container *cdkVirtualFor="let node of dataSource; let i = index">
|
||||
<div class="node" [class.parent]="node.hasChildren">
|
||||
<div *ngIf="node.hasChildren" class="section" [ngClass]="'depth-' + node.level">
|
||||
<div *ngIf="node.hasChildren" class="section" [ngClass]="'depth-' + node.level" (mouseenter)="rowHovered(node.data?.path)">
|
||||
<div class="content"
|
||||
(click)="toggleNode(node)"
|
||||
[ngClass]="node?.data?.classStyle"
|
||||
[class.selected-diff]="checkIfSelectedPath(node.data)"
|
||||
[class.hovered]="checkIfHoveredPath(node.data)"
|
||||
[class.identical-row]="!allPaths[node.data.path]">
|
||||
<i class="fas" [style.margin-left.px]="2 + node.level * 20" [ngClass]="treeControl.isExpanded(node) ? 'fa-chevron-down' : 'fa-chevron-right'"></i>
|
||||
<span
|
||||
class="title-key"
|
||||
[smTooltip]="(renameMap[node.data.key] || node.data.key) | hideHashTitle"
|
||||
[matTooltipShowDelay]="500"
|
||||
matTooltipPosition="above"
|
||||
smShowTooltipIfEllipsis
|
||||
[class.ellipsis]="showEllipsis"
|
||||
[style.width.px]="showEllipsis ? nativeWidth - 45 - node.level * 20 : null"
|
||||
[style.width.px]="showEllipsis ? nativeWidth - 65 - node.level * 20 : null"
|
||||
>{{(renameMap[node.data.key] || node.data.key) | hideHashTitle}}</span>
|
||||
<i *ngIf="node.data.tooltip" class="al-icon sm al-ico-description node-icon" customClass="hyper-parameters-tooltip" [smTooltip]="node.data.tooltip"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="!node.hasChildren" class="section" (click)="keyClicked(node.data)">
|
||||
<div *ngIf="!node.hasChildren" class="section" (click)="keyClicked(node.data)" (mouseenter)="rowHovered(node.data?.path)">
|
||||
<div [style.padding-left.px]="2 + node.level * 20" [ngClass]="{
|
||||
'node-item-container': true,
|
||||
'identical-row': checkIfIdenticalRow(node.data),
|
||||
'selected-diff': checkIfSelectedPath(node.data),
|
||||
'hovered': checkIfHoveredPath(node.data),
|
||||
'not-existing-on-origin': !node.data.existOnOrigin,
|
||||
'not-existing-on-compared': !node.data.existOnCompared,
|
||||
'diff-row': !node.data.isValueEqualToOrigin,
|
||||
@@ -51,9 +57,9 @@
|
||||
}">
|
||||
<div>
|
||||
<pre *ngIf="(node.data.value !== undefined) || (node.data.existOnOrigin && node.data.existOnCompared)"
|
||||
[class.no-ellipsis]="((node.data.key | hideHash) + node.data.value).length < 51"
|
||||
[class.no-ellipsis]="((node.data.key | hideHash) + node.data.value).length < 45"
|
||||
[class.with-ellipsis]="showEllipsis"
|
||||
[style.width.px]="showEllipsis ? nativeWidth - 45 - node.level * 20 : null"
|
||||
[style.width.px]="showEllipsis ? nativeWidth - 55 - node.level * 20 : null"
|
||||
>{{node.data.key | hideHash}}{{node.data.value}}<i class="al-icon sm al-ico-line-expand extend-toggle" [class.fa-rotate-180]="!showEllipsis" (click)="toggleEllipsis(); $event.stopPropagation()"></i>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
@@ -45,6 +45,6 @@ $list-width: 300px;
|
||||
}
|
||||
|
||||
sm-experiment-graphs {
|
||||
height: calc(100% - 64px);
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,8 @@ export class ExperimentCompareScalarChartsComponent implements OnInit, OnDestroy
|
||||
private changeDetection: ChangeDetectorRef,
|
||||
private refresh: RefreshService
|
||||
) {
|
||||
this.listOfHidden = this.store.pipe(select(selectSelectedSettingsHiddenScalar));
|
||||
this.listOfHidden = this.store.select(selectSelectedSettingsHiddenScalar)
|
||||
.pipe(distinctUntilChanged(isEqual));
|
||||
this.searchTerm$ = this.store.pipe(select(selectExperimentMetricsSearchTerm));
|
||||
this.smoothWeight$ = this.store.select(selectCompareSelectedSettingsSmoothWeight);
|
||||
this.xAxisType$ = this.store.select(selectCompareSelectedSettingsxAxisType);
|
||||
@@ -161,7 +162,7 @@ export class ExperimentCompareScalarChartsComponent implements OnInit, OnDestroy
|
||||
}
|
||||
|
||||
searchTermChanged(searchTerm: string) {
|
||||
this.store.dispatch(new SetExperimentMetricsSearchTerm({searchTerm: searchTerm}));
|
||||
this.store.dispatch(new SetExperimentMetricsSearchTerm({searchTerm}));
|
||||
}
|
||||
|
||||
resetMetrics() {
|
||||
|
||||
@@ -9,17 +9,26 @@
|
||||
</sm-experiment-compare-general-data>
|
||||
</div>
|
||||
<div class="tree-card-body" smSyncScroll *compareCardBody="let experiment; let i = index;">
|
||||
<div *ngFor="let node of comparedTasks[i]" class="node" [ngClass]="node?.metaData?.classStyle">
|
||||
<div class="content" (click)="collapsedToggled(node)">
|
||||
<i class="fas" [style.margin-left.px]="2 + node.level * 20" [ngClass]="realIsNodeOpen(node) ? 'fa-chevron-down' : 'fa-chevron-right'"></i>
|
||||
{{node.data.key}}
|
||||
<div *ngFor="let node of comparedTasks[i]"
|
||||
class="node"
|
||||
[ngClass]="node?.metaData?.classStyle"
|
||||
[class.hovered]="!hoveredTable && hoveredRow === node.data.key"
|
||||
(mouseenter)="onRowHovered(node.data?.key, null)">
|
||||
<div class="content"
|
||||
(click)="collapsedToggled(node)"
|
||||
(mouseenter)="onRowHovered(node.data?.key, null)">
|
||||
<i class="node-chevron fas" [style.margin-left.px]="2 + node.level * 20" [ngClass]="realIsNodeOpen(node) ? 'fa-chevron-down' : 'fa-chevron-right'"></i>
|
||||
<span class="node-key">{{node.data.key}}</span>
|
||||
</div>
|
||||
<al-table-diff *ngIf="realIsNodeOpen(node)"
|
||||
[valueTitle]="valuesMode.name | uppercase" keyTitle="VARIANT"
|
||||
[isOrigin]="i===0"
|
||||
(sortByChanged)="metricSortChanged($event,node.data)"
|
||||
[sortConfig]="(sortOrder$| async)[node.data.key] || {order:'asc', keyOrValue: 'key'}"
|
||||
[keyValueArray]="node.data | getKeyValueArrayPipe:allKeysEmptyObject:(sortOrder$| async)[node.data.key]:valuesMode.key">
|
||||
[keyValueArray]="node.data | getKeyValueArrayPipe:allKeysEmptyObject:(sortOrder$| async)[node.data.key]:valuesMode.key"
|
||||
[hoveredRow]="hoveredTable === node.data.key && hoveredRow"
|
||||
(sortByChanged)="metricSortChanged($event,node.data)"
|
||||
(rowHovered)="onRowHovered($event, node.data.key)"
|
||||
>
|
||||
<ng-template
|
||||
let-metricValue>
|
||||
{{metricValue}}
|
||||
|
||||
@@ -40,14 +40,39 @@
|
||||
position: relative;
|
||||
display: block;
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
content: " ";
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
.content {
|
||||
background: #f2f3f6;
|
||||
pointer-events: none;
|
||||
|
||||
.node-key, .node-chevron {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
al-table-diff {
|
||||
display: block;
|
||||
position: relative;
|
||||
pointer-events: none;
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
content: " ";
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: #f2f3f6;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.hovered {
|
||||
.content, &:after {
|
||||
|
||||
box-shadow: 0 0 0 1px $blue-250 inset;
|
||||
|
||||
&.diff-row {
|
||||
box-shadow: 0 0 0 1px lighten($strong-red, 20%) inset, 0 0 0 2px white inset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@ export class ExperimentCompareMetricValuesComponent implements OnInit, OnDestroy
|
||||
public experiments = [];
|
||||
private taskIds: string;
|
||||
public valuesMode: ValueMode;
|
||||
public hoveredRow: string;
|
||||
public hoveredTable: string;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
@@ -149,6 +151,7 @@ export class ExperimentCompareMetricValuesComponent implements OnInit, OnDestroy
|
||||
} else {
|
||||
this.paths = this.paths.filter(path => path !== node.data.key);
|
||||
}
|
||||
this.hoveredRow = node.data.key;
|
||||
}
|
||||
|
||||
metricSortChanged(event, nodeData) {
|
||||
@@ -194,4 +197,9 @@ export class ExperimentCompareMetricValuesComponent implements OnInit, OnDestroy
|
||||
copyIdToClipboard() {
|
||||
this.store.dispatch(addMessage('success', 'Copied to clipboard'));
|
||||
}
|
||||
|
||||
onRowHovered(tableKey: string, tableName: string) {
|
||||
this.hoveredRow = tableKey;
|
||||
this.hoveredTable = tableName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,21 +22,23 @@
|
||||
<cdk-virtual-scroll-viewport #virtualScrollRef class="virtual-scroll-container" [class.is-not-origin]="i > 0" itemSize="28" minBufferPx="280" maxBufferPx="560">
|
||||
<ng-container *cdkVirtualFor="let node of dataSource; let i = index">
|
||||
<div class="node" [class.parent]="node.hasChildren">
|
||||
<div *ngIf="node.hasChildren" class="section" [ngClass]="'depth-' + node.level">
|
||||
<div *ngIf="node.hasChildren" class="section" [ngClass]="'depth-' + node.level" (mouseenter)="rowHovered(node.data?.path)">
|
||||
<div class="content"
|
||||
(click)="toggleNode(node)"
|
||||
[ngClass]="node?.data?.classStyle"
|
||||
[class.selected-diff]="checkIfSelectedPath(node.data)"
|
||||
[class.hovered]="checkIfHoveredPath(node.data)"
|
||||
[class.identical-row]="!allPaths[node.data.path]">
|
||||
<i class="fas" [style.margin-left.px]="2 + node.level * 20" [ngClass]="treeControl.isExpanded(node) ? 'fa-chevron-down' : 'fa-chevron-right'"></i>
|
||||
<span class="title-key" [class.ellipsis]="showEllipsis">{{(renameMap[node.data.key] || node.data.key)}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="!node.hasChildren" class="section" (click)="keyClicked(node.data)">
|
||||
<div *ngIf="!node.hasChildren" class="section" (click)="keyClicked(node.data)" (mouseenter)="rowHovered(node.data?.path)">
|
||||
<div [style.padding-left.px]="2 + node.level * 20" [ngClass]="{
|
||||
'node-item-container': true,
|
||||
'identical-row': checkIfIdenticalRow(node.data),
|
||||
'selected-diff': checkIfSelectedPath(node.data),
|
||||
'hovered': checkIfHoveredPath(node.data),
|
||||
'not-existing-on-origin': !node.data.existOnOrigin,
|
||||
'not-existing-on-compared': !node.data.existOnCompared,
|
||||
'diff-row': !node.data.isValueEqualToOrigin,
|
||||
@@ -50,7 +52,7 @@
|
||||
>{{node.data.key}}<i class="al-icon sm al-ico-line-expand extend-toggle" [class.fa-rotate-180]="!showEllipsis" (click)="toggleEllipsis(); $event.stopPropagation()"></i>
|
||||
</pre>
|
||||
<pre class="node-val"
|
||||
[style.width]="showEllipsis ? (section.clientWidth - 250) + 'px' : null"
|
||||
[style.width]="showEllipsis ? (section.clientWidth - 320) + 'px' : null"
|
||||
[class.no-ellipsis]="node.data.value.length < 15"
|
||||
[class.ellipsis]="showEllipsis">{{node.data.value}}<i class="al-icon sm al-ico-line-expand extend-toggle" [class.fa-rotate-180]="!showEllipsis" (click)="toggleEllipsis(); $event.stopPropagation()"></i>
|
||||
</pre>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
justify-content: flex-start;
|
||||
|
||||
.node-key {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
@@ -33,6 +34,7 @@
|
||||
}
|
||||
|
||||
.node-val {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user