Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-08-04 00:08:37 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-08-04 00:08:37 +0300
commit01fa7c10d9c301ca09e40ce2b2e70514cd3f1053 (patch)
treed0063186a2fad2f3f6ed6e56f72e367cc43097d3
parent9be457ffc1727f6a942a68c16e47ca0bcaa2f64a (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.eslintrc.yml5
-rw-r--r--app/assets/javascripts/ci/ci_variable_list/components/ci_variable_settings.vue1
-rw-r--r--app/assets/javascripts/ci/ci_variable_list/components/ci_variable_table.vue397
-rw-r--r--app/assets/javascripts/repository/index.js8
-rw-r--r--app/assets/stylesheets/framework/new_card.scss3
-rw-r--r--app/assets/stylesheets/pages/settings.scss1
-rw-r--r--app/mailers/emails/merge_requests.rb2
-rw-r--r--app/views/admin/application_settings/ci_cd.html.haml2
-rw-r--r--app/views/ci/variables/_index.html.haml6
-rw-r--r--app/views/projects/_files.html.haml2
-rw-r--r--app/views/projects/tree/_tree_header.html.haml4
-rw-r--r--data/deprecations/16-3-deprecate-twitter-saas.yml10
-rw-r--r--data/deprecations/16-3-deprecate-twitter-sm.yml10
-rw-r--r--doc/development/ai_features.md47
-rw-r--r--doc/integration/twitter.md9
-rw-r--r--doc/update/deprecations.md28
-rw-r--r--locale/gitlab.pot12
-rw-r--r--spec/frontend/ci/ci_variable_list/components/ci_variable_table_spec.js25
18 files changed, 374 insertions, 198 deletions
diff --git a/.eslintrc.yml b/.eslintrc.yml
index c9ae61e75a5..527e94d4101 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -148,11 +148,6 @@ rules:
methods: 'sanitize'
overrides:
- files:
- - 'ee/**/*'
- # See https://gitlab.com/gitlab-org/gitlab/-/issues/360551
- rules:
- vue/multi-word-component-names: off
- - files:
- '{,ee/,jh/}spec/frontend*/**/*'
rules:
'@gitlab/require-i18n-strings': off
diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_settings.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_settings.vue
index 4faec24e19d..f4e1da9b34f 100644
--- a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_settings.vue
+++ b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_settings.vue
@@ -117,6 +117,7 @@ export default {
@handle-prev-page="$emit('handle-prev-page')"
@handle-next-page="$emit('handle-next-page')"
@set-selected-variable="setSelectedVariable"
+ @delete-variable="deleteVariable"
@sort-changed="(val) => $emit('sort-changed', val)"
/>
<ci-variable-modal
diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_table.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_table.vue
index ec7a921664f..a14cd1e387a 100644
--- a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_table.vue
+++ b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_table.vue
@@ -3,11 +3,14 @@ import {
GlAlert,
GlBadge,
GlButton,
+ GlCard,
+ GlIcon,
GlLoadingIcon,
GlModalDirective,
GlKeysetPagination,
GlLink,
GlTable,
+ GlModal,
GlTooltipDirective,
} from '@gitlab/ui';
import { __, s__, sprintf } from '~/locale';
@@ -45,9 +48,8 @@ export default {
},
{
key: 'actions',
- label: '',
- tdClass: 'text-right',
- thClass: 'gl-w-5p',
+ label: __('Actions'),
+ thClass: 'gl-text-right',
},
],
inheritedVarsFields: [
@@ -73,10 +75,13 @@ export default {
GlAlert,
GlBadge,
GlButton,
+ GlCard,
GlKeysetPagination,
GlLink,
+ GlIcon,
GlLoadingIcon,
GlTable,
+ GlModal,
},
directives: {
GlModalDirective,
@@ -84,6 +89,14 @@ export default {
},
mixins: [glFeatureFlagsMixin()],
inject: ['isInheritedGroupVars'],
+ i18n: {
+ title: s__('CiVariables|CI/CD Variables'),
+ addButton: s__('CiVariables|Add variable'),
+ editButton: __('Edit'),
+ deleteButton: __('Delete'),
+ modalDeleteTitle: s__('CiVariables|Delete variable'),
+ modalDeleteMessage: s__('CiVariables|Do you want to delete the variable %{key}?'),
+ },
props: {
entity: {
type: String,
@@ -107,6 +120,20 @@ export default {
required: true,
},
},
+ deleteModal: {
+ actionPrimary: {
+ text: __('Delete'),
+ attributes: {
+ variant: 'danger',
+ },
+ },
+ actionSecondary: {
+ text: __('Cancel'),
+ attributes: {
+ variant: 'default',
+ },
+ },
+ },
data() {
return {
areValuesHidden: true,
@@ -165,6 +192,9 @@ export default {
setSelectedVariable(index = -1) {
this.$emit('set-selected-variable', this.variables[index] ?? null);
},
+ deleteSelectedVariable(index = -1) {
+ this.$emit('delete-variable', this.variables[index] ?? null);
+ },
getAttributes(item) {
const attributes = [];
if (item.variableType === variableTypes.fileType) {
@@ -181,188 +211,219 @@ export default {
}
return attributes;
},
+ removeVariableMessage(key) {
+ return sprintf(this.$options.i18n.modalDeleteMessage, {
+ key,
+ });
+ },
},
maximumVariableLimitReached: MAXIMUM_VARIABLE_LIMIT_REACHED,
};
</script>
<template>
- <div class="ci-variable-table" :data-testid="tableDataTestId">
- <gl-loading-icon v-if="isLoading" />
- <gl-alert
- v-if="showAlert"
- :dismissible="false"
- :title="$options.maximumVariableLimitReached"
- variant="info"
- >
- {{ exceedsVariableLimitText }}
- </gl-alert>
- <div
- v-if="showPagination && !isInheritedGroupVars"
- class="ci-variable-actions gl-display-flex gl-justify-content-end gl-my-3"
+ <div>
+ <gl-card
+ class="gl-new-card ci-variable-table"
+ header-class="gl-new-card-header"
+ body-class="gl-new-card-body gl-px-0"
+ :data-testid="tableDataTestId"
>
- <gl-button v-if="!isTableEmpty" @click="toggleHiddenState">{{ valuesButtonText }}</gl-button>
- <gl-button
- v-gl-modal-directive="$options.modalId"
- class="gl-mx-3"
- data-qa-selector="add_ci_variable_button"
- variant="confirm"
- category="primary"
- :aria-label="__('Add')"
- :disabled="exceedsVariableLimit"
- @click="setSelectedVariable()"
- >{{ __('Add variable') }}</gl-button
- >
- </div>
- <gl-table
- v-if="!isLoading"
- :fields="fields"
- :items="variablesWithAttributes"
- tbody-tr-class="js-ci-variable-row"
- sort-by="key"
- sort-direction="asc"
- stacked="lg"
- fixed
- show-empty
- sort-icon-left
- no-sort-reset
- no-local-sorting
- @sort-changed="(val) => $emit('sort-changed', val)"
- >
- <template #table-colgroup="scope">
- <col v-for="field in scope.fields" :key="field.key" :style="field.customStyle" />
- </template>
- <template #cell(key)="{ item }">
- <div
- class="gl-display-flex gl-align-items-flex-start gl-justify-content-end gl-lg-justify-content-start gl-mr-n3"
- >
- <span
- :id="`ci-variable-key-${item.id}`"
- class="gl-display-inline-block gl-max-w-full gl-word-break-word"
- >{{ item.key }}</span
- >
+ <template #header>
+ <div class="gl-new-card-title-wrapper">
+ <h5 class="gl-new-card-title">{{ $options.i18n.title }}</h5>
+ <span class="gl-new-card-count">
+ <gl-icon name="code" class="gl-mr-2" />
+ {{ variables.length }}
+ </span>
+ </div>
+ <div v-if="!isInheritedGroupVars" class="gl-new-card-actions gl-font-size-0">
<gl-button
- v-gl-tooltip
+ v-if="!isTableEmpty"
category="tertiary"
- icon="copy-to-clipboard"
- class="gl-my-n3 gl-ml-2"
- :title="__('Copy key')"
- :data-clipboard-text="item.key"
- :aria-label="__('Copy to clipboard')"
- />
- </div>
- </template>
- <template v-if="!isInheritedGroupVars" #cell(value)="{ item }">
- <div
- class="gl-display-flex gl-align-items-flex-start gl-justify-content-end gl-lg-justify-content-start gl-mr-n3"
- >
- <span v-if="areValuesHidden" data-testid="hiddenValue">*****</span>
- <span
- v-else
- :id="`ci-variable-value-${item.id}`"
- class="gl-display-inline-block gl-max-w-full gl-text-truncate"
- data-testid="revealedValue"
- >{{ item.value }}</span
+ size="small"
+ class="gl-mr-3"
+ @click="toggleHiddenState"
+ >{{ valuesButtonText }}</gl-button
>
<gl-button
- v-gl-tooltip
- category="tertiary"
- icon="copy-to-clipboard"
- class="gl-my-n3 gl-ml-2"
- :title="__('Copy value')"
- :data-clipboard-text="item.value"
- :aria-label="__('Copy to clipboard')"
- />
+ v-gl-modal-directive="$options.modalId"
+ size="small"
+ :disabled="exceedsVariableLimit"
+ data-qa-selector="add_ci_variable_button"
+ data-testid="add-ci-variable-button"
+ @click="setSelectedVariable()"
+ >{{ $options.i18n.addButton }}</gl-button
+ >
</div>
</template>
- <template #cell(attributes)="{ item }">
- <span data-testid="ci-variable-table-row-attributes">
- <gl-badge
- v-for="attribute in item.attributes"
- :key="`${item.key}-${attribute}`"
- class="gl-mr-2"
- variant="info"
- size="sm"
+
+ <gl-loading-icon v-if="isLoading" class="gl-p-4" />
+ <gl-alert
+ v-if="showAlert"
+ :dismissible="false"
+ :title="$options.maximumVariableLimitReached"
+ variant="info"
+ >
+ {{ exceedsVariableLimitText }}
+ </gl-alert>
+ <gl-table
+ v-if="!isLoading"
+ :fields="fields"
+ :items="variablesWithAttributes"
+ tbody-tr-class="js-ci-variable-row"
+ sort-by="key"
+ sort-direction="asc"
+ stacked="md"
+ fixed
+ show-empty
+ sort-icon-left
+ no-sort-reset
+ no-local-sorting
+ @sort-changed="(val) => $emit('sort-changed', val)"
+ >
+ <template #table-colgroup="scope">
+ <col v-for="field in scope.fields" :key="field.key" :style="field.customStyle" />
+ </template>
+ <template #cell(key)="{ item }">
+ <div
+ class="gl-display-flex gl-align-items-flex-start gl-justify-content-end gl-lg-justify-content-start gl-mr-n3"
>
- {{ attribute }}
- </gl-badge>
- </span>
- </template>
- <template #cell(environmentScope)="{ item }">
- <div
- class="gl-display-flex gl-align-items-flex-start gl-justify-content-end gl-lg-justify-content-start gl-mr-n3"
- >
- <span
- :id="`ci-variable-env-${item.id}`"
- class="gl-display-inline-block gl-max-w-full gl-word-break-word"
- >{{ convertEnvironmentScopeValue(item.environmentScope) }}</span
+ <span
+ :id="`ci-variable-key-${item.id}`"
+ class="gl-display-inline-block gl-max-w-full gl-word-break-word"
+ >{{ item.key }}</span
+ >
+ <gl-button
+ v-gl-tooltip
+ category="tertiary"
+ icon="copy-to-clipboard"
+ class="gl-my-n3 gl-ml-2"
+ :title="__('Copy key')"
+ :data-clipboard-text="item.key"
+ :aria-label="__('Copy to clipboard')"
+ />
+ </div>
+ </template>
+ <template v-if="!isInheritedGroupVars" #cell(value)="{ item }">
+ <div
+ class="gl-display-flex gl-align-items-flex-start gl-justify-content-end gl-lg-justify-content-start gl-mr-n3"
>
- <gl-button
- v-gl-tooltip
- category="tertiary"
- icon="copy-to-clipboard"
- class="gl-my-n3 gl-ml-2"
- :title="__('Copy environment')"
- :data-clipboard-text="convertEnvironmentScopeValue(item.environmentScope)"
- :aria-label="__('Copy to clipboard')"
- />
- </div>
- </template>
- <template v-if="isInheritedGroupVars" #cell(group)="{ item }">
- <div
- class="gl-display-flex gl-align-items-flex-start gl-justify-content-end gl-lg-justify-content-start gl-mr-n3"
- >
- <gl-link
- :id="`ci-variable-group-${item.id}`"
- data-testid="ci-variable-table-row-cicd-path"
- class="gl-display-inline-block gl-max-w-full gl-word-break-word"
- :href="item.groupCiCdSettingsPath"
+ <span v-if="areValuesHidden" data-testid="hiddenValue">*****</span>
+ <span
+ v-else
+ :id="`ci-variable-value-${item.id}`"
+ class="gl-display-inline-block gl-max-w-full gl-text-truncate"
+ data-testid="revealedValue"
+ >{{ item.value }}</span
+ >
+ <gl-button
+ v-gl-tooltip
+ category="tertiary"
+ icon="copy-to-clipboard"
+ class="gl-my-n3 gl-ml-2"
+ :title="__('Copy value')"
+ :data-clipboard-text="item.value"
+ :aria-label="__('Copy to clipboard')"
+ />
+ </div>
+ </template>
+ <template #cell(attributes)="{ item }">
+ <span data-testid="ci-variable-table-row-attributes">
+ <gl-badge
+ v-for="attribute in item.attributes"
+ :key="`${item.key}-${attribute}`"
+ class="gl-mr-2"
+ variant="info"
+ size="sm"
+ >
+ {{ attribute }}
+ </gl-badge>
+ </span>
+ </template>
+ <template #cell(environmentScope)="{ item }">
+ <div
+ class="gl-display-flex gl-align-items-flex-start gl-justify-content-end gl-lg-justify-content-start gl-mr-n3"
>
- {{ item.groupName }}
- </gl-link>
- </div>
- </template>
- <template v-if="!isInheritedGroupVars" #cell(actions)="{ item }">
- <gl-button
- v-gl-modal-directive="$options.modalId"
- icon="pencil"
- :aria-label="__('Edit')"
- data-qa-selector="edit_ci_variable_button"
- @click="setSelectedVariable(item.index)"
- />
- </template>
- <template #empty>
- <p class="gl-text-center gl-py-6 gl-text-black-normal gl-mb-0">
- {{ __('There are no variables yet.') }}
- </p>
- </template>
- </gl-table>
- <gl-alert
- v-if="showAlert"
- :dismissible="false"
- :title="$options.maximumVariableLimitReached"
- variant="info"
- >
- {{ exceedsVariableLimitText }}
- </gl-alert>
+ <span
+ :id="`ci-variable-env-${item.id}`"
+ class="gl-display-inline-block gl-max-w-full gl-word-break-word"
+ >{{ convertEnvironmentScopeValue(item.environmentScope) }}</span
+ >
+ <gl-button
+ v-gl-tooltip
+ category="tertiary"
+ icon="copy-to-clipboard"
+ class="gl-my-n3 gl-ml-2"
+ :title="__('Copy environment')"
+ :data-clipboard-text="convertEnvironmentScopeValue(item.environmentScope)"
+ :aria-label="__('Copy to clipboard')"
+ />
+ </div>
+ </template>
+ <template v-if="isInheritedGroupVars" #cell(group)="{ item }">
+ <div
+ class="gl-display-flex gl-align-items-flex-start gl-justify-content-end gl-lg-justify-content-start gl-mr-n3"
+ >
+ <gl-link
+ :id="`ci-variable-group-${item.id}`"
+ data-testid="ci-variable-table-row-cicd-path"
+ class="gl-display-inline-block gl-max-w-full gl-word-break-word"
+ :href="item.groupCiCdSettingsPath"
+ >
+ {{ item.groupName }}
+ </gl-link>
+ </div>
+ </template>
+ <template v-if="!isInheritedGroupVars" #cell(actions)="{ item }">
+ <div class="gl-display-flex gl-justify-content-end gl-mt-n2 gl-mb-n2">
+ <gl-button
+ v-gl-modal-directive="$options.modalId"
+ icon="pencil"
+ size="small"
+ class="gl-mr-3"
+ :aria-label="$options.i18n.editButton"
+ data-qa-selector="edit_ci_variable_button"
+ @click="setSelectedVariable(item.index)"
+ />
+ <gl-button
+ v-gl-modal-directive="`delete-variable-${item.index}`"
+ variant="danger"
+ category="secondary"
+ icon="remove"
+ size="small"
+ :aria-label="$options.i18n.deleteButton"
+ data-qa-selector="delete_ci_variable_button"
+ />
+ <gl-modal
+ ref="modal"
+ :modal-id="`delete-variable-${item.index}`"
+ :title="$options.i18n.modalDeleteTitle"
+ :action-primary="$options.deleteModal.actionPrimary"
+ :action-secondary="$options.deleteModal.actionSecondary"
+ @primary="deleteSelectedVariable(item.index)"
+ >
+ {{ removeVariableMessage(item.key) }}
+ </gl-modal>
+ </div>
+ </template>
+ <template #empty>
+ <p class="gl-text-secondary gl-text-center gl-py-1 gl-mb-0">
+ {{ __('There are no variables yet.') }}
+ </p>
+ </template>
+ </gl-table>
+ <gl-alert
+ v-if="showAlert"
+ :dismissible="false"
+ :title="$options.maximumVariableLimitReached"
+ variant="info"
+ >
+ {{ exceedsVariableLimitText }}
+ </gl-alert>
+ </gl-card>
<div v-if="!isInheritedGroupVars">
- <div v-if="!showPagination" class="ci-variable-actions gl-display-flex gl-mt-5">
- <gl-button
- v-gl-modal-directive="$options.modalId"
- class="gl-mr-3"
- data-qa-selector="add_ci_variable_button"
- variant="confirm"
- category="primary"
- :aria-label="__('Add')"
- :disabled="exceedsVariableLimit"
- @click="setSelectedVariable()"
- >{{ __('Add variable') }}</gl-button
- >
- <gl-button v-if="!isTableEmpty" @click="toggleHiddenState">{{
- valuesButtonText
- }}</gl-button>
- </div>
- <div v-else class="gl-display-flex gl-justify-content-center gl-mt-6">
+ <div v-if="showPagination" class="gl-display-flex gl-justify-content-center gl-mt-5">
<gl-keyset-pagination
v-bind="pageInfo"
:prev-text="__('Previous')"
diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js
index 5c53fc04640..87274e9c8ea 100644
--- a/app/assets/javascripts/repository/index.js
+++ b/app/assets/javascripts/repository/index.js
@@ -230,20 +230,12 @@ export default function setupVueRepositoryList() {
const treeHistoryLinkEl = document.getElementById('js-tree-history-link');
const { historyLink } = treeHistoryLinkEl.dataset;
- let { isProjectOverview } = treeHistoryLinkEl.dataset;
-
- const isProjectOverviewAfterEach = router.afterEach(() => {
- isProjectOverview = false;
- isProjectOverviewAfterEach();
- });
// eslint-disable-next-line no-new
new Vue({
el: treeHistoryLinkEl,
router,
render(h) {
- if (parseBoolean(isProjectOverview) && !this.$route.params.path) return null;
-
return h(
GlButton,
{
diff --git a/app/assets/stylesheets/framework/new_card.scss b/app/assets/stylesheets/framework/new_card.scss
index a1d7761bd88..3fcaab671f3 100644
--- a/app/assets/stylesheets/framework/new_card.scss
+++ b/app/assets/stylesheets/framework/new_card.scss
@@ -97,7 +97,8 @@
// Table adjustments
@mixin new-card-table-adjustments {
tbody > tr {
- &:first-of-type > td[data-label] {
+ &:first-of-type > td[data-label],
+ &:first-of-type > td:first-of-type:last-of-type {
@include gl-border-t-0;
}
diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss
index ad792a6ee50..c6f2b730d9b 100644
--- a/app/assets/stylesheets/pages/settings.scss
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -50,7 +50,6 @@
}
}
-.ci-variable-table,
.deploy-freeze-table,
.ci-secure-files-table {
table {
diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb
index 6678bb563ed..67a445c14ab 100644
--- a/app/mailers/emails/merge_requests.rb
+++ b/app/mailers/emails/merge_requests.rb
@@ -186,5 +186,3 @@ module Emails
end
end
end
-
-Emails::MergeRequests.prepend_mod_with('Emails::MergeRequests')
diff --git a/app/views/admin/application_settings/ci_cd.html.haml b/app/views/admin/application_settings/ci_cd.html.haml
index a9a16f72ebe..9b36a983f26 100644
--- a/app/views/admin/application_settings/ci_cd.html.haml
+++ b/app/views/admin/application_settings/ci_cd.html.haml
@@ -8,7 +8,7 @@
= render 'admin/application_settings/ci/header', expanded: expanded_by_default?
.settings-content
- if ci_variable_protected_by_default?
- %p.settings-message.text-center
+ %p.settings-message.text-center.gl-mb-0
- link_start = '<a href="%{url}">'.html_safe % { url: help_page_path('ci/variables/index', anchor: 'protect-a-cicd-variable') }
= s_('Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default.').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
#js-instance-variables{ data: { endpoint: admin_ci_variables_path, maskable_regex: ci_variable_maskable_regex, protected_by_default: ci_variable_protected_by_default?.to_s} }
diff --git a/app/views/ci/variables/_index.html.haml b/app/views/ci/variables/_index.html.haml
index 5eed4e92386..7e5e8d8f961 100644
--- a/app/views/ci/variables/_index.html.haml
+++ b/app/views/ci/variables/_index.html.haml
@@ -32,7 +32,5 @@
environment_scope_link: help_page_path('ci/environments/index', anchor: 'limit-the-environment-scope-of-a-cicd-variable') } }
- if !@group && @project.group
- .settings-header.border-top.gl-mt-6
- = render 'ci/group_variables/header'
- .settings-content.pr-0
- = render 'ci/group_variables/index'
+ = render 'ci/group_variables/header'
+ = render 'ci/group_variables/index'
diff --git a/app/views/projects/_files.html.haml b/app/views/projects/_files.html.haml
index ba6cf689bdc..cb341ede9de 100644
--- a/app/views/projects/_files.html.haml
+++ b/app/views/projects/_files.html.haml
@@ -15,7 +15,7 @@
#js-code-owners{ data: { branch: @ref, can_view_branch_rules: can_view_branch_rules?, branch_rules_path: branch_rules_path } }
.nav-block.gl-display-flex.gl-xs-flex-direction-column.gl-align-items-stretch
- = render 'projects/tree/tree_header', tree: @tree, is_project_overview: is_project_overview
+ = render 'projects/tree/tree_header', tree: @tree
- if project.forked?
#js-fork-info{ data: vue_fork_divergence_data(project, ref) }
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index c834a0bc818..a4ed19c2fc9 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -1,5 +1,3 @@
-- is_project_overview = local_assigns.fetch(:is_project_overview, false)
-
.tree-ref-container.gl-display-flex.gl-flex-wrap.gl-gap-2.mb-2.mb-md-0
.tree-ref-holder.gl-max-w-26{ data: { qa_selector: 'ref_dropdown_container' } }
#js-tree-ref-switcher{ data: { project_id: @project.id, ref_type: @ref_type.to_s, project_root_path: project_path(@project) } }
@@ -10,7 +8,7 @@
.tree-controls
.d-block.d-sm-flex.flex-wrap.align-items-start.gl-children-ml-sm-3.gl-first-child-ml-sm-0<
= render_if_exists 'projects/tree/lock_link'
- #js-tree-history-link{ data: { history_link: project_commits_path(@project, @ref), is_project_overview: is_project_overview.to_s } }
+ #js-tree-history-link{ data: { history_link: project_commits_path(@project, @ref) } }
= render 'projects/find_file_link'
= render 'shared/web_ide_button', blob: nil
diff --git a/data/deprecations/16-3-deprecate-twitter-saas.yml b/data/deprecations/16-3-deprecate-twitter-saas.yml
new file mode 100644
index 00000000000..3c0150ec65c
--- /dev/null
+++ b/data/deprecations/16-3-deprecate-twitter-saas.yml
@@ -0,0 +1,10 @@
+- title: "Twitter OmniAuth login option is removed from GitLab.com"
+ removal_milestone: "16.3"
+ announcement_milestone: "16.3"
+ breaking_change: true
+ reporter: jessieay
+ stage: Manage
+ issue_url: https://gitlab.com/gitlab-com/Product/-/issues/11417
+ body: | # (required) Do not modify this line, instead modify the lines below.
+ Twitter OAuth 1.0a OmniAuth is being deprecated and removed on GitLab.com in GitLab 16.3 due to low use, lack of gem support, and the lack of a functional sign-in option for this feature. If you sign into GitLab.com with Twitter, you can sign in with a password or another [supported OmniAuth provider](https://gitlab.com/users/sign_in).
+ documentation_url: https://docs.gitlab.com/ee/integration/twitter.html
diff --git a/data/deprecations/16-3-deprecate-twitter-sm.yml b/data/deprecations/16-3-deprecate-twitter-sm.yml
new file mode 100644
index 00000000000..562fce1ff1c
--- /dev/null
+++ b/data/deprecations/16-3-deprecate-twitter-sm.yml
@@ -0,0 +1,10 @@
+- title: "Twitter OmniAuth login option is deprecated from self-managed GitLab"
+ removal_milestone: "17.0"
+ announcement_milestone: "16.3"
+ breaking_change: true
+ reporter: jessieay
+ stage: Manage
+ issue_url: https://gitlab.com/gitlab-com/Product/-/issues/11417
+ body: | # (required) Do not modify this line, instead modify the lines below.
+ Twitter OAuth 1.0a OmniAuth is deprecated and will be removed for self-managed GitLab instances in GitLab 17.0 due to low use and lack of gem support. Use [another supported OmniAuth provider](https://docs.gitlab.com/ee/integration/omniauth.html#supported-providers) instead.
+ documentation_url: https://docs.gitlab.com/ee/integration/twitter.html
diff --git a/doc/development/ai_features.md b/doc/development/ai_features.md
index 79d03ff6c72..aa9051438c8 100644
--- a/doc/development/ai_features.md
+++ b/doc/development/ai_features.md
@@ -138,6 +138,53 @@ To populate the embedding database for GitLab chat:
1. Open a rails console
1. Run [this script](https://gitlab.com/gitlab-com/gl-infra/production/-/issues/10588#note_1373586079) to populate the embedding database
+### Contributing to GitLab Duo Chat
+
+The Chat feature uses a [zero-shot agent](https://gitlab.com/gitlab-org/gitlab/blob/master/ee/lib/gitlab/llm/chain/agents/zero_shot/executor.rb) that includes a system prompt explaining how the large language model should interpret the question and provide an
+answer. The system prompt defines available tools that can be used to gather
+information to answer the user's question.
+
+The zero-shot agent receives the user's question and decides which tools to use to gather information to answer it.
+It then makes a request to the large language model, which decides if it can answer directly or if it needs to use one
+of the defined tools.
+
+The tools each have their own prompt that provides instructions to the large language model on how to use that tool to
+gather information. The tools are designed to be self-sufficient and avoid multiple requests back and forth to
+the large language model.
+
+After the tools have gathered the required information, it is returned to the zero-shot agent, which asks the large language
+model if enough information has been gathered to provide the final answer to the user's question.
+
+#### Adding a new tool
+
+To add a new tool:
+
+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.
+
+1. Write a class for the tool that includes:
+
+ - 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. 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/introduction-to-prompt-design) on working on prompts.
+
+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 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 tests by adding questions to the test-suite for which the new tool should respond to. Iterate on the prompts as needed.
+
+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,
+adding new tools can expand the capabilities of the chat feature.
+
+There are available short [videos](https://www.youtube.com/playlist?list=PL05JrBw4t0KoOK-bm_bwfHaOv-1cveh8i) covering this topic.
+
### Debugging
To gather more insights about the full request, use the `Gitlab::Llm::Logger` file to debug logs.
diff --git a/doc/integration/twitter.md b/doc/integration/twitter.md
index 3edb0f25938..4e1762d0268 100644
--- a/doc/integration/twitter.md
+++ b/doc/integration/twitter.md
@@ -4,7 +4,14 @@ group: Authentication and Authorization
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
-# Twitter OAuth 1.0a OmniAuth Provider **(FREE SELF)**
+# Twitter OAuth 1.0a OmniAuth Provider (deprecated) **(FREE SELF)**
+
+<!--- start_remove The following content will be removed on remove_date: '2024-05-17' -->
+
+WARNING:
+This feature was [deprecated](https://gitlab.com/gitlab-com/Product/-/issues/11417) in GitLab 16.3 and is planned for removal in 17.0. Use [another supported OmniAuth provider](omniauth.md#supported-providers) instead. This change is a breaking change.
+
+<!--- end_remove -->
NOTE:
Twitter OAuth 2.0 support is [not supported](https://gitlab.com/gitlab-org/gitlab/-/issues/366213).
diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md
index 89d7f04b549..47bece45a45 100644
--- a/doc/update/deprecations.md
+++ b/doc/update/deprecations.md
@@ -774,6 +774,20 @@ In some cases, like when a downstream pipeline had the `passed with warnings` st
<div class="deprecation breaking-change" data-milestone="17.0">
+### Twitter OmniAuth login option is deprecated from self-managed GitLab
+
+<div class="deprecation-notes">
+- Announced in GitLab <span class="milestone">16.3</span>
+- Removal in GitLab <span class="milestone">17.0</span> ([breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change))
+- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-com/Product/-/issues/11417).
+</div>
+
+Twitter OAuth 1.0a OmniAuth is deprecated and will be removed for self-managed GitLab instances in GitLab 17.0 due to low use and lack of gem support. Use [another supported OmniAuth provider](https://docs.gitlab.com/ee/integration/omniauth.html#supported-providers) instead.
+
+</div>
+
+<div class="deprecation breaking-change" data-milestone="17.0">
+
### Unified approval rules are deprecated
<div class="deprecation-notes">
@@ -1032,6 +1046,20 @@ The GitLab [License Compliance](https://docs.gitlab.com/ee/user/compliance/licen
| LS template is included but DS template is not | License data from LS job is used | License data from LS job is used | No license data |
</div>
+
+<div class="deprecation breaking-change" data-milestone="16.3">
+
+### Twitter OmniAuth login option is removed from GitLab.com
+
+<div class="deprecation-notes">
+- Announced in GitLab <span class="milestone">16.3</span>
+- Removal in GitLab <span class="milestone">16.3</span> ([breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change))
+- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-com/Product/-/issues/11417).
+</div>
+
+Twitter OAuth 1.0a OmniAuth is being deprecated and removed on GitLab.com in GitLab 16.3 due to low use, lack of gem support, and the lack of a functional sign-in option for this feature. If you sign into GitLab.com with Twitter, you can sign in with a password or another [supported OmniAuth provider](https://gitlab.com/users/sign_in).
+
+</div>
</div>
<div class="milestone-wrapper" data-milestone="16.1">
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 758f6701083..d87aaee2558 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -10083,15 +10083,27 @@ msgstr ""
msgid "CiVariables|Add Variable"
msgstr ""
+msgid "CiVariables|Add variable"
+msgstr ""
+
msgid "CiVariables|Attributes"
msgstr ""
+msgid "CiVariables|CI/CD Variables"
+msgstr ""
+
msgid "CiVariables|Cancel"
msgstr ""
msgid "CiVariables|Cannot use Masked Variable with current value"
msgstr ""
+msgid "CiVariables|Delete variable"
+msgstr ""
+
+msgid "CiVariables|Do you want to delete the variable %{key}?"
+msgstr ""
+
msgid "CiVariables|Environments"
msgstr ""
diff --git a/spec/frontend/ci/ci_variable_list/components/ci_variable_table_spec.js b/spec/frontend/ci/ci_variable_list/components/ci_variable_table_spec.js
index f3f1c5bd2c5..39c03a41660 100644
--- a/spec/frontend/ci/ci_variable_list/components/ci_variable_table_spec.js
+++ b/spec/frontend/ci/ci_variable_list/components/ci_variable_table_spec.js
@@ -1,4 +1,4 @@
-import { GlAlert, GlBadge, GlKeysetPagination } from '@gitlab/ui';
+import { GlAlert, GlBadge, GlKeysetPagination, GlCard, GlIcon } from '@gitlab/ui';
import { sprintf } from '~/locale';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import CiVariableTable from '~/ci/ci_variable_list/components/ci_variable_table.vue';
@@ -36,7 +36,7 @@ describe('Ci variable table', () => {
};
const findRevealButton = () => wrapper.findByText('Reveal values');
- const findAddButton = () => wrapper.findByLabelText('Add');
+ const findAddButton = () => wrapper.findByTestId('add-ci-variable-button');
const findEditButton = () => wrapper.findByLabelText('Edit');
const findEmptyVariablesPlaceholder = () => wrapper.findByText('There are no variables yet.');
const findHiddenValues = () => wrapper.findAllByTestId('hiddenValue');
@@ -50,11 +50,30 @@ describe('Ci variable table', () => {
const findGroupCiCdSettingsLink = (rowIndex) =>
wrapper.findAllByTestId('ci-variable-table-row-cicd-path').at(rowIndex).attributes('href');
const findKeysetPagination = () => wrapper.findComponent(GlKeysetPagination);
+ const findCard = () => wrapper.findComponent(GlCard);
const generateExceedsVariableLimitText = (entity, currentVariableCount, maxVariableLimit) => {
return sprintf(EXCEEDS_VARIABLE_LIMIT_TEXT, { entity, currentVariableCount, maxVariableLimit });
};
+ describe('card', () => {
+ it('displays the correct title', () => {
+ createComponent();
+ expect(findCard().text()).toContain('CI/CD Variables');
+ });
+
+ it('displays the correct icon', () => {
+ createComponent();
+ expect(findCard().findComponent(GlIcon).props('name')).toBe('code');
+ });
+
+ it('displays the number of added CI/CD Variables', () => {
+ const variables = [1, 2, 3];
+ createComponent({ props: { variables } });
+ expect(findCard().text()).toContain(String(variables.length));
+ });
+ });
+
describe.each`
isVariablePagesEnabled | text
${true} | ${'enabled'}
@@ -88,7 +107,7 @@ describe('Ci variable table', () => {
${1} | ${'Value'}
${2} | ${'Attributes'}
${3} | ${'Environments'}
- ${4} | ${''}
+ ${4} | ${'Actions'}
`('renders the $text column', ({ index, text }) => {
expect(findTableColumnText(index)).toEqual(text);
});