diff options
Diffstat (limited to 'app')
102 files changed, 462 insertions, 798 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; + }); +}); diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index c4a0ed63080..82b4ec750ff 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -28,7 +28,6 @@ @import 'framework/issue_box'; @import 'framework/lists'; @import 'framework/logo'; -@import 'framework/job_log'; @import 'framework/markdown_area'; @import 'framework/media_object'; @import 'framework/modal'; diff --git a/app/assets/stylesheets/framework/contextual_sidebar.scss b/app/assets/stylesheets/framework/contextual_sidebar.scss index 05ae9b1d1ed..3238b01c6c0 100644 --- a/app/assets/stylesheets/framework/contextual_sidebar.scss +++ b/app/assets/stylesheets/framework/contextual_sidebar.scss @@ -141,7 +141,7 @@ } .sidebar-top-level-items > li > a { - min-height: 45px; + min-height: 44px; } .fly-out-top-item { diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss index 13a2a74fdab..7e7b08797b2 100644 --- a/app/assets/stylesheets/framework/flash.scss +++ b/app/assets/stylesheets/framework/flash.scss @@ -12,7 +12,7 @@ $notification-box-shadow-color: rgba(0, 0, 0, 0.25); position: sticky; position: -webkit-sticky; top: $flash-container-top; - z-index: 251; + z-index: 200; .flash-content { box-shadow: 0 2px 4px 0 $notification-box-shadow-color; diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index 2289f0a7011..81ccea1e01f 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -245,7 +245,7 @@ .select2-highlighted { .group-result { .group-path { - color: $gray-800; + color: $white-light; } } } diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index b9cfcf6ce5c..43d0e51e4c9 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -171,7 +171,7 @@ position: absolute; top: $gl-padding; bottom: $gl-padding; - left: map-get($spacers, 2) - px-to-rem(1px); + left: map-get($spacers, 2) - 1px; } &-row { @@ -187,7 +187,7 @@ * 2px extra is to give a little more height than needed * to hide timeline line before/after the element starts/ends */ - height: map-get($spacers, 4) + px-to-rem(2px); + height: map-get($spacers, 4) + 2px; z-index: 1; position: relative; top: -3px; diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 7577112cb0e..801e9e7204c 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -877,16 +877,11 @@ pre.light-well { flex-direction: column; // Disable Flexbox for admin page - &.admin-projects, - &.group-settings-projects { + &.admin-projects { display: block; .project-row { display: block; - - .description > p { - margin-bottom: 0; - } } } diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index 80c0a0d88a8..7012bfcefe3 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -78,8 +78,8 @@ class Dashboard::TodosController < Dashboard::ApplicationController def todos_counts { - count: current_user.todos_pending_count, - done_count: current_user.todos_done_count + count: number_with_delimiter(current_user.todos_pending_count), + done_count: number_with_delimiter(current_user.todos_done_count) } end diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb index c418b11ab13..72f830fc9a1 100644 --- a/app/controllers/import/github_controller.rb +++ b/app/controllers/import/github_controller.rb @@ -2,7 +2,6 @@ class Import::GithubController < Import::BaseController include ImportHelper - include ActionView::Helpers::SanitizeHelper before_action :verify_import_enabled before_action :provider_auth, only: [:status, :realtime_changes, :create] @@ -56,7 +55,7 @@ class Import::GithubController < Import::BaseController def realtime_changes Gitlab::PollingInterval.set_header(response, interval: 3_000) - render json: already_added_projects.to_json(only: [:id], methods: [:import_status]) + render json: find_jobs(provider) end private @@ -83,7 +82,7 @@ class Import::GithubController < Import::BaseController end def already_added_projects - @already_added_projects ||= filtered(find_already_added_projects(provider)) + @already_added_projects ||= find_already_added_projects(provider) end def already_added_project_names @@ -105,7 +104,7 @@ class Import::GithubController < Import::BaseController end def client_repos - @client_repos ||= filtered(client.repos) + @client_repos ||= client.repos end def verify_import_enabled @@ -186,20 +185,6 @@ class Import::GithubController < Import::BaseController def extra_import_params {} end - - def sanitized_filter_param - @filter ||= sanitize(params[:filter]) - end - - def filter_attribute - :name - end - - def filtered(collection) - return collection unless sanitized_filter_param - - collection.select { |item| item[filter_attribute].include?(sanitized_filter_param) } - end end Import::GithubController.prepend_if_ee('EE::Import::GithubController') diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index 50399a8cfbb..da8a371acaa 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -8,37 +8,10 @@ class Projects::ArtifactsController < Projects::ApplicationController layout 'project' before_action :authorize_read_build! before_action :authorize_update_build!, only: [:keep] - before_action :authorize_destroy_artifacts!, only: [:destroy] before_action :extract_ref_name_and_path - before_action :validate_artifacts!, except: [:index, :download, :destroy] + before_action :validate_artifacts!, except: [:download] before_action :entry, only: [:file] - MAX_PER_PAGE = 20 - - def index - # Loading artifacts is very expensive in projects with a lot of artifacts. - # This feature flag prevents a DOS attack vector. - # It should be removed only after resolving the underlying performance - # issues: https://gitlab.com/gitlab-org/gitlab/issues/32281 - return head :no_content unless Feature.enabled?(:artifacts_management_page, @project) - - finder = ArtifactsFinder.new(@project, artifacts_params) - all_artifacts = finder.execute - - @artifacts = all_artifacts.page(params[:page]).per(MAX_PER_PAGE) - @total_size = all_artifacts.total_size - end - - def destroy - notice = if artifact.destroy - _('Artifact was successfully deleted.') - else - _('Artifact could not be deleted.') - end - - redirect_to project_artifacts_path(@project), status: :see_other, notice: notice - end - def download return render_404 unless artifacts_file @@ -101,10 +74,6 @@ class Projects::ArtifactsController < Projects::ApplicationController @ref_name, @path = extract_ref(params[:ref_name_and_path]) end - def artifacts_params - params.permit(:sort) - end - def validate_artifacts! render_404 unless build&.artifacts? end @@ -116,11 +85,6 @@ class Projects::ArtifactsController < Projects::ApplicationController end end - def artifact - @artifact ||= - project.job_artifacts.find(params[:id]) - end - def build_from_id project.builds.find_by_id(params[:job_id]) if params[:job_id] end diff --git a/app/controllers/projects/registry/tags_controller.rb b/app/controllers/projects/registry/tags_controller.rb index b5ee0ba9beb..54e2faa2dd7 100644 --- a/app/controllers/projects/registry/tags_controller.rb +++ b/app/controllers/projects/registry/tags_controller.rb @@ -19,12 +19,14 @@ module Projects end def destroy - result = Projects::ContainerRepository::DeleteTagsService - .new(image.project, current_user, tags: [params[:id]]) - .execute(image) - - respond_to do |format| - format.json { head(result[:status] == :success ? :ok : bad_request) } + if tag.delete + respond_to do |format| + format.json { head :no_content } + end + else + respond_to do |format| + format.json { head :bad_request } + end end end @@ -40,12 +42,21 @@ module Projects return end - result = Projects::ContainerRepository::DeleteTagsService - .new(image.project, current_user, tags: tag_names) - .execute(image) + @tags = tag_names.map { |tag_name| image.tag(tag_name) } + unless @tags.all? { |tag| tag.valid_name? } + head :bad_request + return + end + + success_count = 0 + @tags.each do |tag| + if tag.delete + success_count += 1 + end + end respond_to do |format| - format.json { head(result[:status] == :success ? :no_content : :bad_request) } + format.json { head(success_count == @tags.size ? :no_content : :bad_request) } end end @@ -59,6 +70,10 @@ module Projects @image ||= project.container_repositories .find(params[:repository_id]) end + + def tag + @tag ||= image.tag(params[:id]) + end end end end diff --git a/app/controllers/projects/templates_controller.rb b/app/controllers/projects/templates_controller.rb index 95739f96d39..f987033a26c 100644 --- a/app/controllers/projects/templates_controller.rb +++ b/app/controllers/projects/templates_controller.rb @@ -13,14 +13,6 @@ class Projects::TemplatesController < Projects::ApplicationController end end - def names - templates = @template_type.dropdown_names(project) - - respond_to do |format| - format.json { render json: templates } - end - end - private # User must have: diff --git a/app/finders/artifacts_finder.rb b/app/finders/artifacts_finder.rb deleted file mode 100644 index 81c5168d782..00000000000 --- a/app/finders/artifacts_finder.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -class ArtifactsFinder - def initialize(project, params = {}) - @project = project - @params = params - end - - def execute - artifacts = @project.job_artifacts - - sort(artifacts) - end - - private - - def sort_key - @params[:sort] || 'created_desc' - end - - def sort(artifacts) - artifacts.order_by(sort_key) - end -end diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 24614b5030c..014523b54cb 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -272,7 +272,7 @@ module IssuablesHelper markdownPreviewPath: preview_markdown_path(parent), markdownDocsPath: help_page_path('user/markdown'), lockVersion: issuable.lock_version, - issuableTemplateNamesPath: template_names_path(parent, issuable), + issuableTemplates: issuable_templates(issuable), initialTitleHtml: markdown_field(issuable, :title), initialTitleText: issuable.title, initialDescriptionHtml: markdown_field(issuable, :description), @@ -429,12 +429,6 @@ module IssuablesHelper end end - def template_names_path(parent, issuable) - return '' unless parent.is_a?(Project) - - project_template_names_path(parent, template_type: issuable.class.name.underscore) - end - def issuable_sidebar_options(issuable) { endpoint: "#{issuable[:issuable_json_path]}?serializer=sidebar_extras", diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index c65611b7efc..0f4e5adca6c 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -34,15 +34,15 @@ module SearchHelper from: from, to: to, count: count, - scope: search_entries_scope_label(scope, count), + scope: search_entries_info_label(scope, count), term: term } end - def search_entries_scope_label(scope, count) + def search_entries_info_label(scope, count) case scope - when 'blobs' - ns_('SearchResults|code result', 'SearchResults|code results', count) + when 'blobs', 'snippet_blobs', 'wiki_blobs' + ns_('SearchResults|result', 'SearchResults|results', count) when 'commits' ns_('SearchResults|commit', 'SearchResults|commits', count) when 'issues' @@ -55,14 +55,10 @@ module SearchHelper ns_('SearchResults|comment', 'SearchResults|comments', count) when 'projects' ns_('SearchResults|project', 'SearchResults|projects', count) - when 'snippet_blobs' - ns_('SearchResults|snippet result', 'SearchResults|snippet results', count) when 'snippet_titles' ns_('SearchResults|snippet', 'SearchResults|snippets', count) when 'users' ns_('SearchResults|user', 'SearchResults|users', count) - when 'wiki_blobs' - ns_('SearchResults|wiki result', 'SearchResults|wiki results', count) else raise "Unrecognized search scope '#{scope}'" end @@ -76,13 +72,6 @@ module SearchHelper end end - def search_entries_empty_message(scope, term) - (s_("SearchResults|We couldn't find any %{scope} matching %{term}") % { - scope: search_entries_scope_label(scope, 0), - term: "<code>#{term}</code>" - }).html_safe - end - def find_project_for_result_blob(projects, result) @project end diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb index 33f3bb0b749..d680e10525d 100644 --- a/app/helpers/sorting_helper.rb +++ b/app/helpers/sorting_helper.rb @@ -28,9 +28,7 @@ module SortingHelper sort_value_priority => sort_title_priority, sort_value_upvotes => sort_title_upvotes, sort_value_contacted_date => sort_title_contacted_date, - sort_value_relative_position => sort_title_relative_position, - sort_value_size => sort_title_size, - sort_value_expire_date => sort_title_expire_date + sort_value_relative_position => sort_title_relative_position } end @@ -408,14 +406,6 @@ module SortingHelper s_('SortOptions|Manual') end - def sort_title_size - s_('SortOptions|Size') - end - - def sort_title_expire_date - s_('SortOptions|Expired date') - end - # Values. def sort_value_access_level_asc 'access_level_asc' @@ -568,12 +558,4 @@ module SortingHelper def sort_value_relative_position 'relative_position' end - - def sort_value_size - 'size_desc' - end - - def sort_value_expire_date - 'expired_asc' - end end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 3ef0ed0ef49..1f8a0373450 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -118,6 +118,8 @@ module Ci scope :eager_load_job_artifacts, -> { includes(:job_artifacts) } + scope :with_artifacts_stored_locally, -> { with_existing_job_artifacts(Ci::JobArtifact.archive.with_files_stored_locally) } + scope :with_archived_trace_stored_locally, -> { with_existing_job_artifacts(Ci::JobArtifact.trace.with_files_stored_locally) } scope :with_artifacts_not_expired, ->() { with_artifacts_archive.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) } scope :with_expired_artifacts, ->() { with_artifacts_archive.where('artifacts_expire_at < ?', Time.now) } scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) } diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb index 62bf2c3ac9c..da2758507ce 100644 --- a/app/models/ci/job_artifact.rb +++ b/app/models/ci/job_artifact.rb @@ -5,7 +5,6 @@ module Ci include AfterCommitQueue include ObjectStorage::BackgroundMove include UpdateProjectStatistics - include Sortable extend Gitlab::Ci::Model NotSupportedAdapterError = Class.new(StandardError) @@ -65,7 +64,6 @@ module Ci after_save :update_file_store, if: :saved_change_to_file? scope :with_files_stored_locally, -> { where(file_store: [nil, ::JobArtifactUploader::Store::LOCAL]) } - scope :with_files_stored_remotely, -> { where(file_store: ::JobArtifactUploader::Store::REMOTE) } scope :with_file_types, -> (file_types) do types = self.file_types.select { |file_type| file_types.include?(file_type) }.values @@ -145,10 +143,6 @@ module Ci self.update_column(:file_store, file.object_store) end - def self.total_size - self.sum(:size) - end - def self.artifacts_size_for(project) self.where(project: project).sum(:size) end diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index 231cadfae05..6a5b98a4676 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -39,7 +39,7 @@ module Clusters def self.has_one_cluster_application(name) # rubocop:disable Naming/PredicateName application = APPLICATIONS[name.to_s] - has_one application.association_name, class_name: application.to_s, inverse_of: :cluster # rubocop:disable Rails/ReflectionClassName + has_one application.association_name, class_name: application.to_s # rubocop:disable Rails/ReflectionClassName end has_one_cluster_application :helm diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb index f73aa2705b7..60b11ad9356 100644 --- a/app/models/lfs_object.rb +++ b/app/models/lfs_object.rb @@ -9,7 +9,6 @@ class LfsObject < ApplicationRecord has_many :projects, -> { distinct }, through: :lfs_objects_projects scope :with_files_stored_locally, -> { where(file_store: LfsObjectUploader::Store::LOCAL) } - scope :with_files_stored_remotely, -> { where(file_store: LfsObjectUploader::Store::REMOTE) } validates :oid, presence: true, uniqueness: true diff --git a/app/models/project.rb b/app/models/project.rb index 18afccf7ddc..7c065db9829 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -273,7 +273,6 @@ class Project < ApplicationRecord has_many :builds, class_name: 'Ci::Build', inverse_of: :project, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :build_trace_section_names, class_name: 'Ci::BuildTraceSectionName' has_many :build_trace_chunks, class_name: 'Ci::BuildTraceChunk', through: :builds, source: :trace_chunks - has_many :job_artifacts, class_name: 'Ci::JobArtifact' has_many :runner_projects, class_name: 'Ci::RunnerProject', inverse_of: :project has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner' has_many :variables, class_name: 'Ci::Variable' diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 86139c62f68..a76970bfa2a 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -298,7 +298,7 @@ class JiraService < IssueTrackerService title: title, status: status, icon: { - title: 'GitLab', url16x16: asset_url(Gitlab::Favicon.main, host: gitlab_config.base_url) + title: 'GitLab', url16x16: asset_url(Gitlab::Favicon.main, host: gitlab_config.url) } } } diff --git a/app/models/release.rb b/app/models/release.rb index 9117a475ee9..cd63b4d5fef 100644 --- a/app/models/release.rb +++ b/app/models/release.rb @@ -22,6 +22,7 @@ class Release < ApplicationRecord accepts_nested_attributes_for :links, allow_destroy: true validates :description, :project, :tag, presence: true + validates :name, presence: true, on: :create validates_associated :milestone_releases, message: -> (_, obj) { obj[:value].map(&:errors).map(&:full_messages).join(",") } scope :sorted, -> { order(released_at: :desc) } diff --git a/app/models/repository.rb b/app/models/repository.rb index 96b1b55e2b1..f084a314392 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -133,28 +133,18 @@ class Repository end end - # the opts are: - # - :path - # - :limit - # - :offset - # - :skip_merges - # - :after - # - :before - # - :all - # - :first_parent - def commits(ref = nil, opts = {}) + def commits(ref = nil, path: nil, limit: nil, offset: nil, skip_merges: false, after: nil, before: nil, all: nil) options = { repo: raw_repository, ref: ref, - path: opts[:path], - follow: Array(opts[:path]).length == 1, - limit: opts[:limit], - offset: opts[:offset], - skip_merges: !!opts[:skip_merges], - after: opts[:after], - before: opts[:before], - all: !!opts[:all], - first_parent: !!opts[:first_parent] + path: path, + limit: limit, + offset: offset, + after: after, + before: before, + follow: Array(path).length == 1, + skip_merges: skip_merges, + all: all } commits = Gitlab::Git::Commit.where(options) diff --git a/app/presenters/project_presenter.rb b/app/presenters/project_presenter.rb index 6d370f6241c..6c300cd8be1 100644 --- a/app/presenters/project_presenter.rb +++ b/app/presenters/project_presenter.rb @@ -51,8 +51,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated new_file_anchor_data, readme_anchor_data, changelog_anchor_data, - contribution_guide_anchor_data, - gitlab_ci_anchor_data + contribution_guide_anchor_data ].compact.reject { |item| item.is_link } end diff --git a/app/services/import_export_clean_up_service.rb b/app/services/import_export_clean_up_service.rb index 66ac7dac4ca..3ecb51b60d0 100644 --- a/app/services/import_export_clean_up_service.rb +++ b/app/services/import_export_clean_up_service.rb @@ -12,20 +12,16 @@ class ImportExportCleanUpService def execute Gitlab::Metrics.measure(:import_export_clean_up) do - execute_cleanup + clean_up_export_object_files + + break unless File.directory?(path) + + clean_up_export_files end end private - def execute_cleanup - clean_up_export_object_files - ensure - # We don't want a failure in cleaning up object storage from - # blocking us from cleaning up temporary storage. - clean_up_export_files if File.directory?(path) - end - def clean_up_export_files Gitlab::Popen.popen(%W(find #{path} -not -path #{path} -mmin +#{mmin} -delete)) end diff --git a/app/services/projects/container_repository/cleanup_tags_service.rb b/app/services/projects/container_repository/cleanup_tags_service.rb index 1b880a7aab1..d1d9b9f22e8 100644 --- a/app/services/projects/container_repository/cleanup_tags_service.rb +++ b/app/services/projects/container_repository/cleanup_tags_service.rb @@ -40,7 +40,7 @@ module Projects return unless tags.count == other_tags.count # delete all tags - tags.map(&:unsafe_delete) + tags.map(&:delete) end def group_by_digest(tags) diff --git a/app/services/projects/container_repository/delete_tags_service.rb b/app/services/projects/container_repository/delete_tags_service.rb deleted file mode 100644 index 21dc1621e5c..00000000000 --- a/app/services/projects/container_repository/delete_tags_service.rb +++ /dev/null @@ -1,66 +0,0 @@ -# frozen_string_literal: true - -module Projects - module ContainerRepository - class DeleteTagsService < BaseService - def execute(container_repository) - return error('access denied') unless can?(current_user, :destroy_container_image, project) - - tag_names = params[:tags] - return error('not tags specified') if tag_names.blank? - - if can_use? - smart_delete(container_repository, tag_names) - else - unsafe_delete(container_repository, tag_names) - end - end - - private - - def unsafe_delete(container_repository, tag_names) - deleted_tags = tag_names.select do |tag_name| - container_repository.tag(tag_name).unsafe_delete - end - - return error('could not delete tags') if deleted_tags.empty? - - success(deleted: deleted_tags) - end - - # Replace a tag on the registry with a dummy tag. - # This is a hack as the registry doesn't support deleting individual - # tags. This code effectively pushes a dummy image and assigns the tag to it. - # This way when the tag is deleted only the dummy image is affected. - # See https://gitlab.com/gitlab-org/gitlab-ce/issues/21405 for a discussion - def smart_delete(container_repository, tag_names) - # generates the blobs for the dummy image - dummy_manifest = container_repository.client.generate_empty_manifest(container_repository.path) - - # update the manifests of the tags with the new dummy image - tag_digests = tag_names.map do |name| - container_repository.client.put_tag(container_repository.path, name, dummy_manifest) - end - - # make sure the digests are the same (it should always be) - tag_digests.uniq! - - # rubocop: disable CodeReuse/ActiveRecord - Gitlab::Sentry.track_exception(ArgumentError.new('multiple tag digests')) if tag_digests.many? - - # deletes the dummy image - # all created tag digests are the same since they all have the same dummy image. - # a single delete is sufficient to remove all tags with it - if container_repository.client.delete_repository_tag(container_repository.path, tag_digests.first) - success(deleted: tag_names) - else - error('could not delete tags') - end - end - - def can_use? - Feature.enabled?(:container_registry_smart_delete, project, default_enabled: true) - end - end - end -end diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 08e668e8623..bb0d62a70c0 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -1,20 +1,8 @@ -- page_title _('Projects') +- page_title "Projects" - params[:visibility_level] ||= [] .top-area.scrolling-tabs-container.inner-page-scroll-tabs - %ul.nav-links.nav.nav-tabs - - opts = params[:visibility_level].present? ? {} : { page: admin_projects_path } - = nav_link(opts) do - = link_to _('All'), admin_projects_path - - = nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::PRIVATE.to_s) }) do - = link_to _('Private'), admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PRIVATE) - = nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::INTERNAL.to_s) }) do - = link_to _('Internal'), admin_projects_path(visibility_level: Gitlab::VisibilityLevel::INTERNAL) - = nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::PUBLIC.to_s) }) do - = link_to _('Public'), admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PUBLIC) - - .nav-controls + .prepend-top-default .search-holder = render 'shared/projects/search_form', autofocus: true, admin_view: true .dropdown @@ -34,4 +22,20 @@ New Project = button_tag "Search", class: "btn btn-primary btn-search hide" + %ul.nav-links.nav.nav-tabs + - opts = params[:visibility_level].present? ? {} : { page: admin_projects_path } + = nav_link(opts) do + = link_to admin_projects_path do + All + + = nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::PRIVATE.to_s) }) do + = link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PRIVATE) do + Private + = nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::INTERNAL.to_s) }) do + = link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::INTERNAL) do + Internal + = nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::PUBLIC.to_s) }) do + = link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PUBLIC) do + Public + = render 'projects' diff --git a/app/views/ci/runner/_how_to_setup_runner_automatically.html.haml b/app/views/ci/runner/_how_to_setup_runner_automatically.html.haml deleted file mode 100644 index 58d2ef5d5e6..00000000000 --- a/app/views/ci/runner/_how_to_setup_runner_automatically.html.haml +++ /dev/null @@ -1,22 +0,0 @@ -.append-bottom-10 - %h4= _('Set up a %{type} Runner automatically') % { type: type } - -%p - - link_to_help_page = link_to(_('Learn more about Kubernetes'), - help_page_path('user/project/clusters/index'), - target: '_blank', - rel: 'noopener noreferrer') - - = _('You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page } - -%ol - %li - = _('Click the button below to begin the install process by navigating to the Kubernetes page') - %li - = _('Select an existing Kubernetes cluster or create a new one') - %li - = _('From the Kubernetes cluster details view, install Runner from the applications list') - -= link_to _('Install Runner on Kubernetes'), - clusters_path, - class: 'btn btn-info' diff --git a/app/views/clusters/clusters/show.html.haml b/app/views/clusters/clusters/show.html.haml index cccba48624b..00fdd5e9562 100644 --- a/app/views/clusters/clusters/show.html.haml +++ b/app/views/clusters/clusters/show.html.haml @@ -3,7 +3,6 @@ - breadcrumb_title @cluster.name - page_title _('Kubernetes Cluster') - manage_prometheus_path = edit_project_service_path(@cluster.project, 'prometheus') if @project -- cluster_environments_path = clusterable.environments_cluster_path(@cluster) - expanded = expanded_by_default? @@ -17,7 +16,7 @@ install_jupyter_path: clusterable.install_applications_cluster_path(@cluster, :jupyter), install_knative_path: clusterable.install_applications_cluster_path(@cluster, :knative), update_knative_path: clusterable.update_applications_cluster_path(@cluster, :knative), - cluster_environments_path: cluster_environments_path, + cluster_environments_path: clusterable.environments_cluster_path(@cluster), toggle_status: @cluster.enabled? ? 'true': 'false', has_rbac: has_rbac_enabled?(@cluster) ? 'true': 'false', cluster_type: @cluster.cluster_type, @@ -38,7 +37,7 @@ %h4= @cluster.name = render 'banner' - - if cluster_environments_path.present? - = render_if_exists 'clusters/clusters/group_cluster_environments', expanded: expanded - - else + = render_if_exists 'clusters/clusters/group_cluster_environments', expanded: expanded + + - unless Gitlab.ee? = render 'configure', expanded: expanded diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml index 8b01e54474a..ba186875a86 100644 --- a/app/views/groups/projects.html.haml +++ b/app/views/groups/projects.html.haml @@ -8,38 +8,21 @@ .controls = link_to new_project_path(namespace_id: @group.id), class: "btn btn-sm btn-success" do New project - %ul.projects-list.content-list.group-settings-projects + %ul.content-list - @projects.each do |project| - %li.project-row{ class: ('no-description' if project.description.blank?) } - .controls - = link_to _('Members'), project_project_members_path(project), id: "edit_#{dom_id(project)}", class: "btn" - = link_to _('Edit'), edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn" - = link_to _('Remove'), project, data: { confirm: remove_project_message(project)}, method: :delete, class: "btn btn-remove" - - .stats - %span.badge.badge-pill - = storage_counter(project.statistics&.storage_size) + %li + .list-item-name + %span{ class: visibility_level_color(project.visibility_level) } + = visibility_level_icon(project.visibility_level) + %strong= link_to project.full_name, project + .float-right - if project.archived %span.badge.badge-warning archived - - .title - = link_to(project_path(project)) do - .dash-project-avatar - .avatar-container.rect-avatar.s40 - = project_icon(project, alt: '', class: 'avatar project-avatar s40', width: 40, height: 40) - %span.project-full-name - %span.namespace-name - - if project.namespace - = project.namespace.human_name - \/ - %span.project-name - = project.name - %span{ class: visibility_level_color(project.visibility_level) } - = visibility_level_icon(project.visibility_level) - - - if project.description.present? - .description - = markdown_field(project, :description) + %span.badge.badge-pill + = storage_counter(project.statistics.storage_size) + = link_to 'Members', project_project_members_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-sm" + = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-sm" + = link_to 'Remove', project, data: { confirm: remove_project_message(project)}, method: :delete, class: "btn btn-sm btn-remove" - if @projects.blank? .nothing-here-block This group has no projects yet diff --git a/app/views/groups/runners/_group_runners.html.haml b/app/views/groups/runners/_group_runners.html.haml index f752bc0a702..fd40ec5a984 100644 --- a/app/views/groups/runners/_group_runners.html.haml +++ b/app/views/groups/runners/_group_runners.html.haml @@ -10,10 +10,6 @@ -# Proper policies should be implemented per -# https://gitlab.com/gitlab-org/gitlab-foss/issues/45894 - if can?(current_user, :admin_pipeline, @group) - = render partial: 'ci/runner/how_to_setup_runner_automatically', - locals: { type: 'group', - clusters_path: group_clusters_path(@group) } - %hr = render partial: 'ci/runner/how_to_setup_runner', locals: { registration_token: @group.runners_token, type: 'group', diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 6cdb85456c3..443a73f5cce 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -17,6 +17,4 @@ %div{ class: "#{(container_class unless @no_container)} #{@content_class}" } .content{ id: "content-body" } = render "layouts/flash", extra_flash_class: 'limit-container-width' - - if Gitlab.com? - = render_if_exists "layouts/privacy_policy_update_callout" = yield diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 367b8c1138e..de0c21e7cf6 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -43,10 +43,7 @@ .issuable-meta %ul.controls - - if issue.moved? - %li.issuable-status - = _('CLOSED (MOVED)') - - elsif issue.closed? + - if issue.closed? %li.issuable-status = _('CLOSED') - if issue.assignees.any? diff --git a/app/views/projects/mirrors/_mirror_repos.html.haml b/app/views/projects/mirrors/_mirror_repos.html.haml index 80d2d2afada..104c68919f0 100644 --- a/app/views/projects/mirrors/_mirror_repos.html.haml +++ b/app/views/projects/mirrors/_mirror_repos.html.haml @@ -1,7 +1,7 @@ - expanded = expanded_by_default? - protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|') -%section.settings.project-mirror-settings.js-mirror-settings.no-animate#js-push-remote-settings{ class: ('expanded' if expanded), data: { qa_selector: 'mirroring_repositories_settings_section' } } +%section.settings.project-mirror-settings.js-mirror-settings.no-animate.qa-mirroring-repositories-settings#js-push-remote-settings{ class: ('expanded' if expanded) } .settings-header %h4= _('Mirroring repositories') %button.btn.js-settings-toggle @@ -59,10 +59,10 @@ - if mirror.disabled? = render 'projects/mirrors/disabled_mirror_badge' - if mirror.last_error.present? - .badge.mirror-error-badge{ data: { toggle: 'tooltip', html: 'true', qa_selector: 'mirror_error_badge' }, title: html_escape(mirror.last_error.try(:strip)) }= _('Error') + .badge.mirror-error-badge{ data: { toggle: 'tooltip', html: 'true' }, title: html_escape(mirror.last_error.try(:strip)) }= _('Error') %td .btn-group.mirror-actions-group.pull-right{ role: 'group' } - if mirror.ssh_key_auth? - = clipboard_button(text: mirror.ssh_public_key, class: 'btn btn-default', title: _('Copy SSH public key'), qa_selector: 'copy_public_key_button') + = clipboard_button(text: mirror.ssh_public_key, class: 'btn btn-default', title: _('Copy SSH public key')) = render 'shared/remote_mirror_update_button', remote_mirror: mirror %button.js-delete-mirror.qa-delete-mirror.rspec-delete-mirror.btn.btn-danger{ type: 'button', data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' }, title: _('Remove') }= icon('trash-o') diff --git a/app/views/projects/mirrors/_ssh_host_keys.html.haml b/app/views/projects/mirrors/_ssh_host_keys.html.haml index 3279d3eb251..7762fb4b844 100644 --- a/app/views/projects/mirrors/_ssh_host_keys.html.haml +++ b/app/views/projects/mirrors/_ssh_host_keys.html.haml @@ -3,13 +3,13 @@ - verified_at = mirror.ssh_known_hosts_verified_at .form-group.js-ssh-host-keys-section{ class: ('collapse' unless mirror.ssh_mirror_url?) } - %button.btn.btn-inverted.btn-secondary.inline.js-detect-host-keys.append-right-10{ type: 'button', data: { qa_selector: 'detect_host_keys' } } + %button.btn.btn-inverted.btn-secondary.inline.js-detect-host-keys.append-right-10{ type: 'button' } = icon('spinner spin', class: 'js-spinner d-none') = _('Detect host keys') .fingerprint-ssh-info.js-fingerprint-ssh-info.prepend-top-10.append-bottom-10{ class: ('collapse' unless mirror.ssh_mirror_url?) } %label.label-bold = _('Fingerprints') - .fingerprints-list.js-fingerprints-list{ data: { qa_selector: 'fingerprints_list' } } + .fingerprints-list.js-fingerprints-list - mirror.ssh_known_hosts_fingerprints.each do |fp| %code= fp.fingerprint - if verified_at diff --git a/app/views/projects/runners/_specific_runners.html.haml b/app/views/projects/runners/_specific_runners.html.haml index 4cc67a8f5d8..dc56a515d4c 100644 --- a/app/views/projects/runners/_specific_runners.html.haml +++ b/app/views/projects/runners/_specific_runners.html.haml @@ -2,9 +2,28 @@ = _('Specific Runners') .bs-callout.help-callout - = render partial: 'ci/runner/how_to_setup_runner_automatically', - locals: { type: 'specific', - clusters_path: project_clusters_path(@project) } + .append-bottom-10 + %h4= _('Set up a specific Runner automatically') + + %p + - link_to_help_page = link_to(_('Learn more about Kubernetes'), + help_page_path('user/project/clusters/index'), + target: '_blank', + rel: 'noopener noreferrer') + + = _('You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page } + + %ol + %li + = _('Click the button below to begin the install process by navigating to the Kubernetes page') + %li + = _('Select an existing Kubernetes cluster or create a new one') + %li + = _('From the Kubernetes cluster details view, install Runner from the applications list') + + = link_to _('Install Runner on Kubernetes'), + project_clusters_path(@project), + class: 'btn btn-info' %hr = render partial: 'ci/runner/how_to_setup_runner', locals: { registration_token: @project.runners_token, diff --git a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml index 6702786fdb3..1d5d90593ae 100644 --- a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml +++ b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml @@ -46,12 +46,12 @@ = form.radio_button :deploy_strategy, 'timed_incremental', class: 'form-check-input' = form.label :deploy_strategy_timed_incremental, class: 'form-check-label' do = s_('CICD|Continuous deployment to production using timed incremental rollout') - = link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'timed-incremental-rollout-to-production-premium'), target: '_blank' + = link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'timed-incremental-rollout-to-production'), target: '_blank' .form-check = form.radio_button :deploy_strategy, 'manual', class: 'form-check-input' = form.label :deploy_strategy_manual, class: 'form-check-label' do = s_('CICD|Automatic deployment to staging, manual deployment to production') - = link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'incremental-rollout-to-production-premium'), target: '_blank' + = link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'incremental-rollout-to-production'), target: '_blank' = f.submit _('Save changes'), class: "btn btn-success prepend-top-15", data: { qa_selector: 'save_changes_button' } diff --git a/app/views/search/results/_empty.html.haml b/app/views/search/results/_empty.html.haml index 6c7c6de1178..9d15995bb51 100644 --- a/app/views/search/results/_empty.html.haml +++ b/app/views/search/results/_empty.html.haml @@ -2,4 +2,5 @@ .search_glyph %h4 = icon('search') - = search_entries_empty_message(@scope, @search_term) + = _("We couldn't find any results matching") + %code= @search_term diff --git a/app/views/shared/_remote_mirror_update_button.html.haml b/app/views/shared/_remote_mirror_update_button.html.haml index 54bd4ba04a0..4b39c8b06e9 100644 --- a/app/views/shared/_remote_mirror_update_button.html.haml +++ b/app/views/shared/_remote_mirror_update_button.html.haml @@ -1,5 +1,5 @@ - if remote_mirror.update_in_progress? - %button.btn.disabled{ type: 'button', data: { toggle: 'tooltip', container: 'body', qa_selector: 'updating_button' }, title: _('Updating') } + %button.btn.disabled{ type: 'button', data: { toggle: 'tooltip', container: 'body' }, title: _('Updating') } = icon("refresh spin") - elsif remote_mirror.enabled? = link_to update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: "btn qa-update-now-button rspec-update-now-button", data: { toggle: 'tooltip', container: 'body' }, title: _('Update now') do |