diff options
Diffstat (limited to 'app/assets/javascripts')
-rw-r--r-- | app/assets/javascripts/api.js | 7 | ||||
-rw-r--r-- | app/assets/javascripts/pages/projects/releases/index/index.js | 3 | ||||
-rw-r--r-- | app/assets/javascripts/releases/components/app.vue | 82 | ||||
-rw-r--r-- | app/assets/javascripts/releases/components/release_block.vue | 89 | ||||
-rw-r--r-- | app/assets/javascripts/releases/index.js | 24 | ||||
-rw-r--r-- | app/assets/javascripts/releases/store/actions.js | 37 | ||||
-rw-r--r-- | app/assets/javascripts/releases/store/index.js | 14 | ||||
-rw-r--r-- | app/assets/javascripts/releases/store/mutation_types.js | 3 | ||||
-rw-r--r-- | app/assets/javascripts/releases/store/mutations.js | 37 | ||||
-rw-r--r-- | app/assets/javascripts/releases/store/state.js | 5 |
10 files changed, 245 insertions, 56 deletions
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index 7607c4b3b79..a1310d18c26 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -29,6 +29,7 @@ const Api = { commitPipelinesPath: '/:project_id/commit/:sha/pipelines', branchSinglePath: '/api/:version/projects/:id/repository/branches/:branch', createBranchPath: '/api/:version/projects/:id/repository/branches', + releasesPath: '/api/:version/project/:id/releases', group(groupId, callback) { const url = Api.buildUrl(Api.groupPath).replace(':id', groupId); @@ -307,6 +308,12 @@ const Api = { }); }, + releases(id) { + const url = Api.buildUrl(this.releasesPath).replace(':id', encodeURIComponent(id)); + + return axios.get(url); + }, + buildUrl(url) { let urlRoot = ''; if (gon.relative_url_root != null) { diff --git a/app/assets/javascripts/pages/projects/releases/index/index.js b/app/assets/javascripts/pages/projects/releases/index/index.js new file mode 100644 index 00000000000..c183fbb9610 --- /dev/null +++ b/app/assets/javascripts/pages/projects/releases/index/index.js @@ -0,0 +1,3 @@ +import initReleases from '~/releases'; + +document.addEventListener('DOMContentLoaded', initReleases); diff --git a/app/assets/javascripts/releases/components/app.vue b/app/assets/javascripts/releases/components/app.vue new file mode 100644 index 00000000000..0ad5ee2915c --- /dev/null +++ b/app/assets/javascripts/releases/components/app.vue @@ -0,0 +1,82 @@ +<script> +import { mapState, mapActions } from 'vuex'; +import { GlLoadingIcon, GlEmptyState } from '@gitlab/ui'; +import ReleaseBlock from './release_block.vue'; + +export default { + name: 'ReleasesApp', + components: { + GlLoadingIcon, + GlEmptyState, + ReleaseBlock, + }, + props: { + projectId: { + type: String, + required: true, + }, + documentationLink: { + type: String, + required: true, + }, + illustrationPath: { + type: String, + required: true, + }, + }, + computed: { + ...mapState(['isLoading', 'releases', 'hasError']), + shouldRenderEmptyState() { + return !this.releases.length && !this.hasError && !this.isLoading; + }, + shouldRenderSuccessState() { + return this.releases.length && !this.isLoading && !this.hasError; + }, + }, + created() { + this.fetchReleases(this.projectId); + }, + methods: { + ...mapActions(['fetchReleases']), + }, +}; +</script> +<template> + <div class="prepend-top-default"> + <gl-loading-icon v-if="isLoading" :size="2" class="js-loading prepend-top-20" /> + + <gl-empty-state + v-else-if="shouldRenderEmptyState" + class="js-empty-state" + :title="__('Getting started with releases')" + :svg-path="illustrationPath" + :description=" + __( + 'Releases mark specific points in a project\'s development history, communicate information about the type of change, and deliver on prepared, often compiled, versions of the software to be reused elsewhere. Currently, releases can only be created through the API.', + ) + " + :primary-button-link="documentationLink" + :primary-button-text="__('Open Documentation')" + /> + + <div v-else-if="shouldRenderSuccessState" class="js-success-state"> + <release-block + v-for="(release, index) in releases" + :key="release.tag_name" + :release="release" + :class="{ 'linked-card': releases.length > 1 && index !== releases.length - 1 }" + /> + </div> + </div> +</template> +<style> +.linked-card::after { + width: 1px; + content: ' '; + border: 1px solid #e5e5e5; + height: 17px; + top: 100%; + position: absolute; + left: 32px; +} +</style> diff --git a/app/assets/javascripts/releases/components/release_block.vue b/app/assets/javascripts/releases/components/release_block.vue index bd65a225d8f..9c2aade51fc 100644 --- a/app/assets/javascripts/releases/components/release_block.vue +++ b/app/assets/javascripts/releases/components/release_block.vue @@ -17,66 +17,36 @@ export default { }, mixins: [timeagoMixin], props: { - name: { - type: String, - required: true, - }, - tag: { - type: String, - required: true, - }, - commit: { - type: Object, - required: true, - }, - description: { - type: String, - required: false, - default: '', - }, - author: { + release: { type: Object, required: true, - }, - createdAt: { - type: String, - required: false, - default: '', - }, - assetsCount: { - type: Number, - required: false, - default: 0, - }, - sources: { - type: Array, - required: false, - default: () => [], - }, - links: { - type: Array, - required: false, - default: () => [], + default: () => ({}), }, }, computed: { releasedTimeAgo() { return sprintf('released %{time}', { - time: this.timeFormated(this.createdAt), + time: this.timeFormated(this.release.created_at), }); }, userImageAltDescription() { - return this.author && this.author.username - ? sprintf("%{username}'s avatar", { username: this.author.username }) + return this.commit.author && this.commit.author.username + ? sprintf("%{username}'s avatar", { username: this.commit.author.username }) : null; }, + commit() { + return this.release.commit || {}; + }, + assets() { + return this.release.assets || {}; + }, }, }; </script> <template> <div class="card"> <div class="card-body"> - <h2 class="card-title mt-0">{{ name }}</h2> + <h2 class="card-title mt-0">{{ release.name }}</h2> <div class="card-subtitle d-flex flex-wrap text-secondary"> <div class="append-right-8"> @@ -86,40 +56,47 @@ export default { <div class="append-right-8"> <icon name="tag" class="align-middle" /> - <span v-gl-tooltip.bottom :title="__('Tag')">{{ tag }}</span> + <span v-gl-tooltip.bottom :title="__('Tag')">{{ release.tag_name }}</span> </div> <div class="append-right-4"> • - <span v-gl-tooltip.bottom :title="tooltipTitle(createdAt)">{{ releasedTimeAgo }}</span> + <span v-gl-tooltip.bottom :title="tooltipTitle(release.created_at)">{{ + releasedTimeAgo + }}</span> </div> - <div class="d-flex"> + <div v-if="commit.author" class="d-flex"> by <user-avatar-link class="prepend-left-4" - :link-href="author.path" - :img-src="author.avatar_url" + :link-href="commit.author.path" + :img-src="commit.author.avatar_url" :img-alt="userImageAltDescription" - :tooltip-text="author.username" + :tooltip-text="commit.author.username" /> </div> </div> - <div class="card-text prepend-top-default"> + <div + v-if="assets.links.length || assets.sources.length" + Sclass="card-text prepend-top-default" + > <b> - {{ __('Assets') }} <span class="js-assets-count badge badge-pill">{{ assetsCount }}</span> + {{ __('Assets') }} + <span class="js-assets-count badge badge-pill">{{ assets.count }}</span> </b> - <ul class="pl-0 mb-0 prepend-top-8 list-unstyled js-assets-list"> - <li v-for="link in links" :key="link.name" class="append-bottom-8"> + <ul v-if="assets.links.length" class="pl-0 mb-0 prepend-top-8 list-unstyled js-assets-list"> + <li v-for="link in assets.links" :key="link.name" class="append-bottom-8"> <gl-link v-gl-tooltip.bottom :title="__('Download asset')" :href="link.url"> - <icon name="package" class="align-middle append-right-4" /> {{ link.name }} + <icon name="package" class="align-middle append-right-4 align-text-bottom" /> + {{ link.name }} </gl-link> </li> </ul> - <div class="dropdown"> + <div v-if="assets.sources.length" class="dropdown"> <button type="button" class="btn btn-link" @@ -132,14 +109,14 @@ export default { </button> <div class="js-sources-dropdown dropdown-menu"> - <li v-for="asset in sources" :key="asset.url"> + <li v-for="asset in assets.sources" :key="asset.url"> <gl-link :href="asset.url">{{ __('Download') }} {{ asset.format }}</gl-link> </li> </div> </div> </div> - <div class="card-text prepend-top-default"><div v-html="description"></div></div> + <div class="card-text prepend-top-default"><div v-html="release.description_html"></div></div> </div> </div> </template> diff --git a/app/assets/javascripts/releases/index.js b/app/assets/javascripts/releases/index.js new file mode 100644 index 00000000000..6fa7298ac5a --- /dev/null +++ b/app/assets/javascripts/releases/index.js @@ -0,0 +1,24 @@ +import Vue from 'vue'; +import App from './components/app.vue'; +import createStore from './store'; + +export default () => { + const element = document.getElementById('js-releases-page'); + + return new Vue({ + el: element, + store: createStore(), + components: { + App, + }, + render(createElement) { + return createElement('app', { + props: { + endpoint: element.dataset.endpoint, + documentationLink: element.dataset.documentationPath, + illustrationPath: element.dataset.illustrationPath, + }, + }); + }, + }); +}; diff --git a/app/assets/javascripts/releases/store/actions.js b/app/assets/javascripts/releases/store/actions.js new file mode 100644 index 00000000000..baa2251403e --- /dev/null +++ b/app/assets/javascripts/releases/store/actions.js @@ -0,0 +1,37 @@ +import * as types from './mutation_types'; +import createFlash from '~/flash'; +import { __ } from '~/locale'; +import api from '~/api'; + +/** + * Commits a mutation to update the state while the main endpoint is being requested. + */ +export const requestReleases = ({ commit }) => commit(types.REQUEST_RELEASES); + +/** + * Fetches the main endpoint. + * Will dispatch requestNamespace action before starting the request. + * Will dispatch receiveNamespaceSuccess if the request is successfull + * Will dispatch receiveNamesapceError if the request returns an error + * + * @param {String} projectId + */ +export const fetchReleases = ({ dispatch }, projectId) => { + dispatch('requestReleases'); + + api + .releases(projectId) + .then(({ data }) => dispatch('receiveReleasesSuccess', data)) + .catch(() => dispatch('receiveReleasesError')); +}; + +export const receiveReleasesSuccess = ({ commit }, data) => + commit(types.RECEIVE_RELEASES_SUCCESS, data); + +export const receiveReleasesError = ({ commit }) => { + commit(types.RECEIVE_RELEASES_ERROR); + createFlash(__('An error occured while fetching the releases. Please try again.')); +}; + +// prevent babel-plugin-rewire from generating an invalid default during karma tests +export default () => {}; diff --git a/app/assets/javascripts/releases/store/index.js b/app/assets/javascripts/releases/store/index.js new file mode 100644 index 00000000000..968b94f0e0d --- /dev/null +++ b/app/assets/javascripts/releases/store/index.js @@ -0,0 +1,14 @@ +import Vue from 'vue'; +import Vuex from 'vuex'; +import state from './state'; +import * as actions from './actions'; +import mutations from './mutations'; + +Vue.use(Vuex); + +export default () => + new Vuex.Store({ + actions, + mutations, + state: state(), + }); diff --git a/app/assets/javascripts/releases/store/mutation_types.js b/app/assets/javascripts/releases/store/mutation_types.js new file mode 100644 index 00000000000..a74bf15c515 --- /dev/null +++ b/app/assets/javascripts/releases/store/mutation_types.js @@ -0,0 +1,3 @@ +export const REQUEST_RELEASES = 'REQUEST_RELEASES'; +export const RECEIVE_RELEASES_SUCCESS = 'RECEIVE_RELEASES_SUCCESS'; +export const RECEIVE_RELEASES_ERROR = 'RECEIVE_RELEASES_ERROR'; diff --git a/app/assets/javascripts/releases/store/mutations.js b/app/assets/javascripts/releases/store/mutations.js new file mode 100644 index 00000000000..b97dc6cb0ab --- /dev/null +++ b/app/assets/javascripts/releases/store/mutations.js @@ -0,0 +1,37 @@ +import * as types from './mutation_types'; + +export default { + /** + * Sets isLoading to true while the request is being made. + * @param {Object} state + */ + [types.REQUEST_RELEASES](state) { + state.isLoading = true; + }, + + /** + * Sets isLoading to false. + * Sets hasError to false. + * Sets the received data + * @param {Object} state + * @param {Object} data + */ + [types.RECEIVE_RELEASES_SUCCESS](state, data) { + state.hasError = false; + state.isLoading = false; + state.releases = data; + }, + + /** + * Sets isLoading to false. + * Sets hasError to true. + * Resets the data + * @param {Object} state + * @param {Object} data + */ + [types.RECEIVE_RELEASES_ERROR](state) { + state.isLoading = false; + state.releases = []; + state.hasError = true; + }, +}; diff --git a/app/assets/javascripts/releases/store/state.js b/app/assets/javascripts/releases/store/state.js new file mode 100644 index 00000000000..bf25e651c99 --- /dev/null +++ b/app/assets/javascripts/releases/store/state.js @@ -0,0 +1,5 @@ +export default () => ({ + isLoading: false, + hasError: false, + releases: [], +}); |