summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/releases/detail
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/releases/detail')
-rw-r--r--app/assets/javascripts/releases/detail/components/app.vue156
-rw-r--r--app/assets/javascripts/releases/detail/index.js19
-rw-r--r--app/assets/javascripts/releases/detail/store/actions.js62
-rw-r--r--app/assets/javascripts/releases/detail/store/index.js14
-rw-r--r--app/assets/javascripts/releases/detail/store/mutation_types.js12
-rw-r--r--app/assets/javascripts/releases/detail/store/mutations.js42
-rw-r--r--app/assets/javascripts/releases/detail/store/state.js15
7 files changed, 320 insertions, 0 deletions
diff --git a/app/assets/javascripts/releases/detail/components/app.vue b/app/assets/javascripts/releases/detail/components/app.vue
new file mode 100644
index 00000000000..54a441de886
--- /dev/null
+++ b/app/assets/javascripts/releases/detail/components/app.vue
@@ -0,0 +1,156 @@
+<script>
+import { mapState, mapActions } from 'vuex';
+import { GlButton, GlFormInput, GlFormGroup } from '@gitlab/ui';
+import { __, sprintf } from '~/locale';
+import MarkdownField from '~/vue_shared/components/markdown/field.vue';
+import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
+
+export default {
+ name: 'ReleaseDetailApp',
+ components: {
+ GlFormInput,
+ GlFormGroup,
+ GlButton,
+ MarkdownField,
+ },
+ directives: {
+ autofocusonshow,
+ },
+ computed: {
+ ...mapState([
+ 'isFetchingRelease',
+ 'fetchError',
+ 'markdownDocsPath',
+ 'markdownPreviewPath',
+ 'releasesPagePath',
+ ]),
+ showForm() {
+ return !this.isFetchingRelease && !this.fetchError;
+ },
+ subtitleText() {
+ return sprintf(
+ __(
+ 'Releases are based on Git tags. We recommend naming tags that fit within semantic versioning, for example %{codeStart}v1.0%{codeEnd}, %{codeStart}v2.0-pre%{codeEnd}.',
+ ),
+ {
+ codeStart: '<code>',
+ codeEnd: '</code>',
+ },
+ false,
+ );
+ },
+ tagName() {
+ return this.$store.state.release.tagName;
+ },
+ releaseTitle: {
+ get() {
+ return this.$store.state.release.name;
+ },
+ set(title) {
+ this.updateReleaseTitle(title);
+ },
+ },
+ releaseNotes: {
+ get() {
+ return this.$store.state.release.description;
+ },
+ set(notes) {
+ this.updateReleaseNotes(notes);
+ },
+ },
+ },
+ created() {
+ this.fetchRelease();
+ },
+ methods: {
+ ...mapActions([
+ 'fetchRelease',
+ 'updateRelease',
+ 'updateReleaseTitle',
+ 'updateReleaseNotes',
+ 'navigateToReleasesPage',
+ ]),
+ },
+};
+</script>
+<template>
+ <div class="d-flex flex-column">
+ <p class="pt-3 js-subtitle-text" v-html="subtitleText"></p>
+ <form v-if="showForm" @submit.prevent="updateRelease()">
+ <div class="row">
+ <gl-form-group class="col-md-6 col-lg-5 col-xl-4">
+ <label for="git-ref">{{ __('Tag name') }}</label>
+ <gl-form-input
+ id="git-ref"
+ v-model="tagName"
+ type="text"
+ class="form-control"
+ aria-describedby="tag-name-help"
+ disabled
+ />
+ <div id="tag-name-help" class="form-text text-muted">
+ {{ __('Choose an existing tag, or create a new one') }}
+ </div>
+ </gl-form-group>
+ </div>
+ <gl-form-group>
+ <label for="release-title">{{ __('Release title') }}</label>
+ <gl-form-input
+ id="release-title"
+ ref="releaseTitleInput"
+ v-model="releaseTitle"
+ v-autofocusonshow
+ autofocus
+ type="text"
+ class="form-control"
+ />
+ </gl-form-group>
+ <gl-form-group>
+ <label for="release-notes">{{ __('Release notes') }}</label>
+ <div class="bordered-box pr-3 pl-3">
+ <markdown-field
+ :can-attach-file="true"
+ :markdown-preview-path="markdownPreviewPath"
+ :markdown-docs-path="markdownDocsPath"
+ :add-spacing-classes="false"
+ class="prepend-top-10 append-bottom-10"
+ >
+ <textarea
+ id="release-notes"
+ slot="textarea"
+ v-model="releaseNotes"
+ class="note-textarea js-gfm-input js-autosize markdown-area"
+ dir="auto"
+ data-supports-quick-actions="false"
+ :aria-label="__('Release notes')"
+ :placeholder="__('Write your release notes or drag your files hereā€¦')"
+ @keydown.meta.enter="updateRelease()"
+ @keydown.ctrl.enter="updateRelease()"
+ >
+ </textarea>
+ </markdown-field>
+ </div>
+ </gl-form-group>
+
+ <div class="d-flex pt-3">
+ <gl-button
+ class="mr-auto js-submit-button"
+ variant="success"
+ type="submit"
+ :aria-label="__('Save changes')"
+ >
+ {{ __('Save changes') }}
+ </gl-button>
+ <gl-button
+ class="js-cancel-button"
+ variant="default"
+ type="button"
+ :aria-label="__('Cancel')"
+ @click="navigateToReleasesPage()"
+ >
+ {{ __('Cancel') }}
+ </gl-button>
+ </div>
+ </form>
+ </div>
+</template>
diff --git a/app/assets/javascripts/releases/detail/index.js b/app/assets/javascripts/releases/detail/index.js
new file mode 100644
index 00000000000..3da971e6d90
--- /dev/null
+++ b/app/assets/javascripts/releases/detail/index.js
@@ -0,0 +1,19 @@
+import Vue from 'vue';
+import ReleaseDetailApp from './components/app.vue';
+import createStore from './store';
+
+export default () => {
+ const el = document.getElementById('js-edit-release-page');
+
+ const store = createStore(el.dataset);
+ store.dispatch('setInitialState', el.dataset);
+
+ return new Vue({
+ el,
+ store,
+ components: { ReleaseDetailApp },
+ render(createElement) {
+ return createElement('release-detail-app');
+ },
+ });
+};
diff --git a/app/assets/javascripts/releases/detail/store/actions.js b/app/assets/javascripts/releases/detail/store/actions.js
new file mode 100644
index 00000000000..c9749582f5c
--- /dev/null
+++ b/app/assets/javascripts/releases/detail/store/actions.js
@@ -0,0 +1,62 @@
+import * as types from './mutation_types';
+import api from '~/api';
+import createFlash from '~/flash';
+import { s__ } from '~/locale';
+import { redirectTo } from '~/lib/utils/url_utility';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+
+export const setInitialState = ({ commit }, initialState) =>
+ commit(types.SET_INITIAL_STATE, initialState);
+
+export const requestRelease = ({ commit }) => commit(types.REQUEST_RELEASE);
+export const receiveReleaseSuccess = ({ commit }, data) =>
+ commit(types.RECEIVE_RELEASE_SUCCESS, data);
+export const receiveReleaseError = ({ commit }, error) => {
+ commit(types.RECEIVE_RELEASE_ERROR, error);
+ createFlash(s__('Release|Something went wrong while getting the release details'));
+};
+
+export const fetchRelease = ({ dispatch, state }) => {
+ dispatch('requestRelease');
+
+ return api
+ .release(state.projectId, state.tagName)
+ .then(({ data: release }) => {
+ const camelCasedRelease = convertObjectPropsToCamelCase(release, { deep: true });
+ dispatch('receiveReleaseSuccess', camelCasedRelease);
+ })
+ .catch(error => {
+ dispatch('receiveReleaseError', error);
+ });
+};
+
+export const updateReleaseTitle = ({ commit }, title) => commit(types.UPDATE_RELEASE_TITLE, title);
+export const updateReleaseNotes = ({ commit }, notes) => commit(types.UPDATE_RELEASE_NOTES, notes);
+
+export const requestUpdateRelease = ({ commit }) => commit(types.REQUEST_UPDATE_RELEASE);
+export const receiveUpdateReleaseSuccess = ({ commit, dispatch }) => {
+ commit(types.RECEIVE_UPDATE_RELEASE_SUCCESS);
+ dispatch('navigateToReleasesPage');
+};
+export const receiveUpdateReleaseError = ({ commit }, error) => {
+ commit(types.RECEIVE_UPDATE_RELEASE_ERROR, error);
+ createFlash(s__('Release|Something went wrong while saving the release details'));
+};
+
+export const updateRelease = ({ dispatch, state }) => {
+ dispatch('requestUpdateRelease');
+
+ return api
+ .updateRelease(state.projectId, state.tagName, {
+ name: state.release.name,
+ description: state.release.description,
+ })
+ .then(() => dispatch('receiveUpdateReleaseSuccess'))
+ .catch(error => {
+ dispatch('receiveUpdateReleaseError', error);
+ });
+};
+
+export const navigateToReleasesPage = ({ state }) => {
+ redirectTo(state.releasesPagePath);
+};
diff --git a/app/assets/javascripts/releases/detail/store/index.js b/app/assets/javascripts/releases/detail/store/index.js
new file mode 100644
index 00000000000..e8623a49356
--- /dev/null
+++ b/app/assets/javascripts/releases/detail/store/index.js
@@ -0,0 +1,14 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import * as actions from './actions';
+import mutations from './mutations';
+import state from './state';
+
+Vue.use(Vuex);
+
+export default () =>
+ new Vuex.Store({
+ actions,
+ mutations,
+ state,
+ });
diff --git a/app/assets/javascripts/releases/detail/store/mutation_types.js b/app/assets/javascripts/releases/detail/store/mutation_types.js
new file mode 100644
index 00000000000..75e1d78a645
--- /dev/null
+++ b/app/assets/javascripts/releases/detail/store/mutation_types.js
@@ -0,0 +1,12 @@
+export const SET_INITIAL_STATE = 'SET_INITIAL_STATE';
+
+export const REQUEST_RELEASE = 'REQUEST_RELEASE';
+export const RECEIVE_RELEASE_SUCCESS = 'RECEIVE_RELEASE_SUCCESS';
+export const RECEIVE_RELEASE_ERROR = 'RECEIVE_RELEASE_ERROR';
+
+export const UPDATE_RELEASE_TITLE = 'UPDATE_RELEASE_TITLE';
+export const UPDATE_RELEASE_NOTES = 'UPDATE_RELEASE_NOTES';
+
+export const REQUEST_UPDATE_RELEASE = 'REQUEST_UPDATE_RELEASE';
+export const RECEIVE_UPDATE_RELEASE_SUCCESS = 'RECEIVE_UPDATE_RELEASE_SUCCESS';
+export const RECEIVE_UPDATE_RELEASE_ERROR = 'RECEIVE_UPDATE_RELEASE_ERROR';
diff --git a/app/assets/javascripts/releases/detail/store/mutations.js b/app/assets/javascripts/releases/detail/store/mutations.js
new file mode 100644
index 00000000000..d739978d755
--- /dev/null
+++ b/app/assets/javascripts/releases/detail/store/mutations.js
@@ -0,0 +1,42 @@
+import * as types from './mutation_types';
+
+export default {
+ [types.SET_INITIAL_STATE](state, initialState) {
+ Object.keys(state).forEach(key => {
+ state[key] = initialState[key];
+ });
+ },
+
+ [types.REQUEST_RELEASE](state) {
+ state.isFetchingRelease = true;
+ },
+ [types.RECEIVE_RELEASE_SUCCESS](state, data) {
+ state.fetchError = undefined;
+ state.isFetchingRelease = false;
+ state.release = data;
+ },
+ [types.RECEIVE_RELEASE_ERROR](state, error) {
+ state.fetchError = error;
+ state.isFetchingRelease = false;
+ state.release = undefined;
+ },
+
+ [types.UPDATE_RELEASE_TITLE](state, title) {
+ state.release.name = title;
+ },
+ [types.UPDATE_RELEASE_NOTES](state, notes) {
+ state.release.description = notes;
+ },
+
+ [types.REQUEST_UPDATE_RELEASE](state) {
+ state.isUpdatingRelease = true;
+ },
+ [types.RECEIVE_UPDATE_RELEASE_SUCCESS](state) {
+ state.updateError = undefined;
+ state.isUpdatingRelease = false;
+ },
+ [types.RECEIVE_UPDATE_RELEASE_ERROR](state, error) {
+ state.updateError = error;
+ state.isUpdatingRelease = false;
+ },
+};
diff --git a/app/assets/javascripts/releases/detail/store/state.js b/app/assets/javascripts/releases/detail/store/state.js
new file mode 100644
index 00000000000..ff98e2bed78
--- /dev/null
+++ b/app/assets/javascripts/releases/detail/store/state.js
@@ -0,0 +1,15 @@
+export default () => ({
+ projectId: null,
+ tagName: null,
+ releasesPagePath: null,
+ markdownDocsPath: null,
+ markdownPreviewPath: null,
+
+ release: null,
+
+ isFetchingRelease: false,
+ fetchError: null,
+
+ isUpdatingRelease: false,
+ updateError: null,
+});