diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..7c16d85f7 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# Global rule: +* @brahmanand1 @spal-sapient @aksshriv1 @shunaray @kunkambl @mampacch @nagendra-battala @Chittauri @gipathak @risshukl0 @ananthpal @manoj-srivastava \ No newline at end of file diff --git a/.github/workflows/Processors_CI_Workflow.yaml b/.github/workflows/Processors_CI_Workflow.yaml new file mode 100644 index 000000000..ece26985e --- /dev/null +++ b/.github/workflows/Processors_CI_Workflow.yaml @@ -0,0 +1,115 @@ +name: Processors_CI_Workflow # Define the name of the workflow + +# Define when the workflow should trigger +on: + pull_request: + types: + - labeled # Trigger when a label is added + - unlabeled # Trigger when a label is removed + - synchronize # Trigger when commits are pushed to the PR + - opened # Trigger when a PR is opened + - edited # Trigger when a PR title or description is edited + - ready_for_review # Trigger when a draft PR is marked as ready + - reopened # Trigger when a closed PR is reopened + - unlocked # Trigger when a locked PR is unlocked + branches: [master, develop, qa-master] # Apply to these branches + pull_request_review: + types: [edited, dismissed] # Trigger when a review is edited or dismissed + branches: [master, develop, qa-master] + workflow_dispatch: # Allow manual triggering of the workflow + +# Define environment variables +env: + GITHUB_HEAD_NAME: $GITHUB_HEAD_REF # Store the head branch name + sonartoken: ${{ secrets.SONARQUBE_TOKEN }} # Secret for SonarQube authentication + sonarurl: ${{ secrets.SONARURL }} # SonarQube URL stored in secrets + +jobs: + + # ✅ Building & Testing Processors + processors_ci: + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set Up Java + uses: actions/setup-java@v2 + with: + distribution: 'adopt' + java-version: '17' + + - name: Cache Maven packages + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + - name: Clone & Build knowhow-common dependency + run: | + SOURCE_BRANCH="${{ github.head_ref }}" + TARGET_BRANCH="${{ github.event.pull_request.base.ref }}" + + echo "Checking if branch '$SOURCE_BRANCH' exists in knowhow-common repo..." + if git ls-remote --heads https://github.com/PublicisSapient/knowhow-common.git $SOURCE_BRANCH | grep $SOURCE_BRANCH; then + BRANCH_TO_CLONE=$SOURCE_BRANCH + else + echo "Branch '$SOURCE_BRANCH' not found. Falling back to target branch '$TARGET_BRANCH'." + BRANCH_TO_CLONE=$TARGET_BRANCH + fi + + git clone --branch $BRANCH_TO_CLONE https://github.com/PublicisSapient/knowhow-common.git + cd knowhow-common + mvn clean install -Ddockerfile.skip=true -X + + - name: Get common version using Maven Help Plugin + run: | + cd knowhow-common + COMMON_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) + echo "COMMON_VERSION=$COMMON_VERSION" + echo "COMMON_VERSION=$COMMON_VERSION" >> $GITHUB_ENV + + - name: Build & Test Jira Processor + run: | + mvn clean install -Pjira-processor -Ddockerfile.skip=true -Dcommon.version=$COMMON_VERSION + + - name: Build & Test Azure Board Processor + run: mvn clean install -Pazure-board-processor -Ddockerfile.skip=true -Dcommon.version=$COMMON_VERSION + + - name: Build & Test DevOps Processor + run: mvn clean install -Pdevops-processor -Ddockerfile.skip=true -Dcommon.version=$COMMON_VERSION + + - name: Build & Test Azure Pipeline Repo Processor + run: mvn clean install -Pazure-pipeline-repo -Ddockerfile.skip=true -Dcommon.version=$COMMON_VERSION + + - name: SonarQube Analysis - Processors + run: | + mvn sonar:sonar -Dsonar.projectKey=ENGINEERING.KPIDASHBOARD.PROCESSORS \ + -Dsonar.projectName=ENGINEERING.KPIDASHBOARD.PROCESSORS \ + -Dsonar.branch.name=${{ env.GITHUB_HEAD_NAME }} \ + -Dsonar.host.url=${{ secrets.SONARQUBE_HOST }} \ + -Dcommon.version=$COMMON_VERSION \ + -Dsonar.login=${{ secrets.SONARQUBE_TOKEN }} -f pom.xml + + - name: Check SonarQube Quality Gate - Processors + run: | + chmod +x SonarQG.sh + ./SonarQG.sh ./target/sonar/report-task.txt + + # ✅ Final Job to Ensure Completion + GitHub_CI_Complete: + needs: [processors_ci] + if: always() + runs-on: ubuntu-latest + steps: + - name: Check Job Status + run: | + if [[ "${{ needs.processors_ci.result }}" == "failure" || \ + "${{ needs.processors_ci.result }}" == "cancelled" ]]; then + echo "❌ One or more jobs failed or were cancelled. Failing CI." + exit 1 + else + echo "✅ All relevant jobs have passed." + fi \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..2883fa8f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,212 @@ +# Created by .ignore support plugin (hsz.mobi) +### Gradle template +.gradle +build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm + +*.iml + +## Directory-based project format: +.idea/ +# if you remove the above rule, at least ignore the following: + +# User-specific stuff: +# .idea/workspace.xml +# .idea/tasks.xml +# .idea/dictionaries + +# Sensitive or high-churn files: +# .idea/dataSources.ids +# .idea/dataSources.xml +# .idea/sqlDataSources.xml +# .idea/dynamic.xml +# .idea/uiDesigner.xml + +# Gradle: +# .idea/gradle.xml +# .idea/libraries + +# Mongo Explorer plugin: +# .idea/mongoSettings.xml + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties + + +### Eclipse template +*.pydevproject +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath + +# Eclipse Core +# Eclipse Core +.project + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# PDT-specific +.buildpath + +# sbteclipse plugin +.target + +# TeXlipse plugin +.texlipse + + +### Java template +*.class + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + + +### Maven template +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties + + +### Grails template +# .gitignore for Grails 1.2 and 1.3 +# Although this should work for most versions of grails, it is +# suggested that you use the "grails integrate-with --git" command +# to generate your .gitignore file. + +# web application files +/web-app/WEB-INF/classes + +# default HSQL database files for production mode +/prodDb.* + +# general HSQL database files +*Db.properties +*Db.script + +# logs +/stacktrace.log +/test/reports +/logs +/**/logs + +# project release file +/*.war + +# plugin release files +/*.zip +/plugin.xml + +# older plugin install locations +/plugins +/web-app/plugins + +# "temporary" build files +/target +.DS* + +/**/.pmd +/**/.ruleset + +application-local.properties +customapi/test-output/ + +**/.factorypath + +customapi/.eclipse-pmd + + +# compiled output +/**/dist +/**/tmp +/**/out-tsc +/**/coverage +/**/.scannerwork + + +# dependencies +/**/node_modules + +# IDEs and editors +.c9/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# misc +/**/.sass-cache +/**/connect.lock +/**/UI/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log +**/UI/typings + +# System Files +.DS_Store +Thumbs.db +package-lock.json + +.github/CODEOWNERS diff --git a/SonarQG.sh b/SonarQG.sh new file mode 100644 index 000000000..b4b8bd779 --- /dev/null +++ b/SonarQG.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# this script checks the status of a quality gate for a particular analysisID +# approach taken from https://docs.sonarqube.org/display/SONARQUBE53/Breaking+the+CI+Build +# When SonarScanner executes, the compute engine task is given an id +# The status of this task, and analysisId for the task can be checked at +# /api/ce/task?id=taskid +# When the status is SUCCESS, the quality gate status can be checked at +# /api/qualitygates/project_status?analysisId=analysisId +#set errexit +#set pipefail +#set nounset + +# in newer versions of sonar scanner the default report-task.txt location may be different +#REPORT_PATH="./customapi/target/sonar/report-task.txt" +#REPORT_PATH=".sonar/report-task.txt" +CE_TASK_ID_KEY="ceTaskId=" + +#SONAR_ACCESS_TOKEN="9000" +SLEEP_TIME=5 + +echo "QG Script --> Using SonarQube instance ${sonarurl}" + +# get the compute engine task id +ce_task_id=$(cat $1 | grep $CE_TASK_ID_KEY | cut -d'=' -f2) +echo "QG Script --> Using task id of ${ce_task_id}" + +if [ -z "$ce_task_id" ]; then + echo "QG Script --> No task id found" + exit 1 +fi + +# grab the status of the task +# if CANCELLED or FAILED, fail the Build +# if SUCCESS, stop waiting and grab the analysisId +wait_for_success=true + +while [ "${wait_for_success}" = "true" ] +do + ce_status=$(curl --user ${sonartoken}: ${sonarurl}/api/ce/task?id="${ce_task_id}" | jq -r .task.status) + + echo "QG Script --> Status of SonarQube task is ${ce_status}" + + if [ "${ce_status}" = "CANCELLED" ]; then + echo "QG Script --> SonarQube Compute job has been cancelled - exiting with error" + exit 1 + fi + + if [ "${ce_status}" = "FAILED" ]; then + echo "QG Script --> SonarQube Compute job has failed - exiting with error" + exit 1 + fi + + if [ "${ce_status}" = "SUCCESS" ]; then + wait_for_success=false + fi + + sleep 10 + +done + +ce_analysis_id=$(curl --user ${sonartoken}: ${sonarurl}/api/ce/task?id=$ce_task_id | jq -r .task.analysisId) +echo "QG Script --> Using analysis id of ${ce_analysis_id}" + +# get the status of the quality gate for this analysisId +qg_status=$(curl --user ${sonartoken}: ${sonarurl}/api/qualitygates/project_status?analysisId="${ce_analysis_id}" | jq -r .projectStatus.status) +echo "QG Script --> Quality Gate status is ${qg_status}" + +if [ "${qg_status}" != "OK" ]; then + echo "Pipeline aborted due to quality gate failure" + exit 1 +fi diff --git a/argocd/pom.xml b/argocd/pom.xml index ad6bafa43..daa67407b 100644 --- a/argocd/pom.xml +++ b/argocd/pom.xml @@ -55,7 +55,7 @@ com.publicissapient.kpidashboard common - ${project.version} + ${common.version} org.projectlombok diff --git a/azure-boards/pom.xml b/azure-boards/pom.xml index 987d567a2..a4d305f17 100644 --- a/azure-boards/pom.xml +++ b/azure-boards/pom.xml @@ -57,7 +57,7 @@ com.publicissapient.kpidashboard common - ${project.version} + ${common.version} compile diff --git a/azure-boards/src/main/java/com/publicissapient/kpidashboard/azure/client/azureissue/ScrumAzureIssueClientImpl.java b/azure-boards/src/main/java/com/publicissapient/kpidashboard/azure/client/azureissue/ScrumAzureIssueClientImpl.java index d92ec91ad..712bd1bab 100644 --- a/azure-boards/src/main/java/com/publicissapient/kpidashboard/azure/client/azureissue/ScrumAzureIssueClientImpl.java +++ b/azure-boards/src/main/java/com/publicissapient/kpidashboard/azure/client/azureissue/ScrumAzureIssueClientImpl.java @@ -23,6 +23,7 @@ import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; @@ -153,8 +154,8 @@ public int processesAzureIssues(ProjectConfFieldMapping projectConfig, String pr Map startTimesByIssueType = new HashMap<>(); - maxChangeDatesByIssueType - .forEach((k, v) -> startTimesByIssueType.put(k, v.minusMinutes(azureProcessorConfig.getMinsToReduce()))); + maxChangeDatesByIssueType.forEach( + (k, v) -> startTimesByIssueType.put(k, v.minusMinutes(azureProcessorConfig.getMinsToReduce()))); int pageSize = azureAdapter.getPageSize(); @@ -390,6 +391,7 @@ public int saveAzureIssueDetails(List currentPagedAzureRs, ProjectConfFie // ADD Production Incident field to feature setProdIncidentIdentificationField(fieldMapping, issue, azureIssue, fieldsMap); + setLateRefinement188(fieldMapping, azureIssue, fieldsMap); setIssueTechStoryType(fieldMapping, issue, azureIssue, fieldsMap); @@ -1209,4 +1211,50 @@ private void saveExecutionTraceLog(ProcessorExecutionTraceLog processorExecution processorExecutionTraceLog.setExecutionEndedAt(System.currentTimeMillis()); processorExecutionTraceLogService.save(processorExecutionTraceLog); } + + private void setLateRefinement188(FieldMapping fieldMapping, JiraIssue azureIssue, Map fieldsMap) { + azureIssue.setUnRefinedValue188(null); + if (!isCustomFieldCriteriaValid(fieldMapping, fieldsMap)) { + return; + } + + String azureValue = fieldsMap.get(fieldMapping.getJiraRefinementByCustomFieldKPI188().trim()).toString(); + if (StringUtils.isBlank(azureValue)) { + azureIssue.setUnRefinedValue188(Collections.singleton("No Value")); + return; + } + + Set customFieldSet = Arrays.stream(azureValue.toLowerCase().split("\\s+")).collect(Collectors.toSet()); + if (StringUtils.isNotEmpty(fieldMapping.getJiraRefinementMinLengthKPI188()) + && CollectionUtils.isNotEmpty(customFieldSet)) { + int i = Integer.parseInt(fieldMapping.getJiraRefinementMinLengthKPI188()); + if (customFieldSet.size() >= i + && CollectionUtils.isNotEmpty(fieldMapping.getJiraRefinementKeywordsKPI188())) { + Set fieldMappingSet = fieldMapping.getJiraRefinementKeywordsKPI188().stream() + .map(String::toLowerCase).collect(Collectors.toSet()); + if (!checkKeyWords(customFieldSet, fieldMappingSet)) { + // when fields are not matching then we will set values + azureIssue.setUnRefinedValue188(customFieldSet); + } + } + } else { + azureIssue.setUnRefinedValue188(customFieldSet); + } + + } + + private static boolean checkKeyWords(Set stringSet, Set fieldMappingSet) { + for (String keyword : fieldMappingSet) { + if (!stringSet.contains(keyword.toLowerCase())) { + return false; + } + } + return true; + } + + private boolean isCustomFieldCriteriaValid(FieldMapping fieldMapping, Map fieldsMap) { + return StringUtils.isNotEmpty(fieldMapping.getJiraRefinementCriteriaKPI188()) + && CommonConstant.CUSTOM_FIELD.equalsIgnoreCase(fieldMapping.getJiraRefinementCriteriaKPI188()) + && fieldsMap.get(fieldMapping.getJiraRefinementByCustomFieldKPI188().trim()) != null; + } } diff --git a/azure-boards/src/main/java/com/publicissapient/kpidashboard/azure/client/metadata/MetaDataClientImpl.java b/azure-boards/src/main/java/com/publicissapient/kpidashboard/azure/client/metadata/MetaDataClientImpl.java index 4b01b3a8d..9bba5f225 100644 --- a/azure-boards/src/main/java/com/publicissapient/kpidashboard/azure/client/metadata/MetaDataClientImpl.java +++ b/azure-boards/src/main/java/com/publicissapient/kpidashboard/azure/client/metadata/MetaDataClientImpl.java @@ -271,8 +271,10 @@ private FieldMapping mapFieldMapping(Map> issueTypeMap, Map fieldMapping.setStoryFirstStatusKPI171(firstStatusList.get(0)); fieldMapping.setStoryFirstStatusKPI148(firstStatusList.get(0)); fieldMapping.setJiraDefectCreatedStatusKPI14(firstStatusList.get(0)); + fieldMapping.setJiraStatusKPI187(firstStatusList); } else { fieldMapping.setStoryFirstStatus(CommonConstant.OPEN); + fieldMapping.setJiraStatusKPI187(firstStatusList); fieldMapping.setStoryFirstStatusKPI171(CommonConstant.OPEN); fieldMapping.setStoryFirstStatusKPI148(CommonConstant.OPEN); fieldMapping.setJiraDefectCreatedStatusKPI14(CommonConstant.OPEN); diff --git a/azure-boards/src/test/java/com/publicissapient/kpidashboard/azure/client/azureissue/ScrumAzureIssueClientImplTest.java b/azure-boards/src/test/java/com/publicissapient/kpidashboard/azure/client/azureissue/ScrumAzureIssueClientImplTest.java index b5a49237f..40adf9ea4 100644 --- a/azure-boards/src/test/java/com/publicissapient/kpidashboard/azure/client/azureissue/ScrumAzureIssueClientImplTest.java +++ b/azure-boards/src/test/java/com/publicissapient/kpidashboard/azure/client/azureissue/ScrumAzureIssueClientImplTest.java @@ -1,5 +1,7 @@ package com.publicissapient.kpidashboard.azure.client.azureissue; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; @@ -450,4 +452,86 @@ private void createIssue() throws URISyntaxException, JSONException { issues.add(issue1); issue1.setFields(fields); } + + @Test + public void testSetLateRefinement188() throws Exception { + // Arrange + FieldMapping fieldMapping = new FieldMapping(); + fieldMapping.setJiraRefinementCriteriaKPI188(CommonConstant.CUSTOM_FIELD); + fieldMapping.setJiraRefinementByCustomFieldKPI188("customfield_14141"); + fieldMapping.setJiraRefinementMinLengthKPI188("2"); + fieldMapping.setJiraRefinementKeywordsKPI188(Arrays.asList("keyword1", "keyword2")); + + JiraIssue jiraIssue = new JiraIssue(); + + Map fieldsMap = new HashMap<>(); + fieldsMap.put("customfield_14141", "keyword1 keyword3"); + + // Use reflection to access the private method + java.lang.reflect.Method method = ScrumAzureIssueClientImpl.class.getDeclaredMethod( + "setLateRefinement188", FieldMapping.class, JiraIssue.class, Map.class); + method.setAccessible(true); + + // Act + method.invoke(scrumIssueClientImpl, fieldMapping, jiraIssue, fieldsMap); + + // Assert + assertNotNull(jiraIssue.getUnRefinedValue188()); + assertTrue(jiraIssue.getUnRefinedValue188().contains("keyword3")); + } + + @Test + public void testSetLateRefinement188_NoValue() throws Exception { + // Arrange + FieldMapping fieldMapping = new FieldMapping(); + fieldMapping.setJiraRefinementCriteriaKPI188(CommonConstant.CUSTOM_FIELD); + fieldMapping.setJiraRefinementByCustomFieldKPI188("customfield_14141"); + + JiraIssue jiraIssue = new JiraIssue(); + + Map fieldsMap = new HashMap<>(); + fieldsMap.put("customfield_14141", ""); + + // Act + java.lang.reflect.Method method = ScrumAzureIssueClientImpl.class.getDeclaredMethod( + "setLateRefinement188", FieldMapping.class, JiraIssue.class, Map.class); + method.setAccessible(true); + + // Act + method.invoke(scrumIssueClientImpl, fieldMapping, jiraIssue, fieldsMap); + + + // Assert + assertNotNull(jiraIssue.getUnRefinedValue188()); + assertTrue(jiraIssue.getUnRefinedValue188().contains("No Value")); + } + + @Test + public void testSetLateRefinement188_NoMatchingKeywords() throws Exception{ + // Arrange + FieldMapping fieldMapping = new FieldMapping(); + fieldMapping.setJiraRefinementCriteriaKPI188(CommonConstant.CUSTOM_FIELD); + fieldMapping.setJiraRefinementByCustomFieldKPI188("customfield_14141"); + fieldMapping.setJiraRefinementMinLengthKPI188("2"); + fieldMapping.setJiraRefinementKeywordsKPI188(Arrays.asList("keyword1", "keyword2")); + + JiraIssue jiraIssue = new JiraIssue(); + + Map fieldsMap = new HashMap<>(); + fieldsMap.put("customfield_14141", "keyword3 keyword4"); + + // Act + java.lang.reflect.Method method = ScrumAzureIssueClientImpl.class.getDeclaredMethod( + "setLateRefinement188", FieldMapping.class, JiraIssue.class, Map.class); + method.setAccessible(true); + + // Act + method.invoke(scrumIssueClientImpl, fieldMapping, jiraIssue, fieldsMap); + + + // Assert + assertNotNull(jiraIssue.getUnRefinedValue188()); + assertTrue(jiraIssue.getUnRefinedValue188().contains("keyword3")); + assertTrue(jiraIssue.getUnRefinedValue188().contains("keyword4")); + } } diff --git a/azure-pipeline/pom.xml b/azure-pipeline/pom.xml index 585952cf6..e6ef8f522 100644 --- a/azure-pipeline/pom.xml +++ b/azure-pipeline/pom.xml @@ -59,7 +59,7 @@ com.publicissapient.kpidashboard common - ${project.version} + ${common.version} ch.qos.logback diff --git a/azure-repo/pom.xml b/azure-repo/pom.xml index 7f995b110..026e74718 100644 --- a/azure-repo/pom.xml +++ b/azure-repo/pom.xml @@ -50,7 +50,7 @@ com.publicissapient.kpidashboard common - ${project.version} + ${common.version} ch.qos.logback diff --git a/bamboo/pom.xml b/bamboo/pom.xml index d5df2c228..3e544bc3a 100644 --- a/bamboo/pom.xml +++ b/bamboo/pom.xml @@ -61,7 +61,7 @@ com.publicissapient.kpidashboard common - ${project.version} + ${common.version} ch.qos.logback diff --git a/bitbucket/pom.xml b/bitbucket/pom.xml index 9c06352b8..59ef7c1c8 100644 --- a/bitbucket/pom.xml +++ b/bitbucket/pom.xml @@ -57,7 +57,7 @@ com.publicissapient.kpidashboard common - ${project.version} + ${common.version} ch.qos.logback diff --git a/github-action/pom.xml b/github-action/pom.xml index b86851164..48d9416cd 100644 --- a/github-action/pom.xml +++ b/github-action/pom.xml @@ -55,7 +55,7 @@ com.publicissapient.kpidashboard common - ${project.version} + ${common.version} ch.qos.logback diff --git a/github/pom.xml b/github/pom.xml index 238ba4386..cf2e8a69b 100644 --- a/github/pom.xml +++ b/github/pom.xml @@ -55,7 +55,7 @@ com.publicissapient.kpidashboard common - ${project.version} + ${common.version} ch.qos.logback diff --git a/gitlab/pom.xml b/gitlab/pom.xml index cae4c2faa..3182c40b3 100644 --- a/gitlab/pom.xml +++ b/gitlab/pom.xml @@ -56,7 +56,7 @@ com.publicissapient.kpidashboard common - ${project.version} + ${common.version} ch.qos.logback diff --git a/jenkins/pom.xml b/jenkins/pom.xml index 9f6cc6f26..1093b991f 100644 --- a/jenkins/pom.xml +++ b/jenkins/pom.xml @@ -51,7 +51,7 @@ com.publicissapient.kpidashboard common - ${project.version} + ${common.version} ch.qos.logback diff --git a/jira-xray-zephyr-squad/pom.xml b/jira-xray-zephyr-squad/pom.xml index adfe128d7..ccf34faac 100644 --- a/jira-xray-zephyr-squad/pom.xml +++ b/jira-xray-zephyr-squad/pom.xml @@ -63,7 +63,7 @@ com.publicissapient.kpidashboard common - ${project.version} + ${common.version} compile diff --git a/jira-zephyr-scale/pom.xml b/jira-zephyr-scale/pom.xml index f0206d471..5e6482791 100644 --- a/jira-zephyr-scale/pom.xml +++ b/jira-zephyr-scale/pom.xml @@ -56,7 +56,7 @@ com.publicissapient.kpidashboard common - ${project.version} + ${common.version} ch.qos.logback diff --git a/jira/pom.xml b/jira/pom.xml index 55d686e68..6f4bea883 100644 --- a/jira/pom.xml +++ b/jira/pom.xml @@ -93,7 +93,7 @@ com.publicissapient.kpidashboard common - ${project.version} + ${common.version} compile diff --git a/jira/src/main/java/com/publicissapient/kpidashboard/jira/processor/JiraIssueProcessorImpl.java b/jira/src/main/java/com/publicissapient/kpidashboard/jira/processor/JiraIssueProcessorImpl.java index cc26a33f8..73720768b 100644 --- a/jira/src/main/java/com/publicissapient/kpidashboard/jira/processor/JiraIssueProcessorImpl.java +++ b/jira/src/main/java/com/publicissapient/kpidashboard/jira/processor/JiraIssueProcessorImpl.java @@ -22,6 +22,7 @@ import static com.publicissapient.kpidashboard.jira.helper.JiraHelper.getFieldValue; import static com.publicissapient.kpidashboard.jira.helper.JiraHelper.getLabelsList; +import java.lang.reflect.Method; import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; @@ -89,8 +90,9 @@ @Slf4j @Service public class JiraIssueProcessorImpl implements JiraIssueProcessor { - private static final String TEST_PHASE = "TestPhase"; - private static final String UAT_PHASE = "UAT"; + private static final String TEST_PHASE = "TestPhase"; + private static final String UAT_PHASE = "UAT"; + private static final String NO_VALUE = "No Value"; AssigneeDetails assigneeDetails; @Autowired @@ -197,6 +199,7 @@ public JiraIssue convertToJiraIssue(Issue issue, ProjectConfFieldMapping project // ADD Production Incident field to feature setProdIncidentIdentificationField(fieldMapping, issue, jiraIssue, fields); setIssueTechStoryType(fieldMapping, issue, jiraIssue, fields); + setLateRefinement188(fieldMapping, jiraIssue, fields, issue); jiraIssue.setAffectedVersions(getAffectedVersions(issue)); setIssueEpics(issueEpics, epic, jiraIssue); setJiraIssueValues(jiraIssue, issue, fieldMapping, fields); @@ -963,5 +966,123 @@ private void setProdIncidentIdentificationField(FieldMapping featureConfig, Issu } catch (Exception e) { log.error("Error while parsing Production Incident field", e); } + + } + + private void setLateRefinement188(FieldMapping fieldMapping, JiraIssue jiraIssue, Map fields, Issue issue) { + jiraIssue.setUnRefinedValue188(null); + + String refinementCriteria = StringUtils.trimToNull(fieldMapping.getJiraRefinementCriteriaKPI188()); + String refinementField = StringUtils.trimToNull(fieldMapping.getJiraRefinementByCustomFieldKPI188()); + + if (refinementCriteria == null || refinementField == null) { + return; + } + + if (!CommonConstant.CUSTOM_FIELD.equalsIgnoreCase(refinementCriteria)) { + return; + } + + Object value = Optional.ofNullable(fields.get(refinementField)) + .map(IssueField::getValue) + .orElseGet(() -> getStandardFieldValue(issue, refinementField)); + + if (value == null) { + setUnrefinedValueReason(jiraIssue, NO_VALUE); + return; + } + + List customFieldValue = getCustomFieldValue(value); + if (CollectionUtils.isEmpty(customFieldValue)) { + setUnrefinedValueReason(jiraIssue, NO_VALUE); + return; + } + + Set customFieldSet = Arrays.stream(String.join(" ", customFieldValue).toLowerCase().split("\\s+")) + .filter(StringUtils::isNotBlank) + .collect(Collectors.toSet()); + + if (CollectionUtils.isEmpty(customFieldSet)) { + setUnrefinedValueReason(jiraIssue, NO_VALUE); + return; + } + + int minLength = parseMinLength(fieldMapping.getJiraRefinementMinLengthKPI188()); + if (customFieldSet.size() < minLength) { + jiraIssue.setUnRefinedValue188(customFieldSet); + return; + } + + Set keywords = Optional.ofNullable(fieldMapping.getJiraRefinementKeywordsKPI188()) + .orElse(Collections.emptyList()) + .stream() + .map(String::toLowerCase) + .collect(Collectors.toSet()); + + if (CollectionUtils.isEmpty(keywords) || !checkKeyWords(customFieldSet, keywords)) { + jiraIssue.setUnRefinedValue188(customFieldSet); + } + } + + + private Object getStandardFieldValue(Issue issue, String fieldName) { + try { + Method getter = Issue.class.getMethod("get" + StringUtils.capitalize(fieldName)); + return getter.invoke(issue); + } catch (Exception e) { + log.debug("Could not find or invoke getter for field: {}", fieldName, e); + return null; + } + } + + private void setUnrefinedValueReason(JiraIssue jiraIssue, String value) { + jiraIssue.setUnRefinedValue188(Collections.singleton(value)); } + + + private int parseMinLength(String minLengthStr) { + try { + return StringUtils.isNotBlank(minLengthStr) ? Integer.parseInt(minLengthStr) : 0; + } catch (NumberFormatException e) { + return 0; + } + } + + + + private static boolean checkKeyWords(Set stringSet, Set fieldMappingSet) { + + for (String keyword : fieldMappingSet) { + if (!stringSet.contains(keyword.toLowerCase())) { + return false; + } + } + return true; + } + + private ArrayList getCustomFieldValue(Object issueFieldValue) { + JSONParser parser = new JSONParser(); + ArrayList customValue = new ArrayList<>(); + try { + if (issueFieldValue instanceof org.codehaus.jettison.json.JSONArray) { + JSONArray array = (JSONArray) parser.parse(issueFieldValue.toString()); + for (Object o : array) { + org.json.simple.JSONObject jsonObject = (org.json.simple.JSONObject) parser.parse(o.toString()); + customValue.add(jsonObject.get(JiraConstants.VALUE).toString()); + } + } else if (issueFieldValue instanceof org.codehaus.jettison.json.JSONObject) { + String jsonObjectValue = ((org.codehaus.jettison.json.JSONObject) issueFieldValue) + .get(JiraConstants.VALUE).toString(); + customValue.add(jsonObjectValue); + } else if (StringUtils.isNotEmpty(issueFieldValue.toString()) + && StringUtils.isNotBlank(issueFieldValue.toString())) { + customValue.add(issueFieldValue.toString()); + } + + } catch (org.json.simple.parser.ParseException | JSONException e) { + log.error("JIRA Processor | Error while parsing custom field field {}", e); + } + return customValue; + } + } diff --git a/jira/src/main/java/com/publicissapient/kpidashboard/jira/service/CreateMetadataImpl.java b/jira/src/main/java/com/publicissapient/kpidashboard/jira/service/CreateMetadataImpl.java index a0a2a6310..9542d5e4c 100644 --- a/jira/src/main/java/com/publicissapient/kpidashboard/jira/service/CreateMetadataImpl.java +++ b/jira/src/main/java/com/publicissapient/kpidashboard/jira/service/CreateMetadataImpl.java @@ -352,6 +352,10 @@ private FieldMapping mapFieldMapping(Map> issueTypeMap, Map .setJiraQAKPI111IssueType(issueTypeMap.getOrDefault(CommonConstant.JIRAQAKPI111ISSUETYPE, new ArrayList<>())); fieldMapping.setJiraStoryIdentificationKPI129( issueTypeMap.getOrDefault(CommonConstant.JIRASTORYIDENTIFICATIONKPI129, new ArrayList<>())); + fieldMapping.setJiraIssueTypeNamesKPI187( + issueTypeMap.getOrDefault(CommonConstant.STORY, new ArrayList<>())); + fieldMapping.setJiraIssueTypeNamesKPI188( + issueTypeMap.getOrDefault(CommonConstant.STORY, new ArrayList<>())); fieldMapping.setJiraStoryIdentificationKPI166( issueTypeMap.getOrDefault(CommonConstant.JIRASTORYIDENTIFICATIONKPI166, new ArrayList<>())); fieldMapping.setJiraSprintVelocityIssueTypeKPI138( @@ -371,6 +375,7 @@ private FieldMapping mapFieldMapping(Map> issueTypeMap, Map .orElse(new ArrayList<>()).stream().collect(Collectors.toList())); fieldMapping.setJiraTechDebtIssueType(issueTypeMap.get(CommonConstant.JIRATECHDEBTISSUETYPE)); fieldMapping.setJiraIssueTypeKPI3(issueTypeMap.getOrDefault(CommonConstant.JIRAISSUETYPEKPI3, new ArrayList<>())); + fieldMapping.setJiraStatusKPI187(workflowMap.get(CommonConstant.STORYFIRSTSTATUSKPI3)); fieldMapping.setStoryFirstStatus(CommonConstant.OPEN); fieldMapping.setJiraStatusToConsiderKPI127(Arrays.asList(CommonConstant.OPEN)); fieldMapping @@ -571,6 +576,7 @@ private FieldMapping mapFieldMapping(Map> issueTypeMap, Map if (CollectionUtils.isNotEmpty(firstStatusList)) { fieldMapping.setStoryFirstStatus(firstStatusList.get(0)); + fieldMapping.setJiraStatusKPI187(workflowMap.get(CommonConstant.STORYFIRSTSTATUSKPI3)); fieldMapping.setStoryFirstStatusKPI171(firstStatusList.get(0)); fieldMapping.setStoryFirstStatusKPI148(firstStatusList.get(0)); fieldMapping.setStoryFirstStatusKPI154(firstStatusList); @@ -578,6 +584,7 @@ private FieldMapping mapFieldMapping(Map> issueTypeMap, Map fieldMapping.setJiraStatusToConsiderKPI127(firstStatusList); } else { fieldMapping.setStoryFirstStatus(CommonConstant.OPEN); + fieldMapping.setJiraStatusKPI187(workflowMap.get(CommonConstant.STORYFIRSTSTATUSKPI3)); fieldMapping.setStoryFirstStatusKPI171(CommonConstant.OPEN); fieldMapping.setStoryFirstStatusKPI148(CommonConstant.OPEN); fieldMapping.setStoryFirstStatusKPI154(Arrays.asList(CommonConstant.OPEN)); @@ -646,6 +653,10 @@ private FieldMapping mapFieldMapping(Map> issueTypeMap, Map fieldMapping.setJiraIssueTypeKPI3(issueTypeMap.getOrDefault(CommonConstant.STORY, new ArrayList<>())); fieldMapping.setJiraStoryIdentification(issueTypeMap.getOrDefault(CommonConstant.STORY, new ArrayList<>())); fieldMapping.setJiraStoryIdentificationKPI129(issueTypeMap.getOrDefault(CommonConstant.STORY, new ArrayList<>())); + fieldMapping.setJiraIssueTypeNamesKPI187( + issueTypeMap.getOrDefault(CommonConstant.STORY, new ArrayList<>())); + fieldMapping.setJiraIssueTypeNamesKPI188( + issueTypeMap.getOrDefault(CommonConstant.STORY, new ArrayList<>())); fieldMapping.setJiraStoryIdentificationKPI166(issueTypeMap.getOrDefault(CommonConstant.STORY, new ArrayList<>())); fieldMapping.setJiraStoryIdentificationKpi40(issueTypeMap.getOrDefault(CommonConstant.STORY, new ArrayList<>())); fieldMapping.setJiraStoryCategoryKpi40(issueTypeMap.getOrDefault(CommonConstant.STORY, new ArrayList<>())); @@ -702,6 +713,7 @@ private void populateKanbanFieldMappingData(FieldMapping fieldMapping, Map fields = new HashMap<>(); + Map fieldValueMap = new HashMap<>(); + fieldValueMap.put("value", "keyword1 keyword3"); + IssueField issueField = new IssueField("customfield_14141", "Custom Field", null, new JSONObject(fieldValueMap)); + fields.put("customfield_14141", issueField); + + Issue issue = issues.get(0); + + // Use reflection to access the private method + Method method = JiraIssueProcessorImpl.class.getDeclaredMethod("setLateRefinement188", FieldMapping.class, JiraIssue.class, Map.class, Issue.class); + method.setAccessible(true); + + // Act + method.invoke(transformFetchedIssueToJiraIssue, fieldMapping, jiraIssue, fields, issue); + + // Assert + assertNotNull(jiraIssue.getUnRefinedValue188()); + assertTrue(jiraIssue.getUnRefinedValue188().contains("keyword3")); + } + } \ No newline at end of file diff --git a/sonar/pom.xml b/sonar/pom.xml index 1f88feaba..4b10064d4 100644 --- a/sonar/pom.xml +++ b/sonar/pom.xml @@ -55,7 +55,7 @@ com.publicissapient.kpidashboard common - ${project.version} + ${common.version} ch.qos.logback diff --git a/teamcity/pom.xml b/teamcity/pom.xml index 14092322b..ddbad1766 100644 --- a/teamcity/pom.xml +++ b/teamcity/pom.xml @@ -72,7 +72,7 @@ com.publicissapient.kpidashboard common - ${project.version} + ${common.version} ch.qos.logback