diff options
6 files changed, 397 insertions, 0 deletions
diff --git a/app/assets/javascripts/repository/components/last_commit.vue b/app/assets/javascripts/repository/components/last_commit.vue new file mode 100644 index 00000000000..b1565699432 --- /dev/null +++ b/app/assets/javascripts/repository/components/last_commit.vue @@ -0,0 +1,145 @@ +<script> +import { GlTooltipDirective, GlLink, GlButton } from '@gitlab/ui'; +import { sprintf, s__ } from '~/locale'; +import Icon from '../../vue_shared/components/icon.vue'; +import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; +import TimeagoTooltip from '../../vue_shared/components/time_ago_tooltip.vue'; +import CommitPipelineStatus from '../../projects/tree/components/commit_pipeline_status_component.vue'; +import CiIcon from '../../vue_shared/components/ci_icon.vue'; +import ClipboardButton from '../../vue_shared/components/clipboard_button.vue'; +import getRefMixin from '../mixins/get_ref'; +import getProjectPath from '../queries/getProjectPath.graphql'; +import pathLastCommit from '../queries/pathLastCommit.query.graphql'; + +export default { + components: { + Icon, + UserAvatarLink, + TimeagoTooltip, + CommitPipelineStatus, + ClipboardButton, + CiIcon, + GlLink, + GlButton, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + mixins: [getRefMixin], + apollo: { + projectPath: { + query: getProjectPath, + }, + commit: { + query: pathLastCommit, + variables() { + return { + projectPath: this.projectPath, + ref: this.ref, + path: this.currentPath.replace(/^\//, ''), + }; + }, + update: data => data.project.repository.tree.commit, + }, + }, + props: { + currentPath: { + type: String, + required: false, + default: '', + }, + }, + data() { + return { + projectPath: '', + commit: {}, + showDescription: false, + }; + }, + computed: { + statusTitle() { + return sprintf(s__('Commits|Commit: %{commitText}'), { + commitText: this.commit.pipeline.detailedStatus.text, + }); + }, + isLoading() { + return this.$apollo.queries.commit.loading; + }, + showCommitId() { + return this.commit.id.substr(0, 8); + }, + }, + methods: { + toggleShowDescription() { + this.showDescription = !this.showDescription; + }, + }, +}; +</script> + +<template> + <div v-if="!isLoading" class="info-well d-none d-sm-flex project-last-commit commit p-3"> + <user-avatar-link + v-if="commit.author" + :link-href="commit.author.webUrl" + :img-src="commit.author.avatarUrl" + :img-size="40" + class="avatar-cell" + /> + <div class="commit-detail flex-list"> + <div class="commit-content qa-commit-content"> + <gl-link :href="commit.webUrl" class="commit-row-message item-title"> + {{ commit.title }} + </gl-link> + <gl-button + v-if="commit.description" + :class="{ open: showDescription }" + :aria-label="__('Show commit description')" + class="text-expander" + @click="toggleShowDescription" + > + <icon name="ellipsis_h" /> + </gl-button> + <div class="committer"> + <gl-link + v-if="commit.author" + :href="commit.author.webUrl" + class="commit-author-link js-user-link" + > + {{ commit.author.name }} + </gl-link> + authored + <timeago-tooltip :time="commit.authoredDate" tooltip-placement="bottom" /> + </div> + <pre + v-if="commit.description" + v-show="showDescription" + class="commit-row-description append-bottom-8" + > + {{ commit.description }} + </pre> + </div> + <div class="commit-actions flex-row"> + <gl-link + v-if="commit.pipeline" + v-gl-tooltip + :href="commit.pipeline.detailedStatus.detailsPath" + :title="statusTitle" + class="js-commit-pipeline" + > + <ci-icon :status="commit.pipeline.detailedStatus" :size="24" :aria-label="statusTitle" /> + </gl-link> + <div class="commit-sha-group d-flex"> + <div class="label label-monospace monospace"> + {{ showCommitId }} + </div> + <clipboard-button + :text="commit.id" + :title="__('Copy commit SHA to clipboard')" + tooltip-placement="bottom" + /> + </div> + </div> + </div> + </div> +</template> diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js index 52f53be045b..d9216e88676 100644 --- a/app/assets/javascripts/repository/index.js +++ b/app/assets/javascripts/repository/index.js @@ -2,6 +2,7 @@ import Vue from 'vue'; import createRouter from './router'; import App from './components/app.vue'; import Breadcrumbs from './components/breadcrumbs.vue'; +import LastCommit from './components/last_commit.vue'; import apolloProvider from './graphql'; import { setTitle } from './utils/title'; @@ -48,6 +49,24 @@ export default function setupVueRepositoryList() { }, }); + const commitEl = document.getElementById('js-last-commit'); + + if (commitEl) { + // eslint-disable-next-line no-new + new Vue({ + el: commitEl, + router, + apolloProvider, + render(h) { + return h(LastCommit, { + props: { + currentPath: this.$route.params.pathMatch, + }, + }); + }, + }); + } + return new Vue({ el, router, diff --git a/app/assets/javascripts/repository/queries/pathLastCommit.query.graphql b/app/assets/javascripts/repository/queries/pathLastCommit.query.graphql new file mode 100644 index 00000000000..90901f54d54 --- /dev/null +++ b/app/assets/javascripts/repository/queries/pathLastCommit.query.graphql @@ -0,0 +1,29 @@ +query pathLastCommit($projectPath: ID!, $path: String, $ref: String!) { + project(fullPath: $projectPath) { + repository { + tree(path: $path, ref: $ref) { + commit { + id + title + message + webUrl + authoredDate + author { + name + avatarUrl + webUrl + } + pipeline { + detailedStatus { + detailsPath + icon + tooltip + text + group + } + } + } + } + } + } +} diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 0cabaeabb9a..e89b1e754f2 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -9140,6 +9140,9 @@ msgstr "" msgid "Show comments only" msgstr "" +msgid "Show commit description" +msgstr "" + msgid "Show complete raw log" msgstr "" diff --git a/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap b/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap new file mode 100644 index 00000000000..3ad6bfa9e5f --- /dev/null +++ b/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap @@ -0,0 +1,98 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Repository last commit component renders commit widget 1`] = ` +<div + class="info-well d-none d-sm-flex project-last-commit commit p-3" +> + <useravatarlink-stub + class="avatar-cell" + imgalt="" + imgcssclasses="" + imgsize="40" + imgsrc="https://test.com" + linkhref="https://test.com/test" + tooltipplacement="top" + tooltiptext="" + username="" + /> + + <div + class="commit-detail flex-list" + > + <div + class="commit-content qa-commit-content" + > + <gllink-stub + class="commit-row-message item-title" + href="https://test.com/commit/123" + > + + Commit title + + </gllink-stub> + + <!----> + + <div + class="committer" + > + <gllink-stub + class="commit-author-link js-user-link" + href="https://test.com/test" + > + + Test + + </gllink-stub> + + authored + + <timeagotooltip-stub + cssclass="" + time="2019-01-01" + tooltipplacement="bottom" + /> + </div> + + <!----> + </div> + + <div + class="commit-actions flex-row" + > + <gllink-stub + class="js-commit-pipeline" + data-original-title="Commit: failed" + href="https://test.com/pipeline" + title="" + > + <ciicon-stub + aria-label="Commit: failed" + cssclasses="" + size="24" + status="[object Object]" + /> + </gllink-stub> + + <div + class="commit-sha-group d-flex" + > + <div + class="label label-monospace monospace" + > + + 12345678 + + </div> + + <clipboardbutton-stub + cssclass="btn-default" + text="123456789" + title="Copy commit SHA to clipboard" + tooltipplacement="bottom" + /> + </div> + </div> + </div> +</div> +`; diff --git a/spec/frontend/repository/components/last_commit_spec.js b/spec/frontend/repository/components/last_commit_spec.js new file mode 100644 index 00000000000..972690a60f6 --- /dev/null +++ b/spec/frontend/repository/components/last_commit_spec.js @@ -0,0 +1,103 @@ +import { shallowMount } from '@vue/test-utils'; +import LastCommit from '~/repository/components/last_commit.vue'; +import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; + +let vm; + +function createCommitData(data = {}) { + return { + id: '123456789', + title: 'Commit title', + message: 'Commit message', + webUrl: 'https://test.com/commit/123', + authoredDate: '2019-01-01', + author: { + name: 'Test', + avatarUrl: 'https://test.com', + webUrl: 'https://test.com/test', + }, + pipeline: { + detailedStatus: { + detailsPath: 'https://test.com/pipeline', + icon: 'failed', + tooltip: 'failed', + text: 'failed', + group: {}, + }, + }, + ...data, + }; +} + +function factory(commit = createCommitData(), loading = false) { + vm = shallowMount(LastCommit, { + mocks: { + $apollo: { + queries: { + commit: { + loading: true, + }, + }, + }, + }, + }); + vm.setData({ commit }); + vm.vm.$apollo.queries.commit.loading = loading; +} + +describe('Repository last commit component', () => { + afterEach(() => { + vm.destroy(); + }); + + it.each` + loading | label + ${true} | ${'hides'} + ${false} | ${'shows'} + `('$label when $loading is true', ({ loading }) => { + factory(createCommitData(), loading); + + expect(vm.isEmpty()).toBe(loading); + }); + + it('renders commit widget', () => { + factory(); + + expect(vm.element).toMatchSnapshot(); + }); + + it('renders short commit ID', () => { + factory(); + + expect(vm.find('.label-monospace').text()).toEqual('12345678'); + }); + + it('hides pipeline components when pipeline does not exist', () => { + factory(createCommitData({ pipeline: null })); + + expect(vm.find('.js-commit-pipeline').exists()).toBe(false); + }); + + it('hides author component when author does not exist', () => { + factory(createCommitData({ author: null })); + + expect(vm.find('.js-user-link').exists()).toBe(false); + expect(vm.find(UserAvatarLink).exists()).toBe(false); + }); + + it('does not render description expander when description is null', () => { + factory(createCommitData({ description: null })); + + expect(vm.find('.text-expander').exists()).toBe(false); + expect(vm.find('.commit-row-description').exists()).toBe(false); + }); + + it('expands commit description when clicking expander', () => { + factory(createCommitData({ description: 'Test description' })); + + vm.find('.text-expander').vm.$emit('click'); + + expect(vm.find('.commit-row-description').isVisible()).toBe(true); + expect(vm.find('.text-expander').classes('open')).toBe(true); + }); +}); |