Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-04-05 18:09:15 +00:00
parent 2f229658ae
commit 6d18e2830d
74 changed files with 557 additions and 205 deletions

View file

@ -181,16 +181,6 @@ Rails/SaveBang:
- 'spec/features/dashboard/issuables_counter_spec.rb'
- 'spec/features/dashboard/project_member_activity_index_spec.rb'
- 'spec/features/dashboard/projects_spec.rb'
- 'spec/features/issues/bulk_assignment_labels_spec.rb'
- 'spec/features/issues/gfm_autocomplete_spec.rb'
- 'spec/features/issues/issue_sidebar_spec.rb'
- 'spec/features/issues/note_polling_spec.rb'
- 'spec/features/issues/user_creates_branch_and_merge_request_spec.rb'
- 'spec/features/issues/user_creates_confidential_merge_request_spec.rb'
- 'spec/features/issues/user_edits_issue_spec.rb'
- 'spec/features/issues/user_filters_issues_spec.rb'
- 'spec/features/issues/user_sees_live_update_spec.rb'
- 'spec/features/issues/user_sorts_issues_spec.rb'
- 'spec/frontend/fixtures/issues.rb'
- 'spec/frontend/fixtures/merge_requests.rb'
- 'spec/graphql/mutations/merge_requests/set_locked_spec.rb'
@ -302,23 +292,6 @@ Rails/SaveBang:
- 'spec/models/user_status_spec.rb'
- 'spec/models/wiki_page/meta_spec.rb'
- 'spec/models/wiki_page_spec.rb'
- 'spec/requests/api/ci/runner_spec.rb'
- 'spec/requests/api/commit_statuses_spec.rb'
- 'spec/requests/api/conan_packages_spec.rb'
- 'spec/requests/api/deployments_spec.rb'
- 'spec/requests/api/environments_spec.rb'
- 'spec/requests/api/go_proxy_spec.rb'
- 'spec/requests/api/graphql/mutations/merge_requests/set_labels_spec.rb'
- 'spec/requests/api/graphql/user_query_spec.rb'
- 'spec/requests/api/graphql_spec.rb'
- 'spec/requests/api/group_import_spec.rb'
- 'spec/requests/api/group_milestones_spec.rb'
- 'spec/requests/api/internal/base_spec.rb'
- 'spec/requests/api/issues/get_group_issues_spec.rb'
- 'spec/requests/api/issues/post_projects_issues_spec.rb'
- 'spec/requests/api/jobs_spec.rb'
- 'spec/requests/api/labels_spec.rb'
- 'spec/requests/api/project_import_spec.rb'
Rails/TimeZone:
Enabled: true
@ -1102,8 +1075,6 @@ RSpec/EmptyLineAfterFinalLetItBe:
- spec/models/ci/stage_spec.rb
- spec/models/clusters/kubernetes_namespace_spec.rb
- spec/models/commit_spec.rb
- spec/models/concerns/batch_destroy_dependent_associations_spec.rb
- spec/models/concerns/featurable_spec.rb
- spec/models/deploy_token_spec.rb
- spec/models/deployment_spec.rb
- spec/models/diff_viewer/server_side_spec.rb
@ -3335,26 +3306,6 @@ Style/HashTransformation:
- 'ee/lib/gitlab/custom_file_templates.rb'
- 'ee/spec/elastic_integration/global_search_spec.rb'
- 'ee/spec/lib/ee/gitlab/application_context_spec.rb'
- 'lib/api/helpers/packages/conan/api_helpers.rb'
- 'lib/api/projects.rb'
- 'lib/atlassian/jira_connect/client.rb'
- 'lib/banzai/filter/repository_link_filter.rb'
- 'lib/gitlab/ci/config/entry/product/variables.rb'
- 'lib/gitlab/ci/config/entry/variables.rb'
- 'lib/gitlab/ci/variables/collection.rb'
- 'lib/gitlab/ci/variables/helpers.rb'
- 'lib/gitlab/git/commit.rb'
- 'lib/gitlab/import_sources.rb'
- 'lib/gitlab/language_detection.rb'
- 'lib/gitlab/metrics/samplers/database_sampler.rb'
- 'lib/gitlab/metrics/subscribers/active_record.rb'
- 'lib/gitlab/phabricator_import/project_creator.rb'
- 'lib/gitlab/prometheus_client.rb'
- 'lib/gitlab/repository_hash_cache.rb'
- 'lib/gitlab/static_site_editor/config/file_config.rb'
- 'lib/gitlab/template/base_template.rb'
- 'lib/gitlab/usage_data_counters/base_counter.rb'
- 'lib/gitlab/usage_data_counters/note_counter.rb'
- 'spec/lib/atlassian/jira_connect/serializers/pull_request_entity_spec.rb'
- 'spec/lib/gitlab/ci/status/composite_spec.rb'
- 'spec/lib/gitlab/conflict/file_spec.rb'

View file

