diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-08-03 15:09:25 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-08-03 15:09:25 +0300 |
commit | aeee636c18f82107ec7a489f33c944c65ad5f34e (patch) | |
tree | 2c30286279e096c9114e9a41a3ed07a83293c059 /app | |
parent | 3d8459c18b7a20d9142359bb9334b467e774eb36 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
26 files changed, 479 insertions, 206 deletions
diff --git a/app/assets/javascripts/boards/components/board_add_new_column.vue b/app/assets/javascripts/boards/components/board_add_new_column.vue index 985b9798b36..6ab9c792bbb 100644 --- a/app/assets/javascripts/boards/components/board_add_new_column.vue +++ b/app/assets/javascripts/boards/components/board_add_new_column.vue @@ -10,9 +10,10 @@ import { import { mapActions, mapGetters, mapState } from 'vuex'; import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; import BoardAddNewColumnForm from '~/boards/components/board_add_new_column_form.vue'; -import { __ } from '~/locale'; +import { __, s__ } from '~/locale'; import { createListMutations, listsQuery, BoardType, ListType } from 'ee_else_ce/boards/constants'; import boardLabelsQuery from '../graphql/board_labels.query.graphql'; +import { setError } from '../graphql/cache_updates'; import { getListByTypeId } from '../boards_util'; export default { @@ -70,6 +71,12 @@ export default { skip() { return !this.isApolloBoard; }, + error(error) { + setError({ + error, + message: s__('Boards|An error occurred while fetching labels. Please try again.'), + }); + }, }, }, computed: { @@ -102,36 +109,43 @@ export default { }, methods: { ...mapActions(['createList', 'fetchLabels', 'highlightList']), - createListApollo({ labelId }) { - return this.$apollo.mutate({ - mutation: createListMutations[this.issuableType].mutation, - variables: { - labelId, - boardId: this.boardId, - }, - update: ( - store, - { - data: { - boardListCreate: { list }, + async createListApollo({ labelId }) { + try { + await this.$apollo.mutate({ + mutation: createListMutations[this.issuableType].mutation, + variables: { + labelId, + boardId: this.boardId, + }, + update: ( + store, + { + data: { + boardListCreate: { list }, + }, }, + ) => { + const sourceData = store.readQuery({ + query: listsQuery[this.issuableType].query, + variables: this.listQueryVariables, + }); + const data = produce(sourceData, (draftData) => { + draftData[this.boardType].board.lists.nodes.push(list); + }); + store.writeQuery({ + query: listsQuery[this.issuableType].query, + variables: this.listQueryVariables, + data, + }); + this.$emit('highlight-list', list.id); }, - ) => { - const sourceData = store.readQuery({ - query: listsQuery[this.issuableType].query, - variables: this.listQueryVariables, - }); - const data = produce(sourceData, (draftData) => { - draftData[this.boardType].board.lists.nodes.push(list); - }); - store.writeQuery({ - query: listsQuery[this.issuableType].query, - variables: this.listQueryVariables, - data, - }); - this.$emit('highlight-list', list.id); - }, - }); + }); + } catch (error) { + setError({ + error, + message: s__('Boards|An error occurred while creating the list. Please try again.'), + }); + } }, addList() { if (!this.selectedLabel) { diff --git a/app/assets/javascripts/boards/components/board_app.vue b/app/assets/javascripts/boards/components/board_app.vue index ca8299ddf80..7d6bcfbe200 100644 --- a/app/assets/javascripts/boards/components/board_app.vue +++ b/app/assets/javascripts/boards/components/board_app.vue @@ -10,6 +10,7 @@ import { listsQuery } from 'ee_else_ce/boards/constants'; import { formatBoardLists } from 'ee_else_ce/boards/boards_util'; import activeBoardItemQuery from 'ee_else_ce/boards/graphql/client/active_board_item.query.graphql'; import errorQuery from '../graphql/client/error.query.graphql'; +import { setError } from '../graphql/cache_updates'; export default { i18n: { @@ -34,12 +35,12 @@ export default { ], data() { return { + boardListsApollo: {}, activeListId: '', boardId: this.initialBoardId, filterParams: { ...this.initialFilterParams }, addColumnFormVisible: false, isShowingEpicsSwimlanes: Boolean(queryToObject(window.location.search).group_by), - apolloError: null, error: null, }; }, @@ -74,8 +75,11 @@ export default { const { lists } = data[this.boardType].board; return formatBoardLists(lists); }, - error() { - this.apolloError = this.$options.i18n.fetchError; + error(error) { + setError({ + error, + message: this.$options.i18n.fetchError, + }); }, }, error: { @@ -151,13 +155,12 @@ export default { @toggleSwimlanes="isShowingEpicsSwimlanes = $event" /> <board-content - v-if="!isApolloBoard || boardListsApollo" :board-id="boardId" :add-column-form-visible="addColumnFormVisible" :is-swimlanes-on="isSwimlanesOn" :filter-params="filterParams" :board-lists-apollo="boardListsApollo" - :apollo-error="apolloError || error" + :apollo-error="error" :list-query-variables="listQueryVariables" @setActiveList="setActiveId" @setAddColumnFormVisibility="addColumnFormVisible = $event" diff --git a/app/assets/javascripts/boards/components/board_content.vue b/app/assets/javascripts/boards/components/board_content.vue index 14c781f588f..b718651a983 100644 --- a/app/assets/javascripts/boards/components/board_content.vue +++ b/app/assets/javascripts/boards/components/board_content.vue @@ -5,6 +5,7 @@ import produce from 'immer'; import Draggable from 'vuedraggable'; import { mapState, mapActions } from 'vuex'; import BoardAddNewColumn from 'ee_else_ce/boards/components/board_add_new_column.vue'; +import { s__ } from '~/locale'; import { defaultSortableOptions } from '~/sortable/constants'; import { DraggableItemTypes, @@ -13,6 +14,7 @@ import { updateListQueries, } from 'ee_else_ce/boards/constants'; import { calculateNewPosition } from 'ee_else_ce/boards/boards_util'; +import { setError } from '../graphql/cache_updates'; import BoardColumn from './board_column.vue'; export default { @@ -122,7 +124,14 @@ export default { this.highlightedLists = this.highlightedLists.filter((id) => id !== listId); }, flashAnimationDuration); }, - updateListPosition({ + dismissError() { + if (this.isApolloBoard) { + setError({ message: null, captureError: false }); + } else { + this.unsetError(); + } + }, + async updateListPosition({ item: { dataset: { listId: movedListId, draggableItemType }, }, @@ -153,7 +162,7 @@ export default { const targetPosition = this.boardListsById[displacedListId].position; try { - this.$apollo.mutate({ + await this.$apollo.mutate({ mutation: updateListQueries[this.issuableType].mutation, variables: { listId: movedListId, @@ -195,8 +204,11 @@ export default { }, }, }); - } catch { - // handle error + } catch (error) { + setError({ + error, + message: s__('Boards|An error occurred while moving the list. Please try again.'), + }); } }, }, @@ -209,7 +221,7 @@ export default { data-qa-selector="boards_list" class="gl-flex-grow-1 gl-display-flex gl-flex-direction-column gl-min-h-0" > - <gl-alert v-if="errorToDisplay" variant="danger" :dismissible="true" @dismiss="unsetError"> + <gl-alert v-if="errorToDisplay" variant="danger" :dismissible="true" @dismiss="dismissError"> {{ errorToDisplay }} </gl-alert> <component diff --git a/app/assets/javascripts/boards/components/board_content_sidebar.vue b/app/assets/javascripts/boards/components/board_content_sidebar.vue index 1b97214ff8b..9394bac4ee8 100644 --- a/app/assets/javascripts/boards/components/board_content_sidebar.vue +++ b/app/assets/javascripts/boards/components/board_content_sidebar.vue @@ -5,7 +5,7 @@ import { mapState, mapActions, mapGetters } from 'vuex'; import SidebarDropdownWidget from 'ee_else_ce/sidebar/components/sidebar_dropdown_widget.vue'; import activeBoardItemQuery from 'ee_else_ce/boards/graphql/client/active_board_item.query.graphql'; import setActiveBoardItemMutation from 'ee_else_ce/boards/graphql/client/set_active_board_item.mutation.graphql'; -import { __, sprintf } from '~/locale'; +import { __, s__, sprintf } from '~/locale'; import BoardSidebarTimeTracker from '~/boards/components/sidebar/board_sidebar_time_tracker.vue'; import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue'; import { INCIDENT } from '~/boards/constants'; @@ -18,6 +18,7 @@ import SidebarSeverityWidget from '~/sidebar/components/severity/sidebar_severit import SidebarSubscriptionsWidget from '~/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue'; import SidebarTodoWidget from '~/sidebar/components/todo_toggle/sidebar_todo_widget.vue'; import SidebarLabelsWidget from '~/sidebar/components/labels/labels_select_widget/labels_select_root.vue'; +import { setError } from '../graphql/cache_updates'; export default { components: { @@ -94,6 +95,12 @@ export default { skip() { return !this.isApolloBoard; }, + error(error) { + setError({ + error, + message: s__('Boards|An error occurred while selecting the card. Please try again.'), + }); + }, }, }, computed: { diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue index b4249c63b4d..f5e1e51cd0c 100644 --- a/app/assets/javascripts/boards/components/board_list.vue +++ b/app/assets/javascripts/boards/components/board_list.vue @@ -3,7 +3,7 @@ import { GlLoadingIcon, GlIntersectionObserver } from '@gitlab/ui'; import Draggable from 'vuedraggable'; import { mapActions, mapState } from 'vuex'; import { STATUS_CLOSED } from '~/issues/constants'; -import { sprintf, __ } from '~/locale'; +import { sprintf, __, s__ } from '~/locale'; import { defaultSortableOptions } from '~/sortable/constants'; import { sortableStart, sortableEnd } from '~/sortable/utils'; import Tracking from '~/tracking'; @@ -122,6 +122,12 @@ export default { context: { isSingleRequest: true, }, + error(error) { + setError({ + error, + message: s__('Boards|An error occurred while fetching a list. Please try again.'), + }); + }, }, toList: { query() { @@ -142,8 +148,16 @@ export default { context: { isSingleRequest: true, }, - error() { - // handle error + error(error) { + setError({ + error, + message: sprintf( + s__('Boards|An error occurred while moving the %{issuableType}. Please try again.'), + { + issuableType: this.isEpicBoard ? 'epic' : 'issue', + }, + ), + }); }, }, }, @@ -442,8 +456,16 @@ export default { }, }, }); - } catch { - // handle error + } catch (error) { + setError({ + error, + message: sprintf( + s__('Boards|An error occurred while moving the %{issuableType}. Please try again.'), + { + issuableType: this.isEpicBoard ? 'epic' : 'issue', + }, + ), + }); } }, updateCacheAfterMovingItem({ issuableMoveList, fromListId, toListId, newIndex, cache }) { @@ -494,52 +516,64 @@ export default { }); } }, - moveToPosition(positionInList, oldIndex, item) { - this.$apollo.mutate({ - mutation: listIssuablesQueries[this.issuableType].moveMutation, - variables: { - ...moveItemVariables({ - iid: item.iid, - epicId: item.id, - fromListId: this.currentList.id, - toListId: this.currentList.id, - isIssue: !this.isEpicBoard, - boardId: this.boardId, - itemToMove: item, - }), - positionInList, - withColor: this.isEpicBoard && this.glFeatures.epicColorHighlight, - }, - optimisticResponse: { - issuableMoveList: { - issuable: item, - errors: [], + async moveToPosition(positionInList, oldIndex, item) { + try { + await this.$apollo.mutate({ + mutation: listIssuablesQueries[this.issuableType].moveMutation, + variables: { + ...moveItemVariables({ + iid: item.iid, + epicId: item.id, + fromListId: this.currentList.id, + toListId: this.currentList.id, + isIssue: !this.isEpicBoard, + boardId: this.boardId, + itemToMove: item, + }), + positionInList, + withColor: this.isEpicBoard && this.glFeatures.epicColorHighlight, }, - }, - update: (cache, { data: { issuableMoveList } }) => { - const { issuable } = issuableMoveList; - removeItemFromList({ - query: listIssuablesQueries[this.issuableType].query, - variables: { ...this.listQueryVariables, id: this.currentList.id }, - boardType: this.boardType, - id: issuable.id, - issuableType: this.issuableType, - cache, - }); - if (positionInList === 0 || this.listItemsCount <= this.boardListItems.length) { - const newIndex = positionInList === 0 ? 0 : this.boardListItems.length - 1; - addItemToList({ + optimisticResponse: { + issuableMoveList: { + issuable: item, + errors: [], + }, + }, + update: (cache, { data: { issuableMoveList } }) => { + const { issuable } = issuableMoveList; + removeItemFromList({ query: listIssuablesQueries[this.issuableType].query, variables: { ...this.listQueryVariables, id: this.currentList.id }, - issuable, - newIndex, boardType: this.boardType, + id: issuable.id, issuableType: this.issuableType, cache, }); - } - }, - }); + if (positionInList === 0 || this.listItemsCount <= this.boardListItems.length) { + const newIndex = positionInList === 0 ? 0 : this.boardListItems.length - 1; + addItemToList({ + query: listIssuablesQueries[this.issuableType].query, + variables: { ...this.listQueryVariables, id: this.currentList.id }, + issuable, + newIndex, + boardType: this.boardType, + issuableType: this.issuableType, + cache, + }); + } + }, + }); + } catch (error) { + setError({ + error, + message: sprintf( + s__('Boards|An error occurred while moving the %{issuableType}. Please try again.'), + { + issuableType: this.isEpicBoard ? 'epic' : 'issue', + }, + ), + }); + } }, async addListItem(input) { this.toggleForm(); @@ -583,7 +617,7 @@ export default { } catch (error) { setError({ message: sprintf( - __('An error occurred while creating the %{issuableType}. Please try again.'), + s__('Boards|An error occurred while creating the %{issuableType}. Please try again.'), { issuableType: this.isEpicBoard ? 'epic' : 'issue', }, diff --git a/app/assets/javascripts/boards/components/board_list_header.vue b/app/assets/javascripts/boards/components/board_list_header.vue index 8db86d0e894..e0a05480c70 100644 --- a/app/assets/javascripts/boards/components/board_list_header.vue +++ b/app/assets/javascripts/boards/components/board_list_header.vue @@ -30,6 +30,7 @@ import { toggleCollapsedMutations, } from 'ee_else_ce/boards/constants'; import eventHub from '../eventhub'; +import { setError } from '../graphql/cache_updates'; import ItemCount from './item_count.vue'; export default { @@ -39,6 +40,9 @@ export default { listSettings: s__('Boards|Edit list settings'), expand: s__('Boards|Expand'), collapse: s__('Boards|Collapse'), + fetchError: s__( + "Boards|An error occurred while fetching list's information. Please try again.", + ), }, components: { GlButton, @@ -206,6 +210,12 @@ export default { context: { isSingleRequest: true, }, + error(error) { + setError({ + error, + message: this.$options.i18n.fetchError, + }); + }, }, }, created() { @@ -293,8 +303,11 @@ export default { }, }, }); - } catch { - this.$emit('error'); + } catch (error) { + setError({ + error, + message: s__('Boards|An error occurred while updating the list. Please try again.'), + }); } } else { this.updateList({ listId: this.list.id, collapsed }); diff --git a/app/assets/javascripts/boards/components/board_settings_sidebar.vue b/app/assets/javascripts/boards/components/board_settings_sidebar.vue index 0f43aae3936..1c5a10bdb66 100644 --- a/app/assets/javascripts/boards/components/board_settings_sidebar.vue +++ b/app/assets/javascripts/boards/components/board_settings_sidebar.vue @@ -11,10 +11,11 @@ import { deleteListQueries, } from 'ee_else_ce/boards/constants'; import { isScopedLabel } from '~/lib/utils/common_utils'; -import { __ } from '~/locale'; +import { __, s__ } from '~/locale'; import eventHub from '~/sidebar/event_hub'; import Tracking from '~/tracking'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import { setError } from '../graphql/cache_updates'; export default { listSettingsText: __('List settings'), @@ -131,23 +132,30 @@ export default { } }, async deleteList(listId) { - await this.$apollo.mutate({ - mutation: deleteListQueries[this.issuableType].mutation, - variables: { - listId, - }, - update: (store) => { - store.updateQuery( - { query: listsQuery[this.issuableType].query, variables: this.queryVariables }, - (sourceData) => - produce(sourceData, (draftData) => { - draftData[this.boardType].board.lists.nodes = draftData[ - this.boardType - ].board.lists.nodes.filter((list) => list.id !== listId); - }), - ); - }, - }); + try { + await this.$apollo.mutate({ + mutation: deleteListQueries[this.issuableType].mutation, + variables: { + listId, + }, + update: (store) => { + store.updateQuery( + { query: listsQuery[this.issuableType].query, variables: this.queryVariables }, + (sourceData) => + produce(sourceData, (draftData) => { + draftData[this.boardType].board.lists.nodes = draftData[ + this.boardType + ].board.lists.nodes.filter((list) => list.id !== listId); + }), + ); + }, + }); + } catch (error) { + setError({ + error, + message: s__('Boards|An error occurred while deleting the list. Please try again.'), + }); + } }, }, }; diff --git a/app/assets/javascripts/boards/components/board_top_bar.vue b/app/assets/javascripts/boards/components/board_top_bar.vue index fd9043a561f..2b8418333a8 100644 --- a/app/assets/javascripts/boards/components/board_top_bar.vue +++ b/app/assets/javascripts/boards/components/board_top_bar.vue @@ -1,8 +1,10 @@ <script> import BoardAddNewColumnTrigger from '~/boards/components/board_add_new_column_trigger.vue'; +import { s__ } from '~/locale'; import BoardsSelector from 'ee_else_ce/boards/components/boards_selector.vue'; import IssueBoardFilteredSearch from 'ee_else_ce/boards/components/issue_board_filtered_search.vue'; import { getBoardQuery } from 'ee_else_ce/boards/boards_util'; +import { setError } from '../graphql/cache_updates'; import ConfigToggle from './config_toggle.vue'; import NewBoardButton from './new_board_button.vue'; import ToggleFocus from './toggle_focus.vue'; @@ -70,6 +72,12 @@ export default { labels: board.labels?.nodes, }; }, + error(error) { + setError({ + error, + message: s__('Boards|An error occurred while fetching board details. Please try again.'), + }); + }, }, }, computed: { diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue index fddb58c45fe..2f87e3fd44f 100644 --- a/app/assets/javascripts/boards/components/boards_selector.vue +++ b/app/assets/javascripts/boards/components/boards_selector.vue @@ -24,12 +24,16 @@ 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 { setError } from '../graphql/cache_updates'; import { fullBoardId } from '../boards_util'; const MIN_BOARDS_TO_VIEW_RECENT = 10; export default { name: 'BoardsSelector', + i18n: { + fetchBoardsError: s__('Boards|An error occurred while fetching boards. Please try again.'), + }, components: { BoardForm, GlLoadingIcon, @@ -143,7 +147,7 @@ export default { eventHub.$off('showBoardModal', this.showPage); }, methods: { - ...mapActions(['setError', 'fetchBoard', 'unsetActiveId']), + ...mapActions(['fetchBoard', 'unsetActiveId']), fullBoardId(boardId) { return fullBoardId(boardId); }, @@ -179,6 +183,12 @@ export default { watchLoading: (isLoading) => { this.loadingBoards = isLoading; }, + error(error) { + setError({ + error, + message: this.$options.i18n.fetchBoardsError, + }); + }, }); this.loadRecentBoards(); @@ -193,6 +203,14 @@ export default { watchLoading: (isLoading) => { this.loadingRecentBoards = isLoading; }, + error(error) { + setError({ + error, + message: s__( + 'Boards|An error occurred while fetching recent boards. Please try again.', + ), + }); + }, }); }, addBoard(board) { @@ -267,9 +285,6 @@ export default { } }, }, - i18n: { - errorFetchingBoard: s__('Board|An error occurred while fetching the board, please try again.'), - }, }; </script> diff --git a/app/assets/javascripts/diffs/components/diff_row.vue b/app/assets/javascripts/diffs/components/diff_row.vue index 7c0937db221..52a8cf29052 100644 --- a/app/assets/javascripts/diffs/components/diff_row.vue +++ b/app/assets/javascripts/diffs/components/diff_row.vue @@ -326,6 +326,7 @@ export default { </div> <div :title="$options.coverageStateLeft(props).text" + :data-tooltip-custom-class="$options.coverageStateLeft(props).class" :class="[ $options.parallelViewLeftLineType(props), $options.coverageStateLeft(props).class, @@ -466,6 +467,7 @@ export default { </div> <div :title="$options.coverageStateRight(props).text" + :data-tooltip-custom-class="$options.coverageStateRight(props).class" :class="[ props.line.right.type, $options.coverageStateRight(props).class, diff --git a/app/assets/javascripts/jobs/components/log/line.vue b/app/assets/javascripts/jobs/components/log/line.vue index 77db932c29a..3c9c5097122 100644 --- a/app/assets/javascripts/jobs/components/log/line.vue +++ b/app/assets/javascripts/jobs/components/log/line.vue @@ -1,7 +1,7 @@ <!-- eslint-disable vue/multi-word-component-names --> <script> +import { getLocationHash } from '~/lib/utils/url_utility'; import { linkRegex } from '../../utils'; - import LineNumber from './line_number.vue'; export default { @@ -64,10 +64,19 @@ export default { }); } + if (window.location.hash) { + const hash = getLocationHash(); + const lineToMatch = `L${line.lineNumber + 1}`; + + if (hash === lineToMatch) { + applyHighlight = true; + } + } + return h( 'div', { - class: ['js-line', 'log-line', applyHighlight ? 'gl-bg-gray-500' : ''], + class: ['js-line', 'log-line', applyHighlight ? 'gl-bg-gray-700' : ''], }, [ h(LineNumber, { diff --git a/app/assets/javascripts/jobs/components/log/line_header.vue b/app/assets/javascripts/jobs/components/log/line_header.vue index de774e8408b..115b090b32a 100644 --- a/app/assets/javascripts/jobs/components/log/line_header.vue +++ b/app/assets/javascripts/jobs/components/log/line_header.vue @@ -1,5 +1,6 @@ <script> import { GlIcon } from '@gitlab/ui'; +import { getLocationHash } from '~/lib/utils/url_utility'; import DurationBadge from './duration_badge.vue'; import LineNumber from './line_number.vue'; @@ -32,6 +33,12 @@ export default { iconName() { return this.isClosed ? 'chevron-lg-right' : 'chevron-lg-down'; }, + applyHighlight() { + const hash = getLocationHash(); + const lineToMatch = `L${this.line.lineNumber + 1}`; + + return hash === lineToMatch; + }, }, methods: { handleOnClick() { @@ -44,6 +51,7 @@ export default { <template> <div class="log-line collapsible-line d-flex justify-content-between ws-normal gl-align-items-flex-start" + :class="{ 'gl-bg-gray-700': applyHighlight }" role="button" @click="handleOnClick" > diff --git a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue index fac070d6e47..128c744f282 100644 --- a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue +++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue @@ -84,6 +84,11 @@ export default { keys: ['request', 'body'], }, { + metric: 'ch', + header: s__('PerformanceBar|ClickHouse queries'), + keys: ['sql', 'database', 'statistics'], + }, + { metric: 'external-http', title: 'external', header: s__('PerformanceBar|External Http calls'), diff --git a/app/assets/javascripts/repository/components/blob_button_group.vue b/app/assets/javascripts/repository/components/blob_button_group.vue index d79ccde61a8..99b861ca104 100644 --- a/app/assets/javascripts/repository/components/blob_button_group.vue +++ b/app/assets/javascripts/repository/components/blob_button_group.vue @@ -76,6 +76,11 @@ export default { type: Boolean, required: true, }, + isUsingLfs: { + type: Boolean, + required: false, + default: false, + }, }, computed: { replaceModalTitle() { @@ -148,6 +153,7 @@ export default { :can-push-code="canPushCode" :can-push-to-branch="canPushToBranch" :empty-repo="emptyRepo" + :is-using-lfs="isUsingLfs" /> </div> </template> diff --git a/app/assets/javascripts/repository/components/blob_content_viewer.vue b/app/assets/javascripts/repository/components/blob_content_viewer.vue index bdb78bab909..310095b0dd4 100644 --- a/app/assets/javascripts/repository/components/blob_content_viewer.vue +++ b/app/assets/javascripts/repository/components/blob_content_viewer.vue @@ -233,7 +233,9 @@ export default { const { createMergeRequestIn, forkProject } = this.userPermissions; const { canModifyBlob } = this.blobInfo; - return this.isLoggedIn && !canModifyBlob && createMergeRequestIn && forkProject; + return ( + this.isLoggedIn && !this.isUsingLfs && !canModifyBlob && createMergeRequestIn && forkProject + ); }, forkPath() { const forkPaths = { @@ -406,6 +408,7 @@ export default { :is-locked="Boolean(pathLockedByUser)" :can-lock="canLock" :show-fork-suggestion="showForkSuggestion" + :is-using-lfs="isUsingLfs" @fork="setForkTarget('view')" /> </template> diff --git a/app/assets/javascripts/repository/components/blob_viewers/index.js b/app/assets/javascripts/repository/components/blob_viewers/index.js index 368f42e0064..d434700b29f 100644 --- a/app/assets/javascripts/repository/components/blob_viewers/index.js +++ b/app/assets/javascripts/repository/components/blob_viewers/index.js @@ -1,4 +1,6 @@ -const viewers = { +import { TEXT_FILE_TYPE, JSON_LANGUAGE } from '../../constants'; + +export const viewers = { csv: () => import('./csv_viewer.vue'), download: () => import('./download_viewer.vue'), image: () => import('./image_viewer.vue'), @@ -18,7 +20,7 @@ const viewers = { export const loadViewer = (type, isUsingLfs, hljsWorkerEnabled, language) => { let viewer = viewers[type]; - if (hljsWorkerEnabled && language === 'json') { + if (hljsWorkerEnabled && language === JSON_LANGUAGE && type === TEXT_FILE_TYPE) { // The New Source Viewer currently only supports JSON files. // More language support will be added in: https://gitlab.com/gitlab-org/gitlab/-/issues/415753 viewer = () => import('~/vue_shared/components/source_viewer/source_viewer_new.vue'); diff --git a/app/assets/javascripts/repository/components/delete_blob_modal.vue b/app/assets/javascripts/repository/components/delete_blob_modal.vue index cbdf6ef9ccd..da3c84c954f 100644 --- a/app/assets/javascripts/repository/components/delete_blob_modal.vue +++ b/app/assets/javascripts/repository/components/delete_blob_modal.vue @@ -1,8 +1,18 @@ <script> -import { GlModal, GlFormGroup, GlFormInput, GlFormTextarea, GlToggle, GlForm } from '@gitlab/ui'; +import { + GlModal, + GlFormGroup, + GlFormInput, + GlFormTextarea, + GlToggle, + GlForm, + GlSprintf, + GlLink, +} from '@gitlab/ui'; import csrf from '~/lib/utils/csrf'; -import { __ } from '~/locale'; +import { __, s__ } from '~/locale'; import validation from '~/vue_shared/directives/validation'; +import { helpPagePath } from '~/helpers/help_page_helper'; import { SECONDARY_OPTIONS_TEXT, COMMIT_LABEL, @@ -28,8 +38,19 @@ export default { GlFormTextarea, GlToggle, GlForm, + GlSprintf, + GlLink, }, i18n: { + LFS_WARNING_TITLE: __("The file you're about to delete is tracked by LFS"), + LFS_WARNING_PRIMARY_CONTENT: s__( + 'BlobViewer|If you delete the file, it will be removed from the branch %{branch}.', + ), + LFS_WARNING_SECONDARY_CONTENT: s__( + 'BlobViewer|This file will still take up space in your LFS storage. %{linkStart}How do I remove tracked objects from Git LFS?%{linkEnd}', + ), + LFS_CONTINUE_TEXT: __('Continue…'), + LFS_CANCEL_TEXT: __('Cancel'), PRIMARY_OPTIONS_TEXT: __('Delete file'), SECONDARY_OPTIONS_TEXT, COMMIT_LABEL, @@ -79,6 +100,11 @@ export default { type: Boolean, required: true, }, + isUsingLfs: { + type: Boolean, + required: false, + default: false, + }, }, data() { const form = { @@ -91,6 +117,7 @@ export default { }, }; return { + lfsWarningDismissed: false, loading: false, createNewMr: true, error: '', @@ -99,7 +126,7 @@ export default { }, computed: { primaryOptions() { - return { + const defaultOptions = { text: this.$options.i18n.PRIMARY_OPTIONS_TEXT, attributes: { variant: 'danger', @@ -107,6 +134,13 @@ export default { disabled: this.loading || !this.form.state, }, }; + + const lfsWarningOptions = { + text: this.$options.i18n.LFS_CONTINUE_TEXT, + attributes: { variant: 'confirm' }, + }; + + return this.showLfsWarning ? lfsWarningOptions : defaultOptions; }, cancelOptions() { return { @@ -139,14 +173,39 @@ export default { (hasFirstLineExceedMaxLength || hasOtherLineExceedMaxLength) ); }, - /* eslint-enable dot-notation */ + showLfsWarning() { + return this.isUsingLfs && !this.lfsWarningDismissed; + }, + title() { + return this.showLfsWarning ? this.$options.i18n.LFS_WARNING_TITLE : this.modalTitle; + }, + showDeleteForm() { + return !this.isUsingLfs || (this.isUsingLfs && this.lfsWarningDismissed); + }, }, methods: { show() { this.$refs[this.modalId].show(); + this.lfsWarningDismissed = false; + }, + cancel() { + this.$refs[this.modalId].hide(); }, - submitForm(e) { + async handleContinueLfsWarning() { + this.lfsWarningDismissed = true; + await this.$nextTick(); + this.$refs.message?.$el.focus(); + }, + async handlePrimaryAction(e) { e.preventDefault(); // Prevent modal from closing + + if (this.showLfsWarning) { + this.lfsWarningDismissed = true; + await this.$nextTick(); + this.$refs.message?.$el.focus(); + return; + } + this.form.showValidation = true; if (!this.form.state) { @@ -158,6 +217,7 @@ export default { this.$refs.form.$el.submit(); }, }, + deleteLfsHelpPath: helpPagePath('topics/git/lfs/index', { anchor: 'removing-objects-from-lfs' }), }; </script> @@ -167,65 +227,85 @@ export default { v-bind="$attrs" data-testid="modal-delete" :modal-id="modalId" - :title="modalTitle" + :title="title" :action-primary="primaryOptions" :action-cancel="cancelOptions" - @primary="submitForm" + @primary="handlePrimaryAction" > - <gl-form ref="form" novalidate :action="deletePath" method="post"> - <input type="hidden" name="_method" value="delete" /> - <input :value="$options.csrf.token" type="hidden" name="authenticity_token" /> - <template v-if="emptyRepo"> - <input type="hidden" name="branch_name" :value="originalBranch" class="js-branch-name" /> - </template> - <template v-else> - <input type="hidden" name="original_branch" :value="originalBranch" /> - <input - v-if="createNewMr || !canPushToBranch" - type="hidden" - name="create_merge_request" - value="1" - /> - <gl-form-group - :label="$options.i18n.COMMIT_LABEL" - label-for="commit_message" - :invalid-feedback="form.fields['commit_message'].feedback" - > - <gl-form-textarea - v-model="form.fields['commit_message'].value" - v-validation:[form.showValidation] - name="commit_message" - data-qa-selector="commit_message_field" - :state="form.fields['commit_message'].state" - :disabled="loading" - required + <div v-if="showLfsWarning"> + <p> + <gl-sprintf :message="$options.i18n.LFS_WARNING_PRIMARY_CONTENT"> + <template #branch> + <code>{{ targetBranch }}</code> + </template> + </gl-sprintf> + </p> + + <p> + <gl-sprintf :message="$options.i18n.LFS_WARNING_SECONDARY_CONTENT"> + <template #link="{ content }"> + <gl-link :href="$options.deleteLfsHelpPath">{{ content }}</gl-link> + </template> + </gl-sprintf> + </p> + </div> + <div v-if="showDeleteForm"> + <gl-form ref="form" novalidate :action="deletePath" method="post"> + <input type="hidden" name="_method" value="delete" /> + <input :value="$options.csrf.token" type="hidden" name="authenticity_token" /> + <template v-if="emptyRepo"> + <input type="hidden" name="branch_name" :value="originalBranch" class="js-branch-name" /> + </template> + <template v-else> + <input type="hidden" name="original_branch" :value="originalBranch" /> + <input + v-if="createNewMr || !canPushToBranch" + type="hidden" + name="create_merge_request" + value="1" /> - <p v-if="showHint" class="form-text gl-text-gray-600" data-testid="hint"> - {{ $options.i18n.COMMIT_MESSAGE_HINT }} - </p> - </gl-form-group> - <gl-form-group - v-if="canPushCode" - :label="$options.i18n.TARGET_BRANCH_LABEL" - label-for="branch_name" - :invalid-feedback="form.fields['branch_name'].feedback" - > - <gl-form-input - v-model="form.fields['branch_name'].value" - v-validation:[form.showValidation] - :state="form.fields['branch_name'].state" + <gl-form-group + :label="$options.i18n.COMMIT_LABEL" + label-for="commit_message" + :invalid-feedback="form.fields['commit_message'].feedback" + > + <gl-form-textarea + ref="message" + v-model="form.fields['commit_message'].value" + v-validation:[form.showValidation] + name="commit_message" + data-qa-selector="commit_message_field" + :state="form.fields['commit_message'].state" + :disabled="loading" + required + /> + <p v-if="showHint" class="form-text gl-text-gray-600" data-testid="hint"> + {{ $options.i18n.COMMIT_MESSAGE_HINT }} + </p> + </gl-form-group> + <gl-form-group + v-if="canPushCode" + :label="$options.i18n.TARGET_BRANCH_LABEL" + label-for="branch_name" + :invalid-feedback="form.fields['branch_name'].feedback" + > + <gl-form-input + v-model="form.fields['branch_name'].value" + v-validation:[form.showValidation] + :state="form.fields['branch_name'].state" + :disabled="loading" + name="branch_name" + required + /> + </gl-form-group> + <gl-toggle + v-if="showCreateNewMrToggle" + v-model="createNewMr" :disabled="loading" - name="branch_name" - required + :label="$options.i18n.TOGGLE_CREATE_MR_LABEL" /> - </gl-form-group> - <gl-toggle - v-if="showCreateNewMrToggle" - v-model="createNewMr" - :disabled="loading" - :label="$options.i18n.TOGGLE_CREATE_MR_LABEL" - /> - </template> - </gl-form> + </template> + </gl-form> + </div> </gl-modal> </template> diff --git a/app/assets/javascripts/repository/constants.js b/app/assets/javascripts/repository/constants.js index 4327b237c8c..3079ef0bfbb 100644 --- a/app/assets/javascripts/repository/constants.js +++ b/app/assets/javascripts/repository/constants.js @@ -83,6 +83,8 @@ export const DEFAULT_BLOB_INFO = { }, }; +export const JSON_LANGUAGE = 'json'; +export const OPENAPI_FILE_TYPE = 'openapi'; export const TEXT_FILE_TYPE = 'text'; export const LFS_STORAGE = 'lfs'; diff --git a/app/assets/javascripts/tooltips/components/tooltips.vue b/app/assets/javascripts/tooltips/components/tooltips.vue index 5e8f4574b28..26479aeffcf 100644 --- a/app/assets/javascripts/tooltips/components/tooltips.vue +++ b/app/assets/javascripts/tooltips/components/tooltips.vue @@ -8,9 +8,14 @@ const getTooltipTitle = (element) => { return element.getAttribute('title') || element.dataset.title; }; +const getTooltipCustomClass = (element) => { + return element.dataset.tooltipCustomClass; +}; + const newTooltip = (element, config = {}) => { const { placement, container, boundary, html, triggers } = element.dataset; const title = getTooltipTitle(element); + const customClass = getTooltipCustomClass(element); return { id: uniqueId('gl-tooltip'), @@ -22,6 +27,7 @@ const newTooltip = (element, config = {}) => { boundary, triggers, disabled: !title, + customClass, ...config, }; }; @@ -116,6 +122,7 @@ export default { :boundary="tooltip.boundary" :disabled="tooltip.disabled" :show="tooltip.show" + :custom-class="tooltip.customClass" @hidden="$emit('hidden', tooltip)" > <span v-if="tooltip.html" v-safe-html:[$options.safeHtmlConfig]="tooltip.title"></span> diff --git a/app/assets/javascripts/work_items/components/work_item_links/okr_actions_split_button.vue b/app/assets/javascripts/work_items/components/work_item_links/okr_actions_split_button.vue index dc5bcdc3dcc..c5be1a3ead3 100644 --- a/app/assets/javascripts/work_items/components/work_item_links/okr_actions_split_button.vue +++ b/app/assets/javascripts/work_items/components/work_item_links/okr_actions_split_button.vue @@ -1,7 +1,7 @@ <script> -import { GlDropdown, GlDropdownSectionHeader, GlDropdownItem, GlDropdownDivider } from '@gitlab/ui'; +import { GlDisclosureDropdown } from '@gitlab/ui'; -import { s__ } from '~/locale'; +import { s__, __ } from '~/locale'; const objectiveActionItems = [ { @@ -29,10 +29,30 @@ export default { keyResultActionItems, objectiveActionItems, components: { - GlDropdown, - GlDropdownSectionHeader, - GlDropdownItem, - GlDropdownDivider, + GlDisclosureDropdown, + }, + computed: { + objectiveDropdownItems() { + return { + name: __('Objective'), + items: this.$options.objectiveActionItems.map((item) => ({ + text: item.title, + action: () => this.change(item), + })), + }; + }, + keyResultDropdownItems() { + return { + name: __('Key result'), + items: this.$options.keyResultActionItems.map((item) => ({ + text: item.title, + action: () => this.change(item), + })), + }; + }, + dropdownItems() { + return [this.objectiveDropdownItems, this.keyResultDropdownItems]; + }, }, methods: { change({ eventName }) { @@ -43,24 +63,10 @@ export default { </script> <template> - <gl-dropdown :text="__('Add')" size="small" right> - <gl-dropdown-section-header>{{ __('Objective') }}</gl-dropdown-section-header> - <gl-dropdown-item - v-for="item in $options.objectiveActionItems" - :key="item.eventName" - @click="change(item)" - > - {{ item.title }} - </gl-dropdown-item> - - <gl-dropdown-divider /> - <gl-dropdown-section-header>{{ __('Key result') }}</gl-dropdown-section-header> - <gl-dropdown-item - v-for="item in $options.keyResultActionItems" - :key="item.eventName" - @click="change(item)" - > - {{ item.title }} - </gl-dropdown-item> - </gl-dropdown> + <gl-disclosure-dropdown + :toggle-text="__('Add')" + size="small" + placement="right" + :items="dropdownItems" + /> </template> diff --git a/app/assets/stylesheets/framework/diffs.scss b/app/assets/stylesheets/framework/diffs.scss index 3b5e573f783..4bf109a0bff 100644 --- a/app/assets/stylesheets/framework/diffs.scss +++ b/app/assets/stylesheets/framework/diffs.scss @@ -929,3 +929,13 @@ table.code { border-bottom: 0; } } + +.tooltip { + &.coverage { + left: -3px !important; + } + + &.no-coverage { + left: -2px !important; + } +} diff --git a/app/models/concerns/web_hooks/auto_disabling.rb b/app/models/concerns/web_hooks/auto_disabling.rb index 2ad2e47ec4e..d6c900f75a9 100644 --- a/app/models/concerns/web_hooks/auto_disabling.rb +++ b/app/models/concerns/web_hooks/auto_disabling.rb @@ -36,7 +36,9 @@ module WebHooks # - and either: # - disabled_until is nil (i.e. this was set by WebHook#fail!) # - or disabled_until is in the future (i.e. this was set by WebHook#backoff!) + # - OR silent mode is enabled. scope :disabled, -> do + return all if Gitlab::SilentMode.enabled? return none unless auto_disabling_enabled? where( @@ -52,7 +54,9 @@ module WebHooks # - OR we have exceeded the grace period and neither of the following is true: # - disabled_until is nil (i.e. this was set by WebHook#fail!) # - disabled_until is in the future (i.e. this was set by WebHook#backoff!) + # - AND silent mode is not enabled. scope :executable, -> do + return none if Gitlab::SilentMode.enabled? return all unless auto_disabling_enabled? where( diff --git a/app/models/project.rb b/app/models/project.rb index f39afcbf161..5ed6039b9df 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -2467,7 +2467,7 @@ class Project < ApplicationRecord break unless pages_enabled? variables.append(key: 'CI_PAGES_DOMAIN', value: Gitlab.config.pages.host) - variables.append(key: 'CI_PAGES_URL', value: Gitlab::Pages::UrlBuilder.new(self).pages_url) + variables.append(key: 'CI_PAGES_URL', value: Gitlab::Pages::UrlBuilder.new(self).pages_url(with_unique_domain: true)) end end diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb index 6837bc47035..5bad2a1583c 100644 --- a/app/services/web_hook_service.rb +++ b/app/services/web_hook_service.rb @@ -57,6 +57,11 @@ class WebHookService end def execute + if Gitlab::SilentMode.enabled? + log_silent_mode_enabled + return ServiceResponse.error(message: 'Silent mode enabled') + end + return ServiceResponse.error(message: 'Hook disabled') if disabled? if recursion_blocked? @@ -98,6 +103,7 @@ class WebHookService def async_execute Gitlab::ApplicationContext.with_context(hook.application_context) do + break log_silent_mode_enabled if Gitlab::SilentMode.enabled? break log_rate_limited if rate_limit! break log_recursion_blocked if recursion_blocked? @@ -237,6 +243,10 @@ class WebHookService ) end + def log_silent_mode_enabled + log_auth_error('GitLab is in silent mode') + end + def log_auth_error(message, params = {}) Gitlab::AuthLogger.error( params.merge( diff --git a/app/views/help/instance_configuration/_size_limits.html.haml b/app/views/help/instance_configuration/_size_limits.html.haml index 0176a9093c0..4f085ca5db6 100644 --- a/app/views/help/instance_configuration/_size_limits.html.haml +++ b/app/views/help/instance_configuration/_size_limits.html.haml @@ -42,8 +42,8 @@ %td= _('Maximum snippet size') %td= instance_configuration_human_size_cell(size_limits[:snippet_size_limit]) %tr - %td= s_('Import|Maximum import remote file size (MiB)') + %td= s_('Import|Maximum import remote file size (MB)') %td= instance_configuration_human_size_cell(size_limits[:max_import_remote_file_size]) %tr - %td= s_('BulkImport|Direct Transfer maximum download file size (MiB)') + %td= s_('BulkImport|Direct transfer maximum download file size (MB)') %td= instance_configuration_human_size_cell(size_limits[:max_import_remote_file_size]) diff --git a/app/workers/users/deactivate_dormant_users_worker.rb b/app/workers/users/deactivate_dormant_users_worker.rb index d024109e754..87566bff467 100644 --- a/app/workers/users/deactivate_dormant_users_worker.rb +++ b/app/workers/users/deactivate_dormant_users_worker.rb @@ -15,16 +15,21 @@ module Users return unless ::Gitlab::CurrentSettings.current_application_settings.deactivate_dormant_users - deactivate_users(User.dormant) - deactivate_users(User.with_no_activity) + admin_bot = User.admin_bot + return unless admin_bot + + deactivate_users(User.dormant, admin_bot) + deactivate_users(User.with_no_activity, admin_bot) end private - def deactivate_users(scope) + def deactivate_users(scope, admin_bot) with_context(caller_id: self.class.name.to_s) do scope.each_batch do |batch| - batch.each(&:deactivate) + batch.each do |user| + Users::DeactivateService.new(admin_bot).execute(user) + end end end end |