Skip to content

Commit

Permalink
feat: Shift the jql construction to the user (#250)
Browse files Browse the repository at this point in the history
* feat: Shift the jql construction to the user

This PR:
- shifts the jql construction is shifted to the user by letting him specify the criteria of how to search of resolved and open issues as part of the action's duplication mechanism prevention
- prints more info when an error occurs in a REST call to Jira
- fixes a bug with the UPLOAD_FILES parameter and with the search criteria of the issues that are already open
- updates the unit tests

Contributes to: #126

Signed-off-by: Stelios Gkiokas <[email protected]>
  • Loading branch information
sgkiokas authored Sep 7, 2021
1 parent 749f44b commit b89098d
Show file tree
Hide file tree
Showing 13 changed files with 120 additions and 42 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Unit Tests

on:
on:
pull_request:

jobs:
Expand All @@ -17,4 +17,4 @@ jobs:
- name: Install Dependencies
run: npm install
- name: Test
run: npm run test
run: npm run test:unit
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@ payloads
.DS_Store

#VS Code
.vscode/
.vscode/

#Dotenv
.env
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ ARG JIRA_PROJECT
ARG JIRA_URI
ARG INPUT_JSON
ARG REPORT_INPUT_KEYS
ARG JIRA_ISSUE_TYPE
ARG ISSUE_TYPE
ARG RUNS_ON_GITHUB

ENV JIRA_USER=$JIRA_USER \
JIRA_PASSWORD=$JIRA_PASSWORD \
JIRA_PROJECT=$JIRA_PROJECT \
JIRA_PROJECT=$JIRA_PROJECT \
JIRA_URI=$JIRA_URI \
INPUT_JSON=$INPUT_JSON \
REPORT_INPUT_KEYS=$REPORT_INPUT_KEYS \
JIRA_ISSUE_TYPE=$JIRA_ISSUE_TYPE \
ISSUE_TYPE=$ISSUE_TYPE \
RUNS_ON_GITHUB=$RUNS_ON_GITHUB

COPY index.js package.json package-lock.json ./
Expand Down
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,16 @@ A GitHub Action to integrate multiple tools with Jira Server and raise relevant
|JIRA_URI|true|N/A|The JIRA URI for your organisation|
|INPUT_JSON|true|N/A|The JSON input to be parsed|
|REPORT_INPUT_KEYS|true|N/A|A list of keys of the input JSON you provide that will be parsed and included in the report|
|JIRA_ISSUE_TYPE|false|Security Vulnerability|Indicates if the JSON to be used for the JIRA REST calls is based on npm audit since there is a need for special treating of the overview report field|
|ISSUE_TYPE|true|""|Indicates if the JSON to be used for the JIRA REST calls is based on npm audit since there is a need for special treating of the overview report field|
|RUNS_ON_GITHUB|true|true|Indicates if the action runs on GitHub or locally, on a Docker container, for testing purporses|
|PRIORITY_MAPPER|false|""|Maps the severity level of the reporting issue to the relevant Jira priority score (A severity level can be skipped if not needed)|
|ISSUE_LABELS_MAPPER|true|N/A|Maps the labels of the reporting issue to the relevant Jira labels field|
|LOAD_BALANCER_COOKIE_ENABLED|false|""|Extra cookie needed for clustered Jira server to accommodate different Load Balancers such as F5, httpd etc.|
|LOAD_BALANCER_COOKIE_NAME|false|""|The name of the cookie for the Load Balancer (if any used)|
|UPLOAD_FILES|false|false|Uploads a file to each Jira issue created based on a comparison of the file name and its relation to each ticket issueSummary|
|UPLOAD_FILES_PATH|false|""|Used only if UPLOAD_FILE is set to true. It's the path holding the files to be uploaded|
|JQL_SEARCH_PAYLOAD_RESOLVED_ISSUES|false|""|The filter you want to apply in order to check for issues that have already been resolved. It is the jql query you get from the \"Advanced\" section while searching Jira tickets and it's used for the duplication mechanism prevention|
|JQL_SEARCH_PAYLOAD_OPEN_ISSUES|false|""|The filter you want to apply in order to check for issues that are already Open. It is the jql query you get from the \"Advanced\" section while searching Jira tickets and it's used for the duplication mechanism prevention|

### Outputs

Expand Down Expand Up @@ -69,19 +71,23 @@ jobs:
- name: Jira ticket creation
id: jira_integration
uses: ./actions-jira-integration/
env:
ISSUE_TYPE: 'Security Vulnerability'
ISSUE_LABELS_MAPPER: 'Security,Triaged,npm_audit_check'
JIRA_PROJECT: MBIL
with:
JIRA_USER: ${{ secrets.JIRA_USER }}
JIRA_PASSWORD: ${{ secrets.JIRA_PASSWORD }}
# the job with id npm_audit outputs a variable called npm_audit_json
INPUT_JSON: ${{ steps.npm_audit.outputs.npm_audit_json }}
JIRA_PROJECT: MBIL
JIRA_PROJECT: ${{ env.JIRA_PROJECT }}
JIRA_URI: 'jira.camelot.global'
REPORT_INPUT_KEYS: |
issueName: {{module_name}}
issueSummary: npm-audit: {{module_name}} module vulnerability\n
issueDescription: \`*Recommendation*:\\n\\n{{recommendation}}\\n\\n*Details for {{cwe}}*\\n\\n_Vulnerable versions_:\\n\\n{{vulnerable_versions}}\\n\\n_Patched versions_:\\n\\n{{patched_versions}}\\n\\n*Overview*\\n\\n{{overview}}\\n\\n*References*\\n\\n{{url}}\\n\\n`
issueSeverity: {{severity}}
JIRA_ISSUE_TYPE: 'Security Vulnerability'
ISSUE_TYPE: ${{ env.ISSUE_TYPE }}
RUNS_ON_GITHUB: true
PRIORITY_MAPPER: |
low: P3
Expand All @@ -92,7 +98,8 @@ jobs:
LOAD_BALANCER_COOKIE_NAME: 'AWSALB'
UPLOAD_FILES: true
UPLOAD_FILES_PATH: './upload_file_path'
```
JQL_SEARCH_PAYLOAD_RESOLVED_ISSUES: 'project=${{ env.JIRA_PROJECT }} AND type="${{ env.ISSUE_TYPE }}" AND labels IN (${{ env.ISSUE_LABELS_MAPPER }}) AND status=Done AND resolution IN (Obsolete,Duplicate,"Won''t Do")'
JQL_SEARCH_PAYLOAD_OPEN_ISSUES: 'project=${{ env.JIRA_PROJECT }} AND type="${{ env.ISSUE_TYPE }}" AND labels IN (${{ env.ISSUE_LABELS_MAPPER }}) AND status NOT IN (Done)'

**NOTE**: when you specify the JSON keys you want to be parsed and evaluated in your final payload, you **must** enclose them in double curly brackets (`{{<keyName>}}`). This is important for the parsing of the action to work properly. Also, the submitted JSON **must** be in its final form that you want it to be processed (not purely the raw output of your report).

Expand Down
16 changes: 12 additions & 4 deletions action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ inputs:
REPORT_INPUT_KEYS:
description: "A list of keys of the input JSON you provide that will be parsed and included in the report"
required: true
JIRA_ISSUE_TYPE:
ISSUE_TYPE:
description: "The type of the issue to be used whuile searching for existing issues. This is needed in order to avoid duplicating already raised issues"
required: false
default: "Security Vulnerability"
required: true
default: ""
RUNS_ON_GITHUB:
description: "Indicates if the action runs on GitHub or locally, on a Docker container, for testing purposes"
required: true
Expand All @@ -44,11 +44,19 @@ inputs:
default: ""
UPLOAD_FILES:
description: "Uploads a file to each Jira issue created based on a comparison of the file name and its relation to each ticket issueSummary"
require: false
required: false
default: false
UPLOAD_FILES_PATH:
description: "Used only if UPLOAD_FILES is set to true. It's the path holding the files to be uploaded"
required: false
default: false
JQL_SEARCH_PAYLOAD_RESOLVED_ISSUES:
description: "The filter you want to apply in order to check for issues that have already been resolved. It is the jql query you get from the \"Advanced\" section while searching Jira tickets and it's used for the duplication mechanism prevention."
required: false
default: ""
JQL_SEARCH_PAYLOAD_OPEN_ISSUES:
description: "The filter you want to apply in order to check for issues that are already Open. It is the jql query you get from the \"Advanced\" section while searching Jira tickets and it's used for the duplication mechanism prevention."
required: false
default: ""
runs:
using: "docker"
Expand Down
5 changes: 3 additions & 2 deletions config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@ const JIRA_CONFIG = {
JIRA_PASSWORD: process.env.JIRA_PASSWORD || core.getInput('JIRA_PASSWORD'),
JIRA_PROJECT: process.env.JIRA_PROJECT || core.getInput('JIRA_PROJECT'),
JIRA_URI: process.env.JIRA_URI || core.getInput('JIRA_URI'),
ISSUE_TYPE: process.env.ISSUE_TYPE || core.getInput('ISSUE_TYPE'),
JIRA_ISSUE_CREATION_ENDPOINT: '/rest/api/2/issue',
JIRA_ISSUE_AUTH_SESSION_ENDPOINT: '/rest/auth/1/session',
JIRA_ISSUE_SEARCH_ENDPOINT: '/rest/api/2/search',
JIRA_ISSUE_SEARCH_PAYLOAD_RESOLVED_ISSUES: {
jql: `project=${core.getInput('JIRA_PROJECT') || process.env.JIRA_PROJECT} AND type="${core.getInput('JIRA_ISSUE_TYPE') || 'Security Vulnerability'}" AND labels IN ("${core.getInput('ISSUE_LABELS_MAPPER') || 'Security'}") AND status="Done" AND resolution IN ("Obsolete", "Duplicate", "Won't Do")`,
jql: process.env.JQL_SEARCH_PAYLOAD_RESOLVED_ISSUES || core.getInput('JQL_SEARCH_PAYLOAD_RESOLVED_ISSUES'),
startAt: 0,
maxResults: 1000,
fields: [
'summary'
]
},
JIRA_ISSUE_SEARCH_PAYLOAD_OPEN_ISSUES: {
jql: `project=${core.getInput('JIRA_PROJECT') || process.env.JIRA_PROJECT} AND type="${core.getInput('JIRA_ISSUE_TYPE') || 'Security Vulnerability'}" AND labels IN ("${core.getInput('ISSUE_LABELS_MAPPER') || 'Security'}") AND status NOT IN ("Done")`,
jql: process.env.JQL_SEARCH_PAYLOAD_OPEN_ISSUES || core.getInput('JQL_SEARCH_PAYLOAD_OPEN_ISSUES'),
startAt: 0,
maxResults: 1000,
fields: [
Expand Down
12 changes: 12 additions & 0 deletions helpers/rest-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ const POSTRequestWrapper = async (
return response;
} catch (error) {
log.warn(`POST request ${requestName} encountered the following error: ${error.message}`);

if (error.response && error.response.body) {
error.response.body.errorMessages.forEach(message => log.warn(message));
log.warn(error.response.body.errors);
}

return error;
}
};
Expand All @@ -53,6 +59,12 @@ const DELETERequestWrapper = async (
return response;
} catch (error) {
log.warn(`DELETE request ${requestName} encountered the following error: ${error.message}`);

if (error.response && error.response.body) {
error.response.body.errorMessages.forEach(message => log.warn(message));
log.warn(error.response.body.errors);
}

return error;
}
};
Expand Down
25 changes: 13 additions & 12 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@ const utils = require('./utils/helper');
const config = require('./config/config');
const jira = require('./helpers/jira-helpers');

const INPUT_JSON = core.getInput('INPUT_JSON') || process.env.INPUT_JSON;
const REPORT_INPUT_KEYS = core.getInput('REPORT_INPUT_KEYS') || process.env.REPORT_INPUT_KEYS;
const PRIORITY_MAPPER = core.getInput('PRIORITY_MAPPER') || process.env.PRIORITY_MAPPER;
const ISSUE_LABELS_MAPPER = core.getInput('ISSUE_LABELS_MAPPER') || process.env.ISSUE_LABELS_MAPPER;
const UPLOAD_FILES = (core.getInput('UPLOAD_FILES') || process.env.UPLOAD_FILES) === 'false';
const REPORT_INPUT_KEYS = utils.getInput('REPORT_INPUT_KEYS');
const PRIORITY_MAPPER = utils.getInput('PRIORITY_MAPPER');
const UPLOAD_FILES = utils.getInput('UPLOAD_FILES') === 'true';
const UPLOAD_FILES_PATH = (core.getInput('UPLOAD_FILES_PATH') || process.env.UPLOAD_FILES_PATH) === '';

let jiraAuthHeaderValue;
Expand Down Expand Up @@ -92,10 +90,14 @@ const kickOffAction = async (inputJson) => {
Object.entries(utils.populateMap(PRIORITY_MAPPER))
);
const reportPairsMapper = utils.populateMap(REPORT_INPUT_KEYS);
const labels =
ISSUE_LABELS_MAPPER.length !== 0
? { labels: ISSUE_LABELS_MAPPER.split(',') }
: { labels: [] };
const issueLabelsMapper = utils.getInput('ISSUE_LABELS_MAPPER');

let labels = { labels: [] };
if (issueLabelsMapper.length === 1) {
labels = { labels: issueLabelsMapper };
} else if (issueLabelsMapper.length > 1) {
labels = { labels: issueLabelsMapper.split(',') };
}

const parsedInput = JSON.parse(inputJson);
for (const inputElement in parsedInput) {
Expand All @@ -107,8 +109,7 @@ const kickOffAction = async (inputJson) => {
const severityMap = priorityMapper.get(reportMapperInstance.issueSeverity);
if (severityMap !== undefined) {
if (
!retrievedIssuesUniqueSummaries.includes(utils.ultraTrim(reportMapperInstance.issueSummary)) &&
!_.isEmpty(retrievedIssuesUniqueSummaries)
!retrievedIssuesUniqueSummaries.includes(utils.ultraTrim(reportMapperInstance.issueSummary))
) {
log.info(`Attempting to create JSON payload for module ${reportMapperInstance.issueName}...`);
utils.amendHandleBarTemplate(
Expand Down Expand Up @@ -151,5 +152,5 @@ const kickOffAction = async (inputJson) => {

(async () => {
utils.folderCleanup(config.UTILS.PAYLOADS_DIR);
await kickOffAction(INPUT_JSON);
await kickOffAction(utils.getInput('INPUT_JSON'));
})();
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"main": "index.js",
"scripts": {
"start": "node index.js",
"test": "nyc mocha",
"test:unit": "nyc mocha",
"lint": "./node_modules/.bin/eslint"
},
"author": "",
Expand All @@ -24,8 +24,8 @@
},
"devDependencies": {
"assert": "^2.0.0",
"bunyan": "^1.8.15",
"child_process": "^1.0.2",
"bunyan": "1.8.15",
"child_process": "1.0.2",
"dirty-json": "0.9.2",
"eslint": "7.28.0",
"eslint-config-standard": "16.0.3",
Expand Down
2 changes: 1 addition & 1 deletion templates/issueCreation.template
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"project": {"key": "{{PROJECT_ID}}"},
"summary": "{{ISSUE_SUMMARY}}",
"issuetype": {
"name": "Security Vulnerability"
"name": "{{ISSUE_TYPE}}"
},
"priority": {
"name": "{{ISSUE_SEVERITY_MAP}}"
Expand Down
39 changes: 35 additions & 4 deletions test/jira-helper.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ describe('Jira REST are functioning properly', () => {
process.env = {
JIRA_PROJECT: mocks.MOCK_JIRA_PROJECT,
JIRA_URI: mocks.MOCK_JIRA_URI,
JIRA_ISSUE_TYPE: mocks.MOCK_JIRA_ISSUE_TYPE_FILTER
ISSUE_TYPE: mocks.MOCK_JIRA_ISSUE_TYPE_FILTER,
JQL_SEARCH_PAYLOAD_RESOLVED_ISSUES: mocks.MOCK_JQL_SEARCH_PAYLOAD_RESOLVED_ISSUES,
JQL_SEARCH_PAYLOAD_OPEN_ISSUES: mocks.MOCK_JQL_SEARCH_PAYLOAD_OPEN_ISSUES
};

let sessionPayload = '';
Expand Down Expand Up @@ -91,6 +93,31 @@ describe('Jira REST are functioning properly', () => {
.to.have.all.keys('expand', 'startAt', 'maxResults', 'total', 'issues');
});

it('a list of JIRA resolved issues can be queried', async () => {
nock(mocks.MOCK_JIRA_URI)
.post(config.JIRA_CONFIG.JIRA_ISSUE_SEARCH_ENDPOINT)
.reply(200, mocks.MOCK_JIRA_ISSUE_SEARCH_RESPONSE);

const response = await jira.searchExistingJiraIssues(authHeaders, config.JIRA_CONFIG.JIRA_ISSUE_SEARCH_PAYLOAD_OPEN_ISSUES);
expect(response.body)
.to.be.instanceOf(Object)
.to.have.all.keys('expand', 'startAt', 'maxResults', 'total', 'issues');
});

it('a list of JIRA issues without a supplied jql query can be queried', async () => {
process.env.JQL_SEARCH_PAYLOAD_OPEN_ISSUES = mocks.MOCK_JQL_SEARCH_PAYLOAD_OPEN_ISSUES_EMPTY;
process.env.JQL_SEARCH_PAYLOAD_RESOLVED_ISSUES = mocks.MOCK_JQL_SEARCH_PAYLOAD_RESOLVED_ISSUES_EMPTY;

nock(mocks.MOCK_JIRA_URI)
.post(config.JIRA_CONFIG.JIRA_ISSUE_SEARCH_ENDPOINT)
.reply(200, mocks.MOCK_JIRA_ISSUE_SEARCH_RESPONSE);

const response = await jira.searchExistingJiraIssues(authHeaders, config.JIRA_CONFIG.JIRA_ISSUE_SEARCH_PAYLOAD_OPEN_ISSUES);
expect(response.body)
.to.be.instanceOf(Object)
.to.have.all.keys('expand', 'startAt', 'maxResults', 'total', 'issues');
});

it('a JIRA session can be invalidated', async () => {
nock(mocks.MOCK_JIRA_URI)
.delete(config.JIRA_CONFIG.JIRA_ISSUE_AUTH_SESSION_ENDPOINT)
Expand All @@ -113,7 +140,7 @@ describe('Jira REST are functioning properly', () => {
process.env = {
JIRA_PROJECT: mocks.MOCK_JIRA_PROJECT,
JIRA_URI: mocks.MOCK_JIRA_URI,
JIRA_ISSUE_TYPE: mocks.MOCK_JIRA_ISSUE_TYPE_FILTER
ISSUE_TYPE: mocks.MOCK_JIRA_ISSUE_TYPE_FILTER
};

it('a JIRA session fails to be created when the Jira URI is invalid', async () => {
Expand All @@ -140,7 +167,9 @@ describe('Jira REST are functioning properly', () => {
JSON.stringify(mocks.MOCK_JIRA_ISSUE_CREATION_WRONG_PAYLOAD)
);
expect(error.response.statusCode).to.be.equal(400);
expect(error.response.body).to.haveOwnProperty('errorMessages');
expect(error.response.body)
.to.be.instanceOf(Object)
.to.have.all.keys('errorMessages', 'errors');
});

it('a list of JIRA issues fails to be fetched when a wrong payload is supplied', async () => {
Expand All @@ -149,7 +178,9 @@ describe('Jira REST are functioning properly', () => {
.reply(400, mocks.MOCK_JIRA_ISSUE_WRONG_SEARCH_RESPONSE);

const error = await jira.searchExistingJiraIssues(authHeaders, config.JIRA_CONFIG.JIRA_ISSUE_SEARCH_PAYLOAD_OPEN_ISSUES);
expect(error.response.body).to.haveOwnProperty('errorMessages');
expect(error.response.body)
.to.be.instanceOf(Object)
.to.have.all.keys('errorMessages', 'errors');
});

// eslint-disable-next-line no-undef
Expand Down
18 changes: 13 additions & 5 deletions test/mocks/jira-helper-mock.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ const MOCK_JIRA_ISSUE_WRONG_SEARCH_RESPONSE = {
errorMessages: ["Field 'priority' is required"],
errors: {}
};
const MOCK_JIRA_ISSUE_RESOLUTION_STATUS = 'Done';
const MOCK_JIRA_ISSUE_RESOLUTION = 'Obsolete';
const MOCK_JQL_SEARCH_PAYLOAD_RESOLVED_ISSUES = `project=${MOCK_JIRA_PROJECT} AND type="${MOCK_JIRA_ISSUE_TYPE}" AND labels IN ("${MOCK_JIRA_ISSUE_LABELS}") AND status="${MOCK_JIRA_ISSUE_RESOLUTION_STATUS}" AND resolution IN (${MOCK_JIRA_ISSUE_RESOLUTION})`;
const MOCK_JQL_SEARCH_PAYLOAD_OPEN_ISSUES = `project=${MOCK_JIRA_PROJECT} AND type="${MOCK_JIRA_ISSUE_TYPE}" AND labels IN ("${MOCK_JIRA_ISSUE_LABELS}") AND status NOT IN ("${MOCK_JIRA_ISSUE_RESOLUTION_STATUS}")`;
const MOCK_JQL_SEARCH_PAYLOAD_RESOLVED_ISSUES_EMPTY = '';
const MOCK_JQL_SEARCH_PAYLOAD_OPEN_ISSUES_EMPTY = '';

module.exports = {
MOCK_LOGIN_SESSION: MOCK_LOGIN_SESSION,
Expand All @@ -90,10 +96,12 @@ module.exports = {
MOCK_JIRA_ISSUE_DESCRIPTION: MOCK_JIRA_ISSUE_DESCRIPTION,
MOCK_JIRA_URI: MOCK_JIRA_URI,
MOCK_JIRA_PROJECT: MOCK_JIRA_PROJECT,
MOCK_JIRA_ISSUE_CREATION_WRONG_PAYLOAD:
MOCK_JIRA_ISSUE_CREATION_WRONG_PAYLOAD,
MOCK_JIRA_ISSUE_CREATION_WRONG_RESPONSE:
MOCK_JIRA_ISSUE_CREATION_WRONG_RESPONSE,
MOCK_JIRA_ISSUE_CREATION_WRONG_PAYLOAD: MOCK_JIRA_ISSUE_CREATION_WRONG_PAYLOAD,
MOCK_JIRA_ISSUE_CREATION_WRONG_RESPONSE: MOCK_JIRA_ISSUE_CREATION_WRONG_RESPONSE,
MOCK_JIRA_ISSUE_TYPE_FILTER: MOCK_JIRA_ISSUE_TYPE_FILTER,
MOCK_JIRA_ISSUE_WRONG_SEARCH_RESPONSE: MOCK_JIRA_ISSUE_WRONG_SEARCH_RESPONSE
MOCK_JIRA_ISSUE_WRONG_SEARCH_RESPONSE: MOCK_JIRA_ISSUE_WRONG_SEARCH_RESPONSE,
MOCK_JQL_SEARCH_PAYLOAD_RESOLVED_ISSUES: MOCK_JQL_SEARCH_PAYLOAD_RESOLVED_ISSUES,
MOCK_JQL_SEARCH_PAYLOAD_OPEN_ISSUES: MOCK_JQL_SEARCH_PAYLOAD_OPEN_ISSUES,
MOCK_JQL_SEARCH_PAYLOAD_RESOLVED_ISSUES_EMPTY: MOCK_JQL_SEARCH_PAYLOAD_RESOLVED_ISSUES_EMPTY,
MOCK_JQL_SEARCH_PAYLOAD_OPEN_ISSUES_EMPTY: MOCK_JQL_SEARCH_PAYLOAD_OPEN_ISSUES_EMPTY
};
Loading

0 comments on commit b89098d

Please sign in to comment.