diff --git a/AMW_angular/io/src/app/deployment/deployment-filter.ts b/AMW_angular/io/src/app/deployment/deployment-filter.ts
index 7ea3a27bb..becea7163 100644
--- a/AMW_angular/io/src/app/deployment/deployment-filter.ts
+++ b/AMW_angular/io/src/app/deployment/deployment-filter.ts
@@ -1,10 +1,9 @@
-import { ComparatorFilterOption } from './comparator-filter-option';
+import { DateTimeModel } from '../shared/date-time-picker/date-time.model';
+
+export type FilterValue = string | number | boolean | DateTimeModel;
export interface DeploymentFilter {
name: string;
comp: string;
- val: any;
- type: string;
- compOptions: ComparatorFilterOption[];
- valOptions: string[];
+ val: FilterValue;
}
diff --git a/AMW_angular/io/src/app/deployment/filter-type.enum.ts b/AMW_angular/io/src/app/deployment/filter-type.enum.ts
new file mode 100644
index 000000000..66141592c
--- /dev/null
+++ b/AMW_angular/io/src/app/deployment/filter-type.enum.ts
@@ -0,0 +1,7 @@
+export enum FilterType {
+ BOOLEAN = 'booleanType',
+ DATE = 'DateType',
+ SPECIAL = 'SpecialFilterType',
+ ENUM = 'ENUM_TYPE',
+ STRING = 'StringType',
+}
diff --git a/AMW_angular/io/src/app/deployments/deployment-filter/deployment-filter.component.html b/AMW_angular/io/src/app/deployments/deployment-filter/deployment-filter.component.html
new file mode 100644
index 000000000..f0fc8d8ff
--- /dev/null
+++ b/AMW_angular/io/src/app/deployments/deployment-filter/deployment-filter.component.html
@@ -0,0 +1,59 @@
+
diff --git a/AMW_angular/io/src/app/deployments/deployment-filter/deployment-filter.component.spec.ts b/AMW_angular/io/src/app/deployments/deployment-filter/deployment-filter.component.spec.ts
new file mode 100644
index 000000000..cbc335024
--- /dev/null
+++ b/AMW_angular/io/src/app/deployments/deployment-filter/deployment-filter.component.spec.ts
@@ -0,0 +1,134 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
+import { provideHttpClientTesting } from '@angular/common/http/testing';
+import { DeploymentFilterComponent } from './deployment-filter.component';
+import { DeploymentService } from '../../deployment/deployment.service';
+import { DeploymentFilter } from '../../deployment/deployment-filter';
+import { of } from 'rxjs';
+
+describe('DeploymentFilterComponent', () => {
+ let component: DeploymentFilterComponent;
+ let fixture: ComponentFixture;
+ let deploymentService: DeploymentService;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [DeploymentFilterComponent],
+ providers: [DeploymentService, provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(DeploymentFilterComponent);
+ component = fixture.componentInstance;
+ deploymentService = TestBed.inject(DeploymentService);
+
+ fixture.componentRef.setInput('filter', { name: 'Test Filter', comp: 'eq', val: 'test' } as DeploymentFilter);
+ fixture.componentRef.setInput('index', 0);
+ fixture.componentRef.setInput('type', 'StringType');
+ fixture.componentRef.setInput('compOptions', [{ name: 'eq', displayName: 'is' }]);
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should load options for ENUM_TYPE filters and mark for check', () => {
+ const filter: DeploymentFilter = {
+ name: 'State',
+ comp: 'eq',
+ val: 'failed',
+ } as DeploymentFilter;
+
+ fixture.componentRef.setInput('filter', filter);
+ fixture.componentRef.setInput('index', 0);
+ fixture.componentRef.setInput('type', 'ENUM_TYPE');
+ fixture.componentRef.setInput('compOptions', [{ name: 'eq', displayName: 'is' }]);
+
+ const mockOptions = ['success', 'failed', 'canceled'];
+ vi.spyOn(deploymentService, 'getFilterOptionValues').mockImplementation(() => {
+ // Before async emission, the component should pre-seed valOptions with the current value
+ expect(component.valOptions()).toEqual(['failed']);
+ return of(mockOptions);
+ });
+
+ component.ngOnInit();
+
+ expect(deploymentService.getFilterOptionValues).toHaveBeenCalledWith('State');
+ expect(component.valOptions()).toEqual(mockOptions);
+ });
+
+ it('should set boolean options for booleanType filters and mark for check', () => {
+ const filter: DeploymentFilter = {
+ name: 'Confirmed',
+ comp: 'eq',
+ val: 'true',
+ } as DeploymentFilter;
+
+ fixture.componentRef.setInput('filter', filter);
+ fixture.componentRef.setInput('index', 0);
+ fixture.componentRef.setInput('type', 'booleanType');
+ fixture.componentRef.setInput('compOptions', [{ name: 'eq', displayName: 'is' }]);
+
+ component.ngOnInit();
+
+ expect(component.valOptions()).toEqual(['true', 'false']);
+ });
+
+ it('should not load options for SpecialFilterType', () => {
+ const filter: DeploymentFilter = {
+ name: 'Special',
+ comp: 'eq',
+ val: '',
+ } as DeploymentFilter;
+
+ fixture.componentRef.setInput('filter', filter);
+ fixture.componentRef.setInput('index', 0);
+ fixture.componentRef.setInput('type', 'SpecialFilterType');
+ fixture.componentRef.setInput('compOptions', []);
+
+ const spy = vi.spyOn(deploymentService, 'getFilterOptionValues');
+
+ component.ngOnInit();
+
+ expect(spy).not.toHaveBeenCalled();
+ expect(component.valOptions()).toEqual([]);
+ });
+
+ it('should not load options for DateType filters', () => {
+ const filter: DeploymentFilter = {
+ name: 'Date',
+ comp: 'eq',
+ val: '',
+ } as DeploymentFilter;
+
+ fixture.componentRef.setInput('filter', filter);
+ fixture.componentRef.setInput('index', 0);
+ fixture.componentRef.setInput('type', 'DateType');
+ fixture.componentRef.setInput('compOptions', []);
+
+ const spy = vi.spyOn(deploymentService, 'getFilterOptionValues');
+
+ component.ngOnInit();
+
+ expect(spy).not.toHaveBeenCalled();
+ expect(component.valOptions()).toEqual([]);
+ });
+
+ it('should emit remove event', () => {
+ const filter: DeploymentFilter = {
+ name: 'State',
+ comp: 'eq',
+ val: 'failed',
+ } as DeploymentFilter;
+
+ fixture.componentRef.setInput('filter', filter);
+ fixture.componentRef.setInput('index', 0);
+ fixture.componentRef.setInput('type', 'ENUM_TYPE');
+
+ let emittedFilter: DeploymentFilter | undefined;
+ component.remove.subscribe((f) => (emittedFilter = f));
+
+ component.onRemove();
+
+ expect(emittedFilter).toBe(filter);
+ });
+});
diff --git a/AMW_angular/io/src/app/deployments/deployment-filter/deployment-filter.component.ts b/AMW_angular/io/src/app/deployments/deployment-filter/deployment-filter.component.ts
new file mode 100644
index 000000000..14e7139ad
--- /dev/null
+++ b/AMW_angular/io/src/app/deployments/deployment-filter/deployment-filter.component.ts
@@ -0,0 +1,61 @@
+import {
+ ChangeDetectionStrategy,
+ Component,
+ inject,
+ input,
+ OnInit,
+ output,
+ signal,
+} from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { DeploymentFilter } from '../../deployment/deployment-filter';
+import { ComparatorFilterOption } from '../../deployment/comparator-filter-option';
+import { DeploymentService } from '../../deployment/deployment.service';
+import { FilterType } from '../../deployment/filter-type.enum';
+import { ButtonComponent } from '../../shared/button/button.component';
+import { IconComponent } from '../../shared/icon/icon.component';
+import { DateTimePickerComponent } from '../../shared/date-time-picker/date-time-picker.component';
+
+@Component({
+ selector: 'app-deployment-filter',
+ templateUrl: './deployment-filter.component.html',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ imports: [CommonModule, FormsModule, ButtonComponent, IconComponent, DateTimePickerComponent],
+})
+export class DeploymentFilterComponent implements OnInit {
+ private deploymentService = inject(DeploymentService);
+ protected readonly FilterType = FilterType;
+
+ filter = input.required();
+ index = input.required();
+ type = input.required();
+ compOptions = input([]);
+ remove = output();
+
+ valOptions = signal([]);
+
+ ngOnInit() {
+ // Pre-seed options with the current value so the select can render it before async options load
+ if (this.filter()?.val) {
+ this.valOptions.set([String(this.filter().val)]);
+ }
+ this.loadOptions();
+ }
+
+ private loadOptions() {
+ if (this.type() === FilterType.BOOLEAN) {
+ this.valOptions.set(['true', 'false']);
+ } else if (this.type() !== FilterType.SPECIAL && this.type() !== FilterType.DATE) {
+ this.deploymentService.getFilterOptionValues(this.filter().name).subscribe({
+ next: (options) => {
+ this.valOptions.set(options);
+ },
+ });
+ }
+ }
+
+ onRemove() {
+ this.remove.emit(this.filter());
+ }
+}
diff --git a/AMW_angular/io/src/app/deployments/deployments.component.html b/AMW_angular/io/src/app/deployments/deployments.component.html
index 11d52f47d..beade6ee5 100644
--- a/AMW_angular/io/src/app/deployments/deployments.component.html
+++ b/AMW_angular/io/src/app/deployments/deployments.component.html
@@ -21,77 +21,23 @@
[(ngModel)]="selectedFilterType"
(change)="addFilter()"
>
- @for (filterType of filterTypes; track filterType.name) {
+ @for (filterType of filterTypes(); track filterType.name) {
}
- @if (filters.length > 0) {
- @for (filter of filters; track $index; let i = $index) {
-
+ @if (filters().length > 0) {
+ @for (filter of filters(); track $index; let i = $index) {
+
+
}
}
@@ -103,7 +49,7 @@
Clear filters Clipboard
@@ -168,7 +114,7 @@
[deployments]="deployments()"
[sortCol]="sortCol"
[sortDirection]="sortDirection"
- [filtersForParam]="filtersForParam"
+ [filtersForParam]="filters()"
(doConfirmDeployment)="confirmDeployment($event)"
(doCancelDeployment)="cancelDeployment($event)"
(doRejectDeployment)="rejectDeployment($event)"
diff --git a/AMW_angular/io/src/app/deployments/deployments.component.spec.ts b/AMW_angular/io/src/app/deployments/deployments.component.spec.ts
index 2ceecaf1d..f64e0be20 100644
--- a/AMW_angular/io/src/app/deployments/deployments.component.spec.ts
+++ b/AMW_angular/io/src/app/deployments/deployments.component.spec.ts
@@ -9,6 +9,7 @@ import { DeploymentService } from '../deployment/deployment.service';
import { DeploymentsListComponent } from './deployments-list.component';
import { DeploymentsEditModalComponent } from './deployments-edit-modal.component';
import { DeploymentFilterType } from '../deployment/deployment-filter-type';
+import { FilterType } from '../deployment/filter-type.enum';
import { ComparatorFilterOption } from '../deployment/comparator-filter-option';
import { DeploymentFilter } from '../deployment/deployment-filter';
import { Deployment } from '../deployment/deployment';
@@ -81,7 +82,7 @@ describe('DeploymentsComponent (with query params)', () => {
expect(deploymentService.canRequestDeployments).toHaveBeenCalled();
});
- it('should enhance filters with the right comparator and comparator options on ngOnInit', () => {
+ it('should enhance filters with the right comparator on ngOnInit', () => {
// given
const deploymentFilters: DeploymentFilterType[] = [
{ name: 'Application', type: 'StringType' },
@@ -101,13 +102,13 @@ describe('DeploymentsComponent (with query params)', () => {
activatedRouteStub.queryParams.next({ filters: filter });
// then
- expect(component.paramFilters[0].compOptions.length).toEqual(1);
- expect(component.paramFilters[1].compOptions.length).toEqual(3);
+ expect(component.paramFilters[0].name).toEqual('Application');
expect(component.paramFilters[0].comp).toEqual('eq');
+ expect(component.paramFilters[1].name).toEqual('Confirmed on');
expect(component.paramFilters[1].comp).toEqual('lt');
});
- it('should enhance filters with the right option values on ngOnInit', () => {
+ it('should enhance filters with correct filter types on ngOnInit', () => {
// given
const deploymentFilters: DeploymentFilterType[] = [
{ name: 'Application', type: 'StringType' },
@@ -115,15 +116,6 @@ describe('DeploymentsComponent (with query params)', () => {
];
vi.spyOn(deploymentService, 'getAllDeploymentFilterTypes').mockReturnValue(of(deploymentFilters));
vi.spyOn(deploymentService, 'getAllComparatorFilterOptions').mockReturnValue(of([]));
- vi.spyOn(deploymentService, 'getFilterOptionValues').mockImplementation((param: string) => {
- const optionValues: {
- [key: string]: string[];
- } = {
- Application: ['app1', 'app2'],
- 'Confirmed on': [],
- };
- return of(optionValues[param]);
- });
vi.spyOn(deploymentService, 'canRequestDeployments').mockReturnValue(of(true));
// when
@@ -131,8 +123,36 @@ describe('DeploymentsComponent (with query params)', () => {
activatedRouteStub.queryParams.next({ filters: filter });
// then
- expect(component.paramFilters[0].valOptions.length).toEqual(2);
- expect(component.paramFilters[1].valOptions.length).toEqual(0);
+ expect(component.paramFilters[0].name).toEqual('Application');
+ expect(component.getFilterType('Application')).toEqual(FilterType.STRING);
+ expect(component.paramFilters[1].name).toEqual('Confirmed on');
+ expect(component.getFilterType('Confirmed on')).toEqual(FilterType.DATE);
+ });
+
+ it('should set URL filter value immediately while component loads options asynchronously', () => {
+ // given
+ const stateFilter = JSON.stringify([{ name: 'State', comp: 'eq', val: 'failed' }]);
+ const deploymentFilters: DeploymentFilterType[] = [{ name: 'State', type: 'ENUM_TYPE' }];
+
+ vi.spyOn(deploymentService, 'getAllDeploymentFilterTypes').mockReturnValue(of(deploymentFilters));
+ vi.spyOn(deploymentService, 'getAllComparatorFilterOptions').mockReturnValue(of([]));
+ vi.spyOn(deploymentService, 'canRequestDeployments').mockReturnValue(of(true));
+ vi.spyOn(deploymentService, 'getFilteredDeployments').mockReturnValue(of({ deployments: [], total: 0 }));
+
+ // when
+ component.ngOnInit();
+ activatedRouteStub.queryParams.next({ filters: stateFilter });
+
+ // then
+ expect(component.filters().length).toBe(1);
+ const filterResult = component.filters()[0];
+
+ // The filter value should be preserved as 'failed' immediately
+ expect(filterResult.val).toEqual('failed');
+ // Filter should have the correct type in filterTypes
+ expect(component.getFilterType('State')).toEqual('ENUM_TYPE');
+ // Component will load options asynchronously
+ expect(filterResult.name).toEqual('State');
});
it('should apply filters ngOnInit ', () => {
@@ -165,7 +185,7 @@ describe('DeploymentsComponent (with query params)', () => {
// then
expect(deploymentService.getFilteredDeploymentsForCsvExport).toHaveBeenCalledWith(
- JSON.stringify(component.filtersForBackend),
+ JSON.stringify([]),
'd.deploymentDate',
'DESC',
);
@@ -378,14 +398,14 @@ describe('DeploymentsComponent (without query params)', () => {
it('should remove filter and reset offset on removeFilter', () => {
// given
component.offset = 10;
- component.filters = [
+ component.filters.set([
{
name: 'Confirmed',
comp: 'eq',
val: 'true',
type: 'booleanType',
} as DeploymentFilter,
- ];
+ ]);
const deploymentFilters: DeploymentFilterType[] = [
{ name: 'Application', type: 'StringType' },
{ name: 'Confirmed on', type: 'DateType' },
@@ -399,24 +419,24 @@ describe('DeploymentsComponent (without query params)', () => {
vi.spyOn(deploymentService, 'getAllComparatorFilterOptions').mockReturnValue(of(comparatorOptions));
// when
- component.removeFilter(component.filters[0]);
+ component.removeFilter(component.filters()[0]);
// then
- expect(component.filters.length).toEqual(0);
+ expect(component.filters().length).toEqual(0);
expect(component.offset).toEqual(0);
});
it('should reset offset on setMaxResultsPerPage', () => {
// given
component.offset = 10;
- component.filters = [
+ component.filters.set([
{
name: 'Confirmed',
comp: 'eq',
val: 'true',
type: 'booleanType',
} as DeploymentFilter,
- ];
+ ]);
// when
component.setMaxResultsPerPage(20);
@@ -486,7 +506,7 @@ describe('DeploymentsComponent (without query params)', () => {
];
const deploymentFilters: DeploymentFilterType[] = [
{ name: 'Application', type: 'StringType' },
- { name: 'Confirmed on', type: 'DateType' },
+ { name: 'Confirmed', type: 'booleanType' },
];
const comparatorOptions: ComparatorFilterOption[] = [
{ name: 'lt', displayName: '<' },
@@ -497,21 +517,19 @@ describe('DeploymentsComponent (without query params)', () => {
vi.spyOn(deploymentService, 'getAllComparatorFilterOptions').mockReturnValue(of(comparatorOptions));
vi.spyOn(deploymentService, 'getFilteredDeployments').mockReturnValue(of({ deployments: [], total: 0 }));
- // given
- component.filters = [
+ component.filterTypes.set(deploymentFilters);
+ component.filters.set([
{
name: 'Confirmed',
comp: 'eq',
val: 'true',
- type: 'booleanType',
} as DeploymentFilter,
{
name: 'Application',
comp: 'eq',
val: 'TestApp',
- type: 'StringType',
} as DeploymentFilter,
- ];
+ ]);
// when
component.applyFilters();
@@ -538,7 +556,7 @@ describe('DeploymentsComponent (without query params)', () => {
const deploymentFilters: DeploymentFilterType[] = [
{ name: 'Application', type: 'StringType' },
{ name: 'Application Server', type: 'StringType' },
- { name: 'Confirmed on', type: 'DateType' },
+ { name: 'Confirmed', type: 'booleanType' },
{ name: 'Latest deployment', type: 'SpecialFilterType' },
];
const comparatorOptions: ComparatorFilterOption[] = [
@@ -550,38 +568,35 @@ describe('DeploymentsComponent (without query params)', () => {
vi.spyOn(deploymentService, 'getAllComparatorFilterOptions').mockReturnValue(of(comparatorOptions));
vi.spyOn(deploymentService, 'getFilteredDeployments').mockReturnValue(of({ deployments: [], total: 0 }));
- component.filters = [
+ component.filterTypes.set(deploymentFilters);
+ component.filters.set([
{
name: 'Confirmed',
comp: 'eq',
val: 'false',
- type: 'booleanType',
} as DeploymentFilter,
{
name: 'Application',
comp: 'eq',
val: 'TestApp',
- type: 'StringType',
} as DeploymentFilter,
{
name: 'Application Server',
comp: 'eq',
val: '',
- type: 'StringType',
} as DeploymentFilter,
{
name: 'Latest deployment',
comp: 'eq',
val: '',
- type: 'SpecialFilterType',
} as DeploymentFilter,
- ];
+ ]);
// when
component.applyFilters();
// then
- expect(component.filters.length).toEqual(3);
+ expect(component.filters().length).toEqual(3);
expect(sessionStorage.getItem('deploymentFilters')).toEqual(JSON.stringify(expectedFilters));
expect(deploymentService.getFilteredDeployments).toHaveBeenCalledWith(
JSON.stringify(expectedFilters),
@@ -609,7 +624,7 @@ describe('DeploymentsComponent (without query params)', () => {
{ name: 'Confirmed', comp: 'eq', val: 'true' } as DeploymentFilter,
{ name: 'Application', comp: 'eq', val: 'TestApp' } as DeploymentFilter,
];
- component.filters = [
+ component.filters.set([
{
name: 'Confirmed',
comp: 'eq',
@@ -622,7 +637,7 @@ describe('DeploymentsComponent (without query params)', () => {
val: 'TestApp',
type: 'StringType',
} as DeploymentFilter,
- ];
+ ]);
const deploymentFilters: DeploymentFilterType[] = [
{ name: 'Application', type: 'StringType' },
{ name: 'Confirmed on', type: 'DateType' },
diff --git a/AMW_angular/io/src/app/deployments/deployments.component.ts b/AMW_angular/io/src/app/deployments/deployments.component.ts
index 6f281502e..f4f53a50d 100644
--- a/AMW_angular/io/src/app/deployments/deployments.component.ts
+++ b/AMW_angular/io/src/app/deployments/deployments.component.ts
@@ -1,11 +1,12 @@
import { Location } from '@angular/common';
-import { ChangeDetectionStrategy, Component, OnInit, ViewChild, inject, signal } from '@angular/core';
+import { ChangeDetectionStrategy, Component, OnInit, ViewChild, computed, inject, signal } from '@angular/core';
import { FormsModule, NgModel } from '@angular/forms';
import { ActivatedRoute, Params } from '@angular/router';
import * as _ from 'lodash-es';
import * as datefns from 'date-fns';
import { Subscription, timer } from 'rxjs';
import { DeploymentFilter } from '../deployment/deployment-filter';
+import { FilterType } from '../deployment/filter-type.enum';
import { DeploymentFilterType } from '../deployment/deployment-filter-type';
import { ComparatorFilterOption } from '../deployment/comparator-filter-option';
import { Deployment } from '../deployment/deployment';
@@ -16,12 +17,12 @@ import { DateTimeModel } from '../shared/date-time-picker/date-time.model';
import { PaginationComponent } from '../shared/pagination/pagination.component';
import { DeploymentsListComponent } from './deployments-list.component';
import { IconComponent } from '../shared/icon/icon.component';
-import { DateTimePickerComponent } from '../shared/date-time-picker/date-time-picker.component';
import { NotificationComponent } from '../shared/elements/notification/notification.component';
import { LoadingIndicatorComponent } from '../shared/elements/loading-indicator.component';
import { PageComponent } from '../layout/page/page.component';
import { ToastService } from '../shared/elements/toast/toast.service';
import { ButtonComponent } from '../shared/button/button.component';
+import { DeploymentFilterComponent } from './deployment-filter/deployment-filter.component';
@Component({
selector: 'app-deployments',
@@ -31,12 +32,12 @@ import { ButtonComponent } from '../shared/button/button.component';
LoadingIndicatorComponent,
NotificationComponent,
FormsModule,
- DateTimePickerComponent,
IconComponent,
DeploymentsListComponent,
PaginationComponent,
PageComponent,
ButtonComponent,
+ DeploymentFilterComponent,
],
})
export class DeploymentsComponent implements OnInit {
@@ -52,29 +53,32 @@ export class DeploymentsComponent implements OnInit {
paramFilters: DeploymentFilter[] = [];
autoload = true;
- // enhanced filters for deployment service
- filtersForBackend: DeploymentFilter[] = [];
- // value of filters parameter. Used to pass as json object to the logView.xhtml
- filtersForParam: DeploymentFilter[] = [];
-
// valid for all, loaded once
- filterTypes: DeploymentFilterType[] = [];
+ filterTypes = signal([]);
+ private filterTypeMap = computed(() => {
+ const map = new Map();
+ this.filterTypes().forEach(filterType => {
+ const validFilterTypes = Object.values(FilterType);
+ if (validFilterTypes.includes(filterType.type as FilterType)) {
+ map.set(filterType.name, filterType.type as FilterType);
+ }
+ });
+ return map;
+ });
comparatorOptions: ComparatorFilterOption[] = [];
comparatorOptionsMap: { [key: string]: string } = {};
+ singleComparatorOption: ComparatorFilterOption[] = [{ name: 'eq', displayName: 'is' }];
hasPermissionToRequestDeployments = false;
csvSeparator = '';
// available edit actions
deploymentDate: number; // for deployment date change
- // available filterValues (if any)
- filterValueOptions: { [key: string]: string[] } = {};
-
// to be added
selectedFilterType: DeploymentFilterType;
// already set
- filters: DeploymentFilter[] = [];
+ filters = signal([]);
// filtered deployments
deployments = signal([]);
@@ -129,11 +133,8 @@ export class DeploymentsComponent implements OnInit {
const newFilter: DeploymentFilter = {} as DeploymentFilter;
newFilter.name = this.selectedFilterType.name;
newFilter.comp = this.defaultComparator;
- newFilter.val = this.selectedFilterType.type === 'booleanType' ? 'true' : '';
- newFilter.type = this.selectedFilterType.type;
- newFilter.compOptions = this.comparatorOptionsForType(this.selectedFilterType.type);
- this.setValueOptionsForFilter(newFilter);
- this.filters.push(newFilter);
+ newFilter.val = this.selectedFilterType.type === FilterType.BOOLEAN ? 'true' : '';
+ this.filters.update((filters) => [...filters, newFilter]);
this.offset = 0;
this.selectedFilterType = null;
this.selectModel.reset(null);
@@ -141,63 +142,44 @@ export class DeploymentsComponent implements OnInit {
}
removeFilter(filter: DeploymentFilter) {
- const i: number = _.findIndex(this.filters, {
+ const i: number = _.findIndex(this.filters(), {
name: filter.name,
comp: filter.comp,
val: filter.val,
});
if (i !== -1) {
- this.filters.splice(i, 1);
+ this.filters.update((filters) => {
+ const newFilters = [...filters];
+ newFilters.splice(i, 1);
+ return newFilters;
+ });
}
this.offset = 0;
}
clearFilters() {
- this.filters = [];
+ this.filters.set([]);
sessionStorage.setItem('deploymentFilters', null);
this.updateFiltersInURL(null);
}
applyFilters() {
- this.filtersForBackend = [];
- this.filtersForParam = [];
const filtersToBeRemoved: DeploymentFilter[] = [];
this.errorMessage = '';
- this.filters.forEach((filter) => {
- if (filter.val || filter.type === 'SpecialFilterType') {
- this.filtersForParam.push({
- name: filter.name,
- comp: filter.comp,
- val: filter.val,
- } as DeploymentFilter);
- if (filter.type === 'DateType') {
- if (!filter.val) {
- this.errorMessage = 'Invalid date';
- }
- this.filtersForBackend.push({
- name: filter.name,
- comp: filter.comp,
- val: filter.val.toEpoch().toString(),
- } as DeploymentFilter);
- } else {
- this.filtersForBackend.push({
- name: filter.name,
- comp: filter.comp,
- val: filter.val,
- } as DeploymentFilter);
- }
- } else {
+ // Remove empty filters first
+ this.filters().forEach((filter) => {
+ const filterType = this.getFilterType(filter.name);
+ if (!filter.val && filterType !== 'SpecialFilterType') {
filtersToBeRemoved.push(filter);
+ } else if (filterType === FilterType.DATE && !filter.val) {
+ this.errorMessage = 'Invalid date';
}
});
filtersToBeRemoved.forEach((filter) => this.removeFilter(filter));
if (!this.errorMessage) {
- this.getFilteredDeployments(JSON.stringify(this.filtersForBackend));
- let filterString: string = null;
- if (this.filtersForParam.length > 0) {
- filterString = JSON.stringify(this.filtersForParam);
- }
+ this.getFilteredDeployments(this.buildBackendFilters());
+ const filterString = this.filters().length > 0 ? JSON.stringify(this.filters()) : null;
sessionStorage.setItem('deploymentFilters', filterString);
this.updateFiltersInURL(filterString);
}
@@ -277,7 +259,7 @@ export class DeploymentsComponent implements OnInit {
exportCSV() {
this.isLoading.set(true);
this.errorMessage = 'Generating your CSV.
Please hold on, depending on the requested data this may take a while';
- this.getFilteredDeploymentsForCsvExport(JSON.stringify(this.filtersForBackend));
+ this.getFilteredDeploymentsForCsvExport(this.buildBackendFilters());
}
async copyURL() {
@@ -323,16 +305,20 @@ export class DeploymentsComponent implements OnInit {
autoRefresh() {
if (this.refreshInterval > 0 && !this.timerSubscription) {
this.timerSubscription = timer(this.refreshInterval * 1000).subscribe(() => {
- this.getFilteredDeployments(JSON.stringify(this.filtersForBackend));
+ this.getFilteredDeployments(this.buildBackendFilters());
this.timerSubscription = null;
});
}
}
+ getFilterType(filterName: string): FilterType | undefined {
+ return this.filterTypeMap().get(filterName);
+ }
+
private canFilterBeAdded(): boolean {
return (
this.selectedFilterType.name !== 'Latest deployment job for App Server and Env' ||
- _.findIndex(this.filters, { name: this.selectedFilterType.name }) === -1
+ _.findIndex(this.filters(), { name: this.selectedFilterType.name }) === -1
);
}
@@ -373,22 +359,28 @@ export class DeploymentsComponent implements OnInit {
}
private comparatorOptionsForType(filterType: string) {
- if (filterType === 'booleanType' || filterType === 'StringType' || filterType === 'ENUM_TYPE') {
- return [{ name: 'eq', displayName: 'is' }];
+ if (filterType === FilterType.BOOLEAN || filterType === FilterType.STRING || filterType === FilterType.ENUM) {
+ return this.singleComparatorOption;
} else {
return this.comparatorOptions;
}
}
- private setValueOptionsForFilter(filter: DeploymentFilter) {
- if (!this.filterValueOptions[filter.name]) {
- if (filter.type === 'booleanType') {
- filter.valOptions = this.filterValueOptions[filter.name] = ['true', 'false'];
- } else {
- this.getAndSetFilterOptionValues(filter);
- }
- }
- filter.valOptions = this.filterValueOptions[filter.name];
+ comparatorOptionsForFilterType(filterType: string) {
+ return this.comparatorOptionsForType(filterType);
+ }
+
+ private buildBackendFilters(): string {
+ const filters = this.filters().map(
+ (filter) =>
+ ({
+ name: filter.name,
+ comp: filter.comp,
+ val: this.getFilterType(filter.name) === FilterType.DATE
+ && filter.val instanceof DateTimeModel ? filter.val.toEpoch().toString() : filter.val,
+ }) as DeploymentFilter,
+ );
+ return JSON.stringify(filters);
}
private mapStates() {
@@ -422,7 +414,7 @@ export class DeploymentsComponent implements OnInit {
private initTypeAndOptions() {
this.isLoading.set(true);
this.deploymentService.getAllDeploymentFilterTypes().subscribe({
- next: (r) => (this.filterTypes = _.sortBy(r, 'name')),
+ next: (r) => this.filterTypes.set(_.sortBy(r, 'name')),
error: (e) => (this.errorMessage = e),
complete: () => {
this.getAllComparatorOptions();
@@ -441,14 +433,6 @@ export class DeploymentsComponent implements OnInit {
});
}
- private getAndSetFilterOptionValues(filter: DeploymentFilter) {
- this.deploymentService.getFilterOptionValues(filter.name).subscribe({
- next: (r) => (this.filterValueOptions[filter.name] = r),
- error: (e) => (this.errorMessage = e),
- complete: () => (filter.valOptions = this.filterValueOptions[filter.name]),
- });
- }
-
private getFilteredDeployments(filterString: string) {
this.isLoading.set(true);
this.deploymentService
@@ -490,31 +474,34 @@ export class DeploymentsComponent implements OnInit {
}
private enhanceParamFilter() {
- if (this.paramFilters) {
+ if (this.paramFilters && this.paramFilters.length > 0) {
this.clearFilters();
+ const enhancedFilters: DeploymentFilter[] = [];
+
this.paramFilters.forEach((filter) => {
- const i: number = _.findIndex(this.filterTypes, ['name', filter.name]);
- if (i >= 0) {
- filter.type = this.filterTypes[i].type;
- filter.compOptions = this.comparatorOptionsForType(filter.type);
+ const filterType = this.getFilterType(filter.name);
+ if (filterType) {
filter.comp = !filter.comp ? this.defaultComparator : filter.comp;
- this.parseDateTime(filter);
- this.setValueOptionsForFilter(filter);
- this.filters.push(filter);
+ this.parseDateTime(filter, filterType);
+ enhancedFilters.push(filter);
} else {
this.errorMessage = 'Error parsing filter';
}
});
- }
- if (this.autoload) {
+
+ this.filters.set(enhancedFilters);
+ if (this.autoload) {
+ this.applyFilters();
+ }
+ } else if (this.autoload) {
this.applyFilters();
}
}
// parse string from json back to DateTimeModel
- private parseDateTime(filter: DeploymentFilter) {
- if (filter.type === 'DateType') {
- filter.val = DateTimeModel.fromLocalString(filter.val);
+ private parseDateTime(filter: DeploymentFilter, filterType: string) {
+ if (filterType === FilterType.DATE) {
+ filter.val = DateTimeModel.fromLocalString(filter.val as string);
}
}