From 9bf758c1177ef4aa9f7c1362bb8b8d1f31ec82be Mon Sep 17 00:00:00 2001 From: mnhnam-axonivy Date: Fri, 26 Jan 2024 11:54:37 +0700 Subject: [PATCH 01/24] IVYPORTAL-16478: Various or urgent requests in IVYPORTAL - S254 > IVYPORTAL-16519: NPE when load processes - Added NPE check # Conflicts: # AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/DashboardProcessBean.java --- .../axonivy/portal/components/util/ProcessViewerUtils.java | 4 +++- .../src/ch/ivy/addon/portalkit/bean/DashboardProcessBean.java | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/AxonIvyPortal/portal-components/src/com/axonivy/portal/components/util/ProcessViewerUtils.java b/AxonIvyPortal/portal-components/src/com/axonivy/portal/components/util/ProcessViewerUtils.java index 2ec874bdd6b..ced7c4a9102 100644 --- a/AxonIvyPortal/portal-components/src/com/axonivy/portal/components/util/ProcessViewerUtils.java +++ b/AxonIvyPortal/portal-components/src/com/axonivy/portal/components/util/ProcessViewerUtils.java @@ -1,7 +1,9 @@ package com.axonivy.portal.components.util; +import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.function.Predicate; import org.apache.commons.collections4.CollectionUtils; @@ -127,7 +129,7 @@ private static List getWebStartables() { if (CollectionUtils.isEmpty(webStartables)) { webStartables = ProcessService.getInstance().findProcesses(); } - return webStartables; + return Optional.ofNullable(webStartables).orElse(new ArrayList<>()); } public static boolean isExpressCase(ICase iCase) { diff --git a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/DashboardProcessBean.java b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/DashboardProcessBean.java index 721b992d912..594ce5f439b 100644 --- a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/DashboardProcessBean.java +++ b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/DashboardProcessBean.java @@ -76,7 +76,7 @@ protected List findProcesses() { List processes = ProcessService.getInstance().findProcesses(); List defaultPortalProcesses = new ArrayList<>(); // TODO fix static ProcessService#ivyProcessResultDTO, maybe cause error when Jmeter 10 user NavigateToGlobalSearch - if (processes != null) { + if (CollectionUtils.isNotEmpty(processes)) { processes.forEach(process -> defaultPortalProcesses.add(new DashboardProcess(process))); } return defaultPortalProcesses; From a597605e08786e36a6fa6437649898aeb29fd68b Mon Sep 17 00:00:00 2001 From: mnhnam-axonivy Date: Thu, 1 Feb 2024 18:35:36 +0700 Subject: [PATCH 02/24] IVYPORTAL-16476: Process Steps not render correctly - Fixed bug on LE --- .../generic/bean/IFrameTaskTemplateBean.java | 6 +++-- .../converter/BusinessEntityConverter.java | 16 +++++++++++++ .../resources/js/iframe-task-template.js | 23 ++++++++++++++++++- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/AxonIvyPortal/portal/src/ch/ivy/addon/portal/generic/bean/IFrameTaskTemplateBean.java b/AxonIvyPortal/portal/src/ch/ivy/addon/portal/generic/bean/IFrameTaskTemplateBean.java index 0f8079df42b..0b09928682a 100644 --- a/AxonIvyPortal/portal/src/ch/ivy/addon/portal/generic/bean/IFrameTaskTemplateBean.java +++ b/AxonIvyPortal/portal/src/ch/ivy/addon/portal/generic/bean/IFrameTaskTemplateBean.java @@ -3,7 +3,6 @@ import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -22,6 +21,7 @@ import org.apache.commons.lang3.math.NumberUtils; import ch.ivy.addon.portal.generic.navigation.PortalNavigator; +import ch.ivy.addon.portalkit.persistence.converter.BusinessEntityConverter; import ch.ivy.addon.portalkit.util.GrowlMessageUtils; import ch.ivyteam.ivy.dialog.execution.api.DialogInstance; import ch.ivyteam.ivy.environment.Ivy; @@ -148,7 +148,9 @@ public void navigateToUrl() throws IOException { public void getDataFromIFrame() throws Exception { Map requestParamMap = getRequestParameterMap(); String currentProcessStepText = requestParamMap.get(CURRENT_PROCESS_STEP_PARAM); - processSteps = StringUtils.isNotBlank(requestParamMap.get(PROCESS_STEPS_PARAM)) ? Arrays.asList(requestParamMap.get(PROCESS_STEPS_PARAM).split("\\s*,\\s*")) : new ArrayList<>(); + processSteps = StringUtils.isNotBlank(requestParamMap.get(PROCESS_STEPS_PARAM)) + ? BusinessEntityConverter.convertJsonToListString(requestParamMap.get(PROCESS_STEPS_PARAM)) + : new ArrayList<>(); stepIndexes = new ArrayList<>(); for (int i= 0; i < processSteps.size(); i++) { stepIndexes.add(String.valueOf(i)); diff --git a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/persistence/converter/BusinessEntityConverter.java b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/persistence/converter/BusinessEntityConverter.java index 8f2469956b7..17eee3ea058 100644 --- a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/persistence/converter/BusinessEntityConverter.java +++ b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/persistence/converter/BusinessEntityConverter.java @@ -5,6 +5,7 @@ import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -13,6 +14,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; @@ -111,4 +113,18 @@ public static ObjectMapper getObjectMapper() { } return objectMapper; } + + public static List convertJsonToListString(String value) + throws JsonMappingException, JsonProcessingException { + if (StringUtils.isBlank(value)) { + return null; + } + try { + return Arrays.asList(objectMapper.readValue(value, String[].class)); + } catch (Exception e) { + Arrays.asList(objectMapper.readValue(value, String.class)); + } + return new ArrayList<>(); + + } } diff --git a/AxonIvyPortal/portal/webContent/resources/js/iframe-task-template.js b/AxonIvyPortal/portal/webContent/resources/js/iframe-task-template.js index a1a6597bdf8..653b4dbdbb1 100644 --- a/AxonIvyPortal/portal/webContent/resources/js/iframe-task-template.js +++ b/AxonIvyPortal/portal/webContent/resources/js/iframe-task-template.js @@ -59,7 +59,7 @@ function processIFrameData(iframe) { value: window.currentProcessStep }, { name: 'processSteps', - value: window.processSteps + value: convertProcessSteps(window.processSteps) }, { name: 'isShowAllSteps', value: window.isShowAllSteps @@ -213,4 +213,25 @@ let updateHistory = (newHref) => { let historyUrl = new URL(window.location); historyUrl.searchParams.set('taskUrl', newHrefUrl.pathname + newHrefUrl.search); history.replaceState({}, "", historyUrl); +} + +const convertProcessSteps = processSteps => { + // If the process steps is a valid array, convert to JSON + if (Array.isArray(processSteps)) { + return JSON.stringify(processSteps); + } + + // Regex for "a,b,c" + const onlyCommaRegex = /^[a-zA-Z0-9\s]+(,[a-zA-Z0-9\s]+)*$/; + + // If process steps is a String in "a,b,c" format, split to array + // Then convert to JSON + if (onlyCommaRegex.test(processSteps)) { + const steps = processSteps.split(','); + const trimmedSteps = steps.map(item => item.trim()); + return JSON.stringify(trimmedSteps); + } + + // Otherwise return JSON of the process step + return JSON.stringify(processSteps); } \ No newline at end of file From 670c38623515a3e63501d19fed1268720ac1a582 Mon Sep 17 00:00:00 2001 From: "Nam.Chu" Date: Wed, 7 Feb 2024 17:43:40 +0700 Subject: [PATCH 03/24] Revert "IVYPORTAL-16459 Make Portal Open Source" This reverts commit 39aec68533d6230823e6fd98936c5bfcb78fb612. --- .github/workflows/main.yml | 33 --------------------------------- 1 file changed, 33 deletions(-) delete mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 8f07ff6205b..00000000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,33 +0,0 @@ ---- -name: Lint - -on: # yamllint disable-line rule:truthy - push: null - pull_request: null - -jobs: - build: - name: Lint - runs-on: ubuntu-latest - - permissions: - contents: read - packages: read - # To report GitHub Actions status checks - statuses: write - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - # super-linter needs the full git history to get the - # list of files that changed across commits - fetch-depth: 0 - - - name: Super-linter - uses: super-linter/super-linter@v6.0.0 # x-release-please-version - env: - DEFAULT_BRANCH: main - # To report GitHub Actions status checks - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -... From 24b76a0151e28a06517470434b698328f53575fc Mon Sep 17 00:00:00 2001 From: Reto Weiss Date: Thu, 8 Feb 2024 10:07:54 +0100 Subject: [PATCH 04/24] Use displayName and provide tooltip with the description of notif. event Use the new notification Event API with the displayName and description for the Notification Channels Subscriptions on the User Profile Page. --- .../portal/generic/bean/UserProfileBean.java | 11 +++---- .../dto/IvyNotificationChannelDTO.java | 25 +++++++++------ .../ivydata/dto/IvyNotificationEventDTO.java | 31 +++++++++++++++++++ .../axonivy/portal/bean/NotificationBean.java | 6 +--- .../setting/UserProfile/UserProfile.xhtml | 6 ++-- 5 files changed, 56 insertions(+), 23 deletions(-) create mode 100644 AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/ivydata/dto/IvyNotificationEventDTO.java diff --git a/AxonIvyPortal/portal/src/ch/ivy/addon/portal/generic/bean/UserProfileBean.java b/AxonIvyPortal/portal/src/ch/ivy/addon/portal/generic/bean/UserProfileBean.java index 8e2fab1ecfe..9568f313cd6 100644 --- a/AxonIvyPortal/portal/src/ch/ivy/addon/portal/generic/bean/UserProfileBean.java +++ b/AxonIvyPortal/portal/src/ch/ivy/addon/portal/generic/bean/UserProfileBean.java @@ -1,7 +1,6 @@ package ch.ivy.addon.portal.generic.bean; import java.io.Serializable; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -21,12 +20,12 @@ import ch.ivy.addon.portalkit.enums.TaskSortField; import ch.ivy.addon.portalkit.ivydata.dto.IvyNotificationChannelDTO; import ch.ivy.addon.portalkit.ivydata.dto.IvyNotificationChannelSubcriptionDTO; +import ch.ivy.addon.portalkit.ivydata.dto.IvyNotificationEventDTO; import ch.ivy.addon.portalkit.ivydata.service.impl.UserSettingService; import ch.ivy.addon.portalkit.service.GlobalSettingService; import ch.ivy.addon.portalkit.util.PermissionUtils; import ch.ivyteam.ivy.environment.Ivy; import ch.ivyteam.ivy.notification.channel.NotificationSubscription; -import ch.ivyteam.ivy.notification.event.NotificationEvent; import ch.ivyteam.ivy.security.ISecurityContext; import ch.ivyteam.ivy.security.ISecurityMember; import ch.ivyteam.ivy.security.IUser; @@ -44,7 +43,7 @@ public UserProfileBean() { private ISecurityMember subscriber; private ISecurityContext securityContext; - private List events; + private List events; private List channels; public void init() { @@ -131,8 +130,8 @@ private String getDefaultSelection(String sortFieldName) { } public void onloadChannel() { - events = new ArrayList<>(NotificationEvent.allAsString()); - channels = IvyNotificationChannelDTO.all(subscriber, securityContext, events); + events = IvyNotificationEventDTO.all(); + channels = IvyNotificationChannelDTO.all(subscriber, securityContext); } public void resetAllChannel() { @@ -166,7 +165,7 @@ public List getChannels() { return channels; } - public List getEvents() { + public List getEvents() { return events; } diff --git a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/ivydata/dto/IvyNotificationChannelDTO.java b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/ivydata/dto/IvyNotificationChannelDTO.java index 4b5e4cf3f3b..c49ec2fb7db 100644 --- a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/ivydata/dto/IvyNotificationChannelDTO.java +++ b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/ivydata/dto/IvyNotificationChannelDTO.java @@ -5,6 +5,7 @@ import java.util.stream.Collectors; import ch.ivyteam.ivy.environment.Ivy; +import ch.ivyteam.ivy.notification.channel.Event; import ch.ivyteam.ivy.notification.channel.NotificationChannel; import ch.ivyteam.ivy.notification.channel.NotificationSubscription; import ch.ivyteam.ivy.security.ISecurityContext; @@ -13,16 +14,16 @@ public class IvyNotificationChannelDTO { private final NotificationChannel channel; - private final Map subscriptions; - + private final Map subscriptions; + private IvyNotificationChannelDTO(NotificationChannel channel, - Map subscriptions) { + Map subscriptions) { this.channel = channel; this.subscriptions = subscriptions; } - public static List all(ISecurityMember subscriber, ISecurityContext securityContext, - List events) { + public static List all(ISecurityMember subscriber, ISecurityContext securityContext) { + var events = Event.all(); var channels = NotificationChannel.all(securityContext).stream().filter(channel -> channel.config().enabled()) .map(channel -> toChannel(subscriber, channel)).toList(); channels.forEach(channel -> events.forEach(event -> channel.setSubscriptionIconAndTitle(event))); @@ -31,7 +32,7 @@ public static List all(ISecurityMember subscriber, IS private static IvyNotificationChannelDTO toChannel(ISecurityMember subscriber, NotificationChannel channel) { var subscriptions = channel.configFor(subscriber).subscriptions().stream() - .collect(Collectors.toMap(NotificationSubscription::event, + .collect(Collectors.toMap(NotificationSubscription::_event, subscription -> new IvyNotificationChannelSubcriptionDTO(subscription.state(), subscription.isSubscribedByDefault()))); return new IvyNotificationChannelDTO(channel, subscriptions); @@ -41,15 +42,19 @@ public NotificationChannel getChannel() { return channel; } - public Map getSubscriptions() { + public Map getSubscriptions() { return subscriptions; } - public IvyNotificationChannelSubcriptionDTO getSubscription(String event) { - return subscriptions.get(event); + public IvyNotificationChannelSubcriptionDTO getSubscription(IvyNotificationEventDTO event) { + return subscriptions.get(event.getEvent()); + } + + public void setSubscriptionIconAndTitle(IvyNotificationEventDTO event) { + setSubscriptionIconAndTitle(event.getEvent()); } - public void setSubscriptionIconAndTitle(String event) { + private void setSubscriptionIconAndTitle(Event event) { var subscription = subscriptions.get(event); if (subscription != null) { var state = subscription.getState(); diff --git a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/ivydata/dto/IvyNotificationEventDTO.java b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/ivydata/dto/IvyNotificationEventDTO.java new file mode 100644 index 00000000000..7ab1fc5329b --- /dev/null +++ b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/ivydata/dto/IvyNotificationEventDTO.java @@ -0,0 +1,31 @@ +package ch.ivy.addon.portalkit.ivydata.dto; + +import java.util.List; +import java.util.stream.Collectors; + +import ch.ivyteam.ivy.notification.channel.Event; + +public class IvyNotificationEventDTO { + + private final Event event; + + private IvyNotificationEventDTO(Event event) { + this.event = event; + } + + public static List all() { + return Event.all().stream().map(IvyNotificationEventDTO::new).collect(Collectors.toList()); + } + + public String getDisplayName() { + return event.displayName(); + } + + public String getDescription() { + return event.description(); + } + + public Event getEvent() { + return event; + } +} diff --git a/AxonIvyPortal/portal/src/com/axonivy/portal/bean/NotificationBean.java b/AxonIvyPortal/portal/src/com/axonivy/portal/bean/NotificationBean.java index 7c9f0c757aa..a8b92ee992c 100644 --- a/AxonIvyPortal/portal/src/com/axonivy/portal/bean/NotificationBean.java +++ b/AxonIvyPortal/portal/src/com/axonivy/portal/bean/NotificationBean.java @@ -1,7 +1,6 @@ package com.axonivy.portal.bean; import java.io.Serializable; -import java.util.ArrayList; import java.util.List; import javax.faces.bean.ManagedBean; @@ -15,7 +14,6 @@ import ch.ivyteam.ivy.notification.channel.NotificationChannel; import ch.ivyteam.ivy.notification.channel.NotificationChannelSystemConfig; import ch.ivyteam.ivy.notification.channel.NotificationSubscription; -import ch.ivyteam.ivy.notification.event.NotificationEvent; import ch.ivyteam.ivy.notification.web.WebNotifications; import ch.ivyteam.ivy.security.ISecurityContext; import ch.ivyteam.ivy.security.ISecurityMember; @@ -35,18 +33,16 @@ public class NotificationBean implements Serializable { private List channels; private ISecurityContext securityContext; private ISecurityMember subscriber; - private List events; private Boolean isBellIconNeeded; public NotificationBean() { - this.events = new ArrayList<>(NotificationEvent.allAsString()); this.webNotifications = WebNotifications.current(); this.countAll = webNotifications.countAll(); this.countUnread = webNotifications.countUnread(); this.dataModel = new NotificationLazyModel(webNotifications); this.subscriber = Ivy.session().getSessionUser(); this.securityContext = ISecurityContext.current(); - this.channels = IvyNotificationChannelDTO.all(subscriber, securityContext, events); + this.channels = IvyNotificationChannelDTO.all(subscriber, securityContext); this.isBellIconNeeded = isNotificationBellRendered(); } diff --git a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portal/setting/UserProfile/UserProfile.xhtml b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portal/setting/UserProfile/UserProfile.xhtml index 65c63c1a1e7..f4a6e4fbf72 100644 --- a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portal/setting/UserProfile/UserProfile.xhtml +++ b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portal/setting/UserProfile/UserProfile.xhtml @@ -214,10 +214,12 @@ var="event" value="#{userProfileBean.events}" id="notification-Channels-Table" + rowIndexVar="rowIndex" lazy="false" styleClass="ui-fluid"> - + sortBy="#{event.displayName}"> + + From c2e471a35ddce695f6dcf3d2ad3fb0741bec86e0 Mon Sep 17 00:00:00 2001 From: mnhnam-axonivy Date: Thu, 15 Feb 2024 14:27:31 +0700 Subject: [PATCH 05/24] IVYPORTAL-16579 Incorrect alignment header of Case/Task widgets when has vertical scrollbar - Fixed for 10 # Conflicts: # AxonIvyPortal/portal/webContent/resources/js/dashboard.js --- .../dashboard/component/CaseWidget/CaseWidget.xhtml | 2 +- .../dashboard/component/TaskWidget/TaskWidget.xhtml | 2 +- .../portal/webContent/resources/js/dashboard.js | 11 +++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portal/generic/dashboard/component/CaseWidget/CaseWidget.xhtml b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portal/generic/dashboard/component/CaseWidget/CaseWidget.xhtml index 7fe73c0f8de..4c1482aec42 100644 --- a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portal/generic/dashboard/component/CaseWidget/CaseWidget.xhtml +++ b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portal/generic/dashboard/component/CaseWidget/CaseWidget.xhtml @@ -20,7 +20,7 @@ + oncomplete="loadWidgetFirstTime('js-loading-#{caseWidget.id}', 'js-dashboard-cases-container-#{caseWidget.id}'); initTableWidget(PF('dashboard-cases-#{caseWidget.id}')); #{cc.attrs.onCompleteLoadWidget}" />
diff --git a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portal/generic/dashboard/component/TaskWidget/TaskWidget.xhtml b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portal/generic/dashboard/component/TaskWidget/TaskWidget.xhtml index 048008bd324..8adcb6c499a 100644 --- a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portal/generic/dashboard/component/TaskWidget/TaskWidget.xhtml +++ b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portal/generic/dashboard/component/TaskWidget/TaskWidget.xhtml @@ -22,7 +22,7 @@ + oncomplete="loadWidgetFirstTime('js-loading-#{taskWidget.id}', 'js-dashboard-tasks-container-#{taskWidget.id}'); initTableWidget(PF('dashboard-tasks-#{taskWidget.id}')); #{cc.attrs.onCompleteLoadWidget};" />
diff --git a/AxonIvyPortal/portal/webContent/resources/js/dashboard.js b/AxonIvyPortal/portal/webContent/resources/js/dashboard.js index f6d4a71ac37..02bc23b1341 100644 --- a/AxonIvyPortal/portal/webContent/resources/js/dashboard.js +++ b/AxonIvyPortal/portal/webContent/resources/js/dashboard.js @@ -441,6 +441,17 @@ function setLineClamp(element, number) { function removeStyle(element) { $(element).removeAttr('style'); } +function initTableWidget(table) { + if (table === undefined || table.cfg === undefined) { + return; + } + + setTimeout(function(){ + var $table = $(document.getElementById(table.id)); + table.cfg.scrollHeight = $table.height().toString(); + table.init(table.cfg); + }, 1000); +} function searcNewhWidgetByNameOrDescription(input) { var keyword = input.value.toLowerCase(); From fa9d6f7059a89daf99a4a8b7b628a50e3a31dfd9 Mon Sep 17 00:00:00 2001 From: mnhnam-axonivy Date: Mon, 19 Feb 2024 09:46:31 +0700 Subject: [PATCH 06/24] IVYPORTAL-16476: Process Steps not render correctly - Fixed JS error when user don't define process steps --- .../portal/webContent/resources/js/iframe-task-template.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/AxonIvyPortal/portal/webContent/resources/js/iframe-task-template.js b/AxonIvyPortal/portal/webContent/resources/js/iframe-task-template.js index 653b4dbdbb1..b0756153dbb 100644 --- a/AxonIvyPortal/portal/webContent/resources/js/iframe-task-template.js +++ b/AxonIvyPortal/portal/webContent/resources/js/iframe-task-template.js @@ -216,6 +216,11 @@ let updateHistory = (newHref) => { } const convertProcessSteps = processSteps => { + // If process steps are not defined, don't do anything + if (processSteps === undefined) { + return ''; + } + // If the process steps is a valid array, convert to JSON if (Array.isArray(processSteps)) { return JSON.stringify(processSteps); From af5473ed21b53f86c02d4884074ef853e095de79 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Tue, 20 Feb 2024 06:02:44 +0700 Subject: [PATCH 07/24] Feature/ivyportal 15289 process order in process start widget on dashboard _ Revert ProcessType --- .../portal/src/ch/ivy/addon/portalkit/enums/ProcessType.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/enums/ProcessType.java b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/enums/ProcessType.java index 982f3785f32..133bb3ad388 100644 --- a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/enums/ProcessType.java +++ b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/enums/ProcessType.java @@ -22,7 +22,7 @@ public String getValue() { } public String getLabel() { - String label = Ivy.cms().co("/ch.ivy.addon.portalkit.ui.jsf/Enums/ProcessSorting" + name()); + String label = Ivy.cms().co("/ch.ivy.addon.portalkit.ui.jsf/Enums/ProcessType/" + name()); return StringUtils.isBlank(label) ? name() : label; } From f34706ed2409b77e5a56f3e3aa0b5b9e57d1a0f7 Mon Sep 17 00:00:00 2001 From: Thai Phan <117443490+pvthai-axonivy@users.noreply.github.com> Date: Tue, 20 Feb 2024 14:15:26 +0700 Subject: [PATCH 08/24] feature/IVYPORTAL-16339-Error-when-parsing-number-field-LE (#487) * IVYPORTAL-16339: Error when parsing number field --- .../widget/NumberFilterColumn/NumberFilterColumn.xhtml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/dashboard/widget/NumberFilterColumn/NumberFilterColumn.xhtml b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/dashboard/widget/NumberFilterColumn/NumberFilterColumn.xhtml index c6f09d8d0fd..b2b7bfdddc8 100644 --- a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/dashboard/widget/NumberFilterColumn/NumberFilterColumn.xhtml +++ b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/dashboard/widget/NumberFilterColumn/NumberFilterColumn.xhtml @@ -19,9 +19,7 @@
- - + minValue="#{empty cc.attrs.minFilterForm ? '-999999999.99' : cc.attrs.minFilterForm}"/>
@@ -31,9 +29,7 @@
- - + maxValue="#{empty cc.attrs.maxFilterTo ? '999999999.99' : cc.attrs.maxFilterTo}"/>
From c601c6e085149787369674df7af687555a07bfda Mon Sep 17 00:00:00 2001 From: mnhnam-axonivy Date: Tue, 20 Feb 2024 17:08:39 +0700 Subject: [PATCH 09/24] IVYPORTAL-16579-Alignment-of-task-widget-and-case-widget-s-headlines-not-correct-horizontally-scrolling-10 - Reduce 500ms --- AxonIvyPortal/portal/webContent/resources/js/dashboard.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AxonIvyPortal/portal/webContent/resources/js/dashboard.js b/AxonIvyPortal/portal/webContent/resources/js/dashboard.js index 02bc23b1341..3c5d7811843 100644 --- a/AxonIvyPortal/portal/webContent/resources/js/dashboard.js +++ b/AxonIvyPortal/portal/webContent/resources/js/dashboard.js @@ -450,7 +450,7 @@ function initTableWidget(table) { var $table = $(document.getElementById(table.id)); table.cfg.scrollHeight = $table.height().toString(); table.init(table.cfg); - }, 1000); + }, 500); } function searcNewhWidgetByNameOrDescription(input) { From dfa626e1b0df5fd9188f519b4aa8f1f25ed2cc46 Mon Sep 17 00:00:00 2001 From: Thai Phan <117443490+pvthai-axonivy@users.noreply.github.com> Date: Thu, 22 Feb 2024 11:06:19 +0700 Subject: [PATCH 10/24] feature/IVYPORTAL-16344-Task-Details-Task-Status-Banner (#511) * IVYPORTAL-16344: Task Status Banner for taskdetail page * IVYPORTAL-16344: add guitest * IVYPORTAL-16344: change slave * IVYPORTAL-16344: Handle feedback * Revert "IVYPORTAL-16344: change slave" This reverts commit b5ecfd6befdd55f2a9450aeb8cf3077dddf2f458. * IVYPORTAL-16344: Handle feedback * IVYPORTAL-16344: add doc (#513) --- .../resources/js/document-screenshot.js | 4 + .../screenshot/PortalTaskScreenshotTest.java | 9 ++ .../portal/selenium/page/TaskDetailsPage.java | 4 + .../selenium/test/task/TaskDetailsTest.java | 39 ++++++++ AxonIvyPortal/portal/cms/cms_de.yaml | 7 ++ AxonIvyPortal/portal/cms/cms_en.yaml | 7 ++ AxonIvyPortal/portal/cms/cms_es.yaml | 7 ++ AxonIvyPortal/portal/cms/cms_fr.yaml | 7 ++ .../Start Processes/PortalStart.p.json | 72 ++++++++++++++ .../addon/portalkit/bean/TaskDetailsBean.java | 99 +++++++++++++++++++ .../TaskItemDetails/TaskItemDetails.xhtml | 6 +- .../webContent/resources/css/module.css | 8 ++ .../full-task-list/index.rst | 7 +- 13 files changed, 274 insertions(+), 2 deletions(-) diff --git a/AxonIvyPortal/portal-selenium-test/resources/js/document-screenshot.js b/AxonIvyPortal/portal-selenium-test/resources/js/document-screenshot.js index 1e47a50b70c..a7d6a1d18f5 100644 --- a/AxonIvyPortal/portal-selenium-test/resources/js/document-screenshot.js +++ b/AxonIvyPortal/portal-selenium-test/resources/js/document-screenshot.js @@ -465,6 +465,10 @@ function highlightTaskDetailComponent() { appendStepAnnotation(histories, 3, 0, histories.width()/2); } +function highlightTaskStatusBanner() { + createRedMediumOutline($("[id$=':task-status-banner']")); +} + function highlightUserExampleCard(cardIndex) { var userCardId = '#panel-' + cardIndex; createRedThickOutline($(userCardId).parent()); diff --git a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/document/screenshot/PortalTaskScreenshotTest.java b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/document/screenshot/PortalTaskScreenshotTest.java index bf0902f89bd..03348e5e468 100644 --- a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/document/screenshot/PortalTaskScreenshotTest.java +++ b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/document/screenshot/PortalTaskScreenshotTest.java @@ -62,6 +62,8 @@ public void screenshotBasicTaskDetails() throws IOException { NewDashboardPage homePage = new NewDashboardPage(); homePage.waitForCaseWidgetLoaded(); TaskWidgetPage taskWidget = mainMenuPage.openTaskList(); + taskWidget.startTask(0); + mainMenuPage.openTaskList(); WaitHelper.waitForNavigation(() -> taskWidget.openTaskDetail(0)); TaskDetailsPage detailsPage = new TaskDetailsPage(); detailsPage.waitUtilsTaskDetailsDisplayed(); @@ -122,9 +124,16 @@ public void screenshotTaskDetails() throws IOException { NewDashboardPage newDashboardPage = new NewDashboardPage(); newDashboardPage.waitForCaseWidgetLoaded(); TaskWidgetPage taskWidget = mainMenuPage.openTaskList(); + taskWidget.startTask(0); + showNewDashboard(); + mainMenuPage.openTaskList(); TaskDetailsPage taskDetails = taskWidget.openTaskDetail(0); taskDetails.waitUtilsTaskDetailsDisplayed(); + ScreenshotUtils.resizeBrowser(new Dimension(2560, 1440)); + ScreenshotUtils.executeDecorateJs("highlightTaskStatusBanner()"); + ScreenshotUtils.capturePageScreenshot(ScreenshotUtils.TASK_DETAIL_FOLDER + "task-status-banner"); + ScreenshotUtils.resizeBrowser(new Dimension(2560, 1440)); ScreenshotUtils.captureElementWithMarginOptionScreenshot(taskDetails.getTaskGeneralInformation(), ScreenshotUtils.TASK_DETAIL_FOLDER + "detailed-task-information-data-description", new ScreenshotMargin(10)); diff --git a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/page/TaskDetailsPage.java b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/page/TaskDetailsPage.java index 81b7b110ad8..327d11ebe8d 100644 --- a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/page/TaskDetailsPage.java +++ b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/page/TaskDetailsPage.java @@ -64,6 +64,10 @@ public SelenideElement getInformationPanel() { return $("div[id$='task-details-information-panel'").shouldBe(Condition.appear, DEFAULT_TIMEOUT); } + public SelenideElement getStatusBanner() { + return $("div[id$='task-detail-template:task-status-banner'"); + } + public void openActionPanel() { $("[id$=':additional-options:task-detail-more-step']").shouldBe(Condition.appear, DEFAULT_TIMEOUT) .shouldBe(getClickableCondition()).click(); diff --git a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/test/task/TaskDetailsTest.java b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/test/task/TaskDetailsTest.java index 0d137ba08c0..34d01f6d0b7 100644 --- a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/test/task/TaskDetailsTest.java +++ b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/test/task/TaskDetailsTest.java @@ -8,6 +8,7 @@ import com.axonivy.ivy.webtest.IvyWebTest; import com.axonivy.portal.selenium.common.BaseTest; +import com.axonivy.portal.selenium.common.TaskState; import com.axonivy.portal.selenium.common.TestAccount; import com.axonivy.portal.selenium.common.Variable; import com.axonivy.portal.selenium.page.CaseDetailsPage; @@ -99,4 +100,42 @@ public void testShareTaskDetails() { taskWidgetPage.openTask(TAKE_ORDER); taskDetailsPage.getShareButton().shouldBe(Condition.disappear); } + + @Test + public void testShowTaskStatusBannerOnTaskDetails() { + redirectToRelativeLink(createTestingTasksUrl); + login(TestAccount.ADMIN_USER); + redirectToNewDashBoard(); + MainMenuPage mainMenuPage = new MainMenuPage(); + mainMenuPage.openTaskList(); + TaskWidgetPage taskWidgetPage = new TaskWidgetPage(); + + taskWidgetPage.openTask("Maternity Leave Request"); + TaskDetailsPage taskDetailsPage = new TaskDetailsPage(); + taskDetailsPage.clickStartTask(); + redirectToNewDashBoard(); + mainMenuPage.openTaskList(); + taskWidgetPage.openTask("Maternity Leave Request"); + taskDetailsPage.getStatusBanner().shouldBe(Condition.appear, DEFAULT_TIMEOUT); + + mainMenuPage.openTaskList(); + taskWidgetPage.filterTasksBy("Sick Leave Request"); + taskWidgetPage.waitTillNameOfFirstTaskToBe("Sick Leave Request"); + taskWidgetPage.isTaskDestroyEnabled(0); + taskWidgetPage.destroyTask(0); + taskWidgetPage.confimDestruction(); + taskWidgetPage.checkTaskState(0, TaskState.DESTROYED.getValue()); + taskWidgetPage.openTask("Sick Leave Request"); + taskDetailsPage.getStatusBanner().shouldBe(Condition.appear, DEFAULT_TIMEOUT); + + redirectToRelativeLink(createCaseWithTechnicalCaseUrl); + mainMenuPage.openTaskList(); + taskWidgetPage.filterTasksBy(TAKE_ORDER); + taskWidgetPage.waitTillNameOfFirstTaskToBe(TAKE_ORDER); + taskWidgetPage.startTaskWithoutUI(0); + redirectToNewDashBoard(); + mainMenuPage.openTaskList(); + taskWidgetPage.openTask(TAKE_ORDER); + taskDetailsPage.getStatusBanner().shouldBe(Condition.appear, DEFAULT_TIMEOUT); + } } diff --git a/AxonIvyPortal/portal/cms/cms_de.yaml b/AxonIvyPortal/portal/cms/cms_de.yaml index a55b6551da9..58f0e6dac80 100644 --- a/AxonIvyPortal/portal/cms/cms_de.yaml +++ b/AxonIvyPortal/portal/cms/cms_de.yaml @@ -1569,7 +1569,14 @@ Dialogs: IconSelection: IconLinkTooltip: Zum Ändern des Symbols anklicken TaskItemDetails: + CannotResetTaskWarning: Jemand arbeitet derzeit an dieser Aufgabe. + CannotWorkOnDestroyedTaskWarning: Sie können nicht an der Aufgabe arbeiten, weil sie zerstört wurde. + CannotWorkOnTaskWarning: Sie können die Aufgabe nicht bearbeiten, weil der Benutzer {0} gerade daran arbeitet. + InvalidStateInfo: Ungültiger Zustand. + ResetTaskWarning: Sie arbeiten gerade an der Aufgabe in einer anderen Sitzung. Sie können die Aufgabe zurücksetzen und erneut starten, wenn Sie Ihren Browser geschlossen haben. Zurücksetzen ShareTaskDetails: Teilen Sie Aufgabendetails + TaskCompletedByOtherInfo: Die Aufgabe wurde bereits von Benutzer {0} an {1} durchgeführt. + TaskCompletedByYouInfo: Sie haben die Aufgabe auf {0} bereits erledigt. com: axonivy: portal: diff --git a/AxonIvyPortal/portal/cms/cms_en.yaml b/AxonIvyPortal/portal/cms/cms_en.yaml index 5947bc579c7..dec12dd8928 100644 --- a/AxonIvyPortal/portal/cms/cms_en.yaml +++ b/AxonIvyPortal/portal/cms/cms_en.yaml @@ -1569,7 +1569,14 @@ Dialogs: IconSelection: IconLinkTooltip: Click to change icon TaskItemDetails: + CannotResetTaskWarning: Someone is currently working on this task. + CannotWorkOnDestroyedTaskWarning: You cannot work on the task because it was destroyed. + CannotWorkOnTaskWarning: You cannot work on the task because user {0} is currently working on it. + InvalidStateInfo: Invalid state. + ResetTaskWarning: You are currently working on the task in a different session. You may reset the task and start it again if you have closed your browser. Reset ShareTaskDetails: Share task details + TaskCompletedByOtherInfo: Task has already been completed by user {0} on {1}. + TaskCompletedByYouInfo: You already have completed the task on {0}. com: axonivy: portal: diff --git a/AxonIvyPortal/portal/cms/cms_es.yaml b/AxonIvyPortal/portal/cms/cms_es.yaml index 144d0720c28..4c3203a7828 100644 --- a/AxonIvyPortal/portal/cms/cms_es.yaml +++ b/AxonIvyPortal/portal/cms/cms_es.yaml @@ -1571,7 +1571,14 @@ Dialogs: IconSelection: IconLinkTooltip: Haga clic para cambiar el icono TaskItemDetails: + CannotResetTaskWarning: Alguien está trabajando actualmente en esta tarea. + CannotWorkOnDestroyedTaskWarning: No puedes trabajar en la tarea porque ha sido destruida. + CannotWorkOnTaskWarning: No puede procesar la tarea porque el usuario {0} está trabajando actualmente en ella. + InvalidStateInfo: Estado no válido. + ResetTaskWarning: Actualmente está trabajando en la tarea en una sesión diferente. Puede reiniciar la tarea y empezarla de nuevo si ha cerrado el navegador. Restablecer ShareTaskDetails: Compartir detalles de la tarea + TaskCompletedByOtherInfo: La tarea ya ha sido completada por el usuario {0} en {1}. + TaskCompletedByYouInfo: Ya ha completado la tarea en {0}. com: axonivy: portal: diff --git a/AxonIvyPortal/portal/cms/cms_fr.yaml b/AxonIvyPortal/portal/cms/cms_fr.yaml index 8d1ba8355ea..3ca06c4f1e6 100644 --- a/AxonIvyPortal/portal/cms/cms_fr.yaml +++ b/AxonIvyPortal/portal/cms/cms_fr.yaml @@ -1564,7 +1564,14 @@ Dialogs: IconSelection: IconLinkTooltip: Cliquez pour changer d'icône TaskItemDetails: + CannotResetTaskWarning: Quelqu'un travaille actuellement sur cette tâche. + CannotWorkOnDestroyedTaskWarning: Vous ne pouvez pas travailler sur la tâche parce qu'elle a été détruite. + CannotWorkOnTaskWarning: Vous ne pouvez pas travailler sur cette tâche car l'utilisateur {0} y travaille déjà. + InvalidStateInfo: État non valide. + ResetTaskWarning: Vous travaillez actuellement sur la tâche dans une autre session. Vous pouvez réinitialiser la tâche et la recommencer si vous avez fermé votre navigateur. Réinitialiser ShareTaskDetails: Partager les détails de la tâche + TaskCompletedByOtherInfo: La tâche a déjà été effectuée par l'utilisateur {0} sur {1}. + TaskCompletedByYouInfo: Vous avez déjà accompli la tâche sur {0}. com: axonivy: portal: diff --git a/AxonIvyPortal/portal/processes/Start Processes/PortalStart.p.json b/AxonIvyPortal/portal/processes/Start Processes/PortalStart.p.json index fb22e76dd32..74d6463bf74 100644 --- a/AxonIvyPortal/portal/processes/Start Processes/PortalStart.p.json +++ b/AxonIvyPortal/portal/processes/Start Processes/PortalStart.p.json @@ -4650,5 +4650,77 @@ "visual" : { "at" : { "x" : 376, "y" : 3848 } } + }, { + "id" : "f269", + "type" : "RequestStart", + "name" : "ResetTask.ivp", + "config" : { + "signature" : "ResetTask", + "input" : { + "params" : [ + { "name" : "uuid", "type" : "String", "desc" : "" } + ], + "map" : { + "out.uuid" : "param.uuid" + } + } + }, + "visual" : { + "at" : { "x" : 96, "y" : 3952 } + }, + "connect" : [ + { "id" : "f273", "to" : "f271" } + ] + }, { + "id" : "f271", + "type" : "Script", + "name" : "Reset Task", + "config" : { + "output" : { + "code" : [ + "import ch.ivy.addon.portalkit.ivydata.service.impl.TaskService;", + "import ch.ivyteam.ivy.workflow.ITask;", + "import ch.ivy.addon.portalkit.util.TaskUtils;", + "", + "ITask task = TaskService.newInstance().findTaskByUUID(in.uuid);", + "", + "if(task != null) {", + " TaskUtils.resetTask(task);", + "}" + ] + } + }, + "visual" : { + "at" : { "x" : 240, "y" : 3952 } + }, + "connect" : [ + { "id" : "f277", "to" : "f278" } + ] + }, { + "id" : "f270", + "type" : "TaskEnd", + "visual" : { + "at" : { "x" : 936, "y" : 3952 } + } + }, { + "id" : "f278", + "type" : "SubProcessCall", + "name" : "Functional Processes/Navigator", + "config" : { + "processCall" : "Functional Processes/Navigator:viewRelatedTask(String,Boolean)", + "call" : { + "map" : { + "param.taskUUID" : "in.uuid", + "param.showItemDetailsHeader" : "false" + } + } + }, + "visual" : { + "at" : { "x" : 631, "y" : 3951 }, + "size" : { "width" : 255, "height" : 61 } + }, + "connect" : [ + { "id" : "f279", "to" : "f270" } + ] } ] } \ No newline at end of file diff --git a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/TaskDetailsBean.java b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/TaskDetailsBean.java index daeef4e79c6..6bfcddc4b8c 100644 --- a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/TaskDetailsBean.java +++ b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/TaskDetailsBean.java @@ -1,26 +1,48 @@ package ch.ivy.addon.portalkit.bean; import java.io.Serializable; +import java.util.Arrays; +import java.util.Date; +import java.util.EnumSet; +import java.util.HashMap; import java.util.List; +import java.util.Map; import javax.faces.bean.ManagedBean; import javax.faces.bean.ViewScoped; import com.axonivy.portal.components.publicapi.PortalNavigatorAPI; +import ch.ivy.addon.portal.generic.navigation.PortalNavigator; import ch.ivy.addon.portalkit.dto.taskdetails.TaskDetails; import ch.ivy.addon.portalkit.enums.GlobalVariable; +import ch.ivy.addon.portalkit.enums.PortalPermission; import ch.ivy.addon.portalkit.enums.PortalVariable; import ch.ivy.addon.portalkit.jsf.Attrs; +import ch.ivy.addon.portalkit.jsf.ManagedBeans; +import ch.ivy.addon.portalkit.service.DateTimeGlobalSettingService; import ch.ivy.addon.portalkit.util.PermissionUtils; +import ch.ivy.addon.portalkit.util.SecurityMemberDisplayNameUtils; +import ch.ivy.addon.portalkit.util.TaskUtils; import ch.ivyteam.ivy.environment.Ivy; +import ch.ivyteam.ivy.security.ISession; import ch.ivyteam.ivy.workflow.ITask; +import ch.ivyteam.ivy.workflow.TaskState; @ViewScoped @ManagedBean public class TaskDetailsBean extends AbstractConfigurableContentBean implements Serializable { private static final long serialVersionUID = 8566646437739271552L; + private static final String RESET_TASK_FRIENDLY_REQUEST_PATH = "Start Processes/PortaStart/ResetTask.ivp"; + private static final String RESET_TASK_WARNING_CMS_URI = "/Dialogs/ch/ivy/addon/portalkit/component/TaskItemDetails/ResetTaskWarning"; + private static final String CANNOT_WORK_ON_TASK_WARNING_CMS_URI = "/Dialogs/ch/ivy/addon/portalkit/component/TaskItemDetails/CannotWorkOnTaskWarning"; + private static final String CANNOT_WORK_ON_DESTROYED_TASK_WARNING_CMS_URI = "/Dialogs/ch/ivy/addon/portalkit/component/TaskItemDetails/CannotWorkOnDestroyedTaskWarning"; + private static final String CANNOT_RESET_TASK_WARNING_CMS_URI = "/Dialogs/ch/ivy/addon/portalkit/component/TaskItemDetails/CannotResetTaskWarning"; + private static final String TASK_COMPLETED_BY_YOU_INFO_CMS_URI = "/Dialogs/ch/ivy/addon/portalkit/component/TaskItemDetails/TaskCompletedByYouInfo"; + private static final String TASK_COMPLETED_BY_OTHER_INFO_CMS_URI = "/Dialogs/ch/ivy/addon/portalkit/component/TaskItemDetails/TaskCompletedByOtherInfo"; + private static final String INVALID_STATE_INFO_CMS_URI = "/Dialogs/ch/ivy/addon/portalkit/component/TaskItemDetails/InvalidStateInfo"; + private static final String UUID = "uuid"; private boolean hasShowDurationTime; private String taskDetailsDescription; private String taskDetailsUrl; @@ -122,4 +144,81 @@ protected Class getConfigurationType() { protected String getDefaultConfigId() { return "default-task-detail"; } + + public void resetTask() { + TaskUtils.resetTask(getSelectedTaskFromData()); + } + + public boolean showInfoBanner() { + ITask selectedTask = getSelectedTaskFromData(); + EnumSet activeTaskStates = EnumSet.of(TaskState.DONE, TaskState.READY_FOR_JOIN, TaskState.JOINING, TaskState.JOIN_FAILED, TaskState.CREATED, TaskState.RESUMED, TaskState.PARKED, TaskState.DESTROYED); + return isActivator() && activeTaskStates.contains(selectedTask.getState()); + } + + private boolean canResetTask() { + if (!PermissionUtils.hasPortalPermission(PortalPermission.TASK_DISPLAY_RESET_ACTION)) { + return false; + } + TaskActionBean taskActionBean = ManagedBeans.get("taskActionBean"); + if (taskActionBean != null) { + ITask selectedTask = getSelectedTaskFromData(); + return taskActionBean.canReset(selectedTask); + } + return false; + } + + public String getInfoBannerSeverity() { + ITask selectedTask = getSelectedTaskFromData(); + boolean validState = selectedTask.getState() == TaskState.RESUMED || selectedTask.getState() == TaskState.CREATED; + boolean notCurrentSession = selectedTask.getWorkerSession() != ISession.current(); + return validState && notCurrentSession && isActivator() && currentIsWorkerUser() ? "warn" : "info"; + } + + public String getInfoBannerMessage() { + ITask selectedTask = getSelectedTaskFromData(); + return switch (selectedTask.getState()) { + case CREATED, RESUMED, PARKED -> { + if (currentIsWorkerUser()) { + yield Ivy.cms().co(RESET_TASK_WARNING_CMS_URI, Arrays.asList(buildResetTaskUrl(selectedTask))); + } else if (canResetTask()) { + yield Ivy.cms().co(CANNOT_WORK_ON_TASK_WARNING_CMS_URI, Arrays.asList(getWorkerUser(selectedTask))); + } else { + yield Ivy.cms().co(CANNOT_RESET_TASK_WARNING_CMS_URI); + } + } + case DONE, READY_FOR_JOIN, JOINING, JOIN_FAILED -> { + if (currentIsWorkerUser()) { + yield Ivy.cms().co(TASK_COMPLETED_BY_YOU_INFO_CMS_URI, Arrays.asList(formatDate(selectedTask.getEndTimestamp()))); + } else { + yield Ivy.cms().co(TASK_COMPLETED_BY_OTHER_INFO_CMS_URI, Arrays.asList(getWorkerUser(selectedTask), formatDate(selectedTask.getEndTimestamp()))); + } + } + case DESTROYED -> Ivy.cms().co(CANNOT_WORK_ON_DESTROYED_TASK_WARNING_CMS_URI); + default -> Ivy.cms().co(INVALID_STATE_INFO_CMS_URI); + }; + } + + private boolean isActivator() { + ITask selectedTask = getSelectedTaskFromData(); + return selectedTask.getActivator() == null ? false : selectedTask.getActivator().isMember(ISession.current(), true); + } + + private boolean currentIsWorkerUser() { + ITask selectedTask = getSelectedTaskFromData(); + return selectedTask.getWorkerUser() == null ? false : selectedTask.getWorkerUser().equals(ISession.current().getSessionUser()); + } + + private String buildResetTaskUrl(ITask task) { + Map params = new HashMap<>(); + params.put(UUID, task.uuid()); + return PortalNavigator.buildUrlByKeyword("ResetTask.ivp", RESET_TASK_FRIENDLY_REQUEST_PATH, params); + } + + private String formatDate(Date datetime) { + return DateTimeGlobalSettingService.getInstance().getDefaultDateTimeFormatter().format(datetime); + } + + private String getWorkerUser(ITask selectedTask) { + return SecurityMemberDisplayNameUtils.generateBriefDisplayNameForSecurityMember(selectedTask.getWorkerUser(), selectedTask.getWorkerUser().getMemberName()); + } } diff --git a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/TaskItemDetails/TaskItemDetails.xhtml b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/TaskItemDetails/TaskItemDetails.xhtml index 805236c6022..7baea386370 100644 --- a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/TaskItemDetails/TaskItemDetails.xhtml +++ b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/TaskItemDetails/TaskItemDetails.xhtml @@ -64,6 +64,10 @@
+ + + + @@ -108,7 +112,7 @@ actionLinkId="task-detail-more-step" panelWidgetVar="task-side-steps-panel-#{rowIndex}" isAdhocExcluded="false" renderedAdditionalAction="#{taskActionBean.showAdditionalOptions(task)}" task="#{task}" isDisabled="#{taskActionBean.noActionAvailable(task) or cc.attrs.readOnly}" - componentToUpdate="#{informationWidgetComponent} #{historyWidgetComponent}" onDestroyComplete="PF('destroy-task-dialog').show()" + componentToUpdate="#{informationWidgetComponent} #{historyWidgetComponent} #{cc.clientId}:task-status-banner" onDestroyComplete="PF('destroy-task-dialog').show()" onEscalationComplete="PF('escalation-task-dialog').show()" showInTaskList="false" onDelegateComplete="initDataToDelegate()" onWorkflowEventComplete="initWorkflowEventData()" /> diff --git a/AxonIvyPortal/portal/webContent/resources/css/module.css b/AxonIvyPortal/portal/webContent/resources/css/module.css index 1a0cdfdf163..9b06de252a6 100644 --- a/AxonIvyPortal/portal/webContent/resources/css/module.css +++ b/AxonIvyPortal/portal/webContent/resources/css/module.css @@ -6068,4 +6068,12 @@ body .ui-dialog.ui-widget.import-dashboard-dialog{ .combined-mode-loading-container { overflow: hidden; +} + +.task-status-banner { + padding: 0.5em; +} + +.task-status-banner-content { + width: 100%; } \ No newline at end of file diff --git a/Documentation/portal-guide/source/portal-user-guide/full-task-list/index.rst b/Documentation/portal-guide/source/portal-user-guide/full-task-list/index.rst index ba1e056c0ca..a7d3434b7bb 100644 --- a/Documentation/portal-guide/source/portal-user-guide/full-task-list/index.rst +++ b/Documentation/portal-guide/source/portal-user-guide/full-task-list/index.rst @@ -82,7 +82,11 @@ handling tasks: Finally, you have the possibility to access the full set of the task data by clicking on the row containing the task Name and Description. -The task details are separated into 3 different sections: +The task details are separated into 4 different sections: + +#. Task status banner to display information that needs attention or that you should take action on. + + |task-status-banner| #. Data and description, you find various metadata concerning the task and the respective case it is related to. @@ -361,3 +365,4 @@ HowTo: Share Task Details .. |how-to-switch-to-edit-mode| image:: ../../screenshots/task-detail/how-to-switch-to-edit-mode.png .. |how-to-reset-to-default| image:: ../../screenshots/task-detail/how-to-reset-to-default.png .. |how-to-share-task-details| image:: ../../screenshots/task-detail/share-page-button.png +.. |task-status-banner| image:: ../../screenshots/task-detail/task-status-banner.png From b9ffdd8b6d86bd96dec51236ad8553fe464d49cf Mon Sep 17 00:00:00 2001 From: "Nam.Chu" Date: Thu, 22 Feb 2024 15:06:28 +0700 Subject: [PATCH 11/24] IVYPORTAL-14904 External Link Widget MultiLanguage Support - Implement for LE --- .../portal/components/dto/DisplayName.java | 56 -------- .../resources/js/document-screenshot.js | 19 ++- .../portalkit/bean/ExternalLinkBean.java | 44 ++++++- .../portalkit/bean/ProcessWidgetBean.java | 70 ++++++++-- .../portalkit/configuration/ExternalLink.java | 62 ++++++++- .../portalkit/util/DisplayNameConvertor.java | 43 +++++++ .../ProcessWidget/ProcessWidgetDialogs.xhtml | 121 +++++++++++++++++- .../webContent/resources/css/module.css | 17 ++- .../full-process-list/index.rst | 6 + 9 files changed, 348 insertions(+), 90 deletions(-) delete mode 100644 AxonIvyPortal/portal-components/src/com/axonivy/portal/components/dto/DisplayName.java diff --git a/AxonIvyPortal/portal-components/src/com/axonivy/portal/components/dto/DisplayName.java b/AxonIvyPortal/portal-components/src/com/axonivy/portal/components/dto/DisplayName.java deleted file mode 100644 index 5c45a9fde0d..00000000000 --- a/AxonIvyPortal/portal-components/src/com/axonivy/portal/components/dto/DisplayName.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.axonivy.portal.components.dto; - -import java.util.Locale; - -import org.apache.commons.lang3.builder.EqualsBuilder; -import org.apache.commons.lang3.builder.HashCodeBuilder; - -public class DisplayName { - - private Locale locale; - private String value; - - public DisplayName() { - } - - public DisplayName(Locale locale, String value) { - this.locale = locale; - this.value = value; - } - - public Locale getLocale() { - return locale; - } - - public void setLocale(Locale locale) { - this.locale = locale; - } - - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof DisplayName)) { - return false; - } - DisplayName other = (DisplayName) obj; - EqualsBuilder builder = new EqualsBuilder(); - builder.append(value, other.getValue()); - builder.append(locale, other.locale); - return builder.isEquals(); - } - - @Override - public int hashCode() { - HashCodeBuilder builder = new HashCodeBuilder(); - builder.append(getValue()); - builder.append(getLocale()); - return builder.hashCode(); - } -} diff --git a/AxonIvyPortal/portal-selenium-test/resources/js/document-screenshot.js b/AxonIvyPortal/portal-selenium-test/resources/js/document-screenshot.js index 1e47a50b70c..5bbe73e517a 100644 --- a/AxonIvyPortal/portal-selenium-test/resources/js/document-screenshot.js +++ b/AxonIvyPortal/portal-selenium-test/resources/js/document-screenshot.js @@ -127,20 +127,29 @@ function highlightAddExternalDialogItem() { var linkName = $("label[for$='add-external-link-form:external-link-name']"); appendStepAnnotation(linkName, "3", -5, linkName.width()); + var translateName = $("button[id$='process-widget:add-external-link-form:add-name-language-button']"); + appendStepAnnotation(translateName, "4", -5, translateName.width()); + var startLink = $("label[for$='add-external-link-form:external-link']"); - appendStepAnnotation(startLink, "4", -5, startLink.width()); + appendStepAnnotation(startLink, "5", -5, startLink.width()); + + var linkDescription = $("label[for$='process-widget:add-external-link-form:external-description']"); + appendStepAnnotation(linkDescription, "6", -5, linkDescription.width()); + + var translateDescription = $("button[id$='process-widget:add-external-link-form:add-description-language-button']"); + appendStepAnnotation(translateDescription, "7", -5, translateDescription.width()); var visibility = $("label[for$='add-external-link-form:external-link-type-radio']"); - appendStepAnnotation(visibility, "5", -5, visibility.width()); + appendStepAnnotation(visibility, "8", -5, visibility.width()); var icon = $("[id$='add-external-link-form:external-link-icon:awesome-icon-selection']"); - appendStepAnnotation(icon, "6", -10, icon.width()); + appendStepAnnotation(icon, "9", -10, icon.width()); var uploadButton = $("[id$='add-external-link-form:external-link-image-upload']"); - appendStepAnnotation(uploadButton, "7", 0, 80); + appendStepAnnotation(uploadButton, "10", 0, 80); var addButton = $("[id$='process-widget:adding-new-external-link-command']"); - appendStepAnnotation(addButton, "8", -25, -5); + appendStepAnnotation(addButton, "11", -25, -5); } function highlightProcessItems() { diff --git a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/ExternalLinkBean.java b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/ExternalLinkBean.java index 61ac1754055..24501a3ba32 100644 --- a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/ExternalLinkBean.java +++ b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/ExternalLinkBean.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.io.Serializable; +import java.util.List; import javax.annotation.PostConstruct; import javax.faces.bean.ManagedBean; @@ -12,13 +13,18 @@ import org.primefaces.PrimeFaces; import org.primefaces.event.FileUploadEvent; +import com.axonivy.portal.service.DeepLTranslationService; import com.axonivy.portal.util.ExternalLinkUtils; import ch.ivy.addon.portalkit.bo.ExternalLinkProcessItem; import ch.ivy.addon.portalkit.configuration.ExternalLink; +import ch.ivy.addon.portalkit.dto.DisplayName; import ch.ivy.addon.portalkit.enums.Protocol; import ch.ivy.addon.portalkit.service.ExternalLinkService; +import ch.ivy.addon.portalkit.util.DisplayNameConvertor; +import ch.ivy.addon.portalkit.util.LanguageUtils; import ch.ivy.addon.portalkit.util.PermissionUtils; +import ch.ivy.addon.portalkit.util.UserUtils; import ch.ivyteam.ivy.environment.Ivy; import ch.ivyteam.ivy.security.IUser; import ch.ivyteam.util.Pair; @@ -48,10 +54,17 @@ public ExternalLink saveNewExternalLink() { externalLink.setSecurityMemberId(sessionUser.getSecurityMemberId()); String processLink = correctLink(externalLink.getLink()); externalLink.setLink(processLink); - + updateEmptyNameAndDescription(externalLink); + externaLinkService.save(externalLink); return externalLink; } + + private void updateEmptyNameAndDescription(ExternalLink externalLink) { + String userLanguguage = UserUtils.getUserLanguage(); + DisplayNameConvertor.updateEmptyValue(userLanguguage, externalLink.getNames()); + DisplayNameConvertor.updateEmptyValue(userLanguguage, externalLink.getDescriptions()); + } public boolean hasPublicLinkCreationPermission() { return PermissionUtils.checkPublicLinkCreationPermission(); @@ -97,4 +110,33 @@ public ExternalLink getExternalLink() { public void setExternalLink(ExternalLink externalLink) { this.externalLink = externalLink; } + + public void updateNameByLocale() { + String currentName = LanguageUtils.getLocalizedName(externalLink.getNames(), externalLink.getName()); + initAndSetValue(currentName, externalLink.getNames()); + } + + public void updateDescriptionByLocale() { + String currentDescription = LanguageUtils.getLocalizedName(externalLink.getDescriptions(), externalLink.getDescription()); + initAndSetValue(currentDescription, externalLink.getDescriptions()); + } + + private void initAndSetValue(String value, List values) { + DisplayNameConvertor.initMultipleLanguages(value, values); + DisplayNameConvertor.setValue(value, values); + } + + public boolean isRequiredField(DisplayName displayName) { + String currentLanguage = UserUtils.getUserLanguage(); + String displayLanguage = displayName.getLocale().getLanguage(); + return currentLanguage.equals(displayLanguage); + } + + public boolean isShowTranslation(DisplayName title) { + return DeepLTranslationService.getInstance().isShowTranslation(title.getLocale()); + } + + public boolean isFocus(DisplayName title) { + return !isShowTranslation(title) && title.getLocale().getLanguage().equals(UserUtils.getUserLanguage()); + } } diff --git a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/ProcessWidgetBean.java b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/ProcessWidgetBean.java index ac63fbc28ec..aa0d0534c41 100644 --- a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/ProcessWidgetBean.java +++ b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/ProcessWidgetBean.java @@ -28,6 +28,7 @@ import org.primefaces.event.UnselectEvent; import com.axonivy.portal.components.dto.SecurityMemberDTO; +import com.axonivy.portal.service.DeepLTranslationService; import com.axonivy.portal.util.ExternalLinkUtils; import ch.ivy.addon.portalkit.bo.ExpressProcess; @@ -37,6 +38,7 @@ import ch.ivy.addon.portalkit.bo.Process; import ch.ivy.addon.portalkit.configuration.ExternalLink; import ch.ivy.addon.portalkit.configuration.GlobalSetting; +import ch.ivy.addon.portalkit.dto.DisplayName; import ch.ivy.addon.portalkit.enums.GlobalVariable; import ch.ivy.addon.portalkit.enums.PortalPermission; import ch.ivy.addon.portalkit.enums.ProcessMode; @@ -48,8 +50,11 @@ import ch.ivy.addon.portalkit.service.ExpressProcessService; import ch.ivy.addon.portalkit.service.ExternalLinkService; import ch.ivy.addon.portalkit.service.GlobalSettingService; +import ch.ivy.addon.portalkit.util.DisplayNameConvertor; +import ch.ivy.addon.portalkit.util.LanguageUtils; import ch.ivy.addon.portalkit.util.PermissionUtils; import ch.ivy.addon.portalkit.util.SecurityMemberUtils; +import ch.ivy.addon.portalkit.util.UserUtils; import ch.ivyteam.ivy.environment.Ivy; import ch.ivyteam.ivy.security.exec.Sudo; import ch.ivyteam.ivy.workflow.IProcessStart; @@ -238,14 +243,9 @@ public void deleteProcessWorkflow() { } switch (deletedProcess.getType()) { - case EXPRESS_PROCESS: - deleteExpressWorkflow(); - break; - case EXTERNAL_LINK: - deleteExternalLink(); - break; - default: - break; + case EXPRESS_PROCESS -> deleteExpressWorkflow(); + case EXTERNAL_LINK -> deleteExternalLink(); + default -> {} } } @@ -262,16 +262,15 @@ public void updateProcessData() { } String oldProcessName = this.editedProcess.getName(); switch (this.editedProcess.getType()) { - case EXPRESS_PROCESS: + case EXPRESS_PROCESS -> { ExpressProcess expressProcess = updateExpressProcess(editedProcess.getId()); this.editedProcess = new PortalExpressProcess(expressProcess); - break; - case EXTERNAL_LINK: + } + case EXTERNAL_LINK -> { ExternalLink externalLink = updateExternalLink(editedProcess.getId()); this.editedProcess = new ExternalLinkProcessItem(externalLink); - break; - default: - break; + } + default -> {} } selectedIconProcess = null; updateStartProcessesList(oldProcessName); @@ -304,6 +303,9 @@ private ExternalLink updateExternalLink(String processId) { if (CollectionUtils.isNotEmpty(this.selectedPermissionsForSavingEditedExternalLink)) { externalLink.setPermissions(this.selectedPermissionsForSavingEditedExternalLink); } + externalLink.setNames(this.editedExternalLink.getNames()); + externalLink.setDescriptions(this.editedExternalLink.getDescriptions()); + updateEmptyNameAndDescription(externalLink); ExternalLinkBean externalLinkBean = ManagedBeans.get("externalLinkBean"); String correctLink = externalLinkBean.correctLink(this.editedExternalLink.getLink()); externalLink.setLink(correctLink); @@ -311,6 +313,12 @@ private ExternalLink updateExternalLink(String processId) { } return externalLink; } + + private void updateEmptyNameAndDescription(ExternalLink externalLink) { + String userLanguguage = UserUtils.getUserLanguage(); + DisplayNameConvertor.updateEmptyValue(userLanguguage, externalLink.getNames()); + DisplayNameConvertor.updateEmptyValue(userLanguguage, externalLink.getDescriptions()); + } public void handleExternalLinkImageUpload(FileUploadEvent event) { if(this.editedExternalLink == null) { @@ -446,6 +454,11 @@ private void updateSeletedEditExternalLink(Process editedProcess) { } else { this.editedExternalLink.setIsPublic(false); } + ExternalLink findById = ExternalLinkService.getInstance().findById(editedProcess.getId()); + if (findById != null) { + this.editedExternalLink.setNames(findById.getNames()); + this.editedExternalLink.setDescriptions(findById.getDescriptions()); + } } private List getSecurityMemberDTOsFromPermissions(List permissions) { @@ -559,4 +572,33 @@ public List getSelectedPermissionsWhenEditingExternalLink() { public void setSelectedPermissionsWhenEditingExternalLink(List selectedPermissionsWhenEditingExternalLink) { this.selectedPermissionsWhenEditingExternalLink = new ArrayList<>(selectedPermissionsWhenEditingExternalLink); } + + public void updateNameByLocale() { + String currentName = LanguageUtils.getLocalizedName(editedExternalLink.getNames(), editedExternalLink.getName()); + initAndSetValue(currentName, editedExternalLink.getNames()); + } + + public void updateDescriptionByLocale() { + String currentDescription = LanguageUtils.getLocalizedName(editedExternalLink.getDescriptions(), editedExternalLink.getDescription()); + initAndSetValue(currentDescription, editedExternalLink.getDescriptions()); + } + + private void initAndSetValue(String value, List values) { + DisplayNameConvertor.initMultipleLanguages(value, values); + DisplayNameConvertor.setValue(value, values); + } + + public boolean isRequiredField(DisplayName displayName) { + String currentLanguage = UserUtils.getUserLanguage(); + String displayLanguage = displayName.getLocale().getLanguage(); + return currentLanguage.equals(displayLanguage); + } + + public boolean isShowTranslation(DisplayName title) { + return DeepLTranslationService.getInstance().isShowTranslation(title.getLocale()); + } + + public boolean isFocus(DisplayName title) { + return !isShowTranslation(title) && title.getLocale().getLanguage().equals(UserUtils.getUserLanguage()); + } } diff --git a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/configuration/ExternalLink.java b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/configuration/ExternalLink.java index 6a75cdc72d4..26fcda59366 100644 --- a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/configuration/ExternalLink.java +++ b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/configuration/ExternalLink.java @@ -2,12 +2,18 @@ import java.util.ArrayList; import java.util.List; +import java.util.Locale; import org.apache.commons.collections4.CollectionUtils; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; +import ch.ivy.addon.portalkit.dto.DisplayName; +import ch.ivy.addon.portalkit.ivydata.bo.IvyLanguage; +import ch.ivy.addon.portalkit.ivydata.service.impl.LanguageService; +import ch.ivy.addon.portalkit.util.LanguageUtils; +import ch.ivy.addon.portalkit.util.LanguageUtils.NameResult; import ch.ivy.addon.portalkit.util.PermissionUtils; import ch.ivyteam.ivy.environment.Ivy; @@ -28,13 +34,39 @@ public class ExternalLink extends AbstractConfiguration { @JsonIgnore private List defaultPermissions = new ArrayList<>(); private String securityMemberId; + private List names; + private List descriptions; + + public ExternalLink() { + IvyLanguage ivyLanguage = LanguageService.newInstance().getIvyLanguageOfUser(); + if (CollectionUtils.isEmpty(names)) { + names = initDisplayName(ivyLanguage); + } + + if (CollectionUtils.isEmpty(descriptions)) { + descriptions = initDisplayName(ivyLanguage); + } + } + + private List initDisplayName(IvyLanguage ivyLanguage){ + List result = new ArrayList<>(); + for (String language : ivyLanguage.getSupportedLanguages()) { + DisplayName newItem = new DisplayName(); + newItem.setLocale(Locale.forLanguageTag(language)); + newItem.setValue(""); + result.add(newItem); + } + return result; + } public String getName() { - return name; + return LanguageUtils.getLocalizedName(names, name); } public void setName(String name) { - this.name = name; + NameResult nameResult = LanguageUtils.collectMultilingualNames(names, name); + this.names = nameResult.names(); + this.name = nameResult.name(); } public String getLink() { @@ -69,11 +101,13 @@ public boolean isAbleToEdit() { } public String getDescription() { - return description; + return LanguageUtils.getLocalizedName(descriptions, description); } public void setDescription(String description) { - this.description = description; + NameResult nameResult = LanguageUtils.collectMultilingualNames(descriptions, description); + this.descriptions = nameResult.names(); + this.description = nameResult.name(); } public String getImageLocation() { @@ -102,8 +136,8 @@ public void setImageType(String imageType) { @Override public String toString() { - return String.format("ExternalLink {creatorId=%s, name=%s, link=%s, isPublic=%s, rolePermission=[%s], icon=%s}", creatorId, name, link, - getIsPublic(), String.join(", ", permissions), icon); + return String.format("ExternalLink {creatorId=%s, name=%s, link=%s, isPublic=%s, rolePermission=[%s], icon=%s, securityMemberId=%s}", creatorId, name, link, + getIsPublic(), String.join(", ", permissions), icon, securityMemberId); } public String getIcon() { @@ -134,4 +168,20 @@ public String getSecurityMemberId() { public void setSecurityMemberId(String securityMemberId) { this.securityMemberId = securityMemberId; } + + public List getNames() { + return names; + } + + public void setNames(List names) { + this.names = names; + } + + public List getDescriptions() { + return descriptions; + } + + public void setDescriptions(List descriptions) { + this.descriptions = descriptions; + } } diff --git a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/util/DisplayNameConvertor.java b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/util/DisplayNameConvertor.java index cfdb3f88f3f..e9854751e95 100644 --- a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/util/DisplayNameConvertor.java +++ b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/util/DisplayNameConvertor.java @@ -2,12 +2,19 @@ import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; import org.primefaces.shaded.json.JSONException; import org.primefaces.shaded.json.JSONObject; +import ch.ivy.addon.portalkit.dto.DisplayName; +import ch.ivy.addon.portalkit.ivydata.service.impl.LanguageService; + public class DisplayNameConvertor { private Map displayNames = new HashMap<>(); @@ -40,4 +47,40 @@ public static DisplayNameConvertor parseJson(String jsonString) throws JSONExcep } return displayName; } + + public static void setValue(String currentValue, List values) { + String currentLanguage = UserUtils.getUserLanguage(); + Optional optional = values.stream() + .filter(lang -> currentLanguage.equals(lang.getLocale().getLanguage())).findFirst(); + if (optional.isPresent()) { + optional.get().setValue(currentValue); + } + } + + public static void initMultipleLanguages(String currentValue, List values) { + Map mapLanguage = values + .stream() + .collect(Collectors.toMap(o -> o.getLocale().toLanguageTag(), o -> o)); + List supportedLanguages = LanguageService.newInstance().getIvyLanguageOfUser().getSupportedLanguages(); + for (String language : supportedLanguages) { + DisplayName localeLanguage = mapLanguage.get(language); + if (localeLanguage == null) { + DisplayName displayName = new DisplayName(); + displayName.setLocale(Locale.forLanguageTag(language)); + displayName.setValue(currentValue); + values.add(displayName); + } else if (StringUtils.isBlank(localeLanguage.getValue())) { + localeLanguage.setValue(currentValue); + } + } + } + + public static void updateEmptyValue(String userLanguguage, List values) { + DisplayName defaultValue = values.stream().filter(item -> item.getLocale().getLanguage().equals(userLanguguage)).findFirst().orElse(null); + for (DisplayName name : values) { + if (StringUtils.isBlank(name.getValue()) && defaultValue != null) { + name.setValue(defaultValue.getValue()); + } + } + } } diff --git a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/ProcessWidget/ProcessWidgetDialogs.xhtml b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/ProcessWidget/ProcessWidgetDialogs.xhtml index 651a5c06ef7..1ff89015eaa 100644 --- a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/ProcessWidget/ProcessWidgetDialogs.xhtml +++ b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/ProcessWidget/ProcessWidgetDialogs.xhtml @@ -73,8 +73,16 @@ - +
+ + +
@@ -101,8 +109,15 @@ value="#{empty processWidgetBean.editedExternalLink.description ? ivy.cms.co('/ch.ivy.addon.portalkit.ui.jsf/common/notAvailable') : processWidgetBean.editedExternalLink.description}" />
- +
+ + +
@@ -245,10 +260,15 @@
-
+
+
@@ -267,9 +287,14 @@
-
+
+
@@ -375,5 +400,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AxonIvyPortal/portal/webContent/resources/css/module.css b/AxonIvyPortal/portal/webContent/resources/css/module.css index 1a0cdfdf163..1cec43534af 100644 --- a/AxonIvyPortal/portal/webContent/resources/css/module.css +++ b/AxonIvyPortal/portal/webContent/resources/css/module.css @@ -1384,6 +1384,18 @@ span.case-details-document-add-link, span.task-details-document-add-link { width: 100%; } +.external-link-description { + padding-right: 0.5em; +} +.external-link-input{ + padding-left: 1em; + padding-top: 1em; +} +.exter-link-description-label{ + padding-top: 1em; + float: left; +} + .process-start-list-item-action.is-external-link { right: 50px; flex: 1 0 auto; @@ -3247,7 +3259,7 @@ _:-ms-fullscreen, :root .new-absence-messages .ui-messages-error { } .edit-process-dialog { - max-width: 550px; + max-width: 650px; overflow: hidden !important; } @@ -3277,7 +3289,8 @@ _:-ms-fullscreen, :root .new-absence-messages .ui-messages-error { } .edit-process-form .edit-process-detail-2nd-cell .ui-inplace-content input, -.edit-process-form .edit-process-detail-2nd-cell .ui-inplace-content textarea { +.edit-process-form .edit-process-detail-2nd-cell .ui-inplace-content textarea, +.edit-process-form .edit-process-detail-2nd-cell .ui-inplace-content div { flex-grow: 1; } diff --git a/Documentation/portal-guide/source/portal-user-guide/full-process-list/index.rst b/Documentation/portal-guide/source/portal-user-guide/full-process-list/index.rst index 681b99803f3..3a7cb3f29ce 100644 --- a/Documentation/portal-guide/source/portal-user-guide/full-process-list/index.rst +++ b/Documentation/portal-guide/source/portal-user-guide/full-process-list/index.rst @@ -97,8 +97,14 @@ HowTo: Add an external link #. For the :guilabel:`Process name`, define the best name for it. +#. In :guilabel:`Process name`, click on the icon to the right (show icon graphic here) to define a name for the external link. You can define it in multiple languages by filling in the various language fields. If you don't fill in a language field, the default language name will be used. + #. For the :guilabel:`Start link`, add the URL of your external link. +#. For the :guilabel:`Description`, add the description of your external link. + +#. In :guilabel:`Description`, click on the icon to the right (show icon graphic here) to define a description for the external link. You can define it in multiple languages by filling in the various language fields. If you don't fill in a language field, the default language description will be used. + #. Grant permission :bdg-ref-warning:`🔑CreatePublicExternalLink ` to allow a user to create public links to external sites. This user can set this link as a public link by selecting :guilabel:`All users` . By default, :guilabel:`Visibility` field is not displayed and this new external link is a private link. From 015e2d81be92b8bb39253e697515717954c56048 Mon Sep 17 00:00:00 2001 From: Nguyen Hung Thinh <117440893+nhthinh-axonivy@users.noreply.github.com> Date: Fri, 23 Feb 2024 09:03:24 +0700 Subject: [PATCH 12/24] IVYPORTAL-16469 Make language selection easier to use with selenide (#512) * IVYPORTAL-16469 Make language selection easier to use with selenide _ Implement * IVYPORTAL-16469-Make-language-selection-easier-to-use-with-selenide --- .../addon/portalkit/ivydata/bo/IvyLanguage.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/ivydata/bo/IvyLanguage.java b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/ivydata/bo/IvyLanguage.java index 729c5be2392..c9201c89bb1 100644 --- a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/ivydata/bo/IvyLanguage.java +++ b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/ivydata/bo/IvyLanguage.java @@ -38,10 +38,20 @@ public List getFormattingLanguages() { private List getLanguages(List selectItems, List languages, Function localeLoader, boolean replaceParentheses){ selectItems.clear(); + Boolean isFirstElement = true; if (CollectionUtils.isNotEmpty(languages)) { for (String item : languages) { - SelectItem it = new SelectItem(item.toLowerCase(), toDisplayName(item.toLowerCase(), localeLoader, replaceParentheses)); - selectItems.add(it); + if (isFirstElement) { + SelectItem it = + new SelectItem(item.toLowerCase(), toDisplayName(item.toLowerCase(), localeLoader, replaceParentheses)); + selectItems.add(it); + isFirstElement = false; + } + else { + SelectItem it = + new SelectItem(item.toLowerCase(), toDisplayName(item.toLowerCase(), localeLoader, replaceParentheses)+ " (" + item + ")"); + selectItems.add(it); + } } } return selectItems; From db659bce5ac2b64fd76df9df2136c42c38bb0602 Mon Sep 17 00:00:00 2001 From: mnhnam-axonivy Date: Fri, 23 Feb 2024 12:06:29 +0700 Subject: [PATCH 13/24] IVYPORTAL-16478 Various or urgent requests in IVYPORTAL - S254 - Make some GUI tests stable --- .../screenshot/SettingScreenshotTest.java | 3 +- .../page/CaseEditWidgetNewDashBoardPage.java | 3 +- .../selenium/page/ExpressProcessPage.java | 3 +- .../page/TaskWidgetNewDashBoardPage.java | 11 +++-- .../portal/selenium/page/UserProfilePage.java | 48 +++++++++++++++---- .../selenium/test/NotificationsTest.java | 7 ++- .../DashboardTaskWidgetActionTest.java | 2 + .../test/task/CustomTaskDelegateTest.java | 13 +++-- .../setting/UserProfile/UserProfile.xhtml | 5 +- 9 files changed, 68 insertions(+), 27 deletions(-) diff --git a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/document/screenshot/SettingScreenshotTest.java b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/document/screenshot/SettingScreenshotTest.java index 5fcd703febb..5dc65eaaaad 100644 --- a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/document/screenshot/SettingScreenshotTest.java +++ b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/document/screenshot/SettingScreenshotTest.java @@ -96,11 +96,12 @@ public void screenshotMyProfile() throws IOException { NewDashboardPage homePage = new NewDashboardPage(); UserProfilePage userProfilePage = homePage.openMyProfilePage(); + userProfilePage.restoreDefaultNotificationSettings(); ScreenshotUtils.resizeBrowser(new Dimension(1400, 1400)); ScreenshotUtils.captureElementScreenshot(userProfilePage.getUserSettingCard(), ScreenshotUtils.MY_PROFILE_FOLDER + "my-profile"); ScreenshotUtils.resizeBrowser(new Dimension(1500, 900)); - userProfilePage.checkBoxTosubscribeChannel(); + userProfilePage.subscribeAllChannels(); ScreenshotUtils.executeDecorateJs("highlightNotificationChannelSettings()"); ScreenshotUtils .captureHalfRightPageScreenShot(ScreenshotUtils.MY_PROFILE_FOLDER + "notification-channels-settings"); diff --git a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/page/CaseEditWidgetNewDashBoardPage.java b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/page/CaseEditWidgetNewDashBoardPage.java index 2d3c431bcf9..1bb35207f2f 100644 --- a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/page/CaseEditWidgetNewDashBoardPage.java +++ b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/page/CaseEditWidgetNewDashBoardPage.java @@ -110,7 +110,8 @@ public void save() { $(caseEditWidgetId).shouldBe(appear, DEFAULT_TIMEOUT); $("[id='widget-configuration-form:new-widget-configuration-component:case-widget-preview:dashboard-cases_head']") .shouldBe(appear, DEFAULT_TIMEOUT); - $("button[id$='widget-configuration-save-button']").shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).click(); + $("button[id$='widget-configuration-save-button']").shouldBe(getClickableCondition(), DEFAULT_TIMEOUT); + clickByJavaScript($("button[id$='widget-configuration-save-button']")); $("button[id$='widget-configuration-save-button']").shouldBe(disappear, DEFAULT_TIMEOUT); $(caseEditWidgetId).shouldBe(disappear, DEFAULT_TIMEOUT); } diff --git a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/page/ExpressProcessPage.java b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/page/ExpressProcessPage.java index 38c78b44d3f..6dd4db18b9c 100644 --- a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/page/ExpressProcessPage.java +++ b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/page/ExpressProcessPage.java @@ -73,7 +73,8 @@ public void addNewTask(int currentTaskIndex) { } public void clickSave() { - $("[id='form:save']").shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).click(); + $("[id='form:save']").shouldBe(getClickableCondition(), DEFAULT_TIMEOUT); + clickByJavaScript($("[id='form:save']")); } public ExpressFormDefinitionPage goToFormDefinition() { diff --git a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/page/TaskWidgetNewDashBoardPage.java b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/page/TaskWidgetNewDashBoardPage.java index 7207c15c8d1..ef99948894c 100644 --- a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/page/TaskWidgetNewDashBoardPage.java +++ b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/page/TaskWidgetNewDashBoardPage.java @@ -103,7 +103,8 @@ public ElementsCollection countRelatedCases() { public void openFilterWidget() { waitForGlobalGrowlDisappear(); getTaskWidgetHeader().$(".widget__filter-sidebar-link").shouldBe(appear, DEFAULT_TIMEOUT) - .shouldBe(getClickableCondition()).click(); + .shouldBe(getClickableCondition(), DEFAULT_TIMEOUT); + clickByJavaScript(getTaskWidgetHeader().$(".widget__filter-sidebar-link")); $("[id$=':widget-saved-filters-items").shouldBe(appear, DEFAULT_TIMEOUT); } @@ -307,12 +308,12 @@ private SelenideElement getFilterCheckBox(String inputField) { } public void selectState(String state) { - getStateFilterCheckBox(state).shouldBe(getClickableCondition()).click(); - getCloseStateFilter().shouldBe(getClickableCondition()).click(); + getStateFilterCheckBox(state).shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).click(); + getCloseStateFilter().shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).click(); } public void filterTaskState() { - getFilterCheckBox(FILTER_TASK_STATE).shouldBe(getClickableCondition()).click(); + getFilterCheckBox(FILTER_TASK_STATE).shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).click(); } public ElementsCollection getActiveTaskActions(int taskIndex) { @@ -322,7 +323,7 @@ public ElementsCollection getActiveTaskActions(int taskIndex) { } public void clickOnTaskActionLink(int taskIndex) { - getColumnOfCaseHasActionIndex(taskIndex, "Actions").shouldBe(getClickableCondition()).click(); + getColumnOfCaseHasActionIndex(taskIndex, "Actions").shouldBe(getClickableCondition(), DEFAULT_TIMEOUT).click(); } public void reserveTask(int taskIndex) { diff --git a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/page/UserProfilePage.java b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/page/UserProfilePage.java index 04c51a542e3..40e904afc0e 100644 --- a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/page/UserProfilePage.java +++ b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/page/UserProfilePage.java @@ -2,6 +2,7 @@ import static com.codeborne.selenide.Condition.appear; import static com.codeborne.selenide.Selenide.$; +import static com.codeborne.selenide.Selenide.$$; import org.openqa.selenium.By; @@ -18,7 +19,7 @@ public class UserProfilePage extends TemplatePage { private static final String CASE_SORT_FIELD_SELECTION_LABEL = "my-profile-form:case-sort-field-selection_label"; private static final String CASE_SORT_FIELD_SELECTION = "my-profile-form:case-sort-field-selection"; public static final String TASK_SORT_DIRECTION_SELECTION_ITEMS = "my-profile-form:task-sort-direction-selection_items"; - private static String NOTI_CHANNELS_CHECKBOX_SELECTOR = "div[id$=':notification-Channels-Table'] div.ui-chkbox-box"; + private static String NOTI_CHANNELS_CHECKBOX_SELECTOR = "div[id$=':notification-Channels-Table'] div[id$=':subscriptionCheckbox']"; private static final String TASK_SORT_FIELD_SELECTION = "my-profile-form:task-sort-field-selection"; private static final String TASK_SORT_FIELD_SELECTION_LABEL = "my-profile-form:task-sort-field-selection_label"; public static final String TASK_SORT_FIELD_SELECTION_ITEMS = "my-profile-form:task-sort-field-selection_items"; @@ -89,15 +90,42 @@ public SelenideElement getUserSettingCard() { return $("[id='my-profile-container']"); } - public void checkBoxTosubscribeChannel() { - checkIntoCheckbox(NOTI_CHANNELS_CHECKBOX_SELECTOR); - } - - private void checkIntoCheckbox(String cssSelector) { - SelenideElement inputSwitch = $(cssSelector).shouldBe(appear, DEFAULT_TIMEOUT); - if (inputSwitch.getAttribute("class").contains("ui-chkbox-box")) { - $(inputSwitch).click(); - } + public void restoreDefaultNotificationSettings() { + $("[id='my-profile-form:restore-to-default-button']").shouldBe(getClickableCondition(), DEFAULT_TIMEOUT); + clickByJavaScript($("[id='my-profile-form:restore-to-default-button']")); + waitPageLoaded(); + } + + public void unsubscribeAllChannels() { + restoreDefaultNotificationSettings(); + $$(NOTI_CHANNELS_CHECKBOX_SELECTOR).asDynamicIterable().forEach(checkbox -> { + boolean isUnsubcribed = false; + while (!isUnsubcribed) { + SelenideElement checkboxElem = checkbox.$(".ui-chkbox-box"); + checkboxElem.shouldBe(getClickableCondition(), DEFAULT_TIMEOUT); + isUnsubcribed = checkboxElem.$("span.ui-chkbox-icon").has(Condition.cssClass("ui-icon-closethick")); + if (!isUnsubcribed) { + checkboxElem.click(); + waitForPageLoad(); + } + } + }); + } + + public void subscribeAllChannels() { + restoreDefaultNotificationSettings(); + $$(NOTI_CHANNELS_CHECKBOX_SELECTOR).asDynamicIterable().forEach(checkbox -> { + boolean isSubcribed = false; + while (!isSubcribed) { + SelenideElement checkboxElem = checkbox.$(".ui-chkbox-box"); + checkboxElem.shouldBe(getClickableCondition(), DEFAULT_TIMEOUT); + isSubcribed = checkboxElem.$("span.ui-chkbox-icon").has(Condition.cssClass("ui-icon-check")); + if (!isSubcribed) { + checkboxElem.click(); + waitForPageLoad(); + } + } + }); } public void selectTaskSortField(String selectValue) { diff --git a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/test/NotificationsTest.java b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/test/NotificationsTest.java index f646a60aab3..0b3770e04af 100644 --- a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/test/NotificationsTest.java +++ b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/test/NotificationsTest.java @@ -65,15 +65,14 @@ public void testDisableNotificationIcon() { login(TestAccount.ADMIN_USER); NewDashboardPage homepage = new NewDashboardPage(); UserProfilePage userProfilePage = homepage.openMyProfilePage(); - userProfilePage.checkBoxTosubscribeChannel(); - userProfilePage.checkBoxTosubscribeChannel(); + userProfilePage.unsubscribeAllChannels(); userProfilePage.save(); homepage.hideNotificationsIcon(); homepage.openMyProfilePage(); - userProfilePage.checkBoxTosubscribeChannel(); + userProfilePage = new UserProfilePage(); + userProfilePage.subscribeAllChannels(); userProfilePage.save(); homepage.showNotificationsIcon(); - } } diff --git a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/test/dashboard/DashboardTaskWidgetActionTest.java b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/test/dashboard/DashboardTaskWidgetActionTest.java index ad910f218a8..98ac254bb4e 100644 --- a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/test/dashboard/DashboardTaskWidgetActionTest.java +++ b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/test/dashboard/DashboardTaskWidgetActionTest.java @@ -63,6 +63,8 @@ public void setup() { public void testVisibilityTaskActionForNormalUser() { login(TestAccount.DEMO_USER); createTasksForTesting(); + newDashboardPage = new NewDashboardPage(); + newDashboardPage.waitForTaskListDisplay(); // TaskState : Ready for Join <=> TaskBusinessState : Done assertTaskActionsByTaskState("Done", Arrays.asList(DETAILS, PROCESS_VIEWER)); // TaskState : Suspended <=> TaskBusinessState : Open diff --git a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/test/task/CustomTaskDelegateTest.java b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/test/task/CustomTaskDelegateTest.java index f561a9b6921..246a50cc292 100644 --- a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/test/task/CustomTaskDelegateTest.java +++ b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/test/task/CustomTaskDelegateTest.java @@ -7,6 +7,7 @@ import com.axonivy.portal.selenium.common.BaseTest; import com.axonivy.portal.selenium.common.TestAccount; import com.axonivy.portal.selenium.page.MainMenuPage; +import com.axonivy.portal.selenium.page.NewDashboardPage; import com.axonivy.portal.selenium.page.TaskWidgetPage; @IvyWebTest @@ -25,7 +26,7 @@ public void setup() { @Test public void testCustomTaskDelegateOnlyToGroup() { login(TestAccount.HR_ROLE_USER); - redirectToNewDashBoard(); + openDashboard(); MainMenuPage menu = new MainMenuPage(); menu.openTaskList(); TaskWidgetPage taskWidgetPage = new TaskWidgetPage(); @@ -44,7 +45,7 @@ public void testCustomTaskDelegateOnlyToGroup() { @Test public void testCustomTaskDelegateOnlyToUser() { login(TestAccount.HR_ROLE_USER); - redirectToNewDashBoard(); + openDashboard(); MainMenuPage menu = new MainMenuPage(); menu.openTaskList(); TaskWidgetPage taskWidgetPage = new TaskWidgetPage(); @@ -63,7 +64,7 @@ public void testCustomTaskDelegateOnlyToUser() { @Test public void testCustomTaskDelegateNoDelegateOption() { login(TestAccount.GUEST_USER); - redirectToNewDashBoard(); + openDashboard(); MainMenuPage menu = new MainMenuPage(); menu.openTaskList(); TaskWidgetPage taskWidgetPage = new TaskWidgetPage(); @@ -74,4 +75,10 @@ public void testCustomTaskDelegateNoDelegateOption() { assertFalse(taskWidgetPage.isDelegateTypeAvailable()); assertEquals("This task cannot be delegated to any other user or group.", taskWidgetPage.getCannotDelegateText()); } + + private void openDashboard() { + redirectToNewDashBoard(); + NewDashboardPage dashboardPage = new NewDashboardPage(); + dashboardPage.waitForCaseWidgetLoaded(); + } } diff --git a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portal/setting/UserProfile/UserProfile.xhtml b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portal/setting/UserProfile/UserProfile.xhtml index f4a6e4fbf72..7c3695e8735 100644 --- a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portal/setting/UserProfile/UserProfile.xhtml +++ b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portal/setting/UserProfile/UserProfile.xhtml @@ -243,7 +243,7 @@ - @@ -254,7 +254,8 @@
From c198785b0c2c910373793867ff1d9a76990a42e2 Mon Sep 17 00:00:00 2001 From: "Nam.Chu" Date: Fri, 23 Feb 2024 17:37:54 +0700 Subject: [PATCH 14/24] IVYPORTAL-14904 External Link Widget MultiLanguage Support - Fix exception when turn on DeepL --- .../portalkit/bean/ExternalLinkBean.java | 56 ++++++++++++ .../portalkit/bean/ProcessWidgetBean.java | 57 ++++++++++++ .../MultiLanguageConfiguration.xhtml | 18 +++- .../ProcessWidget/ProcessWidgetDialogs.xhtml | 91 ++++--------------- .../webContent/resources/css/module.css | 7 +- 5 files changed, 143 insertions(+), 86 deletions(-) diff --git a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/ExternalLinkBean.java b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/ExternalLinkBean.java index 24501a3ba32..056c8f51b1c 100644 --- a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/ExternalLinkBean.java +++ b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/ExternalLinkBean.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.io.Serializable; import java.util.List; +import java.util.Optional; import javax.annotation.PostConstruct; import javax.faces.bean.ManagedBean; @@ -10,6 +11,7 @@ import javax.faces.context.FacesContext; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.util.Strings; import org.primefaces.PrimeFaces; import org.primefaces.event.FileUploadEvent; @@ -36,6 +38,8 @@ public class ExternalLinkBean implements Serializable { private static final long serialVersionUID = 4772777911430826945L; private ExternalLink externalLink; private ExternalLinkService externaLinkService; + private String warningText; + private String translatedText; @PostConstruct public void init() { @@ -139,4 +143,56 @@ public boolean isShowTranslation(DisplayName title) { public boolean isFocus(DisplayName title) { return !isShowTranslation(title) && title.getLocale().getLanguage().equals(UserUtils.getUserLanguage()); } + + public String getWarningText() { + return warningText; + } + + public void setWarningText(String warningText) { + this.warningText = warningText; + } + + public String getTranslatedText() { + return translatedText; + } + + public void setTranslatedText(String translatedText) { + this.translatedText = translatedText; + } + + public void translate(DisplayName title) { + translateValues(title, externalLink.getNames()); + } + + public void translateTextArea(DisplayName title) { + translateValues(title, externalLink.getDescriptions()); + } + + private void translateValues(DisplayName title, List languages) { + translatedText = Strings.EMPTY; + warningText = Strings.EMPTY; + + String currentLanguage = UserUtils.getUserLanguage(); + if (!title.getLocale().getLanguage().equals(currentLanguage)) { + Optional optional = languages.stream() + .filter(lang -> currentLanguage.equals(lang.getLocale().getLanguage())).findFirst(); + if (optional.isPresent()) { + try { + translatedText = DeepLTranslationService.getInstance().translate(optional.get().getValue(), + optional.get().getLocale(), title.getLocale()); + } catch (Exception e) { + warningText = Ivy.cms() + .co("/ch.ivy.addon.portalkit.ui.jsf/dashboard/DashboardConfiguration/SomeThingWentWrong"); + Ivy.log().error("DeepL Translation Service error: ", e.getMessage()); + } + } + } + } + + public void applyTranslatedText(DisplayName displayName) { + if (StringUtils.isNotBlank(translatedText)) { + displayName.setValue(translatedText); + translatedText = ""; + } + } } diff --git a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/ProcessWidgetBean.java b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/ProcessWidgetBean.java index aa0d0534c41..bddac584cc3 100644 --- a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/ProcessWidgetBean.java +++ b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/ProcessWidgetBean.java @@ -14,6 +14,7 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -23,6 +24,7 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.util.Strings; import org.primefaces.event.FileUploadEvent; import org.primefaces.event.SelectEvent; import org.primefaces.event.UnselectEvent; @@ -82,6 +84,9 @@ public class ProcessWidgetBean extends AbstractProcessBean implements Serializab private List selectedPermissionsForSavingEditedExternalLink; private IProcessStart createExpressWorkflowProcessStart; private Map> processesByAlphabet; + + private String warningText; + private String translatedText; public void initConfiguration() { initProcessViewMode(); @@ -601,4 +606,56 @@ public boolean isShowTranslation(DisplayName title) { public boolean isFocus(DisplayName title) { return !isShowTranslation(title) && title.getLocale().getLanguage().equals(UserUtils.getUserLanguage()); } + + public String getWarningText() { + return warningText; + } + + public String getTranslatedText() { + return translatedText; + } + + public void setWarningText(String warningText) { + this.warningText = warningText; + } + + public void setTranslatedText(String translatedText) { + this.translatedText = translatedText; + } + + public void translate(DisplayName title) { + translateValues(title, editedExternalLink.getNames()); + } + + public void translateTextArea(DisplayName title) { + translateValues(title, editedExternalLink.getDescriptions()); + } + + private void translateValues(DisplayName title, List languages) { + translatedText = Strings.EMPTY; + warningText = Strings.EMPTY; + + String currentLanguage = UserUtils.getUserLanguage(); + if (!title.getLocale().getLanguage().equals(currentLanguage)) { + Optional optional = languages.stream() + .filter(lang -> currentLanguage.equals(lang.getLocale().getLanguage())).findFirst(); + if (optional.isPresent()) { + try { + translatedText = DeepLTranslationService.getInstance().translate(optional.get().getValue(), + optional.get().getLocale(), title.getLocale()); + } catch (Exception e) { + warningText = Ivy.cms() + .co("/ch.ivy.addon.portalkit.ui.jsf/dashboard/DashboardConfiguration/SomeThingWentWrong"); + Ivy.log().error("DeepL Translation Service error: ", e.getMessage()); + } + } + } + } + + public void applyTranslatedText(DisplayName displayName) { + if (StringUtils.isNotBlank(translatedText)) { + displayName.setValue(translatedText); + translatedText = ""; + } + } } diff --git a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portal/generic/dashboard/component/MultiLanguageConfiguration/MultiLanguageConfiguration.xhtml b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portal/generic/dashboard/component/MultiLanguageConfiguration/MultiLanguageConfiguration.xhtml index 2f2435368a3..9223b29a4dd 100644 --- a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portal/generic/dashboard/component/MultiLanguageConfiguration/MultiLanguageConfiguration.xhtml +++ b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portal/generic/dashboard/component/MultiLanguageConfiguration/MultiLanguageConfiguration.xhtml @@ -18,6 +18,7 @@ + @@ -31,22 +32,29 @@ - + - +
- - + + + + diff --git a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/ProcessWidget/ProcessWidgetDialogs.xhtml b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/ProcessWidget/ProcessWidgetDialogs.xhtml index 1ff89015eaa..95f3021138c 100644 --- a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/ProcessWidget/ProcessWidgetDialogs.xhtml +++ b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/ProcessWidget/ProcessWidgetDialogs.xhtml @@ -115,8 +115,8 @@ + update="#{cc.clientId}:edit-description-language-config:multiple-languages-dialog" + oncomplete="PF('multiple-languages-dialog').show();" />
@@ -293,8 +293,8 @@ + update="#{cc.clientId}:description-language-config:multiple-languages-dialog" + oncomplete="PF('multiple-languages-dialog').show();" />
@@ -413,76 +413,17 @@ componentToUpdateOnCreate="#{cc.clientId}:edit-process-form:external-link-name" componentToUpdateOnModify="#{cc.clientId}:edit-process-form:external-link-name"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + \ No newline at end of file diff --git a/AxonIvyPortal/portal/webContent/resources/css/module.css b/AxonIvyPortal/portal/webContent/resources/css/module.css index f1fa8dc4bd7..b75134c7b2b 100644 --- a/AxonIvyPortal/portal/webContent/resources/css/module.css +++ b/AxonIvyPortal/portal/webContent/resources/css/module.css @@ -1387,12 +1387,7 @@ span.case-details-document-add-link, span.task-details-document-add-link { .external-link-description { padding-right: 0.5em; } -.external-link-input{ - padding-left: 1em; - padding-top: 1em; -} -.exter-link-description-label{ - padding-top: 1em; +.multi-language-textarea-label { float: left; } From bdc8377ad0b8f221d314bb94ff94565b43fa9ce0 Mon Sep 17 00:00:00 2001 From: Nam Mai <117440763+mnhnam-axonivy@users.noreply.github.com> Date: Thu, 22 Feb 2024 15:38:19 +0700 Subject: [PATCH 15/24] Merge pull request #505 from axonivy-market/bug/IVYPORTAL-16560-Cannot-show-process-in-custom-widget-10 IVYPORTAL-16560: Cannot show process in custom widget # Conflicts: # AxonIvyPortal/portal-components/src/com/axonivy/portal/components/service/impl/ProcessService.java --- .../service/impl/ProcessService.java | 39 ++----------------- 1 file changed, 3 insertions(+), 36 deletions(-) diff --git a/AxonIvyPortal/portal-components/src/com/axonivy/portal/components/service/impl/ProcessService.java b/AxonIvyPortal/portal-components/src/com/axonivy/portal/components/service/impl/ProcessService.java index 8a542a0ee8d..0212d0b3a56 100644 --- a/AxonIvyPortal/portal-components/src/com/axonivy/portal/components/service/impl/ProcessService.java +++ b/AxonIvyPortal/portal-components/src/com/axonivy/portal/components/service/impl/ProcessService.java @@ -1,23 +1,18 @@ package com.axonivy.portal.components.service.impl; import static com.axonivy.portal.components.constant.CustomFields.IS_DASHBOARD_PROCESS; -import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; import java.util.ArrayList; import java.util.List; -import java.util.UUID; import java.util.function.Predicate; import java.util.stream.Collectors; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; -import com.axonivy.portal.components.enums.SessionAttribute; -import com.axonivy.portal.components.util.UserUtils; import ch.ivyteam.ivy.environment.Ivy; import ch.ivyteam.ivy.security.exec.Sudo; -import ch.ivyteam.ivy.server.restricted.EngineMode; import ch.ivyteam.ivy.workflow.IWorkflowSession; import ch.ivyteam.ivy.workflow.start.IWebStartable; @@ -27,36 +22,23 @@ public class ProcessService { private static final String PORTAL_START_REQUEST_PATH = "/DefaultApplicationHomePage.ivp"; private static ProcessService instance; private static List processes; - private static List customDashboardProcesses; - private static String sessionUserId; - private static String userLanguage; public ProcessService() { } public static ProcessService getInstance() { if (instance == null) { instance = new ProcessService(); processes = new ArrayList(); - customDashboardProcesses = new ArrayList<>(); } return instance; } - public List findProcesses() { - if (isInSession() && isNotEmpty(processes)) { - return processes; - } - updateUserSessionAtributes(); - processes = new ArrayList<>(); + @Override + public IvyProcessResultDTO findProcesses() { return Sudo.get(() -> { return findStartablesWithoutPortalHomeAndMSTeamsProcess(Ivy.session()); }); } - private void updateUserSessionAtributes() { - sessionUserId = Ivy.session().getAttribute(SessionAttribute.SESSION_IDENTIFIER.toString()).toString(); - userLanguage = UserUtils.getUserLanguage(); - } - private List findStartablesWithoutPortalHomeAndMSTeamsProcess(IWorkflowSession session) { return session.getStartables().stream().filter(process -> isNotPortalHomeAndMSTeamsProcess(process)) .collect(Collectors.toList()); @@ -67,24 +49,9 @@ private boolean isNotPortalHomeAndMSTeamsProcess(IWebStartable process) { return !StringUtils.endsWithAny(relativeEncoded, PORTAL_START_REQUEST_PATH, PORTAL_IN_TEAMS_REQUEST_PATH); } - private boolean isInSession() { - String currentUserLanguage = UserUtils.getUserLanguage(); - String sessionIdAttribute = SessionAttribute.SESSION_IDENTIFIER.toString(); - if (Ivy.session().getAttribute(sessionIdAttribute) == null) { - Ivy.session().setAttribute(sessionIdAttribute, UUID.randomUUID().toString()); - } - return EngineMode.isNot(EngineMode.DESIGNER_EMBEDDED) && currentUserLanguage.equals(userLanguage) - && Ivy.session().getAttribute(sessionIdAttribute).toString().equals(sessionUserId); - } - public List findCustomDashboardProcesses() { - if (isInSession() && isNotEmpty(customDashboardProcesses)) { - return customDashboardProcesses; - } - updateUserSessionAtributes(); - customDashboardProcesses = new ArrayList<>( + return new ArrayList<>( Ivy.session().getAllStartables().filter(filterByCustomDashboardProcess()).collect(Collectors.toList())); - return customDashboardProcesses; } private Predicate filterByCustomDashboardProcess() { From 0d4c9d454ea21003875a0b6f58f3fe7146cb2e2f Mon Sep 17 00:00:00 2001 From: mnhnam-axonivy Date: Mon, 26 Feb 2024 10:11:55 +0700 Subject: [PATCH 16/24] IVYPORTAL-16560: Cannot show process in custom widget - Fixed wrong code --- .../portal/components/service/impl/ProcessService.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/AxonIvyPortal/portal-components/src/com/axonivy/portal/components/service/impl/ProcessService.java b/AxonIvyPortal/portal-components/src/com/axonivy/portal/components/service/impl/ProcessService.java index 0212d0b3a56..e7bceec617b 100644 --- a/AxonIvyPortal/portal-components/src/com/axonivy/portal/components/service/impl/ProcessService.java +++ b/AxonIvyPortal/portal-components/src/com/axonivy/portal/components/service/impl/ProcessService.java @@ -21,19 +21,17 @@ public class ProcessService { private static final String PORTAL_IN_TEAMS_REQUEST_PATH = "InTeams.ivp"; private static final String PORTAL_START_REQUEST_PATH = "/DefaultApplicationHomePage.ivp"; private static ProcessService instance; - private static List processes; + public ProcessService() { } public static ProcessService getInstance() { if (instance == null) { instance = new ProcessService(); - processes = new ArrayList(); } return instance; } - @Override - public IvyProcessResultDTO findProcesses() { + public List findProcesses() { return Sudo.get(() -> { return findStartablesWithoutPortalHomeAndMSTeamsProcess(Ivy.session()); }); @@ -57,5 +55,4 @@ public List findCustomDashboardProcesses() { private Predicate filterByCustomDashboardProcess() { return start -> BooleanUtils.toBoolean(start.customFields().value(IS_DASHBOARD_PROCESS)); } - } From c4f7dccec95445bc6a98497f049736fc5231c993 Mon Sep 17 00:00:00 2001 From: Phan Van Thai Date: Mon, 26 Feb 2024 11:54:53 +0700 Subject: [PATCH 17/24] IVYPORTAL-16344: Updated task banner when destroy task --- .../src/ch/ivy/addon/portalkit/bean/TaskDetailsBean.java | 2 +- .../portalkit/component/TaskItemDetails/TaskItemDetails.xhtml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/TaskDetailsBean.java b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/TaskDetailsBean.java index 6bfcddc4b8c..a295d525f3f 100644 --- a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/TaskDetailsBean.java +++ b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/TaskDetailsBean.java @@ -169,7 +169,7 @@ private boolean canResetTask() { public String getInfoBannerSeverity() { ITask selectedTask = getSelectedTaskFromData(); - boolean validState = selectedTask.getState() == TaskState.RESUMED || selectedTask.getState() == TaskState.CREATED; + boolean validState = selectedTask.getState() == TaskState.RESUMED || selectedTask.getState() == TaskState.CREATED || selectedTask.getState() == TaskState.PARKED; boolean notCurrentSession = selectedTask.getWorkerSession() != ISession.current(); return validState && notCurrentSession && isActivator() && currentIsWorkerUser() ? "warn" : "info"; } diff --git a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/TaskItemDetails/TaskItemDetails.xhtml b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/TaskItemDetails/TaskItemDetails.xhtml index 7baea386370..20f9665c087 100644 --- a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/TaskItemDetails/TaskItemDetails.xhtml +++ b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portalkit/component/TaskItemDetails/TaskItemDetails.xhtml @@ -17,7 +17,7 @@ - + @@ -112,7 +112,7 @@ actionLinkId="task-detail-more-step" panelWidgetVar="task-side-steps-panel-#{rowIndex}" isAdhocExcluded="false" renderedAdditionalAction="#{taskActionBean.showAdditionalOptions(task)}" task="#{task}" isDisabled="#{taskActionBean.noActionAvailable(task) or cc.attrs.readOnly}" - componentToUpdate="#{informationWidgetComponent} #{historyWidgetComponent} #{cc.clientId}:task-status-banner" onDestroyComplete="PF('destroy-task-dialog').show()" + componentToUpdate="#{informationWidgetComponent} #{historyWidgetComponent}" onDestroyComplete="PF('destroy-task-dialog').show()" onEscalationComplete="PF('escalation-task-dialog').show()" showInTaskList="false" onDelegateComplete="initDataToDelegate()" onWorkflowEventComplete="initWorkflowEventData()" /> From 3a3db927588402e03a286887625dfd50fc4d6843 Mon Sep 17 00:00:00 2001 From: mnhnam-axonivy Date: Mon, 26 Feb 2024 13:08:36 +0700 Subject: [PATCH 18/24] IVYPORTAL-16478: Various or urgent requests in IVYPORTAL - S254 - Fix DashboardProcessWidgetTest.testSortProcessCompactProcessModeCustomSorting --- .../page/ProcessEditWidgetNewDashBoardPage.java | 10 ++++++---- .../test/dashboard/DashboardProcessWidgetTest.java | 4 +++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/page/ProcessEditWidgetNewDashBoardPage.java b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/page/ProcessEditWidgetNewDashBoardPage.java index 2a70da763f6..af42cbadb1e 100644 --- a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/page/ProcessEditWidgetNewDashBoardPage.java +++ b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/page/ProcessEditWidgetNewDashBoardPage.java @@ -430,12 +430,14 @@ public void dragAndDropProcess(int fromIndex, int toIndex) { processList.shouldBe(Condition.appear, DEFAULT_TIMEOUT); List findAll = processList.findAll("li.ui-orderlist-item"); if (findAll.size() > toIndex) { - SelenideElement fromElement = findAll.get(fromIndex).shouldBe(clickable(), DEFAULT_TIMEOUT); - SelenideElement toElement = findAll.get(toIndex).shouldBe(clickable(), DEFAULT_TIMEOUT); + SelenideElement fromElement = findAll.get(fromIndex).$(".process-start-list-item").shouldBe(clickable(), + DEFAULT_TIMEOUT); + SelenideElement toElement = findAll.get(toIndex).$(".process-start-list-item").shouldBe(clickable(), + DEFAULT_TIMEOUT); Actions builder = new Actions(WebDriverRunner.getWebDriver()); - Action dragAndDrop = builder.clickAndHold(fromElement).pause(500).moveToElement(toElement).pause(500) - .release(toElement).build(); + Action dragAndDrop = builder.clickAndHold(fromElement).pause(500).moveToElement(toElement).pause(1000) + .release(toElement).pause(1000).build(); dragAndDrop.perform(); } } diff --git a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/test/dashboard/DashboardProcessWidgetTest.java b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/test/dashboard/DashboardProcessWidgetTest.java index 171c25a75fa..aff27ad07f2 100644 --- a/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/test/dashboard/DashboardProcessWidgetTest.java +++ b/AxonIvyPortal/portal-selenium-test/src_test/com/axonivy/portal/selenium/test/dashboard/DashboardProcessWidgetTest.java @@ -433,7 +433,9 @@ public void testSortProcessCompactProcessModeCustomSorting() { editProcessWidgetConfiguration.getPreviewProcessElement(0).shouldBe(Condition.appear, DEFAULT_TIMEOUT) .shouldHave(Condition.exactTextCaseSensitive(CLEAN_ABSENCES)); int fromIndex = 0; - int toIndex = 4; + int toIndex = 6; + editProcessWidgetConfiguration.dragAndDropProcess(fromIndex, toIndex); + editProcessWidgetConfiguration.dragAndDropProcess(fromIndex, toIndex); editProcessWidgetConfiguration.dragAndDropProcess(fromIndex, toIndex); editProcessWidgetConfiguration.save(); editProcessWidgetConfiguration = newDashboardPage.editProcessWidgetConfiguration(); From 4b119380a9db2a054af201cbef37a9161719f4df Mon Sep 17 00:00:00 2001 From: "Nam.Chu" Date: Mon, 26 Feb 2024 14:21:56 +0700 Subject: [PATCH 19/24] IVYPORTAL-14904 External Link Widget MultiLanguage Support - Small fix: set cms for label --- .../MultiLanguageConfiguration/MultiLanguageConfiguration.xhtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portal/generic/dashboard/component/MultiLanguageConfiguration/MultiLanguageConfiguration.xhtml b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portal/generic/dashboard/component/MultiLanguageConfiguration/MultiLanguageConfiguration.xhtml index 9223b29a4dd..df06a20cf4d 100644 --- a/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portal/generic/dashboard/component/MultiLanguageConfiguration/MultiLanguageConfiguration.xhtml +++ b/AxonIvyPortal/portal/src_hd/ch/ivy/addon/portal/generic/dashboard/component/MultiLanguageConfiguration/MultiLanguageConfiguration.xhtml @@ -35,7 +35,7 @@ - From 09e763d7d7ed2a57584d7e6bd46995f1f51a7b88 Mon Sep 17 00:00:00 2001 From: mnhnam-axonivy Date: Mon, 26 Feb 2024 16:23:39 +0700 Subject: [PATCH 20/24] IVYPORTAL-16478 Various or urgent requests in IVYPORTAL - S254 > IVYPORTAL-16578 French Translations Business Details - Updated CMS --- AxonIvyPortal/portal/cms/cms_fr.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AxonIvyPortal/portal/cms/cms_fr.yaml b/AxonIvyPortal/portal/cms/cms_fr.yaml index 3ca06c4f1e6..7c80dd15be6 100644 --- a/AxonIvyPortal/portal/cms/cms_fr.yaml +++ b/AxonIvyPortal/portal/cms/cms_fr.yaml @@ -1558,7 +1558,7 @@ Dialogs: CaseOwner: Responsable de processus Finished: Terminé GlobalSearchText: Les cas contiennent le mot-clé "{0}" dans Id du dossier{1}. - ShowBusinessDetails: Détails de l'entreprise + ShowBusinessDetails: Détails de la demande caseNameNotAvailable: '[Nom de cas pas disponible]' defaultEmptyMessage: Il n'y a pas de dossiers IconSelection: From 1a9fdbd123fb2d9824970f94bb014acf30da2a1e Mon Sep 17 00:00:00 2001 From: Phan Van Thai Date: Tue, 27 Feb 2024 17:45:15 +0700 Subject: [PATCH 21/24] IVYPORTAL-16344: Handle feedback - change message --- AxonIvyPortal/portal/cms/cms_de.yaml | 1 - AxonIvyPortal/portal/cms/cms_en.yaml | 1 - AxonIvyPortal/portal/cms/cms_es.yaml | 1 - AxonIvyPortal/portal/cms/cms_fr.yaml | 1 - .../addon/portalkit/bean/TaskDetailsBean.java | 19 +------------------ 5 files changed, 1 insertion(+), 22 deletions(-) diff --git a/AxonIvyPortal/portal/cms/cms_de.yaml b/AxonIvyPortal/portal/cms/cms_de.yaml index 58f0e6dac80..72b6b843978 100644 --- a/AxonIvyPortal/portal/cms/cms_de.yaml +++ b/AxonIvyPortal/portal/cms/cms_de.yaml @@ -1569,7 +1569,6 @@ Dialogs: IconSelection: IconLinkTooltip: Zum Ändern des Symbols anklicken TaskItemDetails: - CannotResetTaskWarning: Jemand arbeitet derzeit an dieser Aufgabe. CannotWorkOnDestroyedTaskWarning: Sie können nicht an der Aufgabe arbeiten, weil sie zerstört wurde. CannotWorkOnTaskWarning: Sie können die Aufgabe nicht bearbeiten, weil der Benutzer {0} gerade daran arbeitet. InvalidStateInfo: Ungültiger Zustand. diff --git a/AxonIvyPortal/portal/cms/cms_en.yaml b/AxonIvyPortal/portal/cms/cms_en.yaml index dec12dd8928..8a05f123475 100644 --- a/AxonIvyPortal/portal/cms/cms_en.yaml +++ b/AxonIvyPortal/portal/cms/cms_en.yaml @@ -1569,7 +1569,6 @@ Dialogs: IconSelection: IconLinkTooltip: Click to change icon TaskItemDetails: - CannotResetTaskWarning: Someone is currently working on this task. CannotWorkOnDestroyedTaskWarning: You cannot work on the task because it was destroyed. CannotWorkOnTaskWarning: You cannot work on the task because user {0} is currently working on it. InvalidStateInfo: Invalid state. diff --git a/AxonIvyPortal/portal/cms/cms_es.yaml b/AxonIvyPortal/portal/cms/cms_es.yaml index 4c3203a7828..d71b281239b 100644 --- a/AxonIvyPortal/portal/cms/cms_es.yaml +++ b/AxonIvyPortal/portal/cms/cms_es.yaml @@ -1571,7 +1571,6 @@ Dialogs: IconSelection: IconLinkTooltip: Haga clic para cambiar el icono TaskItemDetails: - CannotResetTaskWarning: Alguien está trabajando actualmente en esta tarea. CannotWorkOnDestroyedTaskWarning: No puedes trabajar en la tarea porque ha sido destruida. CannotWorkOnTaskWarning: No puede procesar la tarea porque el usuario {0} está trabajando actualmente en ella. InvalidStateInfo: Estado no válido. diff --git a/AxonIvyPortal/portal/cms/cms_fr.yaml b/AxonIvyPortal/portal/cms/cms_fr.yaml index 7c80dd15be6..84ebbda93f8 100644 --- a/AxonIvyPortal/portal/cms/cms_fr.yaml +++ b/AxonIvyPortal/portal/cms/cms_fr.yaml @@ -1564,7 +1564,6 @@ Dialogs: IconSelection: IconLinkTooltip: Cliquez pour changer d'icône TaskItemDetails: - CannotResetTaskWarning: Quelqu'un travaille actuellement sur cette tâche. CannotWorkOnDestroyedTaskWarning: Vous ne pouvez pas travailler sur la tâche parce qu'elle a été détruite. CannotWorkOnTaskWarning: Vous ne pouvez pas travailler sur cette tâche car l'utilisateur {0} y travaille déjà. InvalidStateInfo: État non valide. diff --git a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/TaskDetailsBean.java b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/TaskDetailsBean.java index a295d525f3f..d88f2beed7a 100644 --- a/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/TaskDetailsBean.java +++ b/AxonIvyPortal/portal/src/ch/ivy/addon/portalkit/bean/TaskDetailsBean.java @@ -16,10 +16,8 @@ import ch.ivy.addon.portal.generic.navigation.PortalNavigator; import ch.ivy.addon.portalkit.dto.taskdetails.TaskDetails; import ch.ivy.addon.portalkit.enums.GlobalVariable; -import ch.ivy.addon.portalkit.enums.PortalPermission; import ch.ivy.addon.portalkit.enums.PortalVariable; import ch.ivy.addon.portalkit.jsf.Attrs; -import ch.ivy.addon.portalkit.jsf.ManagedBeans; import ch.ivy.addon.portalkit.service.DateTimeGlobalSettingService; import ch.ivy.addon.portalkit.util.PermissionUtils; import ch.ivy.addon.portalkit.util.SecurityMemberDisplayNameUtils; @@ -38,7 +36,6 @@ public class TaskDetailsBean extends AbstractConfigurableContentBean { if (currentIsWorkerUser()) { yield Ivy.cms().co(RESET_TASK_WARNING_CMS_URI, Arrays.asList(buildResetTaskUrl(selectedTask))); - } else if (canResetTask()) { - yield Ivy.cms().co(CANNOT_WORK_ON_TASK_WARNING_CMS_URI, Arrays.asList(getWorkerUser(selectedTask))); } else { - yield Ivy.cms().co(CANNOT_RESET_TASK_WARNING_CMS_URI); + yield Ivy.cms().co(CANNOT_WORK_ON_TASK_WARNING_CMS_URI, Arrays.asList(getWorkerUser(selectedTask))); } } case DONE, READY_FOR_JOIN, JOINING, JOIN_FAILED -> { From 2bdce75ae56ffab7c0b789fa76d2f75dc667f9ac Mon Sep 17 00:00:00 2001 From: Nam Chu <117632090+chnam-axonivy@users.noreply.github.com> Date: Mon, 4 Mar 2024 14:55:14 +0700 Subject: [PATCH 22/24] Create main.yml set up super-linter for main branch --- .github/workflows/main.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000000..43a86b24019 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,26 @@ +name: Lint Code Base + +on: + pull_request: + branches: [ main ] + push: + branches: [ feature/IVYPORTAL-16459-OpenSource ] + +jobs: + build: + name: Lint Code Base + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v4.1.1 + with: + fetch-depth: 0 + + - name: Super-Linter + uses: super-linter/super-linter@v5.7.2 + env: + VALIDATE_ALL_CODEBASE: false + DEFAULT_BRANCH: ${{ github.event.pull_request.base.ref }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DISABLE_ERRORS: true From 83810a2aebbbb125fbb4c6d33dd4d00aa81b090d Mon Sep 17 00:00:00 2001 From: Nam Chu <117632090+chnam-axonivy@users.noreply.github.com> Date: Mon, 4 Mar 2024 14:56:41 +0700 Subject: [PATCH 23/24] Update branch name in main.yml --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 43a86b24019..c998f1849a1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,7 +2,7 @@ name: Lint Code Base on: pull_request: - branches: [ main ] + branches: [ master ] push: branches: [ feature/IVYPORTAL-16459-OpenSource ] From 3c7a7b9172fc045082de37c54bda323d2564dc6f Mon Sep 17 00:00:00 2001 From: Nam Chu <117632090+chnam-axonivy@users.noreply.github.com> Date: Mon, 4 Mar 2024 15:47:00 +0700 Subject: [PATCH 24/24] Update linter file main.yml --- .github/workflows/main.yml | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c998f1849a1..92f3319674f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,19 +8,25 @@ on: jobs: build: - name: Lint Code Base + name: Lint runs-on: ubuntu-latest + permissions: + contents: read + packages: read + # To report GitHub Actions status checks + statuses: write + steps: - - name: Checkout Code - uses: actions/checkout@v4.1.1 + - name: Checkout code + uses: actions/checkout@v4 with: + # super-linter needs the full git history to get the + # list of files that changed across commits fetch-depth: 0 - - name: Super-Linter - uses: super-linter/super-linter@v5.7.2 + - name: Super-linter + uses: super-linter/super-linter@v6.3.0 # x-release-please-version env: - VALIDATE_ALL_CODEBASE: false - DEFAULT_BRANCH: ${{ github.event.pull_request.base.ref }} + # To report GitHub Actions status checks GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - DISABLE_ERRORS: true