summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/awards_handler.js2
-rw-r--r--app/assets/javascripts/due_date_select.js2
-rw-r--r--app/assets/javascripts/issuable_form.js1
-rw-r--r--app/assets/javascripts/issue_show/components/description.vue14
-rw-r--r--app/assets/javascripts/main.js3
-rw-r--r--app/assets/javascripts/member_expiration_date.js1
-rw-r--r--app/assets/javascripts/merge_request.js8
-rw-r--r--app/assets/javascripts/notes/components/note_actions.vue9
-rw-r--r--app/assets/javascripts/notes/components/note_awards_list.vue2
-rw-r--r--app/assets/javascripts/pages/projects/graphs/charts/index.js18
-rw-r--r--app/assets/javascripts/pages/users/activity_calendar.js10
-rw-r--r--app/assets/javascripts/pages/users/user_tabs.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue40
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue38
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue91
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue278
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/pikaday.vue1
-rw-r--r--app/assets/stylesheets/framework/common.scss9
-rw-r--r--app/assets/stylesheets/framework/variables.scss1
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss43
-rw-r--r--app/controllers/profiles/preferences_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests/application_controller.rb3
-rw-r--r--app/helpers/application_settings_helper.rb1
-rw-r--r--app/helpers/preferences_helper.rb15
-rw-r--r--app/models/application_setting.rb1
-rw-r--r--app/models/user.rb3
-rw-r--r--app/services/merge_requests/update_service.rb7
-rw-r--r--app/services/task_list_toggle_service.rb3
-rw-r--r--app/views/admin/application_settings/_localization.html.haml11
-rw-r--r--app/views/admin/application_settings/preferences.html.haml11
-rw-r--r--app/views/award_emoji/_awards_block.html.haml4
-rw-r--r--app/views/profiles/preferences/show.html.haml18
-rw-r--r--changelogs/unreleased/2105-add-setting-for-first-day-of-the-week.yml5
-rw-r--r--changelogs/unreleased/56014-better-squash-commit-messages.yml6
-rw-r--r--changelogs/unreleased/tooltips-to-top.yml5
-rw-r--r--db/migrate/20181027114222_add_first_day_of_week_to_user_preferences.rb9
-rw-r--r--db/migrate/20181028120717_add_first_day_of_week_to_application_settings.rb16
-rw-r--r--db/schema.rb2
-rw-r--r--doc/api/settings.md3
-rw-r--r--doc/development/contributing/issue_workflow.md2
-rw-r--r--doc/topics/autodevops/index.md6
-rw-r--r--doc/user/group/clusters/index.md3
-rw-r--r--doc/user/profile/preferences.md8
-rw-r--r--doc/user/project/clusters/index.md3
-rw-r--r--doc/user/project/merge_requests/img/squash_mr_message.pngbin0 -> 150302 bytes
-rw-r--r--doc/user/project/merge_requests/squash_and_merge.md15
-rw-r--r--lib/gitlab/gon_helper.rb1
-rw-r--r--locale/gitlab.pot80
-rw-r--r--qa/qa/page/main/login.rb14
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb3
-rw-r--r--qa/spec/spec_helper.rb14
-rw-r--r--spec/controllers/profiles/preferences_controller_spec.rb3
-rw-r--r--spec/features/merge_request/user_accepts_merge_request_spec.rb4
-rw-r--r--spec/features/merge_request/user_customizes_merge_commit_message_spec.rb10
-rw-r--r--spec/helpers/preferences_helper_spec.rb24
-rw-r--r--spec/javascripts/issue_show/components/description_spec.js10
-rw-r--r--spec/javascripts/merge_request_spec.js53
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/commit_edit_spec.js85
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js61
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_commits_header_spec.js110
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js153
-rw-r--r--spec/javascripts/vue_mr_widget/mock_data.js2
-rw-r--r--spec/migrations/clean_up_for_members_spec.rb4
-rw-r--r--spec/services/issues/update_service_spec.rb72
-rw-r--r--spec/services/merge_requests/update_service_spec.rb2
-rw-r--r--spec/services/task_list_toggle_service_spec.rb11
-rw-r--r--spec/support/shared_examples/issuable_shared_examples.rb73
74 files changed, 1187 insertions, 356 deletions
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index cace8bb9dba..73ce3e760ab 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -437,7 +437,7 @@ export class AwardsHandler {
createAwardButtonForVotesBlock(votesBlock, emojiName) {
const buttonHtml = `
- <button class="btn award-control js-emoji-btn has-tooltip active" title="You" data-placement="bottom">
+ <button class="btn award-control js-emoji-btn has-tooltip active" title="You">
${this.emoji.glEmojiTag(emojiName)}
<span class="award-control-text js-counter">1</span>
</button>
diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js
index dbfcf8cc921..cb1b1173190 100644
--- a/app/assets/javascripts/due_date_select.js
+++ b/app/assets/javascripts/due_date_select.js
@@ -64,6 +64,7 @@ class DueDateSelect {
this.saveDueDate(true);
}
},
+ firstDay: gon.first_day_of_week,
});
calendar.setDate(parsePikadayDate($dueDateInput.val()));
@@ -183,6 +184,7 @@ export default class DueDateSelectors {
onSelect(dateText) {
$datePicker.val(calendar.toString(dateText));
},
+ firstDay: gon.first_day_of_week,
});
calendar.setDate(parsePikadayDate(datePickerVal));
diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js
index 4d2533d01f1..9336b71cfd7 100644
--- a/app/assets/javascripts/issuable_form.js
+++ b/app/assets/javascripts/issuable_form.js
@@ -44,6 +44,7 @@ export default class IssuableForm {
parse: dateString => parsePikadayDate(dateString),
toString: date => pikadayToString(date),
onSelect: dateText => $issuableDueDate.val(calendar.toString(dateText)),
+ firstDay: gon.first_day_of_week,
});
calendar.setDate(parsePikadayDate($issuableDueDate.val()));
}
diff --git a/app/assets/javascripts/issue_show/components/description.vue b/app/assets/javascripts/issue_show/components/description.vue
index e664269b199..58f14bac8c8 100644
--- a/app/assets/javascripts/issue_show/components/description.vue
+++ b/app/assets/javascripts/issue_show/components/description.vue
@@ -1,6 +1,7 @@
<script>
import $ from 'jquery';
-import { __ } from '~/locale';
+import { s__, sprintf } from '~/locale';
+import createFlash from '~/flash';
import animateMixin from '../mixins/animate';
import TaskList from '../../task_list';
import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor';
@@ -91,9 +92,14 @@ export default {
},
taskListUpdateError() {
- window.Flash(
- __(
- 'Someone edited this issue at the same time you did. The description has been updated and you will need to make your changes again.',
+ createFlash(
+ sprintf(
+ s__(
+ 'Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again.',
+ ),
+ {
+ issueType: this.issuableType,
+ },
),
);
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 8e10b3ad912..63db4938cd7 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -124,9 +124,6 @@ function deferredInitialisation() {
selector: '.has-tooltip, [data-toggle="tooltip"]',
trigger: 'hover',
boundary: 'viewport',
- placement(tip, el) {
- return $(el).data('placement') || 'bottom';
- },
});
// Initialize popovers
diff --git a/app/assets/javascripts/member_expiration_date.js b/app/assets/javascripts/member_expiration_date.js
index 0beedcacf33..0dabb28ea66 100644
--- a/app/assets/javascripts/member_expiration_date.js
+++ b/app/assets/javascripts/member_expiration_date.js
@@ -33,6 +33,7 @@ export default function memberExpirationDate(selector = '.js-access-expiration-d
toggleClearInput.call($input);
},
+ firstDay: gon.first_day_of_week,
});
calendar.setDate(parsePikadayDate($input.val()));
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index ac3b47cd218..3b42a154af8 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -2,6 +2,7 @@
import $ from 'jquery';
import { __ } from '~/locale';
+import createFlash from '~/flash';
import TaskList from './task_list';
import MergeRequestTabs from './merge_request_tabs';
import IssuablesHelper from './helpers/issuables_helper';
@@ -40,6 +41,13 @@ function MergeRequest(opts) {
document.querySelector('#task_status').innerText = result.task_status;
document.querySelector('#task_status_short').innerText = result.task_status_short;
},
+ onError: () => {
+ createFlash(
+ __(
+ 'Someone edited this merge request at the same time you did. Please refresh the page to see changes.',
+ ),
+ );
+ },
});
}
}
diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue
index 394f2a80a67..cad0d382fa2 100644
--- a/app/assets/javascripts/notes/components/note_actions.vue
+++ b/app/assets/javascripts/notes/components/note_actions.vue
@@ -151,10 +151,9 @@ export default {
</div>
<div v-if="canAwardEmoji" class="note-actions-item">
<a
- v-gl-tooltip.bottom
+ v-gl-tooltip
:class="{ 'js-user-authored': isAuthoredByCurrentUser }"
class="note-action-button note-emoji-button js-add-award js-note-emoji"
- data-position="right"
href="#"
title="Add reaction"
>
@@ -175,7 +174,7 @@ export default {
/>
<div v-if="canEdit" class="note-actions-item">
<button
- v-gl-tooltip.bottom
+ v-gl-tooltip
type="button"
title="Edit comment"
class="note-action-button js-note-edit btn btn-transparent"
@@ -186,7 +185,7 @@ export default {
</div>
<div v-if="showDeleteAction" class="note-actions-item">
<button
- v-gl-tooltip.bottom
+ v-gl-tooltip
type="button"
title="Delete comment"
class="note-action-button js-note-delete btn btn-transparent"
@@ -197,7 +196,7 @@ export default {
</div>
<div v-else-if="shouldShowActionsDropdown" class="dropdown more-actions note-actions-item">
<button
- v-gl-tooltip.bottom
+ v-gl-tooltip
type="button"
title="More actions"
class="note-action-button more-actions-toggle btn btn-transparent"
diff --git a/app/assets/javascripts/notes/components/note_awards_list.vue b/app/assets/javascripts/notes/components/note_awards_list.vue
index 3efdd1c5c17..17e5fcab5b7 100644
--- a/app/assets/javascripts/notes/components/note_awards_list.vue
+++ b/app/assets/javascripts/notes/components/note_awards_list.vue
@@ -171,7 +171,6 @@ export default {
:class="getAwardClassBindings(awardList)"
:title="awardTitle(awardList)"
data-boundary="viewport"
- data-placement="bottom"
class="btn award-control"
type="button"
@click="handleAward(awardName)"
@@ -187,7 +186,6 @@ export default {
title="Add reaction"
aria-label="Add reaction"
data-boundary="viewport"
- data-placement="bottom"
type="button"
>
<span class="award-control-icon award-control-icon-neutral">
diff --git a/app/assets/javascripts/pages/projects/graphs/charts/index.js b/app/assets/javascripts/pages/projects/graphs/charts/index.js
index 3ccad513c05..26d7fa7371d 100644
--- a/app/assets/javascripts/pages/projects/graphs/charts/index.js
+++ b/app/assets/javascripts/pages/projects/graphs/charts/index.js
@@ -43,10 +43,26 @@ document.addEventListener('DOMContentLoaded', () => {
],
});
+ const reorderWeekDays = (weekDays, firstDayOfWeek = 0) => {
+ if (firstDayOfWeek === 0) {
+ return weekDays;
+ }
+
+ return Object.keys(weekDays).reduce((acc, dayName, idx, arr) => {
+ const reorderedDayName = arr[(idx + firstDayOfWeek) % arr.length];
+
+ return {
+ ...acc,
+ [reorderedDayName]: weekDays[reorderedDayName],
+ };
+ }, {});
+ };
+
const hourData = chartData(projectChartData.hour);
responsiveChart($('#hour-chart'), hourData);
- const dayData = chartData(projectChartData.weekDays);
+ const weekDays = reorderWeekDays(projectChartData.weekDays, gon.first_day_of_week);
+ const dayData = chartData(weekDays);
responsiveChart($('#weekday-chart'), dayData);
const monthData = chartData(projectChartData.month);
diff --git a/app/assets/javascripts/pages/users/activity_calendar.js b/app/assets/javascripts/pages/users/activity_calendar.js
index 8a84ac37dab..afa099d0e0b 100644
--- a/app/assets/javascripts/pages/users/activity_calendar.js
+++ b/app/assets/javascripts/pages/users/activity_calendar.js
@@ -159,7 +159,7 @@ export default class ActivityCalendar {
.append('g')
.attr('transform', (group, i) => {
_.each(group, (stamp, a) => {
- if (a === 0 && stamp.day === 0) {
+ if (a === 0 && stamp.day === this.firstDayOfWeek) {
const month = stamp.date.getMonth();
const x = this.daySizeWithSpace * i + 1 + this.daySizeWithSpace;
const lastMonth = _.last(this.months);
@@ -205,6 +205,14 @@ export default class ActivityCalendar {
y: 29 + this.dayYPos(5),
},
];
+
+ if (this.firstDayOfWeek === 1) {
+ days.push({
+ text: 'S',
+ y: 29 + this.dayYPos(7),
+ });
+ }
+
this.svg
.append('g')
.selectAll('text')
diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js
index 1c3fd58ca74..39cd891c111 100644
--- a/app/assets/javascripts/pages/users/user_tabs.js
+++ b/app/assets/javascripts/pages/users/user_tabs.js
@@ -234,7 +234,7 @@ export default class UserTabs {
data,
calendarActivitiesPath,
utcOffset,
- 0,
+ gon.first_day_of_week,
monthsAgo,
);
}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue
new file mode 100644
index 00000000000..a38f25cce35
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue
@@ -0,0 +1,40 @@
+<script>
+export default {
+ props: {
+ value: {
+ type: String,
+ required: true,
+ },
+ label: {
+ type: String,
+ required: true,
+ },
+ inputId: {
+ type: String,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <li>
+ <div class="commit-message-editor">
+ <div class="d-flex flex-wrap align-items-center justify-content-between">
+ <label class="col-form-label" :for="inputId">
+ <strong>{{ label }}</strong>
+ </label>
+ <slot name="header"></slot>
+ </div>
+ <textarea
+ :id="inputId"
+ :value="value"
+ class="form-control js-gfm-input append-bottom-default commit-message-edit"
+ required="required"
+ rows="7"
+ @input="$emit('input', $event.target.value)"
+ ></textarea>
+ <slot name="checkbox"></slot>
+ </div>
+ </li>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue
new file mode 100644
index 00000000000..b3c1c0e329d
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue
@@ -0,0 +1,38 @@
+<script>
+import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlDropdown,
+ GlDropdownItem,
+ },
+ props: {
+ commits: {
+ type: Array,
+ required: true,
+ default: () => [],
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <gl-dropdown
+ right
+ no-caret
+ text="Use an existing commit message"
+ variant="link"
+ class="mr-commit-dropdown"
+ >
+ <gl-dropdown-item
+ v-for="commit in commits"
+ :key="commit.short_id"
+ class="text-nowrap text-truncate"
+ @click="$emit('input', commit.message)"
+ >
+ <span class="monospace mr-2">{{ commit.short_id }}</span> {{ commit.title }}
+ </gl-dropdown-item>
+ </gl-dropdown>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
new file mode 100644
index 00000000000..a1d3a09cca4
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
@@ -0,0 +1,91 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import _ from 'underscore';
+import { __, n__, sprintf, s__ } from '~/locale';
+import Icon from '~/vue_shared/components/icon.vue';
+
+export default {
+ components: {
+ Icon,
+ GlButton,
+ },
+ props: {
+ isSquashEnabled: {
+ type: Boolean,
+ required: true,
+ },
+ commitsCount: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
+ targetBranch: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ expanded: false,
+ };
+ },
+ computed: {
+ collapseIcon() {
+ return this.expanded ? 'chevron-down' : 'chevron-right';
+ },
+ commitsCountMessage() {
+ return n__(__('%d commit'), __('%d commits'), this.isSquashEnabled ? 1 : this.commitsCount);
+ },
+ modifyLinkMessage() {
+ return this.isSquashEnabled ? __('Modify commit messages') : __('Modify merge commit');
+ },
+ ariaLabel() {
+ return this.expanded ? __('Collapse') : __('Expand');
+ },
+ message() {
+ return sprintf(
+ s__(
+ 'mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}.',
+ ),
+ {
+ commitCount: `<strong class="commits-count-message">${this.commitsCountMessage}</strong>`,
+ mergeCommitCount: `<strong>${s__('mrWidgetCommitsAdded|1 merge commit')}</strong>`,
+ targetBranch: `<span class="label-branch">${_.escape(this.targetBranch)}</span>`,
+ },
+ false,
+ );
+ },
+ },
+ methods: {
+ toggle() {
+ this.expanded = !this.expanded;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div
+ class="js-mr-widget-commits-count mr-widget-extension clickable d-flex align-items-center px-3 py-2"
+ @click="toggle()"
+ >
+ <gl-button
+ :aria-label="ariaLabel"
+ variant="blank"
+ class="commit-edit-toggle mr-2"
+ @click.stop="toggle()"
+ >
+ <icon :name="collapseIcon" :size="16" />
+ </gl-button>
+ <span v-if="expanded">{{ __('Collapse') }}</span>
+ <span v-else>
+ <span v-html="message"></span>
+ <gl-button variant="link" class="modify-message-button">
+ {{ modifyLinkMessage }}
+ </gl-button>
+ </span>
+ </div>
+ <div v-show="expanded"><slot></slot></div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
index b8f29649eb5..ce4207864ea 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
@@ -2,17 +2,24 @@
import successSvg from 'icons/_icon_status_success.svg';
import warningSvg from 'icons/_icon_status_warning.svg';
import simplePoll from '~/lib/utils/simple_poll';
+import { __ } from '~/locale';
import MergeRequest from '../../../merge_request';
import Flash from '../../../flash';
import statusIcon from '../mr_widget_status_icon.vue';
import eventHub from '../../event_hub';
import SquashBeforeMerge from './squash_before_merge.vue';
+import CommitsHeader from './commits_header.vue';
+import CommitEdit from './commit_edit.vue';
+import CommitMessageDropdown from './commit_message_dropdown.vue';
export default {
name: 'ReadyToMerge',
components: {
statusIcon,
SquashBeforeMerge,
+ CommitsHeader,
+ CommitEdit,
+ CommitMessageDropdown,
},
props: {
mr: { type: Object, required: true },
@@ -22,27 +29,20 @@ export default {
return {
removeSourceBranch: this.mr.shouldRemoveSourceBranch,
mergeWhenBuildSucceeds: false,
- useCommitMessageWithDescription: false,
setToMergeWhenPipelineSucceeds: false,
- showCommitMessageEditor: false,
isMakingRequest: false,
isMergingImmediately: false,
commitMessage: this.mr.commitMessage,
squashBeforeMerge: this.mr.squash,
successSvg,
warningSvg,
+ squashCommitMessage: this.mr.squashCommitMessage,
};
},
computed: {
shouldShowMergeWhenPipelineSucceedsText() {
return this.mr.isPipelineActive;
},
- commitMessageLinkTitle() {
- const withDesc = 'Include description in commit message';
- const withoutDesc = "Don't include description in commit message";
-
- return this.useCommitMessageWithDescription ? withoutDesc : withDesc;
- },
status() {
const { pipeline, isPipelineActive, isPipelineFailed, hasCI, ciStatus } = this.mr;
@@ -84,9 +84,9 @@ export default {
},
mergeButtonText() {
if (this.isMergingImmediately) {
- return 'Merge in progress';
+ return __('Merge in progress');
} else if (this.shouldShowMergeWhenPipelineSucceedsText) {
- return 'Merge when pipeline succeeds';
+ return __('Merge when pipeline succeeds');
}
return 'Merge';
@@ -98,7 +98,7 @@ export default {
const { commitMessage } = this;
return Boolean(
!commitMessage.length ||
- !this.shouldShowMergeControls() ||
+ !this.shouldShowMergeControls ||
this.isMakingRequest ||
this.mr.preventMerge,
);
@@ -110,18 +110,14 @@ export default {
const { commitsCount, enableSquashBeforeMerge } = this.mr;
return enableSquashBeforeMerge && commitsCount > 1;
},
- },
- methods: {
shouldShowMergeControls() {
return this.mr.isMergeAllowed || this.shouldShowMergeWhenPipelineSucceedsText;
},
- updateCommitMessage() {
- const cmwd = this.mr.commitMessageWithDescription;
- this.useCommitMessageWithDescription = !this.useCommitMessageWithDescription;
- this.commitMessage = this.useCommitMessageWithDescription ? cmwd : this.mr.commitMessage;
- },
- toggleCommitMessageEditor() {
- this.showCommitMessageEditor = !this.showCommitMessageEditor;
+ },
+ methods: {
+ updateMergeCommitMessage(includeDescription) {
+ const { commitMessageWithDescription, commitMessage } = this.mr;
+ this.commitMessage = includeDescription ? commitMessageWithDescription : commitMessage;
},
handleMergeButtonClick(mergeWhenBuildSucceeds, mergeImmediately) {
// TODO: Remove no-param-reassign
@@ -139,6 +135,7 @@ export default {
merge_when_pipeline_succeeds: this.setToMergeWhenPipelineSucceeds,
should_remove_source_branch: this.removeSourceBranch === true,
squash: this.squashBeforeMerge,
+ squash_commit_message: this.squashCommitMessage,
};
this.isMakingRequest = true;
@@ -158,7 +155,7 @@ export default {
})
.catch(() => {
this.isMakingRequest = false;
- new Flash('Something went wrong. Please try again.'); // eslint-disable-line
+ new Flash(__('Something went wrong. Please try again.')); // eslint-disable-line
});
},
initiateMergePolling() {
@@ -194,7 +191,7 @@ export default {
}
})
.catch(() => {
- new Flash('Something went wrong while merging this merge request. Please try again.'); // eslint-disable-line
+ new Flash(__('Something went wrong while merging this merge request. Please try again.')); // eslint-disable-line
});
},
initiateRemoveSourceBranchPolling() {
@@ -223,7 +220,7 @@ export default {
}
})
.catch(() => {
- new Flash('Something went wrong while deleting the source branch. Please try again.'); // eslint-disable-line
+ new Flash(__('Something went wrong while deleting the source branch. Please try again.')); // eslint-disable-line
});
},
},
@@ -231,127 +228,136 @@ export default {
</script>
<template>
- <div class="mr-widget-body media">
- <status-icon :status="iconClass" />
- <div class="media-body">
- <div class="mr-widget-body-controls media space-children">
- <span class="btn-group">
- <button
- :disabled="isMergeButtonDisabled"
- :class="mergeButtonClass"
- type="button"
- class="qa-merge-button"
- @click="handleMergeButtonClick()"
- >
- <i v-if="isMakingRequest" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
- {{ mergeButtonText }}
- </button>
- <button
- v-if="shouldShowMergeOptionsDropdown"
- :disabled="isMergeButtonDisabled"
- type="button"
- class="btn btn-sm btn-info dropdown-toggle js-merge-moment"
- data-toggle="dropdown"
- aria-label="Select merge moment"
- >
- <i class="fa fa-chevron-down qa-merge-moment-dropdown" aria-hidden="true"></i>
- </button>
- <ul
- v-if="shouldShowMergeOptionsDropdown"
- class="dropdown-menu dropdown-menu-right"
- role="menu"
- >
- <li>
- <a
- class="merge_when_pipeline_succeeds qa-merge-when-pipeline-succeeds-option"
- href="#"
- @click.prevent="handleMergeButtonClick(true)"
- >
- <span class="media">
- <span class="merge-opt-icon" aria-hidden="true" v-html="successSvg"></span>
- <span class="media-body merge-opt-title">Merge when pipeline succeeds</span>
- </span>
- </a>
- </li>
- <li>
- <a
- class="accept-merge-request qa-merge-immediately-option"
- href="#"
- @click.prevent="handleMergeButtonClick(false, true)"
- >
- <span class="media">
- <span class="merge-opt-icon" aria-hidden="true" v-html="warningSvg"></span>
- <span class="media-body merge-opt-title">Merge immediately</span>
- </span>
- </a>
- </li>
- </ul>
- </span>
- <div class="media-body-wrap space-children">
- <template v-if="shouldShowMergeControls()">
- <label v-if="mr.canRemoveSourceBranch">
- <input
- id="remove-source-branch-input"
- v-model="removeSourceBranch"
- :disabled="isRemoveSourceBranchButtonDisabled"
- class="js-remove-source-branch-checkbox"
- type="checkbox"
- />
- Delete source branch
- </label>
-
- <!-- Placeholder for EE extension of this component -->
- <squash-before-merge
- v-if="shouldShowSquashBeforeMerge"
- v-model="squashBeforeMerge"
- :help-path="mr.squashBeforeMergeHelpPath"
- :is-disabled="isMergeButtonDisabled"
- />
-
- <span v-if="mr.ffOnlyEnabled" class="js-fast-forward-message">
- Fast-forward merge without a merge commit
- </span>
+ <div>
+ <div class="mr-widget-body media">
+ <status-icon :status="iconClass" />
+ <div class="media-body">
+ <div class="mr-widget-body-controls media space-children">
+ <span class="btn-group">
<button
- v-else
:disabled="isMergeButtonDisabled"
- class="js-modify-commit-message-button btn btn-default btn-sm"
+ :class="mergeButtonClass"
type="button"
- @click="toggleCommitMessageEditor"
+ class="qa-merge-button"
+ @click="handleMergeButtonClick()"
>
- Modify commit message
+ <i v-if="isMakingRequest" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
+ {{ mergeButtonText }}
</button>
- </template>
- <template v-else>
- <span class="bold js-resolve-mr-widget-items-message">
- You can only merge once the items above are resolved
- </span>
- </template>
- </div>
- </div>
- <div v-if="showCommitMessageEditor" class="prepend-top-default commit-message-editor">
- <div class="form-group clearfix">
- <label class="col-form-label" for="commit-message"> Commit message </label>
- <div class="col-sm-10">
- <div class="commit-message-container">
- <div class="max-width-marker"></div>
- <textarea
- id="commit-message"
- v-model="commitMessage"
- class="form-control js-commit-message"
- required="required"
- rows="14"
- name="Commit message"
- ></textarea>
- </div>
- <p class="hint">
- Try to keep the first line under 52 characters and the others under 72
- </p>
- <div class="hint">
- <a href="#" @click.prevent="updateCommitMessage"> {{ commitMessageLinkTitle }} </a>
- </div>
+ <button
+ v-if="shouldShowMergeOptionsDropdown"
+ :disabled="isMergeButtonDisabled"
+ type="button"
+ class="btn btn-sm btn-info dropdown-toggle js-merge-moment"
+ data-toggle="dropdown"
+ aria-label="Select merge moment"
+ >
+ <i class="fa fa-chevron-down qa-merge-moment-dropdown" aria-hidden="true"></i>
+ </button>
+ <ul
+ v-if="shouldShowMergeOptionsDropdown"
+ class="dropdown-menu dropdown-menu-right"
+ role="menu"
+ >
+ <li>
+ <a
+ class="merge_when_pipeline_succeeds qa-merge-when-pipeline-succeeds-option"
+ href="#"
+ @click.prevent="handleMergeButtonClick(true)"
+ >
+ <span class="media">
+ <span class="merge-opt-icon" aria-hidden="true" v-html="successSvg"></span>
+ <span class="media-body merge-opt-title">{{
+ __('Merge when pipeline succeeds')
+ }}</span>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a
+ class="accept-merge-request qa-merge-immediately-option"
+ href="#"
+ @click.prevent="handleMergeButtonClick(false, true)"
+ >
+ <span class="media">
+ <span class="merge-opt-icon" aria-hidden="true" v-html="warningSvg"></span>
+ <span class="media-body merge-opt-title">{{ __('Merge immediately') }}</span>
+ </span>
+ </a>
+ </li>
+ </ul>
+ </span>
+ <div class="media-body-wrap space-children">
+ <template v-if="shouldShowMergeControls">
+ <label v-if="mr.canRemoveSourceBranch">
+ <input
+ id="remove-source-branch-input"
+ v-model="removeSourceBranch"
+ :disabled="isRemoveSourceBranchButtonDisabled"
+ class="js-remove-source-branch-checkbox"
+ type="checkbox"
+ />
+ {{ __('Delete source branch') }}
+ </label>
+
+ <!-- Placeholder for EE extension of this component -->
+ <squash-before-merge
+ v-if="shouldShowSquashBeforeMerge"
+ v-model="squashBeforeMerge"
+ :help-path="mr.squashBeforeMergeHelpPath"
+ :is-disabled="isMergeButtonDisabled"
+ />
+ </template>
+ <template v-else>
+ <span class="bold js-resolve-mr-widget-items-message">
+ {{ __('You can only merge once the items above are resolved') }}
+ </span>
+ </template>
</div>
</div>
</div>
</div>
+ <template v-if="shouldShowMergeControls">
+ <div v-if="mr.ffOnlyEnabled" class="mr-fast-forward-message">
+ {{ __('Fast-forward merge without a merge commit') }}
+ </div>
+ <template v-else>
+ <commits-header
+ :is-squash-enabled="squashBeforeMerge"
+ :commits-count="mr.commitsCount"
+ :target-branch="mr.targetBranch"
+ >
+ <ul class="border-top content-list commits-list flex-list">
+ <commit-edit
+ v-if="squashBeforeMerge"
+ v-model="squashCommitMessage"
+ :label="__('Squash commit message')"
+ input-id="squash-message-edit"
+ squash
+ >
+ <commit-message-dropdown
+ slot="header"
+ v-model="squashCommitMessage"
+ :commits="mr.commits"
+ />
+ </commit-edit>
+ <commit-edit
+ v-model="commitMessage"
+ :label="__('Merge commit message')"
+ input-id="merge-message-edit"
+ >
+ <label slot="checkbox">
+ <input
+ id="include-description"
+ type="checkbox"
+ @change="updateMergeCommitMessage($event.target.checked)"
+ />
+ {{ __('Include merge request description') }}
+ </label>
+ </commit-edit>
+ </ul>
+ </commits-header>
+ </template>
+ </template>
</div>
</template>
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 57c4dfbe3b7..abbbe19c5ef 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
@@ -315,7 +315,7 @@ export default {
:endpoint="mr.testResultsPath"
/>
- <div class="mr-widget-section">
+ <div class="mr-widget-section p-0">
<component :is="componentName" :mr="mr" :service="service" />
<section v-if="shouldRenderCollaborationStatus" class="mr-info-list mr-links">
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
index 36cac230d9d..58363f632a9 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
@@ -42,6 +42,8 @@ export default class MergeRequestStore {
this.mergePipeline = data.merge_pipeline || {};
this.deployments = this.deployments || data.deployments || [];
this.postMergeDeployments = this.postMergeDeployments || [];
+ this.commits = data.commits_without_merge_commits || [];
+ this.squashCommitMessage = data.default_squash_commit_message;
this.initRebase(data);
if (data.issues_links) {
diff --git a/app/assets/javascripts/vue_shared/components/pikaday.vue b/app/assets/javascripts/vue_shared/components/pikaday.vue
index 8bdb5bf22c2..13eb46437dd 100644
--- a/app/assets/javascripts/vue_shared/components/pikaday.vue
+++ b/app/assets/javascripts/vue_shared/components/pikaday.vue
@@ -40,6 +40,7 @@ export default {
toString: date => pikadayToString(date),
onSelect: this.selected.bind(this),
onClose: this.toggled.bind(this),
+ firstDay: gon.first_day_of_week,
});
this.$el.append(this.calendar.el);
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index cb449b642e7..c5c3b66438c 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -391,6 +391,11 @@ img.emoji {
.flex-no-shrink { flex-shrink: 0; }
.ws-initial { white-space: initial; }
.overflow-auto { overflow: auto; }
+.d-flex-center {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
/** COMMON SIZING CLASSES **/
.w-0 { width: 0; }
@@ -402,6 +407,10 @@ img.emoji {
.min-height-0 { min-height: 0; }
+.w-3 { width: #{3 * $grid-size}; }
+
+.h-3 { width: #{3 * $grid-size}; }
+
/** COMMON SPACING CLASSES **/
.gl-pl-0 { padding-left: 0; }
.gl-pl-1 { padding-left: #{0.5 * $grid-size}; }
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 7088a6f59dc..96dab609a13 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -243,6 +243,7 @@ $gl-padding-8: 8px;
$gl-padding: 16px;
$gl-padding-24: 24px;
$gl-padding-32: 32px;
+$gl-padding-50: 50px;
$gl-col-padding: 15px;
$gl-input-padding: 10px;
$gl-vert-padding: 6px;
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 38a7e199c6a..135730d71e9 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -38,9 +38,7 @@
}
.mr-widget-section {
- .media {
- align-items: center;
- }
+ border-radius: $border-radius-default $border-radius-default 0 0;
.code-text {
flex: 1;
@@ -56,6 +54,11 @@
.mr-widget-extension {
border-top: 1px solid $border-color;
background-color: $gray-light;
+
+ &.clickable:hover {
+ background-color: $gl-gray-200;
+ cursor: pointer;
+ }
}
.mr-widget-workflow {
@@ -78,6 +81,7 @@
border-top: 0;
}
+.mr-widget-body,
.mr-widget-section,
.mr-widget-content,
.mr-widget-footer {
@@ -87,11 +91,38 @@
.mr-state-widget {
color: $gl-text-color;
+ .commit-message-edit {
+ border-radius: $border-radius-default;
+ }
+
.mr-widget-section,
.mr-widget-footer {
border-top: solid 1px $border-color;
}
+ .mr-fast-forward-message {
+ padding-left: $gl-padding-50;
+ padding-bottom: $gl-padding;
+ }
+
+ .commits-list {
+ > li {
+ padding: $gl-padding;
+
+ @include media-breakpoint-up(md) {
+ padding-left: $gl-padding-50;
+ }
+ }
+ }
+
+ .mr-commit-dropdown {
+ .dropdown-menu {
+ @include media-breakpoint-up(md) {
+ width: 150%;
+ }
+ }
+ }
+
.mr-widget-footer {
padding: 0;
}
@@ -405,7 +436,7 @@
}
.mr-widget-help {
- padding: 10px 16px 10px 48px;
+ padding: 10px 16px 10px $gl-padding-50;
font-style: italic;
}
@@ -423,10 +454,6 @@
}
}
-.mr-widget-body-controls {
- flex-wrap: wrap;
-}
-
.mr_source_commit,
.mr_target_commit {
margin-bottom: 0;
diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb
index a27e3cceaeb..94002095739 100644
--- a/app/controllers/profiles/preferences_controller.rb
+++ b/app/controllers/profiles/preferences_controller.rb
@@ -37,6 +37,6 @@ class Profiles::PreferencesController < Profiles::ApplicationController
end
def preferences_param_names
- [:color_scheme_id, :layout, :dashboard, :project_view, :theme_id]
+ [:color_scheme_id, :layout, :dashboard, :project_view, :theme_id, :first_day_of_week]
end
end
diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb
index 54ff7ded8e5..6045ee4e171 100644
--- a/app/controllers/projects/merge_requests/application_controller.rb
+++ b/app/controllers/projects/merge_requests/application_controller.rb
@@ -34,7 +34,8 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
:task_num,
:title,
:discussion_locked,
- label_ids: []
+ label_ids: [],
+ update_task: [:index, :checked, :line_number, :line_source]
]
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 5ba90a272c9..e635f608237 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -150,6 +150,7 @@ module ApplicationSettingsHelper
:email_author_in_body,
:enabled_git_access_protocol,
:enforce_terms,
+ :first_day_of_week,
:gitaly_timeout_default,
:gitaly_timeout_medium,
:gitaly_timeout_fast,
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index f4f46b0fe96..bc1742e8167 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -43,6 +43,17 @@ module PreferencesHelper
]
end
+ def first_day_of_week_choices
+ [
+ [_('Sunday'), 0],
+ [_('Monday'), 1]
+ ]
+ end
+
+ def first_day_of_week_choices_with_default
+ first_day_of_week_choices.unshift([_('System default (%{default})') % { default: default_first_day_of_week }, nil])
+ end
+
def user_application_theme
@user_application_theme ||= Gitlab::Themes.for_user(current_user).css_class
end
@@ -66,4 +77,8 @@ module PreferencesHelper
def excluded_dashboard_choices
['operations']
end
+
+ def default_first_day_of_week
+ first_day_of_week_choices.rassoc(Gitlab::CurrentSettings.first_day_of_week).first
+ end
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index c658211d12d..daadf9427ba 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -250,6 +250,7 @@ class ApplicationSetting < ActiveRecord::Base
dsa_key_restriction: 0,
ecdsa_key_restriction: 0,
ed25519_key_restriction: 0,
+ first_day_of_week: 0,
gitaly_timeout_default: 55,
gitaly_timeout_fast: 10,
gitaly_timeout_medium: 30,
diff --git a/app/models/user.rb b/app/models/user.rb
index 9c091ac366c..24101eda0b1 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -228,6 +228,9 @@ class User < ApplicationRecord
delegate :path, to: :namespace, allow_nil: true, prefix: true
delegate :notes_filter_for, to: :user_preference
delegate :set_notes_filter, to: :user_preference
+ delegate :first_day_of_week, :first_day_of_week=, to: :user_preference
+
+ accepts_nested_attributes_for :user_preference, update_only: true
state_machine :state, initial: :active do
event :block do
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 86a04587f79..8112c2a4299 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -21,7 +21,7 @@ module MergeRequests
end
handle_wip_event(merge_request)
- update(merge_request)
+ update_task_event(merge_request) || update(merge_request)
end
# rubocop:disable Metrics/AbcSize
@@ -83,6 +83,11 @@ module MergeRequests
end
# rubocop:enable Metrics/AbcSize
+ def handle_task_changes(merge_request)
+ todo_service.mark_pending_todos_as_done(merge_request, current_user)
+ todo_service.update_merge_request(merge_request, current_user)
+ end
+
def merge_from_quick_action(merge_request)
last_diff_sha = params.delete(:merge)
return unless merge_request.mergeable_with_quick_action?(current_user, last_diff_sha: last_diff_sha)
diff --git a/app/services/task_list_toggle_service.rb b/app/services/task_list_toggle_service.rb
index b5c4cd3235d..cfe187d9b12 100644
--- a/app/services/task_list_toggle_service.rb
+++ b/app/services/task_list_toggle_service.rb
@@ -32,7 +32,8 @@ class TaskListToggleService
source_line_index = line_number - 1
markdown_task = source_lines[source_line_index]
- return unless markdown_task == line_source
+ # The source in the DB could be using either \n or \r\n line endings
+ return unless markdown_task == line_source || markdown_task == line_source + "\r"
return unless source_checkbox = Taskable::ITEM_PATTERN.match(markdown_task)
currently_checked = TaskList::Item.new(source_checkbox[1]).complete?
diff --git a/app/views/admin/application_settings/_localization.html.haml b/app/views/admin/application_settings/_localization.html.haml
new file mode 100644
index 00000000000..95d016a94a5
--- /dev/null
+++ b/app/views/admin/application_settings/_localization.html.haml
@@ -0,0 +1,11 @@
+= form_for @application_setting, url: admin_application_settings_path(anchor: 'js-localization-settings'), html: { class: 'fieldset-form' } do |f|
+ = form_errors(@application_setting)
+
+ %fieldset
+ .form-group
+ = f.label :first_day_of_week, _('Default first day of the week'), class: 'label-bold'
+ = f.select :first_day_of_week, first_day_of_week_choices, {}, class: 'form-control'
+ .form-text.text-muted
+ = _('Default first day of the week in calendars and date pickers.')
+
+ = f.submit _('Save changes'), class: "btn btn-success"
diff --git a/app/views/admin/application_settings/preferences.html.haml b/app/views/admin/application_settings/preferences.html.haml
index 00000b86ab7..c468d69d7b8 100644
--- a/app/views/admin/application_settings/preferences.html.haml
+++ b/app/views/admin/application_settings/preferences.html.haml
@@ -56,3 +56,14 @@
= _('Configure Gitaly timeouts.')
.settings-content
= render 'gitaly'
+
+%section.settings.as-localization.no-animate#js-localization-settings{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('Localization')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Various localization settings.')
+ .settings-content
+ = render 'localization'
diff --git a/app/views/award_emoji/_awards_block.html.haml b/app/views/award_emoji/_awards_block.html.haml
index a758a63dfb3..8d9c083d223 100644
--- a/app/views/award_emoji/_awards_block.html.haml
+++ b/app/views/award_emoji/_awards_block.html.haml
@@ -3,7 +3,7 @@
- awards_sort(grouped_emojis).each do |emoji, awards|
%button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button",
class: [(award_state_class(awardable, awards, current_user))],
- data: { placement: "bottom", title: award_user_list(awards, current_user) } }
+ data: { title: award_user_list(awards, current_user) } }
= emoji_icon(emoji)
%span.award-control-text.js-counter
= awards.count
@@ -12,7 +12,7 @@
.award-menu-holder.js-award-holder
%button.btn.award-control.has-tooltip.js-add-award{ type: 'button',
'aria-label': _('Add reaction'),
- data: { title: _('Add reaction'), placement: "bottom" } }
+ data: { title: _('Add reaction') } }
%span{ class: "award-control-icon award-control-icon-neutral" }= custom_icon('emoji_slightly_smiling_face')
%span{ class: "award-control-icon award-control-icon-positive" }= custom_icon('emoji_smiley')
%span{ class: "award-control-icon award-control-icon-super-positive" }= custom_icon('emoji_smile')
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index c1616810185..1a9aca1f6bf 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -60,5 +60,21 @@
= f.select :project_view, project_view_choices, {}, class: 'form-control'
.form-text.text-muted
Choose what content you want to see on a project’s overview page.
+
+ .col-sm-12
+ %hr
+
+ .col-lg-4.profile-settings-sidebar
+ %h4.prepend-top-0
+ = _('Localization')
+ %p
+ = _('Customize language and region related settings.')
+ = succeed '.' do
+ = link_to _('Learn more'), help_page_path('user/profile/preferences', anchor: 'localization'), target: '_blank'
+ .col-lg-8
+ .form-group
+ = f.label :first_day_of_week, class: 'label-bold' do
+ = _('First day of the week')
+ = f.select :first_day_of_week, first_day_of_week_choices_with_default, {}, class: 'form-control'
.form-group
- = f.submit 'Save changes', class: 'btn btn-success'
+ = f.submit _('Save changes'), class: 'btn btn-success'
diff --git a/changelogs/unreleased/2105-add-setting-for-first-day-of-the-week.yml b/changelogs/unreleased/2105-add-setting-for-first-day-of-the-week.yml
new file mode 100644
index 00000000000..f4a52b1aacd
--- /dev/null
+++ b/changelogs/unreleased/2105-add-setting-for-first-day-of-the-week.yml
@@ -0,0 +1,5 @@
+---
+title: Add setting for first day of the week
+merge_request: 22755
+author: Fabian Schneider @fabsrc
+type: added
diff --git a/changelogs/unreleased/56014-better-squash-commit-messages.yml b/changelogs/unreleased/56014-better-squash-commit-messages.yml
deleted file mode 100644
index b08d584ac0a..00000000000
--- a/changelogs/unreleased/56014-better-squash-commit-messages.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Default squash commit message is now selected from the longest commit when
- squashing merge requests
-merge_request: 24518
-author:
-type: changed
diff --git a/changelogs/unreleased/tooltips-to-top.yml b/changelogs/unreleased/tooltips-to-top.yml
new file mode 100644
index 00000000000..51bf127089e
--- /dev/null
+++ b/changelogs/unreleased/tooltips-to-top.yml
@@ -0,0 +1,5 @@
+---
+title: Change spawning of tooltips to be top by default
+merge_request: 21223
+author:
+type: changed
diff --git a/db/migrate/20181027114222_add_first_day_of_week_to_user_preferences.rb b/db/migrate/20181027114222_add_first_day_of_week_to_user_preferences.rb
new file mode 100644
index 00000000000..a0e76c2186e
--- /dev/null
+++ b/db/migrate/20181027114222_add_first_day_of_week_to_user_preferences.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddFirstDayOfWeekToUserPreferences < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def change
+ add_column :user_preferences, :first_day_of_week, :integer
+ end
+end
diff --git a/db/migrate/20181028120717_add_first_day_of_week_to_application_settings.rb b/db/migrate/20181028120717_add_first_day_of_week_to_application_settings.rb
new file mode 100644
index 00000000000..53cfaa289f6
--- /dev/null
+++ b/db/migrate/20181028120717_add_first_day_of_week_to_application_settings.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class AddFirstDayOfWeekToApplicationSettings < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+
+ def up
+ add_column_with_default(:application_settings, :first_day_of_week, :integer, default: 0)
+ end
+
+ def down
+ remove_column(:application_settings, :first_day_of_week)
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index c0026170edc..023eee5f33e 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -169,6 +169,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.boolean "protected_ci_variables", default: false, null: false
t.string "runners_registration_token_encrypted"
t.integer "local_markdown_version", default: 0, null: false
+ t.integer "first_day_of_week", default: 0, null: false
t.index ["usage_stats_set_by_user_id"], name: "index_application_settings_on_usage_stats_set_by_user_id", using: :btree
end
@@ -2154,6 +2155,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.integer "merge_request_notes_filter", limit: 2, default: 0, null: false
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
+ t.integer "first_day_of_week"
t.string "issues_sort"
t.string "merge_requests_sort"
t.index ["user_id"], name: "index_user_preferences_on_user_id", unique: true, using: :btree
diff --git a/doc/api/settings.md b/doc/api/settings.md
index 287e48c2a42..2e0a2a09133 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -57,6 +57,7 @@ Example response:
"dsa_key_restriction": 0,
"ecdsa_key_restriction": 0,
"ed25519_key_restriction": 0,
+ "first_day_of_week": 0,
"enforce_terms": true,
"terms": "Hello world!",
"performance_bar_allowed_group_id": 42,
@@ -114,6 +115,7 @@ Example response:
"dsa_key_restriction": 0,
"ecdsa_key_restriction": 0,
"ed25519_key_restriction": 0,
+ "first_day_of_week": 0,
"enforce_terms": true,
"terms": "Hello world!",
"performance_bar_allowed_group_id": 42,
@@ -159,6 +161,7 @@ are listed in the descriptions of the relevant settings.
| `email_author_in_body` | boolean | no | Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead. |
| `enabled_git_access_protocol` | string | no | Enabled protocols for Git access. Allowed values are: `ssh`, `http`, and `nil` to allow both protocols. |
| `enforce_terms` | boolean | no | (**If enabled, requires:** `terms`) Enforce application ToS to all users. |
+| `first_day_of_week` | integer | no | Start day of the week for calendar views and date pickers. Valid values are `0` (default) for Sunday and `1` for Monday. |
| `gitaly_timeout_default` | integer | no | Default Gitaly timeout, in seconds. This timeout is not enforced for git fetch/push operations or Sidekiq jobs. Set to `0` to disable timeouts. |
| `gitaly_timeout_fast` | integer | no | Gitaly fast operation timeout, in seconds. Some Gitaly operations are expected to be fast. If they exceed this threshold, there may be a problem with a storage shard and 'failing fast' can help maintain the stability of the GitLab instance. Set to `0` to disable timeouts. |
| `gitaly_timeout_medium` | integer | no | Medium Gitaly timeout, in seconds. This should be a value between the Fast and the Default timeout. Set to `0` to disable timeouts. |
diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md
index 24feb1378a1..c5344139ab4 100644
--- a/doc/development/contributing/issue_workflow.md
+++ b/doc/development/contributing/issue_workflow.md
@@ -20,7 +20,7 @@ All labels, their meaning and priority are defined on the
If you come across an issue that has none of these, and you're allowed to set
labels, you can _always_ add the team and type, and often also the subject.
-[milestones-page]: https://gitlab.com/gitlab-org/gitlab-ce/milestones
+[milestones-page]: https://gitlab.com/groups/gitlab-org/-/milestones
## Type labels
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 5b6b857dd74..463bdd59282 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -140,8 +140,8 @@ in any of the following places:
- or at the group level as a variable: `KUBE_INGRESS_BASE_DOMAIN`.
NOTE: **Note**
-Auto DevOps base domain variable (`KUBE_INGRESS_BASE_DOMAIN`) follows the same order of precedence
-as other environment [varibles](../../ci/variables/README.md#priority-of-variables).
+The Auto DevOps base domain variable (`KUBE_INGRESS_BASE_DOMAIN`) follows the same order of precedence
+as other environment [variables](../../ci/variables/README.md#priority-of-variables).
A wildcard DNS A record matching the base domain(s) is required, for example,
given a base domain of `example.com`, you'd need a DNS entry like:
@@ -685,7 +685,7 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac
| **Variable** | **Description** |
| ------------ | --------------- |
-| `AUTO_DEVOPS_DOMAIN` | The [Auto DevOps domain](#auto-devops-domain). By default, set automatically by the [Auto DevOps setting](#enabling-auto-devops). This variable is deprecated and [is scheduled to be removed](https://gitlab.com/gitlab-org/gitlab-ce/issues/56959) in GitLab 12.0. |
+| `AUTO_DEVOPS_DOMAIN` | The [Auto DevOps domain](#auto-devops-domain). By default, set automatically by the [Auto DevOps setting](#enabling-auto-devops). This variable is deprecated and [is scheduled to be removed](https://gitlab.com/gitlab-org/gitlab-ce/issues/56959) in GitLab 12.0. Use `KUBE_INGRESS_BASE_DOMAIN` instead. |
| `AUTO_DEVOPS_CHART` | The Helm Chart used to deploy your apps; defaults to the one [provided by GitLab](https://gitlab.com/charts/auto-deploy-app). |
| `AUTO_DEVOPS_CHART_REPOSITORY` | The Helm Chart repository used to search for charts; defaults to `https://charts.gitlab.io`. |
| `REPLICAS` | The number of replicas to deploy; defaults to 1. |
diff --git a/doc/user/group/clusters/index.md b/doc/user/group/clusters/index.md
index 52db51fd7bc..9fc50741407 100644
--- a/doc/user/group/clusters/index.md
+++ b/doc/user/group/clusters/index.md
@@ -61,8 +61,7 @@ differentiate the new cluster from the rest.
## Base domain
-NOTE: **Note:**
-[Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24580) in GitLab 11.8.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24580) in GitLab 11.8.
Domains at the cluster level permit support for multiple domains
per [multiple Kubernetes clusters](#multiple-kubernetes-clusters-premium). When specifying a domain,
diff --git a/doc/user/profile/preferences.md b/doc/user/profile/preferences.md
index eb2d731343e..363d3db8db1 100644
--- a/doc/user/profile/preferences.md
+++ b/doc/user/profile/preferences.md
@@ -87,3 +87,11 @@ You can choose between 3 options:
- Files and Readme (default)
- Readme
- Activity
+
+## Localization
+
+### First day of the week
+
+The first day of the week can be customised for calendar views and date pickers.
+
+You can choose **Sunday** or **Monday** as the first day of the week. If you select **System Default**, the system-wide default setting will be used.
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index ff490478232..85a4af24dc5 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -174,8 +174,7 @@ applications running on the cluster.
## Base domain
-NOTE: **Note:**
-[Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24580) in GitLab 11.8.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24580) in GitLab 11.8.
Domains at the cluster level permit support for multiple domains
per [multiple Kubernetes clusters](#multiple-kubernetes-clusters-premium). When specifying a domain,
diff --git a/doc/user/project/merge_requests/img/squash_mr_message.png b/doc/user/project/merge_requests/img/squash_mr_message.png
new file mode 100644
index 00000000000..8734cab29aa
--- /dev/null
+++ b/doc/user/project/merge_requests/img/squash_mr_message.png
Binary files differ
diff --git a/doc/user/project/merge_requests/squash_and_merge.md b/doc/user/project/merge_requests/squash_and_merge.md
index 34cba867e2c..4ff8ec3a7e6 100644
--- a/doc/user/project/merge_requests/squash_and_merge.md
+++ b/doc/user/project/merge_requests/squash_and_merge.md
@@ -23,11 +23,14 @@ The squashed commit's commit message will be either:
- Taken from the first multi-line commit message in the merge.
- The merge request's title if no multi-line commit message is found.
-Note that the squashed commit is still followed by a merge commit,
-as the merge method for this example repository uses a merge commit.
-Squashing also works with the fast-forward merge strategy, see
-[squashing and fast-forward merge](#squash-and-fast-forward-merge) for more
-details.
+It can be customized before merging a merge request.
+
+![A squash commit message editor](img/squash_mr_message.png)
+
+NOTE: **Note:**
+The squashed commit in this example is followed by a merge commit, as the merge method for this example repository uses a merge commit.
+
+Squashing also works with the fast-forward merge strategy, see [squashing and fast-forward merge](#squash-and-fast-forward-merge) for more details.
## Use cases
@@ -60,7 +63,7 @@ This can then be overridden at the time of accepting the merge request:
The squashed commit has the following metadata:
-- Message: the message of the squash commit.
+- Message: the message of the squash commit, or a customized message.
- Author: the author of the merge request.
- Committer: the user who initiated the squash.
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 9b1794eec91..3235d3ccc4e 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -24,6 +24,7 @@ module Gitlab
gon.emoji_sprites_css_path = ActionController::Base.helpers.stylesheet_path('emoji_sprites')
gon.test_env = Rails.env.test?
gon.suggested_label_colors = LabelsHelper.suggested_colors
+ gon.first_day_of_week = current_user&.first_day_of_week || Gitlab::CurrentSettings.first_day_of_week
if current_user
gon.current_user_id = current_user.id
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 1377912f7bc..37ed73a180b 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -32,6 +32,9 @@ msgid_plural "%d commits behind"
msgstr[0] ""
msgstr[1] ""
+msgid "%d commits"
+msgstr ""
+
msgid "%d exporter"
msgid_plural "%d exporters"
msgstr[0] ""
@@ -2429,6 +2432,9 @@ msgstr ""
msgid "Customize how Google Code email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Customize language and region related settings."
+msgstr ""
+
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -2492,6 +2498,12 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default first day of the week"
+msgstr ""
+
+msgid "Default first day of the week in calendars and date pickers."
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -2531,6 +2543,9 @@ msgstr ""
msgid "Delete list"
msgstr ""
+msgid "Delete source branch"
+msgstr ""
+
msgid "Delete this attachment"
msgstr ""
@@ -3271,6 +3286,9 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge without a merge commit"
+msgstr ""
+
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
@@ -3360,6 +3378,9 @@ msgstr ""
msgid "Finished"
msgstr ""
+msgid "First day of the week"
+msgstr ""
+
msgid "FirstPushedBy|First"
msgstr ""
@@ -3920,6 +3941,9 @@ msgstr ""
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include merge request description"
+msgstr ""
+
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
@@ -4314,6 +4338,9 @@ msgstr ""
msgid "Loading…"
msgstr ""
+msgid "Localization"
+msgstr ""
+
msgid "Lock"
msgstr ""
@@ -4464,9 +4491,18 @@ msgstr ""
msgid "Merge Requests"
msgstr ""
+msgid "Merge commit message"
+msgstr ""
+
msgid "Merge events"
msgstr ""
+msgid "Merge immediately"
+msgstr ""
+
+msgid "Merge in progress"
+msgstr ""
+
msgid "Merge request"
msgstr ""
@@ -4476,6 +4512,9 @@ msgstr ""
msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others"
msgstr ""
+msgid "Merge when pipeline succeeds"
+msgstr ""
+
msgid "MergeRequests|Add a reply"
msgstr ""
@@ -4632,6 +4671,15 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit messages"
+msgstr ""
+
+msgid "Modify merge commit"
+msgstr ""
+
+msgid "Monday"
+msgstr ""
+
msgid "Monitor your errors by integrating with Sentry"
msgstr ""
@@ -6625,7 +6673,10 @@ msgstr ""
msgid "Snippets"
msgstr ""
-msgid "Someone edited this issue at the same time you did. The description has been updated and you will need to make your changes again."
+msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
+msgstr ""
+
+msgid "Someone edited this merge request at the same time you did. Please refresh the page to see changes."
msgstr ""
msgid "Something went wrong on our end"
@@ -6652,6 +6703,9 @@ msgstr ""
msgid "Something went wrong while closing the %{issuable}. Please try again later"
msgstr ""
+msgid "Something went wrong while deleting the source branch. Please try again."
+msgstr ""
+
msgid "Something went wrong while fetching comments. Please try again."
msgstr ""
@@ -6664,6 +6718,9 @@ msgstr ""
msgid "Something went wrong while fetching the registry list."
msgstr ""
+msgid "Something went wrong while merging this merge request. Please try again."
+msgstr ""
+
msgid "Something went wrong while reopening the %{issuable}. Please try again later"
msgstr ""
@@ -6808,6 +6865,9 @@ msgstr ""
msgid "Specify the following URL during the Runner setup:"
msgstr ""
+msgid "Squash commit message"
+msgstr ""
+
msgid "Squash commits"
msgstr ""
@@ -6946,6 +7006,9 @@ msgstr ""
msgid "Suggested change"
msgstr ""
+msgid "Sunday"
+msgstr ""
+
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
@@ -6958,6 +7021,9 @@ msgstr ""
msgid "System Info"
msgstr ""
+msgid "System default (%{default})"
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -7997,6 +8063,9 @@ msgstr ""
msgid "Various email settings."
msgstr ""
+msgid "Various localization settings."
+msgstr ""
+
msgid "Various settings that affect GitLab performance."
msgstr ""
@@ -8324,6 +8393,9 @@ msgstr ""
msgid "You can only edit files when you are on a branch"
msgstr ""
+msgid "You can only merge once the items above are resolved"
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -8618,6 +8690,12 @@ msgstr[1] ""
msgid "missing"
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
+msgstr ""
+
+msgid "mrWidgetCommitsAdded|1 merge commit"
+msgstr ""
+
msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
msgstr ""
diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb
index d5377f1d1c1..e476cbe29a2 100644
--- a/qa/qa/page/main/login.rb
+++ b/qa/qa/page/main/login.rb
@@ -40,17 +40,9 @@ module QA
element :login_page
end
- def initialize
- # The login page is usually the entry point for all the scenarios so
- # we need to wait for the instance to start. That said, in some cases
- # we are already logged-in so we check both cases here.
- # Check if we're already logged in first. If we don't then we have to
- # wait 10 seconds for the check for the login page to fail every time
- # we use this class when we're already logged in (E.g., whenever we
- # create a personal access token to use for API access).
- wait(max: 500) do
- Page::Main::Menu.act { has_personal_area?(wait: 0) } ||
- has_element?(:login_page)
+ def page_loaded?
+ wait(max: 60) do
+ has_element?(:login_page)
end
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
index 6632c2977ef..2fb8402edd8 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Manage', :smoke do
+ # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/72
+ context 'Manage', :smoke, :quarantine do
describe 'Project creation' do
it 'user creates a new project' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb
index d4cedc9362d..e172206eb88 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Manage' do
+ # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/21
+ context 'Manage', :quarantine do
describe 'Project activity' do
it 'user creates an event in the activity page upon Git push' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
index 10cc0480794..545da0a8b85 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/31
+ context 'Create', :quarantine do
describe 'Merge request squashing' do
it 'user squashes commits while merging' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb
index 0837b720df1..e444bc7ef1b 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Verify' do
+ # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/30
+ context 'Verify', :quarantine do
describe 'CI variable support' do
it 'user adds a CI variable' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb
index 6f39a755392..aa01e5a618e 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Release' do
+ # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/26
+ context 'Release', :quarantine do
describe 'Deploy key creation' do
it 'user adds a deploy key' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
index 11a9653db81..97d36095a69 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
@@ -3,7 +3,8 @@
require 'digest/sha1'
module QA
- context 'Release', :docker do
+ # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/70
+ context 'Release', :docker, :quarantine do
describe 'Git clone using a deploy key' do
def login
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb
index 3537ba7c235..0f3cf5f4408 100644
--- a/qa/spec/spec_helper.rb
+++ b/qa/spec/spec_helper.rb
@@ -3,6 +3,20 @@ require_relative '../qa'
Dir[::File.join(__dir__, 'support', '**', '*.rb')].each { |f| require f }
RSpec.configure do |config|
+ ServerNotRespondingError = Class.new(RuntimeError)
+
+ # The login page could take some time to load the first time it is visited.
+ # We visit the login page and wait for it to properly load only once at the beginning of the suite.
+ config.before(:suite) do
+ if QA::Runtime::Scenario.respond_to?(:gitlab_address)
+ QA::Runtime::Browser.visit(:gitlab, QA::Page::Main::Login)
+
+ unless QA::Page::Main::Login.perform(&:page_loaded?)
+ raise ServerNotRespondingError, "Login page did not load at #{QA::Page::Main::Login.perform(&:current_url)}"
+ end
+ end
+ end
+
config.before do |example|
QA::Runtime::Logger.debug("Starting test: #{example.full_description}") if QA::Runtime::Env.debug?
diff --git a/spec/controllers/profiles/preferences_controller_spec.rb b/spec/controllers/profiles/preferences_controller_spec.rb
index 012f016b091..760c0fab130 100644
--- a/spec/controllers/profiles/preferences_controller_spec.rb
+++ b/spec/controllers/profiles/preferences_controller_spec.rb
@@ -42,7 +42,8 @@ describe Profiles::PreferencesController do
prefs = {
color_scheme_id: '1',
dashboard: 'stars',
- theme_id: '2'
+ theme_id: '2',
+ first_day_of_week: '1'
}.with_indifferent_access
expect(user).to receive(:assign_attributes).with(ActionController::Parameters.new(prefs).permit!)
diff --git a/spec/features/merge_request/user_accepts_merge_request_spec.rb b/spec/features/merge_request/user_accepts_merge_request_spec.rb
index 00ac7c72a11..5fa23dbb998 100644
--- a/spec/features/merge_request/user_accepts_merge_request_spec.rb
+++ b/spec/features/merge_request/user_accepts_merge_request_spec.rb
@@ -80,8 +80,8 @@ describe 'User accepts a merge request', :js do
end
it 'accepts a merge request' do
- click_button('Modify commit message')
- fill_in('Commit message', with: 'wow such merge')
+ find('.js-mr-widget-commits-count').click
+ fill_in('merge-message-edit', with: 'wow such merge')
click_button('Merge')
diff --git a/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb b/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb
index 8d2d4279d3c..c6b11fce388 100644
--- a/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb
+++ b/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb
@@ -13,7 +13,7 @@ describe 'Merge request < User customizes merge commit message', :js do
description: "Description\n\nclosing #{issue_1.to_reference}, #{issue_2.to_reference}"
)
end
- let(:textbox) { page.find(:css, '.js-commit-message', visible: false) }
+ let(:textbox) { page.find(:css, '#merge-message-edit', visible: false) }
let(:default_message) do
[
"Merge branch 'feature' into 'master'",
@@ -38,16 +38,16 @@ describe 'Merge request < User customizes merge commit message', :js do
end
it 'toggles commit message between message with description and without description' do
- expect(page).not_to have_selector('.js-commit-message')
- click_button "Modify commit message"
+ expect(page).not_to have_selector('#merge-message-edit')
+ first('.js-mr-widget-commits-count').click
expect(textbox).to be_visible
expect(textbox.value).to eq(default_message)
- click_link "Include description in commit message"
+ check('Include merge request description')
expect(textbox.value).to eq(message_with_description)
- click_link "Don't include description in commit message"
+ uncheck('Include merge request description')
expect(textbox.value).to eq(default_message)
end
diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb
index c112c8ed633..4c395248644 100644
--- a/spec/helpers/preferences_helper_spec.rb
+++ b/spec/helpers/preferences_helper_spec.rb
@@ -35,6 +35,30 @@ describe PreferencesHelper do
end
end
+ describe '#first_day_of_week_choices' do
+ it 'returns Sunday and Monday as choices' do
+ expect(helper.first_day_of_week_choices).to eq [
+ ['Sunday', 0],
+ ['Monday', 1]
+ ]
+ end
+ end
+
+ describe '#first_day_of_week_choices_with_default' do
+ it 'returns choices including system default' do
+ expect(helper.first_day_of_week_choices_with_default).to eq [
+ ['System default (Sunday)', nil], ['Sunday', 0], ['Monday', 1]
+ ]
+ end
+
+ it 'returns choices including system default set to Monday' do
+ stub_application_setting(first_day_of_week: 1)
+ expect(helper.first_day_of_week_choices_with_default).to eq [
+ ['System default (Monday)', nil], ['Sunday', 0], ['Monday', 1]
+ ]
+ end
+ end
+
describe '#user_application_theme' do
context 'with a user' do
it "returns user's theme's css_class" do
diff --git a/spec/javascripts/issue_show/components/description_spec.js b/spec/javascripts/issue_show/components/description_spec.js
index 72716b97f5f..2eeed6770be 100644
--- a/spec/javascripts/issue_show/components/description_spec.js
+++ b/spec/javascripts/issue_show/components/description_spec.js
@@ -21,7 +21,8 @@ describe('Description component', () => {
if (!document.querySelector('.issuable-meta')) {
const metaData = document.createElement('div');
metaData.classList.add('issuable-meta');
- metaData.innerHTML = '<span id="task_status"></span><span id="task_status_short"></span>';
+ metaData.innerHTML =
+ '<div class="flash-container"></div><span id="task_status"></span><span id="task_status_short"></span>';
document.body.appendChild(metaData);
}
@@ -33,6 +34,10 @@ describe('Description component', () => {
vm.$destroy();
});
+ afterAll(() => {
+ $('.issuable-meta .flash-container').remove();
+ });
+
it('animates description changes', done => {
vm.descriptionHtml = 'changed';
@@ -192,12 +197,11 @@ describe('Description component', () => {
it('should create flash notification and emit an event to parent', () => {
const msg =
'Someone edited this issue at the same time you did. The description has been updated and you will need to make your changes again.';
- spyOn(window, 'Flash');
spyOn(vm, '$emit');
vm.taskListUpdateError();
- expect(window.Flash).toHaveBeenCalledWith(msg);
+ expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(msg);
expect(vm.$emit).toHaveBeenCalledWith('taskListUpdateFailed');
});
});
diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js
index 32623d1781a..ab809930804 100644
--- a/spec/javascripts/merge_request_spec.js
+++ b/spec/javascripts/merge_request_spec.js
@@ -40,30 +40,51 @@ describe('MergeRequest', function() {
expect($('.js-task-list-field').val()).toBe('- [x] Task List Item');
});
- it('submits an ajax request on tasklist:changed', done => {
+ describe('tasklist', () => {
const lineNumber = 8;
const lineSource = '- [ ] item 8';
const index = 3;
const checked = true;
- $('.js-task-list-field').trigger({
- type: 'tasklist:changed',
- detail: { lineNumber, lineSource, index, checked },
+ it('submits an ajax request on tasklist:changed', done => {
+ $('.js-task-list-field').trigger({
+ type: 'tasklist:changed',
+ detail: { lineNumber, lineSource, index, checked },
+ });
+
+ setTimeout(() => {
+ expect(axios.patch).toHaveBeenCalledWith(
+ `${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/merge_requests/1.json`,
+ {
+ merge_request: {
+ description: '- [ ] Task List Item',
+ lock_version: undefined,
+ update_task: { line_number: lineNumber, line_source: lineSource, index, checked },
+ },
+ },
+ );
+
+ done();
+ });
});
- setTimeout(() => {
- expect(axios.patch).toHaveBeenCalledWith(
- `${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/merge_requests/1.json`,
- {
- merge_request: {
- description: '- [ ] Task List Item',
- lock_version: undefined,
- update_task: { line_number: lineNumber, line_source: lineSource, index, checked },
- },
- },
- );
+ it('shows an error notification when tasklist update failed', done => {
+ mock
+ .onPatch(`${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/merge_requests/1.json`)
+ .reply(409, {});
+
+ $('.js-task-list-field').trigger({
+ type: 'tasklist:changed',
+ detail: { lineNumber, lineSource, index, checked },
+ });
+
+ setTimeout(() => {
+ expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(
+ 'Someone edited this merge request at the same time you did. Please refresh the page to see changes.',
+ );
- done();
+ done();
+ });
});
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/commit_edit_spec.js b/spec/javascripts/vue_mr_widget/components/states/commit_edit_spec.js
new file mode 100644
index 00000000000..994d6255324
--- /dev/null
+++ b/spec/javascripts/vue_mr_widget/components/states/commit_edit_spec.js
@@ -0,0 +1,85 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import CommitEdit from '~/vue_merge_request_widget/components/states/commit_edit.vue';
+
+const localVue = createLocalVue();
+const testCommitMessage = 'Test commit message';
+const testLabel = 'Test label';
+const testInputId = 'test-input-id';
+
+describe('Commits edit component', () => {
+ let wrapper;
+
+ const createComponent = (slots = {}) => {
+ wrapper = shallowMount(localVue.extend(CommitEdit), {
+ localVue,
+ sync: false,
+ propsData: {
+ value: testCommitMessage,
+ label: testLabel,
+ inputId: testInputId,
+ },
+ slots: {
+ ...slots,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findTextarea = () => wrapper.find('.form-control');
+
+ it('has a correct label', () => {
+ const labelElement = wrapper.find('.col-form-label');
+
+ expect(labelElement.text()).toBe(testLabel);
+ });
+
+ describe('textarea', () => {
+ it('has a correct ID', () => {
+ expect(findTextarea().attributes('id')).toBe(testInputId);
+ });
+
+ it('has a correct value', () => {
+ expect(findTextarea().element.value).toBe(testCommitMessage);
+ });
+
+ it('emits an input event and receives changed value', () => {
+ const changedCommitMessage = 'Changed commit message';
+
+ findTextarea().element.value = changedCommitMessage;
+ findTextarea().trigger('input');
+
+ expect(wrapper.emitted().input[0]).toEqual([changedCommitMessage]);
+ expect(findTextarea().element.value).toBe(changedCommitMessage);
+ });
+ });
+
+ describe('when slots are present', () => {
+ beforeEach(() => {
+ createComponent({
+ header: `<div class="test-header">${testCommitMessage}</div>`,
+ checkbox: `<label slot="checkbox" class="test-checkbox">${testLabel}</label >`,
+ });
+ });
+
+ it('renders header slot correctly', () => {
+ const headerSlotElement = wrapper.find('.test-header');
+
+ expect(headerSlotElement.exists()).toBe(true);
+ expect(headerSlotElement.text()).toBe(testCommitMessage);
+ });
+
+ it('renders checkbox slot correctly', () => {
+ const checkboxSlotElement = wrapper.find('.test-checkbox');
+
+ expect(checkboxSlotElement.exists()).toBe(true);
+ expect(checkboxSlotElement.text()).toBe(testLabel);
+ });
+ });
+});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js
new file mode 100644
index 00000000000..daf1cc2d98b
--- /dev/null
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js
@@ -0,0 +1,61 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { GlDropdownItem } from '@gitlab/ui';
+import CommitMessageDropdown from '~/vue_merge_request_widget/components/states/commit_message_dropdown.vue';
+
+const localVue = createLocalVue();
+const commits = [
+ {
+ title: 'Commit 1',
+ short_id: '78d5b7',
+ message: 'Update test.txt',
+ },
+ {
+ title: 'Commit 2',
+ short_id: '34cbe28b',
+ message: 'Fixed test',
+ },
+ {
+ title: 'Commit 3',
+ short_id: 'fa42932a',
+ message: 'Added changelog',
+ },
+];
+
+describe('Commits message dropdown component', () => {
+ let wrapper;
+
+ const createComponent = () => {
+ wrapper = shallowMount(localVue.extend(CommitMessageDropdown), {
+ localVue,
+ sync: false,
+ propsData: {
+ commits,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findDropdownElements = () => wrapper.findAll(GlDropdownItem);
+ const findFirstDropdownElement = () => findDropdownElements().at(0);
+
+ it('should have 3 elements in dropdown list', () => {
+ expect(findDropdownElements().length).toBe(3);
+ });
+
+ it('should have correct message for the first dropdown list element', () => {
+ expect(findFirstDropdownElement().text()).toBe('78d5b7 Commit 1');
+ });
+
+ it('should emit a commit title on selecting commit', () => {
+ findFirstDropdownElement().vm.$emit('click');
+
+ expect(wrapper.emitted().input[0]).toEqual(['Update test.txt']);
+ });
+});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_commits_header_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_commits_header_spec.js
new file mode 100644
index 00000000000..5cf6408cf34
--- /dev/null
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_commits_header_spec.js
@@ -0,0 +1,110 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import CommitsHeader from '~/vue_merge_request_widget/components/states/commits_header.vue';
+import Icon from '~/vue_shared/components/icon.vue';
+
+const localVue = createLocalVue();
+
+describe('Commits header component', () => {
+ let wrapper;
+
+ const createComponent = props => {
+ wrapper = shallowMount(localVue.extend(CommitsHeader), {
+ localVue,
+ sync: false,
+ propsData: {
+ isSquashEnabled: false,
+ targetBranch: 'master',
+ commitsCount: 5,
+ ...props,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findHeaderWrapper = () => wrapper.find('.js-mr-widget-commits-count');
+ const findCommitToggle = () => wrapper.find('.commit-edit-toggle');
+ const findIcon = () => wrapper.find(Icon);
+ const findCommitsCountMessage = () => wrapper.find('.commits-count-message');
+ const findTargetBranchMessage = () => wrapper.find('.label-branch');
+ const findModifyButton = () => wrapper.find('.modify-message-button');
+
+ describe('when collapsed', () => {
+ it('toggle has aria-label equal to Expand', () => {
+ createComponent();
+
+ expect(findCommitToggle().attributes('aria-label')).toBe('Expand');
+ });
+
+ it('has a chevron-right icon', () => {
+ createComponent();
+ wrapper.setData({ expanded: false });
+
+ expect(findIcon().props('name')).toBe('chevron-right');
+ });
+
+ describe('when squash is disabled', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('has commits count message showing correct amount of commits', () => {
+ expect(findCommitsCountMessage().text()).toBe('5 commits');
+ });
+
+ it('has button with modify merge commit message', () => {
+ expect(findModifyButton().text()).toBe('Modify merge commit');
+ });
+ });
+
+ describe('when squash is enabled', () => {
+ beforeEach(() => {
+ createComponent({ isSquashEnabled: true });
+ });
+
+ it('has commits count message showing one commit when squash is enabled', () => {
+ expect(findCommitsCountMessage().text()).toBe('1 commit');
+ });
+
+ it('has button with modify commit messages text', () => {
+ expect(findModifyButton().text()).toBe('Modify commit messages');
+ });
+ });
+
+ it('has correct target branch displayed', () => {
+ createComponent();
+
+ expect(findTargetBranchMessage().text()).toBe('master');
+ });
+ });
+
+ describe('when expanded', () => {
+ beforeEach(() => {
+ createComponent();
+ wrapper.setData({ expanded: true });
+ });
+
+ it('toggle has aria-label equal to collapse', done => {
+ wrapper.vm.$nextTick(() => {
+ expect(findCommitToggle().attributes('aria-label')).toBe('Collapse');
+ done();
+ });
+ });
+
+ it('has a chevron-down icon', done => {
+ wrapper.vm.$nextTick(() => {
+ expect(findIcon().props('name')).toBe('chevron-down');
+ done();
+ });
+ });
+
+ it('has a collapse text', done => {
+ wrapper.vm.$nextTick(() => {
+ expect(findHeaderWrapper().text()).toBe('Collapse');
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
index e387367d1a2..631da202d1d 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -1,10 +1,14 @@
import Vue from 'vue';
import ReadyToMerge from '~/vue_merge_request_widget/components/states/ready_to_merge.vue';
import SquashBeforeMerge from '~/vue_merge_request_widget/components/states/squash_before_merge.vue';
+import CommitsHeader from '~/vue_merge_request_widget/components/states/commits_header.vue';
+import CommitEdit from '~/vue_merge_request_widget/components/states/commit_edit.vue';
+import CommitMessageDropdown from '~/vue_merge_request_widget/components/states/commit_message_dropdown.vue';
import eventHub from '~/vue_merge_request_widget/event_hub';
import { createLocalVue, shallowMount } from '@vue/test-utils';
const commitMessage = 'This is the commit message';
+const squashCommitMessage = 'This is the squash commit message';
const commitMessageWithDescription = 'This is the commit message description';
const createTestMr = customConfig => {
const mr = {
@@ -19,9 +23,11 @@ const createTestMr = customConfig => {
sha: '12345678',
squash: false,
commitMessage,
+ squashCommitMessage,
commitMessageWithDescription,
shouldRemoveSourceBranch: true,
canRemoveSourceBranch: false,
+ targetBranch: 'master',
};
Object.assign(mr, customConfig.mr);
@@ -98,21 +104,6 @@ describe('ReadyToMerge', () => {
});
});
- describe('commitMessageLinkTitle', () => {
- const withDesc = 'Include description in commit message';
- const withoutDesc = "Don't include description in commit message";
-
- it('should return message with description', () => {
- expect(vm.commitMessageLinkTitle).toEqual(withDesc);
- });
-
- it('should return message without description', () => {
- vm.useCommitMessageWithDescription = true;
-
- expect(vm.commitMessageLinkTitle).toEqual(withoutDesc);
- });
- });
-
describe('status', () => {
it('defaults to success', () => {
vm.mr.pipeline = true;
@@ -279,55 +270,43 @@ describe('ReadyToMerge', () => {
vm.mr.isMergeAllowed = false;
vm.mr.isPipelineActive = false;
- expect(vm.shouldShowMergeControls()).toBeFalsy();
+ expect(vm.shouldShowMergeControls).toBeFalsy();
});
it('should return true when the build succeeded or build not required to succeed', () => {
vm.mr.isMergeAllowed = true;
vm.mr.isPipelineActive = false;
- expect(vm.shouldShowMergeControls()).toBeTruthy();
+ expect(vm.shouldShowMergeControls).toBeTruthy();
});
it('should return true when showing the MWPS button and a pipeline is running that needs to be successful', () => {
vm.mr.isMergeAllowed = false;
vm.mr.isPipelineActive = true;
- expect(vm.shouldShowMergeControls()).toBeTruthy();
+ expect(vm.shouldShowMergeControls).toBeTruthy();
});
it('should return true when showing the MWPS button but not required for the pipeline to succeed', () => {
vm.mr.isMergeAllowed = true;
vm.mr.isPipelineActive = true;
- expect(vm.shouldShowMergeControls()).toBeTruthy();
+ expect(vm.shouldShowMergeControls).toBeTruthy();
});
});
- describe('updateCommitMessage', () => {
+ describe('updateMergeCommitMessage', () => {
it('should revert flag and change commitMessage', () => {
- expect(vm.useCommitMessageWithDescription).toBeFalsy();
expect(vm.commitMessage).toEqual(commitMessage);
- vm.updateCommitMessage();
+ vm.updateMergeCommitMessage(true);
- expect(vm.useCommitMessageWithDescription).toBeTruthy();
expect(vm.commitMessage).toEqual(commitMessageWithDescription);
- vm.updateCommitMessage();
+ vm.updateMergeCommitMessage(false);
- expect(vm.useCommitMessageWithDescription).toBeFalsy();
expect(vm.commitMessage).toEqual(commitMessage);
});
});
- describe('toggleCommitMessageEditor', () => {
- it('should toggle showCommitMessageEditor flag', () => {
- expect(vm.showCommitMessageEditor).toBeFalsy();
- vm.toggleCommitMessageEditor();
-
- expect(vm.showCommitMessageEditor).toBeTruthy();
- });
- });
-
describe('handleMergeButtonClick', () => {
const returnPromise = status =>
new Promise(resolve => {
@@ -623,7 +602,7 @@ describe('ReadyToMerge', () => {
});
});
- describe('Squash checkbox component', () => {
+ describe('render children components', () => {
let wrapper;
const localVue = createLocalVue();
@@ -642,25 +621,101 @@ describe('ReadyToMerge', () => {
});
const findCheckboxElement = () => wrapper.find(SquashBeforeMerge);
+ const findCommitsHeaderElement = () => wrapper.find(CommitsHeader);
+ const findCommitEditElements = () => wrapper.findAll(CommitEdit);
+ const findCommitDropdownElement = () => wrapper.find(CommitMessageDropdown);
+
+ describe('squash checkbox', () => {
+ it('should be rendered when squash before merge is enabled and there is more than 1 commit', () => {
+ createLocalComponent({
+ mr: { commitsCount: 2, enableSquashBeforeMerge: true },
+ });
- it('should be rendered when squash before merge is enabled and there is more than 1 commit', () => {
- createLocalComponent({
- mr: { commitsCount: 2, enableSquashBeforeMerge: true },
+ expect(findCheckboxElement().exists()).toBeTruthy();
});
- expect(findCheckboxElement().exists()).toBeTruthy();
+ it('should not be rendered when squash before merge is disabled', () => {
+ createLocalComponent({ mr: { commitsCount: 2, enableSquashBeforeMerge: false } });
+
+ expect(findCheckboxElement().exists()).toBeFalsy();
+ });
+
+ it('should not be rendered when there is only 1 commit', () => {
+ createLocalComponent({ mr: { commitsCount: 1, enableSquashBeforeMerge: true } });
+
+ expect(findCheckboxElement().exists()).toBeFalsy();
+ });
});
- it('should not be rendered when squash before merge is disabled', () => {
- createLocalComponent({ mr: { commitsCount: 2, enableSquashBeforeMerge: false } });
+ describe('commits count collapsible header', () => {
+ it('should be rendered if fast-forward is disabled', () => {
+ createLocalComponent();
- expect(findCheckboxElement().exists()).toBeFalsy();
+ expect(findCommitsHeaderElement().exists()).toBeTruthy();
+ });
+
+ it('should not be rendered if fast-forward is enabled', () => {
+ createLocalComponent({ mr: { ffOnlyEnabled: true } });
+
+ expect(findCommitsHeaderElement().exists()).toBeFalsy();
+ });
});
- it('should not be rendered when there is only 1 commit', () => {
- createLocalComponent({ mr: { commitsCount: 1, enableSquashBeforeMerge: true } });
+ describe('commits edit components', () => {
+ it('should have one edit component when squash is disabled', () => {
+ createLocalComponent();
+
+ expect(findCommitEditElements().length).toBe(1);
+ });
- expect(findCheckboxElement().exists()).toBeFalsy();
+ const findFirstCommitEditLabel = () =>
+ findCommitEditElements()
+ .at(0)
+ .props('label');
+
+ it('should have two edit components when squash is enabled', () => {
+ createLocalComponent({
+ mr: {
+ commitsCount: 2,
+ squash: true,
+ enableSquashBeforeMerge: true,
+ },
+ });
+
+ expect(findCommitEditElements().length).toBe(2);
+ });
+
+ it('should have correct edit merge commit label', () => {
+ createLocalComponent();
+
+ expect(findFirstCommitEditLabel()).toBe('Merge commit message');
+ });
+
+ it('should have correct edit squash commit label', () => {
+ createLocalComponent({
+ mr: {
+ commitsCount: 2,
+ squash: true,
+ enableSquashBeforeMerge: true,
+ },
+ });
+
+ expect(findFirstCommitEditLabel()).toBe('Squash commit message');
+ });
+ });
+
+ describe('commits dropdown', () => {
+ it('should not be rendered if squash is disabled', () => {
+ createLocalComponent();
+
+ expect(findCommitDropdownElement().exists()).toBeFalsy();
+ });
+
+ it('should be rendered if squash is enabled', () => {
+ createLocalComponent({ mr: { squash: true } });
+
+ expect(findCommitDropdownElement().exists()).toBeTruthy();
+ });
});
});
@@ -696,10 +751,6 @@ describe('ReadyToMerge', () => {
expect(vm.$el.querySelector('.js-remove-source-branch-checkbox')).toBeNull();
});
- it('does not show modify commit message button', () => {
- expect(vm.$el.querySelector('.js-modify-commit-message-button')).toBeNull();
- });
-
it('shows message to resolve all items before being allowed to merge', () => {
expect(vm.$el.querySelector('.js-resolve-mr-widget-items-message')).toBeDefined();
});
@@ -712,7 +763,7 @@ describe('ReadyToMerge', () => {
mr: { ffOnlyEnabled: false },
});
- expect(customVm.$el.querySelector('.js-fast-forward-message')).toBeNull();
+ expect(customVm.$el.querySelector('.mr-fast-forward-message')).toBeNull();
expect(customVm.$el.querySelector('.js-modify-commit-message-button')).toBeDefined();
});
@@ -721,7 +772,7 @@ describe('ReadyToMerge', () => {
mr: { ffOnlyEnabled: true },
});
- expect(customVm.$el.querySelector('.js-fast-forward-message')).toBeDefined();
+ expect(customVm.$el.querySelector('.mr-fast-forward-message')).toBeDefined();
expect(customVm.$el.querySelector('.js-modify-commit-message-button')).toBeNull();
});
});
diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js
index 75b197fb2ba..6ef07f81705 100644
--- a/spec/javascripts/vue_mr_widget/mock_data.js
+++ b/spec/javascripts/vue_mr_widget/mock_data.js
@@ -215,12 +215,14 @@ export default {
project_archived: false,
default_merge_commit_message_with_description:
"Merge branch 'daaaa' into 'master'\n\nUpdate README.md\n\nSee merge request !22",
+ default_squash_commit_message: 'Test squash commit message',
diverged_commits_count: 0,
only_allow_merge_if_pipeline_succeeds: false,
commit_change_content_path: '/root/acets-app/merge_requests/22/commit_change_content',
merge_commit_path:
'http://localhost:3000/root/acets-app/commit/53027d060246c8f47e4a9310fb332aa52f221775',
troubleshooting_docs_path: 'help',
+ squash: true,
};
export const mockStore = {
diff --git a/spec/migrations/clean_up_for_members_spec.rb b/spec/migrations/clean_up_for_members_spec.rb
index 7876536cb3e..1a79f94cf0d 100644
--- a/spec/migrations/clean_up_for_members_spec.rb
+++ b/spec/migrations/clean_up_for_members_spec.rb
@@ -2,6 +2,10 @@ require 'spec_helper'
require Rails.root.join('db', 'migrate', '20171216111734_clean_up_for_members.rb')
describe CleanUpForMembers, :migration do
+ before do
+ stub_feature_flags(enforced_sso: false)
+ end
+
let(:migration) { described_class.new }
let(:groups) { table(:namespaces) }
let!(:group_member) { create_group_member }
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index ef76e2311b1..931e47d3a77 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -471,6 +471,8 @@ describe Issues::UpdateService, :mailer do
it { expect(issue.tasks?).to eq(true) }
+ it_behaves_like 'updating a single task'
+
context 'when tasks are marked as completed' do
before do
update_issue(description: "- [x] Task 1\n- [X] Task 2")
@@ -543,76 +545,6 @@ describe Issues::UpdateService, :mailer do
end
end
- context 'when updating a single task' do
- before do
- update_issue(description: "- [ ] Task 1\n- [ ] Task 2")
- end
-
- it { expect(issue.tasks?).to eq(true) }
-
- context 'when a task is marked as completed' do
- before do
- update_issue(update_task: { index: 1, checked: true, line_source: '- [ ] Task 1', line_number: 1 })
- end
-
- it 'creates system note about task status change' do
- note1 = find_note('marked the task **Task 1** as completed')
-
- expect(note1).not_to be_nil
-
- description_notes = find_notes('description')
- expect(description_notes.length).to eq(1)
- end
- end
-
- context 'when a task is marked as incomplete' do
- before do
- update_issue(description: "- [x] Task 1\n- [X] Task 2")
- update_issue(update_task: { index: 2, checked: false, line_source: '- [X] Task 2', line_number: 2 })
- end
-
- it 'creates system note about task status change' do
- note1 = find_note('marked the task **Task 2** as incomplete')
-
- expect(note1).not_to be_nil
-
- description_notes = find_notes('description')
- expect(description_notes.length).to eq(1)
- end
- end
-
- context 'when the task position has been modified' do
- before do
- update_issue(description: "- [ ] Task 1\n- [ ] Task 3\n- [ ] Task 2")
- end
-
- it 'raises an exception' do
- expect(Note.count).to eq(2)
- expect do
- update_issue(update_task: { index: 2, checked: true, line_source: '- [ ] Task 2', line_number: 2 })
- end.to raise_error(ActiveRecord::StaleObjectError)
- expect(Note.count).to eq(2)
- end
- end
-
- context 'when the content changes but not task line number' do
- before do
- update_issue(description: "Paragraph\n\n- [ ] Task 1\n- [x] Task 2")
- update_issue(description: "Paragraph with more words\n\n- [ ] Task 1\n- [x] Task 2")
- update_issue(update_task: { index: 2, checked: false, line_source: '- [x] Task 2', line_number: 4 })
- end
-
- it 'creates system note about task status change' do
- note1 = find_note('marked the task **Task 2** as incomplete')
-
- expect(note1).not_to be_nil
-
- description_notes = find_notes('description')
- expect(description_notes.length).to eq(2)
- end
- end
- end
-
context 'updating labels' do
let(:label3) { create(:label, project: project) }
let(:result) { described_class.new(project, user, params).execute(issue).reload }
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index be5ad849ba7..20580bf14b9 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -466,6 +466,8 @@ describe MergeRequests::UpdateService, :mailer do
it { expect(@merge_request.tasks?).to eq(true) }
+ it_behaves_like 'updating a single task'
+
context 'when tasks are marked as completed' do
before do
update_merge_request({ description: "- [x] Task 1\n- [X] Task 2" })
diff --git a/spec/services/task_list_toggle_service_spec.rb b/spec/services/task_list_toggle_service_spec.rb
index cc64dd25085..7c5480d382f 100644
--- a/spec/services/task_list_toggle_service_spec.rb
+++ b/spec/services/task_list_toggle_service_spec.rb
@@ -67,6 +67,17 @@ describe TaskListToggleService do
expect(toggler.execute).to be_falsey
end
+ it 'tolerates \r\n line endings' do
+ rn_markdown = markdown.gsub("\n", "\r\n")
+ toggler = described_class.new(rn_markdown, markdown_html,
+ toggle_as_checked: true,
+ line_source: '* [ ] Task 1', line_number: 1)
+
+ expect(toggler.execute).to be_truthy
+ expect(toggler.updated_markdown.lines[0]).to eq "* [x] Task 1\r\n"
+ expect(toggler.updated_markdown_html).to include('disabled checked> Task 1')
+ end
+
it 'returns false if markdown is nil' do
toggler = described_class.new(nil, markdown_html,
toggle_as_checked: false,
diff --git a/spec/support/shared_examples/issuable_shared_examples.rb b/spec/support/shared_examples/issuable_shared_examples.rb
index 42f3b4db23c..c3d40c5b231 100644
--- a/spec/support/shared_examples/issuable_shared_examples.rb
+++ b/spec/support/shared_examples/issuable_shared_examples.rb
@@ -36,3 +36,76 @@ shared_examples 'system notes for milestones' do
end
end
end
+
+shared_examples 'updating a single task' do
+ def update_issuable(opts)
+ issuable = try(:issue) || try(:merge_request)
+ described_class.new(project, user, opts).execute(issuable)
+ end
+
+ before do
+ update_issuable(description: "- [ ] Task 1\n- [ ] Task 2")
+ end
+
+ context 'when a task is marked as completed' do
+ before do
+ update_issuable(update_task: { index: 1, checked: true, line_source: '- [ ] Task 1', line_number: 1 })
+ end
+
+ it 'creates system note about task status change' do
+ note1 = find_note('marked the task **Task 1** as completed')
+
+ expect(note1).not_to be_nil
+
+ description_notes = find_notes('description')
+ expect(description_notes.length).to eq(1)
+ end
+ end
+
+ context 'when a task is marked as incomplete' do
+ before do
+ update_issuable(description: "- [x] Task 1\n- [X] Task 2")
+ update_issuable(update_task: { index: 2, checked: false, line_source: '- [X] Task 2', line_number: 2 })
+ end
+
+ it 'creates system note about task status change' do
+ note1 = find_note('marked the task **Task 2** as incomplete')
+
+ expect(note1).not_to be_nil
+
+ description_notes = find_notes('description')
+ expect(description_notes.length).to eq(1)
+ end
+ end
+
+ context 'when the task position has been modified' do
+ before do
+ update_issuable(description: "- [ ] Task 1\n- [ ] Task 3\n- [ ] Task 2")
+ end
+
+ it 'raises an exception' do
+ expect(Note.count).to eq(2)
+ expect do
+ update_issuable(update_task: { index: 2, checked: true, line_source: '- [ ] Task 2', line_number: 2 })
+ end.to raise_error(ActiveRecord::StaleObjectError)
+ expect(Note.count).to eq(2)
+ end
+ end
+
+ context 'when the content changes but not task line number' do
+ before do
+ update_issuable(description: "Paragraph\n\n- [ ] Task 1\n- [x] Task 2")
+ update_issuable(description: "Paragraph with more words\n\n- [ ] Task 1\n- [x] Task 2")
+ update_issuable(update_task: { index: 2, checked: false, line_source: '- [x] Task 2', line_number: 4 })
+ end
+
+ it 'creates system note about task status change' do
+ note1 = find_note('marked the task **Task 2** as incomplete')
+
+ expect(note1).not_to be_nil
+
+ description_notes = find_notes('description')
+ expect(description_notes.length).to eq(2)
+ end
+ end
+end