summaryrefslogtreecommitdiff
path: root/app/assets/javascripts
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts')
-rw-r--r--app/assets/javascripts/awards_handler.js4
-rw-r--r--app/assets/javascripts/boards/components/board.js2
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue (renamed from app/assets/javascripts/boards/components/board_list.js)175
-rw-r--r--app/assets/javascripts/boards/components/board_sidebar.js3
-rw-r--r--app/assets/javascripts/boards/components/modal/footer.js5
-rw-r--r--app/assets/javascripts/boards/components/new_list_dropdown.js7
-rw-r--r--app/assets/javascripts/boards/components/sidebar/remove_issue.js5
-rw-r--r--app/assets/javascripts/ci_variable_list/ajax_variable_list.js117
-rw-r--r--app/assets/javascripts/ci_variable_list/ci_variable_list.js33
-rw-r--r--app/assets/javascripts/clusters/clusters_bundle.js3
-rw-r--r--app/assets/javascripts/clusters/components/application_row.vue24
-rw-r--r--app/assets/javascripts/clusters/components/applications.vue22
-rw-r--r--app/assets/javascripts/clusters/stores/clusters_store.js4
-rw-r--r--app/assets/javascripts/commit/image_file.js1
-rw-r--r--app/assets/javascripts/commits.js72
-rw-r--r--app/assets/javascripts/commons/jquery.js2
-rw-r--r--app/assets/javascripts/commons/polyfills/element.js19
-rw-r--r--app/assets/javascripts/create_merge_request_dropdown.js1
-rw-r--r--app/assets/javascripts/diff.js7
-rw-r--r--app/assets/javascripts/dispatcher.js171
-rw-r--r--app/assets/javascripts/droplab/constants.js2
-rw-r--r--app/assets/javascripts/droplab/drop_down.js13
-rw-r--r--app/assets/javascripts/environments/components/environment_item.vue31
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_bundle.js1
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js27
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js68
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_token_keys.js5
-rw-r--r--app/assets/javascripts/gl_form.js3
-rw-r--r--app/assets/javascripts/gpg_badges.js17
-rw-r--r--app/assets/javascripts/groups/components/app.vue6
-rw-r--r--app/assets/javascripts/groups_select.js22
-rw-r--r--app/assets/javascripts/ide/monaco_loader.js5
-rw-r--r--app/assets/javascripts/importer_status.js117
-rw-r--r--app/assets/javascripts/integrations/index.js7
-rw-r--r--app/assets/javascripts/issue.js76
-rw-r--r--app/assets/javascripts/job.js2
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js21
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js123
-rw-r--r--app/assets/javascripts/lib/utils/http_status.js2
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js7
-rw-r--r--app/assets/javascripts/merge_conflicts/components/diff_file_editor.js21
-rw-r--r--app/assets/javascripts/merge_request.js2
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue2
-rw-r--r--app/assets/javascripts/monitoring/components/empty_state.vue32
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue61
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue1
-rw-r--r--app/assets/javascripts/notes/index.js2
-rw-r--r--app/assets/javascripts/notes/services/notes_service.js3
-rw-r--r--app/assets/javascripts/notes/stores/actions.js33
-rw-r--r--app/assets/javascripts/notes/stores/getters.js1
-rw-r--r--app/assets/javascripts/notes/stores/mutation_types.js4
-rw-r--r--app/assets/javascripts/notes/stores/mutations.js8
-rw-r--r--app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue26
-rw-r--r--app/assets/javascripts/pages/admin/jobs/index/index.js43
-rw-r--r--app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue125
-rw-r--r--app/assets/javascripts/pages/admin/projects/index/index.js37
-rw-r--r--app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue174
-rw-r--r--app/assets/javascripts/pages/admin/users/index.js43
-rw-r--r--app/assets/javascripts/pages/ci/lints/ci_lint_editor.js7
-rw-r--r--app/assets/javascripts/pages/dashboard/issues/index.js4
-rw-r--r--app/assets/javascripts/pages/dashboard/merge_requests/index.js4
-rw-r--r--app/assets/javascripts/pages/dashboard/milestones/show/index.js4
-rw-r--r--app/assets/javascripts/pages/dashboard/projects/index.js2
-rw-r--r--app/assets/javascripts/pages/dashboard/todos/index/index.js2
-rw-r--r--app/assets/javascripts/pages/explore/groups/index.js4
-rw-r--r--app/assets/javascripts/pages/explore/projects/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/issues/index.js8
-rw-r--r--app/assets/javascripts/pages/groups/merge_requests/index.js8
-rw-r--r--app/assets/javascripts/pages/groups/milestones/edit/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/milestones/new/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/milestones/show/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js17
-rw-r--r--app/assets/javascripts/pages/projects/branches/index/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/branches/new/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/commits/show/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/compare/show/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/environments/metrics/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/issues/edit/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/issues/index/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/issues/new/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/creations/index.js (renamed from app/assets/javascripts/pages/projects/merge_requests/creations/diffs/index.js)2
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/edit/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/index/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/show/index.js24
-rw-r--r--app/assets/javascripts/pages/projects/milestones/edit/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/milestones/index/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/milestones/new/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/milestones/show/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/create/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/edit/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js (renamed from app/assets/javascripts/pipeline_schedules/pipeline_schedules_index_bundle.js)2
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/new/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue (renamed from app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue)0
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue (renamed from app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.vue)2
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js (renamed from app/assets/javascripts/pipeline_schedules/components/target_branch_dropdown.js)0
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js (renamed from app/assets/javascripts/pipeline_schedules/components/timezone_dropdown.js)0
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/icons/intro_illustration.svg (renamed from app/assets/javascripts/pipeline_schedules/icons/intro_illustration.svg)0
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js (renamed from app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js)10
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/update/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/pipelines/charts/index.js56
-rw-r--r--app/assets/javascripts/pages/projects/services/edit/index.js13
-rw-r--r--app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js17
-rw-r--r--app/assets/javascripts/pages/projects/tree/show/index.js2
-rw-r--r--app/assets/javascripts/pages/search/init_filtered_search.js4
-rw-r--r--app/assets/javascripts/pages/users/activity_calendar.js (renamed from app/assets/javascripts/users/activity_calendar.js)0
-rw-r--r--app/assets/javascripts/pages/users/index.js (renamed from app/assets/javascripts/users/index.js)2
-rw-r--r--app/assets/javascripts/pages/users/show/index.js3
-rw-r--r--app/assets/javascripts/pages/users/user_tabs.js (renamed from app/assets/javascripts/users/user_tabs.js)10
-rw-r--r--app/assets/javascripts/pipelines/components/async_button.vue16
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_table.vue6
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_table_row.vue7
-rw-r--r--app/assets/javascripts/pipelines/components/retry_confirmation_modal.vue65
-rw-r--r--app/assets/javascripts/pipelines/components/stop_confirmation_modal.vue65
-rw-r--r--app/assets/javascripts/pipelines/pipelines_charts.js38
-rw-r--r--app/assets/javascripts/pipelines/pipelines_times.js27
-rw-r--r--app/assets/javascripts/profile/profile.js47
-rw-r--r--app/assets/javascripts/prometheus_metrics/index.js6
-rw-r--r--app/assets/javascripts/render_math.js44
-rw-r--r--app/assets/javascripts/render_mermaid.js3
-rw-r--r--app/assets/javascripts/search_autocomplete.js39
-rw-r--r--app/assets/javascripts/settings_panels.js2
-rw-r--r--app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue10
-rw-r--r--app/assets/javascripts/sidebar/components/confidential/edit_form.vue23
-rw-r--r--app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/lock/edit_form.vue22
-rw-r--r--app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.js4
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.js2
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/help_state.js34
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.js2
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js4
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue (renamed from app/assets/javascripts/sidebar/components/time_tracking/time_tracker.js)149
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue5
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js43
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue62
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.js19
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue25
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js18
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue24
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/dependencies.js6
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js1
-rw-r--r--app/assets/javascripts/vue_shared/components/confirmation_input.vue62
-rw-r--r--app/assets/javascripts/vue_shared/components/gl_modal.vue106
-rw-r--r--app/assets/javascripts/vue_shared/components/loading_button.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/modal.vue20
146 files changed, 2066 insertions, 1073 deletions
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index 87109a802e5..3283ce5ec36 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -50,10 +50,8 @@ class AwardsHandler {
this.registerEventListener('on', $('html'), 'click', (e) => {
const $target = $(e.target);
- if (!$target.closest('.emoji-menu-content').length) {
- $('.js-awards-block.current').removeClass('current');
- }
if (!$target.closest('.emoji-menu').length) {
+ $('.js-awards-block.current').removeClass('current');
if ($('.emoji-menu').is(':visible')) {
$('.js-add-award.is-active').removeClass('is-active');
this.hideMenuElement($('.emoji-menu'));
diff --git a/app/assets/javascripts/boards/components/board.js b/app/assets/javascripts/boards/components/board.js
index a8dafd31f12..9c4cc2338c8 100644
--- a/app/assets/javascripts/boards/components/board.js
+++ b/app/assets/javascripts/boards/components/board.js
@@ -2,7 +2,7 @@
import Sortable from 'vendor/Sortable';
import Vue from 'vue';
import AccessorUtilities from '../../lib/utils/accessor';
-import boardList from './board_list';
+import boardList from './board_list.vue';
import boardBlankState from './board_blank_state';
import './board_delete';
diff --git a/app/assets/javascripts/boards/components/board_list.js b/app/assets/javascripts/boards/components/board_list.vue
index 591f1dc8313..9a0442e2afe 100644
--- a/app/assets/javascripts/boards/components/board_list.js
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -1,3 +1,4 @@
+<script>
import Sortable from 'vendor/Sortable';
import boardNewIssue from './board_new_issue';
import boardCard from './board_card.vue';
@@ -8,6 +9,11 @@ const Store = gl.issueBoards.BoardsStore;
export default {
name: 'BoardList',
+ components: {
+ boardCard,
+ boardNewIssue,
+ loadingIcon,
+ },
props: {
disabled: {
type: Boolean,
@@ -42,46 +48,6 @@ export default {
showIssueForm: false,
};
},
- components: {
- boardCard,
- boardNewIssue,
- loadingIcon,
- },
- methods: {
- listHeight() {
- return this.$refs.list.getBoundingClientRect().height;
- },
- scrollHeight() {
- return this.$refs.list.scrollHeight;
- },
- scrollTop() {
- return this.$refs.list.scrollTop + this.listHeight();
- },
- scrollToTop() {
- this.$refs.list.scrollTop = 0;
- },
- loadNextPage() {
- const getIssues = this.list.nextPage();
- const loadingDone = () => {
- this.list.loadingMore = false;
- };
-
- if (getIssues) {
- this.list.loadingMore = true;
- getIssues
- .then(loadingDone)
- .catch(loadingDone);
- }
- },
- toggleForm() {
- this.showIssueForm = !this.showIssueForm;
- },
- onScroll() {
- if (!this.loadingMore && (this.scrollTop() > this.scrollHeight() - this.scrollOffset)) {
- this.loadNextPage();
- }
- },
- },
watch: {
filters: {
handler() {
@@ -157,51 +123,90 @@ export default {
eventHub.$off(`scroll-board-list-${this.list.id}`, this.scrollToTop);
this.$refs.list.removeEventListener('scroll', this.onScroll);
},
- template: `
- <div class="board-list-component">
- <div
- class="board-list-loading text-center"
- aria-label="Loading issues"
- v-if="loading">
- <loading-icon />
- </div>
- <board-new-issue
- :list="list"
- v-if="list.type !== 'closed' && showIssueForm"/>
- <ul
- class="board-list"
- v-show="!loading"
- ref="list"
- :data-board="list.id"
- :class="{ 'is-smaller': showIssueForm }">
- <board-card
- v-for="(issue, index) in issues"
- ref="issue"
- :index="index"
- :list="list"
- :issue="issue"
- :issue-link-base="issueLinkBase"
- :root-path="rootPath"
- :disabled="disabled"
- :key="issue.id" />
- <li
- class="board-list-count text-center"
- v-if="showCount"
- data-issue-id="-1">
+ methods: {
+ listHeight() {
+ return this.$refs.list.getBoundingClientRect().height;
+ },
+ scrollHeight() {
+ return this.$refs.list.scrollHeight;
+ },
+ scrollTop() {
+ return this.$refs.list.scrollTop + this.listHeight();
+ },
+ scrollToTop() {
+ this.$refs.list.scrollTop = 0;
+ },
+ loadNextPage() {
+ const getIssues = this.list.nextPage();
+ const loadingDone = () => {
+ this.list.loadingMore = false;
+ };
- <loading-icon
- v-show="list.loadingMore"
- label="Loading more issues"
- />
+ if (getIssues) {
+ this.list.loadingMore = true;
+ getIssues
+ .then(loadingDone)
+ .catch(loadingDone);
+ }
+ },
+ toggleForm() {
+ this.showIssueForm = !this.showIssueForm;
+ },
+ onScroll() {
+ if (!this.loadingMore && (this.scrollTop() > this.scrollHeight() - this.scrollOffset)) {
+ this.loadNextPage();
+ }
+ },
+ },
+};
+</script>
- <span v-if="list.issues.length === list.issuesSize">
- Showing all issues
- </span>
- <span v-else>
- Showing {{ list.issues.length }} of {{ list.issuesSize }} issues
- </span>
- </li>
- </ul>
+<template>
+ <div class="board-list-component">
+ <div
+ class="board-list-loading text-center"
+ aria-label="Loading issues"
+ v-if="loading">
+ <loading-icon />
</div>
- `,
-};
+ <board-new-issue
+ :list="list"
+ v-if="list.type !== 'closed' && showIssueForm"/>
+ <ul
+ class="board-list"
+ v-show="!loading"
+ ref="list"
+ :data-board="list.id"
+ :class="{ 'is-smaller': showIssueForm }">
+ <board-card
+ v-for="(issue, index) in issues"
+ ref="issue"
+ :index="index"
+ :list="list"
+ :issue="issue"
+ :issue-link-base="issueLinkBase"
+ :root-path="rootPath"
+ :disabled="disabled"
+ :key="issue.id" />
+ <li
+ class="board-list-count text-center"
+ v-if="showCount"
+ data-issue-id="-1">
+ <loading-icon
+ v-show="list.loadingMore"
+ label="Loading more issues"
+ />
+ <span
+ v-if="list.issues.length === list.issuesSize"
+ >
+ Showing all issues
+ </span>
+ <span
+ v-else
+ >
+ Showing {{ list.issues.length }} of {{ list.issuesSize }} issues
+ </span>
+ </li>
+ </ul>
+ </div>
+</template>
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js
index 983429550f0..add24303e7b 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
@@ -2,6 +2,7 @@
import Vue from 'vue';
import Flash from '../../flash';
+import { __ } from '../../locale';
import Sidebar from '../../right_sidebar';
import eventHub from '../../sidebar/event_hub';
import assigneeTitle from '../../sidebar/components/assignees/assignee_title';
@@ -95,7 +96,7 @@ gl.issueBoards.BoardSidebar = Vue.extend({
})
.catch(() => {
this.loadingAssignees = false;
- return new Flash('An error occurred while saving assignees');
+ Flash(__('An error occurred while saving assignees'));
});
},
},
diff --git a/app/assets/javascripts/boards/components/modal/footer.js b/app/assets/javascripts/boards/components/modal/footer.js
index 182957113a2..03cd7ef65cb 100644
--- a/app/assets/javascripts/boards/components/modal/footer.js
+++ b/app/assets/javascripts/boards/components/modal/footer.js
@@ -1,7 +1,6 @@
-/* eslint-disable no-new */
-
import Vue from 'vue';
import Flash from '../../../flash';
+import { __ } from '../../../locale';
import './lists_dropdown';
import { pluralize } from '../../../lib/utils/text_utility';
@@ -36,7 +35,7 @@ gl.issueBoards.ModalFooter = Vue.extend({
gl.boardService.bulkUpdate(issueIds, {
add_label_ids: [list.label.id],
}).catch(() => {
- new Flash('Failed to update issues, please try again.', 'alert');
+ Flash(__('Failed to update issues, please try again.'));
selectedIssues.forEach((issue) => {
list.removeIssue(issue);
diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js b/app/assets/javascripts/boards/components/new_list_dropdown.js
index c19c989680d..cf0bb5f5376 100644
--- a/app/assets/javascripts/boards/components/new_list_dropdown.js
+++ b/app/assets/javascripts/boards/components/new_list_dropdown.js
@@ -1,5 +1,6 @@
/* eslint-disable func-names, no-new, space-before-function-paren, one-var,
promise/catch-or-return */
+import axios from '~/lib/utils/axios_utils';
import _ from 'underscore';
import CreateLabelDropdown from '../../create_label';
@@ -28,9 +29,9 @@ gl.issueBoards.newListDropdownInit = () => {
$this.glDropdown({
data(term, callback) {
- $.get($this.attr('data-list-labels-path'))
- .then((resp) => {
- callback(resp);
+ axios.get($this.attr('data-list-labels-path'))
+ .then(({ data }) => {
+ callback(data);
});
},
renderRow (label) {
diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.js b/app/assets/javascripts/boards/components/sidebar/remove_issue.js
index 1ad97211934..0ae32bb4d0a 100644
--- a/app/assets/javascripts/boards/components/sidebar/remove_issue.js
+++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.js
@@ -1,7 +1,6 @@
-/* eslint-disable no-new */
-
import Vue from 'vue';
import Flash from '../../../flash';
+import { __ } from '../../../locale';
const Store = gl.issueBoards.BoardsStore;
@@ -45,7 +44,7 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({
},
};
Vue.http.patch(this.updateUrl, data).catch(() => {
- new Flash('Failed to remove issue from board, please try again.', 'alert');
+ Flash(__('Failed to remove issue from board, please try again.'));
lists.forEach((list) => {
list.addIssue(issue);
diff --git a/app/assets/javascripts/ci_variable_list/ajax_variable_list.js b/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
new file mode 100644
index 00000000000..b33adff609f
--- /dev/null
+++ b/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
@@ -0,0 +1,117 @@
+import _ from 'underscore';
+import axios from '../lib/utils/axios_utils';
+import { s__ } from '../locale';
+import Flash from '../flash';
+import { convertPermissionToBoolean } from '../lib/utils/common_utils';
+import statusCodes from '../lib/utils/http_status';
+import VariableList from './ci_variable_list';
+
+function generateErrorBoxContent(errors) {
+ const errorList = [].concat(errors).map(errorString => `
+ <li>
+ ${_.escape(errorString)}
+ </li>
+ `);
+
+ return `
+ <p>
+ ${s__('CiVariable|Validation failed')}
+ </p>
+ <ul>
+ ${errorList.join('')}
+ </ul>
+ `;
+}
+
+// Used for the variable list on CI/CD projects/groups settings page
+export default class AjaxVariableList {
+ constructor({
+ container,
+ saveButton,
+ errorBox,
+ formField = 'variables',
+ saveEndpoint,
+ }) {
+ this.container = container;
+ this.saveButton = saveButton;
+ this.errorBox = errorBox;
+ this.saveEndpoint = saveEndpoint;
+
+ this.variableList = new VariableList({
+ container: this.container,
+ formField,
+ });
+
+ this.bindEvents();
+ this.variableList.init();
+ }
+
+ bindEvents() {
+ this.saveButton.addEventListener('click', this.onSaveClicked.bind(this));
+ }
+
+ onSaveClicked() {
+ const loadingIcon = this.saveButton.querySelector('.js-secret-variables-save-loading-icon');
+ loadingIcon.classList.toggle('hide', false);
+ this.errorBox.classList.toggle('hide', true);
+ // We use this to prevent a user from changing a key before we have a chance
+ // to match it up in `updateRowsWithPersistedVariables`
+ this.variableList.toggleEnableRow(false);
+
+ return axios.patch(this.saveEndpoint, {
+ variables_attributes: this.variableList.getAllData(),
+ }, {
+ // We want to be able to process the `res.data` from a 400 error response
+ // and print the validation messages such as duplicate variable keys
+ validateStatus: status => (
+ status >= statusCodes.OK &&
+ status < statusCodes.MULTIPLE_CHOICES
+ ) ||
+ status === statusCodes.BAD_REQUEST,
+ })
+ .then((res) => {
+ loadingIcon.classList.toggle('hide', true);
+ this.variableList.toggleEnableRow(true);
+
+ if (res.status === statusCodes.OK && res.data) {
+ this.updateRowsWithPersistedVariables(res.data.variables);
+ this.variableList.hideValues();
+ } else if (res.status === statusCodes.BAD_REQUEST) {
+ // Validation failed
+ this.errorBox.innerHTML = generateErrorBoxContent(res.data);
+ this.errorBox.classList.toggle('hide', false);
+ }
+ })
+ .catch(() => {
+ loadingIcon.classList.toggle('hide', true);
+ this.variableList.toggleEnableRow(true);
+ Flash(s__('CiVariable|Error occured while saving variables'));
+ });
+ }
+
+ updateRowsWithPersistedVariables(persistedVariables = []) {
+ const persistedVariableMap = [].concat(persistedVariables).reduce((variableMap, variable) => ({
+ ...variableMap,
+ [variable.key]: variable,
+ }), {});
+
+ this.container.querySelectorAll('.js-row').forEach((row) => {
+ // If we submitted a row that was destroyed, remove it so we don't try
+ // to destroy it again which would cause a BE error
+ const destroyInput = row.querySelector('.js-ci-variable-input-destroy');
+ if (convertPermissionToBoolean(destroyInput.value)) {
+ row.remove();
+ // Update the ID input so any future edits and `_destroy` will apply on the BE
+ } else {
+ const key = row.querySelector('.js-ci-variable-input-key').value;
+ const persistedVariable = persistedVariableMap[key];
+
+ if (persistedVariable) {
+ // eslint-disable-next-line no-param-reassign
+ row.querySelector('.js-ci-variable-input-id').value = persistedVariable.id;
+ row.setAttribute('data-is-persisted', 'true');
+ }
+ }
+ });
+ }
+}
diff --git a/app/assets/javascripts/ci_variable_list/ci_variable_list.js b/app/assets/javascripts/ci_variable_list/ci_variable_list.js
index e46478ddb98..745f3404295 100644
--- a/app/assets/javascripts/ci_variable_list/ci_variable_list.js
+++ b/app/assets/javascripts/ci_variable_list/ci_variable_list.js
@@ -11,7 +11,7 @@ function createEnvironmentItem(value) {
return {
title: value === '*' ? ALL_ENVIRONMENTS_STRING : value,
id: value,
- text: value,
+ text: value === '*' ? s__('CiVariable|* (All environments)') : value,
};
}
@@ -39,13 +39,13 @@ export default class VariableList {
},
protected: {
selector: '.js-ci-variable-input-protected',
- default: 'true',
+ default: 'false',
},
- environment: {
+ environment_scope: {
// We can't use a `.js-` class here because
// gl_dropdown replaces the <input> and doesn't copy over the class
// See https://gitlab.com/gitlab-org/gitlab-ce/issues/42458
- selector: `input[name="${this.formField}[variables_attributes][][environment]"]`,
+ selector: `input[name="${this.formField}[variables_attributes][][environment_scope]"]`,
default: '*',
},
_destroy: {
@@ -104,12 +104,15 @@ export default class VariableList {
setupToggleButtons($row[0]);
+ // Reset the resizable textarea
+ $row.find(this.inputMap.value.selector).css('height', '');
+
const $environmentSelect = $row.find('.js-variable-environment-toggle');
if ($environmentSelect.length) {
const createItemDropdown = new CreateItemDropdown({
$dropdown: $environmentSelect,
defaultToggleLabel: ALL_ENVIRONMENTS_STRING,
- fieldName: `${this.formField}[variables_attributes][][environment]`,
+ fieldName: `${this.formField}[variables_attributes][][environment_scope]`,
getData: (term, callback) => callback(this.getEnvironmentValues()),
createNewItemFromValue: createEnvironmentItem,
onSelect: () => {
@@ -117,7 +120,7 @@ export default class VariableList {
// so they have the new value we just picked
this.refreshDropdownData();
- $row.find(this.inputMap.environment.selector).trigger('trigger-change');
+ $row.find(this.inputMap.environment_scope.selector).trigger('trigger-change');
},
});
@@ -143,7 +146,8 @@ export default class VariableList {
$row.after($rowClone);
}
- removeRow($row) {
+ removeRow(row) {
+ const $row = $(row);
const isPersisted = convertPermissionToBoolean($row.attr('data-is-persisted'));
if (isPersisted) {
@@ -155,6 +159,10 @@ export default class VariableList {
} else {
$row.remove();
}
+
+ // Refresh the other dropdowns in the variable list
+ // so any value with the variable deleted is gone
+ this.refreshDropdownData();
}
checkIfRowTouched($row) {
@@ -165,6 +173,15 @@ export default class VariableList {
});
}
+ toggleEnableRow(isEnabled = true) {
+ this.$container.find(this.inputMap.key.selector).attr('disabled', !isEnabled);
+ this.$container.find('.js-row-remove-button').attr('disabled', !isEnabled);
+ }
+
+ hideValues() {
+ this.secretValues.updateDom(false);
+ }
+
getAllData() {
// Ignore the last empty row because we don't want to try persist
// a blank variable and run into validation problems.
@@ -185,7 +202,7 @@ export default class VariableList {
}
getEnvironmentValues() {
- const valueMap = this.$container.find(this.inputMap.environment.selector).toArray()
+ const valueMap = this.$container.find(this.inputMap.environment_scope.selector).toArray()
.reduce((prevValueMap, envInput) => ({
...prevValueMap,
[envInput.value]: envInput.value,
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js
index 3d6ec37e6dd..b070a59cf15 100644
--- a/app/assets/javascripts/clusters/clusters_bundle.js
+++ b/app/assets/javascripts/clusters/clusters_bundle.js
@@ -32,6 +32,7 @@ export default class Clusters {
installIngressPath,
installRunnerPath,
installPrometheusPath,
+ managePrometheusPath,
clusterStatus,
clusterStatusReason,
helpPath,
@@ -40,6 +41,7 @@ export default class Clusters {
this.store = new ClustersStore();
this.store.setHelpPaths(helpPath, ingressHelpPath);
+ this.store.setManagePrometheusPath(managePrometheusPath);
this.store.updateStatus(clusterStatus);
this.store.updateStatusReason(clusterStatusReason);
this.service = new ClustersService({
@@ -95,6 +97,7 @@ export default class Clusters {
applications: this.state.applications,
helpPath: this.state.helpPath,
ingressHelpPath: this.state.ingressHelpPath,
+ managePrometheusPath: this.state.managePrometheusPath,
},
});
},
diff --git a/app/assets/javascripts/clusters/components/application_row.vue b/app/assets/javascripts/clusters/components/application_row.vue
index c13bbcee863..50e35bbbba5 100644
--- a/app/assets/javascripts/clusters/components/application_row.vue
+++ b/app/assets/javascripts/clusters/components/application_row.vue
@@ -32,6 +32,10 @@
type: String,
required: false,
},
+ manageLink: {
+ type: String,
+ required: false,
+ },
description: {
type: String,
required: true,
@@ -89,6 +93,12 @@
return label;
},
+ showManageButton() {
+ return this.manageLink && this.status === APPLICATION_INSTALLED;
+ },
+ manageButtonLabel() {
+ return s__('ClusterIntegration|Manage');
+ },
hasError() {
return this.status === APPLICATION_ERROR ||
this.requestStatus === REQUEST_FAILURE;
@@ -141,9 +151,21 @@
<div v-html="description"></div>
</div>
<div
- class="table-section table-button-footer section-15 section-align-top"
+ class="table-section table-button-footer section-align-top"
+ :class="{ 'section-20': showManageButton, 'section-15': !showManageButton }"
role="gridcell"
>
+ <div
+ v-if="showManageButton"
+ class="btn-group table-action-buttons"
+ >
+ <a
+ class="btn"
+ :href="manageLink"
+ >
+ {{ manageButtonLabel }}
+ </a>
+ </div>
<div class="btn-group table-action-buttons">
<loading-button
class="js-cluster-application-install-button"
diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue
index f4259700370..978881a4831 100644
--- a/app/assets/javascripts/clusters/components/applications.vue
+++ b/app/assets/javascripts/clusters/components/applications.vue
@@ -23,13 +23,19 @@
required: false,
default: '',
},
+ managePrometheusPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
generalApplicationDescription() {
return sprintf(
- _.escape(s__(`ClusterIntegration|Install applications on your Kubernetes cluster.
- Read more about %{helpLink}`)),
- {
+ _.escape(s__(
+ `ClusterIntegration|Install applications on your Kubernetes cluster.
+ Read more about %{helpLink}`,
+ )), {
helpLink: `<a href="${this.helpPath}">
${_.escape(s__('ClusterIntegration|installing applications'))}
</a>`,
@@ -96,11 +102,12 @@
},
prometheusDescription() {
return sprintf(
- _.escape(s__(`ClusterIntegration|Prometheus is an open-source monitoring system
- with %{gitlabIntegrationLink} to monitor deployed applications.`)),
- {
+ _.escape(s__(
+ `ClusterIntegration|Prometheus is an open-source monitoring system
+ with %{gitlabIntegrationLink} to monitor deployed applications.`,
+ )), {
gitlabIntegrationLink: `<a href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html"
-target="_blank" rel="noopener noreferrer">
+ target="_blank" rel="noopener noreferrer">
${_.escape(s__('ClusterIntegration|GitLab Integration'))}</a>`,
},
false,
@@ -149,6 +156,7 @@ target="_blank" rel="noopener noreferrer">
id="prometheus"
:title="applications.prometheus.title"
title-link="https://prometheus.io/docs/introduction/overview/"
+ :manage-link="managePrometheusPath"
:description="prometheusDescription"
:status="applications.prometheus.status"
:status-reason="applications.prometheus.statusReason"
diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js
index 49c3d184ef9..904ee5fd475 100644
--- a/app/assets/javascripts/clusters/stores/clusters_store.js
+++ b/app/assets/javascripts/clusters/stores/clusters_store.js
@@ -45,6 +45,10 @@ export default class ClusterStore {
this.state.ingressHelpPath = ingressHelpPath;
}
+ setManagePrometheusPath(managePrometheusPath) {
+ this.state.managePrometheusPath = managePrometheusPath;
+ }
+
updateStatus(status) {
this.state.status = status;
}
diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js
index 525fbf9dac9..6504a0bbbfc 100644
--- a/app/assets/javascripts/commit/image_file.js
+++ b/app/assets/javascripts/commit/image_file.js
@@ -1,5 +1,4 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-use-before-define, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, quotes, one-var, one-var-declaration-per-line, no-unused-vars, no-return-assign, comma-dangle, quote-props, no-unused-expressions, no-sequences, object-shorthand, max-len */
-import 'vendor/jquery.waitforimages';
// Width where images must fits in, for 2-up this gets divided by 2
const availWidth = 900;
diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js
index 4b2f75fffde..2be63bd8c76 100644
--- a/app/assets/javascripts/commits.js
+++ b/app/assets/javascripts/commits.js
@@ -1,52 +1,36 @@
-/* eslint-disable func-names, wrap-iife, consistent-return,
- no-return-assign, no-param-reassign, one-var-declaration-per-line, no-unused-vars,
- prefer-template, object-shorthand, prefer-arrow-callback */
-
import { pluralize } from './lib/utils/text_utility';
import { localTimeAgo } from './lib/utils/datetime_utility';
import Pager from './pager';
import axios from './lib/utils/axios_utils';
-export default (function () {
- const CommitsList = {};
-
- CommitsList.timer = null;
+export default class CommitsList {
+ constructor(limit = 0) {
+ this.timer = null;
- CommitsList.init = function (limit) {
this.$contentList = $('.content_list');
- $('body').on('click', '.day-commits-table li.commit', function (e) {
- if (e.target.nodeName !== 'A') {
- location.href = $(this).attr('url');
- e.stopPropagation();
- return false;
- }
- });
-
- Pager.init(parseInt(limit, 10), false, false, this.processCommits);
+ Pager.init(parseInt(limit, 10), false, false, this.processCommits.bind(this));
this.content = $('#commits-list');
this.searchField = $('#commits-search');
this.lastSearch = this.searchField.val();
- return this.initSearch();
- };
+ this.initSearch();
+ }
- CommitsList.initSearch = function () {
+ initSearch() {
this.timer = null;
- return this.searchField.keyup((function (_this) {
- return function () {
- clearTimeout(_this.timer);
- return _this.timer = setTimeout(_this.filterResults, 500);
- };
- })(this));
- };
+ this.searchField.on('keyup', () => {
+ clearTimeout(this.timer);
+ this.timer = setTimeout(this.filterResults.bind(this), 500);
+ });
+ }
- CommitsList.filterResults = function () {
+ filterResults() {
const form = $('.commits-search-form');
- const search = CommitsList.searchField.val();
- if (search === CommitsList.lastSearch) return Promise.resolve();
- const commitsUrl = form.attr('action') + '?' + form.serialize();
- CommitsList.content.fadeTo('fast', 0.5);
+ const search = this.searchField.val();
+ if (search === this.lastSearch) return Promise.resolve();
+ const commitsUrl = `${form.attr('action')}?${form.serialize()}`;
+ this.content.fadeTo('fast', 0.5);
const params = form.serializeArray().reduce((acc, obj) => Object.assign(acc, {
[obj.name]: obj.value,
}), {});
@@ -55,9 +39,9 @@ export default (function () {
params,
})
.then(({ data }) => {
- CommitsList.lastSearch = search;
- CommitsList.content.html(data.html);
- CommitsList.content.fadeTo('fast', 1.0);
+ this.lastSearch = search;
+ this.content.html(data.html);
+ this.content.fadeTo('fast', 1.0);
// Change url so if user reload a page - search results are saved
history.replaceState({
@@ -65,16 +49,16 @@ export default (function () {
}, document.title, commitsUrl);
})
.catch(() => {
- CommitsList.content.fadeTo('fast', 1.0);
- CommitsList.lastSearch = null;
+ this.content.fadeTo('fast', 1.0);
+ this.lastSearch = null;
});
- };
+ }
// Prepare loaded data.
- CommitsList.processCommits = (data) => {
+ processCommits(data) {
let processedData = data;
const $processedData = $(processedData);
- const $commitsHeadersLast = CommitsList.$contentList.find('li.js-commit-header').last();
+ const $commitsHeadersLast = this.$contentList.find('li.js-commit-header').last();
const lastShownDay = $commitsHeadersLast.data('day');
const $loadedCommitsHeadersFirst = $processedData.filter('li.js-commit-header').first();
const loadedShownDayFirst = $loadedCommitsHeadersFirst.data('day');
@@ -97,7 +81,5 @@ export default (function () {
localTimeAgo($processedData.find('.js-timeago'));
return processedData;
- };
-
- return CommitsList;
-})();
+ }
+}
diff --git a/app/assets/javascripts/commons/jquery.js b/app/assets/javascripts/commons/jquery.js
index b93e94a3c97..a7ed175f7a4 100644
--- a/app/assets/javascripts/commons/jquery.js
+++ b/app/assets/javascripts/commons/jquery.js
@@ -6,5 +6,5 @@ import 'vendor/jquery.endless-scroll';
import 'vendor/jquery.caret';
import 'vendor/jquery.atwho';
import 'vendor/jquery.scrollTo';
-import 'vendor/jquery.waitforimages';
+import 'jquery.waitforimages';
import 'select2/select2';
diff --git a/app/assets/javascripts/commons/polyfills/element.js b/app/assets/javascripts/commons/polyfills/element.js
index 9a1f73bf2ac..b593bde6aa2 100644
--- a/app/assets/javascripts/commons/polyfills/element.js
+++ b/app/assets/javascripts/commons/polyfills/element.js
@@ -18,3 +18,22 @@ Element.prototype.matches = Element.prototype.matches ||
while (i >= 0 && elms.item(i) !== this) { i -= 1; }
return i > -1;
};
+
+// From the polyfill on MDN, https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove#Polyfill
+((arr) => {
+ arr.forEach((item) => {
+ if (Object.prototype.hasOwnProperty.call(item, 'remove')) {
+ return;
+ }
+ Object.defineProperty(item, 'remove', {
+ configurable: true,
+ enumerable: true,
+ writable: true,
+ value: function remove() {
+ if (this.parentNode !== null) {
+ this.parentNode.removeChild(this);
+ }
+ },
+ });
+ });
+})([Element.prototype, CharacterData.prototype, DocumentType.prototype]);
diff --git a/app/assets/javascripts/create_merge_request_dropdown.js b/app/assets/javascripts/create_merge_request_dropdown.js
index 482d83621e2..fb1fc9cd32e 100644
--- a/app/assets/javascripts/create_merge_request_dropdown.js
+++ b/app/assets/javascripts/create_merge_request_dropdown.js
@@ -180,6 +180,7 @@ export default class CreateMergeRequestDropdown {
valueAttribute: 'data-text',
},
],
+ hideOnClick: false,
};
}
diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js
index a162424b3cf..3ab8f3ab7ad 100644
--- a/app/assets/javascripts/diff.js
+++ b/app/assets/javascripts/diff.js
@@ -1,3 +1,6 @@
+import axios from '~/lib/utils/axios_utils';
+import flash from '~/flash';
+import { __ } from '~/locale';
import { getLocationHash } from './lib/utils/url_utility';
import FilesCommentButton from './files_comment_button';
import SingleFileDiff from './single_file_diff';
@@ -69,7 +72,9 @@ export default class Diff {
const view = file.data('view');
const params = { since, to, bottom, offset, unfold, view };
- $.get(link, params, response => $target.parent().replaceWith(response));
+ axios.get(link, { params })
+ .then(({ data }) => $target.parent().replaceWith(data))
+ .catch(() => flash(__('An error occurred while loading diff')));
}
openAnchoredDiff(cb) {
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index ab28b7d8d44..7e750d15d3d 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -1,15 +1,9 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */
-import MergeRequest from './merge_request';
import Flash from './flash';
import GfmAutoComplete from './gfm_auto_complete';
-import ZenMode from './zen_mode';
-import initNotes from './init_notes';
-import initIssuableSidebar from './init_issuable_sidebar';
import { convertPermissionToBoolean } from './lib/utils/common_utils';
import GlFieldErrors from './gl_field_errors';
import Shortcuts from './shortcuts';
-import ShortcutsIssuable from './shortcuts_issuable';
-import Diff from './diff';
import SearchAutocomplete from './search_autocomplete';
var Dispatcher;
@@ -49,154 +43,14 @@ var Dispatcher;
});
switch (page) {
- case 'projects:environments:metrics':
- import('./pages/projects/environments/metrics')
- .then(callDefault)
- .catch(fail);
- break;
case 'projects:merge_requests:index':
case 'projects:issues:index':
case 'projects:issues:show':
- shortcut_handler = true;
- break;
- case 'projects:milestones:index':
- import('./pages/projects/milestones/index')
- .then(callDefault)
- .catch(fail);
- break;
- case 'projects:milestones:show':
- import('./pages/projects/milestones/show')
- .then(callDefault)
- .catch(fail);
- break;
- case 'groups:milestones:show':
- import('./pages/groups/milestones/show')
- .then(callDefault)
- .catch(fail);
- break;
- case 'dashboard:milestones:show':
- import('./pages/dashboard/milestones/show')
- .then(callDefault)
- .catch(fail);
- break;
- case 'dashboard:issues':
- import('./pages/dashboard/issues')
- .then(callDefault)
- .catch(fail);
- break;
- case 'dashboard:merge_requests':
- import('./pages/dashboard/merge_requests')
- .then(callDefault)
- .catch(fail);
- break;
- case 'groups:issues':
- import('./pages/groups/issues')
- .then(callDefault)
- .catch(fail);
- break;
- case 'groups:merge_requests':
- import('./pages/groups/merge_requests')
- .then(callDefault)
- .catch(fail);
- break;
- case 'dashboard:todos:index':
- import('./pages/dashboard/todos/index')
- .then(callDefault)
- .catch(fail);
- break;
- case 'admin:jobs:index':
- import('./pages/admin/jobs/index')
- .then(callDefault)
- .catch(fail);
- break;
- case 'dashboard:projects:index':
- case 'dashboard:projects:starred':
- import('./pages/dashboard/projects')
- .then(callDefault)
- .catch(fail);
- break;
- case 'explore:projects:index':
- case 'explore:projects:trending':
- case 'explore:projects:starred':
- import('./pages/explore/projects')
- .then(callDefault)
- .catch(fail);
- break;
- case 'explore:groups:index':
- import('./pages/explore/groups')
- .then(callDefault)
- .catch(fail);
- break;
- case 'projects:milestones:new':
- case 'projects:milestones:create':
- import('./pages/projects/milestones/new')
- .then(callDefault)
- .catch(fail);
- break;
- case 'projects:milestones:edit':
- case 'projects:milestones:update':
- import('./pages/projects/milestones/edit')
- .then(callDefault)
- .catch(fail);
- break;
- case 'groups:milestones:new':
- case 'groups:milestones:create':
- import('./pages/groups/milestones/new')
- .then(callDefault)
- .catch(fail);
- break;
- case 'groups:milestones:edit':
- case 'groups:milestones:update':
- import('./pages/groups/milestones/edit')
- .then(callDefault)
- .catch(fail);
- break;
- case 'projects:compare:show':
- import('./pages/projects/compare/show')
- .then(callDefault)
- .catch(fail);
- break;
- case 'projects:branches:new':
- import('./pages/projects/branches/new')
- .then(callDefault)
- .catch(fail);
- break;
- case 'projects:branches:create':
- import('./pages/projects/branches/new')
- .then(callDefault)
- .catch(fail);
- break;
- case 'projects:branches:index':
- import('./pages/projects/branches/index')
- .then(callDefault)
- .catch(fail);
- break;
case 'projects:issues:new':
- import('./pages/projects/issues/new')
- .then(callDefault)
- .catch(fail);
- shortcut_handler = true;
- break;
case 'projects:issues:edit':
- import('./pages/projects/issues/edit')
- .then(callDefault)
- .catch(fail);
- shortcut_handler = true;
- break;
case 'projects:merge_requests:creations:new':
- import('./pages/projects/merge_requests/creations/new')
- .then(callDefault)
- .catch(fail);
case 'projects:merge_requests:creations:diffs':
- import('./pages/projects/merge_requests/creations/diffs')
- .then(callDefault)
- .catch(fail);
- shortcut_handler = true;
- break;
case 'projects:merge_requests:edit':
- import('./pages/projects/merge_requests/edit')
- .then(callDefault)
- .catch(fail);
shortcut_handler = true;
break;
case 'projects:tags:new':
@@ -215,6 +69,11 @@ var Dispatcher;
.then(callDefault)
.catch(fail);
break;
+ case 'projects:services:edit':
+ import('./pages/projects/services/edit')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'projects:snippets:edit':
case 'projects:snippets:update':
import('./pages/projects/snippets/edit')
@@ -247,17 +106,10 @@ var Dispatcher;
.catch(fail);
break;
case 'projects:merge_requests:show':
- new Diff();
- new ZenMode();
-
- initIssuableSidebar();
- initNotes();
-
- const mrShowNode = document.querySelector('.merge-request');
- window.mergeRequest = new MergeRequest({
- action: mrShowNode.dataset.mrAction,
- });
- shortcut_handler = new ShortcutsIssuable(true);
+ import('./pages/projects/merge_requests/show')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
break;
case 'dashboard:activity':
import('./pages/dashboard/activity')
@@ -460,11 +312,6 @@ var Dispatcher;
.then(callDefault)
.catch(fail);
break;
- case 'users:show':
- import('./pages/users/show')
- .then(callDefault)
- .catch(fail);
- break;
case 'admin:conversational_development_index:show':
import('./pages/admin/conversational_development_index/show')
.then(callDefault)
diff --git a/app/assets/javascripts/droplab/constants.js b/app/assets/javascripts/droplab/constants.js
index 673e9bb4c0f..868d47e91b3 100644
--- a/app/assets/javascripts/droplab/constants.js
+++ b/app/assets/javascripts/droplab/constants.js
@@ -3,7 +3,6 @@ const DATA_DROPDOWN = 'data-dropdown';
const SELECTED_CLASS = 'droplab-item-selected';
const ACTIVE_CLASS = 'droplab-item-active';
const IGNORE_CLASS = 'droplab-item-ignore';
-const IGNORE_HIDING_CLASS = 'droplab-item-ignore-hiding';
// Matches `{{anything}}` and `{{ everything }}`.
const TEMPLATE_REGEX = /\{\{(.+?)\}\}/g;
@@ -14,5 +13,4 @@ export {
ACTIVE_CLASS,
TEMPLATE_REGEX,
IGNORE_CLASS,
- IGNORE_HIDING_CLASS,
};
diff --git a/app/assets/javascripts/droplab/drop_down.js b/app/assets/javascripts/droplab/drop_down.js
index 5eb0a339a1c..3cc316c3f3e 100644
--- a/app/assets/javascripts/droplab/drop_down.js
+++ b/app/assets/javascripts/droplab/drop_down.js
@@ -1,13 +1,14 @@
import utils from './utils';
-import { SELECTED_CLASS, IGNORE_CLASS, IGNORE_HIDING_CLASS } from './constants';
+import { SELECTED_CLASS, IGNORE_CLASS } from './constants';
class DropDown {
- constructor(list, config = {}) {
+ constructor(list, config = { }) {
this.currentIndex = 0;
this.hidden = true;
this.list = typeof list === 'string' ? document.querySelector(list) : list;
this.items = [];
this.eventWrapper = {};
+ this.hideOnClick = config.hideOnClick !== false;
if (config.addActiveClassToDropdownButton) {
this.dropdownToggle = this.list.parentNode.querySelector('.js-dropdown-toggle');
@@ -37,15 +38,17 @@ class DropDown {
clickEvent(e) {
if (e.target.tagName === 'UL') return;
- if (e.target.classList.contains(IGNORE_CLASS)) return;
+ if (e.target.closest(`.${IGNORE_CLASS}`)) return;
- const selected = utils.closest(e.target, 'LI');
+ const selected = e.target.closest('li');
if (!selected) return;
this.addSelectedClass(selected);
e.preventDefault();
- if (!e.target.classList.contains(IGNORE_HIDING_CLASS)) this.hide();
+ if (this.hideOnClick) {
+ this.hide();
+ }
const listEvent = new CustomEvent('click.dl', {
detail: {
diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue
index a9d554e549e..79326ca3487 100644
--- a/app/assets/javascripts/environments/components/environment_item.vue
+++ b/app/assets/javascripts/environments/components/environment_item.vue
@@ -1,8 +1,9 @@
<script>
import Timeago from 'timeago.js';
import _ from 'underscore';
- import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
- import { humanize } from '../../lib/utils/text_utility';
+ import tooltip from '~/vue_shared/directives/tooltip';
+ import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
+ import { humanize } from '~/lib/utils/text_utility';
import ActionsComponent from './environment_actions.vue';
import ExternalUrlComponent from './environment_external_url.vue';
import StopComponent from './environment_stop.vue';
@@ -21,14 +22,18 @@
export default {
components: {
- userAvatarLink,
- 'commit-component': CommitComponent,
- 'actions-component': ActionsComponent,
- 'external-url-component': ExternalUrlComponent,
- 'stop-component': StopComponent,
- 'rollback-component': RollbackComponent,
- 'terminal-button-component': TerminalButtonComponent,
- 'monitoring-button-component': MonitoringButtonComponent,
+ UserAvatarLink,
+ CommitComponent,
+ ActionsComponent,
+ ExternalUrlComponent,
+ StopComponent,
+ RollbackComponent,
+ TerminalButtonComponent,
+ MonitoringButtonComponent,
+ },
+
+ directives: {
+ tooltip,
},
props: {
@@ -443,7 +448,11 @@
v-if="!model.isFolder"
class="environment-name flex-truncate-parent table-mobile-content"
:href="environmentPath">
- <span class="flex-truncate-child">{{ model.name }}</span>
+ <span
+ class="flex-truncate-child"
+ v-tooltip
+ :title="model.name"
+ >{{ model.name }}</span>
</a>
<span
v-else
diff --git a/app/assets/javascripts/filtered_search/filtered_search_bundle.js b/app/assets/javascripts/filtered_search/filtered_search_bundle.js
index 6d5dd747224..293154917fa 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_bundle.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_bundle.js
@@ -3,7 +3,6 @@ import './dropdown_hint';
import './dropdown_non_user';
import './dropdown_user';
import './dropdown_utils';
-import './filtered_search_token_keys';
import './filtered_search_dropdown_manager';
import './filtered_search_dropdown';
import './filtered_search_manager';
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
index ff046aa286a..b2add862051 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
@@ -3,11 +3,11 @@ import DropLab from '~/droplab/drop_lab';
import FilteredSearchContainer from './container';
class FilteredSearchDropdownManager {
- constructor(baseEndpoint = '', tokenizer, page) {
+ constructor(baseEndpoint = '', tokenizer, page, isGroup, filteredSearchTokenKeys) {
this.container = FilteredSearchContainer.container;
this.baseEndpoint = baseEndpoint.replace(/\/$/, '');
this.tokenizer = tokenizer;
- this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
+ this.filteredSearchTokenKeys = filteredSearchTokenKeys;
this.filteredSearchInput = this.container.querySelector('.filtered-search');
this.page = page;
@@ -29,7 +29,15 @@ class FilteredSearchDropdownManager {
}
setupMapping() {
- this.mapping = {
+ const supportedTokens = this.filteredSearchTokenKeys.getKeys();
+ const allowedMappings = {
+ hint: {
+ reference: null,
+ gl: 'DropdownHint',
+ element: this.container.querySelector('#js-dropdown-hint'),
+ },
+ };
+ const availableMappings = {
author: {
reference: null,
gl: 'DropdownUser',
@@ -64,12 +72,15 @@ class FilteredSearchDropdownManager {
gl: 'DropdownEmoji',
element: this.container.querySelector('#js-dropdown-my-reaction'),
},
- hint: {
- reference: null,
- gl: 'DropdownHint',
- element: this.container.querySelector('#js-dropdown-hint'),
- },
};
+
+ supportedTokens.forEach((type) => {
+ if (availableMappings[type]) {
+ allowedMappings[type] = availableMappings[type];
+ }
+ });
+
+ this.mapping = allowedMappings;
}
static addWordToInput(tokenName, tokenValue = '', clicked = false) {
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 58ed0012f01..532a5fe1090 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -3,20 +3,33 @@ import { visitUrl } from '../lib/utils/url_utility';
import Flash from '../flash';
import FilteredSearchContainer from './container';
import RecentSearchesRoot from './recent_searches_root';
+import FilteredSearchTokenKeys from './filtered_search_token_keys';
import RecentSearchesStore from './stores/recent_searches_store';
import RecentSearchesService from './services/recent_searches_service';
import eventHub from './event_hub';
import { addClassIfElementExists } from '../lib/utils/dom_utils';
class FilteredSearchManager {
- constructor(page) {
+ constructor({
+ page,
+ filteredSearchTokenKeys = FilteredSearchTokenKeys,
+ stateFiltersSelector = '.issues-state-filters',
+ }) {
+ this.isGroup = false;
+ this.states = ['opened', 'closed', 'merged', 'all'];
+
this.page = page;
this.container = FilteredSearchContainer.container;
this.filteredSearchInput = this.container.querySelector('.filtered-search');
this.filteredSearchInputForm = this.filteredSearchInput.form;
this.clearSearchButton = this.container.querySelector('.clear-search');
this.tokensContainer = this.container.querySelector('.tokens-container');
- this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
+ this.filteredSearchTokenKeys = filteredSearchTokenKeys;
+ this.stateFiltersSelector = stateFiltersSelector;
+ this.recentsStorageKeyNames = {
+ issues: 'issue-recent-searches',
+ merge_requests: 'merge-request-recent-searches',
+ };
this.recentSearchesStore = new RecentSearchesStore({
isLocalStorageAvailable: RecentSearchesService.isAvailable(),
@@ -25,11 +38,7 @@ class FilteredSearchManager {
this.searchHistoryDropdownElement = document.querySelector('.js-filtered-search-history-dropdown');
const fullPath = this.searchHistoryDropdownElement ?
this.searchHistoryDropdownElement.dataset.fullPath : 'project';
- let recentSearchesPagePrefix = 'issue-recent-searches';
- if (this.page === 'merge_requests') {
- recentSearchesPagePrefix = 'merge-request-recent-searches';
- }
- const recentSearchesKey = `${fullPath}-${recentSearchesPagePrefix}`;
+ const recentSearchesKey = `${fullPath}-${this.recentsStorageKeyNames[this.page]}`;
this.recentSearchesService = new RecentSearchesService(recentSearchesKey);
}
@@ -58,7 +67,13 @@ class FilteredSearchManager {
if (this.filteredSearchInput) {
this.tokenizer = gl.FilteredSearchTokenizer;
- this.dropdownManager = new gl.FilteredSearchDropdownManager(this.filteredSearchInput.getAttribute('data-base-endpoint') || '', this.tokenizer, this.page);
+ this.dropdownManager = new gl.FilteredSearchDropdownManager(
+ this.filteredSearchInput.getAttribute('data-base-endpoint') || '',
+ this.tokenizer,
+ this.page,
+ this.isGroup,
+ this.filteredSearchTokenKeys,
+ );
this.recentSearchesRoot = new RecentSearchesRoot(
this.recentSearchesStore,
@@ -86,40 +101,33 @@ class FilteredSearchManager {
}
bindStateEvents() {
- this.stateFilters = document.querySelector('.container-fluid .issues-state-filters');
+ this.stateFilters = document.querySelector(`.container-fluid ${this.stateFiltersSelector}`);
if (this.stateFilters) {
this.searchStateWrapper = this.searchState.bind(this);
- this.stateFilters.querySelector('[data-state="opened"]')
- .addEventListener('click', this.searchStateWrapper);
- this.stateFilters.querySelector('[data-state="closed"]')
- .addEventListener('click', this.searchStateWrapper);
- this.stateFilters.querySelector('[data-state="all"]')
- .addEventListener('click', this.searchStateWrapper);
-
- this.mergedState = this.stateFilters.querySelector('[data-state="merged"]');
- if (this.mergedState) {
- this.mergedState.addEventListener('click', this.searchStateWrapper);
- }
+ this.applyToStateFilters((filterEl) => {
+ filterEl.addEventListener('click', this.searchStateWrapper);
+ });
}
}
unbindStateEvents() {
if (this.stateFilters) {
- this.stateFilters.querySelector('[data-state="opened"]')
- .removeEventListener('click', this.searchStateWrapper);
- this.stateFilters.querySelector('[data-state="closed"]')
- .removeEventListener('click', this.searchStateWrapper);
- this.stateFilters.querySelector('[data-state="all"]')
- .removeEventListener('click', this.searchStateWrapper);
-
- if (this.mergedState) {
- this.mergedState.removeEventListener('click', this.searchStateWrapper);
- }
+ this.applyToStateFilters((filterEl) => {
+ filterEl.removeEventListener('click', this.searchStateWrapper);
+ });
}
}
+ applyToStateFilters(callback) {
+ this.stateFilters.querySelectorAll('a[data-state]').forEach((filterEl) => {
+ if (this.states.indexOf(filterEl.dataset.state) > -1) {
+ callback(filterEl);
+ }
+ });
+ }
+
bindEvents() {
this.handleFormSubmit = this.handleFormSubmit.bind(this);
this.setDropdownWrapper = this.dropdownManager.setDropdown.bind(this.dropdownManager);
diff --git a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
index be595d7df1a..087ef5cd6f2 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
@@ -71,7 +71,7 @@ const conditions = [{
value: 'none',
}];
-class FilteredSearchTokenKeys {
+export default class FilteredSearchTokenKeys {
static get() {
return tokenKeys;
}
@@ -121,6 +121,3 @@ class FilteredSearchTokenKeys {
.find(condition => condition.tokenKey === key && condition.value === value) || null;
}
}
-
-window.gl = window.gl || {};
-gl.FilteredSearchTokenKeys = FilteredSearchTokenKeys;
diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js
index d0f9e6af0f8..d200044b79f 100644
--- a/app/assets/javascripts/gl_form.js
+++ b/app/assets/javascripts/gl_form.js
@@ -1,5 +1,4 @@
-/* global autosize */
-
+import autosize from 'autosize';
import GfmAutoComplete from './gfm_auto_complete';
import dropzoneInput from './dropzone_input';
import textUtils from './lib/utils/text_markdown';
diff --git a/app/assets/javascripts/gpg_badges.js b/app/assets/javascripts/gpg_badges.js
index 7ac9dcd1112..b33165f9402 100644
--- a/app/assets/javascripts/gpg_badges.js
+++ b/app/assets/javascripts/gpg_badges.js
@@ -1,3 +1,8 @@
+import { parseQueryStringIntoObject } from '~/lib/utils/common_utils';
+import axios from '~/lib/utils/axios_utils';
+import flash from '~/flash';
+import { __ } from '~/locale';
+
export default class GpgBadges {
static fetch() {
const badges = $('.js-loading-gpg-badge');
@@ -5,13 +10,13 @@ export default class GpgBadges {
badges.html('<i class="fa fa-spinner fa-spin"></i>');
- $.get({
- url: form.data('signatures-path'),
- data: form.serialize(),
- }).done((response) => {
- response.signatures.forEach((signature) => {
+ const params = parseQueryStringIntoObject(form.serialize());
+ return axios.get(form.data('signatures-path'), { params })
+ .then(({ data }) => {
+ data.signatures.forEach((signature) => {
badges.filter(`[data-commit-sha="${signature.commit_sha}"]`).replaceWith(signature.html);
});
- });
+ })
+ .catch(() => flash(__('An error occurred while loading commits')));
}
}
diff --git a/app/assets/javascripts/groups/components/app.vue b/app/assets/javascripts/groups/components/app.vue
index e035ba462db..b8f0566f48c 100644
--- a/app/assets/javascripts/groups/components/app.vue
+++ b/app/assets/javascripts/groups/components/app.vue
@@ -152,14 +152,14 @@ export default {
showLeaveGroupModal(group, parentGroup) {
this.targetGroup = group;
this.targetParentGroup = parentGroup;
- this.showModal = true;
+ this.updateModal = true;
this.groupLeaveConfirmationMessage = s__(`GroupsTree|Are you sure you want to leave the "${group.fullName}" group?`);
},
hideLeaveGroupModal() {
- this.showModal = false;
+ this.updateModal = false;
},
leaveGroup() {
- this.showModal = false;
+ this.updateModal = false;
this.targetGroup.isBeingRemoved = true;
this.service.leaveGroup(this.targetGroup.leavePath)
.then(res => res.json())
diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js
index a69a0bde17b..65a2395fe29 100644
--- a/app/assets/javascripts/groups_select.js
+++ b/app/assets/javascripts/groups_select.js
@@ -1,5 +1,6 @@
+import axios from './lib/utils/axios_utils';
import Api from './api';
-import { normalizeCRLFHeaders } from './lib/utils/common_utils';
+import { normalizeHeaders } from './lib/utils/common_utils';
export default function groupsSelect() {
// Needs to be accessible in rspec
@@ -17,24 +18,23 @@ export default function groupsSelect() {
dataType: 'json',
quietMillis: 250,
transport(params) {
- return $.ajax(params)
- .then((data, status, xhr) => {
- const results = data || [];
-
- const headers = normalizeCRLFHeaders(xhr.getAllResponseHeaders());
+ axios[params.type.toLowerCase()](params.url, {
+ params: params.data,
+ })
+ .then((res) => {
+ const results = res.data || [];
+ const headers = normalizeHeaders(res.headers);
const currentPage = parseInt(headers['X-PAGE'], 10) || 0;
const totalPages = parseInt(headers['X-TOTAL-PAGES'], 10) || 0;
const more = currentPage < totalPages;
- return {
+ params.success({
results,
pagination: {
more,
},
- };
- })
- .then(params.success)
- .fail(params.error);
+ });
+ }).catch(params.error);
},
data(search, page) {
return {
diff --git a/app/assets/javascripts/ide/monaco_loader.js b/app/assets/javascripts/ide/monaco_loader.js
index af83a1ec0b4..142a220097b 100644
--- a/app/assets/javascripts/ide/monaco_loader.js
+++ b/app/assets/javascripts/ide/monaco_loader.js
@@ -6,6 +6,11 @@ monacoContext.require.config({
},
});
+// ignore CDN config and use local assets path for service worker which cannot be cross-domain
+const relativeRootPath = (gon && gon.relative_url_root) || '';
+const monacoPath = `${relativeRootPath}/assets/webpack/monaco-editor/vs`;
+window.MonacoEnvironment = { getWorkerUrl: () => `${monacoPath}/base/worker/workerMain.js` };
+
// eslint-disable-next-line no-underscore-dangle
window.__monaco_context__ = monacoContext;
export default monacoContext.require;
diff --git a/app/assets/javascripts/importer_status.js b/app/assets/javascripts/importer_status.js
index 1dc70872d92..35094f8e73b 100644
--- a/app/assets/javascripts/importer_status.js
+++ b/app/assets/javascripts/importer_status.js
@@ -1,3 +1,7 @@
+import { __ } from './locale';
+import axios from './lib/utils/axios_utils';
+import flash from './flash';
+
class ImporterStatus {
constructor(jobsUrl, importUrl) {
this.jobsUrl = jobsUrl;
@@ -9,29 +13,7 @@ class ImporterStatus {
initStatusPage() {
$('.js-add-to-import')
.off('click')
- .on('click', (event) => {
- const $btn = $(event.currentTarget);
- const $tr = $btn.closest('tr');
- const $targetField = $tr.find('.import-target');
- const $namespaceInput = $targetField.find('.js-select-namespace option:selected');
- const id = $tr.attr('id').replace('repo_', '');
- let targetNamespace;
- let newName;
- if ($namespaceInput.length > 0) {
- targetNamespace = $namespaceInput[0].innerHTML;
- newName = $targetField.find('#path').prop('value');
- $targetField.empty().append(`${targetNamespace}/${newName}`);
- }
- $btn.disable().addClass('is-loading');
-
- return $.post(this.importUrl, {
- repo_id: id,
- target_namespace: targetNamespace,
- new_name: newName,
- }, {
- dataType: 'script',
- });
- });
+ .on('click', this.addToImport.bind(this));
$('.js-import-all')
.off('click')
@@ -44,34 +26,74 @@ class ImporterStatus {
});
}
- setAutoUpdate() {
- return setInterval(() => $.get(this.jobsUrl, data => $.each(data, (i, job) => {
- const jobItem = $(`#project_${job.id}`);
- const statusField = jobItem.find('.job-status');
+ addToImport(event) {
+ const $btn = $(event.currentTarget);
+ const $tr = $btn.closest('tr');
+ const $targetField = $tr.find('.import-target');
+ const $namespaceInput = $targetField.find('.js-select-namespace option:selected');
+ const id = $tr.attr('id').replace('repo_', '');
+ let targetNamespace;
+ let newName;
+ if ($namespaceInput.length > 0) {
+ targetNamespace = $namespaceInput[0].innerHTML;
+ newName = $targetField.find('#path').prop('value');
+ $targetField.empty().append(`${targetNamespace}/${newName}`);
+ }
+ $btn.disable().addClass('is-loading');
+
+ return axios.post(this.importUrl, {
+ repo_id: id,
+ target_namespace: targetNamespace,
+ new_name: newName,
+ })
+ .then(({ data }) => {
+ const job = $(`tr#repo_${id}`);
+ job.attr('id', `project_${data.id}`);
- const spinner = '<i class="fa fa-spinner fa-spin"></i>';
+ job.find('.import-target').html(`<a href="${data.full_path}">${data.full_path}</a>`);
+ $('table.import-jobs tbody').prepend(job);
- switch (job.import_status) {
- case 'finished':
- jobItem.removeClass('active').addClass('success');
- statusField.html('<span><i class="fa fa-check"></i> done</span>');
- break;
- case 'scheduled':
- statusField.html(`${spinner} scheduled`);
- break;
- case 'started':
- statusField.html(`${spinner} started`);
- break;
- default:
- statusField.html(job.import_status);
- break;
- }
- })), 4000);
+ job.addClass('active');
+ job.find('.import-actions').html('<i class="fa fa-spinner fa-spin" aria-label="importing"></i> started');
+ })
+ .catch(() => flash(__('An error occurred while importing project')));
+ }
+
+ autoUpdate() {
+ return axios.get(this.jobsUrl)
+ .then(({ data = [] }) => {
+ data.forEach((job) => {
+ const jobItem = $(`#project_${job.id}`);
+ const statusField = jobItem.find('.job-status');
+
+ const spinner = '<i class="fa fa-spinner fa-spin"></i>';
+
+ switch (job.import_status) {
+ case 'finished':
+ jobItem.removeClass('active').addClass('success');
+ statusField.html('<span><i class="fa fa-check"></i> done</span>');
+ break;
+ case 'scheduled':
+ statusField.html(`${spinner} scheduled`);
+ break;
+ case 'started':
+ statusField.html(`${spinner} started`);
+ break;
+ default:
+ statusField.html(job.import_status);
+ break;
+ }
+ });
+ });
+ }
+
+ setAutoUpdate() {
+ setInterval(this.autoUpdate.bind(this), 4000);
}
}
// eslint-disable-next-line consistent-return
-export default function initImporterStatus() {
+function initImporterStatus() {
const importerStatus = document.querySelector('.js-importer-status');
if (importerStatus) {
@@ -79,3 +101,8 @@ export default function initImporterStatus() {
return new ImporterStatus(data.jobsImportPath, data.importPath);
}
}
+
+export {
+ initImporterStatus as default,
+ ImporterStatus,
+};
diff --git a/app/assets/javascripts/integrations/index.js b/app/assets/javascripts/integrations/index.js
deleted file mode 100644
index 10fe6bac0e8..00000000000
--- a/app/assets/javascripts/integrations/index.js
+++ /dev/null
@@ -1,7 +0,0 @@
-/* eslint-disable no-new */
-import IntegrationSettingsForm from './integration_settings_form';
-
-$(() => {
- const integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form');
- integrationSettingsForm.init();
-});
diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js
index ff65ea99e9a..333bbd9e0ba 100644
--- a/app/assets/javascripts/issue.js
+++ b/app/assets/javascripts/issue.js
@@ -1,5 +1,4 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, max-len */
-import 'vendor/jquery.waitforimages';
import axios from './lib/utils/axios_utils';
import { addDelimiter } from './lib/utils/text_utility';
import flash from './flash';
@@ -25,6 +24,51 @@ export default class Issue {
if (Issue.createMrDropdownWrap) {
this.createMergeRequestDropdown = new CreateMergeRequestDropdown(Issue.createMrDropdownWrap);
}
+
+ // Listen to state changes in the Vue app
+ document.addEventListener('issuable_vue_app:change', (event) => {
+ this.updateTopState(event.detail.isClosed, event.detail.data);
+ });
+ }
+
+ /**
+ * This method updates the top area of the issue.
+ *
+ * Once the issue state changes, either through a click on the top area (jquery)
+ * or a click on the bottom area (Vue) we need to update the top area.
+ *
+ * @param {Boolean} isClosed
+ * @param {Array} data
+ * @param {String} issueFailMessage
+ */
+ updateTopState(isClosed, data, issueFailMessage = 'Unable to update this issue at this time.') {
+ if ('id' in data) {
+ const isClosedBadge = $('div.status-box-issue-closed');
+ const isOpenBadge = $('div.status-box-open');
+ const projectIssuesCounter = $('.issue_counter');
+
+ isClosedBadge.toggleClass('hidden', !isClosed);
+ isOpenBadge.toggleClass('hidden', isClosed);
+
+ $(document).trigger('issuable:change', isClosed);
+ this.toggleCloseReopenButton(isClosed);
+
+ let numProjectIssues = Number(projectIssuesCounter.first().text().trim().replace(/[^\d]/, ''));
+ numProjectIssues = isClosed ? numProjectIssues - 1 : numProjectIssues + 1;
+ projectIssuesCounter.text(addDelimiter(numProjectIssues));
+
+ if (this.createMergeRequestDropdown) {
+ if (isClosed) {
+ this.createMergeRequestDropdown.unavailable();
+ this.createMergeRequestDropdown.disable();
+ } else {
+ // We should check in case a branch was created in another tab
+ this.createMergeRequestDropdown.checkAbilityToCreateBranch();
+ }
+ }
+ } else {
+ flash(issueFailMessage);
+ }
}
initIssueBtnEventListeners() {
@@ -45,34 +89,8 @@ export default class Issue {
url = $button.attr('href');
return axios.put(url)
.then(({ data }) => {
- const isClosedBadge = $('div.status-box-issue-closed');
- const isOpenBadge = $('div.status-box-open');
- const projectIssuesCounter = $('.issue_counter');
-
- if ('id' in data) {
- const isClosed = $button.hasClass('btn-close');
- isClosedBadge.toggleClass('hidden', !isClosed);
- isOpenBadge.toggleClass('hidden', isClosed);
-
- $(document).trigger('issuable:change', isClosed);
- this.toggleCloseReopenButton(isClosed);
-
- let numProjectIssues = Number(projectIssuesCounter.first().text().trim().replace(/[^\d]/, ''));
- numProjectIssues = isClosed ? numProjectIssues - 1 : numProjectIssues + 1;
- projectIssuesCounter.text(addDelimiter(numProjectIssues));
-
- if (this.createMergeRequestDropdown) {
- if (isClosed) {
- this.createMergeRequestDropdown.unavailable();
- this.createMergeRequestDropdown.disable();
- } else {
- // We should check in case a branch was created in another tab
- this.createMergeRequestDropdown.checkAbilityToCreateBranch();
- }
- }
- } else {
- flash(issueFailMessage);
- }
+ const isClosed = $button.hasClass('btn-close');
+ this.updateTopState(isClosed, data);
})
.catch(() => flash(issueFailMessage))
.then(() => {
diff --git a/app/assets/javascripts/job.js b/app/assets/javascripts/job.js
index d0b7ea75082..f39ae764d3c 100644
--- a/app/assets/javascripts/job.js
+++ b/app/assets/javascripts/job.js
@@ -217,7 +217,7 @@ export default class Job {
}
this.isLogComplete = log.complete;
- if (!log.complete) {
+ if (log.complete === false) {
this.timeout = setTimeout(() => {
this.getBuildTrace();
}, 4000);
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 5811d059e0b..7d2cf4b634f 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -1,5 +1,6 @@
import axios from './axios_utils';
import { getLocationHash } from './url_utility';
+import { convertToCamelCase } from './text_utility';
export const getPagePath = (index = 0) => $('body').attr('data-page').split(':')[index];
@@ -395,6 +396,26 @@ export const spriteIcon = (icon, className = '') => {
return `<svg ${classAttribute}><use xlink:href="${gon.sprite_icons}#${icon}" /></svg>`;
};
+/**
+ * This method takes in object with snake_case property names
+ * and returns new object with camelCase property names
+ *
+ * Reasoning for this method is to ensure consistent property
+ * naming conventions across JS code.
+ */
+export const convertObjectPropsToCamelCase = (obj = {}) => {
+ if (obj === null) {
+ return {};
+ }
+
+ return Object.keys(obj).reduce((acc, prop) => {
+ const result = acc;
+
+ result[convertToCamelCase(prop)] = obj[prop];
+ return acc;
+ }, {});
+};
+
export const imagePath = imgUrl => `${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/${imgUrl}`;
window.gl = window.gl || {};
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index 1fa6715180e..d6cccbef42b 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -10,6 +10,20 @@ window.timeago = timeago;
window.dateFormat = dateFormat;
/**
+ * Returns i18n month names array.
+ * If `abbreviated` is provided, returns abbreviated
+ * name.
+ *
+ * @param {Boolean} abbreviated
+ */
+const getMonthNames = (abbreviated) => {
+ if (abbreviated) {
+ return [s__('Jan'), s__('Feb'), s__('Mar'), s__('Apr'), s__('May'), s__('Jun'), s__('Jul'), s__('Aug'), s__('Sep'), s__('Oct'), s__('Nov'), s__('Dec')];
+ }
+ return [s__('January'), s__('February'), s__('March'), s__('April'), s__('May'), s__('June'), s__('July'), s__('August'), s__('September'), s__('October'), s__('November'), s__('December')];
+};
+
+/**
* Given a date object returns the day of the week in English
* @param {date} date
* @returns {String}
@@ -143,7 +157,6 @@ export const getDayDifference = (a, b) => {
* @param {Number} seconds
* @return {String}
*/
-// eslint-disable-next-line import/prefer-default-export
export function timeIntervalInWords(intervalInSeconds) {
const secondsInteger = parseInt(intervalInSeconds, 10);
const minutes = Math.floor(secondsInteger / 60);
@@ -158,7 +171,7 @@ export function timeIntervalInWords(intervalInSeconds) {
return text;
}
-export function dateInWords(date, abbreviated = false) {
+export function dateInWords(date, abbreviated = false, hideYear = false) {
if (!date) return date;
const month = date.getMonth();
@@ -169,9 +182,115 @@ export function dateInWords(date, abbreviated = false) {
const monthName = abbreviated ? monthNamesAbbr[month] : monthNames[month];
+ if (hideYear) {
+ return `${monthName} ${date.getDate()}`;
+ }
+
return `${monthName} ${date.getDate()}, ${year}`;
}
+/**
+ * Returns month name based on provided date.
+ *
+ * @param {Date} date
+ * @param {Boolean} abbreviated
+ */
+export const monthInWords = (date, abbreviated = false) => {
+ if (!date) {
+ return '';
+ }
+
+ return getMonthNames(abbreviated)[date.getMonth()];
+};
+
+/**
+ * Returns number of days in a month for provided date.
+ * courtesy: https://stacko(verflow.com/a/1185804/414749
+ *
+ * @param {Date} date
+ */
+export const totalDaysInMonth = (date) => {
+ if (!date) {
+ return 0;
+ }
+ return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
+};
+
+/**
+ * Returns list of Dates referring to Sundays of the month
+ * based on provided date
+ *
+ * @param {Date} date
+ */
+export const getSundays = (date) => {
+ if (!date) {
+ return [];
+ }
+
+ const daysToSunday = ['Saturday', 'Friday', 'Thursday', 'Wednesday', 'Tuesday', 'Monday', 'Sunday'];
+
+ const month = date.getMonth();
+ const year = date.getFullYear();
+ const sundays = [];
+ const dateOfMonth = new Date(year, month, 1);
+
+ while (dateOfMonth.getMonth() === month) {
+ const dayName = getDayName(dateOfMonth);
+ if (dayName === 'Sunday') {
+ sundays.push(new Date(dateOfMonth.getTime()));
+ }
+
+ const daysUntilNextSunday = daysToSunday.indexOf(dayName) + 1;
+ dateOfMonth.setDate(dateOfMonth.getDate() + daysUntilNextSunday);
+ }
+
+ return sundays;
+};
+
+/**
+ * Returns list of Dates representing a timeframe of Months from month of provided date (inclusive)
+ * up to provided length
+ *
+ * For eg;
+ * If current month is January 2018 and `length` provided is `6`
+ * Then this method will return list of Date objects as follows;
+ *
+ * [ October 2017, November 2017, December 2017, January 2018, February 2018, March 2018 ]
+ *
+ * If current month is March 2018 and `length` provided is `3`
+ * Then this method will return list of Date objects as follows;
+ *
+ * [ February 2018, March 2018, April 2018 ]
+ *
+ * @param {Number} length
+ * @param {Date} date
+ */
+export const getTimeframeWindow = (length, date) => {
+ if (!length) {
+ return [];
+ }
+
+ const currentDate = date instanceof Date ? date : new Date();
+ const currentMonthIndex = Math.floor(length / 2);
+ const timeframe = [];
+
+ // Move date object backward to the first month of timeframe
+ currentDate.setDate(1);
+ currentDate.setMonth(currentDate.getMonth() - currentMonthIndex);
+
+ // Iterate and update date for the size of length
+ // and push date reference to timeframe list
+ for (let i = 0; i < length; i += 1) {
+ timeframe.push(new Date(currentDate.getTime()));
+ currentDate.setMonth(currentDate.getMonth() + 1);
+ }
+
+ // Change date of last timeframe item to last date of the month
+ timeframe[length - 1].setDate(totalDaysInMonth(timeframe[length - 1]));
+
+ return timeframe;
+};
+
window.gl = window.gl || {};
window.gl.utils = {
...(window.gl.utils || {}),
diff --git a/app/assets/javascripts/lib/utils/http_status.js b/app/assets/javascripts/lib/utils/http_status.js
index 625e53ee9de..bb151929431 100644
--- a/app/assets/javascripts/lib/utils/http_status.js
+++ b/app/assets/javascripts/lib/utils/http_status.js
@@ -6,4 +6,6 @@ export default {
ABORTED: 0,
NO_CONTENT: 204,
OK: 200,
+ MULTIPLE_CHOICES: 300,
+ BAD_REQUEST: 400,
};
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index 62d80c4a649..94d03621bff 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -73,3 +73,10 @@ export function capitalizeFirstCharacter(text) {
* @returns {String}
*/
export const stripHtml = (string, replace = '') => string.replace(/<[^>]*>/g, replace);
+
+/**
+ * Converts snake_case string to camelCase
+ *
+ * @param {*} string
+ */
+export const convertToCamelCase = string => string.replace(/(_\w)/g, s => s[1].toUpperCase());
diff --git a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js
index 93f8f6ee926..2cb238529aa 100644
--- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js
+++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js
@@ -2,7 +2,9 @@
/* global ace */
import Vue from 'vue';
-import Flash from '../../flash';
+import axios from '~/lib/utils/axios_utils';
+import flash from '~/flash';
+import { __ } from '~/locale';
((global) => {
global.mergeConflicts = global.mergeConflicts || {};
@@ -49,27 +51,26 @@ import Flash from '../../flash';
loadEditor() {
this.loading = true;
- $.get(this.file.content_path)
- .done((file) => {
+ axios.get(this.file.content_path)
+ .then(({ data }) => {
const content = this.$el.querySelector('pre');
- const fileContent = document.createTextNode(file.content);
+ const fileContent = document.createTextNode(data.content);
content.textContent = fileContent.textContent;
- this.originalContent = file.content;
+ this.originalContent = data.content;
this.fileLoaded = true;
this.editor = ace.edit(content);
this.editor.$blockScrolling = Infinity; // Turn off annoying warning
- this.editor.getSession().setMode(`ace/mode/${file.blob_ace_mode}`);
+ this.editor.getSession().setMode(`ace/mode/${data.blob_ace_mode}`);
this.editor.on('change', () => {
this.saveDiffResolution();
});
this.saveDiffResolution();
+ this.loading = false;
})
- .fail(() => {
- new Flash('Failed to load the file, please try again.');
- })
- .always(() => {
+ .catch(() => {
+ flash(__('An error occurred while loading the file'));
this.loading = false;
});
},
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index bedd50de1bb..a64093afcf4 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -1,6 +1,4 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, no-underscore-dangle, one-var, one-var-declaration-per-line, consistent-return, dot-notation, quote-props, comma-dangle, object-shorthand, max-len, prefer-arrow-callback */
-
-import 'vendor/jquery.waitforimages';
import { __ } from '~/locale';
import TaskList from './task_list';
import MergeRequestTabs from './merge_request_tabs';
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 5afae93724b..031badc7026 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -27,6 +27,7 @@
hasMetrics: convertPermissionToBoolean(metricsData.hasMetrics),
documentationPath: metricsData.documentationPath,
settingsPath: metricsData.settingsPath,
+ clustersPath: metricsData.clustersPath,
tagsPath: metricsData.tagsPath,
projectPath: metricsData.projectPath,
metricsEndpoint: metricsData.additionalMetrics,
@@ -132,6 +133,7 @@
:selected-state="state"
:documentation-path="documentationPath"
:settings-path="settingsPath"
+ :clusters-path="clustersPath"
:empty-getting-started-svg-path="emptyGettingStartedSvgPath"
:empty-loading-svg-path="emptyLoadingSvgPath"
:empty-unable-to-connect-svg-path="emptyUnableToConnectSvgPath"
diff --git a/app/assets/javascripts/monitoring/components/empty_state.vue b/app/assets/javascripts/monitoring/components/empty_state.vue
index 56cd60c583b..9517b8ccb67 100644
--- a/app/assets/javascripts/monitoring/components/empty_state.vue
+++ b/app/assets/javascripts/monitoring/components/empty_state.vue
@@ -10,6 +10,11 @@
required: false,
default: '',
},
+ clustersPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
selectedState: {
type: String,
required: true,
@@ -35,7 +40,10 @@
title: 'Get started with performance monitoring',
description: `Stay updated about the performance and health
of your environment by configuring Prometheus to monitor your deployments.`,
- buttonText: 'Configure Prometheus',
+ buttonText: 'Install Prometheus on clusters',
+ buttonPath: this.clustersPath,
+ secondaryButtonText: 'Configure existing Prometheus',
+ secondaryButtonPath: this.settingsPath,
},
loading: {
svgUrl: this.emptyLoadingSvgPath,
@@ -43,6 +51,7 @@
description: `Creating graphs uses the data from the Prometheus server.
If this takes a long time, ensure that data is available.`,
buttonText: 'View documentation',
+ buttonPath: this.documentationPath,
},
noData: {
svgUrl: this.emptyUnableToConnectSvgPath,
@@ -50,12 +59,14 @@
description: `You are connected to the Prometheus server, but there is currently
no data to display.`,
buttonText: 'Configure Prometheus',
+ buttonPath: this.settingsPath,
},
unableToConnect: {
svgUrl: this.emptyUnableToConnectSvgPath,
title: 'Unable to connect to Prometheus server',
description: 'Ensure connectivity is available from the GitLab server to the ',
buttonText: 'View documentation',
+ buttonPath: this.documentationPath,
},
},
};
@@ -65,13 +76,6 @@
return this.states[this.selectedState];
},
- buttonPath() {
- if (this.selectedState === 'gettingStarted') {
- return this.settingsPath;
- }
- return this.documentationPath;
- },
-
showButtonDescription() {
if (this.selectedState === 'unableToConnect') return true;
return false;
@@ -99,11 +103,21 @@
</p>
<div class="state-button">
<a
+ v-if="currentState.buttonPath"
class="btn btn-success"
- :href="buttonPath"
+ :href="currentState.buttonPath"
>
{{ currentState.buttonText }}
</a>
</div>
+ <div class="state-button">
+ <a
+ v-if="currentState.secondaryButtonPath"
+ class="btn"
+ :href="currentState.secondaryButtonPath"
+ >
+ {{ currentState.secondaryButtonText }}
+ </a>
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index 3c8452ac808..df796050e0d 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -2,16 +2,18 @@
import { mapActions, mapGetters } from 'vuex';
import _ from 'underscore';
import Autosize from 'autosize';
+ import { __ } from '~/locale';
import Flash from '../../flash';
import Autosave from '../../autosave';
import TaskList from '../../task_list';
import * as constants from '../constants';
import eventHub from '../event_hub';
import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
- import noteSignedOutWidget from './note_signed_out_widget.vue';
- import discussionLockedWidget from './discussion_locked_widget.vue';
import markdownField from '../../vue_shared/components/markdown/field.vue';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
+ import loadingButton from '../../vue_shared/components/loading_button.vue';
+ import noteSignedOutWidget from './note_signed_out_widget.vue';
+ import discussionLockedWidget from './discussion_locked_widget.vue';
import issuableStateMixin from '../mixins/issuable_state';
export default {
@@ -22,6 +24,7 @@
discussionLockedWidget,
markdownField,
userAvatarLink,
+ loadingButton,
},
mixins: [
issuableStateMixin,
@@ -30,9 +33,6 @@
return {
note: '',
noteType: constants.COMMENT,
- // Can't use mapGetters,
- // this needs to be in the data object because it belongs to the state
- issueState: this.$store.getters.getNoteableData.state,
isSubmitting: false,
isSubmitButtonDisabled: true,
};
@@ -43,6 +43,7 @@
'getUserData',
'getNoteableData',
'getNotesData',
+ 'issueState',
]),
isLoggedIn() {
return this.getUserData.id;
@@ -105,7 +106,7 @@
mounted() {
// jQuery is needed here because it is a custom event being dispatched with jQuery.
$(document).on('issuable:change', (e, isClosed) => {
- this.issueState = isClosed ? constants.CLOSED : constants.REOPENED;
+ this.toggleIssueLocalState(isClosed ? constants.CLOSED : constants.REOPENED);
});
this.initAutoSave();
@@ -117,6 +118,9 @@
'stopPolling',
'restartPolling',
'removePlaceholderNotes',
+ 'closeIssue',
+ 'reopenIssue',
+ 'toggleIssueLocalState',
]),
setIsSubmitButtonDisabled(note, isSubmitting) {
if (!_.isEmpty(note) && !isSubmitting) {
@@ -126,6 +130,8 @@
}
},
handleSave(withIssueAction) {
+ this.isSubmitting = true;
+
if (this.note.length) {
const noteData = {
endpoint: this.endpoint,
@@ -142,7 +148,6 @@
if (this.noteType === constants.DISCUSSION) {
noteData.data.note.type = constants.DISCUSSION_NOTE;
}
- this.isSubmitting = true;
this.note = ''; // Empty textarea while being requested. Repopulate in catch
this.resizeTextarea();
this.stopPolling();
@@ -184,13 +189,25 @@ Please check your network connection and try again.`;
this.toggleIssueState();
}
},
+ enableButton() {
+ this.isSubmitting = false;
+ },
toggleIssueState() {
- this.issueState = this.isIssueOpen ? constants.CLOSED : constants.REOPENED;
-
- // This is out of scope for the Notes Vue component.
- // It was the shortest path to update the issue state and relevant places.
- const btnClass = this.isIssueOpen ? 'btn-reopen' : 'btn-close';
- $(`.js-btn-issue-action.${btnClass}:visible`).trigger('click');
+ if (this.isIssueOpen) {
+ this.closeIssue()
+ .then(() => this.enableButton())
+ .catch(() => {
+ this.enableButton();
+ Flash(__('Something went wrong while closing the issue. Please try again later'));
+ });
+ } else {
+ this.reopenIssue()
+ .then(() => this.enableButton())
+ .catch(() => {
+ this.enableButton();
+ Flash(__('Something went wrong while reopening the issue. Please try again later'));
+ });
+ }
},
discard(shouldClear = true) {
// `blur` is needed to clear slash commands autocomplete cache if event fired.
@@ -367,15 +384,19 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
</li>
</ul>
</div>
- <button
- type="button"
- @click="handleSave(true)"
+
+ <loading-button
v-if="canUpdateIssue"
- :class="actionButtonClassNames"
+ :loading="isSubmitting"
+ @click="handleSave(true)"
+ :container-class="[
+ actionButtonClassNames,
+ 'btn btn-comment btn-comment-and-close js-action-button'
+ ]"
:disabled="isSubmitting"
- class="btn btn-comment btn-comment-and-close js-action-button">
- {{ issueActionButtonTitle }}
- </button>
+ :label="issueActionButtonTitle"
+ />
+
<button
type="button"
v-if="note.length"
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index 30e7ccc8229..045077de383 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -102,6 +102,7 @@
.then(() => {
this.isEditing = false;
this.isRequesting = false;
+ this.oldContent = null;
$(this.$refs.noteBody.$el).renderGFM();
this.$refs.noteBody.resetAutoSave();
callback();
diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js
index d250dd8d25b..48e7cfddb63 100644
--- a/app/assets/javascripts/notes/index.js
+++ b/app/assets/javascripts/notes/index.js
@@ -28,6 +28,8 @@ document.addEventListener('DOMContentLoaded', () => new Vue({
notesPath: notesDataset.notesPath,
markdownDocsPath: notesDataset.markdownDocsPath,
quickActionsDocsPath: notesDataset.quickActionsDocsPath,
+ closeIssuePath: notesDataset.closeIssuePath,
+ reopenIssuePath: notesDataset.reopenIssuePath,
},
};
},
diff --git a/app/assets/javascripts/notes/services/notes_service.js b/app/assets/javascripts/notes/services/notes_service.js
index b51b0cb2013..b8e7ffc8c46 100644
--- a/app/assets/javascripts/notes/services/notes_service.js
+++ b/app/assets/javascripts/notes/services/notes_service.js
@@ -32,4 +32,7 @@ export default {
toggleAward(endpoint, data) {
return Vue.http.post(endpoint, data, { emulateJSON: true });
},
+ toggleIssueState(endpoint, data) {
+ return Vue.http.put(endpoint, data);
+ },
};
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index 085b18642ba..4c846d69b86 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -61,6 +61,39 @@ export const createNewNote = ({ commit }, { endpoint, data }) => service
export const removePlaceholderNotes = ({ commit }) =>
commit(types.REMOVE_PLACEHOLDER_NOTES);
+export const closeIssue = ({ commit, dispatch, state }) => service
+ .toggleIssueState(state.notesData.closeIssuePath)
+ .then(res => res.json())
+ .then((data) => {
+ commit(types.CLOSE_ISSUE);
+ dispatch('emitStateChangedEvent', data);
+ });
+
+export const reopenIssue = ({ commit, dispatch, state }) => service
+ .toggleIssueState(state.notesData.reopenIssuePath)
+ .then(res => res.json())
+ .then((data) => {
+ commit(types.REOPEN_ISSUE);
+ dispatch('emitStateChangedEvent', data);
+ });
+
+export const emitStateChangedEvent = ({ commit, getters }, data) => {
+ const event = new CustomEvent('issuable_vue_app:change', { detail: {
+ data,
+ isClosed: getters.issueState === constants.CLOSED,
+ } });
+
+ document.dispatchEvent(event);
+};
+
+export const toggleIssueLocalState = ({ commit }, newState) => {
+ if (newState === constants.CLOSED) {
+ commit(types.CLOSE_ISSUE);
+ } else if (newState === constants.REOPENED) {
+ commit(types.REOPEN_ISSUE);
+ }
+};
+
export const saveNote = ({ commit, dispatch }, noteData) => {
const { note } = noteData.data.note;
let placeholderText = note;
diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js
index e18b277119e..82024104d73 100644
--- a/app/assets/javascripts/notes/stores/getters.js
+++ b/app/assets/javascripts/notes/stores/getters.js
@@ -8,6 +8,7 @@ export const getNotesDataByProp = state => prop => state.notesData[prop];
export const getNoteableData = state => state.noteableData;
export const getNoteableDataByProp = state => prop => state.noteableData[prop];
+export const issueState = state => state.noteableData.state;
export const getUserData = state => state.userData || {};
export const getUserDataByProp = state => prop => state.userData && state.userData[prop];
diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js
index d520c197407..6d7c3bbae0f 100644
--- a/app/assets/javascripts/notes/stores/mutation_types.js
+++ b/app/assets/javascripts/notes/stores/mutation_types.js
@@ -12,3 +12,7 @@ export const SHOW_PLACEHOLDER_NOTE = 'SHOW_PLACEHOLDER_NOTE';
export const TOGGLE_AWARD = 'TOGGLE_AWARD';
export const TOGGLE_DISCUSSION = 'TOGGLE_DISCUSSION';
export const UPDATE_NOTE = 'UPDATE_NOTE';
+
+// Issue
+export const CLOSE_ISSUE = 'CLOSE_ISSUE';
+export const REOPEN_ISSUE = 'REOPEN_ISSUE';
diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js
index 20f81a430c2..b3f66578c9a 100644
--- a/app/assets/javascripts/notes/stores/mutations.js
+++ b/app/assets/javascripts/notes/stores/mutations.js
@@ -152,4 +152,12 @@ export default {
noteObj.notes.splice(noteObj.notes.indexOf(comment), 1, note);
}
},
+
+ [types.CLOSE_ISSUE](state) {
+ Object.assign(state.noteableData, { state: constants.CLOSED });
+ },
+
+ [types.REOPEN_ISSUE](state) {
+ Object.assign(state.noteableData, { state: constants.REOPENED });
+ },
};
diff --git a/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue b/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue
index 555725cbe12..ba1d8e4d8db 100644
--- a/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue
+++ b/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue
@@ -1,13 +1,13 @@
<script>
import axios from '~/lib/utils/axios_utils';
- import Flash from '~/flash';
- import modal from '~/vue_shared/components/modal.vue';
- import { s__ } from '~/locale';
+ import createFlash from '~/flash';
+ import GlModal from '~/vue_shared/components/gl_modal.vue';
import { redirectTo } from '~/lib/utils/url_utility';
+ import { s__ } from '~/locale';
export default {
components: {
- modal,
+ GlModal,
},
props: {
url: {
@@ -17,7 +17,7 @@
},
computed: {
text() {
- return s__('AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running.');
+ return s__('AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running.');
},
},
methods: {
@@ -28,7 +28,7 @@
redirectTo(response.request.responseURL);
})
.catch((error) => {
- Flash(s__('AdminArea|Stopping jobs failed'));
+ createFlash(s__('AdminArea|Stopping jobs failed'));
throw error;
});
},
@@ -37,11 +37,13 @@
</script>
<template>
- <modal
+ <gl-modal
id="stop-jobs-modal"
- :title="s__('AdminArea|Stop all jobs?')"
- :text="text"
- kind="danger"
- :primary-button-label="s__('AdminArea|Stop jobs')"
- @submit="onSubmit" />
+ :header-title-text="s__('AdminArea|Stop all jobs?')"
+ footer-primary-button-variant="danger"
+ :footer-primary-button-text="s__('AdminArea|Stop jobs')"
+ @submit="onSubmit"
+ >
+ {{ text }}
+ </gl-modal>
</template>
diff --git a/app/assets/javascripts/pages/admin/jobs/index/index.js b/app/assets/javascripts/pages/admin/jobs/index/index.js
index 0e004bd9174..5a4f8c6e745 100644
--- a/app/assets/javascripts/pages/admin/jobs/index/index.js
+++ b/app/assets/javascripts/pages/admin/jobs/index/index.js
@@ -1,29 +1,28 @@
import Vue from 'vue';
-
import Translate from '~/vue_shared/translate';
-
import stopJobsModal from './components/stop_jobs_modal.vue';
Vue.use(Translate);
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
const stopJobsButton = document.getElementById('stop-jobs-button');
-
- // eslint-disable-next-line no-new
- new Vue({
- el: '#stop-jobs-modal',
- components: {
- stopJobsModal,
- },
- mounted() {
- stopJobsButton.classList.remove('disabled');
- },
- render(createElement) {
- return createElement('stop-jobs-modal', {
- props: {
- url: stopJobsButton.dataset.url,
- },
- });
- },
- });
-};
+ if (stopJobsButton) {
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: '#stop-jobs-modal',
+ components: {
+ stopJobsModal,
+ },
+ mounted() {
+ stopJobsButton.classList.remove('disabled');
+ },
+ render(createElement) {
+ return createElement('stop-jobs-modal', {
+ props: {
+ url: stopJobsButton.dataset.url,
+ },
+ });
+ },
+ });
+ }
+});
diff --git a/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue b/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue
new file mode 100644
index 00000000000..14315d5492e
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue
@@ -0,0 +1,125 @@
+<script>
+ import _ from 'underscore';
+ import modal from '~/vue_shared/components/modal.vue';
+ import { s__, sprintf } from '~/locale';
+
+ export default {
+ components: {
+ modal,
+ },
+ props: {
+ deleteProjectUrl: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ projectName: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ csrfToken: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ data() {
+ return {
+ enteredProjectName: '',
+ };
+ },
+ computed: {
+ title() {
+ return sprintf(s__('AdminProjects|Delete Project %{projectName}?'),
+ {
+ projectName: `'${_.escape(this.projectName)}'`,
+ },
+ false,
+ );
+ },
+ text() {
+ return sprintf(s__(`AdminProjects|
+ You’re about to permanently delete the project %{projectName}, its repository,
+ and all related resources including issues, merge requests, etc.. Once you confirm and press
+ %{strong_start}Delete project%{strong_end}, it cannot be undone or recovered.`),
+ {
+ projectName: `<strong>${_.escape(this.projectName)}</strong>`,
+ strong_start: '<strong>',
+ strong_end: '</strong>',
+ },
+ false,
+ );
+ },
+ confirmationTextLabel() {
+ return sprintf(s__('AdminUsers|To confirm, type %{projectName}'),
+ {
+ projectName: `<code>${_.escape(this.projectName)}</code>`,
+ },
+ false,
+ );
+ },
+ primaryButtonLabel() {
+ return s__('AdminProjects|Delete project');
+ },
+ canSubmit() {
+ return this.enteredProjectName === this.projectName;
+ },
+ },
+ methods: {
+ onCancel() {
+ this.enteredProjectName = '';
+ },
+ onSubmit() {
+ this.$refs.form.submit();
+ this.enteredProjectName = '';
+ },
+ },
+ };
+</script>
+
+<template>
+ <modal
+ id="delete-project-modal"
+ :title="title"
+ :text="text"
+ kind="danger"
+ :primary-button-label="primaryButtonLabel"
+ :submit-disabled="!canSubmit"
+ @submit="onSubmit"
+ @cancel="onCancel"
+ >
+ <template
+ slot="body"
+ slot-scope="props"
+ >
+ <p v-html="props.text"></p>
+ <p v-html="confirmationTextLabel"></p>
+ <form
+ ref="form"
+ :action="deleteProjectUrl"
+ method="post"
+ >
+ <input
+ ref="method"
+ type="hidden"
+ name="_method"
+ value="delete"
+ />
+ <input
+ type="hidden"
+ name="authenticity_token"
+ :value="csrfToken"
+ />
+ <input
+ name="projectName"
+ class="form-control"
+ type="text"
+ v-model="enteredProjectName"
+ aria-labelledby="input-label"
+ autocomplete="off"
+ />
+ </form>
+ </template>
+ </modal>
+</template>
diff --git a/app/assets/javascripts/pages/admin/projects/index/index.js b/app/assets/javascripts/pages/admin/projects/index/index.js
new file mode 100644
index 00000000000..3c597a1093e
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/projects/index/index.js
@@ -0,0 +1,37 @@
+import Vue from 'vue';
+
+import Translate from '~/vue_shared/translate';
+import csrf from '~/lib/utils/csrf';
+
+import deleteProjectModal from './components/delete_project_modal.vue';
+
+document.addEventListener('DOMContentLoaded', () => {
+ Vue.use(Translate);
+
+ const deleteProjectModalEl = document.getElementById('delete-project-modal');
+
+ const deleteModal = new Vue({
+ el: deleteProjectModalEl,
+ data: {
+ deleteProjectUrl: '',
+ projectName: '',
+ },
+ render(createElement) {
+ return createElement(deleteProjectModal, {
+ props: {
+ deleteProjectUrl: this.deleteProjectUrl,
+ projectName: this.projectName,
+ csrfToken: csrf.token,
+ },
+ });
+ },
+ });
+
+ $(document).on('shown.bs.modal', (event) => {
+ if (event.relatedTarget.classList.contains('delete-project-button')) {
+ const buttonProps = event.relatedTarget.dataset;
+ deleteModal.deleteProjectUrl = buttonProps.deleteProjectUrl;
+ deleteModal.projectName = buttonProps.projectName;
+ }
+ });
+});
diff --git a/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue b/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue
new file mode 100644
index 00000000000..7b5e333011e
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue
@@ -0,0 +1,174 @@
+<script>
+ import _ from 'underscore';
+ import modal from '~/vue_shared/components/modal.vue';
+ import { s__, sprintf } from '~/locale';
+
+ export default {
+ components: {
+ modal,
+ },
+ props: {
+ deleteUserUrl: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ blockUserUrl: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ deleteContributions: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ username: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ csrfToken: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ data() {
+ return {
+ enteredUsername: '',
+ };
+ },
+ computed: {
+ title() {
+ const keepContributionsTitle = s__('AdminUsers|Delete User %{username}?');
+ const deleteContributionsTitle = s__('AdminUsers|Delete User %{username} and contributions?');
+
+ return sprintf(
+ this.deleteContributions ? deleteContributionsTitle : keepContributionsTitle, {
+ username: `'${_.escape(this.username)}'`,
+ }, false);
+ },
+ text() {
+ const keepContributionsText = s__(`AdminArea|
+ You are about to permanently delete the user %{username}.
+ This will delete all of the issues, merge requests, and groups linked to them.
+ To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead.
+ Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered.`);
+
+ const deleteContributionsText = s__(`AdminArea|
+ You are about to permanently delete the user %{username}.
+ Issues, merge requests, and groups linked to them will be transferred to a system-wide "Ghost-user".
+ To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead.
+ Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered.`);
+
+ return sprintf(this.deleteContributions ? deleteContributionsText : keepContributionsText,
+ {
+ username: `<strong>${_.escape(this.username)}</strong>`,
+ strong_start: '<strong>',
+ strong_end: '</strong>',
+ },
+ false,
+ );
+ },
+ confirmationTextLabel() {
+ return sprintf(s__('AdminUsers|To confirm, type %{username}'),
+ {
+ username: `<code>${_.escape(this.username)}</code>`,
+ },
+ false,
+ );
+ },
+ primaryButtonLabel() {
+ const keepContributionsLabel = s__('AdminUsers|Delete user');
+ const deleteContributionsLabel = s__('AdminUsers|Delete user and contributions');
+
+ return this.deleteContributions ? deleteContributionsLabel : keepContributionsLabel;
+ },
+ secondaryButtonLabel() {
+ return s__('AdminUsers|Block user');
+ },
+ canSubmit() {
+ return this.enteredUsername === this.username;
+ },
+ },
+ methods: {
+ onCancel() {
+ this.enteredUsername = '';
+ },
+ onSecondaryAction() {
+ const form = this.$refs.form;
+
+ form.action = this.blockUserUrl;
+ this.$refs.method.value = 'put';
+
+ form.submit();
+ },
+ onSubmit() {
+ this.$refs.form.submit();
+ this.enteredUsername = '';
+ },
+ },
+ };
+</script>
+
+<template>
+ <modal
+ id="delete-user-modal"
+ :title="title"
+ :text="text"
+ kind="danger"
+ :primary-button-label="primaryButtonLabel"
+ :secondary-button-label="secondaryButtonLabel"
+ :submit-disabled="!canSubmit"
+ @submit="onSubmit"
+ @cancel="onCancel"
+ >
+ <template
+ slot="body"
+ slot-scope="props"
+ >
+ <p v-html="props.text"></p>
+ <p v-html="confirmationTextLabel"></p>
+ <form
+ ref="form"
+ :action="deleteUserUrl"
+ method="post"
+ >
+ <input
+ ref="method"
+ type="hidden"
+ name="_method"
+ value="delete"
+ />
+ <input
+ type="hidden"
+ name="authenticity_token"
+ :value="csrfToken"
+ />
+ <input
+ type="text"
+ name="username"
+ class="form-control"
+ v-model="enteredUsername"
+ aria-labelledby="input-label"
+ autocomplete="off"
+ />
+ </form>
+ </template>
+ <template
+ slot="secondary-button"
+ slot-scope="props"
+ >
+ <button
+ type="button"
+ class="btn js-secondary-button btn-warning"
+ :disabled="!canSubmit"
+ @click="onSecondaryAction"
+ data-dismiss="modal"
+ >
+ {{ secondaryButtonLabel }}
+ </button>
+ </template>
+ </modal>
+</template>
diff --git a/app/assets/javascripts/pages/admin/users/index.js b/app/assets/javascripts/pages/admin/users/index.js
new file mode 100644
index 00000000000..4f5d6b55031
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/users/index.js
@@ -0,0 +1,43 @@
+import Vue from 'vue';
+
+import Translate from '~/vue_shared/translate';
+import csrf from '~/lib/utils/csrf';
+
+import deleteUserModal from './components/delete_user_modal.vue';
+
+document.addEventListener('DOMContentLoaded', () => {
+ Vue.use(Translate);
+
+ const deleteUserModalEl = document.getElementById('delete-user-modal');
+
+ const deleteModal = new Vue({
+ el: deleteUserModalEl,
+ data: {
+ deleteUserUrl: '',
+ blockUserUrl: '',
+ deleteContributions: '',
+ username: '',
+ },
+ render(createElement) {
+ return createElement(deleteUserModal, {
+ props: {
+ deleteUserUrl: this.deleteUserUrl,
+ blockUserUrl: this.blockUserUrl,
+ deleteContributions: this.deleteContributions,
+ username: this.username,
+ csrfToken: csrf.token,
+ },
+ });
+ },
+ });
+
+ $(document).on('shown.bs.modal', (event) => {
+ if (event.relatedTarget.classList.contains('delete-user-button')) {
+ const buttonProps = event.relatedTarget.dataset;
+ deleteModal.deleteUserUrl = buttonProps.deleteUserUrl;
+ deleteModal.blockUserUrl = buttonProps.blockUserUrl;
+ deleteModal.deleteContributions = event.relatedTarget.hasAttribute('data-delete-contributions');
+ deleteModal.username = buttonProps.username;
+ }
+ });
+});
diff --git a/app/assets/javascripts/pages/ci/lints/ci_lint_editor.js b/app/assets/javascripts/pages/ci/lints/ci_lint_editor.js
index b9469e5b7cb..9ab73be80a0 100644
--- a/app/assets/javascripts/pages/ci/lints/ci_lint_editor.js
+++ b/app/assets/javascripts/pages/ci/lints/ci_lint_editor.js
@@ -2,11 +2,18 @@ export default class CILintEditor {
constructor() {
this.editor = window.ace.edit('ci-editor');
this.textarea = document.querySelector('#content');
+ this.clearYml = document.querySelector('.clear-yml');
this.editor.getSession().setMode('ace/mode/yaml');
this.editor.on('input', () => {
const content = this.editor.getSession().getValue();
this.textarea.value = content;
});
+
+ this.clearYml.addEventListener('click', this.clear.bind(this));
+ }
+
+ clear() {
+ this.editor.setValue('');
}
}
diff --git a/app/assets/javascripts/pages/dashboard/issues/index.js b/app/assets/javascripts/pages/dashboard/issues/index.js
index b7353669e65..c4901dd1cb6 100644
--- a/app/assets/javascripts/pages/dashboard/issues/index.js
+++ b/app/assets/javascripts/pages/dashboard/issues/index.js
@@ -1,7 +1,7 @@
import projectSelect from '~/project_select';
import initLegacyFilters from '~/init_legacy_filters';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
projectSelect();
initLegacyFilters();
-};
+});
diff --git a/app/assets/javascripts/pages/dashboard/merge_requests/index.js b/app/assets/javascripts/pages/dashboard/merge_requests/index.js
index b7353669e65..c4901dd1cb6 100644
--- a/app/assets/javascripts/pages/dashboard/merge_requests/index.js
+++ b/app/assets/javascripts/pages/dashboard/merge_requests/index.js
@@ -1,7 +1,7 @@
import projectSelect from '~/project_select';
import initLegacyFilters from '~/init_legacy_filters';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
projectSelect();
initLegacyFilters();
-};
+});
diff --git a/app/assets/javascripts/pages/dashboard/milestones/show/index.js b/app/assets/javascripts/pages/dashboard/milestones/show/index.js
index 2e7a08a369c..06195d73c0a 100644
--- a/app/assets/javascripts/pages/dashboard/milestones/show/index.js
+++ b/app/assets/javascripts/pages/dashboard/milestones/show/index.js
@@ -1,7 +1,7 @@
import Milestone from '~/milestone';
import Sidebar from '~/right_sidebar';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
new Milestone(); // eslint-disable-line no-new
new Sidebar(); // eslint-disable-line no-new
-};
+});
diff --git a/app/assets/javascripts/pages/dashboard/projects/index.js b/app/assets/javascripts/pages/dashboard/projects/index.js
index c88cbf1a6ba..0c585e162cb 100644
--- a/app/assets/javascripts/pages/dashboard/projects/index.js
+++ b/app/assets/javascripts/pages/dashboard/projects/index.js
@@ -1,3 +1,3 @@
import ProjectsList from '~/projects_list';
-export default () => new ProjectsList();
+document.addEventListener('DOMContentLoaded', () => new ProjectsList());
diff --git a/app/assets/javascripts/pages/dashboard/todos/index/index.js b/app/assets/javascripts/pages/dashboard/todos/index/index.js
index 77c23685943..9d2c2f2994f 100644
--- a/app/assets/javascripts/pages/dashboard/todos/index/index.js
+++ b/app/assets/javascripts/pages/dashboard/todos/index/index.js
@@ -1,3 +1,3 @@
import Todos from './todos';
-export default () => new Todos();
+document.addEventListener('DOMContentLoaded', () => new Todos());
diff --git a/app/assets/javascripts/pages/explore/groups/index.js b/app/assets/javascripts/pages/explore/groups/index.js
index e59c38b8bc4..3c7edbdd7c7 100644
--- a/app/assets/javascripts/pages/explore/groups/index.js
+++ b/app/assets/javascripts/pages/explore/groups/index.js
@@ -2,7 +2,7 @@ import GroupsList from '~/groups_list';
import Landing from '~/landing';
import initGroupsList from '../../../groups';
-export default function () {
+document.addEventListener('DOMContentLoaded', () => {
new GroupsList(); // eslint-disable-line no-new
initGroupsList();
const landingElement = document.querySelector('.js-explore-groups-landing');
@@ -13,4 +13,4 @@ export default function () {
'explore_groups_landing_dismissed',
);
exploreGroupsLanding.toggle();
-}
+});
diff --git a/app/assets/javascripts/pages/explore/projects/index.js b/app/assets/javascripts/pages/explore/projects/index.js
index c88cbf1a6ba..0c585e162cb 100644
--- a/app/assets/javascripts/pages/explore/projects/index.js
+++ b/app/assets/javascripts/pages/explore/projects/index.js
@@ -1,3 +1,3 @@
import ProjectsList from '~/projects_list';
-export default () => new ProjectsList();
+document.addEventListener('DOMContentLoaded', () => new ProjectsList());
diff --git a/app/assets/javascripts/pages/groups/issues/index.js b/app/assets/javascripts/pages/groups/issues/index.js
index 78db543a64d..d149b307e7f 100644
--- a/app/assets/javascripts/pages/groups/issues/index.js
+++ b/app/assets/javascripts/pages/groups/issues/index.js
@@ -2,7 +2,9 @@ import projectSelect from '~/project_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
import { FILTERED_SEARCH } from '~/pages/constants';
-export default () => {
- initFilteredSearch(FILTERED_SEARCH.ISSUES);
+document.addEventListener('DOMContentLoaded', () => {
+ initFilteredSearch({
+ page: FILTERED_SEARCH.ISSUES,
+ });
projectSelect();
-};
+});
diff --git a/app/assets/javascripts/pages/groups/merge_requests/index.js b/app/assets/javascripts/pages/groups/merge_requests/index.js
index 9b3af4537e7..a5cc1f34b63 100644
--- a/app/assets/javascripts/pages/groups/merge_requests/index.js
+++ b/app/assets/javascripts/pages/groups/merge_requests/index.js
@@ -2,7 +2,9 @@ import projectSelect from '~/project_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
import { FILTERED_SEARCH } from '~/pages/constants';
-export default () => {
- initFilteredSearch(FILTERED_SEARCH.MERGE_REQUESTS);
+document.addEventListener('DOMContentLoaded', () => {
+ initFilteredSearch({
+ page: FILTERED_SEARCH.MERGE_REQUESTS,
+ });
projectSelect();
-};
+});
diff --git a/app/assets/javascripts/pages/groups/milestones/edit/index.js b/app/assets/javascripts/pages/groups/milestones/edit/index.js
index 5c99c90e24d..ddd10fe5062 100644
--- a/app/assets/javascripts/pages/groups/milestones/edit/index.js
+++ b/app/assets/javascripts/pages/groups/milestones/edit/index.js
@@ -1,3 +1,3 @@
import initForm from '../../../../shared/milestones/form';
-export default () => initForm(false);
+document.addEventListener('DOMContentLoaded', () => initForm(false));
diff --git a/app/assets/javascripts/pages/groups/milestones/new/index.js b/app/assets/javascripts/pages/groups/milestones/new/index.js
index 5c99c90e24d..ddd10fe5062 100644
--- a/app/assets/javascripts/pages/groups/milestones/new/index.js
+++ b/app/assets/javascripts/pages/groups/milestones/new/index.js
@@ -1,3 +1,3 @@
import initForm from '../../../../shared/milestones/form';
-export default () => initForm(false);
+document.addEventListener('DOMContentLoaded', () => initForm(false));
diff --git a/app/assets/javascripts/pages/groups/milestones/show/index.js b/app/assets/javascripts/pages/groups/milestones/show/index.js
index c9a18353f2e..88f40b5278e 100644
--- a/app/assets/javascripts/pages/groups/milestones/show/index.js
+++ b/app/assets/javascripts/pages/groups/milestones/show/index.js
@@ -1,3 +1,3 @@
import initMilestonesShow from '~/pages/milestones/shared/init_milestones_show';
-export default initMilestonesShow;
+document.addEventListener('DOMContentLoaded', initMilestonesShow);
diff --git a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
index f26c7360fbe..ad79f7e09ac 100644
--- a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
+++ b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
@@ -1,11 +1,12 @@
-import SecretValues from '~/behaviors/secret_values';
+import AjaxVariableList from '~/ci_variable_list/ajax_variable_list';
export default () => {
- const secretVariableTable = document.querySelector('.js-secret-variable-table');
- if (secretVariableTable) {
- const secretVariableTableValues = new SecretValues({
- container: secretVariableTable,
- });
- secretVariableTableValues.init();
- }
+ const variableListEl = document.querySelector('.js-ci-variable-list-section');
+ // eslint-disable-next-line no-new
+ new AjaxVariableList({
+ container: variableListEl,
+ saveButton: variableListEl.querySelector('.js-secret-variables-save-button'),
+ errorBox: variableListEl.querySelector('.js-ci-variable-error-box'),
+ saveEndpoint: variableListEl.dataset.saveEndpoint,
+ });
};
diff --git a/app/assets/javascripts/pages/projects/branches/index/index.js b/app/assets/javascripts/pages/projects/branches/index/index.js
index cee0f19bf2a..8fa266a37ce 100644
--- a/app/assets/javascripts/pages/projects/branches/index/index.js
+++ b/app/assets/javascripts/pages/projects/branches/index/index.js
@@ -1,7 +1,7 @@
import AjaxLoadingSpinner from '~/ajax_loading_spinner';
import DeleteModal from '~/branches/branches_delete_modal';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
AjaxLoadingSpinner.init();
new DeleteModal(); // eslint-disable-line no-new
-};
+});
diff --git a/app/assets/javascripts/pages/projects/branches/new/index.js b/app/assets/javascripts/pages/projects/branches/new/index.js
index ae5e033e97e..d32d5c6cb29 100644
--- a/app/assets/javascripts/pages/projects/branches/new/index.js
+++ b/app/assets/javascripts/pages/projects/branches/new/index.js
@@ -1,3 +1,5 @@
import NewBranchForm from '~/new_branch_form';
-export default () => new NewBranchForm($('.js-create-branch-form'), JSON.parse(document.getElementById('availableRefs').innerHTML));
+document.addEventListener('DOMContentLoaded', () => (
+ new NewBranchForm($('.js-create-branch-form'), JSON.parse(document.getElementById('availableRefs').innerHTML))
+));
diff --git a/app/assets/javascripts/pages/projects/commits/show/index.js b/app/assets/javascripts/pages/projects/commits/show/index.js
index 90b5882a24f..6110fda17de 100644
--- a/app/assets/javascripts/pages/projects/commits/show/index.js
+++ b/app/assets/javascripts/pages/projects/commits/show/index.js
@@ -3,7 +3,7 @@ import GpgBadges from '~/gpg_badges';
import ShortcutsNavigation from '~/shortcuts_navigation';
export default () => {
- CommitsList.init(document.querySelector('.js-project-commits-show').dataset.commitsLimit);
+ new CommitsList(document.querySelector('.js-project-commits-show').dataset.commitsLimit); // eslint-disable-line no-new
new ShortcutsNavigation(); // eslint-disable-line no-new
GpgBadges.fetch();
};
diff --git a/app/assets/javascripts/pages/projects/compare/show/index.js b/app/assets/javascripts/pages/projects/compare/show/index.js
index 6b8d4503568..2b4fd3c47c0 100644
--- a/app/assets/javascripts/pages/projects/compare/show/index.js
+++ b/app/assets/javascripts/pages/projects/compare/show/index.js
@@ -1,8 +1,8 @@
import Diff from '~/diff';
import initChangesDropdown from '~/init_changes_dropdown';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
new Diff(); // eslint-disable-line no-new
const paddingTop = 16;
initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - paddingTop);
-};
+});
diff --git a/app/assets/javascripts/pages/projects/environments/metrics/index.js b/app/assets/javascripts/pages/projects/environments/metrics/index.js
index f4760cb2720..0b644780ad4 100644
--- a/app/assets/javascripts/pages/projects/environments/metrics/index.js
+++ b/app/assets/javascripts/pages/projects/environments/metrics/index.js
@@ -1,3 +1,3 @@
import monitoringBundle from '~/monitoring/monitoring_bundle';
-export default monitoringBundle;
+document.addEventListener('DOMContentLoaded', monitoringBundle);
diff --git a/app/assets/javascripts/pages/projects/issues/edit/index.js b/app/assets/javascripts/pages/projects/issues/edit/index.js
index 7f27f379d8c..ffc84dc106b 100644
--- a/app/assets/javascripts/pages/projects/issues/edit/index.js
+++ b/app/assets/javascripts/pages/projects/issues/edit/index.js
@@ -1,5 +1,3 @@
import initForm from '../form';
-export default () => {
- initForm();
-};
+document.addEventListener('DOMContentLoaded', initForm);
diff --git a/app/assets/javascripts/pages/projects/issues/index/index.js b/app/assets/javascripts/pages/projects/issues/index/index.js
index 39c043edc38..70fdb0ef40d 100644
--- a/app/assets/javascripts/pages/projects/issues/index/index.js
+++ b/app/assets/javascripts/pages/projects/issues/index/index.js
@@ -8,7 +8,9 @@ import { FILTERED_SEARCH } from '~/pages/constants';
import { ISSUABLE_INDEX } from '~/pages/projects/constants';
document.addEventListener('DOMContentLoaded', () => {
- initFilteredSearch(FILTERED_SEARCH.ISSUES);
+ initFilteredSearch({
+ page: FILTERED_SEARCH.ISSUES,
+ });
new IssuableIndex(ISSUABLE_INDEX.ISSUE);
new ShortcutsNavigation();
diff --git a/app/assets/javascripts/pages/projects/issues/new/index.js b/app/assets/javascripts/pages/projects/issues/new/index.js
index 7f27f379d8c..ffc84dc106b 100644
--- a/app/assets/javascripts/pages/projects/issues/new/index.js
+++ b/app/assets/javascripts/pages/projects/issues/new/index.js
@@ -1,5 +1,3 @@
import initForm from '../form';
-export default () => {
- initForm();
-};
+document.addEventListener('DOMContentLoaded', initForm);
diff --git a/app/assets/javascripts/pages/projects/merge_requests/creations/diffs/index.js b/app/assets/javascripts/pages/projects/merge_requests/creations/index.js
index 734d01ae6f2..febfecebbd2 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/creations/diffs/index.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/creations/index.js
@@ -1,3 +1,3 @@
import initMergeRequest from '~/pages/projects/merge_requests/init_merge_request';
-export default initMergeRequest;
+document.addEventListener('DOMContentLoaded', initMergeRequest);
diff --git a/app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js b/app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js
index ccd0b54c5ed..1d5aec4001d 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js
@@ -1,7 +1,7 @@
import Compare from '~/compare';
import MergeRequest from '~/merge_request';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
const mrNewCompareNode = document.querySelector('.js-merge-request-new-compare');
if (mrNewCompareNode) {
new Compare({ // eslint-disable-line no-new
@@ -15,4 +15,4 @@ export default () => {
action: mrNewSubmitNode.dataset.mrSubmitAction,
});
}
-};
+});
diff --git a/app/assets/javascripts/pages/projects/merge_requests/edit/index.js b/app/assets/javascripts/pages/projects/merge_requests/edit/index.js
index 734d01ae6f2..febfecebbd2 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/edit/index.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/edit/index.js
@@ -1,3 +1,3 @@
import initMergeRequest from '~/pages/projects/merge_requests/init_merge_request';
-export default initMergeRequest;
+document.addEventListener('DOMContentLoaded', initMergeRequest);
diff --git a/app/assets/javascripts/pages/projects/merge_requests/index/index.js b/app/assets/javascripts/pages/projects/merge_requests/index/index.js
index adadbf28e49..a7aa616319f 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/index/index.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/index/index.js
@@ -6,7 +6,9 @@ import { FILTERED_SEARCH } from '~/pages/constants';
import { ISSUABLE_INDEX } from '~/pages/projects/constants';
document.addEventListener('DOMContentLoaded', () => {
- initFilteredSearch(FILTERED_SEARCH.MERGE_REQUESTS);
+ initFilteredSearch({
+ page: FILTERED_SEARCH.MERGE_REQUESTS,
+ });
new IssuableIndex(ISSUABLE_INDEX.MERGE_REQUEST); // eslint-disable-line no-new
new ShortcutsNavigation(); // eslint-disable-line no-new
new UsersSelect(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/projects/merge_requests/show/index.js b/app/assets/javascripts/pages/projects/merge_requests/show/index.js
new file mode 100644
index 00000000000..c3463c266e3
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/merge_requests/show/index.js
@@ -0,0 +1,24 @@
+import MergeRequest from '~/merge_request';
+import ZenMode from '~/zen_mode';
+import initNotes from '~/init_notes';
+import initIssuableSidebar from '~/init_issuable_sidebar';
+import ShortcutsIssuable from '~/shortcuts_issuable';
+import Diff from '~/diff';
+import { handleLocationHash } from '~/lib/utils/common_utils';
+
+export default () => {
+ new Diff(); // eslint-disable-line no-new
+ new ZenMode(); // eslint-disable-line no-new
+
+ initIssuableSidebar(); // eslint-disable-line no-new
+ initNotes(); // eslint-disable-line no-new
+
+ const mrShowNode = document.querySelector('.merge-request');
+
+ window.mergeRequest = new MergeRequest({
+ action: mrShowNode.dataset.mrAction,
+ });
+
+ new ShortcutsIssuable(true); // eslint-disable-line no-new
+ handleLocationHash();
+};
diff --git a/app/assets/javascripts/pages/projects/milestones/edit/index.js b/app/assets/javascripts/pages/projects/milestones/edit/index.js
index 10e3979a36e..9a4ebf9890d 100644
--- a/app/assets/javascripts/pages/projects/milestones/edit/index.js
+++ b/app/assets/javascripts/pages/projects/milestones/edit/index.js
@@ -1,3 +1,3 @@
import initForm from '../../../../shared/milestones/form';
-export default () => initForm();
+document.addEventListener('DOMContentLoaded', () => initForm());
diff --git a/app/assets/javascripts/pages/projects/milestones/index/index.js b/app/assets/javascripts/pages/projects/milestones/index/index.js
index 8fb4d83d8a3..38789365a67 100644
--- a/app/assets/javascripts/pages/projects/milestones/index/index.js
+++ b/app/assets/javascripts/pages/projects/milestones/index/index.js
@@ -1,3 +1,3 @@
import milestones from '~/pages/milestones/shared';
-export default milestones;
+document.addEventListener('DOMContentLoaded', milestones);
diff --git a/app/assets/javascripts/pages/projects/milestones/new/index.js b/app/assets/javascripts/pages/projects/milestones/new/index.js
index 10e3979a36e..9a4ebf9890d 100644
--- a/app/assets/javascripts/pages/projects/milestones/new/index.js
+++ b/app/assets/javascripts/pages/projects/milestones/new/index.js
@@ -1,3 +1,3 @@
import initForm from '../../../../shared/milestones/form';
-export default () => initForm();
+document.addEventListener('DOMContentLoaded', () => initForm());
diff --git a/app/assets/javascripts/pages/projects/milestones/show/index.js b/app/assets/javascripts/pages/projects/milestones/show/index.js
index 35b5c9c2ced..84a52421598 100644
--- a/app/assets/javascripts/pages/projects/milestones/show/index.js
+++ b/app/assets/javascripts/pages/projects/milestones/show/index.js
@@ -1,7 +1,7 @@
import initMilestonesShow from '~/pages/milestones/shared/init_milestones_show';
import milestones from '~/pages/milestones/shared';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
initMilestonesShow();
milestones();
-};
+});
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/create/index.js b/app/assets/javascripts/pages/projects/pipeline_schedules/create/index.js
new file mode 100644
index 00000000000..d65be6bc69e
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/create/index.js
@@ -0,0 +1,3 @@
+import initForm from '../shared/init_form';
+
+document.addEventListener('DOMContentLoaded', initForm);
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/edit/index.js b/app/assets/javascripts/pages/projects/pipeline_schedules/edit/index.js
new file mode 100644
index 00000000000..d65be6bc69e
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/edit/index.js
@@ -0,0 +1,3 @@
+import initForm from '../shared/init_form';
+
+document.addEventListener('DOMContentLoaded', initForm);
diff --git a/app/assets/javascripts/pipeline_schedules/pipeline_schedules_index_bundle.js b/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js
index a6c945e22b0..544360dcd51 100644
--- a/app/assets/javascripts/pipeline_schedules/pipeline_schedules_index_bundle.js
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import PipelineSchedulesCallout from './components/pipeline_schedules_callout.vue';
+import PipelineSchedulesCallout from '../shared/components/pipeline_schedules_callout.vue';
document.addEventListener('DOMContentLoaded', () => new Vue({
el: '#pipeline-schedules-callout',
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/new/index.js b/app/assets/javascripts/pages/projects/pipeline_schedules/new/index.js
new file mode 100644
index 00000000000..d65be6bc69e
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/new/index.js
@@ -0,0 +1,3 @@
+import initForm from '../shared/init_form';
+
+document.addEventListener('DOMContentLoaded', initForm);
diff --git a/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
index 2d18fa2044b..2d18fa2044b 100644
--- a/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
diff --git a/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.vue b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue
index aa04a0ac47a..77508e62cef 100644
--- a/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.vue
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue
@@ -1,7 +1,7 @@
<script>
import Vue from 'vue';
import Cookies from 'js-cookie';
- import Translate from '../../vue_shared/translate';
+ import Translate from '../../../../../vue_shared/translate';
import illustrationSvg from '../icons/intro_illustration.svg';
Vue.use(Translate);
diff --git a/app/assets/javascripts/pipeline_schedules/components/target_branch_dropdown.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js
index 0c3926d76b5..0c3926d76b5 100644
--- a/app/assets/javascripts/pipeline_schedules/components/target_branch_dropdown.js
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js
diff --git a/app/assets/javascripts/pipeline_schedules/components/timezone_dropdown.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js
index 95ed9c7dc21..95ed9c7dc21 100644
--- a/app/assets/javascripts/pipeline_schedules/components/timezone_dropdown.js
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js
diff --git a/app/assets/javascripts/pipeline_schedules/icons/intro_illustration.svg b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/icons/intro_illustration.svg
index 26d1ff97b3e..26d1ff97b3e 100644
--- a/app/assets/javascripts/pipeline_schedules/icons/intro_illustration.svg
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/icons/intro_illustration.svg
diff --git a/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js
index 0b1a81bae13..cfd30d6053f 100644
--- a/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js
@@ -1,10 +1,10 @@
import Vue from 'vue';
-import Translate from '../vue_shared/translate';
-import GlFieldErrors from '../gl_field_errors';
+import Translate from '../../../../vue_shared/translate';
+import GlFieldErrors from '../../../../gl_field_errors';
import intervalPatternInput from './components/interval_pattern_input.vue';
import TimezoneDropdown from './components/timezone_dropdown';
import TargetBranchDropdown from './components/target_branch_dropdown';
-import setupNativeFormVariableList from '../ci_variable_list/native_form_variable_list';
+import setupNativeFormVariableList from '../../../../ci_variable_list/native_form_variable_list';
Vue.use(Translate);
@@ -27,7 +27,7 @@ function initIntervalPatternInput() {
});
}
-document.addEventListener('DOMContentLoaded', () => {
+export default () => {
/* Most of the form is written in haml, but for fields with more complex behaviors,
* you should mount individual Vue components here. If at some point components need
* to share state, it may make sense to refactor the whole form to Vue */
@@ -46,4 +46,4 @@ document.addEventListener('DOMContentLoaded', () => {
container: $('.js-ci-variable-list-section'),
formField: 'schedule',
});
-});
+};
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/update/index.js b/app/assets/javascripts/pages/projects/pipeline_schedules/update/index.js
new file mode 100644
index 00000000000..d65be6bc69e
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/update/index.js
@@ -0,0 +1,3 @@
+import initForm from '../shared/init_form';
+
+document.addEventListener('DOMContentLoaded', initForm);
diff --git a/app/assets/javascripts/pages/projects/pipelines/charts/index.js b/app/assets/javascripts/pages/projects/pipelines/charts/index.js
new file mode 100644
index 00000000000..bb92f4e1459
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/pipelines/charts/index.js
@@ -0,0 +1,56 @@
+import Chart from 'chart.js';
+
+const options = {
+ scaleOverlay: true,
+ responsive: true,
+ maintainAspectRatio: false,
+};
+
+const buildChart = (chartScope) => {
+ const data = {
+ labels: chartScope.labels,
+ datasets: [{
+ fillColor: '#707070',
+ strokeColor: '#707070',
+ pointColor: '#707070',
+ pointStrokeColor: '#EEE',
+ data: chartScope.totalValues,
+ },
+ {
+ fillColor: '#1aaa55',
+ strokeColor: '#1aaa55',
+ pointColor: '#1aaa55',
+ pointStrokeColor: '#fff',
+ data: chartScope.successValues,
+ },
+ ],
+ };
+ const ctx = $(`#${chartScope.scope}Chart`).get(0).getContext('2d');
+
+ new Chart(ctx).Line(data, options);
+};
+
+document.addEventListener('DOMContentLoaded', () => {
+ const chartTimesData = JSON.parse(document.getElementById('pipelinesTimesChartsData').innerHTML);
+ const chartsData = JSON.parse(document.getElementById('pipelinesChartsData').innerHTML);
+ const data = {
+ labels: chartTimesData.labels,
+ datasets: [{
+ fillColor: 'rgba(220,220,220,0.5)',
+ strokeColor: 'rgba(220,220,220,1)',
+ barStrokeWidth: 1,
+ barValueSpacing: 1,
+ barDatasetSpacing: 1,
+ data: chartTimesData.values,
+ }],
+ };
+
+ if (window.innerWidth < 768) {
+ // Scale fonts if window width lower than 768px (iPad portrait)
+ options.scaleFontSize = 8;
+ }
+
+ new Chart($('#build_timesChart').get(0).getContext('2d')).Bar(data, options);
+
+ chartsData.forEach(scope => buildChart(scope));
+});
diff --git a/app/assets/javascripts/pages/projects/services/edit/index.js b/app/assets/javascripts/pages/projects/services/edit/index.js
new file mode 100644
index 00000000000..5c73171e62e
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/services/edit/index.js
@@ -0,0 +1,13 @@
+import IntegrationSettingsForm from '~/integrations/integration_settings_form';
+import PrometheusMetrics from '~/prometheus_metrics/prometheus_metrics';
+
+export default () => {
+ const prometheusSettingsWrapper = document.querySelector('.js-prometheus-metrics-monitoring');
+ const integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form');
+ integrationSettingsForm.init();
+
+ if (prometheusSettingsWrapper) {
+ const prometheusMetrics = new PrometheusMetrics('.js-prometheus-metrics-monitoring');
+ prometheusMetrics.loadActiveMetrics();
+ }
+};
diff --git a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
index 18dc1dc03a5..a563d0f9961 100644
--- a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
+++ b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
@@ -1,9 +1,11 @@
import initSettingsPanels from '~/settings_panels';
import SecretValues from '~/behaviors/secret_values';
+import AjaxVariableList from '~/ci_variable_list/ajax_variable_list';
export default function () {
// Initialize expandable settings panels
initSettingsPanels();
+
const runnerToken = document.querySelector('.js-secret-runner-token');
if (runnerToken) {
const runnerTokenSecretValue = new SecretValues({
@@ -12,11 +14,12 @@ export default function () {
runnerTokenSecretValue.init();
}
- const secretVariableTable = document.querySelector('.js-secret-variable-table');
- if (secretVariableTable) {
- const secretVariableTableValues = new SecretValues({
- container: secretVariableTable,
- });
- secretVariableTableValues.init();
- }
+ const variableListEl = document.querySelector('.js-ci-variable-list-section');
+ // eslint-disable-next-line no-new
+ new AjaxVariableList({
+ container: variableListEl,
+ saveButton: variableListEl.querySelector('.js-secret-variables-save-button'),
+ errorBox: variableListEl.querySelector('.js-ci-variable-error-box'),
+ saveEndpoint: variableListEl.dataset.saveEndpoint,
+ });
}
diff --git a/app/assets/javascripts/pages/projects/tree/show/index.js b/app/assets/javascripts/pages/projects/tree/show/index.js
index c4b3356e478..cba57058380 100644
--- a/app/assets/javascripts/pages/projects/tree/show/index.js
+++ b/app/assets/javascripts/pages/projects/tree/show/index.js
@@ -14,7 +14,7 @@ export default () => {
$('#tree-slider').waitForImages(() =>
ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath));
- const commitPipelineStatusEl = document.getElementById('commit-pipeline-status');
+ const commitPipelineStatusEl = document.querySelector('.js-commit-pipeline-status');
const statusLink = document.querySelector('.commit-actions .ci-status-link');
if (statusLink != null) {
statusLink.remove();
diff --git a/app/assets/javascripts/pages/search/init_filtered_search.js b/app/assets/javascripts/pages/search/init_filtered_search.js
index 44853636aea..250f9d992ab 100644
--- a/app/assets/javascripts/pages/search/init_filtered_search.js
+++ b/app/assets/javascripts/pages/search/init_filtered_search.js
@@ -1,7 +1,7 @@
-export default (page) => {
+export default ({ page }) => {
const filteredSearchEnabled = gl.FilteredSearchManager && document.querySelector('.filtered-search');
if (filteredSearchEnabled) {
- const filteredSearchManager = new gl.FilteredSearchManager(page);
+ const filteredSearchManager = new gl.FilteredSearchManager({ page });
filteredSearchManager.setup();
}
};
diff --git a/app/assets/javascripts/users/activity_calendar.js b/app/assets/javascripts/pages/users/activity_calendar.js
index 57306322aa4..57306322aa4 100644
--- a/app/assets/javascripts/users/activity_calendar.js
+++ b/app/assets/javascripts/pages/users/activity_calendar.js
diff --git a/app/assets/javascripts/users/index.js b/app/assets/javascripts/pages/users/index.js
index 9fd8452a2b6..899dcd42e37 100644
--- a/app/assets/javascripts/users/index.js
+++ b/app/assets/javascripts/pages/users/index.js
@@ -1,3 +1,4 @@
+import UserCallout from '~/user_callout';
import Cookies from 'js-cookie';
import UserTabs from './user_tabs';
@@ -22,4 +23,5 @@ document.addEventListener('DOMContentLoaded', () => {
const page = $('body').attr('data-page');
const action = page.split(':')[1];
initUserProfile(action);
+ new UserCallout(); // eslint-disable-line no-new
});
diff --git a/app/assets/javascripts/pages/users/show/index.js b/app/assets/javascripts/pages/users/show/index.js
deleted file mode 100644
index f18f98b4e9a..00000000000
--- a/app/assets/javascripts/pages/users/show/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import UserCallout from '~/user_callout';
-
-export default () => new UserCallout();
diff --git a/app/assets/javascripts/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js
index e13b9839a20..c1217623467 100644
--- a/app/assets/javascripts/users/user_tabs.js
+++ b/app/assets/javascripts/pages/users/user_tabs.js
@@ -1,9 +1,9 @@
-import axios from '../lib/utils/axios_utils';
-import Activities from '../activities';
+import axios from '~/lib/utils/axios_utils';
+import Activities from '~/activities';
+import { localTimeAgo } from '~/lib/utils/datetime_utility';
+import { __ } from '~/locale';
+import flash from '~/flash';
import ActivityCalendar from './activity_calendar';
-import { localTimeAgo } from '../lib/utils/datetime_utility';
-import { __ } from '../locale';
-import flash from '../flash';
/**
* UserTabs
diff --git a/app/assets/javascripts/pipelines/components/async_button.vue b/app/assets/javascripts/pipelines/components/async_button.vue
index 77553ca67cc..a5f22c4ec80 100644
--- a/app/assets/javascripts/pipelines/components/async_button.vue
+++ b/app/assets/javascripts/pipelines/components/async_button.vue
@@ -31,10 +31,9 @@
type: String,
required: true,
},
- confirmActionMessage: {
- type: String,
- required: false,
- default: '',
+ id: {
+ type: Number,
+ required: true,
},
},
data() {
@@ -49,11 +48,10 @@
},
methods: {
onClick() {
- if (this.confirmActionMessage !== '' && confirm(this.confirmActionMessage)) {
- this.makeRequest();
- } else if (this.confirmActionMessage === '') {
- this.makeRequest();
- }
+ eventHub.$emit('actionConfirmationModal', {
+ id: this.id,
+ callback: this.makeRequest,
+ });
},
makeRequest() {
this.isLoading = true;
diff --git a/app/assets/javascripts/pipelines/components/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_table.vue
index 6681b89e629..62fe479fdf4 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_table.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_table.vue
@@ -1,5 +1,7 @@
<script>
import pipelinesTableRowComponent from './pipelines_table_row.vue';
+ import stopConfirmationModal from './stop_confirmation_modal.vue';
+ import retryConfirmationModal from './retry_confirmation_modal.vue';
/**
* Pipelines Table Component.
@@ -9,6 +11,8 @@
export default {
components: {
pipelinesTableRowComponent,
+ stopConfirmationModal,
+ retryConfirmationModal,
},
props: {
pipelines: {
@@ -70,5 +74,7 @@
:auto-devops-help-path="autoDevopsHelpPath"
:view-type="viewType"
/>
+ <stop-confirmation-modal />
+ <retry-confirmation-modal />
</div>
</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
index d0e4cf7ff40..0e3a10ed7f4 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
@@ -305,6 +305,9 @@
css-class="js-pipelines-retry-button btn-default btn-retry"
title="Retry"
icon="repeat"
+ :id="pipeline.id"
+ data-toggle="modal"
+ data-target="#retry-confirmation-modal"
/>
<async-button-component
@@ -313,7 +316,9 @@
css-class="js-pipelines-cancel-button btn-remove"
title="Cancel"
icon="close"
- confirm-action-message="Are you sure you want to cancel this pipeline?"
+ :id="pipeline.id"
+ data-toggle="modal"
+ data-target="#stop-confirmation-modal"
/>
</div>
</div>
diff --git a/app/assets/javascripts/pipelines/components/retry_confirmation_modal.vue b/app/assets/javascripts/pipelines/components/retry_confirmation_modal.vue
new file mode 100644
index 00000000000..e2ac08d67bc
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/retry_confirmation_modal.vue
@@ -0,0 +1,65 @@
+<script>
+ import modal from '~/vue_shared/components/modal.vue';
+ import { s__, sprintf } from '~/locale';
+ import eventHub from '../event_hub';
+
+ export default {
+ components: {
+ modal,
+ },
+ data() {
+ return {
+ id: '',
+ callback: () => {},
+ };
+ },
+ computed: {
+ title() {
+ return sprintf(s__('Pipeline|Retry pipeline #%{id}?'), {
+ id: `'${this.id}'`,
+ }, false);
+ },
+ text() {
+ return sprintf(s__('Pipeline|You’re about to retry pipeline %{id}.'), {
+ id: `<strong>#${this.id}</strong>`,
+ }, false);
+ },
+ primaryButtonLabel() {
+ return s__('Pipeline|Retry pipeline');
+ },
+ },
+ created() {
+ eventHub.$on('actionConfirmationModal', this.updateModal);
+ },
+ beforeDestroy() {
+ eventHub.$off('actionConfirmationModal', this.updateModal);
+ },
+ methods: {
+ updateModal(action) {
+ this.id = action.id;
+ this.callback = action.callback;
+ },
+ onSubmit() {
+ this.callback();
+ },
+ },
+ };
+</script>
+
+<template>
+ <modal
+ id="retry-confirmation-modal"
+ :title="title"
+ :text="text"
+ kind="danger"
+ :primary-button-label="primaryButtonLabel"
+ @submit="onSubmit"
+ >
+ <template
+ slot="body"
+ slot-scope="props"
+ >
+ <p v-html="props.text"></p>
+ </template>
+ </modal>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/stop_confirmation_modal.vue b/app/assets/javascripts/pipelines/components/stop_confirmation_modal.vue
new file mode 100644
index 00000000000..d737d567787
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/stop_confirmation_modal.vue
@@ -0,0 +1,65 @@
+<script>
+ import modal from '~/vue_shared/components/modal.vue';
+ import { s__, sprintf } from '~/locale';
+ import eventHub from '../event_hub';
+
+ export default {
+ components: {
+ modal,
+ },
+ data() {
+ return {
+ id: '',
+ callback: () => {},
+ };
+ },
+ computed: {
+ title() {
+ return sprintf(s__('Pipeline|Stop pipeline #%{id}?'), {
+ id: `'${this.id}'`,
+ }, false);
+ },
+ text() {
+ return sprintf(s__('Pipeline|You’re about to stop pipeline %{id}.'), {
+ id: `<strong>#${this.id}</strong>`,
+ }, false);
+ },
+ primaryButtonLabel() {
+ return s__('Pipeline|Stop pipeline');
+ },
+ },
+ created() {
+ eventHub.$on('actionConfirmationModal', this.updateModal);
+ },
+ beforeDestroy() {
+ eventHub.$off('actionConfirmationModal', this.updateModal);
+ },
+ methods: {
+ updateModal(action) {
+ this.id = action.id;
+ this.callback = action.callback;
+ },
+ onSubmit() {
+ this.callback();
+ },
+ },
+ };
+</script>
+
+<template>
+ <modal
+ id="stop-confirmation-modal"
+ :title="title"
+ :text="text"
+ kind="danger"
+ :primary-button-label="primaryButtonLabel"
+ @submit="onSubmit"
+ >
+ <template
+ slot="body"
+ slot-scope="props"
+ >
+ <p v-html="props.text"></p>
+ </template>
+ </modal>
+</template>
diff --git a/app/assets/javascripts/pipelines/pipelines_charts.js b/app/assets/javascripts/pipelines/pipelines_charts.js
deleted file mode 100644
index 2fce1945906..00000000000
--- a/app/assets/javascripts/pipelines/pipelines_charts.js
+++ /dev/null
@@ -1,38 +0,0 @@
-import Chart from 'chart.js';
-
-document.addEventListener('DOMContentLoaded', () => {
- const chartData = JSON.parse(document.getElementById('pipelinesChartsData').innerHTML);
- const buildChart = (chartScope) => {
- const data = {
- labels: chartScope.labels,
- datasets: [{
- fillColor: '#707070',
- strokeColor: '#707070',
- pointColor: '#707070',
- pointStrokeColor: '#EEE',
- data: chartScope.totalValues,
- },
- {
- fillColor: '#1aaa55',
- strokeColor: '#1aaa55',
- pointColor: '#1aaa55',
- pointStrokeColor: '#fff',
- data: chartScope.successValues,
- },
- ],
- };
- const ctx = $(`#${chartScope.scope}Chart`).get(0).getContext('2d');
- const options = {
- scaleOverlay: true,
- responsive: true,
- maintainAspectRatio: false,
- };
- if (window.innerWidth < 768) {
- // Scale fonts if window width lower than 768px (iPad portrait)
- options.scaleFontSize = 8;
- }
- new Chart(ctx).Line(data, options);
- };
-
- chartData.forEach(scope => buildChart(scope));
-});
diff --git a/app/assets/javascripts/pipelines/pipelines_times.js b/app/assets/javascripts/pipelines/pipelines_times.js
deleted file mode 100644
index 14a0eca77e9..00000000000
--- a/app/assets/javascripts/pipelines/pipelines_times.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import Chart from 'chart.js';
-
-document.addEventListener('DOMContentLoaded', () => {
- const chartData = JSON.parse(document.getElementById('pipelinesTimesChartsData').innerHTML);
- const data = {
- labels: chartData.labels,
- datasets: [{
- fillColor: 'rgba(220,220,220,0.5)',
- strokeColor: 'rgba(220,220,220,1)',
- barStrokeWidth: 1,
- barValueSpacing: 1,
- barDatasetSpacing: 1,
- data: chartData.values,
- }],
- };
- const ctx = $('#build_timesChart').get(0).getContext('2d');
- const options = {
- scaleOverlay: true,
- responsive: true,
- maintainAspectRatio: false,
- };
- if (window.innerWidth < 768) {
- // Scale fonts if window width lower than 768px (iPad portrait)
- options.scaleFontSize = 8;
- }
- new Chart(ctx).Bar(data, options);
-});
diff --git a/app/assets/javascripts/profile/profile.js b/app/assets/javascripts/profile/profile.js
index ba4ac850346..930f0fb381e 100644
--- a/app/assets/javascripts/profile/profile.js
+++ b/app/assets/javascripts/profile/profile.js
@@ -1,7 +1,9 @@
/* eslint-disable comma-dangle, no-unused-vars, class-methods-use-this, quotes, consistent-return, func-names, prefer-arrow-callback, space-before-function-paren, max-len */
import Cookies from 'js-cookie';
-import Flash from '../flash';
-import { getPagePath } from '../lib/utils/common_utils';
+import { getPagePath } from '~/lib/utils/common_utils';
+import axios from '~/lib/utils/axios_utils';
+import { __ } from '~/locale';
+import flash from '../flash';
((global) => {
class Profile {
@@ -31,9 +33,6 @@ import { getPagePath } from '../lib/utils/common_utils';
$('input[name="user[multi_file]"]').on('change', this.setNewRepoCookie);
$('#user_notification_email').on('change', this.submitForm);
$('#user_notified_of_own_activity').on('change', this.submitForm);
- $('.update-username').on('ajax:before', this.beforeUpdateUsername);
- $('.update-username').on('ajax:complete', this.afterUpdateUsername);
- $('.update-notifications').on('ajax:success', this.onUpdateNotifs);
this.form.on('submit', this.onSubmitForm);
}
@@ -46,21 +45,6 @@ import { getPagePath } from '../lib/utils/common_utils';
return this.saveForm();
}
- beforeUpdateUsername() {
- $('.loading-username', this).removeClass('hidden');
- }
-
- afterUpdateUsername() {
- $('.loading-username', this).addClass('hidden');
- $('button[type=submit]', this).enable();
- }
-
- onUpdateNotifs(e, data) {
- return data.saved ?
- new Flash("Notification settings saved", "notice") :
- new Flash("Failed to save new settings", "alert");
- }
-
saveForm() {
const self = this;
const formData = new FormData(this.form[0]);
@@ -70,21 +54,18 @@ import { getPagePath } from '../lib/utils/common_utils';
formData.append('user[avatar]', avatarBlob, 'avatar.png');
}
- return $.ajax({
+ axios({
+ method: this.form.attr('method'),
url: this.form.attr('action'),
- type: this.form.attr('method'),
data: formData,
- dataType: "json",
- processData: false,
- contentType: false,
- success: response => new Flash(response.message, 'notice'),
- error: jqXHR => new Flash(jqXHR.responseJSON.message, 'alert'),
- complete: () => {
- window.scrollTo(0, 0);
- // Enable submit button after requests ends
- return self.form.find(':input[disabled]').enable();
- }
- });
+ })
+ .then(({ data }) => flash(data.message, 'notice'))
+ .then(() => {
+ window.scrollTo(0, 0);
+ // Enable submit button after requests ends
+ self.form.find(':input[disabled]').enable();
+ })
+ .catch(error => flash(error.message));
}
setNewRepoCookie() {
diff --git a/app/assets/javascripts/prometheus_metrics/index.js b/app/assets/javascripts/prometheus_metrics/index.js
deleted file mode 100644
index a0c43c5abe1..00000000000
--- a/app/assets/javascripts/prometheus_metrics/index.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import PrometheusMetrics from './prometheus_metrics';
-
-$(() => {
- const prometheusMetrics = new PrometheusMetrics('.js-prometheus-metrics-monitoring');
- prometheusMetrics.loadActiveMetrics();
-});
diff --git a/app/assets/javascripts/render_math.js b/app/assets/javascripts/render_math.js
index 73b6aafdd12..eabdb01b2a9 100644
--- a/app/assets/javascripts/render_math.js
+++ b/app/assets/javascripts/render_math.js
@@ -1,4 +1,5 @@
-/* global katex */
+import { __ } from './locale';
+import flash from './flash';
// Renders math using KaTeX in any element with the
// `js-render-math` class
@@ -8,15 +9,8 @@
// <code class="js-render-math"></div>
//
-import { __ } from './locale';
-import axios from './lib/utils/axios_utils';
-import flash from './flash';
-
-// Only load once
-let katexLoaded = false;
-
// Loop over all math elements and render math
-function renderWithKaTeX(elements) {
+function renderWithKaTeX(elements, katex) {
elements.each(function katexElementsLoop() {
const mathNode = $('<span></span>');
const $this = $(this);
@@ -34,30 +28,10 @@ function renderWithKaTeX(elements) {
export default function renderMath($els) {
if (!$els.length) return;
-
- if (katexLoaded) {
- renderWithKaTeX($els);
- } else {
- axios.get(gon.katex_css_url)
- .then(() => {
- const css = $('<link>', {
- rel: 'stylesheet',
- type: 'text/css',
- href: gon.katex_css_url,
- });
- css.appendTo('head');
- })
- .then(() => axios.get(gon.katex_js_url, {
- responseType: 'text',
- }))
- .then(({ data }) => {
- // Add katex js to our document
- $.globalEval(data);
- })
- .then(() => {
- katexLoaded = true;
- renderWithKaTeX($els); // Run KaTeX
- })
- .catch(() => flash(__('An error occurred while rendering KaTeX')));
- }
+ Promise.all([
+ import(/* webpackChunkName: 'katex' */ 'katex'),
+ import(/* webpackChunkName: 'katex' */ 'katex/dist/katex.css'),
+ ]).then(([katex]) => {
+ renderWithKaTeX($els, katex);
+ }).catch(() => flash(__('An error occurred while rendering KaTeX')));
}
diff --git a/app/assets/javascripts/render_mermaid.js b/app/assets/javascripts/render_mermaid.js
index 31c7a772cf4..d4f18955bd2 100644
--- a/app/assets/javascripts/render_mermaid.js
+++ b/app/assets/javascripts/render_mermaid.js
@@ -30,6 +30,9 @@ export default function renderMermaid($els) {
$els.each((i, el) => {
const source = el.textContent;
+ // Remove any extra spans added by the backend syntax highlighting.
+ Object.assign(el, { textContent: source });
+
mermaid.init(undefined, el, (id) => {
const svg = document.getElementById(id);
diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js
index 98b524f7e3f..8f4a8704c3b 100644
--- a/app/assets/javascripts/search_autocomplete.js
+++ b/app/assets/javascripts/search_autocomplete.js
@@ -1,4 +1,5 @@
/* eslint-disable no-return-assign, one-var, no-var, no-underscore-dangle, one-var-declaration-per-line, no-unused-vars, no-cond-assign, consistent-return, object-shorthand, prefer-arrow-callback, func-names, space-before-function-paren, prefer-template, quotes, class-methods-use-this, no-sequences, wrap-iife, no-lonely-if, no-else-return, no-param-reassign, vars-on-top, max-len */
+import axios from './lib/utils/axios_utils';
import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from './lib/utils/common_utils';
/**
@@ -146,23 +147,25 @@ export default class SearchAutocomplete {
this.loadingSuggestions = true;
- return $.get(this.autocompletePath, {
- project_id: this.projectId,
- project_ref: this.projectRef,
- term: term,
- }, (response) => {
- var firstCategory, i, lastCategory, len, suggestion;
+ return axios.get(this.autocompletePath, {
+ params: {
+ project_id: this.projectId,
+ project_ref: this.projectRef,
+ term: term,
+ },
+ }).then((response) => {
// Hide dropdown menu if no suggestions returns
- if (!response.length) {
+ if (!response.data.length) {
this.disableAutocomplete();
return;
}
const data = [];
// List results
- firstCategory = true;
- for (i = 0, len = response.length; i < len; i += 1) {
- suggestion = response[i];
+ let firstCategory = true;
+ let lastCategory;
+ for (let i = 0, len = response.data.length; i < len; i += 1) {
+ const suggestion = response.data[i];
// Add group header before list each group
if (lastCategory !== suggestion.category) {
if (!firstCategory) {
@@ -177,7 +180,7 @@ export default class SearchAutocomplete {
lastCategory = suggestion.category;
}
data.push({
- id: (suggestion.category.toLowerCase()) + "-" + suggestion.id,
+ id: `${suggestion.category.toLowerCase()}-${suggestion.id}`,
category: suggestion.category,
text: suggestion.label,
url: suggestion.url,
@@ -187,13 +190,17 @@ export default class SearchAutocomplete {
if (data.length) {
data.push('separator');
data.push({
- text: "Result name contains \"" + term + "\"",
- url: "/search?search=" + term + "&project_id=" + (this.projectInputEl.val()) + "&group_id=" + (this.groupInputEl.val()),
+ text: `Result name contains "${term}"`,
+ url: `/search?search=${term}&project_id=${this.projectInputEl.val()}&group_id=${this.groupInputEl.val()}`,
});
}
- return callback(data);
- })
- .always(() => { this.loadingSuggestions = false; });
+
+ callback(data);
+
+ this.loadingSuggestions = false;
+ }).catch(() => {
+ this.loadingSuggestions = false;
+ });
}
getCategoryContents() {
diff --git a/app/assets/javascripts/settings_panels.js b/app/assets/javascripts/settings_panels.js
index d34a21b37e1..d0e4f533d8a 100644
--- a/app/assets/javascripts/settings_panels.js
+++ b/app/assets/javascripts/settings_panels.js
@@ -42,7 +42,7 @@ export default function initSettingsPanels() {
if (location.hash) {
const $target = $(location.hash);
- if ($target.length && $target.hasClass('.settings')) {
+ if ($target.length && $target.hasClass('settings')) {
expandSection($target);
}
}
diff --git a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
index 02153fb86a5..8a86c409b62 100644
--- a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
+++ b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
@@ -2,6 +2,7 @@
import Flash from '../../../flash';
import editForm from './edit_form.vue';
import Icon from '../../../vue_shared/components/icon.vue';
+ import { __ } from '../../../locale';
export default {
components: {
@@ -40,8 +41,7 @@
this.service.update('issue', { confidential })
.then(() => location.reload())
.catch(() => {
- Flash(`Something went wrong trying to
- change the confidentiality of this issue`);
+ Flash(__('Something went wrong trying to change the confidentiality of this issue'));
});
},
},
@@ -58,7 +58,7 @@
/>
</div>
<div class="title hide-collapsed">
- Confidentiality
+ {{ __('Confidentiality') }}
<a
v-if="isEditable"
class="pull-right confidential-edit"
@@ -84,7 +84,7 @@
aria-hidden="true"
class="sidebar-item-icon inline"
/>
- Not confidential
+ {{ __('Not confidential') }}
</div>
<div
v-else
@@ -95,7 +95,7 @@
aria-hidden="true"
class="sidebar-item-icon inline is-active"
/>
- This issue is confidential
+ {{ __('This issue is confidential') }}
</div>
</div>
</div>
diff --git a/app/assets/javascripts/sidebar/components/confidential/edit_form.vue b/app/assets/javascripts/sidebar/components/confidential/edit_form.vue
index 6a81235a1a7..c569843b05f 100644
--- a/app/assets/javascripts/sidebar/components/confidential/edit_form.vue
+++ b/app/assets/javascripts/sidebar/components/confidential/edit_form.vue
@@ -1,5 +1,6 @@
<script>
import editFormButtons from './edit_form_buttons.vue';
+ import { s__ } from '../../../locale';
export default {
components: {
@@ -19,6 +20,14 @@
type: Function,
},
},
+ computed: {
+ confidentialityOnWarning() {
+ return s__('confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue.');
+ },
+ confidentialityOffWarning() {
+ return s__('confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue.');
+ },
+ },
};
</script>
@@ -26,15 +35,13 @@
<div class="dropdown open">
<div class="dropdown-menu sidebar-item-warning-message">
<div>
- <p v-if="!isConfidential">
- You are going to turn on the confidentiality. This means that only team members with
- <strong>at least Reporter access</strong>
- are able to see and leave comments on the issue.
+ <p
+ v-if="!isConfidential"
+ v-html="confidentialityOnWarning">
</p>
- <p v-else>
- You are going to turn off the confidentiality. This means
- <strong>everyone</strong>
- will be able to see and leave a comment on this issue.
+ <p
+ v-else
+ v-html="confidentialityOffWarning">
</p>
<edit-form-buttons
:is-confidential="isConfidential"
diff --git a/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue b/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue
index 7ed0619ee6b..49d5dfeea1a 100644
--- a/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue
+++ b/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue
@@ -32,7 +32,7 @@ export default {
class="btn btn-default append-right-10"
@click="toggleForm"
>
- Cancel
+ {{ __('Cancel') }}
</button>
<button
type="button"
diff --git a/app/assets/javascripts/sidebar/components/lock/edit_form.vue b/app/assets/javascripts/sidebar/components/lock/edit_form.vue
index e7a87636aa7..bc32e974bc3 100644
--- a/app/assets/javascripts/sidebar/components/lock/edit_form.vue
+++ b/app/assets/javascripts/sidebar/components/lock/edit_form.vue
@@ -1,6 +1,7 @@
<script>
import editFormButtons from './edit_form_buttons.vue';
import issuableMixin from '../../../vue_shared/mixins/issuable';
+ import { __, sprintf } from '../../../locale';
export default {
components: {
@@ -25,6 +26,14 @@
type: Function,
},
},
+ computed: {
+ lockWarning() {
+ return sprintf(__('Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment.'), { issuableDisplayName: this.issuableDisplayName });
+ },
+ unlockWarning() {
+ return sprintf(__('Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment.'), { issuableDisplayName: this.issuableDisplayName });
+ },
+ },
};
</script>
@@ -33,19 +42,14 @@
<div class="dropdown-menu sidebar-item-warning-message">
<p
class="text"
- v-if="isLocked">
- Unlock this {{ issuableDisplayName }}?
- <strong>Everyone</strong>
- will be able to comment.
+ v-if="isLocked"
+ v-html="unlockWarning">
</p>
<p
class="text"
- v-else>
- Lock this {{ issuableDisplayName }}?
- Only
- <strong>project members</strong>
- will be able to comment.
+ v-else
+ v-html="lockWarning">
</p>
<edit-form-buttons
diff --git a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
index 02876a6c175..9d22b9d77be 100644
--- a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
+++ b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
@@ -72,10 +72,10 @@
</div>
<div class="title hide-collapsed">
- Lock {{ issuableDisplayName }}
+ {{ sprintf(__('Lock %{issuableDisplayName}'), { issuableDisplayName: issuableDisplayName }) }}
<button
v-if="isEditable"
- class="pull-right lock-edit btn btn-blank"
+ class="pull-right lock-edit"
type="button"
@click.prevent="toggleForm"
>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.js b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.js
index fd0d4570d68..b5ebccd3795 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.js
+++ b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.js
@@ -68,7 +68,7 @@ export default {
<div class="compare-display-container">
<div class="compare-display pull-left">
<span class="compare-label">
- Spent
+ {{ s__('TimeTracking|Spent') }}
</span>
<span class="compare-value spent">
{{ timeSpentHumanReadable }}
@@ -76,7 +76,7 @@ export default {
</div>
<div class="compare-display estimated pull-right">
<span class="compare-label">
- Est
+ {{ s__('TimeTrackingEstimated|Est') }}
</span>
<span class="compare-value">
{{ timeEstimateHumanReadable }}
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.js b/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.js
index ad1b9179db0..2d324c71379 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.js
+++ b/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.js
@@ -9,7 +9,7 @@ export default {
template: `
<div class="time-tracking-estimate-only-pane">
<span class="bold">
- Estimated:
+ {{ s__('TimeTracking|Estimated:') }}
</span>
{{ timeEstimateHumanReadable }}
</div>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/help_state.js b/app/assets/javascripts/sidebar/components/time_tracking/help_state.js
index 142ad437509..19f74ad3c6d 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/help_state.js
+++ b/app/assets/javascripts/sidebar/components/time_tracking/help_state.js
@@ -1,3 +1,5 @@
+import { sprintf, s__ } from '../../../locale';
+
export default {
name: 'time-tracking-help-state',
props: {
@@ -10,33 +12,39 @@ export default {
href() {
return `${this.rootPath}help/workflow/time_tracking.md`;
},
+ estimateText() {
+ return sprintf(
+ s__('estimateCommand|%{slash_command} will update the estimated time with the latest command.'), {
+ slash_command: '<code>/estimate</code>',
+ }, false,
+ );
+ },
+ spendText() {
+ return sprintf(
+ s__('spendCommand|%{slash_command} will update the sum of the time spent.'), {
+ slash_command: '<code>/spend</code>',
+ }, false,
+ );
+ },
},
template: `
<div class="time-tracking-help-state">
<div class="time-tracking-info">
<h4>
- Track time with quick actions
+ {{ __('Track time with quick actions') }}
</h4>
<p>
- Quick actions can be used in the issues description and comment boxes.
+ {{ __('Quick actions can be used in the issues description and comment boxes.') }}
</p>
- <p>
- <code>
- /estimate
- </code>
- will update the estimated time with the latest command.
+ <p v-html="estimateText">
</p>
- <p>
- <code>
- /spend
- </code>
- will update the sum of the time spent.
+ <p v-html="spendText">
</p>
<a
class="btn btn-default learn-more-button"
:href="href"
>
- Learn more
+ {{ __('Learn more') }}
</a>
</div>
</div>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.js b/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.js
index d1dd1dcdd27..38da76c6771 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.js
+++ b/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.js
@@ -3,7 +3,7 @@ export default {
template: `
<div class="time-tracking-no-tracking-pane">
<span class="no-value">
- No estimate or time spent
+ {{ __('No estimate or time spent') }}
</span>
</div>
`,
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js b/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js
index d32fe4abc7d..782e4ba4fad 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js
+++ b/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js
@@ -2,7 +2,7 @@ import _ from 'underscore';
import '~/smart_interval';
-import timeTracker from './time_tracker';
+import IssuableTimeTracker from './time_tracker.vue';
import Store from '../../stores/sidebar_store';
import Mediator from '../../sidebar_mediator';
@@ -16,7 +16,7 @@ export default {
};
},
components: {
- 'issuable-time-tracker': timeTracker,
+ IssuableTimeTracker,
},
methods: {
listenForQuickActions() {
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.js b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
index ed0d71a4f79..230736a56b8 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.js
+++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
@@ -1,3 +1,4 @@
+<script>
import timeTrackingHelpState from './help_state';
import timeTrackingCollapsedState from './collapsed_state';
import timeTrackingSpentOnlyPane from './spent_only_pane';
@@ -8,7 +9,15 @@ import timeTrackingComparisonPane from './comparison_pane';
import eventHub from '../../event_hub';
export default {
- name: 'issuable-time-tracker',
+ name: 'IssuableTimeTracker',
+ components: {
+ 'time-tracking-collapsed-state': timeTrackingCollapsedState,
+ 'time-tracking-estimate-only-pane': timeTrackingEstimateOnlyPane,
+ 'time-tracking-spent-only-pane': timeTrackingSpentOnlyPane,
+ 'time-tracking-no-tracking-pane': timeTrackingNoTrackingPane,
+ 'time-tracking-comparison-pane': timeTrackingComparisonPane,
+ 'time-tracking-help-state': timeTrackingHelpState,
+ },
props: {
time_estimate: {
type: Number,
@@ -38,14 +47,6 @@ export default {
showHelp: false,
};
},
- components: {
- 'time-tracking-collapsed-state': timeTrackingCollapsedState,
- 'time-tracking-estimate-only-pane': timeTrackingEstimateOnlyPane,
- 'time-tracking-spent-only-pane': timeTrackingSpentOnlyPane,
- 'time-tracking-no-tracking-pane': timeTrackingNoTrackingPane,
- 'time-tracking-comparison-pane': timeTrackingComparisonPane,
- 'time-tracking-help-state': timeTrackingHelpState,
- },
computed: {
timeSpent() {
return this.time_spent;
@@ -81,6 +82,9 @@ export default {
return !!this.showHelp;
},
},
+ created() {
+ eventHub.$on('timeTracker:updateData', this.update);
+ },
methods: {
toggleHelpState(show) {
this.showHelp = show;
@@ -92,72 +96,73 @@ export default {
this.human_time_spent = data.human_time_spent;
},
},
- created() {
- eventHub.$on('timeTracker:updateData', this.update);
- },
- template: `
- <div
- class="time_tracker time-tracking-component-wrap"
- v-cloak
- >
- <time-tracking-collapsed-state
- :show-comparison-state="showComparisonState"
- :show-no-time-tracking-state="showNoTimeTrackingState"
- :show-help-state="showHelpState"
- :show-spent-only-state="showSpentOnlyState"
- :show-estimate-only-state="showEstimateOnlyState"
+};
+</script>
+
+<template>
+ <div
+ class="time_tracker time-tracking-component-wrap"
+ v-cloak
+ >
+ <time-tracking-collapsed-state
+ :show-comparison-state="showComparisonState"
+ :show-no-time-tracking-state="showNoTimeTrackingState"
+ :show-help-state="showHelpState"
+ :show-spent-only-state="showSpentOnlyState"
+ :show-estimate-only-state="showEstimateOnlyState"
+ :time-spent-human-readable="timeSpentHumanReadable"
+ :time-estimate-human-readable="timeEstimateHumanReadable"
+ />
+ <div class="title hide-collapsed">
+ {{ __('Time tracking') }}
+ <div
+ class="help-button pull-right"
+ v-if="!showHelpState"
+ @click="toggleHelpState(true)"
+ >
+ <i
+ class="fa fa-question-circle"
+ aria-hidden="true"
+ >
+ </i>
+ </div>
+ <div
+ class="close-help-button pull-right"
+ v-if="showHelpState"
+ @click="toggleHelpState(false)"
+ >
+ <i
+ class="fa fa-close"
+ aria-hidden="true"
+ >
+ </i>
+ </div>
+ </div>
+ <div class="time-tracking-content hide-collapsed">
+ <time-tracking-estimate-only-pane
+ v-if="showEstimateOnlyState"
+ :time-estimate-human-readable="timeEstimateHumanReadable"
+ />
+ <time-tracking-spent-only-pane
+ v-if="showSpentOnlyState"
+ :time-spent-human-readable="timeSpentHumanReadable"
+ />
+ <time-tracking-no-tracking-pane
+ v-if="showNoTimeTrackingState"
+ />
+ <time-tracking-comparison-pane
+ v-if="showComparisonState"
+ :time-estimate="timeEstimate"
+ :time-spent="timeSpent"
:time-spent-human-readable="timeSpentHumanReadable"
:time-estimate-human-readable="timeEstimateHumanReadable"
/>
- <div class="title hide-collapsed">
- Time tracking
- <div
- class="help-button pull-right"
- v-if="!showHelpState"
- @click="toggleHelpState(true)"
- >
- <i
- class="fa fa-question-circle"
- aria-hidden="true"
- />
- </div>
- <div
- class="close-help-button pull-right"
+ <transition name="help-state-toggle">
+ <time-tracking-help-state
v-if="showHelpState"
- @click="toggleHelpState(false)"
- >
- <i
- class="fa fa-close"
- aria-hidden="true"
- />
- </div>
- </div>
- <div class="time-tracking-content hide-collapsed">
- <time-tracking-estimate-only-pane
- v-if="showEstimateOnlyState"
- :time-estimate-human-readable="timeEstimateHumanReadable"
+ :root-path="rootPath"
/>
- <time-tracking-spent-only-pane
- v-if="showSpentOnlyState"
- :time-spent-human-readable="timeSpentHumanReadable"
- />
- <time-tracking-no-tracking-pane
- v-if="showNoTimeTrackingState"
- />
- <time-tracking-comparison-pane
- v-if="showComparisonState"
- :time-estimate="timeEstimate"
- :time-spent="timeSpent"
- :time-spent-human-readable="timeSpentHumanReadable"
- :time-estimate-human-readable="timeEstimateHumanReadable"
- />
- <transition name="help-state-toggle">
- <time-tracking-help-state
- v-if="showHelpState"
- :rootPath="rootPath"
- />
- </transition>
- </div>
+ </transition>
</div>
- `,
-};
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue
index 40c3cb500bb..ebaf2b972eb 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue
@@ -44,7 +44,10 @@
type="button"
class="btn btn-xs btn-default"
>
- <loading-icon v-if="isRefreshing" />
+ <loading-icon
+ v-if="isRefreshing"
+ :inline="true"
+ />
{{ s__("mrWidget|Refresh") }}
</button>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js
deleted file mode 100644
index 7733fb74afe..00000000000
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import statusIcon from '../mr_widget_status_icon.vue';
-import tooltip from '../../../vue_shared/directives/tooltip';
-import mrWidgetMergeHelp from '../../components/mr_widget_merge_help.vue';
-
-export default {
- name: 'MRWidgetMissingBranch',
- props: {
- mr: { type: Object, required: true },
- },
- directives: {
- tooltip,
- },
- components: {
- 'mr-widget-merge-help': mrWidgetMergeHelp,
- statusIcon,
- },
- computed: {
- missingBranchName() {
- return this.mr.sourceBranchRemoved ? 'source' : 'target';
- },
- message() {
- return `If the ${this.missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line`;
- },
- },
- template: `
- <div class="mr-widget-body media">
- <status-icon status="warning" :show-disabled-button="true" />
- <div class="media-body space-children">
- <span class="bold js-branch-text">
- <span class="capitalize">
- {{missingBranchName}}
- </span> branch does not exist.
- Please restore it or use a different {{missingBranchName}} branch
- <i
- v-tooltip
- class="fa fa-question-circle"
- :title="message"
- :aria-label="message"></i>
- </span>
- </div>
- </div>
- `,
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue
new file mode 100644
index 00000000000..718c0e4b3c6
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue
@@ -0,0 +1,62 @@
+<script>
+ import { sprintf, s__ } from '~/locale';
+ import tooltip from '~/vue_shared/directives/tooltip';
+ import statusIcon from '../mr_widget_status_icon.vue';
+ import mrWidgetMergeHelp from '../../components/mr_widget_merge_help.vue';
+
+ export default {
+ name: 'MRWidgetMissingBranch',
+ directives: {
+ tooltip,
+ },
+ components: {
+ mrWidgetMergeHelp,
+ statusIcon,
+ },
+ props: {
+ mr: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ missingBranchName() {
+ return this.mr.sourceBranchRemoved ? 'source' : 'target';
+ },
+ missingBranchNameMessage() {
+ return sprintf(s__('mrWidget| Please restore it or use a different %{missingBranchName} branch'), {
+ missingBranchName: this.missingBranchName,
+ });
+ },
+ message() {
+ return sprintf(s__('mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line'), {
+ missingBranchName: this.missingBranchName,
+ });
+ },
+ },
+ };
+</script>
+<template>
+ <div class="mr-widget-body media">
+ <status-icon
+ status="warning"
+ :show-disabled-button="true"
+ />
+
+ <div class="media-body space-children">
+ <span class="bold js-branch-text">
+ <span class="capitalize">
+ {{ missingBranchName }}
+ </span> {{ s__("mrWidget|branch does not exist.") }}
+ {{ missingBranchNameMessage }}
+ <i
+ v-tooltip
+ class="fa fa-question-circle"
+ :title="message"
+ :aria-label="message"
+ >
+ </i>
+ </span>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.js
deleted file mode 100644
index cea3d97fa88..00000000000
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import statusIcon from '../mr_widget_status_icon.vue';
-
-export default {
- name: 'MRWidgetNotAllowed',
- components: {
- statusIcon,
- },
- template: `
- <div class="mr-widget-body media">
- <status-icon status="success" :show-disabled-button="true" />
- <div class="media-body space-children">
- <span class="bold">
- Ready to be merged automatically.
- Ask someone with write access to this repository to merge this request
- </span>
- </div>
- </div>
- `,
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue
new file mode 100644
index 00000000000..e4af50b09f8
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue
@@ -0,0 +1,25 @@
+<script>
+ import StatusIcon from '../mr_widget_status_icon.vue';
+
+ export default {
+ name: 'MRWidgetNotAllowed',
+ components: {
+ StatusIcon,
+ },
+ };
+</script>
+
+<template>
+ <div class="mr-widget-body media">
+ <status-icon
+ status="success"
+ :show-disabled-button="true"
+ />
+ <div class="media-body space-children">
+ <span class="bold">
+ {{ s__(`mrWidget|Ready to be merged automatically.
+Ask someone with write access to this repository to merge this request`) }}
+ </span>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js
deleted file mode 100644
index e66ce071ab4..00000000000
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import statusIcon from '../mr_widget_status_icon.vue';
-
-export default {
- name: 'MRWidgetPipelineBlocked',
- components: {
- statusIcon,
- },
- template: `
- <div class="mr-widget-body media">
- <status-icon status="warning" :show-disabled-button="true" />
- <div class="media-body space-children">
- <span class="bold">
- Pipeline blocked. The pipeline for this merge request requires a manual action to proceed
- </span>
- </div>
- </div>
- `,
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue
new file mode 100644
index 00000000000..6d7cc03f7ad
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue
@@ -0,0 +1,24 @@
+<script>
+ import StatusIcon from '../mr_widget_status_icon.vue';
+
+ export default {
+ name: 'MRWidgetPipelineBlocked',
+ components: {
+ StatusIcon,
+ },
+ };
+</script>
+<template>
+ <div class="mr-widget-body media">
+ <status-icon
+ status="warning"
+ :show-disabled-button="true"
+ />
+ <div class="media-body space-children">
+ <span class="bold">
+ {{ s__(`mrWidget|Pipeline blocked.
+The pipeline for this merge request requires a manual action to proceed`) }}
+ </span>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/dependencies.js b/app/assets/javascripts/vue_merge_request_widget/dependencies.js
index 7ca15537719..edb3baa39e4 100644
--- a/app/assets/javascripts/vue_merge_request_widget/dependencies.js
+++ b/app/assets/javascripts/vue_merge_request_widget/dependencies.js
@@ -24,12 +24,12 @@ export { default as WipState } from './components/states/mr_widget_wip';
export { default as ArchivedState } from './components/states/mr_widget_archived.vue';
export { default as ConflictsState } from './components/states/mr_widget_conflicts.vue';
export { default as NothingToMergeState } from './components/states/mr_widget_nothing_to_merge';
-export { default as MissingBranchState } from './components/states/mr_widget_missing_branch';
-export { default as NotAllowedState } from './components/states/mr_widget_not_allowed';
+export { default as MissingBranchState } from './components/states/mr_widget_missing_branch.vue';
+export { default as NotAllowedState } from './components/states/mr_widget_not_allowed.vue';
export { default as ReadyToMergeState } from './components/states/mr_widget_ready_to_merge';
export { default as SHAMismatchState } from './components/states/mr_widget_sha_mismatch';
export { default as UnresolvedDiscussionsState } from './components/states/mr_widget_unresolved_discussions';
-export { default as PipelineBlockedState } from './components/states/mr_widget_pipeline_blocked';
+export { default as PipelineBlockedState } from './components/states/mr_widget_pipeline_blocked.vue';
export { default as PipelineFailedState } from './components/states/mr_widget_pipeline_failed';
export { default as MergeWhenPipelineSucceedsState } from './components/states/mr_widget_merge_when_pipeline_succeeds.vue';
export { default as RebaseState } from './components/states/mr_widget_rebase.vue';
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
index edf67fcd0a7..d8f0442ef9d 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
@@ -152,6 +152,7 @@ export default {
},
handleNotification(data) {
if (data.ci_status === this.mr.ciStatus) return;
+ if (!data.pipeline) return;
const label = data.pipeline.details.status.label;
const title = `Pipeline ${label}`;
diff --git a/app/assets/javascripts/vue_shared/components/confirmation_input.vue b/app/assets/javascripts/vue_shared/components/confirmation_input.vue
deleted file mode 100644
index 1aa03ea6317..00000000000
--- a/app/assets/javascripts/vue_shared/components/confirmation_input.vue
+++ /dev/null
@@ -1,62 +0,0 @@
-<script>
- import _ from 'underscore';
- import { __, sprintf } from '~/locale';
-
- export default {
- props: {
- inputId: {
- type: String,
- required: true,
- },
- confirmationKey: {
- type: String,
- required: true,
- },
- confirmationValue: {
- type: String,
- required: true,
- },
- shouldEscapeConfirmationValue: {
- type: Boolean,
- required: false,
- default: true,
- },
- },
- computed: {
- inputLabel() {
- let value = this.confirmationValue;
- if (this.shouldEscapeConfirmationValue) {
- value = _.escape(value);
- }
-
- return sprintf(
- __('Type %{value} to confirm:'),
- { value: `<code>${value}</code>` },
- false,
- );
- },
- },
- methods: {
- hasCorrectValue() {
- return this.$refs.enteredValue.value === this.confirmationValue;
- },
- },
- };
-</script>
-
-<template>
- <div>
- <label
- v-html="inputLabel"
- :for="inputId"
- >
- </label>
- <input
- :id="inputId"
- :name="confirmationKey"
- type="text"
- ref="enteredValue"
- class="form-control"
- />
- </div>
-</template>
diff --git a/app/assets/javascripts/vue_shared/components/gl_modal.vue b/app/assets/javascripts/vue_shared/components/gl_modal.vue
new file mode 100644
index 00000000000..67c9181c7b1
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/gl_modal.vue
@@ -0,0 +1,106 @@
+<script>
+ const buttonVariants = [
+ 'danger',
+ 'primary',
+ 'success',
+ 'warning',
+ ];
+
+ export default {
+ name: 'GlModal',
+
+ props: {
+ id: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ headerTitleText: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ footerPrimaryButtonVariant: {
+ type: String,
+ required: false,
+ default: 'primary',
+ validator: value => buttonVariants.indexOf(value) !== -1,
+ },
+ footerPrimaryButtonText: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+
+ methods: {
+ emitCancel(event) {
+ this.$emit('cancel', event);
+ },
+ emitSubmit(event) {
+ this.$emit('submit', event);
+ },
+ },
+ };
+</script>
+
+<template>
+ <div
+ :id="id"
+ class="modal fade"
+ tabindex="-1"
+ role="dialog"
+ >
+ <div
+ class="modal-dialog"
+ role="document"
+ >
+ <div class="modal-content">
+ <div class="modal-header">
+ <slot name="header">
+ <button
+ type="button"
+ class="close"
+ data-dismiss="modal"
+ :aria-label="s__('Modal|Close')"
+ @click="emitCancel($event)"
+ >
+ <span aria-hidden="true">&times;</span>
+ </button>
+ <h4 class="modal-title">
+ <slot name="title">
+ {{ headerTitleText }}
+ </slot>
+ </h4>
+ </slot>
+ </div>
+
+ <div class="modal-body">
+ <slot></slot>
+ </div>
+
+ <div class="modal-footer">
+ <slot name="footer">
+ <button
+ type="button"
+ class="btn"
+ data-dismiss="modal"
+ @click="emitCancel($event)"
+ >
+ {{ s__('Modal|Cancel') }}
+ </button>
+ <button
+ type="button"
+ class="btn"
+ :class="`btn-${footerPrimaryButtonVariant}`"
+ data-dismiss="modal"
+ @click="emitSubmit($event)"
+ >
+ {{ footerPrimaryButtonText }}
+ </button>
+ </slot>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/loading_button.vue b/app/assets/javascripts/vue_shared/components/loading_button.vue
index ff8c0f7c1d2..6ae6b179f7f 100644
--- a/app/assets/javascripts/vue_shared/components/loading_button.vue
+++ b/app/assets/javascripts/vue_shared/components/loading_button.vue
@@ -40,7 +40,7 @@
required: false,
},
containerClass: {
- type: String,
+ type: [String, Array, Object],
required: false,
default: 'btn btn-align-content',
},
diff --git a/app/assets/javascripts/vue_shared/components/modal.vue b/app/assets/javascripts/vue_shared/components/modal.vue
index 8227428d8ba..5f1364421aa 100644
--- a/app/assets/javascripts/vue_shared/components/modal.vue
+++ b/app/assets/javascripts/vue_shared/components/modal.vue
@@ -46,6 +46,11 @@
required: false,
default: '',
},
+ secondaryButtonLabel: {
+ type: String,
+ required: false,
+ default: '',
+ },
submitDisabled: {
type: Boolean,
required: false,
@@ -129,6 +134,21 @@
>
{{ closeButtonLabel }}
</button>
+
+ <slot
+ v-if="secondaryButtonLabel"
+ name="secondary-button"
+ >
+ <button
+ v-if="secondaryButtonLabel"
+ type="button"
+ class="btn"
+ data-dismiss="modal"
+ >
+ {{ secondaryButtonLabel }}
+ </button>
+ </slot>
+
<button
v-if="primaryButtonLabel"
type="button"