Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
beb4c23e
Commit
beb4c23e
authored
May 16, 2018
by
Lukas Eipert
Committed by
Filipa Lacerda
May 16, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Migrates EE-only js components to single file components
parent
26263a1c
Changes
17
Hide whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
548 additions
and
448 deletions
+548
-448
app/assets/javascripts/vue_merge_request_widget/dependencies.js
...sets/javascripts/vue_merge_request_widget/dependencies.js
+1
-1
ee/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_body.vue
...ge_request_widget/components/approvals/approvals_body.vue
+46
-33
ee/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_footer.vue
..._request_widget/components/approvals/approvals_footer.vue
+58
-47
ee/app/assets/javascripts/vue_merge_request_widget/components/approvals/mr_widget_approvals.vue
...quest_widget/components/approvals/mr_widget_approvals.vue
+100
-0
ee/app/assets/javascripts/vue_merge_request_widget/components/codequality_issue_body.vue
...erge_request_widget/components/codequality_issue_body.vue
+20
-20
ee/app/assets/javascripts/vue_merge_request_widget/components/performance_issue_body.vue
...erge_request_widget/components/performance_issue_body.vue
+23
-23
ee/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.vue
...est_widget/components/states/mr_widget_ready_to_merge.vue
+9
-7
ee/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_secondary_geo_node.js
..._widget/components/states/mr_widget_secondary_geo_node.js
+0
-33
ee/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_secondary_geo_node.vue
...widget/components/states/mr_widget_secondary_geo_node.vue
+39
-0
ee/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.vue
...idget/components/states/mr_widget_squash_before_merge.vue
+2
-1
ee/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
...avascripts/vue_merge_request_widget/mr_widget_options.vue
+2
-2
ee/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js
...ts/vue_merge_request_widget/services/mr_widget_service.js
+6
-9
ee/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js
...ascripts/vue_merge_request_widget/stores/get_state_key.js
+1
-2
ee/app/assets/javascripts/vue_shared/components/link_to_member_avatar.vue
...vascripts/vue_shared/components/link_to_member_avatar.vue
+32
-31
spec/javascripts/approvals/approvals_body_spec.js
spec/javascripts/approvals/approvals_body_spec.js
+88
-104
spec/javascripts/approvals/approvals_footer_spec.js
spec/javascripts/approvals/approvals_footer_spec.js
+51
-68
spec/javascripts/vue_shared/components/link_to_member_avatar_spec.js
...ripts/vue_shared/components/link_to_member_avatar_spec.js
+70
-67
No files found.
app/assets/javascripts/vue_merge_request_widget/dependencies.js
View file @
beb4c23e
...
...
@@ -26,7 +26,7 @@ export { default as ConflictsState } from './components/states/mr_widget_conflic
export
{
default
as
NothingToMergeState
}
from
'
./components/states/nothing_to_merge.vue
'
;
export
{
default
as
MissingBranchState
}
from
'
./components/states/mr_widget_missing_branch.vue
'
;
export
{
default
as
NotAllowedState
}
from
'
./components/states/mr_widget_not_allowed.vue
'
;
export
{
default
as
ReadyToMergeState
}
from
'
ee/vue_merge_request_widget/components/states/mr_widget_ready_to_merge
'
;
export
{
default
as
ReadyToMergeState
}
from
'
ee/vue_merge_request_widget/components/states/mr_widget_ready_to_merge
.vue
'
;
export
{
default
as
ShaMismatchState
}
from
'
./components/states/sha_mismatch.vue
'
;
export
{
default
as
UnresolvedDiscussionsState
}
from
'
./components/states/unresolved_discussions.vue
'
;
export
{
default
as
PipelineBlockedState
}
from
'
./components/states/mr_widget_pipeline_blocked.vue
'
;
...
...
ee/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_body.
js
→
ee/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_body.
vue
View file @
beb4c23e
<
script
>
import
{
n__
,
s__
,
sprintf
}
from
'
~/locale
'
;
import
Flash
from
'
~/flash
'
;
import
M
R
WidgetAuthor
from
'
~/vue_merge_request_widget/components/mr_widget_author.vue
'
;
import
M
r
WidgetAuthor
from
'
~/vue_merge_request_widget/components/mr_widget_author.vue
'
;
import
eventHub
from
'
~/vue_merge_request_widget/event_hub
'
;
export
default
{
name
:
'
approvals-body
'
,
name
:
'
ApprovalsBody
'
,
components
:
{
MrWidgetAuthor
,
},
props
:
{
mr
:
{
type
:
Object
,
...
...
@@ -17,27 +21,29 @@ export default {
approvedBy
:
{
type
:
Array
,
required
:
false
,
default
:
()
=>
[],
},
approvalsLeft
:
{
type
:
Number
,
required
:
false
,
default
:
0
,
},
userCanApprove
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
userHasApproved
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
suggestedApprovers
:
{
type
:
Array
,
required
:
false
,
default
:
()
=>
[],
},
},
components
:
{
'
mr-widget-author
'
:
MRWidgetAuthor
,
},
data
()
{
return
{
approving
:
false
,
...
...
@@ -102,32 +108,39 @@ export default {
});
},
},
template
:
`
<div class="approvals-body space-children">
<span v-if="showApproveButton" class="approvals-approve-button-wrap">
<button
:disabled="approving"
@click="approveMergeRequest"
class="btn btn-primary btn-sm approve-btn"
:class="approveButtonClass">
<i
v-if="approving"
class="fa fa-spinner fa-spin"
aria-hidden="true" />
{{approveButtonText}}
</button>
</span>
<span class="approvals-required-text bold">
{{approvalsRequiredStringified}}
<span v-if="showSuggestedApprovers">
<mr-widget-author
v-for="approver in suggestedApprovers"
:key="approver.username"
:author="approver"
:show-author-name="false"
:show-author-tooltip="true" />
</span>
</span>
</div>
`
,
};
</
script
>
<
template
>
<div
class=
"approvals-body space-children"
>
<span
v-if=
"showApproveButton"
class=
"approvals-approve-button-wrap"
>
<button
:disabled=
"approving"
@
click=
"approveMergeRequest"
class=
"btn btn-primary btn-sm approve-btn"
:class=
"approveButtonClass"
>
<i
v-if=
"approving"
class=
"fa fa-spinner fa-spin"
aria-hidden=
"true"
></i>
{{
approveButtonText
}}
</button>
</span>
<span
class=
"approvals-required-text bold"
>
{{
approvalsRequiredStringified
}}
<span
v-if=
"showSuggestedApprovers"
>
<mr-widget-author
v-for=
"approver in suggestedApprovers"
:key=
"approver.username"
:author=
"approver"
:show-author-name=
"false"
:show-author-tooltip=
"true"
/>
</span>
</span>
</div>
</
template
>
ee/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_footer.
js
→
ee/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_footer.
vue
View file @
beb4c23e
<
script
>
import
Flash
from
'
~/flash
'
;
import
LinkToMemberAvatar
from
'
ee/vue_shared/components/link_to_member_avatar
'
;
import
LinkToMemberAvatar
from
'
ee/vue_shared/components/link_to_member_avatar
.vue
'
;
import
{
s__
}
from
'
~/locale
'
;
import
eventHub
from
'
~/vue_merge_request_widget/event_hub
'
;
export
default
{
name
:
'
approvals-footer
'
,
name
:
'
ApprovalsFooter
'
,
components
:
{
LinkToMemberAvatar
,
},
props
:
{
mr
:
{
type
:
Object
,
...
...
@@ -17,22 +21,27 @@ export default {
approvedBy
:
{
type
:
Array
,
required
:
false
,
default
:
()
=>
[],
},
approvalsLeft
:
{
type
:
Number
,
required
:
false
,
default
:
0
,
},
userCanApprove
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
userHasApproved
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
suggestedApprovers
:
{
type
:
Array
,
required
:
false
,
default
:
()
=>
[],
},
},
data
()
{
...
...
@@ -40,9 +49,6 @@ export default {
unapproving
:
false
,
};
},
components
:
{
'
link-to-member-avatar
'
:
LinkToMemberAvatar
,
},
computed
:
{
showUnapproveButton
()
{
const
isMerged
=
this
.
mr
.
state
===
'
merged
'
;
...
...
@@ -58,8 +64,9 @@ export default {
methods
:
{
unapproveMergeRequest
()
{
this
.
unapproving
=
true
;
this
.
service
.
unapproveMergeRequest
()
.
then
((
data
)
=>
{
this
.
service
.
unapproveMergeRequest
()
.
then
(
data
=>
{
this
.
mr
.
setApprovals
(
data
);
eventHub
.
$emit
(
'
MRWidgetUpdateRequested
'
);
this
.
unapproving
=
false
;
...
...
@@ -70,45 +77,49 @@ export default {
});
},
},
template
:
`
<div
v-if="approvedBy.length"
class="approved-by-users approvals-footer clearfix mr-info-list">
<div class="approvers-prefix">
<p>{{approvedByText}}</p>
<div class="approvers-list">
<link-to-member-avatar
v-for="(approver, index) in approvedBy"
:key="index"
:avatar-size="20"
:avatar-url="approver.user.avatar_url"
extra-link-class="approver-avatar js-approver-list-member"
:display-name="approver.user.name"
:profile-url="approver.user.web_url"
:show-tooltip="true"
/>
<link-to-member-avatar
v-for="n in approvalsLeft"
:key="n"
:avatar-size="20"
:clickable="false"
:show-tooltip="false"
/>
</div>
<button
v-if="showUnapproveButton"
type="button"
:disabled="unapproving"
@click="unapproveMergeRequest"
class="btn btn-sm unapprove-btn-wrap">
<i
v-if="unapproving"
class="fa fa-spinner fa-spin"
aria-hidden="true">
</i>
{{removeApprovalText}}
</button>
};
</
script
>
<
template
>
<div
v-if=
"approvedBy.length"
class=
"approved-by-users approvals-footer clearfix mr-info-list"
>
<div
class=
"approvers-prefix"
>
<p>
{{
approvedByText
}}
</p>
<div
class=
"approvers-list"
>
<link-to-member-avatar
v-for=
"(approver, index) in approvedBy"
:key=
"index"
:avatar-size=
"20"
:avatar-url=
"approver.user.avatar_url"
extra-link-class=
"approver-avatar js-approver-list-member"
:display-name=
"approver.user.name"
:profile-url=
"approver.user.web_url"
:show-tooltip=
"true"
/>
<link-to-member-avatar
v-for=
"n in approvalsLeft"
:key=
"n"
:avatar-size=
"20"
:clickable=
"false"
:show-tooltip=
"false"
/>
</div>
<button
v-if=
"showUnapproveButton"
type=
"button"
:disabled=
"unapproving"
@
click=
"unapproveMergeRequest"
class=
"btn btn-sm unapprove-btn-wrap"
>
<i
v-if=
"unapproving"
class=
"fa fa-spinner fa-spin"
aria-hidden=
"true"
>
</i>
{{
removeApprovalText
}}
</button>
</div>
`
,
};
</div>
</
template
>
ee/app/assets/javascripts/vue_merge_request_widget/components/approvals/mr_widget_approvals.
js
→
ee/app/assets/javascripts/vue_merge_request_widget/components/approvals/mr_widget_approvals.
vue
View file @
beb4c23e
<
script
>
import
Flash
from
'
~/flash
'
;
import
statusIcon
from
'
~/vue_merge_request_widget/components/mr_widget_status_icon.vue
'
;
import
{
s__
}
from
'
~/locale
'
;
import
ApprovalsBody
from
'
./approvals_body
'
;
import
ApprovalsFooter
from
'
./approvals_footer
'
;
import
ApprovalsBody
from
'
./approvals_body
.vue
'
;
import
ApprovalsFooter
from
'
./approvals_footer
.vue
'
;
export
default
{
name
:
'
MRWidgetApprovals
'
,
components
:
{
ApprovalsBody
,
ApprovalsFooter
,
statusIcon
,
},
props
:
{
mr
:
{
type
:
Object
,
...
...
@@ -21,11 +27,7 @@ export default {
fetchingApprovals
:
true
,
};
},
components
:
{
'
approvals-body
'
:
ApprovalsBody
,
'
approvals-footer
'
:
ApprovalsFooter
,
statusIcon
,
},
computed
:
{
status
()
{
if
(
this
.
mr
.
approvals
.
approvals_left
>
0
)
{
...
...
@@ -35,51 +37,64 @@ export default {
},
},
created
()
{
const
flashErrorMessage
=
s__
(
'
mrWidget|An error occured while retrieving approval data for this merge request.
'
);
const
flashErrorMessage
=
s__
(
'
mrWidget|An error occured while retrieving approval data for this merge request.
'
,
);
this
.
service
.
fetchApprovals
()
.
then
((
data
)
=>
{
this
.
service
.
fetchApprovals
()
.
then
(
data
=>
{
this
.
mr
.
setApprovals
(
data
);
this
.
fetchingApprovals
=
false
;
})
.
catch
(()
=>
new
Flash
(
flashErrorMessage
));
},
template
:
`
};
</
script
>
<
template
>
<div
v-if=
"mr.approvalsRequired"
class=
"mr-widget-approvals-container mr-widget-body mr-widget-section media"
>
<div
v-if="mr.approvalsRequired"
class="mr-widget-approvals-container mr-widget-body mr-widget-section media">
<div
v-if="fetchingApprovals"
class="mr-widget-icon">
<i class="fa fa-spinner fa-spin" />
</div>
<status-icon v-else :status="status" />
<div
v-show="fetchingApprovals"
class="mr-approvals-loading-state media-body">
<span class="approvals-loading-text">
Checking approval status
</span>
</div>
<div
v-if="!fetchingApprovals"
class="approvals-components media-body">
<approvals-body
:mr="mr"
:service="service"
:user-can-approve="mr.approvals.user_can_approve"
:user-has-approved="mr.approvals.user_has_approved"
:approved-by="mr.approvals.approved_by"
:approvals-left="mr.approvals.approvals_left"
:suggested-approvers="mr.approvals.suggested_approvers" />
<approvals-footer
:mr="mr"
:service="service"
:user-can-approve="mr.approvals.user_can_approve"
:user-has-approved="mr.approvals.user_has_approved"
:approved-by="mr.approvals.approved_by"
:approvals-left="mr.approvals.approvals_left" />
</div>
v-if=
"fetchingApprovals"
class=
"mr-widget-icon"
>
<i
class=
"fa fa-spinner fa-spin"
></i>
</div>
`
,
};
<status-icon
v-else
:status=
"status"
/>
<div
v-show=
"fetchingApprovals"
class=
"mr-approvals-loading-state media-body"
>
<span
class=
"approvals-loading-text"
>
Checking approval status
</span>
</div>
<div
v-if=
"!fetchingApprovals"
class=
"approvals-components media-body"
>
<approvals-body
:mr=
"mr"
:service=
"service"
:user-can-approve=
"mr.approvals.user_can_approve"
:user-has-approved=
"mr.approvals.user_has_approved"
:approved-by=
"mr.approvals.approved_by"
:approvals-left=
"mr.approvals.approvals_left"
:suggested-approvers=
"mr.approvals.suggested_approvers"
/>
<approvals-footer
:mr=
"mr"
:service=
"service"
:user-can-approve=
"mr.approvals.user_can_approve"
:user-has-approved=
"mr.approvals.user_has_approved"
:approved-by=
"mr.approvals.approved_by"
:approvals-left=
"mr.approvals.approvals_left"
/>
</div>
</div>
</
template
>
ee/app/assets/javascripts/vue_merge_request_widget/components/codequality_issue_body.vue
View file @
beb4c23e
<
script
>
/**
* Renders Code quality body text
* Fixed: [name] in [link]:[line]
*/
import
ReportLink
from
'
ee/vue_shared/security_reports/components/report_link.vue
'
;
/**
* Renders Code quality body text
* Fixed: [name] in [link]:[line]
*/
import
ReportLink
from
'
ee/vue_shared/security_reports/components/report_link.vue
'
;
export
default
{
name
:
'
CodequalityIssueBody
'
,
export
default
{
name
:
'
CodequalityIssueBody
'
,
components
:
{
ReportLink
,
},
components
:
{
ReportLink
,
},
props
:
{
isStatusSuccess
:
{
type
:
Boolean
,
required
:
true
,
},
issue
:
{
type
:
Object
,
required
:
true
,
},
props
:
{
isStatusSuccess
:
{
type
:
Boolean
,
required
:
true
,
},
issue
:
{
type
:
Object
,
required
:
true
,
},
};
},
};
</
script
>
<
template
>
<div
class=
"report-block-list-issue-description prepend-top-5 append-bottom-5"
>
...
...
ee/app/assets/javascripts/vue_merge_request_widget/components/performance_issue_body.vue
View file @
beb4c23e
<
script
>
/**
* Renders Perfomance issue body text
* [name] :[score] [symbol] [delta] in [link]
*/
import
ReportLink
from
'
ee/vue_shared/security_reports/components/report_link.vue
'
;
/**
* Renders Perfomance issue body text
* [name] :[score] [symbol] [delta] in [link]
*/
import
ReportLink
from
'
ee/vue_shared/security_reports/components/report_link.vue
'
;
export
default
{
name
:
'
PerformanceIssueBody
'
,
export
default
{
name
:
'
PerformanceIssueBody
'
,
components
:
{
ReportLink
,
},
components
:
{
ReportLink
,
},
props
:
{
issue
:
{
type
:
Object
,
required
:
true
,
},
props
:
{
issue
:
{
type
:
Object
,
required
:
true
,
},
},
methods
:
{
formatScore
(
value
)
{
if
(
Math
.
floor
(
value
)
!==
value
)
{
return
parseFloat
(
value
).
toFixed
(
2
);
}
return
value
;
},
methods
:
{
formatScore
(
value
)
{
if
(
Math
.
floor
(
value
)
!==
value
)
{
return
parseFloat
(
value
).
toFixed
(
2
);
}
return
value
;
},
};
},
};
</
script
>
<
template
>
<div
class=
"report-block-list-issue-description prepend-top-5 append-bottom-5"
>
...
...
ee/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.
js
→
ee/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.
vue
View file @
beb4c23e
<
script
>
import
eventHub
from
'
~/vue_merge_request_widget/event_hub
'
;
import
ReadyToMergeState
from
'
~/vue_merge_request_widget/components/states/ready_to_merge.vue
'
;
import
SquashBeforeMerge
from
'
./mr_widget_squash_before_merge.vue
'
;
export
default
{
extends
:
ReadyToMergeState
,
name
:
'
ReadyToMerge
'
,
components
:
{
'
squash-before-merge
'
:
SquashBeforeMerge
,
SquashBeforeMerge
,
},
extends
:
ReadyToMergeState
,
data
()
{
return
{
additionalParams
:
{
...
...
@@ -15,6 +16,11 @@ export default {
},
};
},
created
()
{
eventHub
.
$on
(
'
MRWidgetUpdateSquash
'
,
val
=>
{
this
.
additionalParams
.
squash
=
val
;
});
},
methods
:
{
// called in CE super component before form submission
setAdditionalParams
(
options
)
{
...
...
@@ -23,9 +29,5 @@ export default {
}
},
},
created
()
{
eventHub
.
$on
(
'
MRWidgetUpdateSquash
'
,
(
val
)
=>
{
this
.
additionalParams
.
squash
=
val
;
});
},
};
</
script
>
ee/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_secondary_geo_node.js
deleted
100644 → 0
View file @
26263a1c
import
statusIcon
from
'
~/vue_merge_request_widget/components/mr_widget_status_icon.vue
'
;
export
default
{
props
:
{
mr
:
{
type
:
Object
,
required
:
true
,
},
},
components
:
{
statusIcon
,
},
template
:
`
<div class="media">
<status-icon status="warning" showDisabledButton />
<div class="media-body">
<span class="bold">
Merge requests are read-only in a secondary Geo node
</span>
<a
:href="mr.geoSecondaryHelpPath"
data-title="About this feature"
data-toggle="tooltip"
data-placement="bottom"
target="_blank"
rel="noopener noreferrer nofollow"
data-container="body">
<i class="fa fa-question-circle"></i>
</a>
</div>
</div>
`
,
};
ee/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_secondary_geo_node.vue
0 → 100644
View file @
beb4c23e
<
script
>
import
statusIcon
from
'
~/vue_merge_request_widget/components/mr_widget_status_icon.vue
'
;
export
default
{
components
:
{
statusIcon
,
},
props
:
{
mr
:
{
type
:
Object
,
required
:
true
,
},
},
};
</
script
>
<
template
>
<div
class=
"media"
>
<status-icon
status=
"warning"
show-disabled-button
/>
<div
class=
"media-body"
>
<span
class=
"bold"
>
Merge requests are read-only in a secondary Geo node
</span>
<a
:href=
"mr.geoSecondaryHelpPath"
data-title=
"About this feature"
data-toggle=
"tooltip"
data-placement=
"bottom"
target=
"_blank"
rel=
"noopener noreferrer nofollow"
data-container=
"body"
>
<i
class=
"fa fa-question-circle"
></i>
</a>
</div>
</div>
</
template
>
ee/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.vue
View file @
beb4c23e
...
...
@@ -51,7 +51,8 @@ export default {
>
<i
class=
"fa fa-question-circle"
aria-hidden=
"true"
>
aria-hidden=
"true"
>
</i>
</a>
</div>
...
...
ee/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
View file @
beb4c23e
<
script
>
import
{
n__
,
s__
,
__
,
sprintf
}
from
'
~/locale
'
;
import
CEWidgetOptions
from
'
~/vue_merge_request_widget/mr_widget_options.vue
'
;
import
WidgetApprovals
from
'
./components/approvals/mr_widget_approvals
'
;
import
GeoSecondaryNode
from
'
./components/states/mr_widget_secondary_geo_node
'
;
import
WidgetApprovals
from
'
./components/approvals/mr_widget_approvals
.vue
'
;
import
GeoSecondaryNode
from
'
./components/states/mr_widget_secondary_geo_node
.vue
'
;
import
ReportSection
from
'
../vue_shared/security_reports/components/report_section.vue
'
;
import
GroupedSecurityReportsApp
from
'
../vue_shared/security_reports/grouped_security_reports_app.vue
'
;
import
reportsMixin
from
'
../vue_shared/security_reports/mixins/reports_mixin
'
;
...
...
ee/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js
View file @
beb4c23e
...
...
@@ -10,22 +10,19 @@ export default class MRWidgetService extends CEWidgetService {
}
fetchApprovals
()
{
return
axios
.
get
(
this
.
approvalsPath
)
.
then
(
res
=>
res
.
data
);
return
axios
.
get
(
this
.
approvalsPath
).
then
(
res
=>
res
.
data
);
}
approveMergeRequest
()
{
return
axios
.
post
(
this
.
approvalsPath
)
.
then
(
res
=>
res
.
data
);
return
axios
.
post
(
this
.
approvalsPath
).
then
(
res
=>
res
.
data
);
}
unapproveMergeRequest
()
{
return
axios
.
delete
(
this
.
approvalsPath
)
.
then
(
res
=>
res
.
data
);
return
axios
.
delete
(
this
.
approvalsPath
).
then
(
res
=>
res
.
data
);
}
fetchReport
(
endpoint
)
{
// eslint-disable-line
return
axios
.
get
(
endpoint
)
.
then
(
res
=>
res
.
data
);
// eslint-disable-next-line class-methods-use-this
fetchReport
(
endpoint
)
{
return
axios
.
get
(
endpoint
)
.
then
(
res
=>
res
.
data
);
}
}
ee/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js
View file @
beb4c23e
import
CEGetStateKey
from
'
~/vue_merge_request_widget/stores/get_state_key
'
;
export
default
function
(
data
)
{
export
default
function
(
data
)
{
if
(
this
.
isGeoSecondaryNode
)
{
return
'
geoSecondaryNode
'
;
}
return
CEGetStateKey
.
call
(
this
,
data
);
}
ee/app/assets/javascripts/vue_shared/components/link_to_member_avatar.
js
→
ee/app/assets/javascripts/vue_shared/components/link_to_member_avatar.
vue
View file @
beb4c23e
<
script
>
// Analogue of link_to_member_avatar in app/helpers/projects_helper.rb
import
pendingAvatarSvg
from
'
ee_icons/_icon_dotted_circle.svg
'
;
...
...
@@ -6,6 +7,7 @@ export default {
avatarUrl
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
profileUrl
:
{
type
:
String
,
...
...
@@ -15,6 +17,7 @@ export default {
displayName
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
extraAvatarClass
:
{
type
:
String
,
...
...
@@ -39,10 +42,7 @@ export default {
tooltipContainer
:
{
type
:
String
,
required
:
false
,
},
avatarHtml
:
{
type
:
String
,
required
:
false
,
default
:
'
body
'
,
},
avatarSize
:
{
type
:
Number
,
...
...
@@ -75,32 +75,33 @@ export default {
linkClass
()
{
return
`author_link
${
this
.
tooltipClass
}
${
this
.
extraLinkClass
}
${
this
.
disabledClass
}
`
;
},
tooltipContainerAttr
()
{
return
this
.
tooltipContainer
||
'
body
'
;
},
},
template
:
`
<div class="link-to-member-avatar">
<a
:href="profileUrl"
:class="linkClass"
:title="displayName"
:data-container="tooltipContainerAttr">
<img
v-if="avatarUrl"
:class="avatarClass"
:src="avatarUrl"
:width="avatarSize"
:height="avatarSize"
:alt="displayName"/>
<span
v-else
v-html="pendingAvatarSvg"
:class="avatarHtmlClass"
:width="avatarSize"
:height="avatarSize">
</span>
</a>
</div>
`
,
};
</
script
>
<
template
>
<div
class=
"link-to-member-avatar"
>
<a
:href=
"profileUrl"
:class=
"linkClass"
:title=
"displayName"
:data-container=
"tooltipContainer"
>
<img
v-if=
"avatarUrl"
:class=
"avatarClass"
:src=
"avatarUrl"
:width=
"avatarSize"
:height=
"avatarSize"
:alt=
"displayName"
/>
<span
v-else
v-html=
"pendingAvatarSvg"
:class=
"avatarHtmlClass"
:width=
"avatarSize"
:height=
"avatarSize"
>
</span>
</a>
</div>
</
template
>
spec/javascripts/approvals/approvals_body_spec.js
View file @
beb4c23e
import
Vue
from
'
vue
'
;
import
_
from
'
underscore
'
;
import
ApprovalsBody
from
'
ee/vue_merge_request_widget/components/approvals/approvals_body
'
;
(()
=>
{
gl
.
ApprovalsStore
=
{
data
:
{},
initStoreOnce
()
{
return
{
then
()
{},
};
import
ApprovalsBody
from
'
ee/vue_merge_request_widget/components/approvals/approvals_body.vue
'
;
describe
(
'
Approvals Body Component
'
,
()
=>
{
let
vm
;
const
initialData
=
{
mr
:
{
isOpen
:
true
,
},
service
:
{},
suggestedApprovers
:
[{
name
:
'
Approver 1
'
}],
userCanApprove
:
false
,
userHasApproved
:
true
,
approvedBy
:
[],
approvalsLeft
:
1
,
pendingAvatarSvg
:
'
<svg></svg>
'
,
checkmarkSvg
:
'
<svg></svg>
'
,
};
function
initApprovalsBodyComponent
()
{
setFixtures
(
`
<div>
<div id="mock-container"></div>
</div>
`
);
this
.
initialData
=
{
mr
:
{
isOpen
:
true
,
},
service
:
{},
suggestedApprovers
:
[{
name
:
'
Approver 1
'
}],
userCanApprove
:
false
,
userHasApproved
:
true
,
approvedBy
:
[],
approvalsLeft
:
1
,
pendingAvatarSvg
:
'
<svg></svg>
'
,
checkmarkSvg
:
'
<svg></svg>
'
,
};
beforeEach
(()
=>
{
setFixtures
(
'
<div id="mock-container"></div>
'
);
const
ApprovalsBodyComponent
=
Vue
.
extend
(
ApprovalsBody
);
this
.
approvalsBody
=
new
ApprovalsBodyComponent
({
vm
=
new
ApprovalsBodyComponent
({
el
:
'
#mock-container
'
,
propsData
:
this
.
initialData
,
propsData
:
initialData
,
});
}
}
);
describe
(
'
Approvals Body Component
'
,
function
()
{
beforeEach
(
function
()
{
initApprovalsBodyComponent
.
call
(
this
);
});
afterEach
(()
=>
{
vm
.
$destroy
();
});
it
(
'
should correctly set component props
'
,
function
()
{
const
approvalsBody
=
this
.
approvalsBody
;
_
.
each
(
approvalsBody
,
(
propValue
,
propKey
)
=>
{
if
(
this
.
initialData
[
propKey
])
{
expect
(
approvalsBody
[
propKey
]).
toBe
(
this
.
initialData
[
propKey
]);
}
});
it
(
'
should correctly set component props
'
,
()
=>
{
Object
.
keys
(
vm
).
forEach
(
propKey
=>
{
if
(
initialData
[
propKey
])
{
expect
(
vm
[
propKey
]).
toBe
(
initialData
[
propKey
]);
}
});
});
describe
(
'
Computed properties
'
,
function
()
{
describe
(
'
approvalsRequiredStringified
'
,
function
()
{
it
(
'
should display the correct string for 1 possible approver
'
,
function
()
{
const
correctText
=
'
Requires 1 more approval by
'
;
expect
(
this
.
approvalsBody
.
approvalsRequiredStringified
).
toBe
(
correctText
);
});
describe
(
'
Computed properties
'
,
()
=>
{
describe
(
'
approvalsRequiredStringified
'
,
()
=>
{
it
(
'
should display the correct string for 1 possible approver
'
,
()
=>
{
const
correctText
=
'
Requires 1 more approval by
'
;
expect
(
vm
.
approvalsRequiredStringified
).
toBe
(
correctText
);
});
it
(
'
should display the correct string for 2 possible approvers
'
,
function
(
done
)
{
this
.
approvalsBody
.
approvalsLeft
=
2
;
this
.
approvalsBody
.
suggestedApprovers
.
push
({
name
:
'
Approver 2
'
});
it
(
'
should display the correct string for 2 possible approvers
'
,
done
=>
{
vm
.
approvalsLeft
=
2
;
vm
.
suggestedApprovers
.
push
({
name
:
'
Approver 2
'
});
Vue
.
nextTick
(()
=>
{
const
correctText
=
'
Requires 2 more approvals by
'
;
expect
(
this
.
approvalsBody
.
approvalsRequiredStringified
).
toBe
(
correctText
);
done
();
});
Vue
.
nextTick
(()
=>
{
const
correctText
=
'
Requires 2 more approvals by
'
;
expect
(
vm
.
approvalsRequiredStringified
).
toBe
(
correctText
);
done
();
});
});
it
(
'
shows the "Approved" text message when there is enough approvals in place
'
,
function
(
done
)
{
this
.
approvalsBody
.
approvalsLeft
=
0
;
it
(
'
shows the "Approved" text message when there is enough approvals in place
'
,
done
=>
{
vm
.
approvalsLeft
=
0
;
Vue
.
nextTick
(()
=>
{
expect
(
this
.
approvalsBody
.
approvalsRequiredStringified
).
toBe
(
'
Approved
'
);
done
();
});
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
approvalsRequiredStringified
).
toBe
(
'
Approved
'
);
done
();
});
});
it
(
'
shows the "Requires 1 more approval" without by when no suggested approvals are available
'
,
function
(
done
)
{
const
correctText
=
'
Requires 1 more approval
'
;
this
.
approvalsBody
.
suggestedApprovers
=
[];
it
(
'
shows the "Requires 1 more approval" without by when no suggested approvals are available
'
,
done
=>
{
const
correctText
=
'
Requires 1 more approval
'
;
vm
.
suggestedApprovers
=
[];
Vue
.
nextTick
(()
=>
{
expect
(
this
.
approvalsBody
.
approvalsRequiredStringified
).
toBe
(
correctText
);
done
();
});
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
approvalsRequiredStringified
).
toBe
(
correctText
);
done
();
});
});
});
describe
(
'
showApproveButton
'
,
function
()
{
it
(
'
should not be true when the user cannot approve
'
,
function
(
done
)
{
this
.
approvalsBody
.
userCanApprove
=
false
;
this
.
approvalsBody
.
userHasApproved
=
true
;
Vue
.
nextTick
(()
=>
{
expect
(
this
.
approvalsBody
.
showApproveButton
).
toBe
(
false
);
done
();
});
describe
(
'
showApproveButton
'
,
()
=>
{
it
(
'
should not be true when the user cannot approve
'
,
done
=>
{
vm
.
userCanApprove
=
false
;
vm
.
userHasApproved
=
true
;
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
showApproveButton
).
toBe
(
false
);
done
();
});
});
it
(
'
should be true when the user can approve
'
,
function
(
done
)
{
this
.
approvalsBody
.
userCanApprove
=
true
;
this
.
approvalsBody
.
userHasApproved
=
false
;
Vue
.
nextTick
(()
=>
{
expect
(
this
.
approvalsBody
.
showApproveButton
).
toBe
(
true
);
done
();
});
it
(
'
should be true when the user can approve
'
,
done
=>
{
vm
.
userCanApprove
=
true
;
vm
.
userHasApproved
=
false
;
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
showApproveButton
).
toBe
(
true
);
done
();
});
});
});
describe
(
'
approveButtonText
'
,
function
()
{
it
(
'
The approve button should have the "Approve" text
'
,
function
(
done
)
{
this
.
approvalsBody
.
approvalsLeft
=
1
;
this
.
approvalsBody
.
userHasApproved
=
false
;
this
.
approvalsBody
.
userCanApprove
=
true
;
describe
(
'
approveButtonText
'
,
()
=>
{
it
(
'
The approve button should have the "Approve" text
'
,
done
=>
{
vm
.
approvalsLeft
=
1
;
vm
.
userHasApproved
=
false
;
vm
.
userCanApprove
=
true
;
Vue
.
nextTick
(()
=>
{
expect
(
this
.
approvalsBody
.
approveButtonText
).
toBe
(
'
Approve
'
);
done
();
});
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
approveButtonText
).
toBe
(
'
Approve
'
);
done
();
});
});
it
(
'
The approve button should have the "Add approval" text
'
,
function
(
done
)
{
this
.
approvalsBody
.
approvalsLeft
=
0
;
this
.
approvalsBody
.
userHasApproved
=
false
;
this
.
approvalsBody
.
userCanApprove
=
true
;
it
(
'
The approve button should have the "Add approval" text
'
,
done
=>
{
vm
.
approvalsLeft
=
0
;
vm
.
userHasApproved
=
false
;
vm
.
userCanApprove
=
true
;
Vue
.
nextTick
(()
=>
{
expect
(
this
.
approvalsBody
.
approveButtonText
).
toBe
(
'
Add approval
'
);
done
();
});
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
approveButtonText
).
toBe
(
'
Add approval
'
);
done
();
});
});
});
});
})
(
window
.
gl
||
(
window
.
gl
=
{}))
;
});
spec/javascripts/approvals/approvals_footer_spec.js
View file @
beb4c23e
import
Vue
from
'
vue
'
;
import
_
from
'
underscore
'
;
import
pendingAvatarSvg
from
'
ee_icons/_icon_dotted_circle.svg
'
;
import
ApprovalsFooter
from
'
ee/vue_merge_request_widget/components/approvals/approvals_footer
'
;
import
ApprovalsFooter
from
'
ee/vue_merge_request_widget/components/approvals/approvals_footer.vue
'
;
import
{
TEST_HOST
}
from
'
spec/test_constants
'
;
(()
=>
{
gl
.
ApprovalsStore
=
{
data
:
{},
initStoreOnce
()
{
return
{
then
()
{},
};
describe
(
'
Approvals Footer Component
'
,
()
=>
{
let
vm
;
const
initialData
=
{
mr
:
{
state
:
'
readyToMerge
'
,
},
service
:
{},
userCanApprove
:
false
,
userHasApproved
:
true
,
approvedBy
:
[],
approvalsLeft
:
1
,
pendingAvatarSvg
,
};
function
initApprovalsFooterComponent
()
{
setFixtures
(
`
<div>
<div id="mock-container"></div>
</div>
`
);
this
.
initialData
=
{
mr
:
{
state
:
'
readyToMerge
'
,
},
service
:
{},
userCanApprove
:
false
,
userHasApproved
:
true
,
approvedBy
:
[],
approvalsLeft
:
1
,
pendingAvatarSvg
,
};
beforeEach
(()
=>
{
setFixtures
(
'
<div id="mock-container"></div>
'
);
const
ApprovalsFooterComponent
=
Vue
.
extend
(
ApprovalsFooter
);
this
.
approvalsFooter
=
new
ApprovalsFooterComponent
({
vm
=
new
ApprovalsFooterComponent
({
el
:
'
#mock-container
'
,
propsData
:
this
.
initialData
,
beforeCreate
()
{},
propsData
:
initialData
,
});
}
}
);
describe
(
'
Approvals Footer Component
'
,
function
()
{
beforeEach
(
function
()
{
initApprovalsFooterComponent
.
call
(
this
);
});
afterEach
(()
=>
{
vm
.
$destroy
();
});
it
(
'
should correctly set component props
'
,
function
()
{
const
approvalsFooter
=
this
.
approvalsFooter
;
_
.
each
(
approvalsFooter
,
(
propValue
,
propKey
)
=>
{
if
(
this
.
initialData
[
propKey
])
{
expect
(
approvalsFooter
[
propKey
]).
toBe
(
this
.
initialData
[
propKey
]);
}
});
it
(
'
should correctly set component props
'
,
()
=>
{
Object
.
keys
(
vm
).
forEach
(
propKey
=>
{
if
(
initialData
[
propKey
])
{
expect
(
vm
[
propKey
]).
toBe
(
initialData
[
propKey
]);
}
});
});
describe
(
'
Computed properties
'
,
function
()
{
it
(
'
should correctly set showUnapproveButton when the user can unapprove
'
,
function
()
{
expect
(
this
.
approvalsFooter
.
showUnapproveButton
).
toBeTruthy
();
this
.
approvalsFooter
.
mr
.
state
=
'
merged
'
;
expect
(
this
.
approvalsFooter
.
showUnapproveButton
).
toBeFalsy
();
});
describe
(
'
Computed properties
'
,
()
=>
{
it
(
'
should correctly set showUnapproveButton when the user can unapprove
'
,
()
=>
{
expect
(
vm
.
showUnapproveButton
).
toBeTruthy
();
vm
.
mr
.
state
=
'
merged
'
;
expect
(
vm
.
showUnapproveButton
).
toBeFalsy
();
});
it
(
'
should correctly set showUnapproveButton when the user can not unapprove
'
,
function
(
done
)
{
this
.
approvalsFooter
.
userCanApprove
=
true
;
it
(
'
should correctly set showUnapproveButton when the user can not unapprove
'
,
done
=>
{
vm
.
userCanApprove
=
true
;
Vue
.
nextTick
(()
=>
{
expect
(
this
.
approvalsFooter
.
showUnapproveButton
).
toBe
(
false
);
done
();
});
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
showUnapproveButton
).
toBe
(
false
);
done
();
});
});
});
describe
(
'
approvers list
'
,
function
()
{
it
(
'
shows link to member avatar for for each approver
'
,
function
(
done
)
{
this
.
approvalsFooter
.
approvedBy
.
push
({
user
:
{
avatar_url
:
'
/dummy.jpg
'
,
},
});
describe
(
'
approvers list
'
,
()
=>
{
it
(
'
shows link to member avatar for for each approver
'
,
done
=>
{
vm
.
approvedBy
.
push
({
user
:
{
avatar_url
:
`
${
TEST_HOST
}
/dummy.jpg`
,
},
});
Vue
.
nextTick
(()
=>
{
const
memberImage
=
document
.
querySelector
(
'
.approvers-list img
'
);
expect
(
memberImage
.
src
).
toMatch
(
/dummy
\.
jpg$/
);
done
();
});
Vue
.
nextTick
(()
=>
{
const
memberImage
=
document
.
querySelector
(
'
.approvers-list img
'
);
expect
(
memberImage
.
src
).
toMatch
(
/dummy
\.
jpg$/
);
done
();
});
});
});
})
(
window
.
gl
||
(
window
.
gl
=
{}))
;
});
spec/javascripts/vue_shared/components/link_to_member_avatar_spec.js
View file @
beb4c23e
/* eslint-disable no-restricted-syntax */
import
$
from
'
jquery
'
;
import
Vue
from
'
vue
'
;
import
linkToMemberAvatar
from
'
ee/vue_shared/components/link_to_member_avatar
'
;
import
linkToMemberAvatar
from
'
ee/vue_shared/components/link_to_member_avatar.vue
'
;
describe
(
'
Link To Members Components
'
,
()
=>
{
let
vm
;
const
propsData
=
{
avatarSize
:
32
,
avatarUrl
:
'
myavatarurl.com
'
,
profileUrl
:
'
profileUrl.com
'
,
displayName
:
'
mydisplayname
'
,
extraAvatarClass
:
'
myextraavatarclass
'
,
extraLinkClass
:
'
myextralinkclass
'
,
showTooltip
:
true
,
};
(()
=>
{
function
initComponent
(
propsData
=
{})
{
setFixtures
(
`
<div>
<div id="mock-container"></div>
</div>
`
);
beforeEach
(()
=>
{
setFixtures
(
'
<div id="mock-container"></div>
'
);
const
LinkToMembersComponent
=
Vue
.
extend
(
linkToMemberAvatar
);
this
.
component
=
new
LinkToMembersComponent
({
vm
=
new
LinkToMembersComponent
({
el
:
'
#mock-container
'
,
propsData
,
}).
$mount
();
});
afterEach
(()
=>
{
vm
.
$destroy
();
});
it
(
'
should default to the body as tooltip container
'
,
()
=>
{
expect
(
vm
.
tooltipContainer
).
toBe
(
'
body
'
);
});
it
(
'
should return a defined Vue component
'
,
()
=>
{
expect
(
vm
).
toBeDefined
();
expect
(
vm
.
$data
).
toBeDefined
();
});
it
(
'
should have <a> children
'
,
()
=>
{
const
componentLink
=
vm
.
$el
.
querySelector
(
'
a
'
);
expect
(
componentLink
).
not
.
toBeNull
();
expect
(
componentLink
.
getAttribute
(
'
href
'
)).
toBe
(
propsData
.
profileUrl
);
});
it
(
'
should show a <img> if the avatarUrl is set
'
,
()
=>
{
const
avatarImg
=
vm
.
$el
.
querySelector
(
'
a img
'
);
expect
(
avatarImg
).
not
.
toBeNull
();
expect
(
avatarImg
.
getAttribute
(
'
src
'
)).
toBe
(
propsData
.
avatarUrl
);
});
it
(
'
should fallback to a <svg> if the avatarUrl is not set
'
,
done
=>
{
vm
.
avatarUrl
=
undefined
;
Vue
.
nextTick
(()
=>
{
const
avatarImg
=
vm
.
$el
.
querySelector
(
'
a svg
'
);
expect
(
avatarImg
).
not
.
toBeNull
();
done
();
});
});
it
(
'
should correctly compute computed values
'
,
()
=>
{
const
correctVals
=
{
disabledClass
:
''
,
avatarSizeClass
:
'
s32
'
,
avatarHtmlClass
:
'
s32 avatar avatar-inline avatar-placeholder
'
,
avatarClass
:
'
avatar avatar-inline s32 myextraavatarclass
'
,
tooltipClass
:
'
has-tooltip
'
,
linkClass
:
'
author_link has-tooltip myextralinkclass
'
,
};
this
.
$document
=
$
(
document
);
}
describe
(
'
Link To Members Components
'
,
function
()
{
describe
(
'
Initialization
'
,
function
()
{
beforeEach
(
function
()
{
const
propsData
=
this
.
propsData
=
{
avatarSize
:
32
,
avatarUrl
:
'
myavatarurl.com
'
,
displayName
:
'
mydisplayname
'
,
extraAvatarClass
:
'
myextraavatarclass
'
,
extraLinkClass
:
'
myextralinkclass
'
,
showTooltip
:
true
,
};
initComponent
.
call
(
this
,
{
propsData
,
});
});
it
(
'
should return a defined Vue component
'
,
function
()
{
expect
(
this
.
component
).
toBeDefined
();
expect
(
this
.
component
.
$data
).
toBeDefined
();
});
it
(
'
should have <a> and <svg> children
'
,
function
()
{
const
componentLink
=
this
.
component
.
$el
.
querySelector
(
'
a
'
);
const
componentPlaceholder
=
componentLink
.
querySelector
(
'
svg
'
);
expect
(
componentLink
).
not
.
toBeNull
();
expect
(
componentPlaceholder
).
not
.
toBeNull
();
});
it
(
'
should correctly compute computed values
'
,
function
(
done
)
{
const
correctVals
=
{
disabledClass
:
''
,
avatarSizeClass
:
'
s32
'
,
avatarHtmlClass
:
'
s32 avatar avatar-inline avatar-placeholder
'
,
avatarClass
:
'
avatar avatar-inline s32
'
,
tooltipClass
:
'
has-tooltip
'
,
linkClass
:
'
author_link has-tooltip
'
,
tooltipContainerAttr
:
'
body
'
,
};
Vue
.
nextTick
(()
=>
{
for
(
const
computedKey
in
correctVals
)
{
if
(
Object
.
prototype
.
hasOwnProperty
.
call
(
correctVals
,
computedKey
))
{
const
expectedVal
=
correctVals
[
computedKey
];
const
actualComputed
=
this
.
component
[
computedKey
];
expect
(
actualComputed
).
toBe
(
expectedVal
);
}
}
done
();
});
});
Object
.
keys
(
correctVals
).
forEach
(
computedKey
=>
{
const
expectedVal
=
correctVals
[
computedKey
];
const
actualComputed
=
vm
[
computedKey
];
expect
(
actualComputed
).
toBe
(
expectedVal
);
});
});
})
()
;
});
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment