Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
498ba9dc41
commit
c657078ecb
|
@ -58,6 +58,9 @@ update-qa-cache:
|
||||||
- tooling/bin/find_change_diffs ${CHANGES_DIFFS_DIR}
|
- tooling/bin/find_change_diffs ${CHANGES_DIFFS_DIR}
|
||||||
script:
|
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=$?
|
tooling/bin/qa/package_and_qa_check ${CHANGES_DIFFS_DIR} && exit_code=$?
|
||||||
if [ $exit_code -eq 0 ]; then
|
if [ $exit_code -eq 0 ]; then
|
||||||
./scripts/trigger-build omnibus
|
./scripts/trigger-build omnibus
|
||||||
|
@ -80,9 +83,11 @@ update-qa-cache:
|
||||||
expire_in: 7d
|
expire_in: 7d
|
||||||
paths:
|
paths:
|
||||||
- ${CHANGES_FILE}
|
- ${CHANGES_FILE}
|
||||||
|
- ${ONLY_QA_CHANGES_FILE}
|
||||||
- ${CHANGES_DIFFS_DIR}/*
|
- ${CHANGES_DIFFS_DIR}/*
|
||||||
variables:
|
variables:
|
||||||
CHANGES_FILE: tmp/changed_files.txt
|
CHANGES_FILE: tmp/changed_files.txt
|
||||||
|
ONLY_QA_CHANGES_FILE: tmp/qa_only_changed_files.txt
|
||||||
CHANGES_DIFFS_DIR: tmp/diffs
|
CHANGES_DIFFS_DIR: tmp/diffs
|
||||||
|
|
||||||
.package-and-qa-ff-base:
|
.package-and-qa-ff-base:
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
|
@ -1,20 +1,14 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlButton, GlFormGroup, GlFormSelect } from '@gitlab/ui';
|
import { GlButton, GlFormGroup, GlFormSelect } from '@gitlab/ui';
|
||||||
import { __ } from '~/locale';
|
import { __ } from '~/locale';
|
||||||
import IncubationBanner from '../incubation_banner.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { GlButton, GlFormGroup, GlFormSelect, IncubationBanner },
|
components: { GlButton, GlFormGroup, GlFormSelect },
|
||||||
props: {
|
props: {
|
||||||
gcpProjects: { required: true, type: Array },
|
gcpProjects: { required: true, type: Array },
|
||||||
environments: { required: true, type: Array },
|
environments: { required: true, type: Array },
|
||||||
cancelPath: { required: true, type: String },
|
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: {
|
i18n: {
|
||||||
title: __('Create service account'),
|
title: __('Create service account'),
|
||||||
gcpProjectLabel: __('Google Cloud project'),
|
gcpProjectLabel: __('Google Cloud project'),
|
||||||
|
@ -31,11 +25,6 @@ export default {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<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">
|
<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>
|
<h2 class="gl-font-size-h1">{{ $options.i18n.title }}</h2>
|
||||||
</header>
|
</header>
|
|
@ -1,40 +1,12 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import { __ } from '~/locale';
|
import App from './components/app.vue';
|
||||||
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,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
for (let i = 0; i < rootComponentMap.length; i += 1) {
|
const root = '#js-google-cloud';
|
||||||
const { root, component } = rootComponentMap[i];
|
const element = document.querySelector(root);
|
||||||
const element = document.querySelector(root);
|
const { screen, ...attrs } = JSON.parse(element.getAttribute('data'));
|
||||||
if (element) {
|
return new Vue({
|
||||||
const props = JSON.parse(element.getAttribute('data'));
|
el: element,
|
||||||
return new Vue({ el: root, render: elementRenderer(component, props) });
|
render: (createElement) => createElement(App, { props: { screen }, attrs }),
|
||||||
}
|
});
|
||||||
}
|
|
||||||
throw new Error(__('Unknown root'));
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { s__, __ } from '~/locale';
|
||||||
|
|
||||||
export const TEST_INTEGRATION_EVENT = 'testIntegration';
|
export const TEST_INTEGRATION_EVENT = 'testIntegration';
|
||||||
export const SAVE_INTEGRATION_EVENT = 'saveIntegration';
|
export const SAVE_INTEGRATION_EVENT = 'saveIntegration';
|
||||||
export const GET_JIRA_ISSUE_TYPES_EVENT = 'getJiraIssueTypes';
|
|
||||||
export const TOGGLE_INTEGRATION_EVENT = 'toggleIntegration';
|
export const TOGGLE_INTEGRATION_EVENT = 'toggleIntegration';
|
||||||
export const VALIDATE_INTEGRATION_FORM_EVENT = 'validateIntegrationForm';
|
export const VALIDATE_INTEGRATION_FORM_EVENT = 'validateIntegrationForm';
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
};
|
|
@ -69,6 +69,10 @@ export default {
|
||||||
return this.isInstanceOrGroupLevel && this.propsSource.resetPath;
|
return this.isInstanceOrGroupLevel && this.propsSource.resetPath;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
// this form element is defined in Haml
|
||||||
|
this.form = document.querySelector('.js-integration-settings-form');
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions([
|
...mapActions([
|
||||||
'setOverride',
|
'setOverride',
|
||||||
|
@ -76,6 +80,7 @@ export default {
|
||||||
'setIsTesting',
|
'setIsTesting',
|
||||||
'setIsResetting',
|
'setIsResetting',
|
||||||
'fetchResetIntegration',
|
'fetchResetIntegration',
|
||||||
|
'requestJiraIssueTypes',
|
||||||
]),
|
]),
|
||||||
onSaveClick() {
|
onSaveClick() {
|
||||||
this.setIsSaving(true);
|
this.setIsSaving(true);
|
||||||
|
@ -88,6 +93,10 @@ export default {
|
||||||
onResetClick() {
|
onResetClick() {
|
||||||
this.fetchResetIntegration();
|
this.fetchResetIntegration();
|
||||||
},
|
},
|
||||||
|
onRequestJiraIssueTypes() {
|
||||||
|
const formData = new FormData(this.form);
|
||||||
|
this.requestJiraIssueTypes(formData);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
helpHtmlConfig: {
|
helpHtmlConfig: {
|
||||||
ADD_ATTR: ['target'], // allow external links, can be removed after https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1427 is implemented
|
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"
|
v-if="isJira && !isInstanceOrGroupLevel"
|
||||||
:key="`${currentKey}-jira-issues-fields`"
|
:key="`${currentKey}-jira-issues-fields`"
|
||||||
v-bind="propsSource.jiraIssuesProps"
|
v-bind="propsSource.jiraIssuesProps"
|
||||||
|
@request-jira-issue-types="onRequestJiraIssueTypes"
|
||||||
/>
|
/>
|
||||||
<div v-if="isEditable" class="footer-block row-content-block">
|
<div v-if="isEditable" class="footer-block row-content-block">
|
||||||
<template v-if="isInstanceOrGroupLevel">
|
<template v-if="isInstanceOrGroupLevel">
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlFormGroup, GlFormCheckbox, GlFormInput, GlSprintf, GlLink } from '@gitlab/ui';
|
import { GlFormGroup, GlFormCheckbox, GlFormInput, GlSprintf, GlLink } from '@gitlab/ui';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import {
|
import { VALIDATE_INTEGRATION_FORM_EVENT } from '~/integrations/constants';
|
||||||
VALIDATE_INTEGRATION_FORM_EVENT,
|
|
||||||
GET_JIRA_ISSUE_TYPES_EVENT,
|
|
||||||
} from '~/integrations/constants';
|
|
||||||
import { s__, __ } from '~/locale';
|
import { s__, __ } from '~/locale';
|
||||||
import eventHub from '../event_hub';
|
import eventHub from '../event_hub';
|
||||||
import JiraUpgradeCta from './jira_upgrade_cta.vue';
|
import JiraUpgradeCta from './jira_upgrade_cta.vue';
|
||||||
|
@ -91,9 +88,6 @@ export default {
|
||||||
validateForm() {
|
validateForm() {
|
||||||
this.validated = true;
|
this.validated = true;
|
||||||
},
|
},
|
||||||
getJiraIssueTypes() {
|
|
||||||
eventHub.$emit(GET_JIRA_ISSUE_TYPES_EVENT);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
i18n: {
|
i18n: {
|
||||||
sectionTitle: s__('JiraService|View Jira issues in GitLab'),
|
sectionTitle: s__('JiraService|View Jira issues in GitLab'),
|
||||||
|
@ -136,7 +130,7 @@ export default {
|
||||||
:initial-issue-type-id="initialVulnerabilitiesIssuetype"
|
:initial-issue-type-id="initialVulnerabilitiesIssuetype"
|
||||||
:show-full-feature="showJiraVulnerabilitiesIntegration"
|
:show-full-feature="showJiraVulnerabilitiesIntegration"
|
||||||
data-testid="jira-for-vulnerabilities"
|
data-testid="jira-for-vulnerabilities"
|
||||||
@request-get-issue-types="getJiraIssueTypes"
|
@request-jira-issue-types="$emit('request-jira-issue-types')"
|
||||||
/>
|
/>
|
||||||
<jira-upgrade-cta
|
<jira-upgrade-cta
|
||||||
v-if="!showJiraVulnerabilitiesIntegration"
|
v-if="!showJiraVulnerabilitiesIntegration"
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { refreshCurrentPage } from '~/lib/utils/url_utility';
|
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';
|
import * as types from './mutation_types';
|
||||||
|
|
||||||
export const setOverride = ({ commit }, override) => commit(types.SET_OVERRIDE, override);
|
export const setOverride = ({ commit }, override) => commit(types.SET_OVERRIDE, override);
|
||||||
|
@ -27,10 +34,28 @@ export const fetchResetIntegration = ({ dispatch, getters }) => {
|
||||||
.catch(() => dispatch('receiveResetIntegrationError'));
|
.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_JIRA_ISSUE_TYPES_ERROR_MESSAGE, '');
|
||||||
commit(types.SET_IS_LOADING_JIRA_ISSUE_TYPES, true);
|
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 = []) => {
|
export const receiveJiraIssueTypesSuccess = ({ commit }, issueTypes = []) => {
|
||||||
commit(types.SET_IS_LOADING_JIRA_ISSUE_TYPES, false);
|
commit(types.SET_IS_LOADING_JIRA_ISSUE_TYPES, false);
|
||||||
commit(types.SET_JIRA_ISSUE_TYPES, issueTypes);
|
commit(types.SET_JIRA_ISSUE_TYPES, issueTypes);
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
import { delay } from 'lodash';
|
import { delay } from 'lodash';
|
||||||
import toast from '~/vue_shared/plugins/global_toast';
|
import toast from '~/vue_shared/plugins/global_toast';
|
||||||
import axios from '../lib/utils/axios_utils';
|
|
||||||
import initForm from './edit';
|
import initForm from './edit';
|
||||||
import eventHub from './edit/event_hub';
|
import eventHub from './edit/event_hub';
|
||||||
import {
|
import {
|
||||||
TEST_INTEGRATION_EVENT,
|
TEST_INTEGRATION_EVENT,
|
||||||
SAVE_INTEGRATION_EVENT,
|
SAVE_INTEGRATION_EVENT,
|
||||||
GET_JIRA_ISSUE_TYPES_EVENT,
|
|
||||||
TOGGLE_INTEGRATION_EVENT,
|
TOGGLE_INTEGRATION_EVENT,
|
||||||
VALIDATE_INTEGRATION_FORM_EVENT,
|
VALIDATE_INTEGRATION_FORM_EVENT,
|
||||||
I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE,
|
|
||||||
I18N_DEFAULT_ERROR_MESSAGE,
|
I18N_DEFAULT_ERROR_MESSAGE,
|
||||||
I18N_SUCCESSFUL_CONNECTION_MESSAGE,
|
I18N_SUCCESSFUL_CONNECTION_MESSAGE,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
import { testIntegrationSettings } from './edit/api';
|
||||||
|
|
||||||
export default class IntegrationSettingsForm {
|
export default class IntegrationSettingsForm {
|
||||||
constructor(formSelector) {
|
constructor(formSelector) {
|
||||||
|
@ -41,9 +39,6 @@ export default class IntegrationSettingsForm {
|
||||||
eventHub.$on(SAVE_INTEGRATION_EVENT, () => {
|
eventHub.$on(SAVE_INTEGRATION_EVENT, () => {
|
||||||
this.saveIntegration();
|
this.saveIntegration();
|
||||||
});
|
});
|
||||||
eventHub.$on(GET_JIRA_ISSUE_TYPES_EVENT, () => {
|
|
||||||
this.getJiraIssueTypes(new FormData(this.$form));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
saveIntegration() {
|
saveIntegration() {
|
||||||
|
@ -96,43 +91,12 @@ export default class IntegrationSettingsForm {
|
||||||
*
|
*
|
||||||
* @return {Promise}
|
* @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
|
* Test Integration config
|
||||||
*/
|
*/
|
||||||
testSettings(formData) {
|
testSettings(formData) {
|
||||||
return this.fetchTestSettings(formData)
|
return testIntegrationSettings(this.testEndPoint, formData)
|
||||||
.then(({ data }) => {
|
.then(({ data }) => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
toast(`${data.message} ${data.service_response}`);
|
toast(`${data.message} ${data.service_response}`);
|
||||||
|
|
|
@ -21,6 +21,6 @@ class Projects::GoogleCloud::BaseController < Projects::ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def feature_flag_enabled!
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,10 +9,11 @@ class Projects::GoogleCloud::ServiceAccountsController < Projects::GoogleCloud::
|
||||||
gcp_projects = google_api_client.list_projects
|
gcp_projects = google_api_client.list_projects
|
||||||
|
|
||||||
if gcp_projects.empty?
|
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'
|
render status: :unauthorized, template: 'projects/google_cloud/errors/no_gcp_projects'
|
||||||
else
|
else
|
||||||
@js_data = {
|
@js_data = {
|
||||||
|
screen: 'service_accounts_form',
|
||||||
gcpProjects: gcp_projects,
|
gcpProjects: gcp_projects,
|
||||||
environments: project.environments,
|
environments: project.environments,
|
||||||
cancelPath: project_google_cloud_index_path(project)
|
cancelPath: project_google_cloud_index_path(project)
|
||||||
|
@ -78,7 +79,7 @@ class Projects::GoogleCloud::ServiceAccountsController < Projects::GoogleCloud::
|
||||||
|
|
||||||
def handle_gcp_error(error, project)
|
def handle_gcp_error(error, project)
|
||||||
Gitlab::ErrorTracking.track_exception(error, project_id: project.id)
|
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'
|
render status: :unauthorized, template: 'projects/google_cloud/errors/gcp_error'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
class Projects::GoogleCloudController < Projects::GoogleCloud::BaseController
|
class Projects::GoogleCloudController < Projects::GoogleCloud::BaseController
|
||||||
def index
|
def index
|
||||||
@js_data = {
|
@js_data = {
|
||||||
|
screen: 'home',
|
||||||
serviceAccounts: GoogleCloud::ServiceAccountsService.new(project).find_for_project,
|
serviceAccounts: GoogleCloud::ServiceAccountsService.new(project).find_for_project,
|
||||||
createServiceAccountUrl: project_google_cloud_service_accounts_path(project),
|
createServiceAccountUrl: project_google_cloud_service_accounts_path(project),
|
||||||
emptyIllustrationUrl: ActionController::Base.helpers.image_path('illustrations/pipelines_empty.svg')
|
emptyIllustrationUrl: ActionController::Base.helpers.image_path('illustrations/pipelines_empty.svg')
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
module Ci
|
module Ci
|
||||||
class JobArtifact < Ci::ApplicationRecord
|
class JobArtifact < Ci::ApplicationRecord
|
||||||
|
include IgnorableColumns
|
||||||
include AfterCommitQueue
|
include AfterCommitQueue
|
||||||
include ObjectStorage::BackgroundMove
|
include ObjectStorage::BackgroundMove
|
||||||
include UpdateProjectStatistics
|
include UpdateProjectStatistics
|
||||||
|
@ -120,6 +121,9 @@ module Ci
|
||||||
belongs_to :project
|
belongs_to :project
|
||||||
belongs_to :job, class_name: "Ci::Build", foreign_key: :job_id
|
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
|
mount_file_store_uploader JobArtifactUploader
|
||||||
|
|
||||||
skip_callback :save, :after, :store_file!, if: :store_after_commit?
|
skip_callback :save, :after, :store_file!, if: :store_after_commit?
|
||||||
|
|
|
@ -3,4 +3,4 @@
|
||||||
|
|
||||||
- @content_class = "limit-container-width" unless fluid_layout
|
- @content_class = "limit-container-width" unless fluid_layout
|
||||||
|
|
||||||
#js-google-cloud-error-gcp-error{ data: @js_data }
|
#js-google-cloud{ data: @js_data }
|
||||||
|
|
|
@ -3,4 +3,4 @@
|
||||||
|
|
||||||
- @content_class = "limit-container-width" unless fluid_layout
|
- @content_class = "limit-container-width" unless fluid_layout
|
||||||
|
|
||||||
#js-google-cloud-error-no-gcp-projects{ data: @js_data }
|
#js-google-cloud{ data: @js_data }
|
||||||
|
|
|
@ -5,4 +5,4 @@
|
||||||
- @content_class = "limit-container-width" unless fluid_layout
|
- @content_class = "limit-container-width" unless fluid_layout
|
||||||
|
|
||||||
= form_tag project_google_cloud_service_accounts_path(@project), method: 'post' do
|
= 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 }
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1 @@
|
||||||
|
88b289d724f98f75e0340cde4c6e2bc3cb55df2a979934fb2bc544d22e4c032d
|
|
@ -0,0 +1 @@
|
||||||
|
2b2c28e0370ae1bb84bee5ff769c9b313902d1f1afc50fa54e23a1627b1121f3
|
|
@ -11699,7 +11699,9 @@ CREATE TABLE ci_job_artifacts (
|
||||||
id bigint NOT NULL,
|
id bigint NOT NULL,
|
||||||
job_id bigint NOT NULL,
|
job_id bigint NOT NULL,
|
||||||
locked smallint DEFAULT 2,
|
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
|
CREATE SEQUENCE ci_job_artifacts_id_seq
|
||||||
|
|
|
@ -60,7 +60,7 @@ assumptions are made:
|
||||||
|
|
||||||
Make sure to follow all steps below:
|
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:
|
memory by shutting down the GitLab service with the following command:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
|
|
@ -42,7 +42,7 @@ to the end of the Bitbucket authorization callback URL.
|
||||||
|
|
||||||
- **Name:** This can be anything. Consider something like `<Organization>'s GitLab`
|
- **Name:** This can be anything. Consider something like `<Organization>'s GitLab`
|
||||||
or `<Your Name>'s GitLab` or something else descriptive.
|
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)
|
- **Callback URL:** (Required in GitLab versions 8.15 and greater)
|
||||||
The URL to your GitLab installation, such as
|
The URL to your GitLab installation, such as
|
||||||
`https://gitlab.example.com/users/auth`.
|
`https://gitlab.example.com/users/auth`.
|
||||||
|
|
|
@ -32,15 +32,15 @@ project, group, or instance level:
|
||||||
1. Scroll to **Add an integration**, and select **Datadog**.
|
1. Scroll to **Add an integration**, and select **Datadog**.
|
||||||
1. Select **Active** to enable the integration.
|
1. Select **Active** to enable the integration.
|
||||||
1. Specify the [**Datadog site**](https://docs.datadoghq.com/getting_started/site/) to send data to.
|
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.
|
Used only in advanced scenarios.
|
||||||
1. Provide your Datadog **API key**.
|
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.
|
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
|
environments), provide an **Env** name. This value is attached to each span
|
||||||
the integration generates.
|
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**.
|
1. Select **Save changes**.
|
||||||
|
|
||||||
When the integration sends data, you can view it in the [CI Visibility](https://app.datadoghq.com/ci)
|
When the integration sends data, you can view it in the [CI Visibility](https://app.datadoghq.com/ci)
|
||||||
|
|
|
@ -279,8 +279,8 @@ To disable the Elasticsearch integration:
|
||||||
1. On the top bar, select **Menu > Admin**.
|
1. On the top bar, select **Menu > Admin**.
|
||||||
1. On the left sidebar, select **Settings > Advanced Search**.
|
1. On the left sidebar, select **Settings > Advanced Search**.
|
||||||
1. Uncheck **Elasticsearch indexing** and **Search with Elasticsearch enabled**.
|
1. Uncheck **Elasticsearch indexing** and **Search with Elasticsearch enabled**.
|
||||||
1. Click **Save changes** for the changes to take effect.
|
1. Select **Save changes**.
|
||||||
1. (Optional) Delete the existing indexes:
|
1. Optional. Delete the existing indexes:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# Omnibus installations
|
# Omnibus installations
|
||||||
|
|
|
@ -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
|
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.
|
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.
|
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
|
payload and click **Parse payload for custom mapping**. Valid JSON is required. If you update
|
||||||
a sample payload, you must also remap the fields.
|
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).
|
**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
|
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.
|
from your integration's **Send test alert** tab after the integration is created.
|
||||||
|
|
|
@ -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 |
|
| 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/`. |
|
| 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). |
|
| 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:
|
To add a different cluster for each environment:
|
||||||
|
|
|
@ -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_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_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_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`,
|
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`,
|
`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`,
|
||||||
|
|
|
@ -321,7 +321,7 @@ To share a group after enabling this feature:
|
||||||
1. Go to your group's page.
|
1. Go to your group's page.
|
||||||
1. On the left sidebar, go to **Group information > Members**, and then select **Invite a group**.
|
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. 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**.
|
1. Select **Invite**.
|
||||||
|
|
||||||
## Manage group memberships via LDAP **(PREMIUM SELF)**
|
## Manage group memberships via LDAP **(PREMIUM SELF)**
|
||||||
|
|
|
@ -37,7 +37,7 @@ Complete these steps in GitLab:
|
||||||
1. Select **Asana**.
|
1. Select **Asana**.
|
||||||
1. Ensure that the **Active** toggle is enabled.
|
1. Ensure that the **Active** toggle is enabled.
|
||||||
1. Paste the token you generated in Asana.
|
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.
|
field, separated with commas.
|
||||||
1. Select **Save changes** or optionally select **Test settings**.
|
1. Select **Save changes** or optionally select **Test settings**.
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ Select a room and create a webhook:
|
||||||
1. Enter the room where you want to receive notifications from GitLab.
|
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. 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. 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. Select **Save**.
|
||||||
1. Copy the webhook URL.
|
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. 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. 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. 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**.
|
1. Select **Save changes**.
|
||||||
|
|
||||||
To test the integration, make a change based on the events you selected and
|
To test the integration, make a change based on the events you selected and
|
||||||
|
|
|
@ -50,12 +50,12 @@ Then fill in the integration configuration:
|
||||||
|
|
||||||
- **Webhook**: The incoming webhook URL on Mattermost, similar to
|
- **Webhook**: The incoming webhook URL on Mattermost, similar to
|
||||||
`http://mattermost.example/hooks/5xo…`.
|
`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.
|
To change the bot's username, provide a value.
|
||||||
- **Notify only broken pipelines**: If you enable the **Pipeline** event, and you want
|
- **Notify only broken pipelines**: If you enable the **Pipeline** event, and you want
|
||||||
notifications about failed pipelines only.
|
notifications about failed pipelines only.
|
||||||
- **Branches for which notifications are to be sent**: The branches to send notifications for.
|
- **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.
|
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,
|
- **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
|
messages are sent when an issue or merge request contains _any_ of the labels specified
|
||||||
|
|
|
@ -42,6 +42,6 @@ Complete these steps in GitLab:
|
||||||
1. Select **Pivotal Tracker**.
|
1. Select **Pivotal Tracker**.
|
||||||
1. Ensure that the **Active** toggle is enabled.
|
1. Ensure that the **Active** toggle is enabled.
|
||||||
1. Paste the token you generated in Pivotal Tracker.
|
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.
|
field, separated with commas.
|
||||||
1. Select **Save changes** or optionally select **Test settings**.
|
1. Select **Save changes** or optionally select **Test settings**.
|
||||||
|
|
|
@ -31,7 +31,7 @@ to control GitLab from Slack. Slash commands are configured separately.
|
||||||
[Triggers for Slack notifications](#triggers-for-slack-notifications).
|
[Triggers for Slack notifications](#triggers-for-slack-notifications).
|
||||||
By default, messages are sent to the channel you configured during
|
By default, messages are sent to the channel you configured during
|
||||||
[Slack configuration](#configure-slack).
|
[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:
|
a direct message:
|
||||||
- *To send messages to channels,* enter the Slack channel names, separated by
|
- *To send messages to channels,* enter the Slack channel names, separated by
|
||||||
commas.
|
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
|
1. In **Webhook**, enter the webhook URL you copied in the
|
||||||
[Slack configuration](#configure-slack) step.
|
[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.
|
the notifications.
|
||||||
1. Select the **Notify only broken pipelines** checkbox to notify only on failures.
|
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
|
1. In the **Branches for which notifications are to be sent** dropdown, select which types of branches
|
||||||
|
|
|
@ -72,9 +72,9 @@ To create a new project label:
|
||||||
1. Select the **New label** button.
|
1. Select the **New label** button.
|
||||||
1. In the **Title** field, enter a short, descriptive name for the label. You
|
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).
|
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.
|
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**
|
available colors, or by entering a hex color value in the **Background color**
|
||||||
field.
|
field.
|
||||||
1. Select **Create label**.
|
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**.
|
1. Click **Create project label**.
|
||||||
- Fill in the name field. Note that you can't specify a description if creating a 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).
|
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.
|
color value for a specific color.
|
||||||
1. Click **Create**.
|
1. Click **Create**.
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,7 @@ To edit a merge request approval rule:
|
||||||
|
|
||||||
1. Go to your project and select **Settings > General**.
|
1. Go to your project and select **Settings > General**.
|
||||||
1. Expand **Merge request (MR) approvals**, and then select **Edit**.
|
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. Set the number of required approvals in **Approvals required**. The minimum value is `0`.
|
||||||
1. Add or remove eligible approvers, as needed:
|
1. Add or remove eligible approvers, as needed:
|
||||||
- *To add users or groups as approvers,* search for users or groups that are
|
- *To add users or groups as approvers,* search for users or groups that are
|
||||||
|
|
|
@ -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. 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:
|
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)
|
![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**.
|
1. Click **Cherry-pick**.
|
||||||
|
|
||||||
## Related topics
|
## Related topics
|
||||||
|
|
|
@ -43,7 +43,7 @@ To start your review:
|
||||||
- **Add to review**: Keep this comment private and add to the current 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.
|
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.
|
- **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
|
The comment shows the actions to perform after publication, but does not perform them
|
||||||
until you submit your review.
|
until you submit your review.
|
||||||
1. When your review is complete, you can [submit the review](#submit-a-review). Your comments
|
1. When your review is complete, you can [submit the review](#submit-a-review). Your comments
|
||||||
|
|
|
@ -18,9 +18,9 @@ configured to generate a Pages site.
|
||||||
To fork a sample project and create a Pages website:
|
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. 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. Select 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. In the top right, select **Fork** and then choose a namespace to fork to.
|
||||||
1. Go to your project's **CI/CD > Pipelines** and click **Run pipeline**.
|
1. For your project, on the left sidebar, select **CI/CD > Pipelines** and then **Run pipeline**.
|
||||||
GitLab CI/CD builds and deploys your site.
|
GitLab CI/CD builds and deploys your site.
|
||||||
|
|
||||||
The site can take approximately 30 minutes to deploy.
|
The site can take approximately 30 minutes to deploy.
|
||||||
|
|
|
@ -101,7 +101,7 @@ To use it, follow the instructions at [Creating a fork](#creating-a-fork) and pr
|
||||||
- The project name.
|
- The project name.
|
||||||
- The project URL.
|
- The project URL.
|
||||||
- The project slug.
|
- The project slug.
|
||||||
- *(Optional)* The project description.
|
- Optional. The project description.
|
||||||
- The visibility level for your fork.
|
- The visibility level for your fork.
|
||||||
|
|
||||||
### Enable or disable the fork project form **(FREE SELF)**
|
### Enable or disable the fork project form **(FREE SELF)**
|
||||||
|
|
|
@ -194,7 +194,7 @@ To push a new project:
|
||||||
remote: The private project namespace/myproject was created.
|
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`
|
`git remote add origin https://gitlab.example.com/namespace/myproject.git`
|
||||||
to match your namespace and project names.
|
to match your namespace and project names.
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,13 @@ module API
|
||||||
|
|
||||||
default_format :json
|
default_format :json
|
||||||
|
|
||||||
|
rescue_from(
|
||||||
|
::ActiveRecord::RecordNotUnique,
|
||||||
|
::PG::UniqueViolation
|
||||||
|
) do |e|
|
||||||
|
render_api_error!(e.message, 422)
|
||||||
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
authenticate!
|
authenticate!
|
||||||
authorize! :read_terraform_state, user_project
|
authorize! :read_terraform_state, user_project
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_dependency 'gitlab/utils'
|
|
||||||
|
|
||||||
module Gitlab
|
module Gitlab
|
||||||
module Utils
|
module Utils
|
||||||
module StrongMemoize
|
module StrongMemoize
|
||||||
|
|
|
@ -90,7 +90,7 @@ module Sidebars
|
||||||
end
|
end
|
||||||
|
|
||||||
def google_cloud_menu_item
|
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)
|
user_has_permissions = can?(context.current_user, :admin_project_google_cloud, context.project)
|
||||||
|
|
||||||
unless feature_is_enabled && user_has_permissions
|
unless feature_is_enabled && user_has_permissions
|
||||||
|
@ -100,7 +100,7 @@ module Sidebars
|
||||||
::Sidebars::MenuItem.new(
|
::Sidebars::MenuItem.new(
|
||||||
title: _('Google Cloud'),
|
title: _('Google Cloud'),
|
||||||
link: project_google_cloud_index_path(context.project),
|
link: project_google_cloud_index_path(context.project),
|
||||||
active_routes: { controller: :google_cloud },
|
active_routes: { controller: [:google_cloud, :service_accounts] },
|
||||||
item_id: :google_cloud
|
item_id: :google_cloud
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
|
@ -37172,7 +37172,7 @@ msgstr ""
|
||||||
msgid "Unknown response text"
|
msgid "Unknown response text"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Unknown root"
|
msgid "Unknown screen"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Unknown user"
|
msgid "Unknown user"
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -13,17 +13,7 @@ require 'active_support/string_inquirer'
|
||||||
|
|
||||||
ENV['SKIP_RAILS_ENV_IN_RAKE'] = 'true'
|
ENV['SKIP_RAILS_ENV_IN_RAKE'] = 'true'
|
||||||
|
|
||||||
module Rails
|
require_relative 'override_rails_constants'
|
||||||
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
|
|
||||||
|
|
||||||
ActiveSupport::Dependencies.autoload_paths << 'lib'
|
ActiveSupport::Dependencies.autoload_paths << 'lib'
|
||||||
|
|
||||||
|
|
|
@ -155,7 +155,8 @@ module Trigger
|
||||||
'ee' => Trigger.ee? ? 'true' : 'false',
|
'ee' => Trigger.ee? ? 'true' : 'false',
|
||||||
'QA_BRANCH' => ENV['QA_BRANCH'] || 'master',
|
'QA_BRANCH' => ENV['QA_BRANCH'] || 'master',
|
||||||
'CACHE_UPDATE' => ENV['OMNIBUS_GITLAB_CACHE_UPDATE'],
|
'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
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
|
@ -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`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -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,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,12 +1,10 @@
|
||||||
import { shallowMount } from '@vue/test-utils';
|
import { shallowMount } from '@vue/test-utils';
|
||||||
import { GlButton, GlFormGroup, GlFormSelect } from '@gitlab/ui';
|
import { GlButton, GlFormGroup, GlFormSelect } from '@gitlab/ui';
|
||||||
import IncubationBanner from '~/google_cloud/components/incubation_banner.vue';
|
import ServiceAccountsForm from '~/google_cloud/components/service_accounts_form.vue';
|
||||||
import ServiceAccountsForm from '~/google_cloud/components/screens/service_accounts_form.vue';
|
|
||||||
|
|
||||||
describe('ServiceAccountsForm component', () => {
|
describe('ServiceAccountsForm component', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
|
||||||
const findIncubationBanner = () => wrapper.findComponent(IncubationBanner);
|
|
||||||
const findHeader = () => wrapper.find('header');
|
const findHeader = () => wrapper.find('header');
|
||||||
const findAllFormGroups = () => wrapper.findAllComponents(GlFormGroup);
|
const findAllFormGroups = () => wrapper.findAllComponents(GlFormGroup);
|
||||||
const findAllFormSelects = () => wrapper.findAllComponents(GlFormSelect);
|
const findAllFormSelects = () => wrapper.findAllComponents(GlFormSelect);
|
||||||
|
@ -22,10 +20,6 @@ describe('ServiceAccountsForm component', () => {
|
||||||
wrapper.destroy();
|
wrapper.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('contains incubation banner', () => {
|
|
||||||
expect(findIncubationBanner().exists()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('contains header', () => {
|
it('contains header', () => {
|
||||||
expect(findHeader().exists()).toBe(true);
|
expect(findHeader().exists()).toBe(true);
|
||||||
});
|
});
|
|
@ -16,6 +16,7 @@ import { createStore } from '~/integrations/edit/store';
|
||||||
|
|
||||||
describe('IntegrationForm', () => {
|
describe('IntegrationForm', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
let dispatch;
|
||||||
|
|
||||||
const createComponent = ({
|
const createComponent = ({
|
||||||
customStateProps = {},
|
customStateProps = {},
|
||||||
|
@ -23,12 +24,15 @@ describe('IntegrationForm', () => {
|
||||||
initialState = {},
|
initialState = {},
|
||||||
props = {},
|
props = {},
|
||||||
} = {}) => {
|
} = {}) => {
|
||||||
|
const store = createStore({
|
||||||
|
customState: { ...mockIntegrationProps, ...customStateProps },
|
||||||
|
...initialState,
|
||||||
|
});
|
||||||
|
dispatch = jest.spyOn(store, 'dispatch').mockImplementation();
|
||||||
|
|
||||||
wrapper = shallowMountExtended(IntegrationForm, {
|
wrapper = shallowMountExtended(IntegrationForm, {
|
||||||
propsData: { ...props },
|
propsData: { ...props },
|
||||||
store: createStore({
|
store,
|
||||||
customState: { ...mockIntegrationProps, ...customStateProps },
|
|
||||||
...initialState,
|
|
||||||
}),
|
|
||||||
stubs: {
|
stubs: {
|
||||||
OverrideDropdown,
|
OverrideDropdown,
|
||||||
ActiveCheckbox,
|
ActiveCheckbox,
|
||||||
|
@ -195,13 +199,29 @@ describe('IntegrationForm', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('type is "jira"', () => {
|
describe('type is "jira"', () => {
|
||||||
it('renders JiraTriggerFields', () => {
|
beforeEach(() => {
|
||||||
createComponent({
|
jest.spyOn(document, 'querySelector').mockReturnValue(document.createElement('form'));
|
||||||
customStateProps: { type: 'jira' },
|
|
||||||
});
|
|
||||||
|
|
||||||
|
createComponent({
|
||||||
|
customStateProps: { type: 'jira', testPath: '/test' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders JiraTriggerFields', () => {
|
||||||
expect(findJiraTriggerFields().exists()).toBe(true);
|
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', () => {
|
describe('triggerEvents is present', () => {
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
import { GlFormCheckbox, GlFormInput } from '@gitlab/ui';
|
import { GlFormCheckbox, GlFormInput } from '@gitlab/ui';
|
||||||
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||||
|
|
||||||
import {
|
import { VALIDATE_INTEGRATION_FORM_EVENT } from '~/integrations/constants';
|
||||||
GET_JIRA_ISSUE_TYPES_EVENT,
|
|
||||||
VALIDATE_INTEGRATION_FORM_EVENT,
|
|
||||||
} from '~/integrations/constants';
|
|
||||||
import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue';
|
import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue';
|
||||||
import eventHub from '~/integrations/edit/event_hub';
|
import eventHub from '~/integrations/edit/event_hub';
|
||||||
import { createStore } from '~/integrations/edit/store';
|
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 () => {
|
it('emits "request-jira-issue-types` when the jira-vulnerabilities component requests to fetch issue types', async () => {
|
||||||
const eventHubEmitSpy = jest.spyOn(eventHub, '$emit');
|
|
||||||
|
|
||||||
await setEnableCheckbox(true);
|
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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -14,3 +14,9 @@ export const mockIntegrationProps = {
|
||||||
type: '',
|
type: '',
|
||||||
inheritFromId: 25,
|
inheritFromId: 25,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const mockJiraIssueTypes = [
|
||||||
|
{ id: '1', name: 'issue', description: 'issue' },
|
||||||
|
{ id: '2', name: 'bug', description: 'bug' },
|
||||||
|
{ id: '3', name: 'epic', description: 'epic' },
|
||||||
|
];
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
|
import axios from 'axios';
|
||||||
|
import MockAdapter from 'axios-mock-adapter';
|
||||||
import testAction from 'helpers/vuex_action_helper';
|
import testAction from 'helpers/vuex_action_helper';
|
||||||
|
import { I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE } from '~/integrations/constants';
|
||||||
import {
|
import {
|
||||||
setOverride,
|
setOverride,
|
||||||
setIsSaving,
|
setIsSaving,
|
||||||
|
@ -14,14 +17,21 @@ import {
|
||||||
import * as types from '~/integrations/edit/store/mutation_types';
|
import * as types from '~/integrations/edit/store/mutation_types';
|
||||||
import createState from '~/integrations/edit/store/state';
|
import createState from '~/integrations/edit/store/state';
|
||||||
import { refreshCurrentPage } from '~/lib/utils/url_utility';
|
import { refreshCurrentPage } from '~/lib/utils/url_utility';
|
||||||
|
import { mockJiraIssueTypes } from '../mock_data';
|
||||||
|
|
||||||
jest.mock('~/lib/utils/url_utility');
|
jest.mock('~/lib/utils/url_utility');
|
||||||
|
|
||||||
describe('Integration form store actions', () => {
|
describe('Integration form store actions', () => {
|
||||||
let state;
|
let state;
|
||||||
|
let mockAxios;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
state = createState();
|
state = createState();
|
||||||
|
mockAxios = new MockAdapter(axios);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
mockAxios.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('setOverride', () => {
|
describe('setOverride', () => {
|
||||||
|
@ -75,11 +85,28 @@ describe('Integration form store actions', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('requestJiraIssueTypes', () => {
|
describe('requestJiraIssueTypes', () => {
|
||||||
it('should commit SET_JIRA_ISSUE_TYPES_ERROR_MESSAGE and SET_IS_LOADING_JIRA_ISSUE_TYPES mutations', () => {
|
describe.each`
|
||||||
return testAction(requestJiraIssueTypes, null, state, [
|
scenario | responseCode | response | action
|
||||||
{ type: types.SET_JIRA_ISSUE_TYPES_ERROR_MESSAGE, payload: '' },
|
${'when successful'} | ${200} | ${{ issuetypes: mockJiraIssueTypes }} | ${{ type: 'receiveJiraIssueTypesSuccess', payload: mockJiraIssueTypes }}
|
||||||
{ type: types.SET_IS_LOADING_JIRA_ISSUE_TYPES, payload: true },
|
${'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],
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,8 @@ import eventHub from '~/integrations/edit/event_hub';
|
||||||
import axios from '~/lib/utils/axios_utils';
|
import axios from '~/lib/utils/axios_utils';
|
||||||
import toast from '~/vue_shared/plugins/global_toast';
|
import toast from '~/vue_shared/plugins/global_toast';
|
||||||
import {
|
import {
|
||||||
I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE,
|
|
||||||
I18N_SUCCESSFUL_CONNECTION_MESSAGE,
|
I18N_SUCCESSFUL_CONNECTION_MESSAGE,
|
||||||
I18N_DEFAULT_ERROR_MESSAGE,
|
I18N_DEFAULT_ERROR_MESSAGE,
|
||||||
GET_JIRA_ISSUE_TYPES_EVENT,
|
|
||||||
TOGGLE_INTEGRATION_EVENT,
|
TOGGLE_INTEGRATION_EVENT,
|
||||||
TEST_INTEGRATION_EVENT,
|
TEST_INTEGRATION_EVENT,
|
||||||
SAVE_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 event hub receives `SAVE_INTEGRATION_EVENT`', () => {
|
||||||
describe('when form is valid', () => {
|
describe('when form is valid', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|
|
@ -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
|
|
@ -152,6 +152,16 @@ RSpec.describe API::Terraform::State do
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
expect(Gitlab::Json.parse(response.body)).to be_empty
|
expect(Gitlab::Json.parse(response.body)).to be_empty
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
context 'without body' do
|
context 'without body' do
|
||||||
|
|
|
@ -28,7 +28,7 @@ RSpec.describe Quality::TestLevel do
|
||||||
context 'when level is unit' do
|
context 'when level is unit' do
|
||||||
it 'returns a pattern' do
|
it 'returns a pattern' do
|
||||||
expect(subject.pattern(:unit))
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ RSpec.describe Quality::TestLevel do
|
||||||
context 'when level is unit' do
|
context 'when level is unit' do
|
||||||
it 'returns a regexp' do
|
it 'returns a regexp' do
|
||||||
expect(subject.regexp(:unit))
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -33,6 +33,7 @@ module Quality
|
||||||
initializers
|
initializers
|
||||||
javascripts
|
javascripts
|
||||||
lib
|
lib
|
||||||
|
metrics_server
|
||||||
models
|
models
|
||||||
policies
|
policies
|
||||||
presenters
|
presenters
|
||||||
|
|
Loading…
Reference in New Issue