Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
d84f18d66c
commit
680d188025
|
@ -19,7 +19,7 @@ See [the general developer security release guidelines](https://gitlab.com/gitla
|
|||
- [ ] A [CHANGELOG entry](https://docs.gitlab.com/ee/development/changelog.html) is added without a `merge_request` value, with `type` set to `security`
|
||||
- [ ] Assign to a reviewer and maintainer, per our [Code Review process].
|
||||
- [ ] For the MR targeting `master`:
|
||||
- [ ] Ping appsec team member who created the issue and ask for a non-blocking review with `Please review this MR`.
|
||||
- [ ] Ask for a non-blocking review from the AppSec team member associated to the issue in the [Canonical repository](https://gitlab.com/gitlab-org/gitlab). If you're unsure who to ping, ask on `#sec-appsec` Slack channel.
|
||||
- [ ] Ensure it's approved according to our [Approval Guidelines].
|
||||
- [ ] Merge request _must not_ close the corresponding security issue, _unless_ it targets `master`.
|
||||
|
||||
|
|
|
@ -359,9 +359,6 @@ RSpec/LeakyConstantDeclaration:
|
|||
- 'spec/lib/gitlab/database/obsolete_ignored_columns_spec.rb'
|
||||
- 'spec/lib/gitlab/database/with_lock_retries_spec.rb'
|
||||
- 'spec/lib/gitlab/git/diff_collection_spec.rb'
|
||||
- 'spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb'
|
||||
- 'spec/lib/gitlab/health_checks/master_check_spec.rb'
|
||||
- 'spec/lib/gitlab/import_export/attribute_configuration_spec.rb'
|
||||
- 'spec/lib/gitlab/import_export/import_test_coverage_spec.rb'
|
||||
- 'spec/lib/gitlab/import_export/project/relation_factory_spec.rb'
|
||||
- 'spec/lib/gitlab/jira_import/issues_importer_spec.rb'
|
||||
|
@ -372,17 +369,14 @@ RSpec/LeakyConstantDeclaration:
|
|||
- 'spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb'
|
||||
- 'spec/lib/gitlab/view/presenter/factory_spec.rb'
|
||||
- 'spec/lib/marginalia_spec.rb'
|
||||
- 'spec/lib/system_check/simple_executor_spec.rb'
|
||||
- 'spec/mailers/notify_spec.rb'
|
||||
- 'spec/migrations/20191125114345_add_admin_mode_protected_path_spec.rb'
|
||||
- 'spec/migrations/encrypt_plaintext_attributes_on_application_settings_spec.rb'
|
||||
- 'spec/models/concerns/batch_destroy_dependent_associations_spec.rb'
|
||||
- 'spec/models/concerns/bulk_insert_safe_spec.rb'
|
||||
- 'spec/models/concerns/bulk_insertable_associations_spec.rb'
|
||||
- 'spec/models/concerns/triggerable_hooks_spec.rb'
|
||||
- 'spec/models/repository_spec.rb'
|
||||
- 'spec/requests/api/graphql/tasks/task_completion_status_spec.rb'
|
||||
- 'spec/rubocop/cop/rspec/env_assignment_spec.rb'
|
||||
- 'spec/serializers/commit_entity_spec.rb'
|
||||
- 'spec/services/clusters/applications/check_installation_progress_service_spec.rb'
|
||||
- 'spec/services/clusters/applications/check_uninstall_progress_service_spec.rb'
|
||||
|
|
|
@ -1,22 +1,32 @@
|
|||
<script>
|
||||
import { mapState, mapActions } from 'vuex';
|
||||
import { GlTable, GlLink, GlLoadingIcon, GlBadge } from '@gitlab/ui';
|
||||
import { GlBadge, GlLink, GlLoadingIcon, GlPagination, GlTable } from '@gitlab/ui';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
import { CLUSTER_TYPES, STATUSES } from '../constants';
|
||||
import { __, sprintf } from '~/locale';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlTable,
|
||||
GlBadge,
|
||||
GlLink,
|
||||
GlLoadingIcon,
|
||||
GlBadge,
|
||||
GlPagination,
|
||||
GlTable,
|
||||
},
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
computed: {
|
||||
...mapState(['clusters', 'loading']),
|
||||
...mapState(['clusters', 'clustersPerPage', 'loading', 'page', 'totalCulsters']),
|
||||
currentPage: {
|
||||
get() {
|
||||
return this.page;
|
||||
},
|
||||
set(newVal) {
|
||||
this.setPage(newVal);
|
||||
this.fetchClusters();
|
||||
},
|
||||
},
|
||||
fields() {
|
||||
return [
|
||||
{
|
||||
|
@ -47,12 +57,15 @@ export default {
|
|||
},
|
||||
];
|
||||
},
|
||||
hasClusters() {
|
||||
return this.clustersPerPage > 0;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchClusters();
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['fetchClusters']),
|
||||
...mapActions(['fetchClusters', 'setPage']),
|
||||
statusClass(status) {
|
||||
const iconClass = STATUSES[status] || STATUSES.default;
|
||||
return iconClass.className;
|
||||
|
@ -67,33 +80,46 @@ export default {
|
|||
|
||||
<template>
|
||||
<gl-loading-icon v-if="loading" size="md" class="mt-3" />
|
||||
<gl-table v-else :items="clusters" :fields="fields" stacked="md" class="qa-clusters-table">
|
||||
<template #cell(name)="{ item }">
|
||||
<div class="d-flex flex-row-reverse flex-md-row js-status">
|
||||
<gl-link data-qa-selector="cluster" :data-qa-cluster-name="item.name" :href="item.path">
|
||||
{{ item.name }}
|
||||
</gl-link>
|
||||
|
||||
<gl-loading-icon
|
||||
v-if="item.status === 'deleting'"
|
||||
v-tooltip
|
||||
:title="statusTitle(item.status)"
|
||||
size="sm"
|
||||
class="mr-2 ml-md-2"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
v-tooltip
|
||||
class="cluster-status-indicator rounded-circle align-self-center gl-w-4 gl-h-4 mr-2 ml-md-2"
|
||||
:class="statusClass(item.status)"
|
||||
:title="statusTitle(item.status)"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
<template #cell(cluster_type)="{value}">
|
||||
<gl-badge variant="light">
|
||||
{{ value }}
|
||||
</gl-badge>
|
||||
</template>
|
||||
</gl-table>
|
||||
<section v-else>
|
||||
<gl-table :items="clusters" :fields="fields" stacked="md" class="qa-clusters-table">
|
||||
<template #cell(name)="{ item }">
|
||||
<div class="d-flex flex-row-reverse flex-md-row js-status">
|
||||
<gl-link data-qa-selector="cluster" :data-qa-cluster-name="item.name" :href="item.path">
|
||||
{{ item.name }}
|
||||
</gl-link>
|
||||
|
||||
<gl-loading-icon
|
||||
v-if="item.status === 'deleting'"
|
||||
v-tooltip
|
||||
:title="statusTitle(item.status)"
|
||||
size="sm"
|
||||
class="mr-2 ml-md-2"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
v-tooltip
|
||||
class="cluster-status-indicator rounded-circle align-self-center gl-w-4 gl-h-4 mr-2 ml-md-2"
|
||||
:class="statusClass(item.status)"
|
||||
:title="statusTitle(item.status)"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
<template #cell(cluster_type)="{value}">
|
||||
<gl-badge variant="light">
|
||||
{{ value }}
|
||||
</gl-badge>
|
||||
</template>
|
||||
</gl-table>
|
||||
|
||||
<gl-pagination
|
||||
v-if="hasClusters"
|
||||
v-model="currentPage"
|
||||
:per-page="clustersPerPage"
|
||||
:total-items="totalCulsters"
|
||||
:prev-text="__('Prev')"
|
||||
:next-text="__('Next')"
|
||||
align="center"
|
||||
/>
|
||||
</section>
|
||||
</template>
|
||||
|
|
|
@ -2,18 +2,22 @@ import Poll from '~/lib/utils/poll';
|
|||
import axios from '~/lib/utils/axios_utils';
|
||||
import flash from '~/flash';
|
||||
import { __ } from '~/locale';
|
||||
import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
|
||||
import * as types from './mutation_types';
|
||||
|
||||
export const fetchClusters = ({ state, commit }) => {
|
||||
const poll = new Poll({
|
||||
resource: {
|
||||
fetchClusters: endpoint => axios.get(endpoint),
|
||||
fetchClusters: paginatedEndPoint => axios.get(paginatedEndPoint),
|
||||
},
|
||||
data: state.endpoint,
|
||||
data: `${state.endpoint}?page=${state.page}`,
|
||||
method: 'fetchClusters',
|
||||
successCallback: ({ data }) => {
|
||||
successCallback: ({ data, headers }) => {
|
||||
if (data.clusters) {
|
||||
commit(types.SET_CLUSTERS_DATA, data);
|
||||
const normalizedHeaders = normalizeHeaders(headers);
|
||||
const paginationInformation = parseIntPagination(normalizedHeaders);
|
||||
|
||||
commit(types.SET_CLUSTERS_DATA, { data, paginationInformation });
|
||||
commit(types.SET_LOADING_STATE, false);
|
||||
poll.stop();
|
||||
}
|
||||
|
@ -24,5 +28,9 @@ export const fetchClusters = ({ state, commit }) => {
|
|||
poll.makeRequest();
|
||||
};
|
||||
|
||||
export const setPage = ({ commit }, page) => {
|
||||
commit(types.SET_PAGE, page);
|
||||
};
|
||||
|
||||
// prevent babel-plugin-rewire from generating an invalid default during karma tests
|
||||
export default () => {};
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export const SET_CLUSTERS_DATA = 'SET_CLUSTERS_DATA';
|
||||
export const SET_LOADING_STATE = 'SET_LOADING_STATE';
|
||||
export const SET_PAGE = 'SET_PAGE';
|
||||
|
|
|
@ -4,10 +4,15 @@ export default {
|
|||
[types.SET_LOADING_STATE](state, value) {
|
||||
state.loading = value;
|
||||
},
|
||||
[types.SET_CLUSTERS_DATA](state, data) {
|
||||
[types.SET_CLUSTERS_DATA](state, { data, paginationInformation }) {
|
||||
Object.assign(state, {
|
||||
clusters: data.clusters,
|
||||
clustersPerPage: paginationInformation.perPage,
|
||||
hasAncestorClusters: data.has_ancestor_clusters,
|
||||
totalCulsters: paginationInformation.total,
|
||||
});
|
||||
},
|
||||
[types.SET_PAGE](state, value) {
|
||||
state.page = Number(value) || 1;
|
||||
},
|
||||
};
|
||||
|
|
|
@ -3,4 +3,7 @@ export default (initialState = {}) => ({
|
|||
hasAncestorClusters: false,
|
||||
loading: true,
|
||||
clusters: [],
|
||||
clustersPerPage: 0,
|
||||
page: 1,
|
||||
totalCulsters: 0,
|
||||
});
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
|
||||
import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
|
||||
export default {
|
||||
|
@ -7,6 +8,10 @@ export default {
|
|||
timeAgoTooltip,
|
||||
GitlabTeamMemberBadge: () =>
|
||||
import('ee_component/vue_shared/components/user_avatar/badges/gitlab_team_member_badge.vue'),
|
||||
GlIcon,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
props: {
|
||||
author: {
|
||||
|
@ -44,6 +49,11 @@ export default {
|
|||
required: false,
|
||||
default: true,
|
||||
},
|
||||
isConfidential: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -174,6 +184,15 @@ export default {
|
|||
</a>
|
||||
<time-ago-tooltip v-else ref="noteTimestamp" :time="createdAt" tooltip-placement="bottom" />
|
||||
</template>
|
||||
<gl-icon
|
||||
v-if="isConfidential"
|
||||
v-gl-tooltip:tooltipcontainer.bottom
|
||||
data-testid="confidentialIndicator"
|
||||
name="eye-slash"
|
||||
:size="14"
|
||||
:title="s__('Notes|Private comments are accessible by internal staff only')"
|
||||
class="gl-ml-1 gl-text-gray-800 align-middle"
|
||||
/>
|
||||
<slot name="extra-controls"></slot>
|
||||
<i
|
||||
v-if="showSpinner"
|
||||
|
|
|
@ -255,7 +255,13 @@ export default {
|
|||
</div>
|
||||
<div class="timeline-content">
|
||||
<div class="note-header">
|
||||
<note-header v-once :author="author" :created-at="note.created_at" :note-id="note.id">
|
||||
<note-header
|
||||
v-once
|
||||
:author="author"
|
||||
:created-at="note.created_at"
|
||||
:note-id="note.id"
|
||||
:is-confidential="note.confidential"
|
||||
>
|
||||
<slot slot="note-header-info" name="note-header-info"></slot>
|
||||
<span v-if="commit" v-html="actionText"></span>
|
||||
<span v-else-if="note.created_at" class="d-none d-sm-inline">·</span>
|
||||
|
|
|
@ -68,7 +68,7 @@ export default {
|
|||
footer-primary-button-variant="warning"
|
||||
@submit="onSubmit"
|
||||
>
|
||||
<template slot="title">
|
||||
<template #title>
|
||||
{{ title }}
|
||||
</template>
|
||||
<div>
|
||||
|
|
|
@ -316,6 +316,7 @@ class ProjectPolicy < BasePolicy
|
|||
enable :update_deployment
|
||||
enable :create_release
|
||||
enable :update_release
|
||||
enable :daily_statistics
|
||||
enable :create_metrics_dashboard_annotation
|
||||
enable :delete_metrics_dashboard_annotation
|
||||
enable :update_metrics_dashboard_annotation
|
||||
|
@ -358,7 +359,6 @@ class ProjectPolicy < BasePolicy
|
|||
enable :create_environment_terminal
|
||||
enable :destroy_release
|
||||
enable :destroy_artifacts
|
||||
enable :daily_statistics
|
||||
enable :admin_operations
|
||||
enable :read_deploy_token
|
||||
enable :create_deploy_token
|
||||
|
|
|
@ -2,8 +2,32 @@
|
|||
|
||||
module Snippets
|
||||
class BaseService < ::BaseService
|
||||
include SpamCheckMethods
|
||||
|
||||
CreateRepositoryError = Class.new(StandardError)
|
||||
|
||||
attr_reader :uploaded_files
|
||||
|
||||
def initialize(project, user = nil, params = {})
|
||||
super
|
||||
|
||||
@uploaded_files = Array(@params.delete(:files).presence)
|
||||
|
||||
filter_spam_check_params
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def visibility_allowed?(snippet, visibility_level)
|
||||
Gitlab::VisibilityLevel.allowed_for?(current_user, visibility_level)
|
||||
end
|
||||
|
||||
def error_forbidden_visibility(snippet)
|
||||
deny_visibility_level(snippet)
|
||||
|
||||
snippet_error_response(snippet, 403)
|
||||
end
|
||||
|
||||
def snippet_error_response(snippet, http_status)
|
||||
ServiceResponse.error(
|
||||
message: snippet.errors.full_messages.to_sentence,
|
||||
|
|
|
@ -2,25 +2,11 @@
|
|||
|
||||
module Snippets
|
||||
class CreateService < Snippets::BaseService
|
||||
include SpamCheckMethods
|
||||
|
||||
CreateRepositoryError = Class.new(StandardError)
|
||||
|
||||
def execute
|
||||
filter_spam_check_params
|
||||
@snippet = build_from_params
|
||||
|
||||
@files = Array(params.delete(:files).presence)
|
||||
|
||||
@snippet = if project
|
||||
project.snippets.build(params)
|
||||
else
|
||||
PersonalSnippet.new(params)
|
||||
end
|
||||
|
||||
unless Gitlab::VisibilityLevel.allowed_for?(current_user, @snippet.visibility_level)
|
||||
deny_visibility_level(@snippet)
|
||||
|
||||
return snippet_error_response(@snippet, 403)
|
||||
unless visibility_allowed?(@snippet, @snippet.visibility_level)
|
||||
return error_forbidden_visibility(@snippet)
|
||||
end
|
||||
|
||||
@snippet.author = current_user
|
||||
|
@ -41,6 +27,14 @@ module Snippets
|
|||
|
||||
private
|
||||
|
||||
def build_from_params
|
||||
if project
|
||||
project.snippets.build(params)
|
||||
else
|
||||
PersonalSnippet.new(params)
|
||||
end
|
||||
end
|
||||
|
||||
def save_and_commit
|
||||
snippet_saved = @snippet.save
|
||||
|
||||
|
@ -91,7 +85,7 @@ module Snippets
|
|||
def move_temporary_files
|
||||
return unless @snippet.is_a?(PersonalSnippet)
|
||||
|
||||
@files.each do |file|
|
||||
uploaded_files.each do |file|
|
||||
FileMover.new(file, from_model: current_user, to_model: @snippet).execute
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,26 +2,15 @@
|
|||
|
||||
module Snippets
|
||||
class UpdateService < Snippets::BaseService
|
||||
include SpamCheckMethods
|
||||
|
||||
COMMITTABLE_ATTRIBUTES = %w(file_name content).freeze
|
||||
|
||||
UpdateError = Class.new(StandardError)
|
||||
CreateRepositoryError = Class.new(StandardError)
|
||||
|
||||
def execute(snippet)
|
||||
# check that user is allowed to set specified visibility_level
|
||||
new_visibility = visibility_level
|
||||
|
||||
if new_visibility && new_visibility.to_i != snippet.visibility_level
|
||||
unless Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
|
||||
deny_visibility_level(snippet, new_visibility)
|
||||
|
||||
return snippet_error_response(snippet, 403)
|
||||
end
|
||||
if visibility_changed?(snippet) && !visibility_allowed?(snippet, visibility_level)
|
||||
return error_forbidden_visibility(snippet)
|
||||
end
|
||||
|
||||
filter_spam_check_params
|
||||
snippet.assign_attributes(params)
|
||||
spam_check(snippet, current_user)
|
||||
|
||||
|
@ -36,6 +25,10 @@ module Snippets
|
|||
|
||||
private
|
||||
|
||||
def visibility_changed?(snippet)
|
||||
visibility_level && visibility_level.to_i != snippet.visibility_level
|
||||
end
|
||||
|
||||
def save_and_commit(snippet)
|
||||
return false unless snippet.save
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
%td
|
||||
- if token.expires?
|
||||
%span{ class: ('text-warning' if token.expires_soon?) }
|
||||
In #{distance_of_time_in_words_to_now(token.expires_at)}
|
||||
= _('In %{time_to_now}') % { time_to_now: distance_of_time_in_words_to_now(token.expires_at) }
|
||||
- else
|
||||
%span.token-never-expires-label= _('Never')
|
||||
%td= token.scopes.present? ? token.scopes.join(', ') : _('<no scopes selected>')
|
||||
|
|
|
@ -22,14 +22,14 @@
|
|||
- elsif event.target
|
||||
= link_to event.target.to_reference, [event.project.namespace.becomes(Namespace), event.project, event.target], class: 'has-tooltip', title: event.target_title
|
||||
|
||||
at
|
||||
= s_('UserProfile|at')
|
||||
%strong
|
||||
- if event.project
|
||||
= link_to_project(event.project)
|
||||
- else
|
||||
= event.resource_parent_name
|
||||
- else
|
||||
made a private contribution
|
||||
= s_('UserProfile|made a private contribution')
|
||||
- else
|
||||
%p
|
||||
= _('No contributions were found')
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add confidential status support for comment and replies
|
||||
merge_request: 31622
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Externalize i18n strings from ./app/views/shared/_personal_access_tokens_table.html.haml
|
||||
merge_request: 32116
|
||||
author: Gilang Gumilar
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Externalize i18n strings from ./app/views/users/calendar_activities.html.haml
|
||||
merge_request: 32094
|
||||
author: Gilang Gumilar
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: 'Cluster index refactor: Add missing pagination'
|
||||
merge_request: 32338
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix leaky constant issue in env assignment spec
|
||||
merge_request: 32040
|
||||
author: Rajendra Kadam
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix leaky constant issue in application settings encrypt spec
|
||||
merge_request: 32066
|
||||
author: Rajendra Kadam
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix leaky constant issue in simple executor spec
|
||||
merge_request: 32082
|
||||
author: Rajendra Kadam
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix leaky constant issue connection, master check and attr config spec
|
||||
merge_request: 32144
|
||||
author: Rajendra Kadam
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Correct the permission according to docs
|
||||
merge_request: 28657
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Validate package types in package metadatum models
|
||||
merge_request: 32091
|
||||
author: Sashi Kumar
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update deprecated slot syntax in ./app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue
|
||||
merge_request: 31980
|
||||
author: Gilang Gumilar
|
||||
type: other
|
|
@ -62,7 +62,7 @@ The following settings are:
|
|||
|
||||
| Setting | Description | Default |
|
||||
|---------|-------------|---------|
|
||||
| `enabled` | Enable/disable object storage | `true` |
|
||||
| `enabled` | Enable/disable object storage | `false` |
|
||||
| `remote_directory` | The bucket name where Terraform state files will be stored | |
|
||||
| `connection` | Various connection options described below | |
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
type: howto
|
||||
---
|
||||
|
||||
# Installing GitLab HA on Amazon Web Services (AWS)
|
||||
# Installing GitLab on Amazon Web Services (AWS)
|
||||
|
||||
This page offers a walkthrough of a common configuration
|
||||
for GitLab on AWS. You should customize it to accommodate your needs.
|
||||
|
@ -16,7 +16,7 @@ GitLab on AWS can leverage many of the services that are already
|
|||
configurable. These services offer a great deal of
|
||||
flexibility and can be adapted to the needs of most companies.
|
||||
|
||||
In this guide, we'll go through a basic HA setup where we'll start by
|
||||
In this guide, we'll go through a multi-node setup where we'll start by
|
||||
configuring our Virtual Private Cloud and subnets to later integrate
|
||||
services such as RDS for our database server and ElastiCache as a Redis
|
||||
cluster to finally manage them within an auto scaling group with custom
|
||||
|
@ -306,7 +306,7 @@ Now, it's time to create the database:
|
|||
1. Under **Settings**, set a DB instance identifier, a master username, and a master password. We'll use `gitlab-db-ha`, `gitlab`, and a very secure password respectively. Make a note of these as we'll need them later.
|
||||
1. For the DB instance size, select **Standard classes** and select an instance size that meets your requirements from the dropdown menu. We'll use a `db.m4.large` instance.
|
||||
1. Under **Storage**, configure the following:
|
||||
1. Select **Provisioned IOPS (SSD)** from the storage type dropdown menu. Provisioned IOPS (SSD) storage is best suited for HA (though you can choose General Purpose (SSD) to reduce the costs). Read more about it at [Storage for Amazon RDS](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html).
|
||||
1. Select **Provisioned IOPS (SSD)** from the storage type dropdown menu. Provisioned IOPS (SSD) storage is best suited for this use (though you can choose General Purpose (SSD) to reduce the costs). Read more about it at [Storage for Amazon RDS](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html).
|
||||
1. Allocate storage and set provisioned IOPS. We'll use the minimum values, `100` and `1000`, respectively.
|
||||
1. Enable storage autoscaling (optional) and set a maximum storage threshold.
|
||||
1. Under **Availability & durability**, select **Create a standby instance** to have a standby RDS instance provisioned in a different [Availability Zone](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.MultiAZ.html).
|
||||
|
|
|
@ -83,7 +83,7 @@ The following table depicts the various user permission levels in a project.
|
|||
| See a container registry | | ✓ | ✓ | ✓ | ✓ |
|
||||
| See environments | | ✓ | ✓ | ✓ | ✓ |
|
||||
| See a list of merge requests | | ✓ | ✓ | ✓ | ✓ |
|
||||
| View project statistics | | ✓ | ✓ | ✓ | ✓ |
|
||||
| View project statistics | | | ✓ | ✓ | ✓ |
|
||||
| View Error Tracking list | | ✓ | ✓ | ✓ | ✓ |
|
||||
| Create new merge request | | ✓ | ✓ | ✓ | ✓ |
|
||||
| View metrics dashboard annotations | | ✓ | ✓ | ✓ | ✓ |
|
||||
|
|
|
@ -11589,6 +11589,9 @@ msgstr ""
|
|||
msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition."
|
||||
msgstr ""
|
||||
|
||||
msgid "In %{time_to_now}"
|
||||
msgstr ""
|
||||
|
||||
msgid "In order to enable instance-level analytics, please ask an admin to enable %{usage_ping_link_start}usage ping%{usage_ping_link_end}."
|
||||
msgstr ""
|
||||
|
||||
|
@ -14519,6 +14522,9 @@ msgstr ""
|
|||
msgid "Notes|Collapse replies"
|
||||
msgstr ""
|
||||
|
||||
msgid "Notes|Private comments are accessible by internal staff only"
|
||||
msgstr ""
|
||||
|
||||
msgid "Notes|Show all activity"
|
||||
msgstr ""
|
||||
|
||||
|
@ -14965,6 +14971,9 @@ msgstr ""
|
|||
msgid "Package type must be NuGet"
|
||||
msgstr ""
|
||||
|
||||
msgid "Package type must be PyPi"
|
||||
msgstr ""
|
||||
|
||||
msgid "Package was removed"
|
||||
msgstr ""
|
||||
|
||||
|
@ -23775,6 +23784,12 @@ msgstr ""
|
|||
msgid "UserProfile|Your projects can be available publicly, internally, or privately, at your choice."
|
||||
msgstr ""
|
||||
|
||||
msgid "UserProfile|at"
|
||||
msgstr ""
|
||||
|
||||
msgid "UserProfile|made a private contribution"
|
||||
msgstr ""
|
||||
|
||||
msgid "Username (optional)"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -57,6 +57,10 @@ module QA
|
|||
click_element :download_export_link
|
||||
end
|
||||
|
||||
def has_download_export_link?
|
||||
has_element? :download_export_link
|
||||
end
|
||||
|
||||
def archive_project
|
||||
page.accept_alert("Are you sure that you want to archive this project?") do
|
||||
click_element :archive_project_link
|
||||
|
|
|
@ -4,7 +4,7 @@ import ClusterStore from '~/clusters_list/store';
|
|||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { apiData } from '../mock_data';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { GlTable, GlLoadingIcon } from '@gitlab/ui';
|
||||
import { GlLoadingIcon, GlTable, GlPagination } from '@gitlab/ui';
|
||||
|
||||
describe('Clusters', () => {
|
||||
let mock;
|
||||
|
@ -14,11 +14,12 @@ describe('Clusters', () => {
|
|||
const endpoint = 'some/endpoint';
|
||||
|
||||
const findLoader = () => wrapper.find(GlLoadingIcon);
|
||||
const findPaginatedButtons = () => wrapper.find(GlPagination);
|
||||
const findTable = () => wrapper.find(GlTable);
|
||||
const findStatuses = () => findTable().findAll('.js-status');
|
||||
|
||||
const mockPollingApi = (response, body, header) => {
|
||||
mock.onGet(endpoint).reply(response, body, header);
|
||||
mock.onGet(`${endpoint}?page=${header['x-page']}`).reply(response, body, header);
|
||||
};
|
||||
|
||||
const mountWrapper = () => {
|
||||
|
@ -29,7 +30,11 @@ describe('Clusters', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
mock = new MockAdapter(axios);
|
||||
mockPollingApi(200, apiData, {});
|
||||
mockPollingApi(200, apiData, {
|
||||
'x-total': apiData.clusters.length,
|
||||
'x-per-page': 20,
|
||||
'x-page': 1,
|
||||
});
|
||||
|
||||
return mountWrapper();
|
||||
});
|
||||
|
@ -93,4 +98,47 @@ describe('Clusters', () => {
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('pagination', () => {
|
||||
const perPage = apiData.clusters.length;
|
||||
const totalFirstPage = 100;
|
||||
const totalSecondPage = 500;
|
||||
|
||||
beforeEach(() => {
|
||||
mockPollingApi(200, apiData, {
|
||||
'x-total': totalFirstPage,
|
||||
'x-per-page': perPage,
|
||||
'x-page': 1,
|
||||
});
|
||||
return mountWrapper();
|
||||
});
|
||||
|
||||
it('should load to page 1 with header values', () => {
|
||||
const buttons = findPaginatedButtons();
|
||||
|
||||
expect(buttons.props('perPage')).toBe(perPage);
|
||||
expect(buttons.props('totalItems')).toBe(totalFirstPage);
|
||||
expect(buttons.props('value')).toBe(1);
|
||||
});
|
||||
|
||||
describe('when updating currentPage', () => {
|
||||
beforeEach(() => {
|
||||
mockPollingApi(200, apiData, {
|
||||
'x-total': totalSecondPage,
|
||||
'x-per-page': perPage,
|
||||
'x-page': 2,
|
||||
});
|
||||
wrapper.setData({ currentPage: 2 });
|
||||
return axios.waitForAll();
|
||||
});
|
||||
|
||||
it('should change pagination when currentPage changes', () => {
|
||||
const buttons = findPaginatedButtons();
|
||||
|
||||
expect(buttons.props('perPage')).toBe(perPage);
|
||||
expect(buttons.props('totalItems')).toBe(totalSecondPage);
|
||||
expect(buttons.props('value')).toBe(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -19,14 +19,29 @@ describe('Clusters store actions', () => {
|
|||
afterEach(() => mock.restore());
|
||||
|
||||
it('should commit SET_CLUSTERS_DATA with received response', done => {
|
||||
mock.onGet().reply(200, apiData);
|
||||
const headers = {
|
||||
'x-total': apiData.clusters.length,
|
||||
'x-per-page': 20,
|
||||
'x-page': 1,
|
||||
};
|
||||
|
||||
const paginationInformation = {
|
||||
nextPage: NaN,
|
||||
page: 1,
|
||||
perPage: 20,
|
||||
previousPage: NaN,
|
||||
total: apiData.clusters.length,
|
||||
totalPages: NaN,
|
||||
};
|
||||
|
||||
mock.onGet().reply(200, apiData, headers);
|
||||
|
||||
testAction(
|
||||
actions.fetchClusters,
|
||||
{ endpoint: apiData.endpoint },
|
||||
{},
|
||||
[
|
||||
{ type: types.SET_CLUSTERS_DATA, payload: apiData },
|
||||
{ type: types.SET_CLUSTERS_DATA, payload: { data: apiData, paginationInformation } },
|
||||
{ type: types.SET_LOADING_STATE, payload: false },
|
||||
],
|
||||
[],
|
||||
|
|
|
@ -39,7 +39,7 @@ describe('Dashboard', () => {
|
|||
const findEnvironmentsDropdown = () => wrapper.find({ ref: 'monitorEnvironmentsDropdown' });
|
||||
const findAllEnvironmentsDropdownItems = () => findEnvironmentsDropdown().findAll(GlDropdownItem);
|
||||
const setSearchTerm = searchTerm => {
|
||||
wrapper.vm.$store.commit(`monitoringDashboard/${types.SET_ENVIRONMENTS_FILTER}`, searchTerm);
|
||||
store.commit(`monitoringDashboard/${types.SET_ENVIRONMENTS_FILTER}`, searchTerm);
|
||||
};
|
||||
|
||||
const createShallowWrapper = (props = {}, options = {}) => {
|
||||
|
@ -135,7 +135,7 @@ describe('Dashboard', () => {
|
|||
it('hides the group panels when showPanels is false', () => {
|
||||
createMountedWrapper({ hasMetrics: true, showPanels: false });
|
||||
|
||||
setupStoreWithData(wrapper.vm.$store);
|
||||
setupStoreWithData(store);
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(wrapper.vm.showEmptyState).toEqual(false);
|
||||
|
@ -148,7 +148,7 @@ describe('Dashboard', () => {
|
|||
|
||||
createMountedWrapper({ hasMetrics: true });
|
||||
|
||||
wrapper.vm.$store.commit(
|
||||
store.commit(
|
||||
`monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
|
||||
environmentData,
|
||||
);
|
||||
|
@ -188,7 +188,7 @@ describe('Dashboard', () => {
|
|||
);
|
||||
|
||||
createMountedWrapper({ hasMetrics: true });
|
||||
setupStoreWithData(wrapper.vm.$store);
|
||||
setupStoreWithData(store);
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/setExpandedPanel', {
|
||||
|
@ -205,7 +205,7 @@ describe('Dashboard', () => {
|
|||
setSearch('');
|
||||
|
||||
createMountedWrapper({ hasMetrics: true });
|
||||
setupStoreWithData(wrapper.vm.$store);
|
||||
setupStoreWithData(store);
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(store.dispatch).not.toHaveBeenCalledWith(
|
||||
|
@ -228,7 +228,7 @@ describe('Dashboard', () => {
|
|||
);
|
||||
|
||||
createMountedWrapper({ hasMetrics: true });
|
||||
setupStoreWithData(wrapper.vm.$store);
|
||||
setupStoreWithData(store);
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(createFlash).toHaveBeenCalled();
|
||||
|
@ -328,7 +328,7 @@ describe('Dashboard', () => {
|
|||
beforeEach(() => {
|
||||
createMountedWrapper({ hasMetrics: true });
|
||||
|
||||
setupStoreWithData(wrapper.vm.$store);
|
||||
setupStoreWithData(store);
|
||||
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
|
@ -436,7 +436,7 @@ describe('Dashboard', () => {
|
|||
it('hides the environments dropdown list when there is no environments', () => {
|
||||
createMountedWrapper({ hasMetrics: true });
|
||||
|
||||
setupStoreWithDashboard(wrapper.vm.$store);
|
||||
setupStoreWithDashboard(store);
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(findAllEnvironmentsDropdownItems()).toHaveLength(0);
|
||||
|
@ -446,7 +446,7 @@ describe('Dashboard', () => {
|
|||
it('renders the datetimepicker dropdown', () => {
|
||||
createMountedWrapper({ hasMetrics: true });
|
||||
|
||||
setupStoreWithData(wrapper.vm.$store);
|
||||
setupStoreWithData(store);
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(wrapper.find(DateTimePicker).exists()).toBe(true);
|
||||
|
@ -456,7 +456,7 @@ describe('Dashboard', () => {
|
|||
it('renders the refresh dashboard button', () => {
|
||||
createMountedWrapper({ hasMetrics: true });
|
||||
|
||||
setupStoreWithData(wrapper.vm.$store);
|
||||
setupStoreWithData(store);
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
const refreshBtn = wrapper.findAll({ ref: 'refreshDashboardBtn' });
|
||||
|
@ -469,8 +469,8 @@ describe('Dashboard', () => {
|
|||
describe('variables section', () => {
|
||||
beforeEach(() => {
|
||||
createShallowWrapper({ hasMetrics: true });
|
||||
setupStoreWithData(wrapper.vm.$store);
|
||||
setupStoreWithVariable(wrapper.vm.$store);
|
||||
setupStoreWithData(store);
|
||||
setupStoreWithVariable(store);
|
||||
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
|
@ -486,7 +486,7 @@ describe('Dashboard', () => {
|
|||
describe('when the panel is not expanded', () => {
|
||||
beforeEach(() => {
|
||||
createShallowWrapper({ hasMetrics: true });
|
||||
setupStoreWithData(wrapper.vm.$store);
|
||||
setupStoreWithData(store);
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
|
@ -524,14 +524,14 @@ describe('Dashboard', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
createShallowWrapper({ hasMetrics: true }, { stubs: { DashboardPanel: MockPanel } });
|
||||
setupStoreWithData(wrapper.vm.$store);
|
||||
setupStoreWithData(store);
|
||||
|
||||
const { panelGroups } = wrapper.vm.$store.state.monitoringDashboard.dashboard;
|
||||
const { panelGroups } = store.state.monitoringDashboard.dashboard;
|
||||
|
||||
group = panelGroups[0].group;
|
||||
[panel] = panelGroups[0].panels;
|
||||
|
||||
wrapper.vm.$store.commit(`monitoringDashboard/${types.SET_EXPANDED_PANEL}`, {
|
||||
store.commit(`monitoringDashboard/${types.SET_EXPANDED_PANEL}`, {
|
||||
group,
|
||||
panel,
|
||||
});
|
||||
|
@ -593,10 +593,8 @@ describe('Dashboard', () => {
|
|||
beforeEach(() => {
|
||||
createShallowWrapper({ hasMetrics: true });
|
||||
|
||||
const { $store } = wrapper.vm;
|
||||
|
||||
setupStoreWithDashboard($store);
|
||||
setMetricResult({ $store, result: [], panel: 2 });
|
||||
setupStoreWithDashboard(store);
|
||||
setMetricResult({ store, result: [], panel: 2 });
|
||||
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
|
@ -622,7 +620,7 @@ describe('Dashboard', () => {
|
|||
beforeEach(() => {
|
||||
createMountedWrapper({ hasMetrics: true }, { attachToDocument: true });
|
||||
|
||||
setupStoreWithData(wrapper.vm.$store);
|
||||
setupStoreWithData(store);
|
||||
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
|
@ -673,7 +671,7 @@ describe('Dashboard', () => {
|
|||
});
|
||||
|
||||
it('shows loading element when environments fetch is still loading', () => {
|
||||
wrapper.vm.$store.commit(`monitoringDashboard/${types.REQUEST_ENVIRONMENTS_DATA}`);
|
||||
store.commit(`monitoringDashboard/${types.REQUEST_ENVIRONMENTS_DATA}`);
|
||||
|
||||
return wrapper.vm
|
||||
.$nextTick()
|
||||
|
@ -681,7 +679,7 @@ describe('Dashboard', () => {
|
|||
expect(wrapper.find({ ref: 'monitorEnvironmentsDropdownLoading' }).exists()).toBe(true);
|
||||
})
|
||||
.then(() => {
|
||||
wrapper.vm.$store.commit(
|
||||
store.commit(
|
||||
`monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
|
||||
environmentData,
|
||||
);
|
||||
|
@ -703,7 +701,7 @@ describe('Dashboard', () => {
|
|||
store.dispatch.mockRestore();
|
||||
|
||||
createShallowWrapper({ hasMetrics: true });
|
||||
setupStoreWithData(wrapper.vm.$store);
|
||||
setupStoreWithData(store);
|
||||
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
|
@ -791,7 +789,7 @@ describe('Dashboard', () => {
|
|||
createShallowWrapper({ hasMetrics: true, showHeader: false });
|
||||
|
||||
// all_dashboards is not defined in health dashboards
|
||||
wrapper.vm.$store.commit(`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, undefined);
|
||||
store.commit(`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, undefined);
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
|
@ -917,7 +915,7 @@ describe('Dashboard', () => {
|
|||
customMetricsPath: '/endpoint',
|
||||
customMetricsAvailable: true,
|
||||
});
|
||||
setupStoreWithData(wrapper.vm.$store);
|
||||
setupStoreWithData(store);
|
||||
|
||||
origPage = document.body.dataset.page;
|
||||
document.body.dataset.page = 'projects:environments:metrics';
|
||||
|
|
|
@ -47,7 +47,7 @@ describe('Metrics dashboard/variables section component', () => {
|
|||
|
||||
it('shows the variables section', () => {
|
||||
createShallowWrapper();
|
||||
wrapper.vm.$store.commit(`monitoringDashboard/${types.SET_VARIABLES}`, sampleVariables);
|
||||
store.commit(`monitoringDashboard/${types.SET_VARIABLES}`, sampleVariables);
|
||||
|
||||
return wrapper.vm.$nextTick(() => {
|
||||
const allInputs = findTextInput().length + findCustomInput().length;
|
||||
|
|
|
@ -2,48 +2,48 @@ import * as types from '~/monitoring/stores/mutation_types';
|
|||
import { metricsResult, environmentData, dashboardGitResponse } from './mock_data';
|
||||
import { metricsDashboardPayload } from './fixture_data';
|
||||
|
||||
export const setMetricResult = ({ $store, result, group = 0, panel = 0, metric = 0 }) => {
|
||||
const { dashboard } = $store.state.monitoringDashboard;
|
||||
export const setMetricResult = ({ store, result, group = 0, panel = 0, metric = 0 }) => {
|
||||
const { dashboard } = store.state.monitoringDashboard;
|
||||
const { metricId } = dashboard.panelGroups[group].panels[panel].metrics[metric];
|
||||
|
||||
$store.commit(`monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`, {
|
||||
store.commit(`monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`, {
|
||||
metricId,
|
||||
result,
|
||||
});
|
||||
};
|
||||
|
||||
const setEnvironmentData = $store => {
|
||||
$store.commit(`monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, environmentData);
|
||||
const setEnvironmentData = store => {
|
||||
store.commit(`monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, environmentData);
|
||||
};
|
||||
|
||||
export const setupAllDashboards = $store => {
|
||||
$store.commit(`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, dashboardGitResponse);
|
||||
export const setupAllDashboards = store => {
|
||||
store.commit(`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, dashboardGitResponse);
|
||||
};
|
||||
|
||||
export const setupStoreWithDashboard = $store => {
|
||||
$store.commit(
|
||||
export const setupStoreWithDashboard = store => {
|
||||
store.commit(
|
||||
`monitoringDashboard/${types.RECEIVE_METRICS_DASHBOARD_SUCCESS}`,
|
||||
metricsDashboardPayload,
|
||||
);
|
||||
$store.commit(
|
||||
store.commit(
|
||||
`monitoringDashboard/${types.RECEIVE_METRICS_DASHBOARD_SUCCESS}`,
|
||||
metricsDashboardPayload,
|
||||
);
|
||||
};
|
||||
|
||||
export const setupStoreWithVariable = $store => {
|
||||
$store.commit(`monitoringDashboard/${types.SET_VARIABLES}`, {
|
||||
export const setupStoreWithVariable = store => {
|
||||
store.commit(`monitoringDashboard/${types.SET_VARIABLES}`, {
|
||||
label1: 'pod',
|
||||
});
|
||||
};
|
||||
|
||||
export const setupStoreWithData = $store => {
|
||||
setupAllDashboards($store);
|
||||
setupStoreWithDashboard($store);
|
||||
export const setupStoreWithData = store => {
|
||||
setupAllDashboards(store);
|
||||
setupStoreWithDashboard(store);
|
||||
|
||||
setMetricResult({ $store, result: [], panel: 0 });
|
||||
setMetricResult({ $store, result: metricsResult, panel: 1 });
|
||||
setMetricResult({ $store, result: metricsResult, panel: 2 });
|
||||
setMetricResult({ store, result: [], panel: 0 });
|
||||
setMetricResult({ store, result: metricsResult, panel: 1 });
|
||||
setMetricResult({ store, result: metricsResult, panel: 2 });
|
||||
|
||||
setEnvironmentData($store);
|
||||
setEnvironmentData(store);
|
||||
};
|
||||
|
|
|
@ -18,6 +18,7 @@ describe('NoteHeader component', () => {
|
|||
const findActionText = () => wrapper.find({ ref: 'actionText' });
|
||||
const findTimestampLink = () => wrapper.find({ ref: 'noteTimestampLink' });
|
||||
const findTimestamp = () => wrapper.find({ ref: 'noteTimestamp' });
|
||||
const findConfidentialIndicator = () => wrapper.find('[data-testid="confidentialIndicator"]');
|
||||
const findSpinner = () => wrapper.find({ ref: 'spinner' });
|
||||
|
||||
const author = {
|
||||
|
@ -231,4 +232,15 @@ describe('NoteHeader component', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with confidentiality indicator', () => {
|
||||
it.each`
|
||||
status | condition
|
||||
${true} | ${'shows'}
|
||||
${false} | ${'hides'}
|
||||
`('$condition icon indicator when isConfidential is $status', ({ status }) => {
|
||||
createComponent({ isConfidential: status });
|
||||
expect(findConfidentialIndicator().exists()).toBe(status);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,6 +9,14 @@ describe Gitlab::Graphql::Pagination::Keyset::Connection do
|
|||
let(:schema) { GraphQL::Schema.define(query: query_type, mutation: nil)}
|
||||
let(:context) { GraphQL::Query::Context.new(query: OpenStruct.new(schema: schema), values: nil, object: nil) }
|
||||
|
||||
before do
|
||||
stub_const('NoPrimaryKey', Class.new(ActiveRecord::Base))
|
||||
NoPrimaryKey.class_eval do
|
||||
self.table_name = 'no_primary_key'
|
||||
self.primary_key = nil
|
||||
end
|
||||
end
|
||||
|
||||
subject(:connection) do
|
||||
described_class.new(nodes, { context: context, max_page_size: 3 }.merge(arguments))
|
||||
end
|
||||
|
@ -303,9 +311,4 @@ describe Gitlab::Graphql::Pagination::Keyset::Connection do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
class NoPrimaryKey < ActiveRecord::Base
|
||||
self.table_name = 'no_primary_key'
|
||||
self.primary_key = nil
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,10 +6,9 @@ require_relative './simple_check_shared'
|
|||
describe Gitlab::HealthChecks::MasterCheck do
|
||||
let(:result_class) { Gitlab::HealthChecks::Result }
|
||||
|
||||
SUCCESS_CODE = 100
|
||||
FAILURE_CODE = 101
|
||||
|
||||
before do
|
||||
stub_const('SUCCESS_CODE', 100)
|
||||
stub_const('FAILURE_CODE', 101)
|
||||
described_class.register_master
|
||||
end
|
||||
|
||||
|
|
|
@ -43,7 +43,4 @@ describe 'Import/Export attribute configuration' do
|
|||
IMPORT_EXPORT_CONFIG: #{Gitlab::ImportExport.config_file}
|
||||
MSG
|
||||
end
|
||||
|
||||
class Author < User
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,99 +4,109 @@ require 'spec_helper'
|
|||
require 'rake_helper'
|
||||
|
||||
describe SystemCheck::SimpleExecutor do
|
||||
class SimpleCheck < SystemCheck::BaseCheck
|
||||
set_name 'my simple check'
|
||||
before do
|
||||
stub_const('SimpleCheck', Class.new(SystemCheck::BaseCheck))
|
||||
stub_const('OtherCheck', Class.new(SystemCheck::BaseCheck))
|
||||
stub_const('SkipCheck', Class.new(SystemCheck::BaseCheck))
|
||||
stub_const('DynamicSkipCheck', Class.new(SystemCheck::BaseCheck))
|
||||
stub_const('MultiCheck', Class.new(SystemCheck::BaseCheck))
|
||||
stub_const('SkipMultiCheck', Class.new(SystemCheck::BaseCheck))
|
||||
stub_const('RepairCheck', Class.new(SystemCheck::BaseCheck))
|
||||
stub_const('BugousCheck', Class.new(SystemCheck::BaseCheck))
|
||||
|
||||
def check?
|
||||
true
|
||||
end
|
||||
end
|
||||
SimpleCheck.class_eval do
|
||||
set_name 'my simple check'
|
||||
|
||||
class OtherCheck < SystemCheck::BaseCheck
|
||||
set_name 'other check'
|
||||
|
||||
def check?
|
||||
false
|
||||
def check?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def show_error
|
||||
$stdout.puts 'this is an error text'
|
||||
end
|
||||
end
|
||||
OtherCheck.class_eval do
|
||||
set_name 'other check'
|
||||
|
||||
class SkipCheck < SystemCheck::BaseCheck
|
||||
set_name 'skip check'
|
||||
set_skip_reason 'this is a skip reason'
|
||||
def check?
|
||||
false
|
||||
end
|
||||
|
||||
def skip?
|
||||
true
|
||||
def show_error
|
||||
$stdout.puts 'this is an error text'
|
||||
end
|
||||
end
|
||||
|
||||
def check?
|
||||
raise 'should not execute this'
|
||||
end
|
||||
end
|
||||
SkipCheck.class_eval do
|
||||
set_name 'skip check'
|
||||
set_skip_reason 'this is a skip reason'
|
||||
|
||||
class DynamicSkipCheck < SystemCheck::BaseCheck
|
||||
set_name 'dynamic skip check'
|
||||
set_skip_reason 'this is a skip reason'
|
||||
def skip?
|
||||
true
|
||||
end
|
||||
|
||||
def skip?
|
||||
self.skip_reason = 'this is a dynamic skip reason'
|
||||
true
|
||||
def check?
|
||||
raise 'should not execute this'
|
||||
end
|
||||
end
|
||||
|
||||
def check?
|
||||
raise 'should not execute this'
|
||||
end
|
||||
end
|
||||
DynamicSkipCheck.class_eval do
|
||||
set_name 'dynamic skip check'
|
||||
set_skip_reason 'this is a skip reason'
|
||||
|
||||
class MultiCheck < SystemCheck::BaseCheck
|
||||
set_name 'multi check'
|
||||
def skip?
|
||||
self.skip_reason = 'this is a dynamic skip reason'
|
||||
true
|
||||
end
|
||||
|
||||
def multi_check
|
||||
$stdout.puts 'this is a multi output check'
|
||||
def check?
|
||||
raise 'should not execute this'
|
||||
end
|
||||
end
|
||||
|
||||
def check?
|
||||
raise 'should not execute this'
|
||||
end
|
||||
end
|
||||
MultiCheck.class_eval do
|
||||
set_name 'multi check'
|
||||
|
||||
class SkipMultiCheck < SystemCheck::BaseCheck
|
||||
set_name 'skip multi check'
|
||||
def multi_check
|
||||
$stdout.puts 'this is a multi output check'
|
||||
end
|
||||
|
||||
def skip?
|
||||
true
|
||||
def check?
|
||||
raise 'should not execute this'
|
||||
end
|
||||
end
|
||||
|
||||
def multi_check
|
||||
raise 'should not execute this'
|
||||
end
|
||||
end
|
||||
SkipMultiCheck.class_eval do
|
||||
set_name 'skip multi check'
|
||||
|
||||
class RepairCheck < SystemCheck::BaseCheck
|
||||
set_name 'repair check'
|
||||
def skip?
|
||||
true
|
||||
end
|
||||
|
||||
def check?
|
||||
false
|
||||
def multi_check
|
||||
raise 'should not execute this'
|
||||
end
|
||||
end
|
||||
|
||||
def repair!
|
||||
true
|
||||
RepairCheck.class_eval do
|
||||
set_name 'repair check'
|
||||
|
||||
def check?
|
||||
false
|
||||
end
|
||||
|
||||
def repair!
|
||||
true
|
||||
end
|
||||
|
||||
def show_error
|
||||
$stdout.puts 'this is an error message'
|
||||
end
|
||||
end
|
||||
|
||||
def show_error
|
||||
$stdout.puts 'this is an error message'
|
||||
end
|
||||
end
|
||||
BugousCheck.class_eval do
|
||||
set_name 'my bugous check'
|
||||
|
||||
class BugousCheck < SystemCheck::BaseCheck
|
||||
CustomError = Class.new(StandardError)
|
||||
set_name 'my bugous check'
|
||||
|
||||
def check?
|
||||
raise CustomError, 'omg'
|
||||
def check?
|
||||
raise StandardError, 'omg'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ describe EncryptPlaintextAttributesOnApplicationSettings do
|
|||
let(:application_settings) { table(:application_settings) }
|
||||
let(:plaintext) { 'secret-token' }
|
||||
|
||||
PLAINTEXT_ATTRIBUTES = %w[
|
||||
plaintext_attributes = %w[
|
||||
akismet_api_key
|
||||
elasticsearch_aws_secret_access_key
|
||||
recaptcha_private_key
|
||||
|
@ -21,7 +21,7 @@ describe EncryptPlaintextAttributesOnApplicationSettings do
|
|||
it 'encrypts token and saves it' do
|
||||
application_setting = application_settings.create
|
||||
application_setting.update_columns(
|
||||
PLAINTEXT_ATTRIBUTES.each_with_object({}) do |plaintext_attribute, attributes|
|
||||
plaintext_attributes.each_with_object({}) do |plaintext_attribute, attributes|
|
||||
attributes[plaintext_attribute] = plaintext
|
||||
end
|
||||
)
|
||||
|
@ -29,7 +29,7 @@ describe EncryptPlaintextAttributesOnApplicationSettings do
|
|||
migration.up
|
||||
|
||||
application_setting.reload
|
||||
PLAINTEXT_ATTRIBUTES.each do |plaintext_attribute|
|
||||
plaintext_attributes.each do |plaintext_attribute|
|
||||
expect(application_setting[plaintext_attribute]).not_to be_nil
|
||||
expect(application_setting["encrypted_#{plaintext_attribute}"]).not_to be_nil
|
||||
expect(application_setting["encrypted_#{plaintext_attribute}_iv"]).not_to be_nil
|
||||
|
@ -40,7 +40,7 @@ describe EncryptPlaintextAttributesOnApplicationSettings do
|
|||
describe '#down' do
|
||||
it 'decrypts encrypted token and saves it' do
|
||||
application_setting = application_settings.create(
|
||||
PLAINTEXT_ATTRIBUTES.each_with_object({}) do |plaintext_attribute, attributes|
|
||||
plaintext_attributes.each_with_object({}) do |plaintext_attribute, attributes|
|
||||
attributes[plaintext_attribute] = plaintext
|
||||
end
|
||||
)
|
||||
|
@ -48,7 +48,7 @@ describe EncryptPlaintextAttributesOnApplicationSettings do
|
|||
migration.down
|
||||
|
||||
application_setting.reload
|
||||
PLAINTEXT_ATTRIBUTES.each do |plaintext_attribute|
|
||||
plaintext_attributes.each do |plaintext_attribute|
|
||||
expect(application_setting[plaintext_attribute]).to eq(plaintext)
|
||||
expect(application_setting["encrypted_#{plaintext_attribute}"]).to be_nil
|
||||
expect(application_setting["encrypted_#{plaintext_attribute}_iv"]).to be_nil
|
||||
|
|
|
@ -42,7 +42,7 @@ describe ProjectPolicy do
|
|||
admin_tag admin_milestone admin_merge_request update_merge_request create_commit_status
|
||||
update_commit_status create_build update_build create_pipeline
|
||||
update_pipeline create_merge_request_from create_wiki push_code
|
||||
resolve_note create_container_image update_container_image destroy_container_image
|
||||
resolve_note create_container_image update_container_image destroy_container_image daily_statistics
|
||||
create_environment update_environment create_deployment update_deployment create_release update_release
|
||||
create_metrics_dashboard_annotation delete_metrics_dashboard_annotation update_metrics_dashboard_annotation
|
||||
]
|
||||
|
@ -54,7 +54,7 @@ describe ProjectPolicy do
|
|||
admin_snippet admin_project_member admin_note admin_wiki admin_project
|
||||
admin_commit_status admin_build admin_container_image
|
||||
admin_pipeline admin_environment admin_deployment destroy_release add_cluster
|
||||
daily_statistics read_deploy_token create_deploy_token destroy_deploy_token
|
||||
read_deploy_token create_deploy_token destroy_deploy_token
|
||||
admin_terraform_state
|
||||
]
|
||||
end
|
||||
|
|
|
@ -3,23 +3,23 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe API::ProjectStatistics do
|
||||
let(:maintainer) { create(:user) }
|
||||
let(:public_project) { create(:project, :public) }
|
||||
let_it_be(:developer) { create(:user) }
|
||||
let_it_be(:public_project) { create(:project, :public) }
|
||||
|
||||
before do
|
||||
public_project.add_maintainer(maintainer)
|
||||
public_project.add_developer(developer)
|
||||
end
|
||||
|
||||
describe 'GET /projects/:id/statistics' do
|
||||
let!(:fetch_statistics1) { create(:project_daily_statistic, project: public_project, fetch_count: 30, date: 29.days.ago) }
|
||||
let!(:fetch_statistics2) { create(:project_daily_statistic, project: public_project, fetch_count: 4, date: 3.days.ago) }
|
||||
let!(:fetch_statistics3) { create(:project_daily_statistic, project: public_project, fetch_count: 3, date: 2.days.ago) }
|
||||
let!(:fetch_statistics4) { create(:project_daily_statistic, project: public_project, fetch_count: 2, date: 1.day.ago) }
|
||||
let!(:fetch_statistics5) { create(:project_daily_statistic, project: public_project, fetch_count: 1, date: Date.today) }
|
||||
let!(:fetch_statistics_other_project) { create(:project_daily_statistic, project: create(:project), fetch_count: 29, date: 29.days.ago) }
|
||||
let_it_be(:fetch_statistics1) { create(:project_daily_statistic, project: public_project, fetch_count: 30, date: 29.days.ago) }
|
||||
let_it_be(:fetch_statistics2) { create(:project_daily_statistic, project: public_project, fetch_count: 4, date: 3.days.ago) }
|
||||
let_it_be(:fetch_statistics3) { create(:project_daily_statistic, project: public_project, fetch_count: 3, date: 2.days.ago) }
|
||||
let_it_be(:fetch_statistics4) { create(:project_daily_statistic, project: public_project, fetch_count: 2, date: 1.day.ago) }
|
||||
let_it_be(:fetch_statistics5) { create(:project_daily_statistic, project: public_project, fetch_count: 1, date: Date.today) }
|
||||
let_it_be(:fetch_statistics_other_project) { create(:project_daily_statistic, project: create(:project), fetch_count: 29, date: 29.days.ago) }
|
||||
|
||||
it 'returns the fetch statistics of the last 30 days' do
|
||||
get api("/projects/#{public_project.id}/statistics", maintainer)
|
||||
get api("/projects/#{public_project.id}/statistics", developer)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
fetches = json_response['fetches']
|
||||
|
@ -32,7 +32,7 @@ describe API::ProjectStatistics do
|
|||
it 'excludes the fetch statistics older than 30 days' do
|
||||
create(:project_daily_statistic, fetch_count: 31, project: public_project, date: 30.days.ago)
|
||||
|
||||
get api("/projects/#{public_project.id}/statistics", maintainer)
|
||||
get api("/projects/#{public_project.id}/statistics", developer)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
fetches = json_response['fetches']
|
||||
|
@ -41,11 +41,11 @@ describe API::ProjectStatistics do
|
|||
expect(fetches['days'].last).to eq({ 'count' => fetch_statistics1.fetch_count, 'date' => fetch_statistics1.date.to_s })
|
||||
end
|
||||
|
||||
it 'responds with 403 when the user is not a maintainer of the repository' do
|
||||
developer = create(:user)
|
||||
public_project.add_developer(developer)
|
||||
it 'responds with 403 when the user is not a developer of the repository' do
|
||||
guest = create(:user)
|
||||
public_project.add_guest(guest)
|
||||
|
||||
get api("/projects/#{public_project.id}/statistics", developer)
|
||||
get api("/projects/#{public_project.id}/statistics", guest)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
expect(json_response['message']).to eq('403 Forbidden')
|
||||
|
|
|
@ -10,8 +10,8 @@ require_relative '../../../../rubocop/cop/rspec/env_assignment'
|
|||
describe RuboCop::Cop::RSpec::EnvAssignment do
|
||||
include CopHelper
|
||||
|
||||
OFFENSE_CALL_SINGLE_QUOTES_KEY = %(ENV['FOO'] = 'bar').freeze
|
||||
OFFENSE_CALL_DOUBLE_QUOTES_KEY = %(ENV["FOO"] = 'bar').freeze
|
||||
offense_call_single_quotes_key = %(ENV['FOO'] = 'bar').freeze
|
||||
offense_call_double_quotes_key = %(ENV["FOO"] = 'bar').freeze
|
||||
|
||||
let(:source_file) { 'spec/foo_spec.rb' }
|
||||
|
||||
|
@ -36,12 +36,12 @@ describe RuboCop::Cop::RSpec::EnvAssignment do
|
|||
end
|
||||
|
||||
context 'with a key using single quotes' do
|
||||
it_behaves_like 'an offensive ENV#[]= call', OFFENSE_CALL_SINGLE_QUOTES_KEY
|
||||
it_behaves_like 'an autocorrected ENV#[]= call', OFFENSE_CALL_SINGLE_QUOTES_KEY, %(stub_env('FOO', 'bar'))
|
||||
it_behaves_like 'an offensive ENV#[]= call', offense_call_single_quotes_key
|
||||
it_behaves_like 'an autocorrected ENV#[]= call', offense_call_single_quotes_key, %(stub_env('FOO', 'bar'))
|
||||
end
|
||||
|
||||
context 'with a key using double quotes' do
|
||||
it_behaves_like 'an offensive ENV#[]= call', OFFENSE_CALL_DOUBLE_QUOTES_KEY
|
||||
it_behaves_like 'an autocorrected ENV#[]= call', OFFENSE_CALL_DOUBLE_QUOTES_KEY, %(stub_env("FOO", 'bar'))
|
||||
it_behaves_like 'an offensive ENV#[]= call', offense_call_double_quotes_key
|
||||
it_behaves_like 'an autocorrected ENV#[]= call', offense_call_double_quotes_key, %(stub_env("FOO", 'bar'))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -74,47 +74,6 @@ describe Snippets::CreateService do
|
|||
end
|
||||
end
|
||||
|
||||
shared_examples 'spam check is performed' do
|
||||
shared_examples 'marked as spam' do
|
||||
it 'marks a snippet as spam' do
|
||||
expect(snippet).to be_spam
|
||||
end
|
||||
|
||||
it 'invalidates the snippet' do
|
||||
expect(snippet).to be_invalid
|
||||
end
|
||||
|
||||
it 'creates a new spam_log' do
|
||||
expect { snippet }
|
||||
.to have_spam_log(title: snippet.title, noteable_type: snippet.class.name)
|
||||
end
|
||||
|
||||
it 'assigns a spam_log to an issue' do
|
||||
expect(snippet.spam_log).to eq(SpamLog.last)
|
||||
end
|
||||
end
|
||||
|
||||
let(:extra_opts) do
|
||||
{ visibility_level: Gitlab::VisibilityLevel::PUBLIC, request: double(:request, env: {}) }
|
||||
end
|
||||
|
||||
before do
|
||||
expect_next_instance_of(Spam::AkismetService) do |akismet_service|
|
||||
expect(akismet_service).to receive_messages(spam?: true)
|
||||
end
|
||||
end
|
||||
|
||||
[true, false, nil].each do |allow_possible_spam|
|
||||
context "when recaptcha_disabled flag is #{allow_possible_spam.inspect}" do
|
||||
before do
|
||||
stub_feature_flags(allow_possible_spam: allow_possible_spam) unless allow_possible_spam.nil?
|
||||
end
|
||||
|
||||
it_behaves_like 'marked as spam'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'snippet create data is tracked' do
|
||||
let(:counter) { Gitlab::UsageDataCounters::SnippetCounter }
|
||||
|
||||
|
@ -280,7 +239,7 @@ describe Snippets::CreateService do
|
|||
|
||||
it_behaves_like 'a service that creates a snippet'
|
||||
it_behaves_like 'public visibility level restrictions apply'
|
||||
it_behaves_like 'spam check is performed'
|
||||
it_behaves_like 'snippets spam check is performed'
|
||||
it_behaves_like 'snippet create data is tracked'
|
||||
it_behaves_like 'an error service response when save fails'
|
||||
it_behaves_like 'creates repository and files'
|
||||
|
@ -306,7 +265,7 @@ describe Snippets::CreateService do
|
|||
|
||||
it_behaves_like 'a service that creates a snippet'
|
||||
it_behaves_like 'public visibility level restrictions apply'
|
||||
it_behaves_like 'spam check is performed'
|
||||
it_behaves_like 'snippets spam check is performed'
|
||||
it_behaves_like 'snippet create data is tracked'
|
||||
it_behaves_like 'an error service response when save fails'
|
||||
it_behaves_like 'creates repository and files'
|
||||
|
|
|
@ -7,7 +7,7 @@ describe Snippets::UpdateService do
|
|||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:admin) { create :user, admin: true }
|
||||
let(:visibility_level) { Gitlab::VisibilityLevel::PRIVATE }
|
||||
let(:options) do
|
||||
let(:base_opts) do
|
||||
{
|
||||
title: 'Test snippet',
|
||||
file_name: 'snippet.rb',
|
||||
|
@ -15,6 +15,8 @@ describe Snippets::UpdateService do
|
|||
visibility_level: visibility_level
|
||||
}
|
||||
end
|
||||
let(:extra_opts) { {} }
|
||||
let(:options) { base_opts.merge(extra_opts) }
|
||||
let(:updater) { user }
|
||||
let(:service) { Snippets::UpdateService.new(project, updater, options) }
|
||||
|
||||
|
@ -85,7 +87,7 @@ describe Snippets::UpdateService do
|
|||
end
|
||||
|
||||
context 'when update fails' do
|
||||
let(:options) { { title: '' } }
|
||||
let(:extra_opts) { { title: '' } }
|
||||
|
||||
it 'does not increment count' do
|
||||
expect { subject }.not_to change { counter.read(:update) }
|
||||
|
@ -273,7 +275,7 @@ describe Snippets::UpdateService do
|
|||
|
||||
shared_examples 'committable attributes' do
|
||||
context 'when file_name is updated' do
|
||||
let(:options) { { file_name: 'snippet.rb' } }
|
||||
let(:extra_opts) { { file_name: 'snippet.rb' } }
|
||||
|
||||
it 'commits to repository' do
|
||||
expect(service).to receive(:create_commit)
|
||||
|
@ -282,7 +284,7 @@ describe Snippets::UpdateService do
|
|||
end
|
||||
|
||||
context 'when content is updated' do
|
||||
let(:options) { { content: 'puts "hello world"' } }
|
||||
let(:extra_opts) { { content: 'puts "hello world"' } }
|
||||
|
||||
it 'commits to repository' do
|
||||
expect(service).to receive(:create_commit)
|
||||
|
@ -314,6 +316,11 @@ describe Snippets::UpdateService do
|
|||
it_behaves_like 'updates repository content'
|
||||
it_behaves_like 'commit operation fails'
|
||||
it_behaves_like 'committable attributes'
|
||||
it_behaves_like 'snippets spam check is performed' do
|
||||
before do
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'when snippet does not have a repository' do
|
||||
let!(:snippet) { create(:project_snippet, author: user, project: project) }
|
||||
|
@ -333,6 +340,11 @@ describe Snippets::UpdateService do
|
|||
it_behaves_like 'updates repository content'
|
||||
it_behaves_like 'commit operation fails'
|
||||
it_behaves_like 'committable attributes'
|
||||
it_behaves_like 'snippets spam check is performed' do
|
||||
before do
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'when snippet does not have a repository' do
|
||||
let!(:snippet) { create(:personal_snippet, author: user, project: project) }
|
||||
|
|
|
@ -39,7 +39,7 @@ RSpec.shared_context 'ProjectPolicy context' do
|
|||
update_pipeline create_merge_request_from create_wiki push_code
|
||||
resolve_note create_container_image update_container_image
|
||||
create_environment create_deployment update_deployment create_release update_release
|
||||
update_environment
|
||||
update_environment daily_statistics
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -49,7 +49,6 @@ RSpec.shared_context 'ProjectPolicy context' do
|
|||
admin_snippet admin_project_member admin_note admin_wiki admin_project
|
||||
admin_commit_status admin_build admin_container_image
|
||||
admin_pipeline admin_environment admin_deployment destroy_release add_cluster
|
||||
daily_statistics
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'snippets spam check is performed' do
|
||||
shared_examples 'marked as spam' do
|
||||
it 'marks a snippet as spam' do
|
||||
expect(snippet).to be_spam
|
||||
end
|
||||
|
||||
it 'invalidates the snippet' do
|
||||
expect(snippet).to be_invalid
|
||||
end
|
||||
|
||||
it 'creates a new spam_log' do
|
||||
expect { snippet }
|
||||
.to have_spam_log(title: snippet.title, noteable_type: snippet.class.name)
|
||||
end
|
||||
|
||||
it 'assigns a spam_log to an issue' do
|
||||
expect(snippet.spam_log).to eq(SpamLog.last)
|
||||
end
|
||||
end
|
||||
|
||||
let(:extra_opts) do
|
||||
{ visibility_level: Gitlab::VisibilityLevel::PUBLIC, request: double(:request, env: {}) }
|
||||
end
|
||||
|
||||
before do
|
||||
expect_next_instance_of(Spam::AkismetService) do |akismet_service|
|
||||
expect(akismet_service).to receive_messages(spam?: true)
|
||||
end
|
||||
end
|
||||
|
||||
[true, false, nil].each do |allow_possible_spam|
|
||||
context "when allow_possible_spam flag is #{allow_possible_spam.inspect}" do
|
||||
before do
|
||||
stub_feature_flags(allow_possible_spam: allow_possible_spam) unless allow_possible_spam.nil?
|
||||
end
|
||||
|
||||
it_behaves_like 'marked as spam'
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue