Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
e358026c1c
commit
c5061bfaef
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
start-release-environments-security-pipeline:
|
||||
allow_failure: true
|
||||
allow_failure: false
|
||||
extends:
|
||||
- .release-environments:rules:start-release-environments-security-pipeline
|
||||
stage: release-environments
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@
|
|||
if: '($CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_EVENT_TYPE != "merge_train") && ($CI_MERGE_REQUEST_LABELS =~ /pipeline::expedited/ || $CI_MERGE_REQUEST_LABELS =~ /pipeline:expedite/)'
|
||||
|
||||
.if-merge-request-not-labels-pipeline-run-e2e-omnibus-once: &if-merge-request-not-labels-pipeline-run-e2e-omnibus-once
|
||||
if: '($CI_MERGE_REQUEST_EVENT_TYPE == "merged_result" || $CI_MERGE_REQUEST_EVENT_TYPE == "detached") && ($CI_MERGE_REQUEST_LABELS !~ /pipeline:run-e2e-omnibus-once/)'
|
||||
if: '($CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_EVENT_TYPE != "merge_train") && ($CI_MERGE_REQUEST_LABELS !~ /pipeline:run-e2e-omnibus-once/)'
|
||||
|
||||
.if-merge-request-labels-frontend-and-feature-flag: &if-merge-request-labels-frontend-and-feature-flag
|
||||
if: '($CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_EVENT_TYPE != "merge_train") && $CI_MERGE_REQUEST_LABELS =~ /frontend/ && $CI_MERGE_REQUEST_LABELS =~ /feature flag/'
|
||||
|
|
|
|||
|
|
@ -75,6 +75,11 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
showWebIdeForkSuggestion: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
projectPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
|
@ -161,6 +166,7 @@ export default {
|
|||
:edit-url="blob.editBlobPath"
|
||||
:web-ide-url="blob.ideEditPath"
|
||||
:needs-to-fork="showForkSuggestion"
|
||||
:needs-to-fork-with-web-ide="showWebIdeForkSuggestion"
|
||||
:show-pipeline-editor-button="Boolean(blob.pipelineEditorPath)"
|
||||
:pipeline-editor-url="blob.pipelineEditorPath"
|
||||
:gitpod-url="blob.gitpodBlobUrl"
|
||||
|
|
|
|||
|
|
@ -29,14 +29,14 @@ export default {
|
|||
},
|
||||
linkClasses: [
|
||||
'gl-border',
|
||||
'gl-text-decoration-none!',
|
||||
'!gl-no-underline',
|
||||
'gl-rounded-base',
|
||||
'gl-p-7',
|
||||
'gl-display-flex',
|
||||
'gl-flex',
|
||||
'gl-h-full',
|
||||
'gl-align-items-center',
|
||||
'gl-items-center',
|
||||
'gl-text-purple-600',
|
||||
'gl-hover-bg-gray-50',
|
||||
'hover:gl-bg-gray-50',
|
||||
],
|
||||
inject: [
|
||||
'newSubgroupPath',
|
||||
|
|
@ -53,28 +53,28 @@ export default {
|
|||
|
||||
<template>
|
||||
<div v-if="canCreateSubgroups || canCreateProjects" class="gl-mt-5">
|
||||
<div class="gl-display-flex -gl-mx-3 -gl-my-3 gl-flex-wrap">
|
||||
<div v-if="canCreateSubgroups" class="gl-p-3 gl-w-full gl-sm-w-half">
|
||||
<div class="-gl-mx-3 -gl-my-3 gl-flex gl-flex-wrap">
|
||||
<div v-if="canCreateSubgroups" class="gl-w-full gl-p-3 sm:gl-w-1/2">
|
||||
<gl-link :href="newSubgroupPath" :class="$options.linkClasses">
|
||||
<div class="svg-content gl-w-15 gl-flex-shrink-0 gl-mr-5">
|
||||
<div class="svg-content gl-mr-5 gl-w-15 gl-shrink-0">
|
||||
<img :src="newSubgroupIllustration" :alt="$options.i18n.withLinks.subgroup.title" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="gl-text-inherit">{{ $options.i18n.withLinks.subgroup.title }}</h4>
|
||||
<p class="gl-text-body">
|
||||
<p class="gl-text-primary">
|
||||
{{ $options.i18n.withLinks.subgroup.description }}
|
||||
</p>
|
||||
</div>
|
||||
</gl-link>
|
||||
</div>
|
||||
<div v-if="canCreateProjects" class="gl-p-3 gl-w-full gl-sm-w-half">
|
||||
<div v-if="canCreateProjects" class="gl-w-full gl-p-3 sm:gl-w-1/2">
|
||||
<gl-link :href="newProjectPath" :class="$options.linkClasses">
|
||||
<div class="svg-content gl-w-13 gl-flex-shrink-0 gl-mr-5">
|
||||
<div class="svg-content gl-mr-5 gl-w-13 gl-shrink-0">
|
||||
<img :src="newProjectIllustration" :alt="$options.i18n.withLinks.project.title" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="gl-text-inherit">{{ $options.i18n.withLinks.project.title }}</h4>
|
||||
<p class="gl-text-body">
|
||||
<p class="gl-text-primary">
|
||||
{{ $options.i18n.withLinks.project.description }}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<ul class="groups-list group-list-tree gl-display-flex gl-flex-direction-column gl-m-0">
|
||||
<ul class="groups-list group-list-tree gl-m-0 gl-flex gl-flex-col">
|
||||
<!-- eslint-disable-next-line vue/no-undef-components -->
|
||||
<group-item
|
||||
v-for="(group, index) in groups"
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ export default {
|
|||
>
|
||||
<div
|
||||
:class="{ 'project-row-contents': !isGroup }"
|
||||
class="group-row-contents gl-flex gl-items-center py-2 pr-3"
|
||||
class="group-row-contents py-2 pr-3 gl-flex gl-items-center"
|
||||
>
|
||||
<div class="folder-toggle-wrap gl-mr-2 !gl-flex gl-items-center">
|
||||
<gl-button
|
||||
|
|
@ -176,11 +176,11 @@ export default {
|
|||
<gl-loading-icon
|
||||
v-if="group.isChildrenLoading"
|
||||
size="lg"
|
||||
class="gl-hidden sm:gl-inline-flex flex-shrink-0 gl-mr-3"
|
||||
class="flex-shrink-0 gl-mr-3 gl-hidden sm:gl-inline-flex"
|
||||
/>
|
||||
<a
|
||||
:class="{ 'sm:gl-flex': !group.isChildrenLoading }"
|
||||
class="gl-hidden gl-text-decoration-none! gl-mr-3"
|
||||
class="gl-mr-3 gl-hidden !gl-no-underline"
|
||||
:href="group.relativePath"
|
||||
:aria-label="group.name"
|
||||
>
|
||||
|
|
@ -192,17 +192,17 @@ export default {
|
|||
:project-name="group.name"
|
||||
/>
|
||||
</a>
|
||||
<div class="group-text-container !gl-flex flex-fill gl-align-items-center">
|
||||
<div class="group-text-container flex-fill !gl-flex gl-items-center">
|
||||
<div class="group-text flex-grow-1 flex-shrink-1">
|
||||
<div
|
||||
class="gl-flex gl-align-items-center gl-flex-wrap title namespace-title gl-font-bold gl-mr-3"
|
||||
class="title namespace-title gl-mr-3 gl-flex gl-flex-wrap gl-items-center gl-font-bold"
|
||||
>
|
||||
<a
|
||||
v-gl-tooltip.bottom
|
||||
data-testid="group-name"
|
||||
:href="group.relativePath"
|
||||
:title="group.fullName"
|
||||
class="no-expand gl-mr-3 gl-text-gray-900! gl-break-anywhere"
|
||||
class="no-expand gl-mr-3 !gl-text-gray-900 gl-break-anywhere"
|
||||
:itemprop="microdata.nameItemprop"
|
||||
>
|
||||
<!-- ending bracket must be by closing tag to prevent -->
|
||||
|
|
@ -219,7 +219,7 @@ export default {
|
|||
<template v-if="shouldShowVisibilityWarning">
|
||||
<gl-button
|
||||
ref="visibilityWarningButton"
|
||||
class="gl-p-1! gl-bg-transparent! gl-mr-3"
|
||||
class="gl-mr-3 !gl-bg-transparent !gl-p-1"
|
||||
category="tertiary"
|
||||
icon="warning"
|
||||
:aria-label="$options.i18n.popoverTitle"
|
||||
|
|
@ -233,7 +233,7 @@ export default {
|
|||
{{ $options.i18n.popoverBody }}
|
||||
<div class="gl-mt-3">
|
||||
<gl-link
|
||||
class="gl-font-sm"
|
||||
class="gl-text-sm"
|
||||
:href="$options.shareProjectsWithGroupsHelpPagePath"
|
||||
>{{ $options.i18n.learnMore }}</gl-link
|
||||
>
|
||||
|
|
@ -244,7 +244,7 @@ export default {
|
|||
{{ group.permission }}
|
||||
</user-access-role-badge>
|
||||
</div>
|
||||
<div v-if="group.description" class="description gl-font-sm gl-mt-1">
|
||||
<div v-if="group.description" class="description gl-mt-1 gl-text-sm">
|
||||
<span
|
||||
v-safe-html:[$options.safeHtmlConfig]="group.description"
|
||||
:itemprop="microdata.descriptionItemprop"
|
||||
|
|
@ -259,13 +259,8 @@ export default {
|
|||
<div v-else-if="group.archived">
|
||||
<gl-badge variant="info">{{ __('Archived') }}</gl-badge>
|
||||
</div>
|
||||
<div
|
||||
class="metadata gl-flex gl-flex-grow-1 gl-flex-shrink-0 gl-flex-wrap justify-content-md-between"
|
||||
>
|
||||
<item-stats
|
||||
:item="group"
|
||||
class="group-stats gl-hidden md:gl-flex gl-align-items-center"
|
||||
/>
|
||||
<div class="metadata justify-content-md-between gl-flex gl-shrink-0 gl-grow gl-flex-wrap">
|
||||
<item-stats :item="group" class="group-stats gl-hidden gl-items-center md:gl-flex" />
|
||||
<item-actions
|
||||
v-if="showActionsMenu"
|
||||
:group="group"
|
||||
|
|
|
|||
|
|
@ -292,7 +292,7 @@ export default {
|
|||
<gl-form-input
|
||||
:id="fields.name.id"
|
||||
v-model="name"
|
||||
class="gl-field-error-ignore gl-h-auto!"
|
||||
class="gl-field-error-ignore !gl-h-auto"
|
||||
required
|
||||
:name="fields.name.name"
|
||||
:placeholder="$options.i18n.inputs.name.placeholder"
|
||||
|
|
@ -315,13 +315,13 @@ export default {
|
|||
<gl-form-group v-if="newSubgroup" class="col-sm-6 gl-pr-0" :label="inputLabels.subgroupPath">
|
||||
<div class="input-group gl-flex-nowrap">
|
||||
<gl-button-group class="gl-w-full">
|
||||
<gl-button class="js-group-namespace-button gl-text-truncate gl-flex-grow-0!" label>
|
||||
<gl-button class="js-group-namespace-button !gl-grow-0 gl-truncate" label>
|
||||
{{ basePath }}
|
||||
</gl-button>
|
||||
|
||||
<gl-dropdown
|
||||
class="js-group-namespace-dropdown gl-flex-grow-1"
|
||||
toggle-class="gl-rounded-top-right-base! gl-rounded-bottom-right-base! gl-w-20"
|
||||
class="js-group-namespace-dropdown gl-grow"
|
||||
toggle-class="!gl-rounded-tr-base !gl-rounded-br-base gl-w-20"
|
||||
@shown="handleDropdownShown"
|
||||
>
|
||||
<template #button-text>
|
||||
|
|
@ -355,7 +355,7 @@ export default {
|
|||
</gl-dropdown>
|
||||
</gl-button-group>
|
||||
|
||||
<div class="gl-align-self-center gl-pl-5">
|
||||
<div class="gl-self-center gl-pl-5">
|
||||
<span class="gl-hidden md:gl-inline">/</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -378,7 +378,7 @@ export default {
|
|||
</template>
|
||||
<gl-form-input
|
||||
:id="fields.path.id"
|
||||
class="gl-field-error-ignore gl-h-auto!"
|
||||
class="gl-field-error-ignore !gl-h-auto"
|
||||
:name="fields.path.name"
|
||||
:value="computedPath"
|
||||
:placeholder="$options.i18n.inputs.path.placeholder"
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ export default {
|
|||
</template>
|
||||
<gl-form-input
|
||||
:id="id"
|
||||
class="gl-field-error-ignore gl-h-auto!"
|
||||
class="gl-field-error-ignore !gl-h-auto"
|
||||
:value="value"
|
||||
:placeholder="$options.i18n.placeholder"
|
||||
:state="state"
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ export default {
|
|||
<pagination-links
|
||||
:change="change"
|
||||
:page-info="pageInfo"
|
||||
class="!gl-flex justify-content-center gl-mt-3"
|
||||
class="justify-content-center gl-mt-3 !gl-flex"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ export default {
|
|||
text: this.$options.i18n.leaveBtnTitle,
|
||||
action: this.onLeaveGroup,
|
||||
extraAttrs: {
|
||||
class: 'gl-text-red-500!',
|
||||
class: '!gl-text-red-500',
|
||||
'data-testid': `leave-group-${this.group.id}-btn`,
|
||||
},
|
||||
};
|
||||
|
|
@ -66,7 +66,7 @@ export default {
|
|||
text: this.$options.i18n.removeBtnTitle,
|
||||
href: this.removeButtonHref,
|
||||
extraAttrs: {
|
||||
class: 'gl-text-red-500!',
|
||||
class: '!gl-text-red-500',
|
||||
'data-testid': `remove-group-${this.group.id}-btn`,
|
||||
},
|
||||
};
|
||||
|
|
@ -87,7 +87,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="gl-display-flex gl-justify-content-end gl-ml-5" @click.stop>
|
||||
<div class="gl-ml-5 gl-flex gl-justify-end" @click.stop>
|
||||
<gl-disclosure-dropdown
|
||||
v-gl-tooltip.hover.focus="$options.i18n.optionsDropdownTitle"
|
||||
icon="ellipsis_v"
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ export default {
|
|||
data-testid="star-count"
|
||||
icon-name="star"
|
||||
/>
|
||||
<div v-if="isProject" class="last-updated gl-font-sm">
|
||||
<div v-if="isProject" class="last-updated gl-text-sm">
|
||||
<time-ago-tooltip :time="item.lastActivityAt" tooltip-placement="bottom" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<span class="item-type-icon gl-display-inline-block gl-text-secondary">
|
||||
<span class="item-type-icon gl-inline-block gl-text-secondary">
|
||||
<gl-icon :name="iconClass" />
|
||||
</span>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -249,7 +249,7 @@ export default {
|
|||
>.
|
||||
</template>
|
||||
</gl-form-fields>
|
||||
<div class="gl-display-flex gl-gap-3">
|
||||
<div class="gl-flex gl-gap-3">
|
||||
<gl-button
|
||||
type="submit"
|
||||
variant="confirm"
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ export default {
|
|||
return labelPieces.join(', ') || this.label;
|
||||
},
|
||||
toggleClass() {
|
||||
return this.toggleLabel === this.label ? 'gl-text-gray-500!' : '';
|
||||
return this.toggleLabel === this.label ? '!gl-text-gray-500' : '';
|
||||
},
|
||||
selection() {
|
||||
return [
|
||||
|
|
|
|||
|
|
@ -174,13 +174,22 @@ export default {
|
|||
|
||||
return pathLock ? pathLock.user : null;
|
||||
},
|
||||
showForkSuggestion() {
|
||||
canFork() {
|
||||
const { createMergeRequestIn, forkProject } = this.userPermissions;
|
||||
const { canModifyBlob } = this.blobInfo;
|
||||
|
||||
return (
|
||||
this.isLoggedIn && !this.isUsingLfs && !canModifyBlob && createMergeRequestIn && forkProject
|
||||
);
|
||||
return this.isLoggedIn && !this.isUsingLfs && createMergeRequestIn && forkProject;
|
||||
},
|
||||
showSingleFileEditorForkSuggestion() {
|
||||
const { canModifyBlob } = this.blobInfo;
|
||||
return this.canFork && !canModifyBlob;
|
||||
},
|
||||
showWebIdeForkSuggestion() {
|
||||
const { canModifyBlobWithWebIde } = this.blobInfo;
|
||||
|
||||
return this.canFork && !canModifyBlobWithWebIde;
|
||||
},
|
||||
showForkSuggestion() {
|
||||
return this.showSingleFileEditorForkSuggestion || this.showWebIdeForkSuggestion;
|
||||
},
|
||||
forkPath() {
|
||||
const forkPaths = {
|
||||
|
|
@ -265,14 +274,24 @@ export default {
|
|||
if (this.$route?.query?.plain === plain) return;
|
||||
this.$router.push({ path: this.$route.path, query: { ...this.$route.query, plain } });
|
||||
},
|
||||
isIdeTarget(target) {
|
||||
return target === 'ide';
|
||||
},
|
||||
forkSuggestionForSelectedEditor(target) {
|
||||
return this.isIdeTarget(target)
|
||||
? this.showWebIdeForkSuggestion
|
||||
: this.showSingleFileEditorForkSuggestion;
|
||||
},
|
||||
editBlob(target) {
|
||||
if (this.showForkSuggestion) {
|
||||
this.setForkTarget(target);
|
||||
return;
|
||||
}
|
||||
|
||||
const { ideEditPath, editBlobPath } = this.blobInfo;
|
||||
visitUrl(target === 'ide' ? ideEditPath : editBlobPath);
|
||||
const isIdeTarget = this.isIdeTarget(target);
|
||||
const showForkSuggestionForSelectedEditor = this.forkSuggestionForSelectedEditor(target);
|
||||
|
||||
if (showForkSuggestionForSelectedEditor) {
|
||||
this.setForkTarget(target);
|
||||
} else {
|
||||
visitUrl(isIdeTarget ? ideEditPath : editBlobPath);
|
||||
}
|
||||
},
|
||||
setForkTarget(target) {
|
||||
this.forkTarget = target;
|
||||
|
|
@ -311,7 +330,8 @@ export default {
|
|||
:has-render-error="hasRenderError"
|
||||
:show-path="false"
|
||||
:override-copy="true"
|
||||
:show-fork-suggestion="showForkSuggestion"
|
||||
:show-fork-suggestion="showSingleFileEditorForkSuggestion"
|
||||
:show-web-ide-fork-suggestion="showWebIdeForkSuggestion"
|
||||
:show-blame-toggle="true"
|
||||
:project-path="projectPath"
|
||||
:project-id="projectId"
|
||||
|
|
@ -334,7 +354,7 @@ export default {
|
|||
:project-path="projectPath"
|
||||
:is-locked="Boolean(pathLockedByUser)"
|
||||
:can-lock="canLock"
|
||||
:show-fork-suggestion="showForkSuggestion"
|
||||
:show-fork-suggestion="showSingleFileEditorForkSuggestion"
|
||||
:is-using-lfs="isUsingLfs"
|
||||
@fork="setForkTarget('view')"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -44,6 +44,11 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
needsToForkWithWebIde: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
gitpodEnabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
|
|
@ -197,7 +202,7 @@ export default {
|
|||
webIdeAction() {
|
||||
if (!this.showWebIdeButton) return null;
|
||||
|
||||
const handleOptions = this.needsToFork
|
||||
const handleOptions = this.needsToForkWithWebIde
|
||||
? {
|
||||
handle: () => {
|
||||
if (this.disableForkModal) {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ query getBlobInfo(
|
|||
environmentFormattedExternalUrl
|
||||
environmentExternalUrlForRouteMap
|
||||
canModifyBlob
|
||||
canModifyBlobWithWebIde
|
||||
canCurrentUserPushToBranch
|
||||
archived
|
||||
storedExternally
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
- signatures_path = namespace_project_signatures_path(namespace_id: @project.namespace.full_path, project_id: @project.path, id: @last_commit, limit: 1)
|
||||
- content_for :prefetch_asset_tags do
|
||||
- webpack_preload_asset_tag('monaco', prefetch: true)
|
||||
- add_page_startup_graphql_call('repository/blob_info', { projectPath: @project.full_path, ref: current_ref, refType: @ref_type.to_s.upcase, filePath: @blob.path, shouldFetchRawText: @blob.rendered_as_text? && !@blob.rich_viewer })
|
||||
- add_page_startup_graphql_call('repository/blob_info', { projectPath: @project.full_path, ref: current_ref, refType: @ref_type.to_s.upcase.presence, filePath: @blob.path, shouldFetchRawText: @blob.rendered_as_text? && !@blob.rich_viewer })
|
||||
|
||||
.js-signature-container{ data: { 'signatures-path': signatures_path } }
|
||||
|
||||
|
|
|
|||
|
|
@ -64,8 +64,8 @@ instructions sent to the LLM to perform certain tasks.
|
|||
The state of the prompts is the result of weeks of iteration. If you want to
|
||||
change any prompt in the current tool, you must put it behind a feature flag.
|
||||
|
||||
If you have any new or updated prompts, ask members of Duo Chat team or AI Framework team to
|
||||
review, because they have significant experience with them.
|
||||
If you have any new or updated prompts, ask members of [Duo Chat team](https://handbook.gitlab.com/handbook/engineering/development/data-science/ai-powered/duo-chat/)
|
||||
to review, because they have significant experience with them.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
|
|
@ -76,11 +76,14 @@ you find a solution.
|
|||
|
||||
| Problem | Solution |
|
||||
|-----------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| There is no Chat button in the GitLab UI. | Make sure your user is a part of a group with enabled experimental and beta features. |
|
||||
| There is no Chat button in the GitLab UI. | Make sure your user is a part of a group with Premium or Ultimate license and enabled Chat. |
|
||||
| Chat replies with "Forbidden by auth provider" error. | Backend can't access LLMs. Make sure your [AI Gateway](index.md#required-install-ai-gateway) is set up correctly. |
|
||||
| Requests take too long to appear in UI | Consider restarting Sidekiq by running `gdk restart rails-background-jobs`. If that doesn't work, try `gdk kill` and then `gdk start`. Alternatively, you can bypass Sidekiq entirely. To do that temporary alter `Llm::CompletionWorker.perform_async` statements with `Llm::CompletionWorker.perform_inline` |
|
||||
| There is no chat button in GitLab UI when GDK is running on non-SaaS mode | You do not have cloud connector access token record or seat assigned. To create cloud connector access record, in rails console put following code: `CloudConnector::Access.new(data: { available_services: [{ name: "duo_chat", serviceStartTime: ":date_in_the_future" }] }).save`. |
|
||||
|
||||
Please, see also the section on [error codes](#interpreting-gitlab-duo-chat-error-codes) where you can read about codes
|
||||
that Chat sends to assist troubleshooting.
|
||||
|
||||
## Contributing to GitLab Duo Chat
|
||||
|
||||
From the code perspective, Chat is implemented in the similar fashion as other
|
||||
|
|
@ -155,28 +158,50 @@ conversation in GitLab Duo Chat towards predefined areas of interest or concern.
|
|||
|
||||
### Adding a new tool
|
||||
|
||||
To add a new tool:
|
||||
To add a new tool you need to add changes both to [AI Gateway](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist)
|
||||
and Rails Monolith. The main chat prompt is stored and assembled on AI Gateway. Rails side is responsible for assembling
|
||||
required parameters of the prompt and sending them to AI Gateway. AI Gateway is responsible for assembling Chat prompt and
|
||||
selecting Chat tools that are available for user based on their subscription and addon.
|
||||
|
||||
1. Create files for the tool in the `ee/lib/gitlab/llm/chain/tools/` folder. Use existing tools like `issue_identifier` or
|
||||
`resource_reader` as a template.
|
||||
When LLM selects the tool to use, this tool is executed on the Rails side. Tools use different endpoint to make
|
||||
a request to AI Gateway. When you add a new tool, please take into account that AI Gateway works with different clients
|
||||
and GitLab applications that have different versions. That means that old versions of GitLab won't know about a new tool,
|
||||
please contact Duo Chat team if you want to add a new tool. We're working on long-term solution for this [problem](https://gitlab.com/gitlab-org/gitlab/-/issues/466247).
|
||||
|
||||
1. Write a class for the tool that includes:
|
||||
#### Changes in AI Gateway
|
||||
|
||||
- Name and description of what the tool does
|
||||
- Example questions that would use this tool
|
||||
- Instructions for the large language model on how to use the tool to gather information - so the main prompts that
|
||||
this tool is using.
|
||||
1. Create a new class for a tool in `ai_gateway/chat/tools/gitlab.py`. This class should include next properties:
|
||||
|
||||
1. Test and iterate on the prompt using RSpec tests that make real requests to the large language model.
|
||||
- Prompts require trial and error, the non-deterministic nature of working with LLM can be surprising.
|
||||
- Anthropic provides good [guide](https://docs.anthropic.com/claude/docs/intro-to-prompting) on working on prompts.
|
||||
- GitLab [guide](prompts.md) on working with prompts.
|
||||
- `name` of the tool
|
||||
- GitLab `resource` that tool works with
|
||||
- `description` of what the tool does
|
||||
- `example` of question and desired answer
|
||||
|
||||
1. Implement code in the tool to parse the response from the large language model and return it to the zero-shot agent.
|
||||
1. Add tool to `__all__` list of tools in `ai_gateway/chat/tools/gitlab.py`.
|
||||
|
||||
1. Add the new tool name to the `tools` array in `ee/lib/gitlab/llm/completions/chat.rb` so the zero-shot agent knows about it.
|
||||
1. Add tool class to the `DuoChatToolsRegistry` in `ai_gateway/chat/toolset.py` with an appropriate Unit Primitive.
|
||||
|
||||
1. Add tests by adding questions to the test-suite for which the new tool should respond to. Iterate on the prompts as needed.
|
||||
1. Add test for your changes.
|
||||
|
||||
#### Changes in Rails Monolith
|
||||
|
||||
1. Create files for the tool in the `ee/lib/gitlab/llm/chain/tools/` folder. Use existing tools like `issue_reader` or
|
||||
`epic_reader` as a template.
|
||||
|
||||
1. Write a class for the tool that includes instructions for the large language model on how to use the tool
|
||||
to gather information - the main prompts that this tool is using.
|
||||
|
||||
1. Implement code in the tool to parse the response from the large language model and return it to the [chat agent](https://gitlab.com/gitlab-org/gitlab/-/blob/e0220502f1b3459b5a571d510ce5d1826877c3ce/ee/lib/gitlab/llm/chain/agents/single_action_executor.rb).
|
||||
|
||||
1. Add the new tool name to the `tools` array in `ee/lib/gitlab/llm/completions/chat.rb` so the agent knows about it.
|
||||
|
||||
#### Testing all together
|
||||
|
||||
Test and iterate on the prompt using RSpec tests that make real requests to the large language model.
|
||||
|
||||
- Prompts require trial and error, the non-deterministic nature of working with LLM can be surprising.
|
||||
- Anthropic provides good [guide](https://docs.anthropic.com/claude/docs/intro-to-prompting) on working on prompts.
|
||||
- GitLab [guide](prompts.md) on working with prompts.
|
||||
|
||||
The key things to keep in mind are properly instructing the large language model through prompts and tool descriptions,
|
||||
keeping tools self-sufficient, and returning responses to the zero-shot agent. With some trial and error on prompts,
|
||||
|
|
@ -590,57 +615,43 @@ flow of how we construct a Chat prompt:
|
|||
from original GraphQL request and initializes a new instance of
|
||||
`Gitlab::Llm::Completions::Chat` and calls `execute` on it
|
||||
([code](https://gitlab.com/gitlab-org/gitlab/-/blob/55b8eb6ff869e61500c839074f080979cc60f9de/ee/lib/gitlab/llm/completions_factory.rb#L89))
|
||||
1. `Gitlab::Llm::Completions::Chat#execute` calls `Gitlab::Llm::Chain::Agents::ZeroShot::Executor`.
|
||||
1. `Gitlab::Llm::Completions::Chat#execute` calls `Gitlab::Llm::Chain::Agents::SingleActionExecutor`.
|
||||
([code](https://gitlab.com/gitlab-org/gitlab/-/blob/d539f64ce6c5bed72ab65294da3bcebdc43f68c6/ee/lib/gitlab/llm/completions/chat.rb#L128-134))
|
||||
1. `Gitlab::Llm::Chain::Agents::ZeroShot::Executor#execute` calls
|
||||
1. `Gitlab::Llm::Chain::Agents::SingleActionExecutor#execute` calls
|
||||
`execute_streamed_request`, which calls `request`, a method defined in the
|
||||
`AiDependent` concern
|
||||
([code](https://gitlab.com/gitlab-org/gitlab/-/blob/d539f64ce6c5bed72ab65294da3bcebdc43f68c6/ee/lib/gitlab/llm/chain/agents/zero_shot/executor.rb#L85))
|
||||
1. (`*`) `AiDependent#request` pulls the base prompt from `provider_prompt_class.prompt`.
|
||||
For Chat, the provider prompt class is `ZeroShot::Prompts::Anthropic`
|
||||
([code](https://gitlab.com/gitlab-org/gitlab/-/blob/4eb4ce67ccc0fe331ddcce3fcc53e0ec0f47cd76/ee/lib/gitlab/llm/chain/concerns/ai_dependent.rb#L44-46))
|
||||
1. (`*`) `ZeroShot::Prompts::Anthropic.prompt` pulls a base prompt and formats
|
||||
it in the way that Anthropic expects it for the
|
||||
[Text Completions API](https://docs.anthropic.com/claude/reference/complete_post)
|
||||
([code](https://gitlab.com/gitlab-org/gitlab/-/blob/4eb4ce67ccc0fe331ddcce3fcc53e0ec0f47cd76/ee/lib/gitlab/llm/chain/agents/zero_shot/prompts/anthropic.rb#L13-24))
|
||||
1. (`*`) As part of constructing the prompt for Anthropic,
|
||||
`ZeroShot::Prompts::Anthropic.prompt` makes a call to the `base_prompt` class
|
||||
method, which is defined in `ZeroShot::Prompts::Base`
|
||||
([code](https://gitlab.com/gitlab-org/gitlab/-/blob/4eb4ce67ccc0fe331ddcce3fcc53e0ec0f47cd76/ee/lib/gitlab/llm/chain/agents/zero_shot/prompts/base.rb#L10-19))
|
||||
1. (`*`) `ZeroShot::Prompts::Base.base_prompt` calls
|
||||
`Utils::Prompt.no_role_text` and passes `prompt_version` to the method call.
|
||||
The `prompt_version` option resolves to `PROMPT_TEMPLATE` from
|
||||
`ZeroShot::Executor`
|
||||
([code](https://gitlab.com/gitlab-org/gitlab/-/blob/e88256b1acc0d70ffc643efab99cad9190529312/ee/lib/gitlab/llm/chain/agents/zero_shot/executor.rb#L143))
|
||||
1. (`*`) `PROMPT_TEMPLATE` is where the tools available and definitions for each
|
||||
tool are interpolated into the zero shot prompt using the `format` method.
|
||||
([code](https://gitlab.com/gitlab-org/gitlab/-/blob/e88256b1acc0d70ffc643efab99cad9190529312/ee/lib/gitlab/llm/chain/agents/zero_shot/executor.rb#L200-237)
|
||||
1. (`*`) The `PROMPT_TEMPLATE` is interpolated into the `default_system_prompt`,
|
||||
defined
|
||||
[(here)](https://gitlab.com/gitlab-org/gitlab/-/blob/e88256b1acc0d70ffc643efab99cad9190529312/ee/lib/gitlab/llm/chain/utils/prompt.rb#L54-73)
|
||||
in the `ZeroShot::Prompts::Base.base_prompt` method call, and that whole big
|
||||
prompt string is sent to `ai_request.request`
|
||||
([code](https://gitlab.com/gitlab-org/gitlab/-/blob/e88256b1acc0d70ffc643efab99cad9190529312/ee/lib/gitlab/llm/chain/concerns/ai_dependent.rb#L19))
|
||||
1. `ai_request` is defined in `Llm::Completions::Chat` and evaluates to either
|
||||
`AiGateway` or `Anthropic` depending on the presence of a feature flag. On
|
||||
production, we use `AiGateway` so this documentation follows that codepath.
|
||||
([code](https://gitlab.com/gitlab-org/gitlab/-/blob/3cd5e5bb3059d6aa9505d21a59cba57fec356473/ee/lib/gitlab/llm/completions/chat.rb#L42)
|
||||
1. The `SingleActionExecutor#prompt_options` method assembles all prompt parameters for the AI Gateway request
|
||||
([code](https://gitlab.com/gitlab-org/gitlab/-/blob/971d07aa37d9f300b108ed66304505f2d7022841/ee/lib/gitlab/llm/chain/agents/single_action_executor.rb#L120-120))
|
||||
1. `ai_request` is defined in `Llm::Completions::Chat` and evaluates to
|
||||
`AiGateway`([code](https://gitlab.com/gitlab-org/gitlab/-/blob/971d07aa37d9f300b108ed66304505f2d7022841/ee/lib/gitlab/llm/completions/chat.rb#L51-51))
|
||||
1. `ai_request.request` routes to `Llm::Chain::Requests::AiGateway#request`,
|
||||
which calls `ai_client.stream`
|
||||
([code](https://gitlab.com/gitlab-org/gitlab/-/blob/e88256b1acc0d70ffc643efab99cad9190529312/ee/lib/gitlab/llm/chain/requests/ai_gateway.rb#L20-27))
|
||||
([code](https://gitlab.com/gitlab-org/gitlab/-/blob/e88256b1acc0d70ffc643efab99cad9190529312/ee/lib/gitlab/llm/chain/requests/ai_gateway.rb#L20-27))
|
||||
1. `ai_client.stream` routes to `Gitlab::Llm::AiGateway::Client#stream`, which
|
||||
makes an API request to the AI Gateway `/v1/chat/agent` endpoint
|
||||
makes an API request to the AI Gateway `/v2/chat/agent` endpoint
|
||||
([code](https://gitlab.com/gitlab-org/gitlab/-/blob/e88256b1acc0d70ffc643efab99cad9190529312/ee/lib/gitlab/llm/ai_gateway/client.rb#L64-82))
|
||||
1. AI Gateway receives the request
|
||||
([code](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist/-/blob/e6f55d143ecb5409e8ca4fefc042e590e5a95158/ai_gateway/api/v2/chat/agent.py#L43-43))
|
||||
1. AI Gateway gets the list of tools available for user
|
||||
([code](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist/-/blob/e6f55d143ecb5409e8ca4fefc042e590e5a95158/ai_gateway/chat/toolset.py#L43-43))
|
||||
1. AI GW gets definitions for each tool
|
||||
([code](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist/-/blob/e6f55d143ecb5409e8ca4fefc042e590e5a95158/ai_gateway/chat/tools/gitlab.py#L11-11))
|
||||
1. And they are inserted into prompt template alongside other prompt parameters that come from Rails
|
||||
([code](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist/-/blob/e6f55d143ecb5409e8ca4fefc042e590e5a95158/ai_gateway/agents/definitions/chat/react/base.yml#L14-14))
|
||||
1. AI Gateway makes request to LLM and return response to Rails.
|
||||
([code](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist/-/blob/e6f55d143ecb5409e8ca4fefc042e590e5a95158/ai_gateway/api/v2/chat/agent.py#L103-103))
|
||||
1. We've now made our first request to the AI Gateway. If the LLM says that the
|
||||
answer to the first request is a final answer, we
|
||||
[parse the answer](https://gitlab.com/gitlab-org/gitlab/-/blob/e88256b1acc0d70ffc643efab99cad9190529312/ee/lib/gitlab/llm/chain/parsers/chain_of_thought_parser.rb)
|
||||
and return it ([code](https://gitlab.com/gitlab-org/gitlab/-/blob/e88256b1acc0d70ffc643efab99cad9190529312/ee/lib/gitlab/llm/chain/agents/zero_shot/executor.rb#L47))
|
||||
1. (`*`) If the first answer is not final, the "thoughts" and "picked tools"
|
||||
[parse the answer](https://gitlab.com/gitlab-org/gitlab/-/blob/971d07aa37d9f300b108ed66304505f2d7022841/ee/lib/gitlab/llm/chain/parsers/single_action_parser.rb#L41-42)
|
||||
and stream it ([code](https://gitlab.com/gitlab-org/gitlab/-/blob/971d07aa37d9f300b108ed66304505f2d7022841/ee/lib/gitlab/llm/chain/concerns/ai_dependent.rb#L25-25))
|
||||
and return it ([code](https://gitlab.com/gitlab-org/gitlab/-/blob/971d07aa37d9f300b108ed66304505f2d7022841/ee/lib/gitlab/llm/chain/agents/single_action_executor.rb#L46-46))
|
||||
1. If the first answer is not final, the "thoughts" and "picked tools"
|
||||
from the first LLM request are parsed and then the relevant tool class is
|
||||
called.
|
||||
([code](https://gitlab.com/gitlab-org/gitlab/-/blob/e88256b1acc0d70ffc643efab99cad9190529312/ee/lib/gitlab/llm/chain/agents/zero_shot/executor.rb#L56-65))
|
||||
([code](https://gitlab.com/gitlab-org/gitlab/-/blob/971d07aa37d9f300b108ed66304505f2d7022841/ee/lib/gitlab/llm/chain/agents/single_action_executor.rb#L54-54))
|
||||
1. The tool executor classes also include `Concerns::AiDependent` and use the
|
||||
included `request` method similar to how `ZeroShot::Executor` does
|
||||
included `request` method similar to how the chat executor does
|
||||
([example](https://gitlab.com/gitlab-org/gitlab/-/blob/70fca6dbec522cb2218c5dcee66caa908c84271d/ee/lib/gitlab/llm/chain/tools/identifier.rb#L8)).
|
||||
The `request` method uses the same `ai_request` instance
|
||||
that was injected into the `context` in `Llm::Completions::Chat`. For Chat,
|
||||
|
|
@ -649,11 +660,9 @@ flow of how we construct a Chat prompt:
|
|||
`prompt` / `PROMPT_TEMPLATE` than for the first request
|
||||
([Example tool prompt template](https://gitlab.com/gitlab-org/gitlab/-/blob/70fca6dbec522cb2218c5dcee66caa908c84271d/ee/lib/gitlab/llm/chain/tools/issue_identifier/executor.rb#L39-104))
|
||||
1. If the tool answer is not final, the response is added to `agent_scratchpad`
|
||||
and the loop in `ZeroShot::Executor` starts again, adding the additional
|
||||
and the loop in `SingleActionExecutor` starts again, adding the additional
|
||||
context to the request. It loops to up to 10 times until a final answer is reached.
|
||||
|
||||
(`*`) indicates that this step is part of the actual construction of the prompt
|
||||
|
||||
## Interpreting GitLab Duo Chat error codes
|
||||
|
||||
GitLab Duo Chat has error codes with specified meanings to assist in debugging.
|
||||
|
|
|
|||
|
|
@ -19767,9 +19767,24 @@ msgstr ""
|
|||
msgid "DuoProDiscover|Accelerate your path to market"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoProDiscover|Access Chat from the GitLab UI or your preferred IDE."
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoProDiscover|Adopt AI with guardrails"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoProDiscover|Automates repetitive tasks and helps catch bugs early."
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoProDiscover|Chat from any location"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoProDiscover|Code explanation"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoProDiscover|Code refactoring"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoProDiscover|Committed to transparent AI"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -19782,18 +19797,33 @@ msgstr ""
|
|||
msgid "DuoProDiscover|Give your developers a single platform that integrates the best AI model for each use case across the entire workflow, from understanding code to fixing security vulnerabilities."
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoProDiscover|Helps you understand code by explaining it in natural language."
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoProDiscover|Improve developer experience"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoProDiscover|Read documentation"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoProDiscover|Ship software faster and more securely with AI integrated into your entire DevSecOps lifecycle."
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoProDiscover|Test generation"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoProDiscover|What's new in GitLab Duo Chat"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoProDiscover|Why GitLab Duo?"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoProDiscover|With GitLab Duo, you control which users, projects, and groups can use AI-powered capabilities. Also, your organization's proprietary code and data aren't used to train AI models."
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoProDiscover|Work to improve existing code quality."
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoProTrial|Activate my trial"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -75,13 +75,15 @@ describe('Blob Header Default Actions', () => {
|
|||
it('renders the WebIdeLink component with the correct props', async () => {
|
||||
const { ideEditPath, editBlobPath, gitpodBlobUrl, pipelineEditorPath } = Blob;
|
||||
const showForkSuggestion = false;
|
||||
await createComponent({ propsData: { showForkSuggestion } });
|
||||
const showWebIdeForkSuggestion = false;
|
||||
await createComponent({ propsData: { showForkSuggestion, showWebIdeForkSuggestion } });
|
||||
|
||||
expect(findWebIdeLink().props()).toMatchObject({
|
||||
showEditButton: true,
|
||||
editUrl: editBlobPath,
|
||||
webIdeUrl: ideEditPath,
|
||||
needsToFork: showForkSuggestion,
|
||||
needsToForkWithWebIde: showWebIdeForkSuggestion,
|
||||
showPipelineEditorButton: Boolean(pipelineEditorPath),
|
||||
pipelineEditorUrl: pipelineEditorPath,
|
||||
gitpodUrl: gitpodBlobUrl,
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ describe('ItemActions', () => {
|
|||
it('renders component template correctly', () => {
|
||||
createComponent();
|
||||
|
||||
expect(wrapper.classes()).toContain('gl-display-flex', 'gl-justify-content-end', 'gl-ml-5');
|
||||
expect(wrapper.classes()).toContain('gl-flex', 'gl-content-end', 'gl-ml-5');
|
||||
});
|
||||
|
||||
it('renders "Edit" group button with correct attribute values', () => {
|
||||
|
|
|
|||
|
|
@ -544,24 +544,27 @@ describe('Blob content viewer component', () => {
|
|||
});
|
||||
|
||||
it.each`
|
||||
loggedIn | canModifyBlob | createMergeRequestIn | forkProject | showForkSuggestion
|
||||
${true} | ${false} | ${true} | ${true} | ${true}
|
||||
${false} | ${false} | ${true} | ${true} | ${false}
|
||||
${true} | ${true} | ${false} | ${true} | ${false}
|
||||
${true} | ${true} | ${true} | ${false} | ${false}
|
||||
loggedIn | canModifyBlob | isUsingLfs | createMergeRequestIn | forkProject | showSingleFileEditorForkSuggestion
|
||||
${true} | ${true} | ${false} | ${true} | ${true} | ${false}
|
||||
${true} | ${false} | ${false} | ${true} | ${true} | ${true}
|
||||
${false} | ${false} | ${false} | ${true} | ${true} | ${false}
|
||||
${true} | ${false} | ${false} | ${false} | ${true} | ${false}
|
||||
${true} | ${false} | ${false} | ${true} | ${false} | ${false}
|
||||
${true} | ${false} | ${true} | ${true} | ${true} | ${false}
|
||||
`(
|
||||
'shows/hides a fork suggestion according to a set of conditions',
|
||||
async ({
|
||||
loggedIn,
|
||||
canModifyBlob,
|
||||
isUsingLfs,
|
||||
createMergeRequestIn,
|
||||
forkProject,
|
||||
showForkSuggestion,
|
||||
showSingleFileEditorForkSuggestion,
|
||||
}) => {
|
||||
isLoggedIn.mockReturnValueOnce(loggedIn);
|
||||
await createComponent(
|
||||
{
|
||||
blob: { ...simpleViewerMock, canModifyBlob },
|
||||
blob: { ...simpleViewerMock, canModifyBlob, storedExternally: isUsingLfs },
|
||||
createMergeRequestIn,
|
||||
forkProject,
|
||||
},
|
||||
|
|
@ -571,7 +574,42 @@ describe('Blob content viewer component', () => {
|
|||
findBlobHeader().vm.$emit('edit', 'simple');
|
||||
await nextTick();
|
||||
|
||||
expect(findForkSuggestion().exists()).toBe(showForkSuggestion);
|
||||
expect(findForkSuggestion().exists()).toBe(showSingleFileEditorForkSuggestion);
|
||||
},
|
||||
);
|
||||
|
||||
it.each`
|
||||
loggedIn | canModifyBlobWithWebIde | isUsingLfs | createMergeRequestIn | forkProject | showWebIdeForkSuggestion
|
||||
${true} | ${true} | ${false} | ${true} | ${true} | ${false}
|
||||
${true} | ${false} | ${false} | ${true} | ${true} | ${true}
|
||||
${false} | ${false} | ${false} | ${true} | ${true} | ${false}
|
||||
${true} | ${false} | ${false} | ${false} | ${true} | ${false}
|
||||
${true} | ${false} | ${false} | ${true} | ${false} | ${false}
|
||||
${true} | ${false} | ${true} | ${true} | ${true} | ${false}
|
||||
`(
|
||||
'shows/hides a fork suggestion for WebIDE according to a set of conditions',
|
||||
async ({
|
||||
loggedIn,
|
||||
canModifyBlobWithWebIde,
|
||||
isUsingLfs,
|
||||
createMergeRequestIn,
|
||||
forkProject,
|
||||
showWebIdeForkSuggestion,
|
||||
}) => {
|
||||
isLoggedIn.mockReturnValueOnce(loggedIn);
|
||||
await createComponent(
|
||||
{
|
||||
blob: { ...simpleViewerMock, canModifyBlobWithWebIde, storedExternally: isUsingLfs },
|
||||
createMergeRequestIn,
|
||||
forkProject,
|
||||
},
|
||||
mount,
|
||||
);
|
||||
|
||||
findBlobHeader().vm.$emit('edit', 'ide');
|
||||
await nextTick();
|
||||
|
||||
expect(findForkSuggestion().exists()).toBe(showWebIdeForkSuggestion);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export const simpleViewerMock = {
|
|||
environmentFormattedExternalUrl: '',
|
||||
environmentExternalUrlForRouteMap: '',
|
||||
canModifyBlob: true,
|
||||
canModifyBlobWithWebIde: true,
|
||||
canCurrentUserPushToBranch: true,
|
||||
archived: false,
|
||||
storedExternally: false,
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ describe('vue_shared/components/web_ide_link', () => {
|
|||
expectedActions: [ACTION_WEB_IDE_EDIT_FORK, ACTION_EDIT],
|
||||
},
|
||||
{
|
||||
props: { needsToFork: true },
|
||||
props: { needsToFork: true, needsToForkWithWebIde: true },
|
||||
expectedActions: [ACTION_WEB_IDE_CONFIRM_FORK, ACTION_EDIT_CONFIRM_FORK],
|
||||
},
|
||||
{
|
||||
|
|
@ -321,7 +321,12 @@ describe('vue_shared/components/web_ide_link', () => {
|
|||
it.each(testActions)(
|
||||
'emits the correct event when an action handler is called',
|
||||
({ props, expectedEventPayload }) => {
|
||||
createComponent({ ...props, needsToFork: true, disableForkModal: true });
|
||||
createComponent({
|
||||
...props,
|
||||
needsToFork: true,
|
||||
needsToForkWithWebIde: true,
|
||||
disableForkModal: true,
|
||||
});
|
||||
|
||||
findDisclosureDropdownItems().at(0).props().item.handle();
|
||||
|
||||
|
|
@ -330,7 +335,7 @@ describe('vue_shared/components/web_ide_link', () => {
|
|||
);
|
||||
|
||||
it.each(testActions)('renders the fork confirmation modal', ({ props }) => {
|
||||
createComponent({ ...props, needsToFork: true });
|
||||
createComponent({ ...props, needsToFork: true, needsToForkWithWebIde: true });
|
||||
|
||||
expect(findForkConfirmModal().exists()).toBe(true);
|
||||
expect(findForkConfirmModal().props()).toEqual({
|
||||
|
|
@ -341,7 +346,10 @@ describe('vue_shared/components/web_ide_link', () => {
|
|||
});
|
||||
|
||||
it.each(testActions)('opens the modal when the button is clicked', async ({ props }) => {
|
||||
createComponent({ ...props, needsToFork: true }, { mountFn: mountExtended });
|
||||
createComponent(
|
||||
{ ...props, needsToFork: true, needsToForkWithWebIde: true },
|
||||
{ mountFn: mountExtended },
|
||||
);
|
||||
|
||||
findDisclosureDropdownItems().at(0).props().item.handle();
|
||||
|
||||
|
|
|
|||
|
|
@ -461,7 +461,7 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler, feature_category: :se
|
|||
expect do
|
||||
receiver.execute
|
||||
end.to enqueue_mail_with(Notify, :service_desk_thank_you_email)
|
||||
.and(not_enqueue_mail_with(Notify, :note_issue_email))
|
||||
.and(not_enqueue_mail_with(Notify, :service_desk_new_note_email))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue