提交 261c9668 编写于 作者: G GitLab Bot

Add latest changes from gitlab-org/gitlab@master

上级 c41b66bd
......@@ -16,9 +16,11 @@
## Author's checklist (required)
- [ ] Follow the [Documentation Guidelines](https://docs.gitlab.com/ee/development/documentation/) and [Style Guide](https://docs.gitlab.com/ee/development/documentation/styleguide.html).
- If you have `developer` access or higher (for example, GitLab team members or [Core Team](https://about.gitlab.com/community/core-team/) members)
- If you have **Developer** permissions or higher:
- [ ] Ensure that the [product tier badge](https://docs.gitlab.com/ee/development/documentation/styleguide.html#product-badges) is added to doc's `h1`.
- [ ] Apply the ~documentation label, plus:
- The corresponding DevOps stage and group label, if applicable.
- The corresponding DevOps stage and group labels, if applicable.
- ~"development guidelines" when changing docs under `doc/development/*`, `CONTRIBUTING.md`, or `README.md`.
- ~"development guidelines" and ~"Documentation guidelines" when changing docs under `development/documentation/*`.
- ~"development guidelines" and ~"Description templates (.gitlab/\*)" when creating/updating issue and MR description templates.
......@@ -30,10 +32,9 @@ When applicable:
- [ ] Update the [permissions table](https://docs.gitlab.com/ee/user/permissions.html).
- [ ] Link docs to and from the higher-level index page, plus other related docs where helpful.
- [ ] Add the [product tier badge](https://docs.gitlab.com/ee/development/documentation/styleguide.html#product-badges) accordingly.
- [ ] Add [GitLab's version history note(s)](https://docs.gitlab.com/ee/development/documentation/styleguide.html#text-for-documentation-requiring-version-text).
- [ ] Add the [product tier badge](https://docs.gitlab.com/ee/development/documentation/styleguide.html#product-badges).
- [ ] Add/update the [feature flag section](https://docs.gitlab.com/ee/development/documentation/feature_flags.html).
- [ ] If you're changing document headings, search `doc/*`, `app/views/*`, and `ee/app/views/*` for old headings replacing with the new ones to [avoid broken anchors](https://docs.gitlab.com/ee/development/documentation/styleguide.html#anchor-links).
## Review checklist
......@@ -46,8 +47,9 @@ All reviewers can help ensure accuracy, clarity, completeness, and adherence to
**2. Technical Writer**
- [ ] Technical writer review. If not requested for this MR, must be scheduled post-merge. To request for this MR, assign the writer listed for the applicable [DevOps stage](https://about.gitlab.com/handbook/product/product-categories/#devops-stages).
- [ ] Ensure ~"Technical Writing", ~"documentation", and a `docs::` scoped label are added.
- [ ] Add ~docs-only when the only files changed are under `doc/*`.
- [ ] Ensure docs metadata are present and up-to-date.
- [ ] Ensure ~"Technical Writing" and ~"documentation" are added.
- [ ] Add the corresponding `docs::` scoped label.
- [ ] Add ~"tw::doing" when starting work on the MR.
- [ ] Add ~"tw::finished" if Technical Writing team work on the MR is complete but it remains open.
......
......@@ -516,7 +516,7 @@ GEM
googleapis-common-protos-types (~> 1.0)
gssapi (1.2.0)
ffi (>= 1.0.1)
guard (2.15.1)
guard (2.16.2)
formatador (>= 0.2.4)
listen (>= 2.7, < 4.0)
lumberjack (>= 1.0.12, < 2.0)
......@@ -649,10 +649,9 @@ GEM
xml-simple
licensee (8.9.2)
rugged (~> 0.24)
listen (3.1.5)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
ruby_dep (~> 1.2)
listen (3.2.1)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
locale (2.1.3)
lockbox (0.3.3)
lograge (0.11.2)
......@@ -664,7 +663,7 @@ GEM
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
lru_redux (1.1.0)
lumberjack (1.0.13)
lumberjack (1.2.7)
mail (2.7.1)
mini_mime (>= 0.1.1)
marcel (0.3.3)
......@@ -899,9 +898,9 @@ GEM
rainbow (3.0.0)
raindrops (0.19.1)
rake (12.3.3)
rb-fsevent (0.10.2)
rb-inotify (0.9.10)
ffi (>= 0.5.0, < 2)
rb-fsevent (0.10.4)
rb-inotify (0.10.1)
ffi (~> 1.0)
rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3)
rbtrace (0.4.14)
......@@ -1020,7 +1019,6 @@ GEM
nokogiri (>= 1.5.10)
ruby-statistics (2.1.2)
ruby2_keywords (0.0.2)
ruby_dep (1.5.0)
ruby_parser (3.13.1)
sexp_processor (~> 4.9)
rubyntlm (0.6.2)
......
/* eslint-disable @gitlab/require-i18n-strings */
import { __ } from '~/locale';
export const ANY_AUTHOR = 'Any';
export const NO_LABEL = 'No label';
const DEFAULT_LABEL_NO_LABEL = { value: 'No label', text: __('No label') };
export const DEFAULT_LABEL_NONE = { value: 'None', text: __('None') };
export const DEFAULT_LABEL_ANY = { value: 'Any', text: __('Any') };
export const DEFAULT_LABELS = [DEFAULT_LABEL_NO_LABEL];
export const DEBOUNCE_DELAY = 200;
......@@ -11,13 +16,11 @@ export const SortDirection = {
ascending: 'ascending',
};
export const defaultMilestones = [
// eslint-disable-next-line @gitlab/require-i18n-strings
{ value: 'None', text: __('None') },
// eslint-disable-next-line @gitlab/require-i18n-strings
{ value: 'Any', text: __('Any') },
// eslint-disable-next-line @gitlab/require-i18n-strings
export const DEFAULT_MILESTONES = [
DEFAULT_LABEL_NONE,
DEFAULT_LABEL_ANY,
{ value: 'Upcoming', text: __('Upcoming') },
// eslint-disable-next-line @gitlab/require-i18n-strings
{ value: 'Started', text: __('Started') },
];
/* eslint-enable @gitlab/require-i18n-strings */
......@@ -14,10 +14,9 @@ import { __ } from '~/locale';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { stripQuotes } from '../filtered_search_utils';
import { NO_LABEL, DEBOUNCE_DELAY } from '../constants';
import { DEFAULT_LABELS, DEBOUNCE_DELAY } from '../constants';
export default {
noLabel: NO_LABEL,
components: {
GlToken,
GlFilteredSearchToken,
......@@ -38,6 +37,7 @@ export default {
data() {
return {
labels: this.config.initialLabels || [],
defaultLabels: this.config.defaultLabels || DEFAULT_LABELS,
loading: true,
};
},
......@@ -105,9 +105,13 @@ export default {
>
</template>
<template #suggestions>
<gl-filtered-search-suggestion :value="$options.noLabel">{{
__('No label')
}}</gl-filtered-search-suggestion>
<gl-filtered-search-suggestion
v-for="label in defaultLabels"
:key="label.value"
:value="label.value"
>
{{ label.text }}
</gl-filtered-search-suggestion>
<gl-dropdown-divider />
<gl-loading-icon v-if="loading" />
<template v-else>
......
......@@ -11,10 +11,9 @@ import createFlash from '~/flash';
import { __ } from '~/locale';
import { stripQuotes } from '../filtered_search_utils';
import { defaultMilestones, DEBOUNCE_DELAY } from '../constants';
import { DEFAULT_MILESTONES, DEBOUNCE_DELAY } from '../constants';
export default {
defaultMilestones,
components: {
GlFilteredSearchToken,
GlFilteredSearchSuggestion,
......@@ -34,6 +33,7 @@ export default {
data() {
return {
milestones: this.config.initialMilestones || [],
defaultMilestones: this.config.defaultMilestones || DEFAULT_MILESTONES,
loading: true,
};
},
......@@ -89,11 +89,12 @@ export default {
</template>
<template #suggestions>
<gl-filtered-search-suggestion
v-for="milestone in $options.defaultMilestones"
v-for="milestone in defaultMilestones"
:key="milestone.value"
:value="milestone.value"
>{{ milestone.text }}</gl-filtered-search-suggestion
>
{{ milestone.text }}
</gl-filtered-search-suggestion>
<gl-dropdown-divider />
<gl-loading-icon v-if="loading" />
<template v-else>
......
......@@ -16,14 +16,6 @@ module Mutations
required: true,
description: 'Title of the snippet'
argument :file_name, GraphQL::STRING_TYPE,
required: false,
description: 'File name of the snippet'
argument :content, GraphQL::STRING_TYPE,
required: false,
description: 'Content of the snippet'
argument :description, GraphQL::STRING_TYPE,
required: false,
description: 'Description of the snippet'
......
......@@ -14,14 +14,6 @@ module Mutations
required: false,
description: 'Title of the snippet'
argument :file_name, GraphQL::STRING_TYPE,
required: false,
description: 'File name of the snippet'
argument :content, GraphQL::STRING_TYPE,
required: false,
description: 'Content of the snippet'
argument :description, GraphQL::STRING_TYPE,
required: false,
description: 'Description of the snippet'
......
......@@ -2,11 +2,13 @@
module Ci
class RetryBuildService < ::BaseService
CLONE_ACCESSORS = %i[pipeline project ref tag options name
allow_failure stage stage_id stage_idx trigger_request
yaml_variables when environment coverage_regex
description tag_list protected needs_attributes
resource_group scheduling_type].freeze
def self.clone_accessors
%i[pipeline project ref tag options name
allow_failure stage stage_id stage_idx trigger_request
yaml_variables when environment coverage_regex
description tag_list protected needs_attributes
resource_group scheduling_type].freeze
end
def execute(build)
build.ensure_scheduling_type!
......@@ -28,7 +30,7 @@ module Ci
raise Gitlab::Access::AccessDeniedError
end
attributes = CLONE_ACCESSORS.map do |attribute|
attributes = self.class.clone_accessors.map do |attribute|
[attribute, build.public_send(attribute)] # rubocop:disable GitlabSecurity/PublicSend
end.to_h
......@@ -68,3 +70,5 @@ module Ci
end
end
end
Ci::RetryBuildService.prepend_if_ee('EE::Ci::RetryBuildService')
---
title: Remove file_name and content in snippet mutations
merge_request: 40727
author:
type: changed
---
title: Improve group search users scope performance
merge_request: 38701
author:
type: performance
---
title: Remove pipeline_id column from requirements_test_reports
merge_request: 38924
author:
type: deprecated
---
title: Add kubernetes_agents usage metric
merge_request: 40559
author:
type: other
# frozen_string_literal: true
class RemovePipelineIdFromTestReports < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
remove_column :requirements_management_test_reports, :pipeline_id
end
def down
add_column :requirements_management_test_reports, :pipeline_id, :integer
with_lock_retries do
# rubocop:disable Migration/AddConcurrentForeignKey
add_foreign_key :requirements_management_test_reports, :ci_pipelines, column: :pipeline_id, on_delete: :nullify
# rubocop:enable Migration/AddConcurrentForeignKey
end
end
end
66653e275889da8e695843f648af36c8a4e275b4d3215119eab4942db1b4b823
\ No newline at end of file
......@@ -15120,7 +15120,6 @@ CREATE TABLE public.requirements_management_test_reports (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
requirement_id bigint NOT NULL,
pipeline_id bigint,
author_id bigint,
state smallint NOT NULL,
build_id bigint
......@@ -20644,8 +20643,6 @@ CREATE INDEX index_requirements_management_test_reports_on_author_id ON public.r
CREATE INDEX index_requirements_management_test_reports_on_build_id ON public.requirements_management_test_reports USING btree (build_id);
CREATE INDEX index_requirements_management_test_reports_on_pipeline_id ON public.requirements_management_test_reports USING btree (pipeline_id);
CREATE INDEX index_requirements_management_test_reports_on_requirement_id ON public.requirements_management_test_reports USING btree (requirement_id);
CREATE INDEX index_requirements_on_author_id ON public.requirements USING btree (author_id);
......@@ -22205,9 +22202,6 @@ ALTER TABLE ONLY public.service_desk_settings
ALTER TABLE ONLY public.group_custom_attributes
ADD CONSTRAINT fk_rails_246e0db83a FOREIGN KEY (group_id) REFERENCES public.namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.requirements_management_test_reports
ADD CONSTRAINT fk_rails_24cecc1e68 FOREIGN KEY (pipeline_id) REFERENCES public.ci_pipelines(id) ON DELETE SET NULL;
ALTER TABLE ONLY public.cluster_agents
ADD CONSTRAINT fk_rails_25e9fc2d5d FOREIGN KEY (project_id) REFERENCES public.projects(id) ON DELETE CASCADE;
......
......@@ -2746,21 +2746,11 @@ input CreateSnippetInput {
"""
clientMutationId: String
"""
Content of the snippet
"""
content: String
"""
Description of the snippet
"""
description: String
"""
File name of the snippet
"""
fileName: String
"""
The project full path the snippet is associated with
"""
......@@ -2959,6 +2949,66 @@ type DastScannerProfileEdge {
node: DastScannerProfile
}
"""
Identifier of DastScannerProfile
"""
scalar DastScannerProfileID
"""
Autogenerated input type of DastScannerProfileUpdate
"""
input DastScannerProfileUpdateInput {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
The project the scanner profile belongs to.
"""
fullPath: ID!
"""
ID of the scanner profile to be updated.
"""
id: DastScannerProfileID!
"""
The name of the scanner profile.
"""
profileName: String!
"""
The maximum number of seconds allowed for the spider to traverse the site.
"""
spiderTimeout: Int!
"""
The maximum number of seconds allowed for the site under test to respond to a request.
"""
targetTimeout: Int!
}
"""
Autogenerated return type of DastScannerProfileUpdate
"""
type DastScannerProfileUpdatePayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Errors encountered during execution of the mutation.
"""
errors: [String!]!
"""
ID of the scanner profile.
"""
id: DastScannerProfileID
}
"""
Represents a DAST Site Profile.
"""
......@@ -9757,6 +9807,7 @@ type Mutation {
createSnippet(input: CreateSnippetInput!): CreateSnippetPayload
dastOnDemandScanCreate(input: DastOnDemandScanCreateInput!): DastOnDemandScanCreatePayload
dastScannerProfileCreate(input: DastScannerProfileCreateInput!): DastScannerProfileCreatePayload
dastScannerProfileUpdate(input: DastScannerProfileUpdateInput!): DastScannerProfileUpdatePayload
dastSiteProfileCreate(input: DastSiteProfileCreateInput!): DastSiteProfileCreatePayload
dastSiteProfileDelete(input: DastSiteProfileDeleteInput!): DastSiteProfileDeletePayload
dastSiteProfileUpdate(input: DastSiteProfileUpdateInput!): DastSiteProfileUpdatePayload
......@@ -16566,21 +16617,11 @@ input UpdateSnippetInput {
"""
clientMutationId: String
"""
Content of the snippet
"""
content: String
"""
Description of the snippet
"""
description: String
"""
File name of the snippet
"""
fileName: String
"""
The global id of the snippet to update
"""
......
......@@ -7390,26 +7390,6 @@
},
"defaultValue": null
},
{
"name": "fileName",
"description": "File name of the snippet",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "content",
"description": "Content of the snippet",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "description",
"description": "Description of the snippet",
......@@ -8016,6 +7996,174 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "DastScannerProfileID",
"description": "Identifier of DastScannerProfile",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "DastScannerProfileUpdateInput",
"description": "Autogenerated input type of DastScannerProfileUpdate",
"fields": null,
"inputFields": [
{
"name": "fullPath",
"description": "The project the scanner profile belongs to.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "id",
"description": "ID of the scanner profile to be updated.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "DastScannerProfileID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "profileName",
"description": "The name of the scanner profile.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "spiderTimeout",
"description": "The maximum number of seconds allowed for the spider to traverse the site.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "targetTimeout",
"description": "The maximum number of seconds allowed for the site under test to respond to a request.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "DastScannerProfileUpdatePayload",
"description": "Autogenerated return type of DastScannerProfileUpdate",
"fields": [
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "errors",
"description": "Errors encountered during execution of the mutation.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "id",
"description": "ID of the scanner profile.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "DastScannerProfileID",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "DastSiteProfile",
......@@ -27986,6 +28134,33 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "dastScannerProfileUpdate",
"description": null,
"args": [
{
"name": "input",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "DastScannerProfileUpdateInput",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "DastScannerProfileUpdatePayload",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "dastSiteProfileCreate",
"description": null,
......@@ -48753,26 +48928,6 @@
},
"defaultValue": null
},
{
"name": "fileName",
"description": "File name of the snippet",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "content",
"description": "Content of the snippet",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "description",
"description": "Description of the snippet",
......@@ -502,6 +502,16 @@ Autogenerated return type of DastScannerProfileCreate
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `id` | ID | ID of the scanner profile. |
## DastScannerProfileUpdatePayload
Autogenerated return type of DastScannerProfileUpdate
| Name | Type | Description |
| --- | ---- | ---------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `id` | DastScannerProfileID | ID of the scanner profile. |
## DastSiteProfile
Represents a DAST Site Profile.
......
......@@ -15,7 +15,7 @@ Elasticsearch is enabled, you'll have the benefit of fast search response times
and the advantage of the following special searches:
- [Advanced Search](../user/search/advanced_global_search.md)
- [Advanced Syntax Search](../user/search/advanced_search_syntax.md)
- [Advanced Search Syntax](../user/search/advanced_search_syntax.md)
## Version requirements
......@@ -746,6 +746,17 @@ Here are some common pitfalls and how to overcome them:
You can run `sudo gitlab-rake gitlab:elastic:projects_not_indexed` to display projects that aren't indexed.
- **No new data is added to the Elasticsearch index when I push code**
NOTE: **Note:**
This was [fixed in GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35936) and the Rake task is not available for versions greater than that.
When performing the initial indexing of blobs, we lock all projects until the project finishes indexing. It could happen that an error during the process causes one or multiple projects to remain locked. In order to unlock them, run:
```shell
sudo gitlab-rake gitlab:elastic:clear_locked_projects
```
- **"Can't specify parent if no parent field has been configured"**
If you enabled Elasticsearch before GitLab 8.12 and have not rebuilt indexes you will get
......
......@@ -57,7 +57,7 @@ With GitLab Enterprise Edition, you can also:
- [Multiple Issue Boards](project/issue_board.md#multiple-issue-boards).
- Create formal relationships between issues with [Related Issues](project/issues/related_issues.md).
- Use [Burndown Charts](project/milestones/burndown_charts.md) to track progress during a sprint or while working on a new version of their software.
- Leverage [Elasticsearch](../integration/elasticsearch.md) with [Advanced Search](search/advanced_global_search.md) and [Advanced Syntax Search](search/advanced_search_syntax.md) for faster, more advanced code search across your entire GitLab instance.
- Leverage [Elasticsearch](../integration/elasticsearch.md) with [Advanced Search](search/advanced_global_search.md) and [Advanced Search Syntax](search/advanced_search_syntax.md) for faster, more advanced code search across your entire GitLab instance.
- [Authenticate users with Kerberos](../integration/kerberos.md).
- [Mirror a repository](project/repository/repository_mirroring.md) from elsewhere on your local server.
- [Export issues as CSV](project/issues/csv_export.md).
......
......@@ -60,7 +60,7 @@ project you have access to.
![Advanced Search](img/advanced_global_search.png)
You can also use the [Advanced Syntax Search](advanced_search_syntax.md) which
You can also use the [Advanced Search Syntax](advanced_search_syntax.md) which
provides some useful queries.
NOTE: **Note:**
......
......@@ -5,7 +5,7 @@ info: "To determine the technical writer assigned to the Stage/Group associated
type: reference
---
# Advanced Syntax Search **(STARTER)**
# Advanced Search Syntax **(STARTER)**
> - Introduced in [GitLab Enterprise Starter](https://about.gitlab.com/pricing/) 9.2
......@@ -19,7 +19,7 @@ visit the [administrator documentation](../../integration/elasticsearch.md).
## Overview
The Advanced Syntax Search is a subset of the
The Advanced Search Syntax is a subset of the
[Advanced Search](advanced_global_search.md), which you can use if you
want to have more specific search results.
......@@ -38,9 +38,9 @@ not so sure.
In that case, using the advanced search syntax in your query will yield much
better results.
## Using the Advanced Syntax Search
## Using the Advanced Search Syntax
The Advanced Syntax Search supports fuzzy or exact search queries with prefixes,
The Advanced Search Syntax supports fuzzy or exact search queries with prefixes,
boolean operators, and much more.
Full details can be found in the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/5.3/query-dsl-simple-query-string-query.html#_simple_query_string_syntax), but
......@@ -57,7 +57,7 @@ here's a quick guide:
### Syntax search filters
The Advanced Syntax Search also supports the use of filters. The available filters are:
The Advanced Search Syntax also supports the use of filters. The available filters are:
- filename: Filters by filename. You can use the glob (`*`) operator for fuzzy matching.
- path: Filters by path. You can use the glob (`*`) operator for fuzzy matching.
......
......@@ -215,8 +215,8 @@ GitLab instance.
[Learn how to use the Advanced Search.](advanced_global_search.md)
## Advanced Syntax Search **(STARTER)**
## Advanced Search Syntax **(STARTER)**
Use advanced queries for more targeted search results.
[Learn how to use the Advanced Syntax Search.](advanced_search_syntax.md)
[Learn how to use the Advanced Search Syntax.](advanced_search_syntax.md)
......@@ -12,20 +12,24 @@ module Gitlab
# rubocop:disable CodeReuse/ActiveRecord
def users
# 1: get all groups the current user has access to
groups = GroupsFinder.new(current_user).execute.joins(:users)
# get all groups the current user has access to
# ignore order inherited from GroupsFinder to improve performance
current_user_groups = GroupsFinder.new(current_user).execute.unscope(:order)
# 2: Get the group's whole hierarchy
group_users = @group.direct_and_indirect_users
# the hierarchy of the current group
group_groups = @group.self_and_hierarchy.unscope(:order)
# 3: get all users the current user has access to (->
# `SearchResults#users`), which also applies the query.
# the groups where the above hierarchies intersect
intersect_groups = group_groups.where(id: current_user_groups)
# members of @group hierarchy where the user has access to the groups
members = GroupMember.where(group: intersect_groups).non_invite
# get all users the current user has access to (-> `SearchResults#users`), which also applies the query
users = super
# 4: filter for users that belong to the previously selected groups
users
.where(id: group_users.select('id'))
.where(id: groups.select('members.user_id'))
# filter users that belong to the previously selected groups
users.where(id: members.select(:user_id))
end
# rubocop:enable CodeReuse/ActiveRecord
......
......@@ -111,6 +111,7 @@ module Gitlab
clusters_applications_jupyter: count(::Clusters::Applications::Jupyter.available),
clusters_applications_cilium: count(::Clusters::Applications::Cilium.available),
clusters_management_project: count(::Clusters::Cluster.with_management_project),
kubernetes_agents: count(::Clusters::Agent),
in_review_folder: count(::Environment.in_review_folder),
grafana_integrated_projects: count(GrafanaIntegration.enabled),
groups: count(Group),
......
......@@ -28080,6 +28080,9 @@ msgstr ""
msgid "You are not authorized to perform this action"
msgstr ""
msgid "You are not authorized to update this scanner profile"
msgstr ""
msgid "You are now impersonating %{username}"
msgstr ""
......
......@@ -65,6 +65,9 @@ FactoryBot.define do
create(:alert_management_alert, issue: alert_bot_issues[0], project: projects[0])
create(:self_managed_prometheus_alert_event, related_issues: [issues[1]], project: projects[0])
# Kubernetes agents
create(:cluster_agent, project: projects[0])
# Enabled clusters
gcp_cluster = create(:cluster_provider_gcp, :created).cluster
create(:cluster_provider_aws, :created)
......
import { mount } from '@vue/test-utils';
import { GlFilteredSearchToken, GlFilteredSearchTokenSegment } from '@gitlab/ui';
import {
GlFilteredSearchToken,
GlFilteredSearchSuggestion,
GlFilteredSearchTokenSegment,
} from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
import {
......@@ -9,13 +13,32 @@ import {
import axios from '~/lib/utils/axios_utils';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import {
DEFAULT_LABELS,
DEFAULT_LABEL_NONE,
DEFAULT_LABEL_ANY,
} from '~/vue_shared/components/filtered_search_bar/constants';
import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue';
import { mockLabelToken } from '../mock_data';
jest.mock('~/flash');
const createComponent = ({ config = mockLabelToken, value = { data: '' }, active = false } = {}) =>
const defaultStubs = {
Portal: true,
GlFilteredSearchSuggestionList: {
template: '<div></div>',
methods: {
getValue: () => '=',
},
},
};
const createComponent = ({
config = mockLabelToken,
value = { data: '' },
active = false,
stubs = defaultStubs,
} = {}) =>
mount(LabelToken, {
propsData: {
config,
......@@ -26,15 +49,7 @@ const createComponent = ({ config = mockLabelToken, value = { data: '' }, active
portalName: 'fake target',
alignSuggestions: function fakeAlignSuggestions() {},
},
stubs: {
Portal: true,
GlFilteredSearchSuggestionList: {
template: '<div></div>',
methods: {
getValue: () => '=',
},
},
},
stubs,
});
describe('LabelToken', () => {
......@@ -43,7 +58,6 @@ describe('LabelToken', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
wrapper = createComponent();
});
afterEach(() => {
......@@ -96,6 +110,10 @@ describe('LabelToken', () => {
});
describe('methods', () => {
beforeEach(() => {
wrapper = createComponent();
});
describe('fetchLabelBySearchTerm', () => {
it('calls `config.fetchLabels` with provided searchTerm param', () => {
jest.spyOn(wrapper.vm.config, 'fetchLabels');
......@@ -138,6 +156,8 @@ describe('LabelToken', () => {
});
describe('template', () => {
const defaultLabels = [DEFAULT_LABEL_NONE, DEFAULT_LABEL_ANY];
beforeEach(async () => {
wrapper = createComponent({ value: { data: `"${mockRegularLabel.title}"` } });
......@@ -164,5 +184,43 @@ describe('LabelToken', () => {
.attributes('style'),
).toBe('background-color: rgb(186, 218, 85); color: rgb(255, 255, 255);');
});
it('renders provided defaultLabels as suggestions', async () => {
wrapper = createComponent({
active: true,
config: { ...mockLabelToken, defaultLabels },
stubs: { Portal: true },
});
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
await wrapper.vm.$nextTick();
const suggestions = wrapper.findAll(GlFilteredSearchSuggestion);
expect(suggestions).toHaveLength(defaultLabels.length);
defaultLabels.forEach((label, index) => {
expect(suggestions.at(index).text()).toBe(label.text);
});
});
it('renders `DEFAULT_LABELS` as default suggestions', async () => {
wrapper = createComponent({
active: true,
config: { ...mockLabelToken },
stubs: { Portal: true },
});
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
await wrapper.vm.$nextTick();
const suggestions = wrapper.findAll(GlFilteredSearchSuggestion);
expect(suggestions).toHaveLength(DEFAULT_LABELS.length);
DEFAULT_LABELS.forEach((label, index) => {
expect(suggestions.at(index).text()).toBe(label.text);
});
});
});
});
import { mount } from '@vue/test-utils';
import { GlFilteredSearchToken, GlFilteredSearchTokenSegment } from '@gitlab/ui';
import {
GlFilteredSearchToken,
GlFilteredSearchSuggestion,
GlFilteredSearchTokenSegment,
} from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
import { DEFAULT_MILESTONES } from '~/vue_shared/components/filtered_search_bar/constants';
import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue';
import {
......@@ -16,10 +21,21 @@ import {
jest.mock('~/flash');
const defaultStubs = {
Portal: true,
GlFilteredSearchSuggestionList: {
template: '<div></div>',
methods: {
getValue: () => '=',
},
},
};
const createComponent = ({
config = mockMilestoneToken,
value = { data: '' },
active = false,
stubs = defaultStubs,
} = {}) =>
mount(MilestoneToken, {
propsData: {
......@@ -31,15 +47,7 @@ const createComponent = ({
portalName: 'fake target',
alignSuggestions: function fakeAlignSuggestions() {},
},
stubs: {
Portal: true,
GlFilteredSearchSuggestionList: {
template: '<div></div>',
methods: {
getValue: () => '=',
},
},
},
stubs,
});
describe('MilestoneToken', () => {
......@@ -126,6 +134,8 @@ describe('MilestoneToken', () => {
});
describe('template', () => {
const defaultMilestones = [{ text: 'foo', value: 'foo' }, { text: 'bar', value: 'baz' }];
beforeEach(async () => {
wrapper = createComponent({ value: { data: `"${mockRegularMilestone.title}"` } });
......@@ -146,5 +156,43 @@ describe('MilestoneToken', () => {
expect(tokenSegments).toHaveLength(3); // Milestone, =, '%"4.0"'
expect(tokenSegments.at(2).text()).toBe(`%"${mockRegularMilestone.title}"`); // "4.0 RC1"
});
it('renders provided defaultMilestones as suggestions', async () => {
wrapper = createComponent({
active: true,
config: { ...mockMilestoneToken, defaultMilestones },
stubs: { Portal: true },
});
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
await wrapper.vm.$nextTick();
const suggestions = wrapper.findAll(GlFilteredSearchSuggestion);
expect(suggestions).toHaveLength(defaultMilestones.length);
defaultMilestones.forEach((milestone, index) => {
expect(suggestions.at(index).text()).toBe(milestone.text);
});
});
it('renders `DEFAULT_MILESTONES` as default suggestions', async () => {
wrapper = createComponent({
active: true,
config: { ...mockMilestoneToken },
stubs: { Portal: true },
});
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
await wrapper.vm.$nextTick();
const suggestions = wrapper.findAll(GlFilteredSearchSuggestion);
expect(suggestions).toHaveLength(DEFAULT_MILESTONES.length);
DEFAULT_MILESTONES.forEach((milestone, index) => {
expect(suggestions.at(index).text()).toBe(milestone.text);
});
});
});
});
......@@ -3,8 +3,10 @@
require 'spec_helper'
RSpec.describe Gitlab::GroupSearchResults do
# group creation calls GroupFinder, so need to create the group
# before so expect(GroupsFinder) check works
let_it_be(:group) { create(:group) }
let(:user) { create(:user) }
let(:group) { create(:group) }
subject(:results) { described_class.new(user, 'gob', anything, group: group) }
......@@ -60,6 +62,19 @@ RSpec.describe Gitlab::GroupSearchResults do
is_expected.to be_empty
end
it 'does not return the user invited to the group' do
user = create(:user, username: 'gob_bluth')
create(:group_member, :invited, :developer, user: user, group: group)
is_expected.to be_empty
end
it 'calls GroupFinder during execution' do
expect(GroupsFinder).to receive(:new).with(user).and_call_original
subject
end
end
describe "#issuable_params" do
......
......@@ -447,6 +447,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
expect(count_data[:clusters_applications_jupyter]).to eq(1)
expect(count_data[:clusters_applications_cilium]).to eq(1)
expect(count_data[:clusters_management_project]).to eq(1)
expect(count_data[:kubernetes_agents]).to eq(1)
expect(count_data[:deployments]).to eq(4)
expect(count_data[:successful_deployments]).to eq(2)
......
......@@ -7,22 +7,24 @@ RSpec.describe 'Creating a Snippet' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let(:content) { 'Initial content' }
let(:description) { 'Initial description' }
let(:title) { 'Initial title' }
let(:file_name) { 'Initial file_name' }
let(:visibility_level) { 'public' }
let(:action) { :create }
let(:file_1) { { filePath: 'example_file1', content: 'This is the example file 1' }}
let(:file_2) { { filePath: 'example_file2', content: 'This is the example file 2' }}
let(:actions) { [{ action: action }.merge(file_1), { action: action }.merge(file_2)] }
let(:project_path) { nil }
let(:uploaded_files) { nil }
let(:mutation_vars) do
{
content: content,
description: description,
visibility_level: visibility_level,
file_name: file_name,
title: title,
project_path: project_path,
uploaded_files: uploaded_files
uploaded_files: uploaded_files,
blob_actions: actions
}
end
......@@ -62,26 +64,47 @@ RSpec.describe 'Creating a Snippet' do
context 'when the user has permission' do
let(:current_user) { user }
context 'with PersonalSnippet' do
it 'creates the Snippet' do
shared_examples 'does not create snippet' do
it 'does not create the Snippet' do
expect do
subject
end.to change { Snippet.count }.by(1)
end.not_to change { Snippet.count }
end
it 'returns the created Snippet' do
it 'does not return Snippet' do
subject
expect(mutation_response['snippet']['blob']['richData']).to be_nil
expect(mutation_response['snippet']['blob']['plainData']).to match(content)
expect(mutation_response['snippet']).to be_nil
end
end
shared_examples 'creates snippet' do
it 'returns the created Snippet' do
expect do
subject
end.to change { Snippet.count }.by(1)
expect(mutation_response['snippet']['title']).to eq(title)
expect(mutation_response['snippet']['description']).to eq(description)
expect(mutation_response['snippet']['fileName']).to eq(file_name)
expect(mutation_response['snippet']['visibilityLevel']).to eq(visibility_level)
expect(mutation_response['snippet']['project']).to be_nil
expect(mutation_response['snippet']['blobs'][0]['plainData']).to match(file_1[:content])
expect(mutation_response['snippet']['blobs'][0]['fileName']).to match(file_1[:file_path])
expect(mutation_response['snippet']['blobs'][1]['plainData']).to match(file_2[:content])
expect(mutation_response['snippet']['blobs'][1]['fileName']).to match(file_2[:file_path])
end
context 'when action is invalid' do
let(:file_1) { { filePath: 'example_file1' }}
it_behaves_like 'a mutation that returns errors in the response', errors: ['Snippet actions have invalid data']
it_behaves_like 'does not create snippet'
end
end
context 'with PersonalSnippet' do
it_behaves_like 'creates snippet'
end
context 'with ProjectSnippet' do
let(:project_path) { project.full_path }
......@@ -89,23 +112,7 @@ RSpec.describe 'Creating a Snippet' do
project.add_developer(current_user)
end
it 'creates the Snippet' do
expect do
subject
end.to change { Snippet.count }.by(1)
end
it 'returns the created Snippet' do
subject
expect(mutation_response['snippet']['blob']['richData']).to be_nil
expect(mutation_response['snippet']['blob']['plainData']).to match(content)
expect(mutation_response['snippet']['title']).to eq(title)
expect(mutation_response['snippet']['description']).to eq(description)
expect(mutation_response['snippet']['fileName']).to eq(file_name)
expect(mutation_response['snippet']['visibilityLevel']).to eq(visibility_level)
expect(mutation_response['snippet']['project']['fullPath']).to eq(project_path)
end
it_behaves_like 'creates snippet'
context 'when the project path is invalid' do
let(:project_path) { 'foobar' }
......@@ -124,61 +131,6 @@ RSpec.describe 'Creating a Snippet' do
end
end
shared_examples 'does not create snippet' do
it 'does not create the Snippet' do
expect do
subject
end.not_to change { Snippet.count }
end
it 'does not return Snippet' do
subject
expect(mutation_response['snippet']).to be_nil
end
end
context 'when snippet is created using the files param' do
let(:action) { :create }
let(:file_1) { { filePath: 'example_file1', content: 'This is the example file 1' }}
let(:file_2) { { filePath: 'example_file2', content: 'This is the example file 2' }}
let(:actions) { [{ action: action }.merge(file_1), { action: action }.merge(file_2)] }
let(:mutation_vars) do
{
description: description,
visibility_level: visibility_level,
project_path: project_path,
title: title,
blob_actions: actions
}
end
it 'creates the Snippet' do
expect do
subject
end.to change { Snippet.count }.by(1)
end
it 'returns the created Snippet' do
subject
expect(mutation_response['snippet']['title']).to eq(title)
expect(mutation_response['snippet']['description']).to eq(description)
expect(mutation_response['snippet']['visibilityLevel']).to eq(visibility_level)
expect(mutation_response['snippet']['blobs'][0]['plainData']).to match(file_1[:content])
expect(mutation_response['snippet']['blobs'][0]['fileName']).to match(file_1[:file_path])
expect(mutation_response['snippet']['blobs'][1]['plainData']).to match(file_2[:content])
expect(mutation_response['snippet']['blobs'][1]['fileName']).to match(file_2[:file_path])
end
context 'when action is invalid' do
let(:file_1) { { filePath: 'example_file1' }}
it_behaves_like 'a mutation that returns errors in the response', errors: ['Snippet actions have invalid data']
it_behaves_like 'does not create snippet'
end
end
context 'when there are ActiveRecord validation errors' do
let(:title) { '' }
......@@ -187,7 +139,7 @@ RSpec.describe 'Creating a Snippet' do
end
context 'when there non ActiveRecord errors' do
let(:file_name) { 'invalid://file/path' }
let(:file_1) { { filePath: 'invalid://file/path', content: 'foobar' }}
it_behaves_like 'a mutation that returns errors in the response', errors: ['Repository Error creating the snippet - Invalid file name']
it_behaves_like 'does not create snippet'
......
......@@ -12,18 +12,20 @@ RSpec.describe 'Updating a Snippet' do
let(:updated_content) { 'Updated content' }
let(:updated_description) { 'Updated description' }
let(:updated_title) { 'Updated_title' }
let(:updated_file_name) { 'Updated file_name' }
let(:current_user) { snippet.author }
let(:updated_file) { 'CHANGELOG' }
let(:deleted_file) { 'README' }
let(:snippet_gid) { GitlabSchema.id_from_object(snippet).to_s }
let(:mutation_vars) do
{
id: snippet_gid,
content: updated_content,
description: updated_description,
visibility_level: 'public',
file_name: updated_file_name,
title: updated_title
title: updated_title,
blob_actions: [
{ action: :update, filePath: updated_file, content: updated_content },
{ action: :delete, filePath: deleted_file }
]
}
end
......@@ -50,21 +52,32 @@ RSpec.describe 'Updating a Snippet' do
end
context 'when the user has permission' do
it 'updates the Snippet' do
it 'updates the snippet record' do
post_graphql_mutation(mutation, current_user: current_user)
expect(snippet.reload.title).to eq(updated_title)
end
it 'returns the updated Snippet' do
it 'updates the Snippet' do
blob_to_update = blob_at(updated_file)
blob_to_delete = blob_at(deleted_file)
expect(blob_to_update.data).not_to eq updated_content
expect(blob_to_delete).to be_present
post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response['snippet']['blob']['richData']).to be_nil
expect(mutation_response['snippet']['blob']['plainData']).to match(updated_content)
expect(mutation_response['snippet']['title']).to eq(updated_title)
expect(mutation_response['snippet']['description']).to eq(updated_description)
expect(mutation_response['snippet']['fileName']).to eq(updated_file_name)
expect(mutation_response['snippet']['visibilityLevel']).to eq('public')
blob_to_update = blob_at(updated_file)
blob_to_delete = blob_at(deleted_file)
aggregate_failures do
expect(blob_to_update.data).to eq updated_content
expect(blob_to_delete).to be_nil
expect(blob_in_mutation_response(updated_file)['plainData']).to match(updated_content)
expect(mutation_response['snippet']['title']).to eq(updated_title)
expect(mutation_response['snippet']['description']).to eq(updated_description)
expect(mutation_response['snippet']['visibilityLevel']).to eq('public')
end
end
context 'when there are ActiveRecord validation errors' do
......@@ -79,16 +92,29 @@ RSpec.describe 'Updating a Snippet' do
end
it 'returns the Snippet with its original values' do
blob_to_update = blob_at(updated_file)
blob_to_delete = blob_at(deleted_file)
post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response['snippet']['blob']['richData']).to be_nil
expect(mutation_response['snippet']['blob']['plainData']).to match(original_content)
expect(mutation_response['snippet']['title']).to eq(original_title)
expect(mutation_response['snippet']['description']).to eq(original_description)
expect(mutation_response['snippet']['fileName']).to eq(original_file_name)
expect(mutation_response['snippet']['visibilityLevel']).to eq('private')
aggregate_failures do
expect(blob_at(updated_file).data).to eq blob_to_update.data
expect(blob_at(deleted_file).data).to eq blob_to_delete.data
expect(blob_in_mutation_response(deleted_file)['plainData']).not_to be_nil
expect(mutation_response['snippet']['title']).to eq(original_title)
expect(mutation_response['snippet']['description']).to eq(original_description)
expect(mutation_response['snippet']['visibilityLevel']).to eq('private')
end
end
end
def blob_in_mutation_response(filename)
mutation_response['snippet']['blobs'].select { |blob| blob['name'] == filename }[0]
end
def blob_at(filename)
snippet.repository.blob_at('HEAD', filename)
end
end
end
......@@ -96,6 +122,7 @@ RSpec.describe 'Updating a Snippet' do
let(:snippet) do
create(:personal_snippet,
:private,
:repository,
file_name: original_file_name,
title: original_title,
content: original_content,
......@@ -111,6 +138,7 @@ RSpec.describe 'Updating a Snippet' do
let(:snippet) do
create(:project_snippet,
:private,
:repository,
project: project,
author: create(:user),
file_name: original_file_name,
......@@ -149,40 +177,4 @@ RSpec.describe 'Updating a Snippet' do
it_behaves_like 'when the snippet is not found'
end
context 'when using the files params' do
let!(:snippet) { create(:personal_snippet, :private, :repository) }
let(:updated_content) { 'updated_content' }
let(:updated_file) { 'CHANGELOG' }
let(:deleted_file) { 'README' }
let(:mutation_vars) do
{
id: snippet_gid,
blob_actions: [
{ action: :update, filePath: updated_file, content: updated_content },
{ action: :delete, filePath: deleted_file }
]
}
end
it 'updates the Snippet' do
blob_to_update = blob_at(updated_file)
expect(blob_to_update.data).not_to eq updated_content
blob_to_delete = blob_at(deleted_file)
expect(blob_to_delete).to be_present
post_graphql_mutation(mutation, current_user: current_user)
blob_to_update = blob_at(updated_file)
expect(blob_to_update.data).to eq updated_content
blob_to_delete = blob_at(deleted_file)
expect(blob_to_delete).to be_nil
end
def blob_at(filename)
snippet.repository.blob_at('HEAD', filename)
end
end
end
......@@ -22,7 +22,7 @@ RSpec.describe Ci::RetryBuildService do
described_class.new(project, user)
end
clone_accessors = described_class::CLONE_ACCESSORS
clone_accessors = described_class.clone_accessors
reject_accessors =
%i[id status user token token_encrypted coverage trace runner
......@@ -143,6 +143,8 @@ RSpec.describe Ci::RetryBuildService do
Ci::Build.reflect_on_all_associations.map(&:name) +
[:tag_list, :needs_attributes]
current_accessors << :secrets if Gitlab.ee?
current_accessors.uniq!
expect(current_accessors).to include(*processed_accessors)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册