Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-05-06 03:07:08 +00:00
parent cf03b586cb
commit 149c20239f
29 changed files with 696 additions and 296 deletions

View File

@ -142,6 +142,7 @@ export default {
:is-ascending="isAscending"
:sort-options="sortOptions"
:active-sort-option="activeSortOption"
:search-input-placeholder="__('Search')"
@filter="onFilter"
@sort-direction-change="onSortDirectionChange"
@sort-by-change="onSortByChange"

View File

@ -77,7 +77,7 @@ export default {
searchInputPlaceholder: {
type: String,
required: false,
default: __('Search or filter results…'),
default: __('Filter or search (3 character minimum)'),
},
},
computed: {

View File

@ -1,6 +1,9 @@
import emptyStateProjectsSvgPath from '@gitlab/svgs/dist/illustrations/empty-state/empty-projects-md.svg?url';
import { __, s__ } from '~/locale';
import ProjectsList from '~/vue_shared/components/projects_list/projects_list.vue';
import ProjectsListEmptyState from '~/vue_shared/components/projects_list/projects_list_empty_state.vue';
import ResourceListsEmptyState, {
TYPES,
} from '~/vue_shared/components/resource_lists/empty_state.vue';
import { formatProjects } from '~/projects/your_work/utils';
import projectsQuery from './graphql/queries/projects.query.graphql';
import userProjectsQuery from './graphql/queries/user_projects.query.graphql';
@ -16,7 +19,12 @@ const baseTab = {
listItemClass: 'gl-px-5',
showProjectIcon: true,
},
emptyStateComponent: ProjectsListEmptyState,
emptyStateComponent: ResourceListsEmptyState,
emptyStateComponentProps: {
svgPath: emptyStateProjectsSvgPath,
searchMinimumLength: 3,
type: TYPES.filter,
},
formatter: formatProjects,
};
@ -29,6 +37,7 @@ export const CONTRIBUTED_TAB = {
queryPath: 'currentUser.contributedProjects',
countsQueryPath: 'currentUser.contributed',
emptyStateComponentProps: {
...baseTab.emptyStateComponentProps,
title: s__("Projects|You haven't contributed to any projects yet."),
description: s__(
'Projects|Projects where you contribute code, create issues or epics, or participate in discussions will appear here.',
@ -46,6 +55,7 @@ export const STARRED_TAB = {
queryPath: 'currentUser.starredProjects',
countsQueryPath: 'currentUser.starred',
emptyStateComponentProps: {
...baseTab.emptyStateComponentProps,
title: s__("Projects|You haven't starred any projects yet."),
description: s__(
'Projects|Visit a project and select the star icon to save projects you want to find later.',
@ -63,6 +73,7 @@ export const PERSONAL_TAB = {
queryPath: 'projects',
countsQueryPath: 'personal',
emptyStateComponentProps: {
...baseTab.emptyStateComponentProps,
title: s__("Projects|You don't have any personal projects yet."),
},
};
@ -76,6 +87,7 @@ export const MEMBER_TAB = {
queryPath: 'projects',
countsQueryPath: 'member',
emptyStateComponentProps: {
...baseTab.emptyStateComponentProps,
title: s__("Projects|You aren't a member of any projects yet."),
},
};
@ -89,6 +101,7 @@ export const INACTIVE_TAB = {
queryPath: 'projects',
countsQueryPath: 'inactive',
emptyStateComponentProps: {
...baseTab.emptyStateComponentProps,
title: s__("Projects|You don't have any inactive projects."),
description: s__('Projects|Projects that are archived or pending deletion will appear here.'),
},

View File

@ -1,4 +1,4 @@
import { omitBy, isNil } from 'lodash';
import { omitBy, isNil, uniqBy } from 'lodash';
import { objectToQuery } from '~/lib/utils/url_utility';
import { sprintf } from '~/locale';
import {
@ -267,6 +267,7 @@ export const autocompleteGroupedSearchOptions = (state) => {
if (group) {
group.items.push(formattedItem);
group.items = uniqBy(group.items, 'id');
} else {
groupedOptions[item.category] = {
name: formattedItem.category,

View File

@ -1,7 +1,7 @@
<script>
import emptyStateSvgPath from '@gitlab/svgs/dist/illustrations/empty-state/empty-search-md.svg';
import { GlEmptyState } from '@gitlab/ui';
import { __ } from '~/locale';
import { __, sprintf } from '~/locale';
export const TYPES = {
search: 'search',
@ -12,6 +12,7 @@ export default {
i18n: {
titleSearch: __('No results found'),
descriptionSearch: __('Edit your search and try again.'),
descriptionSearchMinLength: __('Search must be at least %{searchMinimumLength} characters.'),
titleFilter: __('No results found'),
descriptionFilter: __('To widen your search, change or remove filters above.'),
},
@ -25,6 +26,16 @@ export default {
default: TYPES.search,
validator: (type) => Object.values(TYPES).includes(type),
},
search: {
type: String,
required: false,
default: '',
},
searchMinimumLength: {
type: Number,
required: false,
default: 0,
},
},
computed: {
title() {
@ -33,6 +44,12 @@ export default {
: this.$options.i18n.titleFilter;
},
description() {
if (this.search.length < this.searchMinimumLength) {
return sprintf(this.$options.i18n.descriptionSearchMinLength, {
searchMinimumLength: this.searchMinimumLength,
});
}
return this.type === TYPES.search
? this.$options.i18n.descriptionSearch
: this.$options.i18n.descriptionFilter;

View File

@ -1,66 +0,0 @@
<script>
import { GlEmptyState } from '@gitlab/ui';
import emptyStateSearchSvgPath from '@gitlab/svgs/dist/illustrations/empty-state/empty-search-md.svg?url';
import emptyStateProjectsSvgPath from '@gitlab/svgs/dist/illustrations/empty-state/empty-projects-md.svg?url';
import { __, s__ } from '~/locale';
export default {
name: 'ProjectsListEmptyState',
i18n: {
emptyStateSearchTitle: __('No results found'),
emptyStateSearchMinCharDescription: __('Search must be at least 3 characters.'),
emptyStateSearchDescription: __('Edit your criteria and try again.'),
},
components: {
GlEmptyState,
},
props: {
search: {
type: String,
required: false,
default: '',
},
title: {
type: String,
required: false,
default: s__("Projects|You don't have any projects yet."),
},
description: {
type: String,
required: false,
default: s__(
'Projects|Projects are where you can store your code, access issues, wiki, and other features of GitLab.',
),
},
},
computed: {
hasSearch() {
return Boolean(this.search);
},
svgPath() {
return this.hasSearch ? emptyStateSearchSvgPath : emptyStateProjectsSvgPath;
},
computedTitle() {
return this.hasSearch ? this.$options.i18n.emptyStateSearchTitle : this.title;
},
computedDescription() {
if (!this.hasSearch) {
return this.description;
}
return this.search.length >= 3
? this.$options.i18n.emptyStateSearchDescription
: this.$options.i18n.emptyStateSearchMinCharDescription;
},
},
};
</script>
<template>
<gl-empty-state
content-class="gl-max-w-75"
:title="computedTitle"
:description="computedDescription"
:svg-path="svgPath"
/>
</template>

View File

@ -0,0 +1,73 @@
<script>
import { GlEmptyState } from '@gitlab/ui';
import EmptyResult, { TYPES } from '~/vue_shared/components/empty_result.vue';
export { TYPES };
export default {
name: 'ResourceListsEmptyState',
components: {
GlEmptyState,
EmptyResult,
},
props: {
search: {
type: String,
required: false,
default: '',
},
searchMinimumLength: {
type: Number,
required: false,
default: 0,
},
type: {
type: String,
required: false,
default: TYPES.search,
validator: (type) => Object.values(TYPES).includes(type),
},
title: {
type: String,
required: true,
},
description: {
type: String,
required: false,
default: null,
},
svgPath: {
type: String,
required: true,
},
},
computed: {
hasSearch() {
return Boolean(this.search);
},
},
};
</script>
<template>
<empty-result
v-if="hasSearch"
:search="search"
:search-minimum-length="searchMinimumLength"
:type="type"
/>
<gl-empty-state
v-else
content-class="gl-max-w-75"
:title="title"
:description="description"
:svg-path="svgPath"
>
<template #description>
<slot name="description"></slot>
</template>
<template #actions>
<slot name="actions"></slot>
</template>
</gl-empty-state>
</template>

View File

@ -59,6 +59,7 @@ import {
CUSTOM_FIELDS_TYPE_NUMBER,
CUSTOM_FIELDS_TYPE_TEXT,
WORK_ITEM_TYPE_NAME_ISSUE,
WIDGET_TYPE_STATUS,
} from '../constants';
import createWorkItemMutation from '../graphql/create_work_item.mutation.graphql';
import namespaceWorkItemTypesQuery from '../graphql/namespace_work_item_types.query.graphql';
@ -103,6 +104,7 @@ export default {
WorkItemIteration: () => import('ee_component/work_items/components/work_item_iteration.vue'),
WorkItemCustomFields: () =>
import('ee_component/work_items/components/work_item_custom_fields.vue'),
WorkItemStatus: () => import('ee_component/work_items/components/work_item_status.vue'),
},
mixins: [glFeatureFlagMixin()],
inject: ['fullPath', 'groupPath'],
@ -467,9 +469,15 @@ export default {
workItemId() {
return this.workItem?.id;
},
workItemStatus() {
return findWidget(WIDGET_TYPE_STATUS, this.workItem);
},
workItemIid() {
return this.workItem?.iid;
},
workItemStatusId() {
return this.workItemStatus?.status?.id;
},
shouldIncludeRelatedItem() {
return (
this.isWidgetSupported(WIDGET_TYPE_LINKED_ITEMS) &&
@ -515,6 +523,7 @@ export default {
Boolean(this.workItemDueDateIsFixed) ||
Boolean(this.workItemStartDateIsFixed) ||
Boolean(this.workItemIterationId) ||
Boolean(this.workItemStatusId) ||
isCustomFieldsFilled
);
},
@ -524,6 +533,9 @@ export default {
workItemCustomFields() {
return findWidget(WIDGET_TYPE_CUSTOM_FIELDS, this.workItem)?.customFieldValues ?? null;
},
showWorkItemStatus() {
return this.workItemStatus && this.glFeatures.workItemStatusFeatureFlag;
},
},
watch: {
shouldDiscardDraft: {
@ -707,6 +719,12 @@ export default {
};
}
if (this.isWidgetSupported(WIDGET_TYPE_STATUS)) {
workItemCreateInput.statusWidget = {
status: this.workItemStatusId,
};
}
if (this.shouldIncludeRelatedItem) {
workItemCreateInput.linkedItemsWidget = {
workItemsIds: [this.relatedItem.id],
@ -946,6 +964,17 @@ export default {
class="work-item-overview-right-sidebar gl-px-3"
:class="{ 'is-modal': true }"
>
<work-item-status
v-if="showWorkItemStatus"
class="work-item-attributes-item"
:can-update="canUpdate"
:full-path="selectedProjectFullPath"
:is-group="isGroup"
:work-item-id="workItemId"
:work-item-iid="workItemIid"
:work-item-type="selectedWorkItemTypeName"
@error="$emit('error', $event)"
/>
<work-item-assignees
v-if="workItemAssignees"
class="js-assignee work-item-attributes-item"

View File

@ -28,6 +28,7 @@ import {
WIDGET_TYPE_LINKED_ITEMS,
STATE_CLOSED,
WIDGET_TYPE_CUSTOM_FIELDS,
WIDGET_TYPE_STATUS,
} from 'ee_else_ce/work_items/constants';
import {
findCurrentUserTodosWidget,
@ -315,6 +316,7 @@ export const setNewWorkItemCache = async (
// eslint-disable-next-line max-params
) => {
const workItemAttributesWrapperOrder = [
WIDGET_TYPE_STATUS,
WIDGET_TYPE_ASSIGNEES,
WIDGET_TYPE_LABELS,
WIDGET_TYPE_WEIGHT,
@ -490,6 +492,14 @@ export const setNewWorkItemCache = async (
});
}
if (widgetName === WIDGET_TYPE_STATUS) {
widgets.push({
type: 'STATUS',
status: null,
__typename: 'WorkItemWidgetStatus',
});
}
if (widgetName === WIDGET_TYPE_HIERARCHY) {
widgets.push({
type: 'HIERARCHY',

View File

@ -27,6 +27,7 @@ import {
CUSTOM_FIELDS_TYPE_SINGLE_SELECT,
CUSTOM_FIELDS_TYPE_MULTI_SELECT,
CUSTOM_FIELDS_TYPE_TEXT,
WIDGET_TYPE_STATUS,
} from '../constants';
import workItemByIidQuery from './work_item_by_iid.query.graphql';
@ -109,6 +110,7 @@ export const updateNewWorkItemCache = (input, cache) => {
milestone,
parent,
customField,
status,
} = input;
try {
@ -171,6 +173,11 @@ export const updateNewWorkItemCache = (input, cache) => {
newData: parent,
nodePath: 'parent',
},
{
widgetType: WIDGET_TYPE_STATUS,
newData: status,
nodePath: 'status',
},
];
widgetUpdates.forEach(({ widgetType, newData, nodePath }) => {

View File

@ -95,6 +95,14 @@ input LocalCustomFieldInput {
selectedOptions: [LocalCustomFieldSelectOptionInput]
}
input LocalStatusInput {
id: ID!
name: String!
iconName: String
color: String
position: Int
}
input LocalUpdateNewWorkItemInput {
fullPath: String!
workItemType: String!
@ -111,6 +119,7 @@ input LocalUpdateNewWorkItemInput {
crmContacts: [LocalCrmContactsInput]
weight: Int
customField: LocalCustomFieldInput
status: LocalStatusInput
}
extend type Mutation {

View File

@ -6,6 +6,9 @@
@import '@gitlab/at.js/dist/css/jquery.atwho';
@import 'dropzone/dist/basic';
// GitLab project specific custom property definitions
@import 'root';
// GitLab UI framework
@import 'framework';

View File

@ -1,19 +1,3 @@
:root {
--user-contribution-graph-cell-level-0: var(--gl-color-neutral-50);
--user-contribution-graph-cell-level-1: var(--gl-color-data-blue-100);
--user-contribution-graph-cell-level-2: var(--gl-color-data-blue-400);
--user-contribution-graph-cell-level-3: var(--gl-color-data-blue-600);
--user-contribution-graph-cell-level-4: var(--gl-color-data-blue-900);
}
:root.gl-dark {
--user-contribution-graph-cell-level-0: var(--gl-color-neutral-900);
--user-contribution-graph-cell-level-1: var(--gl-color-data-blue-900);
--user-contribution-graph-cell-level-2: var(--gl-color-data-blue-600);
--user-contribution-graph-cell-level-3: var(--gl-color-data-blue-400);
--user-contribution-graph-cell-level-4: var(--gl-color-data-blue-100);
}
.user-contrib-cell {
&:hover,
&:focus,

View File

@ -1,22 +1,5 @@
// stylelint-disable length-zero-no-unit
:root {
--performance-bar-height: 0px;
--system-header-height: 0px;
--header-height: 0px;
--top-bar-height: 0px;
--broadcast-message-height: 0px;
--system-footer-height: 0px;
--mr-review-bar-height: 0px;
--application-header-height: calc(
var(--header-height) +
var(--system-header-height) +
var(--performance-bar-height) +
var(--top-bar-height)
);
--application-bar-left: 0px;
--application-bar-right: 0px;
@each $name, $size in $grid-breakpoints {
--breakpoint-#{$name}: #{$size};
}

View File

@ -3,15 +3,6 @@
$diff-file-header: 41px;
:root {
--diff-warning-text-color: var(--gl-feedback-warning-text-color);
--diff-warning-background-color: var(--gl-feedback-warning-background-color);
}
:root.gl-dark {
--diff-warning-background-color: #453522;
}
// Common
.diff-file {
margin-bottom: $gl-padding;

View File

@ -49,60 +49,6 @@ a.ci-icon:focus {
@apply gl-focus;
}
:root {
--ci-icon-success-background-color: var(--gl-color-green-100);
--ci-icon-success-text-color: var(--gl-color-green-700);
--ci-icon-success-icon-color: var(--gl-color-neutral-0);
--ci-icon-success-icon-background-color: var(--gl-color-green-500);
--ci-icon-warning-background-color: var(--gl-color-orange-100);
--ci-icon-warning-text-color: var(--gl-color-orange-700);
--ci-icon-warning-icon-color: var(--gl-color-neutral-0);
--ci-icon-warning-icon-background-color: var(--gl-color-orange-500);
--ci-icon-danger-background-color: var(--gl-color-red-100);
--ci-icon-danger-text-color: var(--gl-color-red-700);
--ci-icon-danger-icon-color: var(--gl-color-neutral-0);
--ci-icon-danger-icon-background-color: var(--gl-color-red-500);
--ci-icon-info-background-color: var(--gl-color-blue-100);
--ci-icon-info-text-color: var(--gl-color-blue-700);
--ci-icon-info-icon-color: var(--gl-color-blue-500);
--ci-icon-info-icon-background-color: var(--gl-color-neutral-0);
--ci-icon-neutral-background-color: var(--gl-color-neutral-100);
--ci-icon-neutral-text-color: var(--gl-color-neutral-700);
--ci-icon-neutral-icon-color: var(--gl-color-neutral-500);
--ci-icon-neutral-icon-background-color: var(--gl-color-neutral-0);
}
:root.gl-dark {
--ci-icon-success-background-color: var(--gl-color-green-300);
--ci-icon-success-text-color: var(--gl-color-green-900);
--ci-icon-success-icon-color: var(--gl-color-green-300);
--ci-icon-success-icon-background-color: var(--gl-color-green-900);
--ci-icon-warning-background-color: var(--gl-color-orange-300);
--ci-icon-warning-text-color: var(--gl-color-orange-900);
--ci-icon-warning-icon-color: var(--gl-color-orange-300);
--ci-icon-warning-icon-background-color: var(--gl-color-orange-900);
--ci-icon-danger-background-color: var(--gl-color-red-300);
--ci-icon-danger-text-color: var(--gl-color-red-900);
--ci-icon-danger-icon-color: var(--gl-color-red-300);
--ci-icon-danger-icon-background-color: var(--gl-color-red-900);
--ci-icon-info-background-color: var(--gl-color-blue-300);
--ci-icon-info-text-color: var(--gl-color-blue-900);
--ci-icon-info-icon-color: var(--gl-color-blue-300);
--ci-icon-info-icon-background-color: var(--gl-color-blue-900);
--ci-icon-neutral-background-color: var(--gl-color-neutral-300);
--ci-icon-neutral-text-color: var(--gl-color-neutral-900);
--ci-icon-neutral-icon-color: var(--gl-color-neutral-300);
--ci-icon-neutral-icon-background-color: var(--gl-color-neutral-900);
}
.ci-icon {
border-radius: 20px;

View File

@ -1,14 +1,3 @@
:root {
--timeline-entry-internal-note-background-color: var(--gl-color-orange-50);
--timeline-entry-target-background-color : var(--gl-color-blue-50);
}
// stylelint-disable-next-line gitlab/no-gl-class
:root.gl-dark {
--timeline-entry-internal-note-background-color: #453522;
--timeline-entry-target-background-color : #2A394E;
}
.timeline {
margin: 0;
padding: 0;

View File

@ -0,0 +1,122 @@
/* stylelint-disable gitlab/no-gl-class */
/* stylelint-disable length-zero-no-unit */
:root {
--performance-bar-height: 0px;
--system-header-height: 0px;
--header-height: 0px;
--top-bar-height: 0px;
--broadcast-message-height: 0px;
--system-footer-height: 0px;
--mr-review-bar-height: 0px;
--application-header-height: calc(
var(--header-height) +
var(--system-header-height) +
var(--performance-bar-height) +
var(--top-bar-height)
);
--application-bar-left: 0px;
--application-bar-right: 0px;
/**
* CI icon styles
* Discussion: #issue
* Owner: Verify: Pipeline execution
*/
--ci-icon-success-background-color: var(--gl-color-green-100);
--ci-icon-success-text-color: var(--gl-color-green-700);
--ci-icon-success-icon-color: var(--gl-color-neutral-0);
--ci-icon-success-icon-background-color: var(--gl-color-green-500);
--ci-icon-warning-background-color: var(--gl-color-orange-100);
--ci-icon-warning-text-color: var(--gl-color-orange-700);
--ci-icon-warning-icon-color: var(--gl-color-neutral-0);
--ci-icon-warning-icon-background-color: var(--gl-color-orange-500);
--ci-icon-danger-background-color: var(--gl-color-red-100);
--ci-icon-danger-text-color: var(--gl-color-red-700);
--ci-icon-danger-icon-color: var(--gl-color-neutral-0);
--ci-icon-danger-icon-background-color: var(--gl-color-red-500);
--ci-icon-info-background-color: var(--gl-color-blue-100);
--ci-icon-info-text-color: var(--gl-color-blue-700);
--ci-icon-info-icon-color: var(--gl-color-blue-500);
--ci-icon-info-icon-background-color: var(--gl-color-neutral-0);
--ci-icon-neutral-background-color: var(--gl-color-neutral-100);
--ci-icon-neutral-text-color: var(--gl-color-neutral-700);
--ci-icon-neutral-icon-color: var(--gl-color-neutral-500);
--ci-icon-neutral-icon-background-color: var(--gl-color-neutral-0);
&.gl-dark {
--ci-icon-success-background-color: var(--gl-color-green-300);
--ci-icon-success-text-color: var(--gl-color-green-900);
--ci-icon-success-icon-color: var(--gl-color-green-300);
--ci-icon-success-icon-background-color: var(--gl-color-green-900);
--ci-icon-warning-background-color: var(--gl-color-orange-300);
--ci-icon-warning-text-color: var(--gl-color-orange-900);
--ci-icon-warning-icon-color: var(--gl-color-orange-300);
--ci-icon-warning-icon-background-color: var(--gl-color-orange-900);
--ci-icon-danger-background-color: var(--gl-color-red-300);
--ci-icon-danger-text-color: var(--gl-color-red-900);
--ci-icon-danger-icon-color: var(--gl-color-red-300);
--ci-icon-danger-icon-background-color: var(--gl-color-red-900);
--ci-icon-info-background-color: var(--gl-color-blue-300);
--ci-icon-info-text-color: var(--gl-color-blue-900);
--ci-icon-info-icon-color: var(--gl-color-blue-300);
--ci-icon-info-icon-background-color: var(--gl-color-blue-900);
--ci-icon-neutral-background-color: var(--gl-color-neutral-300);
--ci-icon-neutral-text-color: var(--gl-color-neutral-900);
--ci-icon-neutral-icon-color: var(--gl-color-neutral-300);
--ci-icon-neutral-icon-background-color: var(--gl-color-neutral-900);
}
/**
* Diff styles
* Discussion: #issue
* Owner: TBD
*/
--diff-warning-text-color: var(--gl-feedback-warning-text-color);
--diff-warning-background-color: var(--gl-feedback-warning-background-color);
&.gl-dark {
--diff-warning-background-color: #453522;
}
/**
* Timeline entry note styles
* Discussion: #issue
* Owner: TBD
*/
--timeline-entry-draft-note-background-color: var(--gl-color-orange-50);
--timeline-entry-internal-note-background-color: var(--gl-color-orange-50);
--timeline-entry-target-background-color : var(--gl-color-blue-50);
&.gl-dark {
--timeline-entry-draft-note-background-color: #453522;
--timeline-entry-internal-note-background-color: #453522;
--timeline-entry-target-background-color : #2A394E;
}
/**
* Contribution graph styles on user profile
* Discussion: #issue
* Owner: Organizations
*/
--user-contribution-graph-cell-level-0: var(--gl-color-neutral-50);
--user-contribution-graph-cell-level-1: var(--gl-color-data-blue-100);
--user-contribution-graph-cell-level-2: var(--gl-color-data-blue-400);
--user-contribution-graph-cell-level-3: var(--gl-color-data-blue-600);
--user-contribution-graph-cell-level-4: var(--gl-color-data-blue-900);
&.gl-dark {
--user-contribution-graph-cell-level-0: var(--gl-color-neutral-900);
--user-contribution-graph-cell-level-1: var(--gl-color-data-blue-900);
--user-contribution-graph-cell-level-2: var(--gl-color-data-blue-600);
--user-contribution-graph-cell-level-3: var(--gl-color-data-blue-400);
--user-contribution-graph-cell-level-4: var(--gl-color-data-blue-100);
}
}

View File

@ -4280,7 +4280,10 @@ msgstr ""
msgid "AdminSelfHostedModels|Apply to all %{mainFeature} sub-features"
msgstr ""
msgid "AdminSelfHostedModels|Assign a setting to the %{subFeatureTitle} sub-feature before applying to all"
msgid "AdminSelfHostedModels|Assign a model to enable this feature"
msgstr ""
msgid "AdminSelfHostedModels|Assign a model to the %{subFeatureTitle} sub-feature before applying to all"
msgstr ""
msgid "AdminSelfHostedModels|Create self-hosted model"
@ -26426,6 +26429,9 @@ msgstr ""
msgid "Filter list for %{title}"
msgstr ""
msgid "Filter or search (3 character minimum)"
msgstr ""
msgid "Filter parameters are not valid. Make sure that the end date is after the start date."
msgstr ""
@ -48534,9 +48540,6 @@ msgstr ""
msgid "Projects|Delete %{nameWithNamespace}"
msgstr ""
msgid "Projects|Projects are where you can store your code, access issues, wiki, and other features of GitLab."
msgstr ""
msgid "Projects|Projects that are archived or pending deletion will appear here."
msgstr ""
@ -48558,9 +48561,6 @@ msgstr ""
msgid "Projects|You don't have any personal projects yet."
msgstr ""
msgid "Projects|You don't have any projects yet."
msgstr ""
msgid "Projects|You haven't contributed to any projects yet."
msgstr ""
@ -53425,7 +53425,7 @@ msgstr ""
msgid "Search milestones"
msgstr ""
msgid "Search must be at least 3 characters."
msgid "Search must be at least %{searchMinimumLength} characters."
msgstr ""
msgid "Search or filter commits"

View File

@ -1,4 +1,4 @@
ARG GDK_SHA=1841cdd5ef752729accd8297ecbf59cbe6eedbfc
ARG GDK_SHA=45624f3167bb847d0039d4edb52082609bbd979c
# Use tag prefix when running on 'stable' branch to make sure 'protected' image is used which is not deleted by registry cleanup
ARG GDK_BASE_TAG_PREFIX

View File

@ -47,7 +47,6 @@ ee/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js
ee/spec/frontend/projects/settings/components/shared_runners_toggle_spec.js
ee/spec/frontend/related_items_tree/components/related_items_tree_body_spec.js
ee/spec/frontend/related_items_tree/components/tree_root_spec.js
ee/spec/frontend/requirements/components/requirement_item_spec.js
ee/spec/frontend/roadmap/components/roadmap_shell_spec.js
ee/spec/frontend/roles_and_permissions/components/role_selector_spec.js
ee/spec/frontend/sidebar/components/sidebar_dropdown_widget_spec.js

View File

@ -59,6 +59,7 @@ describe('GroupsListWithFilteredSearch', () => {
text: SORTING_ITEM_UPDATED.label,
},
isAscending: true,
searchInputPlaceholder: 'Search',
});
});

View File

@ -47,7 +47,7 @@ describe('FilteredSearchAndSort', () => {
initialFilterValue: [TOKEN_EMPTY_SEARCH_TERM],
syncFilterAndSort: true,
recentSearchesStorageKey: defaultPropsData.filteredSearchRecentSearchesStorageKey,
searchInputPlaceholder: 'Search or filter results…',
searchInputPlaceholder: 'Filter or search (3 character minimum)',
});
});

View File

@ -14,7 +14,7 @@ import axios from '~/lib/utils/axios_utils';
import TabView from '~/groups_projects/components/tab_view.vue';
import { formatProjects } from '~/projects/your_work/utils';
import ProjectsList from '~/vue_shared/components/projects_list/projects_list.vue';
import ProjectsListEmptyState from '~/vue_shared/components/projects_list/projects_list_empty_state.vue';
import ResourceListsEmptyState from '~/vue_shared/components/resource_lists/empty_state.vue';
import NestedGroupsProjectsList from '~/vue_shared/components/nested_groups_projects_list/nested_groups_projects_list.vue';
import { DEFAULT_PER_PAGE } from '~/api';
import { createAlert } from '~/alert';
@ -90,7 +90,7 @@ describe('TabView', () => {
const findProjectsList = () => wrapper.findComponent(ProjectsList);
const findKeysetPagination = () => wrapper.findComponent(GlKeysetPagination);
const findOffsetPagination = () => wrapper.findComponent(GlPagination);
const findEmptyState = () => wrapper.findComponent(ProjectsListEmptyState);
const findEmptyState = () => wrapper.findComponent(ResourceListsEmptyState);
beforeEach(() => {
mockAxios = new MockAdapter(axios);

View File

@ -241,6 +241,41 @@ export const MOCK_AUTOCOMPLETE_OPTIONS_RES = [
value: 'MockProject2',
url: 'project/2',
},
{
category: 'Users',
id: 12457737,
value: 'Madelein van Niekerk',
label: 'maddievn',
url: '/maddievn',
avatar_url: '/uploads/-/system/user/avatar/12457737/avatar.png',
},
{
category: 'Users',
id: 18478596,
value: 'Ben Madden',
label: 'benmadden',
url: '/benmadden',
avatar_url:
'https://secure.gravatar.com/avatar/85562f816f3da990011dfe131bd5d0e1b976f8fcc16416130963799aa8d08563?s=80\u0026d=identicon',
},
{
category: 'Users',
id: 13986091,
value: 'Dean Kadiev',
label: 'dean.kadiev',
url: '/dean.kadiev',
avatar_url:
'https://secure.gravatar.com/avatar/544ddc3cad1e42783ddf2f3c951b84656b16f29ef6cdf6d42a9ad1b09fc3558b?s=80\u0026d=identicon',
},
{
category: 'Users',
id: 26276383,
value: 'Chris Madden',
label: 'chris1062',
url: '/chris1062',
avatar_url:
'https://secure.gravatar.com/avatar/bfac33d5df10dd93d581f067403dab052362f300301bbc9d0e855840b3304b12?s=80\u0026d=identicon',
},
{
category: 'Help',
label: 'GitLab Help',
@ -273,6 +308,41 @@ export const MOCK_AUTOCOMPLETE_OPTIONS = [
url: 'project/2',
avatar_url: '/project/avatar/2/avatar.png',
},
{
category: 'Users',
id: 12457737,
value: 'Madelein van Niekerk',
label: 'maddievn',
url: '/maddievn',
avatar_url: '/uploads/-/system/user/avatar/12457737/avatar.png',
},
{
category: 'Users',
id: 18478596,
value: 'Ben Madden',
label: 'benmadden',
url: '/benmadden',
avatar_url:
'https://secure.gravatar.com/avatar/85562f816f3da990011dfe131bd5d0e1b976f8fcc16416130963799aa8d08563?s=80\u0026d=identicon',
},
{
category: 'Users',
id: 13986091,
value: 'Dean Kadiev',
label: 'dean.kadiev',
url: '/dean.kadiev',
avatar_url:
'https://secure.gravatar.com/avatar/544ddc3cad1e42783ddf2f3c951b84656b16f29ef6cdf6d42a9ad1b09fc3558b?s=80\u0026d=identicon',
},
{
category: 'Users',
id: 26276383,
value: 'Chris Madden',
label: 'chris1062',
url: '/chris1062',
avatar_url:
'https://secure.gravatar.com/avatar/bfac33d5df10dd93d581f067403dab052362f300301bbc9d0e855840b3304b12?s=80\u0026d=identicon',
},
{
category: 'Help',
label: 'GitLab Help',
@ -342,6 +412,82 @@ export const MOCK_GROUPED_AUTOCOMPLETE_OPTIONS = [
},
],
},
{
name: 'Users',
items: [
{
id: 12457737,
category: 'Users',
value: 'Madelein van Niekerk',
label: 'maddievn',
text: 'Madelein van Niekerk',
href: '/maddievn',
avatar_url: '/uploads/-/system/user/avatar/12457737/avatar.png',
avatar_size: 16,
namespace: 'maddievn',
entity_id: 12457737,
entity_name: 'maddievn',
extraAttrs: {
'data-track-action': 'click_command_palette_item',
'data-track-label': 'users',
},
},
{
id: 18478596,
category: 'Users',
value: 'Ben Madden',
label: 'benmadden',
text: 'Ben Madden',
href: '/benmadden',
avatar_url:
'https://secure.gravatar.com/avatar/85562f816f3da990011dfe131bd5d0e1b976f8fcc16416130963799aa8d08563?s=80&d=identicon',
avatar_size: 16,
namespace: 'benmadden',
entity_id: 18478596,
entity_name: 'benmadden',
extraAttrs: {
'data-track-action': 'click_command_palette_item',
'data-track-label': 'users',
},
},
{
id: 13986091,
category: 'Users',
value: 'Dean Kadiev',
label: 'dean.kadiev',
text: 'Dean Kadiev',
href: '/dean.kadiev',
avatar_url:
'https://secure.gravatar.com/avatar/544ddc3cad1e42783ddf2f3c951b84656b16f29ef6cdf6d42a9ad1b09fc3558b?s=80&d=identicon',
avatar_size: 16,
namespace: 'dean.kadiev',
entity_id: 13986091,
entity_name: 'dean.kadiev',
extraAttrs: {
'data-track-action': 'click_command_palette_item',
'data-track-label': 'users',
},
},
{
id: 26276383,
category: 'Users',
value: 'Chris Madden',
label: 'chris1062',
text: 'Chris Madden',
href: '/chris1062',
avatar_url:
'https://secure.gravatar.com/avatar/bfac33d5df10dd93d581f067403dab052362f300301bbc9d0e855840b3304b12?s=80&d=identicon',
avatar_size: 16,
namespace: 'chris1062',
entity_id: 26276383,
entity_name: 'chris1062',
extraAttrs: {
'data-track-action': 'click_command_palette_item',
'data-track-label': 'users',
},
},
],
},
{
name: 'Help',
items: [
@ -413,6 +559,77 @@ export const MOCK_SORTED_AUTOCOMPLETE_OPTIONS = [
'data-track-label': 'projects',
},
},
{
id: 12457737,
category: 'Users',
value: 'Madelein van Niekerk',
label: 'maddievn',
text: 'Madelein van Niekerk',
href: '/maddievn',
avatar_url: '/uploads/-/system/user/avatar/12457737/avatar.png',
avatar_size: 16,
namespace: 'maddievn',
entity_id: 12457737,
entity_name: 'maddievn',
extraAttrs: {
'data-track-action': 'click_command_palette_item',
'data-track-label': 'users',
},
},
{
id: 18478596,
category: 'Users',
value: 'Ben Madden',
label: 'benmadden',
text: 'Ben Madden',
href: '/benmadden',
avatar_url:
'https://secure.gravatar.com/avatar/85562f816f3da990011dfe131bd5d0e1b976f8fcc16416130963799aa8d08563?s=80&d=identicon',
avatar_size: 16,
namespace: 'benmadden',
entity_id: 18478596,
entity_name: 'benmadden',
extraAttrs: {
'data-track-action': 'click_command_palette_item',
'data-track-label': 'users',
},
},
{
id: 13986091,
category: 'Users',
value: 'Dean Kadiev',
label: 'dean.kadiev',
text: 'Dean Kadiev',
href: '/dean.kadiev',
avatar_url:
'https://secure.gravatar.com/avatar/544ddc3cad1e42783ddf2f3c951b84656b16f29ef6cdf6d42a9ad1b09fc3558b?s=80&d=identicon',
avatar_size: 16,
namespace: 'dean.kadiev',
entity_id: 13986091,
entity_name: 'dean.kadiev',
extraAttrs: {
'data-track-action': 'click_command_palette_item',
'data-track-label': 'users',
},
},
{
id: 26276383,
category: 'Users',
value: 'Chris Madden',
label: 'chris1062',
text: 'Chris Madden',
href: '/chris1062',
avatar_url:
'https://secure.gravatar.com/avatar/bfac33d5df10dd93d581f067403dab052362f300301bbc9d0e855840b3304b12?s=80&d=identicon',
avatar_size: 16,
namespace: 'chris1062',
entity_id: 26276383,
entity_name: 'chris1062',
extraAttrs: {
'data-track-action': 'click_command_palette_item',
'data-track-label': 'users',
},
},
{
avatar_size: 16,
entity_name: 'GitLab Help',

View File

@ -396,15 +396,58 @@ describe('Global Search Store Getters', () => {
});
describe('autocompleteGroupedSearchOptions', () => {
beforeEach(() => {
createState();
state.autocompleteOptions = MOCK_AUTOCOMPLETE_OPTIONS;
const findGroupItems = (res, name) => res.find((group) => group.name === name).items;
const totalResults = 4;
const totalProjects = 2;
const expectUniqueAutocompleteResults = (result) => {
expect(result).toStrictEqual(MOCK_GROUPED_AUTOCOMPLETE_OPTIONS);
expect(findGroupItems(result, 'Users').length).toBe(totalResults);
expect(findGroupItems(result, 'Projects').length).toBe(totalProjects);
};
describe('without duplicates', () => {
beforeEach(() => {
createState();
state.autocompleteOptions = MOCK_AUTOCOMPLETE_OPTIONS;
});
it('returns the correct grouped array', () => {
expectUniqueAutocompleteResults(getters.autocompleteGroupedSearchOptions(state));
});
});
it('returns the correct grouped array', () => {
expect(getters.autocompleteGroupedSearchOptions(state)).toStrictEqual(
MOCK_GROUPED_AUTOCOMPLETE_OPTIONS,
);
describe('with duplicates', () => {
beforeEach(() => {
createState();
state.autocompleteOptions = [
...MOCK_AUTOCOMPLETE_OPTIONS,
{
category: 'Users',
id: 12457737,
value: 'Madelein van Niekerk (duplicate)',
label: 'maddievn',
url: '/maddievn',
avatar_url: '/uploads/-/system/user/avatar/12457737/avatar.png',
},
{
category: 'Projects',
id: 1,
label: 'Gitlab Org / MockProject1 (duplicate)',
value: 'MockProject1',
url: 'project/1',
avatar_url: '/project/avatar/1/avatar.png',
},
];
});
it('returns the correct grouped array with duplicates removed', () => {
expectUniqueAutocompleteResults(getters.autocompleteGroupedSearchOptions(state));
});
});
});

View File

@ -14,23 +14,55 @@ describe('Empty result', () => {
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
it('renders empty search state', () => {
createComponent({ type: 'search' });
describe('when searchMinimumLength prop is not passed', () => {
it('renders empty search state', () => {
createComponent({ type: 'search' });
expect(findEmptyState().props()).toMatchObject({
svgPath: emptyStateSvgPath,
title: 'No results found',
description: 'Edit your search and try again.',
expect(findEmptyState().props()).toMatchObject({
svgPath: emptyStateSvgPath,
title: 'No results found',
description: 'Edit your search and try again.',
});
});
it('renders empty filter state', () => {
createComponent({ type: 'filter' });
expect(findEmptyState().props()).toMatchObject({
svgPath: emptyStateSvgPath,
title: 'No results found',
description: 'To widen your search, change or remove filters above.',
});
});
});
it('renders empty filter state', () => {
createComponent({ type: 'filter' });
describe('when searchMinimumLength prop is passed', () => {
describe('with search >= minimum search length', () => {
beforeEach(() => {
createComponent({ search: 'tes', searchMinimumLength: 3 });
});
expect(findEmptyState().props()).toMatchObject({
svgPath: emptyStateSvgPath,
title: 'No results found',
description: 'To widen your search, change or remove filters above.',
it('renders empty state correctly', () => {
expect(findEmptyState().props()).toMatchObject({
title: 'No results found',
description: 'Edit your search and try again.',
svgPath: emptyStateSvgPath,
});
});
});
describe('with search < minimum search length', () => {
beforeEach(() => {
createComponent({ search: 'te', searchMinimumLength: 3 });
});
it('renders empty state correctly', () => {
expect(findEmptyState().props()).toMatchObject({
title: 'No results found',
description: 'Search must be at least 3 characters.',
svgPath: emptyStateSvgPath,
});
});
});
});
});

View File

@ -1,86 +0,0 @@
import { GlEmptyState } from '@gitlab/ui';
import emptyStateProjectsSvgPath from '@gitlab/svgs/dist/illustrations/empty-state/empty-projects-md.svg?url';
import emptyStateSearchSvgPath from '@gitlab/svgs/dist/illustrations/empty-state/empty-search-md.svg?url';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import ProjectsListEmptyState from '~/vue_shared/components/projects_list/projects_list_empty_state.vue';
describe('ProjectsListEmptyState', () => {
let wrapper;
const createComponent = (props) => {
wrapper = shallowMountExtended(ProjectsListEmptyState, {
propsData: {
search: '',
...props,
},
});
};
const findGlEmptyState = () => wrapper.findComponent(GlEmptyState);
describe('without search', () => {
it('renders empty state correctly', () => {
createComponent();
expect(findGlEmptyState().props()).toMatchObject({
title: "You don't have any projects yet.",
description:
'Projects are where you can store your code, access issues, wiki, and other features of GitLab.',
svgPath: emptyStateProjectsSvgPath,
});
});
describe('when title prop is passed', () => {
const title = "You haven't starred any projects yet.";
beforeEach(() => {
createComponent({ title });
});
it('correctly passes to `GlEmptyState` component', () => {
expect(findGlEmptyState().props('title')).toBe(title);
});
});
describe('when description prop is passed', () => {
const description =
'Visit a project and select the star icon to save projects you want to find later.';
beforeEach(() => {
createComponent({ description });
});
it('correctly passes to `GlEmptyState` component', () => {
expect(findGlEmptyState().props('description')).toBe(description);
});
});
});
describe('with search >=3 characters', () => {
beforeEach(() => {
createComponent({ search: 'tes' });
});
it('renders empty state correctly', () => {
expect(findGlEmptyState().props()).toMatchObject({
title: 'No results found',
description: 'Edit your criteria and try again.',
svgPath: emptyStateSearchSvgPath,
});
});
});
describe('with search <3 characters', () => {
beforeEach(() => {
createComponent({ search: 'te' });
});
it('renders empty state correctly', () => {
expect(findGlEmptyState().props()).toMatchObject({
title: 'No results found',
description: 'Search must be at least 3 characters.',
svgPath: emptyStateSearchSvgPath,
});
});
});
});

View File

@ -0,0 +1,82 @@
import { GlEmptyState } from '@gitlab/ui';
import emptyStateProjectsSvgPath from '@gitlab/svgs/dist/illustrations/empty-state/empty-projects-md.svg?url';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import ResourceListsEmptyState, {
TYPES,
} from '~/vue_shared/components/resource_lists/empty_state.vue';
import EmptyResult from '~/vue_shared/components/empty_result.vue';
describe('ResourceListsEmptyState', () => {
let wrapper;
const defaultPropsData = {
search: '',
svgPath: emptyStateProjectsSvgPath,
title: "You don't have any projects yet.",
description:
'Projects are where you can store your code, access issues, wiki, and other features of GitLab.',
type: TYPES.search,
};
const createComponent = ({ propsData = {}, scopedSlots } = {}) => {
wrapper = shallowMountExtended(ResourceListsEmptyState, {
propsData: {
...defaultPropsData,
...propsData,
},
scopedSlots,
});
};
const findGlEmptyState = () => wrapper.findComponent(GlEmptyState);
describe('without search', () => {
it('renders empty state correctly', () => {
createComponent();
expect(findGlEmptyState().props()).toMatchObject({
title: defaultPropsData.title,
description: defaultPropsData.description,
svgPath: emptyStateProjectsSvgPath,
});
});
describe('with description slot', () => {
beforeEach(() => {
createComponent({
scopedSlots: { description: '<div data-testid="description-slot"></div>' },
});
});
it('correctly renders description', () => {
expect(wrapper.findByTestId('description-slot').exists()).toBe(true);
});
});
describe('with actions slot', () => {
beforeEach(() => {
createComponent({
scopedSlots: { actions: '<div data-testid="actions-slot"></div>' },
});
});
it('correctly renders actions', () => {
expect(wrapper.findByTestId('actions-slot').exists()).toBe(true);
});
});
});
describe('with search', () => {
beforeEach(() => {
createComponent({ propsData: { search: 'tes' } });
});
it('renders EmptyResult component with correct props', () => {
expect(wrapper.findComponent(EmptyResult).props()).toEqual({
search: 'tes',
searchMinimumLength: 0,
type: defaultPropsData.type,
});
});
});
});