Skip to content
Snippets Groups Projects
Commit fcf612c4 authored by Eric Eastwood's avatar Eric Eastwood
Browse files
parent b2ef4fa3
No related branches found
No related tags found
No related merge requests found
Pipeline #
Showing
with 518 additions and 35 deletions
<script>
export default {
name: 'AddIssuableForm',
props: {
addButtonLabel: {
type: String,
required: true,
},
},
methods: {
},
};
</script>
<template>
<div>
<div class="add-issuable-form-input-wrapper form-control">
<ul class="add-issuable-form-input-token-list">
<li>some token</li>
</ul>
<input
type="text"
class="add-issuable-form-input"
value="asdf"
placeholder="Search issues..."
@input="onInput" />
</div>
<div class="clearfix prepend-top-10">
<button class="btn btn-new pull-left">
{{ addButtonLabel }}
</button>
<button class="btn btn-default pull-right">
Cancel
</button>
</div>
</div>
</template>
<script>
export default {
name: 'IssueToken',
props: {
reference: {
type: String,
required: true,
},
title: {
type: String,
required: true,
},
path: {
type: String,
required: true,
},
state: {
type: String,
required: false,
default: null,
},
},
computed: {
stateIconClass() {
let iconClass = null;
if (this.state === 'opened') {
iconClass = 'issue-token-state-icon-open fa fa-circle-o';
} else if (this.state === 'closed') {
iconClass = 'issue-token-state-icon-closed fa fa-minus';
}
return iconClass;
},
accessibleLabel() {
return `${this.state} ${this.reference} ${this.title}`;
},
removeButtonLabel() {
return `Remove related issue ${this.reference}`;
},
},
methods: {
},
};
</script>
<template>
<div class="issue-token">
<a
class="issue-token-reference"
:href="path">
<i
v-if="stateIconClass"
:class="stateIconClass"
:aria-label="accessibleLabel" />
{{ reference }}
</a>
<div class="issue-token-title">
<a
class="issue-token-title-link"
:href="path"
aria-hidden="true"
tabindex="-1">
{{ title }}
</a>
<button
class="issue-token-remove-button has-tooltip"
:title="removeButtonLabel">
<i class="fa fa-times" aria-hidden="true" />
</button>
</div>
</div>
</template>
<script>
import eventHub from '../event_hub';
import IssueToken from './issue_token.vue';
import AddIssuableForm from './add_issuable_form.vue';
export default {
name: 'RelatedIssuesBlock',
props: {
relatedIssues: {
type: Array,
required: false,
default: [],
},
fetchError: {
type: Error,
required: false,
default: null,
},
isAddRelatedIssuesFormVisible: {
type: Boolean,
required: false,
default: false,
},
},
components: {
addIssuableForm: AddIssuableForm,
issueToken: IssueToken,
},
computed: {
relatedIssueCount() {
return this.relatedIssues.length;
},
canAddRelatedIssues() {
// TODO:
return true;
},
},
methods: {
showAddRelatedIssuesForm() {
eventHub.$emit('showAddRelatedIssuesForm');
},
},
};
</script>
<template>
<div
class="panel-slim panel-default">
<div class="panel-heading">
<h3 class="panel-title">
Related issues
<div class="issue-count-holder">
<span class="issue-count-holder-count has-btn">
{{ relatedIssueCount }}
</span>
<button
v-if="canAddRelatedIssues"
class="issue-count-holder-add-button btn btn-small btn-default has-tooltip"
type="button"
aria-label="Add an issue"
title="Add an issue"
data-placement="top"
@click="showAddRelatedIssuesForm">
<i class="fa fa-plus" aria-hidden="true" />
</button>
</div>
</h3>
</div>
<div
v-if="isAddRelatedIssuesFormVisible"
class="related-issues-add-related-issues-form panel-body">
<addIssuableForm
add-button-label="Add related issues" />
</div>
<div class="panel-body">
<template v-if="fetchError">
<i class="fa fa-exclamation-circle" aria-hidden="true" />
An error occurred while fetching the related issues
</template>
<ul
v-else-if="relatedIssues"
class="related-issues-token-body">
<li
v-for="issue in relatedIssues"
class="related-issues-token-list-item">
<issueToken
:reference="issue.reference"
:title="issue.title"
:path="issue.path"
:state="issue.state" />
</li>
</ul>
<template v-else>
<i class="fa fa-spinner fa-spin" aria-hidden="true" />
Fetching related issues
</template>
</div>
</div>
</div>
</template>
import Vue from 'vue';
export default new Vue();
import Vue from 'vue';
import eventHub from './event_hub';
import RelatedIssuesBlock from './components/related_issues_block.vue';
import RelatedIssuesStore from './stores/related_issues_store';
import RelatedIssuesService from './services/related_issues_service';
class RelatedIssuesRoot {
constructor(wrapperElement) {
this.wrapperElement = wrapperElement;
const endpoint = this.wrapperElement.dataset.endpoint;
this.store = new RelatedIssuesStore({
});
this.service = new RelatedIssuesService(endpoint);
}
init() {
this.bindEvents();
this.fetchRelatedIssues();
this.render();
}
bindEvents() {
this.onShowAddRelatedIssuesFormWrapper = this.onShowAddRelatedIssuesForm.bind(this);
eventHub.$on('showAddRelatedIssuesForm', this.onShowAddRelatedIssuesFormWrapper);
}
unbindEvents() {
eventHub.$off('showAddRelatedIssuesForm', this.onShowAddRelatedIssuesFormWrapper);
}
render() {
this.vm = new Vue({
el: this.wrapperElement,
data: this.store.state,
components: {
relatedIssuesBlock: RelatedIssuesBlock,
},
template: `
<relatedIssuesBlock
:related-issues="relatedIssues"
:fetch-error="fetchError"
:is-add-related-issues-form-visible="isAddRelatedIssuesFormVisible" />
`,
});
}
onShowAddRelatedIssuesForm() {
this.store.setIsAddRelatedIssuesFormVisible(true);
}
fetchRelatedIssues() {
this.service.fetchRelatedIssues()
.then((issues) => {
this.store.setRelatedIssues(issues);
})
.catch((err) => {
this.store.setFetchError(err);
});
}
destroy() {
this.unbindEvents();
if (this.vm) {
this.vm.$destroy();
}
}
}
export default RelatedIssuesRoot;
import Vue from 'vue';
import vueResource from 'vue-resource';
import '../../../vue_shared/vue_resource_interceptor';
Vue.use(vueResource);
class RelatedIssuesService {
constructor(endpoint) {
this.relatedIssuesResource = Vue.resource(`${endpoint}`);
}
fetchRelatedIssues() {
return this.relatedIssuesResource.get()
.then((res) => {
const issues = res.data;
if (!issues) {
throw new Error('Response didn\'t include `service_desk_address`');
}
return issues;
});
}
}
export default RelatedIssuesService;
class RelatedIssuesStore {
constructor(initialState = {}) {
this.state = Object.assign({
relatedIssues: [],
fetchError: null,
isAddRelatedIssuesFormVisible: false,
}, initialState);
}
setRelatedIssues(value) {
this.state.relatedIssues = value;
}
setFetchError(value) {
this.state.fetchError = value;
}
setIsAddRelatedIssuesFormVisible(value) {
this.state.isAddRelatedIssuesFormVisible = value;
}
}
export default RelatedIssuesStore;
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, max-len */
/* global Flash */
 
