From aa038f4f8253f45558efe78cc43cf24045ff13d7 Mon Sep 17 00:00:00 2001 From: shyallegro <52773084+shyallegro@users.noreply.github.com> Date: Wed, 2 Aug 2023 15:35:38 +0300 Subject: [PATCH] Release v1.12 (#61) Co-authored-by: shallegro --- angular.json | 11 +- package-lock.json | 3421 +++++++++-------- package.json | 18 +- proxy.config.js | 2 +- src/app/app.component.ts | 2 +- src/app/app.constants.ts | 33 +- src/app/app.routes.ts | 7 +- .../api-services/organization.service.ts | 51 +- .../api-services/pipelines.service.ts | 47 + .../api-services/projects.service.ts | 49 + .../model/organization/fieldMapping.ts | 28 + ...nizationPrepareDownloadForGetAllRequest.ts | 52 + ...izationPrepareDownloadForGetAllResponse.ts | 20 + .../pipelines/pipelinesDeleteRunsRequest.ts | 24 + .../pipelines/pipelinesDeleteRunsResponse.ts | 20 + .../pipelinesDeleteRunsResponseError.ts | 22 + .../pipelinesDeleteRunsResponseFailed.ts | 22 + .../pipelinesDeleteRunsResponseSucceeded.ts | 40 + .../pipelinesStartPipelineRequest.ts | 5 +- .../pipelinesStartPipelineRequestArgs.ts | 17 + .../model/projects/projectsDeleteRequest.ts | 4 + .../model/projects/projectsGetAllExRequest.ts | 236 +- .../projects/projectsGetUserNamesRequest.ts | 36 + .../projects/projectsGetUserNamesResponse.ts | 21 + .../projectsGetUserNamesResponseUsers.ts | 24 + .../projectsValidateDeleteResponse.ts | 16 + .../model/tasks/tasksEnqueueManyResponse.ts | 4 + .../users/usersGetCurrentUserResponse.ts | 9 +- .../usersGetCurrentUserResponseSettings.ts | 20 + src/app/core/actions/users.action.ts | 4 +- src/app/core/core.module.ts | 6 +- src/app/core/effects/projects.effects.ts | 1 - src/app/core/reducers/recent-tasks-reducer.ts | 17 - src/app/core/reducers/users.reducer.ts | 1 + src/app/core/reducers/view.reducer.ts | 4 + .../search-results-page.component.html | 7 +- .../search-results-page.component.scss | 19 +- .../datasets/datasets-routing.module.ts | 6 +- src/app/features/datasets/datasets.module.ts | 6 +- .../nested-datasets-page.component.html | 57 + .../nested-datasets-page.component.ts | 57 + .../delete-entity/delete-dialog.effects.ts | 18 +- .../experiments/experiments.module.ts | 2 + .../shared/experiment-execution.model.ts | 3 +- .../feature-nested-project-view.module.ts | 19 - ...-project-view-page-extended.component.html | 2 - ...-project-view-page-extended.component.scss | 0 ...oject-view-page-extended.component.spec.ts | 33 - ...ed-project-view-page-extended.component.ts | 14 - .../nested-project-view-utils.ts | 31 - .../features/projects/projects-page.utils.ts | 25 +- src/app/features/projects/projects.actions.ts | 8 + src/app/features/projects/projects.effect.ts | 32 +- src/app/features/projects/projects.reducer.ts | 27 +- .../projects/shared/projects-shared.module.ts | 4 + .../user-credentials.component.html | 2 +- .../assets/fonts/trains-icons.scss | 12 +- src/app/webapp-common/assets/fonts/trains.ttf | Bin 63112 -> 60136 bytes .../webapp-common/assets/fonts/variables.scss | 2 + .../report-widgets/src/app/app.actions.ts | 1 + .../report-widgets/src/app/app.component.html | 1 + .../report-widgets/src/app/app.component.ts | 45 +- .../report-widgets/src/app/app.effects.ts | 8 +- .../report-widgets/src/app/app.reducer.ts | 2 +- .../src/environments/environment.ts | 2 + .../report-widgets/src/styles.scss | 139 + .../common-search/common-search.component.ts | 2 +- src/app/webapp-common/common-styles.scss | 24 +- .../core/actions/projects.actions.ts | 26 +- .../core/actions/recent-tasks.actions.ts | 14 +- .../core/actions/router.actions.ts | 30 +- .../core/actions/tasks.actions.ts | 28 +- .../webapp-common/core/cache/cache.module.ts | 12 - .../core/cache/entities-cache.service.ts | 70 - .../core/effects/common-auth.effects.ts | 2 +- .../core/effects/layout.effects.ts | 4 +- .../core/effects/projects.effects.ts | 162 +- .../core/effects/router.effects.ts | 23 +- .../core/effects/users.effects.ts | 2 +- .../core/interceptors/webapp-interceptor.ts | 4 +- src/app/webapp-common/core/models/actions.ts | 10 - .../core/reducers/projects.reducer.ts | 116 +- .../core/reducers/recent-tasks-reducer.ts | 18 +- .../core/reducers/router-reducer.ts | 7 +- .../core/reducers/users-reducer.ts | 7 +- .../core/reducers/view.reducer.ts | 1 - .../core/services/get-state.service.ts | 2 +- .../services/sync-state-selector.service.ts | 2 +- .../dashboard-search.effects.ts | 5 +- .../dashboard/common-dashboard.effects.ts | 16 +- .../dashboard-experiments.component.ts | 2 +- .../dashboard-projects.component.ts | 2 +- .../dashboard-search.component.base.ts | 2 +- .../dataset-version-step.component.html | 6 +- .../dataset-version-step.component.scss | 2 +- ...ple-dataset-version-content.component.html | 1 + ...ple-dataset-version-details.component.html | 43 +- ...simple-dataset-version-info.component.html | 6 +- .../simple-dataset-version-info.component.ts | 2 +- ...simple-dataset-version-menu.component.html | 23 +- ...ple-dataset-version-preview.component.html | 4 +- ...ple-dataset-version-preview.component.scss | 10 + ...imple-dataset-version-preview.component.ts | 6 +- .../simple-dataset-versions.component.html | 3 + .../simple-dataset-versions.component.ts | 37 +- ...nested-simple-datasets-page.component.html | 60 + .../nested-simple-datasets-page.component.ts | 126 + .../simple-dataset-card.component.html | 7 +- .../datasets/simple-datasets/empty.scss | 33 + .../simple-datasets.component.html | 2 +- .../simple-datasets.component.scss | 34 +- .../simple-datasets.component.spec.ts | 25 - .../simple-datasets.component.ts | 31 +- .../debug-images-view.component.html | 2 +- .../debug-images/debug-images.component.html | 18 +- .../debug-images/debug-images.component.ts | 89 +- .../actions/compare-header.actions.ts | 2 +- .../containers/experiment-compare-base.ts | 2 +- .../experiment-compare-details.component.ts | 2 +- ...-compare-hyper-params-graph.component.html | 13 +- ...nt-compare-hyper-params-graph.component.ts | 86 +- ...iment-compare-scalar-charts.component.html | 6 +- ...eriment-compare-scalar-charts.component.ts | 35 +- ...eriment-compare-metric-values.component.ts | 2 +- .../experiment-compare-params.component.ts | 2 +- .../experiment-compare-plots.component.html | 1 + .../experiment-compare-plots.component.ts | 11 +- .../model-compare-details.component.ts | 3 +- ...elect-experiments-for-compare.component.ts | 54 +- .../experiment-compare-header.component.ts | 2 +- .../parallel-coordinates-graph.component.ts | 4 +- .../experiments-compare-charts.effects.ts | 2 +- .../experiments-compare-details.effects.ts | 2 +- .../experiments-compare-params.effects.ts | 2 +- ...-experiment-for-compare-effects.service.ts | 16 +- .../experiment-compare-router-helper.guard.ts | 2 +- .../experiments-compare.component.ts | 105 +- .../experiments-compare.module.ts | 45 +- .../reducers/compare-header.reducer.ts | 9 +- .../experiments-compare-charts.reducer.ts | 4 +- .../experiments-compare/reducers/index.ts | 6 +- .../common-experiments-menu.actions.ts | 4 + .../common-experiments-view.actions.ts | 16 +- .../experiment-info-aritfacts.component.scss | 20 +- .../experiment-info-artifacts.component.html | 4 +- .../experiment-info-artifacts.component.ts | 2 +- ...experiment-info-artifact-item.component.ts | 2 +- .../experiment-info-execution.component.html | 18 +- .../experiment-info-execution.component.scss | 2 +- .../experiment-info-execution.component.ts | 15 +- .../experiment-info-general.component.html | 2 +- .../experiment-info-general.component.ts | 28 +- ...r-parameters-form-container.component.html | 5 +- ...per-parameters-form-container.component.ts | 27 +- ...iment-info-hyper-parameters.component.html | 2 +- ...eriment-info-hyper-parameters.component.ts | 2 +- .../experiment-info-model.component.ts | 4 +- .../experiment-info-task-model.component.ts | 2 +- .../base-experiment-output.component.scss | 12 +- .../base-experiment-output.component.ts | 31 +- .../experiment-output-log.component.ts | 2 +- .../experiment-output-plots.component.html | 4 +- .../experiment-output-plots.component.scss | 2 +- .../experiment-output-plots.component.ts | 22 +- .../experiment-output-scalars.component.html | 12 +- .../experiment-output-scalars.component.scss | 12 +- .../experiment-output-scalars.component.ts | 39 +- .../shared-experiment-output.scss | 12 +- .../dumb/base-clickable-artifact.component.ts | 2 +- ...periment-artifact-item-view.component.html | 6 +- ...experiment-artifact-item-view.component.ts | 2 +- ...riment-execution-parameters.component.html | 7 +- ...riment-execution-parameters.component.scss | 4 + ...iment-execution-source-code.component.html | 29 +- ...iment-execution-source-code.component.scss | 6 + ...eriment-execution-source-code.component.ts | 7 +- .../experiment-general-info.component.html | 8 +- .../experiment-general-info.component.ts | 30 +- .../experiment-header.component.html | 9 +- .../experiment-header.component.scss | 6 + .../experiment-header.component.ts | 5 - ...eriment-hyper-params-navbar.component.html | 7 +- ...iment-info-edit-description.component.html | 4 +- ...eriment-info-edit-description.component.ts | 4 +- .../experiment-info-header.component.html | 39 +- .../experiment-info-header.component.scss | 9 +- .../experiment-info-header.component.ts | 32 +- .../experiment-log-info.component.html | 6 +- ...experiment-models-form-view.component.html | 2 +- .../experiment-models-form-view.component.ts | 21 +- ...xperiment-output-model-view.component.html | 10 +- .../experiment-output-model-view.component.ts | 2 +- .../experiments-table.component.html | 2 +- .../experiments-table.component.ts | 6 +- .../common-experiment-output.effects.ts | 10 +- .../common-experiments-info.effects.ts | 11 +- .../common-experiments-menu.effects.ts | 116 +- .../common-experiments-view.effects.ts | 263 +- .../experiments/experiment-routes.ts | 15 +- .../experiments/experiment.consts.ts | 183 +- .../experiments/experiments.component.html | 3 + .../experiments/experiments.component.ts | 137 +- .../common-experiment-info.reducer.ts | 38 +- .../reducers/experiment-output.reducer.ts | 2 + .../experiments/reducers/index.ts | 43 +- .../abort-all-children-dialog.component.ts | 3 +- .../change-project-dialog.component.ts | 3 +- .../clone-dialog/clone-dialog.component.ts | 2 +- .../experiment-menu.component.html | 26 +- .../experiment-menu.component.ts | 36 +- .../select-queue/select-queue.actions.ts | 54 +- .../select-queue/select-queue.component.ts | 10 +- .../select-queue/select-queue.effects.ts | 20 +- .../select-queue/select-queue.reducer.ts | 25 +- .../common-experiment-reverter.service.ts | 6 +- .../breadcrumbs/breadcrumbs.component.scss | 1 + .../breadcrumbs/breadcrumbs.component.ts | 116 +- .../layout/header/header.component.html | 2 +- .../layout/header/header.component.ts | 6 +- src/app/webapp-common/layout/layout.module.ts | 2 + .../logged-out-alert.component.ts | 2 +- .../project-context-navbar.component.scss | 2 +- .../s3-access-dialog.component.html | 23 +- .../s3-access-dialog.component.scss | 2 + .../s3-access-dialog.component.ts | 22 +- ...notification-dialog-container.component.ts | 2 +- .../tip-of-the-day-modal.component.html | 6 +- .../tip-of-the-day-modal.component.ts | 2 +- .../welcome-message.component.html | 8 +- .../welcome-message.component.ts | 2 +- .../login/login/login.component.ts | 2 +- .../models/actions/models-view.actions.ts | 13 + .../model-experiments-table.component.html | 2 +- .../model-experiments-table.component.scss | 5 +- .../model-experiments-table.component.ts | 3 +- .../model-info-experiments.component.ts | 2 +- .../model-info-general.component.ts | 2 +- .../model-info-labels.component.ts | 2 +- .../model-info-metadata.component.ts | 2 +- .../model-info-network.component.ts | 2 +- .../model-info-plots.component.html | 1 + .../model-info-plots.component.ts | 24 +- .../model-info-scalars.component.html | 1 - .../model-info-scalars.component.ts | 26 +- .../model-info/model-info.component.html | 20 +- .../model-info/model-info.component.scss | 24 +- .../model-info/model-info.component.ts | 110 +- .../model-menu/model-menu.component.html | 31 +- .../model-menu/model-menu.component.ts | 3 +- .../model-general-info.component.ts | 2 +- .../model-header/model-header.component.html | 7 +- .../model-header/model-header.component.scss | 4 + .../model-header/model-header.component.ts | 1 + .../model-info-header.component.html | 45 +- .../model-info-header.component.scss | 7 +- .../model-info-header.component.ts | 41 +- .../models/effects/models-info.effects.ts | 39 +- .../models/effects/models-menu.effects.ts | 66 +- .../models/effects/models-view.effects.ts | 200 +- .../models/models-routing.module.ts | 62 +- .../models/models.component.html | 16 +- .../webapp-common/models/models.component.ts | 126 +- src/app/webapp-common/models/models.consts.ts | 15 +- src/app/webapp-common/models/models.module.ts | 35 +- .../webapp-common/models/reducers/index.ts | 87 +- .../models/reducers/model-info.reducer.ts | 6 +- .../models/reducers/models-view.reducer.ts | 1 + .../models-table/models-table.component.html | 2 +- .../models-table/models-table.component.ts | 10 +- .../select-model-header.component.html | 8 - .../select-model-header.component.ts | 13 - .../nested-card/nested-card.component.html | 6 +- .../nested-card/nested-card.component.ts | 25 +- .../nested-project-view-page.component.html | 147 +- .../nested-project-view-page.component.scss | 6 +- ...nested-project-view-page.component.spec.ts | 33 - .../nested-project-view-page.component.ts | 126 +- .../nested-project-view.module.ts | 59 - .../nested-project-view.route.ts | 21 - .../controllers.component.html | 3 + .../controllers.component.ts | 52 +- .../controllers.consts.ts | 11 +- .../pipeline-controller-info.component.ts | 2 +- .../abort-controller-dialog.component.ts | 3 +- .../pipeline-controller-menu.component.html | 31 +- .../pipeline-controller-menu.component.ts | 6 +- ...-pipeline-controller-dialog.component.html | 19 +- ...-pipeline-controller-dialog.component.scss | 3 + ...un-pipeline-controller-dialog.component.ts | 15 +- .../nested-pipeline-page.component.html | 70 + .../nested-pipeline-page.component.ts | 47 + .../pipeline-card-menu.component.html | 22 +- .../pipeline-card.component.html | 3 +- .../pipeline-card.component.spec.ts | 31 - .../pipelines-page.component.ts | 46 +- .../pipelines/pipelines.route.ts | 5 +- .../project-stats.component.html | 13 +- .../project-stats.component.scss | 20 +- .../project-stats/project-stats.component.ts | 4 +- .../project-info-routing.module.ts | 4 +- .../project-info/project-info.component.ts | 48 +- .../projects/common-projects.actions.ts | 4 - .../projects/common-projects.effects.ts | 109 +- .../projects/common-projects.module.ts | 5 +- .../projects/common-projects.reducer.ts | 74 +- .../projects/common-projects.utils.ts | 19 +- .../common-projects-page.component.ts | 92 +- .../nested-reports-page.component.html | 53 + .../nested-reports-page.component.ts | 81 + .../report-card-menu.component.html | 22 +- .../report-card/report-card.component.html | 1 + .../report-dialog/report-dialog.component.ts | 6 +- .../reports/report/report.component.html | 37 +- .../reports/report/report.component.scss | 4 + .../reports/report/report.component.spec.ts | 23 - .../reports/report/report.component.ts | 54 +- .../reports-page/reports-page.component.ts | 65 +- .../reports/reports-routing.module.ts | 13 +- .../webapp-common/reports/reports.actions.ts | 12 +- .../webapp-common/reports/reports.effects.ts | 60 +- .../webapp-common/reports/reports.module.ts | 27 +- .../webapp-common/reports/reports.reducer.ts | 32 +- .../select-model/select-model.actions.ts | 4 +- .../select-model/select-model.component.html | 8 +- .../select-model/select-model.component.ts | 108 +- .../select-model/select-model.effects.ts | 56 +- .../select-model/select-model.module.ts | 10 +- .../select-model/select-model.reducer.ts | 19 +- .../admin-footer/admin-footer.component.ts | 2 +- .../settings/admin/base-admin.service.ts | 12 +- .../profile-key-storage.component.ts | 2 +- .../profile-name/profile-name.component.ts | 2 +- .../profile-preferences.component.html | 4 +- .../profile-preferences.component.ts | 2 +- .../redacted-arguments-dialog.component.html | 2 +- .../redacted-arguments-dialog.component.ts | 6 +- .../admin/s3-access/s3-access.component.html | 48 +- .../admin/s3-access/s3-access.component.scss | 13 +- .../admin/s3-access/s3-access.component.ts | 25 +- .../base-context-menu.component.ts | 14 +- .../charts/donut/donut.component.html | 5 +- .../charts/donut/donut.component.ts | 8 +- .../line-chart/line-chart.component.scss | 17 +- .../charts/line-chart/line-chart.component.ts | 5 +- .../scatter-plot/scatter-plot.component.scss | 13 +- .../scatter-plot/scatter-plot.component.ts | 17 +- .../experiment-refresh.component.ts | 2 +- .../experiment-settings.html | 2 +- .../json-viewer/json-viewer.component.html | 63 + .../json-viewer.component.scss} | 49 +- .../json-viewer/json-viewer.component.ts | 215 ++ .../main-pages-header-filter.component.html | 2 +- .../main-pages-header-filter.component.ts | 27 +- .../markdown-editor.component.scss | 119 +- .../multi-line-tooltip.component.html | 2 +- .../multi-line-tooltip.component.scss | 8 +- .../ngx-json-viewer.component.html | 39 - .../ngx-json-viewer.component.ts | 136 - .../router-tab-nav-bar.component.spec.ts | 23 - .../router-tab-nav-bar.component.ts | 3 +- .../scroll-textarea.component.html | 2 +- .../show-only-user-work.component.ts | 2 +- .../virtual-grid/virtual-grid.component.html | 2 +- .../debug-image-snippet.component.html | 17 +- .../debug-image-snippet.component.ts | 2 +- .../debug-sample/debug-sample.effects.ts | 2 +- .../base-image-viewer.component.ts | 10 +- .../image-viewer/image-viewer.component.html | 47 +- .../image-viewer/image-viewer.component.scss | 2 +- .../base-entity-header.component.ts | 14 +- .../shared/entity-page/base-entity-page.ts | 100 +- .../base-delete-dialog.effects.ts | 126 +- .../common-delete-dialog.component.ts | 8 +- .../entity-footer.component.html | 36 +- .../entity-footer/entity-footer.component.ts | 2 +- .../footer-items/compare-footer-item.ts | 2 +- .../footer-items/dequeue-footer-item.ts | 6 +- .../footer-items/enqueue-footer-item.ts | 4 +- .../footer-items/selected-tags-footer-item.ts | 2 +- .../experiment-graphs.component.html | 43 +- .../experiment-graphs.component.scss | 339 +- .../experiment-graphs.component.ts | 33 +- .../graph-settings-bar.component.html | 81 +- .../graph-settings-bar.component.scss | 68 +- .../graph-settings-bar.component.ts | 53 +- .../experiment-type-icon-label.component.ts | 2 +- .../guards/account-administration.guard.ts | 34 +- .../webapp-common/shared/guards/auth.guard.ts | 24 - ...general-leaving-before-save-alert.guard.ts | 55 +- .../guards/leaving-before-save-alert.guard.ts | 80 +- .../shared/guards/project-redirect.guard.ts | 34 +- .../pipes/hide-redacted-arguments.pipe.ts | 23 +- .../shared/pipes/show-selected-first.pipe.ts | 2 +- .../create-new-project-form.component.html | 2 + .../create-new-project-form.component.ts | 2 +- .../project-dialog/project-dialog.actions.ts | 49 +- .../project-dialog.component.ts | 11 +- .../project-dialog/project-dialog.effects.ts | 29 +- .../project-dialog/project-dialog.reducer.ts | 19 +- .../create-new-queue-form.component.html | 4 +- .../queue-create-dialog.actions.ts | 71 +- .../queue-create-dialog.component.ts | 10 +- .../queue-create-dialog.effects.ts | 32 +- .../queue-create-dialog.reducer.ts | 24 +- .../shared/services/breadcrumbs.service.ts | 60 + .../services/color-hash/color-hash.service.ts | 2 +- .../shared/services/error.service.ts | 6 +- .../report-code-embed-base.service.ts | 4 + .../shared/services/server-updates.service.ts | 2 +- .../shared/services/ui-updates.service.ts | 2 +- src/app/webapp-common/shared/shared.module.ts | 37 +- .../graph-viewer/graph-viewer.component.html | 72 +- .../graph-viewer/graph-viewer.component.scss | 56 +- .../graph-viewer/graph-viewer.component.ts | 96 +- .../shared/single-graph/plotly-graph-base.ts | 21 +- .../single-graph/single-graph.component.html | 2 +- .../single-graph/single-graph.component.scss | 37 +- .../single-graph/single-graph.component.ts | 149 +- .../single-graph/single-graph.effects.ts | 2 +- .../single-graph/single-graph.module.ts | 22 +- .../shared/single-graph/single-graph.utils.ts | 37 + .../single-value-summary-table.component.ts | 5 +- .../labeled-row/labeled-row.component.html | 2 +- .../selectable-list.component.ts | 2 +- .../data/table/base-table-view.ts | 6 +- .../table-card-filter-template.component.html | 2 +- .../table-card-filter-template.component.scss | 5 + .../table-filter-sort-template.component.html | 2 +- .../table-filter-sort-template.component.ts | 9 +- .../data/table/table.component.scss | 1 + .../data/table/table.component.ts | 86 +- .../ui-components/data/table/table.consts.ts | 1 + .../choose-color/choose-color.directive.ts | 4 +- .../snippet-error/snippet-error.component.ts | 2 +- .../color-picker-wrapper.component.ts | 2 +- .../duration-input.component.html | 10 +- .../inputs/search/search.component.ts | 5 +- ...ect-autocomplete-with-chips.component.scss | 4 + .../overlay/edit-json/edit-json.component.ts | 4 +- .../overlay/leaf/leaf.component.html | 2 +- .../overlay/leaf/leaf.component.scss | 5 - .../share-dialog/share-dialog.component.ts | 4 +- .../overlay/spinner/spinner.component.ts | 2 +- .../terms-of-use-dialog.component.html | 3 +- .../wizard-dialog-step.component.html | 4 +- .../wizard-dialog-step.component.scss | 17 +- .../wizard-dialog-step.component.ts | 12 +- .../panel/card/card.component.html | 2 +- .../checkbox-three-state-list.component.html | 2 +- .../editable-section.component.scss | 1 - .../editable-section.component.ts | 2 +- .../panel/menu/menu.component.html | 1 + .../panel/menu/menu.component.scss | 6 + .../panel/menu/menu.component.ts | 1 + .../navbar-item/navbar-item.component.scss | 5 +- .../project-card/project-card.component.html | 9 +- .../project-card/project-card.component.ts | 2 +- .../shared/ui-components/styles/icons.scss | 7 + .../styles/material-overide.scss | 58 +- .../styles/mixins/wizard-template.scss | 4 - .../ui-components/styles/themes/light.scss | 2 +- .../tag-color-menu.component.html | 8 +- .../tag-color-menu.component.scss | 4 - .../tags/tags-menu/tags-menu.component.html | 92 +- .../tags/tags-menu/tags-menu.component.scss | 4 +- .../tags/tags-menu/tags-menu.component.ts | 23 +- ...ocomplete-selection-validator.directive.ts | 2 +- .../ui-components/ui-components.module.ts | 66 +- .../webapp-common/shared/utils/download.ts | 16 +- .../shared/utils/helpers.util.ts | 2 + .../shared/utils/shared-utils.ts | 5 + src/app/webapp-common/tasks/tasks.utils.ts | 56 +- .../actions/queues.actions.ts | 16 - .../queue-stats/queue-stats.component.ts | 2 +- .../containers/queues/queues.component.ts | 2 +- .../workers-stats/workers-stats.component.ts | 2 +- .../containers/workers/workers.component.ts | 2 +- .../effects/queues.effects.ts | 4 +- .../effects/workers.effects.ts | 2 +- .../orchestration.component.scss | 2 + .../orchestration.component.ts | 2 +- src/main.ts | 2 - 482 files changed, 8743 insertions(+), 6056 deletions(-) create mode 100644 src/app/business-logic/model/organization/fieldMapping.ts create mode 100644 src/app/business-logic/model/organization/organizationPrepareDownloadForGetAllRequest.ts create mode 100644 src/app/business-logic/model/organization/organizationPrepareDownloadForGetAllResponse.ts create mode 100644 src/app/business-logic/model/pipelines/pipelinesDeleteRunsRequest.ts create mode 100644 src/app/business-logic/model/pipelines/pipelinesDeleteRunsResponse.ts create mode 100644 src/app/business-logic/model/pipelines/pipelinesDeleteRunsResponseError.ts create mode 100644 src/app/business-logic/model/pipelines/pipelinesDeleteRunsResponseFailed.ts create mode 100644 src/app/business-logic/model/pipelines/pipelinesDeleteRunsResponseSucceeded.ts create mode 100644 src/app/business-logic/model/pipelines/pipelinesStartPipelineRequestArgs.ts create mode 100644 src/app/business-logic/model/projects/projectsGetUserNamesRequest.ts create mode 100644 src/app/business-logic/model/projects/projectsGetUserNamesResponse.ts create mode 100644 src/app/business-logic/model/projects/projectsGetUserNamesResponseUsers.ts create mode 100644 src/app/business-logic/model/users/usersGetCurrentUserResponseSettings.ts delete mode 100755 src/app/core/reducers/recent-tasks-reducer.ts create mode 100644 src/app/features/datasets/nested-datasets-page/nested-datasets-page.component.html create mode 100644 src/app/features/datasets/nested-datasets-page/nested-datasets-page.component.ts delete mode 100644 src/app/features/nested-project-view/feature-nested-project-view.module.ts delete mode 100644 src/app/features/nested-project-view/nested-project-view-page-extended/nested-project-view-page-extended.component.html delete mode 100644 src/app/features/nested-project-view/nested-project-view-page-extended/nested-project-view-page-extended.component.scss delete mode 100644 src/app/features/nested-project-view/nested-project-view-page-extended/nested-project-view-page-extended.component.spec.ts delete mode 100644 src/app/features/nested-project-view/nested-project-view-page-extended/nested-project-view-page-extended.component.ts delete mode 100644 src/app/features/nested-project-view/nested-project-view-utils.ts delete mode 100755 src/app/webapp-common/core/cache/cache.module.ts delete mode 100755 src/app/webapp-common/core/cache/entities-cache.service.ts delete mode 100755 src/app/webapp-common/core/models/actions.ts create mode 100644 src/app/webapp-common/datasets/nested-simple-datasets-page/nested-simple-datasets-page.component.html create mode 100644 src/app/webapp-common/datasets/nested-simple-datasets-page/nested-simple-datasets-page.component.ts create mode 100644 src/app/webapp-common/datasets/simple-datasets/empty.scss delete mode 100644 src/app/webapp-common/datasets/simple-datasets/simple-datasets.component.spec.ts delete mode 100644 src/app/webapp-common/models/containers/model-info-scalars/model-info-scalars.component.html delete mode 100644 src/app/webapp-common/nested-project-view/nested-project-view-page/nested-project-view-page.component.spec.ts delete mode 100644 src/app/webapp-common/nested-project-view/nested-project-view.module.ts delete mode 100644 src/app/webapp-common/nested-project-view/nested-project-view.route.ts create mode 100644 src/app/webapp-common/pipelines/nested-pipeline-page/nested-pipeline-page.component.html create mode 100644 src/app/webapp-common/pipelines/nested-pipeline-page/nested-pipeline-page.component.ts delete mode 100644 src/app/webapp-common/pipelines/pipeline-card/pipeline-card.component.spec.ts create mode 100644 src/app/webapp-common/reports/nested-reports-page/nested-reports-page.component.html create mode 100644 src/app/webapp-common/reports/nested-reports-page/nested-reports-page.component.ts delete mode 100644 src/app/webapp-common/reports/report/report.component.spec.ts create mode 100644 src/app/webapp-common/shared/components/json-viewer/json-viewer.component.html rename src/app/webapp-common/shared/components/{ngx-json-viewer/ngx-json-viewer.component.scss => json-viewer/json-viewer.component.scss} (73%) create mode 100644 src/app/webapp-common/shared/components/json-viewer/json-viewer.component.ts delete mode 100644 src/app/webapp-common/shared/components/ngx-json-viewer/ngx-json-viewer.component.html delete mode 100644 src/app/webapp-common/shared/components/ngx-json-viewer/ngx-json-viewer.component.ts delete mode 100644 src/app/webapp-common/shared/components/router-tab-nav-bar/router-tab-nav-bar.component.spec.ts delete mode 100755 src/app/webapp-common/shared/guards/auth.guard.ts create mode 100644 src/app/webapp-common/shared/services/breadcrumbs.service.ts create mode 100644 src/app/webapp-common/shared/single-graph/single-graph.utils.ts diff --git a/angular.json b/angular.json index 349d0158..abd1e6c9 100644 --- a/angular.json +++ b/angular.json @@ -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, diff --git a/package-lock.json b/package-lock.json index fe551c80..dbbb6e80 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "clearml-webapp", - "version": "1.11.0", + "version": "1.12.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "clearml-webapp", - "version": "1.11.0", + "version": "1.12.0", "dependencies": { "@angular/animations": "^15.2.8", "@angular/cdk": "^15.2.8", @@ -21,8 +21,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", @@ -33,11 +33,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", @@ -50,7 +53,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", @@ -59,6 +62,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", @@ -85,7 +89,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", @@ -110,12 +114,12 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.1502.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1502.6.tgz", - "integrity": "sha512-n4oJ9vzFWwabf+AfgqqevVzdJhNKNCav7ytefjD/Y01vkNwlXqWnHcvyyHCLkVibJ6WR8J9lK4t77j/HFlDvWQ==", + "version": "0.1502.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1502.8.tgz", + "integrity": "sha512-rTltw2ABHrcKc8EGimALvXmrDTP5hlNbEy6nYolJoXEI9EwHgriWrVLVPs3OEF+/ed47dbJi9EGOXUOgzgpB5A==", "dev": true, "dependencies": { - "@angular-devkit/core": "15.2.6", + "@angular-devkit/core": "15.2.8", "rxjs": "6.6.7" }, "engines": { @@ -143,15 +147,15 @@ "dev": true }, "node_modules/@angular-devkit/build-angular": { - "version": "15.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-15.2.6.tgz", - "integrity": "sha512-OmMcdXXUrAdZNxwxDE8SUx1FMcq9FyMnrSv1PmP9sHPBoxAdBVc/qNdGA9V7C5yHvWHGgzsx7ZK5TDuvifzS5g==", + "version": "15.2.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-15.2.8.tgz", + "integrity": "sha512-TGDnXhhOG6h6TOrWWzfnkha7wYBOXi7iJc1o1w1VKCayE3T6TZZdF847aK66vL9KG7AKYVdGhWEGw2WBHUBUpg==", "dev": true, "dependencies": { "@ampproject/remapping": "2.2.0", - "@angular-devkit/architect": "0.1502.6", - "@angular-devkit/build-webpack": "0.1502.6", - "@angular-devkit/core": "15.2.6", + "@angular-devkit/architect": "0.1502.8", + "@angular-devkit/build-webpack": "0.1502.8", + "@angular-devkit/core": "15.2.8", "@babel/core": "7.20.12", "@babel/generator": "7.20.14", "@babel/helper-annotate-as-pure": "7.18.6", @@ -163,7 +167,7 @@ "@babel/runtime": "7.20.13", "@babel/template": "7.20.7", "@discoveryjs/json-ext": "0.5.7", - "@ngtools/webpack": "15.2.6", + "@ngtools/webpack": "15.2.8", "ansi-colors": "4.1.3", "autoprefixer": "10.4.13", "babel-loader": "9.1.2", @@ -350,12 +354,12 @@ "dev": true }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.1502.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1502.6.tgz", - "integrity": "sha512-X7XQ11QDz2Bs5qpJ3a5glIytvI+S74ORQxdzvT6a6KB8ayW0SgZEhTwD+GF7pa5My8draIaXBGzzQR1qmpWK5Q==", + "version": "0.1502.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1502.8.tgz", + "integrity": "sha512-jWtNv+S03FFLDe/C8SPCcRvkz3bSb2R+919IT086Q9axIPQ1VowOEwzt2k3qXPSSrC7GSYuASM+X92dB47NTQQ==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.1502.6", + "@angular-devkit/architect": "0.1502.8", "rxjs": "6.6.7" }, "engines": { @@ -387,9 +391,9 @@ "dev": true }, "node_modules/@angular-devkit/core": { - "version": "15.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-15.2.6.tgz", - "integrity": "sha512-YVTWZ+M+xNKdFX4EnY9QX49PZraawiaA0iTd2CUW8ZoTUvU7yOGMKZLSdz6aokTMRVfm0449wt6YL994ibOo1g==", + "version": "15.2.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-15.2.8.tgz", + "integrity": "sha512-Lo4XrbDMtXarKnMrFgWLmQdSX+3QPNAg4otG8cmp/U4jJyjV4dAYKEAsb1sCNGUSM4h4v09EQU/5ugVjDU29lQ==", "dev": true, "dependencies": { "ajv": "8.12.0", @@ -431,12 +435,12 @@ "dev": true }, "node_modules/@angular-devkit/schematics": { - "version": "15.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-15.2.6.tgz", - "integrity": "sha512-f7VgnAcok7AwR/DhX0ZWskB0rFBo/KsvtIUA2qZSrpKMf8eFiwu03dv/b2mI0vnf+1FBfIQzJvO0ww45zRp6dA==", + "version": "15.2.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-15.2.8.tgz", + "integrity": "sha512-w6EUGC96kVsH9f8sEzajzbONMawezyVBiSo+JYp5r25rQArAz/a+KZntbuETWHQ0rQOEsKmUNKxwmr11BaptSQ==", "dev": true, "dependencies": { - "@angular-devkit/core": "15.2.6", + "@angular-devkit/core": "15.2.8", "jsonc-parser": "3.2.0", "magic-string": "0.29.0", "ora": "5.4.1", @@ -1022,15 +1026,15 @@ } }, "node_modules/@angular/cli": { - "version": "15.2.6", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-15.2.6.tgz", - "integrity": "sha512-wNkQ/qCVbd4pERaGVagKJPifEvjRNY5otwsd4iRVubY/XOcIHcYChUThZwgQdVfNAImfJPMZNrhbGxejuWLA9w==", + "version": "15.2.8", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-15.2.8.tgz", + "integrity": "sha512-3VlTfm6DUZfFHBY43vQSAaqmFTxy3VtRd/iDBCHcEPhHwYLWBvNwReJuJfNja8O105QQ6DBiYVBExEBtPmjQ4w==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.1502.6", - "@angular-devkit/core": "15.2.6", - "@angular-devkit/schematics": "15.2.6", - "@schematics/angular": "15.2.6", + "@angular-devkit/architect": "0.1502.8", + "@angular-devkit/core": "15.2.8", + "@angular-devkit/schematics": "15.2.8", + "@schematics/angular": "15.2.8", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.3", "ini": "3.0.1", @@ -1090,9 +1094,9 @@ } }, "node_modules/@angular/compiler-cli": { - "version": "15.2.8", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-15.2.8.tgz", - "integrity": "sha512-fFxaDlbILo0t2t662qA0cjgn+kWItGlc1tFYKU6X7bvYb3t2e0cd9FzrFPLXUQVboGis83ULcJ2zkDxScnuPuQ==", + "version": "15.2.9", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-15.2.9.tgz", + "integrity": "sha512-zsbI8G2xHOeYWI0hjFzrI//ZhZV9il/uQW5dAimfwJp06KZDeXZ3PdwY9JQslf6F+saLwOObxy6QMrIVvfjy9w==", "dev": true, "dependencies": { "@babel/core": "7.19.3", @@ -1115,7 +1119,7 @@ "node": "^14.20.0 || ^16.13.0 || >=18.10.0" }, "peerDependencies": { - "@angular/compiler": "15.2.8", + "@angular/compiler": "15.2.9", "typescript": ">=4.8.2 <5.0" } }, @@ -1476,11 +1480,11 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@aws-sdk/abort-controller": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.310.0.tgz", - "integrity": "sha512-v1zrRQxDLA1MdPim159Vx/CPHqsB4uybSxRi1CnfHO5ZjHryx3a5htW2gdGAykVCul40+yJXvfpufMrELVxH+g==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.357.0.tgz", + "integrity": "sha512-nQYDJon87quPwt2JZJwUN2GFKJnvE5kWb6tZP4xb5biSGUKBqDQo06oYed7yokatCuCMouIXV462aN0fWODtOw==", "dependencies": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -1496,63 +1500,63 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.319.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.319.0.tgz", - "integrity": "sha512-/XzElEO4iZTBgvrcWq20sxKLvhRetjT1gOPRF4Ra2iSCbeVIT/feYdEaSSgMsaiqrREywBc+59NiOyxImWTaOA==", + "version": "3.360.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.360.0.tgz", + "integrity": "sha512-6URI0ZWk5ar0atJ8xTxD2u/oLWwBlosLTyqNpsMe7DKvZQ5DgUfLw3BHeC2d4FQID1I74rkGCdHLtRe4MOiIfA==", "dependencies": { "@aws-crypto/sha1-browser": "3.0.0", "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.319.0", - "@aws-sdk/config-resolver": "3.310.0", - "@aws-sdk/credential-provider-node": "3.319.0", - "@aws-sdk/eventstream-serde-browser": "3.310.0", - "@aws-sdk/eventstream-serde-config-resolver": "3.310.0", - "@aws-sdk/eventstream-serde-node": "3.310.0", - "@aws-sdk/fetch-http-handler": "3.310.0", - "@aws-sdk/hash-blob-browser": "3.310.0", - "@aws-sdk/hash-node": "3.310.0", - "@aws-sdk/hash-stream-node": "3.310.0", - "@aws-sdk/invalid-dependency": "3.310.0", - "@aws-sdk/md5-js": "3.310.0", - "@aws-sdk/middleware-bucket-endpoint": "3.310.0", - "@aws-sdk/middleware-content-length": "3.310.0", - "@aws-sdk/middleware-endpoint": "3.310.0", - "@aws-sdk/middleware-expect-continue": "3.310.0", - "@aws-sdk/middleware-flexible-checksums": "3.310.0", - "@aws-sdk/middleware-host-header": "3.310.0", - "@aws-sdk/middleware-location-constraint": "3.310.0", - "@aws-sdk/middleware-logger": "3.310.0", - "@aws-sdk/middleware-recursion-detection": "3.310.0", - "@aws-sdk/middleware-retry": "3.310.0", - "@aws-sdk/middleware-sdk-s3": "3.310.0", - "@aws-sdk/middleware-serde": "3.310.0", - "@aws-sdk/middleware-signing": "3.310.0", - "@aws-sdk/middleware-ssec": "3.310.0", - "@aws-sdk/middleware-stack": "3.310.0", - "@aws-sdk/middleware-user-agent": "3.319.0", - "@aws-sdk/node-config-provider": "3.310.0", - "@aws-sdk/node-http-handler": "3.310.0", - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/signature-v4-multi-region": "3.310.0", - "@aws-sdk/smithy-client": "3.316.0", - "@aws-sdk/types": "3.310.0", - "@aws-sdk/url-parser": "3.310.0", + "@aws-sdk/client-sts": "3.360.0", + "@aws-sdk/config-resolver": "3.357.0", + "@aws-sdk/credential-provider-node": "3.360.0", + "@aws-sdk/eventstream-serde-browser": "3.357.0", + "@aws-sdk/eventstream-serde-config-resolver": "3.357.0", + "@aws-sdk/eventstream-serde-node": "3.357.0", + "@aws-sdk/fetch-http-handler": "3.357.0", + "@aws-sdk/hash-blob-browser": "3.357.0", + "@aws-sdk/hash-node": "3.357.0", + "@aws-sdk/hash-stream-node": "3.357.0", + "@aws-sdk/invalid-dependency": "3.357.0", + "@aws-sdk/md5-js": "3.357.0", + "@aws-sdk/middleware-bucket-endpoint": "3.357.0", + "@aws-sdk/middleware-content-length": "3.357.0", + "@aws-sdk/middleware-endpoint": "3.357.0", + "@aws-sdk/middleware-expect-continue": "3.357.0", + "@aws-sdk/middleware-flexible-checksums": "3.357.0", + "@aws-sdk/middleware-host-header": "3.357.0", + "@aws-sdk/middleware-location-constraint": "3.357.0", + "@aws-sdk/middleware-logger": "3.357.0", + "@aws-sdk/middleware-recursion-detection": "3.357.0", + "@aws-sdk/middleware-retry": "3.357.0", + "@aws-sdk/middleware-sdk-s3": "3.357.0", + "@aws-sdk/middleware-serde": "3.357.0", + "@aws-sdk/middleware-signing": "3.357.0", + "@aws-sdk/middleware-ssec": "3.357.0", + "@aws-sdk/middleware-stack": "3.357.0", + "@aws-sdk/middleware-user-agent": "3.357.0", + "@aws-sdk/node-config-provider": "3.357.0", + "@aws-sdk/node-http-handler": "3.360.0", + "@aws-sdk/signature-v4-multi-region": "3.357.0", + "@aws-sdk/smithy-client": "3.360.0", + "@aws-sdk/types": "3.357.0", + "@aws-sdk/url-parser": "3.357.0", "@aws-sdk/util-base64": "3.310.0", "@aws-sdk/util-body-length-browser": "3.310.0", "@aws-sdk/util-body-length-node": "3.310.0", - "@aws-sdk/util-defaults-mode-browser": "3.316.0", - "@aws-sdk/util-defaults-mode-node": "3.316.0", - "@aws-sdk/util-endpoints": "3.319.0", - "@aws-sdk/util-retry": "3.310.0", - "@aws-sdk/util-stream-browser": "3.310.0", - "@aws-sdk/util-stream-node": "3.310.0", - "@aws-sdk/util-user-agent-browser": "3.310.0", - "@aws-sdk/util-user-agent-node": "3.310.0", + "@aws-sdk/util-defaults-mode-browser": "3.360.0", + "@aws-sdk/util-defaults-mode-node": "3.360.0", + "@aws-sdk/util-endpoints": "3.357.0", + "@aws-sdk/util-retry": "3.357.0", + "@aws-sdk/util-stream": "3.360.0", + "@aws-sdk/util-user-agent-browser": "3.357.0", + "@aws-sdk/util-user-agent-node": "3.357.0", "@aws-sdk/util-utf8": "3.310.0", - "@aws-sdk/util-waiter": "3.310.0", + "@aws-sdk/util-waiter": "3.357.0", "@aws-sdk/xml-builder": "3.310.0", - "fast-xml-parser": "4.1.2", + "@smithy/protocol-http": "^1.0.1", + "@smithy/types": "^1.0.0", + "fast-xml-parser": "4.2.5", "tslib": "^2.5.0" }, "engines": { @@ -1560,41 +1564,42 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.319.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.319.0.tgz", - "integrity": "sha512-g46KgAjRiYBS8Oi85DPwSAQpt+Hgmw/YFgGVwZqMfTL70KNJwLFKRa5D9UocQd7t7OjPRdKF7g0Gp5peyAK9dw==", + "version": "3.360.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.360.0.tgz", + "integrity": "sha512-0f6eG+6XFbDxrma5xxNGg/FJxh/OHC6h8AkfNms9Lv1gBoQSagpcTor+ax0z9F6lypOjaelX6k4DpeKAp4PZeA==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.310.0", - "@aws-sdk/fetch-http-handler": "3.310.0", - "@aws-sdk/hash-node": "3.310.0", - "@aws-sdk/invalid-dependency": "3.310.0", - "@aws-sdk/middleware-content-length": "3.310.0", - "@aws-sdk/middleware-endpoint": "3.310.0", - "@aws-sdk/middleware-host-header": "3.310.0", - "@aws-sdk/middleware-logger": "3.310.0", - "@aws-sdk/middleware-recursion-detection": "3.310.0", - "@aws-sdk/middleware-retry": "3.310.0", - "@aws-sdk/middleware-serde": "3.310.0", - "@aws-sdk/middleware-stack": "3.310.0", - "@aws-sdk/middleware-user-agent": "3.319.0", - "@aws-sdk/node-config-provider": "3.310.0", - "@aws-sdk/node-http-handler": "3.310.0", - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/smithy-client": "3.316.0", - "@aws-sdk/types": "3.310.0", - "@aws-sdk/url-parser": "3.310.0", + "@aws-sdk/config-resolver": "3.357.0", + "@aws-sdk/fetch-http-handler": "3.357.0", + "@aws-sdk/hash-node": "3.357.0", + "@aws-sdk/invalid-dependency": "3.357.0", + "@aws-sdk/middleware-content-length": "3.357.0", + "@aws-sdk/middleware-endpoint": "3.357.0", + "@aws-sdk/middleware-host-header": "3.357.0", + "@aws-sdk/middleware-logger": "3.357.0", + "@aws-sdk/middleware-recursion-detection": "3.357.0", + "@aws-sdk/middleware-retry": "3.357.0", + "@aws-sdk/middleware-serde": "3.357.0", + "@aws-sdk/middleware-stack": "3.357.0", + "@aws-sdk/middleware-user-agent": "3.357.0", + "@aws-sdk/node-config-provider": "3.357.0", + "@aws-sdk/node-http-handler": "3.360.0", + "@aws-sdk/smithy-client": "3.360.0", + "@aws-sdk/types": "3.357.0", + "@aws-sdk/url-parser": "3.357.0", "@aws-sdk/util-base64": "3.310.0", "@aws-sdk/util-body-length-browser": "3.310.0", "@aws-sdk/util-body-length-node": "3.310.0", - "@aws-sdk/util-defaults-mode-browser": "3.316.0", - "@aws-sdk/util-defaults-mode-node": "3.316.0", - "@aws-sdk/util-endpoints": "3.319.0", - "@aws-sdk/util-retry": "3.310.0", - "@aws-sdk/util-user-agent-browser": "3.310.0", - "@aws-sdk/util-user-agent-node": "3.310.0", + "@aws-sdk/util-defaults-mode-browser": "3.360.0", + "@aws-sdk/util-defaults-mode-node": "3.360.0", + "@aws-sdk/util-endpoints": "3.357.0", + "@aws-sdk/util-retry": "3.357.0", + "@aws-sdk/util-user-agent-browser": "3.357.0", + "@aws-sdk/util-user-agent-node": "3.357.0", "@aws-sdk/util-utf8": "3.310.0", + "@smithy/protocol-http": "^1.0.1", + "@smithy/types": "^1.0.0", "tslib": "^2.5.0" }, "engines": { @@ -1602,41 +1607,42 @@ } }, "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.319.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.319.0.tgz", - "integrity": "sha512-GJBgT/tephRZY3oTbDBMv+G9taoqKUIvGPn+7shmzz2P1SerutsRSfKfDXV+VptPNRoGmjjCLPmWjMFYbFKILQ==", + "version": "3.360.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.360.0.tgz", + "integrity": "sha512-czIpPt75fS3gH3vgFz76+WTaKcvPxC/DnPuqVyHdihMmP0UuwGPU9jn+Xx9RdUw7Yay3+rJRe3AYgBn4Xb220g==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.310.0", - "@aws-sdk/fetch-http-handler": "3.310.0", - "@aws-sdk/hash-node": "3.310.0", - "@aws-sdk/invalid-dependency": "3.310.0", - "@aws-sdk/middleware-content-length": "3.310.0", - "@aws-sdk/middleware-endpoint": "3.310.0", - "@aws-sdk/middleware-host-header": "3.310.0", - "@aws-sdk/middleware-logger": "3.310.0", - "@aws-sdk/middleware-recursion-detection": "3.310.0", - "@aws-sdk/middleware-retry": "3.310.0", - "@aws-sdk/middleware-serde": "3.310.0", - "@aws-sdk/middleware-stack": "3.310.0", - "@aws-sdk/middleware-user-agent": "3.319.0", - "@aws-sdk/node-config-provider": "3.310.0", - "@aws-sdk/node-http-handler": "3.310.0", - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/smithy-client": "3.316.0", - "@aws-sdk/types": "3.310.0", - "@aws-sdk/url-parser": "3.310.0", + "@aws-sdk/config-resolver": "3.357.0", + "@aws-sdk/fetch-http-handler": "3.357.0", + "@aws-sdk/hash-node": "3.357.0", + "@aws-sdk/invalid-dependency": "3.357.0", + "@aws-sdk/middleware-content-length": "3.357.0", + "@aws-sdk/middleware-endpoint": "3.357.0", + "@aws-sdk/middleware-host-header": "3.357.0", + "@aws-sdk/middleware-logger": "3.357.0", + "@aws-sdk/middleware-recursion-detection": "3.357.0", + "@aws-sdk/middleware-retry": "3.357.0", + "@aws-sdk/middleware-serde": "3.357.0", + "@aws-sdk/middleware-stack": "3.357.0", + "@aws-sdk/middleware-user-agent": "3.357.0", + "@aws-sdk/node-config-provider": "3.357.0", + "@aws-sdk/node-http-handler": "3.360.0", + "@aws-sdk/smithy-client": "3.360.0", + "@aws-sdk/types": "3.357.0", + "@aws-sdk/url-parser": "3.357.0", "@aws-sdk/util-base64": "3.310.0", "@aws-sdk/util-body-length-browser": "3.310.0", "@aws-sdk/util-body-length-node": "3.310.0", - "@aws-sdk/util-defaults-mode-browser": "3.316.0", - "@aws-sdk/util-defaults-mode-node": "3.316.0", - "@aws-sdk/util-endpoints": "3.319.0", - "@aws-sdk/util-retry": "3.310.0", - "@aws-sdk/util-user-agent-browser": "3.310.0", - "@aws-sdk/util-user-agent-node": "3.310.0", + "@aws-sdk/util-defaults-mode-browser": "3.360.0", + "@aws-sdk/util-defaults-mode-node": "3.360.0", + "@aws-sdk/util-endpoints": "3.357.0", + "@aws-sdk/util-retry": "3.357.0", + "@aws-sdk/util-user-agent-browser": "3.357.0", + "@aws-sdk/util-user-agent-node": "3.357.0", "@aws-sdk/util-utf8": "3.310.0", + "@smithy/protocol-http": "^1.0.1", + "@smithy/types": "^1.0.0", "tslib": "^2.5.0" }, "engines": { @@ -1644,45 +1650,46 @@ } }, "node_modules/@aws-sdk/client-sts": { - "version": "3.319.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.319.0.tgz", - "integrity": "sha512-PRGGKCSKtyM3x629J9j4DMsH1cQT8UGW+R67u9Q5HrMK05gfjpmg+X1DQ3pgve4D8MI4R/Cm3NkYl2eUTbQHQg==", + "version": "3.360.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.360.0.tgz", + "integrity": "sha512-ORRwSdwlSYGHfhQCXKtr1eJeTjI14l5IZRJbRDgXs46y4/GQj/rt/2Q6WGjVMfM1ZRRiEII2/vK7mU7IJcWkFw==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.310.0", - "@aws-sdk/credential-provider-node": "3.319.0", - "@aws-sdk/fetch-http-handler": "3.310.0", - "@aws-sdk/hash-node": "3.310.0", - "@aws-sdk/invalid-dependency": "3.310.0", - "@aws-sdk/middleware-content-length": "3.310.0", - "@aws-sdk/middleware-endpoint": "3.310.0", - "@aws-sdk/middleware-host-header": "3.310.0", - "@aws-sdk/middleware-logger": "3.310.0", - "@aws-sdk/middleware-recursion-detection": "3.310.0", - "@aws-sdk/middleware-retry": "3.310.0", - "@aws-sdk/middleware-sdk-sts": "3.310.0", - "@aws-sdk/middleware-serde": "3.310.0", - "@aws-sdk/middleware-signing": "3.310.0", - "@aws-sdk/middleware-stack": "3.310.0", - "@aws-sdk/middleware-user-agent": "3.319.0", - "@aws-sdk/node-config-provider": "3.310.0", - "@aws-sdk/node-http-handler": "3.310.0", - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/smithy-client": "3.316.0", - "@aws-sdk/types": "3.310.0", - "@aws-sdk/url-parser": "3.310.0", + "@aws-sdk/config-resolver": "3.357.0", + "@aws-sdk/credential-provider-node": "3.360.0", + "@aws-sdk/fetch-http-handler": "3.357.0", + "@aws-sdk/hash-node": "3.357.0", + "@aws-sdk/invalid-dependency": "3.357.0", + "@aws-sdk/middleware-content-length": "3.357.0", + "@aws-sdk/middleware-endpoint": "3.357.0", + "@aws-sdk/middleware-host-header": "3.357.0", + "@aws-sdk/middleware-logger": "3.357.0", + "@aws-sdk/middleware-recursion-detection": "3.357.0", + "@aws-sdk/middleware-retry": "3.357.0", + "@aws-sdk/middleware-sdk-sts": "3.357.0", + "@aws-sdk/middleware-serde": "3.357.0", + "@aws-sdk/middleware-signing": "3.357.0", + "@aws-sdk/middleware-stack": "3.357.0", + "@aws-sdk/middleware-user-agent": "3.357.0", + "@aws-sdk/node-config-provider": "3.357.0", + "@aws-sdk/node-http-handler": "3.360.0", + "@aws-sdk/smithy-client": "3.360.0", + "@aws-sdk/types": "3.357.0", + "@aws-sdk/url-parser": "3.357.0", "@aws-sdk/util-base64": "3.310.0", "@aws-sdk/util-body-length-browser": "3.310.0", "@aws-sdk/util-body-length-node": "3.310.0", - "@aws-sdk/util-defaults-mode-browser": "3.316.0", - "@aws-sdk/util-defaults-mode-node": "3.316.0", - "@aws-sdk/util-endpoints": "3.319.0", - "@aws-sdk/util-retry": "3.310.0", - "@aws-sdk/util-user-agent-browser": "3.310.0", - "@aws-sdk/util-user-agent-node": "3.310.0", + "@aws-sdk/util-defaults-mode-browser": "3.360.0", + "@aws-sdk/util-defaults-mode-node": "3.360.0", + "@aws-sdk/util-endpoints": "3.357.0", + "@aws-sdk/util-retry": "3.357.0", + "@aws-sdk/util-user-agent-browser": "3.357.0", + "@aws-sdk/util-user-agent-node": "3.357.0", "@aws-sdk/util-utf8": "3.310.0", - "fast-xml-parser": "4.1.2", + "@smithy/protocol-http": "^1.0.1", + "@smithy/types": "^1.0.0", + "fast-xml-parser": "4.2.5", "tslib": "^2.5.0" }, "engines": { @@ -1690,13 +1697,13 @@ } }, "node_modules/@aws-sdk/config-resolver": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.310.0.tgz", - "integrity": "sha512-8vsT+/50lOqfDxka9m/rRt6oxv1WuGZoP8oPMk0Dt+TxXMbAzf4+rejBgiB96wshI1k3gLokYRjSQZn+dDtT8g==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.357.0.tgz", + "integrity": "sha512-cukfg0nX7Tzx/xFyH5F4Eyb8DA1ITCGtSQv4vnEjgUop+bkzckuGLKEeBcBhyZY+aw+2C9CVwIHwIMhRm0ul5w==", "dependencies": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "@aws-sdk/util-config-provider": "3.310.0", - "@aws-sdk/util-middleware": "3.310.0", + "@aws-sdk/util-middleware": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -1704,12 +1711,12 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.310.0.tgz", - "integrity": "sha512-vvIPQpI16fj95xwS7M3D48F7QhZJBnnCgB5lR+b7So+vsG9ibm1mZRVGzVpdxCvgyOhHFbvrby9aalNJmmIP1A==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.357.0.tgz", + "integrity": "sha512-UOecwfqvXgJVqhfWSZ2S44v2Nq2oceW0PQVQp0JAa9opc2rxSVIfyOhPr0yMoPmpyNcP22rgeg6ce70KULYwiA==", "dependencies": { - "@aws-sdk/property-provider": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/property-provider": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -1717,14 +1724,14 @@ } }, "node_modules/@aws-sdk/credential-provider-imds": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.310.0.tgz", - "integrity": "sha512-baxK7Zp6dai5AGW01FIW27xS2KAaPUmKLIXv5SvFYsUgXXvNW55im4uG3b+2gA0F7V+hXvVBH08OEqmwW6we5w==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.357.0.tgz", + "integrity": "sha512-upw/bfsl7/WydT6gM0lBuR4Ipp4fzYm/E3ObFr0Mg5OkgVPt5ZJE+eeFTvwCpDdBSTKs4JfrK6/iEK8A23Q1jQ==", "dependencies": { - "@aws-sdk/node-config-provider": "3.310.0", - "@aws-sdk/property-provider": "3.310.0", - "@aws-sdk/types": "3.310.0", - "@aws-sdk/url-parser": "3.310.0", + "@aws-sdk/node-config-provider": "3.357.0", + "@aws-sdk/property-provider": "3.357.0", + "@aws-sdk/types": "3.357.0", + "@aws-sdk/url-parser": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -1732,18 +1739,18 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.319.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.319.0.tgz", - "integrity": "sha512-pzx388Fw1KlSgmIMUyRY8DJVYM3aXpwzjprD4RiQVPJeAI+t7oQmEvd2FiUZEuHDjWXcuonxgU+dk7i7HUk/HQ==", + "version": "3.360.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.360.0.tgz", + "integrity": "sha512-pWuLTq+yjSFssPGhDJ8oxvZsu7/F1KissGRt65G4qrfxHhoiMRcLF1GtFJueDQpitZ1i3mZXHVn/OSv4LPQ1Lw==", "dependencies": { - "@aws-sdk/credential-provider-env": "3.310.0", - "@aws-sdk/credential-provider-imds": "3.310.0", - "@aws-sdk/credential-provider-process": "3.310.0", - "@aws-sdk/credential-provider-sso": "3.319.0", - "@aws-sdk/credential-provider-web-identity": "3.310.0", - "@aws-sdk/property-provider": "3.310.0", - "@aws-sdk/shared-ini-file-loader": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/credential-provider-env": "3.357.0", + "@aws-sdk/credential-provider-imds": "3.357.0", + "@aws-sdk/credential-provider-process": "3.357.0", + "@aws-sdk/credential-provider-sso": "3.360.0", + "@aws-sdk/credential-provider-web-identity": "3.357.0", + "@aws-sdk/property-provider": "3.357.0", + "@aws-sdk/shared-ini-file-loader": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -1751,19 +1758,19 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.319.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.319.0.tgz", - "integrity": "sha512-DS4a0Rdd7ZtMshoeE+zuSgbC05YBcdzd0h89u/eX+1Yqx+HCjeb8WXkbXsz0Mwx8q9TE04aS8f6Bw9J4x4mO5g==", + "version": "3.360.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.360.0.tgz", + "integrity": "sha512-j4Lu5vXkdzz/L6fGKKxnL0vcwAGHlwFHjTg9nRagMn1lvaVjtktXeM30duHTBQq9i+ejdFxpVNWYrmHGaWPNdg==", "dependencies": { - "@aws-sdk/credential-provider-env": "3.310.0", - "@aws-sdk/credential-provider-imds": "3.310.0", - "@aws-sdk/credential-provider-ini": "3.319.0", - "@aws-sdk/credential-provider-process": "3.310.0", - "@aws-sdk/credential-provider-sso": "3.319.0", - "@aws-sdk/credential-provider-web-identity": "3.310.0", - "@aws-sdk/property-provider": "3.310.0", - "@aws-sdk/shared-ini-file-loader": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/credential-provider-env": "3.357.0", + "@aws-sdk/credential-provider-imds": "3.357.0", + "@aws-sdk/credential-provider-ini": "3.360.0", + "@aws-sdk/credential-provider-process": "3.357.0", + "@aws-sdk/credential-provider-sso": "3.360.0", + "@aws-sdk/credential-provider-web-identity": "3.357.0", + "@aws-sdk/property-provider": "3.357.0", + "@aws-sdk/shared-ini-file-loader": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -1771,13 +1778,13 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.310.0.tgz", - "integrity": "sha512-h73sg6GPMUWC+3zMCbA1nZ2O03nNJt7G96JdmnantiXBwHpRKWW8nBTLzx5uhXn6hTuTaoQRP/P+oxQJKYdMmA==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.357.0.tgz", + "integrity": "sha512-qFWWilFPsc2hR7O0KIhwcE78w+pVIK+uQR6MQMfdRyxUndgiuCorJwVjedc3yZtmnoELHF34j+m8whTBXv9E7Q==", "dependencies": { - "@aws-sdk/property-provider": "3.310.0", - "@aws-sdk/shared-ini-file-loader": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/property-provider": "3.357.0", + "@aws-sdk/shared-ini-file-loader": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -1785,15 +1792,15 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.319.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.319.0.tgz", - "integrity": "sha512-gAUnWH41lxkIbANXu+Rz5zS0Iavjjmpf3C56vAMT7oaYZ3Cg/Ys5l2SwAucQGOCA2DdS2hDiSI8E+Yhr4F5toA==", + "version": "3.360.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.360.0.tgz", + "integrity": "sha512-kW0FR8AbMQrJxADxIqYSjHVN2RXwHmA5DzogYm1AjOkYRMN9JHDVOMQP2K2M6FCynZqTYsKW5lzjPOjS0fu8Dw==", "dependencies": { - "@aws-sdk/client-sso": "3.319.0", - "@aws-sdk/property-provider": "3.310.0", - "@aws-sdk/shared-ini-file-loader": "3.310.0", - "@aws-sdk/token-providers": "3.319.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/client-sso": "3.360.0", + "@aws-sdk/property-provider": "3.357.0", + "@aws-sdk/shared-ini-file-loader": "3.357.0", + "@aws-sdk/token-providers": "3.360.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -1801,12 +1808,12 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.310.0.tgz", - "integrity": "sha512-H4SzuZXILNhK6/IR1uVvsUDZvzc051hem7GLyYghBCu8mU+tq28YhKE8MfSroi6eL2e5Vujloij1OM2EQQkPkw==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.357.0.tgz", + "integrity": "sha512-0KRRAFrXy5HJe2vqnCWCoCS+fQw7IoIj3KQsuURJMW4F+ifisxCgEsh3brJ2LQlN4ElWTRJhlrDHNZ/pd61D4w==", "dependencies": { - "@aws-sdk/property-provider": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/property-provider": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -1814,23 +1821,23 @@ } }, "node_modules/@aws-sdk/eventstream-codec": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-codec/-/eventstream-codec-3.310.0.tgz", - "integrity": "sha512-clIeSgWbZbxwtsxZ/yoedNM0/kJFSIjjHPikuDGhxhqc+vP6TN3oYyVMFrYwFaTFhk2+S5wZcWYMw8Op1pWo+A==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-codec/-/eventstream-codec-3.357.0.tgz", + "integrity": "sha512-bqenTHG6GH6aCk/Il+ooWXVVAZuc8lOgVEy9bE2hI49oVqT8zSuXxQB+w1WWyZoAOPcelsjayB1wfPub8VDBxQ==", "dependencies": { "@aws-crypto/crc32": "3.0.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "@aws-sdk/util-hex-encoding": "3.310.0", "tslib": "^2.5.0" } }, "node_modules/@aws-sdk/eventstream-serde-browser": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-serde-browser/-/eventstream-serde-browser-3.310.0.tgz", - "integrity": "sha512-3S6ziuQVALgEyz0TANGtYDVeG8ArK4Y05mcgrs8qUTmsvlDIXX37cR/DvmVbNB76M4IrsZeSAIajL9644CywkA==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-serde-browser/-/eventstream-serde-browser-3.357.0.tgz", + "integrity": "sha512-hBabtmwuspVHGSKnUccDiSIbg+IVoBThx6wYt6i4edbWAITHF3ADVKXy7icV400CAyG0XTZgxjE6FKpiDxj9rQ==", "dependencies": { - "@aws-sdk/eventstream-serde-universal": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/eventstream-serde-universal": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -1838,11 +1845,11 @@ } }, "node_modules/@aws-sdk/eventstream-serde-config-resolver": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.310.0.tgz", - "integrity": "sha512-8s1Qdn9STj+sV75nUp9yt0W6fHS4BZ2jTm4Z/1Pcbvh2Gqs0WjH5n2StS+pDW5Y9J/HSGBl0ogmUr5lC5bXFHg==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.357.0.tgz", + "integrity": "sha512-E6rwk+1KFXhKmJ+v7JW5Uyyda1yN5XRVupCnCrtFsHFmhVGQxFacoUZIee3bfuCpC58dLSyESggxGpUd3XOSsw==", "dependencies": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -1850,12 +1857,12 @@ } }, "node_modules/@aws-sdk/eventstream-serde-node": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-serde-node/-/eventstream-serde-node-3.310.0.tgz", - "integrity": "sha512-kSnRomCgW43K9TmQYuwN9+AoYPnhyOKroanUMyZEzJk7rpCPMj4OzaUpXfDYOvznFNYn7NLaH6nHLJAr0VPlJA==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-serde-node/-/eventstream-serde-node-3.357.0.tgz", + "integrity": "sha512-boXDy+JWcPfHc9OIKV6I4Bh2XrLcg+eac+/LldNZFcDIB33/gHIM2CJw8u565Iebdz1NKEkP/QPPZbk2y+abPA==", "dependencies": { - "@aws-sdk/eventstream-serde-universal": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/eventstream-serde-universal": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -1863,12 +1870,12 @@ } }, "node_modules/@aws-sdk/eventstream-serde-universal": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-serde-universal/-/eventstream-serde-universal-3.310.0.tgz", - "integrity": "sha512-Qyjt5k/waV5cDukpgT824ISZAz5U0pwzLz5ztR409u85AGNkF/9n7MS+LSyBUBSb0WJ5pUeSD47WBk+nLq9Nhw==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-serde-universal/-/eventstream-serde-universal-3.357.0.tgz", + "integrity": "sha512-9/Wcdxx38XQAturqOAGYNCaLOzFVnW+xwxd4af9eNOfZfZ5PP5PRKBIpvKDsN26e3l4f3GodHx7MS1WB7BBc2w==", "dependencies": { - "@aws-sdk/eventstream-codec": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/eventstream-codec": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -1876,33 +1883,33 @@ } }, "node_modules/@aws-sdk/fetch-http-handler": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.310.0.tgz", - "integrity": "sha512-Bi9vIwzdkw1zMcvi/zGzlWS9KfIEnAq4NNhsnCxbQ4OoIRU9wvU+WGZdBBhxg0ZxZmpp1j1aZhU53lLjA07MHw==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.357.0.tgz", + "integrity": "sha512-5sPloTO8y8fAnS/6/Sfp/aVoL9zuhzkLdWBORNzMazdynVNEzWKWCPZ27RQpgkaCDHiXjqUY4kfuFXAGkvFfDQ==", "dependencies": { - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/querystring-builder": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/protocol-http": "3.357.0", + "@aws-sdk/querystring-builder": "3.357.0", + "@aws-sdk/types": "3.357.0", "@aws-sdk/util-base64": "3.310.0", "tslib": "^2.5.0" } }, "node_modules/@aws-sdk/hash-blob-browser": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/hash-blob-browser/-/hash-blob-browser-3.310.0.tgz", - "integrity": "sha512-OoR8p0cbypToysLT0v3o2oyjy6+DKrY7GNCAzHOHJK9xmqXCt+DsjKoPeiY7o1sWX2aN6Plmvubj/zWxMKEn/A==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/hash-blob-browser/-/hash-blob-browser-3.357.0.tgz", + "integrity": "sha512-RDd6UgrGHDmleTnXM9LRSSVa69euSAG2mlNhZMEDWk3OFseXVYqBDaqroVbQ01rM2UAe8MeBFchlV9OmxuVgvw==", "dependencies": { "@aws-sdk/chunked-blob-reader": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "node_modules/@aws-sdk/hash-node": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.310.0.tgz", - "integrity": "sha512-NvE2fhRc8GRwCXBfDehxVAWCmVwVMILliAKVPAEr4yz2CkYs0tqU51S48x23dtna07H4qHtgpeNqVTthcIQOEQ==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.357.0.tgz", + "integrity": "sha512-fq3LS9AxHKb7dTZkm6iM1TrGk6XOTZz96iEZPME1+vjiSEXGWuebHt87q92n+KozVGRypn9MId3lHOPBBjygNQ==", "dependencies": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "@aws-sdk/util-buffer-from": "3.310.0", "@aws-sdk/util-utf8": "3.310.0", "tslib": "^2.5.0" @@ -1912,11 +1919,11 @@ } }, "node_modules/@aws-sdk/hash-stream-node": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/hash-stream-node/-/hash-stream-node-3.310.0.tgz", - "integrity": "sha512-ZoXdybNgvMz1Hl6k/e32xVL3jmG5p2IEk5mTtLfFEuskTJ74Z+VMYKkkF1whyy7KQfH83H+TQGnsGtlRCchQKw==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/hash-stream-node/-/hash-stream-node-3.357.0.tgz", + "integrity": "sha512-KZjN1VAw1KHNp+xKVOWBGS+MpaYQTjZFD5f+7QQqW4TfbAkFFwIAEYIHq5Q8Gw+jVh0h61OrV/LyW3J2PVzc+w==", "dependencies": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "@aws-sdk/util-utf8": "3.310.0", "tslib": "^2.5.0" }, @@ -1925,11 +1932,11 @@ } }, "node_modules/@aws-sdk/invalid-dependency": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.310.0.tgz", - "integrity": "sha512-1s5RG5rSPXoa/aZ/Kqr5U/7lqpx+Ry81GprQ2bxWqJvWQIJ0IRUwo5pk8XFxbKVr/2a+4lZT/c3OGoBOM1yRRA==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.357.0.tgz", + "integrity": "sha512-HnCYZczf0VdyxMVMMxmA3QJAyyPSFbcMtZzgKbxVTWTG7GKpQe0psWZu/7O2Nk31mKg6vEUdiP1FylqLBsgMOA==", "dependencies": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, @@ -1945,22 +1952,22 @@ } }, "node_modules/@aws-sdk/md5-js": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/md5-js/-/md5-js-3.310.0.tgz", - "integrity": "sha512-x5sRBUrEfLWAS1EhwbbDQ7cXq6uvBxh3qR2XAsnGvFFceTeAadk7cVogWxlk3PC+OCeeym7c3/6Bv2HQ2f1YyQ==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/md5-js/-/md5-js-3.357.0.tgz", + "integrity": "sha512-to42sFAL7KgV/X9X40LLfEaNMHMGQL6/7mPMVCL/W2BZf3zw5OTl3lAaNyjXA+gO5Uo4lFEiQKAQVKNbr8b8Nw==", "dependencies": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "@aws-sdk/util-utf8": "3.310.0", "tslib": "^2.5.0" } }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.310.0.tgz", - "integrity": "sha512-uJJfHI7v4AgbJZRLtyI8ap2QRWkBokGc3iyUoQ+dVNT3/CE2ZCu694A6W+H0dRqg79dIE+f9CRNdtLGa/Ehhvg==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.357.0.tgz", + "integrity": "sha512-ep2T0FJXRDl6nffLqiVZUYfDocZ3B72wvHeozckkLVRX0TK91WEpzv4Zz2vdeBp6CGkM3g8oGjbb6ZqllUZ6TA==", "dependencies": { - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/protocol-http": "3.357.0", + "@aws-sdk/types": "3.357.0", "@aws-sdk/util-arn-parser": "3.310.0", "@aws-sdk/util-config-provider": "3.310.0", "tslib": "^2.5.0" @@ -1970,12 +1977,12 @@ } }, "node_modules/@aws-sdk/middleware-content-length": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.310.0.tgz", - "integrity": "sha512-P8tQZxgDt6CAh1wd/W6WPzjc+uWPJwQkm+F7rAwRlM+k9q17HrhnksGDKcpuuLyIhPQYdmOMIkpKVgXGa4avhQ==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.357.0.tgz", + "integrity": "sha512-zQOFEyzOXAgN4M54tYNWGxKxnyzY0WwYDTFzh9riJRmxN1hTEKHUKmze4nILIf5rkQmOG4kTf1qmfazjkvZAhw==", "dependencies": { - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/protocol-http": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -1983,14 +1990,14 @@ } }, "node_modules/@aws-sdk/middleware-endpoint": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.310.0.tgz", - "integrity": "sha512-Z+N2vOL8K354/lstkClxLLsr6hCpVRh+0tCMXrVj66/NtKysCEZ/0b9LmqOwD9pWHNiI2mJqXwY0gxNlKAroUg==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.357.0.tgz", + "integrity": "sha512-ScJi0SL8X/Lyi0Fp5blg0QN/Z6PoRwV/ZJXd8dQkXSznkbSvJHfqPP0xk/w3GcQ1TKsu5YEPfeYy8ejcq+7Pgg==", "dependencies": { - "@aws-sdk/middleware-serde": "3.310.0", - "@aws-sdk/types": "3.310.0", - "@aws-sdk/url-parser": "3.310.0", - "@aws-sdk/util-middleware": "3.310.0", + "@aws-sdk/middleware-serde": "3.357.0", + "@aws-sdk/types": "3.357.0", + "@aws-sdk/url-parser": "3.357.0", + "@aws-sdk/util-middleware": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -1998,12 +2005,12 @@ } }, "node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.310.0.tgz", - "integrity": "sha512-l3d1z2gt+gINJDnPSyu84IxfzjzPfCQrqC1sunw2cZGo/sXtEiq698Q3SiTcO2PGP4LBQAy2RHb5wVBJP708CQ==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.357.0.tgz", + "integrity": "sha512-KeizuiiDmdLeAbiNsJt/rZENY5iJo4wCTl7h81htDC60wSwVwFG03IdgvZlFH6jktYRh4mUDD/6Oljme6yPNxw==", "dependencies": { - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/protocol-http": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -2011,15 +2018,15 @@ } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.310.0.tgz", - "integrity": "sha512-5ndnLgzgGVpWkmHBAiYkagHqiSuow8q62J4J6E2PzaQ77+fm8W3nfdy7hK5trHokEyouCZdxT/XK/IRhgj/4PA==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.357.0.tgz", + "integrity": "sha512-NNQ/iPN6YyzqgVaV8AeYQMZ8y1OmUW27vmt0R66UUw5H5THGc6X9QXoKfie7OHn80Qv1S8P5jw8z5MpvDtjSnQ==", "dependencies": { "@aws-crypto/crc32": "3.0.0", "@aws-crypto/crc32c": "3.0.0", "@aws-sdk/is-array-buffer": "3.310.0", - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/protocol-http": "3.357.0", + "@aws-sdk/types": "3.357.0", "@aws-sdk/util-utf8": "3.310.0", "tslib": "^2.5.0" }, @@ -2028,12 +2035,12 @@ } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.310.0.tgz", - "integrity": "sha512-QWSA+46/hXorXyWa61ic2K7qZzwHTiwfk2e9mRRjeIRepUgI3qxFjsYqrWtrOGBjmFmq0pYIY8Bb/DCJuQqcoA==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.357.0.tgz", + "integrity": "sha512-HuGLcP7JP1qJ5wGT9GSlEknDaTSnOzHY4T6IPFuvFjAy3PvY5siQNm6+VRqdVS+n6/kzpL3JP5sAVM3aoxHT6Q==", "dependencies": { - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/protocol-http": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -2041,11 +2048,11 @@ } }, "node_modules/@aws-sdk/middleware-location-constraint": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.310.0.tgz", - "integrity": "sha512-LFm0JTQWwTPWL/tZU2wsQTl8J5PpDEkXjEhaXVKamtyH0xhysRqd+0n92n65dc8oztAuQkb9xUbErGn5b6gsew==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.357.0.tgz", + "integrity": "sha512-4IsIHhwZ2/o7yjLI1XtGMkJ442cbIN5/NtI/Ml0G5UHYviUm8sqvH2vldFBMK5bPuVdk6GpqXpy6wYc9rLJj2w==", "dependencies": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -2053,11 +2060,11 @@ } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.310.0.tgz", - "integrity": "sha512-Lurm8XofrASBRnAVtiSNuDSRsRqPNg27RIFLLsLp/pqog9nFJ0vz0kgdb9S5Z+zw83Mm+UlqOe6D8NTUNp4fVg==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.357.0.tgz", + "integrity": "sha512-dncT3tr+lZ9+duZo52rASgO6AKVwRcsc2/T93gmaYVrJqI6WWAwQ7yML5s72l9ZjQ5LZ+4jjrgtlufavAS0eCg==", "dependencies": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -2065,12 +2072,12 @@ } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.310.0.tgz", - "integrity": "sha512-SuB75/xk/gyue24gkriTwO2jFd7YcUGZDClQYuRejgbXSa3CO0lWyawQtfLcSSEBp9izrEVXuFH24K1eAft5nQ==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.357.0.tgz", + "integrity": "sha512-AXC54IeDS3jC1dbbkYHML4STvBPcKZ4IJTWdjEK1RCOgqXd0Ze1cE1e21wyj1tM6prF03zLyvpBd+3TS++nqfA==", "dependencies": { - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/protocol-http": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -2078,15 +2085,15 @@ } }, "node_modules/@aws-sdk/middleware-retry": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.310.0.tgz", - "integrity": "sha512-oTPsRy2W4s+dfxbJPW7Km+hHtv/OMsNsVfThAq8DDYKC13qlr1aAyOqGLD+dpBy2aKe7ss517Sy2HcHtHqm7/g==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.357.0.tgz", + "integrity": "sha512-ZCbXCYv3nglQqwREYxxpclrnR9MYPAnHlLcC8e9PbApqxGnaZdhoywxoqbgqT3hf/RM7kput4vEHDl1fyymcRQ==", "dependencies": { - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/service-error-classification": "3.310.0", - "@aws-sdk/types": "3.310.0", - "@aws-sdk/util-middleware": "3.310.0", - "@aws-sdk/util-retry": "3.310.0", + "@aws-sdk/protocol-http": "3.357.0", + "@aws-sdk/service-error-classification": "3.357.0", + "@aws-sdk/types": "3.357.0", + "@aws-sdk/util-middleware": "3.357.0", + "@aws-sdk/util-retry": "3.357.0", "tslib": "^2.5.0", "uuid": "^8.3.2" }, @@ -2103,12 +2110,12 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.310.0.tgz", - "integrity": "sha512-QK9x9g2ksg0hOjjYgqddeFcn5ctUEGdxJVu4OumPXceulefMcSO2jyH2qTybYSA93nqNQFdFmg5wQfvIRUWFCQ==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.357.0.tgz", + "integrity": "sha512-EFQaPD8SoXcK7RiEOZz0zIX9owQW6txu8vrOOVva9xMts36z/3E7b4FVsgEJ53Ixa1x38ddPJxp4U8EIaf+pvQ==", "dependencies": { - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/protocol-http": "3.357.0", + "@aws-sdk/types": "3.357.0", "@aws-sdk/util-arn-parser": "3.310.0", "tslib": "^2.5.0" }, @@ -2117,12 +2124,12 @@ } }, "node_modules/@aws-sdk/middleware-sdk-sts": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.310.0.tgz", - "integrity": "sha512-+5PFwlYNLvLLIfw0ASAoWV/iIF8Zv6R6QGtyP0CclhRSvNjgbQDVnV0g95MC5qvh+GB/Yjlkt8qAjLSPjHfsrQ==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.357.0.tgz", + "integrity": "sha512-Ng2VjLrPiL02QOcs1qs9jG2boO4Gn+v3VIbOJLG4zXcfbSq55iIWtlmr2ljfw9vP5aLhWtcODfmKHS5Bp+019Q==", "dependencies": { - "@aws-sdk/middleware-signing": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/middleware-signing": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -2130,11 +2137,11 @@ } }, "node_modules/@aws-sdk/middleware-serde": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.310.0.tgz", - "integrity": "sha512-RNeeTVWSLTaentUeCgQKZhAl+C6hxtwD78cQWS10UymWpQFwbaxztzKUu4UQS5xA2j6PxwPRRUjqa4jcFjfLsg==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.357.0.tgz", + "integrity": "sha512-bGI4kYuuEsFjlANbyJLyy4AovETnyf/SukgLOG7Qjbua+ZGuzvRhMsk21mBKKGrnsTO4PmtieJo6xClThGAN8g==", "dependencies": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -2142,15 +2149,15 @@ } }, "node_modules/@aws-sdk/middleware-signing": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.310.0.tgz", - "integrity": "sha512-f9mKq+XMdW207Af3hKjdTnpNhdtwqWuvFs/ZyXoOkp/g1MY1O6L23Jy6i52m29LxbT4AuNRG1oKODfXM0vYVjQ==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.357.0.tgz", + "integrity": "sha512-yB9ewEqI6Fw1OrmKFrUypbCqN5ijk06UGPochybamMuPxxkwMT3bnrm7eezsCA+TZbJyKhpffpyobwuv+xGNrA==", "dependencies": { - "@aws-sdk/property-provider": "3.310.0", - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/signature-v4": "3.310.0", - "@aws-sdk/types": "3.310.0", - "@aws-sdk/util-middleware": "3.310.0", + "@aws-sdk/property-provider": "3.357.0", + "@aws-sdk/protocol-http": "3.357.0", + "@aws-sdk/signature-v4": "3.357.0", + "@aws-sdk/types": "3.357.0", + "@aws-sdk/util-middleware": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -2158,11 +2165,11 @@ } }, "node_modules/@aws-sdk/middleware-ssec": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.310.0.tgz", - "integrity": "sha512-CnEwNKVpd5bXnrCKPaePF8mWTA9ET21OMBb54y9b0fd8K02zoOcdBz4DWfh1SjFD4HkgCdja4egd8l2ivyvqmw==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.357.0.tgz", + "integrity": "sha512-uE3nNvJclcY7SgGoOgDCUgfc7ElXQmWVpks8AZzAjJj7bG5j6Bv3FOOYtGtvtxUzTHaOdn+yQwjssV1cZ6GTQw==", "dependencies": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -2170,9 +2177,9 @@ } }, "node_modules/@aws-sdk/middleware-stack": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.310.0.tgz", - "integrity": "sha512-010O1PD+UAcZVKRvqEusE1KJqN96wwrf6QsqbRM0ywsKQ21NDweaHvEDlds2VHpgmofxkRLRu/IDrlPkKRQrRg==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.357.0.tgz", + "integrity": "sha512-nNV+jfwGwmbOGZujAY/U8AW3EbVlxa9DJDLz3TPp/39o6Vu5KEzHJyDDNreo2k9V/TMvV+nOzHafufgPdagv7w==", "dependencies": { "tslib": "^2.5.0" }, @@ -2181,13 +2188,13 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.319.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.319.0.tgz", - "integrity": "sha512-ytaLx2dlR5AdMSne6FuDCISVg8hjyKj+cHU20b2CRA/E/z+XXrLrssp4JrCgizRKPPUep0psMIa22Zd6osTT5Q==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.357.0.tgz", + "integrity": "sha512-M/CsAXjGblZS4rEbMb0Dn9IXbfq4EjVaTHBfvuILU/dKRppWvjnm2lRtqCZ+LIT3ATbAjA3/dY7dWsjxQWwijA==", "dependencies": { - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/types": "3.310.0", - "@aws-sdk/util-endpoints": "3.319.0", + "@aws-sdk/protocol-http": "3.357.0", + "@aws-sdk/types": "3.357.0", + "@aws-sdk/util-endpoints": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -2195,13 +2202,13 @@ } }, "node_modules/@aws-sdk/node-config-provider": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.310.0.tgz", - "integrity": "sha512-T/Pp6htc6hq/Cq+MLNDSyiwWCMVF6GqbBbXKVlO5L8rdHx4sq9xPdoPveZhGWrxvkanjA6eCwUp6E0riBOSVng==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.357.0.tgz", + "integrity": "sha512-kwBIzKCaW3UWqLdELhy7TcN8itNMOjbzga530nalFILMvn2IxrkdKQhNgxGBXy6QK6kCOtH6OmcrG3/oZkLwig==", "dependencies": { - "@aws-sdk/property-provider": "3.310.0", - "@aws-sdk/shared-ini-file-loader": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/property-provider": "3.357.0", + "@aws-sdk/shared-ini-file-loader": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -2209,14 +2216,14 @@ } }, "node_modules/@aws-sdk/node-http-handler": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.310.0.tgz", - "integrity": "sha512-irv9mbcM9xC2xYjArQF5SYmHBMu4ciMWtGsoHII1nRuFOl9FoT4ffTvEPuLlfC6pznzvKt9zvnm6xXj7gDChKg==", + "version": "3.360.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.360.0.tgz", + "integrity": "sha512-oMsXdMmNwHpUbebETO44bq0N4SocEMGfPjYNUTRs8md7ita5fuFd2qFuvf+ZRt6iVcGWluIqmF8DidD+b7d+TA==", "dependencies": { - "@aws-sdk/abort-controller": "3.310.0", - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/querystring-builder": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/abort-controller": "3.357.0", + "@aws-sdk/protocol-http": "3.357.0", + "@aws-sdk/querystring-builder": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -2224,11 +2231,11 @@ } }, "node_modules/@aws-sdk/property-provider": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.310.0.tgz", - "integrity": "sha512-3lxDb0akV6BBzmFe4nLPaoliQbAifyWJhuvuDOu7e8NzouvpQXs0275w9LePhhcgjKAEVXUIse05ZW2DLbxo/g==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.357.0.tgz", + "integrity": "sha512-im4W0u8WaYxG7J7ko4Xl3OEzK3Mrm1Rz6/txTGe6hTIHlyUISu1ekOQJXK6XYPqNMn8v1G3BiQREoRXUEJFbHg==", "dependencies": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -2236,11 +2243,11 @@ } }, "node_modules/@aws-sdk/protocol-http": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.310.0.tgz", - "integrity": "sha512-fgZ1aw/irQtnrsR58pS8ThKOWo57Py3xX6giRvwSgZDEcxHfVzuQjy9yPuV++v04fdmdtgpbGf8WfvAAJ11yXQ==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.357.0.tgz", + "integrity": "sha512-w1JHiI50VEea7duDeAspUiKJmmdIQblvRyjVMOqWA6FIQAyDVuEiPX7/MdQr0ScxhtRQxHbP0I4MFyl7ctRQvA==", "dependencies": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -2248,11 +2255,11 @@ } }, "node_modules/@aws-sdk/querystring-builder": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.310.0.tgz", - "integrity": "sha512-ZHH8GV/80+pWGo7DzsvwvXR5xVxUHXUvPJPFAkhr6nCf78igdoF8gR10ScFoEKbtEapoNTaZlKHPXxpD8aPG7A==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.357.0.tgz", + "integrity": "sha512-aQcicqB6Y2cNaXPPwunz612a01SMiQQPsdz632F/3Lzn0ua82BJKobHOtaiTUlmVJ5Q4/EAeNfwZgL7tTUNtDQ==", "dependencies": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "@aws-sdk/util-uri-escape": "3.310.0", "tslib": "^2.5.0" }, @@ -2261,11 +2268,11 @@ } }, "node_modules/@aws-sdk/querystring-parser": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.310.0.tgz", - "integrity": "sha512-YkIznoP6lsiIUHinx++/lbb3tlMURGGqMpo0Pnn32zYzGrJXA6eC3D0as2EcMjo55onTfuLcIiX4qzXes2MYOA==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.357.0.tgz", + "integrity": "sha512-Svvq+atRNP9s2VxiklcUNgCzmt3T5kfs7X2C+yjmxHvOQTPjLNaNGbfC/vhjOK7aoXw0h+lBac48r5ymx1PbQA==", "dependencies": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -2273,16 +2280,16 @@ } }, "node_modules/@aws-sdk/s3-request-presigner": { - "version": "3.319.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.319.0.tgz", - "integrity": "sha512-mq/4nqr/3sHGx9iJqPXXkfBPAlXGB000u4dX4QCMPFmMupfH5sT8M+hYgcB6N/2HuZ+JNJQntwu9q97gjhwpDQ==", + "version": "3.360.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.360.0.tgz", + "integrity": "sha512-SWfRyyEbGKnrllKRWHsFfS4xbfM1YHYsaO2PJRo49vYIZvsj10vs0HZX8fF0v4md1XW99E7wBanxkoMo3mYVvg==", "dependencies": { - "@aws-sdk/middleware-endpoint": "3.310.0", - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/signature-v4-multi-region": "3.310.0", - "@aws-sdk/smithy-client": "3.316.0", - "@aws-sdk/types": "3.310.0", - "@aws-sdk/util-format-url": "3.310.0", + "@aws-sdk/middleware-endpoint": "3.357.0", + "@aws-sdk/protocol-http": "3.357.0", + "@aws-sdk/signature-v4-multi-region": "3.357.0", + "@aws-sdk/smithy-client": "3.360.0", + "@aws-sdk/types": "3.357.0", + "@aws-sdk/util-format-url": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -2290,19 +2297,19 @@ } }, "node_modules/@aws-sdk/service-error-classification": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.310.0.tgz", - "integrity": "sha512-PuyC7k3qfIKeH2LCnDwbttMOKq3qAx4buvg0yfnJtQOz6t1AR8gsnAq0CjKXXyfkXwNKWTqCpE6lVNUIkXgsMw==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.357.0.tgz", + "integrity": "sha512-VuXeL4g5vKO9HjgCZlxmH8Uv1FcqUSjmbPpQkbNtYIDck6u0qzM0rG+n0/1EjyQbPSr3MhW/pkWs5nx2Nljlyg==", "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/shared-ini-file-loader": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.310.0.tgz", - "integrity": "sha512-N0q9pG0xSjQwc690YQND5bofm+4nfUviQ/Ppgan2kU6aU0WUq8KwgHJBto/YEEI+VlrME30jZJnxtOvcZJc2XA==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.357.0.tgz", + "integrity": "sha512-ceyqM4XxQe0Plb/oQAD2t1UOV2Iy4PFe1oAGM8dfJzYrRKu7zvMwru7/WaB3NYq+/mIY6RU+jjhRmjQ3GySVqA==", "dependencies": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -2310,14 +2317,15 @@ } }, "node_modules/@aws-sdk/signature-v4": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.310.0.tgz", - "integrity": "sha512-1M60P1ZBNAjCFv9sYW29OF6okktaeibWyW3lMXqzoHF70lHBZh+838iUchznXUA5FLabfn4jBFWMRxlAXJUY2Q==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.357.0.tgz", + "integrity": "sha512-itt4/Jh9FqnzK30qIjXFBvM4J7zN4S/AAqsRMnaX7U4f/MV+1YxQHmzimpdMnsCXXs2jqFqKVRu6DewxJ3nbxg==", "dependencies": { + "@aws-sdk/eventstream-codec": "3.357.0", "@aws-sdk/is-array-buffer": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "@aws-sdk/util-hex-encoding": "3.310.0", - "@aws-sdk/util-middleware": "3.310.0", + "@aws-sdk/util-middleware": "3.357.0", "@aws-sdk/util-uri-escape": "3.310.0", "@aws-sdk/util-utf8": "3.310.0", "tslib": "^2.5.0" @@ -2327,13 +2335,13 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.310.0.tgz", - "integrity": "sha512-q8W+RIomTS/q85Ntgks/CoDElwqkC9+4OCicee5YznNHjQ4gtNWhUkYIyIRWRmXa/qx/AUreW9DM8FAecCOdng==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.357.0.tgz", + "integrity": "sha512-eyO3GibYLNCPZ/YxM/ZVDh1fTMKvIUj4fpVo0bxQTKNlqNkVumAIOVLoH5um1A9FN7nDdz+40a7jwYSPlkxW6A==", "dependencies": { - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/signature-v4": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/protocol-http": "3.357.0", + "@aws-sdk/signature-v4": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -2349,12 +2357,14 @@ } }, "node_modules/@aws-sdk/smithy-client": { - "version": "3.316.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.316.0.tgz", - "integrity": "sha512-6YXOKbRnXeS8r8RWzuL6JMBolDYM5Wa4fD/VY6x/wK78i2xErHOvqzHgyyeLI1MMw4uqyd4wRNJNWC9TMPduXw==", + "version": "3.360.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.360.0.tgz", + "integrity": "sha512-R7wbT2SkgWNEAxMekOTNcPcvBszabW2+qHjrcelbbVJNjx/2yK+MbpZI4WRSncByQMeeoW+aSUP+JgsbpiOWfw==", "dependencies": { - "@aws-sdk/middleware-stack": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/middleware-stack": "3.357.0", + "@aws-sdk/types": "3.357.0", + "@aws-sdk/util-stream": "3.360.0", + "@smithy/types": "^1.0.0", "tslib": "^2.5.0" }, "engines": { @@ -2362,14 +2372,14 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.319.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.319.0.tgz", - "integrity": "sha512-5utg6VL6Pl0uiLUn8ZJPYYxzCb9VRPsgJmGXktRUwq0YlTJ6ABcaxTXwZcC++sjh/qyCQDK5PPLNU5kIBttHMQ==", + "version": "3.360.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.360.0.tgz", + "integrity": "sha512-gtnCmn2NL7uSwadqQPeU74Wo7Wf1NMJtui+KSWPYpc3joRZqIYj0kL5w0IT2S9tPQwCFerWVfhkvRkSGJ4nZ/g==", "dependencies": { - "@aws-sdk/client-sso-oidc": "3.319.0", - "@aws-sdk/property-provider": "3.310.0", - "@aws-sdk/shared-ini-file-loader": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/client-sso-oidc": "3.360.0", + "@aws-sdk/property-provider": "3.357.0", + "@aws-sdk/shared-ini-file-loader": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -2377,9 +2387,9 @@ } }, "node_modules/@aws-sdk/types": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.310.0.tgz", - "integrity": "sha512-j8eamQJ7YcIhw7fneUfs8LYl3t01k4uHi4ZDmNRgtbmbmTTG3FZc2MotStZnp3nZB6vLiPF1o5aoJxWVvkzS6A==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.357.0.tgz", + "integrity": "sha512-/riCRaXg3p71BeWnShrai0y0QTdXcouPSM0Cn1olZbzTf7s71aLEewrc96qFrL70XhY4XvnxMpqQh+r43XIL3g==", "dependencies": { "tslib": "^2.5.0" }, @@ -2388,12 +2398,12 @@ } }, "node_modules/@aws-sdk/url-parser": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.310.0.tgz", - "integrity": "sha512-mCLnCaSB9rQvAgx33u0DujLvr4d5yEm/W5r789GblwwQnlNXedVu50QRizMLTpltYWyAUoXjJgQnJHmJMaKXhw==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.357.0.tgz", + "integrity": "sha512-fAaU6cFsaAba01lCRsRJiYR/LfXvX2wudyEyutBVglE4dWSoSeu3QJNxImIzTBULfbiFhz59++NQ1JUVx88IVg==", "dependencies": { - "@aws-sdk/querystring-parser": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/querystring-parser": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, @@ -2463,12 +2473,12 @@ } }, "node_modules/@aws-sdk/util-defaults-mode-browser": { - "version": "3.316.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.316.0.tgz", - "integrity": "sha512-6FSqLhYmaihtH2n1s4b2rlLW0ABU8N6VZIfzLfe2ING4PF0MzfaMMhnTFUHVXfKCVGoR8yP6iyFTRCyHGVEL1w==", + "version": "3.360.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.360.0.tgz", + "integrity": "sha512-/GR8VlK9xo1Q5WbVYuNaZ+XfoCFdWNb4z4mpoEgvEgBH4R0GjqiAqLftUA8Ykq1tJuDAKPYVzUNzK8DC0pt7/g==", "dependencies": { - "@aws-sdk/property-provider": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/property-provider": "3.357.0", + "@aws-sdk/types": "3.357.0", "bowser": "^2.11.0", "tslib": "^2.5.0" }, @@ -2477,15 +2487,15 @@ } }, "node_modules/@aws-sdk/util-defaults-mode-node": { - "version": "3.316.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.316.0.tgz", - "integrity": "sha512-dkYy10hdjPSScXXvnjGpZpnJxllkb6ICHgLMwZ4JczLHhPM12T/4PQ758YN8HS+muiYDGX1Bl2z1jd/bMcewBQ==", + "version": "3.360.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.360.0.tgz", + "integrity": "sha512-gR3Ctqpyl7SgStDJ1Jlq6qQDuw/rS9AgbAXx+s3wsmm3fm8lHKkXkDPYVvNDqd6dVXRO6q8MRx00lwkGI4qrpQ==", "dependencies": { - "@aws-sdk/config-resolver": "3.310.0", - "@aws-sdk/credential-provider-imds": "3.310.0", - "@aws-sdk/node-config-provider": "3.310.0", - "@aws-sdk/property-provider": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/config-resolver": "3.357.0", + "@aws-sdk/credential-provider-imds": "3.357.0", + "@aws-sdk/node-config-provider": "3.357.0", + "@aws-sdk/property-provider": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -2493,11 +2503,11 @@ } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.319.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.319.0.tgz", - "integrity": "sha512-3I64UMoYA2e2++oOUJXRcFtYLpLylnZFRltWfPo1B3dLlf+MIWat9djT+mMus+hW1ntLsvAIVu1hLVePJC0gvw==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.357.0.tgz", + "integrity": "sha512-XHKyS5JClT9su9hDif715jpZiWHQF9gKZXER8tW0gOizU3R9cyWc9EsJ2BRhFNhi7nt/JF/CLUEc5qDx3ETbUw==", "dependencies": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -2505,12 +2515,12 @@ } }, "node_modules/@aws-sdk/util-format-url": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.310.0.tgz", - "integrity": "sha512-NBOvmvvVR3ydquHmznfgtakiSgDhq8Ww6fq8TUaEjM+Es6+iqY4AwZo0rZ9xTX3GpCcoZy391HUi6kiXRAFzuA==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.357.0.tgz", + "integrity": "sha512-mdTO210Nkraw+gY/KxJN9wvX8brIT5+d5tLtLSNKx4K8Fx+qbIug9VeOaVVlv9S1tX5lUvG1l9SEIwq3RCserg==", "dependencies": { - "@aws-sdk/querystring-builder": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/querystring-builder": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -2540,9 +2550,9 @@ } }, "node_modules/@aws-sdk/util-middleware": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.310.0.tgz", - "integrity": "sha512-FTSUKL/eRb9X6uEZClrTe27QFXUNNp7fxYrPndZwk1hlaOP5ix+MIHBcI7pIiiY/JPfOUmPyZOu+HetlFXjWog==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.357.0.tgz", + "integrity": "sha512-pV1krjZs7BdahZBfsCJMatE8kcor7GFsBOWrQgQDm9T0We5b5xPpOO2vxAD0RytBpY8Ky2ELs/+qXMv7l5fWIA==", "dependencies": { "tslib": "^2.5.0" }, @@ -2551,39 +2561,30 @@ } }, "node_modules/@aws-sdk/util-retry": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-retry/-/util-retry-3.310.0.tgz", - "integrity": "sha512-FwWGhCBLfoivTMUHu1LIn4NjrN9JLJ/aX5aZmbcPIOhZVFJj638j0qDgZXyfvVqBuBZh7M8kGq0Oahy3dp69OA==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-retry/-/util-retry-3.357.0.tgz", + "integrity": "sha512-SUqYJE9msbuOVq+vnUy+t0LH7XuYNFz66dSF8q6tedsbJK4j8tgya0I1Ct3m06ynGrXDJMaj39I7AXCyW9bjtw==", "dependencies": { - "@aws-sdk/service-error-classification": "3.310.0", + "@aws-sdk/service-error-classification": "3.357.0", "tslib": "^2.5.0" }, "engines": { "node": ">= 14.0.0" } }, - "node_modules/@aws-sdk/util-stream-browser": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-stream-browser/-/util-stream-browser-3.310.0.tgz", - "integrity": "sha512-bysXZHwFwvbqOTCScCdCnoLk1K3GCo0HRIYEZuL7O7MHrQmfaYRXcaft/p22+GUv9VeFXS/eJJZ5r4u32az94w==", + "node_modules/@aws-sdk/util-stream": { + "version": "3.360.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-stream/-/util-stream-3.360.0.tgz", + "integrity": "sha512-t3naBfNesXwLis29pzSfLx2ifCn2180GiPjRaIsQP14IiVCBOeT1xaU6Dpyk7WeR/jW4cu7wGl+kbeyfNF6QmQ==", "dependencies": { - "@aws-sdk/fetch-http-handler": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/fetch-http-handler": "3.357.0", + "@aws-sdk/node-http-handler": "3.360.0", + "@aws-sdk/types": "3.357.0", "@aws-sdk/util-base64": "3.310.0", + "@aws-sdk/util-buffer-from": "3.310.0", "@aws-sdk/util-hex-encoding": "3.310.0", "@aws-sdk/util-utf8": "3.310.0", "tslib": "^2.5.0" - } - }, - "node_modules/@aws-sdk/util-stream-node": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-stream-node/-/util-stream-node-3.310.0.tgz", - "integrity": "sha512-hueAXFK0GVvnfYFgqbF7587xZfMZff5jlIFZOHqx7XVU7bl7qrRUCnphHk8H6yZ7RoQbDPcfmHJgtEoAJg1T1Q==", - "dependencies": { - "@aws-sdk/node-http-handler": "3.310.0", - "@aws-sdk/types": "3.310.0", - "@aws-sdk/util-buffer-from": "3.310.0", - "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" @@ -2601,22 +2602,22 @@ } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.310.0.tgz", - "integrity": "sha512-yU/4QnHHuQ5z3vsUqMQVfYLbZGYwpYblPiuZx4Zo9+x0PBkNjYMqctdDcrpoH9Z2xZiDN16AmQGK1tix117ZKw==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.357.0.tgz", + "integrity": "sha512-JHaWlNIUkPNvXkqeDOrqFzAlAgdwZK5mZw7FQnCRvf8tdSogpGZSkuyb9Z6rLD9gC40Srbc2nepO1cFpeMsDkA==", "dependencies": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "bowser": "^2.11.0", "tslib": "^2.5.0" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.310.0.tgz", - "integrity": "sha512-Ra3pEl+Gn2BpeE7KiDGpi4zj7WJXZA5GXnGo3mjbi9+Y3zrbuhJAbdZO3mO/o7xDgMC6ph4xCTbaSGzU6b6EDg==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.357.0.tgz", + "integrity": "sha512-RdpQoaJWQvcS99TVgSbT451iGrlH4qpWUWFA9U1IRhxOSsmC1hz8ME7xc8nci9SREx/ZlfT3ai6LpoAzAtIEMA==", "dependencies": { - "@aws-sdk/node-config-provider": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/node-config-provider": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -2652,12 +2653,12 @@ } }, "node_modules/@aws-sdk/util-waiter": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-waiter/-/util-waiter-3.310.0.tgz", - "integrity": "sha512-AV5j3guH/Y4REu+Qh3eXQU9igljHuU4XjX2sADAgf54C0kkhcCCkkiuzk3IsX089nyJCqIcj5idbjdvpnH88Vw==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-waiter/-/util-waiter-3.357.0.tgz", + "integrity": "sha512-jQQGA5G8bm0JP5C4U85VzMpkFHdeeT7fOSUncXLG9Sh8Ambzi4XTud8m5/dA7aNJkvPwZeIF9QdgWCOzpkp1xA==", "dependencies": { - "@aws-sdk/abort-controller": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/abort-controller": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" }, "engines": { @@ -2676,21 +2677,21 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.20.14", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.14.tgz", - "integrity": "sha512-0YpKHD6ImkWMEINCyDAD0HLLUH/lPCefG8ld9it8DJB2wnApraKuhgYTvTY1z7UFIfBTGy5LwncZ+5HWWGbhFw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.5.tgz", + "integrity": "sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA==", "dev": true, "engines": { "node": ">=6.9.0" @@ -2789,13 +2790,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", - "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz", + "integrity": "sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-validator-option": "^7.18.6", + "@babel/compat-data": "^7.22.5", + "@babel/helper-validator-option": "^7.22.5", "browserslist": "^4.21.3", "lru-cache": "^5.1.1", "semver": "^6.3.0" @@ -2896,9 +2897,9 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", + "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", "dev": true, "engines": { "node": ">=6.9.0" @@ -2917,37 +2918,51 @@ } }, "node_modules/@babel/helper-function-name": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", - "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", + "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", "dev": true, "dependencies": { - "@babel/template": "^7.18.10", - "@babel/types": "^7.19.0" + "@babel/template": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name/node_modules/@babel/template": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", + "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.20.7.tgz", - "integrity": "sha512-9J0CxJLq315fEdi4s7xK5TQaNYjZw+nDVpVqr1axNGKzdrdwYBD5b4uKv3n75aABG0rCCTK8Im8Ww7eYfMrZgw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz", + "integrity": "sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==", "dev": true, "dependencies": { - "@babel/types": "^7.20.7" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -2985,21 +3000,21 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", - "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", - "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", "dev": true, "engines": { "node": ">=6.9.0" @@ -3024,17 +3039,31 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz", - "integrity": "sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.5.tgz", + "integrity": "sha512-aLdNM5I3kdI/V9xGNyKSF3X/gTyMUBohTZ+/3QdQKAA9vxIiy12E+8E2HoOP1/DjeqU+g6as35QHJNMDDYpuCg==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-member-expression-to-functions": "^7.20.7", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", - "@babel/types": "^7.20.7" + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers/node_modules/@babel/template": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", + "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -3077,27 +3106,27 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", + "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", "dev": true, "engines": { "node": ">=6.9.0" @@ -3133,12 +3162,12 @@ } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", + "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-validator-identifier": "^7.22.5", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, @@ -3147,9 +3176,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.20.15", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.15.tgz", - "integrity": "sha512-DI4a1oZuf8wC+oAJA9RW6ga3Zbe8RZFt7kD9i4qAspz3I/yHet1VvC3DiSy/fsUvv5pvJuNPh0LPOdCcqinDPg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", + "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -3694,19 +3723,19 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.7.tgz", - "integrity": "sha512-LWYbsiXTPKl+oBlXUGlwNlJZetXD5Am+CyBdqhPsDVjM9Jc8jwBJFrKhHf900Kfk2eZG1y9MAG3UNajol7A4VQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.5.tgz", + "integrity": "sha512-2edQhLfibpWpsVBx2n/GKOz6JdGQvLruZQfGr9l1qes2KQaWswjBzhQF7UDUZMNaMMQeYnQzxwOMPsbYF7wqPQ==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-replace-supers": "^7.20.7", - "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", "globals": "^11.1.0" }, "engines": { @@ -3716,6 +3745,30 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-classes/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-transform-classes/node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz", + "integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/plugin-transform-computed-properties": { "version": "7.20.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.20.7.tgz", @@ -3810,14 +3863,14 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", - "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz", + "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==", "dev": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -4308,19 +4361,19 @@ } }, "node_modules/@babel/traverse": { - "version": "7.20.13", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.13.tgz", - "integrity": "sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", + "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.7", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.20.13", - "@babel/types": "^7.20.7", + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -4328,14 +4381,55 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/types": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz", - "integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==", + "node_modules/@babel/traverse/node_modules/@babel/generator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", + "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/types": "^7.22.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz", + "integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", + "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", "to-fast-properties": "^2.0.0" }, "engines": { @@ -4940,9 +5034,9 @@ } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.8.tgz", - "integrity": "sha512-YK5G9LaddzGbcucK4c8h5tWFmMPBvRZ/uyWmN1/SbBdIvqGUdWGkJ5BAaccgs6XbzVLsqbPJrBSFwKv3kT9i7w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", "dev": true, "engines": { "node": ">=6.0.0" @@ -4988,13 +5082,13 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.14", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz", - "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==", + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" } }, "node_modules/@leichtgewicht/ip-codec": { @@ -5841,9 +5935,9 @@ } }, "node_modules/@ngtools/webpack": { - "version": "15.2.6", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-15.2.6.tgz", - "integrity": "sha512-I+kekKItfsCLdX+ZjjmsWqd0AyoYGTQPjlbQAiPtmdH73/rfPOF4Q/3AU4tzTdn0n0GXqZWv6VOs91w99ydi0A==", + "version": "15.2.8", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-15.2.8.tgz", + "integrity": "sha512-BJexeT4FxMtToVBGa3wdl6rrkYXgilP0kkSH4Qzu4MPlLPbeBSr4XQalQriewlpC2uzG0r2SJfrAe2eDhtSykA==", "dev": true, "engines": { "node": "^14.20.0 || ^16.13.0 || >=18.10.0", @@ -6044,13 +6138,13 @@ } }, "node_modules/@schematics/angular": { - "version": "15.2.6", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-15.2.6.tgz", - "integrity": "sha512-OcBUvVAxZEMBX+fi0ytybeAdmStra+GwtlvipS70yOxcAgJ84ZrnZGN7a072cCVQcq7AgqUfssnyqCx1wu+yCg==", + "version": "15.2.8", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-15.2.8.tgz", + "integrity": "sha512-F49IEzCFxQlpaMIgTO/wF1l/CLQKif7VaiDdyiTKOeT22IMmyd61FUmWDyZYfCBqMlvBmvDGx64HaHWes1HYCg==", "dev": true, "dependencies": { - "@angular-devkit/core": "15.2.6", - "@angular-devkit/schematics": "15.2.6", + "@angular-devkit/core": "15.2.8", + "@angular-devkit/schematics": "15.2.8", "jsonc-parser": "3.2.0" }, "engines": { @@ -6068,6 +6162,29 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/@smithy/protocol-http": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-1.1.0.tgz", + "integrity": "sha512-H5y/kZOqfJSqRkwtcAoVbqONmhdXwSgYNJ1Glk5Ry8qlhVVy5qUzD9EklaCH8/XLnoCsLO/F/Giee8MIvaBRkg==", + "dependencies": { + "@smithy/types": "^1.1.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-1.1.0.tgz", + "integrity": "sha512-KzmvisMmuwD2jZXuC9e65JrgsZM97y5NpDU7g347oB+Q+xQLU6hQZ5zFNNbEfwwOJHoOvEVTna+dk1h/lW7alw==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -6319,9 +6436,9 @@ } }, "node_modules/@types/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.2.tgz", + "integrity": "sha512-kNnC1GFBLuhImSnV7w4njQkUiJi0ZXUycu1rUaouPqiKlXkh77JKgdRnTAp1x5eBwcIwbtI+3otwzuIDEuDoxQ==", "dev": true }, "node_modules/@types/ws": { @@ -6897,6 +7014,23 @@ "rxjs": ">=6.0.0" } }, + "node_modules/angular2-csv": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/angular2-csv/-/angular2-csv-0.2.9.tgz", + "integrity": "sha512-RmVpaT2DSLJikPIxXfvhNHT/a03g+jM7Wsx0x+kNmCTuTqA1MAIPVhZPNgFvoSvZM898ACgcTSV6vvsDLdt1BA==", + "dependencies": { + "tslib": "^1.9.0" + }, + "peerDependencies": { + "@angular/common": "^6.0.0-rc.0 || ^6.0.0", + "@angular/core": "^6.0.0-rc.0 || ^6.0.0" + } + }, + "node_modules/angular2-csv/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -7433,13 +7567,53 @@ } }, "node_modules/britecharts": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/britecharts/-/britecharts-2.18.0.tgz", - "integrity": "sha512-dG/kXx3KcUnWaMn+hInrpQNX1hbXgwTCJBawhHUN/GHkVMTe5+aVD/le+5oOhwDpfaMs8nn3TpY9D4o2hdP8HQ==", + "version": "3.0.0-alpha-6.1.6", + "resolved": "https://registry.npmjs.org/britecharts/-/britecharts-3.0.0-alpha-6.1.6.tgz", + "integrity": "sha512-ypSOJOJcBzXkxSY/OIxIIlnKKSCFqV+9QiHGbtM9a/fo8O5SFn18GKARKG8ZdGfvb8LwfuDfWTghlUA6WmcR7A==", "dependencies": { "base-64": "^0.1.0", - "d3": "^5.16.0", - "lodash.assign": "^4.2.0" + "d3-axis": "^1.0.12", + "d3-brush": "^1.1.6", + "d3-collection": "^1.0.7", + "d3-format": "^1.4.5", + "d3-scale": "^3.2.4", + "d3-selection": "^1.4.1", + "d3-shape": "^1.3.7", + "d3-time": "^1.1.0", + "d3-time-format": "^3.0.0", + "d3-transition": "^1.3.2", + "d3-voronoi": "^1.1.4", + "d3-zoom": "^1.8.3" + } + }, + "node_modules/britecharts/node_modules/d3-selection": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", + "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" + }, + "node_modules/britecharts/node_modules/d3-transition": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.3.2.tgz", + "integrity": "sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==", + "dependencies": { + "d3-color": "1", + "d3-dispatch": "1", + "d3-ease": "1", + "d3-interpolate": "1", + "d3-selection": "^1.1.0", + "d3-timer": "1" + } + }, + "node_modules/britecharts/node_modules/d3-zoom": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.8.3.tgz", + "integrity": "sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==", + "dependencies": { + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" } }, "node_modules/browserslist": { @@ -7897,7 +8071,7 @@ "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, "node_modules/color-support": { @@ -7923,7 +8097,8 @@ "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true }, "node_modules/comment-parser": { "version": "1.3.1", @@ -8374,48 +8549,13 @@ "node": ">=10" } }, - "node_modules/d3": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/d3/-/d3-5.16.0.tgz", - "integrity": "sha512-4PL5hHaHwX4m7Zr1UapXW23apo6pexCgdetdJ5kTmADpG/7T9Gkxw0M0tf/pjoB63ezCCm0u5UaFYy2aMt0Mcw==", - "dependencies": { - "d3-array": "1", - "d3-axis": "1", - "d3-brush": "1", - "d3-chord": "1", - "d3-collection": "1", - "d3-color": "1", - "d3-contour": "1", - "d3-dispatch": "1", - "d3-drag": "1", - "d3-dsv": "1", - "d3-ease": "1", - "d3-fetch": "1", - "d3-force": "1", - "d3-format": "1", - "d3-geo": "1", - "d3-hierarchy": "1", - "d3-interpolate": "1", - "d3-path": "1", - "d3-polygon": "1", - "d3-quadtree": "1", - "d3-random": "1", - "d3-scale": "2", - "d3-scale-chromatic": "1", - "d3-selection": "1", - "d3-shape": "1", - "d3-time": "1", - "d3-time-format": "2", - "d3-timer": "1", - "d3-transition": "1", - "d3-voronoi": "1", - "d3-zoom": "1" - } - }, "node_modules/d3-array": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", - "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dependencies": { + "internmap": "^1.0.0" + } }, "node_modules/d3-axis": { "version": "1.0.12", @@ -8439,13 +8579,17 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" }, - "node_modules/d3-chord": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.6.tgz", - "integrity": "sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==", + "node_modules/d3-brush/node_modules/d3-transition": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.3.2.tgz", + "integrity": "sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==", "dependencies": { - "d3-array": "1", - "d3-path": "1" + "d3-color": "1", + "d3-dispatch": "1", + "d3-ease": "1", + "d3-interpolate": "1", + "d3-selection": "^1.1.0", + "d3-timer": "1" } }, "node_modules/d3-collection": { @@ -8458,14 +8602,6 @@ "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz", "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==" }, - "node_modules/d3-contour": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-1.3.2.tgz", - "integrity": "sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==", - "dependencies": { - "d3-array": "^1.1.1" - } - }, "node_modules/d3-dispatch": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz", @@ -8485,69 +8621,16 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" }, - "node_modules/d3-dsv": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.2.0.tgz", - "integrity": "sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g==", - "dependencies": { - "commander": "2", - "iconv-lite": "0.4", - "rw": "1" - }, - "bin": { - "csv2json": "bin/dsv2json", - "csv2tsv": "bin/dsv2dsv", - "dsv2dsv": "bin/dsv2dsv", - "dsv2json": "bin/dsv2json", - "json2csv": "bin/json2dsv", - "json2dsv": "bin/json2dsv", - "json2tsv": "bin/json2dsv", - "tsv2csv": "bin/dsv2dsv", - "tsv2json": "bin/dsv2json" - } - }, "node_modules/d3-ease": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.7.tgz", "integrity": "sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ==" }, - "node_modules/d3-fetch": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.2.0.tgz", - "integrity": "sha512-yC78NBVcd2zFAyR/HnUiBS7Lf6inSCoWcSxFfw8FYL7ydiqe80SazNwoffcqOfs95XaLo7yebsmQqDKSsXUtvA==", - "dependencies": { - "d3-dsv": "1" - } - }, - "node_modules/d3-force": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz", - "integrity": "sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==", - "dependencies": { - "d3-collection": "1", - "d3-dispatch": "1", - "d3-quadtree": "1", - "d3-timer": "1" - } - }, "node_modules/d3-format": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz", "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==" }, - "node_modules/d3-geo": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.12.1.tgz", - "integrity": "sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg==", - "dependencies": { - "d3-array": "1" - } - }, - "node_modules/d3-hierarchy": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz", - "integrity": "sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==" - }, "node_modules/d3-interpolate": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz", @@ -8561,41 +8644,24 @@ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" }, - "node_modules/d3-polygon": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.6.tgz", - "integrity": "sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ==" - }, - "node_modules/d3-quadtree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.7.tgz", - "integrity": "sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==" - }, - "node_modules/d3-random": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.2.tgz", - "integrity": "sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ==" - }, "node_modules/d3-scale": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz", - "integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", + "integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==", "dependencies": { - "d3-array": "^1.2.0", - "d3-collection": "1", - "d3-format": "1", - "d3-interpolate": "1", - "d3-time": "1", - "d3-time-format": "2" + "d3-array": "^2.3.0", + "d3-format": "1 - 2", + "d3-interpolate": "1.2.0 - 2", + "d3-time": "^2.1.1", + "d3-time-format": "2 - 3" } }, - "node_modules/d3-scale-chromatic": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz", - "integrity": "sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg==", + "node_modules/d3-scale/node_modules/d3-time": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz", + "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==", "dependencies": { - "d3-color": "1", - "d3-interpolate": "1" + "d3-array": "2" } }, "node_modules/d3-selection": { @@ -8620,11 +8686,11 @@ "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" }, "node_modules/d3-time-format": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.3.0.tgz", - "integrity": "sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz", + "integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==", "dependencies": { - "d3-time": "1" + "d3-time": "1 - 2" } }, "node_modules/d3-timer": { @@ -8633,49 +8699,54 @@ "integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==" }, "node_modules/d3-transition": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.3.2.tgz", - "integrity": "sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", "dependencies": { - "d3-color": "1", - "d3-dispatch": "1", - "d3-ease": "1", - "d3-interpolate": "1", - "d3-selection": "^1.1.0", - "d3-timer": "1" + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" } }, - "node_modules/d3-transition/node_modules/d3-selection": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", - "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" - }, "node_modules/d3-voronoi": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz", "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==" }, "node_modules/d3-zoom": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.8.3.tgz", - "integrity": "sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", "dependencies": { - "d3-dispatch": "1", - "d3-drag": "1", - "d3-interpolate": "1", - "d3-selection": "1", - "d3-transition": "1" + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" } }, - "node_modules/d3-zoom/node_modules/d3-selection": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", - "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" - }, - "node_modules/d3/node_modules/d3-selection": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", - "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" + "node_modules/d3-zoom/node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } }, "node_modules/damerau-levenshtein": { "version": "1.0.8", @@ -10019,18 +10090,24 @@ "dev": true }, "node_modules/fast-xml-parser": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.1.2.tgz", - "integrity": "sha512-CDYeykkle1LiA/uqQyNwYpFbyF6Axec6YapmpUP+/RHWIoR1zKjocdvNaTsxCxZzQ6v9MLXaSYm9Qq0thv0DHg==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz", + "integrity": "sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==", + "funding": [ + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + }, + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], "dependencies": { "strnum": "^1.0.5" }, "bin": { "fxparser": "src/cli/cli.js" - }, - "funding": { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" } }, "node_modules/fastparse": { @@ -10558,7 +10635,7 @@ "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, "engines": { "node": ">=4" @@ -10835,6 +10912,7 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -11127,6 +11205,11 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" + }, "node_modules/ip": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", @@ -11588,9 +11671,9 @@ } }, "node_modules/istanbul-lib-instrument": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz", - "integrity": "sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, "dependencies": { "@babel/core": "^7.12.3", @@ -11946,11 +12029,6 @@ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, - "node_modules/lodash.assign": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", - "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=" - }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -12781,9 +12859,9 @@ } }, "node_modules/ngx-markdown-editor": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ngx-markdown-editor/-/ngx-markdown-editor-5.3.0.tgz", - "integrity": "sha512-IeGqtvq+jDMjGGnFFdHhEPphx+jM27EdvcJ3IboCQlXzolpe10JUdCCe53T6TgFe2MFvegVlaVuo44Dcve5IZw==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ngx-markdown-editor/-/ngx-markdown-editor-5.3.1.tgz", + "integrity": "sha512-RXOq+GSc9oBGaBBU4LbAqo3wcIWGXpGD3y8/G4mqbKEM0ANZAaj6EvPPkhaUffNN6KBwStYg0uglxLA3dKuuQg==", "dependencies": { "tslib": "^2.3.0" }, @@ -14484,11 +14562,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rw": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" - }, "node_modules/rxjs": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", @@ -14534,7 +14607,8 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true }, "node_modules/safevalues": { "version": "0.3.4", @@ -15339,6 +15413,14 @@ "node": ">=0.10" } }, + "node_modules/taira": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/taira/-/taira-3.2.2.tgz", + "integrity": "sha512-Uu06/5JrIRdokVSnbjCVwtmTXYMMNGwRYvWEYZhkB1WGcOinL5Ikr01X6MrUTqA/BU0dGWoIE1l5Ghr1Jq2QYA==", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -16516,12 +16598,12 @@ } }, "@angular-devkit/architect": { - "version": "0.1502.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1502.6.tgz", - "integrity": "sha512-n4oJ9vzFWwabf+AfgqqevVzdJhNKNCav7ytefjD/Y01vkNwlXqWnHcvyyHCLkVibJ6WR8J9lK4t77j/HFlDvWQ==", + "version": "0.1502.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1502.8.tgz", + "integrity": "sha512-rTltw2ABHrcKc8EGimALvXmrDTP5hlNbEy6nYolJoXEI9EwHgriWrVLVPs3OEF+/ed47dbJi9EGOXUOgzgpB5A==", "dev": true, "requires": { - "@angular-devkit/core": "15.2.6", + "@angular-devkit/core": "15.2.8", "rxjs": "6.6.7" }, "dependencies": { @@ -16543,15 +16625,15 @@ } }, "@angular-devkit/build-angular": { - "version": "15.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-15.2.6.tgz", - "integrity": "sha512-OmMcdXXUrAdZNxwxDE8SUx1FMcq9FyMnrSv1PmP9sHPBoxAdBVc/qNdGA9V7C5yHvWHGgzsx7ZK5TDuvifzS5g==", + "version": "15.2.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-15.2.8.tgz", + "integrity": "sha512-TGDnXhhOG6h6TOrWWzfnkha7wYBOXi7iJc1o1w1VKCayE3T6TZZdF847aK66vL9KG7AKYVdGhWEGw2WBHUBUpg==", "dev": true, "requires": { "@ampproject/remapping": "2.2.0", - "@angular-devkit/architect": "0.1502.6", - "@angular-devkit/build-webpack": "0.1502.6", - "@angular-devkit/core": "15.2.6", + "@angular-devkit/architect": "0.1502.8", + "@angular-devkit/build-webpack": "0.1502.8", + "@angular-devkit/core": "15.2.8", "@babel/core": "7.20.12", "@babel/generator": "7.20.14", "@babel/helper-annotate-as-pure": "7.18.6", @@ -16563,7 +16645,7 @@ "@babel/runtime": "7.20.13", "@babel/template": "7.20.7", "@discoveryjs/json-ext": "0.5.7", - "@ngtools/webpack": "15.2.6", + "@ngtools/webpack": "15.2.8", "ansi-colors": "4.1.3", "autoprefixer": "10.4.13", "babel-loader": "9.1.2", @@ -16693,12 +16775,12 @@ } }, "@angular-devkit/build-webpack": { - "version": "0.1502.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1502.6.tgz", - "integrity": "sha512-X7XQ11QDz2Bs5qpJ3a5glIytvI+S74ORQxdzvT6a6KB8ayW0SgZEhTwD+GF7pa5My8draIaXBGzzQR1qmpWK5Q==", + "version": "0.1502.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1502.8.tgz", + "integrity": "sha512-jWtNv+S03FFLDe/C8SPCcRvkz3bSb2R+919IT086Q9axIPQ1VowOEwzt2k3qXPSSrC7GSYuASM+X92dB47NTQQ==", "dev": true, "requires": { - "@angular-devkit/architect": "0.1502.6", + "@angular-devkit/architect": "0.1502.8", "rxjs": "6.6.7" }, "dependencies": { @@ -16720,9 +16802,9 @@ } }, "@angular-devkit/core": { - "version": "15.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-15.2.6.tgz", - "integrity": "sha512-YVTWZ+M+xNKdFX4EnY9QX49PZraawiaA0iTd2CUW8ZoTUvU7yOGMKZLSdz6aokTMRVfm0449wt6YL994ibOo1g==", + "version": "15.2.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-15.2.8.tgz", + "integrity": "sha512-Lo4XrbDMtXarKnMrFgWLmQdSX+3QPNAg4otG8cmp/U4jJyjV4dAYKEAsb1sCNGUSM4h4v09EQU/5ugVjDU29lQ==", "dev": true, "requires": { "ajv": "8.12.0", @@ -16750,12 +16832,12 @@ } }, "@angular-devkit/schematics": { - "version": "15.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-15.2.6.tgz", - "integrity": "sha512-f7VgnAcok7AwR/DhX0ZWskB0rFBo/KsvtIUA2qZSrpKMf8eFiwu03dv/b2mI0vnf+1FBfIQzJvO0ww45zRp6dA==", + "version": "15.2.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-15.2.8.tgz", + "integrity": "sha512-w6EUGC96kVsH9f8sEzajzbONMawezyVBiSo+JYp5r25rQArAz/a+KZntbuETWHQ0rQOEsKmUNKxwmr11BaptSQ==", "dev": true, "requires": { - "@angular-devkit/core": "15.2.6", + "@angular-devkit/core": "15.2.8", "jsonc-parser": "3.2.0", "magic-string": "0.29.0", "ora": "5.4.1", @@ -17130,15 +17212,15 @@ } }, "@angular/cli": { - "version": "15.2.6", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-15.2.6.tgz", - "integrity": "sha512-wNkQ/qCVbd4pERaGVagKJPifEvjRNY5otwsd4iRVubY/XOcIHcYChUThZwgQdVfNAImfJPMZNrhbGxejuWLA9w==", + "version": "15.2.8", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-15.2.8.tgz", + "integrity": "sha512-3VlTfm6DUZfFHBY43vQSAaqmFTxy3VtRd/iDBCHcEPhHwYLWBvNwReJuJfNja8O105QQ6DBiYVBExEBtPmjQ4w==", "dev": true, "requires": { - "@angular-devkit/architect": "0.1502.6", - "@angular-devkit/core": "15.2.6", - "@angular-devkit/schematics": "15.2.6", - "@schematics/angular": "15.2.6", + "@angular-devkit/architect": "0.1502.8", + "@angular-devkit/core": "15.2.8", + "@angular-devkit/schematics": "15.2.8", + "@schematics/angular": "15.2.8", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.3", "ini": "3.0.1", @@ -17172,9 +17254,9 @@ } }, "@angular/compiler-cli": { - "version": "15.2.8", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-15.2.8.tgz", - "integrity": "sha512-fFxaDlbILo0t2t662qA0cjgn+kWItGlc1tFYKU6X7bvYb3t2e0cd9FzrFPLXUQVboGis83ULcJ2zkDxScnuPuQ==", + "version": "15.2.9", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-15.2.9.tgz", + "integrity": "sha512-zsbI8G2xHOeYWI0hjFzrI//ZhZV9il/uQW5dAimfwJp06KZDeXZ3PdwY9JQslf6F+saLwOObxy6QMrIVvfjy9w==", "dev": true, "requires": { "@babel/core": "7.19.3", @@ -17476,11 +17558,11 @@ } }, "@aws-sdk/abort-controller": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.310.0.tgz", - "integrity": "sha512-v1zrRQxDLA1MdPim159Vx/CPHqsB4uybSxRi1CnfHO5ZjHryx3a5htW2gdGAykVCul40+yJXvfpufMrELVxH+g==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.357.0.tgz", + "integrity": "sha512-nQYDJon87quPwt2JZJwUN2GFKJnvE5kWb6tZP4xb5biSGUKBqDQo06oYed7yokatCuCMouIXV462aN0fWODtOw==", "requires": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, @@ -17493,386 +17575,389 @@ } }, "@aws-sdk/client-s3": { - "version": "3.319.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.319.0.tgz", - "integrity": "sha512-/XzElEO4iZTBgvrcWq20sxKLvhRetjT1gOPRF4Ra2iSCbeVIT/feYdEaSSgMsaiqrREywBc+59NiOyxImWTaOA==", + "version": "3.360.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.360.0.tgz", + "integrity": "sha512-6URI0ZWk5ar0atJ8xTxD2u/oLWwBlosLTyqNpsMe7DKvZQ5DgUfLw3BHeC2d4FQID1I74rkGCdHLtRe4MOiIfA==", "requires": { "@aws-crypto/sha1-browser": "3.0.0", "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.319.0", - "@aws-sdk/config-resolver": "3.310.0", - "@aws-sdk/credential-provider-node": "3.319.0", - "@aws-sdk/eventstream-serde-browser": "3.310.0", - "@aws-sdk/eventstream-serde-config-resolver": "3.310.0", - "@aws-sdk/eventstream-serde-node": "3.310.0", - "@aws-sdk/fetch-http-handler": "3.310.0", - "@aws-sdk/hash-blob-browser": "3.310.0", - "@aws-sdk/hash-node": "3.310.0", - "@aws-sdk/hash-stream-node": "3.310.0", - "@aws-sdk/invalid-dependency": "3.310.0", - "@aws-sdk/md5-js": "3.310.0", - "@aws-sdk/middleware-bucket-endpoint": "3.310.0", - "@aws-sdk/middleware-content-length": "3.310.0", - "@aws-sdk/middleware-endpoint": "3.310.0", - "@aws-sdk/middleware-expect-continue": "3.310.0", - "@aws-sdk/middleware-flexible-checksums": "3.310.0", - "@aws-sdk/middleware-host-header": "3.310.0", - "@aws-sdk/middleware-location-constraint": "3.310.0", - "@aws-sdk/middleware-logger": "3.310.0", - "@aws-sdk/middleware-recursion-detection": "3.310.0", - "@aws-sdk/middleware-retry": "3.310.0", - "@aws-sdk/middleware-sdk-s3": "3.310.0", - "@aws-sdk/middleware-serde": "3.310.0", - "@aws-sdk/middleware-signing": "3.310.0", - "@aws-sdk/middleware-ssec": "3.310.0", - "@aws-sdk/middleware-stack": "3.310.0", - "@aws-sdk/middleware-user-agent": "3.319.0", - "@aws-sdk/node-config-provider": "3.310.0", - "@aws-sdk/node-http-handler": "3.310.0", - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/signature-v4-multi-region": "3.310.0", - "@aws-sdk/smithy-client": "3.316.0", - "@aws-sdk/types": "3.310.0", - "@aws-sdk/url-parser": "3.310.0", + "@aws-sdk/client-sts": "3.360.0", + "@aws-sdk/config-resolver": "3.357.0", + "@aws-sdk/credential-provider-node": "3.360.0", + "@aws-sdk/eventstream-serde-browser": "3.357.0", + "@aws-sdk/eventstream-serde-config-resolver": "3.357.0", + "@aws-sdk/eventstream-serde-node": "3.357.0", + "@aws-sdk/fetch-http-handler": "3.357.0", + "@aws-sdk/hash-blob-browser": "3.357.0", + "@aws-sdk/hash-node": "3.357.0", + "@aws-sdk/hash-stream-node": "3.357.0", + "@aws-sdk/invalid-dependency": "3.357.0", + "@aws-sdk/md5-js": "3.357.0", + "@aws-sdk/middleware-bucket-endpoint": "3.357.0", + "@aws-sdk/middleware-content-length": "3.357.0", + "@aws-sdk/middleware-endpoint": "3.357.0", + "@aws-sdk/middleware-expect-continue": "3.357.0", + "@aws-sdk/middleware-flexible-checksums": "3.357.0", + "@aws-sdk/middleware-host-header": "3.357.0", + "@aws-sdk/middleware-location-constraint": "3.357.0", + "@aws-sdk/middleware-logger": "3.357.0", + "@aws-sdk/middleware-recursion-detection": "3.357.0", + "@aws-sdk/middleware-retry": "3.357.0", + "@aws-sdk/middleware-sdk-s3": "3.357.0", + "@aws-sdk/middleware-serde": "3.357.0", + "@aws-sdk/middleware-signing": "3.357.0", + "@aws-sdk/middleware-ssec": "3.357.0", + "@aws-sdk/middleware-stack": "3.357.0", + "@aws-sdk/middleware-user-agent": "3.357.0", + "@aws-sdk/node-config-provider": "3.357.0", + "@aws-sdk/node-http-handler": "3.360.0", + "@aws-sdk/signature-v4-multi-region": "3.357.0", + "@aws-sdk/smithy-client": "3.360.0", + "@aws-sdk/types": "3.357.0", + "@aws-sdk/url-parser": "3.357.0", "@aws-sdk/util-base64": "3.310.0", "@aws-sdk/util-body-length-browser": "3.310.0", "@aws-sdk/util-body-length-node": "3.310.0", - "@aws-sdk/util-defaults-mode-browser": "3.316.0", - "@aws-sdk/util-defaults-mode-node": "3.316.0", - "@aws-sdk/util-endpoints": "3.319.0", - "@aws-sdk/util-retry": "3.310.0", - "@aws-sdk/util-stream-browser": "3.310.0", - "@aws-sdk/util-stream-node": "3.310.0", - "@aws-sdk/util-user-agent-browser": "3.310.0", - "@aws-sdk/util-user-agent-node": "3.310.0", + "@aws-sdk/util-defaults-mode-browser": "3.360.0", + "@aws-sdk/util-defaults-mode-node": "3.360.0", + "@aws-sdk/util-endpoints": "3.357.0", + "@aws-sdk/util-retry": "3.357.0", + "@aws-sdk/util-stream": "3.360.0", + "@aws-sdk/util-user-agent-browser": "3.357.0", + "@aws-sdk/util-user-agent-node": "3.357.0", "@aws-sdk/util-utf8": "3.310.0", - "@aws-sdk/util-waiter": "3.310.0", + "@aws-sdk/util-waiter": "3.357.0", "@aws-sdk/xml-builder": "3.310.0", - "fast-xml-parser": "4.1.2", + "@smithy/protocol-http": "^1.0.1", + "@smithy/types": "^1.0.0", + "fast-xml-parser": "4.2.5", "tslib": "^2.5.0" } }, "@aws-sdk/client-sso": { - "version": "3.319.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.319.0.tgz", - "integrity": "sha512-g46KgAjRiYBS8Oi85DPwSAQpt+Hgmw/YFgGVwZqMfTL70KNJwLFKRa5D9UocQd7t7OjPRdKF7g0Gp5peyAK9dw==", + "version": "3.360.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.360.0.tgz", + "integrity": "sha512-0f6eG+6XFbDxrma5xxNGg/FJxh/OHC6h8AkfNms9Lv1gBoQSagpcTor+ax0z9F6lypOjaelX6k4DpeKAp4PZeA==", "requires": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.310.0", - "@aws-sdk/fetch-http-handler": "3.310.0", - "@aws-sdk/hash-node": "3.310.0", - "@aws-sdk/invalid-dependency": "3.310.0", - "@aws-sdk/middleware-content-length": "3.310.0", - "@aws-sdk/middleware-endpoint": "3.310.0", - "@aws-sdk/middleware-host-header": "3.310.0", - "@aws-sdk/middleware-logger": "3.310.0", - "@aws-sdk/middleware-recursion-detection": "3.310.0", - "@aws-sdk/middleware-retry": "3.310.0", - "@aws-sdk/middleware-serde": "3.310.0", - "@aws-sdk/middleware-stack": "3.310.0", - "@aws-sdk/middleware-user-agent": "3.319.0", - "@aws-sdk/node-config-provider": "3.310.0", - "@aws-sdk/node-http-handler": "3.310.0", - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/smithy-client": "3.316.0", - "@aws-sdk/types": "3.310.0", - "@aws-sdk/url-parser": "3.310.0", + "@aws-sdk/config-resolver": "3.357.0", + "@aws-sdk/fetch-http-handler": "3.357.0", + "@aws-sdk/hash-node": "3.357.0", + "@aws-sdk/invalid-dependency": "3.357.0", + "@aws-sdk/middleware-content-length": "3.357.0", + "@aws-sdk/middleware-endpoint": "3.357.0", + "@aws-sdk/middleware-host-header": "3.357.0", + "@aws-sdk/middleware-logger": "3.357.0", + "@aws-sdk/middleware-recursion-detection": "3.357.0", + "@aws-sdk/middleware-retry": "3.357.0", + "@aws-sdk/middleware-serde": "3.357.0", + "@aws-sdk/middleware-stack": "3.357.0", + "@aws-sdk/middleware-user-agent": "3.357.0", + "@aws-sdk/node-config-provider": "3.357.0", + "@aws-sdk/node-http-handler": "3.360.0", + "@aws-sdk/smithy-client": "3.360.0", + "@aws-sdk/types": "3.357.0", + "@aws-sdk/url-parser": "3.357.0", "@aws-sdk/util-base64": "3.310.0", "@aws-sdk/util-body-length-browser": "3.310.0", "@aws-sdk/util-body-length-node": "3.310.0", - "@aws-sdk/util-defaults-mode-browser": "3.316.0", - "@aws-sdk/util-defaults-mode-node": "3.316.0", - "@aws-sdk/util-endpoints": "3.319.0", - "@aws-sdk/util-retry": "3.310.0", - "@aws-sdk/util-user-agent-browser": "3.310.0", - "@aws-sdk/util-user-agent-node": "3.310.0", + "@aws-sdk/util-defaults-mode-browser": "3.360.0", + "@aws-sdk/util-defaults-mode-node": "3.360.0", + "@aws-sdk/util-endpoints": "3.357.0", + "@aws-sdk/util-retry": "3.357.0", + "@aws-sdk/util-user-agent-browser": "3.357.0", + "@aws-sdk/util-user-agent-node": "3.357.0", "@aws-sdk/util-utf8": "3.310.0", + "@smithy/protocol-http": "^1.0.1", + "@smithy/types": "^1.0.0", "tslib": "^2.5.0" } }, "@aws-sdk/client-sso-oidc": { - "version": "3.319.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.319.0.tgz", - "integrity": "sha512-GJBgT/tephRZY3oTbDBMv+G9taoqKUIvGPn+7shmzz2P1SerutsRSfKfDXV+VptPNRoGmjjCLPmWjMFYbFKILQ==", + "version": "3.360.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.360.0.tgz", + "integrity": "sha512-czIpPt75fS3gH3vgFz76+WTaKcvPxC/DnPuqVyHdihMmP0UuwGPU9jn+Xx9RdUw7Yay3+rJRe3AYgBn4Xb220g==", "requires": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.310.0", - "@aws-sdk/fetch-http-handler": "3.310.0", - "@aws-sdk/hash-node": "3.310.0", - "@aws-sdk/invalid-dependency": "3.310.0", - "@aws-sdk/middleware-content-length": "3.310.0", - "@aws-sdk/middleware-endpoint": "3.310.0", - "@aws-sdk/middleware-host-header": "3.310.0", - "@aws-sdk/middleware-logger": "3.310.0", - "@aws-sdk/middleware-recursion-detection": "3.310.0", - "@aws-sdk/middleware-retry": "3.310.0", - "@aws-sdk/middleware-serde": "3.310.0", - "@aws-sdk/middleware-stack": "3.310.0", - "@aws-sdk/middleware-user-agent": "3.319.0", - "@aws-sdk/node-config-provider": "3.310.0", - "@aws-sdk/node-http-handler": "3.310.0", - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/smithy-client": "3.316.0", - "@aws-sdk/types": "3.310.0", - "@aws-sdk/url-parser": "3.310.0", + "@aws-sdk/config-resolver": "3.357.0", + "@aws-sdk/fetch-http-handler": "3.357.0", + "@aws-sdk/hash-node": "3.357.0", + "@aws-sdk/invalid-dependency": "3.357.0", + "@aws-sdk/middleware-content-length": "3.357.0", + "@aws-sdk/middleware-endpoint": "3.357.0", + "@aws-sdk/middleware-host-header": "3.357.0", + "@aws-sdk/middleware-logger": "3.357.0", + "@aws-sdk/middleware-recursion-detection": "3.357.0", + "@aws-sdk/middleware-retry": "3.357.0", + "@aws-sdk/middleware-serde": "3.357.0", + "@aws-sdk/middleware-stack": "3.357.0", + "@aws-sdk/middleware-user-agent": "3.357.0", + "@aws-sdk/node-config-provider": "3.357.0", + "@aws-sdk/node-http-handler": "3.360.0", + "@aws-sdk/smithy-client": "3.360.0", + "@aws-sdk/types": "3.357.0", + "@aws-sdk/url-parser": "3.357.0", "@aws-sdk/util-base64": "3.310.0", "@aws-sdk/util-body-length-browser": "3.310.0", "@aws-sdk/util-body-length-node": "3.310.0", - "@aws-sdk/util-defaults-mode-browser": "3.316.0", - "@aws-sdk/util-defaults-mode-node": "3.316.0", - "@aws-sdk/util-endpoints": "3.319.0", - "@aws-sdk/util-retry": "3.310.0", - "@aws-sdk/util-user-agent-browser": "3.310.0", - "@aws-sdk/util-user-agent-node": "3.310.0", + "@aws-sdk/util-defaults-mode-browser": "3.360.0", + "@aws-sdk/util-defaults-mode-node": "3.360.0", + "@aws-sdk/util-endpoints": "3.357.0", + "@aws-sdk/util-retry": "3.357.0", + "@aws-sdk/util-user-agent-browser": "3.357.0", + "@aws-sdk/util-user-agent-node": "3.357.0", "@aws-sdk/util-utf8": "3.310.0", + "@smithy/protocol-http": "^1.0.1", + "@smithy/types": "^1.0.0", "tslib": "^2.5.0" } }, "@aws-sdk/client-sts": { - "version": "3.319.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.319.0.tgz", - "integrity": "sha512-PRGGKCSKtyM3x629J9j4DMsH1cQT8UGW+R67u9Q5HrMK05gfjpmg+X1DQ3pgve4D8MI4R/Cm3NkYl2eUTbQHQg==", + "version": "3.360.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.360.0.tgz", + "integrity": "sha512-ORRwSdwlSYGHfhQCXKtr1eJeTjI14l5IZRJbRDgXs46y4/GQj/rt/2Q6WGjVMfM1ZRRiEII2/vK7mU7IJcWkFw==", "requires": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.310.0", - "@aws-sdk/credential-provider-node": "3.319.0", - "@aws-sdk/fetch-http-handler": "3.310.0", - "@aws-sdk/hash-node": "3.310.0", - "@aws-sdk/invalid-dependency": "3.310.0", - "@aws-sdk/middleware-content-length": "3.310.0", - "@aws-sdk/middleware-endpoint": "3.310.0", - "@aws-sdk/middleware-host-header": "3.310.0", - "@aws-sdk/middleware-logger": "3.310.0", - "@aws-sdk/middleware-recursion-detection": "3.310.0", - "@aws-sdk/middleware-retry": "3.310.0", - "@aws-sdk/middleware-sdk-sts": "3.310.0", - "@aws-sdk/middleware-serde": "3.310.0", - "@aws-sdk/middleware-signing": "3.310.0", - "@aws-sdk/middleware-stack": "3.310.0", - "@aws-sdk/middleware-user-agent": "3.319.0", - "@aws-sdk/node-config-provider": "3.310.0", - "@aws-sdk/node-http-handler": "3.310.0", - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/smithy-client": "3.316.0", - "@aws-sdk/types": "3.310.0", - "@aws-sdk/url-parser": "3.310.0", + "@aws-sdk/config-resolver": "3.357.0", + "@aws-sdk/credential-provider-node": "3.360.0", + "@aws-sdk/fetch-http-handler": "3.357.0", + "@aws-sdk/hash-node": "3.357.0", + "@aws-sdk/invalid-dependency": "3.357.0", + "@aws-sdk/middleware-content-length": "3.357.0", + "@aws-sdk/middleware-endpoint": "3.357.0", + "@aws-sdk/middleware-host-header": "3.357.0", + "@aws-sdk/middleware-logger": "3.357.0", + "@aws-sdk/middleware-recursion-detection": "3.357.0", + "@aws-sdk/middleware-retry": "3.357.0", + "@aws-sdk/middleware-sdk-sts": "3.357.0", + "@aws-sdk/middleware-serde": "3.357.0", + "@aws-sdk/middleware-signing": "3.357.0", + "@aws-sdk/middleware-stack": "3.357.0", + "@aws-sdk/middleware-user-agent": "3.357.0", + "@aws-sdk/node-config-provider": "3.357.0", + "@aws-sdk/node-http-handler": "3.360.0", + "@aws-sdk/smithy-client": "3.360.0", + "@aws-sdk/types": "3.357.0", + "@aws-sdk/url-parser": "3.357.0", "@aws-sdk/util-base64": "3.310.0", "@aws-sdk/util-body-length-browser": "3.310.0", "@aws-sdk/util-body-length-node": "3.310.0", - "@aws-sdk/util-defaults-mode-browser": "3.316.0", - "@aws-sdk/util-defaults-mode-node": "3.316.0", - "@aws-sdk/util-endpoints": "3.319.0", - "@aws-sdk/util-retry": "3.310.0", - "@aws-sdk/util-user-agent-browser": "3.310.0", - "@aws-sdk/util-user-agent-node": "3.310.0", + "@aws-sdk/util-defaults-mode-browser": "3.360.0", + "@aws-sdk/util-defaults-mode-node": "3.360.0", + "@aws-sdk/util-endpoints": "3.357.0", + "@aws-sdk/util-retry": "3.357.0", + "@aws-sdk/util-user-agent-browser": "3.357.0", + "@aws-sdk/util-user-agent-node": "3.357.0", "@aws-sdk/util-utf8": "3.310.0", - "fast-xml-parser": "4.1.2", + "@smithy/protocol-http": "^1.0.1", + "@smithy/types": "^1.0.0", + "fast-xml-parser": "4.2.5", "tslib": "^2.5.0" } }, "@aws-sdk/config-resolver": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.310.0.tgz", - "integrity": "sha512-8vsT+/50lOqfDxka9m/rRt6oxv1WuGZoP8oPMk0Dt+TxXMbAzf4+rejBgiB96wshI1k3gLokYRjSQZn+dDtT8g==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.357.0.tgz", + "integrity": "sha512-cukfg0nX7Tzx/xFyH5F4Eyb8DA1ITCGtSQv4vnEjgUop+bkzckuGLKEeBcBhyZY+aw+2C9CVwIHwIMhRm0ul5w==", "requires": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "@aws-sdk/util-config-provider": "3.310.0", - "@aws-sdk/util-middleware": "3.310.0", + "@aws-sdk/util-middleware": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/credential-provider-env": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.310.0.tgz", - "integrity": "sha512-vvIPQpI16fj95xwS7M3D48F7QhZJBnnCgB5lR+b7So+vsG9ibm1mZRVGzVpdxCvgyOhHFbvrby9aalNJmmIP1A==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.357.0.tgz", + "integrity": "sha512-UOecwfqvXgJVqhfWSZ2S44v2Nq2oceW0PQVQp0JAa9opc2rxSVIfyOhPr0yMoPmpyNcP22rgeg6ce70KULYwiA==", "requires": { - "@aws-sdk/property-provider": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/property-provider": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/credential-provider-imds": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.310.0.tgz", - "integrity": "sha512-baxK7Zp6dai5AGW01FIW27xS2KAaPUmKLIXv5SvFYsUgXXvNW55im4uG3b+2gA0F7V+hXvVBH08OEqmwW6we5w==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.357.0.tgz", + "integrity": "sha512-upw/bfsl7/WydT6gM0lBuR4Ipp4fzYm/E3ObFr0Mg5OkgVPt5ZJE+eeFTvwCpDdBSTKs4JfrK6/iEK8A23Q1jQ==", "requires": { - "@aws-sdk/node-config-provider": "3.310.0", - "@aws-sdk/property-provider": "3.310.0", - "@aws-sdk/types": "3.310.0", - "@aws-sdk/url-parser": "3.310.0", + "@aws-sdk/node-config-provider": "3.357.0", + "@aws-sdk/property-provider": "3.357.0", + "@aws-sdk/types": "3.357.0", + "@aws-sdk/url-parser": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/credential-provider-ini": { - "version": "3.319.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.319.0.tgz", - "integrity": "sha512-pzx388Fw1KlSgmIMUyRY8DJVYM3aXpwzjprD4RiQVPJeAI+t7oQmEvd2FiUZEuHDjWXcuonxgU+dk7i7HUk/HQ==", + "version": "3.360.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.360.0.tgz", + "integrity": "sha512-pWuLTq+yjSFssPGhDJ8oxvZsu7/F1KissGRt65G4qrfxHhoiMRcLF1GtFJueDQpitZ1i3mZXHVn/OSv4LPQ1Lw==", "requires": { - "@aws-sdk/credential-provider-env": "3.310.0", - "@aws-sdk/credential-provider-imds": "3.310.0", - "@aws-sdk/credential-provider-process": "3.310.0", - "@aws-sdk/credential-provider-sso": "3.319.0", - "@aws-sdk/credential-provider-web-identity": "3.310.0", - "@aws-sdk/property-provider": "3.310.0", - "@aws-sdk/shared-ini-file-loader": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/credential-provider-env": "3.357.0", + "@aws-sdk/credential-provider-imds": "3.357.0", + "@aws-sdk/credential-provider-process": "3.357.0", + "@aws-sdk/credential-provider-sso": "3.360.0", + "@aws-sdk/credential-provider-web-identity": "3.357.0", + "@aws-sdk/property-provider": "3.357.0", + "@aws-sdk/shared-ini-file-loader": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/credential-provider-node": { - "version": "3.319.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.319.0.tgz", - "integrity": "sha512-DS4a0Rdd7ZtMshoeE+zuSgbC05YBcdzd0h89u/eX+1Yqx+HCjeb8WXkbXsz0Mwx8q9TE04aS8f6Bw9J4x4mO5g==", + "version": "3.360.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.360.0.tgz", + "integrity": "sha512-j4Lu5vXkdzz/L6fGKKxnL0vcwAGHlwFHjTg9nRagMn1lvaVjtktXeM30duHTBQq9i+ejdFxpVNWYrmHGaWPNdg==", "requires": { - "@aws-sdk/credential-provider-env": "3.310.0", - "@aws-sdk/credential-provider-imds": "3.310.0", - "@aws-sdk/credential-provider-ini": "3.319.0", - "@aws-sdk/credential-provider-process": "3.310.0", - "@aws-sdk/credential-provider-sso": "3.319.0", - "@aws-sdk/credential-provider-web-identity": "3.310.0", - "@aws-sdk/property-provider": "3.310.0", - "@aws-sdk/shared-ini-file-loader": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/credential-provider-env": "3.357.0", + "@aws-sdk/credential-provider-imds": "3.357.0", + "@aws-sdk/credential-provider-ini": "3.360.0", + "@aws-sdk/credential-provider-process": "3.357.0", + "@aws-sdk/credential-provider-sso": "3.360.0", + "@aws-sdk/credential-provider-web-identity": "3.357.0", + "@aws-sdk/property-provider": "3.357.0", + "@aws-sdk/shared-ini-file-loader": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/credential-provider-process": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.310.0.tgz", - "integrity": "sha512-h73sg6GPMUWC+3zMCbA1nZ2O03nNJt7G96JdmnantiXBwHpRKWW8nBTLzx5uhXn6hTuTaoQRP/P+oxQJKYdMmA==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.357.0.tgz", + "integrity": "sha512-qFWWilFPsc2hR7O0KIhwcE78w+pVIK+uQR6MQMfdRyxUndgiuCorJwVjedc3yZtmnoELHF34j+m8whTBXv9E7Q==", "requires": { - "@aws-sdk/property-provider": "3.310.0", - "@aws-sdk/shared-ini-file-loader": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/property-provider": "3.357.0", + "@aws-sdk/shared-ini-file-loader": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/credential-provider-sso": { - "version": "3.319.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.319.0.tgz", - "integrity": "sha512-gAUnWH41lxkIbANXu+Rz5zS0Iavjjmpf3C56vAMT7oaYZ3Cg/Ys5l2SwAucQGOCA2DdS2hDiSI8E+Yhr4F5toA==", + "version": "3.360.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.360.0.tgz", + "integrity": "sha512-kW0FR8AbMQrJxADxIqYSjHVN2RXwHmA5DzogYm1AjOkYRMN9JHDVOMQP2K2M6FCynZqTYsKW5lzjPOjS0fu8Dw==", "requires": { - "@aws-sdk/client-sso": "3.319.0", - "@aws-sdk/property-provider": "3.310.0", - "@aws-sdk/shared-ini-file-loader": "3.310.0", - "@aws-sdk/token-providers": "3.319.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/client-sso": "3.360.0", + "@aws-sdk/property-provider": "3.357.0", + "@aws-sdk/shared-ini-file-loader": "3.357.0", + "@aws-sdk/token-providers": "3.360.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/credential-provider-web-identity": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.310.0.tgz", - "integrity": "sha512-H4SzuZXILNhK6/IR1uVvsUDZvzc051hem7GLyYghBCu8mU+tq28YhKE8MfSroi6eL2e5Vujloij1OM2EQQkPkw==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.357.0.tgz", + "integrity": "sha512-0KRRAFrXy5HJe2vqnCWCoCS+fQw7IoIj3KQsuURJMW4F+ifisxCgEsh3brJ2LQlN4ElWTRJhlrDHNZ/pd61D4w==", "requires": { - "@aws-sdk/property-provider": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/property-provider": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/eventstream-codec": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-codec/-/eventstream-codec-3.310.0.tgz", - "integrity": "sha512-clIeSgWbZbxwtsxZ/yoedNM0/kJFSIjjHPikuDGhxhqc+vP6TN3oYyVMFrYwFaTFhk2+S5wZcWYMw8Op1pWo+A==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-codec/-/eventstream-codec-3.357.0.tgz", + "integrity": "sha512-bqenTHG6GH6aCk/Il+ooWXVVAZuc8lOgVEy9bE2hI49oVqT8zSuXxQB+w1WWyZoAOPcelsjayB1wfPub8VDBxQ==", "requires": { "@aws-crypto/crc32": "3.0.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "@aws-sdk/util-hex-encoding": "3.310.0", "tslib": "^2.5.0" } }, "@aws-sdk/eventstream-serde-browser": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-serde-browser/-/eventstream-serde-browser-3.310.0.tgz", - "integrity": "sha512-3S6ziuQVALgEyz0TANGtYDVeG8ArK4Y05mcgrs8qUTmsvlDIXX37cR/DvmVbNB76M4IrsZeSAIajL9644CywkA==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-serde-browser/-/eventstream-serde-browser-3.357.0.tgz", + "integrity": "sha512-hBabtmwuspVHGSKnUccDiSIbg+IVoBThx6wYt6i4edbWAITHF3ADVKXy7icV400CAyG0XTZgxjE6FKpiDxj9rQ==", "requires": { - "@aws-sdk/eventstream-serde-universal": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/eventstream-serde-universal": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/eventstream-serde-config-resolver": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.310.0.tgz", - "integrity": "sha512-8s1Qdn9STj+sV75nUp9yt0W6fHS4BZ2jTm4Z/1Pcbvh2Gqs0WjH5n2StS+pDW5Y9J/HSGBl0ogmUr5lC5bXFHg==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.357.0.tgz", + "integrity": "sha512-E6rwk+1KFXhKmJ+v7JW5Uyyda1yN5XRVupCnCrtFsHFmhVGQxFacoUZIee3bfuCpC58dLSyESggxGpUd3XOSsw==", "requires": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/eventstream-serde-node": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-serde-node/-/eventstream-serde-node-3.310.0.tgz", - "integrity": "sha512-kSnRomCgW43K9TmQYuwN9+AoYPnhyOKroanUMyZEzJk7rpCPMj4OzaUpXfDYOvznFNYn7NLaH6nHLJAr0VPlJA==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-serde-node/-/eventstream-serde-node-3.357.0.tgz", + "integrity": "sha512-boXDy+JWcPfHc9OIKV6I4Bh2XrLcg+eac+/LldNZFcDIB33/gHIM2CJw8u565Iebdz1NKEkP/QPPZbk2y+abPA==", "requires": { - "@aws-sdk/eventstream-serde-universal": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/eventstream-serde-universal": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/eventstream-serde-universal": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-serde-universal/-/eventstream-serde-universal-3.310.0.tgz", - "integrity": "sha512-Qyjt5k/waV5cDukpgT824ISZAz5U0pwzLz5ztR409u85AGNkF/9n7MS+LSyBUBSb0WJ5pUeSD47WBk+nLq9Nhw==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-serde-universal/-/eventstream-serde-universal-3.357.0.tgz", + "integrity": "sha512-9/Wcdxx38XQAturqOAGYNCaLOzFVnW+xwxd4af9eNOfZfZ5PP5PRKBIpvKDsN26e3l4f3GodHx7MS1WB7BBc2w==", "requires": { - "@aws-sdk/eventstream-codec": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/eventstream-codec": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/fetch-http-handler": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.310.0.tgz", - "integrity": "sha512-Bi9vIwzdkw1zMcvi/zGzlWS9KfIEnAq4NNhsnCxbQ4OoIRU9wvU+WGZdBBhxg0ZxZmpp1j1aZhU53lLjA07MHw==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.357.0.tgz", + "integrity": "sha512-5sPloTO8y8fAnS/6/Sfp/aVoL9zuhzkLdWBORNzMazdynVNEzWKWCPZ27RQpgkaCDHiXjqUY4kfuFXAGkvFfDQ==", "requires": { - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/querystring-builder": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/protocol-http": "3.357.0", + "@aws-sdk/querystring-builder": "3.357.0", + "@aws-sdk/types": "3.357.0", "@aws-sdk/util-base64": "3.310.0", "tslib": "^2.5.0" } }, "@aws-sdk/hash-blob-browser": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/hash-blob-browser/-/hash-blob-browser-3.310.0.tgz", - "integrity": "sha512-OoR8p0cbypToysLT0v3o2oyjy6+DKrY7GNCAzHOHJK9xmqXCt+DsjKoPeiY7o1sWX2aN6Plmvubj/zWxMKEn/A==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/hash-blob-browser/-/hash-blob-browser-3.357.0.tgz", + "integrity": "sha512-RDd6UgrGHDmleTnXM9LRSSVa69euSAG2mlNhZMEDWk3OFseXVYqBDaqroVbQ01rM2UAe8MeBFchlV9OmxuVgvw==", "requires": { "@aws-sdk/chunked-blob-reader": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/hash-node": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.310.0.tgz", - "integrity": "sha512-NvE2fhRc8GRwCXBfDehxVAWCmVwVMILliAKVPAEr4yz2CkYs0tqU51S48x23dtna07H4qHtgpeNqVTthcIQOEQ==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.357.0.tgz", + "integrity": "sha512-fq3LS9AxHKb7dTZkm6iM1TrGk6XOTZz96iEZPME1+vjiSEXGWuebHt87q92n+KozVGRypn9MId3lHOPBBjygNQ==", "requires": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "@aws-sdk/util-buffer-from": "3.310.0", "@aws-sdk/util-utf8": "3.310.0", "tslib": "^2.5.0" } }, "@aws-sdk/hash-stream-node": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/hash-stream-node/-/hash-stream-node-3.310.0.tgz", - "integrity": "sha512-ZoXdybNgvMz1Hl6k/e32xVL3jmG5p2IEk5mTtLfFEuskTJ74Z+VMYKkkF1whyy7KQfH83H+TQGnsGtlRCchQKw==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/hash-stream-node/-/hash-stream-node-3.357.0.tgz", + "integrity": "sha512-KZjN1VAw1KHNp+xKVOWBGS+MpaYQTjZFD5f+7QQqW4TfbAkFFwIAEYIHq5Q8Gw+jVh0h61OrV/LyW3J2PVzc+w==", "requires": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "@aws-sdk/util-utf8": "3.310.0", "tslib": "^2.5.0" } }, "@aws-sdk/invalid-dependency": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.310.0.tgz", - "integrity": "sha512-1s5RG5rSPXoa/aZ/Kqr5U/7lqpx+Ry81GprQ2bxWqJvWQIJ0IRUwo5pk8XFxbKVr/2a+4lZT/c3OGoBOM1yRRA==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.357.0.tgz", + "integrity": "sha512-HnCYZczf0VdyxMVMMxmA3QJAyyPSFbcMtZzgKbxVTWTG7GKpQe0psWZu/7O2Nk31mKg6vEUdiP1FylqLBsgMOA==", "requires": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, @@ -17885,121 +17970,121 @@ } }, "@aws-sdk/md5-js": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/md5-js/-/md5-js-3.310.0.tgz", - "integrity": "sha512-x5sRBUrEfLWAS1EhwbbDQ7cXq6uvBxh3qR2XAsnGvFFceTeAadk7cVogWxlk3PC+OCeeym7c3/6Bv2HQ2f1YyQ==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/md5-js/-/md5-js-3.357.0.tgz", + "integrity": "sha512-to42sFAL7KgV/X9X40LLfEaNMHMGQL6/7mPMVCL/W2BZf3zw5OTl3lAaNyjXA+gO5Uo4lFEiQKAQVKNbr8b8Nw==", "requires": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "@aws-sdk/util-utf8": "3.310.0", "tslib": "^2.5.0" } }, "@aws-sdk/middleware-bucket-endpoint": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.310.0.tgz", - "integrity": "sha512-uJJfHI7v4AgbJZRLtyI8ap2QRWkBokGc3iyUoQ+dVNT3/CE2ZCu694A6W+H0dRqg79dIE+f9CRNdtLGa/Ehhvg==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.357.0.tgz", + "integrity": "sha512-ep2T0FJXRDl6nffLqiVZUYfDocZ3B72wvHeozckkLVRX0TK91WEpzv4Zz2vdeBp6CGkM3g8oGjbb6ZqllUZ6TA==", "requires": { - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/protocol-http": "3.357.0", + "@aws-sdk/types": "3.357.0", "@aws-sdk/util-arn-parser": "3.310.0", "@aws-sdk/util-config-provider": "3.310.0", "tslib": "^2.5.0" } }, "@aws-sdk/middleware-content-length": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.310.0.tgz", - "integrity": "sha512-P8tQZxgDt6CAh1wd/W6WPzjc+uWPJwQkm+F7rAwRlM+k9q17HrhnksGDKcpuuLyIhPQYdmOMIkpKVgXGa4avhQ==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.357.0.tgz", + "integrity": "sha512-zQOFEyzOXAgN4M54tYNWGxKxnyzY0WwYDTFzh9riJRmxN1hTEKHUKmze4nILIf5rkQmOG4kTf1qmfazjkvZAhw==", "requires": { - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/protocol-http": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/middleware-endpoint": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.310.0.tgz", - "integrity": "sha512-Z+N2vOL8K354/lstkClxLLsr6hCpVRh+0tCMXrVj66/NtKysCEZ/0b9LmqOwD9pWHNiI2mJqXwY0gxNlKAroUg==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.357.0.tgz", + "integrity": "sha512-ScJi0SL8X/Lyi0Fp5blg0QN/Z6PoRwV/ZJXd8dQkXSznkbSvJHfqPP0xk/w3GcQ1TKsu5YEPfeYy8ejcq+7Pgg==", "requires": { - "@aws-sdk/middleware-serde": "3.310.0", - "@aws-sdk/types": "3.310.0", - "@aws-sdk/url-parser": "3.310.0", - "@aws-sdk/util-middleware": "3.310.0", + "@aws-sdk/middleware-serde": "3.357.0", + "@aws-sdk/types": "3.357.0", + "@aws-sdk/url-parser": "3.357.0", + "@aws-sdk/util-middleware": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/middleware-expect-continue": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.310.0.tgz", - "integrity": "sha512-l3d1z2gt+gINJDnPSyu84IxfzjzPfCQrqC1sunw2cZGo/sXtEiq698Q3SiTcO2PGP4LBQAy2RHb5wVBJP708CQ==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.357.0.tgz", + "integrity": "sha512-KeizuiiDmdLeAbiNsJt/rZENY5iJo4wCTl7h81htDC60wSwVwFG03IdgvZlFH6jktYRh4mUDD/6Oljme6yPNxw==", "requires": { - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/protocol-http": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/middleware-flexible-checksums": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.310.0.tgz", - "integrity": "sha512-5ndnLgzgGVpWkmHBAiYkagHqiSuow8q62J4J6E2PzaQ77+fm8W3nfdy7hK5trHokEyouCZdxT/XK/IRhgj/4PA==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.357.0.tgz", + "integrity": "sha512-NNQ/iPN6YyzqgVaV8AeYQMZ8y1OmUW27vmt0R66UUw5H5THGc6X9QXoKfie7OHn80Qv1S8P5jw8z5MpvDtjSnQ==", "requires": { "@aws-crypto/crc32": "3.0.0", "@aws-crypto/crc32c": "3.0.0", "@aws-sdk/is-array-buffer": "3.310.0", - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/protocol-http": "3.357.0", + "@aws-sdk/types": "3.357.0", "@aws-sdk/util-utf8": "3.310.0", "tslib": "^2.5.0" } }, "@aws-sdk/middleware-host-header": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.310.0.tgz", - "integrity": "sha512-QWSA+46/hXorXyWa61ic2K7qZzwHTiwfk2e9mRRjeIRepUgI3qxFjsYqrWtrOGBjmFmq0pYIY8Bb/DCJuQqcoA==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.357.0.tgz", + "integrity": "sha512-HuGLcP7JP1qJ5wGT9GSlEknDaTSnOzHY4T6IPFuvFjAy3PvY5siQNm6+VRqdVS+n6/kzpL3JP5sAVM3aoxHT6Q==", "requires": { - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/protocol-http": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/middleware-location-constraint": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.310.0.tgz", - "integrity": "sha512-LFm0JTQWwTPWL/tZU2wsQTl8J5PpDEkXjEhaXVKamtyH0xhysRqd+0n92n65dc8oztAuQkb9xUbErGn5b6gsew==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.357.0.tgz", + "integrity": "sha512-4IsIHhwZ2/o7yjLI1XtGMkJ442cbIN5/NtI/Ml0G5UHYviUm8sqvH2vldFBMK5bPuVdk6GpqXpy6wYc9rLJj2w==", "requires": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/middleware-logger": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.310.0.tgz", - "integrity": "sha512-Lurm8XofrASBRnAVtiSNuDSRsRqPNg27RIFLLsLp/pqog9nFJ0vz0kgdb9S5Z+zw83Mm+UlqOe6D8NTUNp4fVg==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.357.0.tgz", + "integrity": "sha512-dncT3tr+lZ9+duZo52rASgO6AKVwRcsc2/T93gmaYVrJqI6WWAwQ7yML5s72l9ZjQ5LZ+4jjrgtlufavAS0eCg==", "requires": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/middleware-recursion-detection": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.310.0.tgz", - "integrity": "sha512-SuB75/xk/gyue24gkriTwO2jFd7YcUGZDClQYuRejgbXSa3CO0lWyawQtfLcSSEBp9izrEVXuFH24K1eAft5nQ==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.357.0.tgz", + "integrity": "sha512-AXC54IeDS3jC1dbbkYHML4STvBPcKZ4IJTWdjEK1RCOgqXd0Ze1cE1e21wyj1tM6prF03zLyvpBd+3TS++nqfA==", "requires": { - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/protocol-http": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/middleware-retry": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.310.0.tgz", - "integrity": "sha512-oTPsRy2W4s+dfxbJPW7Km+hHtv/OMsNsVfThAq8DDYKC13qlr1aAyOqGLD+dpBy2aKe7ss517Sy2HcHtHqm7/g==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.357.0.tgz", + "integrity": "sha512-ZCbXCYv3nglQqwREYxxpclrnR9MYPAnHlLcC8e9PbApqxGnaZdhoywxoqbgqT3hf/RM7kput4vEHDl1fyymcRQ==", "requires": { - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/service-error-classification": "3.310.0", - "@aws-sdk/types": "3.310.0", - "@aws-sdk/util-middleware": "3.310.0", - "@aws-sdk/util-retry": "3.310.0", + "@aws-sdk/protocol-http": "3.357.0", + "@aws-sdk/service-error-classification": "3.357.0", + "@aws-sdk/types": "3.357.0", + "@aws-sdk/util-middleware": "3.357.0", + "@aws-sdk/util-retry": "3.357.0", "tslib": "^2.5.0", "uuid": "^8.3.2" }, @@ -18012,226 +18097,229 @@ } }, "@aws-sdk/middleware-sdk-s3": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.310.0.tgz", - "integrity": "sha512-QK9x9g2ksg0hOjjYgqddeFcn5ctUEGdxJVu4OumPXceulefMcSO2jyH2qTybYSA93nqNQFdFmg5wQfvIRUWFCQ==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.357.0.tgz", + "integrity": "sha512-EFQaPD8SoXcK7RiEOZz0zIX9owQW6txu8vrOOVva9xMts36z/3E7b4FVsgEJ53Ixa1x38ddPJxp4U8EIaf+pvQ==", "requires": { - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/protocol-http": "3.357.0", + "@aws-sdk/types": "3.357.0", "@aws-sdk/util-arn-parser": "3.310.0", "tslib": "^2.5.0" } }, "@aws-sdk/middleware-sdk-sts": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.310.0.tgz", - "integrity": "sha512-+5PFwlYNLvLLIfw0ASAoWV/iIF8Zv6R6QGtyP0CclhRSvNjgbQDVnV0g95MC5qvh+GB/Yjlkt8qAjLSPjHfsrQ==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.357.0.tgz", + "integrity": "sha512-Ng2VjLrPiL02QOcs1qs9jG2boO4Gn+v3VIbOJLG4zXcfbSq55iIWtlmr2ljfw9vP5aLhWtcODfmKHS5Bp+019Q==", "requires": { - "@aws-sdk/middleware-signing": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/middleware-signing": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/middleware-serde": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.310.0.tgz", - "integrity": "sha512-RNeeTVWSLTaentUeCgQKZhAl+C6hxtwD78cQWS10UymWpQFwbaxztzKUu4UQS5xA2j6PxwPRRUjqa4jcFjfLsg==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.357.0.tgz", + "integrity": "sha512-bGI4kYuuEsFjlANbyJLyy4AovETnyf/SukgLOG7Qjbua+ZGuzvRhMsk21mBKKGrnsTO4PmtieJo6xClThGAN8g==", "requires": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/middleware-signing": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.310.0.tgz", - "integrity": "sha512-f9mKq+XMdW207Af3hKjdTnpNhdtwqWuvFs/ZyXoOkp/g1MY1O6L23Jy6i52m29LxbT4AuNRG1oKODfXM0vYVjQ==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.357.0.tgz", + "integrity": "sha512-yB9ewEqI6Fw1OrmKFrUypbCqN5ijk06UGPochybamMuPxxkwMT3bnrm7eezsCA+TZbJyKhpffpyobwuv+xGNrA==", "requires": { - "@aws-sdk/property-provider": "3.310.0", - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/signature-v4": "3.310.0", - "@aws-sdk/types": "3.310.0", - "@aws-sdk/util-middleware": "3.310.0", + "@aws-sdk/property-provider": "3.357.0", + "@aws-sdk/protocol-http": "3.357.0", + "@aws-sdk/signature-v4": "3.357.0", + "@aws-sdk/types": "3.357.0", + "@aws-sdk/util-middleware": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/middleware-ssec": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.310.0.tgz", - "integrity": "sha512-CnEwNKVpd5bXnrCKPaePF8mWTA9ET21OMBb54y9b0fd8K02zoOcdBz4DWfh1SjFD4HkgCdja4egd8l2ivyvqmw==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.357.0.tgz", + "integrity": "sha512-uE3nNvJclcY7SgGoOgDCUgfc7ElXQmWVpks8AZzAjJj7bG5j6Bv3FOOYtGtvtxUzTHaOdn+yQwjssV1cZ6GTQw==", "requires": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/middleware-stack": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.310.0.tgz", - "integrity": "sha512-010O1PD+UAcZVKRvqEusE1KJqN96wwrf6QsqbRM0ywsKQ21NDweaHvEDlds2VHpgmofxkRLRu/IDrlPkKRQrRg==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.357.0.tgz", + "integrity": "sha512-nNV+jfwGwmbOGZujAY/U8AW3EbVlxa9DJDLz3TPp/39o6Vu5KEzHJyDDNreo2k9V/TMvV+nOzHafufgPdagv7w==", "requires": { "tslib": "^2.5.0" } }, "@aws-sdk/middleware-user-agent": { - "version": "3.319.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.319.0.tgz", - "integrity": "sha512-ytaLx2dlR5AdMSne6FuDCISVg8hjyKj+cHU20b2CRA/E/z+XXrLrssp4JrCgizRKPPUep0psMIa22Zd6osTT5Q==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.357.0.tgz", + "integrity": "sha512-M/CsAXjGblZS4rEbMb0Dn9IXbfq4EjVaTHBfvuILU/dKRppWvjnm2lRtqCZ+LIT3ATbAjA3/dY7dWsjxQWwijA==", "requires": { - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/types": "3.310.0", - "@aws-sdk/util-endpoints": "3.319.0", + "@aws-sdk/protocol-http": "3.357.0", + "@aws-sdk/types": "3.357.0", + "@aws-sdk/util-endpoints": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/node-config-provider": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.310.0.tgz", - "integrity": "sha512-T/Pp6htc6hq/Cq+MLNDSyiwWCMVF6GqbBbXKVlO5L8rdHx4sq9xPdoPveZhGWrxvkanjA6eCwUp6E0riBOSVng==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.357.0.tgz", + "integrity": "sha512-kwBIzKCaW3UWqLdELhy7TcN8itNMOjbzga530nalFILMvn2IxrkdKQhNgxGBXy6QK6kCOtH6OmcrG3/oZkLwig==", "requires": { - "@aws-sdk/property-provider": "3.310.0", - "@aws-sdk/shared-ini-file-loader": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/property-provider": "3.357.0", + "@aws-sdk/shared-ini-file-loader": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/node-http-handler": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.310.0.tgz", - "integrity": "sha512-irv9mbcM9xC2xYjArQF5SYmHBMu4ciMWtGsoHII1nRuFOl9FoT4ffTvEPuLlfC6pznzvKt9zvnm6xXj7gDChKg==", + "version": "3.360.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.360.0.tgz", + "integrity": "sha512-oMsXdMmNwHpUbebETO44bq0N4SocEMGfPjYNUTRs8md7ita5fuFd2qFuvf+ZRt6iVcGWluIqmF8DidD+b7d+TA==", "requires": { - "@aws-sdk/abort-controller": "3.310.0", - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/querystring-builder": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/abort-controller": "3.357.0", + "@aws-sdk/protocol-http": "3.357.0", + "@aws-sdk/querystring-builder": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/property-provider": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.310.0.tgz", - "integrity": "sha512-3lxDb0akV6BBzmFe4nLPaoliQbAifyWJhuvuDOu7e8NzouvpQXs0275w9LePhhcgjKAEVXUIse05ZW2DLbxo/g==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.357.0.tgz", + "integrity": "sha512-im4W0u8WaYxG7J7ko4Xl3OEzK3Mrm1Rz6/txTGe6hTIHlyUISu1ekOQJXK6XYPqNMn8v1G3BiQREoRXUEJFbHg==", "requires": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/protocol-http": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.310.0.tgz", - "integrity": "sha512-fgZ1aw/irQtnrsR58pS8ThKOWo57Py3xX6giRvwSgZDEcxHfVzuQjy9yPuV++v04fdmdtgpbGf8WfvAAJ11yXQ==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.357.0.tgz", + "integrity": "sha512-w1JHiI50VEea7duDeAspUiKJmmdIQblvRyjVMOqWA6FIQAyDVuEiPX7/MdQr0ScxhtRQxHbP0I4MFyl7ctRQvA==", "requires": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/querystring-builder": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.310.0.tgz", - "integrity": "sha512-ZHH8GV/80+pWGo7DzsvwvXR5xVxUHXUvPJPFAkhr6nCf78igdoF8gR10ScFoEKbtEapoNTaZlKHPXxpD8aPG7A==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.357.0.tgz", + "integrity": "sha512-aQcicqB6Y2cNaXPPwunz612a01SMiQQPsdz632F/3Lzn0ua82BJKobHOtaiTUlmVJ5Q4/EAeNfwZgL7tTUNtDQ==", "requires": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "@aws-sdk/util-uri-escape": "3.310.0", "tslib": "^2.5.0" } }, "@aws-sdk/querystring-parser": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.310.0.tgz", - "integrity": "sha512-YkIznoP6lsiIUHinx++/lbb3tlMURGGqMpo0Pnn32zYzGrJXA6eC3D0as2EcMjo55onTfuLcIiX4qzXes2MYOA==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.357.0.tgz", + "integrity": "sha512-Svvq+atRNP9s2VxiklcUNgCzmt3T5kfs7X2C+yjmxHvOQTPjLNaNGbfC/vhjOK7aoXw0h+lBac48r5ymx1PbQA==", "requires": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/s3-request-presigner": { - "version": "3.319.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.319.0.tgz", - "integrity": "sha512-mq/4nqr/3sHGx9iJqPXXkfBPAlXGB000u4dX4QCMPFmMupfH5sT8M+hYgcB6N/2HuZ+JNJQntwu9q97gjhwpDQ==", + "version": "3.360.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.360.0.tgz", + "integrity": "sha512-SWfRyyEbGKnrllKRWHsFfS4xbfM1YHYsaO2PJRo49vYIZvsj10vs0HZX8fF0v4md1XW99E7wBanxkoMo3mYVvg==", "requires": { - "@aws-sdk/middleware-endpoint": "3.310.0", - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/signature-v4-multi-region": "3.310.0", - "@aws-sdk/smithy-client": "3.316.0", - "@aws-sdk/types": "3.310.0", - "@aws-sdk/util-format-url": "3.310.0", + "@aws-sdk/middleware-endpoint": "3.357.0", + "@aws-sdk/protocol-http": "3.357.0", + "@aws-sdk/signature-v4-multi-region": "3.357.0", + "@aws-sdk/smithy-client": "3.360.0", + "@aws-sdk/types": "3.357.0", + "@aws-sdk/util-format-url": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/service-error-classification": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.310.0.tgz", - "integrity": "sha512-PuyC7k3qfIKeH2LCnDwbttMOKq3qAx4buvg0yfnJtQOz6t1AR8gsnAq0CjKXXyfkXwNKWTqCpE6lVNUIkXgsMw==" + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.357.0.tgz", + "integrity": "sha512-VuXeL4g5vKO9HjgCZlxmH8Uv1FcqUSjmbPpQkbNtYIDck6u0qzM0rG+n0/1EjyQbPSr3MhW/pkWs5nx2Nljlyg==" }, "@aws-sdk/shared-ini-file-loader": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.310.0.tgz", - "integrity": "sha512-N0q9pG0xSjQwc690YQND5bofm+4nfUviQ/Ppgan2kU6aU0WUq8KwgHJBto/YEEI+VlrME30jZJnxtOvcZJc2XA==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.357.0.tgz", + "integrity": "sha512-ceyqM4XxQe0Plb/oQAD2t1UOV2Iy4PFe1oAGM8dfJzYrRKu7zvMwru7/WaB3NYq+/mIY6RU+jjhRmjQ3GySVqA==", "requires": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/signature-v4": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.310.0.tgz", - "integrity": "sha512-1M60P1ZBNAjCFv9sYW29OF6okktaeibWyW3lMXqzoHF70lHBZh+838iUchznXUA5FLabfn4jBFWMRxlAXJUY2Q==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.357.0.tgz", + "integrity": "sha512-itt4/Jh9FqnzK30qIjXFBvM4J7zN4S/AAqsRMnaX7U4f/MV+1YxQHmzimpdMnsCXXs2jqFqKVRu6DewxJ3nbxg==", "requires": { + "@aws-sdk/eventstream-codec": "3.357.0", "@aws-sdk/is-array-buffer": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "@aws-sdk/util-hex-encoding": "3.310.0", - "@aws-sdk/util-middleware": "3.310.0", + "@aws-sdk/util-middleware": "3.357.0", "@aws-sdk/util-uri-escape": "3.310.0", "@aws-sdk/util-utf8": "3.310.0", "tslib": "^2.5.0" } }, "@aws-sdk/signature-v4-multi-region": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.310.0.tgz", - "integrity": "sha512-q8W+RIomTS/q85Ntgks/CoDElwqkC9+4OCicee5YznNHjQ4gtNWhUkYIyIRWRmXa/qx/AUreW9DM8FAecCOdng==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.357.0.tgz", + "integrity": "sha512-eyO3GibYLNCPZ/YxM/ZVDh1fTMKvIUj4fpVo0bxQTKNlqNkVumAIOVLoH5um1A9FN7nDdz+40a7jwYSPlkxW6A==", "requires": { - "@aws-sdk/protocol-http": "3.310.0", - "@aws-sdk/signature-v4": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/protocol-http": "3.357.0", + "@aws-sdk/signature-v4": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/smithy-client": { - "version": "3.316.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.316.0.tgz", - "integrity": "sha512-6YXOKbRnXeS8r8RWzuL6JMBolDYM5Wa4fD/VY6x/wK78i2xErHOvqzHgyyeLI1MMw4uqyd4wRNJNWC9TMPduXw==", + "version": "3.360.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.360.0.tgz", + "integrity": "sha512-R7wbT2SkgWNEAxMekOTNcPcvBszabW2+qHjrcelbbVJNjx/2yK+MbpZI4WRSncByQMeeoW+aSUP+JgsbpiOWfw==", "requires": { - "@aws-sdk/middleware-stack": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/middleware-stack": "3.357.0", + "@aws-sdk/types": "3.357.0", + "@aws-sdk/util-stream": "3.360.0", + "@smithy/types": "^1.0.0", "tslib": "^2.5.0" } }, "@aws-sdk/token-providers": { - "version": "3.319.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.319.0.tgz", - "integrity": "sha512-5utg6VL6Pl0uiLUn8ZJPYYxzCb9VRPsgJmGXktRUwq0YlTJ6ABcaxTXwZcC++sjh/qyCQDK5PPLNU5kIBttHMQ==", + "version": "3.360.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.360.0.tgz", + "integrity": "sha512-gtnCmn2NL7uSwadqQPeU74Wo7Wf1NMJtui+KSWPYpc3joRZqIYj0kL5w0IT2S9tPQwCFerWVfhkvRkSGJ4nZ/g==", "requires": { - "@aws-sdk/client-sso-oidc": "3.319.0", - "@aws-sdk/property-provider": "3.310.0", - "@aws-sdk/shared-ini-file-loader": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/client-sso-oidc": "3.360.0", + "@aws-sdk/property-provider": "3.357.0", + "@aws-sdk/shared-ini-file-loader": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/types": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.310.0.tgz", - "integrity": "sha512-j8eamQJ7YcIhw7fneUfs8LYl3t01k4uHi4ZDmNRgtbmbmTTG3FZc2MotStZnp3nZB6vLiPF1o5aoJxWVvkzS6A==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.357.0.tgz", + "integrity": "sha512-/riCRaXg3p71BeWnShrai0y0QTdXcouPSM0Cn1olZbzTf7s71aLEewrc96qFrL70XhY4XvnxMpqQh+r43XIL3g==", "requires": { "tslib": "^2.5.0" } }, "@aws-sdk/url-parser": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.310.0.tgz", - "integrity": "sha512-mCLnCaSB9rQvAgx33u0DujLvr4d5yEm/W5r789GblwwQnlNXedVu50QRizMLTpltYWyAUoXjJgQnJHmJMaKXhw==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.357.0.tgz", + "integrity": "sha512-fAaU6cFsaAba01lCRsRJiYR/LfXvX2wudyEyutBVglE4dWSoSeu3QJNxImIzTBULfbiFhz59++NQ1JUVx88IVg==", "requires": { - "@aws-sdk/querystring-parser": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/querystring-parser": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, @@ -18286,45 +18374,45 @@ } }, "@aws-sdk/util-defaults-mode-browser": { - "version": "3.316.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.316.0.tgz", - "integrity": "sha512-6FSqLhYmaihtH2n1s4b2rlLW0ABU8N6VZIfzLfe2ING4PF0MzfaMMhnTFUHVXfKCVGoR8yP6iyFTRCyHGVEL1w==", + "version": "3.360.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.360.0.tgz", + "integrity": "sha512-/GR8VlK9xo1Q5WbVYuNaZ+XfoCFdWNb4z4mpoEgvEgBH4R0GjqiAqLftUA8Ykq1tJuDAKPYVzUNzK8DC0pt7/g==", "requires": { - "@aws-sdk/property-provider": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/property-provider": "3.357.0", + "@aws-sdk/types": "3.357.0", "bowser": "^2.11.0", "tslib": "^2.5.0" } }, "@aws-sdk/util-defaults-mode-node": { - "version": "3.316.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.316.0.tgz", - "integrity": "sha512-dkYy10hdjPSScXXvnjGpZpnJxllkb6ICHgLMwZ4JczLHhPM12T/4PQ758YN8HS+muiYDGX1Bl2z1jd/bMcewBQ==", + "version": "3.360.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.360.0.tgz", + "integrity": "sha512-gR3Ctqpyl7SgStDJ1Jlq6qQDuw/rS9AgbAXx+s3wsmm3fm8lHKkXkDPYVvNDqd6dVXRO6q8MRx00lwkGI4qrpQ==", "requires": { - "@aws-sdk/config-resolver": "3.310.0", - "@aws-sdk/credential-provider-imds": "3.310.0", - "@aws-sdk/node-config-provider": "3.310.0", - "@aws-sdk/property-provider": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/config-resolver": "3.357.0", + "@aws-sdk/credential-provider-imds": "3.357.0", + "@aws-sdk/node-config-provider": "3.357.0", + "@aws-sdk/property-provider": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/util-endpoints": { - "version": "3.319.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.319.0.tgz", - "integrity": "sha512-3I64UMoYA2e2++oOUJXRcFtYLpLylnZFRltWfPo1B3dLlf+MIWat9djT+mMus+hW1ntLsvAIVu1hLVePJC0gvw==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.357.0.tgz", + "integrity": "sha512-XHKyS5JClT9su9hDif715jpZiWHQF9gKZXER8tW0gOizU3R9cyWc9EsJ2BRhFNhi7nt/JF/CLUEc5qDx3ETbUw==", "requires": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, "@aws-sdk/util-format-url": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.310.0.tgz", - "integrity": "sha512-NBOvmvvVR3ydquHmznfgtakiSgDhq8Ww6fq8TUaEjM+Es6+iqY4AwZo0rZ9xTX3GpCcoZy391HUi6kiXRAFzuA==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.357.0.tgz", + "integrity": "sha512-mdTO210Nkraw+gY/KxJN9wvX8brIT5+d5tLtLSNKx4K8Fx+qbIug9VeOaVVlv9S1tX5lUvG1l9SEIwq3RCserg==", "requires": { - "@aws-sdk/querystring-builder": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/querystring-builder": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, @@ -18345,46 +18433,37 @@ } }, "@aws-sdk/util-middleware": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.310.0.tgz", - "integrity": "sha512-FTSUKL/eRb9X6uEZClrTe27QFXUNNp7fxYrPndZwk1hlaOP5ix+MIHBcI7pIiiY/JPfOUmPyZOu+HetlFXjWog==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.357.0.tgz", + "integrity": "sha512-pV1krjZs7BdahZBfsCJMatE8kcor7GFsBOWrQgQDm9T0We5b5xPpOO2vxAD0RytBpY8Ky2ELs/+qXMv7l5fWIA==", "requires": { "tslib": "^2.5.0" } }, "@aws-sdk/util-retry": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-retry/-/util-retry-3.310.0.tgz", - "integrity": "sha512-FwWGhCBLfoivTMUHu1LIn4NjrN9JLJ/aX5aZmbcPIOhZVFJj638j0qDgZXyfvVqBuBZh7M8kGq0Oahy3dp69OA==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-retry/-/util-retry-3.357.0.tgz", + "integrity": "sha512-SUqYJE9msbuOVq+vnUy+t0LH7XuYNFz66dSF8q6tedsbJK4j8tgya0I1Ct3m06ynGrXDJMaj39I7AXCyW9bjtw==", "requires": { - "@aws-sdk/service-error-classification": "3.310.0", + "@aws-sdk/service-error-classification": "3.357.0", "tslib": "^2.5.0" } }, - "@aws-sdk/util-stream-browser": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-stream-browser/-/util-stream-browser-3.310.0.tgz", - "integrity": "sha512-bysXZHwFwvbqOTCScCdCnoLk1K3GCo0HRIYEZuL7O7MHrQmfaYRXcaft/p22+GUv9VeFXS/eJJZ5r4u32az94w==", + "@aws-sdk/util-stream": { + "version": "3.360.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-stream/-/util-stream-3.360.0.tgz", + "integrity": "sha512-t3naBfNesXwLis29pzSfLx2ifCn2180GiPjRaIsQP14IiVCBOeT1xaU6Dpyk7WeR/jW4cu7wGl+kbeyfNF6QmQ==", "requires": { - "@aws-sdk/fetch-http-handler": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/fetch-http-handler": "3.357.0", + "@aws-sdk/node-http-handler": "3.360.0", + "@aws-sdk/types": "3.357.0", "@aws-sdk/util-base64": "3.310.0", + "@aws-sdk/util-buffer-from": "3.310.0", "@aws-sdk/util-hex-encoding": "3.310.0", "@aws-sdk/util-utf8": "3.310.0", "tslib": "^2.5.0" } }, - "@aws-sdk/util-stream-node": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-stream-node/-/util-stream-node-3.310.0.tgz", - "integrity": "sha512-hueAXFK0GVvnfYFgqbF7587xZfMZff5jlIFZOHqx7XVU7bl7qrRUCnphHk8H6yZ7RoQbDPcfmHJgtEoAJg1T1Q==", - "requires": { - "@aws-sdk/node-http-handler": "3.310.0", - "@aws-sdk/types": "3.310.0", - "@aws-sdk/util-buffer-from": "3.310.0", - "tslib": "^2.5.0" - } - }, "@aws-sdk/util-uri-escape": { "version": "3.310.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.310.0.tgz", @@ -18394,22 +18473,22 @@ } }, "@aws-sdk/util-user-agent-browser": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.310.0.tgz", - "integrity": "sha512-yU/4QnHHuQ5z3vsUqMQVfYLbZGYwpYblPiuZx4Zo9+x0PBkNjYMqctdDcrpoH9Z2xZiDN16AmQGK1tix117ZKw==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.357.0.tgz", + "integrity": "sha512-JHaWlNIUkPNvXkqeDOrqFzAlAgdwZK5mZw7FQnCRvf8tdSogpGZSkuyb9Z6rLD9gC40Srbc2nepO1cFpeMsDkA==", "requires": { - "@aws-sdk/types": "3.310.0", + "@aws-sdk/types": "3.357.0", "bowser": "^2.11.0", "tslib": "^2.5.0" } }, "@aws-sdk/util-user-agent-node": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.310.0.tgz", - "integrity": "sha512-Ra3pEl+Gn2BpeE7KiDGpi4zj7WJXZA5GXnGo3mjbi9+Y3zrbuhJAbdZO3mO/o7xDgMC6ph4xCTbaSGzU6b6EDg==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.357.0.tgz", + "integrity": "sha512-RdpQoaJWQvcS99TVgSbT451iGrlH4qpWUWFA9U1IRhxOSsmC1hz8ME7xc8nci9SREx/ZlfT3ai6LpoAzAtIEMA==", "requires": { - "@aws-sdk/node-config-provider": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/node-config-provider": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, @@ -18431,12 +18510,12 @@ } }, "@aws-sdk/util-waiter": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-waiter/-/util-waiter-3.310.0.tgz", - "integrity": "sha512-AV5j3guH/Y4REu+Qh3eXQU9igljHuU4XjX2sADAgf54C0kkhcCCkkiuzk3IsX089nyJCqIcj5idbjdvpnH88Vw==", + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-waiter/-/util-waiter-3.357.0.tgz", + "integrity": "sha512-jQQGA5G8bm0JP5C4U85VzMpkFHdeeT7fOSUncXLG9Sh8Ambzi4XTud8m5/dA7aNJkvPwZeIF9QdgWCOzpkp1xA==", "requires": { - "@aws-sdk/abort-controller": "3.310.0", - "@aws-sdk/types": "3.310.0", + "@aws-sdk/abort-controller": "3.357.0", + "@aws-sdk/types": "3.357.0", "tslib": "^2.5.0" } }, @@ -18449,18 +18528,18 @@ } }, "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", "dev": true, "requires": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.5" } }, "@babel/compat-data": { - "version": "7.20.14", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.14.tgz", - "integrity": "sha512-0YpKHD6ImkWMEINCyDAD0HLLUH/lPCefG8ld9it8DJB2wnApraKuhgYTvTY1z7UFIfBTGy5LwncZ+5HWWGbhFw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.5.tgz", + "integrity": "sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA==", "dev": true }, "@babel/core": { @@ -18538,13 +18617,13 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", - "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz", + "integrity": "sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw==", "dev": true, "requires": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-validator-option": "^7.18.6", + "@babel/compat-data": "^7.22.5", + "@babel/helper-validator-option": "^7.22.5", "browserslist": "^4.21.3", "lru-cache": "^5.1.1", "semver": "^6.3.0" @@ -18622,9 +18701,9 @@ } }, "@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", + "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", "dev": true }, "@babel/helper-explode-assignable-expression": { @@ -18637,31 +18716,44 @@ } }, "@babel/helper-function-name": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", - "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", + "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", "dev": true, "requires": { - "@babel/template": "^7.18.10", - "@babel/types": "^7.19.0" + "@babel/template": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "dependencies": { + "@babel/template": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", + "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5" + } + } } }, "@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.20.7.tgz", - "integrity": "sha512-9J0CxJLq315fEdi4s7xK5TQaNYjZw+nDVpVqr1axNGKzdrdwYBD5b4uKv3n75aABG0rCCTK8Im8Ww7eYfMrZgw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz", + "integrity": "sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==", "dev": true, "requires": { - "@babel/types": "^7.20.7" + "@babel/types": "^7.22.5" } }, "@babel/helper-module-imports": { @@ -18690,18 +18782,18 @@ } }, "@babel/helper-optimise-call-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", - "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-plugin-utils": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", - "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", "dev": true }, "@babel/helper-remap-async-to-generator": { @@ -18717,17 +18809,30 @@ } }, "@babel/helper-replace-supers": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz", - "integrity": "sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.5.tgz", + "integrity": "sha512-aLdNM5I3kdI/V9xGNyKSF3X/gTyMUBohTZ+/3QdQKAA9vxIiy12E+8E2HoOP1/DjeqU+g6as35QHJNMDDYpuCg==", "dev": true, "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-member-expression-to-functions": "^7.20.7", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", - "@babel/types": "^7.20.7" + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "dependencies": { + "@babel/template": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", + "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5" + } + } } }, "@babel/helper-simple-access": { @@ -18758,21 +18863,21 @@ } }, "@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true }, "@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", "dev": true }, "@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", + "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", "dev": true }, "@babel/helper-wrap-function": { @@ -18799,20 +18904,20 @@ } }, "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", + "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-validator-identifier": "^7.22.5", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.20.15", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.15.tgz", - "integrity": "sha512-DI4a1oZuf8wC+oAJA9RW6ga3Zbe8RZFt7kD9i4qAspz3I/yHet1VvC3DiSy/fsUvv5pvJuNPh0LPOdCcqinDPg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", + "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==", "dev": true }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { @@ -19168,20 +19273,40 @@ } }, "@babel/plugin-transform-classes": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.7.tgz", - "integrity": "sha512-LWYbsiXTPKl+oBlXUGlwNlJZetXD5Am+CyBdqhPsDVjM9Jc8jwBJFrKhHf900Kfk2eZG1y9MAG3UNajol7A4VQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.5.tgz", + "integrity": "sha512-2edQhLfibpWpsVBx2n/GKOz6JdGQvLruZQfGr9l1qes2KQaWswjBzhQF7UDUZMNaMMQeYnQzxwOMPsbYF7wqPQ==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-replace-supers": "^7.20.7", - "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", "globals": "^11.1.0" + }, + "dependencies": { + "@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz", + "integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + } } }, "@babel/plugin-transform-computed-properties": { @@ -19242,14 +19367,14 @@ } }, "@babel/plugin-transform-function-name": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", - "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz", + "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==", "dev": true, "requires": { - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-transform-literals": { @@ -19591,31 +19716,65 @@ } }, "@babel/traverse": { - "version": "7.20.13", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.13.tgz", - "integrity": "sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", + "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.7", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.20.13", - "@babel/types": "^7.20.7", + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5", "debug": "^4.1.0", "globals": "^11.1.0" + }, + "dependencies": { + "@babel/generator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", + "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz", + "integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } } }, "@babel/types": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz", - "integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", + "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", "dev": true, "requires": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", "to-fast-properties": "^2.0.0" } }, @@ -19951,9 +20110,9 @@ } }, "@jridgewell/resolve-uri": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.8.tgz", - "integrity": "sha512-YK5G9LaddzGbcucK4c8h5tWFmMPBvRZ/uyWmN1/SbBdIvqGUdWGkJ5BAaccgs6XbzVLsqbPJrBSFwKv3kT9i7w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", "dev": true }, "@jridgewell/set-array": { @@ -19992,13 +20151,13 @@ "dev": true }, "@jridgewell/trace-mapping": { - "version": "0.3.14", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz", - "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==", + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", "dev": true, "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" } }, "@leichtgewicht/ip-codec": { @@ -20814,9 +20973,9 @@ } }, "@ngtools/webpack": { - "version": "15.2.6", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-15.2.6.tgz", - "integrity": "sha512-I+kekKItfsCLdX+ZjjmsWqd0AyoYGTQPjlbQAiPtmdH73/rfPOF4Q/3AU4tzTdn0n0GXqZWv6VOs91w99ydi0A==", + "version": "15.2.8", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-15.2.8.tgz", + "integrity": "sha512-BJexeT4FxMtToVBGa3wdl6rrkYXgilP0kkSH4Qzu4MPlLPbeBSr4XQalQriewlpC2uzG0r2SJfrAe2eDhtSykA==", "dev": true }, "@nodelib/fs.scandir": { @@ -20958,13 +21117,13 @@ } }, "@schematics/angular": { - "version": "15.2.6", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-15.2.6.tgz", - "integrity": "sha512-OcBUvVAxZEMBX+fi0ytybeAdmStra+GwtlvipS70yOxcAgJ84ZrnZGN7a072cCVQcq7AgqUfssnyqCx1wu+yCg==", + "version": "15.2.8", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-15.2.8.tgz", + "integrity": "sha512-F49IEzCFxQlpaMIgTO/wF1l/CLQKif7VaiDdyiTKOeT22IMmyd61FUmWDyZYfCBqMlvBmvDGx64HaHWes1HYCg==", "dev": true, "requires": { - "@angular-devkit/core": "15.2.6", - "@angular-devkit/schematics": "15.2.6", + "@angular-devkit/core": "15.2.8", + "@angular-devkit/schematics": "15.2.8", "jsonc-parser": "3.2.0" } }, @@ -20974,6 +21133,23 @@ "integrity": "sha512-a31EnjuIDSX8IXBUib3cYLDRlPMU36AWX4xS8ysLaNu4ZzUesDiPt83pgrW2X1YLMe5L2HbDyaKK5BrL4cNKaQ==", "dev": true }, + "@smithy/protocol-http": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-1.1.0.tgz", + "integrity": "sha512-H5y/kZOqfJSqRkwtcAoVbqONmhdXwSgYNJ1Glk5Ry8qlhVVy5qUzD9EklaCH8/XLnoCsLO/F/Giee8MIvaBRkg==", + "requires": { + "@smithy/types": "^1.1.0", + "tslib": "^2.5.0" + } + }, + "@smithy/types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-1.1.0.tgz", + "integrity": "sha512-KzmvisMmuwD2jZXuC9e65JrgsZM97y5NpDU7g347oB+Q+xQLU6hQZ5zFNNbEfwwOJHoOvEVTna+dk1h/lW7alw==", + "requires": { + "tslib": "^2.5.0" + } + }, "@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -21215,9 +21391,9 @@ } }, "@types/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.2.tgz", + "integrity": "sha512-kNnC1GFBLuhImSnV7w4njQkUiJi0ZXUycu1rUaouPqiKlXkh77JKgdRnTAp1x5eBwcIwbtI+3otwzuIDEuDoxQ==", "dev": true }, "@types/ws": { @@ -21649,6 +21825,21 @@ "tslib": "^2.0.0" } }, + "angular2-csv": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/angular2-csv/-/angular2-csv-0.2.9.tgz", + "integrity": "sha512-RmVpaT2DSLJikPIxXfvhNHT/a03g+jM7Wsx0x+kNmCTuTqA1MAIPVhZPNgFvoSvZM898ACgcTSV6vvsDLdt1BA==", + "requires": { + "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, "ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -22038,13 +22229,55 @@ } }, "britecharts": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/britecharts/-/britecharts-2.18.0.tgz", - "integrity": "sha512-dG/kXx3KcUnWaMn+hInrpQNX1hbXgwTCJBawhHUN/GHkVMTe5+aVD/le+5oOhwDpfaMs8nn3TpY9D4o2hdP8HQ==", + "version": "3.0.0-alpha-6.1.6", + "resolved": "https://registry.npmjs.org/britecharts/-/britecharts-3.0.0-alpha-6.1.6.tgz", + "integrity": "sha512-ypSOJOJcBzXkxSY/OIxIIlnKKSCFqV+9QiHGbtM9a/fo8O5SFn18GKARKG8ZdGfvb8LwfuDfWTghlUA6WmcR7A==", "requires": { "base-64": "^0.1.0", - "d3": "^5.16.0", - "lodash.assign": "^4.2.0" + "d3-axis": "^1.0.12", + "d3-brush": "^1.1.6", + "d3-collection": "^1.0.7", + "d3-format": "^1.4.5", + "d3-scale": "^3.2.4", + "d3-selection": "^1.4.1", + "d3-shape": "^1.3.7", + "d3-time": "^1.1.0", + "d3-time-format": "^3.0.0", + "d3-transition": "^1.3.2", + "d3-voronoi": "^1.1.4", + "d3-zoom": "^1.8.3" + }, + "dependencies": { + "d3-selection": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", + "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" + }, + "d3-transition": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.3.2.tgz", + "integrity": "sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==", + "requires": { + "d3-color": "1", + "d3-dispatch": "1", + "d3-ease": "1", + "d3-interpolate": "1", + "d3-selection": "^1.1.0", + "d3-timer": "1" + } + }, + "d3-zoom": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.8.3.tgz", + "integrity": "sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==", + "requires": { + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" + } + } } }, "browserslist": { @@ -22372,7 +22605,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, "color-support": { @@ -22395,7 +22628,8 @@ "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true }, "comment-parser": { "version": "1.3.1", @@ -22746,55 +22980,13 @@ "resolved": "https://registry.npmjs.org/curved-arrows/-/curved-arrows-0.1.0.tgz", "integrity": "sha512-FchrDNr8b3ijJOycRM3iT0tc7d5NJSJ5e1fPZibTv73N2yXcgIbSi858R+QfCPRbrjqSWXnR8OaUUu9w+TxTRg==" }, - "d3": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/d3/-/d3-5.16.0.tgz", - "integrity": "sha512-4PL5hHaHwX4m7Zr1UapXW23apo6pexCgdetdJ5kTmADpG/7T9Gkxw0M0tf/pjoB63ezCCm0u5UaFYy2aMt0Mcw==", - "requires": { - "d3-array": "1", - "d3-axis": "1", - "d3-brush": "1", - "d3-chord": "1", - "d3-collection": "1", - "d3-color": "1", - "d3-contour": "1", - "d3-dispatch": "1", - "d3-drag": "1", - "d3-dsv": "1", - "d3-ease": "1", - "d3-fetch": "1", - "d3-force": "1", - "d3-format": "1", - "d3-geo": "1", - "d3-hierarchy": "1", - "d3-interpolate": "1", - "d3-path": "1", - "d3-polygon": "1", - "d3-quadtree": "1", - "d3-random": "1", - "d3-scale": "2", - "d3-scale-chromatic": "1", - "d3-selection": "1", - "d3-shape": "1", - "d3-time": "1", - "d3-time-format": "2", - "d3-timer": "1", - "d3-transition": "1", - "d3-voronoi": "1", - "d3-zoom": "1" - }, - "dependencies": { - "d3-selection": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", - "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" - } - } - }, "d3-array": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", - "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "requires": { + "internmap": "^1.0.0" + } }, "d3-axis": { "version": "1.0.12", @@ -22817,18 +23009,22 @@ "version": "1.4.2", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" + }, + "d3-transition": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.3.2.tgz", + "integrity": "sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==", + "requires": { + "d3-color": "1", + "d3-dispatch": "1", + "d3-ease": "1", + "d3-interpolate": "1", + "d3-selection": "^1.1.0", + "d3-timer": "1" + } } } }, - "d3-chord": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.6.tgz", - "integrity": "sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==", - "requires": { - "d3-array": "1", - "d3-path": "1" - } - }, "d3-collection": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", @@ -22839,14 +23035,6 @@ "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz", "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==" }, - "d3-contour": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-1.3.2.tgz", - "integrity": "sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==", - "requires": { - "d3-array": "^1.1.1" - } - }, "d3-dispatch": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz", @@ -22868,58 +23056,16 @@ } } }, - "d3-dsv": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.2.0.tgz", - "integrity": "sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g==", - "requires": { - "commander": "2", - "iconv-lite": "0.4", - "rw": "1" - } - }, "d3-ease": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.7.tgz", "integrity": "sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ==" }, - "d3-fetch": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.2.0.tgz", - "integrity": "sha512-yC78NBVcd2zFAyR/HnUiBS7Lf6inSCoWcSxFfw8FYL7ydiqe80SazNwoffcqOfs95XaLo7yebsmQqDKSsXUtvA==", - "requires": { - "d3-dsv": "1" - } - }, - "d3-force": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz", - "integrity": "sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==", - "requires": { - "d3-collection": "1", - "d3-dispatch": "1", - "d3-quadtree": "1", - "d3-timer": "1" - } - }, "d3-format": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz", "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==" }, - "d3-geo": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.12.1.tgz", - "integrity": "sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg==", - "requires": { - "d3-array": "1" - } - }, - "d3-hierarchy": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz", - "integrity": "sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==" - }, "d3-interpolate": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz", @@ -22933,41 +23079,26 @@ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" }, - "d3-polygon": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.6.tgz", - "integrity": "sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ==" - }, - "d3-quadtree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.7.tgz", - "integrity": "sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==" - }, - "d3-random": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.2.tgz", - "integrity": "sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ==" - }, "d3-scale": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz", - "integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", + "integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==", "requires": { - "d3-array": "^1.2.0", - "d3-collection": "1", - "d3-format": "1", - "d3-interpolate": "1", - "d3-time": "1", - "d3-time-format": "2" - } - }, - "d3-scale-chromatic": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz", - "integrity": "sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg==", - "requires": { - "d3-color": "1", - "d3-interpolate": "1" + "d3-array": "^2.3.0", + "d3-format": "1 - 2", + "d3-interpolate": "1.2.0 - 2", + "d3-time": "^2.1.1", + "d3-time-format": "2 - 3" + }, + "dependencies": { + "d3-time": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz", + "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==", + "requires": { + "d3-array": "2" + } + } } }, "d3-selection": { @@ -22989,11 +23120,11 @@ "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" }, "d3-time-format": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.3.0.tgz", - "integrity": "sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz", + "integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==", "requires": { - "d3-time": "1" + "d3-time": "1 - 2" } }, "d3-timer": { @@ -23002,23 +23133,15 @@ "integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==" }, "d3-transition": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.3.2.tgz", - "integrity": "sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", "requires": { - "d3-color": "1", - "d3-dispatch": "1", - "d3-ease": "1", - "d3-interpolate": "1", - "d3-selection": "^1.1.0", - "d3-timer": "1" - }, - "dependencies": { - "d3-selection": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", - "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" - } + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" } }, "d3-voronoi": { @@ -23027,21 +23150,25 @@ "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==" }, "d3-zoom": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.8.3.tgz", - "integrity": "sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", "requires": { - "d3-dispatch": "1", - "d3-drag": "1", - "d3-interpolate": "1", - "d3-selection": "1", - "d3-transition": "1" + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" }, "dependencies": { - "d3-selection": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", - "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" + "d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + } } } }, @@ -24090,9 +24217,9 @@ "dev": true }, "fast-xml-parser": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.1.2.tgz", - "integrity": "sha512-CDYeykkle1LiA/uqQyNwYpFbyF6Axec6YapmpUP+/RHWIoR1zKjocdvNaTsxCxZzQ6v9MLXaSYm9Qq0thv0DHg==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz", + "integrity": "sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==", "requires": { "strnum": "^1.0.5" } @@ -24479,7 +24606,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true }, "has-property-descriptors": { @@ -24698,6 +24825,7 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } @@ -24910,6 +25038,11 @@ "side-channel": "^1.0.4" } }, + "internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" + }, "ip": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", @@ -25221,9 +25354,9 @@ "dev": true }, "istanbul-lib-instrument": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz", - "integrity": "sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, "requires": { "@babel/core": "^7.12.3", @@ -25492,11 +25625,6 @@ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, - "lodash.assign": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", - "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=" - }, "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -26144,9 +26272,9 @@ } }, "ngx-markdown-editor": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ngx-markdown-editor/-/ngx-markdown-editor-5.3.0.tgz", - "integrity": "sha512-IeGqtvq+jDMjGGnFFdHhEPphx+jM27EdvcJ3IboCQlXzolpe10JUdCCe53T6TgFe2MFvegVlaVuo44Dcve5IZw==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ngx-markdown-editor/-/ngx-markdown-editor-5.3.1.tgz", + "integrity": "sha512-RXOq+GSc9oBGaBBU4LbAqo3wcIWGXpGD3y8/G4mqbKEM0ANZAaj6EvPPkhaUffNN6KBwStYg0uglxLA3dKuuQg==", "requires": { "tslib": "^2.3.0" } @@ -27379,11 +27507,6 @@ "queue-microtask": "^1.2.2" } }, - "rw": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" - }, "rxjs": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", @@ -27412,7 +27535,8 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true }, "safevalues": { "version": "0.3.4", @@ -28050,6 +28174,11 @@ "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", "dev": true }, + "taira": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/taira/-/taira-3.2.2.tgz", + "integrity": "sha512-Uu06/5JrIRdokVSnbjCVwtmTXYMMNGwRYvWEYZhkB1WGcOinL5Ikr01X6MrUTqA/BU0dGWoIE1l5Ghr1Jq2QYA==" + }, "tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", diff --git a/package.json b/package.json index 4cbd7190..1f0bafa6 100644 --- a/package.json +++ b/package.json @@ -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" } -} \ No newline at end of file +} diff --git a/proxy.config.js b/proxy.config.js index 01f9bef0..ac23c46f 100644 --- a/proxy.config.js +++ b/proxy.config.js @@ -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 = { diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 3a9d5a42..b2736c22 100755 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -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( diff --git a/src/app/app.constants.ts b/src/app/app.constants.ts index 077e6c2a..02f7237b 100755 --- a/src/app/app.constants.ts +++ b/src/app/app.constants.ts @@ -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; diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 9dfbd21b..6e8de498 100755 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -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) }, diff --git a/src/app/business-logic/api-services/organization.service.ts b/src/app/business-logic/api-services/organization.service.ts index 3c63c5ab..a4de1475 100644 --- a/src/app/business-logic/api-services/organization.service.ts +++ b/src/app/business-logic/api-services/organization.service.ts @@ -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 { + 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(`${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 diff --git a/src/app/business-logic/api-services/pipelines.service.ts b/src/app/business-logic/api-services/pipelines.service.ts index ab939491..a2ad63af 100644 --- a/src/app/business-logic/api-services/pipelines.service.ts +++ b/src/app/business-logic/api-services/pipelines.service.ts @@ -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 { + 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(`${this.basePath}/pipelines.delete_runs`, + request, + { + withCredentials: this.configuration.withCredentials, + headers: headers, + observe: observe, + reportProgress: reportProgress + } + ); + } + /** * diff --git a/src/app/business-logic/api-services/projects.service.ts b/src/app/business-logic/api-services/projects.service.ts index 58ef5bc9..0e48fe65 100644 --- a/src/app/business-logic/api-services/projects.service.ts +++ b/src/app/business-logic/api-services/projects.service.ts @@ -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 { + 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(`${this.basePath}/projects.get_user_names`, + request, + { + withCredentials: this.configuration.withCredentials, + headers: headers, + observe: observe, + reportProgress: reportProgress + } + ); + } + + /** * * Moves all the source project\'s contents to the destination project and remove the source project diff --git a/src/app/business-logic/model/organization/fieldMapping.ts b/src/app/business-logic/model/organization/fieldMapping.ts new file mode 100644 index 00000000..b99edee5 --- /dev/null +++ b/src/app/business-logic/model/organization/fieldMapping.ts @@ -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}[]; +} diff --git a/src/app/business-logic/model/organization/organizationPrepareDownloadForGetAllRequest.ts b/src/app/business-logic/model/organization/organizationPrepareDownloadForGetAllRequest.ts new file mode 100644 index 00000000..a5ef7b0e --- /dev/null +++ b/src/app/business-logic/model/organization/organizationPrepareDownloadForGetAllRequest.ts @@ -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; + /** + * 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; +} +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 + } +} diff --git a/src/app/business-logic/model/organization/organizationPrepareDownloadForGetAllResponse.ts b/src/app/business-logic/model/organization/organizationPrepareDownloadForGetAllResponse.ts new file mode 100644 index 00000000..4af57c2f --- /dev/null +++ b/src/app/business-logic/model/organization/organizationPrepareDownloadForGetAllResponse.ts @@ -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; +} diff --git a/src/app/business-logic/model/pipelines/pipelinesDeleteRunsRequest.ts b/src/app/business-logic/model/pipelines/pipelinesDeleteRunsRequest.ts new file mode 100644 index 00000000..f649cffe --- /dev/null +++ b/src/app/business-logic/model/pipelines/pipelinesDeleteRunsRequest.ts @@ -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; + /** + * Pipeline project ids. When deleting at least one run should be left + */ + project: string; +} diff --git a/src/app/business-logic/model/pipelines/pipelinesDeleteRunsResponse.ts b/src/app/business-logic/model/pipelines/pipelinesDeleteRunsResponse.ts new file mode 100644 index 00000000..bb49a8ea --- /dev/null +++ b/src/app/business-logic/model/pipelines/pipelinesDeleteRunsResponse.ts @@ -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; + failed?: Array; +} diff --git a/src/app/business-logic/model/pipelines/pipelinesDeleteRunsResponseError.ts b/src/app/business-logic/model/pipelines/pipelinesDeleteRunsResponseError.ts new file mode 100644 index 00000000..0714a2d9 --- /dev/null +++ b/src/app/business-logic/model/pipelines/pipelinesDeleteRunsResponseError.ts @@ -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; + msg?: string; + data?: object; +} diff --git a/src/app/business-logic/model/pipelines/pipelinesDeleteRunsResponseFailed.ts b/src/app/business-logic/model/pipelines/pipelinesDeleteRunsResponseFailed.ts new file mode 100644 index 00000000..b235f494 --- /dev/null +++ b/src/app/business-logic/model/pipelines/pipelinesDeleteRunsResponseFailed.ts @@ -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; +} diff --git a/src/app/business-logic/model/pipelines/pipelinesDeleteRunsResponseSucceeded.ts b/src/app/business-logic/model/pipelines/pipelinesDeleteRunsResponseSucceeded.ts new file mode 100644 index 00000000..f19acc83 --- /dev/null +++ b/src/app/business-logic/model/pipelines/pipelinesDeleteRunsResponseSucceeded.ts @@ -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; +} diff --git a/src/app/business-logic/model/pipelines/pipelinesStartPipelineRequest.ts b/src/app/business-logic/model/pipelines/pipelinesStartPipelineRequest.ts index a8fdab07..ca6d2dac 100644 --- a/src/app/business-logic/model/pipelines/pipelinesStartPipelineRequest.ts +++ b/src/app/business-logic/model/pipelines/pipelinesStartPipelineRequest.ts @@ -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; } diff --git a/src/app/business-logic/model/pipelines/pipelinesStartPipelineRequestArgs.ts b/src/app/business-logic/model/pipelines/pipelinesStartPipelineRequestArgs.ts new file mode 100644 index 00000000..1e82e0da --- /dev/null +++ b/src/app/business-logic/model/pipelines/pipelinesStartPipelineRequestArgs.ts @@ -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; +} diff --git a/src/app/business-logic/model/projects/projectsDeleteRequest.ts b/src/app/business-logic/model/projects/projectsDeleteRequest.ts index 2e3815f1..fe846c97 100644 --- a/src/app/business-logic/model/projects/projectsDeleteRequest.ts +++ b/src/app/business-logic/model/projects/projectsDeleteRequest.ts @@ -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; } diff --git a/src/app/business-logic/model/projects/projectsGetAllExRequest.ts b/src/app/business-logic/model/projects/projectsGetAllExRequest.ts index bb3e7661..4f4dac7e 100644 --- a/src/app/business-logic/model/projects/projectsGetAllExRequest.ts +++ b/src/app/business-logic/model/projects/projectsGetAllExRequest.ts @@ -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; - /** - * 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; - /** - * System tags list used to filter results. Prepend \'-\' to system tag name to indicate exclusion - */ - system_tags?: Array; - /** - * 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; - /** - * 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; - _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; - /** - * 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; + /** + * 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; + /** + * System tags list used to filter results. Prepend \'-\' to system tag name to indicate exclusion + */ + system_tags?: Array; + /** + * 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; + /** + * 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; + _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; + /** + * 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; } + + 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 + }; } diff --git a/src/app/business-logic/model/projects/projectsGetUserNamesRequest.ts b/src/app/business-logic/model/projects/projectsGetUserNamesRequest.ts new file mode 100644 index 00000000..ddb8d075 --- /dev/null +++ b/src/app/business-logic/model/projects/projectsGetUserNamesRequest.ts @@ -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; + /** + * 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 + } +} diff --git a/src/app/business-logic/model/projects/projectsGetUserNamesResponse.ts b/src/app/business-logic/model/projects/projectsGetUserNamesResponse.ts new file mode 100644 index 00000000..589681ce --- /dev/null +++ b/src/app/business-logic/model/projects/projectsGetUserNamesResponse.ts @@ -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; +} diff --git a/src/app/business-logic/model/projects/projectsGetUserNamesResponseUsers.ts b/src/app/business-logic/model/projects/projectsGetUserNamesResponseUsers.ts new file mode 100644 index 00000000..08a52532 --- /dev/null +++ b/src/app/business-logic/model/projects/projectsGetUserNamesResponseUsers.ts @@ -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; +} diff --git a/src/app/business-logic/model/projects/projectsValidateDeleteResponse.ts b/src/app/business-logic/model/projects/projectsValidateDeleteResponse.ts index a0788ae4..645eaf74 100644 --- a/src/app/business-logic/model/projects/projectsValidateDeleteResponse.ts +++ b/src/app/business-logic/model/projects/projectsValidateDeleteResponse.ts @@ -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; } diff --git a/src/app/business-logic/model/tasks/tasksEnqueueManyResponse.ts b/src/app/business-logic/model/tasks/tasksEnqueueManyResponse.ts index 9ed0e637..2460581b 100644 --- a/src/app/business-logic/model/tasks/tasksEnqueueManyResponse.ts +++ b/src/app/business-logic/model/tasks/tasksEnqueueManyResponse.ts @@ -17,4 +17,8 @@ import { TasksResetManyResponseFailed } from '././tasksResetManyResponseFailed'; export interface TasksEnqueueManyResponse { succeeded?: Array; failed?: Array; + /** + * Returns Trueif there are workers or autscalers working with the queue + */ + queue_watched?: boolean; } diff --git a/src/app/business-logic/model/users/usersGetCurrentUserResponse.ts b/src/app/business-logic/model/users/usersGetCurrentUserResponse.ts index d5c4ee08..5e26b14c 100644 --- a/src/app/business-logic/model/users/usersGetCurrentUserResponse.ts +++ b/src/app/business-logic/model/users/usersGetCurrentUserResponse.ts @@ -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; + } diff --git a/src/app/business-logic/model/users/usersGetCurrentUserResponseSettings.ts b/src/app/business-logic/model/users/usersGetCurrentUserResponseSettings.ts new file mode 100644 index 00000000..df23bd20 --- /dev/null +++ b/src/app/business-logic/model/users/usersGetCurrentUserResponseSettings.ts @@ -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; +} diff --git a/src/app/core/actions/users.action.ts b/src/app/core/actions/users.action.ts index 9b34560c..254eafc2 100644 --- a/src/app/core/actions/users.action.ts +++ b/src/app/core/actions/users.action.ts @@ -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; + }>() ); diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index ee56cbce..235e5cbc 100755 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -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 useFactory: userPrefMetaFactory }, {provide: DEFAULT_CURRENCY_CODE, useValue: 'USD'}, + BreadcrumbsService, ], declarations: [], exports: [] diff --git a/src/app/core/effects/projects.effects.ts b/src/app/core/effects/projects.effects.ts index 6c5c845c..d31af3f0 100644 --- a/src/app/core/effects/projects.effects.ts +++ b/src/app/core/effects/projects.effects.ts @@ -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), ]; diff --git a/src/app/core/reducers/recent-tasks-reducer.ts b/src/app/core/reducers/recent-tasks-reducer.ts deleted file mode 100755 index 0bc56843..00000000 --- a/src/app/core/reducers/recent-tasks-reducer.ts +++ /dev/null @@ -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 : >>[], -}; - - -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; - } -} diff --git a/src/app/core/reducers/users.reducer.ts b/src/app/core/reducers/users.reducer.ts index c2b736d8..7ad255ac 100644 --- a/src/app/core/reducers/users.reducer.ts +++ b/src/app/core/reducers/users.reducer.ts @@ -14,6 +14,7 @@ export const usersReducer = createReducer(initUsers, currentUser: action.user, gettingStarted: action.getting_started, activeWorkspace: action.user?.company, + settings: action.settings, userWorkspaces: [action.user?.company], })) ); diff --git a/src/app/core/reducers/view.reducer.ts b/src/app/core/reducers/view.reducer.ts index 900473d0..3bed9143 100644 --- a/src/app/core/reducers/view.reducer.ts +++ b/src/app/core/reducers/view.reducer.ts @@ -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) { diff --git a/src/app/features/dashboard/dumb/search-results-page/search-results-page.component.html b/src/app/features/dashboard/dumb/search-results-page/search-results-page.component.html index dd121dd0..9b4f618e 100755 --- a/src/app/features/dashboard/dumb/search-results-page/search-results-page.component.html +++ b/src/app/features/dashboard/dumb/search-results-page/search-results-page.component.html @@ -1,6 +1,6 @@
-
-
+
+
{{searchTab.label}} ({{resultsCount?.[searchTab.name]}}) @@ -21,7 +21,8 @@ [cardHeight]="getCardHeight()" [showLoadMoreButton]="getResults().length < resultsCount?.[activeLink]" (itemClicked)="projectClicked($event)" - (loadMoreClicked)="loadMoreClicked.emit()"> + (loadMoreClicked)="loadMoreClicked.emit()" + >
diff --git a/src/app/features/dashboard/dumb/search-results-page/search-results-page.component.scss b/src/app/features/dashboard/dumb/search-results-page/search-results-page.component.scss index 6bf1cdf6..69647f82 100755 --- a/src/app/features/dashboard/dumb/search-results-page/search-results-page.component.scss +++ b/src/app/features/dashboard/dumb/search-results-page/search-results-page.component.scss @@ -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; } diff --git a/src/app/features/datasets/datasets-routing.module.ts b/src/app/features/datasets/datasets-routing.module.ts index e948f083..0d9ffa30 100644 --- a/src/app/features/datasets/datasets-routing.module.ts +++ b/src/app/features/datasets/datasets-routing.module.ts @@ -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} }, { diff --git a/src/app/features/datasets/datasets.module.ts b/src/app/features/datasets/datasets.module.ts index c33139f1..622ae1d2 100644 --- a/src/app/features/datasets/datasets.module.ts +++ b/src/app/features/datasets/datasets.module.ts @@ -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: [ diff --git a/src/app/features/datasets/nested-datasets-page/nested-datasets-page.component.html b/src/app/features/datasets/nested-datasets-page/nested-datasets-page.component.html new file mode 100644 index 00000000..e189abf1 --- /dev/null +++ b/src/app/features/datasets/nested-datasets-page/nested-datasets-page.component.html @@ -0,0 +1,57 @@ + + + + +
+
+
NO DATASETS TO SHOW
+
Run your first dataset to see it displayed here + or generate + example + +
+ +
+ +
+ + + + + + + + + + + + diff --git a/src/app/features/datasets/nested-datasets-page/nested-datasets-page.component.ts b/src/app/features/datasets/nested-datasets-page/nested-datasets-page.component.ts new file mode 100644 index 00000000..b5194fab --- /dev/null +++ b/src/app/features/datasets/nested-datasets-page/nested-datasets-page.component.ts @@ -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 []; + } + +} diff --git a/src/app/features/delete-entity/delete-dialog.effects.ts b/src/app/features/delete-entity/delete-dialog.effects.ts index 3416a5d9..cd22ceee 100755 --- a/src/app/features/delete-entity/delete-dialog.effects.ts +++ b/src/app/features/delete-entity/delete-dialog.effects.ts @@ -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, - 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. } diff --git a/src/app/features/experiments/experiments.module.ts b/src/app/features/experiments/experiments.module.ts index 2e0e052f..194366c1 100755 --- a/src/app/features/experiments/experiments.module.ts +++ b/src/app/features/experiments/experiments.module.ts @@ -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, diff --git a/src/app/features/experiments/shared/experiment-execution.model.ts b/src/app/features/experiments/shared/experiment-execution.model.ts index 34755da6..7ad2ecbd 100755 --- a/src/app/features/experiments/shared/experiment-execution.model.ts +++ b/src/app/features/experiments/shared/experiment-execution.model.ts @@ -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; diff --git a/src/app/features/nested-project-view/feature-nested-project-view.module.ts b/src/app/features/nested-project-view/feature-nested-project-view.module.ts deleted file mode 100644 index aa5df730..00000000 --- a/src/app/features/nested-project-view/feature-nested-project-view.module.ts +++ /dev/null @@ -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 { } diff --git a/src/app/features/nested-project-view/nested-project-view-page-extended/nested-project-view-page-extended.component.html b/src/app/features/nested-project-view/nested-project-view-page-extended/nested-project-view-page-extended.component.html deleted file mode 100644 index 919e7510..00000000 --- a/src/app/features/nested-project-view/nested-project-view-page-extended/nested-project-view-page-extended.component.html +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/app/features/nested-project-view/nested-project-view-page-extended/nested-project-view-page-extended.component.scss b/src/app/features/nested-project-view/nested-project-view-page-extended/nested-project-view-page-extended.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/src/app/features/nested-project-view/nested-project-view-page-extended/nested-project-view-page-extended.component.spec.ts b/src/app/features/nested-project-view/nested-project-view-page-extended/nested-project-view-page-extended.component.spec.ts deleted file mode 100644 index 299932ab..00000000 --- a/src/app/features/nested-project-view/nested-project-view-page-extended/nested-project-view-page-extended.component.spec.ts +++ /dev/null @@ -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; - - 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(); - }); -}); diff --git a/src/app/features/nested-project-view/nested-project-view-page-extended/nested-project-view-page-extended.component.ts b/src/app/features/nested-project-view/nested-project-view-page-extended/nested-project-view-page-extended.component.ts deleted file mode 100644 index 5de13612..00000000 --- a/src/app/features/nested-project-view/nested-project-view-page-extended/nested-project-view-page-extended.component.ts +++ /dev/null @@ -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 { - -} diff --git a/src/app/features/nested-project-view/nested-project-view-utils.ts b/src/app/features/nested-project-view/nested-project-view-utils.ts deleted file mode 100644 index 981152b3..00000000 --- a/src/app/features/nested-project-view/nested-project-view-utils.ts +++ /dev/null @@ -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; - } -}; - - diff --git a/src/app/features/projects/projects-page.utils.ts b/src/app/features/projects/projects-page.utils.ts index 896dfb45..e7be7077 100755 --- a/src/app/features/projects/projects-page.utils.ts +++ b/src/app/features/projects/projects-page.utils.ts @@ -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); diff --git a/src/app/features/projects/projects.actions.ts b/src/app/features/projects/projects.actions.ts index e69de29b..a8111e6b 100644 --- a/src/app/features/projects/projects.actions.ts +++ b/src/app/features/projects/projects.actions.ts @@ -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}>() +); diff --git a/src/app/features/projects/projects.effect.ts b/src/app/features/projects/projects.effect.ts index 5b1cbee0..73c44d5c 100644 --- a/src/app/features/projects/projects.effect.ts +++ b/src/app/features/projects/projects.effect.ts @@ -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 + }, } }) ]) diff --git a/src/app/features/projects/projects.reducer.ts b/src/app/features/projects/projects.reducer.ts index 9f44dd41..2e5703ca 100644 --- a/src/app/features/projects/projects.reducer.ts +++ b/src/app/features/projects/projects.reducer.ts @@ -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, diff --git a/src/app/features/projects/shared/projects-shared.module.ts b/src/app/features/projects/shared/projects-shared.module.ts index b4bb6c21..8f7ad2a2 100755 --- a/src/app/features/projects/shared/projects-shared.module.ts +++ b/src/app/features/projects/shared/projects-shared.module.ts @@ -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({ diff --git a/src/app/features/settings/containers/admin/user-credentials/user-credentials.component.html b/src/app/features/settings/containers/admin/user-credentials/user-credentials.component.html index d495ba9d..0df621d7 100644 --- a/src/app/features/settings/containers/admin/user-credentials/user-credentials.component.html +++ b/src/app/features/settings/containers/admin/user-credentials/user-credentials.component.html @@ -12,6 +12,6 @@ Create new credentials + (click)="createCredential()"> Create new credentials
diff --git a/src/app/webapp-common/assets/fonts/trains-icons.scss b/src/app/webapp-common/assets/fonts/trains-icons.scss index 4f07fd33..3b32b6c9 100644 --- a/src/app/webapp-common/assets/fonts/trains-icons.scss +++ b/src/app/webapp-common/assets/fonts/trains-icons.scss @@ -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; diff --git a/src/app/webapp-common/assets/fonts/trains.ttf b/src/app/webapp-common/assets/fonts/trains.ttf index d4617e4d426a077abd200016e72691c752ac6f91..50eed6a4166f453fec54f8ffc1a0086bb314c702 100644 GIT binary patch delta 4391 zcmaJ_e{2)i9e>Xk`~2H+Y~T6pOYHO6cS%ef|8^PzNhpv&(m)^%5E4Ql1PH&WtAAbsM9kgZ@(04py}>o6zVwR86R&OhVO)HbnS?t`%dIN~?~xh@!S4$G*>wQ)lby z=-#{Ud!O%n=kI;KpZ9eR{+qfnMI9D0(bp7&Fpm%#-8!&$1o~324@0lry7S?od%vyf z0{a<+u3g)TKAEJ~b`iMS+F zK1mLQa3Y=?0dKYaz1$8ElF-(l75cYhf^+h-@=70 zt&fJ@XPvNWs5Vj6=26a>4DX5k$wX~vsogGmC{2lS!WLv(22byEd>zd^hQid3r~sOW z)f6?pu(_g!h>I zMw7`SSFVy}?=g?1wAAA9%JQm8+0*2Xb9QMsAW6pp!-3ZcDmQ-2QwkqX-pAAAio5KA zVS^M108BFajp+1C%Xa-NqekPvxN%^d899M<^H353Blt89tDT7n(PY9ZV=+&zavT$_ zd74>cvtv{u%vNBQiHNGKg336qOf^}?O(|1p&RWK0yScJ5ZZgf4S-G^^)#P>qyE*H0 z9+=KdO`|OF$>MG+$61xhT-U8aSps%Jx*>EgTLMXe43H8lQ7=4R5y7!NM{Xdzj3qgQ zWk`e>I_I8>#T7UpH{$=3LKz%u#B7RhzQ~uagSGU^fEO%oazd#V$DyxF&`YFoT+quoA&Zq+&#X$i0XQ z5=F#jjzOX7W>a4#ODJ>YN+8mfNVJ6m**;Tqb)u~;p`-~>2xoAPIXT8X8V*eECwKd& zrq-Jl<$%@A=Jk_-Fma#$UEagW;Yq?ra)ELK_k-~8L1aO2P|Rh0CN5E;Op7*IOvG$4 z2Nj&26r)j5)eY^B9~oFZ_=K(LkrPc+FzTPq`Xext_D843zy2EUi>^`Dtbyp5XE!h) z4oN5u5n74*(024gbO0SeFQSv^3_`XdKC9zW5Mt=CXPEgonxqS?Bq5^vv&w^prJXE;%1bILrX+bIgGIc`NfUcWGz>~ylt zDUH^w$H-8s)2wrpYt%uz&7h)6D4jv2G8nn#EUi~jSg&z3I7$SD(okBL)$Y-7VhLrk zSd3UDIB6flS8m#?HfrvRM3*}pPPP-i?y@JsYKtzbH>;S8%4&F6qbqlem1wYNg_wa} zjZ%}Uec?{=wnDwD+prCHwGR zC5>M;Vxuzpwuv&n*ZoUn8Z4qk8dx_7#ZwTuL5-H6PP7~S2%!v!US5rIQpA#TjT~zW z$@tvD$(jQ?ta)Hww-(?WP%cJ(-->g9BAj&@(5gT<93TV?L?SC@IAy(u2*vX=`94Qp zm{xAiU;~7@1Q9IISv7W$_#Xr<1d&wUCF3}jvyK)rhQ?8XQ7(cG0w{Bkfbt&Zs`Aofr>%m*?l=AYZ18D@1U{ zxFi8F?xWqh23Atw;uaffG>V^;N)+#y9` zil?T>bH~HDKS~yfnsp|Vt{R$*?$^230h>csNbOu1J%g)Gou+%m;uhXi9#MQ^GhHl;C{8$TI+JL ztjkqvwW^h~{S-|*EEa1lIPZSOq0OqYS_gCgykN{~u{da&noiHP0q|cQ%Iun%!lHO8 z-PDw3;6wUoIJ`!Zy^JWXi^uP~FCJefii}s5)`Y{+G%}DJe zrn|8zjY@D3G8LU>iCv7Zi zv$L$dn>1jslao|BJ)LbC9BfgMo7^ZsvU!}1of-$xQGw`mfaqx9<$(%e6AoBoR<%PN zNT^i^v?|+-`#PqVVhgq?Ur^eQZ=6+bNrTY&FZLbpw={f-3{ddG z=i1cpszN`}dNYO16XQ2mlN;1ywRi&s;P2I5=_g zR&C~@KCVAx2pKLJ4;WuGDy9naH$W=q&OJK~Hx zmpb=5|IF#RwcIP*ZI|9P;yUH-cK?A-@+174{N3`=@;eo!74;RHD$aXKJiKSA=e+k9 z-c#Q5-b>zV-p>Uj@Iq4P5=MpRGQxSE#&_1Ih>RE#Tg8iht$&`s*Z->jlfb^fCCMzc zOFN}oa*uqpvZ?YPRhO#ktDmdB5?mR)5WG6CdfwTZXw8wDi#6A4?u2%S7l*He??etp z&O||4jW?^M~f2X|OlcHymg>+3au5Jie%O(S=2y z-oJlw*WyEqUs-&6Nym~SOJ7*}X-l$Yq~%0wY3oqyrDgtQZ!WvL{Gm3c&EIyY?Q(m( z{mcWeJaDa}y<=m?TOC(BZm(!sacE`b%HEZCJDJY*&Xb*QbtzH-*J?%Xot`Dz2y#8wM#@_3F z!+mdTXy0(X-`GFc|IWtojjwL}+d%!mlbc#M9o_8Ryma%(=2L_E!3$eTw;b4Vd1%wn zzM*qNceXCtc5>S{+ef#b8?G3h+~MCbyyKmnl{?q&>ezL2w`=#gJ!2!KBP&P9zdV3k W*sRfQKwiQnq{{uD;j`P%;{O1dY-=q5 delta 7323 zcmcIpYj7Lab-rhx0CpG81wc{+NdP230ThV^_$E!#dQcM0hbWoUn<6bsrWMJgDOOQc z)yv9`6E})Wld+m8YHTO2r;aje5+#nK@nn>Y;`oQFaW&>Nb~Ki&I&7PfHJ(=XBpnOx zxw`-#GD%rZi_v0t@4dM9@_y%h-#JJBF~xr6eRe#km%sQ2LLi?IGWx*4!C|~R@qHZc zmIwC#`QG8fuRMb9-yr1L@xeU<_b-3u#@`ciy%_te2eCofVS51Ici?;R;DOP@6+azw z;rkdNTw?#w?tyDxK68eU|M~$TOx}Tk!^5yb=qKdH>o~sXp@9Q?a-O}q_Bm|0hKn2- z9y&NmSnMJn4L`@XV&Ey_( zlsrKQABsdv${|_~Rh88>O^{0Z`~~3m`20RW6hu3OAw+Lp)oAAna0DF}WsM(&p)wK< z3D}Fxtf&g&UX@j){Gk>YTOJ53r`PBPkqec!StV+0fIK0LQsFAZ@lw;}y;D3uD)E9&u`cjdS$pKNRUa#dx0eWkm=m;P!=>GH0U zNI|rOeJ~j>8@+4ga9r^!ue#*?g2Y%IjJorcXyWGaKxrvmAL@-Iqc}C+)g|(wsJW4V zMYgn=3vR$waeo#?wgl>0D#PJQdYw&r|Cp?tB4ccvnId)MAfi@)2P%jf9xtB1W*mMq z7^jcz4roL)B2n)|cSNTL*$nvx097Us6aft}pkvJ6)@X2v#}6k9qRjd7@_Zw{hTy7Q zdA@`wIAup&sVA%)R2&Xv$L)vEiQq3eSrjFn+k%tgRG|i(F}H73sNSam18T$#4kfIX z);W|BCG1th$`hO*7$fU_2YtD_R^l%bb4S+Nab&TIBY8>0O`fe>f8f!xJ1;tI`4-Yc z2z#3CAq8lf6{MX!N&b%fj9kQnntZpaKxeD`Fk?;{+Ex^gN~<-DRZFNEJ<1r+j#{V# zPc3L@%UTpq5&vmbcvAFU4sj^Q0~bVZ*feO(i|2~}G?7l?^QRDqL}eXBP4le-(|~n9 z&X}3p+y<_Vi-v0|0#&KgadIDt_&4Xu3kCUQM zlf_Rr{G&>vpr;Hc@oSW)5HjjTXnyqW)%-XKH2@RLswmD|hwNpNQ3mSQht<&T4+FmGfp`gs+?5^diuzL^Zna{{#&)M|LV7M zU@haln(4fE?7Qqz+`@dUTV{A4LMXr4zZwkBj- zKd@{!Zjz!={wM1jZc0c>R09HiuC4#~b5i9d&BHex%=LzEG5JpCK!&z3Vh zqvK!E&9dM}b!bAN5cq#(z}SAWBE1DYgTP zs@ZMCY-Z}&ZDckX%Px9ar+ss;sq7!Z7{ZF=DmhGURjfAq%`11JcDE5b8m#DMwDK@{ ziCiF8U>U@q7FyBdj94J*#juX52@UP=k!o8eFrYRN&IB3Aw|;!okYK64CF+m@YYdG; zX~UQ$mBc2ha`lu+X}E)S?Dt~isOd(`W31t)kvoPq1}CF}8vQ0Qo3chHrfiEwpUBFb z`JU;=P0lCZ8KcEn6QTJ=8#Fer&k<+3_i9S*h9b|A<(c4$%5pyV7tv$t4D;m}-&hqm z3&$&Xv791`79>q1SPpqdc8XRVC0BKDR+e|5emXf*7wswvrq4MUXBeUX?ll8YT*z@2 z{wcxAvq?auW^yeO12=z|0TLBJuhXo`zYgbtYM5|S#jUE}CzSP~Uz_n~1SQBr{1J=U# z3l>l;0<30vm$aH;B@5Cu6VCE!96ifoGpzXx=;a}d&1RxE#bbmC62oGYz-D5%F9tU! z1xZ*uNC>FNq#`oun+B7r7L_TpcVZo&vtqa^<;mEb2g4M@?))ilILe~OQOASPMVM*CjN)#^xjDF00EalQ9_oK~ z;7P{H3s#*O?qwuVurZ?z!|gj5n}CnZdzrXy9`&4o&wv$;=#<(8>ndD zgD0L97C@a_tS`-FX=5GepDk~@jnWlXQwghQng!~oJ{{{oC@1j}FPs*u9QMo~3nZpl8 zHiq$znxH9m6{QL4^Ydu}&1ELH^BT5;`2C%TOYa9mvQGWH^5-@~0v-2cu8l!uWFu|E;(5t&LBEm$@l9&8e5;tTvilW32J6 zk(!~`8wAb5FlN+>2aM#}o=N0}F&IYr-d?mN`A{~NH*z@4>=h%2^9BOm!nd|Io&Ybh zG!1w&6%vJ%l0lt(5!35Q@e+~lIX&TPNKV6Q| zj55u)T`F`4P30%uYP)dP3Z~*Nq223-ua9%(K3_SNc6qx~5bFvvzv|ez^KeIBUq_t2(N?q> z8mMwc@~;p*Mh3_*YWs8KCs6f#y*HUS1QI9o~)#?k?%~ljs17vyiTlJ+BV>5ibAO)>TVqe|#vC@xwE<^45Y zFTWX}Js;G?YofQ&R{k`8-~{ki=Iu`i=w!+);VlU0 zomE4ZmdmG`ntEDW&-3iM-k!C67Dch_$2w31F{M6d%(`+cJQuv#a0x0yFv3>B`9~({kprPRjot*FUiJ+I}EykZ#B? z$mf+JrNxo&IO6!R^KsWX*W2#5Jx4v))h2JRZ<}x2AMyA4Cvtjo26LXzc_+6l_ekz* zdA_`|yotO^%MLGlGk;6|ME>PKLEuC|PC>F@N5NwS(}j`3rozF(>7p}5mx`u~ZUmX2 zI~WQ!1-Aqb1y2ND(Sy^W($Kr%qHt|^LwH{?SKL&5U-3loJ0(RW-;Y=#t0TuE--(t+ z$D^;89$M~>Sz^PnH{x>q@p!t-S=L{6W5wW#*UGu_obs~r=MzUXSu4|iu3b;&B|DQR zYbI*TYuD9|)PAS-joLrd_tjr)C~r8`aJ{juak%kZ%hkKL-L1c}vUO#8RcO`AtN(iS zh1HkW#MV5v=C#&KYeQ@IuYFd^F-%IUCypp*P*Tp8~Qd(bSJypyN`8W?fz(E<;L@y$~LXsG`{KTz3RQ~_kQKx z_j-Iitv%;@PxW5fY}uULd}{OW^)0PiCbl}awr?HYc5Hjz_PyI*-QnDEVrS0IXLnxf zJGLul*UDWd`$_*x{qGD21H%L7?|bCF*LU~sKDzsb-P8BC+@Idly64=Ui+l6-cJ4hn z$PI2BeEq?^2M_II_8r~#%P*YSfB3+)p|auehet-feXwuz*q`+s!U6*|B9MhXQiNr8 P{4mD()); export const getSample = createAction('[App] getSample', props<{ diff --git a/src/app/webapp-common/clearml-applications/report-widgets/src/app/app.component.html b/src/app/webapp-common/clearml-applications/report-widgets/src/app/app.component.html index 21d0b2f8..8668b937 100644 --- a/src/app/webapp-common/clearml-applications/report-widgets/src/app/app.component.html +++ b/src/app/webapp-common/clearml-applications/report-widgets/src/app/app.component.html @@ -22,6 +22,7 @@ [showLoaderOnDraw]="false" [identifier]="'lala'" [width]="400" + [xAxisType]="xaxis" [isCompare]="true" [noMargins]="true" [legendConfiguration]="{noTextWrap: true}" diff --git a/src/app/webapp-common/clearml-applications/report-widgets/src/app/app.component.ts b/src/app/webapp-common/clearml-applications/report-widgets/src/app/app.component.ts index a48f43bf..7d9cc551 100644 --- a/src/app/webapp-common/clearml-applications/report-widgets/src/app/app.component.ts +++ b/src/app/webapp-common/clearml-applications/report-widgets/src/app/app.component.ts @@ -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, + 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 => `¶ms=${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; + }, {}); + } } diff --git a/src/app/webapp-common/clearml-applications/report-widgets/src/app/app.effects.ts b/src/app/webapp-common/clearml-applications/report-widgets/src/app/app.effects.ts index 08c48244..a2b454a6 100644 --- a/src/app/webapp-common/clearml-applications/report-widgets/src/app/app.effects.ts +++ b/src/app/webapp-common/clearml-applications/report-widgets/src/app/app.effects.ts @@ -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, + 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 => [ diff --git a/src/app/webapp-common/clearml-applications/report-widgets/src/app/app.reducer.ts b/src/app/webapp-common/clearml-applications/report-widgets/src/app/app.reducer.ts index 1f0b0904..6cabe0b8 100644 --- a/src/app/webapp-common/clearml-applications/report-widgets/src/app/app.reducer.ts +++ b/src/app/webapp-common/clearml-applications/report-widgets/src/app/app.reducer.ts @@ -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; diff --git a/src/app/webapp-common/clearml-applications/report-widgets/src/environments/environment.ts b/src/app/webapp-common/clearml-applications/report-widgets/src/environments/environment.ts index cce89f42..cd3d25cf 100644 --- a/src/app/webapp-common/clearml-applications/report-widgets/src/environments/environment.ts +++ b/src/app/webapp-common/clearml-applications/report-widgets/src/environments/environment.ts @@ -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 = { diff --git a/src/app/webapp-common/clearml-applications/report-widgets/src/styles.scss b/src/app/webapp-common/clearml-applications/report-widgets/src/styles.scss index 414345cd..d1794439 100644 --- a/src/app/webapp-common/clearml-applications/report-widgets/src/styles.scss +++ b/src/app/webapp-common/clearml-applications/report-widgets/src/styles.scss @@ -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 { diff --git a/src/app/webapp-common/common-search/containers/common-search/common-search.component.ts b/src/app/webapp-common/common-search/containers/common-search/common-search.component.ts index ac34e15a..f11d66e9 100644 --- a/src/app/webapp-common/common-search/containers/common-search/common-search.component.ts +++ b/src/app/webapp-common/common-search/containers/common-search/common-search.component.ts @@ -27,7 +27,7 @@ export class CommonSearchComponent implements OnInit { minChars = 3; public regexError: boolean; - constructor(private store: Store, private router: Router, private route: ActivatedRoute, private cdr: ChangeDetectorRef) { + constructor(private store: Store, private router: Router, private route: ActivatedRoute, private cdr: ChangeDetectorRef) { } ngOnInit() { diff --git a/src/app/webapp-common/common-styles.scss b/src/app/webapp-common/common-styles.scss index 7479dab9..ec351bb0 100644 --- a/src/app/webapp-common/common-styles.scss +++ b/src/app/webapp-common/common-styles.scss @@ -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; + } +} diff --git a/src/app/webapp-common/core/actions/projects.actions.ts b/src/app/webapp-common/core/actions/projects.actions.ts index 83a849d2..b5762d68 100755 --- a/src/app/webapp-common/core/actions/projects.actions.ts +++ b/src/app/webapp-common/core/actions/projects.actions.ts @@ -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[]; scrollId: string; loadMore?: boolean }>() ); + +export const downloadForGetAll = createAction( + PROJECTS_PREFIX + ' [downloadForGetAll]', + props<{ prepareId: string}>() +); diff --git a/src/app/webapp-common/core/actions/recent-tasks.actions.ts b/src/app/webapp-common/core/actions/recent-tasks.actions.ts index 9fbd616e..eada46e1 100755 --- a/src/app/webapp-common/core/actions/recent-tasks.actions.ts +++ b/src/app/webapp-common/core/actions/recent-tasks.actions.ts @@ -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 }; - - constructor(tasks: Array) { - 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 })); diff --git a/src/app/webapp-common/core/actions/router.actions.ts b/src/app/webapp-common/core/actions/router.actions.ts index 611c7825..82c018a2 100755 --- a/src/app/webapp-common/core/actions/router.actions.ts +++ b/src/app/webapp-common/core/actions/router.actions.ts @@ -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', diff --git a/src/app/webapp-common/core/actions/tasks.actions.ts b/src/app/webapp-common/core/actions/tasks.actions.ts index baa52328..d862523a 100755 --- a/src/app/webapp-common/core/actions/tasks.actions.ts +++ b/src/app/webapp-common/core/actions/tasks.actions.ts @@ -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 +})); diff --git a/src/app/webapp-common/core/cache/cache.module.ts b/src/app/webapp-common/core/cache/cache.module.ts deleted file mode 100755 index 35dbef24..00000000 --- a/src/app/webapp-common/core/cache/cache.module.ts +++ /dev/null @@ -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 { } diff --git a/src/app/webapp-common/core/cache/entities-cache.service.ts b/src/app/webapp-common/core/cache/entities-cache.service.ts deleted file mode 100755 index aabf67f3..00000000 --- a/src/app/webapp-common/core/cache/entities-cache.service.ts +++ /dev/null @@ -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) { - 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 { - 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) { - const data = this.syncSelector.selectSync(entity.selector); - const shouldRequest = (!data || entity.didTimeExpire()); - - return shouldRequest; - } - -} diff --git a/src/app/webapp-common/core/effects/common-auth.effects.ts b/src/app/webapp-common/core/effects/common-auth.effects.ts index 592bb72e..2d461d9f 100644 --- a/src/app/webapp-common/core/effects/common-auth.effects.ts +++ b/src/app/webapp-common/core/effects/common-auth.effects.ts @@ -25,7 +25,7 @@ export class CommonAuthEffects { constructor( private actions: Actions, private credentialsApi: ApiAuthService, - private store: Store, + private store: Store, private adminService: AdminService, private matDialog: MatDialog ) {} diff --git a/src/app/webapp-common/core/effects/layout.effects.ts b/src/app/webapp-common/core/effects/layout.effects.ts index 6ac18208..2730e734 100755 --- a/src/app/webapp-common/core/effects/layout.effects.ts +++ b/src/app/webapp-common/core/effects/layout.effects.ts @@ -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 = createEffect( () => this.actions.pipe( diff --git a/src/app/webapp-common/core/effects/projects.effects.ts b/src/app/webapp-common/core/effects/projects.effects.ts index 6063a895..f57666cf 100755 --- a/src/app/webapp-common/core/effects/projects.effects.ts +++ b/src/app/webapp-common/core/effects/projects.effects.ts @@ -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, 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) { + 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); + } + })); + } } diff --git a/src/app/webapp-common/core/effects/router.effects.ts b/src/app/webapp-common/core/effects/router.effects.ts index 19d2c952..7f89783f 100755 --- a/src/app/webapp-common/core/effects/router.effects.ts +++ b/src/app/webapp-common/core/effects/router.effects.ts @@ -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(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(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( diff --git a/src/app/webapp-common/core/effects/users.effects.ts b/src/app/webapp-common/core/effects/users.effects.ts index 541e50bd..d7ab039e 100755 --- a/src/app/webapp-common/core/effects/users.effects.ts +++ b/src/app/webapp-common/core/effects/users.effects.ts @@ -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, private errorService: ErrorService + private store: Store, private errorService: ErrorService ) { } diff --git a/src/app/webapp-common/core/interceptors/webapp-interceptor.ts b/src/app/webapp-common/core/interceptors/webapp-interceptor.ts index 6a6fc995..89504f0f 100644 --- a/src/app/webapp-common/core/interceptors/webapp-interceptor.ts +++ b/src/app/webapp-common/core/interceptors/webapp-interceptor.ts @@ -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) { + 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, 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 && ( diff --git a/src/app/webapp-common/core/models/actions.ts b/src/app/webapp-common/core/models/actions.ts deleted file mode 100755 index 6b9b060a..00000000 --- a/src/app/webapp-common/core/models/actions.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {Action} from '@ngrx/store'; - -export interface ISmAction extends Action { - payload?: any; - meta?: ISmActionMeta; // deprecated -} - -export interface ISmActionMeta { - loading?: boolean; -} diff --git a/src/app/webapp-common/core/reducers/projects.reducer.ts b/src/app/webapp-common/core/reducers/projects.reducer.ts index ced1c757..325922a1 100755 --- a/src/app/webapp-common/core/reducers/projects.reducer.ts +++ b/src/app/webapp-common/core/reducers/projects.reducer.ts @@ -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[]; 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 })) ); diff --git a/src/app/webapp-common/core/reducers/recent-tasks-reducer.ts b/src/app/webapp-common/core/reducers/recent-tasks-reducer.ts index b6efb65b..4e5c4277 100755 --- a/src/app/webapp-common/core/reducers/recent-tasks-reducer.ts +++ b/src/app/webapp-common/core/reducers/recent-tasks-reducer.ts @@ -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 : >>[], @@ -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})) +); diff --git a/src/app/webapp-common/core/reducers/router-reducer.ts b/src/app/webapp-common/core/reducers/router-reducer.ts index 27570ad2..619f906c 100755 --- a/src/app/webapp-common/core/reducers/router-reducer.ts +++ b/src/app/webapp-common/core/reducers/router-reducer.ts @@ -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 })), ); diff --git a/src/app/webapp-common/core/reducers/users-reducer.ts b/src/app/webapp-common/core/reducers/users-reducer.ts index c23cc985..31a2f79a 100755 --- a/src/app/webapp-common/core/reducers/users-reducer.ts +++ b/src/app/webapp-common/core/reducers/users-reducer.ts @@ -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); diff --git a/src/app/webapp-common/core/reducers/view.reducer.ts b/src/app/webapp-common/core/reducers/view.reducer.ts index 7c248148..915f8d22 100644 --- a/src/app/webapp-common/core/reducers/view.reducer.ts +++ b/src/app/webapp-common/core/reducers/view.reducer.ts @@ -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; diff --git a/src/app/webapp-common/core/services/get-state.service.ts b/src/app/webapp-common/core/services/get-state.service.ts index bbf030cf..e15997cf 100755 --- a/src/app/webapp-common/core/services/get-state.service.ts +++ b/src/app/webapp-common/core/services/get-state.service.ts @@ -5,7 +5,7 @@ import {take} from 'rxjs/operators'; @Injectable() export class SmGetStateService { - constructor(private store: Store) {} + constructor(private store: Store) {} getState() { let _state: any; diff --git a/src/app/webapp-common/core/services/sync-state-selector.service.ts b/src/app/webapp-common/core/services/sync-state-selector.service.ts index fe019340..111fecf8 100755 --- a/src/app/webapp-common/core/services/sync-state-selector.service.ts +++ b/src/app/webapp-common/core/services/sync-state-selector.service.ts @@ -5,7 +5,7 @@ import {take} from 'rxjs/operators'; @Injectable() export class SmSyncStateSelectorService { - constructor(private store: Store) { + constructor(private store: Store) { } selectSync(selector: Selector) { diff --git a/src/app/webapp-common/dashboard-search/dashboard-search.effects.ts b/src/app/webapp-common/dashboard-search/dashboard-search.effects.ts index eabbd6d2..33e731bd 100644 --- a/src/app/webapp-common/dashboard-search/dashboard-search.effects.ts +++ b/src/app/webapp-common/dashboard-search/dashboard-search.effects.ts @@ -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(); } ) )); diff --git a/src/app/webapp-common/dashboard/common-dashboard.effects.ts b/src/app/webapp-common/dashboard/common-dashboard.effects.ts index 80b55f50..c0ce79c9 100755 --- a/src/app/webapp-common/dashboard/common-dashboard.effects.ts +++ b/src/app/webapp-common/dashboard/common-dashboard.effects.ts @@ -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, + 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, diff --git a/src/app/webapp-common/dashboard/containers/dashboard-experiments/dashboard-experiments.component.ts b/src/app/webapp-common/dashboard/containers/dashboard-experiments/dashboard-experiments.component.ts index 479c78b1..f0c9c967 100644 --- a/src/app/webapp-common/dashboard/containers/dashboard-experiments/dashboard-experiments.component.ts +++ b/src/app/webapp-common/dashboard/containers/dashboard-experiments/dashboard-experiments.component.ts @@ -15,7 +15,7 @@ import {filter, take} from 'rxjs/operators'; export class DashboardExperimentsComponent implements OnInit { @Input() recentTasks: IRecentTask[]; - constructor(private store: Store, private router: Router) { + constructor(private store: Store, private router: Router) { } ngOnInit() { diff --git a/src/app/webapp-common/dashboard/containers/dashboard-projects/dashboard-projects.component.ts b/src/app/webapp-common/dashboard/containers/dashboard-projects/dashboard-projects.component.ts index 5e2871bb..6daf6302 100644 --- a/src/app/webapp-common/dashboard/containers/dashboard-projects/dashboard-projects.component.ts +++ b/src/app/webapp-common/dashboard/containers/dashboard-projects/dashboard-projects.component.ts @@ -30,7 +30,7 @@ export class DashboardProjectsComponent implements OnInit, AfterViewInit, OnDest @Output() width = new EventEmitter(); constructor( - private store: Store, + private store: Store, public router: Router, private matDialog: MatDialog ) { diff --git a/src/app/webapp-common/dashboard/dashboard-search.component.base.ts b/src/app/webapp-common/dashboard/dashboard-search.component.base.ts index 1549f3e1..cdf30274 100644 --- a/src/app/webapp-common/dashboard/dashboard-search.component.base.ts +++ b/src/app/webapp-common/dashboard/dashboard-search.component.base.ts @@ -66,7 +66,7 @@ export class DashboardSearchBaseComponent implements OnInit, OnDestroy{ public resultsCount$: Observable>; public reportsResults$: Observable>; - constructor(public store: Store, 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); diff --git a/src/app/webapp-common/dataset-version/dataset-version-step/dataset-version-step.component.html b/src/app/webapp-common/dataset-version/dataset-version-step/dataset-version-step.component.html index dcf7ba96..3edbcf2c 100644 --- a/src/app/webapp-common/dataset-version/dataset-version-step/dataset-version-step.component.html +++ b/src/app/webapp-common/dataset-version/dataset-version-step/dataset-version-step.component.html @@ -1,5 +1,5 @@
+ [class.selected]="selected" data-id="processSteps">
{{step?.data?.name ?? step.name}} v{{step.data.version}}
- +
diff --git a/src/app/webapp-common/dataset-version/dataset-version-step/dataset-version-step.component.scss b/src/app/webapp-common/dataset-version/dataset-version-step/dataset-version-step.component.scss index 25777341..ffaefdaf 100644 --- a/src/app/webapp-common/dataset-version/dataset-version-step/dataset-version-step.component.scss +++ b/src/app/webapp-common/dataset-version/dataset-version-step/dataset-version-step.component.scss @@ -19,7 +19,7 @@ font-size: 12px; &.in_progress { - background-color: $running-green; + background-color: darken($running-green, 20%); } } diff --git a/src/app/webapp-common/dataset-version/simple-dataset-version-content/simple-dataset-version-content.component.html b/src/app/webapp-common/dataset-version/simple-dataset-version-content/simple-dataset-version-content.component.html index 775e3997..9a0077eb 100644 --- a/src/app/webapp-common/dataset-version/simple-dataset-version-content/simple-dataset-version-content.component.html +++ b/src/app/webapp-common/dataset-version/simple-dataset-version-content/simple-dataset-version-content.component.html @@ -24,6 +24,7 @@ (smHesitate)="menuHesitate.hesitateStatus && menu.closed.emit()" >
-
{{entity.name}} v{{entity.runtime.version}}
- {{entity.status | replaceViaMapPipe:convertStatusMap | replaceViaMapPipe:convertStatusMapBase}} +
{{entity.name}} v{{entity.runtime.version}}
+ {{entity.status | replaceViaMapPipe:convertStatusMap | replaceViaMapPipe:convertStatusMapBase}}
-
ID
+
ID
{{entity.id?.slice(0, 8)}}...
-
Parent
-
FILES CHANGED
-
Added
-
{{entity?.runtime?.ds_change_add ?? '-'}}
+
Added
+
{{entity?.runtime?.ds_change_add ?? '-'}}
-
Modified
-
{{entity?.runtime?.ds_change_modify ?? '-'}}
+
Modified
+
{{entity?.runtime?.ds_change_modify ?? '-'}}
-
Removed
-
{{entity?.runtime?.ds_change_remove ?? '-'}}
+
Removed
+
{{entity?.runtime?.ds_change_remove ?? '-'}}
-
Size
-
Size
+
-
DESCRIPTION
+
DESCRIPTION
{{entity?.comment}}
@@ -87,7 +88,7 @@ class="arr-link" target="_blank" [href]="'/projects/' + project + '/experiments/' + entity?.id + '/output/execution'"> - Task information + Task information
diff --git a/src/app/webapp-common/dataset-version/simple-dataset-version-info/simple-dataset-version-info.component.html b/src/app/webapp-common/dataset-version/simple-dataset-version-info/simple-dataset-version-info.component.html index 481db9f9..28f9b1a5 100644 --- a/src/app/webapp-common/dataset-version/simple-dataset-version-info/simple-dataset-version-info.component.html +++ b/src/app/webapp-common/dataset-version/simple-dataset-version-info/simple-dataset-version-info.component.html @@ -5,7 +5,7 @@ (editDescription)="editDescription($event)" >
-
@@ -63,8 +63,8 @@ (valueChanged)="detailsPanelMode = $event" >
- - + +
diff --git a/src/app/webapp-common/dataset-version/simple-dataset-version-info/simple-dataset-version-info.component.ts b/src/app/webapp-common/dataset-version/simple-dataset-version-info/simple-dataset-version-info.component.ts index cf6ccde2..53f7a226 100644 --- a/src/app/webapp-common/dataset-version/simple-dataset-version-info/simple-dataset-version-info.component.ts +++ b/src/app/webapp-common/dataset-version/simple-dataset-version-info/simple-dataset-version-info.component.ts @@ -26,7 +26,7 @@ export class SimpleDatasetVersionInfoComponent extends PipelineControllerInfoCom constructor( protected _dagManager: DagManagerUnsortedService, - protected store: Store, + protected store: Store, protected cdr: ChangeDetectorRef, protected zone: NgZone, private dialog: MatDialog, diff --git a/src/app/webapp-common/dataset-version/simple-dataset-version-menu/simple-dataset-version-menu.component.html b/src/app/webapp-common/dataset-version/simple-dataset-version-menu/simple-dataset-version-menu.component.html index 9fac945f..d7b33581 100644 --- a/src/app/webapp-common/dataset-version/simple-dataset-version-menu/simple-dataset-version-menu.component.html +++ b/src/app/webapp-common/dataset-version/simple-dataset-version-menu/simple-dataset-version-menu.component.html @@ -7,13 +7,13 @@ [style.top.px]="position.y" [matMenuTriggerFor]="experimentMenu">
- +
- - - - + + diff --git a/src/app/webapp-common/dataset-version/simple-dataset-version-preview/simple-dataset-version-preview.component.html b/src/app/webapp-common/dataset-version/simple-dataset-version-preview/simple-dataset-version-preview.component.html index 9c93a821..15c78628 100644 --- a/src/app/webapp-common/dataset-version/simple-dataset-version-preview/simple-dataset-version-preview.component.html +++ b/src/app/webapp-common/dataset-version/simple-dataset-version-preview/simple-dataset-version-preview.component.html @@ -1,7 +1,7 @@
@@ -12,6 +12,6 @@ [disableStatusRefreshFilter]="true" [selected]="selected" > -
No preview to show
+
No preview to show
diff --git a/src/app/webapp-common/dataset-version/simple-dataset-version-preview/simple-dataset-version-preview.component.scss b/src/app/webapp-common/dataset-version/simple-dataset-version-preview/simple-dataset-version-preview.component.scss index 46cbdb57..c032d46b 100644 --- a/src/app/webapp-common/dataset-version/simple-dataset-version-preview/simple-dataset-version-preview.component.scss +++ b/src/app/webapp-common/dataset-version/simple-dataset-version-preview/simple-dataset-version-preview.component.scss @@ -12,3 +12,13 @@ .dataset-version-preview.hidden { display: none; } + +.hidden-like { + visibility: hidden; + height: 0 !important; + display: flex !important; +} + +sm-debug-images { + height: unset; +} diff --git a/src/app/webapp-common/dataset-version/simple-dataset-version-preview/simple-dataset-version-preview.component.ts b/src/app/webapp-common/dataset-version/simple-dataset-version-preview/simple-dataset-version-preview.component.ts index e9d7b661..eae5be46 100644 --- a/src/app/webapp-common/dataset-version/simple-dataset-version-preview/simple-dataset-version-preview.component.ts +++ b/src/app/webapp-common/dataset-version/simple-dataset-version-preview/simple-dataset-version-preview.component.ts @@ -1,7 +1,7 @@ import {Component, Input} from '@angular/core'; -import {ExperimentSharedModule} from "~/features/experiments/shared/experiment-shared.module"; -import {DebugImagesModule} from "@common/debug-images/debug-images.module"; -import {NgIf} from "@angular/common"; +import {ExperimentSharedModule} from '~/features/experiments/shared/experiment-shared.module'; +import {DebugImagesModule} from '@common/debug-images/debug-images.module'; +import {NgIf} from '@angular/common'; @Component({ selector: 'sm-simple-dataset-version-preview', diff --git a/src/app/webapp-common/dataset-version/simple-dataset-versions/simple-dataset-versions.component.html b/src/app/webapp-common/dataset-version/simple-dataset-versions/simple-dataset-versions.component.html index c5987424..3f7f20cd 100644 --- a/src/app/webapp-common/dataset-version/simple-dataset-versions/simple-dataset-versions.component.html +++ b/src/app/webapp-common/dataset-version/simple-dataset-versions/simple-dataset-versions.component.html @@ -9,6 +9,7 @@ [tableFilters]="tableFilters$ | async" [tableMode]="firstExperiment && (tableMode$ | async)" [rippleEffect]="tableModeAwareness$ | async" + [noData]="!((experiments$ | async)?.length > 0)" (isArchivedChanged)="archivedChanged($event)" (selectedTableColsChanged)="selectedTableColsChanged($event)" (getMetricsToDisplay)="getMetricsToDisplay()" @@ -19,6 +20,8 @@ (clearSelection)="clearSelection()" (clearTableFilters)="clearTableFiltersHandler($event)" (tableModeChanged)="modeChanged($event); tableModeUserAware()" + (downloadTableAsCSV)="downloadTableAsCSV()" + (downloadFullTableAsCSV)="downloadFullTableAsCSV()" >
, - protected syncSelector: SmSyncStateSelectorService, + constructor(protected store: Store, protected route: ActivatedRoute, protected router: Router, protected dialog: MatDialog, protected refresh: RefreshService ) { - super(store, syncSelector, route, router, dialog, refresh); + super(store, route, router, dialog, refresh); this.tableCols = INITIAL_CONTROLLER_TABLE_COLS.map((col) => col.id === EXPERIMENTS_TABLE_COL_FIELDS.NAME ? {...col, header: 'VERSION NAME'} : col); } @@ -72,4 +71,30 @@ export class SimpleDatasetVersionsComponent extends ControllersComponent impleme this.footerItems.splice(5, 1); } + downloadTableAsCSV() { + this.table.table.downloadTableAsCSV(`ClearML ${this.selectedProject.id === '*'? 'All': this.selectedProject?.basename?.substring(0,60)} Datasets`); + } + setupBreadcrumbsOptions() { + this.sub.add(this.selectedProject$.pipe( + withLatestFrom(this.store.select(selectDefaultNestedModeForFeature)) + ).subscribe(([selectedProject, defaultNestedModeForFeature]) => { + this.store.dispatch(setBreadcrumbsOptions({ + breadcrumbOptions: { + showProjects: !!selectedProject, + featureBreadcrumb: { + name: 'DATASETS', + url: defaultNestedModeForFeature['datasets'] ? 'datasets/simple/*/projects' : 'datasets' + }, + projectsOptions: { + basePath: 'datasets/simple', + filterBaseNameWith: ['.datasets'], + compareModule: null, + showSelectedProject: selectedProject?.id !== '*', + ...(selectedProject && selectedProject?.id !== '*' && {selectedProjectBreadcrumb: {name: selectedProject?.basename}}) + } + } + })); + })); + } + } diff --git a/src/app/webapp-common/datasets/nested-simple-datasets-page/nested-simple-datasets-page.component.html b/src/app/webapp-common/datasets/nested-simple-datasets-page/nested-simple-datasets-page.component.html new file mode 100644 index 00000000..19869802 --- /dev/null +++ b/src/app/webapp-common/datasets/nested-simple-datasets-page/nested-simple-datasets-page.component.html @@ -0,0 +1,60 @@ + + + + +
+
+
NO DATASETS TO SHOW
+
Run your first dataset to see it displayed here + or generate + example + +
+ +
+ +
+ + + + + + + + + + + + diff --git a/src/app/webapp-common/datasets/nested-simple-datasets-page/nested-simple-datasets-page.component.ts b/src/app/webapp-common/datasets/nested-simple-datasets-page/nested-simple-datasets-page.component.ts new file mode 100644 index 00000000..6859fb16 --- /dev/null +++ b/src/app/webapp-common/datasets/nested-simple-datasets-page/nested-simple-datasets-page.component.ts @@ -0,0 +1,126 @@ +import {Component, OnDestroy, OnInit} 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'; +import { + getProjectsTags, + setBreadcrumbsOptions, + setDefaultNestedModeForFeature, + setTags +} from '@common/core/actions/projects.actions'; +import { + selectDefaultNestedModeForFeature, + selectMainPageTagsFilter, + selectMainPageTagsFilterMatchMode, + selectProjectTags +} from '@common/core/reducers/projects.reducer'; +import {combineLatest, Observable, Subscription} from 'rxjs'; +import {debounceTime, withLatestFrom} from 'rxjs/operators'; +import {getAllProjectsPageProjects, resetProjects} from '@common/projects/common-projects.actions'; + +@Component({ + selector: 'sm-nested-simple-datasets-page', + templateUrl: './nested-simple-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/empty.scss' + ], + imports: [ + ProjectsSharedModule, + SMSharedModule, + AsyncPipe, + NgIf + ], + standalone: true +}) +export class NestedSimpleDatasetsPageComponent extends CommonProjectsPageComponent implements OnInit, OnDestroy { + entityTypeEnum = ProjectTypeEnum; + circleTypeEnum = CircleTypeEnum; + hideMenu = false; + entityType = ProjectTypeEnum.datasets; + projectsTags$: Observable; + private mainPageFilterSub: Subscription; + + 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) { + this.store.dispatch(setDefaultNestedModeForFeature({feature: this.entityType, isNested: nested})); + if (!nested) { + this.router.navigateByUrl(this.entityType); + } + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + protected getExtraProjects(selectedProjectId, selectedProject) { + return []; + } + + ngOnInit() { + super.ngOnInit(); + this.projectsTags$ = this.store.select(selectProjectTags); + this.store.dispatch(getProjectsTags({entity: 'dataset'})); + // Todo: 1 subscription at base, get function to supply relevant selectors + this.mainPageFilterSub = combineLatest([ + this.store.select(selectMainPageTagsFilter), + this.store.select(selectMainPageTagsFilterMatchMode) + ]).pipe(debounceTime(0)) + .subscribe(() => { + this.store.dispatch(resetProjects()); + this.store.dispatch(getAllProjectsPageProjects()); + }); + } + noProjectsReRoute() { + return this.router.navigate(['..', 'datasets'], {relativeTo: this.route}); + } + + shouldReRoute(selectedProject, config) { + const relevantSubProjects = selectedProject?.sub_projects?.filter(proj => proj.name.includes('.datasets')); + return config[3] === 'projects' && selectedProject.id !== '*' && (relevantSubProjects?.every(subProject => subProject.name.startsWith(selectedProject.name + '/.'))); + }; + + ngOnDestroy() { + super.ngOnDestroy(); + this.mainPageFilterSub.unsubscribe(); + this.store.dispatch(setTags({tags: []})); + } + + setupBreadcrumbsOptions() { + this.subs.add(this.selectedProject$.pipe( + withLatestFrom(this.store.select(selectDefaultNestedModeForFeature)) + ).subscribe(([selectedProject, defaultNestedModeForFeature]) => { + this.store.dispatch(setBreadcrumbsOptions({ + breadcrumbOptions: { + showProjects: !!selectedProject, + featureBreadcrumb: { + name: 'DATASETS', + url: defaultNestedModeForFeature['datasets'] ? 'datasets/simple/*/projects' : 'datasets' + }, + projectsOptions: { + basePath: 'datasets/simple', + filterBaseNameWith: ['.datasets'], + compareModule: null, + showSelectedProject: selectedProject?.id !== '*', + ...(selectedProject && selectedProject?.id !== '*' && {selectedProjectBreadcrumb: {name: selectedProject?.basename}}) + } + } + })); + })); + } +} diff --git a/src/app/webapp-common/datasets/simple-dataset-card/simple-dataset-card.component.html b/src/app/webapp-common/datasets/simple-dataset-card/simple-dataset-card.component.html index 1a76697a..e9024496 100644 --- a/src/app/webapp-common/datasets/simple-dataset-card/simple-dataset-card.component.html +++ b/src/app/webapp-common/datasets/simple-dataset-card/simple-dataset-card.component.html @@ -13,15 +13,16 @@ [editable]="true" [minLength]="2" [required]="true" - pattern="^[^/]{2,}$" + pattern="^[^\/]{2,}$" + [forbiddenString]="[]" [inlineDisabled]="true" (textChanged)="prepareProjectNameForChange($event)" > {{project.name | shortProjectName}} + >{{project.basename}} diff --git a/src/app/webapp-common/datasets/simple-datasets/empty.scss b/src/app/webapp-common/datasets/simple-datasets/empty.scss new file mode 100644 index 00000000..593cecc6 --- /dev/null +++ b/src/app/webapp-common/datasets/simple-datasets/empty.scss @@ -0,0 +1,33 @@ +@import "variables"; + +.empty-datasets { + width: 100%; + grid-column: 1/-1; + display: flex; + flex-direction: column; + max-width: map-get($grid-max-widths, xl); + margin: 0 auto; + + .title-icon { + color: $blue-600; + text-align: center; + margin-bottom: 24px; + } + .title { + font-size: 20px; + font-weight: 500; + color: $blue-100; + margin-bottom: 6px; + text-align: center; + } + + .sub-title { + text-align: center; + margin-bottom: 64px; + + .link { + color: $neon-yellow; + } + } + +} diff --git a/src/app/webapp-common/datasets/simple-datasets/simple-datasets.component.html b/src/app/webapp-common/datasets/simple-datasets/simple-datasets.component.html index 1223a4fc..b14fc099 100644 --- a/src/app/webapp-common/datasets/simple-datasets/simple-datasets.component.html +++ b/src/app/webapp-common/datasets/simple-datasets/simple-datasets.component.html @@ -39,7 +39,7 @@
- +
diff --git a/src/app/webapp-common/datasets/simple-datasets/simple-datasets.component.scss b/src/app/webapp-common/datasets/simple-datasets/simple-datasets.component.scss index f38bc555..2777b31f 100644 --- a/src/app/webapp-common/datasets/simple-datasets/simple-datasets.component.scss +++ b/src/app/webapp-common/datasets/simple-datasets/simple-datasets.component.scss @@ -1,4 +1,5 @@ @import "variables"; +@import "./empty"; :host { .sm-card-list-layout{ @@ -21,39 +22,6 @@ background-color: $blue-800; margin-bottom: -12px; } - - .empty-datasets { - width: 100%; - grid-column: 1/-1; - display: flex; - flex-direction: column; - max-width: map-get($grid-max-widths, xl); - margin: 0 auto; - - .title-icon { - color: $blue-600; - text-align: center; - margin-bottom: 24px; - } - .title { - font-size: 20px; - font-weight: 500; - color: $blue-100; - margin-bottom: 6px; - text-align: center; - } - - .sub-title { - text-align: center; - margin-bottom: 64px; - - .link { - color: $neon-yellow; - } - } - - } - } diff --git a/src/app/webapp-common/datasets/simple-datasets/simple-datasets.component.spec.ts b/src/app/webapp-common/datasets/simple-datasets/simple-datasets.component.spec.ts deleted file mode 100644 index e1073865..00000000 --- a/src/app/webapp-common/datasets/simple-datasets/simple-datasets.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { SimpleDatasetsComponent } from './simple-datasets.component'; - -describe('SimpleDatasetsComponent', () => { - let component: SimpleDatasetsComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ SimpleDatasetsComponent ] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(SimpleDatasetsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/webapp-common/datasets/simple-datasets/simple-datasets.component.ts b/src/app/webapp-common/datasets/simple-datasets/simple-datasets.component.ts index ef6a1a80..72e0b395 100644 --- a/src/app/webapp-common/datasets/simple-datasets/simple-datasets.component.ts +++ b/src/app/webapp-common/datasets/simple-datasets/simple-datasets.component.ts @@ -1,11 +1,17 @@ import {Component, OnInit} from '@angular/core'; import {PipelinesPageComponent} from '@common/pipelines/pipelines-page/pipelines-page.component'; import {ProjectsGetAllResponseSingle} from '~/business-logic/model/projects/projectsGetAllResponseSingle'; -import {setDefaultNestedModeForFeature, setSelectedProjectId} from '@common/core/actions/projects.actions'; +import { + setBreadcrumbsOptions, + setDefaultNestedModeForFeature, + setSelectedProjectId +} from '@common/core/actions/projects.actions'; import {showExampleDatasets} from '../../projects/common-projects.actions'; import {selectShowDatasetExamples} from '../../projects/common-projects.reducer'; import {EntityTypeEnum} from '~/shared/constants/non-common-consts'; import {DatasetEmptyComponent} from '@common/datasets/dataset-empty/dataset-empty.component'; +import {withLatestFrom} from 'rxjs/operators'; +import {selectDefaultNestedModeForFeature} from '@common/core/reducers/projects.reducer'; @Component({ selector: 'sm-simple-datasets', @@ -50,4 +56,27 @@ export class SimpleDatasetsComponent extends PipelinesPageComponent implements O this.router.navigateByUrl('datasets'); } } + + setupBreadcrumbsOptions() { + this.subs.add(this.selectedProject$.pipe( + withLatestFrom(this.store.select(selectDefaultNestedModeForFeature)) + ).subscribe(([selectedProject, defaultNestedModeForFeature]) => { + this.store.dispatch(setBreadcrumbsOptions({ + breadcrumbOptions: { + showProjects: !!selectedProject, + featureBreadcrumb: { + name: 'DATASETS', + url: defaultNestedModeForFeature['datasets'] ? 'datasets/simple/*/projects' : 'datasets' + }, + projectsOptions: { + basePath: 'datasets/simple', + filterBaseNameWith: ['.datasets'], + compareModule: null, + showSelectedProject: selectedProject?.id !== '*', + ...(selectedProject && selectedProject?.id !== '*' && {selectedProjectBreadcrumb: {name: selectedProject?.basename}}) + } + } + })); + })); + } } diff --git a/src/app/webapp-common/debug-images/debug-images-view/debug-images-view.component.html b/src/app/webapp-common/debug-images/debug-images-view/debug-images-view.component.html index 0fa4026d..04aae383 100755 --- a/src/app/webapp-common/debug-images/debug-images-view/debug-images-view.component.html +++ b/src/app/webapp-common/debug-images/debug-images-view/debug-images-view.component.html @@ -1,6 +1,6 @@ - + {{iteration.iter}} diff --git a/src/app/webapp-common/debug-images/debug-images.component.html b/src/app/webapp-common/debug-images/debug-images.component.html index 16e6afb4..5adce43e 100644 --- a/src/app/webapp-common/debug-images/debug-images.component.html +++ b/src/app/webapp-common/debug-images/debug-images.component.html @@ -12,16 +12,16 @@ > -
diff --git a/src/app/webapp-common/debug-images/debug-images.component.ts b/src/app/webapp-common/debug-images/debug-images.component.ts index 3d76ebf8..ac31c078 100644 --- a/src/app/webapp-common/debug-images/debug-images.component.ts +++ b/src/app/webapp-common/debug-images/debug-images.component.ts @@ -11,8 +11,7 @@ import { SimpleChanges } from '@angular/core'; import {combineLatest, Observable, Subscription} from 'rxjs'; -import {select, Store} from '@ngrx/store'; -import {ExperimentInfoState} from '~/features/experiments/reducers/experiment-info.reducer'; +import {Store} from '@ngrx/store'; import {AdminService} from '~/shared/services/admin.service'; import {selectS3BucketCredentials} from '../core/reducers/common-auth-reducer'; import {MatDialog} from '@angular/material/dialog'; @@ -118,7 +117,7 @@ export class DebugImagesComponent implements OnInit, OnDestroy, OnChanges { LIMITED_VIEW_LIMIT = LIMITED_VIEW_LIMIT; constructor( - private store: Store, + private store: Store, private adminService: AdminService, private dialog: MatDialog, private changeDetection: ChangeDetectorRef, @@ -135,50 +134,50 @@ export class DebugImagesComponent implements OnInit, OnDestroy, OnChanges { this.debugImagesSubscription = combineLatest([ - store.pipe(select(selectS3BucketCredentials)), - store.pipe(select(selectDebugImages))]).pipe( - map(([, debugImages]) => !debugImages ? null : Object.entries(debugImages).reduce(((acc, val: any) => { - const id = val[0]; - const iterations = val[1].metrics.find(m => m.task === id).iterations; - if (iterations?.length === 0) { - return {[id]: {}}; - } - acc[id] = { - data: iterations.map(iteration => ({ - iter: iteration.iter, - events: iteration.events.map(event => { - this.store.dispatch(getSignedUrl({url: event.url, config: {disableCache: event.timestamp}})); - return { - ...event, - url: event.url, - variantAndMetric: this.selectedMetric === ALL_IMAGES ? `${event.metric}/${event.variant}` : '' - }; - }) - })) - }; - acc[id].metrics = val[1].metrics.map(metric => metric.metric || metric.iterations[0].events[0].metric); - acc[id].metric = acc[id].metrics[0]; - acc[id].scrollId = val[1].scroll_id; - return acc; - }), {})) - ).subscribe(debugImages => { - this.debugImages = debugImages; - if (debugImages === null) { - return; - } - Object.keys(debugImages).forEach(key => { - if (!this.selectedMetrics[key]) { - this.selectedMetrics[key] = debugImages[key]?.metric; - } + store.select(selectS3BucketCredentials), + store.select(selectDebugImages) + ]) + .pipe( + map(([, debugImages]) => !debugImages ? {} : Object.entries(debugImages).reduce(((acc, val: any) => { + const id = val[0]; + const iterations = val[1].metrics.find(m => m.task === id).iterations; + if (iterations?.length === 0) { + return {[id]: {}}; + } + acc[id] = { + data: iterations.map(iteration => ({ + iter: iteration.iter, + events: iteration.events.map(event => { + this.store.dispatch(getSignedUrl({url: event.url, config: {disableCache: event.timestamp}})); + return { + ...event, + url: event.url, + variantAndMetric: this.selectedMetric === ALL_IMAGES ? `${event.metric}/${event.variant}` : '' + }; + }) + })) + }; + acc[id].metrics = val[1].metrics.map(metric => metric.metric || metric.iterations[0].events[0].metric); + acc[id].metric = acc[id].metrics[0]; + acc[id].scrollId = val[1].scroll_id; + return acc; + }), {})) + ) + .subscribe(debugImages => { + this.debugImages = debugImages; + Object.keys(debugImages).forEach(key => { + if (!this.selectedMetrics[key]) { + this.selectedMetrics[key] = debugImages[key]?.metric; + } + }); + this.changeDetection.markForCheck(); }); - this.changeDetection.markForCheck(); - }); - this.routerParams$ = this.store.pipe( - select(selectRouterParams), - filter(params => !!params.ids || !!params.experimentId), - distinctUntilChanged() - ); + this.routerParams$ = this.store.select(selectRouterParams) + .pipe( + filter(params => !!params.ids || !!params.experimentId), + distinctUntilChanged() + ); } ngOnChanges(changes: SimpleChanges): void { diff --git a/src/app/webapp-common/experiments-compare/actions/compare-header.actions.ts b/src/app/webapp-common/experiments-compare/actions/compare-header.actions.ts index 84508ee9..44f89a58 100644 --- a/src/app/webapp-common/experiments-compare/actions/compare-header.actions.ts +++ b/src/app/webapp-common/experiments-compare/actions/compare-header.actions.ts @@ -27,7 +27,7 @@ export const refreshIfNeeded = createAction(REFRESH_IF_NEEDED, props<{ payload: export const toggleShowScalarOptions = createAction(TOGGLE_SHOW_SACLARS_OPTIONS); export const setSearchExperimentsForCompareResults = createAction(SET_SELECT_EXPERIMENTS_FOR_COMPARE, props<{ payload: Array }>()); export const setShowSearchExperimentsForCompare = createAction(SET_SHOW_SEARCH_EXPERIMENTS_FOR_COMPARE, props<{ payload: boolean }>()); -export const resetSelectCompareHeader = createAction(RESET_SELECT_EXPERIMENT_FOR_COMPARE); +export const resetSelectCompareHeader = createAction(RESET_SELECT_EXPERIMENT_FOR_COMPARE, props<{fullReset?: boolean}>()); export const getSelectedExperimentsForCompareAddDialog = createAction(GET_SELECTED_EXPERIMENTS_FOR_COMPARE, props<{tasksIds?: string[]}>()); export const compareAddDialogTableSortChanged = createAction( EXPERIMENTS_COMPARE_SELECT_EXPERIMENT_ + ' [table sort changed]', diff --git a/src/app/webapp-common/experiments-compare/containers/experiment-compare-base.ts b/src/app/webapp-common/experiments-compare/containers/experiment-compare-base.ts index 459de85c..e31cb3e1 100644 --- a/src/app/webapp-common/experiments-compare/containers/experiment-compare-base.ts +++ b/src/app/webapp-common/experiments-compare/containers/experiment-compare-base.ts @@ -93,7 +93,7 @@ export abstract class ExperimentCompareBase extends ExperimentCompareDetailsBase constructor( protected router: Router, - protected store: Store, + protected store: Store, protected changeDetection: ChangeDetectorRef, protected activeRoute: ActivatedRoute, protected refresh: RefreshService, diff --git a/src/app/webapp-common/experiments-compare/containers/experiment-compare-details/experiment-compare-details.component.ts b/src/app/webapp-common/experiments-compare/containers/experiment-compare-details/experiment-compare-details.component.ts index 5f1a6998..a7f740be 100644 --- a/src/app/webapp-common/experiments-compare/containers/experiment-compare-details/experiment-compare-details.component.ts +++ b/src/app/webapp-common/experiments-compare/containers/experiment-compare-details/experiment-compare-details.component.ts @@ -26,7 +26,7 @@ export class ExperimentCompareDetailsComponent extends ExperimentCompareBase imp constructor( public router: Router, - public store: Store, + public store: Store, public changeDetection: ChangeDetectorRef, public activeRoute: ActivatedRoute, public cdr: ChangeDetectorRef, diff --git a/src/app/webapp-common/experiments-compare/containers/experiment-compare-hyper-params-graph/experiment-compare-hyper-params-graph.component.html b/src/app/webapp-common/experiments-compare/containers/experiment-compare-hyper-params-graph/experiment-compare-hyper-params-graph.component.html index db6aff0d..3fe6e2ac 100644 --- a/src/app/webapp-common/experiments-compare/containers/experiment-compare-hyper-params-graph/experiment-compare-hyper-params-graph.component.html +++ b/src/app/webapp-common/experiments-compare/containers/experiment-compare-hyper-params-graph/experiment-compare-hyper-params-graph.component.html @@ -72,12 +72,13 @@
-
diff --git a/src/app/webapp-common/experiments-compare/containers/experiment-compare-hyper-params-graph/experiment-compare-hyper-params-graph.component.ts b/src/app/webapp-common/experiments-compare/containers/experiment-compare-hyper-params-graph/experiment-compare-hyper-params-graph.component.ts index c4e58d3a..cbf9af6c 100644 --- a/src/app/webapp-common/experiments-compare/containers/experiment-compare-hyper-params-graph/experiment-compare-hyper-params-graph.component.ts +++ b/src/app/webapp-common/experiments-compare/containers/experiment-compare-hyper-params-graph/experiment-compare-hyper-params-graph.component.ts @@ -1,9 +1,8 @@ -import {Component, ElementRef, HostListener, OnDestroy, OnInit, ViewChild} from '@angular/core'; +import {ChangeDetectorRef, Component, ElementRef, HostListener, OnDestroy, OnInit, ViewChild} from '@angular/core'; import {combineLatest, Observable, Subscription} from 'rxjs'; import {select, Store} from '@ngrx/store'; -import {ExperimentInfoState} from '~/features/experiments/reducers/experiment-info.reducer'; -import {distinctUntilChanged, filter, map} from 'rxjs/operators'; -import {selectRouterParams} from '@common/core/reducers/router-reducer'; +import {debounceTime, distinctUntilChanged, filter, map, withLatestFrom} from 'rxjs/operators'; +import {selectRouterParams, selectRouterQueryParams} from '@common/core/reducers/router-reducer'; import {has} from 'lodash-es'; import {setExperimentSettings, setSelectedExperiments} from '../../actions/experiments-compare-charts.actions'; import { @@ -31,7 +30,7 @@ import {ExtFrame} from '@common/shared/single-graph/plotly-graph-base'; import {RefreshService} from '@common/core/services/refresh.service'; import {MetricValueType, SelectedMetric} from '@common/experiments-compare/experiments-compare.constants'; import {ReportCodeEmbedService} from '~/shared/services/report-code-embed.service'; -import {ActivatedRoute} from '@angular/router'; +import {ActivatedRoute, Router} from '@angular/router'; export const _filter = (opt: VariantOption[], value: string): VariantOption[] => { @@ -46,11 +45,7 @@ export const _filter = (opt: VariantOption[], value: string): VariantOption[] => styleUrls: ['./experiment-compare-hyper-params-graph.component.scss'] }) export class ExperimentCompareHyperParamsGraphComponent implements OnInit, OnDestroy { - private routerParamsSubscription: Subscription; - private hyperParamsSubscription: Subscription; - private metricSubscription: Subscription; - private selectMetricSubscription: Subscription; - private refreshingSubscription: Subscription; + private subs = new Subscription(); public selectShowIdenticalHyperParams$: Observable; public hyperParams$: Observable; @@ -87,10 +82,12 @@ export class ExperimentCompareHyperParamsGraphComponent implements OnInit, OnDes } } - constructor(private store: Store, + constructor(private store: Store, private route: ActivatedRoute, + private router: Router, private refresh: RefreshService, - private reportEmbed: ReportCodeEmbedService) { + private reportEmbed: ReportCodeEmbedService, + private cdr: ChangeDetectorRef) { this.metrics$ = this.store.pipe(select(selectScalarsGraphMetrics)); this.hyperParams$ = this.store.pipe(select(selectScalarsGraphHyperParams)); this.selectedHyperParams$ = this.store.pipe(select(selectSelectedSettingsHyperParams)); @@ -102,20 +99,21 @@ export class ExperimentCompareHyperParamsGraphComponent implements OnInit, OnDes } ngOnInit() { - this.selectMetricSubscription = this.selectedMetric$.pipe( + this.subs.add(this.selectedMetric$.pipe( distinctUntilChanged((x, y) => x?.path === y?.path) - ).subscribe((selectedMetric: SelectedMetric) => this.selectedMetric = selectedMetric?.name ? {...selectedMetric} : null); + ).subscribe((selectedMetric: SelectedMetric) => { + this.selectedMetric = selectedMetric?.path ? {...selectedMetric} : null; + this.cdr.detectChanges(); + })); - this.metricSubscription = this.metrics$.pipe(filter(metrics => !!metrics)).subscribe(metrics => { + this.subs.add(this.metrics$.pipe( + filter(metrics => !!metrics) + ).subscribe((metrics) => { this.metrics = metrics; this.metricsOptions = [...metrics]; + })); - if (this.selectedMetric && this.metrics.every(metric => metric.variants.every(variant => this.selectedMetric.name !== variant.value.name))) { - this.selectedMetric = null; - } - }); - - this.hyperParamsSubscription = combineLatest([this.selectedHyperParams$, this.hyperParams$, this.selectShowIdenticalHyperParams$]) + this.subs.add(combineLatest([this.selectedHyperParams$, this.hyperParams$, this.selectShowIdenticalHyperParams$]) .pipe( filter(([, allParams]) => !!allParams), ) @@ -136,10 +134,25 @@ export class ExperimentCompareHyperParamsGraphComponent implements OnInit, OnDes } return acc; }, {}); - this.selectedHyperParams = selectedParams.filter(selectedParam => has( this.hyperParams, selectedParam)); - }); + this.selectedHyperParams = selectedParams?.filter(selectedParam => has(this.hyperParams, selectedParam)); + this.cdr.detectChanges(); + })); - this.routerParamsSubscription = this.store.pipe( + this.subs.add(combineLatest([this.metrics$, this.hyperParams$]).pipe( + debounceTime(0), + filter(([metircs, hyperparams]) => metircs?.length > 0 && Object.keys(hyperparams || {})?.length > 0), + withLatestFrom(this.store.select(selectRouterQueryParams)) + ).subscribe(([[metircs], queryParams]) => { + if (queryParams.metricPath) { + const selectedMetric = metircs.map(a => a.variants).flat(2).find(variant => variant.value.path === queryParams.metricPath)?.value ?? null; + const params = Array.isArray(queryParams.params) ? queryParams.params : [queryParams.params]; + this.updateServer(selectedMetric, params, true); + this.listOpen = false; + this.cdr.detectChanges(); + } + })); + + this.subs.add(this.store.pipe( select(selectRouterParams), map(params => params?.ids), distinctUntilChanged(), @@ -149,27 +162,24 @@ export class ExperimentCompareHyperParamsGraphComponent implements OnInit, OnDes this.taskIds = ids.split(','); this.store.dispatch(setSelectedExperiments({selectedExperiments: ['hyper-param-graph']})); this.store.dispatch(getExperimentsHyperParams({experimentsIds: this.taskIds})); - }); + })); - this.refreshingSubscription = this.refresh.tick + this.subs.add(this.refresh.tick .pipe(filter(auto => auto !== null)) .subscribe(autoRefresh => this.store.dispatch(getExperimentsHyperParams({experimentsIds: this.taskIds, autoRefresh})) - ); + )); this.listOpen = true; window.setTimeout(() => { this.searchMetricRef.nativeElement.focus(); this.initView = false; + this.cdr.detectChanges(); }, 200); } ngOnDestroy() { - this.routerParamsSubscription.unsubscribe(); - this.hyperParamsSubscription.unsubscribe(); - this.metricSubscription.unsubscribe(); - this.selectMetricSubscription.unsubscribe(); - this.refreshingSubscription.unsubscribe(); + this.subs.unsubscribe(); } private _filterGroup(value: string): MetricOption[] { @@ -193,7 +203,6 @@ export class ExperimentCompareHyperParamsGraphComponent implements OnInit, OnDes } clearSelection() { - this.updateServer(null, this.selectedHyperParams); this.updateServer(this.selectedMetric, []); } @@ -201,12 +210,19 @@ export class ExperimentCompareHyperParamsGraphComponent implements OnInit, OnDes this.store.dispatch(setShowIdenticalHyperParams()); } - updateServer(selectedMetric, selectedParams) { + updateServer(selectedMetric: SelectedMetric, selectedParams: string[], skipNavigation?: boolean) { + !skipNavigation && this.router.navigate([], { + queryParams: { + metricPath: selectedMetric?.path || undefined, + metricName: selectedMetric?.name || undefined, + params: selectedParams + }, + queryParamsHandling: 'merge' + }); this.store.dispatch(setExperimentSettings({ id: ['hyper-param-graph'], changes: {selectedMetric, selectedHyperParams: selectedParams} })); - } updateMetricsList(event: Event) { diff --git a/src/app/webapp-common/experiments-compare/containers/experiment-compare-metric-charts/experiment-compare-scalar-charts.component.html b/src/app/webapp-common/experiments-compare/containers/experiment-compare-metric-charts/experiment-compare-scalar-charts.component.html index 1b1c2650..4fa102d9 100644 --- a/src/app/webapp-common/experiments-compare/containers/experiment-compare-metric-charts/experiment-compare-scalar-charts.component.html +++ b/src/app/webapp-common/experiments-compare/containers/experiment-compare-metric-charts/experiment-compare-scalar-charts.component.html @@ -5,6 +5,7 @@ class="drawer-settings-bar" [verticalLayout]="true" [smoothWeight]="smoothWeight$ | async" + [smoothType]="smoothType$ | async" [xAxisType]="xAxisType$ | async" [groupBy]="groupBy" [groupByOptions]="groupByOptions" @@ -12,6 +13,7 @@ (toggleSettings)="toggleSettingsBar()" (changeXAxisType)="changeXAxisType($event)" (changeGroupBy)="changeGroupBy($event)" + (changeSmoothType)="changeSmoothType($event)" > ; + public smoothWeightDelayed$: Observable; public xAxisType$: Observable; public groupBy$: Observable; private routerParams$: Observable; @@ -69,35 +70,40 @@ export class ExperimentCompareScalarChartsComponent implements OnInit, OnDestroy @ViewChild(ExperimentGraphsComponent) graphsComponent: ExperimentGraphsComponent; public hoverMode$: Observable; private entityType: EntityTypeEnum; + public smoothType$: Observable; + public modelsFeature: boolean; constructor( - private store: Store, + private store: Store, private changeDetection: ChangeDetectorRef, private route: ActivatedRoute, private refresh: RefreshService, private reportEmbed: ReportCodeEmbedService, ) { + this.modelsFeature = this.route.snapshot?.parent.data?.setAllProject; this.listOfHidden = this.store.select(selectSelectedSettingsHiddenScalar) .pipe(distinctUntilChanged(isEqual)); - this.searchTerm$ = this.store.pipe(select(selectExperimentMetricsSearchTerm)); + this.searchTerm$ = this.store.select(selectExperimentMetricsSearchTerm); this.smoothWeight$ = this.store.select(selectCompareSelectedSettingsSmoothWeight); + this.smoothWeightDelayed$ = this.store.select(selectCompareSelectedSettingsSmoothWeight).pipe(debounceTime(75)); + this.smoothType$ = this.store.select(selectSelectedSettingsSmoothType); this.xAxisType$ = this.store.select(selectCompareSelectedSettingsxAxisType); this.hoverMode$ = this.store.select(selectScalarsHoverMode); this.groupBy$ = this.store.select(selectCompareSelectedSettingsGroupBy); - this.metrics$ = this.store.pipe( - select(selectCompareTasksScalarCharts), + this.metrics$ = this.store.select(selectCompareTasksScalarCharts).pipe( filter(metrics => !!metrics), distinctUntilChanged() ); - this.experimentSettings$ = this.store.pipe( - select(selectSelectedExperimentSettings), + this.experimentSettings$ = this.store.select(selectSelectedExperimentSettings).pipe( + filter(settings => !!settings), map(settings => settings ? settings.selectedScalar : null), + filter(selectedPlot => selectedPlot !== undefined), distinctUntilChanged() ); - this.routerParams$ = this.store.pipe( - select(selectRouterParams), + this.routerParams$ = this.store.select(selectRouterParams).pipe( + filter(params => !!params.ids), distinctUntilChanged() ); @@ -183,6 +189,10 @@ export class ExperimentCompareScalarChartsComponent implements OnInit, OnDestroy this.store.dispatch(setExperimentSettings({id: this.taskIds, changes: {smoothWeight: $event}})); } + changeSmoothType($event: SmoothTypeEnum) { + this.store.dispatch(setExperimentSettings({id: this.taskIds, changes: {smoothType: $event}})); + } + changeXAxisType($event: ScalarKeyEnum) { this.store.dispatch(setExperimentSettings({id: this.taskIds, changes: {xAxisType: $event}})); } @@ -215,6 +225,7 @@ export class ExperimentCompareScalarChartsComponent implements OnInit, OnDestroy .subscribe(selectedMetric => { this.selectedGraph = selectedMetric; this.graphsComponent.scrollToGraph(selectedMetric); + this.store.dispatch(setExperimentSettings({id: this.taskIds, changes: {selectedScalar: undefined}})); }); } } diff --git a/src/app/webapp-common/experiments-compare/containers/experiment-compare-metric-values/experiment-compare-metric-values.component.ts b/src/app/webapp-common/experiments-compare/containers/experiment-compare-metric-values/experiment-compare-metric-values.component.ts index cf9c22ae..3aefc499 100644 --- a/src/app/webapp-common/experiments-compare/containers/experiment-compare-metric-values/experiment-compare-metric-values.component.ts +++ b/src/app/webapp-common/experiments-compare/containers/experiment-compare-metric-values/experiment-compare-metric-values.component.ts @@ -65,7 +65,7 @@ export class ExperimentCompareMetricValuesComponent implements OnInit, OnDestroy constructor( private router: Router, private route: ActivatedRoute, - public store: Store, + public store: Store, private changeDetection: ChangeDetectorRef, private refresh: RefreshService ) { diff --git a/src/app/webapp-common/experiments-compare/containers/experiment-compare-params/experiment-compare-params.component.ts b/src/app/webapp-common/experiments-compare/containers/experiment-compare-params/experiment-compare-params.component.ts index 7b4670a3..ff64d1dd 100644 --- a/src/app/webapp-common/experiments-compare/containers/experiment-compare-params/experiment-compare-params.component.ts +++ b/src/app/webapp-common/experiments-compare/containers/experiment-compare-params/experiment-compare-params.component.ts @@ -25,7 +25,7 @@ export class ExperimentCompareParamsComponent extends ExperimentCompareBase impl constructor( public router: Router, - public store: Store, + public store: Store, public changeDetection: ChangeDetectorRef, public activeRoute: ActivatedRoute, public refresh: RefreshService, diff --git a/src/app/webapp-common/experiments-compare/containers/experiment-compare-plots/experiment-compare-plots.component.html b/src/app/webapp-common/experiments-compare/containers/experiment-compare-plots/experiment-compare-plots.component.html index ae4cab86..c83fb27d 100644 --- a/src/app/webapp-common/experiments-compare/containers/experiment-compare-plots/experiment-compare-plots.component.html +++ b/src/app/webapp-common/experiments-compare/containers/experiment-compare-plots/experiment-compare-plots.component.html @@ -19,6 +19,7 @@ [isCompare]="true" [isGroupGraphs]="true" [hiddenList]="listOfHidden | async" + [exportForReport]="!modelsFeature" (createEmbedCode)="createEmbedCode($event)" (resetGraphs)="resetMetrics()" > diff --git a/src/app/webapp-common/experiments-compare/containers/experiment-compare-plots/experiment-compare-plots.component.ts b/src/app/webapp-common/experiments-compare/containers/experiment-compare-plots/experiment-compare-plots.component.ts index db88eb0f..acbaa61e 100644 --- a/src/app/webapp-common/experiments-compare/containers/experiment-compare-plots/experiment-compare-plots.component.ts +++ b/src/app/webapp-common/experiments-compare/containers/experiment-compare-plots/experiment-compare-plots.component.ts @@ -54,14 +54,16 @@ export class ExperimentComparePlotsComponent implements OnInit, OnDestroy { @ViewChild(ExperimentGraphsComponent) graphsComponent: ExperimentGraphsComponent; private entityType: EntityTypeEnum; + public modelsFeature: boolean; constructor( - private store: Store, + private store: Store, private changeDetection: ChangeDetectorRef, private route: ActivatedRoute, private refresh: RefreshService, private reportEmbed: ReportCodeEmbedService, ) { + this.modelsFeature = this.route.snapshot?.parent.data?.setAllProject; this.listOfHidden = this.store.pipe(select(selectSelectedSettingsHiddenPlot)); this.searchTerm$ = this.store.pipe(select(selectExperimentMetricsSearchTerm)); this.plots$ = this.store.pipe( @@ -73,6 +75,7 @@ export class ExperimentComparePlotsComponent implements OnInit, OnDestroy { select(selectSelectedExperimentSettings), filter(settings => !!settings), map(settings => settings ? settings.selectedPlot : null), + filter(selectedPlot => selectedPlot !== undefined), distinctUntilChanged() ); @@ -104,6 +107,7 @@ export class ExperimentComparePlotsComponent implements OnInit, OnDestroy { .subscribe((selectedPlot) => { this.selectedGraph = selectedPlot; this.graphsComponent?.scrollToGraph(selectedPlot); + this.store.dispatch(setExperimentSettings({id: this.taskIds, changes: {selectedPlot: null}})); }); this.routerParamsSubscription = this.routerParams$ @@ -153,11 +157,12 @@ export class ExperimentComparePlotsComponent implements OnInit, OnDestroy { this.store.dispatch(resetExperimentMetrics()); } - createEmbedCode(event: { metrics?: string[]; variants?: string[]; domRect: DOMRect }) { + createEmbedCode(event: { metrics?: string[]; variants?: string[]; originalObject?: string; domRect: DOMRect }) { const entityType = this.entityType === EntityTypeEnum.model ? 'model' : 'task'; + const idsOriginalFirst = event.originalObject ? [event.originalObject, ...this.taskIds.filter(id => id !== event.originalObject)] : this.taskIds; this.reportEmbed.createCode({ type: 'plot', - objects: this.taskIds, + objects: idsOriginalFirst, objectType: entityType, ...event }); diff --git a/src/app/webapp-common/experiments-compare/containers/model-compare-details/model-compare-details.component.ts b/src/app/webapp-common/experiments-compare/containers/model-compare-details/model-compare-details.component.ts index e0ed6eee..13bb60ab 100644 --- a/src/app/webapp-common/experiments-compare/containers/model-compare-details/model-compare-details.component.ts +++ b/src/app/webapp-common/experiments-compare/containers/model-compare-details/model-compare-details.component.ts @@ -7,7 +7,6 @@ import {ExperimentCompareTree,} from '~/features/experiments-compare/experiments import {convertmodelsArrays, getAllKeysEmptyObject, isDetailsConverted} from '../../jsonToDiffConvertor'; import {ExperimentCompareBase} from '../experiment-compare-base'; import {ActivatedRoute, Router} from '@angular/router'; -import {ExperimentInfoState} from '~/features/experiments/reducers/experiment-info.reducer'; import {ConfigurationItem} from '~/business-logic/model/tasks/configurationItem'; import {RefreshService} from '@common/core/services/refresh.service'; import {LIMITED_VIEW_LIMIT} from '@common/experiments-compare/experiments-compare.constants'; @@ -25,7 +24,7 @@ export class ModelCompareDetailsComponent extends ExperimentCompareBase implemen constructor( public router: Router, - public store: Store, + public store: Store, public changeDetection: ChangeDetectorRef, public activeRoute: ActivatedRoute, public cdr: ChangeDetectorRef, diff --git a/src/app/webapp-common/experiments-compare/containers/select-experiments-for-compare/select-experiments-for-compare.component.ts b/src/app/webapp-common/experiments-compare/containers/select-experiments-for-compare/select-experiments-for-compare.component.ts index 62ec014a..9dfd08fb 100644 --- a/src/app/webapp-common/experiments-compare/containers/select-experiments-for-compare/select-experiments-for-compare.component.ts +++ b/src/app/webapp-common/experiments-compare/containers/select-experiments-for-compare/select-experiments-for-compare.component.ts @@ -35,8 +35,7 @@ import { selectActiveParentsFilter, selectExperimentsList, selectExperimentsMetricsColsForProject, - selectExperimentsParents, - selectExperimentsTableCols, + selectExperimentsParents, selectExperimentsTableCols, selectExperimentsTableColsOrder, selectExperimentsTags, selectExperimentsTypes, @@ -64,9 +63,12 @@ import {EntityTypeEnum} from '~/shared/constants/non-common-consts'; import {ExperimentsTableComponent} from '@common/experiments/dumb/experiments-table/experiments-table.component'; import {MESSAGES_SEVERITY} from '@common/constants'; import {MatSlideToggleChange} from '@angular/material/slide-toggle'; -import {getTablesFilterProjectsOptions, resetTablesFilterProjectsOptions} from '@common/core/actions/projects.actions'; +import {getProjectUsers, getTablesFilterProjectsOptions, resetTablesFilterProjectsOptions} from '@common/core/actions/projects.actions'; import {EXPERIMENTS_PAGE_SIZE} from '@common/experiments/shared/common-experiments.const'; -import {setParents} from '../../../experiments/actions/common-experiments-view.actions'; +import {setParents} from '@common/experiments/actions/common-experiments-view.actions'; +import {INITIAL_CONTROLLER_TABLE_COLS} from '@common/pipelines-controller/controllers.consts'; +import {EXPERIMENTS_TABLE_COL_FIELDS} from '~/features/experiments/shared/experiments.const'; +import {setTableCols} from '../../../experiments/actions/common-experiments-view.actions'; export const allowAddExperiment$ = (selectRouterParams$: Observable) => selectRouterParams$.pipe( distinctUntilKeyChanged('ids'), @@ -82,8 +84,8 @@ export const allowAddExperiment$ = (selectRouterParams$: Observable) => styleUrls: ['./select-experiments-for-compare.component.scss'] }) export class SelectExperimentsForCompareComponent implements OnInit, OnDestroy { - public tableCols = INITIAL_EXPERIMENT_TABLE_COLS; public entityTypes = EntityTypeEnum; + public initTableCols = this.getInitTablesCols(this.data.entityType); public experimentsResults$: Observable; public selectedExperimentsIds: string[] = []; private paramsSubscription: Subscription; @@ -118,14 +120,14 @@ export class SelectExperimentsForCompareComponent implements OnInit, OnDestroy { private parents: ProjectsGetTaskParentsResponseParents[]; constructor( - private store: Store, + private store: Store, private eRef: ElementRef, private changedDetectRef: ChangeDetectorRef, public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: { entityType: EntityTypeEnum } ) { this.resizedCols$.next(this._resizedCols); - this.experimentsResults$ = this.store.pipe(select(selectSelectedExperimentsForCompareAdd)); + this.experimentsResults$ = this.store.select(selectSelectedExperimentsForCompareAdd); this.showArchived$ = this.store.select(selectViewArchivedInAddTable); this.experiments$ = combineLatest([ this.store.select(selectExperimentsList), @@ -133,7 +135,7 @@ export class SelectExperimentsForCompareComponent implements OnInit, OnDestroy { ]).pipe( map(([experiments, selectedExperiments]) => { const union = unionBy(selectedExperiments, experiments, 'id'); - if (experiments?.length >= EXPERIMENTS_PAGE_SIZE && (union.length - (selectedExperiments?.length ?? 0) <= EXPERIMENTS_PAGE_SIZE * (this.loadMoreCount + 1))) { + if (experiments?.length >= EXPERIMENTS_PAGE_SIZE && (union.length - (selectedExperiments?.length ?? 0) < EXPERIMENTS_PAGE_SIZE * (this.loadMoreCount + 1))) { this.store.dispatch(experimentsActions.getNextExperiments()); } return union; @@ -158,8 +160,7 @@ export class SelectExperimentsForCompareComponent implements OnInit, OnDestroy { this.tableCols$ = combineLatest([this.columns$, this.metricTableCols$, this.resizedCols$]) .pipe( map(([tableCols, metricCols, resizedCols]) => - (tableCols.length > 0 ? tableCols : this.tableCols) - .concat(metricCols.map(col => ({...col, metric: true}))) + tableCols.concat(metricCols.map(col => ({...col, metric: true}))) .map(col => ({ ...col, style: {...col.style, width: resizedCols[col.id] || col.style?.width} @@ -201,17 +202,19 @@ export class SelectExperimentsForCompareComponent implements OnInit, OnDestroy { } refreshTagsList() { - this.store.dispatch(experimentsActions.getTags()); + this.store.dispatch(experimentsActions.getTags({allProjects: true})); } refreshTypesList() { - this.store.dispatch(experimentsActions.getProjectTypes()); + this.store.dispatch(experimentsActions.getProjectTypes({allProjects: true})); } ngOnInit() { + this.store.dispatch(setTableCols({cols: this.initTableCols})); this.store.dispatch(setShowSearchExperimentsForCompare({payload: true})); - this.store.dispatch(getTablesFilterProjectsOptions({searchString: '', loadMore: false})); + this.store.dispatch(resetTablesFilterProjectsOptions()); + this.store.dispatch(getProjectUsers({projectId: '*'})); window.setTimeout(() => this.table.table.rowRightClick = new EventEmitter()); this.paramsSubscription = this.store.pipe( select(selectRouterParams), @@ -229,9 +232,9 @@ export class SelectExperimentsForCompareComponent implements OnInit, OnDestroy { this.changedDetectRef.detectChanges(); }); this.allowAddExperiment$ = allowAddExperiment$(this.store.select(selectRouterParams)); - this.store.dispatch(experimentsActions.getTags()); - this.store.dispatch(experimentsActions.getProjectTypes()); - this.store.dispatch(experimentsActions.getParents({searchValue: null})); + this.store.dispatch(experimentsActions.getTags({allProjects: true})); + this.store.dispatch(experimentsActions.getProjectTypes({allProjects: true})); + this.store.dispatch(experimentsActions.getParents({searchValue: null, allProjects: true})); this.store.dispatch(getSelectedExperimentsForCompareAddDialog(null)); this.syncAppSearch(); } @@ -241,7 +244,7 @@ export class SelectExperimentsForCompareComponent implements OnInit, OnDestroy { this.store.dispatch(setShowSearchExperimentsForCompare({payload: false})); this.store.dispatch(resetExperiments()); this.store.dispatch(resetGlobalFilter()); - this.store.dispatch(resetSelectCompareHeader()); + this.store.dispatch(resetSelectCompareHeader({fullReset: false})); } @@ -301,7 +304,7 @@ export class SelectExperimentsForCompareComponent implements OnInit, OnDestroy { this.store.dispatch(setAddTableViewArchived({show: event.checked})); } - filterSearchChanged({colId, value}: { colId: string; value: {value: string; loadMore?: boolean} }) { + filterSearchChanged({colId, value}: { colId: string; value: { value: string; loadMore?: boolean } }) { switch (colId) { case 'project.name': if (this.projectId === '*' && !value.loadMore) { @@ -314,8 +317,21 @@ export class SelectExperimentsForCompareComponent implements OnInit, OnDestroy { this.store.dispatch(setParents({parents: [...this.parents]})); } else { this.store.dispatch(experimentsActions.resetTablesFilterParentsOptions()); - this.store.dispatch(experimentsActions.getParents({searchValue: value.value || ''})); + this.store.dispatch(experimentsActions.getParents({searchValue: value.value || '', allProjects: true})); } } } + + private getInitTablesCols(entityType: EntityTypeEnum) { + switch (entityType) { + case this.entityTypes.controller: + return INITIAL_CONTROLLER_TABLE_COLS.map((col) => + col.id === EXPERIMENTS_TABLE_COL_FIELDS.NAME ? {...col, header: 'RUN'} : col); + case this.entityTypes.dataset: + return INITIAL_CONTROLLER_TABLE_COLS.map((col) => + col.id === EXPERIMENTS_TABLE_COL_FIELDS.NAME ? {...col, header: 'VERSION NAME'} : col); + default: + return INITIAL_EXPERIMENT_TABLE_COLS; + } + } } diff --git a/src/app/webapp-common/experiments-compare/dumbs/experiment-compare-header/experiment-compare-header.component.ts b/src/app/webapp-common/experiments-compare/dumbs/experiment-compare-header/experiment-compare-header.component.ts index 84619fc1..8fa675e8 100644 --- a/src/app/webapp-common/experiments-compare/dumbs/experiment-compare-header/experiment-compare-header.component.ts +++ b/src/app/webapp-common/experiments-compare/dumbs/experiment-compare-header/experiment-compare-header.component.ts @@ -40,7 +40,7 @@ export class ExperimentCompareHeaderComponent implements OnInit, OnDestroy { @Output() selectionChanged = new EventEmitter(); constructor( - private store: Store, + private store: Store, private route: ActivatedRoute, private router: Router, private cdr: ChangeDetectorRef, diff --git a/src/app/webapp-common/experiments-compare/dumbs/parallel-coordinates-graph/parallel-coordinates-graph.component.ts b/src/app/webapp-common/experiments-compare/dumbs/parallel-coordinates-graph/parallel-coordinates-graph.component.ts index 4d3d6e45..dbe2b699 100644 --- a/src/app/webapp-common/experiments-compare/dumbs/parallel-coordinates-graph/parallel-coordinates-graph.component.ts +++ b/src/app/webapp-common/experiments-compare/dumbs/parallel-coordinates-graph/parallel-coordinates-graph.component.ts @@ -428,11 +428,11 @@ export class ParallelCoordinatesGraphComponent extends PlotlyGraphBaseComponent maximize() { this.dialog.open(GraphViewerComponent, { data: { - embedFunction: (rect: DOMRect) => this.creatingEmbedCode(rect), + embedFunction: (data) => this.creatingEmbedCode(data.domRect), // signed url are updated after originChart was cloned - need to update images urls! chart: cloneDeep({ data: this.data as unknown as ExtData[], - layout: this.getLayout(false), + layout: {...this.getLayout(false), title: this.metric?.name || ''}, config: { displaylogo: false, displayModeBar: false, diff --git a/src/app/webapp-common/experiments-compare/effects/experiments-compare-charts.effects.ts b/src/app/webapp-common/experiments-compare/effects/experiments-compare-charts.effects.ts index ebffe4ac..ca9f516f 100644 --- a/src/app/webapp-common/experiments-compare/effects/experiments-compare-charts.effects.ts +++ b/src/app/webapp-common/experiments-compare/effects/experiments-compare-charts.effects.ts @@ -19,7 +19,7 @@ import {EntityTypeEnum} from '~/shared/constants/non-common-consts'; @Injectable() export class ExperimentsCompareChartsEffects { - constructor(private actions$: Actions, private store: Store, private apiTasks: ApiTasksService, + constructor(private actions$: Actions, private store: Store, private apiTasks: ApiTasksService, private authApi: ApiAuthService, private taskBl: BlTasksService, private eventsApi: ApiEventsService) { } diff --git a/src/app/webapp-common/experiments-compare/effects/experiments-compare-details.effects.ts b/src/app/webapp-common/experiments-compare/effects/experiments-compare-details.effects.ts index 81f39bf2..e7dc3959 100644 --- a/src/app/webapp-common/experiments-compare/effects/experiments-compare-details.effects.ts +++ b/src/app/webapp-common/experiments-compare/effects/experiments-compare-details.effects.ts @@ -26,7 +26,7 @@ export class ExperimentsCompareDetailsEffects { constructor(private actions$: Actions, private tasksApi: ApiTasksService, private modelsApi: ApiModelsService, - private store: Store, + private store: Store, private experimentDetailsReverter: ExperimentDetailsReverterService, private modelDetailsReverter: ModelDetailsReverterService, ) { diff --git a/src/app/webapp-common/experiments-compare/effects/experiments-compare-params.effects.ts b/src/app/webapp-common/experiments-compare/effects/experiments-compare-params.effects.ts index 254cbb0c..3b5a7866 100644 --- a/src/app/webapp-common/experiments-compare/effects/experiments-compare-params.effects.ts +++ b/src/app/webapp-common/experiments-compare/effects/experiments-compare-params.effects.ts @@ -24,7 +24,7 @@ import {EntityTypeEnum} from '~/shared/constants/non-common-consts'; export class ExperimentsCompareParamsEffects { constructor(private actions$: Actions, - private store: Store, + private store: Store, private tasksApi: ApiTasksService, private modelsApi: ApiModelsService, private experimentParamsReverter: ExperimentParamsReverterService, diff --git a/src/app/webapp-common/experiments-compare/effects/select-experiment-for-compare-effects.service.ts b/src/app/webapp-common/experiments-compare/effects/select-experiment-for-compare-effects.service.ts index 65ffa1dd..f65aa4a3 100644 --- a/src/app/webapp-common/experiments-compare/effects/select-experiment-for-compare-effects.service.ts +++ b/src/app/webapp-common/experiments-compare/effects/select-experiment-for-compare-effects.service.ts @@ -16,7 +16,7 @@ import {flatten, isEmpty} from 'lodash-es'; import {selectExperimentsUpdateTime} from '../reducers'; import {selectRouterParams} from '../../core/reducers/router-reducer'; import {selectAppVisible} from '../../core/reducers/view.reducer'; -import {MINIMUM_ONLY_FIELDS} from '../../experiments/experiment.consts'; +import {INITIAL_EXPERIMENT_TABLE_COLS, MINIMUM_ONLY_FIELDS} from '../../experiments/experiment.consts'; import * as exSelectors from '../../experiments/reducers'; import { selectExperimentsMetricsCols, @@ -71,18 +71,18 @@ export class SelectCompareHeaderEffects { }), switchMap(([action, , experimentsIds, experimentsUpdateTime, isLimitedView]) => // eslint-disable-next-line @typescript-eslint/naming-convention - (action.entityType === EntityTypeEnum.experiment ? this.experimentsApi.tasksGetAllEx({ - id: isLimitedView ? experimentsIds.slice(0, LIMITED_VIEW_LIMIT) : experimentsIds, - // eslint-disable-next-line @typescript-eslint/naming-convention - only_fields: ['last_change'] - }) : this.modelsApi.modelsGetAllEx({ + (action.entityType === EntityTypeEnum.model ? this.modelsApi.modelsGetAllEx({ id: isLimitedView ? experimentsIds.slice(0, LIMITED_VIEW_LIMIT) : experimentsIds, // eslint-disable-next-line @typescript-eslint/naming-convention only_fields: ['last_update'] + }) : this.experimentsApi.tasksGetAllEx({ + id: isLimitedView ? experimentsIds.slice(0, LIMITED_VIEW_LIMIT) : experimentsIds, + // eslint-disable-next-line @typescript-eslint/naming-convention + only_fields: ['last_change'] })).pipe( mergeMap((res) => { const updatedExperimentsUpdateTime: { [key: string]: Date } = {}; - res[action.entityType === EntityTypeEnum.experiment ? 'tasks' : 'models'].forEach(task => { + res[action.entityType === EntityTypeEnum.model ? 'models' : 'tasks'].forEach(task => { updatedExperimentsUpdateTime[task.id] = task.last_change; }); const experimentsWhereUpdated = experimentsIds.some(id => @@ -122,7 +122,7 @@ export class SelectCompareHeaderEffects { id: action.tasksIds ? action.tasksIds : tasksIds, // eslint-disable-next-line @typescript-eslint/naming-convention only_fields: [...new Set([...MINIMUM_ONLY_FIELDS, - ...flatten(cols.filter(col => col.id !== 'selected' && !col.hidden).map(col => col.getter || col.id)), + ...flatten((cols.length > 0 ? cols : INITIAL_EXPERIMENT_TABLE_COLS).filter(col => col.id !== 'selected' && !col.hidden).map(col => col.getter || col.id)), ...(metricCols ? flatten(metricCols.map(col => (col?.isParam && typeof col.getter === 'string') ? encodeHyperParameter(col.getter) : col.getter || col.id)) : [])])] as string[] }).pipe( mergeMap((res) => [setSearchExperimentsForCompareResults({payload: [...res?.tasks]}), deactivateLoader(action.type)]), diff --git a/src/app/webapp-common/experiments-compare/experiment-compare-router-helper.guard.ts b/src/app/webapp-common/experiments-compare/experiment-compare-router-helper.guard.ts index e6f249fc..db302017 100755 --- a/src/app/webapp-common/experiments-compare/experiment-compare-router-helper.guard.ts +++ b/src/app/webapp-common/experiments-compare/experiment-compare-router-helper.guard.ts @@ -5,7 +5,7 @@ import {Store} from '@ngrx/store'; @Injectable({providedIn: 'root'}) export class RouterHelperGuard implements CanActivate { - constructor(public router: Router, public store: Store) { + constructor(public router: Router, public store: Store) { } canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { diff --git a/src/app/webapp-common/experiments-compare/experiments-compare.component.ts b/src/app/webapp-common/experiments-compare/experiments-compare.component.ts index 4537845a..a2e95976 100644 --- a/src/app/webapp-common/experiments-compare/experiments-compare.component.ts +++ b/src/app/webapp-common/experiments-compare/experiments-compare.component.ts @@ -1,12 +1,19 @@ import {ChangeDetectionStrategy, Component, OnDestroy, OnInit} from '@angular/core'; import {Store} from '@ngrx/store'; import {selectRouterQueryParams} from '../core/reducers/router-reducer'; -import {Subscription} from 'rxjs'; +import {Observable, Subscription} from 'rxjs'; import {ActivatedRoute, Params, Router} from '@angular/router'; import {selectNavigationPreferences} from './reducers'; -import {debounceTime} from 'rxjs/operators'; -import {setNavigationPreferences} from './actions/compare-header.actions'; +import {debounceTime, filter, withLatestFrom} from 'rxjs/operators'; +import {resetSelectCompareHeader, setNavigationPreferences} from './actions/compare-header.actions'; import {EntityTypeEnum} from '~/shared/constants/non-common-consts'; +import {getCompanyTags, setBreadcrumbsOptions, setSelectedProject} from '@common/core/actions/projects.actions'; +import {selectSelectedProject} from '@common/core/reducers/projects.reducer'; +import {Project} from '~/business-logic/model/projects/project'; +import {TitleCasePipe} from '@angular/common'; +import {resetSelectModelState} from '@common/select-model/select-model.actions'; +import {selectProjectType} from '~/core/reducers/view.reducer'; +import {ALL_PROJECTS_OBJECT} from '@common/core/effects/projects.effects'; @Component({ selector: 'sm-experiments-compare', @@ -15,15 +22,35 @@ import {EntityTypeEnum} from '~/shared/constants/non-common-consts'; changeDetection: ChangeDetectionStrategy.OnPush }) export class ExperimentsCompareComponent implements OnInit, OnDestroy { - private queryParams$: Subscription; - private navigationPreferences$: Subscription; + private subs = new Subscription(); private queryParams: Params; public entityType: EntityTypeEnum; public entityTypeEnum = EntityTypeEnum; + private selectedProject$: Observable; + private modelsFeature: boolean; - constructor(private store: Store, private router: Router, private activatedRoute: ActivatedRoute) { + constructor(private store: Store, private router: Router, private activatedRoute: ActivatedRoute, private titleCasePipe: TitleCasePipe) { // updating URL with store query params - this.navigationPreferences$ = this.store.select(selectNavigationPreferences).pipe(debounceTime(10)).subscribe((queryParams) => { + this.selectedProject$ = this.store.select(selectSelectedProject); + this.entityType = this.activatedRoute.snapshot.data.entityType; + this.modelsFeature = this.activatedRoute.snapshot.data?.setAllProject; + if (this.modelsFeature) { + this.store.dispatch(getCompanyTags()); + } + } + + ngOnDestroy(): void { + this.subs.unsubscribe(); + this.store.dispatch(resetSelectCompareHeader({fullReset: true})); + this.store.dispatch(resetSelectModelState({fullReset: true})); + } + + ngOnInit(): void { + // Update store with url query params on load + this.subs.add(this.selectedProject$.pipe(filter(selectedProject => (this.modelsFeature && !selectedProject))).subscribe(() => + this.store.dispatch(setSelectedProject({project: ALL_PROJECTS_OBJECT})) + )); + this.subs.add(this.store.select(selectNavigationPreferences).pipe(debounceTime(10)).subscribe((queryParams) => { this.router.navigate( [], { @@ -31,21 +58,11 @@ export class ExperimentsCompareComponent implements OnInit, OnDestroy { queryParams, queryParamsHandling: 'merge' }); - }); + })); - this.queryParams$ = this.store.select(selectRouterQueryParams).subscribe((queryParams) => this.queryParams = queryParams); - - this.entityType = this.activatedRoute.snapshot.data.entityType; - } - - ngOnDestroy(): void { - this.queryParams$.unsubscribe(); - this.navigationPreferences$.unsubscribe(); - } - - ngOnInit(): void { - // Update store with url query params on load + this.subs.add(this.store.select(selectRouterQueryParams).subscribe((queryParams) => this.queryParams = queryParams)); this.store.dispatch(setNavigationPreferences({navigationPreferences: this.queryParams})); + this.setupBreadcrumbsOptions(); } updateUrl(ids: string[]) { @@ -55,4 +72,52 @@ export class ExperimentsCompareComponent implements OnInit, OnDestroy { relativeTo: this.activatedRoute, }); } + + setupBreadcrumbsOptions() { + this.subs.add(this.selectedProject$.pipe( + withLatestFrom(this.store.select(selectProjectType)) + ).subscribe(([selectedProject, projectType]) => { + const projectTypeBasePath = { + projects: 'projects', + datasets: 'datasets/simple', + pipelines: 'pipelines' + }; + if (this.modelsFeature) { + this.store.dispatch(setBreadcrumbsOptions({ + breadcrumbOptions: { + showProjects: false, + featureBreadcrumb: {name: 'Models', url: 'models'}, + subFeatureBreadcrumb: { + name: `Compare ${this.titleCasePipe.transform(this.entityType)}s` + }, + } + })); + } else { + this.store.dispatch(setBreadcrumbsOptions({ + breadcrumbOptions: { + showProjects: !!selectedProject, + featureBreadcrumb: { + name: this.titleCasePipe.transform(projectType), + url: projectType + }, + subFeatureBreadcrumb: { + name: `Compare ${this.titleCasePipe.transform(this.entityType)}s` + }, + projectsOptions: { + basePath: projectTypeBasePath[projectType], + filterBaseNameWith: null, + compareModule: null, + showSelectedProject: selectedProject && selectedProject?.id !== '*', + ...(selectedProject && { + selectedProjectBreadcrumb: { + name: selectedProject?.id === '*' ? `All ${this.titleCasePipe.transform(this.entityType)}s` : selectedProject?.basename, + url: `${projectTypeBasePath[projectType]}/${selectedProject?.id}/${this.entityType === 'model' ? 'model' : 'experiment'}s` + } + }) + } + } + })); + } + })); + } } diff --git a/src/app/webapp-common/experiments-compare/experiments-compare.module.ts b/src/app/webapp-common/experiments-compare/experiments-compare.module.ts index 28f6ffdf..f1c97886 100644 --- a/src/app/webapp-common/experiments-compare/experiments-compare.module.ts +++ b/src/app/webapp-common/experiments-compare/experiments-compare.module.ts @@ -1,8 +1,8 @@ -import {NgModule} from '@angular/core'; +import {InjectionToken, NgModule} from '@angular/core'; import {CommonModule} from '@angular/common'; import {ExperimentsCompareComponent} from './experiments-compare.component'; import {ExperimentsCompareRoutingModule} from './experiments-compare-routing.module'; -import {StoreModule} from '@ngrx/store'; +import {ActionReducer, StoreConfig, StoreModule} from '@ngrx/store'; import {EffectsModule} from '@ngrx/effects'; import {experimentsCompareReducers} from './reducers'; import {ExperimentsCompareDetailsEffects} from './effects/experiments-compare-details.effects'; @@ -58,11 +58,43 @@ import {UiComponentsModule} from '../shared/ui-components/ui-components.module'; import {SMMaterialModule} from '../shared/material/material.module'; import {SharedPipesModule} from '@common/shared/pipes/shared-pipes.module'; import {ModelCompareDetailsComponent} from '@common/experiments-compare/containers/model-compare-details/model-compare-details.component'; +import {IExperimentCompareChartsState} from '@common/experiments-compare/reducers/experiments-compare-charts.reducer'; +import {UserPreferences} from '@common/user-preferences'; +import {merge, pick} from 'lodash-es'; +import {EXPERIMENTS_PREFIX, EXPERIMENTS_STORE_KEY} from '@common/experiments/experiment.consts'; +import {createUserPrefFeatureReducer} from '@common/core/meta-reducers/user-pref-reducer'; +import {EXPERIMENTS_COMPARE_METRICS_CHARTS_} from '@common/experiments-compare/actions/experiments-compare-charts.actions'; +import {EXPERIMENTS_COMPARE_SELECT_EXPERIMENT_} from '@common/experiments-compare/actions/compare-header.actions'; + +export const COMPARE_CONFIG_TOKEN = + new InjectionToken>('CompareConfigToken'); export const compareSyncedKeys = [ - 'charts.settingsList', ]; +const localStorageKey = '_saved_compare_state_'; + +export const getCompareConfig = (userPreferences: UserPreferences) => ({ + metaReducers: [ + reducer => { + let onInit = true; + return (state, action) => { + const nextState = reducer(state, action); + if (onInit) { + onInit = false; + const savedState = JSON.parse(localStorage.getItem(localStorageKey)); + return merge({}, nextState, savedState); + } + if (action.type.startsWith(EXPERIMENTS_PREFIX)) { + localStorage.setItem(localStorageKey, JSON.stringify(pick(nextState, ['charts.settingsList']))); + } + return nextState; + }; + }, + (reducer: ActionReducer) => + createUserPrefFeatureReducer(EXPERIMENTS_STORE_KEY, compareSyncedKeys, [EXPERIMENTS_COMPARE_METRICS_CHARTS_, EXPERIMENTS_COMPARE_SELECT_EXPERIMENT_], userPreferences, reducer), + ] +}); @NgModule({ declarations: [ @@ -100,7 +132,7 @@ export const compareSyncedKeys = [ ExperimentsCompareRoutingModule, ExperimentGraphsModule, ExperimentCompareSharedModule, - StoreModule.forFeature('experimentsCompare', experimentsCompareReducers), + StoreModule.forFeature('experimentsCompare', experimentsCompareReducers, COMPARE_CONFIG_TOKEN), EffectsModule.forFeature([ ExperimentsCompareDetailsEffects, ExperimentsCompareParamsEffects, @@ -113,7 +145,10 @@ export const compareSyncedKeys = [ FormsModule, ParallelCoordinatesGraphComponent, SharedPipesModule, - ] + ], + providers: [ + {provide: COMPARE_CONFIG_TOKEN, useFactory: getCompareConfig, deps: [UserPreferences]}, + ], }) export class ExperimentsCompareModule { } diff --git a/src/app/webapp-common/experiments-compare/reducers/compare-header.reducer.ts b/src/app/webapp-common/experiments-compare/reducers/compare-header.reducer.ts index 0bafc1cd..4dfff9b0 100644 --- a/src/app/webapp-common/experiments-compare/reducers/compare-header.reducer.ts +++ b/src/app/webapp-common/experiments-compare/reducers/compare-header.reducer.ts @@ -62,7 +62,12 @@ export const compareHeader = createReducer( ...state, navigationPreferences: {...state.navigationPreferences, ...navigationPreferences} })), - on(resetSelectCompareHeader, () => ({...initialState})), + on(resetSelectCompareHeader, (state, action) => ({ + ...initialState, + ...(!action.fullReset && {projectColumnFilters: state.projectColumnFilters, + viewArchived: state.viewArchived, + projectColumnsSortOrder: state.projectColumnsSortOrder}) + })), on(compareAddDialogSetTableSort, (state, action) => { let orders = action.orders.filter(order => action.colIds.includes(order.field)); orders = orders.length > 0 ? orders : null; @@ -72,7 +77,7 @@ export const compareHeader = createReducer( ...state, projectColumnFilters: { ...state.projectColumnFilters, - [action.projectId]: {['project.name']: {value: [action.projectId], matchMode: undefined}} + [action.projectId]: {...state.projectColumnFilters[action.projectId], ['project.name']: {value: [action.projectId], matchMode: undefined}} } })), on(compareAddTableClearAllFilters, (state, action) => ({ diff --git a/src/app/webapp-common/experiments-compare/reducers/experiments-compare-charts.reducer.ts b/src/app/webapp-common/experiments-compare/reducers/experiments-compare-charts.reducer.ts index b03a8c29..319a7622 100644 --- a/src/app/webapp-common/experiments-compare/reducers/experiments-compare-charts.reducer.ts +++ b/src/app/webapp-common/experiments-compare/reducers/experiments-compare-charts.reducer.ts @@ -61,14 +61,14 @@ export const experimentsCompareChartsReducer = createReducer( on(actions.setExperimentSettings, (state, action) => { let newSettings: ExperimentCompareSettings[]; const changes = {...action.changes, id: action.id, lastModified: (new Date()).getTime()} as ExperimentCompareSettings; - const ids = action.id.join(); + const ids = action.id ? action.id.join() : ''; const experimentExists = state.settingsList.find((setting) => setting.id.join() === ids); const discardBefore = new Date(); discardBefore.setMonth(discardBefore.getMonth() - 6); if (experimentExists) { newSettings = state.settingsList .filter(setting => discardBefore < new Date(setting.lastModified || 1648771200000)) - .map(setting => setting.id.join() === ids ? {...setting, ...changes} : setting); + .map(setting => setting.id?.join() === ids ? {...setting, ...changes} : setting); } else { newSettings = [ ...state.settingsList.filter(setting => discardBefore < new Date(setting.lastModified || 1648771200000)), diff --git a/src/app/webapp-common/experiments-compare/reducers/index.ts b/src/app/webapp-common/experiments-compare/reducers/index.ts index e4ef2700..81cdccb7 100644 --- a/src/app/webapp-common/experiments-compare/reducers/index.ts +++ b/src/app/webapp-common/experiments-compare/reducers/index.ts @@ -23,6 +23,7 @@ import {groupByCharts, GroupByCharts} from '../../experiments/reducers/experimen import {selectSelectedProjectId} from '../../core/reducers/projects.reducer'; import {selectRouterConfig} from '../../core/reducers/router-reducer'; import {MetricValueType} from '@common/experiments-compare/experiments-compare.constants'; +import {smoothTypeEnum, SmoothTypeEnum} from '@common/shared/single-graph/single-graph.utils'; export const experimentsCompareReducers: ActionReducerMap = { details: experimentsCompareDetailsReducer, @@ -54,6 +55,7 @@ export const selectExperimentIdsParams = createSelector(selectExperimentsParams, // select experiments for compare and header export const selectCompareHeader = createSelector(experimentsCompare, state => (state?.compareHeader ?? {}) as CompareHeaderState); export const selectIsCompare = createSelector(selectRouterConfig, (config): boolean => config?.includes('compare-experiments')); +export const selectIsSelectModel = createSelector(selectRouterConfig, (config): boolean => config?.includes('compare-models') || config?.includes('input-model')); export const selectIsModels = createSelector(selectRouterConfig, (config): boolean => config?.includes('models')); export const selectIsPipelines = createSelector(selectRouterConfig, (config): boolean => config?.[0] === 'pipelines'); export const selectIsDatasets = createSelector(selectRouterConfig, (config): boolean => config?.[0] === 'datasets'); @@ -85,7 +87,7 @@ export const selectCompareHistogramCacheAxisType = createSelector(compareCharts, export const selectCompareTasksPlotCharts = createSelector(compareCharts, state => state.metricsPlotsCharts); export const selectSelectedExperimentSettings = createSelector(compareCharts, selectSelectedExperiments, - (output, currentExperiments): ExperimentCompareSettings => output.settingsList && output.settingsList.find((setting) => currentExperiments && setting.id.join() === currentExperiments.join())); + (output, currentExperiments): ExperimentCompareSettings => output.settingsList && output.settingsList.find((setting) => currentExperiments && setting.id?.join() === currentExperiments.join())); export const selectSelectedSettingsHiddenPlot = createSelector(selectSelectedExperimentSettings, (settings): Array => settings?.hiddenMetricsPlot || []); @@ -102,6 +104,8 @@ export const selectSelectedSettingsHiddenScalar = createSelector(selectSelectedE export const selectExperimentMetricsSearchTerm = createSelector(compareCharts, (state) => state.searchTerm); export const selectCompareSelectedSettingsSmoothWeight = createSelector(selectSelectedExperimentSettings, (settings): number => settings?.smoothWeight || 0); +export const selectSelectedSettingsSmoothType = createSelector(selectSelectedExperimentSettings, + (settings): SmoothTypeEnum => settings?.smoothType ?? smoothTypeEnum.exponential); export const selectCompareSelectedSettingsxAxisType = createSelector(selectSelectedExperimentSettings, settings => settings?.xAxisType ?? ScalarKeyEnum.Iter as ScalarKeyEnum); diff --git a/src/app/webapp-common/experiments/actions/common-experiments-menu.actions.ts b/src/app/webapp-common/experiments/actions/common-experiments-menu.actions.ts index 2b81566c..a38d6b4f 100644 --- a/src/app/webapp-common/experiments/actions/common-experiments-menu.actions.ts +++ b/src/app/webapp-common/experiments/actions/common-experiments-menu.actions.ts @@ -82,6 +82,10 @@ export const enqueueClicked = createAction( EXPERIMENTS_INFO_PREFIX + '[enqueue experiments]', props<{ selectedEntities: ISelectedExperiment[]; queue: Queue; verifyWatchers: boolean }>() ); +export const openEmptyQueueMessage = createAction( + EXPERIMENTS_INFO_PREFIX + '[open empty queue message]', + props<{ queue: Queue }>() +); export const archiveSelectedExperiments = createAction( EXPERIMENTS_INFO_PREFIX + '[archive selected experiments]', diff --git a/src/app/webapp-common/experiments/actions/common-experiments-view.actions.ts b/src/app/webapp-common/experiments/actions/common-experiments-view.actions.ts index 7469c9e1..d539929b 100755 --- a/src/app/webapp-common/experiments/actions/common-experiments-view.actions.ts +++ b/src/app/webapp-common/experiments/actions/common-experiments-view.actions.ts @@ -9,6 +9,10 @@ import {CountAvailableAndIsDisableSelectedFiltered} from '@common/shared/entity- import {TasksEnqueueManyResponseSucceeded} from '~/business-logic/model/tasks/tasksEnqueueManyResponseSucceeded'; import {EXPERIMENTS_INFO_PREFIX} from '@common/experiments/actions/common-experiments-menu.actions'; import {EXPERIMENTS_PREFIX} from '@common/experiments/experiment.consts'; +import { + OrganizationPrepareDownloadForGetAllRequest +} from '~/business-logic/model/organization/organizationPrepareDownloadForGetAllRequest'; +import {PROJECTS_PREFIX} from '@common/core/actions/projects.actions'; // COMMANDS: export const getExperiments = createAction(EXPERIMENTS_PREFIX + ' [get experiments]'); @@ -119,7 +123,7 @@ export const setActiveParentsFilter = createAction( export const getParents = createAction( EXPERIMENTS_PREFIX + '[get project experiments parents]', - props<{searchValue: string}>()); + props<{searchValue: string; allProjects?: boolean}>()); export const tableFilterChanged = createAction( EXPERIMENTS_PREFIX + '[table filter changed]', @@ -146,7 +150,8 @@ export const setProjectsTypes = createAction( props<{ types?: Array }>() ); -export const getProjectTypes = createAction(EXPERIMENTS_PREFIX + 'GET_PROJECT_TYPES'); +export const getProjectTypes = createAction(EXPERIMENTS_PREFIX + 'GET_PROJECT_TYPES', + props<{allProjects?: boolean}>()); export const showOnlySelected = createAction( EXPERIMENTS_PREFIX + ' [show only selected]', @@ -225,7 +230,8 @@ export const hyperParamSelectedExperiments = createAction( props<{ col: ISmCol }>() ); -export const getTags = createAction(EXPERIMENTS_PREFIX + ' [get experiments tags]'); +export const getTags = createAction(EXPERIMENTS_PREFIX + ' [get experiments tags]' , + props<{allProjects?: boolean}>()); export const setTags = createAction( EXPERIMENTS_PREFIX + '[set experiment tags]', @@ -243,3 +249,7 @@ export const setTableMode = createAction( EXPERIMENTS_PREFIX + '[set table view mode]', props<{ mode: 'info' | 'table' }>() ); +export const prepareTableForDownload = createAction( + EXPERIMENTS_PREFIX + ' [prepareTableForDownload]', + props<{ entityType: OrganizationPrepareDownloadForGetAllRequest.EntityTypeEnum }>() +); diff --git a/src/app/webapp-common/experiments/containers/experiment-info-aritfacts/experiment-info-aritfacts.component.scss b/src/app/webapp-common/experiments/containers/experiment-info-aritfacts/experiment-info-aritfacts.component.scss index 327f51bf..5ad7d6f4 100755 --- a/src/app/webapp-common/experiments/containers/experiment-info-aritfacts/experiment-info-aritfacts.component.scss +++ b/src/app/webapp-common/experiments/containers/experiment-info-aritfacts/experiment-info-aritfacts.component.scss @@ -6,21 +6,19 @@ position: relative; height: 100%; - .no-data{ - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 100%; - width: 100%; + .no-data { + text-align: center; + color: #ced1dc; + position: absolute; + top: 50%; + left: 50%; + transform: translateY(-50%) translateX(-50%); + display: block; .i-no-data-artifacts{ width: 200px; height: 160px; } - span{ - font-size: 20px; - font-weight: 500; - line-height: 1.45; + h3 { color: $cloudy-blue-two; } } diff --git a/src/app/webapp-common/experiments/containers/experiment-info-aritfacts/experiment-info-artifacts.component.html b/src/app/webapp-common/experiments/containers/experiment-info-aritfacts/experiment-info-artifacts.component.html index a2e0ea40..fba1030d 100755 --- a/src/app/webapp-common/experiments/containers/experiment-info-aritfacts/experiment-info-artifacts.component.html +++ b/src/app/webapp-common/experiments/containers/experiment-info-aritfacts/experiment-info-artifacts.component.html @@ -9,7 +9,7 @@ - +
- NO ARTIFACTS RECORDED +

NO ARTIFACTS RECORDED

diff --git a/src/app/webapp-common/experiments/containers/experiment-info-aritfacts/experiment-info-artifacts.component.ts b/src/app/webapp-common/experiments/containers/experiment-info-aritfacts/experiment-info-artifacts.component.ts index c36edcec..c9fa1901 100755 --- a/src/app/webapp-common/experiments/containers/experiment-info-aritfacts/experiment-info-artifacts.component.ts +++ b/src/app/webapp-common/experiments/containers/experiment-info-aritfacts/experiment-info-artifacts.component.ts @@ -38,7 +38,7 @@ export class ExperimentInfoArtifactsComponent implements OnDestroy { private previousTarget: string; private sub = new Subscription(); - constructor(private store: Store, public router: Router, private route: ActivatedRoute + constructor(private store: Store, public router: Router, private route: ActivatedRoute ) { this.minimized = !!this.route.snapshot?.routeConfig?.data?.minimized; this.backdropActive$ = this.store.select(selectBackdropActive); diff --git a/src/app/webapp-common/experiments/containers/experiment-info-artifact-item/experiment-info-artifact-item.component.ts b/src/app/webapp-common/experiments/containers/experiment-info-artifact-item/experiment-info-artifact-item.component.ts index c67f0404..932d53fe 100755 --- a/src/app/webapp-common/experiments/containers/experiment-info-artifact-item/experiment-info-artifact-item.component.ts +++ b/src/app/webapp-common/experiments/containers/experiment-info-artifact-item/experiment-info-artifact-item.component.ts @@ -22,7 +22,7 @@ export class ExperimentInfoArtifactItemComponent implements OnInit, OnDestroy { public selectedArtifact: Artifact; public artifactKey$: Observable; - constructor(private store: Store) { + constructor(private store: Store) { this.modelInfo$ = this.store.select(selectExperimentModelInfoData); this.artifactKey$ = this.store.select(selectRouterParams) .pipe( diff --git a/src/app/webapp-common/experiments/containers/experiment-info-execution/experiment-info-execution.component.html b/src/app/webapp-common/experiments/containers/experiment-info-execution/experiment-info-execution.component.html index b224913d..afcb0d88 100755 --- a/src/app/webapp-common/experiments/containers/experiment-info-execution/experiment-info-execution.component.html +++ b/src/app/webapp-common/experiments/containers/experiment-info-execution/experiment-info-execution.component.html @@ -29,7 +29,7 @@ [saving]="saving" [editable]="editable" (cancelClicked)="cancelFormChange()" - (saveClicked)="sourceCode.sourceCodeForm.valid? saveSourceData(): sourceCode.sourceCodeForm.onSubmit(null)" + (saveClicked)="sourceCode.sourceCodeForm.valid ? saveSourceData() : sourceCode.sourceCodeForm.onSubmit(null)" (activateEditClicked)="activateEditChanged('source', sourceSection)"> EDIT + (click)="diffSection.editModeChanged(true)" data-id="edit uncommitted changes">EDIT @@ -90,11 +90,11 @@ + (click)="requirementsSection.editModeChanged(true)" data-id="Edit Installed Packages">EDIT @@ -112,13 +112,13 @@
{{formData.container?.image}}
- +
{{formData.container?.arguments | hideRedactedArguments: (redactedArguments$| async) }}
- +
@@ -165,14 +165,14 @@
{{formData.output?.destination}}
- +
{{formData.output?.logLevel}}
- +
diff --git a/src/app/webapp-common/experiments/containers/experiment-info-execution/experiment-info-execution.component.scss b/src/app/webapp-common/experiments/containers/experiment-info-execution/experiment-info-execution.component.scss index d2990330..e8401abb 100755 --- a/src/app/webapp-common/experiments/containers/experiment-info-execution/experiment-info-execution.component.scss +++ b/src/app/webapp-common/experiments/containers/experiment-info-execution/experiment-info-execution.component.scss @@ -15,7 +15,7 @@ } mat-nav-list { - min-width: 300px; + min-width: 360px; padding-top: 0; } diff --git a/src/app/webapp-common/experiments/containers/experiment-info-execution/experiment-info-execution.component.ts b/src/app/webapp-common/experiments/containers/experiment-info-execution/experiment-info-execution.component.ts index d25b4246..08323a3a 100755 --- a/src/app/webapp-common/experiments/containers/experiment-info-execution/experiment-info-execution.component.ts +++ b/src/app/webapp-common/experiments/containers/experiment-info-execution/experiment-info-execution.component.ts @@ -2,11 +2,8 @@ import {Component, ElementRef, OnDestroy, OnInit, ViewChild} from '@angular/core import {Store} from '@ngrx/store'; import {Observable, Subscription} from 'rxjs'; import {IExecutionForm, sourceTypesEnum} from '~/features/experiments/shared/experiment-execution.model'; -import {IExperimentInfo} from '~/features/experiments/shared/experiment-info.model'; -import {ExperimentInfoState} from '~/features/experiments/reducers/experiment-info.reducer'; import { selectIsExperimentEditable, - selectSelectedExperiment, selectShowExtraDataSpinner } from '~/features/experiments/reducers'; import * as commonInfoActions from '../../actions/common-experiments-info.actions'; @@ -37,8 +34,6 @@ export class ExperimentInfoExecutionComponent implements OnInit, OnDestroy { public executionInfo$: Observable; public showExtraDataSpinner$: Observable; - public selectedExperimentSubscrition: Subscription; - private selectedExperiment: IExperimentInfo; public editable$: Observable; public isInDev$: Observable; public saving$: Observable; @@ -61,7 +56,7 @@ export class ExperimentInfoExecutionComponent implements OnInit, OnDestroy { public redactedArguments$: Observable<{ key: string }[]>; constructor( - private store: Store, + private store: Store, private dialog: MatDialog, private route: ActivatedRoute, private element: ElementRef @@ -78,10 +73,6 @@ export class ExperimentInfoExecutionComponent implements OnInit, OnDestroy { } ngOnInit() { - this.selectedExperimentSubscrition = this.store.select(selectSelectedExperiment) - .subscribe(selectedExperiment => { - this.selectedExperiment = selectedExperiment; - }); this.store.dispatch(commonInfoActions.setExperimentFormErrors({errors: null})); this.formDataSubscription = this.executionInfo$.subscribe(formData => { @@ -90,18 +81,18 @@ export class ExperimentInfoExecutionComponent implements OnInit, OnDestroy { } ngOnDestroy(): void { - this.selectedExperimentSubscrition.unsubscribe(); this.formDataSubscription.unsubscribe(); } saveSourceData() { - const source = this.sourceCode.sourceCodeForm.form.value; + const source = this.sourceCode.sourceCodeForm.form.value as IExecutionForm['source']; this.store.dispatch(commonInfoActions.saveExperimentSection({ script: { /* eslint-disable @typescript-eslint/naming-convention */ repository: source.repository, entry_point: source.entry_point, working_dir: source.working_dir, + binary: source.binary?.trim(), /* eslint-enable @typescript-eslint/naming-convention */ ...this.convertScriptType(source) } diff --git a/src/app/webapp-common/experiments/containers/experiment-info-general/experiment-info-general.component.html b/src/app/webapp-common/experiments/containers/experiment-info-general/experiment-info-general.component.html index 49b4064f..fee8aa92 100755 --- a/src/app/webapp-common/experiments/containers/experiment-info-general/experiment-info-general.component.html +++ b/src/app/webapp-common/experiments/containers/experiment-info-general/experiment-info-general.component.html @@ -1,5 +1,5 @@ ; public editable$: Observable; public experimentInfoData$: Observable; public isExample: boolean; private selectedExperiment: IExperimentInfo; + private sub = new Subscription(); - constructor(private store: Store) { + @ViewChild(ExperimentGeneralInfoComponent) info: ExperimentGeneralInfoComponent; + + constructor(private store: Store, private readonly ref: ElementRef) { this.selectedExperiment$ = this.store.select(selectSelectedExperiment) .pipe( filter(experiment => !!experiment), @@ -31,10 +37,22 @@ export class ExperimentInfoGeneralComponent { ); this.experimentInfoData$ = this.store.select(selectExperimentInfoData); this.editable$ = this.store.select(selectIsExperimentEditable); + this.sub.add(this.store.select(selectEditingDescription) + .pipe( + filter(edit => edit)) + .subscribe(() => { + this.ref.nativeElement.scrollTo({top: 0, behavior: 'smooth'}); + this.info?.experimentDescriptionSection.editModeChanged(true); + }) + ); } commentChanged(comment) { this.store.dispatch(experimentDetailsUpdated({id: this.selectedExperiment.id, changes: {comment}})); } + ngOnDestroy() { + this.sub.unsubscribe(); + } + } diff --git a/src/app/webapp-common/experiments/containers/experiment-info-hyper-parameters-form-container/experiment-info-hyper-parameters-form-container.component.html b/src/app/webapp-common/experiments/containers/experiment-info-hyper-parameters-form-container/experiment-info-hyper-parameters-form-container.component.html index 6702abcd..65ff6ced 100755 --- a/src/app/webapp-common/experiments/containers/experiment-info-hyper-parameters-form-container/experiment-info-hyper-parameters-form-container.component.html +++ b/src/app/webapp-common/experiments/containers/experiment-info-hyper-parameters-form-container/experiment-info-hyper-parameters-form-container.component.html @@ -2,9 +2,10 @@ @@ -25,7 +26,7 @@ #executionParamsForm class="form-section" [searchedText]="searchedText" - [editable]="(!isExample) && ((editable$ | async) || propSection) && parameterSection.inEditMode" + [editable]="(isExample$ | async) !== true && ((editable$ | async) || propSection) && parameterSection.inEditMode" [section]="selectedSection" [formData]="(selectedSectionHyperParams$ | async) | sort:'name'" [size]="size$ | async" diff --git a/src/app/webapp-common/experiments/containers/experiment-info-hyper-parameters-form-container/experiment-info-hyper-parameters-form-container.component.ts b/src/app/webapp-common/experiments/containers/experiment-info-hyper-parameters-form-container/experiment-info-hyper-parameters-form-container.component.ts index 0568e15f..ad91d341 100755 --- a/src/app/webapp-common/experiments/containers/experiment-info-hyper-parameters-form-container/experiment-info-hyper-parameters-form-container.component.ts +++ b/src/app/webapp-common/experiments/containers/experiment-info-hyper-parameters-form-container/experiment-info-hyper-parameters-form-container.component.ts @@ -1,17 +1,19 @@ -import {ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild} from '@angular/core'; +import {ChangeDetectorRef, Component, OnDestroy, ViewChild} from '@angular/core'; import {Store} from '@ngrx/store'; import { selectExperimentHyperParamsSelectedSectionFromRoute, selectExperimentHyperParamsSelectedSectionParams, selectIsExperimentSaving, - selectIsSelectedExperimentInDev, + selectIsSelectedExperimentInDev, selectSelectedExperimentReadOnly, selectSplitSize, } from '../../reducers'; -import {CommonExperimentInfoState} from '../../reducers/common-experiment-info.reducer'; import {IExperimentInfo} from '~/features/experiments/shared/experiment-info.model'; import {selectBackdropActive} from '@common/core/reducers/view.reducer'; import {Observable, Subscription} from 'rxjs'; -import {selectIsExperimentEditable, selectSelectedExperiment} from '~/features/experiments/reducers'; +import { + selectIsExperimentEditable, + selectSelectedExperiment, +} from '~/features/experiments/reducers'; import {selectRouterConfig} from '@common/core/reducers/router-reducer'; import { activateEdit, @@ -25,14 +27,13 @@ import { import {ParamsItem} from '~/business-logic/model/tasks/paramsItem'; import {Router} from '@angular/router'; import {ExperimentExecutionParametersComponent} from '../../dumb/experiment-execution-parameters/experiment-execution-parameters.component'; -import {isReadOnly} from '@common/shared/utils/is-read-only'; @Component({ selector : 'sm-experiment-info-hyper-parameters-form-container', templateUrl: './experiment-info-hyper-parameters-form-container.component.html', styleUrls : ['./experiment-info-hyper-parameters-form-container.component.scss'] }) -export class ExperimentInfoHyperParametersFormContainerComponent implements OnInit, OnDestroy { +export class ExperimentInfoHyperParametersFormContainerComponent implements OnDestroy { sectionReplaceMap = { _legacy: 'General', properties: 'User Properties', @@ -45,12 +46,11 @@ export class ExperimentInfoHyperParametersFormContainerComponent implements OnIn public isInDev$: Observable; public saving$: Observable; public backdropActive$: Observable; - private selectedExperimentSubscription: Subscription; public routerConfig$: Observable; public selectedSection$: Observable; private selectedSectionSubscription: Subscription; public selectedSection: string; - public isExample: boolean; + public isExample$ = this.store.select(selectSelectedExperimentReadOnly); public selectedExperiment$: Observable; public propSection: boolean; public searchedText: string; @@ -58,8 +58,7 @@ export class ExperimentInfoHyperParametersFormContainerComponent implements OnIn public scrollIndexCounter: number; public size$: Observable; - constructor(private store: Store, protected router: Router, private cdr: ChangeDetectorRef) { - + constructor(private store: Store, protected router: Router, private cdr: ChangeDetectorRef) { this.selectedSectionHyperParams$ = this.store.select(selectExperimentHyperParamsSelectedSectionParams); this.editable$ = this.store.select(selectIsExperimentEditable); this.selectedSection$ = this.store.select(selectExperimentHyperParamsSelectedSectionFromRoute); @@ -77,15 +76,7 @@ export class ExperimentInfoHyperParametersFormContainerComponent implements OnIn }); } - ngOnInit() { - this.selectedExperimentSubscription = this.store.select(selectSelectedExperiment) - .subscribe(selectedExperiment => { - this.isExample = isReadOnly(selectedExperiment); - }); - } - ngOnDestroy(): void { - this.selectedExperimentSubscription?.unsubscribe(); this.selectedSectionSubscription?.unsubscribe(); } diff --git a/src/app/webapp-common/experiments/containers/experiment-info-hyper-parameters/experiment-info-hyper-parameters.component.html b/src/app/webapp-common/experiments/containers/experiment-info-hyper-parameters/experiment-info-hyper-parameters.component.html index 92c12887..f2f722fa 100755 --- a/src/app/webapp-common/experiments/containers/experiment-info-hyper-parameters/experiment-info-hyper-parameters.component.html +++ b/src/app/webapp-common/experiments/containers/experiment-info-hyper-parameters/experiment-info-hyper-parameters.component.html @@ -1,6 +1,6 @@ - + , + private store: Store, protected router: Router, private route: ActivatedRoute ) { diff --git a/src/app/webapp-common/experiments/containers/experiment-info-model/experiment-info-model.component.ts b/src/app/webapp-common/experiments/containers/experiment-info-model/experiment-info-model.component.ts index 60c4ca31..b921515c 100755 --- a/src/app/webapp-common/experiments/containers/experiment-info-model/experiment-info-model.component.ts +++ b/src/app/webapp-common/experiments/containers/experiment-info-model/experiment-info-model.component.ts @@ -43,7 +43,7 @@ export class ExperimentInfoModelComponent implements OnInit, OnDestroy { @ViewChild('experimentModelForm') experimentModelForm: ExperimentModelsFormViewComponent; private orgModels: IModelInfo[]; - constructor(private store: Store, private router: Router, private route: ActivatedRoute) { + constructor(private store: Store, private router: Router, private route: ActivatedRoute) { this.modelInfo$ = this.store.select(selectExperimentModelInfoData); this.editable$ = this.store.select(selectIsExperimentEditable); this.userKnowledge$ = this.store.select(selectExperimentUserKnowledge); @@ -98,7 +98,7 @@ export class ExperimentInfoModelComponent implements OnInit, OnDestroy { ]; } this.store.dispatch(commonInfoActions.saveExperimentSection({models: {input: newModels as any}})); - return this.router.navigate([{modelId: selectedModelId || ''}], {relativeTo: this.route, replaceUrl: true}); + return this.router.navigate([{modelId: selectedModelId || ''}], {relativeTo: this.route, replaceUrl: true, queryParamsHandling:'preserve'}); } } diff --git a/src/app/webapp-common/experiments/containers/experiment-info-task-model/experiment-info-task-model.component.ts b/src/app/webapp-common/experiments/containers/experiment-info-task-model/experiment-info-task-model.component.ts index 056b0633..b156f762 100755 --- a/src/app/webapp-common/experiments/containers/experiment-info-task-model/experiment-info-task-model.component.ts +++ b/src/app/webapp-common/experiments/containers/experiment-info-task-model/experiment-info-task-model.component.ts @@ -42,7 +42,7 @@ export class ExperimentInfoTaskModelComponent implements OnInit, OnDestroy { @ViewChild('prototext') prototext: EditableSectionComponent; - constructor(private store: Store, private dialog: MatDialog) { + constructor(private store: Store, private dialog: MatDialog) { this.configInfo$ = this.store.select(selectExperimentConfigObj); this.selectedConfigObj$ = this.store.select(selectExperimentSelectedConfigObjectFromRoute); this.editable$ = this.store.select(selectIsExperimentEditable); diff --git a/src/app/webapp-common/experiments/containers/experiment-ouptut/base-experiment-output.component.scss b/src/app/webapp-common/experiments/containers/experiment-ouptut/base-experiment-output.component.scss index 5269aefb..7afec96f 100644 --- a/src/app/webapp-common/experiments/containers/experiment-ouptut/base-experiment-output.component.scss +++ b/src/app/webapp-common/experiments/containers/experiment-ouptut/base-experiment-output.component.scss @@ -24,10 +24,6 @@ $output-tabs-height: 64px; ::ng-deep .no-output-icon { height: 100px; width: 150px; - left: 50%; - transform: translateX(-50%); - margin-bottom: 10px; - position: relative; } sm-experiment-settings.maximized { @@ -48,13 +44,9 @@ $output-tabs-height: 64px; .output-body { position: relative; - height: calc(100% - #{$output-tabs-height + $experiment-info-header-height + 48px}); + height: calc(100% - #{$output-tabs-height + $experiment-info-header-height + 32px}); &.minimized { - height: calc(100% - #{$experiment-info-header-height + $experiment-info-tabs-height + 15px}); + height: calc(100% - #{$experiment-info-header-height + $experiment-info-tabs-height + 3px}); } } - - sm-experiment-info-header { - margin-bottom: 12px; - } } diff --git a/src/app/webapp-common/experiments/containers/experiment-ouptut/base-experiment-output.component.ts b/src/app/webapp-common/experiments/containers/experiment-ouptut/base-experiment-output.component.ts index cee72c11..ba7ddc78 100644 --- a/src/app/webapp-common/experiments/containers/experiment-ouptut/base-experiment-output.component.ts +++ b/src/app/webapp-common/experiments/containers/experiment-ouptut/base-experiment-output.component.ts @@ -23,6 +23,8 @@ import {isDevelopment} from '~/features/experiments/shared/experiments.utils'; import * as experimentsActions from '../../actions/common-experiments-view.actions'; import {isReadOnly} from '@common/shared/utils/is-read-only'; import {MESSAGES_SEVERITY} from '@common/constants'; +import {setBreadcrumbsOptions} from '@common/core/actions/projects.actions'; +import {selectSelectedProject} from '@common/core/reducers/projects.reducer'; @Component({ selector: 'sm-base-experiment-output', @@ -47,10 +49,11 @@ export abstract class BaseExperimentOutputComponent implements OnInit, OnDestroy public isDevelopment: boolean; private toMaximize = false; public selectSplitSize$: Observable; + private selectedProject$: Observable; constructor( - private store: Store, + private store: Store, private router: Router, private route: ActivatedRoute, private refresh: RefreshService @@ -61,6 +64,8 @@ export abstract class BaseExperimentOutputComponent implements OnInit, OnDestroy this.isAppVisible$ = this.store.select(selectAppVisible); this.backdropActive$ = this.store.select(selectBackdropActive); this.selectSplitSize$ = this.store.select(selectSplitSize); + this.selectedProject$ = this.store.select(selectSelectedProject); + } @@ -68,6 +73,9 @@ export abstract class BaseExperimentOutputComponent implements OnInit, OnDestroy ngOnInit() { this.subs.add(this.store.select(selectRouterConfig).subscribe(routerConfig => { this.minimized = !routerConfig.includes('output'); + if (!this.minimized){ + this.setupBreadcrumbsOptions(); + } this.routerConfig = routerConfig; })); @@ -161,4 +169,25 @@ export abstract class BaseExperimentOutputComponent implements OnInit, OnDestroy this.store.dispatch(experimentsActions.setTableMode({mode: 'table'})); return this.router.navigate(['..'], {relativeTo: this.route, queryParamsHandling: 'merge'}); } + setupBreadcrumbsOptions() { + this.subs.add(this.selectedProject$.pipe( + ).subscribe((selectedProject) => { + this.store.dispatch(setBreadcrumbsOptions({ + breadcrumbOptions: { + showProjects: !!selectedProject, + featureBreadcrumb: { + name: 'PROJECTS', + url: 'projects' + }, + projectsOptions: { + basePath: 'projects', + filterBaseNameWith: null, + compareModule: null, + showSelectedProject: selectedProject?.id !== '*', + ...(selectedProject && {selectedProjectBreadcrumb: {name: selectedProject?.id === '*' ? 'All Experiments' : selectedProject?.basename}}) + } + } + })); + })); + } } diff --git a/src/app/webapp-common/experiments/containers/experiment-output-log/experiment-output-log.component.ts b/src/app/webapp-common/experiments/containers/experiment-output-log/experiment-output-log.component.ts index e541970e..aad5b0ad 100755 --- a/src/app/webapp-common/experiments/containers/experiment-output-log/experiment-output-log.component.ts +++ b/src/app/webapp-common/experiments/containers/experiment-output-log/experiment-output-log.component.ts @@ -55,7 +55,7 @@ export class ExperimentOutputLogComponent implements OnInit, AfterViewInit, OnDe private experiment$ = new BehaviorSubject(null); @ViewChildren(ExperimentLogInfoComponent) private logRefs: QueryList; - constructor(private store: Store, private cdr: ChangeDetectorRef, private refresh: RefreshService) { + constructor(private store: Store, private cdr: ChangeDetectorRef, private refresh: RefreshService) { this.log$ = this.store.select(selectExperimentLog); this.logBeginning$ = this.store.select(selectExperimentBeginningOfLog); this.filter$ = this.store.select(selectLogFilter); diff --git a/src/app/webapp-common/experiments/containers/experiment-output-plots/experiment-output-plots.component.html b/src/app/webapp-common/experiments/containers/experiment-output-plots/experiment-output-plots.component.html index 22475e7a..3098b60a 100644 --- a/src/app/webapp-common/experiments/containers/experiment-output-plots/experiment-output-plots.component.html +++ b/src/app/webapp-common/experiments/containers/experiment-output-plots/experiment-output-plots.component.html @@ -1,6 +1,6 @@ - + - +
, - private router: Router, + private store: Store, private activeRoute: ActivatedRoute, private changeDetection: ChangeDetectorRef, - private reportEmbed: ReportCodeEmbedService + private reportEmbed: ReportCodeEmbedService, + protected refreshService: RefreshService ) { this.searchTerm$ = this.store.select(selectExperimentMetricsSearchTerm); this.splitSize$ = this.store.select(selectSplitSize); @@ -80,6 +80,7 @@ export class ExperimentOutputPlotsComponent implements OnInit, OnDestroy, OnChan .pipe( filter(settings => !!settings), map(settings => settings ? settings.selectedPlot : null), + filter(selectedPlot => selectedPlot !== undefined), distinctUntilChanged() ); @@ -95,7 +96,7 @@ export class ExperimentOutputPlotsComponent implements OnInit, OnDestroy, OnChan } ngOnChanges(changes: SimpleChanges): void { - if(changes.selected && this.experimentId!== changes.selected.currentValue.id ){ + if(changes.selected && this.experimentId !== changes.selected.currentValue.id ){ this.dark = true; this.experimentId = changes.selected.currentValue.id; this.refresh(); @@ -124,6 +125,7 @@ export class ExperimentOutputPlotsComponent implements OnInit, OnDestroy, OnChan .subscribe((selectedPlot) => { this.selectedGraph = selectedPlot; this.graphsComponent?.scrollToGraph(selectedPlot); + this.store.dispatch(setExperimentSettings({id: this.experimentId, changes: {selectedPlot: null}})); })); this.subs.add(this.routerParams$ @@ -140,13 +142,17 @@ export class ExperimentOutputPlotsComponent implements OnInit, OnDestroy, OnChan this.subs.add(this.store.select(selectSelectedExperiment) .pipe( filter(experiment => !!experiment && !this.isDatasetVersionPreview), - distinctUntilChanged() + distinctUntilKeyChanged('id') ) .subscribe(experiment => { this.experimentId = experiment.id; this.refresh(); })); + this.subs.add(this.refreshService.tick + .pipe(filter(autoRefresh => autoRefresh !== null && !!this.experimentId)) + .subscribe(() => this.refresh()) + ); } ngOnDestroy() { diff --git a/src/app/webapp-common/experiments/containers/experiment-output-scalars/experiment-output-scalars.component.html b/src/app/webapp-common/experiments/containers/experiment-output-scalars/experiment-output-scalars.component.html index 437f717e..55711532 100644 --- a/src/app/webapp-common/experiments/containers/experiment-output-scalars/experiment-output-scalars.component.html +++ b/src/app/webapp-common/experiments/containers/experiment-output-scalars/experiment-output-scalars.component.html @@ -1,12 +1,13 @@ - +
@@ -47,7 +52,7 @@ (click)="drawer.open()" smTooltip="Toggle Graphs" > - +
Object.keys(metricsScalar || []).reduce((acc, curr) => { @@ -63,6 +64,8 @@ export class ExperimentOutputScalarsComponent implements OnInit, OnDestroy { public graphs: { [key: string]: ExtFrame[] }; public selectIsExperimentPendingRunning: Observable; public smoothWeight$: Observable; + public smoothWeightDelayed$: Observable; + public smoothType$: Observable; public showSettingsBar: boolean = false; public xAxisType$: Observable; public groupBy$: Observable; @@ -89,11 +92,12 @@ export class ExperimentOutputScalarsComponent implements OnInit, OnDestroy { private singleValueExists: boolean; constructor( - protected store: Store, + protected store: Store, protected router: Router, protected activeRoute: ActivatedRoute, protected changeDetection: ChangeDetectorRef, - protected reportEmbed: ReportCodeEmbedService + protected reportEmbed: ReportCodeEmbedService, + protected refreshService: RefreshService ) { this.searchTerm$ = this.store.select(selectExperimentMetricsSearchTerm); this.splitSize$ = this.store.select(selectSplitSize); @@ -106,10 +110,14 @@ export class ExperimentOutputScalarsComponent implements OnInit, OnDestroy { this.settings$ = this.store.select(selectSelectedExperimentSettings) .pipe( filter(settings => !!settings), - map(settings => settings ? settings.selectedScalar : null) + map(settings => settings ? settings.selectedScalar : null), + distinctUntilChanged(), + filter(selectedPlot => selectedPlot !== undefined) ); this.smoothWeight$ = this.store.select(selectSelectedSettingsSmoothWeight); - this.xAxisType$ = this.store.select(selectSelectedSettingsxAxisType); + this.smoothWeightDelayed$ = this.store.select(selectSelectedSettingsSmoothWeight).pipe(debounceTime(75)); + this.smoothType$ = this.store.select(selectSelectedSettingsSmoothType); + this.xAxisType$ = this.store.select(selectSelectedSettingsxAxisType(false)); this.groupBy$ = this.store.select(selectSelectedSettingsGroupBy); this.singleValueData$ = this.store.select(selectScalarSingleValue) .pipe(tap( data => this.singleValueExists = data?.length > 0)); @@ -132,8 +140,7 @@ export class ExperimentOutputScalarsComponent implements OnInit, OnDestroy { } ngOnInit() { - this.minimized = this.activeRoute.snapshot.routeConfig.data.minimized; - + this.minimized = this.activeRoute.snapshot.routeConfig.data?.minimized; this.subs.add(this.groupBy$ .pipe(filter((groupBy) => !!groupBy)) .subscribe(groupBy => { @@ -150,7 +157,7 @@ export class ExperimentOutputScalarsComponent implements OnInit, OnDestroy { this.subs.add(this.entitySelector .pipe( filter(experiment => !!experiment?.id), - distinctUntilChanged() + distinctUntilKeyChanged('id') ) .subscribe(experiment => { this.experimentId = experiment.id; @@ -160,6 +167,11 @@ export class ExperimentOutputScalarsComponent implements OnInit, OnDestroy { }) ); + this.subs.add(this.refreshService.tick + .pipe(filter(autoRefresh => autoRefresh !== null && !!this.experimentId)) + .subscribe(() => this.refresh()) + ); + this.subs.add(this.scalars$ .subscribe(scalars => { this.refreshDisabled = false; @@ -172,6 +184,7 @@ export class ExperimentOutputScalarsComponent implements OnInit, OnDestroy { .subscribe((selectedScalar) => { this.selectedGraph = selectedScalar; this.experimentGraphs?.scrollToGraph(selectedScalar); + this.store.dispatch(setExperimentSettings({id: this.experimentId, changes: {selectedScalar: undefined}})); }) ); @@ -230,6 +243,10 @@ export class ExperimentOutputScalarsComponent implements OnInit, OnDestroy { this.store.dispatch(setExperimentSettings({id: this.experimentId, changes: {smoothWeight: $event}})); } + changeSmoothType($event: SmoothTypeEnum) { + this.store.dispatch(setExperimentSettings({id: this.experimentId, changes: {smoothType: $event}})); + } + changeXAxisType($event: ScalarKeyEnum) { this.store.dispatch(setExperimentSettings({id: this.experimentId, changes: {xAxisType: $event}})); } @@ -251,7 +268,7 @@ export class ExperimentOutputScalarsComponent implements OnInit, OnDestroy { }, {}); } - createEmbedCode(event: { metrics?: string[]; variants?: string[]; domRect: DOMRect}) { + createEmbedCode(event: { metrics?: string[]; variants?: string[]; xaxis?: ScalarKeyEnum; domRect: DOMRect}) { this.reportEmbed.createCode({ type: (!event.metrics && !event.variants) ? 'single' : 'scalar', objects: [this.experimentId], diff --git a/src/app/webapp-common/experiments/containers/experiment-output-scalars/shared-experiment-output.scss b/src/app/webapp-common/experiments/containers/experiment-output-scalars/shared-experiment-output.scss index e32d08ce..5ed61881 100644 --- a/src/app/webapp-common/experiments/containers/experiment-output-scalars/shared-experiment-output.scss +++ b/src/app/webapp-common/experiments/containers/experiment-output-scalars/shared-experiment-output.scss @@ -1,5 +1,6 @@ +@import "variables"; - mat-drawer { +mat-drawer { background-color: #ffffff; } @@ -32,3 +33,12 @@ align-items: center; } } + ::ng-deep .no-output { + text-align: center; + color: $cloudy-blue-two; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + display: block; + } diff --git a/src/app/webapp-common/experiments/dumb/base-clickable-artifact.component.ts b/src/app/webapp-common/experiments/dumb/base-clickable-artifact.component.ts index 14f827e3..c84f05d9 100644 --- a/src/app/webapp-common/experiments/dumb/base-clickable-artifact.component.ts +++ b/src/app/webapp-common/experiments/dumb/base-clickable-artifact.component.ts @@ -12,7 +12,7 @@ import {getSignedUrl} from '../../core/actions/common-auth.actions'; export class BaseClickableArtifactComponent { protected timestamp: number; - constructor(protected adminService: AdminService, protected store: Store) { + constructor(protected adminService: AdminService, protected store: Store) { } artifactFilePathClicked(url: string) { diff --git a/src/app/webapp-common/experiments/dumb/experiment-artifact-item-view/experiment-artifact-item-view.component.html b/src/app/webapp-common/experiments/dumb/experiment-artifact-item-view/experiment-artifact-item-view.component.html index 12f1edca..eb0b28ae 100755 --- a/src/app/webapp-common/experiments/dumb/experiment-artifact-item-view/experiment-artifact-item-view.component.html +++ b/src/app/webapp-common/experiments/dumb/experiment-artifact-item-view/experiment-artifact-item-view.component.html @@ -1,6 +1,6 @@
- + {{artifact?.uri}} {{artifact?.uri}} - {{(artifact?.content_size | filesize : fileSizeConfigStorage) || ''}} - {{artifact?.hash}} + {{(artifact?.content_size | filesize : fileSizeConfigStorage) || ''}} + {{artifact?.hash}} {{data[1]}}
diff --git a/src/app/webapp-common/experiments/dumb/experiment-artifact-item-view/experiment-artifact-item-view.component.ts b/src/app/webapp-common/experiments/dumb/experiment-artifact-item-view/experiment-artifact-item-view.component.ts index 96bc549c..eb3e3953 100755 --- a/src/app/webapp-common/experiments/dumb/experiment-artifact-item-view/experiment-artifact-item-view.component.ts +++ b/src/app/webapp-common/experiments/dumb/experiment-artifact-item-view/experiment-artifact-item-view.component.ts @@ -32,7 +32,7 @@ export class ExperimentArtifactItemViewComponent extends BaseClickableArtifactCo return this._artifact; } - constructor(protected adminService: AdminService, protected store: Store) { + constructor(protected adminService: AdminService, protected store: Store) { super(adminService, store); } diff --git a/src/app/webapp-common/experiments/dumb/experiment-execution-parameters/experiment-execution-parameters.component.html b/src/app/webapp-common/experiments/dumb/experiment-execution-parameters/experiment-execution-parameters.component.html index fdb42e17..d2ada6a2 100755 --- a/src/app/webapp-common/experiments/dumb/experiment-execution-parameters/experiment-execution-parameters.component.html +++ b/src/app/webapp-common/experiments/dumb/experiment-execution-parameters/experiment-execution-parameters.component.html @@ -1,11 +1,12 @@
- + -
+
+ class="strength" + >
{{formData?.repository}}
- +
@@ -21,8 +21,8 @@
-
- +
+ Type {{type.label}} @@ -30,7 +30,7 @@ + [class.d-none]="scriptType.value !== sourceTypesEnum.VersionNum" class="flex-grow-1"> Required Commit ID - + Branch Required - + Required Tag
{{formData?.entry_point}}
- +
{{formData?.working_dir}}
- +
+ +
+ {{formData?.binary}} + +
+ + + +
diff --git a/src/app/webapp-common/experiments/dumb/experiment-execution-source-code/experiment-execution-source-code.component.scss b/src/app/webapp-common/experiments/dumb/experiment-execution-source-code/experiment-execution-source-code.component.scss index 9e546bdc..8409e903 100755 --- a/src/app/webapp-common/experiments/dumb/experiment-execution-source-code/experiment-execution-source-code.component.scss +++ b/src/app/webapp-common/experiments/dumb/experiment-execution-source-code/experiment-execution-source-code.component.scss @@ -1,3 +1,9 @@ +@import "variables"; + .source-code-container { margin-bottom: 16px; } + +.al-ico-alert-outline { + color: $orangada; +} diff --git a/src/app/webapp-common/experiments/dumb/experiment-execution-source-code/experiment-execution-source-code.component.ts b/src/app/webapp-common/experiments/dumb/experiment-execution-source-code/experiment-execution-source-code.component.ts index 85bbd69a..fa2929bc 100755 --- a/src/app/webapp-common/experiments/dumb/experiment-execution-source-code/experiment-execution-source-code.component.ts +++ b/src/app/webapp-common/experiments/dumb/experiment-execution-source-code/experiment-execution-source-code.component.ts @@ -1,9 +1,8 @@ import {Component, EventEmitter, Input, OnDestroy, OnInit, Output, ViewChild} from '@angular/core'; import {NgForm} from '@angular/forms'; import {Subscription} from 'rxjs'; -import {IExperimentInfoFormComponent} from '../../../../features/experiments/shared/experiment-info.model'; -import {IExecutionForm, sourceTypesEnum} from '../../../../features/experiments/shared/experiment-execution.model'; -import {HELP_TEXTS} from '../../shared/common-experiments.const'; +import {IExperimentInfoFormComponent} from '~/features/experiments/shared/experiment-info.model'; +import {IExecutionForm, sourceTypesEnum} from '~/features/experiments/shared/experiment-execution.model'; @Component({ selector : 'sm-experiment-execution-source-code', @@ -20,7 +19,6 @@ export class ExperimentExecutionSourceCodeComponent implements OnInit, IExperime @ViewChild('sourceCodeForm', { static: true }) sourceCodeForm: NgForm; - HELP_TEXTS = HELP_TEXTS; readonly sourceTypesEnum = sourceTypesEnum; scriptTypeOptions = [ @@ -47,6 +45,7 @@ export class ExperimentExecutionSourceCodeComponent implements OnInit, IExperime [sourceTypesEnum.Tag] : 'TAG NAME', [sourceTypesEnum.Branch] : 'BRANCH NAME' }; + pythonRegexp = /^python([23](\.\d{1,2}){0,2})?$/; ngOnInit(): void { diff --git a/src/app/webapp-common/experiments/dumb/experiment-general-info/experiment-general-info.component.html b/src/app/webapp-common/experiments/dumb/experiment-general-info/experiment-general-info.component.html index bdea98de..bf83b88f 100755 --- a/src/app/webapp-common/experiments/dumb/experiment-general-info/experiment-general-info.component.html +++ b/src/app/webapp-common/experiments/dumb/experiment-general-info/experiment-general-info.component.html @@ -19,7 +19,7 @@ - {{(experiment?.status_changed | date : TIME_FORMAT_STRING) | NA}} + {{(experiment?.status_changed | date : timeFormatString) | NA}} @@ -38,12 +38,12 @@ - {{(experiment?.created | date : TIME_FORMAT_STRING) | NA}} + {{(experiment?.created | date : timeFormatString) | NA}} - {{(experiment?.started | date : TIME_FORMAT_STRING) | NA}} + {{(experiment?.started | date : timeFormatString) | NA}} @@ -53,7 +53,7 @@ - {{(experiment?.completed | date : TIME_FORMAT_STRING) | NA}} + {{(experiment?.completed | date : timeFormatString) | NA}} diff --git a/src/app/webapp-common/experiments/dumb/experiment-general-info/experiment-general-info.component.ts b/src/app/webapp-common/experiments/dumb/experiment-general-info/experiment-general-info.component.ts index dc4e4361..3163d02e 100755 --- a/src/app/webapp-common/experiments/dumb/experiment-general-info/experiment-general-info.component.ts +++ b/src/app/webapp-common/experiments/dumb/experiment-general-info/experiment-general-info.component.ts @@ -1,12 +1,9 @@ -import {AfterViewInit, Component, EventEmitter, Input, OnDestroy, Output, ViewChild} from '@angular/core'; +import {Component, EventEmitter, Input, Output, ViewChild} from '@angular/core'; import {UntypedFormControl} from '@angular/forms'; import {IExperimentInfo} from '~/features/experiments/shared/experiment-info.model'; import {TIME_FORMAT_STRING} from '@common/constants'; import {Store} from '@ngrx/store'; import {activateEdit, deactivateEdit} from '../../actions/common-experiments-info.actions'; -import {selectCurrentActiveSectionEdit} from '../../reducers'; -import {filter} from 'rxjs/operators'; -import {Subscription} from 'rxjs'; import {EditableSectionComponent} from '@common/shared/ui-components/panel/editable-section/editable-section.component'; export const EXPERIMENT_COMMENT = 'ExperimentComment'; @@ -16,20 +13,18 @@ export const EXPERIMENT_COMMENT = 'ExperimentComment'; templateUrl: './experiment-general-info.component.html', styleUrls: ['./experiment-general-info.component.scss'] }) -export class ExperimentGeneralInfoComponent implements AfterViewInit, OnDestroy { - constructor(private store: Store) { - } +export class ExperimentGeneralInfoComponent { + constructor(private store: Store) {} commentControl = new UntypedFormControl(); experimentCommentText: string; experimentCommentOriginal: string; - private selectCurrentActiveSectionEditSub: Subscription; - @ViewChild('experimentDescriptionSection') private experimentDescriptionSection: EditableSectionComponent; + + @ViewChild('experimentDescriptionSection') experimentDescriptionSection: EditableSectionComponent; @Input() experiment: IExperimentInfo; @Input() editable: boolean; @Input() isExample: boolean; - // TODO: remove ISelectedExperiment and use the form object... @Input() set experimentComment(experimentComment: string) { this.experimentCommentText = experimentComment; this.experimentCommentOriginal = experimentComment; @@ -37,15 +32,7 @@ export class ExperimentGeneralInfoComponent implements AfterViewInit, OnDestroy } @Output() commentChanged = new EventEmitter(); - TIME_FORMAT_STRING = TIME_FORMAT_STRING; - - ngAfterViewInit() { - this.selectCurrentActiveSectionEditSub = this.store.select(selectCurrentActiveSectionEdit) - .pipe(filter(currentActiveSectionEdit => currentActiveSectionEdit === EXPERIMENT_COMMENT)) - .subscribe(() => { - this.experimentDescriptionSection.editModeChanged(true); - }); - } + timeFormatString = TIME_FORMAT_STRING; rebuildCommentControl(comment) { this.commentControl = new UntypedFormControl(comment); @@ -68,9 +55,4 @@ export class ExperimentGeneralInfoComponent implements AfterViewInit, OnDestroy this.store.dispatch(deactivateEdit()); } } - - ngOnDestroy() { - this.selectCurrentActiveSectionEditSub?.unsubscribe(); - } - } diff --git a/src/app/webapp-common/experiments/dumb/experiment-header/experiment-header.component.html b/src/app/webapp-common/experiments/dumb/experiment-header/experiment-header.component.html index a738e4d8..1dbd0ebf 100755 --- a/src/app/webapp-common/experiments/dumb/experiment-header/experiment-header.component.html +++ b/src/app/webapp-common/experiments/dumb/experiment-header/experiment-header.component.html @@ -7,7 +7,7 @@ [class.hide-item]="sharedView" [showArchived]="isArchived" [minimize]="(isSmallScreen$ | async).matches" - (toggleArchived)="onIsArchivedChanged($event)" + (toggleArchived)="isArchivedChanged.emit($event)" > + + + + + (); @Output() tableModeChanged = new EventEmitter<'table' | 'info'>(); - - - onIsArchivedChanged(value: boolean) { - this.isArchivedChanged.emit(value); - } } diff --git a/src/app/webapp-common/experiments/dumb/experiment-hyper-params-navbar/experiment-hyper-params-navbar.component.html b/src/app/webapp-common/experiments/dumb/experiment-hyper-params-navbar/experiment-hyper-params-navbar.component.html index 8d7b2c2a..d194856b 100644 --- a/src/app/webapp-common/experiments/dumb/experiment-hyper-params-navbar/experiment-hyper-params-navbar.component.html +++ b/src/app/webapp-common/experiments/dumb/experiment-hyper-params-navbar/experiment-hyper-params-navbar.component.html @@ -1,5 +1,5 @@ - + USER PROPERTIES
+ [class.blue-400]="selectedHyperParam !== 'properties'" + data-id="properties"> Properties
- + HYPERPARAMETERS

{{selectedExperiment?.comment}}

-
Edit description
+
Edit description
-
Add description
+
Add description
diff --git a/src/app/webapp-common/experiments/dumb/experiment-info-edit-description/experiment-info-edit-description.component.ts b/src/app/webapp-common/experiments/dumb/experiment-info-edit-description/experiment-info-edit-description.component.ts index a0099b98..b61652ec 100644 --- a/src/app/webapp-common/experiments/dumb/experiment-info-edit-description/experiment-info-edit-description.component.ts +++ b/src/app/webapp-common/experiments/dumb/experiment-info-edit-description/experiment-info-edit-description.component.ts @@ -1,5 +1,5 @@ import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; -import {IExperimentInfo} from '../../../../features/experiments/shared/experiment-info.model'; +import {IExperimentInfo} from '~/features/experiments/shared/experiment-info.model'; @Component({ selector: 'sm-experiment-info-edit-description', @@ -8,7 +8,7 @@ import {IExperimentInfo} from '../../../../features/experiments/shared/experimen }) export class ExperimentInfoEditDescriptionComponent implements OnInit { @Input() selectedExperiment: IExperimentInfo; - @Output() onDescription = new EventEmitter(); + @Output() editDescription = new EventEmitter(); public isEntered = false; public isOpen = false; diff --git a/src/app/webapp-common/experiments/dumb/experiment-info-header/experiment-info-header.component.html b/src/app/webapp-common/experiments/dumb/experiment-info-header/experiment-info-header.component.html index c1e10775..ac279842 100644 --- a/src/app/webapp-common/experiments/dumb/experiment-info-header/experiment-info-header.component.html +++ b/src/app/webapp-common/experiments/dumb/experiment-info-header/experiment-info-header.component.html @@ -20,7 +20,7 @@
+ >
- - - + > +
diff --git a/src/app/webapp-common/experiments/dumb/experiment-info-header/experiment-info-header.component.scss b/src/app/webapp-common/experiments/dumb/experiment-info-header/experiment-info-header.component.scss index 55499e1c..b94dad6e 100755 --- a/src/app/webapp-common/experiments/dumb/experiment-info-header/experiment-info-header.component.scss +++ b/src/app/webapp-common/experiments/dumb/experiment-info-header/experiment-info-header.component.scss @@ -2,7 +2,6 @@ :host { display: block; - position: relative; padding: 0 24px; .tags{ grid-area: tags; @@ -86,7 +85,9 @@ } .shared { - margin: 0 6px; + margin: 0 24px; + display: flex; + align-items: center; } .status { @@ -147,4 +148,8 @@ height: 24px; } } + + sm-tag-list { + max-width: 100%; + } } diff --git a/src/app/webapp-common/experiments/dumb/experiment-info-header/experiment-info-header.component.ts b/src/app/webapp-common/experiments/dumb/experiment-info-header/experiment-info-header.component.ts index a039e79f..2a3e66bf 100755 --- a/src/app/webapp-common/experiments/dumb/experiment-info-header/experiment-info-header.component.ts +++ b/src/app/webapp-common/experiments/dumb/experiment-info-header/experiment-info-header.component.ts @@ -2,10 +2,9 @@ import {Component, EventEmitter, Input, OnDestroy, Output, ViewChild} from '@ang import {getSystemTags, isDevelopment} from '~/features/experiments/shared/experiments.utils'; import {Observable} from 'rxjs'; import {Store} from '@ngrx/store'; -import {selectCompanyTags, selectProjectTags, selectTagsFilterByProject} from '@common/core/reducers/projects.reducer'; +import {selectCompanyTags, selectTagsFilterByProject} from '@common/core/reducers/projects.reducer'; import {addTag, removeTag} from '../../actions/common-experiments-menu.actions'; import {TagsMenuComponent} from '@common/shared/ui-components/tags/tags-menu/tags-menu.component'; -import {MenuComponent} from '@common/shared/ui-components/panel/menu/menu.component'; import {activateEdit, deactivateEdit, setExperiment} from '../../actions/common-experiments-info.actions'; import {EXPERIMENTS_STATUS_LABELS, ExperimentTagsEnum} from '~/features/experiments/shared/experiments.const'; import {EXPERIMENT_COMMENT} from '../experiment-general-info/experiment-general-info.component'; @@ -25,6 +24,8 @@ import { selectionDisabledViewWorker } from '@common/shared/entity-page/items.utils'; import {addMessage} from '@common/core/actions/layout.actions'; +import {MatMenuTrigger} from '@angular/material/menu'; +import { selectExperimentsTags } from '@common/experiments/reducers'; @Component({ selector: 'sm-experiment-info-header', @@ -41,7 +42,7 @@ export class ExperimentInfoHeaderComponent implements OnDestroy { public systemTags = [] as string[]; public shared: boolean; public isPipeline: boolean; - selectedDisableAvailable = {}; + public selectedDisableAvailable = {}; @Input() editable: boolean = true; @Input() infoData; @@ -53,18 +54,18 @@ export class ExperimentInfoHeaderComponent implements OnDestroy { @Output() minimizeClicked = new EventEmitter(); @Output() closeInfoClicked = new EventEmitter(); @Output() maximizedClicked = new EventEmitter(); - @ViewChild('tagMenu') tagMenu: MenuComponent; - @ViewChild('tagsMenuContent') tagMenuContent: TagsMenuComponent; + @ViewChild('tagsMenuTrigger') tagMenuTrigger: MatMenuTrigger; + @ViewChild(TagsMenuComponent) tagMenu: TagsMenuComponent; - constructor(private store: Store, private router: Router, private activatedRoute: ActivatedRoute) { + constructor(private store: Store, private router: Router, private activatedRoute: ActivatedRoute) { this.tagsFilterByProject$ = this.store.select(selectTagsFilterByProject); - this.projectTags$ = this.store.select(selectProjectTags); + this.projectTags$ = this.store.select(selectExperimentsTags); this.companyTags$ = this.store.select(selectCompanyTags); } ngOnDestroy(): void { + this.tagMenuTrigger = null; this.tagMenu = null; - this.tagMenuContent = null; this.store.dispatch(setExperiment(null)); } @@ -103,15 +104,14 @@ export class ExperimentInfoHeaderComponent implements OnDestroy { this.experimentNameChanged.emit(name); } - openTagMenu(event: MouseEvent) { - if (!this.tagMenu) { + openTagMenu() { + if (!this.tagMenuTrigger) { return; } window.setTimeout(() => this.store.dispatch(activateEdit('tags')), 200); - this.tagMenu.position = {x: event.clientX, y: event.clientY}; window.setTimeout(() => { - this.tagMenu.openMenu(); - this.tagMenuContent.focus(); + this.tagMenuTrigger.openMenu(); + this.tagMenu.focus(); }); } @@ -125,7 +125,7 @@ export class ExperimentInfoHeaderComponent implements OnDestroy { tagsMenuClosed() { this.store.dispatch(deactivateEdit()); - this.tagMenuContent.clear(); + this.tagMenu.clear(); } editExperimentName(edit) { @@ -144,9 +144,9 @@ export class ExperimentInfoHeaderComponent implements OnDestroy { return EXPERIMENTS_STATUS_LABELS[this.experiment?.status] || ''; } - onDescriptionHandler() { + editDescriptionHandler() { this.router.navigate(['general'], {relativeTo: this.activatedRoute}); - this.store.dispatch(activateEdit(EXPERIMENT_COMMENT)); + window.setTimeout(() => this.store.dispatch(activateEdit(EXPERIMENT_COMMENT)), 50); } copyToClipboard() { diff --git a/src/app/webapp-common/experiments/dumb/experiment-log-info/experiment-log-info.component.html b/src/app/webapp-common/experiments/dumb/experiment-log-info/experiment-log-info.component.html index 204309fb..1dbbcb6b 100755 --- a/src/app/webapp-common/experiments/dumb/experiment-log-info/experiment-log-info.component.html +++ b/src/app/webapp-common/experiments/dumb/experiment-log-info/experiment-log-info.component.html @@ -1,8 +1,8 @@ -
-
{{line.timestamp | date:'y-MM-dd HH:mm:ss'}}
-
+
+
{{line.timestamp | date:'y-MM-dd HH:mm:ss'}}
+
{{line.entry}}
diff --git a/src/app/webapp-common/experiments/dumb/experiment-models-form-view/experiment-models-form-view.component.html b/src/app/webapp-common/experiments/dumb/experiment-models-form-view/experiment-models-form-view.component.html index 0980fbf0..839a8ec8 100755 --- a/src/app/webapp-common/experiments/dumb/experiment-models-form-view/experiment-models-form-view.component.html +++ b/src/app/webapp-common/experiments/dumb/experiment-models-form-view/experiment-models-form-view.component.html @@ -12,7 +12,7 @@ class="light" [hideBackground]="true" > - +
diff --git a/src/app/webapp-common/experiments/dumb/experiment-models-form-view/experiment-models-form-view.component.ts b/src/app/webapp-common/experiments/dumb/experiment-models-form-view/experiment-models-form-view.component.ts index ac105bd9..2731dbc4 100755 --- a/src/app/webapp-common/experiments/dumb/experiment-models-form-view/experiment-models-form-view.component.ts +++ b/src/app/webapp-common/experiments/dumb/experiment-models-form-view/experiment-models-form-view.component.ts @@ -1,4 +1,4 @@ -import {Component, EventEmitter, Input, Output} from '@angular/core'; +import {Component, EventEmitter, Input, OnDestroy, Output} from '@angular/core'; import {IModelInfo, IModelInfoSource} from '../../shared/common-experiment-model.model'; import {MatDialog} from '@angular/material/dialog'; import {filter} from 'rxjs/operators'; @@ -7,6 +7,9 @@ import {SelectModelComponent} from '@common/select-model/select-model.component' import {AdminService} from '~/shared/services/admin.service'; import {Store} from '@ngrx/store'; import {BaseClickableArtifactComponent} from '../base-clickable-artifact.component'; +import {addMessage} from '@common/core/actions/layout.actions'; +import {MESSAGES_SEVERITY} from '@common/constants'; +import {resetSelectModelState} from '@common/select-model/select-model.actions'; @Component({ @@ -14,7 +17,7 @@ import {BaseClickableArtifactComponent} from '../base-clickable-artifact.compone templateUrl: './experiment-models-form-view.component.html', styleUrls: ['./experiment-models-form-view.component.scss'] }) -export class ExperimentModelsFormViewComponent extends BaseClickableArtifactComponent { +export class ExperimentModelsFormViewComponent extends BaseClickableArtifactComponent implements OnDestroy{ public isLocalFile: boolean; private _model: IModelInfo; @@ -37,7 +40,7 @@ export class ExperimentModelsFormViewComponent extends BaseClickableArtifactComp @Output() modelSelectedId = new EventEmitter(); - constructor(private dialog: MatDialog, protected adminService: AdminService, protected store: Store) { + constructor(private dialog: MatDialog, protected adminService: AdminService, protected store: Store) { super(adminService, store); } @@ -53,10 +56,20 @@ export class ExperimentModelsFormViewComponent extends BaseClickableArtifactComp }); chooseModelDialog.afterClosed() .pipe(filter(model => !!model)) - .subscribe((selectedModelId: string) => this.modelSelectedId.emit(selectedModelId)); + .subscribe((selectedModelId: string) => { + this.modelSelectedId.emit(selectedModelId); + }); } removeModel() { this.modelSelectedId.emit(null); } + + copySuccess() { + this.store.dispatch(addMessage(MESSAGES_SEVERITY.SUCCESS, 'Copied to clipboard')); + } + + ngOnDestroy(): void { + this.store.dispatch(resetSelectModelState({fullReset: true})); + } } diff --git a/src/app/webapp-common/experiments/dumb/experiment-output-model-view/experiment-output-model-view.component.html b/src/app/webapp-common/experiments/dumb/experiment-output-model-view/experiment-output-model-view.component.html index d6ee3985..ca0349ea 100755 --- a/src/app/webapp-common/experiments/dumb/experiment-output-model-view/experiment-output-model-view.component.html +++ b/src/app/webapp-common/experiments/dumb/experiment-output-model-view/experiment-output-model-view.component.html @@ -7,10 +7,12 @@ class="pointer fa fa-download ms-2" (click)="artifactFilePathClicked(model.uri)" >
-
diff --git a/src/app/webapp-common/experiments/dumb/experiment-output-model-view/experiment-output-model-view.component.ts b/src/app/webapp-common/experiments/dumb/experiment-output-model-view/experiment-output-model-view.component.ts index 6a78da1b..b5934bed 100755 --- a/src/app/webapp-common/experiments/dumb/experiment-output-model-view/experiment-output-model-view.component.ts +++ b/src/app/webapp-common/experiments/dumb/experiment-output-model-view/experiment-output-model-view.component.ts @@ -30,7 +30,7 @@ export class ExperimentOutputModelViewComponent extends BaseClickableArtifactCom return this._model; } - constructor(protected adminService: AdminService, protected store: Store) { + constructor(protected adminService: AdminService, protected store: Store) { super(adminService, store); } } diff --git a/src/app/webapp-common/experiments/dumb/experiments-table/experiments-table.component.html b/src/app/webapp-common/experiments/dumb/experiments-table/experiments-table.component.html index dc0837d6..99ff30ec 100755 --- a/src/app/webapp-common/experiments/dumb/experiments-table/experiments-table.component.html +++ b/src/app/webapp-common/experiments/dumb/experiments-table/experiments-table.component.html @@ -109,7 +109,7 @@ [smTooltip]="experiment.name"> {{experiment.name}}
- +
diff --git a/src/app/webapp-common/experiments/dumb/experiments-table/experiments-table.component.ts b/src/app/webapp-common/experiments/dumb/experiments-table/experiments-table.component.ts index 285bebaa..e9c06ba0 100755 --- a/src/app/webapp-common/experiments/dumb/experiments-table/experiments-table.component.ts +++ b/src/app/webapp-common/experiments/dumb/experiments-table/experiments-table.component.ts @@ -37,7 +37,6 @@ import {createFiltersFromStore, excludedKey, uniqueFilterValueAndExcluded} from import {getRoundedNumber} from '../../shared/common-experiments.utils'; import {EntityTypeEnum} from '~/shared/constants/non-common-consts'; import {MAT_TOOLTIP_DEFAULT_OPTIONS, MatTooltipDefaultOptions} from '@angular/material/tooltip'; -import {getTablesFilterProjectsOptions, resetTablesFilterProjectsOptions} from '@common/core/actions/projects.actions'; @Component({ selector: 'sm-experiments-table', @@ -116,6 +115,7 @@ export class ExperimentsTableComponent extends BaseTableView implements OnInit, label: value, value }))); + this.sortOptionsList(id); }); } @@ -187,6 +187,7 @@ export class ExperimentsTableComponent extends BaseTableView implements OnInit, label: project.name, value: project.id, })); + this.sortOptionsList(EXPERIMENTS_TABLE_COL_FIELDS.PROJECT); } @Input() systemTags = [] as string[]; @@ -234,7 +235,7 @@ export class ExperimentsTableComponent extends BaseTableView implements OnInit, constructor( private changeDetector: ChangeDetectorRef, - private store: Store, + private store: Store, private noUnderscorePipe: NoUnderscorePipe, private router: Router ) { @@ -328,6 +329,7 @@ export class ExperimentsTableComponent extends BaseTableView implements OnInit, } columnFilterOpened(col: ISmCol) { + this.sortOptionsList(col.id); if (col.id === EXPERIMENTS_TABLE_COL_FIELDS.TAGS) { if (!this.filtersOptions[EXPERIMENTS_TABLE_COL_FIELDS.TAGS]?.length) { this.tagsMenuOpened.emit(); diff --git a/src/app/webapp-common/experiments/effects/common-experiment-output.effects.ts b/src/app/webapp-common/experiments/effects/common-experiment-output.effects.ts index a6e8d1c7..78b8abf8 100644 --- a/src/app/webapp-common/experiments/effects/common-experiment-output.effects.ts +++ b/src/app/webapp-common/experiments/effects/common-experiment-output.effects.ts @@ -1,5 +1,5 @@ import {Injectable} from '@angular/core'; -import {Actions, createEffect, ofType} from '@ngrx/effects'; +import {Actions, concatLatestFrom, createEffect, ofType} from '@ngrx/effects'; import {Store} from '@ngrx/store'; import {ApiTasksService} from '~/business-logic/api-services/tasks.service'; import {ApiAuthService} from '~/business-logic/api-services/auth.service'; @@ -10,7 +10,6 @@ import {activeLoader, deactivateLoader, setServerError} from '../../core/actions import {requestFailed} from '../../core/actions/http.actions'; import * as outputActions from '../actions/common-experiment-output.actions'; import {setExperimentScalarSingleValue} from '../actions/common-experiment-output.actions'; -import {ExperimentOutputState} from '@common/experiments/reducers/experiment-output.reducer'; import {LOG_BATCH_SIZE} from '../shared/common-experiments.const'; import {selectExperimentHistogramCacheAxisType, selectPipelineSelectedStep, selectSelectedSettingsxAxisType} from '../reducers'; import {refreshExperiments} from '../actions/common-experiments-view.actions'; @@ -26,7 +25,7 @@ import {EventsGetTaskPlotsResponse} from '~/business-logic/model/events/eventsGe export class CommonExperimentOutputEffects { constructor( - private actions$: Actions, private store: Store, private apiTasks: ApiTasksService, + private actions$: Actions, private store: Store, private apiTasks: ApiTasksService, private authApi: ApiAuthService, private taskBl: BlTasksService, private eventsApi: ApiEventsService ) { } @@ -118,9 +117,10 @@ export class CommonExperimentOutputEffects { fetchExperimentScalar$ = createEffect(() => this.actions$.pipe( ofType(outputActions.experimentScalarRequested), - withLatestFrom( - this.store.select(selectSelectedSettingsxAxisType), + concatLatestFrom( (action) => [ + this.store.select(selectSelectedSettingsxAxisType(action.model)), this.store.select(selectExperimentHistogramCacheAxisType) + ] ), switchMap(([action, axisType, prevAxisType]) => { if ([ScalarKeyEnum.IsoTime, ScalarKeyEnum.Timestamp].includes(prevAxisType) && diff --git a/src/app/webapp-common/experiments/effects/common-experiments-info.effects.ts b/src/app/webapp-common/experiments/effects/common-experiments-info.effects.ts index 35b2af55..ac565ed0 100755 --- a/src/app/webapp-common/experiments/effects/common-experiments-info.effects.ts +++ b/src/app/webapp-common/experiments/effects/common-experiments-info.effects.ts @@ -10,7 +10,6 @@ import { selectExperimentInfoDataFreeze, selectSelectedExperiment } from '~/features/experiments/reducers'; -import {ExperimentInfoState} from '~/features/experiments/reducers/experiment-info.reducer'; import {ExperimentReverterService} from '~/features/experiments/shared/services/experiment-reverter.service'; import {requestFailed} from '../../core/actions/http.actions'; import { @@ -50,7 +49,7 @@ import { import {convertStopToComplete} from '../shared/common-experiments.utils'; import {ExperimentConverterService} from '~/features/experiments/shared/services/experiment-converter.service'; import {of} from 'rxjs'; -import {EmptyAction} from '~/app.constants'; +import {emptyAction} from '~/app.constants'; import {ReplaceHyperparamsEnum} from '~/business-logic/model/tasks/replaceHyperparamsEnum'; import {Router} from '@angular/router'; import {selectRouterConfig, selectRouterParams} from '../../core/reducers/router-reducer'; @@ -75,7 +74,7 @@ export class CommonExperimentsInfoEffects { constructor( private actions$: Actions, - private store: Store, + private store: Store, private apiTasks: ApiTasksService, private reverter: ExperimentReverterService, private converter: ExperimentConverterService, @@ -121,7 +120,7 @@ export class CommonExperimentsInfoEffects { id: experimentId, changes: {configuration: configurations} }), - selectedConfiguration ? getExperimentConfigurationObj() : new EmptyAction(), + selectedConfiguration ? getExperimentConfigurationObj() : emptyAction(), deactivateLoader(action.type), ]; }), @@ -284,7 +283,7 @@ export class CommonExperimentsInfoEffects { setBackdrop({active: false}), deactivateEdit(), setExperimentSaving({saving: false}), - graphView && selectedStep?.id ? getSelectedPipelineStep({id: selectedStep.id}) : new EmptyAction() + graphView && selectedStep?.id ? getSelectedPipelineStep({id: selectedStep.id}) : emptyAction() ]; } else { this.router.navigate(['dashboard']); @@ -356,7 +355,7 @@ export class CommonExperimentsInfoEffects { [commonInfoActions.updateExperimentInfoData({ id: action.id, changes })] : [] ), - ...(changes.tags ? [getTags()] : []) + ...(changes.tags ? [getTags({})] : []) ]; }), catchError((err: HttpErrorResponse) => [ diff --git a/src/app/webapp-common/experiments/effects/common-experiments-menu.effects.ts b/src/app/webapp-common/experiments/effects/common-experiments-menu.effects.ts index 7d940a54..630582de 100644 --- a/src/app/webapp-common/experiments/effects/common-experiments-menu.effects.ts +++ b/src/app/webapp-common/experiments/effects/common-experiments-menu.effects.ts @@ -1,12 +1,12 @@ import {Injectable} from '@angular/core'; -import {Actions, createEffect, ofType} from '@ngrx/effects'; +import {Actions, concatLatestFrom, createEffect, ofType} from '@ngrx/effects'; import {Action, Store} from '@ngrx/store'; import {ApiTasksService} from '~/business-logic/api-services/tasks.service'; import {ApiAuthService} from '~/business-logic/api-services/auth.service'; import {BlTasksService} from '~/business-logic/services/tasks.service'; import {ApiEventsService} from '~/business-logic/api-services/events.service'; -import {Router} from '@angular/router'; -import {catchError, map, mergeMap, switchMap, tap, withLatestFrom} from 'rxjs/operators'; +import {ActivatedRoute, Router} from '@angular/router'; +import {catchError, map, mergeMap, switchMap, tap} from 'rxjs/operators'; import { activeLoader, addMessage, @@ -18,14 +18,13 @@ import * as menuActions from '../actions/common-experiments-menu.actions'; import {stopClicked} from '../actions/common-experiments-menu.actions'; import {Observable, of} from 'rxjs'; import {requestFailed} from '../../core/actions/http.actions'; -import {ExperimentInfoState} from '~/features/experiments/reducers/experiment-info.reducer'; import {ExperimentConverterService} from '~/features/experiments/shared/services/experiment-converter.service'; import * as exSelectors from '../reducers'; import {selectSelectedExperiments, selectTableMode} from '../reducers'; import {selectSelectedExperiment} from '~/features/experiments/reducers'; import * as infoActions from '../actions/common-experiments-info.actions'; import {autoRefreshExperimentInfo, experimentDetailsUpdated} from '../actions/common-experiments-info.actions'; -import {EmptyAction} from '~/app.constants'; +import {emptyAction} from '~/app.constants'; import * as viewActions from '../actions/common-experiments-view.actions'; import {IExperimentInfo, ISelectedExperiment} from '~/features/experiments/shared/experiment-info.model'; import {ApiProjectsService} from '~/business-logic/api-services/projects.service'; @@ -54,6 +53,7 @@ import {WelcomeMessageComponent} from '@common/layout/welcome-message/welcome-me import {selectNeverShowPopups} from '@common/core/reducers/view.reducer'; import {MESSAGES_SEVERITY} from '@common/constants'; import {TasksCloneResponse} from '~/business-logic/model/tasks/tasksCloneResponse'; +import {addProjectsTag} from '../actions/common-experiments-view.actions'; export const getChildrenExperiments = (tasksApi, parents, filters?: { [key: string]: any }): Observable => tasksApi.tasksGetAllEx({ @@ -71,10 +71,9 @@ export const getChildrenExperiments = (tasksApi, parents, filters?: { [key: stri @Injectable() export class CommonExperimentsMenuEffects { - private selectedExperiment: IExperimentInfo; constructor(private actions$: Actions, - private store: Store, + private store: Store, private apiTasks: ApiTasksService, private pipelineApi: ApiPipelinesService, private authApi: ApiAuthService, @@ -83,10 +82,9 @@ export class CommonExperimentsMenuEffects { private projectApi: ApiProjectsService, private converter: ExperimentConverterService, private router: Router, + private route: ActivatedRoute, private dialog: MatDialog - ) { - store.select(selectSelectedExperiment).subscribe(exp => this.selectedExperiment = exp); - } + ) {} activeLoader = createEffect(() => this.actions$.pipe( ofType( @@ -103,7 +101,7 @@ export class CommonExperimentsMenuEffects { enqueueExperiment$ = createEffect(() => this.actions$.pipe( ofType(menuActions.enqueueClicked), - withLatestFrom(this.store.select(selectSelectedExperiment)), + concatLatestFrom(() => this.store.select(selectSelectedExperiment)), switchMap(([action, selectedEntity]: [ReturnType, IExperimentInfo]) => { const ids = action.selectedEntities.map(exp => exp.id); return this.apiTasks.tasksEnqueueMany({ @@ -115,31 +113,33 @@ export class CommonExperimentsMenuEffects { /* eslint-enable @typescript-eslint/naming-convention */ }) .pipe( - withLatestFrom(this.store.select(selectNeverShowPopups)), - tap(([res, neverShowAgainPopups]) => { - if (res.queue_watched === false && !neverShowAgainPopups.includes('orphanedQueue')) { - this.dialog.open(WelcomeMessageComponent, { - data: { - queue: action.queue, - step: 2 - } - }).afterClosed().subscribe(doNotShowAgain => { - if (doNotShowAgain) { - this.store.dispatch(neverShowPopupAgain({popupId: 'orphanedQueue'})); - } - }); - } - }), - mergeMap(([res]) => this.updateExperimentsSuccess(action, MenuItems.enqueue, ids, selectedEntity, res)), + mergeMap((res: TasksEnqueueManyResponse) => [ + ...this.updateExperimentsSuccess(action, MenuItems.enqueue, ids, selectedEntity, res), + ...(res.queue_watched === false ? [menuActions.openEmptyQueueMessage({queue: action.queue})] : []) + ]), catchError(error => this.updateExperimentFailed(action.type, error)) ); } ) )); + openMessage = createEffect(() => this.actions$.pipe( + ofType(menuActions.openEmptyQueueMessage), + concatLatestFrom(() => this.store.select(selectNeverShowPopups)), + switchMap(([action, neverShowAgainPopups]) => !neverShowAgainPopups.includes('orphanedQueue') ? + this.dialog.open(WelcomeMessageComponent, { + data: { + queue: action.queue, + step: 2 + } + }).afterClosed() : of(null) + ), + map(doNotShowAgain => doNotShowAgain ? neverShowPopupAgain({popupId: 'orphanedQueue'}) : emptyAction()) + )); + startPipeline$ = createEffect(() => this.actions$.pipe( ofType(menuActions.startPipeline), - withLatestFrom(this.store.select(selectRouterParams).pipe(map(params => params?.projectId))), + concatLatestFrom(() => this.store.select(selectRouterParams).pipe(map(params => params?.projectId))), switchMap(([action, projectId]) => this.pipelineApi.pipelinesStartPipeline({ task: action.task, ...(action.queue && {queue: action.queue}), @@ -166,7 +166,7 @@ export class CommonExperimentsMenuEffects { getPipelineControllerForRunDialog$ = createEffect(() => this.actions$.pipe( ofType(menuActions.getControllerForStartPipelineDialog), - withLatestFrom(this.store.select(selectRouterParams).pipe(map(params => params?.projectId))), + concatLatestFrom(() => this.store.select(selectRouterParams).pipe(map(params => params?.projectId))), switchMap(([action, projectId]) => this.apiTasks.tasksGetAllEx({ /* eslint-disable @typescript-eslint/naming-convention */ @@ -193,7 +193,7 @@ export class CommonExperimentsMenuEffects { dequeueExperiment$ = createEffect(() => this.actions$.pipe( ofType(menuActions.dequeueClicked), - withLatestFrom(this.store.select(selectSelectedExperiment)), + concatLatestFrom(() => this.store.select(selectSelectedExperiment)), switchMap(([action, selectedEntity]) => { const ids = action.selectedEntities.map(exp => exp.id); return this.apiTasks.tasksDequeueMany({ids}) @@ -208,7 +208,7 @@ export class CommonExperimentsMenuEffects { cloneExperimentRequested$ = createEffect(() => this.actions$.pipe( ofType(menuActions.cloneExperimentClicked), - withLatestFrom(this.store.select(selectTableMode)), + concatLatestFrom(() => this.store.select(selectTableMode)), switchMap(([action, ]) => this.apiTasks.tasksClone({ task: action.originExperiment.id, /* eslint-disable @typescript-eslint/naming-convention */ @@ -238,7 +238,7 @@ export class CommonExperimentsMenuEffects { // resetExperiment$ = createEffect(() => this.actions$.pipe( // ofType(menuActions.resetClicked), - // withLatestFrom(this.store.select(selectSelectedExperiment)), + // concatLatestFrom(() => this.store.select(selectSelectedExperiment)), // switchMap( // ([action, selectedExp]) => { // const ids = action.selectedEntities.map(exp => exp.id); @@ -254,7 +254,7 @@ export class CommonExperimentsMenuEffects { publishExperiment$ = createEffect(() => this.actions$.pipe( ofType(menuActions.publishClicked), - withLatestFrom(this.store.select(selectSelectedExperiment)), + concatLatestFrom(() => this.store.select(selectSelectedExperiment)), switchMap(([action, selectedEntity]) => { const ids = action.selectedEntities.map(exp => exp.id); return this.apiTasks.tasksPublishMany({ids}) @@ -267,7 +267,7 @@ export class CommonExperimentsMenuEffects { abortAllChildren = createEffect(() => this.actions$.pipe( ofType(menuActions.abortAllChildren), - withLatestFrom(this.store.select(selectIsPipelines)), + concatLatestFrom(() => this.store.select(selectIsPipelines)), switchMap(([action, isPipeline]) => getChildrenExperiments(this.apiTasks, action.experiments) .pipe( tap(() => this.store.dispatch(deactivateLoader(action.type))), @@ -277,7 +277,7 @@ export class CommonExperimentsMenuEffects { data: {tasks: action.experiments, shouldBeAbortedTasks} })).afterClosed()), mergeMap(confirmed => [ - confirmed ? stopClicked({selectedEntities: [...confirmed.shouldBeAbortedTasks, ...action.experiments]}) : new EmptyAction(), + confirmed ? stopClicked({selectedEntities: [...confirmed.shouldBeAbortedTasks, ...action.experiments]}) : emptyAction(), deactivateLoader(action.type) ]), catchError(error => [deactivateLoader(action.type), requestFailed(error), addMessage(MESSAGES_SEVERITY.ERROR, 'Failed to fetch tasks running children')]) @@ -287,7 +287,7 @@ export class CommonExperimentsMenuEffects { stopExperiment$ = createEffect(() => this.actions$.pipe( ofType(menuActions.stopClicked), - withLatestFrom(this.store.select(selectSelectedExperiment)), + concatLatestFrom(() => this.store.select(selectSelectedExperiment)), switchMap(([action, selectedEntity]) => { const ids = action.selectedEntities.map(exp => exp.id); return this.apiTasks.tasksStopMany({ids}) @@ -311,10 +311,11 @@ export class CommonExperimentsMenuEffects { }) .pipe( tap((res) => this.router.navigate([`projects/${action.project.id ? action.project.id : res.project_id ?? '*'}/experiments/${action.selectedEntities.length === 1 ? action.selectedEntities[0].id : ''}`], {queryParamsHandling: 'merge'})), - mergeMap(() => [ + concatLatestFrom(() => this.store.select(selectSelectedExperiment)), + mergeMap(([, selectedExperiment]) => [ viewActions.resetExperiments(), viewActions.setSelectedExperiments({experiments: []}), - ...action.selectedEntities.map(exp => this.setExperimentIfSelected(exp.id, {project: action.project ?? '*'})), + ...action.selectedEntities.map(exp => this.setExperimentIfSelected(selectedExperiment, exp.id, {project: action.project ?? '*'})), deactivateLoader(action.type), viewActions.getExperiments(), addMessage(MESSAGES_SEVERITY.SUCCESS, `Experiment moved successfully to ${action.project.name ?? 'Projects root'}`) @@ -344,25 +345,28 @@ export class CommonExperimentsMenuEffects { ]; } - setExperimentIfSelected(experimentId, payload) { - if (this.selectedExperiment?.id === experimentId) { - return infoActions.setExperiment({experiment: {...this.selectedExperiment, ...payload}}); + setExperimentIfSelected(selectedExperiment, experimentId, payload) { + if (selectedExperiment?.id === experimentId) { + return infoActions.setExperiment({experiment: {...selectedExperiment, ...payload}}); } - return new EmptyAction(); + return emptyAction(); } addTag$ = createEffect(() => this.actions$.pipe( ofType(menuActions.addTag), - withLatestFrom(this.store.select(selectSelectedExperiments), this.store.select(selectSelectedExperiment)), + concatLatestFrom(() => [ + this.store.select(selectSelectedExperiments), + this.store.select(selectSelectedExperiment) + ]), switchMap(([action, selectedExperiments, selectedExperimentInfo]) => { const experimentsFromState = selectedExperimentInfo ? selectedExperiments.concat(selectedExperimentInfo) as ISelectedExperiment[] : selectedExperiments; - return action.experiments.map(experiment => { + return [...action.experiments.map(experiment => { const experimentFromState = experimentsFromState.find(exp => exp.id === experiment.id); return experimentDetailsUpdated({ id: experiment.id, changes: {tags: Array.from(new Set((experimentFromState?.tags || experiment.tags || []).concat([action.tag]))).sort()} }); - }); + }), addProjectsTag({tag: action.tag})]; }) )); @@ -393,14 +397,14 @@ export class CommonExperimentsMenuEffects { archiveExperiments = createEffect(() => this.actions$.pipe( ofType(menuActions.archiveSelectedExperiments), - withLatestFrom( + concatLatestFrom(() => [ this.store.select(selectRouterParams), this.store.select(exSelectors.selectSelectedTableExperiment), this.store.select(selectRouterConfig), - ), + ]), switchMap(([action, routerParams, selectedExperiment]) => this.apiTasks.tasksArchiveMany({ids: action.selectedEntities.map(exp => exp.id)}) .pipe( - withLatestFrom(this.store.select(selectRouterConfig)), + concatLatestFrom(() => this.store.select(selectRouterConfig)), mergeMap(([res, routerConfig]: [TasksArchiveManyResponse, RouterState['config']]) => { const experiments = action.selectedEntities; const allFailed = res.failed.length === experiments.length; @@ -453,22 +457,18 @@ export class CommonExperimentsMenuEffects { restoreExperiments = createEffect(() => this.actions$.pipe( ofType(menuActions.restoreSelectedExperiments), - withLatestFrom( + concatLatestFrom(() => [ this.store.select(selectRouterParams), this.store.select(exSelectors.selectSelectedTableExperiment), - this.store.select(selectRouterConfig), - this.store.select(selectTableMode) - ), - tap(([action, routerParams, selectedExperiment, routeConfig, tableMode]) => { + ]), + tap(([action, , selectedExperiment]) => { if (this.isSelectedExpInCheckedExps(action.selectedEntities, selectedExperiment)) { - const module = routeConfig.includes('pipelines') ? 'pipelines' : (routeConfig.includes('datasets') && routeConfig.includes('simple')) ? 'datasets/simple' : 'projects'; - this.router.navigate([`${module}/${routerParams.projectId}/experiments/${tableMode === 'info' ? - (module === 'datasets/simple' ? routerParams.versionId : routerParams.experimentId) : ''}`]); + this.router.navigate([], {relativeTo: this.route, queryParams: {archive: undefined}, queryParamsHandling: 'merge'}); } }), switchMap(([action, routerParams]) => this.apiTasks.tasksUnarchiveMany({ids: action.selectedEntities.map(exp => exp.id)}) .pipe( - withLatestFrom(this.store.select(selectRouterConfig)), + concatLatestFrom(() => this.store.select(selectRouterConfig)), mergeMap(([res, routerConfig]: [TasksArchiveManyResponse, RouterState['config']]) => { const experiments = action.selectedEntities; const allFailed = res.failed.length === experiments.length; @@ -518,7 +518,7 @@ export class CommonExperimentsMenuEffects { navigateToQueue = createEffect(() => this.actions$.pipe( ofType(menuActions.navigateToQueue), - withLatestFrom(this.store.select(selectSelectedExperiment)), + concatLatestFrom(() => this.store.select(selectSelectedExperiment)), switchMap(([action, info]) => { if (action.experimentId === info?.id && info?.execution?.queue?.id) { return of({tasks: [info]}); diff --git a/src/app/webapp-common/experiments/effects/common-experiments-view.effects.ts b/src/app/webapp-common/experiments/effects/common-experiments-view.effects.ts index d763d532..d8e663d7 100755 --- a/src/app/webapp-common/experiments/effects/common-experiments-view.effects.ts +++ b/src/app/webapp-common/experiments/effects/common-experiments-view.effects.ts @@ -1,12 +1,13 @@ import {Injectable} from '@angular/core'; import {ActivatedRoute, Router} from '@angular/router'; -import {Actions, createEffect, ofType} from '@ngrx/effects'; +import {Actions, concatLatestFrom, createEffect, ofType} from '@ngrx/effects'; import {Action, Store} from '@ngrx/store'; import {cloneDeep, flatten, isEqual} from 'lodash-es'; import {EMPTY, Observable, of} from 'rxjs'; import { auditTime, - catchError, debounceTime, + catchError, + debounceTime, expand, filter, map, @@ -14,12 +15,11 @@ import { reduce, switchMap, tap, - withLatestFrom } from 'rxjs/operators'; import {ApiProjectsService} from '~/business-logic/api-services/projects.service'; import {ApiTasksService} from '~/business-logic/api-services/tasks.service'; import {BlTasksService} from '~/business-logic/services/tasks.service'; -import {GET_ALL_QUERY_ANY_FIELDS} from '~/features/experiments/experiments.consts'; +import {GET_ALL_QUERY_ANY_FIELDS, INITIAL_EXPERIMENT_TABLE_COLS} from '~/features/experiments/experiments.consts'; import {selectSelectedExperiment} from '~/features/experiments/reducers'; import {excludeTypes, EXPERIMENTS_TABLE_COL_FIELDS} from '~/features/experiments/shared/experiments.const'; import {requestFailed} from '../../core/actions/http.actions'; @@ -27,7 +27,7 @@ import {activeLoader, addMessage, deactivateLoader, setServerError} from '../../ import {setURLParams} from '../../core/actions/router.actions'; import { selectIsArchivedMode, - selectIsDeepMode, + selectIsDeepMode, selectRouterProjectId, selectSelectedProject, selectShowHidden } from '../../core/reducers/projects.reducer'; @@ -53,7 +53,7 @@ import {EXPERIMENTS_PAGE_SIZE} from '../shared/common-experiments.const'; import {convertStopToComplete, encodeHyperParameter} from '../shared/common-experiments.utils'; import {sortByField} from '../../tasks/tasks.utils'; import {MODEL_TAGS} from '../../models/shared/models.const'; -import {EmptyAction} from '~/app.constants'; +import {emptyAction} from '~/app.constants'; import {selectExperimentsList, selectTableFilters, selectTableMode} from '../reducers'; import {ProjectsGetTaskParentsResponse} from '~/business-logic/model/projects/projectsGetTaskParentsResponse'; import {ProjectsGetTaskParentsRequest} from '~/business-logic/model/projects/projectsGetTaskParentsRequest'; @@ -92,27 +92,27 @@ import { compareAddDialogTableSortChanged, compareAddTableClearAllFilters, compareAddTableFilterChanged, - setAddTableViewArchived as setCompareAddTableViewArchived, - compareAddTableFilterInit + compareAddTableFilterInit, + setAddTableViewArchived as setCompareAddTableViewArchived } from '../../experiments-compare/actions/compare-header.actions'; import {TaskTypeEnum} from '~/business-logic/model/tasks/taskTypeEnum'; -import {getFilteredUsers, setProjectUsers} from '@common/core/actions/projects.actions'; +import {downloadForGetAll, getFilteredUsers, setProjectUsers} from '@common/core/actions/projects.actions'; import {selectActiveWorkspaceReady} from '~/core/reducers/view.reducer'; import {escapeRegex} from '@common/shared/utils/escape-regex'; import {MESSAGES_SEVERITY} from '@common/constants'; -import { - modelExperimentsTableFilterChanged, -} from '@common/models/actions/models-info.actions'; -import {ExperimentsViewState} from '@common/experiments/reducers/experiments-view.reducer'; +import {modelExperimentsTableFilterChanged,} from '@common/models/actions/models-info.actions'; +import {ApiOrganizationService} from '~/business-logic/api-services/organization.service'; +import {INITIAL_CONTROLLER_TABLE_COLS} from '@common/pipelines-controller/controllers.consts'; +import {prepareColsForDownload} from '@common/shared/utils/download'; @Injectable() export class CommonExperimentsViewEffects { /* eslint-disable @typescript-eslint/naming-convention */ constructor( - private actions$: Actions, private store: Store, private apiTasks: ApiTasksService, + private actions$: Actions, private store: Store, private apiTasks: ApiTasksService, private projectsApi: ApiProjectsService, private taskBl: BlTasksService, private router: Router, - private route: ActivatedRoute + private route: ActivatedRoute, private orgApi: ApiOrganizationService ) { } @@ -128,7 +128,7 @@ export class CommonExperimentsViewEffects { tableSortChange = createEffect(() => this.actions$.pipe( ofType(exActions.tableSortChanged), - withLatestFrom(this.store.select(exSelectors.selectTableSortFields)), + concatLatestFrom(() => this.store.select(exSelectors.selectTableSortFields)), switchMap(([action, oldOrders]) => { const orders = addMultipleSortColumns(oldOrders, action.colId, action.isShift); return [setURLParams({orders, update: true})]; @@ -137,7 +137,7 @@ export class CommonExperimentsViewEffects { tableFilterChange = createEffect(() => this.actions$.pipe( ofType(exActions.tableFilterChanged), - withLatestFrom(this.store.select(exSelectors.selectTableFilters)), + concatLatestFrom(() => this.store.select(exSelectors.selectTableFilters)), switchMap(([action, oldFilters]) => [setURLParams({ filters: { @@ -153,6 +153,65 @@ export class CommonExperimentsViewEffects { private refreshActions = []; + prepareTableForDownload = createEffect(() => this.actions$.pipe( + ofType(exActions.prepareTableForDownload), + // eslint-disable-next-line @typescript-eslint/naming-convention + concatLatestFrom(() => [ + this.store.select(selectRouterParams).pipe(map(params => params?.projectId)), + this.store.select(selectIsArchivedMode), + this.store.select(exSelectors.selectGlobalFilter), + this.store.select(exSelectors.selectTableSortFields), + this.store.select(exSelectors.selectTableFilters), + this.store.select(exSelectors.selectSelectedExperiments), + this.store.select(exSelectors.selectShowAllSelectedIsActive), + this.store.select(exSelectors.selectExperimentsTableCols), + this.store.select(exSelectors.selectExperimentsMetricsColsForProject), + this.store.select(selectIsDeepMode), + this.store.select(selectShowHidden), + this.store.select(selectIsCompare), + this.store.select(selectViewArchivedInAddTable), + this.store.select(selectIsPipelines), + this.store.select(selectIsDatasets), + ]), + switchMap(([ + , projectId, isArchived, gb, orderFields, filters, selectedExperiments, + showAllSelectedIsActive, cols, metricCols, deep, showHidden, isCompare, showArcived, + isPipeline, isDataset + ]) => { + const tableFilters = cloneDeep(filters) || {} as { [key: string]: FilterMetadata }; + if (tableFilters && tableFilters.status && tableFilters.status.value.includes('completed')) { + tableFilters.status.value.push('closed'); + } + const selectedIds = showAllSelectedIsActive ? selectedExperiments.map(exp => exp.id) : []; + return this.orgApi.organizationPrepareDownloadForGetAll({ + entity_type: 'task', only_fields: [], + field_mappings: prepareColsForDownload(cols.concat(metricCols)), + ...this.getGetAllQuery({ + projectId, + searchQuery: gb, + archived: isArchived, + orderFields, + tableFilters, + selectedIds, + cols, + metricCols, + deep, + showHidden, + isCompare, + showArchived: isCompare && showArcived, + isPipeline, + isDataset + }) + }) + .pipe( + map((res) => downloadForGetAll({prepareId: res.prepare_id})), + catchError(error => [requestFailed(error)]) + ); + } + ) + ) + ); + reFetchExperiment = createEffect(() => this.actions$.pipe( ofType( exActions.getExperiments, exActions.getExperimentsWithPageSize, exActions.globalFilterChanged, @@ -194,12 +253,12 @@ export class CommonExperimentsViewEffects { refreshExperiments = createEffect(() => this.actions$.pipe( ofType(exActions.refreshExperiments), filter(() => !this.lockRefresh), - withLatestFrom( + concatLatestFrom(() => [ this.store.select(exSelectors.selectCurrentScrollId), this.store.select(selectSelectedExperiment), this.store.select(selectExperimentsList), this.store.select(selectTableRefreshList) - ), + ]), switchMap(([action, currentScrollId, selectedExperiment, experiments, refreshPending]) => { this.lockRefresh = !action.autoRefresh; return this.fetchExperiments$(currentScrollId, true) @@ -264,11 +323,11 @@ export class CommonExperimentsViewEffects { getNextExperiments = createEffect(() => this.actions$.pipe( ofType(exActions.getNextExperiments), - withLatestFrom( + concatLatestFrom(() => [ this.store.select(exSelectors.selectCurrentScrollId), this.store.select(selectExperimentsList), this.store.select(selectTableRefreshList) - ), + ]), switchMap(([action, scrollId, tasks, refreshPending]) => this.fetchExperiments$(scrollId) .pipe( mergeMap(res => { @@ -305,7 +364,7 @@ export class CommonExperimentsViewEffects { experimentSelectionChanged = createEffect(() => this.actions$.pipe( ofType(exActions.experimentSelectionChanged), - withLatestFrom(this.store.select(selectRouterConfig)), + concatLatestFrom(() => this.store.select(selectRouterConfig)), tap(([action, routeConfig]) => this.navigateAfterExperimentSelectionChanged(action.experiment as ITableExperiment, action.project, routeConfig, action.replaceURL)), mergeMap(() => [exActions.setTableMode({mode: 'info'})]) @@ -313,11 +372,12 @@ export class CommonExperimentsViewEffects { selectNextExperimentEffect = createEffect(() => this.actions$.pipe( ofType(exActions.selectNextExperiment), - withLatestFrom(this.store.select(selectRouterConfig), + concatLatestFrom(() => [ + this.store.select(selectRouterConfig), this.store.select(selectExperimentsList), this.store.select(selectRouterParams).pipe(map(params => params?.projectId)), this.store.select(selectTableMode) - ), + ]), filter(([, , , , tableMode]) => tableMode === 'info'), tap(([, routeConfig, tasks, projectId]) => this.navigateAfterExperimentSelectionChanged(tasks[0] as ITableExperiment, projectId, routeConfig)), mergeMap(() => [exActions.setTableMode({mode: 'info'})]) @@ -326,46 +386,49 @@ export class CommonExperimentsViewEffects { getTypesEffect = createEffect(() => this.actions$.pipe( ofType(exActions.getProjectTypes), - withLatestFrom(this.store.select(selectRouterParams).pipe(map(params => params?.projectId))), - switchMap(([action, projectId]) => this.apiTasks.tasksGetTypes({projects: (projectId !== '*' ? [projectId] : [])}).pipe( - withLatestFrom(this.store.select(exSelectors.selectTableFilters)), - mergeMap(([res, tableFilters]) => { - let shouldFilterFilters: boolean; - let filteredTableFilter = {} as TableFilter; - if (tableFilters?.type?.value) { - filteredTableFilter = { - col: 'type', - value: tableFilters.type.value.filter(filterType => res.types.includes(filterType)) - }; - shouldFilterFilters = filteredTableFilter.value.length !== tableFilters.type.value.length; - } - return [ - shouldFilterFilters ? exActions.tableFilterChanged({ - filters: [filteredTableFilter], - projectId - }) : new EmptyAction(), - exActions.setProjectsTypes(res), - deactivateLoader(action.type) - ]; - }), - catchError(error => [ - requestFailed(error), - deactivateLoader(action.type), - addMessage('warn', 'Fetch types failed', error?.meta && [{ - name: 'More info', - actions: [setServerError(error, null, 'Fetch types failed')] - }])] - ) - )))); + concatLatestFrom(() => + this.store.select(selectRouterParams).pipe(map(params => params?.projectId)) + ), + switchMap(([action, projectId]) => + this.apiTasks.tasksGetTypes({projects: (action.allProjects || projectId === '*' ? [] : [projectId])}).pipe( + concatLatestFrom(() => this.store.select(exSelectors.selectTableFilters)), + mergeMap(([res, tableFilters]) => { + let shouldFilterFilters: boolean; + let filteredTableFilter = {} as TableFilter; + if (tableFilters?.type?.value) { + filteredTableFilter = { + col: 'type', + value: tableFilters.type.value.filter(filterType => res.types.includes(filterType)) + }; + shouldFilterFilters = filteredTableFilter.value.length !== tableFilters.type.value.length; + } + return [ + shouldFilterFilters ? exActions.tableFilterChanged({ + filters: [filteredTableFilter], + projectId + }) : emptyAction(), + exActions.setProjectsTypes(res), + deactivateLoader(action.type) + ]; + }), + catchError(error => [ + requestFailed(error), + deactivateLoader(action.type), + addMessage('warn', 'Fetch types failed', error?.meta && [{ + name: 'More info', + actions: [setServerError(error, null, 'Fetch types failed')] + }])] + ) + )))); getUsersEffect = createEffect(() => this.actions$.pipe( ofType(setProjectUsers), - withLatestFrom(this.store.select(selectTableFilters)), + concatLatestFrom(() => this.store.select(selectTableFilters)), map(([action, filters]) => { const userFiltersValue = filters?.[EXPERIMENTS_TABLE_COL_FIELDS.USER]?.value ?? []; const resIds = action.users.map(user => user.id); const shouldGetFilteredUsersNames = !(userFiltersValue.every(id => resIds.includes(id))); - return shouldGetFilteredUsersNames ? getFilteredUsers({filteredUsers: userFiltersValue}) : new EmptyAction(); + return shouldGetFilteredUsersNames ? getFilteredUsers({filteredUsers: userFiltersValue}) : emptyAction(); }), catchError(error => [ requestFailed(error), @@ -379,18 +442,18 @@ export class CommonExperimentsViewEffects { getParentsEffect = createEffect(() => this.actions$.pipe( ofType(exActions.getParents), debounceTime(300), - withLatestFrom( + concatLatestFrom(() => [ this.store.select(selectRouterParams).pipe(map(params => params?.projectId)), this.store.select(selectIsArchivedMode) - ), + ]), switchMap(([action, projectId, isArchive]) => this.projectsApi.projectsGetTaskParents({ - projects: projectId !== '*' ? [projectId] : [], + projects: (projectId === '*' || action.allProjects) ? [] : [projectId], tasks_state: isArchive ? ProjectsGetTaskParentsRequest.TasksStateEnum.Archived : ProjectsGetTaskParentsRequest.TasksStateEnum.Active, - ...(action.searchValue && {task_name: `(?i)${action.searchValue}`}) // (?i) is case insensitive + ...(action.searchValue && {task_name: `(?i)${action.searchValue}`}) // (?i) is case insensitive }).pipe( - withLatestFrom(this.store.select(selectTableFilters).pipe(map(filters => filters?.['parent.name']?.value || []))), + concatLatestFrom(() => this.store.select(selectTableFilters).pipe(map(filters => filters?.['parent.name']?.value || []))), mergeMap(([res, filteredParentIds]: [ProjectsGetTaskParentsResponse, string[]]) => { const missingParentsIds = filteredParentIds.filter(parentId => !res.parents.find(parent => (parent as any).id === parentId)); return (missingParentsIds.length ? this.apiTasks.tasksGetAllEx({ @@ -415,10 +478,10 @@ export class CommonExperimentsViewEffects { getCustomMetrics = createEffect(() => this.actions$.pipe( ofType(exActions.getCustomMetrics), - withLatestFrom( + concatLatestFrom(() => [ this.store.select(selectRouterParams).pipe(map(params => params?.projectId)), this.store.select(selectIsDeepMode) - ), + ]), switchMap(([action, projectId, isDeep]) => this.projectsApi.projectsGetUniqueMetricVariants({ project: projectId === '*' ? null : projectId, include_subprojects: isDeep @@ -442,7 +505,10 @@ export class CommonExperimentsViewEffects { getCustomHyperParams = createEffect(() => this.actions$.pipe( ofType(exActions.getCustomHyperParams), - withLatestFrom(this.store.select(selectRouterParams).pipe(map(params => params?.projectId)), this.store.select(selectIsDeepMode)), + concatLatestFrom(() => [ + this.store.select(selectRouterParams).pipe(map(params => params?.projectId)), + this.store.select(selectIsDeepMode) + ]), switchMap(([action, projectId, isDeep]) => this.projectsApi.projectsGetHyperParameters({ project: projectId === '*' ? null : projectId, page_size: 5000, @@ -467,18 +533,19 @@ export class CommonExperimentsViewEffects { hyperParameterMenuClicked = createEffect(() => this.actions$.pipe( ofType(exActions.hyperParamSelectedExperiments), - withLatestFrom( + concatLatestFrom(() => [ this.store.select(selectIsDeepMode), - this.store.select(selectSelectedProject) - ), - switchMap(([action, isDeep, selectedProject]) => { + this.store.select(selectSelectedProject), + this.store.select(selectIsCompare), + ]), + switchMap(([action, isDeep, selectedProject, isCompare]) => { const id = action.col.id; const getter = Array.isArray(action.col.getter) ? action.col.getter[0] : action.col.getter; const projectId = action.col.projectId || selectedProject.id; const {section, name} = decodeHyperParam(getter || id); return this.projectsApi.projectsGetHyperparamValues({ include_subprojects: isDeep, section, name, - ...(projectId !== '*' && {projects: [projectId]}) + ...((!isCompare && projectId !== '*') && {projects: [projectId]}) }).pipe( map((data: ProjectsGetHyperparamValuesResponse) => { const values = data.values.filter(x => hasValue(x) && x !== ''); @@ -490,22 +557,29 @@ export class CommonExperimentsViewEffects { selectAll = createEffect(() => this.actions$.pipe( ofType(exActions.selectAllExperiments), - withLatestFrom( + concatLatestFrom(() => [ this.store.select(selectRouterParams).pipe(map(params => params?.projectId)), this.store.select(selectIsArchivedMode), this.store.select(exSelectors.selectGlobalFilter), + this.store.select(exSelectors.selectExperimentsTableCols), + this.store.select(exSelectors.selectExperimentsMetricsColsForProject), this.store.select(exSelectors.selectTableFilters), this.store.select(selectIsDeepMode), this.store.select(selectShowHidden), this.store.select(selectIsPipelines), this.store.select(selectIsDatasets), - ), - switchMap(([action, projectId, archived, globalSearch, tableFilters, deep, showHidden, isPipeline, isDataset]) => { + ]), + switchMap(([ + action, projectId, archived, globalSearch, cols, metricCols, + tableFilters, deep, showHidden, isPipeline, isDataset + ]) => { const pageSize = 5000; const query = this.getGetAllQuery({ projectId, searchQuery: globalSearch, archived, + cols, + metricCols, tableFilters: action.filtered ? tableFilters : {}, orderFields: [{order: -1, field: EXPERIMENTS_TABLE_COL_FIELDS.LAST_UPDATE}], deep, @@ -534,7 +608,7 @@ export class CommonExperimentsViewEffects { setURLParams = createEffect(() => this.actions$.pipe( ofType(exActions.updateUrlParams, exActions.toggleColHidden), - withLatestFrom( + concatLatestFrom(() => [ this.store.select(selectRouterParams).pipe(map(params => params?.projectId)), this.store.select(selectIsArchivedMode), this.store.select(exSelectors.selectGlobalFilter), @@ -546,7 +620,7 @@ export class CommonExperimentsViewEffects { this.store.select(exSelectors.selectExperimentsTableColsOrder), this.store.select(selectIsDeepMode), this.route.queryParams - ), + ]), map(([, projectId, isArchived, , sortFields, filters, cols, hiddenCols, metricsCols, colsOrder, isDeep, queryParams ]) => { @@ -626,8 +700,9 @@ export class CommonExperimentsViewEffects { .concat(isDataset ? ['dataset'] : []); let filters = createFiltersFromStore(tableFilters, true); + const tableCols = cols.length > 0 ? cols : isDataset ? INITIAL_CONTROLLER_TABLE_COLS : INITIAL_EXPERIMENT_TABLE_COLS; filters = Object.keys(filters).reduce((acc, colId) => { - const col = [...metricCols, ...cols].find(c => c.id === colId); + const col = [...metricCols, ...tableCols].find(c => c.id === colId); let key = col?.getter || colId; if (col?.isParam && typeof col.getter === 'string') { key = encodeHyperParameter(col.getter); @@ -650,11 +725,11 @@ export class CommonExperimentsViewEffects { return getter ? {...field, field: getter} : field; }); - const colsFilters = flatten(cols.filter(col => col.id !== 'selected' && !col.hidden).map(col => col.getter || col.id)); + const colsFilters = flatten(tableCols.filter(col => col.id !== 'selected' && !col.hidden).map(col => col.getter || col.id)); const metricColsFilters = metricCols ? flatten(metricCols.map(col => (col?.isParam && typeof col.getter === 'string') ? encodeHyperParameter(col.getter) : col.getter || col.id)) : []; const only_fields = [...new Set([...MINIMUM_ONLY_FIELDS, ...colsFilters, ...metricColsFilters] - .concat(isPipeline || isDataset ? ['runtime._pipeline_hash', 'runtime.version', 'execution.queue', 'type', 'hyperparams.properties.version'] : []))]; + .concat(isPipeline || isDataset ? ['parent.name', 'runtime._pipeline_hash', 'runtime.version', 'execution.queue', 'type', 'hyperparams.properties.version'] : []))]; return { ...filters, id: selectedIds, @@ -684,8 +759,8 @@ export class CommonExperimentsViewEffects { fetchExperiments$(scrollId1: string, refreshScroll: boolean = false, pageSize = EXPERIMENTS_PAGE_SIZE): Observable { return of(scrollId1) .pipe( - withLatestFrom( - this.store.select(selectRouterParams).pipe(map(params => params?.projectId)), + concatLatestFrom(() => [ + this.store.select(selectRouterProjectId), this.store.select(selectIsArchivedMode), this.store.select(exSelectors.selectGlobalFilter), this.store.select(exSelectors.selectTableSortFields), @@ -700,7 +775,7 @@ export class CommonExperimentsViewEffects { this.store.select(selectViewArchivedInAddTable), this.store.select(selectIsPipelines), this.store.select(selectIsDatasets), - ), + ]), switchMap(([ scrollId, projectId, isArchived, gb, orderFields, filters, selectedExperiments, showAllSelectedIsActive, cols, metricCols, deep, showHidden, isCompare, showArcived, @@ -712,9 +787,23 @@ export class CommonExperimentsViewEffects { } const selectedIds = showAllSelectedIsActive ? selectedExperiments.map(exp => exp.id) : []; return this.apiTasks.tasksGetAllEx(this.getGetAllQuery({ - refreshScroll, scrollId, projectId, searchQuery: gb, archived: isArchived, orderFields, - tableFilters, selectedIds, cols, metricCols, deep, showHidden, isCompare, showArchived: isCompare && showArcived, - isPipeline, pageSize, isDataset + refreshScroll, + scrollId, + projectId, + searchQuery: gb, + archived: isArchived, + orderFields, + tableFilters, + selectedIds, + cols, + metricCols, + deep, + showHidden, + isCompare, + showArchived: isCompare && showArcived, + isPipeline, + pageSize, + isDataset })); }) ); @@ -726,9 +815,9 @@ export class CommonExperimentsViewEffects { getTagsEffect = createEffect(() => this.actions$.pipe( ofType(exActions.getTags), - withLatestFrom(this.store.select(selectRouterParams).pipe(map(params => params?.projectId))), + concatLatestFrom(() => this.store.select(selectRouterParams).pipe(map(params => params?.projectId))), switchMap(([action, projectId]) => this.projectsApi.projectsGetTaskTags({ - projects: projectId === '*' ? [] : [projectId] + projects: (projectId === '*' || action.allProjects) ? [] : [projectId] }).pipe( mergeMap(res => [ exActions.setTags({tags: res.tags.concat(null)}), @@ -747,7 +836,7 @@ export class CommonExperimentsViewEffects { setSelectedExperiments = createEffect(() => this.actions$.pipe( ofType(exActions.setSelectedExperiments, exActions.updateExperiment, updateManyExperiment.type), - withLatestFrom( + concatLatestFrom(() => this.store.select(exSelectors.selectSelectedExperiments), ), switchMap(([action, selectSelectedExperiments]) => { diff --git a/src/app/webapp-common/experiments/experiment-routes.ts b/src/app/webapp-common/experiments/experiment-routes.ts index 0a801465..6dc500be 100644 --- a/src/app/webapp-common/experiments/experiment-routes.ts +++ b/src/app/webapp-common/experiments/experiment-routes.ts @@ -1,7 +1,7 @@ import {Routes} from '@angular/router'; import {ExperimentsComponent} from '@common/experiments/experiments.component'; import {ExperimentInfoExecutionComponent} from './containers/experiment-info-execution/experiment-info-execution.component'; -import {LeavingBeforeSaveAlertGuard} from '../shared/guards/leaving-before-save-alert.guard'; +import {leavingBeforeSaveAlertGuard} from '../shared/guards/leaving-before-save-alert.guard'; import {ExperimentInfoArtifactsComponent} from './containers/experiment-info-aritfacts/experiment-info-artifacts.component'; import {ExperimentInfoModelComponent} from './containers/experiment-info-model/experiment-info-model.component'; import {ExperimentInfoArtifactItemComponent} from './containers/experiment-info-artifact-item/experiment-info-artifact-item.component'; @@ -14,6 +14,7 @@ import {ExperimentOutputScalarsComponent} from './containers/experiment-output-s import {ExperimentOutputPlotsComponent} from './containers/experiment-output-plots/experiment-output-plots.component'; import {DebugImagesComponent} from '../debug-images/debug-images.component'; import {ExperimentOutputLogComponent} from './containers/experiment-output-log/experiment-output-log.component'; +import {selectIsExperimentInEditMode} from '@common/experiments/reducers'; export const routes: Routes = [ { @@ -27,13 +28,13 @@ export const routes: Routes = [ { path: 'execution', component: ExperimentInfoExecutionComponent, - canDeactivate: [LeavingBeforeSaveAlertGuard], + canDeactivate: [leavingBeforeSaveAlertGuard(selectIsExperimentInEditMode)], data: {minimized: true} }, { path: 'artifacts', component: ExperimentInfoArtifactsComponent, - canDeactivate: [LeavingBeforeSaveAlertGuard], + canDeactivate: [leavingBeforeSaveAlertGuard(selectIsExperimentInEditMode)], data : {minimized: true}, children: [ {path: '', redirectTo: 'input-model', pathMatch: 'full'}, @@ -49,7 +50,7 @@ export const routes: Routes = [ { path: 'hyper-params', component: ExperimentInfoHyperParametersComponent, - canDeactivate: [LeavingBeforeSaveAlertGuard], + canDeactivate: [leavingBeforeSaveAlertGuard(selectIsExperimentInEditMode)], data : {minimized: true}, children: [ {path: 'configuration/:configObject', component: ExperimentInfoTaskModelComponent}, @@ -81,13 +82,13 @@ export const routes: Routes = [ {path: '', redirectTo: 'execution', pathMatch: 'full'}, {path: 'execution', component: ExperimentInfoExecutionComponent, - canDeactivate: [LeavingBeforeSaveAlertGuard] + canDeactivate: [leavingBeforeSaveAlertGuard(selectIsExperimentInEditMode)] }, { path: 'hyper-params', component: ExperimentInfoHyperParametersComponent, data: {}, - canDeactivate: [LeavingBeforeSaveAlertGuard], + canDeactivate: [leavingBeforeSaveAlertGuard(selectIsExperimentInEditMode)], children: [ {path: 'configuration/:configObject', component: ExperimentInfoTaskModelComponent}, {path: 'hyper-param/:hyperParamId', component: ExperimentInfoHyperParametersFormContainerComponent} @@ -97,7 +98,7 @@ export const routes: Routes = [ path: 'artifacts', component: ExperimentInfoArtifactsComponent, data: {}, - canDeactivate: [LeavingBeforeSaveAlertGuard], + canDeactivate: [leavingBeforeSaveAlertGuard(selectIsExperimentInEditMode)], children: [ {path: '', redirectTo: 'input-model', pathMatch: 'full'}, {path: 'input-model/:modelId', component: ExperimentInfoModelComponent , data: {outputModel: false}}, diff --git a/src/app/webapp-common/experiments/experiment.consts.ts b/src/app/webapp-common/experiments/experiment.consts.ts index 0e3c9427..47c3408f 100644 --- a/src/app/webapp-common/experiments/experiment.consts.ts +++ b/src/app/webapp-common/experiments/experiment.consts.ts @@ -8,44 +8,45 @@ export const EXPERIMENTS_PREFIX = 'EXPERIMENTS_'; export const INITIAL_EXPERIMENT_TABLE_COLS: ISmCol[] = [ { - id : EXPERIMENTS_TABLE_COL_FIELDS.SELECTED, - sortable : false, - filterable : false, - headerType : ColHeaderTypeEnum.checkBox, - header : '', - hidden : false, - bodyStyleClass : 'selected-col-body type-col', + id: EXPERIMENTS_TABLE_COL_FIELDS.SELECTED, + sortable: false, + filterable: false, + headerType: ColHeaderTypeEnum.checkBox, + header: '', + hidden: false, + bodyStyleClass: 'selected-col-body type-col', headerStyleClass: 'selected-col-header', - style : {width: '65px'}, - disableDrag : true, + style: {width: '65px'}, + disableDrag: true, disablePointerEvents: true, }, { - id : EXPERIMENTS_TABLE_COL_FIELDS.ID, - headerType : ColHeaderTypeEnum.title, - header : 'ID', - style : {width: '100px'}, + id: EXPERIMENTS_TABLE_COL_FIELDS.ID, + headerType: ColHeaderTypeEnum.title, + header: 'ID', + style: {width: '100px'}, }, { - id : EXPERIMENTS_TABLE_COL_FIELDS.TYPE, - headerType : ColHeaderTypeEnum.sortFilter, - sortable : true, - filterable : true, - header : 'TYPE', + id: EXPERIMENTS_TABLE_COL_FIELDS.TYPE, + headerType: ColHeaderTypeEnum.sortFilter, + sortable: true, + filterable: true, + header: 'TYPE', bodyStyleClass: 'type-col', - style : {width: '115px'}, + style: {width: '115px'}, showInCardFilters: true }, { - id : EXPERIMENTS_TABLE_COL_FIELDS.NAME, - headerType : ColHeaderTypeEnum.sortFilter, - sortable : true, - header : 'NAME', - style : {width: '400px'}, + id: EXPERIMENTS_TABLE_COL_FIELDS.NAME, + headerType: ColHeaderTypeEnum.sortFilter, + sortable: true, + header: 'NAME', + style: {width: '400px'}, }, { id: EXPERIMENTS_TABLE_COL_FIELDS.TAGS, - headerType : ColHeaderTypeEnum.sortFilter, + getter: ['tags', 'system_tags'], + headerType: ColHeaderTypeEnum.sortFilter, filterable: true, searchableFilter: true, sortable: false, @@ -57,94 +58,94 @@ export const INITIAL_EXPERIMENT_TABLE_COLS: ISmCol[] = [ showInCardFilters: true }, { - id : EXPERIMENTS_TABLE_COL_FIELDS.STATUS, - headerType : ColHeaderTypeEnum.sortFilter, - filterable : true, - header : 'STATUS', - style : {width: '115px'}, + id: EXPERIMENTS_TABLE_COL_FIELDS.STATUS, + headerType: ColHeaderTypeEnum.sortFilter, + filterable: true, + header: 'STATUS', + style: {width: '115px'}, showInCardFilters: true }, { - id : EXPERIMENTS_TABLE_COL_FIELDS.PROJECT, - headerType : ColHeaderTypeEnum.sortFilter, - header : 'PROJECT', - filterable : true, + id: EXPERIMENTS_TABLE_COL_FIELDS.PROJECT, + headerType: ColHeaderTypeEnum.sortFilter, + header: 'PROJECT', + filterable: true, searchableFilter: true, asyncFilter: true, paginatedFilterPageSize: rootProjectsPageSize, - sortable : false, - style : {width: '150px'}, + sortable: false, + style: {width: '150px'}, }, { - id : EXPERIMENTS_TABLE_COL_FIELDS.USER, - getter : 'user.name', - headerType : ColHeaderTypeEnum.sortFilter, + id: EXPERIMENTS_TABLE_COL_FIELDS.USER, + getter: 'user.name', + headerType: ColHeaderTypeEnum.sortFilter, searchableFilter: true, - filterable : true, - sortable : false, - header : 'USER', - style : {width: '115px'}, + filterable: true, + sortable: false, + header: 'USER', + style: {width: '115px'}, showInCardFilters: true }, { - id : EXPERIMENTS_TABLE_COL_FIELDS.STARTED, - headerType : ColHeaderTypeEnum.sortFilter, - sortable : true, - filterType : ColHeaderFilterTypeEnum.durationDate, - filterable: true, - searchableFilter: false, - header : 'STARTED', - style : {width: '150px'}, - }, - { - id : EXPERIMENTS_TABLE_COL_FIELDS.LAST_UPDATE, - headerType : ColHeaderTypeEnum.sortFilter, - sortable : true, - filterType : ColHeaderFilterTypeEnum.durationDate, - filterable: true, - searchableFilter: false, - header : 'UPDATED', - label : 'Updated', - style : {width: '150px'}, - }, - { - id : EXPERIMENTS_TABLE_COL_FIELDS.LAST_ITERATION, - headerType : ColHeaderTypeEnum.sortFilter, - sortable : true, - filterType : ColHeaderFilterTypeEnum.durationNumeric, - filterable : true, - searchableFilter: false, - header : 'ITERATION', - label : 'Iterations:', - style : {width: '115px'}, - }, - { - id : EXPERIMENTS_TABLE_COL_FIELDS.COMMENT, + id: EXPERIMENTS_TABLE_COL_FIELDS.STARTED, headerType: ColHeaderTypeEnum.sortFilter, - sortable : true, - header : 'DESCRIPTION', - style : {width: '300px'} + sortable: true, + filterType: ColHeaderFilterTypeEnum.durationDate, + filterable: true, + searchableFilter: false, + header: 'STARTED', + style: {width: '150px'}, }, { - id : EXPERIMENTS_TABLE_COL_FIELDS.ACTIVE_DURATION, + id: EXPERIMENTS_TABLE_COL_FIELDS.LAST_UPDATE, headerType: ColHeaderTypeEnum.sortFilter, - sortable : true, + sortable: true, + filterType: ColHeaderFilterTypeEnum.durationDate, filterable: true, - filterType : ColHeaderFilterTypeEnum.duration, + searchableFilter: false, + header: 'UPDATED', + label: 'Updated', + style: {width: '150px'}, + }, + { + id: EXPERIMENTS_TABLE_COL_FIELDS.LAST_ITERATION, + headerType: ColHeaderTypeEnum.sortFilter, + sortable: true, + filterType: ColHeaderFilterTypeEnum.durationNumeric, + filterable: true, + searchableFilter: false, + header: 'ITERATION', + label: 'Iterations:', + style: {width: '115px'}, + }, + { + id: EXPERIMENTS_TABLE_COL_FIELDS.COMMENT, + headerType: ColHeaderTypeEnum.sortFilter, + sortable: true, + header: 'DESCRIPTION', + style: {width: '300px'} + }, + { + id: EXPERIMENTS_TABLE_COL_FIELDS.ACTIVE_DURATION, + headerType: ColHeaderTypeEnum.sortFilter, + sortable: true, + filterable: true, + filterType: ColHeaderFilterTypeEnum.duration, searchableFilter: false, bodyStyleClass: 'type-col', - header : 'RUN TIME', - style : {width: '150px'} + header: 'RUN TIME', + style: {width: '150px'} }, { - id : EXPERIMENTS_TABLE_COL_FIELDS.PARENT, - getter : [EXPERIMENTS_TABLE_COL_FIELDS.PARENT, 'parent.project.id', 'parent.project.name'], + id: EXPERIMENTS_TABLE_COL_FIELDS.PARENT, + getter: [EXPERIMENTS_TABLE_COL_FIELDS.PARENT, 'parent.project.id', 'parent.project.name'], headerType: ColHeaderTypeEnum.sortFilter, searchableFilter: true, - filterable : true, - sortable : false, - header : 'PARENT TASK', - style : {width: '200px'}, + filterable: true, + sortable: false, + header: 'PARENT TASK', + style: {width: '200px'}, showInCardFilters: true, asyncFilter: true } @@ -191,7 +192,7 @@ export const EXPERIMENT_INFO_ONLY_FIELDS_BASE = [ ]; export const MINIMUM_ONLY_FIELDS = [ - 'name', 'status', 'system_tags', 'project', 'company', 'last_change', 'started', 'last_iteration', 'tags', + 'name', 'status', 'system_tags', 'project.name', 'company', 'last_change', 'last_update', 'started', 'last_iteration', 'tags', 'user.name', 'runtime.progress' ]; diff --git a/src/app/webapp-common/experiments/experiments.component.html b/src/app/webapp-common/experiments/experiments.component.html index 3b4beff1..2795985b 100644 --- a/src/app/webapp-common/experiments/experiments.component.html +++ b/src/app/webapp-common/experiments/experiments.component.html @@ -11,6 +11,7 @@ [tableMode]="firstExperiment && (tableMode$ | async)" [rippleEffect]="tableModeAwareness$ | async" [addButtonTemplate]="addButton" + [noData]="!((experiments$ | async)?.length > 0)" (isArchivedChanged)="archivedChanged($event)" (selectedTableColsChanged)="selectedTableColsChanged($event)" (getMetricsToDisplay)="getMetricsToDisplay()" @@ -21,6 +22,8 @@ (clearSelection)="clearSelection()" (clearTableFilters)="clearTableFiltersHandler($event)" (tableModeChanged)="modeChanged($event); tableModeUserAware()" + (downloadTableAsCSV)="downloadTableAsCSV()" + (downloadFullTableAsCSV)="downloadFullTableAsCSV()" >
-
logo
{{environment.whiteLabelSlogan}}
diff --git a/src/app/webapp-common/layout/header/header.component.ts b/src/app/webapp-common/layout/header/header.component.ts index c83f1c5b..3730dc46 100644 --- a/src/app/webapp-common/layout/header/header.component.ts +++ b/src/app/webapp-common/layout/header/header.component.ts @@ -27,7 +27,7 @@ import {MESSAGES_SEVERITY} from '@common/constants'; export class HeaderComponent implements OnInit, OnDestroy { @Input() isShareMode: boolean; @Input() isLogin: boolean; - isDashboard: boolean; + showLogo: boolean; profile: boolean; userFocus: boolean; environment = ConfigurationService.globalEnvironment; @@ -39,7 +39,7 @@ export class HeaderComponent implements OnInit, OnDestroy { private sub = new Subscription(); constructor( - private store: Store, + private store: Store, private dialog: MatDialog, private tipsService: TipsService, private loginService: LoginService, @@ -72,7 +72,7 @@ export class HeaderComponent implements OnInit, OnDestroy { getRouteData() { this.userFocus = !!this.activeRoute?.firstChild?.snapshot.data?.userFocus; - this.isDashboard = this.activeRoute?.firstChild?.snapshot.url?.[0]?.path === 'dashboard'; + this.showLogo = this.activeRoute?.firstChild?.snapshot.url?.[0]?.path === 'dashboard' || this.activeRoute?.firstChild?.snapshot.data.hideSideNav; } ngOnDestroy(): void { diff --git a/src/app/webapp-common/layout/layout.module.ts b/src/app/webapp-common/layout/layout.module.ts index 636bf7ea..d48da89c 100755 --- a/src/app/webapp-common/layout/layout.module.ts +++ b/src/app/webapp-common/layout/layout.module.ts @@ -20,6 +20,7 @@ import {WelcomeMessageComponent} from '@common/layout/welcome-message/welcome-me import {YouTubePlayerModule} from '@angular/youtube-player'; import {SharedPipesModule} from '@common/shared/pipes/shared-pipes.module'; import {BreadcrumbsComponent} from '@common/layout/breadcrumbs/breadcrumbs.component'; +import {LabeledFormFieldDirective} from '@common/shared/directive/labeled-form-field.directive'; @NgModule({ @@ -36,6 +37,7 @@ import {BreadcrumbsComponent} from '@common/layout/breadcrumbs/breadcrumbs.compo SharedPipesModule, NgOptimizedImage, BreadcrumbsComponent, + LabeledFormFieldDirective ], declarations: [ HeaderComponent, ProjectContextNavbarComponent, LoggedOutAlertComponent, diff --git a/src/app/webapp-common/layout/logged-out-alert/logged-out-alert.component.ts b/src/app/webapp-common/layout/logged-out-alert/logged-out-alert.component.ts index 9d6f93f7..4987a59f 100755 --- a/src/app/webapp-common/layout/logged-out-alert/logged-out-alert.component.ts +++ b/src/app/webapp-common/layout/logged-out-alert/logged-out-alert.component.ts @@ -32,7 +32,7 @@ export class LoggedOutAlertComponent { }); } - constructor(private dialog: MatDialog, private store: Store) { + constructor(private dialog: MatDialog, private store: Store) { } } diff --git a/src/app/webapp-common/layout/project-context-navbar/project-context-navbar.component.scss b/src/app/webapp-common/layout/project-context-navbar/project-context-navbar.component.scss index a4875bdd..023c51fe 100755 --- a/src/app/webapp-common/layout/project-context-navbar/project-context-navbar.component.scss +++ b/src/app/webapp-common/layout/project-context-navbar/project-context-navbar.component.scss @@ -7,7 +7,7 @@ $lg-breakpoint: map-get($grid-breakpoints, lg); margin-left: $side-bar-close-width; .nav-bar-items-container { display: flex; - gap: 1px; + gap: 2px; position: absolute; left: 50%; transform: translateX(-50%); diff --git a/src/app/webapp-common/layout/s3-access-dialog/s3-access-dialog.component.html b/src/app/webapp-common/layout/s3-access-dialog/s3-access-dialog.component.html index 1142aa51..d707af98 100755 --- a/src/app/webapp-common/layout/s3-access-dialog/s3-access-dialog.component.html +++ b/src/app/webapp-common/layout/s3-access-dialog/s3-access-dialog.component.html @@ -3,34 +3,33 @@
- + Key - *Required - + Secret - *Required - + Token - + - + Region - - + Endpoint - - = new EventEmitter(); @Output() closeSave: EventEmitter = new EventEmitter(); @Input() saveEnabled = true; - public formIsSubmitted: boolean; - public secured = window.location.protocol === 'https:'; - constructor(public adminService: AdminService, private formBuilder: UntypedFormBuilder) { + constructor(public adminService: AdminService) { } ngOnChanges(changes: SimpleChanges): void { if (changes) { - this.S3Form= { + this.s3Form = { Key : changes.isAzure.currentValue ? 'azure' : changes.key.currentValue, Secret : changes.secret.currentValue, Token : changes.token.currentValue, @@ -50,16 +52,16 @@ export class S3AccessDialogComponent implements OnChanges { public saveNewCredentials() { this.formIsSubmitted = true; - if (this.S3NGForm.invalid) { + if (this.s3NGForm.invalid) { return false; } else { - this.closeSave.emit(this.S3Form); + this.closeSave.emit(this.s3Form); } } public cancel() { - this.closeCancel.emit(this.S3Form); + this.closeCancel.emit(this.s3Form); } } diff --git a/src/app/webapp-common/layout/server-notification-dialog-container/server-notification-dialog-container.component.ts b/src/app/webapp-common/layout/server-notification-dialog-container/server-notification-dialog-container.component.ts index 2f3ccdfa..4adef312 100644 --- a/src/app/webapp-common/layout/server-notification-dialog-container/server-notification-dialog-container.component.ts +++ b/src/app/webapp-common/layout/server-notification-dialog-container/server-notification-dialog-container.component.ts @@ -17,7 +17,7 @@ export class ServerNotificationDialogContainerComponent implements OnInit, OnDes private notificationSubscription: Subscription; private dialogRef: MatDialogRef; - constructor(private store: Store, private dialog: MatDialog) { + constructor(private store: Store, private dialog: MatDialog) { } ngOnInit() { diff --git a/src/app/webapp-common/layout/tip-of-the-day-modal/tip-of-the-day-modal.component.html b/src/app/webapp-common/layout/tip-of-the-day-modal/tip-of-the-day-modal.component.html index 1c5d594c..01d7138d 100644 --- a/src/app/webapp-common/layout/tip-of-the-day-modal/tip-of-the-day-modal.component.html +++ b/src/app/webapp-common/layout/tip-of-the-day-modal/tip-of-the-day-modal.component.html @@ -21,9 +21,9 @@
-
{{tipIndex + 1}} / {{tips.length}}
+
{{tipIndex + 1}} / {{tips.length}}
diff --git a/src/app/webapp-common/layout/tip-of-the-day-modal/tip-of-the-day-modal.component.ts b/src/app/webapp-common/layout/tip-of-the-day-modal/tip-of-the-day-modal.component.ts index 59b1a3e7..78e5c2d7 100644 --- a/src/app/webapp-common/layout/tip-of-the-day-modal/tip-of-the-day-modal.component.ts +++ b/src/app/webapp-common/layout/tip-of-the-day-modal/tip-of-the-day-modal.component.ts @@ -17,7 +17,7 @@ export class TipOfTheDayModalComponent { private visitedIndex: number = 0; public hideDontShow: boolean; - constructor(public matDialogRef: MatDialogRef, private store: Store, + constructor(public matDialogRef: MatDialogRef, private store: Store, @Inject(MAT_DIALOG_DATA) public data: { tips: Tip[]; visitedIndex: number; hideDontShow: boolean }) { this.tips = data.tips; this.hideDontShow = data.hideDontShow; diff --git a/src/app/webapp-common/layout/welcome-message/welcome-message.component.html b/src/app/webapp-common/layout/welcome-message/welcome-message.component.html index 222667a6..55cdb64a 100644 --- a/src/app/webapp-common/layout/welcome-message/welcome-message.component.html +++ b/src/app/webapp-common/layout/welcome-message/welcome-message.component.html @@ -49,8 +49,8 @@
{{step.header}}
{{step.title}}
{{isJupyter? 'Set the ClearML environment for your notebook':'Run the ClearML setup script'}}
@@ -59,6 +59,7 @@ [hideBackground]="false" [label]="''" [copyIcon]="'al-icon sm-md al-ico-copy-to-clipboard'" + data-id="copyButton" theme="text-area" [clipboardText]="stepCode.innerHTML">
@@ -98,6 +99,7 @@ }
"graph title", [hideBackground]="false" [label]="''" [copyIcon]="'al-icon sm-md al-ico-copy-to-clipboard'" + data-id="copyButton" theme="text-area" [clipboardText]="content.textContent">
@@ -152,6 +155,7 @@ task.get_logger().report_scalar(title="graph title", [hideBackground]="true" [label]="''" [copyIcon]="'al-icon sm-md al-ico-copy-to-clipboard'" + data-id="copyButton" theme="text-area" [clipboardText]="content.textContent">
diff --git a/src/app/webapp-common/layout/welcome-message/welcome-message.component.ts b/src/app/webapp-common/layout/welcome-message/welcome-message.component.ts index 67751902..5ddb4659 100644 --- a/src/app/webapp-common/layout/welcome-message/welcome-message.component.ts +++ b/src/app/webapp-common/layout/welcome-message/welcome-message.component.ts @@ -83,7 +83,7 @@ export class WelcomeMessageComponent implements OnInit, OnDestroy { isJupyter: boolean = false; constructor( - private store: Store, + private store: Store, private dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data, private adminService: AdminService, diff --git a/src/app/webapp-common/login/login/login.component.ts b/src/app/webapp-common/login/login/login.component.ts index 0cd375fb..08ad45b1 100644 --- a/src/app/webapp-common/login/login/login.component.ts +++ b/src/app/webapp-common/login/login/login.component.ts @@ -61,7 +61,7 @@ export class LoginComponent implements OnInit, OnDestroy { private router: Router, private loginService: LoginService, private dialog: MatDialog, - private store: Store, + private store: Store, private route: ActivatedRoute, private userPreferences: UserPreferences, private config: ConfigurationService, diff --git a/src/app/webapp-common/models/actions/models-view.actions.ts b/src/app/webapp-common/models/actions/models-view.actions.ts index bfaf28af..83b0420b 100755 --- a/src/app/webapp-common/models/actions/models-view.actions.ts +++ b/src/app/webapp-common/models/actions/models-view.actions.ts @@ -6,6 +6,9 @@ import {SortMeta} from 'primeng/api'; import {CountAvailableAndIsDisableSelectedFiltered} from '@common/shared/entity-page/items.utils'; import {EXPERIMENTS_PREFIX} from '@common/experiments/experiment.consts'; import {MetricVariantResult} from '~/business-logic/model/projects/metricVariantResult'; +import { + OrganizationPrepareDownloadForGetAllRequest +} from '~/business-logic/model/organization/organizationPrepareDownloadForGetAllRequest'; const MODELS_PREFIX = 'MODELS_'; @@ -42,6 +45,7 @@ export const toggleColHidden = createAction( export const getTags = createAction( MODELS_PREFIX + 'GET_TAGS'); + export const getTagsForAllProjects = createAction( MODELS_PREFIX + 'GET_TAGS_ALL_PROJECTS'); @@ -50,6 +54,11 @@ export const setTags = createAction( props<{ tags: string[] }>() ); +export const addProjectTag = createAction( + MODELS_PREFIX + 'Add project tag', + props<{ tag: string[] }>() +); + export const setMetadataKeys = createAction( MODELS_PREFIX + 'SET_METADATA_KEYS', props<{ keys: string[] }>() @@ -218,3 +227,7 @@ export const setCustomMetrics = createAction( EXPERIMENTS_PREFIX + ' [set custom metrics]', props<{ metrics: MetricVariantResult[] }>() ); +export const prepareTableForDownload = createAction( + MODELS_PREFIX + ' [prepareTableForDownload]', + props<{ entityType: OrganizationPrepareDownloadForGetAllRequest.EntityTypeEnum }>() +); diff --git a/src/app/webapp-common/models/containers/model-experiments-table/model-experiments-table.component.html b/src/app/webapp-common/models/containers/model-experiments-table/model-experiments-table.component.html index 84be2bb8..3af324fa 100644 --- a/src/app/webapp-common/models/containers/model-experiments-table/model-experiments-table.component.html +++ b/src/app/webapp-common/models/containers/model-experiments-table/model-experiments-table.component.html @@ -1,7 +1,7 @@

USED BY

- + ; constructor( - private store: Store, + private store: Store, ) { this.resizedCols$.next(this._resizedCols); this.experiments$ = this.store.select(selectExperimentsList); diff --git a/src/app/webapp-common/models/containers/model-info-experiments/model-info-experiments.component.ts b/src/app/webapp-common/models/containers/model-info-experiments/model-info-experiments.component.ts index 03f2ea66..de712043 100644 --- a/src/app/webapp-common/models/containers/model-info-experiments/model-info-experiments.component.ts +++ b/src/app/webapp-common/models/containers/model-info-experiments/model-info-experiments.component.ts @@ -24,7 +24,7 @@ export class ModelInfoExperimentsComponent { public selectedModelCreatingTaskLink$: Observable; - constructor(private store: Store, + constructor(private store: Store, ) { this.selectedModel$ = this.store.select(selectSelectedModel); this.selectedModelCreatingTaskLink$ = this.selectedModel$.pipe(map(model=>`/projects/${get( model, 'task.project.id', '*')}/experiments/${get(model, 'task.id','' )}` )); diff --git a/src/app/webapp-common/models/containers/model-info-general/model-info-general.component.ts b/src/app/webapp-common/models/containers/model-info-general/model-info-general.component.ts index 03c02c2d..036474f1 100755 --- a/src/app/webapp-common/models/containers/model-info-general/model-info-general.component.ts +++ b/src/app/webapp-common/models/containers/model-info-general/model-info-general.component.ts @@ -22,7 +22,7 @@ export class ModelInfoGeneralComponent implements OnDestroy { public isExample: boolean; private selectedModelSubscription: Subscription; - constructor(private store: Store, private adminService: AdminService) { + constructor(private store: Store, private adminService: AdminService) { this.selectedModelSubscription = this.store.select(selectSelectedModel).pipe( filter(model => !!model)) .subscribe(model => { diff --git a/src/app/webapp-common/models/containers/model-info-labels/model-info-labels.component.ts b/src/app/webapp-common/models/containers/model-info-labels/model-info-labels.component.ts index 35d27215..1c8a4dbf 100755 --- a/src/app/webapp-common/models/containers/model-info-labels/model-info-labels.component.ts +++ b/src/app/webapp-common/models/containers/model-info-labels/model-info-labels.component.ts @@ -19,7 +19,7 @@ export class ModelInfoLabelsComponent { public saving$: Observable; public isSharedAndNotOwner$: Observable; - constructor(private store: Store) { + constructor(private store: Store) { this.selectedModel$ = this.store.select(selectSelectedModel).pipe(filter(model => !!model)); this.saving$ = this.store.select(selectIsModelSaving); this.isSharedAndNotOwner$ = this.store.select(selectIsSharedAndNotOwner); diff --git a/src/app/webapp-common/models/containers/model-info-metadata/model-info-metadata.component.ts b/src/app/webapp-common/models/containers/model-info-metadata/model-info-metadata.component.ts index 10628013..d4387762 100644 --- a/src/app/webapp-common/models/containers/model-info-metadata/model-info-metadata.component.ts +++ b/src/app/webapp-common/models/containers/model-info-metadata/model-info-metadata.component.ts @@ -41,7 +41,7 @@ export class ModelInfoMetadataComponent implements OnInit, OnDestroy { public searchResultsCount: number; public scrollIndexCounter: number; - constructor(private store: Store) { + constructor(private store: Store) { this.selectedModel$ = this.store.select(selectSelectedModel).pipe(filter(model => !!model)); this.saving$ = this.store.select(selectIsModelSaving); this.isSharedAndNotOwner$ = this.store.select(selectIsSharedAndNotOwner); diff --git a/src/app/webapp-common/models/containers/model-info-network/model-info-network.component.ts b/src/app/webapp-common/models/containers/model-info-network/model-info-network.component.ts index ff45e526..7491a0fb 100755 --- a/src/app/webapp-common/models/containers/model-info-network/model-info-network.component.ts +++ b/src/app/webapp-common/models/containers/model-info-network/model-info-network.component.ts @@ -17,7 +17,7 @@ export class ModelInfoNetworkComponent implements OnInit { public saving$: Observable; public isSharedAndNotOwner$: Observable; - constructor(private store: Store) { + constructor(private store: Store) { this.selectedModel$ = this.store.select(selectSelectedModel); this.isSharedAndNotOwner$ = this.store.select(selectIsSharedAndNotOwner); this.saving$ = this.store.select(selectIsModelSaving); diff --git a/src/app/webapp-common/models/containers/model-info-plots/model-info-plots.component.html b/src/app/webapp-common/models/containers/model-info-plots/model-info-plots.component.html index 7f08ed55..a34a2128 100644 --- a/src/app/webapp-common/models/containers/model-info-plots/model-info-plots.component.html +++ b/src/app/webapp-common/models/containers/model-info-plots/model-info-plots.component.html @@ -36,6 +36,7 @@ [minimized]="minimized" [splitSize]="splitSize$ | async" [isDarkTheme]="dark" + [exportForReport]="!modelsFeature" (createEmbedCode)="createEmbedCode($event)" (resetGraphs)="resetMetrics()" > diff --git a/src/app/webapp-common/models/containers/model-info-plots/model-info-plots.component.ts b/src/app/webapp-common/models/containers/model-info-plots/model-info-plots.component.ts index a41dacf9..d151b781 100644 --- a/src/app/webapp-common/models/containers/model-info-plots/model-info-plots.component.ts +++ b/src/app/webapp-common/models/containers/model-info-plots/model-info-plots.component.ts @@ -1,15 +1,17 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; -import {SelectableListItem} from '@common/shared/ui-components/data/selectable-list/selectable-list.model'; +import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild} from '@angular/core'; +import {ActivatedRoute} from '@angular/router'; import {Observable, Subscription} from 'rxjs'; -import {Store} from '@ngrx/store'; -import {selectModelPlots, selectSelectedModel, selectSplitSize} from '@common/models/reducers'; import {distinctUntilChanged, filter} from 'rxjs/operators'; +import {Store} from '@ngrx/store'; +import {MetricsPlotEvent} from '~/business-logic/model/events/metricsPlotEvent'; +import {ReportCodeEmbedService} from '~/shared/services/report-code-embed.service'; +import {SelectableListItem} from '@common/shared/ui-components/data/selectable-list/selectable-list.model'; +import {selectModelPlots, selectSelectedModel, selectSplitSize} from '@common/models/reducers'; import {addMessage} from '@common/core/actions/layout.actions'; import {convertPlots, groupIterations, sortMetricsList} from '@common/tasks/tasks.utils'; -import { MetricsPlotEvent } from '~/business-logic/model/events/metricsPlotEvent'; import {selectRouterParams} from '@common/core/reducers/router-reducer'; import {getPlots, setPlots} from '@common/models/actions/models-info.actions'; -import {ReportCodeEmbedService} from '~/shared/services/report-code-embed.service'; +import {ExperimentGraphsComponent} from '@common/shared/experiment-graphs/experiment-graphs.component'; @Component({ selector: 'sm-model-info-plot', @@ -29,20 +31,27 @@ export class ModelInfoPlotsComponent implements OnInit, OnDestroy { public minimized = true; public dark = false; public splitSize$: Observable; + public modelsFeature = false; private sub = new Subscription(); private refreshDisabled: boolean; private modelId: string; + @ViewChild(ExperimentGraphsComponent) graphsComponent: ExperimentGraphsComponent; + constructor( private store: Store, private cdr: ChangeDetectorRef, - private reportEmbed: ReportCodeEmbedService + private activeRoute: ActivatedRoute, + private reportEmbed: ReportCodeEmbedService, + private route: ActivatedRoute ) { // this.searchTerm$ = this.store.select(selectExperimentMetricsSearchTerm); this.splitSize$ = this.store.select(selectSplitSize); + this.modelsFeature = !!route.snapshot?.parent?.parent?.data?.setAllProject; } ngOnInit(): void { + this.minimized = this.activeRoute.snapshot.routeConfig.data?.minimized; this.sub.add(this.store.select(selectSelectedModel) .pipe( filter(model => !!model), @@ -101,6 +110,7 @@ export class ModelInfoPlotsComponent implements OnInit, OnDestroy { metricSelected(id: string) { this.selectedGraph = id; + this.graphsComponent?.scrollToGraph(id); } hiddenListChanged(hiddenList: string[]) { diff --git a/src/app/webapp-common/models/containers/model-info-scalars/model-info-scalars.component.html b/src/app/webapp-common/models/containers/model-info-scalars/model-info-scalars.component.html deleted file mode 100644 index 17ba8f23..00000000 --- a/src/app/webapp-common/models/containers/model-info-scalars/model-info-scalars.component.html +++ /dev/null @@ -1 +0,0 @@ -

model-info-scalars works!

diff --git a/src/app/webapp-common/models/containers/model-info-scalars/model-info-scalars.component.ts b/src/app/webapp-common/models/containers/model-info-scalars/model-info-scalars.component.ts index 8ad96ede..1c7b7522 100644 --- a/src/app/webapp-common/models/containers/model-info-scalars/model-info-scalars.component.ts +++ b/src/app/webapp-common/models/containers/model-info-scalars/model-info-scalars.component.ts @@ -1,19 +1,19 @@ import {ChangeDetectorRef, Component} from '@angular/core'; +import {RefreshService} from '@common/core/services/refresh.service'; import { ExperimentOutputScalarsComponent } from '@common/experiments/containers/experiment-output-scalars/experiment-output-scalars.component'; -import {ExperimentInfoState} from '~/features/experiments/reducers/experiment-info.reducer'; -import {select, Store} from '@ngrx/store'; +import {Store} from '@ngrx/store'; import {ActivatedRoute, Router} from '@angular/router'; import {selectSelectedModel} from '@common/models/reducers'; import {experimentScalarRequested} from '@common/experiments/actions/common-experiment-output.actions'; import {ReportCodeEmbedService} from '~/shared/services/report-code-embed.service'; -import {distinctUntilChanged, filter, map, tap} from 'rxjs/operators'; +import {debounceTime, distinctUntilChanged, filter, map, tap} from 'rxjs/operators'; import {selectSelectedModelSettings} from '~/features/experiments/reducers'; import {selectRouterParams} from '@common/core/reducers/router-reducer'; import { selectModelInfoHistograms, - selectModelSettingsGroupBy, selectModelSettingsHiddenScalar, selectModelSettingsSmoothWeight, + selectModelSettingsGroupBy, selectModelSettingsHiddenScalar, selectModelSettingsSmoothType, selectModelSettingsSmoothWeight, selectModelSettingsXAxisType, } from '@common/experiments/reducers'; import {isEqual} from 'lodash-es'; @@ -31,25 +31,31 @@ export class ModelInfoScalarsComponent extends ExperimentOutputScalarsComponent protected entityType: 'task' | 'model' = 'model'; constructor( - protected store: Store, + protected store: Store, protected router: Router, + private route: ActivatedRoute, protected activeRoute: ActivatedRoute, protected changeDetection: ChangeDetectorRef, - protected reportEmbed: ReportCodeEmbedService + protected reportEmbed: ReportCodeEmbedService, + protected refreshService: RefreshService ) { - super(store, router, activeRoute, changeDetection, reportEmbed); + super(store, router, activeRoute, changeDetection, reportEmbed, refreshService); + + this.exportForReport = !route.snapshot?.parent?.parent?.data?.setAllProject; this.xAxisType$ = this.store.select(selectModelSettingsXAxisType); this.groupBy$ = this.store.select(selectModelSettingsGroupBy); this.smoothWeight$ = this.store.select(selectModelSettingsSmoothWeight); + this.smoothWeightDelayed$ = this.store.select(selectModelSettingsSmoothWeight).pipe(debounceTime(75)); + this.smoothType$ = this.store.select(selectModelSettingsSmoothType); this.listOfHidden$ = this.store.select(selectModelSettingsHiddenScalar) .pipe(distinctUntilChanged(isEqual)); - this.settings$ = this.store.pipe( - select(selectSelectedModelSettings), + this.settings$ = this.store.select(selectSelectedModelSettings).pipe( filter(settings => !!settings), map(settings => settings ? settings.selectedScalar : null), - distinctUntilChanged() + distinctUntilChanged(), + filter(selectedPlot => selectedPlot !== undefined) ); this.scalars$ = this.store.select(selectModelInfoHistograms) diff --git a/src/app/webapp-common/models/containers/model-info/model-info.component.html b/src/app/webapp-common/models/containers/model-info/model-info.component.html index a27588a3..d3b4891d 100755 --- a/src/app/webapp-common/models/containers/model-info/model-info.component.html +++ b/src/app/webapp-common/models/containers/model-info/model-info.component.html @@ -8,22 +8,38 @@ [model]="((selectedTableModel$ | async) || selectedModel)" [editable]="!isExample" [backdropActive]="backdropActive$ | async" + [minimized]="minimized" + (minimizeClicked)="minimizeView()" + (maximizedClicked)="maximize()" + [isSharedAndNotOwner]="isSharedAndNotOwner$ | async" (modelNameChanged)="updateModelName($event)" (closeInfoClicked)="closePanel()" > -
+
+ + + +
+ +
diff --git a/src/app/webapp-common/models/containers/model-info/model-info.component.scss b/src/app/webapp-common/models/containers/model-info/model-info.component.scss index 1c9a94f4..8543027f 100755 --- a/src/app/webapp-common/models/containers/model-info/model-info.component.scss +++ b/src/app/webapp-common/models/containers/model-info/model-info.component.scss @@ -2,10 +2,6 @@ :host { - sm-model-info-header { - margin-bottom: 12px; - } - sm-model-info-header { display: block; } @@ -28,18 +24,19 @@ } .model-info-body { - height: calc(100% - 119px); + height: calc(100% - 108px); } .tab-nav { - //display: grid; need to fix when we'll have full page - display: flex; + display: grid; grid-template-columns: 200px 1fr 200px; align-items: center; border-bottom: 1px solid #efefef; + margin-right: 30px; &.minimized { display: flex; + margin-right: 0; } @media(max-width: 1200px) { @@ -57,12 +54,19 @@ margin-left: auto; } + sm-experiment-settings.maximized { + display: block; + padding-top: 6px; + } + .refresh-position { - position: absolute; - right: 26px; - top: 2px; display: flex; align-items: center; + margin-left: auto; + } + + .refreshIcon { + margin-right: 10px; } sm-experiment-settings.maximized { diff --git a/src/app/webapp-common/models/containers/model-info/model-info.component.ts b/src/app/webapp-common/models/containers/model-info/model-info.component.ts index 9218f13a..824fbf86 100755 --- a/src/app/webapp-common/models/containers/model-info/model-info.component.ts +++ b/src/app/webapp-common/models/containers/model-info/model-info.component.ts @@ -1,34 +1,39 @@ -import {Component, OnDestroy, OnInit, ViewChild} from '@angular/core'; +import {Component, OnDestroy, OnInit} from '@angular/core'; import {ActivatedRoute, Router} from '@angular/router'; import {selectRouterConfig, selectRouterParams} from '@common/core/reducers/router-reducer'; import {Store} from '@ngrx/store'; -import {ModelInfoState} from '../../reducers/model-info.reducer'; import * as infoActions from '../../actions/models-info.actions'; -import {selectSelectedModel, selectSelectedTableModel, selectSplitSize} from '../../reducers'; +import {selectIsModelInEditMode, selectSelectedModel, selectSelectedTableModel, selectSplitSize} from '../../reducers'; import {SelectedModel} from '../../shared/models.model'; import {selectS3BucketCredentials} from '@common/core/reducers/common-auth-reducer'; import {Observable, Subscription} from 'rxjs'; -import {debounceTime, distinctUntilChanged, filter, map, tap} from 'rxjs/operators'; -import {addMessage} from '@common/core/actions/layout.actions'; +import {debounceTime, distinctUntilChanged, filter, map, tap, withLatestFrom} from 'rxjs/operators'; +import {addMessage, setAutoRefresh} from '@common/core/actions/layout.actions'; import {selectBackdropActive} from '@common/core/reducers/view.reducer'; import {setTableMode} from '@common/models/actions/models-view.actions'; import {isReadOnly} from '@common/shared/utils/is-read-only'; import {MESSAGES_SEVERITY} from '@common/constants'; import {toggleSettings} from '@common/experiments/actions/common-experiment-output.actions'; import {Link} from '@common/shared/components/router-tab-nav-bar/router-tab-nav-bar.component'; +import {selectIsSharedAndNotOwner} from '~/features/experiments/reducers'; +import {RefreshService} from '@common/core/services/refresh.service'; +import {getCompanyTags, setBreadcrumbsOptions, setSelectedProject} from '@common/core/actions/projects.actions'; +import {selectSelectedProject} from '@common/core/reducers/projects.reducer'; +import {Project} from '~/business-logic/model/projects/project'; +import {ALL_PROJECTS_OBJECT} from '@common/core/effects/projects.effects'; @Component({ - selector : 'sm-model-info', + selector: 'sm-model-info', templateUrl: './model-info.component.html', - styleUrls : ['./model-info.component.scss'] + styleUrls: ['./model-info.component.scss'] }) export class ModelInfoComponent implements OnInit, OnDestroy { + public minimized: boolean; public selectedModel: SelectedModel; private sub = new Subscription(); private s3BucketCredentials: Observable; - @ViewChild('modelInfoHeader', { static: true }) modelInfoHeader; public selectedModel$: Observable; public isExample: boolean; public backdropActive$: Observable; @@ -45,19 +50,38 @@ export class ModelInfoComponent implements OnInit, OnDestroy { {name: 'scalars', url: ['scalars']}, {name: 'plots', url: ['plots']}, ] as Link[]; + public isSharedAndNotOwner$: Observable; + public toMaximize: boolean; + private isModelInEditMode$: Observable; + private selectedProject$: Observable; + private modelsFeature: boolean; constructor( private router: Router, - private store: Store, - private route: ActivatedRoute + private store: Store, + private route: ActivatedRoute, + private refresh: RefreshService ) { this.s3BucketCredentials = store.select(selectS3BucketCredentials); this.backdropActive$ = this.store.select(selectBackdropActive); this.selectedTableModel$ = this.store.select(selectSelectedTableModel); this.splitSize$ = this.store.select(selectSplitSize); + this.isSharedAndNotOwner$ = this.store.select((selectIsSharedAndNotOwner)); + this.isModelInEditMode$ = this.store.select(selectIsModelInEditMode); + this.selectedProject$ = this.store.select(selectSelectedProject); + this.modelsFeature = this.route.snapshot.data?.setAllProject; + if (this.modelsFeature) { + this.store.dispatch(setSelectedProject({project: ALL_PROJECTS_OBJECT})); + this.store.dispatch(getCompanyTags()); + } + } ngOnInit() { + this.minimized = this.route.snapshot.firstChild?.data.minimized; + if (!this.minimized) { + this.setupBreadcrumbsOptions(); + } this.scalars$ = this.store.select(selectRouterConfig) .pipe( filter(c => !!c), @@ -68,7 +92,7 @@ export class ModelInfoComponent implements OnInit, OnDestroy { this.sub.add(this.store.select(selectSelectedModel) .subscribe(model => { this.selectedModel = model; - this.isExample = isReadOnly(model); + this.isExample = isReadOnly(model); }) ); @@ -85,16 +109,31 @@ export class ModelInfoComponent implements OnInit, OnDestroy { .subscribe(id => this.store.dispatch(infoActions.getModelInfo({id}))) ); + this.sub.add(this.refresh.tick + .pipe( + withLatestFrom(this.isModelInEditMode$), + filter(([, isModelInEditMode]) => !isModelInEditMode && !this.minimized) + ).subscribe(([auto]) => { + if (auto === null) { + this.store.dispatch(infoActions.refreshModelInfo(this.selectedModel.id)); + } else { + this.store.dispatch(infoActions.getModelInfo({id: this.selectedModel.id})); + } + }) + ); + this.selectedModel$ = this.store.select(selectSelectedModel) .pipe(filter(model => !!model)); } ngOnDestroy(): void { this.sub.unsubscribe(); - this.store.dispatch(infoActions.setModelInfo({model: null})); + if (!this.toMaximize) { + this.store.dispatch(infoActions.setModelInfo({model: null})); + } } - public updateModelName(name) { + public updateModelName(name: string) { if (name.trim().length > 2) { this.store.dispatch(infoActions.updateModelDetails({id: this.selectedModel.id, changes: {name}})); } else { @@ -117,5 +156,50 @@ export class ModelInfoComponent implements OnInit, OnDestroy { this.store.dispatch(setTableMode({mode: 'table'})); return this.router.navigate(['..'], {relativeTo: this.route, queryParamsHandling: 'merge'}); } + + maximize() { + const last = this.route.snapshot.firstChild.url[0].path; + return this.router.navigate(['output', last], {relativeTo: this.route, queryParamsHandling: 'preserve'}); + } + + minimizeView() { + const last = this.route.snapshot.firstChild.url[0].path; + return this.router.navigate(['..', last], {relativeTo: this.route, queryParamsHandling: 'preserve'}); + } + + setAutoRefresh($event: boolean) { + this.store.dispatch(setAutoRefresh({autoRefresh: $event})); + } + + setupBreadcrumbsOptions() { + this.sub.add(this.selectedProject$.pipe( + ).subscribe((selectedProject) => { + if (this.modelsFeature) { + this.store.dispatch(setBreadcrumbsOptions({ + breadcrumbOptions: { + showProjects: false, + featureBreadcrumb: {name: 'Models'}, + } + })); + } else { + this.store.dispatch(setBreadcrumbsOptions({ + breadcrumbOptions: { + showProjects: !!selectedProject, + featureBreadcrumb: { + name: 'PROJECTS', + url: 'projects' + }, + projectsOptions: { + basePath: 'projects', + filterBaseNameWith: null, + compareModule: null, + showSelectedProject: selectedProject?.id !== '*', + ...(selectedProject && {selectedProjectBreadcrumb: {name: selectedProject?.id === '*' ? 'All Models' : selectedProject?.basename}}) + } + } + })); + } + })); + } } diff --git a/src/app/webapp-common/models/containers/model-menu/model-menu.component.html b/src/app/webapp-common/models/containers/model-menu/model-menu.component.html index af6353eb..4d52bf64 100644 --- a/src/app/webapp-common/models/containers/model-menu/model-menu.component.html +++ b/src/app/webapp-common/models/containers/model-menu/model-menu.component.html @@ -1,11 +1,15 @@ - +
- + -
+ +
@@ -27,6 +27,11 @@ [tableFilters]="tableFilters" (clearTableFilters)="clearTableFilters.emit(tableFilters)" > + + + + (); @Output() addModelClicked = new EventEmitter(); @Output() refreshListClicked = new EventEmitter(); diff --git a/src/app/webapp-common/models/dumbs/model-info-header/model-info-header.component.html b/src/app/webapp-common/models/dumbs/model-info-header/model-info-header.component.html index 4a53a749..31d58247 100755 --- a/src/app/webapp-common/models/dumbs/model-info-header/model-info-header.component.html +++ b/src/app/webapp-common/models/dumbs/model-info-header/model-info-header.component.html @@ -20,6 +20,16 @@
+
+ +
+ +
+ +
- - - + > +
diff --git a/src/app/webapp-common/models/dumbs/model-info-header/model-info-header.component.scss b/src/app/webapp-common/models/dumbs/model-info-header/model-info-header.component.scss index 4e92c3c8..3f648e25 100755 --- a/src/app/webapp-common/models/dumbs/model-info-header/model-info-header.component.scss +++ b/src/app/webapp-common/models/dumbs/model-info-header/model-info-header.component.scss @@ -52,12 +52,13 @@ overflow: hidden; } .model-name-cont{ - max-width: calc(100% - 140px); + overflow: hidden; .edit-name { overflow: hidden; } } - - + sm-tag-list { + max-width: 100%; + } } diff --git a/src/app/webapp-common/models/dumbs/model-info-header/model-info-header.component.ts b/src/app/webapp-common/models/dumbs/model-info-header/model-info-header.component.ts index a06df1f7..9c175fc1 100755 --- a/src/app/webapp-common/models/dumbs/model-info-header/model-info-header.component.ts +++ b/src/app/webapp-common/models/dumbs/model-info-header/model-info-header.component.ts @@ -1,11 +1,10 @@ import {Component, EventEmitter, Input, Output, ViewChild} from '@angular/core'; import {TagsMenuComponent} from '@common/shared/ui-components/tags/tags-menu/tags-menu.component'; import {Store} from '@ngrx/store'; -import {selectCompanyTags, selectProjectTags, selectTagsFilterByProject} from '@common/core/reducers/projects.reducer'; -import {Observable} from 'rxjs'; +import {selectCompanyTags, selectSelectedProjectId, selectTagsFilterByProject} from '@common/core/reducers/projects.reducer'; +import {Observable, Subscription} from 'rxjs'; import {addTag, removeTag} from '../../actions/models-menu.actions'; import {SelectedModel, TableModel} from '../../shared/models.model'; -import {MenuComponent} from '@common/shared/ui-components/panel/menu/menu.component'; import {getSysTags} from '../../model.utils'; import {activateModelEdit, cancelModelEdit} from '../../actions/models-info.actions'; import { @@ -17,6 +16,8 @@ import { selectionDisabledPublishModels } from '@common/shared/entity-page/items.utils'; import {addMessage} from '@common/core/actions/layout.actions'; +import {MatMenuTrigger} from '@angular/material/menu'; +import {selectModelsTags} from '@common/models/reducers'; @Component({ selector : 'sm-model-info-header', @@ -26,24 +27,35 @@ import {addMessage} from '@common/core/actions/layout.actions'; export class ModelInfoHeaderComponent { public viewId: boolean; + private sub = new Subscription(); public tagsFilterByProject$: Observable; public projectTags$: Observable; public companyTags$: Observable; public sysTags: string[]; - selectedDisableAvailable: Record; + public selectedDisableAvailable: Record; + public menuPosition = { x: 0, y: 0 }; + public allProjects: boolean; @Input() editable: boolean; @Input() backdropActive: boolean; - @Output() modelNameChanged = new EventEmitter(); + @Input() minimized: boolean; + @Input() isSharedAndNotOwner: boolean; + @Output() modelNameChanged = new EventEmitter(); @Output() closeInfoClicked = new EventEmitter(); - @ViewChild('tagMenu') tagMenu: MenuComponent; - @ViewChild('tagsMenuContent') tagMenuContent: TagsMenuComponent; + @Output() maximizedClicked = new EventEmitter(); + @Output() minimizeClicked = new EventEmitter(); + @ViewChild('tagsMenuTrigger') tagMenuTrigger: MatMenuTrigger; + @ViewChild(TagsMenuComponent) tagMenu: TagsMenuComponent; - constructor(private store: Store) { + constructor(private store: Store) { this.tagsFilterByProject$ = this.store.select(selectTagsFilterByProject); - this.projectTags$ = this.store.select(selectProjectTags); + this.projectTags$ = this.store.select(selectModelsTags); this.companyTags$ = this.store.select(selectCompanyTags); - + this.sub.add(store.select(selectSelectedProjectId) + .subscribe(id => { + this.allProjects = id === '*'; + }) + ); } private _model: TableModel | SelectedModel; @@ -70,15 +82,14 @@ export class ModelInfoHeaderComponent { this.modelNameChanged.emit(name); } - openTagMenu(event: MouseEvent) { + openTagMenu() { if (!this.tagMenu) { return; } window.setTimeout(() => this.store.dispatch(activateModelEdit('tags')), 200); - this.tagMenu.position = {x: event.clientX, y: event.clientY}; window.setTimeout(() => { - this.tagMenu.openMenu(); - this.tagMenuContent.focus(); + this.tagMenuTrigger.openMenu(); + this.tagMenu.focus(); }); } @@ -92,7 +103,7 @@ export class ModelInfoHeaderComponent { tagsMenuClosed() { this.store.dispatch(cancelModelEdit()); - this.tagMenuContent.clear(); + this.tagMenu.clear(); } editExperimentName(edit: boolean) { diff --git a/src/app/webapp-common/models/effects/models-info.effects.ts b/src/app/webapp-common/models/effects/models-info.effects.ts index 72012544..a12b2b09 100755 --- a/src/app/webapp-common/models/effects/models-info.effects.ts +++ b/src/app/webapp-common/models/effects/models-info.effects.ts @@ -35,7 +35,7 @@ export class ModelsInfoEffects { constructor( private actions$: Actions, - private store: Store, + private store: Store, private apiModels: ApiModelsService, private eventsService: ApiEventsService ) {} @@ -171,41 +171,4 @@ export class ModelsInfoEffects { setServerError(error, null, 'Failed to get Plot Charts') ]) )); - - // fetchScalars$ = createEffect(() => this.actions$.pipe( - // ofType(infoActions.getScalars), - // withLatestFrom( - // this.store.select(selectSelectedSettingsxAxisType), - // this.store.select(selectExperimentHistogramCacheAxisType) - // ), - // switchMap(([action, axisType, prevAxisType]) => { - // if ([ScalarKeyEnum.IsoTime, ScalarKeyEnum.Timestamp].includes(prevAxisType) && - // [ScalarKeyEnum.IsoTime, ScalarKeyEnum.Timestamp].includes(axisType)) { - // return [ - // deactivateLoader(infoActions.refreshModelInfo.type), - // deactivateLoader(action.type) - // ]; - // } - // - // return this.eventsService.eventsScalarMetricsIterHistogram({ - // task: action.id, - // // eslint-disable-next-line @typescript-eslint/naming-convention - // model_events: true, - // key: axisType === ScalarKeyEnum.IsoTime ? ScalarKeyEnum.Timestamp : axisType - // }) - // .pipe( - // mergeMap(res => [ - // infoActions.setScalars({scalars: res, axisType}), - // deactivateLoader(infoActions.refreshModelInfo.type), - // deactivateLoader(action.type) - // ]), - // catchError(error => [ - // requestFailed(error), - // deactivateLoader(action.type), - // deactivateLoader(infoActions.refreshModelInfo.type), - // setServerError(error, null, 'Failed to get Scalar Charts') - // ]) - // ); - // }) - // )); } diff --git a/src/app/webapp-common/models/effects/models-menu.effects.ts b/src/app/webapp-common/models/effects/models-menu.effects.ts index 329ea88f..f76ba00d 100644 --- a/src/app/webapp-common/models/effects/models-menu.effects.ts +++ b/src/app/webapp-common/models/effects/models-menu.effects.ts @@ -1,9 +1,8 @@ import {Injectable} from '@angular/core'; -import {Actions, createEffect, ofType} from '@ngrx/effects'; +import {Actions, concatLatestFrom, createEffect, ofType} from '@ngrx/effects'; import {Action, Store} from '@ngrx/store'; -import {ModelInfoState} from '../reducers/model-info.reducer'; import {ApiModelsService} from '~/business-logic/api-services/models.service'; -import {catchError, map, mergeMap, switchMap, tap, withLatestFrom} from 'rxjs/operators'; +import {catchError, map, mergeMap, switchMap, tap} from 'rxjs/operators'; import * as infoActions from '../actions/models-info.actions'; import {updateModelDetails} from '../actions/models-info.actions'; import * as viewActions from '../actions/models-view.actions'; @@ -11,29 +10,31 @@ import * as menuActions from '../actions/models-menu.actions'; import {addTag, removeTag} from '../actions/models-menu.actions'; import {activeLoader, addMessage, deactivateLoader, setServerError} from '../../core/actions/layout.actions'; import {requestFailed} from '../../core/actions/http.actions'; -import {Router} from '@angular/router'; +import {ActivatedRoute, Router} from '@angular/router'; import {ApiTasksService} from '~/business-logic/api-services/tasks.service'; import {of} from 'rxjs'; -import {selectSelectedModel, selectSelectedModels, selectSelectedTableModel, selectTableMode} from '../reducers'; +import {selectSelectedModel, selectSelectedModels, selectSelectedTableModel} from '../reducers'; import {SelectedModel} from '../shared/models.model'; -import {RouterState, selectRouterConfig, selectRouterParams} from '../../core/reducers/router-reducer'; +import {RouterState, selectRouterConfig} from '../../core/reducers/router-reducer'; import {ModelsArchiveManyResponse} from '~/business-logic/model/models/modelsArchiveManyResponse'; -import {EmptyAction} from '~/app.constants'; +import {emptyAction} from '~/app.constants'; import {EntityTypeEnum} from '~/shared/constants/non-common-consts'; import {ModelsUnarchiveManyResponse} from '~/business-logic/model/models/modelsUnarchiveManyResponse'; import {getNotificationAction, MenuItems, MoreMenuItems} from '../../shared/entity-page/items.utils'; import {MESSAGES_SEVERITY} from '@common/constants'; +import {addProjectTag} from '../actions/models-view.actions'; @Injectable() export class ModelsMenuEffects { constructor( private actions$: Actions, - private store: Store, + private store: Store, private apiModels: ApiModelsService, private apiTasks: ApiTasksService, - private router: Router) { - } + private router: Router, + private readonly route: ActivatedRoute + ) {} activeLoader = createEffect( () => this.actions$.pipe( ofType(menuActions.archiveSelectedModels, menuActions.publishModelClicked, @@ -43,7 +44,7 @@ export class ModelsMenuEffects { publishModel$ = createEffect( () => this.actions$.pipe( ofType(menuActions.publishModelClicked), - withLatestFrom(this.store.select(selectSelectedModel)), + concatLatestFrom(() => this.store.select(selectSelectedModel)), switchMap(([action, selectedModel]) => { const ids = action.selectedEntities.map(model => model.id); return this.apiModels.modelsPublishMany({ids}) @@ -65,7 +66,7 @@ export class ModelsMenuEffects { changeProject$ = createEffect( () => this.actions$.pipe( ofType(menuActions.changeProjectRequested), - withLatestFrom(this.store.select(selectSelectedModel)), + concatLatestFrom(() => this.store.select(selectSelectedModel)), switchMap( ([action, selectedInfoModel]) => { const selectedModel = action.selectedModels.find(model => model.id === selectedInfoModel?.id); @@ -75,10 +76,15 @@ export class ModelsMenuEffects { // eslint-disable-next-line @typescript-eslint/naming-convention project_name: action.project.name}) .pipe( - tap((res) => this.router.navigate([`projects/${action.project.id? action.project.id : res.project_id ?? '*'}/models/${action.selectedModels.length === 1 ? action.selectedModels[0].id : ''}`], {queryParamsHandling: 'merge'})), + tap(res => this.route.snapshot.firstChild.data.setAllProject ? + this.router.navigate([]) : + this.router.navigate([ + 'projects', action.project.id? action.project.id : res.project_id ?? '*', + 'models', action.selectedModels.length === 1 ? action.selectedModels[0].id : '' + ], {queryParamsHandling: 'merge'})), mergeMap(() => [ viewActions.resetState(), - selectedModel ? infoActions.setModelInfo({model: selectedModel}) : new EmptyAction(), + selectedModel ? infoActions.setModelInfo({model: selectedModel}) : emptyAction(), deactivateLoader(action.type), addMessage(MESSAGES_SEVERITY.SUCCESS, `Model moved successfully to ${action.project.name ?? 'Projects root'}`) ]), @@ -107,16 +113,19 @@ export class ModelsMenuEffects { addTag$ = createEffect( () => this.actions$.pipe( ofType(addTag), - withLatestFrom(this.store.select(selectSelectedModels), this.store.select(selectSelectedTableModel)), + concatLatestFrom(() => [ + this.store.select(selectSelectedModels), + this.store.select(selectSelectedTableModel) + ]), switchMap(([action, selectedModels, selectedModelInfo]) => { const modelsFromState = selectedModelInfo ? selectedModels.concat(selectedModelInfo) as SelectedModel[] : selectedModels; - return action.models.map(model => { + return [...action.models.map(model => { const modelFromState = modelsFromState.find(mod => mod.id === model.id); return updateModelDetails({ id: model.id, changes: {tags: Array.from(new Set((modelFromState?.tags || model.tags || []).concat([action.tag]))).sort() as string[]} }); - }); + }), addProjectTag]; } ) )); @@ -153,13 +162,11 @@ export class ModelsMenuEffects { archiveModels = createEffect(() => this.actions$.pipe( ofType(menuActions.archiveSelectedModels), - withLatestFrom( - this.store.select(selectSelectedTableModel) - ), + concatLatestFrom(() => this.store.select(selectSelectedTableModel)), switchMap(([action, selectedTableModel]) => this.apiModels.modelsArchiveMany({ids: action.selectedEntities.map((model) => model.id)}) .pipe( - withLatestFrom(this.store.select(selectRouterConfig)), + concatLatestFrom(() => this.store.select(selectRouterConfig)), mergeMap(([res, routerConfig]: [ModelsArchiveManyResponse, RouterState['config']]) => { const models = action.selectedEntities; const allFailed = res.failed.length === models.length; @@ -201,19 +208,22 @@ export class ModelsMenuEffects { restoreModels = createEffect(() => this.actions$.pipe( ofType(menuActions.restoreSelectedModels), - withLatestFrom( - this.store.select(selectRouterParams), + concatLatestFrom(() => [ this.store.select(selectSelectedTableModel), - this.store.select(selectTableMode) - ), - tap(([action, routerParams, selectedModel, tableMode]) => { + ]), + tap(([action, selectedModel]) => { if (this.isSelectedModelInCheckedModels(action.selectedEntities, selectedModel)) { - this.router.navigate([`projects/${routerParams.projectId}/models/${tableMode === 'info' ? routerParams.modelId : ''}`]); + this.router.navigate([], + { + relativeTo: this.route, + queryParams: {archive: undefined}, + queryParamsHandling: 'merge', + }); } }), switchMap(([action]) => this.apiModels.modelsUnarchiveMany({ids: action.selectedEntities.map((model) => model.id)}) .pipe( - withLatestFrom(this.store.select(selectRouterConfig)), + concatLatestFrom(() => this.store.select(selectRouterConfig)), mergeMap(([res, routerConfig]: [ModelsUnarchiveManyResponse, RouterState['config']]) => { const models = action.selectedEntities; const allFailed = res.failed.length === models.length; diff --git a/src/app/webapp-common/models/effects/models-view.effects.ts b/src/app/webapp-common/models/effects/models-view.effects.ts index fec9c8af..60be02f3 100755 --- a/src/app/webapp-common/models/effects/models-view.effects.ts +++ b/src/app/webapp-common/models/effects/models-view.effects.ts @@ -1,6 +1,6 @@ import {Injectable} from '@angular/core'; import {ActivatedRoute, Router} from '@angular/router'; -import {Actions, createEffect, ofType} from '@ngrx/effects'; +import {Actions, concatLatestFrom, createEffect, ofType} from '@ngrx/effects'; import {Action, Store} from '@ngrx/store'; import {flatten, isEqual} from 'lodash-es'; import {EMPTY, of} from 'rxjs'; @@ -14,25 +14,28 @@ import { reduce, switchMap, tap, - withLatestFrom } from 'rxjs/operators'; import {ApiModelsService} from '~/business-logic/api-services/models.service'; import {BlModelsService} from '~/business-logic/services/models.service'; import {requestFailed} from '../../core/actions/http.actions'; import {activeLoader, addMessage, deactivateLoader, setServerError} from '../../core/actions/layout.actions'; -import {getFilteredUsers, setArchive as setProjectArchive, setProjectUsers} from '../../core/actions/projects.actions'; +import { + downloadForGetAll, + getFilteredUsers, + setArchive as setProjectArchive, + setProjectUsers +} from '../../core/actions/projects.actions'; import {setURLParams} from '../../core/actions/router.actions'; import {selectIsArchivedMode, selectIsDeepMode, selectSelectedProject} from '../../core/reducers/projects.reducer'; import {selectRouterParams} from '../../core/reducers/router-reducer'; import {selectAppVisible} from '../../core/reducers/view.reducer'; -import {addMultipleSortColumns, getRouteFullUrl} from '../../shared/utils/shared-utils'; +import {addMultipleSortColumns} from '../../shared/utils/shared-utils'; import {getModelInfo, refreshModelInfo} from '../actions/models-info.actions'; import * as actions from '../actions/models-view.actions'; import {setMetadataKeys, setSelectedModelsDisableAvailable} from '../actions/models-view.actions'; import {MODELS_PAGE_SIZE, MODELS_TABLE_COLS} from '../models.consts'; import * as modelsSelectors from '../reducers'; import {selectModelsList, selectSelectedModels, selectTableFilters, selectTableSortFields} from '../reducers'; -import {IModelsViewState} from '../reducers/models-view.reducer'; import {MODEL_TAGS, MODELS_ONLY_FIELDS, MODELS_TABLE_COL_FIELDS} from '../shared/models.const'; import {SelectedModel} from '../shared/models.model'; import {ModelsGetAllExRequest} from '~/business-logic/model/models/modelsGetAllExRequest'; @@ -42,7 +45,7 @@ import { encodeColumns, encodeOrder } from '../../shared/utils/tableParamEncode'; -import {EmptyAction} from '~/app.constants'; +import {emptyAction} from '~/app.constants'; import {ApiProjectsService} from '~/business-logic/api-services/projects.service'; import {SearchState} from '../../common-search/common-search.reducer'; import {SortMeta} from 'primeng/api'; @@ -67,14 +70,16 @@ import {selectActiveWorkspaceReady} from '~/core/reducers/view.reducer'; import {escapeRegex} from '@common/shared/utils/escape-regex'; import {MESSAGES_SEVERITY} from '@common/constants'; import {sortByField} from '@common/tasks/tasks.utils'; +import {ApiOrganizationService} from '~/business-logic/api-services/organization.service'; +import {prepareColsForDownload} from '@common/shared/utils/download'; @Injectable() export class ModelsViewEffects { constructor( - private actions$: Actions, private store: Store, private apiModels: ApiModelsService, + private actions$: Actions, private store: Store, private apiModels: ApiModelsService, private projectsApi: ApiProjectsService, private modelBl: BlModelsService, private router: Router, - private route: ActivatedRoute + private route: ActivatedRoute, private orgApi: ApiOrganizationService ) { } @@ -88,9 +93,7 @@ export class ModelsViewEffects { tableSortChange = createEffect(() => this.actions$.pipe( ofType(actions.tableSortChanged), - withLatestFrom( - this.store.select(selectTableSortFields), - ), + concatLatestFrom(() => this.store.select(selectTableSortFields)), switchMap(([action, oldOrders]) => { const orders = addMultipleSortColumns(oldOrders, action.colId, action.isShift); return [setURLParams({orders, update: true})]; @@ -99,7 +102,7 @@ export class ModelsViewEffects { tableFilterChange = createEffect(() => this.actions$.pipe( ofType(actions.tableFilterChanged), - withLatestFrom(this.store.select(selectTableFilters)), + concatLatestFrom(() => this.store.select(selectTableFilters)), switchMap(([action, oldFilters]) => [setURLParams({ filters: { @@ -115,10 +118,10 @@ export class ModelsViewEffects { getModelsMetadataValuesForKey = createEffect(() => this.actions$.pipe( ofType(actions.getModelsMetadataValuesForKey), - withLatestFrom( + concatLatestFrom(() => [ this.store.select(selectIsDeepMode), this.store.select(selectSelectedProject) - ), + ]), switchMap(([action, isDeep, selectedProject]) => { const projectId = action.col.projectId || selectedProject.id; return this.projectsApi.projectsGetModelMetadataValues({ @@ -153,7 +156,10 @@ export class ModelsViewEffects { catchError(error => [ requestFailed(error), deactivateLoader('Fetch Models'), - addMessage('warn', 'Fetch Models for selection failed', [{name: 'More info', actions: [setServerError(error, null, 'Fetch Models for selection failed')]}]) + addMessage('warn', 'Fetch Models for selection failed', [{ + name: 'More info', + actions: [setServerError(error, null, 'Fetch Models for selection failed')] + }]) ]) ) ) @@ -161,8 +167,8 @@ export class ModelsViewEffects { getFrameworksEffect = createEffect(() => this.actions$.pipe( ofType(actions.getFrameworks, actions.getAllProjectsFrameworks), - withLatestFrom( - this.store.select(selectRouterParams).pipe(map(params => params?.projectId)), + concatLatestFrom(() => + this.store.select(selectRouterParams).pipe(map(params => params?.projectId ?? '*')), ), switchMap(([action, projectId]) => this.apiModels.modelsGetFrameworks({projects: projectId !== '*' && action.type !== actions.getAllProjectsFrameworks.type ? [projectId] : []}) .pipe( @@ -171,7 +177,10 @@ export class ModelsViewEffects { ]), catchError(error => [ requestFailed(error), - addMessage('warn', 'Fetch frameworks failed', [{name: 'More info', actions: [setServerError(error, null, 'Fetch frameworks failed')]}])] + addMessage('warn', 'Fetch frameworks failed', [{ + name: 'More info', + actions: [setServerError(error, null, 'Fetch frameworks failed')] + }])] ) ) ) @@ -179,18 +188,18 @@ export class ModelsViewEffects { getUsersEffect = createEffect(() => this.actions$.pipe( ofType(setProjectUsers), - withLatestFrom(this.store.select(modelsSelectors.selectTableFilters)), + concatLatestFrom(() => this.store.select(modelsSelectors.selectTableFilters)), map(([action, filters]) => { const userFiltersValue = filters?.user?.['name']?.value ?? []; const resIds = action.users.map(user => user.id); const shouldGetFilteredUsersNames = !(userFiltersValue.every(id => resIds.includes(id))); - return shouldGetFilteredUsersNames ? getFilteredUsers({filteredUsers: userFiltersValue}) : new EmptyAction(); + return shouldGetFilteredUsersNames ? getFilteredUsers({filteredUsers: userFiltersValue}) : emptyAction(); }) )); getTagsEffect = createEffect(() => this.actions$.pipe( ofType(actions.getTags, actions.getTagsForAllProjects), - withLatestFrom(this.store.select(selectRouterParams).pipe(map(params => params?.projectId))), + concatLatestFrom(() => this.store.select(selectRouterParams).pipe(map(params => params?.projectId ?? '*'))), switchMap(([action, projectId]) => this.projectsApi.projectsGetModelTags({ projects: (projectId === '*' || action.type === actions.getTagsForAllProjects.type) ? [] : [projectId] }).pipe( @@ -201,14 +210,17 @@ export class ModelsViewEffects { catchError(error => [ requestFailed(error), deactivateLoader('Fetch Models'), - addMessage('warn', 'Fetch tags failed', [{name: 'More info', actions: [setServerError(error, null, 'Fetch tags failed')]}])] + addMessage('warn', 'Fetch tags failed', [{ + name: 'More info', + actions: [setServerError(error, null, 'Fetch tags failed')] + }])] ) )) )); getMetadataKeysForProjectEffect = createEffect(() => this.actions$.pipe( ofType(actions.getMetadataKeysForProject), - withLatestFrom(this.store.select(selectRouterParams).pipe(map(params => params?.projectId))), + concatLatestFrom(() => this.store.select(selectRouterParams).pipe(map(params => params?.projectId))), switchMap(([action, projectId]) => this.projectsApi.projectsGetModelMetadataKeys({ project: projectId !== '*' ? projectId : null }).pipe( @@ -219,17 +231,20 @@ export class ModelsViewEffects { catchError(error => [ requestFailed(error), deactivateLoader(action.type), - addMessage('warn', '${action.type}failed', [{name: 'More info', actions: [setServerError(error, null, '${action.type} failed')]}])] + addMessage('warn', '${action.type}failed', [{ + name: 'More info', + actions: [setServerError(error, null, '${action.type} failed')] + }])] ) )) )); getCustomMetrics = createEffect(() => this.actions$.pipe( ofType(actions.getCustomMetrics), - withLatestFrom( + concatLatestFrom(() => [ this.store.select(selectRouterParams).pipe(map(params => params?.projectId)), this.store.select(selectIsDeepMode) - ), + ]), switchMap(([action, projectId, isDeep]) => this.projectsApi.projectsGetUniqueMetricVariants({ project: projectId === '*' ? null : projectId, model_metrics: true, @@ -256,11 +271,12 @@ export class ModelsViewEffects { refreshModels = createEffect(() => this.actions$.pipe( ofType(actions.refreshModels), filter(() => !this.lockRefresh), - withLatestFrom( + concatLatestFrom(() => [ this.store.select(modelsSelectors.selectCurrentScrollId), this.store.select(modelsSelectors.selectSelectedModel), this.store.select(modelsSelectors.selectModelsList), - this.store.select(selectAppVisible)), + this.store.select(selectAppVisible) + ]), filter((values) => values[4]), switchMap(([action, scrollId, selectedModel, models]) => { this.lockRefresh = !action.autoRefresh; @@ -288,7 +304,10 @@ export class ModelsViewEffects { return [ requestFailed(error), deactivateLoader('Fetch Models'), - addMessage('warn', 'Fetch models failed', [{name: 'More info', actions: [setServerError(error, null, 'Fetch models failed')]}]) + addMessage('warn', 'Fetch models failed', [{ + name: 'More info', + actions: [setServerError(error, null, 'Fetch models failed')] + }]) ]; }) ); @@ -298,10 +317,10 @@ export class ModelsViewEffects { getModels = createEffect(() => this.actions$.pipe( ofType(actions.getNextModels), - withLatestFrom( + concatLatestFrom(() => [ this.store.select(modelsSelectors.selectCurrentScrollId), this.store.select(modelsSelectors.selectModelsList) - ), + ]), switchMap(([, scrollId, modelsList]) => this.fetchModels$(scrollId) .pipe( mergeMap(res => { @@ -320,7 +339,10 @@ export class ModelsViewEffects { catchError(error => [ requestFailed(error), deactivateLoader('Fetch Models'), - addMessage('warn', 'Fetch models failed', [{name: 'More info', actions: [setServerError(error, null, 'Fetch models failed')]}]) + addMessage('warn', 'Fetch models failed', [{ + name: 'More info', + actions: [setServerError(error, null, 'Fetch models failed')] + }]) ]) ) ) @@ -328,20 +350,26 @@ export class ModelsViewEffects { selectAll = createEffect(() => this.actions$.pipe( ofType(actions.selectAllModels), - withLatestFrom( + concatLatestFrom(() => [ this.store.select(selectRouterParams).pipe(map(params => params?.projectId)), this.store.select(selectIsArchivedMode), this.store.select(modelsSelectors.selectGlobalFilter), this.store.select(modelsSelectors.selectTableFilters), this.store.select(selectIsDeepMode), - ), + ]), switchMap(([action, projectId, archived, globalSearch, tableFilters, deep]) => { const pageSize = 1000; const query = this.getGetAllQuery({ - projectId, searchQuery: action.filtered && globalSearch, archived, orderFields: [{order: -1, field: MODELS_TABLE_COL_FIELDS.LAST_UPDATE}], - filters: action.filtered ? tableFilters : {}, selectedIds: [], deep, pageSize + projectId, + searchQuery: action.filtered && globalSearch, + archived, + orderFields: [{order: -1, field: MODELS_TABLE_COL_FIELDS.LAST_UPDATE}], + filters: action.filtered ? tableFilters : {}, + selectedIds: [], + deep, + pageSize }); - query.only_fields = [MODELS_TABLE_COL_FIELDS.NAME, MODELS_TABLE_COL_FIELDS.READY, 'company.id']; + query.only_fields = [MODELS_TABLE_COL_FIELDS.NAME, MODELS_TABLE_COL_FIELDS.READY, 'company.id', 'system_tags']; return this.apiModels.modelsGetAllEx(query).pipe( expand((res: ModelsGetAllExResponse) => res.models.length === pageSize ? this.apiModels.modelsGetAllEx({ ...query, @@ -354,14 +382,16 @@ export class ModelsViewEffects { catchError(error => [ requestFailed(error), deactivateLoader('Fetch Models'), - addMessage('warn', 'Fetch models failed', [{name: 'More info', actions: [setServerError(error, null, 'Fetch models failed')]}]) + addMessage('warn', 'Fetch models failed', [{ + name: 'More info', + actions: [setServerError(error, null, 'Fetch models failed')] + }]) ]) )); updateModelsUrlParams = createEffect(() => this.actions$.pipe( ofType(actions.updateUrlParams, actions.toggleColHidden, actions.addColumn, actions.removeCol), - filter(action => !(action as ReturnType)?.fromUrl), - withLatestFrom( + concatLatestFrom(() => [ this.store.select(modelsSelectors.selectTableFilters), this.store.select(modelsSelectors.selectTableSortFields), this.store.select(selectIsArchivedMode), @@ -369,7 +399,7 @@ export class ModelsViewEffects { this.store.select(modelsSelectors.selectModelsTableColsOrder), this.store.select(modelsSelectors.selectModelsHiddenTableCols), this.store.select(selectIsDeepMode) - ), + ]), mergeMap(([, filters, sortFields, isArchived, metadataCols, colsOrder, hiddenCols, isDeep]) => [setURLParams({ filters: filters as any, @@ -383,19 +413,18 @@ export class ModelsViewEffects { modelSelectionChanged = createEffect(() => this.actions$.pipe( ofType(actions.modelSelectionChanged), - tap(action => this.navigateAfterModelSelectionChanged(action.model, action.project)), + tap(action => this.navigateAfterModelSelectionChanged(action.model)), mergeMap(() => [actions.setTableMode({mode: 'info'})]) // map(action => actions.setSelectedModel({model: action.model})) )); selectNextModelEffect = createEffect(() => this.actions$.pipe( ofType(actions.selectNextModel), - withLatestFrom( + concatLatestFrom(() => [ this.store.select(selectModelsList), - this.store.select(selectRouterParams).pipe(map(params => params?.projectId)), this.store.select(selectTableMode) - ), - filter(([, , , tableMode]) => tableMode === 'info'), - tap(([, models, projectId]) => this.navigateAfterModelSelectionChanged(models[0] as SelectedModel, projectId)), + ]), + filter(([, , tableMode]) => tableMode === 'info'), + tap(([, models]) => this.navigateAfterModelSelectionChanged(models[0] as SelectedModel)), mergeMap(() => [actions.setTableMode({mode: 'info'})]) )); @@ -417,6 +446,42 @@ export class ModelsViewEffects { } } + prepareTableForDownload = createEffect(() => this.actions$.pipe( + ofType(actions.prepareTableForDownload), + // eslint-disable-next-line @typescript-eslint/naming-convention + concatLatestFrom(() => [ + this.store.select(selectRouterParams).pipe(map(params => params?.projectId)), + this.store.select(selectIsArchivedMode), + this.store.select(modelsSelectors.selectMetadataColsForProject), + this.store.select(modelsSelectors.selectGlobalFilter), + this.store.select(modelsSelectors.selectTableSortFields), + this.store.select(modelsSelectors.selectTableFilters), + this.store.select(modelsSelectors.selectSelectedModels), + this.store.select(modelsSelectors.selectShowAllSelectedIsActive), + this.store.select(modelsSelectors.selectModelsTableColsOrder), + this.store.select(modelsSelectors.selectModelsHiddenTableCols), + this.store.select(modelsSelectors.selectModelTableColumns), + this.store.select(selectIsDeepMode) + ]), + switchMap(([, projectId, isArchived, metadataCols, gb, orderFields, filters, selectedModels, showAllSelectedIsActive, colsOrder, hiddenCols, cols, deep]) => { + const selectedIds = showAllSelectedIsActive ? selectedModels.map(exp => exp.id) : []; + const columns = encodeColumns(MODELS_TABLE_COLS, hiddenCols, metadataCols, colsOrder); + this.setModelsUrlParams(filters, orderFields, isArchived, columns, deep); + return this.orgApi.organizationPrepareDownloadForGetAll({ + entity_type: 'model', only_fields: [], + field_mappings: prepareColsForDownload(cols), + ...this.getGetAllQuery({ + projectId, searchQuery: gb, archived: isArchived, + orderFields, filters, selectedIds, deep, cols, metaCols: metadataCols + }) + }).pipe( + map((res) => downloadForGetAll({prepareId: res.prepare_id})), + catchError(error => [requestFailed(error)]) + ); + } + ) + )); + getGetAllQuery({ refreshScroll = false, scrollId = null, projectId, searchQuery, archived, orderFields, filters, selectedIds = [], deep, pageSize = MODELS_PAGE_SIZE, cols = [], metaCols = [] @@ -430,7 +495,7 @@ export class ModelsViewEffects { filters: { [key: string]: FilterMetadata }; selectedIds: string[]; deep: boolean; - pageSize: number; + pageSize?: number; cols?: ISmCol[]; metaCols?: ISmCol[]; }): ModelsGetAllExRequest { @@ -476,8 +541,8 @@ export class ModelsViewEffects { fetchModels$(scrollId1: string, refreshScroll: boolean = false, pageSize = MODELS_PAGE_SIZE) { return of(scrollId1) .pipe( - withLatestFrom( - this.store.select(selectRouterParams).pipe(map(params => params?.projectId)), + concatLatestFrom(() => [ + this.store.select(modelsSelectors.selectProjectId), this.store.select(selectIsArchivedMode), this.store.select(modelsSelectors.selectMetadataColsForProject), this.store.select(modelsSelectors.selectGlobalFilter), @@ -488,25 +553,24 @@ export class ModelsViewEffects { this.store.select(modelsSelectors.selectModelsTableColsOrder), this.store.select(modelsSelectors.selectModelsHiddenTableCols), this.store.select(modelsSelectors.selectModelTableColumns), - this.store.select(selectIsDeepMode), - ), + this.store.select(selectIsDeepMode) + ]), switchMap(([scrollId, projectId, isArchived, metadataCols, gb, orderFields, filters, selectedModels, showAllSelectedIsActive, colsOrder, hiddenCols, cols, deep]) => { const selectedIds = showAllSelectedIsActive ? selectedModels.map(exp => exp.id) : []; const columns = encodeColumns(MODELS_TABLE_COLS, hiddenCols, metadataCols, colsOrder); this.setModelsUrlParams(filters, orderFields, isArchived, columns, deep); - return this.apiModels.modelsGetAllEx(this.getGetAllQuery({ - refreshScroll, scrollId, projectId, searchQuery: gb, archived: isArchived, - orderFields, filters, selectedIds, deep, pageSize, cols, metaCols: metadataCols - })); + return this.apiModels.modelsGetAllEx( + this.getGetAllQuery({ + refreshScroll, scrollId, projectId, searchQuery: gb, archived: isArchived, + orderFields, filters, selectedIds, deep, pageSize, cols, metaCols: metadataCols + })); }) ); } setSelectedModels = createEffect(() => this.actions$.pipe( ofType(actions.setSelectedModels, actions.updateModel), - withLatestFrom( - this.store.select(selectSelectedModels), - ), + concatLatestFrom(() => this.store.select(selectSelectedModels)), switchMap(([action, selectedModels]) => { const payload = action.type === actions.setSelectedModels.type ? (action as ReturnType).models : selectedModels; @@ -526,13 +590,19 @@ export class ModelsViewEffects { this.store.dispatch(setURLParams({filters, orders: sortFields, isArchived, columns, isDeep})); } - navigateAfterModelSelectionChanged(selectedModel: SelectedModel, modelProject: string) { - // wow angular really suck... - const activeChild = this.route?.firstChild?.firstChild?.firstChild?.firstChild?.firstChild?.firstChild; - const activeChildUrl = activeChild ? getRouteFullUrl(activeChild) : 'general'; - const baseUrl = 'projects/' + modelProject + '/models'; + navigateAfterModelSelectionChanged(selectedModel: SelectedModel) { + let activeChild = this.route.snapshot; + while (activeChild.firstChild) { + activeChild = activeChild.firstChild; + } + const activeChildUrl = activeChild?.url?.[0]?.path ?? 'general'; + let baseUrl = this.route; + while (baseUrl.snapshot.routeConfig?.path !== 'models') { + baseUrl = baseUrl.firstChild; + } selectedModel ? - this.router.navigate([baseUrl + '/' + selectedModel.id + '/' + activeChildUrl], {queryParamsHandling: 'preserve'}) : this.router.navigate([baseUrl], {queryParamsHandling: 'preserve'}); + this.router.navigate([selectedModel.id, activeChildUrl], {queryParamsHandling: 'preserve', relativeTo: baseUrl}) : + this.router.navigate([], {queryParamsHandling: 'preserve', relativeTo: baseUrl}); } isSelectedModelInCheckedModels(checkedModels, selectedModel) { diff --git a/src/app/webapp-common/models/models-routing.module.ts b/src/app/webapp-common/models/models-routing.module.ts index a6df067c..54ae9109 100755 --- a/src/app/webapp-common/models/models-routing.module.ts +++ b/src/app/webapp-common/models/models-routing.module.ts @@ -5,33 +5,73 @@ import {ModelInfoComponent} from './containers/model-info/model-info.component'; import {ModelInfoGeneralComponent} from './containers/model-info-general/model-info-general.component'; import {ModelInfoNetworkComponent} from './containers/model-info-network/model-info-network.component'; import {ModelInfoLabelsComponent} from './containers/model-info-labels/model-info-labels.component'; -import {LeavingBeforeSaveAlertGuard} from '../shared/guards/leaving-before-save-alert.guard'; +import {leavingBeforeSaveAlertGuard} from '../shared/guards/leaving-before-save-alert.guard'; import {ModelInfoMetadataComponent} from './containers/model-info-metadata/model-info-metadata.component'; import { ModelInfoExperimentsComponent -} from "@common/models/containers/model-info-experiments/model-info-experiments.component"; +} from '@common/models/containers/model-info-experiments/model-info-experiments.component'; import {ModelInfoScalarsComponent} from '@common/models/containers/model-info-scalars/model-info-scalars.component'; import {ModelInfoPlotsComponent} from '@common/models/containers/model-info-plots/model-info-plots.component'; +import {CrumbTypeEnum, IBreadcrumbsLink} from '@common/layout/breadcrumbs/breadcrumbs.component'; +import {selectIsModelInEditMode} from '@common/models/reducers'; export const routes: Routes = [ { - path : '', + path: '', component: ModelsComponent, + data: { + staticBreadcrumb: [[{ + name: 'Models', + type: CrumbTypeEnum.Feature + } as IBreadcrumbsLink]] + }, children : [ { - path : ':modelId', component: ModelInfoComponent, + path: ':modelId', component: ModelInfoComponent, children: [ - {path: '', redirectTo: 'general', pathMatch: 'full'}, - {path: 'general', component: ModelInfoGeneralComponent}, - {path: 'network', component: ModelInfoNetworkComponent, canDeactivate: [LeavingBeforeSaveAlertGuard]}, - {path: 'labels', component: ModelInfoLabelsComponent, canDeactivate: [LeavingBeforeSaveAlertGuard]}, - {path: 'metadata', component: ModelInfoMetadataComponent, canDeactivate: [LeavingBeforeSaveAlertGuard]}, - {path: 'experiments', component: ModelInfoExperimentsComponent, canDeactivate: [LeavingBeforeSaveAlertGuard]}, + {path: '', redirectTo: 'general', pathMatch: 'full', data: {minimized: true}}, + {path: 'general', component: ModelInfoGeneralComponent, data: {minimized: true}}, + { + path: 'network', + component: ModelInfoNetworkComponent, + canDeactivate: [leavingBeforeSaveAlertGuard(selectIsModelInEditMode)], + data: {minimized: true} + }, + {path: 'labels', + component: ModelInfoLabelsComponent, + canDeactivate: [leavingBeforeSaveAlertGuard(selectIsModelInEditMode)], + data: {minimized: true} + }, + {path: 'metadata', + component: ModelInfoMetadataComponent, + canDeactivate: [leavingBeforeSaveAlertGuard(selectIsModelInEditMode)], + data: {minimized: true} + }, + {path: 'experiments', + component: ModelInfoExperimentsComponent, + canDeactivate: [leavingBeforeSaveAlertGuard(selectIsModelInEditMode)], + data: {minimized: true} + }, {path: 'scalars', component: ModelInfoScalarsComponent, data: {minimized: true}}, - {path: 'plots', component: ModelInfoPlotsComponent}, + {path: 'plots', component: ModelInfoPlotsComponent, data: {minimized: true}}, ] }, ] + }, + { + path: ':modelId/output', + component: ModelInfoComponent, + data: {search: false}, + children: [ + {path: '', redirectTo: 'general', pathMatch: 'full'}, + {path: 'general', component: ModelInfoGeneralComponent}, + {path: 'network', component: ModelInfoNetworkComponent, canDeactivate: [leavingBeforeSaveAlertGuard(selectIsModelInEditMode)]}, + {path: 'labels', component: ModelInfoLabelsComponent, canDeactivate: [leavingBeforeSaveAlertGuard(selectIsModelInEditMode)]}, + {path: 'metadata', component: ModelInfoMetadataComponent, canDeactivate: [leavingBeforeSaveAlertGuard(selectIsModelInEditMode)]}, + {path: 'experiments', component: ModelInfoExperimentsComponent, canDeactivate: [leavingBeforeSaveAlertGuard(selectIsModelInEditMode)]}, + {path: 'scalars', component: ModelInfoScalarsComponent}, + {path: 'plots', component: ModelInfoPlotsComponent}, + ] } ]; diff --git a/src/app/webapp-common/models/models.component.html b/src/app/webapp-common/models/models.component.html index 88c68cdb..15c0545d 100755 --- a/src/app/webapp-common/models/models.component.html +++ b/src/app/webapp-common/models/models.component.html @@ -9,7 +9,9 @@ [metadataKeys]="metadataKeys$ | async" [metricVariants]="metricVariants$ | async" [tableMode]="firstModel && (tableMode$ | async)" + [hideNavigation]="modelsPage$| async" [rippleEffect]="tableModeAwareness$ | async" + [noData]="!((models$ | async)?.length > 0)" (isArchivedChanged)="archivedChanged($event)" (setAutoRefresh)="setAutoRefresh($event)" (selectedTableColsChanged)="selectedTableColsChanged($event)" @@ -19,9 +21,13 @@ (selectMetadataKeysActiveChanged)="selectMetadataKeysActiveChanged($event)" (clearTableFilters)="clearTableFiltersHandler($event)" (tableModeChanged)="modeChanged($event); tableModeUserAware()" + (downloadTableAsCSV)="downloadTableAsCSV()" + (downloadFullTableAsCSV)="downloadFullTableAsCSV()" + >
-
+
diff --git a/src/app/webapp-common/models/models.component.ts b/src/app/webapp-common/models/models.component.ts index 03f75169..ce8ffa8b 100755 --- a/src/app/webapp-common/models/models.component.ts +++ b/src/app/webapp-common/models/models.component.ts @@ -1,40 +1,52 @@ import {Component, OnDestroy, OnInit, ViewChild} from '@angular/core'; import {MatDialog} from '@angular/material/dialog'; import {ActivatedRoute, Params, Router} from '@angular/router'; -import {select, Store} from '@ngrx/store'; +import {Store} from '@ngrx/store'; import {isEqual} from 'lodash-es'; -import {combineLatest, Observable, of} from 'rxjs'; +import {combineLatest, Observable} from 'rxjs'; import {debounceTime, distinctUntilChanged, filter, map, skip, switchMap, withLatestFrom} from 'rxjs/operators'; -import {getTags, setArchive as setProjectArchive, setDeep} from '../core/actions/projects.actions'; +import { + getCompanyTags, + getTags, + setArchive as setProjectArchive, + setBreadcrumbsOptions, + setDeep, setSelectedProject +} from '../core/actions/projects.actions'; import {initSearch, resetSearch} from '../common-search/common-search.actions'; import {SearchState, selectSearchQuery} from '../common-search/common-search.reducer'; import {resetAceCaretsPositions, setAutoRefresh} from '../core/actions/layout.actions'; import { selectCompanyTags, - selectIsArchivedMode, + selectIsArchivedMode, selectIsDeepMode, selectProjectSystemTags, - selectProjectTags, selectSelectedProjectId, + selectProjectTags, + selectSelectedProjectId, selectTagsFilterByProject } from '../core/reducers/projects.reducer'; -import {selectRouterParams} from '../core/reducers/router-reducer'; import {selectAppVisible} from '../core/reducers/view.reducer'; import {BaseEntityPageComponent} from '../shared/entity-page/base-entity-page'; import {FilterMetadata} from 'primeng/api/filtermetadata'; import {ISmCol, TableSortOrderEnum} from '../shared/ui-components/data/table/table.consts'; -import {createMetadataCol, createMetricColumn, decodeColumns, decodeFilter, decodeOrder} from '../shared/utils/tableParamEncode'; +import { + createMetadataCol, + createMetricColumn, + decodeColumns, + decodeFilter, + decodeOrder +} from '../shared/utils/tableParamEncode'; import * as modelsActions from './actions/models-view.actions'; import {MODELS_TABLE_COLS} from './models.consts'; import * as modelsSelectors from './reducers'; import { selectMetadataColsForProject, selectMetadataColsOptions, - selectMetadataKeys, selectMetricVariants, + selectMetadataKeys, + selectMetricVariants, selectModelsFrameworks, selectModelsTags, selectModelTableColumns, selectTableMode } from './reducers'; -import {IModelsViewState} from './reducers/models-view.reducer'; import {SelectedModel, TableModel} from './shared/models.model'; import {SortMeta} from 'primeng/api'; import {selectIsSharedAndNotOwner} from '~/features/experiments/reducers'; @@ -51,11 +63,11 @@ import {PublishFooterItem} from '../shared/entity-page/footer-items/publish-foot import {HasReadOnlyFooterItem} from '../shared/entity-page/footer-items/has-read-only-footer-item'; import {SelectedTagsFooterItem} from '../shared/entity-page/footer-items/selected-tags-footer-item'; import {RefreshService} from '@common/core/services/refresh.service'; -import {SmSyncStateSelectorService} from '../core/services/sync-state-selector.service'; import {CompareFooterItem} from '@common/shared/entity-page/footer-items/compare-footer-item'; import {MetricVariantResult} from '~/business-logic/model/projects/metricVariantResult'; import {MetricValueType} from '@common/experiments-compare/experiments-compare.constants'; import {CustomColumnMode} from '@common/experiments/shared/common-experiments.const'; +import {ALL_PROJECTS_OBJECT} from '@common/core/effects/projects.effects'; @Component({ @@ -92,6 +104,7 @@ export class ModelsComponent extends BaseEntityPageComponent implements OnInit, public firstModel: TableModel; public metadataColsOptions$: Observable>; public metricVariants$: Observable; + public modelsPage$: Observable; protected inEditMode$: Observable; protected addTag = addTag; protected setSplitSizeAction = modelsActions.setSplitSize; @@ -104,21 +117,21 @@ export class ModelsComponent extends BaseEntityPageComponent implements OnInit, private readonly companyTags$: Observable; @ViewChild('modelsTable') private table: ModelsTableComponent; + private modelsFeature = false; constructor( - protected store: Store, + protected store: Store, protected route: ActivatedRoute, protected router: Router, protected dialog: MatDialog, protected refresh: RefreshService, - protected syncSelector: SmSyncStateSelectorService, ) { - super(store, route, router, dialog, refresh, syncSelector); + super(store, route, router, dialog, refresh); this.selectSplitSize$ = this.store.select(modelsSelectors.selectSplitSize); this.tableSortFields$ = this.store.select(modelsSelectors.selectTableSortFields); this.selectedModel$ = this.store.select(modelsSelectors.selectSelectedTableModel); this.selectedModels$ = this.store.select(modelsSelectors.selectSelectedModels); - this.selectedModelsDisableAvailable$ = this.store.select(modelsSelectors.selectedModelsDisableAvailable); + this.selectedModelsDisableAvailable$ = this.store.select(modelsSelectors.selectSelectedModelsDisableAvailable); this.tableFilters$ = this.store.select(modelsSelectors.selectTableFilters); this.searchValue$ = this.store.select(modelsSelectors.selectGlobalFilter); this.isArchived$ = this.store.select(selectIsArchivedMode); @@ -126,6 +139,7 @@ export class ModelsComponent extends BaseEntityPageComponent implements OnInit, this.isSharedAndNotOwner$ = this.store.select(selectIsSharedAndNotOwner); this.metadataColsOptions$ = this.store.select(selectMetadataColsOptions); this.metricVariants$ = this.store.select(selectMetricVariants); + this.modelsPage$ = this.store.select(modelsSelectors.selectModesPage); this.showAllSelectedIsActive$ = this.store.select(modelsSelectors.selectShowAllSelectedIsActive); this.searchQuery$ = this.store.select(selectSearchQuery); @@ -133,7 +147,7 @@ export class ModelsComponent extends BaseEntityPageComponent implements OnInit, this.activeSectionEdit$ = this.store.select(modelsSelectors.selectActiveSectionEdit); this.inEditMode$ = this.store.select(modelsSelectors.selectIsModelInEditMode); this.tableColsOrder$ = this.store.select(modelsSelectors.selectModelsTableColsOrder); - this.selectedProjectId$ = this.store.select(selectRouterParams).pipe(map(params => params?.projectId)); + this.selectedProjectId$ = this.store.select(modelsSelectors.selectProjectId); this.tags$ = this.store.select(selectModelsTags); this.metadataKeys$ = this.store.select(selectMetadataKeys); this.tagsFilterByProject$ = this.store.select(selectTagsFilterByProject); @@ -152,8 +166,17 @@ export class ModelsComponent extends BaseEntityPageComponent implements OnInit, tableCols.concat(metaCols.map(col => ({...col, meta: true}))) )); + let child = this.route.snapshot; + while (child.firstChild && !child.data.setAllProject) { + child = child.firstChild; + } + if (child.data.setAllProject) { + this.store.dispatch(getCompanyTags()); + this.modelsFeature =true; + } + this.tableCols$ = this.filteredTableCols$.pipe( - distinctUntilChanged((a, b) => isEqual(a, b)), + distinctUntilChanged(isEqual), map(cols => cols.filter(col => !col.hidden)) ); @@ -163,18 +186,18 @@ export class ModelsComponent extends BaseEntityPageComponent implements OnInit, // lil hack for hiding archived models after they been archived from models info or footer... map(([models, showArchived]) => this.filterArchivedModels(models, showArchived)), ); - this.showInfo$ = this.store.pipe( - select(selectRouterParams), - map(params => !!params?.modelId) - ); + this.showInfo$ = this.store.select(modelsSelectors.selectModelId); this.syncAppSearch(); } ngOnInit() { super.ngOnInit(); let prevQueryParams: Params; + this.sub.add(this.selectedProject$.pipe(filter(selectedProject => (this.modelsFeature && !selectedProject))).subscribe(() => + this.store.dispatch(setSelectedProject({project: ALL_PROJECTS_OBJECT})) + )); this.sub.add(combineLatest([ - this.store.select(selectRouterParams).pipe(map(params => params?.projectId)), + this.store.select(modelsSelectors.selectProjectId), this.route.queryParams, ]) .pipe( @@ -209,7 +232,7 @@ export class ModelsComponent extends BaseEntityPageComponent implements OnInit, } if (params.columns) { - const [,metrics , , metadataCols, allIds] = decodeColumns(params.columns, this.originalTableColumns); + const [, metrics, , metadataCols, allIds] = decodeColumns(params.columns, this.originalTableColumns); const hiddenCols = {}; this.originalTableColumns.forEach((tableCol) => { if (tableCol.id !== 'selected') { @@ -237,7 +260,7 @@ export class ModelsComponent extends BaseEntityPageComponent implements OnInit, tags$: this.tags$, data$: this.selectedModelsDisableAvailable$, tagsFilterByProject$: this.tagsFilterByProject$, - companyTags$: this.selectedProjectId === '*' ? of(null) : this.companyTags$, + companyTags$: this.companyTags$, projectTags$: this.store.select(selectSelectedProjectId).pipe(switchMap(id => id === '*' ? this.companyTags$ : this.projectTags$ )), @@ -282,9 +305,7 @@ export class ModelsComponent extends BaseEntityPageComponent implements OnInit, selectModelFromUrl() { this.sub.add(combineLatest([ - this.store.select(selectRouterParams).pipe( - map(params => params?.modelId) - ), + this.store.select(modelsSelectors.selectModelId), this.models$ ]) .pipe( @@ -375,6 +396,7 @@ export class ModelsComponent extends BaseEntityPageComponent implements OnInit, columnsReordered(cols: string[], fromUrl = false) { this.store.dispatch(modelsActions.setColsOrderForProject({cols, project: this.projectId, fromUrl})); + this.store.dispatch(modelsActions.updateUrlParams()); } selectedTableColsChanged(col) { @@ -461,7 +483,7 @@ export class ModelsComponent extends BaseEntityPageComponent implements OnInit, this.store.dispatch(modelsActions.tableFilterChanged({filters, projectId: this.projectId})); } - selectMetadataKeysActiveChanged(mode: {customMode: CustomColumnMode}) { + selectMetadataKeysActiveChanged(mode: { customMode: CustomColumnMode }) { if (mode.customMode === CustomColumnMode.Metadata) { this.store.dispatch(modelsActions.getMetadataKeysForProject()); } else if (mode.customMode === CustomColumnMode.Metrics) { @@ -515,4 +537,54 @@ export class ModelsComponent extends BaseEntityPageComponent implements OnInit, this.store.dispatch(modelsActions.removeMetricCol({id: variantCol.id, projectId: variantCol.projectId})); } } + + downloadTableAsCSV() { + this.table.table.downloadTableAsCSV(`ClearML ${this.selectedProject.id === '*'? 'All': this.selectedProject?.basename?.substring(0, 60)} Models`); + } + + downloadFullTableAsCSV() { + this.store.dispatch(modelsActions.prepareTableForDownload({entityType: 'model'})); + } + + setupBreadcrumbsOptions() { + this.sub.add(this.selectedProject$.pipe( + withLatestFrom(this.store.select(selectIsDeepMode)), + ).subscribe(([selectedProject, isDeep]) => { + if (this.modelsFeature) { + this.store.dispatch(setBreadcrumbsOptions({ + breadcrumbOptions: { + showProjects: false, + featureBreadcrumb: {name: 'Models'}, + } + })); + } else { + this.store.dispatch(setBreadcrumbsOptions({ + breadcrumbOptions: { + showProjects: !!selectedProject && !this.modelsFeature, + featureBreadcrumb: { + name: 'PROJECTS', + url: 'projects' + }, + ...(isDeep && selectedProject?.id !== '*' && { + subFeatureBreadcrumb: { + name: 'All Models' + } + }), + projectsOptions: { + basePath: 'projects', + filterBaseNameWith: null, + compareModule: null, + showSelectedProject: selectedProject?.id !== '*', + ...(selectedProject && { + selectedProjectBreadcrumb: { + name: selectedProject?.id === '*' ? 'All Models' : selectedProject?.basename, + url: `projects/${selectedProject?.id}/projects` + } + }) + } + } + })); + } + })); + } } diff --git a/src/app/webapp-common/models/models.consts.ts b/src/app/webapp-common/models/models.consts.ts index 40589616..0d5f043a 100755 --- a/src/app/webapp-common/models/models.consts.ts +++ b/src/app/webapp-common/models/models.consts.ts @@ -1,4 +1,4 @@ -import {ColHeaderTypeEnum, ISmCol} from '../shared/ui-components/data/table/table.consts'; +import {ColHeaderFilterTypeEnum, ColHeaderTypeEnum, ISmCol} from '../shared/ui-components/data/table/table.consts'; import {MODELS_TABLE_COL_FIELDS} from './shared/models.const'; import {rootProjectsPageSize} from '@common/constants'; @@ -60,6 +60,7 @@ export const MODELS_TABLE_COLS: ISmCol[] = [ headerType : ColHeaderTypeEnum.sortFilter, sortable : true, filterable : true, + searchableFilter: true, header : 'FRAMEWORK', style : {width: '100px'}, showInCardFilters: true @@ -74,6 +75,7 @@ export const MODELS_TABLE_COLS: ISmCol[] = [ { id : MODELS_TABLE_COL_FIELDS.TAGS, headerType : ColHeaderTypeEnum.sortFilter, + getter: ['tags', 'system_tags'], filterable : true, sortable : false, searchableFilter: true, @@ -123,11 +125,14 @@ export const MODELS_TABLE_COLS: ISmCol[] = [ }, { id : MODELS_TABLE_COL_FIELDS.LAST_UPDATE, - headerType: ColHeaderTypeEnum.sortFilter, + headerType : ColHeaderTypeEnum.sortFilter, sortable : true, - header : 'UPDATED', - label : 'Updated', - style : {width: '120px'} + filterType : ColHeaderFilterTypeEnum.durationDate, + filterable: true, + searchableFilter: false, + header : 'UPDATED', + label : 'Updated', + style : {width: '150px'}, }, { id : MODELS_TABLE_COL_FIELDS.COMMENT, diff --git a/src/app/webapp-common/models/models.module.ts b/src/app/webapp-common/models/models.module.ts index 8f4bce69..d6de676d 100755 --- a/src/app/webapp-common/models/models.module.ts +++ b/src/app/webapp-common/models/models.module.ts @@ -43,6 +43,7 @@ import {UserPreferences} from '@common/user-preferences'; import {SharedPipesModule} from '@common/shared/pipes/shared-pipes.module'; import {ExperimentCompareSharedModule} from '@common/experiments-compare/shared/experiment-compare-shared.module'; import {RouterTabNavBarComponent} from '@common/shared/components/router-tab-nav-bar/router-tab-nav-bar.component'; +import {ExperimentsModule} from '~/features/experiments/experiments.module'; export const modelSyncedKeys = [ 'view.projectColumnsSortOrder', @@ -106,22 +107,26 @@ const getInitState = (userPreferences: UserPreferences) => ({ ExperimentGraphsModule, ExperimentCompareSharedModule, RouterTabNavBarComponent, + ExperimentsModule, ], - providers : [ - SmFormBuilderService, DatePipe, - {provide: MODELS_CONFIG_TOKEN, useFactory: getInitState, deps: [UserPreferences]}, - ], - declarations : [ModelInfoComponent, ModelsComponent, ModelInfoHeaderComponent, - ModelViewNetworkComponent, ModelInfoNetworkComponent, - ModelInfoLabelsComponent, ModelInfoLabelsViewComponent, ModelInfoGeneralComponent, - ModelGeneralInfoComponent, ModelHeaderComponent, - ModelCustomColsMenuComponent, - ModelInfoMetadataComponent, - ModelInfoExperimentsComponent, - ModelExperimentsTableComponent, - ModelInfoPlotsComponent, - ModelInfoScalarsComponent - ] + providers: [ + SmFormBuilderService, DatePipe, + {provide: MODELS_CONFIG_TOKEN, useFactory: getInitState, deps: [UserPreferences]}, + ], + exports: [ + ModelsComponent + ], + declarations: [ModelInfoComponent, ModelsComponent, ModelInfoHeaderComponent, + ModelViewNetworkComponent, ModelInfoNetworkComponent, + ModelInfoLabelsComponent, ModelInfoLabelsViewComponent, ModelInfoGeneralComponent, + ModelGeneralInfoComponent, ModelHeaderComponent, + ModelCustomColsMenuComponent, + ModelInfoMetadataComponent, + ModelInfoExperimentsComponent, + ModelExperimentsTableComponent, + ModelInfoPlotsComponent, + ModelInfoScalarsComponent + ] }) export class ModelsModule { } diff --git a/src/app/webapp-common/models/reducers/index.ts b/src/app/webapp-common/models/reducers/index.ts index 911ca269..f2513afa 100755 --- a/src/app/webapp-common/models/reducers/index.ts +++ b/src/app/webapp-common/models/reducers/index.ts @@ -5,9 +5,9 @@ import {SelectedModel} from '../shared/models.model'; import {CountAvailableAndIsDisableSelectedFiltered} from '@common/shared/entity-page/items.utils'; import {MODELS_TABLE_COLS} from '@common/models/models.consts'; import {ISmCol} from '@common/shared/ui-components/data/table/table.consts'; -import {selectRouterParams} from '@common/core/reducers/router-reducer'; -import {FilterMetadata} from 'primeng/api/filtermetadata'; +import {selectRouterConfig, selectRouterParams} from '@common/core/reducers/router-reducer'; import {MetricVariantResult} from '~/business-logic/model/projects/metricVariantResult'; +import {selectSelectedProjectId} from '@common/core/reducers/projects.reducer'; export interface ModelsState { view: IModelsViewState; @@ -22,60 +22,63 @@ export const reducers: ActionReducerMap = { const models = (state) => state.models; // view selectors. -export const modelsView = createSelector(models, (state): IModelsViewState => state ? state.view : {}); -export const selectModelsList = createSelector(modelsView, (state) => state.models); -export const selectCurrentScrollId = createSelector(modelsView, (state): string => state.scrollId); -export const selectGlobalFilter = createSelector(modelsView, (state) => state.globalFilter); -export const selectTableSortFields = createSelector(modelsView, selectRouterParams, - (state, params) => state.projectColumnsSortOrder[params?.projectId] || modelsInitialState.tableSortFields); -export const selectTableFilters = createSelector(modelsView, selectRouterParams, - (state, params) => state.projectColumnFilters?.[params?.projectId] || {}); -export const selectSelectedModels = createSelector(modelsView, (state): Array => state.selectedModels); -export const selectedModelsDisableAvailable = createSelector(modelsView, (state): Record => state.selectedModelsDisableAvailable); -export const selectSelectedTableModel = createSelector(modelsView, (state): SelectedModel => state.selectedModel); -export const selectNoMoreModels = createSelector(modelsView, (state): boolean => state.noMoreModels); -export const selectShowAllSelectedIsActive = createSelector(modelsView, (state): boolean => state.showAllSelectedIsActive); -export const selectModelsTableColsOrder = createSelector(modelsView, selectRouterParams, - (state, params): string[] => (state.colsOrder && params?.projectId) ? state.colsOrder[params?.projectId] : undefined); -export const selectModelsFrameworks = createSelector(modelsView, (state): Array => state.frameworks); -export const selectModelsTags = createSelector(modelsView, (state): Array => state.projectTags); -export const selectMetadataKeys = createSelector(modelsView, (state): Array => state.projectMetadataKeys); -export const selectMetadataColsOptions = createSelector(modelsView, (state): Record => state.metadataColsOptions); -export const selectMetadataCols = createSelector(modelsView, (state): ISmCol[] => state.metadataCols); -export const selectMetricVariants = createSelector(modelsView, (state): MetricVariantResult[] => state.metricVariants); +export const selectModelsView = createSelector(models, (state): IModelsViewState => state ? state.view : {}); +export const selectModelsList = createSelector(selectModelsView, (state) => state.models); +export const selectCurrentScrollId = createSelector(selectModelsView, (state): string => state.scrollId); +export const selectGlobalFilter = createSelector(selectModelsView, (state) => state.globalFilter); +export const selectTableSortFields = createSelector(selectModelsView, selectSelectedProjectId, + (state, projectId) => state.projectColumnsSortOrder[projectId] || modelsInitialState.tableSortFields); +export const selectTableFilters = createSelector(selectModelsView, selectSelectedProjectId, + (state, projectId) => state.projectColumnFilters?.[projectId] || {}); +export const selectSelectedModels = createSelector(selectModelsView, (state): Array => state.selectedModels); +export const selectSelectedModelsDisableAvailable = createSelector(selectModelsView, (state): Record => state.selectedModelsDisableAvailable); +export const selectSelectedTableModel = createSelector(selectModelsView, (state): SelectedModel => state.selectedModel); +export const selectNoMoreModels = createSelector(selectModelsView, (state): boolean => state.noMoreModels); +export const selectShowAllSelectedIsActive = createSelector(selectModelsView, (state): boolean => state.showAllSelectedIsActive); +export const selectModelsTableColsOrder = createSelector(selectModelsView, selectSelectedProjectId, + (state, projectId): string[] => (state.colsOrder && projectId) ? state.colsOrder[projectId] : undefined); +export const selectModelsFrameworks = createSelector(selectModelsView, (state): Array => state.frameworks); +export const selectModelsTags = createSelector(selectModelsView, (state): Array => state.projectTags); +export const selectMetadataKeys = createSelector(selectModelsView, (state): Array => state.projectMetadataKeys); +export const selectMetadataColsOptions = createSelector(selectModelsView, (state): Record => state.metadataColsOptions); +export const selectMetricVariants = createSelector(selectModelsView, (state): MetricVariantResult[] => state.metricVariants); +export const selectModelsTableColsWidth = createSelector(selectModelsView, selectSelectedProjectId, + (state, projectId) => state.projectColumnsWidth?.[projectId] || {}); -export const selectModelsTableColsWidth = createSelector(modelsView, selectRouterParams, - (state, params) => state.projectColumnsWidth?.[params?.projectId] || {}); - -export const selectModelsHiddenTableCols = createSelector(modelsView, selectRouterParams, - (state, params) => state.hiddenProjectTableCols?.[params?.projectId] || modelsInitialState.hiddenTableCols); -export const selectModelTableColumns = createSelector(modelsView, selectModelsHiddenTableCols, selectModelsTableColsWidth, - (state, hidden, colWidth) => +export const selectModelsHiddenTableCols = createSelector(selectModelsView, selectSelectedProjectId, + (state, projectId) => state.hiddenProjectTableCols?.[projectId] || modelsInitialState.hiddenTableCols); +export const selectModelTableColumns = createSelector(selectModelsHiddenTableCols, selectModelsTableColsWidth, + (hidden, colWidth) => MODELS_TABLE_COLS.map(col => ({ ...col, hidden: !!hidden[col.id], style: {...col.style, ...(colWidth[col.id] && {width: `${colWidth[col.id]}px`})} } as ISmCol))); -export const selectMetadataColsForProject = createSelector([modelsView, selectRouterParams, selectModelsHiddenTableCols, selectModelsTableColsWidth], (state, params, hidden, colWidth) => - state.metadataCols - .filter(metaCol => metaCol.projectId === params?.projectId) +export const selectMetadataColumns = createSelector(selectModelsView, state=> state.metadataCols); +export const selectMetadataColsForProject = createSelector(selectMetadataColumns, selectSelectedProjectId, selectModelsHiddenTableCols, selectModelsTableColsWidth, (metadataCols, projectId, hidden, colWidth) => + metadataCols + .filter(metaCol => metaCol.projectId === projectId) .map(col => ({ ...col, hidden: !!hidden[col.id], style: {...col.style, ...(colWidth[col.id] && {width: `${colWidth[col.id]}px`})} } as ISmCol))); -export const selectSplitSize = createSelector(modelsView, (state): number => state.splitSize); -export const selectTableMode = createSelector(modelsView, state => state.tableMode); +export const selectSplitSize = createSelector(selectModelsView, (state): number => state.splitSize); +export const selectTableMode = createSelector(selectModelsView, state => state.tableMode); // info selectors -export const modelInfo = createSelector(models, (state): ModelInfoState => state ? state.info : {}); -export const selectSelectedModel = createSelector(modelInfo, (state): SelectedModel => state.selectedModel); -export const selectIsModelSaving = createSelector(modelInfo, (state): boolean => state.saving); -export const selectActiveSectionEdit = createSelector(modelInfo, state => state.activeSectionEdit); -export const selectIsModelInEditMode = createSelector(modelInfo, (state): boolean => !!state.activeSectionEdit); -export const selectModelExperimentsTableFilters = createSelector(modelInfo, (state): { [columnId: string]: FilterMetadata } => state.modelExperimentsTableFilter); +export const selectModelInfo = createSelector(models, (state): ModelInfoState => state ? state.info : {}); +export const selectSelectedModel = createSelector(selectModelInfo, (state): SelectedModel => state.selectedModel); +export const selectIsModelSaving = createSelector(selectModelInfo, (state): boolean => state.saving); +export const selectActiveSectionEdit = createSelector(selectModelInfo, state => state.activeSectionEdit); +export const selectIsModelInEditMode = createSelector(selectModelInfo, (state): boolean => !!state.activeSectionEdit); +export const selectModelExperimentsTableFilters = createSelector(selectModelInfo, state => state.modelExperimentsTableFilter); -export const selectModelPlots = createSelector(modelInfo, state => state.plots); +export const selectModelPlots = createSelector(selectModelInfo, state => state.plots); + +export const selectModesPage = createSelector(selectRouterConfig, config => config[0] === 'models'); +export const selectModelId = createSelector(selectRouterParams, params => params?.modelId); +export const selectProjectId = createSelector(selectModesPage, selectSelectedProjectId, (modelsPage, projectId) => modelsPage ? '*' : projectId); diff --git a/src/app/webapp-common/models/reducers/model-info.reducer.ts b/src/app/webapp-common/models/reducers/model-info.reducer.ts index f53e4790..f99a4490 100755 --- a/src/app/webapp-common/models/reducers/model-info.reducer.ts +++ b/src/app/webapp-common/models/reducers/model-info.reducer.ts @@ -43,7 +43,7 @@ const initialState: ModelInfoState = { export const modelsInfoReducer = createReducer( initialState, - on(getModelInfo, (state) => ({...state, selectedModel: null})), + on(getModelInfo, (state, action) => ({...state, ...(state.selectedModel?.id !== action.id && {selectedModel: null})})), on(setModelInfo, (state, action) => ({...state, selectedModel: action.model as TableModel})), on(modelDetailsUpdated, (state, action) => ({ ...state, @@ -68,7 +68,7 @@ export const modelsInfoReducer = createReducer( } })), on(modelsExperimentsTableClearAllFilters, state => - ({...state, modelExperimentsTableFilter: initialState.modelExperimentsTableFilter,}) - ) + ({...state, modelExperimentsTableFilter: initialState.modelExperimentsTableFilter,}) + ) ); diff --git a/src/app/webapp-common/models/reducers/models-view.reducer.ts b/src/app/webapp-common/models/reducers/models-view.reducer.ts index de44a347..dc33e32a 100755 --- a/src/app/webapp-common/models/reducers/models-view.reducer.ts +++ b/src/app/webapp-common/models/reducers/models-view.reducer.ts @@ -163,6 +163,7 @@ export const modelsViewReducer = createReducer( on(actions.setFrameworks, (state, action): IModelsViewState => ({...state, frameworks: action.frameworks})), on(actions.setTags, (state, action): IModelsViewState => ({...state, projectTags: action.tags})), + on(actions.addProjectTag, (state, action): IModelsViewState => ({...state, projectTags: Array.from(new Set(state.projectTags.concat(action.tag))).sort()})), on(actions.setMetadataKeys, (state, action): IModelsViewState => ({...state, projectMetadataKeys: action.keys})), on(actions.setMetadataColValuesOptions, (state, action): IModelsViewState => ({...state, metadataColsOptions: {...state.metadataColsOptions, [action.col.id]: action.values}})), diff --git a/src/app/webapp-common/models/shared/models-table/models-table.component.html b/src/app/webapp-common/models/shared/models-table/models-table.component.html index b1e0d3d9..7bd0d728 100755 --- a/src/app/webapp-common/models/shared/models-table/models-table.component.html +++ b/src/app/webapp-common/models/shared/models-table/models-table.component.html @@ -111,7 +111,7 @@
{{model.name}}
- +
diff --git a/src/app/webapp-common/models/shared/models-table/models-table.component.ts b/src/app/webapp-common/models/shared/models-table/models-table.component.ts index 875fac85..9b2e415e 100755 --- a/src/app/webapp-common/models/shared/models-table/models-table.component.ts +++ b/src/app/webapp-common/models/shared/models-table/models-table.component.ts @@ -119,7 +119,9 @@ export class ModelsTableComponent extends BaseTableView { @Input() set onlyPublished(only: boolean) { const readyCol = this.tableCols.find(col => col.id === MODELS_TABLE_COL_FIELDS.READY); - readyCol.hidden = only; + if (readyCol) { + readyCol.hidden = only; + } } @Input() set projects(projects) { @@ -132,6 +134,7 @@ export class ModelsTableComponent extends BaseTableView { value: project.id, tooltip: `${project.name}` })); + this.sortOptionsList(MODELS_TABLE_COL_FIELDS.PROJECT); } @Input() set enableMultiSelect(enable: boolean) { @@ -169,7 +172,7 @@ export class ModelsTableComponent extends BaseTableView { private _selectedModel; @Input() set selectedModel(model) { - if (model && model !== this._selectedModel) { + if (model && model.id !== this._selectedModel?.id) { window.setTimeout(() => this.table?.focusSelected()); } this._selectedModel = model; @@ -259,7 +262,7 @@ export class ModelsTableComponent extends BaseTableView { } } - constructor(private changeDetector: ChangeDetectorRef, private store: Store) { + constructor(private changeDetector: ChangeDetectorRef, private store: Store) { super(); this.tagsFilterByProject$ = this.store.select(selectTagsFilterByProject); this.projectTags$ = this.store.select(selectProjectTags); @@ -350,6 +353,7 @@ export class ModelsTableComponent extends BaseTableView { } columnFilterOpened(col: ISmCol) { + this.sortOptionsList(col.id); if (col.id === MODELS_TABLE_COL_FIELDS.TAGS && !this.filtersOptions[MODELS_TABLE_COL_FIELDS.TAGS]?.length) { this.tagsMenuOpened.emit(); } else if (col.type === 'metadata') { diff --git a/src/app/webapp-common/models/shared/select-model-header/select-model-header.component.html b/src/app/webapp-common/models/shared/select-model-header/select-model-header.component.html index 24fee5f6..264c24ea 100755 --- a/src/app/webapp-common/models/shared/select-model-header/select-model-header.component.html +++ b/src/app/webapp-common/models/shared/select-model-header/select-model-header.component.html @@ -9,14 +9,6 @@ (focusout)="onSearchFocusOut()">
-
- - -
- {{project.name | shortProjectName}} + {{project.basename}}
Sample
diff --git a/src/app/webapp-common/nested-project-view/nested-card/nested-card.component.ts b/src/app/webapp-common/nested-project-view/nested-card/nested-card.component.ts index d5b082e3..a7be0829 100644 --- a/src/app/webapp-common/nested-project-view/nested-card/nested-card.component.ts +++ b/src/app/webapp-common/nested-project-view/nested-card/nested-card.component.ts @@ -4,6 +4,7 @@ import {CircleTypeEnum} from '~/shared/constants/non-common-consts'; import {Project} from '~/business-logic/model/projects/project'; import {ICONS} from '@common/constants'; import {trackById} from '@common/shared/utils/forms-track-by'; +import {ProjectTypeEnum} from '@common/nested-project-view/nested-project-view-page/nested-project-view-page.component'; @Component({ @@ -13,10 +14,7 @@ import {trackById} from '@common/shared/utils/forms-track-by'; changeDetection: ChangeDetectionStrategy.OnPush }) export class NestedCardComponent { - @Input() allTags: string[]; - @Output() addTag = new EventEmitter(); - @Output() removeTag = new EventEmitter(); - @Output() delete = new EventEmitter(); + trackById = trackById; private _project: ProjectsGetAllResponseSingle; public computeTime: string; @@ -26,6 +24,7 @@ export class NestedCardComponent { @Input() projectsNames: string[]; @Input() set project(data: Project) { + // eslint-disable-next-line @typescript-eslint/naming-convention this._project = {...data, sub_projects: data.sub_projects?.filter(subP => !subP.name.includes('.pipelines') && !subP.name.includes('.datasets') && !subP.name.includes('.reports'))}; }; @@ -35,17 +34,29 @@ export class NestedCardComponent { @Input() isRootProject; @Input() hideMenu = true; - @Output() projectCardClicked = new EventEmitter(); + @Input() allTags: string[]; + @Input() entityType: ProjectTypeEnum; + @Output() addTag = new EventEmitter(); + @Output() removeTag = new EventEmitter(); + @Output() delete = new EventEmitter(); + @Output() projectCardClicked = new EventEmitter<{ hasSubProjects: boolean; id: string; name: string }>(); @Output() projectNameChanged = new EventEmitter(); @Output() deleteProjectClicked = new EventEmitter(); @ViewChild('projectName', {static: true}) projectName; public projectClicked() { - this.projectCardClicked.emit(this.project); + const hasSubProjects = this.project.sub_projects?.filter((subProject) => (!subProject.name.slice(this.project.name.length).startsWith(`/.${this.entityType}`))).length > 0; + this.projectCardClicked.emit({hasSubProjects, id: this.project.id, name: this.project.name}); } subProjectClicked(project) { - this.projectCardClicked.emit(project); + const hasSubProjects = this.project.sub_projects?.filter(pr => pr.name.startsWith(`${project.name}/`)) + .filter((subProject) => { + const realSubProject = subProject.name.slice(project.name.length); + return !!realSubProject && !realSubProject.startsWith(`/.${this.entityType}`); + }).length > 0; + this.projectCardClicked.emit({hasSubProjects, id: project.id, name: project.name}); + } prepareProjectNameForChange(projectName: string) { diff --git a/src/app/webapp-common/nested-project-view/nested-project-view-page/nested-project-view-page.component.html b/src/app/webapp-common/nested-project-view/nested-project-view-page/nested-project-view-page.component.html index e4b133f3..2b9fbae4 100644 --- a/src/app/webapp-common/nested-project-view/nested-project-view-page/nested-project-view-page.component.html +++ b/src/app/webapp-common/nested-project-view/nested-project-view-page/nested-project-view-page.component.html @@ -1,11 +1,11 @@
+ [class.in-empty-state]="!(projectsList?.length !== 0 || searching)">
- - + -
- - + + - - - - - - - - - - - - - - - -
Pipeline controller queue
- - - - {{queue.name}} + + + + + + +
-
+
diff --git a/src/app/webapp-common/pipelines-controller/run-pipeline-controller-dialog/run-pipeline-controller-dialog.component.scss b/src/app/webapp-common/pipelines-controller/run-pipeline-controller-dialog/run-pipeline-controller-dialog.component.scss index ad6ff476..58b386ea 100644 --- a/src/app/webapp-common/pipelines-controller/run-pipeline-controller-dialog/run-pipeline-controller-dialog.component.scss +++ b/src/app/webapp-common/pipelines-controller/run-pipeline-controller-dialog/run-pipeline-controller-dialog.component.scss @@ -34,4 +34,7 @@ } } } + .search-icon { + transform: translateY(3px); + } } diff --git a/src/app/webapp-common/pipelines-controller/run-pipeline-controller-dialog/run-pipeline-controller-dialog.component.ts b/src/app/webapp-common/pipelines-controller/run-pipeline-controller-dialog/run-pipeline-controller-dialog.component.ts index 7da5d23d..2e838c89 100644 --- a/src/app/webapp-common/pipelines-controller/run-pipeline-controller-dialog/run-pipeline-controller-dialog.component.ts +++ b/src/app/webapp-common/pipelines-controller/run-pipeline-controller-dialog/run-pipeline-controller-dialog.component.ts @@ -5,7 +5,7 @@ import {combineLatest, filter, Subscription} from 'rxjs'; import {Queue} from '~/business-logic/model/queues/queue'; import {selectQueuesList} from '../../experiments/shared/components/select-queue/select-queue.reducer'; import {ConfirmDialogComponent} from '../../shared/ui-components/overlay/confirm-dialog/confirm-dialog.component'; -import {GetQueuesForEnqueue} from '@common/experiments/shared/components/select-queue/select-queue.actions'; +import {getQueuesForEnqueue} from '@common/experiments/shared/components/select-queue/select-queue.actions'; import {cloneDeep} from 'lodash-es'; import { getControllerForStartPipelineDialog, @@ -27,14 +27,14 @@ export class RunPipelineControllerDialogComponent implements OnInit, OnDestroy { public title: string; public params: any; public task: IExperimentInfo; - public chooseCustomQueue: boolean= false; + public chooseCustomQueue: boolean = false; private queuesSub: Subscription; private baseControllerSub: Subscription; private selectedQueueSub: Subscription; constructor( public dialogRef: MatDialogRef, - private store: Store, + private store: Store, @Inject(MAT_DIALOG_DATA) public data: { task } ) { if (data?.task?.hyperparams?.Args) { @@ -51,7 +51,7 @@ export class RunPipelineControllerDialogComponent implements OnInit, OnDestroy { } ngOnInit() { - this.store.dispatch(new GetQueuesForEnqueue()); + this.store.dispatch(getQueuesForEnqueue()); this.baseControllerSub = this.baseController$.pipe(filter(task => !!task)).subscribe(task => { this.task = task; this.title = this.getTitle(); @@ -80,7 +80,7 @@ export class RunPipelineControllerDialogComponent implements OnInit, OnDestroy { changeChooseCustomQueue() { this.chooseCustomQueue = !this.chooseCustomQueue; - if(!this.chooseCustomQueue){ + if (!this.chooseCustomQueue) { this.selectedQueue = this.queues.find(queue => this.task.execution?.queue?.id === queue?.id) || this.queues[0]; } } @@ -92,4 +92,9 @@ export class RunPipelineControllerDialogComponent implements OnInit, OnDestroy { } return this.task.name; } + + displayFn = (entityObj: { name: string; id: string }) => entityObj?.name ?? ''; + + isFocused = (locationRef: HTMLInputElement) => document.activeElement === locationRef + } diff --git a/src/app/webapp-common/pipelines/nested-pipeline-page/nested-pipeline-page.component.html b/src/app/webapp-common/pipelines/nested-pipeline-page/nested-pipeline-page.component.html new file mode 100644 index 00000000..35d7adcb --- /dev/null +++ b/src/app/webapp-common/pipelines/nested-pipeline-page/nested-pipeline-page.component.html @@ -0,0 +1,70 @@ + + + +
+
+
NO PIPELINES TO SHOW
+
Run your first pipeline to see it displayed here + or generate example + +
+ +
+
+ + + + + + + + + + + + + + diff --git a/src/app/webapp-common/pipelines/nested-pipeline-page/nested-pipeline-page.component.ts b/src/app/webapp-common/pipelines/nested-pipeline-page/nested-pipeline-page.component.ts new file mode 100644 index 00000000..00cca677 --- /dev/null +++ b/src/app/webapp-common/pipelines/nested-pipeline-page/nested-pipeline-page.component.ts @@ -0,0 +1,47 @@ +import {Component} from '@angular/core'; +import {PipelinesPageComponent} from '@common/pipelines/pipelines-page/pipelines-page.component'; +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 {showExamplePipelines} from '@common/projects/common-projects.actions'; +import {ProjectsSharedModule} from '~/features/projects/shared/projects-shared.module'; +import {SMSharedModule} from '@common/shared/shared.module'; +import {AsyncPipe, NgIf} from '@angular/common'; +import {setDefaultNestedModeForFeature} from '@common/core/actions/projects.actions'; + +@Component({ + selector: 'sm-nested-pipeline-page', + templateUrl: './nested-pipeline-page.component.html', + styleUrls: ['../../nested-project-view/nested-project-view-page/nested-project-view-page.component.scss'], + imports: [ + ProjectsSharedModule, + SMSharedModule, + AsyncPipe, + NgIf + ], + standalone: true +}) +export class NestedPipelinePageComponent extends PipelinesPageComponent { + entityTypeEnum = ProjectTypeEnum; + circleTypeEnum = CircleTypeEnum; + hideMenu = false; + entityType = ProjectTypeEnum.pipelines; + + projectCardClicked(data: { hasSubProjects: boolean; id: string; name: string }) { + if (data.hasSubProjects) { + this.router.navigate([data.id, 'projects'], {relativeTo: this.route.parent?.parent}); + } else { + this.router.navigate([data.id, this.entityType], {relativeTo: this.route.parent?.parent}); + } + } + + createPipelineExamples() { + this.store.dispatch(showExamplePipelines()); + } + + toggleNestedView(nested: boolean) { + this.store.dispatch(setDefaultNestedModeForFeature({feature: this.entityType, isNested: nested})); + if (!nested) { + this.router.navigateByUrl(this.entityType); + } + } +} diff --git a/src/app/webapp-common/pipelines/pipeline-card-menu/pipeline-card-menu.component.html b/src/app/webapp-common/pipelines/pipeline-card-menu/pipeline-card-menu.component.html index d8f43ac0..d3f7a690 100644 --- a/src/app/webapp-common/pipelines/pipeline-card-menu/pipeline-card-menu.component.html +++ b/src/app/webapp-common/pipelines/pipeline-card-menu/pipeline-card-menu.component.html @@ -5,7 +5,7 @@ -
@@ -13,14 +13,12 @@ Delete - - - - + + diff --git a/src/app/webapp-common/pipelines/pipeline-card/pipeline-card.component.html b/src/app/webapp-common/pipelines/pipeline-card/pipeline-card.component.html index 69a24864..a1be30a2 100644 --- a/src/app/webapp-common/pipelines/pipeline-card/pipeline-card.component.html +++ b/src/app/webapp-common/pipelines/pipeline-card/pipeline-card.component.html @@ -25,7 +25,8 @@ [editable]="true" [minLength]="2" [required]="true" - pattern="^[^/]{2,}$" + pattern="^[^\/]{2,}$" + [forbiddenString]="[]" [inlineDisabled]="true" (textChanged)="prepareProjectNameForChange($event)" > diff --git a/src/app/webapp-common/pipelines/pipeline-card/pipeline-card.component.spec.ts b/src/app/webapp-common/pipelines/pipeline-card/pipeline-card.component.spec.ts deleted file mode 100644 index f9a4895b..00000000 --- a/src/app/webapp-common/pipelines/pipeline-card/pipeline-card.component.spec.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { PipelineCardComponent } from './pipeline-card.component'; -import {SharedPipesModule} from '@common/shared/pipes/shared-pipes.module'; -import {RouterTestingModule} from '@angular/router/testing'; - -describe('PipelineCardComponent', () => { - let component: PipelineCardComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ PipelineCardComponent ], - imports: [ - SharedPipesModule, - RouterTestingModule, - ] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(PipelineCardComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - // expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/webapp-common/pipelines/pipelines-page/pipelines-page.component.ts b/src/app/webapp-common/pipelines/pipelines-page/pipelines-page.component.ts index 9d98400a..694341a5 100644 --- a/src/app/webapp-common/pipelines/pipelines-page/pipelines-page.component.ts +++ b/src/app/webapp-common/pipelines/pipelines-page/pipelines-page.component.ts @@ -5,12 +5,13 @@ import {isExample} from '@common/shared/utils/shared-utils'; import {trackById} from '@common/shared/utils/forms-track-by'; import { addProjectTags, - getProjectsTags, + getProjectsTags, setBreadcrumbsOptions, setDefaultNestedModeForFeature, setSelectedProjectId, setTags } from '@common/core/actions/projects.actions'; import { + selectDefaultNestedModeForFeature, selectMainPageTagsFilter, selectMainPageTagsFilterMatchMode, selectProjectTags @@ -26,8 +27,11 @@ import { import {ProjectsGetAllResponseSingle} from '~/business-logic/model/projects/projectsGetAllResponseSingle'; import {selectShowPipelineExamples} from '@common/projects/common-projects.reducer'; import {EntityTypeEnum} from '~/shared/constants/non-common-consts'; -import {PipelinesEmptyStateComponent} from '@common/pipelines/pipelines-page/pipelines-empty-state/pipelines-empty-state.component'; -import {debounceTime} from 'rxjs/operators'; +import { + PipelinesEmptyStateComponent +} from '@common/pipelines/pipelines-page/pipelines-empty-state/pipelines-empty-state.component'; +import {debounceTime, withLatestFrom} from 'rxjs/operators'; +import {ProjectTypeEnum} from '@common/nested-project-view/nested-project-view-page/nested-project-view-page.component'; @Component({ selector: 'sm-pipelines-page', @@ -61,6 +65,7 @@ if __name__ == '__main__': pipeline_logic(do_stuff=True)`; pageSize = pageSize; + protected entityType = ProjectTypeEnum.pipelines; isExample = isExample; trackById = trackById; public projectsTags$: Observable; @@ -71,9 +76,8 @@ if __name__ == '__main__': ngOnInit() { super.ngOnInit(); + this.store.dispatch(getProjectsTags({entity: this.getName()})); this.showExamples$ = this.store.select(selectShowPipelineExamples); - // Todo: delayed because of nested views, remove timeout after implementing nested view template - window.setTimeout(() => this.store.dispatch(getProjectsTags({entity: this.getName()}))); this.projectsTags$ = this.store.select(selectProjectTags); this.mainPageFilterSub = combineLatest([ this.store.select(selectMainPageTagsFilter), @@ -136,6 +140,15 @@ if __name__ == '__main__': this.store.dispatch(showExamplePipelines()); } + shouldReRoute(selectedProject, config) { + const relevantSubProjects = selectedProject?.sub_projects?.filter(proj => proj.name.includes('.pipelines')); + return config[2] === 'projects' && selectedProject.id !== '*' && (relevantSubProjects?.every(subProject => subProject.name.startsWith(selectedProject.name + '/.'))); + }; + + noProjectsReRoute() { + return this.router.navigate(['..', 'pipelines'], {relativeTo: this.route}); + } + toggleNestedView(nested: boolean) { this.store.dispatch(setDefaultNestedModeForFeature({feature: 'pipelines', isNested: nested})); @@ -145,4 +158,27 @@ if __name__ == '__main__': this.router.navigateByUrl('pipelines'); } } + + setupBreadcrumbsOptions() { + this.subs.add(this.selectedProject$.pipe( + withLatestFrom(this.store.select(selectDefaultNestedModeForFeature)) + ).subscribe(([selectedProject, defaultNestedModeForFeature]) => { + this.store.dispatch(setBreadcrumbsOptions({ + breadcrumbOptions: { + showProjects: !!selectedProject, + featureBreadcrumb: { + name: 'PIPELINES', + url: defaultNestedModeForFeature['pipelines'] ? 'pipelines/*/projects' : 'pipelines' + }, + projectsOptions: { + basePath: 'pipelines', + filterBaseNameWith: ['.pipelines'], + compareModule: null, + showSelectedProject: selectedProject?.id !== '*', + ...(selectedProject && selectedProject?.id !== '*' && {selectedProjectBreadcrumb: {name: selectedProject?.basename}}) + } + } + })); + })); + } } diff --git a/src/app/webapp-common/pipelines/pipelines.route.ts b/src/app/webapp-common/pipelines/pipelines.route.ts index 65bc8c99..a67014c9 100644 --- a/src/app/webapp-common/pipelines/pipelines.route.ts +++ b/src/app/webapp-common/pipelines/pipelines.route.ts @@ -1,13 +1,12 @@ import {FeaturesEnum} from '~/business-logic/model/users/featuresEnum'; import {NgModule} from '@angular/core'; import {RouterModule, Routes} from '@angular/router'; -import {PipelinesPageComponent} from "@common/pipelines/pipelines-page/pipelines-page.component"; -import {CrumbTypeEnum} from "@common/layout/breadcrumbs/breadcrumbs.component"; +import {PipelinesPageComponent} from '@common/pipelines/pipelines-page/pipelines-page.component'; +import {CrumbTypeEnum} from '@common/layout/breadcrumbs/breadcrumbs.component'; const routes = [{ path: '', component: PipelinesPageComponent, - // canActivate: [RolePermissionsGuard], data: {search: true, features: FeaturesEnum.Pipelines, staticBreadcrumb:[[{ name: 'PIPELINES', type: CrumbTypeEnum.Feature diff --git a/src/app/webapp-common/project-info/conteiners/project-stats/project-stats.component.html b/src/app/webapp-common/project-info/conteiners/project-stats/project-stats.component.html index 7964e1c8..be42667c 100644 --- a/src/app/webapp-common/project-info/conteiners/project-stats/project-stats.component.html +++ b/src/app/webapp-common/project-info/conteiners/project-stats/project-stats.component.html @@ -1,11 +1,12 @@ -
+
+
- {{state.label}} + {{state.label}}
diff --git a/src/app/webapp-common/project-info/conteiners/project-stats/project-stats.component.scss b/src/app/webapp-common/project-info/conteiners/project-stats/project-stats.component.scss index fed2f273..2220d5a3 100644 --- a/src/app/webapp-common/project-info/conteiners/project-stats/project-stats.component.scss +++ b/src/app/webapp-common/project-info/conteiners/project-stats/project-stats.component.scss @@ -7,16 +7,16 @@ background-color: $blue-900; .header { - height: 0; - - .trigger { - position: absolute; - left: 15%; - } + display: flex; + justify-content: space-between; + align-items: center; + position: absolute; + top: 7px; + width: calc(50% + 33px); + left: calc(50% - 90px); + z-index: 1; .btn { - position: relative; - top: -40px; height: 32px; width: 180px; overflow: hidden; @@ -26,11 +26,11 @@ } .line-chart-container { - height: calc(100% - 36px); + height: calc(100% - 64px); } .legend { - margin-top: -4px; + margin-top: 12px; text-align: center; color: $blue-100; diff --git a/src/app/webapp-common/project-info/conteiners/project-stats/project-stats.component.ts b/src/app/webapp-common/project-info/conteiners/project-stats/project-stats.component.ts index 681b6722..8aa5b4cf 100644 --- a/src/app/webapp-common/project-info/conteiners/project-stats/project-stats.component.ts +++ b/src/app/webapp-common/project-info/conteiners/project-stats/project-stats.component.ts @@ -102,8 +102,8 @@ export class ProjectStatsComponent implements OnInit, OnDestroy { .map(val => ({ ...val, title: val.name, - nameExt: `Created By ${val.user}, Finished ${new Date(val.x).toLocaleString()}`, - name: val.id + name: `Created By ${val.user}, Finished ${new Date(val.x).toLocaleString()}`, + value: val.y })); this.cdr.detectChanges(); this.plot?.onResize(); diff --git a/src/app/webapp-common/project-info/project-info-routing.module.ts b/src/app/webapp-common/project-info/project-info-routing.module.ts index 2dbc1287..12f189a2 100755 --- a/src/app/webapp-common/project-info/project-info-routing.module.ts +++ b/src/app/webapp-common/project-info/project-info-routing.module.ts @@ -1,14 +1,14 @@ import {RouterModule, Routes} from '@angular/router'; import {NgModule} from '@angular/core'; import {ProjectInfoComponent} from './project-info.component'; -import {GeneralLeavingBeforeSaveAlertGuard} from '../shared/guards/general-leaving-before-save-alert.guard'; +import {generalLeavingBeforeSaveAlertGuard} from '../shared/guards/general-leaving-before-save-alert.guard'; export const routes: Routes = [ { path : '', component: ProjectInfoComponent, data: {search: false, archiveLabel: ''}, - canDeactivate: [GeneralLeavingBeforeSaveAlertGuard], + canDeactivate: [generalLeavingBeforeSaveAlertGuard], } ]; diff --git a/src/app/webapp-common/project-info/project-info.component.ts b/src/app/webapp-common/project-info/project-info.component.ts index 8516a552..c42ffaf5 100644 --- a/src/app/webapp-common/project-info/project-info.component.ts +++ b/src/app/webapp-common/project-info/project-info.component.ts @@ -2,14 +2,14 @@ import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit import {Store} from '@ngrx/store'; import {Observable} from 'rxjs/internal/Observable'; import {Subscription} from 'rxjs'; -import {filter, take} from 'rxjs/operators'; +import {filter, take, withLatestFrom} from 'rxjs/operators'; import 'ngx-markdown-editor'; import { - RootProjects, + selectIsDeepMode, selectSelectedMetricVariantForCurrProject, selectSelectedProject } from '../core/reducers/projects.reducer'; -import {updateProject} from '../core/actions/projects.actions'; +import {setBreadcrumbsOptions, updateProject} from '../core/actions/projects.actions'; import {Project} from '~/business-logic/model/projects/project'; import {isExample} from '../shared/utils/shared-utils'; @@ -21,7 +21,7 @@ import {isExample} from '../shared/utils/shared-utils'; changeDetection: ChangeDetectionStrategy.OnPush }) export class ProjectInfoComponent implements OnInit, OnDestroy { - private selecteProject$: Observable; + private selectedProject$: Observable; private infoSubs: Subscription; public info: string; public editMode: boolean; @@ -34,13 +34,13 @@ export class ProjectInfoComponent implements OnInit, OnDestroy { private selectedVariantSub: Subscription; - constructor(private store: Store, private cdr: ChangeDetectorRef) { - this.selecteProject$ = this.store.select(selectSelectedProject); + constructor(private store: Store, private cdr: ChangeDetectorRef) { + this.selectedProject$ = this.store.select(selectSelectedProject); this.loading = true; } ngOnInit(): void { - this.infoSubs = this.selecteProject$ + this.infoSubs = this.selectedProject$ .pipe( filter(project => !!project?.id) ).subscribe(project => { @@ -56,6 +56,7 @@ export class ProjectInfoComponent implements OnInit, OnDestroy { this.setMetricsPanel(true); this.cdr.detectChanges(); }); + this.setupBreadcrumbsOptions(); } ngOnDestroy() { @@ -70,4 +71,37 @@ export class ProjectInfoComponent implements OnInit, OnDestroy { saveInfo(info: string) { this.store.dispatch(updateProject({id: this.projectId, changes: {description: info}})); } + + setupBreadcrumbsOptions() { + this.infoSubs.add(this.selectedProject$.pipe( + withLatestFrom(this.store.select(selectIsDeepMode)) + ).subscribe(([selectedProject, isDeep]) => { + this.store.dispatch(setBreadcrumbsOptions({ + breadcrumbOptions: { + showProjects: !!selectedProject, + featureBreadcrumb: { + name: 'PROJECTS', + url: 'projects' + }, + ...(isDeep && selectedProject?.id !== '*' && { + subFeatureBreadcrumb: { + name: 'All Experiments' + } + }), + projectsOptions: { + basePath: 'projects', + filterBaseNameWith: null, + compareModule: null, + showSelectedProject: selectedProject?.id !== '*', + ...(selectedProject && { + selectedProjectBreadcrumb: { + name: selectedProject?.id === '*' ? 'All Experiments' : selectedProject?.basename, + url: `projects/${selectedProject?.id}/projects` + } + }) + } + } + })); + })); + } } diff --git a/src/app/webapp-common/projects/common-projects.actions.ts b/src/app/webapp-common/projects/common-projects.actions.ts index ace2c8be..291ac22f 100755 --- a/src/app/webapp-common/projects/common-projects.actions.ts +++ b/src/app/webapp-common/projects/common-projects.actions.ts @@ -32,10 +32,6 @@ export const checkProjectForDeletion = createAction( PROJECTS_PREFIX + 'CHECK_PROJECT_FOR_DELETION', props<{project: Project}>() ); -export const setProjectReadyForDeletion= createAction( - PROJECTS_PREFIX + 'SET_PROJECT_READY_FOR_DELETION', - props<{readyForDeletion}>() -); export const resetReadyToDelete = createAction(PROJECTS_PREFIX + 'RESET_READY_TO_DELETE'); export const setNoMoreProjects = createAction( diff --git a/src/app/webapp-common/projects/common-projects.effects.ts b/src/app/webapp-common/projects/common-projects.effects.ts index cca0faed..746035fc 100755 --- a/src/app/webapp-common/projects/common-projects.effects.ts +++ b/src/app/webapp-common/projects/common-projects.effects.ts @@ -1,5 +1,5 @@ import {Injectable} from '@angular/core'; -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 {requestFailed} from '../core/actions/http.actions'; import {activeLoader, deactivateLoader, setServerError} from '../core/actions/layout.actions'; @@ -25,7 +25,7 @@ import {Store} from '@ngrx/store'; import {TABLE_SORT_ORDER} from '../shared/ui-components/data/table/table.consts'; import {ProjectsGetAllExRequest} from '~/business-logic/model/projects/projectsGetAllExRequest'; import {isExample} from '../shared/utils/shared-utils'; -import {catchError, debounceTime, filter, map, mergeMap, switchMap, withLatestFrom} from 'rxjs/operators'; +import {catchError, debounceTime, filter, map, mergeMap, switchMap} from 'rxjs/operators'; import {pageSize} from './common-projects.consts'; import {selectRouterParams} from '../core/reducers/router-reducer'; import {selectCurrentUser, selectShowOnlyUserWork} from '../core/reducers/users-reducer'; @@ -60,7 +60,7 @@ export class CommonProjectsEffects { constructor( private actions: Actions, public projectsApi: ApiProjectsService, - private store: Store, private route: ActivatedRoute, + private store: Store, private route: ActivatedRoute, ) { } @@ -88,11 +88,11 @@ export class CommonProjectsEffects { getAllProjects = createEffect(() => this.actions.pipe( ofType(getAllProjectsPageProjects), - withLatestFrom( + concatLatestFrom(() => [ this.store.select(selectRouterParams).pipe(map(params => params?.projectId)), this.store.select(selectSelectedProjectId), this.store.select(selectSelectedProject), - ), + ]), switchMap(([action, routerProjectId, projectId, selectedProject]) => (selectedProject || !projectId && !routerProjectId ? of(selectedProject) : this.projectsApi.projectsGetAllEx({ id: routerProjectId || projectId, @@ -100,7 +100,7 @@ export class CommonProjectsEffects { only_fields: ['name'] }).pipe(map(res => res.projects[0]))) .pipe( - withLatestFrom( + concatLatestFrom(() => [ this.store.select(selectProjectsOrderBy), this.store.select(selectProjectsSortOrder), this.store.select(selectProjectsSearchQuery), @@ -111,14 +111,15 @@ export class CommonProjectsEffects { this.store.select(selectHideExamples), this.store.select(selectMainPageTagsFilter), this.store.select(selectMainPageTagsFilterMatchMode), - ), + ]), - switchMap(( [currentProject, + switchMap(([currentProject, orderBy, sortOrder, searchQuery, scrollId, user, showOnlyUserWork, showHidden, hideExamples, mainPageTagsFilter, mainPageTagsFilterMatchMode] ) => { const selectedProjectId = routerProjectId || projectId; // In rare cases where router not updated yet with const selectedProjectName = selectSelectedProjectId && selectedProjectId !== '*' ? currentProject?.name : null; + const selectedProjectBasename = selectedProjectName?.split('/').at(-1); // current project id const projectsView = this.route.snapshot.firstChild.routeConfig.path === 'projects'; const nested = this.route.snapshot.firstChild?.firstChild?.firstChild?.routeConfig?.path === 'projects'; @@ -135,14 +136,16 @@ export class CommonProjectsEffects { } } if (projectsView && !showHidden) { - statsFilter = {system_tags: ['-pipeline', '-dataset', '-Annotation','-report']}; + statsFilter = {system_tags: ['-pipeline', '-dataset', '-Annotation', '-report']}; } return forkJoin([ + // projects list this.projectsApi.projectsGetAllEx({ - ...(mainPageTagsFilter?.length > 0 && {tags: [(mainPageTagsFilterMatchMode ? '__$and' : '__$or'), ...addExcludeFilters(mainPageTagsFilter)]}), + ...(!nested && mainPageTagsFilter?.length > 0 && {tags: [(mainPageTagsFilterMatchMode ? '__$and' : '__$or'), ...addExcludeFilters(mainPageTagsFilter)]}), + ...(nested && mainPageTagsFilter?.length > 0 && {children_tags: [(mainPageTagsFilterMatchMode ? '__$and' : '__$or'), ...addExcludeFilters(mainPageTagsFilter)]}), stats_for_state: ProjectsGetAllExRequest.StatsForStateEnum.Active, include_stats: true, - shallow_search: true, + shallow_search: !searchQuery?.query, ...((projectsView && !searchQuery?.query) && {permission_roots_only: true}), ...((projectsView && selectedProjectId && selectedProjectId !== '*') && {parent: [selectedProjectId]}), scroll_id: scrollId || null, // null to create new scroll (undefined doesn't generate scroll) @@ -165,20 +168,21 @@ export class CommonProjectsEffects { ((nested || projectsView) && selectedProjectId && selectedProjectId !== '*' && !scrollId && !searchQuery?.query) ? this.projectsApi.projectsGetAllEx({ ...(!datasets && !pipelines && !reports && {id: selectedProjectId}), - ...(datasets && {name: `^${selectedProjectName}/\\.datasets$`, search_hidden: true, children_type:'dataset'}), + ...(datasets && {name: `^${selectedProjectName}/\\.datasets$`, search_hidden: true, children_type: 'dataset'}), ...(pipelines && {name: `^${selectedProjectName}/\\.pipelines$`, search_hidden: true, children_type: 'pipeline'}), ...(reports && {name: `^${selectedProjectName}/\\.reports$`, search_hidden: true, children_type: 'report'}), ...((!showHidden && projectsView) && {include_stats_filter: statsFilter}), ...((pipelines && !nested) && {include_stats_filter: {system_tags: ['pipeline'], type: ['controller']}}), ...(datasets && !nested ? {include_dataset_stats: true} : {include_stats: true}), - stats_with_children: false, + stats_with_children: reports || pipelines || datasets, stats_for_state: ProjectsGetAllExRequest.StatsForStateEnum.Active, ...(showHidden && {search_hidden: true}), check_own_contents: true, // in order to check if project is empty ...(showOnlyUserWork && {active_users: [user.id]}), only_fields: ['name', 'company', 'user', 'created', 'default_output_destination'], ...(!projectsView && getSelfFeatureProjectRequest(this.route.snapshot)), - }) : nested && reports && selectedProjectId === '*' && !scrollId && !searchQuery ? + }) : nested && reports && selectedProjectId === '*' && !scrollId && !searchQuery?.query ? + // nested reports virtual card this.projectsApi.projectsGetAllEx({ name: '^\\.reports$', search_hidden: true, @@ -188,43 +192,50 @@ export class CommonProjectsEffects { include_stats: true, check_own_contents: true, // in order to check if project is empty ...(showOnlyUserWork && {active_users: [user.id]}), - only_fields: ['id', 'company'] + only_fields: ['id', 'company'], + ...(mainPageTagsFilter?.length > 0 && {children_tags: [(mainPageTagsFilterMatchMode ? '__$and' : '__$or'), ...addExcludeFilters(mainPageTagsFilter)]}), /* eslint-enable @typescript-eslint/naming-convention */ }) : of(null), ]).pipe( - debounceTime(0), - map(([projectsRes, currentProjectRes]: [ProjectsGetAllExResponse, ProjectsGetAllExResponse]) => ({ - newScrollId: projectsRes.scroll_id, - projects: currentProjectRes !== null && currentProjectRes.projects.length !== 0 && - this.isNotEmptyExampleProject(currentProjectRes.projects[0]) ? - /* eslint-disable @typescript-eslint/naming-convention */ - [(currentProjectRes?.projects?.length === 0 ? - {isRoot: true, sub_projects: null, name: `[${selectedProjectName}]`} : - { - ...currentProjectRes.projects[0], - id: selectedProjectId, - isRoot: true, - // eslint-disable-next-line @typescript-eslint/naming-convention - sub_projects: null, - name: !selectedProjectName && currentProjectRes.projects[0].stats ? '[Root]' : `[${selectedProjectName}]` - }), - ...projectsRes.projects - /* eslint-enable @typescript-eslint/naming-convention */ - ] : - projectsRes.projects - } - )), - mergeMap(({newScrollId, projects}) => [ - addToProjectsList({ - projects, reset: - [setMainPageTagsFilter.type, setMainPageTagsFilterMatchMode.type].includes(action.type) - }), - deactivateLoader(action.type), - setCurrentScrollId({scrollId: newScrollId}), - setNoMoreProjects({payload: projects.length < pageSize})]), - catchError(error => [deactivateLoader(action.type), requestFailed(error)]) - ); + debounceTime(0), + map(([projectsRes, currentProjectRes]: [ProjectsGetAllExResponse, ProjectsGetAllExResponse]) => ({ + newScrollId: projectsRes.scroll_id, + projects: currentProjectRes !== null && currentProjectRes.projects.length !== 0 && + this.isNotEmptyExampleProject(currentProjectRes.projects[0]) ? + /* eslint-disable @typescript-eslint/naming-convention */ + [(currentProjectRes?.projects?.length === 0 ? + { + isRoot: true, + sub_projects: null, + name: `[${selectedProjectName}]`, + basename: `[${selectedProjectBasename}]` + } : + { + ...currentProjectRes.projects[0], + id: selectedProjectId, + isRoot: true, + // eslint-disable-next-line @typescript-eslint/naming-convention + sub_projects: null, + name: !selectedProjectName && currentProjectRes.projects[0].stats ? '[Root]' : `[${selectedProjectName}]`, + basename: !selectedProjectName && currentProjectRes.projects[0].stats ? '[Root]' : `[${selectedProjectBasename}]` + }), + ...projectsRes.projects + /* eslint-enable @typescript-eslint/naming-convention */ + ] : + projectsRes.projects + } + )), + mergeMap(({newScrollId, projects}) => [ + addToProjectsList({ + projects, reset: + [setMainPageTagsFilter.type, setMainPageTagsFilterMatchMode.type].includes(action.type) + }), + deactivateLoader(action.type), + setCurrentScrollId({scrollId: newScrollId}), + setNoMoreProjects({payload: projects.length < pageSize})]), + catchError(error => [deactivateLoader(action.type), requestFailed(error)]) + ); } ) ) @@ -233,11 +244,11 @@ export class CommonProjectsEffects { updateProjectStats = createEffect(() => this.actions.pipe( ofType(setFilterByUser), - withLatestFrom( + concatLatestFrom(() => [ this.store.select(selectSelectedProject), this.store.select(selectCurrentUser), this.store.select(selectShowHidden) - ), + ]), filter(([, project]) => !!project), switchMap(([action, project, user, showHidden]) => this.projectsApi.projectsGetAllEx({ /* eslint-disable @typescript-eslint/naming-convention */ diff --git a/src/app/webapp-common/projects/common-projects.module.ts b/src/app/webapp-common/projects/common-projects.module.ts index 5e4115f3..0291fdb3 100755 --- a/src/app/webapp-common/projects/common-projects.module.ts +++ b/src/app/webapp-common/projects/common-projects.module.ts @@ -5,7 +5,6 @@ import {SMSharedModule} from '../shared/shared.module'; import {EffectsModule} from '@ngrx/effects'; import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import {ProjectsListComponent} from './dumb/projects-list/projects-list.component'; -import {ProjectsHeaderComponent} from './dumb/projects-header/projects-header.component'; import {ProjectDialogModule} from '../shared/project-dialog/project-dialog.module'; import {ProjectsSharedModule} from '~/features/projects/shared/projects-shared.module'; import {CommonLayoutModule} from '../layout/layout.module'; @@ -27,8 +26,8 @@ import {LabeledFormFieldDirective} from '@common/shared/directive/labeled-form-f CommonLayoutModule, LabeledFormFieldDirective, ], - declarations: [CommonProjectsPageComponent, ProjectsListComponent, ProjectsHeaderComponent], - exports: [CommonProjectsPageComponent, ProjectsListComponent, ProjectsHeaderComponent] + declarations: [CommonProjectsPageComponent, ProjectsListComponent], + exports: [CommonProjectsPageComponent, ProjectsListComponent] }) export class CommonProjectsModule { } diff --git a/src/app/webapp-common/projects/common-projects.reducer.ts b/src/app/webapp-common/projects/common-projects.reducer.ts index 789b7702..f14f4e9e 100755 --- a/src/app/webapp-common/projects/common-projects.reducer.ts +++ b/src/app/webapp-common/projects/common-projects.reducer.ts @@ -2,15 +2,32 @@ import {createReducer, createSelector, on, ReducerTypes} from '@ngrx/store'; import {Project} from '~/business-logic/model/projects/project'; import {TABLE_SORT_ORDER, TableSortOrderEnum} from '../shared/ui-components/data/table/table.consts'; import { - addToProjectsList, checkProjectForDeletion, resetProjects, resetProjectsSearchQuery, resetReadyToDelete, setCurrentScrollId, setNoMoreProjects, setProjectReadyForDeletion, setProjectsOrderBy, - setProjectsSearchQuery, setTableModeAwareness, showExampleDatasets, showExamplePipelines, updateProjectSuccess + addToProjectsList, + checkProjectForDeletion, + resetProjects, + resetProjectsSearchQuery, + resetReadyToDelete, + setCurrentScrollId, + setNoMoreProjects, + setProjectsOrderBy, + setProjectsSearchQuery, + setTableModeAwareness, + showExampleDatasets, + showExamplePipelines, + updateProjectSuccess } from './common-projects.actions'; import {SearchState} from '../common-search/common-search.reducer'; -export interface CommonProjectReadyForDeletion { +export interface CommonReadyForDeletion { + experiments: { total: number; archived: number; unarchived: number }; + models: { total: number; archived: number; unarchived: number }; + reports: { total: number; archived: number; unarchived: number }; + pipelines: { total: number; unarchived: number }; + datasets: { total: number; unarchived: number }; +} + +export interface CommonProjectReadyForDeletion extends CommonReadyForDeletion { project: Project; - experiments: {archived: number; unarchived: number}; - models: {archived: number; unarchived: number}; } export interface CommonProjectsState { @@ -21,7 +38,8 @@ export interface CommonProjectsState { projectsNonFilteredList: Project[]; selectedProjectId: string; selectedProject: Project; - projectReadyForDeletion: CommonProjectReadyForDeletion; + projectReadyForDeletion: CommonReadyForDeletion; + validatedProject: Project; noMoreProjects: boolean; scrollId: string; tableModeAwareness: boolean; @@ -38,10 +56,13 @@ export const commonProjectsInitState: CommonProjectsState = { searchQuery: null, projectsNonFilteredList: [], projectReadyForDeletion: { - project: null, experiments: null, - models: null + models: null, + pipelines: null, + reports: null, + datasets: null, }, + validatedProject: null, noMoreProjects: true, scrollId: null, tableModeAwareness: true, @@ -58,11 +79,21 @@ const getCorrectSortingOrder = (currentSortOrder: TableSortOrderEnum, currentOrd }; export const commonProjectsReducers = [ - on(addToProjectsList, (state, action) => ({...state, projects: action.reset? action.projects: [...(state.projects || []), ...action.projects]})), + on(addToProjectsList, (state, action) => ({ + ...state, + projects: action.reset ? action.projects : [...(state.projects || []), ...action.projects] + })), on(setCurrentScrollId, (state, action) => ({...state, scrollId: action.scrollId})), on(setNoMoreProjects, (state, action) => ({...state, noMoreProjects: action.payload})), - on(updateProjectSuccess, (state, action) => ({...state, projects: state.projects?.map(ex => ex.id === action.id ? {...ex, ...action.changes} : ex)})), - on(resetProjects, state => ({...state, + on(updateProjectSuccess, (state, action) => ({ + ...state, projects: state.projects?.map(pr => pr.id === action.id ? { + ...pr, + ...action.changes, + ...(!!action.changes?.name && {basename: action.changes?.name.split('/').at(-1)}) + } : pr) + })), + on(resetProjects, state => ({ + ...state, scrollId: null, noMoreProjects: commonProjectsInitState.noMoreProjects, projects: commonProjectsInitState.projects @@ -91,14 +122,13 @@ export const commonProjectsReducers = [ })), on(checkProjectForDeletion, (state, action) => ({ ...state, - projectReadyForDeletion: { - ...commonProjectsInitState.projectReadyForDeletion, - project: action.project - } + validatedProject: action.project + })), + on(resetReadyToDelete, state => ({ + ...state, + projectReadyForDeletion: commonProjectsInitState.projectReadyForDeletion, + validatedProject: commonProjectsInitState.validatedProject })), - on(resetReadyToDelete, state => ({...state, projectReadyForDeletion: commonProjectsInitState.projectReadyForDeletion})), - on(setProjectReadyForDeletion, (state, action) => - ({...state, projectReadyForDeletion: {...state.projectReadyForDeletion, ...action.readyForDeletion}})), on(setTableModeAwareness, (state, action) => ({...state, tableModeAwareness: (action as ReturnType).awareness})), on(showExamplePipelines, state => ({...state, showPipelineExamples: true})), @@ -115,8 +145,12 @@ export const selectNonFilteredProjectsList = createSelector(projects, state => s export const selectProjectsOrderBy = createSelector(projects, state => state?.orderBy || ''); export const selectProjectsSortOrder = createSelector(projects, state => state?.sortOrder || TABLE_SORT_ORDER.DESC); export const selectProjectsSearchQuery = createSelector(projects, state => state?.searchQuery); -export const selectProjectReadyForDeletion = createSelector(projects, state => state?.projectReadyForDeletion); -export const selectProjectForDelete = createSelector(projects, state => [state?.projectReadyForDeletion.project]); +export const selectValidatedProject = createSelector(projects, state => state.validatedProject); +export const selectReadyForDeletion = createSelector(projects, state => + state.projectReadyForDeletion); +export const selectProjectReadyForDeletion = createSelector(selectValidatedProject, selectReadyForDeletion, + (project, projectReadyForDeletion) => ({...projectReadyForDeletion, project})); +export const selectProjectForDelete = createSelector(projects, state => [state?.validatedProject]); export const selectNoMoreProjects = createSelector(projects, state => state.noMoreProjects); export const selectProjectsScrollId = createSelector(projects, (state): string => state?.scrollId || null); export const selectTableModeAwareness = createSelector(projects, state => state?.tableModeAwareness); diff --git a/src/app/webapp-common/projects/common-projects.utils.ts b/src/app/webapp-common/projects/common-projects.utils.ts index 071b8b1d..80a7da0d 100644 --- a/src/app/webapp-common/projects/common-projects.utils.ts +++ b/src/app/webapp-common/projects/common-projects.utils.ts @@ -1,15 +1,16 @@ import {ProjectsGetAllExRequest} from '~/business-logic/model/projects/projectsGetAllExRequest'; import {ActivatedRouteSnapshot} from '@angular/router'; +import ChildrenTypeEnum = ProjectsGetAllExRequest.ChildrenTypeEnum; export const getPipelineRequest = (nested, searchQuery, selectedProjectName, selectedProjectId): ProjectsGetAllExRequest => ({ /* eslint-disable @typescript-eslint/naming-convention */ - ...(nested && { - children_type: 'pipeline', + ...(nested ? { + children_type: ChildrenTypeEnum.Pipeline, shallow_search: true, ...(selectedProjectName && {parent: [selectedProjectId]}), search_hidden: false, - }), - ...(!nested && { + } : + { search_hidden: true, shallow_search: false, name: selectedProjectName ? `^${selectedProjectName}/.pipelines/` : '/\\.pipelines/', @@ -21,7 +22,7 @@ export const getPipelineRequest = (nested, searchQuery, selectedProjectName, sel export const getReportRequest = (nested, searchQuery, selectedProjectName, selectedProjectId): ProjectsGetAllExRequest => ({ /* eslint-disable @typescript-eslint/naming-convention */ - children_type: 'report', + children_type: ChildrenTypeEnum.Report, shallow_search: nested, search_hidden: !nested && selectedProjectName, ...(!nested && selectedProjectName && {name: `^${selectedProjectName}/.reports/`}), @@ -31,12 +32,12 @@ export const getReportRequest = (nested, searchQuery, selectedProjectName, selec export const getDatasetsRequest = (nested: boolean, searchQuery: any, selectedProjectName: any, selectedProjectId: any) => ({ /* eslint-disable @typescript-eslint/naming-convention */ - ...(nested && { - children_type: 'dataset', + ...(nested ? { + children_type: ChildrenTypeEnum.Dataset, shallow_search: true, ...(selectedProjectName && {parent: [selectedProjectId]}), search_hidden: false, - }), - ...(!nested && { + } : + { search_hidden: true, shallow_search: false, name: selectedProjectName ? `^${selectedProjectName}/.datasets/` : '/\\.datasets/', diff --git a/src/app/webapp-common/projects/containers/projects-page/common-projects-page.component.ts b/src/app/webapp-common/projects/containers/projects-page/common-projects-page.component.ts index 69552ddc..1c6192ae 100755 --- a/src/app/webapp-common/projects/containers/projects-page/common-projects-page.component.ts +++ b/src/app/webapp-common/projects/containers/projects-page/common-projects-page.component.ts @@ -1,4 +1,4 @@ -import {Component, OnDestroy, OnInit} from '@angular/core'; +import {Component, inject, OnDestroy, OnInit} from '@angular/core'; import {Store} from '@ngrx/store'; import { CommonProjectReadyForDeletion, @@ -18,7 +18,7 @@ import { setProjectsSearchQuery, updateProject } from '../../common-projects.actions'; -import {ActivatedRoute, Router} from '@angular/router'; +import {ActivatedRoute, Params, Router} from '@angular/router'; import {MatDialog, MatDialogRef} from '@angular/material/dialog'; import {ProjectsGetAllResponseSingle} from '~/business-logic/model/projects/projectsGetAllResponseSingle'; import {ProjectDialogComponent} from '@common/shared/project-dialog/project-dialog.component'; @@ -35,7 +35,7 @@ import { withLatestFrom } from 'rxjs/operators'; import {ConfirmDialogComponent} from '@common/shared/ui-components/overlay/confirm-dialog/confirm-dialog.component'; -import * as coreProjectsActions from '../../../core/actions/projects.actions'; +import * as coreProjectsActions from '@common/core/actions/projects.actions'; import {setDeep, setSelectedProjectId} from '@common/core/actions/projects.actions'; import {initSearch, resetSearch} from '@common/common-search/common-search.actions'; import {SearchState, selectSearchQuery} from '@common/common-search/common-search.reducer'; @@ -45,7 +45,7 @@ import { popupEntitiesListConst, readyForDeletionFilter } from '~/features/projects/projects-page.utils'; -import {selectRouterParams} from '@common/core/reducers/router-reducer'; +import {selectRouterConfig, selectRouterParams} from '@common/core/reducers/router-reducer'; import {selectShowOnlyUserWork} from '@common/core/reducers/users-reducer'; import {Project} from '~/business-logic/model/projects/project'; import {CommonDeleteDialogComponent} from '@common/shared/entity-page/entity-delete/common-delete-dialog.component'; @@ -54,7 +54,6 @@ import {isExample} from '@common/shared/utils/shared-utils'; import {selectSelectedProject} from '@common/core/reducers/projects.reducer'; import {selectActiveWorkspaceReady} from '~/core/reducers/view.reducer'; import {EntityTypeEnum} from '~/shared/constants/non-common-consts'; -import {selectIsDatasets} from '@common/experiments-compare/reducers'; @Component({ selector: 'sm-common-projects-page', @@ -92,25 +91,24 @@ export class CommonProjectsPageComponent implements OnInit, OnDestroy { public selectedProjectId$: Observable; public allExamples: boolean; /* eslint-enable @typescript-eslint/naming-convention */ - private projectReadyForDeletion$: Observable; - private readonly searchQuery$: Observable; + protected readonly searchQuery$: Observable; private projectDialog: MatDialogRef; - private selectedProject$: Observable; + public selectedProject$: Observable; public projectId: string; - private subs = new Subscription(); + public subs = new Subscription(); private selectedProject: Project; + protected store = inject(Store); + protected router = inject(Router); + protected route = inject(ActivatedRoute); + protected dialog = inject(MatDialog); + private projectReadyForDeletion$: Observable; - constructor( - protected store: Store, - protected router: Router, - protected route: ActivatedRoute, - protected dialog: MatDialog - ) { + constructor() { this.searchQuery$ = this.store.select(selectSearchQuery); this.projectsOrderBy$ = this.store.select(selectProjectsOrderBy); this.projectsSortOrder$ = this.store.select(selectProjectsSortOrder); this.noMoreProjects$ = this.store.select(selectNoMoreProjects); - this.selectedProjectId$ = this.store.select(selectRouterParams).pipe(map(params => params?.projectId)); + this.selectedProjectId$ = this.store.select(selectRouterParams).pipe(map((params: Params) => params?.projectId)); this.selectedProject$ = this.store.select(selectSelectedProject).pipe(tap(selectedProject => this.selectedProject = selectedProject)); this.projectReadyForDeletion$ = this.store.select(selectProjectReadyForDeletion).pipe( distinctUntilChanged(), @@ -120,9 +118,9 @@ export class CommonProjectsPageComponent implements OnInit, OnDestroy { this.store.select(selectProjects), this.store.select(selectSelectedProject) ]).pipe( - debounceTime(0), - withLatestFrom(this.selectedProjectId$, this.searchQuery$, this.store.select(selectIsDatasets)), - map(([[projectsList, selectedProject], selectedProjectId, searchQuery, isDatasets]) => { + debounceTime(50), + withLatestFrom(this.selectedProjectId$, this.searchQuery$, this.store.select(selectRouterConfig)), + map(([[projectsList, selectedProject = {} as Project], selectedProjectId, searchQuery, config]) => { this.searching = searchQuery?.query.length > 0; this.allExamples = projectsList?.length > 0 && projectsList?.every(project => isExample(project)); if (projectsList === null) { @@ -131,8 +129,8 @@ export class CommonProjectsPageComponent implements OnInit, OnDestroy { if ((searchQuery?.query || searchQuery?.regExp)) { return projectsList; } else { - if (selectedProject?.sub_projects?.length === 0 && selectedProjectId === selectedProject?.id) { - this.router.navigate(['..', 'experiments'], {relativeTo: isDatasets ? this.route : this.route.parent}); + if ((selectedProject?.sub_projects?.length === 0 || this.shouldReRoute(selectedProject, config)) && selectedProjectId === selectedProject?.id) { + this.noProjectsReRoute(); return []; } const pageProjectsList = this.getExtraProjects(selectedProjectId, selectedProject); @@ -144,19 +142,34 @@ export class CommonProjectsPageComponent implements OnInit, OnDestroy { this.syncAppSearch(); } + noProjectsReRoute() { + return this.router.navigate(['..', 'experiments'], {relativeTo: this.route.parent}); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + shouldReRoute(selectedProject, config) { + return false; + }; + protected getExtraProjects(selectedProjectId, selectedProject) { return [{ - ...((selectedProjectId && selectedProject) ? selectedProject : this.ALL_EXPERIMENTS_CARD), + ...((selectedProjectId && selectedProject?.id) ? selectedProject : this.ALL_EXPERIMENTS_CARD), // eslint-disable-next-line @typescript-eslint/naming-convention - id: selectedProjectId ? selectedProjectId : '*', name: 'All Experiments', sub_projects: null, + id: selectedProjectId ? selectedProjectId : '*', + name: 'All Experiments', + basename: 'All Experiments', + // eslint-disable-next-line @typescript-eslint/naming-convention + sub_projects: null, } as ProjectsGetAllResponseSingle]; } ngOnInit() { // this.store.dispatch(new ResetSelectedProject()); + this.store.dispatch(resetProjects()); this.store.dispatch(setDeep({deep: false})); + // Todo: join the 2 subscriptions to 1 selector. this.subs.add(this.store.select(selectActiveWorkspaceReady) - .pipe(filter(ready => ready), take(1)) + .pipe(filter(ready => !!ready), take(1)) .subscribe(() => { this.store.dispatch(getAllProjectsPageProjects()); })); @@ -171,6 +184,10 @@ export class CommonProjectsPageComponent implements OnInit, OnDestroy { } )); + + + + this.subs.add(this.selectedProject$.pipe( filter(project => (!!project)), distinctUntilKeyChanged('id'), @@ -182,7 +199,7 @@ export class CommonProjectsPageComponent implements OnInit, OnDestroy { this.subs.add(this.store.select(selectShowOnlyUserWork) .pipe(skip(1)) .subscribe(() => { - this.store.dispatch(resetProjectsSearchQuery()); + this.store.dispatch(resetProjects()); this.store.dispatch(getAllProjectsPageProjects()); })); @@ -193,12 +210,34 @@ export class CommonProjectsPageComponent implements OnInit, OnDestroy { this.showConfirmDialog(readyForDeletion); } })); + this.setupBreadcrumbsOptions(); } protected getDeletePopupEntitiesList() { return 'experiment'; } + setupBreadcrumbsOptions(){ + this.subs.add(this.selectedProject$.pipe().subscribe((selectedProject) => { + this.store.dispatch(coreProjectsActions.setBreadcrumbsOptions({ + breadcrumbOptions: { + showProjects: !!selectedProject, + featureBreadcrumb: { + name: 'PROJECTS', + url: 'projects' + }, + projectsOptions: { + basePath: 'projects', + filterBaseNameWith: null, + compareModule: null, + showSelectedProject: true, + ...(selectedProject && {selectedProjectBreadcrumb: {name: selectedProject?.basename}}) + } + } + })); + })); + } + private showConfirmDialog(readyForDeletion: CommonProjectReadyForDeletion) { const name = this.getName(); const confirmDialogRef: MatDialogRef = this.dialog.open(ConfirmDialogComponent, { @@ -220,7 +259,7 @@ export class CommonProjectsPageComponent implements OnInit, OnDestroy { }); } - private showDeleteDialog(readyForDeletion: CommonProjectReadyForDeletion) { + private showDeleteDialog(readyForDeletion) { const confirmDialogRef = this.dialog.open(CommonDeleteDialogComponent, { data: { entity: readyForDeletion.project, @@ -245,6 +284,7 @@ export class CommonProjectsPageComponent implements OnInit, OnDestroy { this.subs.unsubscribe(); this.store.dispatch(resetReadyToDelete()); this.store.dispatch(resetProjectsSearchQuery()); + this.store.dispatch(resetProjects()); } stopSyncSearch() { diff --git a/src/app/webapp-common/reports/nested-reports-page/nested-reports-page.component.html b/src/app/webapp-common/reports/nested-reports-page/nested-reports-page.component.html new file mode 100644 index 00000000..5ecf4cca --- /dev/null +++ b/src/app/webapp-common/reports/nested-reports-page/nested-reports-page.component.html @@ -0,0 +1,53 @@ + + + + +
+
+
NO REPORTS TO SHOW
+
+ +
+
+ + +
+ + + + + + + diff --git a/src/app/webapp-common/reports/nested-reports-page/nested-reports-page.component.ts b/src/app/webapp-common/reports/nested-reports-page/nested-reports-page.component.ts new file mode 100644 index 00000000..8c03a2f4 --- /dev/null +++ b/src/app/webapp-common/reports/nested-reports-page/nested-reports-page.component.ts @@ -0,0 +1,81 @@ +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 {ReportsPageComponent} from '@common/reports/reports-page/reports-page.component'; +import {getAllProjectsPageProjects, resetProjects, setProjectsOrderBy} from '@common/projects/common-projects.actions'; +import {setDefaultNestedModeForFeature} from '@common/core/actions/projects.actions'; +import {selectMainPageTagsFilter, selectMainPageTagsFilterMatchMode} from "@common/core/reducers/projects.reducer"; +import {combineLatest, Subscription} from "rxjs"; +import {debounceTime} from "rxjs/operators"; + +@Component({ + selector: 'sm-nested-reports-page', + templateUrl: './nested-reports-page.component.html', + styleUrls: ['../../nested-project-view/nested-project-view-page/nested-project-view-page.component.scss'], + imports: [ + ProjectsSharedModule, + SMSharedModule, + AsyncPipe, + NgIf + ], + standalone: true +}) +export class NestedReportsPageComponent extends ReportsPageComponent { + entityTypeEnum = ProjectTypeEnum; + circleTypeEnum = CircleTypeEnum; + hideMenu = false; + entityType = ProjectTypeEnum.reports; + private mainPageFilterSub: Subscription; + projectCardClicked(data: { hasSubProjects: boolean; id: string; name: string }) { + if (data.hasSubProjects) { + this.router.navigate([data.id, 'projects'], {relativeTo: this.route.parent?.parent}); + } else { + this.router.navigate([data.id, this.entityType], {relativeTo: this.route.parent?.parent}); + } + } + + protected getReports() { + // Override getReports from reports page - we need projects (from common-projects), not reports. + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + protected getExtraProjects(selectedProjectId, selectedProject) { + return []; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + orderByChanged(sortByFieldName: string) { + this.store.dispatch(setProjectsOrderBy({orderBy: sortByFieldName})); + } + + loadMore() { + this.store.dispatch(getAllProjectsPageProjects()); + } + + toggleNestedView(nested: boolean) { + this.store.dispatch(setDefaultNestedModeForFeature({feature: this.entityType, isNested: nested})); + if (!nested) { + this.router.navigateByUrl(this.entityType); + } + } + ngOnInit() { + super.ngOnInit(); + this.mainPageFilterSub = combineLatest([ + this.store.select(selectMainPageTagsFilter), + this.store.select(selectMainPageTagsFilterMatchMode) + ]).pipe(debounceTime(0)) + .subscribe(() => { + this.store.dispatch(resetProjects()); + this.store.dispatch(getAllProjectsPageProjects()); + }); + } + + ngOnDestroy() { + super.ngOnDestroy(); + this.mainPageFilterSub.unsubscribe(); + } + +} diff --git a/src/app/webapp-common/reports/report-card-menu/report-card-menu.component.html b/src/app/webapp-common/reports/report-card-menu/report-card-menu.component.html index 3e8b541b..858d2cdb 100644 --- a/src/app/webapp-common/reports/report-card-menu/report-card-menu.component.html +++ b/src/app/webapp-common/reports/report-card-menu/report-card-menu.component.html @@ -8,7 +8,7 @@ -
@@ -25,14 +25,12 @@ - - - - + + diff --git a/src/app/webapp-common/reports/report-card/report-card.component.html b/src/app/webapp-common/reports/report-card/report-card.component.html index 8769e2b3..230835b2 100644 --- a/src/app/webapp-common/reports/report-card/report-card.component.html +++ b/src/app/webapp-common/reports/report-card/report-card.component.html @@ -12,6 +12,7 @@ [editable]="true" [minLength]="3" [required]="true" + [forbiddenString]="[]" [inlineDisabled]="true" (textChanged)="nameChanged.emit($event)" > diff --git a/src/app/webapp-common/reports/report-dialog/report-dialog.component.ts b/src/app/webapp-common/reports/report-dialog/report-dialog.component.ts index d8a7fa3e..072ffeb5 100644 --- a/src/app/webapp-common/reports/report-dialog/report-dialog.component.ts +++ b/src/app/webapp-common/reports/report-dialog/report-dialog.component.ts @@ -19,7 +19,11 @@ export class ReportDialogComponent { public readOnlyProjectsNames$: Observable; - constructor(private store: Store, private matDialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: { defaultProjectId: string}) { + constructor( + private store: Store, + private matDialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: { defaultProjectId: string} + ) { this.projects$ = this.store.select(selectTablesFilterProjectsOptions); this.readOnlyProjectsNames$ = this.store.select(selectTablesFilterProjectsOptions) .pipe(map(projects => projects?.filter(project => isReadOnly(project)).map(project=> project.name))); diff --git a/src/app/webapp-common/reports/report/report.component.html b/src/app/webapp-common/reports/report/report.component.html index 057e2ed6..28d07ab9 100644 --- a/src/app/webapp-common/reports/report/report.component.html +++ b/src/app/webapp-common/reports/report/report.component.html @@ -1,28 +1,11 @@
{{report?.name}}
-
+
- - -
+ +
diff --git a/src/app/webapp-common/reports/report/report.component.scss b/src/app/webapp-common/reports/report/report.component.scss index 7dcb50c7..054560bc 100644 --- a/src/app/webapp-common/reports/report/report.component.scss +++ b/src/app/webapp-common/reports/report/report.component.scss @@ -32,6 +32,10 @@ max-width: 400px; } } + + .trigger { + position: absolute; + } } .content { diff --git a/src/app/webapp-common/reports/report/report.component.spec.ts b/src/app/webapp-common/reports/report/report.component.spec.ts deleted file mode 100644 index e3f7ab9b..00000000 --- a/src/app/webapp-common/reports/report/report.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ReportComponent } from './report.component'; - -describe('ReportComponent', () => { - let component: ReportComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ ReportComponent ] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ReportComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/webapp-common/reports/report/report.component.ts b/src/app/webapp-common/reports/report/report.component.ts index 7ccc122b..cefacc2c 100644 --- a/src/app/webapp-common/reports/report/report.component.ts +++ b/src/app/webapp-common/reports/report/report.component.ts @@ -17,7 +17,7 @@ import { getReportsTags, moveReport, publishReport, - restoreReport, + restoreReport, setEditMode, setReport, setReportChanges, updateReport } from '@common/reports/reports.actions'; @@ -27,10 +27,10 @@ import { distinctUntilChanged, filter, map, - switchMap, + switchMap, withLatestFrom, } from 'rxjs/operators'; import {fromEvent, lastValueFrom, Observable, Subscription, take, combineLatest, merge} from 'rxjs'; -import {selectReport, selectReportsTags} from '@common/reports/reports.reducer'; +import {selectEditingReport, selectReport, selectReportsTags} from '@common/reports/reports.reducer'; import {ReportStatusEnum} from '~/business-logic/model/reports/reportStatusEnum'; import {getBaseName, isExample} from '@common/shared/utils/shared-utils'; import {MatDialog} from '@angular/material/dialog'; @@ -48,6 +48,10 @@ import {HTTP} from '~/app.constants'; import {convertToReverseProxy} from '~/shared/utils/url'; import {escapeRegex} from '@common/shared/utils/escape-regex'; import {Actions, ofType} from '@ngrx/effects'; +import {MatMenuTrigger} from '@angular/material/menu'; +import {TagsMenuComponent} from '@common/shared/ui-components/tags/tags-menu/tags-menu.component'; +import {selectDefaultNestedModeForFeature, selectSelectedProject} from '@common/core/reducers/projects.reducer'; +import {setBreadcrumbsOptions} from '@common/core/actions/projects.actions'; const replaceSlash = (part) => part .replace('\\', '/') @@ -76,18 +80,22 @@ export class ReportComponent implements OnInit, OnDestroy { public archived: boolean; public reportTags$: Observable; public smallScreen$: Observable; + public editMode$: Observable; public printStyle = { table: {border: '1px solid gray'}, th: {border: '1px solid gray'}, td: {border: '1px solid gray'}, details: {border: '1px solid #ccc', margin: '6px 0', padding: '6px'}, + // eslint-disable-next-line @typescript-eslint/naming-convention + code: {'white-space': 'pre'}, iframe: {border: 'none', width: '840px'} }; public widthExpanded: boolean = false; - public editMode: boolean; public handleUpload: (files: File[]) => Promise; public showDescription = false; public resources: { unused: boolean; url: string }[]; + public menuPosition = { x: 0, y: 0 }; + @ViewChild(MarkdownEditorComponent) mdEditor: MarkdownEditorComponent; @ViewChild('printHiddenButton') printHiddenButton: ElementRef; @@ -104,6 +112,7 @@ export class ReportComponent implements OnInit, OnDestroy { ) { this.smallScreen$ = breakpointObserver.observe('(max-width: 1200px)') .pipe(map(state => state.matches)); + this.editMode$ = this.store.select(selectEditingReport); this.reportTags$ = this.store.select(selectReportsTags); this.store.dispatch(getReportsTags()); @@ -191,6 +200,28 @@ export class ReportComponent implements OnInit, OnDestroy { return lastValueFrom(uploads$); }; } + setupBreadcrumbsOptions() { + this.sub.add(combineLatest([this.store.select(selectReport), this.store.select(selectSelectedProject)]).pipe( + withLatestFrom(this.store.select(selectDefaultNestedModeForFeature)) + ).subscribe(([[selectedReport,selectedProject], defaultNestedModeForFeature]) => { + this.store.dispatch(setBreadcrumbsOptions({ + breadcrumbOptions: { + showProjects: !!selectedReport, + featureBreadcrumb: { + name: 'REPORTS', + url: defaultNestedModeForFeature['reports'] ? 'reports/*/projects' : 'reports' + }, + projectsOptions: { + basePath: 'reports', + filterBaseNameWith: ['.reports'], + compareModule: null, + showSelectedProject: false, + selectedProjectBreadcrumb: null + } + } + })); + })); + } ngOnInit() { this.sub.add( @@ -200,12 +231,13 @@ export class ReportComponent implements OnInit, OnDestroy { this.report = report; this.calculateUnusedResources(); this.example = isExample(report); - this.archived = report?.system_tags.includes('archived'); + this.archived = report?.system_tags?.includes('archived'); this.router.navigate(['.'], {relativeTo: this.route, queryParams: {...(this.archived && {archive: this.archived})}}); this.draft = this.report.status !== ReportStatusEnum.Published; this.cdr.detectChanges(); }) ); + this.setupBreadcrumbsOptions(); } save(report: string) { @@ -213,14 +245,14 @@ export class ReportComponent implements OnInit, OnDestroy { Array.from(window.frames).forEach(frame => frame.postMessage('renderPlot', '*')); } - openTagMenu(event: MouseEvent, tagMenu, tagMenuContent) { - if (!tagMenu) { + openTagMenu(event: MouseEvent, tagMenuTrigger: MatMenuTrigger, tagsMenu: TagsMenuComponent) { + if (!tagMenuTrigger) { return; } - tagMenu.position = {x: event.clientX, y: event.clientY}; + this.menuPosition = {x: event.clientX, y: event.clientY}; window.setTimeout(() => { - tagMenu.openMenu(); - tagMenuContent.focus(); + tagMenuTrigger.openMenu(); + tagsMenu.focus(); }); } @@ -296,7 +328,7 @@ export class ReportComponent implements OnInit, OnDestroy { editModeChanged() { this.widthExpanded = this.mdEditor.isExpand; - this.editMode = this.mdEditor.editMode; + this.store.dispatch(setEditMode({editing: this.mdEditor.editMode})); setTimeout(() => Array.from(window.frames).forEach(frame => frame.postMessage('resizePlot', '*')), 500); } diff --git a/src/app/webapp-common/reports/reports-page/reports-page.component.ts b/src/app/webapp-common/reports/reports-page/reports-page.component.ts index 4e9e1b2e..759332ed 100644 --- a/src/app/webapp-common/reports/reports-page/reports-page.component.ts +++ b/src/app/webapp-common/reports/reports-page/reports-page.component.ts @@ -1,7 +1,5 @@ -import {Component, OnDestroy, OnInit} from '@angular/core'; -import {Store} from '@ngrx/store'; -import {ActivatedRoute, Params, Router} from '@angular/router'; -import {MatDialog} from '@angular/material/dialog'; +import {Component, OnDestroy, OnInit, } from '@angular/core'; +import {Params} from '@angular/router'; import {ReportDialogComponent} from '../report-dialog/report-dialog.component'; import { addReportsTags, @@ -32,22 +30,26 @@ import {Report} from '~/business-logic/model/reports/report'; import {IReport} from '../reports.consts'; import {addMessage} from '../../core/actions/layout.actions'; import {MESSAGES_SEVERITY} from '../../constants'; -import {selectMainPageTagsFilter, selectMainPageTagsFilterMatchMode} from '../../core/reducers/projects.reducer'; +import { + selectDefaultNestedModeForFeature, + selectMainPageTagsFilter, + selectMainPageTagsFilterMatchMode +} from '../../core/reducers/projects.reducer'; import {selectShowOnlyUserWork} from '../../core/reducers/users-reducer'; -import {selectSearchQuery} from '../../common-search/common-search.reducer'; import {initSearch, setSearching, setSearchQuery} from '../../common-search/common-search.actions'; -import {debounceTime, filter, map, tap} from 'rxjs/operators'; -import {setDefaultNestedModeForFeature} from '@common/core/actions/projects.actions'; +import {debounceTime, filter, map, tap, withLatestFrom} from 'rxjs/operators'; +import {setBreadcrumbsOptions, setDefaultNestedModeForFeature} from '@common/core/actions/projects.actions'; import {isEqual} from 'lodash-es'; import {ClipboardService} from 'ngx-clipboard'; import {selectRouterParams} from '@common/core/reducers/router-reducer'; +import {CommonProjectsPageComponent} from '@common/projects/containers/projects-page/common-projects-page.component'; @Component({ selector: 'sm-reports-page', templateUrl: './reports-page.component.html', styleUrls: ['./reports-page.component.scss'] }) -export class ReportsPageComponent implements OnInit, OnDestroy { +export class ReportsPageComponent extends CommonProjectsPageComponent implements OnInit, OnDestroy { public reports$: Observable; public archive$: Observable; public reportsTags$: Observable; @@ -55,30 +57,25 @@ export class ReportsPageComponent implements OnInit, OnDestroy { public noMoreReports$: Observable; public reportsOrderBy$: Observable; public reportsSortOrder$: Observable<1 | -1>; - private searchQuery$: Observable<{ query: string; regExp?: boolean; original?: string }>; public selectedProjectId$: Observable; constructor( - private store: Store, - private router: Router, - private route: ActivatedRoute, - private matDialog: MatDialog, private _clipboardService: ClipboardService ) { + super(); this.reports$ = this.store.select(selectReports); this.reportsTags$ = this.store.select(selectReportsTags); this.archive$ = this.store.select(selectArchiveView); this.noMoreReports$ = this.store.select(selectNoMoreReports); this.reportsOrderBy$ = this.store.select(selectReportsOrderBy); this.reportsSortOrder$ = this.store.select(selectReportsSortOrder); - this.searchQuery$ = this.store.select(selectSearchQuery); - this.selectedProjectId$ = this.store.select(selectRouterParams).pipe(map(params => params?.projectId)); + this.selectedProjectId$ = this.store.select(selectRouterParams).pipe(map((params: Params) => params?.projectId)); this.store.dispatch(getReportsTags()); } public openCreateReportDialog(projectId) { - this.matDialog.open(ReportDialogComponent, { + this.dialog.open(ReportDialogComponent, { data: {defaultProjectId: projectId}, panelClass: 'light-theme', }) @@ -91,11 +88,13 @@ export class ReportsPageComponent implements OnInit, OnDestroy { } ngOnDestroy(): void { + super.ngOnDestroy(); this.sub.unsubscribe(); this.store.dispatch(resetReports()); } ngOnInit(): void { + super.ngOnInit(); this.store.dispatch(initSearch({payload: 'Search for reports'})); let prevQueryParams: Params; this.sub.add(combineLatest([ @@ -106,12 +105,12 @@ export class ReportsPageComponent implements OnInit, OnDestroy { this.route.queryParams .pipe( filter(params => !isEqual(params, prevQueryParams)), - tap(params => this.store.dispatch(setArchive({archive: params?.archive}))) + tap((params: Params) => this.store.dispatch(setArchive({archive: params?.archive}))) ) ]) .pipe(debounceTime(0)) .subscribe(() => { - this.store.dispatch(getReports()); + this.getReports(); }) ); @@ -120,6 +119,9 @@ export class ReportsPageComponent implements OnInit, OnDestroy { })); } + protected getReports() { + this.store.dispatch(getReports()); + } reportSelected(report: Report) { this.store.dispatch(setSearchQuery({query: ''})); @@ -144,7 +146,7 @@ export class ReportsPageComponent implements OnInit, OnDestroy { .pipe(take(1)) .subscribe(() => this.store.dispatch(addMessage(MESSAGES_SEVERITY.SUCCESS, 'Report link copied to clipboard')) ); - this._clipboardService.copy(`${window.location.href}/${report.project.id}/${report.id}`); + this._clipboardService.copy(`${window.location.origin}/reports/${report.project.id}/${report.id}`); } addTag($event: { report: IReport; tag: string }) { @@ -188,4 +190,27 @@ export class ReportsPageComponent implements OnInit, OnDestroy { this.router.navigateByUrl('reports'); } } + + setupBreadcrumbsOptions() { + this.subs.add(this.selectedProject$.pipe( + withLatestFrom(this.store.select(selectDefaultNestedModeForFeature)) + ).subscribe(([selectedProject, defaultNestedModeForFeature]) => { + this.store.dispatch(setBreadcrumbsOptions({ + breadcrumbOptions: { + showProjects: !!selectedProject, + featureBreadcrumb: { + name: 'REPORTS', + url: defaultNestedModeForFeature['reports'] ? 'reports/*/projects' : 'reports' + }, + projectsOptions: { + basePath: 'reports', + filterBaseNameWith: ['.reports'], + compareModule: null, + showSelectedProject: selectedProject?.id !== '*', + ...(selectedProject && selectedProject?.id !== '*' && {selectedProjectBreadcrumb: {name: selectedProject?.basename}}) + } + } + })); + })); + } } diff --git a/src/app/webapp-common/reports/reports-routing.module.ts b/src/app/webapp-common/reports/reports-routing.module.ts index f3d15648..7df404ec 100644 --- a/src/app/webapp-common/reports/reports-routing.module.ts +++ b/src/app/webapp-common/reports/reports-routing.module.ts @@ -2,10 +2,10 @@ import {RouterModule, Routes} from '@angular/router'; import {NgModule} from '@angular/core'; import {ReportsPageComponent} from './reports-page/reports-page.component'; import {ReportComponent} from '@common/reports/report/report.component'; -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 {NestedReportsPageComponent} from '@common/reports/nested-reports-page/nested-reports-page.component'; +import {leavingBeforeSaveAlertGuard} from '@common/shared/guards/leaving-before-save-alert.guard'; +import {selectEditingReport} from '@common/reports/reports.reducer'; export const routes: Routes = [ { @@ -19,8 +19,11 @@ export const routes: Routes = [ path: ':projectId', children: [ {path: 'reports', component: ReportsPageComponent, data: {search: true}}, - {path: 'projects', component: NestedProjectViewPageComponent, data: {search: true}}, - {path: ':reportId', component: ReportComponent} + {path: 'projects', component: NestedReportsPageComponent, data: {search: true}}, + { + path: ':reportId', component: ReportComponent, + canDeactivate: [leavingBeforeSaveAlertGuard(selectEditingReport)], + } ] }, ]; diff --git a/src/app/webapp-common/reports/reports.actions.ts b/src/app/webapp-common/reports/reports.actions.ts index 96fae802..84bba2d9 100644 --- a/src/app/webapp-common/reports/reports.actions.ts +++ b/src/app/webapp-common/reports/reports.actions.ts @@ -3,7 +3,7 @@ import {ReportsCreateRequest} from '~/business-logic/model/reports/reportsCreate import {ReportsGetAllExResponse} from '~/business-logic/model/reports/reportsGetAllExResponse'; import {IReport} from './reports.consts'; -const REPORTS_PREFIX = 'REPORTS_'; +export const REPORTS_PREFIX = 'REPORTS_'; export const createReport = createAction( REPORTS_PREFIX + 'CREATE_REPORT', @@ -66,6 +66,12 @@ export const moveReport = createAction( props<{ report: IReport }>() ); +export const navigateToProjectAfterMove = createAction( + REPORTS_PREFIX + '[navigateToProjectAfterMove]', + props<{ projectId: string }>() +); + + export const publishReport = createAction( REPORTS_PREFIX + '[publish report]', props<{ id: string }>() @@ -110,3 +116,7 @@ export const deleteResource = createAction( props<{resource: string}>() ); +export const setEditMode = createAction( + REPORTS_PREFIX + 'Set Edit Mode', + props<{editing: boolean}>() +); diff --git a/src/app/webapp-common/reports/reports.effects.ts b/src/app/webapp-common/reports/reports.effects.ts index 8569cdea..b4bded64 100644 --- a/src/app/webapp-common/reports/reports.effects.ts +++ b/src/app/webapp-common/reports/reports.effects.ts @@ -1,8 +1,8 @@ import {Injectable} from '@angular/core'; -import {Actions, createEffect, ofType} from '@ngrx/effects'; +import {Actions, concatLatestFrom, createEffect, ofType} from '@ngrx/effects'; import {Action, Store} from '@ngrx/store'; import {ActivatedRoute, Router} from '@angular/router'; -import {catchError, filter, map, mergeMap, switchMap, tap, withLatestFrom} from 'rxjs/operators'; +import {catchError, filter, map, mergeMap, switchMap, tap} from 'rxjs/operators'; import {activeLoader, addMessage, deactivateLoader, setServerError} from '../core/actions/layout.actions'; import {requestFailed} from '../core/actions/http.actions'; import { @@ -10,10 +10,12 @@ import { archiveReport, createReport, deleteReport, + deleteResource, getReport, getReports, getReportsTags, moveReport, + navigateToProjectAfterMove, publishReport, removeReport, restoreReport, @@ -22,13 +24,13 @@ import { setReports, setReportsOrderBy, setReportsTags, - updateReport, - deleteResource + updateReport } from './reports.actions'; import {ApiReportsService} from '~/business-logic/api-services/reports.service'; import {IReport, PAGE_SIZE} from './reports.consts'; import { - selectArchiveView, selectReport, + selectArchiveView, selectNestedReports, + selectReport, selectReportsOrderBy, selectReportsQueryString, selectReportsScrollId, @@ -44,7 +46,6 @@ import { selectMainPageTagsFilterMatchMode, selectSelectedProjectId } from '../core/reducers/projects.reducer'; -import {ReportsGetTagsResponse} from '~/business-logic/model/reports/reportsGetTagsResponse'; import {TABLE_SORT_ORDER} from '../shared/ui-components/data/table/table.consts'; import {selectCurrentUser, selectShowOnlyUserWork} from '../core/reducers/users-reducer'; import {addExcludeFilters} from '../shared/utils/tableParamEncode'; @@ -60,13 +61,15 @@ import {ReportsCreateResponse} from '~/business-logic/model/reports/reportsCreat import {ConfirmDialogComponent} from '@common/shared/ui-components/overlay/confirm-dialog/confirm-dialog.component'; import {HttpClient} from '@angular/common/http'; import {selectRouterParams} from '@common/core/reducers/router-reducer'; +import {setMainPageTagsFilter} from '@common/core/actions/projects.actions'; +import {cleanTag} from '@common/shared/utils/helpers.util'; @Injectable() export class ReportsEffects { constructor( private actions: Actions, - private store: Store, + private store: Store, private route: ActivatedRoute, private router: Router, private reportsApiService: ApiReportsService, @@ -97,7 +100,7 @@ export class ReportsEffects { getReports = createEffect(() => this.actions.pipe( ofType(getReports, setReportsOrderBy), - withLatestFrom( + concatLatestFrom(() => [ this.store.select(selectReportsScrollId), this.store.select(selectArchiveView), this.store.select(selectReportsOrderBy), @@ -109,7 +112,7 @@ export class ReportsEffects { this.store.select(selectReportsQueryString), this.store.select(selectHideExamples), this.store.select(selectRouterParams).pipe(map(params => params?.projectId)), - ), + ]), switchMap(([action, scroll, archive, orderBy, sortOrder, showOnlyUserWork, mainPageTagsFilter, mainPageTagsFilterMatchMode, user, searchQuery, hideExamples, projectId]) => this.reportsApiService.reportsGetAllEx({ /* eslint-disable @typescript-eslint/naming-convention */ @@ -157,8 +160,16 @@ export class ReportsEffects { getReportsTags = createEffect(() => this.actions.pipe( ofType(getReportsTags), switchMap(() => this.reportsApiService.reportsGetTags({})), - mergeMap((res: ReportsGetTagsResponse) => [ - setReportsTags({tags: res.tags}) + concatLatestFrom(() => this.store.select(selectMainPageTagsFilter)), + mergeMap(([res, fTags]) => [ + setReportsTags({tags: res.tags}), + ...(fTags?.some(fTag => !res.tags.includes(cleanTag(fTag))) ? + [ + setMainPageTagsFilter({ + tags: fTags.filter(fTag => res.tags.includes(cleanTag(fTag))), + feature: 'reports' + }) + ] : []), ]), catchError(err => [ requestFailed(err), @@ -177,7 +188,7 @@ export class ReportsEffects { only_fields: ['name', 'status', 'company.id', 'user.id', 'comment', 'report', 'tags', 'system_tags', 'report_assets', 'project.name'] })), tap(res => { - if(res.tasks.length === 0){ + if (res.tasks.length === 0) { this.router.navigateByUrl('/404'); } }), @@ -231,19 +242,23 @@ export class ReportsEffects { ), switchMap((moveRequest: ReportsMoveRequest) => this.reportsApiService.reportsMove(moveRequest) .pipe( - withLatestFrom(this.store.select(selectSelectedProjectId)), - mergeMap(([res, projectId]: [ReportsMoveResponse, string]) => [ + concatLatestFrom(() => [ + this.store.select(selectSelectedProjectId), + this.store.select(selectNestedReports) + ]), + mergeMap(([res, projectId, nested]: [ReportsMoveResponse, string, boolean]) => [ setReportChanges({ id: moveRequest.task, - filterOut: projectId && projectId !== '*' && projectId !== moveRequest.project, + filterOut: projectId && (projectId !== '*' || nested) && projectId !== moveRequest.project, changes: { project: (moveRequest.project ? - {id: moveRequest.project, name: moveRequest.project_name} : + {id: res.project_id, name: moveRequest.project_name} : {id: res.project_id, name: moveRequest.project_name ?? 'Root project'}) } }), deactivateLoader(moveReport.type), - addMessage(MESSAGES_SEVERITY.SUCCESS, `Report moved successfully to ${moveRequest.project_name ?? 'Projects root'}`) + addMessage(MESSAGES_SEVERITY.SUCCESS, `Report moved successfully to ${moveRequest.project_name ?? 'Projects root'}`), + navigateToProjectAfterMove({projectId: res.project_id}) ]), catchError(err => [ deactivateLoader(moveReport.type), @@ -254,6 +269,15 @@ export class ReportsEffects { ) )); + navigateToProjectAfterMove = createEffect(() => this.actions.pipe( + ofType(navigateToProjectAfterMove), + concatLatestFrom(() => this.store.select(selectReport)), + filter(([, report])=> !!report?.id), + tap(([action, report]) => { + this.router.navigateByUrl(`reports/${action.projectId}/${report.id}`); + }), + ), {dispatch: false}); + publishReport = createEffect(() => this.actions.pipe( ofType(publishReport), switchMap(action => this.reportsApiService.reportsPublish({task: action.id}) @@ -379,7 +403,7 @@ export class ReportsEffects { switchMap(action => this.http.delete(action.resource) .pipe( catchError(() => [addMessage(MESSAGES_SEVERITY.ERROR, 'failed to delete resource')]), - withLatestFrom(this.store.select(selectReport)), + concatLatestFrom(() => this.store.select(selectReport)), mergeMap(([, report]) => [updateReport({ id: report.id, changes: { diff --git a/src/app/webapp-common/reports/reports.module.ts b/src/app/webapp-common/reports/reports.module.ts index 62766254..7160b4aa 100644 --- a/src/app/webapp-common/reports/reports.module.ts +++ b/src/app/webapp-common/reports/reports.module.ts @@ -1,4 +1,4 @@ -import {NgModule} from '@angular/core'; +import {InjectionToken, NgModule} from '@angular/core'; import {CommonModule} from '@angular/common'; import {SMSharedModule} from '../shared/shared.module'; import {EffectsModule} from '@ngrx/effects'; @@ -6,8 +6,8 @@ import {CommonLayoutModule} from '../layout/layout.module'; import {SharedModule} from '~/shared/shared.module'; import {ReportsEffects} from './reports.effects'; import {ReportsPageComponent} from './reports-page/reports-page.component'; -import {StoreModule} from '@ngrx/store'; -import {REPORTS_KEY, reportsReducer} from './reports.reducer'; +import {ActionReducer, StoreConfig, StoreModule} from '@ngrx/store'; +import {REPORTS_KEY, reportsReducer, ReportsState} from './reports.reducer'; import {ReportsRoutingModule} from './reports-routing.module'; import {ReportsListComponent} from './reports-list/reports-list.component'; import {ReportsHeaderComponent} from './reports-filters/reports-header.component'; @@ -24,7 +24,21 @@ import {ExistNameValidatorDirective} from '@common/shared/ui-components/template import {SharedPipesModule} from '@common/shared/pipes/shared-pipes.module'; import {MatProgressSpinnerModule} from '@angular/material/progress-spinner'; import {LabeledFormFieldDirective} from '@common/shared/directive/labeled-form-field.directive'; +import {UserPreferences} from '@common/user-preferences'; +import {createUserPrefFeatureReducer} from '@common/core/meta-reducers/user-pref-reducer'; +import {REPORTS_PREFIX} from '@common/reports/reports.actions'; + +const reportsSyncedKeys = ['orderBy', 'sortOrder']; +export const REPORTS_STORE_CONFIG_TOKEN = + new InjectionToken>('DatasetsConfigToken'); + +const getInitState = (userPreferences: UserPreferences) => ({ + metaReducers: [ + (reducer: ActionReducer) => + createUserPrefFeatureReducer(REPORTS_KEY, reportsSyncedKeys, [REPORTS_PREFIX], userPreferences, reducer), + ] +}); @NgModule({ imports: [ UiComponentsModule, @@ -34,7 +48,7 @@ import {LabeledFormFieldDirective} from '@common/shared/directive/labeled-form-f ReactiveFormsModule, SMMaterialModule, EffectsModule.forFeature([ReportsEffects]), - StoreModule.forFeature(REPORTS_KEY, reportsReducer), + StoreModule.forFeature(REPORTS_KEY, reportsReducer, REPORTS_STORE_CONFIG_TOKEN), SharedModule, CommonLayoutModule, ReportsRoutingModule, @@ -48,7 +62,10 @@ import {LabeledFormFieldDirective} from '@common/shared/directive/labeled-form-f ], declarations: [ReportsPageComponent, ReportsListComponent, ReportsHeaderComponent, ReportDialogComponent, CreateNewReportFormComponent, ReportComponent], - exports: [] + exports: [], + providers: [ + {provide: REPORTS_STORE_CONFIG_TOKEN, useFactory: getInitState, deps: [UserPreferences]}, + ], }) export class ReportsModule { } diff --git a/src/app/webapp-common/reports/reports.reducer.ts b/src/app/webapp-common/reports/reports.reducer.ts index 7460a278..0dc6ea46 100644 --- a/src/app/webapp-common/reports/reports.reducer.ts +++ b/src/app/webapp-common/reports/reports.reducer.ts @@ -3,7 +3,7 @@ import { addReports, addReportsTags, removeReport, resetReports, - setArchive, + setArchive, setEditMode, setReport, setReportChanges, setReports, @@ -13,6 +13,7 @@ import { } from './reports.actions'; import {IReport} from './reports.consts'; import {TABLE_SORT_ORDER, TableSortOrderEnum} from '../shared/ui-components/data/table/table.consts'; +import {selectRouterConfig} from '@common/core/reducers/router-reducer'; const getCorrectSortingOrder = (currentSortOrder: TableSortOrderEnum, currentOrderField: string, nextOrderField: string) => { if (currentOrderField === nextOrderField) { @@ -34,6 +35,7 @@ export interface ReportsState { archive: boolean; noMoreReports: boolean; queryString: { query: string; regExp?: boolean }; + editing: boolean; } export const reportsInitState: ReportsState = { @@ -46,35 +48,36 @@ export const reportsInitState: ReportsState = { archive: false, noMoreReports: null, queryString: null, + editing: false }; export const reportsReducer = createReducer( reportsInitState, - on(setReports, (state, action) => ({ + on(setReports, (state, action): ReportsState => ({ ...state, reports: action.reports, scroll: action.scroll, noMoreReports: action.noMoreReports }) ), - on(addReports, (state, action) => ({ + on(addReports, (state, action): ReportsState => ({ ...state, reports: [...(action.scroll && state.reports || []), ...action.reports], scroll: action.scroll, noMoreReports: action.noMoreReports }) ), - on(setReportsTags, (state, action) => ({ + on(setReportsTags, (state, action): ReportsState => ({ ...state, reportsTags: action.tags }) ), - on(addReportsTags, (state, action) => ({ + on(addReportsTags, (state, action): ReportsState => ({ ...state, reportsTags: Array.from(new Set(state.reportsTags.concat(action.tags))).sort() })), - on(setReport, (state, action) => ({...state, report: action.report})), - on(setReportChanges, (state, action) => ({ + on(setReport, (state, action): ReportsState => ({...state, report: action.report})), + on(setReportChanges, (state, action): ReportsState => ({ ...state, report: {...state.report, ...action.changes}, reports: (action.filterOut ? @@ -82,23 +85,24 @@ export const reportsReducer = createReducer( state.reports )?.map(report => report.id !== action.id ? report : {...report, ...action.changes}) })), - on(setArchive, (state, action) => ({...state, archive: action.archive, scroll: null, reports: null})), - on(resetReports, (state) => ({ + on(setArchive, (state, action): ReportsState => ({...state, archive: action.archive, scroll: null, reports: null})), + on(resetReports, (state): ReportsState => ({ ...state, reports: reportsInitState.reports, - scrollId: reportsInitState.scroll, + scroll: reportsInitState.scroll, noMoreReports: reportsInitState.noMoreReports })), - on(setReportsOrderBy, (state, action) => ({ + on(setReportsOrderBy, (state, action): ReportsState => ({ ...state, orderBy: action.orderBy, sortOrder: getCorrectSortingOrder(state.sortOrder, state.orderBy, action.orderBy), })), - on(removeReport, (state, action) => ({...state, reports: state.reports?.filter(report => report.id !== action.id)})), - on(setReportsSearchQuery, (state, action) => ({ + on(removeReport, (state, action): ReportsState => ({...state, reports: state.reports?.filter(report => report.id !== action.id)})), + on(setReportsSearchQuery, (state, action): ReportsState => ({ ...state, queryString: (action as ReturnType), })), + on(setEditMode, (state, action): ReportsState => ({...state, editing: action.editing})), ); export const selectReportsState = state => state[REPORTS_KEY] as ReportsState; @@ -112,3 +116,5 @@ export const selectNoMoreReports = createSelector(selectReportsState, state => s export const selectReportsOrderBy = createSelector(selectReportsState, state => state ? state.orderBy : reportsInitState.orderBy); export const selectReportsSortOrder = createSelector(selectReportsState, state => state ? state.sortOrder : reportsInitState.sortOrder); export const selectReportsQueryString = createSelector(selectReportsState, state => state ? state.queryString : reportsInitState.queryString); +export const selectEditingReport = createSelector(selectReportsState, state => state.editing); +export const selectNestedReports = createSelector(selectRouterConfig, config => config?.length === 3 && config.at(-1) === 'reports'); diff --git a/src/app/webapp-common/select-model/select-model.actions.ts b/src/app/webapp-common/select-model/select-model.actions.ts index d4114875..873f219e 100755 --- a/src/app/webapp-common/select-model/select-model.actions.ts +++ b/src/app/webapp-common/select-model/select-model.actions.ts @@ -34,8 +34,6 @@ export const globalFilterChanged = createAction(`${SELECT_MODEL_PREFIX} global f export const setCurrentScrollId = createAction(SELECT_MODEL_PREFIX + 'set current scrollId', props<{ scrollId: string }>()); -export const allProjectsModeChanged = createAction(`${SELECT_MODEL_PREFIX} all projects mode changed`, props<{ isAllProjects: boolean }>()); - export const setViewMode = createAction(`${SELECT_MODEL_PREFIX} set view mode`, props<{ viewMode: ModelsViewModesEnum }>()); -export const resetState = createAction(`${SELECT_MODEL_PREFIX} reset state`); +export const resetSelectModelState = createAction(`${SELECT_MODEL_PREFIX} reset state`, props<{fullReset: boolean}>()); diff --git a/src/app/webapp-common/select-model/select-model.component.html b/src/app/webapp-common/select-model/select-model.component.html index 314b080a..439e1fe5 100755 --- a/src/app/webapp-common/select-model/select-model.component.html +++ b/src/app/webapp-common/select-model/select-model.component.html @@ -4,14 +4,11 @@ @@ -24,7 +21,8 @@ [hideSelectAll]="true" [reorderableColumns]="false" [models]="models$ | async" - [tableCols]="tableCols" + [tableCols]="tableCols$ | async" + [metadataValuesOptions]="metadataColsOptions$ | async" [noMoreModels]="noMoreModels$ | async" [users]="users$ |async" [frameworks]="frameworks$ |async" @@ -48,7 +46,7 @@ -
diff --git a/src/app/webapp-common/settings/admin/profile-preferences/profile-preferences.component.ts b/src/app/webapp-common/settings/admin/profile-preferences/profile-preferences.component.ts index 9e23b2d1..4014ab5c 100644 --- a/src/app/webapp-common/settings/admin/profile-preferences/profile-preferences.component.ts +++ b/src/app/webapp-common/settings/admin/profile-preferences/profile-preferences.component.ts @@ -36,7 +36,7 @@ export class ProfilePreferencesComponent implements OnDestroy { public hideRedactedArguments$: Observable<{ key: string }[]>; public hideExamples$: Observable; - constructor(private store: Store, private dialog: MatDialog) { + constructor(private store: Store, private dialog: MatDialog) { this.hideRedactedArguments$ = this.store.select(selectHideRedactedArguments); this.show$ = store.select(selectShowHiddenUserSelection); diff --git a/src/app/webapp-common/settings/admin/redacted-arguments-dialog/redacted-arguments-dialog.component.html b/src/app/webapp-common/settings/admin/redacted-arguments-dialog/redacted-arguments-dialog.component.html index 3cfb3fae..dbf632f8 100644 --- a/src/app/webapp-common/settings/admin/redacted-arguments-dialog/redacted-arguments-dialog.component.html +++ b/src/app/webapp-common/settings/admin/redacted-arguments-dialog/redacted-arguments-dialog.component.html @@ -1,7 +1,7 @@
- + Required ; private redactedArgumentsSub: Subscription; - constructor(public dialogRef: MatDialogRef, private store: Store) { + constructor(public dialogRef: MatDialogRef, private store: Store) { this.redactedArguments$ = this.store.select(selectRedactedArguments); } @@ -29,7 +29,9 @@ export class RedactedArgumentsDialogComponent implements OnInit, OnDestroy { } applyChanges() { - this.store.dispatch(setRedactedArguments({redactedArguments: this.redactedArguments})); + this.store.dispatch(setRedactedArguments({ + redactedArguments: this.redactedArguments.filter(value => !!value.key?.trim()) + })); this.dialogRef.close(); } diff --git a/src/app/webapp-common/settings/admin/s3-access/s3-access.component.html b/src/app/webapp-common/settings/admin/s3-access/s3-access.component.html index ddb00d40..723e1080 100755 --- a/src/app/webapp-common/settings/admin/s3-access/s3-access.component.html +++ b/src/app/webapp-common/settings/admin/s3-access/s3-access.component.html @@ -1,42 +1,30 @@ -
-
Bucket
-
Key
-
Secret / SAS
-
Token
-
AWS Region
-
Host (Endpoint)
+
+
Bucket
+
Key
+
Secret / SAS
+
Token
+
AWS Region
+
Host (Endpoint)
-
-
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- + + + + + + + +
+
-
+
diff --git a/src/app/webapp-common/settings/admin/s3-access/s3-access.component.scss b/src/app/webapp-common/settings/admin/s3-access/s3-access.component.scss index 578be031..2c61b688 100755 --- a/src/app/webapp-common/settings/admin/s3-access/s3-access.component.scss +++ b/src/app/webapp-common/settings/admin/s3-access/s3-access.component.scss @@ -6,14 +6,18 @@ } .s3-row { - margin-bottom: 10px !important; + display: grid; + grid-gap: 6px; + grid-template-columns: minmax(60px, 20%) minmax(40px, 15%) minmax(60px, 20%) minmax(40px, 10%) minmax(40px, 10%) minmax(60px, 20%) 50px; + padding: 6px 0; + border-bottom: $blue-500 solid 1px; + input { color: $blue-100; } &:last-child { padding-bottom: 8px; - border-bottom: $blue-500 solid 1px; } } @@ -21,9 +25,14 @@ color: $blue-400; border-bottom: solid 1px; padding-bottom: 6px; + margin-bottom: 10px; text-transform: uppercase; font-weight: 500; font-size: 12px; + + & > div { + padding-left: 6px; + } } .delete-button { diff --git a/src/app/webapp-common/settings/admin/s3-access/s3-access.component.ts b/src/app/webapp-common/settings/admin/s3-access/s3-access.component.ts index a447cdf8..0c34ebf3 100755 --- a/src/app/webapp-common/settings/admin/s3-access/s3-access.component.ts +++ b/src/app/webapp-common/settings/admin/s3-access/s3-access.component.ts @@ -21,16 +21,19 @@ export class S3AccessComponent implements OnDestroy, OnInit { @Input() set s3bucketCredentials(s3bucketCredentials) { this.formChangeSubscription && this.formChangeSubscription.unsubscribe(); - this.buildS3Form(s3bucketCredentials); - this.formChangeSubscription = this.S3Form.valueChanges.pipe(debounceTime(300)).subscribe(formData => { - this.saveCredentials(this.S3Form.getRawValue()); - this.disableTutorialBucket(); - }); + if (!this.S3Form) { + this.buildS3Form(s3bucketCredentials); + } + this.formChangeSubscription = this.S3Form.valueChanges + .pipe(debounceTime(300)).subscribe(() => { + this.saveCredentials(this.S3Form.getRawValue()); + this.disableTutorialBucket(); + }); }; @Output() s3bucketCredentialsChanged = new EventEmitter(); - constructor(private store: Store, private formBuilder: UntypedFormBuilder) { + constructor(private store: Store, private formBuilder: UntypedFormBuilder) { } get bucketCredentials(): UntypedFormArray { @@ -53,13 +56,13 @@ export class S3AccessComponent implements OnDestroy, OnInit { this.bucketCredentials.removeAt(index); } - private buildS3Form(S3Credentials) { + private buildS3Form(s3Credentials) { this.S3Form = this.formBuilder.group({ bucketCredentials: this.formBuilder.array([]), }); - if (S3Credentials?.bucketCredentials?.length) { - S3Credentials.bucketCredentials.forEach(bucket => { + if (s3Credentials?.bucketCredentials?.length) { + s3Credentials.bucketCredentials.forEach(bucket => { this.addBucket(bucket || {}); }); } @@ -67,10 +70,6 @@ export class S3AccessComponent implements OnDestroy, OnInit { this.disableTutorialBucket(); } - public trackByFn(index, item) { - return index; - } - public saveCredentials(formData) { this.s3bucketCredentialsChanged.emit(formData); } diff --git a/src/app/webapp-common/shared/components/base-context-menu/base-context-menu.component.ts b/src/app/webapp-common/shared/components/base-context-menu/base-context-menu.component.ts index 0534d178..71ff7c04 100644 --- a/src/app/webapp-common/shared/components/base-context-menu/base-context-menu.component.ts +++ b/src/app/webapp-common/shared/components/base-context-menu/base-context-menu.component.ts @@ -6,9 +6,8 @@ import {deactivateEdit, activateEdit} from 'app/webapp-common/experiments/action import {activateModelEdit, cancelModelEdit} from 'app/webapp-common/models/actions/models-info.actions'; import {CountAvailableAndIsDisableSelectedFiltered} from '@common/shared/entity-page/items.utils'; import {MenuItems} from '../../entity-page/items.utils'; -import {selectRouterParams} from '@common/core/reducers/router-reducer'; -import {map} from 'rxjs/operators'; import {Subscription} from 'rxjs'; +import {selectSelectedProjectId} from '@common/core/reducers/projects.reducer'; @Component({ selector: 'sm-base-context-menu', @@ -18,6 +17,7 @@ export class BaseContextMenuComponent implements OnDestroy{ public position = {x: 0, y: 0}; public menuItems = MenuItems; public projectId: string; + public allProjects: boolean; protected sub = new Subscription(); @ViewChild('tagMenuContent') tagMenu: TagsMenuComponent; @@ -38,12 +38,14 @@ export class BaseContextMenuComponent implements OnDestroy{ } constructor( - protected store: Store, + protected store: Store, protected eRef: ElementRef ) { - this.sub.add(store.select(selectRouterParams) - .pipe(map(params => params?.projectId)) - .subscribe(id => this.projectId = id) + this.sub.add(store.select(selectSelectedProjectId) + .subscribe(id => { + this.projectId = id; + this.allProjects = id === '*'; + }) ); } diff --git a/src/app/webapp-common/shared/components/charts/donut/donut.component.html b/src/app/webapp-common/shared/components/charts/donut/donut.component.html index 3daef803..107cb53b 100644 --- a/src/app/webapp-common/shared/components/charts/donut/donut.component.html +++ b/src/app/webapp-common/shared/components/charts/donut/donut.component.html @@ -1,5 +1,5 @@
-
+
{{slice.name}}({{slice.quantity}}) + data-id="annotationColorButton" + >
{{slice.name}}({{slice.quantity}})
diff --git a/src/app/webapp-common/shared/components/charts/donut/donut.component.ts b/src/app/webapp-common/shared/components/charts/donut/donut.component.ts index 3d6d0771..a25c0c42 100644 --- a/src/app/webapp-common/shared/components/charts/donut/donut.component.ts +++ b/src/app/webapp-common/shared/components/charts/donut/donut.component.ts @@ -9,7 +9,7 @@ import { } from '@angular/core'; import {select, Selection} from 'd3-selection'; import {ColorHashService} from '../../../services/color-hash/color-hash.service'; -import donut from 'britecharts/dist/umd/donut.min'; +import {donut} from 'britecharts'; import {Store} from '@ngrx/store'; import {BehaviorSubject, combineLatest, fromEvent, Subscription} from 'rxjs'; import {debounceTime, filter, startWith} from 'rxjs/operators'; @@ -54,7 +54,7 @@ export class DonutComponent implements OnInit, AfterViewInit, OnDestroy { if (this.donutContainer !== undefined) { this.donutChart.colorSchema(colors); if (this.donutData) { - this.donutContainer.datum(this.donutData).call(this.donutChart); + this.redraw(); } } } @@ -81,7 +81,7 @@ export class DonutComponent implements OnInit, AfterViewInit, OnDestroy { filter(source => source !== null), debounceTime(50) ) - .subscribe(() => this.onResize())); + .subscribe(() => this.redraw())); } ngAfterViewInit(): void { @@ -107,7 +107,7 @@ export class DonutComponent implements OnInit, AfterViewInit, OnDestroy { }); } - onResize() { + redraw() { this.donutContainer.select('svg').remove(); this.donutChart = donut(); this._colors && this.donutChart.colorSchema(this._colors); diff --git a/src/app/webapp-common/shared/components/charts/line-chart/line-chart.component.scss b/src/app/webapp-common/shared/components/charts/line-chart/line-chart.component.scss index 7191c178..19a01e68 100644 --- a/src/app/webapp-common/shared/components/charts/line-chart/line-chart.component.scss +++ b/src/app/webapp-common/shared/components/charts/line-chart/line-chart.component.scss @@ -1,4 +1,3 @@ -@use "sass:math"; @import "variables"; :host { @@ -23,7 +22,7 @@ .loading { display: flex; align-items: center; - transform: translateY(#{-1 * math.div($chart-bar-height, 2)}); + transform: translateY(#{-1 * $chart-bar-height * 0.5}); } .text { font-size: 14px; @@ -45,11 +44,6 @@ fill: $black; } - ::ng-deep .horizontal-grid-line { - stroke: $dark-grey-blue; - stroke-dasharray: none; - } - ::ng-deep .extended-x-line { stroke: $dark-grey-blue; } @@ -64,7 +58,7 @@ fill: $light-periwinkle-two; } - ::ng-deep rect.tooltip-text-container { + ::ng-deep rect.tooltip-background { fill: black !important; stroke: $blue-300 !important; } @@ -82,4 +76,11 @@ ::ng-deep .tooltip-left-text, ::ng-deep .tooltip-right-text { fill: $blue-200 !important; } + + ::ng-deep .grid { + .grid-line { + stroke: $dark-grey-blue; + stroke-dasharray: none; + } + } } diff --git a/src/app/webapp-common/shared/components/charts/line-chart/line-chart.component.ts b/src/app/webapp-common/shared/components/charts/line-chart/line-chart.component.ts index 3dfe6677..72f6f56c 100644 --- a/src/app/webapp-common/shared/components/charts/line-chart/line-chart.component.ts +++ b/src/app/webapp-common/shared/components/charts/line-chart/line-chart.component.ts @@ -8,10 +8,9 @@ import { AfterViewInit, NgZone, } from '@angular/core'; -import line from 'britecharts/dist/umd/line.min'; -import tooltip from 'britecharts/dist/umd/tooltip.min'; -import legend from 'britecharts/dist/umd/legend.min'; +import {line, tooltip, legend} from 'britecharts'; import {select, Selection} from 'd3-selection'; +import 'd3-transition'; interface Topic { topicName: string; diff --git a/src/app/webapp-common/shared/components/charts/scatter-plot/scatter-plot.component.scss b/src/app/webapp-common/shared/components/charts/scatter-plot/scatter-plot.component.scss index 15bef8b5..afb10bb1 100644 --- a/src/app/webapp-common/shared/components/charts/scatter-plot/scatter-plot.component.scss +++ b/src/app/webapp-common/shared/components/charts/scatter-plot/scatter-plot.component.scss @@ -8,8 +8,9 @@ fill: $blue-100; } - ::ng-deep .extended-x-line { - stroke-width: 0; + ::ng-deep .extended-x-line, ::ng-deep .extended-y-line { + stroke-width: 1px; + stroke: $dark-grey-blue; } } @@ -58,4 +59,12 @@ ::ng-deep .vertical-grid-line, ::ng-deep .horizontal-grid-line { opacity: 0.2; } + + ::ng-deep .grid { + .grid-line { + stroke: $dark-grey-blue !important; + stroke-dasharray: none !important; + } + } } + diff --git a/src/app/webapp-common/shared/components/charts/scatter-plot/scatter-plot.component.ts b/src/app/webapp-common/shared/components/charts/scatter-plot/scatter-plot.component.ts index 7c1293d2..a7d858d1 100644 --- a/src/app/webapp-common/shared/components/charts/scatter-plot/scatter-plot.component.ts +++ b/src/app/webapp-common/shared/components/charts/scatter-plot/scatter-plot.component.ts @@ -10,11 +10,9 @@ import { Output, ViewChild } from '@angular/core'; -import scatterPlot from 'britecharts/dist/umd/scatterPlot.min'; -// import scatterPlot from 'britecharts/src/charts/scatter-plot'; -import miniTooltip from 'britecharts/dist/umd/miniTooltip.min'; -// import miniTooltip from 'britecharts/src/charts/mini-tooltip'; +import {scatterPlot, miniTooltip, MousePosition, ChartSize} from 'britecharts'; import {select, Selection} from 'd3-selection'; +import 'd3-zoom'; import {cloneDeep} from 'lodash-es'; import {fromEvent, Subscription} from 'rxjs'; import {throttleTime} from 'rxjs/operators'; @@ -96,10 +94,11 @@ export class ScatterPlotComponent implements AfterViewInit, OnDestroy { .yAxisLabelOffset(-40) .xAxisFormatType('time') .xAxisFormat('%x') - .maxCircleArea(3) + .maxCircleArea(0) .isAnimated(false) + .enableZoom(true) .on('customMouseOver', this.tooltip.show) - .on('customMouseMove', (dataPoint, mousePos, chartSize) => { + .on('customMouseMove', (dataPoint: ProjectStatsGraphData, mousePos: MousePosition, chartSize: ChartSize) => { this.tooltip.title(dataPoint.title); this.tooltip.update(dataPoint, mousePos, chartSize); }) @@ -108,7 +107,7 @@ export class ScatterPlotComponent implements AfterViewInit, OnDestroy { this.updateChart(); - this.tooltip.valueLabel('y').nameLabel('nameExt').title('Experiment'); + this.tooltip.title('Experiment'); this.tooltipContainer = this.chartContainer.select('.scatter-plot'); this.tooltipContainer.datum([]).call(this.tooltip); this.initialized = true; @@ -120,10 +119,10 @@ export class ScatterPlotComponent implements AfterViewInit, OnDestroy { this.chart.colorSchema(this.colors); } if (this.chartData) { - this.chartContainer.datum(this.chartData).call(this.chart); + this.chartContainer.datum(this.chartData).call(this.chart, null); window.setTimeout(() => this.chartContainer.selectAll('circle.data-point').attr('r', 3), 1000); } - this.chartContainer.selectAll('#scatter-clip-path').remove(); + // this.chartContainer.selectAll('#scatter-clip-path').remove(); } ngOnDestroy(): void { diff --git a/src/app/webapp-common/shared/components/experiment-refresh/experiment-refresh.component.ts b/src/app/webapp-common/shared/components/experiment-refresh/experiment-refresh.component.ts index b275427a..8c550529 100644 --- a/src/app/webapp-common/shared/components/experiment-refresh/experiment-refresh.component.ts +++ b/src/app/webapp-common/shared/components/experiment-refresh/experiment-refresh.component.ts @@ -22,7 +22,7 @@ export class ExperimentRefreshComponent implements OnInit, OnDestroy { @Output() onRefreshLogClicked = new EventEmitter<{isAutoRefresh: boolean}>(); @Output() toggleSettings = new EventEmitter(); - constructor(private store: Store) { + constructor(private store: Store) { } ngOnInit() { diff --git a/src/app/webapp-common/shared/components/experiment-settings/experiment-settings.html b/src/app/webapp-common/shared/components/experiment-settings/experiment-settings.html index e41fc9d6..cf1316e6 100644 --- a/src/app/webapp-common/shared/components/experiment-settings/experiment-settings.html +++ b/src/app/webapp-common/shared/components/experiment-settings/experiment-settings.html @@ -1,3 +1,3 @@
- +
diff --git a/src/app/webapp-common/shared/components/json-viewer/json-viewer.component.html b/src/app/webapp-common/shared/components/json-viewer/json-viewer.component.html new file mode 100644 index 00000000..6fa3cef2 --- /dev/null +++ b/src/app/webapp-common/shared/components/json-viewer/json-viewer.component.html @@ -0,0 +1,63 @@ + + +
+
+ {{part[0]}}{{part[1]}} + : + + + {{part[0]}}{{part[1]}} + +
+
+ + +
+ +
{{ segment.type === 'array' ? ']' : '}' }}
+
+
+ +
+
{{ segment.closing }}
+
+
+ diff --git a/src/app/webapp-common/shared/components/ngx-json-viewer/ngx-json-viewer.component.scss b/src/app/webapp-common/shared/components/json-viewer/json-viewer.component.scss similarity index 73% rename from src/app/webapp-common/shared/components/ngx-json-viewer/ngx-json-viewer.component.scss rename to src/app/webapp-common/shared/components/json-viewer/json-viewer.component.scss index 7f282d61..d2a07dcd 100644 --- a/src/app/webapp-common/shared/components/ngx-json-viewer/ngx-json-viewer.component.scss +++ b/src/app/webapp-common/shared/components/json-viewer/json-viewer.component.scss @@ -1,4 +1,5 @@ @import "variables"; +@import "src/app/webapp-common/assets/fonts/variables"; $type-colors: ( string: #FF6B6B, @@ -16,31 +17,47 @@ $type-colors: ( ); .ngx-json-viewer { + height: 100%; + overflow: auto; + font-family: $font-family-monospace; font-size: 13px; position: relative; .segment { padding: 2px; - margin: 2px 0 2px 14px; + margin-left: 14px; .segment-main { word-wrap: break-word; .toggler { position: absolute; - margin-left: -14px; + margin-left: -24px; + line-height: 16px; color: $blue-300; &:hover { color: $blue-100; } &::after { display: inline-block; - content: "â–¶"; + font-family: $icomoon-font-family; + content: $al-ico-ico-chevron-down; + font-size: 18px; + transform: rotate(-90deg); transition: transform 0.1s ease-in, color 0.1s ease-in; } } + &.expanded > .toggler::after { + transform: none; + } + + &.expandable, + &.expandable > .toggler { + cursor: pointer; + } + .segment-key { color: #4E187C; } @@ -60,21 +77,21 @@ $type-colors: ( } } - .found { &::selection { background-color: $white; color: darken($neon-yellow, 20%); } + background-color: lighten($neon-yellow, 20%); border-radius: 4px; padding: 0 2px; - &.current { - background-color: lighten($orangada, 10%); - &::selection { - background-color: $white; - color: darken($orangada, 10%); - } + } + .current .found{ + background-color: lighten($orangada, 10%); + &::selection { + background-color: $white; + color: darken($orangada, 10%); } } @@ -101,13 +118,15 @@ $type-colors: ( white-space: nowrap; } - .expanded > .toggler::after { - transform: rotate(90deg); + + .braces.last { + position: relative; + left: -12px; } - .expandable, - .expandable > .toggler { - cursor: pointer; + mat-tree-node { + font-size: 13px; + line-height: normal; } } diff --git a/src/app/webapp-common/shared/components/json-viewer/json-viewer.component.ts b/src/app/webapp-common/shared/components/json-viewer/json-viewer.component.ts new file mode 100644 index 00000000..f15e39c6 --- /dev/null +++ b/src/app/webapp-common/shared/components/json-viewer/json-viewer.component.ts @@ -0,0 +1,215 @@ +import {ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, Output} from '@angular/core'; +import {MatTreeFlatDataSource, MatTreeFlattener, MatTreeModule} from '@angular/material/tree'; +import {FlatTreeControl} from '@angular/cdk/tree'; +import {MatButtonModule} from '@angular/material/button'; +import {NgClass, NgForOf, NgIf, NgTemplateOutlet} from '@angular/common'; + +export interface Segment { + key: string; + value: any; + type: undefined | string; + description: string; + found?: boolean; + level: number; + expandable: boolean; + closing?: string; + searchIndex?: number; +} + +interface JsonChild { + key: string; + value: any; + closing?: string; +} + +@Component({ + selector: 'sm-json-viewer', + templateUrl: './json-viewer.component.html', + styleUrls: ['./json-viewer.component.scss'], + standalone: true, + imports: [ + MatButtonModule, + MatTreeModule, + NgIf, + NgClass, + NgTemplateOutlet, + NgForOf, + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class JsonViewerComponent { + private readonly treeFlattener: MatTreeFlattener; + public readonly treeControl: FlatTreeControl; + public dataSource: MatTreeFlatDataSource; + + @Input() set json(json: any) { + if (json) { + this.dataSource.data = [{key: null, value: json}]; + this.treeControl.expandAll(); + } + } + + private _searchIndex: number; + get searchIndex(): number { + return this._searchIndex; + } + + @Input() set searchIndex(value: number) { + this._searchIndex = value; + window.setTimeout(() => + this.ref.nativeElement.getElementsByClassName('current').item(0)?.scrollIntoView({behavior: 'smooth'}) + ); + } + get search() { + return this._search; + } + + @Input() set search(val: string) { + this._search = val; + if (this.treeControl && val?.length > 0) { + const compareValue = val?.toLowerCase(); + this.expandByTerm(this.treeControl.dataNodes, val); + let i = 0; + this.treeControl.dataNodes.forEach(node => + (!node.expandable && `${node.value}`.toLowerCase().includes(compareValue)) || node.key?.toLowerCase().includes(compareValue) ? + node.searchIndex = i++ : + node.searchIndex = null + ); + + this.searchCounterChanged.emit(i); + } + } + @Input() testLink?: (str: string) => boolean; + @Output() linkAction = new EventEmitter(); + @Output() searchCounterChanged = new EventEmitter(); + + private _search: string = null; + public isArray: boolean; + public index: number = 0; + public stringify = JSON.stringify; + // nestedNodeMap = new Map(); + // flatNodeMap = new Map(); + + + constructor(private readonly ref: ElementRef) { + this.treeFlattener = new MatTreeFlattener( + ({key, value, closing}, level: number) => this.parseKeyValue(key, value, closing, level), + this.getLevel, this.isExpandable, this.getChildren + ); + this.treeControl = new FlatTreeControl(this.getLevel, this.isExpandable); + this.dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener); + } + + getLevel = (segment: Segment) => segment.level; + + isExpandable = (segment: Segment) => segment.expandable; + + private getChildren({value}: JsonChild): JsonChild[] { + if (Array.isArray(value)) { + return value.length ? + value.map((node, index) => + ({key: `${index}`, value: node} as JsonChild)).concat([{key: null, value: null, closing: ']'}]) : + []; + } else if (typeof value === 'object') { + return Object.keys(value).map((key) => + ({key, value: value[key]} as JsonChild)).concat(Object.keys(value).length ? [{key: null, value: null, closing: '}'}] : []); + } else { + return []; + } + } + + private parseKeyValue(key: string, value: any, closing: string, level: number): Segment { + const segment: Segment = { + key, + value, + level, + expandable: false, + type: undefined, + description: '' + value, + closing + }; + + if (closing) { + return {...segment, type: 'closing'}; + } + + switch (typeof segment.value) { + case 'number': { + segment.type = 'number'; + break; + } + case 'boolean': { + segment.type = 'boolean'; + break; + } + case 'function': { + segment.type = 'function'; + break; + } + case 'string': { + if (this.testLink !== undefined && this.testLink(segment.value)) { + segment.type = 'link'; + segment.description = segment.value; + } else { + segment.type = 'string'; + segment.description = '"' + segment.value + '"'; + } + break; + } + case 'undefined': { + segment.type = 'undefined'; + segment.description = 'undefined'; + break; + } + case 'object': { + // yea, null is object + if (segment.value === null) { + segment.type = 'null'; + segment.description = 'null'; + } else if (Array.isArray(segment.value)) { + segment.type = 'array'; + segment.description = 'Array[' + segment.value.length + '] ' + JSON.stringify(segment.value); + segment.expandable = segment.value.length > 0; + } else if (segment.value instanceof Date) { + segment.type = 'date'; + } else { + segment.type = 'object'; + segment.description = 'Object ' + JSON.stringify(segment.value); + segment.expandable = Object.keys(segment.value).length > 0; + } + break; + } + } + + return segment; + } + + hasChild = (_: number, node: Segment) => node.expandable; + closing = (_: number, node: Segment) => !!node.closing; + + linkActionWrapper(event: Event, link: string) { + event.preventDefault(); + this.linkAction.emit(link); + } + + subsectionAction(link: string) { + this.linkAction.emit(link); + } + + expandByTerm(nodes: Segment[], term: string) { + nodes.forEach(segment => { + if (segment.description.toLowerCase().includes(term)) { + this.treeControl.expand(segment); + } + }); + } + + split(line: string, search: string) { + if (!line) { + return []; + } + const regex = new RegExp(search, 'gi'); + const match = line.match(regex); + return line.split(regex).map( (part, i) => [part, match?.[i]]); + } +} diff --git a/src/app/webapp-common/shared/components/main-pages-header-filter/main-pages-header-filter.component.html b/src/app/webapp-common/shared/components/main-pages-header-filter/main-pages-header-filter.component.html index 009274ac..71da6120 100644 --- a/src/app/webapp-common/shared/components/main-pages-header-filter/main-pages-header-filter.component.html +++ b/src/app/webapp-common/shared/components/main-pages-header-filter/main-pages-header-filter.component.html @@ -1,7 +1,7 @@
- +
diff --git a/src/app/webapp-common/shared/debug-sample/debug-image-snippet/debug-image-snippet.component.html b/src/app/webapp-common/shared/debug-sample/debug-image-snippet/debug-image-snippet.component.html index a0e4b5db..abe1610c 100644 --- a/src/app/webapp-common/shared/debug-sample/debug-image-snippet/debug-image-snippet.component.html +++ b/src/app/webapp-common/shared/debug-sample/debug-image-snippet/debug-image-snippet.component.html @@ -2,9 +2,10 @@
-
+
+ smTooltip="Copy to Report" + data-id="copyToReportButton">
-
+
@@ -51,23 +54,25 @@
- +
+ smTooltip="Copy to Report" data-id="CopyToReportButton">
diff --git a/src/app/webapp-common/shared/debug-sample/debug-image-snippet/debug-image-snippet.component.ts b/src/app/webapp-common/shared/debug-sample/debug-image-snippet/debug-image-snippet.component.ts index 3387496a..3bbc4026 100644 --- a/src/app/webapp-common/shared/debug-sample/debug-image-snippet/debug-image-snippet.component.ts +++ b/src/app/webapp-common/shared/debug-sample/debug-image-snippet/debug-image-snippet.component.ts @@ -53,7 +53,7 @@ export class DebugImageSnippetComponent { isFailed = false; isLoading = true; - constructor(private store: Store) { + constructor(private store: Store) { } openInNewTab(source: string) { diff --git a/src/app/webapp-common/shared/debug-sample/debug-sample.effects.ts b/src/app/webapp-common/shared/debug-sample/debug-sample.effects.ts index b0433bb5..2ee190e2 100644 --- a/src/app/webapp-common/shared/debug-sample/debug-sample.effects.ts +++ b/src/app/webapp-common/shared/debug-sample/debug-sample.effects.ts @@ -15,7 +15,7 @@ import {Store} from '@ngrx/store'; export class DebugSampleEffects { - constructor(private actions$: Actions, private eventsApi: ApiEventsService, private store: Store) { + constructor(private actions$: Actions, private eventsApi: ApiEventsService, private store: Store) { } fetchDebugImagesForIter$ = createEffect(() => this.actions$.pipe( diff --git a/src/app/webapp-common/shared/debug-sample/image-viewer/base-image-viewer.component.ts b/src/app/webapp-common/shared/debug-sample/image-viewer/base-image-viewer.component.ts index 4d9f9027..6f3d3b31 100644 --- a/src/app/webapp-common/shared/debug-sample/image-viewer/base-image-viewer.component.ts +++ b/src/app/webapp-common/shared/debug-sample/image-viewer/base-image-viewer.component.ts @@ -40,7 +40,7 @@ export class BaseImageViewerComponent implements OnInit, OnDestroy { public currentDebugImageSubscription: Subscription; public imageLoaded: boolean = false; public iteration: number; - + public isFileserverUrl = isFileserverUrl; @HostListener('document:keydown', ['$event']) baseOnKeyDown(e: KeyboardEvent) { @@ -55,7 +55,7 @@ export class BaseImageViewerComponent implements OnInit, OnDestroy { } constructor( - @Inject(MAT_DIALOG_DATA) public data: {index: number; isAllMetrics: boolean; snippetsMetaData: Array<{task: string; metric: string; variant: string; iter: number}>}, + @Inject(MAT_DIALOG_DATA) public data: {index: number; isAllMetrics: boolean; withoutNavigation: boolean; snippetsMetaData: Array<{task: string; metric: string; variant: string; iter: number}>}, public dialogRef: MatDialogRef, public changeDetector: ChangeDetectorRef, public store: Store @@ -140,13 +140,15 @@ export class BaseImageViewerComponent implements OnInit, OnDestroy { downloadImage() { if (this.currentDebugImage) { - const src = new URL(this.currentDebugImage.url); + const src = new URL(this.url ?? this.currentDebugImage.url); if (isFileserverUrl(this.currentDebugImage.url)) { src.searchParams.set('download', ''); } const a = document.createElement('a') as HTMLAnchorElement; a.href = src.toString(); - a.download = last(src.pathname.split('/')); + if (!this.data.withoutNavigation) { + a.download = last(src.pathname.split('/')); + } a.target = '_blank'; a.click(); } diff --git a/src/app/webapp-common/shared/debug-sample/image-viewer/image-viewer.component.html b/src/app/webapp-common/shared/debug-sample/image-viewer/image-viewer.component.html index 047823ae..6d0592a5 100644 --- a/src/app/webapp-common/shared/debug-sample/image-viewer/image-viewer.component.html +++ b/src/app/webapp-common/shared/debug-sample/image-viewer/image-viewer.component.html @@ -2,14 +2,17 @@

{{currentDebugImage?.metric ? (currentDebugImage?.metric + ' - ' + currentDebugImage?.variant) : ''}}

+ smShowTooltipIfEllipsis data-id="debugSampleName">{{currentDebugImage?.metric ? (currentDebugImage?.metric + ' - ' + currentDebugImage?.variant) : ''}}
-
- Iteration {{iteration}} +
+ + Iteration {{iteration}} + @@ -27,20 +31,19 @@
-
- +
+
-
+
-
+
-
-
- +
+ matTooltipPosition="left" data-id="ZoomInButton">
- +
+ smTooltip="Zoom out" matTooltipPosition="left" data-id="zoomOutButton">
+
@@ -96,13 +99,13 @@
{{debugImage.naturalWidth}}
{{debugImage.naturalHeight}}
-
-
+
{{xCord}}
-
{{yCord}}
-
+
{{this.scale | toPercentage}}%
diff --git a/src/app/webapp-common/shared/debug-sample/image-viewer/image-viewer.component.scss b/src/app/webapp-common/shared/debug-sample/image-viewer/image-viewer.component.scss index 62852c63..271962e0 100644 --- a/src/app/webapp-common/shared/debug-sample/image-viewer/image-viewer.component.scss +++ b/src/app/webapp-common/shared/debug-sample/image-viewer/image-viewer.component.scss @@ -128,7 +128,7 @@ linear-gradient(45deg, #F6F6F6 25%, transparent 25%, transparent 75%, #F6F6F6 75%, #F6F6F6); background-size: 24px 24px; background-position: 0 0, 12px 12px; - min-height: calc(100% - 58px); + height: calc(100% - 58px); width: 100%; display: flex; justify-content: center; diff --git a/src/app/webapp-common/shared/entity-page/base-entity-header/base-entity-header.component.ts b/src/app/webapp-common/shared/entity-page/base-entity-header/base-entity-header.component.ts index 249b7c38..19f31b63 100644 --- a/src/app/webapp-common/shared/entity-page/base-entity-header/base-entity-header.component.ts +++ b/src/app/webapp-common/shared/entity-page/base-entity-header/base-entity-header.component.ts @@ -1,15 +1,23 @@ -import {Component} from '@angular/core'; +import {Component, EventEmitter, Input, Output} from '@angular/core'; import {BreakpointObserver, BreakpointState} from '@angular/cdk/layout'; import {Observable} from 'rxjs'; +import {Store} from '@ngrx/store'; +import {selectMaxDownloadItems} from '@common/core/reducers/users-reducer'; @Component({ selector: 'sm-base-entity-header', template: '', }) export class BaseEntityHeaderComponent { - public isSmallScreen$: Observable; + @Input() noData: boolean; + @Output() downloadTableAsCSV = new EventEmitter(); + @Output() downloadFullTableAsCSV = new EventEmitter(); - constructor(private breakpointObserver: BreakpointObserver) { + public isSmallScreen$: Observable; + public maxDownloadItems$: Observable; + + constructor(private breakpointObserver: BreakpointObserver, private store: Store) { this.isSmallScreen$ = breakpointObserver.observe('(max-width: 1450px)'); + this.maxDownloadItems$ = this.store.select(selectMaxDownloadItems); } } diff --git a/src/app/webapp-common/shared/entity-page/base-entity-page.ts b/src/app/webapp-common/shared/entity-page/base-entity-page.ts index 64e17eab..62f9123c 100644 --- a/src/app/webapp-common/shared/entity-page/base-entity-page.ts +++ b/src/app/webapp-common/shared/entity-page/base-entity-page.ts @@ -1,6 +1,6 @@ import {AfterViewInit, Component, OnDestroy, OnInit, ViewChild} from '@angular/core'; import {ActionCreator, Store} from '@ngrx/store'; -import {combineLatest, Observable, of, Subject, Subscription} from 'rxjs'; +import {combineLatest, Observable, of, Subject, Subscription, switchMap} from 'rxjs'; import { debounceTime, filter, @@ -13,9 +13,7 @@ import { } from 'rxjs/operators'; import {Project} from '~/business-logic/model/projects/project'; import { - selectAllProjectsUsers, - selectProjectUsers, - selectSelectedProject, selectTablesFilterProjectsOptions + selectSelectedProject, selectSelectedProjectUsers, selectTablesFilterProjectsOptions } from '../../core/reducers/projects.reducer'; import {IOutputData} from 'angular-split/lib/interface'; import {SplitComponent} from 'angular-split'; @@ -31,8 +29,13 @@ import { selectionHasExample } from './items.utils'; import {ActivatedRoute, Params, Router} from '@angular/router'; -import {getTablesFilterProjectsOptions, resetProjectSelection, resetTablesFilterProjectsOptions, setTablesFilterProjectsOptions} from '@common/core/actions/projects.actions'; -import {MatDialog, MatDialogRef} from '@angular/material/dialog'; +import { + getTablesFilterProjectsOptions, + resetProjectSelection, + resetTablesFilterProjectsOptions, + setTablesFilterProjectsOptions +} from '@common/core/actions/projects.actions'; +import {MatDialog} from '@angular/material/dialog'; import {ConfirmDialogComponent} from '@common/shared/ui-components/overlay/confirm-dialog/confirm-dialog.component'; import {RefreshService} from '@common/core/services/refresh.service'; import {selectTableModeAwareness} from '@common/projects/common-projects.reducer'; @@ -40,7 +43,6 @@ import {setTableModeAwareness} from '@common/projects/common-projects.actions'; import {User} from '~/business-logic/model/users/user'; import {neverShowPopupAgain} from '../../core/actions/layout.actions'; import {selectNeverShowPopups} from '../../core/reducers/view.reducer'; -import {SmSyncStateSelectorService} from '../../core/services/sync-state-selector.service'; import {isReadOnly} from '@common/shared/utils/is-read-only'; import {setCustomMetrics} from '@common/models/actions/models-view.actions'; import * as experimentsActions from '@common/experiments/actions/common-experiments-view.actions'; @@ -75,7 +77,10 @@ export abstract class BaseEntityPageComponent implements OnInit, AfterViewInit, @ViewChild('split') split: SplitComponent; protected abstract inEditMode$: Observable; - private selectedProject: Project; + public selectedProject: Project; + private currentSelection: any[]; + public showAllSelectedIsActive$: Observable; + private allProjects: boolean; abstract onFooterHandler({emitValue, item}): void; @@ -98,13 +103,13 @@ export abstract class BaseEntityPageComponent implements OnInit, AfterViewInit, protected router: Router, protected dialog: MatDialog, protected refresh: RefreshService, - protected syncSelector: SmSyncStateSelectorService, ) { - this.users$ = this.selectedProjectId === '*' ? this.store.select(selectAllProjectsUsers) : this.store.select(selectProjectUsers); - this.store.select(selectSelectedProject).pipe(filter(p => !!p), take(1)).subscribe((project: Project) => { + this.users$ = this.store.select(selectSelectedProjectUsers); + this.sub.add(this.store.select(selectSelectedProject).pipe(filter(p => !!p)).subscribe((project: Project) => { this.selectedProject = project; + this.allProjects = project?.id === '*'; this.isExampleProject = isReadOnly(project); - }); + })); this.projectsOptions$ = this.store.select(selectTablesFilterProjectsOptions); this.tableModeAwareness$ = store.select(selectTableModeAwareness) @@ -115,6 +120,7 @@ export abstract class BaseEntityPageComponent implements OnInit, AfterViewInit, } ngOnInit() { + this.selectedProject$ = this.store.select(selectSelectedProject); this.selectSplitSize$?.pipe(filter(x => !!x), take(1)) .subscribe(x => this.splitInitialSize = x); @@ -130,12 +136,13 @@ export abstract class BaseEntityPageComponent implements OnInit, AfterViewInit, this.sub.add(this.refresh.tick .pipe( - withLatestFrom(this.inEditMode$), - filter(([, edit]) => !edit), + withLatestFrom(this.inEditMode$, this.showAllSelectedIsActive$), + filter(([, edit, showAllSelectedIsActive]) => !edit && !showAllSelectedIsActive), map(([auto]) => auto) ) .subscribe(auto => this.refreshList(auto === null)) ); + this.setupBreadcrumbsOptions(); } ngAfterViewInit() { @@ -207,9 +214,9 @@ export abstract class BaseEntityPageComponent implements OnInit, AfterViewInit, config.selected$, config.data$, config.showAllSelectedIsActive$, - config.companyTags$, - config.projectTags$, - config.tagsFilterByProject$ + this.allProjects ? of(null) : config.companyTags$, + this.allProjects ? config.companyTags$ : config.projectTags$, + this.allProjects ? of(true) : config.tagsFilterByProject$ ); } @@ -237,7 +244,8 @@ export abstract class BaseEntityPageComponent implements OnInit, AfterViewInit, ).pipe( takeUntil(this.destroy$), debounceTime(100), - filter(([selected]) => selected.length > 1), + filter(([selected]) => selected.length > 1 || this.currentSelection?.length > 1), + tap(([selected]) => this.currentSelection = selected), map(([selected, data, showAllSelectedIsActive, companyTags, projectTags, tagsFilterByProject]) => { const _selectionAllHasExample = selectionAllHasExample(selected); const _selectionHasExample = selectionHasExample(selected); @@ -266,19 +274,28 @@ export abstract class BaseEntityPageComponent implements OnInit, AfterViewInit, this.afterArchiveChanged(); this.store.dispatch(resetProjectSelection()); }); - if (this.getSelectedEntities().length > 0 && !this.syncSelector.selectSync(selectNeverShowPopups)?.includes('go-to-archive')) { - const archiveDialog: MatDialogRef = this.dialog.open(ConfirmDialogComponent, { - data: { - title: 'Are you sure?', - body: 'Navigating between "Live" and "Archive" will deselect your selected data views.', - yes: 'Proceed', - no: 'Back', - iconClass: '', - showNeverShowAgain: true - } - }); - - archiveDialog.afterClosed().subscribe((confirmed) => { + this.store.select(selectNeverShowPopups) + .pipe( + take(1), + switchMap(neverShow => { + if (this.getSelectedEntities().length > 0 && !neverShow?.includes('go-to-archive')) { + return this.dialog.open(ConfirmDialogComponent, { + data: { + title: 'Are you sure?', + body: 'Navigating between "Live" and "Archive" will deselect your selected data views.', + yes: 'Proceed', + no: 'Back', + iconClass: '', + showNeverShowAgain: true + } + }).afterClosed(); + } else { + navigate(); + return of(false); + } + }) + ) + .subscribe((confirmed) => { if (confirmed) { navigate(); if (confirmed.neverShowAgain) { @@ -286,9 +303,6 @@ export abstract class BaseEntityPageComponent implements OnInit, AfterViewInit, } } }); - } else { - navigate(); - } } updateUrl(queryParams: Params) { @@ -299,14 +313,20 @@ export abstract class BaseEntityPageComponent implements OnInit, AfterViewInit, }); } - filterSearchChanged({colId, value}: { colId: string; value: {value: string; loadMore?: boolean} }) { + filterSearchChanged({colId, value}: { colId: string; value: { value: string; loadMore?: boolean } }) { switch (colId) { case 'project.name': - if (this.selectedProjectId === '*') { + if ((this.projectId || this.selectedProjectId) === '*') { !value.loadMore && this.store.dispatch(resetTablesFilterProjectsOptions()); - this.store.dispatch(getTablesFilterProjectsOptions({searchString: value.value || '', loadMore: value.loadMore})); + this.store.dispatch(getTablesFilterProjectsOptions({ + searchString: value.value || '', + loadMore: value.loadMore + })); } else { - this.store.dispatch(setTablesFilterProjectsOptions({projects: this.selectedProject ? [this.selectedProject, ...this.selectedProject?.sub_projects] : [], scrollId: null})); + this.store.dispatch(setTablesFilterProjectsOptions({ + projects: this.selectedProject ? [this.selectedProject, + ...this.selectedProject?.sub_projects] : [], scrollId: null + })); } break; case 'parent.name': @@ -319,4 +339,8 @@ export abstract class BaseEntityPageComponent implements OnInit, AfterViewInit, } } } + + public setupBreadcrumbsOptions() { + + } } diff --git a/src/app/webapp-common/shared/entity-page/entity-delete/base-delete-dialog.effects.ts b/src/app/webapp-common/shared/entity-page/entity-delete/base-delete-dialog.effects.ts index 5cb1fa9e..a74e045a 100755 --- a/src/app/webapp-common/shared/entity-page/entity-delete/base-delete-dialog.effects.ts +++ b/src/app/webapp-common/shared/entity-page/entity-delete/base-delete-dialog.effects.ts @@ -1,10 +1,11 @@ import {deactivateLoader, setServerError} from '@common/core/actions/layout.actions'; +import {selectSelectedProjectId} from '@common/core/reducers/projects.reducer'; import {Actions, createEffect, ofType} from '@ngrx/effects'; import {ApiProjectsService} from '~/business-logic/api-services/projects.service'; import {requestFailed} from '@common/core/actions/http.actions'; -import {Injectable} from '@angular/core'; -import {catchError, filter, map, mergeMap, switchMap, withLatestFrom} from 'rxjs/operators'; -import {EmptyAction} from '~/app.constants'; +import {inject, Injectable} from '@angular/core'; +import {catchError, filter, map, mergeMap, switchMap, tap, withLatestFrom} from 'rxjs/operators'; +import {emptyAction} from '~/app.constants'; import {Action, Store} from '@ngrx/store'; import {forkJoin, of} from 'rxjs'; import {fromFetch} from 'rxjs/fetch'; @@ -38,27 +39,53 @@ import {TasksResetManyResponseSucceeded} from '~/business-logic/model/tasks/task import {updateManyExperiment} from '@common/experiments/actions/common-experiments-view.actions'; import {getBucketAndKeyFromSrc, SignResponse} from '@common/settings/admin/base-admin-utils'; import {MemoizedSelector} from '@ngrx/store/src/selector'; +import {ApiPipelinesService} from '~/business-logic/api-services/pipelines.service'; +import {PipelinesDeleteRunsResponse} from '~/business-logic/model/pipelines/pipelinesDeleteRunsResponse'; +import {MatDialog} from '@angular/material/dialog'; +import {ConfirmDialogComponent} from '@common/shared/ui-components/overlay/confirm-dialog/confirm-dialog.component'; +import {ConfirmDialogConfig} from '@common/shared/ui-components/overlay/confirm-dialog/confirm-dialog.model'; +import {ErrorService} from '@common/shared/services/error.service'; +import {Router} from '@angular/router'; @Injectable() export class DeleteDialogEffectsBase { + private store: Store; + private tasksApi: ApiTasksService; + private modelsApi: ApiModelsService; + private projectsApi: ApiProjectsService; + private adminService: AdminService; + private configService: ConfigurationService; + private pipelinesService: ApiPipelinesService; + private dialog: MatDialog; + private errorService: ErrorService; + private router: Router; - constructor( - public actions$: Actions, - public store: Store, - public tasksApi: ApiTasksService, - public modelsApi: ApiModelsService, - public projectsApi: ApiProjectsService, - public adminService: AdminService, - public configService: ConfigurationService - ) { + constructor(protected actions$: Actions) { + this.store = inject(Store); + this.tasksApi = inject(ApiTasksService); + this.modelsApi = inject(ApiModelsService); + this.projectsApi = inject(ApiProjectsService); + this.adminService = inject(AdminService); + this.configService = inject(ConfigurationService); + this.pipelinesService = inject(ApiPipelinesService); + this.dialog = inject(MatDialog); + this.errorService = inject(ErrorService); + this.router = inject(Router); } deleteEntityApi(entityType: EntityTypeEnum, entities: any[], deleteArtifacts?: boolean, - resetMode?: boolean): Observable<{ failed: any[]; urlsToDelete: string[]; succeeded?: TasksResetManyResponseSucceeded[] }> { - const ids = entities.map(entity => entity.id); + resetMode?: boolean, projectId?: string): Observable<{ failed: any[]; urlsToDelete: string[]; succeeded?: TasksResetManyResponseSucceeded[] }> { + const ids = entities.map(entity => entity.id as string); switch (entityType) { - case EntityTypeEnum.dataset: case EntityTypeEnum.controller: + return this.pipelinesService.pipelinesDeleteRuns({ids, project: projectId || entities[0]?.project?.id}) + .pipe( + map((res: PipelinesDeleteRunsResponse) => ({ + failed: res.failed, + succeeded: res.succeeded, + urlsToDelete: [] + }))); + case EntityTypeEnum.dataset: case EntityTypeEnum.experiment: // eslint-disable-next-line @typescript-eslint/naming-convention return (resetMode ? this.tasksApi.tasksResetMany({ @@ -91,7 +118,7 @@ export class DeleteDialogEffectsBase { case EntityTypeEnum.pipeline: case EntityTypeEnum.simpleDataset: // eslint-disable-next-line @typescript-eslint/naming-convention - return this.projectsApi.projectsDelete({project: entities[0].id, delete_contents: true}).pipe( + return this.projectsApi.projectsDelete({project: entities[0].id, delete_contents: true, delete_external_artifacts: deleteArtifacts}).pipe( map((res: TasksDeleteResponse) => ({ urlsToDelete: [...res.urls.model_urls, ...res.urls.artifact_urls, ...res.urls.event_urls], failed: [] @@ -123,7 +150,7 @@ export class DeleteDialogEffectsBase { case EntityTypeEnum.model: return [activateModelEdit('delete')]; default: - return [new EmptyAction()]; + return [emptyAction()]; } } @@ -135,15 +162,22 @@ export class DeleteDialogEffectsBase { this.store.select(this.getEntitySelector(action.entityType)), ), map(([, entities]) => action.entity ? [action.entity] : entities), - switchMap(entities => action.includeChildren ? getChildrenExperiments(this.tasksApi, entities).pipe( - map((children: Task[]) => [...children, ...entities])) : of(entities)), - switchMap(entities => - this.deleteEntityApi(action.entityType, entities, action.deleteArtifacts, action.resetMode).pipe( - map(({ - failed, - succeeded, - urlsToDelete - }) => [this.parseErrors(failed, entities), this.getUrlsPerProvider(action.deleteArtifacts ? urlsToDelete : []), succeeded]), + switchMap(entities => action.includeChildren ? getChildrenExperiments(this.tasksApi, entities) + .pipe(map((children: Task[]) => [...children, ...entities])) : of(entities)), + withLatestFrom(this.store.select(selectSelectedProjectId)), + switchMap(([entities, projectId]) => this.deleteEntityApi( + action.entityType, + entities, + action.deleteArtifacts, + action.resetMode, + projectId + ) + .pipe( + map(({failed, succeeded, urlsToDelete}) => [ + this.parseErrors(failed, entities), + this.getUrlsPerProvider(action.deleteArtifacts ? urlsToDelete : []), + succeeded + ]), mergeMap(([failed, /*urlsPerSource*/, succeeded]: [{ id: string; name: string; message: string }[], { [provider in CloudProviders]: string[] }, TasksResetManyResponseSucceeded[]]) => [ ...this.pauseAutorefresh(action.entityType), setNumberOfSourcesToDelete({numberOfFiles: 0}),//Object.values(urlsPerSource).flat().length}), // Currently deleting only in BE @@ -153,14 +187,19 @@ export class DeleteDialogEffectsBase { // deleteGoogleCloudeSource(urlsPerSource['gc']), // deleteAzure(urlsPerSource['azure']), // addFailedDeletedFiles({filePaths: urlsPerSource['misc']}), // Currently deleting only in BE - no need to count files - action.resetMode ? updateManyExperiment({changeList: succeeded}) : new EmptyAction() + action.resetMode ? updateManyExperiment({changeList: succeeded}) : emptyAction() ] ), - catchError(error => [ - requestFailed(error), - deactivateLoader(action.type), - setServerError(error, null, `Can't delete ${action.entityType} ${error?.meta?.error_data?.id || ''}`)] - ) + catchError(error => { + if (this.errorService.lastRunError(error.error)) { + return this.handleLastRun(projectId || entities[0]?.project?.id); + } + return [ + requestFailed(error), + deactivateLoader(action.type), + setServerError(error, null, `Can't delete ${action.entityType} ${error?.meta?.error_data?.id || ''}`) + ]; + }) ) ) )), @@ -252,4 +291,27 @@ export class DeleteDialogEffectsBase { // return 'misc'; // } } + + private handleLastRun(project: string) { + return this.dialog.open(ConfirmDialogComponent, { + data: { + title: 'Delete Pipeline', + body: 'Deleting the last run of a pipeline will also delete the pipeline', + iconClass: 'i-alert', + yes: 'Delete', + no: 'Cancel' + } as ConfirmDialogConfig + }).afterClosed() + .pipe( + tap(confirm => !confirm && this.dialog.closeAll()), + filter(confirm => !!confirm), + tap(() => this.router.navigate(['pipelines'])), + switchMap(() => [ + deleteEntities({ + entityType: EntityTypeEnum.project, + entity: {id: project}, + deleteArtifacts: true + })]) + ); + } } diff --git a/src/app/webapp-common/shared/entity-page/entity-delete/common-delete-dialog.component.ts b/src/app/webapp-common/shared/entity-page/entity-delete/common-delete-dialog.component.ts index c01f0442..19594f42 100755 --- a/src/app/webapp-common/shared/entity-page/entity-delete/common-delete-dialog.component.ts +++ b/src/app/webapp-common/shared/entity-page/entity-delete/common-delete-dialog.component.ts @@ -14,7 +14,7 @@ import {deleteEntities, resetDeleteState} from './common-delete-dialog.actions'; import {getDeleteProjectPopupStatsBreakdown} from '~/features/projects/projects-page.utils'; import {EntityTypeEnum, hideDeleteArtifactsEntities} from '~/shared/constants/non-common-consts'; import {takeUntil, tap} from 'rxjs/operators'; -import {CommonProjectReadyForDeletion} from '@common/projects/common-projects.reducer'; +import {CommonReadyForDeletion} from '@common/projects/common-projects.reducer'; @Component({ @@ -51,12 +51,12 @@ export class CommonDeleteDialogComponent implements OnInit, OnDestroy { constructor( - private store: Store, + private store: Store, @Inject(MAT_DIALOG_DATA) data: { numSelected: number; entity: Task; entityType: EntityTypeEnum; - projectStats: CommonProjectReadyForDeletion; + projectStats: CommonReadyForDeletion; useCurrentEntity: boolean; includeChildren: boolean; resetMode: boolean; @@ -140,7 +140,7 @@ export class CommonDeleteDialogComponent implements OnInit, OnDestroy { this.isOpenEntities = !this.isOpenEntities; } - getMessageByEntity(entityType: EntityTypeEnum, stats?: CommonProjectReadyForDeletion): string { + getMessageByEntity(entityType: EntityTypeEnum, stats?: CommonReadyForDeletion): string { switch (entityType as any) { case EntityTypeEnum.controller: case EntityTypeEnum.experiment: diff --git a/src/app/webapp-common/shared/entity-page/entity-footer/entity-footer.component.html b/src/app/webapp-common/shared/entity-page/entity-footer/entity-footer.component.html index 2c0552ce..9d460831 100755 --- a/src/app/webapp-common/shared/entity-page/entity-footer/entity-footer.component.html +++ b/src/app/webapp-common/shared/entity-page/entity-footer/entity-footer.component.html @@ -1,5 +1,5 @@ - + - - + +
{{footerItem.title}}
{{footerItemState?.title}}
@@ -36,24 +38,22 @@ footerItemState.description || footerItem.description" > - - - - - + + +
diff --git a/src/app/webapp-common/shared/entity-page/entity-footer/entity-footer.component.ts b/src/app/webapp-common/shared/entity-page/entity-footer/entity-footer.component.ts index e2122d52..38ff0bb7 100755 --- a/src/app/webapp-common/shared/entity-page/entity-footer/entity-footer.component.ts +++ b/src/app/webapp-common/shared/entity-page/entity-footer/entity-footer.component.ts @@ -22,7 +22,7 @@ export class EntityFooterComponent extends BaseContextMenuComponent { icons = ICONS; trackBy = trackByIndex; - constructor(store: Store, eRef: ElementRef) { + constructor(store: Store, eRef: ElementRef) { super(store, eRef); } diff --git a/src/app/webapp-common/shared/entity-page/footer-items/compare-footer-item.ts b/src/app/webapp-common/shared/entity-page/footer-items/compare-footer-item.ts index d9e7fdea..1e2b56c6 100644 --- a/src/app/webapp-common/shared/entity-page/footer-items/compare-footer-item.ts +++ b/src/app/webapp-common/shared/entity-page/footer-items/compare-footer-item.ts @@ -16,7 +16,7 @@ export class CompareFooterItem extends ItemFooterModel { } getItemState(state): any { return { - disable: state.selected.length > compareLimitations, + disable: state.selected.length > compareLimitations || state.selected?.length <2, }; } diff --git a/src/app/webapp-common/shared/entity-page/footer-items/dequeue-footer-item.ts b/src/app/webapp-common/shared/entity-page/footer-items/dequeue-footer-item.ts index 894404d5..ffe26b78 100644 --- a/src/app/webapp-common/shared/entity-page/footer-items/dequeue-footer-item.ts +++ b/src/app/webapp-common/shared/entity-page/footer-items/dequeue-footer-item.ts @@ -14,9 +14,9 @@ export class DequeueFooterItem extends ItemFooterModel { getItemState(state: IFooterState): { icon?: IconNames; title?: string; description?: string; disable?: boolean; disableDescription?: string; emit?: boolean; emitValue?: boolean; preventCurrentItem?: boolean; class?: string; wrapperClass?: string } { const dequeue = state.data[this.id]; return { - preventCurrentItem: dequeue.disable, - disable: dequeue.disable, - description: this.menuItemText.transform(dequeue.available, 'Dequeue'), + preventCurrentItem: dequeue?.disable, + disable: dequeue?.disable, + description: this.menuItemText.transform(dequeue?.available, 'Dequeue'), disableDescription: state.selectionIsOnlyExamples ? 'Dequeue' : `You can only dequeue experiments with ‘Pending’ status` }; } diff --git a/src/app/webapp-common/shared/entity-page/footer-items/enqueue-footer-item.ts b/src/app/webapp-common/shared/entity-page/footer-items/enqueue-footer-item.ts index 37816b62..4c3192e6 100644 --- a/src/app/webapp-common/shared/entity-page/footer-items/enqueue-footer-item.ts +++ b/src/app/webapp-common/shared/entity-page/footer-items/enqueue-footer-item.ts @@ -15,9 +15,9 @@ export class EnqueueFooterItem extends ItemFooterModel { const enqueue = state.data[this.id]; return { - disable: enqueue.disable, + disable: enqueue?.disable, preventCurrentItem: state.selectionAllIsArchive, - description: this.menuItemText.transform(enqueue.available, 'Enqueue'), + description: this.menuItemText.transform(enqueue?.available, 'Enqueue'), disableDescription: state.selectionIsOnlyExamples ? 'Enqueue' : `You can only enqueue experiments with ‘Draft’/'Aborted' status` }; diff --git a/src/app/webapp-common/shared/entity-page/footer-items/selected-tags-footer-item.ts b/src/app/webapp-common/shared/entity-page/footer-items/selected-tags-footer-item.ts index dbffe6b5..d92b9921 100644 --- a/src/app/webapp-common/shared/entity-page/footer-items/selected-tags-footer-item.ts +++ b/src/app/webapp-common/shared/entity-page/footer-items/selected-tags-footer-item.ts @@ -23,7 +23,7 @@ export class SelectedTagsFooterItem extends ItemFooterModel { const tags = state.data[this.id]; return { disable: state.selectionAllHasExample, - description: this.menuItemText.transform(tags.selectedFiltered.length, 'Add Tag'), + description: this.menuItemText.transform(tags?.selectedFiltered?.length, 'Add Tag'), disableDescription: 'Tags', emitValue: tags.selectedFiltered, tags: selectionTags(state.selected), diff --git a/src/app/webapp-common/shared/experiment-graphs/experiment-graphs.component.html b/src/app/webapp-common/shared/experiment-graphs/experiment-graphs.component.html index aa52faf2..e72d7ff1 100644 --- a/src/app/webapp-common/shared/experiment-graphs/experiment-graphs.component.html +++ b/src/app/webapp-common/shared/experiment-graphs/experiment-graphs.component.html @@ -1,23 +1,33 @@
-

NO CHART DATA

+

NO CHART DATA

+ (createEmbedCode)="creatingEmbedCode(null, $event)" + > + + +
-
@@ -25,17 +35,18 @@
-
- + >
(); - @Output() createEmbedCode = new EventEmitter<{ metrics?: string[]; variants?: string[]; domRect: DOMRect }>(); + @Output() createEmbedCode = new EventEmitter<{ metrics?: string[]; variants?: string[]; originalObject?: string; xaxis?: ScalarKeyEnum; domRect: DOMRect }>(); @ViewChildren('metricGroup') allMetricGroups !: QueryList; @ViewChildren('singleGraphContainer') singleGraphs !: QueryList; @@ -139,7 +141,7 @@ export class ExperimentGraphsComponent implements OnDestroy { private el: ElementRef, private changeDetection: ChangeDetectorRef, private adminService: AdminService, - private store: Store, + private store: Store, private renderer: Renderer2 ) { @@ -300,7 +302,7 @@ export class ExperimentGraphsComponent implements OnDestroy { trackByFn = (index: number, item) => item; trackByIdFn = (index: number, item: ExtFrame) => - `${item.layout.title} ${(this.isDarkTheme ? '' : item.iter)}`; + `${item.layout.title} ${item.data?.length} ${(this.isDarkTheme ? '' : item.iter ?? '')}`; isWidthBigEnough() { return this.el.nativeElement.clientWidth > this.breakPoint; @@ -325,11 +327,11 @@ export class ExperimentGraphsComponent implements OnDestroy { this.width = width - 16 / this.graphsPerRow; this.height = this.maxUserHeight || this.height; if (!this.isGroupGraphs) { - this.allMetricGroups.forEach(metricGroup => this.renderer.setStyle(metricGroup.nativeElement, 'width', `${this.width}px`)); - this.allMetricGroups.forEach(metricGroup => this.renderer.setStyle(metricGroup.nativeElement, 'height', `${this.height}px`)); + this.allMetricGroups?.forEach(metricGroup => this.renderer.setStyle(metricGroup.nativeElement, 'width', `${this.width}px`)); + this.allMetricGroups?.forEach(metricGroup => this.renderer.setStyle(metricGroup.nativeElement, 'height', `${this.height}px`)); } else { - this.singleGraphs.forEach(singleGraph => this.renderer.setStyle(singleGraph.nativeElement, 'width', `${this.width}px`)); - this.singleGraphs.forEach(singleGraph => this.renderer.setStyle(singleGraph.nativeElement, 'height', `${this.height}px`)); + this.singleGraphs?.forEach(singleGraph => this.renderer.setStyle(singleGraph.nativeElement, 'width', `${this.width}px`)); + this.singleGraphs?.forEach(singleGraph => this.renderer.setStyle(singleGraph.nativeElement, 'height', `${this.height}px`)); } } this.prepareRedraw(); @@ -359,9 +361,9 @@ export class ExperimentGraphsComponent implements OnDestroy { if ($event.edges.bottom) { this.height = $event.rectangle.height; if (!this.isGroupGraphs) { - this.allMetricGroups.forEach(metricGroup => this.renderer.setStyle(metricGroup.nativeElement, 'height', `${this.height}px`)); + this.allMetricGroups?.forEach(metricGroup => this.renderer.setStyle(metricGroup.nativeElement, 'height', `${this.height}px`)); } else { - this.singleGraphs.forEach(singleGraph => this.renderer.setStyle(singleGraph.nativeElement, 'height', `${this.height}px`)); + this.singleGraphs?.forEach(singleGraph => this.renderer.setStyle(singleGraph.nativeElement, 'height', `${this.height}px`)); } } this.prepareRedraw(); @@ -423,9 +425,9 @@ export class ExperimentGraphsComponent implements OnDestroy { public generateIdentifier = (chartItem: any) => `${this.singleGraphidPrefix} ${this.experimentGraphidPrefix} ${chartItem.metric} ${chartItem.layout.title} ${chartItem.iter} ${chartItem.variant} ${(chartItem.layout.images && chartItem.layout.images[0]?.source)}`; - creatingEmbedCode(chartItem: any, domRect: DOMRect) { + creatingEmbedCode(chartItem: any, {domRect, xaxis}: {xaxis: ScalarKeyEnum; domRect: DOMRect}) { if (!chartItem) { - this.createEmbedCode.emit({domRect}); + this.createEmbedCode.emit({xaxis, domRect}); return; } @@ -434,10 +436,17 @@ export class ExperimentGraphsComponent implements OnDestroy { this.createEmbedCode.emit({ metrics: [chartItem.data[0].originalMetric ?? chartItem.metric.substring(0, chartItem.metric.lastIndexOf('/'))?.trim()], variants: [chartItem.data[0].name], + ...((xaxis || this.xAxisType) && {xaxis: xaxis ?? this.xAxisType}), domRect }); } else { - this.createEmbedCode.emit({metrics: [chartItem.metric], variants: chartItem.variants ?? [chartItem.variant], domRect}); + this.createEmbedCode.emit({ + metrics: [chartItem.metric], + variants: chartItem.variants ?? [chartItem.variant], + ...((xaxis || this.xAxisType) && {xaxis: xaxis ?? this.xAxisType}), + ...(chartItem.data.length < 2 && {originalObject: chartItem.task}), + ...(chartItem.data[0]?.seriesName && {seriesName: chartItem.data[0]?.seriesName}), + domRect}); } } } diff --git a/src/app/webapp-common/shared/experiment-graphs/graph-settings-bar/graph-settings-bar.component.html b/src/app/webapp-common/shared/experiment-graphs/graph-settings-bar/graph-settings-bar.component.html index 6f50257a..5aa0f46c 100644 --- a/src/app/webapp-common/shared/experiment-graphs/graph-settings-bar/graph-settings-bar.component.html +++ b/src/app/webapp-common/shared/experiment-graphs/graph-settings-bar/graph-settings-bar.component.html @@ -1,47 +1,58 @@ -
-
- Group by - - +
+
+ Group by + + {{type.name}}
-
- Horizontal Axis - +
+ Horizontal Axis + {{type.name}}
-
- Smoothing - - - - - - - +
+
+ Smoothing + + + +
+ + + + + + + {{smoothTypeOption.value}} + + + +
diff --git a/src/app/webapp-common/shared/experiment-graphs/graph-settings-bar/graph-settings-bar.component.scss b/src/app/webapp-common/shared/experiment-graphs/graph-settings-bar/graph-settings-bar.component.scss index c5c58a0b..51e5af89 100644 --- a/src/app/webapp-common/shared/experiment-graphs/graph-settings-bar/graph-settings-bar.component.scss +++ b/src/app/webapp-common/shared/experiment-graphs/graph-settings-bar/graph-settings-bar.component.scss @@ -19,16 +19,13 @@ .group-by-field, .axis-type-field { width: unset; - flex: 1 1 150px; - max-width: 150px; + max-width: 200px; } .label-text { display: flex; align-items: center; white-space: nowrap; - width: 95px; - min-width: 95px; padding-right: 12px; padding-bottom: 4px; } @@ -43,25 +40,44 @@ .smooth-options { width: 100%; + flex-wrap: wrap; + gap: 12px; + border-top: 1px solid #efefef; + padding-top: 12px; mat-slider { - flex: 1 1 80px; - min-width: unset; + min-width: 100px; } + .field { + flex-grow: 1; + } + + .smooth-type { + max-width: 200px; + margin-left: auto; + width: 100%; + } + } + + .smooth-input { + margin-left: 0; } } } .x-axis-options { display: flex; + flex-grow: 1; justify-content: space-between; + align-items: center; + gap: 12px; } .smooth-input { height: 36px; width: 74px; - margin-left: 6px; + margin-left: 16px; } .smooth-options { @@ -72,7 +88,7 @@ .label-text { padding-right: 6px; - margin: auto; + flex-grow: 1; &.axis-text-margin-left { margin-left: 24px; @@ -80,14 +96,11 @@ } .smoothing-text { - margin: auto 0; - margin-left: auto; - padding-right: 6px; + padding-right: 16px; } .smooth-options i { - margin: auto 0; - margin-left: 25px; + margin: auto 6px auto 25px; } mat-form-field { @@ -99,6 +112,15 @@ mat-form-field { width: 130px; } +.smooth-type { + margin-left: 6px; + width: 200px; +} + +.axis-type-field { + width: 170px; +} + .group-by-field { height: 36px; width: 140px; @@ -108,3 +130,23 @@ mat-slider { min-width: 121px; } +.field { + align-items: center; +} + +.short-mode { + .field { + align-items: unset; + flex-direction: column; + + .label-text { + margin-left: 0; + font-size: 12px; + } + } + + .smooth-input, + .smooth-type { + margin-top: 18px; + } +} diff --git a/src/app/webapp-common/shared/experiment-graphs/graph-settings-bar/graph-settings-bar.component.ts b/src/app/webapp-common/shared/experiment-graphs/graph-settings-bar/graph-settings-bar.component.ts index c4467bf0..058ce3c5 100644 --- a/src/app/webapp-common/shared/experiment-graphs/graph-settings-bar/graph-settings-bar.component.ts +++ b/src/app/webapp-common/shared/experiment-graphs/graph-settings-bar/graph-settings-bar.component.ts @@ -1,7 +1,8 @@ -import {Component, EventEmitter, Input, Output} from '@angular/core'; +import {ChangeDetectorRef, Component, ElementRef, EventEmitter, HostListener, inject, Input, Output} from '@angular/core'; import {ScalarKeyEnum} from '~/business-logic/model/events/scalarKeyEnum'; import {MatSelectChange} from '@angular/material/select'; import {GroupByCharts} from '@common/experiments/reducers/experiment-output.reducer'; +import {SmoothTypeEnum, smoothTypeEnum} from '@common/shared/single-graph/single-graph.utils'; @Component({ selector: 'sm-graph-settings-bar', @@ -11,17 +12,32 @@ import {GroupByCharts} from '@common/experiments/reducers/experiment-output.redu export class GraphSettingsBarComponent { readonly scalarKeyEnum = ScalarKeyEnum; readonly round = Math.round; + protected readonly smoothTypeEnum = smoothTypeEnum; + private el = inject(ElementRef); + private cdr = inject(ChangeDetectorRef); + public shortMode: boolean; + + @Input() set splitSize(splitSize: number) { + this.shortMode = this.el.nativeElement.clientWidth < 1130; + } @Input() smoothWeight: number; + @Input() smoothType: SmoothTypeEnum; @Input() xAxisType: ScalarKeyEnum = ScalarKeyEnum.Iter; @Input() groupBy: GroupByCharts = 'metric'; - @Input() groupByOptions: {name: string; value: GroupByCharts }[]; + @Input() groupByOptions: { name: string; value: GroupByCharts }[]; @Input() verticalLayout: boolean = false; - @Output() changeWeight = new EventEmitter(); + @Output() changeWeight = new EventEmitter(); + @Output() changeSmoothType = new EventEmitter(); @Output() changeXAxisType = new EventEmitter(); @Output() changeGroupBy = new EventEmitter(); @Output() toggleSettings = new EventEmitter(); + @HostListener('window:resize') + onResize() { + this.shortMode = this.el.nativeElement.clientWidth < 1130; + } + xAxisTypeOption = [ { name: 'Iterations', @@ -44,4 +60,35 @@ export class GraphSettingsBarComponent { groupByChanged(key: MatSelectChange) { this.changeGroupBy.emit(key.value); } + + selectSmoothType($event: MatSelectChange) { + this.changeWeight.emit([smoothTypeEnum.exponential, smoothTypeEnum.any].includes($event.value) ? 0 : 10); + this.changeSmoothType.emit($event.value); + } + + trimToLimits(value: number) { + if (value === 0) { + return; + } + if (value > (this.smoothType === smoothTypeEnum.exponential ? 0.999 : 100) || value < (this.smoothType === smoothTypeEnum.exponential ? 0 : 1)) { + this.smoothWeight = null; + } + setTimeout(() => { + if (this.smoothType === smoothTypeEnum.exponential) { + if (value > 0.999) { + this.smoothWeight = 0.999; + } else if (value < 0) { + this.smoothWeight = 0; + } + } else { + if (value > 100) { + this.smoothWeight = 100; + } else if (value < 1) { + this.smoothWeight = 1; + } + } + this.cdr.detectChanges(); + this.changeWeight.emit(this.smoothWeight); + }); + } } diff --git a/src/app/webapp-common/shared/experiment-type-icon-label/experiment-type-icon-label.component.ts b/src/app/webapp-common/shared/experiment-type-icon-label/experiment-type-icon-label.component.ts index 0d64f8d2..bc7be2da 100644 --- a/src/app/webapp-common/shared/experiment-type-icon-label/experiment-type-icon-label.component.ts +++ b/src/app/webapp-common/shared/experiment-type-icon-label/experiment-type-icon-label.component.ts @@ -10,7 +10,7 @@ import {TaskTypeEnum} from '~/business-logic/model/tasks/taskTypeEnum'; }) export class ExperimentTypeIconLabelComponent{ @Input() type: TaskTypeEnum; - @Input() iconClass = 'md'; + @Input() iconClass = ''; @Input() showLabel = true; constructor() { } } diff --git a/src/app/webapp-common/shared/guards/account-administration.guard.ts b/src/app/webapp-common/shared/guards/account-administration.guard.ts index 2a4ef77a..f4ad02d3 100644 --- a/src/app/webapp-common/shared/guards/account-administration.guard.ts +++ b/src/app/webapp-common/shared/guards/account-administration.guard.ts @@ -1,27 +1,19 @@ -import { Injectable } from '@angular/core'; -import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router'; -import {combineLatest, Observable} from 'rxjs'; +import {inject} from '@angular/core'; +import {CanActivateFn} from '@angular/router'; +import {combineLatest} from 'rxjs'; import {ConfigurationService} from '../services/configuration.service'; import {filter, map} from 'rxjs/operators'; import {Store} from '@ngrx/store'; import {selectCurrentUser} from '../../core/reducers/users-reducer'; -@Injectable({ - providedIn: 'root' -}) -export class AccountAdministrationGuard implements CanActivate { - constructor(private configService: ConfigurationService, - private store: Store) { - } - canActivate( - route: ActivatedRouteSnapshot, - state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { +export const accountAdministrationGuard: CanActivateFn = () => { + const configService = inject(ConfigurationService); + const store = inject(Store); - return combineLatest([ - this.configService.globalEnvironmentObservable, - this.store.select(selectCurrentUser).pipe(filter(user => !!user)) - ]).pipe( - map( ([env, currentUser]) => env.accountAdministration && currentUser.role === 'admin') - ); - } -} + return combineLatest([ + configService.globalEnvironmentObservable, + store.select(selectCurrentUser).pipe(filter(user => !!user)) + ]).pipe( + map( ([env, currentUser]) => env.accountAdministration && currentUser.role === 'admin') + ); +}; diff --git a/src/app/webapp-common/shared/guards/auth.guard.ts b/src/app/webapp-common/shared/guards/auth.guard.ts deleted file mode 100755 index 16878890..00000000 --- a/src/app/webapp-common/shared/guards/auth.guard.ts +++ /dev/null @@ -1,24 +0,0 @@ -// src/app/auth/auth-guard.service.ts -import {Injectable} from '@angular/core'; -import {Router, CanActivate} from '@angular/router'; -import {Store} from '@ngrx/store'; -import {selectCurrentUser} from '../../core/reducers/users-reducer'; -import {User} from '../../../business-logic/model/users/user'; - -@Injectable() -export class AuthGuard implements CanActivate { - private user: User; - - constructor( - public router: Router, - public store: Store) { - this.store.select(selectCurrentUser).subscribe(user => {this.user = user; }); - } - - canActivate(): boolean { - if (!this.user) { - this.router.navigateByUrl('/login'); - } - return !!this.user; - } -} diff --git a/src/app/webapp-common/shared/guards/general-leaving-before-save-alert.guard.ts b/src/app/webapp-common/shared/guards/general-leaving-before-save-alert.guard.ts index 66840aba..af039d93 100644 --- a/src/app/webapp-common/shared/guards/general-leaving-before-save-alert.guard.ts +++ b/src/app/webapp-common/shared/guards/general-leaving-before-save-alert.guard.ts @@ -1,41 +1,24 @@ -import {Injectable} from '@angular/core'; -import {CanDeactivate} from '@angular/router'; -import {Observable} from 'rxjs'; -import {MatDialog, MatDialogRef} from '@angular/material/dialog'; +import {inject} from '@angular/core'; +import {CanDeactivateFn} from '@angular/router'; +import {MatDialog} from '@angular/material/dialog'; import {ConfirmDialogComponent} from '../ui-components/overlay/confirm-dialog/confirm-dialog.component'; +import {map} from 'rxjs/operators'; -@Injectable() -export class GeneralLeavingBeforeSaveAlertGuard implements CanDeactivate { - constructor(private dialog: MatDialog) { +export const generalLeavingBeforeSaveAlertGuard: CanDeactivateFn = (component) => { + const dialog = inject(MatDialog); + + if (!component.isDirty) { + return true; } - public canDeactivate(component: any): Observable | Promise | boolean { - - if (!component.isDirty) { - return true; + return dialog.open(ConfirmDialogComponent, { + data: { + title: 'Attention', + body: 'You have unsaved changes. Do you want to stay on this page or leave without saving?', + yes: 'Leave', + no: 'Stay', + iconClass: 'i-alert', } - - - return Observable.create(observer => { - const confirmDialogRef: MatDialogRef = this.dialog.open(ConfirmDialogComponent, { - data: { - title: 'Attention', - body: 'You have unsaved changes. Do you want to stay on this page or leave without saving?', - yes: 'Leave', - no: 'Stay', - iconClass: 'i-alert', - } - }); - - confirmDialogRef.afterClosed().subscribe((confirmed) => { - if (confirmed) { - observer.next(true); - observer.complete(); - } else { - observer.next(false); - observer.complete(); - } - }); - }); - } -} + }).afterClosed() + .pipe(map(leave => leave)); +}; diff --git a/src/app/webapp-common/shared/guards/leaving-before-save-alert.guard.ts b/src/app/webapp-common/shared/guards/leaving-before-save-alert.guard.ts index 0b50ac9f..b5c51e92 100755 --- a/src/app/webapp-common/shared/guards/leaving-before-save-alert.guard.ts +++ b/src/app/webapp-common/shared/guards/leaving-before-save-alert.guard.ts @@ -1,52 +1,40 @@ -import {Injectable} from '@angular/core'; -import {ActivatedRouteSnapshot, CanDeactivate, RouterStateSnapshot} from '@angular/router'; -import {Observable} from 'rxjs'; -import {MatDialog, MatDialogRef} from '@angular/material/dialog'; +import {inject} from '@angular/core'; +import { + ActivatedRouteSnapshot, + CanDeactivateFn, + RouterStateSnapshot +} from '@angular/router'; +import {of, switchMap} from 'rxjs'; +import {MatDialog} from '@angular/material/dialog'; import {ConfirmDialogComponent} from '../ui-components/overlay/confirm-dialog/confirm-dialog.component'; import {Store} from '@ngrx/store'; -import {GuardBase} from '~/shared/guards/guard-base'; +import {debounceTime, map} from 'rxjs/operators'; +import {MemoizedSelector} from '@ngrx/store/src/selector'; -@Injectable() -export class LeavingBeforeSaveAlertGuard extends GuardBase implements CanDeactivate { - private inEditMode: boolean; - constructor(private dialog: MatDialog, private store: Store) { - super(store); - this.inEditMode$.subscribe(inEditModes => { - this.inEditMode = inEditModes.includes(true); - }); - } - public canDeactivate(component: any, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState?: RouterStateSnapshot): Observable | Promise | boolean { +export const leavingBeforeSaveAlertGuard = (inEditSelector: MemoizedSelector): CanDeactivateFn => + (component, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState: RouterStateSnapshot) => { + const store = inject(Store); + const dialog = inject(MatDialog); - const unGuard = nextState?.root?.queryParams?.unGuard; - if (unGuard === 'true') { - return true; - } - if (!this.inEditMode) { - return true; - } - - return Observable.create(observer => { - const confirmDialogRef: MatDialogRef = this.dialog.open(ConfirmDialogComponent, { - data: { - title : 'Attention', - body : 'You have unsaved changes. Do you want to stay on this page or leave without saving?', - yes : 'Leave', - no : 'Stay', - iconClass: 'i-alert', - } - }); - - confirmDialogRef.afterClosed().subscribe((confirmed) => { - if (confirmed) { - observer.next(true); - observer.complete(); - } else { - observer.next(false); - observer.complete(); - } - }); - }); - } -} + return store.select(inEditSelector) + .pipe( + debounceTime(0), + switchMap(inEditMode => { + const unGuard = nextState?.root?.queryParams?.unGuard; + if (unGuard === 'true' || !inEditMode) { + return of(true); + } + return dialog.open(ConfirmDialogComponent, { + data: { + title : 'Attention', + body : 'You have unsaved changes. Do you want to stay on this page or leave without saving?', + yes : 'Leave', + no : 'Stay', + iconClass: 'i-alert', + } + }).afterClosed().pipe(map((leave)=> !!leave)); + }), + ); + }; diff --git a/src/app/webapp-common/shared/guards/project-redirect.guard.ts b/src/app/webapp-common/shared/guards/project-redirect.guard.ts index f6999781..89987404 100644 --- a/src/app/webapp-common/shared/guards/project-redirect.guard.ts +++ b/src/app/webapp-common/shared/guards/project-redirect.guard.ts @@ -1,28 +1,16 @@ -import {Injectable} from '@angular/core'; -import {ActivatedRouteSnapshot, CanActivate, Router, UrlTree} from '@angular/router'; -import {Observable} from 'rxjs'; +import {inject} from '@angular/core'; +import {ActivatedRouteSnapshot, CanActivateFn, Router} from '@angular/router'; import {Store} from '@ngrx/store'; import {selectSelectedMetricVariantForCurrProject, selectSelectedProject} from '../../core/reducers/projects.reducer'; -import {Project} from '~/business-logic/model/projects/project'; import {filter, map, tap, withLatestFrom} from 'rxjs/operators'; import {setSelectedProjectId} from '@common/core/actions/projects.actions'; -@Injectable({ - providedIn: 'root' -}) -export class ProjectRedirectGuardGuard implements CanActivate { - private selectedProject$: Observable; - - constructor(private store: Store, private router: Router) { - this.selectedProject$ = this.store.select(selectSelectedProject); - } - - canActivate(route: ActivatedRouteSnapshot): Observable | Promise | boolean | UrlTree { - return this.selectedProject$.pipe( - tap(project => project === null && this.store.dispatch(setSelectedProjectId({projectId: route.params.projectId}))), - filter(project => project?.id === route.params.projectId), - withLatestFrom(this.store.select(selectSelectedMetricVariantForCurrProject)), - map(([project, metVar]) => this.router.parseUrl(`projects/${project.id}/${(project.description || metVar) ? 'overview' : 'experiments'}`))); - } - -} +export const projectRedirectGuardGuard: CanActivateFn = (route: ActivatedRouteSnapshot) => { + const store = inject(Store); + const router = inject(Router); + return store.select(selectSelectedProject).pipe( + tap(project => project === null && store.dispatch(setSelectedProjectId({projectId: route.params.projectId}))), + filter(project => project?.id === route.params.projectId), + withLatestFrom(store.select(selectSelectedMetricVariantForCurrProject)), + map(([project, metVar]) => router.parseUrl(`projects/${project.id}/${(project.description || metVar) ? 'overview' : 'experiments'}`))); +}; diff --git a/src/app/webapp-common/shared/pipes/hide-redacted-arguments.pipe.ts b/src/app/webapp-common/shared/pipes/hide-redacted-arguments.pipe.ts index cd92db4e..b791f6bb 100644 --- a/src/app/webapp-common/shared/pipes/hide-redacted-arguments.pipe.ts +++ b/src/app/webapp-common/shared/pipes/hide-redacted-arguments.pipe.ts @@ -1,5 +1,7 @@ import {Pipe, PipeTransform} from '@angular/core'; +const SECRET = '****************'; + @Pipe({ name: 'hideRedactedArguments', pure: true @@ -7,17 +9,18 @@ import {Pipe, PipeTransform} from '@angular/core'; export class HideRedactedArgumentsPipe implements PipeTransform { transform(value: string, redactedArguments: { key: string }[]): string { - if (!value || typeof (value) !== 'string' || !redactedArguments) { + if (!value || typeof (value) !== 'string' || !redactedArguments || redactedArguments?.length < 1) { return value; } - redactedArguments.forEach(redactedArgument => { - if(value.includes(redactedArgument.key+ ' =')||value.includes(redactedArgument.key+ '=') ){ - const splitValue = value.split('='); - const redactedArgumentValue = splitValue[splitValue.findIndex(val=> val.trim().endsWith(redactedArgument.key))+1].split(' ')[0]; - value = value.replace(redactedArgumentValue.trim(), '**************************'); - } - }); - return value; + const reg = new RegExp(redactedArguments.map(arg => `(${arg.key}\\s*=)`).join('|')); + return value.trim() + .split(/\s+(?!=)/) + .map(section => { + if (reg.test(section)) { + return `${section.split(/\s*=/)[0]}=${SECRET}`; + } + return section; + }) + .join(' '); } - } diff --git a/src/app/webapp-common/shared/pipes/show-selected-first.pipe.ts b/src/app/webapp-common/shared/pipes/show-selected-first.pipe.ts index c9bd9d11..2acf5429 100644 --- a/src/app/webapp-common/shared/pipes/show-selected-first.pipe.ts +++ b/src/app/webapp-common/shared/pipes/show-selected-first.pipe.ts @@ -1,6 +1,6 @@ import {Pipe, PipeTransform} from '@angular/core'; -export function sortByArr(a, b, colsOrder=[]) { +export function sortByArr(a, b, colsOrder = []) { const indexOfA = colsOrder.indexOf(a); const indexOfB = colsOrder.indexOf(b); return ((indexOfA >= 0) ? indexOfA : 99) - ((indexOfB >= 0) ? indexOfB : 99); diff --git a/src/app/webapp-common/shared/project-dialog/create-new-project-form/create-new-project-form.component.html b/src/app/webapp-common/shared/project-dialog/create-new-project-form/create-new-project-form.component.html index 50645d8d..885c93ed 100755 --- a/src/app/webapp-common/shared/project-dialog/create-new-project-form/create-new-project-form.component.html +++ b/src/app/webapp-common/shared/project-dialog/create-new-project-form/create-new-project-form.component.html @@ -5,6 +5,7 @@ *Project name in this path already exists. *Project name should contain more than 3 characters. *Project name can't contain only spaces. + *Project name can't contain slash (/)
diff --git a/src/app/webapp-common/shared/project-dialog/create-new-project-form/create-new-project-form.component.ts b/src/app/webapp-common/shared/project-dialog/create-new-project-form/create-new-project-form.component.ts index f37ec7f2..f44cc3c2 100755 --- a/src/app/webapp-common/shared/project-dialog/create-new-project-form/create-new-project-form.component.ts +++ b/src/app/webapp-common/shared/project-dialog/create-new-project-form/create-new-project-form.component.ts @@ -17,7 +17,7 @@ export class CreateNewProjectFormComponent implements OnChanges, OnInit, OnDestr public rootFiltered: boolean; public readonly projectsRoot = 'Projects root'; public projectsNames: Array = null; - public outputDestPattern = `${URI_REGEX.S3_WITH_BUCKET}$|${URI_REGEX.S3_WITH_BUCKET_AND_HOST}$|${URI_REGEX.FILE}$|${URI_REGEX.NON_AWS_S3}$|${URI_REGEX.GS_WITH_BUCKET}$|${URI_REGEX.GS_WITH_BUCKET_AND_HOST}$`; + public outputDestPattern = `${URI_REGEX.S3_WITH_BUCKET}$|${URI_REGEX.S3_WITH_BUCKET_AND_HOST}$|${URI_REGEX.FILE}$|${URI_REGEX.NON_AWS_S3}$|${URI_REGEX.GS_WITH_BUCKET}$|${URI_REGEX.GS_WITH_BUCKET_AND_HOST}|${URI_REGEX.AZURE_WITH_BUCKET}`; public project = { name: '', description: '', diff --git a/src/app/webapp-common/shared/project-dialog/project-dialog.actions.ts b/src/app/webapp-common/shared/project-dialog/project-dialog.actions.ts index 8b6d2780..691e593c 100755 --- a/src/app/webapp-common/shared/project-dialog/project-dialog.actions.ts +++ b/src/app/webapp-common/shared/project-dialog/project-dialog.actions.ts @@ -1,51 +1,12 @@ -import {ISmAction} from '../../core/models/actions'; import {CreationStatusEnum} from './project-dialog.reducer'; import {ProjectsCreateRequest} from '~/business-logic/model/projects/projectsCreateRequest'; import {createAction, props} from '@ngrx/store'; -const CREATE_PROJECT_DIALOG_PREFIX = 'CREATE_PROJECT_DIALOG_'; - -export const CREATE_PROJECT_ACTIONS = { - RESET_STATE : CREATE_PROJECT_DIALOG_PREFIX + 'RESET_STATE', - CREATE_NEW_PROJECT : CREATE_PROJECT_DIALOG_PREFIX + 'CREATE_NEW_PROJECT', - SET_CREATION_STATUS : CREATE_PROJECT_DIALOG_PREFIX + 'SET_CREATION_STATUS', - NAVIGATE_TO_NEW_PROJECT: CREATE_PROJECT_DIALOG_PREFIX + 'NAVIGATE_TO_NEW_PROJECT' -}; - - -export class ResetState implements ISmAction { - readonly type = CREATE_PROJECT_ACTIONS.RESET_STATE; - - constructor() { - } -} - - - -export class CreateNewProject implements ISmAction { - readonly type = CREATE_PROJECT_ACTIONS.CREATE_NEW_PROJECT; - - constructor(public payload: ProjectsCreateRequest) { - } -} - -export class NavigateToNewProject implements ISmAction { - readonly type = CREATE_PROJECT_ACTIONS.NAVIGATE_TO_NEW_PROJECT; - - constructor(public payload: string) { - this.payload = payload; - } -} -export class SetNewProjectCreationStatus implements ISmAction { - readonly type = CREATE_PROJECT_ACTIONS.SET_CREATION_STATUS; - public payload: { creationStatus: CreationStatusEnum }; - - constructor(creationStatus: CreationStatusEnum) { - this.payload = {creationStatus}; - } -} - +export const resetState = createAction('[CREATE_PROJECT_DIALOG] RESET_STATE'); +export const createNewProject = createAction('[CREATE_PROJECT_DIALOG] CREATE_NEW_PROJECT', props<{req: ProjectsCreateRequest}>()); +export const navigateToNewProject = createAction('[CREATE_PROJECT_DIALOG] NAVIGATE_TO_NEW_PROJECT', props<{id: string}>()); +export const setCreationStatus = createAction('[CREATE_PROJECT_DIALOG] SET_CREATION_STATUS', props<{status: CreationStatusEnum}>()); export const moveProject = createAction( - CREATE_PROJECT_DIALOG_PREFIX + 'MOVE_PROJECT', + '[CREATE_PROJECT_DIALOG] MOVE_PROJECT', props<{project?: string; new_location?: string; name: string; fromName: string; toName: string; projectName: string}>() ); diff --git a/src/app/webapp-common/shared/project-dialog/project-dialog.component.ts b/src/app/webapp-common/shared/project-dialog/project-dialog.component.ts index f459b687..e4e793a3 100755 --- a/src/app/webapp-common/shared/project-dialog/project-dialog.component.ts +++ b/src/app/webapp-common/shared/project-dialog/project-dialog.component.ts @@ -35,8 +35,11 @@ export class ProjectDialogComponent implements OnInit, OnDestroy { }; - constructor(private store: Store, private matDialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) data: { project: Project; mode: string }) { + constructor( + private store: Store, + private matDialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) data: { project: Project; mode: string } + ) { this.baseProject = data.project; this.mode = data.mode; this.projects$ = this.store.select(selectTablesFilterProjectsOptions); @@ -52,13 +55,13 @@ export class ProjectDialogComponent implements OnInit, OnDestroy { } ngOnDestroy(): void { - this.store.dispatch(new createNewProjectActions.ResetState()); + this.store.dispatch(createNewProjectActions.resetState()); this.creationStatusSubscription.unsubscribe(); } public createProject(projectForm) { const project = this.convertFormToProject(projectForm); - this.store.dispatch(new createNewProjectActions.CreateNewProject(project)); + this.store.dispatch(createNewProjectActions.createNewProject({req: project})); } moveProject(event: {location: string; name: string; fromName: string; toName: string; projectName: string}) { diff --git a/src/app/webapp-common/shared/project-dialog/project-dialog.effects.ts b/src/app/webapp-common/shared/project-dialog/project-dialog.effects.ts index 21f33df0..51c4f563 100755 --- a/src/app/webapp-common/shared/project-dialog/project-dialog.effects.ts +++ b/src/app/webapp-common/shared/project-dialog/project-dialog.effects.ts @@ -1,5 +1,4 @@ -import * as createNewProjectActions from './project-dialog.actions'; -import {CREATE_PROJECT_ACTIONS} from './project-dialog.actions'; +import * as newProjectActions from './project-dialog.actions'; import {activeLoader, addMessage, deactivateLoader} from '../../core/actions/layout.actions'; import {Actions, createEffect, ofType} from '@ngrx/effects'; import {ApiProjectsService} from '~/business-logic/api-services/projects.service'; @@ -21,54 +20,54 @@ export class ProjectDialogEffects { private actions: Actions, private projectsApiService: ApiProjectsService, private router: Router, - private store: Store, + private store: Store, private shortProjectName: ShortProjectNamePipe, private projectLocation: ProjectLocationPipe ) { } activeLoader = createEffect(() => this.actions.pipe( - ofType(CREATE_PROJECT_ACTIONS.CREATE_NEW_PROJECT), + ofType(newProjectActions.createNewProject), map(action => activeLoader(action.type)) )); navigateToNewProject = createEffect(() => this.actions.pipe( - ofType(CREATE_PROJECT_ACTIONS.NAVIGATE_TO_NEW_PROJECT), - filter(action => !!action.payload), - map((action) => this.router.navigateByUrl(`projects/${action.payload}`)) + ofType(newProjectActions.navigateToNewProject), + filter(action => !!action.id), + map((action) => this.router.navigateByUrl(`projects/${action.id}`)) ), {dispatch: false}); createProject = createEffect(() => this.actions.pipe( - ofType(CREATE_PROJECT_ACTIONS.CREATE_NEW_PROJECT), + ofType(newProjectActions.createNewProject), withLatestFrom(this.store.select(selectActiveWorkspace)), - switchMap(([action]) => this.projectsApiService.projectsCreate(action.payload) + switchMap(([action]) => this.projectsApiService.projectsCreate({...action.req}) .pipe( mergeMap(() => [ deactivateLoader(action.type), - new createNewProjectActions.SetNewProjectCreationStatus(CREATION_STATUS.SUCCESS), + newProjectActions.setCreationStatus({status: CREATION_STATUS.SUCCESS}), getAllSystemProjects(), - addMessage(MESSAGES_SEVERITY.SUCCESS, `${this.shortProjectName.transform(action.payload.name)} has been created successfully in ${this.projectLocation.transform(action.payload.name)}`), + addMessage(MESSAGES_SEVERITY.SUCCESS, `${this.shortProjectName.transform(action.req.name)} has been created successfully in ${this.projectLocation.transform(action.req.name)}`), ] ), - catchError(error => [deactivateLoader(action.type), requestFailed(error), addMessage(MESSAGES_SEVERITY.ERROR, 'Project Created Failed'), new createNewProjectActions.SetNewProjectCreationStatus(CREATION_STATUS.FAILED)]) + catchError(error => [deactivateLoader(action.type), requestFailed(error), addMessage(MESSAGES_SEVERITY.ERROR, 'Project Created Failed'), newProjectActions.setCreationStatus({status: CREATION_STATUS.FAILED})]) ) ) )); moveProject = createEffect(() => this.actions.pipe( - ofType(createNewProjectActions.moveProject), + ofType(newProjectActions.moveProject), withLatestFrom(this.store.select(selectActiveWorkspace)), // eslint-disable-next-line @typescript-eslint/naming-convention switchMap(([action]) => this.projectsApiService.projectsMove({project: action.project, new_location: action.new_location}) .pipe( mergeMap(() => [ deactivateLoader(action.type), - new createNewProjectActions.SetNewProjectCreationStatus(CREATION_STATUS.SUCCESS), + newProjectActions.setCreationStatus({status: CREATION_STATUS.SUCCESS}), getAllSystemProjects(), addMessage(MESSAGES_SEVERITY.SUCCESS, `${action.projectName} has been moved from ${action.fromName} to ${action.toName}`), ] ), - catchError(error => [deactivateLoader(action.type), requestFailed(error), addMessage(MESSAGES_SEVERITY.ERROR, 'Project Move Failed'), new createNewProjectActions.SetNewProjectCreationStatus(CREATION_STATUS.FAILED)]) + catchError(error => [deactivateLoader(action.type), requestFailed(error), addMessage(MESSAGES_SEVERITY.ERROR, 'Project Move Failed'), newProjectActions.setCreationStatus({status: CREATION_STATUS.FAILED})]) ) ) )); diff --git a/src/app/webapp-common/shared/project-dialog/project-dialog.reducer.ts b/src/app/webapp-common/shared/project-dialog/project-dialog.reducer.ts index 4af283fa..dc6f9ebc 100755 --- a/src/app/webapp-common/shared/project-dialog/project-dialog.reducer.ts +++ b/src/app/webapp-common/shared/project-dialog/project-dialog.reducer.ts @@ -1,5 +1,5 @@ -import {createFeatureSelector, createSelector} from '@ngrx/store'; -import {CREATE_PROJECT_ACTIONS} from './project-dialog.actions'; +import {createFeatureSelector, createReducer, createSelector, on} from '@ngrx/store'; +import {setCreationStatus, resetState} from './project-dialog.actions'; export type CreationStatusEnum = 'success' | 'failed' | 'inProgress'; export const CREATION_STATUS = { @@ -19,13 +19,8 @@ const createProjectInitState: ICreateProjectDialog = { export const selectCreateProjectDialog = createFeatureSelector('projectCreateDialog'); export const selectCreationStatus = createSelector(selectCreateProjectDialog, (state): CreationStatusEnum => state.creationStatus); -export function projectDialogReducer(state: ICreateProjectDialog = createProjectInitState, action): ICreateProjectDialog { - switch (action.type) { - case CREATE_PROJECT_ACTIONS.SET_CREATION_STATUS: - return {...state, creationStatus: action.payload.creationStatus}; - case CREATE_PROJECT_ACTIONS.RESET_STATE: - return {...createProjectInitState}; - default: - return state; - } -} +export const projectDialogReducer = createReducer( + createProjectInitState, + on(setCreationStatus, (state, action): ICreateProjectDialog => ({...state, creationStatus: action.status})), + on(resetState, (): ICreateProjectDialog => ({...createProjectInitState})), +); diff --git a/src/app/webapp-common/shared/queue-create-dialog/create-new-queue-form/create-new-queue-form.component.html b/src/app/webapp-common/shared/queue-create-dialog/create-new-queue-form/create-new-queue-form.component.html index 39b052aa..c6f37f4a 100755 --- a/src/app/webapp-common/shared/queue-create-dialog/create-new-queue-form/create-new-queue-form.component.html +++ b/src/app/webapp-common/shared/queue-create-dialog/create-new-queue-form/create-new-queue-form.component.html @@ -1,4 +1,4 @@ - +
Required @@ -23,4 +23,4 @@
- + diff --git a/src/app/webapp-common/shared/queue-create-dialog/queue-create-dialog.actions.ts b/src/app/webapp-common/shared/queue-create-dialog/queue-create-dialog.actions.ts index 476b82ca..2107092a 100755 --- a/src/app/webapp-common/shared/queue-create-dialog/queue-create-dialog.actions.ts +++ b/src/app/webapp-common/shared/queue-create-dialog/queue-create-dialog.actions.ts @@ -1,63 +1,12 @@ import {CreationStatusEnum} from './queue-create-dialog.reducer'; -import {ISmAction} from '../../core/models/actions'; -import {Queue} from '../../../business-logic/model/queues/queue'; -import {QueuesUpdateRequest} from '../../../business-logic/model/queues/queuesUpdateRequest'; -import {QueuesCreateRequest} from '../../../business-logic/model/queues/queuesCreateRequest'; +import {Queue} from '~/business-logic/model/queues/queue'; +import {QueuesUpdateRequest} from '~/business-logic/model/queues/queuesUpdateRequest'; +import {QueuesCreateRequest} from '~/business-logic/model/queues/queuesCreateRequest'; +import {createAction, props} from '@ngrx/store'; -const CREATE_QUEUE_DIALOG_PREFIX = 'CREATE_QUEUE_DIALOG_'; - -export const CREATE_QUEUE_ACTIONS = { - GET_QUEUES : CREATE_QUEUE_DIALOG_PREFIX + 'GET_QUEUES', - SET_QUEUES : CREATE_QUEUE_DIALOG_PREFIX + 'SET_QUEUES', - RESET_STATE : CREATE_QUEUE_DIALOG_PREFIX + 'RESET_STATE', - CREATE_NEW_QUEUE : CREATE_QUEUE_DIALOG_PREFIX + 'CREATE_NEW_QUEUE', - UPDATE_QUEUE : CREATE_QUEUE_DIALOG_PREFIX + 'UPDATE_QUEUE', - SET_CREATION_STATUS: CREATE_QUEUE_DIALOG_PREFIX + 'SET_CREATION_STATUS', -}; - - -export class GetQueues implements ISmAction { - readonly type = CREATE_QUEUE_ACTIONS.GET_QUEUES; - - constructor() { - } -} - -export class SetQueues implements ISmAction { - readonly type = CREATE_QUEUE_ACTIONS.SET_QUEUES; - public payload: { queues: Array }; - - constructor(queues: Array) { - this.payload = {queues}; - } -} - -export class ResetState implements ISmAction { - readonly type = CREATE_QUEUE_ACTIONS.RESET_STATE; - - constructor() { - } -} - -export class CreateNewQueue implements ISmAction { - readonly type = CREATE_QUEUE_ACTIONS.CREATE_NEW_QUEUE; - - constructor(public payload: QueuesCreateRequest) { - } -} - -export class UpdateQueue implements ISmAction { - readonly type = CREATE_QUEUE_ACTIONS.UPDATE_QUEUE; - - constructor(public payload: QueuesUpdateRequest) { - } -} - -export class SetNewQueueCreationStatus implements ISmAction { - readonly type = CREATE_QUEUE_ACTIONS.SET_CREATION_STATUS; - public payload: { creationStatus: CreationStatusEnum }; - - constructor(creationStatus: CreationStatusEnum) { - this.payload = {creationStatus}; - } -} +export const getQueues = createAction('[CREATE_QUEUE_DIALOG] GET_QUEUES'); +export const setQueues = createAction('[CREATE_QUEUE_DIALOG] SET_QUEUES', props<{ queues: Queue[] }>()); +export const resetState = createAction('[CREATE_QUEUE_DIALOG] RESET_STATE'); +export const createNewQueue = createAction('[CREATE_QUEUE_DIALOG] CREATE_NEW_QUEUE', props()); +export const updateQueue = createAction('[CREATE_QUEUE_DIALOG] UPDATE_QUEUE', props<{ queue: QueuesUpdateRequest }>()); +export const setCreationStatus = createAction('[CREATE_QUEUE_DIALOG] SET_CREATION_STATUS', props<{ status: CreationStatusEnum }>()); diff --git a/src/app/webapp-common/shared/queue-create-dialog/queue-create-dialog.component.ts b/src/app/webapp-common/shared/queue-create-dialog/queue-create-dialog.component.ts index edb5841e..3cb74357 100755 --- a/src/app/webapp-common/shared/queue-create-dialog/queue-create-dialog.component.ts +++ b/src/app/webapp-common/shared/queue-create-dialog/queue-create-dialog.component.ts @@ -18,7 +18,7 @@ export class QueueCreateDialogComponent implements OnInit, OnDestroy { private editMode = false; public queue = {name: null, id: null}; - constructor(private store: Store, private matDialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data) { + constructor(private store: Store, private matDialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data) { if (data) { this.queue = data; this.editMode = true; @@ -27,7 +27,7 @@ export class QueueCreateDialogComponent implements OnInit, OnDestroy { } ngOnInit(): void { - this.store.dispatch(new createNewQueueActions.GetQueues()); + this.store.dispatch(createNewQueueActions.getQueues()); this.creationStatusSubscription = this.store.select(createQueueSelectors.selectCreationStatus).subscribe(status => { if (status === CREATION_STATUS.SUCCESS) { return this.matDialogRef.close(true); @@ -36,15 +36,15 @@ export class QueueCreateDialogComponent implements OnInit, OnDestroy { } ngOnDestroy(): void { - this.store.dispatch(new createNewQueueActions.ResetState()); + this.store.dispatch(createNewQueueActions.resetState()); this.creationStatusSubscription.unsubscribe(); } public createQueue(queue) { if (queue.id) { - this.store.dispatch(new createNewQueueActions.UpdateQueue({queue: queue.id, name: queue.name})); + this.store.dispatch(createNewQueueActions.updateQueue({queue: {queue: queue.id, name: queue.name}})); } else { - this.store.dispatch(new createNewQueueActions.CreateNewQueue(queue)); + this.store.dispatch(createNewQueueActions.createNewQueue(queue)); } } diff --git a/src/app/webapp-common/shared/queue-create-dialog/queue-create-dialog.effects.ts b/src/app/webapp-common/shared/queue-create-dialog/queue-create-dialog.effects.ts index 0c92edd2..db9ed3e5 100755 --- a/src/app/webapp-common/shared/queue-create-dialog/queue-create-dialog.effects.ts +++ b/src/app/webapp-common/shared/queue-create-dialog/queue-create-dialog.effects.ts @@ -1,5 +1,4 @@ import * as createNewQueueActions from './queue-create-dialog.actions'; -import {CREATE_QUEUE_ACTIONS} from './queue-create-dialog.actions'; import {Actions, createEffect, ofType} from '@ngrx/effects'; import {Injectable} from '@angular/core'; import {CREATION_STATUS} from './queue-create-dialog.reducer'; @@ -15,43 +14,52 @@ export class QueueCreateDialogEffects { } activeLoader = createEffect(() => this.actions.pipe( - ofType(CREATE_QUEUE_ACTIONS.CREATE_NEW_QUEUE), + ofType(createNewQueueActions.createNewQueue), map(action => activeLoader(action.type)) )); createQueue = createEffect(() => this.actions.pipe( - ofType(CREATE_QUEUE_ACTIONS.CREATE_NEW_QUEUE), - mergeMap((action) => this.queuesApiService.queuesCreate(action.payload) + ofType(createNewQueueActions.createNewQueue), + mergeMap((action) => this.queuesApiService.queuesCreate({name: action.name}) .pipe( mergeMap(() => [ deactivateLoader(action.type), - new createNewQueueActions.SetNewQueueCreationStatus(CREATION_STATUS.SUCCESS), + createNewQueueActions.setCreationStatus({status: CREATION_STATUS.SUCCESS}), addMessage(MESSAGES_SEVERITY.SUCCESS, 'Queue Created Successfully'), ]), - catchError(error => [deactivateLoader(action.type), requestFailed(error), addMessage(MESSAGES_SEVERITY.ERROR, 'Queue Created Failed'), new createNewQueueActions.SetNewQueueCreationStatus(CREATION_STATUS.FAILED)]) + catchError(error => [deactivateLoader(action.type), + requestFailed(error), + addMessage(MESSAGES_SEVERITY.ERROR, 'Queue Created Failed'), + createNewQueueActions.setCreationStatus({status: CREATION_STATUS.FAILED}) + ]) ) ) )); updateQueue = createEffect(() => this.actions.pipe( - ofType(CREATE_QUEUE_ACTIONS.UPDATE_QUEUE), - mergeMap((action) => this.queuesApiService.queuesUpdate(action.payload) + ofType(createNewQueueActions.updateQueue), + mergeMap((action) => this.queuesApiService.queuesUpdate(action.queue) .pipe( mergeMap(() => [ deactivateLoader(action.type), - new createNewQueueActions.SetNewQueueCreationStatus(CREATION_STATUS.SUCCESS), + createNewQueueActions.setCreationStatus({status: CREATION_STATUS.SUCCESS}), addMessage(MESSAGES_SEVERITY.SUCCESS, 'Queue Updated Successfully'), ]), - catchError(error => [deactivateLoader(action.type), requestFailed(error), addMessage(MESSAGES_SEVERITY.ERROR, 'Queue Created Failed'), new createNewQueueActions.SetNewQueueCreationStatus(CREATION_STATUS.FAILED)]) + catchError(error => [ + deactivateLoader(action.type), + requestFailed(error), + addMessage(MESSAGES_SEVERITY.ERROR, 'Queue Update Failed'), + createNewQueueActions.setCreationStatus({status: CREATION_STATUS.FAILED}) + ]) ) ) )); getAllQueues = createEffect(() => this.actions.pipe( - ofType(CREATE_QUEUE_ACTIONS.GET_QUEUES), + ofType(createNewQueueActions.getQueues), switchMap(() => this.queuesApiService.queuesGetAllEx({}) .pipe( - mergeMap(res => [new createNewQueueActions.SetQueues(res.queues)]), + mergeMap(res => [createNewQueueActions.setQueues({queues: res.queues})]), catchError(error => [requestFailed(error)]) ) ) diff --git a/src/app/webapp-common/shared/queue-create-dialog/queue-create-dialog.reducer.ts b/src/app/webapp-common/shared/queue-create-dialog/queue-create-dialog.reducer.ts index cab1a1bb..ec2baa99 100755 --- a/src/app/webapp-common/shared/queue-create-dialog/queue-create-dialog.reducer.ts +++ b/src/app/webapp-common/shared/queue-create-dialog/queue-create-dialog.reducer.ts @@ -1,6 +1,6 @@ -import {createFeatureSelector, createSelector} from '@ngrx/store'; -import {CREATE_QUEUE_ACTIONS} from './queue-create-dialog.actions'; -import {Queue} from '../../../business-logic/model/queues/queue'; +import {createFeatureSelector, createReducer, createSelector, on} from '@ngrx/store'; +import {setCreationStatus, setQueues, resetState} from './queue-create-dialog.actions'; +import {Queue} from '~/business-logic/model/queues/queue'; export type CreationStatusEnum = 'success' | 'failed' | 'inProgress'; export const CREATION_STATUS = { @@ -23,15 +23,9 @@ export const selectCreateQueueDialog = createFeatureSelector export const selectQueues = createSelector(selectCreateQueueDialog, (state): Array => state.queues); export const selectCreationStatus = createSelector(selectCreateQueueDialog, (state): CreationStatusEnum => state.creationStatus); -export function queueCreateDialogReducer(state: ICreateQueueDialog = createQueueInitState, action): ICreateQueueDialog { - switch (action.type) { - case CREATE_QUEUE_ACTIONS.SET_CREATION_STATUS: - return {...state, creationStatus: action.payload.creationStatus}; - case CREATE_QUEUE_ACTIONS.SET_QUEUES: - return {...state, queues: action.payload.queues}; - case CREATE_QUEUE_ACTIONS.RESET_STATE: - return {...createQueueInitState}; - default: - return state; - } -} +export const queueCreateDialogReducer = createReducer( + createQueueInitState, + on(setQueues, (state, action): ICreateQueueDialog => ({...state, queues: action.queues})), + on(setCreationStatus, (state, action): ICreateQueueDialog => ({...state, creationStatus: action.status})), + on(resetState, (): ICreateQueueDialog => ({...createQueueInitState})), +); diff --git a/src/app/webapp-common/shared/services/breadcrumbs.service.ts b/src/app/webapp-common/shared/services/breadcrumbs.service.ts new file mode 100644 index 00000000..0207ba8b --- /dev/null +++ b/src/app/webapp-common/shared/services/breadcrumbs.service.ts @@ -0,0 +1,60 @@ +import {Injectable, OnDestroy} from '@angular/core'; +import {Store} from '@ngrx/store'; +import {combineLatest, Subscription} from 'rxjs'; +import {selectRouterConfig} from '@common/core/reducers/router-reducer'; +import {selectBreadcrumbOptions, selectProjectAncestors} from '@common/core/reducers/projects.reducer'; +import {debounceTime, distinctUntilChanged, filter, map} from 'rxjs/operators'; +import {setBreadcrumbs} from '@common/core/actions/router.actions'; +import {isExample} from '@common/shared/utils/shared-utils'; +import {CrumbTypeEnum} from '@common/layout/breadcrumbs/breadcrumbs.component'; +import {setBreadcrumbsOptions} from '@common/core/actions/projects.actions'; + +@Injectable({ + providedIn: 'root' +}) +export class BreadcrumbsService implements OnDestroy { + private sub = new Subscription(); + + constructor(private store: Store) { + this.sub.add(this.store.select(selectRouterConfig) + .pipe( + map(conf => conf?.[0]), + distinctUntilChanged() + ) + .subscribe(() => { + this.store.dispatch(setBreadcrumbsOptions({breadcrumbOptions: null})); + } + )); + this.sub.add(combineLatest([ + this.store.select(selectProjectAncestors), + this.store.select(selectBreadcrumbOptions) + ]).pipe( + filter(([projectAncestors, breadcrumbOptions]) => !!breadcrumbOptions && (!breadcrumbOptions.showProjects || projectAncestors !== null)), + debounceTime(200), + ).subscribe(([projectAncestors, breadcrumbOptions]) => { + this.store.dispatch(setBreadcrumbs({ + breadcrumbs: [ + [breadcrumbOptions.featureBreadcrumb], + ...(projectAncestors?.length > 0 ? [projectAncestors?.filter(ancestor => (!breadcrumbOptions.projectsOptions.filterBaseNameWith || !breadcrumbOptions.projectsOptions.filterBaseNameWith.includes(ancestor.basename))) + .map(ancestor => ({ + name: ancestor.basename, + example: isExample(ancestor), + url: `${breadcrumbOptions.projectsOptions.basePath}/${ancestor.id}/projects`, + type: CrumbTypeEnum.Project, + hidden: ancestor.hidden, + collapsable: true + }))] : []), + ...(breadcrumbOptions.projectsOptions?.selectedProjectBreadcrumb ? [[breadcrumbOptions.projectsOptions.selectedProjectBreadcrumb]] : []), + ...(breadcrumbOptions.subFeatureBreadcrumb ? [[breadcrumbOptions.subFeatureBreadcrumb]] : []), + ] + })); + }) + ); + } + + ngOnDestroy() { + this.sub.unsubscribe(); + } + + +} diff --git a/src/app/webapp-common/shared/services/color-hash/color-hash.service.ts b/src/app/webapp-common/shared/services/color-hash/color-hash.service.ts index c5f56e34..ab794846 100755 --- a/src/app/webapp-common/shared/services/color-hash/color-hash.service.ts +++ b/src/app/webapp-common/shared/services/color-hash/color-hash.service.ts @@ -23,7 +23,7 @@ export class ColorHashService { this._colorCache.next(obj); } - constructor(private store: Store) { + constructor(private store: Store) { this.store.select(selectColorPreferences) .pipe( filter(preferenceColors => !!preferenceColors), diff --git a/src/app/webapp-common/shared/services/error.service.ts b/src/app/webapp-common/shared/services/error.service.ts index 528aba9a..83a00fec 100644 --- a/src/app/webapp-common/shared/services/error.service.ts +++ b/src/app/webapp-common/shared/services/error.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; -interface Error { +export interface Error { meta: { result_code: number; result_subcode: number; @@ -60,4 +60,8 @@ export class ErrorService { } return error?.meta?.result_msg || ''; } + + lastRunError(error: Error) { + return error?.meta?.result_code === 400 && error?.meta?.result_subcode === 160; + } } diff --git a/src/app/webapp-common/shared/services/report-code-embed-base.service.ts b/src/app/webapp-common/shared/services/report-code-embed-base.service.ts index a7e2ad3e..9f4bd16d 100644 --- a/src/app/webapp-common/shared/services/report-code-embed-base.service.ts +++ b/src/app/webapp-common/shared/services/report-code-embed-base.service.ts @@ -19,6 +19,8 @@ export interface ReportCodeEmbedConfiguration { metrics?: string[]; variants?: string[]; valueType?: string; + seriesName?: string; + xaxis?: string; } @Injectable({ @@ -52,7 +54,9 @@ export class ReportCodeEmbedBaseService { url.pathname = url.pathname + 'widgets/'; url.searchParams.set('type', conf.type); url.searchParams.set('objectType', conf.objectType); + conf.seriesName && url.searchParams.set('series', conf.seriesName); conf.valueType && url.searchParams.set('value_type', conf.valueType); + conf.xaxis && url.searchParams.set('xaxis', conf.xaxis); let urlStr = url.toString(); ['objects', 'metrics', 'variants'].forEach(key => { if (conf[key]?.filter(v => !!v).length > 0) { diff --git a/src/app/webapp-common/shared/services/server-updates.service.ts b/src/app/webapp-common/shared/services/server-updates.service.ts index 79887e1d..e5a21a63 100644 --- a/src/app/webapp-common/shared/services/server-updates.service.ts +++ b/src/app/webapp-common/shared/services/server-updates.service.ts @@ -17,7 +17,7 @@ export class ServerUpdatesService { private initialized = false; private currentUser: GetCurrentUserResponseUserObject; - constructor(private httpClient: HttpClient, private store: Store, private serverService: ApiServerService) { + constructor(private httpClient: HttpClient, private store: Store, private serverService: ApiServerService) { if (localStorage.getItem('currentVersion') !== versionConf.version) { this.resetUpdateState(); } diff --git a/src/app/webapp-common/shared/services/ui-updates.service.ts b/src/app/webapp-common/shared/services/ui-updates.service.ts index aadde73c..50d6ad73 100644 --- a/src/app/webapp-common/shared/services/ui-updates.service.ts +++ b/src/app/webapp-common/shared/services/ui-updates.service.ts @@ -11,7 +11,7 @@ import {MatDialog, MatDialogRef} from '@angular/material/dialog'; export class UiUpdatesService { private updateDialog: MatDialogRef; - constructor(private httpClient: HttpClient, private store: Store, private matDialog: MatDialog, + constructor(private httpClient: HttpClient, private store: Store, private matDialog: MatDialog, ) { } diff --git a/src/app/webapp-common/shared/shared.module.ts b/src/app/webapp-common/shared/shared.module.ts index 10e02b7a..a15c11d8 100755 --- a/src/app/webapp-common/shared/shared.module.ts +++ b/src/app/webapp-common/shared/shared.module.ts @@ -22,16 +22,13 @@ import {TableModule} from 'primeng/table'; import {SectionHeaderComponent} from './components/section-header/section-header.component'; import {LineChartComponent} from './components/charts/line-chart/line-chart.component'; import {DonutComponent} from './components/charts/donut/donut.component'; -import {NgxJsonViewerComponent} from './components/ngx-json-viewer/ngx-json-viewer.component'; import {ExperimentRefreshComponent} from './components/experiment-refresh/experiment-refresh.component'; -import {LeavingBeforeSaveAlertGuard} from './guards/leaving-before-save-alert.guard'; import {CustomColumnsListComponent} from './components/custom-columns-list/custom-columns-list.component'; import {BaseContextMenuComponent} from './components/base-context-menu/base-context-menu.component'; import {EntityFooterComponent} from './entity-page/entity-footer/entity-footer.component'; import {CheckPermissionDirective} from '~/shared/directives/check-permission.directive'; import {ScrollTextareaComponent} from './components/scroll-textarea/scroll-textarea.component'; import {ShowOnlyUserWorkComponent} from './components/show-only-user-work/show-only-user-work.component'; -import {GeneralLeavingBeforeSaveAlertGuard} from './guards/general-leaving-before-save-alert.guard'; import {SortHumanizePipe} from './pipes/sort.pipe'; import {ScatterPlotComponent} from './components/charts/scatter-plot/scatter-plot.component'; import {ClearFiltersButtonComponent,} from './components/clear-filters-button/clear-filters-button.component'; @@ -71,7 +68,6 @@ const _declarations = [ SectionHeaderComponent, LineChartComponent, DonutComponent, - NgxJsonViewerComponent, ExperimentRefreshComponent, CustomColumnsListComponent, EntityFooterComponent, @@ -90,22 +86,22 @@ const _declarations = [ @NgModule({ - imports: [ - CommonModule, - RouterModule, - FormsModule, - ReactiveFormsModule, - TableModule, - ClipboardModule, - UiComponentsModule, - MatProgressSpinnerModule, - MatButtonToggleModule, - ChipsModule, - ScrollingModule, - LMarkdownEditorModule, - SharedPipesModule, - LabeledFormFieldDirective, - ], + imports: [ + CommonModule, + RouterModule, + FormsModule, + ReactiveFormsModule, + TableModule, + ClipboardModule, + UiComponentsModule, + MatProgressSpinnerModule, + MatButtonToggleModule, + ChipsModule, + ScrollingModule, + LMarkdownEditorModule, + SharedPipesModule, + LabeledFormFieldDirective, + ], declarations: [ ..._declarations, BaseContextMenuComponent, @@ -118,7 +114,6 @@ const _declarations = [ exports: [..._declarations, UiComponentsModule, TableModule, ClipboardModule, ScatterPlotComponent, ClearFiltersButtonComponent, IdBadgeComponent ], - providers: [LeavingBeforeSaveAlertGuard, GeneralLeavingBeforeSaveAlertGuard] }) export class SMSharedModule { } diff --git a/src/app/webapp-common/shared/single-graph/graph-viewer/graph-viewer.component.html b/src/app/webapp-common/shared/single-graph/graph-viewer/graph-viewer.component.html index 6bea5be7..df31a48b 100644 --- a/src/app/webapp-common/shared/single-graph/graph-viewer/graph-viewer.component.html +++ b/src/app/webapp-common/shared/single-graph/graph-viewer/graph-viewer.component.html @@ -4,8 +4,11 @@
- Iteration {{iteration}} + + Iteration {{iteration}} +
-
+
@@ -30,56 +33,77 @@
-
+ +
Horizontal Axis {{type.name}}
-
- Smoothing - - - - - +
+ Smoothing +
+ + + + + + + + + + + {{smoothTypeOption.value}} + + + +
+
-
+ +
null; + embedFunction: (data: {xaxis: ScalarKeyEnum; domRect: DOMRect}) => null; + legendConfiguration: Partial; } @Component({ @@ -62,6 +44,7 @@ export class GraphViewerComponent implements AfterViewInit, OnInit, OnDestroy { @ViewChild('singleGraph') singleGraph; @ViewChild('modalContainer') modalContainer; public height; + public width; private sub = new Subscription(); public minMaxIterations$: Observable<{ minIteration: number; maxIteration: number }>; public isFetchingData$: Observable; @@ -78,9 +61,11 @@ export class GraphViewerComponent implements AfterViewInit, OnInit, OnDestroy { private isForward: boolean = true; private charts: ExtFrame[]; public index: number = null; - public embedFunction: (DOMRect) => null; + public embedFunction: (data) => null; public yAxisType: AxisType; public showSmooth: boolean; + protected readonly smoothTypeEnum = smoothTypeEnum; + public smoothType: SmoothTypeEnum; @HostListener('document:keydown', ['$event']) onKeyDown(e: KeyboardEvent) { @@ -102,6 +87,8 @@ export class GraphViewerComponent implements AfterViewInit, OnInit, OnDestroy { @HostListener('window:resize') onResize() { + this.singleGraph.shouldRefresh = true; + this.width = this.modalContainer.nativeElement.clientWidth; this.height = this.modalContainer.nativeElement.clientHeight - 80; } @@ -112,6 +99,7 @@ export class GraphViewerComponent implements AfterViewInit, OnInit, OnDestroy { public plotLoaded: boolean = false; public beginningOfTime: boolean = false; public endOfTime: boolean = false; + smoothWeight: any = 0; xAxisTypeOption = [ { name: 'Iterations', @@ -126,7 +114,6 @@ export class GraphViewerComponent implements AfterViewInit, OnInit, OnDestroy { value: ScalarKeyEnum.IsoTime }, ]; - smoothWeight: any = 0; constructor( @Inject(MAT_DIALOG_DATA) public data: GraphViewerData, @@ -153,6 +140,8 @@ export class GraphViewerComponent implements AfterViewInit, OnInit, OnDestroy { this.embedFunction = data.embedFunction; this.darkTheme = data.darkTheme; this.smoothWeight = data.smoothWeight ?? 0; + this.smoothType = data.smoothType ?? smoothTypeEnum.exponential; + const reqData = { task: this.data.chart.task, metric: this.data.chart.metric, @@ -189,7 +178,7 @@ export class GraphViewerComponent implements AfterViewInit, OnInit, OnDestroy { parsingError && this.store.dispatch(addMessage('warn', `Couldn't read all plots. Please make sure all plots are properly formatted (NaN & Inf aren't supported).`, [], true)); Object.values(graphs).forEach((graphss: ExtFrame[]) => { graphss.forEach((graph: ExtFrame) => { - if ((graph?.layout?.images?.length ?? 0 ) > 0) { + if ((graph?.layout?.images?.length ?? 0) > 0) { graph.layout.images.forEach((image: Plotly.Image) => { this.store.dispatch(getSignedUrl({ url: image.source, @@ -215,6 +204,8 @@ export class GraphViewerComponent implements AfterViewInit, OnInit, OnDestroy { this.index = this.charts.findIndex(chrt => chrt.metric === this.chart?.metric && chrt.variant === this.chart?.variant); this.index = this.index === -1 ? (this.isForward ? 0 : this.charts.length - 1) : this.index; } + this.chart = null; + this.cdr.detectChanges(); this.chart = this.charts[this.index]; this.iteration = currentPlotEvents[0].iter; })); @@ -245,7 +236,9 @@ export class GraphViewerComponent implements AfterViewInit, OnInit, OnDestroy { .pipe(filter(plot => !!plot)) .subscribe(chart => { this.plotLoaded = true; - this.singleGraph.shouldRefresh = true; + if (this.singleGraph) { + this.singleGraph.shouldRefresh = true; + } this.chart = cloneDeep(chart); this.cdr.detectChanges(); })); @@ -288,7 +281,28 @@ export class GraphViewerComponent implements AfterViewInit, OnInit, OnDestroy { } changeWeight(value: number) { - this.smoothWeight = value; + if (value === 0) { + return; + } + if (value > (this.smoothType === smoothTypeEnum.exponential ? 0.999 : 100) || value < (this.smoothType === smoothTypeEnum.exponential ? 0 : 1)) { + this.smoothWeight = null; + } + setTimeout(() => { + if (this.smoothType === smoothTypeEnum.exponential) { + if (value > 0.999) { + this.smoothWeight = 0.999; + } else if (value < 0) { + this.smoothWeight = 0; + } + } else { + if (value > 100) { + this.smoothWeight = 100; + } else if (value < 1) { + this.smoothWeight = 1; + } + } + this.cdr.detectChanges(); + }); } refresh() { @@ -310,12 +324,14 @@ export class GraphViewerComponent implements AfterViewInit, OnInit, OnDestroy { next() { if (this.canGoNext() && this.chart && !this.darkTheme) { this.isForward = true; - if (this.charts[this.index + 1]) { + const task = this.chart.task; + if (this.charts?.[this.index + 1]) { + this.chart = null; this.chart = this.charts[++this.index]; this.store.dispatch(setViewerBeginningOfTime({beginningOfTime: false})); } else { this.plotLoaded = false; - this.store.dispatch(getNextPlotSample({task: this.chart.task, navigateEarlier: false})); + this.store.dispatch(getNextPlotSample({task, navigateEarlier: false})); } } } @@ -323,25 +339,27 @@ export class GraphViewerComponent implements AfterViewInit, OnInit, OnDestroy { previous() { if (this.canGoBack() && this.chart && !this.darkTheme) { this.isForward = false; - if (this.charts[this.index - 1]) { + const task = this.chart.task; + if (this.charts?.[this.index - 1]) { + this.chart = null; this.chart = this.charts[--this.index]; this.store.dispatch(setViewerEndOfTime({endOfTime: false})); } else { this.plotLoaded = false; - this.store.dispatch(getNextPlotSample({task: this.chart.task, navigateEarlier: true})); + this.store.dispatch(getNextPlotSample({task, navigateEarlier: true})); } } } nextIteration() { - if (this.canGoNext() && this.chart && !this.darkTheme) { + if (!this.isFullDetailsMode && this.canGoNext() && this.chart && !this.darkTheme) { this.plotLoaded = false; this.store.dispatch(getNextPlotSample({task: this.chart.task, navigateEarlier: false, iteration: true})); } } previousIteration() { - if (this.canGoBack() && this.chart && !this.darkTheme) { + if (!this.isFullDetailsMode && this.canGoBack() && this.chart && !this.darkTheme) { this.plotLoaded = false; this.store.dispatch(getNextPlotSample({task: this.chart.task, navigateEarlier: true, iteration: true})); } @@ -355,4 +373,8 @@ export class GraphViewerComponent implements AfterViewInit, OnInit, OnDestroy { return !this.beginningOfTime && this.plotLoaded; } + selectSmoothType($event: MatSelectChange) { + this.smoothWeight = [smoothTypeEnum.exponential, smoothTypeEnum.any].includes($event.value) ? 0 : 10; + this.smoothType = $event.value; + } } diff --git a/src/app/webapp-common/shared/single-graph/plotly-graph-base.ts b/src/app/webapp-common/shared/single-graph/plotly-graph-base.ts index 5a4ddd76..78226282 100644 --- a/src/app/webapp-common/shared/single-graph/plotly-graph-base.ts +++ b/src/app/webapp-common/shared/single-graph/plotly-graph-base.ts @@ -1,6 +1,6 @@ import {Subscription} from 'rxjs'; import {Component, Input, OnDestroy} from '@angular/core'; -import {Config, Frame, Layout, LayoutAxis, Legend, PlotData} from 'plotly.js'; +import {Config, Frame, Layout, Legend, PlotData} from 'plotly.js'; import {selectScaleFactor} from '@common/core/reducers/view.reducer'; import {Store} from '@ngrx/store'; import tinycolor from 'tinycolor2'; @@ -31,14 +31,8 @@ export interface ExtLegend extends Legend { itemwidth: number; } -export interface ExtLayoutAxis extends Omit { - spikesnap: string; -} - -export interface ExtLayout extends Omit { +export interface ExtLayout extends Omit { type: string; - xaxis: Partial; - yaxis: Partial; legend: Partial; uirevision: number | string; name: string; @@ -49,6 +43,7 @@ export interface ExtData extends PlotData { cells: any; header: any; name: string; + colorKey?: string; isSmoothed: boolean; colorHash: string; originalMetric?: string; @@ -77,11 +72,15 @@ export abstract class PlotlyGraphBaseComponent implements OnDestroy { } const colorString = tinycolor({r: newColor[0], g: newColor[1], b: newColor[2]}) .lighten((this.isSmooth && !trace.isSmoothed) ? 20 : 0).toRgbString(); + if (trace.marker) { + trace.marker.color = colorString; + if (trace.marker.line) { + trace.marker.line.color = colorString; + } + } if (trace.line) { trace.line.color = colorString; - } else if (trace.marker) { - trace.marker.color = colorString; - } else { + } else { // Guess that a graph without a lne or a marker should have a line, may cause havoc trace.line = {}; trace.line.color = colorString; diff --git a/src/app/webapp-common/shared/single-graph/single-graph.component.html b/src/app/webapp-common/shared/single-graph/single-graph.component.html index d7360017..1feec54c 100644 --- a/src/app/webapp-common/shared/single-graph/single-graph.component.html +++ b/src/app/webapp-common/shared/single-graph/single-graph.component.html @@ -8,5 +8,5 @@ (scroll)="repositionModeBar($event.target)" >
{{title}}
-
Loading...
+
diff --git a/src/app/webapp-common/shared/single-graph/single-graph.component.scss b/src/app/webapp-common/shared/single-graph/single-graph.component.scss index 36d8afe7..956eb219 100644 --- a/src/app/webapp-common/shared/single-graph/single-graph.component.scss +++ b/src/app/webapp-common/shared/single-graph/single-graph.component.scss @@ -44,31 +44,30 @@ } &.whitebg-table { - height: unset; - padding-bottom: 12px; + height: unset !important; margin-left: 24px; margin-right: 24px; } } + .whitebg-table { + overflow-x: auto; + overflow-y: visible; + } + .plot-loader { display: block; position: absolute; top: 50%; left: 50%; transform: translateY(-50%) translateX(-50%); - font-size: 14px; - color: #fff; - padding: 0 6px; - background-color: $blue-400; - border-radius: 4px; z-index: 2; } ::ng-deep sm-color-picker-wrapper { display: block; - top: 0px; + top: 0; } ::ng-deep .legendlines path, ::ng-deep .legendpoints path { @@ -109,7 +108,6 @@ } - .dark-theme { background: transparent !important; @@ -131,6 +129,7 @@ } } } + .modebar-group:empty { border-left: none; } @@ -157,6 +156,22 @@ fill: $blue-50; } } + + svg .cell-text:has(a):hover { + a { + fill: $blue-50; + } + } + + .hoverlayer .spikeline:nth-child(2) { + stroke: $blue-300 !important; + } + + .selectionlayer path, + .zoomlayer .select-outline { + stroke: #ffffff !important; + fill: #ffffff !important; + } } } @@ -191,8 +206,8 @@ &:only-child { border-left: none; - margin-left: 0px; - padding-left: 0px; + margin-left: 0; + padding-left: 0; } } } diff --git a/src/app/webapp-common/shared/single-graph/single-graph.component.ts b/src/app/webapp-common/shared/single-graph/single-graph.component.ts index 22c9345f..ea893fcb 100644 --- a/src/app/webapp-common/shared/single-graph/single-graph.component.ts +++ b/src/app/webapp-common/shared/single-graph/single-graph.component.ts @@ -25,7 +25,9 @@ import { PlotlyHTMLElement, PlotMarker, PlotType, - Root + Root, + deleteTraces, + react } from 'plotly.js'; import {Subject} from 'rxjs'; import {debounceTime, filter} from 'rxjs/operators'; @@ -40,6 +42,7 @@ import {PALLET} from '@common/constants'; import {download} from '@common/shared/utils/download'; import {chooseTimeUnit} from '@common/shared/utils/choose-time-unit'; import {GraphViewerComponent, GraphViewerData} from './graph-viewer/graph-viewer.component'; +import {getSmoothedLine} from '@common/shared/single-graph/single-graph.utils'; // eslint-disable-next-line @typescript-eslint/naming-convention declare const Plotly; @@ -62,13 +65,12 @@ export class SingleGraphComponent extends PlotlyGraphBaseComponent { public title: string; private originalChart: ExtFrame; private _chart: ExtFrame; - private smoothnessTimeout: number; private chartData: HTMLDivElement; private previousOffsetWidth: number; private modeBar: HTMLElement; private previousHeight: number; private ratioEnable: boolean; - private smooth$ = new Subject(); + private smooth$ = new Subject(); private _height: number; private _hoverMode: ChartHoverModeEnum; private listeningToHoverMode = false; @@ -86,7 +88,7 @@ export class SingleGraphComponent extends PlotlyGraphBaseComponent { @Input() set height(height: number) { this._height = height; if (this.chart) { - this.drawGraph$.next({forceRedraw: true, forceSkipReact: false}); + this.drawGraph$.next({forceRedraw: false, forceSkipReact: false}); } } @@ -96,23 +98,42 @@ export class SingleGraphComponent extends PlotlyGraphBaseComponent { @Input() set width(width: number) { if (this.chart) { - this.drawGraph$.next({forceRedraw: true, forceSkipReact: false}); + this.drawGraph$.next({forceRedraw: false, forceSkipReact: false}); } } + get chart(): ExtFrame { + return this._chart; + } + @Input() set chart(chart: ExtFrame) { if (chart) { this.ratioEnable = !!chart.layout.width && !!chart.layout.height; this.ratio = this.ratioEnable ? chart.layout.width / chart.layout.height : null; this._height = chart.layout.height || this.height || 450; + if (this.originalChart?.data.length === chart.data.length) { + this.rebootGraph(chart, true); + } else { + this._chart = cloneDeep(chart); + } this.originalChart = chart; - this._chart = cloneDeep(chart); this.drawGraph$.next({forceRedraw: true, forceSkipReact: false}); } } - get chart(): ExtFrame { - return this._chart; + private rebootGraph(chart: ExtFrame, clean?: boolean) { + const hidden = this._chart?.data + .filter(d => !d.isSmoothed) + .map((d, i) => d.visible === 'legendonly' ? i : null) + .filter(index => index !== null); + if (clean && this.alreadyDrawn && this.type === 'scatter' && chart.data[0]?.x?.length === 1) { + (Plotly.deleteTraces as typeof deleteTraces)(this.chartElm, Array.from({length: this.chartElm.data.length ?? 0}, (v, i) => i)); + this.modeBar = null; + } + this._chart = cloneDeep(chart); + if (hidden) { + this._chart.data.forEach((d, i) => d.visible = hidden.includes(i) ? 'legendonly': true); + } } @Input() moveLegendToTitle = false; @@ -131,6 +152,18 @@ export class SingleGraphComponent extends PlotlyGraphBaseComponent { return this._hoverMode; } + @Input() set smoothType(smoothType: string) { + this._smoothType = smoothType; + this.isSmooth = this.smoothWeight > 0; + if (this.alreadyDrawn) { + this.smooth$.next(null); + } + } + + get smoothType() { + return this._smoothType; + } + @Input() set smoothWeight(ratio: number) { this._smoothWeight = ratio; this.isSmooth = ratio > 0; @@ -147,11 +180,12 @@ export class SingleGraphComponent extends PlotlyGraphBaseComponent { @Input() hideDownloadButtons = false; @Input() noMargins = false; @Output() hoverModeChanged = new EventEmitter(); - @Output() createEmbedCode = new EventEmitter(); + @Output() createEmbedCode = new EventEmitter<{xaxis: ScalarKeyEnum; domRect: DOMRect}>(); @Output() maximizeClicked = new EventEmitter(); @ViewChild('drawHere', {static: true}) plotlyContainer: ElementRef; private chartElm; private _smoothWeight: number; + private _smoothType: string; constructor( @@ -170,7 +204,7 @@ export class SingleGraphComponent extends PlotlyGraphBaseComponent { debounceTime(50), filter(() => !!this.chart)) .subscribe(() => { - this._chart = cloneDeep(this.originalChart); + this.rebootGraph(this.originalChart); this.drawGraph$.next({forceRedraw: true, forceSkipReact: false}); }) ); @@ -217,7 +251,7 @@ export class SingleGraphComponent extends PlotlyGraphBaseComponent { this.previousHeight = this.height; if (!skipReact) { - this.zone.runOutsideAngular(() => Plotly.react(root, data, layout, config).then(() => { + this.zone.runOutsideAngular(() => (Plotly.react as typeof react)(root, data, layout, config).then(() => { this.loading = false; this.changeDetector.detectChanges(); })); @@ -264,7 +298,7 @@ export class SingleGraphComponent extends PlotlyGraphBaseComponent { const graph = this.formatChartLines() as ExtFrame; this.type = (graph?.data?.[0]?.type ?? graph?.layout?.type) as PlotType; - this.title = this.isDarkTheme ? '' : this.addIterationString(graph.layout.title as string, graph.iter) || (graph.layout.title as Record).text; + this.title = (this.isDarkTheme || this.hideTitle) ? '' : this.getTitle(graph); let layout = { ...this.hideDownloadButtons ? {} : this.addParametersIfDarkTheme({ // eslint-disable-next-line @typescript-eslint/naming-convention @@ -279,7 +313,7 @@ export class SingleGraphComponent extends PlotlyGraphBaseComponent { ...graph.layout, // eslint-disable-next-line @typescript-eslint/naming-convention ...this.addParametersIfDarkTheme({plot_bgcolor: 'transparent'}), - height: this.height, + height: this.type === 'table' ? this.height - 20 : this.height, width: this.ratio ? (this.height * this.ratio) + RATIO_OFFSET_FIX : undefined, modebar: { color: '#5a658e', @@ -489,17 +523,14 @@ export class SingleGraphComponent extends PlotlyGraphBaseComponent { } svg.parentElement.attributes['data-title'].value = this.getLogButtonTitle(this.yAxisType === 'log'); path.attributes[0].value = icon.path; - this.smoothnessTimeout = window.setTimeout(() => { - this._chart = cloneDeep(this.originalChart); - this.drawGraph$.next({forceRedraw: true, forceSkipReact: false}); - }, 400); + this.smooth$.next(this.smoothWeight); } }); } - if (!['table', 'parcoords'].includes(graph?.data?.[0]?.type) && graph.layout?.showlegend !== false && !this.moveLegendToTitle) { + if (!['table', 'parcoords'].includes(graph?.data?.[0]?.type) && !this.moveLegendToTitle) { modeBarButtonsToAdd.push({ name: 'Hide legend', - title: this.getHideButtonTitle(), + title: graph.layout?.showlegend !== false ? 'Hide legend' : 'Show legend', icon: this.getToggleLegendIcon(), click: (element, ev: MouseEvent) => { const pathElement = (ev.target as HTMLElement).tagName === 'path' ? (ev.target as HTMLElement) : (ev.target as HTMLElement).querySelector('path'); @@ -538,7 +569,7 @@ export class SingleGraphComponent extends PlotlyGraphBaseComponent { title: 'Download JSON', icon: this.getJsonDownloadIcon(), click: () => { - this.downloadGraphAsJson(cloneDeep(this.originalChart)); + this.downloadGraphAsJson(this.originalChart); } }); } @@ -560,8 +591,7 @@ export class SingleGraphComponent extends PlotlyGraphBaseComponent { attr: 'plotly-embedded-modebar-button', icon: this.getEmbedIcon(), click: (event) => { - this.createEmbedCode.emit(event.querySelector('[data-title="Copy embed code"]').getBoundingClientRect() - ); + this.createEmbedCode.emit({xaxis: this.xAxisType, domRect: event.querySelector('[data-title="Copy embed code"]').getBoundingClientRect()}); } }; modeBarButtonsToAdd.push(button); @@ -584,12 +614,36 @@ export class SingleGraphComponent extends PlotlyGraphBaseComponent { modeBarButtonsToRemove: (this.hideDownloadButtons ? ['sendDataToCloud', 'toImage']: ['sendDataToCloud']) as ModeBarDefaultButtons[], displaylogo: false, modeBarButtonsToAdd, - toImageButtonOptions : {scale: 3} + toImageButtonOptions : { + filename: this.getExportName(this.chart), + scale: 3 + } } as Config; return [this.chartElm, graph.data, layout, config, this.chartData]; } + private getTitle(graph: ExtFrame) { + if (graph.layout.title) { + const title = (typeof graph.layout.title === 'string') ? graph.layout.title : graph.layout.title.text; + return this.addIterationString(title?.trim(), graph.iter); + } + return ''; + } + + private getExportName(graph: ExtFrame) { + const title = this.getTitle(graph); + if (title) { + return title; + } + const metric = this.chart.metric?.trim(); + const variant = this.chart.variant?.trim(); + if (metric && variant) { + return `${metric} - ${variant}`; + } + return `${metric || variant}` || 'chart'; + } + private updateLegend() { const graph = select(this.plotlyContainer.nativeElement); graph.selectAll('.legendpoints path') @@ -615,7 +669,7 @@ export class SingleGraphComponent extends PlotlyGraphBaseComponent { for (let i = 0; i < graph.data.length; i++) { if (!graph.data[i].name) { - graph.data[i].name = `graph.metric ${i}`; + graph.data[i].name = this.getTitle(graph); } if (!this.alreadyDrawn && !graph.data[i].name.includes(' { - if (!isFinite(last)) { - return null; - } else { - // 1st-order IIR low-pass filter to attenuate the higher-frequency - // components of the time-series, with bias fix toward initial value. - last = last * this.smoothWeight + (1 - this.smoothWeight) * d; - validPoints++; - const debiasWeight = (i > 0 && this.smoothWeight < 1) ? 1 - Math.pow(this.smoothWeight, validPoints) : 1; - return last / debiasWeight; - } - }) + y: getSmoothedLine(data.y, this.smoothWeight, this.smoothType) } as ExtData; } @@ -791,20 +832,21 @@ export class SingleGraphComponent extends PlotlyGraphBaseComponent { downloadGraphAsJson(chart: ExtFrame) { let timeUnit; + let data; if (this.xAxisType === ScalarKeyEnum.Timestamp) { - chart.data.forEach(graphData => { + data = chart.data.map(graphData => { if (!graphData.name) { - return; + return graphData; } - timeUnit = typeof timeUnit === 'undefined' ? chooseTimeUnit(chart.data) : timeUnit; const zeroTime = graphData.x[0] as number; - graphData.x = (graphData.x as number[]).map(timestamp => (timestamp - zeroTime) / timeUnit.time); - // graph.data[i].hovertext = graph.data[i].x.map(timestamp => timeInWords((timestamp - zeroTime))); + return {...graphData, x: (graphData.x as number[]).map(timestamp => (timestamp - zeroTime) / timeUnit.time)}; }); + } else { + data = chart.data; } - const exportName = `${chart.layout.title} - ${this.getAxisText(timeUnit) || chart.layout.name}.json`; - const dataStr = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(chart.data)); + const exportName = `${this.getExportName(this.chart)}.json`; + const dataStr = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(data)); download(dataStr, exportName); } @@ -916,7 +958,7 @@ export class SingleGraphComponent extends PlotlyGraphBaseComponent { this.zone.run(() => { this.dialog.open(GraphViewerComponent, { data: { - ...(this.exportForReport && {embedFunction: (rect: DOMRect) => this.createEmbedCode.emit(rect)}), + ...(this.exportForReport && {embedFunction: (data) => this.createEmbedCode.emit({xaxis: data.xaxis, domRect: data.domRect})}), // signed url are updated after originChart was cloned - need to update images urls! chart: cloneDeep({ ...this.originalChart, @@ -926,8 +968,10 @@ export class SingleGraphComponent extends PlotlyGraphBaseComponent { xAxisType: this.xAxisType, yAxisType: this.yAxisType, smoothWeight: this.smoothWeight, + smoothType: this.smoothType, darkTheme: this.isDarkTheme, isCompare: this.isCompare, + legendConfiguration: this.legendConfiguration } as GraphViewerData, panelClass: ['image-viewer-dialog', this.isDarkTheme ? 'dark-theme' : 'light-theme'], height: '100%', @@ -943,6 +987,9 @@ export class SingleGraphComponent extends PlotlyGraphBaseComponent { } private addIterationString(name: string, iter: number) { + if (!name) { + return name; + } return name + ((iter || (this.graphsNumber > 1 && iter === 0)) ? ` - Iteration ${iter}` : ''); } @@ -964,7 +1011,7 @@ export class SingleGraphComponent extends PlotlyGraphBaseComponent { } data = data.slice(0, -1) + '\n'; } - const exportName = `${this.chart.layout.title} - ${this.chart.layout.name}.csv`; + const exportName = `${this.getExportName(this.chart)}.csv`; data = 'data:text/csv;charset=utf-8,' + encodeURIComponent(data); download(data, exportName); } diff --git a/src/app/webapp-common/shared/single-graph/single-graph.effects.ts b/src/app/webapp-common/shared/single-graph/single-graph.effects.ts index 13c142e4..3974e2fa 100644 --- a/src/app/webapp-common/shared/single-graph/single-graph.effects.ts +++ b/src/app/webapp-common/shared/single-graph/single-graph.effects.ts @@ -21,7 +21,7 @@ import {selectRouterConfig} from '@common/core/reducers/router-reducer'; export class SingleGraphEffects { - constructor(private actions$: Actions, private store: Store, private eventsApi: ApiEventsService) {} + constructor(private actions$: Actions, private store: Store, private eventsApi: ApiEventsService) {} fetchPlotsForIter$ = createEffect(() => this.actions$.pipe( ofType(getPlotSample), diff --git a/src/app/webapp-common/shared/single-graph/single-graph.module.ts b/src/app/webapp-common/shared/single-graph/single-graph.module.ts index 4d8823e2..a8456917 100644 --- a/src/app/webapp-common/shared/single-graph/single-graph.module.ts +++ b/src/app/webapp-common/shared/single-graph/single-graph.module.ts @@ -11,21 +11,23 @@ import {MatSelectModule} from '@angular/material/select'; import {FormsModule} from '@angular/forms'; import {MatInputModule} from '@angular/material/input'; import {TooltipDirective} from '@common/shared/ui-components/indicators/tooltip/tooltip.directive'; +import {MatProgressSpinnerModule} from '@angular/material/progress-spinner'; @NgModule({ declarations: [SingleGraphComponent, GraphViewerComponent], exports: [SingleGraphComponent, GraphViewerComponent], - imports: [ - CommonModule, - StoreModule.forFeature('singleGraph', singleGraphReducer), - EffectsModule.forFeature([SingleGraphEffects]), - MatSliderModule, - MatSelectModule, - FormsModule, - MatInputModule, - TooltipDirective, - ] + imports: [ + CommonModule, + StoreModule.forFeature('singleGraph', singleGraphReducer), + EffectsModule.forFeature([SingleGraphEffects]), + MatSliderModule, + MatSelectModule, + FormsModule, + MatInputModule, + TooltipDirective, + MatProgressSpinnerModule, + ] }) export class SingleGraphModule { } diff --git a/src/app/webapp-common/shared/single-graph/single-graph.utils.ts b/src/app/webapp-common/shared/single-graph/single-graph.utils.ts new file mode 100644 index 00000000..3a3fc0a0 --- /dev/null +++ b/src/app/webapp-common/shared/single-graph/single-graph.utils.ts @@ -0,0 +1,37 @@ +import * as smoothing from 'taira'; + +export type SmoothTypeEnum = 'runningAverage' | 'exponential' | 'gaussian' | 'any'; +export const smoothTypeEnum = { + exponential: 'Exponential Moving Average' as SmoothTypeEnum, + runningAverage: 'Running Average' as SmoothTypeEnum, + gaussian: 'Gaussian' as SmoothTypeEnum, + any: 'No Smoothing' as SmoothTypeEnum +}; + +export const averageDebiased = (arr, weight) => { + let last = !!arr?.length ? 0 : NaN; + let validPoints = 0; + return arr.map((d, i) => { + if (!isFinite(last)) { + return null; + } else { + // 1st-order IIR low-pass filter to attenuate the higher-frequency + // components of the time-series, with bias fix toward initial value. + last = last * weight + (1 - weight) * d; + validPoints++; + const debiasWeight = (i > 0 && weight < 1) ? 1 - Math.pow(weight, validPoints) : 1; + return last / debiasWeight; + } + }); +}; + +export const getSmoothedLine = (arr, weight, smoothType): number[] => { + switch (smoothType) { + case smoothTypeEnum.runningAverage: + return arr.length > 5 ? smoothing.smoothen(arr, smoothing.ALGORITHMS.AVERAGE, 2, weight, false) : arr; + case smoothTypeEnum.gaussian: + return arr.length > 5 ? smoothing.smoothen(arr, smoothing.ALGORITHMS.GAUSSIAN, 2, Math.round(weight / 100 * 5), false) : arr; + case smoothTypeEnum.exponential: + return averageDebiased(arr, weight); + } +}; diff --git a/src/app/webapp-common/shared/single-value-summary-table/single-value-summary-table.component.ts b/src/app/webapp-common/shared/single-value-summary-table/single-value-summary-table.component.ts index 2169d144..9702f91b 100644 --- a/src/app/webapp-common/shared/single-value-summary-table/single-value-summary-table.component.ts +++ b/src/app/webapp-common/shared/single-value-summary-table/single-value-summary-table.component.ts @@ -2,6 +2,7 @@ import {ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output} import {EventsGetTaskSingleValueMetricsResponseValues} from '~/business-logic/model/events/eventsGetTaskSingleValueMetricsResponseValues'; import {download} from '../utils/download'; import {NgForOf, NgIf} from '@angular/common'; +import {ScalarKeyEnum} from '~/business-logic/model/events/scalarKeyEnum'; @Component({ selector: 'sm-single-value-summary-table', @@ -18,7 +19,7 @@ export class SingleValueSummaryTableComponent implements OnInit { @Input() data: Array; @Input() experimentName; @Input() darkTheme: boolean; - @Output() createEmbedCode = new EventEmitter(); + @Output() createEmbedCode = new EventEmitter<{xaxis: ScalarKeyEnum; domRect: any}>(); public hover: boolean; constructor() { } @@ -36,6 +37,6 @@ export class SingleValueSummaryTableComponent implements OnInit { } createEmbedCodeClicked($event: MouseEvent) { - this.createEmbedCode.emit({x: $event.clientX, y: $event.clientY}); + this.createEmbedCode.emit({xaxis: null, domRect: {x: $event.clientX, y: $event.clientY}}); } } diff --git a/src/app/webapp-common/shared/ui-components/data/labeled-row/labeled-row.component.html b/src/app/webapp-common/shared/ui-components/data/labeled-row/labeled-row.component.html index e9b7d1b1..398d9132 100755 --- a/src/app/webapp-common/shared/ui-components/data/labeled-row/labeled-row.component.html +++ b/src/app/webapp-common/shared/ui-components/data/labeled-row/labeled-row.component.html @@ -1,5 +1,5 @@
-
{{label}}
+
{{label}}
diff --git a/src/app/webapp-common/shared/ui-components/data/selectable-list/selectable-list.component.ts b/src/app/webapp-common/shared/ui-components/data/selectable-list/selectable-list.component.ts index 6065aaab..04967152 100644 --- a/src/app/webapp-common/shared/ui-components/data/selectable-list/selectable-list.component.ts +++ b/src/app/webapp-common/shared/ui-components/data/selectable-list/selectable-list.component.ts @@ -30,7 +30,7 @@ export class SelectableListComponent implements OnChanges{ ngOnChanges(changes: SimpleChanges): void { if ((changes.list || changes.checkedList)) { - this.showList = this.list.map(item => ({...item, visible: !this.checkedList.includes(item.name) } as SelectableListItem)); + this.showList = this.list.map(item => ({...item, visible: !this.checkedList?.includes(item.name) } as SelectableListItem)); this.cdr.detectChanges(); } } diff --git a/src/app/webapp-common/shared/ui-components/data/table/base-table-view.ts b/src/app/webapp-common/shared/ui-components/data/table/base-table-view.ts index 3e7d45d4..51937fc1 100644 --- a/src/app/webapp-common/shared/ui-components/data/table/base-table-view.ts +++ b/src/app/webapp-common/shared/ui-components/data/table/base-table-view.ts @@ -10,6 +10,7 @@ import {EntityTypeEnum} from '~/shared/constants/non-common-consts'; import {sortByArr} from '../../../pipes/show-selected-first.pipe'; import {IOption} from '../../inputs/select-autocomplete-for-template-forms/select-autocomplete-for-template-forms.component'; import {DATASETS_STATUS_LABEL} from '~/features/experiments/shared/experiments.const'; +import {cleanTag} from '@common/shared/utils/helpers.util'; @Directive() export abstract class BaseTableView implements AfterViewInit, OnDestroy { @@ -158,8 +159,9 @@ export abstract class BaseTableView implements AfterViewInit, OnDestroy { if (!this.filtersOptions[columnId]) { return; } + const cleanFilterValues = columnId ==='tags'? this.filtersValues[columnId]?.map(tag=> cleanTag(tag)): this.filtersValues[columnId]; this.filtersOptions[columnId].sort((a, b) => - sortByArr(a.value, b.value, [null, ...(this.filtersValues[columnId] || [])])); + sortByArr(a.value, b.value, [null, ...(cleanFilterValues || [])])); this.filtersOptions = {...this.filtersOptions, [columnId]: [...this.filtersOptions[columnId]]}; } @@ -188,7 +190,7 @@ export abstract class BaseTableView implements AfterViewInit, OnDestroy { afterTableInit() { const key = this.selectedEntitiesKey.slice(0, -1); if (this[key]) { - window.setTimeout(() => this.table.scrollToElement(this[key]), 200); + window.setTimeout(() => this.table?.scrollToElement(this[key]), 200); } } diff --git a/src/app/webapp-common/shared/ui-components/data/table/table-card-filter-template/table-card-filter-template.component.html b/src/app/webapp-common/shared/ui-components/data/table/table-card-filter-template/table-card-filter-template.component.html index d6e9b1ea..ef9035d8 100755 --- a/src/app/webapp-common/shared/ui-components/data/table/table-card-filter-template/table-card-filter-template.component.html +++ b/src/app/webapp-common/shared/ui-components/data/table/table-card-filter-template/table-card-filter-template.component.html @@ -3,7 +3,7 @@
- + ) { - if (options) { + if (options && this.isOpen) { this.noMoreOptions = options?.length < this.filterPageSize || options?.length === this.previousLength && this.searchValue === this.previousSearchValue; this.previousLength = options?.length; this.previousSearchValue = this.searchValue; @@ -155,5 +156,11 @@ export class TableFilterSortTemplateComponent { this.previousLength = 0; } this.menuClosed.emit(); + this.isOpen = false; + } + + onMenuOpen() { + this.isOpen = true; + this.menuOpened.emit(); } } diff --git a/src/app/webapp-common/shared/ui-components/data/table/table.component.scss b/src/app/webapp-common/shared/ui-components/data/table/table.component.scss index 7063f415..62dbd6cf 100755 --- a/src/app/webapp-common/shared/ui-components/data/table/table.component.scss +++ b/src/app/webapp-common/shared/ui-components/data/table/table.component.scss @@ -213,6 +213,7 @@ background-color: rgba($blue-600, 95%); border-radius: 4px; border: solid 1px $blue-400; + z-index: 2; &:hover { color: $blue-300; background-color: lighten($blue-600, 5%); diff --git a/src/app/webapp-common/shared/ui-components/data/table/table.component.ts b/src/app/webapp-common/shared/ui-components/data/table/table.component.ts index 10051cc5..5faffd21 100755 --- a/src/app/webapp-common/shared/ui-components/data/table/table.component.ts +++ b/src/app/webapp-common/shared/ui-components/data/table/table.component.ts @@ -19,12 +19,12 @@ import { ViewChild, ViewChildren } from '@angular/core'; -import {get} from 'lodash-es'; +import {get, isArray, isString} from 'lodash-es'; import {MenuItem, PrimeTemplate, SortMeta} from 'primeng/api'; import {FilterMetadata} from 'primeng/api/filtermetadata'; import {ContextMenu} from 'primeng/contextmenu'; import {Table} from 'primeng/table'; -import {BehaviorSubject, fromEvent, Subject, Subscription, startWith, combineLatest, interval} from 'rxjs'; +import {BehaviorSubject, combineLatest, fromEvent, interval, startWith, Subject, Subscription} from 'rxjs'; import {debounce, debounceTime, filter, take, throttleTime} from 'rxjs/operators'; import {custumFilterFunc, custumSortSingle} from './overrideFilterFunc'; import {ColHeaderTypeEnum, ISmCol, TableSortOrderEnum} from './table.consts'; @@ -32,6 +32,8 @@ import {Store} from '@ngrx/store'; import {selectScaleFactor} from '@common/core/reducers/view.reducer'; import {trackById} from '@common/shared/utils/forms-track-by'; import {sortCol} from '@common/shared/utils/sortCol'; +import {Options, Angular2CsvComponent} from 'angular2-csv'; +import {prepareColsForDownload} from '@common/shared/utils/download'; @Component({ selector: 'sm-table', @@ -86,6 +88,7 @@ export class TableComponent implements AfterContentInit, AfterViewInit, OnInit, @Input() expandableRows = false; @Input() initialColumns; private waitForClick: number; + @Input() set tableData(tableData) { this.loading = false; this.rowsNumber = tableData ? tableData.length : 0; @@ -171,12 +174,12 @@ export class TableComponent implements AfterContentInit, AfterViewInit, OnInit, @Input() hasExperimentUpdate: boolean; @Output() sortChanged = new EventEmitter<{ field: ISmCol['id']; isShift: boolean }>(); - @Output() rowClicked = new EventEmitter<{e: MouseEvent; data: any}>(); - @Output() rowDoubleClicked = new EventEmitter<{e: MouseEvent; data: any}>(); + @Output() rowClicked = new EventEmitter<{ e: MouseEvent; data: any }>(); + @Output() rowDoubleClicked = new EventEmitter<{ e: MouseEvent; data: any }>(); @Output() rowSelectionChanged = new EventEmitter<{ data: Array; originalEvent?: Event }>(); @Output() firstChanged = new EventEmitter(); @Output() loadMoreClicked = new EventEmitter(); - @Output() rowRightClick = new EventEmitter<{e: MouseEvent; rowData; single?: boolean}>(); + @Output() rowRightClick = new EventEmitter<{ e: MouseEvent; rowData; single?: boolean }>(); @Output() colReordered = new EventEmitter(); @Output() columnResized = new EventEmitter<{ columnId: string; widthPx: number }>(); search: string; @@ -395,13 +398,20 @@ export class TableComponent implements AfterContentInit, AfterViewInit, OnInit, public scrollToIndex(rowIndex) { if (rowIndex > -1 && this.table) { - const row = this.table.el.nativeElement.getElementsByTagName('tr')[rowIndex] as HTMLTableRowElement; - if (row) { - let location = row.offsetTop; - if (rowIndex + 1 === this.tableData.length) { - location += row.getBoundingClientRect().height; + if (this.virtualScroll) { + const {height} = this.table.el.nativeElement.getBoundingClientRect(); + const rowsInPage = height / this.rowHeight; + const maxScroll = Math.ceil(this.rowsNumber - rowsInPage); + this.table.scrollToVirtualIndex(Math.min(maxScroll, rowIndex)); + } else { + const row = this.table.el.nativeElement.getElementsByTagName('tr')[rowIndex] as HTMLTableRowElement; + if (row) { + let location = row.offsetTop; + if (rowIndex + 1 === this.tableData.length) { + location += row.getBoundingClientRect().height; + } + this.table.scrollTo({top: location, behavior: 'smooth'}); } - this.table.scrollTo({top: location, behavior: 'smooth'}); } } } @@ -465,14 +475,14 @@ export class TableComponent implements AfterContentInit, AfterViewInit, OnInit, } loadMore() { - this.loading = true; - this.loadMoreDebouncer.next(null); + this.loading = true; + this.loadMoreDebouncer.next(null); } onColReorder($event: any) { const columnsList = $event.columns.map(column => column.id); this.colReordered.emit(columnsList); - this.calcResize(); + this.resize(); } orderColumns() { @@ -514,12 +524,12 @@ export class TableComponent implements AfterContentInit, AfterViewInit, OnInit, } focusSelected() { - this.table.el.nativeElement.getElementsByClassName('selected')[0]?.focus(); + this.table.el?.nativeElement.getElementsByClassName('selected')[0]?.focus(); } colResize({delta, element}: { delta: number; element: HTMLTableHeaderCellElement }) { if (delta) { - setTimeout( () => { + setTimeout(() => { const width = element.clientWidth; const columnId = element.attributes['data-col-id']?.value; this.columnResized.emit({columnId, widthPx: width}); @@ -564,18 +574,56 @@ export class TableComponent implements AfterContentInit, AfterViewInit, OnInit, } checkClick(param: { data: any; e: MouseEvent }) { - if (param.e.type === 'dblclick') { + if (param.e.type === 'dblclick' || this.waitForClick) { window.clearTimeout(this.waitForClick); + this.waitForClick = null; this.rowDoubleClicked.emit(param); } else { this.waitForClick = window.setTimeout(() => { this.rowClicked.emit(param); - }, 50); + this.waitForClick = null; + }, 250); } } - updateNumberOfRows({event, expanded}: {event: any; expanded: boolean}) { + updateNumberOfRows({event, expanded}: { event: any; expanded: boolean }) { const expandedIndex = Object.values(this.tableData).findIndex((row: any) => row.id === event.data.id); this.lastRowExpanded = expanded && expandedIndex === this.rowsNumber - 1; } + + + downloadTableAsCSV(tableName?: string) { + const options: Options = { + filename: null, + fieldSeparator: ',', + quoteStrings: '"', + decimalseparator: '.', + title: '', + showLabels: false, + headers: [], + showTitle: false, + useBom: true, + removeNewLines: true, + keys: [] + }; + + const rows = []; + const downloadCols = prepareColsForDownload(this.columns); + options.keys = downloadCols.map(dCol=>dCol.field); + options.headers = downloadCols.map(dCol=> dCol.name); + this.tableData.forEach(row => { + rows.push(options.keys.reduce((acc, key) => { + const val = get(row, key, ''); + acc[key] = isArray(val) ? val.toString() : isString(val) ? val.replace(/\r?\n|\r/g + , '') : val; + return acc; + }, {})); + }); + const angular2csvButton = new Angular2CsvComponent(); + angular2csvButton.data = rows; + angular2csvButton.options = options; + angular2csvButton.filename = `${tableName}${tableName ? '-' : ''}table`; + angular2csvButton.generateCsv(); + } + } diff --git a/src/app/webapp-common/shared/ui-components/data/table/table.consts.ts b/src/app/webapp-common/shared/ui-components/data/table/table.consts.ts index 89dc1839..d0190d87 100755 --- a/src/app/webapp-common/shared/ui-components/data/table/table.consts.ts +++ b/src/app/webapp-common/shared/ui-components/data/table/table.consts.ts @@ -54,4 +54,5 @@ export interface ISmCol { key?: string; type?: string; showInCardFilters?: boolean; + downloadKey?: string; } diff --git a/src/app/webapp-common/shared/ui-components/directives/choose-color/choose-color.directive.ts b/src/app/webapp-common/shared/ui-components/directives/choose-color/choose-color.directive.ts index 055eb7b1..ab5a06d0 100644 --- a/src/app/webapp-common/shared/ui-components/directives/choose-color/choose-color.directive.ts +++ b/src/app/webapp-common/shared/ui-components/directives/choose-color/choose-color.directive.ts @@ -49,7 +49,7 @@ export class ChooseColorDirective { } } - constructor(private el: ElementRef, private store: Store) {} + constructor(private el: ElementRef, private store: Store) {} openColorPicker(event: MouseEvent) { let top = event.pageY - (this.colorPickerHeight / 3); @@ -74,7 +74,7 @@ export class ChooseColorDirective { } -export const attachColorChooser = (text: string, buttonElement: Element, colorHash: ColorHashService, store: Store, withAlpha?: boolean, orgColor?: number[]) => { +export const attachColorChooser = (text: string, buttonElement: Element, colorHash: ColorHashService, store: Store, withAlpha?: boolean, orgColor?: number[]) => { const directive = new ChooseColorDirective(new ElementRef(buttonElement), store); directive.colorButtonRef = new ElementRef(buttonElement); directive.stringToColor = text; diff --git a/src/app/webapp-common/shared/ui-components/indicators/snippet-error/snippet-error.component.ts b/src/app/webapp-common/shared/ui-components/indicators/snippet-error/snippet-error.component.ts index 21f75eac..dc794192 100644 --- a/src/app/webapp-common/shared/ui-components/indicators/snippet-error/snippet-error.component.ts +++ b/src/app/webapp-common/shared/ui-components/indicators/snippet-error/snippet-error.component.ts @@ -28,7 +28,7 @@ export class SnippetErrorComponent { public min = Math.min; public missingSource: boolean = false; - constructor(private store: Store) {} + constructor(private store: Store) {} @Output() openImageClicked = new EventEmitter(); @Input() set copyContent(content: string) { diff --git a/src/app/webapp-common/shared/ui-components/inputs/color-picker/color-picker-wrapper.component.ts b/src/app/webapp-common/shared/ui-components/inputs/color-picker/color-picker-wrapper.component.ts index 4baed519..c58e0b27 100644 --- a/src/app/webapp-common/shared/ui-components/inputs/color-picker/color-picker-wrapper.component.ts +++ b/src/app/webapp-common/shared/ui-components/inputs/color-picker/color-picker-wrapper.component.ts @@ -39,7 +39,7 @@ export class ColorPickerWrapperComponent implements OnInit, OnDestroy { public props: ColorPickerProps; public toggle = false; - constructor(private store: Store, private colorHashService: ColorHashService, private cdr: ChangeDetectorRef) { + constructor(private store: Store, private colorHashService: ColorHashService, private cdr: ChangeDetectorRef) { } ngOnInit() { diff --git a/src/app/webapp-common/shared/ui-components/inputs/duration-input/duration-input.component.html b/src/app/webapp-common/shared/ui-components/inputs/duration-input/duration-input.component.html index 79e4fb2b..f976e2f7 100644 --- a/src/app/webapp-common/shared/ui-components/inputs/duration-input/duration-input.component.html +++ b/src/app/webapp-common/shared/ui-components/inputs/duration-input/duration-input.component.html @@ -4,7 +4,7 @@ matInput name="hour" [(ngModel)]="hours" - (keypress)="checkChars($event)" + (keydown)="checkChars($event)" (keyup)=" ($any($event.target).selectionStart === 2) && mins.focus()" (keyup.enter)="onChangePartial($event)" (keyup.arrowRight)="($any($event.target).selectionStart===2 || $any($event.target).value.length <= 1) && mins.focus()" @@ -17,7 +17,7 @@ matInput name="min" [(ngModel)]="minutes" - (keypress)="checkChars($event)" + (keydown)="checkChars($event)" (keyup)="($any($event.target).selectionStart===2 ) && secs.focus()" (keyup.enter)="onChangePartial($event)" (keyup.arrowRight)="($any($event.target).selectionStart===2 || $any($event.target).value.length <= 1) && secs.focus()" @@ -26,12 +26,12 @@ maxlength="2" smKeyDownStopPropagation (focus)="$any($event.target).select()" - smTooltip="Minuets">: + smTooltip="Minutes">: (); @ViewChild('searchBar', {static: true}) searchBarInput; + constructor(private readonly cdr: ChangeDetectorRef) {} + ngOnInit(): void { this.subs.add(this.value$.pipe( tap((val: string) => this.empty = val?.length === 0), @@ -58,6 +60,7 @@ export class SearchComponent implements OnInit, OnChanges, OnDestroy { this.valueChanged.emit(''); this.clear(true); } + this.cdr.detectChanges(); })); } diff --git a/src/app/webapp-common/shared/ui-components/inputs/select-autocomplete-with-chips/select-autocomplete-with-chips.component.scss b/src/app/webapp-common/shared/ui-components/inputs/select-autocomplete-with-chips/select-autocomplete-with-chips.component.scss index 105bca37..2c2c1c49 100644 --- a/src/app/webapp-common/shared/ui-components/inputs/select-autocomplete-with-chips/select-autocomplete-with-chips.component.scss +++ b/src/app/webapp-common/shared/ui-components/inputs/select-autocomplete-with-chips/select-autocomplete-with-chips.component.scss @@ -31,6 +31,10 @@ gap: 6px 0; } } + // allow changing chip color when edit is disabled + ::ng-deep .mdc-text-field--disabled { + pointer-events: auto; + } } :host-context(.single-selection) { diff --git a/src/app/webapp-common/shared/ui-components/overlay/edit-json/edit-json.component.ts b/src/app/webapp-common/shared/ui-components/overlay/edit-json/edit-json.component.ts index 538842c0..1b05bb05 100644 --- a/src/app/webapp-common/shared/ui-components/overlay/edit-json/edit-json.component.ts +++ b/src/app/webapp-common/shared/ui-components/overlay/edit-json/edit-json.component.ts @@ -59,7 +59,7 @@ export class EditJsonComponent implements AfterViewInit{ @Inject(MAT_DIALOG_DATA) public data: EditJsonData, private dialogRef: MatDialogRef, private jsonPipe: JsonPipe, - private store: Store, + private store: Store, private zone: NgZone, ) { this.format = data.format; @@ -116,7 +116,7 @@ export class EditJsonComponent implements AfterViewInit{ } as Partial); - aceEditor.renderer.setScrollMargin(12, 12, 12, 12); + aceEditor.renderer.setScrollMargin(12, 12, 0, 12); aceEditor.renderer.setPadding(12); (aceEditor.renderer.container.querySelector('.ace_cursor') as HTMLElement).style.color = 'white'; diff --git a/src/app/webapp-common/shared/ui-components/overlay/leaf/leaf.component.html b/src/app/webapp-common/shared/ui-components/overlay/leaf/leaf.component.html index b7479a43..ba5e4db6 100755 --- a/src/app/webapp-common/shared/ui-components/overlay/leaf/leaf.component.html +++ b/src/app/webapp-common/shared/ui-components/overlay/leaf/leaf.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/app/webapp-common/shared/ui-components/overlay/leaf/leaf.component.scss b/src/app/webapp-common/shared/ui-components/overlay/leaf/leaf.component.scss index c7c81fcb..fb06d86b 100755 --- a/src/app/webapp-common/shared/ui-components/overlay/leaf/leaf.component.scss +++ b/src/app/webapp-common/shared/ui-components/overlay/leaf/leaf.component.scss @@ -1,10 +1,5 @@ @import "../../styles/mixins"; -.gradiented-background { - height: 100%; - background-image: linear-gradient(to bottom, #ffffff, rgba(0, 0, 0, 0)); - position: relative; -} .choose-container { @include choose-leaf; } diff --git a/src/app/webapp-common/shared/ui-components/overlay/share-dialog/share-dialog.component.ts b/src/app/webapp-common/shared/ui-components/overlay/share-dialog/share-dialog.component.ts index d068ae30..c3e4e9ef 100755 --- a/src/app/webapp-common/shared/ui-components/overlay/share-dialog/share-dialog.component.ts +++ b/src/app/webapp-common/shared/ui-components/overlay/share-dialog/share-dialog.component.ts @@ -26,7 +26,7 @@ export class ShareDialogComponent { constructor(@Inject(MAT_DIALOG_DATA) public data: IShareDialogConfig, public dialogRef: MatDialogRef, - private store: Store) { + private store: Store) { this.title = data.title || ''; this.sharedSubtitle =`Any registered user with this link has read-only access to this task and all its contents (Artifacts, Results, etc.)`; this.privateSubtitle = `Create a shareable link to grant read access to any registered user you provide this link to.`; @@ -45,7 +45,7 @@ export class ShareDialogComponent { } createLink() { - this.store.dispatch(shareSelectedExperiments({share: !this.shared, task:this.task})); + this.store.dispatch(shareSelectedExperiments({share: !this.shared, task: this.task})); this.shared = !this.shared; } diff --git a/src/app/webapp-common/shared/ui-components/overlay/spinner/spinner.component.ts b/src/app/webapp-common/shared/ui-components/overlay/spinner/spinner.component.ts index 4e496741..9f619232 100755 --- a/src/app/webapp-common/shared/ui-components/overlay/spinner/spinner.component.ts +++ b/src/app/webapp-common/shared/ui-components/overlay/spinner/spinner.component.ts @@ -26,7 +26,7 @@ export class SpinnerComponent implements OnInit, OnDestroy { private navEndSubscription: Subscription; private loading$: Observable<{ [p: string]: boolean }>; - constructor(private store: Store, private router: Router, private cdr: ChangeDetectorRef) { + constructor(private store: Store, private router: Router, private cdr: ChangeDetectorRef) { this.loading$ = store.select(selectLoading); } diff --git a/src/app/webapp-common/shared/ui-components/overlay/terms-of-use-dialog/terms-of-use-dialog.component.html b/src/app/webapp-common/shared/ui-components/overlay/terms-of-use-dialog/terms-of-use-dialog.component.html index ffc4b785..97f7494f 100644 --- a/src/app/webapp-common/shared/ui-components/overlay/terms-of-use-dialog/terms-of-use-dialog.component.html +++ b/src/app/webapp-common/shared/ui-components/overlay/terms-of-use-dialog/terms-of-use-dialog.component.html @@ -1,11 +1,12 @@ - +