Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
838a94b
add EDC Contract Definition step to offer
Mortega5 Jun 10, 2026
cbd346d
set conditional contract definitino
Mortega5 Jun 11, 2026
7a5aced
refactor dsc product spec form
Mortega5 Jun 11, 2026
f82d4c0
refactor update product spec to include dsp config
Mortega5 Jun 11, 2026
524cf12
fix edc-contract-definition update
Mortega5 Jun 16, 2026
219f5e5
restore json render in product spec
Mortega5 Jun 16, 2026
5692729
fix: continue loading offers when individual item requests fail
Mortega5 Jun 17, 2026
7efbc5a
update e2e tests
Mortega5 Jun 19, 2026
ebd8180
Add tests to new components
Mortega5 Jun 19, 2026
4305485
add data-cy attributes to dsp fields
Mortega5 Jun 22, 2026
736eafb
update tsconfig for Angular 17 and TypeScript 5.2
Mortega5 Jun 24, 2026
d6c4873
fix(style): migrate all moment.js imports to default import style
Mortega5 Jun 24, 2026
600a5f6
invoice must show new customer bills (#267)
sluFicodes Jun 25, 2026
0e41cbe
Add support to google tag manager (#277)
fdelavega Jul 2, 2026
a38e47f
Fix navigation of services from the landing page (#278)
fdelavega Jul 6, 2026
1414754
Merge branch 'main' into feat/support-dps
fdelavega Jul 9, 2026
42ae0f3
add EDC Contract Definition step to offer
Mortega5 Jun 10, 2026
fd353bd
set conditional contract definitino
Mortega5 Jun 11, 2026
f47902b
refactor dsc product spec form
Mortega5 Jun 11, 2026
3a28c4e
refactor update product spec to include dsp config
Mortega5 Jun 11, 2026
45ca8eb
fix edc-contract-definition update
Mortega5 Jun 16, 2026
a8ca59f
restore json render in product spec
Mortega5 Jun 16, 2026
44f6416
fix: continue loading offers when individual item requests fail
Mortega5 Jun 17, 2026
d86bb93
update e2e tests
Mortega5 Jun 19, 2026
eece0dd
Add tests to new components
Mortega5 Jun 19, 2026
7065b84
add data-cy attributes to dsp fields
Mortega5 Jun 22, 2026
ae4a75e
update tsconfig for Angular 17 and TypeScript 5.2
Mortega5 Jun 24, 2026
55981c4
fix(style): migrate all moment.js imports to default import style
Mortega5 Jun 24, 2026
f1c9a01
add transferPath characteristic to dsp product-spec
Mortega5 Jun 29, 2026
e107129
add transferType characteristic to dsp product-spec
Mortega5 Jun 29, 2026
6ad0127
do not featch search next page details twice
Mortega5 Jul 15, 2026
d68a2a4
fix loadMore loading spinner
Mortega5 Jul 15, 2026
58c0d0d
Merge main
fdelavega Jul 21, 2026
d74fbe1
Merge remote changesz
fdelavega Jul 21, 2026
1cbb8d7
Merge branch 'feat/eds-contract-definition' into feat/support-dps
fdelavega Jul 21, 2026
c47650d
Remove duplicate import
fdelavega Jul 21, 2026
baec13f
fix DOM listener memory leak in seller and inventory components
Mortega5 Jul 22, 2026
b3116ec
Merge branch 'feat/eds-contract-definition' of https://github.com/Mor…
fdelavega Jul 22, 2026
18a227b
Merge main
fdelavega Jul 24, 2026
941aee1
Add DSP enabled as feature flag
fdelavega Jul 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cypress/e2e/blog.cy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { loginAcc, blogEntry, local_items } from "../support/constants"
import * as moment from 'moment';
import moment from 'moment';

describe('/blog',{
viewportHeight: 800,
Expand Down
2 changes: 1 addition & 1 deletion cypress/e2e/dashboard.cy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { category_launched, init_config, init_stat, local_items, login_token, product_offering, checkHeaderPostLogin, checkHeaderPreLogin, loginAcc } from "../support/constants"
import * as moment from 'moment';
import moment from 'moment';

describe('/dashboard',{
viewportHeight: 800,
Expand Down
1 change: 1 addition & 0 deletions cypress/support/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const init_config = {
'quotesEnabled': false,
'tenderingEnabled': false,
'dataSpaceEnabled': false,
'dspEnabled': false,
'launchValidationEnabled': false,
'tenderDevButtonsOpenCloseEnabled': false,
'aiEnabled': false,
Expand Down
2 changes: 1 addition & 1 deletion src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Router } from '@angular/router';
import { LoginInfo } from 'src/app/models/interfaces';
import { ApiServiceService } from 'src/app/services/product-service.service';
import { RefreshLoginServiceService } from "src/app/services/refresh-login-service.service";
import * as moment from 'moment';
import moment from 'moment';
import {ThemeService} from "./services/theme.service";
import {environment} from "../environments/environment";
import { filter } from 'rxjs';
Expand Down
8 changes: 8 additions & 0 deletions src/app/data/featuresConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type FeatureFlagKey =
| 'quotesEnabled'
| 'tenderingEnabled'
| 'dataSpaceEnabled'
| 'dspEnabled'
| 'launchValidationEnabled'
| 'tenderDevButtonsOpenCloseEnabled'
| 'aiEnabled';
Expand Down Expand Up @@ -45,6 +46,11 @@ export const FEATURE_FLAG_DEFINITIONS: FeatureFlagDefinition[] = [
label: 'Data space',
description: 'Enable data space fields in organization and offer forms.'
},
{
key: 'dspEnabled',
label: 'DSP',
description: 'Enable DSP-related data space contract and schema fields.'
},
{
key: 'launchValidationEnabled',
label: 'Launch validation',
Expand Down Expand Up @@ -76,6 +82,7 @@ export function readFeaturesConfig(config: any): FeaturesConfig {
result.quotesEnabled = readBoolean(source, 'quotesEnabled');
result.tenderingEnabled = readBoolean(source, 'tenderingEnabled');
result.dataSpaceEnabled = readBoolean(source, 'dataSpaceEnabled');
result.dspEnabled = readBoolean(source, 'dspEnabled');
result.launchValidationEnabled = readBoolean(source, 'launchValidationEnabled');
result.tenderDevButtonsOpenCloseEnabled = readBoolean(source, 'tenderDevButtonsOpenCloseEnabled');
result.aiEnabled = readBoolean(source, 'aiEnabled');
Expand All @@ -91,6 +98,7 @@ export function applyRuntimeFeaturesConfig(config: any): void {
environment.QUOTES_ENABLED = features.quotesEnabled ?? false;
environment.TENDER_ENABLED = features.tenderingEnabled ?? false;
environment.DATA_SPACE_ENABLED = features.dataSpaceEnabled ?? false;
environment.DSP_ENABLED = features.dspEnabled ?? environment.DSP_ENABLED;
environment.LAUNCH_VALIDATION_ENABLED = features.launchValidationEnabled ?? false;
environment.TENDER_DEV_BUTTONS_OPEN_CLOSE_ENABLED = features.tenderDevButtonsOpenCloseEnabled ?? false;
environment.AI_SEARCH_ENABLED = features.aiEnabled ?? false;
Expand Down
2 changes: 1 addition & 1 deletion src/app/guard/auth.guard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { TranslateModule } from '@ngx-translate/core';
import { RouterTestingModule } from '@angular/router/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
import * as moment from 'moment';
import moment from 'moment';
import { LocalStorageService } from '../services/local-storage.service';
import { AuthGuard } from './auth.guard';

Expand Down
2 changes: 1 addition & 1 deletion src/app/guard/auth.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from
import {LocalStorageService} from "../services/local-storage.service";
import { Observable } from 'rxjs';
import { LoginInfo } from '../models/interfaces';
import * as moment from 'moment';
import moment from 'moment';
import { environment } from 'src/environments/environment';

@Injectable({
Expand Down
2 changes: 1 addition & 1 deletion src/app/interceptors/requests-interceptor.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import * as moment from 'moment';
import moment from 'moment';
import { environment } from 'src/environments/environment';
import { PROVIDER_COUNTRY_LIST_URL } from '../models/search-organizations-filters.model';
import { LocalStorageService } from '../services/local-storage.service';
Expand Down
2 changes: 1 addition & 1 deletion src/app/interceptors/requests-interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
import { Observable } from 'rxjs';
import {LocalStorageService} from "../services/local-storage.service";
import { LoginInfo } from '../models/interfaces';
import * as moment from 'moment';
import moment from 'moment';
import { environment } from 'src/environments/environment';

export function shouldAttachAuthHeaders(requestUrl: string, internalBaseUrls: string[]): boolean {
Expand Down
37 changes: 37 additions & 0 deletions src/app/models/formFields/form-field.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
interface BaseFormField {
name: string;
label: string;
required?: boolean;
colSpan?: number;
defaultValue?: any;
}

export interface StringFormField extends BaseFormField {
type: 'string';
maxLength?: number;
placeholder?: string;
}

export interface NumberFormField extends BaseFormField {
type: 'number';
min?: number;
max?: number;
step?: number;
}

export interface SelectOption {
value: string;
label: string;
}

export interface SelectableFormField extends BaseFormField {
type: 'select';
options: SelectOption[];
multiple?: boolean;
}

export interface BooleanFormField extends BaseFormField {
type: 'boolean';
}

export type FormField = StringFormField | NumberFormField | SelectableFormField | BooleanFormField;
196 changes: 196 additions & 0 deletions src/app/models/paginated-list.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import { TestBed } from '@angular/core/testing';
import { PaginatedList } from './paginated-list';

describe('PaginatedList', () => {
const PAGE_SIZE = 10;

function makeItems(count: number, offset = 0): string[] {
return Array.from({ length: count }, (_, i) => `item-${offset + i}`);
}

beforeEach(() => {
TestBed.configureTestingModule({});
});

it('should start with empty items, loading false and hasMore false', () => {
const list = new PaginatedList(() => Promise.resolve([]), PAGE_SIZE);
expect(list.items()).toEqual([]);
expect(list.loading()).toBeFalse();
expect(list.loadingMore()).toBeFalse();
expect(list.hasMore()).toBeFalse();
});

it('load should set loading=true during fetch and false when done', async () => {
const loadingStates: boolean[] = [];
const fetcher = jasmine.createSpy().and.callFake(() => {
loadingStates.push(list.loading());
return Promise.resolve([]);
});
const list = new PaginatedList(fetcher, PAGE_SIZE);
await list.load();
expect(loadingStates[0]).toBeTrue();
expect(list.loading()).toBeFalse();
});

it('load should populate items from the first fetch call', async () => {
const page0 = makeItems(10);
const fetcher = jasmine.createSpy()
.and.returnValues(Promise.resolve(page0), Promise.resolve([]));
const list = new PaginatedList(fetcher, PAGE_SIZE);
await list.load();
expect(list.items()).toEqual(page0);
});

it('load should set hasMore=true when prefetch returns non-null items', async () => {
const fetcher = jasmine.createSpy()
.and.returnValues(Promise.resolve(makeItems(10)), Promise.resolve(makeItems(10, 10)));
const list = new PaginatedList(fetcher, PAGE_SIZE);
await list.load();
expect(list.hasMore()).toBeTrue();
});

it('load should set hasMore=false when prefetch returns empty array', async () => {
const fetcher = jasmine.createSpy()
.and.returnValues(Promise.resolve(makeItems(5)), Promise.resolve([]));
const list = new PaginatedList(fetcher, PAGE_SIZE);
await list.load();
expect(list.hasMore()).toBeFalse();
});

it('load should set hasMore=false when prefetch returns array of nulls', async () => {
const fetcher = jasmine.createSpy()
.and.returnValues(Promise.resolve(makeItems(5)), Promise.resolve([null, null] as any));
const list = new PaginatedList(fetcher, PAGE_SIZE);
await list.load();
expect(list.hasMore()).toBeFalse();
});

it('load should reset items and state on reload', async () => {
const fetcher = jasmine.createSpy()
.and.returnValues(
Promise.resolve(makeItems(10)), // first load: page 0
Promise.resolve(makeItems(10, 10)), // first load: prefetch
Promise.resolve(makeItems(10, 20)), // loadMore: next prefetch
Promise.resolve(makeItems(3, 30)), // second load: page 0
Promise.resolve([]) // second load: prefetch
);
const list = new PaginatedList(fetcher, PAGE_SIZE);
await list.load();
await list.loadMore();
await list.load();
expect(list.items()).toEqual(makeItems(3, 30));
});

it('load should set loading=false even when fetcher rejects', async () => {
const fetcher = jasmine.createSpy().and.returnValue(Promise.reject(new Error('fail')));
const list = new PaginatedList(fetcher, PAGE_SIZE);
try {
await list.load();
} catch {
// expected
}
expect(list.loading()).toBeFalse();
});

it('loadMore should do nothing when hasMore is false', async () => {
const fetcher = jasmine.createSpy()
.and.returnValues(Promise.resolve(makeItems(5)), Promise.resolve([]));
const list = new PaginatedList(fetcher, PAGE_SIZE);
await list.load();
const callCount = fetcher.calls.count();

await list.loadMore();
expect(fetcher.calls.count()).toBe(callCount);
expect(list.items()).toEqual(makeItems(5));
});

it('loadMore should set loadingMore=true during fetch and false when done', async () => {
let resolveSecond!: (v: string[]) => void;
const fetcher = jasmine.createSpy()
.and.returnValues(
Promise.resolve(makeItems(10)),
Promise.resolve(makeItems(10, 10)),
new Promise<string[]>(r => { resolveSecond = r; })
);
const list = new PaginatedList(fetcher, PAGE_SIZE);
await list.load();

const morePromise = list.loadMore();
expect(list.loadingMore()).toBeTrue();
resolveSecond([]);
await morePromise;
expect(list.loadingMore()).toBeFalse();
});

it('loadMore should append prefetched items to existing items', async () => {
const page0 = makeItems(10);
const page1 = makeItems(10, 10);
const fetcher = jasmine.createSpy()
.and.returnValues(
Promise.resolve(page0),
Promise.resolve(page1),
Promise.resolve([])
);
const list = new PaginatedList(fetcher, PAGE_SIZE);
await list.load();
await list.loadMore();
expect(list.items()).toEqual([...page0, ...page1]);
});

it('loadMore should update hasMore based on next prefetch result', async () => {
const fetcher = jasmine.createSpy()
.and.returnValues(
Promise.resolve(makeItems(10)),
Promise.resolve(makeItems(10, 10)),
Promise.resolve(makeItems(5, 20)),
Promise.resolve([])
);
const list = new PaginatedList(fetcher, PAGE_SIZE);
await list.load();
expect(list.hasMore()).toBeTrue();
await list.loadMore();
expect(list.hasMore()).toBeTrue();
await list.loadMore();
expect(list.hasMore()).toBeFalse();
});

it('loadMore should set loadingMore=false even when fetcher rejects', async () => {
const fetcher = jasmine.createSpy()
.and.returnValues(
Promise.resolve(makeItems(10)),
Promise.resolve(makeItems(10, 10)),
Promise.reject(new Error('fail'))
);
const list = new PaginatedList(fetcher, PAGE_SIZE);
await list.load();
try {
await list.loadMore();
} catch {
// expected
}
expect(list.loadingMore()).toBeFalse();
});

it('load should call fetcher with page 0 first, then pageSize', async () => {
const fetcher = jasmine.createSpy()
.and.returnValues(Promise.resolve([]), Promise.resolve([]));
const list = new PaginatedList(fetcher, PAGE_SIZE);
await list.load();
expect(fetcher.calls.argsFor(0)).toEqual([0]);
expect(fetcher.calls.argsFor(1)).toEqual([PAGE_SIZE]);
});

it('loadMore should advance the page cursor on each call', async () => {
const fetcher = jasmine.createSpy()
.and.returnValues(
Promise.resolve(makeItems(10)),
Promise.resolve(makeItems(10, 10)),
Promise.resolve(makeItems(10, 20)),
Promise.resolve([])
);
const list = new PaginatedList(fetcher, PAGE_SIZE);
await list.load();
await list.loadMore();
expect(fetcher.calls.argsFor(2)).toEqual([PAGE_SIZE * 2]);
});
});
49 changes: 49 additions & 0 deletions src/app/models/paginated-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { signal } from '@angular/core';

export class PaginatedList<T> {
readonly items = signal<T[]>([]);
readonly loading = signal(false);
readonly loadingMore = signal(false);
readonly hasMore = signal(false);

private page = 0;
private prefetched: T[] = [];
private readonly fetcher: (page: number) => Promise<T[]>;
private readonly pageSize: number;

constructor(fetcher: (page: number) => Promise<T[]>, pageSize: number) {
this.fetcher = fetcher;
this.pageSize = pageSize;
}

async load(): Promise<void> {
this.loading.set(true);
this.page = 0;
this.items.set([]);
this.prefetched = [];

try {
this.items.set(await this.fetcher(0));
this.page = this.pageSize;
this.prefetched = await this.fetcher(this.page);
this.page += this.pageSize;
this.hasMore.set(this.prefetched.some(item => item != null));
} finally {
this.loading.set(false);
}
}

async loadMore(): Promise<void> {
if (!this.hasMore()) return;
this.loadingMore.set(true);

try {
this.items.set([...this.items(), ...this.prefetched]);
this.prefetched = await this.fetcher(this.page);
this.page += this.pageSize;
this.hasMore.set(this.prefetched.some(item => item != null));
} finally {
this.loadingMore.set(false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {EventMessageService} from "src/app/services/event-message.service";
import { LoginInfo } from 'src/app/models/interfaces';
import { initFlowbite } from 'flowbite';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import * as moment from 'moment';
import moment from 'moment';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

Expand Down
Loading
Loading