Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
f7e83434e3
commit
9796aa22cd
|
@ -27,6 +27,7 @@
|
|||
/doc/api/graphql/ @msedlakjakubowski @kpaizee
|
||||
/doc/api/graphql/reference/ @kpaizee
|
||||
/doc/api/group_activity_analytics.md @fneill
|
||||
/doc/api/vulnerabilities.md @fneill
|
||||
/doc/ci/ @marcel.amirault @sselhorn
|
||||
/doc/ci/environments/ @rdickenson
|
||||
/doc/ci/services/ @sselhorn
|
||||
|
@ -49,6 +50,11 @@
|
|||
/doc/user/application_security/ @rdickenson
|
||||
/doc/user/application_security/container_scanning/ @ngaskill
|
||||
/doc/user/application_security/cluster_image_scanning/ @ngaskill
|
||||
/doc/user/application_security/cve_id_request.md @fneill
|
||||
/doc/user/application_security/security_dashboard @fneill
|
||||
/doc/user/application_security/vulnerabilities @fneill
|
||||
/doc/user/application_security/vulnerability_management @fneill
|
||||
/doc/user/application_security/vulnerability_report @fneill
|
||||
/doc/user/clusters/ @marcia
|
||||
/doc/user/compliance/ @rdickenson @eread
|
||||
/doc/user/group/ @msedlakjakubowski
|
||||
|
|
|
@ -2599,7 +2599,6 @@ Style/OpenStructUse:
|
|||
- 'spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb'
|
||||
- 'spec/lib/gitlab/legacy_github_import/project_creator_spec.rb'
|
||||
- 'spec/lib/gitlab/quick_actions/command_definition_spec.rb'
|
||||
- 'spec/lib/gitlab/quick_actions/dsl_spec.rb'
|
||||
- 'spec/lib/gitlab/relative_positioning/range_spec.rb'
|
||||
- 'spec/models/design_management/design_action_spec.rb'
|
||||
- 'spec/models/design_management/design_at_version_spec.rb'
|
||||
|
|
|
@ -1 +1 @@
|
|||
d69b465fdff3c04f8e3f395aed34aaa59f23fe76
|
||||
b6dda5d1f7a7e05c34ed0f72f161a46aee536d75
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// This file only applies to use of experiments through https://gitlab.com/gitlab-org/gitlab-experiment
|
||||
import { get } from 'lodash';
|
||||
import { get, mapValues, pick } from 'lodash';
|
||||
import { DEFAULT_VARIANT, CANDIDATE_VARIANT, TRACKING_CONTEXT_SCHEMA } from './constants';
|
||||
|
||||
function getExperimentsData() {
|
||||
|
@ -8,19 +8,18 @@ function getExperimentsData() {
|
|||
// Pull from preferred window.gl.experiments
|
||||
const experimentsFromGl = get(window, ['gl', 'experiments'], {});
|
||||
|
||||
return { ...experimentsFromGon, ...experimentsFromGl };
|
||||
}
|
||||
|
||||
function convertExperimentDataToExperimentContext(experimentData) {
|
||||
// Bandaid to allow-list only the properties which the current gitlab_experiment context schema suppports.
|
||||
// Bandaid to allow-list only the properties which the current gitlab_experiment
|
||||
// context schema suppports, since we most often use this data to create that
|
||||
// Snowplow context.
|
||||
// See TRACKING_CONTEXT_SCHEMA for current version (1-0-0)
|
||||
// https://gitlab.com/gitlab-org/iglu/-/blob/master/public/schemas/com.gitlab/gitlab_experiment/jsonschema/1-0-0
|
||||
const { experiment: experimentName, key, variant, migration_keys } = experimentData;
|
||||
return mapValues({ ...experimentsFromGon, ...experimentsFromGl }, (xp) => {
|
||||
return pick(xp, ['experiment', 'key', 'variant', 'migration_keys']);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
schema: TRACKING_CONTEXT_SCHEMA,
|
||||
data: { experiment: experimentName, key, variant, migration_keys },
|
||||
};
|
||||
function createGitlabExperimentContext(experimentData) {
|
||||
return { schema: TRACKING_CONTEXT_SCHEMA, data: experimentData };
|
||||
}
|
||||
|
||||
export function getExperimentData(experimentName) {
|
||||
|
@ -28,7 +27,7 @@ export function getExperimentData(experimentName) {
|
|||
}
|
||||
|
||||
export function getAllExperimentContexts() {
|
||||
return Object.values(getExperimentsData()).map(convertExperimentDataToExperimentContext);
|
||||
return Object.values(getExperimentsData()).map(createGitlabExperimentContext);
|
||||
}
|
||||
|
||||
export function isExperimentVariant(experimentName, variantName) {
|
||||
|
|
|
@ -39,7 +39,7 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['getNotesDataByProp', 'timelineEnabled']),
|
||||
...mapGetters(['getNotesDataByProp', 'timelineEnabled', 'isLoading']),
|
||||
currentFilter() {
|
||||
if (!this.currentValue) return this.filters[0];
|
||||
return this.filters.find((filter) => filter.value === this.currentValue);
|
||||
|
@ -119,6 +119,7 @@ export default {
|
|||
class="gl-mr-3 full-width-mobile discussion-filter-container js-discussion-filter-container"
|
||||
data-qa-selector="discussion_filter_dropdown"
|
||||
:text="currentFilter.title"
|
||||
:disabled="isLoading"
|
||||
>
|
||||
<div v-for="filter in filters" :key="filter.value" class="dropdown-item-wrapper">
|
||||
<gl-dropdown-item
|
||||
|
|
|
@ -601,7 +601,8 @@ export const setLoadingState = ({ commit }, data) => {
|
|||
commit(types.SET_NOTES_LOADING_STATE, data);
|
||||
};
|
||||
|
||||
export const filterDiscussion = ({ dispatch }, { path, filter, persistFilter }) => {
|
||||
export const filterDiscussion = ({ commit, dispatch }, { path, filter, persistFilter }) => {
|
||||
commit(types.CLEAR_DISCUSSIONS);
|
||||
dispatch('setLoadingState', true);
|
||||
dispatch('fetchDiscussions', { path, filter, persistFilter })
|
||||
.then(() => {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
export const ADD_NEW_NOTE = 'ADD_NEW_NOTE';
|
||||
export const ADD_NEW_REPLY_TO_DISCUSSION = 'ADD_NEW_REPLY_TO_DISCUSSION';
|
||||
export const ADD_OR_UPDATE_DISCUSSIONS = 'ADD_OR_UPDATE_DISCUSSIONS';
|
||||
export const CLEAR_DISCUSSIONS = 'CLEAR_DISCUSSIONS';
|
||||
export const DELETE_NOTE = 'DELETE_NOTE';
|
||||
export const REMOVE_PLACEHOLDER_NOTES = 'REMOVE_PLACEHOLDER_NOTES';
|
||||
export const SET_NOTES_DATA = 'SET_NOTES_DATA';
|
||||
|
|
|
@ -129,6 +129,10 @@ export default {
|
|||
Object.assign(state, { userData: data });
|
||||
},
|
||||
|
||||
[types.CLEAR_DISCUSSIONS](state) {
|
||||
state.discussions = [];
|
||||
},
|
||||
|
||||
[types.ADD_OR_UPDATE_DISCUSSIONS](state, discussionsData) {
|
||||
discussionsData.forEach((d) => {
|
||||
const discussion = { ...d };
|
||||
|
|
|
@ -15,6 +15,7 @@ import { setUrlFragment } from '~/lib/utils/url_utility';
|
|||
import { __, s__, sprintf } from '~/locale';
|
||||
import Tracking from '~/tracking';
|
||||
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
|
||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import {
|
||||
CONTENT_EDITOR_LOADED_ACTION,
|
||||
SAVED_USING_CONTENT_EDITOR_ACTION,
|
||||
|
@ -104,6 +105,8 @@ export default {
|
|||
newPage: s__('WikiPage|Create page'),
|
||||
},
|
||||
cancel: s__('WikiPage|Cancel'),
|
||||
editSourceButtonText: s__('WikiPage|Edit source'),
|
||||
editRichTextButtonText: s__('WikiPage|Edit rich text'),
|
||||
},
|
||||
contentEditorFeedbackIssue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/332629',
|
||||
components: {
|
||||
|
@ -123,7 +126,7 @@ export default {
|
|||
directives: {
|
||||
GlModalDirective,
|
||||
},
|
||||
mixins: [trackingMixin],
|
||||
mixins: [trackingMixin, glFeatureFlagMixin()],
|
||||
inject: ['formatOptions', 'pageInfo'],
|
||||
data() {
|
||||
return {
|
||||
|
@ -131,7 +134,6 @@ export default {
|
|||
format: this.pageInfo.format || 'markdown',
|
||||
content: this.pageInfo.content || '',
|
||||
isContentEditorAlertDismissed: false,
|
||||
isContentEditorLoading: true,
|
||||
useContentEditor: false,
|
||||
commitMessage: '',
|
||||
isDirty: false,
|
||||
|
@ -164,6 +166,11 @@ export default {
|
|||
linkExample() {
|
||||
return MARKDOWN_LINK_TEXT[this.format];
|
||||
},
|
||||
toggleEditingModeButtonText() {
|
||||
return this.isContentEditorActive
|
||||
? this.$options.i18n.editSourceButtonText
|
||||
: this.$options.i18n.editRichTextButtonText;
|
||||
},
|
||||
submitButtonText() {
|
||||
return this.pageInfo.persisted
|
||||
? this.$options.i18n.submitButton.existingPage
|
||||
|
@ -188,7 +195,23 @@ export default {
|
|||
return this.format === 'markdown';
|
||||
},
|
||||
showContentEditorAlert() {
|
||||
return this.isMarkdownFormat && !this.useContentEditor && !this.isContentEditorAlertDismissed;
|
||||
return (
|
||||
!this.glFeatures.wikiSwitchBetweenContentEditorRawMarkdown &&
|
||||
this.isMarkdownFormat &&
|
||||
!this.useContentEditor &&
|
||||
!this.isContentEditorAlertDismissed
|
||||
);
|
||||
},
|
||||
showSwitchEditingModeButton() {
|
||||
return this.glFeatures.wikiSwitchBetweenContentEditorRawMarkdown && this.isMarkdownFormat;
|
||||
},
|
||||
displayWikiSpecificMarkdownHelp() {
|
||||
return !this.isContentEditorActive;
|
||||
},
|
||||
displaySwitchBackToClassicEditorMessage() {
|
||||
return (
|
||||
!this.glFeatures.wikiSwitchBetweenContentEditorRawMarkdown && this.isContentEditorActive
|
||||
);
|
||||
},
|
||||
disableSubmitButton() {
|
||||
return this.noContent || !this.title || this.contentEditorRenderFailed;
|
||||
|
@ -212,6 +235,14 @@ export default {
|
|||
.then(({ data }) => data.body);
|
||||
},
|
||||
|
||||
toggleEditingMode() {
|
||||
if (this.useContentEditor) {
|
||||
this.content = this.contentEditor.getSerializedContent();
|
||||
}
|
||||
|
||||
this.useContentEditor = !this.useContentEditor;
|
||||
},
|
||||
|
||||
async handleFormSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
|
@ -405,6 +436,17 @@ export default {
|
|||
}}</label>
|
||||
</div>
|
||||
<div class="col-sm-10">
|
||||
<div
|
||||
v-if="showSwitchEditingModeButton"
|
||||
class="gl-display-flex gl-justify-content-end gl-mb-3"
|
||||
>
|
||||
<gl-button
|
||||
data-testid="toggle-editing-mode-button"
|
||||
variant="link"
|
||||
@click="toggleEditingMode"
|
||||
>{{ toggleEditingModeButtonText }}</gl-button
|
||||
>
|
||||
</div>
|
||||
<gl-alert
|
||||
v-if="showContentEditorAlert"
|
||||
class="gl-mb-6"
|
||||
|
@ -498,7 +540,7 @@ export default {
|
|||
<div class="error-alert"></div>
|
||||
|
||||
<div class="form-text gl-text-gray-600">
|
||||
<gl-sprintf v-if="!isContentEditorActive" :message="$options.i18n.linksHelpText">
|
||||
<gl-sprintf v-if="displayWikiSpecificMarkdownHelp" :message="$options.i18n.linksHelpText">
|
||||
<template #linkExample
|
||||
><code>{{ linkExample }}</code></template
|
||||
>
|
||||
|
@ -513,7 +555,7 @@ export default {
|
|||
></template
|
||||
>
|
||||
</gl-sprintf>
|
||||
<span v-else>
|
||||
<span v-if="displaySwitchBackToClassicEditorMessage">
|
||||
{{ $options.i18n.contentEditor.switchToOldEditor.helpText }}
|
||||
<gl-button variant="link" @click="confirmSwitchToOldEditor">{{
|
||||
$options.i18n.contentEditor.switchToOldEditor.label
|
||||
|
|
|
@ -21,6 +21,10 @@ module WikiActions
|
|||
before_action :load_sidebar, except: [:pages]
|
||||
before_action :set_content_class
|
||||
|
||||
before_action do
|
||||
push_frontend_feature_flag(:wiki_switch_between_content_editor_raw_markdown, @group, default_enabled: :yaml)
|
||||
end
|
||||
|
||||
before_action only: [:show, :edit, :update] do
|
||||
@valid_encoding = valid_encoding?
|
||||
end
|
||||
|
|
|
@ -82,17 +82,17 @@ module Projects
|
|||
|
||||
def create_service
|
||||
Projects::Prometheus::Alerts::CreateService
|
||||
.new(project, current_user, alerts_params)
|
||||
.new(project: project, current_user: current_user, params: alerts_params)
|
||||
end
|
||||
|
||||
def update_service
|
||||
Projects::Prometheus::Alerts::UpdateService
|
||||
.new(project, current_user, alerts_params)
|
||||
.new(project: project, current_user: current_user, params: alerts_params)
|
||||
end
|
||||
|
||||
def destroy_service
|
||||
Projects::Prometheus::Alerts::DestroyService
|
||||
.new(project, current_user, nil)
|
||||
.new(project: project, current_user: current_user, params: nil)
|
||||
end
|
||||
|
||||
def schedule_prometheus_update!
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
module Projects
|
||||
module Prometheus
|
||||
module Alerts
|
||||
class CreateService < BaseService
|
||||
class CreateService < BaseProjectService
|
||||
include AlertParams
|
||||
|
||||
def execute
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
module Projects
|
||||
module Prometheus
|
||||
module Alerts
|
||||
class DestroyService < BaseService
|
||||
class DestroyService < BaseProjectService
|
||||
def execute(alert)
|
||||
alert.destroy
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
module Projects
|
||||
module Prometheus
|
||||
module Alerts
|
||||
class UpdateService < BaseService
|
||||
class UpdateService < BaseProjectService
|
||||
include AlertParams
|
||||
|
||||
def execute(alert)
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
"type": "array"
|
||||
},
|
||||
"context_line": {
|
||||
"type": "string"
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"post_context": {
|
||||
"type": "array"
|
||||
|
|
|
@ -41,7 +41,7 @@ class IssuableExportCsvWorker # rubocop:disable Scalability/IdempotentWorker
|
|||
|
||||
def parse_params(params, project_id)
|
||||
params
|
||||
.symbolize_keys
|
||||
.with_indifferent_access
|
||||
.except(:sort)
|
||||
.merge(project_id: project_id)
|
||||
end
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: wiki_switch_between_content_editor_raw_markdown
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74457
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/345398
|
||||
milestone: '14.6'
|
||||
type: development
|
||||
group: group::editor
|
||||
default_enabled: false
|
|
@ -28,11 +28,15 @@ Gitlab::Application.configure do |config|
|
|||
config.middleware.insert_after(Labkit::Middleware::Rack, Gitlab::Metrics::RequestsRackMiddleware)
|
||||
end
|
||||
|
||||
Sidekiq.configure_server do |config|
|
||||
config.on(:startup) do
|
||||
# Do not clean the metrics directory here - the supervisor script should
|
||||
# have already taken care of that
|
||||
Gitlab::Metrics::Exporter::SidekiqExporter.instance.start
|
||||
if Gitlab::Runtime.sidekiq? && (!ENV['SIDEKIQ_WORKER_ID'] || ENV['SIDEKIQ_WORKER_ID'] == '0')
|
||||
# The single worker outside of a sidekiq-cluster, or the first worker (sidekiq_0)
|
||||
# in a cluster of processes, is responsible for serving health checks.
|
||||
Sidekiq.configure_server do |config|
|
||||
config.on(:startup) do
|
||||
# Do not clean the metrics directory here - the supervisor script should
|
||||
# have already taken care of that
|
||||
Gitlab::Metrics::Exporter::SidekiqExporter.instance.start
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddCreatedAtToNamespaceMonthlyUsages < Gitlab::Database::Migration[1.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
with_lock_retries do
|
||||
add_column :ci_namespace_monthly_usages, :created_at, :datetime_with_timezone
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_column :ci_namespace_monthly_usages, :created_at
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddCreatedAtToProjectMonthlyUsage < Gitlab::Database::Migration[1.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
with_lock_retries do
|
||||
add_column :ci_project_monthly_usages, :created_at, :datetime_with_timezone
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_column :ci_project_monthly_usages, :created_at
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
2b6bc8067402744b79eee06022cf3c91ba7ffd519df83aae4067600a6bbf43ad
|
|
@ -0,0 +1 @@
|
|||
ad65e6deb885397dc91f33dc117a50e9a1b6d60f4caed8c5b77d474ec0340995
|
|
@ -11748,6 +11748,7 @@ CREATE TABLE ci_namespace_monthly_usages (
|
|||
amount_used numeric(18,2) DEFAULT 0.0 NOT NULL,
|
||||
notification_level smallint DEFAULT 100 NOT NULL,
|
||||
shared_runners_duration integer DEFAULT 0 NOT NULL,
|
||||
created_at timestamp with time zone,
|
||||
CONSTRAINT ci_namespace_monthly_usages_year_month_constraint CHECK ((date = date_trunc('month'::text, (date)::timestamp with time zone)))
|
||||
);
|
||||
|
||||
|
@ -11993,6 +11994,7 @@ CREATE TABLE ci_project_monthly_usages (
|
|||
date date NOT NULL,
|
||||
amount_used numeric(18,2) DEFAULT 0.0 NOT NULL,
|
||||
shared_runners_duration integer DEFAULT 0 NOT NULL,
|
||||
created_at timestamp with time zone,
|
||||
CONSTRAINT ci_project_monthly_usages_year_month_constraint CHECK ((date = date_trunc('month'::text, (date)::timestamp with time zone)))
|
||||
);
|
||||
|
||||
|
|
|
@ -7,11 +7,14 @@ type: howto
|
|||
|
||||
# Geo proxying for secondary sites **(PREMIUM SELF)**
|
||||
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5914) in GitLab 14.4 [with a flag](../../feature_flags.md) named `geo_secondary_proxy`. Disabled by default.
|
||||
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5914) in GitLab 14.4 [with a flag](../../feature_flags.md) named `geo_secondary_proxy`. Disabled by default.
|
||||
> - [Enabled by default for unified URLs](https://gitlab.com/gitlab-org/gitlab/-/issues/325732) in GitLab 14.5.
|
||||
> - [Disabled by default for different URLs](https://gitlab.com/gitlab-org/gitlab/-/issues/325732) in GitLab 14.5 [with a flag](../../feature_flags.md) named `geo_secondary_proxy_separate_urls`.
|
||||
|
||||
FLAG:
|
||||
On self-managed GitLab, by default this feature is not available. See below to [Set up a unified URL for Geo sites](#set-up-a-unified-url-for-geo-sites).
|
||||
The feature is not ready for production use.
|
||||
On self-managed GitLab, this feature is only available by default for Geo sites using a unified URL. See below to
|
||||
[set up a unified URL for Geo sites](#set-up-a-unified-url-for-geo-sites).
|
||||
The feature is not ready for production use with separate URLs.
|
||||
|
||||
Use Geo proxying to:
|
||||
|
||||
|
@ -65,8 +68,10 @@ a single URL used by all Geo sites, including the primary.
|
|||
In the Geo administration page of the **primary** site, edit each Geo secondary that
|
||||
is using the secondary proxying and set the `URL` field to the single URL.
|
||||
Make sure the primary site is also using this URL.
|
||||
|
||||
## Disable Geo proxying
|
||||
|
||||
### Enable secondary proxying
|
||||
You can disable the secondary proxying on each Geo site, separately, by following these steps:
|
||||
|
||||
1. SSH into each application node (serving user traffic directly) on your secondary Geo site
|
||||
and add the following environment variable:
|
||||
|
@ -77,7 +82,7 @@ a single URL used by all Geo sites, including the primary.
|
|||
|
||||
```ruby
|
||||
gitlab_workhorse['env'] = {
|
||||
"GEO_SECONDARY_PROXY" => "1"
|
||||
"GEO_SECONDARY_PROXY" => "0"
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -87,18 +92,34 @@ a single URL used by all Geo sites, including the primary.
|
|||
gitlab-ctl reconfigure
|
||||
```
|
||||
|
||||
1. SSH into one node running Rails on your primary Geo site and enable the Geo secondary proxy feature flag:
|
||||
|
||||
```shell
|
||||
sudo gitlab-rails runner "Feature.enable(:geo_secondary_proxy)"
|
||||
```
|
||||
|
||||
## Enable Geo proxying with Separate URLs
|
||||
|
||||
The ability to use proxying with separate URLs is still in development. You can follow the
|
||||
["Geo secondary proxying with separate URLs" epic](https://gitlab.com/groups/gitlab-org/-/epics/6865)
|
||||
for progress.
|
||||
|
||||
To try out this feature, enable the `geo_secondary_proxy_separate_urls` feature flag.
|
||||
SSH into one node running Rails on your primary Geo site and run:
|
||||
|
||||
```shell
|
||||
sudo gitlab-rails runner "Feature.enable(:geo_secondary_proxy_separate_urls)"
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
The asynchronous Geo replication can cause unexpected issues when secondary proxying is used, for accelerated
|
||||
data types that may be replicated to the Geo secondaries with a delay.
|
||||
|
||||
For example, we found a potential issue where
|
||||
[Replication lag introduces read-your-own-write inconsistencies](https://gitlab.com/gitlab-org/gitlab/-/issues/345267).
|
||||
If the replication lag is high enough, this can result in Git reads receiving stale data when hitting a secondary.
|
||||
|
||||
Non-Rails requests are not proxied, so other services may need to use a separate, non-unified URL to ensure requests
|
||||
are always sent to the primary. These services include:
|
||||
|
||||
- GitLab Container Registry - [can be configured to use a separate domain](../../packages/container_registry.md#configure-container-registry-under-its-own-domain).
|
||||
- GitLab Pages - should always use a separate domain, as part of [the prerequisites for running GitLab Pages](../../pages/index.md#prerequisites).
|
||||
|
||||
## Features accelerated by secondary Geo sites
|
||||
|
||||
Most HTTP traffic sent to a secondary Geo site can be proxied to the primary Geo site. With this architecture,
|
||||
|
|
|
@ -26,6 +26,7 @@ If you installed GitLab using the Omnibus packages (highly recommended):
|
|||
1. [Configure GitLab](../replication/configuration.md) to set the **primary** and **secondary** site(s).
|
||||
1. Optional: [Configure a secondary LDAP server](../../auth/ldap/index.md) for the **secondary** site(s). See [notes on LDAP](../index.md#ldap).
|
||||
1. Follow the [Using a Geo Site](../replication/usage.md) guide.
|
||||
1. [Configure Geo secondary proxying](../secondary_proxy/index.md) to use a single, unified URL for all Geo sites. This step is recommended to accelerate most read requests while transparently proxying writes to the primary Geo site.
|
||||
|
||||
## Post-installation documentation
|
||||
|
||||
|
|
|
@ -287,12 +287,13 @@ Example response:
|
|||
Returns metadata for a specific package version:
|
||||
|
||||
```plaintext
|
||||
GET <route-prefix>/metadata/:package_name/index
|
||||
GET <route-prefix>/metadata/:package_name/:package_version
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| -------------- | ------ | -------- | ----------- |
|
||||
| `package_name` | string | yes | The name of the package. |
|
||||
| Attribute | Type | Required | Description |
|
||||
| ----------------- | ------ | -------- | ----------- |
|
||||
| `package_name` | string | yes | The name of the package. |
|
||||
| `package_version` | string | yes | The version of the package. |
|
||||
|
||||
```shell
|
||||
curl --user <username>:<personal_access_token> "https://gitlab.example.com/api/v4/projects/1/packages/nuget/metadata/MyNuGetPkg/1.3.0.17"
|
||||
|
|
|
@ -6,7 +6,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
# Auto DevOps **(FREE)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/38366) in GitLab 11.0.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/38366) in GitLab 11.0.
|
||||
> - Support for the GitLab Kubernetes Agent was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/299350) in GitLab 14.5.
|
||||
|
||||
GitLab Auto DevOps is a collection of pre-configured features and integrations
|
||||
that work together to support your software delivery process.
|
||||
|
|
|
@ -7,8 +7,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
# Vulnerability Report **(ULTIMATE)**
|
||||
|
||||
The Vulnerability Report provides information about vulnerabilities from scans of the branch most
|
||||
recently merged into the default branch. It is available for groups, projects, and the Security Center.
|
||||
The Vulnerability Report provides information about vulnerabilities from scans of the default branch. It is available for groups, projects, and the Security Center.
|
||||
|
||||
At all levels, the Vulnerability Report contains:
|
||||
|
||||
|
|
|
@ -58,7 +58,6 @@ the Kubernetes Agent model on the [Agent's blueprint documentation](../../../arc
|
|||
- [Pod logs](../../project/clusters/kubernetes_pod_logs.md)
|
||||
- [Clusters health](manage/clusters_health.md)
|
||||
- [Crossplane integration](../../clusters/crossplane.md)
|
||||
- [Auto Deploy](../../../topics/autodevops/stages.md#auto-deploy)
|
||||
- [Web terminals](../../../administration/integration/terminal.md)
|
||||
|
||||
### Cluster levels
|
||||
|
|
|
@ -24,7 +24,7 @@ kics-iac-sast:
|
|||
image:
|
||||
name: "$SAST_ANALYZER_IMAGE"
|
||||
variables:
|
||||
SAST_ANALYZER_IMAGE_TAG: 0
|
||||
SAST_ANALYZER_IMAGE_TAG: 1
|
||||
SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/kics:$SAST_ANALYZER_IMAGE_TAG"
|
||||
rules:
|
||||
- if: $SAST_DISABLED
|
||||
|
|
|
@ -13,8 +13,10 @@ before_script:
|
|||
- apt-get update -yqq
|
||||
- apt-get install apt-transport-https -yqq
|
||||
# Add keyserver for SBT
|
||||
- echo "deb http://dl.bintray.com/sbt/debian /" | tee -a /etc/apt/sources.list.d/sbt.list
|
||||
- apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 2EE0EA64E40A89B84B2DF73499E82A75642AC823
|
||||
- echo "deb https://repo.scala-sbt.org/scalasbt/debian /" | tee -a /etc/apt/sources.list.d/sbt.list
|
||||
- mkdir -p /root/.gnupg
|
||||
- gpg --recv-keys --no-default-keyring --keyring gnupg-ring:/etc/apt/trusted.gpg.d/scalasbt-release.gpg --keyserver hkp://keyserver.ubuntu.com:80 2EE0EA64E40A89B84B2DF73499E82A75642AC823
|
||||
- chmod 644 /etc/apt/trusted.gpg.d/scalasbt-release.gpg
|
||||
# Install SBT
|
||||
- apt-get update -yqq
|
||||
- apt-get install sbt -yqq
|
||||
|
|
|
@ -15,29 +15,6 @@ module Gitlab
|
|||
File::NULL
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Sidekiq Exporter does not work properly in sidekiq-cluster
|
||||
# mode. It tries to start the service on the same port for
|
||||
# each of the cluster workers, this results in failure
|
||||
# due to duplicate binding.
|
||||
#
|
||||
# For now we ignore this error, as metrics are still "kind of"
|
||||
# valid as they are rendered from shared directory.
|
||||
#
|
||||
# Issue: https://gitlab.com/gitlab-org/gitlab/issues/5714
|
||||
def start_working
|
||||
super
|
||||
rescue Errno::EADDRINUSE => e
|
||||
Sidekiq.logger.error(
|
||||
class: self.class.to_s,
|
||||
message: 'Cannot start sidekiq_exporter',
|
||||
'exception.message' => e.message
|
||||
)
|
||||
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -39140,6 +39140,12 @@ msgstr ""
|
|||
msgid "WikiPage|Create page"
|
||||
msgstr ""
|
||||
|
||||
msgid "WikiPage|Edit rich text"
|
||||
msgstr ""
|
||||
|
||||
msgid "WikiPage|Edit source"
|
||||
msgstr ""
|
||||
|
||||
msgid "WikiPage|Format"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -27,10 +27,10 @@ RSpec.describe 'User uploads new design', :js do
|
|||
expect(page).to have_content('dk.png')
|
||||
end
|
||||
|
||||
upload_design(gif_fixture, count: 2)
|
||||
upload_design([gif_fixture, logo_svg_fixture, big_image_fixture], count: 4)
|
||||
|
||||
expect(page).to have_selector('.js-design-list-item', count: 2)
|
||||
expect(page.all('.js-design-list-item').map(&:text)).to eq(['dk.png', 'banana_sample.gif'])
|
||||
expect(page).to have_selector('.js-design-list-item', count: 4)
|
||||
expect(page.all('.js-design-list-item').map(&:text)).to eq(['dk.png', 'banana_sample.gif', 'logo_sample.svg', 'big-image.png'])
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -50,8 +50,16 @@ RSpec.describe 'User uploads new design', :js do
|
|||
Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
|
||||
end
|
||||
|
||||
def upload_design(fixture, count:)
|
||||
attach_file(:upload_file, fixture, match: :first, make_visible: true)
|
||||
def logo_svg_fixture
|
||||
Rails.root.join('spec', 'fixtures', 'logo_sample.svg')
|
||||
end
|
||||
|
||||
def big_image_fixture
|
||||
Rails.root.join('spec', 'fixtures', 'big-image.png')
|
||||
end
|
||||
|
||||
def upload_design(fixtures, count:)
|
||||
attach_file(:upload_file, fixtures, multiple: true, match: :first, make_visible: true)
|
||||
|
||||
wait_for('designs uploaded') do
|
||||
issue.reload.designs.count == count
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{"breadcrumbs":{"values":[]},"contexts":{"runtime":{"build":"3.9.5 (default, May 12 2021, 15:36:59) \n[GCC 8.3.0]","name":"CPython","version":"3.9.5"}},"environment":"production","event_id":"","exception":{"values":[{"mechanism":null,"module":null,"stacktrace":{"frames":[{"abs_path":"/srv/autodevops/<stdin>","context_line":null,"filename":"<stdin>","function":"<module>","in_app":true,"lineno":2,"module":"__main__","post_context":[],"pre_context":[],"vars":{"__annotations__":{},"__builtins__":"<module 'builtins' (built-in)>","__doc__":"None","__loader__":"<class '_frozen_importlib.BuiltinImporter'>","__name__":"'__main__'","__package__":"None","__spec__":"None","capture_exception":"<function capture_exception at 0x7f5dbb3eb940>","e":"ZeroDivisionError('division by zero')","init":"<function _init at 0x7f5dbb3ea1f0>"}}]},"type":"ZeroDivisionError","value":"division by zero"}]},"extra":{"sys.argv":[""]},"level":"error","modules":{"appdirs":"1.4.4","apscheduler":"3.7.0","asgiref":"3.3.4","beautifulsoup4":"4.9.3","certifi":"2020.12.5","chardet":"4.0.0","django":"3.2.3","django-anymail":"1.3","django-environ":"0.4.5","django-livereload-server":"0.3.2","django-widget-tweaks":"1.4.8","fcache":"0.4.7","idna":"2.10","mmh3":"3.0.0","pip":"21.1.2","psycopg2-binary":"2.8.6","pytz":"2021.1","requests":"2.25.1","sentry-sdk":"1.5.0","setuptools":"57.0.0","six":"1.16.0","soupsieve":"2.2.1","sqlparse":"0.4.1","tornado":"6.1","tzlocal":"2.1","unleashclient":"4.2.0","urllib3":"1.26.4","uwsgi":"2.0.19.1","wheel":"0.36.2"},"platform":"python","sdk":{"integrations":["argv","atexit","dedupe","django","excepthook","logging","modules","stdlib","threading","tornado"],"name":"sentry.python","packages":[{"name":"pypi:sentry-sdk","version":"1.5.0"}],"version":"1.5.0"},"server_name":"","timestamp":"2021-11-17T14:46:20.898210Z"}
|
|
@ -51,6 +51,29 @@ describe('experiment Utilities', () => {
|
|||
expect(experimentUtils.getExperimentData(...input)).toEqual(output);
|
||||
});
|
||||
});
|
||||
|
||||
it('only collects the data properties which are supported by the schema', () => {
|
||||
origGl = window.gl;
|
||||
window.gl.experiments = {
|
||||
my_experiment: {
|
||||
experiment: 'my_experiment',
|
||||
variant: 'control',
|
||||
key: 'randomization-unit-key',
|
||||
migration_keys: 'migration_keys object',
|
||||
excluded: false,
|
||||
other: 'foobar',
|
||||
},
|
||||
};
|
||||
|
||||
expect(experimentUtils.getExperimentData('my_experiment')).toEqual({
|
||||
experiment: 'my_experiment',
|
||||
variant: 'control',
|
||||
key: 'randomization-unit-key',
|
||||
migration_keys: 'migration_keys object',
|
||||
});
|
||||
|
||||
window.gl = origGl;
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAllExperimentContexts', () => {
|
||||
|
@ -72,19 +95,6 @@ describe('experiment Utilities', () => {
|
|||
it('returns an empty array if there are no experiments', () => {
|
||||
expect(experimentUtils.getAllExperimentContexts()).toEqual([]);
|
||||
});
|
||||
|
||||
it('only collects the data properties which are supported by the schema', () => {
|
||||
origGl = window.gl;
|
||||
window.gl.experiments = {
|
||||
my_experiment: { experiment: 'my_experiment', variant: 'control', excluded: false },
|
||||
};
|
||||
|
||||
expect(experimentUtils.getAllExperimentContexts()).toEqual([
|
||||
{ schema, data: { experiment: 'my_experiment', variant: 'control' } },
|
||||
]);
|
||||
|
||||
window.gl = origGl;
|
||||
});
|
||||
});
|
||||
|
||||
describe('isExperimentVariant', () => {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { GlDropdown } from '@gitlab/ui';
|
||||
import { createLocalVue, mount } from '@vue/test-utils';
|
||||
import AxiosMockAdapter from 'axios-mock-adapter';
|
||||
import Vuex from 'vuex';
|
||||
|
@ -88,6 +89,12 @@ describe('DiscussionFilter component', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('disables the dropdown when discussions are loading', () => {
|
||||
store.state.isLoading = true;
|
||||
|
||||
expect(wrapper.findComponent(GlDropdown).props('disabled')).toBe(true);
|
||||
});
|
||||
|
||||
it('updates to the selected item', () => {
|
||||
const filterItem = findFilter(DISCUSSION_FILTER_TYPES.ALL);
|
||||
|
||||
|
|
|
@ -1183,8 +1183,14 @@ describe('Actions Notes Store', () => {
|
|||
dispatch.mockReturnValue(new Promise(() => {}));
|
||||
});
|
||||
|
||||
it('clears existing discussions', () => {
|
||||
actions.filterDiscussion({ commit, dispatch }, { path, filter, persistFilter: false });
|
||||
|
||||
expect(commit.mock.calls).toEqual([[mutationTypes.CLEAR_DISCUSSIONS]]);
|
||||
});
|
||||
|
||||
it('fetches discussions with filter and persistFilter false', () => {
|
||||
actions.filterDiscussion({ dispatch }, { path, filter, persistFilter: false });
|
||||
actions.filterDiscussion({ commit, dispatch }, { path, filter, persistFilter: false });
|
||||
|
||||
expect(dispatch.mock.calls).toEqual([
|
||||
['setLoadingState', true],
|
||||
|
@ -1193,7 +1199,7 @@ describe('Actions Notes Store', () => {
|
|||
});
|
||||
|
||||
it('fetches discussions with filter and persistFilter true', () => {
|
||||
actions.filterDiscussion({ dispatch }, { path, filter, persistFilter: true });
|
||||
actions.filterDiscussion({ commit, dispatch }, { path, filter, persistFilter: true });
|
||||
|
||||
expect(dispatch.mock.calls).toEqual([
|
||||
['setLoadingState', true],
|
||||
|
|
|
@ -159,6 +159,18 @@ describe('Notes Store mutations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('CLEAR_DISCUSSIONS', () => {
|
||||
it('should set discussions to an empty array', () => {
|
||||
const state = {
|
||||
discussions: [discussionMock],
|
||||
};
|
||||
|
||||
mutations.CLEAR_DISCUSSIONS(state);
|
||||
|
||||
expect(state.discussions).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ADD_OR_UPDATE_DISCUSSIONS', () => {
|
||||
it('should set the initial notes received', () => {
|
||||
const state = {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { nextTick } from 'vue';
|
||||
import { GlLoadingIcon, GlModal } from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { mount, shallowMount } from '@vue/test-utils';
|
||||
import axios from 'axios';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { mockTracking } from 'helpers/tracking_helper';
|
||||
|
@ -32,12 +33,15 @@ describe('WikiForm', () => {
|
|||
const findSubmitButton = () => wrapper.findByTestId('wiki-submit-button');
|
||||
const findCancelButton = () => wrapper.findByRole('link', { name: 'Cancel' });
|
||||
const findUseNewEditorButton = () => wrapper.findByRole('button', { name: 'Use the new editor' });
|
||||
const findToggleEditingModeButton = () => wrapper.findByTestId('toggle-editing-mode-button');
|
||||
const findDismissContentEditorAlertButton = () =>
|
||||
wrapper.findByRole('button', { name: 'Try this later' });
|
||||
const findSwitchToOldEditorButton = () =>
|
||||
wrapper.findByRole('button', { name: 'Switch me back to the classic editor.' });
|
||||
const findTitleHelpLink = () => wrapper.findByRole('link', { name: 'More Information.' });
|
||||
const findMarkdownHelpLink = () => wrapper.findByTestId('wiki-markdown-help-link');
|
||||
const findContentEditor = () => wrapper.findComponent(ContentEditor);
|
||||
const findClassicEditor = () => wrapper.findComponent(MarkdownField);
|
||||
|
||||
const setFormat = (value) => {
|
||||
const format = findFormat();
|
||||
|
@ -73,18 +77,24 @@ describe('WikiForm', () => {
|
|||
path: '/project/path/-/wikis/home',
|
||||
};
|
||||
|
||||
function createWrapper(persisted = false, { pageInfo } = {}) {
|
||||
const formatOptions = {
|
||||
Markdown: 'markdown',
|
||||
RDoc: 'rdoc',
|
||||
AsciiDoc: 'asciidoc',
|
||||
Org: 'org',
|
||||
};
|
||||
|
||||
function createWrapper(
|
||||
persisted = false,
|
||||
{ pageInfo, glFeatures = { wikiSwitchBetweenContentEditorRawMarkdown: false } } = {},
|
||||
) {
|
||||
wrapper = extendedWrapper(
|
||||
mount(
|
||||
WikiForm,
|
||||
{
|
||||
provide: {
|
||||
formatOptions: {
|
||||
Markdown: 'markdown',
|
||||
RDoc: 'rdoc',
|
||||
AsciiDoc: 'asciidoc',
|
||||
Org: 'org',
|
||||
},
|
||||
formatOptions,
|
||||
glFeatures,
|
||||
pageInfo: {
|
||||
...(persisted ? pageInfoPersisted : pageInfoNew),
|
||||
...pageInfo,
|
||||
|
@ -96,6 +106,27 @@ describe('WikiForm', () => {
|
|||
);
|
||||
}
|
||||
|
||||
const createShallowWrapper = (
|
||||
persisted = false,
|
||||
{ pageInfo, glFeatures = { wikiSwitchBetweenContentEditorRawMarkdown: false } } = {},
|
||||
) => {
|
||||
wrapper = extendedWrapper(
|
||||
shallowMount(WikiForm, {
|
||||
provide: {
|
||||
formatOptions,
|
||||
glFeatures,
|
||||
pageInfo: {
|
||||
...(persisted ? pageInfoPersisted : pageInfoNew),
|
||||
...pageInfo,
|
||||
},
|
||||
},
|
||||
stubs: {
|
||||
MarkdownField,
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
trackingSpy = mockTracking(undefined, null, jest.spyOn);
|
||||
mock = new MockAdapter(axios);
|
||||
|
@ -193,14 +224,13 @@ describe('WikiForm', () => {
|
|||
});
|
||||
|
||||
describe('when wiki content is updated', () => {
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
createWrapper(true);
|
||||
|
||||
const input = findContent();
|
||||
input.setValue(' Lorem ipsum dolar sit! ');
|
||||
input.element.dispatchEvent(new Event('input'));
|
||||
|
||||
return wrapper.vm.$nextTick();
|
||||
await input.trigger('input');
|
||||
});
|
||||
|
||||
it('sets before unload warning', () => {
|
||||
|
@ -279,6 +309,100 @@ describe('WikiForm', () => {
|
|||
);
|
||||
});
|
||||
|
||||
describe('when wikiSwitchBetweenContentEditorRawMarkdown feature flag is not enabled', () => {
|
||||
beforeEach(() => {
|
||||
createShallowWrapper(true, {
|
||||
glFeatures: { wikiSwitchBetweenContentEditorRawMarkdown: false },
|
||||
});
|
||||
});
|
||||
|
||||
it('hides toggle editing mode button', () => {
|
||||
expect(findToggleEditingModeButton().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when wikiSwitchBetweenContentEditorRawMarkdown feature flag is enabled', () => {
|
||||
beforeEach(() => {
|
||||
createShallowWrapper(true, {
|
||||
glFeatures: { wikiSwitchBetweenContentEditorRawMarkdown: true },
|
||||
});
|
||||
});
|
||||
|
||||
it('hides gl-alert containing "use new editor" button', () => {
|
||||
expect(findUseNewEditorButton().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('displays toggle editing mode button', () => {
|
||||
expect(findToggleEditingModeButton().exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('when content editor is not active', () => {
|
||||
it('displays "Edit rich text" label in the toggle editing mode button', () => {
|
||||
expect(findToggleEditingModeButton().text()).toBe('Edit rich text');
|
||||
});
|
||||
|
||||
describe('when clicking the toggle editing mode button', () => {
|
||||
beforeEach(() => {
|
||||
findToggleEditingModeButton().vm.$emit('click');
|
||||
});
|
||||
|
||||
it('hides the classic editor', () => {
|
||||
expect(findClassicEditor().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('hides the content editor', () => {
|
||||
expect(findContentEditor().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when content editor is active', () => {
|
||||
let mockContentEditor;
|
||||
|
||||
beforeEach(() => {
|
||||
mockContentEditor = {
|
||||
getSerializedContent: jest.fn(),
|
||||
setSerializedContent: jest.fn(),
|
||||
};
|
||||
|
||||
findToggleEditingModeButton().vm.$emit('click');
|
||||
});
|
||||
|
||||
it('hides switch to old editor button', () => {
|
||||
expect(findSwitchToOldEditorButton().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('displays "Edit source" label in the toggle editing mode button', () => {
|
||||
expect(findToggleEditingModeButton().text()).toBe('Edit source');
|
||||
});
|
||||
|
||||
describe('when clicking the toggle editing mode button', () => {
|
||||
const contentEditorFakeSerializedContent = 'fake content';
|
||||
|
||||
beforeEach(() => {
|
||||
mockContentEditor.getSerializedContent.mockReturnValueOnce(
|
||||
contentEditorFakeSerializedContent,
|
||||
);
|
||||
|
||||
findContentEditor().vm.$emit('initialized', mockContentEditor);
|
||||
findToggleEditingModeButton().vm.$emit('click');
|
||||
});
|
||||
|
||||
it('hides the content editor', () => {
|
||||
expect(findContentEditor().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('displays the classic editor', () => {
|
||||
expect(findClassicEditor().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('updates the classic editor content field', () => {
|
||||
expect(findContent().element.value).toBe(contentEditorFakeSerializedContent);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('wiki content editor', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper(true);
|
||||
|
@ -306,8 +430,8 @@ describe('WikiForm', () => {
|
|||
});
|
||||
|
||||
const assertOldEditorIsVisible = () => {
|
||||
expect(wrapper.findComponent(ContentEditor).exists()).toBe(false);
|
||||
expect(wrapper.findComponent(MarkdownField).exists()).toBe(true);
|
||||
expect(findContentEditor().exists()).toBe(false);
|
||||
expect(findClassicEditor().exists()).toBe(true);
|
||||
expect(findSubmitButton().props('disabled')).toBe(false);
|
||||
|
||||
expect(wrapper.text()).not.toContain(
|
||||
|
@ -376,10 +500,6 @@ describe('WikiForm', () => {
|
|||
findUseNewEditorButton().trigger('click');
|
||||
});
|
||||
|
||||
it('shows a loading indicator for the rich text editor', () => {
|
||||
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('shows a tip to send feedback', () => {
|
||||
expect(wrapper.text()).toContain('Tell us your experiences with the new Markdown editor');
|
||||
});
|
||||
|
@ -412,16 +532,8 @@ describe('WikiForm', () => {
|
|||
});
|
||||
|
||||
describe('when wiki content is updated', () => {
|
||||
beforeEach(async () => {
|
||||
// wait for content editor to load
|
||||
await waitForPromises();
|
||||
|
||||
wrapper.vm.contentEditor.tiptapEditor.commands.setContent(
|
||||
'<p>hello __world__ from content editor</p>',
|
||||
true,
|
||||
);
|
||||
|
||||
return wrapper.vm.$nextTick();
|
||||
beforeEach(() => {
|
||||
findContentEditor().vm.$emit('change', { empty: false });
|
||||
});
|
||||
|
||||
it('sets before unload warning', () => {
|
||||
|
@ -432,7 +544,7 @@ describe('WikiForm', () => {
|
|||
it('unsets before unload warning on form submit', async () => {
|
||||
triggerFormSubmit();
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
await nextTick();
|
||||
|
||||
const e = dispatchBeforeUnload();
|
||||
expect(e.preventDefault).not.toHaveBeenCalled();
|
||||
|
|
|
@ -29,10 +29,10 @@ RSpec.describe Banzai::ReferenceParser::BaseParser do
|
|||
|
||||
describe '#project_for_node' do
|
||||
it 'returns the Project for a node' do
|
||||
document = instance_double('document', fragment?: false)
|
||||
project = instance_double('project')
|
||||
object = instance_double('object', project: project)
|
||||
node = instance_double('node', document: document)
|
||||
document = double('document', fragment?: false)
|
||||
project = instance_double('Project')
|
||||
object = double('object', project: project)
|
||||
node = double('node', document: document)
|
||||
|
||||
context.associate_document(document, object)
|
||||
|
||||
|
|
|
@ -7,15 +7,15 @@ RSpec.describe Banzai::RenderContext do
|
|||
|
||||
describe '#project_for_node' do
|
||||
it 'returns the default project if no associated project was found' do
|
||||
project = instance_double('project')
|
||||
project = instance_double('Project')
|
||||
context = described_class.new(project)
|
||||
|
||||
expect(context.project_for_node(document)).to eq(project)
|
||||
end
|
||||
|
||||
it 'returns the associated project if one was associated explicitly' do
|
||||
project = instance_double('project')
|
||||
obj = instance_double('object', project: project)
|
||||
project = instance_double('Project')
|
||||
obj = double('object', project: project)
|
||||
context = described_class.new
|
||||
|
||||
context.associate_document(document, obj)
|
||||
|
@ -24,8 +24,8 @@ RSpec.describe Banzai::RenderContext do
|
|||
end
|
||||
|
||||
it 'returns the project associated with a DocumentFragment when using a node' do
|
||||
project = instance_double('project')
|
||||
obj = instance_double('object', project: project)
|
||||
project = instance_double('Project')
|
||||
obj = double('object', project: project)
|
||||
context = described_class.new
|
||||
node = document.children.first
|
||||
|
||||
|
|
|
@ -3,16 +3,18 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe ErrorTracking::Collector::PayloadValidator do
|
||||
let(:validator) { described_class.new }
|
||||
|
||||
describe '#valid?' do
|
||||
RSpec.shared_examples 'valid payload' do
|
||||
it 'returns true' do
|
||||
expect(described_class.new.valid?(payload)).to be_truthy
|
||||
specify do
|
||||
expect(validator).to be_valid(payload)
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'invalid payload' do
|
||||
it 'returns false' do
|
||||
expect(described_class.new.valid?(payload)).to be_falsey
|
||||
specify do
|
||||
expect(validator).not_to be_valid(payload)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -28,6 +30,12 @@ RSpec.describe ErrorTracking::Collector::PayloadValidator do
|
|||
it_behaves_like 'valid payload'
|
||||
end
|
||||
|
||||
context 'python payload in repl' do
|
||||
let(:payload) { Gitlab::Json.parse(fixture_file('error_tracking/python_event_repl.json')) }
|
||||
|
||||
it_behaves_like 'valid payload'
|
||||
end
|
||||
|
||||
context 'browser payload' do
|
||||
let(:payload) { Gitlab::Json.parse(fixture_file('error_tracking/browser_event.json')) }
|
||||
|
||||
|
|
|
@ -67,12 +67,12 @@ RSpec.describe Gitlab::ContributionsCalendar do
|
|||
|
||||
context "when the user has opted-in for private contributions" do
|
||||
it "shows private and public events to all users" do
|
||||
user.update_column(:include_private_contributions, true)
|
||||
contributor.update_column(:include_private_contributions, true)
|
||||
create_event(private_project, today)
|
||||
create_event(public_project, today)
|
||||
|
||||
expect(calendar.activity_dates[today]).to eq(1)
|
||||
expect(calendar(user).activity_dates[today]).to eq(1)
|
||||
expect(calendar.activity_dates[today]).to eq(2)
|
||||
expect(calendar(user).activity_dates[today]).to eq(2)
|
||||
expect(calendar(contributor).activity_dates[today]).to eq(2)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -97,7 +97,7 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
|
|||
|
||||
describe '#push_frontend_experiment' do
|
||||
it 'pushes an experiment to the frontend' do
|
||||
gon = instance_double('gon')
|
||||
gon = class_double('Gon')
|
||||
stub_experiment_for_subject(my_experiment: true)
|
||||
allow(controller).to receive(:gon).and_return(gon)
|
||||
|
||||
|
|
|
@ -110,7 +110,7 @@ RSpec.describe Gitlab::Git::RuggedImpl::UseRugged, :seed_helper do
|
|||
describe '#running_puma_with_multiple_threads?' do
|
||||
context 'when using Puma' do
|
||||
before do
|
||||
stub_const('::Puma', class_double('Puma'))
|
||||
stub_const('::Puma', double('puma constant'))
|
||||
allow(Gitlab::Runtime).to receive(:puma?).and_return(true)
|
||||
end
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ RSpec.describe Gitlab::GonHelper do
|
|||
end
|
||||
|
||||
it 'pushes a feature flag to the frontend' do
|
||||
gon = instance_double('gon')
|
||||
gon = class_double('Gon')
|
||||
thing = stub_feature_flag_gate('thing')
|
||||
|
||||
stub_feature_flags(my_feature_flag: thing)
|
||||
|
|
|
@ -73,7 +73,7 @@ RSpec.describe ::Gitlab::LetsEncrypt::Client do
|
|||
subject(:new_order) { client.new_order('example.com') }
|
||||
|
||||
before do
|
||||
order_double = instance_double('Acme::Order')
|
||||
order_double = double('Acme::Order')
|
||||
allow(stub_client).to receive(:new_order).and_return(order_double)
|
||||
end
|
||||
|
||||
|
@ -107,7 +107,7 @@ RSpec.describe ::Gitlab::LetsEncrypt::Client do
|
|||
subject { client.load_challenge(url) }
|
||||
|
||||
before do
|
||||
acme_challenge = instance_double('Acme::Client::Resources::Challenge')
|
||||
acme_challenge = double('Acme::Client::Resources::Challenge')
|
||||
allow(stub_client).to receive(:challenge).with(url: url).and_return(acme_challenge)
|
||||
end
|
||||
|
||||
|
|
|
@ -50,40 +50,4 @@ RSpec.describe Gitlab::Metrics::Exporter::SidekiqExporter do
|
|||
expect(exporter.log_filename).to end_with('sidekiq_exporter.log')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when port is already taken' do
|
||||
let(:first_exporter) { described_class.new }
|
||||
|
||||
before do
|
||||
stub_config(
|
||||
monitoring: {
|
||||
sidekiq_exporter: {
|
||||
enabled: true,
|
||||
port: 9992,
|
||||
address: '127.0.0.1'
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
first_exporter.start
|
||||
end
|
||||
|
||||
after do
|
||||
first_exporter.stop
|
||||
end
|
||||
|
||||
it 'does print error message' do
|
||||
expect(Sidekiq.logger).to receive(:error)
|
||||
.with(
|
||||
class: described_class.to_s,
|
||||
message: 'Cannot start sidekiq_exporter',
|
||||
'exception.message' => anything)
|
||||
|
||||
exporter.start
|
||||
end
|
||||
|
||||
it 'does not start thread' do
|
||||
expect(exporter.start).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -96,8 +96,8 @@ RSpec.describe Gitlab::QuickActions::Dsl do
|
|||
|
||||
expect(dynamic_description_def.name).to eq(:dynamic_description)
|
||||
expect(dynamic_description_def.aliases).to eq([])
|
||||
expect(dynamic_description_def.to_h(OpenStruct.new(noteable: 'issue'))[:description]).to eq('A dynamic description for ISSUE')
|
||||
expect(dynamic_description_def.execute_message(OpenStruct.new(noteable: 'issue'), 'arg')).to eq('A dynamic execution message for ISSUE passing arg')
|
||||
expect(dynamic_description_def.to_h(double('desc', noteable: 'issue'))[:description]).to eq('A dynamic description for ISSUE')
|
||||
expect(dynamic_description_def.execute_message(double('desc', noteable: 'issue'), 'arg')).to eq('A dynamic execution message for ISSUE passing arg')
|
||||
expect(dynamic_description_def.params).to eq(['The first argument', 'The second argument'])
|
||||
expect(dynamic_description_def.condition_block).to be_nil
|
||||
expect(dynamic_description_def.types).to eq([])
|
||||
|
|
|
@ -5,8 +5,8 @@ require 'spec_helper'
|
|||
RSpec.describe Gitlab::RackAttack, :aggregate_failures do
|
||||
describe '.configure' do
|
||||
let(:fake_rack_attack) { class_double("Rack::Attack") }
|
||||
let(:fake_rack_attack_request) { class_double("Rack::Attack::Request") }
|
||||
let(:fake_cache) { instance_double("Rack::Attack::Cache") }
|
||||
let(:fake_rack_attack_request) { class_double(Rack::Attack::Request) }
|
||||
let(:fake_cache) { instance_double(Rack::Attack::Cache) }
|
||||
|
||||
let(:throttles) do
|
||||
{
|
||||
|
@ -27,9 +27,6 @@ RSpec.describe Gitlab::RackAttack, :aggregate_failures do
|
|||
end
|
||||
|
||||
before do
|
||||
stub_const("Rack::Attack", fake_rack_attack)
|
||||
stub_const("Rack::Attack::Request", fake_rack_attack_request)
|
||||
|
||||
allow(fake_rack_attack).to receive(:throttled_response=)
|
||||
allow(fake_rack_attack).to receive(:throttle)
|
||||
allow(fake_rack_attack).to receive(:track)
|
||||
|
@ -37,6 +34,9 @@ RSpec.describe Gitlab::RackAttack, :aggregate_failures do
|
|||
allow(fake_rack_attack).to receive(:blocklist)
|
||||
allow(fake_rack_attack).to receive(:cache).and_return(fake_cache)
|
||||
allow(fake_cache).to receive(:store=)
|
||||
|
||||
fake_rack_attack.const_set('Request', fake_rack_attack_request)
|
||||
stub_const("Rack::Attack", fake_rack_attack)
|
||||
end
|
||||
|
||||
it 'extends the request class' do
|
||||
|
|
|
@ -111,7 +111,7 @@ RSpec.describe DeploymentMetrics do
|
|||
}
|
||||
end
|
||||
|
||||
let(:prometheus_adapter) { instance_double('prometheus_adapter', can_query?: true, configured?: true) }
|
||||
let(:prometheus_adapter) { instance_double(::Integrations::Prometheus, can_query?: true, configured?: true) }
|
||||
|
||||
before do
|
||||
allow(deployment_metrics).to receive(:prometheus_adapter).and_return(prometheus_adapter)
|
||||
|
|
|
@ -53,7 +53,7 @@ RSpec.describe "deleting designs" do
|
|||
|
||||
context 'the designs list contains filenames we cannot find' do
|
||||
it_behaves_like 'a failed request' do
|
||||
let(:designs) { %w/foo bar baz/.map { |fn| instance_double('file', filename: fn) } }
|
||||
let(:designs) { %w/foo bar baz/.map { |fn| double('file', filename: fn) } }
|
||||
let(:the_error) { a_string_matching %r/filenames were not found/ }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -58,7 +58,7 @@ RSpec.describe Clusters::Integrations::PrometheusHealthCheckService, '#execute'
|
|||
let(:prometheus_enabled) { true }
|
||||
|
||||
before do
|
||||
client = instance_double('PrometheusClient', healthy?: client_healthy)
|
||||
client = instance_double('Gitlab::PrometheusClient', healthy?: client_healthy)
|
||||
expect(prometheus).to receive(:prometheus_client).and_return(client)
|
||||
end
|
||||
|
||||
|
|
|
@ -151,7 +151,7 @@ RSpec.describe MergeRequests::MergeService do
|
|||
|
||||
it 'closes GitLab issue tracker issues' do
|
||||
issue = create :issue, project: project
|
||||
commit = instance_double('commit', safe_message: "Fixes #{issue.to_reference}", date: Time.current, authored_date: Time.current)
|
||||
commit = double('commit', safe_message: "Fixes #{issue.to_reference}", date: Time.current, authored_date: Time.current)
|
||||
allow(merge_request).to receive(:commits).and_return([commit])
|
||||
merge_request.cache_merge_request_closes_issues!
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ RSpec.describe Projects::Prometheus::Alerts::CreateService do
|
|||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
let(:service) { described_class.new(project, user, params) }
|
||||
let(:service) { described_class.new(project: project, current_user: user, params: params) }
|
||||
|
||||
subject { service.execute }
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ RSpec.describe Projects::Prometheus::Alerts::DestroyService do
|
|||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:alert) { create(:prometheus_alert, project: project) }
|
||||
|
||||
let(:service) { described_class.new(project, user, nil) }
|
||||
let(:service) { described_class.new(project: project, current_user: user, params: nil) }
|
||||
|
||||
describe '#execute' do
|
||||
subject { service.execute(alert) }
|
||||
|
|
|
@ -11,7 +11,7 @@ RSpec.describe Projects::Prometheus::Alerts::UpdateService do
|
|||
create(:prometheus_alert, project: project, environment: environment)
|
||||
end
|
||||
|
||||
let(:service) { described_class.new(project, user, params) }
|
||||
let(:service) { described_class.new(project: project, current_user: user, params: params) }
|
||||
|
||||
let(:params) do
|
||||
{
|
||||
|
|
|
@ -7,19 +7,26 @@ require_relative '../../sidekiq_cluster/sidekiq_cluster'
|
|||
RSpec.describe Gitlab::SidekiqCluster do # rubocop:disable RSpec/FilePath
|
||||
describe '.start' do
|
||||
it 'starts Sidekiq with the given queues, environment and options' do
|
||||
expected_options = {
|
||||
env: :production,
|
||||
directory: 'foo/bar',
|
||||
max_concurrency: 20,
|
||||
min_concurrency: 10,
|
||||
timeout: 25,
|
||||
dryrun: true
|
||||
process_options = {
|
||||
pgroup: true,
|
||||
err: $stderr,
|
||||
out: $stdout
|
||||
}
|
||||
|
||||
expect(described_class).to receive(:start_sidekiq).ordered.with(%w(foo), expected_options.merge(worker_id: 0))
|
||||
expect(described_class).to receive(:start_sidekiq).ordered.with(%w(bar baz), expected_options.merge(worker_id: 1))
|
||||
expect(Process).to receive(:spawn).ordered.with({
|
||||
"ENABLE_SIDEKIQ_CLUSTER" => "1",
|
||||
"SIDEKIQ_WORKER_ID" => "0"
|
||||
},
|
||||
"bundle", "exec", "sidekiq", "-c10", "-eproduction", "-t25", "-gqueues:foo", "-rfoo/bar", "-qfoo,1", process_options
|
||||
)
|
||||
expect(Process).to receive(:spawn).ordered.with({
|
||||
"ENABLE_SIDEKIQ_CLUSTER" => "1",
|
||||
"SIDEKIQ_WORKER_ID" => "1"
|
||||
},
|
||||
"bundle", "exec", "sidekiq", "-c10", "-eproduction", "-t25", "-gqueues:bar,baz", "-rfoo/bar", "-qbar,1", "-qbaz,1", process_options
|
||||
)
|
||||
|
||||
described_class.start([%w(foo), %w(bar baz)], env: :production, directory: 'foo/bar', max_concurrency: 20, min_concurrency: 10, dryrun: true)
|
||||
described_class.start([%w(foo), %w(bar baz)], env: :production, directory: 'foo/bar', max_concurrency: 20, min_concurrency: 10)
|
||||
end
|
||||
|
||||
it 'starts Sidekiq with the given queues and sensible default options' do
|
||||
|
|
|
@ -52,8 +52,6 @@ module SimpleCovEnv
|
|||
add_filter '/app/controllers/sherlock/' # Profiling tool used only in development
|
||||
add_filter '/bin/'
|
||||
add_filter 'db/fixtures/' # Matches EE files as well
|
||||
add_filter '/lib/gitlab/sidekiq_middleware/'
|
||||
add_filter '/lib/system_check/'
|
||||
|
||||
add_group 'Channels', 'app/channels' # Matches EE files as well
|
||||
add_group 'Controllers', 'app/controllers' # Matches EE files as well
|
||||
|
|
|
@ -475,3 +475,7 @@ Rugged::Settings['search_path_global'] = Rails.root.join('tmp/tests').to_s
|
|||
|
||||
# Initialize FactoryDefault to use create_default helper
|
||||
TestProf::FactoryDefault.init
|
||||
|
||||
# Exclude the Geo proxy API request from getting on_next_request Warden handlers,
|
||||
# necessary to prevent race conditions with feature tests not getting authenticated.
|
||||
::Warden.asset_paths << %r{^/api/v4/geo/proxy$}
|
||||
|
|
|
@ -15,7 +15,10 @@ require 'rubocop'
|
|||
require 'rubocop/rspec/support'
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.mock_with :rspec
|
||||
config.mock_with :rspec do |mocks|
|
||||
mocks.verify_doubled_constant_names = true
|
||||
end
|
||||
|
||||
config.raise_errors_for_deprecations!
|
||||
|
||||
config.include StubConfiguration
|
||||
|
|
|
@ -138,11 +138,26 @@ RSpec.shared_examples 'User updates wiki page' do
|
|||
end
|
||||
|
||||
context 'when using the content editor' do
|
||||
before do
|
||||
click_button 'Use the new editor'
|
||||
context 'with feature flag on' do
|
||||
before do
|
||||
click_button 'Edit rich text'
|
||||
end
|
||||
|
||||
it_behaves_like 'edits content using the content editor'
|
||||
end
|
||||
|
||||
it_behaves_like 'edits content using the content editor'
|
||||
context 'with feature flag off' do
|
||||
before do
|
||||
stub_feature_flags(wiki_switch_between_content_editor_raw_markdown: false)
|
||||
visit(wiki_path(wiki))
|
||||
|
||||
click_link('Edit')
|
||||
|
||||
click_button 'Use the new editor'
|
||||
end
|
||||
|
||||
it_behaves_like 'edits content using the content editor'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -35,10 +35,15 @@ RSpec.describe IssuableExportCsvWorker do
|
|||
end
|
||||
|
||||
context 'with params' do
|
||||
let(:params) { { 'test_key' => true } }
|
||||
let(:params) { { 'test_key' => true, 'not' => { 'label_name' => ['SomeLabel'] } } }
|
||||
|
||||
it 'converts controller string keys to symbol keys for IssuesFinder' do
|
||||
expect(IssuesFinder).to receive(:new).with(user, hash_including(test_key: true)).and_call_original
|
||||
it 'allows symbol access for IssuesFinder' do
|
||||
expect(IssuesFinder).to receive(:new).and_wrap_original do |method, user, params|
|
||||
expect(params[:test_key]).to eq(true)
|
||||
expect(params[:not][:label_name]).to eq(['SomeLabel'])
|
||||
|
||||
method.call(user, params)
|
||||
end
|
||||
|
||||
subject
|
||||
end
|
||||
|
|
|
@ -1 +1 @@
|
|||
golang 1.16.9
|
||||
golang 1.16.10
|
||||
|
|
|
@ -270,7 +270,7 @@ func TestUploadHandlerForMultipleFiles(t *testing.T) {
|
|||
require.NoError(t, s.writer.Close())
|
||||
|
||||
response := testUploadArtifacts(t, s.writer.FormDataContentType(), s.url, s.buffer)
|
||||
require.Equal(t, http.StatusBadRequest, response.Code)
|
||||
require.Equal(t, http.StatusInternalServerError, response.Code)
|
||||
}
|
||||
|
||||
func TestUploadFormProcessing(t *testing.T) {
|
||||
|
|
|
@ -24,10 +24,12 @@ import (
|
|||
"gitlab.com/gitlab-org/gitlab/workhorse/internal/upload/exif"
|
||||
)
|
||||
|
||||
const maxFilesAllowed = 10
|
||||
|
||||
// ErrInjectedClientParam means that the client sent a parameter that overrides one of our own fields
|
||||
var (
|
||||
ErrInjectedClientParam = errors.New("injected client parameter")
|
||||
ErrMultipleFilesUploaded = errors.New("upload request contains more than one file")
|
||||
ErrInjectedClientParam = errors.New("injected client parameter")
|
||||
ErrTooManyFilesUploaded = fmt.Errorf("upload request contains more than %v files", maxFilesAllowed)
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -117,8 +119,8 @@ func rewriteFormFilesFromMultipart(r *http.Request, writer *multipart.Writer, pr
|
|||
}
|
||||
|
||||
func (rew *rewriter) handleFilePart(ctx context.Context, name string, p *multipart.Part, opts *filestore.SaveFileOpts) error {
|
||||
if rew.filter.Count() > 0 {
|
||||
return ErrMultipleFilesUploaded
|
||||
if rew.filter.Count() >= maxFilesAllowed {
|
||||
return ErrTooManyFilesUploaded
|
||||
}
|
||||
|
||||
multipartFiles.WithLabelValues(rew.filter.Name()).Inc()
|
||||
|
|
|
@ -27,6 +27,10 @@ func (s *SavedFileTracker) Count() int {
|
|||
}
|
||||
|
||||
func (s *SavedFileTracker) ProcessFile(_ context.Context, fieldName string, file *filestore.FileHandler, _ *multipart.Writer) error {
|
||||
if _, ok := s.rewrittenFields[fieldName]; ok {
|
||||
return fmt.Errorf("the %v field has already been processed", fieldName)
|
||||
}
|
||||
|
||||
s.Track(fieldName, file.LocalPath)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -37,3 +37,14 @@ func TestSavedFileTracking(t *testing.T) {
|
|||
|
||||
require.Contains(t, rewrittenFields, "test")
|
||||
}
|
||||
|
||||
func TestDuplicatedFileProcessing(t *testing.T) {
|
||||
tracker := SavedFileTracker{}
|
||||
file := &filestore.FileHandler{}
|
||||
|
||||
require.NoError(t, tracker.ProcessFile(context.Background(), "file", file, nil))
|
||||
|
||||
err := tracker.ProcessFile(context.Background(), "file", file, nil)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, "the file field has already been processed", err.Error())
|
||||
}
|
||||
|
|
|
@ -35,8 +35,8 @@ func HandleFileUploads(w http.ResponseWriter, r *http.Request, h http.Handler, p
|
|||
switch err {
|
||||
case ErrInjectedClientParam:
|
||||
helper.CaptureAndFail(w, r, err, "Bad Request", http.StatusBadRequest)
|
||||
case ErrMultipleFilesUploaded:
|
||||
helper.CaptureAndFail(w, r, err, "Uploading multiple files is not allowed", http.StatusBadRequest)
|
||||
case ErrTooManyFilesUploaded:
|
||||
helper.CaptureAndFail(w, r, err, err.Error(), http.StatusBadRequest)
|
||||
case http.ErrNotMultipart:
|
||||
h.ServeHTTP(w, r)
|
||||
case filestore.ErrEntityTooLarge:
|
||||
|
|
|
@ -260,10 +260,10 @@ func TestUploadingMultipleFiles(t *testing.T) {
|
|||
var buffer bytes.Buffer
|
||||
|
||||
writer := multipart.NewWriter(&buffer)
|
||||
_, err = writer.CreateFormFile("file", "my.file")
|
||||
require.NoError(t, err)
|
||||
_, err = writer.CreateFormFile("file", "my.file")
|
||||
require.NoError(t, err)
|
||||
for i := 0; i < 11; i++ {
|
||||
_, err = writer.CreateFormFile(fmt.Sprintf("file %v", i), "my.file")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.NoError(t, writer.Close())
|
||||
|
||||
httpRequest, err := http.NewRequest("PUT", "/url/path", &buffer)
|
||||
|
@ -279,7 +279,7 @@ func TestUploadingMultipleFiles(t *testing.T) {
|
|||
HandleFileUploads(response, httpRequest, nilHandler, apiResponse, &testFormProcessor{}, opts)
|
||||
|
||||
require.Equal(t, 400, response.Code)
|
||||
require.Equal(t, "Uploading multiple files is not allowed\n", response.Body.String())
|
||||
require.Equal(t, "upload request contains more than 10 files\n", response.Body.String())
|
||||
}
|
||||
|
||||
func TestUploadProcessingFile(t *testing.T) {
|
||||
|
|
|
@ -65,7 +65,7 @@ func newUpstream(cfg config.Config, accessLogger *logrus.Logger, routesCallback
|
|||
Config: cfg,
|
||||
accessLogger: accessLogger,
|
||||
// Kind of a feature flag. See https://gitlab.com/groups/gitlab-org/-/epics/5914#note_564974130
|
||||
enableGeoProxyFeature: os.Getenv("GEO_SECONDARY_PROXY") == "1",
|
||||
enableGeoProxyFeature: os.Getenv("GEO_SECONDARY_PROXY") != "0",
|
||||
geoProxyBackend: &url.URL{},
|
||||
}
|
||||
if up.geoProxyPollSleep == nil {
|
||||
|
@ -207,8 +207,8 @@ func (u *upstream) findGeoProxyRoute(cleanedPath string, r *http.Request) *route
|
|||
|
||||
func (u *upstream) pollGeoProxyAPI() {
|
||||
for {
|
||||
u.callGeoProxyAPI()
|
||||
u.geoProxyPollSleep(geoProxyApiPollingInterval)
|
||||
u.callGeoProxyAPI()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -310,5 +310,9 @@ func startWorkhorseServer(railsServerURL string, enableGeoProxyFeature bool) (*h
|
|||
}
|
||||
}
|
||||
|
||||
// Since the first sleep happens before any API call, this ensures
|
||||
// we call the API at least once.
|
||||
waitForNextApiPoll()
|
||||
|
||||
return ws, ws.Close, waitForNextApiPoll
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue