Commit 4886f07c authored by Olena Horal-Koretska's avatar Olena Horal-Koretska Committed by Natalia Tepluhina

Status page settings

parent 535d5da7
......@@ -7,5 +7,7 @@ document.addEventListener('DOMContentLoaded', () => {
mountErrorTrackingForm();
mountOperationSettings();
mountGrafanaIntegration();
initSettingsPanels();
if (!IS_EE) {
initSettingsPanels();
}
});
......@@ -8,3 +8,4 @@
= render 'projects/settings/operations/external_dashboard'
= render 'projects/settings/operations/grafana_integration'
= render_if_exists 'projects/settings/operations/tracing'
= render_if_exists 'projects/settings/operations/status_page'
import '~/pages/projects/settings/operations/show/index';
import mountStatusPageForm from 'ee/status_page_settings';
import initSettingsPanels from '~/settings_panels';
document.addEventListener('DOMContentLoaded', () => {
mountStatusPageForm();
initSettingsPanels();
});
<script>
import { mapActions, mapState } from 'vuex';
import { mapComputed } from '~/vuex_shared/bindings';
import {
GlButton,
GlSprintf,
GlLink,
GlIcon,
GlFormGroup,
GlFormInput,
GlFormCheckbox,
} from '@gitlab/ui';
import { __, s__ } from '~/locale';
export default {
components: { GlButton, GlSprintf, GlLink, GlFormGroup, GlFormInput, GlIcon, GlFormCheckbox },
i18n: {
headerText: s__('StatusPage|Status page'),
expandBtnLabel: __('Expand'),
saveBtnLabel: __('Save changes'),
subHeaderText: s__(
'StatusPage|Configure file storage settings to link issues in this project to an external status page.',
),
introText: s__(
'StatusPage|To publish incidents to an external status page, GitLab will store a JSON file in your Amazon S3 account in a location accessible to your external status page service. Make sure to also set up %{docsLink}',
),
introLinkText: s__('StatusPage|your status page frontend.'),
activeLabel: s__('StatusPage|Active'),
bucket: {
label: s__('StatusPage|S3 Bucket name'),
helpText: s__('StatusPage|Bucket %{docsLink}'),
linkText: s__('StatusPage|configuration documentation'),
},
region: {
label: s__('StatusPage|AWS region'),
helpText: s__('StatusPage|For help with configuration, visit %{docsLink}'),
linkText: s__('StatusPage|AWS documentation'),
},
accessKey: {
label: s__('StatusPage|AWS access key ID'),
},
secretAccessKey: {
label: s__('StatusPage|AWS Secret access key'),
},
},
computed: {
...mapState(['loading']),
...mapComputed([
{ key: 'enabled', updateFn: 'setStatusPageEnabled' },
{ key: 'bucketName', updateFn: 'setStatusPageBucketName' },
{ key: 'region', updateFn: 'setStatusPageRegion' },
{ key: 'awsAccessKey', updateFn: 'setStatusPageAccessKey' },
{ key: 'awsSecretKey', updateFn: 'setStatusPageSecretAccessKey' },
]),
},
methods: {
...mapActions(['updateStatusPageSettings']),
},
};
</script>
<template>
<section id="status-page" class="settings no-animate js-status-page-settings">
<div class="settings-header">
<h3 ref="sectionHeader" class="h4">
{{ $options.i18n.headerText }}
</h3>
<gl-button ref="toggleBtn" class="js-settings-toggle">{{
$options.i18n.expandBtnLabel
}}</gl-button>
<p ref="sectionSubHeader">
{{ $options.i18n.subHeaderText }}
</p>
</div>
<div class="settings-content">
<!-- eslint-disable @gitlab/vue-i18n/no-bare-attribute-strings -->
<p>
<gl-sprintf :message="$options.i18n.introText">
<template #docsLink>
<gl-link href="#">
<span>{{ $options.i18n.introLinkText }}</span>
</gl-link>
</template>
</gl-sprintf>
</p>
<form ref="settingsForm" @submit.prevent="updateStatusPageSettings">
<gl-form-group class="gl-pl-0 mb-3">
<gl-form-checkbox v-model="enabled">
<span class="bold">{{ $options.i18n.activeLabel }}</span></gl-form-checkbox
>
</gl-form-group>
<gl-form-group
:label="$options.i18n.bucket.label"
label-size="sm"
label-for="status-page-s3-bucket-name"
class="col-8 col-md-9 gl-pl-0 mb-3"
>
<gl-form-input id="status-page-s3-bucket-name" v-model="bucketName" />
<p class="form-text text-muted">
<gl-sprintf :message="$options.i18n.bucket.helpText">
<template #docsLink>
<gl-link
target="_blank"
href="https://docs.aws.amazon.com/AmazonS3/latest/dev/HostingWebsiteOnS3Setup.html"
>
<span>{{ $options.i18n.bucket.linkText }}</span>
<gl-icon name="external-link" class="vertical-align-middle" />
</gl-link>
</template>
</gl-sprintf>
</p>
</gl-form-group>
<gl-form-group
:label="$options.i18n.region.label"
label-size="sm"
label-for="status-page-aws-region"
class="col-8 col-md-9 gl-pl-0 mb-3"
>
<gl-form-input
id="status-page-aws-region"
v-model="region"
placeholder="example: us-west-2"
/>
<p class="form-text text-muted">
<gl-sprintf :message="$options.i18n.region.helpText">
<template #docsLink>
<gl-link href="https://github.com/aws/aws-sdk-ruby#configuration" target="_blank">
<span>{{ $options.i18n.region.linkText }}</span>
<gl-icon name="external-link" class="vertical-align-middle" />
</gl-link>
</template>
</gl-sprintf>
</p>
</gl-form-group>
<gl-form-group
:label="$options.i18n.accessKey.label"
label-size="sm"
label-for="status-page-aws-access-key-id"
class="col-8 col-md-9 gl-pl-0 mb-3"
>
<gl-form-input id="status-page-aws-access-key " v-model="awsAccessKey" />
</gl-form-group>
<gl-form-group
:label="$options.i18n.secretAccessKey.label"
label-size="sm"
label-for="status-page-aws-secret-access-key"
class="col-8 col-md-9 gl-pl-0 mb-3"
>
<gl-form-input id="status-page-aws-secret-access-key " v-model="awsSecretKey" />
</gl-form-group>
<gl-button
ref="submitBtn"
:disabled="loading"
variant="success"
type="submit"
class="js-no-auto-disable"
>
{{ $options.i18n.saveBtnLabel }}
</gl-button>
</form>
</div>
</section>
</template>
import Vue from 'vue';
import StatusPageSettings from './components/settings_form.vue';
import createStore from './store';
export default () => {
const el = document.querySelector('.js-status-page-settings');
if (!el) {
return null;
}
return new Vue({
el,
store: createStore(el.dataset),
render(createElement) {
return createElement(StatusPageSettings);
},
});
};
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
import createFlash from '~/flash';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
import * as mutationTypes from './mutation_types';
export const setStatusPageEnabled = ({ commit }, { enabled }) =>
commit(mutationTypes.SET_STATUS_PAGE_ENABLED, enabled);
export const setStatusPageBucketName = ({ commit }, { bucketName }) =>
commit(mutationTypes.SET_BUCKET_NAME, bucketName);
export const setStatusPageRegion = ({ commit }, { region }) =>
commit(mutationTypes.SET_REGION, region);
export const setStatusPageAccessKey = ({ commit }, { awsAccessKey }) =>
commit(mutationTypes.SET_ACCESS_KEY_ID, awsAccessKey);
export const setStatusPageSecretAccessKey = ({ commit }, { awsSecretKey }) =>
commit(mutationTypes.SET_SECRET_ACCESS_KEY, awsSecretKey);
export const updateStatusPageSettings = ({ state, dispatch, commit }) => {
commit(mutationTypes.LOADING, true);
axios
.patch(state.operationsSettingsEndpoint, {
project: {
status_page_setting_attributes: {
enabled: state.enabled,
aws_s3_bucket_name: state.bucketName,
aws_region: state.region,
aws_access_key: state.awsAccessKey,
aws_secret_key: state.awsSecretKey,
},
},
})
.then(() => dispatch('receiveStatusPageSettingsUpdateSuccess'))
.catch(error => dispatch('receiveStatusPageSettingsUpdateError', error))
.finally(() => commit(mutationTypes.LOADING, false));
};
export const receiveStatusPageSettingsUpdateSuccess = () => {
/**
* The operations_controller currently handles successful requests
* by creating a flash banner messsage to notify the user.
*/
refreshCurrentPage();
};
export const receiveStatusPageSettingsUpdateError = (_, error) => {
const { response } = error;
const message = response?.data?.message || '';
createFlash(`${__('There was an error saving your changes.')} ${message}`, 'alert');
};
import Vue from 'vue';
import Vuex from 'vuex';
import createState from './state';
import * as actions from './actions';
import mutations from './mutations';
Vue.use(Vuex);
export default initialState =>
new Vuex.Store({
state: createState(initialState),
actions,
mutations,
});
export const SET_STATUS_PAGE_ENABLED = 'SET_STATUS_PAGE_ENABLED';
export const SET_BUCKET_NAME = 'SET_BUCKET_NAME';
export const SET_REGION = 'SET_REGION';
export const SET_ACCESS_KEY_ID = 'SET_ACCESS_KEY_ID';
export const SET_SECRET_ACCESS_KEY = 'SET_SECRET_ACCESS_KEY';
export const LOADING = 'LOADING';
import * as types from './mutation_types';
export default {
[types.SET_STATUS_PAGE_ENABLED](state, enabled) {
state.enabled = enabled;
},
[types.SET_BUCKET_NAME](state, bucketName) {
state.bucketName = bucketName;
},
[types.SET_REGION](state, region) {
state.region = region;
},
[types.SET_ACCESS_KEY_ID](state, awsAccessKey) {
state.awsAccessKey = awsAccessKey;
},
[types.SET_SECRET_ACCESS_KEY](state, awsSecretKey) {
state.awsSecretKey = awsSecretKey;
},
[types.LOADING](state, loading) {
state.loading = loading;
},
};
import { parseBoolean } from '~/lib/utils/common_utils';
export default (initialState = {}) => ({
enabled: parseBoolean(initialState.enabled) || false,
bucketName: initialState.bucketName || '',
region: initialState.region || '',
awsAccessKey: initialState.awsAccessKey || '',
awsSecretKey: initialState.awsSecretKey || '',
operationsSettingsEndpoint: initialState.operationsSettingsEndpoint || '',
loading: false,
});
- return unless @project.feature_available?(:status_page, current_user)
- setting = status_page_settings_data(@project.status_page_setting)
.js-status-page-settings{ data: { operations_settings_endpoint: project_settings_operations_path(@project),
enabled: setting['setting-enabled'],
bucket_name: setting['setting-aws-s3-bucket-name'],
region: setting['setting-aws-region'],
aws_access_key: setting['setting-aws-access-key'],
aws_secret_key: setting['setting-masked-aws-secret-key'] } }
---
title: Status page settings
merge_request: 25820
author:
type: added
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Status Page settings form default state should match the default snapshot 1`] = `
<section
class="settings no-animate js-status-page-settings"
id="status-page"
>
<div
class="settings-header"
>
<h3
class="h4"
>
Status page
</h3>
<gl-button-stub
class="js-settings-toggle"
size="md"
variant="secondary"
>
Expand
</gl-button-stub>
<p>
Configure file storage settings to link issues in this project to an external status page.
</p>
</div>
<div
class="settings-content"
>
<p>
<gl-sprintf-stub
message="To publish incidents to an external status page, GitLab will store a JSON file in your Amazon S3 account in a location accessible to your external status page service. Make sure to also set up %{docsLink}"
/>
</p>
<form>
<gl-form-group-stub
class="gl-pl-0 mb-3"
>
<gl-form-checkbox-stub>
<span
class="bold"
>
Active
</span>
</gl-form-checkbox-stub>
</gl-form-group-stub>
<gl-form-group-stub
class="col-8 col-md-9 gl-pl-0 mb-3"
label="S3 Bucket name"
label-for="status-page-s3-bucket-name"
label-size="sm"
>
<gl-form-input-stub
id="status-page-s3-bucket-name"
value=""
/>
<p
class="form-text text-muted"
>
<gl-sprintf-stub
message="Bucket %{docsLink}"
/>
</p>
</gl-form-group-stub>
<gl-form-group-stub
class="col-8 col-md-9 gl-pl-0 mb-3"
label="AWS region"
label-for="status-page-aws-region"
label-size="sm"
>
<gl-form-input-stub
id="status-page-aws-region"
placeholder="example: us-west-2"
value=""
/>
<p
class="form-text text-muted"
>
<gl-sprintf-stub
message="For help with configuration, visit %{docsLink}"
/>
</p>
</gl-form-group-stub>
<gl-form-group-stub
class="col-8 col-md-9 gl-pl-0 mb-3"
label="AWS access key ID"
label-for="status-page-aws-access-key-id"
label-size="sm"
>
<gl-form-input-stub
id="status-page-aws-access-key "
value=""
/>
</gl-form-group-stub>
<gl-form-group-stub
class="col-8 col-md-9 gl-pl-0 mb-3"
label="AWS Secret access key"
label-for="status-page-aws-secret-access-key"
label-size="sm"
>
<gl-form-input-stub
id="status-page-aws-secret-access-key "
value=""
/>
</gl-form-group-stub>
<gl-button-stub
class="js-no-auto-disable"
size="md"
type="submit"
variant="success"
>
Save changes
</gl-button-stub>
</form>
</div>
</section>
`;
import { shallowMount } from '@vue/test-utils';
import StatusPageSettingsForm from 'ee/status_page_settings/components/settings_form.vue';
import createStore from 'ee/status_page_settings/store';
describe('Status Page settings form', () => {
let wrapper;
const store = createStore();
const findForm = () => wrapper.find({ ref: 'settingsForm' });
const findToggleButton = () => wrapper.find({ ref: 'toggleBtn' });
const findSectionHeader = () => wrapper.find({ ref: 'sectionHeader' });
const findSectionSubHeader = () => wrapper.find({ ref: 'sectionSubHeader' });
beforeEach(() => {
wrapper = shallowMount(StatusPageSettingsForm, { store });
});
afterEach(() => {
if (wrapper) {
wrapper.destroy();
}
});
describe('default state', () => {
it('should match the default snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
});
it('renders header text', () => {
expect(findSectionHeader().text()).toBe('Status page');
});
describe('expand/collapse button', () => {
it('renders as an expand button by default', () => {
expect(findToggleButton().text()).toBe('Expand');
});
});
describe('sub-header', () => {
it('renders descriptive text', () => {
expect(findSectionSubHeader().text()).toContain(
'Configure file storage settings to link issues in this project to an external status page.',
);
});
});
describe('form', () => {
beforeEach(() => {
jest.spyOn(wrapper.vm, 'updateStatusPageSettings').mockImplementation();
});
describe('submit button', () => {
it('submits form on click', () => {
findForm().trigger('submit');
expect(wrapper.vm.updateStatusPageSettings).toHaveBeenCalled();
});
});
});
});
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
import * as actions from 'ee/status_page_settings/store/actions';
import * as types from 'ee/status_page_settings/store/mutation_types';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
jest.mock('~/flash.js');
jest.mock('~/lib/utils/url_utility');
let mock;
describe('Status Page actions', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
createFlash.mockClear();
});
const state = {
enabled: true,
bucketName: 'test-bucket',
region: 'us-west',
};
it.each`
mutation | action | value | key
${types.SET_STATUS_PAGE_ENABLED} | ${'setStatusPageEnabled'} | ${true} | ${'enabled'}
${types.SET_BUCKET_NAME} | ${'setStatusPageBucketName'} | ${'my-bucket'} | ${'bucketName'}
${types.SET_REGION} | ${'setStatusPageRegion'} | ${'us-west'} | ${'region'}
${types.SET_ACCESS_KEY_ID} | ${'setStatusPageAccessKey'} | ${'key-id'} | ${'awsAccessKey'}
${types.SET_SECRET_ACCESS_KEY} | ${'setStatusPageSecretAccessKey'} | ${'secret'} | ${'awsSecretKey'}
`('$action will commit $mutation with $value', ({ mutation, action, value, key }) => {
testAction(
actions[action],
{ [key]: value },
null,
[
{
type: mutation,
payload: value,
},
],
[],
);
});
describe('updateStatusPageSettings', () => {
it('should handle successful status update', () => {
mock.onPatch().reply(200, {});
testAction(
actions.updateStatusPageSettings,
null,
state,
[
{
payload: true,
type: types.LOADING,
},
{
payload: false,
type: types.LOADING,
},
],
[{ type: 'receiveStatusPageSettingsUpdateSuccess' }],
);
});
it('should handle unsuccessful status update', () => {
mock.onPatch().reply(400, {});
testAction(
actions.updateStatusPageSettings,
null,
state,
[
{
payload: true,
type: types.LOADING,
},
{
payload: false,
type: types.LOADING,
},
],
[
{
payload: expect.any(Object),
type: 'receiveStatusPageSettingsUpdateError',
},
],
);
});
});
describe('receiveStatusPageSettingsUpdateSuccess', () => {
it('should handle successful settings update', done => {
testAction(actions.receiveStatusPageSettingsUpdateSuccess, null, null, [], [], () => {
expect(refreshCurrentPage).toHaveBeenCalledTimes(1);
done();
});
});
});
describe('receiveStatusPageSettingsUpdateError', () => {
const error = { response: { data: { message: 'Update error' } } };
it('should handle error update', done => {
testAction(actions.receiveStatusPageSettingsUpdateError, error, null, [], [], () => {
expect(createFlash).toHaveBeenCalledWith(
`There was an error saving your changes. ${error.response.data.message}`,
'alert',
);
done();
});
});
});
});
......@@ -18771,6 +18771,45 @@ msgstr ""
msgid "Status: %{title}"
msgstr ""
msgid "StatusPage|AWS Secret access key"
msgstr ""
msgid "StatusPage|AWS access key ID"
msgstr ""
msgid "StatusPage|AWS documentation"
msgstr ""
msgid "StatusPage|AWS region"
msgstr ""
msgid "StatusPage|Active"
msgstr ""
msgid "StatusPage|Bucket %{docsLink}"
msgstr ""
msgid "StatusPage|Configure file storage settings to link issues in this project to an external status page."
msgstr ""
msgid "StatusPage|For help with configuration, visit %{docsLink}"
msgstr ""
msgid "StatusPage|S3 Bucket name"
msgstr ""
msgid "StatusPage|Status page"
msgstr ""
msgid "StatusPage|To publish incidents to an external status page, GitLab will store a JSON file in your Amazon S3 account in a location accessible to your external status page service. Make sure to also set up %{docsLink}"
msgstr ""
msgid "StatusPage|configuration documentation"
msgstr ""
msgid "StatusPage|your status page frontend."
msgstr ""
msgid "Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments."
msgstr ""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment