diff options
author | Phil Hughes <me@iamphill.com> | 2019-06-25 15:26:55 +0100 |
---|---|---|
committer | Phil Hughes <me@iamphill.com> | 2019-06-25 15:26:55 +0100 |
commit | 89278cffdf50d1ddeb146d7ad924f213f69fa102 (patch) | |
tree | 0532657cd40dd4760a9a05e9b8fc7e71af7e736b | |
parent | f53b05cfde5aeac73a7c81a2fe951b5642338ae0 (diff) | |
download | gitlab-ce-89278cffdf50d1ddeb146d7ad924f213f69fa102.tar.gz |
Fetch commit message with log_tree endpoint
13 files changed, 293 insertions, 9 deletions
diff --git a/app/assets/javascripts/repository/components/breadcrumbs.vue b/app/assets/javascripts/repository/components/breadcrumbs.vue index 0d4d431855c..67963dc1923 100644 --- a/app/assets/javascripts/repository/components/breadcrumbs.vue +++ b/app/assets/javascripts/repository/components/breadcrumbs.vue @@ -36,7 +36,7 @@ export default { to: `/tree/${this.ref}${path}`, }); }, - [{ name: this.projectShortPath, path: '/', to: `/tree/${this.ref}` }], + [{ name: this.projectShortPath, path: '/', to: `/tree/${this.ref}/` }], ); }, }, diff --git a/app/assets/javascripts/repository/components/table/index.vue b/app/assets/javascripts/repository/components/table/index.vue index 891e3fe9d16..1e66ccbfa29 100644 --- a/app/assets/javascripts/repository/components/table/index.vue +++ b/app/assets/javascripts/repository/components/table/index.vue @@ -131,7 +131,9 @@ export default { v-for="entry in val" :id="entry.id" :key="`${entry.flatPath}-${entry.id}`" + :project-path="projectPath" :current-path="path" + :name="entry.name" :path="entry.flatPath" :type="entry.type" :url="entry.webUrl" diff --git a/app/assets/javascripts/repository/components/table/row.vue b/app/assets/javascripts/repository/components/table/row.vue index 4519f82fc93..c31e7fa71a2 100644 --- a/app/assets/javascripts/repository/components/table/row.vue +++ b/app/assets/javascripts/repository/components/table/row.vue @@ -1,12 +1,30 @@ <script> -import { GlBadge } from '@gitlab/ui'; +import { GlBadge, GlLink, GlSkeletonLoading } from '@gitlab/ui'; import { visitUrl } from '~/lib/utils/url_utility'; +import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import { getIconName } from '../../utils/icon'; import getRefMixin from '../../mixins/get_ref'; +import getCommit from '../../queries/getCommit.query.graphql'; export default { components: { GlBadge, + GlLink, + GlSkeletonLoading, + TimeagoTooltip, + }, + apollo: { + commit: { + query: getCommit, + variables() { + return { + fileName: this.name, + type: this.type, + path: this.currentPath, + projectPath: this.projectPath, + }; + }, + }, }, mixins: [getRefMixin], props: { @@ -14,10 +32,18 @@ export default { type: String, required: true, }, + projectPath: { + type: String, + required: true, + }, currentPath: { type: String, required: true, }, + name: { + type: String, + required: true, + }, path: { type: String, required: true, @@ -37,6 +63,11 @@ export default { default: null, }, }, + data() { + return { + commit: null, + }; + }, computed: { routerLinkTo() { return this.isFolder ? { path: `/tree/${this.ref}/${this.path}` } : null; @@ -73,7 +104,7 @@ export default { </script> <template> - <tr v-once :class="`file_${id}`" class="tree-item" @click="openRow"> + <tr :class="`file_${id}`" class="tree-item" @click="openRow"> <td class="tree-item-file-name"> <i :aria-label="type" role="img" :class="iconName" class="fa fa-fw"></i> <component :is="linkComponent" :to="routerLinkTo" :href="url" class="str-truncated"> @@ -83,10 +114,18 @@ export default { LFS </gl-badge> <template v-if="isSubmodule"> - @ <a href="#" class="commit-sha">{{ shortSha }}</a> + @ <gl-link href="#" class="commit-sha">{{ shortSha }}</gl-link> </template> </td> - <td class="d-none d-sm-table-cell tree-commit"></td> - <td class="tree-time-ago text-right"></td> + <td class="d-none d-sm-table-cell tree-commit"> + <gl-link v-if="commit" :href="commit.commitPath" class="str-truncated-100 tree-commit-link"> + {{ commit.message }} + </gl-link> + <gl-skeleton-loading v-else :lines="1" class="h-auto" /> + </td> + <td class="tree-time-ago text-right"> + <timeago-tooltip v-if="commit" :time="commit.committedDate" tooltip-placement="bottom" /> + <gl-skeleton-loading v-else :lines="1" class="ml-auto h-auto w-50" /> + </td> </tr> </template> diff --git a/app/assets/javascripts/repository/graphql.js b/app/assets/javascripts/repository/graphql.js index ef147ec15cb..6cb253c8169 100644 --- a/app/assets/javascripts/repository/graphql.js +++ b/app/assets/javascripts/repository/graphql.js @@ -3,6 +3,7 @@ import VueApollo from 'vue-apollo'; import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'; import createDefaultClient from '~/lib/graphql'; import introspectionQueryResultData from './fragmentTypes.json'; +import { fetchLogsTree } from './log_tree'; Vue.use(VueApollo); @@ -13,7 +14,21 @@ const fragmentMatcher = new IntrospectionFragmentMatcher({ }); const defaultClient = createDefaultClient( - {}, + { + Query: { + commit(_, { path, fileName, type }) { + return new Promise(resolve => { + fetchLogsTree(defaultClient, path, '0', { + resolve, + entry: { + name: fileName, + type, + }, + }); + }); + }, + }, + }, { cacheConfig: { fragmentMatcher, diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js index d9216e88676..6280977b05b 100644 --- a/app/assets/javascripts/repository/index.js +++ b/app/assets/javascripts/repository/index.js @@ -16,6 +16,7 @@ export default function setupVueRepositoryList() { projectPath, projectShortPath, ref, + commits: [], }, }); diff --git a/app/assets/javascripts/repository/log_tree.js b/app/assets/javascripts/repository/log_tree.js new file mode 100644 index 00000000000..2c19aca2397 --- /dev/null +++ b/app/assets/javascripts/repository/log_tree.js @@ -0,0 +1,64 @@ +import axios from '~/lib/utils/axios_utils'; +import getCommits from './queries/getCommits.query.graphql'; +import getProjectPath from './queries/getProjectPath.query.graphql'; +import getRef from './queries/getRef.query.graphql'; + +let fetchpromise; +let resolvers = []; + +export function normalizeData(data) { + return data.map(d => ({ + sha: d.commit.id, + message: d.commit.message, + committedDate: d.commit.committed_date, + commitPath: d.commit_path, + fileName: d.file_name, + type: d.type, + __typename: 'LogTreeCommit', + })); +} + +export function resolveCommit(commits, { resolve, entry }) { + const commit = commits.find(c => c.fileName === entry.name && c.type === entry.type); + + if (commit) { + resolve(commit); + } +} + +export function fetchLogsTree(client, path, offset, resolver = null) { + if (resolver) { + resolvers.push(resolver); + } + + if (fetchpromise) return fetchpromise; + + const { projectPath } = client.readQuery({ query: getProjectPath }); + const { ref } = client.readQuery({ query: getRef }); + + fetchpromise = axios + .get(`${gon.gitlab_url}/${projectPath}/refs/${ref}/logs_tree${path ? `/${path}` : ''}`, { + params: { format: 'json', offset }, + }) + .then(({ data, headers }) => { + const headerLogsOffset = headers['more-logs-offset']; + const { commits } = client.readQuery({ query: getCommits }); + const newCommitData = [...commits, ...normalizeData(data)]; + client.writeQuery({ + query: getCommits, + data: { commits: newCommitData }, + }); + + resolvers.forEach(r => resolveCommit(newCommitData, r)); + + fetchpromise = null; + + if (headerLogsOffset) { + fetchLogsTree(client, path, headerLogsOffset); + } else { + resolvers = []; + } + }); + + return fetchpromise; +} diff --git a/app/assets/javascripts/repository/queries/getCommit.query.graphql b/app/assets/javascripts/repository/queries/getCommit.query.graphql new file mode 100644 index 00000000000..e2a2d831e47 --- /dev/null +++ b/app/assets/javascripts/repository/queries/getCommit.query.graphql @@ -0,0 +1,10 @@ +query getCommit($fileName: String!, $type: String!, $path: String!) { + commit(path: $path, fileName: $fileName, type: $type) @client { + sha + message + committedDate + commitPath + fileName + type + } +} diff --git a/app/assets/javascripts/repository/queries/getCommits.query.graphql b/app/assets/javascripts/repository/queries/getCommits.query.graphql new file mode 100644 index 00000000000..df9e67cc440 --- /dev/null +++ b/app/assets/javascripts/repository/queries/getCommits.query.graphql @@ -0,0 +1,10 @@ +query getCommits { + commits @client { + sha + message + committedDate + commitPath + fileName + type + } +} diff --git a/app/assets/javascripts/repository/queries/getFiles.query.graphql b/app/assets/javascripts/repository/queries/getFiles.query.graphql index ef924fde556..4c24fc4087f 100644 --- a/app/assets/javascripts/repository/queries/getFiles.query.graphql +++ b/app/assets/javascripts/repository/queries/getFiles.query.graphql @@ -1,5 +1,6 @@ fragment TreeEntry on Entry { id + name flatPath type } diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index b3447812ef2..b4ca9074ca9 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -55,6 +55,7 @@ class Projects::RefsController < Projects::ApplicationController format.html { render_404 } format.json do response.headers["More-Logs-Url"] = @more_log_url if summary.more? + response.headers["More-Logs-Offset"] = summary.next_offset if summary.more? render json: @logs end diff --git a/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap b/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap index 1f06d693411..d55dc553031 100644 --- a/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap +++ b/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap @@ -29,10 +29,20 @@ exports[`Repository table row component renders table row 1`] = ` <td class="d-none d-sm-table-cell tree-commit" - /> + > + <glskeletonloading-stub + class="h-auto" + lines="1" + /> + </td> <td class="tree-time-ago text-right" - /> + > + <glskeletonloading-stub + class="ml-auto h-auto w-50" + lines="1" + /> + </td> </tr> `; diff --git a/spec/frontend/repository/components/table/row_spec.js b/spec/frontend/repository/components/table/row_spec.js index 5a345ddeacd..c566057ad3f 100644 --- a/spec/frontend/repository/components/table/row_spec.js +++ b/spec/frontend/repository/components/table/row_spec.js @@ -16,6 +16,8 @@ function factory(propsData = {}) { vm = shallowMount(TableRow, { propsData: { ...propsData, + name: propsData.path, + projectPath: 'gitlab-org/gitlab-ce', url: `https://test.com`, }, mocks: { diff --git a/spec/frontend/repository/log_tree_spec.js b/spec/frontend/repository/log_tree_spec.js new file mode 100644 index 00000000000..a9499f7c61b --- /dev/null +++ b/spec/frontend/repository/log_tree_spec.js @@ -0,0 +1,129 @@ +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import { normalizeData, resolveCommit, fetchLogsTree } from '~/repository/log_tree'; + +const mockData = [ + { + commit: { + id: '123', + message: 'testing message', + committed_date: '2019-01-01', + }, + commit_path: `https://test.com`, + file_name: 'index.js', + type: 'blob', + }, +]; + +describe('normalizeData', () => { + it('normalizes data into LogTreeCommit object', () => { + expect(normalizeData(mockData)).toEqual([ + { + sha: '123', + message: 'testing message', + committedDate: '2019-01-01', + commitPath: 'https://test.com', + fileName: 'index.js', + type: 'blob', + __typename: 'LogTreeCommit', + }, + ]); + }); +}); + +describe('resolveCommit', () => { + it('calls resolve when commit found', () => { + const resolver = { + entry: { name: 'index.js', type: 'blob' }, + resolve: jest.fn(), + }; + const commits = [{ fileName: 'index.js', type: 'blob' }]; + + resolveCommit(commits, resolver); + + expect(resolver.resolve).toHaveBeenCalledWith({ fileName: 'index.js', type: 'blob' }); + }); +}); + +describe('fetchLogsTree', () => { + let mock; + let client; + let resolver; + + beforeEach(() => { + mock = new MockAdapter(axios); + + mock.onGet(/(.*)/).reply(200, mockData, {}); + + jest.spyOn(axios, 'get'); + + global.gon = { gitlab_url: 'https://test.com' }; + + client = { + readQuery: () => ({ + projectPath: 'gitlab-org/gitlab-ce', + ref: 'master', + commits: [], + }), + writeQuery: jest.fn(), + }; + + resolver = { + entry: { name: 'index.js', type: 'blob' }, + resolve: jest.fn(), + }; + }); + + afterEach(() => { + mock.restore(); + }); + + it('calls axios get', () => + fetchLogsTree(client, '', '0', resolver).then(() => { + expect(axios.get).toHaveBeenCalledWith( + 'https://test.com/gitlab-org/gitlab-ce/refs/master/logs_tree', + { params: { format: 'json', offset: '0' } }, + ); + })); + + it('calls axios get once', () => + Promise.all([ + fetchLogsTree(client, '', '0', resolver), + fetchLogsTree(client, '', '0', resolver), + ]).then(() => { + expect(axios.get.mock.calls.length).toEqual(1); + })); + + it('calls entry resolver', () => + fetchLogsTree(client, '', '0', resolver).then(() => { + expect(resolver.resolve).toHaveBeenCalledWith({ + __typename: 'LogTreeCommit', + commitPath: 'https://test.com', + committedDate: '2019-01-01', + fileName: 'index.js', + message: 'testing message', + sha: '123', + type: 'blob', + }); + })); + + it('writes query to client', () => + fetchLogsTree(client, '', '0', resolver).then(() => { + expect(client.writeQuery).toHaveBeenCalledWith({ + query: expect.anything(), + data: { + commits: [ + { + __typename: 'LogTreeCommit', + commitPath: 'https://test.com', + committedDate: '2019-01-01', + fileName: 'index.js', + message: 'testing message', + sha: '123', + type: 'blob', + }, + ], + }, + }); + })); +}); |