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
f59fcd60
Commit
f59fcd60
authored
Dec 04, 2020
by
GitLab Bot
Browse files
Options
Browse Files
Download
Plain Diff
Automatic merge of gitlab-org/gitlab master
parents
fa153a3c
26a10be0
Changes
28
Hide whitespace changes
Inline
Side-by-side
Showing
28 changed files
with
580 additions
and
264 deletions
+580
-264
GITALY_SERVER_VERSION
GITALY_SERVER_VERSION
+1
-1
app/assets/javascripts/boards/stores/getters.js
app/assets/javascripts/boards/stores/getters.js
+0
-1
app/assets/javascripts/diffs/components/diff_file.vue
app/assets/javascripts/diffs/components/diff_file.vue
+1
-1
app/assets/javascripts/diffs/components/diff_file_header.vue
app/assets/javascripts/diffs/components/diff_file_header.vue
+1
-1
app/assets/javascripts/diffs/store/actions.js
app/assets/javascripts/diffs/store/actions.js
+1
-1
app/assets/javascripts/diffs/store/utils.js
app/assets/javascripts/diffs/store/utils.js
+1
-1
app/assets/javascripts/diffs/utils/diff_file.js
app/assets/javascripts/diffs/utils/diff_file.js
+1
-1
app/assets/javascripts/notes/components/diff_with_note.vue
app/assets/javascripts/notes/components/diff_with_note.vue
+1
-1
app/assets/javascripts/whats_new/components/app.vue
app/assets/javascripts/whats_new/components/app.vue
+64
-66
app/assets/javascripts/whats_new/components/feature.vue
app/assets/javascripts/whats_new/components/feature.vue
+64
-0
app/assets/javascripts/whats_new/index.js
app/assets/javascripts/whats_new/index.js
+5
-3
app/assets/javascripts/whats_new/store/actions.js
app/assets/javascripts/whats_new/store/actions.js
+2
-1
app/assets/stylesheets/components/whats_new.scss
app/assets/stylesheets/components/whats_new.scss
+26
-0
app/controllers/whats_new_controller.rb
app/controllers/whats_new_controller.rb
+20
-7
app/helpers/whats_new_helper.rb
app/helpers/whats_new_helper.rb
+5
-1
app/models/release_highlight.rb
app/models/release_highlight.rb
+28
-10
app/views/layouts/header/_default.html.haml
app/views/layouts/header/_default.html.haml
+1
-1
ee/app/assets/javascripts/boards/components/toggle_labels.vue
...pp/assets/javascripts/boards/components/toggle_labels.vue
+49
-0
ee/app/assets/javascripts/boards/toggle_labels.js
ee/app/assets/javascripts/boards/toggle_labels.js
+3
-39
ee/spec/frontend/boards/toggle_labels_spec.js
ee/spec/frontend/boards/toggle_labels_spec.js
+58
-0
locale/gitlab.pot
locale/gitlab.pot
+6
-0
spec/frontend/boards/stores/getters_spec.js
spec/frontend/boards/stores/getters_spec.js
+0
-18
spec/frontend/diffs/utils/diff_file_spec.js
spec/frontend/diffs/utils/diff_file_spec.js
+1
-1
spec/frontend/whats_new/components/app_spec.js
spec/frontend/whats_new/components/app_spec.js
+133
-68
spec/frontend/whats_new/store/actions_spec.js
spec/frontend/whats_new/store/actions_spec.js
+17
-0
spec/helpers/whats_new_helper_spec.rb
spec/helpers/whats_new_helper_spec.rb
+12
-2
spec/models/release_highlight_spec.rb
spec/models/release_highlight_spec.rb
+62
-34
spec/requests/whats_new_controller_spec.rb
spec/requests/whats_new_controller_spec.rb
+17
-5
No files found.
GITALY_SERVER_VERSION
View file @
f59fcd60
add5f3dd182c99b4d9e1cf93e45fec1214c0065
9
9fd57cbd0b63d448f9a9555b53f065ee1c11019
9
app/assets/javascripts/boards/stores/getters.js
View file @
f59fcd60
...
@@ -2,7 +2,6 @@ import { find } from 'lodash';
...
@@ -2,7 +2,6 @@ import { find } from 'lodash';
import
{
inactiveId
}
from
'
../constants
'
;
import
{
inactiveId
}
from
'
../constants
'
;
export
default
{
export
default
{
labelToggleState
:
state
=>
(
state
.
isShowingLabels
?
'
on
'
:
'
off
'
),
isSidebarOpen
:
state
=>
state
.
activeId
!==
inactiveId
,
isSidebarOpen
:
state
=>
state
.
activeId
!==
inactiveId
,
isSwimlanesOn
:
()
=>
false
,
isSwimlanesOn
:
()
=>
false
,
getIssueById
:
state
=>
id
=>
{
getIssueById
:
state
=>
id
=>
{
...
...
app/assets/javascripts/diffs/components/diff_file.vue
View file @
f59fcd60
...
@@ -10,7 +10,7 @@ import notesEventHub from '../../notes/event_hub';
...
@@ -10,7 +10,7 @@ import notesEventHub from '../../notes/event_hub';
import
DiffFileHeader
from
'
./diff_file_header.vue
'
;
import
DiffFileHeader
from
'
./diff_file_header.vue
'
;
import
DiffContent
from
'
./diff_content.vue
'
;
import
DiffContent
from
'
./diff_content.vue
'
;
import
{
diffViewerErrors
}
from
'
~/ide/constants
'
;
import
{
diffViewerErrors
}
from
'
~/ide/constants
'
;
import
{
collapsedType
,
isCollapsed
}
from
'
../diff_file
'
;
import
{
collapsedType
,
isCollapsed
}
from
'
../
utils/
diff_file
'
;
import
{
import
{
DIFF_FILE_AUTOMATIC_COLLAPSE
,
DIFF_FILE_AUTOMATIC_COLLAPSE
,
DIFF_FILE_MANUAL_COLLAPSE
,
DIFF_FILE_MANUAL_COLLAPSE
,
...
...
app/assets/javascripts/diffs/components/diff_file_header.vue
View file @
f59fcd60
...
@@ -19,7 +19,7 @@ import { __, s__, sprintf } from '~/locale';
...
@@ -19,7 +19,7 @@ import { __, s__, sprintf } from '~/locale';
import
{
diffViewerModes
}
from
'
~/ide/constants
'
;
import
{
diffViewerModes
}
from
'
~/ide/constants
'
;
import
DiffStats
from
'
./diff_stats.vue
'
;
import
DiffStats
from
'
./diff_stats.vue
'
;
import
{
scrollToElement
}
from
'
~/lib/utils/common_utils
'
;
import
{
scrollToElement
}
from
'
~/lib/utils/common_utils
'
;
import
{
isCollapsed
}
from
'
../diff_file
'
;
import
{
isCollapsed
}
from
'
../
utils/
diff_file
'
;
import
{
DIFF_FILE_HEADER
}
from
'
../i18n
'
;
import
{
DIFF_FILE_HEADER
}
from
'
../i18n
'
;
export
default
{
export
default
{
...
...
app/assets/javascripts/diffs/store/actions.js
View file @
f59fcd60
...
@@ -49,7 +49,7 @@ import {
...
@@ -49,7 +49,7 @@ import {
DIFF_FILE_BY_FILE_COOKIE_NAME
,
DIFF_FILE_BY_FILE_COOKIE_NAME
,
}
from
'
../constants
'
;
}
from
'
../constants
'
;
import
{
diffViewerModes
}
from
'
~/ide/constants
'
;
import
{
diffViewerModes
}
from
'
~/ide/constants
'
;
import
{
isCollapsed
}
from
'
../diff_file
'
;
import
{
isCollapsed
}
from
'
../
utils/
diff_file
'
;
export
const
setBaseConfig
=
({
commit
},
options
)
=>
{
export
const
setBaseConfig
=
({
commit
},
options
)
=>
{
const
{
const
{
...
...
app/assets/javascripts/diffs/store/utils.js
View file @
f59fcd60
...
@@ -16,7 +16,7 @@ import {
...
@@ -16,7 +16,7 @@ import {
SHOW_WHITESPACE
,
SHOW_WHITESPACE
,
NO_SHOW_WHITESPACE
,
NO_SHOW_WHITESPACE
,
}
from
'
../constants
'
;
}
from
'
../constants
'
;
import
{
prepareRawDiffFile
}
from
'
../diff_file
'
;
import
{
prepareRawDiffFile
}
from
'
../
utils/
diff_file
'
;
export
const
isAdded
=
line
=>
[
'
new
'
,
'
new-nonewline
'
].
includes
(
line
.
type
);
export
const
isAdded
=
line
=>
[
'
new
'
,
'
new-nonewline
'
].
includes
(
line
.
type
);
export
const
isRemoved
=
line
=>
[
'
old
'
,
'
old-nonewline
'
].
includes
(
line
.
type
);
export
const
isRemoved
=
line
=>
[
'
old
'
,
'
old-nonewline
'
].
includes
(
line
.
type
);
...
...
app/assets/javascripts/diffs/diff_file.js
→
app/assets/javascripts/diffs/
utils/
diff_file.js
View file @
f59fcd60
...
@@ -3,7 +3,7 @@ import {
...
@@ -3,7 +3,7 @@ import {
DIFF_FILE_DELETED_MODE
,
DIFF_FILE_DELETED_MODE
,
DIFF_FILE_MANUAL_COLLAPSE
,
DIFF_FILE_MANUAL_COLLAPSE
,
DIFF_FILE_AUTOMATIC_COLLAPSE
,
DIFF_FILE_AUTOMATIC_COLLAPSE
,
}
from
'
./constants
'
;
}
from
'
.
.
/constants
'
;
function
fileSymlinkInformation
(
file
,
fileList
)
{
function
fileSymlinkInformation
(
file
,
fileList
)
{
const
duplicates
=
fileList
.
filter
(
iteratedFile
=>
iteratedFile
.
file_hash
===
file
.
file_hash
);
const
duplicates
=
fileList
.
filter
(
iteratedFile
=>
iteratedFile
.
file_hash
===
file
.
file_hash
);
...
...
app/assets/javascripts/notes/components/diff_with_note.vue
View file @
f59fcd60
...
@@ -7,7 +7,7 @@ import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
...
@@ -7,7 +7,7 @@ import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
import
ImageDiffOverlay
from
'
~/diffs/components/image_diff_overlay.vue
'
;
import
ImageDiffOverlay
from
'
~/diffs/components/image_diff_overlay.vue
'
;
import
{
getDiffMode
}
from
'
~/diffs/store/utils
'
;
import
{
getDiffMode
}
from
'
~/diffs/store/utils
'
;
import
{
diffViewerModes
}
from
'
~/ide/constants
'
;
import
{
diffViewerModes
}
from
'
~/ide/constants
'
;
import
{
isCollapsed
}
from
'
../../diffs/diff_file
'
;
import
{
isCollapsed
}
from
'
../../diffs/
utils/
diff_file
'
;
const
FIRST_CHAR_REGEX
=
/^
(\+
|-|
)
/
;
const
FIRST_CHAR_REGEX
=
/^
(\+
|-|
)
/
;
...
...
app/assets/javascripts/whats_new/components/app.vue
View file @
f59fcd60
...
@@ -2,13 +2,15 @@
...
@@ -2,13 +2,15 @@
import
{
mapState
,
mapActions
}
from
'
vuex
'
;
import
{
mapState
,
mapActions
}
from
'
vuex
'
;
import
{
import
{
GlDrawer
,
GlDrawer
,
GlBadge
,
GlIcon
,
GlLink
,
GlInfiniteScroll
,
GlInfiniteScroll
,
GlResizeObserverDirective
,
GlResizeObserverDirective
,
GlTabs
,
GlTab
,
GlBadge
,
GlLoadingIcon
,
}
from
'
@gitlab/ui
'
;
}
from
'
@gitlab/ui
'
;
import
SkeletonLoader
from
'
./skeleton_loader.vue
'
;
import
SkeletonLoader
from
'
./skeleton_loader.vue
'
;
import
Feature
from
'
./feature.vue
'
;
import
Tracking
from
'
~/tracking
'
;
import
Tracking
from
'
~/tracking
'
;
import
{
getDrawerBodyHeight
}
from
'
../utils/get_drawer_body_height
'
;
import
{
getDrawerBodyHeight
}
from
'
../utils/get_drawer_body_height
'
;
...
@@ -17,11 +19,13 @@ const trackingMixin = Tracking.mixin();
...
@@ -17,11 +19,13 @@ const trackingMixin = Tracking.mixin();
export
default
{
export
default
{
components
:
{
components
:
{
GlDrawer
,
GlDrawer
,
GlBadge
,
GlIcon
,
GlLink
,
GlInfiniteScroll
,
GlInfiniteScroll
,
GlTabs
,
GlTab
,
SkeletonLoader
,
SkeletonLoader
,
Feature
,
GlBadge
,
GlLoadingIcon
,
},
},
directives
:
{
directives
:
{
GlResizeObserver
:
GlResizeObserverDirective
,
GlResizeObserver
:
GlResizeObserverDirective
,
...
@@ -31,11 +35,19 @@ export default {
...
@@ -31,11 +35,19 @@ export default {
storageKey
:
{
storageKey
:
{
type
:
String
,
type
:
String
,
required
:
true
,
required
:
true
,
default
:
null
,
},
versions
:
{
type
:
Array
,
required
:
true
,
},
gitlabDotCom
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
},
},
},
computed
:
{
computed
:
{
...
mapState
([
'
open
'
,
'
features
'
,
'
pageInfo
'
,
'
drawerBodyHeight
'
]),
...
mapState
([
'
open
'
,
'
features
'
,
'
pageInfo
'
,
'
drawerBodyHeight
'
,
'
fetching
'
]),
},
},
mounted
()
{
mounted
()
{
this
.
openDrawer
(
this
.
storageKey
);
this
.
openDrawer
(
this
.
storageKey
);
...
@@ -49,14 +61,25 @@ export default {
...
@@ -49,14 +61,25 @@ export default {
methods
:
{
methods
:
{
...
mapActions
([
'
openDrawer
'
,
'
closeDrawer
'
,
'
fetchItems
'
,
'
setDrawerBodyHeight
'
]),
...
mapActions
([
'
openDrawer
'
,
'
closeDrawer
'
,
'
fetchItems
'
,
'
setDrawerBodyHeight
'
]),
bottomReached
()
{
bottomReached
()
{
if
(
this
.
pageInfo
.
nextPage
)
{
const
page
=
this
.
pageInfo
.
nextPage
;
this
.
fetchItems
(
this
.
pageInfo
.
nextPage
);
if
(
page
)
{
this
.
fetchItems
({
page
});
}
}
},
},
handleResize
()
{
handleResize
()
{
const
height
=
getDrawerBodyHeight
(
this
.
$refs
.
drawer
.
$el
);
const
height
=
getDrawerBodyHeight
(
this
.
$refs
.
drawer
.
$el
);
this
.
setDrawerBodyHeight
(
height
);
this
.
setDrawerBodyHeight
(
height
);
},
},
featuresForVersion
(
version
)
{
return
this
.
features
.
filter
(
feature
=>
{
return
feature
.
release
===
parseFloat
(
version
);
});
},
fetchVersion
(
version
)
{
if
(
this
.
featuresForVersion
(
version
).
length
===
0
)
{
this
.
fetchItems
({
version
});
}
},
},
},
};
};
</
script
>
</
script
>
...
@@ -73,64 +96,39 @@ export default {
...
@@ -73,64 +96,39 @@ export default {
<template
#header
>
<template
#header
>
<h4
class=
"page-title gl-my-2"
>
{{
__
(
"
What's new at GitLab
"
)
}}
</h4>
<h4
class=
"page-title gl-my-2"
>
{{
__
(
"
What's new at GitLab
"
)
}}
</h4>
</
template
>
</
template
>
<gl-infinite-scroll
<
template
v-if=
"features.length"
>
v-if=
"features.length"
<gl-infinite-scroll
:fetched-items=
"features.length"
v-if=
"gitlabDotCom"
:max-list-height=
"drawerBodyHeight"
:fetched-items=
"features.length"
class=
"gl-p-0"
:max-list-height=
"drawerBodyHeight"
@
bottomReached=
"bottomReached"
class=
"gl-p-0"
>
@
bottomReached=
"bottomReached"
<
template
#items
>
>
<div
<template
#items
>
v-for=
"feature in features"
<feature
v-for=
"feature in features"
:key=
"feature.title"
:feature=
"feature"
/>
:key=
"feature.title"
</
template
>
class=
"gl-pb-7 gl-pt-5 gl-px-5 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100"
</gl-infinite-scroll>
<gl-tabs
v-else
:style=
"{ height: `${drawerBodyHeight}px` }"
class=
"gl-p-0"
>
<gl-tab
v-for=
"(version, index) in versions"
:key=
"version"
@
click=
"fetchVersion(version)"
>
>
<gl-link
<
template
#title
>
:href=
"feature.url"
<span>
{{
version
}}
</span>
target=
"_blank"
<gl-badge
v-if=
"index === 0"
>
{{
__
(
'
Your Version
'
)
}}
</gl-badge>
class=
"whats-new-item-title-link"
</
template
>
data-track-event=
"click_whats_new_item"
<gl-loading-icon
v-if=
"fetching"
size=
"lg"
class=
"text-center"
/>
:data-track-label=
"feature.title"
<
template
v-else
>
:data-track-property=
"feature.url"
<feature
>
v-for=
"feature in featuresForVersion(version)"
<h5
class=
"gl-font-lg"
>
{{
feature
.
title
}}
</h5>
:key=
"feature.title"
</gl-link>
:feature=
"feature"
<div
v-if=
"feature.packages"
class=
"gl-mb-3"
>
<gl-badge
v-for=
"package_name in feature.packages"
:key=
"package_name"
size=
"sm"
class=
"whats-new-item-badge gl-mr-2"
>
<gl-icon
name=
"license"
/>
{{
package_name
}}
</gl-badge>
</div>
<gl-link
:href=
"feature.url"
target=
"_blank"
data-track-event=
"click_whats_new_item"
:data-track-label=
"feature.title"
:data-track-property=
"feature.url"
>
<img
:alt=
"feature.title"
:src=
"feature.image_url"
class=
"img-thumbnail gl-px-8 gl-py-3 whats-new-item-image"
/>
/>
</gl-link>
</
template
>
<p
class=
"gl-pt-3"
>
{{
feature
.
body
}}
</p>
</gl-tab>
<gl-link
</gl-tabs>
:href=
"feature.url"
</template>
target=
"_blank"
data-track-event=
"click_whats_new_item"
:data-track-label=
"feature.title"
:data-track-property=
"feature.url"
>
{{
__
(
'
Learn more
'
)
}}
</gl-link
>
</div>
</
template
>
</gl-infinite-scroll>
<div
v-else
class=
"gl-mt-5"
>
<div
v-else
class=
"gl-mt-5"
>
<skeleton-loader
/>
<skeleton-loader
/>
<skeleton-loader
/>
<skeleton-loader
/>
...
...
app/assets/javascripts/whats_new/components/feature.vue
0 → 100644
View file @
f59fcd60
<
script
>
import
{
GlBadge
,
GlIcon
,
GlLink
}
from
'
@gitlab/ui
'
;
export
default
{
components
:
{
GlBadge
,
GlIcon
,
GlLink
,
},
props
:
{
feature
:
{
type
:
Object
,
required
:
true
,
},
},
};
</
script
>
<
template
>
<div
class=
"gl-pb-7 gl-pt-5 gl-px-5 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100"
>
<gl-link
:href=
"feature.url"
target=
"_blank"
class=
"whats-new-item-title-link"
data-track-event=
"click_whats_new_item"
:data-track-label=
"feature.title"
:data-track-property=
"feature.url"
>
<h5
class=
"gl-font-lg"
data-test-id=
"feature-title"
>
{{
feature
.
title
}}
</h5>
</gl-link>
<div
v-if=
"feature.packages"
class=
"gl-mb-3"
>
<gl-badge
v-for=
"packageName in feature.packages"
:key=
"packageName"
size=
"sm"
class=
"whats-new-item-badge gl-mr-2"
>
<gl-icon
name=
"license"
/>
{{
packageName
}}
</gl-badge>
</div>
<gl-link
:href=
"feature.url"
target=
"_blank"
data-track-event=
"click_whats_new_item"
:data-track-label=
"feature.title"
:data-track-property=
"feature.url"
>
<img
:alt=
"feature.title"
:src=
"feature.image_url"
class=
"img-thumbnail gl-px-8 gl-py-3 whats-new-item-image"
/>
</gl-link>
<p
class=
"gl-pt-3"
>
{{
feature
.
body
}}
</p>
<gl-link
:href=
"feature.url"
target=
"_blank"
data-track-event=
"click_whats_new_item"
:data-track-label=
"feature.title"
:data-track-property=
"feature.url"
>
{{
__
(
'
Learn more
'
)
}}
</gl-link
>
</div>
</
template
>
app/assets/javascripts/whats_new/index.js
View file @
f59fcd60
...
@@ -10,8 +10,6 @@ export default el => {
...
@@ -10,8 +10,6 @@ export default el => {
if
(
whatsNewApp
)
{
if
(
whatsNewApp
)
{
store
.
dispatch
(
'
openDrawer
'
);
store
.
dispatch
(
'
openDrawer
'
);
}
else
{
}
else
{
const
storageKey
=
getStorageKey
(
el
);
whatsNewApp
=
new
Vue
({
whatsNewApp
=
new
Vue
({
el
,
el
,
store
,
store
,
...
@@ -28,7 +26,11 @@ export default el => {
...
@@ -28,7 +26,11 @@ export default el => {
},
},
render
(
createElement
)
{
render
(
createElement
)
{
return
createElement
(
'
app
'
,
{
return
createElement
(
'
app
'
,
{
props
:
{
storageKey
},
props
:
{
storageKey
:
getStorageKey
(
el
),
versions
:
JSON
.
parse
(
el
.
getAttribute
(
'
data-versions
'
)),
gitlabDotCom
:
el
.
getAttribute
(
'
data-gitlab-dot-com
'
),
},
});
});
},
},
});
});
...
...
app/assets/javascripts/whats_new/store/actions.js
View file @
f59fcd60
...
@@ -13,7 +13,7 @@ export default {
...
@@ -13,7 +13,7 @@ export default {
localStorage
.
setItem
(
storageKey
,
JSON
.
stringify
(
false
));
localStorage
.
setItem
(
storageKey
,
JSON
.
stringify
(
false
));
}
}
},
},
fetchItems
({
commit
,
state
},
page
)
{
fetchItems
({
commit
,
state
},
{
page
,
version
}
=
{
page
:
null
,
version
:
null
}
)
{
if
(
state
.
fetching
)
{
if
(
state
.
fetching
)
{
return
false
;
return
false
;
}
}
...
@@ -24,6 +24,7 @@ export default {
...
@@ -24,6 +24,7 @@ export default {
.
get
(
'
/-/whats_new
'
,
{
.
get
(
'
/-/whats_new
'
,
{
params
:
{
params
:
{
page
,
page
,
version
,
},
},
})
})
.
then
(({
data
,
headers
})
=>
{
.
then
(({
data
,
headers
})
=>
{
...
...
app/assets/stylesheets/components/whats_new.scss
View file @
f59fcd60
...
@@ -6,6 +6,32 @@
...
@@ -6,6 +6,32 @@
.gl-infinite-scroll-legend
{
.gl-infinite-scroll-legend
{
@include
gl-display-none
;
@include
gl-display-none
;
}
}
.gl-tabs
{
@include
gl-overflow-y-auto
;
}
.gl-tabs-nav
{
flex-wrap
:
nowrap
;
overflow-x
:
scroll
;
align-items
:
stretch
;
.nav-item
{
@include
gl-flex-shrink-0
;
a
{
@include
gl-h-full
;
line-height
:
1
.5
;
}
}
}
.gl-spinner-container
{
@include
gl-w-full
;
@include
gl-absolute
;
top
:
50%
;
transform
:
translateY
(
-50%
);
}
}
}
.with-performance-bar
.whats-new-drawer
{
.with-performance-bar
.whats-new-drawer
{
...
...
app/controllers/whats_new_controller.rb
View file @
f59fcd60
# frozen_string_literal: true
# frozen_string_literal: true
class
WhatsNewController
<
ApplicationController
class
WhatsNewController
<
ApplicationController
include
Gitlab
::
Utils
::
StrongMemoize
skip_before_action
:authenticate_user!
skip_before_action
:authenticate_user!
before_action
:check_feature_flag
,
:check_valid_page_param
,
:set_pagination_headers
before_action
:check_feature_flag
before_action
:check_valid_page_param
,
:set_pagination_headers
,
unless:
->
{
has_version_param?
}
feature_category
:navigation
feature_category
:navigation
def
index
def
index
respond_to
do
|
format
|
respond_to
do
|
format
|
format
.
js
do
format
.
js
do
render
json:
most_recen
t_items
render
json:
highligh
t_items
end
end
end
end
end
end
...
@@ -29,15 +32,25 @@ class WhatsNewController < ApplicationController
...
@@ -29,15 +32,25 @@ class WhatsNewController < ApplicationController
params
[
:page
]
&
.
to_i
||
1
params
[
:page
]
&
.
to_i
||
1
end
end
def
most_recent
def
highlights
@most_recent
||=
ReleaseHighlight
.
paginated
(
page:
current_page
)
strong_memoize
(
:highlights
)
do
if
has_version_param?
ReleaseHighlight
.
for_version
(
version:
params
[
:version
])
else
ReleaseHighlight
.
paginated
(
page:
current_page
)
end
end
end
end
def
most_recen
t_items
def
highligh
t_items
most_recent
[
:items
]
.
map
{
|
item
|
Gitlab
::
WhatsNew
::
ItemPresenter
.
present
(
item
)
}
highlights
.
map
{
|
item
|
Gitlab
::
WhatsNew
::
ItemPresenter
.
present
(
item
)
}
end
end
def
set_pagination_headers
def
set_pagination_headers
response
.
set_header
(
'X-Next-Page'
,
most_recent
[
:next_page
])
response
.
set_header
(
'X-Next-Page'
,
highlights
.
next_page
)
end
def
has_version_param?
params
[
:version
].
present?
end
end
end
end
app/helpers/whats_new_helper.rb
View file @
f59fcd60
...
@@ -6,10 +6,14 @@ module WhatsNewHelper
...
@@ -6,10 +6,14 @@ module WhatsNewHelper
end
end
def
whats_new_storage_key
def
whats_new_storage_key
most_recent_version
=
ReleaseHighlight
.
most_recent_version
most_recent_version
=
ReleaseHighlight
.
versions
&
.
first
return
unless
most_recent_version
return
unless
most_recent_version
[
'display-whats-new-notification'
,
most_recent_version
].
join
(
'-'
)
[
'display-whats-new-notification'
,
most_recent_version
].
join
(
'-'
)
end
end
def
whats_new_versions
ReleaseHighlight
.
versions
end
end
end
app/models/release_highlight.rb
View file @
f59fcd60
...
@@ -3,6 +3,17 @@
...
@@ -3,6 +3,17 @@
class
ReleaseHighlight
class
ReleaseHighlight
CACHE_DURATION
=
1
.
hour
CACHE_DURATION
=
1
.
hour
FILES_PATH
=
Rails
.
root
.
join
(
'data'
,
'whats_new'
,
'*.yml'
)
FILES_PATH
=
Rails
.
root
.
join
(
'data'
,
'whats_new'
,
'*.yml'
)
RELEASE_VERSIONS_IN_A_YEAR
=
12
def
self
.
for_version
(
version
:)
index
=
self
.
versions
.
index
(
version
)
return
if
index
.
nil?
page
=
index
+
1
self
.
paginated
(
page:
page
)
end
def
self
.
paginated
(
page:
1
)
def
self
.
paginated
(
page:
1
)
Rails
.
cache
.
fetch
(
cache_key
(
page
),
expires_in:
CACHE_DURATION
)
do
Rails
.
cache
.
fetch
(
cache_key
(
page
),
expires_in:
CACHE_DURATION
)
do
...
@@ -10,10 +21,7 @@ class ReleaseHighlight
...
@@ -10,10 +21,7 @@ class ReleaseHighlight
next
if
items
.
nil?
next
if
items
.
nil?
{
QueryResult
.
new
(
items:
items
,
next_page:
next_page
(
current_page:
page
))
items:
items
,
next_page:
next_page
(
current_page:
page
)
}
end
end
end
end
...
@@ -53,15 +61,25 @@ class ReleaseHighlight
...
@@ -53,15 +61,25 @@ class ReleaseHighlight
next_page
if
self
.
file_paths
[
next_index
]
next_page
if
self
.
file_paths
[
next_index
]
end
end
def
self
.
most_recent_
version
def
self
.
most_recent_
item_count
Gitlab
::
ProcessMemoryCache
.
cache_backend
.
fetch
(
'release_highlight:re
lease_version
'
,
expires_in:
CACHE_DURATION
)
do
Gitlab
::
ProcessMemoryCache
.
cache_backend
.
fetch
(
'release_highlight:re
cent_item_count
'
,
expires_in:
CACHE_DURATION
)
do
self
.
paginated
&
.
[
](
:items
)
&
.
first
&
.
[
](
'release'
)
self
.
paginated
&
.
items
&
.
count
end
end
end
end
def
self
.
most_recent_item_count
def
self
.
versions
Gitlab
::
ProcessMemoryCache
.
cache_backend
.
fetch
(
'release_highlight:recent_item_count'
,
expires_in:
CACHE_DURATION
)
do
Gitlab
::
ProcessMemoryCache
.
cache_backend
.
fetch
(
'release_highlight:versions'
,
expires_in:
CACHE_DURATION
)
do
self
.
paginated
&
.
[
](
:items
)
&
.
count
versions
=
self
.
file_paths
.
first
(
RELEASE_VERSIONS_IN_A_YEAR
).
map
do
|
path
|
/\d*\_(\d*\_\d*)\.yml$/
.
match
(
path
).
captures
[
0
].
gsub
(
/0(?=\d)/
,
""
).
tr
(
"_"
,
"."
)
end
versions
.
uniq
end
end
end
end
QueryResult
=
Struct
.
new
(
:items
,
:next_page
,
keyword_init:
true
)
do
include
Enumerable
delegate
:each
,
to: :items
end
end
end
app/views/layouts/header/_default.html.haml
View file @
f59fcd60
...
@@ -102,7 +102,7 @@
...
@@ -102,7 +102,7 @@
=
sprite_icon
(
'close'
,
size:
12
,
css_class:
'close-icon js-navbar-toggle-left'
)
=
sprite_icon
(
'close'
,
size:
12
,
css_class:
'close-icon js-navbar-toggle-left'
)
-
if
::
Feature
.
enabled?
(
:whats_new_drawer
,
current_user
)
-
if
::
Feature
.
enabled?
(
:whats_new_drawer
,
current_user
)
#whats-new-app
{
data:
{
storage_key:
whats_new_storage_key
}
}
#whats-new-app
{
data:
{
storage_key:
whats_new_storage_key
,
versions:
whats_new_versions
,
gitlab_dot_com:
Gitlab
.
dev_env_org_or_com?
}
}
-
if
can?
(
current_user
,
:update_user_status
,
current_user
)
-
if
can?
(
current_user
,
:update_user_status
,
current_user
)
.js-set-status-modal-wrapper
{
data:
user_status_data
}
.js-set-status-modal-wrapper
{
data:
user_status_data
}
ee/app/assets/javascripts/boards/components/toggle_labels.vue
0 → 100644
View file @
f59fcd60
<
script
>
import
{
mapState
,
mapActions
}
from
'
vuex
'
;
import
{
GlToggle
}
from
'
@gitlab/ui
'
;
import
{
parseBoolean
}
from
'
~/lib/utils/common_utils
'
;
import
LocalStorageSync
from
'
~/vue_shared/components/local_storage_sync.vue
'
;
export
default
{
components
:
{
GlToggle
,
LocalStorageSync
,
},
computed
:
{
...
mapState
([
'
isShowingLabels
'
]),
trackProperty
()
{
return
this
.
isShowingLabels
?
'
on
'
:
'
off
'
;
},
},
methods
:
{
...
mapActions
([
'
setShowLabels
'
]),
onToggle
(
val
)
{
this
.
setShowLabels
(
val
);
},
onStorageUpdate
(
val
)
{
this
.
setShowLabels
(
parseBoolean
(
val
));
},
},
};
</
script
>
<
template
>
<div
class=
"board-labels-toggle-wrapper gl-display-flex gl-align-items-center gl-ml-3"
>
<local-storage-sync
storage-key=
"gl-show-board-labels"
:value=
"JSON.stringify(isShowingLabels)"
@
input=
"onStorageUpdate"
/>
<gl-toggle
:value=
"isShowingLabels"
:label=
"__('Show labels')"
:data-track-property=
"trackProperty"
data-track-event=
"toggle"
data-track-label=
"show_labels"
label-position=
"left"
aria-describedby=
"board-labels-toggle-text"
data-qa-selector=
"show_labels_toggle"
@
change=
"onToggle"
/>
</div>
</
template
>
ee/app/assets/javascripts/boards/toggle_labels.js
View file @
f59fcd60
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
{
mapState
,
mapGetters
,
mapActions
}
from
'
vuex
'
;
import
{
GlToggle
}
from
'
@gitlab/ui
'
;
import
Tracking
from
'
~/tracking
'
;
import
store
from
'
~/boards/stores
'
;
import
store
from
'
~/boards/stores
'
;
import
LocalStorageSync
from
'
~/vue_shared/components/local_storage_sync
.vue
'
;
import
ToggleLabels
from
'
./components/toggle_labels
.vue
'
;
export
default
()
=>
export
default
()
=>
new
Vue
({
new
Vue
({
el
:
document
.
getElementById
(
'
js-board-labels-toggle
'
),
el
:
document
.
getElementById
(
'
js-board-labels-toggle
'
),
components
:
{
components
:
{
GlToggle
,
ToggleLabels
,
LocalStorageSync
,
},
},
store
,
store
,
computed
:
{
render
:
createElement
=>
createElement
(
'
toggle-labels
'
),
...
mapState
([
'
isShowingLabels
'
]),
...
mapGetters
([
'
labelToggleState
'
]),
},
methods
:
{
...
mapActions
([
'
setShowLabels
'
]),
onToggle
(
val
)
{
this
.
setShowLabels
(
val
);
Tracking
.
event
(
document
.
body
.
dataset
.
page
,
'
toggle
'
,
{
label
:
'
show_labels
'
,
property
:
this
.
labelToggleState
,
});
},
onStorageUpdate
(
val
)
{
this
.
setShowLabels
(
JSON
.
parse
(
val
));
},
},
template
:
`
<div class="board-labels-toggle-wrapper d-flex align-items-center gl-ml-3">
<local-storage-sync storage-key="gl-show-board-labels" :value="JSON.stringify(isShowingLabels)" @input="onStorageUpdate" />
<gl-toggle
:value="isShowingLabels"
label="Show labels"
label-position="left"
aria-describedby="board-labels-toggle-text"
data-qa-selector="show_labels_toggle"
@change="onToggle"
/>
</div>
`
,
});
});
ee/spec/frontend/boards/toggle_labels_spec.js
0 → 100644
View file @
f59fcd60
import
{
GlToggle
}
from
'
@gitlab/ui
'
;
import
{
shallowMount
,
createLocalVue
}
from
'
@vue/test-utils
'
;
import
Vuex
from
'
vuex
'
;
import
ToggleLabels
from
'
ee/boards/components/toggle_labels
'
;
import
LocalStorageSync
from
'
~/vue_shared/components/local_storage_sync.vue
'
;
const
localVue
=
createLocalVue
();
localVue
.
use
(
Vuex
);
describe
(
'
ToggleLabels component
'
,
()
=>
{
let
wrapper
;
let
setShowLabels
;
function
createComponent
(
state
=
{})
{
setShowLabels
=
jest
.
fn
();
return
shallowMount
(
ToggleLabels
,
{
localVue
,
store
:
new
Vuex
.
Store
({
state
:
{
isShowingLabels
:
true
,
...
state
,
},
actions
:
{
setShowLabels
,
},
}),
stubs
:
{
LocalStorageSync
,
},
});
}
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
});
it
(
'
onStorageUpdate parses empty value as false
'
,
async
()
=>
{
wrapper
=
createComponent
();
const
localStorageSync
=
wrapper
.
find
(
LocalStorageSync
);
localStorageSync
.
vm
.
$emit
(
'
input
'
,
''
);
await
wrapper
.
vm
.
$nextTick
();
expect
(
setShowLabels
).
toHaveBeenCalledWith
(
expect
.
any
(
Object
),
false
);
});
it
(
'
sets GlToggle value from store.isShowingLabels
'
,
()
=>
{
wrapper
=
createComponent
({
isShowingLabels
:
true
});
expect
(
wrapper
.
find
(
GlToggle
).
props
(
'
value
'
)).
toEqual
(
true
);
wrapper
=
createComponent
({
isShowingLabels
:
false
});
expect
(
wrapper
.
find
(
GlToggle
).
props
(
'
value
'
)).
toEqual
(
false
);
});
});
locale/gitlab.pot
View file @
f59fcd60
...
@@ -25177,6 +25177,9 @@ msgstr ""
...
@@ -25177,6 +25177,9 @@ msgstr ""
msgid "Show file contents"
msgid "Show file contents"
msgstr ""
msgstr ""
msgid "Show labels"
msgstr ""
msgid "Show latest version"
msgid "Show latest version"
msgstr ""
msgstr ""
...
@@ -31714,6 +31717,9 @@ msgstr ""
...
@@ -31714,6 +31717,9 @@ msgstr ""
msgid "Your U2F device was registered!"
msgid "Your U2F device was registered!"
msgstr ""
msgstr ""
msgid "Your Version"
msgstr ""
msgid "Your WebAuthn device did not send a valid JSON response."
msgid "Your WebAuthn device did not send a valid JSON response."
msgstr ""
msgstr ""
...
...
spec/frontend/boards/stores/getters_spec.js
View file @
f59fcd60
...
@@ -10,24 +10,6 @@ import {
...
@@ -10,24 +10,6 @@ import {
}
from
'
../mock_data
'
;
}
from
'
../mock_data
'
;
describe
(
'
Boards - Getters
'
,
()
=>
{
describe
(
'
Boards - Getters
'
,
()
=>
{
describe
(
'
labelToggleState
'
,
()
=>
{
it
(
'
should return "on" when isShowingLabels is true
'
,
()
=>
{
const
state
=
{
isShowingLabels
:
true
,
};
expect
(
getters
.
labelToggleState
(
state
)).
toBe
(
'
on
'
);
});
it
(
'
should return "off" when isShowingLabels is false
'
,
()
=>
{
const
state
=
{
isShowingLabels
:
false
,
};
expect
(
getters
.
labelToggleState
(
state
)).
toBe
(
'
off
'
);
});
});
describe
(
'
isSidebarOpen
'
,
()
=>
{
describe
(
'
isSidebarOpen
'
,
()
=>
{
it
(
'
returns true when activeId is not equal to 0
'
,
()
=>
{
it
(
'
returns true when activeId is not equal to 0
'
,
()
=>
{
const
state
=
{
const
state
=
{
...
...
spec/frontend/diffs/diff_file_spec.js
→
spec/frontend/diffs/
utils/
diff_file_spec.js
View file @
f59fcd60
import
{
prepareRawDiffFile
}
from
'
~/diffs/diff_file
'
;
import
{
prepareRawDiffFile
}
from
'
~/diffs/
utils/
diff_file
'
;
const
DIFF_FILES
=
[
const
DIFF_FILES
=
[
{
{
...
...
spec/frontend/whats_new/components/app_spec.js
View file @
f59fcd60
import
{
createLocalVue
,
mount
}
from
'
@vue/test-utils
'
;
import
{
createLocalVue
,
mount
}
from
'
@vue/test-utils
'
;
import
Vuex
from
'
vuex
'
;
import
Vuex
from
'
vuex
'
;
import
{
GlDrawer
,
GlInfiniteScroll
}
from
'
@gitlab/ui
'
;
import
{
GlDrawer
,
GlInfiniteScroll
,
GlTabs
}
from
'
@gitlab/ui
'
;
import
{
mockTracking
,
unmockTracking
,
triggerEvent
}
from
'
helpers/tracking_helper
'
;
import
{
mockTracking
,
unmockTracking
,
triggerEvent
}
from
'
helpers/tracking_helper
'
;
import
{
createMockDirective
,
getBinding
}
from
'
helpers/vue_mock_directive
'
;
import
{
createMockDirective
,
getBinding
}
from
'
helpers/vue_mock_directive
'
;
import
App
from
'
~/whats_new/components/app.vue
'
;
import
App
from
'
~/whats_new/components/app.vue
'
;
...
@@ -16,12 +16,18 @@ const localVue = createLocalVue();
...
@@ -16,12 +16,18 @@ const localVue = createLocalVue();
localVue
.
use
(
Vuex
);
localVue
.
use
(
Vuex
);
describe
(
'
App
'
,
()
=>
{
describe
(
'
App
'
,
()
=>
{
const
propsData
=
{
storageKey
:
'
storage-key
'
};
let
wrapper
;
let
wrapper
;
let
store
;
let
store
;
let
actions
;
let
actions
;
let
state
;
let
state
;
let
trackingSpy
;
let
trackingSpy
;
let
gitlabDotCom
=
true
;
const
buildProps
=
()
=>
({
storageKey
:
'
storage-key
'
,
versions
:
[
'
3.11
'
,
'
3.10
'
],
gitlabDotCom
,
});
const
buildWrapper
=
()
=>
{
const
buildWrapper
=
()
=>
{
actions
=
{
actions
=
{
...
@@ -45,7 +51,7 @@ describe('App', () => {
...
@@ -45,7 +51,7 @@ describe('App', () => {
wrapper
=
mount
(
App
,
{
wrapper
=
mount
(
App
,
{
localVue
,
localVue
,
store
,
store
,
propsData
,
propsData
:
buildProps
()
,
directives
:
{
directives
:
{
GlResizeObserver
:
createMockDirective
(),
GlResizeObserver
:
createMockDirective
(),
},
},
...
@@ -53,112 +59,171 @@ describe('App', () => {
...
@@ -53,112 +59,171 @@ describe('App', () => {
};
};
const
findInfiniteScroll
=
()
=>
wrapper
.
find
(
GlInfiniteScroll
);
const
findInfiniteScroll
=
()
=>
wrapper
.
find
(
GlInfiniteScroll
);
const
emitBottomReached
=
()
=>
findInfiniteScroll
().
vm
.
$emit
(
'
bottomReached
'
);
beforeEach
(
async
()
=>
{
const
setup
=
async
()
=>
{
document
.
body
.
dataset
.
page
=
'
test-page
'
;
document
.
body
.
dataset
.
page
=
'
test-page
'
;
document
.
body
.
dataset
.
namespaceId
=
'
namespace-840
'
;
document
.
body
.
dataset
.
namespaceId
=
'
namespace-840
'
;
trackingSpy
=
mockTracking
(
'
_category_
'
,
null
,
jest
.
spyOn
);
trackingSpy
=
mockTracking
(
'
_category_
'
,
null
,
jest
.
spyOn
);
buildWrapper
();
buildWrapper
();
wrapper
.
vm
.
$store
.
state
.
features
=
[{
title
:
'
Whats New Drawer
'
,
url
:
'
www.url.com
'
}];
wrapper
.
vm
.
$store
.
state
.
features
=
[
{
title
:
'
Whats New Drawer
'
,
url
:
'
www.url.com
'
,
release
:
3.11
},
];
wrapper
.
vm
.
$store
.
state
.
drawerBodyHeight
=
MOCK_DRAWER_BODY_HEIGHT
;
wrapper
.
vm
.
$store
.
state
.
drawerBodyHeight
=
MOCK_DRAWER_BODY_HEIGHT
;
await
wrapper
.
vm
.
$nextTick
();
await
wrapper
.
vm
.
$nextTick
();
}
)
;
};
afterEach
(()
=>
{
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
.
destroy
();
unmockTracking
();
unmockTracking
();
});
});
const
getDrawer
=
()
=>
wrapper
.
find
(
GlDrawer
);
describe
(
'
gitlab.com
'
,
()
=>
{
beforeEach
(()
=>
{
setup
();
});
it
(
'
contains a drawer
'
,
()
=>
{
const
getDrawer
=
()
=>
wrapper
.
find
(
GlDrawer
);
expect
(
getDrawer
().
exists
()).
toBe
(
true
);
});
it
(
'
dispatches openDrawer and tracking calls when mounted
'
,
()
=>
{
it
(
'
contains a drawer
'
,
()
=>
{
expect
(
actions
.
openDrawer
).
toHaveBeenCalledWith
(
expect
.
any
(
Object
),
'
storage-key
'
);
expect
(
getDrawer
().
exists
()).
toBe
(
true
);
expect
(
trackingSpy
).
toHaveBeenCalledWith
(
undefined
,
'
click_whats_new_drawer
'
,
{
label
:
'
namespace_id
'
,
value
:
'
namespace-840
'
,
});
});
});
it
(
'
dispatches closeDrawer when clicking close
'
,
()
=>
{
it
(
'
dispatches openDrawer and tracking calls when mounted
'
,
()
=>
{
getDrawer
().
vm
.
$emit
(
'
close
'
);
expect
(
actions
.
openDrawer
).
toHaveBeenCalledWith
(
expect
.
any
(
Object
),
'
storage-key
'
);
expect
(
actions
.
closeDrawer
).
toHaveBeenCalled
();
expect
(
trackingSpy
).
toHaveBeenCalledWith
(
undefined
,
'
click_whats_new_drawer
'
,
{
});
label
:
'
namespace_id
'
,
value
:
'
namespace-840
'
,
});
});
it
.
each
([
true
,
false
])(
'
passes open property
'
,
async
openState
=>
{
it
(
'
dispatches closeDrawer when clicking close
'
,
()
=>
{
wrapper
.
vm
.
$store
.
state
.
open
=
openState
;
getDrawer
().
vm
.
$emit
(
'
close
'
);
expect
(
actions
.
closeDrawer
).
toHaveBeenCalled
();
});
await
wrapper
.
vm
.
$nextTick
();
it
.
each
([
true
,
false
])(
'
passes open property
'
,
async
openState
=>
{
wrapper
.
vm
.
$store
.
state
.
open
=
openState
;
expect
(
getDrawer
().
props
(
'
open
'
)).
toBe
(
openState
);
await
wrapper
.
vm
.
$nextTick
();
});
it
(
'
renders features when provided via ajax
'
,
()
=>
{
expect
(
getDrawer
().
props
(
'
open
'
)).
toBe
(
openState
);
expect
(
actions
.
fetchItems
).
toHaveBeenCalled
();
});
expect
(
wrapper
.
find
(
'
h5
'
).
text
()).
toBe
(
'
Whats New Drawer
'
);
});
it
(
'
send an event when feature item is clicked
'
,
()
=>
{
it
(
'
renders features when provided via ajax
'
,
()
=>
{
trackingSpy
=
mockTracking
(
'
_category_
'
,
wrapper
.
element
,
jest
.
spyOn
);
expect
(
actions
.
fetchItems
).
toHaveBeenCalled
();
expect
(
wrapper
.
find
(
'
[data-test-id="feature-title"]
'
).
text
()).
toBe
(
'
Whats New Drawer
'
);
});
const
link
=
wrapper
.
find
(
'
.whats-new-item-title-link
'
);
it
(
'
send an event when feature item is clicked
'
,
()
=>
{
triggerEvent
(
link
.
element
);
trackingSpy
=
mockTracking
(
'
_category_
'
,
wrapper
.
element
,
jest
.
spyOn
);
expect
(
trackingSpy
.
mock
.
calls
[
1
]).
toMatchObject
([
const
link
=
wrapper
.
find
(
'
.whats-new-item-title-link
'
);
'
_category_
'
,
triggerEvent
(
link
.
element
);
'
click_whats_new_item
'
,
{
expect
(
trackingSpy
.
mock
.
calls
[
1
]).
toMatchObject
([
label
:
'
Whats New Drawer
'
,
'
_category_
'
,
property
:
'
www.url.com
'
,
'
click_whats_new_item
'
,
},
{
]);
label
:
'
Whats New Drawer
'
,
});
property
:
'
www.url.com
'
,
},
]);
});
it
(
'
renders infinite scroll
'
,
()
=>
{
const
scroll
=
findInfiniteScroll
();
expect
(
scroll
.
props
()).
toMatchObject
({
fetchedItems
:
wrapper
.
vm
.
$store
.
state
.
features
.
length
,
maxListHeight
:
MOCK_DRAWER_BODY_HEIGHT
,
});
});
describe
(
'
bottomReached
'
,
()
=>
{
const
emitBottomReached
=
()
=>
findInfiniteScroll
().
vm
.
$emit
(
'
bottomReached
'
);
it
(
'
renders infinite scroll
'
,
()
=>
{
beforeEach
(()
=>
{
const
scroll
=
findInfiniteScroll
();
actions
.
fetchItems
.
mockClear
();
});
expect
(
scroll
.
props
()).
toMatchObject
({
it
(
'
when nextPage exists it calls fetchItems
'
,
()
=>
{
fetchedItems
:
wrapper
.
vm
.
$store
.
state
.
features
.
length
,
wrapper
.
vm
.
$store
.
state
.
pageInfo
=
{
nextPage
:
840
};
maxListHeight
:
MOCK_DRAWER_BODY_HEIGHT
,
emitBottomReached
();
expect
(
actions
.
fetchItems
).
toHaveBeenCalledWith
(
expect
.
anything
(),
{
page
:
840
});
});
it
(
'
when nextPage does not exist it does not call fetchItems
'
,
()
=>
{
wrapper
.
vm
.
$store
.
state
.
pageInfo
=
{
nextPage
:
null
};
emitBottomReached
();
expect
(
actions
.
fetchItems
).
not
.
toHaveBeenCalled
();
});
});
it
(
'
calls getDrawerBodyHeight and setDrawerBodyHeight when resize directive is triggered
'
,
()
=>
{
const
{
value
}
=
getBinding
(
getDrawer
().
element
,
'
gl-resize-observer
'
);
value
();
expect
(
getDrawerBodyHeight
).
toHaveBeenCalledWith
(
wrapper
.
find
(
GlDrawer
).
element
);
expect
(
actions
.
setDrawerBodyHeight
).
toHaveBeenCalledWith
(
expect
.
any
(
Object
),
MOCK_DRAWER_BODY_HEIGHT
,
);
});
});
});
});
describe
(
'
bottomReached
'
,
()
=>
{
describe
(
'
self managed
'
,
()
=>
{
const
findTabs
=
()
=>
wrapper
.
find
(
GlTabs
);
const
clickSecondTab
=
async
()
=>
{
const
secondTab
=
wrapper
.
findAll
(
'
.nav-link
'
).
at
(
1
);
await
secondTab
.
trigger
(
'
click
'
);
await
new
Promise
(
resolve
=>
requestAnimationFrame
(
resolve
));
};
beforeEach
(()
=>
{
beforeEach
(()
=>
{
actions
.
fetchItems
.
mockClear
();
gitlabDotCom
=
false
;
setup
();
});
});
it
(
'
when nextPage exists it calls fetchItems
'
,
()
=>
{
it
(
'
renders tabs with drawer body height and content
'
,
()
=>
{
wrapper
.
vm
.
$store
.
state
.
pageInfo
=
{
nextPage
:
840
}
;
const
scroll
=
findInfiniteScroll
()
;
emitBottomReached
();
const
tabs
=
findTabs
();
expect
(
actions
.
fetchItems
).
toHaveBeenCalledWith
(
expect
.
anything
(),
840
);
expect
(
scroll
.
exists
()).
toBe
(
false
);
expect
(
tabs
.
attributes
().
style
).
toBe
(
`height:
${
MOCK_DRAWER_BODY_HEIGHT
}
px;`
);
expect
(
wrapper
.
find
(
'
h5
'
).
text
()).
toBe
(
'
Whats New Drawer
'
);
});
});
it
(
'
when nextPage does not exist it does not call fetchItems
'
,
()
=>
{
describe
(
'
fetchVersion
'
,
()
=>
{
wrapper
.
vm
.
$store
.
state
.
pageInfo
=
{
nextPage
:
null
};
beforeEach
(()
=>
{
emitBottomReached
();
actions
.
fetchItems
.
mockClear
();
});
expect
(
actions
.
fetchItems
).
not
.
toHaveBeenCalled
();
it
(
'
when version isnt fetched, clicking a tab calls fetchItems
'
,
async
()
=>
{
}
);
const
fetchVersionSpy
=
jest
.
spyOn
(
wrapper
.
vm
,
'
fetchVersion
'
);
}
);
await
clickSecondTab
(
);
it
(
'
calls getDrawerBodyHeight and setDrawerBodyHeight when resize directive is triggered
'
,
()
=>
{
expect
(
fetchVersionSpy
).
toHaveBeenCalledWith
(
'
3.10
'
);
const
{
value
}
=
getBinding
(
getDrawer
().
element
,
'
gl-resize-observer
'
);
expect
(
actions
.
fetchItems
).
toHaveBeenCalledWith
(
expect
.
anything
(),
{
version
:
'
3.10
'
});
});
value
();
it
(
'
when version has been fetched, clicking a tab calls fetchItems
'
,
async
()
=>
{
wrapper
.
vm
.
$store
.
state
.
features
.
push
({
title
:
'
GitLab Stories
'
,
release
:
3.1
});
await
wrapper
.
vm
.
$nextTick
();
expect
(
getDrawerBodyHeight
).
toHaveBeenCalledWith
(
wrapper
.
find
(
GlDrawer
).
element
);
const
fetchVersionSpy
=
jest
.
spyOn
(
wrapper
.
vm
,
'
fetchVersion
'
);
await
clickSecondTab
();
expect
(
actions
.
setDrawerBodyHeight
).
toHaveBeenCalledWith
(
expect
(
fetchVersionSpy
).
toHaveBeenCalledWith
(
'
3.10
'
);
expect
.
any
(
Object
),
expect
(
actions
.
fetchItems
).
not
.
toHaveBeenCalled
();
MOCK_DRAWER_BODY_HEIGHT
,
expect
(
wrapper
.
find
(
'
.tab-pane.active h5
'
).
text
()).
toBe
(
'
GitLab Stories
'
);
);
});
});
});
});
});
});
spec/frontend/whats_new/store/actions_spec.js
View file @
f59fcd60
...
@@ -41,6 +41,23 @@ describe('whats new actions', () => {
...
@@ -41,6 +41,23 @@ describe('whats new actions', () => {
axiosMock
.
restore
();
axiosMock
.
restore
();
});
});
it
(
'
passes arguments
'
,
()
=>
{
axiosMock
.
reset
();
axiosMock
.
onGet
(
'
/-/whats_new
'
,
{
params
:
{
page
:
8
,
version
:
40
}
})
.
replyOnce
(
200
,
[{
title
:
'
GitLab Stories
'
}]);
testAction
(
actions
.
fetchItems
,
{
page
:
8
,
version
:
40
},
{},
expect
.
arrayContaining
([
{
type
:
types
.
ADD_FEATURES
,
payload
:
[{
title
:
'
GitLab Stories
'
}]
},
]),
);
});
it
(
'
if already fetching, does not fetch
'
,
()
=>
{
it
(
'
if already fetching, does not fetch
'
,
()
=>
{
testAction
(
actions
.
fetchItems
,
{},
{
fetching
:
true
},
[]);
testAction
(
actions
.
fetchItems
,
{},
{
fetching
:
true
},
[]);
});
});
...
...
spec/helpers/whats_new_helper_spec.rb
View file @
f59fcd60
...
@@ -10,7 +10,7 @@ RSpec.describe WhatsNewHelper do
...
@@ -10,7 +10,7 @@ RSpec.describe WhatsNewHelper do
let
(
:release_item
)
{
double
(
:item
)
}
let
(
:release_item
)
{
double
(
:item
)
}
before
do
before
do
allow
(
ReleaseHighlight
).
to
receive
(
:
most_recent_version
).
and_return
(
84.0
)
allow
(
ReleaseHighlight
).
to
receive
(
:
versions
).
and_return
([
84.0
]
)
end
end
it
{
is_expected
.
to
eq
(
'display-whats-new-notification-84.0'
)
}
it
{
is_expected
.
to
eq
(
'display-whats-new-notification-84.0'
)
}
...
@@ -18,7 +18,7 @@ RSpec.describe WhatsNewHelper do
...
@@ -18,7 +18,7 @@ RSpec.describe WhatsNewHelper do
context
'when most recent release highlights do NOT exist'
do
context
'when most recent release highlights do NOT exist'
do
before
do
before
do
allow
(
ReleaseHighlight
).
to
receive
(
:
most_recent_version
).
and_return
(
nil
)
allow
(
ReleaseHighlight
).
to
receive
(
:
versions
).
and_return
(
nil
)
end
end
it
{
is_expected
.
to
be_nil
}
it
{
is_expected
.
to
be_nil
}
...
@@ -44,4 +44,14 @@ RSpec.describe WhatsNewHelper do
...
@@ -44,4 +44,14 @@ RSpec.describe WhatsNewHelper do
end
end
end
end
end
end
describe
'#whats_new_versions'
do
let
(
:versions
)
{
[
84.0
]
}
it
'returns ReleaseHighlight.versions'
do
expect
(
ReleaseHighlight
).
to
receive
(
:versions
).
and_return
(
versions
)
expect
(
helper
.
whats_new_versions
).
to
eq
(
versions
)
end
end
end
end
spec/models/release_highlight_spec.rb
View file @
f59fcd60
...
@@ -3,21 +3,44 @@
...
@@ -3,21 +3,44 @@
require
'spec_helper'
require
'spec_helper'
RSpec
.
describe
ReleaseHighlight
do
RSpec
.
describe
ReleaseHighlight
do
describe
'#paginated'
do
let
(
:fixture_dir_glob
)
{
Dir
.
glob
(
File
.
join
(
'spec'
,
'fixtures'
,
'whats_new'
,
'*.yml'
))
}
let
(
:fixture_dir_glob
)
{
Dir
.
glob
(
File
.
join
(
'spec'
,
'fixtures'
,
'whats_new'
,
'*.yml'
))
}
let
(
:cache_mock
)
{
double
(
:cache_mock
)
}
let
(
:cache_mock
)
{
double
(
:cache_mock
)
}
before
do
allow
(
Dir
).
to
receive
(
:glob
).
with
(
Rails
.
root
.
join
(
'data'
,
'whats_new'
,
'*.yml'
)).
and_return
(
fixture_dir_glob
)
allow
(
cache_mock
).
to
receive
(
:fetch
).
with
(
'release_highlight:file_paths'
,
expires_in:
1
.
hour
).
and_yield
end
after
do
ReleaseHighlight
.
instance_variable_set
(
:@file_paths
,
nil
)
end
describe
'.for_version'
do
subject
{
ReleaseHighlight
.
for_version
(
version:
version
)
}
let
(
:version
)
{
'1.1'
}
context
'with version param that exists'
do
it
'returns items from that version'
do
expect
(
subject
.
items
.
first
[
'title'
]).
to
eq
(
"It's gonna be a bright"
)
end
end
context
'with version param that does NOT exist'
do
let
(
:version
)
{
'84.0'
}
it
'returns nil'
do
expect
(
subject
).
to
be_nil
end
end
end
describe
'.paginated'
do
let
(
:dot_com
)
{
false
}
let
(
:dot_com
)
{
false
}
before
do
before
do
allow
(
Gitlab
).
to
receive
(
:com?
).
and_return
(
dot_com
)
allow
(
Gitlab
).
to
receive
(
:com?
).
and_return
(
dot_com
)
allow
(
Dir
).
to
receive
(
:glob
).
with
(
Rails
.
root
.
join
(
'data'
,
'whats_new'
,
'*.yml'
)).
and_return
(
fixture_dir_glob
)
expect
(
Rails
).
to
receive
(
:cache
).
twice
.
and_return
(
cache_mock
)
expect
(
Rails
).
to
receive
(
:cache
).
twice
.
and_return
(
cache_mock
)
expect
(
cache_mock
).
to
receive
(
:fetch
).
with
(
'release_highlight:file_paths'
,
expires_in:
1
.
hour
).
and_yield
end
after
do
ReleaseHighlight
.
instance_variable_set
(
:@file_paths
,
nil
)
end
end
context
'with page param'
do
context
'with page param'
do
...
@@ -90,46 +113,51 @@ RSpec.describe ReleaseHighlight do
...
@@ -90,46 +113,51 @@ RSpec.describe ReleaseHighlight do
end
end
end
end
describe
'.most_recent_
version
'
do
describe
'.most_recent_
item_count
'
do
subject
{
ReleaseHighlight
.
most_recent_
version
}
subject
{
ReleaseHighlight
.
most_recent_
item_count
}
context
'when version exist'
do
context
'when recent release items exist'
do
let
(
:release_item
)
{
double
(
:item
)
}
it
'returns the count from the most recent file'
do
allow
(
ReleaseHighlight
).
to
receive
(
:paginated
).
and_return
(
double
(
:paginated
,
items:
[
double
(
:item
)]))
before
do
expect
(
subject
).
to
eq
(
1
)
allow
(
ReleaseHighlight
).
to
receive
(
:paginated
).
and_return
({
items:
[
release_item
]
})
allow
(
release_item
).
to
receive
(
:[]
).
with
(
'release'
).
and_return
(
84.0
)
end
end
it
{
is_expected
.
to
eq
(
84.0
)
}
end
end
context
'when
most recent release highlight
s do NOT exist'
do
context
'when
recent release item
s do NOT exist'
do
before
do
it
'returns nil'
do
allow
(
ReleaseHighlight
).
to
receive
(
:paginated
).
and_return
(
nil
)
allow
(
ReleaseHighlight
).
to
receive
(
:paginated
).
and_return
(
nil
)
end
it
{
is_expected
.
to
be_nil
}
expect
(
subject
).
to
be_nil
end
end
end
end
end
describe
'#most_recent_item_count'
do
describe
'.versions'
do
subject
{
ReleaseHighlight
.
most_recent_item_count
}
it
'returns versions from the file paths'
do
expect
(
ReleaseHighlight
.
versions
).
to
eq
([
'1.5'
,
'1.2'
,
'1.1'
])
end
context
'when recent release items exist'
do
context
'when there are more than 12 versions'
do
it
'returns the count from the most recent file'
do
let
(
:file_paths
)
do
allow
(
ReleaseHighlight
).
to
receive
(
:paginated
).
and_return
({
items:
[
double
(
:item
)]
})
i
=
0
Array
.
new
(
20
)
{
"20201225_01_
#{
i
+=
1
}
.yml"
}
end
expect
(
subject
).
to
eq
(
1
)
it
'limits to 12 versions'
do
allow
(
ReleaseHighlight
).
to
receive
(
:file_paths
).
and_return
(
file_paths
)
expect
(
ReleaseHighlight
.
versions
.
count
).
to
eq
(
12
)
end
end
end
end
end
context
'when recent release items do NOT exist'
do
describe
'QueryResult'
do
it
'returns nil'
do
subject
{
ReleaseHighlight
::
QueryResult
.
new
(
items:
items
,
next_page:
2
)
}
allow
(
ReleaseHighlight
).
to
receive
(
:paginated
).
and_return
(
nil
)
expect
(
subject
).
to
be_nil
let
(
:items
)
{
[
:item
]
}
end
it
'responds to map'
do
expect
(
subject
.
map
(
&
:to_s
)).
to
eq
(
items
.
map
(
&
:to_s
))
end
end
end
end
end
end
spec/requests/whats_new_controller_spec.rb
View file @
f59fcd60
...
@@ -4,22 +4,22 @@ require 'spec_helper'
...
@@ -4,22 +4,22 @@ require 'spec_helper'
RSpec
.
describe
WhatsNewController
do
RSpec
.
describe
WhatsNewController
do
describe
'whats_new_path'
do
describe
'whats_new_path'
do
let
(
:item
)
{
double
(
:item
)
}
let
(
:highlights
)
{
double
(
:highlight
,
items:
[
item
],
map:
[
item
].
map
,
next_page:
2
)
}
context
'with whats_new_drawer feature enabled'
do
context
'with whats_new_drawer feature enabled'
do
before
do
before
do
stub_feature_flags
(
whats_new_drawer:
true
)
stub_feature_flags
(
whats_new_drawer:
true
)
end
end
context
'with no page param'
do
context
'with no page param'
do
let
(
:most_recent
)
{
{
items:
[
item
],
next_page:
2
}
}
let
(
:item
)
{
double
(
:item
)
}
it
'responds with paginated data and headers'
do
it
'responds with paginated data and headers'
do
allow
(
ReleaseHighlight
).
to
receive
(
:paginated
).
with
(
page:
1
).
and_return
(
most_recent
)
allow
(
ReleaseHighlight
).
to
receive
(
:paginated
).
with
(
page:
1
).
and_return
(
highlights
)
allow
(
Gitlab
::
WhatsNew
::
ItemPresenter
).
to
receive
(
:present
).
with
(
item
).
and_return
(
item
)
allow
(
Gitlab
::
WhatsNew
::
ItemPresenter
).
to
receive
(
:present
).
with
(
item
).
and_return
(
item
)
get
whats_new_path
,
xhr:
true
get
whats_new_path
,
xhr:
true
expect
(
response
.
body
).
to
eq
(
most_recent
[
:items
]
.
to_json
)
expect
(
response
.
body
).
to
eq
(
highlights
.
items
.
to_json
)
expect
(
response
.
headers
[
'X-Next-Page'
]).
to
eq
(
2
)
expect
(
response
.
headers
[
'X-Next-Page'
]).
to
eq
(
2
)
end
end
end
end
...
@@ -37,6 +37,18 @@ RSpec.describe WhatsNewController do
...
@@ -37,6 +37,18 @@ RSpec.describe WhatsNewController do
expect
(
response
).
to
have_gitlab_http_status
(
:not_found
)
expect
(
response
).
to
have_gitlab_http_status
(
:not_found
)
end
end
end
end
context
'with version param'
do
it
'returns items without pagination headers'
do
allow
(
ReleaseHighlight
).
to
receive
(
:for_version
).
with
(
version:
'42'
).
and_return
(
highlights
)
allow
(
Gitlab
::
WhatsNew
::
ItemPresenter
).
to
receive
(
:present
).
with
(
item
).
and_return
(
item
)
get
whats_new_path
(
version:
42
),
xhr:
true
expect
(
response
.
body
).
to
eq
(
highlights
.
items
.
to_json
)
expect
(
response
.
headers
[
'X-Next-Page'
]).
to
be_nil
end
end
end
end
context
'with whats_new_drawer feature disabled'
do
context
'with whats_new_drawer feature disabled'
do
...
...
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