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
6b9733cf
Commit
6b9733cf
authored
Dec 04, 2020
by
GitLab Bot
Browse files
Options
Browse Files
Download
Plain Diff
Automatic merge of gitlab-org/gitlab master
parents
8bdbb463
ccc73d1e
Changes
46
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
46 changed files
with
1854 additions
and
857 deletions
+1854
-857
.gitlab/CODEOWNERS
.gitlab/CODEOWNERS
+1
-0
app/assets/javascripts/whats_new/components/app.vue
app/assets/javascripts/whats_new/components/app.vue
+66
-64
app/assets/javascripts/whats_new/components/feature.vue
app/assets/javascripts/whats_new/components/feature.vue
+0
-64
app/assets/javascripts/whats_new/index.js
app/assets/javascripts/whats_new/index.js
+3
-5
app/assets/javascripts/whats_new/store/actions.js
app/assets/javascripts/whats_new/store/actions.js
+1
-2
app/assets/stylesheets/components/whats_new.scss
app/assets/stylesheets/components/whats_new.scss
+0
-26
app/controllers/whats_new_controller.rb
app/controllers/whats_new_controller.rb
+7
-20
app/helpers/whats_new_helper.rb
app/helpers/whats_new_helper.rb
+1
-5
app/models/ci/build.rb
app/models/ci/build.rb
+13
-1
app/models/ci/pipeline.rb
app/models/ci/pipeline.rb
+1
-1
app/models/release_highlight.rb
app/models/release_highlight.rb
+10
-28
app/services/pages/legacy_storage_lease.rb
app/services/pages/legacy_storage_lease.rb
+1
-1
app/views/layouts/header/_default.html.haml
app/views/layouts/header/_default.html.haml
+1
-1
changelogs/unreleased/eb-cobertura-background-fix.yml
changelogs/unreleased/eb-cobertura-background-fix.yml
+5
-0
changelogs/unreleased/error_when_not_licensed_273719.yml
changelogs/unreleased/error_when_not_licensed_273719.yml
+5
-0
config/feature_flags/development/pages_use_legacy_storage_lease.yml
...ture_flags/development/pages_use_legacy_storage_lease.yml
+1
-1
config/feature_flags/development/smart_cobertura_parser.yml
config/feature_flags/development/smart_cobertura_parser.yml
+8
-0
ee/app/assets/javascripts/roadmap/components/epic_item_timeline.vue
...ets/javascripts/roadmap/components/epic_item_timeline.vue
+1
-1
ee/app/assets/javascripts/roadmap/components/roadmap_shell.vue
...p/assets/javascripts/roadmap/components/roadmap_shell.vue
+1
-9
ee/app/assets/stylesheets/page_bundles/roadmap.scss
ee/app/assets/stylesheets/page_bundles/roadmap.scss
+0
-29
ee/spec/features/epics/epic_show_spec.rb
ee/spec/features/epics/epic_show_spec.rb
+2
-2
ee/spec/frontend/roadmap/components/epic_item_spec.js
ee/spec/frontend/roadmap/components/epic_item_spec.js
+2
-2
ee/spec/frontend/roadmap/components/roadmap_shell_spec.js
ee/spec/frontend/roadmap/components/roadmap_shell_spec.js
+55
-66
ee/spec/frontend/roadmap/mock_data.js
ee/spec/frontend/roadmap/mock_data.js
+2
-2
ee/spec/lib/gitlab/ci/templates/Jobs/dast_default_branch_gitlab_ci_yaml_spec.rb
...templates/Jobs/dast_default_branch_gitlab_ci_yaml_spec.rb
+2
-2
ee/spec/lib/gitlab/ci/templates/dast_gitlab_ci_yaml_spec.rb
ee/spec/lib/gitlab/ci/templates/dast_gitlab_ci_yaml_spec.rb
+70
-6
lib/api/api.rb
lib/api/api.rb
+1
-1
lib/api/concerns/packages/nuget_endpoints.rb
lib/api/concerns/packages/nuget_endpoints.rb
+135
-0
lib/api/nuget_project_packages.rb
lib/api/nuget_project_packages.rb
+5
-118
lib/gitlab/ci/parsers/coverage/cobertura.rb
lib/gitlab/ci/parsers/coverage/cobertura.rb
+101
-17
lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
+29
-0
locale/gitlab.pot
locale/gitlab.pot
+0
-3
spec/factories/ci/job_artifacts.rb
spec/factories/ci/job_artifacts.rb
+10
-0
spec/fixtures/cobertura/coverage_with_paths_not_relative_to_project_root.xml.gz
...a/coverage_with_paths_not_relative_to_project_root.xml.gz
+0
-0
spec/frontend/whats_new/components/app_spec.js
spec/frontend/whats_new/components/app_spec.js
+68
-133
spec/frontend/whats_new/store/actions_spec.js
spec/frontend/whats_new/store/actions_spec.js
+0
-17
spec/helpers/whats_new_helper_spec.rb
spec/helpers/whats_new_helper_spec.rb
+2
-12
spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb
spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb
+616
-133
spec/models/ci/build_spec.rb
spec/models/ci/build_spec.rb
+28
-1
spec/models/ci/pipeline_spec.rb
spec/models/ci/pipeline_spec.rb
+10
-0
spec/models/release_highlight_spec.rb
spec/models/release_highlight_spec.rb
+34
-62
spec/requests/api/nuget_project_packages_spec.rb
spec/requests/api/nuget_project_packages_spec.rb
+280
-0
spec/requests/whats_new_controller_spec.rb
spec/requests/whats_new_controller_spec.rb
+5
-17
spec/services/ci/pipelines/create_artifact_service_spec.rb
spec/services/ci/pipelines/create_artifact_service_spec.rb
+2
-1
spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb
..._examples/requests/api/nuget_endpoints_shared_examples.rb
+265
-0
spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
...d_examples/requests/api/nuget_packages_shared_examples.rb
+4
-4
No files found.
.gitlab/CODEOWNERS
View file @
6b9733cf
...
...
@@ -159,6 +159,7 @@
/lib/gitlab/github_import/ @gitlab-org/maintainers/database
/app/finders/ @gitlab-org/maintainers/database
/ee/app/finders/ @gitlab-org/maintainers/database
/rubocop/rubocop-migrations.yml @gitlab-org/maintainers/database
[Engineering Productivity]
/.gitlab-ci.yml @gl-quality/eng-prod
...
...
app/assets/javascripts/whats_new/components/app.vue
View file @
6b9733cf
...
...
@@ -2,15 +2,13 @@
import
{
mapState
,
mapActions
}
from
'
vuex
'
;
import
{
GlDrawer
,
GlBadge
,
GlIcon
,
GlLink
,
GlInfiniteScroll
,
GlResizeObserverDirective
,
GlTabs
,
GlTab
,
GlBadge
,
GlLoadingIcon
,
}
from
'
@gitlab/ui
'
;
import
SkeletonLoader
from
'
./skeleton_loader.vue
'
;
import
Feature
from
'
./feature.vue
'
;
import
Tracking
from
'
~/tracking
'
;
import
{
getDrawerBodyHeight
}
from
'
../utils/get_drawer_body_height
'
;
...
...
@@ -19,13 +17,11 @@ const trackingMixin = Tracking.mixin();
export
default
{
components
:
{
GlDrawer
,
GlBadge
,
GlIcon
,
GlLink
,
GlInfiniteScroll
,
GlTabs
,
GlTab
,
SkeletonLoader
,
Feature
,
GlBadge
,
GlLoadingIcon
,
},
directives
:
{
GlResizeObserver
:
GlResizeObserverDirective
,
...
...
@@ -35,19 +31,11 @@ export default {
storageKey
:
{
type
:
String
,
required
:
true
,
},
versions
:
{
type
:
Array
,
required
:
true
,
},
gitlabDotCom
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
default
:
null
,
},
},
computed
:
{
...
mapState
([
'
open
'
,
'
features
'
,
'
pageInfo
'
,
'
drawerBodyHeight
'
,
'
fetching
'
]),
...
mapState
([
'
open
'
,
'
features
'
,
'
pageInfo
'
,
'
drawerBodyHeight
'
]),
},
mounted
()
{
this
.
openDrawer
(
this
.
storageKey
);
...
...
@@ -61,25 +49,14 @@ export default {
methods
:
{
...
mapActions
([
'
openDrawer
'
,
'
closeDrawer
'
,
'
fetchItems
'
,
'
setDrawerBodyHeight
'
]),
bottomReached
()
{
const
page
=
this
.
pageInfo
.
nextPage
;
if
(
page
)
{
this
.
fetchItems
({
page
});
if
(
this
.
pageInfo
.
nextPage
)
{
this
.
fetchItems
(
this
.
pageInfo
.
nextPage
);
}
},
handleResize
()
{
const
height
=
getDrawerBodyHeight
(
this
.
$refs
.
drawer
.
$el
);
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
>
...
...
@@ -96,39 +73,64 @@ export default {
<template
#header
>
<h4
class=
"page-title gl-my-2"
>
{{
__
(
"
What's new at GitLab
"
)
}}
</h4>
</
template
>
<
template
v-if=
"features.length"
>
<gl-infinite-scroll
v-if=
"gitlabDotCom"
:fetched-items=
"features.length"
:max-list-height=
"drawerBodyHeight"
class=
"gl-p-0"
@
bottomReached=
"bottomReached"
>
<template
#items
>
<feature
v-for=
"feature in features"
:key=
"feature.title"
:feature=
"feature"
/>
</
template
>
</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-infinite-scroll
v-if=
"features.length"
:fetched-items=
"features.length"
:max-list-height=
"drawerBodyHeight"
class=
"gl-p-0"
@
bottomReached=
"bottomReached"
>
<
template
#items
>
<div
v-for=
"feature in features"
:key=
"feature.title"
class=
"gl-pb-7 gl-pt-5 gl-px-5 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100"
>
<
template
#title
>
<span>
{{
version
}}
</span>
<gl-badge
v-if=
"index === 0"
>
{{
__
(
'
Your Version
'
)
}}
</gl-badge>
</
template
>
<gl-loading-icon
v-if=
"fetching"
size=
"lg"
class=
"text-center"
/>
<
template
v-else
>
<feature
v-for=
"feature in featuresForVersion(version)"
:key=
"feature.title"
:feature=
"feature"
<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"
>
{{
feature
.
title
}}
</h5>
</gl-link>
<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"
/>
</
template
>
</gl-tab>
</gl-tabs>
</template>
</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
>
</gl-infinite-scroll>
<div
v-else
class=
"gl-mt-5"
>
<skeleton-loader
/>
<skeleton-loader
/>
...
...
app/assets/javascripts/whats_new/components/feature.vue
deleted
100644 → 0
View file @
8bdbb463
<
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 @
6b9733cf
...
...
@@ -10,6 +10,8 @@ export default el => {
if
(
whatsNewApp
)
{
store
.
dispatch
(
'
openDrawer
'
);
}
else
{
const
storageKey
=
getStorageKey
(
el
);
whatsNewApp
=
new
Vue
({
el
,
store
,
...
...
@@ -26,11 +28,7 @@ export default el => {
},
render
(
createElement
)
{
return
createElement
(
'
app
'
,
{
props
:
{
storageKey
:
getStorageKey
(
el
),
versions
:
JSON
.
parse
(
el
.
getAttribute
(
'
data-versions
'
)),
gitlabDotCom
:
el
.
getAttribute
(
'
data-gitlab-dot-com
'
),
},
props
:
{
storageKey
},
});
},
});
...
...
app/assets/javascripts/whats_new/store/actions.js
View file @
6b9733cf
...
...
@@ -13,7 +13,7 @@ export default {
localStorage
.
setItem
(
storageKey
,
JSON
.
stringify
(
false
));
}
},
fetchItems
({
commit
,
state
},
{
page
,
version
}
=
{
page
:
null
,
version
:
null
}
)
{
fetchItems
({
commit
,
state
},
page
)
{
if
(
state
.
fetching
)
{
return
false
;
}
...
...
@@ -24,7 +24,6 @@ export default {
.
get
(
'
/-/whats_new
'
,
{
params
:
{
page
,
version
,
},
})
.
then
(({
data
,
headers
})
=>
{
...
...
app/assets/stylesheets/components/whats_new.scss
View file @
6b9733cf
...
...
@@ -6,32 +6,6 @@
.gl-infinite-scroll-legend
{
@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
{
...
...
app/controllers/whats_new_controller.rb
View file @
6b9733cf
# frozen_string_literal: true
class
WhatsNewController
<
ApplicationController
include
Gitlab
::
Utils
::
StrongMemoize
skip_before_action
:authenticate_user!
before_action
:check_feature_flag
before_action
:check_valid_page_param
,
:set_pagination_headers
,
unless:
->
{
has_version_param?
}
before_action
:check_feature_flag
,
:check_valid_page_param
,
:set_pagination_headers
feature_category
:navigation
def
index
respond_to
do
|
format
|
format
.
js
do
render
json:
highligh
t_items
render
json:
most_recen
t_items
end
end
end
...
...
@@ -32,25 +29,15 @@ class WhatsNewController < ApplicationController
params
[
:page
]
&
.
to_i
||
1
end
def
highlights
strong_memoize
(
:highlights
)
do
if
has_version_param?
ReleaseHighlight
.
for_version
(
version:
params
[
:version
])
else
ReleaseHighlight
.
paginated
(
page:
current_page
)
end
end
def
most_recent
@most_recent
||=
ReleaseHighlight
.
paginated
(
page:
current_page
)
end
def
highligh
t_items
highlights
.
map
{
|
item
|
Gitlab
::
WhatsNew
::
ItemPresenter
.
present
(
item
)
}
def
most_recen
t_items
most_recent
[
:items
]
.
map
{
|
item
|
Gitlab
::
WhatsNew
::
ItemPresenter
.
present
(
item
)
}
end
def
set_pagination_headers
response
.
set_header
(
'X-Next-Page'
,
highlights
.
next_page
)
end
def
has_version_param?
params
[
:version
].
present?
response
.
set_header
(
'X-Next-Page'
,
most_recent
[
:next_page
])
end
end
app/helpers/whats_new_helper.rb
View file @
6b9733cf
...
...
@@ -6,14 +6,10 @@ module WhatsNewHelper
end
def
whats_new_storage_key
most_recent_version
=
ReleaseHighlight
.
versions
&
.
first
most_recent_version
=
ReleaseHighlight
.
most_recent_version
return
unless
most_recent_version
[
'display-whats-new-notification'
,
most_recent_version
].
join
(
'-'
)
end
def
whats_new_versions
ReleaseHighlight
.
versions
end
end
app/models/ci/build.rb
View file @
6b9733cf
...
...
@@ -916,8 +916,20 @@ module Ci
end
def
collect_coverage_reports!
(
coverage_report
)
project_path
,
worktree_paths
=
if
Feature
.
enabled?
(
:smart_cobertura_parser
,
project
)
# If the flag is disabled, we intentionally pass nil
# for both project_path and worktree_paths to fallback
# to the non-smart behavior of the parser
[
project
.
full_path
,
pipeline
.
all_worktree_paths
]
end
each_report
(
Ci
::
JobArtifact
::
COVERAGE_REPORT_FILE_TYPES
)
do
|
file_type
,
blob
|
Gitlab
::
Ci
::
Parsers
.
fabricate!
(
file_type
).
parse!
(
blob
,
coverage_report
)
Gitlab
::
Ci
::
Parsers
.
fabricate!
(
file_type
).
parse!
(
blob
,
coverage_report
,
project_path:
project_path
,
worktree_paths:
worktree_paths
)
end
coverage_report
...
...
app/models/ci/pipeline.rb
View file @
6b9733cf
...
...
@@ -972,7 +972,7 @@ module Ci
def
coverage_reports
Gitlab
::
Ci
::
Reports
::
CoverageReports
.
new
.
tap
do
|
coverage_reports
|
latest_report_builds
(
Ci
::
JobArtifact
.
coverage_reports
).
each
do
|
build
|
latest_report_builds
(
Ci
::
JobArtifact
.
coverage_reports
).
includes
(
:project
).
find_
each
do
|
build
|
build
.
collect_coverage_reports!
(
coverage_reports
)
end
end
...
...
app/models/release_highlight.rb
View file @
6b9733cf
...
...
@@ -3,17 +3,6 @@
class
ReleaseHighlight
CACHE_DURATION
=
1
.
hour
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
)
Rails
.
cache
.
fetch
(
cache_key
(
page
),
expires_in:
CACHE_DURATION
)
do
...
...
@@ -21,7 +10,10 @@ class ReleaseHighlight
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
...
...
@@ -61,25 +53,15 @@ class ReleaseHighlight
next_page
if
self
.
file_paths
[
next_index
]
end
def
self
.
most_recent_
item_count
Gitlab
::
ProcessMemoryCache
.
cache_backend
.
fetch
(
'release_highlight:re
cent_item_count
'
,
expires_in:
CACHE_DURATION
)
do
self
.
paginated
&
.
items
&
.
count
def
self
.
most_recent_
version
Gitlab
::
ProcessMemoryCache
.
cache_backend
.
fetch
(
'release_highlight:re
lease_version
'
,
expires_in:
CACHE_DURATION
)
do
self
.
paginated
&
.
[
](
:items
)
&
.
first
&
.
[
](
'release'
)
end
end
def
self
.
versions
Gitlab
::
ProcessMemoryCache
.
cache_backend
.
fetch
(
'release_highlight:versions'
,
expires_in:
CACHE_DURATION
)
do
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
def
self
.
most_recent_item_count
Gitlab
::
ProcessMemoryCache
.
cache_backend
.
fetch
(
'release_highlight:recent_item_count'
,
expires_in:
CACHE_DURATION
)
do
self
.
paginated
&
.
[
](
:items
)
&
.
count
end
end
QueryResult
=
Struct
.
new
(
:items
,
:next_page
,
keyword_init:
true
)
do
include
Enumerable
delegate
:each
,
to: :items
end
end
app/services/pages/legacy_storage_lease.rb
View file @
6b9733cf
...
...
@@ -12,7 +12,7 @@ module Pages
# TODO: just remove this method after testing this in production
# https://gitlab.com/gitlab-org/gitlab/-/issues/282464
def
try_obtain_lease
return
yield
unless
Feature
.
enabled?
(
:pages_use_legacy_storage_lease
,
project
)
return
yield
unless
Feature
.
enabled?
(
:pages_use_legacy_storage_lease
,
project
,
default_enabled:
true
)
super
end
...
...
app/views/layouts/header/_default.html.haml
View file @
6b9733cf
...
...
@@ -102,7 +102,7 @@
=
sprite_icon
(
'close'
,
size:
12
,
css_class:
'close-icon js-navbar-toggle-left'
)
-
if
::
Feature
.
enabled?
(
:whats_new_drawer
,
current_user
)
#whats-new-app
{
data:
{
storage_key:
whats_new_storage_key
,
versions:
whats_new_versions
,
gitlab_dot_com:
Gitlab
.
dev_env_org_or_com?
}
}
#whats-new-app
{
data:
{
storage_key:
whats_new_storage_key
}
}
-
if
can?
(
current_user
,
:update_user_status
,
current_user
)
.js-set-status-modal-wrapper
{
data:
user_status_data
}
changelogs/unreleased/eb-cobertura-background-fix.yml
0 → 100644
View file @
6b9733cf
---
title
:
Implement smart cobertura class path correction
merge_request
:
48048
author
:
type
:
changed
changelogs/unreleased/error_when_not_licensed_273719.yml
0 → 100644
View file @
6b9733cf
---
title
:
Add a job to the DAST template that shows an error in the console if the user is not licensed to use DAST.
merge_request
:
47484
author
:
type
:
changed
config/feature_flags/development/pages_use_legacy_storage_lease.yml
View file @
6b9733cf
...
...
@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/282464
milestone
:
'
13.7'
type
:
development
group
:
group::release
default_enabled
:
fals
e
default_enabled
:
tru
e
config/feature_flags/development/smart_cobertura_parser.yml
0 → 100644
View file @
6b9733cf
---
name
:
smart_cobertura_parser
introduced_by_url
:
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48048
rollout_issue_url
:
https://gitlab.com/gitlab-org/gitlab/-/issues/284822
milestone
:
'
13.7'
type
:
development
group
:
group::testing
default_enabled
:
false
ee/app/assets/javascripts/roadmap/components/epic_item_timeline.vue
View file @
6b9733cf
...
...
@@ -165,7 +165,7 @@ export default {
<
template
>
<span
class=
"epic-timeline-cell"
data-qa-selector=
"epic_timeline_cell"
>
<current-day-indicator
:preset-type=
"presetType"
:timeframe-item=
"timeframeItem"
/>
<div
class=
"
epic-bar-wrapper
"
>
<div
class=
"
gl-relative
"
>
<a
v-if=
"hasStartDate"
:id=
"generateKey(epic)"
...
...
ee/app/assets/javascripts/roadmap/components/roadmap_shell.vue
View file @
6b9733cf
<
script
>
import
{
mapState
}
from
'
vuex
'
;
import
{
GlDeprecatedSkeletonLoading
as
GlSkeletonLoading
}
from
'
@gitlab/ui
'
;
import
{
isInViewport
}
from
'
~/lib/utils/common_utils
'
;
import
{
EXTEND_AS
}
from
'
../constants
'
;
...
...
@@ -12,7 +11,6 @@ import roadmapTimelineSection from './roadmap_timeline_section.vue';
export
default
{
components
:
{
GlSkeletonLoading
,
epicsListSection
,
milestonesListSection
,
roadmapTimelineSection
,
...
...
@@ -92,7 +90,7 @@ export default {
<
template
>
<div
class=
"
roadmap-shell js-roadmap-shell
"
class=
"
js-roadmap-shell gl-relative gl-h-full gl-w-full gl-overflow-x-auto
"
data-qa-selector=
"roadmap_shell"
@
scroll=
"handleScroll"
>
...
...
@@ -109,13 +107,7 @@ export default {
:timeframe=
"timeframe"
:current-group-id=
"currentGroupId"
/>
<div
v-if=
"!epics.length"
class=
"skeleton-loader js-skeleton-loader"
>
<div
v-for=
"n in 10"
:key=
"n"
class=
"mt-2"
>
<gl-skeleton-loading
:lines=
"2"
/>
</div>
</div>
<epics-list-section
v-else
:preset-type=
"presetType"
:epics=
"epics"
:timeframe=
"timeframe"
...
...
ee/app/assets/stylesheets/page_bundles/roadmap.scss
View file @
6b9733cf
...
...
@@ -123,31 +123,6 @@ html.group-epics-roadmap-html {
}
}
.roadmap-shell
{
position
:
relative
;
height
:
100%
;
width
:
100%
;
overflow-x
:
auto
;
.skeleton-loader
{
position
:
absolute
;
top
:
$header-item-height
;
width
:
$details-cell-width
;
height
:
100%
;
padding-top
:
$gl-padding-top
;
padding-left
:
$gl-padding
;
z-index
:
4
;
&
:
:
after
{
height
:
100%
;
}
}
&
.prevent-vertical-scroll
{
overflow-y
:
hidden
;
}
}
.
roadmap-timeline-section
.
timeline-header-blank
:
:
after
,
.
epics-list-section
.
epic-details-cell
::
after
,
.
milestones-list-section
.
milestones-list-title
::
after
,
...
...
@@ -324,10 +299,6 @@ html.group-epics-roadmap-html {
}
}
.epic-bar-wrapper
{
position
:
relative
;
}
.epic-bar
{
position
:
absolute
;
top
:
5px
;
...
...
ee/spec/features/epics/epic_show_spec.rb
View file @
6b9733cf
...
...
@@ -67,9 +67,9 @@ RSpec.describe 'Epic show', :js do
it
'shows Roadmap timeline with child epics'
do
page
.
within
(
'.js-epic-tabs-content #roadmap'
)
do
expect
(
page
).
to
have_selector
(
'.roadmap-container .roadmap-shell'
)
expect
(
page
).
to
have_selector
(
'.roadmap-container .
js-
roadmap-shell'
)
page
.
within
(
'.roadmap-shell .epics-list-section'
)
do
page
.
within
(
'.
js-
roadmap-shell .epics-list-section'
)
do
expect
(
page
).
not_to
have_content
(
not_child
.
title
)
expect
(
find
(
'.epic-item-container:nth-child(1) .epics-list-item .epic-title'
)).
to
have_content
(
'Child epic B'
)
expect
(
find
(
'.epic-item-container:nth-child(2) .epics-list-item .epic-title'
)).
to
have_content
(
'Child epic A'
)
...
...
ee/spec/frontend/roadmap/components/epic_item_spec.js
View file @
6b9733cf
...
...
@@ -99,7 +99,7 @@ describe('EpicItemComponent', () => {
describe
(
'
timeframeString
'
,
()
=>
{
it
(
'
returns timeframe string correctly when both start and end dates are defined
'
,
()
=>
{
expect
(
wrapper
.
vm
.
timeframeString
(
mockEpic
)).
toBe
(
'
Jul
10, 2017 – Jun 2, 2018
'
);
expect
(
wrapper
.
vm
.
timeframeString
(
mockEpic
)).
toBe
(
'
Nov
10, 2017 – Jun 2, 2018
'
);
});
it
(
'
returns timeframe string correctly when no dates are defined
'
,
()
=>
{
...
...
@@ -113,7 +113,7 @@ describe('EpicItemComponent', () => {
const
epic
=
{
...
mockEpic
,
endDateUndefined
:
true
};
wrapper
=
createComponent
({
epic
});
expect
(
wrapper
.
vm
.
timeframeString
(
epic
)).
toBe
(
'
Jul
10, 2017 – No end date
'
);
expect
(
wrapper
.
vm
.
timeframeString
(
epic
)).
toBe
(
'
Nov
10, 2017 – No end date
'
);
});
it
(
'
returns timeframe string correctly when only end date is defined
'
,
()
=>
{
...
...
ee/spec/frontend/roadmap/components/roadmap_shell_spec.js
View file @
6b9733cf
import
Vue
from
'
vue
'
;
import
Vuex
from
'
vuex
'
;
import
{
mount
,
createLocalVue
}
from
'
@vue/test-utils
'
;
import
roadmapShellComponent
from
'
ee/roadmap/components/roadmap_shell.vue
'
;
import
RoadmapShell
from
'
ee/roadmap/components/roadmap_shell.vue
'
;
import
{
PRESET_TYPES
}
from
'
ee/roadmap/constants
'
;
import
eventHub
from
'
ee/roadmap/event_hub
'
;
import
createStore
from
'
ee/roadmap/store
'
;
...
...
@@ -12,65 +13,71 @@ import {
mockGroupId
,
mockMilestone
,
}
from
'
ee_jest/roadmap/mock_data
'
;
import
{
mountComponentWithStore
}
from
'
helpers/vue_mount_component_helper
'
;
const
mockTimeframeMonths
=
getTimeframeForMonthsView
(
mockTimeframeInitialDate
);
const
createComponent
=
(
{
epics
=
[
mockEpic
],
milestones
=
[
mockMilestone
],
timeframe
=
mockTimeframeMonths
,
currentGroupId
=
mockGroupId
,
defaultInnerHeight
=
0
,
hasFiltersApplied
=
false
,
},
el
,
)
=>
{
const
Component
=
Vue
.
extend
(
roadmapShellComponent
);
const
store
=
createStore
();
store
.
dispatch
(
'
setInitialData
'
,
{
defaultInnerHeight
,
childrenFlags
:
{
'
1
'
:
{
itemExpanded
:
false
}
},
});
describe
(
'
RoadmapShell
'
,
()
=>
{
const
localVue
=
createLocalVue
();
localVue
.
use
(
Vuex
);
return
mountComponentWithStore
(
Component
,
{
el
,
store
,
props
:
{
presetType
:
PRESET_TYPES
.
MONTHS
,
epics
,
milestones
,
timeframe
,
currentGroupId
,
hasFiltersApplied
,
},
});
};
let
store
;
let
wrapper
;
describe
(
'
RoadmapShellComponent
'
,
()
=>
{
let
vm
;
const
storeFactory
=
({
defaultInnerHeight
=
0
})
=>
{
store
=
createStore
();
store
.
dispatch
(
'
setInitialData
'
,
{
defaultInnerHeight
,
childrenFlags
:
{
'
1
'
:
{
itemExpanded
:
false
}
},
});
};
const
createComponent
=
(
{
epics
=
[
mockEpic
],
milestones
=
[
mockMilestone
],
timeframe
=
mockTimeframeMonths
,
currentGroupId
=
mockGroupId
,
hasFiltersApplied
=
false
,
},
el
,
)
=>
{
wrapper
=
mount
(
RoadmapShell
,
{
localVue
,
store
,
attachTo
:
el
,
propsData
:
{
presetType
:
PRESET_TYPES
.
MONTHS
,
epics
,
milestones
,
timeframe
,
currentGroupId
,
hasFiltersApplied
,
},
});
};
beforeEach
(
done
=>
{
vm
=
createComponent
({});
vm
.
$nextTick
(
done
);
beforeEach
(
()
=>
{
storeFactory
({});
createComponent
({}
);
});
afterEach
(()
=>
{
vm
.
$destroy
();
wrapper
.
destroy
();
wrapper
=
null
;
store
=
null
;
});
describe
(
'
data
'
,
()
=>
{
it
(
'
returns default data props
'
,
()
=>
{
expect
(
vm
.
timeframeStartOffset
).
toBe
(
0
);
expect
(
wrapper
.
vm
.
timeframeStartOffset
).
toBe
(
0
);
});
});
describe
(
'
methods
'
,
()
=>
{
beforeEach
(()
=>
{
document
.
body
.
innerHTML
+=
'
<div class="roadmap-container"><div id="roadmap-shell"></div></div>
'
;
'
<div class="roadmap-container"><div data-testid="roadmap-shell"></div></div>
'
;
createComponent
({},
document
.
querySelector
(
'
[data-testid="roadmap-shell"]
'
));
});
afterEach
(()
=>
{
...
...
@@ -78,38 +85,20 @@ describe('RoadmapShellComponent', () => {
});
describe
(
'
handleScroll
'
,
()
=>
{
it
(
'
emits `epicsListScrolled` event via eventHub
'
,
done
=>
{
const
vmWithParentEl
=
createComponent
({},
document
.
getElementById
(
'
roadmap-shell
'
));
it
(
'
emits `epicsListScrolled` event via eventHub
'
,
async
()
=>
{
jest
.
spyOn
(
eventHub
,
'
$emit
'
).
mockImplementation
(()
=>
{});
Vue
.
nextTick
()
.
then
(()
=>
{
vmWithParentEl
.
handleScroll
();
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
epicsListScrolled
'
,
expect
.
any
(
Object
));
await
wrapper
.
vm
.
$nextTick
();
wrapper
.
vm
.
handleScroll
();
vmWithParentEl
.
$destroy
();
})
.
then
(
done
)
.
catch
(
done
.
fail
);
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
epicsListScrolled
'
,
expect
.
any
(
Object
));
});
});
});
describe
(
'
template
'
,
()
=>
{
it
(
'
renders component container element with class `roadmap-shell`
'
,
()
=>
{
expect
(
vm
.
$el
.
classList
.
contains
(
'
roadmap-shell
'
)).
toBe
(
true
);
});
it
(
'
renders skeleton loader element when Epics list is empty
'
,
done
=>
{
vm
.
epics
=
[];
vm
.
$nextTick
()
.
then
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.js-skeleton-loader
'
)).
not
.
toBeNull
();
})
.
then
(
done
)
.
catch
(
done
.
fail
);
it
(
'
renders component container element with class `js-roadmap-shell`
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.js-roadmap-shell
'
).
exists
()).
toBe
(
true
);
});
});
});
ee/spec/frontend/roadmap/mock_data.js
View file @
6b9733cf
...
...
@@ -104,8 +104,8 @@ export const mockEpic = {
groupId
:
2
,
groupName
:
'
Gitlab Org
'
,
groupFullName
:
'
Gitlab Org
'
,
startDate
:
new
Date
(
'
2017-
07
-10
'
),
originalStartDate
:
new
Date
(
'
2017-
07
-10
'
),
startDate
:
new
Date
(
'
2017-
11
-10
'
),
originalStartDate
:
new
Date
(
'
2017-
11
-10
'
),
endDate
:
new
Date
(
'
2018-06-02
'
),
webUrl
:
'
/groups/gitlab-org/-/epics/1
'
,
descendantCounts
:
{
...
...
ee/spec/lib/gitlab/ci/templates/Jobs/dast_default_branch_gitlab_ci_yaml_spec.rb
View file @
6b9733cf
...
...
@@ -47,8 +47,8 @@ RSpec.describe 'Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml' do
end
context
'when project has no license'
do
it
'includes
no DAST jobs
'
do
expect
(
build_names
).
to
eq
%w(placeholder)
it
'includes
dast_unlicensed job
'
do
expect
(
build_names
).
to
eq
%w(placeholder
dast_unlicensed
)
end
end
...
...
ee/spec/lib/gitlab/ci/templates/dast_gitlab_ci_yaml_spec.rb
View file @
6b9733cf
...
...
@@ -21,8 +21,72 @@ RSpec.describe 'DAST.gitlab-ci.yml' do
end
context
'when project has no license'
do
it
'includes no jobs'
do
expect
{
pipeline
}.
to
raise_error
(
Ci
::
CreatePipelineService
::
CreateError
)
let
(
:cluster
)
{
create
(
:cluster
,
:project
,
:provided_by_gcp
,
projects:
[
project
])
}
context
'when k8 is active'
do
before
do
allow
(
cluster
).
to
receive
(
:active?
).
and_return
(
true
)
end
it
'includes dast_unlicensed job'
do
expect
(
build_names
).
to
match_array
(
%w[dast_unlicensed]
)
end
end
context
'when DAST_WEBSITE is active'
do
before
do
create
(
:ci_variable
,
project:
project
,
key:
'DAST_WEBSITE'
,
value:
'http://example.com'
)
end
it
'includes dast_unlicensed job'
do
expect
(
build_names
).
to
match_array
(
%w[dast_unlicensed]
)
end
end
context
'when DAST_API_SPECIFICATION is set'
do
before
do
create
(
:ci_variable
,
project:
project
,
key:
'DAST_API_SPECIFICATION'
,
value:
'http://example/open-api-v2.json'
)
end
it
'includes dast_unlicensed job'
do
expect
(
build_names
).
to
match_array
(
%w[dast_unlicensed]
)
end
end
context
'when DAST_DISABLED'
do
before
do
create
(
:ci_variable
,
project:
project
,
key:
'DAST_DISABLED'
,
value:
'1'
)
end
it
'includes no job'
do
expect
{
pipeline
}.
to
raise_error
(
Ci
::
CreatePipelineService
::
CreateError
)
end
end
context
'when DAST_DISABLED_FOR_DEFAULT_BRANCH and CI_DEFAULT_BRANCH equals CI_COMMIT_REF_NAME'
do
before
do
create
(
:ci_variable
,
project:
project
,
key:
'DAST_DISABLED_FOR_DEFAULT_BRANCH'
,
value:
'1'
)
create
(
:ci_variable
,
project:
project
,
key:
'CI_DEFAULT_BRANCH'
,
value:
'test_branch'
)
create
(
:ci_variable
,
project:
project
,
key:
'CI_COMMIT_REF_NAME'
,
value:
'test_branch'
)
end
it
'includes no job'
do
expect
{
pipeline
}.
to
raise_error
(
Ci
::
CreatePipelineService
::
CreateError
)
end
end
context
'when not on default branch and review disabled and both DAST_WEBSITE and DAST_API_SPECIFICATION are not set'
do
before
do
create
(
:ci_variable
,
project:
project
,
key:
'CI_DEFAULT_BRANCH'
,
value:
'main'
)
create
(
:ci_variable
,
project:
project
,
key:
'CI_COMMIT_REF_NAME'
,
value:
'test_branch'
)
create
(
:ci_variable
,
project:
project
,
key:
'REVIEW_DISABLED'
,
value:
'1'
)
create
(
:ci_variable
,
project:
project
,
key:
'DAST_WEBSITE'
,
value:
nil
)
create
(
:ci_variable
,
project:
project
,
key:
'DAST_API_SPECIFICATION'
,
value:
nil
)
end
it
'includes no job'
do
expect
{
pipeline
}.
to
raise_error
(
Ci
::
CreatePipelineService
::
CreateError
)
end
end
end
...
...
@@ -56,7 +120,7 @@ RSpec.describe 'DAST.gitlab-ci.yml' do
create
(
:ci_variable
,
project:
project
,
key:
'DAST_WEBSITE'
,
value:
'http://example.com'
)
end
it
'includes job'
do
it
'includes
dast
job'
do
expect
(
build_names
).
to
match_array
(
%w[dast]
)
end
end
...
...
@@ -66,7 +130,7 @@ RSpec.describe 'DAST.gitlab-ci.yml' do
create
(
:ci_variable
,
project:
project
,
key:
'DAST_API_SPECIFICATION'
,
value:
'http://my.api/api-specification.yml'
)
end
it
'includes job'
do
it
'includes
dast
job'
do
expect
(
build_names
).
to
match_array
(
%w[dast]
)
end
end
...
...
@@ -104,7 +168,7 @@ RSpec.describe 'DAST.gitlab-ci.yml' do
project
.
repository
.
create_branch
(
pipeline_branch
)
end
it
'includes job'
do
it
'includes
dast
job'
do
expect
(
build_names
).
to
match_array
(
%w[dast]
)
end
end
...
...
@@ -118,7 +182,7 @@ RSpec.describe 'DAST.gitlab-ci.yml' do
end
context
'when on default branch'
do
it
'includes job'
do
it
'includes
dast
job'
do
expect
(
build_names
).
to
match_array
(
%w[dast]
)
end
end
...
...
lib/api/api.rb
View file @
6b9733cf
...
...
@@ -211,7 +211,7 @@ module API
mount
::
API
::
ProjectPackages
mount
::
API
::
GroupPackages
mount
::
API
::
PackageFiles
mount
::
API
::
NugetPackages
mount
::
API
::
NugetP
rojectP
ackages
mount
::
API
::
PypiPackages
mount
::
API
::
ComposerPackages
mount
::
API
::
ConanProjectPackages
...
...
lib/api/concerns/packages/nuget_endpoints.rb
0 → 100644
View file @
6b9733cf
# frozen_string_literal: true
#
# NuGet Package Manager Client API
#
# These API endpoints are not consumed directly by users, so there is no documentation for the
# individual endpoints. They are called by the NuGet package manager client when users run commands
# like `nuget install` or `nuget push`. The usage of the GitLab NuGet registry is documented here:
# https://docs.gitlab.com/ee/user/packages/nuget_repository/
#
# Technical debt: https://gitlab.com/gitlab-org/gitlab/issues/35798
module
API
module
Concerns
module
Packages
module
NugetEndpoints
extend
ActiveSupport
::
Concern
POSITIVE_INTEGER_REGEX
=
%r{
\A
[1-9]
\d
*
\z
}
.
freeze
NON_NEGATIVE_INTEGER_REGEX
=
%r{
\A
0|[1-9]
\d
*
\z
}
.
freeze
included
do
helpers
do
def
find_packages
packages
=
package_finder
.
execute
not_found!
(
'Packages'
)
unless
packages
.
exists?
packages
end
def
find_package
package
=
package_finder
(
package_version:
params
[
:package_version
]).
execute
.
first
not_found!
(
'Package'
)
unless
package
package
end
def
package_finder
(
finder_params
=
{})
::
Packages
::
Nuget
::
PackageFinder
.
new
(
authorized_user_project
,
**
finder_params
.
merge
(
package_name:
params
[
:package_name
])
)
end
end
# https://docs.microsoft.com/en-us/nuget/api/service-index
desc
'The NuGet Service Index'
do
detail
'This feature was introduced in GitLab 12.6'
end
route_setting
:authentication
,
deploy_token_allowed:
true
,
job_token_allowed: :basic_auth
,
basic_auth_personal_access_token:
true
get
'index'
,
format: :json
do
authorize_read_package!
(
authorized_user_project
)
track_package_event
(
'cli_metadata'
,
:nuget
,
category:
'API::NugetPackages'
)
present
::
Packages
::
Nuget
::
ServiceIndexPresenter
.
new
(
authorized_user_project
),
with:
::
API
::
Entities
::
Nuget
::
ServiceIndex
end
# https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource
params
do
requires
:package_name
,
type:
String
,
desc:
'The NuGet package name'
,
regexp:
API
::
NO_SLASH_URL_PART_REGEX
end
namespace
'/metadata/*package_name'
do
before
do
authorize_read_package!
(
authorized_user_project
)
end
desc
'The NuGet Metadata Service - Package name level'
do
detail
'This feature was introduced in GitLab 12.8'
end
route_setting
:authentication
,
deploy_token_allowed:
true
,
job_token_allowed: :basic_auth
,
basic_auth_personal_access_token:
true
get
'index'
,
format: :json
do
present
::
Packages
::
Nuget
::
PackagesMetadataPresenter
.
new
(
find_packages
),
with:
::
API
::
Entities
::
Nuget
::
PackagesMetadata
end
desc
'The NuGet Metadata Service - Package name and version level'
do
detail
'This feature was introduced in GitLab 12.8'
end
params
do
requires
:package_version
,
type:
String
,
desc:
'The NuGet package version'
,
regexp:
API
::
NO_SLASH_URL_PART_REGEX
end
route_setting
:authentication
,
deploy_token_allowed:
true
,
job_token_allowed: :basic_auth
,
basic_auth_personal_access_token:
true
get
'*package_version'
,
format: :json
do
present
::
Packages
::
Nuget
::
PackageMetadataPresenter
.
new
(
find_package
),
with:
::
API
::
Entities
::
Nuget
::
PackageMetadata
end
end
# https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource
params
do
requires
:q
,
type:
String
,
desc:
'The search term'
optional
:skip
,
type:
Integer
,
desc:
'The number of results to skip'
,
default:
0
,
regexp:
NON_NEGATIVE_INTEGER_REGEX
optional
:take
,
type:
Integer
,
desc:
'The number of results to return'
,
default:
Kaminari
.
config
.
default_per_page
,
regexp:
POSITIVE_INTEGER_REGEX
optional
:prerelease
,
type:
::
Grape
::
API
::
Boolean
,
desc:
'Include prerelease versions'
,
default:
true
end
namespace
'/query'
do
before
do
authorize_read_package!
(
authorized_user_project
)
end
desc
'The NuGet Search Service'
do
detail
'This feature was introduced in GitLab 12.8'
end
route_setting
:authentication
,
deploy_token_allowed:
true
,
job_token_allowed: :basic_auth
,
basic_auth_personal_access_token:
true
get
format: :json
do
search_options
=
{
include_prerelease_versions:
params
[
:prerelease
],
per_page:
params
[
:take
],
padding:
params
[
:skip
]
}
search
=
::
Packages
::
Nuget
::
SearchService
.
new
(
authorized_user_project
,
params
[
:q
],
search_options
)
.
execute
track_package_event
(
'search_package'
,
:nuget
,
category:
'API::NugetPackages'
)
present
::
Packages
::
Nuget
::
SearchResultsPresenter
.
new
(
search
),
with:
::
API
::
Entities
::
Nuget
::
SearchResults
end
end
end
end
end
end
end
lib/api/nuget_packages.rb
→
lib/api/nuget_p
roject_p
ackages.rb
View file @
6b9733cf
...
...
@@ -6,15 +6,12 @@
# called by the NuGet package manager client when users run commands
# like `nuget install` or `nuget push`.
module
API
class
NugetPackages
<
::
API
::
Base
class
NugetP
rojectP
ackages
<
::
API
::
Base
helpers
::
API
::
Helpers
::
PackagesManagerClientsHelpers
helpers
::
API
::
Helpers
::
Packages
::
BasicAuthHelpers
feature_category
:package_registry
POSITIVE_INTEGER_REGEX
=
%r{
\A
[1-9]
\d
*
\z
}
.
freeze
NON_NEGATIVE_INTEGER_REGEX
=
%r{
\A
0|[1-9]
\d
*
\z
}
.
freeze
PACKAGE_FILENAME
=
'package.nupkg'
default_format
:json
...
...
@@ -23,38 +20,12 @@ module API
render_api_error!
(
e
.
message
,
400
)
end
helpers
do
def
find_packages
packages
=
package_finder
.
execute
not_found!
(
'Packages'
)
unless
packages
.
exists?
packages
end
def
find_package
package
=
package_finder
(
package_version:
params
[
:package_version
]).
execute
.
first
not_found!
(
'Package'
)
unless
package
package
end
def
package_finder
(
finder_params
=
{})
::
Packages
::
Nuget
::
PackageFinder
.
new
(
authorized_user_project
,
**
finder_params
.
merge
(
package_name:
params
[
:package_name
])
)
end
end
before
do
require_packages_enabled!
end
params
do
requires
:id
,
type:
String
,
desc:
'The ID of a project'
,
regexp:
POSITIVE_INTEGER_REGEX
requires
:id
,
type:
String
,
desc:
'The ID of a project'
,
regexp:
::
API
::
Concerns
::
Packages
::
NugetEndpoints
::
POSITIVE_INTEGER_REGEX
end
route_setting
:authentication
,
deploy_token_allowed:
true
,
job_token_allowed: :basic_auth
,
basic_auth_personal_access_token:
true
...
...
@@ -65,21 +36,7 @@ module API
end
namespace
':id/packages/nuget'
do
# https://docs.microsoft.com/en-us/nuget/api/service-index
desc
'The NuGet Service Index'
do
detail
'This feature was introduced in GitLab 12.6'
end
route_setting
:authentication
,
deploy_token_allowed:
true
,
job_token_allowed: :basic_auth
,
basic_auth_personal_access_token:
true
get
'index'
,
format: :json
do
authorize_read_package!
(
authorized_user_project
)
track_package_event
(
'cli_metadata'
,
:nuget
)
present
::
Packages
::
Nuget
::
ServiceIndexPresenter
.
new
(
authorized_user_project
),
with:
::
API
::
Entities
::
Nuget
::
ServiceIndex
end
include
::
API
::
Concerns
::
Packages
::
NugetEndpoints
# https://docs.microsoft.com/en-us/nuget/api/package-publish-resource
desc
'The NuGet Package Publish endpoint'
do
...
...
@@ -112,7 +69,7 @@ module API
file_params
.
merge
(
build:
current_authenticated_job
)
).
execute
track_package_event
(
'push_package'
,
:nuget
)
track_package_event
(
'push_package'
,
:nuget
,
category:
'API::NugetPackages'
)
::
Packages
::
Nuget
::
ExtractionWorker
.
perform_async
(
package_file
.
id
)
# rubocop:disable CodeReuse/Worker
...
...
@@ -133,41 +90,6 @@ module API
)
end
params
do
requires
:package_name
,
type:
String
,
desc:
'The NuGet package name'
,
regexp:
API
::
NO_SLASH_URL_PART_REGEX
end
namespace
'/metadata/*package_name'
do
before
do
authorize_read_package!
(
authorized_user_project
)
end
# https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource
desc
'The NuGet Metadata Service - Package name level'
do
detail
'This feature was introduced in GitLab 12.8'
end
route_setting
:authentication
,
deploy_token_allowed:
true
,
job_token_allowed: :basic_auth
,
basic_auth_personal_access_token:
true
get
'index'
,
format: :json
do
present
::
Packages
::
Nuget
::
PackagesMetadataPresenter
.
new
(
find_packages
),
with:
::
API
::
Entities
::
Nuget
::
PackagesMetadata
end
desc
'The NuGet Metadata Service - Package name and version level'
do
detail
'This feature was introduced in GitLab 12.8'
end
params
do
requires
:package_version
,
type:
String
,
desc:
'The NuGet package version'
,
regexp:
API
::
NO_SLASH_URL_PART_REGEX
end
route_setting
:authentication
,
deploy_token_allowed:
true
,
job_token_allowed: :basic_auth
,
basic_auth_personal_access_token:
true
get
'*package_version'
,
format: :json
do
present
::
Packages
::
Nuget
::
PackageMetadataPresenter
.
new
(
find_package
),
with:
::
API
::
Entities
::
Nuget
::
PackageMetadata
end
end
# https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource
params
do
requires
:package_name
,
type:
String
,
desc:
'The NuGet package name'
,
regexp:
API
::
NO_SLASH_URL_PART_REGEX
...
...
@@ -205,47 +127,12 @@ module API
not_found!
(
'Package'
)
unless
package_file
track_package_event
(
'pull_package'
,
:nuget
)
track_package_event
(
'pull_package'
,
:nuget
,
category:
'API::NugetPackages'
)
# nuget and dotnet don't support 302 Moved status codes, supports_direct_download has to be set to false
present_carrierwave_file!
(
package_file
.
file
,
supports_direct_download:
false
)
end
end
params
do
requires
:q
,
type:
String
,
desc:
'The search term'
optional
:skip
,
type:
Integer
,
desc:
'The number of results to skip'
,
default:
0
,
regexp:
NON_NEGATIVE_INTEGER_REGEX
optional
:take
,
type:
Integer
,
desc:
'The number of results to return'
,
default:
Kaminari
.
config
.
default_per_page
,
regexp:
POSITIVE_INTEGER_REGEX
optional
:prerelease
,
type:
Boolean
,
desc:
'Include prerelease versions'
,
default:
true
end
namespace
'/query'
do
before
do
authorize_read_package!
(
authorized_user_project
)
end
# https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource
desc
'The NuGet Search Service'
do
detail
'This feature was introduced in GitLab 12.8'
end
route_setting
:authentication
,
deploy_token_allowed:
true
,
job_token_allowed: :basic_auth
,
basic_auth_personal_access_token:
true
get
format: :json
do
search_options
=
{
include_prerelease_versions:
params
[
:prerelease
],
per_page:
params
[
:take
],
padding:
params
[
:skip
]
}
search
=
Packages
::
Nuget
::
SearchService
.
new
(
authorized_user_project
,
params
[
:q
],
search_options
)
.
execute
track_package_event
(
'search_package'
,
:nuget
)
present
::
Packages
::
Nuget
::
SearchResultsPresenter
.
new
(
search
),
with:
::
API
::
Entities
::
Nuget
::
SearchResults
end
end
end
end
end
...
...
lib/gitlab/ci/parsers/coverage/cobertura.rb
View file @
6b9733cf
...
...
@@ -5,50 +5,113 @@ module Gitlab
module
Parsers
module
Coverage
class
Cobertura
CoberturaParserError
=
Class
.
new
(
Gitlab
::
Ci
::
Parsers
::
ParserError
)
InvalidXMLError
=
Class
.
new
(
Gitlab
::
Ci
::
Parsers
::
ParserError
)
InvalidLineInformationError
=
Class
.
new
(
Gitlab
::
Ci
::
Parsers
::
ParserError
)
def
parse!
(
xml_data
,
coverage_report
)
GO_SOURCE_PATTERN
=
'/usr/local/go/src'
MAX_SOURCES
=
100
def
parse!
(
xml_data
,
coverage_report
,
project_path:
nil
,
worktree_paths:
nil
)
root
=
Hash
.
from_xml
(
xml_data
)
parse_all
(
root
,
coverage_report
)
context
=
{
project_path:
project_path
,
paths:
worktree_paths
&
.
to_set
,
sources:
[]
}
parse_all
(
root
,
coverage_report
,
context
)
rescue
Nokogiri
::
XML
::
SyntaxError
raise
CoberturaParserError
,
"XML parsing failed"
rescue
raise
CoberturaParserError
,
"Cobertura parsing failed"
raise
InvalidXMLError
,
"XML parsing failed"
end
private
def
parse_all
(
root
,
coverage_report
)
def
parse_all
(
root
,
coverage_report
,
context
)
return
unless
root
.
present?
root
.
each
do
|
key
,
value
|
parse_node
(
key
,
value
,
coverage_report
)
parse_node
(
key
,
value
,
coverage_report
,
context
)
end
end
def
parse_node
(
key
,
value
,
coverage_report
)
return
if
key
==
'sources'
if
key
==
'class
'
def
parse_node
(
key
,
value
,
coverage_report
,
context
)
if
key
==
'sources'
&&
value
[
'source'
].
present?
parse_sources
(
value
[
'source'
],
context
)
elsif
key
==
'package
'
Array
.
wrap
(
value
).
each
do
|
item
|
parse_class
(
item
,
coverage_report
)
parse_package
(
item
,
coverage_report
,
context
)
end
elsif
key
==
'class'
# This means the cobertura XML does not have classes within package nodes.
# This is possible in some cases like in simple JS project structures
# running Jest.
Array
.
wrap
(
value
).
each
do
|
item
|
parse_class
(
item
,
coverage_report
,
context
)
end
elsif
value
.
is_a?
(
Hash
)
parse_all
(
value
,
coverage_report
)
parse_all
(
value
,
coverage_report
,
context
)
elsif
value
.
is_a?
(
Array
)
value
.
each
do
|
item
|
parse_all
(
item
,
coverage_report
)
parse_all
(
item
,
coverage_report
,
context
)
end
end
end
def
parse_class
(
file
,
coverage_report
)
def
parse_sources
(
sources
,
context
)
return
unless
context
[
:project_path
]
&&
context
[
:paths
]
sources
=
Array
.
wrap
(
sources
)
# TODO: Go cobertura has a different format with how their packages
# are included in the filename. So we can't rely on the sources.
# We'll deal with this later.
return
if
sources
.
include?
(
GO_SOURCE_PATTERN
)
sources
.
each
do
|
source
|
source
=
build_source_path
(
source
,
context
)
context
[
:sources
]
<<
source
if
source
.
present?
end
end
def
build_source_path
(
source
,
context
)
# | raw source | extracted |
# |-----------------------------|------------|
# | /builds/foo/test/SampleLib/ | SampleLib/ |
# | /builds/foo/test/something | something |
# | /builds/foo/test/ | nil |
# | /builds/foo/test | nil |
source
.
split
(
"
#{
context
[
:project_path
]
}
/"
,
2
)[
1
]
end
def
parse_package
(
package
,
coverage_report
,
context
)
classes
=
package
.
dig
(
'classes'
,
'class'
)
return
unless
classes
.
present?
matched_filenames
=
Array
.
wrap
(
classes
).
map
do
|
item
|
parse_class
(
item
,
coverage_report
,
context
)
end
# Remove these filenames from the paths to avoid conflict
# with other packages that may contain the same class filenames
remove_matched_filenames
(
matched_filenames
,
context
)
end
def
remove_matched_filenames
(
filenames
,
context
)
return
unless
context
[
:paths
]
filenames
.
each
{
|
f
|
context
[
:paths
].
delete
(
f
)
}
end
def
parse_class
(
file
,
coverage_report
,
context
)
return
unless
file
[
"filename"
].
present?
&&
file
[
"lines"
].
present?
parsed_lines
=
parse_lines
(
file
[
"lines"
])
filename
=
determine_filename
(
file
[
"filename"
],
context
)
coverage_report
.
add_file
(
filename
,
Hash
[
parsed_lines
])
if
filename
coverage_report
.
add_file
(
file
[
"filename"
],
Hash
[
parsed_lines
])
filename
end
def
parse_lines
(
lines
)
...
...
@@ -58,6 +121,27 @@ module Gitlab
# Using `Integer()` here to raise exception on invalid values
[
Integer
(
line
[
"number"
]),
Integer
(
line
[
"hits"
])]
end
rescue
raise
InvalidLineInformationError
,
"Line information had invalid values"
end
def
determine_filename
(
filename
,
context
)
return
filename
unless
context
[
:sources
].
any?
full_filename
=
nil
context
[
:sources
].
each_with_index
do
|
source
,
index
|
break
if
index
>=
MAX_SOURCES
break
if
full_filename
=
check_source
(
source
,
filename
,
context
)
end
full_filename
end
def
check_source
(
source
,
filename
,
context
)
full_path
=
File
.
join
(
source
,
filename
)
return
full_path
if
context
[
:paths
].
include?
(
full_path
)
end
end
end
...
...
lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
View file @
6b9733cf
...
...
@@ -49,3 +49,32 @@ dast:
-
if
:
$CI_COMMIT_BRANCH &&
$GITLAB_FEATURES =~ /\bdast\b/ &&
$DAST_API_SPECIFICATION
dast_unlicensed
:
stage
:
dast
allow_failure
:
true
variables
:
GIT_STRATEGY
:
none
rules
:
-
if
:
$DAST_DISABLED
when
:
never
-
if
:
$DAST_DISABLED_FOR_DEFAULT_BRANCH &&
$CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
when
:
never
-
if
:
$CI_DEFAULT_BRANCH != $CI_COMMIT_REF_NAME &&
$REVIEW_DISABLED && $DAST_WEBSITE ==
null
&&
$DAST_API_SPECIFICATION ==
null
when
:
never
-
if
:
$CI_COMMIT_BRANCH &&
$CI_KUBERNETES_ACTIVE &&
$GITLAB_FEATURES !~ /\bdast\b/
-
if
:
$CI_COMMIT_BRANCH &&
$GITLAB_FEATURES !~ /\bdast\b/ &&
$DAST_WEBSITE
-
if
:
$CI_COMMIT_BRANCH &&
$GITLAB_FEATURES !~ /\bdast\b/ &&
$DAST_API_SPECIFICATION
script
:
-
|
echo "Error: Your GitLab project is not licensed for DAST."
-
exit
1
locale/gitlab.pot
View file @
6b9733cf
...
...
@@ -31747,9 +31747,6 @@ msgstr ""
msgid "Your U2F device was registered!"
msgstr ""
msgid "Your Version"
msgstr ""
msgid "Your WebAuthn device did not send a valid JSON response."
msgstr ""
...
...
spec/factories/ci/job_artifacts.rb
View file @
6b9733cf
...
...
@@ -229,6 +229,16 @@ FactoryBot.define do
end
end
trait
:coverage_with_paths_not_relative_to_project_root
do
file_type
{
:cobertura
}
file_format
{
:gzip
}
after
(
:build
)
do
|
artifact
,
evaluator
|
artifact
.
file
=
fixture_file_upload
(
Rails
.
root
.
join
(
'spec/fixtures/cobertura/coverage_with_paths_not_relative_to_project_root.xml.gz'
),
'application/x-gzip'
)
end
end
trait
:coverage_with_corrupted_data
do
file_type
{
:cobertura
}
file_format
{
:gzip
}
...
...
spec/fixtures/cobertura/coverage_with_paths_not_relative_to_project_root.xml.gz
0 → 100644
View file @
6b9733cf
File added
spec/frontend/whats_new/components/app_spec.js
View file @
6b9733cf
import
{
createLocalVue
,
mount
}
from
'
@vue/test-utils
'
;
import
Vuex
from
'
vuex
'
;
import
{
GlDrawer
,
GlInfiniteScroll
,
GlTabs
}
from
'
@gitlab/ui
'
;
import
{
GlDrawer
,
GlInfiniteScroll
}
from
'
@gitlab/ui
'
;
import
{
mockTracking
,
unmockTracking
,
triggerEvent
}
from
'
helpers/tracking_helper
'
;
import
{
createMockDirective
,
getBinding
}
from
'
helpers/vue_mock_directive
'
;
import
App
from
'
~/whats_new/components/app.vue
'
;
...
...
@@ -16,18 +16,12 @@ const localVue = createLocalVue();
localVue
.
use
(
Vuex
);
describe
(
'
App
'
,
()
=>
{
const
propsData
=
{
storageKey
:
'
storage-key
'
};
let
wrapper
;
let
store
;
let
actions
;
let
state
;
let
trackingSpy
;
let
gitlabDotCom
=
true
;
const
buildProps
=
()
=>
({
storageKey
:
'
storage-key
'
,
versions
:
[
'
3.11
'
,
'
3.10
'
],
gitlabDotCom
,
});
const
buildWrapper
=
()
=>
{
actions
=
{
...
...
@@ -51,7 +45,7 @@ describe('App', () => {
wrapper
=
mount
(
App
,
{
localVue
,
store
,
propsData
:
buildProps
()
,
propsData
,
directives
:
{
GlResizeObserver
:
createMockDirective
(),
},
...
...
@@ -59,171 +53,112 @@ describe('App', () => {
};
const
findInfiniteScroll
=
()
=>
wrapper
.
find
(
GlInfiniteScroll
);
const
emitBottomReached
=
()
=>
findInfiniteScroll
().
vm
.
$emit
(
'
bottomReached
'
);
const
setup
=
async
()
=>
{
beforeEach
(
async
()
=>
{
document
.
body
.
dataset
.
page
=
'
test-page
'
;
document
.
body
.
dataset
.
namespaceId
=
'
namespace-840
'
;
trackingSpy
=
mockTracking
(
'
_category_
'
,
null
,
jest
.
spyOn
);
buildWrapper
();
wrapper
.
vm
.
$store
.
state
.
features
=
[
{
title
:
'
Whats New Drawer
'
,
url
:
'
www.url.com
'
,
release
:
3.11
},
];
wrapper
.
vm
.
$store
.
state
.
features
=
[{
title
:
'
Whats New Drawer
'
,
url
:
'
www.url.com
'
}];
wrapper
.
vm
.
$store
.
state
.
drawerBodyHeight
=
MOCK_DRAWER_BODY_HEIGHT
;
await
wrapper
.
vm
.
$nextTick
();
};
}
)
;
afterEach
(()
=>
{
wrapper
.
destroy
();
unmockTracking
();
});
describe
(
'
gitlab.com
'
,
()
=>
{
beforeEach
(()
=>
{
setup
();
});
const
getDrawer
=
()
=>
wrapper
.
find
(
GlDrawer
);
it
(
'
contains a drawer
'
,
()
=>
{
expect
(
getDrawer
().
exists
()).
toBe
(
true
);
});
it
(
'
dispatches openDrawer and tracking calls when mounted
'
,
()
=>
{
expect
(
actions
.
openDrawer
).
toHaveBeenCalledWith
(
expect
.
any
(
Object
),
'
storage-key
'
);
expect
(
trackingSpy
).
toHaveBeenCalledWith
(
undefined
,
'
click_whats_new_drawer
'
,
{
label
:
'
namespace_id
'
,
value
:
'
namespace-840
'
,
});
});
it
(
'
dispatches closeDrawer when clicking close
'
,
()
=>
{
getDrawer
().
vm
.
$emit
(
'
close
'
);
expect
(
actions
.
closeDrawer
).
toHaveBeenCalled
();
});
it
.
each
([
true
,
false
])(
'
passes open property
'
,
async
openState
=>
{
wrapper
.
vm
.
$store
.
state
.
open
=
openState
;
await
wrapper
.
vm
.
$nextTick
();
expect
(
getDrawer
().
props
(
'
open
'
)).
toBe
(
openState
);
});
it
(
'
renders features when provided via ajax
'
,
()
=>
{
expect
(
actions
.
fetchItems
).
toHaveBeenCalled
();
expect
(
wrapper
.
find
(
'
[data-test-id="feature-title"]
'
).
text
()).
toBe
(
'
Whats New Drawer
'
);
});
it
(
'
send an event when feature item is clicked
'
,
()
=>
{
trackingSpy
=
mockTracking
(
'
_category_
'
,
wrapper
.
element
,
jest
.
spyOn
);
const
getDrawer
=
()
=>
wrapper
.
find
(
GlDrawer
);
const
link
=
wrapper
.
find
(
'
.whats-new-item-title-link
'
);
triggerEvent
(
link
.
element
);
it
(
'
contains a drawer
'
,
()
=>
{
expect
(
getDrawer
().
exists
()).
toBe
(
true
);
});
expect
(
trackingSpy
.
mock
.
calls
[
1
]).
toMatchObject
([
'
_category_
'
,
'
click_whats_new_item
'
,
{
label
:
'
Whats New Drawer
'
,
property
:
'
www.url.com
'
,
},
]);
it
(
'
dispatches openDrawer and tracking calls when mounted
'
,
()
=>
{
expect
(
actions
.
openDrawer
).
toHaveBeenCalledWith
(
expect
.
any
(
Object
),
'
storage-key
'
);
expect
(
trackingSpy
).
toHaveBeenCalledWith
(
undefined
,
'
click_whats_new_drawer
'
,
{
label
:
'
namespace_id
'
,
value
:
'
namespace-840
'
,
});
});
it
(
'
renders infinite scroll
'
,
()
=>
{
const
scroll
=
findInfiniteScroll
();
expect
(
scroll
.
props
()).
toMatchObject
({
fetchedItems
:
wrapper
.
vm
.
$store
.
state
.
features
.
length
,
maxListHeight
:
MOCK_DRAWER_BODY_HEIGHT
,
});
});
it
(
'
dispatches closeDrawer when clicking close
'
,
()
=>
{
getDrawer
().
vm
.
$emit
(
'
close
'
);
expect
(
actions
.
closeDrawer
).
toHaveBeenCalled
();
});
describe
(
'
bottomReached
'
,
()
=>
{
const
emitBottomReached
=
()
=>
findInfiniteScroll
().
vm
.
$emit
(
'
bottomReached
'
)
;
it
.
each
([
true
,
false
])(
'
passes open property
'
,
async
openState
=>
{
wrapper
.
vm
.
$store
.
state
.
open
=
openState
;
beforeEach
(()
=>
{
actions
.
fetchItems
.
mockClear
();
});
await
wrapper
.
vm
.
$nextTick
();
it
(
'
when nextPage exists it calls fetchItems
'
,
()
=>
{
wrapper
.
vm
.
$store
.
state
.
pageInfo
=
{
nextPage
:
840
};
emitBottomReached
();
expect
(
getDrawer
().
props
(
'
open
'
)).
toBe
(
openState
);
});
expect
(
actions
.
fetchItems
).
toHaveBeenCalledWith
(
expect
.
anything
(),
{
page
:
840
});
});
it
(
'
renders features when provided via ajax
'
,
()
=>
{
expect
(
actions
.
fetchItems
).
toHaveBeenCalled
();
expect
(
wrapper
.
find
(
'
h5
'
).
text
()).
toBe
(
'
Whats New Drawer
'
);
});
it
(
'
when nextPage does not exist it does not call fetchItems
'
,
()
=>
{
wrapper
.
vm
.
$store
.
state
.
pageInfo
=
{
nextPage
:
null
};
emitBottomReached
();
it
(
'
send an event when feature item is clicked
'
,
()
=>
{
trackingSpy
=
mockTracking
(
'
_category_
'
,
wrapper
.
element
,
jest
.
spyOn
);
expect
(
actions
.
fetchItems
).
not
.
toHaveBeenCalled
();
});
});
const
link
=
wrapper
.
find
(
'
.whats-new-item-title-link
'
);
triggerEvent
(
link
.
element
);
it
(
'
calls getDrawerBodyHeight and setDrawerBodyHeight when resize directive is triggered
'
,
()
=>
{
const
{
value
}
=
getBinding
(
getDrawer
().
element
,
'
gl-resize-observer
'
);
value
();
expect
(
trackingSpy
.
mock
.
calls
[
1
]).
toMatchObject
([
'
_category_
'
,
'
click_whats_new_item
'
,
{
label
:
'
Whats New Drawer
'
,
property
:
'
www.url.com
'
,
},
]);
});
expect
(
getDrawerBodyHeight
).
toHaveBeenCalledWith
(
wrapper
.
find
(
GlDrawer
).
element
);
it
(
'
renders infinite scroll
'
,
()
=>
{
const
scroll
=
findInfiniteScroll
();
expect
(
actions
.
setDrawerBodyHeight
).
toHaveBeenCalledWith
(
expect
.
any
(
Object
),
MOCK_DRAWER_BODY_HEIGHT
,
);
expect
(
scroll
.
props
()).
toMatchObject
({
fetchedItems
:
wrapper
.
vm
.
$store
.
state
.
features
.
length
,
maxListHeight
:
MOCK_DRAWER_BODY_HEIGHT
,
});
});
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
));
};
describe
(
'
bottomReached
'
,
()
=>
{
beforeEach
(()
=>
{
gitlabDotCom
=
false
;
setup
();
actions
.
fetchItems
.
mockClear
();
});
it
(
'
renders tabs with drawer body height and content
'
,
()
=>
{
const
scroll
=
findInfiniteScroll
()
;
const
tabs
=
findTabs
();
it
(
'
when nextPage exists it calls fetchItems
'
,
()
=>
{
wrapper
.
vm
.
$store
.
state
.
pageInfo
=
{
nextPage
:
840
}
;
emitBottomReached
();
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
'
);
expect
(
actions
.
fetchItems
).
toHaveBeenCalledWith
(
expect
.
anything
(),
840
);
});
describe
(
'
fetchVersion
'
,
()
=>
{
beforeEach
(()
=>
{
actions
.
fetchItems
.
mockClear
();
});
it
(
'
when nextPage does not exist it does not call fetchItems
'
,
()
=>
{
wrapper
.
vm
.
$store
.
state
.
pageInfo
=
{
nextPage
:
null
};
emitBottomReached
();
it
(
'
when version isnt fetched, clicking a tab calls fetchItems
'
,
async
()
=>
{
const
fetchVersionSpy
=
jest
.
spyOn
(
wrapper
.
vm
,
'
fetchVersion
'
);
await
clickSecondTab
(
);
expect
(
actions
.
fetchItems
).
not
.
toHaveBeenCalled
();
}
);
}
);
expect
(
fetchVersionSpy
).
toHaveBeenCalledWith
(
'
3.10
'
);
expect
(
actions
.
fetchItems
).
toHaveBeenCalledWith
(
expect
.
anything
(),
{
version
:
'
3.10
'
});
});
it
(
'
calls getDrawerBodyHeight and setDrawerBodyHeight when resize directive is triggered
'
,
()
=>
{
const
{
value
}
=
getBinding
(
getDrawer
().
element
,
'
gl-resize-observer
'
);
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
();
value
();
const
fetchVersionSpy
=
jest
.
spyOn
(
wrapper
.
vm
,
'
fetchVersion
'
);
await
clickSecondTab
();
expect
(
getDrawerBodyHeight
).
toHaveBeenCalledWith
(
wrapper
.
find
(
GlDrawer
).
element
);
expect
(
fetchVersionSpy
).
toHaveBeenCalledWith
(
'
3.10
'
);
expect
(
actions
.
fetchItems
).
not
.
toHaveBeenCalled
();
expect
(
wrapper
.
find
(
'
.tab-pane.active h5
'
).
text
()).
toBe
(
'
GitLab Stories
'
);
});
});
expect
(
actions
.
setDrawerBodyHeight
).
toHaveBeenCalledWith
(
expect
.
any
(
Object
),
MOCK_DRAWER_BODY_HEIGHT
,
);
});
});
spec/frontend/whats_new/store/actions_spec.js
View file @
6b9733cf
...
...
@@ -41,23 +41,6 @@ describe('whats new actions', () => {
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
'
,
()
=>
{
testAction
(
actions
.
fetchItems
,
{},
{
fetching
:
true
},
[]);
});
...
...
spec/helpers/whats_new_helper_spec.rb
View file @
6b9733cf
...
...
@@ -10,7 +10,7 @@ RSpec.describe WhatsNewHelper do
let
(
:release_item
)
{
double
(
:item
)
}
before
do
allow
(
ReleaseHighlight
).
to
receive
(
:
versions
).
and_return
([
84.0
]
)
allow
(
ReleaseHighlight
).
to
receive
(
:
most_recent_version
).
and_return
(
84.0
)
end
it
{
is_expected
.
to
eq
(
'display-whats-new-notification-84.0'
)
}
...
...
@@ -18,7 +18,7 @@ RSpec.describe WhatsNewHelper do
context
'when most recent release highlights do NOT exist'
do
before
do
allow
(
ReleaseHighlight
).
to
receive
(
:
versions
).
and_return
(
nil
)
allow
(
ReleaseHighlight
).
to
receive
(
:
most_recent_version
).
and_return
(
nil
)
end
it
{
is_expected
.
to
be_nil
}
...
...
@@ -44,14 +44,4 @@ RSpec.describe WhatsNewHelper do
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
spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb
View file @
6b9733cf
This diff is collapsed.
Click to expand it.
spec/models/ci/build_spec.rb
View file @
6b9733cf
...
...
@@ -4059,13 +4059,40 @@ RSpec.describe Ci::Build do
end
end
context
'when there is a Cobertura coverage report with class filename paths not relative to project root'
do
before
do
allow
(
build
.
project
).
to
receive
(
:full_path
).
and_return
(
'root/javademo'
)
allow
(
build
.
pipeline
).
to
receive
(
:all_worktree_paths
).
and_return
([
'src/main/java/com/example/javademo/User.java'
])
create
(
:ci_job_artifact
,
:coverage_with_paths_not_relative_to_project_root
,
job:
build
,
project:
build
.
project
)
end
it
'parses blobs and add the results to the coverage report with corrected paths'
do
expect
{
subject
}.
not_to
raise_error
expect
(
coverage_report
.
files
.
keys
).
to
match_array
([
'src/main/java/com/example/javademo/User.java'
])
end
context
'and smart_cobertura_parser feature flag is disabled'
do
before
do
stub_feature_flags
(
smart_cobertura_parser:
false
)
end
it
'parses blobs and add the results to the coverage report with unmodified paths'
do
expect
{
subject
}.
not_to
raise_error
expect
(
coverage_report
.
files
.
keys
).
to
match_array
([
'com/example/javademo/User.java'
])
end
end
end
context
'when there is a corrupted Cobertura coverage report'
do
before
do
create
(
:ci_job_artifact
,
:coverage_with_corrupted_data
,
job:
build
,
project:
build
.
project
)
end
it
'raises an error'
do
expect
{
subject
}.
to
raise_error
(
Gitlab
::
Ci
::
Parsers
::
Coverage
::
Cobertura
::
CoberturaParser
Error
)
expect
{
subject
}.
to
raise_error
(
Gitlab
::
Ci
::
Parsers
::
Coverage
::
Cobertura
::
InvalidLineInformation
Error
)
end
end
end
...
...
spec/models/ci/pipeline_spec.rb
View file @
6b9733cf
...
...
@@ -3418,6 +3418,16 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
])
end
it
'does not execute N+1 queries'
do
single_build_pipeline
=
create
(
:ci_empty_pipeline
,
status: :created
,
project:
project
)
single_rspec
=
create
(
:ci_build
,
:success
,
name:
'rspec'
,
pipeline:
single_build_pipeline
,
project:
project
)
create
(
:ci_job_artifact
,
:cobertura
,
job:
single_rspec
,
project:
project
)
control
=
ActiveRecord
::
QueryRecorder
.
new
{
single_build_pipeline
.
coverage_reports
}
expect
{
subject
}.
not_to
exceed_query_limit
(
control
)
end
context
'when builds are retried'
do
let!
(
:build_rspec
)
{
create
(
:ci_build
,
:retried
,
:success
,
name:
'rspec'
,
pipeline:
pipeline
,
project:
project
)
}
let!
(
:build_golang
)
{
create
(
:ci_build
,
:retried
,
:success
,
name:
'golang'
,
pipeline:
pipeline
,
project:
project
)
}
...
...
spec/models/release_highlight_spec.rb
View file @
6b9733cf
...
...
@@ -3,44 +3,21 @@
require
'spec_helper'
RSpec
.
describe
ReleaseHighlight
do
let
(
:fixture_dir_glob
)
{
Dir
.
glob
(
File
.
join
(
'spec'
,
'fixtures'
,
'whats_new'
,
'*.yml'
))
}
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
describe
'#paginated'
do
let
(
:fixture_dir_glob
)
{
Dir
.
glob
(
File
.
join
(
'spec'
,
'fixtures'
,
'whats_new'
,
'*.yml'
))
}
let
(
:cache_mock
)
{
double
(
:cache_mock
)
}
let
(
:dot_com
)
{
false
}
before
do
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
(
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
context
'with page param'
do
...
...
@@ -113,51 +90,46 @@ RSpec.describe ReleaseHighlight do
end
end
describe
'.most_recent_
item_count
'
do
subject
{
ReleaseHighlight
.
most_recent_
item_count
}
describe
'.most_recent_
version
'
do
subject
{
ReleaseHighlight
.
most_recent_
version
}
context
'when recent release items exist'
do
it
'returns the count from the most recent file'
do
allow
(
ReleaseHighlight
).
to
receive
(
:paginated
).
and_return
(
double
(
:paginated
,
items:
[
double
(
:item
)]))
context
'when version exist'
do
let
(
:release_item
)
{
double
(
:item
)
}
expect
(
subject
).
to
eq
(
1
)
before
do
allow
(
ReleaseHighlight
).
to
receive
(
:paginated
).
and_return
({
items:
[
release_item
]
})
allow
(
release_item
).
to
receive
(
:[]
).
with
(
'release'
).
and_return
(
84.0
)
end
it
{
is_expected
.
to
eq
(
84.0
)
}
end
context
'when
recent release item
s do NOT exist'
do
it
'returns nil'
do
context
'when
most recent release highlight
s do NOT exist'
do
before
do
allow
(
ReleaseHighlight
).
to
receive
(
:paginated
).
and_return
(
nil
)
expect
(
subject
).
to
be_nil
end
it
{
is_expected
.
to
be_nil
}
end
end
describe
'.versions'
do
it
'returns versions from the file paths'
do
expect
(
ReleaseHighlight
.
versions
).
to
eq
([
'1.5'
,
'1.2'
,
'1.1'
])
end
describe
'#most_recent_item_count'
do
subject
{
ReleaseHighlight
.
most_recent_item_count
}
context
'when there are more than 12 versions'
do
let
(
:file_paths
)
do
i
=
0
Array
.
new
(
20
)
{
"20201225_01_
#{
i
+=
1
}
.yml"
}
end
context
'when recent release items exist'
do
it
'returns the count from the most recent file'
do
allow
(
ReleaseHighlight
).
to
receive
(
:paginated
).
and_return
({
items:
[
double
(
:item
)]
})
it
'limits to 12 versions'
do
allow
(
ReleaseHighlight
).
to
receive
(
:file_paths
).
and_return
(
file_paths
)
expect
(
ReleaseHighlight
.
versions
.
count
).
to
eq
(
12
)
expect
(
subject
).
to
eq
(
1
)
end
end
end
describe
'QueryResult'
do
subject
{
ReleaseHighlight
::
QueryResult
.
new
(
items:
items
,
next_page:
2
)
}
let
(
:items
)
{
[
:item
]
}
context
'when recent release items do NOT exist'
do
it
'returns nil'
do
allow
(
ReleaseHighlight
).
to
receive
(
:paginated
).
and_return
(
nil
)
it
'responds to map'
do
e
xpect
(
subject
.
map
(
&
:to_s
)).
to
eq
(
items
.
map
(
&
:to_s
))
expect
(
subject
).
to
be_nil
e
nd
end
end
end
spec/requests/api/nuget_packages_spec.rb
→
spec/requests/api/nuget_p
roject_p
ackages_spec.rb
View file @
6b9733cf
This diff is collapsed.
Click to expand it.
spec/requests/whats_new_controller_spec.rb
View file @
6b9733cf
...
...
@@ -4,22 +4,22 @@ require 'spec_helper'
RSpec
.
describe
WhatsNewController
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
before
do
stub_feature_flags
(
whats_new_drawer:
true
)
end
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
allow
(
ReleaseHighlight
).
to
receive
(
:paginated
).
with
(
page:
1
).
and_return
(
highlights
)
allow
(
ReleaseHighlight
).
to
receive
(
:paginated
).
with
(
page:
1
).
and_return
(
most_recent
)
allow
(
Gitlab
::
WhatsNew
::
ItemPresenter
).
to
receive
(
:present
).
with
(
item
).
and_return
(
item
)
get
whats_new_path
,
xhr:
true
expect
(
response
.
body
).
to
eq
(
highlights
.
items
.
to_json
)
expect
(
response
.
body
).
to
eq
(
most_recent
[
:items
]
.
to_json
)
expect
(
response
.
headers
[
'X-Next-Page'
]).
to
eq
(
2
)
end
end
...
...
@@ -37,18 +37,6 @@ RSpec.describe WhatsNewController do
expect
(
response
).
to
have_gitlab_http_status
(
:not_found
)
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
context
'with whats_new_drawer feature disabled'
do
...
...
spec/services/ci/pipelines/create_artifact_service_spec.rb
View file @
6b9733cf
...
...
@@ -7,7 +7,8 @@ RSpec.describe ::Ci::Pipelines::CreateArtifactService do
subject
{
described_class
.
new
.
execute
(
pipeline
)
}
context
'when pipeline has coverage reports'
do
let
(
:pipeline
)
{
create
(
:ci_pipeline
,
:with_coverage_reports
)
}
let
(
:project
)
{
create
(
:project
,
:repository
)
}
let
(
:pipeline
)
{
create
(
:ci_pipeline
,
:with_coverage_reports
,
project:
project
)
}
context
'when pipeline is finished'
do
it
'creates a pipeline artifact'
do
...
...
spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb
0 → 100644
View file @
6b9733cf
This diff is collapsed.
Click to expand it.
spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
View file @
6b9733cf
...
...
@@ -26,7 +26,7 @@ RSpec.shared_examples 'process nuget service index request' do |user_type, statu
it_behaves_like
'returning response status'
,
status
it_behaves_like
'a package tracking event'
,
described_class
.
name
,
'cli_metadata'
it_behaves_like
'a package tracking event'
,
'API::NugetPackages'
,
'cli_metadata'
it
'returns a valid json response'
do
subject
...
...
@@ -169,7 +169,7 @@ RSpec.shared_examples 'process nuget upload' do |user_type, status, add_member =
context
'with correct params'
do
it_behaves_like
'package workhorse uploads'
it_behaves_like
'creates nuget package files'
it_behaves_like
'a package tracking event'
,
described_class
.
name
,
'push_package'
it_behaves_like
'a package tracking event'
,
'API::NugetPackages'
,
'push_package'
end
end
...
...
@@ -286,7 +286,7 @@ RSpec.shared_examples 'process nuget download content request' do |user_type, st
it_behaves_like
'returning response status'
,
status
it_behaves_like
'a package tracking event'
,
described_class
.
name
,
'pull_package'
it_behaves_like
'a package tracking event'
,
'API::NugetPackages'
,
'pull_package'
it
'returns a valid package archive'
do
subject
...
...
@@ -336,7 +336,7 @@ RSpec.shared_examples 'process nuget search request' do |user_type, status, add_
it_behaves_like
'returns a valid json search response'
,
status
,
4
,
[
1
,
5
,
5
,
1
]
it_behaves_like
'a package tracking event'
,
described_class
.
name
,
'search_package'
it_behaves_like
'a package tracking event'
,
'API::NugetPackages'
,
'search_package'
context
'with skip set to 2'
do
let
(
:skip
)
{
2
}
...
...
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