Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-07-20 06:10:04 +00:00
parent 514ada7cc9
commit 026ef03e77
32 changed files with 258 additions and 140 deletions

View File

@ -10,7 +10,7 @@ class-methods-use-this */
deprecated_notes_spec.js is the spec for the legacy, jQuery notes application. It has nothing to do with the new, fancy Vue notes app.
*/
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import { GlSkeletonLoader } from '@gitlab/ui';
import Autosize from 'autosize';
import $ from 'jquery';
import { escape, uniqueId } from 'lodash';
@ -1233,10 +1233,10 @@ export default class Notes {
new Vue({
el,
components: {
GlSkeletonLoading,
GlSkeletonLoader,
},
render(createElement) {
return createElement('gl-skeleton-loading');
return createElement('gl-skeleton-loader');
},
});
}

View File

@ -11,7 +11,7 @@ import {
fromSearchToVariables,
isSearchFiltered,
} from 'ee_else_ce/runner/runner_search_utils';
import runnersAdminQuery from 'ee_else_ce/runner/graphql/list/admin_runners.query.graphql';
import allRunnersQuery from 'ee_else_ce/runner/graphql/list/all_runners.query.graphql';
import RegistrationDropdown from '../components/registration/registration_dropdown.vue';
import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.vue';
@ -64,7 +64,7 @@ export default {
},
apollo: {
runners: {
query: runnersAdminQuery,
query: allRunnersQuery,
fetchPolicy: fetchPolicies.NETWORK_ONLY,
variables() {
return this.variables;

View File

@ -1,7 +1,7 @@
<script>
import { fetchPolicies } from '~/lib/graphql';
import { captureException } from '../../sentry_utils';
import runnersAdminCountQuery from '../../graphql/list/admin_runners_count.query.graphql';
import allRunnersCountQuery from '../../graphql/list/all_runners_count.query.graphql';
import groupRunnersCountQuery from '../../graphql/list/group_runners_count.query.graphql';
import { INSTANCE_TYPE, GROUP_TYPE } from '../../constants';
@ -53,7 +53,7 @@ export default {
count: {
query() {
if (this.scope === INSTANCE_TYPE) {
return runnersAdminCountQuery;
return allRunnersCountQuery;
} else if (this.scope === GROUP_TYPE) {
return groupRunnersCountQuery;
}

View File

@ -1,7 +1,7 @@
#import "ee_else_ce/runner/graphql/list/list_item.fragment.graphql"
#import "~/graphql_shared/fragments/page_info.fragment.graphql"
query getRunners(
query getAllRunners(
$before: String
$after: String
$first: Int

View File

@ -1,4 +1,4 @@
query getRunnersCount(
query getAllRunnersCount(
$paused: Boolean
$status: CiRunnerStatus
$type: CiRunnerType

View File

@ -126,7 +126,7 @@ module GroupsHelper
group.root? && current_user.can?(:admin_setting_to_allow_project_access_token_creation, group)
end
def show_thanks_for_purchase_banner?
def show_thanks_for_purchase_alert?
params.key?(:purchased_quantity) && params[:purchased_quantity].to_i > 0
end

View File

@ -33,9 +33,12 @@ module WorkItems
end
def transaction_create(work_item)
super
execute_widgets(work_item: work_item, callback: :after_create_in_transaction, widget_params: @widget_params)
super.tap do |save_result|
if save_result
execute_widgets(work_item: work_item, callback: :after_create_in_transaction,
widget_params: @widget_params)
end
end
end
private

View File

@ -3,8 +3,8 @@
- @skip_current_level_breadcrumb = true
- add_page_specific_style 'page_bundles/group'
- if show_thanks_for_purchase_banner?
= render_if_exists 'shared/thanks_for_purchase_banner', plan_title: plan_title, quantity: params[:purchased_quantity].to_i
- if show_thanks_for_purchase_alert?
= render_if_exists 'shared/thanks_for_purchase_alert', plan_title: plan_title, quantity: params[:purchased_quantity].to_i
= render_if_exists 'shared/qrtly_reconciliation_alert', group: @group
= render_if_exists 'shared/free_user_cap_alert', source: @group

View File

@ -1,5 +1,5 @@
= form_for [@project, @trigger], html: { class: 'gl-show-field-errors' } do |f|
= form_errors(@trigger)
= form_errors(@trigger, pajamas_alert: true)
- if @trigger.token
.form-group

View File

@ -1,5 +1,5 @@
= gitlab_ui_form_for [@project.namespace, @project, @deploy_keys.new_key], url: namespace_project_deploy_keys_path, html: { class: "js-requires-input container" } do |f|
= form_errors(@deploy_keys.new_key)
= form_errors(@deploy_keys.new_key, pajamas_alert: true)
.form-group.row
= f.label :title, class: "label-bold"
= f.text_field :title, class: 'form-control gl-form-input', required: true, data: { qa_selector: 'deploy_key_title_field' }

View File

@ -651,8 +651,11 @@ to start again from scratch, there are a few steps that can help you:
1. Reset the Tracking Database.
WARNING:
If you skipped the optional step 3, be sure both `geo-postgresql` and `postgresql` services are running.
```shell
gitlab-rake db:drop:geo # on a secondary app node
gitlab-rake db:drop:geo DISABLE_DATABASE_ENVIRONMENT_CHECK=1 # on a secondary app node
gitlab-ctl reconfigure # on the tracking database node
gitlab-rake db:migrate:geo # on a secondary app node
```

View File

@ -502,6 +502,8 @@ Feedback is welcome on our vision for [unifying the user experience for these tw
## Troubleshooting
<!-- NOTE: The below subsection(`### Secure job failing with exit code 1`) documentation URL is referred in the [/gitlab-org/security-products/analyzers/command](https://gitlab.com/gitlab-org/security-products/analyzers/command/-/blob/main/command.go#L19) repository. If this section/subsection changes, please ensure to update the corresponding URL in the mentioned repository.
-->
### Secure job failing with exit code 1
If a Secure job is failing and it's unclear why, add `SECURE_LOG_LEVEL: "debug"` as a global CI/CD variable for

View File

@ -115,6 +115,15 @@ module API
redirect(download_path)
end
get do
latest_package = packages.order_version.last
render_api_error!({ error: "No version found for #{params[:module_name]} module" }, :not_found) if latest_package&.version.nil?
presenter = ::Terraform::ModuleVersionPresenter.new(latest_package, params[:module_system])
present presenter, with: ::API::Entities::Terraform::ModuleVersion
end
params do
includes :module_version
end

View File

@ -8,7 +8,7 @@ code_quality:
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: ""
CODE_QUALITY_IMAGE: "registry.gitlab.com/gitlab-org/ci-cd/codequality:0.85.26"
CODE_QUALITY_IMAGE: "registry.gitlab.com/gitlab-org/ci-cd/codequality:0.85.29"
needs: []
script:
- export SOURCE_CODE=$PWD

View File

@ -11454,9 +11454,6 @@ msgstr ""
msgid "CycleAnalyticsStage|should be under a group"
msgstr ""
msgid "CycleAnalytics|%{selectedLabelsCount} selected (%{maxLabels} max)"
msgstr ""
msgid "CycleAnalytics|'%{name}' is collecting the data. This can take a few minutes."
msgstr ""
@ -11481,9 +11478,6 @@ msgstr ""
msgid "CycleAnalytics|Date"
msgstr ""
msgid "CycleAnalytics|Display chart filters"
msgstr ""
msgid "CycleAnalytics|If you have recently upgraded to GitLab Premium, it can take up to 30 minutes for data to collect and display."
msgstr ""
@ -11493,20 +11487,11 @@ msgstr ""
msgid "CycleAnalytics|Number of tasks"
msgstr ""
msgid "CycleAnalytics|Only %{maxLabels} labels can be selected at this time"
msgstr ""
msgid "CycleAnalytics|Project selected"
msgid_plural "CycleAnalytics|%d projects selected"
msgstr[0] ""
msgstr[1] ""
msgid "CycleAnalytics|Select labels"
msgstr ""
msgid "CycleAnalytics|Show"
msgstr ""
msgid "CycleAnalytics|Stage time: %{title}"
msgstr ""
@ -36141,6 +36126,9 @@ msgstr ""
msgid "Showing version #%{versionNumber}"
msgstr ""
msgid "Shows issues and %{labels_count} label for group '%{group_name}' from Nov 1, 2019 to Dec 31, 2019"
msgstr ""
msgid "Shows issues and %{labels_count} labels for group '%{group_name}' from Nov 1, 2019 to Dec 31, 2019"
msgstr ""
@ -37596,9 +37584,6 @@ msgstr ""
msgid "Succeeded"
msgstr ""
msgid "Successful purchase image"
msgstr ""
msgid "Successfully activated"
msgstr ""

View File

@ -192,7 +192,7 @@ module QA
find_element(:commit_sha_content).text
end
def commit_changes(commit_message = nil, open_merge_request: false)
def commit_changes(commit_message = nil, open_merge_request: false, wait_for_success: true)
# Clicking :begin_commit_button switches from the
# edit to the commit view
click_element(:begin_commit_button)
@ -226,13 +226,17 @@ module QA
end
click_element(:commit_button) if has_element?(:commit_button)
break unless wait_for_success
# If this is the first commit, the commit SHA only appears after reloading
wait_until(reload: true) do
active_element?(:edit_mode_tab) && commit_sha != original_commit
end
end
raise "The changes do not appear to have been committed successfully." unless commit_success
if wait_for_success
raise "The changes do not appear to have been committed successfully." unless commit_success
end
end
end

View File

@ -0,0 +1,34 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Create', :skip_live_env, except: { job: 'review-qa-*' } do
describe 'Git Server Hooks' do
let(:file_path) { File.absolute_path(File.join('qa', 'fixtures', 'web_ide', 'README.md')) }
let(:project) do
Resource::Project.fabricate_via_api! do |project|
# Projects that have names that include pattern 'reject-prereceive' trigger a server hook on orchestrated env
# that returns an error string using GL-HOOK-ERR
project.name = "project-reject-prereceive-#{SecureRandom.hex(8)}"
end
end
before do
Flow::Login.sign_in
project.visit!
end
context 'Custom error messages' do
it 'renders preconfigured error message when user hook failed on commit in WebIDE',
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/364751' do
Page::Project::Show.perform(&:open_web_ide_via_shortcut)
Page::Project::WebIDE::Edit.perform do |ide|
ide.upload_file(file_path)
ide.commit_changes(wait_for_success: false)
expect(ide).to have_text('Custom error message rejecting prereceive hook for projects with GL_PROJECT_PATH')
end
end
end
end
end
end

View File

@ -22,7 +22,7 @@ RSpec.describe 'Group' do
end
describe 'as a non-admin' do
it 'creates a group and persists visibility radio selection', :js, :saas do
it 'creates a group and persists visibility radio selection', :js do
stub_application_setting(default_group_visibility: :private)
fill_in 'Group name', with: 'test-group'

View File

@ -39,19 +39,19 @@ RSpec.describe 'Runner (JavaScript fixtures)' do
end
describe GraphQL::Query, type: :request do
admin_runners_query = 'list/admin_runners.query.graphql'
all_runners_query = 'list/all_runners.query.graphql'
let_it_be(:query) do
get_graphql_query_as_string("#{query_path}#{admin_runners_query}")
get_graphql_query_as_string("#{query_path}#{all_runners_query}")
end
it "#{fixtures_path}#{admin_runners_query}.json" do
it "#{fixtures_path}#{all_runners_query}.json" do
post_graphql(query, current_user: admin, variables: {})
expect_graphql_errors_to_be_empty
end
it "#{fixtures_path}#{admin_runners_query}.paginated.json" do
it "#{fixtures_path}#{all_runners_query}.paginated.json" do
post_graphql(query, current_user: admin, variables: { first: 2 })
expect_graphql_errors_to_be_empty
@ -59,13 +59,13 @@ RSpec.describe 'Runner (JavaScript fixtures)' do
end
describe GraphQL::Query, type: :request do
admin_runners_count_query = 'list/admin_runners_count.query.graphql'
all_runners_count_query = 'list/all_runners_count.query.graphql'
let_it_be(:query) do
get_graphql_query_as_string("#{query_path}#{admin_runners_count_query}")
get_graphql_query_as_string("#{query_path}#{all_runners_count_query}")
end
it "#{fixtures_path}#{admin_runners_count_query}.json" do
it "#{fixtures_path}#{all_runners_count_query}.json" do
post_graphql(query, current_user: admin, variables: {})
expect_graphql_errors_to_be_empty

View File

@ -41,14 +41,14 @@ import {
STATUS_STALE,
RUNNER_PAGE_SIZE,
} from '~/runner/constants';
import adminRunnersQuery from 'ee_else_ce/runner/graphql/list/admin_runners.query.graphql';
import adminRunnersCountQuery from '~/runner/graphql/list/admin_runners_count.query.graphql';
import allRunnersQuery from 'ee_else_ce/runner/graphql/list/all_runners.query.graphql';
import allRunnersCountQuery from '~/runner/graphql/list/all_runners_count.query.graphql';
import { captureException } from '~/runner/sentry_utils';
import {
runnersData,
allRunnersData,
runnersCountData,
runnersDataPaginated,
allRunnersDataPaginated,
onlineContactTimeoutSecs,
staleTimeoutSecs,
emptyStateSvgPath,
@ -56,11 +56,11 @@ import {
} from '../mock_data';
const mockRegistrationToken = 'MOCK_REGISTRATION_TOKEN';
const mockRunners = runnersData.data.runners.nodes;
const mockRunners = allRunnersData.data.runners.nodes;
const mockRunnersCount = runnersCountData.data.runners.count;
const mockRunnersQuery = jest.fn();
const mockRunnersCountQuery = jest.fn();
const mockRunnersHandler = jest.fn();
const mockRunnersCountHandler = jest.fn();
jest.mock('~/flash');
jest.mock('~/runner/sentry_utils');
@ -96,8 +96,8 @@ describe('AdminRunnersApp', () => {
({ cacheConfig, localMutations } = createLocalState());
const handlers = [
[adminRunnersQuery, mockRunnersQuery],
[adminRunnersCountQuery, mockRunnersCountQuery],
[allRunnersQuery, mockRunnersHandler],
[allRunnersCountQuery, mockRunnersCountHandler],
];
wrapper = mountFn(AdminRunnersApp, {
@ -121,13 +121,13 @@ describe('AdminRunnersApp', () => {
};
beforeEach(() => {
mockRunnersQuery.mockResolvedValue(runnersData);
mockRunnersCountQuery.mockResolvedValue(runnersCountData);
mockRunnersHandler.mockResolvedValue(allRunnersData);
mockRunnersCountHandler.mockResolvedValue(runnersCountData);
});
afterEach(() => {
mockRunnersQuery.mockReset();
mockRunnersCountQuery.mockReset();
mockRunnersHandler.mockReset();
mockRunnersCountHandler.mockReset();
wrapper.destroy();
});
@ -149,9 +149,9 @@ describe('AdminRunnersApp', () => {
it('shows total runner counts', async () => {
await createComponent({ mountFn: mountExtended });
expect(mockRunnersCountQuery).toHaveBeenCalledWith({ status: STATUS_ONLINE });
expect(mockRunnersCountQuery).toHaveBeenCalledWith({ status: STATUS_OFFLINE });
expect(mockRunnersCountQuery).toHaveBeenCalledWith({ status: STATUS_STALE });
expect(mockRunnersCountHandler).toHaveBeenCalledWith({ status: STATUS_ONLINE });
expect(mockRunnersCountHandler).toHaveBeenCalledWith({ status: STATUS_OFFLINE });
expect(mockRunnersCountHandler).toHaveBeenCalledWith({ status: STATUS_STALE });
expect(findRunnerStats().text()).toContain(
`${s__('Runners|Online runners')} ${mockRunnersCount}`,
@ -197,7 +197,7 @@ describe('AdminRunnersApp', () => {
it('requests the runners with no filters', async () => {
await createComponent();
expect(mockRunnersQuery).toHaveBeenLastCalledWith({
expect(mockRunnersHandler).toHaveBeenLastCalledWith({
status: undefined,
type: undefined,
sort: DEFAULT_SORT,
@ -234,7 +234,7 @@ describe('AdminRunnersApp', () => {
const FILTERED_COUNT_QUERIES = 4; // Smart queries that display a count of runners in tabs
beforeEach(async () => {
mockRunnersCountQuery.mockClear();
mockRunnersCountHandler.mockClear();
await createComponent({ mountFn: mountExtended });
showToast = jest.spyOn(wrapper.vm.$root.$toast, 'show');
@ -248,11 +248,11 @@ describe('AdminRunnersApp', () => {
});
it('When runner is paused or unpaused, some data is refetched', async () => {
expect(mockRunnersCountQuery).toHaveBeenCalledTimes(COUNT_QUERIES);
expect(mockRunnersCountHandler).toHaveBeenCalledTimes(COUNT_QUERIES);
findRunnerActionsCell().vm.$emit('toggledPaused');
expect(mockRunnersCountQuery).toHaveBeenCalledTimes(COUNT_QUERIES + FILTERED_COUNT_QUERIES);
expect(mockRunnersCountHandler).toHaveBeenCalledTimes(COUNT_QUERIES + FILTERED_COUNT_QUERIES);
expect(showToast).toHaveBeenCalledTimes(0);
});
@ -289,7 +289,7 @@ describe('AdminRunnersApp', () => {
});
it('requests the runners with filter parameters', () => {
expect(mockRunnersQuery).toHaveBeenLastCalledWith({
expect(mockRunnersHandler).toHaveBeenLastCalledWith({
status: STATUS_ONLINE,
type: INSTANCE_TYPE,
tagList: ['tag1'],
@ -299,7 +299,7 @@ describe('AdminRunnersApp', () => {
});
it('fetches count results for requested status', () => {
expect(mockRunnersCountQuery).toHaveBeenCalledWith({
expect(mockRunnersCountHandler).toHaveBeenCalledWith({
type: INSTANCE_TYPE,
status: STATUS_ONLINE,
tagList: ['tag1'],
@ -334,7 +334,7 @@ describe('AdminRunnersApp', () => {
});
it('requests the runners with filters', () => {
expect(mockRunnersQuery).toHaveBeenLastCalledWith({
expect(mockRunnersHandler).toHaveBeenLastCalledWith({
status: STATUS_ONLINE,
tagList: ['tag1'],
sort: CREATED_ASC,
@ -343,7 +343,7 @@ describe('AdminRunnersApp', () => {
});
it('fetches count results for requested status', () => {
expect(mockRunnersCountQuery).toHaveBeenCalledWith({
expect(mockRunnersCountHandler).toHaveBeenCalledWith({
tagList: ['tag1'],
status: STATUS_ONLINE,
});
@ -392,7 +392,7 @@ describe('AdminRunnersApp', () => {
describe('when no runners are found', () => {
beforeEach(async () => {
mockRunnersQuery.mockResolvedValue({
mockRunnersHandler.mockResolvedValue({
data: {
runners: { nodes: [] },
},
@ -423,7 +423,7 @@ describe('AdminRunnersApp', () => {
describe('when runners query fails', () => {
beforeEach(async () => {
mockRunnersQuery.mockRejectedValue(new Error('Error!'));
mockRunnersHandler.mockRejectedValue(new Error('Error!'));
await createComponent();
});
@ -441,7 +441,7 @@ describe('AdminRunnersApp', () => {
describe('Pagination', () => {
beforeEach(async () => {
mockRunnersQuery.mockResolvedValue(runnersDataPaginated);
mockRunnersHandler.mockResolvedValue(allRunnersDataPaginated);
await createComponent({ mountFn: mountExtended });
});
@ -449,10 +449,10 @@ describe('AdminRunnersApp', () => {
it('navigates to the next page', async () => {
await findRunnerPaginationNext().trigger('click');
expect(mockRunnersQuery).toHaveBeenLastCalledWith({
expect(mockRunnersHandler).toHaveBeenLastCalledWith({
sort: CREATED_DESC,
first: RUNNER_PAGE_SIZE,
after: runnersDataPaginated.data.runners.pageInfo.endCursor,
after: allRunnersDataPaginated.data.runners.pageInfo.endCursor,
});
});
});

View File

@ -4,9 +4,9 @@ import RunnerActionsCell from '~/runner/components/cells/runner_actions_cell.vue
import RunnerPauseButton from '~/runner/components/runner_pause_button.vue';
import RunnerEditButton from '~/runner/components/runner_edit_button.vue';
import RunnerDeleteButton from '~/runner/components/runner_delete_button.vue';
import { runnersData } from '../../mock_data';
import { allRunnersData } from '../../mock_data';
const mockRunner = runnersData.data.runners.nodes[0];
const mockRunner = allRunnersData.data.runners.nodes[0];
describe('RunnerActionsCell', () => {
let wrapper;

View File

@ -17,9 +17,9 @@ import {
import RunnerDeleteButton from '~/runner/components/runner_delete_button.vue';
import RunnerDeleteModal from '~/runner/components/runner_delete_modal.vue';
import { runnersData } from '../mock_data';
import { allRunnersData } from '../mock_data';
const mockRunner = runnersData.data.runners.nodes[0];
const mockRunner = allRunnersData.data.runners.nodes[0];
const mockRunnerId = getIdFromGraphQLId(mockRunner.id);
Vue.use(VueApollo);

View File

@ -7,9 +7,9 @@ import {
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import RunnerList from '~/runner/components/runner_list.vue';
import RunnerStatusPopover from '~/runner/components/runner_status_popover.vue';
import { runnersData, onlineContactTimeoutSecs, staleTimeoutSecs } from '../mock_data';
import { allRunnersData, onlineContactTimeoutSecs, staleTimeoutSecs } from '../mock_data';
const mockRunners = runnersData.data.runners.nodes;
const mockRunners = allRunnersData.data.runners.nodes;
const mockActiveRunnersCount = mockRunners.length;
describe('RunnerList', () => {

View File

@ -16,9 +16,9 @@ import {
} from '~/runner/constants';
import RunnerPauseButton from '~/runner/components/runner_pause_button.vue';
import { runnersData } from '../mock_data';
import { allRunnersData } from '../mock_data';
const mockRunner = runnersData.data.runners.nodes[0];
const mockRunner = allRunnersData.data.runners.nodes[0];
Vue.use(VueApollo);

View File

@ -7,8 +7,8 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { captureException } from '~/runner/sentry_utils';
import adminRunnersCountQuery from '~/runner/graphql/list/admin_runners_count.query.graphql';
import getGroupRunnersCountQuery from '~/runner/graphql/list/group_runners_count.query.graphql';
import allRunnersCountQuery from '~/runner/graphql/list/all_runners_count.query.graphql';
import groupRunnersCountQuery from '~/runner/graphql/list/group_runners_count.query.graphql';
import { runnersCountData, groupRunnersCountData } from '../../mock_data';
@ -18,13 +18,13 @@ Vue.use(VueApollo);
describe('RunnerCount', () => {
let wrapper;
let mockRunnersCountQuery;
let mockGroupRunnersCountQuery;
let mockRunnersCountHandler;
let mockGroupRunnersCountHandler;
const createComponent = ({ props = {}, ...options } = {}) => {
const handlers = [
[adminRunnersCountQuery, mockRunnersCountQuery],
[getGroupRunnersCountQuery, mockGroupRunnersCountQuery],
[allRunnersCountQuery, mockRunnersCountHandler],
[groupRunnersCountQuery, mockGroupRunnersCountHandler],
];
wrapper = shallowMount(RunnerCount, {
@ -42,8 +42,8 @@ describe('RunnerCount', () => {
};
beforeEach(() => {
mockRunnersCountQuery = jest.fn().mockResolvedValue(runnersCountData);
mockGroupRunnersCountQuery = jest.fn().mockResolvedValue(groupRunnersCountData);
mockRunnersCountHandler = jest.fn().mockResolvedValue(runnersCountData);
mockGroupRunnersCountHandler = jest.fn().mockResolvedValue(groupRunnersCountData);
});
describe('in admin scope', () => {
@ -54,21 +54,21 @@ describe('RunnerCount', () => {
});
it('fetches data from admin query', () => {
expect(mockRunnersCountQuery).toHaveBeenCalledTimes(1);
expect(mockRunnersCountQuery).toHaveBeenCalledWith({});
expect(mockRunnersCountHandler).toHaveBeenCalledTimes(1);
expect(mockRunnersCountHandler).toHaveBeenCalledWith({});
});
it('fetches data with filters', async () => {
await createComponent({ props: { scope: INSTANCE_TYPE, variables: mockVariables } });
expect(mockRunnersCountQuery).toHaveBeenCalledTimes(2);
expect(mockRunnersCountQuery).toHaveBeenCalledWith(mockVariables);
expect(mockRunnersCountHandler).toHaveBeenCalledTimes(2);
expect(mockRunnersCountHandler).toHaveBeenCalledWith(mockVariables);
expect(wrapper.html()).toBe(`<strong>${runnersCountData.data.runners.count}</strong>`);
});
it('does not fetch from the group query', async () => {
expect(mockGroupRunnersCountQuery).not.toHaveBeenCalled();
expect(mockGroupRunnersCountHandler).not.toHaveBeenCalled();
});
describe('when this query is skipped after data was loaded', () => {
@ -90,8 +90,8 @@ describe('RunnerCount', () => {
});
it('does not fetch data', async () => {
expect(mockRunnersCountQuery).not.toHaveBeenCalled();
expect(mockGroupRunnersCountQuery).not.toHaveBeenCalled();
expect(mockRunnersCountHandler).not.toHaveBeenCalled();
expect(mockGroupRunnersCountHandler).not.toHaveBeenCalled();
expect(wrapper.html()).toBe('<strong></strong>');
});
@ -101,7 +101,7 @@ describe('RunnerCount', () => {
const mockError = new Error('error!');
beforeEach(async () => {
mockRunnersCountQuery.mockRejectedValue(mockError);
mockRunnersCountHandler.mockRejectedValue(mockError);
await createComponent({ props: { scope: INSTANCE_TYPE } });
});
@ -122,8 +122,8 @@ describe('RunnerCount', () => {
});
it('fetches data from the group query', async () => {
expect(mockGroupRunnersCountQuery).toHaveBeenCalledTimes(1);
expect(mockGroupRunnersCountQuery).toHaveBeenCalledWith({});
expect(mockGroupRunnersCountHandler).toHaveBeenCalledTimes(1);
expect(mockGroupRunnersCountHandler).toHaveBeenCalledWith({});
expect(wrapper.html()).toBe(
`<strong>${groupRunnersCountData.data.group.runners.count}</strong>`,
@ -131,7 +131,7 @@ describe('RunnerCount', () => {
});
it('does not fetch from the group query', () => {
expect(mockRunnersCountQuery).not.toHaveBeenCalled();
expect(mockRunnersCountHandler).not.toHaveBeenCalled();
});
});
@ -142,7 +142,7 @@ describe('RunnerCount', () => {
});
it('data is not shown and error is reported', async () => {
expect(mockRunnersCountQuery).toHaveBeenCalledTimes(2);
expect(mockRunnersCountHandler).toHaveBeenCalledTimes(2);
});
});
});

View File

@ -39,8 +39,8 @@ import {
RUNNER_PAGE_SIZE,
I18N_EDIT,
} from '~/runner/constants';
import getGroupRunnersQuery from '~/runner/graphql/list/group_runners.query.graphql';
import getGroupRunnersCountQuery from '~/runner/graphql/list/group_runners_count.query.graphql';
import groupRunnersQuery from '~/runner/graphql/list/group_runners.query.graphql';
import groupRunnersCountQuery from '~/runner/graphql/list/group_runners_count.query.graphql';
import GroupRunnersApp from '~/runner/group_runners/group_runners_app.vue';
import { captureException } from '~/runner/sentry_utils';
import {
@ -61,8 +61,8 @@ const mockRegistrationToken = 'AABBCC';
const mockGroupRunnersEdges = groupRunnersData.data.group.runners.edges;
const mockGroupRunnersCount = mockGroupRunnersEdges.length;
const mockGroupRunnersQuery = jest.fn();
const mockGroupRunnersCountQuery = jest.fn();
const mockGroupRunnersHandler = jest.fn();
const mockGroupRunnersCountHandler = jest.fn();
jest.mock('~/flash');
jest.mock('~/runner/sentry_utils');
@ -87,8 +87,8 @@ describe('GroupRunnersApp', () => {
const createComponent = ({ props = {}, mountFn = shallowMountExtended, ...options } = {}) => {
const handlers = [
[getGroupRunnersQuery, mockGroupRunnersQuery],
[getGroupRunnersCountQuery, mockGroupRunnersCountQuery],
[groupRunnersQuery, mockGroupRunnersHandler],
[groupRunnersCountQuery, mockGroupRunnersCountHandler],
];
wrapper = mountFn(GroupRunnersApp, {
@ -112,13 +112,13 @@ describe('GroupRunnersApp', () => {
};
beforeEach(async () => {
mockGroupRunnersQuery.mockResolvedValue(groupRunnersData);
mockGroupRunnersCountQuery.mockResolvedValue(groupRunnersCountData);
mockGroupRunnersHandler.mockResolvedValue(groupRunnersData);
mockGroupRunnersCountHandler.mockResolvedValue(groupRunnersCountData);
});
afterEach(() => {
mockGroupRunnersQuery.mockReset();
mockGroupRunnersCountQuery.mockReset();
mockGroupRunnersHandler.mockReset();
mockGroupRunnersCountHandler.mockReset();
wrapper.destroy();
});
@ -140,15 +140,15 @@ describe('GroupRunnersApp', () => {
it('shows total runner counts', async () => {
await createComponent({ mountFn: mountExtended });
expect(mockGroupRunnersCountQuery).toHaveBeenCalledWith({
expect(mockGroupRunnersCountHandler).toHaveBeenCalledWith({
status: STATUS_ONLINE,
groupFullPath: mockGroupFullPath,
});
expect(mockGroupRunnersCountQuery).toHaveBeenCalledWith({
expect(mockGroupRunnersCountHandler).toHaveBeenCalledWith({
status: STATUS_OFFLINE,
groupFullPath: mockGroupFullPath,
});
expect(mockGroupRunnersCountQuery).toHaveBeenCalledWith({
expect(mockGroupRunnersCountHandler).toHaveBeenCalledWith({
status: STATUS_STALE,
groupFullPath: mockGroupFullPath,
});
@ -174,7 +174,7 @@ describe('GroupRunnersApp', () => {
it('requests the runners with group path and no other filters', async () => {
await createComponent();
expect(mockGroupRunnersQuery).toHaveBeenLastCalledWith({
expect(mockGroupRunnersHandler).toHaveBeenLastCalledWith({
groupFullPath: mockGroupFullPath,
status: undefined,
type: undefined,
@ -231,11 +231,11 @@ describe('GroupRunnersApp', () => {
});
it('When runner is paused or unpaused, some data is refetched', async () => {
expect(mockGroupRunnersCountQuery).toHaveBeenCalledTimes(COUNT_QUERIES);
expect(mockGroupRunnersCountHandler).toHaveBeenCalledTimes(COUNT_QUERIES);
findRunnerActionsCell().vm.$emit('toggledPaused');
expect(mockGroupRunnersCountQuery).toHaveBeenCalledTimes(
expect(mockGroupRunnersCountHandler).toHaveBeenCalledTimes(
COUNT_QUERIES + FILTERED_COUNT_QUERIES,
);
@ -272,7 +272,7 @@ describe('GroupRunnersApp', () => {
});
it('requests the runners with filter parameters', () => {
expect(mockGroupRunnersQuery).toHaveBeenLastCalledWith({
expect(mockGroupRunnersHandler).toHaveBeenLastCalledWith({
groupFullPath: mockGroupFullPath,
status: STATUS_ONLINE,
type: INSTANCE_TYPE,
@ -282,7 +282,7 @@ describe('GroupRunnersApp', () => {
});
it('fetches count results for requested status', () => {
expect(mockGroupRunnersCountQuery).toHaveBeenCalledWith({
expect(mockGroupRunnersCountHandler).toHaveBeenCalledWith({
groupFullPath: mockGroupFullPath,
type: INSTANCE_TYPE,
status: STATUS_ONLINE,
@ -319,7 +319,7 @@ describe('GroupRunnersApp', () => {
});
it('requests the runners with filters', () => {
expect(mockGroupRunnersQuery).toHaveBeenLastCalledWith({
expect(mockGroupRunnersHandler).toHaveBeenLastCalledWith({
groupFullPath: mockGroupFullPath,
status: STATUS_ONLINE,
tagList: ['tag1'],
@ -329,7 +329,7 @@ describe('GroupRunnersApp', () => {
});
it('fetches count results for requested status', () => {
expect(mockGroupRunnersCountQuery).toHaveBeenCalledWith({
expect(mockGroupRunnersCountHandler).toHaveBeenCalledWith({
groupFullPath: mockGroupFullPath,
tagList: ['tag1'],
status: STATUS_ONLINE,
@ -344,7 +344,7 @@ describe('GroupRunnersApp', () => {
describe('when no runners are found', () => {
beforeEach(async () => {
mockGroupRunnersQuery.mockResolvedValue({
mockGroupRunnersHandler.mockResolvedValue({
data: {
group: {
id: '1',
@ -362,7 +362,7 @@ describe('GroupRunnersApp', () => {
describe('when runners query fails', () => {
beforeEach(async () => {
mockGroupRunnersQuery.mockRejectedValue(new Error('Error!'));
mockGroupRunnersHandler.mockRejectedValue(new Error('Error!'));
await createComponent();
});
@ -380,7 +380,7 @@ describe('GroupRunnersApp', () => {
describe('Pagination', () => {
beforeEach(async () => {
mockGroupRunnersQuery.mockResolvedValue(groupRunnersDataPaginated);
mockGroupRunnersHandler.mockResolvedValue(groupRunnersDataPaginated);
await createComponent({ mountFn: mountExtended });
});
@ -388,7 +388,7 @@ describe('GroupRunnersApp', () => {
it('navigates to the next page', async () => {
await findRunnerPaginationNext().trigger('click');
expect(mockGroupRunnersQuery).toHaveBeenLastCalledWith({
expect(mockGroupRunnersHandler).toHaveBeenLastCalledWith({
groupFullPath: mockGroupFullPath,
sort: CREATED_DESC,
first: RUNNER_PAGE_SIZE,

View File

@ -10,9 +10,9 @@ import runnerJobsData from 'test_fixtures/graphql/runner/show/runner_jobs.query.
import runnerFormData from 'test_fixtures/graphql/runner/edit/runner_form.query.graphql.json';
// List queries
import runnersData from 'test_fixtures/graphql/runner/list/admin_runners.query.graphql.json';
import runnersDataPaginated from 'test_fixtures/graphql/runner/list/admin_runners.query.graphql.paginated.json';
import runnersCountData from 'test_fixtures/graphql/runner/list/admin_runners_count.query.graphql.json';
import allRunnersData from 'test_fixtures/graphql/runner/list/all_runners.query.graphql.json';
import allRunnersDataPaginated from 'test_fixtures/graphql/runner/list/all_runners.query.graphql.paginated.json';
import runnersCountData from 'test_fixtures/graphql/runner/list/all_runners_count.query.graphql.json';
import groupRunnersData from 'test_fixtures/graphql/runner/list/group_runners.query.graphql.json';
import groupRunnersDataPaginated from 'test_fixtures/graphql/runner/list/group_runners.query.graphql.paginated.json';
import groupRunnersCountData from 'test_fixtures/graphql/runner/list/group_runners_count.query.graphql.json';
@ -227,8 +227,8 @@ export const emptyStateSvgPath = 'emptyStateSvgPath.svg';
export const emptyStateFilteredSvgPath = 'emptyStateFilteredSvgPath.svg';
export {
runnersData,
runnersDataPaginated,
allRunnersData,
allRunnersDataPaginated,
runnersCountData,
groupRunnersData,
groupRunnersDataPaginated,

View File

@ -355,8 +355,8 @@ RSpec.describe GroupsHelper do
end
end
describe '#show_thanks_for_purchase_banner?' do
subject { helper.show_thanks_for_purchase_banner? }
describe '#show_thanks_for_purchase_alert?' do
subject { helper.show_thanks_for_purchase_alert? }
it 'returns true with purchased_quantity present in params' do
allow(controller).to receive(:params) { { purchased_quantity: '1' } }

View File

@ -51,7 +51,7 @@ RSpec.describe StorageHelper do
end
end
describe "storage_enforcement_banner", :saas do
describe "storage_enforcement_banner" do
let_it_be_with_refind(:current_user) { create(:user) }
let_it_be(:free_group) { create(:group) }
let_it_be(:paid_group) { create(:group) }

View File

@ -84,7 +84,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
end
end
it 'respects the defined payload schema', :saas do
it 'respects the defined payload schema' do
expect(::Gitlab::HTTP).to receive(:post) do |_url, params|
expect(params[:body]).to match_schema('/external_validation')
expect(params[:timeout]).to eq(described_class::DEFAULT_VALIDATION_REQUEST_TIMEOUT)

View File

@ -100,7 +100,7 @@ RSpec.describe API::Terraform::Modules::V1::Packages do
describe 'GET /api/v4/packages/terraform/modules/v1/:module_namespace/:module_name/:module_system/download' do
context 'empty registry' do
let(:url) { api("/packages/terraform/modules/v1/module-2/system/download") }
let(:url) { api("/packages/terraform/modules/v1/#{group.path}/module-2/system/download") }
let(:headers) { {} }
subject { get(url, headers: headers) }
@ -171,6 +171,74 @@ RSpec.describe API::Terraform::Modules::V1::Packages do
end
end
describe 'GET /api/v4/packages/terraform/modules/v1/:module_namespace/:module_name/:module_system' do
context 'empty registry' do
let(:url) { api("/packages/terraform/modules/v1/#{group.path}/non-existent/system") }
let(:headers) { { 'Authorization' => "Bearer #{tokens[:personal_access_token]}" } }
subject { get(url, headers: headers) }
it 'returns not found when there is no module' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'with valid namespace' do
let(:url) { api("/packages/terraform/modules/v1/#{group.path}/#{package.name}") }
subject { get(url, headers: headers) }
where(:visibility, :user_role, :member, :token_type, :shared_examples_name, :expected_status) do
:public | :developer | true | :personal_access_token | 'returns terraform module version' | :success
:public | :guest | true | :personal_access_token | 'returns terraform module version' | :success
:public | :developer | true | :invalid | 'rejects terraform module packages access' | :unauthorized
:public | :guest | true | :invalid | 'rejects terraform module packages access' | :unauthorized
:public | :developer | false | :personal_access_token | 'returns terraform module version' | :success
:public | :guest | false | :personal_access_token | 'returns terraform module version' | :success
:public | :developer | false | :invalid | 'rejects terraform module packages access' | :unauthorized
:public | :guest | false | :invalid | 'rejects terraform module packages access' | :unauthorized
:public | :anonymous | false | nil | 'returns terraform module version' | :success
:private | :developer | true | :personal_access_token | 'returns terraform module version' | :success
:private | :guest | true | :personal_access_token | 'rejects terraform module packages access' | :forbidden
:private | :developer | true | :invalid | 'rejects terraform module packages access' | :unauthorized
:private | :guest | true | :invalid | 'rejects terraform module packages access' | :unauthorized
:private | :developer | false | :personal_access_token | 'rejects terraform module packages access' | :forbidden
:private | :guest | false | :personal_access_token | 'rejects terraform module packages access' | :forbidden
:private | :developer | false | :invalid | 'rejects terraform module packages access' | :unauthorized
:private | :guest | false | :invalid | 'rejects terraform module packages access' | :unauthorized
:private | :anonymous | false | nil | 'rejects terraform module packages access' | :unauthorized
:public | :developer | true | :job_token | 'returns terraform module version' | :success
:public | :guest | true | :job_token | 'returns terraform module version' | :success
:public | :guest | true | :invalid | 'rejects terraform module packages access' | :unauthorized
:public | :developer | false | :job_token | 'returns terraform module version' | :success
:public | :guest | false | :job_token | 'returns terraform module version' | :success
:public | :developer | false | :invalid | 'rejects terraform module packages access' | :unauthorized
:public | :guest | false | :invalid | 'rejects terraform module packages access' | :unauthorized
:private | :developer | true | :job_token | 'returns terraform module version' | :success
:private | :guest | true | :job_token | 'rejects terraform module packages access' | :forbidden
:private | :developer | true | :invalid | 'rejects terraform module packages access' | :unauthorized
:private | :guest | true | :invalid | 'rejects terraform module packages access' | :unauthorized
:private | :developer | false | :job_token | 'rejects terraform module packages access' | :forbidden
:private | :guest | false | :job_token | 'rejects terraform module packages access' | :forbidden
:private | :developer | false | :invalid | 'rejects terraform module packages access' | :unauthorized
:private | :guest | false | :invalid | 'rejects terraform module packages access' | :unauthorized
end
with_them do
let(:headers) { user_role == :anonymous ? {} : { 'Authorization' => "Bearer #{token}" } }
before do
group.update!(visibility: visibility.to_s)
project.update!(visibility: visibility.to_s)
end
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
end
end
end
describe 'GET /api/v4/packages/terraform/modules/v1/:module_namespace/:module_name/:module_system/:module_version' do
let(:url) { api("/packages/terraform/modules/v1/#{group.path}/#{package.name}/#{package.version}") }
let(:headers) { {} }

View File

@ -27,16 +27,18 @@ RSpec.describe WorkItems::CreateService do
end
describe '#execute' do
subject(:service_result) do
let(:service) do
described_class.new(
project: project,
current_user: current_user,
params: opts,
spam_params: spam_params,
widget_params: widget_params
).execute
)
end
subject(:service_result) { service.execute }
before do
stub_spam_services
end
@ -73,6 +75,14 @@ RSpec.describe WorkItems::CreateService do
it 'returns validation errors' do
expect(service_result.errors).to contain_exactly("Title can't be blank")
end
it 'does not execute after-create transaction widgets' do
expect(service).to receive(:create).and_call_original
expect(service).not_to receive(:execute_widgets)
.with(callback: :after_create_in_transaction, widget_params: widget_params)
service_result
end
end
context 'checking spam' do