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
13d7df98
Commit
13d7df98
authored
Aug 30, 2021
by
GitLab Bot
Browse files
Options
Browse Files
Download
Plain Diff
Automatic merge of gitlab-org/gitlab master
parents
8bce1b3f
48b17dba
Changes
39
Hide whitespace changes
Inline
Side-by-side
Showing
39 changed files
with
317 additions
and
481 deletions
+317
-481
app/assets/javascripts/main.js
app/assets/javascripts/main.js
+1
-0
app/assets/javascripts/main_jh.js
app/assets/javascripts/main_jh.js
+1
-0
app/assets/javascripts/sidebar/mount_sidebar.js
app/assets/javascripts/sidebar/mount_sidebar.js
+1
-0
app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue
...onents/sidebar/labels_select_widget/dropdown_contents.vue
+4
-4
app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue
...ar/labels_select_widget/dropdown_contents_create_view.vue
+29
-0
app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue
...ar/labels_select_widget/dropdown_contents_labels_view.vue
+4
-4
app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_title.vue
...omponents/sidebar/labels_select_widget/dropdown_title.vue
+0
-40
app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/create_label.mutation.graphql
...abels_select_widget/graphql/create_label.mutation.graphql
+0
-2
app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue
...nents/sidebar/labels_select_widget/labels_select_root.vue
+56
-133
app/finders/packages/helm/package_files_finder.rb
app/finders/packages/helm/package_files_finder.rb
+2
-0
app/models/packages/package_file.rb
app/models/packages/package_file.rb
+4
-0
app/presenters/project_presenter.rb
app/presenters/project_presenter.rb
+3
-15
app/views/users/show.html.haml
app/views/users/show.html.haml
+7
-0
app/workers/packages/debian/process_changes_worker.rb
app/workers/packages/debian/process_changes_worker.rb
+1
-6
app/workers/packages/helm/extraction_worker.rb
app/workers/packages/helm/extraction_worker.rb
+1
-3
config/feature_flags/experiment/repo_integrations_link.yml
config/feature_flags/experiment/repo_integrations_link.yml
+0
-8
config/routes/user.rb
config/routes/user.rb
+1
-1
config/webpack.config.js
config/webpack.config.js
+19
-2
doc/user/project/repository/gpg_signed_commits/index.md
doc/user/project/repository/gpg_signed_commits/index.md
+5
-0
jest.config.base.js
jest.config.base.js
+22
-0
jest.config.integration.js
jest.config.integration.js
+4
-0
lib/api/helm_packages.rb
lib/api/helm_packages.rb
+1
-1
lib/gitlab/gon_helper.rb
lib/gitlab/gon_helper.rb
+1
-0
locale/gitlab.pot
locale/gitlab.pot
+5
-0
spec/features/users/show_spec.rb
spec/features/users/show_spec.rb
+23
-0
spec/finders/packages/helm/package_files_finder_spec.rb
spec/finders/packages/helm/package_files_finder_spec.rb
+22
-13
spec/frontend/tracking_spec.js
spec/frontend/tracking_spec.js
+1
-1
spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view_spec.js
...abels_select_widget/dropdown_contents_create_view_spec.js
+14
-1
spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view_spec.js
...abels_select_widget/dropdown_contents_labels_view_spec.js
+1
-7
spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_title_spec.js
...nents/sidebar/labels_select_widget/dropdown_title_spec.js
+0
-61
spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js
...s/sidebar/labels_select_widget/labels_select_root_spec.js
+33
-136
spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js
...ared/components/sidebar/labels_select_widget/mock_data.js
+0
-2
spec/models/packages/package_file_spec.rb
spec/models/packages/package_file_spec.rb
+4
-0
spec/presenters/project_presenter_spec.rb
spec/presenters/project_presenter_spec.rb
+10
-28
spec/requests/api/helm_packages_spec.rb
spec/requests/api/helm_packages_spec.rb
+5
-3
spec/support/database/prevent_cross_joins.rb
spec/support/database/prevent_cross_joins.rb
+3
-6
spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb
...ed_examples/requests/api/helm_packages_shared_examples.rb
+8
-1
spec/support_specs/database/prevent_cross_joins_spec.rb
spec/support_specs/database/prevent_cross_joins_spec.rb
+9
-1
spec/workers/packages/helm/extraction_worker_spec.rb
spec/workers/packages/helm/extraction_worker_spec.rb
+12
-2
No files found.
app/assets/javascripts/main.js
View file @
13d7df98
...
...
@@ -37,6 +37,7 @@ import initBroadcastNotifications from './broadcast_notification';
import
{
initTopNav
}
from
'
./nav
'
;
import
'
ee_else_ce/main_ee
'
;
import
'
jh_else_ce/main_jh
'
;
applyGitLabUIConfig
();
...
...
app/assets/javascripts/main_jh.js
0 → 100644
View file @
13d7df98
// This is an empty file to satisfy jh_else_ce import for the JH main entry point
app/assets/javascripts/sidebar/mount_sidebar.js
View file @
13d7df98
...
...
@@ -258,6 +258,7 @@ export function mountSidebarLabels() {
allowScopedLabels
:
parseBoolean
(
el
.
dataset
.
allowScopedLabels
),
initiallySelectedLabels
:
JSON
.
parse
(
el
.
dataset
.
selectedLabels
),
variant
:
DropdownVariant
.
Sidebar
,
canUpdate
:
parseBoolean
(
el
.
dataset
.
canEdit
),
},
render
:
(
createElement
)
=>
createElement
(
SidebarLabels
),
});
...
...
app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue
View file @
13d7df98
...
...
@@ -60,7 +60,7 @@ export default {
},
},
methods
:
{
...
mapActions
([
'
toggleDropdownContentsCreateView
'
,
'
toggleDropdownContents
'
]),
...
mapActions
([
'
toggleDropdownContentsCreateView
'
]),
},
};
</
script
>
...
...
@@ -83,7 +83,7 @@ export default {
size=
"small"
class=
"js-btn-back dropdown-header-button p-0"
icon=
"arrow-left"
@
click=
"toggleDropdownContentsCreateView"
@
click
.stop
=
"toggleDropdownContentsCreateView"
/>
<span
class=
"flex-grow-1"
>
{{
dropdownTitle
}}
</span>
<gl-button
...
...
@@ -92,7 +92,7 @@ export default {
size=
"small"
class=
"dropdown-header-button gl-p-0!"
icon=
"close"
@
click=
"
toggleDropdownContents
"
@
click=
"
$emit('closeDropdown')
"
/>
</div>
<component
...
...
@@ -103,7 +103,7 @@ export default {
:footer-create-label-title=
"footerCreateLabelTitle"
:footer-manage-label-title=
"footerManageLabelTitle"
@
hideCreateView=
"toggleDropdownContentsCreateView"
@
closeDropdown=
"$emit('closeDropdown
', $event)"
@
setLabels=
"$emit('setLabels
', $event)"
@
toggleDropdownContentsCreateView=
"toggleDropdownContentsCreateView"
/>
</div>
...
...
app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue
View file @
13d7df98
<
script
>
import
{
GlTooltipDirective
,
GlButton
,
GlFormInput
,
GlLink
,
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
import
produce
from
'
immer
'
;
import
createFlash
from
'
~/flash
'
;
import
{
__
}
from
'
~/locale
'
;
import
createLabelMutation
from
'
./graphql/create_label.mutation.graphql
'
;
import
projectLabelsQuery
from
'
./graphql/project_labels.query.graphql
'
;
const
errorMessage
=
__
(
'
Error creating label.
'
);
...
...
@@ -47,6 +49,25 @@ export default {
handleColorClick
(
color
)
{
this
.
selectedColor
=
this
.
getColorCode
(
color
);
},
updateLabelsInCache
(
store
,
label
)
{
const
sourceData
=
store
.
readQuery
({
query
:
projectLabelsQuery
,
variables
:
{
fullPath
:
this
.
projectPath
,
searchTerm
:
''
},
});
const
collator
=
new
Intl
.
Collator
(
'
en
'
);
const
data
=
produce
(
sourceData
,
(
draftData
)
=>
{
const
{
nodes
}
=
draftData
.
workspace
.
labels
;
nodes
.
push
(
label
);
nodes
.
sort
((
a
,
b
)
=>
collator
.
compare
(
a
.
title
,
b
.
title
));
});
store
.
writeQuery
({
query
:
projectLabelsQuery
,
variables
:
{
fullPath
:
this
.
projectPath
,
searchTerm
:
''
},
data
,
});
},
async
createLabel
()
{
this
.
labelCreateInProgress
=
true
;
try
{
...
...
@@ -59,6 +80,14 @@ export default {
color
:
this
.
selectedColor
,
projectPath
:
this
.
projectPath
,
},
update
:
(
store
,
{
data
:
{
labelCreate
:
{
label
},
},
},
)
=>
this
.
updateLabelsInCache
(
store
,
label
),
});
if
(
labelCreate
.
errors
.
length
)
{
createFlash
({
message
:
errorMessage
});
...
...
app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue
View file @
13d7df98
...
...
@@ -112,7 +112,7 @@ export default {
this
.
debouncedSearchKeyUpdate
=
debounce
(
this
.
setSearchKey
,
DEFAULT_DEBOUNCE_AND_THROTTLE_MS
);
},
beforeDestroy
()
{
this
.
$emit
(
'
closeDropdown
'
,
this
.
localSelectedLabels
);
this
.
$emit
(
'
setLabels
'
,
this
.
localSelectedLabels
);
this
.
debouncedSearchKeyUpdate
.
cancel
();
},
methods
:
{
...
...
@@ -166,7 +166,7 @@ export default {
this
.
updateSelectedLabels
(
this
.
visibleLabels
[
this
.
currentHighlightItem
]);
this
.
searchKey
=
''
;
}
else
if
(
e
.
keyCode
===
ESC_KEY_CODE
)
{
this
.
$emit
(
'
closeDropdown
'
,
this
.
localSelectedLabels
);
this
.
$emit
(
'
setLabels
'
,
this
.
localSelectedLabels
);
}
if
(
e
.
keyCode
!==
ESC_KEY_CODE
)
{
...
...
@@ -180,7 +180,7 @@ export default {
handleLabelClick
(
label
)
{
this
.
updateSelectedLabels
(
label
);
if
(
!
this
.
allowMultiselect
)
{
this
.
$emit
(
'
closeDropdown
'
,
this
.
localSelectedLabels
);
this
.
$emit
(
'
setLabels
'
,
this
.
localSelectedLabels
);
}
},
setSearchKey
(
value
)
{
...
...
@@ -240,7 +240,7 @@ export default {
<gl-link
class=
"gl-display-flex gl-flex-direction-row gl-w-full gl-overflow-break-word label-item"
data-testid=
"create-label-button"
@
click=
"$emit('toggleDropdownContentsCreateView')"
@
click
.stop
=
"$emit('toggleDropdownContentsCreateView')"
>
{{
footerCreateLabelTitle
}}
</gl-link>
...
...
app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_title.vue
deleted
100644 → 0
View file @
8bce1b3f
<
script
>
import
{
GlButton
,
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
import
{
mapState
,
mapActions
}
from
'
vuex
'
;
export
default
{
components
:
{
GlButton
,
GlLoadingIcon
,
},
props
:
{
labelsSelectInProgress
:
{
type
:
Boolean
,
required
:
true
,
},
},
computed
:
{
...
mapState
([
'
allowLabelEdit
'
,
'
labelsFetchInProgress
'
]),
},
methods
:
{
...
mapActions
([
'
toggleDropdownContents
'
]),
},
};
</
script
>
<
template
>
<div
class=
"title hide-collapsed gl-mb-3"
>
{{
__
(
'
Labels
'
)
}}
<template
v-if=
"allowLabelEdit"
>
<gl-loading-icon
v-show=
"labelsSelectInProgress"
size=
"sm"
inline
/>
<gl-button
category=
"tertiary"
size=
"small"
class=
"float-right js-sidebar-dropdown-toggle gl-mr-n2"
data-qa-selector=
"labels_edit_button"
@
click=
"toggleDropdownContents"
>
{{
__
(
'
Edit
'
)
}}
</gl-button
>
</
template
>
</div>
</template>
app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/create_label.mutation.graphql
View file @
13d7df98
...
...
@@ -6,9 +6,7 @@ mutation createLabel($title: String!, $color: String, $projectPath: ID, $groupPa
id
color
description
descriptionHtml
title
textColor
}
errors
}
...
...
app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue
View file @
13d7df98
<
script
>
import
$
from
'
jquery
'
;
import
Vue
from
'
vue
'
;
import
Vuex
,
{
mapState
,
mapActions
,
mapGetters
}
from
'
vuex
'
;
import
{
isInViewport
}
from
'
~/lib/utils/common_utils
'
;
import
{
__
}
from
'
~/locale
'
;
import
SidebarEditableItem
from
'
~/sidebar/components/sidebar_editable_item.vue
'
;
import
{
DropdownVariant
}
from
'
./constants
'
;
import
DropdownButton
from
'
./dropdown_button.vue
'
;
import
DropdownContents
from
'
./dropdown_contents.vue
'
;
import
DropdownTitle
from
'
./dropdown_title.vue
'
;
import
DropdownValue
from
'
./dropdown_value.vue
'
;
import
DropdownValueCollapsed
from
'
./dropdown_value_collapsed.vue
'
;
import
issueLabelsQuery
from
'
./graphql/issue_labels.query.graphql
'
;
...
...
@@ -19,11 +17,11 @@ Vue.use(Vuex);
export
default
{
store
:
new
Vuex
.
Store
(
labelsSelectModule
()),
components
:
{
DropdownTitle
,
DropdownValue
,
DropdownButton
,
DropdownContents
,
DropdownValueCollapsed
,
SidebarEditableItem
,
},
inject
:
[
'
iid
'
,
'
projectPath
'
],
props
:
{
...
...
@@ -139,15 +137,12 @@ export default {
},
},
computed
:
{
...
mapState
([
'
showDropdown
Button
'
,
'
showDropdown
Contents
'
]),
...
mapState
([
'
showDropdownContents
'
]),
...
mapGetters
([
'
isDropdownVariantSidebar
'
,
'
isDropdownVariantStandalone
'
,
'
isDropdownVariantEmbedded
'
,
]),
dropdownButtonVisible
()
{
return
this
.
isDropdownVariantSidebar
?
this
.
showDropdownButton
:
true
;
},
},
watch
:
{
selectedLabels
(
selectedLabels
)
{
...
...
@@ -182,99 +177,20 @@ export default {
footerCreateLabelTitle
:
this
.
footerCreateLabelTitle
,
footerManageLabelTitle
:
this
.
footerManageLabelTitle
,
});
this
.
$store
.
subscribeAction
({
after
:
this
.
handleVuexActionDispatch
,
});
document
.
addEventListener
(
'
mousedown
'
,
this
.
handleDocumentMousedown
);
document
.
addEventListener
(
'
click
'
,
this
.
handleDocumentClick
);
},
beforeDestroy
()
{
document
.
removeEventListener
(
'
mousedown
'
,
this
.
handleDocumentMousedown
);
document
.
removeEventListener
(
'
click
'
,
this
.
handleDocumentClick
);
},
methods
:
{
...
mapActions
([
'
setInitialState
'
,
'
toggleDropdownContents
'
]),
/**
* This method stores a mousedown event's target.
* Required by the click listener because the click
* event itself has no reference to this element.
*/
handleDocumentMousedown
({
target
})
{
this
.
mousedownTarget
=
target
;
},
/**
* This method listens for document-wide click event
* and toggle dropdown if user clicks anywhere outside
* the dropdown while dropdown is visible.
*/
handleDocumentClick
({
target
})
{
// We also perform the toggle exception check for the
// last mousedown event's target to avoid hiding the
// box when the mousedown happened inside the box and
// only the mouseup did not.
if
(
this
.
showDropdownContents
&&
!
this
.
preventDropdownToggleOnClick
(
target
)
&&
!
this
.
preventDropdownToggleOnClick
(
this
.
mousedownTarget
)
)
{
this
.
toggleDropdownContents
();
}
},
/**
* This method checks whether a given click target
* should prevent the dropdown from being toggled.
*/
preventDropdownToggleOnClick
(
target
)
{
// This approach of element detection is needed
// as the dropdown wrapper is not using `GlDropdown` as
// it will also require us to use `BDropdownForm`
// which is yet to be implemented in GitLab UI.
const
hasExceptionClass
=
[
'
js-dropdown-button
'
,
'
js-btn-cancel-create
'
,
'
js-sidebar-dropdown-toggle
'
,
].
some
(
(
className
)
=>
target
?.
classList
.
contains
(
className
)
||
target
?.
parentElement
?.
classList
.
contains
(
className
),
);
const
hasExceptionParent
=
[
'
.js-btn-back
'
,
'
.js-labels-list
'
].
some
(
(
className
)
=>
$
(
target
).
parents
(
className
).
length
,
);
const
isInDropdownButtonCollapsed
=
this
.
$refs
.
dropdownButtonCollapsed
?.
$el
.
contains
(
target
);
const
isInDropdownContents
=
this
.
$refs
.
dropdownContents
?.
$el
.
contains
(
target
);
return
(
hasExceptionClass
||
hasExceptionParent
||
isInDropdownButtonCollapsed
||
isInDropdownContents
);
},
...
mapActions
([
'
setInitialState
'
]),
handleDropdownClose
(
labels
)
{
// Only emit label updates if there are any labels to update
// on UI.
if
(
this
.
showDropdownContents
)
{
this
.
toggleDropdownContents
();
}
if
(
labels
.
length
)
this
.
$emit
(
'
updateSelectedLabels
'
,
labels
);
this
.
$emit
(
'
onDropdownClose
'
);
},
collapseDropdown
()
{
this
.
$refs
.
editable
.
collapse
();
},
handleCollapsedValueClick
()
{
this
.
$emit
(
'
toggleCollapse
'
);
},
setContentIsOnViewport
(
showDropdownContents
)
{
if
(
!
showDropdownContents
)
{
this
.
contentIsOnViewport
=
true
;
return
;
}
setContentIsOnViewport
()
{
this
.
$nextTick
(()
=>
{
if
(
this
.
$refs
.
dropdownContents
)
{
this
.
contentIsOnViewport
=
isInViewport
(
this
.
$refs
.
dropdownContents
.
$el
);
...
...
@@ -299,48 +215,55 @@ export default {
:labels=
"issueLabels"
@
onValueClick=
"handleCollapsedValueClick"
/>
<dropdown-title
:allow-label-edit=
"allowLabelEdit"
:labels-select-in-progress=
"labelsSelectInProgress"
/>
<dropdown-value
:disable-labels=
"labelsSelectInProgress"
:selected-labels=
"issueLabels"
:allow-label-remove=
"allowLabelRemove"
:allow-scoped-labels=
"allowScopedLabels"
:labels-filter-base-path=
"labelsFilterBasePath"
:labels-filter-param=
"labelsFilterParam"
@
onLabelRemove=
"$emit('onLabelRemove', $event)"
<sidebar-editable-item
ref=
"editable"
:title=
"__('Labels')"
:loading=
"labelsSelectInProgress"
@
open=
"setContentIsOnViewport"
@
close=
"contentIsOnViewport = true"
>
<slot></slot>
</dropdown-value>
<dropdown-button
v-show=
"dropdownButtonVisible"
class=
"gl-mt-2"
/>
<dropdown-contents
v-if=
"dropdownButtonVisible && showDropdownContents"
ref=
"dropdownContents"
:allow-multiselect=
"allowMultiselect"
:labels-list-title=
"labelsListTitle"
:footer-create-label-title=
"footerCreateLabelTitle"
:footer-manage-label-title=
"footerManageLabelTitle"
:render-on-top=
"!contentIsOnViewport"
:labels-create-title=
"labelsCreateTitle"
:selected-labels=
"selectedLabels"
@
closeDropdown=
"handleDropdownClose"
/>
</
template
>
<
template
v-if=
"isDropdownVariantStandalone || isDropdownVariantEmbedded"
>
<dropdown-button
v-show=
"dropdownButtonVisible"
/>
<dropdown-contents
v-if=
"dropdownButtonVisible && showDropdownContents"
ref=
"dropdownContents"
:allow-multiselect=
"allowMultiselect"
:labels-list-title=
"labelsListTitle"
:footer-create-label-title=
"footerCreateLabelTitle"
:footer-manage-label-title=
"footerManageLabelTitle"
:render-on-top=
"!contentIsOnViewport"
:selected-labels=
"selectedLabels"
@
closeDropdown=
"handleDropdownClose"
/>
<template
#collapsed
>
<dropdown-value
:disable-labels=
"labelsSelectInProgress"
:selected-labels=
"issueLabels"
:allow-label-remove=
"allowLabelRemove"
:allow-scoped-labels=
"allowScopedLabels"
:labels-filter-base-path=
"labelsFilterBasePath"
:labels-filter-param=
"labelsFilterParam"
@
onLabelRemove=
"$emit('onLabelRemove', $event)"
>
<slot></slot>
</dropdown-value>
</
template
>
<
template
#default=
"{ edit }"
>
<dropdown-value
:disable-labels=
"labelsSelectInProgress"
:selected-labels=
"issueLabels"
:allow-label-remove=
"allowLabelRemove"
:allow-scoped-labels=
"allowScopedLabels"
:labels-filter-base-path=
"labelsFilterBasePath"
:labels-filter-param=
"labelsFilterParam"
class=
"gl-mb-2"
@
onLabelRemove=
"$emit('onLabelRemove', $event)"
>
<slot></slot>
</dropdown-value>
<dropdown-button
/>
<dropdown-contents
v-if=
"edit"
ref=
"dropdownContents"
:allow-multiselect=
"allowMultiselect"
:labels-list-title=
"labelsListTitle"
:footer-create-label-title=
"footerCreateLabelTitle"
:footer-manage-label-title=
"footerManageLabelTitle"
:render-on-top=
"!contentIsOnViewport"
:labels-create-title=
"labelsCreateTitle"
:selected-labels=
"selectedLabels"
@
closeDropdown=
"collapseDropdown"
@
setLabels=
"handleDropdownClose"
/>
</
template
>
</sidebar-editable-item>
</template>
</div>
</template>
app/finders/packages/helm/package_files_finder.rb
View file @
13d7df98
...
...
@@ -6,6 +6,8 @@ module Packages
DEFAULT_PACKAGE_FILES_COUNT
=
20
MAX_PACKAGE_FILES_COUNT
=
1000
delegate
:most_recent!
,
to: :execute
def
initialize
(
project
,
channel
,
params
=
{})
@project
=
project
@channel
=
channel
...
...
app/models/packages/package_file.rb
View file @
13d7df98
...
...
@@ -77,6 +77,10 @@ class Packages::PackageFile < ApplicationRecord
.
where
(
packages_conan_file_metadata:
{
conan_package_reference:
conan_package_reference
})
end
def
self
.
most_recent!
recent
.
first!
end
mount_file_store_uploader
Packages
::
PackageFileUploader
update_project_statistics
project_statistics_name: :packages_size
...
...
app/presenters/project_presenter.rb
View file @
13d7df98
...
...
@@ -431,22 +431,10 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
private
def
integrations_anchor_data
experiment
(
:repo_integrations_link
,
project:
project
)
do
|
e
|
e
.
exclude!
unless
can?
(
current_user
,
:admin_project
,
project
)
e
.
use
{}
# nil control
e
.
try
do
label
=
statistic_icon
(
'settings'
)
+
_
(
'Configure Integrations'
)
AnchorData
.
new
(
false
,
label
,
project_settings_integrations_path
(
project
),
nil
,
nil
,
nil
,
{
'track-event'
:
'click'
,
'track-experiment'
:
e
.
name
})
end
e
.
run
# call run so the return value will be the AnchorData (or nil)
return
unless
can?
(
current_user
,
:admin_project
,
project
)
e
.
track
(
:view
,
value:
project
.
id
)
# track an event for the view, with project id
end
label
=
statistic_icon
(
'settings'
)
+
_
(
'Configure Integrations'
)
AnchorData
.
new
(
false
,
label
,
project_settings_integrations_path
(
project
),
nil
,
nil
,
nil
)
end
def
cicd_missing?
...
...
app/views/users/show.html.haml
View file @
13d7df98
...
...
@@ -26,6 +26,13 @@
=
link_to
new_abuse_report_path
(
user_id:
@user
.
id
,
ref_url:
request
.
referer
),
class:
link_classes
+
'btn gl-button btn-default btn-icon'
,
title:
s_
(
'UserProfile|Report abuse'
),
data:
{
toggle:
'tooltip'
,
placement:
'bottom'
,
container:
'body'
}
do
=
sprite_icon
(
'error'
)
-
verified_gpg_keys
=
@user
.
gpg_keys
.
select
(
&
:verified?
)
-
if
verified_gpg_keys
.
any?
=
link_to
user_gpg_keys_path
,
class:
link_classes
+
'btn btn-default btn-md gl-button btn-icon has-tooltip'
,
title:
n_
(
'View public GPG key'
,
'View public GPG keys'
,
verified_gpg_keys
.
length
),
data:
{
toggle:
'tooltip'
,
placement:
'bottom'
,
container:
'body'
}
do
=
sprite_icon
(
'key'
,
css_class:
'gl-button-icon gl-icon'
)
-
if
can?
(
current_user
,
:read_user_profile
,
@user
)
=
link_to
user_path
(
@user
,
rss_url_options
),
class:
link_classes
+
'btn gl-button btn-default btn-icon has-tooltip'
,
title:
s_
(
'UserProfile|Subscribe'
),
data:
{
toggle:
'tooltip'
,
placement:
'bottom'
,
container:
'body'
}
do
...
...
app/workers/packages/debian/process_changes_worker.rb
View file @
13d7df98
...
...
@@ -22,12 +22,7 @@ module Packages
return
unless
package_file
&&
user
::
Packages
::
Debian
::
ProcessChangesService
.
new
(
package_file
,
user
).
execute
rescue
ArgumentError
,
Packages
::
Debian
::
ExtractChangesMetadataService
::
ExtractionError
,
Packages
::
Debian
::
ExtractDebMetadataService
::
CommandFailedError
,
Packages
::
Debian
::
ExtractMetadataService
::
ExtractionError
,
Packages
::
Debian
::
ParseDebian822Service
::
InvalidDebian822Error
,
ActiveRecord
::
RecordNotFound
=>
e
rescue
StandardError
=>
e
Gitlab
::
ErrorTracking
.
log_exception
(
e
,
package_file_id:
@package_file_id
,
user_id:
@user_id
)
package_file
.
destroy!
end
...
...
app/workers/packages/helm/extraction_worker.rb
View file @
13d7df98
...
...
@@ -20,9 +20,7 @@ module Packages
::
Packages
::
Helm
::
ProcessFileService
.
new
(
channel
,
package_file
).
execute
rescue
::
Packages
::
Helm
::
ExtractFileMetadataService
::
ExtractionError
,
::
Packages
::
Helm
::
ProcessFileService
::
ExtractionError
,
::
ActiveModel
::
ValidationError
=>
e
rescue
StandardError
=>
e
Gitlab
::
ErrorTracking
.
log_exception
(
e
,
project_id:
package_file
.
project_id
)
package_file
.
package
.
update_column
(
:status
,
:error
)
end
...
...
config/feature_flags/experiment/repo_integrations_link.yml
deleted
100644 → 0
View file @
8bce1b3f
---
name
:
repo_integrations_link
introduced_by_url
:
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54652/
rollout_issue_url
:
https://gitlab.com/gitlab-org/gitlab/-/issues/285154
milestone
:
'
13.10'
type
:
experiment
group
:
group::adoption
default_enabled
:
false
config/routes/user.rb
View file @
13d7df98
...
...
@@ -64,7 +64,7 @@ constraints(::Constraints::UserUrlConstrainer.new) do
get
':username.keys'
=>
'users#ssh_keys'
,
constraints:
{
username:
Gitlab
::
PathRegex
.
root_namespace_route_regex
}
# Get all GPG keys of user
get
':username.gpg'
=>
'users#gpg_keys'
,
constraints:
{
username:
Gitlab
::
PathRegex
.
root_namespace_route_regex
}
get
':username.gpg'
=>
'users#gpg_keys'
,
as:
'user_gpg_keys'
,
constraints:
{
username:
Gitlab
::
PathRegex
.
root_namespace_route_regex
}
scope
(
path:
':username'
,
as: :user
,
...
...
config/webpack.config.js
View file @
13d7df98
...
...
@@ -161,6 +161,9 @@ const alias = {
// the following resolves files which are different between CE and EE
ee_else_ce
:
path
.
join
(
ROOT_PATH
,
'
app/assets/javascripts
'
),
// the following resolves files which are different between CE and JH
jh_else_ce
:
path
.
join
(
ROOT_PATH
,
'
app/assets/javascripts
'
),
// override loader path for icons.svg so we do not duplicate this asset
'
@gitlab/svgs/dist/icons.svg
'
:
path
.
join
(
ROOT_PATH
,
...
...
@@ -184,10 +187,13 @@ if (IS_EE) {
if
(
IS_JH
)
{
Object
.
assign
(
alias
,
{
jh
:
path
.
join
(
ROOT_PATH
,
'
jh/app/assets/javascripts
'
),
jh_component
:
path
.
join
(
ROOT_PATH
,
'
jh/app/assets/javascripts
'
),
jh_empty_states
:
path
.
join
(
ROOT_PATH
,
'
jh/app/views/shared/empty_states
'
),
jh_icons
:
path
.
join
(
ROOT_PATH
,
'
jh/app/views/shared/icons
'
),
jh_images
:
path
.
join
(
ROOT_PATH
,
'
jh/app/assets/images
'
),
jh_spec
:
path
.
join
(
ROOT_PATH
,
'
jh/spec/javascripts
'
),
jh_jest
:
path
.
join
(
ROOT_PATH
,
'
jh/spec/frontend
'
),
jh_else_ce
:
path
.
join
(
ROOT_PATH
,
'
jh/app/assets/javascripts
'
),
});
}
...
...
@@ -523,6 +529,15 @@ module.exports = {
);
}),
!
IS_JH
&&
new
webpack
.
NormalModuleReplacementPlugin
(
/^jh_component
\/(
.*
)\.
vue/
,
(
resource
)
=>
{
// eslint-disable-next-line no-param-reassign
resource
.
request
=
path
.
join
(
ROOT_PATH
,
'
app/assets/javascripts/vue_shared/components/empty_component.js
'
,
);
}),
new
CopyWebpackPlugin
({
patterns
:
[
{
...
...
@@ -638,10 +653,12 @@ module.exports = {
}),
new
webpack
.
DefinePlugin
({
// Th
is one is used to define window.gon.ee
and other things properly in tests:
// Th
ese are used to define window.gon.ee, window.gon.jh
and other things properly in tests:
'
process.env.IS_EE
'
:
JSON
.
stringify
(
IS_EE
),
// This one is used to check against "EE" properly in application code
'
process.env.IS_JH
'
:
JSON
.
stringify
(
IS_JH
),
// These are used to check against "EE" properly in application code
IS_EE
:
IS_EE
?
'
window.gon && window.gon.ee
'
:
JSON
.
stringify
(
false
),
IS_JH
:
IS_JH
?
'
window.gon && window.gon.jh
'
:
JSON
.
stringify
(
false
),
// This is used by Sourcegraph because these assets are loaded dnamically
'
process.env.SOURCEGRAPH_PUBLIC_PATH
'
:
JSON
.
stringify
(
SOURCEGRAPH_PUBLIC_PATH
),
}),
...
...
doc/user/project/repository/gpg_signed_commits/index.md
View file @
13d7df98
...
...
@@ -19,6 +19,11 @@ NOTE:
The term GPG is used for all OpenPGP/PGP/GPG related material and
implementations.
To view a user's public GPG key, you can:
-
Go to
`https://gitlab.example.com/<username>.gpg`
.
-
Select
**View public GPG keys**
(
**{key}**
) in the top right of the user's profile.
GPG verified tags are not supported yet.
See the
[
further reading
](
#further-reading
)
section for more details on GPG.
...
...
jest.config.base.js
View file @
13d7df98
const
IS_EE
=
require
(
'
./config/helpers/is_ee_env
'
);
const
isESLint
=
require
(
'
./config/helpers/is_eslint
'
);
const
IS_JH
=
require
(
'
./config/helpers/is_jh_env
'
);
module
.
exports
=
(
path
,
options
=
{})
=>
{
const
{
moduleNameMapper
:
extModuleNameMapper
=
{},
moduleNameMapperEE
:
extModuleNameMapperEE
=
{},
moduleNameMapperJH
:
extModuleNameMapperJH
=
{},
}
=
options
;
const
reporters
=
[
'
default
'
];
...
...
@@ -29,6 +31,9 @@ module.exports = (path, options = {}) => {
testMatch
.
push
(
`<rootDir>/ee/
${
glob
}
`
);
}
if
(
IS_JH
)
{
testMatch
.
push
(
`<rootDir>/jh/
${
glob
}
`
);
}
// workaround for eslint-import-resolver-jest only resolving in test files
// see https://github.com/JoinColony/eslint-import-resolver-jest#note
if
(
isESLint
(
module
))
{
...
...
@@ -41,8 +46,11 @@ module.exports = (path, options = {}) => {
'
^~(/.*)$
'
:
'
<rootDir>/app/assets/javascripts$1
'
,
'
^ee_component(/.*)$
'
:
'
<rootDir>/app/assets/javascripts/vue_shared/components/empty_component.js
'
,
'
^jh_component(/.*)$
'
:
'
<rootDir>/app/assets/javascripts/vue_shared/components/empty_component.js
'
,
'
^shared_queries(/.*)$
'
:
'
<rootDir>/app/graphql/queries$1
'
,
'
^ee_else_ce(/.*)$
'
:
'
<rootDir>/app/assets/javascripts$1
'
,
'
^jh_else_ce(/.*)$
'
:
'
<rootDir>/app/assets/javascripts$1
'
,
'
^helpers(/.*)$
'
:
'
<rootDir>/spec/frontend/__helpers__$1
'
,
'
^vendor(/.*)$
'
:
'
<rootDir>/vendor/assets/javascripts$1
'
,
[
TEST_FIXTURES_PATTERN
]:
'
<rootDir>/tmp/tests/frontend/fixtures$1
'
,
...
...
@@ -70,6 +78,19 @@ module.exports = (path, options = {}) => {
collectCoverageFrom
.
push
(
rootDirEE
.
replace
(
'
$1
'
,
'
/**/*.{js,vue}
'
));
}
if
(
IS_JH
)
{
const
rootDirJH
=
'
<rootDir>/jh/app/assets/javascripts$1
'
;
Object
.
assign
(
moduleNameMapper
,
{
'
^jh(/.*)$
'
:
rootDirJH
,
'
^jh_component(/.*)$
'
:
rootDirJH
,
'
^jh_else_ce(/.*)$
'
:
rootDirJH
,
'
^jh_jest/(.*)$
'
:
'
<rootDir>/jh/spec/frontend/$1
'
,
...
extModuleNameMapperJH
,
});
collectCoverageFrom
.
push
(
rootDirJH
.
replace
(
'
$1
'
,
'
/**/*.{js,vue}
'
));
}
const
coverageDirectory
=
()
=>
{
if
(
process
.
env
.
CI_NODE_INDEX
&&
process
.
env
.
CI_NODE_TOTAL
)
{
return
`<rootDir>/coverage-frontend/jest-
${
process
.
env
.
CI_NODE_INDEX
}
-
${
process
.
env
.
CI_NODE_TOTAL
}
`
;
...
...
@@ -107,6 +128,7 @@ module.exports = (path, options = {}) => {
testEnvironment
:
'
<rootDir>/spec/frontend/environment.js
'
,
testEnvironmentOptions
:
{
IS_EE
,
IS_JH
,
},
};
};
jest.config.integration.js
View file @
13d7df98
...
...
@@ -8,9 +8,13 @@ module.exports = {
moduleNameMapper
:
{
'
^test_helpers(/.*)$
'
:
'
<rootDir>/spec/frontend_integration/test_helpers$1
'
,
'
^ee_else_ce_test_helpers(/.*)$
'
:
'
<rootDir>/spec/frontend_integration/test_helpers$1
'
,
'
^jh_else_ce_test_helpers(/.*)$
'
:
'
<rootDir>/spec/frontend_integration/test_helpers$1
'
,
},
moduleNameMapperEE
:
{
'
^ee_else_ce_test_helpers(/.*)$
'
:
'
<rootDir>/ee/spec/frontend_integration/test_helpers$1
'
,
},
moduleNameMapperJH
:
{
'
^jh_else_ce_test_helpers(/.*)$
'
:
'
<rootDir>/jh/spec/frontend_integration/test_helpers$1
'
,
},
}),
};
lib/api/helm_packages.rb
View file @
13d7df98
...
...
@@ -66,7 +66,7 @@ module API
get
":channel/charts/:file_name.tgz"
,
requirements:
FILE_NAME_REQUIREMENTS
do
authorize_read_package!
(
authorized_user_project
)
package_file
=
Packages
::
Helm
::
PackageFilesFinder
.
new
(
authorized_user_project
,
params
[
:channel
],
file_name:
"
#{
params
[
:file_name
]
}
.tgz"
).
execute
.
las
t!
package_file
=
Packages
::
Helm
::
PackageFilesFinder
.
new
(
authorized_user_project
,
params
[
:channel
],
file_name:
"
#{
params
[
:file_name
]
}
.tgz"
).
most_recen
t!
track_package_event
(
'pull_package'
,
:helm
,
project:
authorized_user_project
,
namespace:
authorized_user_project
.
namespace
)
...
...
lib/gitlab/gon_helper.rb
View file @
13d7df98
...
...
@@ -37,6 +37,7 @@ module Gitlab
gon
.
first_day_of_week
=
current_user
&
.
first_day_of_week
||
Gitlab
::
CurrentSettings
.
first_day_of_week
gon
.
time_display_relative
=
true
gon
.
ee
=
Gitlab
.
ee?
gon
.
jh
=
Gitlab
.
jh?
gon
.
dot_com
=
Gitlab
.
com?
if
current_user
...
...
locale/gitlab.pot
View file @
13d7df98
...
...
@@ -36989,6 +36989,11 @@ msgstr ""
msgid "View project labels"
msgstr ""
msgid "View public GPG key"
msgid_plural "View public GPG keys"
msgstr[0] ""
msgstr[1] ""
msgid "View replaced file @ "
msgstr ""
...
...
spec/features/users/show_spec.rb
View file @
13d7df98
...
...
@@ -429,4 +429,27 @@ RSpec.describe 'User page' do
end
end
end
context
'GPG keys'
do
context
'when user has verified GPG keys'
do
let_it_be
(
:user
)
{
create
(
:user
,
email:
GpgHelpers
::
User1
.
emails
.
first
)
}
let_it_be
(
:gpg_key
)
{
create
(
:gpg_key
,
user:
user
,
key:
GpgHelpers
::
User1
.
public_key
)
}
let_it_be
(
:gpg_key2
)
{
create
(
:gpg_key
,
user:
user
,
key:
GpgHelpers
::
User1
.
public_key2
)
}
it
'shows link to public GPG keys'
do
subject
expect
(
page
).
to
have_link
(
'View public GPG keys'
,
href:
user_gpg_keys_path
(
user
))
end
end
context
'when user does not have verified GPG keys'
do
it
'does not show link to public GPG keys'
do
subject
expect
(
page
).
not_to
have_link
(
'View public GPG key'
,
href:
user_gpg_keys_path
(
user
))
expect
(
page
).
not_to
have_link
(
'View public GPG keys'
,
href:
user_gpg_keys_path
(
user
))
end
end
end
end
spec/finders/packages/helm/package_files_finder_spec.rb
View file @
13d7df98
...
...
@@ -6,42 +6,51 @@ RSpec.describe ::Packages::Helm::PackageFilesFinder do
let_it_be
(
:project1
)
{
create
(
:project
)
}
let_it_be
(
:project2
)
{
create
(
:project
)
}
let_it_be
(
:helm_package
)
{
create
(
:helm_package
,
project:
project1
)
}
let_it_be
(
:helm_package_file
)
{
helm_package
.
package_files
.
first
}
let_it_be
(
:helm_package_file1
)
{
helm_package
.
package_files
.
first
}
let_it_be
(
:helm_package_file2
)
{
create
(
:helm_package_file
,
package:
helm_package
)
}
let_it_be
(
:debian_package
)
{
create
(
:debian_package
,
project:
project1
)
}
describe
'#execute'
do
let
(
:project
)
{
project1
}
let
(
:channel
)
{
'stable'
}
let
(
:params
)
{
{}
}
let
(
:project
)
{
project1
}
let
(
:channel
)
{
'stable'
}
let
(
:params
)
{
{}
}
let
(
:service
)
{
described_class
.
new
(
project
,
channel
,
params
)
}
subject
{
described_class
.
new
(
project
,
channel
,
params
).
execute
}
describe
'#execute'
do
subject
{
service
.
execute
}
context
'with empty params'
do
it
{
is_expected
.
to
match_array
([
helm_package_file
])
}
it
{
is_expected
.
to
eq
([
helm_package_file2
,
helm_package_file1
])
}
end
context
'with another project'
do
let
(
:project
)
{
project2
}
it
{
is_expected
.
to
match_array
([])
}
it
{
is_expected
.
to
eq
([])
}
end
context
'with another channel'
do
let
(
:channel
)
{
'staging'
}
it
{
is_expected
.
to
match_array
([])
}
it
{
is_expected
.
to
eq
([])
}
end
context
'with file_name'
do
let
(
:params
)
{
{
file_name:
helm_package_file
.
file_name
}
}
context
'with
matching
file_name'
do
let
(
:params
)
{
{
file_name:
helm_package_file
1
.
file_name
}
}
it
{
is_expected
.
to
match_array
([
helm_package_file
])
}
it
{
is_expected
.
to
eq
([
helm_package_file2
,
helm_package_file1
])
}
end
context
'with another file_name'
do
let
(
:params
)
{
{
file_name:
'foobar.tgz'
}
}
it
{
is_expected
.
to
match_array
([])
}
it
{
is_expected
.
to
eq
([])
}
end
end
describe
'#most_recent!'
do
subject
{
service
.
most_recent!
}
it
{
is_expected
.
to
eq
(
helm_package_file2
)
}
end
end
spec/frontend/tracking_spec.js
View file @
13d7df98
...
...
@@ -349,7 +349,7 @@ describe('Tracking', () => {
it
(
'
includes experiment data if linked to an experiment
'
,
()
=>
{
const
mockExperimentData
=
{
variant
:
'
candidate
'
,
experiment
:
'
repo_integrations_link
'
,
experiment
:
'
example
'
,
key
:
'
2bff73f6bb8cc11156c50a8ba66b9b8b
'
,
};
getExperimentData
.
mockReturnValue
(
mockExperimentData
);
...
...
spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view_spec.js
View file @
13d7df98
...
...
@@ -7,7 +7,12 @@ import waitForPromises from 'helpers/wait_for_promises';
import
createFlash
from
'
~/flash
'
;
import
DropdownContentsCreateView
from
'
~/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue
'
;
import
createLabelMutation
from
'
~/vue_shared/components/sidebar/labels_select_widget/graphql/create_label.mutation.graphql
'
;
import
{
mockSuggestedColors
,
createLabelSuccessfulResponse
}
from
'
./mock_data
'
;
import
projectLabelsQuery
from
'
~/vue_shared/components/sidebar/labels_select_widget/graphql/project_labels.query.graphql
'
;
import
{
mockSuggestedColors
,
createLabelSuccessfulResponse
,
labelsQueryResponse
,
}
from
'
./mock_data
'
;
jest
.
mock
(
'
~/flash
'
);
...
...
@@ -44,6 +49,14 @@ describe('DropdownContentsCreateView', () => {
const
createComponent
=
({
mutationHandler
=
createLabelSuccessHandler
}
=
{})
=>
{
const
mockApollo
=
createMockApollo
([[
createLabelMutation
,
mutationHandler
]]);
mockApollo
.
clients
.
defaultClient
.
cache
.
writeQuery
({
query
:
projectLabelsQuery
,
data
:
labelsQueryResponse
.
data
,
variables
:
{
fullPath
:
''
,
searchTerm
:
''
,
},
});
wrapper
=
shallowMount
(
DropdownContentsCreateView
,
{
localVue
,
...
...
spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view_spec.js
View file @
13d7df98
...
...
@@ -137,12 +137,6 @@ describe('DropdownContentsLabelsView', () => {
expect
(
findLabels
().
at
(
0
).
attributes
(
'
islabelset
'
)).
toBe
(
'
true
'
);
});
it
(
'
emits `closeDropdown event` when Esc button is pressed
'
,
()
=>
{
findDropdownWrapper
().
trigger
(
'
keydown.esc
'
);
expect
(
wrapper
.
emitted
(
'
closeDropdown
'
)).
toEqual
([[
selectedLabels
]]);
});
});
it
(
'
when search returns 0 results
'
,
async
()
=>
{
...
...
@@ -205,7 +199,7 @@ describe('DropdownContentsLabelsView', () => {
});
it
(
'
emits `toggleDropdownContentsCreateView` event on create label button click
'
,
()
=>
{
findCreateLabelButton
().
vm
.
$emit
(
'
click
'
);
findCreateLabelButton
().
vm
.
$emit
(
'
click
'
,
new
MouseEvent
(
'
click
'
)
);
expect
(
wrapper
.
emitted
(
'
toggleDropdownContentsCreateView
'
)).
toEqual
([[]]);
});
...
...
spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_title_spec.js
deleted
100644 → 0
View file @
8bce1b3f
import
{
GlButton
,
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
import
{
shallowMount
,
createLocalVue
}
from
'
@vue/test-utils
'
;
import
Vuex
from
'
vuex
'
;
import
DropdownTitle
from
'
~/vue_shared/components/sidebar/labels_select_widget/dropdown_title.vue
'
;
import
labelsSelectModule
from
'
~/vue_shared/components/sidebar/labels_select_widget/store
'
;
import
{
mockConfig
}
from
'
./mock_data
'
;
const
localVue
=
createLocalVue
();
localVue
.
use
(
Vuex
);
const
createComponent
=
(
initialState
=
mockConfig
)
=>
{
const
store
=
new
Vuex
.
Store
(
labelsSelectModule
());
store
.
dispatch
(
'
setInitialState
'
,
initialState
);
return
shallowMount
(
DropdownTitle
,
{
localVue
,
store
,
propsData
:
{
labelsSelectInProgress
:
false
,
},
});
};
describe
(
'
DropdownTitle
'
,
()
=>
{
let
wrapper
;
beforeEach
(()
=>
{
wrapper
=
createComponent
();
});
afterEach
(()
=>
{
wrapper
.
destroy
();
});
describe
(
'
template
'
,
()
=>
{
it
(
'
renders component container element with string "Labels"
'
,
()
=>
{
expect
(
wrapper
.
text
()).
toContain
(
'
Labels
'
);
});
it
(
'
renders edit link
'
,
()
=>
{
const
editBtnEl
=
wrapper
.
find
(
GlButton
);
expect
(
editBtnEl
.
exists
()).
toBe
(
true
);
expect
(
editBtnEl
.
text
()).
toBe
(
'
Edit
'
);
});
it
(
'
renders loading icon element when `labelsSelectInProgress` prop is true
'
,
()
=>
{
wrapper
.
setProps
({
labelsSelectInProgress
:
true
,
});
return
wrapper
.
vm
.
$nextTick
(()
=>
{
expect
(
wrapper
.
find
(
GlLoadingIcon
).
isVisible
()).
toBe
(
true
);
});
});
});
});
spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js
View file @
13d7df98
import
{
shallowMount
,
createLocalVue
}
from
'
@vue/test-utils
'
;
import
Vuex
from
'
vuex
'
;
import
{
isInViewport
}
from
'
~/lib/utils/common_utils
'
;
import
{
DropdownVariant
}
from
'
~/vue_shared/components/sidebar/labels_select_widget/constants
'
;
import
DropdownButton
from
'
~/vue_shared/components/sidebar/labels_select_widget/dropdown_button.vue
'
;
import
SidebarEditableItem
from
'
~/sidebar/components/sidebar_editable_item.vue
'
;
import
DropdownContents
from
'
~/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue
'
;
import
DropdownTitle
from
'
~/vue_shared/components/sidebar/labels_select_widget/dropdown_title.vue
'
;
import
DropdownValue
from
'
~/vue_shared/components/sidebar/labels_select_widget/dropdown_value.vue
'
;
import
DropdownValueCollapsed
from
'
~/vue_shared/components/sidebar/labels_select_widget/dropdown_value_collapsed.vue
'
;
import
LabelsSelectRoot
from
'
~/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue
'
;
...
...
@@ -32,11 +28,13 @@ describe('LabelsSelectRoot', () => {
store
,
propsData
:
config
,
stubs
:
{
'
dropdown-contents
'
:
DropdownContents
,
DropdownContents
,
SidebarEditableItem
,
},
provide
:
{
iid
:
'
1
'
,
projectPath
:
'
test
'
,
canUpdate
:
true
,
},
});
};
...
...
@@ -49,145 +47,44 @@ describe('LabelsSelectRoot', () => {
wrapper
.
destroy
();
});
describe
(
'
methods
'
,
()
=>
{
describe
(
'
handleDropdownClose
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
();
});
it
(
'
emits `updateSelectedLabels` & `onDropdownClose` events on component when provided `labels` param is not empty
'
,
()
=>
{
wrapper
.
vm
.
handleDropdownClose
([{
id
:
1
},
{
id
:
2
}]);
expect
(
wrapper
.
emitted
().
updateSelectedLabels
).
toBeTruthy
();
expect
(
wrapper
.
emitted
().
onDropdownClose
).
toBeTruthy
();
});
it
(
'
emits only `onDropdownClose` event on component when provided `labels` param is empty
'
,
()
=>
{
wrapper
.
vm
.
handleDropdownClose
([]);
expect
(
wrapper
.
emitted
().
updateSelectedLabels
).
toBeFalsy
();
expect
(
wrapper
.
emitted
().
onDropdownClose
).
toBeTruthy
();
});
});
describe
(
'
handleCollapsedValueClick
'
,
()
=>
{
it
(
'
emits `toggleCollapse` event on component
'
,
()
=>
{
createComponent
();
wrapper
.
vm
.
handleCollapsedValueClick
();
expect
(
wrapper
.
emitted
().
toggleCollapse
).
toBeTruthy
();
});
});
it
(
'
renders component with classes `labels-select-wrapper position-relative`
'
,
()
=>
{
createComponent
();
expect
(
wrapper
.
classes
()).
toEqual
([
'
labels-select-wrapper
'
,
'
position-relative
'
]);
});
describe
(
'
template
'
,
()
=>
{
it
(
'
renders component with classes `labels-select-wrapper position-relative`
'
,
()
=>
{
createComponent
();
expect
(
wrapper
.
attributes
(
'
class
'
)).
toContain
(
'
labels-select-wrapper position-relative
'
);
});
it
.
each
`
variant | cssClass
${
'
standalone
'
}
|
${
'
is-standalone
'
}
${
'
embedded
'
}
|
${
'
is-embedded
'
}
`
(
'
renders component root element with CSS class `$cssClass` when `state.variant` is "$variant"
'
,
({
variant
,
cssClass
})
=>
{
createComponent
({
...
mockConfig
,
variant
,
});
return
wrapper
.
vm
.
$nextTick
(()
=>
{
expect
(
wrapper
.
classes
()).
toContain
(
cssClass
);
});
},
);
it
(
'
renders `dropdown-value-collapsed` component when `allowLabelCreate` prop is `true`
'
,
async
()
=>
{
createComponent
();
await
wrapper
.
vm
.
$nextTick
;
expect
(
wrapper
.
find
(
DropdownValueCollapsed
).
exists
()).
toBe
(
true
);
});
it
(
'
renders `dropdown-title` component
'
,
async
()
=>
{
createComponent
();
await
wrapper
.
vm
.
$nextTick
;
expect
(
wrapper
.
find
(
DropdownTitle
).
exists
()).
toBe
(
true
);
});
it
(
'
renders `dropdown-value` component
'
,
async
()
=>
{
createComponent
(
mockConfig
,
{
default
:
'
None
'
,
it
.
each
`
variant | cssClass
${
'
standalone
'
}
|
${
'
is-standalone
'
}
${
'
embedded
'
}
|
${
'
is-embedded
'
}
`
(
'
renders component root element with CSS class `$cssClass` when `state.variant` is "$variant"
'
,
({
variant
,
cssClass
})
=>
{
createComponent
({
...
mockConfig
,
variant
,
});
await
wrapper
.
vm
.
$nextTick
;
const
valueComp
=
wrapper
.
find
(
DropdownValue
);
expect
(
valueComp
.
exists
()).
toBe
(
true
);
expect
(
valueComp
.
text
()).
toBe
(
'
None
'
);
});
it
(
'
renders `dropdown-button` component when `showDropdownButton` prop is `true`
'
,
async
()
=>
{
createComponent
();
wrapper
.
vm
.
$store
.
dispatch
(
'
toggleDropdownButton
'
);
await
wrapper
.
vm
.
$nextTick
;
expect
(
wrapper
.
find
(
DropdownButton
).
exists
()).
toBe
(
true
);
});
it
(
'
renders `dropdown-contents` component when `showDropdownButton` & `showDropdownContents` prop is `true`
'
,
async
()
=>
{
createComponent
();
wrapper
.
vm
.
$store
.
dispatch
(
'
toggleDropdownContents
'
);
await
wrapper
.
vm
.
$nextTick
;
expect
(
wrapper
.
find
(
DropdownContents
).
exists
()).
toBe
(
true
);
});
describe
(
'
sets content direction based on viewport
'
,
()
=>
{
describe
.
each
(
Object
.
values
(
DropdownVariant
))(
'
when labels variant is "%s"
'
,
({
variant
})
=>
{
beforeEach
(()
=>
{
createComponent
({
...
mockConfig
,
variant
});
wrapper
.
vm
.
$store
.
dispatch
(
'
toggleDropdownContents
'
);
});
it
(
'
set direction when out of viewport
'
,
()
=>
{
isInViewport
.
mockImplementation
(()
=>
false
);
wrapper
.
vm
.
setContentIsOnViewport
(
wrapper
.
vm
.
$store
.
state
);
return
wrapper
.
vm
.
$nextTick
().
then
(()
=>
{
expect
(
wrapper
.
find
(
DropdownContents
).
props
(
'
renderOnTop
'
)).
toBe
(
true
);
});
});
it
(
'
does not set direction when inside of viewport
'
,
()
=>
{
isInViewport
.
mockImplementation
(()
=>
true
);
wrapper
.
vm
.
setContentIsOnViewport
(
wrapper
.
vm
.
$store
.
state
);
return
wrapper
.
vm
.
$nextTick
().
then
(()
=>
{
expect
(
wrapper
.
find
(
DropdownContents
).
props
(
'
renderOnTop
'
)).
toBe
(
false
);
});
});
},
);
});
});
return
wrapper
.
vm
.
$nextTick
(()
=>
{
expect
(
wrapper
.
classes
()).
toContain
(
cssClass
);
});
},
);
it
(
'
calls toggleDropdownContents action when isEditing prop is changing to true
'
,
async
()
=>
{
it
(
'
renders `dropdown-value-collapsed` component when `allowLabelCreate` prop is `true`
'
,
async
()
=>
{
createComponent
();
jest
.
spyOn
(
store
,
'
dispatch
'
).
mockResolvedValue
();
await
wrapper
.
setProps
({
isEditing
:
true
});
expect
(
store
.
dispatch
).
toHaveBeenCalledWith
(
'
toggleDropdownContents
'
);
await
wrapper
.
vm
.
$nextTick
;
expect
(
wrapper
.
find
(
DropdownValueCollapsed
).
exists
()).
toBe
(
true
);
});
it
(
'
does not call toggleDropdownContents action when isEditing prop is changing to false
'
,
async
()
=>
{
createComponent
();
it
(
'
renders `dropdown-value` component
'
,
async
()
=>
{
createComponent
(
mockConfig
,
{
default
:
'
None
'
,
});
await
wrapper
.
vm
.
$nextTick
;
jest
.
spyOn
(
store
,
'
dispatch
'
).
mockResolvedValue
();
await
wrapper
.
setProps
({
isEditing
:
false
});
const
valueComp
=
wrapper
.
find
(
DropdownValue
);
expect
(
store
.
dispatch
).
not
.
toHaveBeenCalled
();
expect
(
valueComp
.
exists
()).
toBe
(
true
);
expect
(
valueComp
.
text
()).
toBe
(
'
None
'
);
});
});
spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js
View file @
13d7df98
...
...
@@ -83,9 +83,7 @@ export const createLabelSuccessfulResponse = {
id
:
'
gid://gitlab/ProjectLabel/126
'
,
color
:
'
#dc143c
'
,
description
:
null
,
descriptionHtml
:
''
,
title
:
'
ewrwrwer
'
,
textColor
:
'
#FFFFFF
'
,
__typename
:
'
Label
'
,
},
errors
:
[],
...
...
spec/models/packages/package_file_spec.rb
View file @
13d7df98
...
...
@@ -139,6 +139,10 @@ RSpec.describe Packages::PackageFile, type: :model do
end
end
describe
'.most_recent!'
do
it
{
expect
(
described_class
.
most_recent!
).
to
eq
(
debian_package
.
package_files
.
last
)
}
end
describe
'#update_file_store callback'
do
let_it_be
(
:package_file
)
{
build
(
:package_file
,
:nuget
,
size:
nil
)
}
...
...
spec/presenters/project_presenter_spec.rb
View file @
13d7df98
...
...
@@ -649,36 +649,18 @@ RSpec.describe ProjectPresenter do
end
end
describe
'experiment(:repo_integrations_link)'
do
context
'when enabled'
do
before
do
stub_experiments
(
repo_integrations_link: :candidate
)
end
it
'includes a button to configure integrations for maintainers'
do
project
.
add_maintainer
(
user
)
expect
(
empty_repo_statistics_buttons
.
map
(
&
:label
)).
to
include
(
a_string_including
(
'Configure Integration'
)
)
end
it
'does not include a button if not a maintainer'
do
expect
(
empty_repo_statistics_buttons
.
map
(
&
:label
)).
not_to
include
(
a_string_including
(
'Configure Integration'
)
)
end
end
it
'includes a button to configure integrations for maintainers'
do
project
.
add_maintainer
(
user
)
context
'when disabled'
do
it
'does not include a button'
do
project
.
add_maintainer
(
user
)
expect
(
empty_repo_statistics_buttons
.
map
(
&
:label
)).
to
include
(
a_string_including
(
'Configure Integration'
)
)
end
expect
(
empty_repo_statistics_buttons
.
map
(
&
:label
)).
not_to
include
(
a_string_including
(
'Configure Integration'
)
)
end
end
it
'does not include a button if not a maintainer'
do
expect
(
empty_repo_statistics_buttons
.
map
(
&
:label
)).
not_to
include
(
a_string_including
(
'Configure Integration'
)
)
end
context
'for a developer'
do
...
...
spec/requests/api/helm_packages_spec.rb
View file @
13d7df98
...
...
@@ -9,16 +9,18 @@ RSpec.describe API::HelmPackages do
let_it_be_with_reload
(
:project
)
{
create
(
:project
,
:public
)
}
let_it_be
(
:deploy_token
)
{
create
(
:deploy_token
,
read_package_registry:
true
,
write_package_registry:
true
)
}
let_it_be
(
:project_deploy_token
)
{
create
(
:project_deploy_token
,
deploy_token:
deploy_token
,
project:
project
)
}
let_it_be
(
:package
)
{
create
(
:helm_package
,
project:
project
)
}
let_it_be
(
:package
)
{
create
(
:helm_package
,
project:
project
,
without_package_files:
true
)
}
let_it_be
(
:package_file1
)
{
create
(
:helm_package_file
,
package:
package
)
}
let_it_be
(
:package_file2
)
{
create
(
:helm_package_file
,
package:
package
)
}
describe
'GET /api/v4/projects/:id/packages/helm/:channel/index.yaml'
do
it_behaves_like
'handling helm chart index requests'
do
let
(
:url
)
{
"/projects/
#{
project
.
id
}
/packages/helm/
#{
package
.
package_files
.
first
.
helm_channel
}
/index.yaml"
}
let
(
:url
)
{
"/projects/
#{
project
.
id
}
/packages/helm/
stable
/index.yaml"
}
end
end
describe
'GET /api/v4/projects/:id/packages/helm/:channel/charts/:file_name.tgz'
do
let
(
:url
)
{
"/projects/
#{
project
.
id
}
/packages/helm/
#{
package
.
package_files
.
first
.
helm_channel
}
/charts/
#{
package
.
name
}
-
#{
package
.
version
}
.tgz"
}
let
(
:url
)
{
"/projects/
#{
project
.
id
}
/packages/helm/
stable
/charts/
#{
package
.
name
}
-
#{
package
.
version
}
.tgz"
}
subject
{
get
api
(
url
),
headers:
headers
}
...
...
spec/support/database/prevent_cross_joins.rb
View file @
13d7df98
...
...
@@ -72,13 +72,10 @@ ALLOW_LIST = Set.new(YAML.load_file(Rails.root.join('.cross-join-allowlist.yml')
RSpec
.
configure
do
|
config
|
config
.
include
(
::
Database
::
PreventCrossJoins
::
SpecHelpers
)
config
.
around
do
|
example
|
# TODO: remove `:prevent_cross_joins` to enable the check by default
config
.
around
(
:each
,
:prevent_cross_joins
)
do
|
example
|
Thread
.
current
[
:has_cross_join_exception
]
=
false
if
ALLOW_LIST
.
include?
(
example
.
file_path
)
example
.
run
else
with_cross_joins_prevented
{
example
.
run
}
end
with_cross_joins_prevented
{
example
.
run
}
end
end
spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb
View file @
13d7df98
...
...
@@ -41,7 +41,7 @@ RSpec.shared_examples 'process helm service index request' do |user_type, status
package_entry
=
yaml_response
[
'entries'
][
package
.
name
]
expect
(
package_entry
.
length
).
to
eq
(
1
)
expect
(
package_entry
.
length
).
to
eq
(
2
)
expect
(
package_entry
.
first
.
keys
).
to
contain_exactly
(
'name'
,
'version'
,
'apiVersion'
,
'created'
,
'digest'
,
'urls'
)
expect
(
package_entry
.
first
[
'digest'
]).
to
eq
(
'fd2b2fa0329e80a2a602c2bb3b40608bcd6ee5cf96cf46fd0d2800a4c129c9db'
)
expect
(
package_entry
.
first
[
'urls'
]).
to
eq
([
"charts/
#{
package
.
name
}
-
#{
package
.
version
}
.tgz"
])
...
...
@@ -174,6 +174,13 @@ RSpec.shared_examples 'process helm download content request' do |user_type, sta
context
"for user type
#{
user_type
}
"
do
before
do
project
.
send
(
"add_
#{
user_type
}
"
,
user
)
if
user_type
!=
:anonymous
&&
user_type
!=
:not_a_member
expect_next_found_instance_of
(
::
Packages
::
PackageFile
)
do
|
package_file
|
expect
(
package_file
).
to
receive
(
:file
).
and_wrap_original
do
|
m
,
*
args
|
expect
(
package_file
.
id
).
to
eq
(
package_file2
.
id
)
m
.
call
(
*
args
)
end
end
end
it_behaves_like
'a package tracking event'
,
'API::HelmPackages'
,
'pull_package'
...
...
spec/support_specs/database/prevent_cross_joins_spec.rb
View file @
13d7df98
...
...
@@ -3,7 +3,7 @@
require
'spec_helper'
RSpec
.
describe
Database
::
PreventCrossJoins
do
context
'when running in
a default scope'
do
context
'when running in
:prevent_cross_joins scope'
,
:prevent_cross_joins
do
context
'when only non-CI tables are used'
do
it
'does not raise exception'
do
expect
{
main_only_query
}.
not_to
raise_error
...
...
@@ -32,6 +32,14 @@ RSpec.describe Database::PreventCrossJoins do
end
end
context
'when running in a default scope'
do
context
'when CI and non-CI tables are used'
do
it
'does not raise exception'
do
expect
{
main_and_ci_query
}.
not_to
raise_error
end
end
end
private
def
main_only_query
...
...
spec/workers/packages/helm/extraction_worker_spec.rb
View file @
13d7df98
...
...
@@ -23,10 +23,10 @@ RSpec.describe Packages::Helm::ExtractionWorker, type: :worker do
subject
{
described_class
.
new
.
perform
(
channel
,
package_file_id
)
}
shared_examples
'handling error'
do
shared_examples
'handling error'
do
|
error_class
=
Packages
::
Helm
::
ExtractFileMetadataService
::
ExtractionError
|
it
'mark the package as errored'
,
:aggregate_failures
do
expect
(
Gitlab
::
ErrorTracking
).
to
receive
(
:log_exception
).
with
(
instance_of
(
Packages
::
Helm
::
ExtractFileMetadataService
::
ExtractionError
),
instance_of
(
error_class
),
project_id:
package_file
.
package
.
project_id
)
expect
{
subject
}
...
...
@@ -88,5 +88,15 @@ RSpec.describe Packages::Helm::ExtractionWorker, type: :worker do
it_behaves_like
'handling error'
end
context
'with an invalid Chart.yaml'
do
before
do
expect_next_instance_of
(
Gem
::
Package
::
TarReader
::
Entry
)
do
|
entry
|
expect
(
entry
).
to
receive
(
:read
).
and_return
(
'{}'
)
end
end
it_behaves_like
'handling error'
,
ActiveRecord
::
RecordInvalid
end
end
end
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