Commit df9f198f authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 152741c4 f82da155
# frozen_string_literal: true
module Types
# rubocop: disable Graphql/AuthorizeTypes
class QueryComplexityType < ::Types::BaseObject
ANALYZER = GraphQL::Analysis::QueryComplexity.new { |_query, complexity| complexity }
graphql_name 'QueryComplexity'
alias_method :query, :object
field :limit, GraphQL::INT_TYPE,
null: true,
method: :max_complexity,
see: {
'GitLab documentation on this limit' =>
'https://docs.gitlab.com/ee/api/graphql/index.html#max-query-complexity'
},
description: 'GraphQL query complexity limit.'
field :score, GraphQL::INT_TYPE,
null: true,
description: 'GraphQL query complexity score.'
def score
::GraphQL::Analysis.analyze_query(query, [ANALYZER]).first
end
end
# rubocop: enable Graphql/AuthorizeTypes
end
......@@ -36,6 +36,10 @@ module Types
resolver: Resolvers::MetadataResolver,
description: 'Metadata about GitLab.'
field :query_complexity, Types::QueryComplexityType,
null: true,
description: 'Information about the complexity of the GraphQL query.'
field :snippets,
Types::SnippetType.connection_type,
null: true,
......@@ -170,6 +174,10 @@ module Types
def application_settings
Gitlab::CurrentSettings.current_application_settings
end
def query_complexity
context.query
end
end
end
......
......@@ -155,7 +155,7 @@ module Integrations
end
def web_url(path = nil, **params)
return unless url.present?
return '' unless url.present?
if Gitlab.com?
params.merge!(ATLASSIAN_REFERRER_GITLAB_COM) unless Gitlab.staging?
......
......@@ -249,8 +249,6 @@ class Wiki
override :default_branch
def default_branch
return 'master' if Feature.disabled?(:wiki_uses_default_branch, user, default_enabled: :yaml)
super || wiki.class.default_ref(container)
end
......
---
name: wiki_uses_default_branch
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64891
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/334754
milestone: '14.1'
type: development
group: group::editor
default_enabled: false
......@@ -298,6 +298,24 @@ query IssueTypes {
More about introspection:
[GraphQL documentation](https://graphql.org/learn/introspection/)
### Query complexity
The calculated [complexity score and limit](index.md#max-query-complexity) for a query can be revealed to clients by
querying for `queryComplexity`.
```graphql
query {
queryComplexity {
score
limit
}
project(fullPath: "gitlab-org/graphql-sandbox") {
name
}
}
```
## Sorting
Some of the GitLab GraphQL endpoints allow you to specify how to sort a
......
......@@ -166,7 +166,7 @@ The complexity of a single query is limited to a maximum of:
- `200` for unauthenticated requests.
- `250` for authenticated requests.
There is no way to discover the complexity of a query except by exceeding the limit.
The complexity score of a query and limit for the request [can be queried for](getting_started.md#query-complexity).
If a query exceeds the complexity limit an error message response will
be returned.
......
......@@ -286,6 +286,12 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="queryprojectssort"></a>`sort` | [`String`](#string) | Sort order of results. |
| <a id="queryprojectstopics"></a>`topics` | [`[String!]`](#string) | Filters projects by topics. |
### `Query.queryComplexity`
Information about the complexity of the GraphQL query.
Returns [`QueryComplexity`](#querycomplexity).
### `Query.runner`
Find a runner. Available only when feature flag `runner_graphql_query` is enabled.
......@@ -12335,6 +12341,15 @@ Pypi metadata.
| <a id="pypimetadataid"></a>`id` | [`PackagesPypiMetadatumID!`](#packagespypimetadatumid) | ID of the metadatum. |
| <a id="pypimetadatarequiredpython"></a>`requiredPython` | [`String`](#string) | Required Python version of the Pypi package. |
### `QueryComplexity`
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="querycomplexitylimit"></a>`limit` | [`Int`](#int) | GraphQL query complexity limit. See [GitLab documentation on this limit](https://docs.gitlab.com/ee/api/graphql/index.html#max-query-complexity). |
| <a id="querycomplexityscore"></a>`score` | [`Int`](#int) | GraphQL query complexity score. |
### `RecentFailures`
Recent failure history of a test case.
......
......@@ -68,9 +68,7 @@ Complexity is explained [on our client-facing API page](../api/graphql/index.md#
Fields default to adding `1` to a query's complexity score, but developers can
[specify a custom complexity](#field-complexity) when defining a field.
To estimate the complexity of a query, you can run the
[`gitlab:graphql:analyze`](rake_tasks.md#analyze-graphql-queries)
Rake task.
The complexity score of a query [can itself be queried for](../api/graphql/getting_started.md#query-complexity).
### Request timeout
......
......@@ -101,7 +101,7 @@ export default {
params.search = search;
}
return Api.groupLabels(this.groupFullPath, {
return Api.groupLabels(encodeURIComponent(this.groupFullPath), {
params,
});
},
......
......@@ -5,6 +5,9 @@ require 'spec_helper'
RSpec.describe 'epics list', :js do
let(:group) { create(:group, :public) }
let(:user) { create(:user) }
let(:user_dev) { create(:user) }
let!(:bug_label) { create(:group_label, group: group, title: 'Bug') }
let!(:critical_label) { create(:group_label, group: group, title: 'Critical') }
before do
stub_licensed_features(epics: true)
......@@ -215,43 +218,68 @@ RSpec.describe 'epics list', :js do
end
context 'vue epics list' do
let!(:epic1) { create(:epic, group: group, start_date: '2020-12-15', end_date: '2021-1-15') }
let!(:epic2) { create(:epic, group: group, start_date: '2020-12-15') }
let!(:epic3) { create(:epic, group: group, end_date: '2021-1-15') }
available_tokens = %w[Author Label My-Reaction]
before do
stub_feature_flags(vue_epics_list: true)
group.add_developer(user)
end
visit group_epics_path(group)
describe 'within a group' do
let!(:epic1) { create(:epic, group: group, start_date: '2020-12-15', end_date: '2021-1-15') }
let!(:epic2) { create(:epic, group: group, start_date: '2020-12-15') }
let!(:epic3) { create(:epic, group: group, end_date: '2021-1-15') }
let!(:award_emoji_star) { create(:award_emoji, name: 'star', user: user, awardable: epic1) }
before do
group.add_developer(user)
group.add_developer(user_dev)
visit group_epics_path(group)
wait_for_requests
end
wait_for_requests
end
it 'renders epics list', :aggregate_failures do
page.within('.issuable-list-container') do
expect(page).to have_selector('.gl-tabs')
expect(page).to have_link('New epic')
expect(page).to have_selector('.vue-filtered-search-bar-container')
expect(page.find('.issuable-list')).to have_selector('li.issue', count: 3)
end
end
it 'renders epics list' do
page.within('.issuable-list-container') do
expect(page).to have_selector('.gl-tabs')
expect(page).to have_link('New epic')
expect(page).to have_selector('.vue-filtered-search-bar-container')
expect(page.find('.issuable-list')).to have_selector('li.issue', count: 3)
it 'renders epics item with metadata', :aggregate_failures do
page.within('.issuable-list-container .issuable-list') do
expect(page.all('.issuable-info-container')[0].find('.issue-title')).to have_content(epic2.title)
expect(page.all('.issuable-info-container')[0].find('.issuable-reference')).to have_content("&#{epic2.iid}")
expect(page.all('.issuable-info-container')[0].find('.issuable-authored')).to have_content('created')
expect(page.all('.issuable-info-container')[0].find('.issuable-authored')).to have_content("by #{epic2.author.name}")
end
end
end
it 'renders epics item with metadata' do
page.within('.issuable-list-container .issuable-list') do
expect(page.all('.issuable-info-container')[0].find('.issue-title')).to have_content(epic2.title)
expect(page.all('.issuable-info-container')[0].find('.issuable-reference')).to have_content("&#{epic2.iid}")
expect(page.all('.issuable-info-container')[0].find('.issuable-authored')).to have_content('created')
expect(page.all('.issuable-info-container')[0].find('.issuable-authored')).to have_content("by #{epic2.author.name}")
it 'renders epic item timeframe', :aggregate_failures do
page.within('.issuable-list-container .issuable-list') do
expect(page.all('.issuable-info-container')[0].find('.issuable-info')).to have_content('Dec 15, 2020 – No due date')
expect(page.all('.issuable-info-container')[1].find('.issuable-info')).to have_content('Dec 15, 2020 – Jan 15, 2021')
expect(page.all('.issuable-info-container')[2].find('.issuable-info')).to have_content('No start date – Jan 15, 2021')
end
end
it_behaves_like 'filtered search bar', available_tokens
end
it 'renders epic item timeframe' do
page.within('.issuable-list-container .issuable-list') do
expect(page.all('.issuable-info-container')[0].find('.issuable-info')).to have_content('Dec 15, 2020 – No due date')
expect(page.all('.issuable-info-container')[1].find('.issuable-info')).to have_content('Dec 15, 2020 – Jan 15, 2021')
expect(page.all('.issuable-info-container')[2].find('.issuable-info')).to have_content('No start date – Jan 15, 2021')
describe 'within a sub-group group' do
let!(:subgroup) { create(:group, parent: group, name: 'subgroup') }
let!(:sub_epic1) { create(:epic, group: subgroup, start_date: '2020-12-15', end_date: '2021-1-15') }
let!(:sub_epic2) { create(:epic, group: subgroup, start_date: '2020-12-15') }
let!(:award_emoji_star) { create(:award_emoji, name: 'star', user: user, awardable: sub_epic1) }
before do
subgroup.add_developer(user)
subgroup.add_developer(user_dev)
visit group_epics_path(subgroup)
wait_for_requests
end
it_behaves_like 'filtered search bar', available_tokens
end
end
end
......@@ -7,14 +7,16 @@ RSpec.describe 'group epic roadmap', :js do
include MobileHelpers
let(:user) { create(:user) }
let(:user_dev) { create(:user) }
let(:group) { create(:group) }
let(:milestone) { create(:milestone, group: group) }
let(:filtered_search) { find('.filtered-search') }
let(:js_dropdown_label) { '#js-dropdown-label' }
let(:filter_dropdown) { find("#{js_dropdown_label} .filter-dropdown") }
let(:state_dropdown) { find('.dropdown-epics-state') }
let(:bug_label) { create(:group_label, group: group, title: 'Bug') }
let(:critical_label) { create(:group_label, group: group, title: 'Critical') }
let!(:bug_label) { create(:group_label, group: group, title: 'Bug') }
let!(:critical_label) { create(:group_label, group: group, title: 'Critical') }
def search_for_label(label)
init_label_search
......@@ -190,4 +192,41 @@ RSpec.describe 'group epic roadmap', :js do
end
end
end
context 'async filtered search' do
available_tokens = %w[Author Label Milestone Epic My-Reaction]
before do
stub_feature_flags(async_filtering: true)
end
describe 'within a group' do
let!(:epic1) { create(:epic, group: group, end_date: 10.days.ago) }
let!(:epic2) { create(:epic, group: group, start_date: 2.days.ago) }
let!(:award_emoji_star) { create(:award_emoji, name: 'star', user: user, awardable: epic1) }
before do
group.add_developer(user_dev)
visit group_roadmap_path(group)
wait_for_requests
end
it_behaves_like 'filtered search bar', available_tokens
end
describe 'within a sub-group group' do
let!(:subgroup) { create(:group, parent: group, name: 'subgroup') }
let!(:sub_epic1) { create(:epic, group: subgroup, end_date: 10.days.ago) }
let!(:sub_epic2) { create(:epic, group: subgroup, start_date: 2.days.ago) }
let!(:award_emoji_star) { create(:award_emoji, name: 'star', user: user, awardable: sub_epic1) }
before do
subgroup.add_developer(user_dev)
visit group_roadmap_path(subgroup)
wait_for_requests
end
it_behaves_like 'filtered search bar', available_tokens
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.shared_examples 'filtered search bar' do |tokens|
minimum_values_for_token = {
# Count must be at least 3 as `Any` & current user are available by default
"Author" => 3,
# Count must be at least 3 as `None` & `Any` are available by default
"Label" => 3,
# Count must be at least 5 as `None`, `Any`, `Upcoming` & `Started` are available by default
"Milestone" => 5,
# Count must be at least 1
"Epic" => 1,
# Count must be at least 3 as `None` & `Any` are available by default
"My-Reaction" => 3
}
def select_token(token_name)
page.find('input.gl-filtered-search-term-input').click
click_link token_name
page.first('.gl-filtered-search-suggestion').click
end
tokens.each do |token|
it "renders values for token '#{token}' correctly" do
page.within('.vue-filtered-search-bar-container .gl-search-box-by-click') do
select_token(token)
wait_for_requests
expect(page.find('.gl-filtered-search-suggestion-list')).to have_selector('li.gl-filtered-search-suggestion', minimum: minimum_values_for_token[token])
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['QueryComplexity'] do
include GraphqlHelpers
specify do
expect(described_class).to have_graphql_fields(:limit, :score).only
end
it 'works when executed' do
query = <<-GQL
query {
queryComplexity {
score
limit
}
currentUser {
name
}
}
GQL
query_result = run_with_clean_state(query).to_h
data = graphql_dig_at(query_result, :data, :queryComplexity)
expect(data).to include(
'score' => be > 0,
'limit' => GitlabSchema::DEFAULT_MAX_COMPLEXITY
)
end
end
......@@ -1024,6 +1024,12 @@ RSpec.describe Integrations::Jira do
expect(integration.web_url('subpath', bar: 'baz baz')).to eq('http://jira.test.com/path/subpath?bar=baz%20baz&foo=bar%20bar&nosso')
end
it 'returns an empty string if URL is not set' do
integration.url = nil
expect(integration.web_url).to eq('')
end
it 'includes Atlassian referrer for gitlab.com' do
allow(Gitlab).to receive(:com?).and_return(true)
......@@ -1041,16 +1047,40 @@ RSpec.describe Integrations::Jira do
end
end
describe '#project_url' do
it 'returns the correct URL' do
expect(integration.project_url).to eq('http://jira.test.com/path')
end
it 'returns an empty string if URL is not set' do
integration.url = nil
expect(integration.project_url).to eq('')
end
end
describe '#issues_url' do
it 'returns the correct URL' do
expect(integration.issues_url).to eq('http://jira.test.com/path/browse/:id')
end
it 'returns an empty string if URL is not set' do
integration.url = nil
expect(integration.issues_url).to eq('')
end
end
describe '#new_issue_url' do
it 'returns the correct URL' do
expect(integration.new_issue_url).to eq('http://jira.test.com/path/secure/CreateIssue!default.jspa')
end
it 'returns an empty string if URL is not set' do
integration.url = nil
expect(integration.new_issue_url).to eq('')
end
end
end
......
......@@ -545,22 +545,12 @@ RSpec.shared_examples 'wiki model' do
allow(Gitlab::DefaultBranch).to receive(:value).and_return('main')
end
shared_examples 'feature flag wiki_uses_default_branch is disabled' do
it 'returns "master"' do
stub_feature_flags(wiki_uses_default_branch: false)
expect(subject).to eq 'master'
end
end
context 'when repository is not created' do
let(:wiki_container) { wiki_container_without_repo }
it 'returns the instance default branch' do
expect(subject).to eq 'main'
end
it_behaves_like 'feature flag wiki_uses_default_branch is disabled'
end
context 'when repository is empty' do
......@@ -573,8 +563,6 @@ RSpec.shared_examples 'wiki model' do
it 'returns the instance default branch' do
expect(subject).to eq 'main'
end
it_behaves_like 'feature flag wiki_uses_default_branch is disabled'
end
context 'when repository is not empty' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment