Commit 2a9aa29b authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 28132cea 58776173
<script>
import { GlFilteredSearchToken } from '@gitlab/ui';
import { mapState } from 'vuex';
import { getParameterByName, setUrlParams, queryToObject } from '~/lib/utils/url_utility';
import {
getParameterByName,
setUrlParams,
queryToObject,
redirectTo,
} from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
import {
SEARCH_TOKEN_TYPE,
......@@ -122,14 +127,16 @@ export default {
const sortParamValue = getParameterByName(SORT_QUERY_PARAM_NAME);
const activeTabParamValue = getParameterByName(ACTIVE_TAB_QUERY_PARAM_NAME);
window.location.href = setUrlParams(
{
...params,
...(sortParamValue && { [SORT_QUERY_PARAM_NAME]: sortParamValue }),
...(activeTabParamValue && { [ACTIVE_TAB_QUERY_PARAM_NAME]: activeTabParamValue }),
},
window.location.href,
true,
redirectTo(
setUrlParams(
{
...params,
...(sortParamValue && { [SORT_QUERY_PARAM_NAME]: sortParamValue }),
...(activeTabParamValue && { [ACTIVE_TAB_QUERY_PARAM_NAME]: activeTabParamValue }),
},
window.location.href,
true,
),
);
},
},
......
......@@ -15,26 +15,22 @@
checkbox_options: { disabled: !can_change_prevent_sharing_groups_outside_hierarchy?(@group) }
.form-group.gl-mb-3
.gl-form-checkbox.custom-control.custom-checkbox
= f.check_box :share_with_group_lock, disabled: !can_change_share_with_group_lock?(@group), class: 'custom-control-input'
= f.label :share_with_group_lock, class: 'custom-control-label' do
%span
= s_('GroupSettings|Prevent sharing a project within %{group} with other groups').html_safe % { group: link_to_group(@group) }
%p.js-descr.help-text= share_with_group_lock_help_text(@group)
= f.gitlab_ui_checkbox_component :share_with_group_lock,
s_('GroupSettings|Prevent sharing a project within %{group} with other groups').html_safe % { group: link_to_group(@group) },
checkbox_options: { disabled: !can_change_share_with_group_lock?(@group) },
help_text: share_with_group_lock_help_text(@group)
.form-group.gl-mb-3
.gl-form-checkbox.custom-control.custom-checkbox
= f.check_box :emails_disabled, checked: @group.emails_disabled?, disabled: !can_disable_group_emails?(@group), class: 'custom-control-input'
= f.label :emails_disabled, class: 'custom-control-label' do
%span= s_('GroupSettings|Disable email notifications')
%p.help-text= s_('GroupSettings|This setting will override user notification preferences for all members of the group, subgroups, and projects.')
= f.gitlab_ui_checkbox_component :emails_disabled,
s_('GroupSettings|Disable email notifications'),
checkbox_options: { checked: @group.emails_disabled?, disabled: !can_disable_group_emails?(@group) },
help_text: s_('GroupSettings|This setting will override user notification preferences for all members of the group, subgroups, and projects.')
.form-group.gl-mb-3
.gl-form-checkbox.custom-control.custom-checkbox
= f.check_box :mentions_disabled, checked: @group.mentions_disabled?, class: 'custom-control-input'
= f.label :mentions_disabled, class: 'custom-control-label' do
%span= s_('GroupSettings|Disable group mentions')
%p.help-text= s_('GroupSettings|This setting will prevent group members from being notified if the group is mentioned.')
= f.gitlab_ui_checkbox_component :mentions_disabled,
s_('GroupSettings|Disable group mentions'),
checkbox_options: { checked: @group.mentions_disabled? },
help_text: s_('GroupSettings|This setting will prevent group members from being notified if the group is mentioned.')
= render 'groups/settings/project_access_token_creation', f: f, group: @group
= render_if_exists 'groups/settings/delayed_project_removal', f: f, group: @group
......
# frozen_string_literal: true
class RemoveForeignKeysFromCiTestCaseFailures < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
TABLE_NAME = :ci_test_case_failures
disable_ddl_transaction!
def up
with_lock_retries do
remove_foreign_key_if_exists(TABLE_NAME, column: :build_id)
end
with_lock_retries do
remove_foreign_key_if_exists(TABLE_NAME, column: :test_case_id)
end
end
def down
add_concurrent_foreign_key(TABLE_NAME, :ci_builds, column: :build_id, on_delete: :cascade)
add_concurrent_foreign_key(TABLE_NAME, :ci_test_cases, column: :test_case_id, on_delete: :cascade)
end
end
# frozen_string_literal: true
class RemoveForeignKeysFromCiTestCases < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
TABLE_NAME = :ci_test_cases
disable_ddl_transaction!
def up
with_lock_retries do
remove_foreign_key_if_exists(TABLE_NAME, column: :project_id)
end
end
def down
add_concurrent_foreign_key(TABLE_NAME, :projects, column: :project_id, on_delete: :cascade)
end
end
# frozen_string_literal: true
class DropCiTestCaseFailuresTable < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
drop_table :ci_test_case_failures
end
def down
create_table :ci_test_case_failures do |t|
t.datetime_with_timezone :failed_at
t.bigint :test_case_id, null: false
t.bigint :build_id, null: false
t.index [:test_case_id, :failed_at, :build_id], name: 'index_test_case_failures_unique_columns', unique: true, order: { failed_at: :desc }
t.index :build_id
end
end
end
# frozen_string_literal: true
class DropCiTestCasesTable < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
drop_table :ci_test_cases
end
def down
create_table_with_constraints :ci_test_cases do |t|
t.bigint :project_id, null: false
t.text :key_hash, null: false
t.text_limit :key_hash, 64
t.index [:project_id, :key_hash], unique: true
end
end
end
22a64ce9a8cbebd2024908cc74cc92a50fb6ccaa1580ebea3be60d3659c48fa0
\ No newline at end of file
6ed7827f6f911dbb40637ac056298877b709fb7356bc9ee3a366cceb48268646
\ No newline at end of file
3cb0c88fddfec66c0d89c4c1f34d0538be88a44f2039e6c542c5282b293ce019
\ No newline at end of file
d983a765482b368bd7a238b3b75fc9b0a45310f295953ea053ee4c42785e8684
\ No newline at end of file
......@@ -11340,38 +11340,6 @@ CREATE SEQUENCE ci_subscriptions_projects_id_seq
ALTER SEQUENCE ci_subscriptions_projects_id_seq OWNED BY ci_subscriptions_projects.id;
CREATE TABLE ci_test_case_failures (
id bigint NOT NULL,
failed_at timestamp with time zone,
test_case_id bigint NOT NULL,
build_id bigint NOT NULL
);
CREATE SEQUENCE ci_test_case_failures_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE ci_test_case_failures_id_seq OWNED BY ci_test_case_failures.id;
CREATE TABLE ci_test_cases (
id bigint NOT NULL,
project_id bigint NOT NULL,
key_hash text NOT NULL,
CONSTRAINT check_dd3c5d1c15 CHECK ((char_length(key_hash) <= 64))
);
CREATE SEQUENCE ci_test_cases_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE ci_test_cases_id_seq OWNED BY ci_test_cases.id;
CREATE TABLE ci_trigger_requests (
id integer NOT NULL,
trigger_id integer NOT NULL,
......@@ -20074,10 +20042,6 @@ ALTER TABLE ONLY ci_stages ALTER COLUMN id SET DEFAULT nextval('ci_stages_id_seq
ALTER TABLE ONLY ci_subscriptions_projects ALTER COLUMN id SET DEFAULT nextval('ci_subscriptions_projects_id_seq'::regclass);
ALTER TABLE ONLY ci_test_case_failures ALTER COLUMN id SET DEFAULT nextval('ci_test_case_failures_id_seq'::regclass);
ALTER TABLE ONLY ci_test_cases ALTER COLUMN id SET DEFAULT nextval('ci_test_cases_id_seq'::regclass);
ALTER TABLE ONLY ci_trigger_requests ALTER COLUMN id SET DEFAULT nextval('ci_trigger_requests_id_seq'::regclass);
ALTER TABLE ONLY ci_triggers ALTER COLUMN id SET DEFAULT nextval('ci_triggers_id_seq'::regclass);
......@@ -21311,12 +21275,6 @@ ALTER TABLE ONLY ci_stages
ALTER TABLE ONLY ci_subscriptions_projects
ADD CONSTRAINT ci_subscriptions_projects_pkey PRIMARY KEY (id);
ALTER TABLE ONLY ci_test_case_failures
ADD CONSTRAINT ci_test_case_failures_pkey PRIMARY KEY (id);
ALTER TABLE ONLY ci_test_cases
ADD CONSTRAINT ci_test_cases_pkey PRIMARY KEY (id);
ALTER TABLE ONLY ci_trigger_requests
ADD CONSTRAINT ci_trigger_requests_pkey PRIMARY KEY (id);
......@@ -23397,10 +23355,6 @@ CREATE INDEX index_ci_subscriptions_projects_on_upstream_project_id ON ci_subscr
CREATE UNIQUE INDEX index_ci_subscriptions_projects_unique_subscription ON ci_subscriptions_projects USING btree (downstream_project_id, upstream_project_id);
CREATE INDEX index_ci_test_case_failures_on_build_id ON ci_test_case_failures USING btree (build_id);
CREATE UNIQUE INDEX index_ci_test_cases_on_project_id_and_key_hash ON ci_test_cases USING btree (project_id, key_hash);
CREATE INDEX index_ci_trigger_requests_on_commit_id ON ci_trigger_requests USING btree (commit_id);
CREATE INDEX index_ci_trigger_requests_on_trigger_id_and_id ON ci_trigger_requests USING btree (trigger_id, id DESC);
......@@ -25205,8 +25159,6 @@ CREATE UNIQUE INDEX index_terraform_states_on_project_id_and_name ON terraform_s
CREATE UNIQUE INDEX index_terraform_states_on_uuid ON terraform_states USING btree (uuid);
CREATE UNIQUE INDEX index_test_case_failures_unique_columns ON ci_test_case_failures USING btree (test_case_id, failed_at DESC, build_id);
CREATE INDEX index_timelogs_on_issue_id ON timelogs USING btree (issue_id);
CREATE INDEX index_timelogs_on_merge_request_id ON timelogs USING btree (merge_request_id);
......@@ -25913,9 +25865,6 @@ ALTER TABLE ONLY design_management_designs_versions
ALTER TABLE ONLY terraform_state_versions
ADD CONSTRAINT fk_04b91e4a9f FOREIGN KEY (ci_build_id) REFERENCES ci_builds(id) ON DELETE SET NULL;
ALTER TABLE ONLY ci_test_cases
ADD CONSTRAINT fk_0526c30ded FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY issues
ADD CONSTRAINT fk_05f1e72feb FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE SET NULL;
......@@ -26537,9 +26486,6 @@ ALTER TABLE ONLY ci_sources_pipelines
ALTER TABLE ONLY geo_event_log
ADD CONSTRAINT fk_d5af95fcd9 FOREIGN KEY (lfs_object_deleted_event_id) REFERENCES geo_lfs_object_deleted_events(id) ON DELETE CASCADE;
ALTER TABLE ONLY ci_test_case_failures
ADD CONSTRAINT fk_d69404d827 FOREIGN KEY (build_id) REFERENCES ci_builds(id) ON DELETE CASCADE;
ALTER TABLE ONLY lists
ADD CONSTRAINT fk_d6cf4279f7 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
......@@ -28142,9 +28088,6 @@ ALTER TABLE ONLY vulnerability_finding_evidence_sources
ALTER TABLE ONLY protected_branch_unprotect_access_levels
ADD CONSTRAINT fk_rails_e9eb8dc025 FOREIGN KEY (protected_branch_id) REFERENCES protected_branches(id) ON DELETE CASCADE;
ALTER TABLE ONLY ci_test_case_failures
ADD CONSTRAINT fk_rails_eab6349715 FOREIGN KEY (test_case_id) REFERENCES ci_test_cases(id) ON DELETE CASCADE;
ALTER TABLE ONLY alert_management_alert_user_mentions
ADD CONSTRAINT fk_rails_eb2de0cdef FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE;
......@@ -74,6 +74,38 @@ curl --request PUT \
"https://gitlab.example.com/api/v4/projects/1/packages/debian/mypkg.deb"
```
## Download a package
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64923) in GitLab 14.2.
Download a package file.
```plaintext
GET projects/:id/packages/debian/pool/:distribution/:letter/:package_name/:package_version/:file_name
```
| Attribute | Type | Required | Description |
| ----------------- | ------ | -------- | ----------- |
| `distribution` | string | yes | The codename or suite of the Debian distribution. |
| `letter` | string | yes | The Debian Classification (first-letter or lib-first-letter). |
| `package_name` | string | yes | The source package name. |
| `package_version` | string | yes | The source package version. |
| `file_name` | string | yes | The file name. |
```shell
curl --header "Private-Token: <personal_access_token>" "https://gitlab.example.com/api/v4/projects/1/packages/pool/my-distro/a/my-pkg/1.0.0/example_1.0.0~alpha2_amd64.deb"
```
Write the output to a file:
```shell
curl --header "Private-Token: <personal_access_token>" \
"https://gitlab.example.com/api/v4/projects/1/packages/pool/my-distro/a/my-pkg/1.0.0/example_1.0.0~alpha2_amd64.deb" \
--remote-name
```
This writes the downloaded file using the remote file name in the current directory.
## Route prefix
The remaining endpoints described are two sets of identical routes that each make requests in
......@@ -108,7 +140,7 @@ The examples in this document all use the project-level prefix.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64067) in GitLab 14.1.
Download a Debian package file.
Download a Debian distribution file.
```plaintext
GET <route-prefix>/dists/*distribution/Release
......@@ -130,16 +162,13 @@ curl --header "Private-Token: <personal_access_token>" \
--remote-name
```
This writes the downloaded file to `Release` in the current directory.
This writes the downloaded file using the remote file name in the current directory.
## Download a signed distribution Release file
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64067) in GitLab 14.1.
Download a Debian package file.
Signed releases are [not supported](https://gitlab.com/groups/gitlab-org/-/epics/6057#note_582697034).
Therefore, this endpoint downloads the unsigned release file.
Download a signed Debian distribution file.
```plaintext
GET <route-prefix>/dists/*distribution/InRelease
......@@ -161,4 +190,62 @@ curl --header "Private-Token: <personal_access_token>" \
--remote-name
```
This writes the downloaded file to `InRelease` in the current directory.
This writes the downloaded file using the remote file name in the current directory.
## Download a release file signature
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64923) in GitLab 14.2.
Download a Debian release file signature.
```plaintext
GET <route-prefix>/dists/*distribution/Release.gpg
```
| Attribute | Type | Required | Description |
| ----------------- | ------ | -------- | ----------- |
| `distribution` | string | yes | The codename or suite of the Debian distribution. |
```shell
curl --header "Private-Token: <personal_access_token>" "https://gitlab.example.com/api/v4/projects/1/packages/debian/dists/my-distro/Release.gpg"
```
Write the output to a file:
```shell
curl --header "Private-Token: <personal_access_token>" \
"https://gitlab.example.com/api/v4/projects/1/packages/debian/dists/my-distro/Release.gpg" \
--remote-name
```
This writes the downloaded file using the remote file name in the current directory.
## Download a binary file's index
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64923) in GitLab 14.2.
Download a distribution index.
```plaintext
GET <route-prefix>/dists/*distribution/:component/binary-:architecture/Packages
```
| Attribute | Type | Required | Description |
| ----------------- | ------ | -------- | ----------- |
| `distribution` | string | yes | The codename or suite of the Debian distribution. |
| `component` | string | yes | The distribution component name. |
| `architecture` | string | yes | The distribution architecture type. |
```shell
curl --header "Private-Token: <personal_access_token>" "https://gitlab.example.com/api/v4/projects/1/packages/debian/dists/my-distro/main/amd64/Packages"
```
Write the output to a file:
```shell
curl --header "Private-Token: <personal_access_token>" \
"https://gitlab.example.com/api/v4/projects/1/packages/debian/dists/my-distro/main/amd64/Packages" \
--remote-name
```
This writes the downloaded file using the remote file name in the current directory.
This diff is collapsed.
......@@ -442,6 +442,18 @@ it('passes', () => {
});
```
NOTE:
To modify only the hash, use either the `setWindowLocation` helper, or assign
directly to `window.location.hash`, e.g.:
```javascript
it('passes', () => {
window.location.hash = '#foo';
expect(window.location.href).toBe('http://test.host/#foo');
});
```
If your tests need to assert that certain `window.location` methods were
called, use the `useMockLocationHelper` helper:
......
......@@ -61,6 +61,15 @@ Feature.disable(:debian_group_packages)
Creating a Debian package is documented [on the Debian Wiki](https://wiki.debian.org/Packaging).
## Authenticate to the Package Registry
To create a distribution, publish a package, or install a private package, you need one of the
following:
- [Personal access token](../../../api/index.md#personalproject-access-tokens)
- [CI/CD job token](../../../api/index.md#gitlab-cicd-job-token)
- [Deploy token](../../project/deploy_tokens/index.md)
## Create a Distribution
On the project-level, Debian packages are published using *Debian Distributions*. To publish
......@@ -116,7 +125,7 @@ To upload these files, you can use `dput-ng >= 1.32` (Debian bullseye):
cat <<EOF > dput.cf
[gitlab]
method = https
fqdn = <login>:<your_access_token>@gitlab.example.com
fqdn = <username>:<your_access_token>@gitlab.example.com
incoming = /api/v4/projects/<project_id>/packages/debian
EOF
......@@ -125,5 +134,27 @@ dput --config=dput.cf --unchecked --no-upload-log gitlab <your_package>.changes
## Install a package
The Debian package registry for GitLab is under development, and isn't ready for production use. You
cannot install packages from the registry. However, you can download files directly from the UI.
To install a package:
1. Configure the repository:
If you are using a private project, add your [credentials](#authenticate-to-the-package-registry) to your apt config:
```shell
echo 'machine gitlab.example.com login <username> password <your_access_token>' \
| sudo tee /etc/apt/auth.conf.d/gitlab_project.conf
```
Add your project as a source:
```shell
echo 'deb [trusted=yes] https://gitlab.example.com/api/v4/projects/<project_id>/packages/debian <codename> <component1> <component2>' \
| sudo tee /etc/apt/sources.list.d/gitlab_project.list
sudo apt-get update
```
1. Install the package:
```shell
sudo apt-get -y install -t <codename> <package-name>
```
......@@ -4,6 +4,11 @@ module EE
module Ci
module BuildFinishedWorker
def process_build(build)
# Always run `super` first since it contains sync operations.
# Failing to run sync operations would cause the worker to retry
# and enqueueing duplicate jobs.
super
unless ::Feature.enabled?(:cancel_pipelines_prior_to_destroy, build.project, default_enabled: :yaml)
::Ci::Minutes::UpdateBuildMinutesService.new(build.project, nil).execute(build)
end
......@@ -15,8 +20,6 @@ module EE
if ::Gitlab.com? && build.has_security_reports?
::Security::TrackSecureScansWorker.perform_async(build.id)
end
super
end
end
end
......
import * as actions from 'ee/audit_events/store/actions';
import * as types from 'ee/audit_events/store/mutation_types';
import createState from 'ee/audit_events/store/state';
import setWindowLocation from 'helpers/set_window_location_helper';
import testAction from 'helpers/vuex_action_helper';
import * as urlUtility from '~/lib/utils/url_utility';
......@@ -48,8 +49,7 @@ describe('Audit Event actions', () => {
let spy;
beforeEach(() => {
delete window.location;
window.location = new URL('https://test/');
setWindowLocation('https://test/');
spy = jest.spyOn(urlUtility, 'visitUrl').mockReturnValue({});
});
......@@ -81,8 +81,7 @@ describe('Audit Event actions', () => {
describe('initializeAuditEvents', () => {
describe('with an empty search query', () => {
beforeEach(() => {
delete window.location;
window.location = { search: '' };
setWindowLocation('?');
});
it(`commits "${types.INITIALIZE_AUDIT_EVENTS}" with empty dates`, () => {
......@@ -100,11 +99,9 @@ describe('Audit Event actions', () => {
describe('with a full search query', () => {
beforeEach(() => {
delete window.location;
window.location = {
search:
'sort=created_desc&entity_type=User&entity_id=44&created_after=2020-06-05&created_before=2020-06-25',
};
setWindowLocation(
'?sort=created_desc&entity_type=User&entity_id=44&created_after=2020-06-05&created_before=2020-06-25',
);
});
it(`commits "${types.INITIALIZE_AUDIT_EVENTS}" with the query data`, () => {
......
......@@ -4,6 +4,7 @@ import {
formatEpicListsPageInfo,
transformBoardConfig,
} from 'ee/boards/boards_util';
import setWindowLocation from 'helpers/set_window_location_helper';
import { mockLabel } from './mock_data';
const listId = 'gid://gitlab/Boards::EpicList/3';
......@@ -105,10 +106,6 @@ describe('formatEpicListsPageInfo', () => {
});
describe('transformBoardConfig', () => {
beforeEach(() => {
delete window.location;
});
const boardConfig = {
milestoneTitle: 'milestone',
assigneeUsername: 'username',
......@@ -120,18 +117,17 @@ describe('transformBoardConfig', () => {
};
it('formats url parameters from boardConfig object', () => {
window.location = { search: '' };
const result = transformBoardConfig(boardConfig);
expect(result).toContain(
expect(result).toBe(
'milestone_title=milestone&weight=0&assignee_username=username&label_name[]=Deliverable&label_name[]=On%20hold',
);
});
it('formats url parameters from boardConfig object preventing duplicates with passed filter query', () => {
window.location = { search: '?label_name[]=Deliverable&label_name[]=On%20hold' };
setWindowLocation('?label_name[]=Deliverable&label_name[]=On%20hold');
const result = transformBoardConfig(boardConfig);
expect(result).toContain('milestone_title=milestone&weight=0&assignee_username=username');
expect(result).toBe('milestone_title=milestone&weight=0&assignee_username=username');
});
});
......@@ -44,7 +44,6 @@ const defaultProps = {
describe('BoardForm', () => {
let wrapper;
let mutate;
let location;
const findModal = () => wrapper.findComponent(GlModal);
const findModalActionPrimary = () => findModal().props('actionPrimary');
......@@ -81,16 +80,10 @@ describe('BoardForm', () => {
});
};
beforeAll(() => {
location = window.location;
delete window.location;
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
mutate = null;
window.location = location;
});
describe('when creating a new epic board', () => {
......@@ -192,7 +185,6 @@ describe('BoardForm', () => {
},
},
});
window.location = new URL('https://test/boards/1');
createComponent({ canAdminBoard: true, currentPage: formType.edit });
findInput().trigger('keyup.enter', { metaKey: true });
......
......@@ -199,7 +199,7 @@ describe('Project Licenses', () => {
`(
'when window.location contains the hash "$givenLocationHash"',
({ givenLocationHash, expectedActiveTab }) => {
const originalLocation = window.location;
const originalLocation = window.location.href;
beforeEach(() => {
setWindowLocation(`http://foo.com/index${givenLocationHash}`);
......@@ -224,7 +224,7 @@ describe('Project Licenses', () => {
});
afterEach(() => {
window.location = originalLocation;
setWindowLocation(originalLocation);
});
it(`sets the active tab to be "${expectedActiveTab}"`, () => {
......
......@@ -56,6 +56,20 @@ RSpec.describe Ci::BuildFinishedWorker do
subject
end
context 'when exception is raised in `super`' do
before do
allow(::BuildHooksWorker)
.to receive(:perform_async)
.and_raise(ArgumentError)
end
it 'does not enqueue the worker in EE' do
expect { subject }.to raise_error(ArgumentError)
expect(::Security::TrackSecureScansWorker).not_to receive(:perform_async)
end
end
context 'when build does not have a security report' do
let(:build) { create(:ee_ci_build, :success, runner: ci_runner) }
......
......@@ -71,6 +71,7 @@ module API
expose(:pages_access_level) { |project, options| project.project_feature.string_access_level(:pages) }
expose(:operations_access_level) { |project, options| project.project_feature.string_access_level(:operations) }
expose(:analytics_access_level) { |project, options| project.project_feature.string_access_level(:analytics) }
expose(:container_registry_access_level) { |project, options| project.project_feature.string_access_level(:container_registry) }
expose :emails_disabled
expose :shared_runners_enabled
......
......@@ -35,13 +35,14 @@ module API
optional :pages_access_level, type: String, values: %w(disabled private enabled public), desc: 'Pages access level. One of `disabled`, `private`, `enabled` or `public`'
optional :operations_access_level, type: String, values: %w(disabled private enabled), desc: 'Operations access level. One of `disabled`, `private` or `enabled`'
optional :analytics_access_level, type: String, values: %w(disabled private enabled), desc: 'Analytics access level. One of `disabled`, `private` or `enabled`'
optional :container_registry_access_level, type: String, values: %w(disabled private enabled), desc: 'Controls visibility of the container registry. One of `disabled`, `private` or `enabled`. `private` will make the container registry accessible only to project members (reporter role and above). `enabled` will make the container registry accessible to everyone who has access to the project. `disabled` will disable the container registry'
optional :emails_disabled, type: Boolean, desc: 'Disable email notifications'
optional :show_default_award_emojis, type: Boolean, desc: 'Show default award emojis'
optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project'
optional :resolve_outdated_diff_discussions, type: Boolean, desc: 'Automatically resolve merge request diffs discussions on lines changed with a push'
optional :remove_source_branch_after_merge, type: Boolean, desc: 'Remove the source branch by default after merge'
optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project'
optional :container_registry_enabled, type: Boolean, desc: 'Deprecated: Use :container_registry_access_level instead. Flag indication if the container registry is enabled for that project'
optional :container_expiration_policy_attributes, type: Hash do
use :optional_container_expiration_policy_params
end
......@@ -124,7 +125,7 @@ module API
:ci_config_path,
:ci_default_git_depth,
:ci_forward_deployment_enabled,
:container_registry_enabled,
:container_registry_access_level,
:container_expiration_policy_attributes,
:default_branch,
:description,
......@@ -169,7 +170,8 @@ module API
:jobs_enabled,
:merge_requests_enabled,
:wiki_enabled,
:snippets_enabled
:snippets_enabled,
:container_registry_enabled
]
end
......
import { getByTestId, fireEvent } from '@testing-library/dom';
import { createWrapper } from '@vue/test-utils';
import setWindowLocation from 'helpers/set_window_location_helper';
import { initRecoveryCodes, initClose2faSuccessMessage } from '~/authentication/two_factor_auth';
import RecoveryCodes from '~/authentication/two_factor_auth/components/recovery_codes.vue';
import * as urlUtils from '~/lib/utils/url_utility';
......@@ -53,8 +54,7 @@ describe('initClose2faSuccessMessage', () => {
describe('when alert is closed', () => {
beforeEach(() => {
delete window.location;
window.location = new URL(
setWindowLocation(
'https://localhost/-/profile/account?two_factor_auth_enabled_successfully=true',
);
......
import setWindowLocation from 'helpers/set_window_location_helper';
import WebAuthnError from '~/authentication/webauthn/error';
describe('WebAuthnError', () => {
......@@ -17,19 +18,8 @@ describe('WebAuthnError', () => {
});
describe('SecurityError', () => {
const { location } = window;
beforeEach(() => {
delete window.location;
window.location = {};
});
afterEach(() => {
window.location = location;
});
it('returns a descriptive error if https is disabled', () => {
window.location.protocol = 'http:';
setWindowLocation('http://localhost');
const expectedMessage =
'WebAuthn only works with HTTPS-enabled websites. Contact your administrator for more details.';
......@@ -39,7 +29,7 @@ describe('WebAuthnError', () => {
});
it('returns a generic error if https is enabled', () => {
window.location.protocol = 'https:';
setWindowLocation('https://localhost');
const expectedMessage = 'There was a problem communicating with your device.';
expect(
......
import $ from 'jquery';
import setWindowLocation from 'helpers/set_window_location_helper';
import waitForPromises from 'helpers/wait_for_promises';
import WebAuthnRegister from '~/authentication/webauthn/register';
import MockWebAuthnDevice from './mock_webauthn_device';
......@@ -50,17 +51,14 @@ describe('WebAuthnRegister', () => {
});
describe('when unsupported', () => {
const { location, PublicKeyCredential } = window;
const { PublicKeyCredential } = window;
beforeEach(() => {
delete window.location;
delete window.credentials;
window.location = {};
window.PublicKeyCredential = undefined;
});
afterEach(() => {
window.location = location;
window.PublicKeyCredential = PublicKeyCredential;
});
......@@ -69,7 +67,7 @@ describe('WebAuthnRegister', () => {
${false} | ${'WebAuthn only works with HTTPS-enabled websites'}
${true} | ${'Please use a supported browser, e.g. Chrome (67+) or Firefox'}
`('when https is $httpsEnabled', ({ httpsEnabled, expectedText }) => {
window.location.protocol = httpsEnabled ? 'https:' : 'http:';
setWindowLocation(`${httpsEnabled ? 'https:' : 'http:'}//localhost`);
component.start();
expect(findMessage().text()).toContain(expectedText);
......
import { GlLabel, GlLoadingIcon, GlTooltip } from '@gitlab/ui';
import { range } from 'lodash';
import Vuex from 'vuex';
import setWindowLocation from 'helpers/set_window_location_helper';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import BoardBlockedIcon from '~/boards/components/board_blocked_icon.vue';
import BoardCardInner from '~/boards/components/board_card_inner.vue';
......@@ -363,8 +364,6 @@ describe('Board card component', () => {
describe('filterByLabel method', () => {
beforeEach(() => {
delete window.location;
wrapper.setProps({
updateFilters: true,
});
......@@ -373,7 +372,7 @@ describe('Board card component', () => {
describe('when selected label is not in the filter', () => {
beforeEach(() => {
jest.spyOn(wrapper.vm, 'performSearch').mockImplementation(() => {});
window.location = { search: '' };
setWindowLocation('?');
wrapper.vm.filterByLabel(label1);
});
......@@ -394,7 +393,7 @@ describe('Board card component', () => {
describe('when selected label is already in the filter', () => {
beforeEach(() => {
jest.spyOn(wrapper.vm, 'performSearch').mockImplementation(() => {});
window.location = { search: '?label_name[]=testing%20123' };
setWindowLocation('?label_name[]=testing%20123');
wrapper.vm.filterByLabel(label1);
});
......
import { GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import setWindowLocation from 'helpers/set_window_location_helper';
import { TEST_HOST } from 'helpers/test_constants';
import waitForPromises from 'helpers/wait_for_promises';
......@@ -75,10 +76,6 @@ describe('BoardForm', () => {
});
};
beforeEach(() => {
delete window.location;
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
......@@ -244,7 +241,7 @@ describe('BoardForm', () => {
updateBoard: { board: { id: 'gid://gitlab/Board/321', webPath: 'test-path' } },
},
});
window.location = new URL('https://test/boards/1');
setWindowLocation('https://test/boards/1');
createComponent({ canAdminBoard: true, currentPage: formType.edit });
findInput().trigger('keyup.enter', { metaKey: true });
......@@ -270,7 +267,7 @@ describe('BoardForm', () => {
updateBoard: { board: { id: 'gid://gitlab/Board/321', webPath: 'test-path' } },
},
});
window.location = new URL('https://test/boards/1?group_by=epic');
setWindowLocation('https://test/boards/1?group_by=epic');
createComponent({ canAdminBoard: true, currentPage: formType.edit });
findInput().trigger('keyup.enter', { metaKey: true });
......
import MockAdapter from 'axios-mock-adapter';
import { loadHTMLFixture } from 'helpers/fixtures';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import { setTestTimeout } from 'helpers/timeout';
import Clusters from '~/clusters/clusters_bundle';
import axios from '~/lib/utils/axios_utils';
......@@ -8,6 +9,8 @@ import initProjectSelectDropdown from '~/project_select';
jest.mock('~/lib/utils/poll');
jest.mock('~/project_select');
useMockLocationHelper();
describe('Clusters', () => {
setTestTimeout(1000);
......@@ -55,20 +58,6 @@ describe('Clusters', () => {
});
describe('updateContainer', () => {
const { location } = window;
beforeEach(() => {
delete window.location;
window.location = {
reload: jest.fn(),
hash: location.hash,
};
});
afterEach(() => {
window.location = location;
});
describe('when creating cluster', () => {
it('should show the creating container', () => {
cluster.updateContainer(null, 'creating');
......
......@@ -4,6 +4,7 @@ import MockAdapter from 'axios-mock-adapter';
import Mousetrap from 'mousetrap';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import setWindowLocation from 'helpers/set_window_location_helper';
import { TEST_HOST } from 'spec/test_constants';
import App from '~/diffs/components/app.vue';
import CommitWidget from '~/diffs/components/commit_widget.vue';
......@@ -428,12 +429,10 @@ describe('diffs/components/app', () => {
jest.spyOn(wrapper.vm, 'refetchDiffData').mockImplementation(() => {});
jest.spyOn(wrapper.vm, 'adjustView').mockImplementation(() => {});
};
let location;
const location = window.location.href;
beforeAll(() => {
location = window.location;
delete window.location;
window.location = COMMIT_URL;
setWindowLocation(COMMIT_URL);
document.title = 'My Title';
});
......@@ -442,7 +441,7 @@ describe('diffs/components/app', () => {
});
afterAll(() => {
window.location = location;
setWindowLocation(location);
});
it('when the commit changes and the app is not loading it should update the history, refetch the diff data, and update the view', async () => {
......
......@@ -220,7 +220,7 @@ describe('CompareVersions', () => {
describe('prev commit', () => {
beforeAll(() => {
setWindowLocation(`${TEST_HOST}?commit_id=${mrCommit.id}`);
setWindowLocation(`?commit_id=${mrCommit.id}`);
});
beforeEach(() => {
......@@ -255,7 +255,7 @@ describe('CompareVersions', () => {
describe('next commit', () => {
beforeAll(() => {
setWindowLocation(`${TEST_HOST}?commit_id=${mrCommit.id}`);
setWindowLocation(`?commit_id=${mrCommit.id}`);
});
beforeEach(() => {
......
import { Range } from 'monaco-editor';
import { useFakeRequestAnimationFrame } from 'helpers/fake_request_animation_frame';
import setWindowLocation from 'helpers/set_window_location_helper';
import {
ERROR_INSTANCE_REQUIRED_FOR_EXTENSION,
EDITOR_TYPE_CODE,
......@@ -152,12 +153,7 @@ describe('The basis for an Source Editor extension', () => {
useFakeRequestAnimationFrame();
beforeEach(() => {
delete window.location;
window.location = new URL(`https://localhost`);
});
afterEach(() => {
window.location.hash = '';
setWindowLocation('https://localhost');
});
it.each`
......
import { GlFilteredSearchToken } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import setWindowLocation from 'helpers/set_window_location_helper';
import { redirectTo } from '~/lib/utils/url_utility';
import MembersFilteredSearchBar from '~/members/components/filter_sort/members_filtered_search_bar.vue';
import { MEMBER_TYPES } from '~/members/constants';
import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
jest.mock('~/lib/utils/url_utility', () => {
const urlUtility = jest.requireActual('~/lib/utils/url_utility');
return {
__esModule: true,
...urlUtility,
redirectTo: jest.fn(),
};
});
const localVue = createLocalVue();
localVue.use(Vuex);
......@@ -113,12 +125,11 @@ describe('MembersFilteredSearchBar', () => {
describe('when filters are set via query params', () => {
beforeEach(() => {
delete window.location;
window.location = new URL('https://localhost');
setWindowLocation('https://localhost');
});
it('parses and passes tokens to `FilteredSearchBar` component as `initialFilterValue` prop', () => {
window.location.search = '?two_factor=enabled&token_not_available=foobar';
setWindowLocation('?two_factor=enabled&token_not_available=foobar');
createComponent();
......@@ -134,7 +145,7 @@ describe('MembersFilteredSearchBar', () => {
});
it('parses and passes search param to `FilteredSearchBar` component as `initialFilterValue` prop', () => {
window.location.search = '?search=foobar';
setWindowLocation('?search=foobar');
createComponent();
......@@ -149,7 +160,7 @@ describe('MembersFilteredSearchBar', () => {
});
it('parses and passes search param with multiple words to `FilteredSearchBar` component as `initialFilterValue` prop', () => {
window.location.search = '?search=foo+bar+baz';
setWindowLocation('?search=foo+bar+baz');
createComponent();
......@@ -166,8 +177,7 @@ describe('MembersFilteredSearchBar', () => {
describe('when filter bar is submitted', () => {
beforeEach(() => {
delete window.location;
window.location = new URL('https://localhost');
setWindowLocation('https://localhost');
});
it('adds correct filter query params', () => {
......@@ -177,7 +187,7 @@ describe('MembersFilteredSearchBar', () => {
{ type: 'two_factor', value: { data: 'enabled', operator: '=' } },
]);
expect(window.location.href).toBe('https://localhost/?two_factor=enabled');
expect(redirectTo).toHaveBeenCalledWith('https://localhost/?two_factor=enabled');
});
it('adds search query param', () => {
......@@ -188,7 +198,9 @@ describe('MembersFilteredSearchBar', () => {
{ type: 'filtered-search-term', value: { data: 'foobar' } },
]);
expect(window.location.href).toBe('https://localhost/?two_factor=enabled&search=foobar');
expect(redirectTo).toHaveBeenCalledWith(
'https://localhost/?two_factor=enabled&search=foobar',
);
});
it('adds search query param with multiple words', () => {
......@@ -199,11 +211,13 @@ describe('MembersFilteredSearchBar', () => {
{ type: 'filtered-search-term', value: { data: 'foo bar baz' } },
]);
expect(window.location.href).toBe('https://localhost/?two_factor=enabled&search=foo+bar+baz');
expect(redirectTo).toHaveBeenCalledWith(
'https://localhost/?two_factor=enabled&search=foo+bar+baz',
);
});
it('adds sort query param', () => {
window.location.search = '?sort=name_asc';
setWindowLocation('?sort=name_asc');
createComponent();
......@@ -212,13 +226,13 @@ describe('MembersFilteredSearchBar', () => {
{ type: 'filtered-search-term', value: { data: 'foobar' } },
]);
expect(window.location.href).toBe(
expect(redirectTo).toHaveBeenCalledWith(
'https://localhost/?two_factor=enabled&search=foobar&sort=name_asc',
);
});
it('adds active tab query param', () => {
window.location.search = '?tab=invited';
setWindowLocation('?tab=invited');
createComponent();
......@@ -226,7 +240,7 @@ describe('MembersFilteredSearchBar', () => {
{ type: 'filtered-search-term', value: { data: 'foobar' } },
]);
expect(window.location.href).toBe('https://localhost/?search=foobar&tab=invited');
expect(redirectTo).toHaveBeenCalledWith('https://localhost/?search=foobar&tab=invited');
});
});
});
import { GlSorting, GlSortingItem } from '@gitlab/ui';
import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import setWindowLocation from 'helpers/set_window_location_helper';
import * as urlUtilities from '~/lib/utils/url_utility';
import SortDropdown from '~/members/components/filter_sort/sort_dropdown.vue';
import { MEMBER_TYPES } from '~/members/constants';
......@@ -52,17 +53,16 @@ describe('SortDropdown', () => {
.findAll(GlSortingItem)
.wrappers.find((dropdownItemWrapper) => dropdownItemWrapper.text() === text);
describe('dropdown options', () => {
beforeEach(() => {
delete window.location;
window.location = new URL(URL_HOST);
});
beforeEach(() => {
setWindowLocation(URL_HOST);
});
describe('dropdown options', () => {
it('adds dropdown items for all the sortable fields', () => {
const URL_FILTER_PARAMS = '?two_factor=enabled&search=foobar';
const EXPECTED_BASE_URL = `${URL_HOST}${URL_FILTER_PARAMS}&sort=`;
window.location.search = URL_FILTER_PARAMS;
setWindowLocation(URL_FILTER_PARAMS);
const expectedDropdownItems = [
{
......@@ -94,7 +94,7 @@ describe('SortDropdown', () => {
});
it('checks selected sort option', () => {
window.location.search = '?sort=access_level_asc';
setWindowLocation('?sort=access_level_asc');
createComponent();
......@@ -103,11 +103,6 @@ describe('SortDropdown', () => {
});
describe('dropdown toggle', () => {
beforeEach(() => {
delete window.location;
window.location = new URL(URL_HOST);
});
it('defaults to sorting by "Account" in ascending order', () => {
createComponent();
......@@ -116,7 +111,7 @@ describe('SortDropdown', () => {
});
it('sets text as selected sort option', () => {
window.location.search = '?sort=access_level_asc';
setWindowLocation('?sort=access_level_asc');
createComponent();
......@@ -126,15 +121,12 @@ describe('SortDropdown', () => {
describe('sort direction toggle', () => {
beforeEach(() => {
delete window.location;
window.location = new URL(URL_HOST);
jest.spyOn(urlUtilities, 'visitUrl');
jest.spyOn(urlUtilities, 'visitUrl').mockImplementation();
});
describe('when current sort direction is ascending', () => {
beforeEach(() => {
window.location.search = '?sort=access_level_asc';
setWindowLocation('?sort=access_level_asc');
createComponent();
});
......@@ -152,7 +144,7 @@ describe('SortDropdown', () => {
describe('when current sort direction is descending', () => {
beforeEach(() => {
window.location.search = '?sort=access_level_desc';
setWindowLocation('?sort=access_level_desc');
createComponent();
});
......
import { GlTabs } from '@gitlab/ui';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import setWindowLocation from 'helpers/set_window_location_helper';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import MembersApp from '~/members/components/app.vue';
import MembersTabs from '~/members/components/members_tabs.vue';
......@@ -90,8 +91,7 @@ describe('MembersTabs', () => {
const findActiveTab = () => wrapper.findByRole('tab', { selected: true });
beforeEach(() => {
delete window.location;
window.location = new URL('https://localhost');
setWindowLocation('https://localhost');
});
afterEach(() => {
......@@ -151,7 +151,7 @@ describe('MembersTabs', () => {
describe('when url param matches `filteredSearchBar.searchParam`', () => {
beforeEach(() => {
window.location.search = '?search_groups=foo+bar';
setWindowLocation('?search_groups=foo+bar');
});
it('shows tab that corresponds to search param', async () => {
......
......@@ -6,6 +6,7 @@ import {
} from '@testing-library/dom';
import { mount, createLocalVue, createWrapper } from '@vue/test-utils';
import Vuex from 'vuex';
import setWindowLocation from 'helpers/set_window_location_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import CreatedAt from '~/members/components/table/created_at.vue';
import ExpirationDatepicker from '~/members/components/table/expiration_datepicker.vue';
......@@ -243,12 +244,8 @@ describe('MembersTable', () => {
});
describe('when required pagination data is provided', () => {
beforeEach(() => {
delete window.location;
});
it('renders `gl-pagination` component with correct props', () => {
window.location = new URL(url);
setWindowLocation(url);
createComponent();
......@@ -268,7 +265,7 @@ describe('MembersTable', () => {
});
it('uses `pagination.paramName` to generate the pagination links', () => {
window.location = new URL(url);
setWindowLocation(url);
createComponent({
pagination: {
......@@ -283,7 +280,7 @@ describe('MembersTable', () => {
});
it('removes any url params defined as `null` in the `params` attribute', () => {
window.location = new URL(`${url}&search_groups=foo`);
setWindowLocation(`${url}&search_groups=foo`);
createComponent({
pagination: {
......
import setWindowLocation from 'helpers/set_window_location_helper';
import { DEFAULT_SORT, MEMBER_TYPES } from '~/members/constants';
import {
generateBadges,
......@@ -150,21 +151,18 @@ describe('Members Utils', () => {
describe('parseSortParam', () => {
beforeEach(() => {
delete window.location;
window.location = new URL(URL_HOST);
setWindowLocation(URL_HOST);
});
describe('when `sort` param is not present', () => {
it('returns default sort options', () => {
window.location.search = '';
expect(parseSortParam(['account'])).toEqual(DEFAULT_SORT);
});
});
describe('when field passed in `sortableFields` argument does not have `sort` key defined', () => {
it('returns default sort options', () => {
window.location.search = '?sort=source_asc';
setWindowLocation('?sort=source_asc');
expect(parseSortParam(['source'])).toEqual(DEFAULT_SORT);
});
......@@ -182,7 +180,7 @@ describe('Members Utils', () => {
${'oldest_sign_in'} | ${{ sortByKey: 'lastSignIn', sortDesc: true }}
`('when `sort` query string param is `$sortParam`', ({ sortParam, expected }) => {
it(`returns ${JSON.stringify(expected)}`, async () => {
window.location.search = `?sort=${sortParam}`;
setWindowLocation(`?sort=${sortParam}`);
expect(parseSortParam(['account', 'granted', 'expires', 'maxRole', 'lastSignIn'])).toEqual(
expected,
......@@ -193,8 +191,7 @@ describe('Members Utils', () => {
describe('buildSortHref', () => {
beforeEach(() => {
delete window.location;
window.location = new URL(URL_HOST);
setWindowLocation(URL_HOST);
});
describe('when field passed in `sortBy` argument does not have `sort` key defined', () => {
......@@ -225,7 +222,7 @@ describe('Members Utils', () => {
describe('when filter params are set', () => {
it('merges the `sort` param with the filter params', () => {
window.location.search = '?two_factor=enabled&with_inherited_permissions=exclude';
setWindowLocation('?two_factor=enabled&with_inherited_permissions=exclude');
expect(
buildSortHref({
......@@ -240,7 +237,7 @@ describe('Members Utils', () => {
describe('when search param is set', () => {
it('merges the `sort` param with the search param', () => {
window.location.search = '?search=foobar';
setWindowLocation('?search=foobar');
expect(
buildSortHref({
......
......@@ -301,9 +301,6 @@ describe('Actions menu', () => {
});
it('redirects to the newly created dashboard', () => {
delete window.location;
window.location = new URL('https://localhost');
const newDashboard = dashboardGitResponse[1];
const newDashboardUrl = 'root/sandbox/-/metrics/dashboard.yml';
......
import MockAdapter from 'axios-mock-adapter';
import VueDraggable from 'vuedraggable';
import setWindowLocation from 'helpers/set_window_location_helper';
import { TEST_HOST } from 'helpers/test_constants';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createFlash from '~/flash';
......@@ -226,32 +227,25 @@ describe('Dashboard', () => {
});
describe('when the URL contains a reference to a panel', () => {
let location;
const location = window.location.href;
const setSearch = (search) => {
window.location = { ...location, search };
const setSearch = (searchParams) => {
setWindowLocation(`?${objectToQuery(searchParams)}`);
};
beforeEach(() => {
location = window.location;
delete window.location;
});
afterEach(() => {
window.location = location;
setWindowLocation(location);
});
it('when the URL points to a panel it expands', () => {
const panelGroup = metricsDashboardViewModel.panelGroups[0];
const panel = panelGroup.panels[0];
setSearch(
objectToQuery({
group: panelGroup.group,
title: panel.title,
y_label: panel.y_label,
}),
);
setSearch({
group: panelGroup.group,
title: panel.title,
y_label: panel.y_label,
});
createMountedWrapper({ hasMetrics: true });
setupStoreWithData(store);
......@@ -268,7 +262,7 @@ describe('Dashboard', () => {
});
it('when the URL does not link to any panel, no panel is expanded', () => {
setSearch('');
setSearch();
createMountedWrapper({ hasMetrics: true });
setupStoreWithData(store);
......@@ -285,13 +279,11 @@ describe('Dashboard', () => {
const panelGroup = metricsDashboardViewModel.panelGroups[0];
const panel = panelGroup.panels[0];
setSearch(
objectToQuery({
group: panelGroup.group,
title: 'incorrect',
y_label: panel.y_label,
}),
);
setSearch({
group: panelGroup.group,
title: 'incorrect',
y_label: panel.y_label,
});
createMountedWrapper({ hasMetrics: true });
setupStoreWithData(store);
......
......@@ -2,6 +2,7 @@ import { GlEmptyState } from '@gitlab/ui';
import { mount, createLocalVue } from '@vue/test-utils';
import { nextTick } from 'vue';
import Vuex from 'vuex';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import stubChildren from 'helpers/stub_children';
import AdditionalMetadata from '~/packages/details/components/additional_metadata.vue';
......@@ -30,6 +31,8 @@ import {
const localVue = createLocalVue();
localVue.use(Vuex);
useMockLocationHelper();
describe('PackagesApp', () => {
let wrapper;
let store;
......@@ -37,7 +40,6 @@ describe('PackagesApp', () => {
const deletePackage = jest.fn();
const deletePackageFile = jest.fn();
const defaultProjectName = 'bar';
const { location } = window;
function createComponent({
packageEntity = mavenPackage,
......@@ -100,14 +102,8 @@ describe('PackagesApp', () => {
const findInstallationCommands = () => wrapper.find(InstallationCommands);
const findPackageFiles = () => wrapper.find(PackageFiles);
beforeEach(() => {
delete window.location;
window.location = { replace: jest.fn() };
});
afterEach(() => {
wrapper.destroy();
window.location = location;
});
it('renders the app and displays the package title', async () => {
......
import { GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import setWindowLocation from 'helpers/set_window_location_helper';
import createFlash from '~/flash';
import * as commonUtils from '~/lib/utils/common_utils';
import PackageListApp from '~/packages/list/components/packages_list_app.vue';
......@@ -233,21 +234,17 @@ describe('packages_list_app', () => {
});
describe('delete alert handling', () => {
const { location } = window.location;
const originalLocation = window.location.href;
const search = `?${SHOW_DELETE_SUCCESS_ALERT}=true`;
beforeEach(() => {
createStore();
jest.spyOn(commonUtils, 'historyReplaceState').mockImplementation(() => {});
delete window.location;
window.location = {
href: `foo_bar_baz${search}`,
search,
};
setWindowLocation(search);
});
afterEach(() => {
window.location = location;
setWindowLocation(originalLocation);
});
it(`creates a flash if the query string contains ${SHOW_DELETE_SUCCESS_ALERT}`, () => {
......@@ -262,11 +259,11 @@ describe('packages_list_app', () => {
it('calls historyReplaceState with a clean url', () => {
mountComponent();
expect(commonUtils.historyReplaceState).toHaveBeenCalledWith('foo_bar_baz');
expect(commonUtils.historyReplaceState).toHaveBeenCalledWith(originalLocation);
});
it(`does nothing if the query string does not contain ${SHOW_DELETE_SUCCESS_ALERT}`, () => {
window.location.search = '';
setWindowLocation('?');
mountComponent();
expect(createFlash).not.toHaveBeenCalled();
......
import MockAdapter from 'axios-mock-adapter';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
......@@ -166,6 +167,8 @@ describe('PersistentUserCallout', () => {
let mockAxios;
let persistentUserCallout;
useMockLocationHelper();
beforeEach(() => {
const fixture = createFollowLinkFixture();
const container = fixture.querySelector('.container');
......@@ -174,9 +177,6 @@ describe('PersistentUserCallout', () => {
persistentUserCallout = new PersistentUserCallout(container);
jest.spyOn(persistentUserCallout.container, 'remove').mockImplementation(() => {});
delete window.location;
window.location = { assign: jest.fn() };
});
afterEach(() => {
......
......@@ -2,6 +2,7 @@ import { GlAlert, GlButton, GlLoadingIcon, GlTabs } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import setWindowLocation from 'helpers/set_window_location_helper';
import waitForPromises from 'helpers/wait_for_promises';
import CommitForm from '~/pipeline_editor/components/commit/commit_form.vue';
import TextEditor from '~/pipeline_editor/components/editor/text_editor.vue';
......@@ -348,15 +349,14 @@ describe('Pipeline editor app component', () => {
});
describe('when a template parameter is present in the URL', () => {
const { location } = window;
const originalLocation = window.location.href;
beforeEach(() => {
delete window.location;
window.location = new URL('https://localhost?template=Android');
setWindowLocation('?template=Android');
});
afterEach(() => {
window.location = location;
setWindowLocation(originalLocation);
});
it('renders the given template', async () => {
......
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import createFlash from '~/flash';
import IntegrationView from '~/profile/preferences/components/integration_view.vue';
......@@ -19,6 +20,8 @@ import {
jest.mock('~/flash');
const expectedUrl = '/foo';
useMockLocationHelper();
describe('ProfilePreferences component', () => {
let wrapper;
const defaultProvide = {
......@@ -174,8 +177,6 @@ describe('ProfilePreferences component', () => {
});
describe('theme changes', () => {
const { location } = window;
let themeInput;
let form;
......@@ -197,18 +198,6 @@ describe('ProfilePreferences component', () => {
form.dispatchEvent(successEvent);
}
beforeAll(() => {
delete window.location;
window.location = {
...location,
reload: jest.fn(),
};
});
afterAll(() => {
window.location = location;
});
beforeEach(() => {
setupBody();
themeInput = createThemeInput();
......
import { GlButton, GlModal } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { ApolloMutation } from 'vue-apollo';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { Blob, BinaryBlob } from 'jest/blob/components/mock_data';
import { differenceInMilliseconds } from '~/lib/utils/datetime_utility';
import SnippetHeader from '~/snippets/components/snippet_header.vue';
import DeleteSnippetMutation from '~/snippets/mutations/deleteSnippet.mutation.graphql';
useMockLocationHelper();
describe('Snippet header component', () => {
let wrapper;
let snippet;
......@@ -200,19 +203,6 @@ describe('Snippet header component', () => {
});
describe('Delete mutation', () => {
const { location } = window;
beforeEach(() => {
delete window.location;
window.location = {
pathname: '',
};
});
afterEach(() => {
window.location = location;
});
it('dispatches a mutation to delete the snippet with correct variables', () => {
createComponent();
wrapper.vm.deleteSnippet();
......
......@@ -121,7 +121,6 @@ project_feature:
- project_id
- requirements_access_level
- security_and_compliance_access_level
- container_registry_access_level
- updated_at
computed_attributes:
- issues_enabled
......
......@@ -207,6 +207,18 @@ RSpec.describe API::Projects do
let(:current_user) { user }
end
it 'includes container_registry_access_level', :aggregate_failures do
project.project_feature.update!(container_registry_access_level: ProjectFeature::DISABLED)
get api('/projects', user)
project_response = json_response.find { |p| p['id'] == project.id }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_an Array
expect(project_response['container_registry_access_level']).to eq('disabled')
expect(project_response['container_registry_enabled']).to eq(false)
end
context 'when some projects are in a group' do
before do
create(:project, :public, group: create(:group))
......@@ -1042,7 +1054,7 @@ RSpec.describe API::Projects do
expect(response).to have_gitlab_http_status(:bad_request)
end
it "assigns attributes to project" do
it "assigns attributes to project", :aggregate_failures do
project = attributes_for(:project, {
path: 'camelCasePath',
issues_enabled: false,
......@@ -1064,6 +1076,7 @@ RSpec.describe API::Projects do
}).tap do |attrs|
attrs[:operations_access_level] = 'disabled'
attrs[:analytics_access_level] = 'disabled'
attrs[:container_registry_access_level] = 'private'
end
post api('/projects', user), params: project
......@@ -1071,7 +1084,10 @@ RSpec.describe API::Projects do
expect(response).to have_gitlab_http_status(:created)
project.each_pair do |k, v|
next if %i[has_external_issue_tracker has_external_wiki issues_enabled merge_requests_enabled wiki_enabled storage_version].include?(k)
next if %i[
has_external_issue_tracker has_external_wiki issues_enabled merge_requests_enabled wiki_enabled storage_version
container_registry_access_level
].include?(k)
expect(json_response[k.to_s]).to eq(v)
end
......@@ -1083,6 +1099,18 @@ RSpec.describe API::Projects do
expect(project.project_feature.wiki_access_level).to eq(ProjectFeature::DISABLED)
expect(project.operations_access_level).to eq(ProjectFeature::DISABLED)
expect(project.project_feature.analytics_access_level).to eq(ProjectFeature::DISABLED)
expect(project.project_feature.container_registry_access_level).to eq(ProjectFeature::PRIVATE)
end
it 'assigns container_registry_enabled to project', :aggregate_failures do
project = attributes_for(:project, { container_registry_enabled: true })
post api('/projects', user), params: project
expect(response).to have_gitlab_http_status(:created)
expect(json_response['container_registry_enabled']).to eq(true)
expect(json_response['container_registry_access_level']).to eq('enabled')
expect(Project.find_by(path: project[:path]).container_registry_access_level).to eq(ProjectFeature::ENABLED)
end
it 'creates a project using a template' do
......@@ -1340,6 +1368,14 @@ RSpec.describe API::Projects do
expect(json_response.map { |project| project['id'] }).to contain_exactly(public_project.id)
end
it 'includes container_registry_access_level', :aggregate_failures do
get api("/users/#{user4.id}/projects/", user)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_an Array
expect(json_response.first.keys).to include('container_registry_access_level')
end
context 'and using id_after' do
let!(:another_public_project) { create(:project, :public, name: 'another_public_project', creator_id: user4.id, namespace: user4.namespace) }
......@@ -1649,6 +1685,59 @@ RSpec.describe API::Projects do
expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_truthy
end
context 'container_registry_enabled' do
using RSpec::Parameterized::TableSyntax
where(:container_registry_enabled, :container_registry_access_level) do
true | ProjectFeature::ENABLED
false | ProjectFeature::DISABLED
end
with_them do
it 'setting container_registry_enabled also sets container_registry_access_level', :aggregate_failures do
project_attributes = attributes_for(:project).tap do |attrs|
attrs[:container_registry_enabled] = container_registry_enabled
end
post api("/projects/user/#{user.id}", admin), params: project_attributes
project = Project.find_by(path: project_attributes[:path])
expect(response).to have_gitlab_http_status(:created)
expect(json_response['container_registry_access_level']).to eq(ProjectFeature.str_from_access_level(container_registry_access_level))
expect(json_response['container_registry_enabled']).to eq(container_registry_enabled)
expect(project.container_registry_access_level).to eq(container_registry_access_level)
expect(project.container_registry_enabled).to eq(container_registry_enabled)
end
end
end
context 'container_registry_access_level' do
using RSpec::Parameterized::TableSyntax
where(:container_registry_access_level, :container_registry_enabled) do
'enabled' | true
'private' | true
'disabled' | false
end
with_them do
it 'setting container_registry_access_level also sets container_registry_enabled', :aggregate_failures do
project_attributes = attributes_for(:project).tap do |attrs|
attrs[:container_registry_access_level] = container_registry_access_level
end
post api("/projects/user/#{user.id}", admin), params: project_attributes
project = Project.find_by(path: project_attributes[:path])
expect(response).to have_gitlab_http_status(:created)
expect(json_response['container_registry_access_level']).to eq(container_registry_access_level)
expect(json_response['container_registry_enabled']).to eq(container_registry_enabled)
expect(project.container_registry_access_level).to eq(ProjectFeature.access_level_from_str(container_registry_access_level))
expect(project.container_registry_enabled).to eq(container_registry_enabled)
end
end
end
end
describe "POST /projects/:id/uploads/authorize" do
......@@ -2034,6 +2123,7 @@ RSpec.describe API::Projects do
expect(json_response['jobs_enabled']).to be_present
expect(json_response['snippets_enabled']).to be_present
expect(json_response['container_registry_enabled']).to be_present
expect(json_response['container_registry_access_level']).to be_present
expect(json_response['created_at']).to be_present
expect(json_response['last_activity_at']).to be_present
expect(json_response['shared_runners_enabled']).to be_present
......@@ -2125,6 +2215,7 @@ RSpec.describe API::Projects do
expect(json_response['resolve_outdated_diff_discussions']).to eq(project.resolve_outdated_diff_discussions)
expect(json_response['remove_source_branch_after_merge']).to be_truthy
expect(json_response['container_registry_enabled']).to be_present
expect(json_response['container_registry_access_level']).to be_present
expect(json_response['created_at']).to be_present
expect(json_response['last_activity_at']).to be_present
expect(json_response['shared_runners_enabled']).to be_present
......@@ -2951,6 +3042,14 @@ RSpec.describe API::Projects do
end
end
it 'sets container_registry_access_level', :aggregate_failures do
put api("/projects/#{project.id}", user), params: { container_registry_access_level: 'private' }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['container_registry_access_level']).to eq('private')
expect(Project.find_by(path: project[:path]).container_registry_access_level).to eq(ProjectFeature::PRIVATE)
end
it 'returns 400 when nothing sent' do
project_param = {}
......
......@@ -25,7 +25,7 @@ RSpec.describe 'groups/edit.html.haml' do
render
expect(rendered).to have_content("Prevent sharing a project within #{test_group.name} with other groups")
expect(rendered).to have_css('.js-descr', text: 'help text here')
expect(rendered).to have_content('help text here')
expect(rendered).to have_field('group_share_with_group_lock', **checkbox_options)
end
end
......
......@@ -25,7 +25,7 @@ RSpec.describe 'groups/edit.html.haml' do
render
expect(rendered).to have_content("Prevent sharing a project within #{test_group.name} with other groups")
expect(rendered).to have_css('.js-descr', text: 'help text here')
expect(rendered).to have_content('help text here')
expect(rendered).to have_field('group_share_with_group_lock', **checkbox_options)
end
end
......
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