diff options
81 files changed, 1708 insertions, 1549 deletions
@@ -419,7 +419,7 @@ end # Gitaly GRPC client gem 'gitaly-proto', '~> 0.118.1', require: 'gitaly' -gem 'grpc', '~> 1.11.0' +gem 'grpc', '~> 1.15.0' # Locked until https://github.com/google/protobuf/issues/4210 is closed gem 'google-protobuf', '= 3.5.1' diff --git a/Gemfile.lock b/Gemfile.lock index bd95f417caa..8317f980eec 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -323,15 +323,14 @@ GEM representable (~> 3.0) retriable (>= 2.0, < 4.0) google-protobuf (3.5.1) - googleapis-common-protos-types (1.0.1) + googleapis-common-protos-types (1.0.2) google-protobuf (~> 3.0) - googleauth (0.6.2) + googleauth (0.6.6) faraday (~> 0.12) jwt (>= 1.4, < 3.0) - logging (~> 2.0) memoist (~> 0.12) multi_json (~> 1.11) - os (~> 0.9) + os (>= 0.9, < 2.0) signet (~> 0.7) gpgme (2.0.13) mini_portile2 (~> 2.1) @@ -355,10 +354,9 @@ GEM railties sprockets-rails graphql (1.8.1) - grpc (1.11.0) + grpc (1.15.0) google-protobuf (~> 3.1) googleapis-common-protos-types (~> 1.0.0) - googleauth (>= 0.5.1, < 0.7) haml (5.0.4) temple (>= 0.8.0) tilt @@ -460,11 +458,7 @@ GEM xml-simple licensee (8.9.2) rugged (~> 0.24) - little-plugger (1.1.4) locale (2.1.2) - logging (2.2.2) - little-plugger (~> 1.1) - multi_json (~> 1.10) lograge (0.10.0) actionpack (>= 4) activesupport (>= 4) @@ -570,7 +564,7 @@ GEM org-ruby (0.9.12) rubypants (~> 0.2) orm_adapter (0.5.0) - os (0.9.6) + os (1.0.0) parallel (1.12.1) parser (2.5.1.0) ast (~> 2.4.0) @@ -838,7 +832,7 @@ GEM sidekiq-cron (0.6.0) rufus-scheduler (>= 3.3.0) sidekiq (>= 4.2.1) - signet (0.8.1) + signet (0.11.0) addressable (~> 2.3) faraday (~> 0.9) jwt (>= 1.5, < 3.0) @@ -1041,7 +1035,7 @@ DEPENDENCIES grape_logging (~> 1.7) graphiql-rails (~> 1.4.10) graphql (~> 1.8.0) - grpc (~> 1.11.0) + grpc (~> 1.15.0) haml_lint (~> 0.26.0) hamlit (~> 2.8.8) hangouts-chat (~> 0.0.5) diff --git a/Gemfile.rails5.lock b/Gemfile.rails5.lock index 8aebf78924f..36bbf403f01 100644 --- a/Gemfile.rails5.lock +++ b/Gemfile.rails5.lock @@ -326,15 +326,14 @@ GEM representable (~> 3.0) retriable (>= 2.0, < 4.0) google-protobuf (3.5.1) - googleapis-common-protos-types (1.0.1) + googleapis-common-protos-types (1.0.2) google-protobuf (~> 3.0) - googleauth (0.6.2) + googleauth (0.6.6) faraday (~> 0.12) jwt (>= 1.4, < 3.0) - logging (~> 2.0) memoist (~> 0.12) multi_json (~> 1.11) - os (~> 0.9) + os (>= 0.9, < 2.0) signet (~> 0.7) gpgme (2.0.13) mini_portile2 (~> 2.1) @@ -358,10 +357,9 @@ GEM railties sprockets-rails graphql (1.8.1) - grpc (1.11.0) + grpc (1.15.0) google-protobuf (~> 3.1) googleapis-common-protos-types (~> 1.0.0) - googleauth (>= 0.5.1, < 0.7) haml (5.0.4) temple (>= 0.8.0) tilt @@ -463,11 +461,7 @@ GEM xml-simple licensee (8.9.2) rugged (~> 0.24) - little-plugger (1.1.4) locale (2.1.2) - logging (2.2.2) - little-plugger (~> 1.1) - multi_json (~> 1.10) lograge (0.10.0) actionpack (>= 4) activesupport (>= 4) @@ -574,7 +568,7 @@ GEM org-ruby (0.9.12) rubypants (~> 0.2) orm_adapter (0.5.0) - os (0.9.6) + os (1.0.0) parallel (1.12.1) parser (2.5.1.0) ast (~> 2.4.0) @@ -846,7 +840,7 @@ GEM sidekiq-cron (0.6.0) rufus-scheduler (>= 3.3.0) sidekiq (>= 4.2.1) - signet (0.8.1) + signet (0.11.0) addressable (~> 2.3) faraday (~> 0.9) jwt (>= 1.5, < 3.0) @@ -1050,7 +1044,7 @@ DEPENDENCIES grape_logging (~> 1.7) graphiql-rails (~> 1.4.10) graphql (~> 1.8.0) - grpc (~> 1.11.0) + grpc (~> 1.15.0) haml_lint (~> 0.26.0) hamlit (~> 2.8.8) hangouts-chat (~> 0.0.5) diff --git a/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js b/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js index d519c033769..25a65b047f1 100644 --- a/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js +++ b/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js @@ -13,6 +13,7 @@ export default { }, [types.SET_SELECTED_TEMPLATE_TYPE](state, type) { state.selectedTemplateType = type; + state.templates = []; }, [types.SET_UPDATE_SUCCESS](state, success) { state.updateSuccess = success; diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js index 250980919dd..7cc7cd6d20e 100644 --- a/app/assets/javascripts/lib/utils/text_utility.js +++ b/app/assets/javascripts/lib/utils/text_utility.js @@ -53,10 +53,7 @@ export const slugify = str => str.trim().toLowerCase(); * @param {String} str * @returns {String} */ -export const slugifyWithHyphens = str => { - const regex = new RegExp(/\s+/, 'g'); - return str.toLowerCase().replace(regex, '-'); -}; +export const slugifyWithHyphens = str => str.toLowerCase().replace(/\s+/g, '-'); /** * Truncates given text diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue index 70518ad73e8..9161f703697 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue @@ -22,9 +22,7 @@ export default { directives: { tooltip, }, - mixins: [ - timeagoMixin, - ], + mixins: [timeagoMixin], props: { deployment: { type: Object, @@ -50,7 +48,7 @@ export default { return !!(this.deployment.url && this.deployment.name); }, hasMetrics() { - return !!(this.deployment.metrics_url); + return !!this.deployment.metrics_url; }, }, methods: { @@ -63,7 +61,7 @@ export default { MRWidgetService.stopEnvironment(this.deployment.stop_url) .then(res => res.data) - .then((data) => { + .then(data => { if (data.redirect_url) { visitUrl(data.redirect_url); } diff --git a/app/assets/javascripts/vue_merge_request_widget/components/memory_usage.vue b/app/assets/javascripts/vue_merge_request_widget/components/memory_usage.vue index 5e76f6b1cac..41dbc5c9cbb 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/memory_usage.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/memory_usage.vue @@ -60,11 +60,29 @@ export default { let memoryUsageMsg = ''; if (memoryTo > memoryFrom) { - memoryUsageMsg = sprintf(s__('mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB'), messageProps, false); + memoryUsageMsg = sprintf( + s__( + 'mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB', + ), + messageProps, + false, + ); } else if (memoryTo < memoryFrom) { - memoryUsageMsg = sprintf(s__('mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB'), messageProps, false); + memoryUsageMsg = sprintf( + s__( + 'mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB', + ), + messageProps, + false, + ); } else { - memoryUsageMsg = sprintf(s__('mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB'), messageProps, false); + memoryUsageMsg = sprintf( + s__( + 'mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB', + ), + messageProps, + false, + ); } return memoryUsageMsg; @@ -77,7 +95,7 @@ export default { methods: { getMegabytes(bytesString) { const valueInBytes = Number(bytesString).toFixed(2); - return (bytesToMiB(valueInBytes)).toFixed(2); + return bytesToMiB(valueInBytes).toFixed(2); }, computeGraphData(metrics, deploymentTime) { this.loadingMetrics = false; @@ -103,7 +121,7 @@ export default { loadMetrics() { backOff((next, stop) => { MRWidgetService.fetchMetrics(this.metricsUrl) - .then((res) => { + .then(res => { if (res.status === statusCodes.NO_CONTENT) { this.backOffRequestCounter += 1; /* eslint-disable no-unused-expressions */ @@ -114,14 +132,14 @@ export default { }) .catch(stop); }) - .then((res) => { + .then(res => { if (res.status === statusCodes.NO_CONTENT) { return res; } return res.data; }) - .then((data) => { + .then(data => { this.computeGraphData(data.metrics, data.deployment_time); return data; }) diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue index 22c2f74f900..2ad9e8be655 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue @@ -1,36 +1,36 @@ <script> - import tooltip from '../../vue_shared/directives/tooltip'; +import tooltip from '../../vue_shared/directives/tooltip'; - export default { - name: 'MrWidgetAuthor', - directives: { - tooltip, +export default { + name: 'MrWidgetAuthor', + directives: { + tooltip, + }, + props: { + author: { + type: Object, + required: true, }, - props: { - author: { - type: Object, - required: true, - }, - showAuthorName: { - type: Boolean, - required: false, - default: true, - }, - showAuthorTooltip: { - type: Boolean, - required: false, - default: false, - }, + showAuthorName: { + type: Boolean, + required: false, + default: true, }, - computed: { - authorUrl() { - return this.author.webUrl || this.author.web_url; - }, - avatarUrl() { - return this.author.avatarUrl || this.author.avatar_url; - }, + showAuthorTooltip: { + type: Boolean, + required: false, + default: false, }, - }; + }, + computed: { + authorUrl() { + return this.author.webUrl || this.author.web_url; + }, + avatarUrl() { + return this.author.avatarUrl || this.author.avatar_url; + }, + }, +}; </script> <template> <a diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue index ba16cb9e2c8..1d902131f49 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue @@ -1,34 +1,34 @@ <script> - import tooltip from '~/vue_shared/directives/tooltip'; - import MrWidgetAuthor from './mr_widget_author.vue'; +import tooltip from '~/vue_shared/directives/tooltip'; +import MrWidgetAuthor from './mr_widget_author.vue'; - export default { - name: 'MrWidgetAuthorTime', - components: { - MrWidgetAuthor, +export default { + name: 'MrWidgetAuthorTime', + components: { + MrWidgetAuthor, + }, + directives: { + tooltip, + }, + props: { + actionText: { + type: String, + required: true, }, - directives: { - tooltip, + author: { + type: Object, + required: true, }, - props: { - actionText: { - type: String, - required: true, - }, - author: { - type: Object, - required: true, - }, - dateTitle: { - type: String, - required: true, - }, - dateReadable: { - type: String, - required: true, - }, + dateTitle: { + type: String, + required: true, }, - }; + dateReadable: { + type: String, + required: true, + }, + }, +}; </script> <template> <h4 class="js-mr-widget-author"> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue index 62b61e1f41f..37c6af13c03 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue @@ -1,24 +1,26 @@ <script> - import { sprintf, s__ } from '~/locale'; +import { sprintf, s__ } from '~/locale'; - export default { - name: 'MRWidgetMergeHelp', - props: { - missingBranch: { - type: String, - required: false, - default: '', - }, +export default { + name: 'MRWidgetMergeHelp', + props: { + missingBranch: { + type: String, + required: false, + default: '', }, - computed: { - missingBranchInfo() { - return sprintf( - s__('mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the'), - { branch: this.missingBranch }, - ); - }, + }, + computed: { + missingBranchInfo() { + return sprintf( + s__( + 'mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the', + ), + { branch: this.missingBranch }, + ); }, - }; + }, +}; </script> <template> <section class="mr-widget-help"> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue index 88d0fcd70f5..cc77b96a589 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue @@ -1,32 +1,32 @@ <script> - import { s__ } from '~/locale'; +import { s__ } from '~/locale'; - export default { - name: 'MRWidgetRelatedLinks', - props: { - relatedLinks: { - type: Object, - required: true, - default: () => ({}), - }, - state: { - type: String, - required: false, - default: '', - }, +export default { + name: 'MRWidgetRelatedLinks', + props: { + relatedLinks: { + type: Object, + required: true, + default: () => ({}), }, - computed: { - closesText() { - if (this.state === 'merged') { - return s__('mrWidget|Closed'); - } - if (this.state === 'closed') { - return s__('mrWidget|Did not close'); - } - return s__('mrWidget|Closes'); - }, + state: { + type: String, + required: false, + default: '', }, - }; + }, + computed: { + closesText() { + if (this.state === 'merged') { + return s__('mrWidget|Closed'); + } + if (this.state === 'closed') { + return s__('mrWidget|Did not close'); + } + return s__('mrWidget|Closes'); + }, + }, +}; </script> <template> <section class="mr-info-list mr-links"> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue index 035ae791a1d..ba6a1687e51 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue @@ -1,33 +1,33 @@ <script> - import ciIcon from '../../vue_shared/components/ci_icon.vue'; +import ciIcon from '../../vue_shared/components/ci_icon.vue'; - export default { - components: { - ciIcon, +export default { + components: { + ciIcon, + }, + props: { + status: { + type: String, + required: true, }, - props: { - status: { - type: String, - required: true, - }, - showDisabledButton: { - type: Boolean, - required: false, - default: false, - }, + showDisabledButton: { + type: Boolean, + required: false, + default: false, }, - computed: { - isLoading() { - return this.status === 'loading'; - }, - statusObj() { - return { - group: this.status, - icon: `status_${this.status}`, - }; - }, + }, + computed: { + isLoading() { + return this.status === 'loading'; }, - }; + statusObj() { + return { + group: this.status, + icon: `status_${this.status}`, + }; + }, + }, +}; </script> <template> <div class="space-children d-flex append-right-10 widget-status-icon"> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue b/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue index 56879c04d16..01f707163d4 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue @@ -1,16 +1,16 @@ <script> - import tooltip from '../../vue_shared/directives/tooltip'; - import { __ } from '../../locale'; +import tooltip from '../../vue_shared/directives/tooltip'; +import { __ } from '../../locale'; - export default { - directives: { - tooltip, - }, - created() { - this.removesBranchText = __('<strong>Removes</strong> source branch'); - this.tooltipTitle = __('A user with write access to the source branch selected this option'); - }, - }; +export default { + directives: { + tooltip, + }, + created() { + this.removesBranchText = __('<strong>Removes</strong> source branch'); + this.tooltipTitle = __('A user with write access to the source branch selected this option'); + }, +}; </script> <template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue index cfbd44d41b2..2a76a878448 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue @@ -1,12 +1,12 @@ <script> - import statusIcon from '../mr_widget_status_icon.vue'; +import statusIcon from '../mr_widget_status_icon.vue'; - export default { - name: 'MRWidgetArchived', - components: { - statusIcon, - }, - }; +export default { + name: 'MRWidgetArchived', + components: { + statusIcon, + }, +}; </script> <template> <div class="mr-widget-body media"> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue index 01294d5b40c..4f8b07484c0 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue @@ -1,32 +1,32 @@ <script> - import eventHub from '../../event_hub'; - import statusIcon from '../mr_widget_status_icon.vue'; +import eventHub from '../../event_hub'; +import statusIcon from '../mr_widget_status_icon.vue'; - export default { - name: 'MRWidgetAutoMergeFailed', - components: { - statusIcon, +export default { + name: 'MRWidgetAutoMergeFailed', + components: { + statusIcon, + }, + props: { + mr: { + type: Object, + required: true, }, - props: { - mr: { - type: Object, - required: true, - }, + }, + data() { + return { + isRefreshing: false, + }; + }, + methods: { + refreshWidget() { + this.isRefreshing = true; + eventHub.$emit('MRWidgetUpdateRequested', () => { + this.isRefreshing = false; + }); }, - data() { - return { - isRefreshing: false, - }; - }, - methods: { - refreshWidget() { - this.isRefreshing = true; - eventHub.$emit('MRWidgetUpdateRequested', () => { - this.isRefreshing = false; - }); - }, - }, - }; + }, +}; </script> <template> <div class="mr-widget-body media"> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue index ae6630dcd6f..fdf0a9fd994 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue @@ -1,12 +1,12 @@ <script> - import statusIcon from '../mr_widget_status_icon.vue'; +import statusIcon from '../mr_widget_status_icon.vue'; - export default { - name: 'MRWidgetChecking', - components: { - statusIcon, - }, - }; +export default { + name: 'MRWidgetChecking', + components: { + statusIcon, + }, +}; </script> <template> <div class="mr-widget-body media"> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue index 25a57e520ee..f06eab95c7e 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue @@ -1,23 +1,23 @@ <script> - import MrWidgetAuthorTime from '../../components/mr_widget_author_time.vue'; - import statusIcon from '../mr_widget_status_icon.vue'; +import MrWidgetAuthorTime from '../../components/mr_widget_author_time.vue'; +import statusIcon from '../mr_widget_status_icon.vue'; - export default { - name: 'MRWidgetClosed', - components: { - MrWidgetAuthorTime, - statusIcon, - }, - props: { - /* TODO: This is providing all store and service down when it +export default { + name: 'MRWidgetClosed', + components: { + MrWidgetAuthorTime, + statusIcon, + }, + props: { + /* TODO: This is providing all store and service down when it only needs metrics and targetBranch */ - mr: { - type: Object, - required: true, - default: () => ({}), - }, + mr: { + type: Object, + required: true, + default: () => ({}), }, - }; + }, +}; </script> <template> <div class="mr-widget-body media"> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue index dff9ec657b9..8c808296178 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue @@ -1,21 +1,21 @@ <script> - import statusIcon from '../mr_widget_status_icon.vue'; +import statusIcon from '../mr_widget_status_icon.vue'; - export default { - name: 'MRWidgetConflicts', - components: { - statusIcon, - }, - props: { - /* TODO: This is providing all store and service down when it +export default { + name: 'MRWidgetConflicts', + components: { + statusIcon, + }, + props: { + /* TODO: This is providing all store and service down when it only needs a few props */ - mr: { - type: Object, - required: true, - default: () => ({}), - }, + mr: { + type: Object, + required: true, + default: () => ({}), }, - }; + }, +}; </script> <template> <div class="mr-widget-body media"> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue index 97f4196b94d..484b5600d63 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue @@ -1,82 +1,82 @@ <script> - import Flash from '../../../flash'; - import statusIcon from '../mr_widget_status_icon.vue'; - import MrWidgetAuthor from '../../components/mr_widget_author.vue'; - import eventHub from '../../event_hub'; +import Flash from '../../../flash'; +import statusIcon from '../mr_widget_status_icon.vue'; +import MrWidgetAuthor from '../../components/mr_widget_author.vue'; +import eventHub from '../../event_hub'; - export default { - name: 'MRWidgetMergeWhenPipelineSucceeds', - components: { - MrWidgetAuthor, - statusIcon, +export default { + name: 'MRWidgetMergeWhenPipelineSucceeds', + components: { + MrWidgetAuthor, + statusIcon, + }, + props: { + mr: { + type: Object, + required: true, + default: () => ({}), }, - props: { - mr: { - type: Object, - required: true, - default: () => ({}), - }, - service: { - type: Object, - required: true, - default: () => ({}), - }, + service: { + type: Object, + required: true, + default: () => ({}), }, - data() { - return { - isCancellingAutoMerge: false, - isRemovingSourceBranch: false, - }; - }, - computed: { - canRemoveSourceBranch() { - const { - shouldRemoveSourceBranch, - canRemoveSourceBranch, - mergeUserId, - currentUserId, - } = this.mr; + }, + data() { + return { + isCancellingAutoMerge: false, + isRemovingSourceBranch: false, + }; + }, + computed: { + canRemoveSourceBranch() { + const { + shouldRemoveSourceBranch, + canRemoveSourceBranch, + mergeUserId, + currentUserId, + } = this.mr; - return !shouldRemoveSourceBranch && - canRemoveSourceBranch && - mergeUserId === currentUserId; - }, + return !shouldRemoveSourceBranch && canRemoveSourceBranch && mergeUserId === currentUserId; }, - methods: { - cancelAutomaticMerge() { - this.isCancellingAutoMerge = true; - this.service.cancelAutomaticMerge() - .then(res => res.data) - .then((data) => { - eventHub.$emit('UpdateWidgetData', data); - }) - .catch(() => { - this.isCancellingAutoMerge = false; - Flash('Something went wrong. Please try again.'); - }); - }, - removeSourceBranch() { - const options = { - sha: this.mr.sha, - merge_when_pipeline_succeeds: true, - should_remove_source_branch: true, - }; + }, + methods: { + cancelAutomaticMerge() { + this.isCancellingAutoMerge = true; + this.service + .cancelAutomaticMerge() + .then(res => res.data) + .then(data => { + eventHub.$emit('UpdateWidgetData', data); + }) + .catch(() => { + this.isCancellingAutoMerge = false; + Flash('Something went wrong. Please try again.'); + }); + }, + removeSourceBranch() { + const options = { + sha: this.mr.sha, + merge_when_pipeline_succeeds: true, + should_remove_source_branch: true, + }; - this.isRemovingSourceBranch = true; - this.service.merge(options) - .then(res => res.data) - .then((data) => { - if (data.status === 'merge_when_pipeline_succeeds') { - eventHub.$emit('MRWidgetUpdateRequested'); - } - }) - .catch(() => { - this.isRemovingSourceBranch = false; - Flash('Something went wrong. Please try again.'); - }); - }, + this.isRemovingSourceBranch = true; + this.service + .merge(options) + .then(res => res.data) + .then(data => { + if (data.status === 'merge_when_pipeline_succeeds') { + eventHub.$emit('MRWidgetUpdateRequested'); + } + }) + .catch(() => { + this.isRemovingSourceBranch = false; + Flash('Something went wrong. Please try again.'); + }); }, - }; + }, +}; </script> <template> <div class="mr-widget-body media"> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue index c19b67f00fa..656c3b5c47e 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue @@ -1,106 +1,100 @@ <script> - import Flash from '~/flash'; - import tooltip from '~/vue_shared/directives/tooltip'; - import { s__, __ } from '~/locale'; - import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; - import MrWidgetAuthorTime from '../../components/mr_widget_author_time.vue'; - import statusIcon from '../mr_widget_status_icon.vue'; - import eventHub from '../../event_hub'; +import Flash from '~/flash'; +import tooltip from '~/vue_shared/directives/tooltip'; +import { s__, __ } from '~/locale'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; +import MrWidgetAuthorTime from '../../components/mr_widget_author_time.vue'; +import statusIcon from '../mr_widget_status_icon.vue'; +import eventHub from '../../event_hub'; - export default { - name: 'MRWidgetMerged', - directives: { - tooltip, +export default { + name: 'MRWidgetMerged', + directives: { + tooltip, + }, + components: { + MrWidgetAuthorTime, + statusIcon, + ClipboardButton, + }, + props: { + mr: { + type: Object, + required: true, + default: () => ({}), }, - components: { - MrWidgetAuthorTime, - statusIcon, - ClipboardButton, + service: { + type: Object, + required: true, + default: () => ({}), }, - props: { - mr: { - type: Object, - required: true, - default: () => ({}), - }, - service: { - type: Object, - required: true, - default: () => ({}), - }, + }, + data() { + return { + isMakingRequest: false, + }; + }, + computed: { + shouldShowRemoveSourceBranch() { + const { sourceBranchRemoved, isRemovingSourceBranch, canRemoveSourceBranch } = this.mr; + + return ( + !sourceBranchRemoved && + canRemoveSourceBranch && + !this.isMakingRequest && + !isRemovingSourceBranch + ); }, - data() { - return { - isMakingRequest: false, - }; + shouldShowSourceBranchRemoving() { + const { sourceBranchRemoved, isRemovingSourceBranch } = this.mr; + return !sourceBranchRemoved && (isRemovingSourceBranch || this.isMakingRequest); }, - computed: { - shouldShowRemoveSourceBranch() { - const { - sourceBranchRemoved, - isRemovingSourceBranch, - canRemoveSourceBranch, - } = this.mr; - - return !sourceBranchRemoved && - canRemoveSourceBranch && - !this.isMakingRequest && - !isRemovingSourceBranch; - }, - shouldShowSourceBranchRemoving() { - const { - sourceBranchRemoved, - isRemovingSourceBranch, - } = this.mr; - return !sourceBranchRemoved && - (isRemovingSourceBranch || this.isMakingRequest); - }, - shouldShowMergedButtons() { - const { - canRevertInCurrentMR, - canCherryPickInCurrentMR, - revertInForkPath, - cherryPickInForkPath, - } = this.mr; + shouldShowMergedButtons() { + const { + canRevertInCurrentMR, + canCherryPickInCurrentMR, + revertInForkPath, + cherryPickInForkPath, + } = this.mr; - return canRevertInCurrentMR || - canCherryPickInCurrentMR || - revertInForkPath || - cherryPickInForkPath; - }, - revertTitle() { - return s__('mrWidget|Revert this merge request in a new merge request'); - }, - cherryPickTitle() { - return s__('mrWidget|Cherry-pick this merge request in a new merge request'); - }, - revertLabel() { - return s__('mrWidget|Revert'); - }, - cherryPickLabel() { - return s__('mrWidget|Cherry-pick'); - }, + return ( + canRevertInCurrentMR || canCherryPickInCurrentMR || revertInForkPath || cherryPickInForkPath + ); + }, + revertTitle() { + return s__('mrWidget|Revert this merge request in a new merge request'); + }, + cherryPickTitle() { + return s__('mrWidget|Cherry-pick this merge request in a new merge request'); + }, + revertLabel() { + return s__('mrWidget|Revert'); + }, + cherryPickLabel() { + return s__('mrWidget|Cherry-pick'); }, - methods: { - removeSourceBranch() { - this.isMakingRequest = true; + }, + methods: { + removeSourceBranch() { + this.isMakingRequest = true; - this.service.removeSourceBranch() - .then(res => res.data) - .then((data) => { - if (data.message === 'Branch was removed') { - eventHub.$emit('MRWidgetUpdateRequested', () => { - this.isMakingRequest = false; - }); - } - }) - .catch(() => { - this.isMakingRequest = false; - Flash(__('Something went wrong. Please try again.')); - }); - }, + this.service + .removeSourceBranch() + .then(res => res.data) + .then(data => { + if (data.message === 'Branch was removed') { + eventHub.$emit('MRWidgetUpdateRequested', () => { + this.isMakingRequest = false; + }); + } + }) + .catch(() => { + this.isMakingRequest = false; + Flash(__('Something went wrong. Please try again.')); + }); }, - }; + }, +}; </script> <template> <div class="mr-widget-body media"> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue index 953ddf40a51..139e64d1878 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue @@ -1,19 +1,19 @@ <script> - import statusIcon from '../mr_widget_status_icon.vue'; +import statusIcon from '../mr_widget_status_icon.vue'; - export default { - name: 'MRWidgetMerging', - components: { - statusIcon, +export default { + name: 'MRWidgetMerging', + components: { + statusIcon, + }, + props: { + mr: { + type: Object, + required: true, + default: () => ({}), }, - props: { - mr: { - type: Object, - required: true, - default: () => ({}), - }, - }, - }; + }, +}; </script> <template> <div class="mr-widget-body mr-state-locked media"> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue index b0e96f74626..227e9b85f9d 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue @@ -1,40 +1,48 @@ <script> - import { sprintf, s__ } from '~/locale'; - import tooltip from '~/vue_shared/directives/tooltip'; - import statusIcon from '../mr_widget_status_icon.vue'; - import mrWidgetMergeHelp from '../../components/mr_widget_merge_help.vue'; +import { sprintf, s__ } from '~/locale'; +import tooltip from '~/vue_shared/directives/tooltip'; +import statusIcon from '../mr_widget_status_icon.vue'; +import mrWidgetMergeHelp from '../../components/mr_widget_merge_help.vue'; - export default { - name: 'MRWidgetMissingBranch', - directives: { - tooltip, +export default { + name: 'MRWidgetMissingBranch', + directives: { + tooltip, + }, + components: { + mrWidgetMergeHelp, + statusIcon, + }, + props: { + mr: { + type: Object, + required: true, }, - components: { - mrWidgetMergeHelp, - statusIcon, + }, + computed: { + missingBranchName() { + return this.mr.sourceBranchRemoved ? 'source' : 'target'; }, - props: { - mr: { - type: Object, - required: true, - }, - }, - computed: { - missingBranchName() { - return this.mr.sourceBranchRemoved ? 'source' : 'target'; - }, - missingBranchNameMessage() { - return sprintf(s__('mrWidget| Please restore it or use a different %{missingBranchName} branch'), { + missingBranchNameMessage() { + return sprintf( + s__('mrWidget| Please restore it or use a different %{missingBranchName} branch'), + { missingBranchName: this.missingBranchName, - }); - }, - message() { - return sprintf(s__('mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line'), { + }, + ); + }, + message() { + return sprintf( + s__( + 'mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line', + ), + { missingBranchName: this.missingBranchName, - }); - }, + }, + ); }, - }; + }, +}; </script> <template> <div class="mr-widget-body media"> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue index 92eee2cf5dd..360559ae0f0 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue @@ -1,12 +1,12 @@ <script> - import StatusIcon from '../mr_widget_status_icon.vue'; +import StatusIcon from '../mr_widget_status_icon.vue'; - export default { - name: 'MRWidgetNotAllowed', - components: { - StatusIcon, - }, - }; +export default { + name: 'MRWidgetNotAllowed', + components: { + StatusIcon, + }, +}; </script> <template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue index 37ee5215cea..a4eb5afb21c 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue @@ -1,12 +1,12 @@ <script> - import StatusIcon from '../mr_widget_status_icon.vue'; +import StatusIcon from '../mr_widget_status_icon.vue'; - export default { - name: 'MRWidgetPipelineBlocked', - components: { - StatusIcon, - }, - }; +export default { + name: 'MRWidgetPipelineBlocked', + components: { + StatusIcon, + }, +}; </script> <template> <div class="mr-widget-body media"> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue index f31c7a3edb8..041fa13a8f5 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue @@ -1,85 +1,87 @@ <script> - import simplePoll from '../../../lib/utils/simple_poll'; - import eventHub from '../../event_hub'; - import statusIcon from '../mr_widget_status_icon.vue'; - import Flash from '../../../flash'; +import simplePoll from '../../../lib/utils/simple_poll'; +import eventHub from '../../event_hub'; +import statusIcon from '../mr_widget_status_icon.vue'; +import Flash from '../../../flash'; - export default { - name: 'MRWidgetRebase', - components: { - statusIcon, +export default { + name: 'MRWidgetRebase', + components: { + statusIcon, + }, + props: { + mr: { + type: Object, + required: true, }, - props: { - mr: { - type: Object, - required: true, - }, - service: { - type: Object, - required: true, - }, + service: { + type: Object, + required: true, }, - data() { - return { - isMakingRequest: false, - rebasingError: null, - }; + }, + data() { + return { + isMakingRequest: false, + rebasingError: null, + }; + }, + computed: { + status() { + if (this.mr.rebaseInProgress || this.isMakingRequest) { + return 'loading'; + } + if (!this.mr.canPushToSourceBranch && !this.mr.rebaseInProgress) { + return 'warning'; + } + return 'success'; }, - computed: { - status() { - if (this.mr.rebaseInProgress || this.isMakingRequest) { - return 'loading'; - } - if (!this.mr.canPushToSourceBranch && !this.mr.rebaseInProgress) { - return 'warning'; - } - return 'success'; - }, - showDisabledButton() { - return ['failed', 'loading'].includes(this.status); - }, + showDisabledButton() { + return ['failed', 'loading'].includes(this.status); }, - methods: { - rebase() { - this.isMakingRequest = true; - this.rebasingError = null; + }, + methods: { + rebase() { + this.isMakingRequest = true; + this.rebasingError = null; - this.service.rebase() - .then(() => { - simplePoll(this.checkRebaseStatus); - }) - .catch((error) => { - this.rebasingError = error.merge_error; + this.service + .rebase() + .then(() => { + simplePoll(this.checkRebaseStatus); + }) + .catch(error => { + this.rebasingError = error.merge_error; + this.isMakingRequest = false; + Flash('Something went wrong. Please try again.'); + }); + }, + checkRebaseStatus(continuePolling, stopPolling) { + this.service + .poll() + .then(res => res.data) + .then(res => { + if (res.rebase_in_progress) { + continuePolling(); + } else { this.isMakingRequest = false; - Flash('Something went wrong. Please try again.'); - }); - }, - checkRebaseStatus(continuePolling, stopPolling) { - this.service.poll() - .then(res => res.data) - .then((res) => { - if (res.rebase_in_progress) { - continuePolling(); - } else { - this.isMakingRequest = false; - - if (res.merge_error && res.merge_error.length) { - this.rebasingError = res.merge_error; - Flash('Something went wrong. Please try again.'); - } - eventHub.$emit('MRWidgetUpdateRequested'); - stopPolling(); + if (res.merge_error && res.merge_error.length) { + this.rebasingError = res.merge_error; + Flash('Something went wrong. Please try again.'); } - }) - .catch(() => { - this.isMakingRequest = false; - Flash('Something went wrong. Please try again.'); + + eventHub.$emit('MRWidgetUpdateRequested'); stopPolling(); - }); - }, + } + }) + .catch(() => { + this.isMakingRequest = false; + Flash('Something went wrong. Please try again.'); + stopPolling(); + }); }, - }; + }, +}; </script> <template> <div class="mr-widget-body media"> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue index 89c9a41f316..9129fcbb918 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue @@ -24,9 +24,10 @@ export default { methods: { removeWIP() { this.isMakingRequest = true; - this.service.removeWIP() + this.service + .removeWIP() .then(res => res.data) - .then((data) => { + .then(data => { eventHub.$emit('UpdateWidgetData', data); new window.Flash('The merge request can now be merged.', 'notice'); // eslint-disable-line $('.merge-request .detail-page-description .title').text(this.mr.title); diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue index 0e445a29de4..7ac3fedb2e3 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue @@ -1,5 +1,4 @@ <script> - import Project from '~/pages/projects/project'; import SmartInterval from '~/smart_interval'; import createFlash from '../flash'; @@ -100,8 +99,11 @@ export default { return !!this.mr.relatedLinks && !this.mr.isNothingToMergeState; }, shouldRenderSourceBranchRemovalStatus() { - return !this.mr.canRemoveSourceBranch && this.mr.shouldRemoveSourceBranch && - (!this.mr.isNothingToMergeState && !this.mr.isMergedState); + return ( + !this.mr.canRemoveSourceBranch && + this.mr.shouldRemoveSourceBranch && + (!this.mr.isNothingToMergeState && !this.mr.isMergedState) + ); }, }, created() { @@ -133,9 +135,10 @@ export default { return new MRWidgetService(endpoints); }, checkStatus(cb) { - return this.service.checkStatus() + return this.service + .checkStatus() .then(res => res.data) - .then((data) => { + .then(data => { this.handleNotification(data); this.mr.setData(data); this.setFaviconHelper(); @@ -172,20 +175,24 @@ export default { return Promise.resolve(); }, fetchDeployments() { - return this.service.fetchDeployments() + return this.service + .fetchDeployments() .then(res => res.data) - .then((data) => { + .then(data => { if (data.length) { this.mr.deployments = data; } }) .catch(() => { - createFlash('Something went wrong while fetching the environments for this merge request. Please try again.'); + createFlash( + 'Something went wrong while fetching the environments for this merge request. Please try again.', + ); }); }, fetchActionsContent() { - this.service.fetchMergeActionsContent() - .then((res) => { + this.service + .fetchMergeActionsContent() + .then(res => { if (res.data) { const el = document.createElement('div'); el.innerHTML = res.data; @@ -212,22 +219,22 @@ export default { this.pollingInterval.stopTimer(); }, bindEventHubListeners() { - eventHub.$on('MRWidgetUpdateRequested', (cb) => { + eventHub.$on('MRWidgetUpdateRequested', cb => { this.checkStatus(cb); }); // `params` should be an Array contains a Boolean, like `[true]` // Passing parameter as Boolean didn't work. - eventHub.$on('SetBranchRemoveFlag', (params) => { + eventHub.$on('SetBranchRemoveFlag', params => { [this.mr.isRemovingSourceBranch] = params; }); - eventHub.$on('FailedToMerge', (mergeError) => { + eventHub.$on('FailedToMerge', mergeError => { this.mr.state = 'failedToMerge'; this.mr.mergeError = mergeError; }); - eventHub.$on('UpdateWidgetData', (data) => { + eventHub.$on('UpdateWidgetData', data => { this.mr.setData(data); }); @@ -268,11 +275,12 @@ export default { :key="deployment.id" :deployment="deployment" /> + <grouped-test-reports-app + v-if="mr.testResultsPath" + class="js-reports-container" + :endpoint="mr.testResultsPath" + /> <div class="mr-section-container"> - <grouped-test-reports-app - v-if="mr.testResultsPath" - :endpoint="mr.testResultsPath" - /> <div class="mr-widget-section"> <component :is="componentName" diff --git a/app/assets/javascripts/vue_shared/components/bar_chart.vue b/app/assets/javascripts/vue_shared/components/bar_chart.vue index 33af7a7f1df..690dd794ba4 100644 --- a/app/assets/javascripts/vue_shared/components/bar_chart.vue +++ b/app/assets/javascripts/vue_shared/components/bar_chart.vue @@ -118,7 +118,9 @@ export default { this.rectYAxisLabelDims.height != null ? this.rectYAxisLabelDims.height / 2 : 0; const yCoord = this.vbHeight / 2 + rectWidth - 5; - return `translate(${this.minX + this.yAxisTextTransformPadding}, ${yCoord}) rotate(-${this.yAxisTextRotation})`; + return `translate(${this.minX + this.yAxisTextTransformPadding}, ${yCoord}) rotate(-${ + this.yAxisTextRotation + })`; }, }, mounted() { @@ -207,8 +209,7 @@ export default { renderedYAxis.selectAll('.tick').each(function createTickLines(d, i) { if (i > 0) { - d3 - .select(this) + d3.select(this) .select('line') .attr('x2', width) .attr('class', 'axis-tick'); @@ -217,8 +218,7 @@ export default { // Add the panning capabilities if (this.isPanAvailable) { - d3 - .select(this.$refs.baseSvg) + d3.select(this.$refs.baseSvg) .call(this.zoom) .on('wheel.zoom', null); // This disables the pan of the graph with the scroll of the mouse wheel } diff --git a/app/assets/javascripts/vue_shared/components/deprecated_modal.vue b/app/assets/javascripts/vue_shared/components/deprecated_modal.vue index 9c1e5c68649..4d63296e332 100644 --- a/app/assets/javascripts/vue_shared/components/deprecated_modal.vue +++ b/app/assets/javascripts/vue_shared/components/deprecated_modal.vue @@ -1,85 +1,85 @@ <script> - /* eslint-disable vue/require-default-prop */ - export default { - name: 'DeprecatedModal', // use GlModal instead +/* eslint-disable vue/require-default-prop */ +export default { + name: 'DeprecatedModal', // use GlModal instead - props: { - id: { - type: String, - required: false, - }, - title: { - type: String, - required: false, - }, - text: { - type: String, - required: false, - }, - hideFooter: { - type: Boolean, - required: false, - default: false, - }, - kind: { - type: String, - required: false, - default: 'primary', - }, - modalDialogClass: { - type: String, - required: false, - default: '', - }, - closeKind: { - type: String, - required: false, - default: 'default', - }, - closeButtonLabel: { - type: String, - required: false, - default: 'Cancel', - }, - primaryButtonLabel: { - type: String, - required: false, - default: '', - }, - secondaryButtonLabel: { - type: String, - required: false, - default: '', - }, - submitDisabled: { - type: Boolean, - required: false, - default: false, - }, + props: { + id: { + type: String, + required: false, }, + title: { + type: String, + required: false, + }, + text: { + type: String, + required: false, + }, + hideFooter: { + type: Boolean, + required: false, + default: false, + }, + kind: { + type: String, + required: false, + default: 'primary', + }, + modalDialogClass: { + type: String, + required: false, + default: '', + }, + closeKind: { + type: String, + required: false, + default: 'default', + }, + closeButtonLabel: { + type: String, + required: false, + default: 'Cancel', + }, + primaryButtonLabel: { + type: String, + required: false, + default: '', + }, + secondaryButtonLabel: { + type: String, + required: false, + default: '', + }, + submitDisabled: { + type: Boolean, + required: false, + default: false, + }, + }, - computed: { - btnKindClass() { - return { - [`btn-${this.kind}`]: true, - }; - }, - btnCancelKindClass() { - return { - [`btn-${this.closeKind}`]: true, - }; - }, + computed: { + btnKindClass() { + return { + [`btn-${this.kind}`]: true, + }; + }, + btnCancelKindClass() { + return { + [`btn-${this.closeKind}`]: true, + }; }, + }, - methods: { - emitCancel(event) { - this.$emit('cancel', event); - }, - emitSubmit(event) { - this.$emit('submit', event); - }, + methods: { + emitCancel(event) { + this.$emit('cancel', event); + }, + emitSubmit(event) { + this.$emit('submit', event); }, - }; + }, +}; </script> <template> diff --git a/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js b/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js index 9bca1993ccc..19611b14be4 100644 --- a/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js +++ b/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js @@ -583,7 +583,5 @@ const fileNameIcons = { }; export default function getIconForFile(name) { - return fileNameIcons[name] || - fileExtensionIcons[name ? name.split('.').pop() : ''] || - ''; + return fileNameIcons[name] || fileExtensionIcons[name ? name.split('.').pop() : ''] || ''; } diff --git a/app/assets/javascripts/vue_shared/components/icon.vue b/app/assets/javascripts/vue_shared/components/icon.vue index fc80e89d505..26f9d5ddc91 100644 --- a/app/assets/javascripts/vue_shared/components/icon.vue +++ b/app/assets/javascripts/vue_shared/components/icon.vue @@ -1,5 +1,4 @@ <script> - // only allow classes in images.scss e.g. s12 const validSizes = [8, 10, 12, 16, 18, 24, 32, 48, 72]; let iconValidator = () => true; diff --git a/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue b/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue index 62f1ab27c1e..dc88749c18f 100644 --- a/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue +++ b/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue @@ -1,34 +1,34 @@ <script> - import icon from '../../../vue_shared/components/icon.vue'; +import icon from '../../../vue_shared/components/icon.vue'; - export default { - components: { - icon, +export default { + components: { + icon, + }, + props: { + isLocked: { + type: Boolean, + default: false, + required: false, }, - props: { - isLocked: { - type: Boolean, - default: false, - required: false, - }, - isConfidential: { - type: Boolean, - default: false, - required: false, - }, + isConfidential: { + type: Boolean, + default: false, + required: false, }, - computed: { - warningIcon() { - if (this.isConfidential) return 'eye-slash'; - if (this.isLocked) return 'lock'; + }, + computed: { + warningIcon() { + if (this.isConfidential) return 'eye-slash'; + if (this.isLocked) return 'lock'; - return ''; - }, - isLockedAndConfidential() { - return this.isConfidential && this.isLocked; - }, + return ''; }, - }; + isLockedAndConfidential() { + return this.isConfidential && this.isLocked; + }, + }, +}; </script> <template> <div class="issuable-note-warning"> diff --git a/app/assets/javascripts/vue_shared/components/loading_button.vue b/app/assets/javascripts/vue_shared/components/loading_button.vue index 4cbd3e6429d..f9b7fd5b1f9 100644 --- a/app/assets/javascripts/vue_shared/components/loading_button.vue +++ b/app/assets/javascripts/vue_shared/components/loading_button.vue @@ -1,6 +1,6 @@ <script> - /* eslint-disable vue/require-default-prop */ - /* This is a re-usable vue component for rendering a button +/* eslint-disable vue/require-default-prop */ +/* This is a re-usable vue component for rendering a button that will probably be sending off ajax requests and need to show the loading status by setting the `loading` option. This can also be used for initial page load when you don't @@ -17,34 +17,34 @@ */ - export default { - props: { - loading: { - type: Boolean, - required: false, - default: false, - }, - disabled: { - type: Boolean, - required: false, - default: false, - }, - label: { - type: String, - required: false, - }, - containerClass: { - type: [String, Array, Object], - required: false, - default: 'btn btn-align-content', - }, +export default { + props: { + loading: { + type: Boolean, + required: false, + default: false, }, - methods: { - onClick(e) { - this.$emit('click', e); - }, + disabled: { + type: Boolean, + required: false, + default: false, }, - }; + label: { + type: String, + required: false, + }, + containerClass: { + type: [String, Array, Object], + required: false, + default: 'btn btn-align-content', + }, + }, + methods: { + onClick(e) { + this.$emit('click', e); + }, + }, +}; </script> <template> diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue index 10e8ddad9cd..4687de62614 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/field.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue @@ -1,141 +1,141 @@ <script> - import $ from 'jquery'; - import { s__ } from '~/locale'; - import Flash from '../../../flash'; - import GLForm from '../../../gl_form'; - import markdownHeader from './header.vue'; - import markdownToolbar from './toolbar.vue'; - import icon from '../icon.vue'; +import $ from 'jquery'; +import { s__ } from '~/locale'; +import Flash from '../../../flash'; +import GLForm from '../../../gl_form'; +import markdownHeader from './header.vue'; +import markdownToolbar from './toolbar.vue'; +import icon from '../icon.vue'; - export default { - components: { - markdownHeader, - markdownToolbar, - icon, +export default { + components: { + markdownHeader, + markdownToolbar, + icon, + }, + props: { + markdownPreviewPath: { + type: String, + required: false, + default: '', }, - props: { - markdownPreviewPath: { - type: String, - required: false, - default: '', - }, - markdownDocsPath: { - type: String, - required: true, - }, - markdownVersion: { - type: Number, - required: false, - default: 0, - }, - addSpacingClasses: { - type: Boolean, - required: false, - default: true, - }, - quickActionsDocsPath: { - type: String, - required: false, - default: '', - }, - canAttachFile: { - type: Boolean, - required: false, - default: true, - }, - enableAutocomplete: { - type: Boolean, - required: false, - default: true, - }, + markdownDocsPath: { + type: String, + required: true, }, - data() { - return { - markdownPreview: '', - referencedCommands: '', - referencedUsers: '', - markdownPreviewLoading: false, - previewMarkdown: false, - }; + markdownVersion: { + type: Number, + required: false, + default: 0, }, - computed: { - shouldShowReferencedUsers() { - const referencedUsersThreshold = 10; - return this.referencedUsers.length >= referencedUsersThreshold; - }, + addSpacingClasses: { + type: Boolean, + required: false, + default: true, }, - mounted() { - /* - GLForm class handles all the toolbar buttons - */ - return new GLForm($(this.$refs['gl-form']), { - emojis: this.enableAutocomplete, - members: this.enableAutocomplete, - issues: this.enableAutocomplete, - mergeRequests: this.enableAutocomplete, - epics: this.enableAutocomplete, - milestones: this.enableAutocomplete, - labels: this.enableAutocomplete, - snippets: this.enableAutocomplete, - }); + quickActionsDocsPath: { + type: String, + required: false, + default: '', }, - beforeDestroy() { - const glForm = $(this.$refs['gl-form']).data('glForm'); - if (glForm) { - glForm.destroy(); - } + canAttachFile: { + type: Boolean, + required: false, + default: true, + }, + enableAutocomplete: { + type: Boolean, + required: false, + default: true, }, - methods: { - showPreviewTab() { - if (this.previewMarkdown) return; + }, + data() { + return { + markdownPreview: '', + referencedCommands: '', + referencedUsers: '', + markdownPreviewLoading: false, + previewMarkdown: false, + }; + }, + computed: { + shouldShowReferencedUsers() { + const referencedUsersThreshold = 10; + return this.referencedUsers.length >= referencedUsersThreshold; + }, + }, + mounted() { + /* + GLForm class handles all the toolbar buttons + */ + return new GLForm($(this.$refs['gl-form']), { + emojis: this.enableAutocomplete, + members: this.enableAutocomplete, + issues: this.enableAutocomplete, + mergeRequests: this.enableAutocomplete, + epics: this.enableAutocomplete, + milestones: this.enableAutocomplete, + labels: this.enableAutocomplete, + snippets: this.enableAutocomplete, + }); + }, + beforeDestroy() { + const glForm = $(this.$refs['gl-form']).data('glForm'); + if (glForm) { + glForm.destroy(); + } + }, + methods: { + showPreviewTab() { + if (this.previewMarkdown) return; - this.previewMarkdown = true; + this.previewMarkdown = true; - /* + /* Can't use `$refs` as the component is technically in the parent component so we access the VNode & then get the element */ - const text = this.$slots.textarea[0].elm.value; + const text = this.$slots.textarea[0].elm.value; - if (text) { - this.markdownPreviewLoading = true; - this.$http - .post(this.versionedPreviewPath(), { text }) - .then(resp => resp.json()) - .then(data => this.renderMarkdown(data)) - .catch(() => new Flash(s__('Error loading markdown preview'))); - } else { - this.renderMarkdown(); - } - }, + if (text) { + this.markdownPreviewLoading = true; + this.$http + .post(this.versionedPreviewPath(), { text }) + .then(resp => resp.json()) + .then(data => this.renderMarkdown(data)) + .catch(() => new Flash(s__('Error loading markdown preview'))); + } else { + this.renderMarkdown(); + } + }, - showWriteTab() { - this.markdownPreview = ''; - this.previewMarkdown = false; - }, + showWriteTab() { + this.markdownPreview = ''; + this.previewMarkdown = false; + }, - renderMarkdown(data = {}) { - this.markdownPreviewLoading = false; - this.markdownPreview = data.body || 'Nothing to preview.'; + renderMarkdown(data = {}) { + this.markdownPreviewLoading = false; + this.markdownPreview = data.body || 'Nothing to preview.'; - if (data.references) { - this.referencedCommands = data.references.commands; - this.referencedUsers = data.references.users; - } + if (data.references) { + this.referencedCommands = data.references.commands; + this.referencedUsers = data.references.users; + } - this.$nextTick(() => { - $(this.$refs['markdown-preview']).renderGFM(); - }); - }, + this.$nextTick(() => { + $(this.$refs['markdown-preview']).renderGFM(); + }); + }, - versionedPreviewPath() { - const { markdownPreviewPath, markdownVersion } = this; - return `${markdownPreviewPath}${ - markdownPreviewPath.indexOf('?') === -1 ? '?' : '&' - }markdown_version=${markdownVersion}`; - }, + versionedPreviewPath() { + const { markdownPreviewPath, markdownVersion } = this; + return `${markdownPreviewPath}${ + markdownPreviewPath.indexOf('?') === -1 ? '?' : '&' + }markdown_version=${markdownVersion}`; }, - }; + }, +}; </script> <template> diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue index ccd53158820..704adf7864f 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/header.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue @@ -1,63 +1,64 @@ <script> - import $ from 'jquery'; - import tooltip from '../../directives/tooltip'; - import toolbarButton from './toolbar_button.vue'; - import icon from '../icon.vue'; +import $ from 'jquery'; +import tooltip from '../../directives/tooltip'; +import toolbarButton from './toolbar_button.vue'; +import icon from '../icon.vue'; - export default { - directives: { - tooltip, +export default { + directives: { + tooltip, + }, + components: { + toolbarButton, + icon, + }, + props: { + previewMarkdown: { + type: Boolean, + required: true, }, - components: { - toolbarButton, - icon, + }, + computed: { + mdTable() { + return [ + '| header | header |', + '| ------ | ------ |', + '| cell | cell |', + '| cell | cell |', + ].join('\n'); }, - props: { - previewMarkdown: { - type: Boolean, - required: true, - }, + }, + mounted() { + $(document).on('markdown-preview:show.vue', this.previewMarkdownTab); + $(document).on('markdown-preview:hide.vue', this.writeMarkdownTab); + }, + beforeDestroy() { + $(document).off('markdown-preview:show.vue', this.previewMarkdownTab); + $(document).off('markdown-preview:hide.vue', this.writeMarkdownTab); + }, + methods: { + isValid(form) { + return ( + !form || + (form.find('.js-vue-markdown-field').length && $(this.$el).closest('form')[0] === form[0]) + ); }, - computed: { - mdTable() { - return [ - '| header | header |', - '| ------ | ------ |', - '| cell | cell |', - '| cell | cell |', - ].join('\n'); - }, - }, - mounted() { - $(document).on('markdown-preview:show.vue', this.previewMarkdownTab); - $(document).on('markdown-preview:hide.vue', this.writeMarkdownTab); - }, - beforeDestroy() { - $(document).off('markdown-preview:show.vue', this.previewMarkdownTab); - $(document).off('markdown-preview:hide.vue', this.writeMarkdownTab); - }, - methods: { - isValid(form) { - return !form || - form.find('.js-vue-markdown-field').length && - $(this.$el).closest('form')[0] === form[0]; - }, - previewMarkdownTab(event, form) { - if (event.target.blur) event.target.blur(); - if (!this.isValid(form)) return; + previewMarkdownTab(event, form) { + if (event.target.blur) event.target.blur(); + if (!this.isValid(form)) return; - this.$emit('preview-markdown'); - }, + this.$emit('preview-markdown'); + }, - writeMarkdownTab(event, form) { - if (event.target.blur) event.target.blur(); - if (!this.isValid(form)) return; + writeMarkdownTab(event, form) { + if (event.target.blur) event.target.blur(); + if (!this.isValid(form)) return; - this.$emit('write-markdown'); - }, + this.$emit('write-markdown'); }, - }; + }, +}; </script> <template> diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue index c45dafa9807..feb7b8f227e 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue @@ -1,32 +1,32 @@ <script> - import { Link } from '@gitlab-org/gitlab-ui'; +import { Link } from '@gitlab-org/gitlab-ui'; - export default { - components: { - 'gl-link': Link, +export default { + components: { + 'gl-link': Link, + }, + props: { + markdownDocsPath: { + type: String, + required: true, }, - props: { - markdownDocsPath: { - type: String, - required: true, - }, - quickActionsDocsPath: { - type: String, - required: false, - default: '', - }, - canAttachFile: { - type: Boolean, - required: false, - default: true, - }, + quickActionsDocsPath: { + type: String, + required: false, + default: '', }, - computed: { - hasQuickActionsDocsPath() { - return this.quickActionsDocsPath !== ''; - }, + canAttachFile: { + type: Boolean, + required: false, + default: true, }, - }; + }, + computed: { + hasQuickActionsDocsPath() { + return this.quickActionsDocsPath !== ''; + }, + }, +}; </script> <template> diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue index bda33636369..3e89e1c1e75 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue @@ -1,44 +1,44 @@ <script> - import tooltip from '../../directives/tooltip'; - import icon from '../icon.vue'; +import tooltip from '../../directives/tooltip'; +import icon from '../icon.vue'; - export default { - components: { - icon, +export default { + components: { + icon, + }, + directives: { + tooltip, + }, + props: { + buttonTitle: { + type: String, + required: true, }, - directives: { - tooltip, + icon: { + type: String, + required: true, }, - props: { - buttonTitle: { - type: String, - required: true, - }, - icon: { - type: String, - required: true, - }, - tag: { - type: String, - required: true, - }, - tagBlock: { - type: String, - required: false, - default: '', - }, - tagSelect: { - type: String, - required: false, - default: '', - }, - prepend: { - type: Boolean, - required: false, - default: false, - }, + tag: { + type: String, + required: true, }, - }; + tagBlock: { + type: String, + required: false, + default: '', + }, + tagSelect: { + type: String, + required: false, + default: '', + }, + prepend: { + type: Boolean, + required: false, + default: false, + }, + }, +}; </script> <template> diff --git a/app/assets/javascripts/vue_shared/components/memory_graph.vue b/app/assets/javascripts/vue_shared/components/memory_graph.vue index 552a92541be..964dedb38c4 100644 --- a/app/assets/javascripts/vue_shared/components/memory_graph.vue +++ b/app/assets/javascripts/vue_shared/components/memory_graph.vue @@ -41,7 +41,8 @@ export default { // Find metric timestamp which is closest to deploymentTime timestampDiff = Math.abs(metricTimestamps[0] - median); metricTimestamps.forEach((timestamp, index) => { - if (index === 0) { // Skip first element + if (index === 0) { + // Skip first element return; } diff --git a/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue b/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue index 38115f268bb..dcad79e521d 100644 --- a/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue +++ b/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue @@ -1,41 +1,39 @@ <script> - /** - * Common component to render a placeholder note and user information. - * - * This component needs to be used with a vuex store. - * That vuex store needs to have a `getUserData` getter that contains - * { - * path: String, - * avatar_url: String, - * name: String, - * username: String, - * } - * - * @example - * <placeholder-note - * :note="{body: 'This is a note'}" - * /> - */ - import { mapGetters } from 'vuex'; - import userAvatarLink from '../user_avatar/user_avatar_link.vue'; +/** + * Common component to render a placeholder note and user information. + * + * This component needs to be used with a vuex store. + * That vuex store needs to have a `getUserData` getter that contains + * { + * path: String, + * avatar_url: String, + * name: String, + * username: String, + * } + * + * @example + * <placeholder-note + * :note="{body: 'This is a note'}" + * /> + */ +import { mapGetters } from 'vuex'; +import userAvatarLink from '../user_avatar/user_avatar_link.vue'; - export default { - name: 'PlaceholderNote', - components: { - userAvatarLink, +export default { + name: 'PlaceholderNote', + components: { + userAvatarLink, + }, + props: { + note: { + type: Object, + required: true, }, - props: { - note: { - type: Object, - required: true, - }, - }, - computed: { - ...mapGetters([ - 'getUserData', - ]), - }, - }; + }, + computed: { + ...mapGetters(['getUserData']), + }, +}; </script> <template> diff --git a/app/assets/javascripts/vue_shared/components/notes/placeholder_system_note.vue b/app/assets/javascripts/vue_shared/components/notes/placeholder_system_note.vue index 95e2b38e292..674f923478d 100644 --- a/app/assets/javascripts/vue_shared/components/notes/placeholder_system_note.vue +++ b/app/assets/javascripts/vue_shared/components/notes/placeholder_system_note.vue @@ -1,21 +1,21 @@ <script> - /** - * Common component to render a placeholder system note. - * - * @example - * <placeholder-system-note - * :note="{ body: 'Commands are being applied'}" - * /> - */ - export default { - name: 'PlaceholderSystemNote', - props: { - note: { - type: Object, - required: true, - }, +/** + * Common component to render a placeholder system note. + * + * @example + * <placeholder-system-note + * :note="{ body: 'Commands are being applied'}" + * /> + */ +export default { + name: 'PlaceholderSystemNote', + props: { + note: { + type: Object, + required: true, }, - }; + }, +}; </script> <template> diff --git a/app/assets/javascripts/vue_shared/components/panel_resizer.vue b/app/assets/javascripts/vue_shared/components/panel_resizer.vue index 7947ae1e4da..bf736a378dd 100644 --- a/app/assets/javascripts/vue_shared/components/panel_resizer.vue +++ b/app/assets/javascripts/vue_shared/components/panel_resizer.vue @@ -1,90 +1,90 @@ <script> - export default { - props: { - startSize: { - type: Number, - required: true, - }, - side: { - type: String, - required: true, - }, - minSize: { - type: Number, - required: false, - default: 0, - }, - maxSize: { - type: Number, - required: false, - default: Number.MAX_VALUE, - }, - enabled: { - type: Boolean, - required: false, - default: true, - }, +export default { + props: { + startSize: { + type: Number, + required: true, }, - data() { - return { - size: this.startSize, - }; + side: { + type: String, + required: true, }, - computed: { - className() { - return `drag-${this.side}`; - }, - cursorStyle() { - if (this.enabled) { - return { cursor: 'ew-resize' }; - } - return {}; - }, + minSize: { + type: Number, + required: false, + default: 0, }, - methods: { - resetSize(e) { - e.preventDefault(); - this.$emit('resize-start', this.size); + maxSize: { + type: Number, + required: false, + default: Number.MAX_VALUE, + }, + enabled: { + type: Boolean, + required: false, + default: true, + }, + }, + data() { + return { + size: this.startSize, + }; + }, + computed: { + className() { + return `drag-${this.side}`; + }, + cursorStyle() { + if (this.enabled) { + return { cursor: 'ew-resize' }; + } + return {}; + }, + }, + methods: { + resetSize(e) { + e.preventDefault(); + this.$emit('resize-start', this.size); - this.size = this.startSize; - this.$emit('update:size', this.size); + this.size = this.startSize; + this.$emit('update:size', this.size); - // End resizing on next tick so that listeners can react to DOM changes - this.$nextTick(() => { - this.$emit('resize-end', this.size); - }); - }, - startDrag(e) { - if (this.enabled) { - e.preventDefault(); - this.startPos = e.clientX; - this.currentStartSize = this.size; - document.addEventListener('mousemove', this.drag); - document.addEventListener('mouseup', this.endDrag, { once: true }); - this.$emit('resize-start', this.size); - } - }, - drag(e) { + // End resizing on next tick so that listeners can react to DOM changes + this.$nextTick(() => { + this.$emit('resize-end', this.size); + }); + }, + startDrag(e) { + if (this.enabled) { e.preventDefault(); - let moved = e.clientX - this.startPos; - if (this.side === 'left') moved = -moved; - let newSize = this.currentStartSize + moved; - if (newSize < this.minSize) { - newSize = this.minSize; - } else if (newSize > this.maxSize) { - newSize = this.maxSize; - } - this.size = newSize; + this.startPos = e.clientX; + this.currentStartSize = this.size; + document.addEventListener('mousemove', this.drag); + document.addEventListener('mouseup', this.endDrag, { once: true }); + this.$emit('resize-start', this.size); + } + }, + drag(e) { + e.preventDefault(); + let moved = e.clientX - this.startPos; + if (this.side === 'left') moved = -moved; + let newSize = this.currentStartSize + moved; + if (newSize < this.minSize) { + newSize = this.minSize; + } else if (newSize > this.maxSize) { + newSize = this.maxSize; + } + this.size = newSize; - this.$emit('update:size', newSize); - }, - endDrag(e) { - e.preventDefault(); - document.removeEventListener('mousemove', this.drag); - this.$emit('resize-end', this.size); - }, + this.$emit('update:size', newSize); + }, + endDrag(e) { + e.preventDefault(); + document.removeEventListener('mousemove', this.drag); + this.$emit('resize-end', this.size); }, - }; + }, +}; </script> <template> diff --git a/app/assets/javascripts/vue_shared/components/pikaday.vue b/app/assets/javascripts/vue_shared/components/pikaday.vue index bfeece12077..782d8e3abf6 100644 --- a/app/assets/javascripts/vue_shared/components/pikaday.vue +++ b/app/assets/javascripts/vue_shared/components/pikaday.vue @@ -1,62 +1,62 @@ <script> - import Pikaday from 'pikaday'; - import { parsePikadayDate, pikadayToString } from '../../lib/utils/datefix'; +import Pikaday from 'pikaday'; +import { parsePikadayDate, pikadayToString } from '../../lib/utils/datefix'; - export default { - name: 'DatePicker', - props: { - label: { - type: String, - required: false, - default: 'Date picker', - }, - selectedDate: { - type: Date, - required: false, - default: null, - }, - minDate: { - type: Date, - required: false, - default: null, - }, - maxDate: { - type: Date, - required: false, - default: null, - }, +export default { + name: 'DatePicker', + props: { + label: { + type: String, + required: false, + default: 'Date picker', }, - mounted() { - this.calendar = new Pikaday({ - field: this.$el.querySelector('.dropdown-menu-toggle'), - theme: 'gitlab-theme animate-picker', - format: 'yyyy-mm-dd', - container: this.$el, - defaultDate: this.selectedDate, - setDefaultDate: !!this.selectedDate, - minDate: this.minDate, - maxDate: this.maxDate, - parse: dateString => parsePikadayDate(dateString), - toString: date => pikadayToString(date), - onSelect: this.selected.bind(this), - onClose: this.toggled.bind(this), - }); - - this.$el.append(this.calendar.el); - this.calendar.show(); + selectedDate: { + type: Date, + required: false, + default: null, + }, + minDate: { + type: Date, + required: false, + default: null, }, - beforeDestroy() { - this.calendar.destroy(); + maxDate: { + type: Date, + required: false, + default: null, + }, + }, + mounted() { + this.calendar = new Pikaday({ + field: this.$el.querySelector('.dropdown-menu-toggle'), + theme: 'gitlab-theme animate-picker', + format: 'yyyy-mm-dd', + container: this.$el, + defaultDate: this.selectedDate, + setDefaultDate: !!this.selectedDate, + minDate: this.minDate, + maxDate: this.maxDate, + parse: dateString => parsePikadayDate(dateString), + toString: date => pikadayToString(date), + onSelect: this.selected.bind(this), + onClose: this.toggled.bind(this), + }); + + this.$el.append(this.calendar.el); + this.calendar.show(); + }, + beforeDestroy() { + this.calendar.destroy(); + }, + methods: { + selected(dateText) { + this.$emit('newDateSelected', this.calendar.toString(dateText)); }, - methods: { - selected(dateText) { - this.$emit('newDateSelected', this.calendar.toString(dateText)); - }, - toggled() { - this.$emit('hidePicker'); - }, + toggled() { + this.$emit('hidePicker'); }, - }; + }, +}; </script> <template> diff --git a/app/assets/javascripts/vue_shared/components/project_avatar/image.vue b/app/assets/javascripts/vue_shared/components/project_avatar/image.vue index 97ca4d93bd7..1a2fd2ad985 100644 --- a/app/assets/javascripts/vue_shared/components/project_avatar/image.vue +++ b/app/assets/javascripts/vue_shared/components/project_avatar/image.vue @@ -1,6 +1,5 @@ <script> - - /* This is a re-usable vue component for rendering a project avatar that +/* This is a re-usable vue component for rendering a project avatar that does not need to link to the project's profile. The image and an optional tooltip can be configured by props passed to this component. @@ -16,70 +15,70 @@ */ - import defaultAvatarUrl from 'images/no_avatar.png'; - import { placeholderImage } from '../../../lazy_loader'; - import tooltip from '../../directives/tooltip'; +import defaultAvatarUrl from 'images/no_avatar.png'; +import { placeholderImage } from '../../../lazy_loader'; +import tooltip from '../../directives/tooltip'; - export default { - name: 'ProjectAvatarImage', - directives: { - tooltip, +export default { + name: 'ProjectAvatarImage', + directives: { + tooltip, + }, + props: { + lazy: { + type: Boolean, + required: false, + default: false, + }, + imgSrc: { + type: String, + required: false, + default: defaultAvatarUrl, + }, + cssClasses: { + type: String, + required: false, + default: '', + }, + imgAlt: { + type: String, + required: false, + default: 'project avatar', + }, + size: { + type: Number, + required: false, + default: 20, + }, + tooltipText: { + type: String, + required: false, + default: '', + }, + tooltipPlacement: { + type: String, + required: false, + default: 'top', + }, + }, + computed: { + // API response sends null when gravatar is disabled and + // we provide an empty string when we use it inside project avatar link. + // In both cases we should render the defaultAvatarUrl + sanitizedSource() { + return this.imgSrc === '' || this.imgSrc === null ? defaultAvatarUrl : this.imgSrc; + }, + resultantSrcAttribute() { + return this.lazy ? placeholderImage : this.sanitizedSource; }, - props: { - lazy: { - type: Boolean, - required: false, - default: false, - }, - imgSrc: { - type: String, - required: false, - default: defaultAvatarUrl, - }, - cssClasses: { - type: String, - required: false, - default: '', - }, - imgAlt: { - type: String, - required: false, - default: 'project avatar', - }, - size: { - type: Number, - required: false, - default: 20, - }, - tooltipText: { - type: String, - required: false, - default: '', - }, - tooltipPlacement: { - type: String, - required: false, - default: 'top', - }, + tooltipContainer() { + return this.tooltipText ? 'body' : null; }, - computed: { - // API response sends null when gravatar is disabled and - // we provide an empty string when we use it inside project avatar link. - // In both cases we should render the defaultAvatarUrl - sanitizedSource() { - return this.imgSrc === '' || this.imgSrc === null ? defaultAvatarUrl : this.imgSrc; - }, - resultantSrcAttribute() { - return this.lazy ? placeholderImage : this.sanitizedSource; - }, - tooltipContainer() { - return this.tooltipText ? 'body' : null; - }, - avatarSizeClass() { - return `s${this.size}`; - }, + avatarSizeClass() { + return `s${this.size}`; }, - }; + }, +}; </script> <template> diff --git a/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue index a2a9a5e6987..09394847b10 100644 --- a/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue +++ b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue @@ -1,67 +1,67 @@ <script> - import DeprecatedModal from './deprecated_modal.vue'; +import DeprecatedModal from './deprecated_modal.vue'; - export default { - name: 'RecaptchaModal', +export default { + name: 'RecaptchaModal', - components: { - DeprecatedModal, - }, + components: { + DeprecatedModal, + }, - props: { - html: { - type: String, - required: false, - default: '', - }, + props: { + html: { + type: String, + required: false, + default: '', }, + }, - data() { - return { - script: {}, - scriptSrc: 'https://www.google.com/recaptcha/api.js', - }; - }, + data() { + return { + script: {}, + scriptSrc: 'https://www.google.com/recaptcha/api.js', + }; + }, - watch: { - html() { - this.appendRecaptchaScript(); - }, + watch: { + html() { + this.appendRecaptchaScript(); }, + }, - mounted() { - window.recaptchaDialogCallback = this.submit.bind(this); - }, + mounted() { + window.recaptchaDialogCallback = this.submit.bind(this); + }, - methods: { - appendRecaptchaScript() { - this.removeRecaptchaScript(); + methods: { + appendRecaptchaScript() { + this.removeRecaptchaScript(); - const script = document.createElement('script'); - script.src = this.scriptSrc; - script.classList.add('js-recaptcha-script'); - script.async = true; - script.defer = true; + const script = document.createElement('script'); + script.src = this.scriptSrc; + script.classList.add('js-recaptcha-script'); + script.async = true; + script.defer = true; - this.script = script; + this.script = script; - document.body.appendChild(script); - }, + document.body.appendChild(script); + }, - removeRecaptchaScript() { - if (this.script instanceof Element) this.script.remove(); - }, + removeRecaptchaScript() { + if (this.script instanceof Element) this.script.remove(); + }, - close() { - this.removeRecaptchaScript(); - this.$emit('close'); - }, + close() { + this.removeRecaptchaScript(); + this.$emit('close'); + }, - submit() { - this.$el.querySelector('form').submit(); - }, + submit() { + this.$el.querySelector('form').submit(); }, - }; + }, +}; </script> <template> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue index 9d757b27edc..500586302cf 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue @@ -1,96 +1,96 @@ <script> - import datePicker from '../pikaday.vue'; - import toggleSidebar from './toggle_sidebar.vue'; - import collapsedCalendarIcon from './collapsed_calendar_icon.vue'; - import { dateInWords } from '../../../lib/utils/datetime_utility'; +import datePicker from '../pikaday.vue'; +import toggleSidebar from './toggle_sidebar.vue'; +import collapsedCalendarIcon from './collapsed_calendar_icon.vue'; +import { dateInWords } from '../../../lib/utils/datetime_utility'; - export default { - name: 'SidebarDatePicker', - components: { - datePicker, - toggleSidebar, - collapsedCalendarIcon, - }, - props: { - blockClass: { - type: String, - required: false, - default: '', - }, - collapsed: { - type: Boolean, - required: false, - default: true, - }, - showToggleSidebar: { - type: Boolean, - required: false, - default: false, - }, - isLoading: { - type: Boolean, - required: false, - default: false, - }, - editable: { - type: Boolean, - required: false, - default: false, - }, - label: { - type: String, - required: false, - default: 'Date picker', - }, - selectedDate: { - type: Date, - required: false, - default: null, - }, - minDate: { - type: Date, - required: false, - default: null, - }, - maxDate: { - type: Date, - required: false, - default: null, - }, - }, - data() { - return { - editing: false, - }; - }, - computed: { - selectedAndEditable() { - return this.selectedDate && this.editable; - }, - selectedDateWords() { - return dateInWords(this.selectedDate, true); - }, - collapsedText() { - return this.selectedDateWords ? this.selectedDateWords : 'None'; - }, - }, - methods: { - stopEditing() { - this.editing = false; - }, - toggleDatePicker() { - this.editing = !this.editing; - }, - newDateSelected(date = null) { - this.date = date; - this.editing = false; - this.$emit('saveDate', date); - }, - toggleSidebar() { - this.$emit('toggleCollapse'); - }, - }, - }; +export default { + name: 'SidebarDatePicker', + components: { + datePicker, + toggleSidebar, + collapsedCalendarIcon, + }, + props: { + blockClass: { + type: String, + required: false, + default: '', + }, + collapsed: { + type: Boolean, + required: false, + default: true, + }, + showToggleSidebar: { + type: Boolean, + required: false, + default: false, + }, + isLoading: { + type: Boolean, + required: false, + default: false, + }, + editable: { + type: Boolean, + required: false, + default: false, + }, + label: { + type: String, + required: false, + default: 'Date picker', + }, + selectedDate: { + type: Date, + required: false, + default: null, + }, + minDate: { + type: Date, + required: false, + default: null, + }, + maxDate: { + type: Date, + required: false, + default: null, + }, + }, + data() { + return { + editing: false, + }; + }, + computed: { + selectedAndEditable() { + return this.selectedDate && this.editable; + }, + selectedDateWords() { + return dateInWords(this.selectedDate, true); + }, + collapsedText() { + return this.selectedDateWords ? this.selectedDateWords : 'None'; + }, + }, + methods: { + stopEditing() { + this.editing = false; + }, + toggleDatePicker() { + this.editing = !this.editing; + }, + newDateSelected(date = null) { + this.date = date; + this.editing = false; + this.$emit('saveDate', date); + }, + toggleSidebar() { + this.$emit('toggleCollapse'); + }, + }, +}; </script> <template> diff --git a/app/assets/javascripts/vue_shared/components/table_pagination.vue b/app/assets/javascripts/vue_shared/components/table_pagination.vue index 8e9621c956f..03a5a078879 100644 --- a/app/assets/javascripts/vue_shared/components/table_pagination.vue +++ b/app/assets/javascripts/vue_shared/components/table_pagination.vue @@ -1,17 +1,17 @@ <script> - import { s__ } from '../../locale'; - - const PAGINATION_UI_BUTTON_LIMIT = 4; - const UI_LIMIT = 6; - const SPREAD = '...'; - const PREV = s__('Pagination|Prev'); - const NEXT = s__('Pagination|Next'); - const FIRST = s__('Pagination|« First'); - const LAST = s__('Pagination|Last »'); - - export default { - props: { - /** +import { s__ } from '../../locale'; + +const PAGINATION_UI_BUTTON_LIMIT = 4; +const UI_LIMIT = 6; +const SPREAD = '...'; +const PREV = s__('Pagination|Prev'); +const NEXT = s__('Pagination|Next'); +const FIRST = s__('Pagination|« First'); +const LAST = s__('Pagination|Last »'); + +export default { + props: { + /** This function will take the information given by the pagination component Here is an example `change` method: @@ -20,12 +20,12 @@ gl.utils.visitUrl(`?page=${pagenum}`); }, */ - change: { - type: Function, - required: true, - }, + change: { + type: Function, + required: true, + }, - /** + /** pageInfo will come from the headers of the API call in the `.then` clause of the VueResource API call there should be a function that contructs the pageInfo for this component @@ -41,94 +41,94 @@ previousPage: +headers['X-Prev-Page'], }); */ - pageInfo: { - type: Object, - required: true, - }, + pageInfo: { + type: Object, + required: true, + }, + }, + computed: { + prev() { + return this.pageInfo.previousPage; + }, + next() { + return this.pageInfo.nextPage; + }, + getItems() { + const total = this.pageInfo.totalPages; + const { page } = this.pageInfo; + const items = []; + + if (page > 1) { + items.push({ title: FIRST, first: true }); + } + + if (page > 1) { + items.push({ title: PREV, prev: true }); + } else { + items.push({ title: PREV, disabled: true, prev: true }); + } + + if (page > UI_LIMIT) items.push({ title: SPREAD, separator: true }); + + const start = Math.max(page - PAGINATION_UI_BUTTON_LIMIT, 1); + const end = Math.min(page + PAGINATION_UI_BUTTON_LIMIT, total); + + for (let i = start; i <= end; i += 1) { + const isActive = i === page; + items.push({ title: i, active: isActive, page: true }); + } + + if (total - page > PAGINATION_UI_BUTTON_LIMIT) { + items.push({ title: SPREAD, separator: true, page: true }); + } + + if (page === total) { + items.push({ title: NEXT, disabled: true, next: true }); + } else if (total - page >= 1) { + items.push({ title: NEXT, next: true }); + } + + if (total - page >= 1) { + items.push({ title: LAST, last: true }); + } + + return items; + }, + showPagination() { + return this.pageInfo.totalPages > 1; }, - computed: { - prev() { - return this.pageInfo.previousPage; - }, - next() { - return this.pageInfo.nextPage; - }, - getItems() { - const total = this.pageInfo.totalPages; - const { page } = this.pageInfo; - const items = []; - - if (page > 1) { - items.push({ title: FIRST, first: true }); - } - - if (page > 1) { - items.push({ title: PREV, prev: true }); - } else { - items.push({ title: PREV, disabled: true, prev: true }); - } - - if (page > UI_LIMIT) items.push({ title: SPREAD, separator: true }); - - const start = Math.max(page - PAGINATION_UI_BUTTON_LIMIT, 1); - const end = Math.min(page + PAGINATION_UI_BUTTON_LIMIT, total); - - for (let i = start; i <= end; i += 1) { - const isActive = i === page; - items.push({ title: i, active: isActive, page: true }); - } - - if (total - page > PAGINATION_UI_BUTTON_LIMIT) { - items.push({ title: SPREAD, separator: true, page: true }); - } - - if (page === total) { - items.push({ title: NEXT, disabled: true, next: true }); - } else if (total - page >= 1) { - items.push({ title: NEXT, next: true }); - } - - if (total - page >= 1) { - items.push({ title: LAST, last: true }); - } - - return items; - }, - showPagination() { - return this.pageInfo.totalPages > 1; - }, + }, + methods: { + changePage(text, isDisabled) { + if (isDisabled) return; + + const { totalPages, nextPage, previousPage } = this.pageInfo; + + switch (text) { + case SPREAD: + break; + case LAST: + this.change(totalPages); + break; + case NEXT: + this.change(nextPage); + break; + case PREV: + this.change(previousPage); + break; + case FIRST: + this.change(1); + break; + default: + this.change(+text); + break; + } }, - methods: { - changePage(text, isDisabled) { - if (isDisabled) return; - - const { totalPages, nextPage, previousPage } = this.pageInfo; - - switch (text) { - case SPREAD: - break; - case LAST: - this.change(totalPages); - break; - case NEXT: - this.change(nextPage); - break; - case PREV: - this.change(previousPage); - break; - case FIRST: - this.change(1); - break; - default: - this.change(+text); - break; - } - }, - hideOnSmallScreen(item) { - return !item.first && !item.last && !item.next && !item.prev && !item.active; - }, + hideOnSmallScreen(item) { + return !item.first && !item.last && !item.next && !item.prev && !item.active; }, - }; + }, +}; </script> <template> <div diff --git a/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue b/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue index 368eeb6c453..d760263929a 100644 --- a/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue +++ b/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue @@ -11,9 +11,7 @@ export default { directives: { tooltip, }, - mixins: [ - timeagoMixin, - ], + mixins: [timeagoMixin], props: { time: { type: String, diff --git a/app/assets/javascripts/vue_shared/components/toggle_button.vue b/app/assets/javascripts/vue_shared/components/toggle_button.vue index 5b9c51786d6..4e9289cbed8 100644 --- a/app/assets/javascripts/vue_shared/components/toggle_button.vue +++ b/app/assets/javascripts/vue_shared/components/toggle_button.vue @@ -1,60 +1,60 @@ <script> - import { s__ } from '../../locale'; - import icon from './icon.vue'; +import { s__ } from '../../locale'; +import icon from './icon.vue'; - const ICON_ON = 'status_success_borderless'; - const ICON_OFF = 'status_failed_borderless'; - const LABEL_ON = s__('ToggleButton|Toggle Status: ON'); - const LABEL_OFF = s__('ToggleButton|Toggle Status: OFF'); +const ICON_ON = 'status_success_borderless'; +const ICON_OFF = 'status_failed_borderless'; +const LABEL_ON = s__('ToggleButton|Toggle Status: ON'); +const LABEL_OFF = s__('ToggleButton|Toggle Status: OFF'); - export default { - components: { - icon, - }, +export default { + components: { + icon, + }, - model: { - prop: 'value', - event: 'change', - }, + model: { + prop: 'value', + event: 'change', + }, - props: { - name: { - type: String, - required: false, - default: null, - }, - value: { - type: Boolean, - required: false, - default: null, - }, - disabledInput: { - type: Boolean, - required: false, - default: false, - }, - isLoading: { - type: Boolean, - required: false, - default: false, - }, + props: { + name: { + type: String, + required: false, + default: null, + }, + value: { + type: Boolean, + required: false, + default: null, }, + disabledInput: { + type: Boolean, + required: false, + default: false, + }, + isLoading: { + type: Boolean, + required: false, + default: false, + }, + }, - computed: { - toggleIcon() { - return this.value ? ICON_ON : ICON_OFF; - }, - ariaLabel() { - return this.value ? LABEL_ON : LABEL_OFF; - }, + computed: { + toggleIcon() { + return this.value ? ICON_ON : ICON_OFF; + }, + ariaLabel() { + return this.value ? LABEL_ON : LABEL_OFF; }, + }, - methods: { - toggleFeature() { - if (!this.disabledInput) this.$emit('change', !this.value); - }, + methods: { + toggleFeature() { + if (!this.disabledInput) this.$emit('change', !this.value); }, - }; + }, +}; </script> <template> diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue index ee3157bcb1b..14cb44b8619 100644 --- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue +++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue @@ -1,5 +1,4 @@ <script> - /* This is a re-usable vue component for rendering a user avatar wrapped in a clickable link (likely to the user's profile). The link, image, and tooltip can be configured by props passed to this component. diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue index ef3b16edf5f..8e460566d09 100644 --- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue +++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue @@ -1,5 +1,4 @@ <script> - /* This is a re-usable vue component for rendering a user avatar svg (typically for a blank state). It will receive styles comparable to the user avatar, but no image is loaded, it isn't wrapped in a link, and tooltips aren't supported. @@ -42,4 +41,3 @@ export default { v-html="svg" /> </template> - diff --git a/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js b/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js index f94cc670edf..67a1632269e 100644 --- a/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js +++ b/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js @@ -4,10 +4,7 @@ * * Components need to have `scope`, `page` and `requestData` */ -import { - historyPushState, - buildUrlWithCurrentLocation, -} from '../../lib/utils/common_utils'; +import { historyPushState, buildUrlWithCurrentLocation } from '../../lib/utils/common_utils'; export default { methods: { @@ -24,12 +21,14 @@ export default { // stop polling this.poll.stop(); - const queryString = Object.keys(parameters).map((parameter) => { - const value = parameters[parameter]; - // update internal state for UI - this[parameter] = value; - return `${parameter}=${encodeURIComponent(value)}`; - }).join('&'); + const queryString = Object.keys(parameters) + .map(parameter => { + const value = parameters[parameter]; + // update internal state for UI + this[parameter] = value; + return `${parameter}=${encodeURIComponent(value)}`; + }) + .join('&'); // update polling parameters this.requestData = parameters; diff --git a/app/assets/javascripts/vue_shared/models/label.js b/app/assets/javascripts/vue_shared/models/label.js index d29c7fe973a..2d2732d0661 100644 --- a/app/assets/javascripts/vue_shared/models/label.js +++ b/app/assets/javascripts/vue_shared/models/label.js @@ -6,7 +6,7 @@ export default class ListLabel { this.color = obj.color; this.textColor = obj.text_color; this.description = obj.description; - this.priority = (obj.priority !== null) ? obj.priority : Infinity; + this.priority = obj.priority !== null ? obj.priority : Infinity; } } diff --git a/app/assets/javascripts/vue_shared/translate.js b/app/assets/javascripts/vue_shared/translate.js index 48c63373b77..e0baf03acc3 100644 --- a/app/assets/javascripts/vue_shared/translate.js +++ b/app/assets/javascripts/vue_shared/translate.js @@ -1,11 +1,6 @@ -import { - __, - n__, - s__, - sprintf, -} from '../locale'; +import { __, n__, s__, sprintf } from '../locale'; -export default (Vue) => { +export default Vue => { Vue.mixin({ methods: { /** diff --git a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js index b9693892f45..754025207c8 100644 --- a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js +++ b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js @@ -21,7 +21,7 @@ Vue.http.interceptors.push((request, next) => { Vue.http.interceptors.push((request, next) => { request.headers.set(csrf.headerKey, csrf.token); - next((response) => { + next(response => { // Headers object has a `forEach` property that iterates through all values. const headers = {}; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index cba5324ce53..8d884ad6891 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -1013,6 +1013,7 @@ .with-performance-bar & { top: 135px; + max-height: calc(100vh - 135px); } } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 45382d4ea43..895db89f289 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -201,7 +201,6 @@ .mr-widget-icon { font-size: 22px; - margin-right: $gl-btn-padding; } .ci-status-icon svg { diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index d0f59aa8162..312e256ea6c 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -10,7 +10,7 @@ class Projects::ArtifactsController < Projects::ApplicationController before_action :authorize_update_build!, only: [:keep] before_action :extract_ref_name_and_path before_action :set_request_format, only: [:file] - before_action :validate_artifacts! + before_action :validate_artifacts!, except: [:download] before_action :entry, only: [:file] def download @@ -102,7 +102,7 @@ class Projects::ArtifactsController < Projects::ApplicationController # rubocop: enable CodeReuse/ActiveRecord def artifacts_file - @artifacts_file ||= build.artifacts_file_for_type(params[:file_type] || :archive) + @artifacts_file ||= build&.artifacts_file_for_type(params[:file_type] || :archive) end def entry diff --git a/app/controllers/projects/build_artifacts_controller.rb b/app/controllers/projects/build_artifacts_controller.rb index 46449a4aae9..7d4d566499c 100644 --- a/app/controllers/projects/build_artifacts_controller.rb +++ b/app/controllers/projects/build_artifacts_controller.rb @@ -6,10 +6,10 @@ class Projects::BuildArtifactsController < Projects::ApplicationController before_action :authorize_read_build! before_action :extract_ref_name_and_path - before_action :validate_artifacts! + before_action :validate_artifacts!, except: [:download] def download - redirect_to download_project_job_artifacts_path(project, job) + redirect_to download_project_job_artifacts_path(project, job, params: request.query_parameters) end def browse diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index f7347ee61b4..662f3e00047 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -118,6 +118,7 @@ class Notify < BaseMailer add_unsubscription_headers_and_links headers["X-GitLab-#{model.class.name}-ID"] = model.id + headers["X-GitLab-#{model.class.name}-IID"] = model.iid if model.respond_to?(:iid) headers['X-GitLab-Reply-Key'] = reply_key @reason = headers['X-GitLab-NotificationReason'] diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index 9a827523ed4..004a3528d4b 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -24,7 +24,7 @@ %p.gl-field-hint.text-secondary Minimum length is #{@minimum_password_length} characters - if Gitlab::CurrentSettings.current_application_settings.enforce_terms? .form-group - = check_box_tag :terms_opt_in, '1', false, required: true + = check_box_tag :terms_opt_in, '1', false, required: true, class: 'qa-new-user-accept-terms' = label_tag :terms_opt_in do - terms_link = link_to s_("I accept the|Terms of Service and Privacy Policy"), terms_path, target: "_blank" - accept_terms_label = _("I accept the %{terms_link}") % { terms_link: terms_link } diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index aba289c790f..283031b06da 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -8,8 +8,8 @@ = render partial: 'flash_messages', locals: { project: @project } -- if @project.repository_exists? && !@project.empty_repo? - - signatures_path = namespace_project_signatures_path(namespace_id: @project.namespace.full_path, project_id: @project.path, id: @project.default_branch) +- if !@project.empty_repo? && can?(current_user, :download_code, @project) + - signatures_path = project_signatures_path(@project, @project.default_branch) .js-signature-container{ data: { 'signatures-path': signatures_path } } %div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } diff --git a/changelogs/unreleased/52147-loading-state.yml b/changelogs/unreleased/52147-loading-state.yml new file mode 100644 index 00000000000..5ae844073f7 --- /dev/null +++ b/changelogs/unreleased/52147-loading-state.yml @@ -0,0 +1,5 @@ +--- +title: Removes extra border from test reports in the merge request widget +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/52477-add-iid-headers-to-emails.yml b/changelogs/unreleased/52477-add-iid-headers-to-emails.yml new file mode 100644 index 00000000000..c17b66c5f54 --- /dev/null +++ b/changelogs/unreleased/52477-add-iid-headers-to-emails.yml @@ -0,0 +1,5 @@ +--- +title: Add IID headers to E-Mail notifications +merge_request: 22263 +author: +type: changed diff --git a/changelogs/unreleased/52686-project-slug-does-not-auto-populate-in-ie11.yml b/changelogs/unreleased/52686-project-slug-does-not-auto-populate-in-ie11.yml new file mode 100644 index 00000000000..5a30317babf --- /dev/null +++ b/changelogs/unreleased/52686-project-slug-does-not-auto-populate-in-ie11.yml @@ -0,0 +1,5 @@ +--- +title: Use literal instead of constructor for creating regex +merge_request: 22367 +author: +type: other diff --git a/changelogs/unreleased/diff-stats-perf-bar.yml b/changelogs/unreleased/diff-stats-perf-bar.yml new file mode 100644 index 00000000000..52d70d4537f --- /dev/null +++ b/changelogs/unreleased/diff-stats-perf-bar.yml @@ -0,0 +1,5 @@ +--- +title: Fixed diff stats not showing when performance bar is enabled +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/ide-file-templates-clear.yml b/changelogs/unreleased/ide-file-templates-clear.yml new file mode 100644 index 00000000000..7878f2231a7 --- /dev/null +++ b/changelogs/unreleased/ide-file-templates-clear.yml @@ -0,0 +1,5 @@ +--- +title: Clear fetched file templates when changing template type in Web IDE +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/sh-fix-commit-signatures-error.yml b/changelogs/unreleased/sh-fix-commit-signatures-error.yml new file mode 100644 index 00000000000..e2ea0e5857e --- /dev/null +++ b/changelogs/unreleased/sh-fix-commit-signatures-error.yml @@ -0,0 +1,5 @@ +--- +title: Fix commit signature error when project is disabled +merge_request: 22344 +author: +type: fixed diff --git a/changelogs/unreleased/test-usage-ping-in-timeout-case.yml b/changelogs/unreleased/test-usage-ping-in-timeout-case.yml new file mode 100644 index 00000000000..daee98765ac --- /dev/null +++ b/changelogs/unreleased/test-usage-ping-in-timeout-case.yml @@ -0,0 +1,5 @@ +--- +title: Fix auto-corrected upload URLs in webhooks +merge_request: 22361 +author: +type: fixed diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md index 7d12cd8f7c2..7c63967c829 100644 --- a/doc/user/project/integrations/webhooks.md +++ b/doc/user/project/integrations/webhooks.md @@ -7,7 +7,7 @@ > - the `project.http_url` key is deprecated in favor of the `project.git_http_url` key > > **Note:** -> Starting from GitLab 11.1, the logs of web hooks are automatically removed after +> Starting from GitLab 11.1, the logs of webhooks are automatically removed after > one month. > > **Note:** @@ -73,8 +73,8 @@ Below are described the supported events. Triggered when you push to the repository except when pushing tags. -> **Note:** When more than 20 commits are pushed at once, the `commits` web hook - attribute will only contain the first 20 for performance reasons. Loading +> **Note:** When more than 20 commits are pushed at once, the `commits` webhook + attribute will only contain the first 20 for performance reasons. Loading detailed commit data is expensive. Note that despite only 20 commits being present in the `commits` attribute, the `total_commits_count` attribute will contain the actual total. @@ -1157,10 +1157,11 @@ its description: ``` It will appear in the webhook body as the below (assuming that GitLab is -installed at gitlab.example.com): +installed at gitlab.example.com, and the project is at +example-group/example-project): ```markdown -![image](https://gitlab.example.com/uploads/$sha/image.png) +![image](https://gitlab.example.com/example-group/example-project/uploads/$sha/image.png) ``` This will not rewrite URLs that already are pointing to HTTP, HTTPS, or @@ -1190,7 +1191,7 @@ From this page, you can repeat delivery with the same data by clicking `Resend R > **Note:** If URL or secret token of the webhook were updated, data will be delivered to the new address. -### Receiving duplicate or multiple web hook requests triggered by one event +### Receiving duplicate or multiple webhook requests triggered by one event When GitLab sends a webhook it expects a response in 10 seconds (set default value). If it does not receive one, it'll retry the webhook. If the endpoint doesn't send its HTTP response within those 10 seconds, GitLab may decide the hook failed and retry it. diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md index ce79bfc0a03..beff4b89424 100644 --- a/doc/user/project/repository/index.md +++ b/doc/user/project/repository/index.md @@ -166,7 +166,11 @@ minutes. ![Repository Languages bar](img/repository_languages.png) Not all files are detected, among others; documentation, -vendored code, and most markup languages are excluded. +vendored code, and most markup languages are excluded. This behaviour can be +adjusted by overriding the default. For example, to enable `.proto` files to be +detected, add the following to `.gitattributes` in the root of your repository. + +> *.proto linguist-detectable=true ## Compare diff --git a/lib/gitlab/hook_data/base_builder.rb b/lib/gitlab/hook_data/base_builder.rb index 4ffca356b29..1a91301e8be 100644 --- a/lib/gitlab/hook_data/base_builder.rb +++ b/lib/gitlab/hook_data/base_builder.rb @@ -25,6 +25,7 @@ module Gitlab markdown_text.gsub(MARKDOWN_SIMPLE_IMAGE) do if $~[:image] url = $~[:url] + url = "#{uploads_prefix}#{url}" if url.start_with?('/uploads') url = "/#{url}" unless url.start_with?('/') "![#{$~[:title]}](#{Gitlab.config.gitlab.url}#{url})" @@ -33,6 +34,16 @@ module Gitlab end end end + + def uploads_prefix + project&.full_path || '' + end + + def project + return unless object.respond_to?(:project) + + object.project + end end end end diff --git a/qa/Dockerfile b/qa/Dockerfile index abf2184e1e2..9956ced0ef6 100644 --- a/qa/Dockerfile +++ b/qa/Dockerfile @@ -3,10 +3,20 @@ LABEL maintainer "Grzegorz Bizon <grzegorz@gitlab.com>" ENV DEBIAN_FRONTEND noninteractive ## +# Add support for stretch-backports +# +RUN echo "deb http://ftp.debian.org/debian stretch-backports main" >> /etc/apt/sources.list + +## # Update APT sources and install some dependencies # RUN sed -i "s/httpredir.debian.org/ftp.us.debian.org/" /etc/apt/sources.list -RUN apt-get update && apt-get install -y wget git unzip xvfb +RUN apt-get update && apt-get install -y wget unzip xvfb + +## +# Install some packages from backports +# +RUN apt-get -y -t stretch-backports install git git-lfs ## # Install Docker diff --git a/qa/qa/page/main/sign_up.rb b/qa/qa/page/main/sign_up.rb index b33ea03fc55..9ca498012eb 100644 --- a/qa/qa/page/main/sign_up.rb +++ b/qa/qa/page/main/sign_up.rb @@ -11,6 +11,7 @@ module QA element :new_user_email_confirmation element :new_user_password element :new_user_register_button + element :new_user_accept_terms end def sign_up!(user) @@ -20,6 +21,8 @@ module QA fill_element :new_user_email_confirmation, user.email fill_element :new_user_password, user.password + check_element :new_user_accept_terms if has_element?(:new_user_accept_terms) + signed_in = with_retry do click_element :new_user_register_button diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb index a5dc9017699..f15129759de 100644 --- a/spec/features/merge_request/user_sees_merge_widget_spec.rb +++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb @@ -423,7 +423,7 @@ describe 'Merge request > User sees merge widget', :js do end it 'shows test reports summary which includes the new failure' do - within(".mr-section-container") do + within(".js-reports-container") do click_button 'Expand' expect(page).to have_content('Test summary contained 1 failed test result out of 2 total tests') @@ -438,7 +438,7 @@ describe 'Merge request > User sees merge widget', :js do context 'when user clicks the new failure' do it 'shows the test report detail' do - within(".mr-section-container") do + within(".js-reports-container") do click_button 'Expand' within(".js-report-section-container") do @@ -468,7 +468,7 @@ describe 'Merge request > User sees merge widget', :js do end it 'shows test reports summary which includes the existing failure' do - within(".mr-section-container") do + within(".js-reports-container") do click_button 'Expand' expect(page).to have_content('Test summary contained 1 failed test result out of 2 total tests') @@ -483,7 +483,7 @@ describe 'Merge request > User sees merge widget', :js do context 'when user clicks the existing failure' do it 'shows test report detail of it' do - within(".mr-section-container") do + within(".js-reports-container") do click_button 'Expand' within(".js-report-section-container") do @@ -519,7 +519,7 @@ describe 'Merge request > User sees merge widget', :js do end it 'shows test reports summary which includes the resolved failure' do - within(".mr-section-container") do + within(".js-reports-container") do click_button 'Expand' expect(page).to have_content('Test summary contained 1 fixed test result out of 2 total tests') @@ -533,7 +533,7 @@ describe 'Merge request > User sees merge widget', :js do context 'when user clicks the resolved failure' do it 'shows test report detail of it' do - within(".mr-section-container") do + within(".js-reports-container") do click_button 'Expand' within(".js-report-section-container") do diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 8e310f38a8c..fb766addb31 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -193,6 +193,23 @@ describe 'Project' do end end + describe 'when the project repository is disabled', :js do + let(:user) { create(:user) } + let(:project) { create(:project, :repository_disabled, :repository, namespace: user.namespace) } + + before do + sign_in(user) + project.add_maintainer(user) + visit project_path(project) + end + + it 'does not show an error' do + wait_for_requests + + expect(page).not_to have_selector('.flash-alert') + end + end + describe 'removal', :js do let(:user) { create(:user) } let(:project) { create(:project, namespace: user.namespace) } diff --git a/spec/javascripts/ide/stores/modules/file_templates/mutations_spec.js b/spec/javascripts/ide/stores/modules/file_templates/mutations_spec.js index a51527d699f..8e0e3ae99a1 100644 --- a/spec/javascripts/ide/stores/modules/file_templates/mutations_spec.js +++ b/spec/javascripts/ide/stores/modules/file_templates/mutations_spec.js @@ -49,6 +49,14 @@ describe('IDE file templates mutations', () => { expect(state.selectedTemplateType).toBe('type'); }); + + it('clears templates', () => { + state.templates = ['test']; + + mutations[types.SET_SELECTED_TEMPLATE_TYPE](state, 'type'); + + expect(state.templates).toEqual([]); + }); }); describe(types.SET_UPDATE_SUCCESS, () => { diff --git a/spec/lib/gitlab/hook_data/base_builder_spec.rb b/spec/lib/gitlab/hook_data/base_builder_spec.rb index a921dd766c3..e3c5ee3b905 100644 --- a/spec/lib/gitlab/hook_data/base_builder_spec.rb +++ b/spec/lib/gitlab/hook_data/base_builder_spec.rb @@ -8,57 +8,94 @@ describe Gitlab::HookData::BaseBuilder do end end - subject { subclass.new(nil) } - using RSpec::Parameterized::TableSyntax - where do - { - 'relative image URL' => { - input: '![an image](foo.png)', - output: "![an image](#{Gitlab.config.gitlab.url}/foo.png)" - }, - 'HTTP URL' => { - input: '![an image](http://example.com/foo.png)', - output: '![an image](http://example.com/foo.png)' - }, - 'HTTPS URL' => { - input: '![an image](https://example.com/foo.png)', - output: '![an image](https://example.com/foo.png)' - }, - 'protocol-relative URL' => { - input: '![an image](//example.com/foo.png)', - output: '![an image](//example.com/foo.png)' - }, - 'URL reference by title' => { - input: "![foo]\n\n[foo]: foo.png", - output: "![foo]\n\n[foo]: foo.png" - }, - 'URL reference by label' => { - input: "![][foo]\n\n[foo]: foo.png", - output: "![][foo]\n\n[foo]: foo.png" - }, - 'in Markdown inline code block' => { - input: '`![an image](foo.png)`', - output: "`![an image](#{Gitlab.config.gitlab.url}/foo.png)`" - }, - 'in HTML tag on the same line' => { - input: '<p>![an image](foo.png)</p>', - output: "<p>![an image](#{Gitlab.config.gitlab.url}/foo.png)</p>" - }, - 'in Markdown multi-line code block' => { - input: "```\n![an image](foo.png)\n```", - output: "```\n![an image](foo.png)\n```" - }, - 'in HTML tag on different lines' => { - input: "<p>\n![an image](foo.png)\n</p>", - output: "<p>\n![an image](foo.png)\n</p>" + context 'with an upload prefix specified' do + let(:project_with_path) { double(full_path: 'baz/bar') } + let(:object_with_project) { double(project: project_with_path) } + subject { subclass.new(object_with_project) } + + where do + { + 'relative image URL' => { + input: '![an image](foo.png)', + output: "![an image](#{Gitlab.config.gitlab.url}/foo.png)" + }, + 'absolute upload URL' => { + input: '![an image](/uploads/foo.png)', + output: "![an image](#{Gitlab.config.gitlab.url}/baz/bar/uploads/foo.png)" + }, + 'absolute non-upload URL' => { + input: '![an image](/downloads/foo.png)', + output: "![an image](#{Gitlab.config.gitlab.url}/downloads/foo.png)" + } } - } + end + + with_them do + it { expect(subject.absolute_image_urls(input)).to eq(output) } + end end - with_them do - it { expect(subject.absolute_image_urls(input)).to eq(output) } + context 'without an upload prefix specified' do + subject { subclass.new(nil) } + + where do + { + 'relative image URL' => { + input: '![an image](foo.png)', + output: "![an image](#{Gitlab.config.gitlab.url}/foo.png)" + }, + 'absolute upload URL' => { + input: '![an image](/uploads/foo.png)', + output: "![an image](#{Gitlab.config.gitlab.url}/uploads/foo.png)" + }, + 'absolute non-upload URL' => { + input: '![an image](/downloads/foo.png)', + output: "![an image](#{Gitlab.config.gitlab.url}/downloads/foo.png)" + }, + 'HTTP URL' => { + input: '![an image](http://example.com/foo.png)', + output: '![an image](http://example.com/foo.png)' + }, + 'HTTPS URL' => { + input: '![an image](https://example.com/foo.png)', + output: '![an image](https://example.com/foo.png)' + }, + 'protocol-relative URL' => { + input: '![an image](//example.com/foo.png)', + output: '![an image](//example.com/foo.png)' + }, + 'URL reference by title' => { + input: "![foo]\n\n[foo]: foo.png", + output: "![foo]\n\n[foo]: foo.png" + }, + 'URL reference by label' => { + input: "![][foo]\n\n[foo]: foo.png", + output: "![][foo]\n\n[foo]: foo.png" + }, + 'in Markdown inline code block' => { + input: '`![an image](foo.png)`', + output: "`![an image](#{Gitlab.config.gitlab.url}/foo.png)`" + }, + 'in HTML tag on the same line' => { + input: '<p>![an image](foo.png)</p>', + output: "<p>![an image](#{Gitlab.config.gitlab.url}/foo.png)</p>" + }, + 'in Markdown multi-line code block' => { + input: "```\n![an image](foo.png)\n```", + output: "```\n![an image](foo.png)\n```" + }, + 'in HTML tag on different lines' => { + input: "<p>\n![an image](foo.png)\n</p>", + output: "<p>\n![an image](foo.png)\n</p>" + } + } + end + + with_them do + it { expect(subject.absolute_image_urls(input)).to eq(output) } + end end end end diff --git a/spec/lib/gitlab/hook_data/issue_builder_spec.rb b/spec/lib/gitlab/hook_data/issue_builder_spec.rb index 60093474f8a..f066c0e3813 100644 --- a/spec/lib/gitlab/hook_data/issue_builder_spec.rb +++ b/spec/lib/gitlab/hook_data/issue_builder_spec.rb @@ -46,7 +46,10 @@ describe Gitlab::HookData::IssueBuilder do let(:builder) { described_class.new(issue_with_description) } it 'sets the image to use an absolute URL' do - expect(data[:description]).to eq("test![Issue_Image](#{Settings.gitlab.url}/uploads/abc/Issue_Image.png)") + expected_path = "#{issue_with_description.project.path_with_namespace}/uploads/abc/Issue_Image.png" + + expect(data[:description]) + .to eq("test![Issue_Image](#{Settings.gitlab.url}/#{expected_path})") end end end diff --git a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb index dd586af6118..9ce697adbba 100644 --- a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb +++ b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb @@ -58,11 +58,14 @@ describe Gitlab::HookData::MergeRequestBuilder do end context 'when the MR has an image in the description' do - let(:mr_with_description) { create(:merge_request, description: 'test![Issue_Image](/uploads/abc/Issue_Image.png)') } + let(:mr_with_description) { create(:merge_request, description: 'test![MR_Image](/uploads/abc/MR_Image.png)') } let(:builder) { described_class.new(mr_with_description) } it 'sets the image to use an absolute URL' do - expect(data[:description]).to eq("test![Issue_Image](#{Settings.gitlab.url}/uploads/abc/Issue_Image.png)") + expected_path = "#{mr_with_description.project.path_with_namespace}/uploads/abc/MR_Image.png" + + expect(data[:description]) + .to eq("test![MR_Image](#{Settings.gitlab.url}/#{expected_path})") end end end diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index d669c42ab4a..69ee5ff4bcd 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -127,6 +127,13 @@ describe Gitlab::UsageData do expect(count_data[:clusters_applications_prometheus]).to eq(1) expect(count_data[:clusters_applications_runner]).to eq(1) end + + it 'works when queries time out' do + allow_any_instance_of(ActiveRecord::Relation) + .to receive(:count).and_raise(ActiveRecord::StatementInvalid.new('')) + + expect { subject }.not_to raise_error + end end describe '#features_usage_data_ce' do diff --git a/spec/support/shared_examples/notify_shared_examples.rb b/spec/support/shared_examples/notify_shared_examples.rb index 5fb9ced3b63..66536e80db2 100644 --- a/spec/support/shared_examples/notify_shared_examples.rb +++ b/spec/support/shared_examples/notify_shared_examples.rb @@ -45,6 +45,20 @@ shared_examples 'an email that contains a header with author username' do end end +shared_examples 'an email with X-GitLab headers containing IDs' do + it 'has X-GitLab-*-ID header' do + is_expected.to have_header "X-GitLab-#{model.class.name}-ID", "#{model.id}" + end + + it 'has X-GitLab-*-IID header if model has iid defined' do + if model.respond_to?(:iid) + is_expected.to have_header "X-GitLab-#{model.class.name}-IID", "#{model.iid}" + else + expect(subject.header["X-GitLab-#{model.class.name}-IID"]).to eq nil + end + end +end + shared_examples 'an email with X-GitLab headers containing project details' do it 'has X-GitLab-Project headers' do aggregate_failures do @@ -69,6 +83,7 @@ end shared_examples 'a thread answer email with reply-by-email enabled' do include_examples 'an email with X-GitLab headers containing project details' + include_examples 'an email with X-GitLab headers containing IDs' it 'has the characteristics of a threaded reply' do host = Gitlab.config.gitlab.host @@ -85,6 +100,7 @@ end shared_examples 'an email starting a new thread with reply-by-email enabled' do include_examples 'an email with X-GitLab headers containing project details' + include_examples 'an email with X-GitLab headers containing IDs' include_examples 'a new thread email with reply-by-email enabled' it 'includes "Reply to this email directly or <View it on GitLab>"' do @@ -109,6 +125,7 @@ end shared_examples 'an answer to an existing thread with reply-by-email enabled' do include_examples 'an email with X-GitLab headers containing project details' + include_examples 'an email with X-GitLab headers containing IDs' include_examples 'a thread answer email with reply-by-email enabled' context 'when reply-by-email is enabled with incoming address with %{key}' do |