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
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-12-05 00:14:01 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-12-05 00:14:01 +0300
commitf4056ff4474daf3da66ceaf4473306b0c4652897 (patch)
treeba6ca64ede0a7ec8d2c6971c7f3f5b3d8ab5f81d /app
parent795b6eb292706d577c13556a3583897f082dda6e (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_header.vue13
-rw-r--r--app/assets/javascripts/invite_members/components/members_token_select.vue19
-rw-r--r--app/assets/javascripts/repository/components/blob_content_viewer.vue12
-rw-r--r--app/assets/javascripts/search/store/mutations.js4
-rw-r--r--app/assets/javascripts/search/topbar/components/app.vue21
-rw-r--r--app/assets/javascripts/search/topbar/components/group_filter.vue33
-rw-r--r--app/assets/javascripts/search/topbar/components/project_filter.vue34
-rw-r--r--app/assets/javascripts/search/topbar/components/searchable_dropdown.vue278
-rw-r--r--app/assets/javascripts/search/topbar/components/searchable_dropdown_item.vue78
-rw-r--r--app/assets/javascripts/vue_shared/components/source_viewer/queries/blame_data.query.graphql1
-rw-r--r--app/controllers/projects_controller.rb4
-rw-r--r--app/views/projects/missing_default_branch.html.haml10
12 files changed, 245 insertions, 262 deletions
diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue
index 20f82500a02..e45fd508a5b 100644
--- a/app/assets/javascripts/diffs/components/diff_file_header.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_header.vue
@@ -211,19 +211,6 @@ export default {
return this.getNoteableData.current_user.can_create_note;
},
},
- watch: {
- 'idState.moreActionsShown': {
- handler(val) {
- const el = this.$el.closest('.vue-recycle-scroller__item-view');
-
- if (el) {
- // We can't add a style with Vue because of the way the virtual
- // scroller library renders the diff files
- el.style.zIndex = val ? '1' : null;
- }
- },
- },
- },
methods: {
...mapActions('diffs', [
'toggleFileDiscussions',
diff --git a/app/assets/javascripts/invite_members/components/members_token_select.vue b/app/assets/javascripts/invite_members/components/members_token_select.vue
index 928f81daf92..b29755545f2 100644
--- a/app/assets/javascripts/invite_members/components/members_token_select.vue
+++ b/app/assets/javascripts/invite_members/components/members_token_select.vue
@@ -67,8 +67,6 @@ export default {
originalInput: '',
users: [],
selectedTokens: [],
- hasBeenFocused: false,
- hideDropdownWithNoItems: true,
};
},
computed: {
@@ -124,7 +122,6 @@ export default {
},
methods: {
handleTextInput(inputQuery) {
- this.hideDropdownWithNoItems = false;
this.originalInput = inputQuery;
this.query = inputQuery.trim();
this.loading = true;
@@ -161,18 +158,10 @@ export default {
handleInput() {
this.$emit('input', this.selectedTokens);
},
- handleBlur() {
- this.hideDropdownWithNoItems = false;
- },
handleFocus() {
- // The modal auto-focuses on the input when opened.
- // This prevents the dropdown from opening when the modal opens.
- if (this.hasBeenFocused) {
- this.loading = true;
- this.retrieveUsers();
- }
-
- this.hasBeenFocused = true;
+ // Search for users when focused on the input
+ this.loading = true;
+ this.retrieveUsers();
},
handleTokenRemove(value) {
if (this.selectedTokens.length) {
@@ -208,11 +197,9 @@ export default {
:dropdown-items="users"
:loading="loading"
:allow-user-defined-tokens="emailIsValid"
- :hide-dropdown-with-no-items="hideDropdownWithNoItems"
:placeholder="placeholderText"
:aria-labelledby="ariaLabelledby"
:text-input-attrs="textInputAttrs"
- @blur="handleBlur"
@text-input="handleTextInput"
@input="handleInput"
@focus="handleFocus"
diff --git a/app/assets/javascripts/repository/components/blob_content_viewer.vue b/app/assets/javascripts/repository/components/blob_content_viewer.vue
index 264dbff525b..4ec57676b79 100644
--- a/app/assets/javascripts/repository/components/blob_content_viewer.vue
+++ b/app/assets/javascripts/repository/components/blob_content_viewer.vue
@@ -204,7 +204,8 @@ export default {
return this.blobInfo.storedExternally && this.blobInfo.externalStorage === LFS_STORAGE;
},
isBlameEnabled() {
- return this.glFeatures.blobBlameInfo && this.blobInfo.language === 'json'; // This feature is currently scoped to JSON files
+ // Blame information within the blob viewer is not yet supported in our fallback (HAML) viewers
+ return this.glFeatures.blobBlameInfo && !this.useFallback;
},
},
watch: {
@@ -295,7 +296,14 @@ export default {
},
handleToggleBlame() {
this.switchViewer(SIMPLE_BLOB_VIEWER);
- this.showBlame = !this.showBlame;
+
+ if (this.$route?.query?.plain === '0') {
+ // If the user is not viewing plain code and clicks the blame button, we always want to show blame info
+ // For instance, when viewing the rendered version of a Markdown file
+ this.showBlame = true;
+ } else {
+ this.showBlame = !this.showBlame;
+ }
const blame = this.showBlame === true ? '1' : '0';
if (this.$route?.query?.blame === blame) return;
diff --git a/app/assets/javascripts/search/store/mutations.js b/app/assets/javascripts/search/store/mutations.js
index b248681f053..7627b2e0e08 100644
--- a/app/assets/javascripts/search/store/mutations.js
+++ b/app/assets/javascripts/search/store/mutations.js
@@ -6,7 +6,7 @@ export default {
},
[types.RECEIVE_GROUPS_SUCCESS](state, data) {
state.fetchingGroups = false;
- state.groups = data;
+ state.groups = [...data];
},
[types.RECEIVE_GROUPS_ERROR](state) {
state.fetchingGroups = false;
@@ -17,7 +17,7 @@ export default {
},
[types.RECEIVE_PROJECTS_SUCCESS](state, data) {
state.fetchingProjects = false;
- state.projects = data;
+ state.projects = [...data];
},
[types.RECEIVE_PROJECTS_ERROR](state) {
state.fetchingProjects = false;
diff --git a/app/assets/javascripts/search/topbar/components/app.vue b/app/assets/javascripts/search/topbar/components/app.vue
index d9f824b6e18..5bee757856f 100644
--- a/app/assets/javascripts/search/topbar/components/app.vue
+++ b/app/assets/javascripts/search/topbar/components/app.vue
@@ -16,15 +16,9 @@ export default {
i18n: {
searchPlaceholder: s__(`GlobalSearch|Search for projects, issues, etc.`),
searchLabel: s__(`GlobalSearch|What are you searching for?`),
- documentFetchErrorMessage: s__(
- 'GlobalSearch|There was an error fetching the "Syntax Options" document.',
- ),
- searchFieldLabel: s__('GlobalSearch|What are you searching for?'),
syntaxOptionsLabel: s__('GlobalSearch|Syntax options'),
groupFieldLabel: s__('GlobalSearch|Group'),
projectFieldLabel: s__('GlobalSearch|Project'),
- searchButtonLabel: s__('GlobalSearch|Search'),
- closeButtonLabel: s__('GlobalSearch|Close'),
},
components: {
GlButton,
@@ -124,17 +118,20 @@ export default {
@submit="applyQuery"
/>
</div>
- <div v-if="showFilters" class="gl-mb-4 gl-lg-mb-0 gl-lg-mx-3">
- <label class="gl-display-block gl-mb-1 gl-md-pb-2">{{
+ <div v-if="showFilters" class="gl-mb-4 gl-lg-mb-0 gl-lg-mx-3 gl-min-w-20">
+ <label id="groupfilterDropdown" class="gl-display-block gl-mb-1 gl-md-pb-2">{{
$options.i18n.groupFieldLabel
}}</label>
- <group-filter :initial-data="groupInitialJson" />
+ <group-filter label-id="groupfilterDropdown" :group-initial-json="groupInitialJson" />
</div>
- <div v-if="showFilters" class="gl-mb-4 gl-lg-mb-0 gl-lg-ml-3">
- <label class="gl-display-block gl-mb-1 gl-md-pb-2">{{
+ <div v-if="showFilters" class="gl-mb-4 gl-lg-mb-0 gl-lg-ml-3 gl-min-w-20">
+ <label id="projectfilterDropdown" class="gl-display-block gl-mb-1 gl-md-pb-2">{{
$options.i18n.projectFieldLabel
}}</label>
- <project-filter :initial-data="projectInitialJson" />
+ <project-filter
+ label-id="projectfilterDropdown"
+ :project-initial-json="projectInitialJson"
+ />
</div>
</div>
</div>
diff --git a/app/assets/javascripts/search/topbar/components/group_filter.vue b/app/assets/javascripts/search/topbar/components/group_filter.vue
index a177eb28991..7f13def8a0f 100644
--- a/app/assets/javascripts/search/topbar/components/group_filter.vue
+++ b/app/assets/javascripts/search/topbar/components/group_filter.vue
@@ -12,27 +12,46 @@ export default {
SearchableDropdown,
},
props: {
- initialData: {
+ groupInitialJson: {
type: Object,
required: false,
default: () => ({}),
},
+ labelId: {
+ type: String,
+ required: false,
+ default: 'labelId',
+ },
+ },
+ data() {
+ return {
+ search: '',
+ };
},
computed: {
...mapState(['query', 'groups', 'fetchingGroups']),
...mapGetters(['frequentGroups', 'currentScope']),
selectedGroup() {
- return isEmpty(this.initialData) ? ANY_OPTION : this.initialData;
+ return isEmpty(this.groupInitialJson) ? ANY_OPTION : this.groupInitialJson;
+ },
+ },
+ watch: {
+ search() {
+ this.debounceSearch();
},
},
created() {
// This tracks groups searched via the top nav search bar
- if (this.query.nav_source === 'navbar' && this.initialData?.id) {
- this.setFrequentGroup(this.initialData);
+ if (this.query.nav_source === 'navbar' && this.groupInitialJson?.id) {
+ this.setFrequentGroup(this.groupInitialJson);
}
},
methods: {
...mapActions(['fetchGroups', 'setFrequentGroup', 'loadFrequentGroups']),
+ firstLoad() {
+ this.loadFrequentGroups();
+ this.fetchGroups();
+ },
handleGroupChange(group) {
// If group.id is null we are clearing the filter and don't need to store that in LS.
if (group.id) {
@@ -58,13 +77,13 @@ export default {
data-testid="group-filter"
:header-text="$options.GROUP_DATA.headerText"
:name="$options.GROUP_DATA.name"
- :full-name="$options.GROUP_DATA.fullName"
:loading="fetchingGroups"
:selected-item="selectedGroup"
:items="groups"
:frequent-items="frequentGroups"
- @first-open="loadFrequentGroups"
- @search="fetchGroups"
+ :search-handler="fetchGroups"
+ :label-id="labelId"
+ @first-open="firstLoad"
@change="handleGroupChange"
/>
</template>
diff --git a/app/assets/javascripts/search/topbar/components/project_filter.vue b/app/assets/javascripts/search/topbar/components/project_filter.vue
index c8190b4002d..ecd118a07ac 100644
--- a/app/assets/javascripts/search/topbar/components/project_filter.vue
+++ b/app/assets/javascripts/search/topbar/components/project_filter.vue
@@ -1,4 +1,5 @@
<script>
+import { isEmpty } from 'lodash';
// eslint-disable-next-line no-restricted-imports
import { mapState, mapActions, mapGetters } from 'vuex';
import { visitUrl, setUrlParams } from '~/lib/utils/url_utility';
@@ -11,27 +12,46 @@ export default {
SearchableDropdown,
},
props: {
- initialData: {
+ projectInitialJson: {
type: Object,
required: false,
default: () => null,
},
+ labelId: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ data() {
+ return {
+ search: '',
+ };
},
computed: {
...mapState(['query', 'projects', 'fetchingProjects']),
...mapGetters(['frequentProjects', 'currentScope']),
selectedProject() {
- return this.initialData ? this.initialData : ANY_OPTION;
+ return isEmpty(this.projectInitialJson) ? ANY_OPTION : this.projectInitialJson;
+ },
+ },
+ watch: {
+ search() {
+ this.debounceSearch();
},
},
created() {
// This tracks projects searched via the top nav search bar
- if (this.query.nav_source === 'navbar' && this.initialData?.id) {
- this.setFrequentProject(this.initialData);
+ if (this.query.nav_source === 'navbar' && this.projectInitialJson?.id) {
+ this.setFrequentProject(this.projectInitialJson);
}
},
methods: {
...mapActions(['fetchProjects', 'setFrequentProject', 'loadFrequentProjects']),
+ firstLoad() {
+ this.loadFrequentProjects();
+ this.fetchProjects();
+ },
handleProjectChange(project) {
// If project.id is null we are clearing the filter and don't need to store that in LS.
if (project.id) {
@@ -58,13 +78,13 @@ export default {
data-testid="project-filter"
:header-text="$options.PROJECT_DATA.headerText"
:name="$options.PROJECT_DATA.name"
- :full-name="$options.PROJECT_DATA.fullName"
:loading="fetchingProjects"
:selected-item="selectedProject"
:items="projects"
:frequent-items="frequentProjects"
- @first-open="loadFrequentProjects"
- @search="fetchProjects"
+ :search-handler="fetchProjects"
+ :label-id="labelId"
+ @first-open="firstLoad"
@change="handleProjectChange"
/>
</template>
diff --git a/app/assets/javascripts/search/topbar/components/searchable_dropdown.vue b/app/assets/javascripts/search/topbar/components/searchable_dropdown.vue
index ff639d538b3..f4d9de636d4 100644
--- a/app/assets/javascripts/search/topbar/components/searchable_dropdown.vue
+++ b/app/assets/javascripts/search/topbar/components/searchable_dropdown.vue
@@ -1,38 +1,31 @@
<script>
-import {
- GlDropdown,
- GlDropdownItem,
- GlDropdownSectionHeader,
- GlSearchBoxByType,
- GlLoadingIcon,
- GlIcon,
- GlButton,
- GlSkeletonLoader,
- GlTooltipDirective,
-} from '@gitlab/ui';
-import { __ } from '~/locale';
+import { GlCollapsibleListbox, GlAvatar } from '@gitlab/ui';
+import { debounce } from 'lodash';
+import SafeHtml from '~/vue_shared/directives/safe_html';
+import highlight from '~/lib/utils/highlight';
+import { truncateNamespace } from '~/lib/utils/text_utility';
+import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants';
+import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
+import { __, s__, n__ } from '~/locale';
import { ANY_OPTION } from '../constants';
-import SearchableDropdownItem from './searchable_dropdown_item.vue';
export default {
- i18n: {
- clearLabel: __('Clear'),
- frequentlySearched: __('Frequently searched'),
- },
name: 'SearchableDropdown',
components: {
- GlDropdown,
- GlDropdownItem,
- GlDropdownSectionHeader,
- GlSearchBoxByType,
- GlLoadingIcon,
- GlIcon,
- GlButton,
- GlSkeletonLoader,
- SearchableDropdownItem,
+ GlAvatar,
+ GlCollapsibleListbox,
},
directives: {
- GlTooltip: GlTooltipDirective,
+ SafeHtml,
+ },
+ i18n: {
+ frequentlySearched: __('Frequently searched'),
+ availableGroups: s__('GlobalSearch|All available groups'),
+ nothingFound: s__('GlobalSearch|Nothing found…'),
+ reset: s__('GlobalSearch|Reset'),
+ itemsFound(count) {
+ return n__('%d item found', '%d items found', count);
+ },
},
props: {
headerText: {
@@ -45,11 +38,6 @@ export default {
required: false,
default: 'name',
},
- fullName: {
- type: String,
- required: false,
- default: 'name',
- },
loading: {
type: Boolean,
required: false,
@@ -69,127 +57,167 @@ export default {
required: false,
default: () => [],
},
+ searchHandler: {
+ type: Function,
+ required: true,
+ },
+ labelId: {
+ type: String,
+ required: false,
+ default: 'labelId',
+ },
},
data() {
return {
searchText: '',
hasBeenOpened: false,
+ showableItems: [],
+ searchInProgress: false,
};
},
- computed: {
- showFrequentItems() {
- return !this.searchText && this.frequentItems.length > 0;
+ computed: {},
+ watch: {
+ items() {
+ if (this.searchText === '') {
+ this.showableItems = this.defaultItems();
+ }
},
},
+ created() {
+ this.showableItems = this.defaultItems();
+ },
methods: {
- isSelected(selected) {
- return selected.id === this.selectedItem.id;
+ defaultItems() {
+ const frequentItems = this.convertItemsFormat([...this.frequentItems]);
+ const nonFrequentItems = this.convertItemsFormat([
+ ...this.uniqueItems(this.items, this.frequentItems),
+ ]);
+
+ return [
+ {
+ text: '',
+ options: [
+ {
+ value: ANY_OPTION.name,
+ text: ANY_OPTION.name,
+ ...ANY_OPTION,
+ },
+ ],
+ },
+ {
+ text: this.$options.i18n.frequentlySearched,
+ options: frequentItems,
+ },
+ {
+ text: this.$options.i18n.availableGroups,
+ options: nonFrequentItems,
+ },
+ ].filter((group) => {
+ return group.options.length > 0;
+ });
+ },
+ search(search) {
+ this.searchText = search;
+ this.searchInProgress = true;
+
+ if (search !== '') {
+ debounce(() => {
+ this.searchHandler(this.searchText);
+ this.showableItems = this.convertItemsFormat([...this.items]);
+ }, DEFAULT_DEBOUNCE_AND_THROTTLE_MS)();
+
+ return;
+ }
+
+ this.showableItems = this.defaultItems();
},
openDropdown() {
if (!this.hasBeenOpened) {
this.hasBeenOpened = true;
this.$emit('first-open');
}
-
- this.$emit('search', this.searchText);
},
resetDropdown() {
this.$emit('change', ANY_OPTION);
},
- updateDropdown(item) {
- this.$emit('change', item);
+ convertItemsFormat(items) {
+ return items.map((item) => ({ value: item.id, text: item.full_name, ...item }));
+ },
+ truncatedNamespace(item) {
+ const itemDuplicat = { ...item };
+ const namespaceWithFallback = itemDuplicat.name_with_namespace
+ ? itemDuplicat.name_with_namespace
+ : itemDuplicat.full_name;
+
+ return truncateNamespace(namespaceWithFallback);
+ },
+ highlightedItemName(item) {
+ return highlight(item.name, item.searchText);
+ },
+ onSelectGroup(selected) {
+ if (selected === ANY_OPTION.name) {
+ this.$emit('change', ANY_OPTION);
+ return;
+ }
+
+ const flatShowableItems = [...this.frequentItems, ...this.items];
+ const newSelectedItem = flatShowableItems.find((item) => item.id === selected);
+ this.$emit('change', newSelectedItem);
+ },
+ uniqueItems(allItems, frequentItems) {
+ return allItems.filter((item) => {
+ const itemNotIdentical = frequentItems.some((fitem) => fitem.id === item.id);
+ return Boolean(!itemNotIdentical);
+ });
},
},
ANY_OPTION,
+ AVATAR_SHAPE_OPTION_RECT,
};
</script>
<template>
- <gl-dropdown
- class="gl-w-full"
- menu-class="global-search-dropdown-menu"
- toggle-class="gl-text-truncate"
+ <gl-collapsible-listbox
+ :items="showableItems"
:header-text="headerText"
- :right="true"
- @show="openDropdown"
- @shown="$refs.searchBox.focusInput()"
+ :toggle-text="selectedItem[name]"
+ :no-results-text="$options.i18n.nothingFound"
+ :selected="selectedItem.id"
+ :searching="loading"
+ :reset-button-label="$options.i18n.reset"
+ :toggle-aria-labelled-by="labelId"
+ searchable
+ block
+ @shown="openDropdown"
+ @search="search"
+ @select="onSelectGroup"
+ @reset="resetDropdown"
>
- <template #button-content>
- <span class="dropdown-toggle-text gl-flex-grow-1 gl-text-truncate">
- {{ selectedItem[name] }}
- </span>
- <gl-loading-icon v-if="loading" size="sm" inline class="gl-mr-3" />
- <gl-button
- v-if="!isSelected($options.ANY_OPTION)"
- v-gl-tooltip
- name="clear"
- category="tertiary"
- :title="$options.i18n.clearLabel"
- :aria-label="$options.i18n.clearLabel"
- class="gl-p-0! gl-mr-2"
- @keydown.enter.stop="resetDropdown"
- @click.stop="resetDropdown"
- >
- <gl-icon name="clear" />
- </gl-button>
- <gl-icon name="chevron-down" />
+ <template #search-summary-sr-only>
+ {{ $options.i18n.itemsFound(showableItems.length) }}
+ </template>
+ <template #list-item="{ item }">
+ <div class="gl-display-flex gl-align-items-center">
+ <gl-avatar
+ :src="item.avatar_url"
+ :entity-id="item.id"
+ :entity-name="item.name"
+ :shape="$options.AVATAR_SHAPE_OPTION_RECT"
+ :size="32"
+ class="gl-mr-3"
+ aria-hidden="true"
+ />
+ <div class="gl-display-flex gl-flex-direction-column">
+ <span
+ v-safe-html="highlightedItemName(item)"
+ class="gl-font-weight-bold gl-white-space-nowrap"
+ data-testid="item-title"
+ ></span>
+ <span class="gl-font-sm gl-text-gray-700" data-testid="item-namespace">
+ {{ truncatedNamespace(item) }}</span
+ >
+ </div>
+ </div>
</template>
- <div class="gl-sticky gl-top-0 gl-z-index-1 gl-bg-white">
- <gl-search-box-by-type
- ref="searchBox"
- v-model="searchText"
- class="gl-m-3"
- :debounce="500"
- @input="openDropdown"
- />
- <gl-dropdown-item
- class="gl-border-b-solid gl-border-b-gray-100 gl-border-b-1 gl-pb-2! gl-mb-2"
- is-check-item
- :is-checked="isSelected($options.ANY_OPTION)"
- is-check-centered
- @click="updateDropdown($options.ANY_OPTION)"
- >
- <span data-testid="item-title">{{ $options.ANY_OPTION.name }}</span>
- </gl-dropdown-item>
- </div>
- <div
- v-if="showFrequentItems"
- class="gl-border-b-solid gl-border-b-gray-100 gl-border-b-1 gl-pb-2 gl-mb-2"
- >
- <gl-dropdown-section-header>{{
- $options.i18n.frequentlySearched
- }}</gl-dropdown-section-header>
- <searchable-dropdown-item
- v-for="item in frequentItems"
- :key="item.id"
- :item="item"
- :selected-item="selectedItem"
- :search-text="searchText"
- :name="name"
- :full-name="fullName"
- data-testid="frequent-items"
- @change="updateDropdown"
- />
- </div>
- <div v-if="!loading">
- <searchable-dropdown-item
- v-for="item in items"
- :key="item.id"
- :item="item"
- :selected-item="selectedItem"
- :search-text="searchText"
- :name="name"
- :full-name="fullName"
- data-testid="searchable-items"
- @change="updateDropdown"
- />
- </div>
- <div v-if="loading" class="gl-mx-4 gl-mt-3">
- <gl-skeleton-loader :height="100">
- <rect y="0" width="90%" height="20" rx="4" />
- <rect y="40" width="70%" height="20" rx="4" />
- <rect y="80" width="80%" height="20" rx="4" />
- </gl-skeleton-loader>
- </div>
- </gl-dropdown>
+ </gl-collapsible-listbox>
</template>
diff --git a/app/assets/javascripts/search/topbar/components/searchable_dropdown_item.vue b/app/assets/javascripts/search/topbar/components/searchable_dropdown_item.vue
deleted file mode 100644
index c1e33df3c42..00000000000
--- a/app/assets/javascripts/search/topbar/components/searchable_dropdown_item.vue
+++ /dev/null
@@ -1,78 +0,0 @@
-<script>
-import { GlDropdownItem, GlAvatar } from '@gitlab/ui';
-import SafeHtml from '~/vue_shared/directives/safe_html';
-import highlight from '~/lib/utils/highlight';
-import { truncateNamespace } from '~/lib/utils/text_utility';
-import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants';
-
-export default {
- name: 'SearchableDropdownItem',
- components: {
- GlDropdownItem,
- GlAvatar,
- },
- directives: {
- SafeHtml,
- },
- props: {
- item: {
- type: Object,
- required: true,
- },
- selectedItem: {
- type: Object,
- required: true,
- },
- searchText: {
- type: String,
- required: false,
- default: '',
- },
- name: {
- type: String,
- required: true,
- },
- fullName: {
- type: String,
- required: true,
- },
- },
- computed: {
- isSelected() {
- return this.item.id === this.selectedItem.id;
- },
- truncatedNamespace() {
- return truncateNamespace(this.item[this.fullName]);
- },
- highlightedItemName() {
- return highlight(this.item[this.name], this.searchText);
- },
- },
- AVATAR_SHAPE_OPTION_RECT,
-};
-</script>
-
-<template>
- <gl-dropdown-item
- is-check-item
- :is-checked="isSelected"
- is-check-centered
- @click="$emit('change', item)"
- >
- <div class="gl-display-flex gl-align-items-center">
- <gl-avatar
- :src="item.avatar_url"
- :entity-id="item.id"
- :entity-name="item[name]"
- :shape="$options.AVATAR_SHAPE_OPTION_RECT"
- :size="32"
- />
- <div class="gl-display-flex gl-flex-direction-column">
- <span v-safe-html="highlightedItemName" data-testid="item-title"></span>
- <span class="gl-font-sm gl-text-gray-700" data-testid="item-namespace">{{
- truncatedNamespace
- }}</span>
- </div>
- </div>
- </gl-dropdown-item>
-</template>
diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/queries/blame_data.query.graphql b/app/assets/javascripts/vue_shared/components/source_viewer/queries/blame_data.query.graphql
index a5f3f348cfc..c497224cde3 100644
--- a/app/assets/javascripts/vue_shared/components/source_viewer/queries/blame_data.query.graphql
+++ b/app/assets/javascripts/vue_shared/components/source_viewer/queries/blame_data.query.graphql
@@ -14,6 +14,7 @@ query getBlameData($fullPath: ID!, $filePath: String!, $fromLine: Int, $toLine:
span
commit {
id
+ authorName
titleHtml
message
authoredDate
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index ef87fedf538..b8b79192d3f 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -397,6 +397,7 @@ class ProjectsController < Projects::ApplicationController
if can?(current_user, :read_code, @project)
return render 'projects/no_repo' unless @project.repository_exists?
+ return render 'projects/missing_default_branch', status: :service_unavailable if @ref == ''
render 'projects/empty' if @project.empty_repo?
else
@@ -553,6 +554,9 @@ class ProjectsController < Projects::ApplicationController
# Override get_id from ExtractsPath in this case is just the root of the default branch.
def get_id
project.repository.root_ref
+ rescue Gitlab::Git::CommandError
+ # Empty string is intentional and prevent the @ref reload
+ ''
end
def build_canonical_path(project)
diff --git a/app/views/projects/missing_default_branch.html.haml b/app/views/projects/missing_default_branch.html.haml
new file mode 100644
index 00000000000..66a466d8890
--- /dev/null
+++ b/app/views/projects/missing_default_branch.html.haml
@@ -0,0 +1,10 @@
+- @skip_current_level_breadcrumb = true
+
+= render Pajamas::AlertComponent.new(alert_options: { class: 'gl-my-5' },
+ variant: :danger,
+ dismissible: false,
+ title: s_('ProjectPage|Unable to load default branch')) do |c|
+ - c.with_body do
+ = s_('ProjectPage|The default branch was not able to be found. Please contact your administrator.')
+
+= render 'home_panel'