testbeats 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,62 @@
1
+ const ENV = process.env;
2
+
3
+ function getCIInformation() {
4
+ if (ENV.GITHUB_ACTIONS) {
5
+ return getGitHubActionsInformation();
6
+ }
7
+ if (ENV.JENKINS_URL) {
8
+ return getJenkinsInformation();
9
+ }
10
+ if (ENV.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI) {
11
+ return getAzureDevOpsInformation();
12
+ }
13
+ }
14
+
15
+ function getGitHubActionsInformation() {
16
+ return {
17
+ ci: 'GITHUB_ACTIONS',
18
+ git: 'GITHUB',
19
+ repository_url: ENV.GITHUB_SERVER_URL + '/' + ENV.GITHUB_REPOSITORY,
20
+ repository_name: ENV.GITHUB_REPOSITORY,
21
+ repository_ref: ENV.GITHUB_REF,
22
+ repository_commit_sha: ENV.GITHUB_SHA,
23
+ build_url: ENV.GITHUB_SERVER_URL + '/' + ENV.GITHUB_REPOSITORY + '/actions/runs/' + ENV.GITHUB_RUN_ID,
24
+ build_number: ENV.GITHUB_RUN_NUMBER,
25
+ build_name: ENV.GITHUB_WORKFLOW,
26
+ user: ENV.GITHUB_ACTOR,
27
+ }
28
+ }
29
+
30
+ function getAzureDevOpsInformation() {
31
+ return {
32
+ ci: 'AZURE_DEVOPS_PIPELINES',
33
+ git: 'AZURE_DEVOPS_REPOS',
34
+ repository_url: ENV.BUILD_REPOSITORY_URI,
35
+ repository_name: ENV.BUILD_REPOSITORY_NAME,
36
+ repository_ref: ENV.BUILD_SOURCEBRANCH,
37
+ repository_commit_sha: ENV.BUILD_SOURCEVERSION,
38
+ build_url: ENV.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI + ENV.SYSTEM_TEAMPROJECT + '/_build/results?buildId=' + ENV.BUILD_BUILDID,
39
+ build_number: ENV.BUILD_BUILDNUMBER,
40
+ build_name: ENV.BUILD_DEFINITIONNAME,
41
+ user: ENV.BUILD_REQUESTEDFOR
42
+ }
43
+ }
44
+
45
+ function getJenkinsInformation() {
46
+ return {
47
+ ci: 'JENKINS',
48
+ git: '',
49
+ repository_url: ENV.GIT_URL || ENV.GITHUB_URL || ENV.BITBUCKET_URL,
50
+ repository_name: ENV.JOB_NAME,
51
+ repository_ref: ENV.BRANCH || ENV.BRANCH_NAME,
52
+ repository_commit_sha: ENV.GIT_COMMIT || ENV.GIT_COMMIT_SHA || ENV.GITHUB_SHA || ENV.BITBUCKET_COMMIT,
53
+ build_url: ENV.BUILD_URL,
54
+ build_number: ENV.BUILD_NUMBER,
55
+ build_name: ENV.JOB_NAME,
56
+ user: ENV.USER || ENV.USERNAME
57
+ }
58
+ }
59
+
60
+ module.exports = {
61
+ getCIInformation
62
+ }
@@ -0,0 +1,45 @@
1
+ const STATUS = Object.freeze({
2
+ PASS: 'pass',
3
+ FAIL: 'fail',
4
+ PASS_OR_FAIL: 'passOrfail'
5
+ });
6
+
7
+ const HOOK = Object.freeze({
8
+ START: 'start',
9
+ AFTER_SUMMARY: 'after-summary',
10
+ END: 'end',
11
+ });
12
+
13
+ const TARGET = Object.freeze({
14
+ SLACK: 'slack',
15
+ TEAMS: 'teams',
16
+ CHAT: 'chat',
17
+ CUSTOM: 'custom',
18
+ DELAY: 'delay',
19
+ INFLUX: 'influx',
20
+ });
21
+
22
+ const EXTENSION = Object.freeze({
23
+ HYPERLINKS: 'hyperlinks',
24
+ MENTIONS: 'mentions',
25
+ REPORT_PORTAL_ANALYSIS: 'report-portal-analysis',
26
+ REPORT_PORTAL_HISTORY: 'report-portal-history',
27
+ QUICK_CHART_TEST_SUMMARY: 'quick-chart-test-summary',
28
+ PERCY_ANALYSIS: 'percy-analysis',
29
+ CUSTOM: 'custom',
30
+ METADATA: 'metadata',
31
+ CI_INFO: 'ci-info',
32
+ });
33
+
34
+ const URLS = Object.freeze({
35
+ PERCY: 'https://percy.io',
36
+ QUICK_CHART: 'https://quickchart.io'
37
+ });
38
+
39
+ module.exports = Object.freeze({
40
+ STATUS,
41
+ HOOK,
42
+ TARGET,
43
+ EXTENSION,
44
+ URLS
45
+ });
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Add Slack Extension function.
3
+ *
4
+ * @param {object} param0 - the payload object
5
+ * @param {object} param0.payload - the payload object
6
+ * @param {import("..").Extension} param0.extension - the extension to add
7
+ * @param {string} param0.text - the text to include
8
+ * @return {void}
9
+ */
10
+ function addSlackExtension({ payload, extension, text }) {
11
+ if (extension.inputs.separator) {
12
+ payload.blocks.push({
13
+ "type": "divider"
14
+ });
15
+ }
16
+ let updated_text = text;
17
+ if (extension.inputs.title) {
18
+ const title = extension.inputs.title_link ? `<${extension.inputs.title_link}|${extension.inputs.title}>` : extension.inputs.title;
19
+ updated_text = `*${title}*\n\n${text}`;
20
+ }
21
+ if (extension.inputs.block_type === 'context') {
22
+ payload.blocks.push({
23
+ "type": "context",
24
+ "elements": [
25
+ {
26
+ "type": "mrkdwn",
27
+ "text": updated_text
28
+ }
29
+ ]
30
+ });
31
+ } else {
32
+ payload.blocks.push({
33
+ "type": "section",
34
+ "text": {
35
+ "type": "mrkdwn",
36
+ "text": updated_text
37
+ }
38
+ });
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Add Teams Extension function.
44
+ *
45
+ * @param {object} param0 - the payload object
46
+ * @param {object} param0.payload - the payload object
47
+ * @param {import("..").Extension} param0.extension - the extension to add
48
+ * @param {string} param0.text - the text to include
49
+ * @return {void}
50
+ */
51
+ function addTeamsExtension({ payload, extension, text }) {
52
+ if (extension.inputs.title) {
53
+ const title = extension.inputs.title_link ? `[${extension.inputs.title}](${extension.inputs.title_link})` : extension.inputs.title
54
+ payload.body.push({
55
+ "type": "TextBlock",
56
+ "text": title,
57
+ "isSubtle": true,
58
+ "weight": "bolder",
59
+ "separator": extension.inputs.separator,
60
+ "wrap": true
61
+ });
62
+ payload.body.push({
63
+ "type": "TextBlock",
64
+ "text": text,
65
+ "wrap": true
66
+ });
67
+ } else {
68
+ payload.body.push({
69
+ "type": "TextBlock",
70
+ "text": text,
71
+ "wrap": true,
72
+ "separator": extension.inputs.separator
73
+ });
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Add Chat Extension function.
79
+ *
80
+ * @param {object} param0 - the payload object
81
+ * @param {object} param0.payload - the payload object
82
+ * @param {import("..").Extension} param0.extension - the extension to add
83
+ * @param {string} param0.text - the text to include
84
+ * @return {void}
85
+ */
86
+ function addChatExtension({ payload, extension, text }) {
87
+ let updated_text = text;
88
+ if (extension.inputs.title) {
89
+ const title = extension.inputs.title_link ? `<a href="${extension.inputs.title_link}">${extension.inputs.title}</a>` : extension.inputs.title;
90
+ updated_text = `<b>${title}</b><br><br>${text}`;
91
+ }
92
+ payload.sections.push({
93
+ "widgets": [
94
+ {
95
+ "textParagraph": {
96
+ "text": updated_text
97
+ }
98
+ }
99
+ ]
100
+ });
101
+ }
102
+
103
+ module.exports = {
104
+ addSlackExtension,
105
+ addTeamsExtension,
106
+ addChatExtension
107
+ }
@@ -0,0 +1,102 @@
1
+ const pretty_ms = require('pretty-ms');
2
+
3
+ const DATA_REF_PATTERN = /(\{[^\}]+\})/g;
4
+ const ALLOWED_CONDITIONS = new Set(['pass', 'fail', 'passorfail']);
5
+ const GENERIC_CONDITIONS = new Set(['always', 'never']);
6
+
7
+ function getPercentage(x, y) {
8
+ if (y > 0) {
9
+ return Math.floor((x / y) * 100);
10
+ }
11
+ return 0;
12
+ }
13
+
14
+ function processText(raw) {
15
+ const dataRefMatches = raw.match(DATA_REF_PATTERN);
16
+ if (dataRefMatches) {
17
+ const values = [];
18
+ for (let i = 0; i < dataRefMatches.length; i++) {
19
+ const dataRefMatch = dataRefMatches[i];
20
+ const content = dataRefMatch.slice(1, -1);
21
+ if (process.env[content]) {
22
+ values.push(process.env[content]);
23
+ } else {
24
+ values.push(content);
25
+ }
26
+ }
27
+ for (let i = 0; i < dataRefMatches.length; i++) {
28
+ raw = raw.replace(dataRefMatches[i], values[i]);
29
+ }
30
+ }
31
+ return raw;
32
+ }
33
+
34
+ /**
35
+ * @returns {import('../index').PublishConfig }
36
+ */
37
+ function processData(data) {
38
+ if (typeof data === 'string') {
39
+ return processText(data);
40
+ }
41
+ if (typeof data === 'object') {
42
+ for (const prop in data) {
43
+ data[prop] = processData(data[prop]);
44
+ }
45
+ }
46
+ return data;
47
+ }
48
+
49
+ function truncate(text, length) {
50
+ if (text && text.length > length) {
51
+ return text.slice(0, length) + "...";
52
+ } else {
53
+ return text;
54
+ }
55
+ }
56
+
57
+ function getPrettyDuration(ms, format) {
58
+ return pretty_ms(parseInt(ms), { [format]: true, secondsDecimalDigits: 0 })
59
+ }
60
+
61
+ function getTitleText({ result, target }) {
62
+ const title = target.inputs.title ? target.inputs.title : result.name;
63
+ if (target.inputs.title_suffix) {
64
+ return `${title} ${target.inputs.title_suffix}`;
65
+ }
66
+ return `${title}`;
67
+ }
68
+
69
+ function getResultText({ result }) {
70
+ const percentage = getPercentage(result.passed, result.total);
71
+ return `${result.passed} / ${result.total} Passed (${percentage}%)`;
72
+ }
73
+
74
+ /**
75
+ *
76
+ * @param {object} param0
77
+ * @param {string | Function} param0.condition
78
+ */
79
+ async function checkCondition({ condition, result, target, extension }) {
80
+ if (typeof condition === 'function') {
81
+ return await condition({ target, result, extension });
82
+ } else {
83
+ const lower_condition = condition.toLowerCase();
84
+ if (ALLOWED_CONDITIONS.has(lower_condition)) {
85
+ return lower_condition.includes(result.status.toLowerCase());
86
+ } else if (GENERIC_CONDITIONS.has(lower_condition)) {
87
+ return lower_condition === 'always';
88
+ } else {
89
+ return eval(condition);
90
+ }
91
+ }
92
+ }
93
+
94
+ module.exports = {
95
+ getPercentage,
96
+ processData,
97
+ truncate,
98
+ getPrettyDuration,
99
+ getTitleText,
100
+ getResultText,
101
+ checkCondition,
102
+ }
@@ -0,0 +1,115 @@
1
+ const { checkCondition } = require('./helper')
2
+
3
+ /**
4
+ * Asynchronously generates metadata text for slack.
5
+ *
6
+ * @param {object} param0 - the payload object
7
+ * @param {Object} param0.elements - The elements to generate metadata text from
8
+ * @param {import('..').Target} param0.target - The result object
9
+ * @param {import('..').Extension} param0.extension - The result object
10
+ * @param {Object} param0.result - The result object
11
+ * @param {string} param0.default_condition - The default condition object
12
+ * @return {string} The generated metadata text
13
+ */
14
+ async function getSlackMetaDataText({ elements, target, extension, result, default_condition }) {
15
+ const items = [];
16
+ for (const element of elements) {
17
+ if (await is_valid({ element, result, default_condition })) {
18
+ if (element.type === 'hyperlink') {
19
+ const url = await get_url({ url: element.value, target, result, extension });
20
+ if (element.label) {
21
+ items.push(`*${element.label}:* <${url}|${element.key}>`);
22
+ } else {
23
+ items.push(`<${url}|${element.key}>`);
24
+ }
25
+ } else if (element.key) {
26
+ items.push(`*${element.key}:* ${element.value}`);
27
+ } else {
28
+ items.push(element.value);
29
+ }
30
+ }
31
+ }
32
+ return items.join(' | ');
33
+ }
34
+
35
+ /**
36
+ * Asynchronously generates metadata text for teams.
37
+ *
38
+ * @param {object} param0 - the payload object
39
+ * @param {Object} param0.elements - The elements to generate metadata text from
40
+ * @param {import('..').Target} param0.target - The result object
41
+ * @param {import('..').Extension} param0.extension - The result object
42
+ * @param {Object} param0.result - The result object
43
+ * @param {string} param0.default_condition - The default condition object
44
+ * @return {string} The generated metadata text
45
+ */
46
+ async function getTeamsMetaDataText({ elements, target, extension, result, default_condition }) {
47
+ const items = [];
48
+ for (const element of elements) {
49
+ if (await is_valid({ element, result, default_condition })) {
50
+ if (element.type === 'hyperlink') {
51
+ const url = await get_url({ url: element.value, target, result, extension });
52
+ if (element.label) {
53
+ items.push(`**${element.label}:** [${element.key}](${url})`);
54
+ } else {
55
+ items.push(`[${element.key}](${url})`);
56
+ }
57
+ } else if (element.key) {
58
+ items.push(`**${element.key}:** ${element.value}`);
59
+ } else {
60
+ items.push(element.value);
61
+ }
62
+ }
63
+ }
64
+ return items.join(' | ');
65
+ }
66
+
67
+ /**
68
+ * Asynchronously generates metadata text for chat.
69
+ *
70
+ * @param {object} param0 - the payload object
71
+ * @param {Object} param0.elements - The elements to generate metadata text from
72
+ * @param {import('..').Target} param0.target - The result object
73
+ * @param {import('..').Extension} param0.extension - The result object
74
+ * @param {Object} param0.result - The result object
75
+ * @param {string} param0.default_condition - The default condition object
76
+ * @return {string} The generated metadata text
77
+ */
78
+ async function getChatMetaDataText({ elements, target, extension, result, default_condition }) {
79
+ const items = [];
80
+ for (const element of elements) {
81
+ if (await is_valid({ element, result, default_condition })) {
82
+ if (element.type === 'hyperlink') {
83
+ const url = await get_url({ url: element.value, target, result, extension });
84
+ if (element.label) {
85
+ items.push(`<b>${element.label}:</b> <a href="${url}">${element.key}</a>`);
86
+ } else {
87
+ items.push(`<a href="${url}">${element.key}</a>`);
88
+ }
89
+ } else if (element.key) {
90
+ items.push(`<b>${element.key}:</b> ${element.value}`);
91
+ } else {
92
+ items.push(element.value);
93
+ }
94
+ }
95
+ }
96
+ return items.join(' | ');
97
+ }
98
+
99
+ function is_valid({ element, result, default_condition }) {
100
+ const condition = element.condition || default_condition;
101
+ return checkCondition({ condition, result });
102
+ }
103
+
104
+ function get_url({ url, target, extension, result}) {
105
+ if (typeof url === 'function') {
106
+ return url({target, extension, result});
107
+ }
108
+ return url;
109
+ }
110
+
111
+ module.exports = {
112
+ getSlackMetaDataText,
113
+ getTeamsMetaDataText,
114
+ getChatMetaDataText
115
+ }
@@ -0,0 +1,60 @@
1
+ const request = require('phin-retry');
2
+
3
+ /**
4
+ * @param {import('../index').PercyAnalysisInputs} inputs
5
+ */
6
+ async function getProjectByName(inputs) {
7
+ return request.get({
8
+ url: `${inputs.url}/api/v1/projects`,
9
+ headers: {
10
+ 'Authorization': `Token ${inputs.token}`
11
+ },
12
+ form: {
13
+ 'project_slug': inputs.project_name
14
+ }
15
+ });
16
+ }
17
+
18
+ /**
19
+ * @param {import('../index').PercyAnalysisInputs} inputs
20
+ */
21
+ async function getLastBuild(inputs) {
22
+ return request.get({
23
+ url: `${inputs.url}/api/v1/builds?project_id=${inputs.project_id}&page[limit]=1`,
24
+ headers: {
25
+ 'Authorization': `Token ${inputs.token}`
26
+ }
27
+ });
28
+ }
29
+
30
+ /**
31
+ * @param {import('../index').PercyAnalysisInputs} inputs
32
+ */
33
+ async function getBuild(inputs) {
34
+ return request.get({
35
+ url: `${inputs.url}/api/v1/builds/${inputs.build_id}`,
36
+ headers: {
37
+ 'Authorization': `Token ${inputs.token}`
38
+ }
39
+ });
40
+ }
41
+
42
+ /**
43
+ * @param {import('../index').PercyAnalysisInputs} inputs
44
+ */
45
+ async function getRemovedSnapshots(inputs) {
46
+ return request.get({
47
+ url: `${inputs.url}/api/v1/builds/${inputs.build_id}/removed-snapshots`,
48
+ headers: {
49
+ 'Authorization': `Token ${inputs.token}`
50
+ }
51
+ });
52
+ }
53
+
54
+
55
+ module.exports = {
56
+ getProjectByName,
57
+ getLastBuild,
58
+ getBuild,
59
+ getRemovedSnapshots
60
+ }
@@ -0,0 +1,141 @@
1
+ const Metric = require("performance-results-parser/src/models/Metric");
2
+ const { checkCondition } = require("./helper");
3
+ const pretty_ms = require('pretty-ms');
4
+
5
+ /**
6
+ * @param {object} param0
7
+ * @param {Metric[]} param0.metrics
8
+ * @param {object} param0.result
9
+ * @param {object} param0.target
10
+ */
11
+ async function getValidMetrics({ metrics, result, target }) {
12
+ if (target.inputs.metrics && target.inputs.metrics.length > 0) {
13
+ const valid_metrics = [];
14
+ for (let i = 0; i < metrics.length; i++) {
15
+ const metric = metrics[i];
16
+ for (let j = 0; j < target.inputs.metrics.length; j++) {
17
+ const metric_config = target.inputs.metrics[j];
18
+ if (metric.name === metric_config.name) {
19
+ const include = await checkCondition({ condition: metric_config.condition || 'always', result, target });
20
+ if (include) valid_metrics.push(metric);
21
+ }
22
+ }
23
+ }
24
+ return valid_metrics;
25
+ }
26
+ return metrics;
27
+ }
28
+
29
+ /**
30
+ * @param {Metric} metric
31
+ * @param {string[]} fields
32
+ */
33
+ function getCounterMetricFieldValue(metric, fields) {
34
+ let value = '';
35
+ if (fields.includes('sum')) {
36
+ const sum_failure = metric.failures.find(_failure => _failure.field === 'sum');
37
+ if (sum_failure) {
38
+ const emoji = getEmoji(sum_failure.difference);
39
+ value = `${emoji} ${metric['sum']} (${getDifferenceSymbol(sum_failure.difference)}${sum_failure.difference}) `
40
+ } else {
41
+ value = `${metric['sum']} `;
42
+ }
43
+ }
44
+ if (fields.includes('rate')) {
45
+ let metric_unit = metric.unit.startsWith('/') ? metric.unit : ` ${metric.unit}`;
46
+ const rate_failure = metric.failures.find(_failure => _failure.field === 'rate');
47
+ if (rate_failure) {
48
+ const emoji = getEmoji(rate_failure.difference);
49
+ value += `${emoji} ${metric['rate']}${metric_unit} (${getDifferenceSymbol(rate_failure.difference)}${rate_failure.difference})`
50
+ } else {
51
+ value += `${metric['rate']}${metric_unit}`;
52
+ }
53
+ }
54
+ return value;
55
+ }
56
+
57
+ /**
58
+ * @param {Metric} metric
59
+ */
60
+ function getTrendMetricFieldValue(metric, field) {
61
+ const failure = metric.failures.find(_failure => _failure.field === field);
62
+ if (failure) {
63
+ const emoji = getEmoji(failure.difference);
64
+ return `${emoji} ${field}=${pretty_ms(metric[field])} (${getDifferenceSymbol(failure.difference)}${pretty_ms(failure.difference)})`
65
+ }
66
+ return `${field}=${pretty_ms(metric[field])}`;
67
+ }
68
+
69
+ /**
70
+ * @param {Metric} metric
71
+ */
72
+ function getRateMetricFieldValue(metric) {
73
+ const failure = metric.failures.find(_failure => _failure.field === 'rate');
74
+ if (failure) {
75
+ const emoji = getEmoji(failure.difference);
76
+ return `${emoji} ${metric['rate']} ${metric.unit} (${getDifferenceSymbol(failure.difference)}${failure.difference})`;
77
+ }
78
+ return `${metric['rate']} ${metric.unit}`;
79
+ }
80
+
81
+ function getEmoji(value) {
82
+ return value > 0 ? '🔺' : '🔻';
83
+ }
84
+
85
+ function getDifferenceSymbol(value) {
86
+ return value > 0 ? `+` : '';
87
+ }
88
+
89
+ /**
90
+ *
91
+ * @param {object} param0
92
+ * @param {Metric} param0.metric
93
+ */
94
+ function getDisplayFields({ metric, target }) {
95
+ let fields = [];
96
+ if (target.inputs.metrics) {
97
+ const metric_config = target.inputs.metrics.find(_metric => _metric.name === metric.name);
98
+ if (metric_config) {
99
+ fields = metric_config.fields;
100
+ }
101
+ }
102
+ if (fields && fields.length > 0) {
103
+ return fields;
104
+ } else {
105
+ switch (metric.type) {
106
+ case 'COUNTER':
107
+ return ['sum', 'rate'];
108
+ case 'RATE':
109
+ return ['rate'];
110
+ case 'TREND':
111
+ return ['avg', 'min', 'med', 'max', 'p90', 'p95', 'p99'];
112
+ default:
113
+ return ['sum', 'min', 'max'];
114
+ }
115
+ }
116
+ }
117
+
118
+ /**
119
+ * @param {object} param0
120
+ * @param {Metric} param0.metric
121
+ */
122
+ function getMetricValuesText({ metric, target }) {
123
+ const fields = getDisplayFields({ metric, target });
124
+ const values = [];
125
+ if (metric.type === 'COUNTER') {
126
+ values.push(getCounterMetricFieldValue(metric, fields));
127
+ } else if (metric.type === 'TREND') {
128
+ for (let i = 0; i < fields.length; i++) {
129
+ const field_metric = fields[i];
130
+ values.push(getTrendMetricFieldValue(metric, field_metric));
131
+ }
132
+ } else if (metric.type === 'RATE') {
133
+ values.push(getRateMetricFieldValue(metric));
134
+ }
135
+ return values.join(' | ');
136
+ }
137
+
138
+ module.exports = {
139
+ getValidMetrics,
140
+ getMetricValuesText
141
+ }
@@ -0,0 +1,47 @@
1
+ const request = require('phin-retry');
2
+
3
+ async function getLaunchDetails(options) {
4
+ return request.get({
5
+ url: `${options.url}/api/v1/${options.project}/launch/${options.launch_id}`,
6
+ headers: {
7
+ 'Authorization': `Bearer ${options.api_key}`
8
+ }
9
+ });
10
+ }
11
+
12
+ async function getLaunchesByName(options) {
13
+ return request.get({
14
+ url: `${options.url}/api/v1/${options.project}/launch?filter.eq.name=${options.launch_name}&page.size=1&page.sort=startTime%2Cdesc`,
15
+ headers: {
16
+ 'Authorization': `Bearer ${options.api_key}`
17
+ }
18
+ });
19
+ }
20
+
21
+ async function getLastLaunchByName(options) {
22
+ const response = await getLaunchesByName(options);
23
+ if (response.content && response.content.length > 0) {
24
+ return response.content[0];
25
+ }
26
+ return null;
27
+ }
28
+
29
+ async function getSuiteHistory(options) {
30
+ return request.get({
31
+ url: `${options.url}/api/v1/${options.project}/item/history`,
32
+ qs: {
33
+ historyDepth: options.history_depth,
34
+ 'filter.eq.launchId': options.launch_id,
35
+ 'filter.!ex.parentId': 'true'
36
+ },
37
+ headers: {
38
+ 'Authorization': `Bearer ${options.api_key}`
39
+ }
40
+ });
41
+ }
42
+
43
+ module.exports = {
44
+ getLaunchDetails,
45
+ getLastLaunchByName,
46
+ getSuiteHistory
47
+ }