@ -42,6 +42,7 @@ export default {
:member-id="member.id"
:message="message"
:title="s__('Member|Revoke invite')"
is-invite
/>
</div>
</action-button-group>

View file

@ -36,6 +36,11 @@ export default {
required: false,
default: false,
},
isInvite: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
...mapState(['memberPath']),
@ -57,6 +62,7 @@ export default {
:data-member-path="computedMemberPath"
:data-member-type="memberType"
:data-is-access-request="isAccessRequest"
:data-is-invite="isInvite"
:data-message="message"
data-qa-selector="delete_member_button"
/>

View file

@ -2,7 +2,7 @@
import { GlFormCheckbox, GlModal } from '@gitlab/ui';
import { parseBoolean } from '~/lib/utils/common_utils';
import csrf from '~/lib/utils/csrf';
import { __ } from '~/locale';
import { s__, __ } from '~/locale';
export default {
actionCancel: {
@ -22,11 +22,20 @@ export default {
isAccessRequest() {
return parseBoolean(this.modalData.isAccessRequest);
},
isInvite() {
return parseBoolean(this.modalData.isInvite);
},
isGroupMember() {
return this.modalData.memberType === 'GroupMember';
},
actionText() {
return this.isAccessRequest ? __('Deny access request') : __('Remove member');
if (this.isAccessRequest) {
return __('Deny access request');
} else if (this.isInvite) {
return s__('Member|Revoke invite');
}
return __('Remove member');
},
actionPrimary() {
return {
@ -36,6 +45,9 @@ export default {
},
};
},
showUnassignIssuablesCheckbox() {
return !this.isAccessRequest && !this.isInvite;
},
},
mounted() {
document.addEventListener('click', this.handleClick);
@ -76,7 +88,7 @@ export default {
<gl-form-checkbox v-if="isGroupMember" name="remove_sub_memberships">
{{ __('Also remove direct user membership from subgroups and projects') }}
</gl-form-checkbox>
<gl-form-checkbox v-if="!isAccessRequest" name="unassign_issuables">
<gl-form-checkbox v-if="showUnassignIssuablesCheckbox" name="unassign_issuables">
{{ __('Also unassign this user from related issues and merge requests') }}
</gl-form-checkbox>
</form>

View file

@ -26,6 +26,8 @@ module Types
description: 'Whether this job is allowed to fail.'
field :duration, GraphQL::INT_TYPE, null: true,
description: 'Duration of the job in seconds.'
field :tags, [GraphQL::STRING_TYPE], null: true,
description: 'Tags for the current job.'
# Life-cycle timestamps:
field :created_at, Types::TimeType, null: false,
@ -68,6 +70,10 @@ module Types
Gitlab::Graphql::Loaders::BatchModelLoader.new(::Ci::Pipeline, object.pipeline_id).find
end
def tags
object.tags.map(&:name) if object.is_a?(::Ci::Build)
end
def detailed_status
object.detailed_status(context[:current_user])
end

View file

@ -187,7 +187,7 @@ module ObjectStorage
hash[:TempPath] = workhorse_local_upload_path
end
hash[:FeatureFlagExtractBase] = Feature.enabled?(:workhorse_extract_filename_base)
hash[:FeatureFlagExtractBase] = Feature.enabled?(:workhorse_extract_filename_base, default_enabled: :yaml)
hash[:MaximumSize] = maximum_size if maximum_size.present?
end
end

View file

@ -8,7 +8,7 @@
.content-block.oneline-block.files-changed.diff-files-changed.js-diff-files-changed
.files-changed-inner
.inline-parallel-buttons.d-none.d-md-block
.inline-parallel-buttons.gl-display-none.gl-md-display-flex
- if !diffs_expanded? && diff_files.any? { |diff_file| diff_file.collapsed? }
= link_to _('Expand all'), url_for(safe_params.merge(expanded: 1, format: nil)), class: 'gl-button btn btn-default'
- if show_whitespace_toggle
@ -20,7 +20,7 @@
= diff_compare_whitespace_link(diffs.project, params[:from], params[:to], class: 'd-none d-sm-inline-block')
- elsif current_controller?(:wikis)
= toggle_whitespace_link(url_for(params_with_whitespace), class: 'd-none d-sm-inline-block')
.btn-group
.btn-group.gl-ml-3
= inline_diff_btn
= parallel_diff_btn
= render 'projects/diffs/stats', diff_files: diff_files

View file

@ -14,10 +14,10 @@
= submodule_diff_compare_link(diff_file)
- unless diff_file.submodule?
.file-actions.d-none.d-sm-block
.file-actions.gl-display-none.gl-sm-display-flex
- if diff_file.blob&.readable_text?
%span.has-tooltip{ title: _("Toggle comments for this file") }
= link_to '#', class: 'js-toggle-diff-comments btn gl-button btn-default selected', disabled: @diff_notes_disabled do
%span.has-tooltip.gl-mr-3{ title: _("Toggle comments for this file") }
= link_to '#', class: 'js-toggle-diff-comments btn gl-button btn-default btn-icon selected', disabled: @diff_notes_disabled do
= sprite_icon('comment')
\
- if editable_diff?(diff_file)

View file

@ -1,15 +1,15 @@
- noteable_name = @note.noteable.human_class_name
.float-left.btn-group.gl-mr-3.droplab-dropdown.comment-type-dropdown.js-comment-type-dropdown
%input.btn.gl-button.btn-success.js-comment-button.js-comment-submit-button{ type: 'submit', value: _('Comment'), data: { qa_selector: 'comment_button' } }
%input.btn.gl-button.btn-confirm.js-comment-button.js-comment-submit-button{ type: 'submit', value: _('Comment'), data: { qa_selector: 'comment_button' } }
- if @note.can_be_discussion_note?
= button_tag type: 'button', class: 'gl-button btn dropdown-toggle btn-success js-note-new-discussion js-disable-on-submit', data: { 'dropdown-trigger' => '#resolvable-comment-menu' }, 'aria-label' => _('Open comment type dropdown') do
= button_tag type: 'button', class: 'gl-button btn dropdown-toggle btn-confirm btn-icon js-note-new-discussion js-disable-on-submit', data: { 'dropdown-trigger' => '#resolvable-comment-menu' }, 'aria-label' => _('Open comment type dropdown') do
= sprite_icon('chevron-down')
%ul#resolvable-comment-menu.dropdown-menu.dropdown-open-top{ data: { dropdown: true } }
%li#comment.droplab-item-selected{ data: { value: '', 'submit-text' => _('Comment'), 'close-text' => _("Comment & close %{noteable_name}") % { noteable_name: noteable_name }, 'reopen-text' => _("Comment & reopen %{noteable_name}") % { noteable_name: noteable_name } } }
%button{ type: 'button' }
%button.btn.gl-button.btn-default-tertiary
= sprite_icon('check', css_class: 'icon')
.description
%strong= _("Comment")
@ -19,7 +19,7 @@
%li.divider.droplab-item-ignore
%li#discussion{ data: { value: 'DiscussionNote', 'submit-text' => _('Start thread'), 'close-text' => _("Start thread & close %{noteable_name}") % { noteable_name: noteable_name }, 'reopen-text' => _("Start thread & reopen %{noteable_name}") % { noteable_name: noteable_name } } }
%button{ type: 'button' }
%button.btn.gl-button.btn-default-tertiary
= sprite_icon('check', css_class: 'icon')
.description
%strong= _("Start thread")

View file

@ -0,0 +1,5 @@
---
title: Update title on revoke member invite modal and hide unneeded related issues and merge requests checkbox
merge_request: 57755
author:
type: changed

View file

@ -0,0 +1,5 @@
---
title: Update buttons and spacing on commit page
merge_request: 56793
author:
type: changed

View file

@ -0,0 +1,5 @@
---
title: Fix Rails/SaveBang Rubocop offenses for requests/api module
merge_request: 57887
author: Huzaifa Iftikhar @huzaifaiftikhar
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Fix EmptyLineAfterFinalLetItBe offenses in spec/models/concerns
merge_request: 58367
author: Huzaifa Iftikhar @huzaifaiftikhar
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Add tags field to jobType in the CI namespace
merge_request: 57631
author:
type: changed

View file

@ -0,0 +1,5 @@
---
title: Fix cop offenses for Style/HashTransformation in lib directory
merge_request: 56583
author: Karthik Sivadas @karthik.sivadas
type: other

View file

@ -0,0 +1,5 @@
---
title: Fix Rails/SaveBang rubocop offenses in spec/features/issues
merge_request: 57900
author: Abdul Wadood @abdulwd
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Set workhorse_extract_filename_base feature flag to default
merge_request: 58504
author:
type: changed

View file

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/326379
milestone: '13.11'
type: development
group: group::source code
default_enabled: false
default_enabled: true

View file

@ -21,6 +21,9 @@ You can use the following environment variables to override certain values:
|--------------------------------------------|---------|---------------------------------------------------------------------------------------------------------|
| `DATABASE_URL` | string | The database URL; is of the form: `postgresql://localhost/blog_development`. |
| `ENABLE_BOOTSNAP` | string | Enables Bootsnap for speeding up initial Rails boot (`1` to enable). |
| `EXTERNAL_VALIDATION_SERVICE_TIMEOUT` | integer | Timeout, in seconds, for an [external CI/CD pipeline validation service](external_pipeline_validation.md). Default is `5`. |
| `EXTERNAL_VALIDATION_SERVICE_URL` | string | URL to an [external CI/CD pipeline validation service](external_pipeline_validation.md). |
| `EXTERNAL_VALIDATION_SERVICE_TOKEN` | string | The `X-Gitlab-Token` for authentication with an [external CI/CD pipeline validation service](external_pipeline_validation.md). |
| `GITLAB_CDN_HOST` | string | Sets the base URL for a CDN to serve static assets (for example, `//mycdnsubdomain.fictional-cdn.com`). |
| `GITLAB_EMAIL_DISPLAY_NAME` | string | The name used in the **From** field in emails sent by GitLab. |
| `GITLAB_EMAIL_FROM` | string | The email address used in the **From** field in emails sent by GitLab. |
@ -29,8 +32,8 @@ You can use the following environment variables to override certain values:
| `GITLAB_HOST` | string | The full URL of the GitLab server (including `http://` or `https://`). |
| `GITLAB_ROOT_PASSWORD` | string | Sets the password for the `root` user on installation. |
| `GITLAB_SHARED_RUNNERS_REGISTRATION_TOKEN` | string | Sets the initial registration token used for runners. |
| `GITLAB_UNICORN_MEMORY_MAX` | integer | The maximum memory threshold (in bytes) for the [unicorn-worker-killer](operations/unicorn.md#unicorn-worker-killer). |
| `GITLAB_UNICORN_MEMORY_MIN` | integer | The minimum memory threshold (in bytes) for the [unicorn-worker-killer](operations/unicorn.md#unicorn-worker-killer). |
| `GITLAB_UNICORN_MEMORY_MAX` | integer | The maximum memory threshold (in bytes) for the [unicorn-worker-killer](operations/unicorn.md#unicorn-worker-killer). |
| `GITLAB_UNICORN_MEMORY_MIN` | integer | The minimum memory threshold (in bytes) for the [unicorn-worker-killer](operations/unicorn.md#unicorn-worker-killer). |
| `RAILS_ENV` | string | The Rails environment; can be one of `production`, `development`, `staging`, or `test`. |
| `UNSTRUCTURED_RAILS_LOG` | string | Enables the unstructured log in addition to JSON logs (defaults to `true`). |

View file

@ -7,7 +7,7 @@ type: reference, howto
# External Pipeline Validation
You can use an external service for validating a pipeline before it's created.
You can use an external service to validate a pipeline before it's created.
WARNING:
This is an experimental feature and subject to change without notice.
@ -19,19 +19,17 @@ data as payload. GitLab then invalidates the pipeline based on the response
code. If there's an error or the request times out, the pipeline is not
invalidated.
Response Code Legend:
Response codes:
- `200` - Accepted
- `406` - Not Accepted
- Other Codes - Accepted and Logged
- `200`: Accepted
- `4XX`: Not accepted
- All other codes: accepted and logged
## Configuration
To configure external pipeline validation:
1. Set the `EXTERNAL_VALIDATION_SERVICE_URL` environment variable to the external
service URL.
1. Enable the `ci_external_validation_service` feature flag.
To configure external pipeline validation, add the
[`EXTERNAL_VALIDATION_SERVICE_URL` environment variable](environment_variables.md)
and set it to the external service URL.
By default, requests to the external service time out after five seconds. To override
the default, set the `EXTERNAL_VALIDATION_SERVICE_TIMEOUT` environment variable to the
@ -131,3 +129,6 @@ required number of seconds.
}
}
```
The `namespace` field is only available in [GitLab Premium](https://about.gitlab.com/pricing/)
and higher.

View file

@ -1235,6 +1235,7 @@ An edge in a connection.
| `stage` | [`CiStage`](#cistage) | Stage of the job. |
| `startedAt` | [`Time`](#time) | When the job was started. |
| `status` | [`CiJobStatus`](#cijobstatus) | Status of the job. |
| `tags` | [`[String!]`](#string) | Tags for the current job. |
### `CiJobArtifact`

View file

@ -182,3 +182,17 @@ Watch a walkthrough of this feature in details in the video below.
<figure class="video-container">
<iframe src="https://www.youtube.com/embed/NmdWRGT8kZg" frameborder="0" allowfullscreen="true"> </iframe>
</figure>
## External pipeline validation service
The [external CI/CD pipeline validation service](../../administration/external_pipeline_validation.md)
is available for use on self-managed GitLab instances, but is not in use on GitLab.com.
It is configured with [environment variables](../../administration/environment_variables.md)
on the instance.
To enable the feature on GitLab.com, enable the `ci_external_validation_service`
[feature flag](../feature_flags/index.md). The valid "Not accepted" response code
for GitLab.com is `406` only.
For more details, see the linked issues and MRs in the
[feature flag rollout issue](https://gitlab.com/gitlab-org/gitlab/-/issues/325982).

View file

@ -212,8 +212,8 @@ When it comes to querying DOM elements in your tests, it is best to uniquely and
the element.
Preferentially, this is done by targeting what the user actually sees using [DOM Testing Library](https://testing-library.com/docs/dom-testing-library/intro/).
When selecting by text it is best to use [`getByRole` or `findByRole`](https://testing-library.com/docs/queries/byrole/)
as these enforce accessibility best practices as well. The examples below demonstrate the order of preference.
When selecting by text it is best to use the [`byRole`](https://testing-library.com/docs/queries/byrole) query
as it helps enforce accessibility best practices. `findByRole` and the other [DOM Testing Library queries](https://testing-library.com/docs/queries/about) are available when using [`shallowMountExtended` or `mountExtended`](#shallowmountextended-and-mountextended).
When writing Vue component unit tests, it can be wise to query children by component, so that the unit test can focus on comprehensive value coverage
rather than dealing with the complexity of a child component's behavior.
@ -223,25 +223,27 @@ possible selectors include:
- A semantic attribute like `name` (also verifies that `name` was setup properly)
- A `data-testid` attribute ([recommended by maintainers of `@vue/test-utils`](https://github.com/vuejs/vue-test-utils/issues/1498#issuecomment-610133465))
optionally combined with [`findByTestId`](#extendedwrapper-and-findbytestid)
optionally combined with [`shallowMountExtended` or `mountExtended`](#shallowmountextended-and-mountextended)
- a Vue `ref` (if using `@vue/test-utils`)
```javascript
import { getByRole, getByText } from '@testing-library/dom'
import { shallowMountExtended } from 'helpers/vue_test_utils_helper'
const wrapper = shallowMountExtended(ExampleComponent);
// In this example, `wrapper` is a `@vue/test-utils` wrapper returned from `mount` or `shallowMount`.
it('exists', () => {
// Best (especially for integration tests)
getByRole(wrapper.element, 'link', { name: /Click Me/i })
getByRole(wrapper.element, 'link', { name: 'Click Me' })
getByText(wrapper.element, 'Click Me')
getByText(wrapper.element, /Click Me/i)
wrapper.findByRole('link', { name: /Click Me/i })
wrapper.findByRole('link', { name: 'Click Me' })
wrapper.findByText('Click Me')
wrapper.findByText(/Click Me/i)
// Good (especially for unit tests)
wrapper.find(FooComponent);
wrapper.find('input[name=foo]');
wrapper.find('[data-testid="my-foo-id"]');
wrapper.findByTestId('my-foo-id'); // with the extendedWrapper utility check below
wrapper.findByTestId('my-foo-id'); // with shallowMountExtended or mountExtended check below
wrapper.find({ ref: 'foo'});
// Bad
@ -1138,23 +1140,40 @@ These are very useful if you don't have a handle to the request's Promise, for e
Both functions run `callback` on the next tick after the requests finish (using `setImmediate()`), to allow any `.then()` or `.catch()` handlers to run.
### `extendedWrapper` and `findByTestId`
### `shallowMountExtended` and `mountExtended`
Using `data-testid` is one of the [recommended ways to query DOM elements](#how-to-query-dom-elements).
You can use the `extendedWrapper` utility on the `wrapper` returned by `shalowMount`/`mount`.
By doing so, the `wrapper` provides you with the ability to perform a `findByTestId`,
which is a shortcut to the more verbose `wrapper.find('[data-testid="my-test-id"]');`
The `shallowMountExtended` and `mountExtended` utilities provide you with the ability to perform
any of the available [DOM Testing Library queries](https://testing-library.com/docs/queries/about)
by prefixing them with `find` or `findAll`.
```javascript
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
describe('FooComponent', () => {
const wrapper = extendedWrapper(shallowMount({
template: `<div data-testid="my-test-id"></div>`,
}));
const wrapper = shallowMountExtended({
template: `
<div data-testid="gitlab-frontend-stack">
<p>GitLab frontend stack</p>
<div role="tablist">
<button role="tab" aria-selected="true">Vue.js</button>
<button role="tab" aria-selected="false">GraphQL</button>
<button role="tab" aria-selected="false">SCSS</button>
</div>
</div>
`,
});
it('exists', () => {
expect(wrapper.findByTestId('my-test-id').exists()).toBe(true);
it('finds elements with `findByTestId`', () => {
expect(wrapper.findByTestId('gitlab-frontend-stack').exists()).toBe(true);
});
it('finds elements with `findByText`', () => {
expect(wrapper.findByText('GitLab frontend stack').exists()).toBe(true);
expect(wrapper.findByText('TypeScript').exists()).toBe(false);
});
it('finds elements with `findAllByRole`', () => {
expect(wrapper.findAllByRole('tab').length).toBe(3);
});
});
```

View file

@ -32,19 +32,15 @@ module API
end
def recipe_upload_urls
{ upload_urls: Hash[
file_names.select(&method(:recipe_file?)).map do |file_name|
[file_name, build_recipe_file_upload_url(file_name)]
end
] }
{ upload_urls: file_names.select(&method(:recipe_file?)).to_h do |file_name|
[file_name, build_recipe_file_upload_url(file_name)]
end }
end
def package_upload_urls
{ upload_urls: Hash[
file_names.select(&method(:package_file?)).map do |file_name|
[file_name, build_package_file_upload_url(file_name)]
end
] }
{ upload_urls: file_names.select(&method(:package_file?)).to_h do |file_name|
[file_name, build_package_file_upload_url(file_name)]
end }
end
def recipe_file?(file_name)

View file

@ -485,7 +485,7 @@ module API
get ':id/languages', feature_category: :source_code_management do
::Projects::RepositoryLanguagesService
.new(user_project, current_user)
.execute.map { |lang| [lang.name, lang.share] }.to_h
.execute.to_h { |lang| [lang.name, lang.share] }
end
desc 'Delete a project'

View file

@ -141,9 +141,9 @@ module Atlassian
def user_notes_count(merge_requests)
return unless merge_requests
Note.count_for_collection(merge_requests.map(&:id), 'MergeRequest').map do |count_group|
Note.count_for_collection(merge_requests.map(&:id), 'MergeRequest').to_h do |count_group|
[count_group.noteable_id, count_group.count]
end.to_h
end
end
def jwt_token(http_method, uri)

View file

@ -60,7 +60,7 @@ module Banzai
def get_uri_types(paths)
return {} if paths.empty?
uri_types = Hash[paths.collect { |name| [name, nil] }]
uri_types = paths.to_h { |name| [name, nil] }
get_blob_types(paths).each do |name, type|
if type == :blob

View file

@ -25,8 +25,7 @@ module Gitlab
def value
@config
.map { |key, value| [key.to_s, Array(value).map(&:to_s)] }
.to_h
.to_h { |key, value| [key.to_s, Array(value).map(&:to_s)] }
end
end
end

View file

@ -18,7 +18,7 @@ module Gitlab
end
def value
Hash[@config.map { |key, value| [key.to_s, expand_value(value)[:value]] }]
@config.to_h { |key, value| [key.to_s, expand_value(value)[:value]] }
end
def self.default(**)
@ -26,7 +26,7 @@ module Gitlab
end
def value_with_data
Hash[@config.map { |key, value| [key.to_s, expand_value(value)] }]
@config.to_h { |key, value| [key.to_s, expand_value(value)] }
end
def use_value_data?

View file

@ -23,7 +23,7 @@ module Gitlab
def transform_from_yaml_variables(vars)
return vars.stringify_keys if vars.is_a?(Hash)
vars.to_a.map { |var| [var[:key].to_s, var[:value]] }.to_h
vars.to_a.to_h { |var| [var[:key].to_s, var[:value]] }
end
def inherit_yaml_variables(from:, to:, inheritance:)

View file

@ -390,7 +390,7 @@ module Gitlab
@committer_name = commit.committer.name.dup
@committer_email = commit.committer.email.dup
@parent_ids = Array(commit.parent_ids)
@trailers = Hash[commit.trailers.map { |t| [t.key, t.value] }]
@trailers = commit.trailers.to_h { |t| [t.key, t.value] }
end
# Gitaly provides a UNIX timestamp in author.date.seconds, and a timezone

View file

@ -28,7 +28,7 @@ module Gitlab
prepend_if_ee('EE::Gitlab::ImportSources') # rubocop: disable Cop/InjectEnterpriseEditionModule
def options
Hash[import_table.map { |importer| [importer.title, importer.name] }]
import_table.to_h { |importer| [importer.title, importer.name] }
end
def values

View file

@ -20,7 +20,7 @@ module Gitlab
# Newly detected languages, returned in a structure accepted by
# Gitlab::Database.bulk_insert
def insertions(programming_languages)
lang_to_id = programming_languages.map { |p| [p.name, p.id] }.to_h
lang_to_id = programming_languages.to_h { |p| [p.name, p.id] }
(languages - previous_language_names).map do |new_lang|
{
@ -63,8 +63,7 @@ module Gitlab
@repository
.languages
.first(MAX_LANGUAGES)
.map { |l| [l[:label], l] }
.to_h
.to_h { |l| [l[:label], l] }
end
end
end

View file

@ -32,9 +32,9 @@ module Gitlab
private
def init_metrics
METRIC_DESCRIPTIONS.map do |name, description|
METRIC_DESCRIPTIONS.to_h do |name, description|
[name, ::Gitlab::Metrics.gauge(:"#{METRIC_PREFIX}#{name}", description)]
end.to_h
end
end
def host_stats

View file

@ -55,12 +55,13 @@ module Gitlab
end
def project_feature_attributes
@project_features_attributes ||= begin
# everything disabled except for issues
ProjectFeature::FEATURES.map do |feature|
[ProjectFeature.access_level_attribute(feature), ProjectFeature::DISABLED]
end.to_h.merge(ProjectFeature.access_level_attribute(:issues) => ProjectFeature::ENABLED)
end
@project_features_attributes ||=
begin
# everything disabled except for issues
ProjectFeature::FEATURES.to_h do |feature|
[ProjectFeature.access_level_attribute(feature), ProjectFeature::DISABLED]
end.merge(ProjectFeature.access_level_attribute(:issues) => ProjectFeature::ENABLED)
end
end
def import_data

View file

@ -140,7 +140,7 @@ module Gitlab
end
def mapped_options
options.keys.map { |k| [gitlab_http_key(k), options[k]] }.to_h
options.keys.to_h { |k| [gitlab_http_key(k), options[k]] }
end
def http_options

View file

@ -148,7 +148,7 @@ module Gitlab
# @param hash [Hash]
# @return [Hash] the stringified hash
def standardize_hash(hash)
hash.map { |k, v| [k.to_s, v.to_s] }.to_h
hash.to_h { |k, v| [k.to_s, v.to_s] }
end
# Record metrics in Prometheus.

View file

@ -28,7 +28,7 @@ module Gitlab
def to_hash_with_defaults
# NOTE: The current approach of simply mapping all the descendents' keys and values ('config')
# into a flat hash may need to be enhanced as we add more complex, non-scalar entries.
@global.descendants.map { |descendant| [descendant.key, descendant.config] }.to_h
@global.descendants.to_h { |descendant| [descendant.key, descendant.config] }
end
private

View file

@ -130,10 +130,10 @@ module Gitlab
return [] if project && !project.repository.exists?
if categories.any?
categories.keys.map do |category|
categories.keys.to_h do |category|
files = self.by_category(category, project)
[category, files.map { |t| { key: t.key, name: t.name, content: t.content } }]
end.to_h
end
else
files = self.all(project)
files.map { |t| { key: t.key, name: t.name, content: t.content } }

View file

@ -22,11 +22,11 @@ module Gitlab::UsageDataCounters
end
def totals
known_events.map { |event| [counter_key(event), read(event)] }.to_h
known_events.to_h { |event| [counter_key(event), read(event)] }
end
def fallback_totals
known_events.map { |event| [counter_key(event), -1] }.to_h
known_events.to_h { |event| [counter_key(event), -1] }
end
def fetch_supported_event(event_name)

View file

@ -24,13 +24,13 @@ module Gitlab::UsageDataCounters
end
def totals
COUNTABLE_TYPES.map do |countable_type|
COUNTABLE_TYPES.to_h do |countable_type|
[counter_key(countable_type), read(:create, countable_type)]
end.to_h
end
end
def fallback_totals
COUNTABLE_TYPES.map { |counter_key| [counter_key(counter_key), -1] }.to_h
COUNTABLE_TYPES.to_h { |counter_key| [counter_key(counter_key), -1] }
end
private

View file

@ -295,8 +295,8 @@ RSpec.describe 'Issues > Labels bulk assignment' do
before do
issue1.milestone = milestone
issue2.milestone = milestone
issue1.save
issue2.save
issue1.save!
issue2.save!
issue1.labels << bug
issue2.labels << feature

View file

@ -171,7 +171,7 @@ RSpec.describe 'Issue Sidebar' do
context 'editing issue labels', :js do
before do
issue.update(labels: [label])
issue.update!(labels: [label])
page.within('.block.labels') do
click_on 'Edit'
end

View file

@ -103,7 +103,7 @@ RSpec.describe 'Issue notes polling', :js do
end
def update_note(note, new_text)
note.update(note: new_text)
note.update!(note: new_text)
wait_for_requests
end

View file

@ -150,7 +150,7 @@ RSpec.describe 'User creates branch and merge request on issue page', :js do
context 'when merge requests are disabled' do
before do
project.project_feature.update(merge_requests_access_level: 0)
project.project_feature.update!(merge_requests_access_level: 0)
visit project_issue_path(project, issue)
end

View file

@ -38,7 +38,7 @@ RSpec.describe 'User creates confidential merge request on issue page', :js do
let(:forked_project) { fork_project(project, user, repository: true) }
before do
forked_project.update(visibility: Gitlab::VisibilityLevel::PRIVATE)
forked_project.update!(visibility: Gitlab::VisibilityLevel::PRIVATE)
visit_confidential_issue
end

View file

@ -78,7 +78,7 @@ RSpec.describe "Issues > User edits issue", :js do
end
it 'warns about version conflict' do
issue.update(title: "New title")
issue.update!(title: "New title")
fill_in 'issue_title', with: 'bug 345'
fill_in 'issue_description', with: 'bug description'
@ -307,7 +307,7 @@ RSpec.describe "Issues > User edits issue", :js do
before do
project.add_guest(guest)
issue.milestone = milestone
issue.save
issue.save!
end
it 'shows milestone text' do

View file

@ -18,7 +18,7 @@ RSpec.describe 'User filters issues', :js do
@issue = Issue.find_by(title: 'foobar')
@issue.milestone = create(:milestone, project: project)
@issue.assignees = []
@issue.save
@issue.save!
end
let(:issue) { @issue }

View file

@ -18,7 +18,7 @@ RSpec.describe 'Issues > User sees live update', :js do
expect(page).to have_text("new title")
issue.update(title: "updated title")
issue.update!(title: "updated title")
wait_for_requests
expect(page).to have_text("updated title")

View file

@ -77,7 +77,7 @@ RSpec.describe "User sorts issues" do
it 'sorts by most recently updated', :js do
issue3.updated_at = Time.now + 100
issue3.save
issue3.save!
visit project_issues_path(project, sort: sort_value_recently_updated)
expect(first_issue).to include('baz')
@ -85,8 +85,8 @@ RSpec.describe "User sorts issues" do
describe 'sorting by due date', :js do
before do
issue1.update(due_date: 1.day.from_now)
issue2.update(due_date: 6.days.from_now)
issue1.update!(due_date: 1.day.from_now)
issue2.update!(due_date: 6.days.from_now)
end
it 'sorts by due date' do
@ -96,7 +96,7 @@ RSpec.describe "User sorts issues" do
end
it 'sorts by due date by excluding nil due dates' do
issue2.update(due_date: nil)
issue2.update!(due_date: nil)
visit project_issues_path(project, sort: sort_value_due_date)
@ -111,7 +111,7 @@ RSpec.describe "User sorts issues" do
end
it 'sorts by least recently due date by excluding nil due dates' do
issue2.update(due_date: nil)
issue2.update!(due_date: nil)
visit project_issues_path(project, label_names: [label.name], sort: sort_value_due_date_later)
@ -122,8 +122,8 @@ RSpec.describe "User sorts issues" do
describe 'filtering by due date', :js do
before do
issue1.update(due_date: 1.day.from_now)
issue2.update(due_date: 6.days.from_now)
issue1.update!(due_date: 1.day.from_now)
issue2.update!(due_date: 6.days.from_now)
end
it 'filters by none' do
@ -147,9 +147,9 @@ RSpec.describe "User sorts issues" do
end
it 'filters by due this week' do
issue1.update(due_date: Date.today.beginning_of_week + 2.days)
issue2.update(due_date: Date.today.end_of_week)
issue3.update(due_date: Date.today - 8.days)
issue1.update!(due_date: Date.today.beginning_of_week + 2.days)
issue2.update!(due_date: Date.today.end_of_week)
issue3.update!(due_date: Date.today - 8.days)
visit project_issues_path(project, due_date: Issue::DueThisWeek.name)
@ -161,9 +161,9 @@ RSpec.describe "User sorts issues" do
end
it 'filters by due this month' do
issue1.update(due_date: Date.today.beginning_of_month + 2.days)
issue2.update(due_date: Date.today.end_of_month)
issue3.update(due_date: Date.today - 50.days)
issue1.update!(due_date: Date.today.beginning_of_month + 2.days)
issue2.update!(due_date: Date.today.end_of_month)
issue3.update!(due_date: Date.today - 50.days)
visit project_issues_path(project, due_date: Issue::DueThisMonth.name)
@ -175,9 +175,9 @@ RSpec.describe "User sorts issues" do
end
it 'filters by overdue' do
issue1.update(due_date: Date.today + 2.days)
issue2.update(due_date: Date.today + 20.days)
issue3.update(due_date: Date.yesterday)
issue1.update!(due_date: Date.today + 2.days)
issue2.update!(due_date: Date.today + 20.days)
issue3.update!(due_date: Date.yesterday)
visit project_issues_path(project, due_date: Issue::Overdue.name)
@ -189,9 +189,9 @@ RSpec.describe "User sorts issues" do
end
it 'filters by due next month and previous two weeks' do
issue1.update(due_date: Date.today - 4.weeks)
issue2.update(due_date: (Date.today + 2.months).beginning_of_month)
issue3.update(due_date: Date.yesterday)
issue1.update!(due_date: Date.today - 4.weeks)
issue2.update!(due_date: (Date.today + 2.months).beginning_of_month)
issue3.update!(due_date: Date.yesterday)
visit project_issues_path(project, due_date: Issue::DueNextMonthAndPreviousTwoWeeks.name)
@ -206,9 +206,9 @@ RSpec.describe "User sorts issues" do
describe 'sorting by milestone', :js do
before do
issue1.milestone = newer_due_milestone
issue1.save
issue1.save!
issue2.milestone = later_due_milestone
issue2.save
issue2.save!
end
it 'sorts by milestone' do
@ -224,9 +224,9 @@ RSpec.describe "User sorts issues" do
before do
issue1.assignees << user2
issue1.save
issue1.save!
issue2.assignees << user2
issue2.save
issue2.save!
end
it 'sorts with a filter applied' do

View file

@ -1,4 +1,6 @@
import { isArray } from 'lodash';
import * as testingLibrary from '@testing-library/dom';
import { createWrapper, WrapperArray, mount, shallowMount } from '@vue/test-utils';
import { isArray, upperFirst } from 'lodash';
const vNodeContainsText = (vnode, text) =>
(vnode.text && vnode.text.includes(text)) ||
@ -37,6 +39,17 @@ export const waitForMutation = (store, expectedMutationType) =>
});
export const extendedWrapper = (wrapper) => {
// https://testing-library.com/docs/queries/about
const AVAILABLE_QUERIES = [
'byRole',
'byLabelText',
'byPlaceholderText',
'byText',
'byDisplayValue',
'byAltText',
'byTitle',
];
if (isArray(wrapper) || !wrapper?.find) {
// eslint-disable-next-line no-console
console.warn(
@ -56,5 +69,63 @@ export const extendedWrapper = (wrapper) => {
return this.findAll(`[data-testid="${id}"]`);
},
},
// `findBy`
...AVAILABLE_QUERIES.reduce((accumulator, query) => {
return {
...accumulator,
[`find${upperFirst(query)}`]: {
value(text, options = {}) {
const elements = testingLibrary[`queryAll${upperFirst(query)}`](
wrapper.element,
text,
options,
);
// Return VTU `ErrorWrapper` if element is not found
// https://github.com/vuejs/vue-test-utils/blob/dev/packages/test-utils/src/error-wrapper.js
// VTU does not expose `ErrorWrapper` so, as of now, this is the best way to
// create an `ErrorWrapper`
if (!elements.length) {
const emptyElement = document.createElement('div');
return createWrapper(emptyElement).find('testing-library-element-not-found');
}
return createWrapper(elements[0], this.options || {});
},
},
};
}, {}),
// `findAllBy`
...AVAILABLE_QUERIES.reduce((accumulator, query) => {
return {
...accumulator,
[`findAll${upperFirst(query)}`]: {
value(text, options = {}) {
const elements = testingLibrary[`queryAll${upperFirst(query)}`](
wrapper.element,
text,
options,
);
const wrappers = elements.map((element) => {
const elementWrapper = createWrapper(element, this.options || {});
elementWrapper.selector = text;
return elementWrapper;
});
const wrapperArray = new WrapperArray(wrappers);
wrapperArray.selector = text;
return wrapperArray;
},
},
};
}, {}),
});
};
export const shallowMountExtended = (...args) => extendedWrapper(shallowMount(...args));
export const mountExtended = (...args) => extendedWrapper(mount(...args));

View file

@ -1,7 +1,27 @@
import { shallowMount } from '@vue/test-utils';
import { extendedWrapper, shallowWrapperContainsSlotText } from './vue_test_utils_helper';
import * as testingLibrary from '@testing-library/dom';
import * as vtu from '@vue/test-utils';
import {
shallowMount,
Wrapper as VTUWrapper,
WrapperArray as VTUWrapperArray,
} from '@vue/test-utils';
import {
extendedWrapper,
shallowMountExtended,
mountExtended,
shallowWrapperContainsSlotText,
} from './vue_test_utils_helper';
jest.mock('@testing-library/dom', () => ({
__esModule: true,
...jest.requireActual('@testing-library/dom'),
}));
describe('Vue test utils helpers', () => {
afterAll(() => {
jest.unmock('@testing-library/dom');
});
describe('shallowWrapperContainsSlotText', () => {
const mockText = 'text';
const mockSlot = `<div>${mockText}</div>`;
@ -84,7 +104,7 @@ describe('Vue test utils helpers', () => {
);
});
it('should find the component by test id', () => {
it('should find the element by test id', () => {
expect(mockComponent.findByTestId(testId).exists()).toBe(true);
});
});
@ -105,5 +125,187 @@ describe('Vue test utils helpers', () => {
expect(mockComponent.findAllByTestId(testId)).toHaveLength(2);
});
});
describe.each`
findMethod | expectedQuery
${'findByRole'} | ${'queryAllByRole'}
${'findByLabelText'} | ${'queryAllByLabelText'}
${'findByPlaceholderText'} | ${'queryAllByPlaceholderText'}
${'findByText'} | ${'queryAllByText'}
${'findByDisplayValue'} | ${'queryAllByDisplayValue'}
${'findByAltText'} | ${'queryAllByAltText'}
`('$findMethod', ({ findMethod, expectedQuery }) => {
const text = 'foo bar';
const options = { selector: 'div' };
const mockDiv = document.createElement('div');
let wrapper;
beforeEach(() => {
wrapper = extendedWrapper(
shallowMount({
template: `<div>foo bar</div>`,
}),
);
});
it(`calls Testing Library \`${expectedQuery}\` function with correct parameters`, () => {
jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => [mockDiv]);
wrapper[findMethod](text, options);
expect(testingLibrary[expectedQuery]).toHaveBeenLastCalledWith(
wrapper.element,
text,
options,
);
});
describe('when element is found', () => {
beforeEach(() => {
jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => [mockDiv]);
jest.spyOn(vtu, 'createWrapper');
});
it('returns a VTU wrapper', () => {
const result = wrapper[findMethod](text, options);
expect(vtu.createWrapper).toHaveBeenCalledWith(mockDiv, wrapper.options);
expect(result).toBeInstanceOf(VTUWrapper);
});
});
describe('when multiple elements are found', () => {
beforeEach(() => {
const mockSpan = document.createElement('span');
jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => [mockDiv, mockSpan]);
jest.spyOn(vtu, 'createWrapper');
});
it('returns the first element as a VTU wrapper', () => {
const result = wrapper[findMethod](text, options);
expect(vtu.createWrapper).toHaveBeenCalledWith(mockDiv, wrapper.options);
expect(result).toBeInstanceOf(VTUWrapper);
});
});
describe('when element is not found', () => {
beforeEach(() => {
jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => []);
});
it('returns a VTU error wrapper', () => {
expect(wrapper[findMethod](text, options).exists()).toBe(false);
});
});
});
describe.each`
findMethod | expectedQuery
${'findAllByRole'} | ${'queryAllByRole'}
${'findAllByLabelText'} | ${'queryAllByLabelText'}
${'findAllByPlaceholderText'} | ${'queryAllByPlaceholderText'}
${'findAllByText'} | ${'queryAllByText'}
${'findAllByDisplayValue'} | ${'queryAllByDisplayValue'}
${'findAllByAltText'} | ${'queryAllByAltText'}
`('$findMethod', ({ findMethod, expectedQuery }) => {
const text = 'foo bar';
const options = { selector: 'div' };
const mockElements = [
document.createElement('li'),
document.createElement('li'),
document.createElement('li'),
];
let wrapper;
beforeEach(() => {
wrapper = extendedWrapper(
shallowMount({
template: `
<ul>
<li>foo</li>
<li>bar</li>
<li>baz</li>
</ul>
`,
}),
);
});
it(`calls Testing Library \`${expectedQuery}\` function with correct parameters`, () => {
jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => mockElements);
wrapper[findMethod](text, options);
expect(testingLibrary[expectedQuery]).toHaveBeenLastCalledWith(
wrapper.element,
text,
options,
);
});
describe('when elements are found', () => {
beforeEach(() => {
jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => mockElements);
});
it('returns a VTU wrapper array', () => {
const result = wrapper[findMethod](text, options);
expect(result).toBeInstanceOf(VTUWrapperArray);
expect(
result.wrappers.every(
(resultWrapper) =>
resultWrapper instanceof VTUWrapper && resultWrapper.options === wrapper.options,
),
).toBe(true);
expect(result.length).toBe(3);
});
});
describe('when elements are not found', () => {
beforeEach(() => {
jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => []);
});
it('returns an empty VTU wrapper array', () => {
const result = wrapper[findMethod](text, options);
expect(result).toBeInstanceOf(VTUWrapperArray);
expect(result.length).toBe(0);
});
});
});
});
describe.each`
mountExtendedFunction | expectedMountFunction
${shallowMountExtended} | ${'shallowMount'}
${mountExtended} | ${'mount'}
`('$mountExtendedFunction', ({ mountExtendedFunction, expectedMountFunction }) => {
const FakeComponent = jest.fn();
const options = {
propsData: {
foo: 'bar',
},
};
beforeEach(() => {
const mockWrapper = { find: jest.fn() };
jest.spyOn(vtu, expectedMountFunction).mockImplementation(() => mockWrapper);
});
it(`calls \`${expectedMountFunction}\` with passed arguments`, () => {
mountExtendedFunction(FakeComponent, options);
expect(vtu[expectedMountFunction]).toHaveBeenCalledWith(FakeComponent, options);
});
it('returns extended wrapper', () => {
const result = mountExtendedFunction(FakeComponent, options);
expect(result).toHaveProperty('find');
expect(result).toHaveProperty('findByTestId');
});
});
});

