Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-07-16 15:12:45 +00:00
parent 8ae6036da0
commit b744b11e7d
107 changed files with 1073 additions and 1159 deletions

View File

@ -131,32 +131,25 @@ detect-tests:
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/ci/${BUILD_OS}-${OS_VERSION}-slim-ruby-${RUBY_VERSION}:rubygems-${RUBYGEMS_VERSION}-git-${GIT_VERSION}
needs: []
stage: prepare
variables:
RSPEC_TESTS_MAPPING_ENABLED: "true"
MAPPING_ARCHIVE: $RSPEC_PACKED_TESTS_MAPPING_PATH
before_script:
- apt update && apt install -y curl
- source ./scripts/utils.sh
- source ./scripts/rspec_helpers.sh
- retrieve_frontend_fixtures_mapping
# Use alternative coverage based mappings for tier-2 pipeline
- |
echo "Fetching crystalball mappings"
if echo "$CI_MERGE_REQUEST_LABELS" | grep -q "pipeline::tier-2"; then
echo "Using alternative coverage based mapping path: $RSPEC_PACKED_TESTS_MAPPING_ALT_PATH"
MAPPING_ARCHIVE=$(echo "$RSPEC_PACKED_TESTS_MAPPING_ALT_PATH")
echo "Using alternative coverage based mappings"
MAPPING_TYPE=coverage
else
echo "Using standard described_class based mapping path: $RSPEC_PACKED_TESTS_MAPPING_PATH"
MAPPING_ARCHIVE=$(echo "$RSPEC_PACKED_TESTS_MAPPING_PATH")
echo "Using standard described_class based mappings"
MAPPING_TYPE=described_class
fi
retrieve_tests_mapping "$MAPPING_ARCHIVE"
script:
- |
# $FIND_CHANGES_MERGE_REQUEST_IID is defined in as-if-foss.gitlab-ci.yml
if [ -n "$CI_MERGE_REQUEST_IID" ] || [ -n "$FIND_CHANGES_MERGE_REQUEST_IID" ]; then
mkdir -p $(dirname "$RSPEC_CHANGED_FILES_PATH")
tooling/bin/predictive_tests --select-tests
tooling/bin/predictive_tests --select-tests --with-crystalball-mappings --mapping-type $MAPPING_TYPE
filter_rspec_matched_foss_tests ${RSPEC_MATCHING_TEST_FILES_PATH} ${RSPEC_MATCHING_TESTS_FOSS_PATH};
filter_rspec_matched_ee_tests ${RSPEC_MATCHING_TEST_FILES_PATH} ${RSPEC_MATCHING_TESTS_EE_PATH};

View File

@ -1,21 +0,0 @@
---
# Cop supports --autocorrect.
InternalAffairs/NodeTypePredicate:
Details: grace period
Exclude:
- 'rubocop/cop/avoid_break_from_strong_memoize.rb'
- 'rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers.rb'
- 'rubocop/cop/avoid_return_from_blocks.rb'
- 'rubocop/cop/graphql/descriptions.rb'
- 'rubocop/cop/migration/add_limit_to_text_columns.rb'
- 'rubocop/cop/migration/add_reference.rb'
- 'rubocop/cop/migration/background_migration_missing_active_concern.rb'
- 'rubocop/cop/migration/datetime.rb'
- 'rubocop/cop/migration/prevent_adding_columns.rb'
- 'rubocop/cop/migration/prevent_strings.rb'
- 'rubocop/cop/migration/refer_to_index_by_name.rb'
- 'rubocop/cop/prefer_class_methods_over_module.rb'
- 'rubocop/cop/project_path_helper.rb'
- 'rubocop/cop/rspec/have_gitlab_http_status.rb'
- 'rubocop/cop/static_translation_definition.rb'
- 'rubocop/cop/usage_data/distinct_count_by_large_foreign_key.rb'

View File

