Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
8ae6036da0
commit
b744b11e7d
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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' },
|
||||
|
|
|
|||
|
|
@ -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 }">
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
mutation WikiPageSubscribe($id: WikiPageMetaID!, $subscribed: Boolean!) {
|
||||
wikiPageSubscribe(input: { id: $id, subscribed: $subscribed }) {
|
||||
wikiPage {
|
||||
id
|
||||
subscribed
|
||||
}
|
||||
errors
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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=
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
e73e22d8adb94f8f4cee96dbbf0a12aea74f36d4988ea25e6f0dfae0ea606177
|
||||
|
|
@ -0,0 +1 @@
|
|||
295ef88c30cab25792044c391ca1414c43f145f3a351da2fc7746f9342c2e0eb
|
||||
|
|
@ -0,0 +1 @@
|
|||
156b23135b551a8ccc419e6ebba96e92884fbb01783bcd83775710585315b93b
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -635,6 +635,7 @@ Example response:
|
|||
"external": false,
|
||||
"private_profile": false,
|
||||
"commit_email": "admin@example.com",
|
||||
"preferred_language": "en",
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 = {};
|
||||
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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]] });
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in New Issue