Release v1.12 (#61)

Co-authored-by: shallegro <shay@allego.ai>
This commit is contained in:
shyallegro
2023-08-02 15:35:38 +03:00
committed by GitHub
parent 37ee7b5c7e
commit aa038f4f82
482 changed files with 8743 additions and 6056 deletions

View File

@@ -60,15 +60,12 @@
"@aws-crypto/crc32c",
"filesize/lib/filesize.es6",
"hex-rgb",
"britecharts/dist/umd/donut.min",
"britecharts/dist/umd/legend.min",
"britecharts/dist/umd/line.min",
"britecharts/dist/umd/tooltip.min",
"britecharts/dist/umd/miniTooltip.min",
"britecharts/dist/umd/scatterPlot.min",
"britecharts",
"localforage",
"dom-to-image",
"ace-builds"
"ace-builds",
"hocon-parser",
"taira"
],
"vendorChunk": true,
"extractLicenses": false,

3421
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "clearml-webapp",
"version": "1.11.0",
"version": "1.12.0",
"license": "",
"scripts": {
"ng": "ng",
@@ -32,8 +32,8 @@
"@angular/router": "^15.2.8",
"@angular/service-worker": "^15.2.8",
"@angular/youtube-player": "^15.2.8",
"@aws-sdk/client-s3": "^3.317.0",
"@aws-sdk/s3-request-presigner": "^3.317.0",
"@aws-sdk/client-s3": "^3.360.0",
"@aws-sdk/s3-request-presigner": "^3.360.0",
"@ctrl/ngx-github-buttons": "^8.0.0",
"@ngneat/dag": "^2.0.0",
"@ngrx/effects": "^15.4.0",
@@ -44,11 +44,14 @@
"angular-google-tag-manager": "^1.7.0",
"angular-resizable-element": "^7.0.2",
"angular-split": "^15.0.0",
"angular2-csv": "^0.2.9",
"ansi-to-html": "^0.7.2",
"bootstrap": "^5.2.3",
"britecharts": "^2.18.0",
"britecharts": "^3.0.0-alpha-6.1.6",
"curved-arrows": "^0.1.0",
"d3-selection": "^3.0.0",
"d3-transition": "^3.0.1",
"d3-zoom": "^3.0.0",
"diff": "^5.1.0",
"dom-to-image": "^2.6.0",
"filesize": "^10.0.7",
@@ -61,7 +64,7 @@
"ngx-clipboard": "^16.0.0",
"ngx-color-picker": "^14.0.0",
"ngx-device-detector": "^5.0.1",
"ngx-markdown-editor": "^5.3.0",
"ngx-markdown-editor": "^5.3.1",
"ngx-print": "^1.3.1",
"ngx-window-token": "^7.0.0",
"object-hash": "^3.0.0",
@@ -70,6 +73,7 @@
"process": "^0.11.10",
"rxjs": "^7.8.0",
"string-to-color": "^2.2.2",
"taira": "^3.2.2",
"tinycolor2": "^1.6.0",
"tslib": "^2.5.0",
"url": "^0.11.0",
@@ -96,7 +100,7 @@
"@types/lodash-es": "^4.17.7",
"@types/node": "^18.16.0",
"@types/plotly.js": "^2.12.18",
"@types/uuid": "^9.0.1",
"@types/uuid": "^9.0.2",
"@typescript-eslint/eslint-plugin": "^5.59.1",
"@typescript-eslint/parser": "^5.59.1",
"codelyzer": "^6.0.2",
@@ -106,4 +110,4 @@
"eslint-plugin-prefer-arrow": "1.2.3",
"typescript": "~4.9.5"
}
}
}

View File

