summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFilipa Lacerda <filipa@gitlab.com>2019-06-24 14:40:18 +0000
committerFilipa Lacerda <filipa@gitlab.com>2019-06-24 14:40:18 +0000
commitf53b05cfde5aeac73a7c81a2fe951b5642338ae0 (patch)
tree4b6d41b4b4ef6acd7db04988615fdeffa6345e4b
parent108c3cf16bed5733ffae086fb62c226961356560 (diff)
parent039117deacf730286d6e1b0b6bc116d62c3b9c31 (diff)
downloadgitlab-ce-f53b05cfde5aeac73a7c81a2fe951b5642338ae0.tar.gz
Merge branch 'divergence-graph-fe-rendering' into 'master'
Render branch divergence graph with Vue See merge request gitlab-org/gitlab-ce!29743
-rw-r--r--app/assets/javascripts/branches/components/divergence_graph.vue72
-rw-r--r--app/assets/javascripts/branches/components/graph_bar.vue69
-rw-r--r--app/assets/javascripts/branches/constants.js6
-rw-r--r--app/assets/javascripts/branches/divergence_graph.js23
-rw-r--r--app/assets/javascripts/pages/projects/branches/index/index.js2
-rw-r--r--app/assets/stylesheets/pages/branches.scss36
-rw-r--r--app/views/projects/branches/_branch.html.haml19
-rw-r--r--spec/frontend/branches/components/__snapshots__/divergence_graph_spec.js.snap37
-rw-r--r--spec/frontend/branches/components/divergence_graph_spec.js67
-rw-r--r--spec/frontend/branches/components/graph_bar_spec.js89
10 files changed, 366 insertions, 54 deletions
diff --git a/app/assets/javascripts/branches/components/divergence_graph.vue b/app/assets/javascripts/branches/components/divergence_graph.vue
new file mode 100644
index 00000000000..36fff370ea1
--- /dev/null
+++ b/app/assets/javascripts/branches/components/divergence_graph.vue
@@ -0,0 +1,72 @@
+<script>
+import { sprintf, __ } from '~/locale';
+import GraphBar from './graph_bar.vue';
+import { MAX_COMMIT_COUNT } from '../constants';
+
+export default {
+ components: {
+ GraphBar,
+ },
+ props: {
+ defaultBranch: {
+ type: String,
+ required: true,
+ },
+ distance: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+ aheadCount: {
+ type: Number,
+ required: true,
+ },
+ behindCount: {
+ type: Number,
+ required: true,
+ },
+ maxCommits: {
+ type: Number,
+ required: true,
+ },
+ },
+ computed: {
+ title() {
+ if (this.distance) {
+ return sprintf(
+ __('More than %{number_commits_distance} commits different with %{default_branch}'),
+ {
+ number_commits_distance:
+ this.distance >= MAX_COMMIT_COUNT ? `${MAX_COMMIT_COUNT - 1}+` : this.distance,
+ default_branch: this.defaultBranch,
+ },
+ );
+ }
+
+ return sprintf(
+ __(
+ '%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead',
+ ),
+ {
+ number_commits_behind: this.behindCount,
+ number_commits_ahead: this.aheadCount,
+ default_branch: this.defaultBranch,
+ },
+ );
+ },
+ },
+};
+</script>
+
+<template>
+ <div :title="title" class="divergence-graph px-2 d-none d-md-block">
+ <template v-if="distance">
+ <graph-bar :count="distance" :max-commits="maxCommits" position="full" />
+ </template>
+ <template v-else>
+ <graph-bar :count="behindCount" :max-commits="maxCommits" position="left" />
+ <div class="graph-separator pull-left mt-1"></div>
+ <graph-bar :count="aheadCount" :max-commits="maxCommits" position="right" />
+ </template>
+ </div>
+</template>
diff --git a/app/assets/javascripts/branches/components/graph_bar.vue b/app/assets/javascripts/branches/components/graph_bar.vue
new file mode 100644
index 00000000000..83da41ca097
--- /dev/null
+++ b/app/assets/javascripts/branches/components/graph_bar.vue
@@ -0,0 +1,69 @@
+<script>
+import { SIDES, MAX_COMMIT_COUNT } from '../constants';
+
+export default {
+ props: {
+ position: {
+ type: String,
+ required: true,
+ },
+ count: {
+ type: Number,
+ required: true,
+ },
+ maxCommits: {
+ type: Number,
+ required: true,
+ },
+ },
+ computed: {
+ label() {
+ if (this.count >= MAX_COMMIT_COUNT) {
+ return `${MAX_COMMIT_COUNT - 1}+`;
+ }
+
+ return this.count;
+ },
+ barGraphWidthFactor() {
+ return this.maxCommits > 0 ? 100 / this.maxCommits : 0;
+ },
+ style() {
+ return {
+ width: `${this.count * this.barGraphWidthFactor}%`,
+ };
+ },
+ isFullWidth() {
+ return this.position === SIDES.full;
+ },
+ isLeftSide() {
+ return this.position === SIDES.left;
+ },
+ roundedClass() {
+ if (this.isFullWidth) return 'rounded';
+
+ return `rounded-${this.position}`;
+ },
+ textAlignmentClass() {
+ if (this.isFullWidth) return 'text-center';
+
+ return `text-${this.isLeftSide ? SIDES.right : SIDES.left}`;
+ },
+ positionSideClass() {
+ return `position-${this.isLeftSide ? SIDES.right : SIDES.left}-0`;
+ },
+ },
+};
+</script>
+
+<template>
+ <div :class="{ full: isFullWidth }" class="position-relative pull-left pt-1 graph-side h-100">
+ <div
+ :style="style"
+ :class="[roundedClass, positionSideClass]"
+ class="position-absolute bar js-graph-bar"
+ ></div>
+ <span :class="textAlignmentClass" class="d-block pt-1 pr-1 count js-graph-count">
+ {{ label }}
+ </span>
+ </div>
+</template>
diff --git a/app/assets/javascripts/branches/constants.js b/app/assets/javascripts/branches/constants.js
new file mode 100644
index 00000000000..16949e662d2
--- /dev/null
+++ b/app/assets/javascripts/branches/constants.js
@@ -0,0 +1,6 @@
+export const SIDES = {
+ full: 'full',
+ left: 'left',
+ right: 'right',
+};
+export const MAX_COMMIT_COUNT = 1000;
diff --git a/app/assets/javascripts/branches/divergence_graph.js b/app/assets/javascripts/branches/divergence_graph.js
new file mode 100644
index 00000000000..670e8e9eb60
--- /dev/null
+++ b/app/assets/javascripts/branches/divergence_graph.js
@@ -0,0 +1,23 @@
+import Vue from 'vue';
+import DivergenceGraph from './components/divergence_graph.vue';
+
+export default () => {
+ document.querySelectorAll('.js-branch-divergence-graph').forEach(el => {
+ const { distance, aheadCount, behindCount, defaultBranch, maxCommits } = el.dataset;
+
+ return new Vue({
+ el,
+ render(h) {
+ return h(DivergenceGraph, {
+ props: {
+ defaultBranch,
+ distance: distance ? parseInt(distance, 10) : null,
+ aheadCount: parseInt(aheadCount, 10),
+ behindCount: parseInt(behindCount, 10),
+ maxCommits: parseInt(maxCommits, 10),
+ },
+ });
+ },
+ });
+ });
+};
diff --git a/app/assets/javascripts/pages/projects/branches/index/index.js b/app/assets/javascripts/pages/projects/branches/index/index.js
index 8fa266a37ce..29de3b7806c 100644
--- a/app/assets/javascripts/pages/projects/branches/index/index.js
+++ b/app/assets/javascripts/pages/projects/branches/index/index.js
@@ -1,7 +1,9 @@
import AjaxLoadingSpinner from '~/ajax_loading_spinner';
import DeleteModal from '~/branches/branches_delete_modal';
+import initDiverganceGraph from '~/branches/divergence_graph';
document.addEventListener('DOMContentLoaded', () => {
AjaxLoadingSpinner.init();
new DeleteModal(); // eslint-disable-line no-new
+ initDiverganceGraph();
});
diff --git a/app/assets/stylesheets/pages/branches.scss b/app/assets/stylesheets/pages/branches.scss
index ce0622b3d48..e1715b8e1bf 100644
--- a/app/assets/stylesheets/pages/branches.scss
+++ b/app/assets/stylesheets/pages/branches.scss
@@ -14,62 +14,26 @@
$graph-side-width: 80px;
$graph-separator-width: 1px;
- padding: 0 6px;
-
.graph-side {
- position: relative;
width: $graph-side-width;
- height: 22px;
- padding: 5px 0 13px;
- float: left;
&.full {
width: $graph-side-width * 2 + $graph-separator-width;
- display: flex;
- justify-content: center;
}
.bar {
- position: absolute;
height: 4px;
background-color: $gl-gray-200;
}
- .bar-behind {
- right: 0;
- border-radius: 3px 0 0 3px;
- }
-
- .bar-ahead {
- left: 0;
- border-radius: 0 3px 3px 0;
- }
-
.count {
- padding-top: 6px;
- padding-bottom: 0;
font-size: 12px;
- color: $gl-text-color;
- display: block;
- }
-
- .count-behind {
- padding-right: 4px;
- text-align: right;
- }
-
- .count-ahead {
- padding-left: 4px;
- text-align: left;
}
}
.graph-separator {
- position: relative;
width: $graph-separator-width;
height: 18px;
- margin: 5px 0 0;
- float: left;
background-color: $gl-gray-200;
}
}
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 33f449287b6..3638334d61c 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -1,6 +1,5 @@
- merged = local_assigns.fetch(:merged, false)
- commit = @repository.commit(branch.dereferenced_target)
-- bar_graph_width_factor = @max_commits > 0 ? 100.0/@max_commits : 0
- diverging_commit_counts = @repository.diverging_commit_counts(branch)
- number_commits_distance = diverging_commit_counts[:distance]
- number_commits_behind = diverging_commit_counts[:behind]
@@ -31,23 +30,7 @@
= s_('Branches|Cant find HEAD commit for this branch')
- if branch.name != @repository.root_ref
- - if number_commits_distance.nil?
- .divergence-graph.d-none.d-md-block{ title: s_('%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead') % { number_commits_behind: diverging_count_label(number_commits_behind),
- default_branch: @repository.root_ref,
- number_commits_ahead: diverging_count_label(number_commits_ahead) } }
- .graph-side
- .bar.bar-behind{ style: "width: #{number_commits_behind * bar_graph_width_factor}%" }
- %span.count.count-behind= diverging_count_label(number_commits_behind)
- .graph-separator
- .graph-side
- .bar.bar-ahead{ style: "width: #{number_commits_ahead * bar_graph_width_factor}%" }
- %span.count.count-ahead= diverging_count_label(number_commits_ahead)
- - else
- .divergence-graph.d-none.d-md-block{ title: s_('More than %{number_commits_distance} commits different with %{default_branch}') % { number_commits_distance: diverging_count_label(number_commits_distance),
- default_branch: @repository.root_ref} }
- .graph-side.full
- .bar{ style: "width: #{number_commits_distance * bar_graph_width_factor}%" }
- %span.count= diverging_count_label(number_commits_distance)
+ .js-branch-divergence-graph{ data: { distance: number_commits_distance, ahead_count: number_commits_ahead, behind_count: number_commits_behind, default_branch: @repository.root_ref, max_commits: @max_commits } }
.controls.d-none.d-md-block<
- if merge_project && create_mr_button?(@repository.root_ref, branch.name)
diff --git a/spec/frontend/branches/components/__snapshots__/divergence_graph_spec.js.snap b/spec/frontend/branches/components/__snapshots__/divergence_graph_spec.js.snap
new file mode 100644
index 00000000000..511c027dbc2
--- /dev/null
+++ b/spec/frontend/branches/components/__snapshots__/divergence_graph_spec.js.snap
@@ -0,0 +1,37 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Branch divergence graph component renders ahead and behind count 1`] = `
+<div
+ class="divergence-graph px-2 d-none d-md-block"
+ title="10 commits behind master, 10 commits ahead"
+>
+ <graphbar-stub
+ count="10"
+ maxcommits="100"
+ position="left"
+ />
+
+ <div
+ class="graph-separator pull-left mt-1"
+ />
+
+ <graphbar-stub
+ count="10"
+ maxcommits="100"
+ position="right"
+ />
+</div>
+`;
+
+exports[`Branch divergence graph component renders distance count 1`] = `
+<div
+ class="divergence-graph px-2 d-none d-md-block"
+ title="More than 900 commits different with master"
+>
+ <graphbar-stub
+ count="900"
+ maxcommits="100"
+ position="full"
+ />
+</div>
+`;
diff --git a/spec/frontend/branches/components/divergence_graph_spec.js b/spec/frontend/branches/components/divergence_graph_spec.js
new file mode 100644
index 00000000000..b54b2ceb233
--- /dev/null
+++ b/spec/frontend/branches/components/divergence_graph_spec.js
@@ -0,0 +1,67 @@
+import { shallowMount } from '@vue/test-utils';
+import DivergenceGraph from '~/branches/components/divergence_graph.vue';
+import GraphBar from '~/branches/components/graph_bar.vue';
+
+let vm;
+
+function factory(propsData = {}) {
+ vm = shallowMount(DivergenceGraph, { propsData });
+}
+
+describe('Branch divergence graph component', () => {
+ afterEach(() => {
+ vm.destroy();
+ });
+
+ it('renders ahead and behind count', () => {
+ factory({
+ defaultBranch: 'master',
+ aheadCount: 10,
+ behindCount: 10,
+ maxCommits: 100,
+ });
+
+ expect(vm.findAll(GraphBar).length).toBe(2);
+ expect(vm.element).toMatchSnapshot();
+ });
+
+ it('sets title for ahead and behind count', () => {
+ factory({
+ defaultBranch: 'master',
+ aheadCount: 10,
+ behindCount: 10,
+ maxCommits: 100,
+ });
+
+ expect(vm.attributes('title')).toBe('10 commits behind master, 10 commits ahead');
+ });
+
+ it('renders distance count', () => {
+ factory({
+ defaultBranch: 'master',
+ aheadCount: 0,
+ behindCount: 0,
+ distance: 900,
+ maxCommits: 100,
+ });
+
+ expect(vm.findAll(GraphBar).length).toBe(1);
+ expect(vm.element).toMatchSnapshot();
+ });
+
+ it.each`
+ distance | titleText
+ ${900} | ${'900'}
+ ${1100} | ${'999+'}
+ `('sets title for $distance as $titleText', ({ distance, titleText }) => {
+ factory({
+ defaultBranch: 'master',
+ aheadCount: 0,
+ behindCount: 0,
+ distance,
+ maxCommits: 100,
+ });
+
+ expect(vm.attributes('title')).toBe(`More than ${titleText} commits different with master`);
+ });
+});
diff --git a/spec/frontend/branches/components/graph_bar_spec.js b/spec/frontend/branches/components/graph_bar_spec.js
new file mode 100644
index 00000000000..61c051b49c6
--- /dev/null
+++ b/spec/frontend/branches/components/graph_bar_spec.js
@@ -0,0 +1,89 @@
+import { shallowMount } from '@vue/test-utils';
+import GraphBar from '~/branches/components/graph_bar.vue';
+
+let vm;
+
+function factory(propsData = {}) {
+ vm = shallowMount(GraphBar, { propsData });
+}
+
+describe('Branch divergence graph bar component', () => {
+ afterEach(() => {
+ vm.destroy();
+ });
+
+ it.each`
+ position | positionClass
+ ${'left'} | ${'position-right-0'}
+ ${'right'} | ${'position-left-0'}
+ ${'full'} | ${'position-left-0'}
+ `(
+ 'sets position class as $positionClass for position $position',
+ ({ position, positionClass }) => {
+ factory({
+ position,
+ count: 10,
+ maxCommits: 100,
+ });
+
+ expect(vm.find('.js-graph-bar').classes()).toContain(positionClass);
+ },
+ );
+
+ it.each`
+ position | textAlignmentClass
+ ${'left'} | ${'text-right'}
+ ${'right'} | ${'text-left'}
+ ${'full'} | ${'text-center'}
+ `(
+ 'sets text alignment class as $textAlignmentClass for position $position',
+ ({ position, textAlignmentClass }) => {
+ factory({
+ position,
+ count: 10,
+ maxCommits: 100,
+ });
+
+ expect(vm.find('.js-graph-count').classes()).toContain(textAlignmentClass);
+ },
+ );
+
+ it.each`
+ position | roundedClass
+ ${'left'} | ${'rounded-left'}
+ ${'right'} | ${'rounded-right'}
+ ${'full'} | ${'rounded'}
+ `('sets rounded class as $roundedClass for position $position', ({ position, roundedClass }) => {
+ factory({
+ position,
+ count: 10,
+ maxCommits: 100,
+ });
+
+ expect(vm.find('.js-graph-bar').classes()).toContain(roundedClass);
+ });
+
+ it.each`
+ count | label
+ ${100} | ${'100'}
+ ${1000} | ${'999+'}
+ `('renders label as $roundedClass for $count', ({ count, label }) => {
+ factory({
+ position: 'left',
+ count,
+ maxCommits: 1000,
+ });
+
+ expect(vm.find('.js-graph-count').text()).toContain(label);
+ });
+
+ it('sets width of bar', () => {
+ factory({
+ position: 'left',
+ count: 100,
+ maxCommits: 1000,
+ });
+
+ expect(vm.find('.js-graph-bar').attributes('style')).toEqual('width: 10%;');
+ });
+});