Commit 53da0237 authored by Clement Ho's avatar Clement Ho

Merge branch 'ee-9688-fe-mr-merge-order' into 'master'

Backport of EE Displaying Blocking MRs

See merge request gitlab-org/gitlab-ce!29095
parents 5e05ec3b c763d98e
...@@ -52,11 +52,21 @@ export default { ...@@ -52,11 +52,21 @@ export default {
required: false, required: false,
default: '', default: '',
}, },
showReportSectionStatus: { showReportSectionStatusIcon: {
type: Boolean, type: Boolean,
required: false, required: false,
default: true, default: true,
}, },
issuesUlElementClass: {
type: String,
required: false,
default: '',
},
issueItemClass: {
type: String,
required: false,
default: null,
},
}, },
computed: { computed: {
issuesWithState() { issuesWithState() {
...@@ -67,6 +77,9 @@ export default { ...@@ -67,6 +77,9 @@ export default {
...this.resolvedIssues.map(wrapIssueWithState(STATUS_SUCCESS)), ...this.resolvedIssues.map(wrapIssueWithState(STATUS_SUCCESS)),
]; ];
}, },
wclass() {
return `report-block-list ${this.issuesUlElementClass}`;
},
}, },
}; };
</script> </script>
...@@ -77,7 +90,7 @@ export default { ...@@ -77,7 +90,7 @@ export default {
:size="$options.typicalReportItemHeight" :size="$options.typicalReportItemHeight"
class="report-block-container" class="report-block-container"
wtag="ul" wtag="ul"
wclass="report-block-list" :wclass="wclass"
> >
<report-item <report-item
v-for="(wrapped, index) in issuesWithState" v-for="(wrapped, index) in issuesWithState"
...@@ -86,7 +99,8 @@ export default { ...@@ -86,7 +99,8 @@ export default {
:status="wrapped.status" :status="wrapped.status"
:component="component" :component="component"
:is-new="wrapped.isNew" :is-new="wrapped.isNew"
:show-report-section-status="showReportSectionStatus" :show-report-section-status-icon="showReportSectionStatusIcon"
:class="issueItemClass"
/> />
</smart-virtual-list> </smart-virtual-list>
</template> </template>
...@@ -3,10 +3,7 @@ import { __ } from '~/locale'; ...@@ -3,10 +3,7 @@ import { __ } from '~/locale';
import StatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue'; import StatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
import Popover from '~/vue_shared/components/help_popover.vue'; import Popover from '~/vue_shared/components/help_popover.vue';
import IssuesList from './issues_list.vue'; import IssuesList from './issues_list.vue';
import { status } from '../constants';
const LOADING = 'LOADING';
const ERROR = 'ERROR';
const SUCCESS = 'SUCCESS';
export default { export default {
name: 'ReportSection', name: 'ReportSection',
...@@ -42,7 +39,8 @@ export default { ...@@ -42,7 +39,8 @@ export default {
}, },
successText: { successText: {
type: String, type: String,
required: true, required: false,
default: '',
}, },
unresolvedIssues: { unresolvedIssues: {
type: Array, type: Array,
...@@ -78,6 +76,21 @@ export default { ...@@ -78,6 +76,21 @@ export default {
required: false, required: false,
default: true, default: true,
}, },
issuesUlElementClass: {
type: String,
required: false,
default: undefined,
},
issuesListContainerClass: {
type: String,
required: false,
default: undefined,
},
issueItemClass: {
type: String,
required: false,
default: undefined,
},
}, },
data() { data() {
...@@ -91,13 +104,13 @@ export default { ...@@ -91,13 +104,13 @@ export default {
return this.isCollapsed ? __('Expand') : __('Collapse'); return this.isCollapsed ? __('Expand') : __('Collapse');
}, },
isLoading() { isLoading() {
return this.status === LOADING; return this.status === status.LOADING;
}, },
loadingFailed() { loadingFailed() {
return this.status === ERROR; return this.status === status.ERROR;
}, },
isSuccess() { isSuccess() {
return this.status === SUCCESS; return this.status === status.SUCCESS;
}, },
isCollapsible() { isCollapsible() {
return !this.alwaysOpen && this.hasIssues; return !this.alwaysOpen && this.hasIssues;
...@@ -132,6 +145,15 @@ export default { ...@@ -132,6 +145,15 @@ export default {
hasPopover() { hasPopover() {
return Object.keys(this.popoverOptions).length > 0; return Object.keys(this.popoverOptions).length > 0;
}, },
slotName() {
if (this.isSuccess) {
return 'success';
} else if (this.isLoading) {
return 'loading';
}
return 'error';
},
}, },
methods: { methods: {
toggleCollapsed() { toggleCollapsed() {
...@@ -147,6 +169,7 @@ export default { ...@@ -147,6 +169,7 @@ export default {
<div class="media-body d-flex flex-align-self-center"> <div class="media-body d-flex flex-align-self-center">
<span class="js-code-text code-text"> <span class="js-code-text code-text">
{{ headerText }} {{ headerText }}
<slot :name="slotName"></slot>
<popover v-if="hasPopover" :options="popoverOptions" class="prepend-left-5" /> <popover v-if="hasPopover" :options="popoverOptions" class="prepend-left-5" />
</span> </span>
...@@ -172,6 +195,9 @@ export default { ...@@ -172,6 +195,9 @@ export default {
:neutral-issues="neutralIssues" :neutral-issues="neutralIssues"
:component="component" :component="component"
:show-report-section-status-icon="showReportSectionStatusIcon" :show-report-section-status-icon="showReportSectionStatusIcon"
:issues-ul-element-class="issuesUlElementClass"
:class="issuesListContainerClass"
:issue-item-class="issueItemClass"
/> />
</slot> </slot>
</div> </div>
......
...@@ -16,3 +16,9 @@ export const STATUS_NEUTRAL = 'neutral'; ...@@ -16,3 +16,9 @@ export const STATUS_NEUTRAL = 'neutral';
export const ICON_WARNING = 'warning'; export const ICON_WARNING = 'warning';
export const ICON_SUCCESS = 'success'; export const ICON_SUCCESS = 'success';
export const ICON_NOTFOUND = 'notfound'; export const ICON_NOTFOUND = 'notfound';
export const status = {
LOADING: 'LOADING',
ERROR: 'ERROR',
SUCCESS: 'SUCCESS',
};
...@@ -24,6 +24,11 @@ export default { ...@@ -24,6 +24,11 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
greyLinkWhenMerged: {
type: Boolean,
required: false,
default: false,
},
}, },
computed: { computed: {
stateTitle() { stateTitle() {
...@@ -36,6 +41,11 @@ export default { ...@@ -36,6 +41,11 @@ export default {
}, },
); );
}, },
issueableLinkClass() {
return this.greyLinkWhenMerged
? `sortable-link ${this.state === 'merged' ? ' text-secondary' : ''}`
: 'sortable-link';
},
}, },
}; };
</script> </script>
...@@ -69,7 +79,7 @@ export default { ...@@ -69,7 +79,7 @@ export default {
class="confidential-icon append-right-4 align-self-baseline align-self-md-auto mt-xl-0" class="confidential-icon append-right-4 align-self-baseline align-self-md-auto mt-xl-0"
:aria-label="__('Confidential')" :aria-label="__('Confidential')"
/> />
<a :href="computedPath" class="sortable-link">{{ title }}</a> <a :href="computedPath" :class="issueableLinkClass">{{ title }}</a>
</div> </div>
<div class="item-meta d-flex flex-wrap mt-xl-0 justify-content-xl-end flex-xl-nowrap"> <div class="item-meta d-flex flex-wrap mt-xl-0 justify-content-xl-end flex-xl-nowrap">
<div <div
......
...@@ -7,11 +7,15 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont ...@@ -7,11 +7,15 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
private private
# rubocop: disable CodeReuse/ActiveRecord
def merge_request def merge_request
@issuable = @merge_request ||= @project.merge_requests.includes(author: :status).find_by!(iid: params[:id]) @issuable =
@merge_request ||=
merge_request_includes(@project.merge_requests).find_by_iid!(params[:id])
end
def merge_request_includes(association)
association.includes(:metrics, :assignees, author: :status) # rubocop:disable CodeReuse/ActiveRecord
end end
# rubocop: enable CodeReuse/ActiveRecord
def merge_request_params def merge_request_params
params.require(:merge_request).permit(merge_request_params_attributes) params.require(:merge_request).permit(merge_request_params_attributes)
......
...@@ -197,4 +197,44 @@ describe('Report section', () => { ...@@ -197,4 +197,44 @@ describe('Report section', () => {
expect(vm.$el.querySelector('.js-collapse-btn').textContent.trim()).toEqual('Expand'); expect(vm.$el.querySelector('.js-collapse-btn').textContent.trim()).toEqual('Expand');
}); });
}); });
describe('Success and Error slots', () => {
const createComponent = status => {
vm = mountComponentWithSlots(ReportSection, {
props: {
status,
hasIssues: true,
},
slots: {
success: ['This is a success'],
loading: ['This is loading'],
error: ['This is an error'],
},
});
};
it('only renders success slot when status is "SUCCESS"', () => {
createComponent('SUCCESS');
expect(vm.$el.textContent.trim()).toContain('This is a success');
expect(vm.$el.textContent.trim()).not.toContain('This is an error');
expect(vm.$el.textContent.trim()).not.toContain('This is loading');
});
it('only renders error slot when status is "ERROR"', () => {
createComponent('ERROR');
expect(vm.$el.textContent.trim()).toContain('This is an error');
expect(vm.$el.textContent.trim()).not.toContain('This is a success');
expect(vm.$el.textContent.trim()).not.toContain('This is loading');
});
it('only renders loading slot when status is "LOADING"', () => {
createComponent('LOADING');
expect(vm.$el.textContent.trim()).toContain('This is loading');
expect(vm.$el.textContent.trim()).not.toContain('This is an error');
expect(vm.$el.textContent.trim()).not.toContain('This is a success');
});
});
}); });
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