diff options
Diffstat (limited to 'app/assets')
32 files changed, 440 insertions, 79 deletions
diff --git a/app/assets/javascripts/boards/components/board_app.vue b/app/assets/javascripts/boards/components/board_app.vue index 858aabb0f05..af753151be8 100644 --- a/app/assets/javascripts/boards/components/board_app.vue +++ b/app/assets/javascripts/boards/components/board_app.vue @@ -1,5 +1,6 @@ <script> -import { mapActions, mapGetters } from 'vuex'; +import { mapGetters } from 'vuex'; +import { refreshCurrentPage } from '~/lib/utils/url_utility'; import BoardContent from '~/boards/components/board_content.vue'; import BoardSettingsSidebar from '~/boards/components/board_settings_sidebar.vue'; import BoardTopBar from '~/boards/components/board_top_bar.vue'; @@ -14,11 +15,11 @@ export default { computed: { ...mapGetters(['isSidebarOpen']), }, - mounted() { - this.performSearch(); + created() { + window.addEventListener('popstate', refreshCurrentPage); }, - methods: { - ...mapActions(['performSearch']), + destroyed() { + window.removeEventListener('popstate', refreshCurrentPage); }, }; </script> diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue index d7edaaa02d8..eaf3facb450 100644 --- a/app/assets/javascripts/boards/components/boards_selector.vue +++ b/app/assets/javascripts/boards/components/boards_selector.vue @@ -14,6 +14,8 @@ import { mapActions, mapGetters, mapState } from 'vuex'; import BoardForm from 'ee_else_ce/boards/components/board_form.vue'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import { isMetaKey } from '~/lib/utils/common_utils'; +import { updateHistory } from '~/lib/utils/url_utility'; import { s__ } from '~/locale'; import eventHub from '../eventhub'; @@ -21,6 +23,7 @@ import groupBoardsQuery from '../graphql/group_boards.query.graphql'; import projectBoardsQuery from '../graphql/project_boards.query.graphql'; import groupRecentBoardsQuery from '../graphql/group_recent_boards.query.graphql'; import projectRecentBoardsQuery from '../graphql/project_recent_boards.query.graphql'; +import { fullBoardId } from '../boards_util'; const MIN_BOARDS_TO_VIEW_RECENT = 10; @@ -112,6 +115,9 @@ export default { this.scrollFadeInitialized = false; this.$nextTick(this.setScrollFade); }, + board(newBoard) { + document.title = newBoard.name; + }, }, created() { eventHub.$on('showBoardModal', this.showPage); @@ -120,7 +126,7 @@ export default { eventHub.$off('showBoardModal', this.showPage); }, methods: { - ...mapActions(['setError', 'setBoardConfig']), + ...mapActions(['setError', 'fetchBoard', 'unsetActiveId']), showPage(page) { this.currentPage = page; }, @@ -196,6 +202,22 @@ export default { this.hasScrollFade = this.isScrolledUp(); }, + fetchCurrentBoard(boardId) { + this.fetchBoard({ + fullPath: this.fullPath, + fullBoardId: fullBoardId(boardId), + boardType: this.boardType, + }); + }, + async switchBoard(boardId, e) { + if (isMetaKey(e)) { + window.open(`${this.boardBaseUrl}/${boardId}`, '_blank'); + } else { + this.unsetActiveId(); + this.fetchCurrentBoard(boardId); + updateHistory({ url: `${this.boardBaseUrl}/${boardId}` }); + } + }, }, i18n: { errorFetchingBoard: s__('Board|An error occurred while fetching the board, please try again.'), @@ -242,8 +264,8 @@ export default { <gl-dropdown-item v-for="recentBoard in recentBoards" :key="`recent-${recentBoard.id}`" - :href="`${boardBaseUrl}/${recentBoard.id}`" data-testid="dropdown-item" + @click.prevent="switchBoard(recentBoard.id, $event)" > {{ recentBoard.name }} </gl-dropdown-item> @@ -258,8 +280,8 @@ export default { <gl-dropdown-item v-for="otherBoard in filteredBoards" :key="otherBoard.id" - :href="`${boardBaseUrl}/${otherBoard.id}`" data-testid="dropdown-item" + @click.prevent="switchBoard(otherBoard.id, $event)" > {{ otherBoard.name }} </gl-dropdown-item> diff --git a/app/assets/javascripts/boards/stores/actions.js b/app/assets/javascripts/boards/stores/actions.js index d319e659ac0..791182af806 100644 --- a/app/assets/javascripts/boards/stores/actions.js +++ b/app/assets/javascripts/boards/stores/actions.js @@ -68,8 +68,7 @@ export default { commit(types.RECEIVE_BOARD_FAILURE); } else { const board = data.workspace?.board; - commit(types.RECEIVE_BOARD_SUCCESS, board); - dispatch('setBoardConfig', board); + dispatch('setBoard', board); } }) .catch(() => commit(types.RECEIVE_BOARD_FAILURE)); @@ -420,9 +419,6 @@ export default { fetchItemsForList: ({ state, commit }, { listId, fetchNext = false }) => { if (!listId) return null; - if (!fetchNext) { - commit(types.RESET_ITEMS_FOR_LIST, listId); - } commit(types.REQUEST_ITEMS_FOR_LIST, { listId, fetchNext }); const { fullPath, fullBoardId, boardType, filterParams } = state; @@ -444,6 +440,7 @@ export default { isSingleRequest: true, }, variables, + ...(!fetchNext ? { fetchPolicy: fetchPolicies.NO_CACHE } : {}), }) .then(({ data }) => { const { lists } = data[boardType].board; diff --git a/app/assets/javascripts/boards/stores/mutation_types.js b/app/assets/javascripts/boards/stores/mutation_types.js index 96ab302c98f..43268f21f96 100644 --- a/app/assets/javascripts/boards/stores/mutation_types.js +++ b/app/assets/javascripts/boards/stores/mutation_types.js @@ -18,7 +18,6 @@ export const MOVE_LISTS = 'MOVE_LISTS'; export const TOGGLE_LIST_COLLAPSED = 'TOGGLE_LIST_COLLAPSED'; export const REMOVE_LIST = 'REMOVE_LIST'; export const REMOVE_LIST_FAILURE = 'REMOVE_LIST_FAILURE'; -export const RESET_ITEMS_FOR_LIST = 'RESET_ITEMS_FOR_LIST'; export const REQUEST_ITEMS_FOR_LIST = 'REQUEST_ITEMS_FOR_LIST'; export const RECEIVE_ITEMS_FOR_LIST_FAILURE = 'RECEIVE_ITEMS_FOR_LIST_FAILURE'; export const RECEIVE_ITEMS_FOR_LIST_SUCCESS = 'RECEIVE_ITEMS_FOR_LIST_SUCCESS'; diff --git a/app/assets/javascripts/boards/stores/mutations.js b/app/assets/javascripts/boards/stores/mutations.js index 6dda0e2af8b..04e7d3643e7 100644 --- a/app/assets/javascripts/boards/stores/mutations.js +++ b/app/assets/javascripts/boards/stores/mutations.js @@ -145,11 +145,6 @@ export default { state.boardLists = listsBackup; }, - [mutationTypes.RESET_ITEMS_FOR_LIST]: (state, listId) => { - Vue.set(state, 'backupItemsList', state.boardItemsByListId[listId]); - Vue.set(state.boardItemsByListId, listId, []); - }, - [mutationTypes.REQUEST_ITEMS_FOR_LIST]: (state, { listId, fetchNext }) => { Vue.set(state.listsFlags, listId, { [fetchNext ? 'isLoadingMore' : 'isLoading']: true }); }, @@ -185,7 +180,6 @@ export default { 'Boards|An error occurred while fetching the board issues. Please reload the page.', ); Vue.set(state.listsFlags, listId, { isLoading: false, isLoadingMore: false }); - Vue.set(state.boardItemsByListId, listId, state.backupItemsList); }, [mutationTypes.RESET_ISSUES]: (state) => { diff --git a/app/assets/javascripts/boards/stores/state.js b/app/assets/javascripts/boards/stores/state.js index 02275e40703..b62c032b921 100644 --- a/app/assets/javascripts/boards/stores/state.js +++ b/app/assets/javascripts/boards/stores/state.js @@ -13,7 +13,6 @@ export default () => ({ boardLists: {}, listsFlags: {}, boardItemsByListId: {}, - backupItemsList: [], isSettingAssignees: false, pageInfoByListId: {}, boardItems: {}, diff --git a/app/assets/javascripts/google_tag_manager/index.js b/app/assets/javascripts/google_tag_manager/index.js index a44a5b30e1e..2969121bf06 100644 --- a/app/assets/javascripts/google_tag_manager/index.js +++ b/app/assets/javascripts/google_tag_manager/index.js @@ -19,6 +19,7 @@ const PRODUCT_INFO = { variant: 'SaaS', }, }; +const EMPTY_NAMESPACE_ID_VALUE = 'not available'; const generateProductInfo = (sku, quantity) => { const product = PRODUCT_INFO[sku]; @@ -200,6 +201,10 @@ export const trackCheckout = (selectedPlan, quantity) => { pushEnhancedEcommerceEvent('EECCheckout', eventData); }; +export const getNamespaceId = () => { + return window.gl.snowplowStandardContext?.data?.namespace_id || EMPTY_NAMESPACE_ID_VALUE; +}; + export const trackTransaction = (transactionDetails) => { if (!isSupported()) { return; @@ -208,6 +213,7 @@ export const trackTransaction = (transactionDetails) => { const transactionId = uuidv4(); const { paymentOption, revenue, tax, selectedPlan, quantity } = transactionDetails; const product = generateProductInfo(selectedPlan, quantity); + const namespaceId = getNamespaceId(); if (Object.keys(product).length === 0) { return; @@ -224,7 +230,7 @@ export const trackTransaction = (transactionDetails) => { revenue: revenue.toString(), tax: tax.toString(), }, - products: [product], + products: [{ ...product, dimension36: namespaceId }], }, }, }; diff --git a/app/assets/javascripts/ide/components/terminal/session.vue b/app/assets/javascripts/ide/components/terminal/session.vue index 3a4128b6207..384e27844c6 100644 --- a/app/assets/javascripts/ide/components/terminal/session.vue +++ b/app/assets/javascripts/ide/components/terminal/session.vue @@ -16,7 +16,7 @@ export default { if (isEndingStatus(this.session.status)) { return { action: () => this.restartSession(), - variant: 'info', + variant: 'confirm', category: 'primary', text: __('Restart Terminal'), }; diff --git a/app/assets/javascripts/incidents_settings/components/pagerduty_form.vue b/app/assets/javascripts/incidents_settings/components/pagerduty_form.vue index 866d2ff399e..e8c9aa53a7c 100644 --- a/app/assets/javascripts/incidents_settings/components/pagerduty_form.vue +++ b/app/assets/javascripts/incidents_settings/components/pagerduty_form.vue @@ -11,6 +11,7 @@ import { GlModal, GlModalDirective, } from '@gitlab/ui'; +import { __ } from '~/locale'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import { I18N_PAGERDUTY_SETTINGS_FORM, CONFIGURE_PAGERDUTY_WEBHOOK_DOCS_LINK } from '../constants'; @@ -42,6 +43,21 @@ export default { }; }, i18n: I18N_PAGERDUTY_SETTINGS_FORM, + modal: { + id: 'resetWebhookModal', + actionPrimary: { + text: I18N_PAGERDUTY_SETTINGS_FORM.webhookUrl.resetWebhookUrl, + attributes: { + variant: 'danger', + }, + }, + actionCancel: { + text: __('Cancel'), + attributes: { + variant: 'default', + }, + }, + }, CONFIGURE_PAGERDUTY_WEBHOOK_DOCS_LINK, computed: { formData() { @@ -152,11 +168,11 @@ export default { {{ $options.i18n.webhookUrl.resetWebhookUrl }} </gl-button> <gl-modal - modal-id="resetWebhookModal" + :modal-id="$options.modal.id" :title="$options.i18n.webhookUrl.resetWebhookUrl" - :ok-title="$options.i18n.webhookUrl.resetWebhookUrl" - ok-variant="danger" - @ok="resetWebhookUrl" + :action-primary="$options.modal.actionPrimary" + :action-cancel="$options.modal.actionCancel" + @primary="resetWebhookUrl" > {{ $options.i18n.webhookUrl.restKeyInfo }} </gl-modal> diff --git a/app/assets/javascripts/issues/index.js b/app/assets/javascripts/issues/index.js index 1b5e2824879..67c6c723dcc 100644 --- a/app/assets/javascripts/issues/index.js +++ b/app/assets/javascripts/issues/index.js @@ -57,7 +57,7 @@ export function initShow() { const { issueType, ...issuableData } = parseIssuableData(el); if (issueType === IssueType.Incident) { - initIncidentApp(issuableData); + initIncidentApp({ ...issuableData, issuableId: el.dataset.issuableId }); initHeaderActions(store, IssueType.Incident); initRelatedIssues(IssueType.Incident); } else { diff --git a/app/assets/javascripts/issues/show/components/incidents/graphql/queries/get_timeline_events.query.graphql b/app/assets/javascripts/issues/show/components/incidents/graphql/queries/get_timeline_events.query.graphql new file mode 100644 index 00000000000..7e049d98c1a --- /dev/null +++ b/app/assets/javascripts/issues/show/components/incidents/graphql/queries/get_timeline_events.query.graphql @@ -0,0 +1,21 @@ +query GetTimelineEvents($fullPath: ID!, $incidentId: IssueID!) { + project(fullPath: $fullPath) { + id + incidentManagementTimelineEvents(incidentId: $incidentId) { + nodes { + id + author { + id + name + username + } + note + noteHtml + action + occurredAt + createdAt + updatedAt + } + } + } +} diff --git a/app/assets/javascripts/issues/show/components/incidents/timeline_events_list.vue b/app/assets/javascripts/issues/show/components/incidents/timeline_events_list.vue new file mode 100644 index 00000000000..a6e58ee0bdc --- /dev/null +++ b/app/assets/javascripts/issues/show/components/incidents/timeline_events_list.vue @@ -0,0 +1,73 @@ +<script> +import { formatDate } from '~/lib/utils/datetime_utility'; +import IncidentTimelineEventListItem from './timeline_events_list_item.vue'; + +export default { + name: 'IncidentTimelineEventList', + components: { + IncidentTimelineEventListItem, + }, + props: { + timelineEventLoading: { + type: Boolean, + required: false, + default: true, + }, + timelineEvents: { + type: Array, + required: true, + default: () => [], + }, + }, + computed: { + dateGroupedEvents() { + const groupedEvents = new Map(); + + this.timelineEvents.forEach((event) => { + const date = formatDate(event.occurredAt, 'isoDate', true); + + if (groupedEvents.has(date)) { + groupedEvents.get(date).push(event); + } else { + groupedEvents.set(date, [event]); + } + }); + + return groupedEvents; + }, + }, + methods: { + isLastItem(groups, groupIndex, events, eventIndex) { + if (groupIndex < groups.size - 1) { + return false; + } + return eventIndex === events.length - 1; + }, + }, +}; +</script> + +<template> + <div class="issuable-discussion incident-timeline-events"> + <div + v-for="([eventDate, events], groupIndex) in dateGroupedEvents" + :key="eventDate" + data-testid="timeline-group" + > + <div class="gl-pb-3 gl-border-gray-50 gl-border-1 gl-border-b-solid"> + <strong class="gl-font-size-h2" data-testid="event-date">{{ eventDate }}</strong> + </div> + <ul class="notes main-notes-list gl-pl-n3"> + <incident-timeline-event-list-item + v-for="(event, eventIndex) in events" + :key="event.id" + :action="event.action" + :occurred-at="event.occurredAt" + :note-html="event.noteHtml" + :is-last-item="isLastItem(dateGroupedEvents, groupIndex, events, eventIndex)" + data-testid="timeline-event" + /> + </ul> + </div> + </div> +</template> diff --git a/app/assets/javascripts/issues/show/components/incidents/timeline_events_list_item.vue b/app/assets/javascripts/issues/show/components/incidents/timeline_events_list_item.vue new file mode 100644 index 00000000000..fef9bf713b7 --- /dev/null +++ b/app/assets/javascripts/issues/show/components/incidents/timeline_events_list_item.vue @@ -0,0 +1,71 @@ +<script> +import { GlIcon, GlSafeHtmlDirective, GlSprintf } from '@gitlab/ui'; +import { formatDate } from '~/lib/utils/datetime_utility'; +import { __ } from '~/locale'; +import { getEventIcon } from './utils'; + +export default { + name: 'IncidentTimelineEventListItem', + i18n: { + timeUTC: __('%{time} UTC'), + }, + components: { + GlIcon, + GlSprintf, + }, + directives: { + SafeHtml: GlSafeHtmlDirective, + }, + props: { + isLastItem: { + type: Boolean, + required: true, + }, + occurredAt: { + type: String, + required: true, + }, + action: { + type: String, + required: true, + }, + noteHtml: { + type: String, + required: true, + }, + }, + computed: { + time() { + return formatDate(this.occurredAt, 'HH:MM', true); + }, + }, + methods: { + getEventIcon, + }, +}; +</script> +<template> + <li + class="timeline-entry timeline-entry-vertical-line note system-note note-wrapper gl-my-2! gl-pr-0!" + > + <div class="gl-display-flex gl-align-items-center"> + <div + class="gl-display-flex gl-align-items-center gl-justify-content-center gl-bg-white gl-text-gray-200 gl-border-gray-100 gl-border-1 gl-border-solid gl-rounded-full gl-mt-n2 gl-mr-3 gl-w-8 gl-h-8 gl-p-3 gl-z-index-1" + > + <gl-icon :name="getEventIcon(action)" class="note-icon" /> + </div> + <div + class="timeline-event-note gl-w-full" + :class="{ 'gl-pb-3 gl-border-gray-50 gl-border-1 gl-border-b-solid': !isLastItem }" + data-testid="event-text-container" + > + <strong class="gl-font-lg" data-testid="event-time"> + <gl-sprintf :message="$options.i18n.timeUTC"> + <template #time>{{ time }}</template> + </gl-sprintf> + </strong> + <div v-safe-html="noteHtml"></div> + </div> + </div> + </li> +</template> diff --git a/app/assets/javascripts/issues/show/components/incidents/timeline_events_tab.vue b/app/assets/javascripts/issues/show/components/incidents/timeline_events_tab.vue index ec101fd943f..400e1f0b725 100644 --- a/app/assets/javascripts/issues/show/components/incidents/timeline_events_tab.vue +++ b/app/assets/javascripts/issues/show/components/incidents/timeline_events_tab.vue @@ -1,21 +1,70 @@ <script> -import { GlTab, GlButton } from '@gitlab/ui'; +import { GlEmptyState, GlLoadingIcon, GlTab } from '@gitlab/ui'; +import { convertToGraphQLId } from '~/graphql_shared/utils'; +import { TYPE_ISSUE } from '~/graphql_shared/constants'; +import { fetchPolicies } from '~/lib/graphql'; +import getTimelineEvents from './graphql/queries/get_timeline_events.query.graphql'; +import { displayAndLogError } from './utils'; + +import IncidentTimelineEventsList from './timeline_events_list.vue'; export default { components: { + GlEmptyState, + GlLoadingIcon, GlTab, - GlButton, + IncidentTimelineEventsList, + }, + inject: ['fullPath', 'issuableId'], + data() { + return { + timelineEvents: [], + }; + }, + apollo: { + timelineEvents: { + fetchPolicy: fetchPolicies.CACHE_AND_NETWORK, + query: getTimelineEvents, + variables() { + return { + fullPath: this.fullPath, + incidentId: convertToGraphQLId(TYPE_ISSUE, this.issuableId), + }; + }, + update(data) { + return data.project.incidentManagementTimelineEvents.nodes; + }, + error(error) { + displayAndLogError(error); + }, + }, + }, + computed: { + timelineEventLoading() { + return this.$apollo.queries.timelineEvents.loading; + }, + hasTimelineEvents() { + return Boolean(this.timelineEvents.length); + }, + showEmptyState() { + return !this.timelineEventLoading && !this.hasTimelineEvents; + }, }, }; </script> <template> <gl-tab :title="s__('Incident|Timeline')"> - <div class="gl-my-4"> - <p>{{ s__('Incident|No timeline items have been added yet.') }}</p> - </div> - <gl-button class="gl-my-3"> - {{ s__('Incident|Add new timeline event') }} - </gl-button> + <gl-loading-icon v-if="timelineEventLoading" size="lg" color="dark" class="gl-mt-5" /> + <gl-empty-state + v-else-if="showEmptyState" + :compact="true" + :description="s__('Incident|No timeline items have been added yet.')" + /> + <incident-timeline-events-list + v-if="hasTimelineEvents" + :timeline-event-loading="timelineEventLoading" + :timeline-events="timelineEvents" + /> </gl-tab> </template> diff --git a/app/assets/javascripts/issues/show/components/incidents/utils.js b/app/assets/javascripts/issues/show/components/incidents/utils.js new file mode 100644 index 00000000000..8b5a2ec4031 --- /dev/null +++ b/app/assets/javascripts/issues/show/components/incidents/utils.js @@ -0,0 +1,18 @@ +import { createAlert } from '~/flash'; +import { s__ } from '~/locale'; + +export const displayAndLogError = (error) => + createAlert({ + message: s__('Incident|Something went wrong while fetching incident timeline events.'), + captureError: true, + error, + }); + +const EVENT_ICONS = { + comment: 'comment', + default: 'comment', +}; + +export const getEventIcon = (actionName) => { + return EVENT_ICONS[actionName] ?? EVENT_ICONS.default; +}; diff --git a/app/assets/javascripts/issues/show/index.js b/app/assets/javascripts/issues/show/index.js index 3f149e39c4e..5bdad010af7 100644 --- a/app/assets/javascripts/issues/show/index.js +++ b/app/assets/javascripts/issues/show/index.js @@ -33,6 +33,7 @@ export function initIncidentApp(issueData = {}) { canCreateIncident, canUpdate, iid, + issuableId, projectNamespace, projectPath, projectId, @@ -53,6 +54,7 @@ export function initIncidentApp(issueData = {}) { canUpdate, fullPath, iid, + issuableId, projectId, slaFeatureAvailable: parseBoolean(slaFeatureAvailable), uploadMetricsFeatureAvailable: parseBoolean(uploadMetricsFeatureAvailable), diff --git a/app/assets/javascripts/monitoring/components/create_dashboard_modal.vue b/app/assets/javascripts/monitoring/components/create_dashboard_modal.vue index 288487d25a5..10178366db5 100644 --- a/app/assets/javascripts/monitoring/components/create_dashboard_modal.vue +++ b/app/assets/javascripts/monitoring/components/create_dashboard_modal.vue @@ -47,7 +47,7 @@ export default { <gl-button category="secondary" @click="cancelHandler">{{ s__('Metrics|Cancel') }}</gl-button> <gl-button category="secondary" - variant="info" + variant="confirm" target="_blank" :href="addDashboardDocumentationPath" data-testid="create-dashboard-modal-docs-button" diff --git a/app/assets/javascripts/monitoring/components/dashboard_panel_builder.vue b/app/assets/javascripts/monitoring/components/dashboard_panel_builder.vue index ec35d8d3aca..8efea2bfc3e 100644 --- a/app/assets/javascripts/monitoring/components/dashboard_panel_builder.vue +++ b/app/assets/javascripts/monitoring/components/dashboard_panel_builder.vue @@ -162,7 +162,7 @@ export default { ref="viewDocumentationBtn" category="secondary" class="gl-xs-w-full gl-xs-mb-3" - variant="info" + variant="confirm" target="_blank" :href="addDashboardDocumentationPath" > diff --git a/app/assets/javascripts/pages/admin/application_settings/payload_downloader.js b/app/assets/javascripts/pages/admin/application_settings/payload_downloader.js index 7c81cf80dc6..8cecc1d3ef7 100644 --- a/app/assets/javascripts/pages/admin/application_settings/payload_downloader.js +++ b/app/assets/javascripts/pages/admin/application_settings/payload_downloader.js @@ -19,7 +19,7 @@ export default class PayloadDownloader { } requestPayload() { - this.spinner.classList.add('d-inline-flex'); + this.spinner.classList.add('gl-display-inline'); return axios .get(this.trigger.dataset.endpoint, { @@ -34,7 +34,7 @@ export default class PayloadDownloader { }); }) .finally(() => { - this.spinner.classList.remove('d-inline-flex'); + this.spinner.classList.remove('gl-display-inline'); }); } diff --git a/app/assets/javascripts/pages/admin/application_settings/payload_previewer.js b/app/assets/javascripts/pages/admin/application_settings/payload_previewer.js index ae08806fe4c..84027203783 100644 --- a/app/assets/javascripts/pages/admin/application_settings/payload_previewer.js +++ b/app/assets/javascripts/pages/admin/application_settings/payload_previewer.js @@ -29,7 +29,7 @@ export default class PayloadPreviewer { requestPayload() { if (this.isInserted) return this.showPayload(); - this.spinner.classList.add('gl-display-inline-flex'); + this.spinner.classList.add('gl-display-inline'); const container = this.getContainer(); @@ -38,11 +38,11 @@ export default class PayloadPreviewer { responseType: 'text', }) .then(({ data }) => { - this.spinner.classList.remove('gl-display-inline-flex'); + this.spinner.classList.remove('gl-display-inline'); this.insertPayload(data); }) .catch(() => { - this.spinner.classList.remove('gl-display-inline-flex'); + this.spinner.classList.remove('gl-display-inline'); createFlash({ message: __('Error fetching payload data.'), }); diff --git a/app/assets/javascripts/pages/projects/merge_requests/creations/new/compare.js b/app/assets/javascripts/pages/projects/merge_requests/creations/new/compare.js index d61209f904d..2d26d3922bf 100644 --- a/app/assets/javascripts/pages/projects/merge_requests/creations/new/compare.js +++ b/app/assets/javascripts/pages/projects/merge_requests/creations/new/compare.js @@ -4,7 +4,8 @@ import { localTimeAgo } from '~/lib/utils/datetime_utility'; import initCompareAutocomplete from './compare_autocomplete'; import initTargetProjectDropdown from './target_project_dropdown'; -const updateCommitList = (url, $loadingIndicator, $commitList, params) => { +const updateCommitList = (url, $emptyState, $loadingIndicator, $commitList, params) => { + $emptyState.hide(); $loadingIndicator.show(); $commitList.empty(); @@ -16,6 +17,10 @@ const updateCommitList = (url, $loadingIndicator, $commitList, params) => { $loadingIndicator.hide(); $commitList.html(data); localTimeAgo($commitList.get(0).querySelectorAll('.js-timeago')); + + if (!data) { + $emptyState.show(); + } }); }; @@ -26,6 +31,7 @@ export default (mrNewCompareNode) => { const updateSourceBranchCommitList = () => updateCommitList( sourceBranchUrl, + $(mrNewCompareNode).find('.js-source-commit-empty'), $(mrNewCompareNode).find('.js-source-loading'), $(mrNewCompareNode).find('.mr_source_commit'), { @@ -35,6 +41,7 @@ export default (mrNewCompareNode) => { const updateTargetBranchCommitList = () => updateCommitList( targetBranchUrl, + $(mrNewCompareNode).find('.js-target-commit-empty'), $(mrNewCompareNode).find('.js-target-loading'), $(mrNewCompareNode).find('.mr_target_commit'), { diff --git a/app/assets/javascripts/pages/projects/merge_requests/creations/new/compare_autocomplete.js b/app/assets/javascripts/pages/projects/merge_requests/creations/new/compare_autocomplete.js index e5f97530c02..9a38c2cc765 100644 --- a/app/assets/javascripts/pages/projects/merge_requests/creations/new/compare_autocomplete.js +++ b/app/assets/javascripts/pages/projects/merge_requests/creations/new/compare_autocomplete.js @@ -12,6 +12,7 @@ export default function initCompareAutocomplete(limitTo = null, clickHandler = ( $('.js-compare-dropdown').each(function () { const $dropdown = $(this); const selected = $dropdown.data('selected'); + const defaultText = $dropdown.data('defaultText').trim(); const $dropdownContainer = $dropdown.closest('.dropdown'); const $fieldInput = $(`input[name="${$dropdown.data('fieldName')}"]`, $dropdownContainer); const $filterInput = $('input[type="search"]', $dropdownContainer); @@ -63,7 +64,11 @@ export default function initCompareAutocomplete(limitTo = null, clickHandler = ( return $el.attr('data-ref'); }, toggleLabel(obj, $el) { - return $el.text().trim(); + if ($el.hasClass('is-active')) { + return $el.text().trim(); + } + + return defaultText; }, clicked: () => clickHandler($dropdown), }); diff --git a/app/assets/javascripts/runner/admin_runner_show/admin_runner_show_app.vue b/app/assets/javascripts/runner/admin_runner_show/admin_runner_show_app.vue index c3f317b40b0..06a8eb790fc 100644 --- a/app/assets/javascripts/runner/admin_runner_show/admin_runner_show_app.vue +++ b/app/assets/javascripts/runner/admin_runner_show/admin_runner_show_app.vue @@ -1,14 +1,16 @@ <script> -import { GlTooltipDirective } from '@gitlab/ui'; +import { GlBadge, GlTab, GlTooltipDirective } from '@gitlab/ui'; import { createAlert, VARIANT_SUCCESS } from '~/flash'; import { TYPE_CI_RUNNER } from '~/graphql_shared/constants'; import { convertToGraphQLId } from '~/graphql_shared/utils'; import { redirectTo } from '~/lib/utils/url_utility'; +import { formatJobCount } from '../utils'; import RunnerDeleteButton from '../components/runner_delete_button.vue'; import RunnerEditButton from '../components/runner_edit_button.vue'; import RunnerPauseButton from '../components/runner_pause_button.vue'; import RunnerHeader from '../components/runner_header.vue'; import RunnerDetails from '../components/runner_details.vue'; +import RunnerJobs from '../components/runner_jobs.vue'; import { I18N_FETCH_ERROR } from '../constants'; import runnerQuery from '../graphql/show/runner.query.graphql'; import { captureException } from '../sentry_utils'; @@ -17,11 +19,14 @@ import { saveAlertToLocalStorage } from '../local_storage_alert/save_alert_to_lo export default { name: 'AdminRunnerShowApp', components: { + GlBadge, + GlTab, RunnerDeleteButton, RunnerEditButton, RunnerPauseButton, RunnerHeader, RunnerDetails, + RunnerJobs, }, directives: { GlTooltip: GlTooltipDirective, @@ -63,6 +68,9 @@ export default { canDelete() { return this.runner.userPermissions?.deleteRunner; }, + jobCount() { + return formatJobCount(this.runner?.jobCount); + }, }, errorCaptured(error) { this.reportToSentry(error); @@ -88,6 +96,24 @@ export default { </template> </runner-header> - <runner-details :runner="runner" /> + <runner-details :runner="runner"> + <template #jobs-tab> + <gl-tab> + <template #title> + {{ s__('Runners|Jobs') }} + <gl-badge + v-if="jobCount" + data-testid="job-count-badge" + class="gl-tab-counter-badge" + size="sm" + > + {{ jobCount }} + </gl-badge> + </template> + + <runner-jobs v-if="runner" :runner="runner" /> + </gl-tab> + </template> + </runner-details> </div> </template> diff --git a/app/assets/javascripts/runner/components/runner_details.vue b/app/assets/javascripts/runner/components/runner_details.vue index fbe08e93d71..75ddec6c716 100644 --- a/app/assets/javascripts/runner/components/runner_details.vue +++ b/app/assets/javascripts/runner/components/runner_details.vue @@ -1,19 +1,16 @@ <script> -import { GlBadge, GlTabs, GlTab, GlIntersperse } from '@gitlab/ui'; +import { GlTabs, GlTab, GlIntersperse } from '@gitlab/ui'; import { s__ } from '~/locale'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import { timeIntervalInWords } from '~/lib/utils/datetime_utility'; import { ACCESS_LEVEL_REF_PROTECTED, GROUP_TYPE, PROJECT_TYPE } from '../constants'; -import { formatJobCount } from '../utils'; import RunnerDetail from './runner_detail.vue'; import RunnerGroups from './runner_groups.vue'; import RunnerProjects from './runner_projects.vue'; -import RunnerJobs from './runner_jobs.vue'; import RunnerTags from './runner_tags.vue'; export default { components: { - GlBadge, GlTabs, GlTab, GlIntersperse, @@ -22,7 +19,6 @@ export default { import('ee_component/runner/components/runner_maintenance_note_detail.vue'), RunnerGroups, RunnerProjects, - RunnerJobs, RunnerTags, TimeAgo, }, @@ -59,9 +55,6 @@ export default { isProjectRunner() { return this.runner?.runnerType === PROJECT_TYPE; }, - jobCount() { - return formatJobCount(this.runner?.jobCount); - }, }, ACCESS_LEVEL_REF_PROTECTED, }; @@ -120,15 +113,6 @@ export default { <runner-projects v-if="isProjectRunner" :runner="runner" /> </template> </gl-tab> - <gl-tab> - <template #title> - {{ s__('Runners|Jobs') }} - <gl-badge v-if="jobCount" data-testid="job-count-badge" class="gl-ml-1" size="sm"> - {{ jobCount }} - </gl-badge> - </template> - - <runner-jobs v-if="runner" :runner="runner" /> - </gl-tab> + <slot name="jobs-tab"></slot> </gl-tabs> </template> diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 13db3677cab..5fa1923af7c 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -175,10 +175,6 @@ @include btn-outline($white, $red-500, $red-500, $red-100, $red-700, $red-500, $red-200, $red-600, $red-800); } - &.btn-warning { - @include btn-outline($white, $orange-500, $orange-500, $orange-50, $orange-600, $orange-600, $orange-100, $orange-700, $orange-700); - } - &.btn-primary, &.btn-info { @include btn-outline($white, $blue-500, $blue-500, $blue-100, $blue-700, $blue-500, $blue-200, $blue-600, $blue-800); @@ -190,10 +186,6 @@ @include btn-blue; } - &.btn-warning { - @include btn-orange; - } - &.btn-danger { @include btn-red; } diff --git a/app/assets/stylesheets/framework/secondary_navigation_elements.scss b/app/assets/stylesheets/framework/secondary_navigation_elements.scss index 549b61aedae..74aed1bd984 100644 --- a/app/assets/stylesheets/framework/secondary_navigation_elements.scss +++ b/app/assets/stylesheets/framework/secondary_navigation_elements.scss @@ -273,6 +273,18 @@ @include scrolling-links(); } + .fade-left::after, + .fade-right::after { + content: ''; + pointer-events: none; + z-index: -1; + display: block; + width: 16px; + height: 100%; + position: absolute; + top: 0; + } + .fade-right { @include fade(left, $gray-light); right: -5px; @@ -280,6 +292,11 @@ svg { right: -7px; } + + &::after { + right: 0; + background: linear-gradient(270deg, $white, transparent); + } } .fade-left { @@ -290,6 +307,11 @@ svg { left: -7px; } + + &::after { + left: 0; + background: linear-gradient(90deg, $white, transparent); + } } } @@ -316,7 +338,6 @@ .fade-right, .fade-left { - bottom: $gl-padding; top: auto; } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 715ba48ab6d..c9bc1c9189e 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -428,7 +428,6 @@ $gl-padding-12: 12px; $gl-padding: 16px; $gl-padding-24: 24px; $gl-padding-32: 32px; -$gl-padding-50: 50px; $gl-input-padding: 10px; $gl-vert-padding: 6px; $gl-padding-top: 10px; diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss index 76f84331790..3356285dd41 100644 --- a/app/assets/stylesheets/page_bundles/merge_requests.scss +++ b/app/assets/stylesheets/page_bundles/merge_requests.scss @@ -315,7 +315,7 @@ $tabs-holder-z-index: 250; } .mr-fast-forward-message { - padding-left: $gl-padding-50; + padding-left: $gl-spacing-scale-9; padding-bottom: $gl-padding; } diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index 7ac3ef2221f..9106ecaa81d 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -40,8 +40,8 @@ } .save-group-loader { - margin-top: $gl-padding-50; - margin-bottom: $gl-padding-50; + margin-top: $gl-spacing-scale-9; + margin-bottom: $gl-spacing-scale-9; color: $gray-700; } diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 387970e06ae..f3182af3047 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -937,3 +937,58 @@ margin-right: -7px; z-index: 1; } + +.issuable-discussion.incident-timeline-events { + .main-notes-list::before { + content: none; + } + + .timeline-event-note { + p { + margin-bottom: 0; + } + } +} + +/** + * We have a very specific design proposal where we cannot + * use `vertical-line` mixin as it is and have to use + * custom styles, see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81284#note_904867444 + */ +.timeline-entry-vertical-line { + &::before, + &::after { + content: ''; + border-left: 2px solid $gray-50; + position: absolute; + left: 39px; + height: 80%; + } + + &:first-child::before, + &:last-child::after { + content: none; + } + + &:first-child { + &::after { + top: 50%; + } + } + + &:last-child { + &::before { + bottom: 50%; + } + } + + &:not(:first-child):not(:last-child) { + &::before { + top: -10%; + } + + &::after { + bottom: -10%; + } + } +} diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index b63fd941a9b..a3fbedd87a9 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -3,6 +3,7 @@ * */ $tabs-holder-z-index: 250; +$comparison-empty-state-height: 62px; .space-children { @include clearfix; @@ -70,6 +71,10 @@ $tabs-holder-z-index: 250; } } +.compare-commit-empty { + min-height: $comparison-empty-state-height; +} + .commits-empty { text-align: center; diff --git a/app/assets/stylesheets/themes/_dark.scss b/app/assets/stylesheets/themes/_dark.scss index 6a9e96c3ac5..fe8a5aec1b3 100644 --- a/app/assets/stylesheets/themes/_dark.scss +++ b/app/assets/stylesheets/themes/_dark.scss @@ -209,7 +209,6 @@ body.gl-dark { &.btn-info, &.btn-success, &.btn-danger, - &.btn-warning, &.btn-confirm { &-tertiary { mix-blend-mode: screen; |