import RelatedIssuesRoot from './issuable/related_issues/related_issues_root';
require('./flash');
require('~/lib/utils/text_utility');
require('vendor/jquery.waitforimages');
Loading
Loading
@@ -23,6 +25,7 @@ class Issue {
 
Issue.$btnNewBranch = $('#new-branch');
 
Issue.initRelatedIssues();
Issue.initMergeRequests();
Issue.initRelatedBranches();
Issue.initCanCreateBranch();
Loading
Loading
@@ -86,6 +89,12 @@ class Issue {
}
}
 
static initRelatedIssues() {
console.log('initRelatedIssues');
new RelatedIssuesRoot(document.querySelector('.js-related-issues-root'))
.init();
}
static initMergeRequests() {
var $container;
$container = $('#merge-requests');
Loading
Loading
Loading
Loading
@@ -317,30 +317,6 @@
margin: 5px;
}
 
.board-issue-count-holder {
margin-top: -3px;
.btn {
line-height: 12px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
.board-issue-count {
padding-right: 10px;
padding-left: 10px;
line-height: 21px;
border-radius: $border-radius-base;
border: 1px solid $border-color;
&.has-btn {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-width: 1px 0 1px 1px;
}
}
.boards-title-holder {
padding: 25px 13px $gl-padding;
 
Loading
Loading
Loading
Loading
@@ -171,3 +171,155 @@ ul.related-merge-requests > li {
.recaptcha {
margin-bottom: 30px;
}
.issue-count-holder {
display: inline-flex;
align-items: stretch;
height: 24px;
}
.issue-count-holder-count {
display: flex;
align-items: center;
padding-right: 10px;
padding-left: 10px;
border: 1px solid $border-color;
border-radius: $border-radius-base;
line-height: 1;
&.has-btn {
border-right: 0;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
}
.issue-count-holder-add-button {
display: flex;
align-items: center;
border-top: 1px solid $border-color;
border-left: 1px solid $border-color;
border-bottom: 1px solid $border-color;
border-right: 1px solid $border-color;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-top-right-radius: $border-radius-base;
border-bottom-right-radius: $border-radius-base;
line-height: 1;
}
.issue-token {
display: inline-flex;
line-height: 1.5;
}
.issue-token-reference {
display: flex;
align-items: baseline;
margin-right: 1px;
padding: 0 .5em;
background-color: $gray-lighter;
transition: background 0.2s ease, color 0.2s ease;
&:hover,
&:focus,
.issue-token:hover > &,
.issue-token:focus > & {
background-color: $gray-normal;
color: $gl-link-hover-color;
text-decoration: none;
}
}
@mixin issue-token-state-icon {
margin-right: 0.35em;
font-size: 0.9em;
}
.issue-token-state-icon-open {
@include issue-token-state-icon;
color: $green-600;
}
.issue-token-state-icon-closed {
@include issue-token-state-icon;
color: $red-600;
}
.issue-token-title {
display: flex;
align-items: baseline;
padding: 0 .5em;
background-color: $gray-normal;
color: $gl-text-color-secondary;
transition: background 0.2s ease, color 0.2s ease;
&:hover,
&:focus,
.issue-token:hover > &,
.issue-token:focus > & {
background-color: darken($gray-normal, 2%);
color: $gl-text-color-secondary;
}
}
.issue-token-title-link {
color: inherit;
&:hover,
&:focus {
text-decoration: none;
color: inherit;
}
}
.issue-token-remove-button {
background-color: transparent;
border: 0;
color: inherit;
font-size: 0.9em;
}
.add-issuable-form-input-wrapper {
display: flex;
align-items: stretch;
}
.add-issuable-form-input-token-list {
display: flex;
list-style: none;
margin-bottom: 0;
padding-left: 0;
}
.add-issuable-form-input {
flex: 1;
border: 0;
&:focus {
outline: none;
}
}
.related-issues-add-related-issues-form {
border-bottom: 1px solid $border-color;
}
.related-issues-token-body {
display: flex;
flex-wrap: wrap;
margin-bottom: 0;
padding-left: 0;
list-style: none;
}
.related-issues-token-list-item {
margin-bottom: 0.5em;
margin-right: 1em;
}
Loading
Loading
@@ -6,11 +6,11 @@
%span.has-tooltip{ ":title" => '(list.label ? list.label.description : "")',
data: { container: "body", placement: "bottom" } }
{{ list.title }}
.board-issue-count-holder.pull-right.clearfix{ "v-if" => 'list.type !== "blank"' }
%span.board-issue-count.pull-left{ ":class" => '{ "has-btn": list.type !== "closed" && !disabled }' }
.issue-count-holder.pull-right.clearfix{ "v-if" => 'list.type !== "blank"' }
%span.issue-count-holder-count{ ":class" => '{ "has-btn": list.type !== "closed" && !disabled }' }
{{ list.issuesSize }}
- if can?(current_user, :admin_issue, @project)
%button.btn.btn-small.btn-default.pull-right.has-tooltip{ type: "button",
%button.issue-count-holder-add-button.btn.btn-small.btn-default.has-tooltip{ type: "button",
"@click" => "showNewIssueForm",
"v-if" => 'list.type !== "closed"',
"aria-label" => "Add an issue",
Loading
Loading
Loading
Loading
@@ -64,6 +64,8 @@
= @issue.description
= edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago')
 
.js-related-issues-root{ data: { endpoint: namespace_project_issue_related_issues_path(@project.namespace, @project, @issue) } }
#merge-requests{ data: { url: referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue) } }
// This element is filled in using JavaScript.
 
Loading
Loading
Loading
Loading
@@ -22,18 +22,18 @@
end
 
it 'displays new issue button' do
expect(page).to have_selector('.board-issue-count-holder .btn', count: 1)
expect(page).to have_selector('.issue-count-holder-add-button', count: 1)
end
 
it 'does not display new issue button in closed list' do
page.within('.board:nth-child(2)') do
expect(page).not_to have_selector('.board-issue-count-holder .btn')
expect(page).not_to have_selector('.issue-count-holder-add-button')
end
end
 
it 'shows form when clicking button' do
page.within(first('.board')) do
find('.board-issue-count-holder .btn').click
find('.issue-count-holder-add-button').click
 
expect(page).to have_selector('.board-new-issue-form')
end
Loading
Loading
@@ -41,7 +41,7 @@
 
it 'hides form when clicking cancel' do
page.within(first('.board')) do
find('.board-issue-count-holder .btn').click
find('.issue-count-holder-add-button').click
 
expect(page).to have_selector('.board-new-issue-form')
 
Loading
Loading
@@ -53,7 +53,7 @@
 
it 'creates new issue' do
page.within(first('.board')) do
find('.board-issue-count-holder .btn').click
find('.issue-count-holder-add-button').click
end
 
page.within(first('.board-new-issue-form')) do
Loading
Loading
@@ -63,14 +63,14 @@
 
wait_for_vue_resource
 
page.within(first('.board .board-issue-count')) do
page.within(first('.board .issue-count-holder-count')) do
expect(page).to have_content('1')
end
end
 
it 'shows sidebar when creating new issue' do
page.within(first('.board')) do
find('.board-issue-count-holder .btn').click
find('.issue-count-holder-add-button').click
end
 
page.within(first('.board-new-issue-form')) do
Loading
Loading
@@ -91,7 +91,7 @@
end
 
it 'does not display new issue button' do
expect(page).to have_selector('.board-issue-count-holder .btn', count: 0)
expect(page).to have_selector('.issue-count-holder-add-button', count: 0)
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment