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

MARP-1561 Create Marketplace release 1.6.0 #247

Merged
merged 5 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public ResponseEntity<ExternalDocumentModel> findExternalDocument(
in = ParameterIn.PATH) String version) {
ExternalDocumentMeta externalDocument = externalDocumentService.findExternalDocument(id, version);
if (externalDocument == null) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}

var model = ExternalDocumentModel.builder().productId(externalDocument.getProductId())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class ProductContentUtils {
public static final String DESCRIPTION = "description";
public static final String DEMO = "demo";
public static final String SETUP = "setup";
public static final String README_IMAGE_FORMAT = "\\(([^)]*?%s[^)]*?)\\)";
public static final String README_IMAGE_FORMAT = "\\(([^)]*?/)?%s\\)";
public static final String IMAGE_DOWNLOAD_URL_FORMAT = "(%s)";

private ProductContentUtils() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ public class BaseSetup {
protected static final String MOCK_GROUP_ID = "com.axonivy.util";
protected static final String MOCK_PRODUCT_NAME = "bpmn statistic";
protected static final String MOCK_PRODUCT_REPOSITORY_NAME = "axonivy-market/bpmn-statistic";
protected static final String MOCK_IMAGE_ID_FORMAT_1 = "imageId-66e2b14868f2f95b2f95549a";
protected static final String MOCK_IMAGE_ID_FORMAT_2 = "imageId-66e2b14868f2f95b2f95550a";
protected static final String MOCK_PRODUCT_JSON_FILE_PATH = "src/test/resources/product.json";
protected static final String MOCK_PRODUCT_JSON_FILE_PATH_NO_URL = "src/test/resources/productMissingURL.json";
protected static final String MOCK_PRODUCT_JSON_WITH_DROPINS_FILE_PATH = "src/test/resources/product-dropins.json";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ void testUpdateImagesWithDownloadUrl() throws IOException {
String productId = MOCK_PRODUCT_ID;
try (MockedStatic<Files> mockedFiles = Mockito.mockStatic(Files.class)) {
Path downloadLocation = Paths.get(EXTRACT_DIR_LOCATION);
Path imagePath1 = Paths.get("screen1.png");
Path imagePath2 = Paths.get("screen2.png");
Path imagePath1 = Paths.get("images/slash-command.png");
Path imagePath2 = Paths.get("images/create-slash-command.png");

when(Files.walk(downloadLocation)).thenReturn(Stream.of(downloadLocation, imagePath1, imagePath2));
mockedFiles.when(() -> Files.isRegularFile(imagePath1)).thenReturn(true);
Expand All @@ -76,9 +76,9 @@ void testUpdateImagesWithDownloadUrl() throws IOException {

String result = productContentService.updateImagesWithDownloadUrl(productId, EXTRACT_DIR_LOCATION,
readmeContent);
String expectedResult = readmeContent.replace("screen1.png \"Screen 1\"",
"imageId-66e2b14868f2f95b2f95549a").replace("screen2.png " + "\"Screen 2\"",
"imageId-66e2b14868f2f95b2f95550a");
String expectedResult = readmeContent.replace("images/slash-command.png",
MOCK_IMAGE_ID_FORMAT_1).replace("images/create-slash-command.png",
MOCK_IMAGE_ID_FORMAT_2);
assertEquals(expectedResult, result);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
import com.axonivy.market.bo.Artifact;
import com.axonivy.market.entity.ProductModuleContent;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;

@ExtendWith(MockitoExtension.class)
class ProductContentUtilsTest extends BaseSetup {
Expand All @@ -18,23 +21,38 @@ void testUpdateProductModule() {
ProductModuleContent mockProductModuleContent = new ProductModuleContent();
Artifact mockArtifact = getMockArtifact();
ProductContentUtils.updateProductModule(mockProductModuleContent, List.of(mockArtifact));
Assertions.assertEquals(mockArtifact.getGroupId(), mockProductModuleContent.getGroupId());
Assertions.assertEquals(mockArtifact.getArtifactId(), mockProductModuleContent.getArtifactId());
Assertions.assertEquals(mockArtifact.getName(), mockProductModuleContent.getName());
Assertions.assertEquals(mockArtifact.getType(), mockProductModuleContent.getType());
assertEquals(mockArtifact.getGroupId(), mockProductModuleContent.getGroupId());
assertEquals(mockArtifact.getArtifactId(), mockProductModuleContent.getArtifactId());
assertEquals(mockArtifact.getName(), mockProductModuleContent.getName());
assertEquals(mockArtifact.getType(), mockProductModuleContent.getType());
}

@Test
void testRemoveFirstLine() {
Assertions.assertEquals(StringUtils.EMPTY, ProductContentUtils.removeFirstLine(null));
Assertions.assertEquals(StringUtils.EMPTY, ProductContentUtils.removeFirstLine(" "));
Assertions.assertEquals(StringUtils.EMPTY, ProductContentUtils.removeFirstLine("#"));
Assertions.assertEquals("Second line", ProductContentUtils.removeFirstLine("#First line\nSecond line"));
assertEquals(StringUtils.EMPTY, ProductContentUtils.removeFirstLine(null));
assertEquals(StringUtils.EMPTY, ProductContentUtils.removeFirstLine(" "));
assertEquals(StringUtils.EMPTY, ProductContentUtils.removeFirstLine("#"));
assertEquals("Second line", ProductContentUtils.removeFirstLine("#First line\nSecond line"));
}

@Test
void testGetReadmeFileLocale() {
Assertions.assertEquals(StringUtils.EMPTY, ProductContentUtils.getReadmeFileLocale("README.md"));
Assertions.assertEquals("DE", ProductContentUtils.getReadmeFileLocale("README_DE.md"));
assertEquals(StringUtils.EMPTY, ProductContentUtils.getReadmeFileLocale("README.md"));
assertEquals("DE", ProductContentUtils.getReadmeFileLocale("README_DE.md"));
}

@Test
void testReplaceImageDirWithImageCustomId() {
String readmeContents = getMockReadmeContent();
Map<String, String> imageUrls = new HashMap<>();
imageUrls.put("slash-command.png", MOCK_IMAGE_ID_FORMAT_1);
imageUrls.put("create-slash-command.png", MOCK_IMAGE_ID_FORMAT_2);

String expectedResult = readmeContents.replace("images/slash-command.png",
MOCK_IMAGE_ID_FORMAT_1).replace("images/create-slash-command.png",
MOCK_IMAGE_ID_FORMAT_2);
String updatedContents = ProductContentUtils.replaceImageDirWithImageCustomId(imageUrls, readmeContents);

assertEquals(expectedResult, updatedContents);
}
}
94 changes: 60 additions & 34 deletions marketplace-service/src/test/resources/README.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,63 @@
# Employee Onboarding solution

Axon Ivy’s Employee Onboarding solution provides best practice guidance to HR
specialists, ensuring that provisioning and all other aspects of the employee
onboarding process are handled quickly and efficiently. In addition, HR managers
and their superiors can capture all the information required to automatically
set up employees in HR systems. The solution:

- ensures timely and personalized communication with new employees from their
first day on the job (and even before)
- guarantees no tasks are left undone
- helps new hires become productive much faster
- makes sure that required materials are procured and ready for employees in
advance
- increases employee retention rates
- contributes to a positive company image

### Summary

You only have one chance to make a first impression. Failing to deliver a smooth
employee onboarding experience can contribute to high turnover rates and
inefficiency. A structured onboarding process ensures that employees have all
the necessary materials and information available when they start work, which
increases employee satisfaction. Axon Ivy’s Employee Onboarding Solution not
only coordinates processes within HR departments, it also automates functions
across external departments and process owners.

### Information

- Industry: All Industries
- Compatible Version(s): 8.0.x, 10.0.x
# Mattermost Connector

## Demo
Axon Ivy’s mattermost connector helps you to accelerate process automation initiatives by integrating Mattermost features into your process application within no time.

This connector:

![Screen 1](screen1.png "Screen 1")
- supports you with a demo implementation to reduce your integration effort.
- gives you full power to the [Mattermost's APIs](https://api.mattermost.com/).
- allow you to start the Axon Ivy process by hitting the slash command key from the mattermost's channel.
- allow you to send a message to the mattermost's channel from the Axon Ivy workplace.
- notifies users on the channel for new Axon Ivy workflow Tasks.

## Demo

![Screen 2](screen2.png "Screen 2")
1. Hit the slash command key on the channel's chat.
The Axon Ivy process will be triggered and create a new task.
The task's information will be sent to the channel by a message.

![call-slash-command](images/slash-command.png)

### Setup

Mattermost Instance

1. Ref to [Deploy Mattermost](https://docs.mattermost.com/guides/deployment.html).
2. Create Team, User, ...
3.Enable Bot Account Creation and create a bot account for sending notification to the channel Axon Ivy. E.g.
axonivy-bot
4.Create a slash command in the Integrations menu.
![create-slash-command](images/create-slash-command.png)

Add the following `Variables` to your `variables.yaml`:

- `Variables.mattermost.baseUrl`
- `Variables.mattermost.accessToken`
- `Variables.mattermost.teamName`
- `Variables.mattermost.botName`

and replace the values with your given setup.

```
# == Variables ==
#
# You can define here your project Variables.
# If you want to define/override a Variable for a specific Environment,
# add an additional ‘variables.yaml’ file in a subdirectory in the ‘Config’ folder:
# '<project>/Config/_<environment>/variables.yaml
#
Variables:
# myVariable: value
mattermost:
# The base URL of matter most
baseUrl: ""
# Personal access tokens function similarly to session tokens and can be used by integrations to authenticate against the REST API.
accessToken: ""
# The team name
teamName: ""
# The name of bot that will inform the task on the channel
botName: ""
# This variable is used for getting incoming webhook list per page
incomingWebhookPerPage: 200

```
15 changes: 8 additions & 7 deletions marketplace-ui/src/app/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { MARKED_OPTIONS, MarkdownModule } from 'ngx-markdown';
import { markedOptionsFactory } from './core/configs/markdown.config';
import { httpLoaderFactory } from './core/configs/translate.config';
import { apiInterceptor } from './core/interceptors/api.interceptor';
import { provideMatomo, withRouter } from 'ngx-matomo-client';
import { environment } from '../environments/environment';

const scrollConfig: InMemoryScrollingOptions = {
scrollPositionRestoration: 'disabled',
Expand All @@ -22,13 +24,12 @@ export const appConfig: ApplicationConfig = {
provideRouter(routes, inMemoryScrollingFeature),
provideHttpClient(withFetch(), withInterceptors([apiInterceptor])),

// Disabled for later HTTPS implementation
// provideMatomo({
// siteId: environment.matomoSiteId,
// trackerUrl: environment.matomoTrackerUrl,
// },
// withRouter(),
// ),
provideMatomo({
siteId: environment.matomoSiteId,
trackerUrl: environment.matomoTrackerUrl,
},
withRouter(),
),
importProvidersFrom(
TranslateModule.forRoot({
loader: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,27 @@ export class ProductDetailInformationTabComponent implements OnChanges {
this.productDetailService.getExternalDocumentForProductByVersion(this.productDetail.id, this.extractVersionValue(version))
.subscribe({
next: response => {
this.externalDocumentLink = response.relativeLink;
this.displayExternalDocName = response.artifactName;
if (response) {
this.externalDocumentLink = response.relativeLink;
this.displayExternalDocName = response.artifactName;
} else {
this.resetValues();
}
this.loadingService.hide();
},
error: () => {
this.externalDocumentLink = '';
this.displayExternalDocName = '';
this.resetValues();
this.loadingService.hide();
}
});
this.displayVersion = this.extractVersionValue(this.selectedVersion);
}

resetValues() {
this.externalDocumentLink = '';
this.displayExternalDocName = '';
}

extractVersionValue(versionDisplayName: string) {
return versionDisplayName.replace(VERSION.displayPrefix, '');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
id="install-button"
name="Install button"
#installButton
[matomoClickCategory]="MatomoCategory.BUTTON"
[matomoClickAction]="MatomoAction.CLICK"
[matomoClickName]="installButton.name + ' - ' + getTrackingEnvironmentBasedOnActionType()"
[lang]="languageService.selectedLanguage()"
class="btn btn__install flex-grow-1 me-lg-2"
data-bs-toggle="tooltip"
Expand All @@ -30,6 +33,9 @@
id="download-button"
name="Download button"
#artifactDownloadButton
[matomoClickCategory]="MatomoCategory.BUTTON"
[matomoClickAction]="MatomoAction.CLICK"
[matomoClickName]="artifactDownloadButton.name + ' - ' + getTrackingEnvironmentBasedOnActionType()"
[lang]="languageService.selectedLanguage()"
class="btn btn__download btn-secondary primary-bg flex-grow-1"
type="button"
Expand Down Expand Up @@ -116,6 +122,9 @@
<button id="install-button"
name="Install button"
#installButton
[matomoClickCategory]="MatomoCategory.BUTTON"
[matomoClickAction]="MatomoAction.CLICK"
[matomoClickName]="installButton.name + ' - ' + getTrackingEnvironmentBasedOnActionType()"
[lang]="languageService.selectedLanguage()"
class="btn btn__install flex-grow-1 install-designer-button m-0 col-4" id="install-button"
(click)="onUpdateInstallationCountForDesigner()" onClick="function installInDesigner() {
Expand All @@ -135,6 +144,9 @@
id="contact-us-button"
name="Contact us button"
#contactUsButton
[matomoClickCategory]="MatomoCategory.BUTTON"
[matomoClickAction]="MatomoAction.CLICK"
[matomoClickName]="contactUsButton.name + ' - ' + getTrackingEnvironmentBasedOnActionType()"
[lang]="languageService.selectedLanguage()"
class="btn row btn_contact-us"
(click)="onNavigateToContactPage()"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { ActivatedRoute, Router } from '@angular/router';
import { ROUTER } from '../../../../shared/constants/router.constant';
import { MatomoCategory, MatomoAction } from '../../../../shared/enums/matomo-tracking.enum';
import { MATOMO_TRACKING_ENVIRONMENT } from '../../../../shared/constants/matomo.constant';
import { MATOMO_DIRECTIVES } from 'ngx-matomo-client';

const showDevVersionCookieName = 'showDevVersions';

Expand All @@ -46,6 +47,7 @@ const showDevVersionCookieName = 'showDevVersions';
FormsModule,
CommonDropdownComponent,
LoadingSpinnerComponent,
MATOMO_DIRECTIVES
],
templateUrl: './product-detail-version-action.component.html',
styleUrl: './product-detail-version-action.component.scss'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,11 +204,11 @@ <h4 class="analysis-title text-primary text-capitalize mb-0">
alt="Message Star" />
<p class="rate-empty-text">
<span [lang]="languageService.selectedLanguage()">
{{ 'common.feedback.noFeedbackMessage1' | translate }}
{{ productDetailService.noFeedbackLabel() | translate }}
</span>
<br />
<span [lang]="languageService.selectedLanguage()">
{{ 'common.feedback.noFeedbackMessage2' | translate }}
{{ 'common.feedback.noFeedbackSecondLabel' | translate }}
</span>
</p>
<button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { of } from 'rxjs';
import { TypeOption } from '../../../shared/enums/type-option.enum';
import {
MOCK_PRODUCT_DETAIL,
MOCK_PRODUCT_DETAIL_BY_VERSION,
MOCK_CRON_JOB_PRODUCT_DETAIL,
MOCK_PRODUCT_MODULE_CONTENT,
MOCK_PRODUCTS
} from '../../../shared/mocks/mock-data';
Expand Down Expand Up @@ -137,7 +137,7 @@ describe('ProductDetailComponent', () => {
targetVersion
);
component.getProductById(productId, false).subscribe(productDetail => {
expect(productDetail).toEqual(MOCK_PRODUCT_DETAIL_BY_VERSION);
expect(productDetail).toEqual(MOCK_CRON_JOB_PRODUCT_DETAIL);
});
});

Expand Down Expand Up @@ -797,5 +797,15 @@ describe('ProductDetailComponent', () => {
it('should generate right text for the rate connector', () => {
const rateConnector = fixture.debugElement.query(By.css('.rate-connector-btn'));
expect(rateConnector.childNodes[0].nativeNode.textContent).toContain("common.feedback.rateFeedbackForConnectorBtnLabel");

const rateConnectorEmptyText = fixture.debugElement.query(By.css('.rate-empty-text'));
expect(rateConnectorEmptyText.childNodes[0].nativeNode.textContent).toContain("common.feedback.noFeedbackForConnectorLabel");

component.route.snapshot.params['id'] = 'cronjob';
spyOn(component, 'getProductById').and.returnValue(of(MOCK_CRON_JOB_PRODUCT_DETAIL));
component.ngOnInit();
fixture.detectChanges();
expect(rateConnector.childNodes[0].nativeNode.textContent).toContain("common.feedback.rateFeedbackForUtilityBtnLabel");
expect(rateConnectorEmptyText.childNodes[0].nativeNode.textContent).toContain("common.feedback.noFeedbackForUtilityLabel");
});
});
Loading
Loading