summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-01-14 21:07:45 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-01-14 21:07:45 +0000
commit0b12a5312c9701fbfed25fbb334d47900ced736b (patch)
treea29a27e297134f573fd8e5c298d241f3156c207a /app
parent92f95ccac81911d1fcc32e999a7f1ce04624a56c (diff)
downloadgitlab-ce-0b12a5312c9701fbfed25fbb334d47900ced736b.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/deploy_keys/components/key.vue6
-rw-r--r--app/assets/javascripts/diffs/components/app.vue8
-rw-r--r--app/assets/javascripts/diffs/components/compare_versions.vue6
-rw-r--r--app/assets/javascripts/diffs/store/actions.js8
-rw-r--r--app/assets/javascripts/diffs/store/getters.js2
-rw-r--r--app/assets/javascripts/diffs/store/mutations.js11
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/modal.vue25
-rw-r--r--app/assets/javascripts/ide/stores/actions.js101
-rw-r--r--app/assets/javascripts/ide/stores/actions/project.js5
-rw-r--r--app/assets/javascripts/pages/admin/application_settings/index.js4
-rw-r--r--app/assets/javascripts/self_monitor/components/self_monitor_form.vue160
-rw-r--r--app/assets/javascripts/self_monitor/index.js23
-rw-r--r--app/assets/javascripts/self_monitor/store/actions.js126
-rw-r--r--app/assets/javascripts/self_monitor/store/index.js21
-rw-r--r--app/assets/javascripts/self_monitor/store/mutation_types.js6
-rw-r--r--app/assets/javascripts/self_monitor/store/mutations.js22
-rw-r--r--app/assets/javascripts/self_monitor/store/state.js15
-rw-r--r--app/helpers/markup_helper.rb2
-rw-r--r--app/views/admin/application_settings/metrics_and_profiling.html.haml3
19 files changed, 470 insertions, 84 deletions
diff --git a/app/assets/javascripts/deploy_keys/components/key.vue b/app/assets/javascripts/deploy_keys/components/key.vue
index 74f1373f144..c856e380c41 100644
--- a/app/assets/javascripts/deploy_keys/components/key.vue
+++ b/app/assets/javascripts/deploy_keys/components/key.vue
@@ -115,12 +115,10 @@ export default {
<div role="rowheader" class="table-mobile-header">{{ s__('DeployKeys|Deploy key') }}</div>
<div class="table-mobile-content qa-key">
<strong class="title qa-key-title"> {{ deployKey.title }} </strong>
- <div class="fingerprint qa-key-fingerprint">
+ <div class="fingerprint" data-qa-selector="key_md5_fingerprint">
{{ __('MD5') }}:{{ deployKey.fingerprint }}
</div>
- <div class="fingerprint qa-key-fingerprint">
- {{ __('SHA256') }}:{{ deployKey.fingerprint_sha256 }}
- </div>
+ <div class="fingerprint">{{ __('SHA256') }}:{{ deployKey.fingerprint_sha256 }}</div>
</div>
</div>
<div class="table-section section-30 section-wrap">
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index c6d32ffef34..23b8458aa6b 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -95,6 +95,7 @@ export default {
return {
treeWidth,
+ diffFilesLength: 0,
};
},
computed: {
@@ -241,7 +242,8 @@ export default {
fetchData(toggleTree = true) {
if (this.glFeatures.diffsBatchLoad) {
this.fetchDiffFilesMeta()
- .then(() => {
+ .then(({ real_size }) => {
+ this.diffFilesLength = parseInt(real_size, 10);
if (toggleTree) this.hideTreeListIfJustOneFile();
this.startDiffRendering();
@@ -264,7 +266,8 @@ export default {
});
} else {
this.fetchDiffFiles()
- .then(() => {
+ .then(({ real_size }) => {
+ this.diffFilesLength = parseInt(real_size, 10);
if (toggleTree) {
this.hideTreeListIfJustOneFile();
}
@@ -351,6 +354,7 @@ export default {
:merge-request-diff="mergeRequestDiff"
:target-branch="targetBranch"
:is-limited-container="isLimitedContainer"
+ :diff-files-length="diffFilesLength"
/>
<hidden-files-warning
diff --git a/app/assets/javascripts/diffs/components/compare_versions.vue b/app/assets/javascripts/diffs/components/compare_versions.vue
index 2e57a47f2f7..24542126b07 100644
--- a/app/assets/javascripts/diffs/components/compare_versions.vue
+++ b/app/assets/javascripts/diffs/components/compare_versions.vue
@@ -42,9 +42,13 @@ export default {
required: false,
default: false,
},
+ diffFilesLength: {
+ type: Number,
+ required: true,
+ },
},
computed: {
- ...mapGetters('diffs', ['hasCollapsedFile', 'diffFilesLength']),
+ ...mapGetters('diffs', ['hasCollapsedFile']),
...mapState('diffs', [
'commit',
'showTreeList',
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index 6714f4e62b8..b920e041135 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -64,6 +64,7 @@ export const fetchDiffFiles = ({ state, commit }) => {
const urlParams = {
w: state.showWhitespace ? '0' : '1',
};
+ let returnData;
if (state.useSingleDiffStyle) {
urlParams.view = state.diffViewType;
@@ -87,9 +88,13 @@ export const fetchDiffFiles = ({ state, commit }) => {
worker.postMessage(state.diffFiles);
+ returnData = res.data;
return Vue.nextTick();
})
- .then(handleLocationHash)
+ .then(() => {
+ handleLocationHash();
+ return returnData;
+ })
.catch(() => worker.terminate());
};
@@ -147,6 +152,7 @@ export const fetchDiffFilesMeta = ({ commit, state }) => {
prepareDiffData(data);
worker.postMessage(data.diff_files);
+ return data;
})
.catch(() => worker.terminate());
};
diff --git a/app/assets/javascripts/diffs/store/getters.js b/app/assets/javascripts/diffs/store/getters.js
index bc27e263bff..c4737090a70 100644
--- a/app/assets/javascripts/diffs/store/getters.js
+++ b/app/assets/javascripts/diffs/store/getters.js
@@ -95,8 +95,6 @@ export const allBlobs = (state, getters) =>
return acc;
}, []);
-export const diffFilesLength = state => state.diffFiles.length;
-
export const getCommentFormForDiffFile = state => fileHash =>
state.commentForms.find(form => form.fileHash === fileHash);
diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js
index 8cfdded1f9b..1505be1a0b2 100644
--- a/app/assets/javascripts/diffs/store/mutations.js
+++ b/app/assets/javascripts/diffs/store/mutations.js
@@ -179,16 +179,19 @@ export default {
const mapDiscussions = (line, extraCheck = () => true) => ({
...line,
discussions: extraCheck()
- ? line.discussions
+ ? line.discussions &&
+ line.discussions
.filter(() => !line.discussions.some(({ id }) => discussion.id === id))
.concat(lineCheck(line) ? discussion : line.discussions)
: [],
});
const setDiscussionsExpanded = line => {
- const isLineNoteTargeted = line.discussions.some(
- disc => disc.notes && disc.notes.find(note => hash === `note_${note.id}`),
- );
+ const isLineNoteTargeted =
+ line.discussions &&
+ line.discussions.some(
+ disc => disc.notes && disc.notes.find(note => hash === `note_${note.id}`),
+ );
return {
...line,
diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
index ecafb4e81c4..bf3d736ddf3 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/modal.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
@@ -67,8 +67,8 @@ export default {
if (this.entryModal.type === modalTypes.rename) {
if (this.entries[this.entryName] && !this.entries[this.entryName].deleted) {
flash(
- sprintf(s__('The name %{entryName} is already taken in this directory.'), {
- entryName: this.entryName,
+ sprintf(s__('The name "%{name}" is already taken in this directory.'), {
+ name: this.entryName,
}),
'alert',
document,
@@ -81,22 +81,11 @@ export default {
const entryName = parentPath.pop();
parentPath = parentPath.join('/');
- const createPromise =
- parentPath && !this.entries[parentPath]
- ? this.createTempEntry({ name: parentPath, type: 'tree' })
- : Promise.resolve();
-
- createPromise
- .then(() =>
- this.renameEntry({
- path: this.entryModal.entry.path,
- name: entryName,
- parentPath,
- }),
- )
- .catch(() =>
- flash(__('Error creating a new path'), 'alert', document, null, false, true),
- );
+ this.renameEntry({
+ path: this.entryModal.entry.path,
+ name: entryName,
+ parentPath,
+ });
}
} else {
this.createTempEntry({
diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js
index 7ffb430296b..3445ef7a75f 100644
--- a/app/assets/javascripts/ide/stores/actions.js
+++ b/app/assets/javascripts/ide/stores/actions.js
@@ -53,60 +53,55 @@ export const setResizingStatus = ({ commit }, resizing) => {
export const createTempEntry = (
{ state, commit, dispatch },
{ name, type, content = '', base64 = false, binary = false, rawPath = '' },
-) =>
- new Promise(resolve => {
- const fullName = name.slice(-1) !== '/' && type === 'tree' ? `${name}/` : name;
-
- if (state.entries[name] && !state.entries[name].deleted) {
- flash(
- `The name "${name.split('/').pop()}" is already taken in this directory.`,
- 'alert',
- document,
- null,
- false,
- true,
- );
-
- resolve();
-
- return null;
- }
-
- const data = decorateFiles({
- data: [fullName],
- projectId: state.currentProjectId,
- branchId: state.currentBranchId,
- type,
- tempFile: true,
- content,
- base64,
- binary,
- rawPath,
- });
- const { file, parentPath } = data;
+) => {
+ const fullName = name.slice(-1) !== '/' && type === 'tree' ? `${name}/` : name;
+
+ if (state.entries[name] && !state.entries[name].deleted) {
+ flash(
+ sprintf(__('The name "%{name}" is already taken in this directory.'), {
+ name: name.split('/').pop(),
+ }),
+ 'alert',
+ document,
+ null,
+ false,
+ true,
+ );
- commit(types.CREATE_TMP_ENTRY, {
- data,
- projectId: state.currentProjectId,
- branchId: state.currentBranchId,
- });
+ return;
+ }
- if (type === 'blob') {
- commit(types.TOGGLE_FILE_OPEN, file.path);
- commit(types.ADD_FILE_TO_CHANGED, file.path);
- dispatch('setFileActive', file.path);
- dispatch('triggerFilesChange');
- dispatch('burstUnusedSeal');
- }
+ const data = decorateFiles({
+ data: [fullName],
+ projectId: state.currentProjectId,
+ branchId: state.currentBranchId,
+ type,
+ tempFile: true,
+ content,
+ base64,
+ binary,
+ rawPath,
+ });
+ const { file, parentPath } = data;
- if (parentPath && !state.entries[parentPath].opened) {
- commit(types.TOGGLE_TREE_OPEN, parentPath);
- }
+ commit(types.CREATE_TMP_ENTRY, {
+ data,
+ projectId: state.currentProjectId,
+ branchId: state.currentBranchId,
+ });
- resolve(file);
+ if (type === 'blob') {
+ commit(types.TOGGLE_FILE_OPEN, file.path);
+ commit(types.ADD_FILE_TO_CHANGED, file.path);
+ dispatch('setFileActive', file.path);
+ dispatch('triggerFilesChange');
+ dispatch('burstUnusedSeal');
+ }
- return null;
- });
+ if (parentPath && !state.entries[parentPath].opened) {
+ commit(types.TOGGLE_TREE_OPEN, parentPath);
+ }
+};
export const scrollToTab = () => {
Vue.nextTick(() => {
@@ -211,8 +206,9 @@ export const deleteEntry = ({ commit, dispatch, state }, path) => {
const entry = state.entries[path];
const { prevPath, prevName, prevParentPath } = entry;
const isTree = entry.type === 'tree';
+ const prevEntry = prevPath && state.entries[prevPath];
- if (prevPath) {
+ if (prevPath && (!prevEntry || prevEntry.deleted)) {
dispatch('renameEntry', {
path,
name: prevName,
@@ -245,6 +241,11 @@ export const resetOpenFiles = ({ commit }) => commit(types.RESET_OPEN_FILES);
export const renameEntry = ({ dispatch, commit, state }, { path, name, parentPath }) => {
const entry = state.entries[path];
const newPath = parentPath ? `${parentPath}/${name}` : name;
+ const existingParent = parentPath && state.entries[parentPath];
+
+ if (parentPath && (!existingParent || existingParent.deleted)) {
+ dispatch('createTempEntry', { name: parentPath, type: 'tree' });
+ }
commit(types.RENAME_ENTRY, { path, name, parentPath });
diff --git a/app/assets/javascripts/ide/stores/actions/project.js b/app/assets/javascripts/ide/stores/actions/project.js
index 52bf9becd0f..e206f9bee9e 100644
--- a/app/assets/javascripts/ide/stores/actions/project.js
+++ b/app/assets/javascripts/ide/stores/actions/project.js
@@ -83,8 +83,11 @@ export const showBranchNotFoundError = ({ dispatch }, branchId) => {
});
};
-export const showEmptyState = ({ commit, state }, { projectId, branchId }) => {
+export const showEmptyState = ({ commit, state, dispatch }, { projectId, branchId }) => {
const treePath = `${projectId}/${branchId}`;
+
+ dispatch('setCurrentBranchId', branchId);
+
commit(types.CREATE_TREE, { treePath });
commit(types.TOGGLE_LOADING, {
entry: state.trees[treePath],
diff --git a/app/assets/javascripts/pages/admin/application_settings/index.js b/app/assets/javascripts/pages/admin/application_settings/index.js
index 47bd70537f1..089dedd14cb 100644
--- a/app/assets/javascripts/pages/admin/application_settings/index.js
+++ b/app/assets/javascripts/pages/admin/application_settings/index.js
@@ -1,7 +1,11 @@
import initSettingsPanels from '~/settings_panels';
import projectSelect from '~/project_select';
+import selfMonitor from '~/self_monitor';
document.addEventListener('DOMContentLoaded', () => {
+ if (gon.features && gon.features.selfMonitoringProject) {
+ selfMonitor();
+ }
// Initialize expandable settings panels
initSettingsPanels();
projectSelect();
diff --git a/app/assets/javascripts/self_monitor/components/self_monitor_form.vue b/app/assets/javascripts/self_monitor/components/self_monitor_form.vue
new file mode 100644
index 00000000000..2f364eae67f
--- /dev/null
+++ b/app/assets/javascripts/self_monitor/components/self_monitor_form.vue
@@ -0,0 +1,160 @@
+<script>
+import Vue from 'vue';
+import { GlFormGroup, GlButton, GlModal, GlToast, GlToggle } from '@gitlab/ui';
+import { mapState, mapActions } from 'vuex';
+import { __, s__, sprintf } from '~/locale';
+import { visitUrl, getBaseURL } from '~/lib/utils/url_utility';
+
+Vue.use(GlToast);
+
+export default {
+ components: {
+ GlFormGroup,
+ GlButton,
+ GlModal,
+ GlToggle,
+ },
+ formLabels: {
+ createProject: __('Create Project'),
+ },
+ data() {
+ return {
+ modalId: 'delete-self-monitor-modal',
+ };
+ },
+ computed: {
+ ...mapState('selfMonitoring', [
+ 'projectEnabled',
+ 'projectCreated',
+ 'showAlert',
+ 'projectPath',
+ 'loading',
+ 'alertContent',
+ ]),
+ selfMonitorEnabled: {
+ get() {
+ return this.projectEnabled;
+ },
+ set(projectEnabled) {
+ this.setSelfMonitor(projectEnabled);
+ },
+ },
+ selfMonitorProjectFullUrl() {
+ return `${getBaseURL()}/${this.projectPath}`;
+ },
+ selfMonitoringFormText() {
+ if (this.projectCreated) {
+ return sprintf(
+ s__(
+ 'SelfMonitoring|Enabling this feature creates a %{projectLinkStart}project%{projectLinkEnd} that can be used to monitor the health of your instance.',
+ ),
+ {
+ projectLinkStart: `<a href="${this.selfMonitorProjectFullUrl}">`,
+ projectLinkEnd: '</a>',
+ },
+ false,
+ );
+ }
+
+ return s__(
+ 'SelfMonitoring|Enabling this feature creates a project that can be used to monitor the health of your instance.',
+ );
+ },
+ },
+ watch: {
+ selfMonitorEnabled() {
+ this.saveChangesSelfMonitorProject();
+ },
+ showAlert() {
+ let toastOptions = {
+ onComplete: () => {
+ this.resetAlert();
+ },
+ };
+
+ if (this.showAlert) {
+ if (this.alertContent.actionName && this.alertContent.actionName.length > 0) {
+ toastOptions = {
+ ...toastOptions,
+ action: {
+ text: this.alertContent.actionText,
+ onClick: (_, toastObject) => {
+ this[this.alertContent.actionName]();
+ toastObject.goAway(0);
+ },
+ },
+ };
+ }
+ this.$toast.show(this.alertContent.message, toastOptions);
+ }
+ },
+ },
+ methods: {
+ ...mapActions('selfMonitoring', [
+ 'setSelfMonitor',
+ 'createProject',
+ 'deleteProject',
+ 'resetAlert',
+ ]),
+ hideSelfMonitorModal() {
+ this.$root.$emit('bv::hide::modal', this.modalId);
+ this.setSelfMonitor(true);
+ },
+ showSelfMonitorModal() {
+ this.$root.$emit('bv::show::modal', this.modalId);
+ },
+ saveChangesSelfMonitorProject() {
+ if (this.projectCreated && !this.projectEnabled) {
+ this.showSelfMonitorModal();
+ } else {
+ this.createProject();
+ }
+ },
+ viewSelfMonitorProject() {
+ visitUrl(this.selfMonitorProjectFullUrl);
+ },
+ },
+};
+</script>
+<template>
+ <section class="settings no-animate js-self-monitoring-settings">
+ <div class="settings-header">
+ <h4 class="js-section-header">
+ {{ s__('SelfMonitoring|Self monitoring') }}
+ </h4>
+ <gl-button class="js-settings-toggle">{{ __('Expand') }}</gl-button>
+ <p class="js-section-sub-header">
+ {{ s__('SelfMonitoring|Enable or disable instance self monitoring') }}
+ </p>
+ </div>
+ <div class="settings-content">
+ <form name="self-monitoring-form">
+ <p v-html="selfMonitoringFormText"></p>
+ <gl-form-group :label="$options.formLabels.createProject" label-for="self-monitor-toggle">
+ <gl-toggle
+ v-model="selfMonitorEnabled"
+ :is-loading="loading"
+ name="self-monitor-toggle"
+ />
+ </gl-form-group>
+ </form>
+ </div>
+ <gl-modal
+ :title="s__('SelfMonitoring|Disable self monitoring?')"
+ :modal-id="modalId"
+ :ok-title="__('Delete project')"
+ :cancel-title="__('Cancel')"
+ ok-variant="danger"
+ @ok="deleteProject"
+ @cancel="hideSelfMonitorModal"
+ >
+ <div>
+ {{
+ s__(
+ 'SelfMonitoring|Disabling this feature will delete the self monitoring project. Are you sure you want to delete the project?',
+ )
+ }}
+ </div>
+ </gl-modal>
+ </section>
+</template>
diff --git a/app/assets/javascripts/self_monitor/index.js b/app/assets/javascripts/self_monitor/index.js
new file mode 100644
index 00000000000..42c94e11989
--- /dev/null
+++ b/app/assets/javascripts/self_monitor/index.js
@@ -0,0 +1,23 @@
+import Vue from 'vue';
+import store from './store';
+import SelfMonitorForm from './components/self_monitor_form.vue';
+
+export default () => {
+ const el = document.querySelector('.js-self-monitoring-settings');
+ let selfMonitorProjectCreated;
+
+ if (el) {
+ selfMonitorProjectCreated = el.dataset.selfMonitoringProjectExists;
+ // eslint-disable-next-line no-new
+ new Vue({
+ el,
+ store: store({
+ projectEnabled: selfMonitorProjectCreated,
+ ...el.dataset,
+ }),
+ render(createElement) {
+ return createElement(SelfMonitorForm);
+ },
+ });
+ }
+};
diff --git a/app/assets/javascripts/self_monitor/store/actions.js b/app/assets/javascripts/self_monitor/store/actions.js
new file mode 100644
index 00000000000..f8430a9b136
--- /dev/null
+++ b/app/assets/javascripts/self_monitor/store/actions.js
@@ -0,0 +1,126 @@
+import { __, s__ } from '~/locale';
+import axios from '~/lib/utils/axios_utils';
+import statusCodes from '~/lib/utils/http_status';
+import { backOff } from '~/lib/utils/common_utils';
+import * as types from './mutation_types';
+
+const TWO_MINUTES = 120000;
+
+function backOffRequest(makeRequestCallback) {
+ return backOff((next, stop) => {
+ makeRequestCallback()
+ .then(resp => {
+ if (resp.status === statusCodes.ACCEPTED) {
+ next();
+ } else {
+ stop(resp);
+ }
+ })
+ .catch(stop);
+ }, TWO_MINUTES);
+}
+
+export const setSelfMonitor = ({ commit }, enabled) => commit(types.SET_ENABLED, enabled);
+
+export const createProject = ({ dispatch }) => dispatch('requestCreateProject');
+
+export const resetAlert = ({ commit }) => commit(types.SET_SHOW_ALERT, false);
+
+export const requestCreateProject = ({ dispatch, state, commit }) => {
+ commit(types.SET_LOADING, true);
+ axios
+ .post(state.createProjectEndpoint)
+ .then(resp => {
+ if (resp.status === statusCodes.ACCEPTED) {
+ dispatch('requestCreateProjectStatus', resp.data.job_id);
+ }
+ })
+ .catch(error => {
+ dispatch('requestCreateProjectError', error);
+ });
+};
+
+export const requestCreateProjectStatus = ({ dispatch, state }, jobId) => {
+ backOffRequest(() => axios.get(state.createProjectStatusEndpoint, { params: { job_id: jobId } }))
+ .then(resp => {
+ if (resp.status === statusCodes.OK) {
+ dispatch('requestCreateProjectSuccess', resp.data);
+ }
+ })
+ .catch(error => {
+ dispatch('requestCreateProjectError', error);
+ });
+};
+
+export const requestCreateProjectSuccess = ({ commit }, selfMonitorData) => {
+ commit(types.SET_LOADING, false);
+ commit(types.SET_PROJECT_URL, selfMonitorData.project_full_path);
+ commit(types.SET_ALERT_CONTENT, {
+ message: s__('SelfMonitoring|Self monitoring project has been successfully created.'),
+ actionText: __('View project'),
+ actionName: 'viewSelfMonitorProject',
+ });
+ commit(types.SET_SHOW_ALERT, true);
+ commit(types.SET_PROJECT_CREATED, true);
+};
+
+export const requestCreateProjectError = ({ commit }, error) => {
+ const { response } = error;
+ const message = response.data && response.data.message ? response.data.message : '';
+
+ commit(types.SET_ALERT_CONTENT, {
+ message: `${__('There was an error saving your changes.')} ${message}`,
+ });
+ commit(types.SET_SHOW_ALERT, true);
+ commit(types.SET_LOADING, false);
+};
+
+export const deleteProject = ({ dispatch }) => dispatch('requestDeleteProject');
+
+export const requestDeleteProject = ({ dispatch, state, commit }) => {
+ commit(types.SET_LOADING, true);
+ axios
+ .delete(state.deleteProjectEndpoint)
+ .then(resp => {
+ if (resp.status === statusCodes.ACCEPTED) {
+ dispatch('requestDeleteProjectStatus', resp.data.job_id);
+ }
+ })
+ .catch(error => {
+ dispatch('requestDeleteProjectError', error);
+ });
+};
+
+export const requestDeleteProjectStatus = ({ dispatch, state }, jobId) => {
+ backOffRequest(() => axios.get(state.deleteProjectStatusEndpoint, { params: { job_id: jobId } }))
+ .then(resp => {
+ if (resp.status === statusCodes.OK) {
+ dispatch('requestDeleteProjectSuccess', resp.data);
+ }
+ })
+ .catch(error => {
+ dispatch('requestDeleteProjectError', error);
+ });
+};
+
+export const requestDeleteProjectSuccess = ({ commit }) => {
+ commit(types.SET_PROJECT_URL, '');
+ commit(types.SET_PROJECT_CREATED, false);
+ commit(types.SET_ALERT_CONTENT, {
+ message: s__('SelfMonitoring|Self monitoring project has been successfully deleted.'),
+ actionText: __('Undo'),
+ actionName: 'createProject',
+ });
+ commit(types.SET_SHOW_ALERT, true);
+ commit(types.SET_LOADING, false);
+};
+
+export const requestDeleteProjectError = ({ commit }, error) => {
+ const { response } = error;
+ const message = response.data && response.data.message ? response.data.message : '';
+
+ commit(types.SET_ALERT_CONTENT, {
+ message: `${__('There was an error saving your changes.')} ${message}`,
+ });
+ commit(types.SET_LOADING, false);
+};
diff --git a/app/assets/javascripts/self_monitor/store/index.js b/app/assets/javascripts/self_monitor/store/index.js
new file mode 100644
index 00000000000..a222e9c87b8
--- /dev/null
+++ b/app/assets/javascripts/self_monitor/store/index.js
@@ -0,0 +1,21 @@
+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 const createStore = initialState =>
+ new Vuex.Store({
+ modules: {
+ selfMonitoring: {
+ namespaced: true,
+ state: createState(initialState),
+ actions,
+ mutations,
+ },
+ },
+ });
+
+export default createStore;
diff --git a/app/assets/javascripts/self_monitor/store/mutation_types.js b/app/assets/javascripts/self_monitor/store/mutation_types.js
new file mode 100644
index 00000000000..c5952b66144
--- /dev/null
+++ b/app/assets/javascripts/self_monitor/store/mutation_types.js
@@ -0,0 +1,6 @@
+export const SET_ENABLED = 'SET_ENABLED';
+export const SET_PROJECT_CREATED = 'SET_PROJECT_CREATED';
+export const SET_SHOW_ALERT = 'SET_SHOW_ALERT';
+export const SET_PROJECT_URL = 'SET_PROJECT_URL';
+export const SET_LOADING = 'SET_LOADING';
+export const SET_ALERT_CONTENT = 'SET_ALERT_CONTENT';
diff --git a/app/assets/javascripts/self_monitor/store/mutations.js b/app/assets/javascripts/self_monitor/store/mutations.js
new file mode 100644
index 00000000000..7dca8bcdc4d
--- /dev/null
+++ b/app/assets/javascripts/self_monitor/store/mutations.js
@@ -0,0 +1,22 @@
+import * as types from './mutation_types';
+
+export default {
+ [types.SET_ENABLED](state, enabled) {
+ state.projectEnabled = enabled;
+ },
+ [types.SET_PROJECT_CREATED](state, created) {
+ state.projectCreated = created;
+ },
+ [types.SET_SHOW_ALERT](state, show) {
+ state.showAlert = show;
+ },
+ [types.SET_PROJECT_URL](state, url) {
+ state.projectPath = url;
+ },
+ [types.SET_LOADING](state, loading) {
+ state.loading = loading;
+ },
+ [types.SET_ALERT_CONTENT](state, content) {
+ state.alertContent = content;
+ },
+};
diff --git a/app/assets/javascripts/self_monitor/store/state.js b/app/assets/javascripts/self_monitor/store/state.js
new file mode 100644
index 00000000000..b8b4a4af614
--- /dev/null
+++ b/app/assets/javascripts/self_monitor/store/state.js
@@ -0,0 +1,15 @@
+import { parseBoolean } from '~/lib/utils/common_utils';
+
+export default (initialState = {}) => ({
+ projectEnabled: parseBoolean(initialState.projectEnabled) || false,
+ projectCreated: parseBoolean(initialState.selfMonitorProjectCreated) || false,
+ createProjectEndpoint: initialState.createSelfMonitoringProjectPath || '',
+ deleteProjectEndpoint: initialState.deleteSelfMonitoringProjectPath || '',
+ createProjectStatusEndpoint: initialState.statusCreateSelfMonitoringProjectPath || '',
+ deleteProjectStatusEndpoint: initialState.statusDeleteSelfMonitoringProjectPath || '',
+ selfMonitorProjectPath: initialState.selfMonitoringProjectFullPath || '',
+ showAlert: false,
+ projectPath: '',
+ loading: false,
+ alertContent: {},
+});
diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb
index 657e5accdab..719de095faf 100644
--- a/app/helpers/markup_helper.rb
+++ b/app/helpers/markup_helper.rb
@@ -281,7 +281,7 @@ module MarkupHelper
context.reverse_merge!(
current_user: (current_user if defined?(current_user)),
- # RelativeLinkFilter
+ # RepositoryLinkFilter and UploadLinkFilter
commit: @commit,
project_wiki: @project_wiki,
ref: @ref,
diff --git a/app/views/admin/application_settings/metrics_and_profiling.html.haml b/app/views/admin/application_settings/metrics_and_profiling.html.haml
index 55a48da8342..ff40d7da892 100644
--- a/app/views/admin/application_settings/metrics_and_profiling.html.haml
+++ b/app/views/admin/application_settings/metrics_and_profiling.html.haml
@@ -47,6 +47,9 @@
.settings-content
= render 'performance_bar'
+- if Feature.enabled?(:self_monitoring_project)
+ .js-self-monitoring-settings{ data: self_monitoring_project_data }
+
%section.settings.as-usage.no-animate#js-usage-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header#usage-statistics
%h4