Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix/marp 1118 reloading of webpage caused a flickering #254

Merged
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
0850286
Update UI
ntqdinh-axonivy Dec 10, 2024
5803f59
refactor css
ntqdinh-axonivy Dec 10, 2024
d5ede2b
update html
ntqdinh-axonivy Dec 10, 2024
e71683f
update service
ntqdinh-axonivy Dec 11, 2024
73fc859
so tired
ntqdinh-axonivy Dec 11, 2024
37e57b5
update service
ntqdinh-axonivy Dec 11, 2024
e285683
updatre service
ntqdinh-axonivy Dec 11, 2024
5ac316e
update css
ntqdinh-axonivy Dec 11, 2024
0e25945
update css style
ntqdinh-axonivy Dec 11, 2024
b8fb576
update service
ntqdinh-axonivy Dec 12, 2024
9558ba7
update service
ntqdinh-axonivy Dec 12, 2024
6639723
update service
ntqdinh-axonivy Dec 12, 2024
ab6c7af
handle feedback
ntqdinh-axonivy Dec 12, 2024
f48e9a8
Merge branch 'develop' into bugfix/MARP-1118-Reloading-of-webpage-cau…
ntqdinh-axonivy Dec 13, 2024
305a97a
udpate service
ntqdinh-axonivy Dec 13, 2024
04c6628
finalize service
ntqdinh-axonivy Dec 16, 2024
afdd135
update test case
ntqdinh-axonivy Dec 16, 2024
9c03e41
update test case
ntqdinh-axonivy Dec 17, 2024
ec32a56
format code
ntqdinh-axonivy Dec 17, 2024
c523f9e
update code
ntqdinh-axonivy Dec 17, 2024
d5985a9
update code
ntqdinh-axonivy Dec 17, 2024
69457a3
Merge branch 'develop' into bugfix/MARP-1118-Reloading-of-webpage-cau…
ntqdinh-axonivy Dec 17, 2024
9b9c237
handle sonnar
ntqdinh-axonivy Dec 17, 2024
76fa3b5
update type
ntqdinh-axonivy Dec 17, 2024
8e76857
handle sonnar
ntqdinh-axonivy Dec 17, 2024
22717c6
update code
ntqdinh-axonivy Dec 17, 2024
1b5e37a
handle feedback
ntqdinh-axonivy Dec 18, 2024
d6755f6
handle feedback
ntqdinh-axonivy Dec 19, 2024
d77d457
Merge branch 'develop' into bugfix/MARP-1118-Reloading-of-webpage-cau…
ntqdinh-axonivy Dec 19, 2024
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
Prev Previous commit
Next Next commit
update test case
ntqdinh-axonivy committed Dec 17, 2024
commit 9c03e41db642ab65c8d15bc0dfe4f69bc40583aa
Original file line number Diff line number Diff line change
@@ -86,7 +86,7 @@ export class ProductFeedbackService {
);
}

private findProductFeedbacksByCriteria(
findProductFeedbacksByCriteria(
productId: string = this.productDetailService.productId(),
page: number = this.page(),
sort: string = this.sort(),
@@ -175,11 +175,12 @@ export class ProductFeedbackService {
this.cookieService.delete(TOKEN_KEY);
}

handleFeedbackApiResponse(response: FeedbackApiResponse) {
handleFeedbackApiResponse(response: FeedbackApiResponse): void {
this.totalPages.set(response.page.totalPages);
this.totalElements.set(response.page.totalElements);
}
getInitFeedbacksObservable() {

getInitFeedbacksObservable(): Observable<FeedbackApiResponse> {
this.page.set(0);
return this.findProductFeedbacksByCriteria();
}
Original file line number Diff line number Diff line change
@@ -10,15 +10,12 @@ import { CookieService } from 'ngx-cookie-service';
import { ActivatedRoute, provideRouter, Router } from '@angular/router';
import { CommonUtils } from '../../../../shared/utils/common.utils';
import { ROUTER } from '../../../../shared/constants/router.constant';
import { MatomoConfiguration, MatomoModule, MatomoRouterModule } from 'ngx-matomo-client';
import { MatomoTestingModule } from 'ngx-matomo-client/testing';
import { ProductDetailActionType } from '../../../../shared/enums/product-detail-action-type';
import { MATOMO_TRACKING_ENVIRONMENT } from '../../../../shared/constants/matomo.constant';

class MockElementRef implements ElementRef {
nativeElement = {
contains: jasmine.createSpy('contains')
};
nativeElement = { contains: jasmine.createSpy('contains') };
}

describe('ProductDetailVersionActionComponent', () => {
@@ -31,10 +28,11 @@ describe('ProductDetailVersionActionComponent', () => {

beforeEach(() => {
productServiceMock = jasmine.createSpyObj('ProductService', [
'sendRequestToProductDetailVersionAPI', 'sendRequestToUpdateInstallationCount', 'sendRequestToGetProductVersionsForDesigner'
'sendRequestToProductDetailVersionAPI',
'sendRequestToUpdateInstallationCount',
'sendRequestToGetProductVersionsForDesigner'
]);
const commonUtilsSpy = jasmine.createSpyObj('CommonUtils', ['getCookieValue']);
// const cookieServiceSpy = jasmine.createSpyObj('CookieService', ['get', 'set']);
const commonUtilsSpy = jasmine.createSpyObj('CommonUtils', [ 'getCookieValue' ]);
const activatedRouteSpy = jasmine.createSpyObj('ActivatedRoute', [], {
snapshot: {
queryParams: {}
@@ -43,7 +41,7 @@ describe('ProductDetailVersionActionComponent', () => {

TestBed.configureTestingModule({
imports: [
ProductDetailVersionActionComponent,
ProductDetailVersionActionComponent,
TranslateModule.forRoot(),
MatomoTestingModule.forRoot()
],
@@ -67,9 +65,7 @@ describe('ProductDetailVersionActionComponent', () => {
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
it('should create', () => { expect(component).toBeTruthy(); });

it('first artifact should be chosen when select corresponding version', () => {
const selectedVersion = 'Version 10.0.2';
@@ -91,11 +87,13 @@ describe('ProductDetailVersionActionComponent', () => {

it('should update selectedVersion, artifacts, selectedArtifactName, and selectedArtifact, and call addVersionParamToRoute', () => {
const version = '1.0';
const artifacts = [{
name: 'Example Artifact',
downloadUrl: 'https://example.com/download',
isProductArtifact: true
} as ItemDropdown];
const artifacts = [
{
name: 'Example Artifact',
downloadUrl: 'https://example.com/download',
isProductArtifact: true
} as ItemDropdown
];
const versionMap = new Map<string, any[]>();
versionMap.set(version, artifacts);

@@ -172,7 +170,6 @@ describe('ProductDetailVersionActionComponent', () => {
});
});


it('all of state should be reset before call rest api', () => {
const selectedVersion = 'Version 10.0.2';
const artifact = {
@@ -235,6 +232,7 @@ describe('ProductDetailVersionActionComponent', () => {
});

it('should send Api to get DevVersion', () => {
component.isDevVersionsDisplayed.set(false);
spyOn(component.isDevVersionsDisplayed, 'set');
expect(component.isDevVersionsDisplayed()).toBeFalse();
mockApiWithExpectedResponse();
@@ -249,7 +247,8 @@ describe('ProductDetailVersionActionComponent', () => {
const mockArtifact1 = {
name: 'Example Artifact1',
downloadUrl: 'https://example.com/download',
isProductArtifact: true, label: 'Example Artifact1'
isProductArtifact: true,
label: 'Example Artifact1'
} as ItemDropdown;
const mockArtifact2 = {
name: 'Example Artifact2',
@@ -291,42 +290,61 @@ describe('ProductDetailVersionActionComponent', () => {
component.versions.set(['1.0', '1.1']);
fixture.detectChanges();
component.getVersionInDesigner();
expect(productServiceMock.sendRequestToGetProductVersionsForDesigner).not.toHaveBeenCalled();
expect(
productServiceMock.sendRequestToGetProductVersionsForDesigner
).not.toHaveBeenCalled();
});

it('should call productService and update versions if versions are empty', () => {
const productId = '123';
component.versions.set([]);
const mockVersions = [{ version: '1.0' }, { version: '2.0' }];
productServiceMock.sendRequestToGetProductVersionsForDesigner.and.returnValue(of(mockVersions));
productServiceMock.sendRequestToGetProductVersionsForDesigner.and.returnValue(
of(mockVersions)
);

// Act
component.getVersionInDesigner();

// Assert
expect(productServiceMock.sendRequestToGetProductVersionsForDesigner).toHaveBeenCalledWith(productId);
expect(
productServiceMock.sendRequestToGetProductVersionsForDesigner
).toHaveBeenCalledWith(productId);
expect(component.versions()).toEqual(['Version 1.0', 'Version 2.0']);
});

it('should handle empty response from productService', () => {
component.versions.set([]);
productServiceMock.sendRequestToGetProductVersionsForDesigner.and.returnValue(of([]));
productServiceMock.sendRequestToGetProductVersionsForDesigner.and.returnValue(
of([])
);

// Act
component.getVersionInDesigner();

// Assert
expect(productServiceMock.sendRequestToGetProductVersionsForDesigner).toHaveBeenCalledWith(productId);
expect(
productServiceMock.sendRequestToGetProductVersionsForDesigner
).toHaveBeenCalledWith(productId);
expect(component.versions()).toEqual([]);
});

it('should return the correct tracking environment based on the action type', () => {
const testCases = [
{ actionType: ProductDetailActionType.STANDARD, expected: MATOMO_TRACKING_ENVIRONMENT.standard },
{ actionType: ProductDetailActionType.DESIGNER_ENV, expected: MATOMO_TRACKING_ENVIRONMENT.designerEnv },
{ actionType: ProductDetailActionType.CUSTOM_SOLUTION, expected: MATOMO_TRACKING_ENVIRONMENT.customSolution },
{
actionType: ProductDetailActionType.STANDARD,
expected: MATOMO_TRACKING_ENVIRONMENT.standard
},
{
actionType: ProductDetailActionType.DESIGNER_ENV,
expected: MATOMO_TRACKING_ENVIRONMENT.designerEnv
},
{
actionType: ProductDetailActionType.CUSTOM_SOLUTION,
expected: MATOMO_TRACKING_ENVIRONMENT.customSolution
}
];

testCases.forEach(({ actionType, expected }) => {
component.actionType = actionType;

@@ -335,9 +353,8 @@ describe('ProductDetailVersionActionComponent', () => {
});
});

it('should return empty environment when action type is default', () => {
it('should return empty environment when action type is default', () => {
const result = component.getTrackingEnvironmentBasedOnActionType();

expect(result).toBe('');
});
});

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -149,7 +149,6 @@ export class ProductDetailComponent {
}

ngOnInit(): void {
this.loadingService.showLoading(LoadingComponentId.DETAIL_PAGE);
this.router.navigate([], {
relativeTo: this.route,
queryParamsHandling: 'merge',
@@ -158,29 +157,32 @@ export class ProductDetailComponent {
const productId = this.route.snapshot.params[ROUTER.ID];
this.productDetailService.productId.set(productId);
if (productId) {
this.loadingService.showLoading(LoadingComponentId.DETAIL_PAGE);
forkJoin({
productDetail: this.getProductDetailObservable(productId),
productFeedBack: this.productFeedbackService.getInitFeedbacksObservable(),
productFeedBack:
this.productFeedbackService.getInitFeedbacksObservable(),
rating: this.productStarRatingService.getRatingObservable(productId),
userFeedback: this.productFeedbackService.findProductFeedbackOfUser()
userFeedback: this.productFeedbackService.findProductFeedbackOfUser(),
params: this.route.queryParams
}).subscribe(res => {
this.handleProductDetail(res.productDetail);
this.productFeedbackService.handleFeedbackApiResponse(res.productFeedBack);
res.rating;
this.productFeedbackService.handleFeedbackApiResponse(
res.productFeedBack
);
this.updateDropdownSelection();
this.checkMediaSize();
this.route.queryParams.subscribe(params => {
this.showPopup = params['showPopup'] === 'true';
if (this.showPopup && this.authService.getToken()) {
this.appModalService
.openAddFeedbackDialog()
.then(() => this.removeQueryParam())
.catch(() => this.removeQueryParam());
}
});
this.showPopup = res.params['showPopup'] === 'true';
console.log(this.showPopup);

if (this.showPopup && this.authService.getToken()) {
this.appModalService
.openAddFeedbackDialog()
.then(() => this.removeQueryParam())
.catch(() => this.removeQueryParam());
}
this.loadingService.hideLoading(LoadingComponentId.DETAIL_PAGE);
});

}
}

@@ -193,7 +195,7 @@ export class ProductDetailComponent {
return this.getProductById(productId, isShowDevVersion);
}

handleProductDetail(productDetail: ProductDetail) {
handleProductDetail(productDetail: ProductDetail): void {
this.productDetail.set(productDetail);
this.productModuleContent.set(productDetail.productModuleContent);
this.metaProductJsonUrl = productDetail.metaProductJsonUrl;
@@ -215,15 +217,15 @@ export class ProductDetailComponent {
}
}

onClickingBackToHomepageButton() {
onClickingBackToHomepageButton(): void {
this.router.navigate([API_URI.APP]);
}

onLogoError() {
onLogoError(): void {
this.logoUrl = DEFAULT_IMAGE_URL;
}

handleProductContentVersion() {
handleProductContentVersion(): void {
if (this.isEmptyProductContent()) {
return;
}
@@ -232,7 +234,7 @@ export class ProductDetailComponent {
);
}

updateProductDetailActionType(productDetail: ProductDetail) {
updateProductDetailActionType(productDetail: ProductDetail): void {
if (productDetail?.sourceUrl === undefined) {
this.productDetailActionType.set(ProductDetailActionType.CUSTOM_SOLUTION);
} else if (this.routingQueryParamService.isDesignerEnv()) {
@@ -242,7 +244,7 @@ export class ProductDetailComponent {
}
}

scrollToTop() {
scrollToTop(): void {
window.scrollTo({ left: 0, top: 0, behavior: 'instant' });
}

@@ -298,7 +300,6 @@ export class ProductDetailComponent {
),
dependency: content.isDependency
};

return conditions[value] ?? false;
}

@@ -307,7 +308,7 @@ export class ProductDetailComponent {
return !content || Object.keys(content).length === 0;
}

loadDetailTabs(selectedVersion: string) {
loadDetailTabs(selectedVersion: string): void {
let version = selectedVersion || this.productDetail().newestReleaseVersion;
version = version.replace(VERSION.displayPrefix, '');
this.productService
@@ -319,17 +320,17 @@ export class ProductDetailComponent {
});
}

onTabChange(event: string) {
onTabChange(event: string): void {
this.setActiveTab(event);
this.isTabDropdownShown.update(value => !value);
this.onTabDropdownShown();
}

getSelectedTabLabel() {
getSelectedTabLabel(): string {
return CommonUtils.getLabel(this.activeTab, PRODUCT_DETAIL_TABS);
}

updateDropdownSelection() {
updateDropdownSelection(): void {
const dropdown = document.getElementById(
'tab-group-dropdown'
) as HTMLSelectElement;
@@ -338,7 +339,7 @@ export class ProductDetailComponent {
}
}

setActiveTab(tab: string) {
setActiveTab(tab: string): void {
this.activeTab = tab;
const hash = '#tab-' + tab;
const path = window.location.pathname;
@@ -357,16 +358,16 @@ export class ProductDetailComponent {
localStorage.setItem(STORAGE_ITEM, JSON.stringify(savedTab));
}

onShowInfoContent() {
onShowInfoContent(): void {
this.isDropdownOpen.update(value => !value);
}

onTabDropdownShown() {
onTabDropdownShown(): void {
this.isTabDropdownShown.set(!this.isTabDropdownShown());
}

@HostListener('document:click', ['$event'])
handleClickOutside(event: MouseEvent) {
handleClickOutside(event: MouseEvent): void {
const formSelect =
this.elementRef.nativeElement.querySelector('.form-select');

@@ -380,11 +381,11 @@ export class ProductDetailComponent {
}

@HostListener('window:resize', ['$event'])
onResize() {
onResize(): void {
this.checkMediaSize();
}

checkMediaSize() {
checkMediaSize(): void {
const mediaQuery = window.matchMedia('(max-width: 767px)');
if (mediaQuery.matches) {
this.isMobileMode.set(true);
@@ -393,7 +394,7 @@ export class ProductDetailComponent {
}
}

onClickRateBtn() {
onClickRateBtn(): void {
const productId = this.productDetailService.productId();
if (this.authService.getToken()) {
this.appModalService.openAddFeedbackDialog();
@@ -402,7 +403,7 @@ export class ProductDetailComponent {
}
}

receiveInstallationCountData(data: number) {
receiveInstallationCountData(data: number): void {
this.installationCount = data;
}

@@ -421,7 +422,7 @@ export class ProductDetailComponent {
}
}

getDisplayedTabsSignal() {
getDisplayedTabsSignal(): ItemDropdown<string>[] {
this.updateWebBrowserTitle();
const displayedTabs: ItemDropdown[] = [];
for (const detailTab of this.detailTabs) {
@@ -449,7 +450,6 @@ export class ProductDetailComponent {
productDetail.vendorImage = vendorImage || vendorImageDarkMode;
productDetail.vendorImageDarkMode = vendorImageDarkMode || vendorImage;
}

return productDetail;
}
}
11 changes: 1 addition & 10 deletions marketplace-ui/src/app/modules/product/product.service.spec.ts
Original file line number Diff line number Diff line change
@@ -20,25 +20,19 @@ describe('ProductService', () => {
let products = MOCK_PRODUCTS._embedded.products;
let service: ProductService;
let httpMock: HttpTestingController;
let loadingServiceSpy: jasmine.SpyObj<LoadingService>;

beforeEach(() => {
const spyLoading = jasmine.createSpyObj('LoadingService', ['show', 'hide']);

TestBed.configureTestingModule({
imports: [],
providers: [
ProductService,
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
{ provide: LoadingService, useValue: spyLoading }
LoadingService
]
});
service = TestBed.inject(ProductService);
httpMock = TestBed.inject(HttpTestingController);
loadingServiceSpy = TestBed.inject(
LoadingService
) as jasmine.SpyObj<LoadingService>;
});

it('should be created', () => {
@@ -194,9 +188,6 @@ describe('ProductService', () => {

expect(req.request.method).toBe('GET');
req.flush(mockResponse);

expect(loadingServiceSpy.showLoading).not.toHaveBeenCalled();
expect(loadingServiceSpy.hideLoading).not.toHaveBeenCalled();
});

it('getProductDetailsWithVersion should return a product detail', () => {
5 changes: 1 addition & 4 deletions marketplace-ui/src/app/modules/product/product.service.ts
Original file line number Diff line number Diff line change
@@ -7,10 +7,7 @@ import { ProductApiResponse } from '../../shared/models/apis/product-response.mo
import { Criteria } from '../../shared/models/criteria.model';
import { ProductDetail } from '../../shared/models/product-detail.model';
import { VersionData } from '../../shared/models/vesion-artifact.model';
import {
SkipLoading,
LoadingComponent
} from '../../core/interceptors/api.interceptor';
import { LoadingComponent } from '../../core/interceptors/api.interceptor';
import { VersionAndUrl } from '../../shared/models/version-and-url';
import { API_URI } from '../../shared/constants/api.constant';
import { LoadingComponentId } from '../../shared/enums/loading-component-id';
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { LoadingSpinnerComponent } from './loading-spinner.component';
import { LoadingComponentId } from '../../enums/loading-component-id';

describe('LoadingSpinnerComponent', () => {
let component: LoadingSpinnerComponent;
@@ -16,25 +17,30 @@ describe('LoadingSpinnerComponent', () => {
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
it('should create', () => expect(component).toBeTruthy());

it('should apply position-fixed class when isFixPosition is true', () => {
const containerElement = fixture.debugElement.query(By.css('.spinner-container'));
expect(containerElement.nativeElement.classList.contains('position-fixed')).toBe(true);
expect(containerElement.nativeElement.classList.contains('position-absolute')).toBe(false);
it('should display when isLoading state is true', () => {
component.key = LoadingComponentId.LANDING_PAGE;
component.loadingService.showLoading(LoadingComponentId.LANDING_PAGE);
fixture.detectChanges();
expect(component.isLoading()).toBeTrue();
});

it('should apply position-absolute class when isFixPosition is false', () => {
it('should display when isLoading state is false', () => {
component.key = LoadingComponentId.LANDING_PAGE;
component.loadingService.hideLoading(LoadingComponentId.LANDING_PAGE);
fixture.detectChanges();
const containerElement = fixture.debugElement.query(By.css('.spinner-container'));
expect(containerElement.nativeElement.classList.contains('position-absolute')).toBe(true);
expect(containerElement.nativeElement.classList.contains('position-fixed')).toBe(false);
expect(component.isLoading()).toBeFalse();
});

it('should contain a spinner-border element', () => {
const spinnerElement = fixture.debugElement.query(By.css('.spinner-border'));
expect(spinnerElement).toBeTruthy();
it('container class should come from input', () => {
component.key = LoadingComponentId.LANDING_PAGE;
component.containerClasses = 'spinner-container';
component.loadingService.showLoading(LoadingComponentId.LANDING_PAGE);
fixture.detectChanges();
const containerElement = fixture.debugElement.query(
By.css('.spinner-container')
);
expect(containerElement.nativeElement).toBeTruthy();
});
});
51 changes: 42 additions & 9 deletions marketplace-ui/src/app/shared/mocks/mock-data.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { FeedbackApiResponse } from '../models/apis/feedback-response.model';
import { ProductApiResponse } from '../models/apis/product-response.model';
import { ExternalDocument } from '../models/external-document.model';
import { ProductDetail } from '../models/product-detail.model';
import { ProductModuleContent } from '../models/product-module-content.model';
import { StarRatingCounting } from '../models/star-rating-counting.model';

export const MOCK_PRODUCTS = {
_embedded: {
@@ -226,15 +228,17 @@ export const MOCK_CRON_JOB_PRODUCT_DETAIL: ProductDetail = {
de: 'Das Cron-Job-Utility übernimmt die automatische Verwaltung deiner zeitgesteuerten Aufgaben.',
en: 'Cron Job Utility handles your scheduled jobs autonomously.'
},
logoUrl: 'https://raw.githubusercontent.com/axonivy-market/market/feature/MARP-463-Multilingualism-for-Website/market/utils/cronjob/logo.png',
logoUrl:
'https://raw.githubusercontent.com/axonivy-market/market/feature/MARP-463-Multilingualism-for-Website/market/utils/cronjob/logo.png',
type: 'util',
tags: ['utils'],
vendor: 'Axon Ivy AG',
platformReview: '4.5',
newestReleaseVersion: 'v10.0.4',
cost: 'Free',
sourceUrl: 'https://github.com/axonivy-market/cronjob',
statusBadgeUrl: 'https://github.com/axonivy-market/cronjob/actions/workflows/ci.yml/badge.svg',
statusBadgeUrl:
'https://github.com/axonivy-market/cronjob/actions/workflows/ci.yml/badge.svg',
language: 'English',
industry: 'Cross-Industry',
compatibility: '10.0+',
@@ -246,10 +250,10 @@ export const MOCK_CRON_JOB_PRODUCT_DETAIL: ProductDetail = {
en: '**Cron Job** is a job-firing schedule that recurs based on calendar-like notions.\n\nThe [Quartz framework](http://www.quartz-scheduler.org/) is used as underlying scheduler framework.\n\nWith Cron Job, you can specify firing-schedules such as “every Friday at noon”, or “every weekday and 9:30 am”, or even “every 5 minutes between 9:00 am and 10:00 am on every Monday, Wednesday and Friday during January”.\n\nFor more details about Cron Expressions please refer to [Lesson 6: CronTrigger](http://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/tutorial-lesson-06.html)'
},
setup: {
en: 'No special setup is needed for this demo. Only start the Engine and watch out the logging which will be updated every 5 seconds with the following logging entry:\n\n```\n\nCron Job ist started at: 2023-01-27 10:43:20.\n\n```',
en: 'No special setup is needed for this demo. Only start the Engine and watch out the logging which will be updated every 5 seconds with the following logging entry:\n\n```\n\nCron Job ist started at: 2023-01-27 10:43:20.\n\n```'
},
demo: {
en: 'In this demo, the CronByGlobalVariableTriggerStartEventBean is defined as the Java class to be executed in the Ivy Program Start element.\n\n![Program Start Element screenshot](https://raw.githubusercontent.com/axonivy-market/cronjob/v10.0.4/cronjob-product/ProgramStartElement.png)\n\nThis bean gets a cron expression via the variable defined as Cron expression and it will schedule by using the expression.\n\n![custom editor UI screenshot](https://raw.githubusercontent.com/axonivy-market/cronjob/v10.0.4/cronjob-product/customEditorUI.png)\n\nFor this demo, the Cron expression is defining the time to start the cron that simply fires every 5 seconds.\n\n```\n\n demoStartCronPattern: 0/5 * * * * ?\n\n```',
en: 'In this demo, the CronByGlobalVariableTriggerStartEventBean is defined as the Java class to be executed in the Ivy Program Start element.\n\n![Program Start Element screenshot](https://raw.githubusercontent.com/axonivy-market/cronjob/v10.0.4/cronjob-product/ProgramStartElement.png)\n\nThis bean gets a cron expression via the variable defined as Cron expression and it will schedule by using the expression.\n\n![custom editor UI screenshot](https://raw.githubusercontent.com/axonivy-market/cronjob/v10.0.4/cronjob-product/customEditorUI.png)\n\nFor this demo, the Cron expression is defining the time to start the cron that simply fires every 5 seconds.\n\n```\n\n demoStartCronPattern: 0/5 * * * * ?\n\n```'
},
isDependency: true,
name: 'cron job',
@@ -280,7 +284,8 @@ export const MOCK_PRODUCT_DETAIL: ProductDetail = {
de: "TODO Atlassian's Jira connector lets you track issues directly from the Axon Ivy platform."
},
installationCount: 1,
logoUrl: 'https://raw.githubusercontent.com/axonivy-market/market/master/market/connector/jira/logo.png',
logoUrl:
'https://raw.githubusercontent.com/axonivy-market/market/master/market/connector/jira/logo.png',
type: 'connector',
tags: ['helper'],
vendor: 'FROX AG',
@@ -289,7 +294,8 @@ export const MOCK_PRODUCT_DETAIL: ProductDetail = {
newestReleaseVersion: 'v10.0.0',
cost: 'Free',
sourceUrl: 'https://github.com/axonivy-market/jira-connector',
statusBadgeUrl: 'https://github.com/axonivy-market/jira-connector/actions/workflows/ci.yml/badge.svg',
statusBadgeUrl:
'https://github.com/axonivy-market/jira-connector/actions/workflows/ci.yml/badge.svg',
language: 'English',
industry: 'Cross-Industry',
compatibility: '9.2+',
@@ -300,17 +306,17 @@ export const MOCK_PRODUCT_DETAIL: ProductDetail = {
en: "Axon Ivy's [Atlassian Jira Connector ](https://www.atlassian.com/software/jira) gives you full power to track issues within your process work. The connector:\n\n- Features three main functionalities (create comment, create issue, and get issue).\n- Provides access to the core API of Atlassian Jira.\n- Supports you with an easy-to-copy demo implementation to reduce your integration effort.\n- Enables low code citizen developers to integrate issue tracking tools without writing a single line of code."
},
setup: {
en: 'Open the `Config/variables.yaml` in your Axon Ivy Designer and paste the\ncode below and adjust the values to your environment.\n\n```\nVariables:\n\n jira-connector:\n \n # Url to the Jira server\n Url: "https://localhost"\n\n # Username to connect to the Jira server\n Username: "admin"\n\n # Password to connect to the Jira server\n Password: "1234"\n```',
en: 'Open the `Config/variables.yaml` in your Axon Ivy Designer and paste the\ncode below and adjust the values to your environment.\n\n```\nVariables:\n\n jira-connector:\n \n # Url to the Jira server\n Url: "https://localhost"\n\n # Username to connect to the Jira server\n Username: "admin"\n\n # Password to connect to the Jira server\n Password: "1234"\n```'
},
demo: {
en: '![jira-connector Demo 1](https://raw.githubusercontent.com/axonivy-market/jira-connector/v10.0.0/jira-connector-product/images/create-issue.png "Create Jira issue")\n![jira-connector Demo 2](https://raw.githubusercontent.com/axonivy-market/jira-connector/v10.0.0/jira-connector-product/images/create-comment.png "Craete Jira comment")',
en: '![jira-connector Demo 1](https://raw.githubusercontent.com/axonivy-market/jira-connector/v10.0.0/jira-connector-product/images/create-issue.png "Create Jira issue")\n![jira-connector Demo 2](https://raw.githubusercontent.com/axonivy-market/jira-connector/v10.0.0/jira-connector-product/images/create-comment.png "Craete Jira comment")'
},
isDependency: true,
name: 'Jira Connector',
groupId: 'com.axonivy.connector.jira',
artifactId: 'jira-connector',
type: 'iar',
productId: 'jira-connector',
productId: 'jira-connector'
},
mavenDropins: false,
_links: {
@@ -329,3 +335,30 @@ export const MOCK_EXTERNAL_DOCUMENT: ExternalDocument = {
artifactName: 'Portal Guide',
relativeLink: '/market-cache/portal/portal-guide/10.0.0/doc/index.html'
};

export const MOCK_FEEDBACK_API_RESPONSE: FeedbackApiResponse = {
_embedded: {
feedbacks: [
{
content: 'cool stuff',
rating: 5,
productId: 'portal'
}
]
},
_links: {
self: { href: '/feedbacks' }
},
page: {
size: 10,
totalElements: 1,
totalPages: 1,
number: 0
}
};

export const MOCK_RATING_STAR: StarRatingCounting[] = [
{ starRating: 0 },
{ starRating: 4 },
{ starRating: 3 }
];
6 changes: 3 additions & 3 deletions marketplace-ui/src/app/shared/services/app-modal.service.ts
Original file line number Diff line number Diff line change
@@ -10,15 +10,15 @@ import { SuccessDialogComponent } from '../../modules/product/product-detail/pro
export class AppModalService {
private readonly modalService = inject(NgbModal);

openShowFeedbacksDialog() {
openShowFeedbacksDialog(): void {
this.modalService.open(ShowFeedbacksDialogComponent, {
centered: true,
modalDialogClass: 'show-feedbacks-modal-dialog',
windowClass: 'overflow-hidden'
});
}

openAddFeedbackDialog() {
openAddFeedbackDialog(): Promise<any> {
const addFeedbackDialog = this.modalService.open(
AddFeedbackDialogComponent,
{
@@ -30,7 +30,7 @@ export class AppModalService {
return addFeedbackDialog.result;
}

openSuccessDialog() {
openSuccessDialog(): void {
this.modalService.open(SuccessDialogComponent, {
fullscreen: 'md',
centered: true,