Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-07-14 21:07:31 +00:00
parent 9ea23bb2f6
commit 38ec668b55
93 changed files with 1534 additions and 655 deletions

View File

@ -206,7 +206,7 @@ workflow:
GLCI_PUSH_RAILS_TEST_FAILURE_ISSUES_TO_GCS: "false"
variables:
PG_VERSION: "14"
PG_VERSION: "16"
DEFAULT_CI_IMAGE: "${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/ci/${BUILD_OS}-${OS_VERSION}-slim-ruby-${RUBY_VERSION}-golang-${GO_VERSION}-node-${NODE_VERSION}-postgresql-${PG_VERSION}:rubygems-${RUBYGEMS_VERSION}-git-${GIT_VERSION}-lfs-${LFS_VERSION}-chrome-${CHROME_VERSION}-yarn-${YARN_VERSION}-graphicsmagick-${GRAPHICSMAGICK_VERSION}"
DEFAULT_JOB_TAG: "gitlab-org"
GITLAB_LARGE_RUNNER_OPTIONAL: "gitlab-org" # overridden just in gitlab-org/gitlab

View File

@ -1279,12 +1279,9 @@ RSpec/FactoryBot/LocalStaticAssignment:
Rails/TransactionExitStatement:
Enabled: true
Search/AvoidCheckingFinishedOnDeprecatedMigrations:
Search/AvoidCheckingFinishedOnInvalidMigrations:
Include:
- 'ee/app/models/**/*.rb'
- 'ee/lib/elastic/**/*.rb'
- 'ee/lib/gitlab/elastic/**/*.rb'
- 'ee/spec/support/helpers/elasticsearch_helpers.rb'
- 'ee/**/*.rb'
# See https://gitlab.com/gitlab-org/gitlab/-/issues/407233
Cop/ExperimentsTestCoverage:

View File

@ -32,7 +32,6 @@ InternalAffairs/ExampleHeredocDelimiter:
- 'spec/rubocop/cop/scalability/cron_worker_context_spec.rb'
- 'spec/rubocop/cop/scalability/file_uploads_spec.rb'
- 'spec/rubocop/cop/scalability/idempotent_worker_spec.rb'
- 'spec/rubocop/cop/search/avoid_checking_finished_on_deprecated_migrations_spec.rb'
- 'spec/rubocop/cop/search/namespaced_class_spec.rb'
- 'spec/rubocop/cop/sidekiq_api_usage_spec.rb'
- 'spec/rubocop/cop/sidekiq_load_balancing/worker_data_consistency_spec.rb'

View File

@ -60,7 +60,6 @@ InternalAffairs/NodeMatcherDirective:
- 'rubocop/cop/scalability/cron_worker_context.rb'
- 'rubocop/cop/scalability/file_uploads.rb'
- 'rubocop/cop/scalability/idempotent_worker.rb'
- 'rubocop/cop/search/avoid_checking_finished_on_deprecated_migrations.rb'
- 'rubocop/cop/sidekiq_api_usage.rb'
- 'rubocop/cop/sidekiq_load_balancing/worker_data_consistency.rb'
- 'rubocop/cop/sidekiq_options_queue.rb'

View File

@ -117,7 +117,6 @@ InternalAffairs/OnSendWithoutOnCSend:
- 'rubocop/cop/scalability/bulk_perform_with_context.rb'
- 'rubocop/cop/scalability/cron_worker_context.rb'
- 'rubocop/cop/scalability/file_uploads.rb'
- 'rubocop/cop/search/avoid_checking_finished_on_deprecated_migrations.rb'
- 'rubocop/cop/sidekiq_api_usage.rb'
- 'rubocop/cop/sidekiq_options_queue.rb'
- 'rubocop/cop/sidekiq_redis_call.rb'

View File

@ -1 +1 @@
838089751c1ffa6700b328f3060ce869217393fe
e30c5e007ce5c67d4dbbdfdf5e95634d0cc09344

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 B

View File

