Commit 56b86fec authored by Emily Ring's avatar Emily Ring Committed by Natalia Tepluhina

Add terraform plan component to javascript

Adds new terraform vue component.
It will display as part of the MR Widgets.

Added tests for new terraform component
parent f70adc9b
<script>
import { __ } from '~/locale';
import { GlIcon, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils';
import CiIcon from '../../vue_shared/components/ci_icon.vue';
import flash from '~/flash';
import Poll from '~/lib/utils/poll';
import Visibility from 'visibilityjs';
export default {
name: 'MRWidgetTerraformPlan',
components: {
CiIcon,
GlIcon,
GlLoadingIcon,
GlSprintf,
},
props: {
endpoint: {
type: String,
required: true,
},
},
data() {
return {
loading: true,
plans: {},
};
},
computed: {
addNum() {
return Number(this.plan.create);
},
changeNum() {
return Number(this.plan.update);
},
deleteNum() {
return Number(this.plan.delete);
},
iconStatusObj() {
return {
group: 'warning',
icon: 'status_warning',
};
},
logUrl() {
return this.plan.job_path;
},
plan() {
return this.plans['tfplan.json'] || {};
},
validPlanValues() {
return this.addNum + this.changeNum + this.deleteNum >= 0;
},
},
created() {
this.fetchPlans();
},
methods: {
fetchPlans() {
this.loading = true;
const poll = new Poll({
resource: {
fetchPlans: () => axios.get(this.endpoint),
},
data: this.endpoint,
method: 'fetchPlans',
successCallback: ({ data }) => {
this.plans = data;
this.loading = false;
},
errorCallback: () => {
this.plans = {};
this.loading = false;
flash(__('An error occurred while loading terraform report'));
},
});
if (!Visibility.hidden()) {
poll.makeRequest();
}
Visibility.change(() => {
if (!Visibility.hidden()) {
poll.restart();
} else {
poll.stop();
}
});
},
},
};
</script>
<template>
<section class="mr-widget-section">
<div class="mr-widget-body media d-flex flex-row">
<span class="append-right-default align-self-start align-self-lg-center">
<ci-icon :status="iconStatusObj" :size="24" />
</span>
<div class="d-flex flex-fill flex-column flex-md-row">
<div class="terraform-mr-plan-text normal d-flex flex-column flex-lg-row">
<p class="m-0 pr-1">{{ __('A terraform report was generated in your pipelines.') }}</p>
<gl-loading-icon v-if="loading" size="md" />
<p v-else-if="validPlanValues" class="m-0">
<gl-sprintf
:message="
__(
'Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete',
)
"
>
<template #addNum>
<strong>{{ addNum }}</strong>
</template>
<template #changeNum>
<strong>{{ changeNum }}</strong>
</template>
<template #deleteNum>
<strong>{{ deleteNum }}</strong>
</template>
</gl-sprintf>
</p>
<p v-else class="m-0">{{ __('Changes are unknown') }}</p>
</div>
<div class="terraform-mr-plan-actions">
<a
v-if="logUrl"
:href="logUrl"
target="_blank"
data-track-event="click_terraform_mr_plan_button"
data-track-label="mr_widget_terraform_mr_plan_button"
data-track-property="terraform_mr_plan_button"
class="btn btn-sm js-terraform-report-link"
rel="noopener"
>
{{ __('View full log') }}
<gl-icon name="external-link" />
</a>
</div>
</div>
</div>
</section>
</template>
...@@ -36,6 +36,7 @@ import CheckingState from './components/states/mr_widget_checking.vue'; ...@@ -36,6 +36,7 @@ import CheckingState from './components/states/mr_widget_checking.vue';
import eventHub from './event_hub'; import eventHub from './event_hub';
import notify from '~/lib/utils/notify'; import notify from '~/lib/utils/notify';
import SourceBranchRemovalStatus from './components/source_branch_removal_status.vue'; import SourceBranchRemovalStatus from './components/source_branch_removal_status.vue';
import TerraformPlan from './components/mr_widget_terraform_plan.vue';
import GroupedTestReportsApp from '../reports/components/grouped_test_reports_app.vue'; import GroupedTestReportsApp from '../reports/components/grouped_test_reports_app.vue';
import { setFaviconOverlay } from '../lib/utils/common_utils'; import { setFaviconOverlay } from '../lib/utils/common_utils';
...@@ -74,6 +75,7 @@ export default { ...@@ -74,6 +75,7 @@ export default {
'mr-widget-rebase': RebaseState, 'mr-widget-rebase': RebaseState,
SourceBranchRemovalStatus, SourceBranchRemovalStatus,
GroupedTestReportsApp, GroupedTestReportsApp,
TerraformPlan,
}, },
props: { props: {
mrData: { mrData: {
...@@ -379,6 +381,8 @@ export default { ...@@ -379,6 +381,8 @@ export default {
:endpoint="mr.testResultsPath" :endpoint="mr.testResultsPath"
/> />
<terraform-plan v-if="mr.terraformReportsPath" :endpoint="mr.terraformReportsPath" />
<div class="mr-widget-section"> <div class="mr-widget-section">
<component :is="componentName" :mr="mr" :service="service" /> <component :is="componentName" :mr="mr" :service="service" />
......
...@@ -101,6 +101,7 @@ export default class MergeRequestStore { ...@@ -101,6 +101,7 @@ export default class MergeRequestStore {
this.isPipelineActive = data.pipeline ? data.pipeline.active : false; this.isPipelineActive = data.pipeline ? data.pipeline.active : false;
this.isPipelineBlocked = pipelineStatus ? pipelineStatus.group === 'manual' : false; this.isPipelineBlocked = pipelineStatus ? pipelineStatus.group === 'manual' : false;
this.ciStatusFaviconPath = pipelineStatus ? pipelineStatus.favicon : null; this.ciStatusFaviconPath = pipelineStatus ? pipelineStatus.favicon : null;
this.terraformReportsPath = data.terraform_reports_path;
this.testResultsPath = data.test_reports_path; this.testResultsPath = data.test_reports_path;
this.exposedArtifactsPath = data.exposed_artifacts_path; this.exposedArtifactsPath = data.exposed_artifacts_path;
this.cancelAutoMergePath = data.cancel_auto_merge_path; this.cancelAutoMergePath = data.cancel_auto_merge_path;
......
---
title: Add terraform report to merge request widget
merge_request: 27700
author:
type: added
...@@ -340,11 +340,15 @@ export default { ...@@ -340,11 +340,15 @@ export default {
:security-approvals-help-page-path="mr.securityApprovalsHelpPagePath" :security-approvals-help-page-path="mr.securityApprovalsHelpPagePath"
report-section-class="mr-widget-border-top" report-section-class="mr-widget-border-top"
/> />
<grouped-test-reports-app <grouped-test-reports-app
v-if="mr.testResultsPath" v-if="mr.testResultsPath"
class="js-reports-container" class="js-reports-container"
:endpoint="mr.testResultsPath" :endpoint="mr.testResultsPath"
/> />
<terraform-plan v-if="mr.terraformReportsPath" :endpoint="mr.terraformReportsPath" />
<div class="mr-widget-section"> <div class="mr-widget-section">
<component :is="componentName" :mr="mr" :service="service" /> <component :is="componentName" :mr="mr" :service="service" />
......
---
title: Add terraform report to merge request widget
merge_request: 27700
author:
type: added
...@@ -888,6 +888,9 @@ msgstr "" ...@@ -888,6 +888,9 @@ msgstr ""
msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project." msgid "A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project."
msgstr "" msgstr ""
msgid "A terraform report was generated in your pipelines."
msgstr ""
msgid "A user with write access to the source branch selected this option" msgid "A user with write access to the source branch selected this option"
msgstr "" msgstr ""
...@@ -2008,6 +2011,9 @@ msgstr "" ...@@ -2008,6 +2011,9 @@ msgstr ""
msgid "An error occurred while loading merge requests." msgid "An error occurred while loading merge requests."
msgstr "" msgstr ""
msgid "An error occurred while loading terraform report"
msgstr ""
msgid "An error occurred while loading the data. Please try again." msgid "An error occurred while loading the data. Please try again."
msgstr "" msgstr ""
...@@ -3526,6 +3532,9 @@ msgstr "" ...@@ -3526,6 +3532,9 @@ msgstr ""
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision." msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr "" msgstr ""
msgid "Changes are unknown"
msgstr ""
msgid "Changes suppressed. Click to show." msgid "Changes suppressed. Click to show."
msgstr "" msgstr ""
...@@ -17019,6 +17028,9 @@ msgstr "" ...@@ -17019,6 +17028,9 @@ msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}" msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr "" msgstr ""
msgid "Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
msgstr ""
msgid "Reporter" msgid "Reporter"
msgstr "" msgstr ""
...@@ -22694,6 +22706,9 @@ msgstr "" ...@@ -22694,6 +22706,9 @@ msgstr ""
msgid "View full dashboard" msgid "View full dashboard"
msgstr "" msgstr ""
msgid "View full log"
msgstr ""
msgid "View group labels" msgid "View group labels"
msgstr "" msgstr ""
......
import { GlLoadingIcon, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import axios from '~/lib/utils/axios_utils';
import MockAdapter from 'axios-mock-adapter';
import MrWidgetTerraformPlan from '~/vue_merge_request_widget/components/mr_widget_terraform_plan.vue';
const plan = {
create: 10,
update: 20,
delete: 30,
job_path: '/path/to/ci/logs',
};
describe('MrWidgetTerraformPlan', () => {
let mock;
let wrapper;
const propsData = { endpoint: '/path/to/terraform/report.json' };
const mockPollingApi = (response, body, header) => {
mock.onGet(propsData.endpoint).reply(response, body, header);
};
const mountWrapper = () => {
wrapper = shallowMount(MrWidgetTerraformPlan, { propsData });
return axios.waitForAll();
};
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
wrapper.destroy();
mock.restore();
});
describe('loading poll', () => {
beforeEach(() => {
mockPollingApi(200, { 'tfplan.json': plan }, {});
return mountWrapper().then(() => {
wrapper.setData({ loading: true });
return wrapper.vm.$nextTick();
});
});
it('Diplays loading icon when loading is true', () => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
expect(wrapper.find(GlSprintf).exists()).toBe(false);
expect(wrapper.text()).not.toContain(
'A terraform report was generated in your pipelines. Changes are unknown',
);
});
});
describe('successful poll', () => {
beforeEach(() => {
mockPollingApi(200, { 'tfplan.json': plan }, {});
return mountWrapper();
});
it('content change text', () => {
expect(wrapper.find(GlSprintf).exists()).toBe(true);
});
it('renders button when url is found', () => {
expect(wrapper.find('a').text()).toContain('View full log');
});
});
describe('polling fails', () => {
beforeEach(() => {
mockPollingApi(500, null, {});
return mountWrapper();
});
it('does not display changes text when api fails', () => {
expect(wrapper.text()).toContain(
'A terraform report was generated in your pipelines. Changes are unknown',
);
expect(wrapper.find('.js-terraform-report-link').exists()).toBe(false);
expect(wrapper.text()).not.toContain('View full log');
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment