diff options
Diffstat (limited to 'app/assets/javascripts')
62 files changed, 331 insertions, 447 deletions
diff --git a/app/assets/javascripts/badges/components/badge_settings.vue b/app/assets/javascripts/badges/components/badge_settings.vue index 531f84ad272..75a522efe7e 100644 --- a/app/assets/javascripts/badges/components/badge_settings.vue +++ b/app/assets/javascripts/badges/components/badge_settings.vue @@ -2,7 +2,7 @@ import { mapState, mapActions } from 'vuex'; import createFlash from '~/flash'; import { s__ } from '~/locale'; -import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue'; +import GlModal from '~/vue_shared/components/gl_modal.vue'; import Badge from './badge.vue'; import BadgeForm from './badge_form.vue'; import BadgeList from './badge_list.vue'; @@ -13,7 +13,7 @@ export default { Badge, BadgeForm, BadgeList, - GlModal: DeprecatedModal2, + GlModal, }, computed: { ...mapState(['badgeInModal', 'isEditing']), diff --git a/app/assets/javascripts/blob/balsamiq/balsamiq_viewer.js b/app/assets/javascripts/blob/balsamiq/balsamiq_viewer.js index 87c8568802e..75777b910ca 100644 --- a/app/assets/javascripts/blob/balsamiq/balsamiq_viewer.js +++ b/app/assets/javascripts/blob/balsamiq/balsamiq_viewer.js @@ -1,7 +1,5 @@ import sqljs from 'sql.js'; import { template as _template } from 'underscore'; -import axios from '~/lib/utils/axios_utils'; -import { successCodes } from '~/lib/utils/http_status'; const PREVIEW_TEMPLATE = _template(` <div class="card"> @@ -18,25 +16,30 @@ class BalsamiqViewer { } loadFile(endpoint) { - return axios - .get(endpoint, { - responseType: 'arraybuffer', - validateStatus(status) { - return status !== successCodes.OK; - }, - }) - .then(({ data }) => { - this.renderFile(data); - }) - .catch(e => { - throw new Error(e); - }); + return new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + + xhr.open('GET', endpoint, true); + xhr.responseType = 'arraybuffer'; + xhr.onload = loadEvent => this.fileLoaded(loadEvent, resolve, reject); + xhr.onerror = reject; + + xhr.send(); + }); + } + + fileLoaded(loadEvent, resolve, reject) { + if (loadEvent.target.status !== 200) return reject(); + + this.renderFile(loadEvent); + + return resolve(); } - renderFile(fileBuffer) { + renderFile(loadEvent) { const container = document.createElement('ul'); - this.initDatabase(fileBuffer); + this.initDatabase(loadEvent.target.response); const previews = this.getPreviews(); previews.forEach(preview => { diff --git a/app/assets/javascripts/blob/template_selector.js b/app/assets/javascripts/blob/template_selector.js index 02216e4e93d..9e69c7d7164 100644 --- a/app/assets/javascripts/blob/template_selector.js +++ b/app/assets/javascripts/blob/template_selector.js @@ -1,7 +1,6 @@ /* eslint-disable class-methods-use-this */ import $ from 'jquery'; -import '~/gl_dropdown'; export default class TemplateSelector { constructor({ dropdown, data, pattern, wrapper, editor, $input } = {}) { diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js index da2669e7cde..3bded4a3258 100644 --- a/app/assets/javascripts/boards/index.js +++ b/app/assets/javascripts/boards/index.js @@ -22,6 +22,7 @@ import Board from 'ee_else_ce/boards/components/board'; import BoardSidebar from 'ee_else_ce/boards/components/board_sidebar'; import initNewListDropdown from 'ee_else_ce/boards/components/new_list_dropdown'; import BoardAddIssuesModal from '~/boards/components/modal/index.vue'; +import '~/vue_shared/vue_resource_interceptor'; import { NavigationType, convertObjectPropsToCamelCase, diff --git a/app/assets/javascripts/commons/vue.js b/app/assets/javascripts/commons/vue.js index 8b62d78c043..798623b94fb 100644 --- a/app/assets/javascripts/commons/vue.js +++ b/app/assets/javascripts/commons/vue.js @@ -1,4 +1,5 @@ import Vue from 'vue'; +import '../vue_shared/vue_resource_interceptor'; if (process.env.NODE_ENV !== 'production') { Vue.config.productionTip = false; diff --git a/app/assets/javascripts/create_item_dropdown.js b/app/assets/javascripts/create_item_dropdown.js index 95b890b04c1..fa0f04c7d82 100644 --- a/app/assets/javascripts/create_item_dropdown.js +++ b/app/assets/javascripts/create_item_dropdown.js @@ -1,5 +1,4 @@ import _ from 'underscore'; -import '~/gl_dropdown'; export default class CreateItemDropdown { /** diff --git a/app/assets/javascripts/diff_notes/services/resolve.js b/app/assets/javascripts/diff_notes/services/resolve.js index 27990b0a45e..0687028ca54 100644 --- a/app/assets/javascripts/diff_notes/services/resolve.js +++ b/app/assets/javascripts/diff_notes/services/resolve.js @@ -2,6 +2,7 @@ import Vue from 'vue'; import Flash from '../../flash'; +import '../../vue_shared/vue_resource_interceptor'; import { __ } from '~/locale'; window.gl = window.gl || {}; diff --git a/app/assets/javascripts/environments/components/stop_environment_modal.vue b/app/assets/javascripts/environments/components/stop_environment_modal.vue index 1ea4e30a7c1..2cc3412e075 100644 --- a/app/assets/javascripts/environments/components/stop_environment_modal.vue +++ b/app/assets/javascripts/environments/components/stop_environment_modal.vue @@ -1,7 +1,7 @@ <script> /* eslint-disable @gitlab/vue-i18n/no-bare-strings */ import { GlTooltipDirective } from '@gitlab/ui'; -import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue'; +import GlModal from '~/vue_shared/components/gl_modal.vue'; import { s__, sprintf } from '~/locale'; import LoadingButton from '~/vue_shared/components/loading_button.vue'; import eventHub from '../event_hub'; @@ -11,7 +11,7 @@ export default { name: 'StopEnvironmentModal', components: { - GlModal: DeprecatedModal2, + GlModal, LoadingButton, }, diff --git a/app/assets/javascripts/event_tracking/issue_sidebar.js b/app/assets/javascripts/event_tracking/issue_sidebar.js new file mode 100644 index 00000000000..6909f82c66f --- /dev/null +++ b/app/assets/javascripts/event_tracking/issue_sidebar.js @@ -0,0 +1,2 @@ +export const initSidebarTracking = () => {}; +export const trackEvent = () => {}; diff --git a/app/assets/javascripts/event_tracking/notes.js b/app/assets/javascripts/event_tracking/notes.js new file mode 100644 index 00000000000..1f70290c397 --- /dev/null +++ b/app/assets/javascripts/event_tracking/notes.js @@ -0,0 +1,2 @@ +// Noop function which has a EE counter-part +export default () => {}; diff --git a/app/assets/javascripts/filterable_list.js b/app/assets/javascripts/filterable_list.js index c21fba06d42..77080691dcb 100644 --- a/app/assets/javascripts/filterable_list.js +++ b/app/assets/javascripts/filterable_list.js @@ -22,7 +22,6 @@ export default class FilterableList { getPagePath() { const action = this.filterForm.getAttribute('action'); - // eslint-disable-next-line no-jquery/no-serialize const params = $(this.filterForm).serialize(); return `${action}${action.indexOf('?') > 0 ? '&' : '?'}${params}`; } diff --git a/app/assets/javascripts/header.js b/app/assets/javascripts/header.js index fdd27e08793..3d846310008 100644 --- a/app/assets/javascripts/header.js +++ b/app/assets/javascripts/header.js @@ -15,10 +15,11 @@ import { parseBoolean } from '~/lib/utils/common_utils'; */ export default function initTodoToggle() { $(document).on('todo:toggle', (e, count) => { + const parsedCount = parseInt(count, 10); const $todoPendingCount = $('.todos-count'); - $todoPendingCount.text(highCountTrim(count)); - $todoPendingCount.toggleClass('hidden', count === 0); + $todoPendingCount.text(highCountTrim(parsedCount)); + $todoPendingCount.toggleClass('hidden', parsedCount === 0); }); } diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list.vue b/app/assets/javascripts/ide/components/commit_sidebar/list.vue index e16918ae025..4f1260de0bc 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/list.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/list.vue @@ -3,7 +3,7 @@ import $ from 'jquery'; import { mapActions } from 'vuex'; import { __, sprintf } from '~/locale'; import Icon from '~/vue_shared/components/icon.vue'; -import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue'; +import GlModal from '~/vue_shared/components/gl_modal.vue'; import tooltip from '~/vue_shared/directives/tooltip'; import ListItem from './list_item.vue'; @@ -11,7 +11,7 @@ export default { components: { Icon, ListItem, - GlModal: DeprecatedModal2, + GlModal, }, directives: { tooltip, diff --git a/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue b/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue index c14b8a47841..09c9d135614 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue @@ -4,12 +4,12 @@ import { mapActions } from 'vuex'; import { sprintf, __ } from '~/locale'; import Icon from '~/vue_shared/components/icon.vue'; import tooltip from '~/vue_shared/directives/tooltip'; -import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue'; +import GlModal from '~/vue_shared/components/gl_modal.vue'; export default { components: { Icon, - GlModal: DeprecatedModal2, + GlModal, }, directives: { tooltip, diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue index a2dd31aebd4..f67666f1fbf 100644 --- a/app/assets/javascripts/ide/components/new_dropdown/modal.vue +++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue @@ -3,12 +3,12 @@ import $ from 'jquery'; import flash from '~/flash'; import { __, sprintf, s__ } from '~/locale'; import { mapActions, mapState, mapGetters } from 'vuex'; -import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue'; +import GlModal from '~/vue_shared/components/gl_modal.vue'; import { modalTypes } from '../../constants'; export default { components: { - GlModal: DeprecatedModal2, + GlModal, }, data() { return { diff --git a/app/assets/javascripts/image_diff/helpers/badge_helper.js b/app/assets/javascripts/image_diff/helpers/badge_helper.js index 7921650e8a0..000157efad0 100644 --- a/app/assets/javascripts/image_diff/helpers/badge_helper.js +++ b/app/assets/javascripts/image_diff/helpers/badge_helper.js @@ -1,5 +1,3 @@ -import { spriteIcon } from '~/lib/utils/common_utils'; - export function createImageBadge(noteId, { x, y }, classNames = []) { const buttonEl = document.createElement('button'); const classList = classNames.concat(['js-image-badge']); @@ -22,7 +20,7 @@ export function addImageBadge(containerEl, { coordinate, badgeText, noteId }) { export function addImageCommentBadge(containerEl, { coordinate, noteId }) { const buttonEl = createImageBadge(noteId, coordinate, ['image-comment-badge']); - buttonEl.innerHTML = spriteIcon('image-comment-dark'); + buttonEl.innerHTML = gl.utils.spriteIcon('image-comment-dark'); containerEl.appendChild(buttonEl); } diff --git a/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js b/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js index df3d90cff68..7051a968dac 100644 --- a/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js +++ b/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js @@ -1,5 +1,3 @@ -import { spriteIcon } from '~/lib/utils/common_utils'; - export function addCommentIndicator(containerEl, { x, y }) { const buttonEl = document.createElement('button'); buttonEl.classList.add('btn-transparent'); @@ -8,7 +6,7 @@ export function addCommentIndicator(containerEl, { x, y }) { buttonEl.style.left = `${x}px`; buttonEl.style.top = `${y}px`; - buttonEl.innerHTML = spriteIcon('image-comment-dark'); + buttonEl.innerHTML = gl.utils.spriteIcon('image-comment-dark'); containerEl.appendChild(buttonEl); } diff --git a/app/assets/javascripts/import_projects/components/import_projects_table.vue b/app/assets/javascripts/import_projects/components/import_projects_table.vue index e5ac3cbafe5..00eb0afb3bf 100644 --- a/app/assets/javascripts/import_projects/components/import_projects_table.vue +++ b/app/assets/javascripts/import_projects/components/import_projects_table.vue @@ -1,5 +1,4 @@ <script> -import _ from 'underscore'; import { mapActions, mapState, mapGetters } from 'vuex'; import { GlLoadingIcon } from '@gitlab/ui'; import LoadingButton from '~/vue_shared/components/loading_button.vue'; @@ -8,8 +7,6 @@ import ImportedProjectTableRow from './imported_project_table_row.vue'; import ProviderRepoTableRow from './provider_repo_table_row.vue'; import eventHub from '../event_hub'; -const reposFetchThrottleDelay = 1000; - export default { name: 'ImportProjectsTable', components: { @@ -26,11 +23,11 @@ export default { }, computed: { - ...mapState(['importedProjects', 'providerRepos', 'isLoadingRepos', 'filter']), + ...mapState(['importedProjects', 'providerRepos', 'isLoadingRepos']), ...mapGetters(['isImportingAnyRepo', 'hasProviderRepos', 'hasImportedProjects']), emptyStateText() { - return sprintf(__('No %{providerTitle} repositories found'), { + return sprintf(__('No %{providerTitle} repositories available to import'), { providerTitle: this.providerTitle, }); }, @@ -50,38 +47,21 @@ export default { }, methods: { - ...mapActions([ - 'fetchRepos', - 'fetchReposFiltered', - 'fetchJobs', - 'stopJobsPolling', - 'clearJobsEtagPoll', - 'setFilter', - ]), + ...mapActions(['fetchRepos', 'fetchJobs', 'stopJobsPolling', 'clearJobsEtagPoll']), importAll() { eventHub.$emit('importAll'); }, - - handleFilterInput({ target }) { - this.setFilter(target.value); - }, - - throttledFetchRepos: _.throttle(function fetch() { - eventHub.$off('importAll'); - this.fetchRepos(); - }, reposFetchThrottleDelay), }, }; </script> <template> <div> - <p class="light text-nowrap mt-2"> - {{ s__('ImportProjects|Select the projects you want to import') }} - </p> - <div class="d-flex justify-content-between align-items-end flex-wrap mb-3"> + <p class="light text-nowrap mt-2 my-sm-0"> + {{ s__('ImportProjects|Select the projects you want to import') }} + </p> <loading-button container-class="btn btn-success js-import-all" :loading="isImportingAnyRepo" @@ -90,19 +70,6 @@ export default { type="button" @click="importAll" /> - <form novalidate @submit.prevent> - <input - :value="filter" - data-qa-selector="githubish_import_filter_field" - class="form-control" - name="filter" - :placeholder="__('Filter your projects by name')" - autofocus - size="40" - @input="handleFilterInput($event)" - @keyup.enter="throttledFetchRepos" - /> - </form> </div> <gl-loading-icon v-if="isLoadingRepos" diff --git a/app/assets/javascripts/import_projects/index.js b/app/assets/javascripts/import_projects/index.js index b069dcb7766..2d99d716609 100644 --- a/app/assets/javascripts/import_projects/index.js +++ b/app/assets/javascripts/import_projects/index.js @@ -38,7 +38,7 @@ export default function mountImportProjectsTable(mountElement) { }, methods: { - ...mapActions(['setInitialData', 'setFilter']), + ...mapActions(['setInitialData']), }, render(createElement) { diff --git a/app/assets/javascripts/import_projects/store/actions.js b/app/assets/javascripts/import_projects/store/actions.js index 0fb9a4cdfd4..c44500937cc 100644 --- a/app/assets/javascripts/import_projects/store/actions.js +++ b/app/assets/javascripts/import_projects/store/actions.js @@ -5,7 +5,6 @@ import Poll from '~/lib/utils/poll'; import createFlash from '~/flash'; import { s__, sprintf } from '~/locale'; import axios from '~/lib/utils/axios_utils'; -import { jobsPathWithFilter, reposPathWithFilter } from './getters'; let eTagPoll; @@ -20,20 +19,16 @@ export const restartJobsPolling = () => { }; export const setInitialData = ({ commit }, data) => commit(types.SET_INITIAL_DATA, data); -export const setFilter = ({ commit }, filter) => commit(types.SET_FILTER, filter); export const requestRepos = ({ commit }, repos) => commit(types.REQUEST_REPOS, repos); export const receiveReposSuccess = ({ commit }, repos) => commit(types.RECEIVE_REPOS_SUCCESS, repos); export const receiveReposError = ({ commit }) => commit(types.RECEIVE_REPOS_ERROR); export const fetchRepos = ({ state, dispatch }) => { - dispatch('stopJobsPolling'); dispatch('requestRepos'); - const { provider } = state; - return axios - .get(reposPathWithFilter(state)) + .get(state.reposPath) .then(({ data }) => dispatch('receiveReposSuccess', convertObjectPropsToCamelCase(data, { deep: true })), ) @@ -41,7 +36,7 @@ export const fetchRepos = ({ state, dispatch }) => { .catch(() => { createFlash( sprintf(s__('ImportProjects|Requesting your %{provider} repositories failed'), { - provider, + provider: state.provider, }), ); @@ -82,23 +77,16 @@ export const fetchImport = ({ state, dispatch }, { newName, targetNamespace, rep export const receiveJobsSuccess = ({ commit }, updatedProjects) => commit(types.RECEIVE_JOBS_SUCCESS, updatedProjects); export const fetchJobs = ({ state, dispatch }) => { - const { filter } = state; - - if (eTagPoll) { - stopJobsPolling(); - clearJobsEtagPoll(); - } + if (eTagPoll) return; eTagPoll = new Poll({ resource: { - fetchJobs: () => axios.get(jobsPathWithFilter(state)), + fetchJobs: () => axios.get(state.jobsPath), }, method: 'fetchJobs', successCallback: ({ data }) => dispatch('receiveJobsSuccess', convertObjectPropsToCamelCase(data, { deep: true })), - errorCallback: () => - createFlash(s__('ImportProjects|Update of imported projects with realtime changes failed')), - data: { filter }, + errorCallback: () => createFlash(s__('ImportProjects|Updating the imported projects failed')), }); if (!Visibility.hidden()) { diff --git a/app/assets/javascripts/import_projects/store/getters.js b/app/assets/javascripts/import_projects/store/getters.js index b107c293181..727b80765bd 100644 --- a/app/assets/javascripts/import_projects/store/getters.js +++ b/app/assets/javascripts/import_projects/store/getters.js @@ -20,8 +20,3 @@ export const isImportingAnyRepo = state => state.reposBeingImported.length > 0; export const hasProviderRepos = state => state.providerRepos.length > 0; export const hasImportedProjects = state => state.importedProjects.length > 0; - -export const reposPathWithFilter = ({ reposPath, filter = '' }) => - filter ? `${reposPath}?filter=${filter}` : reposPath; -export const jobsPathWithFilter = ({ jobsPath, filter = '' }) => - filter ? `${jobsPath}?filter=${filter}` : jobsPath; diff --git a/app/assets/javascripts/import_projects/store/mutation_types.js b/app/assets/javascripts/import_projects/store/mutation_types.js index 16574f4450f..6ba3fd6f29e 100644 --- a/app/assets/javascripts/import_projects/store/mutation_types.js +++ b/app/assets/javascripts/import_projects/store/mutation_types.js @@ -9,5 +9,3 @@ export const RECEIVE_IMPORT_SUCCESS = 'RECEIVE_IMPORT_SUCCESS'; export const RECEIVE_IMPORT_ERROR = 'RECEIVE_IMPORT_ERROR'; export const RECEIVE_JOBS_SUCCESS = 'RECEIVE_JOBS_SUCCESS'; - -export const SET_FILTER = 'SET_FILTER'; diff --git a/app/assets/javascripts/import_projects/store/mutations.js b/app/assets/javascripts/import_projects/store/mutations.js index 6c56cfa8298..b88de0268e7 100644 --- a/app/assets/javascripts/import_projects/store/mutations.js +++ b/app/assets/javascripts/import_projects/store/mutations.js @@ -6,10 +6,6 @@ export default { Object.assign(state, data); }, - [types.SET_FILTER](state, filter) { - state.filter = filter; - }, - [types.REQUEST_REPOS](state) { state.isLoadingRepos = true; }, diff --git a/app/assets/javascripts/import_projects/store/state.js b/app/assets/javascripts/import_projects/store/state.js index 829f3aa4fbb..637fef6e53c 100644 --- a/app/assets/javascripts/import_projects/store/state.js +++ b/app/assets/javascripts/import_projects/store/state.js @@ -12,5 +12,4 @@ export default () => ({ isLoadingRepos: false, canSelectNamespace: false, ciCdOnly: false, - filter: '', }); diff --git a/app/assets/javascripts/integrations/integration_settings_form.js b/app/assets/javascripts/integrations/integration_settings_form.js index 1c9b94ade8a..a7746bb3a0b 100644 --- a/app/assets/javascripts/integrations/integration_settings_form.js +++ b/app/assets/javascripts/integrations/integration_settings_form.js @@ -42,7 +42,6 @@ export default class IntegrationSettingsForm { // and test the service using provided configuration. if (this.$form.get(0).checkValidity() && this.canTestService) { e.preventDefault(); - // eslint-disable-next-line no-jquery/no-serialize this.testSettings(this.$form.serialize()); } } diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue index b8b3a4f44fd..88975c2cc73 100644 --- a/app/assets/javascripts/issue_show/components/app.vue +++ b/app/assets/javascripts/issue_show/components/app.vue @@ -102,10 +102,10 @@ export default { required: false, default: '', }, - issuableTemplateNamesPath: { - type: String, + issuableTemplates: { + type: Array, required: false, - default: '', + default: () => [], }, markdownPreviewPath: { type: String, @@ -156,13 +156,9 @@ export default { store, state: store.state, showForm: false, - templatesRequested: false, }; }, computed: { - issuableTemplates() { - return this.store.formState.issuableTemplates; - }, formState() { return this.store.formState; }, @@ -237,7 +233,6 @@ export default { } return undefined; }, - updateStoreState() { return this.service .getData() @@ -250,7 +245,7 @@ export default { }); }, - updateAndShowForm(templates = []) { + openForm() { if (!this.showForm) { this.showForm = true; this.store.setFormState({ @@ -259,32 +254,9 @@ export default { lock_version: this.state.lock_version, lockedWarningVisible: false, updateLoading: false, - issuableTemplates: templates, - }); - } - }, - - requestTemplatesAndShowForm() { - return this.service - .loadTemplates(this.issuableTemplateNamesPath) - .then(res => { - this.updateAndShowForm(res.data); - }) - .catch(() => { - createFlash(this.defaultErrorMessage); - this.updateAndShowForm(); }); - }, - - openForm() { - if (!this.templatesRequested) { - this.templatesRequested = true; - this.requestTemplatesAndShowForm(); - } else { - this.updateAndShowForm(this.issuableTemplates); } }, - closeForm() { this.showForm = false; }, diff --git a/app/assets/javascripts/issue_show/index.js b/app/assets/javascripts/issue_show/index.js index e170d338408..5a9dd91817e 100644 --- a/app/assets/javascripts/issue_show/index.js +++ b/app/assets/javascripts/issue_show/index.js @@ -1,6 +1,8 @@ import Vue from 'vue'; +import { initSidebarTracking } from 'ee_else_ce/event_tracking/issue_sidebar'; import issuableApp from './components/app.vue'; import { parseIssuableData } from './utils/parse_data'; +import '../vue_shared/vue_resource_interceptor'; export default function initIssueableApp() { return new Vue({ @@ -8,6 +10,9 @@ export default function initIssueableApp() { components: { issuableApp, }, + mounted() { + initSidebarTracking(); + }, render(createElement) { return createElement('issuable-app', { props: parseIssuableData(), diff --git a/app/assets/javascripts/issue_show/services/index.js b/app/assets/javascripts/issue_show/services/index.js index b1deeaae0fc..3c8334bee50 100644 --- a/app/assets/javascripts/issue_show/services/index.js +++ b/app/assets/javascripts/issue_show/services/index.js @@ -17,13 +17,4 @@ export default class Service { updateIssuable(data) { return axios.put(this.endpoint, data); } - - // eslint-disable-next-line class-methods-use-this - loadTemplates(templateNamesEndpoint) { - if (!templateNamesEndpoint) { - return Promise.resolve([]); - } - - return axios.get(templateNamesEndpoint); - } } diff --git a/app/assets/javascripts/issue_show/stores/index.js b/app/assets/javascripts/issue_show/stores/index.js index d32747b5053..3c17e73ccec 100644 --- a/app/assets/javascripts/issue_show/stores/index.js +++ b/app/assets/javascripts/issue_show/stores/index.js @@ -9,7 +9,6 @@ export default class Store { lockedWarningVisible: false, updateLoading: false, lock_version: 0, - issuableTemplates: [], }; } diff --git a/app/assets/javascripts/jobs/components/log/collapsible_section.vue b/app/assets/javascripts/jobs/components/log/collapsible_section.vue deleted file mode 100644 index 7c0deb08488..00000000000 --- a/app/assets/javascripts/jobs/components/log/collapsible_section.vue +++ /dev/null @@ -1,55 +0,0 @@ -<script> -import LogLine from './line.vue'; -import LogLineHeader from './line_header.vue'; - -export default { - name: 'CollpasibleLogSection', - components: { - LogLine, - LogLineHeader, - }, - props: { - section: { - type: Object, - required: true, - }, - traceEndpoint: { - type: String, - required: true, - }, - }, - computed: { - badgeDuration() { - return this.section.line && this.section.line.section_duration; - }, - }, - methods: { - handleOnClickCollapsibleLine(section) { - this.$emit('onClickCollapsibleLine', section); - }, - }, -}; -</script> -<template> - <div> - <log-line-header - :line="section.line" - :duration="badgeDuration" - :path="traceEndpoint" - :is-closed="section.isClosed" - @toggleLine="handleOnClickCollapsibleLine(section)" - /> - <template v-if="!section.isClosed"> - <template v-for="line in section.lines"> - <collpasible-log-section - v-if="line.isHeader" - :key="`collapsible-nested-${line.offset}`" - :section="line" - :trace-endpoint="traceEndpoint" - @toggleLine="handleOnClickCollapsibleLine" - /> - <log-line v-else :key="line.offset" :line="line" :path="traceEndpoint" /> - </template> - </template> - </div> -</template> diff --git a/app/assets/javascripts/jobs/components/log/log.vue b/app/assets/javascripts/jobs/components/log/log.vue index ef126166e8b..429796aeb4e 100644 --- a/app/assets/javascripts/jobs/components/log/log.vue +++ b/app/assets/javascripts/jobs/components/log/log.vue @@ -1,12 +1,12 @@ <script> import { mapState, mapActions } from 'vuex'; -import CollpasibleLogSection from './collapsible_section.vue'; import LogLine from './line.vue'; +import LogLineHeader from './line_header.vue'; export default { components: { - CollpasibleLogSection, LogLine, + LogLineHeader, }, computed: { ...mapState(['traceEndpoint', 'trace', 'isTraceComplete']), @@ -22,13 +22,24 @@ export default { <template> <code class="job-log d-block"> <template v-for="(section, index) in trace"> - <collpasible-log-section - v-if="section.isHeader" - :key="`collapsible-${index}`" - :section="section" - :trace-endpoint="traceEndpoint" - @onClickCollapsibleLine="handleOnClickCollapsibleLine" - /> + <template v-if="section.isHeader"> + <log-line-header + :key="`collapsible-${index}`" + :line="section.line" + :duration="section.section_duration" + :path="traceEndpoint" + :is-closed="section.isClosed" + @toggleLine="handleOnClickCollapsibleLine(section)" + /> + <template v-if="!section.isClosed"> + <log-line + v-for="line in section.lines" + :key="line.offset" + :line="line" + :path="traceEndpoint" + /> + </template> + </template> <log-line v-else :key="section.offset" :line="section" :path="traceEndpoint" /> </template> diff --git a/app/assets/javascripts/lib/utils/axios_utils.js b/app/assets/javascripts/lib/utils/axios_utils.js index c17f62c671c..37721cd030c 100644 --- a/app/assets/javascripts/lib/utils/axios_utils.js +++ b/app/assets/javascripts/lib/utils/axios_utils.js @@ -8,19 +8,19 @@ axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; // Maintain a global counter for active requests // see: spec/support/wait_for_requests.rb axios.interceptors.request.use(config => { - window.pendingRequests = window.pendingRequests || 0; - window.pendingRequests += 1; + window.activeVueResources = window.activeVueResources || 0; + window.activeVueResources += 1; return config; }); // Remove the global counter axios.interceptors.response.use( response => { - window.pendingRequests -= 1; + window.activeVueResources -= 1; return response; }, err => { - window.pendingRequests -= 1; + window.activeVueResources -= 1; return Promise.reject(err); }, ); diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index c19a845eb69..0ddf40b0405 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -37,7 +37,6 @@ import GlFieldErrors from './gl_field_errors'; import initUserPopovers from './user_popovers'; import { initUserTracking } from './tracking'; import { __ } from './locale'; -import initPrivacyPolicyUpdateCallout from './privacy_policy_update_callout'; import 'ee_else_ce/main_ee'; @@ -97,7 +96,6 @@ function deferredInitialisation() { initUsagePingConsent(); initUserPopovers(); initUserTracking(); - initPrivacyPolicyUpdateCallout(); if (document.querySelector('.search')) initSearchAutocomplete(); @@ -314,7 +312,6 @@ document.addEventListener('DOMContentLoaded', () => { const action = `${this.action}${link.search === '' ? '?' : '&'}`; event.preventDefault(); - // eslint-disable-next-line no-jquery/no-serialize visitUrl(`${action}${$(this).serialize()}`); }); diff --git a/app/assets/javascripts/namespace_select.js b/app/assets/javascripts/namespace_select.js index 4660e4397a2..4ddbec71ba6 100644 --- a/app/assets/javascripts/namespace_select.js +++ b/app/assets/javascripts/namespace_select.js @@ -1,7 +1,6 @@ /* eslint-disable func-names, no-else-return, prefer-template, prefer-arrow-callback */ import $ from 'jquery'; -import '~/gl_dropdown'; import Api from './api'; import { mergeUrlParams } from './lib/utils/url_utility'; import { parseBoolean } from '~/lib/utils/common_utils'; diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index ed52eec8b18..9cc56b34c75 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1461,7 +1461,6 @@ export default class Notes { getFormData($form) { const content = $form.find('.js-note-text').val(); return { - // eslint-disable-next-line no-jquery/no-serialize formData: $form.serialize(), formContent: _.escape(content), formAction: $form.attr('action'), diff --git a/app/assets/javascripts/notes/components/note_actions/reply_button.vue b/app/assets/javascripts/notes/components/note_actions/reply_button.vue index 8bdee759a23..1aeb07d6608 100644 --- a/app/assets/javascripts/notes/components/note_actions/reply_button.vue +++ b/app/assets/javascripts/notes/components/note_actions/reply_button.vue @@ -19,9 +19,7 @@ export default { <gl-button ref="button" v-gl-tooltip - class="note-action-button" - data-track-event="click_button" - data-track-label="reply_comment_button" + class="note-action-button js-note-action-reply" variant="transparent" :title="__('Reply to comment')" @click="$emit('startReplying')" diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js index 30372103590..c70c0e4095c 100644 --- a/app/assets/javascripts/notes/index.js +++ b/app/assets/javascripts/notes/index.js @@ -1,4 +1,5 @@ import Vue from 'vue'; +import initNoteStats from 'ee_else_ce/event_tracking/notes'; import notesApp from './components/notes_app.vue'; import initDiscussionFilters from './discussion_filters'; import createStore from './stores'; @@ -38,6 +39,9 @@ document.addEventListener('DOMContentLoaded', () => { notesData: JSON.parse(notesDataset.notesData), }; }, + mounted() { + initNoteStats(); + }, render(createElement) { return createElement('notes-app', { props: { 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 eb03baf4894..e2fec3c7172 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 createFlash from '~/flash'; -import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue'; +import GlModal from '~/vue_shared/components/gl_modal.vue'; import { redirectTo } from '~/lib/utils/url_utility'; import { s__ } from '~/locale'; export default { components: { - GlModal: DeprecatedModal2, + GlModal, }, props: { url: { diff --git a/app/assets/javascripts/pages/dashboard/todos/index/todos.js b/app/assets/javascripts/pages/dashboard/todos/index/todos.js index 5230bdf9cdd..d51d411f3c6 100644 --- a/app/assets/javascripts/pages/dashboard/todos/index/todos.js +++ b/app/assets/javascripts/pages/dashboard/todos/index/todos.js @@ -1,11 +1,9 @@ /* eslint-disable class-methods-use-this, no-unneeded-ternary */ import $ from 'jquery'; -import '~/gl_dropdown'; import { visitUrl } from '~/lib/utils/url_utility'; import UsersSelect from '~/users_select'; import { isMetaClick } from '~/lib/utils/common_utils'; -import { addDelimiter } from '~/lib/utils/text_utility'; import { __ } from '~/locale'; import flash from '~/flash'; import axios from '~/lib/utils/axios_utils'; @@ -147,8 +145,8 @@ export default class Todos { updateBadges(data) { $(document).trigger('todo:toggle', data.count); - document.querySelector('.todos-pending .badge').innerHTML = addDelimiter(data.count); - document.querySelector('.todos-done .badge').innerHTML = addDelimiter(data.done_count); + document.querySelector('.todos-pending .badge').innerHTML = data.count; + document.querySelector('.todos-done .badge').innerHTML = data.done_count; } goToTodoUrl(e) { diff --git a/app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue b/app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue index 26adf4cbbe0..c563514d36b 100644 --- a/app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue +++ b/app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue @@ -1,14 +1,14 @@ <script> import axios from '~/lib/utils/axios_utils'; import createFlash from '~/flash'; -import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue'; +import GlModal from '~/vue_shared/components/gl_modal.vue'; import { s__, sprintf } from '~/locale'; import { visitUrl } from '~/lib/utils/url_utility'; import eventHub from '../event_hub'; export default { components: { - GlModal: DeprecatedModal2, + GlModal, }, props: { milestoneTitle: { diff --git a/app/assets/javascripts/pages/projects/commit/pipelines/index.js b/app/assets/javascripts/pages/projects/commit/pipelines/index.js index 9f08260c3d6..8cc3cb0a57c 100644 --- a/app/assets/javascripts/pages/projects/commit/pipelines/index.js +++ b/app/assets/javascripts/pages/projects/commit/pipelines/index.js @@ -6,7 +6,6 @@ document.addEventListener('DOMContentLoaded', () => { new MiniPipelineGraph({ container: '.js-commit-pipeline-graph', }).bindEvents(); - // eslint-disable-next-line no-jquery/no-load $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath); initPipelines(); }); diff --git a/app/assets/javascripts/pages/projects/commit/show/index.js b/app/assets/javascripts/pages/projects/commit/show/index.js index 5aa4734244e..6fc982967eb 100644 --- a/app/assets/javascripts/pages/projects/commit/show/index.js +++ b/app/assets/javascripts/pages/projects/commit/show/index.js @@ -21,7 +21,6 @@ document.addEventListener('DOMContentLoaded', () => { }).bindEvents(); initNotes(); initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight + performanceHeight); - // eslint-disable-next-line no-jquery/no-load $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath); fetchCommitMergeRequests(); initDiffNotes(); diff --git a/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue b/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue index bb95f33c838..e723cd3fea9 100644 --- a/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue +++ b/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue @@ -2,14 +2,14 @@ import _ from 'underscore'; import axios from '~/lib/utils/axios_utils'; import createFlash from '~/flash'; -import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue'; +import GlModal from '~/vue_shared/components/gl_modal.vue'; import { s__, sprintf } from '~/locale'; import { visitUrl } from '~/lib/utils/url_utility'; import eventHub from '../event_hub'; export default { components: { - GlModal: DeprecatedModal2, + GlModal, }, props: { url: { diff --git a/app/assets/javascripts/pages/search/show/search.js b/app/assets/javascripts/pages/search/show/search.js index dff9d855b67..8f6c48ab065 100644 --- a/app/assets/javascripts/pages/search/show/search.js +++ b/app/assets/javascripts/pages/search/show/search.js @@ -1,5 +1,4 @@ import $ from 'jquery'; -import '~/gl_dropdown'; import Flash from '~/flash'; import Api from '~/api'; import { __ } from '~/locale'; diff --git a/app/assets/javascripts/performance_bar/components/detailed_metric.vue b/app/assets/javascripts/performance_bar/components/detailed_metric.vue index f2d98cf07e1..a271284dd89 100644 --- a/app/assets/javascripts/performance_bar/components/detailed_metric.vue +++ b/app/assets/javascripts/performance_bar/components/detailed_metric.vue @@ -1,10 +1,10 @@ <script> -import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue'; +import GlModal from '~/vue_shared/components/gl_modal.vue'; import Icon from '~/vue_shared/components/icon.vue'; export default { components: { - GlModal: DeprecatedModal2, + GlModal, Icon, }, props: { diff --git a/app/assets/javascripts/pipelines/components/pipeline_stop_modal.vue b/app/assets/javascripts/pipelines/components/pipeline_stop_modal.vue index 2e71b3c637b..4cafd147511 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_stop_modal.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_stop_modal.vue @@ -1,6 +1,6 @@ <script> import _ from 'underscore'; -import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue'; +import GlModal from '~/vue_shared/components/gl_modal.vue'; import { GlLink } from '@gitlab/ui'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import CiIcon from '~/vue_shared/components/ci_icon.vue'; @@ -13,7 +13,7 @@ import { s__, sprintf } from '~/locale'; */ export default { components: { - GlModal: DeprecatedModal2, + GlModal, GlLink, ClipboardButton, CiIcon, diff --git a/app/assets/javascripts/privacy_policy_update_callout.js b/app/assets/javascripts/privacy_policy_update_callout.js index 97f41deb30f..126b1ee1132 100644 --- a/app/assets/javascripts/privacy_policy_update_callout.js +++ b/app/assets/javascripts/privacy_policy_update_callout.js @@ -1,7 +1,7 @@ import PersistentUserCallout from '~/persistent_user_callout'; function initPrivacyPolicyUpdateCallout() { - const callout = document.querySelector('.js-privacy-policy-update'); + const callout = document.querySelector('.privacy-policy-update-64341'); PersistentUserCallout.factory(callout); } diff --git a/app/assets/javascripts/profile/account/components/update_username.vue b/app/assets/javascripts/profile/account/components/update_username.vue index 72867ecd709..e1085c0a44d 100644 --- a/app/assets/javascripts/profile/account/components/update_username.vue +++ b/app/assets/javascripts/profile/account/components/update_username.vue @@ -1,13 +1,13 @@ <script> import _ from 'underscore'; import axios from '~/lib/utils/axios_utils'; -import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue'; +import GlModal from '~/vue_shared/components/gl_modal.vue'; import { s__, sprintf } from '~/locale'; import Flash from '~/flash'; export default { components: { - GlModal: DeprecatedModal2, + GlModal, }, props: { actionUrl: { diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js index c198c4eea4a..e73a828c0ae 100644 --- a/app/assets/javascripts/project_find_file.js +++ b/app/assets/javascripts/project_find_file.js @@ -81,7 +81,7 @@ export default class ProjectFindFile { // find file } - // files paths load + // files pathes load load(url) { axios .get(url) diff --git a/app/assets/javascripts/ref_select_dropdown.js b/app/assets/javascripts/ref_select_dropdown.js index 2e0113271df..75bac035aca 100644 --- a/app/assets/javascripts/ref_select_dropdown.js +++ b/app/assets/javascripts/ref_select_dropdown.js @@ -1,5 +1,4 @@ import $ from 'jquery'; -import '~/gl_dropdown'; class RefSelectDropdown { constructor($dropdownButton, availableRefs) { diff --git a/app/assets/javascripts/reports/components/modal.vue b/app/assets/javascripts/reports/components/modal.vue index 6019af2dfe0..cb9c1642608 100644 --- a/app/assets/javascripts/reports/components/modal.vue +++ b/app/assets/javascripts/reports/components/modal.vue @@ -1,13 +1,13 @@ <script> // import { sprintf, __ } from '~/locale'; -import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue'; +import Modal from '~/vue_shared/components/gl_modal.vue'; import LoadingButton from '~/vue_shared/components/loading_button.vue'; import CodeBlock from '~/vue_shared/components/code_block.vue'; import { fieldTypes } from '../constants'; export default { components: { - Modal: DeprecatedModal2, + Modal, LoadingButton, CodeBlock, }, diff --git a/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue b/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue index f4dac38b9e1..63b93a80ead 100644 --- a/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue +++ b/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue @@ -1,5 +1,6 @@ <script> import { n__ } from '~/locale'; +import { trackEvent } from 'ee_else_ce/event_tracking/issue_sidebar'; export default { name: 'AssigneeTitle', @@ -29,6 +30,11 @@ export default { return n__('Assignee', `%d Assignees`, assignees); }, }, + methods: { + trackEdit() { + trackEvent('click_edit_button', 'assignee'); + }, + }, }; </script> <template> @@ -39,9 +45,7 @@ export default { v-if="editable" class="js-sidebar-dropdown-toggle edit-link float-right" href="#" - data-track-event="click_edit_button" - data-track-label="right_sidebar" - data-track-property="assignee" + @click.prevent="trackEdit" > {{ __('Edit') }} </a> 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 e350264de96..1c75b6148e8 100644 --- a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue +++ b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue @@ -5,6 +5,7 @@ import tooltip from '~/vue_shared/directives/tooltip'; import Icon from '~/vue_shared/components/icon.vue'; import eventHub from '~/sidebar/event_hub'; import editForm from './edit_form.vue'; +import { trackEvent } from 'ee_else_ce/event_tracking/issue_sidebar'; export default { components: { @@ -51,6 +52,11 @@ export default { toggleForm() { this.edit = !this.edit; }, + onEditClick() { + this.toggleForm(); + + trackEvent('click_edit_button', 'confidentiality'); + }, updateConfidentialAttribute(confidential) { this.service .update('issue', { confidential }) @@ -82,10 +88,7 @@ export default { v-if="isEditable" class="float-right confidential-edit" href="#" - data-track-event="click_edit_button" - data-track-label="right_sidebar" - data-track-property="confidentiality" - @click.prevent="toggleForm" + @click.prevent="onEditClick" > {{ __('Edit') }} </a> 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 c7c5e0e20f1..ec2a7b93a98 100644 --- a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue +++ b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue @@ -6,6 +6,7 @@ import issuableMixin from '~/vue_shared/mixins/issuable'; import Icon from '~/vue_shared/components/icon.vue'; import eventHub from '~/sidebar/event_hub'; import editForm from './edit_form.vue'; +import { trackEvent } from 'ee_else_ce/event_tracking/issue_sidebar'; export default { components: { @@ -65,6 +66,11 @@ export default { toggleForm() { this.mediator.store.isLockDialogOpen = !this.mediator.store.isLockDialogOpen; }, + onEditClick() { + this.toggleForm(); + + trackEvent('click_edit_button', 'lock_issue'); + }, updateLockedAttribute(locked) { this.mediator.service .update(this.issuableType, { @@ -108,10 +114,7 @@ export default { v-if="isEditable" class="float-right lock-edit" type="button" - data-track-event="click_edit_button" - data-track-label="right_sidebar" - data-track-property="lock_issue" - @click.prevent="toggleForm" + @click.prevent="onEditClick" > {{ __('Edit') }} </button> diff --git a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue index ea5edb3ce3f..1f5f19d1931 100644 --- a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue +++ b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue @@ -1,10 +1,10 @@ <script> import { __ } from '~/locale'; -import Tracking from '~/tracking'; import icon from '~/vue_shared/components/icon.vue'; import toggleButton from '~/vue_shared/components/toggle_button.vue'; import tooltip from '~/vue_shared/directives/tooltip'; import eventHub from '../../event_hub'; +import { trackEvent } from 'ee_else_ce/event_tracking/issue_sidebar'; const ICON_ON = 'notifications'; const ICON_OFF = 'notifications-off'; @@ -19,7 +19,6 @@ export default { icon, toggleButton, }, - mixins: [Tracking.mixin({ label: 'right_sidebar' })], props: { loading: { type: Boolean, @@ -66,10 +65,7 @@ export default { // Component event emission. this.$emit('toggleSubscription', this.id); - this.track('toggle_button', { - property: 'notifications', - value: this.subscribed ? 0 : 1, - }); + trackEvent('toggle_button', 'notifications', this.subscribed ? 0 : 1); }, onClickCollapsedIcon() { this.$emit('toggleSidebar'); diff --git a/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js b/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js index 66d1fed7d31..110175a6779 100644 --- a/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js +++ b/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js @@ -1,5 +1,4 @@ import $ from 'jquery'; -import '~/gl_dropdown'; import _ from 'underscore'; import { __ } from '~/locale'; diff --git a/app/assets/javascripts/tracking.js b/app/assets/javascripts/tracking.js index 7c0097fbe37..1b4ca1d5741 100644 --- a/app/assets/javascripts/tracking.js +++ b/app/assets/javascripts/tracking.js @@ -1,4 +1,4 @@ -import _ from 'underscore'; +import $ from 'jquery'; const DEFAULT_SNOWPLOW_OPTIONS = { namespace: 'gl', @@ -14,31 +14,18 @@ const DEFAULT_SNOWPLOW_OPTIONS = { linkClickTracking: false, }; -const eventHandler = (e, func, opts = {}) => { - const el = e.target.closest('[data-track-event]'); - const action = el && el.dataset.trackEvent; - if (!action) return; - - let value = el.dataset.trackValue || el.value || undefined; - if (el.type === 'checkbox' && !el.checked) value = false; - - const data = { - label: el.dataset.trackLabel, - property: el.dataset.trackProperty, - value, - context: el.dataset.trackContext, - }; - - func(opts.category, action + (opts.suffix || ''), _.omit(data, _.isUndefined)); -}; - -const eventHandlers = (category, func) => { - const handler = opts => e => eventHandler(e, func, { ...{ category }, ...opts }); - const handlers = []; - handlers.push({ name: 'click', func: handler() }); - handlers.push({ name: 'show.bs.dropdown', func: handler({ suffix: '_show' }) }); - handlers.push({ name: 'hide.bs.dropdown', func: handler({ suffix: '_hide' }) }); - return handlers; +const extractData = (el, opts = {}) => { + const { trackEvent, trackLabel = '', trackProperty = '' } = el.dataset; + let trackValue = el.dataset.trackValue || el.value || ''; + if (el.type === 'checkbox' && !el.checked) trackValue = false; + return [ + trackEvent + (opts.suffix || ''), + { + label: trackLabel, + property: trackProperty, + value: trackValue, + }, + ]; }; export default class Tracking { @@ -52,43 +39,49 @@ export default class Tracking { return typeof window.snowplow === 'function' && this.trackable(); } - static event(category = document.body.dataset.page, action = 'generic', data = {}) { + static event(category = document.body.dataset.page, event = 'generic', data = {}) { if (!this.enabled()) return false; // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings if (!category) throw new Error('Tracking: no category provided for tracking.'); - const { label, property, value, context } = data; - const contexts = context ? [context] : undefined; - return window.snowplow('trackStructEvent', category, action, label, property, value, contexts); + return window.snowplow( + 'trackStructEvent', + category, + event, + Object.assign({}, { label: '', property: '', value: '' }, data), + ); } - static bindDocument(category = document.body.dataset.page, documentOverride = null) { - const el = documentOverride || document; - if (!this.enabled() || el.trackingBound) return []; + constructor(category = document.body.dataset.page) { + this.category = category; + } + + bind(container = document) { + if (!this.constructor.enabled()) return; + container.querySelectorAll(`[data-track-event]`).forEach(el => { + if (this.customHandlingFor(el)) return; + // jquery is required for select2, so we use it always + // see: https://github.com/select2/select2/issues/4686 + $(el).on('click', this.eventHandler(this.category)); + }); + } - el.trackingBound = true; + customHandlingFor(el) { + const classes = el.classList; - const handlers = eventHandlers(category, (...args) => this.event(...args)); - handlers.forEach(event => el.addEventListener(event.name, event.func)); - return handlers; + // bootstrap dropdowns + if (classes.contains('dropdown')) { + $(el).on('show.bs.dropdown', this.eventHandler(this.category, { suffix: '_show' })); + $(el).on('hide.bs.dropdown', this.eventHandler(this.category, { suffix: '_hide' })); + return true; + } + + return false; } - static mixin(opts) { - return { - data() { - return { - tracking: { - // eslint-disable-next-line no-underscore-dangle - category: this.$options.name || this.$options._componentTag, - }, - }; - }, - methods: { - track(action, data) { - const category = opts.category || data.category || this.tracking.category; - Tracking.event(category || 'unspecified', action, { ...opts, ...this.tracking, ...data }); - }, - }, + eventHandler(category = null, opts = {}) { + return e => { + this.constructor.event(category || this.category, ...extractData(e.currentTarget, opts)); }; } } @@ -96,7 +89,7 @@ export default class Tracking { export function initUserTracking() { if (!Tracking.enabled()) return; - const opts = { ...DEFAULT_SNOWPLOW_OPTIONS, ...window.snowplowOptions }; + const opts = Object.assign({}, DEFAULT_SNOWPLOW_OPTIONS, window.snowplowOptions); window.snowplow('newTracker', opts.namespace, opts.hostname, opts); window.snowplow('enableActivityTracking', 30, 30); @@ -104,6 +97,4 @@ export function initUserTracking() { if (opts.formTracking) window.snowplow('enableFormTracking'); if (opts.linkClickTracking) window.snowplow('enableLinkClickTracking'); - - Tracking.bindDocument(); } diff --git a/app/assets/javascripts/vue_shared/components/deprecated_modal_2.vue b/app/assets/javascripts/vue_shared/components/deprecated_modal_2.vue deleted file mode 100644 index 543547b37fe..00000000000 --- a/app/assets/javascripts/vue_shared/components/deprecated_modal_2.vue +++ /dev/null @@ -1,118 +0,0 @@ -<script> -import $ from 'jquery'; - -const buttonVariants = ['danger', 'primary', 'success', 'warning']; -const sizeVariants = ['sm', 'md', 'lg', 'xl']; - -export default { - name: 'DeprecatedModal2', // use GlModal instead - - props: { - id: { - type: String, - required: false, - default: null, - }, - modalSize: { - type: String, - required: false, - default: 'md', - validator: value => sizeVariants.includes(value), - }, - headerTitleText: { - type: String, - required: false, - default: '', - }, - footerPrimaryButtonVariant: { - type: String, - required: false, - default: 'primary', - validator: value => buttonVariants.includes(value), - }, - footerPrimaryButtonText: { - type: String, - required: false, - default: '', - }, - }, - computed: { - modalSizeClass() { - return this.modalSize === 'md' ? '' : `modal-${this.modalSize}`; - }, - }, - mounted() { - $(this.$el) - .on('shown.bs.modal', this.opened) - .on('hidden.bs.modal', this.closed); - }, - beforeDestroy() { - $(this.$el) - .off('shown.bs.modal', this.opened) - .off('hidden.bs.modal', this.closed); - }, - methods: { - emitCancel(event) { - this.$emit('cancel', event); - }, - emitSubmit(event) { - this.$emit('submit', event); - }, - opened() { - this.$emit('open'); - }, - closed() { - this.$emit('closed'); - }, - }, -}; -</script> - -<template> - <div :id="id" class="modal fade" tabindex="-1" role="dialog"> - <div :class="modalSizeClass" class="modal-dialog" role="document"> - <div class="modal-content"> - <div class="modal-header"> - <slot name="header"> - <h4 class="modal-title"> - <slot name="title"> {{ headerTitleText }} </slot> - </h4> - <button - :aria-label="s__('Modal|Close')" - type="button" - class="close js-modal-close-action" - data-dismiss="modal" - @click="emitCancel($event)" - > - <span aria-hidden="true">×</span> - </button> - </slot> - </div> - - <div class="modal-body"><slot></slot></div> - - <div class="modal-footer"> - <slot name="footer"> - <button - type="button" - class="btn js-modal-cancel-action qa-modal-cancel-button" - data-dismiss="modal" - @click="emitCancel($event)" - > - {{ s__('Modal|Cancel') }} - </button> - <button - :class="`btn-${footerPrimaryButtonVariant}`" - type="button" - class="btn js-modal-primary-action qa-modal-primary-button" - data-dismiss="modal" - @click="emitSubmit($event)" - > - {{ footerPrimaryButtonText }} - </button> - </slot> - </div> - </div> - </div> - </div> -</template> diff --git a/app/assets/javascripts/vue_shared/components/gl_modal.vue b/app/assets/javascripts/vue_shared/components/gl_modal.vue index 4b91d4c00e3..438851e5ac7 100644 --- a/app/assets/javascripts/vue_shared/components/gl_modal.vue +++ b/app/assets/javascripts/vue_shared/components/gl_modal.vue @@ -1,6 +1,117 @@ <script> -// This file was only introduced to not break master and shall be delete soon. -import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue'; +import $ from 'jquery'; -export default DeprecatedModal2; +const buttonVariants = ['danger', 'primary', 'success', 'warning']; +const sizeVariants = ['sm', 'md', 'lg', 'xl']; + +export default { + name: 'GlModal', + props: { + id: { + type: String, + required: false, + default: null, + }, + modalSize: { + type: String, + required: false, + default: 'md', + validator: value => sizeVariants.includes(value), + }, + headerTitleText: { + type: String, + required: false, + default: '', + }, + footerPrimaryButtonVariant: { + type: String, + required: false, + default: 'primary', + validator: value => buttonVariants.includes(value), + }, + footerPrimaryButtonText: { + type: String, + required: false, + default: '', + }, + }, + computed: { + modalSizeClass() { + return this.modalSize === 'md' ? '' : `modal-${this.modalSize}`; + }, + }, + mounted() { + $(this.$el) + .on('shown.bs.modal', this.opened) + .on('hidden.bs.modal', this.closed); + }, + beforeDestroy() { + $(this.$el) + .off('shown.bs.modal', this.opened) + .off('hidden.bs.modal', this.closed); + }, + methods: { + emitCancel(event) { + this.$emit('cancel', event); + }, + emitSubmit(event) { + this.$emit('submit', event); + }, + opened() { + this.$emit('open'); + }, + closed() { + this.$emit('closed'); + }, + }, +}; </script> + +<template> + <div :id="id" class="modal fade" tabindex="-1" role="dialog"> + <div :class="modalSizeClass" class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <slot name="header"> + <h4 class="modal-title"> + <slot name="title"> {{ headerTitleText }} </slot> + </h4> + <button + :aria-label="s__('Modal|Close')" + type="button" + class="close js-modal-close-action" + data-dismiss="modal" + @click="emitCancel($event)" + > + <span aria-hidden="true">×</span> + </button> + </slot> + </div> + + <div class="modal-body"><slot></slot></div> + + <div class="modal-footer"> + <slot name="footer"> + <button + type="button" + class="btn js-modal-cancel-action qa-modal-cancel-button" + data-dismiss="modal" + @click="emitCancel($event)" + > + {{ s__('Modal|Cancel') }} + </button> + <button + :class="`btn-${footerPrimaryButtonVariant}`" + type="button" + class="btn js-modal-primary-action qa-modal-primary-button" + data-dismiss="modal" + @click="emitSubmit($event)" + > + {{ footerPrimaryButtonText }} + </button> + </slot> + </div> + </div> + </div> + </div> +</template> diff --git a/app/assets/javascripts/vue_shared/components/pagination/table_pagination.vue b/app/assets/javascripts/vue_shared/components/pagination/table_pagination.vue index e89638130f5..1e2d4ffa7e3 100644 --- a/app/assets/javascripts/vue_shared/components/pagination/table_pagination.vue +++ b/app/assets/javascripts/vue_shared/components/pagination/table_pagination.vue @@ -27,7 +27,8 @@ export default { /** pageInfo will come from the headers of the API call - there should be a function that constructs the pageInfo for this component + in the `.then` clause of the VueResource API call + there should be a function that contructs the pageInfo for this component This is an example: diff --git a/app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue b/app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue index 478e44d104c..7f0345c7ec0 100644 --- a/app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue +++ b/app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue @@ -52,7 +52,7 @@ export default { this.$emit('projectClicked', project); }, isSelected(project) { - return Boolean(_.find(this.selectedProjects, { id: project.id })); + return Boolean(_.findWhere(this.selectedProjects, { id: project.id })); }, onInput: _.debounce(function debouncedOnInput() { this.$emit('searched', this.searchQuery); diff --git a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js new file mode 100644 index 00000000000..754025207c8 --- /dev/null +++ b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js @@ -0,0 +1,34 @@ +import Vue from 'vue'; +import VueResource from 'vue-resource'; +import csrf from '../lib/utils/csrf'; + +Vue.use(VueResource); + +// Maintain a global counter for active requests +// see: spec/support/wait_for_requests.rb +Vue.http.interceptors.push((request, next) => { + window.activeVueResources = window.activeVueResources || 0; + window.activeVueResources += 1; + + next(() => { + window.activeVueResources -= 1; + }); +}); + +// Inject CSRF token and parse headers. +// New Vue Resource version uses Headers, we are expecting a plain object to render pagination +// and polling. +Vue.http.interceptors.push((request, next) => { + request.headers.set(csrf.headerKey, csrf.token); + + next(response => { + // Headers object has a `forEach` property that iterates through all values. + const headers = {}; + + response.headers.forEach((value, key) => { + headers[key] = value; + }); + // eslint-disable-next-line no-param-reassign + response.headers = headers; + }); +}); |