Commit b440e0fa authored by Phil Hughes's avatar Phil Hughes

remove duplicated code

added in permission checks
parent 8eab7b08
......@@ -14,7 +14,7 @@ document.addEventListener('DOMContentLoaded', () => {
render: createElement => createElement('related-issues-root', {
props: {
endpoint: relatedIssuesRootElement.dataset.endpoint,
canAddRelatedIssues: convertPermissionToBoolean(
canAdmin: convertPermissionToBoolean(
relatedIssuesRootElement.dataset.canAddRelatedIssues,
),
helpPath: relatedIssuesRootElement.dataset.helpPath,
......
......@@ -127,7 +127,9 @@ export default {
event-namespace="pendingIssuable"
:id-key="index"
:display-reference="reference"
:can-remove="true" />
:can-remove="true"
:is-condensed="true"
/>
</li>
<li class="add-issuable-form-input-list-item">
<input
......
......@@ -4,7 +4,11 @@ import tooltip from '../../../vue_shared/directives/tooltip';
export default {
name: 'IssueToken',
data() {
return {
removeDisabled: false,
};
},
props: {
idKey: {
type: Number,
......@@ -39,6 +43,11 @@ export default {
required: false,
default: false,
},
isCondensed: {
type: Boolean,
required: false,
default: false,
},
},
directives: {
......@@ -47,11 +56,16 @@ export default {
computed: {
removeButtonLabel() {
return `Remove related issue ${this.displayReference}`;
return `Remove ${this.displayReference}`;
},
hasState() {
return this.state && this.state.length > 0;
},
stateTitle() {
if (this.isCondensed) return '';
return this.isOpen ? 'Open' : 'Closed';
},
isOpen() {
return this.state === 'opened';
},
......@@ -67,6 +81,12 @@ export default {
computedPath() {
return this.path.length ? this.path : null;
},
innerComponentType() {
return this.isCondensed ? 'span' : 'div';
},
issueTitle() {
return this.isCondensed ? this.title : '';
},
},
methods: {
......@@ -77,53 +97,80 @@ export default {
}
eventHub.$emit(`${namespacePrefix}removeRequest`, this.idKey);
this.removeDisabled = true;
},
},
};
</script>
<template>
<div class="issue-token">
<div :class="{
'issue-token': isCondensed,
'flex-row issue-info-container': !isCondensed,
}">
<component
v-tooltip
:is="this.computedLinkElementType"
ref="link"
class="issue-token-link"
:class="{
'issue-token-link': isCondensed,
'issue-main-info': !isCondensed,
}"
:href="computedPath"
:title="title"
data-placement="top">
<span
:title="issueTitle"
data-placement="top"
>
<component
:is="innerComponentType"
v-if="hasTitle"
ref="title"
:class="{
'js-issue-token-title issue-token-title issue-token-end': isCondensed,
'issue-title block-truncated': !isCondensed,
'issue-token-title-standalone': !canRemove
}">
<span class="issue-token-title-text">
{{ title }}
</span>
</component>
<component
:is="innerComponentType"
ref="reference"
class="issue-token-reference">
:class="{
'issue-token-reference': isCondensed,
'issuable-info': !isCondensed,
}">
<i
ref="stateIcon"
v-if="hasState"
v-tooltip
class="fa"
:class="{
'issue-token-state-icon-open fa-circle-o': isOpen,
'issue-token-state-icon-closed fa-minus': isClosed,
}"
:aria-label="state">
</i>
{{ displayReference }}
</span>
<span
v-if="hasTitle"
ref="title"
class="js-issue-token-title issue-token-title"
:class="{ 'issue-token-title-standalone': !canRemove }">
<span class="issue-token-title-text">
{{ title }}
</span>
</span>
:title="stateTitle"
:aria-label="state"
>
</i>{{ displayReference }}
</component>
</component>
<button
v-if="canRemove"
v-tooltip
ref="removeButton"
type="button"
class="js-issue-token-remove-button issue-token-remove-button"
class="js-issue-token-remove-button"
:class="{
'issue-token-remove-button': isCondensed,
'btn btn-default': !isCondensed
}"
:title="removeButtonLabel"
:aria-label="removeButtonLabel"
@click="onRemoveRequest">
:disabled="removeDisabled"
@click="onRemoveRequest"
>
<i
class="fa fa-times"
aria-hidden="true">
......
<script>
import eventHub from '../event_hub';
import tooltip from '../../../vue_shared/directives/tooltip';
export default {
props: {
idKey: {
type: Number,
required: true,
},
displayReference: {
type: String,
required: true,
},
eventNamespace: {
type: String,
required: false,
default: '',
},
title: {
type: String,
required: false,
default: '',
},
path: {
type: String,
required: false,
default: '',
},
state: {
type: String,
required: false,
default: '',
},
canRemove: {
type: Boolean,
required: false,
default: false,
},
},
directives: {
tooltip,
},
computed: {
removeButtonLabel() {
return `Remove related issue ${this.displayReference}`;
},
hasState() {
return this.state && this.state.length > 0;
},
isOpen() {
return this.state === 'opened';
},
isClosed() {
return this.state === 'closed';
},
hasTitle() {
return this.title.length > 0;
},
computedLinkElementType() {
return this.path.length > 0 ? 'a' : 'span';
},
computedPath() {
return this.path.length ? this.path : null;
},
},
methods: {
onRemoveRequest() {
let namespacePrefix = '';
if (this.eventNamespace && this.eventNamespace.length > 0) {
namespacePrefix = `${this.eventNamespace}-`;
}
eventHub.$emit(`${namespacePrefix}removeRequest`, this.idKey);
},
},
};
</script>
<template>
<div class="flex-row issue-info-container">
<component
:is="this.computedLinkElementType"
ref="link"
:href="computedPath"
:title="title"
data-placement="top"
class="issue-main-info"
>
<div
v-if="hasTitle"
ref="title"
class="issue-title block-truncated"
:class="{ 'issue-token-title-standalone': !canRemove }"
>
{{ title }}
</div>
<div
ref="reference"
class="issuable-info">
<i
ref="stateIcon"
v-if="hasState"
class="fa"
:class="{
'issue-token-state-icon-open fa-circle-o': isOpen,
'issue-token-state-icon-closed fa-minus': isClosed,
}"
:aria-label="state">
</i>
{{ displayReference }}
</div>
</component>
<button
v-if="canRemove"
ref="removeButton"
type="button"
class="js-issue-token-remove-button btn btn-default"
style="margin-left: auto"
:aria-label="removeButtonLabel"
@click="onRemoveRequest">
<i
class="fa fa-times"
aria-hidden="true">
</i>
</button>
</div>
</template>
......@@ -2,7 +2,7 @@
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import tooltip from '~/vue_shared/directives/tooltip';
import eventHub from '../event_hub';
import relatedIssue from './related_issue.vue';
import issueToken from './issue_token.vue';
import addIssuableForm from './add_issuable_form.vue';
export default {
......@@ -24,7 +24,7 @@ export default {
required: false,
default: () => [],
},
canAddRelatedIssues: {
canAdmin: {
type: Boolean,
required: false,
default: false,
......@@ -67,7 +67,7 @@ export default {
components: {
loadingIcon,
addIssuableForm,
relatedIssue,
issueToken,
},
computed: {
......@@ -116,11 +116,11 @@ export default {
<div class="js-related-issues-header-issue-count related-issues-header-issue-count issue-count-badge">
<span
class="issue-count-badge-count"
:class="{ 'has-btn': this.canAddRelatedIssues }">
:class="{ 'has-btn': this.canAdmin }">
{{ badgeLabel }}
</span>
<button
v-if="canAddRelatedIssues"
v-if="canAdmin"
ref="issueCountBadgeAddButton"
type="button"
class="js-issue-count-badge-add-button issue-count-badge-add-button btn btn-sm btn-default"
......@@ -165,14 +165,15 @@ export default {
:key="issue.id"
v-for="issue in relatedIssues"
class="js-related-issues-token-list-item">
<related-issue
<issue-token
event-namespace="relatedIssue"
:id-key="issue.id"
:display-reference="issue.reference"
:title="issue.title"
:path="issue.path"
:state="issue.state"
:can-remove="true" />
:can-remove="canAdmin"
/>
</li>
</ul>
</div>
......
......@@ -40,7 +40,7 @@ export default {
type: String,
required: true,
},
canAddRelatedIssues: {
canAdmin: {
type: Boolean,
required: false,
default: false,
......@@ -97,13 +97,11 @@ export default {
})
.catch((res) => {
if (res && res.status !== 404) {
// eslint-disable-next-line no-new
new Flash('An error occurred while removing related issues.');
Flash('An error occurred while removing issues.');
}
});
} else {
// eslint-disable-next-line no-new
new Flash('We could not determine the path to remove the related issue');
Flash('We could not determine the path to remove the issue');
}
},
onToggleAddRelatedIssuesForm() {
......@@ -130,8 +128,7 @@ export default {
})
.catch((res) => {
this.isSubmitting = false;
// eslint-disable-next-line no-new
new Flash(res.data.message || 'We can\'t find an issue that matches what you are looking for.');
Flash(res.data.message || 'We can\'t find an issue that matches what you are looking for.');
});
}
},
......@@ -151,7 +148,7 @@ export default {
.catch(() => {
this.store.setRelatedIssues([]);
this.isFetching = false;
Flash('An error occurred while fetching issues.')
Flash('An error occurred while fetching issues.');
});
},
......@@ -226,7 +223,7 @@ export default {
:is-fetching="isFetching"
:is-submitting="isSubmitting"
:related-issues="state.relatedIssues"
:can-add-related-issues="canAddRelatedIssues"
:can-admin="canAdmin"
:pending-references="state.pendingReferences"
:is-form-visible="isFormVisible"
:input-value="inputValue"
......
@import "./issues/issue_count_badge";
@import "./issues/related_issues";
.issues-list {
.issue {
......
......@@ -18,7 +18,7 @@ $token_spacing_bottom: 0.5em;
}
.related-issues-token-body {
padding: 0 0 0 $gl-padding;
padding: 0;
transition-property: max-height, padding, opacity;
transition-duration: $general-hover-transition-duration;
transition-timing-function: $general-hover-transition-curve;
......@@ -30,6 +30,10 @@ $token_spacing_bottom: 0.5em;
padding-bottom: 0;
opacity: 0;
}
li .issue-info-container {
padding-left: $gl-padding;
}
}
.related-issues-loading-icon {
......@@ -50,3 +54,7 @@ $token_spacing_bottom: 0.5em;
margin-bottom: $token_spacing_bottom;
margin-right: 5px;
}
.issue-token-end {
order: 1;
}
......@@ -215,6 +215,7 @@ module IssuablesHelper
endpoint: issuable_path(issuable),
canUpdate: can?(current_user, :"update_#{issuable.to_ability_name}", issuable),
canDestroy: can?(current_user, :"destroy_#{issuable.to_ability_name}", issuable),
canAdmin: can?(current_user, :"admin_#{issuable.to_ability_name}", issuable),
issuableRef: issuable.to_reference,
markdownPreviewPath: preview_markdown_path(parent),
markdownDocsPath: help_page_path('user/markdown'),
......
<script>
import issuableApp from '~/issue_show/components/app.vue';
import epicHeader from './epic_header.vue';
import relatedIssuesRoot from '~/issuable/related_issues/components/related_issues_root.vue';
import epicHeader from './epic_header.vue';
export default {
name: 'epicShowApp',
......@@ -19,6 +18,10 @@
required: true,
type: Boolean,
},
canAdmin: {
required: true,
type: Boolean,
},
markdownPreviewPath: {
type: String,
required: true,
......@@ -57,18 +60,16 @@
type: Object,
required: true,
},
issueLinksEndpoint: {
type: String,
required: true,
},
},
components: {
epicHeader,
issuableApp,
relatedIssuesRoot,
},
computed: {
// TODO: this should be a prop here, from a data-attribute on epic-show-app element
issuesEndpoint() {
return `${this.endpoint}/links`;
},
},
created() {
// Epics specific configuration
this.issuableRef = '';
......@@ -101,8 +102,8 @@
:show-inline-edit-button="true"
/>
<related-issues-root
:endpoint="issuesEndpoint"
:can-add-related-issues="true"
:endpoint="issueLinksEndpoint"
:can-admin="canAdmin"
:allow-auto-complete="false"
title="Issues"
/>
......
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