@ -47,7 +47,7 @@ export default {
action: () => this.execute('setHorizontalRule', 'horizontalRule'),
},
{
text: __('GitLab Query Language (GLQL) view'),
text: __('Embedded view'),
action: () => this.execute('insertGLQLView', 'glqlView'),
badge: {
text: __('Beta'),

View File

@ -66,7 +66,7 @@ const CODE_BLOCK_LANGUAGES = [
{ syntax: 'gauss', variants: 'gss', label: 'GAUSS' },
{ syntax: 'gcode', variants: 'nc', label: 'G-code (ISO 6983)' },
{ syntax: 'gherkin', variants: 'feature', label: 'Gherkin' },
{ syntax: 'glql', label: 'GitLab Query Language (GLQL)' },
{ syntax: 'glql', label: 'Embedded view (GLQL)' },
{ syntax: 'glsl', label: 'GLSL' },
{ syntax: 'gml', label: 'GML' },
{ syntax: 'go', variants: 'golang', label: 'Go' },

View File

@ -215,7 +215,12 @@ export default {
return !this.isCollapsed || this.automaticallyCollapsed;
},
showWarning() {
return this.isCollapsed && this.automaticallyCollapsed && !this.viewDiffsFileByFile;
return (
!this.reviewed &&
this.isCollapsed &&
this.automaticallyCollapsed &&
!this.viewDiffsFileByFile
);
},
expandableWarning() {
return this.file.viewer?.generated
@ -629,7 +634,7 @@ export default {
<div v-else v-safe-html="errorMessage" class="nothing-here-block"></div>
</div>
<template v-else>
<div v-if="showWarning" :class="$options.warningClasses">
<div v-if="showWarning" :class="$options.warningClasses" data-testid="diff-file-warning">
<p class="!gl-mb-0">
<gl-sprintf :message="expandableWarning">
<template #tag="{ content }">

View File

@ -60,14 +60,14 @@ export default {
<gl-disclosure-dropdown
v-gl-tooltip
class="glql-actions"
:title="__('GLQL view options')"
:title="__('Embedded view options')"
:items="items"
:toggle-id="toggleId"
:no-caret="true"
size="small"
category="tertiary"
icon="ellipsis_v"
:toggle-text="__('GLQL view options')"
:toggle-text="__('Embedded view options')"
text-sr-only
placement="bottom-end"
/>

View File

@ -91,7 +91,8 @@ export default {
},
title() {
return (
this.config.title || (this.config.display === 'table' ? __('GLQL table') : __('GLQL list'))
this.config.title ||
(this.config.display === 'table' ? __('Embedded table view') : __('Embedded list view'))
);
},
showEmptyState() {
@ -232,13 +233,13 @@ export default {
i18n: {
glqlDisplayError: {
variant: 'warning',
title: __('An error occurred when trying to display this GLQL view:'),
title: __('An error occurred when trying to display this embedded view:'),
},
glqlLimitError: {
variant: 'warning',
title: sprintf(
__(
'Only %{n} GLQL views can be automatically displayed on a page. Click the button below to manually display this block.',
'Only %{n} embedded views can be automatically displayed on a page. Click the button below to manually display this block.',
),
{ n: MAX_GLQL_BLOCKS },
),
@ -246,16 +247,19 @@ export default {
},
glqlTimeoutError: {
variant: 'warning',
title: sprintf(__('GLQL view timed out. Add more filters to reduce the number of results.'), {
n: MAX_GLQL_BLOCKS,
}),
title: sprintf(
__('Embedded view timed out. Add more filters to reduce the number of results.'),
{
n: MAX_GLQL_BLOCKS,
},
),
action: __('Retry'),
},
glqlForbiddenError: {
variant: 'danger',
title: __('GLQL view timed out. Try again later.'),
title: __('Embedded view timed out. Try again later.'),
},
loadGlqlView: __('Load GLQL view'),
loadGlqlView: __('Load embedded view'),
},
numGlqlBlocks: new Counter(MAX_GLQL_BLOCKS),
};

View File

@ -16,14 +16,14 @@ export default {
return glqlWorkItemsFeatureFlagEnabled();
},
},
docsPath: `${helpPagePath('user/glql/_index')}#glql-views`,
docsPath: `${helpPagePath('user/glql/_index')}#embedded-views`,
};
</script>
<template>
<div class="gl-flex gl-items-center gl-gap-1 gl-text-sm gl-text-subtle">
<gl-icon class="gl-mb-1 gl-mr-1" :size="12" name="tanuki" />
<gl-sprintf :message="__('%{linkStart}View%{linkEnd} powered by GLQL')">
<gl-sprintf :message="__('%{linkStart}Embedded view%{linkEnd} powered by GLQL')">
<template #link="{ content }">
<gl-link
:href="$options.docsPath"

View File

@ -21,7 +21,6 @@ import inboundRemoveProjectCIJobTokenScopeMutation from '../graphql/mutations/in
import inboundRemoveGroupCIJobTokenScopeMutation from '../graphql/mutations/inbound_remove_group_ci_job_token_scope.mutation.graphql';
import inboundUpdateCIJobTokenScopeMutation from '../graphql/mutations/inbound_update_ci_job_token_scope.mutation.graphql';
import inboundGetCIJobTokenScopeQuery from '../graphql/queries/inbound_get_ci_job_token_scope.query.graphql';
import inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery from '../graphql/queries/inbound_get_groups_and_projects_with_ci_job_token_scope.query.graphql';
import autopopulateAllowlistMutation from '../graphql/mutations/autopopulate_allowlist.mutation.graphql';
import getCiJobTokenScopeAllowlistQuery from '../graphql/queries/get_ci_job_token_scope_allowlist.query.graphql';
import getAuthLogCountQuery from '../graphql/queries/get_auth_log_count.query.graphql';
@ -91,7 +90,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
inject: ['enforceAllowlist', 'fullPath', 'projectAllowlistLimit', 'isJobTokenPoliciesEnabled'],
inject: ['enforceAllowlist', 'fullPath', 'projectAllowlistLimit'],
apollo: {
authLogCount: {
query: getAuthLogCountQuery,
@ -126,37 +125,20 @@ export default {
},
groupsAndProjectsWithAccess: {
query() {
return this.isJobTokenPoliciesEnabled
? getCiJobTokenScopeAllowlistQuery
: inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery;
return getCiJobTokenScopeAllowlistQuery;
},
variables() {
return { fullPath: this.fullPath };
},
update({ project }) {
let groups;
let projects;
if (this.isJobTokenPoliciesEnabled) {
const allowlist = project?.ciJobTokenScopeAllowlist;
groups = this.mapAllowlistNodes(allowlist?.groupsAllowlist);
projects = this.mapAllowlistNodes(allowlist?.projectsAllowlist);
// Add a dummy entry for the current project. The new ciJobTokenScopeAllowlist endpoint doesn't have an entry
// for the current project like the old ciJobTokenScope endpoint did, so we have to add it in manually, if it
// doesn't exist yet.
if (!projects.some(({ id }) => id === project.id))
projects.push({ ...project, defaultPermissions: true, jobTokenPolicies: [] });
} else {
projects = project?.ciJobTokenScope?.inboundAllowlist?.nodes ?? [];
groups = project?.ciJobTokenScope?.groupsAllowlist?.nodes ?? [];
const groupAllowlistAutopopulatedIds =
project?.ciJobTokenScope?.groupAllowlistAutopopulatedIds ?? [];
const inboundAllowlistAutopopulatedIds =
project?.ciJobTokenScope?.inboundAllowlistAutopopulatedIds ?? [];
projects = this.addAutopopulatedAttribute(projects, inboundAllowlistAutopopulatedIds);
groups = this.addAutopopulatedAttribute(groups, groupAllowlistAutopopulatedIds);
}
const allowlist = project?.ciJobTokenScopeAllowlist;
const groups = this.mapAllowlistNodes(allowlist?.groupsAllowlist);
const projects = this.mapAllowlistNodes(allowlist?.projectsAllowlist);
// Add a dummy entry for the current project. The new ciJobTokenScopeAllowlist endpoint doesn't have an entry
// for the current project like the old ciJobTokenScope endpoint did, so we have to add it in manually, if it
// doesn't exist yet.
if (!projects.some(({ id }) => id === project.id))
projects.push({ ...project, defaultPermissions: true, jobTokenPolicies: [] });
return { projects, groups };
},
@ -418,12 +400,6 @@ export default {
this.namespaceToEdit = namespace;
showFormFn();
},
addAutopopulatedAttribute(collection, idList) {
return collection.map((item) => ({
...item,
autopopulated: idList.includes(item.id),
}));
},
},
};
</script>
@ -545,7 +521,6 @@ export default {
:items="allowlist"
:loading="isAllowlistLoading"
:loading-message="allowlistLoadingMessage"
:show-policies="isJobTokenPoliciesEnabled"
@editItem="showNamespaceForm($event, showForm)"
@removeItem="namespaceToRemove = $event"
/>

View File

@ -7,7 +7,7 @@ import PoliciesSelector from './policies_selector.vue';
export default {
components: { GlFormGroup, GlButton, GlFormInput, PoliciesSelector },
inject: ['fullPath', 'isJobTokenPoliciesEnabled'],
inject: ['fullPath'],
props: {
namespace: {
type: Object,
@ -51,12 +51,12 @@ export default {
this.isSaving = true;
this.errorMessage = '';
const variables = { projectPath: this.fullPath, targetPath: this.targetPath };
if (this.isJobTokenPoliciesEnabled) {
variables.defaultPermissions = this.defaultPermissions;
variables.jobTokenPolicies = this.defaultPermissions ? [] : this.jobTokenPolicies;
}
const variables = {
projectPath: this.fullPath,
targetPath: this.targetPath,
defaultPermissions: this.defaultPermissions,
};
variables.jobTokenPolicies = this.defaultPermissions ? [] : this.jobTokenPolicies;
const mutation = this.namespace ? editNamespaceMutation : addNamespaceMutation;
const response = await this.$apollo.mutate({ mutation, variables });
@ -104,7 +104,6 @@ export default {
</gl-form-group>
<policies-selector
v-if="isJobTokenPoliciesEnabled"
:is-default-permissions-selected="defaultPermissions"
:job-token-policies="jobTokenPolicies"
:disabled="isSaving"

View File

@ -47,20 +47,13 @@ export default {
required: false,
default: '',
},
// This can be removed after outbound_token_access.vue is removed, which is a deprecated feature. We need to hide
// policies for that component, but show them on inbound_token_access.vue.
showPolicies: {
type: Boolean,
required: false,
default: true,
},
},
computed: {
fields() {
const fullPath = {
key: 'fullPath',
label: s__('CICD|Group or project'),
tdClass: this.showPolicies ? 'md:gl-w-3/5' : 'gl-w-full',
tdClass: 'md:gl-w-3/5',
};
const policies = {
key: 'jobTokenPolicies',
@ -74,7 +67,7 @@ export default {
tdClass: 'md:!gl-pb-0 md:!gl-pt-4',
};
return this.showPolicies ? [fullPath, policies, actions] : [fullPath, actions];
return [fullPath, policies, actions];
},
},
methods: {
@ -161,7 +154,6 @@ export default {
<template #cell(actions)="{ item }">
<div class="gl-flex gl-gap-2">
<gl-button
v-if="showPolicies"
icon="pencil"
:aria-label="__('Edit')"
data-testid="token-access-table-edit-button"

View File

@ -1,27 +0,0 @@
query inboundGetGroupsAndProjectsWithCIJobTokenScope($fullPath: ID!) {
project(fullPath: $fullPath) {
id
ciJobTokenScope {
groupsAllowlist {
nodes {
id
name
fullPath
avatarUrl
webUrl
}
}
inboundAllowlist {
nodes {
id
name
fullPath
avatarUrl
webUrl
}
}
groupAllowlistAutopopulatedIds
inboundAllowlistAutopopulatedIds
}
}
}

View File

@ -2,7 +2,6 @@ import { GlToast } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils';
import TokenAccessApp from './components/token_access_app.vue';
import cacheConfig from './graphql/cache_config';
@ -20,13 +19,8 @@ export const initTokenAccess = (containerId = 'js-ci-token-access-app') => {
return false;
}
const {
csvDownloadPath,
enforceAllowlist,
fullPath,
projectAllowlistLimit,
jobTokenPoliciesEnabled,
} = containerEl.dataset;
const { csvDownloadPath, enforceAllowlist, fullPath, projectAllowlistLimit } =
containerEl.dataset;
return new Vue({
el: containerEl,
@ -37,7 +31,6 @@ export const initTokenAccess = (containerId = 'js-ci-token-access-app') => {
enforceAllowlist: JSON.parse(enforceAllowlist),
fullPath,
projectAllowlistLimit: Number(projectAllowlistLimit),
isJobTokenPoliciesEnabled: parseBoolean(jobTokenPoliciesEnabled),
},
render(createElement) {
return createElement(TokenAccessApp);

View File

@ -1,13 +1,17 @@
<script>
import { GlButton, GlLink, GlSprintf, GlTooltipDirective } from '@gitlab/ui';
import { GlButton, GlLink, GlSprintf, GlTooltipDirective, GlIcon } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import PageHeading from '~/vue_shared/components/page_heading.vue';
import wikiPageQuery from '~/wikis/graphql/wiki_page.query.graphql';
import wikiPageSubscribeMutation from '~/wikis/graphql/wiki_page_subscribe.mutation.graphql';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import WikiMoreDropdown from './wiki_more_dropdown.vue';
export default {
components: {
GlButton,
GlIcon,
GlLink,
GlSprintf,
WikiMoreDropdown,
@ -28,6 +32,21 @@ export default {
isEditingPath: { default: null },
wikiUrl: { default: null },
pagePersisted: { default: null },
queryVariables: { default: null },
},
apollo: {
wikiPage: {
query: wikiPageQuery,
variables() {
return { ...this.queryVariables, skipDiscussions: true };
},
},
},
data() {
return {
changingSubState: false,
wikiPage: {},
};
},
computed: {
pageHeadingComputed() {
@ -57,6 +76,16 @@ export default {
editTooltip() {
return `${this.editTooltipText} <kbd class='flat ml-1' aria-hidden=true>e</kbd>`;
},
subscribeItem() {
return {
text: this.wikiPage?.subscribed ? __('Notifications On') : __('Notifications Off'),
icon: this.wikiPage?.subscribed ? 'notifications' : 'notifications-off',
action: this.toggleSubscribe,
extraAttrs: {
'data-testid': 'page-subscribe-button',
},
};
},
},
mounted() {
if (this.editButtonUrl) {
@ -81,6 +110,48 @@ export default {
setEditingMode() {
this.$emit('is-editing', true);
},
async toggleSubscribe() {
if (this.changingSubState) return;
this.changingSubState = true;
const newSubState = !this.wikiPage.subscribed;
try {
await this.$apollo.mutate({
mutation: wikiPageSubscribeMutation,
variables: {
id: this.wikiPage.id,
subscribed: newSubState,
},
optimisticResponse: {
wikiPageSubscribe: {
errors: [],
wikiPage: {
id: this.wikiPage.id,
subscribed: newSubState,
},
},
},
});
const message = newSubState
? __('Notifications turned on')
: __('Notifications turned off');
this.$toast.show(message);
} catch (error) {
this.handleSubscribeError(error, newSubState);
}
this.changingSubState = false;
},
handleSubscribeError(error, newSubState) {
const message = newSubState
? __('An error occurred while subscribing to this page. Please try again later.')
: __('An error occurred while unsubscribing from this page. Please try again later.');
this.$toast.show(message);
Sentry.captureException(error);
},
},
i18n: {
edit: __('Edit'),
@ -110,6 +181,19 @@ export default {
>
{{ $options.i18n.edit }}
</gl-button>
<gl-button
v-gl-tooltip.html
class="btn-icon"
:disabled="!wikiPage.id"
:title="subscribeItem.text"
data-testid="wiki-subscribe-button"
@click="toggleSubscribe"
>
<gl-icon
:name="subscribeItem.icon"
:class="{ '!gl-text-status-info': wikiPage.subscribed }"
/>
</gl-button>
<gl-button
v-gl-tooltip.html
icon="chevron-double-lg-left"

View File

@ -1,13 +1,19 @@
#import "./notes/wiki_page_note.fragment.graphql"
query wikiPageQuery($slug: String, $projectId: ProjectID, $namespaceId: NamespaceID) {
query wikiPageQuery(
$slug: String
$projectId: ProjectID
$namespaceId: NamespaceID
$skipDiscussions: Boolean = false
) {
wikiPage(slug: $slug, projectId: $projectId, namespaceId: $namespaceId) {
id
title
subscribed
userPermissions {
markNoteAsInternal
}
discussions {
discussions @skip(if: $skipDiscussions) {
nodes {
id
replyId

View File

@ -0,0 +1,9 @@
mutation WikiPageSubscribe($id: WikiPageMetaID!, $subscribed: Boolean!) {
wikiPageSubscribe(input: { id: $id, subscribed: $subscribed }) {
wikiPage {
id
subscribed
}
errors
}
}

View File

@ -3,6 +3,8 @@ import VueApollo from 'vue-apollo';
import createApolloClient from '~/lib/graphql';
import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils';
import csrf from '~/lib/utils/csrf';
import { TYPENAME_PROJECT, TYPENAME_GROUP } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import SidebarResizer from './components/sidebar_resizer.vue';
import Wikis from './wikis';
import WikiContentApp from './app.vue';
@ -46,11 +48,24 @@ const mountWikiContentApp = () => {
pagePersisted,
templates,
formatOptions,
containerId,
containerType,
} = el.dataset;
Vue.use(VueApollo);
const apolloProvider = new VueApollo({ defaultClient: createApolloClient() });
const pageInfoData = convertObjectPropsToCamelCase(JSON.parse(pageInfo));
const queryVariables = {
slug: pageInfoData.slug,
};
if (containerType === 'project') {
queryVariables.projectId = convertToGraphQLId(TYPENAME_PROJECT, containerId);
} else if (containerType === 'group') {
queryVariables.namespaceId = convertToGraphQLId(TYPENAME_GROUP, containerId);
}
return new Vue({
el,
apolloProvider,
@ -59,7 +74,8 @@ const mountWikiContentApp = () => {
pageHeading,
contentApi,
showEditButton: parseBoolean(showEditButton),
pageInfo: convertObjectPropsToCamelCase(JSON.parse(pageInfo)),
pageInfo: pageInfoData,
queryVariables,
isPageTemplate: parseBoolean(isPageTemplate),
isPageHistorical: parseBoolean(isPageHistorical),
editButtonUrl,

View File

@ -98,8 +98,7 @@ module Groups
params.require(:group).permit(
:max_artifacts_size,
:allow_runner_registration_token,
:jwt_ci_cd_job_token_enabled,
:job_token_policies_enabled
:jwt_ci_cd_job_token_enabled
)
end

View File

@ -46,13 +46,10 @@ module Mutations
def resolve(project_path:, target_path:, default_permissions:, job_token_policies:)
project = authorized_find!(project_path)
target = find_target_path(target_path)
policies_enabled = project.job_token_policies_enabled?
# Use default permissions if policies feature isn't enabled.
default = policies_enabled ? default_permissions : true
result = ::Ci::JobTokenScope::AddGroupOrProjectService
.new(project, current_user)
.execute(target, default_permissions: default, policies: job_token_policies)
.execute(target, default_permissions: default_permissions, policies: job_token_policies)
if result.success?
{

View File

@ -37,10 +37,6 @@ module Mutations
project = authorized_find!(project_path)
target = find_target_using_path(target_path)
unless project.job_token_policies_enabled?
raise_resource_not_available_error! 'job token policies are disabled.'
end
result = ::Ci::JobTokenScope::UpdatePoliciesService
.new(project, current_user)
.execute(target, default_permissions, job_token_policies)

View File

@ -75,13 +75,7 @@ module Types
end
end
def default_permissions
object.source_project.job_token_policies_enabled? ? object.default_permissions : true
end
def job_token_policies
return unless object.source_project.job_token_policies_enabled?
object.job_token_policies - ::Ci::JobToken::Policies::DEPRECATED_POLICIES.map(&:to_s)
end
end

View File

@ -4,8 +4,6 @@ module Ci
class Allowlist
include ::Gitlab::Utils::StrongMemoize
delegate :job_token_policies_enabled?, to: :@source_project
def initialize(source_project, direction: :inbound)
@source_project = source_project
@direction = direction
@ -35,29 +33,23 @@ module Ci
end
def add!(target_project, user:, default_permissions: true, policies: [])
job_token_policies = job_token_policies_enabled? ? policies : []
default_permissions = job_token_policies_enabled? ? default_permissions : true
Ci::JobToken::ProjectScopeLink.create!(
source_project: @source_project,
direction: @direction,
target_project: target_project,
added_by: user,
default_permissions: default_permissions,
job_token_policies: job_token_policies
job_token_policies: policies
)
end
def add_group!(target_group, user:, default_permissions: true, policies: [])
job_token_policies = job_token_policies_enabled? ? policies : []
default_permissions = job_token_policies_enabled? ? default_permissions : true
Ci::JobToken::GroupScopeLink.create!(
source_project: @source_project,
target_group: target_group,
added_by: user,
default_permissions: default_permissions,
job_token_policies: job_token_policies
job_token_policies: policies
)
end
@ -92,7 +84,6 @@ module Ci
def bulk_add_projects!(target_projects, user:, autopopulated: false, policies: [])
now = Time.zone.now
job_token_policies = job_token_policies_enabled? ? policies : []
projects = target_projects.map do |target_project|
Ci::JobToken::ProjectScopeLink.new(
@ -100,7 +91,7 @@ module Ci
target_project: target_project,
autopopulated: autopopulated,
added_by: user,
job_token_policies: job_token_policies,
job_token_policies: policies,
direction: @direction,
created_at: now
)
@ -111,7 +102,6 @@ module Ci
def bulk_add_groups!(target_groups, user:, autopopulated: false, policies: [])
now = Time.zone.now
job_token_policies = job_token_policies_enabled? ? policies : []
groups = target_groups.map do |target_group|
Ci::JobToken::GroupScopeLink.new(
@ -119,7 +109,7 @@ module Ci
target_group: target_group,
autopopulated: autopopulated,
added_by: user,
job_token_policies: job_token_policies,
job_token_policies: policies,
created_at: now
)
end

View File

@ -38,12 +38,11 @@ module Ci
end
def policies_allowed?(accessed_project, policies)
# We capture policies even if job token policies or allowlists are disabled, or the project is not allowlisted
# We capture policies even if allowlists are disabled, or the project is not allowlisted
Ci::JobToken::Authorization.capture_job_token_policies(policies) if policies.present?
return true unless accessed_project.job_token_policies_enabled?
return true unless accessed_project.ci_inbound_job_token_scope_enabled?
return false unless accessible?(accessed_project)
return false unless inbound_accessible?(accessed_project)
policies_allowed_for_accessed_project?(accessed_project, policies)
end

View File

@ -193,7 +193,7 @@ class Namespace < ApplicationRecord
:resource_access_token_notify_inherited_locked?,
:resource_access_token_notify_inherited_locked_by_ancestor?,
:resource_access_token_notify_inherited_locked_by_application_setting?
delegate :jwt_ci_cd_job_token_enabled?, :job_token_policies_enabled?
delegate :jwt_ci_cd_job_token_enabled?
with_options allow_nil: true do
delegate :prevent_sharing_groups_outside_hierarchy, :prevent_sharing_groups_outside_hierarchy=

View File

@ -14,6 +14,7 @@ class NamespaceSetting < ApplicationRecord
ignore_column :third_party_ai_features_enabled, remove_with: '16.11', remove_after: '2024-04-18'
ignore_column :code_suggestions, remove_with: '17.8', remove_after: '2024-05-16'
ignore_column :job_token_policies_enabled, remove_with: '18.5', remove_after: '2025-09-13'
cascading_attr :math_rendering_limits_enabled, :resource_access_token_notify_inherited, :web_based_commit_signing_enabled
@ -84,7 +85,6 @@ class NamespaceSetting < ApplicationRecord
math_rendering_limits_enabled
lock_math_rendering_limits_enabled
jwt_ci_cd_job_token_enabled
job_token_policies_enabled
].freeze
# matches the size set in the database constraint

View File

@ -3626,11 +3626,6 @@ class Project < ApplicationRecord
end
end
def job_token_policies_enabled?
namespace.root_ancestor.namespace_settings&.job_token_policies_enabled?
end
strong_memoize_attr :job_token_policies_enabled?
# Overridden for EE
def licensed_ai_features_available?
false

View File

@ -7,8 +7,6 @@ module Ci
include ScopeEventTracking
def execute(target, default_permissions, policies)
return error_updating(nil) unless project.job_token_policies_enabled?
validate_target_exists!(target)
validate_permissions!(target)

View File

@ -39,10 +39,6 @@ module NamespaceSettings
param_key: :enabled_git_access_protocol,
user_policy: :update_git_access_protocol
)
validate_settings_param_for_root_group(
param_key: :job_token_policies_enabled,
user_policy: :admin_group
)
handle_default_branch_name
handle_default_branch_protection unless settings_params[:default_branch_protection].blank?

View File

@ -3,7 +3,6 @@
full_path: @project.full_path,
csv_download_path: export_job_token_authorizations_namespace_project_settings_ci_cd_path(@project.namespace, @project),
enforce_allowlist: Gitlab::CurrentSettings.enforce_ci_inbound_job_token_scope_enabled?.to_s,
project_allowlist_limit: Ci::JobToken::ProjectScopeLink::PROJECT_LINK_DIRECTIONAL_LIMIT,
job_token_policies_enabled: @project.job_token_policies_enabled?.to_s
project_allowlist_limit: Ci::JobToken::ProjectScopeLink::PROJECT_LINK_DIRECTIONAL_LIMIT
}
}

View File

@ -23,14 +23,3 @@
label: s_('GroupSettings|Enable JWT format for CI/CD job tokens'),
is_checked: group.jwt_ci_cd_job_token_enabled?) do
= s_('GroupSettings|Enable JWT format for the CI_JOB_TOKEN variable. When disabled, it uses the legacy database format.')
.gl-mb-5
= gitlab_ui_form_for group, url: group_settings_ci_cd_path(group, anchor: 'js-general-pipeline-settings') do |f|
%span.gl-toggle-label.gl-shrink-0= s_('GroupSettings|Enable fine-grained permissions for CI/CD job tokens')
%span= render_if_exists 'shared/experimental_badge_tag'
= f.hidden_field :job_token_policies_enabled, class: 'js-setting-input', value: group.job_token_policies_enabled?
= render Pajamas::ToggleComponent.new(classes: 'js-setting-toggle gl-mt-3',
label: s_('GroupSettings|Enable fine-grained permissions for CI/CD job tokens'),
label_position: :hidden,
is_checked: group.job_token_policies_enabled?) do
= s_('GroupSettings|Enable fine-grained permissions for CI/CD job tokens.')
= link_to _('Learn more.'), help_page_path('ci/jobs/fine_grained_permissions.md'), target: '_blank', rel: 'noopener noreferrer'

View File

@ -11,6 +11,8 @@
#js-vue-wiki-content-app{ data: {
testid: 'wiki-page-content-app',
container_id: @wiki.container.id,
container_type: @wiki.container.is_a?(Project) ? 'project' : 'group',
page_heading: @page.human_title,
content_api: wiki_page_render_api_endpoint(@page),
show_edit_button: (can?(current_user, :create_wiki, @wiki.container) && @page.latest? && @valid_encoding).to_s,

View File

@ -1,5 +1,5 @@
---
description: Someone clicks the "View powered by GLQL" link below a GLQL view
description: Someone clicks the "Embedded view powered by GLQL" link below an embedded view
internal_events: true
status: active
action: click_glql_info_link

View File

@ -1,6 +1,6 @@
---
key_path: redis_hll_counters.count_distinct_user_id_from_click_glql_info_link
description: Count of unique users who clicked the "View powered by GLQL link"
description: Count of unique users who clicked the Embedded view powered by GLQL link"
product_group: knowledge
performance_indicator_type: []
value_type: number

View File

@ -5,4 +5,4 @@ feature_category: source_code_management
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/183124
milestone: '17.10'
queued_migration_version: 20250301123505
finalized_by: # version of the migration that finalized this BBM
finalized_by: '20250707161551'

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
class AddReplicaIndexedAt < Gitlab::Database::Migration[2.3]
milestone '18.3'
def change
add_column :p_knowledge_graph_replicas, :indexed_at, :datetime_with_timezone
add_column :p_knowledge_graph_replicas, :schema_version, :smallint, null: false, default: 0
# rubocop:disable Migration/AddIndex -- replica table is empty
add_index :p_knowledge_graph_replicas, :schema_version
# rubocop:enable Migration/AddIndex
end
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddZoektNodeKgSchema < Gitlab::Database::Migration[2.3]
milestone '18.3'
def change
add_column :zoekt_nodes, :knowledge_graph_schema_version, :smallint, null: false, default: 0
end
end

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
class FinalizeBackfillSnippetStatisticsSnippetProjectId < Gitlab::Database::Migration[2.3]
milestone '18.3'
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
def up
ensure_batched_background_migration_is_finished(
job_class_name: 'BackfillSnippetStatisticsSnippetProjectId',
table_name: :snippet_statistics,
column_name: :snippet_id,
job_arguments: [:snippet_project_id, :snippets, :project_id, :snippet_id],
finalize: true
)
end
def down; end
end

View File

@ -0,0 +1 @@
e73e22d8adb94f8f4cee96dbbf0a12aea74f36d4988ea25e6f0dfae0ea606177

View File

@ -0,0 +1 @@
295ef88c30cab25792044c391ca1414c43f145f3a351da2fc7746f9342c2e0eb

View File

@ -0,0 +1 @@
156b23135b551a8ccc419e6ebba96e92884fbb01783bcd83775710585315b93b

View File

@ -5139,6 +5139,8 @@ CREATE TABLE p_knowledge_graph_replicas (
state smallint DEFAULT 0 NOT NULL,
retries_left smallint NOT NULL,
reserved_storage_bytes bigint DEFAULT 10485760 NOT NULL,
indexed_at timestamp with time zone,
schema_version smallint DEFAULT 0 NOT NULL,
CONSTRAINT c_p_knowledge_graph_replicas_retries_status CHECK (((retries_left > 0) OR ((retries_left = 0) AND (state >= 200))))
)
PARTITION BY RANGE (namespace_id);
@ -26939,6 +26941,7 @@ CREATE TABLE zoekt_nodes (
usable_storage_bytes_locked_until timestamp with time zone,
schema_version smallint DEFAULT 0 NOT NULL,
services smallint[] DEFAULT '{0}'::smallint[] NOT NULL,
knowledge_graph_schema_version smallint DEFAULT 0 NOT NULL,
CONSTRAINT check_32f39efba3 CHECK ((char_length(search_base_url) <= 1024)),
CONSTRAINT check_38c354a3c2 CHECK ((char_length(index_base_url) <= 1024))
);
@ -37119,6 +37122,8 @@ CREATE INDEX index_p_knowledge_graph_enabled_namespaces_on_state ON ONLY p_knowl
CREATE INDEX index_p_knowledge_graph_replicas_on_namespace_id ON ONLY p_knowledge_graph_replicas USING btree (namespace_id);
CREATE INDEX index_p_knowledge_graph_replicas_on_schema_version ON ONLY p_knowledge_graph_replicas USING btree (schema_version);
CREATE INDEX index_p_knowledge_graph_replicas_on_state ON ONLY p_knowledge_graph_replicas USING btree (state);
CREATE INDEX index_p_knowledge_graph_replicas_on_zoekt_node_id ON ONLY p_knowledge_graph_replicas USING btree (zoekt_node_id);

View File

@ -633,6 +633,14 @@ To workaround the issue, you must hot-patch all Sidekiq nodes in the secondary s
1. Unless you upgrade to a version containing the fix, you would have to repeat this workaround after every GitLab upgrade.
### Error: `Error syncing repository: 13:creating repository: cloning repository: exit status 128`
You might see this error for projects that do not sync successfully.
Exit code 128 during repository creation means Git encountered a fatal error while cloning. This could be due to repository corruption, network issues, authentication problems, resource limits or because the project does not have an associated Git repository. More details about the specific cause for such failures can be found in the Gitaly logs.
When unsure where to start, run an integrity check on the source repository on the Primary site by [executing the `git fsck` command manually on the command line](../../../../administration/repository_checks.md#run-a-check-using-the-command-line).
### Error: `fetch remote: signal: terminated: context deadline exceeded` at exactly 3 hours
If Git fetch fails at exactly three hours while syncing a Git repository:

View File

@ -45908,6 +45908,9 @@ definitions:
example: 'null'
commit_email:
type: string
preferred_language:
type: string
example: en
shared_runners_minutes_limit:
type: string
extra_shared_runners_minutes_limit:
@ -65640,6 +65643,9 @@ definitions:
example: 'null'
commit_email:
type: string
preferred_language:
type: string
example: en
shared_runners_minutes_limit:
type: string
extra_shared_runners_minutes_limit:

View File

@ -635,6 +635,7 @@ Example response:
"external": false,
"private_profile": false,
"commit_email": "admin@example.com",
"preferred_language": "en",
}
```

View File

@ -35,22 +35,6 @@ These permissions are applied to the CI/CD job tokens in a specified project.
This feature is in [beta](../../policy/development_stages_support.md#beta).
## Enable fine-grained permissions
Prerequisites:
- You must have the Owner role for a group.
You must turn on fine-grained permissions at the group level. Then, each project in the group can
apply fine-grained permissions for CI/CD job tokens to grant access to individual resources.
To enable fine-grained permissions for all projects in a group:
1. On the left sidebar, select **Search or go to** and find your group.
1. On the left sidebar, select **Settings > CI/CD**.
1. Expand **General pipelines**.
1. Turn on the **Enable fine-grained permissions for CI/CD job tokens** toggle.
## Add fine-grained permissions to the job token allowlist
Prerequisites:

View File

@ -30,7 +30,7 @@ For [client-side state management](state_management.md) in Vue, depending on the
we use:
- [Apollo](https://www.apollographql.com/) (default choice for applications relying on [GraphQL](graphql.md))
- [Pinia](pinia.md) (in [pilot phase](https://gitlab.com/gitlab-org/gitlab/-/issues/479279))
- [Pinia](pinia.md)
- Stateful components.
[Vuex is deprecated](vuex.md) and you should [migrate away from it](migrating_from_vuex.md) whenever possible.

View File

@ -21,7 +21,7 @@ pure Vue and Apollo, or how to rely less on VueX.
[Pick your preferred state manager solution](state_management.md) before proceeding with the migration.
- If you plan to use Pinia (in pilot phase), [follow this guide](pinia.md#migrating-from-vuex).
- If you plan to use Pinia [follow this guide](pinia.md#migrating-from-vuex).
- If you plan to use Apollo Client for all state management, then [follow the guide below](#migrate-to-vue-managed-state-and-apollo-client).
### Migrate to Vue-managed state and Apollo Client

View File

@ -5,14 +5,6 @@ info: Any user with at least the Maintainer role can merge updates to this conte
title: Pinia
---
{{< alert type="warning" >}}
**[Pilot Phase](https://gitlab.com/gitlab-org/gitlab/-/issues/479279)**: Adopt Pinia with caution.
This is a new technology at GitLab, and we might not have all the necessary precautions and best practices in place yet.
If you're considering using Pinia, drop a message in the `#frontend` internal Slack channel for evaluation.
{{< /alert >}}
[Pinia](https://pinia.vuejs.org/) is a tool for [managing client-side state](state_management.md) for Vue applications.
Refer to the [official documentation](https://pinia.vuejs.org/core-concepts/) on how to use Pinia.

View File

@ -65,14 +65,6 @@ If you're still uncertain, prefer using Apollo before Pinia.
## Pinia
{{< alert type="warning" >}}
**[Pilot Phase](https://gitlab.com/gitlab-org/gitlab/-/issues/479279)**: Adopt Pinia with caution.
This is a new technology at GitLab and we might not have all the necessary precautions and best practices in place yet.
If you're considering using Pinia, drop a message in the `#frontend` internal Slack channel for evaluation.
{{< /alert >}}
[Pinia](https://pinia.vuejs.org/) is the client-side state management tool Vue recommends.
[Learn more about Pinia at GitLab](pinia.md).

View File

@ -34,7 +34,7 @@ GitLab Query Language (GLQL) is an attempt to create a single query language for
Use it to filter and embed content from anywhere in the platform, using familiar syntax.
Embed queries in Markdown code blocks.
A view is the rendered output of a GLQL source code block.
An embedded view is the rendered output of a GLQL source code block.
Share your feedback in the [GLQL beta feedback issue](https://gitlab.com/gitlab-org/gitlab/-/issues/509791).
@ -83,14 +83,14 @@ Values can include:
- GitLab references (like `~label` for a label, `%Backlog` for a milestone, or `@username` for a user)
- Lists containing any of the previous values (surrounded by parenthesis: `()` and delimited by commas: `,`)
## GLQL views
## Embedded views
A view is the output of a GLQL source code block in Markdown. The source includes YAML
An embedded view is the output of a GLQL source code block in Markdown. The source includes YAML
attributes that describe how to display the GLQL query results, along with the query.
### Supported areas
Views can be embedded in the following areas:
Embedded views can be displayed in the following areas:
- Group and project wikis
- Descriptions and comments of:
@ -101,7 +101,7 @@ Views can be embedded in the following areas:
### Syntax
The syntax of a view's source is a superset of YAML that consists of:
The syntax of an embedded view's source is a superset of YAML that consists of:
- The `query` parameter: Expressions joined together with a logical operator, such as `and`.
- Parameters related to the presentation layer, like `display`, `limit`, or `fields`, `title`, and `description`
@ -144,14 +144,14 @@ more optional parameters.
Supported parameters:
| Parameter | Default | Description |
| ------------- | --------------------------- | ----------- |
| `description` | None | An optional description to display below the title. |
| `display` | `table` | How to display the data. Supported options: `table`, `list`, or `orderedList`. |
| `fields` | `title` | A comma-separated list of [fields](fields.md#fields-in-glql-views) to include in the view. |
| `limit` | `100` | How many items to display on the first page. The maximum value is `100`. |
| `sort` | `updated desc` | The [field to sort the data by](fields.md#fields-to-sort-glql-views-by) followed by a sort order (`asc` or `desc`). |
| `title` | `GLQL table` or `GLQL list` | A title displayed at the top of the GLQL view. |
| Parameter | Default | Description |
| ------------- | --------------------------------------------- | ----------- |
| `description` | None | An optional description to display below the title. |
| `display` | `table` | How to display the data. Supported options: `table`, `list`, or `orderedList`. |
| `fields` | `title` | A comma-separated list of [fields](fields.md#fields-in-embedded-views) to include in the view. |
| `limit` | `100` | How many items to display on the first page. The maximum value is `100`. |
| `sort` | `updated desc` | The [field to sort the data by](fields.md#fields-to-sort-embedded-views-by) followed by a sort order (`asc` or `desc`). |
| `title` | `Embedded table view` or `Embedded list view` | A title displayed at the top of the embedded view. |
For example, to display the first five issues assigned to the current user in the `gitlab-org/gitlab`
project as a list, sorted by due date (earliest first) and displaying the `title`, `health`, and `due` fields:
@ -174,7 +174,7 @@ query: project = "gitlab-org/gitlab" AND assignee = currentUser() AND state = op
{{< /history >}}
GLQL views display the first page of results by default.
Embedded views display the first page of results by default.
The `limit` parameter controls the number of items shown.
To load the next page, in the last row, select **Load more**.
@ -182,7 +182,7 @@ To load the next page, in the last row, select **Load more**.
#### Field functions
To create dynamically generated columns, use functions in the `fields` parameters in views.
For a full list, see [Functions in GLQL views](functions.md#functions-in-glql-views).
For a full list, see [Functions in embedded views](functions.md#functions-in-embedded-views).
#### Custom field aliases

View File

@ -33,10 +33,10 @@ This feature is available for testing, but not ready for production use.
With GitLab Query Language (GLQL), fields are used to:
- Filter the results returned from a [GLQL query](_index.md#query-syntax).
- Control the details displayed in a [GLQL view](_index.md#presentation-syntax).
- Sort the results displayed in a GLQL view.
- Control the details displayed in an [embedded view](_index.md#presentation-syntax).
- Sort the results displayed in an embedded view.
You use fields in three GLQL components:
You use fields in three embedded view parameters:
- **`query`** - Set conditions to determine which items to retrieve
- **`fields`** - Specify which columns and details appear in your view
@ -46,7 +46,7 @@ The following sections describe the available fields for each component.
## Fields inside query
In a GLQL view, the `query` parameter can be used to include one or more expressions of the
In an embedded view, the `query` parameter can be used to include one or more expressions of the
format `<field> <operator> <value>`. Multiple expressions are joined with `and`,
for example, `group = "gitlab-org" and author = currentUser()`.
@ -522,7 +522,7 @@ The table below provides an overview of all available query fields and their spe
- Only one group can be queried at a time.
- The `group` cannot be used together with the `project` field.
- If omitted when using inside a GLQL view in a group object (like an epic), `group` is assumed to
- If omitted when using inside an embedded view in a group object (like an epic), `group` is assumed to
be the current group.
- Using the `group` field queries all objects in that group, all its subgroups, and child projects.
- By default, issues or merge requests are searched in all descendant projects across all subgroups.
@ -899,7 +899,7 @@ The table below provides an overview of all available query fields and their spe
- Only one project can be queried at a time.
- The `project` field cannot be used together with the `group` field.
- If omitted when using inside a GLQL view, `project` is assumed to be the current project.
- If omitted when using inside an embedded view, `project` is assumed to be the current project.
**Examples**:
@ -1106,7 +1106,7 @@ The table below provides an overview of all available query fields and their spe
**Additional details**:
- If omitted when used inside a GLQL view, the default `type` is `Issue`.
- If omitted when used inside an embedded view, the default `type` is `Issue`.
- `type = Epic` queries can only be used together with the [group](#group) field.
**Examples**:
@ -1211,7 +1211,7 @@ The table below provides an overview of all available query fields and their spe
weight != 5
```
## Fields in GLQL views
## Fields in embedded views
{{< history >}}
@ -1223,8 +1223,8 @@ The table below provides an overview of all available query fields and their spe
{{< /history >}}
In GLQL views, the `fields` view component is a comma-separated list of fields, or field functions that
can be used to indicate what fields to include in the rendered GLQL view,
In embedded views, the `fields` view parameter is a comma-separated list of fields, or field functions that
can be used to indicate what fields to include in the rendered embedded view,
for example, `fields: title, state, health, epic, milestone, weight, updated`.
| Field | Name or alias | Objects supported | Description |
@ -1260,7 +1260,7 @@ for example, `fields: title, state, health, epic, milestone, weight, updated`.
| Updated at | `updated`, `updatedAt` | Issues, epics, merge requests | Display time since the object was last updated |
| Weight | `weight` | Issues | Display the weight of the object. Available in the Premium and Ultimate tiers. |
## Fields to sort GLQL views by
## Fields to sort embedded views by
{{< history >}}
@ -1268,7 +1268,7 @@ for example, `fields: title, state, health, epic, milestone, weight, updated`.
{{< /history >}}
In GLQL views, the `sort` view parameter is a field name followed by
In embedded views, the `sort` view parameter is a field name followed by
a sort order (`asc` or `desc`) that sorts the results by the specified
field and order.
@ -1357,7 +1357,7 @@ field and order.
You might encounter these error messages:
```plaintext
GLQL view timed out. Add more filters to reduce the number of results.
Embedded view timed out. Add more filters to reduce the number of results.
```
```plaintext

View File

@ -93,9 +93,9 @@ by filtering by a current user or a date.
type = MergeRequest and merged = today()
```
## Functions in GLQL views
## Functions in embedded views
To derive a new column from an existing field of a [GLQL view](_index.md#glql-views), include
To derive a new column from an existing field of an [embedded view](_index.md#embedded-views), include
functions in the `fields` parameter.
### Extract labels into a new column
@ -140,7 +140,7 @@ in the regular `labels` column, if you choose to display that column as well.
labels("*end")
```
To include the `labels` function in a GLQL view:
To include the `labels` function in an embedded view:
````markdown
```glql

View File

@ -21,6 +21,7 @@ module API
expose :private_profile, documentation: { type: 'boolean', example: :null }
expose :commit_email_or_default, as: :commit_email
expose :preferred_language, documentation: { type: 'string', example: 'en' }
end
end
end

View File

@ -7,6 +7,9 @@ module API
include Helpers::CustomAttributes
allow_access_with_scope :read_user, if: ->(request) { request.get? || request.head? }
allow_access_with_scope :ai_workflows, if: ->(request) do
request.head? || request_current_user?(request)
end
feature_category :user_profile,
%w[
@ -21,6 +24,10 @@ module API
/users/:id/custom_attributes/:key
]
def self.request_current_user?(request)
request.get? && request.path.match?(%r{/api/v\d+/user$})
end
resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do
include CustomAttributesEndpoints

View File

@ -476,7 +476,8 @@ module Gitlab
global_options: parse_global_options!(options),
disable_walk: true, # This option is deprecated. The 'walk' implementation is being removed.
trailers: options[:trailers],
include_referenced_by: options[:include_referenced_by]
include_referenced_by: options[:include_referenced_by],
message_regex: options[:message_regex]
)
request.after = GitalyClient.timestamp(options[:after]) if options[:after]
request.before = GitalyClient.timestamp(options[:before]) if options[:before]

View File

@ -1146,7 +1146,7 @@ msgstr ""
msgid "%{linkStart}Advanced search%{linkEnd} is enabled."
msgstr ""
msgid "%{linkStart}View%{linkEnd} powered by GLQL"
msgid "%{linkStart}Embedded view%{linkEnd} powered by GLQL"
msgstr ""
msgid "%{link_start}Communication Preference Center%{link_end}."
@ -7201,7 +7201,7 @@ msgstr ""
msgid "An error occurred restoring this group. Please refresh the page to try again."
msgstr ""
msgid "An error occurred when trying to display this GLQL view:"
msgid "An error occurred when trying to display this embedded view:"
msgstr ""
msgid "An error occurred when updating the title"
@ -7477,6 +7477,9 @@ msgstr ""
msgid "An error occurred while searching for labels, please try again."
msgstr ""
msgid "An error occurred while subscribing to this page. Please try again later."
msgstr ""
msgid "An error occurred while triggering the job."
msgstr ""
@ -7501,6 +7504,9 @@ msgstr ""
msgid "An error occurred while trying to update the registry: '%{error_message}'."
msgstr ""
msgid "An error occurred while unsubscribing from this page. Please try again later."
msgstr ""
msgid "An error occurred while updating approvers"
msgstr ""
@ -24658,6 +24664,24 @@ msgstr ""
msgid "Embed"
msgstr ""
msgid "Embedded list view"
msgstr ""
msgid "Embedded table view"
msgstr ""
msgid "Embedded view"
msgstr ""
msgid "Embedded view options"
msgstr ""
msgid "Embedded view timed out. Add more filters to reduce the number of results."
msgstr ""
msgid "Embedded view timed out. Try again later."
msgstr ""
msgid "Emoji"
msgstr ""
@ -27883,21 +27907,6 @@ msgstr ""
msgid "GCP region configured"
msgstr ""
msgid "GLQL list"
msgstr ""
msgid "GLQL table"
msgstr ""
msgid "GLQL view options"
msgstr ""
msgid "GLQL view timed out. Add more filters to reduce the number of results."
msgstr ""
msgid "GLQL view timed out. Try again later."
msgstr ""
msgid "GPG Key ID:"
msgstr ""
@ -28916,9 +28925,6 @@ msgstr ""
msgid "GitLab Premium"
msgstr ""
msgid "GitLab Query Language (GLQL) view"
msgstr ""
msgid "GitLab Security"
msgstr ""
@ -31004,12 +31010,6 @@ msgstr ""
msgid "GroupSettings|Enable extension marketplace"
msgstr ""
msgid "GroupSettings|Enable fine-grained permissions for CI/CD job tokens"
msgstr ""
msgid "GroupSettings|Enable fine-grained permissions for CI/CD job tokens."
msgstr ""
msgid "GroupSettings|Enable overview background aggregation for Value Streams Dashboard"
msgstr ""
@ -37416,7 +37416,7 @@ msgstr ""
msgid "Load %{count} more"
msgstr ""
msgid "Load GLQL view"
msgid "Load embedded view"
msgstr ""
msgid "Load more"
@ -42255,6 +42255,12 @@ msgstr ""
msgid "Notifications"
msgstr ""
msgid "Notifications Off"
msgstr ""
msgid "Notifications On"
msgstr ""
msgid "Notifications for %{project_or_group} are rate-limited due to the high volume of notifications sent."
msgstr ""
@ -42270,9 +42276,15 @@ msgstr ""
msgid "Notifications temporarily disabled"
msgstr ""
msgid "Notifications turned off"
msgstr ""
msgid "Notifications turned off."
msgstr ""
msgid "Notifications turned on"
msgstr ""
msgid "Notifications turned on."
msgstr ""
@ -43498,7 +43510,7 @@ msgstr ""
msgid "Online license"
msgstr ""
msgid "Only %{n} GLQL views can be automatically displayed on a page. Click the button below to manually display this block."
msgid "Only %{n} embedded views can be automatically displayed on a page. Click the button below to manually display this block."
msgstr ""
msgid "Only %{workspaceType} members with %{permissions} can view or be notified about this %{issuableType}."
@ -69669,9 +69681,6 @@ msgstr ""
msgid "Vulnerability|Scanner:"
msgstr ""
msgid "Vulnerability|Scanner: %{scannerName}"
msgstr ""
msgid "Vulnerability|Search or filter vulnerabilities..."
msgstr ""
@ -69735,12 +69744,18 @@ msgstr ""
msgid "Vulnerability|URL:"
msgstr ""
msgid "Vulnerability|Unknown"
msgstr ""
msgid "Vulnerability|Unmodified response:"
msgstr ""
msgid "Vulnerability|Use AI to understand a vulnerability and suggest a fix"
msgstr ""
msgid "Vulnerability|Vendor: %{vendorName}"
msgstr ""
msgid "Vulnerability|View code flow"
msgstr ""

View File

@ -39,7 +39,7 @@ module RuboCop
private
def container_block_for(current_node)
current_node = current_node.parent until current_node.type == :block && current_node.method?(:strong_memoize)
current_node = current_node.parent until current_node.block_type? && current_node.method?(:strong_memoize)
current_node
end

View File

@ -12,7 +12,7 @@ module RuboCop
return unless node.method?(OBSERVED_METHOD)
node.arguments.each do |argument|
if argument.type == :kwarg || argument.type == :kwoptarg
if argument.type?(:kwarg, :kwoptarg)
add_offense(node)
end
end

View File

@ -45,7 +45,7 @@ module RuboCop
top_block = nil
while current_node && current_node.type != :def
top_block = current_node if current_node.type == :block
top_block = current_node if current_node.block_type?
current_node = current_node.parent
end
@ -56,7 +56,7 @@ module RuboCop
blocks = []
until node == current_node || def?(current_node)
blocks << current_node if current_node.type == :block
blocks << current_node if current_node.block_type?
current_node = current_node.parent
end
@ -65,8 +65,8 @@ module RuboCop
end
def def?(node)
node.type == :def || node.type == :defs ||
(node.type == :block && DEF_METHODS.include?(node.method_name))
node.type?(:def, :defs) ||
(node.block_type? && DEF_METHODS.include?(node.method_name))
end
def allowlisted?(block_node)

View File

@ -134,7 +134,7 @@ module RuboCop
# Returns true if `description` node is a `:str` (as opposed to a `#copy_field_description` call)
def string?(description)
description.type == :str
description.str_type?
end
# Returns a `Parser::Source::Range` that ends just before the final `String` delimiter.

View File

@ -74,7 +74,7 @@ module RuboCop
migration_method = node.children[1]
if migration_method == :text
modifier.type == :lvar
modifier.lvar_type?
elsif ADD_COLUMN_METHODS.include?(migration_method)
modifier.nil? && text_column?(node.children[4])
end
@ -160,7 +160,7 @@ module RuboCop
end
def table_name_or_const_name(node)
node.type == :const ? node.const_name : node.value
node.const_type? ? node.const_name : node.value
end
def encrypted_attribute_name?(attribute_name)

View File

@ -59,7 +59,7 @@ module RuboCop
def index_missing?(node)
opts = node.children.last
return true if opts && opts.type == :hash
return true if opts && opts.hash_type?
index_present = false

View File

@ -46,7 +46,7 @@ module RuboCop
def module_extends_activesupport_concern?(node)
while node = node.parent
break if node.type == :module
break if node.module_type?
end
return false unless node

View File

@ -29,7 +29,7 @@ module RuboCop
return unless in_migration?(node)
node.each_descendant do |descendant|
next unless descendant.type == :sym
next unless descendant.sym_type?
last_argument = descendant.children.last

View File

@ -60,7 +60,7 @@ module RuboCop
end
def valid_table_node?(table_name)
table_name && table_name.type == :sym
table_name && table_name.sym_type?
end
end
end

View File

@ -41,14 +41,14 @@ module RuboCop
migration_method = node.children[1]
if migration_method == :string
modifier.type == :lvar
modifier.lvar_type?
elsif ADD_COLUMN_METHODS.include?(migration_method)
modifier.nil? && string_column?(node.children[4])
end
end
def string_column?(column_type)
column_type.type == :sym && column_type.value == :string
column_type.sym_type? && column_type.value == :string
end
end
end

View File

@ -52,7 +52,7 @@ module RuboCop
def remove_index_offense?(send_node)
match_remove_index(send_node) do |column_or_options_node|
break true unless column_or_options_node.type == :hash
break true unless column_or_options_node.hash_type?
column_or_options_node.children.none? { |pair| name_option?(pair) }
end

View File

@ -62,7 +62,7 @@ module RuboCop
def container_module_of(node)
while node = node.parent
break if node.type == :module
break if node.module_type?
end
node

View File

@ -17,7 +17,7 @@ module RuboCop
namespace_expr, project_expr = arguments(node)
return unless namespace_expr && project_expr
return unless namespace_expr.type == :send
return unless namespace_expr.send_type?
return unless method_name(namespace_expr) == :namespace
return unless receiver(namespace_expr) == project_expr

View File

@ -140,7 +140,7 @@ module RuboCop
def extract_numeric_code(node)
arg_node = argument(node)
return unless arg_node&.type == :int
return unless arg_node&.int_type?
arg_node.children[0]
end

View File

@ -88,7 +88,7 @@ module RuboCop
private
def memoized?(node)
node.type == :or_asgn
node.or_asgn_type?
end
def dynamic?(node, memoized)
@ -109,11 +109,11 @@ module RuboCop
end
def instance_method_definition?(node)
node.type == :def
node.def_type?
end
def unmemoized_class_method_definition?(node, memoized)
node.type == :defs && !memoized
node.defs_type? && !memoized
end
end
end

View File

@ -51,7 +51,7 @@ module RuboCop
batch_set_to_false = false
options.each_pair do |key, value|
next unless value.boolean_type? && value.falsey_literal?
next unless key.type == :sym && key.value == :batch
next unless key.sym_type? && key.value == :batch
batch_set_to_false = true
break

View File

@ -175,15 +175,6 @@ RSpec.describe Groups::Settings::CiCdController, feature_category: :continuous_i
.from(false).to(true)
end
end
context 'when updating `job_token_policies_enabled`' do
let(:params) { { group_id: group, group: { job_token_policies_enabled: true } } }
it 'can update `job_token_policies_enabled`' do
expect { perform_request }.to change { group.reload.namespace_settings.job_token_policies_enabled }
.from(false).to(true)
end
end
end
context 'when user is a group maintainer' do
@ -208,16 +199,6 @@ RSpec.describe Groups::Settings::CiCdController, feature_category: :continuous_i
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when updating `job_token_policies_enabled`' do
let(:params) { { group_id: group, group: { job_token_policies_enabled: true } } }
it 'cannot update `job_token_policies_enabled`' do
expect { perform_request }.not_to change { group.reload.namespace_settings.job_token_policies_enabled }
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'when user is an admin' do
@ -246,15 +227,6 @@ RSpec.describe Groups::Settings::CiCdController, feature_category: :continuous_i
.from(false).to(true)
end
end
context 'when updating `job_token_policies_enabled`' do
let(:params) { { group_id: group, group: { job_token_policies_enabled: true } } }
it 'can update `job_token_policies_enabled`' do
expect { perform_request }.to change { group.reload.namespace_settings.job_token_policies_enabled }
.from(false).to(true)
end
end
end
context 'when admin mode is enabled', :enable_admin_mode do

View File

@ -38,19 +38,19 @@ describe('content_editor/components/toolbar_more_dropdown', () => {
});
describe.each`
name | contentType | command | params
${'Alert'} | ${'alert'} | ${'insertAlert'} | ${[]}
${'Code block'} | ${'codeBlock'} | ${'setNode'} | ${['codeBlock']}
${'Details block'} | ${'details'} | ${'toggleList'} | ${['details', 'detailsContent']}
${'Bullet list'} | ${'bulletList'} | ${'toggleList'} | ${['bulletList', 'listItem']}
${'Ordered list'} | ${'orderedList'} | ${'toggleList'} | ${['orderedList', 'listItem']}
${'Task list'} | ${'taskList'} | ${'toggleList'} | ${['taskList', 'taskItem']}
${'Mermaid diagram'} | ${'diagram'} | ${'setNode'} | ${['diagram', { language: 'mermaid' }]}
${'PlantUML diagram'} | ${'diagram'} | ${'setNode'} | ${['diagram', { language: 'plantuml' }]}
${'Table of contents'} | ${'tableOfContents'} | ${'insertTableOfContents'} | ${[]}
${'Horizontal rule'} | ${'horizontalRule'} | ${'setHorizontalRule'} | ${[]}
${'Create or edit diagram'} | ${'drawioDiagram'} | ${'createOrEditDiagram'} | ${[]}
${'GitLab Query Language (GLQL) view Beta'} | ${'glqlView'} | ${'insertGLQLView'} | ${[]}
name | contentType | command | params
${'Alert'} | ${'alert'} | ${'insertAlert'} | ${[]}
${'Code block'} | ${'codeBlock'} | ${'setNode'} | ${['codeBlock']}
${'Details block'} | ${'details'} | ${'toggleList'} | ${['details', 'detailsContent']}
${'Bullet list'} | ${'bulletList'} | ${'toggleList'} | ${['bulletList', 'listItem']}
${'Ordered list'} | ${'orderedList'} | ${'toggleList'} | ${['orderedList', 'listItem']}
${'Task list'} | ${'taskList'} | ${'toggleList'} | ${['taskList', 'taskItem']}
${'Mermaid diagram'} | ${'diagram'} | ${'setNode'} | ${['diagram', { language: 'mermaid' }]}
${'PlantUML diagram'} | ${'diagram'} | ${'setNode'} | ${['diagram', { language: 'plantuml' }]}
${'Table of contents'} | ${'tableOfContents'} | ${'insertTableOfContents'} | ${[]}
${'Horizontal rule'} | ${'horizontalRule'} | ${'setHorizontalRule'} | ${[]}
${'Create or edit diagram'} | ${'drawioDiagram'} | ${'createOrEditDiagram'} | ${[]}
${'Embedded view Beta'} | ${'glqlView'} | ${'insertGLQLView'} | ${[]}
`('when option $name is clicked', ({ name, command, contentType, params }) => {
let commands;
let btn;
@ -92,10 +92,10 @@ describe('content_editor/components/toolbar_more_dropdown', () => {
});
});
it('shows a beta badge for the GLQL view option', () => {
it('shows a beta badge for the embedded view option', () => {
buildWrapper();
const btn = wrapper.findByRole('button', { name: 'GitLab Query Language (GLQL) view Beta' });
const btn = wrapper.findByRole('button', { name: 'Embedded view Beta' });
const badge = wrapper.findComponent(GlBadge);
expect(btn.exists()).toBe(true);

View File

@ -98,12 +98,12 @@ describe('content_editor/extensions/code_block_highlight', () => {
});
});
describe('when inserting a GLQL view', () => {
describe('when inserting an embedded view', () => {
beforeEach(() => {
tiptapEditor.commands.insertGLQLView();
});
it('inserts a GLQL view', () => {
it('inserts an embedded view', () => {
expect(tiptapEditor.getJSON()).toEqual(
doc(
codeBlock(

View File

@ -400,6 +400,8 @@ describe('DiffFile', () => {
it('should show the collapsed file warning with expansion button', () => {
createComponent();
expect(wrapper.findByTestId('diff-file-warning').exists()).toBe(true);
expect(findDiffContentArea(wrapper).html()).toContain(
'Files with large changes are collapsed by default.',
);
@ -410,6 +412,12 @@ describe('DiffFile', () => {
createComponent();
expect(wrapper.classes('has-body')).toBe(true);
});
it('should hide the collapsed file warning when file has been reviewed', () => {
createComponent({ props: { reviewed: true } });
expect(wrapper.findByTestId('diff-file-warning').exists()).toBe(false);
});
});
describe('automatically collapsed generated file', () => {

View File

@ -39,8 +39,8 @@ describe('GlqlActions', () => {
});
it('sets correct tooltip and text for dropdown', () => {
expect(findDropdown().attributes('title')).toBe('GLQL view options');
expect(findDropdown().props('toggleText')).toBe('GLQL view options');
expect(findDropdown().attributes('title')).toBe('Embedded view options');
expect(findDropdown().props('toggleText')).toBe('Embedded view options');
});
it.each`

View File

@ -51,8 +51,8 @@ describe('GlqlFacade', () => {
expect(wrapper.find('code').text()).toBe('assignee = "foo"');
});
it('renders the Load GLQL view button', () => {
expect(wrapper.findComponent(GlButton).text()).toEqual('Load GLQL view');
it('renders the Load embedded view button', () => {
expect(wrapper.findComponent(GlButton).text()).toEqual('Load embedded view');
});
});
@ -85,13 +85,13 @@ describe('GlqlFacade', () => {
it('renders actions', () => {
expect(wrapper.findComponent(GlqlActions).props()).toEqual({
modalTitle: 'GLQL list',
modalTitle: 'Embedded list view',
showCopyContents: true,
});
});
it('renders a footer text', () => {
expect(wrapper.text()).toContain('View powered by GLQL');
expect(wrapper.text()).toContain('Embedded view powered by GLQL');
});
it('shows a "No data" message if the list of items provided is empty', async () => {
@ -141,7 +141,7 @@ describe('GlqlFacade', () => {
expect(alert.exists()).toBe(true);
expect(alert.props('variant')).toBe('warning');
expect(alert.text()).toContain(
'GLQL view timed out. Add more filters to reduce the number of results.',
'Embedded view timed out. Add more filters to reduce the number of results.',
);
expect(alert.props('primaryButtonText')).toBe('Retry');
});
@ -172,7 +172,7 @@ describe('GlqlFacade', () => {
const alert = wrapper.findComponent(GlAlert);
expect(alert.exists()).toBe(true);
expect(alert.props('variant')).toBe('danger');
expect(alert.text()).toContain('GLQL view timed out. Try again later.');
expect(alert.text()).toContain('Embedded view timed out. Try again later.');
});
});
@ -225,12 +225,12 @@ describe('GlqlFacade', () => {
await triggerIntersectionObserver();
});
it('displays limit error alert after exceeding GLQL block limit', () => {
it('displays limit error alert after exceeding embedded view block limit', () => {
const alert = wrapper.findComponent(GlAlert);
expect(alert.exists()).toBe(true);
expect(alert.props('variant')).toBe('warning');
expect(alert.text()).toContain(
'Only 20 GLQL views can be automatically displayed on a page. Click the button below to manually display this block.',
'Only 20 embedded views can be automatically displayed on a page. Click the button below to manually display this block.',
);
expect(alert.props('primaryButtonText')).toBe('Display block');
});

View File

@ -23,7 +23,6 @@ import inboundRemoveGroupCIJobTokenScopeMutation from '~/token_access/graphql/mu
import inboundRemoveProjectCIJobTokenScopeMutation from '~/token_access/graphql/mutations/inbound_remove_project_ci_job_token_scope.mutation.graphql';
import inboundUpdateCIJobTokenScopeMutation from '~/token_access/graphql/mutations/inbound_update_ci_job_token_scope.mutation.graphql';
import inboundGetCIJobTokenScopeQuery from '~/token_access/graphql/queries/inbound_get_ci_job_token_scope.query.graphql';
import inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery from '~/token_access/graphql/queries/inbound_get_groups_and_projects_with_ci_job_token_scope.query.graphql';
import getAuthLogCountQuery from '~/token_access/graphql/queries/get_auth_log_count.query.graphql';
import getCiJobTokenScopeAllowlistQuery from '~/token_access/graphql/queries/get_ci_job_token_scope_allowlist.query.graphql';
import removeAutopopulatedEntriesMutation from '~/token_access/graphql/mutations/remove_autopopulated_entries.mutation.graphql';
@ -35,7 +34,6 @@ import { stubComponent } from 'helpers/stub_component';
import {
inboundJobTokenScopeEnabledResponse,
inboundJobTokenScopeDisabledResponse,
inboundGroupsAndProjectsWithScopeResponse,
inboundRemoveNamespaceSuccess,
inboundUpdateScopeSuccessResponse,
mockAuthLogsCountResponse,
@ -58,7 +56,10 @@ describe('TokenAccess component', () => {
const authLogCountResponseHandler = jest.fn().mockResolvedValue(mockAuthLogsCountResponse(4));
const ciJobTokenScopeAllowlistResponseHandler = jest
.fn()
.mockResolvedValue(mockCiJobTokenScopeAllowlistResponse);
.mockResolvedValue(mockCiJobTokenScopeAllowlistResponse());
const ciJobTokenScopeAllowlistWithoutAutopopulatedEntriesResponseHandler = jest
.fn()
.mockResolvedValue(mockCiJobTokenScopeAllowlistResponse(false));
const authLogZeroCountResponseHandler = jest.fn().mockResolvedValue(mockAuthLogsCountResponse(0));
const autopopulateAllowlistResponseHandler = jest
.fn()
@ -72,12 +73,6 @@ describe('TokenAccess component', () => {
const inboundJobTokenScopeDisabledResponseHandler = jest
.fn()
.mockResolvedValue(inboundJobTokenScopeDisabledResponse);
const inboundGroupsAndProjectsWithScopeResponseHandler = jest
.fn()
.mockResolvedValue(inboundGroupsAndProjectsWithScopeResponse(true));
const inboundGroupsAndProjectsWithoutAutopopulatedEntriesResponseHandler = jest
.fn()
.mockResolvedValue(inboundGroupsAndProjectsWithScopeResponse(false));
const inboundRemoveGroupSuccessHandler = jest
.fn()
.mockResolvedValue(inboundRemoveNamespaceSuccess);
@ -116,20 +111,13 @@ describe('TokenAccess component', () => {
const createComponent = (
requestHandlers,
{
isJobTokenPoliciesEnabled = false,
enforceAllowlist = false,
projectAllowlistLimit = 2,
stubs = {},
isLoading = false,
} = {},
{ enforceAllowlist = false, projectAllowlistLimit = 2, stubs = {}, isLoading = false } = {},
) => {
wrapper = shallowMountExtended(InboundTokenAccess, {
provide: {
fullPath: projectPath,
enforceAllowlist,
projectAllowlistLimit,
isJobTokenPoliciesEnabled,
},
apolloProvider: createMockApollo(requestHandlers),
mocks: {
@ -156,10 +144,7 @@ describe('TokenAccess component', () => {
createComponent(
[
[inboundGetCIJobTokenScopeQuery, inboundJobTokenScopeEnabledResponseHandler],
[
inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
inboundGroupsAndProjectsWithScopeResponseHandler,
],
[getCiJobTokenScopeAllowlistQuery, ciJobTokenScopeAllowlistResponseHandler],
],
{ isLoading: true },
);
@ -178,10 +163,7 @@ describe('TokenAccess component', () => {
await createComponent(
[
[inboundGetCIJobTokenScopeQuery, inboundJobTokenScopeEnabledResponseHandler],
[
inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
inboundGroupsAndProjectsWithScopeResponseHandler,
],
[getCiJobTokenScopeAllowlistQuery, ciJobTokenScopeAllowlistResponseHandler],
[getAuthLogCountQuery, authLogCountResponseHandler],
],
{ projectAllowlistLimit },
@ -212,10 +194,7 @@ describe('TokenAccess component', () => {
it('handles fetches auth log count error correctly', async () => {
await createComponent([
[inboundGetCIJobTokenScopeQuery, inboundJobTokenScopeEnabledResponseHandler],
[
inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
inboundGroupsAndProjectsWithScopeResponseHandler,
],
[getCiJobTokenScopeAllowlistQuery, ciJobTokenScopeAllowlistResponseHandler],
[getAuthLogCountQuery, failureHandler],
]);
@ -233,22 +212,17 @@ describe('TokenAccess component', () => {
createComponent([
[inboundGetCIJobTokenScopeQuery, inboundJobTokenScopeEnabledResponseHandler],
[
inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
inboundGroupsAndProjectsWithScopeResponseHandler,
],
[getCiJobTokenScopeAllowlistQuery, ciJobTokenScopeAllowlistResponseHandler],
]);
expect(inboundJobTokenScopeEnabledResponseHandler).toHaveBeenCalledWith(expectedVariables);
expect(inboundGroupsAndProjectsWithScopeResponseHandler).toHaveBeenCalledWith(
expectedVariables,
);
expect(ciJobTokenScopeAllowlistResponseHandler).toHaveBeenCalledWith(expectedVariables);
});
it('handles fetch groups and projects error correctly', async () => {
await createComponent([
[inboundGetCIJobTokenScopeQuery, inboundJobTokenScopeEnabledResponseHandler],
[inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery, failureHandler],
[getCiJobTokenScopeAllowlistQuery, failureHandler],
]);
expect(createAlert).toHaveBeenCalledWith({
@ -259,10 +233,7 @@ describe('TokenAccess component', () => {
it('handles fetch scope error correctly', async () => {
await createComponent([
[inboundGetCIJobTokenScopeQuery, failureHandler],
[
inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
inboundGroupsAndProjectsWithScopeResponseHandler,
],
[getCiJobTokenScopeAllowlistQuery, ciJobTokenScopeAllowlistResponseHandler],
]);
expect(createAlert).toHaveBeenCalledWith({
@ -271,10 +242,9 @@ describe('TokenAccess component', () => {
});
it('adds the current project at the top of the list', async () => {
await createComponent(
[[getCiJobTokenScopeAllowlistQuery, ciJobTokenScopeAllowlistResponseHandler]],
{ isJobTokenPoliciesEnabled: true },
);
await createComponent([
[getCiJobTokenScopeAllowlistQuery, ciJobTokenScopeAllowlistResponseHandler],
]);
expect(findTokenAccessTable().props('items')[0].fullPath).toBe('root/my-repo');
expect(findTokenAccessTable().props('items')[1].fullPath).toBe('abc/123');
@ -287,10 +257,7 @@ describe('TokenAccess component', () => {
it('is on and the alert is hidden', async () => {
await createComponent([
[inboundGetCIJobTokenScopeQuery, inboundJobTokenScopeEnabledResponseHandler],
[
inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
inboundGroupsAndProjectsWithScopeResponseHandler,
],
[getCiJobTokenScopeAllowlistQuery, ciJobTokenScopeAllowlistResponseHandler],
]);
expect(findRadioGroup().attributes('checked')).toBe('true');
@ -300,10 +267,7 @@ describe('TokenAccess component', () => {
it('is off and the alert is visible', async () => {
await createComponent([
[inboundGetCIJobTokenScopeQuery, inboundJobTokenScopeDisabledResponseHandler],
[
inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
inboundGroupsAndProjectsWithScopeResponseHandler,
],
[getCiJobTokenScopeAllowlistQuery, ciJobTokenScopeAllowlistResponseHandler],
]);
expect(findRadioGroup().attributes('checked')).toBeUndefined();
@ -314,10 +278,7 @@ describe('TokenAccess component', () => {
it('uses the correct "options" prop', async () => {
await createComponent([
[inboundGetCIJobTokenScopeQuery, inboundJobTokenScopeDisabledResponseHandler],
[
inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
inboundGroupsAndProjectsWithScopeResponseHandler,
],
[getCiJobTokenScopeAllowlistQuery, ciJobTokenScopeAllowlistResponseHandler],
]);
const expectedOptions = [
@ -434,12 +395,7 @@ describe('TokenAccess component', () => {
describe('namespace form', () => {
beforeEach(() =>
createComponent(
[
[
inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
inboundGroupsAndProjectsWithScopeResponseHandler,
],
],
[[getCiJobTokenScopeAllowlistQuery, ciJobTokenScopeAllowlistResponseHandler]],
{ stubs: { CrudComponent } },
),
);
@ -474,7 +430,7 @@ describe('TokenAccess component', () => {
findNamespaceForm().vm.$emit('saved');
expect(inboundGroupsAndProjectsWithScopeResponseHandler).toHaveBeenCalledTimes(2);
expect(ciJobTokenScopeAllowlistResponseHandler).toHaveBeenCalledTimes(2);
});
});
});
@ -484,10 +440,7 @@ describe('TokenAccess component', () => {
createComponent(
[
[inboundGetCIJobTokenScopeQuery, inboundJobTokenScopeEnabledResponseHandler],
[
inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
inboundGroupsAndProjectsWithScopeResponseHandler,
],
[getCiJobTokenScopeAllowlistQuery, ciJobTokenScopeAllowlistResponseHandler],
[autopopulateAllowlistMutation, autopopulateAllowlistResponseHandler],
[removeAutopopulatedEntriesMutation, removeAutopopulatedEntriesMutationHandler],
[getAuthLogCountQuery, authLogCountResponseHandler],
@ -551,7 +504,7 @@ describe('TokenAccess component', () => {
it('calls the autopopulate allowlist mutation and refetches allowlist and job token setting', async () => {
expect(autopopulateAllowlistResponseHandler).toHaveBeenCalledTimes(0);
expect(inboundGroupsAndProjectsWithScopeResponseHandler).toHaveBeenCalledTimes(1);
expect(ciJobTokenScopeAllowlistResponseHandler).toHaveBeenCalledTimes(1);
expect(inboundJobTokenScopeEnabledResponseHandler).toHaveBeenCalledTimes(1);
findFormSelector().vm.$emit('select', JOB_TOKEN_FORM_AUTOPOPULATE_AUTH_LOG);
@ -560,18 +513,15 @@ describe('TokenAccess component', () => {
await nextTick();
expect(autopopulateAllowlistResponseHandler).toHaveBeenCalledTimes(1);
expect(inboundGroupsAndProjectsWithScopeResponseHandler).toHaveBeenCalledTimes(2);
expect(inboundJobTokenScopeEnabledResponseHandler).toHaveBeenCalledTimes(2);
expect(ciJobTokenScopeAllowlistResponseHandler).toHaveBeenCalledTimes(2);
expect(inboundJobTokenScopeEnabledResponseHandler).toHaveBeenCalledTimes(3);
});
it('shows error alert when mutation returns an error', async () => {
createComponent(
[
[inboundGetCIJobTokenScopeQuery, inboundJobTokenScopeEnabledResponseHandler],
[
inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
inboundGroupsAndProjectsWithScopeResponseHandler,
],
[getCiJobTokenScopeAllowlistQuery, ciJobTokenScopeAllowlistResponseHandler],
[autopopulateAllowlistMutation, autopopulateAllowlistResponseErrorHandler],
[getAuthLogCountQuery, authLogCountResponseHandler],
],
@ -596,10 +546,7 @@ describe('TokenAccess component', () => {
createComponent(
[
[inboundGetCIJobTokenScopeQuery, inboundJobTokenScopeEnabledResponseHandler],
[
inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
inboundGroupsAndProjectsWithScopeResponseHandler,
],
[getCiJobTokenScopeAllowlistQuery, ciJobTokenScopeAllowlistResponseHandler],
[autopopulateAllowlistMutation, failureHandler],
[getAuthLogCountQuery, authLogCountResponseHandler],
],
@ -674,14 +621,14 @@ describe('TokenAccess component', () => {
it('calls the remove autopopulated entries mutation and refetches allowlist', async () => {
expect(removeAutopopulatedEntriesMutationHandler).toHaveBeenCalledTimes(0);
expect(inboundGroupsAndProjectsWithScopeResponseHandler).toHaveBeenCalledTimes(1);
expect(ciJobTokenScopeAllowlistResponseHandler).toHaveBeenCalledTimes(1);
triggerRemoveEntries();
await waitForPromises();
await nextTick();
expect(removeAutopopulatedEntriesMutationHandler).toHaveBeenCalledTimes(1);
expect(inboundGroupsAndProjectsWithScopeResponseHandler).toHaveBeenCalledTimes(2);
expect(ciJobTokenScopeAllowlistResponseHandler).toHaveBeenCalledTimes(2);
});
it('shows toast message when mutation is successful', async () => {
@ -698,10 +645,7 @@ describe('TokenAccess component', () => {
createComponent(
[
[inboundGetCIJobTokenScopeQuery, inboundJobTokenScopeEnabledResponseHandler],
[
inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
inboundGroupsAndProjectsWithScopeResponseHandler,
],
[getCiJobTokenScopeAllowlistQuery, ciJobTokenScopeAllowlistResponseHandler],
[removeAutopopulatedEntriesMutation, removeAutopopulatedEntriesMutationErrorHandler],
[getAuthLogCountQuery, authLogCountResponseHandler],
],
@ -725,10 +669,7 @@ describe('TokenAccess component', () => {
createComponent(
[
[inboundGetCIJobTokenScopeQuery, inboundJobTokenScopeEnabledResponseHandler],
[
inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
inboundGroupsAndProjectsWithScopeResponseHandler,
],
[getCiJobTokenScopeAllowlistQuery, ciJobTokenScopeAllowlistResponseHandler],
[removeAutopopulatedEntriesMutation, failureHandler],
[getAuthLogCountQuery, authLogCountResponseHandler],
],
@ -774,8 +715,8 @@ describe('TokenAccess component', () => {
[
[inboundGetCIJobTokenScopeQuery, inboundJobTokenScopeEnabledResponseHandler],
[
inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
inboundGroupsAndProjectsWithoutAutopopulatedEntriesResponseHandler,
getCiJobTokenScopeAllowlistQuery,
ciJobTokenScopeAllowlistWithoutAutopopulatedEntriesResponseHandler,
],
[getAuthLogCountQuery, authLogZeroCountResponseHandler],
],
@ -847,10 +788,7 @@ describe('TokenAccess component', () => {
beforeEach(() => {
const requestHandlers = [
[inboundGetCIJobTokenScopeQuery, inboundJobTokenScopeDisabledResponseHandler],
[
inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
inboundGroupsAndProjectsWithScopeResponseHandler,
],
[getCiJobTokenScopeAllowlistQuery, ciJobTokenScopeAllowlistResponseHandler],
];
return createComponent(requestHandlers, { enforceAllowlist: true });
@ -866,10 +804,7 @@ describe('TokenAccess component', () => {
describe('allowlist counts', () => {
beforeEach(() => {
const requestHandlers = [
[
inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
inboundGroupsAndProjectsWithScopeResponseHandler,
],
[getCiJobTokenScopeAllowlistQuery, ciJobTokenScopeAllowlistResponseHandler],
];
return createComponent(requestHandlers, { stubs: { CrudComponent } });
@ -891,13 +826,13 @@ describe('TokenAccess component', () => {
});
it('shows project count', () => {
expect(findProjectCount().text()).toBe('1');
expect(findProjectCount().text()).toBe('3');
});
it('has project count tooltip', () => {
const tooltip = getBinding(findProjectCount().element, 'gl-tooltip');
expect(tooltip).toMatchObject({ modifiers: { d0: true }, value: '1 project has access' });
expect(tooltip).toMatchObject({ modifiers: { d0: true }, value: '3 projects have access' });
});
});
@ -923,36 +858,6 @@ describe('TokenAccess component', () => {
});
});
describe.each`
isJobTokenPoliciesEnabled | oldQueryCallCount | newQueryCallCount
${true} | ${0} | ${1}
${false} | ${1} | ${0}
`(
'when isJobTokenPoliciesEnabled is $isJobTokenPoliciesEnabled',
({ isJobTokenPoliciesEnabled, oldQueryCallCount, newQueryCallCount }) => {
const oldQueryHandler = jest.fn();
const newQueryHandler = jest.fn();
beforeEach(() => {
createComponent(
[
[inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery, oldQueryHandler],
[getCiJobTokenScopeAllowlistQuery, newQueryHandler],
],
{ isJobTokenPoliciesEnabled },
);
});
it(`calls the old query ${oldQueryCallCount} times`, () => {
expect(oldQueryHandler).toHaveBeenCalledTimes(oldQueryCallCount);
});
it(`calls the new query ${newQueryCallCount} times`, () => {
expect(newQueryHandler).toHaveBeenCalledTimes(newQueryCallCount);
});
},
);
describe('editing an allowlist item', () => {
const item = {};

View File

@ -164,48 +164,6 @@ export const inboundJobTokenScopeDisabledResponse = {
},
};
export const inboundGroupsAndProjectsWithScopeResponse = (hasAutopopulatedEntries = true) => ({
data: {
project: {
__typename: 'Project',
id: 1,
ciJobTokenScope: {
__typename: 'CiJobTokenScopeType',
inboundAllowlist: {
__typename: 'CiJobTokenAccessibleProjectConnection',
nodes: [
{
__typename: 'CiJobTokenAccessibleProject',
fullPath: 'root/ci-project',
id: 'gid://gitlab/Project/23',
name: 'ci-project',
avatarUrl: '',
webUrl: 'http://localhost/root/ci-project',
},
],
},
groupsAllowlist: {
__typename: 'CiJobTokenAccessibleGroupConnection',
nodes: [
{
__typename: 'CiJobTokenAccessibleGroup',
fullPath: 'root/ci-group',
id: 'gid://gitlab/Group/45',
name: 'ci-group',
avatarUrl: '',
webUrl: 'http://localhost/root/ci-group',
},
],
},
groupAllowlistAutopopulatedIds: hasAutopopulatedEntries ? ['gid://gitlab/Group/45'] : [],
inboundAllowlistAutopopulatedIds: hasAutopopulatedEntries
? ['gid://gitlab/Project/23']
: [],
},
},
},
});
export const getSaveNamespaceHandler = (error) =>
jest.fn().mockResolvedValue({
data: { saveNamespace: { errors: error ? [error] : [] } },
@ -271,7 +229,7 @@ export const mockAuthLogsCountResponse = (count) => ({
},
});
export const mockCiJobTokenScopeAllowlistResponse = {
export const mockCiJobTokenScopeAllowlistResponse = (hasAutopopulatedEntries = true) => ({
data: {
project: {
id: 'gid://gitlab/Project/26',
@ -285,7 +243,7 @@ export const mockCiJobTokenScopeAllowlistResponse = {
{
defaultPermissions: true,
jobTokenPolicies: [],
autopopulated: true,
autopopulated: hasAutopopulatedEntries,
target: {
id: 'gid://gitlab/Group/4',
name: 'zed',
@ -303,7 +261,7 @@ export const mockCiJobTokenScopeAllowlistResponse = {
{
defaultPermissions: true,
jobTokenPolicies: [],
autopopulated: true,
autopopulated: hasAutopopulatedEntries,
target: {
id: 'gid://gitlab/Project/23',
name: 'your-repo',
@ -317,7 +275,7 @@ export const mockCiJobTokenScopeAllowlistResponse = {
{
defaultPermissions: true,
jobTokenPolicies: [],
autopopulated: true,
autopopulated: hasAutopopulatedEntries,
target: {
id: 'gid://gitlab/Project/14',
name: 'abc123',
@ -333,7 +291,7 @@ export const mockCiJobTokenScopeAllowlistResponse = {
},
},
},
};
});
export const mockAuthLogsResponse = (hasNextPage = false) => ({
data: {

View File

@ -23,7 +23,6 @@ describe('Namespace form component', () => {
namespace,
addMutationHandler = defaultAddMutationHandler,
editMutationHandler = defaultEditMutationHandler,
isJobTokenPoliciesEnabled = true,
} = {}) => {
wrapper = shallowMountExtended(NamespaceForm, {
apolloProvider: createMockApollo([
@ -31,7 +30,7 @@ describe('Namespace form component', () => {
[editNamespaceMutation, editMutationHandler],
]),
propsData: { namespace },
provide: { fullPath: 'full/path', isJobTokenPoliciesEnabled },
provide: { fullPath: 'full/path' },
stubs: {
GlFormInput: stubComponent(GlFormInput, {
props: ['autofocus', 'disabled', 'state', 'placeholder'],
@ -236,26 +235,6 @@ describe('Namespace form component', () => {
});
});
describe('when isJobTokenPoliciesEnabled is false', () => {
beforeEach(() => createWrapper({ isJobTokenPoliciesEnabled: false }));
it('does not show permissions selector', () => {
expect(findPoliciesSelector().exists()).toBe(false);
});
describe('when namespace is saved', () => {
it('calls mutation without defaultPermissions or jobTokenPolicies', () => {
findFormInput().vm.$emit('input', 'gitlab');
findSubmitButton().vm.$emit('click');
expect(defaultAddMutationHandler).toHaveBeenCalledWith({
projectPath: 'full/path',
targetPath: 'gitlab',
});
});
});
});
describe('editing a namespace', () => {
beforeEach(() =>
createWrapper({

View File

@ -133,22 +133,6 @@ describe('Token access table', () => {
});
});
describe('when showPolicies prop is false', () => {
beforeEach(() => createComponent({ showPolicies: false, items: mockGroups }));
it('does not show policies column', () => {
const tableFieldKeys = findTable()
.props('fields')
.map(({ key }) => key);
expect(tableFieldKeys).not.toContain('policies');
});
it('does not show edit button', () => {
expect(findEditButton().exists()).toBe(false);
});
});
describe('group auto-populated icon', () => {
it('shows the icon when the item is auto-populated', () => {
createComponent({ items: [mockGroups[0]] });

View File

@ -1,16 +1,40 @@
import { GlSprintf } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
import Vue, { nextTick } from 'vue';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import PageHeading from '~/vue_shared/components/page_heading.vue';
import WikiHeader from '~/wikis/components/wiki_header.vue';
import createMockApollo from 'helpers/mock_apollo_helper';
import wikiPageQuery from '~/wikis/graphql/wiki_page.query.graphql';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import { queryVariables, wikiPageQueryMockData } from '../notes/mock_data';
Vue.use(VueApollo);
describe('wikis/components/wiki_header', () => {
let wrapper;
let fakeApollo;
const mockToastShow = jest.fn();
function buildWrapper(provide = {}, mockQueryData = {}) {
fakeApollo = createMockApollo([
[
wikiPageQuery,
jest.fn().mockResolvedValue({
data: {
wikiPage: wikiPageQueryMockData,
...mockQueryData,
},
}),
],
]);
function buildWrapper(provide = {}) {
wrapper = shallowMountExtended(WikiHeader, {
apolloProvider: fakeApollo,
provide: {
pageHeading: 'Wiki page heading',
queryVariables,
isPageTemplate: false,
isEditingPath: false,
showEditButton: true,
@ -30,11 +54,17 @@ describe('wikis/components/wiki_header', () => {
TimeAgo,
PageHeading,
},
mocks: {
$toast: {
show: mockToastShow,
},
},
});
}
const findPageHeading = () => wrapper.findByTestId('page-heading');
const findEditButton = () => wrapper.findByTestId('wiki-edit-button');
const findSubscribeButton = () => wrapper.findByTestId('wiki-subscribe-button');
const findLastVersion = () => wrapper.findByTestId('wiki-page-last-version');
describe('renders', () => {
@ -62,4 +92,98 @@ describe('wikis/components/wiki_header', () => {
expect(findLastVersion().exists()).toBe(false);
});
});
describe('subscribe button functionality', () => {
let mutateSpy;
beforeEach(async () => {
buildWrapper();
mutateSpy = jest.spyOn(wrapper.vm.$apollo.provider.defaultClient, 'mutate');
await nextTick();
});
afterEach(() => {
mutateSpy.mockRestore();
});
it('calls apollo with the correct variables when the subscribe button is clicked', () => {
findSubscribeButton().vm.$emit('click');
expect(mutateSpy).toHaveBeenCalledWith(
expect.objectContaining({
variables: {
id: 'gid://gitlab/WikiPage/1',
subscribed: true,
},
}),
);
});
it('calls apollo with the correct variables when the unsubscribe button is clicked', async () => {
const id = 'gid://gitlab/WikiPage/1';
buildWrapper(
{},
{
wikiPage: {
...wikiPageQueryMockData,
id,
subscribed: true,
},
},
);
const spy = jest.spyOn(wrapper.vm.$apollo.provider.defaultClient, 'mutate');
await wrapper.vm.$apollo.queries.wikiPage.refetch();
await nextTick();
findSubscribeButton().vm.$emit('click');
expect(spy).toHaveBeenCalledWith(
expect.objectContaining({
variables: {
id,
subscribed: false,
},
}),
);
});
it('calls the toast method if the mutation succeeds', async () => {
mutateSpy.mockResolvedValue();
findSubscribeButton().vm.$emit('click');
await wrapper.vm.$apollo.queries.wikiPage.refetch();
await nextTick();
expect(mockToastShow).toHaveBeenCalled();
});
it('calls the toast method and captures error if the mutation fails', async () => {
const error = new Error('An error occurred');
mutateSpy.mockRejectedValue(error);
const sentrySpy = jest.spyOn(Sentry, 'captureException');
findSubscribeButton().vm.$emit('click');
await wrapper.vm.$apollo.queries.wikiPage.refetch();
await nextTick();
expect(mockToastShow).toHaveBeenCalled();
expect(sentrySpy).toHaveBeenCalledWith(error);
});
it('does not call the apollo mutate method if the state of the subscription has not been resolved', () => {
expect(mutateSpy).toHaveBeenCalledTimes(0);
// first click
findSubscribeButton().vm.$emit('click');
expect(mutateSpy).toHaveBeenCalledTimes(1);
// second click, while the subscription state is still resolving
findSubscribeButton().vm.$emit('click');
// there should be no second mutation call
expect(mutateSpy).toHaveBeenCalledTimes(1);
});
});
});

View File

@ -73,6 +73,7 @@ describe('WikiNotesApp', () => {
userPermissions: {
markNoteAsInternal: true,
},
subscribed: false,
discussions: {
nodes: [mockDiscussion('Discussion 1')],
},
@ -101,6 +102,7 @@ describe('WikiNotesApp', () => {
userPermissions: {
markNoteAsInternal: true,
},
subscribed: false,
discussions: {
nodes: [mockDiscussion('Discussion 1')],
},
@ -223,6 +225,7 @@ describe('WikiNotesApp', () => {
userPermissions: {
markNoteAsInternal: true,
},
subscribed: false,
discussions,
},
},
@ -273,6 +276,7 @@ describe('WikiNotesApp', () => {
userPermissions: {
markNoteAsInternal: true,
},
subscribed: false,
discussions,
},
},

View File

@ -99,6 +99,14 @@ export const awardEmoji = {
export const noteableId = '7';
export const queryVariables = { slug: 'home', projectId: 'gid://gitlab/Group/7' };
export const wikiPageQueryMockData = {
title: 'home',
id: 'gid://gitlab/WikiPage/1',
subscribed: false,
userPermissions: {
markNoteAsInternal: false,
},
};
export const wikiCommentFormProvideData = {
pageInfo,

View File

@ -32,12 +32,6 @@ RSpec.describe Mutations::Ci::JobTokenScope::AddGroupOrProject, feature_category
end
end
before do
allow_next_found_instance_of(Project) do |project|
allow(project).to receive(:job_token_policies_enabled?).and_return(true)
end
end
context 'when we add a project' do
let_it_be(:target_project) { create(:project) }
let_it_be(:target_project_path) { target_project.full_path }

View File

@ -288,9 +288,6 @@ RSpec.describe API::Helpers, feature_category: :shared do
allow(helper).to receive(:route_authentication_setting).and_return({})
allow(helper).to receive(:route_setting).with(:authorization).and_return(job_token_policies: job_token_policy)
allow(user).to receive(:ci_job_token_scope).and_return(user.set_ci_job_token_scope!(job))
allow_next_found_instance_of(Project) do |project|
allow(project).to receive(:job_token_policies_enabled?).and_return(true)
end
end
subject(:find_project!) { helper.find_project!(project.id) }
@ -349,16 +346,6 @@ RSpec.describe API::Helpers, feature_category: :shared do
find_project!
end
context 'when job token policies are disabled' do
before do
allow_next_found_instance_of(Project) do |project|
allow(project).to receive(:job_token_policies_enabled?).and_return(false)
end
end
it { is_expected.to eq project }
end
end
context "when route settings don't exist" do

View File

@ -854,153 +854,89 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen
end
describe "#log" do
shared_examples 'repository log' do
let(:commit_with_old_name) do
Gitlab::Git::Commit.find(repository, @commit_with_old_name_id)
let(:commit_with_old_name) do
Gitlab::Git::Commit.find(repository, @commit_with_old_name_id)
end
let(:commit_with_new_name) do
Gitlab::Git::Commit.find(repository, @commit_with_new_name_id)
end
let(:rename_commit) do
Gitlab::Git::Commit.find(repository, @rename_commit_id)
end
before do
# Add new commits so that there's a renamed file in the commit history
@commit_with_old_name_id = repository.commit_files(
user,
branch_name: repository.root_ref,
message: 'Update CHANGELOG',
actions: [{
action: :update,
file_path: 'CHANGELOG',
content: 'CHANGELOG'
}]
).newrev
@rename_commit_id = repository.commit_files(
user,
branch_name: repository.root_ref,
message: 'Move CHANGELOG to encoding/',
actions: [{
action: :move,
previous_path: 'CHANGELOG',
file_path: 'encoding/CHANGELOG',
content: 'CHANGELOG'
}]
).newrev
@commit_with_new_name_id = repository.commit_files(
user,
branch_name: repository.root_ref,
message: 'Edit encoding/CHANGELOG',
actions: [{
action: :update,
file_path: 'encoding/CHANGELOG',
content: "I'm a new changelog with different text"
}]
).newrev
end
after do
# Erase our commits so other tests get the original repo
repository.write_ref(repository.root_ref, TestEnv::BRANCH_SHA['master'])
end
context "where 'follow' == true" do
let(:options) { { ref: "master", follow: true } }
context "and 'path' is a directory" do
it "does not follow renames" do
log_commits = repository.log(options.merge(path: "encoding"))
aggregate_failures do
expect(log_commits).to include(commit_with_new_name)
expect(log_commits).to include(rename_commit)
expect(log_commits).not_to include(commit_with_old_name)
end
end
end
let(:commit_with_new_name) do
Gitlab::Git::Commit.find(repository, @commit_with_new_name_id)
end
let(:rename_commit) do
Gitlab::Git::Commit.find(repository, @rename_commit_id)
end
before do
# Add new commits so that there's a renamed file in the commit history
@commit_with_old_name_id = repository.commit_files(
user,
branch_name: repository.root_ref,
message: 'Update CHANGELOG',
actions: [{
action: :update,
file_path: 'CHANGELOG',
content: 'CHANGELOG'
}]
).newrev
@rename_commit_id = repository.commit_files(
user,
branch_name: repository.root_ref,
message: 'Move CHANGELOG to encoding/',
actions: [{
action: :move,
previous_path: 'CHANGELOG',
file_path: 'encoding/CHANGELOG',
content: 'CHANGELOG'
}]
).newrev
@commit_with_new_name_id = repository.commit_files(
user,
branch_name: repository.root_ref,
message: 'Edit encoding/CHANGELOG',
actions: [{
action: :update,
file_path: 'encoding/CHANGELOG',
content: "I'm a new changelog with different text"
}]
).newrev
end
after do
# Erase our commits so other tests get the original repo
repository.write_ref(repository.root_ref, TestEnv::BRANCH_SHA['master'])
end
context "where 'follow' == true" do
let(:options) { { ref: "master", follow: true } }
context "and 'path' is a directory" do
it "does not follow renames" do
log_commits = repository.log(options.merge(path: "encoding"))
context "and 'path' is a file that matches the new filename" do
context 'without offset' do
it "follows renames" do
log_commits = repository.log(options.merge(path: "encoding/CHANGELOG"))
aggregate_failures do
expect(log_commits).to include(commit_with_new_name)
expect(log_commits).to include(rename_commit)
expect(log_commits).not_to include(commit_with_old_name)
expect(log_commits).to include(commit_with_old_name)
end
end
end
context "and 'path' is a file that matches the new filename" do
context 'without offset' do
it "follows renames" do
log_commits = repository.log(options.merge(path: "encoding/CHANGELOG"))
aggregate_failures do
expect(log_commits).to include(commit_with_new_name)
expect(log_commits).to include(rename_commit)
expect(log_commits).to include(commit_with_old_name)
end
end
end
context 'with offset=1' do
it "follows renames and skip the latest commit" do
log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1))
aggregate_failures do
expect(log_commits).not_to include(commit_with_new_name)
expect(log_commits).to include(rename_commit)
expect(log_commits).to include(commit_with_old_name)
end
end
end
context 'with offset=1', 'and limit=1' do
it "follows renames, skip the latest commit and return only one commit" do
log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1, limit: 1))
expect(log_commits).to contain_exactly(rename_commit)
end
end
context 'with offset=1', 'and limit=2' do
it "follows renames, skip the latest commit and return only two commits" do
log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1, limit: 2))
aggregate_failures do
expect(log_commits).to contain_exactly(rename_commit, commit_with_old_name)
end
end
end
context 'with offset=2' do
it "follows renames and skip the latest commit" do
log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2))
aggregate_failures do
expect(log_commits).not_to include(commit_with_new_name)
expect(log_commits).not_to include(rename_commit)
expect(log_commits).to include(commit_with_old_name)
end
end
end
context 'with offset=2', 'and limit=1' do
it "follows renames, skip the two latest commit and return only one commit" do
log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2, limit: 1))
expect(log_commits).to contain_exactly(commit_with_old_name)
end
end
context 'with offset=2', 'and limit=2' do
it "follows renames, skip the two latest commit and return only one commit" do
log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2, limit: 2))
aggregate_failures do
expect(log_commits).not_to include(commit_with_new_name)
expect(log_commits).not_to include(rename_commit)
expect(log_commits).to include(commit_with_old_name)
end
end
end
end
context "and 'path' is a file that matches the old filename" do
it "does not follow renames" do
log_commits = repository.log(options.merge(path: "CHANGELOG"))
context 'with offset=1' do
it "follows renames and skip the latest commit" do
log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1))
aggregate_failures do
expect(log_commits).not_to include(commit_with_new_name)
@ -1010,121 +946,179 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen
end
end
context "unknown ref" do
it "returns an empty array" do
log_commits = repository.log(options.merge(ref: 'unknown'))
context 'with offset=1', 'and limit=1' do
it "follows renames, skip the latest commit and return only one commit" do
log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1, limit: 1))
expect(log_commits).to eq([])
expect(log_commits).to contain_exactly(rename_commit)
end
end
context 'with offset=1', 'and limit=2' do
it "follows renames, skip the latest commit and return only two commits" do
log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1, limit: 2))
aggregate_failures do
expect(log_commits).to contain_exactly(rename_commit, commit_with_old_name)
end
end
end
context 'with offset=2' do
it "follows renames and skip the latest commit" do
log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2))
aggregate_failures do
expect(log_commits).not_to include(commit_with_new_name)
expect(log_commits).not_to include(rename_commit)
expect(log_commits).to include(commit_with_old_name)
end
end
end
context 'with offset=2', 'and limit=1' do
it "follows renames, skip the two latest commit and return only one commit" do
log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2, limit: 1))
expect(log_commits).to contain_exactly(commit_with_old_name)
end
end
context 'with offset=2', 'and limit=2' do
it "follows renames, skip the two latest commit and return only one commit" do
log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2, limit: 2))
aggregate_failures do
expect(log_commits).not_to include(commit_with_new_name)
expect(log_commits).not_to include(rename_commit)
expect(log_commits).to include(commit_with_old_name)
end
end
end
end
context "where 'follow' == false" do
options = { follow: false }
context "and 'path' is a file that matches the old filename" do
it "does not follow renames" do
log_commits = repository.log(options.merge(path: "CHANGELOG"))
context "and 'path' is a directory" do
let(:log_commits) do
repository.log(options.merge(path: "encoding"))
end
it "does not follow renames" do
expect(log_commits).to include(commit_with_new_name)
expect(log_commits).to include(rename_commit)
expect(log_commits).not_to include(commit_with_old_name)
end
end
context "and 'path' is a file that matches the new filename" do
let(:log_commits) do
repository.log(options.merge(path: "encoding/CHANGELOG"))
end
it "does not follow renames" do
expect(log_commits).to include(commit_with_new_name)
expect(log_commits).to include(rename_commit)
expect(log_commits).not_to include(commit_with_old_name)
end
end
context "and 'path' is a file that matches the old filename" do
let(:log_commits) do
repository.log(options.merge(path: "CHANGELOG"))
end
it "does not follow renames" do
expect(log_commits).to include(commit_with_old_name)
expect(log_commits).to include(rename_commit)
aggregate_failures do
expect(log_commits).not_to include(commit_with_new_name)
expect(log_commits).to include(rename_commit)
expect(log_commits).to include(commit_with_old_name)
end
end
end
context "where provides 'after' timestamp" do
options = { after: Time.iso8601('2014-03-03T20:15:01+00:00') }
context "unknown ref" do
it "returns an empty array" do
log_commits = repository.log(options.merge(ref: 'unknown'))
it "returns commits on or after that timestamp" do
commits = repository.log(options)
expect(commits.size).to be > 0
expect(commits).to satisfy do |commits|
commits.all? { |commit| commit.committed_date >= options[:after] }
end
end
end
context "where provides 'before' timestamp" do
options = { before: Time.iso8601('2014-03-03T20:15:01+00:00') }
it "returns commits on or before that timestamp" do
commits = repository.log(options)
expect(commits.size).to be > 0
expect(commits).to satisfy do |commits|
commits.all? { |commit| commit.committed_date <= options[:before] }
end
end
end
context 'when multiple paths are provided' do
let(:options) { { ref: 'master', path: ['PROCESS.md', 'README.md'] } }
def commit_files(commit)
commit.deltas.flat_map do |delta|
[delta.old_path, delta.new_path].uniq.compact
end
end
it 'only returns commits matching at least one path' do
commits = repository.log(options)
expect(commits.size).to be > 0
expect(commits).to satisfy do |commits|
commits.none? { |commit| (commit_files(commit) & options[:path]).empty? }
end
end
end
context 'limit validation' do
where(:limit) do
[0, nil, '', 'foo']
end
with_them do
it { expect { repository.log(limit: limit) }.to raise_error(ArgumentError) }
end
end
context 'with all' do
it 'returns a list of commits' do
commits = repository.log({ all: true, limit: 50 })
expect(commits.size).to eq(50)
expect(log_commits).to eq([])
end
end
end
context 'when Gitaly find_commits feature is enabled' do
it_behaves_like 'repository log'
context "where 'follow' == false" do
options = { follow: false }
context "and 'path' is a directory" do
let(:log_commits) do
repository.log(options.merge(path: "encoding"))
end
it "does not follow renames" do
expect(log_commits).to include(commit_with_new_name)
expect(log_commits).to include(rename_commit)
expect(log_commits).not_to include(commit_with_old_name)
end
end
context "and 'path' is a file that matches the new filename" do
let(:log_commits) do
repository.log(options.merge(path: "encoding/CHANGELOG"))
end
it "does not follow renames" do
expect(log_commits).to include(commit_with_new_name)
expect(log_commits).to include(rename_commit)
expect(log_commits).not_to include(commit_with_old_name)
end
end
context "and 'path' is a file that matches the old filename" do
let(:log_commits) do
repository.log(options.merge(path: "CHANGELOG"))
end
it "does not follow renames" do
expect(log_commits).to include(commit_with_old_name)
expect(log_commits).to include(rename_commit)
expect(log_commits).not_to include(commit_with_new_name)
end
end
end
context "where provides 'after' timestamp" do
options = { after: Time.iso8601('2014-03-03T20:15:01+00:00') }
it "returns commits on or after that timestamp" do
commits = repository.log(options)
expect(commits.size).to be > 0
expect(commits).to satisfy do |commits|
commits.all? { |commit| commit.committed_date >= options[:after] }
end
end
end
context "where provides 'before' timestamp" do
options = { before: Time.iso8601('2014-03-03T20:15:01+00:00') }
it "returns commits on or before that timestamp" do
commits = repository.log(options)
expect(commits.size).to be > 0
expect(commits).to satisfy do |commits|
commits.all? { |commit| commit.committed_date <= options[:before] }
end
end
end
context 'when multiple paths are provided' do
let(:options) { { ref: 'master', path: ['PROCESS.md', 'README.md'] } }
def commit_files(commit)
commit.deltas.flat_map do |delta|
[delta.old_path, delta.new_path].uniq.compact
end
end
it 'only returns commits matching at least one path' do
commits = repository.log(options)
expect(commits.size).to be > 0
expect(commits).to satisfy do |commits|
commits.none? { |commit| (commit_files(commit) & options[:path]).empty? }
end
end
end
context 'limit validation' do
where(:limit) do
[0, nil, '', 'foo']
end
with_them do
it { expect { repository.log(limit: limit) }.to raise_error(ArgumentError) }
end
end
context 'with all' do
it 'returns a list of commits' do
commits = repository.log({ all: true, limit: 50 })
expect(commits.size).to eq(50)
end
end
end

View File

@ -989,6 +989,22 @@ RSpec.describe Gitlab::GitalyClient::CommitService, feature_category: :gitaly do
client.find_commits(order: 'default', author: "Billy Baggins <bilbo@shire.com>")
end
it 'sends an RPC request with a message_regex' do
request = Gitaly::FindCommitsRequest.new(
repository: repository_message,
disable_walk: true,
order: 'NONE',
message_regex: '^foo',
global_options: Gitaly::GlobalOptions.new(literal_pathspecs: false)
)
expect_any_instance_of(Gitaly::CommitService::Stub)
.to receive(:find_commits).with(request, kind_of(Hash))
.and_return([])
client.find_commits(message_regex: '^foo')
end
end
describe '#object_existence_map' do

View File

@ -11,10 +11,6 @@ RSpec.describe Ci::JobToken::Allowlist, feature_category: :continuous_integratio
let(:allowlist) { described_class.new(source_project, direction: direction) }
let(:direction) { :outbound }
before do
allow(source_project).to receive(:job_token_policies_enabled?).and_return(true)
end
describe '#projects' do
subject(:projects) { allowlist.projects }
@ -103,23 +99,6 @@ RSpec.describe Ci::JobToken::Allowlist, feature_category: :continuous_integratio
expect(project_link.target_project_id).to eq(added_project.id)
expect(project_link.job_token_policies).to eq(policies)
end
context 'when job token policies are disabled' do
before do
allow(source_project).to receive(:job_token_policies_enabled?).and_return(false)
end
it 'adds the project scope link but with empty job token policies' do
project_link = add_project
expect(allowlist.projects).to contain_exactly(source_project, added_project)
expect(project_link.added_by_id).to eq(user.id)
expect(project_link.source_project_id).to eq(source_project.id)
expect(project_link.target_project_id).to eq(added_project.id)
expect(project_link.default_permissions).to be(true)
expect(project_link.job_token_policies).to eq([])
end
end
end
end
end
@ -155,23 +134,6 @@ RSpec.describe Ci::JobToken::Allowlist, feature_category: :continuous_integratio
expect(group_link.target_group_id).to eq(added_group.id)
expect(group_link.job_token_policies).to eq(policies)
end
context 'when job token policies are disabled' do
before do
allow(source_project).to receive(:job_token_policies_enabled?).and_return(false)
end
it 'adds the group scope link but with empty job token policies' do
group_link = add_group
expect(allowlist.groups).to contain_exactly(added_group)
expect(group_link.added_by_id).to eq(user.id)
expect(group_link.source_project_id).to eq(source_project.id)
expect(group_link.target_group_id).to eq(added_group.id)
expect(group_link.default_permissions).to be(true)
expect(group_link.job_token_policies).to eq([])
end
end
end
describe '#includes_project?' do
@ -331,25 +293,6 @@ RSpec.describe Ci::JobToken::Allowlist, feature_category: :continuous_integratio
expect(project_link.target_project_id).to eq(added_project1.id)
expect(project_link.job_token_policies).to eq(policies)
end
context 'when job token policies are disabled' do
before do
allow(source_project).to receive(:job_token_policies_enabled?).and_return(false)
end
it 'adds the project scope link but with empty job token policies' do
add_projects
project_links = Ci::JobToken::ProjectScopeLink.where(source_project_id: source_project.id)
project_link = project_links.first
expect(allowlist.projects).to match_array([source_project, added_project1, added_project2])
expect(project_link.added_by_id).to eq(user.id)
expect(project_link.source_project_id).to eq(source_project.id)
expect(project_link.target_project_id).to eq(added_project1.id)
expect(project_link.job_token_policies).to eq([])
end
end
end
describe '#bulk_add_groups!' do
@ -375,26 +318,6 @@ RSpec.describe Ci::JobToken::Allowlist, feature_category: :continuous_integratio
expect(group_link.job_token_policies).to eq(policies)
expect(group_link.autopopulated).to be true
end
context 'when job token policies are disabled' do
before do
allow(source_project).to receive(:job_token_policies_enabled?).and_return(false)
end
it 'adds the group scope link but with empty job token policies' do
add_groups
group_links = Ci::JobToken::GroupScopeLink.where(source_project_id: source_project.id)
group_link = group_links.first
expect(allowlist.groups).to match_array([added_group1, added_group2])
expect(group_link.added_by_id).to eq(user.id)
expect(group_link.source_project_id).to eq(source_project.id)
expect(group_link.target_group_id).to eq(added_group1.id)
expect(group_link.job_token_policies).to eq([])
expect(group_link.autopopulated).to be true
end
end
end
describe '#autopopulated_project_global_ids' do

View File

@ -255,10 +255,6 @@ RSpec.describe Ci::JobToken::Scope, feature_category: :continuous_integration, f
let_it_be(:allowed_policy) { ::Ci::JobToken::Policies::POLICIES.first }
let(:accessed_project) { create_inbound_accessible_project_for_policies(target_project, [allowed_policy]) }
before do
allow(accessed_project).to receive(:job_token_policies_enabled?).and_return(true)
end
shared_examples 'capturing job token policies' do
it 'captures job token policies' do
expect(::Ci::JobToken::Authorization).to receive(:capture_job_token_policies).with(policies)
@ -334,16 +330,6 @@ RSpec.describe Ci::JobToken::Scope, feature_category: :continuous_integration, f
it_behaves_like 'capturing job token policies'
end
context 'when job token policies are disabled' do
before do
allow(accessed_project).to receive(:job_token_policies_enabled?).and_return(false)
end
it { is_expected.to be(true) }
it_behaves_like 'capturing job token policies'
end
context 'when the accessed project has not enabled fine grained permissions' do
let(:accessed_project) { create_inbound_accessible_project(target_project) }

View File

@ -10259,24 +10259,6 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr
end
end
describe '#job_token_policies_enabled?' do
let_it_be(:project) { build_stubbed(:project) }
subject { project.job_token_policies_enabled? }
where(:setting_enabled) { [true, false] }
before do
project.clear_memoization(:job_token_policies_enabled?)
allow(project).to receive_message_chain(:namespace, :root_ancestor, :namespace_settings,
:job_token_policies_enabled?).and_return(setting_enabled)
end
with_them do
it { is_expected.to eq(setting_enabled) }
end
end
context 'with loose foreign key on projects.pool_repository_id' do
it_behaves_like 'cleanup by a loose foreign key' do
let_it_be(:parent) { create(:pool_repository) }

View File

@ -169,12 +169,6 @@ RSpec.describe 'Querying CI_JOB_TOKEN allowlist for a project', feature_category
)
end
before do
allow_next_found_instance_of(Project) do |project|
allow(project).to receive(:job_token_policies_enabled?).and_return(true)
end
end
it 'returns the correct data' do
post_graphql(query, current_user: current_user)
@ -182,30 +176,6 @@ RSpec.describe 'Querying CI_JOB_TOKEN allowlist for a project', feature_category
expect(allowlist['projectsAllowlist']).to eq(expected_projects_allowlist)
end
context 'when job token policies are disabled' do
before do
allow_next_found_instance_of(Project) do |project|
allow(project).to receive(:job_token_policies_enabled?).and_return(false)
end
end
it 'returns job token policies as null', :aggregate_failures do
post_graphql(query, current_user: current_user)
expect(allowlist.dig('groupsAllowlist', 'nodes', 0, 'jobTokenPolicies')).to be_nil
expect(allowlist.dig('projectsAllowlist', 'nodes', 0, 'jobTokenPolicies')).to be_nil
expect(allowlist.dig('projectsAllowlist', 'nodes', 1, 'jobTokenPolicies')).to be_nil
end
it 'returns default permissions as true', :aggregate_failures do
post_graphql(query, current_user: current_user)
expect(allowlist.dig('groupsAllowlist', 'nodes', 0, 'defaultPermissions')).to be(true)
expect(allowlist.dig('projectsAllowlist', 'nodes', 0, 'defaultPermissions')).to be(true)
expect(allowlist.dig('projectsAllowlist', 'nodes', 1, 'defaultPermissions')).to be(true)
end
end
it 'avoids N+1 queries', :use_sql_query_cache do
control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
post_graphql(query, current_user: current_user)

View File

@ -85,12 +85,6 @@ RSpec.describe 'CiJobTokenScopeAddGroupOrProject', feature_category: :continuous
end
end
before do
allow_next_found_instance_of(Project) do |project|
allow(project).to receive(:job_token_policies_enabled?).and_return(true)
end
end
context 'when we add a group' do
let_it_be(:target_group) { create(:group, :private) }
let(:target_path) { target_group }

View File

@ -43,12 +43,6 @@ RSpec.describe 'CiJobTokenScopeUpdatePolicies', feature_category: :continuous_in
let(:mutation_response) { graphql_mutation_response(:ci_job_token_scope_update_policies) }
before do
allow_next_found_instance_of(Project) do |project|
allow(project).to receive(:job_token_policies_enabled?).and_return(true)
end
end
context 'when policies are updated for a target project' do
let_it_be(:target_project) { create(:project, :private) }
let_it_be(:target_path) { target_project.full_path }
@ -116,17 +110,6 @@ RSpec.describe 'CiJobTokenScopeUpdatePolicies', feature_category: :continuous_in
let(:match_errors) { include(/was provided invalid value for jobTokenPolicies/) }
end
end
context 'when job token policies are disabled' do
before do
allow_next_found_instance_of(Project) do |project|
allow(project).to receive(:job_token_policies_enabled?).and_return(false)
end
end
it_behaves_like 'a mutation that returns top-level errors',
errors: ['job token policies are disabled.']
end
end
end
end
@ -197,17 +180,6 @@ RSpec.describe 'CiJobTokenScopeUpdatePolicies', feature_category: :continuous_in
let(:match_errors) { include(/was provided invalid value for jobTokenPolicies/) }
end
end
context 'when job token policies are disabled' do
before do
allow_next_found_instance_of(Project) do |project|
allow(project).to receive(:job_token_policies_enabled?).and_return(false)
end
end
it_behaves_like 'a mutation that returns top-level errors',
errors: ['job token policies are disabled.']
end
end
end
end

View File

@ -319,6 +319,7 @@ RSpec.describe API::Users, :with_current_organization, :aggregate_failures, feat
get api(path, admin, admin_mode: true)
expect(json_response).to have_key('note')
expect(json_response).to have_key('preferred_language')
expect(json_response['note']).to eq(admin.note)
end
end
@ -3271,6 +3272,18 @@ RSpec.describe API::Users, :with_current_organization, :aggregate_failures, feat
it_behaves_like 'get user info', 'v3'
it_behaves_like 'get user info', 'v4'
context 'when authenticated with a token that has the ai_workflows scope' do
let(:oauth_token) { create(:oauth_access_token, user: user, scopes: [:ai_workflows]) }
subject(:get_user) { get api("/user", oauth_access_token: oauth_token) }
it 'is successful' do
get_user
expect(response).to have_gitlab_http_status(:ok)
end
end
end
describe "GET /user/preferences" do

View File

@ -25,26 +25,6 @@ RSpec.describe Ci::JobTokenScope::AddGroupService, feature_category: :continuous
expect(group_link.default_permissions).to eq(default_permissions)
expect(group_link.job_token_policies).to eq(policies)
end
context 'when job token policies are disabled' do
before do
allow(project).to receive(:job_token_policies_enabled?).and_return(false)
end
it 'adds the group to the scope without the policies', :aggregate_failures do
expect { result }.to change { Ci::JobToken::GroupScopeLink.count }.by(1)
expect(result).to be_success
group_link = result.payload[:group_link]
expect(group_link.source_project).to eq(project)
expect(group_link.target_group).to eq(target_group)
expect(group_link.added_by).to eq(current_user)
expect(group_link.default_permissions).to be(true)
expect(group_link.job_token_policies).to eq([])
end
end
end
shared_examples 'event tracking' do
@ -78,10 +58,6 @@ RSpec.describe Ci::JobTokenScope::AddGroupService, feature_category: :continuous
let(:default_permissions) { false }
before do
allow(project).to receive(:job_token_policies_enabled?).and_return(true)
end
it_behaves_like 'editable group job token scope' do
context 'when user has permissions on source and target groups' do
before_all do
@ -91,22 +67,6 @@ RSpec.describe Ci::JobTokenScope::AddGroupService, feature_category: :continuous
it_behaves_like 'adds group'
it_behaves_like 'event tracking'
context 'when default_permissions is set to true' do
let(:default_permissions) { true }
it_behaves_like 'adds group'
it_behaves_like 'event tracking'
end
context 'when token scope is disabled' do
before do
project.ci_cd_settings.update!(job_token_scope_enabled: false)
end
it_behaves_like 'adds group'
it_behaves_like 'event tracking'
end
end
context 'when group is already in the allowlist' do

View File

@ -24,26 +24,6 @@ RSpec.describe Ci::JobTokenScope::AddProjectService, feature_category: :continuo
expect(project_link.default_permissions).to eq(default_permissions)
expect(project_link.job_token_policies).to eq(policies)
end
context 'when job token policies are disabled' do
before do
allow(project).to receive(:job_token_policies_enabled?).and_return(false)
end
it 'adds the project to the scope but without the policies', :aggregate_failures do
expect { result }.to change { Ci::JobToken::ProjectScopeLink.count }.by(1)
expect(result).to be_success
project_link = result.payload[:project_link]
expect(project_link.source_project).to eq(project)
expect(project_link.target_project).to eq(target_project)
expect(project_link.added_by).to eq(current_user)
expect(project_link.default_permissions).to be(true)
expect(project_link.job_token_policies).to eq([])
end
end
end
shared_examples 'event tracking' do
@ -80,10 +60,6 @@ RSpec.describe Ci::JobTokenScope::AddProjectService, feature_category: :continuo
let(:default_permissions) { false }
before do
allow(project).to receive(:job_token_policies_enabled?).and_return(true)
end
it_behaves_like 'editable job token scope' do
context 'when user has permissions on source and target projects' do
let(:resulting_direction) { result.payload.fetch(:project_link)&.direction }

View File

@ -13,10 +13,6 @@ RSpec.describe Ci::JobTokenScope::UpdatePoliciesService, feature_category: :cont
described_class.new(project, current_user).execute(target, default_permissions, policies)
end
before do
allow(project).to receive(:job_token_policies_enabled?).and_return(true)
end
describe '#execute' do
shared_examples 'when user is not logged in' do
let(:current_user) { nil }
@ -87,20 +83,6 @@ RSpec.describe Ci::JobTokenScope::UpdatePoliciesService, feature_category: :cont
it_behaves_like 'internal event not tracked'
end
shared_examples 'when job token policies are disabled' do
before do
allow(project).to receive(:job_token_policies_enabled?).and_return(false)
end
it 'returns an error and does not update the policies' do
expect(execute).to be_error
expect(execute.message).to eq('Failed to update job token scope')
expect(scope.reload.job_token_policies).to eq(%w[read_deployments])
end
it_behaves_like 'internal event not tracked'
end
shared_examples 'event tracking for project scope' do
it 'logs to Snowplow, Redis, and product analytics tooling', :clean_gitlab_redis_shared_state do
self_referential = target == project
@ -199,7 +181,6 @@ RSpec.describe Ci::JobTokenScope::UpdatePoliciesService, feature_category: :cont
end
it_behaves_like 'event tracking for project scope'
it_behaves_like 'when job token policies are disabled'
context 'when default permissions are not updated' do
let(:default_permissions) { true }
@ -286,7 +267,6 @@ RSpec.describe Ci::JobTokenScope::UpdatePoliciesService, feature_category: :cont
end
it_behaves_like 'event tracking for group scope'
it_behaves_like 'when job token policies are disabled'
context 'when default permissions are not updated' do
let(:default_permissions) { true }

View File

@ -309,7 +309,6 @@ RSpec.describe NamespaceSettings::AssignAttributesService, feature_category: :gr
:new_user_signups_cap | nil | 100
:seat_control | 'off' | 'user_cap'
:enabled_git_access_protocol | 'all' | 'ssh'
:job_token_policies_enabled | false | true
end
with_them do

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