From 89278cffdf50d1ddeb146d7ad924f213f69fa102 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 25 Jun 2019 15:26:55 +0100 Subject: Fetch commit message with log_tree endpoint --- .../repository/components/breadcrumbs.vue | 2 +- .../repository/components/table/index.vue | 2 + .../repository/components/table/row.vue | 49 +++++++- app/assets/javascripts/repository/graphql.js | 17 ++- app/assets/javascripts/repository/index.js | 1 + app/assets/javascripts/repository/log_tree.js | 64 ++++++++++ .../repository/queries/getCommit.query.graphql | 10 ++ .../repository/queries/getCommits.query.graphql | 10 ++ .../repository/queries/getFiles.query.graphql | 1 + app/controllers/projects/refs_controller.rb | 1 + .../table/__snapshots__/row_spec.js.snap | 14 ++- .../repository/components/table/row_spec.js | 2 + spec/frontend/repository/log_tree_spec.js | 129 +++++++++++++++++++++ 13 files changed, 293 insertions(+), 9 deletions(-) create mode 100644 app/assets/javascripts/repository/log_tree.js create mode 100644 app/assets/javascripts/repository/queries/getCommit.query.graphql create mode 100644 app/assets/javascripts/repository/queries/getCommits.query.graphql create mode 100644 spec/frontend/repository/log_tree_spec.js 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 @@ 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`] = ` + > + + + > + + `; 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', + }, + ], + }, + }); + })); +}); -- cgit v1.2.1