@ -109,6 +109,11 @@ export default {
required: false,
default: null,
},
isWidget: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
@ -300,7 +305,7 @@ export default {
<template>
<div>
<div class="gl-mt-5">
<p data-testid="runner-token-message">
<p v-if="!isWidget" data-testid="runner-token-message">
<gl-icon name="information-o" variant="info" />
<gl-sprintf :message="tokenMessage">
<template #token>
@ -326,11 +331,12 @@ export default {
</gl-sprintf>
</p>
</div>
<hr />
<hr v-if="!isWidget" />
<!-- start: before you begin -->
<div>
<h2 class="gl-heading-2">{{ $options.i18n.beforeHeading }}</h2>
<h3 v-if="isWidget" class="gl-heading-3">{{ $options.i18n.beforeHeading }}</h3>
<h2 v-else class="gl-heading-2">{{ $options.i18n.beforeHeading }}</h2>
<ul>
<li>
<gl-sprintf :message="$options.i18n.permissionsText">
@ -374,7 +380,8 @@ export default {
<!-- end: before you begin -->
<!-- start: step one -->
<h2 class="gl-heading-2">{{ $options.i18n.stepOneHeading }}</h2>
<h3 v-if="isWidget" class="gl-heading-3">{{ $options.i18n.stepOneHeading }}</h3>
<h2 v-else class="gl-heading-2">{{ $options.i18n.stepOneHeading }}</h2>
<p>{{ $options.i18n.stepOneDescription }}</p>
<google-cloud-field-group
@ -504,7 +511,8 @@ export default {
<hr />
<h2 class="gl-heading-2">{{ $options.i18n.stepOneNodePoolHeading }}</h2>
<h3 v-if="isWidget" class="gl-heading-3">{{ $options.i18n.stepOneNodePoolHeading }}</h3>
<h2 v-else class="gl-heading-2">{{ $options.i18n.stepOneNodePoolHeading }}</h2>
<p>{{ $options.i18n.stepOneNodePoolDescription }}</p>
<template v-for="(nodePool, index) in nodePools">
@ -527,7 +535,8 @@ export default {
<!-- end: step 1.2 -->
<!-- start: step two -->
<h2 class="gl-heading-2">{{ $options.i18n.stepTwoHeading }}</h2>
<h3 v-if="isWidget" class="gl-heading-3">{{ $options.i18n.stepTwoHeading }}</h3>
<h2 v-else class="gl-heading-2">{{ $options.i18n.stepTwoHeading }}</h2>
<p>{{ $options.i18n.stepTwoDescription }}</p>
<gl-alert
v-if="showAlert"
@ -559,6 +568,6 @@ export default {
:apply-terraform-script="applyTerraformScript"
/>
<hr />
<hr v-if="!isWidget" />
</div>
</template>

View File

@ -101,6 +101,11 @@ export default {
required: false,
default: null,
},
isWidget: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
@ -245,7 +250,7 @@ export default {
<template>
<div>
<div class="gl-mt-5">
<p>
<p v-if="!isWidget" data-testid="runner-token-message">
<gl-icon name="information-o" variant="info" />
<gl-sprintf :message="tokenMessage">
<template #token>
@ -271,11 +276,12 @@ export default {
</gl-sprintf>
</p>
</div>
<hr />
<hr v-if="!isWidget" />
<!-- start: before you begin -->
<div>
<h2 class="gl-heading-2">{{ $options.i18n.beforeHeading }}</h2>
<h3 v-if="isWidget" class="gl-heading-3">{{ $options.i18n.beforeHeading }}</h3>
<h2 v-else class="gl-heading-2">{{ $options.i18n.beforeHeading }}</h2>
<ul>
<li>
<gl-sprintf :message="$options.i18n.permissionsText">
@ -319,7 +325,8 @@ export default {
<!-- end: before you begin -->
<!-- start: step one -->
<h2 class="gl-heading-2">{{ $options.i18n.stepOneHeading }}</h2>
<h3 v-if="isWidget" class="gl-heading-3">{{ $options.i18n.stepOneHeading }}</h3>
<h2 v-else class="gl-heading-2">{{ $options.i18n.stepOneHeading }}</h2>
<p>{{ $options.i18n.stepOneDescription }}</p>
<google-cloud-field-group
@ -501,7 +508,8 @@ export default {
<!-- end: step one -->
<!-- start: step two -->
<h2 class="gl-heading-2">{{ $options.i18n.stepTwoHeading }}</h2>
<h3 v-if="isWidget" class="gl-heading-3">{{ $options.i18n.stepTwoHeading }}</h3>
<h2 v-else class="gl-heading-2">{{ $options.i18n.stepTwoHeading }}</h2>
<p>{{ $options.i18n.stepTwoDescription }}</p>
<gl-alert
v-if="showAlert"
@ -533,6 +541,6 @@ export default {
:apply-terraform-script="applyTerraformScript"
/>
<hr />
<hr v-if="!isWidget" />
</div>
</template>

View File

@ -0,0 +1,144 @@
<script>
import { GlIcon, GlLink, GlSprintf, GlBadge } from '@gitlab/ui';
import CrudComponent from '~/vue_shared/components/crud_component.vue';
import { EXECUTORS_HELP_URL, SERVICE_COMMANDS_HELP_URL } from '../../constants';
import PlatformsDrawer from './platforms_drawer.vue';
import CliCommand from './cli_command.vue';
import { commandPrompt, registerCommand, runCommand } from './utils';
export default {
name: 'OperatingSystemRegistrationInstructions',
components: {
GlIcon,
GlLink,
GlSprintf,
GlBadge,
CrudComponent,
CliCommand,
PlatformsDrawer,
},
props: {
token: {
type: String,
required: true,
},
platform: {
type: String,
required: true,
},
title: {
type: String,
required: true,
},
},
data() {
return {
isDrawerOpen: false,
};
},
computed: {
commandPrompt() {
return commandPrompt({ platform: this.platform });
},
registerCommand() {
return registerCommand({
platform: this.platform,
token: this.token,
});
},
runCommand() {
return runCommand({ platform: this.platform });
},
},
methods: {
onToggleDrawer(val = !this.isDrawerOpen) {
this.isDrawerOpen = val;
},
},
EXECUTORS_HELP_URL,
SERVICE_COMMANDS_HELP_URL,
};
</script>
<template>
<crud-component :is-collapsible="true" :collapsed="true">
<template #title>
{{ title }}
<gl-badge variant="neutral">{{ s__('Runners|Operating System') }}</gl-badge>
</template>
<p>
<gl-sprintf
:message="
s__(
'Runners|GitLab Runner must be installed before you can register a runner. %{linkStart}How do I install GitLab Runner?%{linkEnd}',
)
"
>
<template #link="{ content }">
<gl-link data-testid="how-to-install-btn" @click="onToggleDrawer()">{{
content
}}</gl-link>
</template>
</gl-sprintf>
</p>
<section class="gl-mt-6" data-testid="step-1">
<h3 class="gl-heading-3">{{ s__('Runners|Step 1') }}</h3>
<p>
{{
s__(
'Runners|Copy and paste the following command into your command line to register the runner.',
)
}}
</p>
<cli-command :prompt="commandPrompt" :command="registerCommand" />
</section>
<section class="gl-mt-6" data-testid="step-2">
<h3 class="gl-heading-3">{{ s__('Runners|Step 2') }}</h3>
<p>
<gl-sprintf
:message="
s__(
'Runners|Choose an executor when prompted by the command line. Executors run builds in different environments. %{linkStart}Not sure which one to select?%{linkEnd}',
)
"
>
<template #link="{ content }">
<gl-link
:href="$options.EXECUTORS_HELP_URL"
target="_blank"
data-testid="executors-help-link"
>
{{ content }} <gl-icon name="external-link" />
</gl-link>
</template>
</gl-sprintf>
</p>
</section>
<section class="gl-mt-6" data-testid="step-3">
<h3 class="gl-heading-3">{{ s__('Runners|Step 3 (optional)') }}</h3>
<p>{{ s__('Runners|Manually verify that the runner is available to pick up jobs.') }}</p>
<cli-command :prompt="commandPrompt" :command="runCommand" />
<p>
<gl-sprintf
:message="
s__(
'Runners|This may not be needed if you manage your runner as a %{linkStart}system or user service%{linkEnd}.',
)
"
>
<template #link="{ content }">
<gl-link
:href="$options.SERVICE_COMMANDS_HELP_URL"
target="_blank"
data-testid="service-commands-help-link"
>
{{ content }} <gl-icon name="external-link" />
</gl-link>
</template>
</gl-sprintf>
</p>
</section>
<platforms-drawer :platform="platform" :open="isDrawerOpen" @close="onToggleDrawer(false)" />
</crud-component>
</template>

View File

@ -3,29 +3,43 @@ import {
GlFormGroup,
GlFormInputGroup,
GlButton,
GlBadge,
GlSprintf,
GlAlert,
GlLink,
GlIcon,
GlLoadingIcon,
} from '@gitlab/ui';
import { createAlert } from '~/alert';
import MultiStepFormTemplate from '~/vue_shared/components/multi_step_form_template.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import CrudComponent from '~/vue_shared/components/crud_component.vue';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { TYPENAME_CI_RUNNER } from '~/graphql_shared/constants';
import runnerForRegistrationQuery from '../graphql/register/runner_for_registration.query.graphql';
import { I18N_FETCH_ERROR } from '../constants';
import { DOCKER_HELP_URL, KUBERNETES_HELP_URL, I18N_FETCH_ERROR } from '../constants';
import { captureException } from '../sentry_utils';
import OperatingSystemInstruction from './registration/wizard_operating_system_instruction.vue';
import GoogleCloudRegistrationInstructions from './registration/google_cloud_registration_instructions.vue';
import GkeRegistrationInstructions from './registration/gke_registration_instructions.vue';
export default {
components: {
GlFormGroup,
GlFormInputGroup,
GlButton,
GlBadge,
GlSprintf,
GlAlert,
GlLink,
GlIcon,
GlLoadingIcon,
MultiStepFormTemplate,
ClipboardButton,
CrudComponent,
OperatingSystemInstruction,
GoogleCloudRegistrationInstructions,
GkeRegistrationInstructions,
},
props: {
currentStep: {
@ -81,6 +95,8 @@ export default {
return this.$apollo.queries.runner.loading;
},
},
DOCKER_HELP_URL,
KUBERNETES_HELP_URL,
};
</script>
<template>
@ -158,7 +174,96 @@ export default {
</gl-sprintf>
</gl-alert>
<!-- instructions will be added in https://gitlab.com/gitlab-org/gitlab/-/issues/396544 -->
<!-- eslint-disable @gitlab/vue-require-i18n-strings -->
<!-- Operating systems -->
<operating-system-instruction
platform="linux"
:token="token"
title="Linux"
class="gl-rounded-b-none"
/>
<operating-system-instruction
platform="osx"
:token="token"
title="macOS"
class="!gl-mt-0 gl-rounded-none gl-border-t-0"
/>
<operating-system-instruction
platform="windows"
:token="token"
title="Windows"
class="!gl-mt-0 gl-rounded-none gl-border-t-0"
/>
<!-- Clouds -->
<crud-component
is-collapsible
:collapsed="true"
:is-empty="false"
class="!gl-mt-0 gl-rounded-none gl-border-t-0"
>
<template #title>
Google Cloud
<gl-badge variant="neutral">{{ s__('Runners|Cloud') }}</gl-badge>
</template>
<google-cloud-registration-instructions :is-widget="true" />
</crud-component>
<crud-component
is-collapsible
:collapsed="true"
:is-empty="false"
class="!gl-mt-0 gl-rounded-none gl-border-t-0"
>
<template #title>
GKE
<gl-badge variant="neutral">{{ s__('Runners|Cloud') }}</gl-badge>
</template>
<gke-registration-instructions :is-widget="true" />
</crud-component>
<!-- Containers -->
<crud-component
is-collapsible
:collapsed="true"
:is-empty="false"
class="!gl-mt-0 gl-rounded-none gl-border-t-0"
>
<template #title>
Docker
<gl-badge variant="neutral">{{ s__('Runners|Container') }}</gl-badge>
</template>
<gl-sprintf :message="s__('Runners|View instructions in %{helpLink}.')">
<template #helpLink>
<gl-link :href="$options.DOCKER_HELP_URL" target="_blank">
{{ s__('Runners|documentation') }}
<gl-icon name="external-link" />
</gl-link>
</template>
</gl-sprintf>
</crud-component>
<crud-component
is-collapsible
:collapsed="true"
:is-empty="false"
class="!gl-mt-0 gl-rounded-t-none gl-border-t-0"
>
<template #title>
Kubernetes
<gl-badge variant="neutral">{{ s__('Runners|Container') }}</gl-badge>
</template>
<gl-sprintf :message="s__('Runners|View instructions in %{helpLink}.')">
<template #helpLink>
<gl-link :href="$options.KUBERNETES_HELP_URL" target="_blank">
{{ s__('Runners|documentation') }}
<gl-icon name="external-link" />
</gl-link>
</template>
</gl-sprintf>
</crud-component>
</template>
</template>
<template v-if="!loading" #next>

View File

@ -263,26 +263,24 @@ export default {
</template>
</groups-app>
</gl-tab>
<template #tabs-end>
<li class="gl-w-full">
<filtered-search-and-sort
class="gl-border-b-0"
:filtered-search-namespace="$options.filteredSearch.namespace"
:filtered-search-tokens="$options.filteredSearch.tokens"
:filtered-search-term-key="$options.filteredSearch.searchTermKey"
:filtered-search-recent-searches-storage-key="
$options.filteredSearch.recentSearchesStorageKey
"
:filtered-search-query="$route.query"
:is-ascending="isAscending"
:sort-options="activeTabSortOptions"
:active-sort-option="activeSortOption"
:search-input-placeholder="$options.i18n.searchPlaceholder"
@filter="onFilter"
@sort-direction-change="onSortDirectionChange"
@sort-by-change="onSortByChange"
/>
</li>
<template #toolbar-end>
<filtered-search-and-sort
class="gl-w-full gl-border-b-0"
:filtered-search-namespace="$options.filteredSearch.namespace"
:filtered-search-tokens="$options.filteredSearch.tokens"
:filtered-search-term-key="$options.filteredSearch.searchTermKey"
:filtered-search-recent-searches-storage-key="
$options.filteredSearch.recentSearchesStorageKey
"
:filtered-search-query="$route.query"
:is-ascending="isAscending"
:sort-options="activeTabSortOptions"
:active-sort-option="activeSortOption"
:search-input-placeholder="$options.i18n.searchPlaceholder"
@filter="onFilter"
@sort-direction-change="onSortDirectionChange"
@sort-by-change="onSortByChange"
/>
</template>
</gl-tabs>
</template>

View File

@ -581,24 +581,22 @@ export default {
<template v-else>{{ tab.text }}</template>
</gl-tab>
<template #tabs-end>
<li class="gl-w-full">
<filtered-search-and-sort
class="gl-border-b-0"
:filtered-search-namespace="filteredSearchNamespace"
:filtered-search-tokens="filteredSearchTokens"
:filtered-search-term-key="filteredSearchTermKey"
:filtered-search-recent-searches-storage-key="filteredSearchRecentSearchesStorageKey"
:filtered-search-query="$route.query"
:search-input-placeholder="filteredSearchInputPlaceholder"
:is-ascending="isAscending"
:sort-options="sortOptions"
:active-sort-option="activeSortOption"
@filter="onFilter"
@sort-direction-change="onSortDirectionChange"
@sort-by-change="onSortByChange"
/>
</li>
<template #toolbar-end>
<filtered-search-and-sort
class="gl-w-full gl-border-b-0"
:filtered-search-namespace="filteredSearchNamespace"
:filtered-search-tokens="filteredSearchTokens"
:filtered-search-term-key="filteredSearchTermKey"
:filtered-search-recent-searches-storage-key="filteredSearchRecentSearchesStorageKey"
:filtered-search-query="$route.query"
:search-input-placeholder="filteredSearchInputPlaceholder"
:is-ascending="isAscending"
:sort-options="sortOptions"
:active-sort-option="activeSortOption"
@filter="onFilter"
@sort-direction-change="onSortDirectionChange"
@sort-by-change="onSortByChange"
/>
</template>
</gl-tabs>
</template>

View File

@ -110,7 +110,7 @@ export default {
/>
<members-app v-else :namespace="tab.namespace" :tab-query-param-value="tab.queryParamValue" />
</gl-tab>
<template #tabs-end>
<template #toolbar-end>
<gl-button
v-if="shouldShowExportButton"
data-event-tracking="click_export_group_members_as_csv"

View File

@ -411,7 +411,7 @@ export default {
/>
</gl-tab>
<template #tabs-end>
<template #toolbar-end>
<div class="gl-ml-auto gl-flex gl-gap-2">
<gl-button
v-gl-modal="$options.uploadCsvModalId"

View File

@ -259,6 +259,7 @@ export default {
<template #tabs-end>
<li role="presentation" class="nav-item">
<gl-link
role="tab"
:href="mergeRequestsSearchDashboardPath"
class="nav-link gl-tab-nav-item !gl-no-underline"
>

View File

@ -22,32 +22,32 @@ class Profiles::ChatNamesController < Profiles::ApplicationController
if new_chat_name.save
flash[:notice] = safe_format(_("Authorized %{new_chat_name}"), new_chat_name: new_chat_name.chat_name)
else
flash[:alert] = _("Could not authorize chat nickname. Try again!")
flash[:alert] = s_("Integrations|Could not authorize integration account nickname. Try again!")
end
delete_chat_name_token
redirect_to profile_chat_names_path
redirect_to user_settings_integration_accounts_path
end
def deny
delete_chat_name_token
flash[:notice] =
safe_format(_("Denied authorization of chat nickname %{user_name}."), user_name: chat_name_params[:user_name])
safe_format(_("Denied authorization of account nickname %{user_name}."), user_name: chat_name_params[:user_name])
redirect_to profile_chat_names_path
redirect_to user_settings_integration_accounts_path
end
def destroy
@chat_name = chat_names.find(params[:id])
if @chat_name.destroy
flash[:notice] = safe_format(_("Deleted chat nickname: %{chat_name}!"), chat_name: @chat_name.chat_name)
flash[:notice] = safe_format(_("Deleted account nickname: %{chat_name}!"), chat_name: @chat_name.chat_name)
else
flash[:alert] = safe_format(_("Could not delete chat nickname %{chat_name}."), chat_name: @chat_name.chat_name)
flash[:alert] = safe_format(_("Could not delete account nickname %{chat_name}."), chat_name: @chat_name.chat_name)
end
redirect_to profile_chat_names_path, status: :found
redirect_to user_settings_integration_accounts_path, status: :found
end
private

View File

@ -735,6 +735,7 @@ module Types
field :languages, [Types::Projects::RepositoryLanguageType],
null: true,
description: "Programming languages used in the project.",
scopes: [:api, :read_api, :ai_workflows],
calls_gitaly: true
field :runners, Types::Ci::RunnerType.connection_type,

View File

@ -6,11 +6,17 @@ module Types
class RepositoryLanguageType < BaseObject
graphql_name 'RepositoryLanguage'
def self.authorization_scopes
super + [:ai_workflows]
end
field :name, GraphQL::Types::String, null: false,
description: 'Name of the repository language.'
description: 'Name of the repository language.',
scopes: [:api, :read_api, :ai_workflows]
field :share, GraphQL::Types::Float, null: true,
description: "Percentage of the repository's languages."
description: "Percentage of the repository's languages.",
scopes: [:api, :read_api, :ai_workflows]
field :color, Types::ColorType, null: true,
description: 'Color to visualize the repository language.'

View File

@ -13,7 +13,7 @@ module ChatNames
token = request_token
new_profile_chat_name_url(token: token) if token
new_user_settings_integration_account_url(token: token) if token
end
private

View File

@ -1,8 +1,5 @@
- if !Gitlab.com? || current_user.try(:onboarding_status_initial_registration_type) != "trial"
= render 'devise/sessions/successful_verification', local_assigns
- else
- experiment(:lightweight_trial_registration_redesign, actor: current_user) do |e|
- e.control do
= render 'devise/sessions/successful_verification', local_assigns
- e.candidate do
= render 'devise/sessions/successful_verification_lightweight', local_assigns
- experiment(:lightweight_trial_registration_redesign, actor: current_user) do |e|
- e.control do
= render 'devise/sessions/successful_verification', local_assigns
- e.candidate do
= render 'devise/sessions/successful_verification_lightweight', local_assigns

View File

@ -11,13 +11,13 @@
%tbody
- failed.each do |build|
%tr.build-state
%td{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; padding: 16px 0; color: #8c8c8c; font-weight: 500; font-size: 14px;" }
%td{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; padding: 16px 0; color: #737278; font-weight: 500; font-size: 14px;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse: collapse;" }
%tbody
%tr
%td{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; color: #d22f57; font-weight: 500; font-size: 16px; vertical-align: middle; padding-right: 8px; line-height: 10px" }
%img{ alt: "✖", height: "10", src: image_url('mailers/ci_pipeline_notif_v1/icon-x-red.gif'), style: "display: block;", width: "10" }/
%td{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; color: #8c8c8c; font-weight: 500; font-size: 14px; vertical-align: middle;" }
%img{ alt: "✖", height: "10", src: image_url('mailers/ci_pipeline_notif_v2/x-red.png'), style: "display: block;", width: "10" }/
%td{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; color: #737278; font-weight: 500; font-size: 14px; vertical-align: middle;" }
= build.stage_name
%td{ align: "right", style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; padding: 16px 0; color: #8c8c8c; font-weight: 500; font-size: 14px;" }
%td{ align: "right", style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; padding: 16px 0; color: #737278; font-weight: 500; font-size: 14px;" }
= render "notify/links/#{build.to_partial_path}", pipeline: pipeline, build: build

View File

@ -1,11 +1,11 @@
- title = local_assigns[:title]
%tr.table-success
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;color:#ffffff;background-color:#31af64;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;color:#ffffff;background-color:#108548;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;" }
%img{ alt: "✓", height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-check-green-inverted.gif'), style: "display:block;", width: "13" }/
%img{ alt: "✓", height: "16", src: image_url('mailers/ci_pipeline_notif_v2/check.png'), style: "display:block;", width: "16" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;" }
= title
%tr.spacer
@ -16,9 +16,9 @@
%table.table-info{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;" }
= _('Project')
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;" }
- namespace_name = @project.group ? @project.group.name : @project.namespace.owner.name
- namespace_url = @project.group ? group_url(@project.group) : user_url(@project.namespace.owner)
%a.muted{ href: namespace_url, style: "color:#333333;text-decoration:none;" }
@ -27,26 +27,26 @@
%a.muted{ href: project_url(@project), style: "color:#333333;text-decoration:none;" }
= @project.name
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;border-top:1px solid #ededed;" }
= _('Branch')
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
%img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "" }/
%img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v2/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
%a.muted{ href: commits_url(@pipeline), style: "color:#333333;text-decoration:none;" }
= @pipeline.source_ref
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;border-top:1px solid #ededed;" }
= _('Commit')
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:400;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
%img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), style: "display:block;", width: "13", alt: "" }/
%img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v2/icon-commit-gray.gif'), style: "display:block;", width: "13", alt: "" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
- commit_link = content_tag(:a, @pipeline.short_sha, href: commit_url(@pipeline), style: "color:#3777b0;text-decoration:none;").html_safe
- if @merge_request
@ -54,13 +54,13 @@
= s_('Notify|%{commit_link} in %{mr_link}').html_safe % { commit_link: commit_link, mr_link: mr_link }
- else
= commit_link
.commit{ style: "color:#5c5c5c;font-weight:300;" }
.commit{ style: "color:#5c5c5c;font-weight:400;" }
= @pipeline.git_commit_message.truncate(50)
- commit = @pipeline.commit
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;border-top:1px solid #ededed;" }
= s_('Notify|Commit Author')
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
%tbody
%tr
@ -75,9 +75,9 @@
= commit.author_name
- if commit.different_committer?
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;border-top:1px solid #ededed;" }
= s_('Notify|Committed by')
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
%tbody
%tr
@ -115,7 +115,7 @@
= _('API')
%tr
%td{ colspan: 2, style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:300;line-height:1.4;padding:15px 5px;text-align:center;" }
%td{ colspan: 2, style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 5px;text-align:center;" }
- job_count = @pipeline.total_size
- stage_count = @pipeline.stages_count
= s_('Notify|successfully completed %{jobs} in %{stages}.').html_safe % { jobs: n_('%d job', '%d jobs', job_count) % job_count, stages: n_('%d stage', '%d stages', stage_count) % stage_count }

View File

@ -1,5 +1,5 @@
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;border-top:1px solid #ededed;" }
= user_label
%td{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; margin: 0; padding: 14px 0 0px 5px; font-size: 15px; line-height: 1.4; color: #333333; font-weight: 400; width: 75%; border-top-style: solid; border-top-color: #ededed; border-top-width: 1px; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt;" }
%ul.users-list{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; font-size: 15px; line-height: 1.4; padding-right: 5px; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt;" }

View File

@ -1,10 +1,10 @@
%tr.success
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;color:#ffffff;background-color:#31af64;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;color:#ffffff;background-color:#108548;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;" }
%img{ alt: "✓", height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-check-green-inverted.gif'), style: "display:block;", width: "13" }/
%img{ alt: "✓", height: "16", src: image_url('mailers/ci_pipeline_notif_v2/check.png'), style: "display:block;", width: "16" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;" }
%span
- if @merge_request.respond_to? :approvals_required
@ -19,8 +19,8 @@
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;width:100%;" }
%tbody
%tr{ style: 'width:100%;' }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;text-align:center;" }
%img{ src: image_url('mailers/approval/icon-merge-request-gray.gif'), style: "height:18px;width:18px;margin-bottom:2px;vertical-align:bottom;", alt: "Merge request icon" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;text-align:center;" }
%img{ src: image_url('mailers/approval/icon-merge-request-gray.gif'), style: "height:16px;width:16px;margin-bottom:2px;vertical-align:bottom;", alt: "Merge request icon" }
= s_('Notify|%{mr_highlight}Merge request%{highlight_end} %{mr_link} %{reviewer_highlight}was approved by%{highlight_end} %{reviewer_avatar} %{reviewer_link}').html_safe % merge_request_hash_param(@merge_request, @approved_by)
%tr.spacer
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" }
@ -30,8 +30,8 @@
%table.info{ border: "0", cellpadding: "0", cellspacing: "0", style: "text-align:left;width:100%;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;" }= _("Project")
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;" }= _("Project")
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;" }
- namespace_name = @project.group ? @project.group.name : @project.namespace.owner.name
- namespace_url = @project.group ? group_url(@project.group) : user_url(@project.namespace.owner)
%a.muted{ href: namespace_url, style: "color:#333333;text-decoration:none;" }
@ -40,19 +40,19 @@
%a.muted{ href: project_url(@project), style: "color:#333333;text-decoration:none;" }
= @project.name
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" }= _("Branch")
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;border-top:1px solid #ededed;" }= _("Branch")
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
%img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "Branch icon" }/
%img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v2/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "Branch icon" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
%span.muted{ style: "color:#333333;text-decoration:none;" }
= @merge_request.source_branch
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" }= _("Author")
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;border-top:1px solid #ededed;" }= _("Author")
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
%tbody
%tr

View File

@ -1,10 +1,10 @@
%tr.success
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;color:#ffffff;background-color:#31af64;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;color:#ffffff;background-color:#108548;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;" }
%img{ alt: "✓", height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-check-green-inverted.gif'), style: "display:block;", width: "13" }
%img{ alt: "✓", height: "16", src: image_url('mailers/ci_pipeline_notif_v2/check.png'), style: "display:block;", width: "16" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;" }
%span= _('Merge request was set to auto-merge')
%tr.spacer
@ -15,8 +15,8 @@
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;width:100%;" }
%tbody
%tr{ style: 'width:100%;' }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;text-align:center;" }
%img{ src: image_url('mailers/approval/icon-merge-request-gray.gif'), style: "height:18px;width:18px;margin-bottom:2px;vertical-align:bottom;", alt: "Merge request icon" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;text-align:center;" }
%img{ src: image_url('mailers/approval/icon-merge-request-gray.gif'), style: "height:16px;width:16px;margin-bottom:2px;vertical-align:bottom;", alt: "Merge request icon" }
%span{ style: "font-weight: 600;color:#333333;" }= _('Merge request')
%a{ href: merge_request_url(@merge_request), style: "font-weight: 600;color:#3777b0;text-decoration:none" }= @merge_request.to_reference
%span= _('was set to auto-merge by')
@ -31,9 +31,9 @@
%table.info{ border: "0", cellpadding: "0", cellspacing: "0", style: "text-align:left;width:100%;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;" }= _('Project')
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;" }= _('Project')
-# haml-lint:disable NoPlainNodes
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;" }
- namespace_name = @project.group ? @project.group.name : @project.namespace.owner.name
- namespace_url = @project.group ? group_url(@project.group) : user_url(@project.namespace.owner)
%a.muted{ href: namespace_url, style: "color:#333333;text-decoration:none;" }
@ -42,19 +42,19 @@
%a.muted{ href: project_url(@project), style: "color:#333333;text-decoration:none;" }
= @project.name
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" }= _('Branch')
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;border-top:1px solid #ededed;" }= _('Branch')
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
%img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "Branch icon" }
%img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v2/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "Branch icon" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
%span.muted{ style: "color:#333333;text-decoration:none;" }
= @merge_request.source_branch
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" }= _('Author')
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;border-top:1px solid #ededed;" }= _('Author')
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
%tbody
%tr

View File

@ -4,7 +4,7 @@
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;line-height:1;" }
%img{ alt: "✖", height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-x-red-inverted.gif'), style: "display:block;", width: "13" }/
%img{ alt: "✖", height: "16", src: image_url('mailers/ci_pipeline_notif_v2/x.png'), style: "display:block;", width: "16" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;" }
= s_('Notify|Pipeline %{pipeline_name_or_id} has failed!') % { pipeline_name_or_id: sanitize_name(@pipeline.name) || "##{@pipeline.id}" }
%tr.spacer
@ -15,9 +15,9 @@
%table.table-info{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;" }
= _('Project')
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;" }
- namespace_name = @project.group ? @project.group.name : @project.namespace.owner.name
- namespace_url = @project.group ? group_url(@project.group) : user_url(@project.namespace.owner)
%a.muted{ href: namespace_url, style: "color:#333333;text-decoration:none;" }
@ -26,26 +26,26 @@
%a.muted{ href: project_url(@project), style: "color:#333333;text-decoration:none;" }
= @project.name
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;border-top:1px solid #ededed;" }
= _('Branch')
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
%img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "" }/
%img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v2/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
%a.muted{ href: commits_url(@pipeline), style: "color:#333333;text-decoration:none;" }
= @pipeline.source_ref
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;border-top:1px solid #ededed;" }
= _('Commit')
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:400;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
%img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), style: "display:block;", width: "13", alt: "" }/
%img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v2/icon-commit-gray.gif'), style: "display:block;", width: "13", alt: "" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
%a{ href: commit_url(@pipeline), style: "color:#3777b0;text-decoration:none;" }
= @pipeline.short_sha
@ -53,13 +53,13 @@
in
%a{ href: merge_request_url(@merge_request), style: "color:#3777b0;text-decoration:none;" }
= @merge_request.to_reference
.commit{ style: "color:#5c5c5c;font-weight:300;" }
.commit{ style: "color:#5c5c5c;font-weight:400;" }
= @pipeline.git_commit_message.truncate(50)
- commit = @pipeline.commit
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;border-top:1px solid #ededed;" }
= s_('Notify|Commit Author')
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
%tbody
%tr
@ -74,8 +74,8 @@
= commit.author_name
- if commit.different_committer?
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Committed by
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Committed by
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
%tbody
%tr

View File

@ -4,7 +4,7 @@
%tbody
%tr
%td{ style: "vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;line-height:1;" }
%img{ alt: "✖", height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-x-red-inverted.gif'), style: "display:block;", width: "13" }/
%img{ alt: "✖", height: "16", src: image_url('mailers/ci_pipeline_notif_v2/x.png'), style: "display:block;", width: "16" }/
%td{ style: "vertical-align:middle;color:#ffffff;text-align:center;" }
= s_('Notify|A remote mirror update has failed.')
%tr.spacer{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif;" }
@ -13,9 +13,9 @@
%tr.section{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif;" }
%td{ style: "padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" }
%table.table-info{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;" }
%tbody{ style: "font-size:15px;line-height:1.4;color:#8c8c8c;" }
%tbody{ style: "font-size:15px;line-height:1.4;color:#737278;" }
%tr
%td{ style: "font-weight:300;padding:14px 0;margin:0;" }
%td{ style: "font-weight:400;padding:14px 0;margin:0;" }
= _('Source project')
%td{ style: "font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;" }
- namespace_url = @project.group ? group_url(@project.group) : user_url(@project.namespace.owner)
@ -25,12 +25,12 @@
%a.muted{ href: project_url(@project), style: "color:#333333;text-decoration:none;" }
= @project.name
%tr
%td{ style: "font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" }
%td{ style: "font-weight:400;padding:14px 0;margin:0;border-top:1px solid #ededed;" }
= s_('Notify|Remote mirror')
%td{ style: "font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
= @remote_mirror.safe_url
%tr
- update_at_start = '<td style="font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;">'.html_safe
- update_at_start = '<td style="font-weight:400;padding:14px 0;margin:0;border-top:1px solid #ededed;">'.html_safe
- update_at_mid = '</td><td style="font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;">'.html_safe
- update_at_end = '</td>'.html_safe
= html_escape(s_('Notify|%{update_at_start} Last update at %{update_at_mid} %{last_update_at} %{update_at_end}')) % {update_at_start: update_at_start, update_at_mid: update_at_mid, last_update_at: @remote_mirror.last_update_at, update_at_end: update_at_end}

View File

@ -1,9 +1,9 @@
- default_font = "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;"
- default_style = "#{default_font}font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;"
- default_style = "#{default_font}font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;"
- spacer_style = "#{default_font};height:18px;font-size:18px;line-height:18px;"
%tr.alert
%td{ style: "#{default_font}padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;color:#ffffff;background-color:#FC6D26;" }
%td{ style: "#{default_font}padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;color:#ffffff;background-color:#d22f57;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;" }
%tbody
%tr

View File

@ -1,10 +1,10 @@
%tr.success
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;color:#ffffff;background-color:#FC6D26;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;color:#ffffff;background-color:#d22f57;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;" }
%img{ alt: "✗", height: "13", src: image_url('mailers/approval/icon-x-orange-inverted.gif'), style: "display:block;", width: "13" }/
%img{ alt: "✗", height: "16", src: image_url('mailers/approval_v2/x.png'), style: "display:block;", width: "16" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;" }
- if @merge_request.respond_to? :approvals_required
%span
@ -20,8 +20,8 @@
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;width:100%;" }
%tbody
%tr{ style: 'width:100%;' }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;text-align:center;" }
%img{ src: image_url('mailers/approval/icon-merge-request-gray.gif'), style: "height:18px;width:18px;margin-bottom:2px;vertical-align:bottom;", alt: "Merge request icon" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;text-align:center;" }
%img{ src: image_url('mailers/approval/icon-merge-request-gray.gif'), style: "height:16px;width:16px;margin-bottom:2px;vertical-align:bottom;", alt: "Merge request icon" }
= s_('Notify|%{mr_highlight}Merge request%{highlight_end} %{mr_link} %{reviewer_highlight}was unapproved by%{highlight_end} %{reviewer_avatar} %{reviewer_link}').html_safe % merge_request_hash_param(@merge_request, @unapproved_by)
%tr.spacer
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" }
@ -31,9 +31,9 @@
%table.info{ border: "0", cellpadding: "0", cellspacing: "0", style: "text-align:left;width:100%;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;" }
= _('Project')
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;" }
- namespace_name = @project.group ? @project.group.name : @project.namespace.owner.name
- namespace_url = @project.group ? group_url(@project.group) : user_url(@project.namespace.owner)
%a.muted{ href: namespace_url, style: "color:#333333;text-decoration:none;" }
@ -42,21 +42,21 @@
%a.muted{ href: project_url(@project), style: "color:#333333;text-decoration:none;" }
= @project.name
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;border-top:1px solid #ededed;" }
= _('Branch')
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
%img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "Branch icon" }/
%img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v2/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "Branch icon" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
%span.muted{ style: "color:#333333;text-decoration:none;" }
= @merge_request.source_branch
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;border-top:1px solid #ededed;" }
= _('Author')
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#737278;font-weight:400;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
%tbody
%tr

View File

@ -1,13 +1,13 @@
- default_font = "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;"
- default_style = "#{default_font}font-size:15px;line-height:1.4;color:#626168;font-weight:300;padding:14px 0;margin:0;"
- default_style = "#{default_font}font-size:15px;line-height:1.4;color:#626168;font-weight:400;padding:14px 0;margin:0;"
- spacer_style = "#{default_font};height:18px;font-size:18px;line-height:18px;"
%tr.alert
%td{ style: "#{default_font}padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;color:#ffffff;background-color:#FC6D26;" }
%td{ style: "#{default_font}padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;color:#3a383f;background-color:#fdf1dd;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;" }
%tbody
%tr
%td{ style: "#{default_font}vertical-align:middle;color:#ffffff;text-align:center;" }
%td{ style: "#{default_font}vertical-align:middle;color:#3a383f;text-align:center;" }
%span
= _("Someone signed in to your %{host} account from a new location") % { host: Gitlab.config.gitlab.host }
%tr.spacer

View File

@ -10,4 +10,4 @@
= _('Never')
%td
= link_button_to _('Remove'), profile_chat_name_path(chat_name), method: :delete, class: 'gl-float-right', aria: { label: _('Remove') }, data: { confirm: _('Are you sure you want to remove this nickname?'), confirm_btn_variant: 'danger' }, variant: :danger
= link_button_to _('Remove'), user_settings_integration_account_path(chat_name), method: :delete, class: 'gl-float-right', aria: { label: _('Remove') }, data: { confirm: _('Are you sure you want to remove this nickname?'), confirm_btn_variant: 'danger' }, variant: :danger

View File

@ -1,12 +1,14 @@
- page_title _('Chat')
- page_title s_('Integrations|Integration accounts')
- @hide_search_settings = true
- @force_desktop_expanded_sidebar = true
= render ::Layouts::SettingsSectionComponent.new(page_title) do |c|
- c.with_description do
= s_('Integrations|Manage your integration accounts in GitLab. Use these connected integrations to perform actions in GitLab.')
- c.with_body do
= render ::Layouts::CrudComponent.new(_('Active chat names'),
= render ::Layouts::CrudComponent.new(s_('Integrations|Connected integration accounts'),
count: @chat_names.size,
icon: 'comment') do |c|
icon: 'connected') do |c|
- c.with_body do
- if @chat_names.present?
.table-responsive
@ -20,4 +22,4 @@
%tbody
= render @chat_names
- else
.gl-text-subtle= _("You don't have any active chat names.")
.gl-text-subtle= s_('Integrations|You do not have any connected integration accounts.')

View File

@ -18,11 +18,11 @@
= s_("SlackIntegration|You don't have to reauthorize this application if the permission scope changes in future releases.")
- c.with_footer do
.gl-flex
= form_tag profile_chat_names_path, method: :post do
= form_tag user_settings_integration_accounts_path, method: :post do
= hidden_field_tag :token, @chat_name_token.token
= render Pajamas::ButtonComponent.new(type: :submit, variant: :danger, button_options: { data: { testid: 'authorize-button' } }) do
= _('Authorize')
= form_tag deny_profile_chat_names_path, method: :delete do
= form_tag deny_user_settings_integration_accounts_path, method: :delete do
= hidden_field_tag :token, @chat_name_token.token
= render Pajamas::ButtonComponent.new(type: :submit, button_options: { class: 'gl-ml-3' }) do
= _('Deny')

View File

@ -51,14 +51,14 @@ resource :profile, only: [] do
end
end
resources :chat_names, only: [:index, :new, :create, :destroy] do
collection do
delete :deny
end
end
resource :avatar, only: [:destroy]
get 'chat_names', to: redirect('-/user_settings/integration_accounts')
get 'chat_names/new', to: redirect { |_params, request|
"-/user_settings/integration_accounts/new?#{request.query_string}"
}
resource :two_factor_auth, only: [:show, :create, :destroy] do
member do
post :codes

View File

@ -30,4 +30,9 @@ namespace :user_settings do
delete :revoke
end
end
resources :integration_accounts, only: [:index, :new, :create, :destroy], controller: '/profiles/chat_names' do
collection do
delete :deny
end
end
end

View File

@ -1,8 +1,9 @@
---
migration_job_name: BackfillOnboardingStatusRegistrationObjective
description: Moves data from user_details.registration_objective to the new registration_objective field in user_details.onboarding_status
description: Moves data from user_details.registration_objective to the new registration_objective
field in user_details.onboarding_status
feature_category: onboarding
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/176428
milestone: '17.9'
queued_migration_version: 20250117172734
finalized_by:
finalized_by: '20250708081911'

View File

@ -1,8 +1,9 @@
---
migration_job_name: BackfillOnboardingStatusSetupForCompany
description: Moves data from user_preferences.setup_for_company to the new setup_for_company field in user_details.onboarding_status
description: Moves data from user_preferences.setup_for_company to the new setup_for_company
field in user_details.onboarding_status
feature_category: onboarding
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/180602
milestone: '17.10'
queued_migration_version: 20250228155146
finalized_by: # version of the migration that finalized this BBM
finalized_by: '20250713232042'

View File

@ -1,8 +1,9 @@
---
migration_job_name: FixUsernamespaceAuditEvents
description: Some audit events are tagged with scope UserNamespace which is incorrect this migration corrects the scope
description: Some audit events are tagged with scope UserNamespace which is incorrect
this migration corrects the scope
feature_category: audit_events
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/177095
milestone: '17.9'
queued_migration_version: 20250106104021
finalized_by: # version of the migration that finalized this BBM
finalized_by: '20250709154335'

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddProjectIdToMergeRequestDiffCommits < Gitlab::Database::Migration[2.3]
milestone '18.2'
def change
add_column :merge_request_diff_commits, :project_id, :bigint # rubocop:disable Migration/PreventAddingColumns -- Needed for future partitioning and sharding
end
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddDeletedAtToAiCatalogItems < Gitlab::Database::Migration[2.3]
milestone '18.3'
def change
add_column :ai_catalog_items, :deleted_at, :datetime_with_timezone
end
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class AddIndexToAiCatalogItemsDeletedAt < Gitlab::Database::Migration[2.3]
disable_ddl_transaction!
milestone '18.3'
INDEX_NAME = 'index_ai_catalog_items_where_deleted_at_is_null'
def up
add_concurrent_index :ai_catalog_items, :deleted_at, where: 'deleted_at IS NULL', name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :ai_catalog_items, INDEX_NAME
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class FinalizeHkBackfillOnboardingStatusRegistrationObjective < Gitlab::Database::Migration[2.3]
milestone '18.2'
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main
def up
ensure_batched_background_migration_is_finished(
job_class_name: 'BackfillOnboardingStatusRegistrationObjective',
table_name: :user_details,
column_name: :user_id,
job_arguments: [],
finalize: true
)
end
def down; end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class FinalizeHkFixUsernamespaceAuditEvents < Gitlab::Database::Migration[2.3]
milestone '18.2'
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main
def up
ensure_batched_background_migration_is_finished(
job_class_name: 'FixUsernamespaceAuditEvents',
table_name: :audit_events,
column_name: :id,
job_arguments: [],
finalize: true
)
end
def down; end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class FinalizeHkBackfillOnboardingStatusSetupForCompany < Gitlab::Database::Migration[2.3]
milestone '18.2'
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main
def up
ensure_batched_background_migration_is_finished(
job_class_name: 'BackfillOnboardingStatusSetupForCompany',
table_name: :user_details,
column_name: :user_id,
job_arguments: [],
finalize: true
)
end
def down; end
end

View File

@ -0,0 +1 @@
fb637948469c5747563202f5395dd1814f24bb92711d5355638f8b4f1fdf3429

View File

@ -0,0 +1 @@
23bd123c461e72a7d497fc10b99d2dc32be516cd518a0cde9655f3dd8d20ae5f

View File

@ -0,0 +1 @@
51a690472117d17ebf648351f10b8acdc23a95da2b623cc29a47e4faca2f1b78

View File

@ -0,0 +1 @@
3825f64cf8388cfdc4b48ba4573b6d90b46ce741df0ec6b6d07d646615d242ec

View File

@ -0,0 +1 @@
82a7340f7a4aa6c7996a795485bbe974f91e106b525697390ea57334036a3e6e

View File

@ -0,0 +1 @@
3235012c99576472c6b504866cfe78ba790260308a5b335eb73e6b7713849be6

View File

@ -8295,6 +8295,7 @@ CREATE TABLE ai_catalog_items (
description text NOT NULL,
name text NOT NULL,
public boolean DEFAULT false NOT NULL,
deleted_at timestamp with time zone,
CONSTRAINT check_7e02a4805b CHECK ((char_length(description) <= 1024)),
CONSTRAINT check_edddd6e1fe CHECK ((char_length(name) <= 255))
);
@ -17504,7 +17505,8 @@ CREATE TABLE merge_request_diff_commits (
trailers jsonb DEFAULT '{}'::jsonb,
commit_author_id bigint,
committer_id bigint,
merge_request_commits_metadata_id bigint
merge_request_commits_metadata_id bigint,
project_id bigint
);
CREATE TABLE merge_request_diff_details (
@ -34537,6 +34539,8 @@ CREATE INDEX index_ai_catalog_items_on_project_id ON ai_catalog_items USING btre
CREATE INDEX index_ai_catalog_items_on_public ON ai_catalog_items USING btree (public);
CREATE INDEX index_ai_catalog_items_where_deleted_at_is_null ON ai_catalog_items USING btree (deleted_at) WHERE (deleted_at IS NULL);
CREATE INDEX index_ai_code_suggestion_events_on_organization_id ON ONLY ai_code_suggestion_events USING btree (organization_id);
CREATE INDEX index_ai_code_suggestion_events_on_user_id ON ONLY ai_code_suggestion_events USING btree (user_id);

View File

@ -18,7 +18,7 @@ like joining projects, commenting on issues, pushing changes to MRs, or closing
For information about activity retention limits, see:
- [User activity time period limit](../user/profile/contributions_calendar.md#event-time-period-limit)
- [Project activity time period limit](../user/project/working_with_projects.md#event-time-period-limit)
- [Project activity time period limit](../user/project/working_with_projects.md#view-project-activity)
## List all events

View File

@ -571,6 +571,27 @@ Get the list of all the compliance requirement controls.
Returns [`ComplianceRequirementControl`](#compliancerequirementcontrol).
### `Query.configuredAiCatalogItems`
AI Catalog items configured for use.
{{< details >}}
**Introduced** in GitLab 18.2.
**Status**: Experiment.
{{< /details >}}
Returns [`AiCatalogItemConsumerConnection!`](#aicatalogitemconsumerconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#pagination-arguments):
`before: String`, `after: String`, `first: Int`, and `last: Int`.
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="queryconfiguredaicatalogitemsprojectid"></a>`projectId` | [`ProjectID!`](#projectid) | Project ID to retrieve configured AI Catalog items for. |
### `Query.containerRepository`
Find a container repository.
@ -2277,6 +2298,33 @@ Input type: `AiCatalogAgentDeleteInput`
| <a id="mutationaicatalogagentdeleteerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during the mutation. |
| <a id="mutationaicatalogagentdeletesuccess"></a>`success` | [`Boolean!`](#boolean) | Returns true if catalog Agent was successfully deleted. |
### `Mutation.aiCatalogFlowCreate`
{{< details >}}
**Introduced** in GitLab 18.3.
**Status**: Experiment.
{{< /details >}}
Input type: `AiCatalogFlowCreateInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationaicatalogflowcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationaicatalogflowcreatedescription"></a>`description` | [`String!`](#string) | Description for the flow. |
| <a id="mutationaicatalogflowcreatename"></a>`name` | [`String!`](#string) | Name for the flow. |
| <a id="mutationaicatalogflowcreateprojectid"></a>`projectId` | [`ProjectID!`](#projectid) | Project for the flow. |
| <a id="mutationaicatalogflowcreatepublic"></a>`public` | [`Boolean!`](#boolean) | Whether the flow is publicly visible in the catalog. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationaicatalogflowcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationaicatalogflowcreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during the mutation. |
| <a id="mutationaicatalogflowcreateitem"></a>`item` | [`AiCatalogItem`](#aicatalogitem) | Item created. |
### `Mutation.aiDuoWorkflowCreate`
{{< details >}}
@ -13966,6 +14014,30 @@ The connection type for [`AiCatalogItem`](#aicatalogitem).
| <a id="aicatalogitemconnectionnodes"></a>`nodes` | [`[AiCatalogItem]`](#aicatalogitem) | A list of nodes. |
| <a id="aicatalogitemconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
#### `AiCatalogItemConsumerConnection`
The connection type for [`AiCatalogItemConsumer`](#aicatalogitemconsumer).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="aicatalogitemconsumerconnectioncount"></a>`count` | [`Int!`](#int) | Total count of collection. |
| <a id="aicatalogitemconsumerconnectionedges"></a>`edges` | [`[AiCatalogItemConsumerEdge]`](#aicatalogitemconsumeredge) | A list of edges. |
| <a id="aicatalogitemconsumerconnectionnodes"></a>`nodes` | [`[AiCatalogItemConsumer]`](#aicatalogitemconsumer) | A list of nodes. |
| <a id="aicatalogitemconsumerconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
#### `AiCatalogItemConsumerEdge`
The edge type for [`AiCatalogItemConsumer`](#aicatalogitemconsumer).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="aicatalogitemconsumeredgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="aicatalogitemconsumeredgenode"></a>`node` | [`AiCatalogItemConsumer`](#aicatalogitemconsumer) | The item at the end of the edge. |
#### `AiCatalogItemEdge`
The edge type for [`AiCatalogItem`](#aicatalogitem).
@ -21991,6 +22063,7 @@ An AI catalog agent.
| <a id="aicatalogagentdescription"></a>`description` | [`String!`](#string) | Description of the item. |
| <a id="aicatalogagentid"></a>`id` | [`ID!`](#id) | ID of the item. |
| <a id="aicatalogagentitemtype"></a>`itemType` | [`AiCatalogItemType!`](#aicatalogitemtype) | Type of the item. |
| <a id="aicatalogagentlatestversion"></a>`latestVersion` | [`AiCatalogItemVersion`](#aicatalogitemversion) | Latest version of the item. |
| <a id="aicatalogagentname"></a>`name` | [`String!`](#string) | Name of the item. |
| <a id="aicatalogagentproject"></a>`project` | [`Project`](#project) | Project for the item. |
| <a id="aicatalogagentpublic"></a>`public` | [`Boolean!`](#boolean) | Whether the item is publicly visible in the catalog. |
@ -22024,6 +22097,7 @@ An AI catalog flow.
| <a id="aicatalogflowdescription"></a>`description` | [`String!`](#string) | Description of the item. |
| <a id="aicatalogflowid"></a>`id` | [`ID!`](#id) | ID of the item. |
| <a id="aicatalogflowitemtype"></a>`itemType` | [`AiCatalogItemType!`](#aicatalogitemtype) | Type of the item. |
| <a id="aicatalogflowlatestversion"></a>`latestVersion` | [`AiCatalogItemVersion`](#aicatalogitemversion) | Latest version of the item. |
| <a id="aicatalogflowname"></a>`name` | [`String!`](#string) | Name of the item. |
| <a id="aicatalogflowproject"></a>`project` | [`Project`](#project) | Project for the item. |
| <a id="aicatalogflowpublic"></a>`public` | [`Boolean!`](#boolean) | Whether the item is publicly visible in the catalog. |
@ -22043,6 +22117,22 @@ An AI catalog flow version.
| <a id="aicatalogflowversionupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp of when the item version was updated. |
| <a id="aicatalogflowversionversionname"></a>`versionName` | [`String`](#string) | Version name of the item version. |
### `AiCatalogItemConsumer`
An AI catalog item configuration.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="aicatalogitemconsumerenabled"></a>`enabled` | [`Boolean!`](#boolean) | Indicates whether the catalog item is enabled or not. |
| <a id="aicatalogitemconsumergroup"></a>`group` | [`Group`](#group) | Group in which the catalog item is configured. |
| <a id="aicatalogitemconsumerid"></a>`id` | [`ID!`](#id) | ID of the configuration item. |
| <a id="aicatalogitemconsumeritem"></a>`item` | [`AiCatalogItem`](#aicatalogitem) | Configuration catalog item. |
| <a id="aicatalogitemconsumerlocked"></a>`locked` | [`Boolean!`](#boolean) | Indicates whether the catalog item configuration is locked or can be overridden. |
| <a id="aicatalogitemconsumerorganization"></a>`organization` | [`Organization`](#organization) | Organization in which the catalog item is configured. |
| <a id="aicatalogitemconsumerproject"></a>`project` | [`Project`](#project) | Project in which the catalog item is configured. |
### `AiConversationsThread`
Conversation thread of the AI feature.
@ -27581,6 +27671,7 @@ GitLab Duo Agent Platform session.
| ---- | ---- | ----------- |
| <a id="duoworkflowagentprivilegesnames"></a>`agentPrivilegesNames` | [`[String!]`](#string) | Privileges granted to the agent during execution. |
| <a id="duoworkflowallowagenttorequestuser"></a>`allowAgentToRequestUser` | [`Boolean`](#boolean) | Allow the agent to request user input. |
| <a id="duoworkflowarchived"></a>`archived` | [`Boolean`](#boolean) | Archived due to retention policy. |
| <a id="duoworkflowcreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp of when the session was created. |
| <a id="duoworkflowenvironment"></a>`environment` | [`WorkflowEnvironment`](#workflowenvironment) | Environment, like IDE or web. |
| <a id="duoworkflowfirstcheckpoint"></a>`firstCheckpoint` | [`DuoWorkflowEvent`](#duoworkflowevent) | First checkpoint of the session. |
@ -27591,6 +27682,7 @@ GitLab Duo Agent Platform session.
| <a id="duoworkflowpreapprovedagentprivilegesnames"></a>`preApprovedAgentPrivilegesNames` | [`[String!]`](#string) | Privileges pre-approved for the agent during execution. |
| <a id="duoworkflowproject"></a>`project` | [`Project!`](#project) | Project that the session is in. |
| <a id="duoworkflowprojectid"></a>`projectId` | [`ProjectID!`](#projectid) | ID of the project. |
| <a id="duoworkflowstalled"></a>`stalled` | [`Boolean`](#boolean) | Workflow got created but has no checkpoints. |
| <a id="duoworkflowstatus"></a>`status` | [`DuoWorkflowStatus`](#duoworkflowstatus) | Status of the session. |
| <a id="duoworkflowstatusname"></a>`statusName` | [`String`](#string) | Status name of the session. |
| <a id="duoworkflowupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp of when the session was last updated. |
@ -50170,6 +50262,7 @@ Implementations:
| <a id="aicatalogitemdescription"></a>`description` | [`String!`](#string) | Description of the item. |
| <a id="aicatalogitemid"></a>`id` | [`ID!`](#id) | ID of the item. |
| <a id="aicatalogitemitemtype"></a>`itemType` | [`AiCatalogItemType!`](#aicatalogitemtype) | Type of the item. |
| <a id="aicatalogitemlatestversion"></a>`latestVersion` | [`AiCatalogItemVersion`](#aicatalogitemversion) | Latest version of the item. |
| <a id="aicatalogitemname"></a>`name` | [`String!`](#string) | Name of the item. |
| <a id="aicatalogitemproject"></a>`project` | [`Project`](#project) | Project for the item. |
| <a id="aicatalogitempublic"></a>`public` | [`Boolean!`](#boolean) | Whether the item is publicly visible in the catalog. |

View File

@ -952,7 +952,7 @@ Example response:
### List projects a user has contributed to
Returns a list of visible projects a given user has contributed to within the past year. For more information about what counts as a contribution, see [view projects you have contributed to](../user/project/working_with_projects.md#view-projects-you-have-contributed-to).
Returns a list of visible projects a given user has contributed to within the past year. For more information about what counts as a contribution, [view projects you have contributed to](../user/project/working_with_projects.md#view-projects-you-work-with).
```plaintext
GET /users/:user_id/contributed_projects

View File

@ -664,7 +664,7 @@ Prerequisites:
- You must have at least the Maintainer role for the project.
- The project must:
- Be set as a [catalog project](#set-a-component-project-as-a-catalog-project).
- Have a [project description](../../user/project/working_with_projects.md#edit-project-name-and-description) defined.
- Have a [project description](../../user/project/working_with_projects.md#edit-a-project) defined.
- Have a `README.md` file in the root directory for the commit SHA of the tag being released.
- Have at least one [CI/CD component in the `templates/` directory](#directory-structure)
for the commit SHA of the tag being released.
@ -833,7 +833,7 @@ To mirror a GitLab.com component in your GitLab Self-Managed instance:
1. Make sure that [network outbound requests](../../security/webhooks.md) are allowed for `gitlab.com`.
1. [Create a group](../../user/group/_index.md#create-a-group) to host the component projects (recommended group: `components`).
1. [Create a mirror of the component project](../../user/project/repository/mirror/pull.md) in the new group.
1. Write a [project description](../../user/project/working_with_projects.md#edit-project-name-and-description)
1. Write a [project description](../../user/project/working_with_projects.md#edit-a-project)
for the component project mirror because mirroring repositories does not copy the description.
1. [Set the self-hosted component project as a catalog resource](#set-a-component-project-as-a-catalog-project).
1. Publish [a new release](../../user/project/releases/_index.md) in the self-hosted component project by

View File

@ -96,5 +96,5 @@ You can view the compute usage for your personal namespace:
1. Select **Edit profile**.
1. On the left sidebar, select **Usage Quotas**.
The projects list shows [personal projects](../../user/project/working_with_projects.md#view-personal-projects)
The projects list shows [personal projects](../../user/project/working_with_projects.md)
with compute usage or instance runners usage in the current month only.

View File

@ -57,19 +57,33 @@ The diagram below shows how protection rules are evaluated in the context of an
%%{init: { "fontFamily": "GitLab Sans" }}%%
graph TD
accTitle: Evaluation of protected and immutable tag rules
accDescr: An illustration of the evaluation process for protected and immutable tag rules during an image push.
A[User attempts to push a tag] --> B{Does the user have the required role for push?}
accDescr: Flow chart showing the evaluation process for protected and immutable tag rules during an image push.
A[User attempts to push a tag] --> B{Protected tag check:<br/>Does user have required role<br/>to push this tag pattern?}
B -- Yes --> C{Does the tag already exist?}
B -- No --> D[Push denied: Insufficient permissions]
C -- Yes --> E{Is the tag marked as immutable?}
B -- No --> D[Push denied:<br/>Protected tag - insufficient permissions]
C -- Yes --> E{Immutable tag check:<br/>Does tag match an<br/>immutable rule pattern?}
C -- No --> F[Tag is created successfully]
E -- Yes --> G[Push denied: Tag is immutable]
E -- Yes --> G[Push denied:<br/>Tag is immutable]
E -- No --> H[Tag is overwritten successfully]
```
### Example scenarios
For a project with these rules:
- Protected tag rule: Pattern `v.*` requires at least the Maintainer role.
- Immutable tag rule: Pattern `v\d+\.\d+\.\d+` protects semantic version tags.
| User role | Action | Protected tag check | Immutable tag check | Result |
|-----------|--------|-------------------|-------------------|---------|
| Developer | Push new tag `v1.0.0` | Denied | Not evaluated | Push denied. User lacks required role. |
| Maintainer | Push new tag `v1.0.0` | Allowed | Not evaluated | Tag created. |
| Maintainer | Overwrite existing tag `v1.0.0` | Allowed | Denied | Push denied. Tag is immutable. |
| Maintainer | Push new tag `v-beta` | Allowed | Not evaluated | Tag created. |
## Prerequisites
To use protected container tags, make sure the container registry is available:
To use immutable container tags, make sure the container registry is available:
- In GitLab.com, the container registry is enabled by default.
- In GitLab Self-Managed, [enable the metadata database](../../../administration/packages/container_registry_metadata_database.md).

View File

@ -19,7 +19,7 @@ You can assign a topic to several projects.
For example, you can create and assign the topics `python` and `hackathon` to all projects that use Python and are intended for Hackathon contributions.
Topics assigned to a project are displayed in the **Project overview** and [**Projects**](working_with_projects.md#view-all-projects-for-the-instance) lists, below the project information description.
Topics assigned to a project are displayed in the **Project overview** and [**Projects**](working_with_projects.md#view-projects) lists, below the project information description.
{{< alert type="note" >}}

View File

@ -68,7 +68,7 @@ In GitLab 17.5 and later, you can also use `https://gitlab.example.com/-/p/<id>`
## Find the Project ID
You might also need the project ID if you want to interact with the project using the [GitLab API](../../api/_index.md).
You might need the project ID if you want to interact with the project using the [GitLab API](../../api/_index.md).
To find the project ID:
@ -76,101 +76,171 @@ To find the project ID:
1. On the project overview page, in the upper-right corner, select **Actions** ({{< icon name="ellipsis_v" >}}).
1. Select **Copy project ID**.
## View all projects for the instance
## View projects
To view all projects for the GitLab instance:
Use the **Projects** list to view:
- All the projects on an instance
- The projects you work with or own
- Inactive projects, including archived projects and projects pending deletion
### View all projects on an instance
To view the projects on your GitLab instance:
1. On the left sidebar, select **Search or go to**.
1. Select **Explore**.
On the left sidebar, **Projects** is selected.
A list of all projects for the instance is displayed.
1. Optional. Select a tab to filter which projects are displayed.
If you are not authenticated, the list shows public projects only.
## View projects you have contributed to
### View projects you work with
{{< history >}}
- [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/13066) in GitLab 17.9 [with a flag](../../administration/feature_flags/_index.md) named `your_work_projects_vue`. Disabled by default.
- [Changed](https://gitlab.com/groups/gitlab-org/-/epics/13066) tab label from **Yours** to **Member** in GitLab 17.9 [with a flag](../../administration/feature_flags/_index.md) named `your_work_projects_vue`. Disabled by default.
- [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/465889) in GitLab 17.10. Feature flag `your_work_projects_vue` removed.
{{< /history >}}
{{< alert type="flag" >}}
The availability of this feature is controlled by a feature flag. For more information, see the history.
{{< /alert >}}
The **Contributed** tab displays projects where you have:
- Created issues, merge requests, or epics
- Commented on issues, merge requests, or epics
- Closed issues, merge requests, or epics
- Pushed commits
- Approved merge requests
- Merged merge requests
To view projects you have contributed to:
To view the projects you have interacted with:
1. On the left sidebar, select **Search or go to**.
1. Select **View all my projects**.
1. Select the **Contributed** tab.
1. Optional. Select a tab to filter which projects are displayed:
- **Contributed**: Projects where you have:
- Created issues, merge requests, or epics
- Commented on issues, merge requests, or epics
- Closed issues, merge requests, or epics
- Pushed commits
- Approved merge requests
- Merged merge requests
- **Starred**: Projects you have [starred](#star-a-project)
- **Personal**: Projects created under your personal namespace
- **Member**: Projects you are a member of
- **Inactive**: Archived projects and projects pending deletion
## View projects you are a member of
You can also view your starred and personal projects from your personal profile:
1. On the left sidebar, select your avatar and then your username.
1. On the left sidebar, select **Starred projects** or **Personal projects**.
### View inactive projects
{{< history >}}
- [Changed](https://gitlab.com/groups/gitlab-org/-/epics/13066) tab label from "Yours" to "Member" in GitLab 17.9 [with a flag](../../administration/feature_flags/_index.md) named `your_work_projects_vue`. Disabled by default.
- [Changed](https://gitlab.com/groups/gitlab-org/-/epics/13066) tab label from "Pending deletion" to "Inactive" in GitLab 17.9 [with a flag](../../administration/feature_flags/_index.md) named `your_work_projects_vue`. Disabled by default.
- [Changed tab label generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/465889) in GitLab 17.10. Feature flag `your_work_projects_vue` removed.
- [Moved](https://gitlab.com/groups/gitlab-org/-/epics/17208) from GitLab Premium to GitLab Free in 18.0.
- [Enabled for projects in personal namespaces](https://gitlab.com/gitlab-org/gitlab/-/issues/536244) in GitLab 18.0.
{{< /history >}}
To view projects you are a member of:
A project is inactive when:
- It is pending deletion.
- It has been archived.
To view all inactive projects:
1. Select either:
- **View all my projects**, to filter your projects.
- **Explore**, to filter all projects you can access.
1. Select the **Inactive** tab.
Each project in the list shows:
- A badge indicating that the project is archived or marked for deletion.
If the project is marked for deletion, the list also shows:
- The time the project was marked for deletion.
- The time the project is scheduled for final deletion.
- A **Restore** action to stop the project being eventually deleted.
### View only projects you own
To view only the projects you are the owner of:
1. On the left sidebar, select **Search or go to**.
1. Select **View all my projects**.
1. Select the **Yours** tab.
1. Select either:
- **View all your projects**, to filter your projects.
- **Explore**, to filter all projects you can access.
1. Above the list of projects, select **Search or filter results**.
1. From the **Role** dropdown list, select **Owner**.
{{< alert type="note" >}}
## View project activity
This tab appears as **Member** when the `your_work_projects_vue` feature flag is enabled.
To view the activity of a project:
{{< /alert >}}
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Manage > Activity**.
1. Optional. To filter activity by contribution type, select a tab:
## View personal projects
- **All**: All contributions by project members.
- **Push events**: Push events in the project.
- **Merge events**: Accepted merge requests in the project.
- **Issue events**: Issues opened and closed in the project.
- **Comments**: Comments posted by project members.
- **Designs**: Designs added, updated, and removed in the project.
- **Team**: Members who joined and left the project.
Personal projects are projects created under your personal namespace.
GitLab removes project activity events older than three years from the events table for performance reasons.
For example, if you create an account with the username `alex`, and create a project
called `my-project` under your username, the project is created at `https://gitlab.example.com/alex/my-project`.
## Filter projects by language
To view your personal projects:
{{< history >}}
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/385465) in GitLab 15.9 [with a flag](../../administration/feature_flags/_index.md) named `project_language_search`. Enabled by default.
- [Generally available](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110956) in GitLab 15.9. Feature flag `project_language_search` removed.
{{< /history >}}
You can filter projects by the programming language they use. To do this:
1. On the left sidebar, select **Search or go to**.
1. Select **View all my projects**.
1. Select the **Personal** tab.
1. Select either:
- **View all your projects**, to filter your projects.
- **Explore**, to filter all projects you can access.
1. Above the list of projects, select **Search or filter results**.
1. From the **Language** dropdown list, select the language you want to filter projects by.
Or
A list of projects that use the selected language is displayed.
1. On the left sidebar, select your avatar and then your username.
1. On the left sidebar, select **Personal projects**.
## Star a project
## View starred projects
You can star projects you use frequently to make them easier to find.
To view projects you have [starred](#star-a-project):
To star a project:
1. On the left sidebar, select **Search or go to**.
1. Select **View all my projects**.
1. Select the **Starred** tab.
1. On the left sidebar, select **Search or go to** and find your project.
1. In the upper-right corner of the page, select **Star**.
Or
## Leave a project
1. On the left sidebar, select your avatar and then your username.
1. On the left sidebar, select **Starred projects**.
{{< history >}}
## Edit project name and description
- The button to leave a project [moved](https://gitlab.com/gitlab-org/gitlab/-/issues/431539) to the Actions menu in GitLab 16.7.
{{< /history >}}
When you leave a project:
- You are no longer a project member and cannot contribute.
- All the issues and merge requests that were assigned
to you are unassigned.
Prerequisites:
- You can leave a project this way only when a project is part of a group under a [group namespace](../namespace/_index.md).
- You must be a [direct member](members/_index.md#membership-types) of the project.
To leave a project:
1. On the left sidebar, select **Search or go to** and find your project.
1. On the project overview page, in the upper-right corner, select **Actions** ({{< icon name="ellipsis_v" >}}).
1. Select **Leave project**, then **Leave project** again.
## Edit a project
Use the project general settings to edit your project details.
@ -185,16 +255,40 @@ Prerequisites:
Components published in the CI/CD catalog require a project description.
1. Select **Save changes**.
## Add a project avatar
### Rename a repository
A project's repository name defines its URL.
Prerequisites:
- You must be an administrator or have the Maintainer or Owner role for the project.
{{< alert type="note" >}}
When you change the repository path, users may experience issues if they push to, or pull from, the old URL.
For more information on redirect duration and its side-effects, see
[redirects when renaming repositories](repository/_index.md#repository-path-changes).
{{< /alert >}}
To rename a repository:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Settings > General**.
1. Expand **Advanced**.
1. In the **Change path** text box, edit the path.
1. Select **Change path**.
### Add a project avatar
Add a project avatar to help visually identify your project. If you do not add an avatar, GitLab displays the first letter of your project name as the default project avatar.
To add a project avatar, use one of the following methods:
- [Add a logo](#add-a-logo-to-your-repository) to your repository.
- [Upload an avatar](#upload-an-avatar-in-project-settings) in your project settings.
- Add a logo to your repository.
- Upload an avatar in your project settings.
### Add a logo to your repository
#### Add a logo to your repository
If you haven't uploaded an avatar to your project settings, GitLab looks for a file named `logo` in your repository to use as the default project avatar.
@ -209,7 +303,7 @@ To add a logo file to use as your project avatar:
1. On the left sidebar, select **Search or go to** and find your project.
1. In the root of your project repository, upload the logo file.
### Upload an avatar in project settings
#### Upload an avatar in project settings
Prerequisites:
@ -231,14 +325,138 @@ To upload an avatar in your project settings:
1. Select your avatar file.
1. Select **Save changes**.
## Star a project
## Delete a project
You can star projects you use frequently to make them easier to find.
{{< history >}}
To star a project:
- Default behavior [changed](https://gitlab.com/gitlab-org/gitlab/-/issues/389557) to delayed project deletion for Premium and Ultimate tiers on [GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/393622) and [GitLab Self-Managed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/119606) in 16.0.
- Option to delete projects immediately as a group setting removed [on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/393622) and [on GitLab Self-Managed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/119606) in GitLab 16.0.
- Default behavior changed to delayed project deletion for [GitLab Free](https://gitlab.com/groups/gitlab-org/-/epics/17208) and [personal projects](https://gitlab.com/gitlab-org/gitlab/-/issues/536244) in 18.0.
- Option to delete projects immediately [moved](https://gitlab.com/groups/gitlab-org/-/epics/17208) from GitLab Premium to GitLab Free in 18.0.
{{< /history >}}
You can schedule a project for deletion.
By default, when you delete a project for the first time, it enters a pending deletion state.
Delete a project again to remove it immediately.
On GitLab.com, after a project is deleted, its data is retained for 30 days.
Prerequisites:
- You must have the Owner role for a project.
- Owners must be [allowed to delete projects](../../administration/settings/visibility_and_access_controls.md#restrict-project-deletion-to-administrators).
To delete a project:
1. On the left sidebar, select **Search or go to** and find your project.
1. In the upper-right corner of the page, select **Star**.
1. Select **Settings > General**.
1. Expand **Advanced**.
1. In the **Delete project** section, select **Delete project**.
1. On the confirmation dialog, enter the project name and select **Yes, delete project**.
1. Optional. To delete the project immediately, repeat these steps.
You can also [delete projects using the Rails console](troubleshooting.md#delete-a-project-using-console).
If the user who scheduled the project deletion loses access to the project before the deletion occurs
(for example, by leaving the project, having their role downgraded, or being banned from the project),
the deletion job restores the project. However, if the user regains access before the deletion job runs,
the job removes the project permanently.
### Restore a project
{{< history >}}
- [Moved](https://gitlab.com/groups/gitlab-org/-/epics/17208) from GitLab Premium to GitLab Free in 18.0.
- [Enabled for projects in personal namespaces](https://gitlab.com/gitlab-org/gitlab/-/issues/536244) in GitLab 18.0.
{{< /history >}}
Prerequisites:
- You must have the Owner role for the project.
To restore a project pending deletion:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Settings > General**.
1. Expand **Advanced**.
1. In the **Restore project** section, select **Restore project**.
## Archive a project
{{< history >}}
- Pages removal [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/343109) in GitLab 17.5.
{{< /history >}}
{{< alert type="note" >}}
When a project is archived, its fork relationship is removed and any open merge requests from forks
targeting this project are automatically closed.
{{< /alert >}}
When you archive a project, some features become read-only.
These features are still accessible, but not writable.
- Repository
- Packages
- Issues
- Merge requests
- Feature flags
- Pull mirroring
- All other project features
Active pipeline schedules of archived projects don't become read-only.
If the project has deployed Pages, they are removed along with any custom domains,
and the Pages link is no longer accessible.
Archived projects are:
- Labeled with an `archived` badge on the project page.
- Listed in the **Inactive** tab on the group page, **Your work** page, and **Explore** page.
- Read-only.
Prerequisites:
- [Deactivate](../../ci/pipelines/schedules.md#edit-a-pipeline-schedule) or delete any active pipeline schedules for the project.
<!-- LP: Remove this prerequisite after the issue is resolved (when a project is archived, active pipeline schedules continue to run). -->
To archive a project:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Settings > General**.
1. Expand **Advanced**.
1. In the **Archive project** section, select **Archive project**.
1. To confirm, select **OK**.
### Unarchive a project
When you unarchive a project, the read-only restriction is removed,
and the project becomes available in project lists.
Prerequisites:
- You must be an administrator or have the Owner role for the project.
1. Find the archived project.
1. On the left sidebar, select **Search or go to**.
1. Select **View all my projects**.
1. Select **Explore projects**.
1. In the **Sort projects** dropdown list, select **Show archived projects**.
1. In the **Filter by name** field, enter the project name.
1. Select the project link.
1. On the left sidebar, select **Settings > General**.
1. Under **Advanced**, select **Expand**.
1. In the **Unarchive project** section, select **Unarchive project**.
1. To confirm, select **OK**.
The deployed Pages are not restored and you must rerun the pipeline.
When a project is unarchived, its pull mirroring process will automatically resume.
## Transfer a project
@ -325,280 +543,6 @@ When you transfer a project from a namespace licensed for GitLab.com Premium or
- [Pipeline subscriptions](../../ci/pipelines/_index.md#trigger-a-pipeline-when-an-upstream-project-is-rebuilt-deprecated)
and [test cases](../../ci/test_cases/_index.md) are deleted.
## Delete a project
{{< history >}}
- Default behavior [changed](https://gitlab.com/gitlab-org/gitlab/-/issues/389557) to delayed project deletion for Premium and Ultimate tiers on [GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/393622) and [GitLab Self-Managed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/119606) in 16.0.
- Option to delete projects immediately as a group setting removed [on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/393622) and [on GitLab Self-Managed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/119606) in GitLab 16.0.
- Default behavior changed to delayed project deletion for [GitLab Free](https://gitlab.com/groups/gitlab-org/-/epics/17208) and [personal projects](https://gitlab.com/gitlab-org/gitlab/-/issues/536244) in 18.0.
- Option to delete projects immediately [moved](https://gitlab.com/groups/gitlab-org/-/epics/17208) from GitLab Premium to GitLab Free in 18.0.
{{< /history >}}
You can schedule a project for deletion.
By default, when you delete a project for the first time, it enters a pending deletion state.
Delete a project again to remove it immediately.
On GitLab.com, after a project is deleted, its data is retained for seven days.
Prerequisites:
- You must have the Owner role for a project.
- Owners must be [allowed to delete projects](../../administration/settings/visibility_and_access_controls.md#restrict-project-deletion-to-administrators).
To delete a project:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Settings > General**.
1. Expand **Advanced**.
1. In the **Delete project** section, select **Delete project**.
1. On the confirmation dialog, enter the project name and select **Yes, delete project**.
1. Optional. To delete the project immediately, repeat these steps.
You can also [delete projects using the Rails console](troubleshooting.md#delete-a-project-using-console).
If the user who scheduled the project deletion loses access to the project before the deletion occurs
(for example, by leaving the project, having their role downgraded, or being banned from the project),
the deletion job restores the project. However, if the user regains access before the deletion job runs,
the job removes the project permanently.
### View projects pending deletion
{{< history >}}
- [Changed](https://gitlab.com/groups/gitlab-org/-/epics/13066) tab label from "Pending deletion" to "Inactive" in GitLab 17.9 [with a flag](../../administration/feature_flags/_index.md) named `your_work_projects_vue`. Disabled by default.
- [Changed tab label generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/465889) in GitLab 17.10. Feature flag `your_work_projects_vue` removed.
- [Moved](https://gitlab.com/groups/gitlab-org/-/epics/17208) from GitLab Premium to GitLab Free in 18.0.
- [Enabled for projects in personal namespaces](https://gitlab.com/gitlab-org/gitlab/-/issues/536244) in GitLab 18.0.
{{< /history >}}
To view a list of all projects that are pending deletion:
1. On the left sidebar, select **Search or go to**.
1. Select **View all my projects**.
1. Select the **Inactive** tab.
Each project in the list shows:
- A badge indicating that the project has been marked for deletion.
- The time the project was marked for deletion.
- The time the project is scheduled for final deletion.
- A **Restore** action to stop the project being eventually deleted.
### Restore a project
{{< history >}}
- [Moved](https://gitlab.com/groups/gitlab-org/-/epics/17208) from GitLab Premium to GitLab Free in 18.0.
- [Enabled for projects in personal namespaces](https://gitlab.com/gitlab-org/gitlab/-/issues/536244) in GitLab 18.0.
{{< /history >}}
Prerequisites:
- You must have the Owner role for the project.
To restore a project pending deletion:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Settings > General**.
1. Expand **Advanced**.
1. In the **Restore project** section, select **Restore project**.
## Archive a project
{{< history >}}
- Pages removal [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/343109) in GitLab 17.5.
{{< /history >}}
{{< alert type="note" >}}
When a project is archived, its fork relationship is removed and any open merge requests from forks
targeting this project are automatically closed.
{{< /alert >}}
When you archive a project, some features become read-only.
These features are still accessible, but not writable.
- Repository
- Packages
- Issues
- Merge requests
- Feature flags
- Pull mirroring
- All other project features
Active pipeline schedules of archived projects don't become read-only.
If the project has deployed Pages, they are removed along with any custom domains,
and the Pages link is no longer accessible.
Archived projects are:
- Labeled with an `archived` badge on the project page.
- Listed in the **Inactive** tab on the group page, **Your work** page, and **Explore** page.
- Read-only.
Prerequisites:
- [Deactivate](../../ci/pipelines/schedules.md#edit-a-pipeline-schedule) or delete any active pipeline schedules for the project.
<!-- LP: Remove this prerequisite after the issue is resolved (when a project is archived, active pipeline schedules continue to run). -->
To archive a project:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Settings > General**.
1. Expand **Advanced**.
1. In the **Archive project** section, select **Archive project**.
1. To confirm, select **OK**.
## Unarchive a project
When you unarchive a project, the read-only restriction is removed,
and the project becomes available in project lists.
Prerequisites:
- You must be an administrator or have the Owner role for the project.
1. Find the archived project.
1. On the left sidebar, select **Search or go to**.
1. Select **View all my projects**.
1. Select **Explore projects**.
1. In the **Sort projects** dropdown list, select **Show archived projects**.
1. In the **Filter by name** field, enter the project name.
1. Select the project link.
1. On the left sidebar, select **Settings > General**.
1. Under **Advanced**, select **Expand**.
1. In the **Unarchive project** section, select **Unarchive project**.
1. To confirm, select **OK**.
The deployed Pages are not restored and you must rerun the pipeline.
When a project is unarchived, its pull mirroring process will automatically resume.
## View project activity
To view the activity of a project:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Manage > Activity**.
1. Optional. To filter activity by contribution type, select a tab:
- **All**: All contributions by project members.
- **Push events**: Push events in the project.
- **Merge events**: Accepted merge requests in the project.
- **Issue events**: Issues opened and closed in the project.
- **Comments**: Comments posted by project members.
- **Designs**: Designs added, updated, and removed in the project.
- **Team**: Members who joined and left the project.
### Event time period limit
GitLab removes project activity events older than 3 years from the events table for performance reasons.
## Search in projects
To search through your projects, on the left sidebar, select **Search or go to**.
GitLab filters as you type.
You can also look for the projects you [starred](#star-a-project) (**Starred projects**).
You can **Explore** all public and internal projects available in GitLab.com, from which you can filter by visibility,
through **Trending**, best rated with **Most stars**, or **All** of them.
You can sort projects by:
- Name
- Created date
- Updated date
- Stars
### Filter projects by language
{{< history >}}
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/385465) in GitLab 15.9 [with a flag](../../administration/feature_flags/_index.md) named `project_language_search`. Enabled by default.
- [Generally available](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110956) in GitLab 15.9. Feature flag `project_language_search` removed.
{{< /history >}}
You can filter projects by the programming language they use. To do this:
1. On the left sidebar, select **Search or go to**.
1. Select either:
- **View all your projects**, to filter your projects.
- **Explore**, to filter all projects you can access.
1. Above the list of projects, select **Search or filter results**.
1. From the **Language** dropdown list, select the language you want to filter projects by.
A list of projects that use the selected language is displayed.
### View only projects you own
To view only the projects you are the owner of:
1. On the left sidebar, select **Search or go to**.
1. Select either:
- **View all your projects**, to filter your projects.
- **Explore**, to filter all projects you can access.
1. Above the list of projects, select **Search or filter results**.
1. From the **Role** dropdown list, select **Owner**.
## Rename a repository
A project's repository name defines its URL.
Prerequisites:
- You must be an administrator or have the Maintainer or Owner role for the project.
{{< alert type="note" >}}
When you change the repository path, users may experience issues if they push to, or pull from, the old URL.
For more information on redirect duration and its side-effects, see
[redirects when renaming repositories](repository/_index.md#repository-path-changes).
{{< /alert >}}
To rename a repository:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Settings > General**.
1. Expand **Advanced**.
1. In the **Change path** text box, edit the path.
1. Select **Change path**.
## Leave a project
{{< history >}}
- The button to leave a project [moved](https://gitlab.com/gitlab-org/gitlab/-/issues/431539) to the Actions menu in GitLab 16.7.
{{< /history >}}
When you leave a project:
- You are no longer a project member and cannot contribute.
- All the issues and merge requests that were assigned
to you are unassigned.
Prerequisites:
- You can leave a project this way only when a project is part of a group under a [group namespace](../namespace/_index.md).
- You must be a [direct member](members/_index.md#membership-types) of the project.
To leave a project:
1. On the left sidebar, select **Search or go to** and find your project.
1. On the project overview page, in the upper-right corner, select **Actions** ({{< icon name="ellipsis_v" >}}).
1. Select **Leave project**, then **Leave project** again.
## Add a compliance framework to a project
{{< details >}}

View File

@ -16,12 +16,9 @@ rspec:
services:
- name: postgres:${POSTGRES_VERSION}
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
needs:
- project: $CI_PROJECT_PATH
job: "db:setup pg16"
ref: "master"
artifacts: true
before_script:
- |
curl -o structure.sql -fsSL --retry 3 --header "JOB-TOKEN: $CI_JOB_TOKEN" "$CI_API_V4_URL/projects/$CI_PROJECT_ID/repository/files/db%2Fstructure.sql/raw?ref=master"
- echo -e "\e[0Ksection_start:`date +%s`:postgresql16\r\e[0KInstalling PostgreSQL 16"
- curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc|gpg --dearmor -o /etc/apt/trusted.gpg.d/postgresql.gpg
- echo "deb https://apt.postgresql.org/pub/repos/apt $(. /etc/os-release && echo "$VERSION_CODENAME")-pgdg main" > /etc/apt/sources.list.d/pgdg.list
@ -29,7 +26,8 @@ rspec:
- echo -e "\e[0Ksection_end:`date +%s`:postgresql16\r\e[0K"
- psql -h postgres -U $POSTGRES_USER -c 'create database gitlabhq_test;'
- psql -h postgres -U $POSTGRES_USER -c 'create database gitlabhq_ci_test;'
- psql -h postgres -U $POSTGRES_USER -q < pg_dumpall.sql > /dev/null
- psql -h postgres -U $POSTGRES_USER -d gitlabhq_test -q < structure.sql > /dev/null
- psql -h postgres -U $POSTGRES_USER -d gitlabhq_ci_test -q < structure.sql > /dev/null
- cp gems/gitlab-backup-cli/spec/fixtures/config/database.yml config/
- "sed -i \"s/username: postgres$/username: $POSTGRES_USER/g\" config/database.yml"
- "sed -i \"s/password:\\s*$/password: $POSTGRES_PASSWORD/g\" config/database.yml"

View File

@ -15,6 +15,8 @@ require_relative 'validation/fixers/missing_index'
require_relative 'validation/validators/different_definition_indexes'
require_relative 'validation/validators/extra_indexes'
require_relative 'validation/validators/missing_indexes'
require_relative 'validation/validators/different_sequence_owners'
require_relative 'validation/validators/missing_sequences'
require_relative 'validation/validators/extra_table_columns'

View File

@ -26,7 +26,9 @@ module Gitlab
end
def statement
"CREATE SEQUENCE #{name}"
statements = "CREATE SEQUENCE #{name};"
statements += "\nALTER SEQUENCE #{name} OWNED BY #{owner}" if owner
statements
end
private

View File

@ -23,7 +23,8 @@ module Gitlab
DifferentDefinitionTables,
DifferentDefinitionIndexes,
DifferentDefinitionTriggers,
DifferentDefinitionForeignKeys
DifferentDefinitionForeignKeys,
DifferentSequenceOwners
]
end

View File

@ -0,0 +1,24 @@
# frozen_string_literal: true
module Gitlab
module Schema
module Validation
module Validators
class DifferentSequenceOwners < Base
ERROR_MESSAGE = "The sequence %s has a different owner between structure.sql and database"
def execute
structure_sql.sequences.filter_map do |sequence|
database_sequence = database.fetch_sequence_by_name(sequence.name)
next if database_sequence.nil?
next if database_sequence.owner == sequence.owner
build_inconsistency(self.class, sequence, database_sequence)
end
end
end
end
end
end
end

View File

@ -1,6 +1,10 @@
CREATE SEQUENCE missing_sequence;
CREATE SEQUENCE shared_audit_event_id_seq;
CREATE SEQUENCE abuse_events_id_seq;
CREATE SEQUENCE zoekt_repositories_id_seq;
ALTER SEQUENCE abuse_events_id_seq OWNED by abuse_events.id;
ALTER SEQUENCE zoekt_repositories_id_seq OWNED by zoekt_repositories.id;
CREATE INDEX missing_index ON events USING btree (created_at, author_id);

View File

@ -28,7 +28,8 @@ RSpec.describe Gitlab::Schema::Validation::Validators::Base, feature_category: :
Gitlab::Schema::Validation::Validators::DifferentDefinitionTables,
Gitlab::Schema::Validation::Validators::DifferentDefinitionIndexes,
Gitlab::Schema::Validation::Validators::DifferentDefinitionTriggers,
Gitlab::Schema::Validation::Validators::DifferentDefinitionForeignKeys
Gitlab::Schema::Validation::Validators::DifferentDefinitionForeignKeys,
Gitlab::Schema::Validation::Validators::DifferentSequenceOwners
])
end
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Schema::Validation::Validators::DifferentSequenceOwners, feature_category: :database do
wrong_sequence_owners = %w[
public.zoekt_repositories_id_seq
]
include_examples 'sequence validators', described_class, wrong_sequence_owners
end

View File

@ -5,7 +5,6 @@ require 'spec_helper'
RSpec.describe Gitlab::Schema::Validation::Validators::MissingSequences, feature_category: :database do
missing_sequences = %w[
public.missing_sequence
public.shared_audit_event_id_seq
public.abuse_events_id_seq
]

View File

@ -4,30 +4,68 @@ require 'spec_helper'
RSpec.shared_examples 'sequence validators' do |validator, expected_result|
let(:structure_file_path) { 'spec/fixtures/structure.sql' }
let(:database_sequences) do
%w[
wrong_sequence
extra_sequence
shared_audit_event_id_seq
]
end
let(:inconsistency_type) { validator.name }
let(:connection_class) { class_double(Class, name: 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter') }
# rubocop:disable RSpec/VerifiedDoubleReference
let(:connection) do
instance_double('connection', class: connection_class, current_schema: 'public')
instance_double('connection', class: connection_class, exec_query: database_sequences, current_schema: 'public')
end
# rubocop:enable RSpec/VerifiedDoubleReference
let(:schema) { 'public' }
let(:database) { Gitlab::Schema::Validation::Sources::Database.new(connection) }
let(:structure_file) { Gitlab::Schema::Validation::Sources::StructureSql.new(structure_file_path, schema) }
before do
allow(database).to receive(:sequence_exists?) do |sequence_name|
database_sequences.include?(sequence_name)
end
let(:database_sequences) do
[
{
'sequence_name' => 'wrong_sequence',
'schema' => schema,
'user_owner' => 'gitlab',
'start_value' => '1',
'increment_by' => '1',
'min_value' => '1',
'max_value' => '9223372036854775807',
'cycle' => 'f',
'cache_size' => '1',
'owned_by_column' => 'some_table.id'
},
{
'sequence_name' => 'extra_sequence',
'schema' => schema,
'user_owner' => 'gitlab',
'start_value' => '1',
'increment_by' => '1',
'min_value' => '1',
'max_value' => '9223372036854775807',
'cycle' => 'f',
'cache_size' => '1',
'owned_by_column' => 'extra_table.id'
},
{
'sequence_name' => 'zoekt_repositories_id_seq',
'schema' => schema,
'user_owner' => 'gitlab',
'start_value' => '1',
'increment_by' => '1',
'min_value' => '1',
'max_value' => '9223372036854775807',
'cycle' => 'f',
'cache_size' => '1',
'owned_by_column' => "wrong_table.id"
},
{
'sequence_name' => 'shared_audit_event_id_seq',
'schema' => schema,
'user_owner' => 'gitlab',
'start_value' => '1',
'increment_by' => '1',
'min_value' => '1',
'max_value' => '9223372036854775807',
'cycle' => 'f',
'cache_size' => '1',
'owned_by_column' => nil
}
]
end
subject(:result) { validator.new(structure_file, database).execute }

View File

@ -8,17 +8,17 @@ module Sidebars
override :link
def link
profile_chat_names_path
user_settings_integration_accounts_path
end
override :title
def title
_('Chat')
s_('Integrations|Integration accounts')
end
override :sprite_icon
def sprite_icon
'comment'
'connected'
end
override :active_routes

View File

@ -3548,9 +3548,6 @@ msgstr ""
msgid "Active Sessions"
msgstr ""
msgid "Active chat names"
msgstr ""
msgid "Active group access tokens"
msgstr ""
@ -12728,9 +12725,6 @@ msgstr ""
msgid "Characters over limit"
msgstr ""
msgid "Chat"
msgstr ""
msgid "ChatMessage|%{project_link}: Pipeline %{pipeline_link} of %{ref_type} %{ref_link} by %{user_combined_name} %{humanized_status} in %{duration}"
msgstr ""
@ -18799,9 +18793,6 @@ msgstr ""
msgid "Could not apply iteration command. There are multiple cadences but no cadence is specified."
msgstr ""
msgid "Could not authorize chat nickname. Try again!"
msgstr ""
msgid "Could not be moved. Select another project or try again."
msgstr ""
@ -18826,7 +18817,7 @@ msgstr ""
msgid "Could not create wiki template"
msgstr ""
msgid "Could not delete chat nickname %{chat_name}."
msgid "Could not delete account nickname %{chat_name}."
msgstr ""
msgid "Could not delete the label %{labelName}. Please try again."
@ -21591,7 +21582,7 @@ msgstr ""
msgid "Deleted"
msgstr ""
msgid "Deleted chat nickname: %{chat_name}!"
msgid "Deleted account nickname: %{chat_name}!"
msgstr ""
msgid "Deleted commits:"
@ -21633,7 +21624,7 @@ msgstr ""
msgid "DeletionSettings|Period that deleted groups and projects will remain restorable for."
msgstr ""
msgid "Denied authorization of chat nickname %{user_name}."
msgid "Denied authorization of account nickname %{user_name}."
msgstr ""
msgid "Deny"
@ -30389,6 +30380,9 @@ msgstr ""
msgid "Group Git LFS status:"
msgstr ""
msgid "Group Name"
msgstr ""
msgid "Group Owner must have signed in with SAML before enabling Group Managed Accounts"
msgstr ""
@ -30488,9 +30482,6 @@ msgstr ""
msgid "Group milestone"
msgstr ""
msgid "Group name"
msgstr ""
msgid "Group name (your organization)"
msgstr ""
@ -33671,6 +33662,9 @@ msgstr ""
msgid "Integrations|Confirm %{type} exclusion removal"
msgstr ""
msgid "Integrations|Connected integration accounts"
msgstr ""
msgid "Integrations|Connection details"
msgstr ""
@ -33680,6 +33674,9 @@ msgstr ""
msgid "Integrations|Connection successful."
msgstr ""
msgid "Integrations|Could not authorize integration account nickname. Try again!"
msgstr ""
msgid "Integrations|Create new issue in Jira"
msgstr ""
@ -33758,6 +33755,9 @@ msgstr ""
msgid "Integrations|Instance-level integration management"
msgstr ""
msgid "Integrations|Integration accounts"
msgstr ""
msgid "Integrations|Integration cannot be reset."
msgstr ""
@ -33770,6 +33770,9 @@ msgstr ""
msgid "Integrations|Keep your PHP dependencies updated on Packagist."
msgstr ""
msgid "Integrations|Manage your integration accounts in GitLab. Use these connected integrations to perform actions in GitLab."
msgstr ""
msgid "Integrations|Mattermost slash commands"
msgstr ""
@ -33866,6 +33869,9 @@ msgstr ""
msgid "Integrations|You can use this alias in your Slack commands"
msgstr ""
msgid "Integrations|You do not have any connected integration accounts."
msgstr ""
msgid "Integrations|You haven't activated any integrations yet."
msgstr ""
@ -48460,6 +48466,9 @@ msgstr ""
msgid "Project ID"
msgstr ""
msgid "Project Name"
msgstr ""
msgid "Project Security Status"
msgstr ""
@ -53232,6 +53241,9 @@ msgstr ""
msgid "Runners|Configuration"
msgstr ""
msgid "Runners|Container"
msgstr ""
msgid "Runners|Containers"
msgstr ""
@ -53648,6 +53660,9 @@ msgstr ""
msgid "Runners|Only administrators can view this."
msgstr ""
msgid "Runners|Operating System"
msgstr ""
msgid "Runners|Operating systems"
msgstr ""
@ -54151,6 +54166,9 @@ msgstr ""
msgid "Runners|View installation instructions"
msgstr ""
msgid "Runners|View instructions in %{helpLink}."
msgstr ""
msgid "Runners|View metrics"
msgstr ""
@ -54202,6 +54220,9 @@ msgstr ""
msgid "Runners|Zone must have the right format."
msgstr ""
msgid "Runners|documentation"
msgstr ""
msgid "Runner|1 day selected"
msgid_plural "Runner|%d days selected"
msgstr[0] ""
@ -72607,9 +72628,6 @@ msgstr ""
msgid "You don't have any WebAuthn devices registered yet."
msgstr ""
msgid "You don't have any active chat names."
msgstr ""
msgid "You don't have any applications."
msgstr ""

View File

@ -1,41 +0,0 @@
# frozen_string_literal: true
module RuboCop
module Cop
module Search
# Cop that prevents checking migration_has_finished? on deprecated migrations
#
# @example
#
# # bad
# def disable_project_joins_for_blob?
# Elastic::DataMigrationService
# .migration_has_finished?(:backfill_archived_on_issues)
# end
#
# # good
# def disable_project_joins_for_blob?
# Elastic::DataMigrationService.migration_has_finished?(:backfill_archived_on_issues)
# end
class AvoidCheckingFinishedOnDeprecatedMigrations < RuboCop::Cop::Base
MSG = 'Migration is deprecated and can not be used with `migration_has_finished?`.'
DEPRECATED_MIGRATIONS = [
:backfill_archived_on_issues
].freeze
def_node_matcher :deprecated_migration?, <<~PATTERN
(send
(const (const {nil? cbase} :Elastic) :DataMigrationService) :migration_has_finished?
(sym {#{DEPRECATED_MIGRATIONS.map { |m| ":#{m}" }.join(' ')}}))
PATTERN
RESTRICT_ON_SEND = %i[migration_has_finished?].freeze
def on_send(node)
add_offense(node) if deprecated_migration?(node)
end
end
end
end
end

View File

@ -0,0 +1,135 @@
# frozen_string_literal: true
require 'yaml'
module RuboCop
module Cop
module Search
# Cop that prevents checking migration_has_finished? on obsolete or non-existing migrations
#
# @example
#
# # bad - obsolete migration
# def disable_project_joins_for_blob?
# Elastic::DataMigrationService
# .migration_has_finished?(:backfill_archived_on_issues)
# end
#
# # bad - non-existing migration
# def disable_project_joins_for_blob?
# Elastic::DataMigrationService
# .migration_has_finished?(:non_existing_migration)
# end
#
# # good - valid migration
# def disable_project_joins_for_blob?
# Elastic::DataMigrationService.migration_has_finished?(:backfill_work_items_incorrect_data)
# end
class AvoidCheckingFinishedOnInvalidMigrations < RuboCop::Cop::Base
MSG_OBSOLETE = 'Migration is obsolete and can not be used with `migration_has_finished?`.'
MSG_NON_EXISTING = 'Migration does not exist and can not be used with `migration_has_finished?`.'
MSG_MISSING_IMPLEMENTATION = 'Migration implementation file is missing.'
DOCS_PATH = 'ee/elastic/docs'
MIGRATIONS_PATH = 'ee/elastic/migrate'
MIGRATION_REGEXP = /\A([0-9]+)_([_a-z0-9]*)\.rb\z/
# @!method migration_has_finished?(node)
def_node_matcher :migration_has_finished?, <<~PATTERN
(send
(const (const {nil? cbase} :Elastic) :DataMigrationService) :migration_has_finished?
(sym $_))
PATTERN
RESTRICT_ON_SEND = %i[migration_has_finished?].freeze
def on_send(node)
return unless migrations_available?
migration_has_finished?(node) do |migration_name|
check_migration(node, migration_name)
end
end
alias_method :on_csend, :on_send
private
def migrations_available?
Dir.exist?(DOCS_PATH) && Dir.exist?(MIGRATIONS_PATH)
end
def check_migration(node, migration_name)
migration_info = find_migration_info(migration_name)
if migration_info.nil?
add_offense(node, message: MSG_NON_EXISTING)
elsif migration_info[:obsolete]
add_offense(node, message: MSG_OBSOLETE)
elsif !migration_file_exists?(migration_name)
add_offense(node, message: MSG_MISSING_IMPLEMENTATION)
end
end
def find_migration_info(migration_name)
return @migration_cache[migration_name] if @migration_cache&.key?(migration_name)
@migration_cache ||= load_migrations
@migration_cache[migration_name]
end
def migration_file_exists?(migration_name)
migration_files_cache.include?(migration_name)
end
def migration_files_cache
@migration_files_cache ||= load_migration_files
end
def load_migration_files
migration_files = Set.new
Dir.glob("#{MIGRATIONS_PATH}/*.rb").each do |file|
filename = File.basename(file, '.rb')
migration_files.add(::Regexp.last_match(1).to_sym) if filename =~ /\A[0-9]+_(.+)\z/
end
migration_files
end
def load_migrations
migrations = {}
migration_files = Dir.glob("#{DOCS_PATH}/*.yml")
migration_files.each do |file|
yaml_content = YAML.safe_load_file(file)
next unless yaml_content.is_a?(Hash)
name = yaml_content['name']
next unless name
# Convert CamelCase to snake_case to match the symbol format used in code
snake_case_name = name.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
.downcase
.to_sym
migrations[snake_case_name] = {
obsolete: yaml_content['obsolete'] == true,
version: yaml_content['version'],
milestone: yaml_content['milestone']
}
rescue StandardError => e
# Skip files that can't be parsed, but log for debugging
warn "Warning: Could not parse migration documentation file #{file}: #{e.message}"
next
end
migrations
end
end
end
end
end

View File

@ -0,0 +1,86 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Profiles::ChatNamesController, feature_category: :integrations do
let_it_be(:user) { create(:user) }
before do
sign_in(user)
end
describe 'POST #create' do
let(:token) { 'valid_token' }
let(:chat_name_token_double) { instance_double(Gitlab::ChatNameToken) }
let(:chat_name_params) do
{
team_id: 'T123',
chat_id: 'U123',
chat_name: 'test_user'
}
end
before do
allow(Gitlab::ChatNameToken).to receive(:new).with(token).and_return(chat_name_token_double)
allow(chat_name_token_double).to receive(:get).and_return(chat_name_params)
allow(chat_name_token_double).to receive(:delete)
end
context 'when save succeeds' do
it 'sets success flash message and redirects' do
post :create, params: { token: token }
expect(flash[:notice]).to include('Authorized test_user')
expect(response).to redirect_to(user_settings_integration_accounts_path)
end
end
context 'when save fails' do
it 'sets error flash message and redirects' do
allow_next_instance_of(ChatName) do |instance|
allow(instance).to receive(:save).and_return(false)
end
post :create, params: { token: token }
expect(flash[:alert]).to eq('Could not authorize integration account nickname. Try again!')
expect(response).to redirect_to(user_settings_integration_accounts_path)
end
end
end
describe 'DELETE #destroy' do
let_it_be(:chat_name) { create(:chat_name, user: user) }
it 'destroys the chat_name' do
expect { delete :destroy, params: { id: chat_name.id } }
.to change { ChatName.count }.by(-1)
end
it 'redirects to the integration accounts page' do
delete :destroy, params: { id: chat_name.id }
expect(response).to redirect_to(user_settings_integration_accounts_path)
end
context 'when destroy fails' do
it 'sets error flash message and redirects' do
mock_chat_name = instance_double(ChatName)
allow(mock_chat_name).to receive_messages(
destroy: false,
chat_name: chat_name.chat_name
)
# As chat_names_controller finds the real db object, #destroy would rarely fail naturally
# Hence, we're intercepting the controller-find process to return failing mock
allow(controller).to receive(:chat_names).and_return(user.chat_names)
allow(user.chat_names).to receive(:find).with(chat_name.id.to_s).and_return(mock_chat_name)
delete :destroy, params: { id: chat_name.id }
expect(flash[:alert]).to eq("Could not delete account nickname #{chat_name.chat_name}.")
expect(response).to redirect_to(user_settings_integration_accounts_path)
end
end
end
end

View File

@ -138,7 +138,7 @@ RSpec.describe 'Database schema',
merge_requests_compliance_violations: %w[target_project_id],
merge_request_diffs: %w[project_id],
merge_request_diff_files: %w[project_id],
merge_request_diff_commits: %w[commit_author_id committer_id merge_request_commits_metadata_id],
merge_request_diff_commits: %w[project_id commit_author_id committer_id merge_request_commits_metadata_id],
# merge_request_diff_commits_b5377a7a34 is the temporary table for the merge_request_diff_commits partitioning
# backfill. It will get foreign keys after the partitioning is finished.
merge_request_diff_commits_b5377a7a34: %w[merge_request_diff_id commit_author_id committer_id project_id],

View File

@ -62,7 +62,7 @@ RSpec.describe 'Profile > Chat', feature_category: :integrations do
end
it 'goes to list of chat names and see chat account' do
expect(page).to have_current_path(profile_chat_names_path, ignore_query: true)
expect(page).to have_current_path(user_settings_integration_accounts_path, ignore_query: true)
expect(page).to have_content('my_chat_team')
expect(page).to have_content('my_chat_user')
end
@ -80,7 +80,7 @@ RSpec.describe 'Profile > Chat', feature_category: :integrations do
end
it 'goes to list of chat names and do not see chat account' do
expect(page).to have_current_path(profile_chat_names_path, ignore_query: true)
expect(page).to have_current_path(user_settings_integration_accounts_path, ignore_query: true)
expect(page).not_to have_content('my_chat_team')
expect(page).not_to have_content('my_chat_user')
end
@ -97,7 +97,7 @@ RSpec.describe 'Profile > Chat', feature_category: :integrations do
let_it_be(:chat_name) { create(:chat_name, user: user) }
before do
visit profile_chat_names_path
visit user_settings_integration_accounts_path
end
it 'sees chat user' do
@ -108,7 +108,7 @@ RSpec.describe 'Profile > Chat', feature_category: :integrations do
it 'removes chat account' do
click_link 'Remove'
expect(page).to have_content("You don't have any active chat names.")
expect(page).to have_content("You do not have any connected integration accounts.")
end
end
end

View File

@ -182,7 +182,7 @@ RSpec.describe "Compare", :js, feature_category: :source_code_management do
end
describe "compare view of tags" do
it "compares tags" do
it "compares tags", quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/547846' do
visit project_compare_index_path(project, from: "master", to: "master")
select_using_dropdown "from", "v1.0.0"

View File

@ -332,4 +332,20 @@ describe('GkeRegistrationInstructions', () => {
});
});
});
describe('when isWidget is true', () => {
beforeEach(() => {
createComponent({
props: { isWidget: true },
});
});
it('does not display h2 headings', () => {
expect(wrapper.find('h2').exists()).toBe(false);
});
it('does not display token message', () => {
expect(findTokenMessage().exists()).toBe(false);
});
});
});

View File

@ -48,6 +48,7 @@ describe('GoogleCloudRegistrationInstructions', () => {
const findZoneLink = () => wrapper.findByTestId('zone-link');
const findMachineTypeLink = () => wrapper.findByTestId('machine-types-link');
const findToken = () => wrapper.findByTestId('runner-token');
const findTokenMessage = () => wrapper.findByTestId('runner-token-message');
const findClipboardButton = () => wrapper.findComponent(ClipboardButton);
const findAlert = () => wrapper.findComponent(GlAlert);
const findInstructionsButton = () => wrapper.findByTestId('show-instructions-button');
@ -327,4 +328,20 @@ describe('GoogleCloudRegistrationInstructions', () => {
});
});
});
describe('when isWidget is true', () => {
beforeEach(() => {
createComponent({
props: { isWidget: true },
});
});
it('does not display h2 headings', () => {
expect(wrapper.find('h2').exists()).toBe(false);
});
it('does not display token message', () => {
expect(findTokenMessage().exists()).toBe(false);
});
});
});

View File

@ -82,6 +82,7 @@ describe('RegistrationInstructions', () => {
propsData: {
runnerId: mockRunnerId,
platform: DEFAULT_PLATFORM,
isWidget: false,
...props,
},
stubs: {
@ -360,6 +361,7 @@ describe('RegistrationInstructions', () => {
token: mockAuthenticationToken,
groupPath: 'mock/group/path',
projectPath: null,
isWidget: false,
});
});
@ -377,6 +379,7 @@ describe('RegistrationInstructions', () => {
token: mockAuthenticationToken,
projectPath: 'mock/project/path',
groupPath: null,
isWidget: false,
});
});
@ -412,6 +415,7 @@ describe('RegistrationInstructions', () => {
token: mockAuthenticationToken,
groupPath: 'mock/group/path',
projectPath: null,
isWidget: false,
});
});
@ -429,6 +433,7 @@ describe('RegistrationInstructions', () => {
token: mockAuthenticationToken,
projectPath: 'mock/project/path',
groupPath: null,
isWidget: false,
});
});

View File

@ -0,0 +1,69 @@
import { GlSprintf } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { TEST_HOST } from 'helpers/test_constants';
import WizardOperatingSystemInstruction from '~/ci/runner/components/registration/wizard_operating_system_instruction.vue';
import PlatformsDrawer from '~/ci/runner/components/registration/platforms_drawer.vue';
import CliCommand from '~/ci/runner/components/registration/cli_command.vue';
import { EXECUTORS_HELP_URL, SERVICE_COMMANDS_HELP_URL } from '~/ci/runner/constants';
describe('New Runner Registration Operation Systems Instructions', () => {
let wrapper;
const createComponent = () => {
wrapper = shallowMountExtended(WizardOperatingSystemInstruction, {
propsData: {
token: 'token-123',
title: 'Linux',
platform: 'linux',
},
stubs: {
GlSprintf,
},
});
};
beforeEach(() => {
createComponent();
});
const findPlatformsDrawer = () => wrapper.findComponent(PlatformsDrawer);
const findStep1Section = () => wrapper.findByTestId('step-1');
const findStep2Section = () => wrapper.findByTestId('step-2');
const findStep3Section = () => wrapper.findByTestId('step-3');
const findExecutorsHelpLink = () => wrapper.findByTestId('executors-help-link');
const findServiceCommandsHelpLink = () => wrapper.findByTestId('service-commands-help-link');
describe('platform installation drawer instructions', () => {
it('opens and closes the drawer', async () => {
expect(findPlatformsDrawer().props('open')).toBe(false);
expect(wrapper.findByTestId('how-to-install-btn').exists()).toBe(true);
await wrapper.findByTestId('how-to-install-btn').vm.$emit('click');
expect(findPlatformsDrawer().props('open')).toBe(true);
await findPlatformsDrawer().vm.$emit('close');
expect(findPlatformsDrawer().props('open')).toBe(false);
});
});
it('renders step 1', () => {
expect(findStep1Section().findComponent(CliCommand).props()).toMatchObject({
command: ['gitlab-runner register', ` --url ${TEST_HOST}`, ` --token token-123`],
prompt: '$',
});
});
it('renders step 2', () => {
expect(findStep2Section().exists()).toBe(true);
expect(findExecutorsHelpLink().attributes('href')).toBe(EXECUTORS_HELP_URL);
});
it('renders step 3', () => {
expect(findStep3Section().findComponent(CliCommand).props()).toMatchObject({
command: 'gitlab-runner run',
prompt: '$',
});
expect(findServiceCommandsHelpLink().attributes('href')).toBe(SERVICE_COMMANDS_HELP_URL);
});
});

View File

@ -1611,7 +1611,7 @@ RSpec.describe GitlabSchema.types['Project'], feature_category: :groups_and_proj
end
describe 'fields with :ai_workflows scope' do
%w[id fullPath workItems].each do |field_name|
%w[id fullPath workItems languages].each do |field_name|
it "includes :ai_workflows scope for the #{field_name} field" do
field = described_class.fields[field_name]
expect(field.instance_variable_get(:@scopes)).to include(:ai_workflows)

View File

@ -12,4 +12,19 @@ RSpec.describe Types::Projects::RepositoryLanguageType do
:color
)
end
describe '.authorization_scopes' do
it 'allows ai_workflows scope token' do
expect(described_class.authorization_scopes).to include(:ai_workflows)
end
end
describe 'fields with :ai_workflows scope' do
%w[name share].each do |field_name|
it "includes :ai_workflows scope for the #{field_name} field" do
field = described_class.fields[field_name]
expect(field.instance_variable_get(:@scopes)).to include(:ai_workflows)
end
end
end
end

View File

@ -929,6 +929,7 @@ project:
- approval_policies
- project_requirement_compliance_statuses
- analyzer_statuses
- configured_ai_catalog_items
award_emoji:
- awardable
- user

View File

@ -4,9 +4,9 @@ require 'spec_helper'
RSpec.describe Sidebars::UserSettings::Menus::ChatMenu, feature_category: :navigation do
it_behaves_like 'User settings menu',
link: '/-/profile/chat',
title: _('Chat'),
icon: 'comment',
link: '/-/user_settings/integration_accounts',
title: _('Integration accounts'),
icon: 'connected',
active_routes: { controller: :chat_names }
it_behaves_like 'User settings menu #render? method'

View File

@ -26,7 +26,7 @@ RSpec.describe Slack::BlockKit::AppHomeOpened, feature_category: :integrations d
{
type: 'button',
text: include({ text: 'Connect your GitLab account' }),
url: include(Gitlab::Routing.url_helpers.new_profile_chat_name_url)
url: include(Gitlab::Routing.url_helpers.new_user_settings_integration_account_url)
}
)
]

View File

@ -1,31 +0,0 @@
# frozen_string_literal: true
require 'rubocop_spec_helper'
require_relative '../../../../rubocop/cop/search/avoid_checking_finished_on_deprecated_migrations'
RSpec.describe RuboCop::Cop::Search::AvoidCheckingFinishedOnDeprecatedMigrations, feature_category: :global_search do
context 'when a deprecated class is used with migration_has_finished?' do
it 'flags it as an offense' do
expect_offense <<~SOURCE
return if Elastic::DataMigrationService.migration_has_finished?(:backfill_archived_on_issues)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Migration is deprecated and can not be used with `migration_has_finished?`.
SOURCE
end
end
context 'when a non deprecated class is used with migration_has_finished?' do
it 'does not flag it as an offense' do
expect_no_offenses <<~SOURCE
return if Elastic::DataMigrationService.migration_has_finished?(:backfill_project_permissions_in_blobs)
SOURCE
end
end
context 'when migration_has_finished? method is called on another class' do
it 'does not flag it as an offense' do
expect_no_offenses <<~SOURCE
return if Klass.migration_has_finished?(:backfill_archived_on_issues)
SOURCE
end
end
end

View File

@ -0,0 +1,80 @@
# frozen_string_literal: true
require 'rubocop_spec_helper'
require_relative '../../../../rubocop/cop/search/avoid_checking_finished_on_invalid_migrations'
RSpec.describe RuboCop::Cop::Search::AvoidCheckingFinishedOnInvalidMigrations, feature_category: :global_search do
# Only run these tests if the migration directories are present
if Dir.exist?('ee/elastic/docs') && Dir.exist?('ee/elastic/migrate')
context 'when an obsolete migration is used with migration_has_finished?' do
it 'flags it as an offense' do
expect_offense <<~RUBY
return if Elastic::DataMigrationService.migration_has_finished?(:backfill_archived_on_issues)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Migration is obsolete and can not be used with `migration_has_finished?`.
RUBY
end
end
context 'when a non-existing migration is used with migration_has_finished?' do
it 'flags it as an offense' do
expect_offense <<~RUBY
return if Elastic::DataMigrationService.migration_has_finished?(:non_existing_migration)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Migration does not exist and can not be used with `migration_has_finished?`.
RUBY
end
end
context 'when a valid migration is used with migration_has_finished?' do
it 'does not flag it as an offense' do
expect_no_offenses <<~RUBY
return if Elastic::DataMigrationService.migration_has_finished?(:backfill_work_items_incorrect_data)
RUBY
end
end
context 'when an obsolete migration (that was documented) is used' do
it 'flags it as an offense' do
expect_offense <<~RUBY
return if Elastic::DataMigrationService.migration_has_finished?(:apply_max_analyzed_offset)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Migration is obsolete and can not be used with `migration_has_finished?`.
RUBY
end
end
context 'when migration_has_finished? method is called on another class' do
it 'does not flag it as an offense' do
expect_no_offenses <<~RUBY
return if Klass.migration_has_finished?(:backfill_archived_on_issues)
RUBY
end
end
context 'when migration exists in docs but missing implementation file' do
it 'flags it as an offense' do
# Mock the migration info to exist but file to be missing
allow(cop).to receive_messages(
find_migration_info: { obsolete: false, version: '1.0', milestone: '14.0' },
migration_file_exists?: false
)
expect_offense <<~RUBY
return if Elastic::DataMigrationService.migration_has_finished?(:missing_implementation)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Migration implementation file is missing.
RUBY
end
end
context 'when YAML parsing fails' do
it 'handles errors gracefully' do
# Mock Dir.glob to return a file that will cause YAML parsing to fail
allow(Dir).to receive(:glob).with('ee/elastic/docs/*.yml').and_return(['invalid_file.yml'])
allow(YAML).to receive(:safe_load_file).and_raise(StandardError.new('Invalid YAML'))
# Capture the warning message
expect { cop.send(:load_migrations) }.to output(
/Warning: Could not parse migration documentation file/
).to_stderr
end
end
end
end