diff --git a/CI/e2e/docker-compose.e2e.yaml b/CI/e2e/docker-compose.e2e.yaml
index b56cd346f1..3311b5ae81 100644
--- a/CI/e2e/docker-compose.e2e.yaml
+++ b/CI/e2e/docker-compose.e2e.yaml
@@ -33,9 +33,11 @@ services:
- "./CI/e2e/frontend.config.e2e.json:/home/node/app/dist/config/frontend.config.json"
- "./CI/e2e/frontend.theme.e2e.json:/home/node/app/dist/config/frontend.theme.json"
- "./CI/e2e/publishedDataConfig.e2e.json:/home/node/app/publishedDataConfig.json"
+ - "./CI/e2e/jobconfig.e2e.yaml:/home/node/app/jobconfig.yaml"
environment:
- DOI_USERNAME=${DOI_USERNAME}
- DOI_PASSWORD=${DOI_PASSWORD}
+ - JOB_CONFIGURATION_FILE=/home/node/app/jobconfig.yaml
depends_on:
mongodb:
condition: service_healthy
diff --git a/CI/e2e/jobconfig.e2e.yaml b/CI/e2e/jobconfig.e2e.yaml
new file mode 100644
index 0000000000..089a1fa02d
--- /dev/null
+++ b/CI/e2e/jobconfig.e2e.yaml
@@ -0,0 +1,111 @@
+configVersion: v1.0 2024-03-01 6f3f38
+jobs:
+ - jobType: all_access
+ create:
+ auth: "#all"
+ actions:
+ - actionType: log
+ update:
+ auth: "#all"
+ - jobType: public_access
+ create:
+ auth: "#datasetPublic"
+ update:
+ auth: "#all"
+ - jobType: authenticated_access
+ create:
+ auth: "#authenticated"
+ update:
+ auth: "#all"
+ - jobType: dataset_access
+ create:
+ auth: "#datasetAccess"
+ update:
+ auth: "#jobOwnerGroup"
+ - jobType: owner_access
+ create:
+ auth: "#datasetOwner"
+ update:
+ auth: "#jobOwnerUser"
+ - jobType: user_access
+ create:
+ auth: user5.1
+ update:
+ auth: user5.1
+ - jobType: group_access
+ create:
+ auth: "@group5"
+ update:
+ auth: "@group5"
+ - jobType: job_admin
+ create:
+ auth: "#jobAdmin"
+ update:
+ auth: "#jobAdmin"
+ - jobType: archive
+ create:
+ auth: "#all"
+ actions:
+ - actionType: validate
+ datasets:
+ datasetlifecycle.archivable:
+ const: true
+ update:
+ auth: "#all"
+ - jobType: retrieve
+ create:
+ auth: "#all"
+ actions:
+ - actionType: validate
+ datasets:
+ datasetlifecycle.retrievable:
+ const: true
+ update:
+ auth: "#all"
+ - jobType: public
+ create:
+ auth: "#all"
+ actions:
+ - actionType: validate
+ datasets:
+ isPublished:
+ const: true
+ update:
+ auth: "#all"
+ - jobType: validate
+ create:
+ auth: admin
+ actions:
+ - actionType: validate
+ request:
+ jobParams.requiredParam:
+ type: string
+ jobParams.arrayOfStrings:
+ type: array
+ items:
+ type: string
+ datasets:
+ datasetlifecycle.archivable:
+ const: true
+ update:
+ auth: admin
+ actions:
+ - actionType: validate
+ request:
+ $:
+ $schema: http://json-schema.org/draft-07/schema#
+ required:
+ - jobResultObject
+ properties:
+ jobResultObject:
+ type: object
+ required:
+ - requiredParam
+ - arrayOfStrings
+ properties:
+ requiredParam:
+ type: string
+ arrayOfStrings:
+ type: array
+ items:
+ type: string
diff --git a/cypress/e2e/jobs/jobs-general.cy.js b/cypress/e2e/jobs/jobs-general.cy.js
new file mode 100644
index 0000000000..299c0f0f30
--- /dev/null
+++ b/cypress/e2e/jobs/jobs-general.cy.js
@@ -0,0 +1,94 @@
+import { testData } from "../../fixtures/testData";
+
+describe("Jobs general", () => {
+ beforeEach(() => {
+ cy.login(Cypress.env("username"), Cypress.env("password"));
+ cy.createDataset({
+ type: "raw",
+ dataFileSize: "small",
+ datasetName: "Jobs Dataset",
+ pid: "6ED35C17-EDD4-4CD4-B917-4E49698F7532",
+ isPublished: true,
+ });
+ });
+
+ afterEach(() => {
+ cy.removeJobs();
+ cy.removeDatasets();
+ });
+
+ describe("Jobs dynamic material table", () => {
+ it("should be able to search for job in the global search", () => {
+ cy.createJob();
+
+ cy.visit("/user/jobs");
+
+ cy.get('[data-cy="text-search"]').type("all_access");
+ cy.get('[data-cy="search-button"]').click();
+
+ cy.get("mat-table mat-row").first().should("contain", "all_access");
+ });
+
+ it("should be able to change page and page size in the job table", () => {
+ cy.createJob({ emailJobInitiator: "test1@example.com" });
+ cy.createJob({ emailJobInitiator: "test2@example.com" });
+ cy.createJob({ emailJobInitiator: "test3@example.com" });
+ cy.createJob({ emailJobInitiator: "test4@example.com" });
+ cy.createJob({ emailJobInitiator: "test5@example.com" });
+ cy.createJob({ emailJobInitiator: "test6@example.com" });
+
+ cy.visit("/user/jobs");
+
+ cy.get("mat-paginator").first().find("mat-select").click({ force: true });
+ cy.get("mat-option").contains("5").click({ force: true });
+
+ cy.get("mat-paginator .mat-mdc-paginator-range-actions").contains(
+ "1 – 5",
+ );
+
+ cy.get("mat-paginator").first().find("[aria-label='Next page']").click();
+
+ cy.get("mat-paginator .mat-mdc-paginator-range-actions").contains(
+ "6 – 6",
+ );
+ });
+
+ it("should be able to change visible columns settings in the table", () => {
+ cy.createJob();
+
+ cy.visit("/user/jobs");
+
+ cy.get("dynamic-mat-table mat-header-row.header").should("exist");
+
+ cy.get("dynamic-mat-table table-menu button").click();
+ cy.get('[role="menu"] button').contains("Default setting").click();
+ cy.get("body").type("{esc}");
+
+ cy.get("dynamic-mat-table")
+ .scrollTo("right", { ensureScrollable: false })
+ .get("mat-header-row")
+ .should("contain", "Initiator");
+
+ cy.get("dynamic-mat-table table-menu button").click();
+ cy.get('[role="menu"] button').contains("Column setting").click();
+
+ cy.get('[role="menu"]')
+ .contains("Initiator")
+ .parent()
+ .find("input[type=checkbox]")
+ .uncheck();
+
+ cy.contains(".column-config-apply button.done-setting", "done").click();
+
+ cy.get("dynamic-mat-table table-menu button").click();
+ cy.get('[role="menu"] button').contains("Save table setting").click();
+
+ cy.reload();
+
+ cy.get("dynamic-mat-table")
+ .scrollTo("right", { ensureScrollable: false })
+ .get("mat-header-row")
+ .should("not.contain", "Initiator");
+ });
+ });
+});
diff --git a/cypress/fixtures/testData.js b/cypress/fixtures/testData.js
index e854094679..323bc20f8d 100644
--- a/cypress/fixtures/testData.js
+++ b/cypress/fixtures/testData.js
@@ -175,6 +175,16 @@ export const testData = {
},
],
},
+ job: {
+ emailJobInitiator: "user@example.com",
+ type: "all_access",
+ jobParams: { dataset: "6ED35C17-EDD4-4CD4-B917-4E49698F7532" },
+ datasetList: [
+ { pid: "6ED35C17-EDD4-4CD4-B917-4E49698F7532", files: [] },
+ ],
+ jobStatusMessage: "jobSubmitted",
+ jobResultObject: {},
+ },
};
export const testConfig = {
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
index 363158e084..5963238d8b 100644
--- a/cypress/support/commands.js
+++ b/cypress/support/commands.js
@@ -244,6 +244,34 @@ Cypress.Commands.add("createSample", (sample) => {
});
});
+Cypress.Commands.add("createJob", (overwrites = {}) => {
+ return cy.getCookie("user").then((userCookie) => {
+ const user = JSON.parse(decodeURIComponent(userCookie.value));
+
+ cy.getToken().then((token) => {
+ cy.log("Job: " + JSON.stringify(overwrites, null, 2));
+ cy.log("User: " + JSON.stringify(user, null, 2));
+
+ const job = {
+ ...testData.job,
+ emailJobInitiator: user.email,
+ ...overwrites,
+ };
+
+ cy.request({
+ method: "POST",
+ url: lbBaseUrl + "/jobs",
+ headers: {
+ Authorization: token,
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ },
+ body: job,
+ });
+ });
+ });
+});
+
Cypress.Commands.add("updateProposal", (proposalId, updateProposalDto) => {
return cy.getCookie("user").then((userCookie) => {
const user = JSON.parse(decodeURIComponent(userCookie.value));
@@ -445,6 +473,51 @@ Cypress.Commands.add("removeSamples", () => {
});
});
+Cypress.Commands.add("removeJobs", () => {
+ cy.login(Cypress.env("username"), Cypress.env("password"));
+ cy.getToken().then((token) => {
+ const fields = { type: "all_access" };
+ const limits = { limit: 10, skip: 0, sort: { type: "asc" } };
+
+ cy.request({
+ method: "GET",
+ url:
+ lbBaseUrl +
+ "/jobs/fullquery?fields=" +
+ encodeURIComponent(JSON.stringify(fields)) +
+ "&limits=" +
+ encodeURIComponent(JSON.stringify(limits)),
+ headers: {
+ Authorization: token,
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ },
+ })
+ .its("body")
+ .as("jobs");
+
+ cy.login(
+ Cypress.env("secondaryUsername"),
+ Cypress.env("secondaryPassword"),
+ );
+ cy.getToken().then((token) => {
+ cy.get("@jobs").then((jobs) => {
+ jobs.forEach((job) => {
+ cy.request({
+ method: "DELETE",
+ url: lbBaseUrl + `/jobs/${encodeURIComponent(job.id)}`,
+ headers: {
+ Authorization: token,
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ },
+ });
+ });
+ });
+ });
+ });
+});
+
Cypress.Commands.add("initializeElasticSearch", (index) => {
cy.login(Cypress.env("username"), Cypress.env("password"));
cy.getToken().then((token) => {
diff --git a/src/app/jobs/jobs-dashboard-new/jobs-dashboard-new.component.html b/src/app/jobs/jobs-dashboard-new/jobs-dashboard-new.component.html
index 8cb997d2b3..aee744ff25 100644
--- a/src/app/jobs/jobs-dashboard-new/jobs-dashboard-new.component.html
+++ b/src/app/jobs/jobs-dashboard-new/jobs-dashboard-new.component.html
@@ -1,7 +1,22 @@
-
-
+
diff --git a/src/app/jobs/jobs-dashboard-new/jobs-dashboard-new.component.spec.ts b/src/app/jobs/jobs-dashboard-new/jobs-dashboard-new.component.spec.ts
index 92f0a07eee..3028c8aab6 100644
--- a/src/app/jobs/jobs-dashboard-new/jobs-dashboard-new.component.spec.ts
+++ b/src/app/jobs/jobs-dashboard-new/jobs-dashboard-new.component.spec.ts
@@ -1,49 +1,50 @@
-import { NO_ERRORS_SCHEMA } from "@angular/compiler";
import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing";
-import { ActivatedRoute, Router } from "@angular/router";
+import { JobsDashboardNewComponent } from "./jobs-dashboard-new.component";
+import { NO_ERRORS_SCHEMA } from "@angular/core";
+import { Router } from "@angular/router";
import { AppConfigService } from "app-config.service";
-import {
- MockActivatedRoute,
- MockAppConfigService,
- MockAuthService,
- MockHttp,
- MockRouter,
-} from "shared/MockStubs";
+import { ScicatDataService } from "shared/services/scicat-data-service";
import { ExportExcelService } from "shared/services/export-excel.service";
-import { JobsDashboardNewComponent } from "./jobs-dashboard-new.component";
-import { SharedTableModule } from "shared/modules/shared-table/shared-table.module";
-import { SharedScicatFrontendModule } from "shared/shared.module";
-import { HttpClient } from "@angular/common/http";
-import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
-import { InternalStorage } from "shared/services/auth/base.storage";
-import { AuthService } from "shared/services/auth/auth.service";
-import { provideLuxonDateAdapter } from "@angular/material-luxon-adapter";
+import { RowEventType } from "shared/modules/dynamic-material-table/models/table-row.model";
+import { provideMockStore } from "@ngrx/store/testing";
+import { selectJobsDashboardPageViewModel } from "state-management/selectors/jobs.selectors";
+
+const getConfig = () => ({});
describe("JobsDashboardNewComponent", () => {
let component: JobsDashboardNewComponent;
let fixture: ComponentFixture;
- beforeEach(waitForAsync(() => {
- const appconfig = new MockAppConfigService(null);
- const authService = new MockAuthService();
+ const router = {
+ navigateByUrl: jasmine.createSpy("navigateByUrl"),
+ };
+
+ const jobsVm = {
+ jobs: [],
+ count: 0,
+ filters: {
+ skip: 0,
+ limit: 5,
+ sortField: "creationTime:desc",
+ mode: undefined,
+ },
+ tableSettings: { columns: [] },
+ };
+ beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
schemas: [NO_ERRORS_SCHEMA],
declarations: [JobsDashboardNewComponent],
- imports: [
- SharedTableModule,
- SharedScicatFrontendModule,
- BrowserAnimationsModule,
- ],
providers: [
- { provide: ActivatedRoute, useClass: MockActivatedRoute },
+ { provide: Router, useValue: router },
+ { provide: AppConfigService, useValue: { getConfig } },
+ { provide: ScicatDataService, useValue: {} },
{ provide: ExportExcelService, useValue: {} },
- { provide: Router, useClass: MockRouter },
- { provide: HttpClient, useClass: MockHttp },
- { provide: AppConfigService, useValue: appconfig },
- { provide: AuthService, useValue: authService },
- { provide: InternalStorage },
- provideLuxonDateAdapter(),
+ provideMockStore({
+ selectors: [
+ { selector: selectJobsDashboardPageViewModel, value: jobsVm },
+ ],
+ }),
],
}).compileComponents();
}));
@@ -61,4 +62,19 @@ describe("JobsDashboardNewComponent", () => {
it("should create", () => {
expect(component).toBeTruthy();
});
+
+ describe("#onRowEvent", () => {
+ it("should navigate to a Job detail", () => {
+ const job = { jobId: "job-1" };
+ const id = encodeURIComponent(job.jobId);
+
+ component.onRowEvent({
+ event: RowEventType.RowClick,
+ sender: { row: job },
+ } as any);
+
+ expect(router.navigateByUrl).toHaveBeenCalledTimes(1);
+ expect(router.navigateByUrl).toHaveBeenCalledWith("/user/jobs/" + id);
+ });
+ });
});
diff --git a/src/app/jobs/jobs-dashboard-new/jobs-dashboard-new.component.ts b/src/app/jobs/jobs-dashboard-new/jobs-dashboard-new.component.ts
index 8a325b1046..7054c36044 100644
--- a/src/app/jobs/jobs-dashboard-new/jobs-dashboard-new.component.ts
+++ b/src/app/jobs/jobs-dashboard-new/jobs-dashboard-new.component.ts
@@ -1,16 +1,29 @@
-import {
- AfterViewChecked,
- ChangeDetectorRef,
- Component,
- OnDestroy,
-} from "@angular/core";
+import { Component, OnDestroy, OnInit } from "@angular/core";
import { Router } from "@angular/router";
-import { SciCatDataSource } from "../../shared/services/scicat.datasource";
-import { ScicatDataService } from "../../shared/services/scicat-data-service";
-import { ExportExcelService } from "../../shared/services/export-excel.service";
-import { Column } from "shared/modules/shared-table/shared-table.module";
+import { Store } from "@ngrx/store";
+import { OutputJobV3Dto } from "@scicatproject/scicat-sdk-ts-angular";
+import { BehaviorSubject, Subscription, take, filter } from "rxjs";
import { AppConfigService } from "app-config.service";
-import { JobsTableData } from "jobs/jobs-dashboard/jobs-dashboard.component";
+import { ScicatDataService } from "shared/services/scicat-data-service";
+import { ExportExcelService } from "shared/services/export-excel.service";
+import { SciCatDataSource } from "shared/services/scicat.datasource";
+import { TableField } from "shared/modules/dynamic-material-table/models/table-field.model";
+import {
+ TablePagination,
+ TablePaginationMode,
+} from "shared/modules/dynamic-material-table/models/table-pagination.model";
+import {
+ ITableSetting,
+ TableSettingEventType,
+} from "shared/modules/dynamic-material-table/models/table-setting.model";
+import { actionMenu } from "shared/modules/dynamic-material-table/utilizes/default-table-settings";
+import {
+ IRowEvent,
+ RowEventType,
+} from "shared/modules/dynamic-material-table/models/table-row.model";
+import { TableConfigService } from "shared/services/table-config.service";
+import { updateUserSettingsAction } from "state-management/actions/user.actions";
+import { selectJobsDashboardPageViewModel } from "state-management/selectors/jobs.selectors";
@Component({
selector: "app-jobs-new-dashboard",
@@ -18,98 +31,101 @@ import { JobsTableData } from "jobs/jobs-dashboard/jobs-dashboard.component";
styleUrls: ["./jobs-dashboard-new.component.scss"],
standalone: false,
})
-export class JobsDashboardNewComponent implements OnDestroy, AfterViewChecked {
- // not needed, date by default is shown in local time and using the locale of the browser (if installed, see app.module.ts)
- // tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
-
- columns: Column[] = [
- {
- id: "id",
- label: "ID",
- canSort: true,
- icon: "perm_device_information",
- matchMode: "contains",
- hideOrder: 0,
- },
- {
- id: "emailJobInitiator",
- label: "Initiator",
- icon: "person",
- canSort: true,
- matchMode: "contains",
- hideOrder: 1,
- },
- {
- id: "type",
- label: "Type",
- icon: "unarchive",
- canSort: true,
- matchMode: "is",
- hideOrder: 2,
- },
- {
- id: "creationTime",
- icon: "schedule",
- label: "Created at local time",
- format: "date medium ",
- canSort: true,
- matchMode: "between",
- hideOrder: 3,
- sortDefault: "desc",
- },
- {
- id: "jobParams",
- icon: "work",
- label: "Parameters",
- format: "json",
- canSort: false,
- matchMode: "contains",
- hideOrder: 4,
+export class JobsDashboardNewComponent implements OnInit, OnDestroy {
+ public vm$ = this.store.select(selectJobsDashboardPageViewModel);
+
+ columns: TableField[] = [];
+ setting: ITableSetting = {};
+
+ tableName = "jobsTable";
+ rowSelectionMode: "single" | "multi" | "none" = "none";
+ paginationMode: TablePaginationMode = "server-side";
+ pending = false;
+ globalTextSearch = "";
+
+ tableDefaultSettingsConfig: ITableSetting = {
+ visibleActionMenu: actionMenu,
+ settingList: [
+ {
+ visibleActionMenu: actionMenu,
+ isDefaultSetting: true,
+ isCurrentSetting: true,
+ columnSetting: [
+ { name: "jobId", header: "ID", index: 0 },
+ { name: "emailJobInitiator", header: "Initiator", index: 1 },
+ { name: "type", header: "Type", index: 2 },
+ {
+ name: "creationTime",
+ header: "Created at local time",
+ index: 3,
+ type: "date",
+ format: "medium",
+ },
+ {
+ name: "jobParams",
+ header: "Parameters",
+ index: 4,
+ customRender: (_, row) => JSON.stringify(row.jobParams),
+ },
+ { name: "jobStatusMessage", header: "Status", index: 5 },
+ {
+ name: "datasetList",
+ header: "Datasets",
+ index: 6,
+ customRender: (_, row) => JSON.stringify(row.datasetList),
+ },
+ {
+ name: "jobResultObject",
+ header: "Result",
+ index: 7,
+ customRender: (_, row) => JSON.stringify(row.jobResultObject),
+ },
+ ],
+ },
+ ],
+ rowStyle: {
+ "border-bottom": "1px solid #d2d2d2",
},
- {
- id: "jobStatusMessage",
- icon: "traffic",
- label: "Status",
- format: "json",
+ };
+
+ pagination: TablePagination = {
+ pageSize: 5,
+ pageIndex: 0,
+ pageSizeOptions: [5, 10, 25, 100],
+ length: 0,
+ };
+
+ scicatColumnsDef =
+ this.tableDefaultSettingsConfig.settingList[0]?.columnSetting?.map((c) => ({
+ id: c.name,
+ label: c.header ?? c.name,
+ hideOrder: c.index ?? 0,
canSort: true,
- matchMode: "contains",
- hideOrder: 5,
- },
- {
- id: "datasetList",
- icon: "list",
- label: "Datasets",
- format: "json",
- canSort: false,
- matchMode: "contains",
- hideOrder: 6,
- },
- {
- id: "jobResultObject",
- icon: "work_outline",
- label: "Result",
- format: "json",
- canSort: false,
- matchMode: "contains",
- hideOrder: 7,
- },
- ];
+ matchMode: c.type === "date" ? "between" : "contains",
+ })) ?? [];
tableDefinition = {
collection: "Jobs",
- columns: this.columns,
+ columns: this.scicatColumnsDef,
};
- dataSource: SciCatDataSource;
+ dataSource: BehaviorSubject = new BehaviorSubject<
+ OutputJobV3Dto[]
+ >([]);
+ scicatDataSource: SciCatDataSource;
+
+ subscriptions: Subscription[] = [];
+ currentFilters: any = {};
constructor(
+ private router: Router,
+ private store: Store,
private appConfigService: AppConfigService,
- private cdRef: ChangeDetectorRef,
private dataService: ScicatDataService,
private exportService: ExportExcelService,
- private router: Router,
+ private tableConfigService: TableConfigService,
) {
- this.dataSource = new SciCatDataSource(
+ this.scicatDataSource = new SciCatDataSource(
this.appConfigService,
this.dataService,
this.exportService,
@@ -117,16 +133,138 @@ export class JobsDashboardNewComponent implements OnDestroy, AfterViewChecked {
);
}
- ngAfterViewChecked() {
- this.cdRef.detectChanges();
+ ngOnInit() {
+ this.subscriptions.push(
+ this.vm$
+ .pipe(
+ filter((vm) => vm.hasFetchedSettings),
+ take(1),
+ )
+ .subscribe((vm) => {
+ this.currentFilters = vm.filters;
+ const { skip, limit } = vm.filters;
+ const pageIndex = skip / limit;
+
+ this.loadData(vm.filters, pageIndex, limit);
+
+ const tableSettingsConfig =
+ this.tableConfigService.getTableSettingsConfig(
+ this.tableName,
+ this.tableDefaultSettingsConfig,
+ vm.tableSettings?.columns || [],
+ );
+
+ const currentColumnSetting =
+ tableSettingsConfig.settingList.find((s) => s.isCurrentSetting)
+ ?.columnSetting ?? [];
+
+ this.columns = currentColumnSetting;
+ this.setting = tableSettingsConfig;
+
+ this.pagination = {
+ ...this.pagination,
+ length: vm.count,
+ pageIndex,
+ pageSize: limit,
+ };
+ }),
+ );
+
+ this.subscriptions.push(
+ this.scicatDataSource.connect().subscribe((data) => {
+ const mapped = data.map((job) => ({
+ ...job,
+ jobId: job.id,
+ }));
+ this.dataSource.next(mapped);
+ }),
+ );
+
+ this.subscriptions.push(
+ this.scicatDataSource.count$.subscribe((count) => {
+ this.pagination = { ...this.pagination, length: count };
+ }),
+ );
+ }
+
+ onRowEvent(event: IRowEvent) {
+ if (event?.event === RowEventType.RowClick) {
+ const id = encodeURIComponent(event.sender.row.jobId);
+ this.router.navigateByUrl("/user/jobs/" + id);
+ }
}
- ngOnDestroy() {
- this.dataSource.disconnectExportData();
+ onPaginationChange(pagination: TablePagination) {
+ const pageIndex = pagination.pageIndex;
+ const pageSize = pagination.pageSize;
+ const newFilters = {
+ ...this.currentFilters,
+ skip: pageIndex * pageSize,
+ limit: pageSize,
+ };
+
+ this.currentFilters = newFilters;
+ this.loadData(newFilters, pageIndex, pageSize);
+ }
+
+ saveTableSettings(setting: ITableSetting) {
+ const columnsSetting = setting.columnSetting.map((column, index) => {
+ const { name, display, width } = column;
+
+ return { name, display, order: index, width };
+ });
+
+ this.store.dispatch(
+ updateUserSettingsAction({
+ property: { fe_job_table_columns: columnsSetting },
+ }),
+ );
+ }
+
+ onSettingChange(event: {
+ type: TableSettingEventType;
+ setting: ITableSetting;
+ }) {
+ if (
+ event.type === TableSettingEventType.save ||
+ event.type === TableSettingEventType.create
+ ) {
+ this.saveTableSettings(event.setting);
+ }
+ }
+
+ loadData(filters: any, pageIndex: number, pageSize: number) {
+ const sortField = filters?.sortField;
+ const [field, direction] = sortField ? sortField.split(":") : ["", "asc"];
+
+ this.scicatDataSource.loadAllData(
+ filters,
+ field,
+ direction,
+ pageIndex,
+ pageSize,
+ );
+ }
+
+ onGlobalTextSearchChange(text: string) {
+ this.globalTextSearch = text;
}
- onRowClick(job: JobsTableData) {
- const id = encodeURIComponent(job.id);
- this.router.navigateByUrl("/user/jobs/" + id);
+ onGlobalTextSearchAction() {
+ const newFilters = {
+ ...this.currentFilters,
+ skip: 0,
+ limit: this.pagination.pageSize,
+ globalSearch: this.globalTextSearch || undefined,
+ };
+
+ this.loadData(newFilters, 0, this.pagination.pageSize);
+ this.currentFilters = newFilters;
+ this.pagination = { ...this.pagination, pageIndex: 0 };
+ }
+
+ ngOnDestroy() {
+ this.scicatDataSource.disconnectExportData();
+ this.subscriptions.forEach((sub) => sub.unsubscribe());
}
}
diff --git a/src/app/jobs/jobs-detail/jobs-detail.component.ts b/src/app/jobs/jobs-detail/jobs-detail.component.ts
index 7f2c393549..277b07150f 100644
--- a/src/app/jobs/jobs-detail/jobs-detail.component.ts
+++ b/src/app/jobs/jobs-detail/jobs-detail.component.ts
@@ -35,7 +35,7 @@ export class JobsDetailComponent implements OnInit, OnDestroy {
});
this.job$.subscribe((job) => {
this.hasJobResultObject =
- Object.keys(job.jobResultObject || {}).length > 0;
+ Object.keys(job?.jobResultObject || {}).length > 0;
});
}
diff --git a/src/app/state-management/effects/user.effects.spec.ts b/src/app/state-management/effects/user.effects.spec.ts
index 6e2df86840..e5d1555335 100644
--- a/src/app/state-management/effects/user.effects.spec.ts
+++ b/src/app/state-management/effects/user.effects.spec.ts
@@ -619,6 +619,7 @@ describe("UserEffects", () => {
fe_sample_table_conditions: [],
fe_instrument_table_columns: [],
fe_file_table_columns: [],
+ fe_job_table_columns: [],
},
} as unknown as UserSettings;
const action = fromActions.fetchUserSettingsAction({ id });
diff --git a/src/app/state-management/models/index.ts b/src/app/state-management/models/index.ts
index 16ce256ff8..7c99f62e1c 100644
--- a/src/app/state-management/models/index.ts
+++ b/src/app/state-management/models/index.ts
@@ -19,6 +19,7 @@ export interface Settings {
fe_sample_table_conditions?: ConditionConfig[];
fe_instrument_table_columns?: TableColumn[];
fe_file_table_columns?: TableColumn[];
+ fe_job_table_columns?: TableColumn[];
}
export interface TableColumn {
@@ -220,6 +221,7 @@ export const SETTINGS_CONFIG = [
configKey: "columns",
},
{ key: "fe_file_table_columns", scope: "file", configKey: "columns" },
+ { key: "fe_job_table_columns", scope: "job", configKey: "columns" },
];
export type SettingScope =
@@ -227,7 +229,8 @@ export type SettingScope =
| "proposal"
| "sample"
| "instrument"
- | "file";
+ | "file"
+ | "job";
export type SettingKind = "columns" | "filters" | "conditions";
export const getSettingKey = (
diff --git a/src/app/state-management/selectors/jobs.selectors.ts b/src/app/state-management/selectors/jobs.selectors.ts
index 7d319b6322..448c7f878e 100644
--- a/src/app/state-management/selectors/jobs.selectors.ts
+++ b/src/app/state-management/selectors/jobs.selectors.ts
@@ -1,5 +1,6 @@
import { createFeatureSelector, createSelector } from "@ngrx/store";
import { JobsState } from "state-management/state/jobs.store";
+import { selectHasFetchedSettings, selectSettings } from "./user.selectors";
const selectJobState = createFeatureSelector("jobs");
@@ -48,3 +49,32 @@ export const selectQueryParams = createSelector(selectFilters, (filters) => {
return { order: sortField, skip, limit };
}
});
+
+export const selectJobsDashboardPageViewModel = createSelector(
+ selectJobs,
+ selectJobsCount,
+ selectPage,
+ selectJobsPerPage,
+ selectFilters,
+ selectSettings,
+ selectHasFetchedSettings,
+ (
+ jobs,
+ count,
+ currentPage,
+ jobsPerPage,
+ filters,
+ settings,
+ hasFetchedSettings,
+ ) => ({
+ jobs,
+ count,
+ currentPage,
+ jobsPerPage,
+ filters,
+ hasFetchedSettings,
+ tableSettings: {
+ columns: settings.fe_job_table_columns,
+ },
+ }),
+);
diff --git a/src/app/state-management/state/user.store.ts b/src/app/state-management/state/user.store.ts
index 9e01a3c661..63ea8e3b0d 100644
--- a/src/app/state-management/state/user.store.ts
+++ b/src/app/state-management/state/user.store.ts
@@ -79,6 +79,7 @@ export const initialUserState: UserState = {
fe_sample_table_conditions: [],
fe_instrument_table_columns: [],
fe_file_table_columns: [],
+ fe_job_table_columns: [],
}, // TODO sync with server settings?
message: undefined,