Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-09-14 18:09:48 +00:00
parent a483e9271b
commit a8caa9ce49
79 changed files with 864 additions and 348 deletions

View File

@ -2,14 +2,6 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 13.3.6 (2020-09-14)
### Fixed (2 changes)
- Fixes Auto DevOps deploy script for multiple additional hosts separated by comma and space. !41404
- Use 'read' method to get request body in Conan to fix uploads when using Unicorn. !41801
## 13.3.4 (2020-09-02)
### Security (1 change)

View File

@ -1,5 +1,5 @@
<script>
import { GlDropdown, GlNewDropdownItem, GlSprintf } from '@gitlab/ui';
import { GlDropdown, GlDropdownItem, GlSprintf } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
import allVersionsMixin from '../../mixins/all_versions';
import { findVersionId } from '../../utils/design_management_utils';
@ -7,7 +7,7 @@ import { findVersionId } from '../../utils/design_management_utils';
export default {
components: {
GlDropdown,
GlNewDropdownItem,
GlDropdownItem,
GlSprintf,
},
mixins: [allVersionsMixin],
@ -64,7 +64,7 @@ export default {
<template>
<gl-dropdown :text="dropdownText" size="small">
<gl-new-dropdown-item
<gl-dropdown-item
v-for="(version, index) in allVersions"
:key="version.id"
:is-check-item="true"
@ -76,6 +76,6 @@ export default {
{{ allVersions.length - index }}
</template>
</gl-sprintf>
</gl-new-dropdown-item>
</gl-dropdown-item>
</gl-dropdown>
</template>

View File

@ -7,7 +7,7 @@ import {
GlFormGroup,
GlFormCheckbox,
GlDropdown,
GlNewDropdownItem,
GlDropdownItem,
} from '@gitlab/ui';
import {
I18N_ALERT_SETTINGS_FORM,
@ -25,7 +25,7 @@ export default {
GlIcon,
GlFormCheckbox,
GlDropdown,
GlNewDropdownItem,
GlDropdownItem,
},
inject: ['service', 'alertSettings'],
data() {
@ -107,7 +107,7 @@ export default {
:text="issueTemplateHeader"
:block="true"
>
<gl-new-dropdown-item
<gl-dropdown-item
v-for="template in templates"
:key="template.key"
data-qa-selector="incident_templates_item"
@ -116,7 +116,7 @@ export default {
@click="selectIssueTemplate(template.key)"
>
{{ template.name }}
</gl-new-dropdown-item>
</gl-dropdown-item>
</gl-dropdown>
</gl-form-group>

View File

@ -1,6 +1,6 @@
<script>
import { mapState } from 'vuex';
import { GlDropdown, GlNewDropdownItem, GlLink } from '@gitlab/ui';
import { GlDropdown, GlDropdownItem, GlLink } from '@gitlab/ui';
import { s__ } from '~/locale';
import { defaultIntegrationLevel, overrideDropdownDescriptions } from '../constants';
@ -20,7 +20,7 @@ export default {
name: 'OverrideDropdown',
components: {
GlDropdown,
GlNewDropdownItem,
GlDropdownItem,
GlLink,
},
props: {
@ -74,13 +74,13 @@ export default {
</span>
<input name="service[inherit_from_id]" :value="override ? '' : inheritFromId" type="hidden" />
<gl-dropdown :text="selected.text">
<gl-new-dropdown-item
<gl-dropdown-item
v-for="option in $options.dropdownOptions"
:key="option.value"
@click="onClick(option)"
>
{{ option.text }}
</gl-new-dropdown-item>
</gl-dropdown-item>
</gl-dropdown>
</div>
</template>

View File

@ -3,7 +3,7 @@ import {
GlAlert,
GlButton,
GlDropdown,
GlNewDropdownItem,
GlDropdownItem,
GlDropdownText,
GlFormGroup,
GlFormSelect,
@ -35,7 +35,7 @@ export default {
GlAlert,
GlButton,
GlDropdown,
GlNewDropdownItem,
GlDropdownItem,
GlDropdownText,
GlFormGroup,
GlFormSelect,
@ -305,14 +305,14 @@ export default {
<gl-loading-icon v-if="isFetching" />
<gl-new-dropdown-item
<gl-dropdown-item
v-for="user in users"
v-else
:key="user.id"
@click="updateMapping(data.item.jiraAccountId, user.id, user.username)"
>
{{ user.username }} ({{ user.name }})
</gl-new-dropdown-item>
</gl-dropdown-item>
<gl-dropdown-text v-show="shouldShowNoMatchesFoundText" class="text-secondary">
{{ __('No matches found') }}

View File

@ -1,9 +1,9 @@
<script>
import {
GlDropdown,
GlNewDropdownDivider,
GlDropdownDivider,
GlNewDropdownHeader,
GlNewDropdownItem,
GlDropdownItem,
GlLoadingIcon,
GlSearchBoxByType,
GlIcon,
@ -18,9 +18,9 @@ const SEARCH_DEBOUNCE_MS = 250;
export default {
components: {
GlDropdown,
GlNewDropdownDivider,
GlDropdownDivider,
GlNewDropdownHeader,
GlNewDropdownItem,
GlDropdownItem,
GlLoadingIcon,
GlSearchBoxByType,
GlIcon,
@ -200,7 +200,7 @@ export default {
<span class="text-center d-block">{{ $options.translations.selectMilestone }}</span>
</gl-new-dropdown-header>
<gl-new-dropdown-divider />
<gl-dropdown-divider />
<gl-search-box-by-type
ref="searchBox"
@ -211,26 +211,26 @@ export default {
@keydown.enter.prevent="onSearchBoxEnter"
/>
<gl-new-dropdown-item @click="onMilestoneClicked(null)">
<gl-dropdown-item @click="onMilestoneClicked(null)">
<span :class="{ 'pl-4': true, 'selected-item': selectedMilestones.length === 0 }">
{{ $options.translations.noMilestone }}
</span>
</gl-new-dropdown-item>
</gl-dropdown-item>
<gl-new-dropdown-divider />
<gl-dropdown-divider />
<template v-if="isLoading">
<gl-loading-icon />
<gl-new-dropdown-divider />
<gl-dropdown-divider />
</template>
<template v-else-if="noResults">
<div class="dropdown-item-space">
<span ref="noResults" class="pl-4">{{ $options.translations.noResultsLabel }}</span>
</div>
<gl-new-dropdown-divider />
<gl-dropdown-divider />
</template>
<template v-else-if="dropdownItems.length">
<gl-new-dropdown-item
<gl-dropdown-item
v-for="item in dropdownItems"
:key="item"
role="milestone option"
@ -239,12 +239,12 @@ export default {
<span :class="{ 'pl-4': true, 'selected-item': isSelectedMilestone(item) }">
{{ item }}
</span>
</gl-new-dropdown-item>
<gl-new-dropdown-divider />
</gl-dropdown-item>
<gl-dropdown-divider />
</template>
<gl-new-dropdown-item v-for="(item, idx) in extraLinks" :key="idx" :href="item.url">
<gl-dropdown-item v-for="(item, idx) in extraLinks" :key="idx" :href="item.url">
<span class="pl-4">{{ item.text }}</span>
</gl-new-dropdown-item>
</gl-dropdown-item>
</gl-dropdown>
</template>

View File

@ -8,7 +8,7 @@ import {
GlFormGroup,
GlFormInput,
GlDropdown,
GlNewDropdownItem as GlDropdownItem,
GlDropdownItem,
GlModal,
GlTooltipDirective,
GlIcon,

View File

@ -3,8 +3,8 @@ import { mapState, mapGetters, mapActions } from 'vuex';
import {
GlDeprecatedButton,
GlDropdown,
GlNewDropdownDivider,
GlNewDropdownItem,
GlDropdownDivider,
GlDropdownItem,
GlModal,
GlIcon,
GlModalDirective,
@ -24,8 +24,8 @@ export default {
components: {
GlDeprecatedButton,
GlDropdown,
GlNewDropdownDivider,
GlNewDropdownItem,
GlDropdownDivider,
GlDropdownItem,
GlModal,
GlIcon,
DuplicateDashboardModal,
@ -157,13 +157,13 @@ export default {
</template>
<template v-if="addingMetricsAvailable">
<gl-new-dropdown-item
<gl-dropdown-item
v-gl-modal="$options.modalIds.addMetric"
data-qa-selector="add_metric_button"
data-testid="add-metric-item"
>
{{ $options.i18n.addMetric }}
</gl-new-dropdown-item>
</gl-dropdown-item>
<gl-modal
ref="addMetricModal"
:modal-id="$options.modalIds.addMetric"
@ -194,20 +194,20 @@ export default {
</gl-modal>
</template>
<gl-new-dropdown-item
<gl-dropdown-item
v-if="isMenuItemEnabled.addPanel"
data-testid="add-panel-item-enabled"
:to="newPanelPageLocation"
>
{{ $options.i18n.addPanel }}
</gl-new-dropdown-item>
</gl-dropdown-item>
<!--
wrapper for tooltip as button can be `disabled`
https://bootstrap-vue.org/docs/components/tooltip#disabled-elements
-->
<div v-else v-gl-tooltip :title="$options.i18n.addPanelInfo">
<gl-new-dropdown-item
<gl-dropdown-item
:alt="$options.i18n.addPanelInfo"
:to="newPanelPageLocation"
data-testid="add-panel-item-disabled"
@ -215,24 +215,24 @@ export default {
class="gl-cursor-not-allowed"
>
<span class="gl-text-gray-400">{{ $options.i18n.addPanel }}</span>
</gl-new-dropdown-item>
</gl-dropdown-item>
</div>
<gl-new-dropdown-item
<gl-dropdown-item
v-if="isMenuItemEnabled.editDashboard"
:href="selectedDashboard ? selectedDashboard.project_blob_path : null"
data-qa-selector="edit_dashboard_button_enabled"
data-testid="edit-dashboard-item-enabled"
>
{{ $options.i18n.editDashboard }}
</gl-new-dropdown-item>
</gl-dropdown-item>
<!--
wrapper for tooltip as button can be `disabled`
https://bootstrap-vue.org/docs/components/tooltip#disabled-elements
-->
<div v-else v-gl-tooltip :title="$options.i18n.editDashboardInfo">
<gl-new-dropdown-item
<gl-dropdown-item
:alt="$options.i18n.editDashboardInfo"
:href="selectedDashboard ? selectedDashboard.project_blob_path : null"
data-testid="edit-dashboard-item-disabled"
@ -240,16 +240,16 @@ export default {
class="gl-cursor-not-allowed"
>
<span class="gl-text-gray-400">{{ $options.i18n.editDashboard }}</span>
</gl-new-dropdown-item>
</gl-dropdown-item>
</div>
<template v-if="isMenuItemShown.duplicateDashboard">
<gl-new-dropdown-item
<gl-dropdown-item
v-gl-modal="$options.modalIds.duplicateDashboard"
data-testid="duplicate-dashboard-item"
>
{{ $options.i18n.duplicateDashboard }}
</gl-new-dropdown-item>
</gl-dropdown-item>
<duplicate-dashboard-modal
:default-branch="defaultBranch"
@ -259,25 +259,25 @@ export default {
/>
</template>
<gl-new-dropdown-item
<gl-dropdown-item
v-if="selectedDashboard"
data-testid="star-dashboard-item"
:disabled="isUpdatingStarredValue"
@click="toggleStarredValue()"
>
{{ selectedDashboard.starred ? $options.i18n.unstarDashboard : $options.i18n.starDashboard }}
</gl-new-dropdown-item>
</gl-dropdown-item>
<gl-new-dropdown-divider />
<gl-dropdown-divider />
<gl-new-dropdown-item
<gl-dropdown-item
v-gl-modal="$options.modalIds.createDashboard"
data-testid="create-dashboard-item"
:disabled="!isMenuItemEnabled.createDashboard"
:class="{ 'monitoring-actions-item-disabled': !isMenuItemEnabled.createDashboard }"
>
{{ $options.i18n.createDashboard }}
</gl-new-dropdown-item>
</gl-dropdown-item>
<template v-if="isMenuItemEnabled.createDashboard">
<create-dashboard-modal

View File

@ -5,7 +5,7 @@ import {
GlButton,
GlDropdown,
GlLoadingIcon,
GlNewDropdownItem,
GlDropdownItem,
GlNewDropdownHeader,
GlSearchBoxByType,
GlModalDirective,
@ -30,7 +30,7 @@ export default {
GlButton,
GlDropdown,
GlLoadingIcon,
GlNewDropdownItem,
GlDropdownItem,
GlNewDropdownHeader,
GlSearchBoxByType,
@ -196,7 +196,7 @@ export default {
<gl-loading-icon v-if="environmentsLoading" :inline="true" />
<div v-else class="flex-fill overflow-auto">
<gl-new-dropdown-item
<gl-dropdown-item
v-for="environment in filteredEnvironments"
:key="environment.id"
:is-check-item="true"
@ -204,7 +204,7 @@ export default {
:href="getEnvironmentPath(environment.id)"
>
{{ environment.name }}
</gl-new-dropdown-item>
</gl-dropdown-item>
</div>
<div
v-show="shouldShowEnvironmentsDropdownNoMatchedMsg"

View File

@ -7,8 +7,8 @@ import {
GlLink,
GlLoadingIcon,
GlDropdown,
GlNewDropdownItem as GlDropdownItem,
GlNewDropdownDivider as GlDropdownDivider,
GlDropdownItem,
GlDropdownDivider,
GlModal,
GlModalDirective,
GlSprintf,

View File

@ -3,9 +3,9 @@ import { mapState, mapGetters } from 'vuex';
import {
GlIcon,
GlDropdown,
GlNewDropdownItem,
GlDropdownItem,
GlNewDropdownHeader,
GlNewDropdownDivider,
GlDropdownDivider,
GlSearchBoxByType,
GlModalDirective,
} from '@gitlab/ui';
@ -18,9 +18,9 @@ export default {
components: {
GlIcon,
GlDropdown,
GlNewDropdownItem,
GlDropdownItem,
GlNewDropdownHeader,
GlNewDropdownDivider,
GlDropdownDivider,
GlSearchBoxByType,
},
directives: {
@ -87,7 +87,7 @@ export default {
/>
<div class="flex-fill overflow-auto">
<gl-new-dropdown-item
<gl-dropdown-item
v-for="dashboard in starredDashboards"
:key="dashboard.path"
:is-check-item="true"
@ -100,13 +100,13 @@ export default {
</span>
<gl-icon class="text-muted gl-flex-shrink-0 gl-ml-3 gl-align-self-center" name="star" />
</div>
</gl-new-dropdown-item>
<gl-new-dropdown-divider
</gl-dropdown-item>
<gl-dropdown-divider
v-if="starredDashboards.length && nonStarredDashboards.length"
ref="starredListDivider"
/>
<gl-new-dropdown-item
<gl-dropdown-item
v-for="dashboard in nonStarredDashboards"
:key="dashboard.path"
:is-check-item="true"
@ -116,7 +116,7 @@ export default {
<span class="gl-overflow-hidden gl-overflow-wrap-break">
{{ dashboardDisplayName(dashboard) }}
</span>
</gl-new-dropdown-item>
</gl-dropdown-item>
</div>
<div

View File

@ -5,8 +5,8 @@ import {
GlButtonGroup,
GlButton,
GlDropdown,
GlNewDropdownItem,
GlNewDropdownDivider,
GlDropdownItem,
GlDropdownDivider,
GlTooltipDirective,
} from '@gitlab/ui';
import { n__, __ } from '~/locale';
@ -49,8 +49,8 @@ export default {
GlButtonGroup,
GlButton,
GlDropdown,
GlNewDropdownItem,
GlNewDropdownDivider,
GlDropdownItem,
GlDropdownDivider,
},
directives: {
GlTooltip: GlTooltipDirective,
@ -158,20 +158,20 @@ export default {
:title="s__('Metrics|Set refresh rate')"
:text="dropdownText"
>
<gl-new-dropdown-item
<gl-dropdown-item
:is-check-item="true"
:is-checked="refreshInterval === null"
@click="removeRefreshInterval()"
>{{ __('Off') }}</gl-new-dropdown-item
>{{ __('Off') }}</gl-dropdown-item
>
<gl-new-dropdown-divider />
<gl-new-dropdown-item
<gl-dropdown-divider />
<gl-dropdown-item
v-for="(option, i) in $options.refreshIntervals"
:key="i"
:is-check-item="true"
:is-checked="isChecked(option)"
@click="setRefreshInterval(option)"
>{{ option.label }}</gl-new-dropdown-item
>{{ option.label }}</gl-dropdown-item
>
</gl-dropdown>
</gl-button-group>

View File

@ -10,7 +10,7 @@ import {
GlFormSelect,
GlLink,
GlDropdown,
GlNewDropdownItem,
GlDropdownItem,
GlSearchBoxByType,
GlSprintf,
} from '@gitlab/ui';
@ -38,7 +38,7 @@ export default {
GlFormSelect,
GlLink,
GlDropdown,
GlNewDropdownItem,
GlDropdownItem,
GlSearchBoxByType,
GlSprintf,
},
@ -179,7 +179,7 @@ export default {
:placeholder="__('Search branches and tags')"
class="gl-p-2"
/>
<gl-new-dropdown-item
<gl-dropdown-item
v-for="(ref, index) in filteredRefs"
:key="index"
class="gl-font-monospace"
@ -188,7 +188,7 @@ export default {
@click="setRefSelected(ref)"
>
{{ ref }}
</gl-new-dropdown-item>
</gl-dropdown-item>
</gl-dropdown>
<template #description>

View File

@ -4,9 +4,9 @@ import { mapState, mapActions } from 'vuex';
import {
GlDropdown,
GlNewDropdownHeader,
GlNewDropdownItem,
GlDropdownItem,
GlSearchBoxByType,
GlNewDropdownDivider,
GlDropdownDivider,
GlTooltipDirective,
} from '@gitlab/ui';
import { redirectTo } from '~/lib/utils/url_utility';
@ -20,9 +20,9 @@ export default {
components: {
GlDropdown,
GlNewDropdownHeader,
GlNewDropdownItem,
GlDropdownItem,
GlSearchBoxByType,
GlNewDropdownDivider,
GlDropdownDivider,
},
directives: {
GlTooltip: GlTooltipDirective,
@ -116,18 +116,18 @@ export default {
<gl-new-dropdown-header>
{{ __('Search by author') }}
</gl-new-dropdown-header>
<gl-new-dropdown-divider />
<gl-dropdown-divider />
<gl-search-box-by-type
v-model.trim="authorInput"
class="gl-m-3"
:placeholder="__('Search')"
@input="searchAuthors"
/>
<gl-new-dropdown-item :is-checked="!currentAuthor" @click="selectAuthor(null)">
<gl-dropdown-item :is-checked="!currentAuthor" @click="selectAuthor(null)">
{{ __('Any Author') }}
</gl-new-dropdown-item>
<gl-new-dropdown-divider />
<gl-new-dropdown-item
</gl-dropdown-item>
<gl-dropdown-divider />
<gl-dropdown-item
v-for="author in commitsAuthors"
:key="author.id"
:is-checked="author.name === currentAuthor"
@ -136,7 +136,7 @@ export default {
@click="selectAuthor(author)"
>
{{ author.name }}
</gl-new-dropdown-item>
</gl-dropdown-item>
</gl-dropdown>
</div>
</template>

View File

@ -1,12 +1,12 @@
<script>
import { GlNewDropdownHeader, GlNewDropdownItem, GlBadge, GlIcon } from '@gitlab/ui';
import { GlNewDropdownHeader, GlDropdownItem, GlBadge, GlIcon } from '@gitlab/ui';
import { s__ } from '~/locale';
export default {
name: 'RefResultsSection',
components: {
GlNewDropdownHeader,
GlNewDropdownItem,
GlDropdownItem,
GlBadge,
GlIcon,
},
@ -97,7 +97,7 @@ export default {
</div>
</template>
<template v-else>
<gl-new-dropdown-item
<gl-dropdown-item
v-for="item in items"
:key="item.name"
@click="$emit('selected', item.value || item.name)"
@ -118,7 +118,7 @@ export default {
s__('DefaultBranchLabel|default')
}}</gl-badge>
</div>
</gl-new-dropdown-item>
</gl-dropdown-item>
</template>
</div>
</template>

View File

@ -2,7 +2,7 @@
import { mapActions, mapGetters, mapState } from 'vuex';
import {
GlDropdown,
GlNewDropdownDivider,
GlDropdownDivider,
GlNewDropdownHeader,
GlSearchBoxByType,
GlSprintf,
@ -19,7 +19,7 @@ export default {
store: createStore(),
components: {
GlDropdown,
GlNewDropdownDivider,
GlDropdownDivider,
GlNewDropdownHeader,
GlSearchBoxByType,
GlSprintf,
@ -134,7 +134,7 @@ export default {
<span class="gl-text-center gl-display-block">{{ i18n.dropdownHeader }}</span>
</gl-new-dropdown-header>
<gl-new-dropdown-divider />
<gl-dropdown-divider />
<gl-search-box-by-type
ref="searchBox"
@ -175,7 +175,7 @@ export default {
@selected="selectRef($event)"
/>
<gl-new-dropdown-divider v-if="showTagsSection || showCommitsSection" />
<gl-dropdown-divider v-if="showTagsSection || showCommitsSection" />
</template>
<template v-if="showTagsSection">
@ -190,7 +190,7 @@ export default {
@selected="selectRef($event)"
/>
<gl-new-dropdown-divider v-if="showCommitsSection" />
<gl-dropdown-divider v-if="showCommitsSection" />
</template>
<template v-if="showCommitsSection">

View File

@ -6,7 +6,7 @@ import {
GlButton,
GlDropdown,
GlNewDropdownHeader as GlDropdownHeader,
GlNewDropdownItem as GlDropdownItem,
GlDropdownItem,
GlTooltipDirective,
} from '@gitlab/ui';
import { n__, s__, sprintf } from '~/locale';

View File

@ -1,11 +1,5 @@
<script>
import {
GlIcon,
GlButton,
GlDeprecatedDropdown,
GlDeprecatedDropdownItem,
GlFormGroup,
} from '@gitlab/ui';
import { GlIcon, GlButton, GlDropdown, GlDropdownItem, GlFormGroup } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
import { convertToFixedRange, isEqualTimeRanges, findTimeRange } from '~/lib/utils/datetime_range';
@ -29,8 +23,8 @@ export default {
components: {
GlIcon,
GlButton,
GlDeprecatedDropdown,
GlDeprecatedDropdownItem,
GlDropdown,
GlDropdownItem,
GlFormGroup,
TooltipOnTruncate,
DateTimePickerInput,
@ -212,7 +206,7 @@ export default {
placement="top"
class="d-inline-block"
>
<gl-deprecated-dropdown
<gl-dropdown
ref="dropdown"
:text="timeWindowText"
v-bind="$attrs"
@ -269,7 +263,7 @@ export default {
<span class="gl-pl-7">{{ __('Quick range') }}</span>
</template>
<gl-deprecated-dropdown-item
<gl-dropdown-item
v-for="(option, index) in options"
:key="index"
data-qa-selector="quick_range_item"
@ -283,9 +277,9 @@ export default {
:class="{ invisible: !isOptionActive(option) }"
/>
{{ option.label }}
</gl-deprecated-dropdown-item>
</gl-dropdown-item>
</gl-form-group>
</div>
</gl-deprecated-dropdown>
</gl-dropdown>
</tooltip-on-truncate>
</template>

View File

@ -128,7 +128,7 @@ export default {
this.focusedIndex = 0;
}
Mousetrap.bind(['t', 'command+p', 'ctrl+p'], e => {
Mousetrap.bind(['t', 'mod+p'], e => {
if (e.preventDefault) {
e.preventDefault();
}
@ -142,7 +142,7 @@ export default {
el.classList.contains('inputarea')
) {
return true;
} else if (combo === 'command+p' || combo === 'ctrl+p') {
} else if (combo === 'mod+p') {
return false;
}

View File

@ -4,7 +4,7 @@ import {
GlButtonGroup,
GlButton,
GlDropdown,
GlNewDropdownItem as GlDropdownItem,
GlDropdownItem,
GlTooltipDirective,
} from '@gitlab/ui';

View File

@ -3,7 +3,7 @@ import {
GlToken,
GlFilteredSearchToken,
GlFilteredSearchSuggestion,
GlNewDropdownDivider as GlDropdownDivider,
GlDropdownDivider,
GlLoadingIcon,
} from '@gitlab/ui';
import { debounce } from 'lodash';

View File

@ -2,7 +2,7 @@
import {
GlFilteredSearchToken,
GlFilteredSearchSuggestion,
GlNewDropdownDivider as GlDropdownDivider,
GlDropdownDivider,
GlLoadingIcon,
} from '@gitlab/ui';
import { debounce } from 'lodash';

View File

@ -28,6 +28,8 @@ module Types
description: 'Timestamp of when the release was created'
field :released_at, Types::TimeType, null: true,
description: 'Timestamp of when the release was released'
field :upcoming_release, GraphQL::BOOLEAN_TYPE, null: true, method: :upcoming_release?,
description: 'Indicates the release is an upcoming release'
field :assets, Types::ReleaseAssetsType, null: true, method: :itself,
description: 'Assets of the release'
field :links, Types::ReleaseLinksType, null: true, method: :itself,

View File

@ -41,7 +41,8 @@ module AlertManagement
project,
user,
title: alert_presenter.title,
description: alert_presenter.issue_description
description: alert_presenter.issue_description,
severity: alert.severity
).execute
end

View File

@ -5,11 +5,12 @@ module IncidentManagement
class CreateService < BaseService
ISSUE_TYPE = 'incident'
def initialize(project, current_user, title:, description:)
def initialize(project, current_user, title:, description:, severity: IssuableSeverity::DEFAULT)
super(project, current_user)
@title = title
@description = description
@severity = severity
end
def execute
@ -23,12 +24,14 @@ module IncidentManagement
return error(issue.errors.full_messages.to_sentence, issue) unless issue.valid?
issue.update_severity(severity)
success(issue)
end
private
attr_reader :title, :description
attr_reader :title, :description, :severity
def success(issue)
ServiceResponse.success(payload: { issue: issue })

View File

@ -34,10 +34,12 @@
%p.validation-pending.gl-field-error-ignore.field-validation.hide= _('Checking group URL availability...')
- if @group.persisted?
.alert.alert-warning.gl-mt-3
= _('Changing group URL can have unintended side effects.')
= succeed '.' do
= link_to _('Learn more'), help_page_path('user/group/index', anchor: 'changing-a-groups-path'), target: '_blank'
.gl-alert.gl-alert-warning.gl-mt-3.gl-mb-3
= sprite_icon('warning', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
.gl-alert-body
= _('Changing group URL can have unintended side effects.')
= succeed '.' do
= link_to _('Learn more'), help_page_path('user/group/index', anchor: 'changing-a-groups-path'), target: '_blank', class: 'gl-link'
- if @group.persisted?
.row

View File

@ -0,0 +1,5 @@
---
title: Add user mapping by username when importing projects for Bitbucket Server importer
merge_request: 36885
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Add the Query Apdex Prometheus metric to usage ping
merge_request: 39256
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Replace bootstrap alerts in app/views/shared/_group_form.html.haml
merge_request: 41348
author: Gilang Gumilar
type: changed

View File

@ -0,0 +1,5 @@
---
title: Set incident severity when it is created from an alert.
merge_request: 42072
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Add support for environment_url.txt to API Fuzzing
merge_request: 41523
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Use 'read' method to get request body in Conan to fix uploads when using Unicorn
merge_request: 41801
author:
type: fixed

View File

@ -0,0 +1,6 @@
---
title: Fixes Auto DevOps deploy script for multiple additional hosts separated by
comma and space
merge_request: 41404
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Add "upcomingRelease" field to GraphQL endpoint
merge_request: 41183
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Stop applying Ctrl+P shortcut on MR page on Mac
merge_request: 42240
author:
type: fixed

View File

@ -0,0 +1,7 @@
---
name: bitbucket_server_user_mapping_by_username
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36885
rollout_issue_url:
group: group::import
type: development
default_enabled: false

View File

@ -0,0 +1,7 @@
---
name: kubernetes_agent_internal_api
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41045
rollout_issue_url:
group: group::configure
type: ops
default_enabled: true

View File

@ -14358,6 +14358,11 @@ type Release {
Relative web path to the tag associated with the release
"""
tagPath: String
"""
Indicates the release is an upcoming release
"""
upcomingRelease: Boolean
}
"""

View File

@ -41832,6 +41832,20 @@
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "upcomingRelease",
"description": "Indicates the release is an upcoming release",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,

View File

@ -1959,6 +1959,7 @@ Represents a release
| `releasedAt` | Time | Timestamp of when the release was released |
| `tagName` | String | Name of the tag associated with the release |
| `tagPath` | String | Relative web path to the tag associated with the release |
| `upcomingRelease` | Boolean | Indicates the release is an upcoming release |
### ReleaseAssetLink

View File

@ -12,7 +12,7 @@ This tutorial demonstrates how to authenticate, configure, and read secrets with
NOTE: **Note:**
[GitLab Premium](https://about.gitlab.com/pricing/) supports read access to a
Hashicorp Vault, and enables you to
[use Vault secrets in a CI job](../../secrets/index.md#use-vault-secrets-in-a-ci-job-premium).
[use Vault secrets in a CI job](../../secrets/index.md#use-vault-secrets-in-a-ci-job).
To learn more, read [Using external secrets in CI](../../secrets/index.md).
## Requirements

View File

@ -27,7 +27,7 @@ the [JSON Web Token](https://gitlab.com/gitlab-org/gitlab/-/issues/207125) (`CI_
introduced in GitLab 12.10.
You must [configure your Vault server](#configure-your-vault-server) before you
can use [use Vault secrets in a CI job](#use-vault-secrets-in-a-ci-job-premium).
can use [use Vault secrets in a CI job](#use-vault-secrets-in-a-ci-job).
NOTE: **Note:**
Read the [Authenticating and Reading Secrets With Hashicorp Vault](../examples/authenticating-with-hashicorp-vault/index.md)
@ -104,7 +104,7 @@ The path to this file is stored in environment variable named `DATABASE_PASSWORD
similar to [CI variables of type `file`](../variables/README.md#custom-environment-variables-of-type-file).
For more information about the supported syntax, read the
[`.gitlab-ci.yml` reference](../yaml/README.md#secretsvault-premium).
[`.gitlab-ci.yml` reference](../yaml/README.md#secretsvault).
## Configure Vault server roles

View File

@ -371,7 +371,23 @@ Example response:
}
```
## Kubernetes agent information
## Kubernetes agent endpoints
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41045) in GitLab 13.4.
> - This feature is not deployed on GitLab.com
> - It's not recommended for production use.
The following endpoints are used by the GitLab Kubernetes Agent Server (kas)
for various purposes.
These endpoints are all authenticated using JWT. The JWT secret is stored in a file
specified in `config/gitlab.yml`. By default, the location is in the root of the
GitLab Rails app in a file called `.gitlab_kas_secret`.
CAUTION: **Caution:**
The Kubernetes agent is under development and is not recommended for production use.
### Kubernetes agent information
Called from GitLab Kubernetes Agent Server (kas) to retrieve agent
information for the given agent token. This returns the Gitaly connection
@ -388,7 +404,7 @@ Example Request:
curl --request GET --header "Gitlab-Kas-Api-Request: <JWT token>" --header "Authorization: Bearer <agent token>" "http://localhost:3000/api/v4/internal/kubernetes/agent_info"
```
## Kubernetes agent project information
### Kubernetes agent project information
Called from GitLab Kubernetes Agent Server (kas) to retrieve project
information for the given agent token. This returns the Gitaly
@ -413,7 +429,7 @@ Example Request:
curl --request GET --header "Gitlab-Kas-Api-Request: <JWT token>" --header "Authorization: Bearer <agent token>" "http://localhost:3000/api/v4/internal/kubernetes/project_info?id=7"
```
## Kubernetes agent usage metrics
### Kubernetes agent usage metrics
Called from GitLab Kubernetes Agent Server (kas) to increase the usage
metric counters.

View File

@ -762,6 +762,7 @@ The following is example content of the Usage Ping payload.
"topology": {
"duration_s": 0.013836685999194742,
"application_requests_per_hour": 4224,
"query_apdex_weekly_average": 0.996,
"failures": [],
"nodes": [
{

View File

@ -107,7 +107,15 @@ Follow these steps to configure API fuzzing in GitLab with an OpenAPI specificat
```
1. The target API instance's base URL is also required. Provide it by using the `FUZZAPI_TARGET_URL`
variable:
variable or an `environment_url.txt` file.
Adding the URL in an `environment_url.txt` file at your project's root is great for testing in
dynamic environments. To run API fuzzing against an app dynamically created during a GitLab CI/CD
pipeline, have the app persist its domain in an `environment_url.txt` file. API fuzzing
automatically parses that file to find its scan target. You can see an
[example of this in our Auto DevOps CI YAML](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml).
Here's an example of using `FUZZAPI_TARGET_URL`:
```yaml
include:
@ -189,7 +197,16 @@ target API to test:
FUZZAPI_HAR: test-api-specification.json
```
1. Add the `FUZZAPI_TARGET_URL` variable and set it to the target API instance's base URL:
1. The target API instance's base URL is also required. Provide it by using the `FUZZAPI_TARGET_URL`
variable or an `environment_url.txt` file.
Adding the URL in an `environment_url.txt` file at your project's root is great for testing in
dynamic environments. To run API fuzzing against an app dynamically created during a GitLab CI/CD
pipeline, have the app persist its domain in an `environment_url.txt` file. API fuzzing
automatically parses that file to find its scan target. You can see an
[example of this in our Auto DevOps CI YAML](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml).
Here's an example of using `FUZZAPI_TARGET_URL`:
```yaml
include:

View File

@ -89,3 +89,10 @@ is created, you are ready to [upload your package](../conan_repository/index.md#
```shell
CONAN_LOGIN_USERNAME=<gitlab-username> CONAN_PASSWORD=<personal_access_token> conan upload MyPackage/1.0.0@foo+bar+my-proj/channel --all --remote=gitlab
```
#### Composer
It is currently not possible to publish a Composer package to a project that is different from where its code resides.
If you attempt to publish a Composer package to a different project, you get a `404 Branch Not Found`
or `404 Tag Not Found` error.

View File

@ -62,6 +62,25 @@ The importer will create any new namespaces (groups) if they don't exist or in
the case the namespace is taken, the repository will be imported under the user's
namespace that started the import process.
#### User assignment by username
Alternatively, user assignment by username is available behind a `bitbucket_server_user_mapping_by_username` feature flag.
The importer will try to find a user in the GitLab user database using author's `username` or `slug` or `displayName`.
Falls back to author's `email` if user is not found by username.
Similarly to user assignment by email, if no such user is available, the project creator is set as the author.
To enable or disable user assignment by username:
Start a [Rails console](../../../administration/troubleshooting/debug.md#starting-a-rails-console-session).
```ruby
# Enable
Feature.enable(:bitbucket_server_user_mapping_by_username)
# Disable
Feature.disable(:bitbucket_server_user_mapping_by_username)
```
## Importing your Bitbucket repositories
1. Sign in to GitLab and go to your dashboard.

View File

@ -45,7 +45,7 @@ module API
end
def check_feature_enabled
not_found! unless Feature.enabled?(:kubernetes_agent_internal_api, default_enabled: true)
not_found! unless Feature.enabled?(:kubernetes_agent_internal_api, default_enabled: true, type: :ops)
end
def check_agent_token

View File

@ -38,7 +38,9 @@ module BitbucketServer
end
def author_username
author['displayName']
author['username'] ||
author['slug'] ||
author['displayName']
end
def author_email

View File

@ -11,6 +11,12 @@ module BitbucketServer
raw.dig('author', 'user', 'emailAddress')
end
def author_username
raw.dig('author', 'user', 'username') ||
raw.dig('author', 'user', 'slug') ||
raw.dig('author', 'user', 'displayName')
end
def description
raw['description']
end

View File

@ -18,6 +18,10 @@ class Feature
superclass.table_name = 'feature_gates'
end
class ActiveSupportCacheStoreAdapter < Flipper::Adapters::ActiveSupportCacheStore
# overrides methods in EE
end
InvalidFeatureFlagError = Class.new(Exception) # rubocop:disable Lint/InheritException
class << self
@ -160,7 +164,7 @@ class Feature
# Redis L2 cache
redis_cache_adapter =
Flipper::Adapters::ActiveSupportCacheStore.new(
ActiveSupportCacheStoreAdapter.new(
active_record_adapter,
l2_cache_backend,
expires_in: 1.hour)
@ -237,4 +241,4 @@ class Feature
end
end
Feature.prepend_if_ee('EE::Feature')
Feature::ActiveSupportCacheStoreAdapter.prepend_if_ee('EE::Feature::ActiveSupportCacheStoreAdapter')

View File

@ -61,17 +61,18 @@ module Gitlab
}.to_json)
end
def gitlab_user_id(email)
find_user_id(email) || project.creator_id
end
def find_user_id(by:, value:)
return unless value
def find_user_id(email)
return unless email
return users[value] if users.key?(value)
return users[email] if users.key?(email)
user = if by == :email
User.find_by_any_email(value, confirmed: true)
else
User.find_by_username(value)
end
user = User.find_by_any_email(email, confirmed: true)
users[email] = user&.id
users[value] = user&.id
user&.id
end
@ -197,9 +198,8 @@ module Gitlab
log_info(stage: 'import_bitbucket_pull_requests', message: 'starting', iid: pull_request.iid)
description = ''
description += @formatter.author_line(pull_request.author) unless find_user_id(pull_request.author_email)
description += author_line(pull_request)
description += pull_request.description if pull_request.description
author_id = gitlab_user_id(pull_request.author_email)
attributes = {
iid: pull_request.iid,
@ -212,7 +212,7 @@ module Gitlab
target_branch: Gitlab::Git.ref_name(pull_request.target_branch_name),
target_branch_sha: pull_request.target_branch_sha,
state_id: MergeRequest.available_states[pull_request.state],
author_id: author_id,
author_id: author_id(pull_request),
created_at: pull_request.created_at,
updated_at: pull_request.updated_at
}
@ -254,7 +254,7 @@ module Gitlab
committer = merge_event.committer_email
user_id = gitlab_user_id(committer)
user_id = find_user_id(by: :email, value: committer) || project.creator_id
timestamp = merge_event.merge_timestamp
merge_request.update({ merge_commit_sha: merge_event.merge_commit })
metric = MergeRequest::Metrics.find_or_initialize_by(merge_request: merge_request)
@ -353,7 +353,7 @@ module Gitlab
end
def pull_request_comment_attributes(comment)
author = find_user_id(comment.author_email)
author = uid(comment)
note = ''
unless author
@ -397,6 +397,23 @@ module Gitlab
def metrics
@metrics ||= Gitlab::Import::Metrics.new(:bitbucket_server_importer, @project)
end
def author_line(rep_object)
return '' if uid(rep_object)
@formatter.author_line(rep_object.author)
end
def author_id(rep_object)
uid(rep_object) || project.creator_id
end
def uid(rep_object)
find_user_id(by: :email, value: rep_object.author_email) unless Feature.enabled?(:bitbucket_server_user_mapping_by_username)
find_user_id(by: :username, value: rep_object.author_username) ||
find_user_id(by: :email, value: rep_object.author_email)
end
end
end
end

View File

@ -43,6 +43,7 @@ module Gitlab
with_prometheus_client(fallback: {}) do |client|
{
application_requests_per_hour: topology_app_requests_per_hour(client),
query_apdex_weekly_average: topology_query_apdex_weekly_average(client),
nodes: topology_node_data(client)
}.compact
end
@ -63,6 +64,16 @@ module Gitlab
(result['value'].last.to_f * 1.hour).to_i
end
def topology_query_apdex_weekly_average(client)
result = query_safely('gitlab_usage_ping:sql_duration_apdex:ratio_rate5m', 'query_apdex', fallback: nil) do |query|
client.query(aggregate_one_week(query)).first
end
return unless result
result['value'].last.to_f
end
def topology_node_data(client)
# node-level data
by_instance_mem = topology_node_memory(client)

View File

@ -20,6 +20,9 @@ module QA
end
def select_project
wait_until(sleep_interval: 2, reload: false) do
has_element? :project_list_item
end
click_element :project_list_item
end
end

View File

@ -20,7 +20,8 @@
]
},
"name": "root",
"slug": "root",
"slug": "slug",
"username": "username",
"type": "NORMAL"
},
"comments": [
@ -38,7 +39,8 @@
]
},
"name": "root",
"slug": "root",
"slug": "slug",
"username": "username",
"type": "NORMAL"
},
"comments": [
@ -56,7 +58,8 @@
]
},
"name": "root",
"slug": "root",
"slug": "slug",
"username": "username",
"type": "NORMAL"
},
"comments": [],
@ -85,7 +88,8 @@
]
},
"name": "root",
"slug": "root",
"slug": "slug",
"username": "username",
"type": "NORMAL"
},
"createdDate": 1530164016725,
@ -115,7 +119,8 @@
]
},
"name": "root",
"slug": "root",
"slug": "slug",
"username": "username",
"type": "NORMAL"
},
"createdDate": 1530164026000,
@ -147,7 +152,8 @@
]
},
"name": "root",
"slug": "root",
"slug": "slug",
"username": "username",
"type": "NORMAL"
},
"comments": [],
@ -194,7 +200,8 @@
]
},
"name": "root",
"slug": "root",
"slug": "slug",
"username": "username",
"type": "NORMAL"
},
"comments": [],
@ -363,7 +370,8 @@
]
},
"name": "root",
"slug": "root",
"slug": "slug",
"username": "username",
"type": "NORMAL"
}
},
@ -383,7 +391,8 @@
]
},
"name": "root",
"slug": "root",
"slug": "slug",
"username": "username",
"type": "NORMAL"
},
"comments": [],
@ -543,7 +552,8 @@
]
},
"name": "root",
"slug": "root",
"slug": "slug",
"username": "username",
"type": "NORMAL"
}
},
@ -563,7 +573,8 @@
]
},
"name": "root",
"slug": "root",
"slug": "slug",
"username": "username",
"type": "NORMAL"
},
"comments": [
@ -581,7 +592,8 @@
]
},
"name": "root",
"slug": "root",
"slug": "slug",
"username": "username",
"type": "NORMAL"
},
"comments": [
@ -599,7 +611,8 @@
]
},
"name": "root",
"slug": "root",
"slug": "slug",
"username": "username",
"type": "NORMAL"
},
"comments": [],
@ -789,7 +802,8 @@
]
},
"name": "root",
"slug": "root",
"slug": "slug",
"username": "username",
"type": "NORMAL"
}
},
@ -809,7 +823,8 @@
]
},
"name": "root",
"slug": "root",
"slug": "slug",
"username": "username",
"type": "NORMAL"
},
"comments": [],
@ -843,7 +858,8 @@
]
},
"name": "root",
"slug": "root",
"slug": "slug",
"username": "username",
"type": "NORMAL"
}
},
@ -863,7 +879,8 @@
]
},
"name": "root",
"slug": "root",
"slug": "slug",
"username": "username",
"type": "NORMAL"
},
"authorTimestamp": 1529727872000,
@ -880,7 +897,8 @@
]
},
"name": "root",
"slug": "root",
"slug": "slug",
"username": "username",
"type": "NORMAL"
},
"committerTimestamp": 1529727872000,
@ -951,7 +969,8 @@
]
},
"name": "root",
"slug": "root",
"slug": "slug",
"username": "username",
"type": "NORMAL"
}
},
@ -971,7 +990,8 @@
]
},
"name": "root",
"slug": "root",
"slug": "slug",
"username": "username",
"type": "NORMAL"
},
"comments": [
@ -989,7 +1009,8 @@
]
},
"name": "root",
"slug": "root",
"slug": "slug",
"username": "username",
"type": "NORMAL"
},
"comments": [],
@ -1038,7 +1059,8 @@
]
},
"name": "root",
"slug": "root",
"slug": "slug",
"username": "username",
"type": "NORMAL"
}
},
@ -1058,7 +1080,8 @@
]
},
"name": "root",
"slug": "root",
"slug": "slug",
"username": "username",
"type": "NORMAL"
},
"comments": [],
@ -1092,7 +1115,8 @@
]
},
"name": "root",
"slug": "root",
"slug": "slug",
"username": "username",
"type": "NORMAL"
}
},
@ -1113,7 +1137,8 @@
]
},
"name": "root",
"slug": "root",
"slug": "slug",
"username": "username",
"type": "NORMAL"
}
}

View File

@ -5,8 +5,9 @@
"status":"UNAPPROVED",
"user":{
"active":true,
"displayName":"root",
"displayName":"displayName",
"emailAddress":"joe.montana@49ers.com",
"username": "username",
"id":1,
"links":{
"self":[
@ -16,7 +17,7 @@
]
},
"name":"root",
"slug":"root",
"slug":"slug",
"type":"NORMAL"
}
},

View File

@ -10,7 +10,7 @@ exports[`Design management design version dropdown component renders design vers
text="Showing latest version"
variant="default"
>
<gl-new-dropdown-item-stub
<gl-dropdown-item-stub
avatarurl=""
iconcolor=""
iconname=""
@ -22,8 +22,8 @@ exports[`Design management design version dropdown component renders design vers
Version
2
(latest)
</gl-new-dropdown-item-stub>
<gl-new-dropdown-item-stub
</gl-dropdown-item-stub>
<gl-dropdown-item-stub
avatarurl=""
iconcolor=""
iconname=""
@ -34,7 +34,7 @@ exports[`Design management design version dropdown component renders design vers
Version
1
</gl-new-dropdown-item-stub>
</gl-dropdown-item-stub>
</gl-dropdown-stub>
`;
@ -48,7 +48,7 @@ exports[`Design management design version dropdown component renders design vers
text="Showing latest version"
variant="default"
>
<gl-new-dropdown-item-stub
<gl-dropdown-item-stub
avatarurl=""
iconcolor=""
iconname=""
@ -60,8 +60,8 @@ exports[`Design management design version dropdown component renders design vers
Version
2
(latest)
</gl-new-dropdown-item-stub>
<gl-new-dropdown-item-stub
</gl-dropdown-item-stub>
<gl-dropdown-item-stub
avatarurl=""
iconcolor=""
iconname=""
@ -72,6 +72,6 @@ exports[`Design management design version dropdown component renders design vers
Version
1
</gl-new-dropdown-item-stub>
</gl-dropdown-item-stub>
</gl-dropdown-stub>
`;

View File

@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
import { GlDropdown, GlNewDropdownItem, GlSprintf } from '@gitlab/ui';
import { GlDropdown, GlDropdownItem, GlSprintf } from '@gitlab/ui';
import DesignVersionDropdown from '~/design_management/components/upload/design_version_dropdown.vue';
import mockAllVersions from './mock_data/all_versions';
@ -42,7 +42,7 @@ describe('Design management design version dropdown component', () => {
wrapper.destroy();
});
const findVersionLink = index => wrapper.findAll(GlNewDropdownItem).at(index);
const findVersionLink = index => wrapper.findAll(GlDropdownItem).at(index);
it('renders design version dropdown button', () => {
createComponent();
@ -107,7 +107,7 @@ describe('Design management design version dropdown component', () => {
createComponent();
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.findAll(GlNewDropdownItem)).toHaveLength(wrapper.vm.allVersions.length);
expect(wrapper.findAll(GlDropdownItem)).toHaveLength(wrapper.vm.allVersions.length);
});
});
});

View File

@ -55,7 +55,7 @@ exports[`Alert integration settings form default state should match the default
text="selecte_tmpl"
variant="default"
>
<gl-new-dropdown-item-stub
<gl-dropdown-item-stub
avatarurl=""
data-qa-selector="incident_templates_item"
iconcolor=""
@ -67,7 +67,7 @@ exports[`Alert integration settings form default state should match the default
No template selected
</gl-new-dropdown-item-stub>
</gl-dropdown-item-stub>
</gl-dropdown-stub>
</gl-form-group-stub>

View File

@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
import { GlNewDropdownItem } from '@gitlab/ui';
import { GlDropdownItem } from '@gitlab/ui';
import { createStore } from '~/monitoring/stores';
import { DASHBOARD_PAGE, PANEL_NEW_PAGE } from '~/monitoring/router/constants';
import { setupAllDashboards, setupStoreWithData } from '../store_utils';
@ -146,8 +146,8 @@ describe('Actions menu', () => {
});
describe('add panel item', () => {
const GlNewDropdownItemStub = {
extends: GlNewDropdownItem,
const GlDropdownItemStub = {
extends: GlDropdownItem,
props: {
to: [String, Object],
},
@ -164,7 +164,7 @@ describe('Actions menu', () => {
},
{
mocks: { $route },
stubs: { GlNewDropdownItem: GlNewDropdownItemStub },
stubs: { GlDropdownItem: GlDropdownItemStub },
},
);
});

View File

@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
import { GlNewDropdownItem, GlSearchBoxByType, GlLoadingIcon, GlButton } from '@gitlab/ui';
import { GlDropdownItem, GlSearchBoxByType, GlLoadingIcon, GlButton } from '@gitlab/ui';
import { createStore } from '~/monitoring/stores';
import * as types from '~/monitoring/stores/mutation_types';
import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
@ -31,7 +31,7 @@ describe('Dashboard header', () => {
const findDashboardDropdown = () => wrapper.find(DashboardsDropdown);
const findEnvsDropdown = () => wrapper.find({ ref: 'monitorEnvironmentsDropdown' });
const findEnvsDropdownItems = () => findEnvsDropdown().findAll(GlNewDropdownItem);
const findEnvsDropdownItems = () => findEnvsDropdown().findAll(GlDropdownItem);
const findEnvsDropdownSearch = () => findEnvsDropdown().find(GlSearchBoxByType);
const findEnvsDropdownSearchMsg = () => wrapper.find({ ref: 'monitorEnvironmentsDropdownMsg' });
const findEnvsDropdownLoadingIcon = () => findEnvsDropdown().find(GlLoadingIcon);

View File

@ -2,7 +2,7 @@ import Vuex from 'vuex';
import { shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
import { setTestTimeout } from 'helpers/timeout';
import { GlNewDropdownItem as GlDropdownItem } from '@gitlab/ui';
import { GlDropdownItem } from '@gitlab/ui';
import invalidUrl from '~/lib/utils/invalid_url';
import axios from '~/lib/utils/axios_utils';
import AlertWidget from '~/monitoring/components/alert_widget.vue';

View File

@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
import { GlNewDropdownItem, GlIcon } from '@gitlab/ui';
import { GlDropdownItem, GlIcon } from '@gitlab/ui';
import DashboardsDropdown from '~/monitoring/components/dashboards_dropdown.vue';
@ -33,8 +33,8 @@ describe('DashboardsDropdown', () => {
});
}
const findItems = () => wrapper.findAll(GlNewDropdownItem);
const findItemAt = i => wrapper.findAll(GlNewDropdownItem).at(i);
const findItems = () => wrapper.findAll(GlDropdownItem);
const findItemAt = i => wrapper.findAll(GlDropdownItem).at(i);
const findSearchInput = () => wrapper.find({ ref: 'monitorDashboardsDropdownSearch' });
const findNoItemsMsg = () => wrapper.find({ ref: 'monitorDashboardsDropdownMsg' });
const findStarredListDivider = () => wrapper.find({ ref: 'starredListDivider' });

View File

@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import Visibility from 'visibilityjs';
import { GlDropdown, GlNewDropdownItem, GlButton } from '@gitlab/ui';
import { GlDropdown, GlDropdownItem, GlButton } from '@gitlab/ui';
import { createStore } from '~/monitoring/stores';
import RefreshButton from '~/monitoring/components/refresh_button.vue';
@ -16,7 +16,7 @@ describe('RefreshButton', () => {
const findRefreshBtn = () => wrapper.find(GlButton);
const findDropdown = () => wrapper.find(GlDropdown);
const findOptions = () => findDropdown().findAll(GlNewDropdownItem);
const findOptions = () => findDropdown().findAll(GlDropdownItem);
const findOptionAt = index => findOptions().at(index);
const expectFetchDataToHaveBeenCalledTimes = times => {

View File

@ -1,5 +1,5 @@
import { mount, shallowMount } from '@vue/test-utils';
import { GlDropdown, GlNewDropdownItem, GlForm } from '@gitlab/ui';
import { GlDropdown, GlDropdownItem, GlForm } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
@ -24,7 +24,7 @@ describe('Pipeline New Form', () => {
const findForm = () => wrapper.find(GlForm);
const findDropdown = () => wrapper.find(GlDropdown);
const findDropdownItems = () => wrapper.findAll(GlNewDropdownItem);
const findDropdownItems = () => wrapper.findAll(GlDropdownItem);
const findVariableRows = () => wrapper.findAll('[data-testid="ci-variable-row"]');
const findRemoveIcons = () => wrapper.findAll('[data-testid="remove-ci-variable-row"]');
const findKeyInputs = () => wrapper.findAll('[data-testid="pipeline-form-ci-variable-key"]');

View File

@ -1,6 +1,6 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { GlDropdown, GlNewDropdownHeader, GlSearchBoxByType, GlNewDropdownItem } from '@gitlab/ui';
import { GlDropdown, GlNewDropdownHeader, GlSearchBoxByType, GlDropdownItem } from '@gitlab/ui';
import * as urlUtility from '~/lib/utils/url_utility';
import AuthorSelect from '~/projects/commits/components/author_select.vue';
import { createStore } from '~/projects/commits/store';
@ -61,7 +61,7 @@ describe('Author Select', () => {
const findDropdown = () => wrapper.find(GlDropdown);
const findDropdownHeader = () => wrapper.find(GlNewDropdownHeader);
const findSearchBox = () => wrapper.find(GlSearchBoxByType);
const findDropdownItems = () => wrapper.findAll(GlNewDropdownItem);
const findDropdownItems = () => wrapper.findAll(GlDropdownItem);
describe('user is searching via "filter by commit message"', () => {
it('disables dropdown container', () => {

View File

@ -2,7 +2,7 @@ import Vuex from 'vuex';
import { mount, createLocalVue } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { GlLoadingIcon, GlSearchBoxByType, GlNewDropdownItem, GlIcon } from '@gitlab/ui';
import { GlLoadingIcon, GlSearchBoxByType, GlDropdownItem, GlIcon } from '@gitlab/ui';
import { trimText } from 'helpers/text_helper';
import { sprintf } from '~/locale';
import { ENTER_KEY } from '~/lib/utils/keys';
@ -87,15 +87,15 @@ describe('Ref selector component', () => {
const findSearchBox = () => wrapper.find(GlSearchBoxByType);
const findBranchesSection = () => wrapper.find('[data-testid="branches-section"]');
const findBranchDropdownItems = () => findBranchesSection().findAll(GlNewDropdownItem);
const findBranchDropdownItems = () => findBranchesSection().findAll(GlDropdownItem);
const findFirstBranchDropdownItem = () => findBranchDropdownItems().at(0);
const findTagsSection = () => wrapper.find('[data-testid="tags-section"]');
const findTagDropdownItems = () => findTagsSection().findAll(GlNewDropdownItem);
const findTagDropdownItems = () => findTagsSection().findAll(GlDropdownItem);
const findFirstTagDropdownItem = () => findTagDropdownItems().at(0);
const findCommitsSection = () => wrapper.find('[data-testid="commits-section"]');
const findCommitDropdownItems = () => findCommitsSection().findAll(GlNewDropdownItem);
const findCommitDropdownItems = () => findCommitsSection().findAll(GlDropdownItem);
const findFirstCommitDropdownItem = () => findCommitDropdownItems().at(0);
//

View File

@ -319,8 +319,8 @@ describe('File finder item spec', () => {
.catch(done.fail);
});
it('calls toggle on `command+p` key press', done => {
Mousetrap.trigger('command+p');
it('calls toggle on `mod+p` key press', done => {
Mousetrap.trigger('mod+p');
vm.$nextTick()
.then(() => {
@ -330,33 +330,12 @@ describe('File finder item spec', () => {
.catch(done.fail);
});
it('calls toggle on `ctrl+p` key press', done => {
Mousetrap.trigger('ctrl+p');
vm.$nextTick()
.then(() => {
expect(vm.toggle).toHaveBeenCalled();
})
.then(done)
.catch(done.fail);
});
it('always allows `command+p` to trigger toggle', () => {
it('always allows `mod+p` to trigger toggle', () => {
expect(
Mousetrap.prototype.stopCallback(
null,
vm.$el.querySelector('.dropdown-input-field'),
'command+p',
),
).toBe(false);
});
it('always allows `ctrl+p` to trigger toggle', () => {
expect(
Mousetrap.prototype.stopCallback(
null,
vm.$el.querySelector('.dropdown-input-field'),
'ctrl+p',
'mod+p',
),
).toBe(false);
});

View File

@ -1,11 +1,5 @@
import { shallowMount, mount } from '@vue/test-utils';
import {
GlFilteredSearch,
GlButtonGroup,
GlButton,
GlDropdown,
GlNewDropdownItem as GlDropdownItem,
} from '@gitlab/ui';
import { GlFilteredSearch, GlButtonGroup, GlButton, GlDropdown, GlDropdownItem } 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';

View File

@ -3,7 +3,7 @@ import {
GlFilteredSearchToken,
GlFilteredSearchTokenSegment,
GlFilteredSearchSuggestion,
GlNewDropdownDivider as GlDropdownDivider,
GlDropdownDivider,
} from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';

View File

@ -3,7 +3,7 @@ import {
GlFilteredSearchToken,
GlFilteredSearchSuggestion,
GlFilteredSearchTokenSegment,
GlNewDropdownDivider as GlDropdownDivider,
GlDropdownDivider,
} from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';

View File

@ -3,7 +3,7 @@ import {
GlFilteredSearchToken,
GlFilteredSearchSuggestion,
GlFilteredSearchTokenSegment,
GlNewDropdownDivider as GlDropdownDivider,
GlDropdownDivider,
} from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';

View File

@ -13,7 +13,30 @@ RSpec.describe BitbucketServer::Representation::Comment do
end
describe '#author_username' do
it { expect(subject.author_username).to eq('root' ) }
it 'returns username' do
expect(subject.author_username).to eq('username')
end
context 'when username is absent' do
before do
comment['comment']['author'].delete('username')
end
it 'returns slug' do
expect(subject.author_username).to eq('slug')
end
end
context 'when slug and username are absent' do
before do
comment['comment']['author'].delete('username')
comment['comment']['author'].delete('slug')
end
it 'returns displayName' do
expect(subject.author_username).to eq('root')
end
end
end
describe '#author_email' do

View File

@ -15,6 +15,33 @@ RSpec.describe BitbucketServer::Representation::PullRequest do
it { expect(subject.author_email).to eq('joe.montana@49ers.com') }
end
describe '#author_username' do
it 'returns username' do
expect(subject.author_username).to eq('username')
end
context 'when username is absent' do
before do
sample_data['author']['user'].delete('username')
end
it 'returns slug' do
expect(subject.author_username).to eq('slug')
end
end
context 'when slug and username are absent' do
before do
sample_data['author']['user'].delete('username')
sample_data['author']['user'].delete('slug')
end
it 'returns displayName' do
expect(subject.author_username).to eq('displayName')
end
end
end
describe '#description' do
it { expect(subject.description).to eq('Test') }
end

View File

@ -6,9 +6,10 @@ RSpec.describe Gitlab::BitbucketServerImport::Importer do
include ImportSpecHelper
let(:import_url) { 'http://my-bitbucket' }
let(:user) { 'bitbucket' }
let(:bitbucket_user) { 'bitbucket' }
let(:project_creator) { create(:user, username: 'project_creator', email: 'project_creator@example.org') }
let(:password) { 'test' }
let(:project) { create(:project, :repository, import_url: import_url) }
let(:project) { create(:project, :repository, import_url: import_url, creator: project_creator) }
let(:now) { Time.now.utc.change(usec: 0) }
let(:project_key) { 'TEST' }
let(:repo_slug) { 'rouge' }
@ -19,7 +20,7 @@ RSpec.describe Gitlab::BitbucketServerImport::Importer do
before do
data = project.create_or_update_import_data(
data: { project_key: project_key, repo_slug: repo_slug },
credentials: { base_uri: import_url, user: user, password: password }
credentials: { base_uri: import_url, user: bitbucket_user, password: password }
)
data.save
project.save
@ -51,12 +52,11 @@ RSpec.describe Gitlab::BitbucketServerImport::Importer do
end
describe '#import_pull_requests' do
before do
allow(subject).to receive(:import_repository)
allow(subject).to receive(:delete_temp_branches)
allow(subject).to receive(:restore_branches)
let(:pull_request_author) { create(:user, username: 'pull_request_author', email: 'pull_request_author@example.org') }
let(:note_author) { create(:user, username: 'note_author', email: 'note_author@example.org') }
pull_request = instance_double(
let(:pull_request) do
instance_double(
BitbucketServer::Representation::PullRequest,
iid: 10,
source_branch_sha: sample.commits.last,
@ -67,65 +67,172 @@ RSpec.describe Gitlab::BitbucketServerImport::Importer do
description: 'This is a test pull request',
state: 'merged',
author: 'Test Author',
author_email: project.owner.email,
author_email: pull_request_author.email,
author_username: pull_request_author.username,
created_at: Time.now,
updated_at: Time.now,
raw: {},
merged?: true)
end
allow(subject.client).to receive(:pull_requests).and_return([pull_request])
@merge_event = instance_double(
let(:merge_event) do
instance_double(
BitbucketServer::Representation::Activity,
comment?: false,
merge_event?: true,
committer_email: project.owner.email,
committer_email: pull_request_author.email,
merge_timestamp: now,
merge_commit: '12345678'
)
end
@pr_note = instance_double(
let(:pr_note) do
instance_double(
BitbucketServer::Representation::Comment,
note: 'Hello world',
author_email: 'unknown@gmail.com',
author_username: 'The Flash',
author_email: note_author.email,
author_username: note_author.username,
comments: [],
created_at: now,
updated_at: now,
parent_comment: nil)
end
@pr_comment = instance_double(
let(:pr_comment) do
instance_double(
BitbucketServer::Representation::Activity,
comment?: true,
inline_comment?: false,
merge_event?: false,
comment: @pr_note)
comment: pr_note)
end
before do
allow(subject).to receive(:import_repository)
allow(subject).to receive(:delete_temp_branches)
allow(subject).to receive(:restore_branches)
allow(subject.client).to receive(:pull_requests).and_return([pull_request])
end
it 'imports merge event' do
expect(subject.client).to receive(:activities).and_return([@merge_event])
expect(subject.client).to receive(:activities).and_return([merge_event])
expect { subject.execute }.to change { MergeRequest.count }.by(1)
merge_request = MergeRequest.first
expect(merge_request.metrics.merged_by).to eq(project.owner)
expect(merge_request.metrics.merged_at).to eq(@merge_event.merge_timestamp)
expect(merge_request.metrics.merged_by).to eq(pull_request_author)
expect(merge_request.metrics.merged_at).to eq(merge_event.merge_timestamp)
expect(merge_request.merge_commit_sha).to eq('12345678')
expect(merge_request.state_id).to eq(3)
end
it 'imports comments' do
expect(subject.client).to receive(:activities).and_return([@pr_comment])
describe 'pull request author user mapping' do
before do
allow(subject.client).to receive(:activities).and_return([merge_event])
end
expect { subject.execute }.to change { MergeRequest.count }.by(1)
shared_examples 'imports pull requests' do
it 'maps user' do
expect { subject.execute }.to change { MergeRequest.count }.by(1)
merge_request = MergeRequest.first
expect(merge_request.notes.count).to eq(1)
note = merge_request.notes.first
expect(note.note).to end_with(@pr_note.note)
expect(note.author).to eq(project.owner)
expect(note.created_at).to eq(@pr_note.created_at)
expect(note.updated_at).to eq(@pr_note.created_at)
merge_request = MergeRequest.first
expect(merge_request.author).to eq(pull_request_author)
end
end
context 'when bitbucket_server_user_mapping_by_username feature flag is disabled' do
before do
stub_feature_flags(bitbucket_server_user_mapping_by_username: false)
end
include_examples 'imports pull requests'
end
context 'when bitbucket_server_user_mapping_by_username feature flag is enabled' do
before do
stub_feature_flags(bitbucket_server_user_mapping_by_username: true)
end
include_examples 'imports pull requests' do
context 'when username is not present' do
before do
allow(pull_request).to receive(:author_username).and_return(nil)
end
it 'maps by email' do
expect { subject.execute }.to change { MergeRequest.count }.by(1)
merge_request = MergeRequest.first
expect(merge_request.author).to eq(pull_request_author)
end
end
end
end
context 'when user is not found' do
before do
allow(pull_request).to receive(:author_username).and_return(nil)
allow(pull_request).to receive(:author_email).and_return(nil)
end
it 'maps importer user' do
expect { subject.execute }.to change { MergeRequest.count }.by(1)
merge_request = MergeRequest.first
expect(merge_request.author).to eq(project_creator)
end
end
end
describe 'comments' do
shared_examples 'imports comments' do
it 'imports comments' do
expect(subject.client).to receive(:activities).and_return([pr_comment])
expect { subject.execute }.to change { MergeRequest.count }.by(1)
merge_request = MergeRequest.first
expect(merge_request.notes.count).to eq(1)
note = merge_request.notes.first
expect(note.note).to end_with(pr_note.note)
expect(note.author).to eq(note_author)
expect(note.created_at).to eq(pr_note.created_at)
expect(note.updated_at).to eq(pr_note.created_at)
end
end
context 'when bitbucket_server_user_mapping_by_username feature flag is disabled' do
before do
stub_feature_flags(bitbucket_server_user_mapping_by_username: false)
end
include_examples 'imports comments'
end
context 'when bitbucket_server_user_mapping_by_username feature flag is enabled' do
before do
stub_feature_flags(bitbucket_server_user_mapping_by_username: true)
end
include_examples 'imports comments'
context 'when username is not present' do
before do
allow(pr_note).to receive(:author_username).and_return(nil)
allow(subject.client).to receive(:activities).and_return([pr_comment])
end
it 'maps by email' do
expect { subject.execute }.to change { MergeRequest.count }.by(1)
merge_request = MergeRequest.first
expect(merge_request.notes.count).to eq(1)
note = merge_request.notes.first
expect(note.author).to eq(note_author)
end
end
end
end
context 'metrics' do
@ -135,7 +242,7 @@ RSpec.describe Gitlab::BitbucketServerImport::Importer do
before do
allow(Gitlab::Metrics).to receive(:counter) { counter }
allow(Gitlab::Metrics).to receive(:histogram) { histogram }
allow(subject.client).to receive(:activities).and_return([@merge_event])
allow(subject.client).to receive(:activities).and_return([merge_event])
end
it 'counts and measures duration of imported projects' do
@ -170,73 +277,137 @@ RSpec.describe Gitlab::BitbucketServerImport::Importer do
end
end
it 'imports threaded discussions' do
reply = instance_double(
BitbucketServer::Representation::PullRequestComment,
author_email: 'someuser@gitlab.com',
author_username: 'Batman',
note: 'I agree',
created_at: now,
updated_at: now)
describe 'threaded discussions' do
let(:reply_author) { create(:user, username: 'reply_author', email: 'reply_author@example.org') }
let(:inline_note_author) { create(:user, username: 'inline_note_author', email: 'inline_note_author@example.org') }
let(:reply) do
instance_double(
BitbucketServer::Representation::PullRequestComment,
author_email: reply_author.email,
author_username: reply_author.username,
note: 'I agree',
created_at: now,
updated_at: now)
end
# https://gitlab.com/gitlab-org/gitlab-test/compare/c1acaa58bbcbc3eafe538cb8274ba387047b69f8...5937ac0a7beb003549fc5fd26fc247ad
inline_note = instance_double(
BitbucketServer::Representation::PullRequestComment,
file_type: 'ADDED',
from_sha: sample.commits.first,
to_sha: sample.commits.last,
file_path: '.gitmodules',
old_pos: nil,
new_pos: 4,
note: 'Hello world',
author_email: 'unknown@gmail.com',
author_username: 'Superman',
comments: [reply],
created_at: now,
updated_at: now,
parent_comment: nil)
let(:inline_note) do
instance_double(
BitbucketServer::Representation::PullRequestComment,
file_type: 'ADDED',
from_sha: sample.commits.first,
to_sha: sample.commits.last,
file_path: '.gitmodules',
old_pos: nil,
new_pos: 4,
note: 'Hello world',
author_email: inline_note_author.email,
author_username: inline_note_author.username,
comments: [reply],
created_at: now,
updated_at: now,
parent_comment: nil)
end
allow(reply).to receive(:parent_comment).and_return(inline_note)
let(:inline_comment) do
instance_double(
BitbucketServer::Representation::Activity,
comment?: true,
inline_comment?: true,
merge_event?: false,
comment: inline_note)
end
inline_comment = instance_double(
BitbucketServer::Representation::Activity,
comment?: true,
inline_comment?: true,
merge_event?: false,
comment: inline_note)
before do
allow(reply).to receive(:parent_comment).and_return(inline_note)
allow(subject.client).to receive(:activities).and_return([inline_comment])
end
expect(subject.client).to receive(:activities).and_return([inline_comment])
shared_examples 'imports threaded discussions' do
it 'imports threaded discussions' do
expect { subject.execute }.to change { MergeRequest.count }.by(1)
expect { subject.execute }.to change { MergeRequest.count }.by(1)
merge_request = MergeRequest.first
expect(merge_request.notes.count).to eq(2)
expect(merge_request.notes.map(&:discussion_id).uniq.count).to eq(1)
merge_request = MergeRequest.first
expect(merge_request.notes.count).to eq(2)
expect(merge_request.notes.map(&:discussion_id).uniq.count).to eq(1)
notes = merge_request.notes.order(:id).to_a
start_note = notes.first
expect(start_note.type).to eq('DiffNote')
expect(start_note.note).to end_with(inline_note.note)
expect(start_note.created_at).to eq(inline_note.created_at)
expect(start_note.updated_at).to eq(inline_note.updated_at)
expect(start_note.position.base_sha).to eq(inline_note.from_sha)
expect(start_note.position.start_sha).to eq(inline_note.from_sha)
expect(start_note.position.head_sha).to eq(inline_note.to_sha)
expect(start_note.position.old_line).to be_nil
expect(start_note.position.new_line).to eq(inline_note.new_pos)
expect(start_note.author).to eq(inline_note_author)
notes = merge_request.notes.order(:id).to_a
start_note = notes.first
expect(start_note.type).to eq('DiffNote')
expect(start_note.note).to end_with(inline_note.note)
expect(start_note.created_at).to eq(inline_note.created_at)
expect(start_note.updated_at).to eq(inline_note.updated_at)
expect(start_note.position.base_sha).to eq(inline_note.from_sha)
expect(start_note.position.start_sha).to eq(inline_note.from_sha)
expect(start_note.position.head_sha).to eq(inline_note.to_sha)
expect(start_note.position.old_line).to be_nil
expect(start_note.position.new_line).to eq(inline_note.new_pos)
reply_note = notes.last
# Make sure author and reply context is included
expect(reply_note.note).to start_with("> #{inline_note.note}\n\n#{reply.note}")
expect(reply_note.author).to eq(reply_author)
expect(reply_note.created_at).to eq(reply.created_at)
expect(reply_note.updated_at).to eq(reply.created_at)
expect(reply_note.position.base_sha).to eq(inline_note.from_sha)
expect(reply_note.position.start_sha).to eq(inline_note.from_sha)
expect(reply_note.position.head_sha).to eq(inline_note.to_sha)
expect(reply_note.position.old_line).to be_nil
expect(reply_note.position.new_line).to eq(inline_note.new_pos)
end
end
reply_note = notes.last
# Make sure author and reply context is included
expect(reply_note.note).to start_with("*By #{reply.author_username} (#{reply.author_email})*\n\n")
expect(reply_note.note).to end_with("> #{inline_note.note}\n\n#{reply.note}")
expect(reply_note.author).to eq(project.owner)
expect(reply_note.created_at).to eq(reply.created_at)
expect(reply_note.updated_at).to eq(reply.created_at)
expect(reply_note.position.base_sha).to eq(inline_note.from_sha)
expect(reply_note.position.start_sha).to eq(inline_note.from_sha)
expect(reply_note.position.head_sha).to eq(inline_note.to_sha)
expect(reply_note.position.old_line).to be_nil
expect(reply_note.position.new_line).to eq(inline_note.new_pos)
context 'when bitbucket_server_user_mapping_by_username feature flag is disabled' do
before do
stub_feature_flags(bitbucket_server_user_mapping_by_username: false)
end
include_examples 'imports threaded discussions'
end
context 'when bitbucket_server_user_mapping_by_username feature flag is enabled' do
before do
stub_feature_flags(bitbucket_server_user_mapping_by_username: true)
end
include_examples 'imports threaded discussions' do
context 'when username is not present' do
before do
allow(reply).to receive(:author_username).and_return(nil)
allow(inline_note).to receive(:author_username).and_return(nil)
end
it 'maps by email' do
expect { subject.execute }.to change { MergeRequest.count }.by(1)
notes = MergeRequest.first.notes.order(:id).to_a
expect(notes.first.author).to eq(inline_note_author)
expect(notes.last.author).to eq(reply_author)
end
end
end
end
context 'when user is not found' do
before do
allow(reply).to receive(:author_username).and_return(nil)
allow(reply).to receive(:author_email).and_return(nil)
allow(inline_note).to receive(:author_username).and_return(nil)
allow(inline_note).to receive(:author_email).and_return(nil)
end
it 'maps importer user' do
expect { subject.execute }.to change { MergeRequest.count }.by(1)
notes = MergeRequest.first.notes.order(:id).to_a
expect(notes.first.author).to eq(project_creator)
expect(notes.last.author).to eq(project_creator)
end
end
end
it 'falls back to comments if diff comments fail to validate' do
@ -312,6 +483,7 @@ RSpec.describe Gitlab::BitbucketServerImport::Importer do
state: 'merged',
author: 'Test Author',
author_email: project.owner.email,
author_username: 'author',
created_at: Time.now,
updated_at: Time.now,
merged?: true)

View File

@ -18,6 +18,7 @@ RSpec.describe Gitlab::UsageData::Topology do
it 'contains node level metrics for each instance' do
expect_prometheus_api_to(
receive_app_request_volume_query,
receive_query_apdex_ratio_query,
receive_node_memory_query,
receive_node_memory_utilization_query,
receive_node_cpu_count_query,
@ -33,6 +34,7 @@ RSpec.describe Gitlab::UsageData::Topology do
expect(subject[:topology]).to eq({
duration_s: 0,
application_requests_per_hour: 36,
query_apdex_weekly_average: 0.996,
failures: [],
nodes: [
{
@ -102,6 +104,7 @@ RSpec.describe Gitlab::UsageData::Topology do
it 'removes the respective entries and includes the failures' do
expect_prometheus_api_to(
receive_app_request_volume_query(result: []),
receive_query_apdex_ratio_query(result: []),
receive_node_memory_query(result: []),
receive_node_memory_utilization_query(result: []),
receive_node_cpu_count_query,
@ -118,6 +121,7 @@ RSpec.describe Gitlab::UsageData::Topology do
duration_s: 0,
failures: [
{ 'app_requests' => 'empty_result' },
{ 'query_apdex' => 'empty_result' },
{ 'node_memory' => 'empty_result' },
{ 'node_memory_utilization' => 'empty_result' },
{ 'service_rss' => 'empty_result' },
@ -240,6 +244,7 @@ RSpec.describe Gitlab::UsageData::Topology do
it 'normalizes equivalent instance values and maps them to the same node' do
expect_prometheus_api_to(
receive_app_request_volume_query(result: []),
receive_query_apdex_ratio_query(result: []),
receive_node_memory_query(result: node_memory_response),
receive_node_memory_utilization_query(result: node_memory_utilization_response),
receive_node_cpu_count_query(result: []),
@ -256,6 +261,7 @@ RSpec.describe Gitlab::UsageData::Topology do
duration_s: 0,
failures: [
{ 'app_requests' => 'empty_result' },
{ 'query_apdex' => 'empty_result' },
{ 'node_cpus' => 'empty_result' },
{ 'node_cpu_utilization' => 'empty_result' },
{ 'service_uss' => 'empty_result' },
@ -304,6 +310,7 @@ RSpec.describe Gitlab::UsageData::Topology do
it 'still reports service metrics' do
expect_prometheus_api_to(
receive_app_request_volume_query(result: []),
receive_query_apdex_ratio_query(result: []),
receive_node_memory_query(result: []),
receive_node_memory_utilization_query(result: []),
receive_node_cpu_count_query(result: []),
@ -320,6 +327,7 @@ RSpec.describe Gitlab::UsageData::Topology do
duration_s: 0,
failures: [
{ 'app_requests' => 'empty_result' },
{ 'query_apdex' => 'empty_result' },
{ 'node_memory' => 'empty_result' },
{ 'node_memory_utilization' => 'empty_result' },
{ 'node_cpus' => 'empty_result' },
@ -377,6 +385,7 @@ RSpec.describe Gitlab::UsageData::Topology do
it 'filters out unknown service data and reports the unknown services as a failure' do
expect_prometheus_api_to(
receive_app_request_volume_query(result: []),
receive_query_apdex_ratio_query(result: []),
receive_node_memory_query(result: []),
receive_node_memory_utilization_query(result: []),
receive_node_cpu_count_query(result: []),
@ -407,6 +416,7 @@ RSpec.describe Gitlab::UsageData::Topology do
duration_s: 0,
failures: [
{ 'app_requests' => 'Gitlab::PrometheusClient::ConnectionError' },
{ 'query_apdex' => 'Gitlab::PrometheusClient::ConnectionError' },
{ 'node_memory' => 'Gitlab::PrometheusClient::ConnectionError' },
{ 'node_memory_utilization' => 'Gitlab::PrometheusClient::ConnectionError' },
{ 'node_cpus' => 'Gitlab::PrometheusClient::ConnectionError' },
@ -437,6 +447,7 @@ RSpec.describe Gitlab::UsageData::Topology do
duration_s: 0,
failures: [
{ 'app_requests' => exception.to_s },
{ 'query_apdex' => 'timeout_cancellation' },
{ 'node_memory' => 'timeout_cancellation' },
{ 'node_memory_utilization' => 'timeout_cancellation' },
{ 'node_cpus' => 'timeout_cancellation' },
@ -512,6 +523,17 @@ RSpec.describe Gitlab::UsageData::Topology do
])
end
def receive_query_apdex_ratio_query(result: nil)
receive(:query)
.with(/gitlab_usage_ping:sql_duration_apdex:ratio_rate5m/)
.and_return(result || [
{
'metric' => {},
'value' => [1000, '0.996']
}
])
end
def receive_node_memory_query(result: nil)
receive(:query)
.with(/node_memory_total_bytes/, an_instance_of(Hash))

View File

@ -11,6 +11,7 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do
let_it_be(:reporter) { create(:user) }
let_it_be(:stranger) { create(:user) }
let_it_be(:link_filepath) { '/direct/asset/link/path' }
let_it_be(:released_at) { Time.now - 1.day }
let(:params_for_issues_and_mrs) { { scope: 'all', state: 'opened', release_tag: release.tag } }
let(:post_query) { post_graphql(query, current_user: current_user) }
@ -39,6 +40,7 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do
name
createdAt
releasedAt
upcomingRelease
})
end
@ -54,7 +56,8 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do
'descriptionHtml' => release.description_html,
'name' => release.name,
'createdAt' => release.created_at.iso8601,
'releasedAt' => release.released_at.iso8601
'releasedAt' => release.released_at.iso8601,
'upcomingRelease' => false
})
end
end
@ -270,7 +273,7 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do
let_it_be(:project) { create(:project, :repository, :private) }
let_it_be(:milestone_1) { create(:milestone, project: project) }
let_it_be(:milestone_2) { create(:milestone, project: project) }
let_it_be(:release) { create(:release, :with_evidence, project: project, milestones: [milestone_1, milestone_2]) }
let_it_be(:release) { create(:release, :with_evidence, project: project, milestones: [milestone_1, milestone_2], released_at: released_at) }
let_it_be(:release_link_1) { create(:release_link, release: release) }
let_it_be(:release_link_2) { create(:release_link, release: release, filepath: link_filepath) }
@ -311,7 +314,7 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do
let_it_be(:project) { create(:project, :repository, :public) }
let_it_be(:milestone_1) { create(:milestone, project: project) }
let_it_be(:milestone_2) { create(:milestone, project: project) }
let_it_be(:release) { create(:release, :with_evidence, project: project, milestones: [milestone_1, milestone_2]) }
let_it_be(:release) { create(:release, :with_evidence, project: project, milestones: [milestone_1, milestone_2], released_at: released_at) }
let_it_be(:release_link_1) { create(:release_link, release: release) }
let_it_be(:release_link_2) { create(:release_link, release: release, filepath: link_filepath) }
@ -373,4 +376,45 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do
it_behaves_like 'no access to the release field'
end
end
describe 'upcoming release' do
let(:path) { path_prefix }
let(:project) { create(:project, :repository, :private) }
let(:release) { create(:release, :with_evidence, project: project, released_at: released_at) }
let(:current_user) { developer }
let(:release_fields) do
query_graphql_field(%{
releasedAt
upcomingRelease
})
end
before do
project.add_developer(developer)
post_query
end
context 'future release' do
let(:released_at) { Time.now + 1.day }
it 'finds all release data' do
expect(data).to eq({
'releasedAt' => release.released_at.iso8601,
'upcomingRelease' => true
})
end
end
context 'past release' do
let(:released_at) { Time.now - 1.day }
it 'finds all release data' do
expect(data).to eq({
'releasedAt' => release.released_at.iso8601,
'upcomingRelease' => false
})
end
end
end
end

View File

@ -82,6 +82,30 @@ RSpec.describe AlertManagement::CreateAlertIssueService do
expect(user).to have_received(:can?).with(:create_issue, project)
end
context 'with alert severity' do
using RSpec::Parameterized::TableSyntax
where(:alert_severity, :incident_severity) do
'critical' | 'critical'
'high' | 'high'
'medium' | 'medium'
'low' | 'low'
'info' | 'unknown'
'unknown' | 'unknown'
end
with_them do
before do
alert.update!(severity: alert_severity)
execute
end
it 'sets the correct severity level' do
expect(created_issue.severity).to eq(incident_severity)
end
end
end
context 'when the alert is prometheus alert' do
let(:alert) { prometheus_alert }
let(:issue) { subject.payload[:issue] }

View File

@ -39,6 +39,34 @@ RSpec.describe IncidentManagement::Incidents::CreateService do
let(:issue) { new_issue }
end
context 'with default severity' do
it 'sets the correct severity level to "unknown"' do
create_incident
expect(new_issue.severity).to eq(IssuableSeverity::DEFAULT)
end
end
context 'with severity' do
using RSpec::Parameterized::TableSyntax
subject(:create_incident) { described_class.new(project, user, title: title, description: description, severity: severity).execute }
where(:severity, :incident_severity) do
'critical' | 'critical'
'high' | 'high'
'medium' | 'medium'
'low' | 'low'
'unknown' | 'unknown'
end
with_them do
it 'sets the correct severity level' do
create_incident
expect(new_issue.severity).to eq(incident_severity)
end
end
end
context 'when incident label does not exists' do
it 'creates incident label' do
expect { create_incident }.to change { project.labels.where(title: label_title).count }.by(1)