View file

@ -42,6 +42,7 @@ describe('AccessRequestActionButtons', () => {
memberId: member.id,
title: 'Deny access',
isAccessRequest: true,
isInvite: false,
icon: 'close',
});
});

View file

@ -43,6 +43,7 @@ describe('InviteActionButtons', () => {
message: `Are you sure you want to revoke the invitation for ${member.invite.email} to join "${member.source.fullName}"`,
title: 'Revoke invite',
isAccessRequest: false,
isInvite: true,
icon: 'remove',
});
});

View file

@ -28,6 +28,7 @@ describe('RemoveMemberButton', () => {
message: 'Are you sure you want to remove John Smith?',
title: 'Remove member',
isAccessRequest: true,
isInvite: true,
...propsData,
},
directives: {
@ -48,6 +49,7 @@ describe('RemoveMemberButton', () => {
'data-member-type': 'GroupMember',
'data-message': 'Are you sure you want to remove John Smith?',
'data-is-access-request': 'true',
'data-is-invite': 'true',
'aria-label': 'Remove member',
title: 'Remove member',
icon: 'remove',

View file

@ -43,6 +43,7 @@ describe('UserActionButtons', () => {
message: `Are you sure you want to remove ${member.user.name} from "${member.source.fullName}"`,
title: 'Remove member',
isAccessRequest: false,
isInvite: false,
icon: 'remove',
});
});

View file

@ -15,16 +15,18 @@ describe('RemoveMemberModal', () => {
});
describe.each`
state | memberType | isAccessRequest | actionText | removeSubMembershipsCheckboxExpected | unassignIssuablesCheckboxExpected | message
${'removing a group member'} | ${'GroupMember'} | ${'false'} | ${'Remove member'} | ${true} | ${true} | ${'Are you sure you want to remove Jane Doe from the Gitlab Org / Gitlab Test project?'}
${'removing a project member'} | ${'ProjectMember'} | ${'false'} | ${'Remove member'} | ${false} | ${true} | ${'Are you sure you want to remove Jane Doe from the Gitlab Org / Gitlab Test project?'}
${'denying an access request'} | ${'ProjectMember'} | ${'true'} | ${'Deny access request'} | ${false} | ${false} | ${"Are you sure you want to deny Jane Doe's request to join the Gitlab Org / Gitlab Test project?"}
state | memberType | isAccessRequest | isInvite | actionText | removeSubMembershipsCheckboxExpected | unassignIssuablesCheckboxExpected | message
${'removing a group member'} | ${'GroupMember'} | ${'false'} | ${'false'} | ${'Remove member'} | ${true} | ${true} | ${'Are you sure you want to remove Jane Doe from the Gitlab Org / Gitlab Test project?'}
${'removing a project member'} | ${'ProjectMember'} | ${'false'} | ${'false'} | ${'Remove member'} | ${false} | ${true} | ${'Are you sure you want to remove Jane Doe from the Gitlab Org / Gitlab Test project?'}
${'denying an access request'} | ${'ProjectMember'} | ${'true'} | ${'false'} | ${'Deny access request'} | ${false} | ${false} | ${"Are you sure you want to deny Jane Doe's request to join the Gitlab Org / Gitlab Test project?"}
${'revoking invite'} | ${'ProjectMember'} | ${'false'} | ${'true'} | ${'Revoke invite'} | ${false} | ${false} | ${'Are you sure you want to revoke the invitation for foo@bar.com to join the Gitlab Org / Gitlab Test project?'}
`(
'when $state',
({
actionText,
memberType,
isAccessRequest,
isInvite,
message,
removeSubMembershipsCheckboxExpected,
unassignIssuablesCheckboxExpected,
@ -35,6 +37,7 @@ describe('RemoveMemberModal', () => {
return {
modalData: {
isAccessRequest,
isInvite,
message,
memberPath,
memberType,

View file

@ -13,6 +13,7 @@ RSpec.describe Resolvers::Ci::JobsResolver do
create(:ci_build, :sast, name: 'DAST job', pipeline: pipeline)
create(:ci_build, :dast, name: 'SAST job', pipeline: pipeline)
create(:ci_build, :container_scanning, name: 'Container scanning job', pipeline: pipeline)
create(:ci_build, name: 'Job with tags', pipeline: pipeline, tag_list: ['review'])
end
describe '#resolve' do
@ -24,7 +25,8 @@ RSpec.describe Resolvers::Ci::JobsResolver do
have_attributes(name: 'Normal job'),
have_attributes(name: 'DAST job'),
have_attributes(name: 'SAST job'),
have_attributes(name: 'Container scanning job')
have_attributes(name: 'Container scanning job'),
have_attributes(name: 'Job with tags')
)
end
end
@ -43,5 +45,18 @@ RSpec.describe Resolvers::Ci::JobsResolver do
)
end
end
context 'when a job has tags' do
it "returns jobs with tags when applicable" do
jobs = resolve(described_class, obj: pipeline)
expect(jobs).to contain_exactly(
have_attributes(tag_list: []),
have_attributes(tag_list: []),
have_attributes(tag_list: []),
have_attributes(tag_list: []),
have_attributes(tag_list: ['review'])
)
end
end
end
end

View file

@ -33,6 +33,7 @@ RSpec.describe Types::Ci::JobType do
stage
started_at
status
tags
]
expect(described_class).to have_graphql_fields(*expected_fields)

View file

@ -26,6 +26,7 @@ RSpec.describe BatchDestroyDependentAssociations do
let_it_be(:project) { create(:project) }
let_it_be(:build) { create(:ci_build, project: project) }
let_it_be(:notification_setting) { create(:notification_setting, project: project) }
let!(:todos) { create(:todo, project: project) }
it 'destroys multiple builds' do

View file

@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Featurable do
let_it_be(:user) { create(:user) }
let(:project) { create(:project) }
let(:feature_class) { subject.class }
let(:features) { feature_class::FEATURES }

View file

@ -14,8 +14,8 @@ RSpec.describe API::CommitStatuses do
let(:get_url) { "/projects/#{project.id}/repository/commits/#{sha}/statuses" }
context 'ci commit exists' do
let!(:master) { project.ci_pipelines.create(source: :push, sha: commit.id, ref: 'master', protected: false) }
let!(:develop) { project.ci_pipelines.create(source: :push, sha: commit.id, ref: 'develop', protected: false) }
let!(:master) { project.ci_pipelines.create!(source: :push, sha: commit.id, ref: 'master', protected: false) }
let!(:develop) { project.ci_pipelines.create!(source: :push, sha: commit.id, ref: 'develop', protected: false) }
context "reporter user" do
let(:statuses_id) { json_response.map { |status| status['id'] } }
@ -270,8 +270,8 @@ RSpec.describe API::CommitStatuses do
end
context 'when a pipeline id is specified' do
let!(:first_pipeline) { project.ci_pipelines.create(source: :push, sha: commit.id, ref: 'master', status: 'created') }
let!(:other_pipeline) { project.ci_pipelines.create(source: :push, sha: commit.id, ref: 'master', status: 'created') }
let!(:first_pipeline) { project.ci_pipelines.create!(source: :push, sha: commit.id, ref: 'master', status: 'created') }
let!(:other_pipeline) { project.ci_pipelines.create!(source: :push, sha: commit.id, ref: 'master', status: 'created') }
subject do
post api(post_url, developer), params: {

View file

@ -345,7 +345,7 @@ RSpec.describe API::Deployments do
context 'as a maintainer' do
it 'returns a 403 when updating a deployment with a build' do
deploy.update(deployable: build)
deploy.update!(deployable: build)
put(
api("/projects/#{project.id}/deployments/#{deploy.id}", user),
@ -394,7 +394,7 @@ RSpec.describe API::Deployments do
end
it 'returns a 403 when updating a deployment with a build' do
deploy.update(deployable: build)
deploy.update!(deployable: build)
put(
api("/projects/#{project.id}/deployments/#{deploy.id}", developer),

View file

@ -214,7 +214,7 @@ RSpec.describe API::Environments do
context 'as a maintainer' do
context 'with a stoppable environment' do
before do
environment.update(state: :available)
environment.update!(state: :available)
post api("/projects/#{project.id}/environments/#{environment.id}/stop", user)
end

View file

@ -363,7 +363,7 @@ RSpec.describe API::GoProxy do
let(:module_name) { base }
before do
project.update(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
end
describe 'GET /projects/:id/packages/go/*module_name/@v/list' do
@ -412,7 +412,7 @@ RSpec.describe API::GoProxy do
let(:module_name) { base }
before do
project.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
end
describe 'GET /projects/:id/packages/go/*module_name/@v/list' do

View file

@ -52,7 +52,7 @@ RSpec.describe 'Setting labels of a merge request' do
end
it 'sets the merge request labels, removing existing ones' do
merge_request.update(labels: [label2])
merge_request.update!(labels: [label2])
post_graphql_mutation(mutation, current_user: current_user)

View file

@ -218,12 +218,14 @@ RSpec.describe API::GroupImport do
stub_uploads_object_storage(ImportExportUploader, direct_upload: true)
end
# rubocop:disable Rails/SaveBang
let(:tmp_object) do
fog_connection.directories.new(key: 'uploads').files.create(
key: "tmp/uploads/#{file_name}",
body: file_upload
)
end
# rubocop:enable Rails/SaveBang
let(:fog_file) { fog_to_uploaded_file(tmp_object) }
let(:params) do

View file

@ -20,7 +20,7 @@ RSpec.describe API::GroupMilestones do
let_it_be(:params) { { include_parent_milestones: true } }
before_all do
group.update(parent: ancestor_group)
group.update!(parent: ancestor_group)
end
shared_examples 'listing all milestones' do
@ -83,9 +83,9 @@ RSpec.describe API::GroupMilestones do
end
def setup_for_group
context_group.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
context_group.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
context_group.add_developer(user)
public_project.update(namespace: context_group)
public_project.update!(namespace: context_group)
context_group.reload
end
end

View file

@ -887,7 +887,7 @@ RSpec.describe API::Internal::Base do
context 'project does not exist' do
context 'git pull' do
it 'returns a 200 response with status: false' do
project.destroy
project.destroy!
pull(key, project)

View file

@ -754,7 +754,7 @@ RSpec.describe API::Issues do
let(:parent_group) { create(:group) }
before do
group.update(parent_id: parent_group.id)
group.update!(parent_id: parent_group.id)
group_closed_issue.reload
end

View file

@ -111,7 +111,7 @@ RSpec.describe API::Issues do
let(:not_member) { create(:user) }
before do
project.project_feature.update(issues_access_level: ProjectFeature::PRIVATE)
project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
end
it 'renders 403' do

View file

@ -215,7 +215,7 @@ RSpec.describe API::Jobs do
first_build = create(:ci_build, :trace_artifact, :artifacts, :test_reports, pipeline: pipeline)
first_build.runner = create(:ci_runner)
first_build.user = create(:user)
first_build.save
first_build.save!
control_count = ActiveRecord::QueryRecorder.new { go }.count
@ -223,7 +223,7 @@ RSpec.describe API::Jobs do
second_build = create(:ci_build, :trace_artifact, :artifacts, :test_reports, pipeline: second_pipeline)
second_build.runner = create(:ci_runner)
second_build.user = create(:user)
second_build.save
second_build.save!
expect { go }.not_to exceed_query_limit(control_count)
end
@ -684,7 +684,7 @@ RSpec.describe API::Jobs do
context 'with regular branch' do
before do
pipeline.reload
pipeline.update(ref: 'master',
pipeline.update!(ref: 'master',
sha: project.commit('master').sha)
get_for_ref('master')
@ -696,7 +696,7 @@ RSpec.describe API::Jobs do
context 'with branch name containing slash' do
before do
pipeline.reload
pipeline.update(ref: 'improve/awesome',
pipeline.update!(ref: 'improve/awesome',
sha: project.commit('improve/awesome').sha)
end
@ -732,7 +732,7 @@ RSpec.describe API::Jobs do
stub_artifacts_object_storage
job.success
project.update(visibility_level: visibility_level,
project.update!(visibility_level: visibility_level,
public_builds: public_builds)
get_artifact_file(artifact)
@ -826,7 +826,7 @@ RSpec.describe API::Jobs do
context 'with branch name containing slash' do
before do
pipeline.reload
pipeline.update(ref: 'improve/awesome',
pipeline.update!(ref: 'improve/awesome',
sha: project.commit('improve/awesome').sha)
end

View file

@ -119,7 +119,7 @@ RSpec.describe API::Labels do
expect(label).not_to be_nil
label.priorities.create(project: label.project, priority: 1)
label.priorities.create!(project: label.project, priority: 1)
label.save!
request_params = {
@ -139,7 +139,7 @@ RSpec.describe API::Labels do
expect(label).not_to be_nil
label_id = spec_params[:name] || spec_params[:label_id]
label.priorities.create(project: label.project, priority: 1)
label.priorities.create!(project: label.project, priority: 1)
label.save!
request_params = {
@ -383,7 +383,7 @@ RSpec.describe API::Labels do
it 'returns 409 if label already exists in group' do
group = create(:group)
group_label = create(:group_label, group: group)
project.update(group: group)
project.update!(group: group)
post api("/projects/#{project.id}/labels", user),
params: {

View file

@ -235,12 +235,14 @@ RSpec.describe API::ProjectImport do
stub_uploads_object_storage(ImportExportUploader, direct_upload: true)
end
# rubocop:disable Rails/SaveBang
let(:tmp_object) do
fog_connection.directories.new(key: 'uploads').files.create(
key: "tmp/uploads/#{file_name}",
body: fixture_file_upload(file)
)
end
# rubocop:enable Rails/SaveBang
let(:file_upload) { fog_to_uploaded_file(tmp_object) }
@ -285,7 +287,7 @@ RSpec.describe API::ProjectImport do
it 'returns the import status and the error if failed' do
project = create(:project, :import_failed)
project.add_maintainer(user)
project.import_state.update(last_error: 'error')
project.import_state.update!(last_error: 'error')
get api("/projects/#{project.id}/import", user)