Commit 72f9f0d8 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 2f429887 1cec26c7
// NOTE: This module will be used in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52044
import { memoize } from 'lodash';
export const RECAPTCHA_API_URL_PREFIX = 'https://www.google.com/recaptcha/api.js';
/**
* The name which will be used for the reCAPTCHA script's onload callback
*/
export const RECAPTCHA_ONLOAD_CALLBACK_NAME = 'recaptchaOnloadCallback';
/**
* Adds the Google reCAPTCHA script tag to the head of the document, and
* returns a promise of the grecaptcha object
* (https://developers.google.com/recaptcha/docs/display#js_api).
*
* It is memoized, so there will only be one instance of the script tag ever
* added to the document.
*
* See the reCAPTCHA documentation for more details:
*
* https://developers.google.com/recaptcha/docs/display#explicit_render
*
*/
export const initRecaptchaScript = memoize(() => {
/**
* Appends the the reCAPTCHA script tag to the head of document
*/
const appendRecaptchaScript = () => {
const script = document.createElement('script');
script.src = `${RECAPTCHA_API_URL_PREFIX}?onload=${RECAPTCHA_ONLOAD_CALLBACK_NAME}&render=explicit`;
script.classList.add('js-recaptcha-script');
document.head.appendChild(script);
};
/**
* Returns a Promise which is fulfilled after the reCAPTCHA script is loaded
*/
return new Promise((resolve) => {
window[RECAPTCHA_ONLOAD_CALLBACK_NAME] = resolve;
appendRecaptchaScript();
});
});
/**
* Clears the cached memoization of the default manager.
*
* This is needed for determinism in tests.
*/
export const clearMemoizeCache = () => {
initRecaptchaScript.cache.clear();
};
......@@ -81,7 +81,7 @@ export default {
v-gl-tooltip
type="button"
:disabled="isLoading"
class="dropdown-new btn btn-default js-pipeline-dropdown-manual-actions"
class="dropdown-new gl-button btn btn-default js-pipeline-dropdown-manual-actions"
:title="__('Run manual or delayed jobs')"
data-toggle="dropdown"
:aria-label="__('Run manual or delayed jobs')"
......
......@@ -50,10 +50,10 @@
.table-action-buttons
.btn-group
- if can?(current_user, :read_build, @project)
= link_to download_project_job_artifacts_path(@project, artifact.job), rel: 'nofollow', download: '', title: _('Download artifacts'), data: { placement: 'top', container: 'body' }, ref: 'tooltip', aria: { label: _('Download artifacts') }, class: 'gl-button btn btn-build has-tooltip ml-0' do
= link_to download_project_job_artifacts_path(@project, artifact.job), rel: 'nofollow', download: '', title: _('Download artifacts'), data: { placement: 'top', container: 'body' }, ref: 'tooltip', aria: { label: _('Download artifacts') }, class: 'gl-button btn btn-default btn-build has-tooltip ml-0' do
= sprite_icon('download')
= link_to browse_project_job_artifacts_path(@project, artifact.job), rel: 'nofollow', title: _('Browse artifacts'), data: { placement: 'top', container: 'body' }, ref: 'tooltip', aria: { label: _('Browse artifacts') }, class: 'gl-button btn btn-build has-tooltip' do
= link_to browse_project_job_artifacts_path(@project, artifact.job), rel: 'nofollow', title: _('Browse artifacts'), data: { placement: 'top', container: 'body' }, ref: 'tooltip', aria: { label: _('Browse artifacts') }, class: 'gl-button btn btn-default btn-build has-tooltip' do
= sprite_icon('folder-open')
- if can?(current_user, :destroy_artifacts, @project)
......
......@@ -99,11 +99,11 @@
%td
.gl-display-flex
- if can?(current_user, :read_job_artifacts, job) && job.artifacts?
= link_to download_project_job_artifacts_path(job.project, job), rel: 'nofollow', download: '', title: _('Download artifacts'), class: 'btn btn-build gl-button btn-icon btn-svg' do
= link_to download_project_job_artifacts_path(job.project, job), rel: 'nofollow', download: '', title: _('Download artifacts'), class: 'btn btn-build btn-default gl-button btn-icon btn-svg' do
= sprite_icon('download')
- if can?(current_user, :update_build, job)
- if job.active?
= link_to cancel_project_job_path(job.project, job, continue: { to: request.fullpath }), method: :post, title: _('Cancel'), class: 'btn gl-button btn-build' do
= link_to cancel_project_job_path(job.project, job, continue: { to: request.fullpath }), method: :post, title: _('Cancel'), class: 'btn gl-button btn-build btn-default' do
= sprite_icon('close')
- elsif job.scheduled?
.btn-group
......@@ -125,7 +125,7 @@
= sprite_icon('time-out')
- elsif allow_retry
- if job.playable? && !admin && can?(current_user, :update_build, job)
= link_to play_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: _('Play'), class: 'btn gl-button btn-build' do
= link_to play_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: _('Play'), class: 'btn gl-button btn-build btn-default' do
= custom_icon('icon_play')
- elsif job.retryable?
= link_to retry_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: _('Retry'), class: 'btn btn-build gl-button btn-icon btn-default' do
......
---
title: Allow cross-origin requests on /oauth/token
merge_request: 52641
author:
type: fixed
---
title: Add btn-default class for btn-build buttons
merge_request: 52093
author: Yogi (@yo)
type: other
......@@ -290,6 +290,14 @@ module Gitlab
methods: :any,
expose: headers_to_expose
end
# Cross-origin requests must be enabled for the Authorization code with PKCE OAuth flow when used from a browser.
allow do
origins '*'
resource '/oauth/token',
credentials: false,
methods: [:post]
end
end
# Use caching across all environments
......
---
name: usage_data_i_testing_group_code_coverage_project_click_total
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/51411
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/299893
milestone: '13.8'
type: development
group: group::testing
default_enabled: true
......@@ -145,6 +145,13 @@ Note the following when promoting a secondary:
a point-in-time recovery to the last known state.
Data that was created on the primary while the secondary was paused will be lost.
NOTE:
In GitLab 13.7 and earlier, if you have a data type with zero items to sync,
this command reports `ERROR - Replication is not up-to-date` even if
replication is actually up-to-date. If replication and verification output
shows that it is complete, you can add `--skip-preflight-checks` to make the
command complete promotion. This bug was fixed in GitLab 13.8 and later.
To promote the secondary node to primary along with preflight checks:
```shell
......
......@@ -45,6 +45,12 @@ be found in `/var/opt/gitlab/gitlab-rails/shared/pages` if using Omnibus).
## Preflight checks
NOTE:
In GitLab 13.7 and earlier, if you have a data type with zero items to sync,
this command reports `ERROR - Replication is not up-to-date` even if
replication is actually up-to-date. This bug was fixed in GitLab 13.8 and
later.
Run this command to list out all preflight checks and automatically check if replication and verification are complete before scheduling a planned failover to ensure the process will go smoothly:
```shell
......
......@@ -233,12 +233,25 @@ To promote the secondary node:
check if replication and verification are complete before scheduling a planned
failover to ensure the process will go smoothly:
NOTE:
In GitLab 13.7 and earlier, if you have a data type with zero items to sync,
this command reports `ERROR - Replication is not up-to-date` even if
replication is actually up-to-date. This bug was fixed in GitLab 13.8 and
later.
```shell
gitlab-ctl promotion-preflight-checks
```
1. Promote the **secondary**:
NOTE:
In GitLab 13.7 and earlier, if you have a data type with zero items to sync,
this command reports `ERROR - Replication is not up-to-date` even if
replication is actually up-to-date. If replication and verification output
shows that it is complete, you can add `--skip-preflight-checks` to make the
command complete promotion. This bug was fixed in GitLab 13.8 and later.
```shell
gitlab-ctl promote-to-primary-node
```
......
......@@ -678,6 +678,20 @@ sudo /opt/gitlab/embedded/bin/gitlab-pg-ctl promote
GitLab 12.9 and later are [unaffected by this error](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5147).
### Message: `ERROR - Replication is not up-to-date` during `gitlab-ctl promotion-preflight-checks`
In GitLab 13.7 and earlier, if you have a data type with zero items to sync,
this command reports `ERROR - Replication is not up-to-date` even if
replication is actually up-to-date. This bug was fixed in GitLab 13.8 and
later.
### Message: `ERROR - Replication is not up-to-date` during `gitlab-ctl promote-to-primary-node`
In GitLab 13.7 and earlier, if you have a data type with zero items to sync,
this command reports `ERROR - Replication is not up-to-date` even if
replication is actually up-to-date. If replication and verification output
shows that it is complete, you can add `--skip-preflight-checks` to make the command complete promotion. This bug was fixed in GitLab 13.8 and later.
## Expired artifacts
If you notice for some reason there are more artifacts on the Geo
......
<script>
import Vue from 'vue';
import { GlCard, GlEmptyState, GlLink, GlSkeletonLoader, GlTable } from '@gitlab/ui';
import api from '~/api';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { __, s__ } from '~/locale';
import { joinPaths } from '~/lib/utils/url_utility';
import { SUPPORTED_FORMATS, getFormatter } from '~/lib/utils/unit_format';
......@@ -19,6 +21,7 @@ export default {
SelectProjectsDropdown,
TimeAgoTooltip,
},
mixins: [glFeatureFlagsMixin()],
apollo: {
projects: {
query: getProjectsTestCoverage,
......@@ -98,6 +101,11 @@ export default {
handleError() {
this.hasError = true;
},
onProjectClick() {
if (this.glFeatures.usageDataITestingGroupCodeCoverageProjectClickTotal) {
api.trackRedisHllUserEvent(this.$options.usagePingProjectEvent);
}
},
selectAllProjects(allProjects) {
this.projectIds = Object.fromEntries(allProjects.map(({ id }) => [id, true]));
this.allProjectsSelected = true;
......@@ -154,6 +162,7 @@ export default {
totalHeight: 15,
},
averageCoverageFormatter: getFormatter(SUPPORTED_FORMATS.percentHundred),
usagePingProjectEvent: 'i_testing_group_code_coverage_project_click_total',
};
</script>
<template>
......@@ -211,7 +220,12 @@ export default {
</template>
<template #cell(project)="{ item }">
<gl-link target="_blank" :href="item.codeCoveragePath" :data-testid="`${item.id}-name`">
<gl-link
target="_blank"
:href="item.codeCoveragePath"
:data-testid="`${item.id}-name`"
@click.once="onProjectClick"
>
{{ item.name }}
</gl-link>
</template>
......
import { EMPTY_BODY_MESSAGE } from './constants';
export const bodyWithFallBack = (body) => body || EMPTY_BODY_MESSAGE;
/**
* A helper function which validates the passed
* in body string.
*
* It returns an empty string if the body has explicitly
* been passed in as an empty string, a fallback
* message if the body is null / undefined, else
* it will return the original body string.
*
* @param {String} body the body message
*
* @return {String} the validated body message
*/
export const bodyWithFallBack = (body) => (body === '' ? '' : body || EMPTY_BODY_MESSAGE);
......@@ -7,6 +7,9 @@ class Groups::Analytics::RepositoryAnalyticsController < Groups::Analytics::Appl
before_action :load_group
before_action -> { check_feature_availability!(:group_repository_analytics) }
before_action -> { authorize_view_by_action!(:read_group_repository_analytics) }
before_action only: [:show] do
push_frontend_feature_flag(:usage_data_i_testing_group_code_coverage_project_click_total, @group, default_enabled: :yaml)
end
track_redis_hll_event :show, name: 'i_testing_group_code_coverage_visit_total', feature: :usage_data_i_testing_group_code_coverage_visit_total, feature_default_enabled: true
def show
......
......@@ -3,7 +3,7 @@
- recreate_index_url = help_page_url('integration/elasticsearch.md')
- recreate_index_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: recreate_index_url }
- recreate_index_text = _("Changes won't take place until the index is %{link_start}recreated%{link_end}.").html_safe % { link_start: recreate_index_link_start, link_end: '</a>'.html_safe }
- elasticsearch_available = Gitlab::Elastic::Helper.default.client.ping
- elasticsearch_available = Gitlab::Elastic::Helper.default.ping?
%section.settings.expanded.as-elasticsearch.no-animate#js-elasticsearch-settings{ data: { qa_selector: 'elasticsearch_tab' } }
.settings-header
......
......@@ -10,8 +10,19 @@ module Security
::Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline|
break unless pipeline.can_store_security_reports?
record_onboarding_progress(pipeline)
Security::StoreScansService.execute(pipeline)
end
end
private
def record_onboarding_progress(pipeline)
# We only record SAST scans since it's a Free feature and available to all users
return unless pipeline.security_scans.sast.any?
OnboardingProgressService.new(pipeline.project.namespace).execute(action: :security_scan_enabled)
end
end
end
---
title: Capture metrics for group coverage project links
merge_request: 51411
author:
type: added
---
title: Show Response fields for vulnerabilties sourced from DAST
merge_request: 51948
author:
type: fixed
---
title: Handle network unreachable for ES settings check
merge_request: 52586
author:
type: fixed
......@@ -294,6 +294,13 @@ module Gitlab
end
end
# handles unreachable hosts and any other exceptions that may be raised
def ping?
client.ping
rescue
false
end
private
def additional_index_options
......
......@@ -6,6 +6,9 @@ import getProjectsTestCoverage from 'ee/analytics/repository_analytics/graphql/q
import { useFakeDate } from 'helpers/fake_date';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import Api from '~/api';
jest.mock('~/api.js');
const localVue = createLocalVue();
......@@ -23,6 +26,20 @@ describe('Test coverage table component', () => {
const findProjectCountById = (id) => wrapper.find(`[data-testid="${id}-count"`);
const findProjectDateById = (id) => wrapper.find(`[data-testid="${id}-date"`);
const mockQueryDataNode = {
fullPath: 'test/test',
name: 'test',
id: 1,
repository: {
rootRef: 'master',
},
codeCoverageSummary: {
averageCoverage: '1.45',
coverageCount: '1',
lastUpdatedOn: new Date().toISOString(),
},
};
const createComponent = ({ data = {}, mountFn = shallowMount } = {}) => {
wrapper = mountFn(TestCoverageTable, {
localVue,
......@@ -52,6 +69,7 @@ describe('Test coverage table component', () => {
data = {},
mountFn = shallowMount,
queryData = {},
glFeatures = {},
} = {}) => {
localVue.use(VueApollo);
fakeApollo = createMockApollo([
......@@ -72,6 +90,9 @@ describe('Test coverage table component', () => {
};
},
apolloProvider: fakeApollo,
provide: {
glFeatures,
},
});
};
......@@ -144,26 +165,19 @@ describe('Test coverage table component', () => {
yesterday.setDate(yesterday.getDate() - 1);
const allCoverageData = [
{
fullPath: '-',
id: 1,
...mockQueryDataNode,
name: 'should be last',
repository: { rootRef: 'master' },
codeCoveragePath: '#',
codeCoverageSummary: {
averageCoverage: '1.45',
coverageCount: '1',
...mockQueryDataNode.codeCoverageSummary,
lastUpdatedOn: yesterday.toISOString(),
},
},
{
fullPath: '-',
id: 2,
...mockQueryDataNode,
name: 'should be first',
repository: { rootRef: 'master' },
codeCoveragePath: '#',
id: 2,
codeCoverageSummary: {
averageCoverage: '1.45',
coverageCount: '1',
...mockQueryDataNode.codeCoverageSummary,
lastUpdatedOn: today.toISOString(),
},
},
......@@ -198,17 +212,12 @@ describe('Test coverage table component', () => {
projects: {
nodes: [
{
...mockQueryDataNode,
fullPath,
name: 'test',
id,
repository: {
rootRef,
},
codeCoverageSummary: {
averageCoverage: '1.45',
coverageCount: '1',
lastUpdatedOn: new Date().toISOString(),
},
},
],
},
......@@ -222,6 +231,77 @@ describe('Test coverage table component', () => {
expect(findTable().exists()).toBe(true);
expect(findProjectNameById(id).attributes('href')).toBe(expectedPath);
});
describe('with usage metrics', () => {
describe('with :usageDataITestingGroupCodeCoverageProjectClickTotal enabled', () => {
it('tracks i_testing_group_code_coverage_project_click_total metric', async () => {
const id = 1;
createComponentWithApollo({
data: {
projectIds: { [id]: true },
},
queryData: {
data: {
projects: {
nodes: [
{
...mockQueryDataNode,
id,
},
],
},
},
},
mountFn: mount,
glFeatures: { usageDataITestingGroupCodeCoverageProjectClickTotal: true },
});
// We have to wait for apollo to make the mock query and fill the table before
// we can click on the project link inside the table. Neither `runOnlyPendingTimers`
// nor `waitForPromises` work on their own to accomplish this.
jest.runOnlyPendingTimers();
await waitForPromises();
findProjectNameById(id).trigger('click');
expect(Api.trackRedisHllUserEvent).toHaveBeenCalledTimes(1);
expect(Api.trackRedisHllUserEvent).toHaveBeenCalledWith(
wrapper.vm.$options.usagePingProjectEvent,
);
});
});
describe('with :usageDataITestingGroupCodeCoverageProjectClickTotal disabled', () => {
it('does not track i_testing_group_code_coverage_project_click_total metric', async () => {
const id = 1;
createComponentWithApollo({
data: {
projectIds: { [id]: true },
},
queryData: {
data: {
projects: {
nodes: [
{
...mockQueryDataNode,
id,
},
],
},
},
},
mountFn: mount,
glFeatures: { usageDataITestingGroupCodeCoverageProjectClickTotal: false },
});
// We have to wait for apollo to make the mock query and fill the table before
// we can click on the project link inside the table. Neither `runOnlyPendingTimers`
// nor `waitForPromises` work on their own to accomplish this.
jest.runOnlyPendingTimers();
await waitForPromises();
findProjectNameById(id).trigger('click');
expect(Api.trackRedisHllUserEvent).not.toHaveBeenCalled();
});
});
});
});
describe('when selected project has no coverage', () => {
......@@ -236,12 +316,8 @@ describe('Test coverage table component', () => {
projects: {
nodes: [
{
fullPath: 'test/test',
name: 'test',
...mockQueryDataNode,
id,
repository: {
rootRef: 'master',
},
codeCoverageSummary: null,
},
],
......
......@@ -174,7 +174,9 @@ describe('VulnerabilityDetails component', () => {
});
describe.each([
['', EMPTY_BODY_MESSAGE],
['', ''],
[undefined, EMPTY_BODY_MESSAGE],
[null, EMPTY_BODY_MESSAGE],
[USER_NOT_FOUND_MESSAGE, USER_NOT_FOUND_MESSAGE],
])('with request information and body set to: %s', (body, renderedBody) => {
let vulnerability;
......@@ -234,7 +236,9 @@ describe('VulnerabilityDetails component', () => {
});
describe.each([
['', EMPTY_BODY_MESSAGE],
['', ''],
[undefined, EMPTY_BODY_MESSAGE],
[null, EMPTY_BODY_MESSAGE],
[USER_NOT_FOUND_MESSAGE, USER_NOT_FOUND_MESSAGE],
])('with response information and body set to: %s', (body, renderedBody) => {
let vulnerability;
......@@ -281,7 +285,9 @@ describe('VulnerabilityDetails component', () => {
});
describe.each([
['', EMPTY_BODY_MESSAGE],
['', ''],
[undefined, EMPTY_BODY_MESSAGE],
[null, EMPTY_BODY_MESSAGE],
[USER_NOT_FOUND_MESSAGE, USER_NOT_FOUND_MESSAGE],
])('with recorded response information and body set to: %s', (body, renderedBody) => {
let vulnerability;
......
......@@ -242,6 +242,12 @@ describe('Vulnerability Details', () => {
isCode: true,
};
const EXPECT_REQUEST_WITH_EMPTY_STRING = {
label: 'Sent request:',
content: 'GET http://www.gitlab.com\nName1: Value1\nName2: Value2',
isCode: true,
};
const EXPECT_RESPONSE = {
label: 'Actual response:',
content: '500 INTERNAL SERVER ERROR\nName1: Value1\nName2: Value2\n\n[{"user_id":1,}]',
......@@ -255,6 +261,12 @@ describe('Vulnerability Details', () => {
isCode: true,
};
const EXPECT_RESPONSE_WITH_EMPTY_STRING = {
label: 'Actual response:',
content: '500 INTERNAL SERVER ERROR\nName1: Value1\nName2: Value2',
isCode: true,
};
const EXPECT_RECORDED_RESPONSE = {
label: 'Unmodified response:',
content: '200 OK\nName1: Value1\nName2: Value2\n\n[{"user_id":1,}]',
......@@ -267,6 +279,12 @@ describe('Vulnerability Details', () => {
isCode: true,
};
const EXPECT_RECORDED_RESPONSE_WITH_EMPTY_STRING = {
label: 'Unmodified response:',
content: '200 OK\nName1: Value1\nName2: Value2',
isCode: true,
};
const getTextContent = (el) => el.textContent.trim();
const getLabel = (el) => getTextContent(getByTestId(el, 'label'));
const getContent = (el) => getTextContent(getByTestId(el, 'value'));
......@@ -293,7 +311,9 @@ describe('Vulnerability Details', () => {
${{ method: 'GET', url: 'http://www.gitlab.com' }} | ${null}
${{ method: 'GET', url: 'http://www.gitlab.com', body: '[{"user_id":1,}]' }} | ${null}
${{ headers: TEST_HEADERS, method: 'GET', url: 'http://www.gitlab.com', body: '[{"user_id":1,}]' }} | ${[EXPECT_REQUEST]}
${{ headers: TEST_HEADERS, method: 'GET', url: 'http://www.gitlab.com', body: '' }} | ${[EXPECT_REQUEST_WITHOUT_BODY]}
${{ headers: TEST_HEADERS, method: 'GET', url: 'http://www.gitlab.com', body: null }} | ${[EXPECT_REQUEST_WITHOUT_BODY]}
${{ headers: TEST_HEADERS, method: 'GET', url: 'http://www.gitlab.com', body: undefined }} | ${[EXPECT_REQUEST_WITHOUT_BODY]}
${{ headers: TEST_HEADERS, method: 'GET', url: 'http://www.gitlab.com', body: '' }} | ${[EXPECT_REQUEST_WITH_EMPTY_STRING]}
`('shows request data for $request', ({ request, expectedData }) => {
createWrapper({ request });
expect(getSectionData('request')).toEqual(expectedData);
......@@ -307,7 +327,9 @@ describe('Vulnerability Details', () => {
${{ headers: TEST_HEADERS, body: '[{"user_id":1,}]' }} | ${null}
${{ headers: TEST_HEADERS, body: '[{"user_id":1,}]', statusCode: '500' }} | ${null}
${{ headers: TEST_HEADERS, body: '[{"user_id":1,}]', statusCode: '500', reasonPhrase: 'INTERNAL SERVER ERROR' }} | ${[EXPECT_RESPONSE]}
${{ headers: TEST_HEADERS, body: '', statusCode: '500', reasonPhrase: 'INTERNAL SERVER ERROR' }} | ${[EXPECT_RESPONSE_WITHOUT_BODY]}
${{ headers: TEST_HEADERS, body: null, statusCode: '500', reasonPhrase: 'INTERNAL SERVER ERROR' }} | ${[EXPECT_RESPONSE_WITHOUT_BODY]}
${{ headers: TEST_HEADERS, body: undefined, statusCode: '500', reasonPhrase: 'INTERNAL SERVER ERROR' }} | ${[EXPECT_RESPONSE_WITHOUT_BODY]}
${{ headers: TEST_HEADERS, body: '', statusCode: '500', reasonPhrase: 'INTERNAL SERVER ERROR' }} | ${[EXPECT_RESPONSE_WITH_EMPTY_STRING]}
`('shows response data for $response', ({ response, expectedData }) => {
createWrapper({ response });
expect(getSectionData('response')).toEqual(expectedData);
......@@ -324,7 +346,9 @@ describe('Vulnerability Details', () => {
${[{}, { response: { headers: TEST_HEADERS, body: '[{"user_id":1,}]', status_code: '200' } }]} | ${null}
${[{}, { response: { headers: TEST_HEADERS, body: '[{"user_id":1,}]', status_code: '200', reason_phrase: 'OK' } }]} | ${null}
${[{}, { name: SUPPORTING_MESSAGE_TYPES.RECORDED, response: { headers: TEST_HEADERS, body: '[{"user_id":1,}]', statusCode: '200', reasonPhrase: 'OK' } }]} | ${[EXPECT_RECORDED_RESPONSE]}
${[{}, { name: SUPPORTING_MESSAGE_TYPES.RECORDED, response: { headers: TEST_HEADERS, body: '', statusCode: '200', reasonPhrase: 'OK' } }]} | ${[EXPECT_RECORDED_RESPONSE_WITHOUT_BODY]}
${[{}, { name: SUPPORTING_MESSAGE_TYPES.RECORDED, response: { headers: TEST_HEADERS, body: null, statusCode: '200', reasonPhrase: 'OK' } }]} | ${[EXPECT_RECORDED_RESPONSE_WITHOUT_BODY]}
${[{}, { name: SUPPORTING_MESSAGE_TYPES.RECORDED, response: { headers: TEST_HEADERS, body: undefined, statusCode: '200', reasonPhrase: 'OK' } }]} | ${[EXPECT_RECORDED_RESPONSE_WITHOUT_BODY]}
${[{}, { name: SUPPORTING_MESSAGE_TYPES.RECORDED, response: { headers: TEST_HEADERS, body: '', statusCode: '200', reasonPhrase: 'OK' } }]} | ${[EXPECT_RECORDED_RESPONSE_WITH_EMPTY_STRING]}
`('shows response data for $supporting_messages', ({ supportingMessages, expectedData }) => {
createWrapper({ supportingMessages });
expect(getSectionData('recorded-response')).toEqual(expectedData);
......
......@@ -359,4 +359,15 @@ RSpec.describe Gitlab::Elastic::Helper do
end
end
end
describe '#ping?' do
subject { helper.ping? }
it 'does not raise any exception' do
allow(Gitlab::Elastic::Helper.default.client).to receive(:ping).and_raise(StandardError)
expect(subject).to be_falsey
expect { subject }.not_to raise_exception
end
end
end
......@@ -218,5 +218,18 @@ RSpec.describe 'admin/application_settings/_elasticsearch_form' do
expect(rendered).to include('Retry migration')
end
end
context 'when elasticsearch is unreachable' do
before do
allow(Gitlab::Elastic::Helper.default).to receive(:ping?).and_return(false)
end
it 'does not show the retry migration card' do
render
expect(rendered).not_to include('There is a halted Elasticsearch migration')
expect(rendered).not_to include('Retry migration')
end
end
end
end
......@@ -4,11 +4,11 @@ require 'spec_helper'
RSpec.describe Security::StoreScansWorker do
let_it_be(:sast_scan) { create(:security_scan, scan_type: :sast) }
let_it_be(:sast_pipeline) { sast_scan.pipeline }
let_it_be(:sast_build) { sast_pipeline.security_scans.sast.last&.build }
let_it_be(:pipeline) { sast_scan.pipeline }
let_it_be(:sast_build) { pipeline.security_scans.sast.last&.build }
describe '#perform' do
subject(:run_worker) { described_class.new.perform(sast_pipeline.id) }
subject(:run_worker) { described_class.new.perform(pipeline.id) }
before do
allow(Security::StoreScansService).to receive(:execute)
......@@ -25,6 +25,8 @@ RSpec.describe Security::StoreScansWorker do
expect(Security::StoreScansService).not_to have_received(:execute)
end
it_behaves_like 'does not record an onboarding progress action'
end
context 'when security reports can be stored for the pipeline' do
......@@ -35,6 +37,18 @@ RSpec.describe Security::StoreScansWorker do
expect(Security::StoreScansService).to have_received(:execute)
end
it_behaves_like 'records an onboarding progress action', :security_scan_enabled do
let(:namespace) { pipeline.project.namespace }
end
context 'dast scan' do
let_it_be(:dast_scan) { create(:security_scan, scan_type: :dast) }
let_it_be(:pipeline) { dast_scan.pipeline }
let_it_be(:dast_build) { pipeline.security_scans.dast.last&.build }
it_behaves_like 'does not record an onboarding progress action'
end
end
end
end
......@@ -268,6 +268,11 @@
redis_slot: testing
aggregation: weekly
feature_flag: usage_data_i_testing_web_performance_widget_total
- name: i_testing_group_code_coverage_project_click_total
category: testing
redis_slot: testing
aggregation: weekly
feature_flag: usage_data_i_testing_group_code_coverage_project_click_total
# Project Management group
- name: g_project_management_issue_title_changed
category: issues_edit
......
import {
RECAPTCHA_API_URL_PREFIX,
RECAPTCHA_ONLOAD_CALLBACK_NAME,
clearMemoizeCache,
initRecaptchaScript,
} from '~/captcha/init_recaptcha_script';
describe('initRecaptchaScript', () => {
afterEach(() => {
// NOTE: The DOM is guaranteed to be clean at the start of a new test file, but it isn't cleaned
// between examples within a file, so we need to clean it after each one. See more context here:
// - https://github.com/facebook/jest/issues/1224
// - https://stackoverflow.com/questions/42805128/does-jest-reset-the-jsdom-document-after-every-suite-or-test
//
// Also note as mentioned in https://github.com/facebook/jest/issues/1224#issuecomment-444586798
// that properties of `window` are NOT cleared between test files. So, we are also
// explicitly unsetting it.
document.head.innerHTML = '';
window[RECAPTCHA_ONLOAD_CALLBACK_NAME] = undefined;
clearMemoizeCache();
});
const triggerScriptOnload = (...args) => window[RECAPTCHA_ONLOAD_CALLBACK_NAME](...args);
describe('when called', () => {
let result;
beforeEach(() => {
result = initRecaptchaScript();
});
it('adds script to head', () => {
expect(document.head).toMatchInlineSnapshot(`
<head>
<script
class="js-recaptcha-script"
src="${RECAPTCHA_API_URL_PREFIX}?onload=${RECAPTCHA_ONLOAD_CALLBACK_NAME}&render=explicit"
/>
</head>
`);
});
it('is memoized', () => {
expect(initRecaptchaScript()).toBe(result);
expect(document.head.querySelectorAll('script').length).toBe(1);
});
it('when onload is triggered, resolves promise', async () => {
const instance = {};
triggerScriptOnload(instance);
await expect(result).resolves.toBe(instance);
});
});
});
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Oauth::TokensController do
it 'allows cross-origin POST requests' do
post '/oauth/token', headers: { 'Origin' => 'http://notgitlab.com' }
expect(response.headers['Access-Control-Allow-Origin']).to eq '*'
expect(response.headers['Access-Control-Allow-Methods']).to eq 'POST'
expect(response.headers['Access-Control-Allow-Headers']).to be_nil
expect(response.headers['Access-Control-Allow-Credentials']).to be_nil
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