Commit 90959f60 authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'vue-tree-logs-tree-request' into 'master'

Fetch commit message with logs_tree endpoint

See merge request gitlab-org/gitlab-ce!29796
parents 6b95c902 89278cff
......@@ -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}/` }],
);
},
},
......
......@@ -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"
......
<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>
......@@ -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,
......
......@@ -16,6 +16,7 @@ export default function setupVueRepositoryList() {
projectPath,
projectShortPath,
ref,
commits: [],
},
});
......
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;
}
query getCommit($fileName: String!, $type: String!, $path: String!) {
commit(path: $path, fileName: $fileName, type: $type) @client {
sha
message
committedDate
commitPath
fileName
type
}
}
query getCommits {
commits @client {
sha
message
committedDate
commitPath
fileName
type
}
}
fragment TreeEntry on Entry {
id
name
flatPath
type
}
......
......@@ -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
......
......@@ -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>
`;
......@@ -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: {
......
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',
},
],
},
});
}));
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment