Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-09-04 12:08:27 +00:00
parent 477c2c2604
commit 6724a6ee6b
113 changed files with 893 additions and 714 deletions

View File

@ -687,9 +687,6 @@ Rails/SaveBang:
- 'ee/spec/lib/gitlab/background_migration/fix_orphan_promoted_issues_spec.rb'
- 'ee/spec/lib/gitlab/elastic/search_results_spec.rb'
- 'ee/spec/lib/gitlab/email/handler/ee/service_desk_handler_spec.rb'
- 'ee/spec/lib/gitlab/geo/cron_manager_spec.rb'
- 'ee/spec/lib/gitlab/geo/jwt_request_decoder_spec.rb'
- 'ee/spec/lib/gitlab/geo/oauth/session_spec.rb'
- 'ee/spec/lib/gitlab/geo_spec.rb'
- 'ee/spec/lib/gitlab/git_access_spec.rb'
- 'ee/spec/lib/gitlab/import_export/group/relation_factory_spec.rb'

View File

@ -1,4 +1,5 @@
<script>
import { mapActions } from 'vuex';
import {
GlButton,
GlButtonGroup,
@ -17,6 +18,7 @@ import boardsStore from '../stores/boards_store';
import eventHub from '../eventhub';
import { ListType } from '../constants';
import { isScopedLabel } from '~/lib/utils/common_utils';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
components: {
@ -32,7 +34,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [isWipLimitsOn],
mixins: [isWipLimitsOn, glFeatureFlagMixin()],
props: {
list: {
type: Object,
@ -128,6 +130,7 @@ export default {
},
},
methods: {
...mapActions(['updateList']),
showScopedLabels(label) {
return boardsStore.scopedLabels.enabled && isScopedLabel(label);
},
@ -136,20 +139,28 @@ export default {
eventHub.$emit(`toggle-issue-form-${this.list.id}`);
},
toggleExpanded() {
if (this.list.isExpandable) {
this.list.isExpanded = !this.list.isExpanded;
this.list.isExpanded = !this.list.isExpanded;
if (AccessorUtilities.isLocalStorageAccessSafe() && !this.isLoggedIn) {
localStorage.setItem(`${this.uniqueKey}.expanded`, this.list.isExpanded);
}
if (!this.isLoggedIn) {
this.addToLocalStorage();
} else {
this.updateListFunction();
}
if (this.isLoggedIn) {
this.list.update();
}
// When expanding/collapsing, the tooltip on the caret button sometimes stays open.
// Close all tooltips manually to prevent dangling tooltips.
this.$root.$emit('bv::hide::tooltip');
// When expanding/collapsing, the tooltip on the caret button sometimes stays open.
// Close all tooltips manually to prevent dangling tooltips.
this.$root.$emit('bv::hide::tooltip');
},
addToLocalStorage() {
if (AccessorUtilities.isLocalStorageAccessSafe()) {
localStorage.setItem(`${this.uniqueKey}.expanded`, this.list.isExpanded);
}
},
updateListFunction() {
if (this.glFeatures.boardsWithSwimlanes && this.isSwimlanesHeader) {
this.updateList({ listId: this.list.id, collapsed: !this.list.isExpanded });
} else {
this.list.update();
}
},
},

View File

@ -47,7 +47,7 @@ class List {
this.loading = true;
this.loadingMore = false;
this.issues = obj.issues || [];
this.issuesSize = obj.issuesSize ? obj.issuesSize : 0;
this.issuesSize = obj.issuesSize || obj.issuesCount || 0;
this.maxIssueCount = obj.maxIssueCount || obj.max_issue_count || 0;
if (obj.label) {

View File

@ -4,6 +4,7 @@ fragment BoardListShared on BoardList {
position
listType
collapsed
issuesCount
label {
id
title

View File

@ -0,0 +1,10 @@
#import "./board_list.fragment.graphql"
mutation UpdateBoardList($listId: ID!, $position: Int, $collapsed: Boolean) {
updateBoardList(input: { listId: $listId, position: $position, collapsed: $collapsed }) {
list {
...BoardListFragment
}
errors
}
}

View File

@ -15,6 +15,7 @@ import projectListsIssuesQuery from '../queries/project_lists_issues.query.graph
import projectBoardQuery from '../queries/project_board.query.graphql';
import groupBoardQuery from '../queries/group_board.query.graphql';
import createBoardListMutation from '../queries/board_list_create.mutation.graphql';
import updateBoardListMutation from '../queries/board_list_update.mutation.graphql';
const notImplemented = () => {
/* eslint-disable-next-line @gitlab/require-i18n-strings */
@ -147,8 +148,42 @@ export default {
notImplemented();
},
updateList: () => {
notImplemented();
moveList: ({ state, commit, dispatch }, { listId, newIndex, adjustmentValue }) => {
const { boardLists } = state;
const backupList = [...boardLists];
const movedList = boardLists.find(({ id }) => id === listId);
const newPosition = newIndex - 1;
const listAtNewIndex = boardLists[newIndex];
movedList.position = newPosition;
listAtNewIndex.position += adjustmentValue;
commit(types.MOVE_LIST, {
movedList,
listAtNewIndex,
});
dispatch('updateList', { listId, position: newPosition, backupList });
},
updateList: ({ commit }, { listId, position, collapsed, backupList }) => {
gqlClient
.mutate({
mutation: updateBoardListMutation,
variables: {
listId,
position,
collapsed,
},
})
.then(({ data }) => {
if (data?.updateBoardList?.errors.length) {
commit(types.UPDATE_LIST_FAILURE, backupList);
}
})
.catch(() => {
commit(types.UPDATE_LIST_FAILURE, backupList);
});
},
deleteList: () => {

View File

@ -858,21 +858,6 @@ const boardsStore = {
},
refreshIssueData(issue, obj) {
// issue.id = obj.id;
// issue.iid = obj.iid;
// issue.title = obj.title;
// issue.confidential = obj.confidential;
// issue.dueDate = obj.due_date || obj.dueDate;
// issue.sidebarInfoEndpoint = obj.issue_sidebar_endpoint;
// issue.referencePath = obj.reference_path || obj.referencePath;
// issue.path = obj.real_path || obj.webUrl;
// issue.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint;
// issue.project_id = obj.project_id;
// issue.timeEstimate = obj.time_estimate || obj.timeEstimate;
// issue.assignableLabelsEndpoint = obj.assignable_labels_endpoint;
// issue.blocked = obj.blocked;
// issue.epic = obj.epic;
const convertedObj = convertObjectPropsToCamelCase(obj, {
dropKeys: ['issue_sidebar_endpoint', 'real_path', 'webUrl'],
});

View File

@ -7,9 +7,8 @@ export const SHOW_PROMOTION_LIST = 'SHOW_PROMOTION_LIST';
export const REQUEST_ADD_LIST = 'REQUEST_ADD_LIST';
export const RECEIVE_ADD_LIST_SUCCESS = 'RECEIVE_ADD_LIST_SUCCESS';
export const RECEIVE_ADD_LIST_ERROR = 'RECEIVE_ADD_LIST_ERROR';
export const REQUEST_UPDATE_LIST = 'REQUEST_UPDATE_LIST';
export const RECEIVE_UPDATE_LIST_SUCCESS = 'RECEIVE_UPDATE_LIST_SUCCESS';
export const RECEIVE_UPDATE_LIST_ERROR = 'RECEIVE_UPDATE_LIST_ERROR';
export const MOVE_LIST = 'MOVE_LIST';
export const UPDATE_LIST_FAILURE = 'UPDATE_LIST_FAILURE';
export const REQUEST_REMOVE_LIST = 'REQUEST_REMOVE_LIST';
export const RECEIVE_REMOVE_LIST_SUCCESS = 'RECEIVE_REMOVE_LIST_SUCCESS';
export const RECEIVE_REMOVE_LIST_ERROR = 'RECEIVE_REMOVE_LIST_ERROR';

View File

@ -1,3 +1,5 @@
import Vue from 'vue';
import { sortBy } from 'lodash';
import * as mutationTypes from './mutation_types';
import { __ } from '~/locale';
@ -44,16 +46,17 @@ export default {
notImplemented();
},
[mutationTypes.REQUEST_UPDATE_LIST]: () => {
notImplemented();
[mutationTypes.MOVE_LIST]: (state, { movedList, listAtNewIndex }) => {
const { boardLists } = state;
const movedListIndex = state.boardLists.findIndex(l => l.id === movedList.id);
Vue.set(boardLists, movedListIndex, movedList);
Vue.set(boardLists, movedListIndex.position + 1, listAtNewIndex);
Vue.set(state, 'boardLists', sortBy(boardLists, 'position'));
},
[mutationTypes.RECEIVE_UPDATE_LIST_SUCCESS]: () => {
notImplemented();
},
[mutationTypes.RECEIVE_UPDATE_LIST_ERROR]: () => {
notImplemented();
[mutationTypes.UPDATE_LIST_FAILURE]: (state, backupList) => {
state.error = __('An error occurred while updating the list. Please try again.');
Vue.set(state, 'boardLists', backupList);
},
[mutationTypes.REQUEST_REMOVE_LIST]: () => {

View File

@ -14,3 +14,40 @@ export const serializeForm = form => {
return serializeFormEntries(entries);
};
/**
* Check if the value provided is empty or not
*
* It is being used to check if a form input
* value has been set or not
*
* @param {String, Number, Array} - Any form value
* @returns {Boolean} - returns false if a value is set
*
* @example
* returns true for '', [], null, undefined
*/
export const isEmptyValue = value => value == null || value.length === 0;
/**
* A form object serializer
*
* @param {Object} - Form Object
* @returns {Object} - Serialized Form Object
*
* @example
* Input
* {"project": {"value": "hello", "state": false}, "username": {"value": "john"}}
*
* Returns
* {"project": "hello", "username": "john"}
*/
export const serializeFormObject = form =>
Object.fromEntries(
Object.entries(form).reduce((acc, [name, { value }]) => {
if (!isEmptyValue(value)) {
acc.push([name, value]);
}
return acc;
}, []),
);

View File

@ -20,7 +20,6 @@ import { localTimeAgo } from './lib/utils/datetime_utility';
import { getLocationHash, visitUrl } from './lib/utils/url_utility';
// everything else
import loadAwardsHandler from './awards_handler';
import { deprecatedCreateFlash as Flash, removeFlashClickListener } from './flash';
import initTodoToggle from './header';
import initImporterStatus from './importer_status';
@ -37,7 +36,7 @@ import GlFieldErrors from './gl_field_errors';
import initUserPopovers from './user_popovers';
import initBroadcastNotifications from './broadcast_notification';
import initPersistentUserCallouts from './persistent_user_callouts';
import { initUserTracking } from './tracking';
import { initUserTracking, initDefaultTrackers } from './tracking';
import { __ } from './locale';
import 'ee_else_ce/main_ee';
@ -111,6 +110,7 @@ function deferredInitialisation() {
initBroadcastNotifications();
initFrequentItemDropdowns();
initPersistentUserCallouts();
initDefaultTrackers();
if (document.querySelector('.search')) initSearchAutocomplete();
@ -154,8 +154,6 @@ function deferredInitialisation() {
viewport: '.layout-page',
});
loadAwardsHandler();
// Adding a helper class to activate animations only after all is rendered
setTimeout(() => $body.addClass('page-initialised'), 1000);
}

View File

@ -1,10 +1,10 @@
<script>
import { GlLoadingIcon } from '@gitlab/ui';
import { GlButton } from '@gitlab/ui';
export default {
name: 'ResolveDiscussionButton',
components: {
GlLoadingIcon,
GlButton,
},
props: {
isResolving: {
@ -21,8 +21,7 @@ export default {
</script>
<template>
<button ref="button" type="button" class="btn btn-default ml-sm-2" @click="$emit('onClick')">
<gl-loading-icon v-if="isResolving" ref="isResolvingIcon" inline />
<gl-button :loading="isResolving" class="ml-sm-2" @click="$emit('onClick')">
{{ buttonTitle }}
</button>
</gl-button>
</template>

View File

@ -14,6 +14,7 @@ import axios from '~/lib/utils/axios_utils';
import syntaxHighlight from '~/syntax_highlight';
import flash from '~/flash';
import { __ } from '~/locale';
import loadAwardsHandler from '~/awards_handler';
document.addEventListener('DOMContentLoaded', () => {
const hasPerfBar = document.querySelector('.with-performance-bar');
@ -48,4 +49,5 @@ document.addEventListener('DOMContentLoaded', () => {
} else {
new Diff();
}
loadAwardsHandler();
});

View File

@ -1,3 +1,4 @@
import loadAwardsHandler from '~/awards_handler';
import initIssuableSidebar from '~/init_issuable_sidebar';
import Issue from '~/issue';
import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
@ -37,4 +38,6 @@ export default function() {
} else {
initIssuableSidebar();
}
loadAwardsHandler();
}

View File

@ -6,6 +6,7 @@ import howToMerge from '~/how_to_merge';
import initPipelines from '~/commit/pipelines/pipelines_bundle';
import initVueIssuableSidebarApp from '~/issuable_sidebar/sidebar_bundle';
import initSourcegraph from '~/sourcegraph';
import loadAwardsHandler from '~/awards_handler';
export default function() {
new ZenMode(); // eslint-disable-line no-new
@ -19,4 +20,5 @@ export default function() {
handleLocationHash();
howToMerge();
initSourcegraph();
loadAwardsHandler();
}

View File

@ -272,6 +272,7 @@ export default {
<ci-badge
:status="pipelineStatus"
:show-text="!isChildView"
:icon-classes="'gl-vertical-align-middle!'"
data-qa-selector="pipeline_commit_status"
/>
</div>

View File

@ -4,6 +4,7 @@ import ZenMode from '~/zen_mode';
import initNotes from '~/init_notes';
import snippetEmbed from '~/snippet/snippet_embed';
import { SnippetShowInit } from '~/snippets';
import loadAwardsHandler from '~/awards_handler';
document.addEventListener('DOMContentLoaded', () => {
if (!gon.features.snippetsVue) {
@ -16,4 +17,5 @@ document.addEventListener('DOMContentLoaded', () => {
SnippetShowInit();
initNotes();
}
loadAwardsHandler();
});

View File

@ -126,14 +126,18 @@ export function initUserTracking() {
const opts = { ...DEFAULT_SNOWPLOW_OPTIONS, ...window.snowplowOptions };
window.snowplow('newTracker', opts.namespace, opts.hostname, opts);
document.dispatchEvent(new Event('SnowplowInitialized'));
}
export function initDefaultTrackers() {
if (!Tracking.enabled()) return;
window.snowplow('enableActivityTracking', 30, 30);
window.snowplow('trackPageView'); // must be after enableActivityTracking
if (opts.formTracking) window.snowplow('enableFormTracking');
if (opts.linkClickTracking) window.snowplow('enableLinkClickTracking');
if (window.snowplowOptions.formTracking) window.snowplow('enableFormTracking');
if (window.snowplowOptions.linkClickTracking) window.snowplow('enableLinkClickTracking');
Tracking.bindDocument();
Tracking.trackLoadEvents();
document.dispatchEvent(new Event('SnowplowInitialized'));
}

View File

@ -39,6 +39,11 @@ export default {
required: false,
default: true,
},
iconClasses: {
type: String,
required: false,
default: '',
},
},
computed: {
cssClass() {
@ -55,7 +60,7 @@ export default {
:class="cssClass"
:title="!showText ? status.text : ''"
>
<ci-icon :status="status" />
<ci-icon :status="status" :css-classes="iconClasses" />
<template v-if="showText">
{{ status.text }}

View File

@ -1,10 +1,13 @@
<script>
import { mapState, mapActions } from 'vuex';
import { GlDrawer } from '@gitlab/ui';
import { GlDrawer, GlBadge, GlIcon, GlLink } from '@gitlab/ui';
export default {
components: {
GlDrawer,
GlBadge,
GlIcon,
GlLink,
},
props: {
features: {
@ -37,16 +40,31 @@ export default {
<div>
<gl-drawer class="mt-6" :open="open" @close="closeDrawer">
<template #header>
<h4>{{ __("What's new at GitLab") }}</h4>
</template>
<template>
<ul>
<li v-for="feature in parsedFeatures" :key="feature.title">
<h5>{{ feature.title }}</h5>
<p>{{ feature.body }}</p>
</li>
</ul>
<h4 class="page-title my-2">{{ __("What's new at GitLab") }}</h4>
</template>
<div class="pb-6">
<div v-for="feature in parsedFeatures" :key="feature.title" class="mb-6">
<gl-link :href="feature.url" target="_blank">
<h5 class="gl-font-base">{{ feature.title }}</h5>
</gl-link>
<div class="mb-2">
<template v-for="package_name in feature.packages">
<gl-badge :key="package_name" size="sm" class="whats-new-item-badge mr-1">
<gl-icon name="license" />{{ package_name }}
</gl-badge>
</template>
</div>
<gl-link :href="feature.url" target="_blank">
<img
:alt="feature.title"
:src="feature.image_url"
class="img-thumbnail px-6 py-2 whats-new-item-image"
/>
</gl-link>
<p class="pt-2">{{ feature.body }}</p>
<gl-link :href="feature.url" target="_blank">{{ __('Learn more') }}</gl-link>
</div>
</div>
</gl-drawer>
</div>
</template>

View File

@ -0,0 +1,9 @@
.gl-badge.whats-new-item-badge {
background-color: $purple-light;
color: $purple;
font-weight: bold;
}
.whats-new-item-image {
border-color: $gray-50;
}

View File

@ -116,7 +116,6 @@
.board-title {
flex-direction: column;
height: 100%;
}
.board-title-caret {

View File

@ -42,9 +42,6 @@ class ProjectsController < Projects::ApplicationController
before_action only: [:edit] do
push_frontend_feature_flag(:service_desk_custom_address, @project)
end
before_action only: [:edit] do
push_frontend_feature_flag(:approval_suggestions, @project)
end

View File

@ -24,28 +24,20 @@ module Storage
parent_full_path = parent&.full_path
Gitlab::UploadsTransfer.new.move_namespace(path, former_parent_full_path, parent_full_path)
if ::Feature.enabled?(:async_pages_move_namespace_transfer, self)
if any_project_with_pages_deployed?
run_after_commit do
Gitlab::PagesTransfer.new.async.move_namespace(path, former_parent_full_path, parent_full_path)
end
if any_project_with_pages_deployed?
run_after_commit do
Gitlab::PagesTransfer.new.async.move_namespace(path, former_parent_full_path, parent_full_path)
end
else
Gitlab::PagesTransfer.new.move_namespace(path, former_parent_full_path, parent_full_path)
end
else
Gitlab::UploadsTransfer.new.rename_namespace(full_path_before_last_save, full_path)
if ::Feature.enabled?(:async_pages_move_namespace_rename, self)
if any_project_with_pages_deployed?
full_path_was = full_path_before_last_save
if any_project_with_pages_deployed?
full_path_was = full_path_before_last_save
run_after_commit do
Gitlab::PagesTransfer.new.async.rename_namespace(full_path_was, full_path)
end
run_after_commit do
Gitlab::PagesTransfer.new.async.rename_namespace(full_path_was, full_path)
end
else
Gitlab::PagesTransfer.new.rename_namespace(full_path_before_last_save, full_path)
end
end

View File

@ -22,11 +22,7 @@ module Ci
return result unless result[:status] == :success
headers = JobArtifactUploader.workhorse_authorize(has_length: false, maximum_size: max_size(artifact_type))
if lsif?(artifact_type)
headers[:ProcessLsif] = true
headers[:ProcessLsifReferences] = Feature.enabled?(:code_navigation_references, project, default_enabled: true)
end
headers[:ProcessLsif] = lsif?(artifact_type)
success(headers: headers)
end

View File

@ -3,11 +3,7 @@
module Pages
class DeleteService < BaseService
def execute
if Feature.enabled?(:async_pages_removal, project)
PagesRemoveWorker.perform_async(project.id)
else
PagesRemoveWorker.new.perform(project.id)
end
PagesRemoveWorker.perform_async(project.id)
end
end
end

View File

@ -96,24 +96,18 @@ module Projects
.rename_project(path_before, project_path, namespace_full_path)
end
if ::Feature.enabled?(:async_pages_move_project_rename, project)
if project.pages_deployed?
# Block will be evaluated in the context of project so we need
# to bind to a local variable to capture it, as the instance
# variable and method aren't available on Project
path_before_local = @path_before
if project.pages_deployed?
# Block will be evaluated in the context of project so we need
# to bind to a local variable to capture it, as the instance
# variable and method aren't available on Project
path_before_local = @path_before
project.run_after_commit_or_now do
Gitlab::PagesTransfer
.new
.async
.rename_project(path_before_local, path, namespace.full_path)
end
project.run_after_commit_or_now do
Gitlab::PagesTransfer
.new
.async
.rename_project(path_before_local, path, namespace.full_path)
end
else
Gitlab::PagesTransfer
.new
.rename_project(path_before, project_path, namespace_full_path)
end
end

View File

@ -181,15 +181,9 @@ module Projects
end
def move_pages(project)
transfer = Gitlab::PagesTransfer.new
if Feature.enabled?(:async_pages_move_project_transfer, project)
# Avoid scheduling moves for directories that don't exist.
return unless project.pages_deployed?
transfer = transfer.async
end
return unless project.pages_deployed?
transfer = Gitlab::PagesTransfer.new.async
transfer.move_project(project.path, @old_namespace.full_path, @new_namespace.full_path)
end

View File

@ -13,43 +13,42 @@
= render partial: 'flash_messages'
%div{ class: [("limit-container-width" unless fluid_layout)] }
= render_if_exists 'trials/banner', namespace: @group
= render_if_exists 'trials/banner', namespace: @group
= render 'groups/home_panel'
= render 'groups/home_panel'
= render_if_exists 'groups/self_or_ancestor_marked_for_deletion_notice', group: @group
= render_if_exists 'groups/self_or_ancestor_marked_for_deletion_notice', group: @group
= render_if_exists 'groups/group_activity_analytics', group: @group
= render_if_exists 'groups/group_activity_analytics', group: @group
.groups-listing{ data: { endpoints: { default: group_children_path(@group, format: :json), shared: group_shared_projects_path(@group, format: :json) } } }
.top-area.group-nav-container.justify-content-between
.scrolling-tabs-container.inner-page-scroll-tabs
.fade-left= sprite_icon('chevron-lg-left', size: 12)
.fade-right= sprite_icon('chevron-lg-right', size: 12)
%ul.nav-links.scrolling-tabs.mobile-separator.nav.nav-tabs
%li.js-subgroups_and_projects-tab
= link_to group_path, data: { target: 'div#subgroups_and_projects', action: 'subgroups_and_projects', toggle: 'tab'} do
= _("Subgroups and projects")
%li.js-shared-tab
= link_to group_shared_path, data: { target: 'div#shared', action: 'shared', toggle: 'tab'} do
= _("Shared projects")
%li.js-archived-tab
= link_to group_archived_path, data: { target: 'div#archived', action: 'archived', toggle: 'tab'} do
= _("Archived projects")
.groups-listing{ data: { endpoints: { default: group_children_path(@group, format: :json), shared: group_shared_projects_path(@group, format: :json) } } }
.top-area.group-nav-container.justify-content-between
.scrolling-tabs-container.inner-page-scroll-tabs
.fade-left= sprite_icon('chevron-lg-left', size: 12)
.fade-right= sprite_icon('chevron-lg-right', size: 12)
%ul.nav-links.scrolling-tabs.mobile-separator.nav.nav-tabs
%li.js-subgroups_and_projects-tab
= link_to group_path, data: { target: 'div#subgroups_and_projects', action: 'subgroups_and_projects', toggle: 'tab'} do
= _("Subgroups and projects")
%li.js-shared-tab
= link_to group_shared_path, data: { target: 'div#shared', action: 'shared', toggle: 'tab'} do
= _("Shared projects")
%li.js-archived-tab
= link_to group_archived_path, data: { target: 'div#archived', action: 'archived', toggle: 'tab'} do
= _("Archived projects")
.nav-controls.d-block.d-md-flex
.group-search
= render "shared/groups/search_form"
.nav-controls.d-block.d-md-flex
.group-search
= render "shared/groups/search_form"
= render "shared/groups/dropdown", options_hash: subgroups_sort_options_hash
= render "shared/groups/dropdown", options_hash: subgroups_sort_options_hash
.tab-content
#subgroups_and_projects.tab-pane
= render "subgroups_and_projects", group: @group
.tab-content
#subgroups_and_projects.tab-pane
= render "subgroups_and_projects", group: @group
#shared.tab-pane
= render "shared_projects", group: @group
#shared.tab-pane
= render "shared_projects", group: @group
#archived.tab-pane
= render "archived_projects", group: @group
#archived.tab-pane
= render "archived_projects", group: @group

View File

@ -20,7 +20,7 @@
.flash-text
.loading-metrics.js-loading-custom-metrics
%p.m-3
= icon('spinner spin', class: 'metrics-load-spinner')
= loading_icon(css_class: 'metrics-load-spinner')
= s_('PrometheusService|Finding custom metrics...')
.empty-metrics.hidden.js-empty-custom-metrics
%p.text-tertiary.m-3.js-no-active-integration-text.hidden

View File

@ -16,7 +16,7 @@
.card-body
.loading-metrics.js-loading-metrics
%p.m-3
= icon('spinner spin', class: 'metrics-load-spinner')
= loading_icon(css_class: 'metrics-load-spinner')
= s_('PrometheusService|Finding and configuring metrics...')
.empty-metrics.hidden.js-empty-metrics
%p.text-tertiary.m-3

View File

@ -21,6 +21,10 @@
#js-suggestions{ data: { project_path: @project.full_path } }
= render 'shared/form_elements/apply_template_warning'
- if issuable.is_a?(Issuable) && @issue
= render 'shared/issuable/form/type_selector', issuable: issuable, form: form, type: @issue[:issue_type]
= render 'shared/form_elements/description', model: issuable, form: form, project: project
- if issuable.respond_to?(:confidential)

View File

@ -14,11 +14,13 @@
= render_if_exists "shared/issuable/form/epic", issuable: issuable, form: form, project: project
.form-group.row.issue-milestone
= form.label :milestone_id, "Milestone", class: "col-form-label #{has_due_date ? "col-md-2 col-lg-4" : "col-sm-2"}"
.col-sm-10{ class: ("col-md-8" if has_due_date) }
.issuable-form-select-holder
= render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, show_started: false, extra_class: "qa-issuable-milestone-dropdown js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone"
- if issuable.supports_milestone?
.form-group.row.issue-milestone
= form.label :milestone_id, "Milestone", class: "col-form-label #{has_due_date ? "col-md-2 col-lg-4" : "col-sm-2"}"
.col-sm-10{ class: ("col-md-8" if has_due_date) }
.issuable-form-select-holder
= render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, show_started: false, extra_class: "qa-issuable-milestone-dropdown js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone"
.form-group.row
= form.label :label_ids, "Labels", class: "col-form-label #{has_due_date ? "col-md-2 col-lg-4" : "col-sm-2"}"
= form.hidden_field :label_ids, multiple: true, value: ''
@ -30,7 +32,8 @@
- if has_due_date || issuable.supports_weight?
.col-lg-6
= render_if_exists "shared/issuable/form/weight", issuable: issuable, form: form
- if @issue[:issue_type] != 'incident'
= render_if_exists "shared/issuable/form/weight", issuable: issuable, form: form
.form-group.row
= form.label :due_date, "Due date", class: "col-form-label col-md-2 col-lg-4"
.col-8

View File

@ -0,0 +1,22 @@
.form-group.row.gl-mb-0
= form.label :type, 'Type', class: 'col-form-label col-sm-2'
.col-sm-10
.issuable-form-select-holder.selectbox.form-group
.dropdown.js-issuable-type-filter-dropdown-wrap
%button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
%span.dropdown-label
= type.capitalize || _("Select type")
= icon('chevron-down')
.dropdown-menu.dropdown-menu-selectable.dropdown-select
.dropdown-title
= _("Select type")
%button.dropdown-title-button.dropdown-menu-close.gl-mt-3
= icon('times', class: 'dropdown-menu-close-icon', 'aria-hidden' => 'true')
.dropdown-content
%ul
%li.js-filter-issuable-type
= link_to new_project_issue_path(@project), class: ("is-active" if type === 'issue') do
= _("Issue")
%li.js-filter-issuable-type
= link_to new_project_issue_path(@project, { 'issue[issue_type]': 'incident', issuable_template: 'incident' }), class: ("is-active" if type === 'incident') do
= _("Incident")

View File

@ -0,0 +1,5 @@
---
title: Migrate '.fa-spinner' to '.spinner' for 'app/views/projects/services/prometheus'
merge_request: 41126
author: Gilang Gumilar
type: changed

View File

@ -0,0 +1,5 @@
---
title: Defer (certain) parts of setting up snowplow telemetry
merge_request: 40299
author:
type: performance

View File

@ -0,0 +1,5 @@
---
title: Add type selector dropdown to new issue form
merge_request: 40981
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Remove the async pages feature flags
merge_request: 40980
author:
type: performance

View File

@ -0,0 +1,5 @@
---
title: Drop one of duplicated limit-container-width classname
merge_request: 41251
author: Takuya Noguchi
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Update pipeline button SVG to be center aligned
merge_request: 41066
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Fix Rails/SaveBang offenses for ee/spec/lib/gitlab/geo/*
merge_request: 41338
author: Rajendra Kadam
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Update dependency vuex to ^3.5.1
merge_request: 39201
author:
type: other

View File

@ -1,7 +0,0 @@
---
name: async_pages_move_namespace_rename
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40259
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/235808
group: team::Scalability
type: development
default_enabled: false

View File

@ -1,7 +0,0 @@
---
name: async_pages_move_namespace_transfer
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40259
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/235808
group: team::Scalability
type: development
default_enabled: false

View File

@ -1,7 +0,0 @@
---
name: async_pages_move_project_rename
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40087
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/235802
group: team::Scalability
type: development
default_enabled: false

View File

@ -1,7 +0,0 @@
---
name: async_pages_move_project_transfer
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40492
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/235757
group: team::Scalability
type: development
default_enabled: false

View File

@ -1,7 +0,0 @@
---
name: async_pages_removal
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38313
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/239318
group: team::Scalability
type: development
default_enabled: false

View File

@ -59,7 +59,7 @@ if gitlab.mr_labels.include?('database') || db_paths_to_review.any?
markdown(DB_MESSAGE)
markdown(DB_FILES_MESSAGE + helper.markdown_list(db_paths_to_review)) if db_paths_to_review.any?
unless has_database_scoped_labels?(gitlab.mr_labels)
unless helper.has_database_scoped_labels?(gitlab.mr_labels)
gitlab.api.update_merge_request(gitlab.mr_json['project_id'],
gitlab.mr_json['iid'],
add_labels: 'database::review pending')

View File

@ -4,7 +4,7 @@
stage: Plan
self-managed: true
gitlab-com: true
packages: ultimate, gold
packages: [Ultimate, Gold]
url: https://docs.gitlab.com/ee/user/project/requirements/index.html
image_url:
published_at: 2020-04-22
@ -14,7 +14,7 @@
stage: Release
self-managed: true
gitlab-com: true
packages: All
packages: [All]
url: https://docs.gitlab.com/ee/ci/examples/authenticating-with-hashicorp-vault/index.html
image_url: https://about.gitlab.com/images/12_10/jwt-vault-1.png
published_at: 2020-04-22
@ -24,7 +24,7 @@
stage: Plan
self-managed: true
gitlab-com: true
packages: ultimate, gold
packages: [Ultimate, Gold]
url: https://docs.gitlab.com/ee/user/project/issues/index.html#health-status-ultimate
image_url: https://about.gitlab.com/images/12_10/epic-health-status.png
published_at: 2020-04-22
@ -34,7 +34,7 @@
stage: Plan
self-managed: true
gitlab-com: true
packages: All
packages: [All]
url: https://docs.gitlab.com/ee/user/project/import/jira.html
image_url: https://about.gitlab.com/images/12_10/jira-importer.png
published_at: 2020-04-22
@ -44,7 +44,7 @@
stage: Verify
self-managed: true
gitlab-com: false
packages: core, starter, premium, ultimate
packages: [Core, Starter, Premium, Ultimate]
url: https://gitlab.com/gitlab-org/ci-cd/custom-executor-drivers/fargate/-/blob/master/docs/README.md
image_url: https://about.gitlab.com/images/12_9/autoscaling_ci_fargate.png
published_at: 2020-04-22

View File

@ -4,7 +4,7 @@
stage: Create
self-managed: true
gitlab-com: false
packages: core, starter, premium, ultimate
packages: [Core, Starter, Premium, Ultimate]
url: https://docs.gitlab.com/ee/administration/gitaly/praefect.html
image_url: https://about.gitlab.com/images/13_0/praefect-architecture.png
published_at: 2020-05-22
@ -14,7 +14,7 @@
stage: Release
self-managed: true
gitlab-com: true
packages: All
packages: [All]
url: https://docs.gitlab.com/ee/topics/autodevops/index.html#aws-ecs
image_url:
published_at: 2020-05-22
@ -24,7 +24,7 @@
stage: Plan
self-managed: true
gitlab-com: true
packages: ultimate, gold
packages: [Ultimate, Gold]
url: https://docs.gitlab.com/ee/user/group/roadmap/
image_url: https://about.gitlab.com/images/13_0/Expand-Epic-Hierarchy-Roadmap_roadmap.png
published_at: 2020-05-22
@ -34,7 +34,7 @@
stage: Create
self-managed: true
gitlab-com: true
packages: All
packages: [All]
url: https://docs.gitlab.com/ee/user/snippets.html#versioned-snippets
image_url: https://about.gitlab.com/images/13_0/phikai-versioned-snippets.png
published_at: 2020-05-22
@ -44,7 +44,7 @@
stage: Create
self-managed: true
gitlab-com: true
packages: All
packages: [All]
url: https://docs.gitlab.com/ee/user/project/web_ide/#themes
image_url: https://about.gitlab.com/images/13_0/phikai-web-ide-dark-theme.png
published_at: 2020-05-22

View File

@ -4,7 +4,7 @@
stage: Monitor
self-managed: true
gitlab-com: true
packages: All
packages: [All]
url: https://docs.gitlab.com/ee/user/project/operations/alert_management.html
image_url: https://about.gitlab.com/images/13_1/alert_management.png
published_at: 2020-06-22
@ -14,7 +14,7 @@
stage: Verify
self-managed: true
gitlab-com: true
packages: All
packages: [All]
url: https://docs.gitlab.com/ee/user/project/merge_requests/accessibility_testing.html#accessibility-merge-request-widget
image_url: https://about.gitlab.com/images/13_1/a11y-merge-request-widget.png
published_at: 2020-06-22
@ -24,7 +24,7 @@
stage: Create
self-managed: true
gitlab-com: true
packages: All
packages: [All]
url: https://docs.gitlab.com/ee/user/project/issues/design_management.html#resolve-design-threads
image_url: https://about.gitlab.com/images/13_1/resolve-design-comment.gif
published_at: 2020-06-22
@ -34,7 +34,7 @@
stage: Create
self-managed: true
gitlab-com: true
packages: All
packages: [All]
url: https://docs.gitlab.com/ee/user/discussions/index.html#merge-request-reviews
image_url: https://about.gitlab.com/images/13_1/batch_comments.png
published_at: 2020-06-22

View File

@ -4,7 +4,7 @@
stage: Plan
self-managed: true
gitlab-com: true
packages: starter, premium, ultimate
packages: [Starter, Premium, Ultimate]
url: https://www.youtube.com/watch?v=31pNKjenlJY&feature=emb_title
image_url: http://i3.ytimg.com/vi/31pNKjenlJY/maxresdefault.jpg
published_at: 2020-07-22
@ -14,7 +14,7 @@
stage: Defend
self-managed: true
gitlab-com: true
packages: All
packages: [All]
url: https://www.youtube.com/watch?v=WxBzBz76FxU&feature=youtu.be
image_url: http://i3.ytimg.com/vi/WxBzBz76FxU/hqdefault.jpg
published_at: 2020-07-22
@ -24,7 +24,7 @@
stage: Create
self-managed: false
gitlab-com: true
packages: All
packages: [All]
url: https://docs.gitlab.com/ee/user/project/issues/design_management.html#gitlab-figma-plugin
image_url: https://about.gitlab.com/images/13_2/figma-plugin.png
published_at: 2020-07-22
@ -34,7 +34,7 @@
stage: Monitor
self-managed: true
gitlab-com: true
packages: All
packages: [All]
url: https://docs.gitlab.com/ee/user/project/clusters/#visualizing-cluster-health
image_url: https://about.gitlab.com/images/13_2/k8s_cluster_monitoring.png
published_at: 2020-07-22

View File

@ -11352,6 +11352,16 @@ type Project {
last: Int
): DastScannerProfileConnection
"""
DAST Site Profile associated with the project
"""
dastSiteProfile(
"""
ID of the site profile
"""
id: DastSiteProfileID!
): DastSiteProfile
"""
DAST Site Profiles associated with the project
"""

View File

@ -33932,6 +33932,33 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "dastSiteProfile",
"description": "DAST Site Profile associated with the project",
"args": [
{
"name": "id",
"description": "ID of the site profile",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "DastSiteProfileID",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "DastSiteProfile",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "dastSiteProfiles",
"description": "DAST Site Profiles associated with the project",

View File

@ -1737,6 +1737,7 @@ Autogenerated return type of PipelineRetry
| `containerExpirationPolicy` | ContainerExpirationPolicy | The container expiration policy of the project |
| `containerRegistryEnabled` | Boolean | Indicates if the project stores Docker container images in a container registry |
| `createdAt` | Time | Timestamp of the project creation |
| `dastSiteProfile` | DastSiteProfile | DAST Site Profile associated with the project |
| `description` | String | Short description of the project |
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
| `environment` | Environment | A single environment of the project |

View File

@ -776,7 +776,7 @@ POST /user/keys
Parameters:
- `title` (required) - new SSH Key's title
- `title` (required) - new SSH key's title
- `key` (required) - new SSH key
- `expires_at` (optional) - The expiration date of the SSH key in ISO 8601 format (`YYYY-MM-DDTHH:MM:SSZ`)
@ -815,7 +815,7 @@ POST /users/:id/keys
Parameters:
- `id` (required) - ID of specified user
- `title` (required) - new SSH Key's title
- `title` (required) - new SSH key's title
- `key` (required) - new SSH key
- `expires_at` (optional) - The expiration date of the SSH key in ISO 8601 format (`YYYY-MM-DDTHH:MM:SSZ`)

View File

@ -1,11 +1,11 @@
# GitLab development style guides
See below the relevant style guides, guidelines, linting, and other information for developing GitLab.
See below for the relevant style guides, guidelines, linting, and other information for developing GitLab.
## JavaScript style guide
We use `eslint` to enforce our [JavaScript style guides](javascript.md). Our guide is based on
the excellent [AirBnB](https://github.com/airbnb/javascript) style guide with a few small
the excellent [Airbnb](https://github.com/airbnb/javascript) style guide with a few small
changes.
## SCSS style guide

View File

@ -711,7 +711,8 @@ If your namespace shows `N/A` as the total storage usage, you can trigger a reca
#### Group push rules **(STARTER)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/34370) in [GitLab Starter](https://about.gitlab.com/pricing/) 12.8.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/34370) in [GitLab Starter](https://about.gitlab.com/pricing/) 12.8.
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/224129) in GitLab 13.4.
Group push rules allow group maintainers to set
[push rules](../../push_rules/push_rules.md) for newly created projects within the specific group.
@ -724,14 +725,6 @@ When set, new subgroups have push rules set for them based on either:
- The closest parent group with push rules defined.
- Push rules set at the instance level, if no parent groups have push rules defined.
##### Enabling the feature
This feature comes with the `:group_push_rules` feature flag disabled by default. It can be enabled for specific group using feature flag [API endpoint](../../api/features.md#set-or-create-a-feature) or by GitLab administrator with Rails console access by running:
```ruby
Feature.enable(:group_push_rules)
```
### Maximum artifacts size **(CORE ONLY)**
For information about setting a maximum artifact size for a group, see

View File

@ -39,38 +39,18 @@ The generated LSIF file must be less than 170MiB.
After the job succeeds, code intelligence data can be viewed while browsing the code:
![Code intelligence](img/code_intelligence_v13_1.png)
![Code intelligence](img/code_intelligence_v13_4.png)
## Find references
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/217392) in GitLab 13.2.
> - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/225621) on GitLab 13.3.
> - It's enabled on GitLab.com.
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/235735) in GitLab 13.4.
To find where a particular object is being used, you can see links to specific lines of code
under the **References** tab:
![Find references](img/code_intelligence_find_references_v13_3.png)
### Enable or disable find references
Find references is under development but ready for production use.
It is deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
can opt to disable it for your instance.
To disable it:
```ruby
Feature.disable(:code_navigation_references)
```
To enable it:
```ruby
Feature.enable(:code_navigation_references)
```
## Language support
Generating an LSIF file requires a language server indexer implementation for the

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -12,7 +12,7 @@ module Gitlab
:seeds_block, :variables_attributes, :push_options,
:chat_data, :allow_mirror_update, :bridge, :content, :dry_run,
# These attributes are set by Chains during processing:
:config_content, :config_processor, :stage_seeds
:config_content, :yaml_processor_result, :stage_seeds
) do
include Gitlab::Utils::StrongMemoize

View File

@ -23,7 +23,7 @@ module Gitlab
add_warnings_to_pipeline(result.warnings)
if result.valid?
@command.config_processor = result
@command.yaml_processor_result = result
else
error(result.errors.first, config_error: true)
end

View File

@ -39,7 +39,7 @@ module Gitlab
end
def workflow_config
@command.config_processor.workflow_attributes || {}
@command.yaml_processor_result.workflow_attributes || {}
end
end
end

View File

@ -6,13 +6,13 @@ module Gitlab
module Chain
class RemoveUnwantedChatJobs < Chain::Base
def perform!
raise ArgumentError, 'missing config processor' unless @command.config_processor
raise ArgumentError, 'missing YAML processor result' unless @command.yaml_processor_result
return unless pipeline.chat?
# When scheduling a chat pipeline we only want to run the build
# that matches the chat command.
@command.config_processor.jobs.select! do |name, _|
@command.yaml_processor_result.jobs.select! do |name, _|
name.to_s == command.chat_data[:command].to_s
end
end

View File

@ -9,7 +9,7 @@ module Gitlab
include Gitlab::Utils::StrongMemoize
def perform!
raise ArgumentError, 'missing config processor' unless @command.config_processor
raise ArgumentError, 'missing YAML processor result' unless @command.yaml_processor_result
# Allocate next IID. This operation must be outside of transactions of pipeline creations.
pipeline.ensure_project_iid!
@ -56,7 +56,7 @@ module Gitlab
end
def stages_attributes
@command.config_processor.stages_attributes
@command.yaml_processor_result.stages_attributes
end
end
end

View File

@ -51,7 +51,7 @@ module Gitlab
def validate_service_request
Gitlab::HTTP.post(
validation_service_url, timeout: VALIDATION_REQUEST_TIMEOUT,
body: validation_service_payload(@pipeline, @command.config_processor.stages_attributes)
body: validation_service_payload(@pipeline, @command.yaml_processor_result.stages_attributes)
)
end

View File

@ -192,6 +192,7 @@ module Gitlab
# Files that don't fit into any category are marked with :none
%r{\A(ee/)?changelogs/} => :none,
%r{\Alocale/gitlab\.pot\z} => :none,
%r{\Adata/whats_new/} => :none,
# Fallbacks in case the above patterns miss anything
%r{\.rb\z} => :backend,

View File

@ -1162,7 +1162,7 @@ msgstr ""
msgid "A merge request approval is required when a security report contains a new vulnerability of high, critical, or unknown severity."
msgstr ""
msgid "A merge request approval is required when the license compliance report contains a blacklisted license."
msgid "A merge request approval is required when the license compliance report contains a denied license."
msgstr ""
msgid "A new Auto DevOps pipeline has been created, go to %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details"
@ -2873,6 +2873,9 @@ msgstr ""
msgid "An error occurred while updating the comment"
msgstr ""
msgid "An error occurred while updating the list. Please try again."
msgstr ""
msgid "An error occurred while validating group path"
msgstr ""
@ -7704,6 +7707,9 @@ msgstr ""
msgid "DAG visualization requires at least 3 dependent jobs."
msgstr ""
msgid "DAST Profiles"
msgstr ""
msgid "DNS"
msgstr ""
@ -7737,6 +7743,9 @@ msgstr ""
msgid "DastProfiles|Are you sure you want to delete this profile?"
msgstr ""
msgid "DastProfiles|Could not create the scanner profile. Please try again."
msgstr ""
msgid "DastProfiles|Could not create the site profile. Please try again."
msgstr ""
@ -7761,6 +7770,9 @@ msgstr ""
msgid "DastProfiles|Could not update the site profile. Please try again."
msgstr ""
msgid "DastProfiles|Do you want to discard this scanner profile?"
msgstr ""
msgid "DastProfiles|Do you want to discard this site profile?"
msgstr ""
@ -7782,10 +7794,13 @@ msgstr ""
msgid "DastProfiles|Manage profiles"
msgstr ""
msgid "DastProfiles|New Profile"
msgid "DastProfiles|Minimum = 0 (no timeout enabled), Maximum = 2880 minutes"
msgstr ""
msgid "DastProfiles|New Scanner Profile"
msgid "DastProfiles|Minimum = 1 second, Maximum = 3600 seconds"
msgstr ""
msgid "DastProfiles|New Profile"
msgstr ""
msgid "DastProfiles|New scanner profile"
@ -7800,6 +7815,9 @@ msgstr ""
msgid "DastProfiles|Please enter a valid URL format, ex: http://www.example.com/home"
msgstr ""
msgid "DastProfiles|Please enter a valid timeout value"
msgstr ""
msgid "DastProfiles|Profile name"
msgstr ""
@ -7821,9 +7839,21 @@ msgstr ""
msgid "DastProfiles|Site Profiles"
msgstr ""
msgid "DastProfiles|Spider timeout"
msgstr ""
msgid "DastProfiles|Target URL"
msgstr ""
msgid "DastProfiles|Target timeout"
msgstr ""
msgid "DastProfiles|The maximum number of seconds allowed for the site under test to respond to a request."
msgstr ""
msgid "DastProfiles|The maximum number of seconds allowed for the spider to traverse the site."
msgstr ""
msgid "Data is still calculating..."
msgstr ""
@ -13076,6 +13106,9 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
msgid "Incident"
msgstr ""
msgid "Incident Management Limits"
msgstr ""
@ -21481,6 +21514,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
msgid "Saved scan settings and target site settings which are reusable."
msgstr ""
msgid "Saving"
msgstr ""
@ -21774,6 +21810,9 @@ msgstr ""
msgid "Secondary"
msgstr ""
msgid "Seconds"
msgstr ""
msgid "Secret"
msgstr ""
@ -21816,6 +21855,9 @@ msgstr ""
msgid "SecurityConfiguration|An error occurred while creating the merge request."
msgstr ""
msgid "SecurityConfiguration|Available for on-demand DAST"
msgstr ""
msgid "SecurityConfiguration|Configure"
msgstr ""
@ -22248,6 +22290,9 @@ msgstr ""
msgid "Select timezone"
msgstr ""
msgid "Select type"
msgstr ""
msgid "Select user"
msgstr ""

View File

@ -151,7 +151,7 @@
"vue-template-compiler": "^2.6.10",
"vue-virtual-scroll-list": "^1.4.4",
"vuedraggable": "^2.23.0",
"vuex": "^3.1.0",
"vuex": "^3.5.1",
"webpack": "^4.42.0",
"webpack-bundle-analyzer": "^3.6.0",
"webpack-cli": "^3.3.11",

View File

@ -32,6 +32,7 @@ RSpec.describe 'Group navbar' do
nav_item: _('Merge Requests'),
nav_sub_items: []
},
(push_rules_nav_item if Gitlab.ee?),
{
nav_item: _('Kubernetes'),
nav_sub_items: []
@ -47,7 +48,6 @@ RSpec.describe 'Group navbar' do
before do
insert_package_nav(_('Kubernetes'))
stub_feature_flags(group_push_rules: false)
stub_feature_flags(group_iterations: false)
stub_feature_flags(group_wiki: false)
group.add_maintainer(user)

View File

@ -188,6 +188,46 @@ RSpec.describe "User creates issue" do
end
end
context 'form create handles issue creation by default' do
let(:project) { create(:project) }
before do
visit new_project_issue_path(project)
end
it 'pre-fills the issue type dropdown with issue type' do
expect(find('.js-issuable-type-filter-dropdown-wrap .dropdown-label')).to have_content('Issue')
end
it 'does not hide the milestone select' do
expect(page).to have_selector('.qa-issuable-milestone-dropdown')
end
end
context 'form create handles incident creation' do
let(:project) { create(:project) }
before do
visit new_project_issue_path(project, { 'issue[issue_type]': 'incident', issuable_template: 'incident' })
end
it 'pre-fills the issue type dropdown with incident type' do
expect(find('.js-issuable-type-filter-dropdown-wrap .dropdown-label')).to have_content('Incident')
end
it 'hides the epic select' do
expect(page).not_to have_selector('.epic-dropdown-container')
end
it 'hides the milestone select' do
expect(page).not_to have_selector('.qa-issuable-milestone-dropdown')
end
it 'hides the weight input' do
expect(page).not_to have_selector('.qa-issuable-weight-input')
end
end
context 'suggestions', :js do
it 'displays list of related issues' do
issue = create(:issue, project: project)

View File

@ -78,7 +78,7 @@ describe('AddContextCommitsModal', () => {
findSearch().vm.$emit('input', searchText);
expect(searchCommits).not.toBeCalled();
jest.advanceTimersByTime(500);
expect(searchCommits).toHaveBeenCalledWith(expect.anything(), searchText, undefined);
expect(searchCommits).toHaveBeenCalledWith(expect.anything(), searchText);
});
it('disabled ok button when no row is selected', () => {
@ -119,18 +119,17 @@ describe('AddContextCommitsModal', () => {
wrapper.vm.$store.state.selectedCommits = [{ ...commit, isSelected: true }];
findModal().vm.$emit('ok');
return wrapper.vm.$nextTick().then(() => {
expect(createContextCommits).toHaveBeenCalledWith(
expect.anything(),
{ commits: [{ ...commit, isSelected: true }], forceReload: true },
undefined,
);
expect(createContextCommits).toHaveBeenCalledWith(expect.anything(), {
commits: [{ ...commit, isSelected: true }],
forceReload: true,
});
});
});
it('"removeContextCommits" when only added commits are to be removed ', () => {
wrapper.vm.$store.state.toRemoveCommits = [commit.short_id];
findModal().vm.$emit('ok');
return wrapper.vm.$nextTick().then(() => {
expect(removeContextCommits).toHaveBeenCalledWith(expect.anything(), true, undefined);
expect(removeContextCommits).toHaveBeenCalledWith(expect.anything(), true);
});
});
it('"createContextCommits" and "removeContextCommits" when new commits are to be added and old commits are to be removed', () => {
@ -138,12 +137,10 @@ describe('AddContextCommitsModal', () => {
wrapper.vm.$store.state.toRemoveCommits = [commit.short_id];
findModal().vm.$emit('ok');
return wrapper.vm.$nextTick().then(() => {
expect(createContextCommits).toHaveBeenCalledWith(
expect.anything(),
{ commits: [{ ...commit, isSelected: true }] },
undefined,
);
expect(removeContextCommits).toHaveBeenCalledWith(expect.anything(), undefined, undefined);
expect(createContextCommits).toHaveBeenCalledWith(expect.anything(), {
commits: [{ ...commit, isSelected: true }],
});
expect(removeContextCommits).toHaveBeenCalledWith(expect.anything(), undefined);
});
});
});
@ -156,7 +153,7 @@ describe('AddContextCommitsModal', () => {
});
it('"resetModalState" to reset all the modal state', () => {
findModal().vm.$emit('cancel');
expect(resetModalState).toHaveBeenCalledWith(expect.anything(), undefined, undefined);
expect(resetModalState).toHaveBeenCalledWith(expect.anything(), undefined);
});
});
@ -168,7 +165,7 @@ describe('AddContextCommitsModal', () => {
});
it('"resetModalState" to reset all the modal state', () => {
findModal().vm.$emit('close');
expect(resetModalState).toHaveBeenCalledWith(expect.anything(), undefined, undefined);
expect(resetModalState).toHaveBeenCalledWith(expect.anything(), undefined);
});
});
});

View File

@ -1,4 +1,5 @@
import testAction from 'helpers/vuex_action_helper';
import { mockListsWithModel } from '../mock_data';
import actions, { gqlClient } from '~/boards/stores/actions';
import * as types from '~/boards/stores/mutation_types';
import { inactiveId, ListType } from '~/boards/constants';
@ -160,8 +161,63 @@ describe('createList', () => {
});
});
describe('moveList', () => {
it('should commit MOVE_LIST mutation and dispatch updateList action', done => {
const state = {
endpoints: { fullPath: 'gitlab-org', boardId: '1' },
boardType: 'group',
disabled: false,
boardLists: mockListsWithModel,
};
testAction(
actions.moveList,
{ listId: 'gid://gitlab/List/1', newIndex: 1, adjustmentValue: 1 },
state,
[
{
type: types.MOVE_LIST,
payload: { movedList: mockListsWithModel[0], listAtNewIndex: mockListsWithModel[1] },
},
],
[
{
type: 'updateList',
payload: { listId: 'gid://gitlab/List/1', position: 0, backupList: mockListsWithModel },
},
],
done,
);
});
});
describe('updateList', () => {
expectNotImplemented(actions.updateList);
it('should commit UPDATE_LIST_FAILURE mutation when API returns an error', done => {
jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
data: {
updateBoardList: {
list: {},
errors: [{ foo: 'bar' }],
},
},
});
const state = {
endpoints: { fullPath: 'gitlab-org', boardId: '1' },
boardType: 'group',
disabled: false,
boardLists: [{ type: 'closed' }],
};
testAction(
actions.updateList,
{ listId: 'gid://gitlab/List/1', position: 1 },
state,
[{ type: types.UPDATE_LIST_FAILURE }],
[],
done,
);
});
});
describe('deleteList', () => {

View File

@ -1,7 +1,7 @@
import mutations from '~/boards/stores/mutations';
import * as types from '~/boards/stores/mutation_types';
import defaultState from '~/boards/stores/state';
import { listObj, listObjDuplicate, mockIssue } from '../mock_data';
import { listObj, listObjDuplicate, mockIssue, mockListsWithModel } from '../mock_data';
const expectNotImplemented = action => {
it('is not implemented', () => {
@ -92,16 +92,35 @@ describe('Board Store Mutations', () => {
expectNotImplemented(mutations.RECEIVE_ADD_LIST_ERROR);
});
describe('REQUEST_UPDATE_LIST', () => {
expectNotImplemented(mutations.REQUEST_UPDATE_LIST);
describe('MOVE_LIST', () => {
it('updates boardLists state with reordered lists', () => {
state = {
...state,
boardLists: mockListsWithModel,
};
mutations.MOVE_LIST(state, {
movedList: mockListsWithModel[0],
listAtNewIndex: mockListsWithModel[1],
});
expect(state.boardLists).toEqual([mockListsWithModel[1], mockListsWithModel[0]]);
});
});
describe('RECEIVE_UPDATE_LIST_SUCCESS', () => {
expectNotImplemented(mutations.RECEIVE_UPDATE_LIST_SUCCESS);
});
describe('UPDATE_LIST_FAILURE', () => {
it('updates boardLists state with previous order and sets error message', () => {
state = {
...state,
boardLists: [mockListsWithModel[1], mockListsWithModel[0]],
error: undefined,
};
describe('RECEIVE_UPDATE_LIST_ERROR', () => {
expectNotImplemented(mutations.RECEIVE_UPDATE_LIST_ERROR);
mutations.UPDATE_LIST_FAILURE(state, mockListsWithModel);
expect(state.boardLists).toEqual(mockListsWithModel);
expect(state.error).toEqual('An error occurred while updating the list. Please try again.');
});
});
describe('REQUEST_REMOVE_LIST', () => {

View File

@ -401,43 +401,33 @@ describe('EksClusterConfigurationForm', () => {
});
it('dispatches setRegion action', () => {
expect(actions.setRegion).toHaveBeenCalledWith(expect.anything(), { region }, undefined);
expect(actions.setRegion).toHaveBeenCalledWith(expect.anything(), { region });
});
it('fetches available vpcs', () => {
expect(vpcsActions.fetchItems).toHaveBeenCalledWith(expect.anything(), { region }, undefined);
expect(vpcsActions.fetchItems).toHaveBeenCalledWith(expect.anything(), { region });
});
it('fetches available key pairs', () => {
expect(keyPairsActions.fetchItems).toHaveBeenCalledWith(
expect.anything(),
{ region },
undefined,
);
expect(keyPairsActions.fetchItems).toHaveBeenCalledWith(expect.anything(), { region });
});
it('cleans selected vpc', () => {
expect(actions.setVpc).toHaveBeenCalledWith(expect.anything(), { vpc: null }, undefined);
expect(actions.setVpc).toHaveBeenCalledWith(expect.anything(), { vpc: null });
});
it('cleans selected key pair', () => {
expect(actions.setKeyPair).toHaveBeenCalledWith(
expect.anything(),
{ keyPair: null },
undefined,
);
expect(actions.setKeyPair).toHaveBeenCalledWith(expect.anything(), { keyPair: null });
});
it('cleans selected subnet', () => {
expect(actions.setSubnet).toHaveBeenCalledWith(expect.anything(), { subnet: [] }, undefined);
expect(actions.setSubnet).toHaveBeenCalledWith(expect.anything(), { subnet: [] });
});
it('cleans selected security group', () => {
expect(actions.setSecurityGroup).toHaveBeenCalledWith(
expect.anything(),
{ securityGroup: null },
undefined,
);
expect(actions.setSecurityGroup).toHaveBeenCalledWith(expect.anything(), {
securityGroup: null,
});
});
});
@ -446,11 +436,7 @@ describe('EksClusterConfigurationForm', () => {
findClusterNameInput().vm.$emit('input', clusterName);
expect(actions.setClusterName).toHaveBeenCalledWith(
expect.anything(),
{ clusterName },
undefined,
);
expect(actions.setClusterName).toHaveBeenCalledWith(expect.anything(), { clusterName });
});
it('dispatches setEnvironmentScope when environment scope input changes', () => {
@ -458,11 +444,9 @@ describe('EksClusterConfigurationForm', () => {
findEnvironmentScopeInput().vm.$emit('input', environmentScope);
expect(actions.setEnvironmentScope).toHaveBeenCalledWith(
expect.anything(),
{ environmentScope },
undefined,
);
expect(actions.setEnvironmentScope).toHaveBeenCalledWith(expect.anything(), {
environmentScope,
});
});
it('dispatches setKubernetesVersion when kubernetes version dropdown changes', () => {
@ -470,11 +454,9 @@ describe('EksClusterConfigurationForm', () => {
findKubernetesVersionDropdown().vm.$emit('input', kubernetesVersion);
expect(actions.setKubernetesVersion).toHaveBeenCalledWith(
expect.anything(),
{ kubernetesVersion },
undefined,
);
expect(actions.setKubernetesVersion).toHaveBeenCalledWith(expect.anything(), {
kubernetesVersion,
});
});
it('dispatches setGitlabManagedCluster when gitlab managed cluster input changes', () => {
@ -482,11 +464,9 @@ describe('EksClusterConfigurationForm', () => {
findGitlabManagedClusterCheckbox().vm.$emit('input', gitlabManagedCluster);
expect(actions.setGitlabManagedCluster).toHaveBeenCalledWith(
expect.anything(),
{ gitlabManagedCluster },
undefined,
);
expect(actions.setGitlabManagedCluster).toHaveBeenCalledWith(expect.anything(), {
gitlabManagedCluster,
});
});
describe('when vpc is selected', () => {
@ -499,35 +479,28 @@ describe('EksClusterConfigurationForm', () => {
});
it('dispatches setVpc action', () => {
expect(actions.setVpc).toHaveBeenCalledWith(expect.anything(), { vpc }, undefined);
expect(actions.setVpc).toHaveBeenCalledWith(expect.anything(), { vpc });
});
it('cleans selected subnet', () => {
expect(actions.setSubnet).toHaveBeenCalledWith(expect.anything(), { subnet: [] }, undefined);
expect(actions.setSubnet).toHaveBeenCalledWith(expect.anything(), { subnet: [] });
});
it('cleans selected security group', () => {
expect(actions.setSecurityGroup).toHaveBeenCalledWith(
expect.anything(),
{ securityGroup: null },
undefined,
);
expect(actions.setSecurityGroup).toHaveBeenCalledWith(expect.anything(), {
securityGroup: null,
});
});
it('dispatches fetchSubnets action', () => {
expect(subnetsActions.fetchItems).toHaveBeenCalledWith(
expect.anything(),
{ vpc, region },
undefined,
);
expect(subnetsActions.fetchItems).toHaveBeenCalledWith(expect.anything(), { vpc, region });
});
it('dispatches fetchSecurityGroups action', () => {
expect(securityGroupsActions.fetchItems).toHaveBeenCalledWith(
expect.anything(),
{ vpc, region },
undefined,
);
expect(securityGroupsActions.fetchItems).toHaveBeenCalledWith(expect.anything(), {
vpc,
region,
});
});
});
@ -539,7 +512,7 @@ describe('EksClusterConfigurationForm', () => {
});
it('dispatches setSubnet action', () => {
expect(actions.setSubnet).toHaveBeenCalledWith(expect.anything(), { subnet }, undefined);
expect(actions.setSubnet).toHaveBeenCalledWith(expect.anything(), { subnet });
});
});
@ -551,7 +524,7 @@ describe('EksClusterConfigurationForm', () => {
});
it('dispatches setRole action', () => {
expect(actions.setRole).toHaveBeenCalledWith(expect.anything(), { role }, undefined);
expect(actions.setRole).toHaveBeenCalledWith(expect.anything(), { role });
});
});
@ -563,7 +536,7 @@ describe('EksClusterConfigurationForm', () => {
});
it('dispatches setKeyPair action', () => {
expect(actions.setKeyPair).toHaveBeenCalledWith(expect.anything(), { keyPair }, undefined);
expect(actions.setKeyPair).toHaveBeenCalledWith(expect.anything(), { keyPair });
});
});
@ -575,11 +548,7 @@ describe('EksClusterConfigurationForm', () => {
});
it('dispatches setSecurityGroup action', () => {
expect(actions.setSecurityGroup).toHaveBeenCalledWith(
expect.anything(),
{ securityGroup },
undefined,
);
expect(actions.setSecurityGroup).toHaveBeenCalledWith(expect.anything(), { securityGroup });
});
});
@ -591,11 +560,7 @@ describe('EksClusterConfigurationForm', () => {
});
it('dispatches setInstanceType action', () => {
expect(actions.setInstanceType).toHaveBeenCalledWith(
expect.anything(),
{ instanceType },
undefined,
);
expect(actions.setInstanceType).toHaveBeenCalledWith(expect.anything(), { instanceType });
});
});
@ -604,7 +569,7 @@ describe('EksClusterConfigurationForm', () => {
findNodeCountInput().vm.$emit('input', nodeCount);
expect(actions.setNodeCount).toHaveBeenCalledWith(expect.anything(), { nodeCount }, undefined);
expect(actions.setNodeCount).toHaveBeenCalledWith(expect.anything(), { nodeCount });
});
describe('when all cluster configuration fields are set', () => {

View File

@ -124,11 +124,7 @@ describe('GkeMachineTypeDropdown', () => {
wrapper.find('.dropdown-content button').trigger('click');
return wrapper.vm.$nextTick().then(() => {
expect(setMachineType).toHaveBeenCalledWith(
expect.anything(),
selectedMachineTypeMock,
undefined,
);
expect(setMachineType).toHaveBeenCalledWith(expect.anything(), selectedMachineTypeMock);
});
});
});

View File

@ -121,23 +121,19 @@ describe('GkeNetworkDropdown', () => {
});
it('cleans selected subnetwork', () => {
expect(setSubnetwork).toHaveBeenCalledWith(expect.anything(), '', undefined);
expect(setSubnetwork).toHaveBeenCalledWith(expect.anything(), '');
});
it('dispatches the setNetwork action', () => {
expect(setNetwork).toHaveBeenCalledWith(expect.anything(), selectedNetwork, undefined);
expect(setNetwork).toHaveBeenCalledWith(expect.anything(), selectedNetwork);
});
it('fetches subnetworks for the selected project, region, and network', () => {
expect(fetchSubnetworks).toHaveBeenCalledWith(
expect.anything(),
{
project: projectId,
region,
network: selectedNetwork.selfLink,
},
undefined,
);
expect(fetchSubnetworks).toHaveBeenCalledWith(expect.anything(), {
project: projectId,
region,
network: selectedNetwork.selfLink,
});
});
});
});

View File

@ -130,7 +130,6 @@ describe('GkeProjectIdDropdown', () => {
expect(setProject).toHaveBeenCalledWith(
expect.anything(),
gapiProjectsResponseMock.projects[0],
undefined,
);
});
});

View File

@ -107,7 +107,7 @@ describe('GkeSubnetworkDropdown', () => {
wrapper.find(ClusterFormDropdown).vm.$emit('input', selectedSubnetwork);
expect(setSubnetwork).toHaveBeenCalledWith(expect.anything(), selectedSubnetwork, undefined);
expect(setSubnetwork).toHaveBeenCalledWith(expect.anything(), selectedSubnetwork);
});
});
});

View File

@ -1,15 +1,14 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { createMockClient } from 'mock-apollo-client';
import VueApollo from 'vue-apollo';
import VueRouter from 'vue-router';
import VueDraggable from 'vuedraggable';
import { InMemoryCache } from 'apollo-cache-inmemory';
import Design from '~/design_management/components/list/item.vue';
import createRouter from '~/design_management/router';
import getDesignListQuery from '~/design_management/graphql/queries/get_design_list.query.graphql';
import permissionsQuery from '~/design_management/graphql/queries/design_permissions.query.graphql';
import moveDesignMutation from '~/design_management/graphql/mutations/move_design.mutation.graphql';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import createMockApollo from '../../helpers/mock_apollo_helper';
import Index from '~/design_management/pages/index.vue';
import {
designListQueryResponse,
@ -39,8 +38,7 @@ const designToMove = {
describe('Design management index page with Apollo mock', () => {
let wrapper;
let mockClient;
let apolloProvider;
let fakeApollo;
let moveDesignHandler;
async function moveDesigns(localWrapper) {
@ -56,41 +54,23 @@ describe('Design management index page with Apollo mock', () => {
});
}
const fragmentMatcher = { match: () => true };
const cache = new InMemoryCache({
fragmentMatcher,
addTypename: false,
});
const findDesigns = () => wrapper.findAll(Design);
function createComponent({
moveHandler = jest.fn().mockResolvedValue(moveDesignMutationResponse),
}) {
mockClient = createMockClient({ cache });
mockClient.setRequestHandler(
getDesignListQuery,
jest.fn().mockResolvedValue(designListQueryResponse),
);
mockClient.setRequestHandler(
permissionsQuery,
jest.fn().mockResolvedValue(permissionsQueryResponse),
);
moveDesignHandler = moveHandler;
mockClient.setRequestHandler(moveDesignMutation, moveDesignHandler);
apolloProvider = new VueApollo({
defaultClient: mockClient,
});
const requestHandlers = [
[getDesignListQuery, jest.fn().mockResolvedValue(designListQueryResponse)],
[permissionsQuery, jest.fn().mockResolvedValue(permissionsQueryResponse)],
[moveDesignMutation, moveDesignHandler],
];
fakeApollo = createMockApollo(requestHandlers);
wrapper = shallowMount(Index, {
localVue,
apolloProvider,
apolloProvider: fakeApollo,
router,
stubs: { VueDraggable },
});
@ -99,8 +79,6 @@ describe('Design management index page with Apollo mock', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
mockClient = null;
apolloProvider = null;
});
it('has a design with id 1 as a first one', async () => {
@ -152,8 +130,9 @@ describe('Design management index page with Apollo mock', () => {
await moveDesigns(wrapper);
await jest.runOnlyPendingTimers();
await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick(); // kick off the DOM update
await jest.runOnlyPendingTimers(); // kick off the mocked GQL stuff (promises)
await wrapper.vm.$nextTick(); // kick off the DOM update for flash
expect(createFlash).toHaveBeenCalledWith(
'Something went wrong when reordering designs. Please try again',

View File

@ -177,23 +177,19 @@ describe('DiffContent', () => {
});
wrapper.find(NoteForm).vm.$emit('handleFormUpdate', noteStub);
expect(saveDiffDiscussionMock).toHaveBeenCalledWith(
expect.any(Object),
{
note: noteStub,
formData: {
noteableData: expect.any(Object),
diffFile: currentDiffFile,
positionType: IMAGE_DIFF_POSITION_TYPE,
x: undefined,
y: undefined,
width: undefined,
height: undefined,
noteableType: undefined,
},
expect(saveDiffDiscussionMock).toHaveBeenCalledWith(expect.any(Object), {
note: noteStub,
formData: {
noteableData: expect.any(Object),
diffFile: currentDiffFile,
positionType: IMAGE_DIFF_POSITION_TYPE,
x: undefined,
y: undefined,
width: undefined,
height: undefined,
noteableType: undefined,
},
undefined,
);
});
});
});
});

View File

@ -1,6 +1,7 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { GlIcon } from '@gitlab/ui';
import { cloneDeep } from 'lodash';
import DiffFileHeader from '~/diffs/components/diff_file_header.vue';
import EditButton from '~/diffs/components/edit_button.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
@ -26,12 +27,16 @@ const diffFile = Object.freeze(
}),
);
const localVue = createLocalVue();
localVue.use(Vuex);
describe('DiffFileHeader component', () => {
let wrapper;
let mockStoreConfig;
const diffHasExpandedDiscussionsResultMock = jest.fn();
const diffHasDiscussionsResultMock = jest.fn();
const mockStoreConfig = {
const defaultMockStoreConfig = {
state: {},
modules: {
diffs: {
@ -56,6 +61,8 @@ describe('DiffFileHeader component', () => {
diffHasExpandedDiscussionsResultMock,
...Object.values(mockStoreConfig.modules.diffs.actions),
].forEach(mock => mock.mockReset());
wrapper.destroy();
});
const findHeader = () => wrapper.find({ ref: 'header' });
@ -80,8 +87,7 @@ describe('DiffFileHeader component', () => {
};
const createComponent = props => {
const localVue = createLocalVue();
localVue.use(Vuex);
mockStoreConfig = cloneDeep(defaultMockStoreConfig);
const store = new Vuex.Store(mockStoreConfig);
wrapper = shallowMount(DiffFileHeader, {
@ -286,7 +292,7 @@ describe('DiffFileHeader component', () => {
findToggleDiscussionsButton().vm.$emit('click');
expect(
mockStoreConfig.modules.diffs.actions.toggleFileDiscussionWrappers,
).toHaveBeenCalledWith(expect.any(Object), diffFile, undefined);
).toHaveBeenCalledWith(expect.any(Object), diffFile);
});
});

View File

@ -50,7 +50,7 @@ describe('Diff settings dropdown component', () => {
vm.find('.js-list-view').trigger('click');
expect(actions.setRenderTreeList).toHaveBeenCalledWith(expect.anything(), false, undefined);
expect(actions.setRenderTreeList).toHaveBeenCalledWith(expect.anything(), false);
});
it('tree view button dispatches setRenderTreeList with true', () => {
@ -58,7 +58,7 @@ describe('Diff settings dropdown component', () => {
vm.find('.js-tree-view').trigger('click');
expect(actions.setRenderTreeList).toHaveBeenCalledWith(expect.anything(), true, undefined);
expect(actions.setRenderTreeList).toHaveBeenCalledWith(expect.anything(), true);
});
it('sets list button as selected when renderTreeList is false', () => {
@ -153,14 +153,10 @@ describe('Diff settings dropdown component', () => {
checkbox.element.checked = true;
checkbox.trigger('change');
expect(actions.setShowWhitespace).toHaveBeenCalledWith(
expect.anything(),
{
showWhitespace: true,
pushState: true,
},
undefined,
);
expect(actions.setShowWhitespace).toHaveBeenCalledWith(expect.anything(), {
showWhitespace: true,
pushState: true,
});
});
});
});

View File

@ -259,23 +259,15 @@ describe('ErrorTrackingList', () => {
errorId: errorsList[0].id,
status: 'ignored',
});
expect(actions.updateStatus).toHaveBeenCalledWith(
expect.anything(),
{
endpoint: `/project/test/-/error_tracking/${errorsList[0].id}.json`,
status: 'ignored',
},
undefined,
);
expect(actions.updateStatus).toHaveBeenCalledWith(expect.anything(), {
endpoint: `/project/test/-/error_tracking/${errorsList[0].id}.json`,
status: 'ignored',
});
});
it('calls an action to remove the item from the list', () => {
findErrorActions().vm.$emit('update-issue-status', { errorId: '1', status: undefined });
expect(actions.removeIgnoredResolvedErrors).toHaveBeenCalledWith(
expect.anything(),
'1',
undefined,
);
expect(actions.removeIgnoredResolvedErrors).toHaveBeenCalledWith(expect.anything(), '1');
});
});
@ -298,23 +290,15 @@ describe('ErrorTrackingList', () => {
errorId: errorsList[0].id,
status: 'resolved',
});
expect(actions.updateStatus).toHaveBeenCalledWith(
expect.anything(),
{
endpoint: `/project/test/-/error_tracking/${errorsList[0].id}.json`,
status: 'resolved',
},
undefined,
);
expect(actions.updateStatus).toHaveBeenCalledWith(expect.anything(), {
endpoint: `/project/test/-/error_tracking/${errorsList[0].id}.json`,
status: 'resolved',
});
});
it('calls an action to remove the item from the list', () => {
findErrorActions().vm.$emit('update-issue-status', { errorId: '1', status: undefined });
expect(actions.removeIgnoredResolvedErrors).toHaveBeenCalledWith(
expect.anything(),
'1',
undefined,
);
expect(actions.removeIgnoredResolvedErrors).toHaveBeenCalledWith(expect.anything(), '1');
});
});
@ -443,7 +427,6 @@ describe('ErrorTrackingList', () => {
expect(actions.fetchPaginatedResults).toHaveBeenLastCalledWith(
expect.anything(),
'previousCursor',
undefined,
);
});
});
@ -462,7 +445,6 @@ describe('ErrorTrackingList', () => {
expect(actions.fetchPaginatedResults).toHaveBeenLastCalledWith(
expect.anything(),
'nextCursor',
undefined,
);
});
});

View File

@ -0,0 +1,23 @@
import { InMemoryCache } from 'apollo-cache-inmemory';
import { createMockClient } from 'mock-apollo-client';
import VueApollo from 'vue-apollo';
export default (handlers = []) => {
const fragmentMatcher = { match: () => true };
const cache = new InMemoryCache({
fragmentMatcher,
addTypename: false,
});
const mockClient = createMockClient({ cache });
if (Array.isArray(handlers)) {
handlers.forEach(([query, value]) => mockClient.setRequestHandler(query, value));
} else {
throw new Error('You should pass an array of handlers to mock Apollo client');
}
const apolloProvider = new VueApollo({ defaultClient: mockClient });
return apolloProvider;
};

View File

@ -51,7 +51,7 @@ describe('IDE error message component', () => {
createComponent();
findDismissButton().trigger('click');
expect(setErrorMessageMock).toHaveBeenCalledWith(expect.any(Object), null, undefined);
expect(setErrorMessageMock).toHaveBeenCalledWith(expect.any(Object), null);
});
describe('with action', () => {

View File

@ -99,11 +99,7 @@ describe('IDE stages list', () => {
it('calls toggleStageCollapsed when clicking stage header', () => {
findCardHeader().trigger('click');
expect(storeActions.toggleStageCollapsed).toHaveBeenCalledWith(
expect.any(Object),
0,
undefined,
);
expect(storeActions.toggleStageCollapsed).toHaveBeenCalledWith(expect.any(Object), 0);
});
it('calls fetchJobs when stage is mounted', () => {

View File

@ -56,14 +56,10 @@ describe('IDE merge requests list', () => {
it('calls fetch on mounted', () => {
createComponent();
expect(fetchMergeRequestsMock).toHaveBeenCalledWith(
expect.any(Object),
{
search: '',
type: '',
},
undefined,
);
expect(fetchMergeRequestsMock).toHaveBeenCalledWith(expect.any(Object), {
search: '',
type: '',
});
});
it('renders loading icon when merge request is loading', () => {
@ -95,14 +91,10 @@ describe('IDE merge requests list', () => {
const searchType = wrapper.vm.$options.searchTypes[0];
expect(findTokenedInput().props('tokens')).toEqual([searchType]);
expect(fetchMergeRequestsMock).toHaveBeenCalledWith(
expect.any(Object),
{
type: searchType.type,
search: '',
},
undefined,
);
expect(fetchMergeRequestsMock).toHaveBeenCalledWith(expect.any(Object), {
type: searchType.type,
search: '',
});
});
});
@ -136,14 +128,10 @@ describe('IDE merge requests list', () => {
input.vm.$emit('input', 'something');
return wrapper.vm.$nextTick().then(() => {
expect(fetchMergeRequestsMock).toHaveBeenCalledWith(
expect.any(Object),
{
search: 'something',
type: '',
},
undefined,
);
expect(fetchMergeRequestsMock).toHaveBeenCalledWith(expect.any(Object), {
search: 'something',
type: '',
});
});
});
});

View File

@ -279,24 +279,16 @@ describe('IDE clientside preview', () => {
});
it('calls getFileData', () => {
expect(storeActions.getFileData).toHaveBeenCalledWith(
expect.any(Object),
{
path: 'package.json',
makeFileActive: false,
},
undefined, // vuex callback
);
expect(storeActions.getFileData).toHaveBeenCalledWith(expect.any(Object), {
path: 'package.json',
makeFileActive: false,
});
});
it('calls getRawFileData', () => {
expect(storeActions.getRawFileData).toHaveBeenCalledWith(
expect.any(Object),
{
path: 'package.json',
},
undefined, // vuex callback
);
expect(storeActions.getRawFileData).toHaveBeenCalledWith(expect.any(Object), {
path: 'package.json',
});
});
});

View File

@ -1,4 +1,4 @@
import { serializeForm } from '~/lib/utils/forms';
import { serializeForm, serializeFormObject, isEmptyValue } from '~/lib/utils/forms';
describe('lib/utils/forms', () => {
const createDummyForm = inputs => {
@ -93,4 +93,46 @@ describe('lib/utils/forms', () => {
});
});
});
describe('isEmptyValue', () => {
it.each`
input | returnValue
${''} | ${true}
${[]} | ${true}
${null} | ${true}
${undefined} | ${true}
${'hello'} | ${false}
${' '} | ${false}
${0} | ${false}
`('returns $returnValue for value $input', ({ input, returnValue }) => {
expect(isEmptyValue(input)).toBe(returnValue);
});
});
describe('serializeFormObject', () => {
it('returns an serialized object', () => {
const form = {
profileName: { value: 'hello', state: null, feedback: null },
spiderTimeout: { value: 2, state: true, feedback: null },
targetTimeout: { value: 12, state: true, feedback: null },
};
expect(serializeFormObject(form)).toEqual({
profileName: 'hello',
spiderTimeout: 2,
targetTimeout: 12,
});
});
it('returns only the entries with value', () => {
const form = {
profileName: { value: '', state: null, feedback: null },
spiderTimeout: { value: 0, state: null, feedback: null },
targetTimeout: { value: null, state: null, feedback: null },
name: { value: undefined, state: null, feedback: null },
};
expect(serializeFormObject(form)).toEqual({
spiderTimeout: 0,
});
});
});
});

View File

@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
import resolveDiscussionButton from '~/notes/components/discussion_resolve_button.vue';
const buttonTitle = 'Resolve discussion';
@ -26,9 +27,9 @@ describe('resolveDiscussionButton', () => {
});
it('should emit a onClick event on button click', () => {
const button = wrapper.find({ ref: 'button' });
const button = wrapper.find(GlButton);
button.trigger('click');
button.vm.$emit('click');
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.emitted()).toEqual({
@ -38,7 +39,7 @@ describe('resolveDiscussionButton', () => {
});
it('should contain the provided button title', () => {
const button = wrapper.find({ ref: 'button' });
const button = wrapper.find(GlButton);
expect(button.text()).toContain(buttonTitle);
});
@ -51,9 +52,9 @@ describe('resolveDiscussionButton', () => {
},
});
const button = wrapper.find({ ref: 'isResolvingIcon' });
const button = wrapper.find(GlButton);
expect(button.exists()).toEqual(true);
expect(button.props('loading')).toEqual(true);
});
it('should only show a loading spinner while resolving', () => {
@ -64,10 +65,10 @@ describe('resolveDiscussionButton', () => {
},
});
const button = wrapper.find({ ref: 'isResolvingIcon' });
const button = wrapper.find(GlButton);
wrapper.vm.$nextTick(() => {
expect(button.exists()).toEqual(false);
expect(button.props('loading')).toEqual(false);
});
});
});

View File

@ -194,13 +194,9 @@ describe('Discussion navigation mixin', () => {
});
it('expands discussion', () => {
expect(expandDiscussion).toHaveBeenCalledWith(
expect.anything(),
{
discussionId: expected,
},
undefined,
);
expect(expandDiscussion).toHaveBeenCalledWith(expect.anything(), {
discussionId: expected,
});
});
it('scrolls to discussion', () => {

View File

@ -133,11 +133,7 @@ describe('Author Select', () => {
const authorName = 'lorem';
findSearchBox().vm.$emit('input', authorName);
expect(store.actions.fetchAuthors).toHaveBeenCalledWith(
expect.anything(),
authorName,
undefined,
);
expect(store.actions.fetchAuthors).toHaveBeenCalledWith(expect.anything(), authorName);
});
});

View File

@ -115,14 +115,10 @@ describe('Release edit component', () => {
const expectStoreMethodToBeCalled = () => {
expect(actions.updateAssetLinkUrl).toHaveBeenCalledTimes(1);
expect(actions.updateAssetLinkUrl).toHaveBeenCalledWith(
expect.anything(),
{
linkIdToUpdate,
newUrl,
},
undefined,
);
expect(actions.updateAssetLinkUrl).toHaveBeenCalledWith(expect.anything(), {
linkIdToUpdate,
newUrl,
});
};
it('calls the "updateAssetLinkUrl" store method when text is entered into the "URL" input field', () => {
@ -177,14 +173,10 @@ describe('Release edit component', () => {
const expectStoreMethodToBeCalled = () => {
expect(actions.updateAssetLinkName).toHaveBeenCalledTimes(1);
expect(actions.updateAssetLinkName).toHaveBeenCalledWith(
expect.anything(),
{
linkIdToUpdate,
newName,
},
undefined,
);
expect(actions.updateAssetLinkName).toHaveBeenCalledWith(expect.anything(), {
linkIdToUpdate,
newName,
});
};
it('calls the "updateAssetLinkName" store method when text is entered into the "Link title" input field', () => {
@ -225,14 +217,10 @@ describe('Release edit component', () => {
wrapper.find({ ref: 'typeSelect' }).vm.$emit('change', newType);
expect(actions.updateAssetLinkType).toHaveBeenCalledTimes(1);
expect(actions.updateAssetLinkType).toHaveBeenCalledWith(
expect.anything(),
{
linkIdToUpdate,
newType,
},
undefined,
);
expect(actions.updateAssetLinkType).toHaveBeenCalledWith(expect.anything(), {
linkIdToUpdate,
newType,
});
});
it('selects the default asset type if no type was provided by the backend', () => {

View File

@ -1,5 +1,5 @@
import { setHTMLFixture } from './helpers/fixtures';
import Tracking, { initUserTracking } from '~/tracking';
import Tracking, { initUserTracking, initDefaultTrackers } from '~/tracking';
describe('Tracking', () => {
let snowplowSpy;
@ -17,11 +17,6 @@ describe('Tracking', () => {
});
describe('initUserTracking', () => {
beforeEach(() => {
bindDocumentSpy = jest.spyOn(Tracking, 'bindDocument').mockImplementation(() => null);
trackLoadEventsSpy = jest.spyOn(Tracking, 'trackLoadEvents').mockImplementation(() => null);
});
it('calls through to get a new tracker with the expected options', () => {
initUserTracking();
expect(snowplowSpy).toHaveBeenCalledWith('newTracker', '_namespace_', 'app.gitfoo.com', {
@ -38,9 +33,16 @@ describe('Tracking', () => {
linkClickTracking: false,
});
});
});
describe('initDefaultTrackers', () => {
beforeEach(() => {
bindDocumentSpy = jest.spyOn(Tracking, 'bindDocument').mockImplementation(() => null);
trackLoadEventsSpy = jest.spyOn(Tracking, 'trackLoadEvents').mockImplementation(() => null);
});
it('should activate features based on what has been enabled', () => {
initUserTracking();
initDefaultTrackers();
expect(snowplowSpy).toHaveBeenCalledWith('enableActivityTracking', 30, 30);
expect(snowplowSpy).toHaveBeenCalledWith('trackPageView');
expect(snowplowSpy).not.toHaveBeenCalledWith('enableFormTracking');
@ -52,18 +54,18 @@ describe('Tracking', () => {
linkClickTracking: true,
};
initUserTracking();
initDefaultTrackers();
expect(snowplowSpy).toHaveBeenCalledWith('enableFormTracking');
expect(snowplowSpy).toHaveBeenCalledWith('enableLinkClickTracking');
});
it('binds the document event handling', () => {
initUserTracking();
initDefaultTrackers();
expect(bindDocumentSpy).toHaveBeenCalled();
});
it('tracks page loaded events', () => {
initUserTracking();
initDefaultTrackers();
expect(trackLoadEventsSpy).toHaveBeenCalled();
});
});

View File

@ -11,7 +11,7 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs do
let(:command) do
double(:command,
config_processor: double(:processor,
yaml_processor_result: double(:processor,
jobs: { echo: double(:job_echo), rspec: double(:job_rspec) }),
project: project,
chat_data: { command: 'echo' })
@ -25,7 +25,7 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs do
subject
expect(command.config_processor.jobs.keys).to eq([:echo])
expect(command.yaml_processor_result.jobs.keys).to eq([:echo])
end
it 'does not remove any jobs for non chat-pipelines' do
@ -33,7 +33,7 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs do
subject
expect(command.config_processor.jobs.keys).to eq([:echo, :rspec])
expect(command.yaml_processor_result.jobs.keys).to eq([:echo, :rspec])
end
end
end

View File

@ -44,7 +44,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
let(:save_incompleted) { true }
let(:command) do
Gitlab::Ci::Pipeline::Chain::Command.new(
project: project, current_user: user, config_processor: yaml_processor_result, save_incompleted: save_incompleted
project: project, current_user: user, yaml_processor_result: yaml_processor_result, save_incompleted: save_incompleted
)
end
@ -128,7 +128,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
end
describe '#validation_service_payload' do
subject(:validation_service_payload) { step.send(:validation_service_payload, pipeline, command.config_processor.stages_attributes) }
subject(:validation_service_payload) { step.send(:validation_service_payload, pipeline, command.yaml_processor_result.stages_attributes) }
it 'respects the defined schema' do
expect(validation_service_payload).to match_schema('/external_validation')

View File

@ -27,8 +27,8 @@ RSpec.describe ProjectBadge do
end
context 'methods' do
let(:badge) { build(:project_badge, link_url: placeholder_url, image_url: placeholder_url) }
let!(:project) { badge.project }
let(:badge) { build_stubbed(:project_badge, link_url: placeholder_url, image_url: placeholder_url) }
let(:project) { badge.project }
describe '#rendered_link_url' do
let(:method) { :link_url }

View File

@ -35,7 +35,9 @@ RSpec.describe DiffNote do
subject { create(:diff_note_on_merge_request, project: project, position: position, noteable: merge_request) }
describe 'validations' do
it_behaves_like 'a valid diff positionable note', :diff_note_on_commit
it_behaves_like 'a valid diff positionable note' do
subject { build(:diff_note_on_commit, project: project, commit_id: commit_id, position: position) }
end
end
describe "#position=" do

Some files were not shown because too many files have changed in this diff Show More