@@ -1,7 +1,7 @@
const fs = require('fs');
const targets = [
'https://demoapi.trains.allegro.ai', // 1
'https://api.trains-master.hosted.allegro.ai', // 1
];
const PROXY_CONFIG = {

View File

@@ -135,7 +135,7 @@ export class AppComponent implements OnInit, OnDestroy {
pageName: item.url
};
this.gtmService?.pushTag(gtmTag);
this.store.dispatch(new routerActions.NavigationEnd());
this.store.dispatch(routerActions.navigationEnd());
});
this.selectedCurrentUserSubscription = this.selectedCurrentUser$.pipe(

View File

@@ -1,4 +1,4 @@
import {Action} from '@ngrx/store';
import {createAction} from '@ngrx/store';
import {Environment} from '../environments/base';
export const NA = 'N/A';
@@ -19,12 +19,14 @@ export const BASE_REGEX = {
FOLDER : '\\/\\S*[^\\/ ]',
S3_BUCKET_NAME : '(?!(xn--|.+-s3alias$|.*\\.{2}.*))[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]',
GS_BUCKET_NAME : '(\\w[A-Za-z0-9\\-_]+\\w\\.)*\\w[A-Za-z0-9\\-_]+\\w',
AZURE_BUCKET_NAME: '(\\w[A-Za-z0-9\\-_]+\\w\\.)*\\w[A-Za-z0-9\\-_]+\\w'
AZURE_BUCKET_NAME: '(\\w[A-Za-z0-9\\-_]+\\w\\.)*\\w[A-Za-z0-9\\-_]+\\w',
AZURE_CONTAINER: '\\/[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]'
};
export const URI_REGEX = {
S3_WITH_BUCKET : BASE_REGEX.S3_PROTOCOL + BASE_REGEX.S3_BUCKET_NAME + BASE_REGEX.PATH,
GS_WITH_BUCKET : BASE_REGEX.GS_PROTOCOL + BASE_REGEX.GS_BUCKET_NAME + BASE_REGEX.PATH,
AZURE_WITH_BUCKET: BASE_REGEX.AZURE_PROTOCOL + BASE_REGEX.AZURE_BUCKET_NAME + BASE_REGEX.AZURE_CONTAINER,
S3_WITH_BUCKET_AND_HOST : BASE_REGEX.S3_PROTOCOL + BASE_REGEX.S3_BUCKET_NAME + BASE_REGEX.DOMAIN + BASE_REGEX.PATH,
GS_WITH_BUCKET_AND_HOST : BASE_REGEX.GS_PROTOCOL + BASE_REGEX.GS_BUCKET_NAME + BASE_REGEX.DOMAIN + BASE_REGEX.PATH,
AZURE_WITH_BUCKET_AND_HOST: BASE_REGEX.AZURE_PROTOCOL + BASE_REGEX.AZURE_BUCKET_NAME + BASE_REGEX.DOMAIN + BASE_REGEX.PATH,
@@ -52,13 +54,6 @@ export const TASK_TYPES = {
TESTING : 'testing',
};
const recentTasksPrefix = 'RECENT_TASKS';
export const RECENT_TASKS_ACTIONS = {
GET_RECENT_TASKS: recentTasksPrefix + 'GET_RECENT_TASKS',
SET_RECENT_TASKS: recentTasksPrefix + 'SET_RECENT_TASKS'
};
export const VIEW_PREFIX = 'VIEW_';
export type MediaContentTypeEnum = 'image/bmp' | 'image/jpeg' | 'image/png' | 'video/mp4';
@@ -75,23 +70,8 @@ export const MESSAGES_SEVERITY = {
};
export const USERS_PREFIX = 'USERS_';
export const USERS_ACTIONS = {
FETCH_CURRENT_USER: USERS_PREFIX + 'FETCH_USER',
SET_CURRENT_USER : USERS_PREFIX + 'SET_CURRENT_USER',
LOGOUT_SUCCESS : USERS_PREFIX + 'LOGOUT_SUCCESS',
LOGOUT : USERS_PREFIX + 'LOGOUT',
SET_PREF : USERS_PREFIX + 'SET_PREF'
};
export const NAVIGATION_PREFIX = 'NAVIGATION_';
export const NAVIGATION_ACTIONS = {
NAVIGATE_TO : NAVIGATION_PREFIX + 'NAVIGATE_TO',
NAVIGATION_END : NAVIGATION_PREFIX + 'NAVIGATION_END',
SET_ROUTER_SEGMENT : NAVIGATION_PREFIX + 'SET_ROUTER_SEGMENT',
UPDATATE_CURRENT_URL_WITHOUT_NAVIGATING: NAVIGATION_PREFIX + 'UPDATATE_CURRENT_URL_WITHOUT_NAVIGATING',
NAVIGATION_SKIPPED : NAVIGATION_PREFIX + 'NAVIGATION_SKIPPED',
};
export const guessAPIServerURL = () => {
const url = window.location.origin;
@@ -145,8 +125,7 @@ export const updateHttpUrlBaseConstant = (_environment: Environment) => {
export const HTTP_PREFIX = 'HTTP_';
export class EmptyAction implements Action {
readonly type = 'EMPTY_ACTION';
}
export const emptyAction = createAction('EMPTY_ACTION');
export const AUTO_REFRESH_INTERVAL = 10 * 1000;

View File

@@ -1,5 +1,5 @@
import {Routes} from '@angular/router';
import {ProjectRedirectGuardGuard} from '@common/shared/guards/project-redirect.guard';
import {projectRedirectGuardGuard} from '@common/shared/guards/project-redirect.guard';
import {EntityTypeEnum} from '~/shared/constants/non-common-consts';
@@ -31,7 +31,7 @@ export const routes: Routes = [
path: ':projectId',
data: {search: true},
children: [
{path: '', pathMatch: 'full', children: [], canActivate: [ProjectRedirectGuardGuard]},
{path: '', pathMatch: 'full', children: [], canActivate: [projectRedirectGuardGuard]},
{path: '', redirectTo: '*', pathMatch: 'full'},
{path: 'overview', loadChildren: () => import('./webapp-common/project-info/project-info.module').then(m => m.ProjectInfoModule)},
{path: 'projects', loadChildren: () => import('./features/projects/projects.module').then(m => m.ProjectsModule)},
@@ -66,7 +66,8 @@ export const routes: Routes = [
path: ':projectId',
children: [
{path: 'pipelines', loadChildren: () => import('@common/pipelines/pipelines.module').then(m => m.PipelinesModule)},
{path: 'projects', loadChildren: () => import('@common/nested-project-view/nested-project-view.module').then(m => m.NestedProjectViewModule)},
{path: 'projects', loadComponent: () => import('@common/pipelines/nested-pipeline-page/nested-pipeline-page.component')
.then(m => m.NestedPipelinePageComponent)},
{
path: 'experiments', loadChildren: () => import('@common/pipelines-controller/pipelines-controller.module').then(m => m.PipelinesControllerModule)
},

View File

@@ -30,6 +30,7 @@ import { OrganizationGetEntitiesCountResponse } from '../model/organization/orga
import { BASE_PATH, COLLECTION_FORMATS } from '../variables';
import { Configuration } from '../configuration';
import {OrganizationPrepareDownloadForGetAllRequest} from '~/business-logic/model/organization/organizationPrepareDownloadForGetAllRequest';
@Injectable()
@@ -65,7 +66,7 @@ export class ApiOrganizationService {
/**
*
*
* Get all the user and system tags used for the company tasks and models
* @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.
@@ -110,7 +111,7 @@ export class ApiOrganizationService {
}
/**
*
*
* Get details for all companies associated with the current 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.
@@ -154,7 +155,51 @@ export class ApiOrganizationService {
);
}
/**
/**
*
* Prepares download from get_all_ex parameters
* @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 organizationPrepareDownloadForGetAll(request: OrganizationPrepareDownloadForGetAllRequest, 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 organizationPrepareDownloadForGetAll.');
}
let headers = this.defaultHeaders;
if (options && options.async_enable) {
headers = headers.set(this.configuration.asyncHeader, '1');
}
// to determine the Accept header
const httpHeaderAccepts: string[] = [
];
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<any>(`${this.basePath}/organization.prepare_download_for_get_all`,
request,
{
withCredentials: this.configuration.withCredentials,
headers: headers,
observe: observe,
reportProgress: reportProgress
}
);
}
/**
*
* Get counts for the company entities according to the passed search criteria
* @param request request body

View File

@@ -27,6 +27,8 @@ import { PipelinesStartPipelineResponse } from '../model/pipelines/pipelinesStar
import { BASE_PATH, COLLECTION_FORMATS } from '../variables';
import { Configuration } from '../configuration';
import {PipelinesDeleteRunsRequest} from '~/business-logic/model/pipelines/pipelinesDeleteRunsRequest';
import {PipelinesDeleteRunsResponse} from '~/business-logic/model/pipelines/pipelinesDeleteRunsResponse';
@Injectable()
@@ -60,6 +62,51 @@ export class ApiPipelinesService {
return false;
}
/**
*
* Delete pipeline runs
* @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 pipelinesDeleteRuns(request: PipelinesDeleteRunsRequest, 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 pipelinesDeleteRuns.');
}
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<PipelinesDeleteRunsResponse>(`${this.basePath}/pipelines.delete_runs`,
request,
{
withCredentials: this.configuration.withCredentials,
headers: headers,
observe: observe,
reportProgress: reportProgress
}
);
}
/**
*

View File

@@ -65,6 +65,8 @@ import {ProjectsGetProjectTagsResponse} from '~/business-logic/model/projects/pr
import {ProjectsGetProjectTagsRequest} from '~/business-logic/model/projects/projectsGetProjectTagsRequest';
import {ProjectsGetModelMetadataValuesRequest} from '~/business-logic/model/projects/projectsGetModelMetadataValuesRequest';
import {ProjectsGetModelMetadataValuesResponse} from '~/business-logic/model/projects/projectsGetModelMetadataValuesResponse';
import {ProjectsGetUserNamesResponse} from '~/business-logic/model/projects/projectsGetUserNamesResponse';
import {ProjectsGetUserNamesRequest} from '~/business-logic/model/projects/projectsGetUserNamesRequest';
@Injectable()
@@ -821,6 +823,53 @@ export class ApiProjectsService {
);
}
/**
*
* Get names and ids of the users who created child entitites under the passed projects
* @param request request body
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public projectsGetUserNames(request: ProjectsGetUserNamesRequest, 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 projectsGetUserNames.');
}
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<ProjectsGetUserNamesResponse>(`${this.basePath}/projects.get_user_names`,
request,
{
withCredentials: this.configuration.withCredentials,
headers: headers,
observe: observe,
reportProgress: reportProgress
}
);
}
/**
*
* Moves all the source project\&#39;s contents to the destination project and remove the source project

View File

@@ -0,0 +1,28 @@
/**
* organization
* 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 FieldMapping {
/**
* The source column name as specified in the only_fields
*/
field: string;
/**
* The column name in the exported csv file
*/
name?: string;
/**
* The column values mapping
*/
values?: {key: any; value: any}[];
}

View File

@@ -0,0 +1,52 @@
/**
* organization
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* OpenAPI spec version: 999.0
*
*
* NOTE: This class is auto generated by the swagger code generator program.
* https://github.com/swagger-api/swagger-codegen.git
* Do not edit the class manually.
*/
import { FieldMapping } from '././fieldMapping';
export interface OrganizationPrepareDownloadForGetAllRequest {
/**
* List of task field names (nesting is supported using \'.\', e.g. execution.model_labels). If provided, this list defines the query\'s projection (only these fields will be returned for each result entry)
*/
only_fields: Array<string>;
/**
* Download type. Determines the downloaded file\'s formatting and mime type.
*/
download_type?: OrganizationPrepareDownloadForGetAllRequest.DownloadTypeEnum;
/**
* Allow public entities to be returned in the results
*/
allow_public?: boolean;
/**
* If set to \'true\' then hidden entities are included in the search results
*/
search_hidden?: boolean;
/**
* The type of the entity to retrieve
*/
entity_type: OrganizationPrepareDownloadForGetAllRequest.EntityTypeEnum;
/**
* The name and value mappings for the exported fields. The fields that are not in the mappings will not be exported
*/
field_mappings?: Array<FieldMapping>;
}
export namespace OrganizationPrepareDownloadForGetAllRequest {
export type DownloadTypeEnum = 'csv';
export const DownloadTypeEnum = {
Csv: 'csv' as DownloadTypeEnum
}
export type EntityTypeEnum = 'task' | 'model';
export const EntityTypeEnum = {
Task: 'task' as EntityTypeEnum,
Model: 'model' as EntityTypeEnum
}
}

View File

@@ -0,0 +1,20 @@
/**
* organization
* 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 OrganizationPrepareDownloadForGetAllResponse {
/**
* Prepare ID (use when calling \'download_for_get_all\')
*/
prepare_id?: string;
}

View File

@@ -0,0 +1,24 @@
/**
* pipelines
* 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 PipelinesDeleteRunsRequest {
/**
* IDs of the pipeline runs to delete. Should be the ids of pipeline controller tasks
*/
ids: Array<string>;
/**
* Pipeline project ids. When deleting at least one run should be left
*/
project: string;
}

View File

@@ -0,0 +1,20 @@
/**
* pipelines
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* OpenAPI spec version: 999.0
*
*
* NOTE: This class is auto generated by the swagger code generator program.
* https://github.com/swagger-api/swagger-codegen.git
* Do not edit the class manually.
*/
import { PipelinesDeleteRunsResponseSucceeded } from '././pipelinesDeleteRunsResponseSucceeded';
import { PipelinesDeleteRunsResponseFailed } from '././pipelinesDeleteRunsResponseFailed';
export interface PipelinesDeleteRunsResponse {
succeeded?: Array<PipelinesDeleteRunsResponseSucceeded>;
failed?: Array<PipelinesDeleteRunsResponseFailed>;
}

View File

@@ -0,0 +1,22 @@
/**
* pipelines
* 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.
*/
/**
* Error info
*/
export interface PipelinesDeleteRunsResponseError {
codes?: Array<number>;
msg?: string;
data?: object;
}

View File

@@ -0,0 +1,22 @@
/**
* pipelines
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* OpenAPI spec version: 999.0
*
*
* NOTE: This class is auto generated by the swagger code generator program.
* https://github.com/swagger-api/swagger-codegen.git
* Do not edit the class manually.
*/
import { PipelinesDeleteRunsResponseError } from '././pipelinesDeleteRunsResponseError';
export interface PipelinesDeleteRunsResponseFailed {
/**
* ID of the failed entity
*/
id?: string;
error?: PipelinesDeleteRunsResponseError;
}

View File

@@ -0,0 +1,40 @@
/**
* pipelines
* 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 PipelinesDeleteRunsResponseSucceeded {
/**
* ID of the succeeded entity
*/
id?: string;
/**
* Indicates whether the task was deleted
*/
deleted?: boolean;
/**
* Number of child tasks whose parent property was updated
*/
updated_children?: number;
/**
* Number of models whose task property was updated
*/
updated_models?: number;
/**
* Number of deleted output models
*/
deleted_models?: number;
/**
* Number of deleted dataset versions
*/
deleted_versions?: number;
}

View File

@@ -10,6 +10,7 @@
* Do not edit the class manually.
*/
import { PipelinesStartPipelineRequestArgs } from '././pipelinesStartPipelineRequestArgs';
export interface PipelinesStartPipelineRequest {
@@ -22,7 +23,7 @@ export interface PipelinesStartPipelineRequest {
*/
queue?: string;
/**
* Task arguments, key/value to be placed in the hyperparameters Args section
* Task arguments, name/value to be placed in the hyperparameters Args section
*/
args?: object;
args?: Array<PipelinesStartPipelineRequestArgs>;
}

View File

@@ -0,0 +1,17 @@
/**
* pipelines
* 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 PipelinesStartPipelineRequestArgs {
name?: string;
}

View File

@@ -25,4 +25,8 @@ export interface ProjectsDeleteRequest {
* If set to \'true\' then the project tasks and models will be deleted. Otherwise their project property will be unassigned. Default value is \'false\'
*/
delete_contents?: boolean;
/**
* If set to \'true\' then BE will try to delete the extenal artifacts associated with the project tasks and models from the fileserver (if configured to do so)
*/
delete_external_artifacts?: boolean;
}

View File

@@ -10,119 +10,135 @@
* Do not edit the class manually.
*/
import { MultiFieldPatternData } from '././multiFieldPatternData';
import {MultiFieldPatternData} from '././multiFieldPatternData';
export interface ProjectsGetAllExRequest {
/**
* List of IDs to filter by
*/
id?: Array<string>;
/**
* Get only projects whose name matches this pattern (python regular expression syntax)
*/
name?: string;
/**
* Project base name
*/
basename?: string;
/**
* Get only projects whose description matches this pattern (python regular expression syntax)
*/
description?: string;
/**
* User-defined tags list used to filter results. Prepend \'-\' to tag name to indicate exclusion
*/
tags?: Array<string>;
/**
* System tags list used to filter results. Prepend \'-\' to system tag name to indicate exclusion
*/
system_tags?: Array<string>;
/**
* List of field names to order by. When search_text is used, \'@text_score\' can be used as a field representing the text score of returned documents. Use \'-\' prefix to specify descending order. Optional, recommended when using page
*/
order_by?: Array<string>;
/**
* Page number, returns a specific page out of the resulting list of dataviews
*/
page?: number;
/**
* Page size, specifies the number of results returned in each page (last page may contain fewer results)
*/
page_size?: number;
/**
* Free text search query
*/
search_text?: string;
/**
* List of document\'s field names (nesting is supported using \'.\', e.g. execution.model_labels). If provided, this list defines the query\'s projection (only these fields will be returned for each result entry)
*/
only_fields?: Array<string>;
_all_?: MultiFieldPatternData;
_any_?: MultiFieldPatternData;
/**
* If true, include project statistic in response.
*/
include_stats?: boolean;
/**
* Report stats include only statistics for tasks in the specified state. If Null is provided, stats for all task states will be returned.
*/
stats_for_state?: ProjectsGetAllExRequest.StatsForStateEnum;
/**
* Return only non-public projects
*/
non_public?: boolean;
active_users?: Array<string>;
/**
* If set to \'true\' then the search with the specified criteria is performed among top level projects only (or if parents specified, among the direct children of the these parents). Otherwise the search is performed among all the company projects (or among all of the descendants of the specified parents).
*/
shallow_search?: boolean;
/**
* If set to \'true\' and project ids are passed to the query then for these projects their own tasks, models and dataviews are counted
*/
check_own_contents?: boolean;
/**
* If set to \'true\' then hidden projects are included in the search results
*/
search_hidden?: boolean;
/**
* Scroll ID returned from the previos calls to get_all
*/
scroll_id?: string;
/**
* If set then all the data received with this scroll will be requeried
*/
refresh_scroll?: boolean;
/**
* The number of projects to retrieve
*/
size?: number;
/**
* If include_stats flag is set then this flag contols whether the child projects tasks are taken into statistics or not
*/
stats_with_children?: boolean;
/**
* The filter for selecting entities that participate in statistics calculation. For each task field that you want to filter on pass the list of allowed values. Prepend the value with \'-\' to exclude
*/
include_stats_filter?: object;
/**
* If true, include project dataset statistic in response
*/
include_dataset_stats?: boolean;
/**
* If Truethen the shallow search is done among all the top projects that the user has access to beneath the requested parent. Even if these projects are not direct children of the parent
*/
permission_roots_only?: boolean;
/**
* Allow public projects to be returned in the results
*/
allow_public?: boolean;
children_type?: string;
/**
* List of IDs to filter by
*/
id?: Array<string>;
/**
* Get only projects whose name matches this pattern (python regular expression syntax)
*/
name?: string;
/**
* Project base name
*/
basename?: string;
/**
* Get only projects whose description matches this pattern (python regular expression syntax)
*/
description?: string;
/**
* User-defined tags list used to filter results. Prepend \'-\' to tag name to indicate exclusion
*/
tags?: Array<string>;
/**
* System tags list used to filter results. Prepend \'-\' to system tag name to indicate exclusion
*/
system_tags?: Array<string>;
/**
* List of field names to order by. When search_text is used, \'@text_score\' can be used as a field representing the text score of returned documents. Use \'-\' prefix to specify descending order. Optional, recommended when using page
*/
order_by?: Array<string>;
/**
* Page number, returns a specific page out of the resulting list of dataviews
*/
page?: number;
/**
* Page size, specifies the number of results returned in each page (last page may contain fewer results)
*/
page_size?: number;
/**
* Free text search query
*/
search_text?: string;
/**
* List of document\'s field names (nesting is supported using \'.\', e.g. execution.model_labels). If provided, this list defines the query\'s projection (only these fields will be returned for each result entry)
*/
only_fields?: Array<string>;
_all_?: MultiFieldPatternData;
_any_?: MultiFieldPatternData;
/**
* If true, include project statistic in response.
*/
include_stats?: boolean;
/**
* Report stats include only statistics for tasks in the specified state. If Null is provided, stats for all task states will be returned.
*/
stats_for_state?: ProjectsGetAllExRequest.StatsForStateEnum;
/**
* Return only non-public projects
*/
non_public?: boolean;
active_users?: Array<string>;
/**
* If set to \'true\' then the search with the specified criteria is performed among top level projects only (or if parents specified, among the direct children of the these parents). Otherwise the search is performed among all the company projects (or among all of the descendants of the specified parents).
*/
shallow_search?: boolean;
/**
* If set to \'true\' and project ids are passed to the query then for these projects their own tasks, models and dataviews are counted
*/
check_own_contents?: boolean;
/**
* If set to \'true\' then hidden projects are included in the search results
*/
search_hidden?: boolean;
/**
* Scroll ID returned from the previos calls to get_all
*/
scroll_id?: string;
/**
* If set then all the data received with this scroll will be requeried
*/
refresh_scroll?: boolean;
/**
* The number of projects to retrieve
*/
size?: number;
/**
* If include_stats flag is set then this flag contols whether the child projects tasks are taken into statistics or not
*/
stats_with_children?: boolean;
/**
* The filter for selecting entities that participate in statistics calculation. For each task field that you want to filter on pass the list of allowed values. Prepend the value with \'-\' to exclude
*/
include_stats_filter?: object;
/**
* If true, include project dataset statistic in response
*/
include_dataset_stats?: boolean;
/**
* If Truethen the shallow search is done among all the top projects that the user has access to beneath the requested parent. Even if these projects are not direct children of the parent
*/
permission_roots_only?: boolean;
/**
* Allow public projects to be returned in the results
*/
allow_public?: boolean;
/**
* If specified that only the projects under which the entities of this type can be found will be returned
*/
children_type?: ProjectsGetAllExRequest.ChildrenTypeEnum;
/**
* The list of tag values to filter children by. Takes effect only if children_type is set. Use \'null\' value to specify empty tags. Use \'__Snot\' value to specify that the following value should be excluded
*/
children_tags?: Array<string>;
}
export namespace ProjectsGetAllExRequest {
export type StatsForStateEnum = 'active' | 'archived';
export const StatsForStateEnum = {
Active: 'active' as StatsForStateEnum,
Archived: 'archived' as StatsForStateEnum
}
export type ChildrenTypeEnum = 'pipeline' | 'report' | 'dataset';
export const ChildrenTypeEnum = {
Pipeline: 'pipeline' as ChildrenTypeEnum,
Report: 'report' as ChildrenTypeEnum,
Dataset: 'dataset' as ChildrenTypeEnum
};
export type StatsForStateEnum = 'active' | 'archived';
export const StatsForStateEnum = {
Active: 'active' as StatsForStateEnum,
Archived: 'archived' as StatsForStateEnum
};
}

View File

@@ -0,0 +1,36 @@
/**
* projects
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* OpenAPI spec version: 999.0
*
*
* NOTE: This class is auto generated by the swagger code generator program.
* https://github.com/swagger-api/swagger-codegen.git
* Do not edit the class manually.
*/
export interface ProjectsGetUserNamesRequest {
/**
* The list of projects. If not passed or empty then all the projects are searched
*/
projects?: Array<string>;
/**
* If set to \'true\' and the projects field is not empty then the result includes user name from the subprojects children
*/
include_subprojects?: boolean;
/**
* The type of the child entity to look for
*/
entity?: ProjectsGetUserNamesRequest.EntityEnum;
}
export namespace ProjectsGetUserNamesRequest {
export type EntityEnum = 'task' | 'model' | 'dataview';
export const EntityEnum = {
Task: 'task' as EntityEnum,
Model: 'model' as EntityEnum,
Dataview: 'dataview' as EntityEnum
}
}

View File

@@ -0,0 +1,21 @@
/**
* projects
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* OpenAPI spec version: 999.0
*
*
* NOTE: This class is auto generated by the swagger code generator program.
* https://github.com/swagger-api/swagger-codegen.git
* Do not edit the class manually.
*/
import { ProjectsGetUserNamesResponseUsers } from '././projectsGetUserNamesResponseUsers';
export interface ProjectsGetUserNamesResponse {
/**
* The list of users sorted by their names
*/
users?: Array<ProjectsGetUserNamesResponseUsers>;
}

View File

@@ -0,0 +1,24 @@
/**
* projects
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* OpenAPI spec version: 999.0
*
*
* NOTE: This class is auto generated by the swagger code generator program.
* https://github.com/swagger-api/swagger-codegen.git
* Do not edit the class manually.
*/
export interface ProjectsGetUserNamesResponseUsers {
/**
* The ID of the user
*/
id?: string;
/**
* The name of the user
*/
name?: string;
}

View File

@@ -29,4 +29,20 @@ export interface ProjectsValidateDeleteResponse {
* The total number of non-archived models under the project and all its children
*/
non_archived_models?: number;
/**
* The total number of non-empty datasets under the project and all its children
*/
datasets?: number;
/**
* The total number of reports under the project and all its children
*/
reports?: number;
/**
* The total number of non-archived reports under the project and all its children
*/
non_archived_reports?: number;
/**
* The total number of pipelines with active controllers under the project and all its children
*/
pipelines?: number;
}

View File

@@ -17,4 +17,8 @@ import { TasksResetManyResponseFailed } from '././tasksResetManyResponseFailed';
export interface TasksEnqueueManyResponse {
succeeded?: Array<TasksEnqueueManyResponseSucceeded>;
failed?: Array<TasksResetManyResponseFailed>;
/**
* Returns Trueif there are workers or autscalers working with the queue
*/
queue_watched?: boolean;
}

View File

@@ -10,10 +10,13 @@
* Do not edit the class manually.
*/
import { GetCurrentUserResponseUserObject } from '././getCurrentUserResponseUserObject';
import {GetCurrentUserResponseUserObject} from '././getCurrentUserResponseUserObject';
import {UsersGetCurrentUserResponseSettings} from "~/business-logic/model/users/usersGetCurrentUserResponseSettings";
export interface UsersGetCurrentUserResponse {
user?: GetCurrentUserResponseUserObject;
getting_started?: object;
user?: GetCurrentUserResponseUserObject;
getting_started?: object;
settings?: UsersGetCurrentUserResponseSettings;
}

View File

@@ -0,0 +1,20 @@
/**
* users
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* OpenAPI spec version: 22.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 UsersGetCurrentUserResponseSettings {
/**
* The maximum items downloaded for this user in csv file downloads
*/
max_download_items?: string;
}

View File

@@ -1,8 +1,10 @@
import {createAction, props} from '@ngrx/store';
import {USERS_PREFIX} from '~/app.constants';
import {GetCurrentUserResponseUserObject} from '~/business-logic/model/users/getCurrentUserResponseUserObject';
import {UsersGetCurrentUserResponseSettings} from "~/business-logic/model/users/usersGetCurrentUserResponseSettings";
export const setCurrentUser = createAction(USERS_PREFIX + 'SET_CURRENT_USER',
// eslint-disable-next-line @typescript-eslint/naming-convention
props<{user: GetCurrentUserResponseUserObject; terms_of_use?: any; getting_started?: any}>()
props<{user: GetCurrentUserResponseUserObject; terms_of_use?: any; getting_started?: any; settings?: UsersGetCurrentUserResponseSettings;
}>()
);

View File

@@ -9,7 +9,7 @@ import {RouterEffects} from '@common/core/effects/router.effects';
import {CommonUserEffects} from '@common/core/effects/users.effects';
import {createUserPrefReducer} from '@common/core/meta-reducers/user-pref-reducer';
import {messagesReducer} from '@common/core/reducers/messages-reducer';
import {projectsReducer, RootProjects} from '@common/core/reducers/projects.reducer';
import {projectsReducer} from '@common/core/reducers/projects.reducer';
import {routerReducer} from '@common/core/reducers/router-reducer';
import {SmSyncStateSelectorService} from '@common/core/services/sync-state-selector.service';
import {
@@ -30,7 +30,6 @@ import {projectSyncedKeys} from '~/features/projects/projects.module';
import {authReducer} from '~/features/settings/containers/admin/auth.reducers';
import {AdminService} from '~/shared/services/admin.service';
import {UserEffects} from './effects/users.effects';
import {recentTasksReducer} from './reducers/recent-tasks-reducer';
import {sourcesReducer} from './reducers/sources-reducer';
import {usageStatsReducer} from './reducers/usage-stats.reducer';
import {usersReducer} from './reducers/users.reducer';
@@ -38,6 +37,8 @@ import {viewReducer} from './reducers/view.reducer';
import {UsageStatsService} from './services/usage-stats.service';
import {extCoreModules} from '~/build-specifics';
import {ReportCodeEmbedService} from '../shared/services/report-code-embed.service';
import {recentTasksReducer} from '@common/core/reducers/recent-tasks-reducer';
import {BreadcrumbsService} from '@common/shared/services/breadcrumbs.service';
export const reducers = {
auth: authReducer,
@@ -141,6 +142,7 @@ const userPrefMetaFactory = (userPreferences: UserPreferences): MetaReducer<any>
useFactory: userPrefMetaFactory
},
{provide: DEFAULT_CURRENCY_CODE, useValue: 'USD'},
BreadcrumbsService,
],
declarations: [],
exports: []

View File

@@ -70,7 +70,6 @@ export class ProjectsEffects {
actions.setSelectedProject({project: projects[0]}),
actions.getProjectUsers(action),
...(!customProjectType ? [actions.getTags()] : []),
actions.getTags(),
actions.getCompanyTags(),
deactivateLoader(action.type),
];

View File

@@ -1,17 +0,0 @@
import {RECENT_TASKS_ACTIONS} from '../../app.constants';
import {SetRecentTasks} from '../../webapp-common/core/actions/recent-tasks.actions';
import {Task} from '../../business-logic/model/tasks/task';
const initTasks = {
data : <Array<Partial<Task>>>[],
};
export function recentTasksReducer(state = initTasks, action: SetRecentTasks) {
switch (action.type) {
case RECENT_TASKS_ACTIONS.SET_RECENT_TASKS:
return {...state, data: action.payload.tasks};
default:
return state;
}
}

View File

@@ -14,6 +14,7 @@ export const usersReducer = createReducer<UsersState>(initUsers,
currentUser: action.user,
gettingStarted: action.getting_started,
activeWorkspace: action.user?.company,
settings: action.settings,
userWorkspaces: [action.user?.company],
}))
);

View File

@@ -6,6 +6,8 @@ import {
} from '@common/core/reducers/view.reducer';
import {dismissSurvey} from '../actions/layout.actions';
import {setServerUpdatesAvailable} from '@common/core/actions/layout.actions';
import {selectRouterConfig} from '@common/core/reducers/router-reducer';
import {routeConfToProjectType} from '~/features/projects/projects-page.utils';
interface ViewState extends CommonViewState {
availableUpdates: string;
@@ -22,6 +24,8 @@ export const selectAvailableUpdates = createSelector(views, state => state.ava
export const selectShowSurvey = createSelector(views, state => state.showSurvey);
export const selectUserSettingsNotificationPath = createSelector(views, (state) => '');
export const selectActiveWorkspaceReady = createSelector(views, (state) => true);
export const selectProjectType = createSelector(selectRouterConfig,
config => (config && routeConfToProjectType(config)) ?? 'datasets');
export function viewReducer(viewState: ViewState = initViewState, action) {

View File

@@ -1,6 +1,6 @@
<div class="search-container">
<div class="d-flex-center">
<div class="pl-3 py-3">
<div class="d-flex-center tabs">
<div class="ps-3 py-3">
<ng-container *ngFor="let searchTab of activeLinksList">
<span [class.active]="activeLink === searchTab.name" class="pointer category-link"
(click)="activeLinkChanged.emit(searchTab.name)">{{searchTab.label}} ({{resultsCount?.[searchTab.name]}}) </span>
@@ -21,7 +21,8 @@
[cardHeight]="getCardHeight()"
[showLoadMoreButton]="getResults().length < resultsCount?.[activeLink]"
(itemClicked)="projectClicked($event)"
(loadMoreClicked)="loadMoreClicked.emit()">
(loadMoreClicked)="loadMoreClicked.emit()"
>
</sm-virtual-grid>
</div>

View File

@@ -3,19 +3,22 @@
.category-link{
padding-right: 24px;
font-size: $font-size-lg;
color: $blue-100;
opacity: 0.3;
&.active{
opacity: 1;
color: $blue-300;
&:hover {
color: $blue-200;
}
&.active {
color: $neon-yellow;
}
}
.search-container{
@include recent-title();
height: calc(100% - 20px);
height: calc(100% - 24px);
.tabs {
@include recent-title();
}
}
.page-container {
height: calc(100% - 35px);
overflow: auto;
}

View File

@@ -2,10 +2,8 @@ import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {SimpleDatasetsComponent} from '@common/datasets/simple-datasets/simple-datasets.component';
import {EntityTypeEnum} from '~/shared/constants/non-common-consts';
import {
NestedProjectViewPageComponent
} from '@common/nested-project-view/nested-project-view-page/nested-project-view-page.component';
import {CrumbTypeEnum} from '@common/layout/breadcrumbs/breadcrumbs.component';
import {NestedSimpleDatasetsPageComponent} from '@common/datasets/nested-simple-datasets-page/nested-simple-datasets-page.component';
const routes: Routes = [
{
@@ -27,7 +25,7 @@ const routes: Routes = [
},
{
path: 'projects',
component: NestedProjectViewPageComponent,
component: NestedSimpleDatasetsPageComponent,
data: {search: true}
},
{

View File

@@ -6,7 +6,8 @@ import {DatasetsRoutingModule} from '~/features/datasets/datasets-routing.module
import {DatasetsSharedModule} from '~/features/datasets/shared/datasets-shared.module';
import {SharedPipesModule} from '@common/shared/pipes/shared-pipes.module';
import {SMSharedModule} from '@common/shared/shared.module';
import {FeatureNestedProjectViewModule} from '~/features/nested-project-view/feature-nested-project-view.module';
import {NestedDatasetsPageComponent} from '~/features/datasets/nested-datasets-page/nested-datasets-page.component';
import {ProjectsSharedModule} from '~/features/projects/shared/projects-shared.module';
import {LabeledFormFieldDirective} from '@common/shared/directive/labeled-form-field.directive';
@@ -18,7 +19,8 @@ import {LabeledFormFieldDirective} from '@common/shared/directive/labeled-form-f
DatasetsRoutingModule,
DatasetsSharedModule,
SharedPipesModule,
FeatureNestedProjectViewModule,
NestedDatasetsPageComponent,
ProjectsSharedModule,
LabeledFormFieldDirective,
],
declarations: [

View File

@@ -0,0 +1,57 @@
<sm-nested-project-view-page
[projectsList]="projectsList$ | async"
[projectsOrderBy]="projectsOrderBy$ | async"
[projectsSortOrder]="projectsOrderBy$ | async"
[noMoreProjects]="noMoreProjects$ | async"
[allExamples]="allExamples"
[cardContentTemplateRef]="cardContent"
[entityType]="entityType"
(cardClicked)="projectCardClicked($event)"
(deleteProjectClicked)="deleteProject($event)"
(projectNameChanged)="projectNameChanged($event)"
(orderByChanged)="orderByChanged($event)"
(loadMore)="loadMore()"
(toggleNestedView)="toggleNestedView($event)"
>
<button create-button
class="btn btn-cml-primary d-flex align-items-center"
(click)="createExamples()">
<i class="al-icon al-ico-add sm me-2"></i>NEW DATASET
</button>
<div empty-state class="empty-datasets">
<div class="title-icon"><i class="al-icon al-ico-datasets xxl"></i></div>
<div class="title">NO DATASETS TO SHOW</div>
<div class="sub-title">Run your first dataset to see it displayed here
<ng-container *ngIf="allExamples"> or <a href="" (click)="$event.preventDefault(); createExamples()" class="link">generate
example
</a></ng-container>
</div>
<sm-dataset-empty [showButton]="true"></sm-dataset-empty>
</div>
</sm-nested-project-view-page>
<ng-template #cardContent let-project>
<sm-circle-counter
[counter]="project.id === '*' ? '' : project.stats?.datasets?.count ?? '0'"
[label]="'DATASETS'"
[type]="project.stats?.datasets?.count===0 ? circleTypeEnum.empty : circleTypeEnum.pending"></sm-circle-counter>
<sm-circle-counter label="TAGS IN USE" [counter]="[]">
<sm-tag-list
*ngIf="!hideMenu; else: ReadOnlyTags"
class="tags-list-counter"
[readonly]="true"
[class.empty-tags]="!(project.stats?.datasets?.tags.length > 0)"
tagsList
[tags]="project.stats?.datasets?.tags"
smClickStopPropagation
></sm-tag-list>
</sm-circle-counter>
<ng-template #ReadOnlyTags>
<sm-tag-list [tags]="project.tags"></sm-tag-list>
</ng-template>
</ng-template>

View File

@@ -0,0 +1,57 @@
import {Component} from '@angular/core';
import {ProjectTypeEnum} from '@common/nested-project-view/nested-project-view-page/nested-project-view-page.component';
import {CircleTypeEnum} from '~/shared/constants/non-common-consts';
import {ProjectsSharedModule} from '~/features/projects/shared/projects-shared.module';
import {SMSharedModule} from '@common/shared/shared.module';
import {AsyncPipe, NgIf} from '@angular/common';
import {CommonProjectsPageComponent} from '@common/projects/containers/projects-page/common-projects-page.component';
import {DatasetEmptyComponent} from '@common/datasets/dataset-empty/dataset-empty.component';
@Component({
selector: 'sm-nested-datasets-page',
templateUrl: './nested-datasets-page.component.html',
styleUrls: [
'../../../webapp-common/nested-project-view/nested-project-view-page/nested-project-view-page.component.scss',
'../../../webapp-common/datasets/simple-datasets/simple-datasets.component.scss'
],
imports: [
ProjectsSharedModule,
SMSharedModule,
AsyncPipe,
NgIf
],
standalone: true
})
export class NestedDatasetsPageComponent extends CommonProjectsPageComponent {
entityTypeEnum = ProjectTypeEnum;
circleTypeEnum = CircleTypeEnum;
hideMenu = false;
entityType = ProjectTypeEnum.datasets;
projectCardClicked(data: { hasSubProjects: boolean; id: string; name: string }) {
if (data.hasSubProjects) {
this.router.navigate(['simple', data.id, 'projects'], {relativeTo: this.route.parent?.parent});
} else {
this.router.navigate(['simple', data.id, ProjectTypeEnum.datasets], {relativeTo: this.route.parent?.parent});
}
}
createExamples() {
this.dialog.open(DatasetEmptyComponent, {
maxWidth: '95vw',
width: '1248px'
});
}
toggleNestedView(nested: boolean) {
if (!nested) {
this.router.navigateByUrl(this.entityType);
}
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected getExtraProjects(selectedProjectId, selectedProject) {
return [];
}
}

View File

@@ -1,24 +1,12 @@
import {Actions} from '@ngrx/effects';
import {ApiProjectsService} from '../../business-logic/api-services/projects.service';
import {Injectable} from '@angular/core';
import {Store} from '@ngrx/store';
import {AdminService} from '~/shared/services/admin.service';
import {ApiTasksService} from '../../business-logic/api-services/tasks.service';
import {ApiModelsService} from '../../business-logic/api-services/models.service';
import {DeleteDialogEffectsBase} from '../../webapp-common/shared/entity-page/entity-delete/base-delete-dialog.effects';
import {ConfigurationService} from '../../webapp-common/shared/services/configuration.service';
import {DeleteDialogEffectsBase} from '@common/shared/entity-page/entity-delete/base-delete-dialog.effects';
@Injectable()
export class DeleteDialogEffects extends DeleteDialogEffectsBase {
constructor(actions$: Actions,
store: Store<any>,
tasksApi: ApiTasksService,
modelsApi: ApiModelsService,
projectsApi: ApiProjectsService,
adminService: AdminService,
configService: ConfigurationService) {
super(actions$, store, tasksApi, modelsApi, projectsApi, adminService, configService);
constructor(actions$: Actions) {
super(actions$);
}
// (Nir) don't delete this. Other repositories need to override some base functions.
}

View File

@@ -51,6 +51,7 @@ import {Overlay} from '@angular/cdk/overlay';
import {ExperimentsComponent} from '@common/experiments/experiments.component';
import {RouterTabNavBarComponent} from '@common/shared/components/router-tab-nav-bar/router-tab-nav-bar.component';
import {MatTabsModule} from '@angular/material/tabs';
import {LabeledFormFieldDirective} from '@common/shared/directive/labeled-form-field.directive';
@NgModule({
@@ -84,6 +85,7 @@ import {MatTabsModule} from '@angular/material/tabs';
RouterTabNavBarComponent,
MatTabsModule,
RouterTabNavBarComponent,
LabeledFormFieldDirective,
],
declarations: [
ExperimentsComponent,

View File

@@ -16,6 +16,7 @@ export interface IExecutionForm {
branch?: string;
entry_point: string;
working_dir: string;
binary: string;
scriptType: sourceTypesEnum;
};
docker_cmd?: string;
@@ -23,7 +24,7 @@ export interface IExecutionForm {
diff: string;
output: {
destination: string;
logLevel?: 'basic' | 'details'; // TODO: should be enum from gencode.
logLevel?: 'INFO' | 'DEBUG' | 'ERROR'; // TODO: should be enum from gencode.
};
queue: Queue;
container?: Container;

View File

@@ -1,19 +0,0 @@
import {NgModule} from '@angular/core';
import {
NestedProjectViewPageExtendedComponent
} from './nested-project-view-page-extended/nested-project-view-page-extended.component';
import {NestedProjectViewModule} from "@common/nested-project-view/nested-project-view.module";
import {CommonModule} from "@angular/common";
@NgModule({
declarations: [
NestedProjectViewPageExtendedComponent,
],
exports: [NestedProjectViewPageExtendedComponent],
imports: [
CommonModule,
NestedProjectViewModule,
]
})
export class FeatureNestedProjectViewModule { }

View File

@@ -1,2 +0,0 @@
<sm-nested-project-view-page>
</sm-nested-project-view-page>

View File

@@ -1,33 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NestedProjectViewPageExtendedComponent } from './nested-project-view-page-extended.component';
import {StoreModule} from '@ngrx/store';
import {RouterTestingModule} from '@angular/router/testing';
import {MatDialogModule} from '@angular/material/dialog';
describe('PipelinesPageComponent', () => {
let component: NestedProjectViewPageExtendedComponent;
let fixture: ComponentFixture<NestedProjectViewPageExtendedComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ NestedProjectViewPageExtendedComponent ],
imports: [
StoreModule.forRoot({}),
RouterTestingModule,
MatDialogModule
]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(NestedProjectViewPageExtendedComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -1,14 +0,0 @@
import {Component, OnDestroy, OnInit} from '@angular/core';
import {
NestedProjectViewPageComponent
} from "@common/nested-project-view/nested-project-view-page/nested-project-view-page.component";
@Component({
selector: 'sm-nested-project-view-page-extended',
templateUrl: './nested-project-view-page-extended.component.html',
styleUrls: ['./nested-project-view-page-extended.component.scss']
})
export class NestedProjectViewPageExtendedComponent extends NestedProjectViewPageComponent implements OnInit, OnDestroy {
}

View File

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

View File

@@ -1,25 +1,38 @@
import {ActivatedRouteSnapshot} from "@angular/router";
import {ActivatedRouteSnapshot} from '@angular/router';
import {
getDatasetsRequest,
getPipelineRequest,
getReportRequest,
isPipelines,
isReports
} from "@common/projects/common-projects.utils";
import {ProjectsGetAllExRequest} from "~/business-logic/model/projects/projectsGetAllExRequest";
} from '@common/projects/common-projects.utils';
import {ProjectsGetAllExRequest} from '~/business-logic/model/projects/projectsGetAllExRequest';
export const isDeletableProject = readyForDeletion => (readyForDeletion.experiments.unarchived + readyForDeletion.models.unarchived) === 0;
export const popupEntitiesListConst = 'experiments or model';
export const popupEntitiesListConst = 'experiments, dataviews pipelines or dataset';
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 const getDeleteProjectPopupStatsBreakdown = (readyForDeletion, statsSubset: 'archived' | 'unarchived' | 'total', experimentCaption) => {
const errors = [
readyForDeletion.experiments[statsSubset] > 0 ?
`${readyForDeletion.experiments[statsSubset]} ${experimentCaption}${readyForDeletion.experiments[statsSubset] > 1 ? 's' : ''} ` : null,
readyForDeletion.models[statsSubset] > 0 ? readyForDeletion.models[statsSubset] + ' models ' : null,
readyForDeletion.pipelines[statsSubset] > 0 ? readyForDeletion.pipelines[statsSubset] + ' pipelines ' : null,
readyForDeletion.datasets[statsSubset] > 0 ? readyForDeletion.datasets[statsSubset] + ' datasets ' : null,
readyForDeletion.reports[statsSubset] > 0 ? readyForDeletion.reports[statsSubset] + ' reports' : null,
].filter(error => error !== null);
const first = errors.slice(0, -2);
const last = errors.slice(-2);
return [...first, last.join(' and ')].join(', ');
};
export const readyForDeletionFilter = readyForDeletion => !(readyForDeletion.experiments === null || readyForDeletion.models === null);
export const isDatasets = (snapshot: ActivatedRouteSnapshot) => snapshot.firstChild.routeConfig.path === 'datasets';
export const routeConfToProjectType = (routeConf: string[]) => routeConf[0];
export const getNoProjectsReRoute = ((routeConf: string[]) => 'experiments');
export const isNestedDatasets = (routeConf: string[]) => ['simple'].includes(routeConf?.[1]);
export const getFeatureProjectRequest = (snapshot: ActivatedRouteSnapshot, nested: boolean, searchQuery: any, selectedProjectName: any, selectedProjectId: any): ProjectsGetAllExRequest => {
const pipelines = isPipelines(snapshot);

View File

@@ -0,0 +1,8 @@
import {createAction, props} from '@ngrx/store';
import {PROJECTS_PREFIX} from '@common/core/actions/projects.actions';
import {CommonReadyForDeletion} from '@common/projects/common-projects.reducer';
export const setProjectReadyForDeletion= createAction(
PROJECTS_PREFIX + 'SET_PROJECT_READY_FOR_DELETION',
props<{readyForDeletion: CommonReadyForDeletion}>()
);

View File

@@ -1,10 +1,11 @@
import {Actions, createEffect, ofType} from '@ngrx/effects';
import {checkProjectForDeletion, setProjectReadyForDeletion} from '@common/projects/common-projects.actions';
import {checkProjectForDeletion} from '@common/projects/common-projects.actions';
import {mergeMap, switchMap} from 'rxjs/operators';
import {ProjectsValidateDeleteResponse} from '~/business-logic/model/projects/projectsValidateDeleteResponse';
import {Injectable} from '@angular/core';
import {ApiProjectsService} from '~/business-logic/api-services/projects.service';
import {ApiModelsService} from '~/business-logic/api-services/models.service';
import {setProjectReadyForDeletion} from '~/features/projects/projects.actions';
@Injectable()
export class ProjectsEffects {
@@ -16,19 +17,32 @@ export class ProjectsEffects {
checkIfProjectExperiments = createEffect(() => this.actions.pipe(
ofType(checkProjectForDeletion),
switchMap((action) => this.projectsApi.projectsValidateDelete({project: action.project.id})),
mergeMap((projectsValidateDeleteResponse: ProjectsValidateDeleteResponse) => [
mergeMap((res: ProjectsValidateDeleteResponse) => [
setProjectReadyForDeletion({
readyForDeletion: {
experiments: {
total: projectsValidateDeleteResponse.tasks,
archived: projectsValidateDeleteResponse.tasks - projectsValidateDeleteResponse.non_archived_tasks,
unarchived: projectsValidateDeleteResponse.non_archived_tasks
total: res.tasks,
archived: res.tasks - res.non_archived_tasks,
unarchived: res.non_archived_tasks
},
models: {
total: projectsValidateDeleteResponse.models,
archived: projectsValidateDeleteResponse.models - projectsValidateDeleteResponse.non_archived_models,
unarchived: projectsValidateDeleteResponse.non_archived_models
}
total: res.models,
archived: res.models - res.non_archived_models,
unarchived: res.non_archived_models
},
reports: {
total: res.reports,
archived: res.reports - res.non_archived_reports,
unarchived: res.non_archived_reports
},
pipelines: {
total: res.pipelines,
unarchived: res.pipelines
},
datasets: {
total: res.datasets,
unarchived: res.datasets
},
}
})
])

View File

@@ -1,37 +1,22 @@
import {on, createReducer, createSelector} from '@ngrx/store';
import {
CommonProjectReadyForDeletion,
commonProjectsInitState,
commonProjectsReducers,
CommonProjectsState
CommonProjectsState, CommonReadyForDeletion
} from '@common/projects/common-projects.reducer';
import {checkProjectForDeletion, resetReadyToDelete, setProjectReadyForDeletion} from '@common/projects/common-projects.actions';
import {setProjectReadyForDeletion} from '~/features/projects/projects.actions';
export type IProjectReadyForDeletion = CommonProjectReadyForDeletion;
export type ProjectReadyForDeletion = CommonReadyForDeletion;
export interface ProjectsState extends CommonProjectsState {
projectReadyForDeletion: IProjectReadyForDeletion;
projectReadyForDeletion: ProjectReadyForDeletion;
}
const projectsInitState: ProjectsState = {
...commonProjectsInitState,
projectReadyForDeletion: {
project: null, experiments: null, models: null
}
};
export const projectsReducer = createReducer(
projectsInitState,
on(checkProjectForDeletion, (state, action) => ({
...state,
projectReadyForDeletion: {
...projectsInitState.projectReadyForDeletion,
project: action.project
}
})),
on(resetReadyToDelete, state => ({...state, projectReadyForDeletion: projectsInitState.projectReadyForDeletion})),
on(setProjectReadyForDeletion, (state, action) => ({
commonProjectsInitState,
on(setProjectReadyForDeletion, (state, action): ProjectsState => ({
...state,
projectReadyForDeletion: {
...state.projectReadyForDeletion,

View File

@@ -12,6 +12,8 @@ import {DatasetEmptyComponent} from '@common/datasets/dataset-empty/dataset-empt
import {NestedCardComponent} from '@common/nested-project-view/nested-card/nested-card.component';
import {SharedPipesModule} from '@common/shared/pipes/shared-pipes.module';
import {PipelinesEmptyStateComponent} from '@common/pipelines/pipelines-page/pipelines-empty-state/pipelines-empty-state.component';
import {ProjectsHeaderComponent} from '@common/projects/dumb/projects-header/projects-header.component';
import {NestedProjectViewPageComponent} from '@common/nested-project-view/nested-project-view-page/nested-project-view-page.component';
const _declarations = [
ProjectCardComponent,
@@ -21,6 +23,8 @@ const _declarations = [
PipelineCardMenuComponent,
NestedCardComponent,
DatasetEmptyComponent,
NestedProjectViewPageComponent,
ProjectsHeaderComponent
];
@NgModule({

View File

@@ -12,6 +12,6 @@
<span
class="add-button d-flex align-items-center pointer"
[class.disabled]="creatingCredentials"
(click)="createCredential()"><i class="al-icon sm al-ico-plus mr-1"></i> Create new credentials
(click)="createCredential()"><i class="al-icon sm al-ico-plus me-1"></i> Create new credentials
</span>
</div>

View File

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

View File

@@ -1,6 +1,8 @@
$icomoon-font-family: "trains" !default;
$icomoon-font-path: "fonts" !default;
$al-ico-model-filled: "\e9f2";
$al-ico-type-report: "\e9f1";
$al-ico-info-circle-outline: "\e9f0";
$al-ico-ghost: "\e9ef";
$al-ico-flat-view: "\e9ee";

View File

@@ -22,6 +22,7 @@ export const getScalar = createAction('[App] getScalar', props<{
variants: string[];
company: string;
models: boolean;
xaxis: string;
otherSearchParams?: URLSearchParams;
}>());
export const getSample = createAction('[App] getSample', props<{

View File

@@ -22,6 +22,7 @@
[showLoaderOnDraw]="false"
[identifier]="'lala'"
[width]="400"
[xAxisType]="xaxis"
[isCompare]="true"
[noMargins]="true"
[legendConfiguration]="{noTextWrap: true}"

View File

@@ -1,7 +1,7 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, OnInit, ViewChild} from '@angular/core';
import {Store} from '@ngrx/store';
import {MatDialog} from '@angular/material/dialog';
import {Observable, withLatestFrom} from 'rxjs';
import {Observable} from 'rxjs';
import {filter, map, switchMap, take} from 'rxjs/operators';
import {Environment} from '../environments/base';
import {getParcoords, getPlot, getSample, getScalar, getSingleValues, reportsPlotlyReady} from './app.actions';
@@ -14,8 +14,7 @@ import {
selectSampleData,
selectSignIsNeeded,
selectSingleValuesData,
selectTaskData,
State
selectTaskData
} from './app.reducer';
import {ExtFrame} from '@common/shared/single-graph/plotly-graph-base';
import {DebugSample} from '@common/shared/debug-sample/debug-sample.reducer';
@@ -33,6 +32,7 @@ import {isFileserverUrl} from '~/shared/utils/url';
import {MetricValueType, SelectedMetric} from '@common/experiments-compare/experiments-compare.constants';
import {ExtraTask} from '@common/experiments-compare/dumbs/parallel-coordinates-graph/parallel-coordinates-graph.component';
import {EventsGetTaskSingleValueMetricsResponseValues} from '~/business-logic/model/events/eventsGetTaskSingleValueMetricsResponseValues';
import {ScalarKeyEnum} from '~/business-logic/model/reports/scalarKeyEnum';
type WidgetTypes = 'plot' | 'scalar' | 'sample' | 'parcoords' | 'single';
@@ -64,6 +64,7 @@ export class AppComponent implements OnInit {
@ViewChild(SingleGraphComponent) 'singleGraph': SingleGraphComponent;
public singleValueData: EventsGetTaskSingleValueMetricsResponseValues[];
public webappLink: string;
public readonly xaxis: ScalarKeyEnum;
@HostListener('window:resize')
onResize() {
@@ -71,7 +72,7 @@ export class AppComponent implements OnInit {
}
constructor(
private store: Store<State>,
private store: Store,
private configService: ConfigurationService,
private dialog: MatDialog,
private cdr: ChangeDetectorRef) {
@@ -86,6 +87,7 @@ export class AppComponent implements OnInit {
this.singleGraphHeight = window.innerHeight;
this.otherSearchParams = this.getOtherSearchParams();
this.isDarkTheme = !this.searchParams.get('light');
this.xaxis = this.searchParams.get('xaxis') as ScalarKeyEnum;
try {
const data = JSON.parse(localStorage.getItem('_saved_state_'));
@@ -189,7 +191,11 @@ export class AppComponent implements OnInit {
} else {
const {merged,} = prepareMultiPlots(metricsPlots);
const newGraphs = convertMultiPlots(merged);
this.plotData = Object.values(newGraphs)[0]?.[0];
const originalObject = this.searchParams.get('objects');
const series = this.searchParams.get('series');
this.plotData = Object.values(newGraphs)[0]?.find(a => originalObject === (a.task ?? a.data[0].task)) ??
Object.values(newGraphs)[0].find(a => (a.data[0] as any).seriesName === series) ??
Object.values(newGraphs)[0]?.[0];
}
@@ -236,7 +242,10 @@ export class AppComponent implements OnInit {
take(1))
.subscribe(metrics => {
this.plotLoaded = true;
this.plotData = Object.values(mergeMultiMetricsGroupedVariant(metrics))?.[0]?.[0];
const lala = [ScalarKeyEnum.IsoTime, ScalarKeyEnum.Timestamp].includes(this.xaxis) ? this.calcXaxis(metrics) : metrics;
this.plotData = Object.values(mergeMultiMetricsGroupedVariant(lala))?.[0]?.[0];
this.cdr.detectChanges();
});
}
@@ -306,6 +315,7 @@ export class AppComponent implements OnInit {
variants: this.searchParams.getAll('variants'),
iterations: this.searchParams.getAll('iterations').map(iteration => parseInt(iteration, 10)),
company: this.searchParams.get('company') || '',
xaxis: this.searchParams.get('xaxis') || '',
models
};
@@ -381,14 +391,17 @@ export class AppComponent implements OnInit {
private buildSourceLink(searchParams: URLSearchParams, project: string, tasks: string[]): string {
const isModels = searchParams.has('models') || this.searchParams.get('objectType') === 'model';
const objects = searchParams.getAll('objects');
let entityIds = objects.length > 0? objects : searchParams.getAll(isModels ? 'models' : 'tasks');
const variants = searchParams.getAll('variants');
const metricPath = searchParams.get('metrics') || '';
let entityIds = objects.length > 0 ? objects : searchParams.getAll(isModels ? 'models' : 'tasks');
if (entityIds.length === 0 && tasks?.length > 0) {
entityIds = tasks;
}
const isCompare = entityIds.length > 1;
let url = `${window.location.origin.replace('4201', '4200')}/projects/${project ?? '*'}/`;
if (isCompare) {
url += `${isModels ? 'compare-models;ids=' : 'compare-experiments;ids='}${entityIds.join(',')}/${this.getComparePath(this.type)}`;
url += `${isModels ? 'compare-models;ids=' : 'compare-experiments;ids='}${entityIds.join(',')}/
${this.getComparePath(this.type)}?metricPath=${metricPath}&metricName=lala${variants.map(par => `&params=${par}`).join('')}`;
} else {
url += `${isModels ? 'models/' : 'experiments/'}${entityIds}/${this.getOutputPath(isModels, this.type)}`;
}
@@ -452,5 +465,21 @@ export class AppComponent implements OnInit {
observer.observe(document.body);
});
}
private calcXaxis(metrics: MetricsPlotEvent[] | ReportsApiMultiplotsResponse) {
return Object.keys(metrics).reduce((groupAcc, groupName) => {
const group = metrics[groupName];
groupAcc[groupName] = Object.keys(group).reduce((graphAcc, graphName) => {
const expGraph = group[graphName];
graphAcc[graphName] = {};
Object.keys(expGraph).reduce((graphAcc2, exp) => {
const graph = expGraph[exp];
return graphAcc[graphName][exp] = {...graph, x: graph.x.map(ts => new Date(ts))};
}, {});
return graphAcc;
}, {});
return groupAcc;
}, {});
}
}

View File

@@ -24,6 +24,7 @@ import {HTTP} from '~/app.constants';
import {DebugSample} from '@common/shared/debug-sample/debug-sample.reducer';
import {requestFailed} from '@common/core/actions/http.actions';
import {Task} from '~/business-logic/model/tasks/task';
import {ScalarKeyEnum} from '~/business-logic/model/events/scalarKeyEnum';
@Injectable()
@@ -33,7 +34,7 @@ export class AppEffects {
constructor(
private httpClient: HttpClient,
private store: Store<State>,
private store: Store,
private actions$: Actions,
private reportsApi: ApiReportsService,
private adminService: BaseAdminService) {
@@ -78,7 +79,8 @@ export class AppEffects {
model_events: action.models,
// eslint-disable-next-line @typescript-eslint/naming-convention
scalar_metrics_iter_histogram: {
metrics: action.metrics.map(metric => ({metric, variants: action.variants}))
metrics: action.metrics.map(metric => ({metric, variants: action.variants})),
key: action.xaxis || ScalarKeyEnum.Iter
}
},
{headers: this.getHeaders(action.company)}
@@ -121,7 +123,7 @@ export class AppEffects {
mergeMap((action) => this.httpClient.post<{ data: ReportsGetTaskDataResponse }>(`${this.basePath}/reports.get_task_data?${action.otherSearchParams.toString()}`, {
id: action.tasks,
// eslint-disable-next-line @typescript-eslint/naming-convention
only_fields: ['last_metrics', 'name', 'last_iteration', ...action.variants.map(variant => `hyperparams.${variant}`)]
only_fields: ['last_metrics', 'name', 'last_iteration', 'project', ...action.variants.map(variant => `hyperparams.${variant}`)]
})
.pipe(
mergeMap(res => [

View File

@@ -4,7 +4,7 @@ import {DebugSample} from '@common/shared/debug-sample/debug-sample.reducer';
import {MetricsPlotEvent} from '~/business-logic/model/events/metricsPlotEvent';
import {MetricValueType, SelectedMetric} from '@common/experiments-compare/experiments-compare.constants';
import {Task} from '~/business-logic/model/tasks/task';
import {SingleValueTaskMetrics} from '~/business-logic/model/reports/singleValueTaskMetrics';
import { SingleValueTaskMetrics} from '~/business-logic/model/reports/singleValueTaskMetrics';
export interface ParCoords {
metric: SelectedMetric;

View File

@@ -15,6 +15,8 @@ import {BASE_ENV, Environment} from './base';
13 https://api.staging.hosted.allegro.ai
14 https://api.clear.ml
15 https://api.dev.hosted.clear.ml
16 https://api.deloitte.hosted.allegro.ai
17 https://api.trains-master.hosted.allegro.ai
*/
export const environment = {

View File

@@ -3,7 +3,42 @@
@use '../../../../../../node_modules/@angular/material/index' as mat;
// Plus imports for other components in your app.
$neon-yellow: #d3ff00;
$purple: #4d66ff;
$white-primary-text: rgba(white, 0.87);
$dark-primary-text: rgba(black, 0.87);
$sm-purple: (
50: lighten(#c3cdf0, 20%),
100: lighten(#c3cdf0, 15%),
200: lighten(#c3cdf0, 10%),
300: lighten(#c3cdf0, 5%),
400: #c3cdf0,
500: #c3cdf0,
600: #c3cdf0,
700: darken(#c3cdf0, 15%),
800: darken(#c3cdf0, 20%),
900: darken(#c3cdf0, 55%),
A100: $purple,
A200: darken($purple, 5%),
A400: darken($purple, 10%),
A700: darken($purple, 15%),
contrast: (
50: $dark-primary-text,
100: $dark-primary-text,
200: $dark-primary-text,
300: $dark-primary-text,
400: white,
500: white,
600: white,
700: white,
800: white,
900: white,
A100: white,
A200: white,
A400: white,
A700: white,
)
);
$sm-neon: (
50: lighten($neon-yellow, 30%),
100: lighten($neon-yellow, 25%),
@@ -47,6 +82,9 @@ $sm-neon: (
$theme-primary: mat.define-palette(mat.$indigo-palette);
$theme-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);
$sm-dark-palette-primary: mat.define-palette($sm-purple);
$sm-dark-palette-accent: mat.define-palette($sm-purple, A200, A100, A400);
$sm-neon-palette-primary: mat.define-palette($sm-neon);
$sm-neon-palette-accent: mat.define-palette($sm-neon, A200, A100, A400);
// The warn palette is optional (defaults to red).
@@ -76,6 +114,14 @@ $sm-neon-theme: mat.define-dark-theme((
density: -2
));
$sm-dark-theme: mat.define-dark-theme((
color: (
primary: $sm-dark-palette-primary,
accent: $sm-dark-palette-accent,
),
typography: $custom-typography,
density: -2
));
// Include theme styles for core and each component used in your app.
// Alternatively, you can import and @include the theme mixins for each component
// that you are using.
@@ -83,6 +129,8 @@ $sm-neon-theme: mat.define-dark-theme((
@include mat.slider-theme($sm-neon-theme);
@include mat.form-field-theme($theme);
@include mat.select-theme($sm-dark-theme);
@import "src/app/webapp-common/shared/ui-components/styles/variables";
* {
@@ -269,6 +317,59 @@ sm-debug-image-snippet {
}
}
.mat-mdc-select-panel {
padding: 0;
font-size: 14px;
&.light-theme {
--mdc-theme-surface: white;
--mdc-theme-text-primary-on-background: #{$blue-500};
}
&.dark, &.black, &.dark-theme {
--mdc-theme-surface: #000;
border: 1px solid $blue-500;
.mat-mdc-option {
--mdc-theme-text-primary-on-background: #{$blue-200};
}
}
.mat-mdc-option {
--mdc-typography-body1-line-height: 36px;
--mdc-typography-body1-font-size: 14px;
min-height: 36px;
}
.mat-mdc-option {
--mdc-theme-text-primary-on-background: #{$blue-200};
border-radius: 4px;
height: 40px;
min-height: 40px;
line-height: 40px;
margin-bottom: 2px;
&:last-child {
margin-bottom: 0;
}
&.mat-mdc-selected:not(.mat-mdc-option-multiple), &.mat-active {
background: $blue-800;
}
}
.mat-pseudo-checkbox-checked.mat-pseudo-checkbox-minimal::after {
color: $blue-200;
}
.mat-mdc-option:hover:not(.mdc-list-item--disabled),
.mat-mdc-option:focus:not(.mdc-list-item--disabled),
.mat-mdc-option.mat-mdc-option-active,
.mat-mdc-option.mdc-list-item--selected:not(.mat-mdc-option-multiple):not(.mdc-list-item--disabled) {
background: $dark-grey-blue;
}
}
.dark-theme, .light-theme {
.mat-mdc-form-field {
--mdc-typography-body1-font-size: 14px;
@@ -318,6 +419,44 @@ sm-debug-image-snippet {
}
}
}
.label-text {
color: $blue-250;
font-size: 14px;
}
.mat-mdc-slider.mdc-slider {
$size: 36px;
height: $size;
.mdc-slider__thumb {
height: $size;
width: $size;
left: -$size * 0.5;
}
.mat-ripple-element.mat-mdc-slider-active-ripple,
.mat-ripple-element.mat-mdc-slider-focus-ripple,
.mat-ripple-element.mat-mdc-slider-hover-ripple {
height: $size !important;
width: $size !important;
left: 0 !important;
top: 0 !important;
}
}
.mat-mdc-form-field {
&.no-bottom {
.mat-mdc-form-field-subscript-wrapper {
display: none;
}
}
}
}
.dark-theme .mdc-text-field--disabled .mdc-text-field__input {
color: rgba(255, 255, 255, 0.38);
}
.mat-mdc-form-field.smooth-input {

View File

@@ -27,7 +27,7 @@ export class CommonSearchComponent implements OnInit {
minChars = 3;
public regexError: boolean;
constructor(private store: Store<any>, private router: Router, private route: ActivatedRoute, private cdr: ChangeDetectorRef) {
constructor(private store: Store, private router: Router, private route: ActivatedRoute, private cdr: ChangeDetectorRef) {
}
ngOnInit() {

View File

@@ -2,12 +2,7 @@
@import "angular-notifier/styles.scss";
@import "angular-notifier/styles/themes/theme-material.scss";
@import "shared/ui-components/styles/notifications";
@import "britecharts/src/styles/charts/line";
@import "britecharts/src/styles/charts/donut";
@import "britecharts/src/styles/common/legend";
@import "britecharts/src/styles/common/tooltip";
@import "britecharts/src/styles/common/axes";
@import "britecharts/src/styles/common/grid";
@import "britecharts/src/styles/britecharts";
// @import "ngx-markdown-editor/assets/highlight.js/agate.min.css";
@import "shared/ui-components/styles/material-palette";
@import "assets/fonts/trains-icons.scss";
@@ -389,7 +384,6 @@ button {
justify-content: center;
height: 100%;
width: 100%;
margin-top: 50px;
}
.empty-menu {
@@ -600,6 +594,12 @@ html {
.light-theme {
.mat-mdc-menu-content {
.mat-mdc-menu-item {
&.cdk-keyboard-focused, &.cdk-program-focused {
color: rgba(0, 0, 0, 0.87);
}
&.mat-mdc-focus-indicator {
--mdc-list-list-item-hover-label-text-color: rgba(0, 0, 0, 0.87);
}
.mdc-list-item__primary-text {
--mdc-list-list-item-label-text-color: rgba(0, 0, 0, 0.87);
}
@@ -630,6 +630,10 @@ html {
overflow: hidden;
text-overflow: ellipsis;
}
&.mat-mdc-menu-item {
}
}
hr {
@@ -930,3 +934,9 @@ button.btn.button-outline-dark {
min-width: fit-content;
max-width: 50vw !important;
}
.background-transparent {
.mat-mdc-dialog-container {
--mdc-dialog-container-color: transparent;
}
}

View File

@@ -16,6 +16,7 @@ import {ProjectStatsGraphData} from '@common/core/reducers/projects.reducer';
import {User} from '~/business-logic/model/users/user';
import {ProjectsGetAllResponseSingle} from '~/business-logic/model/projects/projectsGetAllResponseSingle';
import {TaskStatusEnum} from '~/business-logic/model/tasks/taskStatusEnum';
import {IBreadcrumbsLink, IBreadcrumbsOptions} from '@common/layout/breadcrumbs/breadcrumbs.component';
export const PROJECTS_PREFIX = '[ROOT_PROJECTS] ';
@@ -117,12 +118,12 @@ export const setCompanyTags = createAction(
export const setMainPageTagsFilter = createAction(
PROJECTS_PREFIX + '[set main page tags filters]',
props<{ tags: string[]; feature: string }>()
props<{ tags?: string[]; feature: string}>()
);
export const setMainPageTagsFilterMatchMode = createAction(
PROJECTS_PREFIX + '[set main page tags filters match mode]',
props<{ matchMode: string }>()
props<{ matchMode: string; feature: string}>()
);
export const addProjectTags = createAction(
@@ -200,6 +201,20 @@ export const setDefaultNestedModeForFeature = createAction(
PROJECTS_PREFIX + ' [set defaultNestedModeForFeature]',
props<{ feature: string; isNested: boolean }>()
);
export const setSelectedBreadcrumbSubFeature = createAction(
PROJECTS_PREFIX + ' [set SelectedSubFeature]',
props<{ breadcrumb: IBreadcrumbsLink }>()
);
export const setBreadcrumbMainFeature = createAction(
PROJECTS_PREFIX + ' [setBreadcrumbMainFeature]',
props<{ breadcrumb: IBreadcrumbsLink }>()
);
export const setBreadcrumbsOptions = createAction(
PROJECTS_PREFIX + ' [setBreadcrumbsOptions]',
props<{ breadcrumbOptions: IBreadcrumbsOptions }>()
);
export const resetTablesFilterProjectsOptions = createAction(
PROJECTS_PREFIX + ' [reset tables filter projects options]'
@@ -207,10 +222,15 @@ export const resetTablesFilterProjectsOptions = createAction(
export const getTablesFilterProjectsOptions = createAction(
PROJECTS_PREFIX + ' [get tables filter projects options]',
props<{ searchString: string; loadMore: boolean }>()
props<{ searchString: string; loadMore: boolean}>()
);
export const setTablesFilterProjectsOptions = createAction(
PROJECTS_PREFIX + ' [set tables filter projects options]',
props<{ projects: Partial<ProjectsGetAllResponseSingle>[]; scrollId: string; loadMore?: boolean }>()
);
export const downloadForGetAll = createAction(
PROJECTS_PREFIX + ' [downloadForGetAll]',
props<{ prepareId: string}>()
);

View File

@@ -1,12 +1,4 @@
import {ISmAction} from '../models/actions';
import {RECENT_TASKS_ACTIONS} from '../../../app.constants';
export class SetRecentTasks implements ISmAction {
type = RECENT_TASKS_ACTIONS.SET_RECENT_TASKS;
public payload: { tasks: Array<any> };
constructor(tasks: Array<any>) {
this.payload = {tasks};
}
}
import {createAction} from '@ngrx/store';
import {Task} from '~/business-logic/model/tasks/task';
export const setRecentTasks = createAction('[RECENT_TASKS] SET_RECENT_TASKS', (tasks: Task[]) => ({ tasks }));

View File

@@ -1,37 +1,25 @@
import {ISmAction} from '../models/actions';
import {NAVIGATION_ACTIONS, NAVIGATION_PREFIX} from '~/app.constants';
import {Action, createAction, props} from '@ngrx/store';
import {NAVIGATION_PREFIX} from '~/app.constants';
import {createAction, props} from '@ngrx/store';
import {Params} from '@angular/router';
import {FilterMetadata} from 'primeng/api/filtermetadata';
import {SortMeta} from 'primeng/api';
import {CrumbTypeEnum, IBreadcrumbsLink} from "@common/layout/breadcrumbs/breadcrumbs.component";
import {CrumbTypeEnum, IBreadcrumbsLink} from '@common/layout/breadcrumbs/breadcrumbs.component';
export const BREADCRUMBS_PREFIX = 'BREADCRUMBS_';
// TODO: remove this action...
export class NavigateTo implements ISmAction {
readonly type = NAVIGATION_ACTIONS.NAVIGATE_TO;
constructor(public payload: {
url: string;
params?: Params;
unGuard?: boolean;
}) {
}
}
export class NavigationEnd implements Action {
readonly type = NAVIGATION_ACTIONS.NAVIGATION_END;
}
export const navigationEnd = createAction(NAVIGATION_PREFIX + 'NAVIGATION_END');
export const setRouterSegments = createAction(
NAVIGATION_ACTIONS.SET_ROUTER_SEGMENT, props<{
NAVIGATION_PREFIX + 'SET_ROUTER_SEGMENT',
props<{
url: string;
params: Params;
queryParams: Params;
config: string[];
}>());
data: any;
}>()
);
export const setURLParams = createAction(
NAVIGATION_PREFIX + 'SET_URL_PARAMS',

View File

@@ -1,24 +1,6 @@
import {TASKS_ACTIONS} from '../../../app.constants';
import {ISmAction} from '../models/actions';
import {Action} from '@ngrx/store';
import {createAction} from '@ngrx/store';
export class StopTask implements ISmAction {
type = TASKS_ACTIONS.STOP_TASK;
public payload = {task: ''};
constructor(taskId: string) {
this.payload.task = taskId;
}
}
export class SetTaskInListAndInSelectedTask implements Action {
type = TASKS_ACTIONS.TASKS_OPTIMISTIC;
public payload: { task: string };
constructor(taskId: string, field: string, value) {
this.payload = {
task : taskId,
[field]: value
};
}
}
export const setTaskInListAndInSelectedTask = createAction('[TASKS_PREFIX] OPTIMISTIC', (taskId: string, field: string, value) => ({
task : taskId,
[field]: value
}));

View File

@@ -1,12 +0,0 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {EntitiesCacheService} from './entities-cache.service';
@NgModule({
imports: [
CommonModule
],
declarations: [],
providers: [EntitiesCacheService]
})
export class CacheModule { }

View File

@@ -1,70 +0,0 @@
import {Injectable} from '@angular/core';
import {Store} from '@ngrx/store';
import {EntitiesEnum} from '../../../business-logic/constants';
import {SmSyncStateSelectorService} from '../services/sync-state-selector.service';
import {getCacheEntityObj} from './get-entity';
import {IEntityObject} from './model';
@Injectable()
export class EntitiesCacheService {
private readonly entities = [];
constructor(private syncSelector: SmSyncStateSelectorService, private store: Store<any>) {
this.entities.forEach(entity => {
entity.stream$.subscribe(({instance, force}) => {
this.sendRequest(instance, force);
});
});
}
public startUpdating(entityName: EntitiesEnum) {
const entity = this.getEntityObj(entityName);
entity.startPinging(false);
}
public stopUpdating(entityName: EntitiesEnum) {
const entity = this.getEntityObj(entityName);
entity.stopPinging();
}
public getEntity(entityName: EntitiesEnum, force = false) {
const entity = this.getEntityObj(entityName);
this.sendRequest(entity, force);
return entity.selector$
}
public updateEntity(entityName: EntitiesEnum) {
const entity = this.getEntityObj(entityName);
this.sendRequest(entity, true);
}
private getEntityObj(entityName): IEntityObject<any> {
const entity = getCacheEntityObj(entityName);
if (!entity) {
throw 'Entity: ' + entityName + ' does not exist.';
}
return entity;
}
private sendRequest(entity, force) {
if (force || this.shouldRequest(entity)) {
this.store.dispatch(entity.getGetAction());
// TODO:
entity.setRequest('...');
} else {
// TODO: update request.
}
}
private shouldRequest(entity: IEntityObject<any>) {
const data = this.syncSelector.selectSync(entity.selector);
const shouldRequest = (!data || entity.didTimeExpire());
return shouldRequest;
}
}

View File

@@ -25,7 +25,7 @@ export class CommonAuthEffects {
constructor(
private actions: Actions,
private credentialsApi: ApiAuthService,
private store: Store<any>,
private store: Store,
private adminService: AdminService,
private matDialog: MatDialog
) {}

View File

@@ -1,6 +1,6 @@
import {Injectable} from '@angular/core';
import {Actions, createEffect, ofType} from '@ngrx/effects';
import {EmptyAction} from '~/app.constants';
import {emptyAction} from '~/app.constants';
import * as layoutActions from '../actions/layout.actions';
import {addMessage} from '../actions/layout.actions';
import {bufferTime, filter, map, mergeMap, switchMap, take} from 'rxjs/operators';
@@ -92,7 +92,7 @@ export class LayoutEffects {
this.notifierService.show({type: payload.severity, message: payload.msg, actions: payload.userActions}) :
EMPTY
),
mergeMap((actions: Action[]) => actions.length > 0 ? actions : [new EmptyAction()])
mergeMap((actions: Action[]) => actions.length > 0 ? actions : [emptyAction()])
));
requestFailed: Observable<any> = createEffect( () => this.actions.pipe(

View File

@@ -1,10 +1,25 @@
import {Injectable} from '@angular/core';
import {Store} from '@ngrx/store';
import {Actions, createEffect, ofType} from '@ngrx/effects';
import {Actions, concatLatestFrom, createEffect, ofType} from '@ngrx/effects';
import {ApiProjectsService} from '~/business-logic/api-services/projects.service';
import * as actions from '../actions/projects.actions';
import {setProjectAncestors, setShowHidden, setTablesFilterProjectsOptions} from '../actions/projects.actions';
import {catchError, debounceTime, filter, map, mergeMap, switchMap, withLatestFrom} from 'rxjs/operators';
import {
downloadForGetAll,
setMainPageTagsFilter,
setProjectAncestors,
setShowHidden,
setTablesFilterProjectsOptions
} from '../actions/projects.actions';
import {
catchError,
debounceTime,
distinctUntilChanged,
filter,
map,
mergeMap,
switchMap,
withLatestFrom
} from 'rxjs/operators';
import {requestFailed} from '../actions/http.actions';
import {activeLoader, deactivateLoader, setServerError} from '../actions/layout.actions';
import {setSelectedModels} from '../../models/actions/models-view.actions';
@@ -12,14 +27,17 @@ import {TagColorMenuComponent} from '../../shared/ui-components/tags/tag-color-m
import {MatDialog} from '@angular/material/dialog';
import {ApiOrganizationService} from '~/business-logic/api-services/organization.service';
import {OrganizationGetTagsResponse} from '~/business-logic/model/organization/organizationGetTagsResponse';
import {selectRouterParams} from '../reducers/router-reducer';
import {EMPTY, forkJoin, of} from 'rxjs';
import {selectRouterConfig, selectRouterParams} from '../reducers/router-reducer';
import {EMPTY, forkJoin, Observable, of} from 'rxjs';
import {ProjectsGetTaskTagsResponse} from '~/business-logic/model/projects/projectsGetTaskTagsResponse';
import {ProjectsGetModelTagsResponse} from '~/business-logic/model/projects/projectsGetModelTagsResponse';
import {
selectAllProjectsUsers, selectProjectsOptionsScrollId,
selectAllProjectsUsers, selectIsDeepMode,
selectMainPageTagsFilter,
selectProjectsOptionsScrollId,
selectSelectedMetricVariantForCurrProject,
selectSelectedProjectId, selectShowHidden,
selectSelectedProjectId,
selectShowHidden,
} from '../reducers/projects.reducer';
import {
OperationErrorDialogComponent
@@ -36,6 +54,14 @@ import {escapeRegex} from '@common/shared/utils/escape-regex';
import {ProjectsGetAllExRequest} from '~/business-logic/model/projects/projectsGetAllExRequest';
import {ProjectsGetAllResponseSingle} from '~/business-logic/model/projects/projectsGetAllResponseSingle';
import {rootProjectsPageSize} from '@common/constants';
import {HTTP} from '~/app.constants';
import {cleanTag} from '@common/shared/utils/helpers.util';
import {selectProjectType} from '~/core/reducers/view.reducer';
import {selectExperimentsTableFilters} from '@common/experiments/reducers';
import {Params} from '@angular/router';
import {selectCompareAddTableFilters} from '@common/experiments-compare/reducers';
import {selectTableFilters} from '@common/models/reducers';
import {selectSelectModelTableFilters} from '@common/select-model/select-model.reducer';
export const ALL_PROJECTS_OBJECT = {id: '*', name: 'All Experiments'};
@@ -45,7 +71,7 @@ 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, private dialog: MatDialog, private tasksApi: ApiTasksService,
private usersApi: ApiUsersService,
) {
}
@@ -57,14 +83,23 @@ export class ProjectsEffects {
));
setDeep = createEffect(() => this.actions$.pipe(
ofType(actions.setDeep),
debounceTime(300),
withLatestFrom(this.store.select(selectSelectedProjectId), this.store.select(selectIsDeepMode)),
distinctUntilChanged(([, , preIsDeep], [, , currIsDeep]) => preIsDeep === currIsDeep),
map(([, projectId,]) => actions.getProjectUsers({projectId}))));
getTablesFilterProjectsOptions$ = createEffect(() => this.actions$.pipe(
ofType(actions.getTablesFilterProjectsOptions),
debounceTime(300),
withLatestFrom(
concatLatestFrom(() => [
this.store.select(selectShowHidden),
this.store.select(selectProjectsOptionsScrollId),
),
switchMap(([action, showHidden, scrollId]) => forkJoin([
this.getRelevantTableFilters(this.store.select(selectRouterConfig))
]),
switchMap(([action, showHidden, scrollId, filters]) => forkJoin([
this.projectsApi.projectsGetAllEx({
/* eslint-disable @typescript-eslint/naming-convention */
page_size: rootProjectsPageSize,
@@ -83,19 +118,30 @@ export class ProjectsEffects {
_any_: {pattern: `^${escapeRegex(action.searchString)}$`, fields: ['name', 'id']},
/* eslint-enable @typescript-eslint/naming-convention */
} as ProjectsGetAllExRequest).pipe(map(res => res.projects)) :
of([])
of([]),
!action.loadMore && filters['project.name']?.value.length ?
this.projectsApi.projectsGetAllEx({
id: filters['project.name']?.value,
only_fields: ['name', 'company'],
/* eslint-enable @typescript-eslint/naming-convention */
} as ProjectsGetAllExRequest).pipe(map(res => res.projects)) :
of([]),
])
.pipe(map(([allProjects, specificProjects]) => ({
.pipe(map(([allProjects, specificProjects, selectedProjects]) => ({
projects: [
...(specificProjects.length > 0 && allProjects.projects.some(project => project.id === specificProjects[0]?.id) ? [] : specificProjects),
...allProjects.projects
...allProjects.projects,
...selectedProjects
],
scrollId: allProjects.scroll_id,
loadMore: action.loadMore
})
))
),
mergeMap((projects: { projects: ProjectsGetAllResponseSingle[]; scrollId: string }) => [setTablesFilterProjectsOptions({...projects})])
mergeMap((projects: {
projects: ProjectsGetAllResponseSingle[];
scrollId: string;
}) => [setTablesFilterProjectsOptions({...projects})])
)
);
@@ -107,7 +153,7 @@ export class ProjectsEffects {
resetAncestorProjects$ = createEffect(() => this.actions$.pipe(
ofType(actions.setSelectedProjectId),
withLatestFrom(this.store.select(selectSelectedProjectId)),
concatLatestFrom(() => this.store.select(selectSelectedProjectId)),
filter(([action, prevProjectId]) => action.projectId !== prevProjectId),
mergeMap(() => [setProjectAncestors({projects: null})])
));
@@ -191,7 +237,14 @@ export class ProjectsEffects {
// eslint-disable-next-line @typescript-eslint/naming-convention
switchMap(action => this.projectsApi.projectsGetProjectTags({filter: {system_tags: [action.entity]}})
.pipe(
map((res: OrganizationGetTagsResponse) => actions.setTags({tags: res.tags})),
withLatestFrom(this.store.select(selectMainPageTagsFilter), this.store.select(selectProjectType)),
mergeMap(([res, fTags, projectType]) => [
actions.setTags({tags: res.tags}),
...(fTags?.length > 0 && fTags.some(fTag => !res.tags.includes(cleanTag(fTag))) ? [setMainPageTagsFilter({
tags: fTags.filter(fTag => res.tags.includes(cleanTag(fTag))),
feature: projectType
})] : []),
]),
catchError(error => [requestFailed(error)])
)
)
@@ -199,7 +252,7 @@ export class ProjectsEffects {
getTagsEffect = createEffect(() => this.actions$.pipe(
ofType(actions.getTags),
withLatestFrom(this.store.select(selectRouterParams).pipe(
concatLatestFrom(() => this.store.select(selectRouterParams).pipe(
map(params => (params === null || params?.projectId === '*') ? [] : [params.projectId]))),
mergeMap(([action, projects]) => {
const ids = action?.projectId ? [action.projectId] : projects;
@@ -239,10 +292,10 @@ export class ProjectsEffects {
fetchProjectStats = createEffect(() => this.actions$.pipe(
ofType(actions.fetchGraphData),
withLatestFrom(
concatLatestFrom(() => [
this.store.select(selectSelectedProjectId),
this.store.select(selectSelectedMetricVariantForCurrProject),
),
]),
filter(([, , variant]) => !!variant),
switchMap(([, projectId, variant]) => {
const col = createMetricColumn(variant, projectId);
@@ -290,10 +343,9 @@ export class ProjectsEffects {
getAllProjectsUsersEffect = createEffect(() => this.actions$.pipe(
ofType(actions.getAllSystemProjects),
switchMap(() => this.usersApi.usersGetAllEx({
switchMap(() => this.projectsApi.projectsGetUserNames({
/* eslint-disable @typescript-eslint/naming-convention */
order_by: ['name'],
only_fields: ['name'],
include_subprojects: true
/* eslint-enable @typescript-eslint/naming-convention */
}, null, 'body', true).pipe(
mergeMap(res => [actions.setAllProjectUsers(res)]),
@@ -306,17 +358,14 @@ export class ProjectsEffects {
getUsersEffect = createEffect(() => this.actions$.pipe(
ofType(actions.getProjectUsers),
withLatestFrom(
this.store.select(selectAllProjectsUsers)
concatLatestFrom(() => [this.store.select(selectAllProjectsUsers), this.store.select(selectIsDeepMode)]
),
switchMap(([action, all]) => (!action.projectId || action.projectId === '*' ?
switchMap(([action, all, isDeep]) => (!action.projectId || action.projectId === '*' ?
of({users: all}) :
this.usersApi.usersGetAllEx({
/* eslint-disable @typescript-eslint/naming-convention */
order_by: ['name'],
only_fields: ['name'],
active_in_projects: [action.projectId]
/* eslint-enable @typescript-eslint/naming-convention */
this.projectsApi.projectsGetUserNames({
projects: [action.projectId],
// eslint-disable-next-line @typescript-eslint/naming-convention
include_subprojects: isDeep
}, null, 'body', true)).pipe(
mergeMap(res => [actions.setProjectUsers(res)]),
catchError(error => [
@@ -347,6 +396,55 @@ export class ProjectsEffects {
))
));
// downloadForGetAll$ = createEffect(() => this.actions$.pipe(
// ofType(downloadForGetAll),
// filter(action => !!action.prepareId),
// withLatestFrom(this.store.select(selectActiveWorkspace)),
// switchMap(([action, workspace]) => fromFetch(`${HTTP.API_BASE_URL}/organization.download_for_get_all`,
// {
// ...(workspace?.id && {headers: {[TENANT_HEADER] : workspace.id}}),
// method: 'POST',
// credentials: 'include',
// // eslint-disable-next-line @typescript-eslint/naming-convention
// body: JSON.stringify({prepare_id: action.prepareId})
// })
// .pipe(
// switchMap(res => from(res.blob())),
// map(fileBlob => {
// const url = window.URL.createObjectURL(fileBlob);
// const a = document.createElement('a');
// a.href = url;
// a.target = '_blank';
// a.download = `full-table.csv`;
// a.click();
// })
// )),
// ), {dispatch: false});
downloadForGetAll$ = createEffect(() => this.actions$.pipe(
ofType(downloadForGetAll),
filter(action => !!action.prepareId),
)).subscribe((action) => {
const a = document.createElement('a');
a.href = `${HTTP.API_BASE_URL}/organization.download_for_get_all?prepare_id=${action.prepareId}`;
a.target = '_blank';
a.click();
}
);
private getRelevantTableFilters(routerConfig$: Observable<Params>) {
return routerConfig$.pipe(switchMap(config => {
if (config.includes('compare-experiments')) {
return this.store.select(selectCompareAddTableFilters);
} else if (config?.includes('models')) {
return this.store.select(selectTableFilters);
} else if (config?.includes('compare-models') || config?.includes('input-model')) {
return this.store.select(selectSelectModelTableFilters);
} else {
return this.store.select(selectExperimentsTableFilters);
}
}));
}
}

View File

@@ -3,9 +3,8 @@ import {ActivatedRoute, NavigationExtras, Params, Router} from '@angular/router'
import {Actions, createEffect, ofType} from '@ngrx/effects';
import {uniq} from 'lodash-es';
import {map, tap} from 'rxjs/operators';
import {NAVIGATION_ACTIONS} from '~/app.constants';
import {encodeFilters, encodeOrder} from '../../shared/utils/tableParamEncode';
import {NavigateTo, NavigationEnd, setRouterSegments, setURLParams} from '../actions/router.actions';
import {navigationEnd, setRouterSegments, setURLParams} from '../actions/router.actions';
@Injectable()
@@ -17,19 +16,15 @@ export class RouterEffects {
) {
}
// TODO: (itay) remove after delete old pages.
activeLoader = createEffect(() => this.actions$.pipe(
ofType<NavigateTo>(NAVIGATION_ACTIONS.NAVIGATE_TO),
tap(action => {
(!action.payload.params || !action.payload.url) ?
this.router.navigateByUrl(action.payload.url, /* Removed unsupported properties by Angular migration: queryParams. */ {}) :
this.router.navigate([action.payload.url, action.payload.params], {queryParams: {unGuard: action.payload.unGuard}});
})
), {dispatch: false});
routerNavigationEnd = createEffect(() => this.actions$.pipe(
ofType<NavigationEnd>(NAVIGATION_ACTIONS.NAVIGATION_END),
map(() => setRouterSegments({url: this.getRouterUrl(), params: this.getRouterParams(), config: this.getRouterConfig(), queryParams: this.route.snapshot.queryParams}))
ofType(navigationEnd),
map(() => setRouterSegments({
url: this.getRouterUrl(),
params: this.getRouterParams(),
config: this.getRouterConfig(),
queryParams: this.route.snapshot.queryParams,
data: this.route.snapshot.firstChild?.data
}))
));
setTableParams = createEffect(() => this.actions$.pipe(

View File

@@ -30,7 +30,7 @@ export class CommonUserEffects {
private actions: Actions, private userService: ApiUsersService,
private router: Router, private loginApi: ApiLoginService,
private serverService: ApiServerService,
private store: Store<any>, private errorService: ErrorService
private store: Store, private errorService: ErrorService
) {
}

View File

@@ -13,7 +13,7 @@ import {setCurrentUser} from '~/core/actions/users.action';
export class WebappInterceptor implements HttpInterceptor {
protected user: GetCurrentUserResponseUserObject;
constructor(protected router: Router, protected store: Store<any>) {
constructor(protected router: Router, protected store: Store) {
this.store.select(selectCurrentUser).subscribe(user => this.user = user);
}
@@ -33,7 +33,7 @@ export class WebappInterceptor implements HttpInterceptor {
protected errorHandler(request: HttpRequest<any>, err: HttpErrorResponse) {
const redirectUrl: string = window.location.pathname + window.location.search;
if (request.url.endsWith('system.company_info')) {
return throwError(err);
return throwError(() => err);
}
// For automatic login don't go to login page (login in APP_INITIALIZER)
if (err.status === 401 && (

View File

@@ -1,10 +0,0 @@
import {Action} from '@ngrx/store';
export interface ISmAction extends Action {
payload?: any;
meta?: ISmActionMeta; // deprecated
}
export interface ISmActionMeta {
loading?: boolean;
}

View File

@@ -7,7 +7,10 @@ import {ITableExperiment} from '../../experiments/shared/common-experiment-model
import {MetricColumn} from '@common/shared/utils/tableParamEncode';
import {User} from '~/business-logic/model/users/user';
import {ProjectsGetAllResponseSingle} from '~/business-logic/model/projects/projectsGetAllResponseSingle';
import {selectRouterConfig} from "@common/core/reducers/router-reducer";
import {selectRouterParams} from '@common/core/reducers/router-reducer';
import {IBreadcrumbsLink, IBreadcrumbsOptions} from '@common/layout/breadcrumbs/breadcrumbs.component';
import {selectProjectType} from '~/core/reducers/view.reducer';
import {uniqBy} from 'lodash-es';
export interface ProjectStatsGraphData {
@@ -18,10 +21,11 @@ export interface ProjectStatsGraphData {
type: string;
status: string;
user: string;
title?: string;
value: number;
}
export interface RootProjects {
projects: Project[];
selectedProject: Project;
projectAncestors: Project[];
archive: boolean;
@@ -40,17 +44,18 @@ export interface RootProjects {
extraUsers: User[];
showHidden: boolean;
hideExamples: boolean;
mainPageTagsFilter: { [Feature: string]: string[] };
mainPageTagsFilter: { [Feature: string]: { tags: string[]; filterMatchMode: string } };
mainPageTagsFilterMatchMode: string;
defaultNestedModeForFeature: { [feature: string]: boolean };
selectedSubFeature: IBreadcrumbsLink;
tablesFilterProjectsOptions: Partial<ProjectsGetAllResponseSingle>[];
projectsOptionsScrollId: string;
breadcrumbOptions: IBreadcrumbsOptions;
}
const initRootProjects: RootProjects = {
mainPageTagsFilter: {},
mainPageTagsFilterMatchMode: 'AND',
projects: null,
selectedProject: null,
projectAncestors: null,
archive: false,
@@ -70,13 +75,17 @@ const initRootProjects: RootProjects = {
showHidden: false,
hideExamples: false,
defaultNestedModeForFeature: {},
selectedSubFeature: null,
breadcrumbOptions: null,
tablesFilterProjectsOptions: null,
projectsOptionsScrollId: null
};
export const projects = state => state.rootProjects as RootProjects;
export const selectRootProjects = createSelector(projects, (state): Project[] => state.projects);
export const selectRouterProjectId = createSelector(selectRouterParams, params => params?.projectId);
export const selectSelectedProject = createSelector(projects, state => state.selectedProject);
export const selectSelectedBreadcrumbSubFeature = createSelector(projects, state => state.selectedSubFeature);
export const selectBreadcrumbOptions = createSelector(projects, state => state.breadcrumbOptions);
export const selectProjectAncestors = createSelector(projects, state => state.projectAncestors);
export const selectSelectedProjectDescription = createSelector(projects, state => state.selectedProject?.description);
export const selectSelectedProjectId = createSelector(selectSelectedProject, (selectedProject): string => selectedProject ? selectedProject.id : '');
@@ -85,8 +94,9 @@ export const selectIsDeepMode = createSelector(projects, state => state.deep);
export const selectTagsFilterByProject = createSelector(projects, selectSelectedProjectId,
(state, projectId) => projectId !== '*' && state.tagsFilterByProject);
export const selectProjectTags = createSelector(projects, state => state.projectTags);
export const selectMainPageTagsFilter = createSelector(projects, selectRouterConfig, (state, config) => config?.[0] ? state.mainPageTagsFilter[config?.[0]] : []);
export const selectMainPageTagsFilterMatchMode = createSelector(projects, state => state.mainPageTagsFilterMatchMode);
export const selectMainPageTagsFilter = createSelector(projects, selectProjectType,(state, projectType) => projectType? state.mainPageTagsFilter[projectType]?.tags : []);
export const selectMainPageTagsFilterMatchMode = createSelector(projects, selectProjectType, (state, projectType) => projectType? state.mainPageTagsFilter[projectType]?.filterMatchMode : null);
export const selectCompanyTags = createSelector(projects, state => state.companyTags);
// eslint-disable-next-line @typescript-eslint/naming-convention
export const selectProjectSystemTags = createSelector(projects, state => getSystemTags({system_tags: state.systemTags} as ITableExperiment));
@@ -107,12 +117,14 @@ export const selectProjectUsers = createSelector(projects, state => state.extraU
state.users
);
export const selectAllProjectsUsers = createSelector(projects, state => state.allUsers);
export const selectSelectedProjectUsers = createSelector(selectSelectedProjectId, selectProjectUsers, selectAllProjectsUsers,
(projectId, projectUsers, allUsers) => projectId === '*' ? allUsers : projectUsers);
export const selectTablesFilterProjectsOptions = createSelector(projects, state => state.tablesFilterProjectsOptions);
export const projectsReducer = createReducer(
initRootProjects,
on(projectsActions.resetProjects, state => ({...state, projects: [], lastUpdate: null})),
on(projectsActions.setSelectedProjectId, (state, action) => {
on(projectsActions.resetProjects, (state): RootProjects => ({...state, lastUpdate: null})),
on(projectsActions.setSelectedProjectId, (state, action): RootProjects => {
const projectId = action.projectId;
return {
...state,
@@ -120,79 +132,103 @@ export const projectsReducer = createReducer(
graphData: initRootProjects.graphData,
};
}),
on(projectsActions.setSelectedProject, (state, action) => ({
on(projectsActions.setSelectedProject, (state, action): RootProjects => ({
...state,
selectedProject: action.project,
extraUsers: []
})),
on(projectsActions.setProjectAncestors, (state, action) => ({...state, projectAncestors: action.projects})),
on(projectsActions.setSelectedProjectStats, (state, action) => ({
on(projectsActions.setProjectAncestors, (state, action): RootProjects => ({
...state,
projectAncestors: action.projects
})),
on(projectsActions.setSelectedBreadcrumbSubFeature, (state, action): RootProjects => ({
...state,
selectedSubFeature: action.breadcrumb
})),
on(projectsActions.setBreadcrumbsOptions, (state, action): RootProjects => ({
...state,
breadcrumbOptions: action.breadcrumbOptions
})),
on(projectsActions.setSelectedProjectStats, (state, action): RootProjects => ({
...state,
selectedProject: {
...state.selectedProject,
stats: action.project?.stats
}
})),
on(projectsActions.resetSelectedProject, state => ({
on(projectsActions.resetSelectedProject, (state): RootProjects => ({
...state,
selectedProject: initRootProjects.selectedProject,
users: [],
extraUsers: []
})),
on(projectsActions.updateProjectCompleted, (state, action) => ({
on(projectsActions.updateProjectCompleted, (state, action): RootProjects => ({
...state,
selectedProject: {...state.selectedProject, ...action.changes},
projects: state.projects.map(project => project.id === action.id ? project : {...project, ...action.changes})
})),
on(projectsActions.setArchive, (state, action) => ({...state, archive: action.archive})),
on(projectsActions.setDeep, (state, action) => ({...state, deep: action.deep})),
on(projectsActions.setTags, (state, action) => ({...state, projectTags: action.tags})),
on(projectsActions.setTagsFilterByProject, (state, action) => ({
on(projectsActions.setArchive, (state, action): RootProjects => ({...state, archive: action.archive})),
on(projectsActions.setDeep, (state, action): RootProjects => ({...state, deep: action.deep})),
on(projectsActions.setTags, (state, action): RootProjects => ({...state, projectTags: action.tags})),
on(projectsActions.setTagsFilterByProject, (state, action): RootProjects => ({
...state,
tagsFilterByProject: action.tagsFilterByProject
})),
on(projectsActions.setCompanyTags, (state, action) => ({
on(projectsActions.setCompanyTags, (state, action): RootProjects => ({
...state,
companyTags: action.tags,
systemTags: action.systemTags
})),
on(projectsActions.addProjectTags, (state, action) => ({
on(projectsActions.addProjectTags, (state, action): RootProjects => ({
...state,
projectTags: Array.from(new Set(state.projectTags.concat(action.tags))).sort()
})),
on(projectsActions.setMainPageTagsFilter, (state, action) => ({
on(projectsActions.setMainPageTagsFilter, (state, action): RootProjects => ({
...state,
mainPageTagsFilter: {...state.mainPageTagsFilter, [action.feature] : action.tags }})),
on(projectsActions.setMainPageTagsFilterMatchMode, (state, action) => ({
...state,
mainPageTagsFilterMatchMode: action.matchMode
mainPageTagsFilter: {
...state.mainPageTagsFilter,
[action.feature]: {...state.mainPageTagsFilter[action.feature], tags: action.tags}
}
})),
on(projectsActions.setTagColors, (state, action) => ({
on(projectsActions.setMainPageTagsFilterMatchMode, (state, action): RootProjects => ({
...state,
mainPageTagsFilter: {
...state.mainPageTagsFilter,
[action.feature]: {...state.mainPageTagsFilter[action.feature], filterMatchMode: action.matchMode}
}
})),
on(projectsActions.setTagColors, (state, action): RootProjects => ({
...state,
tagsColors: {...state.tagsColors, [action.tag]: action.colors}
})),
on(projectsActions.setMetricVariant, (state, action) => ({
on(projectsActions.setMetricVariant, (state, action): RootProjects => ({
...state, graphVariant: {...state.graphVariant, [action.projectId]: action.col}
})),
on(projectsActions.setGraphData, (state, action) => ({...state, graphData: action.stats})),
on(projectsActions.toggleState, (state, action) => ({
on(projectsActions.setGraphData, (state, action): RootProjects => ({...state, graphData: action.stats})),
on(projectsActions.toggleState, (state, action): RootProjects => ({
...state,
hiddenStates: {...state.hiddenStates, [action.state]: !state.hiddenStates?.[action.state]}
})),
on(projectsActions.setLastUpdate, (state, action) => ({...state, lastUpdate: action.lastUpdate})),
on(projectsActions.setProjectUsers, (state, action) => ({...state, users: action.users, extraUsers: []})),
on(projectsActions.setAllProjectUsers, (state, action) => ({...state, allUsers: action.users})),
on(projectsActions.setProjectExtraUsers, (state, action) => ({...state, extraUsers: action.users})),
on(projectsActions.setShowHidden, (state, action) => ({...state, showHidden: action.show})),
on(projectsActions.setHideExamples, (state, action) => ({...state, hideExamples: action.hide})),
on(projectsActions.setDefaultNestedModeForFeature, (state, action) => ({
on(projectsActions.setLastUpdate, (state, action): RootProjects => ({...state, lastUpdate: action.lastUpdate})),
on(projectsActions.setProjectUsers, (state, action): RootProjects => ({
...state,
users: action.users,
extraUsers: []
})),
on(projectsActions.setAllProjectUsers, (state, action): RootProjects => ({...state, allUsers: action.users})),
on(projectsActions.setProjectExtraUsers, (state, action): RootProjects => ({...state, extraUsers: action.users})),
on(projectsActions.setShowHidden, (state, action): RootProjects => ({...state, showHidden: action.show})),
on(projectsActions.setHideExamples, (state, action): RootProjects => ({...state, hideExamples: action.hide})),
on(projectsActions.setDefaultNestedModeForFeature, (state, action): RootProjects => ({
...state,
defaultNestedModeForFeature: {...state.defaultNestedModeForFeature, [action.feature]: action.isNested}
})),
on(projectsActions.resetTablesFilterProjectsOptions, (state) => ({...state, tablesFilterProjectsOptions: null})),
on(projectsActions.setTablesFilterProjectsOptions, (state, action) => ({
on(projectsActions.resetTablesFilterProjectsOptions, (state): RootProjects => ({
...state,
tablesFilterProjectsOptions: action.loadMore ? (state.tablesFilterProjectsOptions || []).concat(action.projects) : action.projects,
tablesFilterProjectsOptions: null
})),
on(projectsActions.setTablesFilterProjectsOptions, (state, action): RootProjects => ({
...state,
tablesFilterProjectsOptions: action.loadMore ? uniqBy((state.tablesFilterProjectsOptions || []).concat(action.projects), 'id') : uniqBy(action.projects, 'id'),
projectsOptionsScrollId: action.scrollId
}))
);

View File

@@ -1,6 +1,6 @@
import {RECENT_TASKS_ACTIONS} from '../../../app.constants';
import {SetRecentTasks} from '../actions/recent-tasks.actions';
import {Task} from '../../../business-logic/model/tasks/task';
import {setRecentTasks} from '../actions/recent-tasks.actions';
import {Task} from '~/business-logic/model/tasks/task';
import {createReducer, on} from '@ngrx/store';
const initTasks = {
data : <Array<Partial<Task>>>[],
@@ -8,11 +8,7 @@ const initTasks = {
export const recentTasks = state => state.recentTasks;
export function recentTasksReducer(state = initTasks, action: SetRecentTasks) {
switch (action.type) {
case RECENT_TASKS_ACTIONS.SET_RECENT_TASKS:
return {...state, data: action.payload.tasks};
default:
return state;
}
}
export const recentTasksReducer = createReducer(
initTasks,
on(setRecentTasks, (state, action) => ({...state, data: action.tasks}))
);

View File

@@ -8,6 +8,7 @@ export interface RouterState {
queryParams: Params;
config: string[];
skipNextNavigation: boolean;
data: any;
}
const initRouter: RouterState = {
@@ -16,6 +17,7 @@ const initRouter: RouterState = {
queryParams: null,
config: null,
skipNextNavigation: false,
data: null
};
export const selectRouter = state => state.router as RouterState;
@@ -23,10 +25,11 @@ export const selectRouterUrl = createSelector(selectRouter, router => router &&
export const selectRouterParams = createSelector(selectRouter, router => router && router?.params);
export const selectRouterQueryParams = createSelector(selectRouter, router => router && router.queryParams);
export const selectRouterConfig = createSelector(selectRouter, router => router && router.config);
export const selectRouterData = createSelector(selectRouter, router => router && router.data);
export const routerReducer = createReducer(initRouter,
on(setRouterSegments, (state, action) => ({
on(setRouterSegments, (state, action): RouterState => ({
...state, params: action.params, queryParams: action.queryParams,
url: action.url, config: action.config
url: action.url, config: action.config, data: action.data
})),
);

View File

@@ -23,6 +23,7 @@ export interface UsersState {
showOnlyUserWork: boolean;
serverVersions: { server: string; api: string };
gettingStarted: any;
settings: any;
}
export const initUsers: UsersState = {
@@ -33,11 +34,13 @@ export const initUsers: UsersState = {
workspaces: [],
showOnlyUserWork: false,
serverVersions: null,
gettingStarted: null
gettingStarted: null,
settings: null,
};
export const users = state => state.users as UsersState;
export const selectSettings = createSelector(users, (state): any => state?.settings);
export const selectMaxDownloadItems = createSelector(selectSettings, (state): number => state?.max_download_items ?? 1000);
export const selectCurrentUser = createSelector(users, state => state.currentUser);
export const selectActiveWorkspace = createSelector(users, state => state.activeWorkspace);
export const selectUserWorkspaces = createSelector(users, state => state.userWorkspaces);

View File

@@ -82,7 +82,6 @@ export const selectShowEmbedReportMenu = createSelector(views, state => state.sh
export const selectBreadcrumbs = createSelector(views, state => state && state.breadcrumbs);
export const viewReducers = [
on(requestFailed, (state, action) => {
const isLoggedOut = action.err && action.err.status === 401;

View File

@@ -5,7 +5,7 @@ import {take} from 'rxjs/operators';
@Injectable()
export class SmGetStateService {
constructor(private store: Store<any>) {}
constructor(private store: Store) {}
getState() {
let _state: any;

View File

@@ -5,7 +5,7 @@ import {take} from 'rxjs/operators';
@Injectable()
export class SmSyncStateSelectorService {
constructor(private store: Store<any>) {
constructor(private store: Store) {
}
selectSync(selector: Selector<any, any>) {

View File

@@ -29,7 +29,7 @@ import {ApiModelsService} from '~/business-logic/api-services/models.service';
import {catchError, mergeMap, map, switchMap, withLatestFrom} from 'rxjs/operators';
import {isEqual} from 'lodash-es';
import {activeSearchLink} from '~/features/dashboard-search/dashboard-search.consts';
import {EmptyAction} from '~/app.constants';
import {emptyAction} from '~/app.constants';
import {escapeRegex} from '@common/shared/utils/escape-regex';
import {selectCurrentUser, selectShowOnlyUserWork} from '@common/core/reducers/users-reducer';
import {selectHideExamples, selectShowHidden} from '@common/core/reducers/projects.reducer';
@@ -60,6 +60,7 @@ export const getEntityStatQuery = (action, searchHidden) => ({
...(action.query && {pattern: action.regExp ? action.query : escapeRegex(action.query)}),
fields: ['name', 'id']
},
system_tags: ['-archived']
},
datasets: {
_any_: {
@@ -148,7 +149,7 @@ export class DashboardSearchEffects {
case activeSearchLink.reports:
return searchReports(term);
}
return new EmptyAction();
return emptyAction();
}
)
));

View File

@@ -1,6 +1,6 @@
import {Injectable} from '@angular/core';
import {ApiProjectsService} from '~/business-logic/api-services/projects.service';
import {Actions, createEffect, ofType} from '@ngrx/effects';
import {Actions, concatLatestFrom, createEffect, ofType} from '@ngrx/effects';
import {requestFailed} from '../core/actions/http.actions';
import {activeLoader, deactivateLoader} from '../core/actions/layout.actions';
import {
@@ -12,7 +12,7 @@ import {
import {CARDS_IN_ROW} from './common-dashboard.const';
import {ApiTasksService} from '~/business-logic/api-services/tasks.service';
import {ProjectsGetAllExRequest} from '~/business-logic/model/projects/projectsGetAllExRequest';
import {catchError, mergeMap, map, switchMap, withLatestFrom} from 'rxjs/operators';
import {catchError, mergeMap, map, switchMap} from 'rxjs/operators';
import {ApiLoginService} from '~/business-logic/api-services/login.service';
import {Store} from '@ngrx/store';
import {ErrorService} from '../shared/services/error.service';
@@ -24,7 +24,7 @@ export class CommonDashboardEffects {
constructor(
private actions: Actions, private projectsApi: ApiProjectsService,
private tasksApi: ApiTasksService, private loginApi: ApiLoginService,
private errorService: ErrorService, private store: Store<any>,
private errorService: ErrorService, private store: Store,
) {}
/* eslint-disable @typescript-eslint/naming-convention */
activeLoader = createEffect(() => this.actions.pipe(
@@ -34,12 +34,12 @@ export class CommonDashboardEffects {
getRecentProjects = createEffect(() => this.actions.pipe(
ofType(getRecentProjects),
withLatestFrom(
concatLatestFrom(() => [
this.store.select(selectCurrentUser),
this.store.select(selectShowOnlyUserWork),
this.store.select(selectShowHidden),
this.store.select(selectHideExamples),
),
]),
mergeMap(([action, user, showOnlyUserWork, showHidden, hideExamples]) =>
this.projectsApi.projectsGetAllEx({
stats_for_state: ProjectsGetAllExRequest.StatsForStateEnum.Active,
@@ -52,7 +52,7 @@ export class CommonDashboardEffects {
...(showHidden && {search_hidden: true}),
...(!showHidden && {include_stats_filter: {system_tags: ['-pipeline']}}),
...(hideExamples && {allow_public: false}),
only_fields: ['name', 'company', 'user', 'created', 'default_output_destination']
only_fields: ['name', 'basename', 'company', 'user', 'created', 'default_output_destination']
}).pipe(
mergeMap(({projects}) => [setRecentProjects({projects}), deactivateLoader(action.type)]),
catchError(error => [deactivateLoader(action.type), requestFailed(error)])
@@ -62,11 +62,11 @@ export class CommonDashboardEffects {
getRecentTasks = createEffect(() => this.actions.pipe(
ofType(getRecentExperiments),
withLatestFrom(
concatLatestFrom(() => [
this.store.select(selectCurrentUser),
this.store.select(selectShowOnlyUserWork),
this.store.select(selectHideExamples),
),
]),
switchMap(([action, user, showOnlyUserWork, hideExamples]) => this.tasksApi.tasksGetAllEx({
page: 0,
page_size: 5,

View File

@@ -15,7 +15,7 @@ import {filter, take} from 'rxjs/operators';
export class DashboardExperimentsComponent implements OnInit {
@Input() recentTasks: IRecentTask[];
constructor(private store: Store<any>, private router: Router) {
constructor(private store: Store, private router: Router) {
}
ngOnInit() {

View File

@@ -30,7 +30,7 @@ export class DashboardProjectsComponent implements OnInit, AfterViewInit, OnDest
@Output() width = new EventEmitter<number>();
constructor(
private store: Store<any>,
private store: Store,
public router: Router,
private matDialog: MatDialog
) {

View File

@@ -66,7 +66,7 @@ export class DashboardSearchBaseComponent implements OnInit, OnDestroy{
public resultsCount$: Observable<Map<ActiveSearchLink, number>>;
public reportsResults$: Observable<Array<IReport>>;
constructor(public store: Store<any>, public router: Router){
constructor(public store: Store, public router: Router){
this.searchQuery$ = store.select(selectSearchQuery);
this.activeSearch$ = store.select(selectActiveSearch);
this.modelsResults$ = store.select(selectModelsResults);

View File

@@ -1,5 +1,5 @@
<div [id]="step.stepId" class="step-container pointer"
[class.selected]="selected">
[class.selected]="selected" data-id="processSteps">
<div class="step-part step-title" [class]="step?.data?.status">
<div
class="title"
@@ -7,12 +7,12 @@
[smTooltip]="step?.data?.name ?? step.name + step?.data?.version ? ' v' + step.data.version : ''"
matTooltipPosition="above"
>{{step?.data?.name ?? step.name}}<ng-container *ngIf="step?.data?.version"> v{{step.data.version}}</ng-container></div>
<i class="al-icon sm-md al-ico-console" (click)="openConsole.emit()"></i>
<i data-id="consoleIcon" class="al-icon sm-md al-ico-console" (click)="openConsole.emit()"></i>
</div>
<div class="step-part step-footer" [class]="step?.data?.status">
<div *ngIf="step?.data?.job_size">{{step.data.job_size | filesize: fileSizeConfigStorage}}</div>
<div class="d-flex-center" *ngIf="step?.data?.last_update">
<i class="al-icon al-ico-upload sm me-1"></i>{{step.data.last_update * 1000 | timeAgo}}
<i class="al-icon al-ico-upload sm me-1" data-id="uploadIcon"></i>{{step.data.last_update * 1000 | timeAgo}}
</div>
</div>
</div>

View File

@@ -19,7 +19,7 @@
font-size: 12px;
&.in_progress {
background-color: $running-green;
background-color: darken($running-green, 20%);
}
}

View File

@@ -24,6 +24,7 @@
(smHesitate)="menuHesitate.hesitateStatus && menu.closed.emit()"
>
<i class="al-icon al-ico-download pointer line-item"
data-id="downloadButton"
#idElement
[matMenuTriggerFor]="menu"
(click)="openMenu(); menuHesitate.hesitateStatus = true"

View File

@@ -4,16 +4,17 @@
</mat-expansion-panel-header>
<div class="panel-body" *ngIf="entity">
<div class="header mt-2">
<div class="name ellipsis">{{entity.name}}<ng-container *ngIf="entity?.runtime?.version"> v{{entity.runtime.version}}</ng-container></div>
<span class="status" [class]="entity.status">{{entity.status | replaceViaMapPipe:convertStatusMap | replaceViaMapPipe:convertStatusMapBase}}</span>
<div class="name ellipsis" data-id="datasetName" >{{entity.name}}<ng-container *ngIf="entity?.runtime?.version"> v{{entity.runtime.version}}</ng-container></div>
<span data-id="datasetStatus" class="status" [class]="entity.status">{{entity.status | replaceViaMapPipe:convertStatusMap | replaceViaMapPipe:convertStatusMapBase}}</span>
</div>
<div class="section">
<div class="param">
<div class="key">ID</div>
<div class="key" data-id="id" >ID</div>
<div class="value d-flex align-item-center justify-content-between" [smTooltip]="entity.id" smShowTooltipIfEllipsis>
<span>{{entity.id?.slice(0, 8)}}...</span>
<i
class="pointer al-icon al-ico-copy-to-clipboard sm"
data-id="copyIcon"
ngxClipboard
[cbContent]="entity?.id"
(cbOnSuccess)="copyToClipboard()"
@@ -21,8 +22,8 @@
</div>
</div>
<div class="param">
<div class="key">Parent</div>
<div class="value" [smTooltip]="entity?.parent?.name" smShowTooltipIfEllipsis>
<div class="key" data-id="parent">Parent</div>
<div data-id="parentValue" class="value" [smTooltip]="entity?.parent?.name" smShowTooltipIfEllipsis>
<a
*ngIf="entity?.parent?.id; else: empty"
class="arr-link"
@@ -36,39 +37,39 @@
</div>
</div>
<div class="param continue">
<div class="key">Size</div>
<div class="value" [smTooltip]="entity?.runtime?.ds_total_size + ' (original)'" smShowTooltipIfEllipsis>{{($any(entity?.runtime?.ds_total_size) | filesize : fileSizeConfigStorage) || '-'}}<span class="comment">(original)</span></div>
<div class="key" data-id="size">Size</div>
<div data-id="sizeValue" class="value" [smTooltip]="entity?.runtime?.ds_total_size + ' (original)'" smShowTooltipIfEllipsis>{{($any(entity?.runtime?.ds_total_size) | filesize : fileSizeConfigStorage) || '-'}}<span class="comment">(original)</span></div>
</div>
<div class="param">
<div class="key"></div>
<div class="value" [smTooltip]="entity?.runtime?.ds_total_size_compressed + ' (compressed)'" smShowTooltipIfEllipsis>{{($any(entity?.runtime?.ds_total_size_compressed) | filesize : fileSizeConfigStorage) || '-'}}<span class="comment">(compressed)</span></div>
</div>
<div class="param">
<div class="key">File count</div>
<div class="value" [smTooltip]="entity?.runtime?.ds_file_count" smShowTooltipIfEllipsis>{{entity?.runtime?.ds_file_count ?? '-'}}</div>
<div class="key" data-id="fileCount">File count</div>
<div data-id="fileCountValue" class="value" [smTooltip]="entity?.runtime?.ds_file_count" smShowTooltipIfEllipsis>{{entity?.runtime?.ds_file_count ?? '-'}}</div>
</div>
<div class="param">
<div class="key">Link count</div>
<div class="value" [smTooltip]="entity?.runtime?.ds_link_count" smShowTooltipIfEllipsis>{{entity?.runtime?.ds_link_count ?? '-'}}</div>
<div class="key" data-id="linkCount" >Link count</div>
<div data-id="linkCountValue" class="value" [smTooltip]="entity?.runtime?.ds_link_count" smShowTooltipIfEllipsis>{{entity?.runtime?.ds_link_count ?? '-'}}</div>
</div>
</div>
<div class="section">
<div class="header">FILES CHANGED</div>
<div class="param">
<div class="key">Added</div>
<div class="value" [smTooltip]="entity?.runtime?.ds_change_add " smShowTooltipIfEllipsis>{{entity?.runtime?.ds_change_add ?? '-'}}</div>
<div class="key" data-id="added">Added</div>
<div data-id="addedValue" class="value" [smTooltip]="entity?.runtime?.ds_change_add " smShowTooltipIfEllipsis>{{entity?.runtime?.ds_change_add ?? '-'}}</div>
</div>
<div class="param">
<div class="key">Modified</div>
<div class="value" [smTooltip]="entity?.runtime?.ds_change_modify " smShowTooltipIfEllipsis>{{entity?.runtime?.ds_change_modify ?? '-'}}</div>
<div class="key" data-id="modified">Modified</div>
<div data-id="modifiedValue" class="value" [smTooltip]="entity?.runtime?.ds_change_modify " smShowTooltipIfEllipsis>{{entity?.runtime?.ds_change_modify ?? '-'}}</div>
</div>
<div class="param">
<div class="key">Removed</div>
<div class="value" [smTooltip]="entity?.runtime?.ds_change_remove " smShowTooltipIfEllipsis>{{entity?.runtime?.ds_change_remove ?? '-'}}</div>
<div class="key" data-id="removed">Removed</div>
<div data-id="removedValue" class="value" [smTooltip]="entity?.runtime?.ds_change_remove " smShowTooltipIfEllipsis>{{entity?.runtime?.ds_change_remove ?? '-'}}</div>
</div>
<div class="param">
<div class="key">Size</div>
<div class="value"
<div class="key" data-id="size">Size</div>
<div data-id="sizeValue" class="value"
*ngIf="entity?.runtime?.ds_change_size"
[smTooltip]="$any(entity?.runtime?.ds_change_size) | filesize: fileSizeConfigStorage"
smShowTooltipIfEllipsis
@@ -76,7 +77,7 @@
</div>
</div>
<div class="section">
<div class="header"><span>DESCRIPTION</span><i class="al-icon al-ico-edit sm" (click)="editDescription.emit(entity)"></i></div>
<div class="header"><span>DESCRIPTION</span><i data-id="editIcon" class="al-icon al-ico-edit sm" (click)="editDescription.emit(entity)"></i></div>
<div class="param continue h-auto">
<div class="multi-line-value">{{entity?.comment}}</div>
</div>
@@ -87,7 +88,7 @@
class="arr-link"
target="_blank"
[href]="'/projects/' + project + '/experiments/' + entity?.id + '/output/execution'">
Task information<i class="al-icon al-ico-link-arrow sm"></i>
Task information<i class="al-icon al-ico-link-arrow sm" data-id="taskInfoIcon" ></i>
</a>
</footer>
</mat-expansion-panel>

View File

@@ -5,7 +5,7 @@
(editDescription)="editDescription($event)"
></sm-simple-dataset-version-details>
<div class="console-button">
<button class="btn btn-cml-primary d-flex align-items-center" (click)="toggleDetails()">
<button data-id="detailsButton" class="btn btn-cml-primary d-flex align-items-center" (click)="toggleDetails()">
<i class="al-icon al-ico-console sm me-3"></i>DETAILS
</button>
</div>
@@ -63,8 +63,8 @@
(valueChanged)="detailsPanelMode = $event"
></sm-button-toggle>
<div class="close">
<i class="al-icon pointer" [class]="maximizeResults ? 'al-ico-min-panel' : 'al-ico-max-panel'" (click)="toggleResultSize()"></i>
<i class="al-icon al-ico-dialog-x pointer ms-4" (click)="openLog(false)"></i>
<i class="al-icon pointer" [class]="maximizeResults ? 'al-ico-min-panel' : 'al-ico-max-panel'" (click)="toggleResultSize()" data-id="expandButton"></i>
<i data-id="crossButton" class="al-icon al-ico-dialog-x pointer ms-4" (click)="openLog(false)"></i>
</div>
</div>
<div [ngSwitch]="detailsPanelMode" class="content">

View File

@@ -26,7 +26,7 @@ export class SimpleDatasetVersionInfoComponent extends PipelineControllerInfoCom
constructor(
protected _dagManager: DagManagerUnsortedService<PipelineItem>,
protected store: Store<any>,
protected store: Store,
protected cdr: ChangeDetectorRef,
protected zone: NgZone,
private dialog: MatDialog,

View File

@@ -7,13 +7,13 @@
[style.top.px]="position.y"
[matMenuTriggerFor]="experimentMenu">
</div>
<mat-menu #experimentMenu="matMenu" [hasBackdrop]="false">
<mat-menu #experimentMenu="matMenu" [hasBackdrop]="false" class="light-theme entity-context-menu">
<button *ngIf="tableMode" mat-menu-item (click)="toggleDetails()">
<i [class]="'al-icon '+ icons.DETAILS + ' sm-md'"></i>Details
</button>
<hr *ngIf="tableMode">
<button mat-menu-item
[matMenuTriggerFor]="tagMenu"
[matMenuTriggerFor]="tagMenu.matMenu"
[disabled]="selectedDisableAvailable[menuItems.tags]?.disable"
(menuOpened)="tagMenuOpened()"
(menuClosed)="tagMenuClosed()">
@@ -34,14 +34,11 @@
</button>
</mat-menu>
<mat-menu #tagMenu="matMenu">
<sm-tags-menu
#tagMenuContent
class="light-theme"
[tags]="experiment?.tags"
[tagsFilterByProject]="tagsFilterByProject"
[projectTags]="projectTags"
[companyTags]="companyTags"
(tagSelected)="tagSelected.emit($event)">
</sm-tags-menu>
</mat-menu>
<sm-tags-menu
#tagMenu
[tags]="experiment?.tags"
[tagsFilterByProject]="tagsFilterByProject"
[projectTags]="projectTags"
[companyTags]="companyTags"
(tagSelected)="tagSelected.emit($event)">
</sm-tags-menu>

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