Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
3e1c760141
commit
1bc5af7661
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { debounce } from 'lodash';
|
||||
import { initEditorLite } from '~/blob/utils';
|
||||
import { SNIPPET_MEASURE_BLOBS_CONTENT } from '~/performance_constants';
|
||||
import { SNIPPET_MEASURE_BLOBS_CONTENT } from '~/performance/constants';
|
||||
|
||||
import eventHub from './eventhub';
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
/* global Mousetrap */
|
||||
import 'mousetrap';
|
||||
import { GlButton, GlButtonGroup } from '@gitlab/ui';
|
||||
import { GlButton, GlButtonGroup, GlTooltipDirective } from '@gitlab/ui';
|
||||
import { s__, sprintf } from '~/locale';
|
||||
import allDesignsMixin from '../../mixins/all_designs';
|
||||
import { DESIGN_ROUTE_NAME } from '../../router/constants';
|
||||
|
@ -11,6 +11,9 @@ export default {
|
|||
GlButton,
|
||||
GlButtonGroup,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
mixins: [allDesignsMixin],
|
||||
props: {
|
||||
id: {
|
||||
|
@ -68,6 +71,7 @@ export default {
|
|||
{{ paginationText }}
|
||||
<gl-button-group class="gl-mx-5">
|
||||
<gl-button
|
||||
v-gl-tooltip.bottom
|
||||
:disabled="!previousDesign"
|
||||
:title="s__('DesignManagement|Go to previous design')"
|
||||
icon="angle-left"
|
||||
|
@ -75,6 +79,7 @@ export default {
|
|||
@click="navigateToDesign(previousDesign)"
|
||||
/>
|
||||
<gl-button
|
||||
v-gl-tooltip.bottom
|
||||
:disabled="!nextDesign"
|
||||
:title="s__('DesignManagement|Go to next design')"
|
||||
icon="angle-right"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlButton, GlIcon } from '@gitlab/ui';
|
||||
import { GlButton, GlIcon, GlTooltipDirective } from '@gitlab/ui';
|
||||
import permissionsQuery from 'shared_queries/design_management/design_permissions.query.graphql';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import timeagoMixin from '~/vue_shared/mixins/timeago';
|
||||
|
@ -14,6 +14,9 @@ export default {
|
|||
DesignNavigation,
|
||||
DeleteButton,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
mixins: [timeagoMixin],
|
||||
props: {
|
||||
id: {
|
||||
|
@ -112,14 +115,21 @@ export default {
|
|||
</div>
|
||||
</div>
|
||||
<design-navigation :id="id" class="gl-ml-auto gl-flex-shrink-0" />
|
||||
<gl-button :href="image" icon="download" />
|
||||
<gl-button
|
||||
v-gl-tooltip.bottom
|
||||
:href="image"
|
||||
icon="download"
|
||||
:title="s__('DesignManagement|Download design')"
|
||||
/>
|
||||
<delete-button
|
||||
v-if="isLatestVersion && canDeleteDesign"
|
||||
v-gl-tooltip.bottom
|
||||
class="gl-ml-3"
|
||||
:is-deleting="isDeleting"
|
||||
button-variant="warning"
|
||||
button-icon="archive"
|
||||
button-category="secondary"
|
||||
:title="s__('DesignManagement|Archive design')"
|
||||
@deleteSelectedDesigns="$emit('delete')"
|
||||
/>
|
||||
</header>
|
||||
|
|
|
@ -10,8 +10,8 @@ import {
|
|||
WEBIDE_MEASURE_TREE_FROM_REQUEST,
|
||||
WEBIDE_MEASURE_FILE_FROM_REQUEST,
|
||||
WEBIDE_MEASURE_FILE_AFTER_INTERACTION,
|
||||
} from '~/performance_constants';
|
||||
import { performanceMarkAndMeasure } from '~/performance_utils';
|
||||
} from '~/performance/constants';
|
||||
import { performanceMarkAndMeasure } from '~/performance/utils';
|
||||
import { modalTypes } from '../constants';
|
||||
import eventHub from '../eventhub';
|
||||
import FindFile from '~/vue_shared/components/file_finder/index.vue';
|
||||
|
|
|
@ -6,8 +6,8 @@ import {
|
|||
WEBIDE_MARK_TREE_START,
|
||||
WEBIDE_MEASURE_TREE_FROM_REQUEST,
|
||||
WEBIDE_MARK_FILE_CLICKED,
|
||||
} from '~/performance_constants';
|
||||
import { performanceMarkAndMeasure } from '~/performance_utils';
|
||||
} from '~/performance/constants';
|
||||
import { performanceMarkAndMeasure } from '~/performance/utils';
|
||||
import eventHub from '../eventhub';
|
||||
import IdeFileRow from './ide_file_row.vue';
|
||||
import NavDropdown from './nav_dropdown.vue';
|
||||
|
|
|
@ -9,8 +9,8 @@ import {
|
|||
WEBIDE_MARK_FILE_START,
|
||||
WEBIDE_MEASURE_FILE_AFTER_INTERACTION,
|
||||
WEBIDE_MEASURE_FILE_FROM_REQUEST,
|
||||
} from '~/performance_constants';
|
||||
import { performanceMarkAndMeasure } from '~/performance_utils';
|
||||
} from '~/performance/constants';
|
||||
import { performanceMarkAndMeasure } from '~/performance/utils';
|
||||
import eventHub from '../eventhub';
|
||||
import {
|
||||
leftSidebarViews,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { languages } from 'monaco-editor';
|
||||
import { flatten, isString } from 'lodash';
|
||||
import { SIDE_LEFT, SIDE_RIGHT } from './constants';
|
||||
import { performanceMarkAndMeasure } from '~/performance_utils';
|
||||
import { performanceMarkAndMeasure } from '~/performance/utils';
|
||||
|
||||
const toLowerCase = x => x.toLowerCase();
|
||||
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
expanded: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
expanded(value) {
|
||||
const layoutPageEl = document.querySelector('.layout-page');
|
||||
|
||||
if (layoutPageEl) {
|
||||
layoutPageEl.classList.toggle('right-sidebar-expanded', value);
|
||||
layoutPageEl.classList.toggle('right-sidebar-collapsed', !value);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<aside
|
||||
:class="{ 'right-sidebar-expanded': expanded, 'right-sidebar-collapsed': !expanded }"
|
||||
class="issues-bulk-update right-sidebar"
|
||||
aria-live="polite"
|
||||
>
|
||||
<div
|
||||
class="gl-display-flex gl-justify-content-space-between gl-p-4 gl-border-b-1 gl-border-b-solid gl-border-gray-100"
|
||||
>
|
||||
<slot name="bulk-edit-actions"></slot>
|
||||
</div>
|
||||
<slot name="sidebar-items"></slot>
|
||||
</aside>
|
||||
</template>
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlLink, GlIcon, GlLabel, GlTooltipDirective } from '@gitlab/ui';
|
||||
import { GlLink, GlIcon, GlLabel, GlFormCheckbox, GlTooltipDirective } from '@gitlab/ui';
|
||||
|
||||
import { __, sprintf } from '~/locale';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
|
@ -14,6 +14,7 @@ export default {
|
|||
GlLink,
|
||||
GlIcon,
|
||||
GlLabel,
|
||||
GlFormCheckbox,
|
||||
IssuableAssignees,
|
||||
},
|
||||
directives: {
|
||||
|
@ -33,6 +34,15 @@ export default {
|
|||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
showCheckbox: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
checked: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
author() {
|
||||
|
@ -109,8 +119,15 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<li class="issue px-3">
|
||||
<li class="issue gl-px-5!">
|
||||
<div class="issue-box">
|
||||
<div v-if="showCheckbox" class="issue-check">
|
||||
<gl-form-checkbox
|
||||
class="gl-mr-0"
|
||||
:checked="checked"
|
||||
@input="$emit('checked-input', $event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="issuable-info-container">
|
||||
<div class="issuable-main-info">
|
||||
<div data-testid="issuable-title" class="issue-title title">
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
<script>
|
||||
import { GlSkeletonLoading, GlPagination } from '@gitlab/ui';
|
||||
import { uniqueId } from 'lodash';
|
||||
|
||||
import { updateHistory, setUrlParams } from '~/lib/utils/url_utility';
|
||||
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
|
||||
|
||||
import IssuableTabs from './issuable_tabs.vue';
|
||||
import IssuableItem from './issuable_item.vue';
|
||||
import IssuableBulkEditSidebar from './issuable_bulk_edit_sidebar.vue';
|
||||
|
||||
import { DEFAULT_SKELETON_COUNT } from '../constants';
|
||||
|
||||
|
@ -15,6 +17,7 @@ export default {
|
|||
IssuableTabs,
|
||||
FilteredSearchBar,
|
||||
IssuableItem,
|
||||
IssuableBulkEditSidebar,
|
||||
GlPagination,
|
||||
},
|
||||
props: {
|
||||
|
@ -85,6 +88,11 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
showBulkEditSidebar: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
defaultPageSize: {
|
||||
type: Number,
|
||||
required: false,
|
||||
|
@ -116,6 +124,11 @@ export default {
|
|||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
checkedIssuables: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
skeletonItemCount() {
|
||||
const { totalItems, defaultPageSize, currentPage } = this;
|
||||
|
@ -128,8 +141,40 @@ export default {
|
|||
}
|
||||
return DEFAULT_SKELETON_COUNT;
|
||||
},
|
||||
allIssuablesChecked() {
|
||||
return this.bulkEditIssuables.length === this.issuables.length;
|
||||
},
|
||||
/**
|
||||
* Returns all the checked issuables from `checkedIssuables` map.
|
||||
*/
|
||||
bulkEditIssuables() {
|
||||
return Object.keys(this.checkedIssuables).reduce((acc, issuableId) => {
|
||||
if (this.checkedIssuables[issuableId].checked) {
|
||||
acc.push(this.checkedIssuables[issuableId].issuable);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
issuables(list) {
|
||||
this.checkedIssuables = list.reduce((acc, issuable) => {
|
||||
const id = this.issuableId(issuable);
|
||||
acc[id] = {
|
||||
// By default, an issuable is not checked,
|
||||
// But if `checkedIssuables` is already
|
||||
// populated, use existing value.
|
||||
checked:
|
||||
typeof this.checkedIssuables[id] !== 'boolean'
|
||||
? false
|
||||
: this.checkedIssuables[id].checked,
|
||||
// We're caching issuable reference here
|
||||
// for ease of populating in `bulkEditIssuables`.
|
||||
issuable,
|
||||
};
|
||||
return acc;
|
||||
}, {});
|
||||
},
|
||||
urlParams: {
|
||||
deep: true,
|
||||
immediate: true,
|
||||
|
@ -144,6 +189,22 @@ export default {
|
|||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
issuableId(issuable) {
|
||||
return issuable.id || issuable.iid || uniqueId();
|
||||
},
|
||||
issuableChecked(issuable) {
|
||||
return this.checkedIssuables[this.issuableId(issuable)]?.checked;
|
||||
},
|
||||
handleIssuableCheckedInput(issuable, value) {
|
||||
this.checkedIssuables[this.issuableId(issuable)].checked = value;
|
||||
},
|
||||
handleAllIssuablesCheckedInput(value) {
|
||||
Object.keys(this.checkedIssuables).forEach(issuableId => {
|
||||
this.checkedIssuables[issuableId].checked = value;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -167,10 +228,21 @@ export default {
|
|||
:sort-options="sortOptions"
|
||||
:initial-filter-value="initialFilterValue"
|
||||
:initial-sort-by="initialSortBy"
|
||||
:show-checkbox="showBulkEditSidebar"
|
||||
:checkbox-checked="allIssuablesChecked"
|
||||
class="gl-flex-grow-1 row-content-block"
|
||||
@checked-input="handleAllIssuablesCheckedInput"
|
||||
@onFilter="$emit('filter', $event)"
|
||||
@onSort="$emit('sort', $event)"
|
||||
/>
|
||||
<issuable-bulk-edit-sidebar :expanded="showBulkEditSidebar">
|
||||
<template #bulk-edit-actions>
|
||||
<slot name="bulk-edit-actions" :checked-issuables="bulkEditIssuables"></slot>
|
||||
</template>
|
||||
<template #sidebar-items>
|
||||
<slot name="sidebar-items" :checked-issuables="bulkEditIssuables"></slot>
|
||||
</template>
|
||||
</issuable-bulk-edit-sidebar>
|
||||
<div class="issuables-holder">
|
||||
<ul v-if="issuablesLoading" class="content-list">
|
||||
<li v-for="n in skeletonItemCount" :key="n" class="issue gl-px-5! gl-py-5!">
|
||||
|
@ -183,10 +255,13 @@ export default {
|
|||
>
|
||||
<issuable-item
|
||||
v-for="issuable in issuables"
|
||||
:key="issuable.id"
|
||||
:key="issuableId(issuable)"
|
||||
:issuable-symbol="issuableSymbol"
|
||||
:issuable="issuable"
|
||||
:enable-label-permalinks="enableLabelPermalinks"
|
||||
:show-checkbox="showBulkEditSidebar"
|
||||
:checked="issuableChecked(issuable)"
|
||||
@checked-input="handleIssuableCheckedInput(issuable, $event)"
|
||||
>
|
||||
<template #reference>
|
||||
<slot name="reference" :issuable="issuable"></slot>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* eslint-disable no-console */
|
||||
import { getCLS, getFID, getLCP } from 'web-vitals';
|
||||
import { PERFORMANCE_TYPE_MARK, PERFORMANCE_TYPE_MEASURE } from '~/performance_constants';
|
||||
import { PERFORMANCE_TYPE_MARK, PERFORMANCE_TYPE_MEASURE } from '~/performance/constants';
|
||||
|
||||
const initVitalsLog = () => {
|
||||
const reportVital = data => {
|
||||
|
|
|
@ -9,9 +9,9 @@ import FormFooterActions from '~/vue_shared/components/form/form_footer_actions.
|
|||
import {
|
||||
SNIPPET_MARK_EDIT_APP_START,
|
||||
SNIPPET_MEASURE_BLOBS_CONTENT,
|
||||
} from '~/performance_constants';
|
||||
} from '~/performance/constants';
|
||||
import eventHub from '~/blob/components/eventhub';
|
||||
import { performanceMarkAndMeasure } from '~/performance_utils';
|
||||
import { performanceMarkAndMeasure } from '~/performance/utils';
|
||||
|
||||
import UpdateSnippetMutation from '../mutations/updateSnippet.mutation.graphql';
|
||||
import CreateSnippetMutation from '../mutations/createSnippet.mutation.graphql';
|
||||
|
|
|
@ -9,8 +9,8 @@ import { SNIPPET_VISIBILITY_PUBLIC } from '~/snippets/constants';
|
|||
import {
|
||||
SNIPPET_MARK_VIEW_APP_START,
|
||||
SNIPPET_MEASURE_BLOBS_CONTENT,
|
||||
} from '~/performance_constants';
|
||||
import { performanceMarkAndMeasure } from '~/performance_utils';
|
||||
} from '~/performance/constants';
|
||||
import { performanceMarkAndMeasure } from '~/performance/utils';
|
||||
import eventHub from '~/blob/components/eventhub';
|
||||
|
||||
import { getSnippetMixin } from '../mixins/snippets';
|
||||
|
|
|
@ -7,8 +7,8 @@ import {
|
|||
SNIPPET_LEVELS_MAP,
|
||||
SNIPPET_VISIBILITY,
|
||||
} from '../constants';
|
||||
import { performanceMarkAndMeasure } from '~/performance_utils';
|
||||
import { SNIPPET_MARK_BLOBS_CONTENT, SNIPPET_MEASURE_BLOBS_CONTENT } from '~/performance_constants';
|
||||
import { performanceMarkAndMeasure } from '~/performance/utils';
|
||||
import { SNIPPET_MARK_BLOBS_CONTENT, SNIPPET_MEASURE_BLOBS_CONTENT } from '~/performance/constants';
|
||||
|
||||
const createLocalId = () => uniqueId('blob_local_');
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { SNIPPET_MEASURE_BLOBS_CONTENT } from '~/performance_constants';
|
||||
import { SNIPPET_MEASURE_BLOBS_CONTENT } from '~/performance/constants';
|
||||
import eventHub from '~/blob/components/eventhub';
|
||||
|
||||
export default {
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
GlButton,
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlFormCheckbox,
|
||||
GlTooltipDirective,
|
||||
} from '@gitlab/ui';
|
||||
|
||||
|
@ -25,6 +26,7 @@ export default {
|
|||
GlButton,
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlFormCheckbox,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
|
@ -59,6 +61,16 @@ export default {
|
|||
default: '',
|
||||
validator: value => value === '' || /(_desc)|(_asc)/g.test(value),
|
||||
},
|
||||
showCheckbox: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
checkboxChecked: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
searchInputPlaceholder: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
@ -291,6 +303,12 @@ export default {
|
|||
|
||||
<template>
|
||||
<div class="vue-filtered-search-bar-container d-md-flex">
|
||||
<gl-form-checkbox
|
||||
v-if="showCheckbox"
|
||||
class="gl-align-self-center"
|
||||
:checked="checkboxChecked"
|
||||
@input="$emit('checked-input', $event)"
|
||||
/>
|
||||
<gl-filtered-search
|
||||
ref="filteredSearchInput"
|
||||
v-model="filterValue"
|
||||
|
|
|
@ -4,10 +4,16 @@ class Analytics::DevopsAdoption::Segment < ApplicationRecord
|
|||
ALLOWED_SEGMENT_COUNT = 20
|
||||
|
||||
has_many :segment_selections
|
||||
has_many :groups, through: :segment_selections
|
||||
|
||||
validates :name, presence: true, uniqueness: true, length: { maximum: 255 }
|
||||
validate :validate_segment_count
|
||||
|
||||
accepts_nested_attributes_for :segment_selections, allow_destroy: true
|
||||
|
||||
scope :ordered_by_name, -> { order(:name) }
|
||||
scope :with_groups, -> { preload(:groups) }
|
||||
|
||||
private
|
||||
|
||||
def validate_segment_count
|
||||
|
|
|
@ -70,6 +70,8 @@
|
|||
= render 'shared/members/sort_dropdown'
|
||||
- if vue_members_list_enabled
|
||||
.js-group-members-list{ data: group_members_list_data_attributes(@group, @members) }
|
||||
.loading
|
||||
.spinner.spinner-md
|
||||
- else
|
||||
%ul.content-list.members-list{ data: { qa_selector: 'members_list' } }
|
||||
= render partial: 'shared/members/member',
|
||||
|
@ -86,6 +88,8 @@
|
|||
= html_escape(_('Groups with access to %{strong_start}%{group_name}%{strong_end}')) % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
|
||||
- if vue_members_list_enabled
|
||||
.js-group-linked-list{ data: linked_groups_list_data_attributes(@group) }
|
||||
.loading
|
||||
.spinner.spinner-md
|
||||
- else
|
||||
%ul.content-list.members-list{ data: { qa_selector: 'groups_list' } }
|
||||
- @group.shared_with_group_links.each do |group_link|
|
||||
|
@ -100,6 +104,8 @@
|
|||
= render 'shared/members/search_field', name: 'search_invited'
|
||||
- if vue_members_list_enabled
|
||||
.js-group-invited-members-list{ data: group_members_list_data_attributes(@group, @invited_members) }
|
||||
.loading
|
||||
.spinner.spinner-md
|
||||
- else
|
||||
%ul.content-list.members-list
|
||||
= render partial: 'shared/members/member',
|
||||
|
@ -116,6 +122,8 @@
|
|||
= html_escape(_('Users requesting access to %{strong_start}%{group_name}%{strong_end}')) % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
|
||||
- if vue_members_list_enabled
|
||||
.js-group-access-requests-list{ data: group_members_list_data_attributes(@group, @requesters) }
|
||||
.loading
|
||||
.spinner.spinner-md
|
||||
- else
|
||||
%ul.content-list.members-list
|
||||
= render partial: 'shared/members/member',
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add tooltips to design buttons
|
||||
merge_request: 46922
|
||||
author: Lee Tickett
|
||||
type: added
|
|
@ -6025,6 +6025,81 @@ type DetailedStatus {
|
|||
tooltip: String
|
||||
}
|
||||
|
||||
"""
|
||||
Segment
|
||||
"""
|
||||
type DevopsAdoptionSegment {
|
||||
"""
|
||||
Assigned groups
|
||||
"""
|
||||
groups(
|
||||
"""
|
||||
Returns the elements in the list that come after the specified cursor.
|
||||
"""
|
||||
after: String
|
||||
|
||||
"""
|
||||
Returns the elements in the list that come before the specified cursor.
|
||||
"""
|
||||
before: String
|
||||
|
||||
"""
|
||||
Returns the first _n_ elements from the list.
|
||||
"""
|
||||
first: Int
|
||||
|
||||
"""
|
||||
Returns the last _n_ elements from the list.
|
||||
"""
|
||||
last: Int
|
||||
): GroupConnection
|
||||
|
||||
"""
|
||||
ID of the segment
|
||||
"""
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
Name of the segment
|
||||
"""
|
||||
name: String!
|
||||
}
|
||||
|
||||
"""
|
||||
The connection type for DevopsAdoptionSegment.
|
||||
"""
|
||||
type DevopsAdoptionSegmentConnection {
|
||||
"""
|
||||
A list of edges.
|
||||
"""
|
||||
edges: [DevopsAdoptionSegmentEdge]
|
||||
|
||||
"""
|
||||
A list of nodes.
|
||||
"""
|
||||
nodes: [DevopsAdoptionSegment]
|
||||
|
||||
"""
|
||||
Information to aid in pagination.
|
||||
"""
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
"""
|
||||
An edge in a connection.
|
||||
"""
|
||||
type DevopsAdoptionSegmentEdge {
|
||||
"""
|
||||
A cursor for use in pagination.
|
||||
"""
|
||||
cursor: String!
|
||||
|
||||
"""
|
||||
The item at the end of the edge.
|
||||
"""
|
||||
node: DevopsAdoptionSegment
|
||||
}
|
||||
|
||||
input DiffImagePositionInput {
|
||||
"""
|
||||
Merge base of the branch the comment was made on
|
||||
|
@ -9165,6 +9240,41 @@ type Group {
|
|||
webUrl: String!
|
||||
}
|
||||
|
||||
"""
|
||||
The connection type for Group.
|
||||
"""
|
||||
type GroupConnection {
|
||||
"""
|
||||
A list of edges.
|
||||
"""
|
||||
edges: [GroupEdge]
|
||||
|
||||
"""
|
||||
A list of nodes.
|
||||
"""
|
||||
nodes: [Group]
|
||||
|
||||
"""
|
||||
Information to aid in pagination.
|
||||
"""
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
"""
|
||||
An edge in a connection.
|
||||
"""
|
||||
type GroupEdge {
|
||||
"""
|
||||
A cursor for use in pagination.
|
||||
"""
|
||||
cursor: String!
|
||||
|
||||
"""
|
||||
The item at the end of the edge.
|
||||
"""
|
||||
node: Group
|
||||
}
|
||||
|
||||
"""
|
||||
Identifier of Group
|
||||
"""
|
||||
|
@ -16540,6 +16650,31 @@ type Query {
|
|||
"""
|
||||
designManagement: DesignManagement!
|
||||
|
||||
"""
|
||||
Get configured DevOps adoption segments on the instance
|
||||
"""
|
||||
devopsAdoptionSegments(
|
||||
"""
|
||||
Returns the elements in the list that come after the specified cursor.
|
||||
"""
|
||||
after: String
|
||||
|
||||
"""
|
||||
Returns the elements in the list that come before the specified cursor.
|
||||
"""
|
||||
before: String
|
||||
|
||||
"""
|
||||
Returns the first _n_ elements from the list.
|
||||
"""
|
||||
first: Int
|
||||
|
||||
"""
|
||||
Returns the last _n_ elements from the list.
|
||||
"""
|
||||
last: Int
|
||||
): DevopsAdoptionSegmentConnection
|
||||
|
||||
"""
|
||||
Text to echo back
|
||||
"""
|
||||
|
|
|
@ -16527,6 +16527,220 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "DevopsAdoptionSegment",
|
||||
"description": "Segment",
|
||||
"fields": [
|
||||
{
|
||||
"name": "groups",
|
||||
"description": "Assigned groups",
|
||||
"args": [
|
||||
{
|
||||
"name": "after",
|
||||
"description": "Returns the elements in the list that come after the specified cursor.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "before",
|
||||
"description": "Returns the elements in the list that come before the specified cursor.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "first",
|
||||
"description": "Returns the first _n_ elements from the list.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "last",
|
||||
"description": "Returns the last _n_ elements from the list.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "GroupConnection",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"description": "ID of the segment",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"description": "Name of the segment",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "DevopsAdoptionSegmentConnection",
|
||||
"description": "The connection type for DevopsAdoptionSegment.",
|
||||
"fields": [
|
||||
{
|
||||
"name": "edges",
|
||||
"description": "A list of edges.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "DevopsAdoptionSegmentEdge",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "nodes",
|
||||
"description": "A list of nodes.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "DevopsAdoptionSegment",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "pageInfo",
|
||||
"description": "Information to aid in pagination.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "PageInfo",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "DevopsAdoptionSegmentEdge",
|
||||
"description": "An edge in a connection.",
|
||||
"fields": [
|
||||
{
|
||||
"name": "cursor",
|
||||
"description": "A cursor for use in pagination.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "node",
|
||||
"description": "The item at the end of the edge.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "DevopsAdoptionSegment",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "DiffImagePositionInput",
|
||||
|
@ -24901,6 +25115,118 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "GroupConnection",
|
||||
"description": "The connection type for Group.",
|
||||
"fields": [
|
||||
{
|
||||
"name": "edges",
|
||||
"description": "A list of edges.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "GroupEdge",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "nodes",
|
||||
"description": "A list of nodes.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "Group",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "pageInfo",
|
||||
"description": "Information to aid in pagination.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "PageInfo",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "GroupEdge",
|
||||
"description": "An edge in a connection.",
|
||||
"fields": [
|
||||
{
|
||||
"name": "cursor",
|
||||
"description": "A cursor for use in pagination.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "node",
|
||||
"description": "The item at the end of the edge.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "Group",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "SCALAR",
|
||||
"name": "GroupID",
|
||||
|
@ -48034,6 +48360,59 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "devopsAdoptionSegments",
|
||||
"description": "Get configured DevOps adoption segments on the instance",
|
||||
"args": [
|
||||
{
|
||||
"name": "after",
|
||||
"description": "Returns the elements in the list that come after the specified cursor.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "before",
|
||||
"description": "Returns the elements in the list that come before the specified cursor.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "first",
|
||||
"description": "Returns the first _n_ elements from the list.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "last",
|
||||
"description": "Returns the last _n_ elements from the list.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "DevopsAdoptionSegmentConnection",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "echo",
|
||||
"description": "Text to echo back",
|
||||
|
|
|
@ -1000,6 +1000,16 @@ Autogenerated return type of DestroySnippet.
|
|||
| `text` | String | Text of the status |
|
||||
| `tooltip` | String | Tooltip associated with the status |
|
||||
|
||||
### DevopsAdoptionSegment
|
||||
|
||||
Segment.
|
||||
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `groups` | GroupConnection | Assigned groups |
|
||||
| `id` | ID! | ID of the segment |
|
||||
| `name` | String! | Name of the segment |
|
||||
|
||||
### DiffPosition
|
||||
|
||||
| Field | Type | Description |
|
||||
|
|
|
@ -945,3 +945,7 @@ bin/rake gitlab:usage_data:dump_sql_in_json
|
|||
# You may pipe the output into a file
|
||||
bin/rake gitlab:usage_data:dump_sql_in_yaml > ~/Desktop/usage-metrics-2020-09-02.yaml
|
||||
```
|
||||
|
||||
## Generating and troubleshooting usage ping
|
||||
|
||||
To get a usage ping, or to troubleshoot caching issues on your GitLab instance, please follow [instructions to generate usage ping](../../administration/troubleshooting/gitlab_rails_cheat_sheet.md#generate-usage-ping).
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 16 KiB |
|
@ -4,34 +4,38 @@ group: Package
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
|
||||
---
|
||||
|
||||
# GitLab NuGet Repository
|
||||
# NuGet packages in the Package Registry
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/20050) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.8.
|
||||
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/221259) to GitLab Core in 13.3.
|
||||
|
||||
With the GitLab NuGet Repository, every project can have its own space to store NuGet packages.
|
||||
Publish NuGet packages in your project’s Package Registry. Then, install the
|
||||
packages whenever you need to use them as a dependency.
|
||||
|
||||
The GitLab NuGet Repository works with:
|
||||
The Package Registry works with:
|
||||
|
||||
- [NuGet CLI](https://docs.microsoft.com/en-us/nuget/reference/nuget-exe-cli-reference)
|
||||
- [.NET Core CLI](https://docs.microsoft.com/en-us/dotnet/core/tools/)
|
||||
- [Visual Studio](https://visualstudio.microsoft.com/vs/)
|
||||
|
||||
## Setting up your development environment
|
||||
## Install NuGet
|
||||
|
||||
[NuGet CLI 5.1 or later](https://www.nuget.org/downloads) is required. Earlier versions have not been tested
|
||||
against the GitLab NuGet Repository and might not work. If you have [Visual Studio](https://visualstudio.microsoft.com/vs/),
|
||||
NuGet CLI is probably already installed.
|
||||
The required minimum versions are:
|
||||
|
||||
Alternatively, you can use [.NET SDK 3.0 or later](https://dotnet.microsoft.com/download/dotnet-core/3.0), which installs NuGet CLI.
|
||||
- [NuGet CLI 5.1 or later](https://www.nuget.org/downloads). If you have
|
||||
[Visual Studio](https://visualstudio.microsoft.com/vs/), the NuGet CLI is
|
||||
probably already installed.
|
||||
- Alternatively, you can use [.NET SDK 3.0 or later](https://dotnet.microsoft.com/download/dotnet-core/3.0),
|
||||
which installs the NuGet CLI.
|
||||
- NuGet protocol version 3 or later.
|
||||
|
||||
You can confirm that [NuGet CLI](https://www.nuget.org/) is properly installed with:
|
||||
Verify that the [NuGet CLI](https://www.nuget.org/) is installed by running:
|
||||
|
||||
```shell
|
||||
nuget help
|
||||
```
|
||||
|
||||
You should see something similar to:
|
||||
The output should be similar to:
|
||||
|
||||
```plaintext
|
||||
NuGet Version: 5.1.0.6013
|
||||
|
@ -43,103 +47,98 @@ Available commands:
|
|||
[output truncated]
|
||||
```
|
||||
|
||||
NOTE: **Note:**
|
||||
GitLab currently only supports NuGet's protocol version 3. Earlier versions are not supported.
|
||||
### Install NuGet on macOS
|
||||
|
||||
### macOS support
|
||||
For macOS, you can use [Mono](https://www.mono-project.com/) to run the
|
||||
NuGet CLI.
|
||||
|
||||
For macOS, you can also use [Mono](https://www.mono-project.com/) to run
|
||||
the NuGet CLI. For Homebrew users, run `brew install mono` to install
|
||||
Mono. Then you should be able to download the Windows C# binary
|
||||
`nuget.exe` from the [NuGet CLI page](https://www.nuget.org/downloads)
|
||||
and run:
|
||||
1. If you use Homebrew, to install Mono, run `brew install mono`.
|
||||
1. Download the Windows C# binary `nuget.exe` from the [NuGet CLI page](https://www.nuget.org/downloads).
|
||||
1. Run this command:
|
||||
|
||||
```shell
|
||||
mono nuget.exe
|
||||
```
|
||||
```shell
|
||||
mono nuget.exe
|
||||
```
|
||||
|
||||
## Enabling the NuGet Repository
|
||||
## Add the Package Registry as a source for NuGet packages
|
||||
|
||||
NOTE: **Note:**
|
||||
This option is available only if your GitLab administrator has
|
||||
[enabled support for the Package Registry](../../../administration/packages/index.md).
|
||||
To publish and install packages to the Package Registry, you must add the
|
||||
Package Registry as a source for your packages.
|
||||
|
||||
When the NuGet Repository is enabled, it is available for all new projects
|
||||
by default. To enable it for existing projects, or if you want to disable it:
|
||||
|
||||
1. Navigate to your project's **Settings > General > Visibility, project features, permissions**.
|
||||
1. Find the Packages feature and enable or disable it.
|
||||
1. Click on **Save changes** for the changes to take effect.
|
||||
|
||||
You should then be able to see the **Packages & Registries** section on the left sidebar.
|
||||
|
||||
## Adding the GitLab NuGet Repository as a source to NuGet
|
||||
|
||||
You need the following:
|
||||
Prerequisites:
|
||||
|
||||
- Your GitLab username.
|
||||
- A personal access token or deploy token. For repository authentication:
|
||||
- You can generate a [personal access token](../../../user/profile/personal_access_tokens.md) with the scope set to `api`.
|
||||
- You can generate a [deploy token](./../../project/deploy_tokens/index.md) with the scope set to `read_package_registry`, `write_package_registry`, or both.
|
||||
- A suitable name for your source.
|
||||
- Your project ID which can be found on the home page of your project.
|
||||
- You can generate a [personal access token](../../../user/profile/personal_access_tokens.md)
|
||||
with the scope set to `api`.
|
||||
- You can generate a [deploy token](./../../project/deploy_tokens/index.md)
|
||||
with the scope set to `read_package_registry`, `write_package_registry`, or
|
||||
both.
|
||||
- A name for your source.
|
||||
- Your project ID, which is found on your project's home page.
|
||||
|
||||
You can now add a new source to NuGet with:
|
||||
|
||||
- [NuGet CLI](#add-nuget-repository-source-with-nuget-cli)
|
||||
- [Visual Studio](#add-nuget-repository-source-with-visual-studio).
|
||||
- [.NET CLI](#add-nuget-repository-source-with-net-cli)
|
||||
- [NuGet CLI](#add-a-source-with-the-nuget-cli)
|
||||
- [Visual Studio](#add-a-source-with-visual-studio)
|
||||
- [.NET CLI](#add-a-source-with-the-net-cli)
|
||||
|
||||
### Add NuGet Repository source with NuGet CLI
|
||||
### Add a source with the NuGet CLI
|
||||
|
||||
To add the GitLab NuGet Repository as a source with `nuget`:
|
||||
To add the Package Registry as a source with `nuget`:
|
||||
|
||||
```shell
|
||||
nuget source Add -Name <source_name> -Source "https://gitlab-instance.example.com/api/v4/projects/<your_project_id>/packages/nuget/index.json" -UserName <gitlab_username or deploy_token_username> -Password <gitlab_personal_access_token or deploy_token>
|
||||
nuget source Add -Name <source_name> -Source "https://gitlab.example.com/api/v4/projects/<your_project_id>/packages/nuget/index.json" -UserName <gitlab_username or deploy_token_username> -Password <gitlab_personal_access_token or deploy_token>
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
- `<source_name>` is your desired source name.
|
||||
- `<source_name>` is the desired source name.
|
||||
|
||||
For example:
|
||||
|
||||
```shell
|
||||
nuget source Add -Name "GitLab" -Source "https://gitlab.example/api/v4/projects/10/packages/nuget/index.json" -UserName carol -Password 12345678asdf
|
||||
nuget source Add -Name "GitLab" -Source "https://gitlab.example.com/api/v4/projects/10/packages/nuget/index.json" -UserName carol -Password 12345678asdf
|
||||
```
|
||||
|
||||
### Add NuGet Repository source with Visual Studio
|
||||
### Add a source with Visual Studio
|
||||
|
||||
To add the Package Registry as a source with Visual Studio:
|
||||
|
||||
1. Open [Visual Studio](https://visualstudio.microsoft.com/vs/).
|
||||
1. Open the **FILE > OPTIONS** (Windows) or **Visual Studio > Preferences** (Mac OS).
|
||||
1. In the **NuGet** section, open **Sources** to see a list of all your NuGet sources.
|
||||
1. Click **Add**.
|
||||
1. Fill the fields with:
|
||||
- **Name**: Desired name for the source
|
||||
- **Location**: `https://gitlab.com/api/v4/projects/<your_project_id>/packages/nuget/index.json`
|
||||
- Replace `<your_project_id>` with your project ID.
|
||||
- If you have a self-managed GitLab installation, replace `gitlab.com` with your domain name.
|
||||
- **Username**: Your GitLab username or deploy token username
|
||||
- **Password**: Your personal access token or deploy token
|
||||
1. In Windows, select **File > Options**. On macOS, select **Visual Studio > Preferences**.
|
||||
1. In the **NuGet** section, select **Sources** to view a list of all your NuGet sources.
|
||||
1. Select **Add**.
|
||||
1. Complete the following fields:
|
||||
- **Name**: Name for the source.
|
||||
- **Location**: `https://gitlab.example.com/api/v4/projects/<your_project_id>/packages/nuget/index.json`,
|
||||
where `<your_project_id>` is your project ID, and `gitlab.example.com` is
|
||||
your domain name.
|
||||
- **Username**: Your GitLab username or deploy token username.
|
||||
- **Password**: Your personal access token or deploy token.
|
||||
|
||||
![Visual Studio Adding a NuGet source](img/visual_studio_adding_nuget_source.png)
|
||||
|
||||
1. Click **Save**.
|
||||
|
||||
![Visual Studio NuGet source added](img/visual_studio_nuget_source_added.png)
|
||||
The source is displayed in your list.
|
||||
|
||||
In case of any warning, please make sure that the **Location**, **Username**, and **Password** are correct.
|
||||
![Visual Studio NuGet source added](img/visual_studio_nuget_source_added.png)
|
||||
|
||||
### Add NuGet Repository source with .NET CLI
|
||||
If you get a warning, ensure that the **Location**, **Username**, and
|
||||
**Password** are correct.
|
||||
|
||||
To add the GitLab NuGet Repository as a source for .NET, create a file named `nuget.config` in the root of your project with the following content:
|
||||
### Add a source with the .NET CLI
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
To add the Package Registry as a source for .NET:
|
||||
|
||||
1. In the root of your project, create a file named `nuget.config`.
|
||||
1. Add this content:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<clear />
|
||||
<add key="gitlab" value="https://gitlab-instance.example.com/api/v4/projects/<your_project_id>/packages/nuget/index.json" />
|
||||
<add key="gitlab" value="https://gitlab.example.com/api/v4/projects/<your_project_id>/packages/nuget/index.json" />
|
||||
</packageSources>
|
||||
<packageSourceCredentials>
|
||||
<gitlab>
|
||||
|
@ -147,46 +146,51 @@ To add the GitLab NuGet Repository as a source for .NET, create a file named `nu
|
|||
<add key="ClearTextPassword" value="<gitlab_personal_access_token or deploy_token>" />
|
||||
</gitlab>
|
||||
</packageSourceCredentials>
|
||||
</configuration>
|
||||
```
|
||||
</configuration>
|
||||
```
|
||||
|
||||
## Uploading packages
|
||||
## Publish a NuGet package
|
||||
|
||||
When uploading packages, note that:
|
||||
When publishing packages:
|
||||
|
||||
- The Package Registry on GitLab.com can store up to 500 MB of content. This limit is [configurable for self-managed GitLab instances](../../../administration/instance_limits.md#package-registry-limits).
|
||||
- If you upload the same package with the same version multiple times, each consecutive upload
|
||||
is saved as a separate file. When installing a package, GitLab serves the most recent file.
|
||||
- When uploading packages to GitLab, they are not displayed in the packages UI of your project
|
||||
immediately. It can take up to 10 minutes to process a package.
|
||||
- The Package Registry on GitLab.com can store up to 500 MB of content.
|
||||
This limit is [configurable for self-managed GitLab instances](../../../administration/instance_limits.md#package-registry-limits).
|
||||
- If you publish the same package with the same version multiple times, each
|
||||
consecutive upload is saved as a separate file. When installing a package,
|
||||
GitLab serves the most recent file.
|
||||
- When publishing packages to GitLab, they aren't displayed in the packages user
|
||||
interface of your project immediately. It can take up to 10 minutes to process
|
||||
a package.
|
||||
|
||||
### Upload packages with NuGet CLI
|
||||
### Publish a package with the NuGet CLI
|
||||
|
||||
This section assumes that your project is properly built and you already [created a NuGet package with NuGet CLI](https://docs.microsoft.com/en-us/nuget/create-packages/creating-a-package).
|
||||
Upload your package using the following command:
|
||||
Prerequisite:
|
||||
|
||||
- [A NuGet package created with NuGet CLI](https://docs.microsoft.com/en-us/nuget/create-packages/creating-a-package).
|
||||
|
||||
Publish a package by running this command:
|
||||
|
||||
```shell
|
||||
nuget push <package_file> -Source <source_name>
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
- `<package_file>` is your package filename, ending in `.nupkg`.
|
||||
- `<source_name>` is the [source name used during setup](#adding-the-gitlab-nuget-repository-as-a-source-to-nuget).
|
||||
- `<source_name>` is the [source name used during setup](#add-a-source-with-the-nuget-cli).
|
||||
|
||||
### Upload packages with .NET CLI
|
||||
### Publish a package with the .NET CLI
|
||||
|
||||
This section assumes that your project is properly built and you already [created a NuGet package with .NET CLI](https://docs.microsoft.com/en-us/nuget/create-packages/creating-a-package-dotnet-cli).
|
||||
Upload your package using the following command:
|
||||
Prerequisite:
|
||||
|
||||
[A NuGet package created with .NET CLI](https://docs.microsoft.com/en-us/nuget/create-packages/creating-a-package-dotnet-cli).
|
||||
|
||||
Publish a package by running this command:
|
||||
|
||||
```shell
|
||||
dotnet nuget push <package_file> --source <source_name>
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
- `<package_file>` is your package filename, ending in `.nupkg`.
|
||||
- `<source_name>` is the [source name used during setup](#adding-the-gitlab-nuget-repository-as-a-source-to-nuget).
|
||||
- `<source_name>` is the [source name used during setup](#add-a-source-with-the-net-cli).
|
||||
|
||||
For example:
|
||||
|
||||
|
@ -194,58 +198,16 @@ For example:
|
|||
dotnet nuget push MyPackage.1.0.0.nupkg --source gitlab
|
||||
```
|
||||
|
||||
## Install packages
|
||||
|
||||
### Install a package with NuGet CLI
|
||||
|
||||
CAUTION: **Warning:**
|
||||
By default, `nuget` checks the official source at `nuget.org` first. If you have a package in the
|
||||
GitLab NuGet Repository with the same name as a package at `nuget.org`, you must specify the source
|
||||
name to install the correct package.
|
||||
|
||||
Install the latest version of a package using the following command:
|
||||
|
||||
```shell
|
||||
nuget install <package_id> -OutputDirectory <output_directory> \
|
||||
-Version <package_version> \
|
||||
-Source <source_name>
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
- `<package_id>` is the package ID.
|
||||
- `<output_directory>` is the output directory, where the package is installed.
|
||||
- `<package_version>` (Optional) is the package version.
|
||||
- `<source_name>` (Optional) is the source name.
|
||||
|
||||
### Install a package with .NET CLI
|
||||
|
||||
CAUTION: **Warning:**
|
||||
If you have a package in the GitLab NuGet Repository with the same name as a package at a different source,
|
||||
you should verify the order in which `dotnet` checks sources during install. This is defined in the
|
||||
`nuget.config` file.
|
||||
|
||||
Install the latest version of a package using the following command:
|
||||
|
||||
```shell
|
||||
dotnet add package <package_id> \
|
||||
-v <package_version>
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
- `<package_id>` is the package ID.
|
||||
- `<package_version>` (Optional) is the package version.
|
||||
|
||||
## Publishing a NuGet package with CI/CD
|
||||
### Publish a NuGet package by using CI/CD
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/36424) in GitLab 13.3.
|
||||
|
||||
If you’re using NuGet with GitLab CI/CD, a CI job token can be used instead of a personal access token or deploy token.
|
||||
The token inherits the permissions of the user that generates the pipeline.
|
||||
If you’re using NuGet with GitLab CI/CD, a CI job token can be used instead of a
|
||||
personal access token or deploy token. The token inherits the permissions of the
|
||||
user that generates the pipeline.
|
||||
|
||||
This example shows how to create a new package each time the `master` branch
|
||||
is updated:
|
||||
This example shows how to create a new package each time the `master` branch is
|
||||
updated:
|
||||
|
||||
1. Add a `deploy` job to your `.gitlab-ci.yml` file:
|
||||
|
||||
|
@ -267,4 +229,43 @@ is updated:
|
|||
- master
|
||||
```
|
||||
|
||||
1. Commit the changes and push it to your GitLab repository to trigger a new CI build.
|
||||
1. Commit the changes and push it to your GitLab repository to trigger a new CI/CD build.
|
||||
|
||||
## Install packages
|
||||
|
||||
### Install a package with the NuGet CLI
|
||||
|
||||
CAUTION: **Warning:**
|
||||
By default, `nuget` checks the official source at `nuget.org` first. If you have
|
||||
a NuGet package in the Package Registry with the same name as a package at
|
||||
`nuget.org`, you must specify the source name to install the correct package.
|
||||
|
||||
Install the latest version of a package by running this command:
|
||||
|
||||
```shell
|
||||
nuget install <package_id> -OutputDirectory <output_directory> \
|
||||
-Version <package_version> \
|
||||
-Source <source_name>
|
||||
```
|
||||
|
||||
- `<package_id>` is the package ID.
|
||||
- `<output_directory>` is the output directory, where the package is installed.
|
||||
- `<package_version>` The package version. Optional.
|
||||
- `<source_name>` The source name. Optional.
|
||||
|
||||
### Install a package with the .NET CLI
|
||||
|
||||
CAUTION: **Warning:**
|
||||
If you have a package in the Package Registry with the same name as a package at
|
||||
a different source, verify the order in which `dotnet` checks sources during
|
||||
install. This is defined in the `nuget.config` file.
|
||||
|
||||
Install the latest version of a package by running this command:
|
||||
|
||||
```shell
|
||||
dotnet add package <package_id> \
|
||||
-v <package_version>
|
||||
```
|
||||
|
||||
- `<package_id>` is the package ID.
|
||||
- `<package_version>` is the package version. Optional.
|
||||
|
|
|
@ -31,23 +31,31 @@ authenticate with GitLab by using the `CI_JOB_TOKEN`.
|
|||
|
||||
CI/CD templates, which you can use to get started, are in [this repo](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates).
|
||||
|
||||
Learn more about [using CI/CD to build Maven packages](../maven_repository/index.md#create-maven-packages-with-gitlab-cicd), [NPM packages](../npm_registry/index.md#publish-an-npm-package-by-using-cicd), [Composer packages](../composer_repository/index.md#publish-a-composer-package-by-using-cicd), [NuGet Packages](../nuget_repository/index.md#publishing-a-nuget-package-with-cicd), [Conan Packages](../conan_repository/index.md#publish-a-conan-package-by-using-cicd), [PyPI packages](../pypi_repository/index.md#using-gitlab-ci-with-pypi-packages), and [generic packages](../generic_packages/index.md#publish-a-generic-package-by-using-cicd).
|
||||
Learn more about using CI/CD to build:
|
||||
|
||||
If you use CI/CD to build a package, extended activity
|
||||
information is displayed when you view the package details:
|
||||
- [Maven packages](../maven_repository/index.md#create-maven-packages-with-gitlab-cicd)
|
||||
- [NPM packages](../npm_registry/index.md#publish-an-npm-package-by-using-cicd)
|
||||
- [Composer packages](../composer_repository/index.md#publish-a-composer-package-by-using-cicd)
|
||||
- [NuGet packages](../nuget_repository/index.md#publish-a-nuget-package-by-using-cicd)
|
||||
- [Conan packages](../conan_repository/index.md#publish-a-conan-package-by-using-cicd)
|
||||
- [PyPI packages](../pypi_repository/index.md#using-gitlab-ci-with-pypi-packages)
|
||||
- [Generic packages](../generic_packages/index.md#publish-a-generic-package-by-using-cicd)
|
||||
|
||||
If you use CI/CD to build a package, extended activity information is displayed
|
||||
when you view the package details:
|
||||
|
||||
![Package CI/CD activity](img/package_activity_v12_10.png)
|
||||
|
||||
When using Maven and NPM, you can view which pipeline published the package, as well as the commit and
|
||||
user who triggered it.
|
||||
When using Maven and NPM, you can view which pipeline published the package, and
|
||||
the commit and user who triggered it.
|
||||
|
||||
## Download a package
|
||||
|
||||
To download a package:
|
||||
|
||||
1. Go to **Packages & Registries > Package Registry**.
|
||||
1. Click the name of the package you want to download.
|
||||
1. In the **Activity** section, click the name of the package you want to download.
|
||||
1. Select the name of the package you want to download.
|
||||
1. In the **Activity** section, select the name of the package you want to download.
|
||||
|
||||
## Delete a package
|
||||
|
||||
|
|
|
@ -9219,6 +9219,9 @@ msgstr ""
|
|||
msgid "DesignManagement|Adding a design with the same filename replaces the file in a new version."
|
||||
msgstr ""
|
||||
|
||||
msgid "DesignManagement|Archive design"
|
||||
msgstr ""
|
||||
|
||||
msgid "DesignManagement|Archive designs"
|
||||
msgstr ""
|
||||
|
||||
|
@ -9276,6 +9279,9 @@ msgstr ""
|
|||
msgid "DesignManagement|Discard comment"
|
||||
msgstr ""
|
||||
|
||||
msgid "DesignManagement|Download design"
|
||||
msgstr ""
|
||||
|
||||
msgid "DesignManagement|Error uploading a new design. Please try again."
|
||||
msgstr ""
|
||||
|
||||
|
|
1
qa/qa.rb
1
qa/qa.rb
|
@ -19,6 +19,7 @@ module QA
|
|||
autoload :Saml, 'qa/flow/saml'
|
||||
autoload :User, 'qa/flow/user'
|
||||
autoload :MergeRequest, 'qa/flow/merge_request'
|
||||
autoload :Pipeline, 'qa/flow/pipeline'
|
||||
end
|
||||
|
||||
##
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
module Flow
|
||||
module Pipeline
|
||||
module_function
|
||||
|
||||
# In some cases we don't need to wait for anything, blocked, running or pending is acceptable
|
||||
# Some cases only need pipeline to finish with different condition (completion, success or replication)
|
||||
def visit_latest_pipeline(pipeline_condition: nil)
|
||||
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
|
||||
Page::Project::Pipeline::Index.perform(&:"wait_for_latest_pipeline_#{pipeline_condition}") if pipeline_condition
|
||||
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -65,8 +65,7 @@ module QA
|
|||
)
|
||||
end.project.visit!
|
||||
|
||||
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
|
||||
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
|
||||
Flow::Pipeline.visit_latest_pipeline
|
||||
|
||||
{
|
||||
'test-success': :passed,
|
||||
|
|
|
@ -29,7 +29,7 @@ module QA
|
|||
Flow::Login.sign_in
|
||||
add_ci_files
|
||||
project.visit!
|
||||
view_the_last_pipeline
|
||||
Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'success')
|
||||
end
|
||||
|
||||
after do
|
||||
|
@ -64,12 +64,6 @@ module QA
|
|||
end
|
||||
end
|
||||
|
||||
def view_the_last_pipeline
|
||||
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
|
||||
Page::Project::Pipeline::Index.perform(&:wait_for_latest_pipeline_success)
|
||||
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
|
||||
end
|
||||
|
||||
def parent_ci_file
|
||||
{
|
||||
file_path: '.gitlab-ci.yml',
|
||||
|
|
|
@ -57,8 +57,7 @@ module QA
|
|||
end
|
||||
|
||||
project.visit!
|
||||
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
|
||||
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
|
||||
Flow::Pipeline.visit_latest_pipeline
|
||||
|
||||
Page::Project::Pipeline::Show.perform do |pipeline|
|
||||
pipeline.click_job('create_package')
|
||||
|
|
|
@ -94,8 +94,7 @@ module QA
|
|||
end
|
||||
|
||||
project.visit!
|
||||
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
|
||||
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
|
||||
Flow::Pipeline.visit_latest_pipeline
|
||||
|
||||
Page::Project::Pipeline::Show.perform do |pipeline|
|
||||
pipeline.click_job('deploy')
|
||||
|
|
|
@ -61,8 +61,7 @@ module QA
|
|||
end
|
||||
|
||||
project.visit!
|
||||
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
|
||||
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
|
||||
Flow::Pipeline.visit_latest_pipeline
|
||||
|
||||
Page::Project::Pipeline::Show.perform do |pipeline|
|
||||
pipeline.click_job('deploy')
|
||||
|
|
|
@ -74,8 +74,7 @@ module QA
|
|||
end
|
||||
|
||||
project.visit!
|
||||
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
|
||||
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
|
||||
Flow::Pipeline.visit_latest_pipeline
|
||||
|
||||
Page::Project::Pipeline::Show.perform do |pipeline|
|
||||
pipeline.click_job('run')
|
||||
|
|
|
@ -77,8 +77,7 @@ module QA
|
|||
|
||||
sha1sum = Digest::SHA1.hexdigest(gitlab_ci)
|
||||
|
||||
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
|
||||
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
|
||||
Flow::Pipeline.visit_latest_pipeline
|
||||
Page::Project::Pipeline::Show.perform(&:click_on_first_job)
|
||||
|
||||
Page::Project::Job::Show.perform do |job|
|
||||
|
|
|
@ -27,7 +27,7 @@ module QA
|
|||
|
||||
it 'parent pipelines passes if child passes', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/751' do
|
||||
add_ci_files(success_child_ci_file)
|
||||
view_pipelines
|
||||
Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'completion')
|
||||
|
||||
Page::Project::Pipeline::Show.perform do |parent_pipeline|
|
||||
expect(parent_pipeline).to have_child_pipeline
|
||||
|
@ -37,7 +37,7 @@ module QA
|
|||
|
||||
it 'parent pipeline fails if child fails', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/752' do
|
||||
add_ci_files(fail_child_ci_file)
|
||||
view_pipelines
|
||||
Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'completion')
|
||||
|
||||
Page::Project::Pipeline::Show.perform do |parent_pipeline|
|
||||
expect(parent_pipeline).to have_child_pipeline
|
||||
|
@ -47,12 +47,6 @@ module QA
|
|||
|
||||
private
|
||||
|
||||
def view_pipelines
|
||||
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
|
||||
Page::Project::Pipeline::Index.perform(&:wait_for_latest_pipeline_completion)
|
||||
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
|
||||
end
|
||||
|
||||
def success_child_ci_file
|
||||
{
|
||||
file_path: '.child-ci.yml',
|
||||
|
|
|
@ -27,7 +27,7 @@ module QA
|
|||
|
||||
it 'parent pipelines passes if child passes', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/754' do
|
||||
add_ci_files(success_child_ci_file)
|
||||
view_pipelines
|
||||
Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'completion')
|
||||
|
||||
Page::Project::Pipeline::Show.perform do |parent_pipeline|
|
||||
expect(parent_pipeline).to have_child_pipeline
|
||||
|
@ -37,7 +37,7 @@ module QA
|
|||
|
||||
it 'parent pipeline passes even if child fails', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/753' do
|
||||
add_ci_files(fail_child_ci_file)
|
||||
view_pipelines
|
||||
Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'completion')
|
||||
|
||||
Page::Project::Pipeline::Show.perform do |parent_pipeline|
|
||||
expect(parent_pipeline).to have_child_pipeline
|
||||
|
@ -47,12 +47,6 @@ module QA
|
|||
|
||||
private
|
||||
|
||||
def view_pipelines
|
||||
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
|
||||
Page::Project::Pipeline::Index.perform(&:wait_for_latest_pipeline_completion)
|
||||
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
|
||||
end
|
||||
|
||||
def success_child_ci_file
|
||||
{
|
||||
file_path: '.child-ci.yml',
|
||||
|
|
|
@ -54,8 +54,7 @@ module QA
|
|||
push.commit_message = 'Create Auto DevOps compatible rack application'
|
||||
end
|
||||
|
||||
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
|
||||
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
|
||||
Flow::Pipeline.visit_latest_pipeline
|
||||
|
||||
Page::Project::Pipeline::Show.perform do |pipeline|
|
||||
pipeline.click_job('build')
|
||||
|
@ -119,8 +118,7 @@ module QA
|
|||
end
|
||||
|
||||
it 'runs an AutoDevOps pipeline', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/444' do
|
||||
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
|
||||
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
|
||||
Flow::Pipeline.visit_latest_pipeline
|
||||
|
||||
Page::Project::Pipeline::Show.perform do |pipeline|
|
||||
expect(pipeline).to have_tag('Auto DevOps')
|
||||
|
|
|
@ -46,6 +46,7 @@ exports[`Design management toolbar component renders design and updated data 1`]
|
|||
href="/-/designs/306/7f747adcd4693afadbe968d7ba7d983349b9012d"
|
||||
icon="download"
|
||||
size="medium"
|
||||
title="Download design"
|
||||
variant="default"
|
||||
/>
|
||||
|
||||
|
@ -57,6 +58,7 @@ exports[`Design management toolbar component renders design and updated data 1`]
|
|||
buttonvariant="warning"
|
||||
class="gl-ml-3"
|
||||
hasselecteddesigns="true"
|
||||
title="Archive design"
|
||||
/>
|
||||
</header>
|
||||
`;
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
|
||||
import IssuableBulkEditSidebar from '~/issuable_list/components/issuable_bulk_edit_sidebar.vue';
|
||||
|
||||
const createComponent = ({ expanded = true } = {}) =>
|
||||
shallowMount(IssuableBulkEditSidebar, {
|
||||
propsData: {
|
||||
expanded,
|
||||
},
|
||||
slots: {
|
||||
'bulk-edit-actions': `
|
||||
<button class="js-edit-issuables">Edit issuables</button>
|
||||
`,
|
||||
'sidebar-items': `
|
||||
<button class="js-sidebar-dropdown">Labels</button>
|
||||
`,
|
||||
},
|
||||
});
|
||||
|
||||
describe('IssuableBulkEditSidebar', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
setFixtures('<div class="layout-page right-sidebar-collapsed"></div>');
|
||||
wrapper = createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('watch', () => {
|
||||
describe('expanded', () => {
|
||||
it.each`
|
||||
expanded | layoutPageClass
|
||||
${true} | ${'right-sidebar-expanded'}
|
||||
${false} | ${'right-sidebar-collapsed'}
|
||||
`(
|
||||
'sets class "$layoutPageClass" on element `.layout-page` when expanded prop is $expanded',
|
||||
async ({ expanded, layoutPageClass }) => {
|
||||
const wrappeCustom = createComponent({
|
||||
expanded: !expanded,
|
||||
});
|
||||
|
||||
// We need to manually flip the value of `expanded` for
|
||||
// watcher to trigger.
|
||||
wrappeCustom.setProps({
|
||||
expanded,
|
||||
});
|
||||
|
||||
await wrappeCustom.vm.$nextTick();
|
||||
|
||||
expect(document.querySelector('.layout-page').classList.contains(layoutPageClass)).toBe(
|
||||
true,
|
||||
);
|
||||
|
||||
wrappeCustom.destroy();
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('template', () => {
|
||||
it.each`
|
||||
expanded | layoutPageClass
|
||||
${true} | ${'right-sidebar-expanded'}
|
||||
${false} | ${'right-sidebar-collapsed'}
|
||||
`(
|
||||
'renders component container with class "$layoutPageClass" when expanded prop is $expanded',
|
||||
async ({ expanded, layoutPageClass }) => {
|
||||
const wrappeCustom = createComponent({
|
||||
expanded: !expanded,
|
||||
});
|
||||
|
||||
// We need to manually flip the value of `expanded` for
|
||||
// watcher to trigger.
|
||||
wrappeCustom.setProps({
|
||||
expanded,
|
||||
});
|
||||
|
||||
await wrappeCustom.vm.$nextTick();
|
||||
|
||||
expect(wrappeCustom.classes()).toContain(layoutPageClass);
|
||||
|
||||
wrappeCustom.destroy();
|
||||
},
|
||||
);
|
||||
|
||||
it('renders contents for slot `bulk-edit-actions`', () => {
|
||||
expect(wrapper.find('button.js-edit-issuables').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders contents for slot `sidebar-items`', () => {
|
||||
expect(wrapper.find('button.js-sidebar-dropdown').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,5 +1,5 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { GlLink, GlLabel } from '@gitlab/ui';
|
||||
import { GlLink, GlLabel, GlFormCheckbox } from '@gitlab/ui';
|
||||
|
||||
import IssuableItem from '~/issuable_list/components/issuable_item.vue';
|
||||
import IssuableAssignees from '~/vue_shared/components/issue/issue_assignees.vue';
|
||||
|
@ -12,6 +12,7 @@ const createComponent = ({ issuableSymbol = '#', issuable = mockIssuable, slots
|
|||
issuableSymbol,
|
||||
issuable,
|
||||
enableLabelPermalinks: true,
|
||||
showCheckbox: false,
|
||||
},
|
||||
slots,
|
||||
});
|
||||
|
@ -196,6 +197,25 @@ describe('IssuableItem', () => {
|
|||
expect(titleEl.find(GlLink).text()).toBe(mockIssuable.title);
|
||||
});
|
||||
|
||||
it('renders checkbox when `showCheckbox` prop is true', async () => {
|
||||
wrapper.setProps({
|
||||
showCheckbox: true,
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.find(GlFormCheckbox).exists()).toBe(true);
|
||||
expect(wrapper.find(GlFormCheckbox).attributes('checked')).not.toBeDefined();
|
||||
|
||||
wrapper.setProps({
|
||||
checked: true,
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.find(GlFormCheckbox).attributes('checked')).toBe('true');
|
||||
});
|
||||
|
||||
it('renders issuable title with `target` set as "_blank" when issuable.webUrl is external', async () => {
|
||||
wrapper.setProps({
|
||||
issuable: {
|
||||
|
|
|
@ -8,11 +8,14 @@ import IssuableTabs from '~/issuable_list/components/issuable_tabs.vue';
|
|||
import IssuableItem from '~/issuable_list/components/issuable_item.vue';
|
||||
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
|
||||
|
||||
import { mockIssuableListProps } from '../mock_data';
|
||||
import { mockIssuableListProps, mockIssuables } from '../mock_data';
|
||||
|
||||
const createComponent = (propsData = mockIssuableListProps) =>
|
||||
const createComponent = ({ props = mockIssuableListProps, data = {} } = {}) =>
|
||||
mount(IssuableListRoot, {
|
||||
propsData,
|
||||
propsData: props,
|
||||
data() {
|
||||
return data;
|
||||
},
|
||||
slots: {
|
||||
'nav-actions': `
|
||||
<button class="js-new-issuable">New issuable</button>
|
||||
|
@ -35,6 +38,14 @@ describe('IssuableListRoot', () => {
|
|||
});
|
||||
|
||||
describe('computed', () => {
|
||||
const mockCheckedIssuables = {
|
||||
[mockIssuables[0].iid]: { checked: true, issuable: mockIssuables[0] },
|
||||
[mockIssuables[1].iid]: { checked: true, issuable: mockIssuables[1] },
|
||||
[mockIssuables[2].iid]: { checked: true, issuable: mockIssuables[2] },
|
||||
};
|
||||
|
||||
const mIssuables = [mockIssuables[0], mockIssuables[1], mockIssuables[2]];
|
||||
|
||||
describe('skeletonItemCount', () => {
|
||||
it.each`
|
||||
totalItems | defaultPageSize | currentPage | returnValue
|
||||
|
@ -57,9 +68,62 @@ describe('IssuableListRoot', () => {
|
|||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('allIssuablesChecked', () => {
|
||||
it.each`
|
||||
checkedIssuables | issuables | specTitle | returnValue
|
||||
${mockCheckedIssuables} | ${mIssuables} | ${'same as'} | ${true}
|
||||
${{}} | ${mIssuables} | ${'not same as'} | ${false}
|
||||
`(
|
||||
'returns $returnValue when bulkEditIssuables count is $specTitle issuables count',
|
||||
async ({ checkedIssuables, issuables, returnValue }) => {
|
||||
wrapper.setProps({
|
||||
issuables,
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
wrapper.setData({
|
||||
checkedIssuables,
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.vm.allIssuablesChecked).toBe(returnValue);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('bulkEditIssuables', () => {
|
||||
it('returns array of issuables which have `checked` set to true within checkedIssuables map', async () => {
|
||||
wrapper.setData({
|
||||
checkedIssuables: mockCheckedIssuables,
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.vm.bulkEditIssuables).toHaveLength(mIssuables.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('watch', () => {
|
||||
describe('issuables', () => {
|
||||
it('populates `checkedIssuables` prop with all issuables', async () => {
|
||||
wrapper.setProps({
|
||||
issuables: [mockIssuables[0]],
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(Object.keys(wrapper.vm.checkedIssuables)).toHaveLength(1);
|
||||
expect(wrapper.vm.checkedIssuables[mockIssuables[0].iid]).toEqual({
|
||||
checked: false,
|
||||
issuable: mockIssuables[0],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('urlParams', () => {
|
||||
it('updates window URL reflecting props within `urlParams`', async () => {
|
||||
const urlParams = {
|
||||
|
@ -82,6 +146,30 @@ describe('IssuableListRoot', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('methods', () => {
|
||||
describe('issuableId', () => {
|
||||
it('returns id value from provided issuable object', () => {
|
||||
expect(wrapper.vm.issuableId({ id: 1 })).toBe(1);
|
||||
expect(wrapper.vm.issuableId({ iid: 1 })).toBe(1);
|
||||
expect(wrapper.vm.issuableId({})).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('issuableChecked', () => {
|
||||
it('returns boolean value representing checked status of issuable item', async () => {
|
||||
wrapper.setData({
|
||||
checkedIssuables: {
|
||||
[mockIssuables[0].iid]: { checked: true, issuable: mockIssuables[0] },
|
||||
},
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.vm.issuableChecked(mockIssuables[0])).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('template', () => {
|
||||
it('renders component container element with class "issuable-list-container"', () => {
|
||||
expect(wrapper.classes()).toContain('issuable-list-container');
|
||||
|
@ -183,12 +271,44 @@ describe('IssuableListRoot', () => {
|
|||
});
|
||||
|
||||
describe('events', () => {
|
||||
let wrapperChecked;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapperChecked = createComponent({
|
||||
data: {
|
||||
checkedIssuables: {
|
||||
[mockIssuables[0].iid]: { checked: true, issuable: mockIssuables[0] },
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapperChecked.destroy();
|
||||
});
|
||||
|
||||
it('issuable-tabs component emits `click-tab` event on `click-tab` event', () => {
|
||||
wrapper.find(IssuableTabs).vm.$emit('click');
|
||||
|
||||
expect(wrapper.emitted('click-tab')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('sets all issuables as checked when filtered-search-bar component emits `checked-input` event', async () => {
|
||||
const searchEl = wrapperChecked.find(FilteredSearchBar);
|
||||
|
||||
searchEl.vm.$emit('checked-input', true);
|
||||
|
||||
await wrapperChecked.vm.$nextTick();
|
||||
|
||||
expect(searchEl.emitted('checked-input')).toBeTruthy();
|
||||
expect(searchEl.emitted('checked-input').length).toBe(1);
|
||||
|
||||
expect(wrapperChecked.vm.checkedIssuables[mockIssuables[0].iid]).toEqual({
|
||||
checked: true,
|
||||
issuable: mockIssuables[0],
|
||||
});
|
||||
});
|
||||
|
||||
it('filtered-search-bar component emits `filter` event on `onFilter` & `sort` event on `onSort` events', () => {
|
||||
const searchEl = wrapper.find(FilteredSearchBar);
|
||||
|
||||
|
@ -198,6 +318,22 @@ describe('IssuableListRoot', () => {
|
|||
expect(wrapper.emitted('sort')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('sets an issuable as checked when issuable-item component emits `checked-input` event', async () => {
|
||||
const issuableItem = wrapperChecked.findAll(IssuableItem).at(0);
|
||||
|
||||
issuableItem.vm.$emit('checked-input', true);
|
||||
|
||||
await wrapperChecked.vm.$nextTick();
|
||||
|
||||
expect(issuableItem.emitted('checked-input')).toBeTruthy();
|
||||
expect(issuableItem.emitted('checked-input').length).toBe(1);
|
||||
|
||||
expect(wrapperChecked.vm.checkedIssuables[mockIssuables[0].iid]).toEqual({
|
||||
checked: true,
|
||||
issuable: mockIssuables[0],
|
||||
});
|
||||
});
|
||||
|
||||
it('gl-pagination component emits `page-change` event on `input` event', async () => {
|
||||
wrapper.setProps({
|
||||
showPaginationControls: true,
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
import { shallowMount, mount } from '@vue/test-utils';
|
||||
import { GlFilteredSearch, GlButtonGroup, GlButton, GlDropdown, GlDropdownItem } from '@gitlab/ui';
|
||||
import {
|
||||
GlFilteredSearch,
|
||||
GlButtonGroup,
|
||||
GlButton,
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlFormCheckbox,
|
||||
} from '@gitlab/ui';
|
||||
|
||||
import FilteredSearchBarRoot from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
|
||||
import { uniqueTokens } from '~/vue_shared/components/filtered_search_bar/filtered_search_utils';
|
||||
|
@ -30,6 +37,8 @@ const createComponent = ({
|
|||
recentSearchesStorageKey = 'requirements',
|
||||
tokens = mockAvailableTokens,
|
||||
sortOptions,
|
||||
showCheckbox = false,
|
||||
checkboxChecked = false,
|
||||
searchInputPlaceholder = 'Filter requirements',
|
||||
} = {}) => {
|
||||
const mountMethod = shallow ? shallowMount : mount;
|
||||
|
@ -40,6 +49,8 @@ const createComponent = ({
|
|||
recentSearchesStorageKey,
|
||||
tokens,
|
||||
sortOptions,
|
||||
showCheckbox,
|
||||
checkboxChecked,
|
||||
searchInputPlaceholder,
|
||||
},
|
||||
});
|
||||
|
@ -364,6 +375,26 @@ describe('FilteredSearchBarRoot', () => {
|
|||
expect(glFilteredSearchEl.props('historyItems')).toEqual(mockHistoryItems);
|
||||
});
|
||||
|
||||
it('renders checkbox when `showCheckbox` prop is true', async () => {
|
||||
let wrapperWithCheckbox = createComponent({
|
||||
showCheckbox: true,
|
||||
});
|
||||
|
||||
expect(wrapperWithCheckbox.find(GlFormCheckbox).exists()).toBe(true);
|
||||
expect(wrapperWithCheckbox.find(GlFormCheckbox).attributes('checked')).not.toBeDefined();
|
||||
|
||||
wrapperWithCheckbox.destroy();
|
||||
|
||||
wrapperWithCheckbox = createComponent({
|
||||
showCheckbox: true,
|
||||
checkboxChecked: true,
|
||||
});
|
||||
|
||||
expect(wrapperWithCheckbox.find(GlFormCheckbox).attributes('checked')).toBe('true');
|
||||
|
||||
wrapperWithCheckbox.destroy();
|
||||
});
|
||||
|
||||
it('renders search history items dropdown with formatting done using token symbols', async () => {
|
||||
const wrapperFullMount = createComponent({ sortOptions: mockSortOptions, shallow: false });
|
||||
wrapperFullMount.vm.recentSearchesStore.addRecentSearch(mockHistoryItems[0]);
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Analytics::DevopsAdoption::Segment, type: :model do
|
||||
subject { build(:devops_adoption_segment) }
|
||||
|
||||
describe 'validation' do
|
||||
it { is_expected.to validate_presence_of(:name) }
|
||||
it { is_expected.to validate_uniqueness_of(:name) }
|
||||
it { is_expected.to validate_length_of(:name).is_at_most(255) }
|
||||
|
||||
context 'limit the number of segments' do
|
||||
subject { build(:devops_adoption_segment) }
|
||||
|
||||
before do
|
||||
create_list(:devops_adoption_segment, 2)
|
||||
|
||||
stub_const("#{described_class}::ALLOWED_SEGMENT_COUNT", 2)
|
||||
end
|
||||
|
||||
it 'shows validation error' do
|
||||
subject.validate
|
||||
|
||||
expect(subject.errors[:name]).to eq([s_('DevopsAdoptionSegment|The maximum number of segments has been reached')])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue