Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
ead25aaac3
commit
ea3aadb056
|
|
@ -16,6 +16,7 @@
|
|||
- export GOPATH=$CI_PROJECT_DIR/.go
|
||||
- mkdir -p $GOPATH
|
||||
- source scripts/utils.sh
|
||||
- log_disk_usage # https://gitlab.com/gitlab-org/gitlab/-/issues/478880
|
||||
|
||||
.default-before_script:
|
||||
before_script:
|
||||
|
|
|
|||
|
|
@ -377,16 +377,26 @@ rspec-ee unit clickhouse:
|
|||
- .rspec-base-pg14-clickhouse23
|
||||
- .rails:rules:ee-only-clickhouse-changes
|
||||
|
||||
rspec ci-config-validation:
|
||||
rspec ci-config-validation branch-pipelines:
|
||||
extends:
|
||||
- .rspec-base-pg14
|
||||
- .rails:rules:rspec-ci-config-validation
|
||||
stage: test
|
||||
- .ci-config-validation-base
|
||||
script:
|
||||
- !reference [.base-script, script]
|
||||
- rspec_section rspec_simple_job "--tag ci_config_validation -- spec/dot_gitlab_ci/"
|
||||
after_script:
|
||||
- echo 'OK' # override after_script from .rspec-base
|
||||
- rspec_section rspec_simple_job "--tag ci_config_validation -- spec/dot_gitlab_ci/ci_configuration_validation/branch_pipeline_spec.rb"
|
||||
|
||||
rspec ci-config-validation pipeline-labels:
|
||||
extends:
|
||||
- .ci-config-validation-base
|
||||
script:
|
||||
- !reference [.base-script, script]
|
||||
- rspec_section rspec_simple_job "--tag ci_config_validation -- spec/dot_gitlab_ci/ci_configuration_validation/pipeline_labels_spec.rb"
|
||||
|
||||
rspec ci-config-validation mr-pipelines:
|
||||
extends:
|
||||
- .ci-config-validation-base
|
||||
script:
|
||||
- !reference [.base-script, script]
|
||||
- rspec_section rspec_simple_job "--tag ci_config_validation -- spec/dot_gitlab_ci/ci_configuration_validation/merge_request_pipeline_spec.rb"
|
||||
|
||||
gitlab:clickhouse:rollback:main:
|
||||
extends:
|
||||
|
|
|
|||
|
|
@ -297,6 +297,13 @@ include:
|
|||
- .use-pg16-opensearch2-ee
|
||||
- .rails:rules:run-search-tests
|
||||
|
||||
.ci-config-validation-base:
|
||||
extends:
|
||||
- .rspec-base-pg14
|
||||
- .rails:rules:rspec-ci-config-validation
|
||||
stage: test
|
||||
after_script: [] # override after_script from .rspec-base
|
||||
|
||||
.db-job-base:
|
||||
extends:
|
||||
- .rails-job-base
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
83d81744aa7215577e8b0bef40723a787de26793
|
||||
ab05fbe1d1f84e10fa4a28d857b6a08429ebe90b
|
||||
|
|
|
|||
|
|
@ -243,7 +243,11 @@ export default {
|
|||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
this.$emit('select-issuable', { iid: this.issuableIid, webUrl: this.issuableLinkHref });
|
||||
this.$emit('select-issuable', {
|
||||
iid: this.issuableIid,
|
||||
webUrl: this.issuableLinkHref,
|
||||
fullPath: this.workItemFullPath,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ export default {
|
|||
},
|
||||
isUpdating: {
|
||||
handler(isUpdating) {
|
||||
this.sortable.option('disabled', isUpdating);
|
||||
this.sortable?.option('disabled', isUpdating);
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ export default {
|
|||
if (isEmpty(this.workItem)) {
|
||||
this.setEmptyState();
|
||||
}
|
||||
if (!this.isModal && this.workItem.namespace) {
|
||||
if (!(this.isModal || this.isDrawer) && this.workItem.namespace) {
|
||||
const path = this.workItem.namespace.fullPath
|
||||
? ` · ${this.workItem.namespace.fullPath}`
|
||||
: '';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
<script>
|
||||
import { GlLink, GlDrawer } from '@gitlab/ui';
|
||||
import { escapeRegExp } from 'lodash';
|
||||
import deleteWorkItemMutation from '~/work_items/graphql/delete_work_item.mutation.graphql';
|
||||
import * as Sentry from '~/sentry/sentry_browser_wrapper';
|
||||
import { visitUrl } from '~/lib/utils/url_utility';
|
||||
|
||||
export default {
|
||||
name: 'WorkItemDrawer',
|
||||
|
|
@ -10,6 +12,7 @@ export default {
|
|||
GlDrawer,
|
||||
WorkItemDetail: () => import('~/work_items/components/work_item_detail.vue'),
|
||||
},
|
||||
inject: ['fullPath'],
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
open: {
|
||||
|
|
@ -22,6 +25,11 @@ export default {
|
|||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
activeItemFullPath() {
|
||||
return this.activeItem?.fullPath;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async deleteWorkItem({ workItemId }) {
|
||||
try {
|
||||
|
|
@ -38,6 +46,24 @@ export default {
|
|||
Sentry.captureException(error);
|
||||
}
|
||||
},
|
||||
redirectToWorkItem() {
|
||||
const workItem = this.activeItem;
|
||||
const escapedFullPath = escapeRegExp(this.fullPath);
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
const regex = new RegExp(`groups\/${escapedFullPath}\/-\/(work_items|epics)\/\\d+`);
|
||||
const isWorkItemPath = regex.test(workItem.webUrl);
|
||||
|
||||
if (isWorkItemPath) {
|
||||
this.$router.push({
|
||||
name: 'workItem',
|
||||
params: {
|
||||
iid: workItem.iid,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
visitUrl(workItem.webUrl);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -51,14 +77,18 @@ export default {
|
|||
@close="$emit('close')"
|
||||
>
|
||||
<template #title>
|
||||
<gl-link :href="activeItem.webUrl" class="gl-text-black-normal">{{
|
||||
__('Open full view')
|
||||
}}</gl-link>
|
||||
<gl-link
|
||||
class="gl-text-black-normal"
|
||||
:href="activeItem.webUrl"
|
||||
@click.prevent="redirectToWorkItem"
|
||||
>{{ __('Open full view') }}</gl-link
|
||||
>
|
||||
</template>
|
||||
<template #default>
|
||||
<work-item-detail
|
||||
:key="activeItem.iid"
|
||||
:work-item-iid="activeItem.iid"
|
||||
:modal-work-item-full-path="activeItemFullPath"
|
||||
is-drawer
|
||||
class="gl-pt-0! work-item-drawer"
|
||||
@deleteWorkItem="deleteWorkItem"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import { GlFilteredSearchToken, GlLoadingIcon } from '@gitlab/ui';
|
||||
import { isEmpty } from 'lodash';
|
||||
import * as Sentry from '~/sentry/sentry_browser_wrapper';
|
||||
import IssueCardStatistics from 'ee_else_ce/issues/list/components/issue_card_statistics.vue';
|
||||
import IssueCardTimeInfo from 'ee_else_ce/issues/list/components/issue_card_time_info.vue';
|
||||
|
|
@ -13,7 +14,6 @@ import {
|
|||
} from 'ee_else_ce/issues/list/utils';
|
||||
import { TYPENAME_USER } from '~/graphql_shared/constants';
|
||||
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
||||
import { visitUrl } from '~/lib/utils/url_utility';
|
||||
import {
|
||||
STATUS_ALL,
|
||||
STATUS_CLOSED,
|
||||
|
|
@ -50,9 +50,10 @@ import {
|
|||
TOKEN_TYPE_TYPE,
|
||||
} from '~/vue_shared/components/filtered_search_bar/constants';
|
||||
import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_root.vue';
|
||||
import WorkItemDrawer from '~/work_items/components/work_item_drawer.vue';
|
||||
import { DEFAULT_PAGE_SIZE, issuableListTabs } from '~/vue_shared/issuable/list/constants';
|
||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import { STATE_CLOSED, WORK_ITEM_TYPE_ENUM_EPIC } from '../constants';
|
||||
import { STATE_CLOSED, STATE_OPEN, WORK_ITEM_TYPE_ENUM_EPIC } from '../constants';
|
||||
import getWorkItemsQuery from '../graphql/list/get_work_items.query.graphql';
|
||||
import { sortOptions, urlSortParams } from './list/constants';
|
||||
|
||||
|
|
@ -66,6 +67,11 @@ const MilestoneToken = () =>
|
|||
import('~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue');
|
||||
const UserToken = () => import('~/vue_shared/components/filtered_search_bar/tokens/user_token.vue');
|
||||
|
||||
const statusMap = {
|
||||
[STATUS_OPEN]: STATE_OPEN,
|
||||
[STATUS_CLOSED]: STATE_CLOSED,
|
||||
};
|
||||
|
||||
export default {
|
||||
issuableListTabs,
|
||||
sortOptions,
|
||||
|
|
@ -74,6 +80,7 @@ export default {
|
|||
IssuableList,
|
||||
IssueCardStatistics,
|
||||
IssueCardTimeInfo,
|
||||
WorkItemDrawer,
|
||||
},
|
||||
mixins: [glFeatureFlagMixin()],
|
||||
inject: [
|
||||
|
|
@ -112,6 +119,8 @@ export default {
|
|||
state: STATUS_OPEN,
|
||||
tabCounts: {},
|
||||
workItems: [],
|
||||
activeItem: null,
|
||||
isRefetching: false,
|
||||
};
|
||||
},
|
||||
apollo: {
|
||||
|
|
@ -163,6 +172,9 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
isItemSelected() {
|
||||
return !isEmpty(this.activeItem);
|
||||
},
|
||||
apiFilterParams() {
|
||||
return convertToApiParams(this.filterTokens);
|
||||
},
|
||||
|
|
@ -173,11 +185,14 @@ export default {
|
|||
hasQualityManagementFeature: this.hasQualityManagementFeature,
|
||||
});
|
||||
},
|
||||
workItemDrawerEnabled() {
|
||||
return this.glFeatures?.issuesListDrawer;
|
||||
},
|
||||
hasSearch() {
|
||||
return Boolean(this.searchQuery);
|
||||
},
|
||||
isLoading() {
|
||||
return this.$apollo.queries.workItems.loading;
|
||||
return this.$apollo.queries.workItems.loading && !this.isRefetching;
|
||||
},
|
||||
isOpenTab() {
|
||||
return this.state === STATUS_OPEN;
|
||||
|
|
@ -339,6 +354,9 @@ export default {
|
|||
this.autocompleteCache = new AutocompleteCache();
|
||||
},
|
||||
methods: {
|
||||
handleSelect(item) {
|
||||
this.activeItem = item;
|
||||
},
|
||||
fetchEmojis(search) {
|
||||
return this.autocompleteCache.fetch({
|
||||
url: this.autocompleteAwardEmojisPath,
|
||||
|
|
@ -428,20 +446,22 @@ export default {
|
|||
Sentry.captureException(error);
|
||||
});
|
||||
},
|
||||
redirectToWorkItem(workItem) {
|
||||
const regex = /groups\/.+?\/-\/work_items\/\d+/;
|
||||
const isWorkItemPath = regex.test(workItem.webUrl);
|
||||
|
||||
if (isWorkItemPath) {
|
||||
this.$router.push({
|
||||
name: 'workItem',
|
||||
params: {
|
||||
iid: workItem.iid,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
visitUrl(workItem.webUrl);
|
||||
deleteItem() {
|
||||
this.activeItem = null;
|
||||
this.refetchItems();
|
||||
},
|
||||
handleStatusChange(workItem) {
|
||||
if (this.state === STATUS_ALL) {
|
||||
return;
|
||||
}
|
||||
if (statusMap[this.state] !== workItem.state) {
|
||||
this.refetchItems();
|
||||
}
|
||||
},
|
||||
async refetchItems() {
|
||||
this.isRefetching = true;
|
||||
await this.$apollo.queries.workItems.refetch();
|
||||
this.isRefetching = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -450,70 +470,80 @@ export default {
|
|||
<template>
|
||||
<gl-loading-icon v-if="!isInitialAllCountSet && !error" class="gl-mt-5" size="lg" />
|
||||
|
||||
<issuable-list
|
||||
v-else-if="hasAnyIssues || error"
|
||||
:current-tab="state"
|
||||
:default-page-size="pageSize"
|
||||
:error="error"
|
||||
:has-next-page="pageInfo.hasNextPage"
|
||||
:has-previous-page="pageInfo.hasPreviousPage"
|
||||
:initial-sort-by="sortKey"
|
||||
:issuables="workItems"
|
||||
:issuables-loading="isLoading"
|
||||
:show-bulk-edit-sidebar="showBulkEditSidebar"
|
||||
namespace="work-items"
|
||||
recent-searches-storage-key="issues"
|
||||
:search-tokens="searchTokens"
|
||||
show-filtered-search-friendly-text
|
||||
:show-page-size-selector="showPageSizeSelector"
|
||||
:show-pagination-controls="showPaginationControls"
|
||||
show-work-item-type-icon
|
||||
:sort-options="$options.sortOptions"
|
||||
:tab-counts="tabCounts"
|
||||
:tabs="$options.issuableListTabs"
|
||||
use-keyset-pagination
|
||||
prevent-redirect
|
||||
@click-tab="handleClickTab"
|
||||
@dismiss-alert="error = undefined"
|
||||
@filter="handleFilter"
|
||||
@next-page="handleNextPage"
|
||||
@page-size-change="handlePageSizeChange"
|
||||
@previous-page="handlePreviousPage"
|
||||
@sort="handleSort"
|
||||
@select-issuable="redirectToWorkItem"
|
||||
>
|
||||
<template #nav-actions>
|
||||
<slot name="nav-actions"></slot>
|
||||
</template>
|
||||
<div v-else-if="hasAnyIssues || error">
|
||||
<work-item-drawer
|
||||
v-if="workItemDrawerEnabled"
|
||||
:active-item="activeItem"
|
||||
:open="isItemSelected"
|
||||
@close="activeItem = null"
|
||||
@addChild="refetchItems"
|
||||
@workItemDeleted="deleteItem"
|
||||
@work-item-updated="handleStatusChange"
|
||||
/>
|
||||
<issuable-list
|
||||
:current-tab="state"
|
||||
:default-page-size="pageSize"
|
||||
:error="error"
|
||||
:has-next-page="pageInfo.hasNextPage"
|
||||
:has-previous-page="pageInfo.hasPreviousPage"
|
||||
:initial-sort-by="sortKey"
|
||||
:issuables="workItems"
|
||||
:issuables-loading="isLoading"
|
||||
:show-bulk-edit-sidebar="showBulkEditSidebar"
|
||||
namespace="work-items"
|
||||
recent-searches-storage-key="issues"
|
||||
:search-tokens="searchTokens"
|
||||
show-filtered-search-friendly-text
|
||||
:show-page-size-selector="showPageSizeSelector"
|
||||
:show-pagination-controls="showPaginationControls"
|
||||
show-work-item-type-icon
|
||||
:sort-options="$options.sortOptions"
|
||||
:tab-counts="tabCounts"
|
||||
:tabs="$options.issuableListTabs"
|
||||
use-keyset-pagination
|
||||
:prevent-redirect="workItemDrawerEnabled"
|
||||
@click-tab="handleClickTab"
|
||||
@dismiss-alert="error = undefined"
|
||||
@filter="handleFilter"
|
||||
@next-page="handleNextPage"
|
||||
@page-size-change="handlePageSizeChange"
|
||||
@previous-page="handlePreviousPage"
|
||||
@sort="handleSort"
|
||||
@select-issuable="handleSelect"
|
||||
>
|
||||
<template #nav-actions>
|
||||
<slot name="nav-actions"></slot>
|
||||
</template>
|
||||
|
||||
<template #timeframe="{ issuable = {} }">
|
||||
<issue-card-time-info :issue="issuable" />
|
||||
</template>
|
||||
<template #timeframe="{ issuable = {} }">
|
||||
<issue-card-time-info :issue="issuable" />
|
||||
</template>
|
||||
|
||||
<template #status="{ issuable }">
|
||||
{{ getStatus(issuable) }}
|
||||
</template>
|
||||
<template #status="{ issuable }">
|
||||
{{ getStatus(issuable) }}
|
||||
</template>
|
||||
|
||||
<template #statistics="{ issuable = {} }">
|
||||
<issue-card-statistics :issue="issuable" />
|
||||
</template>
|
||||
<template #statistics="{ issuable = {} }">
|
||||
<issue-card-statistics :issue="issuable" />
|
||||
</template>
|
||||
|
||||
<template #empty-state>
|
||||
<slot name="list-empty-state" :has-search="hasSearch" :is-open-tab="isOpenTab"></slot>
|
||||
</template>
|
||||
<template #empty-state>
|
||||
<slot name="list-empty-state" :has-search="hasSearch" :is-open-tab="isOpenTab"></slot>
|
||||
</template>
|
||||
|
||||
<template #list-body>
|
||||
<slot name="list-body"></slot>
|
||||
</template>
|
||||
<template #list-body>
|
||||
<slot name="list-body"></slot>
|
||||
</template>
|
||||
|
||||
<template #bulk-edit-actions="{ checkedIssuables }">
|
||||
<slot name="bulk-edit-actions" :checked-issuables="checkedIssuables"></slot>
|
||||
</template>
|
||||
<template #bulk-edit-actions="{ checkedIssuables }">
|
||||
<slot name="bulk-edit-actions" :checked-issuables="checkedIssuables"></slot>
|
||||
</template>
|
||||
|
||||
<template #sidebar-items="{ checkedIssuables }">
|
||||
<slot name="sidebar-items" :checked-issuables="checkedIssuables"></slot>
|
||||
</template>
|
||||
</issuable-list>
|
||||
<template #sidebar-items="{ checkedIssuables }">
|
||||
<slot name="sidebar-items" :checked-issuables="checkedIssuables"></slot>
|
||||
</template>
|
||||
</issuable-list>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<slot name="page-empty-state"></slot>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ require 'gon'
|
|||
require 'fogbugz'
|
||||
|
||||
class ApplicationController < BaseActionController
|
||||
use Gitlab::Middleware::ActionControllerStaticContext
|
||||
|
||||
include Gitlab::GonHelper
|
||||
include Gitlab::NoCacheHeaders
|
||||
include GitlabRoutingHelper
|
||||
|
|
@ -439,14 +441,27 @@ class ApplicationController < BaseActionController
|
|||
end
|
||||
|
||||
def set_current_context(&block)
|
||||
static_context =
|
||||
if Feature.enabled?(:controller_static_context, Feature.current_request)
|
||||
{} # middleware should've included caller_id and feature_category
|
||||
else
|
||||
{ caller_id: self.class.endpoint_id_for_action(action_name) }
|
||||
end
|
||||
|
||||
# even though feature_category is pre-populated by
|
||||
# Gitlab::Middleware::ActionControllerStaticContext
|
||||
# using the static annotation on controllers, the
|
||||
# controllers can override feature_category conditionally
|
||||
static_context[:feature_category] = feature_category if feature_category.present?
|
||||
|
||||
Gitlab::ApplicationContext.push(
|
||||
user: -> { context_user },
|
||||
project: -> { @project if @project&.persisted? },
|
||||
namespace: -> { @group if @group&.persisted? },
|
||||
caller_id: self.class.endpoint_id_for_action(action_name),
|
||||
remote_ip: request.ip,
|
||||
feature_category: feature_category,
|
||||
**http_router_rule_context
|
||||
static_context.merge({
|
||||
user: -> { context_user },
|
||||
project: -> { @project if @project&.persisted? },
|
||||
namespace: -> { @group if @group&.persisted? },
|
||||
remote_ip: request.ip,
|
||||
**http_router_rule_context
|
||||
})
|
||||
)
|
||||
yield
|
||||
ensure
|
||||
|
|
|
|||
|
|
@ -148,6 +148,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
create_params = issue_params.merge(
|
||||
add_related_issue: add_related_issue,
|
||||
merge_request_to_resolve_discussions_of: params[:merge_request_to_resolve_discussions_of],
|
||||
observability_links: params[:observability_links],
|
||||
discussion_to_resolve: params[:discussion_to_resolve]
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,15 @@ class Projects::JobsController < Projects::ApplicationController
|
|||
|
||||
render json: Ci::JobSerializer
|
||||
.new(project: @project, current_user: @current_user)
|
||||
.represent(@build.present(current_user: current_user), {}, BuildDetailsEntity)
|
||||
.represent(
|
||||
@build.present(current_user: current_user),
|
||||
{
|
||||
# Pipeline will show all failed builds by default if not using disable_failed_builds
|
||||
disable_coverage: true,
|
||||
disable_failed_builds: true
|
||||
},
|
||||
BuildDetailsEntity
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ module Types
|
|||
|
||||
connection_type_class Types::LimitedCountableConnectionType
|
||||
|
||||
markdown_field :description_html, null: true
|
||||
|
||||
field :id, ::Types::GlobalIDType[::Ml::ModelVersion], null: false, description: 'ID of the model version.'
|
||||
|
||||
field :created_at, Types::TimeType, null: false, description: 'Date of creation.'
|
||||
|
|
@ -29,6 +31,9 @@ module Types
|
|||
|
||||
field :_links, ::Types::Ml::ModelVersionLinksType, null: false, method: :itself,
|
||||
description: 'Map of links to perform actions on the model version.'
|
||||
def description_html_resolver
|
||||
::MarkupHelper.markdown(object.description, context.to_h.dup)
|
||||
end
|
||||
end
|
||||
# rubocop: enable Graphql/AuthorizeTypes
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ module Projects
|
|||
can_write_model_registry: can_write_model_registry?(user, project),
|
||||
mlflow_tracking_url: mlflow_tracking_url(project),
|
||||
max_allowed_file_size: max_allowed_file_size(project),
|
||||
markdown_preview_path: ::Gitlab::Routing.url_helpers.project_ml_preview_markdown_url(project)
|
||||
markdown_preview_path: preview_markdown_path(project)
|
||||
}
|
||||
|
||||
to_json(data)
|
||||
|
|
@ -30,7 +30,7 @@ module Projects
|
|||
model_name: model.name,
|
||||
max_allowed_file_size: max_allowed_file_size(project),
|
||||
latest_version: model.latest_version&.version,
|
||||
markdown_preview_path: ::Gitlab::Routing.url_helpers.project_ml_preview_markdown_url(project)
|
||||
markdown_preview_path: preview_markdown_path(project)
|
||||
}
|
||||
|
||||
to_json(data)
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ module WorkItemsHelper
|
|||
is_signed_in: current_user.present?.to_s,
|
||||
show_new_issue_link: can?(current_user, :create_work_item, group).to_s,
|
||||
issues_list_path: issues_group_path(group),
|
||||
report_abuse_path: add_category_abuse_reports_path,
|
||||
labels_manage_path: group_labels_path(group),
|
||||
can_admin_label: can?(current_user, :admin_label, group).to_s
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ module VirtualRegistries
|
|||
|
||||
validates :group, top_level_group: true, presence: true, uniqueness: true
|
||||
validates :cache_validity_hours, numericality: { greater_than_or_equal_to: 0, only_integer: true }
|
||||
|
||||
scope :for_group, ->(group) { where(group: group) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -675,7 +675,7 @@ class IssuableBaseService < ::BaseContainerService
|
|||
end
|
||||
|
||||
def allowed_create_params(params)
|
||||
params
|
||||
params.except(:observability_links)
|
||||
end
|
||||
|
||||
def allowed_update_params(params)
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@
|
|||
will stay unresolved. Ask someone with permission to resolve
|
||||
= @discussion_to_resolve ? 'it.' : 'them.'
|
||||
|
||||
= render_if_exists 'observability/observability_links', observability_values: @observability_values, issuable: issuable, project: project
|
||||
|
||||
- is_footer = !(issuable.is_a?(MergeRequest) && issuable.new_record?)
|
||||
.gl-mt-5{ class: (is_footer ? "footer-block" : "middle-block") }
|
||||
- if !issuable.persisted? && !issuable.project.empty_repo? && (guide_url = issuable.project.present.contribution_guide_path)
|
||||
|
|
|
|||
|
|
@ -22,12 +22,7 @@
|
|||
= html_escape(s_('%{time_ago}')) % { time_ago: time_ago_with_tooltip(key.created_at).html_safe}
|
||||
|
||||
%td{ data: { label: s_('Profiles|Last used'), testid: 'last-used' } }
|
||||
-# TODO: Remove this conditional when https://gitlab.com/gitlab-org/gitlab/-/issues/324764 is resolved.
|
||||
- if Feature.enabled?(:disable_ssh_key_used_tracking)
|
||||
= _('Unavailable')
|
||||
= link_to sprite_icon('question-o'), help_page_path('user/ssh', anchor: 'view-your-accounts-ssh-keys')
|
||||
- else
|
||||
= key.last_used_at ? time_ago_with_tooltip(key.last_used_at) : _('Never')
|
||||
= key.last_used_at ? time_ago_with_tooltip(key.last_used_at) : _('Never')
|
||||
|
||||
%td{ data: { label: s_('Profiles|Expires'), testid: 'expires' } }
|
||||
- if key.expired?
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
name: controller_static_context
|
||||
feature_issue_url: https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/3593
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/157628
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/470047
|
||||
milestone: '17.4'
|
||||
group: group::scalability
|
||||
type: beta
|
||||
default_enabled: false
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: disable_ssh_key_used_tracking
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54335
|
||||
rollout_issue_url: https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/889
|
||||
milestone: '13.9'
|
||||
type: development
|
||||
group: group::source code
|
||||
default_enabled: false
|
||||
|
|
@ -461,6 +461,10 @@ vulnerability_occurrences:
|
|||
- table: ci_pipelines
|
||||
column: latest_pipeline_id
|
||||
on_delete: async_nullify
|
||||
vulnerability_scanners:
|
||||
- table: projects
|
||||
column: project_id
|
||||
on_delete: async_delete
|
||||
vulnerability_state_transitions:
|
||||
- table: ci_pipelines
|
||||
column: state_changed_at_pipeline_id
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
table_name: observability_logs_issues_connections
|
||||
classes:
|
||||
- Observability::LogsIssuesConnection
|
||||
feature_categories:
|
||||
- logging
|
||||
description: Represents the join between an Issue and an Observability log stored in Gitlab Observability Backend.
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/162194
|
||||
milestone: '17.4'
|
||||
gitlab_schema: gitlab_main_cell
|
||||
sharding_key:
|
||||
project_id: projects
|
||||
|
|
@ -7,8 +7,6 @@ feature_categories:
|
|||
description: Stores information about the vulnerability scanners used by projects
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/6896
|
||||
milestone: '11.4'
|
||||
gitlab_schema: gitlab_main_cell
|
||||
allow_cross_foreign_keys:
|
||||
- gitlab_main_clusterwide
|
||||
gitlab_schema: gitlab_sec
|
||||
sharding_key:
|
||||
project_id: projects
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateLogsIssuesConnections < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.4'
|
||||
|
||||
def up
|
||||
create_table :observability_logs_issues_connections do |t|
|
||||
t.references :issue, null: false, foreign_key: { to_table: :issues, on_delete: :cascade }, index: false
|
||||
t.bigint :project_id, null: false, index: true
|
||||
t.datetime_with_timezone :log_timestamp, null: false
|
||||
t.timestamps_with_timezone null: false
|
||||
t.integer :severity_number, null: false, limit: 2
|
||||
t.text :service_name, null: false, limit: 500
|
||||
t.text :trace_identifier, null: false, limit: 128
|
||||
t.text :log_fingerprint, null: false, limit: 128
|
||||
|
||||
t.index [:issue_id, :service_name, :severity_number, :log_timestamp, :log_fingerprint, :trace_identifier],
|
||||
unique: true,
|
||||
name: 'idx_o11y_log_issue_conn_on_issue_id_logs_search_metadata'
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
drop_table :observability_logs_issues_connections
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddProjectsO11yLogsFk < Gitlab::Database::Migration[2.2]
|
||||
disable_ddl_transaction!
|
||||
milestone '17.4'
|
||||
|
||||
def up
|
||||
add_concurrent_foreign_key :observability_logs_issues_connections,
|
||||
:projects,
|
||||
column: :project_id,
|
||||
on_delete: :cascade
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_foreign_key :observability_logs_issues_connections, column: :project_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoveProjectsVulnerabilityScannersProjectIdFk < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.4'
|
||||
disable_ddl_transaction!
|
||||
|
||||
FOREIGN_KEY_NAME = "fk_rails_5c9d42a221"
|
||||
|
||||
def up
|
||||
with_lock_retries do
|
||||
remove_foreign_key_if_exists(:vulnerability_scanners, :projects,
|
||||
name: FOREIGN_KEY_NAME, reverse_lock_order: true)
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_foreign_key(:vulnerability_scanners, :projects,
|
||||
name: FOREIGN_KEY_NAME, column: :project_id,
|
||||
target_column: :id, on_delete: :cascade)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
af0d599ff84d3041a9fdf145df8e302c47b27b003f47d5d5de60482dfcd169e8
|
||||
|
|
@ -0,0 +1 @@
|
|||
df6aa8fe56933d3aa7a582e0cc8478538362b8ce0649acf4088b5d2791b00ce9
|
||||
|
|
@ -0,0 +1 @@
|
|||
706048cbc961983c9cf50d3dfd97d30637b4f17f334bf05207d5974962a6b2dd
|
||||
|
|
@ -13964,6 +13964,31 @@ CREATE SEQUENCE oauth_openid_requests_id_seq
|
|||
|
||||
ALTER SEQUENCE oauth_openid_requests_id_seq OWNED BY oauth_openid_requests.id;
|
||||
|
||||
CREATE TABLE observability_logs_issues_connections (
|
||||
id bigint NOT NULL,
|
||||
issue_id bigint NOT NULL,
|
||||
project_id bigint NOT NULL,
|
||||
log_timestamp timestamp with time zone NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
severity_number smallint NOT NULL,
|
||||
service_name text NOT NULL,
|
||||
trace_identifier text NOT NULL,
|
||||
log_fingerprint text NOT NULL,
|
||||
CONSTRAINT check_80945187b6 CHECK ((char_length(trace_identifier) <= 128)),
|
||||
CONSTRAINT check_ab38275544 CHECK ((char_length(log_fingerprint) <= 128)),
|
||||
CONSTRAINT check_d86a20d56b CHECK ((char_length(service_name) <= 500))
|
||||
);
|
||||
|
||||
CREATE SEQUENCE observability_logs_issues_connections_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE observability_logs_issues_connections_id_seq OWNED BY observability_logs_issues_connections.id;
|
||||
|
||||
CREATE TABLE observability_metrics_issues_connections (
|
||||
id bigint NOT NULL,
|
||||
issue_id bigint NOT NULL,
|
||||
|
|
@ -21616,6 +21641,8 @@ ALTER TABLE ONLY oauth_device_grants ALTER COLUMN id SET DEFAULT nextval('oauth_
|
|||
|
||||
ALTER TABLE ONLY oauth_openid_requests ALTER COLUMN id SET DEFAULT nextval('oauth_openid_requests_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY observability_logs_issues_connections ALTER COLUMN id SET DEFAULT nextval('observability_logs_issues_connections_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY observability_metrics_issues_connections ALTER COLUMN id SET DEFAULT nextval('observability_metrics_issues_connections_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY onboarding_progresses ALTER COLUMN id SET DEFAULT nextval('onboarding_progresses_id_seq'::regclass);
|
||||
|
|
@ -23991,6 +24018,9 @@ ALTER TABLE ONLY oauth_device_grants
|
|||
ALTER TABLE ONLY oauth_openid_requests
|
||||
ADD CONSTRAINT oauth_openid_requests_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY observability_logs_issues_connections
|
||||
ADD CONSTRAINT observability_logs_issues_connections_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY observability_metrics_issues_connections
|
||||
ADD CONSTRAINT observability_metrics_issues_connections_pkey PRIMARY KEY (id);
|
||||
|
||||
|
|
@ -26304,6 +26334,8 @@ CREATE UNIQUE INDEX idx_namespace_settings_on_default_compliance_framework_id ON
|
|||
|
||||
CREATE INDEX idx_namespace_settings_on_last_dormant_member_review_at ON namespace_settings USING btree (last_dormant_member_review_at) WHERE (remove_dormant_members IS TRUE);
|
||||
|
||||
CREATE UNIQUE INDEX idx_o11y_log_issue_conn_on_issue_id_logs_search_metadata ON observability_logs_issues_connections USING btree (issue_id, service_name, severity_number, log_timestamp, log_fingerprint, trace_identifier);
|
||||
|
||||
CREATE UNIQUE INDEX idx_o11y_metric_issue_conn_on_issue_id_metric_type_name ON observability_metrics_issues_connections USING btree (issue_id, metric_type, metric_name);
|
||||
|
||||
CREATE UNIQUE INDEX idx_on_approval_group_rules_any_approver_type ON approval_group_rules USING btree (group_id, rule_type) WHERE (rule_type = 4);
|
||||
|
|
@ -28758,6 +28790,8 @@ CREATE UNIQUE INDEX index_oauth_device_grants_on_user_code ON oauth_device_grant
|
|||
|
||||
CREATE INDEX index_oauth_openid_requests_on_access_grant_id ON oauth_openid_requests USING btree (access_grant_id);
|
||||
|
||||
CREATE INDEX index_observability_logs_issues_connections_on_project_id ON observability_logs_issues_connections USING btree (project_id);
|
||||
|
||||
CREATE INDEX index_observability_metrics_issues_connections_on_namespace_id ON observability_metrics_issues_connections USING btree (namespace_id);
|
||||
|
||||
CREATE UNIQUE INDEX index_on_deploy_keys_id_and_type_and_public ON keys USING btree (id, type) WHERE (public = true);
|
||||
|
|
@ -33269,6 +33303,9 @@ ALTER TABLE ONLY catalog_resource_components
|
|||
ALTER TABLE ONLY ci_build_pending_states
|
||||
ADD CONSTRAINT fk_861cd17da3_p FOREIGN KEY (partition_id, build_id) REFERENCES p_ci_builds(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY observability_logs_issues_connections
|
||||
ADD CONSTRAINT fk_86c5fb94cc FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY packages_package_files
|
||||
ADD CONSTRAINT fk_86f0f182f8 FOREIGN KEY (package_id) REFERENCES packages_packages(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
@ -34748,9 +34785,6 @@ ALTER TABLE ONLY cluster_providers_gcp
|
|||
ALTER TABLE ONLY insights
|
||||
ADD CONSTRAINT fk_rails_5c4391f60a FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY vulnerability_scanners
|
||||
ADD CONSTRAINT fk_rails_5c9d42a221 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY reviews
|
||||
ADD CONSTRAINT fk_rails_5ca11d8c31 FOREIGN KEY (merge_request_id) REFERENCES merge_requests(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
@ -34766,6 +34800,9 @@ ALTER TABLE ONLY packages_nuget_symbols
|
|||
ALTER TABLE ONLY resource_weight_events
|
||||
ADD CONSTRAINT fk_rails_5eb5cb92a1 FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY observability_logs_issues_connections
|
||||
ADD CONSTRAINT fk_rails_5f0f58ca3a FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY approval_project_rules
|
||||
ADD CONSTRAINT fk_rails_5fb4dd100b FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
|
|||
|
|
@ -27133,6 +27133,7 @@ Version of a machine learning model.
|
|||
| <a id="mlmodelversioncandidate"></a>`candidate` | [`MlCandidate!`](#mlcandidate) | Metrics, params and metadata for the model version. |
|
||||
| <a id="mlmodelversioncreatedat"></a>`createdAt` | [`Time!`](#time) | Date of creation. |
|
||||
| <a id="mlmodelversiondescription"></a>`description` | [`String`](#string) | Description of the version. |
|
||||
| <a id="mlmodelversiondescriptionhtml"></a>`descriptionHtml` | [`String`](#string) | GitLab Flavored Markdown rendering of `description`. |
|
||||
| <a id="mlmodelversionid"></a>`id` | [`MlModelVersionID!`](#mlmodelversionid) | ID of the model version. |
|
||||
| <a id="mlmodelversionpackageid"></a>`packageId` | [`PackagesPackageID!`](#packagespackageid) | Package for model version artifacts. |
|
||||
| <a id="mlmodelversionversion"></a>`version` | [`String!`](#string) | Name of the version. |
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ GitLab package.
|
|||
|
||||
## Prerequisites
|
||||
|
||||
- Create an [upgrade plan](../plan_your_upgrade.md).
|
||||
We recommend upgrading in a test environment first and having a [rollback plan](../plan_your_upgrade.md#rollback-plan)
|
||||
to reduce the risk of unplanned outages and extended downtime.
|
||||
- Decide when to upgrade by viewing the [supported upgrade paths](../index.md#upgrade-paths).
|
||||
You can't directly skip major versions (for example, go from 10.3 to 12.7 in one step).
|
||||
- If you are upgrading from a non-package installation to a GitLab package installation, see
|
||||
|
|
|
|||
|
|
@ -59,12 +59,11 @@ In the GitLab UI, GitLab Duo Chat knows about these areas:
|
|||
|
||||
In the IDEs, GitLab Duo Chat knows about these areas:
|
||||
|
||||
| Area | How to ask Chat |
|
||||
|-------------------------------|-----------------------|
|
||||
| Selected lines in the editor | With the lines selected, ask about `this code` or `this file`. Chat is not aware of the file; you must select the lines you want to ask about. |
|
||||
| Epics | Ask about the URL. |
|
||||
| Issues | Ask about the URL. |
|
||||
| Open files | Start working in a file, making sure that you have opened the files you want as used as context. For details, see [open tabs as context](../project/repository/code_suggestions/index.md#open-tabs-as-context). |
|
||||
| Area | How to ask Chat |
|
||||
|---------|------------------|
|
||||
| Selected lines in the editor | With the lines selected, ask about `this code` or `this file`. Chat is not aware of the file; you must select the lines you want to ask about. |
|
||||
| Epics | Ask about the URL. |
|
||||
| Issues | Ask about the URL. |
|
||||
|
||||
In addition, in the IDEs, when you use any of the slash commands,
|
||||
like `/explain`, `/refactor`, `/fix`, or `/tests,` Duo Chat has access to the
|
||||
|
|
|
|||
|
|
@ -104,9 +104,11 @@ you might write something like:
|
|||
AI is non-deterministic, so you may not get the same suggestion every time with the same input.
|
||||
To generate quality code, write clear, descriptive, specific tasks.
|
||||
|
||||
### Best practice examples
|
||||
|
||||
For use cases and best practices, follow the [GitLab Duo examples documentation](../../../gitlab_duo_examples.md).
|
||||
|
||||
## Open tabs as context
|
||||
#### Use open tabs as context
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/464767) in GitLab 17.2 [with a flag](../../../../administration/feature_flags.md) named `advanced_context_resolver`. Disabled by default.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/462750) in GitLab 17.2 [with a flag](../../../../administration/feature_flags.md) named `code_suggestions_context`. Disabled by default.
|
||||
|
|
@ -118,54 +120,30 @@ FLAG:
|
|||
The availability of this feature is controlled by a feature flag.
|
||||
For more information, see the history.
|
||||
|
||||
To get more accurate and relevant results from Code Suggestions and Code Generation, you can use
|
||||
the contents of the files open in tabs in your IDE. Similar to prompt engineering, these files
|
||||
For better results from GitLab Duo Code Suggestions, ensure that Open Tabs Context is enabled in your IDE settings.
|
||||
This feature uses the contents of the files currently open in your IDE to get more
|
||||
accurate and relevant results from Code Suggestions. Like prompt engineering, these files
|
||||
give GitLab Duo more information about the standards and practices in your code project.
|
||||
|
||||
### Enable open tabs as context
|
||||
To get the most benefit from using your open tabs as context, open the files relevant to the code
|
||||
you want to create, including configuration files. When you start work in a new file,
|
||||
Code Suggestions offers you suggestions in the new file.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- GitLab Duo Code Suggestions must be enabled for your project.
|
||||
- For GitLab self-managed instances, the `code_suggestions_context`and the
|
||||
`advanced_context_resolver` [feature flags](../../../feature_flags.md) must be enabled.
|
||||
- Use a supported code language:
|
||||
- Code Completion: All configured languages.
|
||||
- Code Generation: Go, Java, JavaScript, Kotlin, Python, Ruby, Rust, TypeScript (`.ts` and `.tsx` files),
|
||||
Vue, and YAML.
|
||||
- Requires GitLab 17.2 and later. Earlier GitLab versions that support Code Suggestions
|
||||
cannot weight the content of open tabs more heavily than other files in your project.
|
||||
- Requires a [supported code language](#advanced-context-supported-languages).
|
||||
|
||||
::Tabs
|
||||
1. Open the files you want to provide for context. Advanced Context uses the most recently
|
||||
opened or changed files for context. If you don’t want a file sent as additional context, close it.
|
||||
1. To fine-tune your Code Generation results, add code comments to your file that explain
|
||||
what you want to build. Code Generation treats your code comments like chat. Your code comments
|
||||
update the `user_instruction`, and then improve the next results you receive.
|
||||
|
||||
:::TabTitle Visual Studio Code
|
||||
|
||||
1. Install the [GitLab Workflow extension version 4.14.2 or later](https://marketplace.visualstudio.com/items?itemName=GitLab.gitlab-workflow)
|
||||
from the Visual Studio Marketplace.
|
||||
1. Configure the [extension settings](https://gitlab.com/gitlab-org/gitlab-vscode-extension#extension-settings).
|
||||
1. Enable the feature by turning on the `gitlab.aiAssistedCodeSuggestions.enabledSupportedLanguages` setting.
|
||||
|
||||
:::TabTitle JetBrains IDEs
|
||||
|
||||
For installation instructions for JetBrains IDEs, see the
|
||||
[GitLab JetBrains Plugin documentation](https://gitlab.com/gitlab-org/editor-extensions/gitlab-jetbrains-plugin#toggle-sending-open-tabs-as-context).
|
||||
|
||||
::EndTabs
|
||||
|
||||
### Use open tabs as context
|
||||
|
||||
Open the files you want to provide for context:
|
||||
|
||||
- Open tabs uses the most recently opened or changed files.
|
||||
- If you do not want a file used as additional context, close that file.
|
||||
|
||||
When you start working in a file, GitLab Duo uses your open files
|
||||
as extra context, within [truncation limits](#truncation-of-file-content).
|
||||
|
||||
You can adjust your Code Generation results by adding code comments to your file
|
||||
that explain what you want to build. Code Generation treats your code comments
|
||||
like chat. Your code comments update the `user_instruction`, and then improve
|
||||
the next results you receive.
|
||||
|
||||
### Related topics
|
||||
As you work, GitLab Duo provides code suggestions that use your other open files
|
||||
(within [truncation limits](#truncation-of-file-content))
|
||||
as extra context.
|
||||
|
||||
To learn about the code that builds the prompt, see these files:
|
||||
|
||||
|
|
@ -173,11 +151,19 @@ To learn about the code that builds the prompt, see these files:
|
|||
[`ee/lib/api/code_suggestions.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/api/code_suggestions.rb#L76)
|
||||
in the `gitlab` repository.
|
||||
- **Code Completion**:
|
||||
[`ai_gateway/code_suggestions/processing/completions.py`](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist/-/blob/fcb3f485a8f047a86a8166aad81f93b6d82106a7/ai_gateway/code_suggestions/processing/completions.py#L273) in the `modelops` repository.
|
||||
[`ai_gateway/code_suggestions/processing/completions.py`](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist/-/blob/fcb3f485a8f047a86a8166aad81f93b6d82106a7/ai_gateway/code_suggestions/processing/completions.py#L273)
|
||||
in the `modelops` repository.
|
||||
|
||||
You can provide feedback about this feature in
|
||||
We'd love your feedback about the Advanced Context feature in
|
||||
[issue 258](https://gitlab.com/gitlab-org/editor-extensions/gitlab-lsp/-/issues/258).
|
||||
|
||||
### Advanced Context supported languages
|
||||
|
||||
The Advanced Context feature supports these languages:
|
||||
|
||||
- Code Completion: all configured languages.
|
||||
- Code Generation: Go, Java, JavaScript, Kotlin, Python, Ruby, Rust, TypeScript (`.ts` and `.tsx` files), Vue, and YAML.
|
||||
|
||||
## Response time
|
||||
|
||||
Code Suggestions is powered by a generative AI model.
|
||||
|
|
|
|||
|
|
@ -141,6 +141,57 @@ To do this:
|
|||
|
||||
::EndTabs
|
||||
|
||||
## Use open tabs as context
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/editor-extensions/gitlab-lsp/-/issues/206) in GitLab 17.2.
|
||||
|
||||
To enhance the accuracy and relevance of GitLab Duo Code Suggestions, enable the use of
|
||||
open tabs as context in your IDE settings. This feature uses the contents of files most recently
|
||||
opened or changed in your IDE to provide more tailored code suggestions, within certain truncation limits.
|
||||
This extra context gives you:
|
||||
|
||||
- More accurate and relevant code suggestions
|
||||
- Better alignment with your project's standards and practices
|
||||
- Improved context for new file creation
|
||||
|
||||
Open tabs as context supports these languages:
|
||||
|
||||
- Code Completion: All configured languages.
|
||||
- Code Generation: Go, Java, JavaScript, Kotlin, Python, Ruby, Rust, TypeScript (`.ts` and `.tsx` files),
|
||||
Vue, and YAML.
|
||||
|
||||
## Enable open tabs as context
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Requires GitLab 17.1 or later.
|
||||
- For GitLab self-managed instances, enable the `code_suggestions_context`and the
|
||||
`advanced_context_resolver` [feature flags](../../../feature_flags.md).
|
||||
- GitLab Duo Code Suggestions enabled for your project
|
||||
- For Visual Studio Code, requires the GitLab Workflow extension, version 4.14.2 or later.
|
||||
|
||||
::Tabs
|
||||
|
||||
:::TabTitle Visual Studio Code
|
||||
|
||||
1. Install the [GitLab Workflow extension](https://marketplace.visualstudio.com/items?itemName=GitLab.gitlab-workflow)
|
||||
from the Visual Studio Marketplace.
|
||||
1. Configure the extension following the
|
||||
[setup instructions](https://gitlab.com/gitlab-org/gitlab-vscode-extension#extension-settings).
|
||||
1. Enable the feature by toggling the `gitlab.aiAssistedCodeSuggestions.enabledSupportedLanguages` setting.
|
||||
|
||||
:::TabTitle JetBrains IDEs
|
||||
|
||||
For installation instructions for JetBrains IDEs, see the
|
||||
[GitLab JetBrains Plugin documentation](https://gitlab.com/gitlab-org/editor-extensions/gitlab-jetbrains-plugin#toggle-sending-open-tabs-as-context).
|
||||
|
||||
::EndTabs
|
||||
|
||||
When you're ready to start coding:
|
||||
|
||||
1. Open relevant files, including configuration files, to provide better context.
|
||||
1. Close any files you don't want to be used as context.
|
||||
|
||||
## View multiple code suggestions
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/issues/1325) in GitLab 17.1.
|
||||
|
|
|
|||
|
|
@ -28,10 +28,6 @@ module API
|
|||
bad_request!(WEB_BROWSER_ERROR_MESSAGE) if MAJOR_BROWSERS.any? { |b| browser.method(:"#{b}?").call }
|
||||
end
|
||||
|
||||
def require_dependency_proxy_enabled!
|
||||
not_found! unless ::Gitlab.config.dependency_proxy.enabled
|
||||
end
|
||||
|
||||
def send_successful_response_from(service_response:)
|
||||
action, action_params = service_response.to_h.values_at(:action, :action_params)
|
||||
case action
|
||||
|
|
|
|||
|
|
@ -0,0 +1,141 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module Concerns
|
||||
module VirtualRegistries
|
||||
module Packages
|
||||
module Maven
|
||||
module RegistryEndpoints
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
desc 'Get the list of all maven virtual registries' do
|
||||
detail 'This feature was introduced in GitLab 17.4. \
|
||||
This feature is currently in an experimental state. \
|
||||
This feature is behind the `virtual_registry_maven` feature flag.'
|
||||
success ::API::Entities::VirtualRegistries::Packages::Maven::Registry
|
||||
failure [
|
||||
{ code: 400, message: 'Bad Request' },
|
||||
{ code: 401, message: 'Unauthorized' },
|
||||
{ code: 403, message: 'Forbidden' },
|
||||
{ code: 404, message: 'Not found' }
|
||||
]
|
||||
tags %w[maven_virtual_registries]
|
||||
hidden true
|
||||
end
|
||||
params do
|
||||
requires :group_id, type: Integer, desc: 'The ID of the group', allow_blank: false
|
||||
end
|
||||
get do
|
||||
group = find_group!(declared_params[:group_id])
|
||||
authorize! :read_virtual_registry, ::VirtualRegistries::Packages::Policies::Group.new(group)
|
||||
|
||||
registries = ::VirtualRegistries::Packages::Maven::Registry.for_group(group)
|
||||
|
||||
present registries, with: ::API::Entities::VirtualRegistries::Packages::Maven::Registry
|
||||
end
|
||||
|
||||
desc 'Create a new maven virtual registry' do
|
||||
detail 'This feature was introduced in GitLab 17.4. \
|
||||
This feature is currently in an experimental state. \
|
||||
This feature is behind the `virtual_registry_maven` feature flag.'
|
||||
success code: 201
|
||||
failure [
|
||||
{ code: 400, message: 'Bad request' },
|
||||
{ code: 401, message: 'Unauthorized' },
|
||||
{ code: 403, message: 'Forbidden' },
|
||||
{ code: 404, message: 'Not found' }
|
||||
]
|
||||
tags %w[maven_virtual_registries]
|
||||
hidden true
|
||||
end
|
||||
params do
|
||||
requires :group_id, type: Integer, desc: 'The ID of the group. Must be a top-level group',
|
||||
allow_blank: false
|
||||
optional :cache_validity_hours, type: Integer, desc: 'The validity of the cache in hours. Defaults to 1'
|
||||
end
|
||||
post do
|
||||
group = find_group!(declared_params[:group_id])
|
||||
authorize! :create_virtual_registry, ::VirtualRegistries::Packages::Policies::Group.new(group)
|
||||
|
||||
new_reg = ::VirtualRegistries::Packages::Maven::Registry.new(declared_params(include_missing: false))
|
||||
|
||||
render_validation_error!(new_reg) unless new_reg.save
|
||||
|
||||
created!
|
||||
end
|
||||
|
||||
route_param :id, type: Integer, desc: 'The ID of the maven virtual registry' do
|
||||
desc 'Get a specific maven virtual registry' do
|
||||
detail 'This feature was introduced in GitLab 17.4. \
|
||||
This feature is currently in an experimental state. \
|
||||
This feature is behind the `virtual_registry_maven` feature flag.'
|
||||
success ::API::Entities::VirtualRegistries::Packages::Maven::Registry
|
||||
failure [
|
||||
{ code: 400, message: 'Bad request' },
|
||||
{ code: 401, message: 'Unauthorized' },
|
||||
{ code: 403, message: 'Forbidden' },
|
||||
{ code: 404, message: 'Not found' }
|
||||
]
|
||||
tags %w[maven_virtual_registries]
|
||||
hidden true
|
||||
end
|
||||
get do
|
||||
authorize! :read_virtual_registry, registry
|
||||
|
||||
present registry, with: ::API::Entities::VirtualRegistries::Packages::Maven::Registry
|
||||
end
|
||||
|
||||
desc 'Update a specific maven virtual registry' do
|
||||
detail 'This feature was introduced in GitLab 17.4. \
|
||||
This feature is currently in an experimental state. \
|
||||
This feature is behind the `virtual_registry_maven` feature flag.'
|
||||
success ::API::Entities::VirtualRegistries::Packages::Maven::Registry
|
||||
failure [
|
||||
{ code: 400, message: 'Bad request' },
|
||||
{ code: 401, message: 'Unauthorized' },
|
||||
{ code: 403, message: 'Forbidden' },
|
||||
{ code: 404, message: 'Not found' }
|
||||
]
|
||||
tags %w[maven_virtual_registries]
|
||||
hidden true
|
||||
end
|
||||
params do
|
||||
requires :cache_validity_hours, type: Integer, desc: 'The validity of the cache in hours'
|
||||
end
|
||||
patch do
|
||||
authorize! :update_virtual_registry, registry
|
||||
|
||||
render_validation_error!(registry) unless registry.update(declared_params)
|
||||
|
||||
present registry, with: ::API::Entities::VirtualRegistries::Packages::Maven::Registry
|
||||
end
|
||||
|
||||
desc 'Delete a specific maven virtual registry' do
|
||||
detail 'This feature was introduced in GitLab 17.4. \
|
||||
This feature is currently in an experimental state. \
|
||||
This feature is behind the `virtual_registry_maven` feature flag.'
|
||||
success code: 204
|
||||
failure [
|
||||
{ code: 400, message: 'Bad request' },
|
||||
{ code: 401, message: 'Unauthorized' },
|
||||
{ code: 403, message: 'Forbidden' },
|
||||
{ code: 404, message: 'Not found' },
|
||||
{ code: 412, message: 'Precondition Failed' }
|
||||
]
|
||||
tags %w[maven_virtual_registries]
|
||||
hidden true
|
||||
end
|
||||
delete do
|
||||
authorize! :destroy_virtual_registry, registry
|
||||
|
||||
destroy_conditionally!(registry)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -10,7 +10,7 @@ module API
|
|||
|
||||
included do
|
||||
desc 'List all maven virtual registry upstreams' do
|
||||
detail 'This feature was introduced in GitLab 17.3. \
|
||||
detail 'This feature was introduced in GitLab 17.4. \
|
||||
This feature is currently in experiment state. \
|
||||
This feature behind the `virtual_registry_maven` feature flag.'
|
||||
success code: 200
|
||||
|
|
@ -30,7 +30,7 @@ module API
|
|||
end
|
||||
|
||||
desc 'Add a maven virtual registry upstream' do
|
||||
detail 'This feature was introduced in GitLab 17.3. \
|
||||
detail 'This feature was introduced in GitLab 17.4. \
|
||||
This feature is currently in experiment state. \
|
||||
This feature behind the `virtual_registry_maven` feature flag.'
|
||||
success code: 201
|
||||
|
|
@ -68,7 +68,7 @@ module API
|
|||
|
||||
route_param :upstream_id, type: Integer, desc: 'The ID of the maven virtual registry upstream' do
|
||||
desc 'Get a specific maven virtual registry upstream' do
|
||||
detail 'This feature was introduced in GitLab 17.3. \
|
||||
detail 'This feature was introduced in GitLab 17.4. \
|
||||
This feature is currently in experiment state. \
|
||||
This feature behind the `virtual_registry_maven` feature flag.'
|
||||
success code: 200
|
||||
|
|
@ -90,7 +90,7 @@ module API
|
|||
end
|
||||
|
||||
desc 'Update a maven virtual registry upstream' do
|
||||
detail 'This feature was introduced in GitLab 17.3. \
|
||||
detail 'This feature was introduced in GitLab 17.4. \
|
||||
This feature is currently in experiment state. \
|
||||
This feature behind the `virtual_registry_maven` feature flag.'
|
||||
success code: 200
|
||||
|
|
@ -121,7 +121,7 @@ module API
|
|||
end
|
||||
|
||||
desc 'Delete a maven virtual registry upstream' do
|
||||
detail 'This feature was introduced in GitLab 17.3. \
|
||||
detail 'This feature was introduced in GitLab 17.4. \
|
||||
This feature is currently in experiment state. \
|
||||
This feature behind the `virtual_registry_maven` feature flag.'
|
||||
success code: 204
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module Entities
|
||||
module VirtualRegistries
|
||||
module Packages
|
||||
module Maven
|
||||
class Registry < Grape::Entity
|
||||
expose :id, :group_id, :cache_validity_hours, :created_at, :updated_at
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -50,8 +50,6 @@ module API
|
|||
end
|
||||
|
||||
def update_last_used_at!
|
||||
return if Feature.enabled?(:disable_ssh_key_used_tracking)
|
||||
|
||||
key&.update_last_used_at
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@ module API
|
|||
|
||||
namespace 'virtual_registries/packages/maven' do
|
||||
namespace :registries do
|
||||
include ::API::Concerns::VirtualRegistries::Packages::Maven::RegistryEndpoints
|
||||
|
||||
route_param :id, type: Integer, desc: 'The ID of the maven virtual registry' do
|
||||
namespace :upstreams do
|
||||
include ::API::Concerns::VirtualRegistries::Packages::Maven::UpstreamEndpoints
|
||||
|
|
|
|||
|
|
@ -136,8 +136,12 @@ module Gitlab
|
|||
# in the best case, where all 100 changes belong to the same project, we'll execute
|
||||
# a single update statement.
|
||||
def execute
|
||||
changes_by_project.each do |changes|
|
||||
connection.execute(update_sql(changes))
|
||||
::Gitlab::Database.allow_cross_joins_across_databases(
|
||||
url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/478765'
|
||||
) do
|
||||
changes_by_project.each do |changes|
|
||||
connection.execute(update_sql(changes))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -282,20 +286,24 @@ module Gitlab
|
|||
def perform
|
||||
user_id = Users::Internal.security_bot.id
|
||||
|
||||
each_sub_batch do |sub_batch|
|
||||
cte = Gitlab::SQL::CTE.new(:batched_relation, sub_batch.limit(100))
|
||||
::Gitlab::Database.allow_cross_joins_across_databases(
|
||||
url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/478765'
|
||||
) do
|
||||
each_sub_batch do |sub_batch|
|
||||
cte = Gitlab::SQL::CTE.new(:batched_relation, sub_batch.limit(100))
|
||||
|
||||
filtered_batch = cte
|
||||
.apply_to(Migratable::Vulnerabilities::Read.all)
|
||||
.joins('INNER JOIN vulnerability_scanners ON vulnerability_scanners.id = vulnerability_reads.scanner_id')
|
||||
.where('vulnerability_scanners.external_id': REMOVED_SCANNERS.keys)
|
||||
filtered_batch = cte
|
||||
.apply_to(Migratable::Vulnerabilities::Read.all)
|
||||
.joins('INNER JOIN vulnerability_scanners ON vulnerability_scanners.id = vulnerability_reads.scanner_id')
|
||||
.where('vulnerability_scanners.external_id': REMOVED_SCANNERS.keys)
|
||||
|
||||
vulnerability_tuples = values_for_fields(
|
||||
filtered_batch, :vulnerability_id, 'vulnerability_reads.project_id', :namespace_id, :severity, :uuid
|
||||
)
|
||||
vulnerability_tuples = values_for_fields(
|
||||
filtered_batch, :vulnerability_id, 'vulnerability_reads.project_id', :namespace_id, :severity, :uuid
|
||||
)
|
||||
|
||||
connection.transaction do
|
||||
perform_bulk_writes(user_id, vulnerability_tuples)
|
||||
connection.transaction do
|
||||
perform_bulk_writes(user_id, vulnerability_tuples)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Middleware
|
||||
class ActionControllerStaticContext
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
return @app.call(env) unless Feature.enabled?(:controller_static_context, Feature.current_request)
|
||||
|
||||
req = ActionDispatch::Request.new(env)
|
||||
|
||||
action_name = req.path_parameters[:action]
|
||||
caller_id = req.controller_class.try(:endpoint_id_for_action, action_name)
|
||||
feature_category = req.controller_class.try(:feature_category_for_action, action_name).to_s
|
||||
context = {}
|
||||
context[:caller_id] = caller_id if caller_id
|
||||
context[:feature_category] = feature_category if feature_category.present?
|
||||
# We need to push the context here, so it persists after this middleware finishes
|
||||
# We need the values present in Labkit's rack middleware that surrounds this one.
|
||||
Gitlab::ApplicationContext.push(context)
|
||||
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -47,7 +47,7 @@ namespace :tw do
|
|||
CodeOwnerRule.new('Dynamic Analysis', '@rdickenson @phillipwells'),
|
||||
CodeOwnerRule.new('Editor Extensions', '@aqualls'),
|
||||
CodeOwnerRule.new('Environments', '@phillipwells'),
|
||||
CodeOwnerRule.new('Foundations', '@sselhorn'),
|
||||
CodeOwnerRule.new('Personal Productivity', '@sselhorn'),
|
||||
# CodeOwnerRule.new('Fulfillment Platform', ''),
|
||||
CodeOwnerRule.new('Fuzz Testing', '@rdickenson'),
|
||||
CodeOwnerRule.new('Geo', '@axil'),
|
||||
|
|
@ -105,7 +105,7 @@ namespace :tw do
|
|||
CodeOwnerRule.new('Distribution', '@gitlab-org/distribution'),
|
||||
CodeOwnerRule.new('Documentation Guidelines', '@sselhorn'),
|
||||
CodeOwnerRule.new('Engineering Productivity', '@gl-quality/eng-prod'),
|
||||
CodeOwnerRule.new('Foundations', '@gitlab-org/manage/foundations/engineering'),
|
||||
CodeOwnerRule.new('Personal Productivity', '@gitlab-org/foundations/engineering'),
|
||||
CodeOwnerRule.new('Gitaly', '@proglottis @toon'),
|
||||
CodeOwnerRule.new('Global Search', '@gitlab-org/search-team/migration-maintainers'),
|
||||
CodeOwnerRule.new('Remote Development',
|
||||
|
|
|
|||
|
|
@ -56956,9 +56956,6 @@ msgstr ""
|
|||
msgid "Unauthorized to update the environment"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unavailable"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unban"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -483,3 +483,11 @@ function define_trigger_branch_in_build_env() {
|
|||
echo "TRIGGER_BRANCH=${TRIGGER_BRANCH}" >> $BUILD_ENV
|
||||
fi
|
||||
}
|
||||
|
||||
function log_disk_usage() {
|
||||
echo -e "df -h"
|
||||
df -h
|
||||
|
||||
echo -e "\n\nls -lhS tmp"
|
||||
ls -lhS tmp
|
||||
}
|
||||
|
|
|
|||
|
|
@ -945,12 +945,6 @@ RSpec.describe ApplicationController, feature_category: :shared do
|
|||
expect(json_response['meta.project']).to eq(project.full_path)
|
||||
end
|
||||
|
||||
it 'sets the caller_id as controller#action' do
|
||||
get :index, format: :json
|
||||
|
||||
expect(json_response['meta.caller_id']).to eq('AnonymousController#index')
|
||||
end
|
||||
|
||||
it 'sets the feature_category as defined in the controller' do
|
||||
get :index, format: :json
|
||||
|
||||
|
|
|
|||
|
|
@ -41,17 +41,6 @@ RSpec.describe ConfirmationsController, feature_category: :system_access do
|
|||
|
||||
expect(response.body).not_to include(user.email)
|
||||
end
|
||||
|
||||
it 'sets the username and caller_id in the context' do
|
||||
expect(controller).to receive(:show).and_wrap_original do |m, *args|
|
||||
m.call(*args)
|
||||
|
||||
expect(Gitlab::ApplicationContext.current)
|
||||
.to include('meta.user' => user.username, 'meta.caller_id' => 'ConfirmationsController#show')
|
||||
end
|
||||
|
||||
perform_request
|
||||
end
|
||||
end
|
||||
|
||||
context 'user accesses the link after the expiry of confirmation token has passed' do
|
||||
|
|
@ -76,17 +65,6 @@ RSpec.describe ConfirmationsController, feature_category: :system_access do
|
|||
|
||||
expect(response.body).not_to include(user.email)
|
||||
end
|
||||
|
||||
it 'sets the username and caller_id in the context' do
|
||||
expect(controller).to receive(:show).and_wrap_original do |m, *args|
|
||||
m.call(*args)
|
||||
|
||||
expect(Gitlab::ApplicationContext.current)
|
||||
.to include('meta.user' => user.username, 'meta.caller_id' => 'ConfirmationsController#show')
|
||||
end
|
||||
|
||||
travel_to(3.days.from_now) { perform_request }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an invalid confirmation token' do
|
||||
|
|
@ -103,17 +81,6 @@ RSpec.describe ConfirmationsController, feature_category: :system_access do
|
|||
|
||||
expect(response.body).to include('Confirmation token is invalid')
|
||||
end
|
||||
|
||||
it 'sets the the caller_id in the context' do
|
||||
expect(controller).to receive(:show).and_wrap_original do |m, *args|
|
||||
expect(Gitlab::ApplicationContext.current)
|
||||
.to include('meta.caller_id' => 'ConfirmationsController#show')
|
||||
|
||||
m.call(*args)
|
||||
end
|
||||
|
||||
perform_request
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -467,19 +467,6 @@ RSpec.describe OmniauthCallbacksController, type: :controller, feature_category:
|
|||
expect(request.env['warden']).to be_authenticated
|
||||
end
|
||||
|
||||
it 'sets the username and caller_id in the context' do
|
||||
expect(controller).to receive(:atlassian_oauth2).and_wrap_original do |m, *args|
|
||||
m.call(*args)
|
||||
|
||||
expect(Gitlab::ApplicationContext.current).to include(
|
||||
'meta.user' => user.username,
|
||||
'meta.caller_id' => 'OmniauthCallbacksController#atlassian_oauth2'
|
||||
)
|
||||
end
|
||||
|
||||
post :atlassian_oauth2
|
||||
end
|
||||
|
||||
context 'when a user has 2FA enabled' do
|
||||
let(:user) { create(:atlassian_user, :two_factor, extern_uid: extern_uid) }
|
||||
|
||||
|
|
@ -742,19 +729,6 @@ RSpec.describe OmniauthCallbacksController, type: :controller, feature_category:
|
|||
expect { post :saml, params: { SAMLResponse: mock_saml_response } }.not_to change { user.identities.count }
|
||||
end
|
||||
|
||||
it 'sets the username and caller_id in the context' do
|
||||
expect(controller).to receive(:saml).and_wrap_original do |m, *args|
|
||||
m.call(*args)
|
||||
|
||||
expect(Gitlab::ApplicationContext.current).to include(
|
||||
'meta.user' => user.username,
|
||||
'meta.caller_id' => 'OmniauthCallbacksController#saml'
|
||||
)
|
||||
end
|
||||
|
||||
post :saml, params: { SAMLResponse: mock_saml_response }
|
||||
end
|
||||
|
||||
context 'with IDP bypass two factor request' do
|
||||
let(:user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'saml') }
|
||||
|
||||
|
|
|
|||
|
|
@ -83,17 +83,6 @@ RSpec.describe PasswordsController, feature_category: :system_access do
|
|||
)
|
||||
end
|
||||
end
|
||||
|
||||
it 'sets the username and caller_id in the context' do
|
||||
expect(controller).to receive(:update).and_wrap_original do |m, *args|
|
||||
m.call(*args)
|
||||
|
||||
expect(Gitlab::ApplicationContext.current)
|
||||
.to include('meta.user' => user.username, 'meta.caller_id' => 'PasswordsController#update')
|
||||
end
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -198,6 +198,16 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state, featu
|
|||
end
|
||||
end
|
||||
|
||||
it "avoids N+1 database queries", :use_sql_query_cache do
|
||||
get_show_json
|
||||
|
||||
control = ActiveRecord::QueryRecorder.new(skip_cached: false) { get_show_json }
|
||||
|
||||
create_list(:ci_build, 5, :failed, pipeline: pipeline)
|
||||
|
||||
expect { get_show_json }.to issue_same_number_of_queries_as(control)
|
||||
end
|
||||
|
||||
context 'when job is running' do
|
||||
before do
|
||||
get_show_json
|
||||
|
|
|
|||
|
|
@ -505,17 +505,6 @@ RSpec.describe RegistrationsController, feature_category: :user_profile do
|
|||
expect(User.last.name).to eq full_name(base_user_params[:first_name], base_user_params[:last_name])
|
||||
end
|
||||
|
||||
it 'sets the caller_id in the context' do
|
||||
expect(controller).to receive(:create).and_wrap_original do |m, *args|
|
||||
m.call(*args)
|
||||
|
||||
expect(Gitlab::ApplicationContext.current)
|
||||
.to include('meta.caller_id' => 'RegistrationsController#create')
|
||||
end
|
||||
|
||||
subject
|
||||
end
|
||||
|
||||
context 'when the password is weak' do
|
||||
render_views
|
||||
let_it_be(:new_user_params) { { new_user: base_user_params.merge({ password: "password" }) } }
|
||||
|
|
@ -718,16 +707,5 @@ RSpec.describe RegistrationsController, feature_category: :user_profile do
|
|||
expect_failure(s_('Profiles|You must accept the Terms of Service in order to perform this action.'))
|
||||
end
|
||||
end
|
||||
|
||||
it 'sets the username and caller_id in the context' do
|
||||
expect(controller).to receive(:destroy).and_wrap_original do |m, *args|
|
||||
m.call(*args)
|
||||
|
||||
expect(Gitlab::ApplicationContext.current)
|
||||
.to include('meta.user' => user.username, 'meta.caller_id' => 'RegistrationsController#destroy')
|
||||
end
|
||||
|
||||
post :destroy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -704,61 +704,6 @@ RSpec.describe SessionsController, feature_category: :system_access do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#set_current_context' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
context 'when signed in' do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'sets the username and caller_id in the context' do
|
||||
expect(controller).to receive(:destroy).and_wrap_original do |m, *args|
|
||||
expect(Gitlab::ApplicationContext.current)
|
||||
.to include('meta.user' => user.username, 'meta.caller_id' => 'SessionsController#destroy')
|
||||
|
||||
m.call(*args)
|
||||
end
|
||||
|
||||
delete :destroy
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not signed in' do
|
||||
it 'sets the caller_id in the context' do
|
||||
expect(controller).to receive(:new).and_wrap_original do |m, *args|
|
||||
expect(Gitlab::ApplicationContext.current)
|
||||
.to include('meta.caller_id' => 'SessionsController#new')
|
||||
expect(Gitlab::ApplicationContext.current)
|
||||
.not_to include('meta.user')
|
||||
|
||||
m.call(*args)
|
||||
end
|
||||
|
||||
get :new
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user becomes locked' do
|
||||
before do
|
||||
user.update!(failed_attempts: User.maximum_attempts.pred)
|
||||
end
|
||||
|
||||
it 'sets the caller_id in the context' do
|
||||
allow_any_instance_of(User).to receive(:lock_access!).and_wrap_original do |m, *args|
|
||||
expect(Gitlab::ApplicationContext.current)
|
||||
.to include('meta.caller_id' => 'SessionsController#create')
|
||||
expect(Gitlab::ApplicationContext.current)
|
||||
.not_to include('meta.user')
|
||||
|
||||
m.call(*args)
|
||||
end
|
||||
|
||||
post :create, params: { user: { login: user.username, password: user.password.succ } }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#destroy' do
|
||||
before do
|
||||
sign_in(user)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
# ***********************************************************************************************************
|
||||
# Tests in this folder are recommended to run locally to test ci configuration changes
|
||||
# The tests are slow due to the yaml size and the amount of nodes to process.
|
||||
# The specific bottleneck is at Gitlab::Ci::Pipeline::Chain::seed #perform!
|
||||
# ***********************************************************************************************************
|
||||
# TEST COVERAGE (Please keep this file updated)
|
||||
#
|
||||
# 1. Branch pipelines
|
||||
# GitLab.com gitlab-org/gitlab-foss Project
|
||||
# Master Pipeline Triggered by Push
|
||||
# Scheduled Master Pipeline
|
||||
# Scheduled ruby3_2 branch scheduled pipeline
|
||||
# Stable branch pipeline
|
||||
#
|
||||
# GitLab.com gitlab-org/gitlab Master Pipeline
|
||||
# Default Master Pipeline Triggered by Push
|
||||
# Scheduled pipeline - Nightly
|
||||
# Scheduled pipeline - Maintenance
|
||||
#
|
||||
# GitLab.com gitlab-org/security/gitlab
|
||||
# Default Master Pipeline
|
||||
# Scheduled pipeline - Nightly
|
||||
# Auto-deploy branch pipeline
|
||||
#
|
||||
# 2. Merge Request Pipeline (and merge train pipeline under the same contexts)
|
||||
# Unlabeled MR Changing GITALY_SERVER_VERSION
|
||||
# Unlabeled MR Changing Dangerfile, .gitlab/ci/frontend.gitlab-ci.yml
|
||||
# Unlabeled MR targeting stable branch Changing app/model/user.rb
|
||||
#
|
||||
# MR Labeled with pipeline:run-all-rspec Changing app/models/user.rb
|
||||
# MR Labeled with pipeline:expedite pipeline::expedited Changing app/models/user.rb
|
||||
# MR Labeled with pipeline::tier-1 Changing app/models/user.rb
|
||||
# MR Labeled with pipeline::tier-2 and pipeline:mr-approved Changing app/models/user.rb
|
||||
# MR Labeled with pipeline::tier-3 and pipeline:mr-approved Changing app/models/user.rb
|
||||
# MR Labeled with pipeline:run-as-if-foss Changing app/models/user.rb
|
||||
# MR Labeled with pipeline:force-run-as-if-jh Changing app/models/user.rb
|
||||
# MR Labeled with pipeline:run-as-if-jh and pipeline:mr-approved Changing app/models/user.rb
|
||||
# MR Labeled with pipeline:run-in-ruby3_2 Changing app/models/user.rb
|
||||
#
|
||||
# Automated MR
|
||||
# MR Created from release-tools/update-gitaly Source Branch Changing GITALY_SERVER_VERSION
|
||||
#
|
||||
# Stable Branch
|
||||
# MR Targeting a Stable Branch Changing app/models/user.rb
|
||||
#
|
||||
# Fork Project MRs
|
||||
# MR Created from a Fork Project Master Branch Changing package.json
|
||||
# MR Created from a Fork Project Feature Branch Changing package.json
|
||||
# ***********************************************************************************************************
|
||||
# CONTRIBUTE
|
||||
#
|
||||
# If you think we are missing coverage for an important pipeline type, please add them.
|
||||
#
|
||||
# For example, ci rule changes could break gitlab-foss pipelines, as seen in
|
||||
# https://gitlab.com/gitlab-org/quality/engineering-productivity/master-broken-incidents/-/issues/7356
|
||||
# we then added test cases by simulating pipeline for gitlab-org/gitlab-foss
|
||||
# See `with gitlab.com gitlab-org gitlab-foss project` context below for details.
|
||||
# ***********************************************************************************************************
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_relative './shared_context_and_examples'
|
||||
|
||||
RSpec.describe 'CI configuration validation - branch pipelines', feature_category: :tooling do
|
||||
include ProjectForksHelper
|
||||
include CiConfigurationValidationHelper
|
||||
|
||||
include_context 'with simulated pipeline attributes and shared project and user'
|
||||
|
||||
let(:pipeline_project) { gitlab_org_gitlab_project }
|
||||
|
||||
let(:create_pipeline_service) do
|
||||
Ci::CreatePipelineService.new(pipeline_project, user, ref: ci_commit_branch)
|
||||
end
|
||||
|
||||
let(:content) do
|
||||
pipeline_project.repository.blob_at(ci_commit_branch, '.gitlab-ci.yml').data
|
||||
end
|
||||
|
||||
subject(:pipeline) do
|
||||
trigger_source = ci_pipeline_source.to_sym
|
||||
create_pipeline_service
|
||||
.execute(trigger_source, dry_run: true, content: content, variables_attributes: variables_attributes)
|
||||
.payload
|
||||
end
|
||||
|
||||
context 'with branch pipelines' do
|
||||
let(:ci_commit_branch) { master_branch }
|
||||
let(:variables_attributes) do
|
||||
[
|
||||
*variables_attributes_base,
|
||||
{ key: 'CI_COMMIT_BRANCH', value: ci_commit_branch },
|
||||
{ key: 'CI_COMMIT_REF_NAME', value: ci_commit_branch }
|
||||
]
|
||||
end
|
||||
|
||||
context 'with gitlab.com gitlab-org/gitlab master pipeline' do
|
||||
context 'with default master pipeline' do
|
||||
let(:expected_job_name) { 'db:migrate:multi-version-upgrade' }
|
||||
|
||||
it_behaves_like 'default branch pipeline'
|
||||
end
|
||||
|
||||
context 'with scheduled nightly' do
|
||||
let(:ci_pipeline_source) { 'schedule' }
|
||||
let(:expected_job_name) { 'db:rollback single-db' }
|
||||
let(:variables_attributes) do
|
||||
super() << { key: 'SCHEDULE_TYPE', value: 'nightly' }
|
||||
end
|
||||
|
||||
it_behaves_like 'default branch pipeline'
|
||||
end
|
||||
|
||||
context 'with scheduled maintenance' do
|
||||
let(:ci_pipeline_source) { 'schedule' }
|
||||
let(:expected_job_name) { 'generate-frontend-fixtures-mapping' }
|
||||
let(:variables_attributes) do
|
||||
super() << { key: 'SCHEDULE_TYPE', value: 'maintenance' }
|
||||
end
|
||||
|
||||
it_behaves_like 'default branch pipeline'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with gitlab.com gitlab-org/gitlab ruby3_2 branch scheduled pipeline' do
|
||||
let(:ci_commit_branch) { 'ruby3_2' }
|
||||
let(:ci_pipeline_source) { 'schedule' }
|
||||
let(:expected_job_name) { 'ruby_syntax: [${RUBY_VERSION_DEFAULT}]' }
|
||||
let(:variables_attributes) do
|
||||
super() << { key: 'SCHEDULE_TYPE', value: 'nightly' }
|
||||
end
|
||||
|
||||
before do
|
||||
sync_local_files_to_project(
|
||||
gitlab_org_gitlab_project,
|
||||
user,
|
||||
ci_commit_branch,
|
||||
files: ci_glob_with_common_file_globs
|
||||
)
|
||||
end
|
||||
|
||||
it_behaves_like 'default branch pipeline'
|
||||
end
|
||||
|
||||
context 'with gitlab.com gitlab-org/gitlab stable branch pipeline' do
|
||||
let(:ci_commit_branch) { '17-1-stable-ee' }
|
||||
let(:expected_job_name) { 'run-dev-fixtures-ee' }
|
||||
|
||||
subject(:pipeline) do
|
||||
trigger_source = ci_pipeline_source.to_sym
|
||||
create_pipeline_service
|
||||
.execute(trigger_source, dry_run: true, content: content, variables_attributes: variables_attributes)
|
||||
.payload
|
||||
end
|
||||
|
||||
before do
|
||||
sync_local_files_to_project(
|
||||
pipeline_project,
|
||||
user,
|
||||
ci_commit_branch,
|
||||
files: ci_glob_with_common_file_globs
|
||||
)
|
||||
end
|
||||
|
||||
it_behaves_like 'default branch pipeline'
|
||||
end
|
||||
|
||||
context 'with fork project' do
|
||||
let(:ci_commit_branch) { master_branch }
|
||||
|
||||
before do
|
||||
pipeline_project.add_developer(user)
|
||||
|
||||
sync_local_files_to_project(
|
||||
pipeline_project,
|
||||
user,
|
||||
ci_commit_branch,
|
||||
files: ci_glob_with_common_file_globs
|
||||
)
|
||||
end
|
||||
|
||||
context 'with gitlab.com gitlab-org/security/gitlab project' do
|
||||
let_it_be(:sub_group) { create(:group, parent: group, path: 'security') }
|
||||
let_it_be(:security_project) { create(:project, :empty_repo, group: sub_group, path: 'gitlab') }
|
||||
let(:ci_project_namespace) { 'gitlab-org/security' }
|
||||
let(:pipeline_project) { security_project }
|
||||
|
||||
context 'when master pipeline is triggered by push' do
|
||||
let(:expected_job_name) { 'static-verification-with-database' }
|
||||
|
||||
it_behaves_like 'default branch pipeline'
|
||||
end
|
||||
|
||||
context 'with scheduled master pipeline' do
|
||||
let(:ci_pipeline_source) { 'schedule' }
|
||||
let(:expected_job_name) { 'update-ruby-gems-coverage-cache-push' }
|
||||
|
||||
it_behaves_like 'default branch pipeline'
|
||||
end
|
||||
|
||||
context 'with auto-deploy branch pipeilne' do
|
||||
let(:ci_commit_branch) { '17-3-auto-deploy-2024080508' }
|
||||
let(:expected_job_name) { 'build-qa-image' }
|
||||
|
||||
it_behaves_like 'default branch pipeline'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with gitlab.com gitlab-org gitlab-foss project' do
|
||||
let_it_be(:foss_project) { create(:project, :empty_repo, group: group, path: 'gitlab-foss') }
|
||||
let(:pipeline_project) { foss_project }
|
||||
|
||||
context 'with master pipeline triggered by push' do
|
||||
let(:expected_job_name) { 'db:backup_and_restore single-db' }
|
||||
|
||||
it_behaves_like 'default branch pipeline'
|
||||
end
|
||||
|
||||
context 'with scheduled master pipeline' do
|
||||
let(:ci_pipeline_source) { 'schedule' }
|
||||
let(:expected_job_name) { 'db:backup_and_restore single-db' }
|
||||
|
||||
it_behaves_like 'default branch pipeline'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_relative './shared_context_and_examples'
|
||||
|
||||
RSpec.describe 'CI configuration validation - branch pipelines', feature_category: :tooling do
|
||||
include ProjectForksHelper
|
||||
include CiConfigurationValidationHelper
|
||||
|
||||
include_context 'with simulated pipeline attributes and shared project and user'
|
||||
include_context 'with simulated MR pipeline attributes'
|
||||
|
||||
let(:pipeline_project) { gitlab_org_gitlab_project }
|
||||
let(:create_pipeline_service) { Ci::CreatePipelineService.new(target_project, user, ref: target_branch) }
|
||||
|
||||
subject(:pipeline) do
|
||||
create_pipeline_service
|
||||
.execute(
|
||||
:push,
|
||||
dry_run: true,
|
||||
merge_request: merge_request,
|
||||
variables_attributes: mr_pipeline_variables_attributes
|
||||
).payload
|
||||
end
|
||||
|
||||
context "when unlabeled MR is changing GITALY_SERVER_VERSION" do
|
||||
let(:changed_files) { ['GITALY_SERVER_VERSION'] }
|
||||
let(:expected_job_name) { 'eslint' }
|
||||
|
||||
it_behaves_like 'merge request pipeline'
|
||||
|
||||
it_behaves_like 'merge train pipeline'
|
||||
end
|
||||
|
||||
context 'when MR is created from "release-tools/update-gitaly" source branch' do
|
||||
let(:source_branch) { 'release-tools/update-gitaly' }
|
||||
let(:changed_files) { ['GITALY_SERVER_VERSION'] }
|
||||
let(:expected_job_name) { 'update-gitaly-binaries-cache' }
|
||||
|
||||
it_behaves_like 'merge request pipeline'
|
||||
end
|
||||
|
||||
context 'when MR targeting a stable branch is changing app/models/user.rb' do
|
||||
let(:target_branch) { '16-10-stable-ee' }
|
||||
let(:changed_files) { ['app/models/user.rb'] }
|
||||
let(:expected_job_name) { 'rspec-all frontend_fixture 1/7' }
|
||||
|
||||
before do
|
||||
sync_local_files_to_project(
|
||||
target_project,
|
||||
user,
|
||||
target_branch,
|
||||
files: ci_glob
|
||||
)
|
||||
end
|
||||
|
||||
after do
|
||||
target_project.repository.delete_branch(target_branch)
|
||||
end
|
||||
|
||||
it_behaves_like 'merge request pipeline'
|
||||
|
||||
it_behaves_like 'merge train pipeline'
|
||||
end
|
||||
|
||||
context 'with fork project MRs' do
|
||||
let_it_be(:fork_project_mr_source) { fork_project(gitlab_org_gitlab_project, user, repository: true) }
|
||||
let(:source_project) { fork_project_mr_source }
|
||||
let(:target_project) { gitlab_org_gitlab_project }
|
||||
|
||||
context 'when MR is created from a fork project master branch' do
|
||||
let(:source_branch) { master_branch }
|
||||
let(:target_branch) { master_branch }
|
||||
let(:changed_files) { ['package.json'] }
|
||||
let(:expected_job_name) { 'rspec-all frontend_fixture 1/7' }
|
||||
|
||||
context 'when running MR pipeline in the context of the fork project' do
|
||||
let(:ci_project_namespace) { fork_project_mr_source.namespace.full_path }
|
||||
let(:ci_project_path) { fork_project_mr_source.full_path }
|
||||
let(:ci_project_name) { fork_project_mr_source.name }
|
||||
|
||||
it_behaves_like 'merge request pipeline'
|
||||
end
|
||||
|
||||
context 'when running MR pipeline in the context of canonical project' do
|
||||
it_behaves_like 'merge request pipeline'
|
||||
end
|
||||
|
||||
it_behaves_like 'merge train pipeline'
|
||||
end
|
||||
|
||||
context 'when MR is created from a fork project feature branch' do
|
||||
let(:source_branch) { "feature_branch_ci_#{SecureRandom.uuid}" }
|
||||
let(:target_branch) { master_branch }
|
||||
let(:changed_files) { ['package.json'] }
|
||||
let(:expected_job_name) { 'rspec-all frontend_fixture 1/7' }
|
||||
|
||||
context 'when running MR pipeline in the context of the fork project' do
|
||||
let(:ci_project_namespace) { fork_project_mr_source.namespace.full_path }
|
||||
let(:ci_project_path) { fork_project_mr_source.full_path }
|
||||
let(:ci_project_name) { fork_project_mr_source.name }
|
||||
|
||||
it_behaves_like 'merge request pipeline'
|
||||
end
|
||||
|
||||
context 'when running MR pipeline in the context of canonical project' do
|
||||
it_behaves_like 'merge request pipeline'
|
||||
end
|
||||
|
||||
it_behaves_like 'merge train pipeline'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_relative './shared_context_and_examples'
|
||||
|
||||
RSpec.describe 'CI configuration validation - branch pipelines', feature_category: :tooling do
|
||||
include ProjectForksHelper
|
||||
include CiConfigurationValidationHelper
|
||||
|
||||
include_context 'with simulated pipeline attributes and shared project and user'
|
||||
include_context 'with simulated MR pipeline attributes'
|
||||
|
||||
let(:pipeline_project) { gitlab_org_gitlab_project }
|
||||
let(:create_pipeline_service) { Ci::CreatePipelineService.new(target_project, user, ref: target_branch) }
|
||||
|
||||
subject(:pipeline) do
|
||||
create_pipeline_service
|
||||
.execute(
|
||||
:push,
|
||||
dry_run: true,
|
||||
merge_request: merge_request,
|
||||
variables_attributes: mr_pipeline_variables_attributes
|
||||
).payload
|
||||
end
|
||||
|
||||
context 'when MR labeled with `pipeline:run-all-rspec` is changing app/models/user.rb' do
|
||||
let(:mr_labels) { ['pipeline:run-all-rspec'] }
|
||||
let(:changed_files) { ['app/models/user.rb'] }
|
||||
let(:expected_job_name) { 'rspec-all frontend_fixture 1/7' }
|
||||
|
||||
it_behaves_like 'merge request pipeline'
|
||||
end
|
||||
|
||||
context 'when MR labeled with `pipeline:expedite pipeline::expedited` is changing app/models/user.rb' do
|
||||
let(:mr_labels) { ['pipeline:expedite', 'pipeline::expedited'] }
|
||||
let(:changed_files) { ['app/models/user.rb'] }
|
||||
let(:expected_job_name) { 'setup-test-env' }
|
||||
|
||||
it_behaves_like 'merge request pipeline'
|
||||
|
||||
it_behaves_like 'merge train pipeline'
|
||||
end
|
||||
|
||||
context 'when MR labeled with `pipeline::tier-1`' do
|
||||
let(:mr_labels) { ['pipeline::tier-1'] }
|
||||
let(:changed_files) { ['app/models/user.rb'] }
|
||||
let(:expected_job_name) { 'pipeline-tier-1' }
|
||||
|
||||
it_behaves_like 'merge request pipeline'
|
||||
end
|
||||
|
||||
context 'when MR labeled with `pipeline::tier-2` and `pipeline:mr-approved`' do
|
||||
let(:mr_labels) { ['pipeline::tier-2', 'pipeline:mr-approved'] }
|
||||
let(:changed_files) { ['app/models/user.rb'] }
|
||||
let(:expected_job_name) { 'pipeline-tier-2' }
|
||||
|
||||
it_behaves_like 'merge request pipeline'
|
||||
end
|
||||
|
||||
context 'when MR labeled with `pipeline::tier-3` and `pipeline:mr-approved`' do
|
||||
let(:mr_labels) { ['pipeline::tier-3', 'pipeline:mr-approved'] }
|
||||
let(:changed_files) { ['app/models/user.rb'] }
|
||||
let(:expected_job_name) { 'pipeline-tier-3' }
|
||||
|
||||
it_behaves_like 'merge request pipeline'
|
||||
end
|
||||
|
||||
context 'when MR labeled with `pipeline:run-as-if-foss` is changing app/models/user.rb' do
|
||||
let(:mr_labels) { ['pipeline:run-as-if-foss'] }
|
||||
let(:changed_files) { ['app/models/user.rb'] }
|
||||
let(:expected_job_name) { 'start-as-if-foss' }
|
||||
|
||||
let(:mr_pipeline_variables_attributes) do
|
||||
super() << { key: 'AS_IF_FOSS_TOKEN', value: 'foss token' }
|
||||
end
|
||||
|
||||
it_behaves_like 'merge request pipeline'
|
||||
|
||||
it_behaves_like 'merge train pipeline'
|
||||
end
|
||||
|
||||
context 'when MR labeled with `pipeline:force-run-as-if-jh` is changing app/models/user.rb' do
|
||||
let(:mr_labels) { ['pipeline:force-run-as-if-jh'] }
|
||||
let(:changed_files) { ['app/models/user.rb'] }
|
||||
let(:expected_job_name) { 'start-as-if-jh' }
|
||||
|
||||
let(:mr_pipeline_variables_attributes) do
|
||||
# workflow rule for "include", see .gitlab-ci.yml
|
||||
super() << { key: 'CI_PROJECT_URL', value: 'https://gitlab.com/gitlab-org/gitlab' }
|
||||
end
|
||||
|
||||
it_behaves_like 'merge request pipeline'
|
||||
|
||||
it_behaves_like 'merge train pipeline'
|
||||
end
|
||||
|
||||
context 'when MR labeled with `pipeline:run-as-if-jh` and `pipeline:mr-approved` is changing app/models/user.rb' do
|
||||
let(:mr_labels) { ['pipeline:run-as-if-jh', 'pipeline:mr-approved'] }
|
||||
let(:changed_files) { ['app/models/user.rb'] }
|
||||
let(:expected_job_name) { 'start-as-if-jh' }
|
||||
|
||||
let(:mr_pipeline_variables_attributes) do
|
||||
base_attributes = super()
|
||||
# workflow rule for "include", see .gitlab-ci.yml
|
||||
base_attributes << { key: 'CI_PROJECT_URL', value: 'https://gitlab.com/gitlab-org/gitlab' }
|
||||
base_attributes << { key: 'CI_AS_IF_JH_ENABLED', value: 'true' }
|
||||
end
|
||||
|
||||
it_behaves_like 'merge request pipeline'
|
||||
|
||||
it_behaves_like 'merge train pipeline'
|
||||
end
|
||||
|
||||
context 'when MR labeled with `pipeline:run-in-ruby3_2` is changing app/models/user.rb' do
|
||||
let(:mr_labels) { ['pipeline:run-in-ruby3_2'] }
|
||||
let(:changed_files) { ['app/models/user.rb'] }
|
||||
let(:expected_job_name) { 'e2e-test-pipeline-generate' }
|
||||
|
||||
it_behaves_like 'merge request pipeline'
|
||||
|
||||
it_behaves_like 'merge train pipeline'
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_context 'with simulated pipeline attributes and shared project and user' do
|
||||
let(:ci_server_host) { 'gitlab.com' }
|
||||
let(:ci_project_namespace) { 'gitlab-org' }
|
||||
let(:ci_project_path) { "#{ci_project_namespace}/#{ci_project_name}" }
|
||||
let(:ci_project_name) { 'gitlab' }
|
||||
let(:ci_pipeline_source) { 'push' }
|
||||
|
||||
let(:variables_attributes_base) do
|
||||
[
|
||||
{ key: 'CI_SERVER_HOST', value: ci_server_host },
|
||||
{ key: 'CI_PROJECT_NAMESPACE', value: ci_project_namespace },
|
||||
{ key: 'CI_PROJECT_PATH', value: ci_project_path },
|
||||
{ key: 'CI_PROJECT_NAME', value: ci_project_name },
|
||||
{ key: 'CI_PIPELINE_SOURCE', value: ci_pipeline_source },
|
||||
{ key: 'GITLAB_INTERNAL', value: 'true' }
|
||||
]
|
||||
end
|
||||
|
||||
let(:jobs) { pipeline.stages.flat_map { |s| s.statuses.map(&:name) } }
|
||||
|
||||
let_it_be(:group) { create(:group, path: 'gitlab-org') }
|
||||
let_it_be(:gitlab_org_gitlab_project) { create(:project, :empty_repo, group: group, path: 'gitlab') }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:ci_glob) { Dir.glob("{.gitlab-ci.yml,.gitlab/**/*.yml}").freeze }
|
||||
let_it_be(:ci_glob_with_common_file_globs) { [*ci_glob, 'lib/api/lint.rb', 'doc/index.md'] }
|
||||
let_it_be(:master_branch) { 'master' }
|
||||
|
||||
around do |example|
|
||||
with_net_connect_allowed { example.run } # creating pipeline requires network call to fetch templates
|
||||
end
|
||||
|
||||
before_all do
|
||||
gitlab_org_gitlab_project.add_developer(user)
|
||||
|
||||
sync_local_files_to_project(
|
||||
gitlab_org_gitlab_project,
|
||||
user,
|
||||
master_branch,
|
||||
files: ci_glob_with_common_file_globs
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
# delete once we have a migration to permanently increase limit
|
||||
stub_application_setting(max_yaml_size_bytes: 2.megabytes)
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_context 'with simulated MR pipeline attributes' do
|
||||
let(:ci_pipeline_source) { 'merge_request_event' }
|
||||
let(:ci_merge_request_event_type) { 'merged_result' }
|
||||
let(:mr_labels) { [] }
|
||||
|
||||
let(:mr_pipeline_variables_attributes) do
|
||||
[
|
||||
*variables_attributes_base,
|
||||
{ key: 'CI_COMMIT_REF_NAME', value: source_branch },
|
||||
{ key: 'CI_MERGE_REQUEST_EVENT_TYPE', value: ci_merge_request_event_type }
|
||||
]
|
||||
end
|
||||
|
||||
let(:create_pipeline_service) { Ci::CreatePipelineService.new(target_project, user, ref: target_branch) }
|
||||
|
||||
let(:source_project) { gitlab_org_gitlab_project }
|
||||
let(:target_project) { gitlab_org_gitlab_project }
|
||||
let(:source_branch) { "feature_branch_ci_#{SecureRandom.uuid}" }
|
||||
let(:target_branch) { master_branch }
|
||||
|
||||
let(:merge_request) do
|
||||
create(:labeled_merge_request,
|
||||
source_project: source_project,
|
||||
source_branch: source_branch,
|
||||
target_project: target_project,
|
||||
target_branch: target_branch,
|
||||
labels: mr_labels.map { |label_title| create(:label, title: label_title) }
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
file_change_actions = changed_files.map do |file_path|
|
||||
action = source_project.repository.blob_at(source_branch, file_path).nil? ? :create : :update
|
||||
{
|
||||
action: action,
|
||||
file_path: file_path,
|
||||
content: 'content'
|
||||
}
|
||||
end
|
||||
|
||||
source_project.repository.commit_files(
|
||||
user,
|
||||
branch_name: source_branch,
|
||||
message: 'changes files',
|
||||
actions: file_change_actions
|
||||
)
|
||||
end
|
||||
|
||||
after do
|
||||
source_project.repository.delete_branch(source_branch)
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'default branch pipeline' do
|
||||
it 'is valid' do
|
||||
expect(pipeline.yaml_errors).to be nil
|
||||
expect(pipeline.status).to eq('created')
|
||||
expect(jobs).to include(expected_job_name)
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'merge request pipeline' do
|
||||
it "succeeds with expected job" do
|
||||
expect(pipeline.yaml_errors).to be nil
|
||||
expect(pipeline.status).to eq('created')
|
||||
expect(jobs).to include(expected_job_name)
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'merge train pipeline' do
|
||||
let(:ci_merge_request_event_type) { 'merge_train' }
|
||||
|
||||
it "succeeds with expected job" do
|
||||
expect(pipeline.yaml_errors).to be nil
|
||||
expect(pipeline.status).to eq('created')
|
||||
expect(jobs).to include('pre-merge-checks')
|
||||
expect(jobs).not_to include('upload-frontend-fixtures')
|
||||
end
|
||||
end
|
||||
|
||||
module CiConfigurationValidationHelper
|
||||
def sync_local_files_to_project(project, user, branch_name, files:)
|
||||
actions = []
|
||||
|
||||
entries = project.repository.tree(branch_name, recursive: true).entries
|
||||
entries.map! { |e| e.dir? ? project.repository.tree(branch_name, e.path, recursive: true).entries : e }
|
||||
current_files = entries.flatten.select(&:file?).map(&:path).uniq
|
||||
|
||||
# Delete old
|
||||
actions.concat (current_files - files).map { |file| { action: :delete, file_path: file } }
|
||||
# Add new
|
||||
actions.concat (files - current_files).map { |file|
|
||||
{ action: :create, file_path: file, content: read_file(file) }
|
||||
}
|
||||
|
||||
# Update changed
|
||||
(current_files & files).each do |file|
|
||||
content = read_file(file)
|
||||
if content != project.repository.blob_data_at(branch_name, file)
|
||||
actions << { action: :update, file_path: file, content: content }
|
||||
end
|
||||
end
|
||||
|
||||
if actions.any?
|
||||
puts "Syncing files to #{project.full_path} #{branch_name} branch"
|
||||
project.repository.commit_files(user, branch_name: branch_name, message: 'syncing', actions: actions)
|
||||
else
|
||||
puts "No file syncing needed"
|
||||
end
|
||||
end
|
||||
|
||||
def read_file(file, ignore_ci_component: true)
|
||||
content = File.read(file)
|
||||
|
||||
return content unless ignore_ci_component
|
||||
|
||||
fake_job = <<~YAML
|
||||
.ignore:
|
||||
script: echo ok
|
||||
YAML
|
||||
|
||||
file.end_with?('.yml') && %r{^\s*- component:.*CI_SERVER_}.match?(content) ? fake_job : content
|
||||
end
|
||||
end
|
||||
|
|
@ -1,475 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
# ***********************************************************************************************************
|
||||
# The tests in this file are recommended to run locally to test ci configuration changes
|
||||
# The tests are slow because pipeline simuation takes time given our current pipeline yaml size
|
||||
# ***********************************************************************************************************
|
||||
# TEST COVERAGE
|
||||
#
|
||||
# GitLab.com gitlab-org/gitlab-foss Project
|
||||
# Master Pipeline Triggered by Push
|
||||
# Scheduled Master Pipeline
|
||||
#
|
||||
# GitLab.com gitlab-org/gitlab Master Pipeline
|
||||
# Default Master Pipeline Triggered by Push
|
||||
# Scheduled pipeline - Nightly
|
||||
# Scheduled pipeline - Maintenance
|
||||
#
|
||||
# Merge Request Pipeline (and merge train pipeline under the same contexts)
|
||||
# Unlabeled MR Changing GITALY_SERVER_VERSION
|
||||
# Unlabeled MR Changing Dangerfile, .gitlab/ci/frontend.gitlab-ci.yml
|
||||
#
|
||||
# MR Labeled with pipeline:run-all-rspec Changing app/models/user.rb
|
||||
# MR Labeled with pipeline:expedite pipeline::expedited Changing app/models/user.rb
|
||||
# MR Labeled with pipeline::tier-1 Changing app/models/user.rb
|
||||
# MR Labeled with pipeline::tier-2 and pipeline:mr-approved Changing app/models/user.rb
|
||||
# MR Labeled with pipeline::tier-3 and pipeline:mr-approved Changing app/models/user.rb
|
||||
# MR Labeled with pipeline:run-as-if-foss Changing app/models/user.rb
|
||||
#
|
||||
# Automated MR
|
||||
# MR Created from release-tools/update-gitaly Source Branch Changing GITALY_SERVER_VERSION
|
||||
#
|
||||
# Stable Branch
|
||||
# MR Targeting a Stable Branch Changing app/models/user.rb
|
||||
#
|
||||
# Fork Project MRs
|
||||
# MR Created from a Fork Project Master Branch Changing package.json
|
||||
# MR Created from a Fork Project Feature Branch Changing package.json
|
||||
# ***********************************************************************************************************
|
||||
# CONTRIBUTE
|
||||
#
|
||||
# If you think we are missing coverage for an important pipeline type, please add them.
|
||||
#
|
||||
# For example, ci rule changes could break gitlab-foss pipelines, as seen in
|
||||
# https://gitlab.com/gitlab-org/quality/engineering-productivity/master-broken-incidents/-/issues/7356
|
||||
# we then added test cases by simulating pipeline for gitlab-org/gitlab-foss
|
||||
# See `with gitlab.com gitlab-org gitlab-foss project` context below for details.
|
||||
# ***********************************************************************************************************
|
||||
RSpec.describe 'ci jobs dependency', feature_category: :tooling do
|
||||
include ProjectForksHelper
|
||||
|
||||
def sync_local_files_to_project(project, user, branch_name, files:)
|
||||
actions = []
|
||||
|
||||
entries = project.repository.tree(branch_name, recursive: true).entries
|
||||
entries.map! { |e| e.dir? ? project.repository.tree(branch_name, e.path, recursive: true).entries : e }
|
||||
current_files = entries.flatten.select(&:file?).map(&:path).uniq
|
||||
|
||||
# Delete old
|
||||
actions.concat (current_files - files).map { |file| { action: :delete, file_path: file } }
|
||||
# Add new
|
||||
actions.concat (files - current_files).map { |file|
|
||||
{ action: :create, file_path: file, content: read_file(file) }
|
||||
}
|
||||
|
||||
# Update changed
|
||||
(current_files & files).each do |file|
|
||||
content = read_file(file)
|
||||
if content != project.repository.blob_data_at(branch_name, file)
|
||||
actions << { action: :update, file_path: file, content: content }
|
||||
end
|
||||
end
|
||||
|
||||
if actions.any?
|
||||
puts "Syncing files to #{project.full_path} #{branch_name} branch"
|
||||
project.repository.commit_files(user, branch_name: branch_name, message: 'syncing', actions: actions)
|
||||
else
|
||||
puts "No file syncing needed"
|
||||
end
|
||||
end
|
||||
|
||||
def read_file(file, ignore_ci_component: true)
|
||||
content = File.read(file)
|
||||
|
||||
return content unless ignore_ci_component
|
||||
|
||||
fake_job = <<~YAML
|
||||
.ignore:
|
||||
script: echo ok
|
||||
YAML
|
||||
|
||||
file.end_with?('.yml') && %r{^\s*- component:.*CI_SERVER_}.match?(content) ? fake_job : content
|
||||
end
|
||||
|
||||
let(:ci_server_host) { 'gitlab.com' }
|
||||
let(:ci_project_namespace) { 'gitlab-org' }
|
||||
let(:ci_project_path) { 'gitlab-org/gitlab' }
|
||||
let(:ci_project_name) { 'gitlab' }
|
||||
let(:ci_pipeline_source) { 'push' }
|
||||
let(:ci_commit_branch) { master_branch }
|
||||
|
||||
let(:variables_attributes_base) do
|
||||
[
|
||||
{ key: 'CI_SERVER_HOST', value: ci_server_host },
|
||||
{ key: 'CI_PROJECT_NAMESPACE', value: ci_project_namespace },
|
||||
{ key: 'CI_PROJECT_PATH', value: ci_project_path },
|
||||
{ key: 'CI_PROJECT_NAME', value: ci_project_name },
|
||||
{ key: 'CI_PIPELINE_SOURCE', value: ci_pipeline_source },
|
||||
{ key: 'CI_COMMIT_BRANCH', value: ci_commit_branch }
|
||||
]
|
||||
end
|
||||
|
||||
let_it_be(:group) { create(:group, path: 'gitlab-org') }
|
||||
let_it_be(:gitlab_org_gitlab_project) { create(:project, :empty_repo, group: group, path: 'gitlab') }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:ci_glob) { Dir.glob("{.gitlab-ci.yml,.gitlab/**/*.yml}").freeze }
|
||||
let_it_be(:master_branch) { 'master' }
|
||||
|
||||
let(:create_pipeline_service) { Ci::CreatePipelineService.new(gitlab_org_gitlab_project, user, ref: master_branch) }
|
||||
let(:jobs) { pipeline.stages.flat_map { |s| s.statuses.map(&:name) } }
|
||||
|
||||
around do |example|
|
||||
with_net_connect_allowed { example.run } # creating pipeline requires network call to fetch templates
|
||||
end
|
||||
|
||||
before_all do
|
||||
gitlab_org_gitlab_project.add_developer(user)
|
||||
|
||||
sync_local_files_to_project(
|
||||
gitlab_org_gitlab_project,
|
||||
user,
|
||||
master_branch,
|
||||
files: ci_glob
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
# delete once we have a migration to permanently increase limit
|
||||
stub_application_setting(max_yaml_size_bytes: 2.megabytes)
|
||||
end
|
||||
|
||||
shared_examples 'master pipeline' do
|
||||
let(:content) do
|
||||
gitlab_org_gitlab_project.repository.blob_at(master_branch, '.gitlab-ci.yml').data
|
||||
end
|
||||
|
||||
subject(:pipeline) do
|
||||
trigger_source = ci_pipeline_source.to_sym
|
||||
create_pipeline_service
|
||||
.execute(trigger_source, dry_run: true, content: content, variables_attributes: variables_attributes)
|
||||
.payload
|
||||
end
|
||||
|
||||
it 'is valid' do
|
||||
expect(pipeline.yaml_errors).to be nil
|
||||
expect(pipeline.status).to eq('created')
|
||||
expect(jobs).to include(expected_job_name)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with gitlab.com gitlab-org/gitlab master pipeline' do
|
||||
context 'with default master pipeline' do
|
||||
let(:variables_attributes) { variables_attributes_base }
|
||||
let(:expected_job_name) { 'db:migrate:multi-version-upgrade' }
|
||||
|
||||
# Test: remove rules from .rails:rules:setup-test-env
|
||||
it_behaves_like 'master pipeline'
|
||||
end
|
||||
|
||||
context 'with scheduled nightly' do
|
||||
let(:ci_pipeline_source) { 'schedule' }
|
||||
let(:expected_job_name) { 'db:rollback single-db' }
|
||||
let(:variables_attributes) do
|
||||
[
|
||||
*variables_attributes_base,
|
||||
{ key: 'SCHEDULE_TYPE', value: 'nightly' }
|
||||
]
|
||||
end
|
||||
|
||||
# .if-default-branch-schedule-nightly
|
||||
# included in .qa:rules:package-and-test-ce
|
||||
# used by e2e:package-and-test-ce
|
||||
# needs e2e-test-pipeline-generate
|
||||
# has rule .qa:rules:determine-e2e-tests
|
||||
|
||||
# Test: I can remove this rule from .qa:rules:determine-e2e-tests
|
||||
# - <<: *if-dot-com-gitlab-org-schedule
|
||||
# allow_failure: true
|
||||
it_behaves_like 'master pipeline'
|
||||
end
|
||||
|
||||
context 'with scheduled maintenance' do
|
||||
let(:ci_pipeline_source) { 'schedule' }
|
||||
let(:expected_job_name) { 'generate-frontend-fixtures-mapping' }
|
||||
let(:variables_attributes) do
|
||||
[
|
||||
*variables_attributes_base,
|
||||
{ key: 'SCHEDULE_TYPE', value: 'maintenance' }
|
||||
]
|
||||
end
|
||||
|
||||
it_behaves_like 'master pipeline'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with gitlab.com gitlab-org gitlab-foss project' do
|
||||
let(:ci_project_name) { 'gitlab-foss' }
|
||||
let(:ci_project_path) { 'gitlab-org/gitlab-foss' }
|
||||
let(:variables_attributes) { variables_attributes_base }
|
||||
|
||||
context 'with master pipeline triggered by push' do
|
||||
let(:expected_job_name) { 'db:backup_and_restore single-db' }
|
||||
|
||||
it_behaves_like 'master pipeline'
|
||||
end
|
||||
|
||||
context 'with scheduled master pipeline' do
|
||||
let(:ci_pipeline_source) { 'schedule' }
|
||||
let(:expected_job_name) { 'db:backup_and_restore single-db' }
|
||||
|
||||
# Verify by removing the following rule from .qa:rules:e2e:test-on-cng
|
||||
# - !reference [".qa:rules:package-and-test-never-run", rules]
|
||||
it_behaves_like 'master pipeline'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with MR pipeline' do
|
||||
let(:ci_pipeline_source) { 'merge_request_event' }
|
||||
let(:ci_merge_request_event_type) { 'merged_result' }
|
||||
let(:ci_commit_branch) { target_branch } # to simulate merged results pipeline
|
||||
let(:ci_merge_request_labels_string) { '' }
|
||||
|
||||
let(:mr_pipeline_variables_attributes) do
|
||||
[
|
||||
*variables_attributes_base,
|
||||
{ key: 'CI_MERGE_REQUEST_EVENT_TYPE', value: ci_merge_request_event_type },
|
||||
{ key: 'CI_MERGE_REQUEST_LABELS', value: ci_merge_request_labels_string }
|
||||
]
|
||||
end
|
||||
|
||||
let(:create_pipeline_service) { Ci::CreatePipelineService.new(target_project, user, ref: target_branch) }
|
||||
|
||||
let(:source_project) { gitlab_org_gitlab_project }
|
||||
let(:target_project) { gitlab_org_gitlab_project }
|
||||
let(:source_branch) { "feature_branch_ci_#{SecureRandom.uuid}" }
|
||||
let(:target_branch) { master_branch }
|
||||
|
||||
let(:merge_request) do
|
||||
create(:merge_request,
|
||||
source_project: source_project,
|
||||
source_branch: source_branch,
|
||||
target_project: target_project,
|
||||
target_branch: target_branch
|
||||
)
|
||||
end
|
||||
|
||||
subject(:pipeline) do
|
||||
create_pipeline_service
|
||||
.execute(
|
||||
:push,
|
||||
dry_run: true,
|
||||
merge_request: merge_request,
|
||||
variables_attributes: mr_pipeline_variables_attributes
|
||||
).payload
|
||||
end
|
||||
|
||||
before do
|
||||
file_change_actions = changed_files.map do |file_path|
|
||||
action = source_project.repository.blob_at(source_branch, file_path).nil? ? :create : :update
|
||||
{
|
||||
action: action,
|
||||
file_path: file_path,
|
||||
content: 'content'
|
||||
}
|
||||
end
|
||||
|
||||
source_project.repository.commit_files(
|
||||
user,
|
||||
branch_name: source_branch,
|
||||
message: 'changes files',
|
||||
actions: file_change_actions
|
||||
)
|
||||
end
|
||||
|
||||
after do
|
||||
source_project.repository.delete_branch(source_branch)
|
||||
end
|
||||
|
||||
shared_examples 'merge request pipeline' do
|
||||
it "succeeds with expected job" do
|
||||
expect(pipeline.yaml_errors).to be nil
|
||||
expect(pipeline.status).to eq('created')
|
||||
# to confirm that the dependent job is actually created and rule out false positives
|
||||
expect(jobs).to include(expected_job_name)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'merge train pipeline' do
|
||||
let(:ci_merge_request_event_type) { 'merge_train' }
|
||||
|
||||
it "succeeds with expected job" do
|
||||
expect(pipeline.yaml_errors).to be nil
|
||||
expect(pipeline.status).to eq('created')
|
||||
expect(jobs).to include('pre-merge-checks')
|
||||
expect(jobs).not_to include('upload-frontend-fixtures')
|
||||
end
|
||||
end
|
||||
|
||||
# gitaly, db, backend patterns
|
||||
context "when unlabeled MR is changing GITALY_SERVER_VERSION" do
|
||||
let(:changed_files) { ['GITALY_SERVER_VERSION'] }
|
||||
let(:expected_job_name) { 'eslint' }
|
||||
|
||||
it_behaves_like 'merge request pipeline'
|
||||
|
||||
it_behaves_like 'merge train pipeline'
|
||||
end
|
||||
|
||||
# Test: remove the following rules from `.frontend:rules:default-frontend-jobs`:
|
||||
# - <<: *if-default-refs
|
||||
# changes: *code-backstage-patterns
|
||||
context 'when unlabled MR is changing Dangerfile, .gitlab/ci/frontend.gitlab-ci.yml' do
|
||||
let(:changed_files) { ['Dangerfile', '.gitlab/ci/frontend.gitlab-ci.yml'] }
|
||||
let(:expected_job_name) { 'rspec-all frontend_fixture 1/7' }
|
||||
|
||||
it_behaves_like 'merge request pipeline'
|
||||
end
|
||||
|
||||
# Test: remove the following rules from `.frontend:rules:default-frontend-jobs`:
|
||||
# - <<: *if-merge-request-labels-run-all-rspec
|
||||
context 'when MR labeled with `pipeline:run-all-rspec` is changing app/models/user.rb' do
|
||||
let(:ci_merge_request_labels_string) { 'pipeline:run-all-rspec' }
|
||||
let(:changed_files) { ['app/models/user.rb'] }
|
||||
let(:expected_job_name) { 'rspec-all frontend_fixture 1/7' }
|
||||
|
||||
it_behaves_like 'merge request pipeline'
|
||||
end
|
||||
|
||||
# code-patterns, code-backstage-patterns, backend patterns, code-qa-patterns
|
||||
context 'when MR labeled with `pipeline:expedite pipeline::expedited` is changing app/models/user.rb' do
|
||||
let(:ci_merge_request_labels_string) { 'pipeline:expedite pipeline::expedited' }
|
||||
let(:changed_files) { ['app/models/user.rb'] }
|
||||
let(:expected_job_name) { 'setup-test-env' }
|
||||
|
||||
it_behaves_like 'merge request pipeline'
|
||||
|
||||
it_behaves_like 'merge train pipeline'
|
||||
end
|
||||
|
||||
context 'when MR labeled with `pipeline::tier-1`' do
|
||||
let(:ci_merge_request_labels_string) { 'pipeline::tier-1' }
|
||||
let(:changed_files) { ['app/models/user.rb'] }
|
||||
let(:expected_job_name) { 'pipeline-tier-1' }
|
||||
|
||||
it_behaves_like 'merge request pipeline'
|
||||
end
|
||||
|
||||
context 'when MR labeled with `pipeline::tier-2` and `pipeline:mr-approved`' do
|
||||
let(:ci_merge_request_labels_string) { 'pipeline::tier-2 pipeline:mr-approved' }
|
||||
let(:changed_files) { ['app/models/user.rb'] }
|
||||
let(:expected_job_name) { 'pipeline-tier-2' }
|
||||
|
||||
it_behaves_like 'merge request pipeline'
|
||||
end
|
||||
|
||||
context 'when MR labeled with `pipeline::tier-3` and `pipeline:mr-approved`' do
|
||||
let(:ci_merge_request_labels_string) { 'pipeline::tier-3 pipeline:mr-approved' }
|
||||
let(:changed_files) { ['app/models/user.rb'] }
|
||||
let(:expected_job_name) { 'pipeline-tier-3' }
|
||||
|
||||
it_behaves_like 'merge request pipeline'
|
||||
end
|
||||
|
||||
context 'when MR labeled with `pipeline:run-as-if-foss` is changing app/models/user.rb' do
|
||||
let(:ci_merge_request_labels_string) { 'pipeline:run-as-if-foss' }
|
||||
let(:changed_files) { ['app/models/user.rb'] }
|
||||
let(:expected_job_name) { 'start-as-if-foss' }
|
||||
|
||||
let(:mr_pipeline_variables_attributes) do
|
||||
super() << { key: 'AS_IF_FOSS_TOKEN', value: 'foss token' }
|
||||
end
|
||||
|
||||
it_behaves_like 'merge request pipeline'
|
||||
|
||||
it_behaves_like 'merge train pipeline'
|
||||
end
|
||||
|
||||
context 'when MR is created from "release-tools/update-gitaly" source branch' do
|
||||
let(:source_branch) { 'release-tools/update-gitaly' }
|
||||
let(:changed_files) { ['GITALY_SERVER_VERSION'] }
|
||||
let(:expected_job_name) { 'update-gitaly-binaries-cache' }
|
||||
|
||||
it_behaves_like 'merge request pipeline'
|
||||
end
|
||||
|
||||
# Reminder, we are NOT verifying the CI config from the remote stable branch
|
||||
# This test just mocks the target branch name to be a stable branch
|
||||
# the tested config is what's currently in the local .gitlab/ci folders
|
||||
|
||||
# Test: remove the following rules from `.frontend:rules:default-frontend-jobs`:
|
||||
# - <<: *if-default-refs
|
||||
# changes: *code-backstage-patterns
|
||||
context 'when MR targeting a stable branch is changing app/models/user.rb' do
|
||||
let(:target_branch) { '16-10-stable-ee' }
|
||||
let(:changed_files) { ['app/models/user.rb'] }
|
||||
let(:expected_job_name) { 'rspec-all frontend_fixture 1/7' }
|
||||
|
||||
before do
|
||||
sync_local_files_to_project(
|
||||
target_project,
|
||||
user,
|
||||
target_branch,
|
||||
files: ci_glob
|
||||
)
|
||||
end
|
||||
|
||||
after do
|
||||
target_project.repository.delete_branch(target_branch)
|
||||
end
|
||||
|
||||
it_behaves_like 'merge request pipeline'
|
||||
|
||||
it_behaves_like 'merge train pipeline'
|
||||
end
|
||||
|
||||
context 'with fork project MRs' do
|
||||
let_it_be(:fork_project_mr_source) { fork_project(gitlab_org_gitlab_project, user, repository: true) }
|
||||
let(:source_project) { fork_project_mr_source }
|
||||
let(:target_project) { gitlab_org_gitlab_project }
|
||||
|
||||
context 'when MR is created from a fork project master branch' do
|
||||
let(:source_branch) { master_branch }
|
||||
let(:target_branch) { master_branch }
|
||||
let(:changed_files) { ['package.json'] }
|
||||
let(:expected_job_name) { 'rspec-all frontend_fixture 1/7' }
|
||||
|
||||
context 'when running MR pipeline in the context of the fork project' do
|
||||
let(:ci_project_namespace) { fork_project_mr_source.namespace.full_path }
|
||||
let(:ci_project_path) { fork_project_mr_source.full_path }
|
||||
let(:ci_project_name) { fork_project_mr_source.name }
|
||||
|
||||
it_behaves_like 'merge request pipeline'
|
||||
end
|
||||
|
||||
context 'when running MR pipeline in the context of canonical project' do
|
||||
it_behaves_like 'merge request pipeline'
|
||||
end
|
||||
|
||||
it_behaves_like 'merge train pipeline'
|
||||
end
|
||||
|
||||
context 'when MR is created from a fork project feature branch' do
|
||||
let(:source_branch) { "feature_branch_ci_#{SecureRandom.uuid}" }
|
||||
let(:target_branch) { master_branch }
|
||||
let(:changed_files) { ['package.json'] }
|
||||
let(:expected_job_name) { 'rspec-all frontend_fixture 1/7' }
|
||||
|
||||
context 'when running MR pipeline in the context of the fork project' do
|
||||
let(:ci_project_namespace) { fork_project_mr_source.namespace.full_path }
|
||||
let(:ci_project_path) { fork_project_mr_source.full_path }
|
||||
let(:ci_project_name) { fork_project_mr_source.name }
|
||||
|
||||
it_behaves_like 'merge request pipeline'
|
||||
end
|
||||
|
||||
context 'when running MR pipeline in the context of canonical project' do
|
||||
it_behaves_like 'merge request pipeline'
|
||||
end
|
||||
|
||||
it_behaves_like 'merge train pipeline'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -642,6 +642,20 @@ describe('IssuableItem', () => {
|
|||
expect(wrapper.emitted('select-issuable')).toEqual([[{ iid, webUrl }]]);
|
||||
});
|
||||
|
||||
it('includes fullPath in emitted event for work items', () => {
|
||||
const { iid, webUrl } = mockIssuable;
|
||||
const fullPath = 'gitlab-org/gitlab';
|
||||
|
||||
wrapper = createComponent({
|
||||
preventRedirect: true,
|
||||
issuable: { ...mockIssuable, namespace: { fullPath } },
|
||||
});
|
||||
|
||||
findIssuableTitleLink().vm.$emit('click', new MouseEvent('click'));
|
||||
|
||||
expect(wrapper.emitted('select-issuable')).toEqual([[{ iid, webUrl, fullPath }]]);
|
||||
});
|
||||
|
||||
it('does not apply highlighted class when item is not active', () => {
|
||||
wrapper = createComponent({
|
||||
preventRedirect: true,
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ describe('WorkItemDrawer', () => {
|
|||
listeners: {
|
||||
customEvent: mockListener,
|
||||
},
|
||||
provide: {
|
||||
fullPath: '/gitlab-org',
|
||||
},
|
||||
stubs: { workItemDetail: true },
|
||||
apolloProvider: createMockApollo([[deleteWorkItemMutation, deleteWorkItemMutationHandler]]),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_ro
|
|||
import WorkItemsListApp from '~/work_items/pages/work_items_list_app.vue';
|
||||
import { sortOptions, urlSortParams } from '~/work_items/pages/list/constants';
|
||||
import getWorkItemsQuery from '~/work_items/graphql/list/get_work_items.query.graphql';
|
||||
import WorkItemDrawer from '~/work_items/components/work_item_drawer.vue';
|
||||
import { STATE_CLOSED } from '~/work_items/constants';
|
||||
import { groupWorkItemsQueryResponse } from '../../mock_data';
|
||||
|
||||
jest.mock('~/lib/utils/scroll_utils', () => ({ scrollUp: jest.fn() }));
|
||||
|
|
@ -58,6 +60,7 @@ describeSkipVue3(skipReason, () => {
|
|||
const findIssuableList = () => wrapper.findComponent(IssuableList);
|
||||
const findIssueCardStatistics = () => wrapper.findComponent(IssueCardStatistics);
|
||||
const findIssueCardTimeInfo = () => wrapper.findComponent(IssueCardTimeInfo);
|
||||
const findDrawer = () => wrapper.findComponent(WorkItemDrawer);
|
||||
|
||||
const mountComponent = ({
|
||||
provide = {},
|
||||
|
|
@ -407,4 +410,94 @@ describeSkipVue3(skipReason, () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('work item drawer', () => {
|
||||
describe('when issues_list_drawer feature is disabled', () => {
|
||||
it('is not rendered when feature is disabled', async () => {
|
||||
mountComponent({
|
||||
provide: {
|
||||
glFeatures: {
|
||||
issuesListDrawer: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
await waitForPromises();
|
||||
|
||||
expect(findDrawer().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when issues_list_drawer feature is enabled', () => {
|
||||
beforeEach(async () => {
|
||||
mountComponent({
|
||||
provide: {
|
||||
glFeatures: {
|
||||
issuesListDrawer: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('is rendered when feature is enabled', () => {
|
||||
expect(findDrawer().exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('selecting issues', () => {
|
||||
const issue = groupWorkItemsQueryResponse.data.group.workItems.nodes[0];
|
||||
const payload = {
|
||||
iid: issue.iid,
|
||||
webUrl: issue.webUrl,
|
||||
fullPath: issue.namespace.fullPath,
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
findIssuableList().vm.$emit('select-issuable', payload);
|
||||
|
||||
await nextTick();
|
||||
});
|
||||
|
||||
it('opens drawer when work item is selected', () => {
|
||||
expect(findDrawer().props('open')).toBe(true);
|
||||
expect(findDrawer().props('activeItem')).toEqual(payload);
|
||||
});
|
||||
|
||||
const checkThatDrawerPropsAreEmpty = () => {
|
||||
expect(findDrawer().props('activeItem')).toBeNull();
|
||||
expect(findDrawer().props('open')).toBe(false);
|
||||
};
|
||||
|
||||
it('resets the selected item when the drawer is closed', async () => {
|
||||
findDrawer().vm.$emit('close');
|
||||
|
||||
await nextTick();
|
||||
|
||||
checkThatDrawerPropsAreEmpty();
|
||||
});
|
||||
|
||||
it('refetches and resets when work item is deleted', async () => {
|
||||
findDrawer().vm.$emit('workItemDeleted');
|
||||
|
||||
await nextTick();
|
||||
|
||||
checkThatDrawerPropsAreEmpty();
|
||||
|
||||
// first call when mounted, second call after deletion
|
||||
expect(defaultQueryHandler).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('refetches when the selected work item is closed', async () => {
|
||||
// component displays open work items by default
|
||||
findDrawer().vm.$emit('work-item-updated', {
|
||||
state: STATE_CLOSED,
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
|
||||
// first call when mounted, second call after update
|
||||
expect(defaultQueryHandler).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe GitlabSchema.types['MlModelVersion'], feature_category: :mlops do
|
||||
let_it_be(:model_version) { create(:ml_model_versions, :with_package, description: 'A description') }
|
||||
let_it_be(:model_version_markdown) { create(:ml_model_versions, :with_package, description: 'A **description**') }
|
||||
let_it_be(:project) { model_version.project }
|
||||
let_it_be(:project_markdown) { model_version_markdown.project }
|
||||
let_it_be(:current_user) { project.owner }
|
||||
|
||||
let(:query) do
|
||||
|
|
@ -17,6 +19,7 @@ RSpec.describe GitlabSchema.types['MlModelVersion'], feature_category: :mlops do
|
|||
version
|
||||
packageId
|
||||
description
|
||||
descriptionHtml
|
||||
candidate {
|
||||
id
|
||||
}
|
||||
|
|
@ -31,12 +34,43 @@ RSpec.describe GitlabSchema.types['MlModelVersion'], feature_category: :mlops do
|
|||
)
|
||||
end
|
||||
|
||||
let(:query_markdown) do
|
||||
%(
|
||||
query {
|
||||
mlModel(id: "gid://gitlab/Ml::Model/#{model_version_markdown.model.id}") {
|
||||
id
|
||||
latestVersion {
|
||||
id
|
||||
version
|
||||
packageId
|
||||
description
|
||||
descriptionHtml
|
||||
candidate {
|
||||
id
|
||||
}
|
||||
_links {
|
||||
packagePath
|
||||
showPath
|
||||
importPath
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
let(:data) { GitlabSchema.execute(query, context: { current_user: project.owner }).as_json }
|
||||
|
||||
specify { expect(described_class.description).to eq('Version of a machine learning model') }
|
||||
|
||||
subject(:data) { GitlabSchema.execute(query, context: { current_user: project.owner }).as_json }
|
||||
subject(:data_markdown) do
|
||||
GitlabSchema.execute(query_markdown, context: {
|
||||
current_user: project_markdown.owner
|
||||
}).as_json
|
||||
end
|
||||
|
||||
it 'includes all fields' do
|
||||
expected_fields = %w[id version created_at _links candidate package_id description]
|
||||
expected_fields = %w[id version created_at _links candidate package_id description description_html]
|
||||
|
||||
expect(described_class).to include_graphql_fields(*expected_fields)
|
||||
end
|
||||
|
|
@ -48,6 +82,7 @@ RSpec.describe GitlabSchema.types['MlModelVersion'], feature_category: :mlops do
|
|||
'id' => "gid://gitlab/Ml::ModelVersion/#{model_version.id}",
|
||||
'version' => model_version.version,
|
||||
'description' => 'A description',
|
||||
'descriptionHtml' => '<p data-sourcepos="1:1-1:13" dir="auto">A description</p>',
|
||||
'packageId' => "gid://gitlab/Packages::Package/#{model_version.package_id}",
|
||||
'candidate' => {
|
||||
'id' => "gid://gitlab/Ml::Candidate/#{model_version.candidate.id}"
|
||||
|
|
@ -59,4 +94,26 @@ RSpec.describe GitlabSchema.types['MlModelVersion'], feature_category: :mlops do
|
|||
}
|
||||
})
|
||||
end
|
||||
|
||||
it 'computes the correct properties with markdown' do
|
||||
version_data = data_markdown.dig('data', 'mlModel', 'latestVersion')
|
||||
version = model_version_markdown
|
||||
expect(version_data).to eq({
|
||||
'id' => "gid://gitlab/Ml::ModelVersion/#{version.id}",
|
||||
'version' => version.version,
|
||||
'description' => 'A **description**',
|
||||
'descriptionHtml' =>
|
||||
'<p data-sourcepos="1:1-1:17" dir="auto">A <strong data-sourcepos="1:3-1:17">description</strong></p>',
|
||||
'packageId' => "gid://gitlab/Packages::Package/#{version.package_id}",
|
||||
'candidate' => {
|
||||
'id' => "gid://gitlab/Ml::Candidate/#{version.candidate.id}"
|
||||
},
|
||||
'_links' => {
|
||||
'showPath' =>
|
||||
"/#{project_markdown.full_path}/-/ml/models/#{version.model.id}/versions/#{version.id}",
|
||||
'packagePath' => "/#{project_markdown.full_path}/-/packages/#{version.package_id}",
|
||||
'importPath' => "/api/v4/projects/#{project_markdown.id}/packages/ml_models/#{version.id}/files/"
|
||||
}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ RSpec.describe Projects::Ml::ModelRegistryHelper, feature_category: :mlops do
|
|||
'canWriteModelRegistry' => true,
|
||||
'maxAllowedFileSize' => 10737418240,
|
||||
'mlflowTrackingUrl' => "http://localhost/api/v4/projects/#{project.id}/ml/mlflow/",
|
||||
'markdownPreviewPath' => "http://localhost/#{project.full_path}/-/ml/preview_markdown"
|
||||
'markdownPreviewPath' => "/#{project.full_path}/-/preview_markdown"
|
||||
})
|
||||
end
|
||||
|
||||
|
|
@ -58,7 +58,7 @@ RSpec.describe Projects::Ml::ModelRegistryHelper, feature_category: :mlops do
|
|||
'modelId' => model.id,
|
||||
'modelName' => 'cool_model',
|
||||
'latestVersion' => model.latest_version.version,
|
||||
"markdownPreviewPath" => "http://localhost/#{project.full_path}/-/ml/preview_markdown"
|
||||
'markdownPreviewPath' => "/#{project.full_path}/-/preview_markdown"
|
||||
})
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ RSpec.describe WorkItemsHelper, feature_category: :team_planning do
|
|||
is_signed_in: current_user.present?.to_s,
|
||||
show_new_issue_link: 'true',
|
||||
issues_list_path: issues_group_path(group),
|
||||
report_abuse_path: add_category_abuse_reports_path,
|
||||
labels_manage_path: group_labels_path(group),
|
||||
can_admin_label: 'true'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe API::Entities::VirtualRegistries::Packages::Maven::Registry, feature_category: :virtual_registry do
|
||||
let(:registry) { build_stubbed(:virtual_registries_packages_maven_registry) }
|
||||
|
||||
subject { described_class.new(registry).as_json }
|
||||
|
||||
it { is_expected.to include(:id, :group_id, :cache_validity_hours, :created_at, :updated_at) }
|
||||
end
|
||||
|
|
@ -232,10 +232,6 @@ RSpec.describe API::Support::GitAccessActor do
|
|||
end
|
||||
|
||||
describe '#update_last_used_at!' do
|
||||
before do
|
||||
stub_feature_flags(disable_ssh_key_used_tracking: false)
|
||||
end
|
||||
|
||||
context 'when initialized with a User' do
|
||||
let(:user) { build(:user) }
|
||||
|
||||
|
|
@ -254,14 +250,6 @@ RSpec.describe API::Support::GitAccessActor do
|
|||
|
||||
subject.update_last_used_at!
|
||||
end
|
||||
|
||||
it 'does not update `last_used_at` when the functionality is disabled' do
|
||||
stub_feature_flags(disable_ssh_key_used_tracking: true)
|
||||
|
||||
expect(key).not_to receive(:update_last_used_at)
|
||||
|
||||
subject.update_last_used_at!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ issues:
|
|||
- issuable_resource_links
|
||||
- synced_epic
|
||||
- observability_metrics
|
||||
- observability_logs
|
||||
work_item_type:
|
||||
- issues
|
||||
- namespace
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Middleware::ActionControllerStaticContext, feature_category: :error_budgets do
|
||||
# to check the end to end tests, go to spec/requests/application_controller_spec.rb
|
||||
|
||||
let(:env) do
|
||||
# env mimicking a controller action parsed by the rails routes
|
||||
{ 'action_dispatch.request.path_parameters' => { controller: "dashboard/groups", action: "index" } }
|
||||
end
|
||||
|
||||
let(:app) { ->(_env) { [200, {}, ["Hello World"]] } }
|
||||
|
||||
context 'when feature flag is disabled' do
|
||||
it 'does not update context' do
|
||||
stub_feature_flags(controller_static_context: false)
|
||||
|
||||
described_class.new(app).call(env)
|
||||
|
||||
expect(Gitlab::ApplicationContext.current.keys).not_to include('meta.feature_category', 'meta.caller_id')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when feature flag is enabled' do
|
||||
it 'populates context with static controller attributes' do
|
||||
stub_feature_flags(controller_static_context: true)
|
||||
|
||||
described_class.new(app).call(env)
|
||||
|
||||
expect(Labkit::Context.current.to_h).to include({
|
||||
'meta.feature_category' => 'groups_and_projects',
|
||||
'meta.caller_id' => 'Dashboard::GroupsController#index'
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -53,6 +53,8 @@ RSpec.describe 'Marginalia spec' do
|
|||
end
|
||||
|
||||
it 'generates a query that includes the component and value' do
|
||||
stub_feature_flags(controller_static_context: false)
|
||||
|
||||
component_map.each do |component, value|
|
||||
expect(recorded.log.last).to include("#{component}:#{value}")
|
||||
end
|
||||
|
|
@ -74,6 +76,8 @@ RSpec.describe 'Marginalia spec' do
|
|||
end
|
||||
|
||||
it 'generates a query that includes the component and value' do
|
||||
stub_feature_flags(controller_static_context: false)
|
||||
|
||||
component_map.each do |component, value|
|
||||
expect(recorded.log.last).to include("#{component}:#{value}")
|
||||
end
|
||||
|
|
|
|||
|
|
@ -26,4 +26,14 @@ RSpec.describe VirtualRegistries::Packages::Maven::Registry, type: :model, featu
|
|||
it { is_expected.to validate_presence_of(:group) }
|
||||
it { is_expected.to validate_numericality_of(:cache_validity_hours).only_integer.is_greater_than_or_equal_to(0) }
|
||||
end
|
||||
|
||||
describe '.for_group' do
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:registry) { create(:virtual_registries_packages_maven_registry, group: group) }
|
||||
let_it_be(:other_registry) { create(:virtual_registries_packages_maven_registry) }
|
||||
|
||||
subject { described_class.for_group(group) }
|
||||
|
||||
it { is_expected.to eq([registry]) }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -43,7 +43,508 @@ RSpec.describe API::VirtualRegistries::Packages::Maven, feature_category: :virtu
|
|||
end
|
||||
|
||||
before do
|
||||
stub_config(dependency_proxy: { enabled: true })
|
||||
stub_config(dependency_proxy: { enabled: true }) # not enabled by default
|
||||
end
|
||||
|
||||
describe 'GET /api/v4/virtual_registries/packages/maven/registries' do
|
||||
let(:group_id) { group.id }
|
||||
let(:url) { "/virtual_registries/packages/maven/registries?group_id=#{group_id}" }
|
||||
|
||||
subject(:api_request) { get api(url), headers: headers }
|
||||
|
||||
shared_examples 'successful response' do
|
||||
it 'returns a successful response' do
|
||||
api_request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(Gitlab::Json.parse(response.body)).to contain_exactly(registry.as_json)
|
||||
end
|
||||
end
|
||||
|
||||
it { is_expected.to have_request_urgency(:low) }
|
||||
|
||||
it_behaves_like 'disabled feature flag'
|
||||
it_behaves_like 'disabled dependency proxy'
|
||||
it_behaves_like 'not authenticated user'
|
||||
|
||||
context 'with valid group_id' do
|
||||
it_behaves_like 'successful response'
|
||||
end
|
||||
|
||||
context 'with invalid group_id' do
|
||||
where(:group_id, :status) do
|
||||
non_existing_record_id | :not_found
|
||||
'foo' | :bad_request
|
||||
'' | :bad_request
|
||||
end
|
||||
|
||||
with_them do
|
||||
it_behaves_like 'returning response status', params[:status]
|
||||
end
|
||||
end
|
||||
|
||||
context 'with missing group_id' do
|
||||
let(:url) { '/virtual_registries/packages/maven/registries' }
|
||||
|
||||
it 'returns a bad request with missing group_id' do
|
||||
api_request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response['error']).to eq('group_id is missing, group_id is empty')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a non member user' do
|
||||
let_it_be(:user) { build_stubbed(:user) }
|
||||
|
||||
where(:group_access_level, :status) do
|
||||
'PUBLIC' | :ok
|
||||
'INTERNAL' | :ok
|
||||
'PRIVATE' | :not_found
|
||||
end
|
||||
|
||||
with_them do
|
||||
before do
|
||||
group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_access_level, false))
|
||||
end
|
||||
|
||||
if params[:status] == :ok
|
||||
it_behaves_like 'successful response'
|
||||
else
|
||||
it_behaves_like 'returning response status', params[:status]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'for authentication' do
|
||||
where(:token, :sent_as, :status) do
|
||||
:personal_access_token | :header | :ok
|
||||
:personal_access_token | :basic_auth | :ok
|
||||
:deploy_token | :header | :ok
|
||||
:deploy_token | :basic_auth | :ok
|
||||
:job_token | :header | :ok
|
||||
:job_token | :basic_auth | :ok
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:headers) do
|
||||
case sent_as
|
||||
when :header
|
||||
token_header(token)
|
||||
when :basic_auth
|
||||
token_basic_auth(token)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'returning response status', params[:status]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v4/virtual_registries/packages/maven/registries' do
|
||||
let_it_be(:registry_class) { ::VirtualRegistries::Packages::Maven::Registry }
|
||||
let(:url) { '/virtual_registries/packages/maven/registries' }
|
||||
|
||||
subject(:api_request) { post api(url), headers: headers, params: params }
|
||||
|
||||
shared_examples 'successful response' do
|
||||
it 'returns a successful response' do
|
||||
expect { api_request }.to change { registry_class.count }.by(1)
|
||||
|
||||
expect(registry_class.last.group_id).to eq(params[:group_id])
|
||||
expect(registry_class.last.cache_validity_hours).to eq(
|
||||
params[:cache_validity_hours] || registry_class.new.cache_validity_hours
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with valid params' do
|
||||
let(:params) { { group_id: group.id, cache_validity_hours: 24 } }
|
||||
|
||||
it { is_expected.to have_request_urgency(:low) }
|
||||
|
||||
it_behaves_like 'disabled feature flag'
|
||||
it_behaves_like 'disabled dependency proxy'
|
||||
it_behaves_like 'not authenticated user'
|
||||
|
||||
where(:user_role, :status) do
|
||||
:owner | :created
|
||||
:maintainer | :created
|
||||
:developer | :forbidden
|
||||
:reporter | :forbidden
|
||||
:guest | :forbidden
|
||||
end
|
||||
|
||||
with_them do
|
||||
before do
|
||||
registry_class.for_group(group).delete_all
|
||||
group.send(:"add_#{user_role}", user)
|
||||
end
|
||||
|
||||
if params[:status] == :created
|
||||
it_behaves_like 'successful response'
|
||||
else
|
||||
it_behaves_like 'returning response status', params[:status]
|
||||
end
|
||||
end
|
||||
|
||||
context 'without cache_validity_hours param' do
|
||||
let(:params) { { group_id: group.id } }
|
||||
|
||||
before_all do
|
||||
registry_class.for_group(group).delete_all
|
||||
group.add_maintainer(user)
|
||||
end
|
||||
|
||||
it_behaves_like 'successful response'
|
||||
end
|
||||
|
||||
context 'with existing registry' do
|
||||
before_all do
|
||||
group.add_maintainer(user)
|
||||
end
|
||||
|
||||
it 'returns a bad request' do
|
||||
api_request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response).to eq({ 'message' => { 'group' => ['has already been taken'] } })
|
||||
end
|
||||
end
|
||||
|
||||
context 'for authentication' do
|
||||
before_all do
|
||||
group.add_maintainer(user)
|
||||
end
|
||||
|
||||
before do
|
||||
registry_class.for_group(group).delete_all
|
||||
end
|
||||
|
||||
where(:token, :sent_as, :status) do
|
||||
:personal_access_token | :header | :created
|
||||
:personal_access_token | :basic_auth | :created
|
||||
:deploy_token | :header | :forbidden
|
||||
:deploy_token | :basic_auth | :forbidden
|
||||
:job_token | :header | :created
|
||||
:job_token | :basic_auth | :created
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:headers) do
|
||||
case sent_as
|
||||
when :header
|
||||
token_header(token)
|
||||
when :basic_auth
|
||||
token_basic_auth(token)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'returning response status', params[:status]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid params' do
|
||||
let(:valid_group_id) { group.id }
|
||||
|
||||
before_all do
|
||||
group.add_maintainer(user)
|
||||
end
|
||||
|
||||
where(:group_id, :cache_validity_hours, :status) do
|
||||
non_existing_record_id | 1 | :not_found
|
||||
'foo' | 1 | :bad_request
|
||||
'' | 1 | :bad_request
|
||||
ref(:valid_group_id) | 'a' | :bad_request
|
||||
ref(:valid_group_id) | -1 | :bad_request
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:params) { { group_id: group_id, cache_validity_hours: cache_validity_hours } }
|
||||
|
||||
it_behaves_like 'returning response status', params[:status]
|
||||
end
|
||||
end
|
||||
|
||||
context 'with subgroup' do
|
||||
let(:subgroup) { create(:group, parent: group, visibility_level: group.visibility_level) }
|
||||
|
||||
let(:params) { { group_id: subgroup.id, cache_validity_hours: 1 } }
|
||||
|
||||
before_all do
|
||||
group.add_maintainer(user)
|
||||
end
|
||||
|
||||
it 'returns a bad request beacuse it is not a top level group' do
|
||||
api_request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response).to eq({ 'message' => { 'group' => ['must be a top level Group'] } })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v4/virtual_registries/packages/maven/registries/:id' do
|
||||
let(:registry_id) { registry.id }
|
||||
let(:url) { "/virtual_registries/packages/maven/registries/#{registry_id}" }
|
||||
|
||||
subject(:api_request) { get api(url), headers: headers }
|
||||
|
||||
shared_examples 'successful response' do
|
||||
it 'returns a successful response' do
|
||||
api_request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(Gitlab::Json.parse(response.body)).to eq(registry.as_json)
|
||||
end
|
||||
end
|
||||
|
||||
it { is_expected.to have_request_urgency(:low) }
|
||||
|
||||
it_behaves_like 'disabled feature flag'
|
||||
it_behaves_like 'disabled dependency proxy'
|
||||
it_behaves_like 'not authenticated user'
|
||||
|
||||
context 'with valid registry_id' do
|
||||
it_behaves_like 'successful response'
|
||||
end
|
||||
|
||||
context 'with invalid registry_id' do
|
||||
where(:registry_id, :status) do
|
||||
non_existing_record_id | :not_found
|
||||
'foo' | :bad_request
|
||||
'' | :bad_request
|
||||
end
|
||||
|
||||
with_them do
|
||||
it_behaves_like 'returning response status', params[:status]
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a non member user' do
|
||||
let_it_be(:user) { build_stubbed(:user) }
|
||||
|
||||
where(:group_access_level, :status) do
|
||||
'PUBLIC' | :ok
|
||||
'INTERNAL' | :ok
|
||||
'PRIVATE' | :forbidden
|
||||
end
|
||||
with_them do
|
||||
before do
|
||||
group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_access_level, false))
|
||||
end
|
||||
|
||||
if params[:status] == :ok
|
||||
it_behaves_like 'successful response'
|
||||
else
|
||||
it_behaves_like 'returning response status', params[:status]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'for authentication' do
|
||||
where(:token, :sent_as, :status) do
|
||||
:personal_access_token | :header | :ok
|
||||
:personal_access_token | :basic_auth | :ok
|
||||
:deploy_token | :header | :ok
|
||||
:deploy_token | :basic_auth | :ok
|
||||
:job_token | :header | :ok
|
||||
:job_token | :basic_auth | :ok
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:headers) do
|
||||
case sent_as
|
||||
when :header
|
||||
token_header(token)
|
||||
when :basic_auth
|
||||
token_basic_auth(token)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'returning response status', params[:status]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PATCH /api/v4/virtual_registries/packages/maven/registries/:id' do
|
||||
let(:registry_id) { registry.id }
|
||||
let(:url) { "/virtual_registries/packages/maven/registries/#{registry_id}" }
|
||||
|
||||
subject(:api_request) { patch api(url), headers: headers, params: params }
|
||||
|
||||
shared_examples 'successful response' do
|
||||
it 'returns a successful response' do
|
||||
api_request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response['cache_validity_hours']).to eq(registry.reset.cache_validity_hours)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with valid params' do
|
||||
let(:params) { { cache_validity_hours: 2 } }
|
||||
|
||||
it { is_expected.to have_request_urgency(:low) }
|
||||
|
||||
it_behaves_like 'disabled feature flag'
|
||||
it_behaves_like 'disabled dependency proxy'
|
||||
it_behaves_like 'not authenticated user'
|
||||
|
||||
where(:user_role, :status) do
|
||||
:owner | :ok
|
||||
:maintainer | :ok
|
||||
:developer | :forbidden
|
||||
:reporter | :forbidden
|
||||
:guest | :forbidden
|
||||
end
|
||||
|
||||
with_them do
|
||||
before do
|
||||
group.send(:"add_#{user_role}", user)
|
||||
end
|
||||
|
||||
if params[:status] == :ok
|
||||
it_behaves_like 'successful response'
|
||||
else
|
||||
it_behaves_like 'returning response status', params[:status]
|
||||
end
|
||||
end
|
||||
|
||||
context 'for authentication' do
|
||||
before_all do
|
||||
group.add_maintainer(user)
|
||||
end
|
||||
|
||||
where(:token, :sent_as, :status) do
|
||||
:personal_access_token | :header | :ok
|
||||
:personal_access_token | :basic_auth | :ok
|
||||
:deploy_token | :header | :forbidden
|
||||
:deploy_token | :basic_auth | :forbidden
|
||||
:job_token | :header | :ok
|
||||
:job_token | :basic_auth | :ok
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:headers) do
|
||||
case sent_as
|
||||
when :header
|
||||
token_header(token)
|
||||
when :basic_auth
|
||||
token_basic_auth(token)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'returning response status', params[:status]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid params' do
|
||||
let(:valid_registry_id) { registry.id }
|
||||
|
||||
before_all do
|
||||
group.add_maintainer(user)
|
||||
end
|
||||
|
||||
where(:registry_id, :cache_validity_hours, :status) do
|
||||
non_existing_record_id | 1 | :not_found
|
||||
'foo' | 1 | :bad_request
|
||||
'' | 1 | :not_found
|
||||
ref(:valid_registry_id) | 'a' | :bad_request
|
||||
ref(:valid_registry_id) | '' | :bad_request
|
||||
ref(:valid_registry_id) | -1 | :bad_request
|
||||
ref(:valid_registry_id) | nil | :bad_request
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:params) { { cache_validity_hours: cache_validity_hours } }
|
||||
|
||||
it_behaves_like 'returning response status', params[:status]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE /api/v4/virtual_registries/packages/maven/registries/:id' do
|
||||
let(:registry_id) { registry.id }
|
||||
let(:url) { "/virtual_registries/packages/maven/registries/#{registry_id}" }
|
||||
|
||||
subject(:api_request) { delete api(url), headers: headers }
|
||||
|
||||
shared_examples 'successful response' do
|
||||
it 'returns a successful response' do
|
||||
expect { api_request }.to change { ::VirtualRegistries::Packages::Maven::Registry.count }.by(-1)
|
||||
end
|
||||
end
|
||||
|
||||
it { is_expected.to have_request_urgency(:low) }
|
||||
|
||||
it_behaves_like 'disabled feature flag'
|
||||
it_behaves_like 'disabled dependency proxy'
|
||||
it_behaves_like 'not authenticated user'
|
||||
|
||||
context 'with valid registry_id' do
|
||||
where(:user_role, :status) do
|
||||
:owner | :no_content
|
||||
:maintainer | :no_content
|
||||
:developer | :forbidden
|
||||
:reporter | :forbidden
|
||||
:guest | :forbidden
|
||||
end
|
||||
|
||||
with_them do
|
||||
before do
|
||||
group.send(:"add_#{user_role}", user)
|
||||
end
|
||||
|
||||
if params[:status] == :no_content
|
||||
it_behaves_like 'successful response'
|
||||
else
|
||||
it_behaves_like 'returning response status', params[:status]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid registry_id' do
|
||||
where(:registry_id, :status) do
|
||||
non_existing_record_id | :not_found
|
||||
'foo' | :bad_request
|
||||
'' | :not_found
|
||||
end
|
||||
|
||||
with_them do
|
||||
it_behaves_like 'returning response status', params[:status]
|
||||
end
|
||||
end
|
||||
|
||||
context 'for authentication' do
|
||||
before_all do
|
||||
group.add_maintainer(user)
|
||||
end
|
||||
|
||||
where(:token, :sent_as, :status) do
|
||||
:personal_access_token | :header | :no_content
|
||||
:personal_access_token | :basic_auth | :no_content
|
||||
:deploy_token | :header | :forbidden
|
||||
:deploy_token | :basic_auth | :forbidden
|
||||
:job_token | :header | :no_content
|
||||
:job_token | :basic_auth | :no_content
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:headers) do
|
||||
case sent_as
|
||||
when :header
|
||||
token_header(token)
|
||||
when :basic_auth
|
||||
token_basic_auth(token)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'returning response status', params[:status]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v4/virtual_registries/packages/maven/registries/:id/upstreams' do
|
||||
|
|
|
|||
|
|
@ -109,4 +109,113 @@ RSpec.describe ApplicationController, type: :request, feature_category: :shared
|
|||
}
|
||||
end
|
||||
end
|
||||
|
||||
describe 'static context middleware', feature_category: :error_budgets do
|
||||
# to check the middleware tests, go to spec/lib/gitlab/middleware/static_context_middleware_spec.rb
|
||||
|
||||
let(:expected_context) do
|
||||
{ 'meta.feature_category' => 'groups_and_projects',
|
||||
'meta.caller_id' => 'Dashboard::GroupsController#index' }
|
||||
end
|
||||
|
||||
context 'when feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(controller_static_context: false)
|
||||
end
|
||||
|
||||
context 'and action is successfully called' do
|
||||
it 'pushes static context to current context' do
|
||||
controller = nil
|
||||
allow_next_instance_of(Dashboard::GroupsController) do |instance|
|
||||
controller = instance
|
||||
end
|
||||
|
||||
get '/dashboard/groups' # randomly picked route
|
||||
|
||||
expect(response).to have_gitlab_http_status(:found)
|
||||
expect(controller.instance_variable_get(:@current_context)).to include expected_context
|
||||
end
|
||||
end
|
||||
|
||||
context 'and an exception is thrown before action' do
|
||||
it 'does not pushes static context to current context before controller callbacks' do
|
||||
context = {}
|
||||
unexpected_error = 'boom 💣💥'
|
||||
|
||||
allow_next_instance_of(Dashboard::GroupsController) do |controller|
|
||||
# picking up a random before_action method to raise an "unexpected" exception
|
||||
allow(controller).to receive(:authenticate_user!).and_raise(unexpected_error)
|
||||
|
||||
context.merge!(Gitlab::ApplicationContext.current.to_h)
|
||||
end
|
||||
|
||||
expect { get '/dashboard/groups' }.to raise_error(unexpected_error)
|
||||
expect(context.keys).not_to include(['meta.feature_category', 'meta.caller_id'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when feature flag is enabled' do
|
||||
before do
|
||||
stub_feature_flags(controller_static_context: true)
|
||||
end
|
||||
|
||||
context 'when action is successfully called' do
|
||||
it 'pushes static context to current context' do
|
||||
context = {}
|
||||
allow_next_instance_of(Dashboard::GroupsController) do |_controller|
|
||||
context.merge!(Gitlab::ApplicationContext.current.to_h)
|
||||
end
|
||||
|
||||
get '/dashboard/groups' # randomly picked route
|
||||
|
||||
expect(response).to have_gitlab_http_status(:found)
|
||||
expect(context).to include expected_context
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an exception is thrown before action' do
|
||||
it 'pushes static context to current context before controller callbacks' do
|
||||
context = {}
|
||||
unexpected_error = 'boom 💣💥'
|
||||
|
||||
allow_next_instance_of(Dashboard::GroupsController) do |controller|
|
||||
# picking up a random before_action method to raise an "unexpected" exception
|
||||
allow(controller).to receive(:authenticate_user!).and_raise(unexpected_error)
|
||||
|
||||
context.merge!(Gitlab::ApplicationContext.current.to_h)
|
||||
end
|
||||
|
||||
expect { get '/dashboard/groups' }.to raise_error(unexpected_error)
|
||||
expect(context).to include expected_context
|
||||
end
|
||||
end
|
||||
|
||||
context 'when controller overrides feature_category with nil' do
|
||||
it 'ignores nil feature category override' do
|
||||
context = {}
|
||||
|
||||
allow_next_instance_of(Projects::NotesController) do |controller|
|
||||
# mimicking a bug overriding feature_category with nil
|
||||
allow(controller).to receive(:feature_category).and_return(nil)
|
||||
|
||||
context.merge!(Gitlab::ApplicationContext.current.to_h)
|
||||
end
|
||||
|
||||
project = create(:project, :public)
|
||||
project_snippet = create(:project_snippet, project: project)
|
||||
create(:note_on_project_snippet, project: project, noteable: project_snippet)
|
||||
# picking a route targeting a controller that overrides feature_category
|
||||
get project_noteable_notes_path(
|
||||
project,
|
||||
target_type: 'project_snippet',
|
||||
target_id: project_snippet.id,
|
||||
html: true
|
||||
)
|
||||
|
||||
expect(context).to include({ 'meta.feature_category' => 'team_planning' })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe ConfirmationsController, type: :request, feature_category: :system_access do
|
||||
describe '#show' do
|
||||
let_it_be_with_reload(:user) { create(:user, :unconfirmed) }
|
||||
let(:expected_context) do
|
||||
{ 'meta.caller_id' => 'ConfirmationsController#show',
|
||||
'meta.user' => user.username }
|
||||
end
|
||||
|
||||
subject(:perform_request) do
|
||||
get user_confirmation_path, params: { confirmation_token: user.confirmation_token }
|
||||
end
|
||||
|
||||
include_examples 'set_current_context'
|
||||
end
|
||||
end
|
||||
|
|
@ -52,4 +52,57 @@ RSpec.describe OmniauthCallbacksController, feature_category: :system_access do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#atlassian_oauth2' do
|
||||
describe 'omniauth with strategies for atlassian_oauth2 when the user and identity already exist' do
|
||||
shared_context 'with sign_up' do
|
||||
let(:extern_uid) { 'my-uid' }
|
||||
let(:user) { create(:atlassian_user, extern_uid: extern_uid) }
|
||||
let(:expected_context) do
|
||||
{ 'meta.caller_id' => 'OmniauthCallbacksController#atlassian_oauth2',
|
||||
'meta.user' => user.username }
|
||||
end
|
||||
|
||||
subject do
|
||||
stub_omniauth_setting(block_auto_created_users: false)
|
||||
|
||||
post '/users/auth/atlassian_oauth2/callback'
|
||||
end
|
||||
|
||||
include_examples 'set_current_context'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#saml' do
|
||||
let(:last_request_id) { 'ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685' }
|
||||
let(:user) { create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: 'saml') }
|
||||
let(:mock_saml_response) { File.read('spec/fixtures/authentication/saml_response.xml') }
|
||||
let(:saml_config) { mock_saml_config_with_upstream_two_factor_authn_contexts }
|
||||
|
||||
before do
|
||||
stub_omniauth_saml_config(
|
||||
enabled: true,
|
||||
auto_link_saml_user: true,
|
||||
allow_single_sign_on: ['saml'],
|
||||
providers: [saml_config]
|
||||
)
|
||||
mock_auth_hash_with_saml_xml('saml', +'my-uid', user.email, mock_saml_response)
|
||||
end
|
||||
|
||||
describe 'with IdP initiated request' do
|
||||
let(:expected_context) do
|
||||
{ 'meta.caller_id' => 'OmniauthCallbacksController#saml',
|
||||
'meta.user' => user.username }
|
||||
end
|
||||
|
||||
subject do
|
||||
sign_in user
|
||||
|
||||
post '/users/auth/saml'
|
||||
end
|
||||
|
||||
include_examples 'set_current_context'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe PasswordsController, type: :request, feature_category: :system_access do
|
||||
describe '#update' do
|
||||
let(:user) { create(:user, password_automatically_set: true, password_expires_at: 10.minutes.ago) }
|
||||
let(:expected_context) do
|
||||
{ 'meta.caller_id' => 'PasswordsController#update',
|
||||
'meta.user' => user.username }
|
||||
end
|
||||
|
||||
subject(:perform_request) do
|
||||
password = User.random_password
|
||||
put user_password_path, params: {
|
||||
user: {
|
||||
password: password,
|
||||
password_confirmation: password,
|
||||
reset_password_token: user.send_reset_password_instructions
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
include_examples 'set_current_context'
|
||||
end
|
||||
end
|
||||
|
|
@ -3,12 +3,16 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe RegistrationsController, type: :request, feature_category: :system_access do
|
||||
describe 'POST #create' do
|
||||
describe '#create' do
|
||||
let_it_be(:user_attrs) { build_stubbed(:user).slice(:first_name, :last_name, :username, :email, :password) }
|
||||
let(:expected_context) do
|
||||
{ 'meta.caller_id' => 'RegistrationsController#create' }
|
||||
end
|
||||
|
||||
subject(:request) { post user_registration_path, params: { user: user_attrs } }
|
||||
|
||||
it_behaves_like 'Base action controller'
|
||||
it_behaves_like 'set_current_context'
|
||||
|
||||
context 'when email confirmation is required' do
|
||||
before do
|
||||
|
|
@ -29,4 +33,19 @@ RSpec.describe RegistrationsController, type: :request, feature_category: :syste
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#destroy' do
|
||||
let(:user) { create(:user) }
|
||||
let(:expected_context) do
|
||||
{ 'meta.caller_id' => 'RegistrationsController#destroy',
|
||||
'meta.user' => user.username }
|
||||
end
|
||||
|
||||
subject do
|
||||
sign_in(user)
|
||||
delete user_registration_path
|
||||
end
|
||||
|
||||
include_examples 'set_current_context'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe SessionsController, type: :request, feature_category: :system_access do
|
||||
describe '#destroy' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let(:expected_context) do
|
||||
{ 'meta.caller_id' => 'SessionsController#destroy',
|
||||
'meta.user' => user.username }
|
||||
end
|
||||
|
||||
subject(:perform_request) do
|
||||
sign_in(user)
|
||||
post destroy_user_session_path
|
||||
end
|
||||
|
||||
include_examples 'set_current_context'
|
||||
end
|
||||
|
||||
describe '#new' do
|
||||
let(:expected_context) do
|
||||
{ 'meta.caller_id' => 'SessionsController#new' }
|
||||
end
|
||||
|
||||
subject(:perform_request) do
|
||||
get new_user_session_path
|
||||
end
|
||||
|
||||
include_examples 'set_current_context'
|
||||
end
|
||||
|
||||
describe '#create' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let(:expected_context) do
|
||||
{ 'meta.caller_id' => 'SessionsController#create',
|
||||
'meta.user' => user.username }
|
||||
end
|
||||
|
||||
subject(:perform_request) do
|
||||
user.update!(failed_attempts: User.maximum_attempts.pred)
|
||||
post user_session_path, params: { user: { login: user.username, password: user.password.succ } }
|
||||
end
|
||||
|
||||
include_examples 'set_current_context'
|
||||
end
|
||||
end
|
||||
|
|
@ -151,7 +151,7 @@ RSpec.configure do |config|
|
|||
metadata[:type] = :feature
|
||||
end
|
||||
|
||||
config.define_derived_metadata(file_path: %r{spec/dot_gitlab_ci/job_dependency_spec.rb}) do |metadata|
|
||||
config.define_derived_metadata(file_path: %r{spec/dot_gitlab_ci/ci_configuration_validation/}) do |metadata|
|
||||
metadata[:ci_config_validation] = true
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples "set_current_context" do
|
||||
it 'sets the metadata of the request in the context' do |example|
|
||||
raise('this shared example should be used in a request spec only') unless example.metadata[:type] == :request
|
||||
|
||||
raise('expected_context should be provided') unless example.metadata[:expected_context].nil?
|
||||
|
||||
raise('described_class is invalid') if described_class.nil?
|
||||
|
||||
meta = example.metadata[:example_group]
|
||||
loop do
|
||||
most_outer_scope = meta[:scoped_id].split(':').size == 1
|
||||
raise('this shared example should be used within the scope of the controller action') if most_outer_scope
|
||||
|
||||
describe_action_scope = meta[:scoped_id].split(':').size == 2
|
||||
break if describe_action_scope
|
||||
|
||||
meta = meta[:parent_example_group]
|
||||
end
|
||||
|
||||
inferred_controller_action = meta[:description]
|
||||
unless inferred_controller_action.start_with?('#')
|
||||
raise('controller action describe should be in the form of "#action_name"')
|
||||
end
|
||||
|
||||
inferred_controller_action = inferred_controller_action.delete('#')
|
||||
|
||||
expect_next_instance_of(described_class) do |controller|
|
||||
expect(controller).to receive(inferred_controller_action).and_wrap_original do |m, *args|
|
||||
m.call(*args)
|
||||
|
||||
expect(Gitlab::ApplicationContext.current).to include(expected_context)
|
||||
end
|
||||
end
|
||||
|
||||
raise('subject/let binding (perform_request) should be provided') unless try(:perform_request) || subject
|
||||
end
|
||||
end
|
||||
|
|
@ -31,39 +31,21 @@ RSpec.describe 'user_settings/ssh_keys/_key.html.haml', feature_category: :syste
|
|||
expect(rendered).to have_button('Remove')
|
||||
end
|
||||
|
||||
context 'when disable_ssh_key_used_tracking is enabled' do
|
||||
before do
|
||||
stub_feature_flags(disable_ssh_key_used_tracking: true)
|
||||
end
|
||||
it 'displays the correct last used date' do
|
||||
render
|
||||
|
||||
it 'renders "Unavailable" for last used' do
|
||||
render
|
||||
|
||||
expect(rendered).to have_text('Unavailable')
|
||||
end
|
||||
expect(rendered).to have_text(l(key.last_used_at, format: "%b %d, %Y"))
|
||||
end
|
||||
|
||||
context 'when disable_ssh_key_used_tracking is disabled' do
|
||||
before do
|
||||
stub_feature_flags(disable_ssh_key_used_tracking: false)
|
||||
context 'when the key has not been used' do
|
||||
let_it_be(:key) do
|
||||
build_stubbed(:personal_key, user: user, last_used_at: nil)
|
||||
end
|
||||
|
||||
it 'displays the correct last used date' do
|
||||
it 'renders "Never" for last used' do
|
||||
render
|
||||
|
||||
expect(rendered).to have_text(l(key.last_used_at, format: "%b %d, %Y"))
|
||||
end
|
||||
|
||||
context 'when the key has not been used' do
|
||||
let_it_be(:key) do
|
||||
build_stubbed(:personal_key, user: user, last_used_at: nil)
|
||||
end
|
||||
|
||||
it 'renders "Never" for last used' do
|
||||
render
|
||||
|
||||
expect(rendered).to have_text('Never')
|
||||
end
|
||||
expect(rendered).to have_text('Never')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue