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
66b9aa80
Commit
66b9aa80
authored
Feb 16, 2021
by
Samantha Ming
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Restyle compare revision and add repo dropdown
Issue:
https://gitlab.com/gitlab-org/gitlab/-/issues/14615/
parent
5b7efd82
Changes
18
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
965 additions
and
104 deletions
+965
-104
app/assets/javascripts/projects/compare/components/app.vue
app/assets/javascripts/projects/compare/components/app.vue
+45
-34
app/assets/javascripts/projects/compare/components/app_legacy.vue
...ts/javascripts/projects/compare/components/app_legacy.vue
+89
-0
app/assets/javascripts/projects/compare/components/repo_dropdown.vue
...javascripts/projects/compare/components/repo_dropdown.vue
+93
-0
app/assets/javascripts/projects/compare/components/revision_card.vue
...javascripts/projects/compare/components/revision_card.vue
+65
-0
app/assets/javascripts/projects/compare/components/revision_dropdown.vue
...scripts/projects/compare/components/revision_dropdown.vue
+59
-56
app/assets/javascripts/projects/compare/components/revision_dropdown_legacy.vue
.../projects/compare/components/revision_dropdown_legacy.vue
+145
-0
app/assets/javascripts/projects/compare/index.js
app/assets/javascripts/projects/compare/index.js
+40
-2
app/assets/stylesheets/pages/projects.scss
app/assets/stylesheets/pages/projects.scss
+12
-0
app/assets/stylesheets/utilities.scss
app/assets/stylesheets/utilities.scss
+14
-0
app/controllers/projects/compare_controller.rb
app/controllers/projects/compare_controller.rb
+4
-0
app/views/projects/compare/index.html.haml
app/views/projects/compare/index.html.haml
+14
-5
locale/gitlab.pot
locale/gitlab.pot
+9
-0
spec/frontend/projects/compare/components/app_legacy_spec.js
spec/frontend/projects/compare/components/app_legacy_spec.js
+116
-0
spec/frontend/projects/compare/components/app_spec.js
spec/frontend/projects/compare/components/app_spec.js
+5
-5
spec/frontend/projects/compare/components/repo_dropdown_spec.js
...rontend/projects/compare/components/repo_dropdown_spec.js
+98
-0
spec/frontend/projects/compare/components/revision_card_spec.js
...rontend/projects/compare/components/revision_card_spec.js
+49
-0
spec/frontend/projects/compare/components/revision_dropdown_legacy_spec.js
...jects/compare/components/revision_dropdown_legacy_spec.js
+92
-0
spec/frontend/projects/compare/components/revision_dropdown_spec.js
...end/projects/compare/components/revision_dropdown_spec.js
+16
-2
No files found.
app/assets/javascripts/projects/compare/components/app.vue
View file @
66b9aa80
<
script
>
import
{
GlButton
}
from
'
@gitlab/ui
'
;
import
csrf
from
'
~/lib/utils/csrf
'
;
import
Revision
Dropdown
from
'
./revision_dropdown
.vue
'
;
import
Revision
Card
from
'
./revision_card
.vue
'
;
export
default
{
csrf
,
components
:
{
Revision
Dropdown
,
Revision
Card
,
GlButton
,
},
props
:
{
...
...
@@ -48,42 +48,53 @@ export default {
<
template
>
<form
ref=
"form"
class=
"
form-inline
js-requires-input js-signature-container"
class=
"js-requires-input js-signature-container"
method=
"POST"
:action=
"projectCompareIndexPath"
>
<input
:value=
"$options.csrf.token"
type=
"hidden"
name=
"authenticity_token"
/>
<revision-dropdown
:refs-project-path=
"refsProjectPath"
revision-text=
"Source"
params-name=
"to"
:params-branch=
"paramsTo"
/>
<div
class=
"compare-ellipsis gl-display-inline"
data-testid=
"ellipsis"
>
...
</div>
<revision-dropdown
:refs-project-path=
"refsProjectPath"
revision-text=
"Target"
params-name=
"from"
:params-branch=
"paramsFrom"
/>
<gl-button
category=
"primary"
variant=
"success"
class=
"gl-ml-3"
@
click=
"onSubmit"
>
{{
s__
(
'
CompareRevisions|Compare
'
)
}}
</gl-button>
<gl-button
v-if=
"projectMergeRequestPath"
:href=
"projectMergeRequestPath"
data-testid=
"projectMrButton"
class=
"btn btn-default gl-button gl-ml-3"
<div
class=
"gl-lg-flex-direction-row gl-lg-display-flex gl-align-items-center compare-revision-cards"
>
{{
s__
(
'
CompareRevisions|View open merge request
'
)
}}
</gl-button>
<gl-button
v-else-if=
"createMrPath"
:href=
"createMrPath"
data-testid=
"createMrButton"
class=
"btn btn-default gl-button gl-ml-3"
>
{{
s__
(
'
CompareRevisions|Create merge request
'
)
}}
</gl-button>
<revision-card
:refs-project-path=
"refsProjectPath"
revision-text=
"Source"
params-name=
"to"
:params-branch=
"paramsTo"
/>
<div
class=
"compare-ellipsis gl-display-flex gl-justify-content-center gl-align-items-center gl-my-4 gl-md-my-0"
data-testid=
"ellipsis"
>
...
</div>
<revision-card
:refs-project-path=
"refsProjectPath"
revision-text=
"Target"
params-name=
"from"
:params-branch=
"paramsFrom"
/>
</div>
<div
class=
"gl-mt-4"
>
<gl-button
category=
"primary"
variant=
"success"
@
click=
"onSubmit"
>
{{
s__
(
'
CompareRevisions|Compare
'
)
}}
</gl-button>
<gl-button
v-if=
"projectMergeRequestPath"
:href=
"projectMergeRequestPath"
data-testid=
"projectMrButton"
class=
"btn btn-default gl-button"
>
{{
s__
(
'
CompareRevisions|View open merge request
'
)
}}
</gl-button>
<gl-button
v-else-if=
"createMrPath"
:href=
"createMrPath"
data-testid=
"createMrButton"
class=
"btn btn-default gl-button"
>
{{
s__
(
'
CompareRevisions|Create merge request
'
)
}}
</gl-button>
</div>
</form>
</
template
>
app/assets/javascripts/projects/compare/components/app_legacy.vue
0 → 100644
View file @
66b9aa80
<
script
>
import
{
GlButton
}
from
'
@gitlab/ui
'
;
import
csrf
from
'
~/lib/utils/csrf
'
;
import
RevisionDropdown
from
'
./revision_dropdown_legacy.vue
'
;
export
default
{
csrf
,
components
:
{
RevisionDropdown
,
GlButton
,
},
props
:
{
projectCompareIndexPath
:
{
type
:
String
,
required
:
true
,
},
refsProjectPath
:
{
type
:
String
,
required
:
true
,
},
paramsFrom
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
paramsTo
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
projectMergeRequestPath
:
{
type
:
String
,
required
:
true
,
},
createMrPath
:
{
type
:
String
,
required
:
true
,
},
},
methods
:
{
onSubmit
()
{
this
.
$refs
.
form
.
submit
();
},
},
};
</
script
>
<
template
>
<form
ref=
"form"
class=
"form-inline js-requires-input js-signature-container"
method=
"POST"
:action=
"projectCompareIndexPath"
>
<input
:value=
"$options.csrf.token"
type=
"hidden"
name=
"authenticity_token"
/>
<revision-dropdown
:refs-project-path=
"refsProjectPath"
revision-text=
"Source"
params-name=
"to"
:params-branch=
"paramsTo"
/>
<div
class=
"compare-ellipsis gl-display-inline"
data-testid=
"ellipsis"
>
...
</div>
<revision-dropdown
:refs-project-path=
"refsProjectPath"
revision-text=
"Target"
params-name=
"from"
:params-branch=
"paramsFrom"
/>
<gl-button
category=
"primary"
variant=
"success"
class=
"gl-ml-3"
@
click=
"onSubmit"
>
{{
s__
(
'
CompareRevisions|Compare
'
)
}}
</gl-button>
<gl-button
v-if=
"projectMergeRequestPath"
:href=
"projectMergeRequestPath"
data-testid=
"projectMrButton"
class=
"btn btn-default gl-button gl-ml-3"
>
{{
s__
(
'
CompareRevisions|View open merge request
'
)
}}
</gl-button>
<gl-button
v-else-if=
"createMrPath"
:href=
"createMrPath"
data-testid=
"createMrButton"
class=
"btn btn-default gl-button gl-ml-3"
>
{{
s__
(
'
CompareRevisions|Create merge request
'
)
}}
</gl-button>
</form>
</
template
>
app/assets/javascripts/projects/compare/components/repo_dropdown.vue
0 → 100644
View file @
66b9aa80
<
script
>
import
{
GlDropdown
,
GlDropdownItem
,
GlSearchBoxByType
}
from
'
@gitlab/ui
'
;
const
SOURCE_PARAM_NAME
=
'
to
'
;
export
default
{
components
:
{
GlDropdown
,
GlDropdownItem
,
GlSearchBoxByType
,
},
inject
:
[
'
projectTo
'
,
'
projectsFrom
'
],
props
:
{
paramsName
:
{
type
:
String
,
required
:
true
,
},
},
data
()
{
return
{
searchTerm
:
''
,
selectedRepo
:
{},
};
},
computed
:
{
filteredRepos
()
{
const
lowerCaseSearchTerm
=
this
.
searchTerm
.
toLowerCase
();
return
this
?.
projectsFrom
.
filter
(({
name
})
=>
name
.
toLowerCase
().
includes
(
lowerCaseSearchTerm
),
);
},
isSourceRevision
()
{
return
this
.
paramsName
===
SOURCE_PARAM_NAME
;
},
inputName
()
{
return
`
${
this
.
paramsName
}
_project_id`
;
},
},
mounted
()
{
this
.
setDefaultRepo
();
},
methods
:
{
onClick
(
repo
)
{
this
.
selectedRepo
=
repo
;
this
.
emitTargetProject
(
repo
.
name
);
},
setDefaultRepo
()
{
if
(
this
.
isSourceRevision
)
{
this
.
selectedRepo
=
this
.
projectTo
;
return
;
}
const
[
defaultTargetProject
]
=
this
.
projectsFrom
;
this
.
emitTargetProject
(
defaultTargetProject
.
name
);
this
.
selectedRepo
=
defaultTargetProject
;
},
emitTargetProject
(
name
)
{
if
(
!
this
.
isSourceRevision
)
{
this
.
$emit
(
'
changeTargetProject
'
,
name
);
}
},
},
};
</
script
>
<
template
>
<div>
<input
type=
"hidden"
:name=
"inputName"
:value=
"selectedRepo.id"
/>
<gl-dropdown
:text=
"selectedRepo.name"
:header-text=
"s__(`CompareRevisions|Select target project`)"
class=
"gl-w-full gl-font-monospace gl-sm-pr-3"
toggle-class=
"gl-min-w-0"
:disabled=
"isSourceRevision"
>
<template
#header
>
<gl-search-box-by-type
v-if=
"!isSourceRevision"
v-model.trim=
"searchTerm"
/>
</
template
>
<
template
v-if=
"!isSourceRevision"
>
<gl-dropdown-item
v-for=
"repo in filteredRepos"
:key=
"repo.id"
is-check-item
:is-checked=
"selectedRepo.id === repo.id"
@
click=
"onClick(repo)"
>
{{
repo
.
name
}}
</gl-dropdown-item>
</
template
>
</gl-dropdown>
</div>
</template>
app/assets/javascripts/projects/compare/components/revision_card.vue
0 → 100644
View file @
66b9aa80
<
script
>
import
{
GlCard
}
from
'
@gitlab/ui
'
;
import
RepoDropdown
from
'
./repo_dropdown.vue
'
;
import
RevisionDropdown
from
'
./revision_dropdown.vue
'
;
export
default
{
components
:
{
RepoDropdown
,
RevisionDropdown
,
GlCard
,
},
props
:
{
refsProjectPath
:
{
type
:
String
,
required
:
true
,
},
revisionText
:
{
type
:
String
,
required
:
true
,
},
paramsName
:
{
type
:
String
,
required
:
true
,
},
paramsBranch
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
},
data
()
{
return
{
selectedRefsProjectPath
:
this
.
refsProjectPath
,
};
},
methods
:
{
onChangeTargetProject
(
targetProjectName
)
{
if
(
this
.
paramsName
===
'
from
'
)
{
this
.
selectedRefsProjectPath
=
`/
${
targetProjectName
}
/refs`
;
}
},
},
};
</
script
>
<
template
>
<gl-card
header-class=
"gl-py-2 gl-px-3 gl-font-weight-bold"
body-class=
"gl-px-3"
>
<template
#header
>
{{
s__
(
`CompareRevisions|${revisionText
}
`
)
}}
<
/template
>
<
div
class
=
"
gl-sm-display-flex gl-align-items-center
"
>
<
repo
-
dropdown
class
=
"
gl-sm-w-half
"
:
params
-
name
=
"
paramsName
"
@
changeTargetProject
=
"
onChangeTargetProject
"
/>
<
revision
-
dropdown
class
=
"
gl-sm-w-half gl-mt-3 gl-sm-mt-0
"
:
refs
-
project
-
path
=
"
selectedRefsProjectPath
"
:
params
-
name
=
"
paramsName
"
:
params
-
branch
=
"
paramsBranch
"
/>
<
/div
>
<
/gl-card
>
<
/template
>
app/assets/javascripts/projects/compare/components/revision_dropdown.vue
View file @
66b9aa80
...
...
@@ -4,6 +4,8 @@ import createFlash from '~/flash';
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
s__
}
from
'
~/locale
'
;
const
emptyDropdownText
=
s__
(
'
CompareRevisions|Select branch/tag
'
);
export
default
{
components
:
{
GlDropdown
,
...
...
@@ -16,10 +18,6 @@ export default {
type
:
String
,
required
:
true
,
},
revisionText
:
{
type
:
String
,
required
:
true
,
},
paramsName
:
{
type
:
String
,
required
:
true
,
...
...
@@ -55,12 +53,24 @@ export default {
return
this
.
filteredTags
.
length
;
},
},
watch
:
{
refsProjectPath
(
newRefsProjectPath
,
oldRefsProjectPath
)
{
if
(
newRefsProjectPath
!==
oldRefsProjectPath
)
{
this
.
fetchBranchesAndTags
(
true
);
}
},
},
mounted
()
{
this
.
fetchBranchesAndTags
();
},
methods
:
{
fetchBranchesAndTags
()
{
fetchBranchesAndTags
(
reset
=
false
)
{
const
endpoint
=
this
.
refsProjectPath
;
this
.
loading
=
true
;
if
(
reset
)
{
this
.
selectedRevision
=
emptyDropdownText
;
}
return
axios
.
get
(
endpoint
)
...
...
@@ -70,9 +80,9 @@ export default {
})
.
catch
(()
=>
{
createFlash
({
message
:
`
${
s__
(
'
CompareRevisions|There was an error while
updat
ing the branch/tag list. Please try again.
'
,
)
}
`
,
message
:
s__
(
'
CompareRevisions|There was an error while
load
ing the branch/tag list. Please try again.
'
,
),
});
})
.
finally
(()
=>
{
...
...
@@ -80,7 +90,7 @@ export default {
});
},
getDefaultBranch
()
{
return
this
.
paramsBranch
||
s__
(
'
CompareRevisions|Select branch/tag
'
)
;
return
this
.
paramsBranch
||
emptyDropdownText
;
},
onClick
(
revision
)
{
this
.
selectedRevision
=
revision
;
...
...
@@ -93,53 +103,46 @@ export default {
</
script
>
<
template
>
<div
class=
"form-group compare-form-group"
:class=
"`js-compare-$
{paramsName}-dropdown`">
<div
class=
"input-group inline-input-group"
>
<span
class=
"input-group-prepend"
>
<div
class=
"input-group-text"
>
{{
revisionText
}}
</div>
</span>
<input
type=
"hidden"
:name=
"paramsName"
:value=
"selectedRevision"
/>
<gl-dropdown
class=
"gl-flex-grow-1 gl-flex-basis-0 gl-min-w-0 gl-font-monospace"
toggle-class=
"form-control compare-dropdown-toggle js-compare-dropdown gl-min-w-0 gl-rounded-top-left-none! gl-rounded-bottom-left-none!"
:text=
"selectedRevision"
header-text=
"Select Git revision"
:loading=
"loading"
<div
:class=
"`js-compare-$
{paramsName}-dropdown`">
<input
type=
"hidden"
:name=
"paramsName"
:value=
"selectedRevision"
/>
<gl-dropdown
class=
"gl-w-full gl-font-monospace"
toggle-class=
"form-control compare-dropdown-toggle js-compare-dropdown gl-min-w-0"
:text=
"selectedRevision"
:header-text=
"s__('CompareRevisions|Select Git revision')"
:loading=
"loading"
>
<template
#header
>
<gl-search-box-by-type
v-model.trim=
"searchTerm"
:placeholder=
"s__('CompareRevisions|Filter by Git revision')"
@
keyup.enter=
"onSearchEnter"
/>
</
template
>
<gl-dropdown-section-header
v-if=
"hasFilteredBranches"
>
{{ s__('CompareRevisions|Branches') }}
</gl-dropdown-section-header>
<gl-dropdown-item
v-for=
"(branch, index) in filteredBranches"
:key=
"`branch${index}`"
is-check-item
:is-checked=
"selectedRevision === branch"
@
click=
"onClick(branch)"
>
{{ branch }}
</gl-dropdown-item>
<gl-dropdown-section-header
v-if=
"hasFilteredTags"
>
{{ s__('CompareRevisions|Tags') }}
</gl-dropdown-section-header>
<gl-dropdown-item
v-for=
"(tag, index) in filteredTags"
:key=
"`tag${index}`"
is-check-item
:is-checked=
"selectedRevision === tag"
@
click=
"onClick(tag)"
>
<template
#header
>
<gl-search-box-by-type
v-model.trim=
"searchTerm"
:placeholder=
"s__('CompareRevisions|Filter by Git revision')"
@
keyup.enter=
"onSearchEnter"
/>
</
template
>
<gl-dropdown-section-header
v-if=
"hasFilteredBranches"
>
{{ s__('CompareRevisions|Branches') }}
</gl-dropdown-section-header>
<gl-dropdown-item
v-for=
"(branch, index) in filteredBranches"
:key=
"`branch${index}`"
is-check-item
:is-checked=
"selectedRevision === branch"
@
click=
"onClick(branch)"
>
{{ branch }}
</gl-dropdown-item>
<gl-dropdown-section-header
v-if=
"hasFilteredTags"
>
{{ s__('CompareRevisions|Tags') }}
</gl-dropdown-section-header>
<gl-dropdown-item
v-for=
"(tag, index) in filteredTags"
:key=
"`tag${index}`"
is-check-item
:is-checked=
"selectedRevision === tag"
@
click=
"onClick(tag)"
>
{{ tag }}
</gl-dropdown-item>
</gl-dropdown>
</div>
{{ tag }}
</gl-dropdown-item>
</gl-dropdown>
</div>
</template>
app/assets/javascripts/projects/compare/components/revision_dropdown_legacy.vue
0 → 100644
View file @
66b9aa80
<
script
>
import
{
GlDropdown
,
GlDropdownItem
,
GlSearchBoxByType
,
GlDropdownSectionHeader
}
from
'
@gitlab/ui
'
;
import
createFlash
from
'
~/flash
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
s__
}
from
'
~/locale
'
;
export
default
{
components
:
{
GlDropdown
,
GlDropdownItem
,
GlDropdownSectionHeader
,
GlSearchBoxByType
,
},
props
:
{
refsProjectPath
:
{
type
:
String
,
required
:
true
,
},
revisionText
:
{
type
:
String
,
required
:
true
,
},
paramsName
:
{
type
:
String
,
required
:
true
,
},
paramsBranch
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
},
data
()
{
return
{
branches
:
[],
tags
:
[],
loading
:
true
,
searchTerm
:
''
,
selectedRevision
:
this
.
getDefaultBranch
(),
};
},
computed
:
{
filteredBranches
()
{
return
this
.
branches
.
filter
((
branch
)
=>
branch
.
toLowerCase
().
includes
(
this
.
searchTerm
.
toLowerCase
()),
);
},
hasFilteredBranches
()
{
return
this
.
filteredBranches
.
length
;
},
filteredTags
()
{
return
this
.
tags
.
filter
((
tag
)
=>
tag
.
toLowerCase
().
includes
(
this
.
searchTerm
.
toLowerCase
()));
},
hasFilteredTags
()
{
return
this
.
filteredTags
.
length
;
},
},
mounted
()
{
this
.
fetchBranchesAndTags
();
},
methods
:
{
fetchBranchesAndTags
()
{
const
endpoint
=
this
.
refsProjectPath
;
return
axios
.
get
(
endpoint
)
.
then
(({
data
})
=>
{
this
.
branches
=
data
.
Branches
;
this
.
tags
=
data
.
Tags
;
})
.
catch
(()
=>
{
createFlash
({
message
:
`
${
s__
(
'
CompareRevisions|There was an error while updating the branch/tag list. Please try again.
'
,
)}
`
,
});
})
.
finally
(()
=>
{
this
.
loading
=
false
;
});
},
getDefaultBranch
()
{
return
this
.
paramsBranch
||
s__
(
'
CompareRevisions|Select branch/tag
'
);
},
onClick
(
revision
)
{
this
.
selectedRevision
=
revision
;
},
onSearchEnter
()
{
this
.
selectedRevision
=
this
.
searchTerm
;
},
},
};
</
script
>
<
template
>
<div
class=
"form-group compare-form-group"
:class=
"`js-compare-$
{paramsName}-dropdown`">
<div
class=
"input-group inline-input-group"
>
<span
class=
"input-group-prepend"
>
<div
class=
"input-group-text"
>
{{
revisionText
}}
</div>
</span>
<input
type=
"hidden"
:name=
"paramsName"
:value=
"selectedRevision"
/>
<gl-dropdown
class=
"gl-flex-grow-1 gl-flex-basis-0 gl-min-w-0 gl-font-monospace"
toggle-class=
"form-control compare-dropdown-toggle js-compare-dropdown gl-min-w-0 gl-rounded-top-left-none! gl-rounded-bottom-left-none!"
:text=
"selectedRevision"
header-text=
"Select Git revision"
:loading=
"loading"
>
<template
#header
>
<gl-search-box-by-type
v-model.trim=
"searchTerm"
:placeholder=
"s__('CompareRevisions|Filter by Git revision')"
@
keyup.enter=
"onSearchEnter"
/>
</
template
>
<gl-dropdown-section-header
v-if=
"hasFilteredBranches"
>
{{ s__('CompareRevisions|Branches') }}
</gl-dropdown-section-header>
<gl-dropdown-item
v-for=
"(branch, index) in filteredBranches"
:key=
"`branch${index}`"
is-check-item
:is-checked=
"selectedRevision === branch"
@
click=
"onClick(branch)"
>
{{ branch }}
</gl-dropdown-item>
<gl-dropdown-section-header
v-if=
"hasFilteredTags"
>
{{ s__('CompareRevisions|Tags') }}
</gl-dropdown-section-header>
<gl-dropdown-item
v-for=
"(tag, index) in filteredTags"
:key=
"`tag${index}`"
is-check-item
:is-checked=
"selectedRevision === tag"
@
click=
"onClick(tag)"
>
{{ tag }}
</gl-dropdown-item>
</gl-dropdown>
</div>
</div>
</template>
app/assets/javascripts/projects/compare/index.js
View file @
66b9aa80
import
Vue
from
'
vue
'
;
import
CompareApp
from
'
./components/app.vue
'
;
import
CompareAppLegacy
from
'
./components/app_legacy.vue
'
;
export
default
function
init
()
{
const
el
=
document
.
getElementById
(
'
js-compare-selector
'
);
if
(
gon
.
features
?.
compareRepoDropdown
)
{
const
{
refsProjectPath
,
paramsFrom
,
paramsTo
,
projectCompareIndexPath
,
projectMergeRequestPath
,
createMrPath
,
projectTo
,
projectsFrom
,
}
=
el
.
dataset
;
return
new
Vue
({
el
,
components
:
{
CompareApp
,
},
provide
:
{
projectTo
:
JSON
.
parse
(
projectTo
),
projectsFrom
:
JSON
.
parse
(
projectsFrom
),
},
render
(
createElement
)
{
return
createElement
(
CompareApp
,
{
props
:
{
refsProjectPath
,
paramsFrom
,
paramsTo
,
projectCompareIndexPath
,
projectMergeRequestPath
,
createMrPath
,
},
});
},
});
}
const
{
refsProjectPath
,
paramsFrom
,
...
...
@@ -15,10 +53,10 @@ export default function init() {
return
new
Vue
({
el
,
components
:
{
CompareApp
,
CompareApp
Legacy
,
},
render
(
createElement
)
{
return
createElement
(
CompareApp
,
{
return
createElement
(
CompareApp
Legacy
,
{
props
:
{
refsProjectPath
,
paramsFrom
,
...
...
app/assets/stylesheets/pages/projects.scss
View file @
66b9aa80
...
...
@@ -1008,6 +1008,18 @@ pre.light-well {
}
}
.compare-revision-cards
{
@media
(
min-width
:
$breakpoint-lg
)
{
.gl-card
{
width
:
calc
(
50%
-
15px
);
}
.compare-ellipsis
{
width
:
30px
;
}
}
}
.clearable-input
{
position
:
relative
;
...
...
app/assets/stylesheets/utilities.scss
View file @
66b9aa80
...
...
@@ -157,3 +157,17 @@
margin-bottom
:
$gl-spacing-scale-4
!
important
;
}
}
// Will be moved to @gitlab/ui in https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1168
.gl-sm-pr-3
{
@media
(
min-width
:
$breakpoint-sm
)
{
padding-right
:
$gl-spacing-scale-3
;
}
}
// Will be moved to @gitlab/ui in https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1168
.gl-sm-w-half
{
@media
(
min-width
:
$breakpoint-sm
)
{
width
:
50%
;
}
}
app/controllers/projects/compare_controller.rb
View file @
66b9aa80
...
...
@@ -20,6 +20,10 @@ class Projects::CompareController < Projects::ApplicationController
# Validation
before_action
:validate_refs!
before_action
do
push_frontend_feature_flag
(
:compare_repo_dropdown
)
end
feature_category
:source_code_management
def
index
...
...
app/views/projects/compare/index.html.haml
View file @
66b9aa80
...
...
@@ -13,8 +13,17 @@
=
html_escape
(
_
(
"Changes are shown as if the %{b_open}source%{b_close} revision was being merged into the %{b_open}target%{b_close} revision."
))
%
{
b_open:
'<b>'
.
html_safe
,
b_close:
'</b>'
.
html_safe
}
.prepend-top-20
#js-compare-selector
{
data:
{
project_compare_index_path:
project_compare_index_path
(
@project
),
refs_project_path:
refs_project_path
(
@project
),
params_from:
params
[
:from
],
params_to:
params
[
:to
],
project_merge_request_path:
@merge_request
.
present?
?
project_merge_request_path
(
@project
,
@merge_request
)
:
''
,
create_mr_path:
create_mr_button?
?
create_mr_path
:
''
}
}
-
if
Feature
.
enabled?
(
:compare_repo_dropdown
)
#js-compare-selector
{
data:
{
project_compare_index_path:
project_compare_index_path
(
@project
),
refs_project_path:
refs_project_path
(
@project
),
params_from:
params
[
:from
],
params_to:
params
[
:to
],
project_merge_request_path:
@merge_request
.
present?
?
project_merge_request_path
(
@project
,
@merge_request
)
:
''
,
create_mr_path:
create_mr_button?
?
create_mr_path
:
''
,
project_to:
{
id:
@project
.
id
,
name:
@project
.
full_path
}.
to_json
,
projects_from:
target_projects
(
@project
).
map
{
|
project
|
{
id
:project
.
id
,
name:
project
.
full_path
}
}.
to_json
}
}
-
else
#js-compare-selector
{
data:
{
project_compare_index_path:
project_compare_index_path
(
@project
),
refs_project_path:
refs_project_path
(
@project
),
params_from:
params
[
:from
],
params_to:
params
[
:to
],
project_merge_request_path:
@merge_request
.
present?
?
project_merge_request_path
(
@project
,
@merge_request
)
:
''
,
create_mr_path:
create_mr_button?
?
create_mr_path
:
''
}
}
locale/gitlab.pot
View file @
66b9aa80
...
...
@@ -7569,12 +7569,21 @@ msgstr ""
msgid "CompareRevisions|Filter by Git revision"
msgstr ""
msgid "CompareRevisions|Select Git revision"
msgstr ""
msgid "CompareRevisions|Select branch/tag"
msgstr ""
msgid "CompareRevisions|Select target project"
msgstr ""
msgid "CompareRevisions|Tags"
msgstr ""
msgid "CompareRevisions|There was an error while loading the branch/tag list. Please try again."
msgstr ""
msgid "CompareRevisions|There was an error while updating the branch/tag list. Please try again."
msgstr ""
...
...
spec/frontend/projects/compare/components/app_legacy_spec.js
0 → 100644
View file @
66b9aa80
import
{
GlButton
}
from
'
@gitlab/ui
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
CompareApp
from
'
~/projects/compare/components/app_legacy.vue
'
;
import
RevisionDropdown
from
'
~/projects/compare/components/revision_dropdown_legacy.vue
'
;
jest
.
mock
(
'
~/lib/utils/csrf
'
,
()
=>
({
token
:
'
mock-csrf-token
'
}));
const
projectCompareIndexPath
=
'
some/path
'
;
const
refsProjectPath
=
'
some/refs/path
'
;
const
paramsFrom
=
'
master
'
;
const
paramsTo
=
'
master
'
;
describe
(
'
CompareApp component
'
,
()
=>
{
let
wrapper
;
const
createComponent
=
(
props
=
{})
=>
{
wrapper
=
shallowMount
(
CompareApp
,
{
propsData
:
{
projectCompareIndexPath
,
refsProjectPath
,
paramsFrom
,
paramsTo
,
projectMergeRequestPath
:
''
,
createMrPath
:
''
,
...
props
,
},
});
};
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
});
beforeEach
(()
=>
{
createComponent
();
});
it
(
'
renders component with prop
'
,
()
=>
{
expect
(
wrapper
.
props
()).
toEqual
(
expect
.
objectContaining
({
projectCompareIndexPath
,
refsProjectPath
,
paramsFrom
,
paramsTo
,
}),
);
});
it
(
'
contains the correct form attributes
'
,
()
=>
{
expect
(
wrapper
.
attributes
(
'
action
'
)).
toBe
(
projectCompareIndexPath
);
expect
(
wrapper
.
attributes
(
'
method
'
)).
toBe
(
'
POST
'
);
});
it
(
'
has input with csrf token
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
input[name="authenticity_token"]
'
).
attributes
(
'
value
'
)).
toBe
(
'
mock-csrf-token
'
,
);
});
it
(
'
has ellipsis
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
[data-testid="ellipsis"]
'
).
exists
()).
toBe
(
true
);
});
it
(
'
render Source and Target BranchDropdown components
'
,
()
=>
{
const
branchDropdowns
=
wrapper
.
findAll
(
RevisionDropdown
);
expect
(
branchDropdowns
.
length
).
toBe
(
2
);
expect
(
branchDropdowns
.
at
(
0
).
props
(
'
revisionText
'
)).
toBe
(
'
Source
'
);
expect
(
branchDropdowns
.
at
(
1
).
props
(
'
revisionText
'
)).
toBe
(
'
Target
'
);
});
describe
(
'
compare button
'
,
()
=>
{
const
findCompareButton
=
()
=>
wrapper
.
find
(
GlButton
);
it
(
'
renders button
'
,
()
=>
{
expect
(
findCompareButton
().
exists
()).
toBe
(
true
);
});
it
(
'
submits form
'
,
()
=>
{
findCompareButton
().
vm
.
$emit
(
'
click
'
);
expect
(
wrapper
.
find
(
'
form
'
).
element
.
submit
).
toHaveBeenCalled
();
});
it
(
'
has compare text
'
,
()
=>
{
expect
(
findCompareButton
().
text
()).
toBe
(
'
Compare
'
);
});
});
describe
(
'
merge request buttons
'
,
()
=>
{
const
findProjectMrButton
=
()
=>
wrapper
.
find
(
'
[data-testid="projectMrButton"]
'
);
const
findCreateMrButton
=
()
=>
wrapper
.
find
(
'
[data-testid="createMrButton"]
'
);
it
(
'
does not have merge request buttons
'
,
()
=>
{
createComponent
();
expect
(
findProjectMrButton
().
exists
()).
toBe
(
false
);
expect
(
findCreateMrButton
().
exists
()).
toBe
(
false
);
});
it
(
'
has "View open merge request" button
'
,
()
=>
{
createComponent
({
projectMergeRequestPath
:
'
some/project/merge/request/path
'
,
});
expect
(
findProjectMrButton
().
exists
()).
toBe
(
true
);
expect
(
findCreateMrButton
().
exists
()).
toBe
(
false
);
});
it
(
'
has "Create merge request" button
'
,
()
=>
{
createComponent
({
createMrPath
:
'
some/create/create/mr/path
'
,
});
expect
(
findProjectMrButton
().
exists
()).
toBe
(
false
);
expect
(
findCreateMrButton
().
exists
()).
toBe
(
true
);
});
});
});
spec/frontend/projects/compare/components/app_spec.js
View file @
66b9aa80
import
{
GlButton
}
from
'
@gitlab/ui
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
CompareApp
from
'
~/projects/compare/components/app.vue
'
;
import
Revision
Dropdown
from
'
~/projects/compare/components/revision_dropdown
.vue
'
;
import
Revision
Card
from
'
~/projects/compare/components/revision_card
.vue
'
;
jest
.
mock
(
'
~/lib/utils/csrf
'
,
()
=>
({
token
:
'
mock-csrf-token
'
}));
...
...
@@ -63,11 +63,11 @@ describe('CompareApp component', () => {
});
it
(
'
render Source and Target BranchDropdown components
'
,
()
=>
{
const
branchDropdowns
=
wrapper
.
findAll
(
RevisionDropdown
);
const
revisionCards
=
wrapper
.
findAll
(
RevisionCard
);
expect
(
branchDropdown
s
.
length
).
toBe
(
2
);
expect
(
branchDropdown
s
.
at
(
0
).
props
(
'
revisionText
'
)).
toBe
(
'
Source
'
);
expect
(
branchDropdown
s
.
at
(
1
).
props
(
'
revisionText
'
)).
toBe
(
'
Target
'
);
expect
(
revisionCard
s
.
length
).
toBe
(
2
);
expect
(
revisionCard
s
.
at
(
0
).
props
(
'
revisionText
'
)).
toBe
(
'
Source
'
);
expect
(
revisionCard
s
.
at
(
1
).
props
(
'
revisionText
'
)).
toBe
(
'
Target
'
);
});
describe
(
'
compare button
'
,
()
=>
{
...
...
spec/frontend/projects/compare/components/repo_dropdown_spec.js
0 → 100644
View file @
66b9aa80
import
{
GlDropdown
}
from
'
@gitlab/ui
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
RepoDropdown
from
'
~/projects/compare/components/repo_dropdown.vue
'
;
const
defaultProps
=
{
paramsName
:
'
to
'
,
};
const
projectToId
=
'
1
'
;
const
projectToName
=
'
some-to-name
'
;
const
projectFromId
=
'
2
'
;
const
projectFromName
=
'
some-from-name
'
;
const
defaultProvide
=
{
projectTo
:
{
id
:
projectToId
,
name
:
projectToName
},
projectsFrom
:
[
{
id
:
projectFromId
,
name
:
projectFromName
},
{
id
:
3
,
name
:
'
some-from-another-name
'
},
],
};
describe
(
'
RepoDropdown component
'
,
()
=>
{
let
wrapper
;
const
createComponent
=
(
props
=
{},
provide
=
{})
=>
{
wrapper
=
shallowMount
(
RepoDropdown
,
{
propsData
:
{
...
defaultProps
,
...
props
,
},
provide
:
{
...
defaultProvide
,
...
provide
,
},
});
};
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
});
const
findGlDropdown
=
()
=>
wrapper
.
find
(
GlDropdown
);
const
findHiddenInput
=
()
=>
wrapper
.
find
(
'
input[type="hidden"]
'
);
describe
(
'
Source Revision
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
();
});
it
(
'
set hidden input
'
,
()
=>
{
expect
(
findHiddenInput
().
attributes
(
'
value
'
)).
toBe
(
projectToId
);
});
it
(
'
displays the project name in the disabled dropdown
'
,
()
=>
{
expect
(
findGlDropdown
().
props
(
'
text
'
)).
toBe
(
projectToName
);
expect
(
findGlDropdown
().
props
(
'
disabled
'
)).
toBe
(
true
);
});
it
(
'
does not emit `changeTargetProject` event
'
,
async
()
=>
{
wrapper
.
vm
.
emitTargetProject
(
'
foo
'
);
await
wrapper
.
vm
.
$nextTick
();
expect
(
wrapper
.
emitted
(
'
changeTargetProject
'
)).
toBeUndefined
();
});
});
describe
(
'
Target Revision
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
({
paramsName
:
'
from
'
});
});
it
(
'
set hidden input of the first project
'
,
()
=>
{
expect
(
findHiddenInput
().
attributes
(
'
value
'
)).
toBe
(
projectFromId
);
});
it
(
'
displays the first project name initially in the dropdown
'
,
()
=>
{
expect
(
findGlDropdown
().
props
(
'
text
'
)).
toBe
(
projectFromName
);
});
it
(
'
updates the hiddin input value when onClick method is triggered
'
,
async
()
=>
{
const
repoId
=
'
100
'
;
wrapper
.
vm
.
onClick
({
id
:
repoId
});
await
wrapper
.
vm
.
$nextTick
();
expect
(
findHiddenInput
().
attributes
(
'
value
'
)).
toBe
(
repoId
);
});
it
(
'
emits initial `changeTargetProject` event with target project
'
,
()
=>
{
expect
(
wrapper
.
emitted
(
'
changeTargetProject
'
)).
toEqual
([[
projectFromName
]]);
});
it
(
'
emits `changeTargetProject` event when another target project is selected
'
,
async
()
=>
{
const
newTargetProject
=
'
new-from-name
'
;
wrapper
.
vm
.
$emit
(
'
changeTargetProject
'
,
newTargetProject
);
await
wrapper
.
vm
.
$nextTick
();
expect
(
wrapper
.
emitted
(
'
changeTargetProject
'
)[
1
]).
toEqual
([
newTargetProject
]);
});
});
});
spec/frontend/projects/compare/components/revision_card_spec.js
0 → 100644
View file @
66b9aa80
import
{
GlCard
}
from
'
@gitlab/ui
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
RepoDropdown
from
'
~/projects/compare/components/repo_dropdown.vue
'
;
import
RevisionCard
from
'
~/projects/compare/components/revision_card.vue
'
;
import
RevisionDropdown
from
'
~/projects/compare/components/revision_dropdown.vue
'
;
const
defaultProps
=
{
refsProjectPath
:
'
some/refs/path
'
,
revisionText
:
'
Source
'
,
paramsName
:
'
to
'
,
paramsBranch
:
'
master
'
,
};
describe
(
'
RepoDropdown component
'
,
()
=>
{
let
wrapper
;
const
createComponent
=
(
props
=
{})
=>
{
wrapper
=
shallowMount
(
RevisionCard
,
{
propsData
:
{
...
defaultProps
,
...
props
,
},
stubs
:
{
GlCard
,
},
});
};
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
});
beforeEach
(()
=>
{
createComponent
();
});
it
(
'
displays revision text
'
,
()
=>
{
expect
(
wrapper
.
find
(
GlCard
).
text
()).
toContain
(
defaultProps
.
revisionText
);
});
it
(
'
renders RepoDropdown component
'
,
()
=>
{
expect
(
wrapper
.
findAll
(
RepoDropdown
).
exists
()).
toBe
(
true
);
});
it
(
'
renders RevisionDropdown component
'
,
()
=>
{
expect
(
wrapper
.
findAll
(
RevisionDropdown
).
exists
()).
toBe
(
true
);
});
});
spec/frontend/projects/compare/components/revision_dropdown_legacy_spec.js
0 → 100644
View file @
66b9aa80
import
{
GlDropdown
}
from
'
@gitlab/ui
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
AxiosMockAdapter
from
'
axios-mock-adapter
'
;
import
createFlash
from
'
~/flash
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
RevisionDropdown
from
'
~/projects/compare/components/revision_dropdown_legacy.vue
'
;
const
defaultProps
=
{
refsProjectPath
:
'
some/refs/path
'
,
revisionText
:
'
Target
'
,
paramsName
:
'
from
'
,
paramsBranch
:
'
master
'
,
};
jest
.
mock
(
'
~/flash
'
);
describe
(
'
RevisionDropdown component
'
,
()
=>
{
let
wrapper
;
let
axiosMock
;
const
createComponent
=
(
props
=
{})
=>
{
wrapper
=
shallowMount
(
RevisionDropdown
,
{
propsData
:
{
...
defaultProps
,
...
props
,
},
});
};
beforeEach
(()
=>
{
axiosMock
=
new
AxiosMockAdapter
(
axios
);
});
afterEach
(()
=>
{
wrapper
.
destroy
();
axiosMock
.
restore
();
});
const
findGlDropdown
=
()
=>
wrapper
.
find
(
GlDropdown
);
it
(
'
sets hidden input
'
,
()
=>
{
createComponent
();
expect
(
wrapper
.
find
(
'
input[type="hidden"]
'
).
attributes
(
'
value
'
)).
toBe
(
defaultProps
.
paramsBranch
,
);
});
it
(
'
update the branches on success
'
,
async
()
=>
{
const
Branches
=
[
'
branch-1
'
,
'
branch-2
'
];
const
Tags
=
[
'
tag-1
'
,
'
tag-2
'
,
'
tag-3
'
];
axiosMock
.
onGet
(
defaultProps
.
refsProjectPath
).
replyOnce
(
200
,
{
Branches
,
Tags
,
});
createComponent
();
await
axios
.
waitForAll
();
expect
(
wrapper
.
vm
.
branches
).
toEqual
(
Branches
);
expect
(
wrapper
.
vm
.
tags
).
toEqual
(
Tags
);
});
it
(
'
shows flash message on error
'
,
async
()
=>
{
axiosMock
.
onGet
(
'
some/invalid/path
'
).
replyOnce
(
404
);
createComponent
();
await
wrapper
.
vm
.
fetchBranchesAndTags
();
expect
(
createFlash
).
toHaveBeenCalled
();
});
describe
(
'
GlDropdown component
'
,
()
=>
{
it
(
'
renders props
'
,
()
=>
{
createComponent
();
expect
(
wrapper
.
props
()).
toEqual
(
expect
.
objectContaining
(
defaultProps
));
});
it
(
'
display default text
'
,
()
=>
{
createComponent
({
paramsBranch
:
null
,
});
expect
(
findGlDropdown
().
props
(
'
text
'
)).
toBe
(
'
Select branch/tag
'
);
});
it
(
'
display params branch text
'
,
()
=>
{
createComponent
();
expect
(
findGlDropdown
().
props
(
'
text
'
)).
toBe
(
defaultProps
.
paramsBranch
);
});
});
});
spec/frontend/projects/compare/components/revision_dropdown_spec.js
View file @
66b9aa80
...
...
@@ -7,7 +7,6 @@ import RevisionDropdown from '~/projects/compare/components/revision_dropdown.vu
const
defaultProps
=
{
refsProjectPath
:
'
some/refs/path
'
,
revisionText
:
'
Target
'
,
paramsName
:
'
from
'
,
paramsBranch
:
'
master
'
,
};
...
...
@@ -57,7 +56,6 @@ describe('RevisionDropdown component', () => {
createComponent
();
await
axios
.
waitForAll
();
expect
(
wrapper
.
vm
.
branches
).
toEqual
(
Branches
);
expect
(
wrapper
.
vm
.
tags
).
toEqual
(
Tags
);
});
...
...
@@ -71,6 +69,22 @@ describe('RevisionDropdown component', () => {
expect
(
createFlash
).
toHaveBeenCalled
();
});
it
(
'
makes a new request when refsProjectPath is changed
'
,
async
()
=>
{
jest
.
spyOn
(
axios
,
'
get
'
);
const
newRefsProjectPath
=
'
new-selected-project-path
'
;
createComponent
();
wrapper
.
setProps
({
...
defaultProps
,
refsProjectPath
:
newRefsProjectPath
,
});
await
axios
.
waitForAll
();
expect
(
axios
.
get
).
toHaveBeenLastCalledWith
(
newRefsProjectPath
);
});
describe
(
'
GlDropdown component
'
,
()
=>
{
it
(
'
renders props
'
,
()
=>
{
createComponent
();
...
...
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