Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-08-03 15:09:25 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-08-03 15:09:25 +0300
commitaeee636c18f82107ec7a489f33c944c65ad5f34e (patch)
tree2c30286279e096c9114e9a41a3ed07a83293c059 /app/assets
parent3d8459c18b7a20d9142359bb9334b467e774eb36 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets')
-rw-r--r--app/assets/javascripts/boards/components/board_add_new_column.vue72
-rw-r--r--app/assets/javascripts/boards/components/board_app.vue13
-rw-r--r--app/assets/javascripts/boards/components/board_content.vue22
-rw-r--r--app/assets/javascripts/boards/components/board_content_sidebar.vue9
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue124
-rw-r--r--app/assets/javascripts/boards/components/board_list_header.vue17
-rw-r--r--app/assets/javascripts/boards/components/board_settings_sidebar.vue44
-rw-r--r--app/assets/javascripts/boards/components/board_top_bar.vue8
-rw-r--r--app/assets/javascripts/boards/components/boards_selector.vue23
-rw-r--r--app/assets/javascripts/diffs/components/diff_row.vue2
-rw-r--r--app/assets/javascripts/jobs/components/log/line.vue13
-rw-r--r--app/assets/javascripts/jobs/components/log/line_header.vue8
-rw-r--r--app/assets/javascripts/performance_bar/components/performance_bar_app.vue5
-rw-r--r--app/assets/javascripts/repository/components/blob_button_group.vue6
-rw-r--r--app/assets/javascripts/repository/components/blob_content_viewer.vue5
-rw-r--r--app/assets/javascripts/repository/components/blob_viewers/index.js6
-rw-r--r--app/assets/javascripts/repository/components/delete_blob_modal.vue198
-rw-r--r--app/assets/javascripts/repository/constants.js2
-rw-r--r--app/assets/javascripts/tooltips/components/tooltips.vue7
-rw-r--r--app/assets/javascripts/work_items/components/work_item_links/okr_actions_split_button.vue58
-rw-r--r--app/assets/stylesheets/framework/diffs.scss10
21 files changed, 453 insertions, 199 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;
+ }
+}