diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-26 18:10:29 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-26 18:10:29 +0300 |
commit | c82ca12a1c5a359325cb45aaf01b483d1fa0efcb (patch) | |
tree | 86bba20fccbaf79f3277ffcba125201492f3e92b /app/assets/javascripts | |
parent | ff579119e2ecf2608370a1f24c4d791d28f269d9 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts')
15 files changed, 309 insertions, 173 deletions
diff --git a/app/assets/javascripts/alert_management/components/alert_details.vue b/app/assets/javascripts/alert_management/components/alert_details.vue index b54d39e67d4..c1ebd234088 100644 --- a/app/assets/javascripts/alert_management/components/alert_details.vue +++ b/app/assets/javascripts/alert_management/components/alert_details.vue @@ -43,16 +43,16 @@ export default { tabsConfig: [ { id: 'overview', - title: s__('AlertManagement|Overview'), - }, - { - id: 'fullDetails', title: s__('AlertManagement|Alert details'), }, { id: 'metrics', title: s__('AlertManagement|Metrics'), }, + { + id: 'activity', + title: s__('AlertManagement|Activity feed'), + }, ], components: { GlBadge, @@ -331,18 +331,9 @@ export default { </div> <div class="gl-pl-2" data-testid="runbook">{{ alert.runbook }}</div> </div> - <template> - <div v-if="alert.notes.nodes" class="issuable-discussion py-5"> - <ul class="notes main-notes-list timeline"> - <system-note v-for="note in alert.notes.nodes" :key="note.id" :note="note" /> - </ul> - </div> - </template> - </gl-tab> - <gl-tab :data-testid="$options.tabsConfig[1].id" :title="$options.tabsConfig[1].title"> <gl-table class="alert-management-details-table" - :items="[{ key: 'Value', ...alert }]" + :items="[{ 'Full Alert Payload': 'Value', ...alert }]" :show-empty="true" :busy="loading" stacked @@ -355,9 +346,16 @@ export default { </template> </gl-table> </gl-tab> - <gl-tab :data-testid="$options.tabsConfig[2].id" :title="$options.tabsConfig[2].title"> + <gl-tab :data-testid="$options.tabsConfig[1].id" :title="$options.tabsConfig[1].title"> <alert-metrics :dashboard-url="alert.metricsDashboardUrl" /> </gl-tab> + <gl-tab :data-testid="$options.tabsConfig[2].id" :title="$options.tabsConfig[2].title"> + <div v-if="alert.notes.nodes.length > 0" class="issuable-discussion"> + <ul class="notes main-notes-list timeline"> + <system-note v-for="note in alert.notes.nodes" :key="note.id" :note="note" /> + </ul> + </div> + </gl-tab> </gl-tabs> <alert-sidebar :alert="alert" diff --git a/app/assets/javascripts/alert_management/components/system_notes/system_note.vue b/app/assets/javascripts/alert_management/components/system_notes/system_note.vue index 7d0885126b8..0b206ce42f4 100644 --- a/app/assets/javascripts/alert_management/components/system_notes/system_note.vue +++ b/app/assets/javascripts/alert_management/components/system_notes/system_note.vue @@ -32,7 +32,7 @@ export default { </script> <template> - <li :id="noteAnchorId" class="timeline-entry note system-note note-wrapper"> + <li :id="noteAnchorId" class="timeline-entry note system-note note-wrapper gl-px-0!"> <div class="timeline-entry-inner"> <div class="timeline-icon" v-html="iconHtml"></div> <div class="timeline-content"> diff --git a/app/assets/javascripts/blob/suggest_web_ide_ci/components/web_ide_alert.vue b/app/assets/javascripts/blob/suggest_web_ide_ci/components/web_ide_alert.vue new file mode 100644 index 00000000000..1308ca53e74 --- /dev/null +++ b/app/assets/javascripts/blob/suggest_web_ide_ci/components/web_ide_alert.vue @@ -0,0 +1,50 @@ +<script> +import { GlAlert, GlButton } from '@gitlab/ui'; +import axios from '~/lib/utils/axios_utils'; + +export default { + components: { + GlAlert, + GlButton, + }, + props: { + dismissEndpoint: { + type: String, + required: true, + }, + featureId: { + type: String, + required: true, + }, + editPath: { + type: String, + required: true, + }, + }, + data() { + return { + showAlert: true, + }; + }, + methods: { + dismissAlert() { + this.showAlert = false; + + return axios.post(this.dismissEndpoint, { + feature_name: this.featureId, + }); + }, + }, +}; +</script> + +<template> + <gl-alert v-if="showAlert" class="gl-mt-5" @dismiss="dismissAlert"> + {{ __('The Web IDE offers advanced syntax highlighting capabilities and more.') }} + <div class="gl-mt-5"> + <gl-button :href="editPath" category="primary" variant="info">{{ + __('Open Web IDE') + }}</gl-button> + </div> + </gl-alert> +</template> diff --git a/app/assets/javascripts/blob/suggest_web_ide_ci/index.js b/app/assets/javascripts/blob/suggest_web_ide_ci/index.js new file mode 100644 index 00000000000..eadf3cd6216 --- /dev/null +++ b/app/assets/javascripts/blob/suggest_web_ide_ci/index.js @@ -0,0 +1,20 @@ +import Vue from 'vue'; +import WebIdeAlert from './components/web_ide_alert.vue'; + +export default el => { + const { dismissEndpoint, featureId, editPath } = el.dataset; + + // eslint-disable-next-line no-new + new Vue({ + el, + render(createElement) { + return createElement(WebIdeAlert, { + props: { + dismissEndpoint, + featureId, + editPath, + }, + }); + }, + }); +}; diff --git a/app/assets/javascripts/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js index 9b9ade28623..89a4b6ec3e3 100644 --- a/app/assets/javascripts/blob_edit/blob_bundle.js +++ b/app/assets/javascripts/blob_edit/blob_bundle.js @@ -7,12 +7,14 @@ import BlobFileDropzone from '../blob/blob_file_dropzone'; import initPopover from '~/blob/suggest_gitlab_ci_yml'; import { disableButtonIfEmptyField, setCookie } from '~/lib/utils/common_utils'; import Tracking from '~/tracking'; +import initWebIdeAlert from '~/blob/suggest_web_ide_ci'; export default () => { const editBlobForm = $('.js-edit-blob-form'); const uploadBlobForm = $('.js-upload-blob-form'); const deleteBlobForm = $('.js-delete-blob-form'); const suggestEl = document.querySelector('.js-suggest-gitlab-ci-yml'); + const alertEl = document.getElementById('js-suggest-web-ide-ci'); if (editBlobForm.length) { const urlRoot = editBlobForm.data('relativeUrlRoot'); @@ -80,4 +82,8 @@ export default () => { }); } } + + if (alertEl) { + initWebIdeAlert(alertEl); + } }; 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 72fdaca7e24..5beb8d2edc5 100644 --- a/app/assets/javascripts/import_projects/components/import_projects_table.vue +++ b/app/assets/javascripts/import_projects/components/import_projects_table.vue @@ -4,20 +4,15 @@ import { mapActions, mapState, mapGetters } from 'vuex'; import { GlButton, GlLoadingIcon } from '@gitlab/ui'; import { __, sprintf } from '~/locale'; import PaginationLinks from '~/vue_shared/components/pagination_links.vue'; -import ImportedProjectTableRow from './imported_project_table_row.vue'; import ProviderRepoTableRow from './provider_repo_table_row.vue'; -import IncompatibleRepoTableRow from './incompatible_repo_table_row.vue'; import PageQueryParamSync from './page_query_param_sync.vue'; -import { isProjectImportable } from '../utils'; const reposFetchThrottleDelay = 1000; export default { name: 'ImportProjectsTable', components: { - ImportedProjectTableRow, ProviderRepoTableRow, - IncompatibleRepoTableRow, PageQueryParamSync, GlLoadingIcon, GlButton, @@ -109,8 +104,6 @@ export default { throttledFetchRepos: throttle(function fetch() { this.fetchRepos(); }, reposFetchThrottleDelay), - - isProjectImportable, }, }; </script> @@ -165,18 +158,11 @@ export default { </thead> <tbody> <template v-for="repo in repositories"> - <incompatible-repo-table-row - v-if="repo.importSource.incompatible" - :key="repo.importSource.id" - :repo="repo" - /> <provider-repo-table-row - v-else-if="isProjectImportable(repo)" - :key="repo.importSource.id" + :key="repo.importSource.providerLink" :repo="repo" :available-namespaces="availableNamespaces" /> - <imported-project-table-row v-else :key="repo.importSource.id" :project="repo" /> </template> </tbody> </table> diff --git a/app/assets/javascripts/import_projects/components/imported_project_table_row.vue b/app/assets/javascripts/import_projects/components/imported_project_table_row.vue deleted file mode 100644 index 50e735b4478..00000000000 --- a/app/assets/javascripts/import_projects/components/imported_project_table_row.vue +++ /dev/null @@ -1,59 +0,0 @@ -<script> -import { GlIcon } from '@gitlab/ui'; -import ImportStatus from './import_status.vue'; -import { STATUSES } from '../constants'; - -export default { - name: 'ImportedProjectTableRow', - components: { - ImportStatus, - GlIcon, - }, - props: { - project: { - type: Object, - required: true, - }, - }, - - computed: { - displayFullPath() { - return this.project.importedProject.fullPath.replace(/^\//, ''); - }, - - isFinished() { - return this.project.importStatus === STATUSES.FINISHED; - }, - }, -}; -</script> - -<template> - <tr class="import-row"> - <td> - <a - :href="project.importSource.providerLink" - rel="noreferrer noopener" - target="_blank" - data-testid="providerLink" - >{{ project.importSource.fullName }} - <gl-icon v-if="project.importSource.providerLink" name="external-link" /> - </a> - </td> - <td data-testid="fullPath">{{ displayFullPath }}</td> - <td> - <import-status :status="project.importStatus" /> - </td> - <td> - <a - v-if="isFinished" - class="btn btn-default" - data-testid="goToProject" - :href="project.importedProject.fullPath" - rel="noreferrer noopener" - target="_blank" - >{{ __('Go to project') }} - </a> - </td> - </tr> -</template> diff --git a/app/assets/javascripts/import_projects/components/incompatible_repo_table_row.vue b/app/assets/javascripts/import_projects/components/incompatible_repo_table_row.vue deleted file mode 100644 index 3140585ccd7..00000000000 --- a/app/assets/javascripts/import_projects/components/incompatible_repo_table_row.vue +++ /dev/null @@ -1,32 +0,0 @@ -<script> -import { GlIcon, GlBadge } from '@gitlab/ui'; - -export default { - components: { - GlBadge, - GlIcon, - }, - props: { - repo: { - type: Object, - required: true, - }, - }, -}; -</script> - -<template> - <tr class="import-row"> - <td> - <a :href="repo.importSource.providerLink" rel="noreferrer noopener" target="_blank" - >{{ repo.importSource.fullName }} - <gl-icon v-if="repo.importSource.providerLink" name="external-link" /> - </a> - </td> - <td></td> - <td></td> - <td> - <gl-badge variant="danger">{{ __('Incompatible project') }}</gl-badge> - </td> - </tr> -</template> diff --git a/app/assets/javascripts/import_projects/components/provider_repo_table_row.vue b/app/assets/javascripts/import_projects/components/provider_repo_table_row.vue index d8cffc6a7d5..18971313dfe 100644 --- a/app/assets/javascripts/import_projects/components/provider_repo_table_row.vue +++ b/app/assets/javascripts/import_projects/components/provider_repo_table_row.vue @@ -1,9 +1,11 @@ <script> import { mapState, mapGetters, mapActions } from 'vuex'; -import { GlIcon } from '@gitlab/ui'; +import { GlIcon, GlBadge } from '@gitlab/ui'; import Select2Select from '~/vue_shared/components/select2_select.vue'; import { __ } from '~/locale'; import ImportStatus from './import_status.vue'; +import { STATUSES } from '../constants'; +import { isProjectImportable, isIncompatible, getImportStatus } from '../utils'; export default { name: 'ProviderRepoTableRow', @@ -11,6 +13,7 @@ export default { Select2Select, ImportStatus, GlIcon, + GlBadge, }, props: { repo: { @@ -27,6 +30,26 @@ export default { ...mapState(['ciCdOnly']), ...mapGetters(['getImportTarget']), + displayFullPath() { + return this.repo.importedProject.fullPath.replace(/^\//, ''); + }, + + isFinished() { + return this.repo.importedProject?.importStatus === STATUSES.FINISHED; + }, + + isImportNotStarted() { + return isProjectImportable(this.repo); + }, + + isIncompatible() { + return isIncompatible(this.repo); + }, + + importStatus() { + return getImportStatus(this.repo); + }, + importTarget() { return this.getImportTarget(this.repo.importSource.id); }, @@ -85,9 +108,9 @@ export default { <gl-icon v-if="repo.importSource.providerLink" name="external-link" /> </a> </td> - <td class="d-flex flex-wrap flex-lg-nowrap"> - <template v-if="repo.target">{{ repo.target }}</template> - <template v-else> + <td class="d-flex flex-wrap flex-lg-nowrap" data-testid="fullPath"> + <template v-if="repo.importSource.target">{{ repo.importSource.target }}</template> + <template v-else-if="isImportNotStarted"> <select2-select v-model="targetNamespaceSelect" :options="select2Options" /> <span class="px-2 import-slash-divider d-flex justify-content-center align-items-center" >/</span @@ -98,18 +121,31 @@ export default { class="form-control import-project-name-input qa-project-path-field" /> </template> + <template v-else-if="repo.importedProject">{{ displayFullPath }}</template> </td> <td> - <import-status :status="repo.importStatus" /> + <import-status :status="importStatus" /> </td> - <td> + <td data-testid="actions"> + <a + v-if="isFinished" + class="btn btn-default" + :href="repo.importedProject.fullPath" + rel="noreferrer noopener" + target="_blank" + >{{ __('Go to project') }} + </a> <button + v-if="isImportNotStarted" type="button" class="qa-import-button btn btn-default" @click="fetchImport(repo.importSource.id)" > {{ importButtonText }} </button> + <gl-badge v-else-if="isIncompatible" variant="danger">{{ + __('Incompatible project') + }}</gl-badge> </td> </tr> </template> diff --git a/app/assets/javascripts/import_projects/store/getters.js b/app/assets/javascripts/import_projects/store/getters.js index 7d529c94d7d..fded478bae2 100644 --- a/app/assets/javascripts/import_projects/store/getters.js +++ b/app/assets/javascripts/import_projects/store/getters.js @@ -1,17 +1,18 @@ import { STATUSES } from '../constants'; +import { isProjectImportable, isIncompatible } from '../utils'; export const isLoading = state => state.isLoadingRepos || state.isLoadingNamespaces; export const isImportingAnyRepo = state => state.repositories.some(repo => - [STATUSES.SCHEDULING, STATUSES.SCHEDULED, STATUSES.STARTED].includes(repo.importStatus), + [STATUSES.SCHEDULING, STATUSES.SCHEDULED, STATUSES.STARTED].includes( + repo.importedProject?.importStatus, + ), ); -export const hasIncompatibleRepos = state => - state.repositories.some(repo => repo.importSource.incompatible); +export const hasIncompatibleRepos = state => state.repositories.some(isIncompatible); -export const hasImportableRepos = state => - state.repositories.some(repo => repo.importStatus === STATUSES.NONE); +export const hasImportableRepos = state => state.repositories.some(isProjectImportable); export const getImportTarget = state => repoId => { if (state.customImportTargets[repoId]) { diff --git a/app/assets/javascripts/import_projects/store/mutations.js b/app/assets/javascripts/import_projects/store/mutations.js index b3dbef896a6..ea719e11d86 100644 --- a/app/assets/javascripts/import_projects/store/mutations.js +++ b/app/assets/javascripts/import_projects/store/mutations.js @@ -11,37 +11,36 @@ export default { state.isLoadingRepos = true; }, - [types.RECEIVE_REPOS_SUCCESS]( - state, - { importedProjects, providerRepos, incompatibleRepos = [] }, - ) { - // Normalizing structure to support legacy backend format - // See https://gitlab.com/gitlab-org/gitlab/-/issues/27370#note_379034091 for details - + [types.RECEIVE_REPOS_SUCCESS](state, repositories) { state.isLoadingRepos = false; - state.repositories = [ - ...importedProjects.map(({ importSource, providerLink, importStatus, ...project }) => ({ - importSource: { - id: `finished-${project.id}`, - fullName: importSource, - sanitizedName: project.name, - providerLink, - }, - importStatus, - importedProject: project, - })), - ...providerRepos.map(project => ({ - importSource: project, - importStatus: STATUSES.NONE, - importedProject: null, - })), - ...incompatibleRepos.map(project => ({ - importSource: { ...project, incompatible: true }, - importStatus: STATUSES.NONE, - importedProject: null, - })), - ]; + if (!Array.isArray(repositories)) { + // Legacy code path, will be removed when all importers will be switched to new pagination format + // https://gitlab.com/gitlab-org/gitlab/-/issues/27370#note_379034091 + state.repositories = [ + ...repositories.importedProjects.map(importedProject => ({ + importSource: { + id: importedProject.id, + fullName: importedProject.importSource, + sanitizedName: importedProject.name, + providerLink: importedProject.providerLink, + }, + importedProject, + })), + ...repositories.providerRepos.map(project => ({ + importSource: project, + importedProject: null, + })), + ...(repositories.incompatibleRepos ?? []).map(project => ({ + importSource: { ...project, incompatible: true }, + importedProject: null, + })), + ]; + + return; + } + + state.repositories = repositories; }, [types.RECEIVE_REPOS_ERROR](state) { @@ -50,31 +49,27 @@ export default { [types.REQUEST_IMPORT](state, { repoId, importTarget }) { const existingRepo = state.repositories.find(r => r.importSource.id === repoId); - existingRepo.importStatus = STATUSES.SCHEDULING; existingRepo.importedProject = { + importStatus: STATUSES.SCHEDULING, fullPath: `/${importTarget.targetNamespace}/${importTarget.newName}`, }; }, [types.RECEIVE_IMPORT_SUCCESS](state, { importedProject, repoId }) { - const { importStatus, ...project } = importedProject; - const existingRepo = state.repositories.find(r => r.importSource.id === repoId); - existingRepo.importStatus = importStatus; - existingRepo.importedProject = project; + existingRepo.importedProject = importedProject; }, [types.RECEIVE_IMPORT_ERROR](state, repoId) { const existingRepo = state.repositories.find(r => r.importSource.id === repoId); - existingRepo.importStatus = STATUSES.NONE; existingRepo.importedProject = null; }, [types.RECEIVE_JOBS_SUCCESS](state, updatedProjects) { updatedProjects.forEach(updatedProject => { const repo = state.repositories.find(p => p.importedProject?.id === updatedProject.id); - if (repo) { - repo.importStatus = updatedProject.importStatus; + if (repo?.importedProject) { + repo.importedProject.importStatus = updatedProject.importStatus; } }); }, diff --git a/app/assets/javascripts/import_projects/utils.js b/app/assets/javascripts/import_projects/utils.js index 02128c72cb0..695b12cbcba 100644 --- a/app/assets/javascripts/import_projects/utils.js +++ b/app/assets/javascripts/import_projects/utils.js @@ -1,6 +1,13 @@ import { STATUSES } from './constants'; -// Will be expanded in future +export function isIncompatible(project) { + return project.importSource.incompatible; +} + +export function getImportStatus(project) { + return project.importedProject?.importStatus ?? STATUSES.NONE; +} + export function isProjectImportable(project) { - return project.importStatus === STATUSES.NONE && !project.importSource.incompatible; + return !isIncompatible(project) && getImportStatus(project) === STATUSES.NONE; } diff --git a/app/assets/javascripts/tooltips/components/tooltips.vue b/app/assets/javascripts/tooltips/components/tooltips.vue new file mode 100644 index 00000000000..4edfddd0f85 --- /dev/null +++ b/app/assets/javascripts/tooltips/components/tooltips.vue @@ -0,0 +1,70 @@ +<script> +import { GlTooltip, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; +import { uniqueId } from 'lodash'; + +const getTooltipTitle = element => { + return element.getAttribute('title') || element.dataset.title; +}; + +const newTooltip = (element, config = {}) => { + const { placement, container, boundary, html, triggers } = element.dataset; + const title = getTooltipTitle(element); + + return { + id: uniqueId('gl-tooltip'), + target: element, + title, + html, + placement, + container, + boundary, + triggers, + disabled: !title, + ...config, + }; +}; + +export default { + components: { + GlTooltip, + }, + directives: { + SafeHtml, + }, + data() { + return { + tooltips: [], + }; + }, + methods: { + addTooltips(elements, config) { + const newTooltips = elements + .filter(element => !this.tooltipExists(element)) + .map(element => newTooltip(element, config)); + + this.tooltips.push(...newTooltips); + }, + tooltipExists(element) { + return this.tooltips.some(tooltip => tooltip.target === element); + }, + }, +}; +</script> +<template> + <div> + <gl-tooltip + v-for="(tooltip, index) in tooltips" + :id="tooltip.id" + :key="index" + :target="tooltip.target" + :triggers="tooltip.triggers" + :placement="tooltip.placement" + :container="tooltip.container" + :boundary="tooltip.boundary" + :disabled="tooltip.disabled" + > + <span v-if="tooltip.html" v-safe-html="tooltip.title"></span> + <span v-else>{{ tooltip.title }}</span> + </gl-tooltip> + </div> +</template> diff --git a/app/assets/javascripts/tooltips/index.js b/app/assets/javascripts/tooltips/index.js new file mode 100644 index 00000000000..985f3350a62 --- /dev/null +++ b/app/assets/javascripts/tooltips/index.js @@ -0,0 +1,58 @@ +import Vue from 'vue'; +import Tooltips from './components/tooltips.vue'; + +let app; + +const EVENTS_MAP = { + hover: 'mouseenter', + click: 'click', + focus: 'focus', +}; + +const DEFAULT_TRIGGER = 'hover focus'; + +const tooltipsApp = () => { + if (!app) { + app = new Vue({ + render(h) { + return h(Tooltips, { + props: { + elements: this.elements, + }, + ref: 'tooltips', + }); + }, + }).$mount(); + } + + return app; +}; + +const isTooltip = (node, selector) => node.matches && node.matches(selector); + +const addTooltips = (elements, config) => { + tooltipsApp().$refs.tooltips.addTooltips(Array.from(elements), config); +}; + +const handleTooltipEvent = (rootTarget, e, selector, config = {}) => { + for (let { target } = e; target && target !== rootTarget; target = target.parentNode) { + if (isTooltip(target, selector)) { + addTooltips([target], { + show: true, + ...config, + }); + break; + } + } +}; + +export const initTooltips = (selector, config = {}) => { + const triggers = config?.triggers || DEFAULT_TRIGGER; + const events = triggers.split(' ').map(trigger => EVENTS_MAP[trigger]); + + events.forEach(event => { + document.addEventListener(event, e => handleTooltipEvent(document, e, selector, config), true); + }); + + return tooltipsApp(); +}; diff --git a/app/assets/javascripts/vue_shared/components/toggle_button.vue b/app/assets/javascripts/vue_shared/components/toggle_button.vue index 540edc9f61c..29d4516bece 100644 --- a/app/assets/javascripts/vue_shared/components/toggle_button.vue +++ b/app/assets/javascripts/vue_shared/components/toggle_button.vue @@ -73,7 +73,7 @@ export default { 'is-disabled': disabledInput, 'is-loading': isLoading, }" - @click="toggleFeature" + @click.prevent="toggleFeature" > <gl-loading-icon class="loading-icon" /> <span class="toggle-icon"> |