Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
04ebfaf17c
commit
0fd2296553
|
@ -1,6 +1,8 @@
|
|||
<script>
|
||||
import { GlDropdown, GlDropdownItem, GlSprintf } from '@gitlab/ui';
|
||||
import defaultAvatarUrl from 'images/no_avatar.png';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import allVersionsMixin from '../../mixins/all_versions';
|
||||
import { findVersionId } from '../../utils/design_management_utils';
|
||||
|
||||
|
@ -9,6 +11,7 @@ export default {
|
|||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlSprintf,
|
||||
TimeAgo,
|
||||
},
|
||||
mixins: [allVersionsMixin],
|
||||
computed: {
|
||||
|
@ -58,6 +61,9 @@ export default {
|
|||
}
|
||||
return __('Version %{versionNumber}');
|
||||
},
|
||||
getAvatarUrl(version) {
|
||||
return version?.author?.avatarUrl || defaultAvatarUrl;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -68,14 +74,28 @@ export default {
|
|||
v-for="(version, index) in allVersions"
|
||||
:key="version.id"
|
||||
:is-check-item="true"
|
||||
:is-check-centered="true"
|
||||
:is-checked="findVersionId(version.id) === currentVersionId"
|
||||
:avatar-url="getAvatarUrl(version)"
|
||||
@click="routeToVersion(version.id)"
|
||||
>
|
||||
<gl-sprintf :message="versionText(version.id)">
|
||||
<template #versionNumber>
|
||||
{{ allVersions.length - index }}
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
<strong>
|
||||
<gl-sprintf :message="versionText(version.id)">
|
||||
<template #versionNumber>
|
||||
{{ allVersions.length - index }}
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</strong>
|
||||
|
||||
<div v-if="version.author" class="gl-text-gray-600 gl-mt-1">
|
||||
<div>{{ version.author.name }}</div>
|
||||
<time-ago
|
||||
v-if="version.createdAt"
|
||||
class="text-1"
|
||||
:time="version.createdAt"
|
||||
tooltip-placement="bottom"
|
||||
/>
|
||||
</div>
|
||||
</gl-dropdown-item>
|
||||
</gl-dropdown>
|
||||
</template>
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
GlSprintf,
|
||||
GlSafeHtmlDirective as SafeHtml,
|
||||
GlTable,
|
||||
GlTooltip,
|
||||
GlFormCheckbox,
|
||||
} from '@gitlab/ui';
|
||||
import { s__, __, n__ } from '~/locale';
|
||||
import PaginationLinks from '~/vue_shared/components/pagination_links.vue';
|
||||
|
@ -40,8 +40,8 @@ export default {
|
|||
GlLink,
|
||||
GlLoadingIcon,
|
||||
GlSearchBoxByClick,
|
||||
GlFormCheckbox,
|
||||
GlSprintf,
|
||||
GlTooltip,
|
||||
GlTable,
|
||||
ImportStatus,
|
||||
ImportTargetCell,
|
||||
|
@ -71,6 +71,7 @@ export default {
|
|||
filter: '',
|
||||
page: 1,
|
||||
perPage: DEFAULT_PAGE_SIZE,
|
||||
selectedGroups: [],
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -85,11 +86,20 @@ export default {
|
|||
},
|
||||
|
||||
fields: [
|
||||
{
|
||||
key: 'selected',
|
||||
label: '',
|
||||
// eslint-disable-next-line @gitlab/require-i18n-strings
|
||||
thClass: `${DEFAULT_TH_CLASSES} gl-w-3 gl-pr-3!`,
|
||||
// eslint-disable-next-line @gitlab/require-i18n-strings
|
||||
tdClass: `${DEFAULT_TD_CLASSES} gl-pr-3!`,
|
||||
},
|
||||
{
|
||||
key: 'web_url',
|
||||
label: s__('BulkImport|From source group'),
|
||||
thClass: `${DEFAULT_TH_CLASSES} import-jobs-from-col`,
|
||||
tdClass: DEFAULT_TD_CLASSES,
|
||||
thClass: `${DEFAULT_TH_CLASSES} gl-pl-0! import-jobs-from-col`,
|
||||
// eslint-disable-next-line @gitlab/require-i18n-strings
|
||||
tdClass: `${DEFAULT_TD_CLASSES} gl-pl-0!`,
|
||||
},
|
||||
{
|
||||
key: 'import_target',
|
||||
|
@ -117,16 +127,16 @@ export default {
|
|||
return this.bulkImportSourceGroups?.nodes ?? [];
|
||||
},
|
||||
|
||||
hasGroupsWithValidationError() {
|
||||
return this.groups.some((g) => g.validation_errors.length);
|
||||
hasSelectedGroups() {
|
||||
return this.selectedGroups.length > 0;
|
||||
},
|
||||
|
||||
hasAllAvailableGroupsSelected() {
|
||||
return this.selectedGroups.length === this.availableGroupsForImport.length;
|
||||
},
|
||||
|
||||
availableGroupsForImport() {
|
||||
return this.groups.filter((g) => g.progress.status === STATUSES.NONE);
|
||||
},
|
||||
|
||||
isImportAllButtonDisabled() {
|
||||
return this.hasGroupsWithValidationError || this.availableGroupsForImport.length === 0;
|
||||
return this.groups.filter((g) => g.progress.status === STATUSES.NONE && !this.isInvalid(g));
|
||||
},
|
||||
|
||||
humanizedTotal() {
|
||||
|
@ -156,7 +166,7 @@ export default {
|
|||
total: 0,
|
||||
};
|
||||
const start = (page - 1) * perPage + 1;
|
||||
const end = start + (this.bulkImportSourceGroups.nodes?.length ?? 0) - 1;
|
||||
const end = start + this.groups.length - 1;
|
||||
|
||||
return { start, end, total };
|
||||
},
|
||||
|
@ -166,6 +176,17 @@ export default {
|
|||
filter() {
|
||||
this.page = 1;
|
||||
},
|
||||
groups() {
|
||||
const table = this.getTableRef();
|
||||
this.groups.forEach((g, idx) => {
|
||||
if (this.selectedGroups.includes(g)) {
|
||||
this.$nextTick(() => {
|
||||
table.selectRow(idx);
|
||||
});
|
||||
}
|
||||
});
|
||||
this.selectedGroups = [];
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -210,13 +231,33 @@ export default {
|
|||
});
|
||||
},
|
||||
|
||||
importAllGroups() {
|
||||
this.importGroups(this.availableGroupsForImport.map((g) => g.id));
|
||||
importSelectedGroups() {
|
||||
this.importGroups(this.selectedGroups.map((g) => g.id));
|
||||
},
|
||||
|
||||
setPageSize(size) {
|
||||
this.perPage = size;
|
||||
},
|
||||
|
||||
getTableRef() {
|
||||
// Acquire reference to BTable to manipulate selection
|
||||
// issue: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1531
|
||||
// refs are not reactive, so do not use computed here
|
||||
return this.$refs.table?.$children[0];
|
||||
},
|
||||
|
||||
preventSelectingAlreadyImportedGroups(updatedSelection) {
|
||||
if (updatedSelection) {
|
||||
this.selectedGroups = updatedSelection;
|
||||
}
|
||||
|
||||
const table = this.getTableRef();
|
||||
this.groups.forEach((group, idx) => {
|
||||
if (table.isRowSelected(idx) && (this.isAlreadyImported(group) || this.isInvalid(group))) {
|
||||
table.unselectRow(idx);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
gitlabLogo: window.gon.gitlab_logo,
|
||||
|
@ -231,28 +272,6 @@ export default {
|
|||
>
|
||||
<img :src="$options.gitlabLogo" class="gl-w-6 gl-h-6 gl-mb-2 gl-display-inline gl-mr-2" />
|
||||
{{ s__('BulkImport|Import groups from GitLab') }}
|
||||
<div ref="importAllButtonWrapper" class="gl-ml-auto">
|
||||
<gl-button
|
||||
v-if="!$apollo.loading && hasGroups"
|
||||
:disabled="isImportAllButtonDisabled"
|
||||
variant="confirm"
|
||||
@click="importAllGroups"
|
||||
>
|
||||
<gl-sprintf :message="s__('BulkImport|Import %{groups}')">
|
||||
<template #groups>
|
||||
{{ groupsCount(availableGroupsForImport.length) }}
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</gl-button>
|
||||
</div>
|
||||
<gl-tooltip v-if="isImportAllButtonDisabled" :target="() => $refs.importAllButtonWrapper">
|
||||
<template v-if="hasGroupsWithValidationError">
|
||||
{{ s__('BulkImport|One or more groups has validation errors') }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ s__('BulkImport|No groups on this page are available for import') }}
|
||||
</template>
|
||||
</gl-tooltip>
|
||||
</h1>
|
||||
<div
|
||||
class="gl-py-5 gl-border-solid gl-border-gray-200 gl-border-0 gl-border-b-1 gl-display-flex"
|
||||
|
@ -298,19 +317,58 @@ export default {
|
|||
:description="s__('Check your source instance permissions.')"
|
||||
/>
|
||||
<template v-else>
|
||||
<div
|
||||
class="gl-bg-gray-10 gl-border-solid gl-border-gray-200 gl-border-0 gl-border-b-1 gl-p-4 gl-display-flex gl-align-items-center"
|
||||
>
|
||||
<gl-sprintf :message="__('%{count} selected')">
|
||||
<template #count>
|
||||
{{ selectedGroups.length }}
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
<gl-button
|
||||
category="primary"
|
||||
variant="confirm"
|
||||
class="gl-ml-4"
|
||||
:disabled="!hasSelectedGroups"
|
||||
@click="importSelectedGroups"
|
||||
>{{ s__('BulkImport|Import selected') }}</gl-button
|
||||
>
|
||||
</div>
|
||||
<gl-table
|
||||
ref="table"
|
||||
class="gl-w-full"
|
||||
data-qa-selector="import_table"
|
||||
tbody-tr-class="gl-border-gray-200 gl-border-0 gl-border-b-1 gl-border-solid"
|
||||
:tbody-tr-attr="qaRowAttributes"
|
||||
:items="bulkImportSourceGroups.nodes"
|
||||
:items="groups"
|
||||
:fields="$options.fields"
|
||||
selectable
|
||||
select-mode="multi"
|
||||
selected-variant="primary"
|
||||
@row-selected="preventSelectingAlreadyImportedGroups"
|
||||
>
|
||||
<template #head(selected)="{ selectAllRows, clearSelected }">
|
||||
<gl-form-checkbox
|
||||
:key="`checkbox-${selectedGroups.length}`"
|
||||
class="gl-h-7 gl-pt-3"
|
||||
:checked="hasSelectedGroups"
|
||||
:indeterminate="hasSelectedGroups && !hasAllAvailableGroupsSelected"
|
||||
@change="hasAllAvailableGroupsSelected ? clearSelected() : selectAllRows()"
|
||||
/>
|
||||
</template>
|
||||
<template #cell(selected)="{ rowSelected, selectRow, unselectRow, item: group }">
|
||||
<gl-form-checkbox
|
||||
class="gl-h-7 gl-pt-3"
|
||||
:checked="rowSelected"
|
||||
:disabled="isAlreadyImported(group) || isInvalid(group)"
|
||||
@change="rowSelected ? unselectRow() : selectRow()"
|
||||
/>
|
||||
</template>
|
||||
<template #cell(web_url)="{ value: web_url, item: { full_path } }">
|
||||
<gl-link
|
||||
:href="web_url"
|
||||
target="_blank"
|
||||
class="gl-display-flex gl-align-items-center gl-h-7"
|
||||
class="gl-display-inline-flex gl-align-items-center gl-h-7"
|
||||
>
|
||||
{{ full_path }} <gl-icon name="external-link" />
|
||||
</gl-link>
|
||||
|
@ -330,7 +388,7 @@ export default {
|
|||
/>
|
||||
</template>
|
||||
<template #cell(progress)="{ value: { status } }">
|
||||
<import-status :status="status" class="gl-mt-2" />
|
||||
<import-status :status="status" class="gl-line-height-32" />
|
||||
</template>
|
||||
<template #cell(actions)="{ item: group }">
|
||||
<gl-button
|
||||
|
|
|
@ -87,7 +87,7 @@ export default {
|
|||
<template>
|
||||
<gl-link
|
||||
v-if="isFinished"
|
||||
class="gl-display-flex gl-align-items-center gl-h-7"
|
||||
class="gl-display-inline-flex gl-align-items-center gl-h-7"
|
||||
:href="absolutePath"
|
||||
>
|
||||
{{ fullPath }}
|
||||
|
|
|
@ -68,7 +68,11 @@ export default {
|
|||
this.$emit('set-option', option || null);
|
||||
},
|
||||
isSelected(option) {
|
||||
return this.selected && this.selected.title === option.title;
|
||||
return (
|
||||
this.selected &&
|
||||
((option.name && this.selected.name === option.name) ||
|
||||
(option.title && this.selected.title === option.title))
|
||||
);
|
||||
},
|
||||
showDropdown() {
|
||||
this.$refs.dropdown.show();
|
||||
|
@ -79,6 +83,13 @@ export default {
|
|||
setSearchTerm(search) {
|
||||
this.$emit('set-search', search);
|
||||
},
|
||||
avatarUrl(option) {
|
||||
return option.avatar_url || option.avatarUrl || null;
|
||||
},
|
||||
secondaryText(option) {
|
||||
// TODO: this has some knowledge of the context where the component is used. We could later rework it.
|
||||
return option.username || null;
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
noMatchingResults: __('No matching results'),
|
||||
|
@ -121,7 +132,9 @@ export default {
|
|||
:is-check-item="true"
|
||||
@click="selectOption(option)"
|
||||
>
|
||||
{{ option.title }}
|
||||
<slot name="preset-item" :item="option">
|
||||
{{ option.title }}
|
||||
</slot>
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-divider />
|
||||
</template>
|
||||
|
@ -131,10 +144,14 @@ export default {
|
|||
:is-checked="isSelected(option)"
|
||||
:is-check-centered="true"
|
||||
:is-check-item="true"
|
||||
:avatar-url="avatarUrl(option)"
|
||||
:secondary-text="secondaryText(option)"
|
||||
data-testid="unselected-option"
|
||||
@click="selectOption(option)"
|
||||
>
|
||||
{{ option.title }}
|
||||
<slot name="item" :item="option">
|
||||
{{ option.title }}
|
||||
</slot>
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-item v-if="noOptionsFound" class="gl-pl-6!">
|
||||
{{ $options.i18n.noMatchingResults }}
|
||||
|
|
|
@ -12,17 +12,22 @@ class ApplicationExperiment < Gitlab::Experiment # rubocop:disable Gitlab/Namesp
|
|||
def publish(_result = nil)
|
||||
super
|
||||
|
||||
publish_to_client if should_track? # publish the experiment data to the client
|
||||
publish_to_database if @record # publish the experiment context to the database
|
||||
publish_to_client
|
||||
publish_to_database
|
||||
end
|
||||
|
||||
def publish_to_client
|
||||
return unless should_track?
|
||||
|
||||
Gon.push({ experiment: { name => signature } }, true)
|
||||
rescue NoMethodError
|
||||
# means we're not in the request cycle, and can't add to Gon. Log a warning maybe?
|
||||
end
|
||||
|
||||
def publish_to_database
|
||||
return unless @record
|
||||
return unless should_track?
|
||||
|
||||
# if the context contains a namespace, group, project, user, or actor
|
||||
value = context.value
|
||||
subject = value[:namespace] || value[:group] || value[:project] || value[:user] || value[:actor]
|
||||
|
|
|
@ -32,6 +32,13 @@ query getDesignList($fullPath: ID!, $iid: String!, $atVersion: ID) {
|
|||
__typename
|
||||
id
|
||||
sha
|
||||
createdAt
|
||||
author {
|
||||
__typename
|
||||
id
|
||||
name
|
||||
avatarUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
- if current_user.can_create_project?
|
||||
.page-title-controls
|
||||
= link_to _("New project"), new_project_path, class: "gl-button btn btn-confirm"
|
||||
= link_to _("New project"), new_project_path, class: "gl-button btn btn-confirm", data: { qa_selector: 'new_project_button' }
|
||||
|
||||
.top-area.scrolling-tabs-container.inner-page-scroll-tabs
|
||||
.fade-left= sprite_icon('chevron-lg-left', size: 12)
|
||||
|
|
|
@ -118,6 +118,8 @@ production: &base
|
|||
# ca_certs_file: /home/git/gitlab/.gitlab_smime_ca_certs
|
||||
|
||||
# Email server smtp settings are in config/initializers/smtp_settings.rb.sample
|
||||
# File location to read encrypted SMTP secrets from
|
||||
# email_smtp_secret_file: /mnt/gitlab/smtp.yaml.enc # Default: shared/encrypted_settings/smtp.yaml.enc
|
||||
|
||||
# default_can_create_group: false # default: true
|
||||
# username_changing_enabled: false # default: true - User can change their username/namespace
|
||||
|
|
|
@ -178,6 +178,7 @@ Settings.gitlab['email_display_name'] ||= ENV['GITLAB_EMAIL_DISPLAY_NAME'] || 'G
|
|||
Settings.gitlab['email_reply_to'] ||= ENV['GITLAB_EMAIL_REPLY_TO'] || "noreply@#{Settings.gitlab.host}"
|
||||
Settings.gitlab['email_subject_suffix'] ||= ENV['GITLAB_EMAIL_SUBJECT_SUFFIX'] || ""
|
||||
Settings.gitlab['email_smime'] = SmimeSignatureSettings.parse(Settings.gitlab['email_smime'])
|
||||
Settings.gitlab['email_smtp_secret_file'] = Settings.absolute(Settings.gitlab['email_smtp_secret_file'] || File.join(Settings.encrypted_settings['path'], "smtp.yaml.enc"))
|
||||
Settings.gitlab['base_url'] ||= Settings.__send__(:build_base_gitlab_url)
|
||||
Settings.gitlab['url'] ||= Settings.__send__(:build_gitlab_url)
|
||||
Settings.gitlab['user'] ||= 'git'
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
if Rails.env.production?
|
||||
Rails.application.config.action_mailer.delivery_method = :smtp
|
||||
secrets = Gitlab::Email::SmtpConfig.secrets
|
||||
|
||||
ActionMailer::Base.delivery_method = :smtp
|
||||
ActionMailer::Base.smtp_settings = {
|
||||
|
@ -16,6 +17,10 @@ if Rails.env.production?
|
|||
port: 465,
|
||||
user_name: "smtp",
|
||||
password: "123456",
|
||||
## If you are using encrypted smtp credentials then you should instead use the secrets user_name/password
|
||||
## See: https://docs.gitlab.com/ee/administration/raketasks/smtp.html#secrets
|
||||
# user_name: secrets.username,
|
||||
# password: secrets.password,
|
||||
domain: "gitlab.company.com",
|
||||
authentication: :login,
|
||||
enable_starttls_auto: true,
|
||||
|
@ -31,6 +36,7 @@ end
|
|||
#
|
||||
# if Rails.env.production?
|
||||
# Rails.application.config.action_mailer.delivery_method = :smtp_pool
|
||||
# secrets = Gitlab::Email::SmtpConfig.secrets
|
||||
#
|
||||
# ActionMailer::Base.delivery_method = :smtp_pool
|
||||
# ActionMailer::Base.smtp_pool_settings = {
|
||||
|
@ -40,6 +46,10 @@ end
|
|||
# port: 465,
|
||||
# user_name: "smtp",
|
||||
# password: "123456",
|
||||
# ## If you are using encrypted smtp credentials then you should instead use the secrets user_name/password
|
||||
# ## See: https://docs.gitlab.com/ee/administration/raketasks/smtp.html#secrets
|
||||
# # user_name: secrets.username,
|
||||
# # password: secrets.password,
|
||||
# domain: "gitlab.company.com",
|
||||
# authentication: :login,
|
||||
# enable_starttls_auto: true,
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
key_path: settings.smtp_encrypted_secrets_enabled
|
||||
description: Is encrypted SMTP secrets configured?
|
||||
product_section: enablement
|
||||
product_stage: enablement
|
||||
product_group: distribution
|
||||
value_type: boolean
|
||||
status: implemented
|
||||
milestone: "14.2"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/67802
|
||||
time_frame: none
|
||||
data_source: system
|
||||
data_category: Optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -10,14 +10,6 @@ Please check the ~"product intelligence" [guide](https://docs.gitlab.com/ee/deve
|
|||
|
||||
MSG
|
||||
|
||||
UPDATE_DICTIONARY_MESSAGE = <<~MSG
|
||||
When adding, changing, or updating metrics, please update the [Metrics Dictionary](https://docs.gitlab.com/ee/development/usage_ping/dictionary.html)
|
||||
|
||||
```shell
|
||||
bundle exec rake gitlab:usage_data:generate_metrics_dictionary
|
||||
```
|
||||
MSG
|
||||
|
||||
# exit if not matching files
|
||||
matching_changed_files = product_intelligence.matching_changed_files
|
||||
return unless matching_changed_files.any?
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ResetJobTokenScopeEnabledAgain < ActiveRecord::Migration[6.1]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
def up
|
||||
with_lock_retries do
|
||||
remove_column :project_ci_cd_settings, :job_token_scope_enabled
|
||||
add_column :project_ci_cd_settings, :job_token_scope_enabled, :boolean, default: false, null: false
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
# no-op
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
24c49a12b6624c8e215e8a0c16b1bc9acc1875e68d3727fc3904b9e2eee1d319
|
|
@ -12,6 +12,7 @@ type: reference
|
|||
GitLab can read settings for certain features from encrypted settings files. The supported features are:
|
||||
|
||||
- [LDAP `user_bn` and `password`](auth/ldap/index.md#using-encrypted-credentials)
|
||||
- [SMTP `user_name` and `password`](raketasks/smtp.md#secrets)
|
||||
|
||||
In order to enable the encrypted configuration settings, a new base key needs to be generated for
|
||||
`encrypted_settings_key_base`. The secret can be generated in the following ways:
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
---
|
||||
stage: Enablement
|
||||
group: Distribution
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# SMTP Rake tasks **(FREE SELF)**
|
||||
|
||||
[Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/67802) in GitLab 14.2.
|
||||
|
||||
The following are SMTP-related Rake tasks.
|
||||
|
||||
## Secrets
|
||||
|
||||
GitLab can use SMTP configuration secrets to read from an encrypted file. The following Rake tasks are provided for updating the contents of the encrypted file.
|
||||
|
||||
### Show secret
|
||||
|
||||
Show the contents of the current SMTP secrets.
|
||||
|
||||
**Omnibus Installation**
|
||||
|
||||
```shell
|
||||
sudo gitlab-rake gitlab:smtp:secret:show
|
||||
```
|
||||
|
||||
**Source Installation**
|
||||
|
||||
```shell
|
||||
bundle exec rake gitlab:smtp:secret:show RAILS_ENV=production
|
||||
```
|
||||
|
||||
**Example output:**
|
||||
|
||||
```plaintext
|
||||
password: '123'
|
||||
user_name: 'gitlab-inst'
|
||||
```
|
||||
|
||||
### Edit secret
|
||||
|
||||
Opens the secret contents in your editor, and writes the resulting content to the encrypted secret file when you exit.
|
||||
|
||||
**Omnibus Installation**
|
||||
|
||||
```shell
|
||||
sudo gitlab-rake gitlab:smtp:secret:edit EDITOR=vim
|
||||
```
|
||||
|
||||
**Source Installation**
|
||||
|
||||
```shell
|
||||
bundle exec rake gitlab:smtp:secret:edit RAILS_ENV=production EDITOR=vim
|
||||
```
|
||||
|
||||
### Write raw secret
|
||||
|
||||
Write new secret content by providing it on STDIN.
|
||||
|
||||
**Omnibus Installation**
|
||||
|
||||
```shell
|
||||
echo -e "password: '123'" | sudo gitlab-rake gitlab:smtp:secret:write
|
||||
```
|
||||
|
||||
**Source Installation**
|
||||
|
||||
```shell
|
||||
echo -e "password: '123'" | bundle exec rake gitlab:smtp:secret:write RAILS_ENV=production
|
||||
```
|
||||
|
||||
### Secrets examples
|
||||
|
||||
**Editor example**
|
||||
|
||||
The write task can be used in cases where the edit command does not work with your editor:
|
||||
|
||||
```shell
|
||||
# Write the existing secret to a plaintext file
|
||||
sudo gitlab-rake gitlab:smtp:secret:show > smtp.yaml
|
||||
# Edit the smtp file in your editor
|
||||
...
|
||||
# Re-encrypt the file
|
||||
cat smtp.yaml | sudo gitlab-rake gitlab:smtp:secret:write
|
||||
# Remove the plaintext file
|
||||
rm smtp.yaml
|
||||
```
|
||||
|
||||
**KMS integration example**
|
||||
|
||||
It can also be used as a receiving application for content encrypted with a KMS:
|
||||
|
||||
```shell
|
||||
gcloud kms decrypt --key my-key --keyring my-test-kms --plaintext-file=- --ciphertext-file=my-file --location=us-west1 | sudo gitlab-rake gitlab:smtp:secret:write
|
||||
```
|
||||
|
||||
**Google Cloud secret integration example**
|
||||
|
||||
It can also be used as a receiving application for secrets out of Google Cloud:
|
||||
|
||||
```shell
|
||||
gcloud secrets versions access latest --secret="my-test-secret" > $1 | sudo gitlab-rake gitlab:smtp:secret:write
|
||||
```
|
|
@ -7,7 +7,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
<!---
|
||||
This documentation is auto generated by a script.
|
||||
|
||||
Please do not edit this file directly, check generate_metrics_dictionary task on lib/tasks/gitlab/usage_data.rake.
|
||||
Please do not edit this file directly, check generate_event_dictionary task on lib/tasks/gitlab/snowplow.rake.
|
||||
--->
|
||||
|
||||
<!-- vale gitlab.Spelling = NO -->
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -42,6 +42,7 @@ The following Rake tasks are available for use with GitLab:
|
|||
| [Project import/export](../administration/raketasks/project_import_export.md) | Prepare for [project exports and imports](../user/project/settings/import_export.md). |
|
||||
| [Sample Prometheus data](generate_sample_prometheus_data.md) | Generate sample Prometheus data. |
|
||||
| [Sidekiq job migration](sidekiq_job_migration.md) | Migrate Sidekiq jobs scheduled for future dates to a new queue. |
|
||||
| [SMTP maintenance](../administration/raketasks/smtp.md) | SMTP-related tasks. |
|
||||
| [SPDX license list import](spdx.md) | Import a local copy of the [SPDX license list](https://spdx.org/licenses/) for matching [License Compliance policies](../user/compliance/license_compliance/index.md). |
|
||||
| [Repository storage](../administration/raketasks/storage.md) | List and migrate existing projects and attachments from legacy storage to hashed storage. |
|
||||
| [Uploads migrate](../administration/raketasks/uploads/migrate.md) | Migrate uploads between local storage and object storage. |
|
||||
|
|
|
@ -14,6 +14,11 @@ enable you to store the state file in a remote, shared store. GitLab uses the
|
|||
to securely store the state files in local storage (the default) or
|
||||
[the remote store of your choice](../../administration/terraform_state.md).
|
||||
|
||||
WARNING:
|
||||
Using local storage (the default) on clustered deployments of GitLab will result in
|
||||
a split state across nodes, making subsequent executions of Terraform inconsistent.
|
||||
You are highly advised to use a remote storage in that case.
|
||||
|
||||
The GitLab managed Terraform state backend can store your Terraform state easily and
|
||||
securely, and spares you from setting up additional remote resources like
|
||||
Amazon S3 or Google Cloud Storage. Its features include:
|
||||
|
|
|
@ -41,31 +41,31 @@ module Gitlab
|
|||
def select_all(arel, name = nil, binds = [], preparable: nil)
|
||||
if arel.respond_to?(:locked) && arel.locked
|
||||
# SELECT ... FOR UPDATE queries should be sent to the primary.
|
||||
write_using_load_balancer(:select_all, [arel, name, binds],
|
||||
write_using_load_balancer(:select_all, arel, name, binds,
|
||||
sticky: true)
|
||||
else
|
||||
read_using_load_balancer(:select_all, [arel, name, binds])
|
||||
read_using_load_balancer(:select_all, arel, name, binds)
|
||||
end
|
||||
end
|
||||
|
||||
NON_STICKY_READS.each do |name|
|
||||
define_method(name) do |*args, &block|
|
||||
read_using_load_balancer(name, args, &block)
|
||||
define_method(name) do |*args, **kwargs, &block|
|
||||
read_using_load_balancer(name, *args, **kwargs, &block)
|
||||
end
|
||||
end
|
||||
|
||||
STICKY_WRITES.each do |name|
|
||||
define_method(name) do |*args, &block|
|
||||
write_using_load_balancer(name, args, sticky: true, &block)
|
||||
define_method(name) do |*args, **kwargs, &block|
|
||||
write_using_load_balancer(name, *args, sticky: true, **kwargs, &block)
|
||||
end
|
||||
end
|
||||
|
||||
def transaction(*args, &block)
|
||||
def transaction(*args, **kwargs, &block)
|
||||
if current_session.fallback_to_replicas_for_ambiguous_queries?
|
||||
track_read_only_transaction!
|
||||
read_using_load_balancer(:transaction, args, &block)
|
||||
read_using_load_balancer(:transaction, *args, **kwargs, &block)
|
||||
else
|
||||
write_using_load_balancer(:transaction, args, sticky: true, &block)
|
||||
write_using_load_balancer(:transaction, *args, sticky: true, **kwargs, &block)
|
||||
end
|
||||
|
||||
ensure
|
||||
|
@ -73,26 +73,26 @@ module Gitlab
|
|||
end
|
||||
|
||||
# Delegates all unknown messages to a read-write connection.
|
||||
def method_missing(name, *args, &block)
|
||||
def method_missing(...)
|
||||
if current_session.fallback_to_replicas_for_ambiguous_queries?
|
||||
read_using_load_balancer(name, args, &block)
|
||||
read_using_load_balancer(...)
|
||||
else
|
||||
write_using_load_balancer(name, args, &block)
|
||||
write_using_load_balancer(...)
|
||||
end
|
||||
end
|
||||
|
||||
# Performs a read using the load balancer.
|
||||
#
|
||||
# name - The name of the method to call on a connection object.
|
||||
def read_using_load_balancer(name, args, &block)
|
||||
def read_using_load_balancer(...)
|
||||
if current_session.use_primary? &&
|
||||
!current_session.use_replicas_for_read_queries?
|
||||
@load_balancer.read_write do |connection|
|
||||
connection.send(name, *args, &block)
|
||||
connection.send(...)
|
||||
end
|
||||
else
|
||||
@load_balancer.read do |connection|
|
||||
connection.send(name, *args, &block)
|
||||
connection.send(...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -102,7 +102,7 @@ module Gitlab
|
|||
# name - The name of the method to call on a connection object.
|
||||
# sticky - If set to true the session will stick to the master after
|
||||
# the write.
|
||||
def write_using_load_balancer(name, args, sticky: false, &block)
|
||||
def write_using_load_balancer(name, *args, sticky: false, **kwargs, &block)
|
||||
if read_only_transaction?
|
||||
raise WriteInsideReadOnlyTransactionError, 'A write query is performed inside a read-only transaction'
|
||||
end
|
||||
|
@ -113,7 +113,7 @@ module Gitlab
|
|||
# secondary instead of on a primary (when necessary).
|
||||
current_session.write! if sticky
|
||||
|
||||
connection.send(name, *args, &block)
|
||||
connection.send(name, *args, **kwargs, &block)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Email
|
||||
class SmtpConfig
|
||||
def self.encrypted_secrets
|
||||
Settings.encrypted(Gitlab.config.gitlab.email_smtp_secret_file)
|
||||
end
|
||||
|
||||
def self.secrets
|
||||
self.new
|
||||
end
|
||||
|
||||
def initialize
|
||||
@secrets ||= self.class.encrypted_secrets.config
|
||||
rescue StandardError => e
|
||||
Gitlab::AppLogger.error "SMTP encrypted secrets are invalid: #{e.inspect}"
|
||||
end
|
||||
|
||||
def username
|
||||
@secrets&.fetch(:user_name, nil)&.chomp
|
||||
end
|
||||
|
||||
def password
|
||||
@secrets&.fetch(:password, nil)&.chomp
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,105 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# rubocop:disable Rails/Output
|
||||
module Gitlab
|
||||
class EncryptedCommandBase
|
||||
DISPLAY_NAME = "Base"
|
||||
EDIT_COMMAND_NAME = "base"
|
||||
|
||||
class << self
|
||||
def encrypted_secrets
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def write(contents)
|
||||
encrypted = encrypted_secrets
|
||||
return unless validate_config(encrypted)
|
||||
|
||||
validate_contents(contents)
|
||||
encrypted.write(contents)
|
||||
|
||||
puts "File encrypted and saved."
|
||||
rescue Interrupt
|
||||
warn "Aborted changing file: nothing saved."
|
||||
rescue ActiveSupport::MessageEncryptor::InvalidMessage
|
||||
warn "Couldn't decrypt #{encrypted.content_path}. Perhaps you passed the wrong key?"
|
||||
end
|
||||
|
||||
def edit
|
||||
encrypted = encrypted_secrets
|
||||
return unless validate_config(encrypted)
|
||||
|
||||
if ENV["EDITOR"].blank?
|
||||
warn 'No $EDITOR specified to open file. Please provide one when running the command:'
|
||||
warn "gitlab-rake #{self::EDIT_COMMAND_NAME} EDITOR=vim"
|
||||
return
|
||||
end
|
||||
|
||||
temp_file = Tempfile.new(File.basename(encrypted.content_path), File.dirname(encrypted.content_path))
|
||||
contents_changed = false
|
||||
|
||||
encrypted.change do |contents|
|
||||
contents = encrypted_file_template unless File.exist?(encrypted.content_path)
|
||||
File.write(temp_file.path, contents)
|
||||
system(ENV['EDITOR'], temp_file.path)
|
||||
changes = File.read(temp_file.path)
|
||||
contents_changed = contents != changes
|
||||
validate_contents(changes)
|
||||
changes
|
||||
end
|
||||
|
||||
puts "Contents were unchanged." unless contents_changed
|
||||
puts "File encrypted and saved."
|
||||
rescue Interrupt
|
||||
warn "Aborted changing file: nothing saved."
|
||||
rescue ActiveSupport::MessageEncryptor::InvalidMessage
|
||||
warn "Couldn't decrypt #{encrypted.content_path}. Perhaps you passed the wrong key?"
|
||||
ensure
|
||||
temp_file&.unlink
|
||||
end
|
||||
|
||||
def show
|
||||
encrypted = encrypted_secrets
|
||||
return unless validate_config(encrypted)
|
||||
|
||||
puts encrypted.read.presence || "File '#{encrypted.content_path}' does not exist. Use `gitlab-rake #{self::EDIT_COMMAND_NAME}` to change that."
|
||||
rescue ActiveSupport::MessageEncryptor::InvalidMessage
|
||||
warn "Couldn't decrypt #{encrypted.content_path}. Perhaps you passed the wrong key?"
|
||||
end
|
||||
|
||||
def validate_config(encrypted)
|
||||
dir_path = File.dirname(encrypted.content_path)
|
||||
|
||||
unless File.exist?(dir_path)
|
||||
warn "Directory #{dir_path} does not exist. Create the directory and try again."
|
||||
return false
|
||||
end
|
||||
|
||||
if encrypted.key.nil?
|
||||
warn "Missing encryption key encrypted_settings_key_base."
|
||||
return false
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def validate_contents(contents)
|
||||
begin
|
||||
config = YAML.safe_load(contents, permitted_classes: [Symbol])
|
||||
error_contents = "Did not include any key-value pairs" unless config.is_a?(Hash)
|
||||
rescue Psych::Exception => e
|
||||
error_contents = e.message
|
||||
end
|
||||
|
||||
puts "WARNING: Content was not a valid #{self::DISPLAY_NAME} secret yml file. #{error_contents}" if error_contents
|
||||
|
||||
contents
|
||||
end
|
||||
|
||||
def encrypted_file_template
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
# rubocop:enable Rails/Output
|
|
@ -2,93 +2,13 @@
|
|||
|
||||
# rubocop:disable Rails/Output
|
||||
module Gitlab
|
||||
class EncryptedLdapCommand
|
||||
class EncryptedLdapCommand < EncryptedCommandBase
|
||||
DISPLAY_NAME = "LDAP"
|
||||
EDIT_COMMAND_NAME = "gitlab:ldap:secret:edit"
|
||||
|
||||
class << self
|
||||
def write(contents)
|
||||
encrypted = Gitlab::Auth::Ldap::Config.encrypted_secrets
|
||||
return unless validate_config(encrypted)
|
||||
|
||||
validate_contents(contents)
|
||||
encrypted.write(contents)
|
||||
|
||||
puts "File encrypted and saved."
|
||||
rescue Interrupt
|
||||
puts "Aborted changing file: nothing saved."
|
||||
rescue ActiveSupport::MessageEncryptor::InvalidMessage
|
||||
puts "Couldn't decrypt #{encrypted.content_path}. Perhaps you passed the wrong key?"
|
||||
end
|
||||
|
||||
def edit
|
||||
encrypted = Gitlab::Auth::Ldap::Config.encrypted_secrets
|
||||
return unless validate_config(encrypted)
|
||||
|
||||
if ENV["EDITOR"].blank?
|
||||
puts 'No $EDITOR specified to open file. Please provide one when running the command:'
|
||||
puts 'gitlab-rake gitlab:ldap:secret:edit EDITOR=vim'
|
||||
return
|
||||
end
|
||||
|
||||
temp_file = Tempfile.new(File.basename(encrypted.content_path), File.dirname(encrypted.content_path))
|
||||
contents_changed = false
|
||||
|
||||
encrypted.change do |contents|
|
||||
contents = encrypted_file_template unless File.exist?(encrypted.content_path)
|
||||
File.write(temp_file.path, contents)
|
||||
system(ENV['EDITOR'], temp_file.path)
|
||||
changes = File.read(temp_file.path)
|
||||
contents_changed = contents != changes
|
||||
validate_contents(changes)
|
||||
changes
|
||||
end
|
||||
|
||||
puts "Contents were unchanged." unless contents_changed
|
||||
puts "File encrypted and saved."
|
||||
rescue Interrupt
|
||||
puts "Aborted changing file: nothing saved."
|
||||
rescue ActiveSupport::MessageEncryptor::InvalidMessage
|
||||
puts "Couldn't decrypt #{encrypted.content_path}. Perhaps you passed the wrong key?"
|
||||
ensure
|
||||
temp_file&.unlink
|
||||
end
|
||||
|
||||
def show
|
||||
encrypted = Gitlab::Auth::Ldap::Config.encrypted_secrets
|
||||
return unless validate_config(encrypted)
|
||||
|
||||
puts encrypted.read.presence || "File '#{encrypted.content_path}' does not exist. Use `gitlab-rake gitlab:ldap:secret:edit` to change that."
|
||||
rescue ActiveSupport::MessageEncryptor::InvalidMessage
|
||||
puts "Couldn't decrypt #{encrypted.content_path}. Perhaps you passed the wrong key?"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_config(encrypted)
|
||||
dir_path = File.dirname(encrypted.content_path)
|
||||
|
||||
unless File.exist?(dir_path)
|
||||
puts "Directory #{dir_path} does not exist. Create the directory and try again."
|
||||
return false
|
||||
end
|
||||
|
||||
if encrypted.key.nil?
|
||||
puts "Missing encryption key encrypted_settings_key_base."
|
||||
return false
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def validate_contents(contents)
|
||||
begin
|
||||
config = YAML.safe_load(contents, permitted_classes: [Symbol])
|
||||
error_contents = "Did not include any key-value pairs" unless config.is_a?(Hash)
|
||||
rescue Psych::Exception => e
|
||||
error_contents = e.message
|
||||
end
|
||||
|
||||
puts "WARNING: Content was not a valid LDAP secret yml file. #{error_contents}" if error_contents
|
||||
|
||||
contents
|
||||
def encrypted_secrets
|
||||
Gitlab::Auth::Ldap::Config.encrypted_secrets
|
||||
end
|
||||
|
||||
def encrypted_file_template
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# rubocop:disable Rails/Output
|
||||
module Gitlab
|
||||
class EncryptedSmtpCommand < EncryptedCommandBase
|
||||
DISPLAY_NAME = "SMTP"
|
||||
EDIT_COMMAND_NAME = "gitlab:smtp:secret:edit"
|
||||
|
||||
class << self
|
||||
def encrypted_secrets
|
||||
Gitlab::Email::SmtpConfig.encrypted_secrets
|
||||
end
|
||||
|
||||
def encrypted_file_template
|
||||
<<~YAML
|
||||
# password: '123'
|
||||
# user_name: 'gitlab-inst'
|
||||
YAML
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
# rubocop:enable Rails/Output
|
|
@ -16,7 +16,7 @@ module Gitlab
|
|||
<!---
|
||||
This documentation is auto generated by a script.
|
||||
|
||||
Please do not edit this file directly, check generate_metrics_dictionary task on lib/tasks/gitlab/usage_data.rake.
|
||||
Please do not edit this file directly, check generate_event_dictionary task on lib/tasks/gitlab/snowplow.rake.
|
||||
--->
|
||||
|
||||
<!-- vale gitlab.Spelling = NO -->
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Usage
|
||||
module Docs
|
||||
# Helper with functions to be used by HAML templates
|
||||
module Helper
|
||||
def auto_generated_comment
|
||||
<<-MARKDOWN.strip_heredoc
|
||||
---
|
||||
stage: Growth
|
||||
group: Product Intelligence
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
|
||||
---
|
||||
|
||||
<!---
|
||||
This documentation is auto generated by a script.
|
||||
|
||||
Please do not edit this file directly, check generate_metrics_dictionary task on lib/tasks/gitlab/usage_data.rake.
|
||||
--->
|
||||
MARKDOWN
|
||||
end
|
||||
|
||||
def render_name(name)
|
||||
"### `#{name}`"
|
||||
end
|
||||
|
||||
def render_description(object)
|
||||
return 'Missing description' unless object[:description].present?
|
||||
|
||||
object[:description]
|
||||
end
|
||||
|
||||
def render_object_schema(object)
|
||||
"[Object JSON schema](#{object.json_schema_path})"
|
||||
end
|
||||
|
||||
def render_yaml_link(yaml_path)
|
||||
"[YAML definition](#{yaml_path})"
|
||||
end
|
||||
|
||||
def render_status(object)
|
||||
"Status: #{format(:status, object[:status])}"
|
||||
end
|
||||
|
||||
def render_owner(object)
|
||||
"Group: `#{object[:product_group]}`"
|
||||
end
|
||||
|
||||
def render_tiers(object)
|
||||
"Tiers:#{format(:tier, object[:tier])}"
|
||||
end
|
||||
|
||||
def render_data_category(object)
|
||||
"Data Category: `#{object[:data_category]}`"
|
||||
end
|
||||
|
||||
def format(key, value)
|
||||
Gitlab::Usage::Docs::ValueFormatter.format(key, value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,32 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Usage
|
||||
module Docs
|
||||
class Renderer
|
||||
include Gitlab::Usage::Docs::Helper
|
||||
DICTIONARY_PATH = Rails.root.join('doc', 'development', 'service_ping')
|
||||
TEMPLATE_PATH = Rails.root.join('lib', 'gitlab', 'usage', 'docs', 'templates', 'default.md.haml')
|
||||
|
||||
def initialize(metrics_definitions)
|
||||
@layout = Haml::Engine.new(File.read(TEMPLATE_PATH))
|
||||
@metrics_definitions = metrics_definitions.sort
|
||||
end
|
||||
|
||||
def contents
|
||||
# Render and remove an extra trailing new line
|
||||
@contents ||= @layout.render(self, metrics_definitions: @metrics_definitions).sub!(/\n(?=\Z)/, '')
|
||||
end
|
||||
|
||||
def write
|
||||
filename = DICTIONARY_PATH.join('dictionary.md').to_s
|
||||
|
||||
FileUtils.mkdir_p(DICTIONARY_PATH)
|
||||
File.write(filename, contents)
|
||||
|
||||
filename
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,48 +0,0 @@
|
|||
= auto_generated_comment
|
||||
|
||||
:plain
|
||||
# Metrics Dictionary
|
||||
|
||||
This file is autogenerated, please do not edit directly.
|
||||
|
||||
To generate these files from the GitLab repository, run:
|
||||
|
||||
```shell
|
||||
bundle exec rake gitlab:usage_data:generate_metrics_dictionary
|
||||
```
|
||||
|
||||
The Metrics Dictionary is based on the following metrics definition YAML files:
|
||||
|
||||
- [`config/metrics`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/config/metrics)
|
||||
- [`ee/config/metrics`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/ee/config/metrics)
|
||||
|
||||
Each table includes a `milestone`, which corresponds to the GitLab version when the metric
|
||||
was released.
|
||||
|
||||
<!-- vale off -->
|
||||
<!-- Docs linting disabled after this line. -->
|
||||
<!-- See https://docs.gitlab.com/ee/development/documentation/testing.html#disable-vale-tests -->
|
||||
|
||||
## Metrics Definitions
|
||||
|
||||
\
|
||||
- metrics_definitions.each do |name, object|
|
||||
|
||||
= render_name(name)
|
||||
\
|
||||
= render_description(object.attributes)
|
||||
- if object.has_json_schema?
|
||||
\
|
||||
= render_object_schema(object)
|
||||
\
|
||||
= render_yaml_link(object.yaml_path)
|
||||
\
|
||||
= render_owner(object.attributes)
|
||||
- if object.attributes[:data_category].present?
|
||||
\
|
||||
= render_data_category(object.attributes)
|
||||
\
|
||||
= render_status(object.attributes)
|
||||
\
|
||||
= render_tiers(object.attributes)
|
||||
\
|
|
@ -1,28 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Usage
|
||||
module Docs
|
||||
class ValueFormatter
|
||||
def self.format(key, value)
|
||||
return '' unless value.present?
|
||||
|
||||
case key
|
||||
when :key_path
|
||||
"**`#{value}`**"
|
||||
when :data_source
|
||||
value.to_s.capitalize
|
||||
when :product_group, :product_category, :status
|
||||
"`#{value}`"
|
||||
when :introduced_by_url
|
||||
"[Introduced by](#{value})"
|
||||
when :distribution, :tier
|
||||
Array(value).map { |tier| " `#{tier}`" }.join(',')
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -256,6 +256,7 @@ module Gitlab
|
|||
{
|
||||
settings: {
|
||||
ldap_encrypted_secrets_enabled: alt_usage_data(fallback: nil) { Gitlab::Auth::Ldap::Config.encrypted_secrets.active? },
|
||||
smtp_encrypted_secrets_enabled: alt_usage_data(fallback: nil) { Gitlab::Email::SmtpConfig.encrypted_secrets.active? },
|
||||
operating_system: alt_usage_data(fallback: nil) { operating_system },
|
||||
gitaly_apdex: alt_usage_data { gitaly_apdex },
|
||||
collected_data_categories: add_metric('CollectedDataCategoriesMetric', time_frame: 'none')
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
namespace :gitlab do
|
||||
namespace :smtp do
|
||||
namespace :secret do
|
||||
desc 'GitLab | SMTP | Secret | Write SMTP secrets'
|
||||
task write: [:environment] do
|
||||
content = $stdin.tty? ? $stdin.gets : $stdin.read
|
||||
Gitlab::EncryptedSmtpCommand.write(content)
|
||||
end
|
||||
|
||||
desc 'GitLab | SMTP | Secret | Edit SMTP secrets'
|
||||
task edit: [:environment] do
|
||||
Gitlab::EncryptedSmtpCommand.edit
|
||||
end
|
||||
|
||||
desc 'GitLab | SMTP | Secret | Show SMTP secrets'
|
||||
task show: [:environment] do
|
||||
Gitlab::EncryptedSmtpCommand.show
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -522,6 +522,9 @@ msgstr[1] ""
|
|||
msgid "%{count} related %{pluralized_subject}: %{links}"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{count} selected"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{count} total weight"
|
||||
msgstr ""
|
||||
|
||||
|
@ -5422,9 +5425,6 @@ msgstr ""
|
|||
msgid "BoardScope|Milestone"
|
||||
msgstr ""
|
||||
|
||||
msgid "BoardScope|No matching results"
|
||||
msgstr ""
|
||||
|
||||
msgid "BoardScope|No milestone"
|
||||
msgstr ""
|
||||
|
||||
|
@ -5813,30 +5813,24 @@ msgstr ""
|
|||
msgid "BulkImport|From source group"
|
||||
msgstr ""
|
||||
|
||||
msgid "BulkImport|Import %{groups}"
|
||||
msgstr ""
|
||||
|
||||
msgid "BulkImport|Import failed: Destination cannot be a subgroup of the source group. Change the destination and try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "BulkImport|Import groups from GitLab"
|
||||
msgstr ""
|
||||
|
||||
msgid "BulkImport|Import selected"
|
||||
msgstr ""
|
||||
|
||||
msgid "BulkImport|Importing the group failed"
|
||||
msgstr ""
|
||||
|
||||
msgid "BulkImport|Name already exists."
|
||||
msgstr ""
|
||||
|
||||
msgid "BulkImport|No groups on this page are available for import"
|
||||
msgstr ""
|
||||
|
||||
msgid "BulkImport|No parent"
|
||||
msgstr ""
|
||||
|
||||
msgid "BulkImport|One or more groups has validation errors"
|
||||
msgstr ""
|
||||
|
||||
msgid "BulkImport|Showing %{start}-%{end} of %{total}"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -13,6 +13,10 @@ module QA
|
|||
element :user_role_content
|
||||
end
|
||||
|
||||
view 'app/views/dashboard/_projects_head.html.haml' do
|
||||
element :new_project_button
|
||||
end
|
||||
|
||||
def has_project_with_access_role?(project_name, access_role)
|
||||
within_element(:project_content, text: project_name) do
|
||||
has_element?(:user_role_content, text: access_role)
|
||||
|
@ -25,6 +29,10 @@ module QA
|
|||
find_link(text: name).click
|
||||
end
|
||||
|
||||
def click_new_project_button
|
||||
click_element(:new_project_button, Page::Project::New)
|
||||
end
|
||||
|
||||
def self.path
|
||||
'/'
|
||||
end
|
||||
|
|
|
@ -20,7 +20,7 @@ module QA
|
|||
:name,
|
||||
:add_name_uuid,
|
||||
:description,
|
||||
:standalone,
|
||||
:personal_namespace,
|
||||
:runners_token,
|
||||
:visibility,
|
||||
:template_name,
|
||||
|
@ -52,7 +52,7 @@ module QA
|
|||
|
||||
def initialize
|
||||
@add_name_uuid = true
|
||||
@standalone = false
|
||||
@personal_namespace = false
|
||||
@description = 'My awesome project'
|
||||
@initialize_with_readme = false
|
||||
@auto_devops_enabled = false
|
||||
|
@ -70,7 +70,9 @@ module QA
|
|||
def fabricate!
|
||||
return if @import
|
||||
|
||||
unless @standalone
|
||||
if @personal_namespace
|
||||
Page::Dashboard::Projects.perform(&:click_new_project_button)
|
||||
else
|
||||
group.visit!
|
||||
Page::Group::Show.perform(&:go_to_new_project)
|
||||
end
|
||||
|
@ -85,13 +87,15 @@ module QA
|
|||
Page::Project::New.perform(&:click_blank_project_link)
|
||||
|
||||
Page::Project::New.perform do |new_page|
|
||||
new_page.choose_test_namespace
|
||||
new_page.choose_test_namespace unless @personal_namespace
|
||||
new_page.choose_name(@name)
|
||||
new_page.add_description(@description)
|
||||
new_page.set_visibility(@visibility)
|
||||
new_page.disable_initialize_with_readme unless @initialize_with_readme
|
||||
new_page.create_new_project
|
||||
end
|
||||
|
||||
@id = Page::Project::Show.perform(&:project_id)
|
||||
end
|
||||
|
||||
def fabricate_via_api!
|
||||
|
@ -219,7 +223,7 @@ module QA
|
|||
auto_devops_enabled: @auto_devops_enabled
|
||||
}
|
||||
|
||||
unless @standalone
|
||||
unless @personal_namespace
|
||||
post_body[:namespace_id] = group.id
|
||||
post_body[:path] = name
|
||||
end
|
||||
|
|
|
@ -47,7 +47,7 @@ module QA
|
|||
|
||||
def create_project(user, api_client, project_name)
|
||||
project = Resource::Project.fabricate_via_api! do |project|
|
||||
project.standalone = true
|
||||
project.personal_namespace = true
|
||||
project.add_name_uuid = false
|
||||
project.name = project_name
|
||||
project.path_with_namespace = "#{user.username}/#{project_name}"
|
||||
|
|
|
@ -2,24 +2,52 @@
|
|||
|
||||
module QA
|
||||
RSpec.describe 'Manage', :smoke do
|
||||
describe 'Project creation' do
|
||||
it 'user creates a new project',
|
||||
testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1857' do
|
||||
describe 'Project' do
|
||||
shared_examples 'successful project creation' do
|
||||
it 'creates a new project' do
|
||||
Page::Project::Show.perform do |project|
|
||||
expect(project).to have_content(project_name)
|
||||
expect(project).to have_content(
|
||||
/Project \S?#{project_name}\S+ was successfully created/
|
||||
)
|
||||
expect(project).to have_content('create awesome project test')
|
||||
expect(project).to have_content('The repository for this project is empty')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
Flow::Login.sign_in
|
||||
project
|
||||
end
|
||||
|
||||
created_project = Resource::Project.fabricate_via_browser_ui! do |project|
|
||||
project.name = 'awesome-project'
|
||||
project.description = 'create awesome project test'
|
||||
context 'in group', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1857' do
|
||||
let(:project_name) { "project-in-group-#{SecureRandom.hex(8)}" }
|
||||
let(:project) do
|
||||
Resource::Project.fabricate_via_browser_ui! do |project|
|
||||
project.name = project_name
|
||||
project.description = 'create awesome project test'
|
||||
end
|
||||
end
|
||||
|
||||
Page::Project::Show.perform do |project|
|
||||
expect(project).to have_content(created_project.name)
|
||||
expect(project).to have_content(
|
||||
/Project \S?awesome-project\S+ was successfully created/
|
||||
)
|
||||
expect(project).to have_content('create awesome project test')
|
||||
expect(project).to have_content('The repository for this project is empty')
|
||||
it_behaves_like 'successful project creation'
|
||||
end
|
||||
|
||||
context 'in personal namespace', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1888' do
|
||||
let(:project_name) { "project-in-personal-namespace-#{SecureRandom.hex(8)}" }
|
||||
let(:project) do
|
||||
Resource::Project.fabricate_via_browser_ui! do |project|
|
||||
project.name = project_name
|
||||
project.description = 'create awesome project test'
|
||||
project.personal_namespace = true
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'successful project creation'
|
||||
end
|
||||
|
||||
after do
|
||||
project.remove_via_api!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -57,37 +57,44 @@ RSpec.describe ApplicationExperiment, :experiment do
|
|||
end
|
||||
|
||||
describe "#publish" do
|
||||
it "doesn't track or publish to the client or database if we can't track", :snowplow do
|
||||
allow(subject).to receive(:should_track?).and_return(false)
|
||||
let(:should_track) { true }
|
||||
|
||||
expect(subject).not_to receive(:publish_to_client)
|
||||
expect(subject).not_to receive(:publish_to_database)
|
||||
|
||||
subject.publish
|
||||
|
||||
expect_no_snowplow_event
|
||||
before do
|
||||
allow(subject).to receive(:should_track?).and_return(should_track)
|
||||
end
|
||||
|
||||
it "tracks the assignment" do
|
||||
expect(subject).to receive(:track).with(:assignment)
|
||||
|
||||
it "tracks the assignment", :snowplow do
|
||||
subject.publish
|
||||
|
||||
expect_snowplow_event(
|
||||
category: 'namespaced/stub',
|
||||
action: 'assignment',
|
||||
context: [{ schema: anything, data: anything }]
|
||||
)
|
||||
end
|
||||
|
||||
it "publishes the to the client" do
|
||||
it "publishes to the client" do
|
||||
expect(subject).to receive(:publish_to_client)
|
||||
|
||||
subject.publish
|
||||
end
|
||||
|
||||
it "publishes to the database if we've opted for that" do
|
||||
subject.record!
|
||||
|
||||
expect(subject).to receive(:publish_to_database)
|
||||
|
||||
subject.publish
|
||||
end
|
||||
|
||||
context 'when we should not track' do
|
||||
let(:should_track) { false }
|
||||
|
||||
it 'does not track an event to Snowplow', :snowplow do
|
||||
subject.publish
|
||||
|
||||
expect_no_snowplow_event
|
||||
end
|
||||
end
|
||||
|
||||
describe "#publish_to_client" do
|
||||
it "adds the data into Gon" do
|
||||
signature = { key: '86208ac54ca798e11f127e8b23ec396a', variant: 'control' }
|
||||
|
@ -101,50 +108,79 @@ RSpec.describe ApplicationExperiment, :experiment do
|
|||
|
||||
expect { subject.publish_to_client }.not_to raise_error
|
||||
end
|
||||
|
||||
context 'when we should not track' do
|
||||
let(:should_track) { false }
|
||||
|
||||
it 'returns early' do
|
||||
expect(Gon).not_to receive(:push)
|
||||
|
||||
subject.publish_to_client
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#publish_to_database" do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
let(:context) { { context_key => context_value }}
|
||||
|
||||
before do
|
||||
subject.record!
|
||||
end
|
||||
|
||||
context "when there's a usable subject" do
|
||||
where(:context_key, :context_value, :object_type) do
|
||||
:namespace | build(:namespace) | :namespace
|
||||
:group | build(:namespace) | :namespace
|
||||
:project | build(:project) | :project
|
||||
:user | build(:user) | :user
|
||||
:actor | build(:user) | :user
|
||||
describe '#publish_to_database' do
|
||||
shared_examples 'does not record to the database' do
|
||||
it 'does not create an experiment record' do
|
||||
expect { subject.publish_to_database }.not_to change(Experiment, :count)
|
||||
end
|
||||
|
||||
with_them do
|
||||
it "creates an experiment and experiment subject record" do
|
||||
expect { subject.publish_to_database }.to change(Experiment, :count).by(1)
|
||||
|
||||
expect(Experiment.last.name).to eq('namespaced/stub')
|
||||
expect(ExperimentSubject.last.send(object_type)).to eq(context[context_key])
|
||||
end
|
||||
it 'does not create an experiment subject record' do
|
||||
expect { subject.publish_to_database }.not_to change(ExperimentSubject, :count)
|
||||
end
|
||||
end
|
||||
|
||||
context "when there's not a usable subject" do
|
||||
where(:context_key, :context_value) do
|
||||
:namespace | nil
|
||||
:foo | :bar
|
||||
context 'when we explicitly request to record' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
before do
|
||||
subject.record!
|
||||
end
|
||||
|
||||
with_them do
|
||||
it "doesn't create an experiment record" do
|
||||
expect { subject.publish_to_database }.not_to change(Experiment, :count)
|
||||
context 'when there is a usable subject' do
|
||||
let(:context) { { context_key => context_value } }
|
||||
|
||||
where(:context_key, :context_value, :object_type) do
|
||||
:namespace | build(:namespace) | :namespace
|
||||
:group | build(:namespace) | :namespace
|
||||
:project | build(:project) | :project
|
||||
:user | build(:user) | :user
|
||||
:actor | build(:user) | :user
|
||||
end
|
||||
|
||||
it "doesn't create an experiment subject record" do
|
||||
expect { subject.publish_to_database }.not_to change(ExperimentSubject, :count)
|
||||
with_them do
|
||||
it 'creates an experiment and experiment subject record' do
|
||||
expect { subject.publish_to_database }.to change(Experiment, :count).by(1)
|
||||
|
||||
expect(Experiment.last.name).to eq('namespaced/stub')
|
||||
expect(ExperimentSubject.last.send(object_type)).to eq(context[context_key])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is not a usable subject' do
|
||||
let(:context) { { context_key => context_value } }
|
||||
|
||||
where(:context_key, :context_value) do
|
||||
:namespace | nil
|
||||
:foo | :bar
|
||||
end
|
||||
|
||||
with_them do
|
||||
include_examples 'does not record to the database'
|
||||
end
|
||||
end
|
||||
|
||||
context 'but we should not track' do
|
||||
let(:should_track) { false }
|
||||
|
||||
include_examples 'does not record to the database'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when we have not explicitly requested to record' do
|
||||
include_examples 'does not record to the database'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,13 +17,31 @@ exports[`Design management design version dropdown component renders design vers
|
|||
iconname=""
|
||||
iconrightarialabel=""
|
||||
iconrightname=""
|
||||
ischeckcentered="true"
|
||||
ischecked="true"
|
||||
ischeckitem="true"
|
||||
secondarytext=""
|
||||
>
|
||||
Version
|
||||
2
|
||||
(latest)
|
||||
<strong>
|
||||
Version
|
||||
2
|
||||
(latest)
|
||||
</strong>
|
||||
|
||||
<div
|
||||
class="gl-text-gray-600 gl-mt-1"
|
||||
>
|
||||
<div>
|
||||
Adminstrator
|
||||
</div>
|
||||
|
||||
<time-ago-stub
|
||||
class="text-1"
|
||||
cssclass=""
|
||||
time="2021-08-09T06:05:00Z"
|
||||
tooltipplacement="bottom"
|
||||
/>
|
||||
</div>
|
||||
</gl-dropdown-item-stub>
|
||||
<gl-dropdown-item-stub
|
||||
avatarurl=""
|
||||
|
@ -31,12 +49,30 @@ exports[`Design management design version dropdown component renders design vers
|
|||
iconname=""
|
||||
iconrightarialabel=""
|
||||
iconrightname=""
|
||||
ischeckcentered="true"
|
||||
ischeckitem="true"
|
||||
secondarytext=""
|
||||
>
|
||||
Version
|
||||
1
|
||||
|
||||
<strong>
|
||||
Version
|
||||
1
|
||||
|
||||
</strong>
|
||||
|
||||
<div
|
||||
class="gl-text-gray-600 gl-mt-1"
|
||||
>
|
||||
<div>
|
||||
Adminstrator
|
||||
</div>
|
||||
|
||||
<time-ago-stub
|
||||
class="text-1"
|
||||
cssclass=""
|
||||
time="2021-08-09T06:05:00Z"
|
||||
tooltipplacement="bottom"
|
||||
/>
|
||||
</div>
|
||||
</gl-dropdown-item-stub>
|
||||
</gl-dropdown-stub>
|
||||
`;
|
||||
|
@ -58,13 +94,31 @@ exports[`Design management design version dropdown component renders design vers
|
|||
iconname=""
|
||||
iconrightarialabel=""
|
||||
iconrightname=""
|
||||
ischeckcentered="true"
|
||||
ischecked="true"
|
||||
ischeckitem="true"
|
||||
secondarytext=""
|
||||
>
|
||||
Version
|
||||
2
|
||||
(latest)
|
||||
<strong>
|
||||
Version
|
||||
2
|
||||
(latest)
|
||||
</strong>
|
||||
|
||||
<div
|
||||
class="gl-text-gray-600 gl-mt-1"
|
||||
>
|
||||
<div>
|
||||
Adminstrator
|
||||
</div>
|
||||
|
||||
<time-ago-stub
|
||||
class="text-1"
|
||||
cssclass=""
|
||||
time="2021-08-09T06:05:00Z"
|
||||
tooltipplacement="bottom"
|
||||
/>
|
||||
</div>
|
||||
</gl-dropdown-item-stub>
|
||||
<gl-dropdown-item-stub
|
||||
avatarurl=""
|
||||
|
@ -72,12 +126,30 @@ exports[`Design management design version dropdown component renders design vers
|
|||
iconname=""
|
||||
iconrightarialabel=""
|
||||
iconrightname=""
|
||||
ischeckcentered="true"
|
||||
ischeckitem="true"
|
||||
secondarytext=""
|
||||
>
|
||||
Version
|
||||
1
|
||||
|
||||
<strong>
|
||||
Version
|
||||
1
|
||||
|
||||
</strong>
|
||||
|
||||
<div
|
||||
class="gl-text-gray-600 gl-mt-1"
|
||||
>
|
||||
<div>
|
||||
Adminstrator
|
||||
</div>
|
||||
|
||||
<time-ago-stub
|
||||
class="text-1"
|
||||
cssclass=""
|
||||
time="2021-08-09T06:05:00Z"
|
||||
tooltipplacement="bottom"
|
||||
/>
|
||||
</div>
|
||||
</gl-dropdown-item-stub>
|
||||
</gl-dropdown-stub>
|
||||
`;
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { GlDropdown, GlDropdownItem, GlSprintf } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import DesignVersionDropdown from '~/design_management/components/upload/design_version_dropdown.vue';
|
||||
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import mockAllVersions from './mock_data/all_versions';
|
||||
|
||||
const LATEST_VERSION_ID = 3;
|
||||
const LATEST_VERSION_ID = 1;
|
||||
const PREVIOUS_VERSION_ID = 2;
|
||||
|
||||
const designRouteFactory = (versionId) => ({
|
||||
|
@ -110,5 +111,13 @@ describe('Design management design version dropdown component', () => {
|
|||
expect(wrapper.findAll(GlDropdownItem)).toHaveLength(wrapper.vm.allVersions.length);
|
||||
});
|
||||
});
|
||||
|
||||
it('should render TimeAgo', async () => {
|
||||
createComponent();
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.findAllComponents(TimeAgo)).toHaveLength(wrapper.vm.allVersions.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,10 +1,20 @@
|
|||
export default [
|
||||
{
|
||||
id: 'gid://gitlab/DesignManagement::Version/3',
|
||||
sha: '0945756378e0b1588b9dd40d5a6b99e8b7198f55',
|
||||
id: 'gid://gitlab/DesignManagement::Version/1',
|
||||
sha: 'b389071a06c153509e11da1f582005b316667001',
|
||||
createdAt: '2021-08-09T06:05:00Z',
|
||||
author: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
name: 'Adminstrator',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gid://gitlab/DesignManagement::Version/2',
|
||||
sha: '5b063fef0cd7213b312db65b30e24f057df21b20',
|
||||
sha: 'b389071a06c153509e11da1f582005b316667021',
|
||||
createdAt: '2021-08-09T06:05:00Z',
|
||||
author: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
name: 'Adminstrator',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -2,5 +2,19 @@ export default [
|
|||
{
|
||||
id: 'gid://gitlab/DesignManagement::Version/1',
|
||||
sha: 'b389071a06c153509e11da1f582005b316667001',
|
||||
createdAt: '2021-08-09T06:05:00Z',
|
||||
author: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
name: 'Adminstrator',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gid://gitlab/DesignManagement::Version/1',
|
||||
sha: 'b389071a06c153509e11da1f582005b316667021',
|
||||
createdAt: '2021-08-09T06:05:00Z',
|
||||
author: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
name: 'Adminstrator',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -5,8 +5,10 @@ import {
|
|||
GlSearchBoxByClick,
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlTable,
|
||||
} from '@gitlab/ui';
|
||||
import { mount, createLocalVue } from '@vue/test-utils';
|
||||
import { nextTick } from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import stubChildren from 'helpers/stub_children';
|
||||
|
@ -40,10 +42,15 @@ describe('import table', () => {
|
|||
];
|
||||
const FAKE_PAGE_INFO = { page: 1, perPage: 20, total: 40, totalPages: 2 };
|
||||
|
||||
const findImportAllButton = () => wrapper.find('h1').find(GlButton);
|
||||
const findImportSelectedButton = () =>
|
||||
wrapper.findAllComponents(GlButton).wrappers.find((w) => w.text() === 'Import selected');
|
||||
const findPaginationDropdown = () => wrapper.findComponent(GlDropdown);
|
||||
const findPaginationDropdownText = () => findPaginationDropdown().find({ ref: 'text' }).text();
|
||||
|
||||
// TODO: remove this ugly approach when
|
||||
// issue: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1531
|
||||
const findTable = () => wrapper.vm.getTableRef();
|
||||
|
||||
const createComponent = ({ bulkImportSourceGroups }) => {
|
||||
apolloProvider = createMockApollo([], {
|
||||
Query: {
|
||||
|
@ -294,16 +301,8 @@ describe('import table', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('import all button', () => {
|
||||
it('does not exists when no groups available', () => {
|
||||
createComponent({
|
||||
bulkImportSourceGroups: () => new Promise(() => {}),
|
||||
});
|
||||
|
||||
expect(findImportAllButton().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('exists when groups are available for import', async () => {
|
||||
describe('bulk operations', () => {
|
||||
it('import selected button is disabled when no groups selected', async () => {
|
||||
createComponent({
|
||||
bulkImportSourceGroups: () => ({
|
||||
nodes: FAKE_GROUPS,
|
||||
|
@ -312,10 +311,66 @@ describe('import table', () => {
|
|||
});
|
||||
await waitForPromises();
|
||||
|
||||
expect(findImportAllButton().exists()).toBe(true);
|
||||
expect(findImportSelectedButton().props().disabled).toBe(true);
|
||||
});
|
||||
|
||||
it('counts only not-imported groups', async () => {
|
||||
it('import selected button is enabled when groups were selected for import', async () => {
|
||||
createComponent({
|
||||
bulkImportSourceGroups: () => ({
|
||||
nodes: FAKE_GROUPS,
|
||||
pageInfo: FAKE_PAGE_INFO,
|
||||
}),
|
||||
});
|
||||
await waitForPromises();
|
||||
wrapper.find(GlTable).vm.$emit('row-selected', [FAKE_GROUPS[0]]);
|
||||
await nextTick();
|
||||
|
||||
expect(findImportSelectedButton().props().disabled).toBe(false);
|
||||
});
|
||||
|
||||
it('does not allow selecting already started groups', async () => {
|
||||
const NEW_GROUPS = [generateFakeEntry({ id: 1, status: STATUSES.FINISHED })];
|
||||
|
||||
createComponent({
|
||||
bulkImportSourceGroups: () => ({
|
||||
nodes: NEW_GROUPS,
|
||||
pageInfo: FAKE_PAGE_INFO,
|
||||
}),
|
||||
});
|
||||
await waitForPromises();
|
||||
|
||||
findTable().selectRow(0);
|
||||
await nextTick();
|
||||
|
||||
expect(findImportSelectedButton().props().disabled).toBe(true);
|
||||
});
|
||||
|
||||
it('does not allow selecting groups with validation errors', async () => {
|
||||
const NEW_GROUPS = [
|
||||
generateFakeEntry({
|
||||
id: 2,
|
||||
status: STATUSES.NONE,
|
||||
validation_errors: [{ field: 'new_name', message: 'FAKE_VALIDATION_ERROR' }],
|
||||
}),
|
||||
];
|
||||
|
||||
createComponent({
|
||||
bulkImportSourceGroups: () => ({
|
||||
nodes: NEW_GROUPS,
|
||||
pageInfo: FAKE_PAGE_INFO,
|
||||
}),
|
||||
});
|
||||
await waitForPromises();
|
||||
|
||||
// TODO: remove this ugly approach when
|
||||
// issue: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1531
|
||||
findTable().selectRow(0);
|
||||
await nextTick();
|
||||
|
||||
expect(findImportSelectedButton().props().disabled).toBe(true);
|
||||
});
|
||||
|
||||
it('invokes importGroups mutation when import selected button is clicked', async () => {
|
||||
const NEW_GROUPS = [
|
||||
generateFakeEntry({ id: 1, status: STATUSES.NONE }),
|
||||
generateFakeEntry({ id: 2, status: STATUSES.NONE }),
|
||||
|
@ -328,31 +383,19 @@ describe('import table', () => {
|
|||
pageInfo: FAKE_PAGE_INFO,
|
||||
}),
|
||||
});
|
||||
jest.spyOn(apolloProvider.defaultClient, 'mutate');
|
||||
await waitForPromises();
|
||||
|
||||
expect(findImportAllButton().text()).toMatchInterpolatedText('Import 2 groups');
|
||||
});
|
||||
findTable().selectRow(0);
|
||||
findTable().selectRow(1);
|
||||
await nextTick();
|
||||
|
||||
it('disables button when any group has validation errors', async () => {
|
||||
const NEW_GROUPS = [
|
||||
generateFakeEntry({ id: 1, status: STATUSES.NONE }),
|
||||
generateFakeEntry({
|
||||
id: 2,
|
||||
status: STATUSES.NONE,
|
||||
validation_errors: [{ field: 'new_name', message: 'test validation error' }],
|
||||
}),
|
||||
generateFakeEntry({ id: 3, status: STATUSES.FINISHED }),
|
||||
];
|
||||
findImportSelectedButton().vm.$emit('click');
|
||||
|
||||
createComponent({
|
||||
bulkImportSourceGroups: () => ({
|
||||
nodes: NEW_GROUPS,
|
||||
pageInfo: FAKE_PAGE_INFO,
|
||||
}),
|
||||
expect(apolloProvider.defaultClient.mutate).toHaveBeenCalledWith({
|
||||
mutation: importGroupsMutation,
|
||||
variables: { sourceGroupIds: [NEW_GROUPS[0].id, NEW_GROUPS[1].id] },
|
||||
});
|
||||
await waitForPromises();
|
||||
|
||||
expect(findImportAllButton().props().disabled).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,7 +13,6 @@ describe('DropdownWidget component', () => {
|
|||
const createComponent = ({ props = {} } = {}) => {
|
||||
wrapper = shallowMount(DropdownWidget, {
|
||||
propsData: {
|
||||
...props,
|
||||
options: [
|
||||
{
|
||||
id: '1',
|
||||
|
@ -24,6 +23,7 @@ describe('DropdownWidget component', () => {
|
|||
title: 'Option 2',
|
||||
},
|
||||
],
|
||||
...props,
|
||||
},
|
||||
stubs: {
|
||||
GlDropdown,
|
||||
|
@ -76,4 +76,22 @@ describe('DropdownWidget component', () => {
|
|||
expect(wrapper.emitted('set-option')).toEqual([[wrapper.props().options[1]]]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when options are users', () => {
|
||||
const mockUser = {
|
||||
id: 1,
|
||||
name: 'User name',
|
||||
username: 'username',
|
||||
avatarUrl: 'foo/bar',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent({ props: { options: [mockUser] } });
|
||||
});
|
||||
|
||||
it('passes user related props to dropdown item', () => {
|
||||
expect(findDropdownItems().at(0).props('avatarUrl')).toBe(mockUser.avatarUrl);
|
||||
expect(findDropdownItems().at(0).props('secondaryText')).toBe(mockUser.username);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,7 +7,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::ConnectionProxy do
|
|||
|
||||
describe '#select' do
|
||||
it 'performs a read' do
|
||||
expect(proxy).to receive(:read_using_load_balancer).with(:select, ['foo'])
|
||||
expect(proxy).to receive(:read_using_load_balancer).with(:select, 'foo')
|
||||
|
||||
proxy.select('foo')
|
||||
end
|
||||
|
@ -26,7 +26,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::ConnectionProxy do
|
|||
arel = double(:arel)
|
||||
|
||||
expect(proxy).to receive(:read_using_load_balancer)
|
||||
.with(:select_all, [arel, 'foo', []])
|
||||
.with(:select_all, arel, 'foo', [])
|
||||
|
||||
proxy.select_all(arel, 'foo')
|
||||
end
|
||||
|
@ -37,7 +37,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::ConnectionProxy do
|
|||
arel = double(:arel, locked: true)
|
||||
|
||||
expect(proxy).to receive(:write_using_load_balancer)
|
||||
.with(:select_all, [arel, 'foo', []], sticky: true)
|
||||
.with(:select_all, arel, 'foo', [], sticky: true)
|
||||
|
||||
proxy.select_all(arel, 'foo')
|
||||
end
|
||||
|
@ -48,7 +48,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::ConnectionProxy do
|
|||
describe "#{name}" do
|
||||
it 'runs the query on the replica' do
|
||||
expect(proxy).to receive(:read_using_load_balancer)
|
||||
.with(name, ['foo'])
|
||||
.with(name, 'foo')
|
||||
|
||||
proxy.send(name, 'foo')
|
||||
end
|
||||
|
@ -59,7 +59,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::ConnectionProxy do
|
|||
describe "#{name}" do
|
||||
it 'runs the query on the primary and sticks to it' do
|
||||
expect(proxy).to receive(:write_using_load_balancer)
|
||||
.with(name, ['foo'], sticky: true)
|
||||
.with(name, 'foo', sticky: true)
|
||||
|
||||
proxy.send(name, 'foo')
|
||||
end
|
||||
|
@ -187,7 +187,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::ConnectionProxy do
|
|||
describe '#method_missing' do
|
||||
it 'runs the query on the primary without sticking to it' do
|
||||
expect(proxy).to receive(:write_using_load_balancer)
|
||||
.with(:foo, ['foo'])
|
||||
.with(:foo, 'foo')
|
||||
|
||||
proxy.foo('foo')
|
||||
end
|
||||
|
@ -197,7 +197,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::ConnectionProxy do
|
|||
|
||||
expect(proxy).to receive(:write_using_load_balancer).and_call_original
|
||||
|
||||
expect { proxy.case_sensitive_comparison(:table, :attribute, :column, { value: :value, format: :format }) }
|
||||
expect { proxy.case_sensitive_comparison(:table, :attribute, :column, value: :value, format: :format) }
|
||||
.not_to raise_error
|
||||
end
|
||||
|
||||
|
@ -212,7 +212,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::ConnectionProxy do
|
|||
end
|
||||
|
||||
it 'runs the query on the replica' do
|
||||
expect(proxy).to receive(:read_using_load_balancer).with(:foo, ['foo'])
|
||||
expect(proxy).to receive(:read_using_load_balancer).with(:foo, 'foo')
|
||||
|
||||
proxy.foo('foo')
|
||||
end
|
||||
|
@ -222,7 +222,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::ConnectionProxy do
|
|||
|
||||
expect(proxy).to receive(:read_using_load_balancer).and_call_original
|
||||
|
||||
expect { proxy.case_sensitive_comparison(:table, :attribute, :column, { value: :value, format: :format }) }
|
||||
expect { proxy.case_sensitive_comparison(:table, :attribute, :column, value: :value, format: :format) }
|
||||
.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
@ -245,7 +245,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::ConnectionProxy do
|
|||
expect(connection).to receive(:foo).with('foo')
|
||||
expect(proxy.load_balancer).to receive(:read).and_yield(connection)
|
||||
|
||||
proxy.read_using_load_balancer(:foo, ['foo'])
|
||||
proxy.read_using_load_balancer(:foo, 'foo')
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -257,7 +257,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::ConnectionProxy do
|
|||
expect(connection).to receive(:foo).with('foo')
|
||||
expect(proxy.load_balancer).to receive(:read).and_yield(connection)
|
||||
|
||||
proxy.read_using_load_balancer(:foo, ['foo'])
|
||||
proxy.read_using_load_balancer(:foo, 'foo')
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -269,7 +269,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::ConnectionProxy do
|
|||
expect(connection).to receive(:foo).with('foo')
|
||||
expect(proxy.load_balancer).to receive(:read).and_yield(connection)
|
||||
|
||||
proxy.read_using_load_balancer(:foo, ['foo'])
|
||||
proxy.read_using_load_balancer(:foo, 'foo')
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -283,7 +283,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::ConnectionProxy do
|
|||
expect(proxy.load_balancer).to receive(:read_write)
|
||||
.and_yield(connection)
|
||||
|
||||
proxy.read_using_load_balancer(:foo, ['foo'])
|
||||
proxy.read_using_load_balancer(:foo, 'foo')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -302,7 +302,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::ConnectionProxy do
|
|||
expect(connection).to receive(:foo).with('foo')
|
||||
expect(session).not_to receive(:write!)
|
||||
|
||||
proxy.write_using_load_balancer(:foo, ['foo'])
|
||||
proxy.write_using_load_balancer(:foo, 'foo')
|
||||
end
|
||||
|
||||
it 'sticks to the primary when sticking is enabled' do
|
||||
|
@ -310,7 +310,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::ConnectionProxy do
|
|||
expect(connection).to receive(:foo).with('foo')
|
||||
expect(session).to receive(:write!)
|
||||
|
||||
proxy.write_using_load_balancer(:foo, ['foo'], sticky: true)
|
||||
proxy.write_using_load_balancer(:foo, 'foo', sticky: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Usage::Docs::Helper do
|
||||
subject(:helper) { klass.new }
|
||||
|
||||
let_it_be(:klass) do
|
||||
Class.new do
|
||||
include Gitlab::Usage::Docs::Helper
|
||||
end
|
||||
end
|
||||
|
||||
let(:metric_definition) do
|
||||
{
|
||||
data_category: 'standard',
|
||||
name: 'test_metric',
|
||||
description: description,
|
||||
product_group: 'group::product intelligence',
|
||||
status: 'data_available',
|
||||
tier: %w(free premium)
|
||||
}
|
||||
end
|
||||
|
||||
let(:description) { 'Metric description' }
|
||||
|
||||
describe '#render_name' do
|
||||
it { expect(helper.render_name(metric_definition[:name])).to eq('### `test_metric`') }
|
||||
end
|
||||
|
||||
describe '#render_description' do
|
||||
context 'without description' do
|
||||
let(:description) { nil }
|
||||
|
||||
it { expect(helper.render_description(metric_definition)).to eq('Missing description') }
|
||||
end
|
||||
|
||||
context 'without description' do
|
||||
it { expect(helper.render_description(metric_definition)).to eq('Metric description') }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#render_yaml_link' do
|
||||
let(:yaml_link) { 'config/metrics/license/test_metric.yml' }
|
||||
let(:expected) { "[YAML definition](#{yaml_link})" }
|
||||
|
||||
it { expect(helper.render_yaml_link(yaml_link)).to eq(expected) }
|
||||
end
|
||||
|
||||
describe '#render_status' do
|
||||
let(:expected) { "Status: `data_available`" }
|
||||
|
||||
it { expect(helper.render_status(metric_definition)).to eq(expected) }
|
||||
end
|
||||
|
||||
describe '#render_owner' do
|
||||
let(:expected) { "Group: `group::product intelligence`" }
|
||||
|
||||
it { expect(helper.render_owner(metric_definition)).to eq(expected) }
|
||||
end
|
||||
|
||||
describe '#render_tiers' do
|
||||
let(:expected) { "Tiers: `free`, `premium`" }
|
||||
|
||||
it { expect(helper.render_tiers(metric_definition)).to eq(expected) }
|
||||
end
|
||||
|
||||
describe '#render_data_category' do
|
||||
let(:expected) { 'Data Category: `standard`' }
|
||||
|
||||
it { expect(helper.render_data_category(metric_definition)).to eq(expected) }
|
||||
end
|
||||
|
||||
describe '#render_owner' do
|
||||
let(:expected) { "Group: `group::product intelligence`" }
|
||||
|
||||
it { expect(helper.render_owner(metric_definition)).to eq(expected) }
|
||||
end
|
||||
end
|
|
@ -1,24 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
CODE_REGEX = %r{<code>(.*)</code>}.freeze
|
||||
|
||||
RSpec.describe Gitlab::Usage::Docs::Renderer do
|
||||
describe 'contents' do
|
||||
let(:dictionary_path) { Gitlab::Usage::Docs::Renderer::DICTIONARY_PATH }
|
||||
let(:items) { Gitlab::Usage::MetricDefinition.definitions.first(10).to_h }
|
||||
|
||||
it 'generates dictionary for given items' do
|
||||
generated_dictionary = described_class.new(items).contents
|
||||
|
||||
generated_dictionary_keys = RDoc::Markdown
|
||||
.parse(generated_dictionary)
|
||||
.table_of_contents
|
||||
.select { |metric_doc| metric_doc.level == 3 }
|
||||
.map { |item| item.text.match(CODE_REGEX)&.captures&.first }
|
||||
|
||||
expect(generated_dictionary_keys).to match_array(items.keys)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,26 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Usage::Docs::ValueFormatter do
|
||||
describe '.format' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
where(:key, :value, :expected_value) do
|
||||
:product_group | 'growth::product intelligence' | '`growth::product intelligence`'
|
||||
:data_source | 'redis' | 'Redis'
|
||||
:data_source | 'ruby' | 'Ruby'
|
||||
:introduced_by_url | 'http://test.com' | '[Introduced by](http://test.com)'
|
||||
:tier | %w(gold premium) | ' `gold`, `premium`'
|
||||
:distribution | %w(ce ee) | ' `ce`, `ee`'
|
||||
:key_path | 'key.path' | '**`key.path`**'
|
||||
:milestone | '13.4' | '13.4'
|
||||
:status | 'data_available' | '`data_available`'
|
||||
end
|
||||
|
||||
with_them do
|
||||
subject { described_class.format(key, value) }
|
||||
|
||||
it { is_expected.to eq(expected_value) }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1067,8 +1067,9 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
|
|||
|
||||
subject { described_class.system_usage_data_settings }
|
||||
|
||||
it 'gathers settings usage data', :aggregate_failures do
|
||||
it 'gathers encrypted secrets usage data', :aggregate_failures do
|
||||
expect(subject[:settings][:ldap_encrypted_secrets_enabled]).to eq(Gitlab::Auth::Ldap::Config.encrypted_secrets.active?)
|
||||
expect(subject[:settings][:smtp_encrypted_secrets_enabled]).to eq(Gitlab::Email::SmtpConfig.encrypted_secrets.active?)
|
||||
end
|
||||
|
||||
it 'populates operating system information' do
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
require_migration!
|
||||
|
||||
RSpec.describe ResetJobTokenScopeEnabledAgain do
|
||||
let(:settings) { table(:project_ci_cd_settings) }
|
||||
let(:projects) { table(:projects) }
|
||||
let(:namespaces) { table(:namespaces) }
|
||||
let(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') }
|
||||
let(:project_1) { projects.create!(name: 'proj-1', path: 'gitlab-org', namespace_id: namespace.id)}
|
||||
let(:project_2) { projects.create!(name: 'proj-2', path: 'gitlab-org', namespace_id: namespace.id)}
|
||||
|
||||
before do
|
||||
settings.create!(id: 1, project_id: project_1.id, job_token_scope_enabled: true)
|
||||
settings.create!(id: 2, project_id: project_2.id, job_token_scope_enabled: false)
|
||||
end
|
||||
|
||||
it 'migrates job_token_scope_enabled to be always false' do
|
||||
expect { migrate! }
|
||||
.to change { settings.where(job_token_scope_enabled: false).count }
|
||||
.from(1).to(2)
|
||||
end
|
||||
end
|
|
@ -38,13 +38,13 @@ RSpec.describe 'gitlab:ldap:secret rake tasks' do
|
|||
it 'displays error when key does not exist' do
|
||||
Settings.encrypted(ldap_secret_file).write('somevalue')
|
||||
allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(nil)
|
||||
expect { run_rake_task('gitlab:ldap:secret:show') }.to output(/Missing encryption key encrypted_settings_key_base./).to_stdout
|
||||
expect { run_rake_task('gitlab:ldap:secret:show') }.to output(/Missing encryption key encrypted_settings_key_base./).to_stderr
|
||||
end
|
||||
|
||||
it 'displays error when key is changed' do
|
||||
Settings.encrypted(ldap_secret_file).write('somevalue')
|
||||
allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(SecureRandom.hex(64))
|
||||
expect { run_rake_task('gitlab:ldap:secret:show') }.to output(/Couldn't decrypt .* Perhaps you passed the wrong key?/).to_stdout
|
||||
expect { run_rake_task('gitlab:ldap:secret:show') }.to output(/Couldn't decrypt .* Perhaps you passed the wrong key?/).to_stderr
|
||||
end
|
||||
|
||||
it 'outputs the unencrypted content when present' do
|
||||
|
@ -64,18 +64,18 @@ RSpec.describe 'gitlab:ldap:secret rake tasks' do
|
|||
|
||||
it 'displays error when key does not exist' do
|
||||
allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(nil)
|
||||
expect { run_rake_task('gitlab:ldap:secret:edit') }.to output(/Missing encryption key encrypted_settings_key_base./).to_stdout
|
||||
expect { run_rake_task('gitlab:ldap:secret:edit') }.to output(/Missing encryption key encrypted_settings_key_base./).to_stderr
|
||||
end
|
||||
|
||||
it 'displays error when key is changed' do
|
||||
Settings.encrypted(ldap_secret_file).write('somevalue')
|
||||
allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(SecureRandom.hex(64))
|
||||
expect { run_rake_task('gitlab:ldap:secret:edit') }.to output(/Couldn't decrypt .* Perhaps you passed the wrong key?/).to_stdout
|
||||
expect { run_rake_task('gitlab:ldap:secret:edit') }.to output(/Couldn't decrypt .* Perhaps you passed the wrong key?/).to_stderr
|
||||
end
|
||||
|
||||
it 'displays error when write directory does not exist' do
|
||||
FileUtils.rm_rf(Rails.root.join('tmp/tests/ldapenc'))
|
||||
expect { run_rake_task('gitlab:ldap:secret:edit') }.to output(/Directory .* does not exist./).to_stdout
|
||||
expect { run_rake_task('gitlab:ldap:secret:edit') }.to output(/Directory .* does not exist./).to_stderr
|
||||
end
|
||||
|
||||
it 'shows a warning when content is invalid' do
|
||||
|
@ -87,7 +87,7 @@ RSpec.describe 'gitlab:ldap:secret rake tasks' do
|
|||
|
||||
it 'displays error when $EDITOR is not set' do
|
||||
stub_env('EDITOR', nil)
|
||||
expect { run_rake_task('gitlab:ldap:secret:edit') }.to output(/No \$EDITOR specified to open file. Please provide one when running the command/).to_stdout
|
||||
expect { run_rake_task('gitlab:ldap:secret:edit') }.to output(/No \$EDITOR specified to open file. Please provide one when running the command/).to_stderr
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -106,12 +106,12 @@ RSpec.describe 'gitlab:ldap:secret rake tasks' do
|
|||
|
||||
it 'displays error when key does not exist' do
|
||||
allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(nil)
|
||||
expect { run_rake_task('gitlab:ldap:secret:write') }.to output(/Missing encryption key encrypted_settings_key_base./).to_stdout
|
||||
expect { run_rake_task('gitlab:ldap:secret:write') }.to output(/Missing encryption key encrypted_settings_key_base./).to_stderr
|
||||
end
|
||||
|
||||
it 'displays error when write directory does not exist' do
|
||||
FileUtils.rm_rf('tmp/tests/ldapenc/')
|
||||
expect { run_rake_task('gitlab:ldap:secret:write') }.to output(/Directory .* does not exist./).to_stdout
|
||||
expect { run_rake_task('gitlab:ldap:secret:write') }.to output(/Directory .* does not exist./).to_stderr
|
||||
end
|
||||
|
||||
it 'shows a warning when content is invalid' do
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rake_helper'
|
||||
|
||||
RSpec.describe 'gitlab:smtp:secret rake tasks' do
|
||||
let(:smtp_secret_file) { 'tmp/tests/smtpenc/smtp_secret.yaml.enc' }
|
||||
|
||||
before do
|
||||
Rake.application.rake_require 'tasks/gitlab/smtp'
|
||||
stub_env('EDITOR', 'cat')
|
||||
stub_warn_user_is_not_gitlab
|
||||
FileUtils.mkdir_p('tmp/tests/smtpenc/')
|
||||
allow(Gitlab.config.gitlab).to receive(:email_smtp_secret_file).and_return(smtp_secret_file)
|
||||
allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(SecureRandom.hex(64))
|
||||
end
|
||||
|
||||
after do
|
||||
FileUtils.rm_rf(Rails.root.join('tmp/tests/smtpenc'))
|
||||
end
|
||||
|
||||
describe ':show' do
|
||||
it 'displays error when file does not exist' do
|
||||
expect { run_rake_task('gitlab:smtp:secret:show') }.to output(/File .* does not exist. Use `gitlab-rake gitlab:smtp:secret:edit` to change that./).to_stdout
|
||||
end
|
||||
|
||||
it 'displays error when key does not exist' do
|
||||
Settings.encrypted(smtp_secret_file).write('somevalue')
|
||||
allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(nil)
|
||||
expect { run_rake_task('gitlab:smtp:secret:show') }.to output(/Missing encryption key encrypted_settings_key_base./).to_stderr
|
||||
end
|
||||
|
||||
it 'displays error when key is changed' do
|
||||
Settings.encrypted(smtp_secret_file).write('somevalue')
|
||||
allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(SecureRandom.hex(64))
|
||||
expect { run_rake_task('gitlab:smtp:secret:show') }.to output(/Couldn't decrypt .* Perhaps you passed the wrong key?/).to_stderr
|
||||
end
|
||||
|
||||
it 'outputs the unencrypted content when present' do
|
||||
encrypted = Settings.encrypted(smtp_secret_file)
|
||||
encrypted.write('somevalue')
|
||||
expect { run_rake_task('gitlab:smtp:secret:show') }.to output(/somevalue/).to_stdout
|
||||
end
|
||||
end
|
||||
|
||||
describe 'edit' do
|
||||
it 'creates encrypted file' do
|
||||
expect { run_rake_task('gitlab:smtp:secret:edit') }.to output(/File encrypted and saved./).to_stdout
|
||||
expect(File.exist?(smtp_secret_file)).to be true
|
||||
value = Settings.encrypted(smtp_secret_file)
|
||||
expect(value.read).to match(/password: '123'/)
|
||||
end
|
||||
|
||||
it 'displays error when key does not exist' do
|
||||
allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(nil)
|
||||
expect { run_rake_task('gitlab:smtp:secret:edit') }.to output(/Missing encryption key encrypted_settings_key_base./).to_stderr
|
||||
end
|
||||
|
||||
it 'displays error when key is changed' do
|
||||
Settings.encrypted(smtp_secret_file).write('somevalue')
|
||||
allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(SecureRandom.hex(64))
|
||||
expect { run_rake_task('gitlab:smtp:secret:edit') }.to output(/Couldn't decrypt .* Perhaps you passed the wrong key?/).to_stderr
|
||||
end
|
||||
|
||||
it 'displays error when write directory does not exist' do
|
||||
FileUtils.rm_rf(Rails.root.join('tmp/tests/smtpenc'))
|
||||
expect { run_rake_task('gitlab:smtp:secret:edit') }.to output(/Directory .* does not exist./).to_stderr
|
||||
end
|
||||
|
||||
it 'shows a warning when content is invalid' do
|
||||
Settings.encrypted(smtp_secret_file).write('somevalue')
|
||||
expect { run_rake_task('gitlab:smtp:secret:edit') }.to output(/WARNING: Content was not a valid SMTP secret yml file/).to_stdout
|
||||
value = Settings.encrypted(smtp_secret_file)
|
||||
expect(value.read).to match(/somevalue/)
|
||||
end
|
||||
|
||||
it 'displays error when $EDITOR is not set' do
|
||||
stub_env('EDITOR', nil)
|
||||
expect { run_rake_task('gitlab:smtp:secret:edit') }.to output(/No \$EDITOR specified to open file. Please provide one when running the command/).to_stderr
|
||||
end
|
||||
end
|
||||
|
||||
describe 'write' do
|
||||
before do
|
||||
allow($stdin).to receive(:tty?).and_return(false)
|
||||
allow($stdin).to receive(:read).and_return('username: foo')
|
||||
end
|
||||
|
||||
it 'creates encrypted file from stdin' do
|
||||
expect { run_rake_task('gitlab:smtp:secret:write') }.to output(/File encrypted and saved./).to_stdout
|
||||
expect(File.exist?(smtp_secret_file)).to be true
|
||||
value = Settings.encrypted(smtp_secret_file)
|
||||
expect(value.read).to match(/username: foo/)
|
||||
end
|
||||
|
||||
it 'displays error when key does not exist' do
|
||||
allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(nil)
|
||||
expect { run_rake_task('gitlab:smtp:secret:write') }.to output(/Missing encryption key encrypted_settings_key_base./).to_stderr
|
||||
end
|
||||
|
||||
it 'displays error when write directory does not exist' do
|
||||
FileUtils.rm_rf('tmp/tests/smtpenc/')
|
||||
expect { run_rake_task('gitlab:smtp:secret:write') }.to output(/Directory .* does not exist./).to_stderr
|
||||
end
|
||||
|
||||
it 'shows a warning when content is invalid' do
|
||||
Settings.encrypted(smtp_secret_file).write('somevalue')
|
||||
expect { run_rake_task('gitlab:smtp:secret:edit') }.to output(/WARNING: Content was not a valid SMTP secret yml file/).to_stdout
|
||||
value = Settings.encrypted(smtp_secret_file)
|
||||
expect(value.read).to match(/somevalue/)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -177,7 +177,6 @@ RSpec.describe Tooling::Danger::ProjectHelper do
|
|||
'ee/config/metrics/counts_7d/20210216174919_g_analytics_issues_weekly.yml' | [:product_intelligence]
|
||||
'lib/gitlab/usage_data_counters/aggregated_metrics/common.yml' | [:product_intelligence]
|
||||
'lib/gitlab/usage_data_counters/hll_redis_counter.rb' | [:backend, :product_intelligence]
|
||||
'doc/development/usage_ping/dictionary.md' | [:docs, :product_intelligence]
|
||||
'lib/gitlab/tracking.rb' | [:backend, :product_intelligence]
|
||||
'spec/lib/gitlab/tracking_spec.rb' | [:backend, :product_intelligence]
|
||||
'app/helpers/tracking_helper.rb' | [:backend, :product_intelligence]
|
||||
|
|
|
@ -39,7 +39,6 @@ module Tooling
|
|||
|
||||
%r{\A(ee/)?config/feature_flags/} => :feature_flag,
|
||||
|
||||
%r{\Adoc/development/usage_ping/dictionary\.md\z} => [:docs, :product_intelligence],
|
||||
%r{\Adoc/.*(\.(md|png|gif|jpg|yml))\z} => :docs,
|
||||
%r{\A(CONTRIBUTING|LICENSE|MAINTENANCE|PHILOSOPHY|PROCESS|README)(\.md)?\z} => :docs,
|
||||
%r{\Adata/whats_new/} => :docs,
|
||||
|
|
Loading…
Reference in New Issue