Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/qa.gitlab-ci.yml5
-rw-r--r--app/assets/javascripts/google_cloud/components/app.vue59
-rw-r--r--app/assets/javascripts/google_cloud/components/home.vue41
-rw-r--r--app/assets/javascripts/google_cloud/components/screens/app.vue50
-rw-r--r--app/assets/javascripts/google_cloud/components/service_accounts_form.vue (renamed from app/assets/javascripts/google_cloud/components/screens/service_accounts_form.vue)13
-rw-r--r--app/assets/javascripts/google_cloud/index.js44
-rw-r--r--app/assets/javascripts/integrations/constants.js1
-rw-r--r--app/assets/javascripts/integrations/edit/api.js9
-rw-r--r--app/assets/javascripts/integrations/edit/components/integration_form.vue10
-rw-r--r--app/assets/javascripts/integrations/edit/components/jira_issues_fields.vue10
-rw-r--r--app/assets/javascripts/integrations/edit/store/actions.js27
-rw-r--r--app/assets/javascripts/integrations/integration_settings_form.js40
-rw-r--r--app/controllers/projects/google_cloud/base_controller.rb2
-rw-r--r--app/controllers/projects/google_cloud/service_accounts_controller.rb5
-rw-r--r--app/controllers/projects/google_cloud_controller.rb1
-rw-r--r--app/models/ci/job_artifact.rb4
-rw-r--r--app/views/projects/google_cloud/errors/gcp_error.html.haml2
-rw-r--r--app/views/projects/google_cloud/errors/no_gcp_projects.html.haml2
-rw-r--r--app/views/projects/google_cloud/service_accounts/index.html.haml2
-rwxr-xr-xbin/metrics-server15
-rw-r--r--db/migrate/20211119085015_add_orignal_filename_to_ci_job_artifact.rb16
-rw-r--r--db/post_migrate/20211119085036_add_text_limit_to_job_artifact_original_filename.rb13
-rw-r--r--db/schema_migrations/202111190850151
-rw-r--r--db/schema_migrations/202111190850361
-rw-r--r--db/structure.sql4
-rw-r--r--doc/install/relative_url.md2
-rw-r--r--doc/integration/bitbucket.md2
-rw-r--r--doc/integration/datadog.md8
-rw-r--r--doc/integration/elasticsearch.md4
-rw-r--r--doc/operations/incident_management/integrations.md4
-rw-r--r--doc/topics/autodevops/multiple_clusters_auto_devops.md2
-rw-r--r--doc/user/application_security/dast/browser_based.md1
-rw-r--r--doc/user/group/index.md2
-rw-r--r--doc/user/project/integrations/asana.md2
-rw-r--r--doc/user/project/integrations/hangouts_chat.md4
-rw-r--r--doc/user/project/integrations/mattermost.md4
-rw-r--r--doc/user/project/integrations/pivotal_tracker.md2
-rw-r--r--doc/user/project/integrations/slack.md4
-rw-r--r--doc/user/project/labels.md6
-rw-r--r--doc/user/project/merge_requests/approvals/rules.md2
-rw-r--r--doc/user/project/merge_requests/cherry_pick_changes.md2
-rw-r--r--doc/user/project/merge_requests/reviews/index.md2
-rw-r--r--doc/user/project/pages/getting_started/pages_forked_sample_project.md6
-rw-r--r--doc/user/project/repository/forking_workflow.md2
-rw-r--r--doc/user/project/working_with_projects.md2
-rw-r--r--lib/api/terraform/state.rb7
-rw-r--r--lib/gitlab/utils/strong_memoize.rb2
-rw-r--r--lib/sidebars/projects/menus/infrastructure_menu.rb4
-rw-r--r--locale/gitlab.pot2
-rw-r--r--metrics_server/dependencies.rb25
-rw-r--r--metrics_server/metrics_server.rb41
-rw-r--r--metrics_server/settings_overrides.rb14
-rw-r--r--scripts/override_rails_constants.rb20
-rwxr-xr-xscripts/setup-test-env12
-rwxr-xr-xscripts/trigger-build3
-rw-r--r--spec/commands/metrics_server/metrics_server_spec.rb63
-rw-r--r--spec/frontend/google_cloud/components/app_spec.js115
-rw-r--r--spec/frontend/google_cloud/components/home_spec.js61
-rw-r--r--spec/frontend/google_cloud/components/screens/app_spec.js66
-rw-r--r--spec/frontend/google_cloud/components/service_accounts_form_spec.js (renamed from spec/frontend/google_cloud/components/screens/service_accounts_form_spec.js)8
-rw-r--r--spec/frontend/integrations/edit/components/integration_form_spec.js32
-rw-r--r--spec/frontend/integrations/edit/components/jira_issues_fields_spec.js13
-rw-r--r--spec/frontend/integrations/edit/mock_data.js6
-rw-r--r--spec/frontend/integrations/edit/store/actions_spec.js37
-rw-r--r--spec/frontend/integrations/integration_settings_form_spec.js58
-rw-r--r--spec/metrics_server/metrics_server_spec.rb81
-rw-r--r--spec/requests/api/terraform/state_spec.rb10
-rw-r--r--spec/tooling/quality/test_level_spec.rb4
-rwxr-xr-xtooling/bin/qa/check_if_qa_only_spec_changes18
-rw-r--r--tooling/quality/test_level.rb1
70 files changed, 789 insertions, 354 deletions
diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml
index 01fdc186602..7505ae1de68 100644
--- a/.gitlab/ci/qa.gitlab-ci.yml
+++ b/.gitlab/ci/qa.gitlab-ci.yml
@@ -58,6 +58,9 @@ update-qa-cache:
- tooling/bin/find_change_diffs ${CHANGES_DIFFS_DIR}
script:
- |
+ tooling/bin/qa/check_if_qa_only_spec_changes ${CHANGES_FILE} ${ONLY_QA_CHANGES_FILE}
+ [ -f $ONLY_QA_CHANGES_FILE ] && export QA_TESTS="`cat $ONLY_QA_CHANGES_FILE`"
+ echo "QA_TESTS: $QA_TESTS"
tooling/bin/qa/package_and_qa_check ${CHANGES_DIFFS_DIR} && exit_code=$?
if [ $exit_code -eq 0 ]; then
./scripts/trigger-build omnibus
@@ -80,9 +83,11 @@ update-qa-cache:
expire_in: 7d
paths:
- ${CHANGES_FILE}
+ - ${ONLY_QA_CHANGES_FILE}
- ${CHANGES_DIFFS_DIR}/*
variables:
CHANGES_FILE: tmp/changed_files.txt
+ ONLY_QA_CHANGES_FILE: tmp/qa_only_changed_files.txt
CHANGES_DIFFS_DIR: tmp/diffs
.package-and-qa-ff-base:
diff --git a/app/assets/javascripts/google_cloud/components/app.vue b/app/assets/javascripts/google_cloud/components/app.vue
new file mode 100644
index 00000000000..64784755b66
--- /dev/null
+++ b/app/assets/javascripts/google_cloud/components/app.vue
@@ -0,0 +1,59 @@
+<script>
+import { __ } from '~/locale';
+
+import Home from './home.vue';
+import IncubationBanner from './incubation_banner.vue';
+import ServiceAccountsForm from './service_accounts_form.vue';
+import NoGcpProjects from './errors/no_gcp_projects.vue';
+import GcpError from './errors/gcp_error.vue';
+
+const SCREEN_GCP_ERROR = 'gcp_error';
+const SCREEN_HOME = 'home';
+const SCREEN_NO_GCP_PROJECTS = 'no_gcp_projects';
+const SCREEN_SERVICE_ACCOUNTS_FORM = 'service_accounts_form';
+
+export default {
+ components: {
+ IncubationBanner,
+ },
+ inheritAttrs: false,
+ props: {
+ screen: {
+ required: true,
+ type: String,
+ },
+ },
+ computed: {
+ mainComponent() {
+ switch (this.screen) {
+ case SCREEN_HOME:
+ return Home;
+ case SCREEN_GCP_ERROR:
+ return GcpError;
+ case SCREEN_NO_GCP_PROJECTS:
+ return NoGcpProjects;
+ case SCREEN_SERVICE_ACCOUNTS_FORM:
+ return ServiceAccountsForm;
+ default:
+ throw new Error(__('Unknown screen'));
+ }
+ },
+ },
+ methods: {
+ feedbackUrl(template) {
+ return `https://gitlab.com/gitlab-org/incubation-engineering/five-minute-production/meta/-/issues/new?issuable_template=${template}`;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <incubation-banner
+ :share-feedback-url="feedbackUrl('general_feedback')"
+ :report-bug-url="feedbackUrl('report_bug')"
+ :feature-request-url="feedbackUrl('feature_request')"
+ />
+ <component :is="mainComponent" v-bind="$attrs" />
+ </div>
+</template>
diff --git a/app/assets/javascripts/google_cloud/components/home.vue b/app/assets/javascripts/google_cloud/components/home.vue
new file mode 100644
index 00000000000..05f39de66ee
--- /dev/null
+++ b/app/assets/javascripts/google_cloud/components/home.vue
@@ -0,0 +1,41 @@
+<script>
+import { GlTabs, GlTab } from '@gitlab/ui';
+import ServiceAccountsList from './service_accounts_list.vue';
+
+export default {
+ components: {
+ GlTabs,
+ GlTab,
+ ServiceAccountsList,
+ },
+ props: {
+ serviceAccounts: {
+ type: Array,
+ required: true,
+ },
+ createServiceAccountUrl: {
+ type: String,
+ required: true,
+ },
+ emptyIllustrationUrl: {
+ type: String,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-tabs>
+ <gl-tab :title="__('Configuration')">
+ <service-accounts-list
+ class="gl-mx-4"
+ :list="serviceAccounts"
+ :create-url="createServiceAccountUrl"
+ :empty-illustration-url="emptyIllustrationUrl"
+ />
+ </gl-tab>
+ <gl-tab :title="__('Deployments')" disabled />
+ <gl-tab :title="__('Services')" disabled />
+ </gl-tabs>
+</template>
diff --git a/app/assets/javascripts/google_cloud/components/screens/app.vue b/app/assets/javascripts/google_cloud/components/screens/app.vue
deleted file mode 100644
index 52c9b478916..00000000000
--- a/app/assets/javascripts/google_cloud/components/screens/app.vue
+++ /dev/null
@@ -1,50 +0,0 @@
-<script>
-import { GlTab, GlTabs } from '@gitlab/ui';
-import IncubationBanner from '../incubation_banner.vue';
-import ServiceAccountsList from '../service_accounts_list.vue';
-
-export default {
- components: { GlTab, GlTabs, IncubationBanner, ServiceAccountsList },
- props: {
- serviceAccounts: {
- type: Array,
- required: true,
- },
- createServiceAccountUrl: {
- type: String,
- required: true,
- },
- emptyIllustrationUrl: {
- type: String,
- required: true,
- },
- },
- methods: {
- feedbackUrl(template) {
- return `https://gitlab.com/gitlab-org/incubation-engineering/five-minute-production/meta/-/issues/new?issuable_template=${template}`;
- },
- },
-};
-</script>
-
-<template>
- <div>
- <incubation-banner
- :share-feedback-url="feedbackUrl('general_feedback')"
- :report-bug-url="feedbackUrl('report_bug')"
- :feature-request-url="feedbackUrl('feature_request')"
- />
- <gl-tabs>
- <gl-tab :title="__('Configuration')">
- <service-accounts-list
- class="gl-mx-3"
- :list="serviceAccounts"
- :create-url="createServiceAccountUrl"
- :empty-illustration-url="emptyIllustrationUrl"
- />
- </gl-tab>
- <gl-tab :title="__('Deployments')" disabled />
- <gl-tab :title="__('Services')" disabled />
- </gl-tabs>
- </div>
-</template>
diff --git a/app/assets/javascripts/google_cloud/components/screens/service_accounts_form.vue b/app/assets/javascripts/google_cloud/components/service_accounts_form.vue
index 6aead296918..e7a09668473 100644
--- a/app/assets/javascripts/google_cloud/components/screens/service_accounts_form.vue
+++ b/app/assets/javascripts/google_cloud/components/service_accounts_form.vue
@@ -1,20 +1,14 @@
<script>
import { GlButton, GlFormGroup, GlFormSelect } from '@gitlab/ui';
import { __ } from '~/locale';
-import IncubationBanner from '../incubation_banner.vue';
export default {
- components: { GlButton, GlFormGroup, GlFormSelect, IncubationBanner },
+ components: { GlButton, GlFormGroup, GlFormSelect },
props: {
gcpProjects: { required: true, type: Array },
environments: { required: true, type: Array },
cancelPath: { required: true, type: String },
},
- methods: {
- feedbackUrl(template) {
- return `https://gitlab.com/gitlab-org/incubation-engineering/five-minute-production/meta/-/issues/new?issuable_template=${template}`;
- },
- },
i18n: {
title: __('Create service account'),
gcpProjectLabel: __('Google Cloud project'),
@@ -31,11 +25,6 @@ export default {
<template>
<div>
- <incubation-banner
- :share-feedback-url="feedbackUrl('general_feedback')"
- :report-bug-url="feedbackUrl('report_bug')"
- :feature-request-url="feedbackUrl('feature_request')"
- />
<header class="gl-my-5 gl-border-b-1 gl-border-b-gray-100 gl-border-b-solid">
<h2 class="gl-font-size-h1">{{ $options.i18n.title }}</h2>
</header>
diff --git a/app/assets/javascripts/google_cloud/index.js b/app/assets/javascripts/google_cloud/index.js
index ba67877e005..ab9e8227812 100644
--- a/app/assets/javascripts/google_cloud/index.js
+++ b/app/assets/javascripts/google_cloud/index.js
@@ -1,40 +1,12 @@
import Vue from 'vue';
-import { __ } from '~/locale';
-import App from './components/screens/app.vue';
-import ServiceAccountsForm from './components/screens/service_accounts_form.vue';
-import ErrorNoGcpProjects from './components/errors/no_gcp_projects.vue';
-import ErrorGcpError from './components/errors/gcp_error.vue';
-
-const elementRenderer = (element, props = {}) => (createElement) =>
- createElement(element, { props });
-
-const rootComponentMap = [
- {
- root: '#js-google-cloud-error-no-gcp-projects',
- component: ErrorNoGcpProjects,
- },
- {
- root: '#js-google-cloud-error-gcp-error',
- component: ErrorGcpError,
- },
- {
- root: '#js-google-cloud-service-accounts',
- component: ServiceAccountsForm,
- },
- {
- root: '#js-google-cloud',
- component: App,
- },
-];
+import App from './components/app.vue';
export default () => {
- for (let i = 0; i < rootComponentMap.length; i += 1) {
- const { root, component } = rootComponentMap[i];
- const element = document.querySelector(root);
- if (element) {
- const props = JSON.parse(element.getAttribute('data'));
- return new Vue({ el: root, render: elementRenderer(component, props) });
- }
- }
- throw new Error(__('Unknown root'));
+ const root = '#js-google-cloud';
+ const element = document.querySelector(root);
+ const { screen, ...attrs } = JSON.parse(element.getAttribute('data'));
+ return new Vue({
+ el: element,
+ render: (createElement) => createElement(App, { props: { screen }, attrs }),
+ });
};
diff --git a/app/assets/javascripts/integrations/constants.js b/app/assets/javascripts/integrations/constants.js
index d214ee4ded6..977811f81a4 100644
--- a/app/assets/javascripts/integrations/constants.js
+++ b/app/assets/javascripts/integrations/constants.js
@@ -2,7 +2,6 @@ import { s__, __ } from '~/locale';
export const TEST_INTEGRATION_EVENT = 'testIntegration';
export const SAVE_INTEGRATION_EVENT = 'saveIntegration';
-export const GET_JIRA_ISSUE_TYPES_EVENT = 'getJiraIssueTypes';
export const TOGGLE_INTEGRATION_EVENT = 'toggleIntegration';
export const VALIDATE_INTEGRATION_FORM_EVENT = 'validateIntegrationForm';
diff --git a/app/assets/javascripts/integrations/edit/api.js b/app/assets/javascripts/integrations/edit/api.js
new file mode 100644
index 00000000000..7bce5604f9d
--- /dev/null
+++ b/app/assets/javascripts/integrations/edit/api.js
@@ -0,0 +1,9 @@
+import axios from '~/lib/utils/axios_utils';
+
+/**
+ * Test the validity of [integrationFormData].
+ * @return Promise<{ issuetypes: []String }> - issuetypes contains valid Jira issue types.
+ */
+export const testIntegrationSettings = (testPath, integrationFormData) => {
+ return axios.put(testPath, integrationFormData);
+};
diff --git a/app/assets/javascripts/integrations/edit/components/integration_form.vue b/app/assets/javascripts/integrations/edit/components/integration_form.vue
index ba1aeb28616..767810950b1 100644
--- a/app/assets/javascripts/integrations/edit/components/integration_form.vue
+++ b/app/assets/javascripts/integrations/edit/components/integration_form.vue
@@ -69,6 +69,10 @@ export default {
return this.isInstanceOrGroupLevel && this.propsSource.resetPath;
},
},
+ mounted() {
+ // this form element is defined in Haml
+ this.form = document.querySelector('.js-integration-settings-form');
+ },
methods: {
...mapActions([
'setOverride',
@@ -76,6 +80,7 @@ export default {
'setIsTesting',
'setIsResetting',
'fetchResetIntegration',
+ 'requestJiraIssueTypes',
]),
onSaveClick() {
this.setIsSaving(true);
@@ -88,6 +93,10 @@ export default {
onResetClick() {
this.fetchResetIntegration();
},
+ onRequestJiraIssueTypes() {
+ const formData = new FormData(this.form);
+ this.requestJiraIssueTypes(formData);
+ },
},
helpHtmlConfig: {
ADD_ATTR: ['target'], // allow external links, can be removed after https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1427 is implemented
@@ -135,6 +144,7 @@ export default {
v-if="isJira && !isInstanceOrGroupLevel"
:key="`${currentKey}-jira-issues-fields`"
v-bind="propsSource.jiraIssuesProps"
+ @request-jira-issue-types="onRequestJiraIssueTypes"
/>
<div v-if="isEditable" class="footer-block row-content-block">
<template v-if="isInstanceOrGroupLevel">
diff --git a/app/assets/javascripts/integrations/edit/components/jira_issues_fields.vue b/app/assets/javascripts/integrations/edit/components/jira_issues_fields.vue
index 7cbfb35aeaa..cd0624d6b5c 100644
--- a/app/assets/javascripts/integrations/edit/components/jira_issues_fields.vue
+++ b/app/assets/javascripts/integrations/edit/components/jira_issues_fields.vue
@@ -1,10 +1,7 @@
<script>
import { GlFormGroup, GlFormCheckbox, GlFormInput, GlSprintf, GlLink } from '@gitlab/ui';
import { mapGetters } from 'vuex';
-import {
- VALIDATE_INTEGRATION_FORM_EVENT,
- GET_JIRA_ISSUE_TYPES_EVENT,
-} from '~/integrations/constants';
+import { VALIDATE_INTEGRATION_FORM_EVENT } from '~/integrations/constants';
import { s__, __ } from '~/locale';
import eventHub from '../event_hub';
import JiraUpgradeCta from './jira_upgrade_cta.vue';
@@ -91,9 +88,6 @@ export default {
validateForm() {
this.validated = true;
},
- getJiraIssueTypes() {
- eventHub.$emit(GET_JIRA_ISSUE_TYPES_EVENT);
- },
},
i18n: {
sectionTitle: s__('JiraService|View Jira issues in GitLab'),
@@ -136,7 +130,7 @@ export default {
:initial-issue-type-id="initialVulnerabilitiesIssuetype"
:show-full-feature="showJiraVulnerabilitiesIntegration"
data-testid="jira-for-vulnerabilities"
- @request-get-issue-types="getJiraIssueTypes"
+ @request-jira-issue-types="$emit('request-jira-issue-types')"
/>
<jira-upgrade-cta
v-if="!showJiraVulnerabilitiesIntegration"
diff --git a/app/assets/javascripts/integrations/edit/store/actions.js b/app/assets/javascripts/integrations/edit/store/actions.js
index 400397c050c..b81ae1b1cb6 100644
--- a/app/assets/javascripts/integrations/edit/store/actions.js
+++ b/app/assets/javascripts/integrations/edit/store/actions.js
@@ -1,5 +1,12 @@
import axios from 'axios';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
+import {
+ VALIDATE_INTEGRATION_FORM_EVENT,
+ I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE,
+ I18N_DEFAULT_ERROR_MESSAGE,
+} from '~/integrations/constants';
+import { testIntegrationSettings } from '../api';
+import eventHub from '../event_hub';
import * as types from './mutation_types';
export const setOverride = ({ commit }, override) => commit(types.SET_OVERRIDE, override);
@@ -27,10 +34,28 @@ export const fetchResetIntegration = ({ dispatch, getters }) => {
.catch(() => dispatch('receiveResetIntegrationError'));
};
-export const requestJiraIssueTypes = ({ commit }) => {
+export const requestJiraIssueTypes = ({ commit, dispatch, getters }, formData) => {
commit(types.SET_JIRA_ISSUE_TYPES_ERROR_MESSAGE, '');
commit(types.SET_IS_LOADING_JIRA_ISSUE_TYPES, true);
+
+ return testIntegrationSettings(getters.propsSource.testPath, formData)
+ .then(
+ ({
+ data: { issuetypes, error, message = I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE },
+ }) => {
+ if (error || !issuetypes?.length) {
+ eventHub.$emit(VALIDATE_INTEGRATION_FORM_EVENT);
+ throw new Error(message);
+ }
+
+ dispatch('receiveJiraIssueTypesSuccess', issuetypes);
+ },
+ )
+ .catch(({ message = I18N_DEFAULT_ERROR_MESSAGE }) => {
+ dispatch('receiveJiraIssueTypesError', message);
+ });
};
+
export const receiveJiraIssueTypesSuccess = ({ commit }, issueTypes = []) => {
commit(types.SET_IS_LOADING_JIRA_ISSUE_TYPES, false);
commit(types.SET_JIRA_ISSUE_TYPES, issueTypes);
diff --git a/app/assets/javascripts/integrations/integration_settings_form.js b/app/assets/javascripts/integrations/integration_settings_form.js
index f519fc87c46..2b6959ed1cd 100644
--- a/app/assets/javascripts/integrations/integration_settings_form.js
+++ b/app/assets/javascripts/integrations/integration_settings_form.js
@@ -1,18 +1,16 @@
import { delay } from 'lodash';
import toast from '~/vue_shared/plugins/global_toast';
-import axios from '../lib/utils/axios_utils';
import initForm from './edit';
import eventHub from './edit/event_hub';
import {
TEST_INTEGRATION_EVENT,
SAVE_INTEGRATION_EVENT,
- GET_JIRA_ISSUE_TYPES_EVENT,
TOGGLE_INTEGRATION_EVENT,
VALIDATE_INTEGRATION_FORM_EVENT,
- I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE,
I18N_DEFAULT_ERROR_MESSAGE,
I18N_SUCCESSFUL_CONNECTION_MESSAGE,
} from './constants';
+import { testIntegrationSettings } from './edit/api';
export default class IntegrationSettingsForm {
constructor(formSelector) {
@@ -41,9 +39,6 @@ export default class IntegrationSettingsForm {
eventHub.$on(SAVE_INTEGRATION_EVENT, () => {
this.saveIntegration();
});
- eventHub.$on(GET_JIRA_ISSUE_TYPES_EVENT, () => {
- this.getJiraIssueTypes(new FormData(this.$form));
- });
}
saveIntegration() {
@@ -96,43 +91,12 @@ export default class IntegrationSettingsForm {
*
* @return {Promise}
*/
- getJiraIssueTypes(formData) {
- const {
- $store: { dispatch },
- } = this.vue;
-
- dispatch('requestJiraIssueTypes');
-
- return this.fetchTestSettings(formData)
- .then(
- ({
- data: { issuetypes, error, message = I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE },
- }) => {
- if (error || !issuetypes?.length) {
- eventHub.$emit(VALIDATE_INTEGRATION_FORM_EVENT);
- throw new Error(message);
- }
-
- dispatch('receiveJiraIssueTypesSuccess', issuetypes);
- },
- )
- .catch(({ message = I18N_DEFAULT_ERROR_MESSAGE }) => {
- dispatch('receiveJiraIssueTypesError', message);
- });
- }
-
- /**
- * Send request to the test endpoint which checks if the current config is valid
- */
- fetchTestSettings(formData) {
- return axios.put(this.testEndPoint, formData);
- }
/**
* Test Integration config
*/
testSettings(formData) {
- return this.fetchTestSettings(formData)
+ return testIntegrationSettings(this.testEndPoint, formData)
.then(({ data }) => {
if (data.error) {
toast(`${data.message} ${data.service_response}`);
diff --git a/app/controllers/projects/google_cloud/base_controller.rb b/app/controllers/projects/google_cloud/base_controller.rb
index 8bfe5c9c5f3..aff305ab7d6 100644
--- a/app/controllers/projects/google_cloud/base_controller.rb
+++ b/app/controllers/projects/google_cloud/base_controller.rb
@@ -21,6 +21,6 @@ class Projects::GoogleCloud::BaseController < Projects::ApplicationController
end
def feature_flag_enabled!
- access_denied! unless Feature.enabled?(:incubation_5mp_google_cloud)
+ access_denied! unless Feature.enabled?(:incubation_5mp_google_cloud, project)
end
end
diff --git a/app/controllers/projects/google_cloud/service_accounts_controller.rb b/app/controllers/projects/google_cloud/service_accounts_controller.rb
index 21b096a6c66..d5db4fabf88 100644
--- a/app/controllers/projects/google_cloud/service_accounts_controller.rb
+++ b/app/controllers/projects/google_cloud/service_accounts_controller.rb
@@ -9,10 +9,11 @@ class Projects::GoogleCloud::ServiceAccountsController < Projects::GoogleCloud::
gcp_projects = google_api_client.list_projects
if gcp_projects.empty?
- @js_data = {}.to_json
+ @js_data = { screen: 'no_gcp_projects' }.to_json
render status: :unauthorized, template: 'projects/google_cloud/errors/no_gcp_projects'
else
@js_data = {
+ screen: 'service_accounts_form',
gcpProjects: gcp_projects,
environments: project.environments,
cancelPath: project_google_cloud_index_path(project)
@@ -78,7 +79,7 @@ class Projects::GoogleCloud::ServiceAccountsController < Projects::GoogleCloud::
def handle_gcp_error(error, project)
Gitlab::ErrorTracking.track_exception(error, project_id: project.id)
- @js_data = { error: error.to_s }.to_json
+ @js_data = { screen: 'gcp_error', error: error.to_s }.to_json
render status: :unauthorized, template: 'projects/google_cloud/errors/gcp_error'
end
end
diff --git a/app/controllers/projects/google_cloud_controller.rb b/app/controllers/projects/google_cloud_controller.rb
index 6cc67391d6c..1fa8ae60376 100644
--- a/app/controllers/projects/google_cloud_controller.rb
+++ b/app/controllers/projects/google_cloud_controller.rb
@@ -3,6 +3,7 @@
class Projects::GoogleCloudController < Projects::GoogleCloud::BaseController
def index
@js_data = {
+ screen: 'home',
serviceAccounts: GoogleCloud::ServiceAccountsService.new(project).find_for_project,
createServiceAccountUrl: project_google_cloud_service_accounts_path(project),
emptyIllustrationUrl: ActionController::Base.helpers.image_path('illustrations/pipelines_empty.svg')
diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb
index b10837d4e21..e6dd62fab34 100644
--- a/app/models/ci/job_artifact.rb
+++ b/app/models/ci/job_artifact.rb
@@ -2,6 +2,7 @@
module Ci
class JobArtifact < Ci::ApplicationRecord
+ include IgnorableColumns
include AfterCommitQueue
include ObjectStorage::BackgroundMove
include UpdateProjectStatistics
@@ -120,6 +121,9 @@ module Ci
belongs_to :project
belongs_to :job, class_name: "Ci::Build", foreign_key: :job_id
+ # We will start using this column once we complete https://gitlab.com/gitlab-org/gitlab/-/issues/285597
+ ignore_column :original_filename, remove_with: '14.7', remove_after: '2022-11-22'
+
mount_file_store_uploader JobArtifactUploader
skip_callback :save, :after, :store_file!, if: :store_after_commit?
diff --git a/app/views/projects/google_cloud/errors/gcp_error.html.haml b/app/views/projects/google_cloud/errors/gcp_error.html.haml
index b91a85250b3..69e481501d5 100644
--- a/app/views/projects/google_cloud/errors/gcp_error.html.haml
+++ b/app/views/projects/google_cloud/errors/gcp_error.html.haml
@@ -3,4 +3,4 @@
- @content_class = "limit-container-width" unless fluid_layout
-#js-google-cloud-error-gcp-error{ data: @js_data }
+#js-google-cloud{ data: @js_data }
diff --git a/app/views/projects/google_cloud/errors/no_gcp_projects.html.haml b/app/views/projects/google_cloud/errors/no_gcp_projects.html.haml
index 743b757de57..69e481501d5 100644
--- a/app/views/projects/google_cloud/errors/no_gcp_projects.html.haml
+++ b/app/views/projects/google_cloud/errors/no_gcp_projects.html.haml
@@ -3,4 +3,4 @@
- @content_class = "limit-container-width" unless fluid_layout
-#js-google-cloud-error-no-gcp-projects{ data: @js_data }
+#js-google-cloud{ data: @js_data }
diff --git a/app/views/projects/google_cloud/service_accounts/index.html.haml b/app/views/projects/google_cloud/service_accounts/index.html.haml
index 69b2123d723..9b82bc0acb5 100644
--- a/app/views/projects/google_cloud/service_accounts/index.html.haml
+++ b/app/views/projects/google_cloud/service_accounts/index.html.haml
@@ -5,4 +5,4 @@
- @content_class = "limit-container-width" unless fluid_layout
= form_tag project_google_cloud_service_accounts_path(@project), method: 'post' do
- #js-google-cloud-service-accounts{ data: @js_data }
+ #js-google-cloud{ data: @js_data }
diff --git a/bin/metrics-server b/bin/metrics-server
new file mode 100755
index 00000000000..48d3a6402c3
--- /dev/null
+++ b/bin/metrics-server
@@ -0,0 +1,15 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+
+require_relative '../metrics_server/metrics_server'
+
+begin
+ target = ENV['METRICS_SERVER_TARGET']
+ raise "Required: METRICS_SERVER_TARGET=[sidekiq]" unless target == 'sidekiq'
+
+ metrics_dir = ENV["prometheus_multiproc_dir"] || File.absolute_path("tmp/prometheus_multiproc_dir/#{target}")
+
+ # Re-raise exceptions in threads on the main thread.
+ Thread.abort_on_exception = true
+ MetricsServer.new(target, metrics_dir).start
+end
diff --git a/db/migrate/20211119085015_add_orignal_filename_to_ci_job_artifact.rb b/db/migrate/20211119085015_add_orignal_filename_to_ci_job_artifact.rb
new file mode 100644
index 00000000000..f52bc346d16
--- /dev/null
+++ b/db/migrate/20211119085015_add_orignal_filename_to_ci_job_artifact.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class AddOrignalFilenameToCiJobArtifact < Gitlab::Database::Migration[1.0]
+ enable_lock_retries!
+
+ # rubocop:disable Migration/AddLimitToTextColumns
+ # limit is added in 20211119085036_add_text_limit_to_job_artifact_original_filename.rb
+ def up
+ add_column :ci_job_artifacts, :original_filename, :text
+ end
+ # rubocop:enable Migration/AddLimitToTextColumns
+
+ def down
+ remove_column :ci_job_artifacts, :original_filename, :text
+ end
+end
diff --git a/db/post_migrate/20211119085036_add_text_limit_to_job_artifact_original_filename.rb b/db/post_migrate/20211119085036_add_text_limit_to_job_artifact_original_filename.rb
new file mode 100644
index 00000000000..3eccbb1b3a4
--- /dev/null
+++ b/db/post_migrate/20211119085036_add_text_limit_to_job_artifact_original_filename.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class AddTextLimitToJobArtifactOriginalFilename < Gitlab::Database::Migration[1.0]
+ disable_ddl_transaction!
+
+ def up
+ add_text_limit :ci_job_artifacts, :original_filename, 512
+ end
+
+ def down
+ remove_text_limit :ci_job_artifacts, :original_filename
+ end
+end
diff --git a/db/schema_migrations/20211119085015 b/db/schema_migrations/20211119085015
new file mode 100644
index 00000000000..874bd158e7c
--- /dev/null
+++ b/db/schema_migrations/20211119085015
@@ -0,0 +1 @@
+88b289d724f98f75e0340cde4c6e2bc3cb55df2a979934fb2bc544d22e4c032d \ No newline at end of file
diff --git a/db/schema_migrations/20211119085036 b/db/schema_migrations/20211119085036
new file mode 100644
index 00000000000..f9d1eeffbcb
--- /dev/null
+++ b/db/schema_migrations/20211119085036
@@ -0,0 +1 @@
+2b2c28e0370ae1bb84bee5ff769c9b313902d1f1afc50fa54e23a1627b1121f3 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index d57b66ad602..cd081495e34 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -11699,7 +11699,9 @@ CREATE TABLE ci_job_artifacts (
id bigint NOT NULL,
job_id bigint NOT NULL,
locked smallint DEFAULT 2,
- CONSTRAINT check_27f0f6dbab CHECK ((file_store IS NOT NULL))
+ original_filename text,
+ CONSTRAINT check_27f0f6dbab CHECK ((file_store IS NOT NULL)),
+ CONSTRAINT check_85573000db CHECK ((char_length(original_filename) <= 512))
);
CREATE SEQUENCE ci_job_artifacts_id_seq
diff --git a/doc/install/relative_url.md b/doc/install/relative_url.md
index 569f6e02ea4..43f2414e8f9 100644
--- a/doc/install/relative_url.md
+++ b/doc/install/relative_url.md
@@ -60,7 +60,7 @@ assumptions are made:
Make sure to follow all steps below:
-1. (Optional) If you run short on resources, you can temporarily free up some
+1. Optional. If you run short on resources, you can temporarily free up some
memory by shutting down the GitLab service with the following command:
```shell
diff --git a/doc/integration/bitbucket.md b/doc/integration/bitbucket.md
index 6b7854b6ef9..db7e7d74efe 100644
--- a/doc/integration/bitbucket.md
+++ b/doc/integration/bitbucket.md
@@ -42,7 +42,7 @@ to the end of the Bitbucket authorization callback URL.
- **Name:** This can be anything. Consider something like `<Organization>'s GitLab`
or `<Your Name>'s GitLab` or something else descriptive.
- - **Application description:** *(Optional)* Fill this in if you wish.
+ - **Application description:** Optional. Fill this in if you wish.
- **Callback URL:** (Required in GitLab versions 8.15 and greater)
The URL to your GitLab installation, such as
`https://gitlab.example.com/users/auth`.
diff --git a/doc/integration/datadog.md b/doc/integration/datadog.md
index 1c699bb622e..89e08d330e8 100644
--- a/doc/integration/datadog.md
+++ b/doc/integration/datadog.md
@@ -32,15 +32,15 @@ project, group, or instance level:
1. Scroll to **Add an integration**, and select **Datadog**.
1. Select **Active** to enable the integration.
1. Specify the [**Datadog site**](https://docs.datadoghq.com/getting_started/site/) to send data to.
-1. (Optional) To override the API URL used to send data directly, provide an **API URL**.
+1. Optional. To override the API URL used to send data directly, provide an **API URL**.
Used only in advanced scenarios.
1. Provide your Datadog **API key**.
-1. (Optional) If you use more than one GitLab instance, provide a unique **Service** name
+1. Optional. If you use more than one GitLab instance, provide a unique **Service** name
to differentiate between your GitLab instances.
-1. (Optional) If you use groups of GitLab instances (such as staging and production
+1. Optional. If you use groups of GitLab instances (such as staging and production
environments), provide an **Env** name. This value is attached to each span
the integration generates.
-1. (Optional) Select **Test settings** to test your integration.
+1. Optional. Select **Test settings** to test your integration.
1. Select **Save changes**.
When the integration sends data, you can view it in the [CI Visibility](https://app.datadoghq.com/ci)
diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md
index 2f9ec7a41c2..757fb53ec2e 100644
--- a/doc/integration/elasticsearch.md
+++ b/doc/integration/elasticsearch.md
@@ -279,8 +279,8 @@ To disable the Elasticsearch integration:
1. On the top bar, select **Menu > Admin**.
1. On the left sidebar, select **Settings > Advanced Search**.
1. Uncheck **Elasticsearch indexing** and **Search with Elasticsearch enabled**.
-1. Click **Save changes** for the changes to take effect.
-1. (Optional) Delete the existing indexes:
+1. Select **Save changes**.
+1. Optional. Delete the existing indexes:
```shell
# Omnibus installations
diff --git a/doc/operations/incident_management/integrations.md b/doc/operations/incident_management/integrations.md
index 92f5a50b1c3..de7316b5ae3 100644
--- a/doc/operations/incident_management/integrations.md
+++ b/doc/operations/incident_management/integrations.md
@@ -65,11 +65,11 @@ and you can [customize the payload](#customize-the-alert-payload-outside-of-gitl
1. Toggle the **Active** alert setting. The **URL** and **Authorization Key** for the webhook
configuration are available in the **View credentials** tab after you save the integration.
You must also input the URL and Authorization Key in your external service.
- 1. _(Optional)_ To map fields from your monitoring tool's alert to GitLab fields, enter a sample
+ 1. Optional. To map fields from your monitoring tool's alert to GitLab fields, enter a sample
payload and click **Parse payload for custom mapping**. Valid JSON is required. If you update
a sample payload, you must also remap the fields.
- 1. _(Optional)_ If you provided a valid sample payload, select each value in
+ 1. Optional. If you provided a valid sample payload, select each value in
**Payload alert key** to [map to a **GitLab alert key**](#map-fields-in-custom-alerts).
1. To save your integration, click **Save Integration**. If desired, you can send a test alert
from your integration's **Send test alert** tab after the integration is created.
diff --git a/doc/topics/autodevops/multiple_clusters_auto_devops.md b/doc/topics/autodevops/multiple_clusters_auto_devops.md
index c6df5ac9e02..8156ae7c7ac 100644
--- a/doc/topics/autodevops/multiple_clusters_auto_devops.md
+++ b/doc/topics/autodevops/multiple_clusters_auto_devops.md
@@ -27,7 +27,7 @@ The following table is an example of how to configure the three different cluste
| Cluster name | Cluster environment scope | `KUBE_INGRESS_BASE_DOMAIN` variable value | Variable environment scope | Notes |
|--------------|---------------------------|-------------------------------------------|----------------------------|---|
| review | `review/*` | `review.example.com` | `review/*` | The review cluster which runs all [Review Apps](../../ci/review_apps/index.md). `*` is a wildcard, used by every environment name starting with `review/`. |
-| staging | `staging` | `staging.example.com` | `staging` | (Optional) The staging cluster which runs the deployments of the staging environments. You must [enable it first](customize.md#deploy-policy-for-staging-and-production-environments). |
+| staging | `staging` | `staging.example.com` | `staging` | Optional. The staging cluster that runs the deployments of the staging environments. You must [enable it first](customize.md#deploy-policy-for-staging-and-production-environments). |
| production | `production` | `example.com` | `production` | The production cluster which runs the production environment deployments. You can use [incremental rollouts](customize.md#incremental-rollout-to-production). |
To add a different cluster for each environment:
diff --git a/doc/user/application_security/dast/browser_based.md b/doc/user/application_security/dast/browser_based.md
index 10ca3430b48..7e3d927f31d 100644
--- a/doc/user/application_security/dast/browser_based.md
+++ b/doc/user/application_security/dast/browser_based.md
@@ -69,6 +69,7 @@ The browser-based crawler can be configured using CI/CD variables.
| `DAST_BROWSER_SEARCH_ELEMENT_TIMEOUT` | [Duration string](https://golang.org/pkg/time/#ParseDuration) | `3s` | The maximum amount of time to allow the browser to search for new elements or navigations. |
| `DAST_BROWSER_EXTRACT_ELEMENT_TIMEOUT` | [Duration string](https://golang.org/pkg/time/#ParseDuration) | `5s` | The maximum amount of time to allow the browser to extract newly found elements or navigations. |
| `DAST_BROWSER_ELEMENT_TIMEOUT` | [Duration string](https://golang.org/pkg/time/#ParseDuration) | `600ms` | The maximum amount of time to wait for an element before determining it is ready for analysis. |
+| `DAST_BROWSER_PAGE_READY_SELECTOR` | selector | `css:#page-is-ready` | Selector that when detected as visible on the page, indicates to the analyzer that the page has finished loading and the scan can continue. Note: When this selector is set, but the element is not found, the scanner waits for the period defined in `DAST_BROWSER_STABILITY_TIMEOUT` before continuing the scan. This can significantly increase scanning time if the element is not present on multiple pages within the site. |
The [DAST variables](index.md#available-cicd-variables) `SECURE_ANALYZERS_PREFIX`, `DAST_FULL_SCAN_ENABLED`, `DAST_AUTO_UPDATE_ADDONS`, `DAST_EXCLUDE_RULES`, `DAST_REQUEST_HEADERS`, `DAST_HTML_REPORT`, `DAST_MARKDOWN_REPORT`, `DAST_XML_REPORT`,
`DAST_AUTH_URL`, `DAST_USERNAME`, `DAST_PASSWORD`, `DAST_USERNAME_FIELD`, `DAST_PASSWORD_FIELD`, `DAST_FIRST_SUBMIT_FIELD`, `DAST_SUBMIT_FIELD`, `DAST_EXCLUDE_URLS`, `DAST_AUTH_VERIFICATION_URL`, `DAST_BROWSER_AUTH_VERIFICATION_SELECTOR`, `DAST_BROWSER_AUTH_VERIFICATION_LOGIN_FORM`, `DAST_BROWSER_AUTH_REPORT`,
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index 9ec5343708e..42fc2f532e1 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -321,7 +321,7 @@ To share a group after enabling this feature:
1. Go to your group's page.
1. On the left sidebar, go to **Group information > Members**, and then select **Invite a group**.
1. Select a group, and select a **Max role**.
-1. (Optional) Select an **Access expiration date**.
+1. Optional. Select an **Access expiration date**.
1. Select **Invite**.
## Manage group memberships via LDAP **(PREMIUM SELF)**
diff --git a/doc/user/project/integrations/asana.md b/doc/user/project/integrations/asana.md
index 963fca34827..b4d7790df1d 100644
--- a/doc/user/project/integrations/asana.md
+++ b/doc/user/project/integrations/asana.md
@@ -37,7 +37,7 @@ Complete these steps in GitLab:
1. Select **Asana**.
1. Ensure that the **Active** toggle is enabled.
1. Paste the token you generated in Asana.
-1. (Optional) To restrict this setting to specific branches, list them in the **Restrict to branch**
+1. Optional. To restrict this setting to specific branches, list them in the **Restrict to branch**
field, separated with commas.
1. Select **Save changes** or optionally select **Test settings**.
diff --git a/doc/user/project/integrations/hangouts_chat.md b/doc/user/project/integrations/hangouts_chat.md
index bcaedbc4b10..7a96bb74e3f 100644
--- a/doc/user/project/integrations/hangouts_chat.md
+++ b/doc/user/project/integrations/hangouts_chat.md
@@ -32,7 +32,7 @@ Select a room and create a webhook:
1. Enter the room where you want to receive notifications from GitLab.
1. Open the room dropdown menu on the top-left and select **Manage webhooks**.
1. Enter the name for your webhook, for example "GitLab integration".
-1. (Optional) Add an avatar for your bot.
+1. Optional. Add an avatar for your bot.
1. Select **Save**.
1. Copy the webhook URL.
@@ -46,7 +46,7 @@ Enable the Google Chat integration in GitLab:
1. Scroll down to the end of the page where you find a **Webhook** field.
1. Enter the webhook URL you copied from Google Chat.
1. Select the events you want to be notified about in your Google Chat room.
-1. (Optional) Select **Test settings** to verify the connection.
+1. Optional. Select **Test settings** to verify the connection.
1. Select **Save changes**.
To test the integration, make a change based on the events you selected and
diff --git a/doc/user/project/integrations/mattermost.md b/doc/user/project/integrations/mattermost.md
index 119f219499c..f3f8d900e12 100644
--- a/doc/user/project/integrations/mattermost.md
+++ b/doc/user/project/integrations/mattermost.md
@@ -50,12 +50,12 @@ Then fill in the integration configuration:
- **Webhook**: The incoming webhook URL on Mattermost, similar to
`http://mattermost.example/hooks/5xo…`.
-- **Username**: (Optional) The username shown in messages sent to Mattermost.
+- **Username**: Optional. The username shown in messages sent to Mattermost.
To change the bot's username, provide a value.
- **Notify only broken pipelines**: If you enable the **Pipeline** event, and you want
notifications about failed pipelines only.
- **Branches for which notifications are to be sent**: The branches to send notifications for.
-- **Labels to be notified**: (Optional) Labels required for the issue or merge request
+- **Labels to be notified**: Optional. Labels required for the issue or merge request
to trigger a notification. Leave blank to notify for all issues and merge requests.
- **Labels to be notified behavior**: When you use the **Labels to be notified** filter,
messages are sent when an issue or merge request contains _any_ of the labels specified
diff --git a/doc/user/project/integrations/pivotal_tracker.md b/doc/user/project/integrations/pivotal_tracker.md
index 93a3490e4b6..8b17f4afaa8 100644
--- a/doc/user/project/integrations/pivotal_tracker.md
+++ b/doc/user/project/integrations/pivotal_tracker.md
@@ -42,6 +42,6 @@ Complete these steps in GitLab:
1. Select **Pivotal Tracker**.
1. Ensure that the **Active** toggle is enabled.
1. Paste the token you generated in Pivotal Tracker.
-1. (Optional) To restrict this setting to specific branches, list them in the **Restrict to branch**
+1. Optional. To restrict this setting to specific branches, list them in the **Restrict to branch**
field, separated with commas.
1. Select **Save changes** or optionally select **Test settings**.
diff --git a/doc/user/project/integrations/slack.md b/doc/user/project/integrations/slack.md
index d399c7f2901..87f38c3482b 100644
--- a/doc/user/project/integrations/slack.md
+++ b/doc/user/project/integrations/slack.md
@@ -31,7 +31,7 @@ to control GitLab from Slack. Slash commands are configured separately.
[Triggers for Slack notifications](#triggers-for-slack-notifications).
By default, messages are sent to the channel you configured during
[Slack configuration](#configure-slack).
-1. (Optional) To send messages to a different channel, multiple channels, or as
+1. Optional. To send messages to a different channel, multiple channels, or as
a direct message:
- *To send messages to channels,* enter the Slack channel names, separated by
commas.
@@ -42,7 +42,7 @@ to control GitLab from Slack. Slash commands are configured separately.
1. In **Webhook**, enter the webhook URL you copied in the
[Slack configuration](#configure-slack) step.
-1. (Optional) In **Username**, enter the username of the Slack bot that sends
+1. Optional. In **Username**, enter the username of the Slack bot that sends
the notifications.
1. Select the **Notify only broken pipelines** checkbox to notify only on failures.
1. In the **Branches for which notifications are to be sent** dropdown, select which types of branches
diff --git a/doc/user/project/labels.md b/doc/user/project/labels.md
index 91f12a36070..3a91a33fc4d 100644
--- a/doc/user/project/labels.md
+++ b/doc/user/project/labels.md
@@ -72,9 +72,9 @@ To create a new project label:
1. Select the **New label** button.
1. In the **Title** field, enter a short, descriptive name for the label. You
can also use this field to create [scoped, mutually exclusive labels](#scoped-labels).
-1. (Optional) In the **Description** field, you can enter additional
+1. Optional. In the **Description** field, you can enter additional
information about how and when to use this label.
-1. (Optional) Select a background color for the label by selecting one of the
+1. Optional. Select a background color for the label by selecting one of the
available colors, or by entering a hex color value in the **Background color**
field.
1. Select **Create label**.
@@ -86,7 +86,7 @@ label section of the right sidebar of an issue or a merge request:
1. Click **Create project label**.
- Fill in the name field. Note that you can't specify a description if creating a label
this way. You can add a description later by editing the label (see below).
- - (Optional) Select a color by clicking on the available colors, or input a hex
+ - Optional. Select a color by clicking on the available colors, or input a hex
color value for a specific color.
1. Click **Create**.
diff --git a/doc/user/project/merge_requests/approvals/rules.md b/doc/user/project/merge_requests/approvals/rules.md
index 1249aa826fa..f4393b2b76d 100644
--- a/doc/user/project/merge_requests/approvals/rules.md
+++ b/doc/user/project/merge_requests/approvals/rules.md
@@ -63,7 +63,7 @@ To edit a merge request approval rule:
1. Go to your project and select **Settings > General**.
1. Expand **Merge request (MR) approvals**, and then select **Edit**.
-1. (Optional) Change the **Rule name**.
+1. Optional. Change the **Rule name**.
1. Set the number of required approvals in **Approvals required**. The minimum value is `0`.
1. Add or remove eligible approvers, as needed:
- *To add users or groups as approvers,* search for users or groups that are
diff --git a/doc/user/project/merge_requests/cherry_pick_changes.md b/doc/user/project/merge_requests/cherry_pick_changes.md
index 54e411fa9c9..15ba6e9de98 100644
--- a/doc/user/project/merge_requests/cherry_pick_changes.md
+++ b/doc/user/project/merge_requests/cherry_pick_changes.md
@@ -76,7 +76,7 @@ merge request is from a fork:
1. Click on the **Options** dropdown and select **Cherry-pick** to show the cherry-pick modal.
1. In **Pick into project** and **Pick into branch**, select the destination project and branch:
![Cherry-pick commit](img/cherry_pick_into_project_v13_11.png)
-1. (Optional) Select **Start a new merge request** if you're ready to create a merge request.
+1. Optional. Select **Start a new merge request** if you're ready to create a merge request.
1. Click **Cherry-pick**.
## Related topics
diff --git a/doc/user/project/merge_requests/reviews/index.md b/doc/user/project/merge_requests/reviews/index.md
index 597dcb3dfb9..be26ce9176f 100644
--- a/doc/user/project/merge_requests/reviews/index.md
+++ b/doc/user/project/merge_requests/reviews/index.md
@@ -43,7 +43,7 @@ To start your review:
- **Add to review**: Keep this comment private and add to the current review.
These review comments are marked **Pending** and are visible only to you.
- **Add comment now**: Submits the specific comment as a regular comment instead of as part of the review.
-1. (Optional) You can use [quick actions](../../quick_actions.md) inside review comments.
+1. Optional. You can use [quick actions](../../quick_actions.md) inside review comments.
The comment shows the actions to perform after publication, but does not perform them
until you submit your review.
1. When your review is complete, you can [submit the review](#submit-a-review). Your comments
diff --git a/doc/user/project/pages/getting_started/pages_forked_sample_project.md b/doc/user/project/pages/getting_started/pages_forked_sample_project.md
index 7640bf1f72d..70b3e1543e7 100644
--- a/doc/user/project/pages/getting_started/pages_forked_sample_project.md
+++ b/doc/user/project/pages/getting_started/pages_forked_sample_project.md
@@ -18,9 +18,9 @@ configured to generate a Pages site.
To fork a sample project and create a Pages website:
1. View the sample projects by navigating to the [GitLab Pages examples](https://gitlab.com/pages) group.
-1. Click the name of the project you want to [fork](../../../../user/project/working_with_projects.md#fork-a-project).
-1. In the top right, click the **Fork** button, and then choose a namespace to fork to.
-1. Go to your project's **CI/CD > Pipelines** and click **Run pipeline**.
+1. Select the name of the project you want to [fork](../../../../user/project/working_with_projects.md#fork-a-project).
+1. In the top right, select **Fork** and then choose a namespace to fork to.
+1. For your project, on the left sidebar, select **CI/CD > Pipelines** and then **Run pipeline**.
GitLab CI/CD builds and deploys your site.
The site can take approximately 30 minutes to deploy.
diff --git a/doc/user/project/repository/forking_workflow.md b/doc/user/project/repository/forking_workflow.md
index bf4ef21e31b..13fd9572dc5 100644
--- a/doc/user/project/repository/forking_workflow.md
+++ b/doc/user/project/repository/forking_workflow.md
@@ -101,7 +101,7 @@ To use it, follow the instructions at [Creating a fork](#creating-a-fork) and pr
- The project name.
- The project URL.
- The project slug.
-- *(Optional)* The project description.
+- Optional. The project description.
- The visibility level for your fork.
### Enable or disable the fork project form **(FREE SELF)**
diff --git a/doc/user/project/working_with_projects.md b/doc/user/project/working_with_projects.md
index 1c95f2bb5ec..03e7ca6db4a 100644
--- a/doc/user/project/working_with_projects.md
+++ b/doc/user/project/working_with_projects.md
@@ -194,7 +194,7 @@ To push a new project:
remote: The private project namespace/myproject was created.
```
-1. (Optional) To configure the remote, alter the command
+1. Optional. To configure the remote, alter the command
`git remote add origin https://gitlab.example.com/namespace/myproject.git`
to match your namespace and project names.
diff --git a/lib/api/terraform/state.rb b/lib/api/terraform/state.rb
index f6dfbcafbb6..29e71611092 100644
--- a/lib/api/terraform/state.rb
+++ b/lib/api/terraform/state.rb
@@ -11,6 +11,13 @@ module API
default_format :json
+ rescue_from(
+ ::ActiveRecord::RecordNotUnique,
+ ::PG::UniqueViolation
+ ) do |e|
+ render_api_error!(e.message, 422)
+ end
+
before do
authenticate!
authorize! :read_terraform_state, user_project
diff --git a/lib/gitlab/utils/strong_memoize.rb b/lib/gitlab/utils/strong_memoize.rb
index 483bfe12c68..255fa0169bf 100644
--- a/lib/gitlab/utils/strong_memoize.rb
+++ b/lib/gitlab/utils/strong_memoize.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-require_dependency 'gitlab/utils'
-
module Gitlab
module Utils
module StrongMemoize
diff --git a/lib/sidebars/projects/menus/infrastructure_menu.rb b/lib/sidebars/projects/menus/infrastructure_menu.rb
index ccc4787601a..3a08aeb9116 100644
--- a/lib/sidebars/projects/menus/infrastructure_menu.rb
+++ b/lib/sidebars/projects/menus/infrastructure_menu.rb
@@ -90,7 +90,7 @@ module Sidebars
end
def google_cloud_menu_item
- feature_is_enabled = Feature.enabled?(:incubation_5mp_google_cloud)
+ feature_is_enabled = Feature.enabled?(:incubation_5mp_google_cloud, context.project)
user_has_permissions = can?(context.current_user, :admin_project_google_cloud, context.project)
unless feature_is_enabled && user_has_permissions
@@ -100,7 +100,7 @@ module Sidebars
::Sidebars::MenuItem.new(
title: _('Google Cloud'),
link: project_google_cloud_index_path(context.project),
- active_routes: { controller: :google_cloud },
+ active_routes: { controller: [:google_cloud, :service_accounts] },
item_id: :google_cloud
)
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index d16a6726157..de1793a7e8c 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -37172,7 +37172,7 @@ msgstr ""
msgid "Unknown response text"
msgstr ""
-msgid "Unknown root"
+msgid "Unknown screen"
msgstr ""
msgid "Unknown user"
diff --git a/metrics_server/dependencies.rb b/metrics_server/dependencies.rb
new file mode 100644
index 00000000000..ecfef502feb
--- /dev/null
+++ b/metrics_server/dependencies.rb
@@ -0,0 +1,25 @@
+# rubocop:disable Naming/FileName
+# frozen_string_literal: true
+
+require 'shellwords'
+require 'fileutils'
+
+require 'active_support/concern'
+require 'active_support/inflector'
+
+require 'prometheus/client'
+require 'rack'
+
+require_relative 'settings_overrides'
+
+require_relative '../lib/gitlab/daemon'
+require_relative '../lib/gitlab/utils/strong_memoize'
+require_relative '../lib/prometheus/cleanup_multiproc_dir_service'
+require_relative '../lib/gitlab/metrics/prometheus'
+require_relative '../lib/gitlab/metrics'
+require_relative '../lib/gitlab/metrics/exporter/base_exporter'
+require_relative '../lib/gitlab/metrics/exporter/sidekiq_exporter'
+require_relative '../lib/gitlab/health_checks/probes/collection'
+require_relative '../lib/gitlab/health_checks/probes/status'
+
+# rubocop:enable Naming/FileName
diff --git a/metrics_server/metrics_server.rb b/metrics_server/metrics_server.rb
new file mode 100644
index 00000000000..09171d8220b
--- /dev/null
+++ b/metrics_server/metrics_server.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require_relative '../config/bundler_setup'
+
+require_relative 'dependencies'
+
+class MetricsServer # rubocop:disable Gitlab/NamespacedClass
+ class << self
+ def spawn(target, gitlab_config: nil)
+ cmd = "#{Rails.root}/bin/metrics-server"
+ env = {
+ 'METRICS_SERVER_TARGET' => target,
+ 'GITLAB_CONFIG' => gitlab_config
+ }
+
+ Process.spawn(env, cmd, err: $stderr, out: $stdout).tap do |pid|
+ Process.detach(pid)
+ end
+ end
+ end
+
+ def initialize(target, metrics_dir)
+ @target = target
+ @metrics_dir = metrics_dir
+ end
+
+ def start
+ ::Prometheus::Client.configure do |config|
+ config.multiprocess_files_dir = @metrics_dir
+ end
+
+ FileUtils.mkdir_p(@metrics_dir, mode: 0700)
+ ::Prometheus::CleanupMultiprocDirService.new.execute
+
+ settings = Settings.monitoring.sidekiq_exporter
+ exporter_class = "Gitlab::Metrics::Exporter::#{@target.camelize}Exporter".constantize
+ server = exporter_class.instance(settings, synchronous: true)
+
+ server.start
+ end
+end
diff --git a/metrics_server/settings_overrides.rb b/metrics_server/settings_overrides.rb
new file mode 100644
index 00000000000..4239f62ad1c
--- /dev/null
+++ b/metrics_server/settings_overrides.rb
@@ -0,0 +1,14 @@
+# rubocop:disable Naming/FileName
+# frozen_string_literal: true
+
+# Sidekiq-cluster code is loaded both inside a Rails/Rspec
+# context as well as outside of it via CLI invocation. When it
+# is loaded outside of a Rails/Rspec context we do not have access
+# to all necessary constants. For example, we need Rails.root to
+# determine the location of bin/metrics-server.
+# Here we make the necessary constants available conditionally.
+require_relative '../scripts/override_rails_constants' unless Object.const_defined?('Rails')
+
+require_relative '../config/settings'
+
+# rubocop:enable Naming/FileName
diff --git a/scripts/override_rails_constants.rb b/scripts/override_rails_constants.rb
new file mode 100644
index 00000000000..1b255dd0011
--- /dev/null
+++ b/scripts/override_rails_constants.rb
@@ -0,0 +1,20 @@
+# rubocop:disable Naming/FileName
+# frozen_string_literal: true
+
+require 'active_support/environment_inquirer'
+
+module Rails # rubocop:disable Gitlab/NamespacedClass
+ extend self
+
+ def env
+ @env ||= ActiveSupport::EnvironmentInquirer.new(
+ ENV["RAILS_ENV"].presence || ENV["RACK_ENV"].presence || "test"
+ )
+ end
+
+ def root
+ Pathname.new(File.expand_path('..', __dir__))
+ end
+end
+
+# rubocop:enable Naming/FileName
diff --git a/scripts/setup-test-env b/scripts/setup-test-env
index a81aaa5cda3..c955a01d769 100755
--- a/scripts/setup-test-env
+++ b/scripts/setup-test-env
@@ -13,17 +13,7 @@ require 'active_support/string_inquirer'
ENV['SKIP_RAILS_ENV_IN_RAKE'] = 'true'
-module Rails
- extend self
-
- def root
- Pathname.new(File.expand_path('..', __dir__))
- end
-
- def env
- @_env ||= ActiveSupport::StringInquirer.new(ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "test")
- end
-end
+require_relative 'override_rails_constants'
ActiveSupport::Dependencies.autoload_paths << 'lib'
diff --git a/scripts/trigger-build b/scripts/trigger-build
index e5fa55f8582..11f9f5cb07c 100755
--- a/scripts/trigger-build
+++ b/scripts/trigger-build
@@ -155,7 +155,8 @@ module Trigger
'ee' => Trigger.ee? ? 'true' : 'false',
'QA_BRANCH' => ENV['QA_BRANCH'] || 'master',
'CACHE_UPDATE' => ENV['OMNIBUS_GITLAB_CACHE_UPDATE'],
- 'GITLAB_QA_OPTIONS' => ENV['GITLAB_QA_OPTIONS']
+ 'GITLAB_QA_OPTIONS' => ENV['GITLAB_QA_OPTIONS'],
+ 'QA_TESTS' => ENV['QA_TESTS']
}
end
end
diff --git a/spec/commands/metrics_server/metrics_server_spec.rb b/spec/commands/metrics_server/metrics_server_spec.rb
new file mode 100644
index 00000000000..4eff9136f2b
--- /dev/null
+++ b/spec/commands/metrics_server/metrics_server_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require_relative '../../../metrics_server/metrics_server'
+
+# End-to-end tests for the metrics server process we use to serve metrics
+# from forking applications (Sidekiq, Puma) to the Prometheus scraper.
+RSpec.describe 'bin/metrics-server', :aggregate_failures do
+ let(:config_file) { Tempfile.new('gitlab.yml') }
+ let(:config) do
+ {
+ 'test' => {
+ 'monitoring' => {
+ 'sidekiq_exporter' => {
+ 'address' => 'localhost',
+ 'enabled' => true,
+ 'port' => 3807
+ }
+ }
+ }
+ }
+ end
+
+ context 'with a running server' do
+ before do
+ # We need to send a request to localhost
+ WebMock.allow_net_connect!
+
+ config_file.write(YAML.dump(config))
+ config_file.close
+ @pid = MetricsServer.spawn('sidekiq', gitlab_config: config_file.path)
+ end
+
+ after do
+ webmock_enable!
+
+ if @pid
+ Timeout.timeout(5) do
+ Process.kill('TERM', @pid)
+ Process.waitpid(@pid)
+ end
+ end
+ rescue Errno::ESRCH => _
+ # 'No such process' means the process died before
+ ensure
+ config_file.unlink
+ end
+
+ it 'serves /metrics endpoint' do
+ expect do
+ Timeout.timeout(5) do
+ http_ok = false
+ until http_ok
+ sleep 1
+ response = Gitlab::HTTP.try_get("http://localhost:3807/metrics", allow_local_requests: true)
+ http_ok = response&.success?
+ end
+ end
+ end.not_to raise_error
+ end
+ end
+end
diff --git a/spec/frontend/google_cloud/components/app_spec.js b/spec/frontend/google_cloud/components/app_spec.js
new file mode 100644
index 00000000000..64e5ac8586f
--- /dev/null
+++ b/spec/frontend/google_cloud/components/app_spec.js
@@ -0,0 +1,115 @@
+import { shallowMount } from '@vue/test-utils';
+import App from '~/google_cloud/components/app.vue';
+import Home from '~/google_cloud/components/home.vue';
+import IncubationBanner from '~/google_cloud/components/incubation_banner.vue';
+import ServiceAccountsForm from '~/google_cloud/components/service_accounts_form.vue';
+import GcpError from '~/google_cloud/components/errors/gcp_error.vue';
+import NoGcpProjects from '~/google_cloud/components/errors/no_gcp_projects.vue';
+
+const BASE_FEEDBACK_URL =
+ 'https://gitlab.com/gitlab-org/incubation-engineering/five-minute-production/meta/-/issues/new';
+
+describe('google_cloud App component', () => {
+ let wrapper;
+
+ const findIncubationBanner = () => wrapper.findComponent(IncubationBanner);
+ const findGcpError = () => wrapper.findComponent(GcpError);
+ const findNoGcpProjects = () => wrapper.findComponent(NoGcpProjects);
+ const findServiceAccountsForm = () => wrapper.findComponent(ServiceAccountsForm);
+ const findHome = () => wrapper.findComponent(Home);
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('for gcp_error screen', () => {
+ beforeEach(() => {
+ const propsData = {
+ screen: 'gcp_error',
+ error: 'mock_gcp_client_error',
+ };
+ wrapper = shallowMount(App, { propsData });
+ });
+
+ it('renders the gcp_error screen', () => {
+ expect(findGcpError().exists()).toBe(true);
+ });
+
+ it('should contain incubation banner', () => {
+ expect(findIncubationBanner().props()).toEqual({
+ shareFeedbackUrl: `${BASE_FEEDBACK_URL}?issuable_template=general_feedback`,
+ reportBugUrl: `${BASE_FEEDBACK_URL}?issuable_template=report_bug`,
+ featureRequestUrl: `${BASE_FEEDBACK_URL}?issuable_template=feature_request`,
+ });
+ });
+ });
+
+ describe('for no_gcp_projects screen', () => {
+ beforeEach(() => {
+ const propsData = {
+ screen: 'no_gcp_projects',
+ };
+ wrapper = shallowMount(App, { propsData });
+ });
+
+ it('renders the no_gcp_projects screen', () => {
+ expect(findNoGcpProjects().exists()).toBe(true);
+ });
+
+ it('should contain incubation banner', () => {
+ expect(findIncubationBanner().props()).toEqual({
+ shareFeedbackUrl: `${BASE_FEEDBACK_URL}?issuable_template=general_feedback`,
+ reportBugUrl: `${BASE_FEEDBACK_URL}?issuable_template=report_bug`,
+ featureRequestUrl: `${BASE_FEEDBACK_URL}?issuable_template=feature_request`,
+ });
+ });
+ });
+
+ describe('for service_accounts_form screen', () => {
+ beforeEach(() => {
+ const propsData = {
+ screen: 'service_accounts_form',
+ gcpProjects: [1, 2, 3],
+ environments: [4, 5, 6],
+ cancelPath: '',
+ };
+ wrapper = shallowMount(App, { propsData });
+ });
+
+ it('renders the service_accounts_form screen', () => {
+ expect(findServiceAccountsForm().exists()).toBe(true);
+ });
+
+ it('should contain incubation banner', () => {
+ expect(findIncubationBanner().props()).toEqual({
+ shareFeedbackUrl: `${BASE_FEEDBACK_URL}?issuable_template=general_feedback`,
+ reportBugUrl: `${BASE_FEEDBACK_URL}?issuable_template=report_bug`,
+ featureRequestUrl: `${BASE_FEEDBACK_URL}?issuable_template=feature_request`,
+ });
+ });
+ });
+
+ describe('for home screen', () => {
+ beforeEach(() => {
+ const propsData = {
+ screen: 'home',
+ serviceAccounts: [{}, {}],
+ createServiceAccountUrl: '#url-create-service-account',
+ emptyIllustrationUrl: '#url-empty-illustration',
+ };
+ wrapper = shallowMount(App, { propsData });
+ });
+
+ it('renders the home screen', () => {
+ expect(findHome().exists()).toBe(true);
+ });
+
+ it('should contain incubation banner', () => {
+ expect(findIncubationBanner().props()).toEqual({
+ shareFeedbackUrl: `${BASE_FEEDBACK_URL}?issuable_template=general_feedback`,
+ reportBugUrl: `${BASE_FEEDBACK_URL}?issuable_template=report_bug`,
+ featureRequestUrl: `${BASE_FEEDBACK_URL}?issuable_template=feature_request`,
+ });
+ });
+ });
+});
diff --git a/spec/frontend/google_cloud/components/home_spec.js b/spec/frontend/google_cloud/components/home_spec.js
new file mode 100644
index 00000000000..9b4c3a79f11
--- /dev/null
+++ b/spec/frontend/google_cloud/components/home_spec.js
@@ -0,0 +1,61 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlTab, GlTabs } from '@gitlab/ui';
+import Home from '~/google_cloud/components/home.vue';
+import ServiceAccountsList from '~/google_cloud/components/service_accounts_list.vue';
+
+describe('google_cloud Home component', () => {
+ let wrapper;
+
+ const findTabs = () => wrapper.findComponent(GlTabs);
+ const findTabItems = () => findTabs().findAllComponents(GlTab);
+ const findTabItemsModel = () =>
+ findTabs()
+ .findAllComponents(GlTab)
+ .wrappers.map((x) => ({
+ title: x.attributes('title'),
+ disabled: x.attributes('disabled'),
+ }));
+
+ const TEST_HOME_PROPS = {
+ serviceAccounts: [{}, {}],
+ createServiceAccountUrl: '#url-create-service-account',
+ emptyIllustrationUrl: '#url-empty-illustration',
+ };
+
+ beforeEach(() => {
+ const propsData = {
+ screen: 'home',
+ ...TEST_HOME_PROPS,
+ };
+ wrapper = shallowMount(Home, { propsData });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('google_cloud App tabs', () => {
+ it('should contain tabs', () => {
+ expect(findTabs().exists()).toBe(true);
+ });
+
+ it('should contain three tab items', () => {
+ expect(findTabItemsModel()).toEqual([
+ { title: 'Configuration', disabled: undefined },
+ { title: 'Deployments', disabled: '' },
+ { title: 'Services', disabled: '' },
+ ]);
+ });
+
+ describe('configuration tab', () => {
+ it('should contain service accounts component', () => {
+ const serviceAccounts = findTabItems().at(0).findComponent(ServiceAccountsList);
+ expect(serviceAccounts.props()).toEqual({
+ list: TEST_HOME_PROPS.serviceAccounts,
+ createUrl: TEST_HOME_PROPS.createServiceAccountUrl,
+ emptyIllustrationUrl: TEST_HOME_PROPS.emptyIllustrationUrl,
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/google_cloud/components/screens/app_spec.js b/spec/frontend/google_cloud/components/screens/app_spec.js
deleted file mode 100644
index bd766a79f51..00000000000
--- a/spec/frontend/google_cloud/components/screens/app_spec.js
+++ /dev/null
@@ -1,66 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlTab, GlTabs } from '@gitlab/ui';
-import App from '~/google_cloud/components/screens/app.vue';
-import IncubationBanner from '~/google_cloud/components/incubation_banner.vue';
-import ServiceAccountsList from '~/google_cloud/components/service_accounts_list.vue';
-
-describe('google_cloud App component', () => {
- let wrapper;
-
- const findIncubationBanner = () => wrapper.findComponent(IncubationBanner);
- const findTabs = () => wrapper.findComponent(GlTabs);
- const findTabItems = () => findTabs().findAllComponents(GlTab);
- const findConfigurationTab = () => findTabItems().at(0);
- const findDeploymentTab = () => findTabItems().at(1);
- const findServicesTab = () => findTabItems().at(2);
- const findServiceAccountsList = () => findConfigurationTab().findComponent(ServiceAccountsList);
-
- beforeEach(() => {
- const propsData = {
- serviceAccounts: [{}, {}],
- createServiceAccountUrl: '#url-create-service-account',
- emptyIllustrationUrl: '#url-empty-illustration',
- };
- wrapper = shallowMount(App, { propsData });
- });
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('should contain incubation banner', () => {
- expect(findIncubationBanner().exists()).toBe(true);
- });
-
- describe('google_cloud App tabs', () => {
- it('should contain tabs', () => {
- expect(findTabs().exists()).toBe(true);
- });
-
- it('should contain three tab items', () => {
- expect(findTabItems().length).toBe(3);
- });
-
- describe('configuration tab', () => {
- it('should exist', () => {
- expect(findConfigurationTab().exists()).toBe(true);
- });
-
- it('should contain service accounts component', () => {
- expect(findServiceAccountsList().exists()).toBe(true);
- });
- });
-
- describe('deployments tab', () => {
- it('should exist', () => {
- expect(findDeploymentTab().exists()).toBe(true);
- });
- });
-
- describe('services tab', () => {
- it('should exist', () => {
- expect(findServicesTab().exists()).toBe(true);
- });
- });
- });
-});
diff --git a/spec/frontend/google_cloud/components/screens/service_accounts_form_spec.js b/spec/frontend/google_cloud/components/service_accounts_form_spec.js
index 3899b7d8a9d..5394d0cdaef 100644
--- a/spec/frontend/google_cloud/components/screens/service_accounts_form_spec.js
+++ b/spec/frontend/google_cloud/components/service_accounts_form_spec.js
@@ -1,12 +1,10 @@
import { shallowMount } from '@vue/test-utils';
import { GlButton, GlFormGroup, GlFormSelect } from '@gitlab/ui';
-import IncubationBanner from '~/google_cloud/components/incubation_banner.vue';
-import ServiceAccountsForm from '~/google_cloud/components/screens/service_accounts_form.vue';
+import ServiceAccountsForm from '~/google_cloud/components/service_accounts_form.vue';
describe('ServiceAccountsForm component', () => {
let wrapper;
- const findIncubationBanner = () => wrapper.findComponent(IncubationBanner);
const findHeader = () => wrapper.find('header');
const findAllFormGroups = () => wrapper.findAllComponents(GlFormGroup);
const findAllFormSelects = () => wrapper.findAllComponents(GlFormSelect);
@@ -22,10 +20,6 @@ describe('ServiceAccountsForm component', () => {
wrapper.destroy();
});
- it('contains incubation banner', () => {
- expect(findIncubationBanner().exists()).toBe(true);
- });
-
it('contains header', () => {
expect(findHeader().exists()).toBe(true);
});
diff --git a/spec/frontend/integrations/edit/components/integration_form_spec.js b/spec/frontend/integrations/edit/components/integration_form_spec.js
index 0a9cbadb249..6767714d214 100644
--- a/spec/frontend/integrations/edit/components/integration_form_spec.js
+++ b/spec/frontend/integrations/edit/components/integration_form_spec.js
@@ -16,6 +16,7 @@ import { createStore } from '~/integrations/edit/store';
describe('IntegrationForm', () => {
let wrapper;
+ let dispatch;
const createComponent = ({
customStateProps = {},
@@ -23,12 +24,15 @@ describe('IntegrationForm', () => {
initialState = {},
props = {},
} = {}) => {
+ const store = createStore({
+ customState: { ...mockIntegrationProps, ...customStateProps },
+ ...initialState,
+ });
+ dispatch = jest.spyOn(store, 'dispatch').mockImplementation();
+
wrapper = shallowMountExtended(IntegrationForm, {
propsData: { ...props },
- store: createStore({
- customState: { ...mockIntegrationProps, ...customStateProps },
- ...initialState,
- }),
+ store,
stubs: {
OverrideDropdown,
ActiveCheckbox,
@@ -195,13 +199,29 @@ describe('IntegrationForm', () => {
});
describe('type is "jira"', () => {
- it('renders JiraTriggerFields', () => {
+ beforeEach(() => {
+ jest.spyOn(document, 'querySelector').mockReturnValue(document.createElement('form'));
+
createComponent({
- customStateProps: { type: 'jira' },
+ customStateProps: { type: 'jira', testPath: '/test' },
});
+ });
+ it('renders JiraTriggerFields', () => {
expect(findJiraTriggerFields().exists()).toBe(true);
});
+
+ it('renders JiraIssuesFields', () => {
+ expect(findJiraIssuesFields().exists()).toBe(true);
+ });
+
+ describe('when JiraIssueFields emits `request-jira-issue-types` event', () => {
+ it('dispatches `requestJiraIssueTypes` action', () => {
+ findJiraIssuesFields().vm.$emit('request-jira-issue-types');
+
+ expect(dispatch).toHaveBeenCalledWith('requestJiraIssueTypes', expect.any(FormData));
+ });
+ });
});
describe('triggerEvents is present', () => {
diff --git a/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js b/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js
index 3a664b652ac..b5a8eed3598 100644
--- a/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js
+++ b/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js
@@ -1,10 +1,7 @@
import { GlFormCheckbox, GlFormInput } from '@gitlab/ui';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import {
- GET_JIRA_ISSUE_TYPES_EVENT,
- VALIDATE_INTEGRATION_FORM_EVENT,
-} from '~/integrations/constants';
+import { VALIDATE_INTEGRATION_FORM_EVENT } from '~/integrations/constants';
import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue';
import eventHub from '~/integrations/edit/event_hub';
import { createStore } from '~/integrations/edit/store';
@@ -216,13 +213,11 @@ describe('JiraIssuesFields', () => {
);
});
- it('emits "getJiraIssueTypes" to the eventHub when the jira-vulnerabilities component requests to fetch issue types', async () => {
- const eventHubEmitSpy = jest.spyOn(eventHub, '$emit');
-
+ it('emits "request-jira-issue-types` when the jira-vulnerabilities component requests to fetch issue types', async () => {
await setEnableCheckbox(true);
- await findJiraForVulnerabilities().vm.$emit('request-get-issue-types');
+ await findJiraForVulnerabilities().vm.$emit('request-jira-issue-types');
- expect(eventHubEmitSpy).toHaveBeenCalledWith(GET_JIRA_ISSUE_TYPES_EVENT);
+ expect(wrapper.emitted('request-jira-issue-types')).toHaveLength(1);
});
});
diff --git a/spec/frontend/integrations/edit/mock_data.js b/spec/frontend/integrations/edit/mock_data.js
index 27ba0768331..3c45ed0fb1b 100644
--- a/spec/frontend/integrations/edit/mock_data.js
+++ b/spec/frontend/integrations/edit/mock_data.js
@@ -14,3 +14,9 @@ export const mockIntegrationProps = {
type: '',
inheritFromId: 25,
};
+
+export const mockJiraIssueTypes = [
+ { id: '1', name: 'issue', description: 'issue' },
+ { id: '2', name: 'bug', description: 'bug' },
+ { id: '3', name: 'epic', description: 'epic' },
+];
diff --git a/spec/frontend/integrations/edit/store/actions_spec.js b/spec/frontend/integrations/edit/store/actions_spec.js
index e2f4c138ece..3cf69c65126 100644
--- a/spec/frontend/integrations/edit/store/actions_spec.js
+++ b/spec/frontend/integrations/edit/store/actions_spec.js
@@ -1,4 +1,7 @@
+import axios from 'axios';
+import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
+import { I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE } from '~/integrations/constants';
import {
setOverride,
setIsSaving,
@@ -14,14 +17,21 @@ import {
import * as types from '~/integrations/edit/store/mutation_types';
import createState from '~/integrations/edit/store/state';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
+import { mockJiraIssueTypes } from '../mock_data';
jest.mock('~/lib/utils/url_utility');
describe('Integration form store actions', () => {
let state;
+ let mockAxios;
beforeEach(() => {
state = createState();
+ mockAxios = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mockAxios.restore();
});
describe('setOverride', () => {
@@ -75,11 +85,28 @@ describe('Integration form store actions', () => {
});
describe('requestJiraIssueTypes', () => {
- it('should commit SET_JIRA_ISSUE_TYPES_ERROR_MESSAGE and SET_IS_LOADING_JIRA_ISSUE_TYPES mutations', () => {
- return testAction(requestJiraIssueTypes, null, state, [
- { type: types.SET_JIRA_ISSUE_TYPES_ERROR_MESSAGE, payload: '' },
- { type: types.SET_IS_LOADING_JIRA_ISSUE_TYPES, payload: true },
- ]);
+ describe.each`
+ scenario | responseCode | response | action
+ ${'when successful'} | ${200} | ${{ issuetypes: mockJiraIssueTypes }} | ${{ type: 'receiveJiraIssueTypesSuccess', payload: mockJiraIssueTypes }}
+ ${'when response has no issue types'} | ${200} | ${{ issuetypes: [] }} | ${{ type: 'receiveJiraIssueTypesError', payload: I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE }}
+ ${'when response includes error'} | ${200} | ${{ error: new Error() }} | ${{ type: 'receiveJiraIssueTypesError', payload: I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE }}
+ ${'when error occurs'} | ${500} | ${{}} | ${{ type: 'receiveJiraIssueTypesError', payload: expect.any(String) }}
+ `('$scenario', ({ responseCode, response, action }) => {
+ it(`should commit SET_JIRA_ISSUE_TYPES_ERROR_MESSAGE and SET_IS_LOADING_JIRA_ISSUE_TYPES mutations, and dispatch ${action.type}`, () => {
+ mockAxios.onPut('/test').replyOnce(responseCode, response);
+
+ return testAction(
+ requestJiraIssueTypes,
+ new FormData(),
+ { propsSource: { testPath: '/test' } },
+ [
+ // should clear the error messages and set the loading state
+ { type: types.SET_JIRA_ISSUE_TYPES_ERROR_MESSAGE, payload: '' },
+ { type: types.SET_IS_LOADING_JIRA_ISSUE_TYPES, payload: true },
+ ],
+ [action],
+ );
+ });
});
});
diff --git a/spec/frontend/integrations/integration_settings_form_spec.js b/spec/frontend/integrations/integration_settings_form_spec.js
index c35d178e518..dc9a551e078 100644
--- a/spec/frontend/integrations/integration_settings_form_spec.js
+++ b/spec/frontend/integrations/integration_settings_form_spec.js
@@ -4,10 +4,8 @@ import eventHub from '~/integrations/edit/event_hub';
import axios from '~/lib/utils/axios_utils';
import toast from '~/vue_shared/plugins/global_toast';
import {
- I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE,
I18N_SUCCESSFUL_CONNECTION_MESSAGE,
I18N_DEFAULT_ERROR_MESSAGE,
- GET_JIRA_ISSUE_TYPES_EVENT,
TOGGLE_INTEGRATION_EVENT,
TEST_INTEGRATION_EVENT,
SAVE_INTEGRATION_EVENT,
@@ -154,62 +152,6 @@ describe('IntegrationSettingsForm', () => {
});
});
- describe('when event hub receives `GET_JIRA_ISSUE_TYPES_EVENT`', () => {
- it('should always dispatch `requestJiraIssueTypes`', () => {
- const dispatchSpy = mockStoreDispatch();
- mockAxios.onPut(integrationSettingsForm.testEndPoint).networkError();
-
- eventHub.$emit(GET_JIRA_ISSUE_TYPES_EVENT);
-
- expect(dispatchSpy).toHaveBeenCalledWith('requestJiraIssueTypes');
- });
-
- it('should make an ajax request with provided `formData`', () => {
- eventHub.$emit(GET_JIRA_ISSUE_TYPES_EVENT);
-
- expect(axios.put).toHaveBeenCalledWith(
- integrationSettingsForm.testEndPoint,
- new FormData(integrationSettingsForm.$form),
- );
- });
-
- it('should dispatch `receiveJiraIssueTypesSuccess` with the correct payload if ajax request is successful', async () => {
- const dispatchSpy = mockStoreDispatch();
- const mockData = ['ISSUE', 'EPIC'];
- mockAxios.onPut(integrationSettingsForm.testEndPoint).reply(200, {
- error: false,
- issuetypes: mockData,
- });
-
- eventHub.$emit(GET_JIRA_ISSUE_TYPES_EVENT);
- await waitForPromises();
-
- expect(dispatchSpy).toHaveBeenCalledWith('receiveJiraIssueTypesSuccess', mockData);
- });
-
- it.each(['Custom error message here', undefined])(
- 'should dispatch "receiveJiraIssueTypesError" with a message if the backend responds with error',
- async (responseErrorMessage) => {
- const dispatchSpy = mockStoreDispatch();
-
- const expectedErrorMessage =
- responseErrorMessage || I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE;
- mockAxios.onPut(integrationSettingsForm.testEndPoint).reply(200, {
- error: true,
- message: responseErrorMessage,
- });
-
- eventHub.$emit(GET_JIRA_ISSUE_TYPES_EVENT);
- await waitForPromises();
-
- expect(dispatchSpy).toHaveBeenCalledWith(
- 'receiveJiraIssueTypesError',
- expectedErrorMessage,
- );
- },
- );
- });
-
describe('when event hub receives `SAVE_INTEGRATION_EVENT`', () => {
describe('when form is valid', () => {
beforeEach(() => {
diff --git a/spec/metrics_server/metrics_server_spec.rb b/spec/metrics_server/metrics_server_spec.rb
new file mode 100644
index 00000000000..58556d2cf5a
--- /dev/null
+++ b/spec/metrics_server/metrics_server_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+require_relative '../../metrics_server/metrics_server'
+require_relative '../support/helpers/next_instance_of'
+
+RSpec.describe MetricsServer do # rubocop:disable RSpec/FilePath
+ include NextInstanceOf
+
+ describe '.spawn' do
+ let(:env) do
+ {
+ 'METRICS_SERVER_TARGET' => 'sidekiq',
+ 'GITLAB_CONFIG' => nil
+ }
+ end
+
+ it 'spawns a process with the correct environment variables and detaches it' do
+ expect(Process).to receive(:spawn).with(env, anything, err: $stderr, out: $stdout).and_return(99)
+ expect(Process).to receive(:detach).with(99)
+
+ described_class.spawn('sidekiq')
+ end
+ end
+
+ describe '#start' do
+ let(:exporter_class) { Class.new(Gitlab::Metrics::Exporter::BaseExporter) }
+ let(:exporter_double) { double('fake_exporter', start: true) }
+ let(:prometheus_client_double) { double(::Prometheus::Client) }
+ let(:prometheus_config) { ::Prometheus::Client::Configuration.new }
+ let(:metrics_dir) { Dir.mktmpdir }
+ let(:settings_double) { double(:settings, sidekiq_exporter: {}) }
+
+ subject(:metrics_server) { described_class.new('fake', metrics_dir)}
+
+ before do
+ stub_env('prometheus_multiproc_dir', metrics_dir)
+ stub_const('Gitlab::Metrics::Exporter::FakeExporter', exporter_class)
+ allow(exporter_class).to receive(:instance).with({}, synchronous: true).and_return(exporter_double)
+ allow(Settings).to receive(:monitoring).and_return(settings_double)
+ end
+
+ after do
+ Dir.rmdir(metrics_dir)
+ end
+
+ it 'configures ::Prometheus::Client' do
+ allow(prometheus_client_double).to receive(:configuration).and_return(prometheus_config)
+
+ metrics_server.start
+
+ expect(prometheus_config.multiprocess_files_dir).to eq metrics_dir
+ end
+
+ it 'ensures that metrics directory exists in correct mode (0700)' do
+ expect(FileUtils).to receive(:mkdir_p).with(metrics_dir, mode: 0700)
+
+ metrics_server.start
+ end
+
+ it 'removes any old metrics files' do
+ FileUtils.touch("#{metrics_dir}/remove_this.db")
+
+ expect { metrics_server.start }.to change { Dir.empty?(metrics_dir) }.from(false).to(true)
+ end
+
+ it 'starts a metrics server' do
+ expect(exporter_double).to receive(:start)
+
+ metrics_server.start
+ end
+
+ it 'sends the correct Settings to the exporter instance' do
+ expect(Settings).to receive(:monitoring).and_return(settings_double)
+ expect(settings_double).to receive(:sidekiq_exporter)
+
+ metrics_server.start
+ end
+ end
+end
diff --git a/spec/requests/api/terraform/state_spec.rb b/spec/requests/api/terraform/state_spec.rb
index 5d2635126e8..24f38b04348 100644
--- a/spec/requests/api/terraform/state_spec.rb
+++ b/spec/requests/api/terraform/state_spec.rb
@@ -152,6 +152,16 @@ RSpec.describe API::Terraform::State do
expect(response).to have_gitlab_http_status(:ok)
expect(Gitlab::Json.parse(response.body)).to be_empty
end
+
+ context 'when serial already exists' do
+ let(:params) { { 'instance': 'example-instance', 'serial': state.latest_version.version } }
+
+ it 'returns unprocessable entity' do
+ request
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+ end
end
context 'without body' do
diff --git a/spec/tooling/quality/test_level_spec.rb b/spec/tooling/quality/test_level_spec.rb
index 94fa9d682e1..3348e495732 100644
--- a/spec/tooling/quality/test_level_spec.rb
+++ b/spec/tooling/quality/test_level_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe Quality::TestLevel do
context 'when level is unit' do
it 'returns a pattern' do
expect(subject.pattern(:unit))
- .to eq("spec/{bin,channels,config,db,dependencies,elastic,elastic_integration,experiments,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,models,policies,presenters,rack_servers,replicators,routing,rubocop,scripts,serializers,services,sidekiq,spam,support_specs,tasks,uploaders,validators,views,workers,tooling}{,/**/}*_spec.rb")
+ .to eq("spec/{bin,channels,config,db,dependencies,elastic,elastic_integration,experiments,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,metrics_server,models,policies,presenters,rack_servers,replicators,routing,rubocop,scripts,serializers,services,sidekiq,spam,support_specs,tasks,uploaders,validators,views,workers,tooling}{,/**/}*_spec.rb")
end
end
@@ -110,7 +110,7 @@ RSpec.describe Quality::TestLevel do
context 'when level is unit' do
it 'returns a regexp' do
expect(subject.regexp(:unit))
- .to eq(%r{spec/(bin|channels|config|db|dependencies|elastic|elastic_integration|experiments|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|models|policies|presenters|rack_servers|replicators|routing|rubocop|scripts|serializers|services|sidekiq|spam|support_specs|tasks|uploaders|validators|views|workers|tooling)})
+ .to eq(%r{spec/(bin|channels|config|db|dependencies|elastic|elastic_integration|experiments|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|metrics_server|models|policies|presenters|rack_servers|replicators|routing|rubocop|scripts|serializers|services|sidekiq|spam|support_specs|tasks|uploaders|validators|views|workers|tooling)})
end
end
diff --git a/tooling/bin/qa/check_if_qa_only_spec_changes b/tooling/bin/qa/check_if_qa_only_spec_changes
new file mode 100755
index 00000000000..fd331559dd7
--- /dev/null
+++ b/tooling/bin/qa/check_if_qa_only_spec_changes
@@ -0,0 +1,18 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+
+# This script assumes the first argument is a path to a file containing a list of changed files and the second argument
+# is the path of a file where a list of end-to-end spec files with the leading 'qa/' trimmed will be written to if
+# all the files are end-to-end test spec files.
+
+abort("ERROR: Please specify the file containing the list of changed files and a file where the qa only spec files will be written") if ARGV.size != 2
+file_contents = File.read(ARGV.shift).split(' ')
+
+all_files_are_qa_specs = file_contents.all? { |file_path| file_path =~ %r{^qa\/qa\/specs\/features\/} }
+
+output_file = ARGV.shift
+
+if all_files_are_qa_specs
+ qa_spec_paths_trimmed = file_contents.map { |path| path.sub('qa/', '') }
+ File.write(output_file, qa_spec_paths_trimmed.join(' '))
+end
diff --git a/tooling/quality/test_level.rb b/tooling/quality/test_level.rb
index 5fbaad073c0..3cc1d87eb36 100644
--- a/tooling/quality/test_level.rb
+++ b/tooling/quality/test_level.rb
@@ -33,6 +33,7 @@ module Quality
initializers
javascripts
lib
+ metrics_server
models
policies
presenters