Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
aca82d5ba4
commit
cfc6fe5100
|
|
@ -11,6 +11,7 @@ module Clusters
|
|||
}.freeze
|
||||
|
||||
self.reactive_cache_key = ->(finder) { finder.model_name }
|
||||
self.reactive_cache_work_type = :external_dependency
|
||||
self.reactive_cache_worker_finder = ->(_id, *cache_args) { from_cache(*cache_args) }
|
||||
|
||||
attr_reader :cluster, :environment
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ module Projects
|
|||
attr_reader :project
|
||||
|
||||
self.reactive_cache_key = ->(finder) { finder.cache_key }
|
||||
self.reactive_cache_work_type = :external_dependency
|
||||
self.reactive_cache_worker_finder = ->(_id, *args) { from_cache(*args) }
|
||||
|
||||
MAX_CLUSTERS = 10
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ module Clusters
|
|||
KUBE_INGRESS_BASE_DOMAIN = 'KUBE_INGRESS_BASE_DOMAIN'
|
||||
APPLICATIONS_ASSOCIATIONS = APPLICATIONS.values.map(&:association_name).freeze
|
||||
|
||||
self.reactive_cache_work_type = :external_dependency
|
||||
|
||||
belongs_to :user
|
||||
belongs_to :management_project, class_name: '::Project', optional: true
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ module PrometheusAdapter
|
|||
self.reactive_cache_lease_timeout = 30.seconds
|
||||
self.reactive_cache_refresh_interval = 30.seconds
|
||||
self.reactive_cache_lifetime = 1.minute
|
||||
self.reactive_cache_work_type = :external_dependency
|
||||
|
||||
def prometheus_client
|
||||
raise NotImplementedError
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ class Environment < ApplicationRecord
|
|||
self.reactive_cache_refresh_interval = 1.minute
|
||||
self.reactive_cache_lifetime = 55.seconds
|
||||
self.reactive_cache_hard_limit = 10.megabytes
|
||||
self.reactive_cache_work_type = :external_dependency
|
||||
|
||||
belongs_to :project, required: true
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ module ErrorTracking
|
|||
}x.freeze
|
||||
|
||||
self.reactive_cache_key = ->(setting) { [setting.class.model_name.singular, setting.project_id] }
|
||||
self.reactive_cache_work_type = :external_dependency
|
||||
|
||||
belongs_to :project
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ class SshHostKey
|
|||
# This is achieved by making the lifetime shorter than the refresh interval.
|
||||
self.reactive_cache_refresh_interval = 15.minutes
|
||||
self.reactive_cache_lifetime = 10.minutes
|
||||
self.reactive_cache_work_type = :external_dependency
|
||||
|
||||
def self.find_by(opts = {})
|
||||
opts = HashWithIndifferentAccess.new(opts)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ module Grafana
|
|||
self.reactive_cache_key = ->(service) { service.cache_key }
|
||||
self.reactive_cache_lease_timeout = 30.seconds
|
||||
self.reactive_cache_refresh_interval = 30.seconds
|
||||
self.reactive_cache_work_type = :external_dependency
|
||||
self.reactive_cache_worker_finder = ->(_id, *args) { from_cache(*args) }
|
||||
|
||||
attr_accessor :project, :datasource_id, :proxy_path, :query_params
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ module Metrics
|
|||
self.reactive_cache_lease_timeout = 30.seconds
|
||||
self.reactive_cache_refresh_interval = 30.minutes
|
||||
self.reactive_cache_lifetime = 30.minutes
|
||||
self.reactive_cache_work_type = :external_dependency
|
||||
self.reactive_cache_worker_finder = ->(_id, *args) { from_cache(*args) }
|
||||
|
||||
class << self
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ module PodLogs
|
|||
:pod_logs,
|
||||
:filter_return_keys
|
||||
|
||||
self.reactive_cache_work_type = :external_dependency
|
||||
self.reactive_cache_worker_finder = ->(id, _cache_key, namespace, params) { new(::Clusters::Cluster.find(id), namespace, params: params) }
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ module PodLogs
|
|||
:split_logs,
|
||||
:filter_return_keys
|
||||
|
||||
self.reactive_cache_work_type = :external_dependency
|
||||
self.reactive_cache_worker_finder = ->(id, _cache_key, namespace, params) { new(::Clusters::Cluster.find(id), namespace, params: params) }
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ module Prometheus
|
|||
# is expected to change *and* be fetched again by the frontend
|
||||
self.reactive_cache_refresh_interval = 90.seconds
|
||||
self.reactive_cache_lifetime = 1.minute
|
||||
self.reactive_cache_work_type = :external_dependency
|
||||
self.reactive_cache_worker_finder = ->(_id, *args) { from_cache(*args) }
|
||||
|
||||
attr_accessor :proxyable, :method, :path, :params
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Delete orphaned rows in application_settings table
|
||||
merge_request: 29981
|
||||
author:
|
||||
type: performance
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoveAdditionalApplicationSettingsRows < ActiveRecord::Migration[6.0]
|
||||
class ApplicationSetting < ActiveRecord::Base
|
||||
self.table_name = 'application_settings'
|
||||
end
|
||||
|
||||
def up
|
||||
return if ApplicationSetting.count == 1
|
||||
|
||||
execute "DELETE from application_settings WHERE id NOT IN (SELECT MAX(id) FROM application_settings);"
|
||||
end
|
||||
|
||||
def down
|
||||
# no changes
|
||||
end
|
||||
end
|
||||
|
|
@ -13421,6 +13421,7 @@ COPY "schema_migrations" (version) FROM STDIN;
|
|||
20200416120128
|
||||
20200416120354
|
||||
20200417044453
|
||||
20200420162730
|
||||
20200420172113
|
||||
20200420172752
|
||||
20200420172927
|
||||
|
|
|
|||
|
|
@ -223,6 +223,9 @@ This action does not delete blobs. In order to delete them and recycle disk spac
|
|||
|
||||
Delete registry repository tags in bulk based on given criteria.
|
||||
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
||||
For an overview, see [Utilize the Container Registry API to delete all tags except *](https://youtu.be/Hi19bKe_xsg).
|
||||
|
||||
```plaintext
|
||||
DELETE /projects/:id/registry/repositories/:repository_id/tags
|
||||
```
|
||||
|
|
|
|||
|
|
@ -126,11 +126,11 @@ To start working locally on an existing remote repository, clone it with the com
|
|||
files to your local computer, automatically preserving the Git connection with the
|
||||
remote repository.
|
||||
|
||||
You can either clone it via HTTPS or [SSH](../ssh/README.md). If you chose to clone
|
||||
it via HTTPS, you'll have to enter your credentials every time you pull and push.
|
||||
You can either clone it via [HTTPS](#clone-via-https) or [SSH](#clone-via-ssh). If you chose to
|
||||
clone it via HTTPS, you'll have to enter your credentials every time you pull and push.
|
||||
You can read more about credential storage in the
|
||||
[Git Credentials documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage).
|
||||
With SSH, you enter your credentials only once.
|
||||
With [SSH](../ssh/README.md), you enter your credentials only once.
|
||||
|
||||
You can find both paths (HTTPS and SSH) by navigating to your project's landing page
|
||||
and clicking **Clone**. GitLab will prompt you with both paths, from which you can copy
|
||||
|
|
@ -142,24 +142,38 @@ As an example, consider this repository path:
|
|||
- SSH: `git@gitlab.com:gitlab-org/gitlab.git`
|
||||
|
||||
To get started, open a terminal window in the directory you wish to clone the
|
||||
repository files into, and run one of the following commands.
|
||||
repository files into, and run one of the `git clone` commands as described below.
|
||||
|
||||
Clone via HTTPS:
|
||||
Both commands will download a copy of the files in a folder named after the project's
|
||||
name. You can then navigate to the new directory and start working on it locally.
|
||||
|
||||
#### Clone via HTTPS
|
||||
|
||||
To clone `https://gitlab.com/gitlab-org/gitlab.git` via HTTPS:
|
||||
|
||||
```shell
|
||||
git clone https://gitlab.com/gitlab-org/gitlab.git
|
||||
```
|
||||
|
||||
Clone via SSH:
|
||||
You'll have to add your password every time you clone through HTTPS. If you have 2FA enabled
|
||||
for your account, you'll have to use a [Personal Access Token](../user/profile/personal_access_tokens.md)
|
||||
with **read_repository** or **write_repository** permissions instead of your account's password.
|
||||
|
||||
If you don't have 2FA enabled, use your account's password.
|
||||
|
||||
TIP: **Troubleshooting:**
|
||||
On Windows, if you entered incorrect passwords multiple times and GitLab is responding `Access denied`,
|
||||
you may have to add your namespace (user name or group name) to clone through HTTPS:
|
||||
`git clone https://namespace@gitlab.com/gitlab-org/gitlab.git`.
|
||||
|
||||
#### Clone via SSH
|
||||
|
||||
To clone `git@gitlab.com:gitlab-org/gitlab.git` via SSH:
|
||||
|
||||
```shell
|
||||
git clone git@gitlab.com:gitlab-org/gitlab.git
|
||||
```
|
||||
|
||||
Both commands will download a copy of the files in a folder named after the project's
|
||||
name. You can then navigate to the directory and start working
|
||||
on it locally.
|
||||
|
||||
### Switch to the master branch
|
||||
|
||||
You are always in a branch when working with Git. The main branch is the master
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ When working within HAML (or Vue templates) we can add `data-track-*` attributes
|
|||
Below is an example of `data-track-*` attributes assigned to a button:
|
||||
|
||||
```haml
|
||||
%button.btn{ data: { track: { event: "click_button", label: "template_preview", property: "my-template" } } }
|
||||
%button.btn{ data: { track_event: "click_button", track_label: "template_preview", track_property: "my-template" } }
|
||||
```
|
||||
|
||||
```html
|
||||
|
|
|
|||
|
|
@ -189,6 +189,9 @@ Once you've set up your identity provider to work with GitLab, you'll need to co
|
|||
|
||||

|
||||
|
||||
NOTE: **Note:**
|
||||
Please note that the certificate [fingerprint algorithm](#additional-setup-options) must be in SHA1. When configuring the identity provider, use a secure [signature algorithm](#additional-setup-options).
|
||||
|
||||
## User access and management
|
||||
|
||||
Once Group SSO is configured and enabled, users can access the GitLab.com group through the identity provider's dashboard. If [SCIM](scim_setup.md) is configured, please see the [user access and linking setup section on the SCIM page](scim_setup.md#user-access-and-linking-setup).
|
||||
|
|
|
|||
|
|
@ -12,25 +12,28 @@ that group is synchronized between GitLab and the identity provider.
|
|||
|
||||
GitLab's [SCIM API](../../../api/scim.md) implements part of [the RFC7644 protocol](https://tools.ietf.org/html/rfc7644).
|
||||
|
||||
## Features
|
||||
|
||||
Currently, the following actions are available:
|
||||
|
||||
- CREATE
|
||||
- UPDATE
|
||||
- DELETE (deprovisioning)
|
||||
- Create users
|
||||
- Update users (Azure only)
|
||||
- Deactivate users
|
||||
|
||||
The following identity providers are supported:
|
||||
|
||||
- Azure
|
||||
- Okta
|
||||
|
||||
## Requirements
|
||||
|
||||
- [Group SSO](index.md) must be configured.
|
||||
- [Group Single Sign-On](index.md) must be configured.
|
||||
|
||||
## GitLab configuration
|
||||
|
||||
Once [Single sign-on](index.md) has been configured, we can:
|
||||
Once [Group Single Sign-On](index.md) has been configured, we can:
|
||||
|
||||
1. Navigate to the group and click **Settings > SAML SSO**.
|
||||
1. Navigate to the group and click **Administration > SAML SSO**.
|
||||
1. Click on the **Generate a SCIM token** button.
|
||||
1. Save the token and URL so they can be used in the next step.
|
||||
|
||||
|
|
@ -38,9 +41,12 @@ Once [Single sign-on](index.md) has been configured, we can:
|
|||
|
||||
## Identity Provider configuration
|
||||
|
||||
### Azure
|
||||
- [Azure](#azure-configuration-steps)
|
||||
- [Okta](#okta-configuration-steps)
|
||||
|
||||
The SAML application that was created during [Single sign-on](index.md) setup now needs to be set up for SCIM.
|
||||
### Azure configuration steps
|
||||
|
||||
The SAML application that was created during [Single sign-on](index.md) setup for [Azure](https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/configure-single-sign-on-non-gallery-applications) now needs to be set up for SCIM.
|
||||
|
||||
1. Check the configuration for your GitLab SAML app and ensure that **Name identifier value** (NameID) points to `user.objectid` or another unique identifier. This will match the `extern_uid` used on GitLab.
|
||||
|
||||
|
|
@ -109,6 +115,38 @@ bottom of the **Provisioning** screen, together with a link to the audit logs.
|
|||
CAUTION: **Warning:**
|
||||
Once synchronized, changing the field mapped to `id` and `externalId` will likely cause provisioning errors, duplicate users, and prevent existing users from accessing the GitLab group.
|
||||
|
||||
### Okta configuration steps
|
||||
|
||||
The SAML application that was created during [Single sign-on](index.md) setup for [Okta](The SAML application that was created during [Single sign-on](index.md) setup for [Okta](https://developer.okta.com/docs/guides/saml-application-setup/overview/) now needs to be set up for SCIM.
|
||||
|
||||
1. Sign in to Okta.
|
||||
1. If you see an **Admin** button in the top right, click the button. This will
|
||||
ensure you are in the Admin area.
|
||||
|
||||
TIP: **Tip:** If you're using the Developer Console, click **Developer Console** in the top
|
||||
bar and select **Classic UI**. Otherwise, you may not see the buttons described
|
||||
in the following steps:
|
||||
|
||||
1. In the **Application** tab, click **Add Application**.
|
||||
1. Search for **GitLab**, find and click on the 'GitLab' application.
|
||||
1. On the GitLab application overview page, click **Add**.
|
||||
1. Under **Application Visibility** select both check boxes. Currently the GitLab application does not support SAML authentication so the icon should not be shown to users.
|
||||
1. Click **Done** to finish adding the application.
|
||||
1. In the **Provisioning** tab, click **Configure API integration**.
|
||||
1. Select **Enable API integration**.
|
||||
- For **Base URL** enter the URL obtained from the GitLab SCIM configuration page
|
||||
- For **API Token** enter the SCIM token obtained from the GitLab SCIM configuration page
|
||||
1. Click 'Test API Credentials' to verify configuration.
|
||||
1. Click **Save** to apply the settings.
|
||||
1. Assign users in the **Assignments** tab. Assigned users will be created and
|
||||
managed in your GitLab group.
|
||||
|
||||
#### Okta Known Issues
|
||||
|
||||
The Okta GitLab application currently only supports SCIM. Continue
|
||||
using the separate Okta [SAML SSO](index.md) configuration along with the new SCIM
|
||||
application described above.
|
||||
|
||||
## User access and linking setup
|
||||
|
||||
As long as [Group SAML](index.md) has been configured, prior to turning on sync, existing GitLab.com users can link to their accounts in one of the following ways, before synchronization is active:
|
||||
|
|
@ -135,7 +173,9 @@ Upon the next sync, the user will be deprovisioned, which means that the user wi
|
|||
|
||||
This section contains possible solutions for problems you might encounter.
|
||||
|
||||
### How do I verify my SCIM configuration is correct?
|
||||
### Azure
|
||||
|
||||
#### How do I verify my SCIM configuration is correct?
|
||||
|
||||
Review the following:
|
||||
|
||||
|
|
@ -148,11 +188,11 @@ Review the following SCIM parameters for sensible values:
|
|||
- `displayName`
|
||||
- `emails[type eq "work"].value`
|
||||
|
||||
### Testing Azure connection: invalid credentials
|
||||
#### Testing Azure connection: invalid credentials
|
||||
|
||||
When testing the connection, you may encounter an error: **You appear to have entered invalid credentials. Please confirm you are using the correct information for an administrative account**. If `Tenant URL` and `secret token` are correct, check whether your group path contains characters that may be considered invalid JSON primitives (such as `.`). Removing such characters from the group path typically resolves the error.
|
||||
|
||||
### Azure: (Field) can't be blank sync error
|
||||
#### Azure: (Field) can't be blank sync error
|
||||
|
||||
When checking the Audit Logs for the Provisioning, you can sometimes see the
|
||||
error `Namespace can't be blank, Name can't be blank, and User can't be blank.`
|
||||
|
|
@ -165,14 +205,7 @@ As a workaround, try an alternate mapping:
|
|||
1. Delete the `name.formatted` target attribute entry.
|
||||
1. Change the `displayName` source attribute to have `name.formatted` target attribute.
|
||||
|
||||
### Message: "SAML authentication failed: Email has already been taken"
|
||||
|
||||
This message may be caused by the following:
|
||||
|
||||
- Existing users have not yet signed into the new app.
|
||||
- The identity provider attempts to create a new user account in GitLab with an email address that already exists in GitLab.com.
|
||||
|
||||
### How do I diagnose why a user is unable to sign in
|
||||
#### How do I diagnose why a user is unable to sign in
|
||||
|
||||
The **Identity** (`extern_uid`) value stored by GitLab is updated by SCIM whenever `id` or `externalId` changes. Users won't be able to sign in unless the GitLab Identity (`extern_uid`) value matches the `NameId` sent by SAML.
|
||||
|
||||
|
|
@ -180,7 +213,7 @@ This value is also used by SCIM to match users on the `id`, and is updated by SC
|
|||
|
||||
It is important that this SCIM `id` and SCIM `externalId` are configured to the same value as the SAML `NameId`. SAML responses can be traced using [debugging tools](./index.md#saml-debugging-tools), and any errors can be checked against our [SAML troubleshooting docs](./index.md#troubleshooting).
|
||||
|
||||
### How do I verify user's SAML NameId matches the SCIM externalId
|
||||
#### How do I verify user's SAML NameId matches the SCIM externalId
|
||||
|
||||
Group owners can see the list of users and the `externalId` stored for each user in the group SAML SSO Settings page.
|
||||
|
||||
|
|
@ -194,7 +227,7 @@ curl 'https://example.gitlab.com/api/scim/v2/groups/GROUP_NAME/Users?startIndex=
|
|||
|
||||
To see how this compares to the value returned as the SAML NameId, you can have the user use a [SAML Tracer](index.md#saml-debugging-tools).
|
||||
|
||||
### Update or fix mismatched SCIM externalId and SAML NameId
|
||||
#### Update or fix mismatched SCIM externalId and SAML NameId
|
||||
|
||||
Whether the value was changed or you need to map to a different field, ensure `id`, `externalId`, and `NameId` all map to the same field.
|
||||
|
||||
|
|
@ -220,7 +253,7 @@ curl --verbose --request PATCH 'https://gitlab.com/api/scim/v2/groups/YOUR_GROUP
|
|||
|
||||
It is important not to update these to incorrect values, since this will cause users to be unable to sign in. It is also important not to assign a value to the wrong user, as this would cause users to get signed into the wrong account.
|
||||
|
||||
### I need to change my SCIM app
|
||||
#### I need to change my SCIM app
|
||||
|
||||
Individual users can follow the instructions in the ["SAML authentication failed: User has already been taken"](./index.md#i-need-to-change-my-saml-app) section.
|
||||
|
||||
|
|
|
|||
|
|
@ -40,10 +40,10 @@
|
|||
"@babel/preset-env": "^7.8.4",
|
||||
"@gitlab/at.js": "1.5.5",
|
||||
"@gitlab/svgs": "1.121.0",
|
||||
"@gitlab/ui": "12.2.0",
|
||||
"@gitlab/ui": "12.3.0",
|
||||
"@gitlab/visual-review-tools": "1.6.1",
|
||||
"@sentry/browser": "^5.10.2",
|
||||
"@sourcegraph/code-host-integration": "0.0.36",
|
||||
"@sourcegraph/code-host-integration": "0.0.37",
|
||||
"apollo-cache-inmemory": "^1.6.3",
|
||||
"apollo-client": "^2.6.4",
|
||||
"apollo-link": "^1.2.11",
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ describe Projects::MirrorsController do
|
|||
|
||||
context 'no data in cache' do
|
||||
it 'requests the cache to be filled and returns a 204 response' do
|
||||
expect(ReactiveCachingWorker).to receive(:perform_async).with(cache.class, cache.id).at_least(:once)
|
||||
expect(ExternalServiceReactiveCachingWorker).to receive(:perform_async).with(cache.class, cache.id).at_least(:once)
|
||||
|
||||
do_get(project)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
require Rails.root.join('db', 'post_migrate', '20200420162730_remove_additional_application_settings_rows.rb')
|
||||
|
||||
describe RemoveAdditionalApplicationSettingsRows do
|
||||
let(:application_settings) { table(:application_settings) }
|
||||
|
||||
it 'removes additional rows from application settings' do
|
||||
3.times { application_settings.create! }
|
||||
latest_settings = application_settings.create!
|
||||
|
||||
disable_migrations_output { migrate! }
|
||||
|
||||
expect(application_settings.count).to eq(1)
|
||||
expect(application_settings.first).to eq(latest_settings)
|
||||
end
|
||||
|
||||
it 'leaves only row in application_settings' do
|
||||
latest_settings = application_settings.create!
|
||||
|
||||
disable_migrations_output { migrate! }
|
||||
|
||||
expect(application_settings.first).to eq(latest_settings)
|
||||
end
|
||||
end
|
||||
|
|
@ -66,7 +66,7 @@ describe Grafana::ProxyService do
|
|||
context 'with caching', :use_clean_rails_memory_store_caching do
|
||||
context 'when value not present in cache' do
|
||||
it 'returns nil' do
|
||||
expect(ReactiveCachingWorker)
|
||||
expect(ExternalServiceReactiveCachingWorker)
|
||||
.to receive(:perform_async)
|
||||
.with(service.class, service.id, *cache_params)
|
||||
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ describe Metrics::Dashboard::GrafanaMetricEmbedService do
|
|||
|
||||
context 'when value not present in cache' do
|
||||
it 'returns nil' do
|
||||
expect(ReactiveCachingWorker)
|
||||
expect(ExternalServiceReactiveCachingWorker)
|
||||
.to receive(:perform_async)
|
||||
.with(service.class, service.id, *cache_params)
|
||||
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ describe Prometheus::ProxyService do
|
|||
|
||||
context 'when value not present in cache' do
|
||||
it 'returns nil' do
|
||||
expect(ReactiveCachingWorker)
|
||||
expect(ExternalServiceReactiveCachingWorker)
|
||||
.to receive(:perform_async)
|
||||
.with(subject.class, subject.id, *opts)
|
||||
|
||||
|
|
|
|||
|
|
@ -246,12 +246,19 @@ module GraphqlHelpers
|
|||
|
||||
# Raises an error if no data is found
|
||||
def graphql_data
|
||||
# Note that `json_response` is defined as `let(:json_response)` and
|
||||
# therefore, in a spec with multiple queries, will only contain data
|
||||
# from the _first_ query, not subsequent ones
|
||||
json_response['data'] || (raise NoData, graphql_errors)
|
||||
end
|
||||
|
||||
def graphql_data_at(*path)
|
||||
graphql_dig_at(graphql_data, *path)
|
||||
end
|
||||
|
||||
def graphql_dig_at(data, *path)
|
||||
keys = path.map { |segment| GraphqlHelpers.fieldnamerize(segment) }
|
||||
graphql_data.dig(*keys)
|
||||
data.dig(*keys)
|
||||
end
|
||||
|
||||
def graphql_errors
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Use this for testing how a GraphQL query handles sorting and pagination.
|
||||
# This is particularly important when using keyset pagination connection,
|
||||
# which is the default for ActiveRecord relations, as certain sort keys
|
||||
# might not be supportable.
|
||||
#
|
||||
# sort_param: the value to specify the sort
|
||||
# data_path: the keys necessary to dig into the return GraphQL data to get the
|
||||
# returned results
|
||||
# first_param: number of items expected (like a page size)
|
||||
# expected_results: array of comparison data of all items sorted correctly
|
||||
# pagination_query: method that specifies the GraphQL query
|
||||
# pagination_results_data: method that extracts the sorted data used to compare against
|
||||
# the expected results
|
||||
#
|
||||
# Example:
|
||||
# describe 'sorting and pagination' do
|
||||
# let(:sort_project) { create(:project, :public) }
|
||||
# let(:data_path) { [:project, :issues] }
|
||||
#
|
||||
# def pagination_query(params, page_info)
|
||||
# graphql_query_for(
|
||||
# 'project',
|
||||
# { 'fullPath' => sort_project.full_path },
|
||||
# "issues(#{params}) { #{page_info} edges { node { iid weight } } }"
|
||||
# )
|
||||
# end
|
||||
#
|
||||
# def pagination_results_data(data)
|
||||
# data.map { |issue| issue.dig('node', 'iid').to_i }
|
||||
# end
|
||||
#
|
||||
# context 'when sorting by weight' do
|
||||
# ...
|
||||
# context 'when ascending' do
|
||||
# it_behaves_like 'sorted paginated query' do
|
||||
# let(:sort_param) { 'WEIGHT_ASC' }
|
||||
# let(:first_param) { 2 }
|
||||
# let(:expected_results) { [weight_issue3.iid, weight_issue5.iid, weight_issue1.iid, weight_issue4.iid, weight_issue2.iid] }
|
||||
# end
|
||||
# end
|
||||
#
|
||||
RSpec.shared_examples 'sorted paginated query' do
|
||||
it_behaves_like 'requires variables' do
|
||||
let(:required_variables) { [:sort_param, :first_param, :expected_results, :data_path, :current_user] }
|
||||
end
|
||||
|
||||
describe do
|
||||
let(:params) { "sort: #{sort_param}" }
|
||||
let(:start_cursor) { graphql_data_at(*data_path, :pageInfo, :startCursor) }
|
||||
let(:end_cursor) { graphql_data_at(*data_path, :pageInfo, :endCursor) }
|
||||
let(:sorted_edges) { graphql_data_at(*data_path, :edges) }
|
||||
let(:page_info) { "pageInfo { startCursor endCursor }" }
|
||||
|
||||
def pagination_query(params, page_info)
|
||||
raise('pagination_query(params, page_info) must be defined in the test, see example in comment') unless defined?(super)
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def pagination_results_data(data)
|
||||
raise('pagination_results_data(data) must be defined in the test, see example in comment') unless defined?(super)
|
||||
|
||||
super(data)
|
||||
end
|
||||
|
||||
before do
|
||||
post_graphql(pagination_query(params, page_info), current_user: current_user)
|
||||
end
|
||||
|
||||
context 'when sorting' do
|
||||
it 'sorts correctly' do
|
||||
expect(pagination_results_data(sorted_edges)).to eq expected_results
|
||||
end
|
||||
|
||||
context 'when paginating' do
|
||||
let(:params) { "sort: #{sort_param}, first: #{first_param}" }
|
||||
|
||||
it 'paginates correctly' do
|
||||
expect(pagination_results_data(sorted_edges)).to eq expected_results.first(first_param)
|
||||
|
||||
cursored_query = pagination_query("sort: #{sort_param}, after: \"#{end_cursor}\"", page_info)
|
||||
post_graphql(cursored_query, current_user: current_user)
|
||||
response_data = graphql_dig_at(JSON.parse(response.body), :data, *data_path, :edges)
|
||||
|
||||
expect(pagination_results_data(response_data)).to eq expected_results.drop(first_param)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'requires variables' do
|
||||
it 'shared example requires variables to be set', :aggregate_failures do
|
||||
variables = Array.wrap(required_variables)
|
||||
|
||||
variables.each do |variable_name|
|
||||
expect { send(variable_name) }.not_to(
|
||||
raise_error, "The following variable must be set to use this shared example: #{variable_name}"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
16
yarn.lock
16
yarn.lock
|
|
@ -786,10 +786,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.121.0.tgz#77083a68f72e9aa0e294da7715f378eef13b839e"
|
||||
integrity sha512-scz/6Y/eED7RMFLAlhT6PwXwe0Wj8ivnRsyulk9NXKoqUmAqZliNmBmzYsHy5bFf9NB6xVV/rOk1/92nbi/Yaw==
|
||||
|
||||
"@gitlab/ui@12.2.0":
|
||||
version "12.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-12.2.0.tgz#2dc6103ef4eeb016cdff8a885bee173ed3eab5f3"
|
||||
integrity sha512-Qkyzrcu28gfJy635kqgfXzIKndOMT6touZ32FlXYowyrJOBQOxQt1q4/Z+S6nDWkC0tbBuOsbGiPx6F37MjWjQ==
|
||||
"@gitlab/ui@12.3.0":
|
||||
version "12.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-12.3.0.tgz#9234205887675a6d13a51945ee62efc3c8b5e890"
|
||||
integrity sha512-XrHC2pK7qlwy6K3OR/+iCP8TDewn3jaDIHCfHjt/KOwvD5LsEmam9RHjTiZ4epPZXLv4+JxCzbc4R+euEbIQ7g==
|
||||
dependencies:
|
||||
"@babel/standalone" "^7.0.0"
|
||||
"@gitlab/vue-toasted" "^1.3.0"
|
||||
|
|
@ -1036,10 +1036,10 @@
|
|||
"@sentry/types" "5.10.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sourcegraph/code-host-integration@0.0.36":
|
||||
version "0.0.36"
|
||||
resolved "https://registry.yarnpkg.com/@sourcegraph/code-host-integration/-/code-host-integration-0.0.36.tgz#2f4d287840ac2944c78ef92f10f0db0ef8a077fa"
|
||||
integrity sha512-Hpj1xiVhPxMsjLNre9MrYYAM1SPOWPE9yG9SPtz4dqYzc6/ycaPGyr+ljcaWEclS9hZCvkk4+qVC5WONpYVjyA==
|
||||
"@sourcegraph/code-host-integration@0.0.37":
|
||||
version "0.0.37"
|
||||
resolved "https://registry.yarnpkg.com/@sourcegraph/code-host-integration/-/code-host-integration-0.0.37.tgz#87f9a602e2a60520b6038311a67face2ece86827"
|
||||
integrity sha512-GQvNuPORLjsMhto57Ue1umeSV3cir+hMEaGxwCKmmq+cc9ZSZpuXa8RVBXuT5azN99K9/8zFps4woyPJ8wrjYA==
|
||||
|
||||
"@types/anymatch@*":
|
||||
version "1.3.0"
|
||||
|
|
|
|||
Loading…
Reference in New Issue