Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
9dbde66947
commit
3a8d99989a
|
|
@ -814,12 +814,12 @@ const Api = {
|
|||
return axios.delete(url, { data });
|
||||
},
|
||||
|
||||
getRawFile(id, path, params = {}) {
|
||||
getRawFile(id, path, params = {}, options = {}) {
|
||||
const url = Api.buildUrl(this.rawFilePath)
|
||||
.replace(':id', encodeURIComponent(id))
|
||||
.replace(':path', encodeURIComponent(path));
|
||||
|
||||
return axios.get(url, { params });
|
||||
return axios.get(url, { params, ...options });
|
||||
},
|
||||
|
||||
updateIssue(project, issue, data = {}) {
|
||||
|
|
|
|||
|
|
@ -74,7 +74,12 @@ export default class EditBlob {
|
|||
let blobContent = '';
|
||||
|
||||
if (filePath) {
|
||||
const { data } = await Api.getRawFile(projectId, filePath, { ref });
|
||||
const { data } = await Api.getRawFile(
|
||||
projectId,
|
||||
filePath,
|
||||
{ ref },
|
||||
{ responseType: 'text', transformResponse: (x) => x },
|
||||
);
|
||||
blobContent = String(data);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -419,6 +419,7 @@ export default {
|
|||
fetchLabels: this.fetchLabels,
|
||||
fetchLatestLabels: this.glFeatures.frontendCaching ? this.fetchLatestLabels : null,
|
||||
recentSuggestionsStorageKey: `${this.fullPath}-issues-recent-tokens-label`,
|
||||
multiSelect: this.glFeatures.groupMultiSelectTokens,
|
||||
},
|
||||
{
|
||||
type: TOKEN_TYPE_TYPE,
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ export default {
|
|||
}[this.imageDetails?.expirationPolicyCleanupStatus];
|
||||
},
|
||||
deleteButtonDisabled() {
|
||||
return this.disabled || !this.imageDetails.canDelete;
|
||||
return this.disabled || !this.imageDetails.userPermissions.destroyContainerRepository;
|
||||
},
|
||||
rootImageTooltip() {
|
||||
return !this.imageDetails.name ? ROOT_IMAGE_TOOLTIP : '';
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ export default {
|
|||
return this.containerRepository?.tags?.nodes || [];
|
||||
},
|
||||
hideBulkDelete() {
|
||||
return !this.containerRepository?.canDelete;
|
||||
return !this.containerRepository?.userPermissions.destroyContainerRepository;
|
||||
},
|
||||
tagsPageInfo() {
|
||||
return this.containerRepository?.tags?.pageInfo;
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ export default {
|
|||
<list-item v-bind="$attrs" :selected="selected" :disabled="disabled">
|
||||
<template #left-action>
|
||||
<gl-form-checkbox
|
||||
v-if="tag.canDelete"
|
||||
v-if="tag.userPermissions.destroyContainerRepositoryTag"
|
||||
:disabled="disabled"
|
||||
class="gl-m-0"
|
||||
:checked="selected"
|
||||
|
|
@ -197,7 +197,7 @@ export default {
|
|||
</gl-sprintf>
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="tag.canDelete" #right-action>
|
||||
<template v-if="tag.userPermissions.destroyContainerRepositoryTag" #right-action>
|
||||
<gl-disclosure-dropdown
|
||||
:disabled="disabled"
|
||||
icon="ellipsis_v"
|
||||
|
|
|
|||
|
|
@ -66,7 +66,9 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
disabledDelete() {
|
||||
return !this.item.canDelete || this.deleting || this.migrating;
|
||||
return (
|
||||
!this.item.userPermissions.destroyContainerRepository || this.deleting || this.migrating
|
||||
);
|
||||
},
|
||||
id() {
|
||||
return getIdFromGraphQLId(this.item.id);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ query getContainerRepositoryDetails($id: ContainerRepositoryID!) {
|
|||
path
|
||||
status
|
||||
location
|
||||
canDelete
|
||||
createdAt
|
||||
expirationPolicyStartedAt
|
||||
expirationPolicyCleanupStatus
|
||||
|
|
@ -18,5 +17,8 @@ query getContainerRepositoryDetails($id: ContainerRepositoryID!) {
|
|||
nextRunAt
|
||||
}
|
||||
}
|
||||
userPermissions {
|
||||
destroyContainerRepository
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ query getContainerRepositoryTags(
|
|||
containerRepository(id: $id) {
|
||||
id
|
||||
tagsCount
|
||||
canDelete
|
||||
userPermissions {
|
||||
destroyContainerRepository
|
||||
}
|
||||
tags(after: $after, before: $before, first: $first, last: $last, name: $name, sort: $sort) {
|
||||
nodes {
|
||||
digest
|
||||
|
|
@ -24,7 +26,9 @@ query getContainerRepositoryTags(
|
|||
createdAt
|
||||
publishedAt
|
||||
totalSize
|
||||
canDelete
|
||||
userPermissions {
|
||||
destroyContainerRepositoryTag
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
...PageInfo
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
<script>
|
||||
import { GlToken, GlFilteredSearchSuggestion } from '@gitlab/ui';
|
||||
|
||||
import { GlIcon, GlIntersperse, GlFilteredSearchSuggestion, GlLabel } from '@gitlab/ui';
|
||||
import { createAlert } from '~/alert';
|
||||
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||
import { isScopedLabel } from '~/lib/utils/common_utils';
|
||||
import { stripQuotes } from '~/lib/utils/text_utility';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
|
|
@ -13,9 +12,12 @@ import BaseToken from './base_token.vue';
|
|||
export default {
|
||||
components: {
|
||||
BaseToken,
|
||||
GlToken,
|
||||
GlIcon,
|
||||
GlFilteredSearchSuggestion,
|
||||
GlIntersperse,
|
||||
GlLabel,
|
||||
},
|
||||
inject: ['hasScopedLabelsFeature'],
|
||||
props: {
|
||||
config: {
|
||||
type: Object,
|
||||
|
|
@ -33,6 +35,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
labels: this.config.initialLabels || [],
|
||||
allLabels: this.config.initialLabels || [],
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
|
|
@ -45,6 +48,21 @@ export default {
|
|||
getActiveLabel(labels, data) {
|
||||
return labels.find((label) => this.getLabelName(label) === stripQuotes(data));
|
||||
},
|
||||
findLabelByName(name) {
|
||||
return this.allLabels.find((label) => this.getLabelName(label) === name);
|
||||
},
|
||||
findLabelById(id) {
|
||||
return this.allLabels.find((label) => label.id === id);
|
||||
},
|
||||
showScopedLabel(labelName) {
|
||||
const label = this.findLabelByName(labelName);
|
||||
return isScopedLabel(label) && this.hasScopedLabelsFeature;
|
||||
},
|
||||
getLabelBackgroundColor(labelName) {
|
||||
const label = this.findLabelByName(labelName);
|
||||
const backgroundColor = label?.color || '#fff0';
|
||||
return backgroundColor;
|
||||
},
|
||||
/**
|
||||
* There's an inconsistency between private and public API
|
||||
* for labels where label name is included in a different
|
||||
|
|
@ -60,15 +78,12 @@ export default {
|
|||
getLabelName(label) {
|
||||
return label.name || label.title;
|
||||
},
|
||||
getContainerStyle(activeLabel) {
|
||||
if (activeLabel) {
|
||||
const { color: backgroundColor, textColor: color } = convertObjectPropsToCamelCase(
|
||||
activeLabel,
|
||||
);
|
||||
|
||||
return { backgroundColor, color };
|
||||
}
|
||||
return {};
|
||||
updateListOfAllLabels() {
|
||||
this.labels.forEach((label) => {
|
||||
if (!this.findLabelById(label.id)) {
|
||||
this.allLabels.push(label);
|
||||
}
|
||||
});
|
||||
},
|
||||
fetchLabels(searchTerm) {
|
||||
this.loading = true;
|
||||
|
|
@ -79,6 +94,8 @@ export default {
|
|||
// labels.json and /groups/:id/labels & /projects/:id/labels
|
||||
// return response differently.
|
||||
this.labels = Array.isArray(res) ? res : res.data;
|
||||
this.updateListOfAllLabels();
|
||||
|
||||
if (this.config.fetchLatestLabels) {
|
||||
this.fetchLatestLabels(searchTerm);
|
||||
}
|
||||
|
|
@ -100,6 +117,7 @@ export default {
|
|||
// labels.json and /groups/:id/labels & /projects/:id/labels
|
||||
// return response differently.
|
||||
this.labels = Array.isArray(res) ? res : res.data;
|
||||
this.updateListOfAllLabels();
|
||||
})
|
||||
.catch(() =>
|
||||
createAlert({
|
||||
|
|
@ -124,24 +142,44 @@ export default {
|
|||
@fetch-suggestions="fetchLabels"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<template
|
||||
#view-token="{ viewTokenProps: { inputValue, cssClasses, listeners, activeTokenValue } }"
|
||||
>
|
||||
<gl-token
|
||||
variant="search-value"
|
||||
:class="cssClasses"
|
||||
:style="getContainerStyle(activeTokenValue)"
|
||||
v-on="listeners"
|
||||
>~{{ activeTokenValue ? getLabelName(activeTokenValue) : inputValue }}</gl-token
|
||||
>
|
||||
<template #view="{ viewTokenProps: { inputValue, activeTokenValue, selectedTokens } }">
|
||||
<gl-intersperse v-if="selectedTokens.length > 0" separator=", ">
|
||||
<gl-label
|
||||
v-for="label in selectedTokens"
|
||||
:key="label"
|
||||
class="js-no-trigger"
|
||||
:background-color="getLabelBackgroundColor(label)"
|
||||
:scoped="showScopedLabel(label)"
|
||||
:title="label"
|
||||
size="sm"
|
||||
/>
|
||||
</gl-intersperse>
|
||||
<template v-else>
|
||||
<gl-label
|
||||
class="js-no-trigger"
|
||||
:background-color="
|
||||
getLabelBackgroundColor(activeTokenValue ? getLabelName(activeTokenValue) : inputValue)
|
||||
"
|
||||
:scoped="showScopedLabel(activeTokenValue ? getLabelName(activeTokenValue) : inputValue)"
|
||||
:title="activeTokenValue ? getLabelName(activeTokenValue) : inputValue"
|
||||
size="sm"
|
||||
/></template>
|
||||
</template>
|
||||
<template #suggestions-list="{ suggestions }">
|
||||
<template #suggestions-list="{ suggestions, selections = [] }">
|
||||
<gl-filtered-search-suggestion
|
||||
v-for="label in suggestions"
|
||||
:key="label.id"
|
||||
:value="getLabelName(label)"
|
||||
>
|
||||
<div class="gl-display-flex gl-align-items-center">
|
||||
<div
|
||||
class="gl-display-flex gl-align-items-center"
|
||||
:class="{ 'gl-pl-6': !selections.includes(label.title) }"
|
||||
>
|
||||
<gl-icon
|
||||
v-if="selections.includes(label.title)"
|
||||
name="check"
|
||||
class="gl-mr-3 gl-text-secondary gl-flex-shrink-0"
|
||||
/>
|
||||
<span
|
||||
:style="{ backgroundColor: label.color }"
|
||||
class="gl-display-inline-block gl-mr-3 gl-p-3"
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ class Groups::LabelsController < Groups::ApplicationController
|
|||
before_action :authorize_label_for_admin_label!, only: [:edit, :update, :destroy]
|
||||
before_action :save_previous_label_path, only: [:edit]
|
||||
|
||||
before_action only: :index do
|
||||
push_frontend_feature_flag(:label_similarity_sort, group)
|
||||
end
|
||||
|
||||
respond_to :html
|
||||
|
||||
feature_category :team_planning
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@ class Projects::LabelsController < Projects::ApplicationController
|
|||
:set_priorities]
|
||||
before_action :authorize_admin_group_labels!, only: [:promote]
|
||||
|
||||
before_action only: :index do
|
||||
push_frontend_feature_flag(:label_similarity_sort, project)
|
||||
end
|
||||
|
||||
respond_to :js, :html
|
||||
|
||||
feature_category :team_planning
|
||||
|
|
|
|||
|
|
@ -66,13 +66,23 @@ class LabelsFinder < UnionFinder
|
|||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def similarity_enabled
|
||||
if project?
|
||||
Feature.enabled?(:label_similarity_sort, project)
|
||||
else
|
||||
Feature.enabled?(:label_similarity_sort, group)
|
||||
end
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def sort(items)
|
||||
if params[:sort]
|
||||
items.order_by(params[:sort])
|
||||
else
|
||||
items.reorder(title: :asc)
|
||||
return items.reorder(title: :asc) unless params[:sort]
|
||||
|
||||
if params[:sort] == 'relevance' && params[:search].present? && similarity_enabled
|
||||
return items.sorted_by_similarity_desc(params[:search])
|
||||
end
|
||||
|
||||
items.order_by(params[:sort])
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ query getProjectContainerRepositories(
|
|||
path
|
||||
status
|
||||
location
|
||||
canDelete
|
||||
createdAt
|
||||
expirationPolicyStartedAt
|
||||
expirationPolicyCleanupStatus
|
||||
|
|
@ -36,6 +35,9 @@ query getProjectContainerRepositories(
|
|||
id
|
||||
path
|
||||
}
|
||||
userPermissions {
|
||||
destroyContainerRepository
|
||||
}
|
||||
__typename
|
||||
}
|
||||
pageInfo {
|
||||
|
|
@ -75,6 +77,9 @@ query getProjectContainerRepositories(
|
|||
id
|
||||
path
|
||||
}
|
||||
userPermissions {
|
||||
destroyContainerRepository
|
||||
}
|
||||
__typename
|
||||
}
|
||||
pageInfo {
|
||||
|
|
|
|||
|
|
@ -119,15 +119,17 @@ module SortingHelper
|
|||
}
|
||||
end
|
||||
|
||||
def label_sort_options_hash
|
||||
{
|
||||
def label_sort_options_hash(relevance)
|
||||
options = {}
|
||||
options[sort_value_relevance] = sort_title_relevance if params[:search].present? && relevance
|
||||
options.merge({
|
||||
sort_value_name => sort_title_name,
|
||||
sort_value_name_desc => sort_title_name_desc,
|
||||
sort_value_recently_created => sort_title_recently_created,
|
||||
sort_value_oldest_created => sort_title_oldest_created,
|
||||
sort_value_recently_updated => sort_title_recently_updated,
|
||||
sort_value_oldest_updated => sort_title_oldest_updated
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
def users_sort_options_hash
|
||||
|
|
|
|||
|
|
@ -190,6 +190,10 @@ module SortingTitlesValuesHelper
|
|||
s_('SortOptions|Expired date')
|
||||
end
|
||||
|
||||
def sort_title_relevance
|
||||
s_('SortOptions|Relevance')
|
||||
end
|
||||
|
||||
# Values.
|
||||
def sort_value_created_date
|
||||
'created_date'
|
||||
|
|
@ -374,6 +378,10 @@ module SortingTitlesValuesHelper
|
|||
def sort_value_expire_date
|
||||
'expired_asc'
|
||||
end
|
||||
|
||||
def sort_value_relevance
|
||||
'relevance'
|
||||
end
|
||||
end
|
||||
|
||||
SortingHelper.include_mod_with('SortingTitlesValuesHelper')
|
||||
|
|
|
|||
|
|
@ -75,6 +75,33 @@ class Label < ApplicationRecord
|
|||
.with_preloaded_container
|
||||
end
|
||||
|
||||
scope :sorted_by_similarity_desc, -> (search) do
|
||||
order_expression = Gitlab::Database::SimilarityScore.build_expression(
|
||||
search: search,
|
||||
rules: [
|
||||
{ column: arel_table["title"], multiplier: 1 },
|
||||
{ column: arel_table["description"], multiplier: 0.2 }
|
||||
])
|
||||
|
||||
order = Gitlab::Pagination::Keyset::Order.build(
|
||||
[
|
||||
Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
|
||||
attribute_name: 'similarity',
|
||||
column_expression: order_expression,
|
||||
order_expression: order_expression.desc,
|
||||
order_direction: :desc,
|
||||
distinct: false,
|
||||
add_to_projections: true
|
||||
),
|
||||
Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
|
||||
attribute_name: 'id',
|
||||
order_expression: Label.arel_table[:id].desc
|
||||
)
|
||||
])
|
||||
|
||||
order.apply_cursor_conditions(reorder(order))
|
||||
end
|
||||
|
||||
def self.pluck_titles
|
||||
pluck(:title)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
.nav-controls
|
||||
= form_tag labels_filter_path, method: :get do
|
||||
= hidden_field_tag :subscribed, params[:subscribed]
|
||||
- if Feature.enabled?(:label_similarity_sort, @project) || Feature.enabled?(:label_similarity_sort, @group)
|
||||
= hidden_field_tag :sort, 'relevance'
|
||||
.input-group
|
||||
= search_field_tag :search, params[:search], { placeholder: _('Filter'), id: 'label-search', class: 'form-control search-text-input input-short', spellcheck: false, autofocus: true }
|
||||
%span.input-group-append
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
- label_sort_options = label_sort_options_hash.map { |value, text| { value: value, text: text, href: page_filter_path(sort: value) } }
|
||||
|
||||
- show_relevance = Feature.enabled?(:label_similarity_sort, @project) || Feature.enabled?(:label_similarity_sort, @group)
|
||||
- label_sort_options = label_sort_options_hash(show_relevance).map { |value, text| { value: value, text: text, href: page_filter_path(sort: value) } }
|
||||
= gl_redirect_listbox_tag label_sort_options, @sort, data: { placement: 'right' }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
name: label_similarity_sort
|
||||
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/443244
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/145821
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/448455
|
||||
milestone: '16.10'
|
||||
group: group::project management
|
||||
type: gitlab_com_derisk
|
||||
default_enabled: false
|
||||
|
|
@ -6,8 +6,12 @@
|
|||
stage: scalability
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/439687
|
||||
body: |
|
||||
The [`sidekiq['min_concurrency']` and `sidekiq['max_concurrency']`](https://docs.gitlab.com/ee/administration/sidekiq/extra_sidekiq_processes.html#manage-thread-counts-explicitly) settings are deprecated in GitLab 16.9 and will be removed in GitLab 17.0.
|
||||
- For Linux package (Omnibus) installations, the [`sidekiq['min_concurrency']` and `sidekiq['max_concurrency']`](https://docs.gitlab.com/ee/administration/sidekiq/extra_sidekiq_processes.html#manage-thread-counts-explicitly) settings are deprecated in GitLab 16.9 and will be removed in GitLab 17.0.
|
||||
|
||||
You can use `sidekiq['concurrency']` in GitLab 16.9 and later to set thread counts explicitly in each process.
|
||||
You can use `sidekiq['concurrency']` in GitLab 16.9 and later to set thread counts explicitly in each process.
|
||||
|
||||
This change only applies to Linux package (Omnibus) installations.
|
||||
The above change only applies to Linux package (Omnibus) installations.
|
||||
|
||||
- For GitLab Helm chart installations, passing `SIDEKIQ_CONCURRENCY_MIN` and/or `SIDEKIQ_CONCURRENCY_MAX` as `extraEnv` to the `sidekiq` sub-chart is deprecated in GitLab 16.10 and will be removed in GitLab 17.0.
|
||||
|
||||
You can use the `concurrency` option to set thread counts explicitly in each process.
|
||||
|
|
|
|||
|
|
@ -15174,6 +15174,7 @@ Represents the approval policy.
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="approvalpolicyallgroupapprovers"></a>`allGroupApprovers` | [`[PolicyApprovalGroup!]`](#policyapprovalgroup) | All potential approvers of the group type, including groups inaccessible to the user. |
|
||||
| <a id="approvalpolicydeprecatedproperties"></a>`deprecatedProperties` **{warning-solid}** | [`[String!]`](#string) | **Introduced** in GitLab 16.10. **Status**: Experiment. All deprecated properties in the policy. Returns `null` if security_policies_breaking_changes feature flag is disabled. |
|
||||
| <a id="approvalpolicydescription"></a>`description` | [`String!`](#string) | Description of the policy. |
|
||||
| <a id="approvalpolicyeditpath"></a>`editPath` | [`String!`](#string) | URL of policy edit page. |
|
||||
| <a id="approvalpolicyenabled"></a>`enabled` | [`Boolean!`](#boolean) | Indicates whether this policy is enabled. |
|
||||
|
|
@ -28024,6 +28025,7 @@ Represents the scan result policy.
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="scanresultpolicyallgroupapprovers"></a>`allGroupApprovers` | [`[PolicyApprovalGroup!]`](#policyapprovalgroup) | All potential approvers of the group type, including groups inaccessible to the user. |
|
||||
| <a id="scanresultpolicydeprecatedproperties"></a>`deprecatedProperties` **{warning-solid}** | [`[String!]`](#string) | **Introduced** in GitLab 16.10. **Status**: Experiment. All deprecated properties in the policy. Returns `null` if security_policies_breaking_changes feature flag is disabled. |
|
||||
| <a id="scanresultpolicydescription"></a>`description` | [`String!`](#string) | Description of the policy. |
|
||||
| <a id="scanresultpolicyeditpath"></a>`editPath` | [`String!`](#string) | URL of policy edit page. |
|
||||
| <a id="scanresultpolicyenabled"></a>`enabled` | [`Boolean!`](#boolean) | Indicates whether this policy is enabled. |
|
||||
|
|
|
|||
|
|
@ -1406,11 +1406,15 @@ Users are advised to upgrade to 3.8.8 or greater.
|
|||
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/439687).
|
||||
</div>
|
||||
|
||||
The [`sidekiq['min_concurrency']` and `sidekiq['max_concurrency']`](https://docs.gitlab.com/ee/administration/sidekiq/extra_sidekiq_processes.html#manage-thread-counts-explicitly) settings are deprecated in GitLab 16.9 and will be removed in GitLab 17.0.
|
||||
- For Linux package (Omnibus) installations, the [`sidekiq['min_concurrency']` and `sidekiq['max_concurrency']`](https://docs.gitlab.com/ee/administration/sidekiq/extra_sidekiq_processes.html#manage-thread-counts-explicitly) settings are deprecated in GitLab 16.9 and will be removed in GitLab 17.0.
|
||||
|
||||
You can use `sidekiq['concurrency']` in GitLab 16.9 and later to set thread counts explicitly in each process.
|
||||
You can use `sidekiq['concurrency']` in GitLab 16.9 and later to set thread counts explicitly in each process.
|
||||
|
||||
This change only applies to Linux package (Omnibus) installations.
|
||||
The above change only applies to Linux package (Omnibus) installations.
|
||||
|
||||
- For GitLab Helm chart installations, passing `SIDEKIQ_CONCURRENCY_MIN` and/or `SIDEKIQ_CONCURRENCY_MAX` as `extraEnv` to the `sidekiq` sub-chart is deprecated in GitLab 16.10 and will be removed in GitLab 17.0.
|
||||
|
||||
You can use the `concurrency` option to set thread counts explicitly in each process.
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -120,7 +120,16 @@ planned for release in 16.9.1.
|
|||
| 16.5 | All | None |
|
||||
| 16.6 | All | None |
|
||||
| 16.7 | All | None |
|
||||
| 16.8 | 16.8.0 - 18.8.3 | 16.8.4 |
|
||||
| 16.8 | 16.8.0 - 16.8.3 | 16.8.4 |
|
||||
| 16.9 | 16.9.0 - 16.9.1 | 16.9.2 |
|
||||
|
||||
- You might experience verification failures on a subset of container registry images due to checksum mismatch between the primary site and the secondary site. [Issue 442667](https://gitlab.com/gitlab-org/gitlab/-/issues/442667) describes the details. While there is no direct risk of data loss as the data is being correctly replicated to the secondary sites, it is not being successfully verified. There are no known workarounds at this time.
|
||||
|
||||
**Affected releases**:
|
||||
|
||||
| Affected minor releases | Affected patch releases | Fixed in |
|
||||
| ----------------------- | ----------------------- | -------- |
|
||||
| 16.8 | 16.8.0 - 16.8.3 | 16.8.4 |
|
||||
| 16.9 | 16.9.0 - 16.9.1 | 16.9.2 |
|
||||
|
||||
## 16.8.0
|
||||
|
|
@ -170,7 +179,16 @@ you must take one of the following actions based on your configuration:
|
|||
| 16.5 | All | None |
|
||||
| 16.6 | All | None |
|
||||
| 16.7 | All | None |
|
||||
| 16.8 | 16.8.0 - 18.8.3 | 16.8.4 |
|
||||
| 16.8 | 16.8.0 - 16.8.3 | 16.8.4 |
|
||||
| 16.9 | 16.9.0 - 16.9.1 | 16.9.2 |
|
||||
|
||||
- You might experience verification failures on a subset of container registry images due to checksum mismatch between the primary site and the secondary site. [Issue 442667](https://gitlab.com/gitlab-org/gitlab/-/issues/442667) describes the details. While there is no direct risk of data loss as the data is being correctly replicated to the secondary sites, it is not being successfully verified. There are no known workarounds at this time.
|
||||
|
||||
**Affected releases**:
|
||||
|
||||
| Affected minor releases | Affected patch releases | Fixed in |
|
||||
| ----------------------- | ----------------------- | -------- |
|
||||
| 16.8 | 16.8.0 - 16.8.3 | 16.8.4 |
|
||||
| 16.9 | 16.9.0 - 16.9.1 | 16.9.2 |
|
||||
|
||||
## 16.7.0
|
||||
|
|
@ -240,7 +258,7 @@ take one of the following actions based on your configuration:
|
|||
| 16.5 | All | None |
|
||||
| 16.6 | All | None |
|
||||
| 16.7 | All | None |
|
||||
| 16.8 | 16.8.0 - 18.8.3 | 16.8.4 |
|
||||
| 16.8 | 16.8.0 - 16.8.3 | 16.8.4 |
|
||||
| 16.9 | 16.9.0 - 16.9.1 | 16.9.2 |
|
||||
|
||||
## 16.6.0
|
||||
|
|
@ -286,7 +304,7 @@ take one of the following actions based on your configuration:
|
|||
| 16.5 | All | None |
|
||||
| 16.6 | All | None |
|
||||
| 16.7 | All | None |
|
||||
| 16.8 | 16.8.0 - 18.8.3 | 16.8.4 |
|
||||
| 16.8 | 16.8.0 - 16.8.3 | 16.8.4 |
|
||||
| 16.9 | 16.9.0 - 16.9.1 | 16.9.2 |
|
||||
|
||||
## 16.5.0
|
||||
|
|
@ -426,7 +444,7 @@ Specific information applies to installations using Geo:
|
|||
| 16.5 | All | None |
|
||||
| 16.6 | All | None |
|
||||
| 16.7 | All | None |
|
||||
| 16.8 | 16.8.0 - 18.8.3 | 16.8.4 |
|
||||
| 16.8 | 16.8.0 - 16.8.3 | 16.8.4 |
|
||||
| 16.9 | 16.9.0 - 16.9.1 | 16.9.2 |
|
||||
|
||||
## 16.4.0
|
||||
|
|
|
|||
|
|
@ -48439,6 +48439,9 @@ msgstr ""
|
|||
msgid "SortOptions|Recently starred"
|
||||
msgstr ""
|
||||
|
||||
msgid "SortOptions|Relevance"
|
||||
msgstr ""
|
||||
|
||||
msgid "SortOptions|Size"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -45,4 +45,30 @@ RSpec.describe 'Search for labels', :js, feature_category: :team_planning do
|
|||
expect(page).not_to have_content(label2.title)
|
||||
expect(page).not_to have_content(label2.description)
|
||||
end
|
||||
|
||||
context 'when label_similarity_sort is enabled' do
|
||||
before do
|
||||
stub_feature_flags(label_similarity_sort: true)
|
||||
end
|
||||
|
||||
it 'sorts by relevance when searching' do
|
||||
find('#label-search').fill_in(with: 'Bar')
|
||||
find('#label-search').native.send_keys(:enter)
|
||||
|
||||
expect(page).to have_button('Relevance')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when label_similarity_sort is disabled' do
|
||||
before do
|
||||
stub_feature_flags(label_similarity_sort: false)
|
||||
end
|
||||
|
||||
it 'sorts by relevance when searching' do
|
||||
find('#label-search').fill_in(with: 'Bar')
|
||||
find('#label-search').native.send_keys(:enter)
|
||||
|
||||
expect(page).to have_button('Name')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -159,7 +159,8 @@ RSpec.describe 'Filter issues', :js, feature_category: :team_planning do
|
|||
end
|
||||
|
||||
it 'filters issues by multiple labels with not operator' do
|
||||
select_tokens 'Label', '!=', bug_label.title, 'Label', '=', caps_sensitive_label.title, submit: true
|
||||
select_tokens 'Label', '!=', bug_label.title, submit: true
|
||||
select_tokens 'Label', '=', caps_sensitive_label.title, submit: true
|
||||
|
||||
expect_negated_label_token(bug_label.title)
|
||||
expect_label_token(caps_sensitive_label.title)
|
||||
|
|
|
|||
|
|
@ -6,12 +6,16 @@ RSpec.describe 'Projects > Files > User edits files', :js, feature_category: :so
|
|||
include Features::SourceEditorSpecHelpers
|
||||
include ProjectForksHelper
|
||||
include Features::BlobSpecHelpers
|
||||
include TreeHelper
|
||||
|
||||
let_it_be(:json_text) { '{"name":"Best package ever!"}' }
|
||||
let_it_be(:project_with_json) { create(:project, :custom_repo, name: 'Project with json', files: { 'package.json' => json_text }) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
let(:project) { create(:project, :repository, name: 'Shop') }
|
||||
let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
|
||||
let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) }
|
||||
let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(vscode_web_ide: false)
|
||||
|
|
@ -248,4 +252,16 @@ RSpec.describe 'Projects > Files > User edits files', :js, feature_category: :so
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when editing a json file', :js do
|
||||
before_all do
|
||||
project_with_json.add_maintainer(user)
|
||||
end
|
||||
|
||||
it 'loads the content as text' do
|
||||
visit(project_edit_blob_path(project_with_json, tree_join(project_with_json.default_branch, 'package.json')))
|
||||
wait_for_requests
|
||||
expect(find('.monaco-editor')).to have_content(json_text)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1202,6 +1202,18 @@ describe('Api', () => {
|
|||
expect(axios.get).toHaveBeenCalledWith(expectedUrl, { params });
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the method is called with options', () => {
|
||||
it('sets the params and options on the request', () => {
|
||||
const options = { responseType: 'text', transformRequest: (x) => x };
|
||||
const params = { ref: 'main' };
|
||||
jest.spyOn(axios, 'get');
|
||||
|
||||
Api.getRawFile(dummyProjectPath, dummyFilePath, params, options);
|
||||
|
||||
expect(axios.get).toHaveBeenCalledWith(expectedUrl, { params, ...options });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when an error occurs while getting a raw file', () => {
|
||||
|
|
|
|||
|
|
@ -99,7 +99,12 @@ describe('Blob Editing', () => {
|
|||
describe('file content', () => {
|
||||
beforeEach(() => initEditor());
|
||||
it('requests raw file content', () => {
|
||||
expect(Api.getRawFile).toHaveBeenCalledWith(projectId, filePath, { ref: 'main' });
|
||||
expect(Api.getRawFile).toHaveBeenCalledWith(
|
||||
projectId,
|
||||
filePath,
|
||||
{ ref: 'main' },
|
||||
{ responseType: 'text', transformResponse: expect.any(Function) },
|
||||
);
|
||||
});
|
||||
|
||||
it('creates an editor instance with the raw content', () => {
|
||||
|
|
|
|||
|
|
@ -117,6 +117,9 @@ describe('IssuesDashboardApp component', () => {
|
|||
propsData: {
|
||||
eeTypeTokenOptions,
|
||||
},
|
||||
stubs: {
|
||||
GlIntersperse: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -264,10 +264,23 @@ const makeFilteredTokens = ({ grouped }) => [
|
|||
{ type: TOKEN_TYPE_MILESTONE, value: { data: 'season 30', operator: OPERATOR_NOT } },
|
||||
{ type: TOKEN_TYPE_LABEL, value: { data: 'cartoon', operator: OPERATOR_IS } },
|
||||
{ type: TOKEN_TYPE_LABEL, value: { data: 'tv', operator: OPERATOR_IS } },
|
||||
{ type: TOKEN_TYPE_LABEL, value: { data: 'live action', operator: OPERATOR_NOT } },
|
||||
{ type: TOKEN_TYPE_LABEL, value: { data: 'drama', operator: OPERATOR_NOT } },
|
||||
{ type: TOKEN_TYPE_LABEL, value: { data: 'comedy', operator: OPERATOR_OR } },
|
||||
{ type: TOKEN_TYPE_LABEL, value: { data: 'sitcom', operator: OPERATOR_OR } },
|
||||
...(grouped
|
||||
? [
|
||||
{
|
||||
type: TOKEN_TYPE_LABEL,
|
||||
value: { data: ['live action', 'drama'], operator: OPERATOR_NOT },
|
||||
},
|
||||
]
|
||||
: [
|
||||
{ type: TOKEN_TYPE_LABEL, value: { data: 'live action', operator: OPERATOR_NOT } },
|
||||
{ type: TOKEN_TYPE_LABEL, value: { data: 'drama', operator: OPERATOR_NOT } },
|
||||
]),
|
||||
...(grouped
|
||||
? [{ type: TOKEN_TYPE_LABEL, value: { data: ['comedy', 'sitcom'], operator: OPERATOR_OR } }]
|
||||
: [
|
||||
{ type: TOKEN_TYPE_LABEL, value: { data: 'comedy', operator: OPERATOR_OR } },
|
||||
{ type: TOKEN_TYPE_LABEL, value: { data: 'sitcom', operator: OPERATOR_OR } },
|
||||
]),
|
||||
{ type: TOKEN_TYPE_RELEASE, value: { data: 'v3', operator: OPERATOR_IS } },
|
||||
{ type: TOKEN_TYPE_RELEASE, value: { data: 'v4', operator: OPERATOR_IS } },
|
||||
{ type: TOKEN_TYPE_RELEASE, value: { data: 'v20', operator: OPERATOR_NOT } },
|
||||
|
|
|
|||
|
|
@ -183,6 +183,7 @@ describe('groupMultiSelectFilterTokens', () => {
|
|||
groupMultiSelectFilterTokens(filteredTokens, [
|
||||
{ type: 'assignee', multiSelect: true },
|
||||
{ type: 'author', multiSelect: true },
|
||||
{ type: 'label', multiSelect: true },
|
||||
]),
|
||||
).toEqual(groupedFilteredTokens);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -108,15 +108,20 @@ describe('Details Header', () => {
|
|||
|
||||
describe('menu', () => {
|
||||
it.each`
|
||||
canDelete | disabled | isVisible
|
||||
${true} | ${false} | ${true}
|
||||
${true} | ${true} | ${false}
|
||||
${false} | ${false} | ${false}
|
||||
${false} | ${true} | ${false}
|
||||
destroyContainerRepository | disabled | isVisible
|
||||
${true} | ${false} | ${true}
|
||||
${true} | ${true} | ${false}
|
||||
${false} | ${false} | ${false}
|
||||
${false} | ${true} | ${false}
|
||||
`(
|
||||
'when canDelete is $canDelete and disabled is $disabled is $isVisible that the menu is visible',
|
||||
({ canDelete, disabled, isVisible }) => {
|
||||
mountComponent({ propsData: { image: { ...defaultImage, canDelete }, disabled } });
|
||||
'when userPermissions.destroyContainerRepository is $destroyContainerRepository and disabled is $disabled is $isVisible that the menu is visible',
|
||||
({ destroyContainerRepository, disabled, isVisible }) => {
|
||||
mountComponent({
|
||||
propsData: {
|
||||
image: { ...defaultImage, userPermissions: { destroyContainerRepository } },
|
||||
disabled,
|
||||
},
|
||||
});
|
||||
|
||||
expect(findMenu().exists()).toBe(isVisible);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ describe('tags list row', () => {
|
|||
});
|
||||
|
||||
it("does not exist when the row can't be deleted", () => {
|
||||
const customTag = { ...tag, canDelete: false };
|
||||
const customTag = { ...tag, userPermissions: { destroyContainerRepositoryTag: false } };
|
||||
|
||||
mountComponent({ ...defaultProps, tag: customTag });
|
||||
|
||||
|
|
@ -306,8 +306,11 @@ describe('tags list row', () => {
|
|||
expect(findAdditionalActionsMenu().classes('gl-pointer-events-none')).toBe(false);
|
||||
});
|
||||
|
||||
it('is not rendered when tag.canDelete is false', () => {
|
||||
mountComponent({ ...defaultProps, tag: { ...tag, canDelete: false } });
|
||||
it('is not rendered when tag.userPermissions.destroyContainerRegistryTag is false', () => {
|
||||
mountComponent({
|
||||
...defaultProps,
|
||||
tag: { ...tag, userPermissions: { destroyContainerRepositoryTag: false } },
|
||||
});
|
||||
|
||||
expect(findAdditionalActionsMenu().exists()).toBe(false);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -314,7 +314,11 @@ describe('Tags List', () => {
|
|||
|
||||
describe('when user does not have permission to delete list rows', () => {
|
||||
it('sets registry list hiddenDelete prop to true', async () => {
|
||||
resolver = jest.fn().mockResolvedValue(imageTagsMock({ canDelete: false }));
|
||||
resolver = jest
|
||||
.fn()
|
||||
.mockResolvedValue(
|
||||
imageTagsMock({ userPermissions: { destroyContainerRepository: false } }),
|
||||
);
|
||||
mountComponent();
|
||||
await waitForApolloRequestRender();
|
||||
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ describe('Image List Row', () => {
|
|||
|
||||
expect(findDeleteBtn().props()).toMatchObject({
|
||||
title: REMOVE_REPOSITORY_LABEL,
|
||||
tooltipDisabled: item.canDelete,
|
||||
tooltipDisabled: item.userPermissions.destroyContainerRepository,
|
||||
tooltipTitle: LIST_DELETE_BUTTON_DISABLED,
|
||||
});
|
||||
});
|
||||
|
|
@ -174,15 +174,23 @@ describe('Image List Row', () => {
|
|||
});
|
||||
|
||||
it.each`
|
||||
canDelete | status | state
|
||||
${false} | ${''} | ${true}
|
||||
${false} | ${IMAGE_DELETE_SCHEDULED_STATUS} | ${true}
|
||||
${true} | ${IMAGE_DELETE_SCHEDULED_STATUS} | ${true}
|
||||
${true} | ${''} | ${false}
|
||||
destroyContainerRepository | status | state
|
||||
${false} | ${''} | ${true}
|
||||
${false} | ${IMAGE_DELETE_SCHEDULED_STATUS} | ${true}
|
||||
${true} | ${IMAGE_DELETE_SCHEDULED_STATUS} | ${true}
|
||||
${true} | ${''} | ${false}
|
||||
`(
|
||||
'disabled is $state when canDelete is $canDelete and status is $status',
|
||||
({ canDelete, status, state }) => {
|
||||
mountComponent({ item: { ...item, canDelete, status } });
|
||||
'disabled is $state when userPermissions.destroyContainerRepository is $destroyContainerRepository and status is $status',
|
||||
({ destroyContainerRepository, status, state }) => {
|
||||
mountComponent({
|
||||
item: {
|
||||
...item,
|
||||
userPermissions: {
|
||||
destroyContainerRepository,
|
||||
},
|
||||
status,
|
||||
},
|
||||
});
|
||||
|
||||
expect(findDeleteBtn().props('disabled')).toBe(state);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
const userPermissionsData = {
|
||||
userPermissions: {
|
||||
destroyContainerRepository: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const imagesListResponse = [
|
||||
{
|
||||
__typename: 'ContainerRepository',
|
||||
|
|
@ -7,7 +13,6 @@ export const imagesListResponse = [
|
|||
status: null,
|
||||
migrationState: 'default',
|
||||
location: '0.0.0.0:5000/gitlab-org/gitlab-test/rails-12009',
|
||||
canDelete: true,
|
||||
createdAt: '2020-11-03T13:29:21Z',
|
||||
expirationPolicyStartedAt: null,
|
||||
expirationPolicyCleanupStatus: 'UNSCHEDULED',
|
||||
|
|
@ -15,6 +20,7 @@ export const imagesListResponse = [
|
|||
id: 'gid://gitlab/Project/22',
|
||||
path: 'GITLAB-TEST',
|
||||
},
|
||||
...userPermissionsData,
|
||||
},
|
||||
{
|
||||
__typename: 'ContainerRepository',
|
||||
|
|
@ -24,7 +30,6 @@ export const imagesListResponse = [
|
|||
status: null,
|
||||
migrationState: 'default',
|
||||
location: '0.0.0.0:5000/gitlab-org/gitlab-test/rails-20572',
|
||||
canDelete: true,
|
||||
createdAt: '2020-09-21T06:57:43Z',
|
||||
expirationPolicyStartedAt: null,
|
||||
expirationPolicyCleanupStatus: 'UNSCHEDULED',
|
||||
|
|
@ -32,6 +37,7 @@ export const imagesListResponse = [
|
|||
id: 'gid://gitlab/Project/22',
|
||||
path: 'gitlab-test',
|
||||
},
|
||||
...userPermissionsData,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -125,7 +131,6 @@ export const containerRepositoryMock = {
|
|||
path: 'gitlab-org/gitlab-test/rails-12009',
|
||||
status: null,
|
||||
location: 'host.docker.internal:5000/gitlab-org/gitlab-test/rails-12009',
|
||||
canDelete: true,
|
||||
createdAt: '2020-11-03T13:29:21Z',
|
||||
expirationPolicyStartedAt: null,
|
||||
expirationPolicyCleanupStatus: 'UNSCHEDULED',
|
||||
|
|
@ -139,6 +144,7 @@ export const containerRepositoryMock = {
|
|||
},
|
||||
__typename: 'Project',
|
||||
},
|
||||
...userPermissionsData,
|
||||
};
|
||||
|
||||
export const tagsPageInfo = {
|
||||
|
|
@ -160,7 +166,9 @@ export const tagsMock = [
|
|||
createdAt: '2020-11-03T13:29:38+00:00',
|
||||
publishedAt: '2020-11-05T13:29:38+00:00',
|
||||
totalSize: '1099511627776',
|
||||
canDelete: true,
|
||||
userPermissions: {
|
||||
destroyContainerRepositoryTag: true,
|
||||
},
|
||||
__typename: 'ContainerRepositoryTag',
|
||||
},
|
||||
{
|
||||
|
|
@ -173,22 +181,27 @@ export const tagsMock = [
|
|||
createdAt: '2020-11-03T13:29:32+00:00',
|
||||
publishedAt: '2020-11-05T13:29:32+00:00',
|
||||
totalSize: '536870912000',
|
||||
canDelete: true,
|
||||
userPermissions: {
|
||||
destroyContainerRepositoryTag: true,
|
||||
},
|
||||
__typename: 'ContainerRepositoryTag',
|
||||
},
|
||||
];
|
||||
|
||||
export const imageTagsMock = ({ nodes = tagsMock, canDelete = true } = {}) => ({
|
||||
export const imageTagsMock = ({ nodes = tagsMock, userPermissions = {} } = {}) => ({
|
||||
data: {
|
||||
containerRepository: {
|
||||
id: containerRepositoryMock.id,
|
||||
tagsCount: nodes.length,
|
||||
canDelete,
|
||||
tags: {
|
||||
nodes,
|
||||
pageInfo: { ...tagsPageInfo },
|
||||
__typename: 'ContainerRepositoryTagConnection',
|
||||
},
|
||||
userPermissions: {
|
||||
...userPermissionsData.userPermissions,
|
||||
...userPermissions,
|
||||
},
|
||||
__typename: 'ContainerRepositoryDetails',
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import {
|
|||
GlFilteredSearchSuggestion,
|
||||
GlFilteredSearchTokenSegment,
|
||||
GlDropdownDivider,
|
||||
GlLabel,
|
||||
} from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
|
|
@ -52,6 +53,7 @@ function createComponent(options = {}) {
|
|||
alignSuggestions: function fakeAlignSuggestions() {},
|
||||
suggestionsListClass: () => 'custom-class',
|
||||
termsAsTokens: () => false,
|
||||
hasScopedLabelsFeature: false,
|
||||
},
|
||||
stubs,
|
||||
listeners,
|
||||
|
|
@ -192,9 +194,9 @@ describe('LabelToken', () => {
|
|||
const tokenSegments = findTokenSegments();
|
||||
|
||||
expect(tokenSegments).toHaveLength(3); // Label, =, "Foo Label"
|
||||
expect(tokenSegments.at(2).text()).toBe(`~${mockRegularLabel.title}`); // "Foo Label"
|
||||
expect(tokenSegments.at(2).find('.gl-token').attributes('style')).toBe(
|
||||
'background-color: rgb(186, 218, 85); color: rgb(255, 255, 255);',
|
||||
expect(tokenSegments.at(2).text()).toBe(`${mockRegularLabel.title}`); // "Foo Label"
|
||||
expect(tokenSegments.at(2).findComponent(GlLabel).props('backgroundColor')).toBe(
|
||||
mockRegularLabel.color,
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -417,6 +417,17 @@ RSpec.describe Label, feature_category: :team_planning do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.sorted_by_similarity_desc' do
|
||||
context 'when sorted by similarity' do
|
||||
it 'returns most relevant labels first' do
|
||||
label1 = create(:label, title: 'exact')
|
||||
label2 = create(:label, title: 'less exact')
|
||||
label3 = create(:label, title: 'other', description: 'mentions exact')
|
||||
expect(described_class.sorted_by_similarity_desc('exact')).to eq([label1, label2, label3])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.optionally_subscribed_by' do
|
||||
let!(:user) { create(:user) }
|
||||
let!(:label) { create(:label) }
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ module FilteredSearchHelpers
|
|||
create_token('Release', release_tag)
|
||||
end
|
||||
|
||||
def label_token(label_name = nil, has_symbol = true)
|
||||
def label_token(label_name = nil, has_symbol = false)
|
||||
symbol = has_symbol ? '~' : nil
|
||||
create_token('Label', label_name, symbol)
|
||||
end
|
||||
|
|
@ -264,11 +264,11 @@ module FilteredSearchHelpers
|
|||
end
|
||||
|
||||
def expect_label_token(value)
|
||||
expect(page).to have_css '.gl-filtered-search-token', text: "Label = ~#{value}"
|
||||
expect(page).to have_css '.gl-filtered-search-token', text: "Label = #{value}"
|
||||
end
|
||||
|
||||
def expect_negated_label_token(value)
|
||||
expect(page).to have_css '.gl-filtered-search-token', text: "Label != ~#{value}"
|
||||
expect(page).to have_css '.gl-filtered-search-token', text: "Label != #{value}"
|
||||
end
|
||||
|
||||
def expect_milestone_token(value)
|
||||
|
|
|
|||
Loading…
Reference in New Issue