diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-06-14 15:08:53 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-06-14 15:08:53 +0300 |
commit | 7a124e225ea58c2a432dd29f82ba682963886383 (patch) | |
tree | 4ad5eefec173bdc56aaacc81e4dfb66a8fb9e254 /app | |
parent | 067b3d04573d1473dbc6c81ef775d70c6636ff3f (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
57 files changed, 539 insertions, 145 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; diff --git a/app/models/key.rb b/app/models/key.rb index 621343cab10..5268ce2e040 100644 --- a/app/models/key.rb +++ b/app/models/key.rb @@ -28,6 +28,7 @@ class Key < ApplicationRecord validate :key_meets_restrictions validate :expiration, on: :create + validate :banned_key, if: :should_check_for_banned_key? delegate :name, :email, to: :user, prefix: true @@ -142,6 +143,27 @@ class Key < ApplicationRecord end end + def should_check_for_banned_key? + return false unless user + + key_changed? && Feature.enabled?(:ssh_banned_key, user) + end + + def banned_key + return unless public_key.banned? + + help_page_url = Rails.application.routes.url_helpers.help_page_url( + 'security/ssh_keys_restrictions', + anchor: 'block-banned-or-compromised-keys' + ) + + errors.add( + :key, + _('cannot be used because it belongs to a compromised private key. Stop using this key and generate a new one.'), + help_page_url: help_page_url + ) + end + def forbidden_key_type_message allowed_types = Gitlab::CurrentSettings.allowed_key_types.map(&:upcase) diff --git a/app/models/project.rb b/app/models/project.rb index aeed681fc6f..4c99809f819 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -2585,16 +2585,7 @@ class Project < ApplicationRecord end def access_request_approvers_to_be_notified - # For a personal project: - # The creator is added as a member with `Owner` access level, starting from GitLab 14.8 - # The creator was added as a member with `Maintainer` access level, before GitLab 14.8 - # So, to make sure access requests for all personal projects work as expected, - # we need to filter members with the scope `owners_and_maintainers`. - access_request_approvers = if personal? - members.owners_and_maintainers - else - members.maintainers - end + access_request_approvers = members.owners_and_maintainers access_request_approvers.connected_to_user.order_recent_sign_in.limit(Member::ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT) end diff --git a/app/uploaders/metric_image_uploader.rb b/app/uploaders/metric_image_uploader.rb index 0826bb251e4..d7d70518565 100644 --- a/app/uploaders/metric_image_uploader.rb +++ b/app/uploaders/metric_image_uploader.rb @@ -6,6 +6,10 @@ class MetricImageUploader < GitlabUploader # rubocop:disable Gitlab/NamespacedCl prepend ObjectStorage::Extension::RecordsUploads include UploaderHelper + def self.workhorse_local_upload_path + File.join(options.storage_path, 'uploads', TMP_UPLOAD_PATH) + end + private def dynamic_segment diff --git a/app/views/admin/application_settings/_account_and_limit.html.haml b/app/views/admin/application_settings/_account_and_limit.html.haml index f914de138a9..e7204f635e6 100644 --- a/app/views/admin/application_settings/_account_and_limit.html.haml +++ b/app/views/admin/application_settings/_account_and_limit.html.haml @@ -1,5 +1,5 @@ = gitlab_ui_form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-account-settings'), html: { class: 'fieldset-form', id: 'account-settings' } do |f| - = form_errors(@application_setting) + = form_errors(@application_setting, pajamas_alert: true) %fieldset .form-group diff --git a/app/views/admin/application_settings/_ci_cd.html.haml b/app/views/admin/application_settings/_ci_cd.html.haml index 201ca830ba4..ba2a2f34d63 100644 --- a/app/views/admin/application_settings/_ci_cd.html.haml +++ b/app/views/admin/application_settings/_ci_cd.html.haml @@ -1,6 +1,6 @@ .settings-content = gitlab_ui_form_for @application_setting, url: ci_cd_admin_application_settings_path(anchor: 'js-ci-cd-settings'), html: { class: 'fieldset-form' } do |f| - = form_errors(@application_setting) + = form_errors(@application_setting, pajamas_alert: true ) %fieldset .form-group @@ -72,7 +72,7 @@ - @plans.each_with_index do |plan, index| .tab-pane{ :id => "plan#{index}", class: index == 0 ? 'active': '' } = form_for plan.actual_limits, url: admin_plan_limits_path(anchor: 'js-ci-cd-settings'), html: { class: 'fieldset-form' }, method: :post do |f| - = form_errors(plan) + = form_errors(plan, pajamas_alert: true) %fieldset = f.hidden_field(:plan_id, value: plan.id) .form-group diff --git a/app/views/admin/application_settings/_floc.html.haml b/app/views/admin/application_settings/_floc.html.haml index 125ed569463..b5a63aa0847 100644 --- a/app/views/admin/application_settings/_floc.html.haml +++ b/app/views/admin/application_settings/_floc.html.haml @@ -12,7 +12,7 @@ .settings-content = gitlab_ui_form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-floc-settings'), html: { class: 'fieldset-form', id: 'floc-settings' } do |f| - = form_errors(@application_setting) + = form_errors(@application_setting, pajamas_alert: true) %fieldset .form-group diff --git a/app/views/admin/application_settings/_kroki.html.haml b/app/views/admin/application_settings/_kroki.html.haml index ad9e7ca5fea..b1dd8a282ec 100644 --- a/app/views/admin/application_settings/_kroki.html.haml +++ b/app/views/admin/application_settings/_kroki.html.haml @@ -10,7 +10,7 @@ = link_to _('Learn more.'), help_page_path('administration/integration/kroki.md'), target: '_blank', rel: 'noopener noreferrer' .settings-content = gitlab_ui_form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-kroki-settings'), html: { class: 'fieldset-form', id: 'kroki-settings' } do |f| - = form_errors(@application_setting) if expanded + = form_errors(@application_setting, pajamas_alert: true) if expanded %fieldset .form-group diff --git a/app/views/admin/application_settings/_package_registry.html.haml b/app/views/admin/application_settings/_package_registry.html.haml index 4858353f3b6..c0fabb1d42e 100644 --- a/app/views/admin/application_settings/_package_registry.html.haml +++ b/app/views/admin/application_settings/_package_registry.html.haml @@ -26,7 +26,7 @@ - @plans.each_with_index do |plan, index| .tab-pane{ :id => "plan#{index}", class: index == 0 ? 'active': '' } = form_for plan.actual_limits, url: admin_plan_limits_path(anchor: 'js-package-settings'), html: { class: 'fieldset-form' }, method: :post do |f| - = form_errors(plan) + = form_errors(plan, pajamas_alert: true) %fieldset = f.hidden_field(:plan_id, value: plan.id) .form-group diff --git a/app/views/admin/application_settings/_signin.html.haml b/app/views/admin/application_settings/_signin.html.haml index 48f0b9b2c31..870bfbf4184 100644 --- a/app/views/admin/application_settings/_signin.html.haml +++ b/app/views/admin/application_settings/_signin.html.haml @@ -1,5 +1,5 @@ = gitlab_ui_form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-signin-settings'), html: { class: 'fieldset-form', id: 'signin-settings' } do |f| - = form_errors(@application_setting) + = form_errors(@application_setting, pajamas_alert: true) %fieldset .form-group diff --git a/app/views/admin/application_settings/_sourcegraph.html.haml b/app/views/admin/application_settings/_sourcegraph.html.haml index 7aff7309e07..a0cbbecb943 100644 --- a/app/views/admin/application_settings/_sourcegraph.html.haml +++ b/app/views/admin/application_settings/_sourcegraph.html.haml @@ -17,7 +17,7 @@ .settings-content = gitlab_ui_form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-sourcegraph-settings'), html: { class: 'fieldset-form', id: 'sourcegraph-settings' } do |f| - = form_errors(@application_setting) + = form_errors(@application_setting, pajamas_alert: true) %fieldset .form-group diff --git a/app/views/admin/application_settings/_terms.html.haml b/app/views/admin/application_settings/_terms.html.haml index a4b6e061c43..c5387db59ef 100644 --- a/app/views/admin/application_settings/_terms.html.haml +++ b/app/views/admin/application_settings/_terms.html.haml @@ -1,5 +1,5 @@ = gitlab_ui_form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-terms-settings'), html: { class: 'fieldset-form', id: 'terms-settings' } do |f| - = form_errors(@application_setting) + = form_errors(@application_setting, pajamas_alert: true) %fieldset .form-group diff --git a/app/views/admin/application_settings/service_usage_data.html.haml b/app/views/admin/application_settings/service_usage_data.html.haml index 55c25ca32d5..25c8bd12345 100644 --- a/app/views/admin/application_settings/service_usage_data.html.haml +++ b/app/views/admin/application_settings/service_usage_data.html.haml @@ -8,12 +8,12 @@ %h3= name - if @service_ping_data_present - %button.gl-button.btn.btn-default.js-payload-preview-trigger{ type: 'button', data: { payload_selector: ".#{payload_class}" } } - = gl_loading_icon(css_class: 'js-spinner gl-display-none gl-mr-2') - .js-text.gl-display-inline= _('Preview payload') - %button.gl-button.btn.btn-default.js-payload-download-trigger{ type: 'button', data: { endpoint: usage_data_admin_application_settings_path(format: :json) } } - = gl_loading_icon(css_class: 'js-spinner gl-display-none gl-mr-2') - .js-text.d-inline= _('Download payload') + = render Pajamas::ButtonComponent.new(button_options: { class: 'js-payload-preview-trigger gl-mr-2', data: { payload_selector: ".#{payload_class}" } } ) do + = gl_loading_icon(css_class: 'js-spinner gl-display-none', inline: true) + %span.js-text.gl-display-inline= _('Preview payload') + = render Pajamas::ButtonComponent.new(button_options: { class: 'js-payload-download-trigger gl-mr-2', data: { endpoint: usage_data_admin_application_settings_path(format: :json) } } ) do + = gl_loading_icon(css_class: 'js-spinner gl-display-none', inline: true) + %span.js-text.gl-display-inline= _('Download payload') %pre.js-syntax-highlight.code.highlight.gl-mt-2.gl-display-none{ class: payload_class, data: { endpoint: usage_data_admin_application_settings_path(format: :html) } } - else = render Pajamas::AlertComponent.new(variant: :warning, diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 3cb9cd967dc..88fbbb28201 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -3,7 +3,6 @@ - billable_users_url = help_page_path('subscriptions/self_managed/index', anchor: 'billable-users') - billable_users_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer nofollow">'.html_safe % { url: billable_users_url } -= render_if_exists 'shared/manual_renewal_banner' = render_if_exists 'shared/manual_quarterly_reconciliation_banner' = render_if_exists 'shared/submit_license_usage_data_banner' = render_if_exists 'shared/qrtly_reconciliation_alert' diff --git a/app/views/admin/jobs/index.html.haml b/app/views/admin/jobs/index.html.haml index 670628f7463..667c90f0228 100644 --- a/app/views/admin/jobs/index.html.haml +++ b/app/views/admin/jobs/index.html.haml @@ -4,14 +4,17 @@ - breadcrumb_title _("Jobs") - page_title _("Jobs") -.top-area.scrolling-tabs-container.inner-page-scroll-tabs - - build_path_proc = ->(scope) { admin_jobs_path(scope: scope) } - = render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope +.top-area + .scrolling-tabs-container.inner-page-scroll-tabs.gl-flex-grow-1.gl-min-w-0.gl-w-full + .fade-left= sprite_icon('chevron-lg-left', size: 12) + .fade-right= sprite_icon('chevron-lg-right', size: 12) + - build_path_proc = ->(scope) { admin_jobs_path(scope: scope) } + = render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope - if @all_builds.running_or_pending.any? #js-stop-jobs-modal .nav-controls - %button#js-stop-jobs-button.btn.gl-button.btn-danger{ data: { url: cancel_all_admin_jobs_path } } + = render Pajamas::ButtonComponent.new(variant: :danger, button_options: { id: 'js-stop-jobs-button', data: { url: cancel_all_admin_jobs_path } }) do = s_('AdminArea|Stop all jobs') .row-content-block.second-block diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 0fae92610f6..f23a688dd48 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -1,14 +1,18 @@ - page_title _('Projects') - params[:visibility_level] ||= [] -.top-area.scrolling-tabs-container.inner-page-scroll-tabs - = gl_tabs_nav({ class: 'gl-border-b-0 gl-overflow-x-auto gl-flex-grow-1 gl-flex-nowrap gl-webkit-scrollbar-display-none' }) do - = gl_tab_link_to _('All'), admin_projects_path(visibility_level: nil), { item_active: params[:visibility_level].empty? } - = gl_tab_link_to _('Private'), admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PRIVATE) - = gl_tab_link_to _('Internal'), admin_projects_path(visibility_level: Gitlab::VisibilityLevel::INTERNAL) - = gl_tab_link_to _('Public'), admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PUBLIC) +.top-area + .scrolling-tabs-container.inner-page-scroll-tabs.gl-flex-grow-1.gl-min-w-0.gl-w-full + .fade-left= sprite_icon('chevron-lg-left', size: 12) + .fade-right= sprite_icon('chevron-lg-right', size: 12) + = gl_tabs_nav({ class: 'scrolling-tabs nav-links gl-display-flex gl-flex-grow-1 gl-w-full nav gl-tabs-nav nav gl-tabs-nav' }) do + = gl_tab_link_to _('All'), admin_projects_path(visibility_level: nil), { item_active: params[:visibility_level].empty? } + = gl_tab_link_to _('Private'), admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + = gl_tab_link_to _('Internal'), admin_projects_path(visibility_level: Gitlab::VisibilityLevel::INTERNAL) + = gl_tab_link_to _('Public'), admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PUBLIC) - .nav-controls + + .nav-controls.gl-pl-2 .search-holder = render 'shared/projects/search_form', autofocus: true, admin_view: true - current_namespace = _('Namespace') diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml index 028edefe821..9c492a0da34 100644 --- a/app/views/dashboard/_projects_head.html.haml +++ b/app/views/dashboard/_projects_head.html.haml @@ -11,10 +11,11 @@ .page-title-controls = link_to _("New project"), new_project_path, class: "gl-button btn btn-confirm", data: { qa_selector: 'new_project_button' } -.top-area.scrolling-tabs-container.inner-page-scroll-tabs - .fade-left= sprite_icon('chevron-lg-left', size: 12) - .fade-right= sprite_icon('chevron-lg-right', size: 12) - = render 'dashboard/projects_nav' +.top-area + .scrolling-tabs-container.inner-page-scroll-tabs.gl-flex-grow-1.gl-min-w-0.gl-w-full + .fade-left= sprite_icon('chevron-lg-left', size: 12) + .fade-right= sprite_icon('chevron-lg-right', size: 12) + = render 'dashboard/projects_nav' - unless feature_project_list_filter_bar .nav-controls = render 'shared/projects/search_form' diff --git a/app/views/dashboard/_projects_nav.html.haml b/app/views/dashboard/_projects_nav.html.haml index 90b40f3c7b7..29c820ddc58 100644 --- a/app/views/dashboard/_projects_nav.html.haml +++ b/app/views/dashboard/_projects_nav.html.haml @@ -1,7 +1,7 @@ - is_your_projects_path = current_page?(dashboard_projects_path) || current_page?(root_path) - is_explore_projects_path = current_page?(explore_root_path) || current_page?(trending_explore_projects_path) || current_page?(starred_explore_projects_path) || current_page?(explore_projects_path) -= gl_tabs_nav({ class: 'scrolling-tabs nav-links gl-display-flex gl-flex-grow-1 gl-flex-nowrap gl-border-0' }) do += gl_tabs_nav({ class: 'scrolling-tabs nav-links gl-display-flex gl-flex-grow-1 gl-w-full nav gl-tabs-nav' }) do = gl_tab_link_to dashboard_projects_path, { item_active: is_your_projects_path, class: 'shortcuts-activity', data: { placement: 'right' } } do = _("Your projects") = gl_tab_counter_badge(limited_counter_with_delimiter(@total_user_projects_count)) diff --git a/app/views/explore/topics/_head.html.haml b/app/views/explore/topics/_head.html.haml index 2a96c6c97c6..f7d80d63c45 100644 --- a/app/views/explore/topics/_head.html.haml +++ b/app/views/explore/topics/_head.html.haml @@ -1,9 +1,10 @@ .page-title-holder.d-flex.align-items-center %h1.page-title.gl-font-size-h-display= _('Projects') -.top-area.scrolling-tabs-container.inner-page-scroll-tabs - .fade-left= sprite_icon('chevron-lg-left', size: 12) - .fade-right= sprite_icon('chevron-lg-right', size: 12) - = render 'dashboard/projects_nav' +.top-area + .scrolling-tabs-container.inner-page-scroll-tabs.gl-flex-grow-1.gl-min-w-0.gl-w-full + .fade-left= sprite_icon('chevron-lg-left', size: 12) + .fade-right= sprite_icon('chevron-lg-right', size: 12) + = render 'dashboard/projects_nav' .nav-controls = render 'shared/topics/search_form' diff --git a/app/views/kaminari/gitlab/_next_page.html.haml b/app/views/kaminari/gitlab/_next_page.html.haml index 9572dd91330..3ddd9fe655f 100644 --- a/app/views/kaminari/gitlab/_next_page.html.haml +++ b/app/views/kaminari/gitlab/_next_page.html.haml @@ -11,4 +11,4 @@ %li.page-item.js-next-button{ class: ('disabled' if current_page.last?) } = link_to page_url, rel: 'next', remote: remote, class: 'page-link' do = s_('Pagination|Next') - = sprite_icon('angle-right', size: 8) + = sprite_icon('chevron-lg-right', size: 8) diff --git a/app/views/kaminari/gitlab/_prev_page.html.haml b/app/views/kaminari/gitlab/_prev_page.html.haml index 4ba7ab6488a..5fb11c975de 100644 --- a/app/views/kaminari/gitlab/_prev_page.html.haml +++ b/app/views/kaminari/gitlab/_prev_page.html.haml @@ -10,5 +10,5 @@ %li.page-item.js-previous-button{ class: ('disabled' if current_page.first?) } = link_to page_url, rel: 'prev', remote: remote, class: 'page-link' do - = sprite_icon('angle-left', size: 8) + = sprite_icon('chevron-lg-left', size: 8) = s_('Pagination|Prev') diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml index c6fb3bcd559..764ddace0ad 100644 --- a/app/views/projects/commits/_commits.html.haml +++ b/app/views/projects/commits/_commits.html.haml @@ -23,7 +23,7 @@ %li.commit-header.js-commit-header %span.font-weight-bold= n_("%d previously merged commit", "%d previously merged commits", context_commits.count) % context_commits.count - if can_update_merge_request - %button.gl-button.btn.btn-default.ml-3.add-review-item-modal-trigger{ type: "button", data: { context_commits_empty: 'false' } } + = render Pajamas::ButtonComponent.new(button_options: { class: 'gl-ml-3 add-review-item-modal-trigger', data: { context_commits_empty: 'false' } }) do = _('Add/remove') %li.commits-row @@ -41,7 +41,7 @@ = n_('%s additional commit has been omitted to prevent performance issues.', '%s additional commits have been omitted to prevent performance issues.', hidden) % number_with_delimiter(hidden) - if can_update_merge_request && context_commits&.empty? - %button.gl-button.btn.btn-default.mt-3.add-review-item-modal-trigger{ type: "button", data: { context_commits_empty: 'true' } } + = render Pajamas::ButtonComponent.new(button_options: { class: 'gl-mt-5', data: { context_commits_empty: 'true' } }) do = _('Add previously merged commits') - if commits.size == 0 && context_commits.nil? diff --git a/app/views/projects/merge_requests/creations/_new_compare.html.haml b/app/views/projects/merge_requests/creations/_new_compare.html.haml index 9513b904804..8cd0d2f9e32 100644 --- a/app/views/projects/merge_requests/creations/_new_compare.html.haml +++ b/app/views/projects/merge_requests/creations/_new_compare.html.haml @@ -12,24 +12,27 @@ .clearfix .merge-request-select.dropdown = f.hidden_field :source_project_id - = dropdown_toggle @merge_request.source_project_path, { toggle: "dropdown", 'field-name': "#{f.object_name}[source_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-source-project" } + = dropdown_toggle @merge_request.source_project_path, { toggle: "dropdown", 'field-name': "#{f.object_name}[source_project_id]", disabled: @merge_request.persisted?, default_text: _("Select source project") }, { toggle_class: "js-compare-dropdown js-source-project" } .dropdown-menu.dropdown-menu-selectable.dropdown-source-project - = dropdown_title("Select source project") - = dropdown_filter("Search projects") + = dropdown_title(_("Select source project")) + = dropdown_filter(_("Search projects")) = dropdown_content do = render 'projects/merge_requests/dropdowns/project', projects: [@merge_request.source_project], selected: f.object.source_project_id .merge-request-select.dropdown = f.hidden_field :source_branch - = dropdown_toggle f.object.source_branch.presence || _("Select source branch"), { toggle: "dropdown", 'field-name': "#{f.object_name}[source_branch]", 'refs-url': refs_project_path(@source_project), selected: f.object.source_branch, qa_selector: "source_branch_dropdown" }, { toggle_class: "js-compare-dropdown js-source-branch monospace" } + = dropdown_toggle f.object.source_branch.presence || _("Select source branch"), { toggle: "dropdown", 'field-name': "#{f.object_name}[source_branch]", 'refs-url': refs_project_path(@source_project), selected: f.object.source_branch, default_text: _("Select target branch"), qa_selector: "source_branch_dropdown" }, { toggle_class: "js-compare-dropdown js-source-branch monospace" } .dropdown-menu.dropdown-menu-selectable.js-source-branch-dropdown.git-revision-dropdown = dropdown_title(_("Select source branch")) = dropdown_filter(_("Search branches")) = dropdown_content = dropdown_loading .gl-bg-gray-50.gl-rounded-base.gl-mx-2.gl-my-4 - = gl_loading_icon(css_class: 'js-source-loading gl-my-3') + .compare-commit-empty.js-source-commit-empty.gl-display-flex.gl-align-items-center.gl-p-5{ style: 'display: none;' } + = sprite_icon('branch', size: 16, css_class: 'gl-mr-3') + = _('Select a branch to compare') + = gl_loading_icon(css_class: 'js-source-loading gl-py-3') %ul.list-unstyled.mr_source_commit .col-lg-6 @@ -40,24 +43,27 @@ - projects = target_projects(@project) .merge-request-select.dropdown = f.hidden_field :target_project_id - = dropdown_toggle f.object.target_project.full_path, { toggle: "dropdown", 'field-name': "#{f.object_name}[target_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-target-project" } + = dropdown_toggle f.object.target_project.full_path, { toggle: "dropdown", 'field-name': "#{f.object_name}[target_project_id]", disabled: @merge_request.persisted?, default_text: _("Select target project") }, { toggle_class: "js-compare-dropdown js-target-project" } .dropdown-menu.dropdown-menu-selectable.dropdown-target-project - = dropdown_title("Select target project") - = dropdown_filter("Search projects") + = dropdown_title(_("Select target project")) + = dropdown_filter(_("Search projects")) = dropdown_content do = render 'projects/merge_requests/dropdowns/project', projects: projects, selected: f.object.target_project_id .merge-request-select.dropdown = f.hidden_field :target_branch - = dropdown_toggle f.object.target_branch.presence || _("Select target branch"), { toggle: "dropdown", 'field-name': "#{f.object_name}[target_branch]", 'refs-url': refs_project_path(f.object.target_project), selected: f.object.target_branch }, { toggle_class: "js-compare-dropdown js-target-branch monospace" } + = dropdown_toggle f.object.target_branch.presence || _("Select target branch"), { toggle: "dropdown", 'field-name': "#{f.object_name}[target_branch]", 'refs-url': refs_project_path(f.object.target_project), selected: f.object.target_branch, default_text: _("Select target branch") }, { toggle_class: "js-compare-dropdown js-target-branch monospace" } .dropdown-menu.dropdown-menu-selectable.js-target-branch-dropdown.git-revision-dropdown = dropdown_title(_("Select target branch")) = dropdown_filter(_("Search branches")) = dropdown_content = dropdown_loading .gl-bg-gray-50.gl-rounded-base.gl-mx-2.gl-my-4 - = gl_loading_icon(css_class: 'js-target-loading gl-my-3') + .compare-commit-empty.js-target-commit-empty.gl-display-flex.gl-align-items-center.gl-p-5{ style: 'display: none;' } + = sprite_icon('branch', size: 16, css_class: 'gl-mr-3') + = _('Select a branch to compare') + = gl_loading_icon(css_class: 'js-target-loading gl-py-3') %ul.list-unstyled.mr_target_commit - if @merge_request.errors.any? diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 83a27314552..74541222fbf 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -14,8 +14,9 @@ dom_id: dom_id(label), type: label.type } } %button.add-priority.btn.gl-button.btn-default-tertiary.btn-sm.has-tooltip{ title: _('Prioritize'), type: 'button', data: { placement: 'bottom' }, aria_label: _('Prioritize label') } = sprite_icon('star-o') - %button.remove-priority.btn.gl-button.btn-default-tertiary.btn-sm.has-tooltip{ title: _('Remove priority'), type: 'button', data: { placement: 'bottom' }, aria_label: _('Deprioritize label') } - = sprite_icon('star') + = render Pajamas::ButtonComponent.new(category: :tertiary, + icon: 'star', + button_options: { class: 'remove-priority has-tooltip', 'title': _('Remove priority'), 'aria_label': _('Deprioritize label'), data: { placement: 'bottom' } }) - if can?(current_user, :admin_label, label) %li.gl-display-inline-block = link_to label.edit_path, class: 'btn gl-button btn-default-tertiary btn-sm edit has-tooltip', title: _('Edit'), data: { placement: 'bottom' }, aria_label: _('Edit') do @@ -23,8 +24,9 @@ - if can?(current_user, :admin_label, label) %li.gl-display-inline-block .dropdown - %button{ type: 'button', class: 'btn gl-button btn-default-tertiary btn-sm js-label-options-dropdown', data: { toggle: 'dropdown' }, aria_label: _('Label actions dropdown') } - = sprite_icon('ellipsis_v') + = render Pajamas::ButtonComponent.new(category: :tertiary, + icon: 'ellipsis_v', + button_options: { class: 'js-label-options-dropdown', 'aria_label': _('Label actions dropdown'), data: { toggle: 'dropdown' } }) .dropdown-menu.dropdown-open-left %ul - if label.project_label? && label.project.group && can?(current_user, :admin_label, label.project.group) diff --git a/app/views/shared/builds/_tabs.html.haml b/app/views/shared/builds/_tabs.html.haml index 3bbd7a32bda..8e4b8d6d428 100644 --- a/app/views/shared/builds/_tabs.html.haml +++ b/app/views/shared/builds/_tabs.html.haml @@ -1,6 +1,6 @@ - count_badge_classes = 'gl-display-none gl-sm-display-inline-flex' -= gl_tabs_nav( {class: 'gl-border-b-0 gl-flex-grow-1', data: { testid: 'jobs-tabs' } } ) do += gl_tabs_nav( {class: 'scrolling-tabs nav-links gl-display-flex gl-flex-grow-1 gl-w-full nav gl-border-b-0', data: { testid: 'jobs-tabs' } } ) do = gl_tab_link_to build_path_proc.call(nil), { item_active: scope.nil? } do = _('All') = gl_tab_counter_badge(limited_counter_with_delimiter(all_builds), { class: count_badge_classes }) diff --git a/app/views/shared/issue_type/_details_content.html.haml b/app/views/shared/issue_type/_details_content.html.haml index 6a903dc3192..7c5b3fd4b3c 100644 --- a/app/views/shared/issue_type/_details_content.html.haml +++ b/app/views/shared/issue_type/_details_content.html.haml @@ -3,7 +3,7 @@ .issue-details.issuable-details.js-issue-details .detail-page-description.content-block.js-detail-page-description - #js-issuable-app{ data: { initial: issuable_initial_data(issuable).to_json, full_path: @project.full_path } } + #js-issuable-app{ data: { initial: issuable_initial_data(issuable).to_json, issuable_id: issuable.id, full_path: @project.full_path } } .title-container %h1.title.page-title.gl-font-size-h-display= markdown_field(issuable, :title) - if issuable.description.present? |