diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-04-21 02:50:22 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-04-21 02:50:22 +0300 |
commit | 9dc93a4519d9d5d7be48ff274127136236a3adb3 (patch) | |
tree | 70467ae3692a0e35e5ea56bcb803eb512a10bedb /app/assets/javascripts/integrations | |
parent | 4b0f34b6d759d6299322b3a54453e930c6121ff0 (diff) |
Add latest changes from gitlab-org/gitlab@13-11-stable-eev13.11.0-rc43
Diffstat (limited to 'app/assets/javascripts/integrations')
9 files changed, 402 insertions, 51 deletions
diff --git a/app/assets/javascripts/integrations/edit/components/dynamic_field.vue b/app/assets/javascripts/integrations/edit/components/dynamic_field.vue index a4baca20ac9..3655f94f06f 100644 --- a/app/assets/javascripts/integrations/edit/components/dynamic_field.vue +++ b/app/assets/javascripts/integrations/edit/components/dynamic_field.vue @@ -3,7 +3,6 @@ import { GlFormGroup, GlFormCheckbox, GlFormInput, GlFormSelect, GlFormTextarea } from '@gitlab/ui'; import { capitalize, lowerCase, isEmpty } from 'lodash'; import { mapGetters } from 'vuex'; -import { __, sprintf } from '~/locale'; import eventHub from '../event_hub'; export default { @@ -77,14 +76,6 @@ export default { isNonEmptyPassword() { return this.isPassword && !isEmpty(this.value); }, - label() { - if (this.isNonEmptyPassword) { - return sprintf(__('Enter new %{field_title}'), { - field_title: this.humanizedTitle, - }); - } - return this.humanizedTitle; - }, humanizedTitle() { return this.title || capitalize(lowerCase(this.name)); }, @@ -136,7 +127,7 @@ export default { <template> <gl-form-group - :label="label" + :label="humanizedTitle" :label-for="fieldId" :invalid-feedback="__('This field is required.')" :state="valid" diff --git a/app/assets/javascripts/integrations/edit/components/jira_issues_fields.vue b/app/assets/javascripts/integrations/edit/components/jira_issues_fields.vue index d3d1fd8ddc3..aea4a8b1c0b 100644 --- a/app/assets/javascripts/integrations/edit/components/jira_issues_fields.vue +++ b/app/assets/javascripts/integrations/edit/components/jira_issues_fields.vue @@ -1,15 +1,8 @@ <script> -import { - GlFormGroup, - GlFormCheckbox, - GlFormInput, - GlSprintf, - GlLink, - GlButton, - GlCard, -} from '@gitlab/ui'; +import { GlFormGroup, GlFormCheckbox, GlFormInput, GlSprintf, GlLink } from '@gitlab/ui'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import eventHub from '../event_hub'; +import JiraUpgradeCta from './jira_upgrade_cta.vue'; export default { name: 'JiraIssuesFields', @@ -19,8 +12,7 @@ export default { GlFormInput, GlSprintf, GlLink, - GlButton, - GlCard, + JiraUpgradeCta, JiraIssueCreationVulnerabilities: () => import('ee_component/integrations/edit/components/jira_issue_creation_vulnerabilities.vue'), }, @@ -84,11 +76,13 @@ export default { return !this.enableJiraIssues || Boolean(this.projectKey) || !this.validated; }, showJiraVulnerabilitiesOptions() { - return ( - this.enableJiraIssues && - this.showJiraVulnerabilitiesIntegration && - this.glFeatures.jiraForVulnerabilities - ); + return this.showJiraVulnerabilitiesIntegration && this.glFeatures.jiraForVulnerabilities; + }, + showUltimateUpgrade() { + return this.showJiraIssuesIntegration && !this.showJiraVulnerabilitiesIntegration; + }, + showPremiumUpgrade() { + return !this.showJiraIssuesIntegration; }, }, created() { @@ -129,33 +123,29 @@ export default { <template #help> {{ s__( - 'JiraService|Warning: All GitLab users that have access to this GitLab project will be able to view all issues from the Jira project specified below.', + 'JiraService|Warning: All GitLab users that have access to this GitLab project are able to view all issues from the Jira project specified below.', ) }} </template> </gl-form-checkbox> <jira-issue-creation-vulnerabilities - v-if="showJiraVulnerabilitiesOptions" + v-if="enableJiraIssues" :project-key="projectKey" :initial-is-enabled="initialEnableJiraVulnerabilities" :initial-issue-type-id="initialVulnerabilitiesIssuetype" + :show-full-feature="showJiraVulnerabilitiesOptions" data-testid="jira-for-vulnerabilities" @request-get-issue-types="getJiraIssueTypes" /> </template> - <gl-card v-else class="gl-mt-7"> - <strong>{{ __('This is a Premium feature') }}</strong> - <p>{{ __('Upgrade your plan to enable this feature of the Jira Integration.') }}</p> - <gl-button - v-if="upgradePlanPath" - category="primary" - variant="info" - :href="upgradePlanPath" - target="_blank" - > - {{ __('Upgrade your plan') }} - </gl-button> - </gl-card> + <jira-upgrade-cta + v-if="showUltimateUpgrade || showPremiumUpgrade" + class="gl-mt-2" + :class="{ 'gl-ml-6': showUltimateUpgrade }" + :upgrade-plan-path="upgradePlanPath" + :show-ultimate-message="showUltimateUpgrade" + :show-premium-message="showPremiumUpgrade" + /> </div> </gl-form-group> <template v-if="showJiraIssuesIntegration"> @@ -169,7 +159,7 @@ export default { id="service_project_key" v-model="projectKey" name="service[project_key]" - :placeholder="s__('JiraService|e.g. AB')" + :placeholder="s__('JiraService|For example, AB')" :required="enableJiraIssues" :state="validProjectKey" :disabled="!enableJiraIssues" diff --git a/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue b/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue index af4e9acf4ba..b0f19e5b585 100644 --- a/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue +++ b/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue @@ -1,7 +1,16 @@ <script> -import { GlFormGroup, GlFormCheckbox, GlFormRadio } from '@gitlab/ui'; +import { + GlFormGroup, + GlFormCheckbox, + GlFormRadio, + GlFormInput, + GlLink, + GlSprintf, +} from '@gitlab/ui'; import { mapGetters } from 'vuex'; +import { helpPagePath } from '~/helpers/help_page_helper'; import { s__ } from '~/locale'; +import eventHub from '../event_hub'; const commentDetailOptions = [ { @@ -18,12 +27,41 @@ const commentDetailOptions = [ }, ]; +const ISSUE_TRANSITION_AUTO = true; +const ISSUE_TRANSITION_CUSTOM = false; + +const issueTransitionOptions = [ + { + value: ISSUE_TRANSITION_AUTO, + label: s__('JiraService|Move to Done'), + help: s__( + 'JiraService|Automatically transitions Jira issues to the "Done" category. %{linkStart}Learn more%{linkEnd}', + ), + link: helpPagePath('user/project/integrations/jira.html', { + anchor: 'automatic-issue-transitions', + }), + }, + { + value: ISSUE_TRANSITION_CUSTOM, + label: s__('JiraService|Use custom transitions'), + help: s__( + 'JiraService|Set a custom final state by using transition IDs. %{linkStart}Learn about transition IDs%{linkEnd}', + ), + link: helpPagePath('user/project/integrations/jira.html', { + anchor: 'custom-issue-transitions', + }), + }, +]; + export default { name: 'JiraTriggerFields', components: { GlFormGroup, GlFormCheckbox, GlFormRadio, + GlFormInput, + GlLink, + GlSprintf, }, props: { initialTriggerCommit: { @@ -43,21 +81,58 @@ export default { required: false, default: 'standard', }, + initialJiraIssueTransitionAutomatic: { + type: Boolean, + required: false, + default: false, + }, + initialJiraIssueTransitionId: { + type: String, + required: false, + default: '', + }, }, data() { return { + validated: false, triggerCommit: this.initialTriggerCommit, triggerMergeRequest: this.initialTriggerMergeRequest, enableComments: this.initialEnableComments, commentDetail: this.initialCommentDetail, + jiraIssueTransitionAutomatic: + this.initialJiraIssueTransitionAutomatic || !this.initialJiraIssueTransitionId, + jiraIssueTransitionId: this.initialJiraIssueTransitionId, + issueTransitionEnabled: + this.initialJiraIssueTransitionAutomatic || Boolean(this.initialJiraIssueTransitionId), commentDetailOptions, + issueTransitionOptions, }; }, computed: { ...mapGetters(['isInheriting']), - showEnableComments() { + showTriggerSettings() { return this.triggerCommit || this.triggerMergeRequest; }, + validIssueTransitionId() { + return !this.validated || Boolean(this.jiraIssueTransitionId); + }, + }, + created() { + eventHub.$on('validateForm', this.validateForm); + }, + beforeDestroy() { + eventHub.$off('validateForm', this.validateForm); + }, + methods: { + validateForm() { + this.validated = true; + }, + showCustomIssueTransitions(currentOption) { + return ( + this.jiraIssueTransitionAutomatic === ISSUE_TRANSITION_CUSTOM && + currentOption === ISSUE_TRANSITION_CUSTOM + ); + }, }, }; </script> @@ -69,7 +144,7 @@ export default { label-for="service[trigger]" :description=" s__( - 'Integrations|When a Jira issue is mentioned in a commit or merge request a remote link and comment (if enabled) will be created.', + 'Integrations|When a Jira issue is mentioned in a commit or merge request a remote link and comment (if enabled) is created.', ) " > @@ -89,7 +164,7 @@ export default { </gl-form-group> <gl-form-group - v-show="showEnableComments" + v-show="showTriggerSettings" :label="s__('Integrations|Comment settings:')" label-for="service[comment_on_event_enabled]" class="gl-pl-6" @@ -106,7 +181,7 @@ export default { </gl-form-group> <gl-form-group - v-show="showEnableComments && enableComments" + v-show="showTriggerSettings && enableComments" :label="s__('Integrations|Comment detail:')" label-for="service[comment_detail]" class="gl-pl-9" @@ -126,5 +201,67 @@ export default { </template> </gl-form-radio> </gl-form-group> + + <gl-form-group + v-if="showTriggerSettings" + :label="s__('JiraService|Transition Jira issues to their final state:')" + class="gl-pl-6" + data-testid="issue-transition-enabled" + > + <input type="hidden" name="service[jira_issue_transition_automatic]" value="false" /> + <input type="hidden" name="service[jira_issue_transition_id]" value="" /> + + <gl-form-checkbox + v-model="issueTransitionEnabled" + :disabled="isInheriting" + data-qa-selector="service_jira_issue_transition_enabled_checkbox" + > + {{ s__('JiraService|Enable Jira transitions') }} + </gl-form-checkbox> + </gl-form-group> + + <gl-form-group + v-if="showTriggerSettings && issueTransitionEnabled" + class="gl-pl-9" + data-testid="issue-transition-mode" + > + <gl-form-radio + v-for="issueTransitionOption in issueTransitionOptions" + :key="issueTransitionOption.value" + v-model="jiraIssueTransitionAutomatic" + name="service[jira_issue_transition_automatic]" + :value="issueTransitionOption.value" + :disabled="isInheriting" + :data-qa-selector="`service_jira_issue_transition_automatic_${issueTransitionOption.value}_radio`" + > + {{ issueTransitionOption.label }} + + <template v-if="showCustomIssueTransitions(issueTransitionOption.value)"> + <gl-form-input + v-model="jiraIssueTransitionId" + name="service[jira_issue_transition_id]" + type="text" + class="gl-my-3" + data-qa-selector="service_jira_issue_transition_id_field" + :placeholder="s__('JiraService|For example, 12, 24')" + :disabled="isInheriting" + :required="true" + :state="validIssueTransitionId" + /> + + <span class="invalid-feedback"> + {{ __('This field is required.') }} + </span> + </template> + + <template #help> + <gl-sprintf :message="issueTransitionOption.help"> + <template #link="{ content }"> + <gl-link :href="issueTransitionOption.link" target="_blank">{{ content }}</gl-link> + </template> + </gl-sprintf> + </template> + </gl-form-radio> + </gl-form-group> </div> </template> diff --git a/app/assets/javascripts/integrations/edit/components/jira_upgrade_cta.vue b/app/assets/javascripts/integrations/edit/components/jira_upgrade_cta.vue new file mode 100644 index 00000000000..9164e484440 --- /dev/null +++ b/app/assets/javascripts/integrations/edit/components/jira_upgrade_cta.vue @@ -0,0 +1,51 @@ +<script> +import { GlButton, GlCard } from '@gitlab/ui'; +import { s__, __ } from '~/locale'; + +export default { + components: { + GlButton, + GlCard, + }, + props: { + upgradePlanPath: { + type: String, + required: false, + default: '', + }, + showPremiumMessage: { + type: Boolean, + required: false, + default: false, + }, + showUltimateMessage: { + type: Boolean, + required: false, + default: false, + }, + }, + computed: { + title() { + return this.showUltimateMessage + ? this.$options.i18n.titleUltimate + : this.$options.i18n.titlePremium; + }, + }, + i18n: { + titleUltimate: s__('JiraService|This is an Ultimate feature'), + titlePremium: s__('JiraService|This is a Premium feature'), + content: s__('JiraService|Upgrade your plan to enable this feature of the Jira Integration.'), + upgrade: __('Upgrade your plan'), + }, +}; +</script> + +<template> + <gl-card> + <strong>{{ title }}</strong> + <p>{{ $options.i18n.content }}</p> + <gl-button v-if="upgradePlanPath" category="primary" variant="info" :href="upgradePlanPath"> + {{ $options.i18n.upgrade }} + </gl-button> + </gl-card> +</template> diff --git a/app/assets/javascripts/integrations/edit/components/trigger_fields.vue b/app/assets/javascripts/integrations/edit/components/trigger_fields.vue index 1bbecea05ad..42bc9e4c8a1 100644 --- a/app/assets/javascripts/integrations/edit/components/trigger_fields.vue +++ b/app/assets/javascripts/integrations/edit/components/trigger_fields.vue @@ -10,8 +10,8 @@ const typeWithPlaceholder = { }; const placeholderForType = { - [typeWithPlaceholder.SLACK]: __('Slack channels (e.g. general, development)'), - [typeWithPlaceholder.MATTERMOST]: __('Channel handle (e.g. town-square)'), + [typeWithPlaceholder.SLACK]: __('general, development'), + [typeWithPlaceholder.MATTERMOST]: __('my-channel'), }; export default { diff --git a/app/assets/javascripts/integrations/edit/index.js b/app/assets/javascripts/integrations/edit/index.js index ab9bdd9ca2e..792e7d8e85e 100644 --- a/app/assets/javascripts/integrations/edit/index.js +++ b/app/assets/javascripts/integrations/edit/index.js @@ -1,5 +1,6 @@ import Vue from 'vue'; -import { parseBoolean } from '~/lib/utils/common_utils'; +import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils'; + import IntegrationForm from './components/integration_form.vue'; import { createStore } from './store'; @@ -28,6 +29,8 @@ function parseDatasetToProps(data) { testPath, resetPath, vulnerabilitiesIssuetype, + jiraIssueTransitionAutomatic, + jiraIssueTransitionId, ...booleanAttributes } = data; const { @@ -59,6 +62,8 @@ function parseDatasetToProps(data) { initialTriggerMergeRequest: mergeRequestEvents, initialEnableComments: enableComments, initialCommentDetail: commentDetail, + initialJiraIssueTransitionAutomatic: jiraIssueTransitionAutomatic, + initialJiraIssueTransitionId: jiraIssueTransitionId, }, jiraIssuesProps: { showJiraIssuesIntegration, @@ -73,7 +78,7 @@ function parseDatasetToProps(data) { }, learnMorePath, triggerEvents: JSON.parse(triggerEvents), - fields: JSON.parse(fields), + fields: convertObjectPropsToCamelCase(JSON.parse(fields), { deep: true }), inheritFromId: parseInt(inheritFromId, 10), integrationLevel, id: parseInt(id, 10), diff --git a/app/assets/javascripts/integrations/index/components/integrations_list.vue b/app/assets/javascripts/integrations/index/components/integrations_list.vue new file mode 100644 index 00000000000..7331437d484 --- /dev/null +++ b/app/assets/javascripts/integrations/index/components/integrations_list.vue @@ -0,0 +1,59 @@ +<script> +import { s__ } from '~/locale'; +import IntegrationsTable from './integrations_table.vue'; + +export default { + name: 'IntegrationsList', + components: { + IntegrationsTable, + }, + props: { + integrations: { + type: Array, + required: true, + }, + }, + computed: { + integrationsGrouped() { + return this.integrations.reduce( + (integrations, integration) => { + if (integration.active) { + integrations.active.push(integration); + } else { + integrations.inactive.push(integration); + } + + return integrations; + }, + { active: [], inactive: [] }, + ); + }, + }, + i18n: { + activeTableEmptyText: s__("Integrations|You haven't activated any integrations yet."), + inactiveTableEmptyText: s__("Integrations|You've activated every integration 🎉"), + activeIntegrationsHeading: s__('Integrations|Active integrations'), + inactiveIntegrationsHeading: s__('Integrations|Add an integration'), + }, +}; +</script> + +<template> + <div> + <h4>{{ $options.i18n.activeIntegrationsHeading }}</h4> + <integrations-table + class="gl-mb-7!" + :integrations="integrationsGrouped.active" + :empty-text="$options.i18n.activeTableEmptyText" + show-updated-at + data-testid="active-integrations-table" + /> + + <h4>{{ $options.i18n.inactiveIntegrationsHeading }}</h4> + <integrations-table + :integrations="integrationsGrouped.inactive" + :empty-text="$options.i18n.inactiveTableEmptyText" + data-testid="inactive-integrations-table" + /> + </div> +</template> diff --git a/app/assets/javascripts/integrations/index/components/integrations_table.vue b/app/assets/javascripts/integrations/index/components/integrations_table.vue new file mode 100644 index 00000000000..439c243f418 --- /dev/null +++ b/app/assets/javascripts/integrations/index/components/integrations_table.vue @@ -0,0 +1,95 @@ +<script> +import { GlIcon, GlLink, GlTable, GlTooltipDirective } from '@gitlab/ui'; +import { sprintf, s__, __ } from '~/locale'; +import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; + +export default { + components: { + GlIcon, + GlLink, + GlTable, + TimeAgoTooltip, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + props: { + integrations: { + type: Array, + required: true, + }, + showUpdatedAt: { + type: Boolean, + required: false, + default: false, + }, + emptyText: { + type: String, + required: false, + default: undefined, + }, + }, + computed: { + fields() { + return [ + { + key: 'active', + label: '', + thClass: 'gl-w-10', + }, + { + key: 'title', + label: __('Integration'), + thClass: 'gl-w-quarter', + }, + { + key: 'description', + label: __('Description'), + thClass: 'gl-display-none d-sm-table-cell', + tdClass: 'gl-display-none d-sm-table-cell', + }, + { + key: 'updated_at', + label: this.showUpdatedAt ? __('Last updated') : '', + thClass: 'gl-w-20p', + }, + ]; + }, + }, + methods: { + getStatusTooltipTitle(integration) { + return sprintf(s__('Integrations|%{integrationTitle}: active'), { + integrationTitle: integration.title, + }); + }, + }, +}; +</script> + +<template> + <gl-table :items="integrations" :fields="fields" :empty-text="emptyText" show-empty fixed> + <template #cell(active)="{ item }"> + <gl-icon + v-if="item.active" + v-gl-tooltip + name="check" + class="gl-text-green-500" + :title="getStatusTooltipTitle(item)" + /> + </template> + + <template #cell(title)="{ item }"> + <gl-link + :href="item.edit_path" + class="gl-font-weight-bold" + :data-qa-selector="`${item.name}_link`" + > + {{ item.title }} + </gl-link> + </template> + + <template #cell(updated_at)="{ item }"> + <time-ago-tooltip v-if="showUpdatedAt && item.updated_at" :time="item.updated_at" /> + </template> + </gl-table> +</template> diff --git a/app/assets/javascripts/integrations/index/index.js b/app/assets/javascripts/integrations/index/index.js new file mode 100644 index 00000000000..09dca3a6d02 --- /dev/null +++ b/app/assets/javascripts/integrations/index/index.js @@ -0,0 +1,23 @@ +import Vue from 'vue'; +import IntegrationList from './components/integrations_list.vue'; + +export default () => { + const el = document.querySelector('.js-integrations-list'); + + if (!el) { + return null; + } + + const { integrations } = el.dataset; + + return new Vue({ + el, + render(createElement) { + return createElement(IntegrationList, { + props: { + integrations: JSON.parse(integrations), + }, + }); + }, + }); +}; |