Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
df94498646
commit
0a154f21ff
|
|
@ -18,4 +18,4 @@ variables:
|
|||
# Retry failed specs in separate process
|
||||
QA_RETRY_FAILED_SPECS: "true"
|
||||
# helm chart ref used by test-on-cng pipeline
|
||||
GITLAB_HELM_CHART_REF: "3c3feae6fc1d26ed4e0352004c92ee623558962a"
|
||||
GITLAB_HELM_CHART_REF: "5b39d7671670d64febc1559ee6b952cbb19e5a30"
|
||||
|
|
|
|||
|
|
@ -969,8 +969,6 @@ Gitlab/NamespacedClass:
|
|||
- 'ee/app/serializers/iteration_serializer.rb'
|
||||
- 'ee/app/serializers/license_entity.rb'
|
||||
- 'ee/app/serializers/license_scanning_reports_serializer.rb'
|
||||
- 'ee/app/serializers/licenses_list_entity.rb'
|
||||
- 'ee/app/serializers/licenses_list_serializer.rb'
|
||||
- 'ee/app/serializers/linked_epic_entity.rb'
|
||||
- 'ee/app/serializers/linked_epic_issue_entity.rb'
|
||||
- 'ee/app/serializers/linked_epic_issue_serializer.rb'
|
||||
|
|
@ -983,7 +981,6 @@ Gitlab/NamespacedClass:
|
|||
- 'ee/app/serializers/milestone_serializer.rb'
|
||||
- 'ee/app/serializers/namespace_entity.rb'
|
||||
- 'ee/app/serializers/productivity_analytics_merge_request_entity.rb'
|
||||
- 'ee/app/serializers/report_list_entity.rb'
|
||||
- 'ee/app/serializers/scim_oauth_access_token_entity.rb'
|
||||
- 'ee/app/serializers/storage_shard_entity.rb'
|
||||
- 'ee/app/serializers/storage_shard_serializer.rb'
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ Lint/SymbolConversion:
|
|||
- 'ee/app/models/geo_node_status.rb'
|
||||
- 'ee/app/policies/ee/group_policy.rb'
|
||||
- 'ee/app/policies/ee/project_policy.rb'
|
||||
- 'ee/app/serializers/report_list_entity.rb'
|
||||
- 'ee/app/services/security/security_orchestration_policies/ci_action/base.rb'
|
||||
- 'ee/app/services/vulnerabilities/manually_create_service.rb'
|
||||
- 'ee/app/workers/security/store_scans_worker.rb'
|
||||
|
|
|
|||
|
|
@ -650,7 +650,6 @@ RSpec/BeforeAllRoleAssignment:
|
|||
- 'ee/spec/support/shared_examples/finders/scan_policy_base_finder_examples.rb'
|
||||
- 'ee/spec/support/shared_examples/models/protected_environments/authorizable_examples.rb'
|
||||
- 'ee/spec/support/shared_examples/policies/dast_on_demand_scans_shared_examples.rb'
|
||||
- 'ee/spec/support/shared_examples/serializers/report_status_shared_examples.rb'
|
||||
- 'ee/spec/views/shared/promotions/_promotion_link_project.html.haml_spec.rb'
|
||||
- 'ee/spec/workers/app_sec/dast/profile_schedule_worker_spec.rb'
|
||||
- 'ee/spec/workers/compliance_management/update_default_framework_worker_spec.rb'
|
||||
|
|
|
|||
|
|
@ -155,8 +155,6 @@ RSpec/FactoryBot/AvoidCreate:
|
|||
- 'ee/spec/serializers/issues/linked_issue_feature_flag_entity_spec.rb'
|
||||
- 'ee/spec/serializers/license_compliance/collapsed_comparer_entity_spec.rb'
|
||||
- 'ee/spec/serializers/license_compliance/comparer_entity_spec.rb'
|
||||
- 'ee/spec/serializers/licenses_list_entity_spec.rb'
|
||||
- 'ee/spec/serializers/licenses_list_serializer_spec.rb'
|
||||
- 'ee/spec/serializers/linked_feature_flag_issue_entity_spec.rb'
|
||||
- 'ee/spec/serializers/member_entity_spec.rb'
|
||||
- 'ee/spec/serializers/member_user_entity_spec.rb'
|
||||
|
|
|
|||
|
|
@ -163,7 +163,6 @@ RSpec/VerifiedDoubles:
|
|||
- 'ee/spec/support/shared_examples/models/concerns/blob_replicator_strategy_shared_examples.rb'
|
||||
- 'ee/spec/support/shared_examples/models/concerns/verifiable_replicator_shared_examples.rb'
|
||||
- 'ee/spec/support/shared_examples/models/geo_verifiable_registry_shared_examples.rb'
|
||||
- 'ee/spec/support/shared_examples/serializers/report_status_shared_examples.rb'
|
||||
- 'ee/spec/support/shared_examples/services/base_sync_service_shared_examples.rb'
|
||||
- 'ee/spec/support/shared_examples/services/geo/geo_request_service_shared_examples.rb'
|
||||
- 'ee/spec/support/shared_examples/status_page/reference_links_examples.rb'
|
||||
|
|
|
|||
|
|
@ -850,13 +850,13 @@ GEM
|
|||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
mini_mime (~> 1.0)
|
||||
google-protobuf (3.25.4)
|
||||
google-cloud-storage_transfer (1.2.0)
|
||||
google-cloud-core (~> 1.6)
|
||||
google-cloud-storage_transfer-v1 (>= 0.5, < 2.a)
|
||||
google-cloud-storage_transfer-v1 (0.8.0)
|
||||
gapic-common (>= 0.20.0, < 2.a)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-protobuf (3.25.4)
|
||||
googleapis-common-protos (1.4.0)
|
||||
google-protobuf (~> 3.14)
|
||||
googleapis-common-protos-types (~> 1.2)
|
||||
|
|
|
|||
|
|
@ -1,38 +1,41 @@
|
|||
<script>
|
||||
import { GlButton, GlSprintf, GlDisclosureDropdown } from '@gitlab/ui';
|
||||
import { GlButton, GlSprintf, GlCollapsibleListbox, GlIcon } from '@gitlab/ui';
|
||||
import GITLAB_LOGO_SVG_URL from '@gitlab/svgs/dist/illustrations/gitlab_logo.svg?url';
|
||||
import { s__ } from '~/locale';
|
||||
import { joinPaths, stripRelativeUrlRootFromPath } from '~/lib/utils/url_utility';
|
||||
import { logError } from '~/lib/logger';
|
||||
|
||||
export default {
|
||||
name: 'OAuthDomainMismatchError',
|
||||
components: {
|
||||
GlButton,
|
||||
GlSprintf,
|
||||
GlDisclosureDropdown,
|
||||
GlCollapsibleListbox,
|
||||
GlIcon,
|
||||
},
|
||||
props: {
|
||||
expectedCallbackUrl: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
callbackUrls: {
|
||||
callbackUrlOrigins: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
dropdownItems() {
|
||||
const currentOrigin = window.location.origin;
|
||||
|
||||
return this.callbackUrls
|
||||
.filter(({ base }) => new URL(base).origin !== currentOrigin)
|
||||
.map(({ base }) => {
|
||||
return {
|
||||
href: joinPaths(base, stripRelativeUrlRootFromPath(window.location.pathname)),
|
||||
text: base,
|
||||
};
|
||||
});
|
||||
return this.callbackUrlOrigins.map((domain) => {
|
||||
return {
|
||||
value: domain,
|
||||
text: domain,
|
||||
};
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
reloadPage(urlDomain) {
|
||||
try {
|
||||
const current = new URL(urlDomain + window.location.pathname);
|
||||
window.location.replace(current.toString());
|
||||
} catch (e) {
|
||||
logError(s__('IDE|Error reloading page'), e);
|
||||
}
|
||||
},
|
||||
},
|
||||
gitlabLogo: GITLAB_LOGO_SVG_URL,
|
||||
|
|
@ -47,7 +50,6 @@ export default {
|
|||
description: s__(
|
||||
"IDE|The URL you're using to access the Web IDE and the configured OAuth callback URL do not match. This issue often occurs when you're using a proxy.",
|
||||
),
|
||||
expected: s__('IDE|Could not find a callback URL entry for %{expectedCallbackUrl}.'),
|
||||
contact: s__(
|
||||
'IDE|Contact your administrator or try to open the Web IDE again with another domain.',
|
||||
),
|
||||
|
|
@ -62,28 +64,27 @@ export default {
|
|||
<p>
|
||||
{{ $options.i18n.description }}
|
||||
</p>
|
||||
<gl-sprintf :message="$options.i18n.expected">
|
||||
<template #expectedCallbackUrl>
|
||||
<code>{{ expectedCallbackUrl }}</code>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
<p>
|
||||
{{ $options.i18n.contact }}
|
||||
</p>
|
||||
<div class="gl-mt-6">
|
||||
<gl-disclosure-dropdown
|
||||
v-if="dropdownItems.length > 1"
|
||||
<gl-collapsible-listbox
|
||||
v-if="callbackUrlOrigins.length > 1"
|
||||
:items="dropdownItems"
|
||||
:toggle-text="$options.i18n.buttonText.domains"
|
||||
/>
|
||||
<gl-button
|
||||
v-else-if="dropdownItems.length === 1"
|
||||
variant="confirm"
|
||||
:href="dropdownItems[0].href"
|
||||
:header-text="$options.i18n.dropdownHeader"
|
||||
@select="reloadPage"
|
||||
>
|
||||
<template #toggle>
|
||||
<gl-button variant="confirm" class="self-center">
|
||||
{{ $options.i18n.buttonText.domains }}
|
||||
<gl-icon class="dropdown-chevron gl-ml-2" name="chevron-down" />
|
||||
</gl-button>
|
||||
</template>
|
||||
</gl-collapsible-listbox>
|
||||
<gl-button v-else variant="confirm" @click="reloadPage(callbackUrlOrigins[0])">
|
||||
<gl-sprintf :message="$options.i18n.buttonText.singleDomain">
|
||||
<template #domain>
|
||||
{{ dropdownItems[0].text }}
|
||||
{{ callbackUrlOrigins[0] }}
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</gl-button>
|
||||
|
|
|
|||
|
|
@ -112,7 +112,3 @@ export const DEFAULT_BRANCH = 'main';
|
|||
export const GITLAB_WEB_IDE_FEEDBACK_ISSUE = 'https://gitlab.com/gitlab-org/gitlab/-/issues/377367';
|
||||
|
||||
export const IDE_ELEMENT_ID = 'ide';
|
||||
|
||||
// note: This path comes from `config/routes.rb`
|
||||
export const IDE_PATH = '/-/ide';
|
||||
export const WEB_IDE_OAUTH_CALLBACK_URL_PATH = '/-/ide/oauth_redirect';
|
||||
|
|
|
|||
|
|
@ -24,20 +24,24 @@ export async function startIde(options) {
|
|||
return;
|
||||
}
|
||||
|
||||
const oAuthCallbackDomainMismatchApp = new OAuthCallbackDomainMismatchErrorApp(
|
||||
ideElement,
|
||||
ideElement.dataset.callbackUrls,
|
||||
);
|
||||
|
||||
if (oAuthCallbackDomainMismatchApp.isVisitingFromNonRegisteredOrigin()) {
|
||||
oAuthCallbackDomainMismatchApp.renderError();
|
||||
return;
|
||||
}
|
||||
|
||||
const useNewWebIde = parseBoolean(ideElement.dataset.useNewWebIde);
|
||||
|
||||
if (!useNewWebIde) {
|
||||
if (useNewWebIde) {
|
||||
const { initGitlabWebIDE } = await import('./init_gitlab_web_ide');
|
||||
initGitlabWebIDE(ideElement);
|
||||
} else {
|
||||
resetServiceWorkersPublicPath();
|
||||
const { initLegacyWebIDE } = await import('./init_legacy_web_ide');
|
||||
initLegacyWebIDE(ideElement, options);
|
||||
}
|
||||
|
||||
const oAuthCallbackDomainMismatchApp = new OAuthCallbackDomainMismatchErrorApp(ideElement);
|
||||
|
||||
if (oAuthCallbackDomainMismatchApp.shouldRenderError()) {
|
||||
oAuthCallbackDomainMismatchApp.renderError();
|
||||
return;
|
||||
}
|
||||
const { initGitlabWebIDE } = await import('./init_gitlab_web_ide');
|
||||
initGitlabWebIDE(ideElement);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ const getGitLabUrl = (gitlabPath = '') => {
|
|||
const path = joinPaths('/', window.gon.relative_url_root || '', gitlabPath);
|
||||
const baseUrlObj = new URL(path, window.location.origin);
|
||||
|
||||
return baseUrlObj.href;
|
||||
return cleanEndingSeparator(baseUrlObj.href);
|
||||
};
|
||||
|
||||
export const getBaseConfig = () => ({
|
||||
// baseUrl - The URL which hosts the Web IDE static web assets
|
||||
baseUrl: cleanEndingSeparator(getGitLabUrl(process.env.GITLAB_WEB_IDE_PUBLIC_PATH)),
|
||||
// gitlabUrl - The URL for the GitLab instance. End with trailing slash so URL's are built properly in relative_url_root.
|
||||
gitlabUrl: getGitLabUrl('/'),
|
||||
baseUrl: getGitLabUrl(process.env.GITLAB_WEB_IDE_PUBLIC_PATH),
|
||||
// baseUrl - The URL for the GitLab instance
|
||||
gitlabUrl: getGitLabUrl(''),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { getOAuthCallbackUrl } from './oauth_callback_urls';
|
||||
export const WEB_IDE_OAUTH_CALLBACK_URL_PATH = '/-/ide/oauth_redirect';
|
||||
|
||||
export const getOAuthConfig = ({ clientId }) => {
|
||||
if (!clientId) {
|
||||
|
|
@ -8,7 +8,7 @@ export const getOAuthConfig = ({ clientId }) => {
|
|||
return {
|
||||
type: 'oauth',
|
||||
clientId,
|
||||
callbackUrl: getOAuthCallbackUrl(),
|
||||
callbackUrl: new URL(WEB_IDE_OAUTH_CALLBACK_URL_PATH, window.location.origin).toString(),
|
||||
protectRefreshToken: true,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,75 +0,0 @@
|
|||
import { joinPaths } from '~/lib/utils/url_utility';
|
||||
import { logError } from '~/lib/logger';
|
||||
import { WEB_IDE_OAUTH_CALLBACK_URL_PATH, IDE_PATH } from '../../constants';
|
||||
|
||||
/**
|
||||
* @returns callback URL constructed from current window url
|
||||
*/
|
||||
export function getOAuthCallbackUrl() {
|
||||
const url = window.location.href;
|
||||
|
||||
// We don't rely on `gon.gitlab_url` and `gon.relative_url_root` here because these may not be configured correctly
|
||||
// or we're visiting the instance through a proxy.
|
||||
// Instead, we split on the `/-/ide` in the `href` and use the first part as the base URL.
|
||||
const baseUrl = url.split(IDE_PATH, 2)[0];
|
||||
const callbackUrl = joinPaths(baseUrl, WEB_IDE_OAUTH_CALLBACK_URL_PATH);
|
||||
|
||||
return callbackUrl;
|
||||
}
|
||||
|
||||
const parseCallbackUrl = (urlStr) => {
|
||||
let callbackUrl;
|
||||
|
||||
try {
|
||||
callbackUrl = new URL(urlStr);
|
||||
} catch {
|
||||
// Not a valid URL. Nothing to do here.
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// If we're an unexpected callback URL
|
||||
if (!callbackUrl.pathname.endsWith(WEB_IDE_OAUTH_CALLBACK_URL_PATH)) {
|
||||
return {
|
||||
base: joinPaths(callbackUrl.origin, '/'),
|
||||
url: urlStr,
|
||||
};
|
||||
}
|
||||
|
||||
// Else, trim the expected bit to get the origin + relative_url_root
|
||||
const callbackRelativePath = callbackUrl.pathname.substring(
|
||||
0,
|
||||
callbackUrl.pathname.length - WEB_IDE_OAUTH_CALLBACK_URL_PATH.length,
|
||||
);
|
||||
const baseUrl = new URL(callbackUrl);
|
||||
baseUrl.pathname = callbackRelativePath;
|
||||
baseUrl.hash = '';
|
||||
baseUrl.search = '';
|
||||
|
||||
return {
|
||||
base: joinPaths(baseUrl.toString(), '/'),
|
||||
url: urlStr,
|
||||
};
|
||||
};
|
||||
|
||||
export const parseCallbackUrls = (callbackUrlsJson) => {
|
||||
if (!callbackUrlsJson) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let urls;
|
||||
|
||||
try {
|
||||
urls = JSON.parse(callbackUrlsJson);
|
||||
} catch {
|
||||
// why: We dont want to translate console errors
|
||||
// eslint-disable-next-line @gitlab/require-i18n-strings
|
||||
logError('Failed to parse callback URLs JSON');
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!urls || !Array.isArray(urls)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return urls.map(parseCallbackUrl).filter(Boolean);
|
||||
};
|
||||
|
|
@ -1,43 +1,48 @@
|
|||
import Vue from 'vue';
|
||||
import OAuthDomainMismatchError from './components/oauth_domain_mismatch_error.vue';
|
||||
import { parseCallbackUrls, getOAuthCallbackUrl } from './lib/gitlab_web_ide/oauth_callback_urls';
|
||||
|
||||
export class OAuthCallbackDomainMismatchErrorApp {
|
||||
#el;
|
||||
#callbackUrls;
|
||||
#expectedCallbackUrl;
|
||||
#callbackUrlOrigins;
|
||||
|
||||
constructor(el) {
|
||||
constructor(el, callbackUrls) {
|
||||
this.#el = el;
|
||||
this.#callbackUrls = parseCallbackUrls(el.dataset.callbackUrls);
|
||||
this.#expectedCallbackUrl = getOAuthCallbackUrl();
|
||||
this.#callbackUrlOrigins =
|
||||
OAuthCallbackDomainMismatchErrorApp.#getCallbackUrlOrigins(callbackUrls);
|
||||
}
|
||||
|
||||
shouldRenderError() {
|
||||
if (!this.#callbackUrls.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.#callbackUrls.every(({ url }) => url !== this.#expectedCallbackUrl);
|
||||
isVisitingFromNonRegisteredOrigin() {
|
||||
return (
|
||||
this.#callbackUrlOrigins.length && !this.#callbackUrlOrigins.includes(window.location.origin)
|
||||
);
|
||||
}
|
||||
|
||||
renderError() {
|
||||
const callbackUrls = this.#callbackUrls;
|
||||
const expectedCallbackUrl = this.#expectedCallbackUrl;
|
||||
const callbackUrlOrigins = this.#callbackUrlOrigins;
|
||||
const el = this.#el;
|
||||
|
||||
if (!el) return null;
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
data() {
|
||||
return {
|
||||
callbackUrlOrigins,
|
||||
};
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement(OAuthDomainMismatchError, {
|
||||
props: {
|
||||
expectedCallbackUrl,
|
||||
callbackUrls,
|
||||
callbackUrlOrigins,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static #getCallbackUrlOrigins(callbackUrls) {
|
||||
if (!callbackUrls) return [];
|
||||
|
||||
return JSON.parse(callbackUrls).map((url) => new URL(url).origin);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -790,14 +790,3 @@ export function buildURLwithRefType({ base = window.location.origin, path, refTy
|
|||
}
|
||||
return url.pathname + url.search;
|
||||
}
|
||||
|
||||
export function stripRelativeUrlRootFromPath(path) {
|
||||
const relativeUrlRoot = joinPaths(window.gon.relative_url_root, '/');
|
||||
|
||||
// If we have no relative url root or path doesn't start with it, just return the path
|
||||
if (relativeUrlRoot === '/' || !path.startsWith(relativeUrlRoot)) {
|
||||
return path;
|
||||
}
|
||||
|
||||
return joinPaths('/', path.substring(relativeUrlRoot.length));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AbuseReport < MainClusterwide::ApplicationRecord
|
||||
class AbuseReport < ApplicationRecord
|
||||
include CacheMarkdownField
|
||||
include Sortable
|
||||
include Gitlab::FileTypeDetection
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class AbuseReportAssignee < MainClusterwide::ApplicationRecord
|
||||
class AbuseReportAssignee < ApplicationRecord
|
||||
self.table_name = 'abuse_report_assignees'
|
||||
|
||||
belongs_to :abuse_report, touch: true
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module AntiAbuse
|
||||
class Event < MainClusterwide::ApplicationRecord
|
||||
class Event < ApplicationRecord
|
||||
self.table_name = 'abuse_events'
|
||||
|
||||
validates :category, presence: true
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module AntiAbuse
|
||||
class TrustScore < MainClusterwide::ApplicationRecord
|
||||
class TrustScore < ApplicationRecord
|
||||
self.table_name = 'abuse_trust_scores'
|
||||
|
||||
enum source: Enums::Abuse::Source.sources
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Appearance < MainClusterwide::ApplicationRecord
|
||||
class Appearance < ApplicationRecord
|
||||
include CacheableAttributes
|
||||
include CacheMarkdownField
|
||||
include WithUploads
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ApplicationSetting < MainClusterwide::ApplicationRecord
|
||||
class ApplicationSetting < ApplicationRecord
|
||||
include CacheableAttributes
|
||||
include CacheMarkdownField
|
||||
include TokenAuthenticatable
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AuthenticationEvent < MainClusterwide::ApplicationRecord
|
||||
class AuthenticationEvent < ApplicationRecord
|
||||
include UsageStatistics
|
||||
|
||||
TWO_FACTOR = 'two-factor'
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ module Ci
|
|||
before_validation :set_partition_id, on: :create
|
||||
validates :partition_id, presence: true
|
||||
|
||||
scope :in_partition, ->(id) do
|
||||
where(partition_id: (id.respond_to?(:partition_id) ? id.partition_id : id))
|
||||
scope :in_partition, ->(id, partition_foreign_key: :partition_id) do
|
||||
where(partition_id: (id.respond_to?(partition_foreign_key) ? id.try(partition_foreign_key) : id))
|
||||
end
|
||||
|
||||
def set_partition_id
|
||||
|
|
|
|||
|
|
@ -102,8 +102,6 @@ module CrossDatabaseModification
|
|||
:gitlab_main
|
||||
when 'Ci::ApplicationRecord'
|
||||
:gitlab_ci
|
||||
when 'MainClusterwide::ApplicationRecord'
|
||||
:gitlab_main_clusterwide
|
||||
when 'PackageMetadata::ApplicationRecord'
|
||||
:gitlab_pm
|
||||
else
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module EarlyAccessProgram
|
||||
class Base < ::MainClusterwide::ApplicationRecord
|
||||
class Base < ::ApplicationRecord
|
||||
self.abstract_class = true
|
||||
self.table_name_prefix = 'early_access_program_'
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Email < MainClusterwide::ApplicationRecord
|
||||
class Email < ApplicationRecord
|
||||
include Sortable
|
||||
include Gitlab::SQL::Pattern
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Identity < MainClusterwide::ApplicationRecord
|
||||
class Identity < ApplicationRecord
|
||||
include Sortable
|
||||
include CaseSensitivity
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Identity < MainClusterwide::ApplicationRecord
|
||||
class Identity < ApplicationRecord
|
||||
# This module and method are defined in a separate file to allow EE to
|
||||
# redefine the `scopes` method before it is used in the `Identity` model.
|
||||
module UniquenessScopes
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Organizations
|
||||
class Organization < MainClusterwide::ApplicationRecord
|
||||
class Organization < ApplicationRecord
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
include Gitlab::SQL::Pattern
|
||||
include Gitlab::VisibilityLevel
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Plan < MainClusterwide::ApplicationRecord
|
||||
class Plan < ApplicationRecord
|
||||
DEFAULT = 'default'
|
||||
|
||||
has_one :limits, class_name: 'PlanLimits'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RedirectRoute < MainClusterwide::ApplicationRecord
|
||||
class RedirectRoute < ApplicationRecord
|
||||
include CaseSensitivity
|
||||
|
||||
belongs_to :source, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ResourceEvents
|
||||
class AbuseReportEvent < MainClusterwide::ApplicationRecord
|
||||
class AbuseReportEvent < ApplicationRecord
|
||||
include AbuseReportEventsHelper
|
||||
|
||||
belongs_to :abuse_report, optional: false
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Route < MainClusterwide::ApplicationRecord
|
||||
class Route < ApplicationRecord
|
||||
include CaseSensitivity
|
||||
include Gitlab::SQL::Pattern
|
||||
include EachBatch
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class SpamLog < MainClusterwide::ApplicationRecord
|
||||
class SpamLog < ApplicationRecord
|
||||
belongs_to :user
|
||||
|
||||
validates :user, presence: true
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module System
|
||||
class BroadcastMessage < MainClusterwide::ApplicationRecord
|
||||
class BroadcastMessage < ApplicationRecord
|
||||
include CacheMarkdownField
|
||||
include Sortable
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'carrierwave/orm/activerecord'
|
||||
|
||||
class User < MainClusterwide::ApplicationRecord
|
||||
class User < ApplicationRecord
|
||||
extend Gitlab::ConfigHelper
|
||||
|
||||
include Gitlab::ConfigHelper
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class UserAgentDetail < MainClusterwide::ApplicationRecord
|
||||
class UserAgentDetail < ApplicationRecord
|
||||
belongs_to :subject, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
|
||||
|
||||
validates :user_agent, :ip_address, :subject_id, :subject_type, presence: true
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class UserDetail < MainClusterwide::ApplicationRecord
|
||||
class UserDetail < ApplicationRecord
|
||||
include IgnorableColumns
|
||||
extend ::Gitlab::Utils::Override
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class UserPreference < MainClusterwide::ApplicationRecord
|
||||
class UserPreference < ApplicationRecord
|
||||
include IgnorableColumns
|
||||
|
||||
ignore_column :use_web_ide_extension_marketplace, remove_with: '17.4', remove_after: '2024-08-15'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class UserStatus < MainClusterwide::ApplicationRecord
|
||||
class UserStatus < ApplicationRecord
|
||||
include CacheMarkdownField
|
||||
|
||||
self.primary_key = :user_id
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class UserSyncedAttributesMetadata < MainClusterwide::ApplicationRecord
|
||||
class UserSyncedAttributesMetadata < ApplicationRecord
|
||||
belongs_to :user
|
||||
|
||||
validates :user, presence: true
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Users
|
||||
class BroadcastMessageDismissal < MainClusterwide::ApplicationRecord
|
||||
class BroadcastMessageDismissal < ApplicationRecord
|
||||
belongs_to :user
|
||||
belongs_to :broadcast_message, class_name: 'System::BroadcastMessage'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Users
|
||||
class Callout < MainClusterwide::ApplicationRecord
|
||||
class Callout < ApplicationRecord
|
||||
include Users::Calloutable
|
||||
|
||||
self.table_name = 'user_callouts'
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Users
|
||||
class CreditCardValidation < MainClusterwide::ApplicationRecord
|
||||
class CreditCardValidation < ApplicationRecord
|
||||
include IgnorableColumns
|
||||
|
||||
RELEASE_DAY = Date.new(2021, 5, 17)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Users
|
||||
class PhoneNumberValidation < MainClusterwide::ApplicationRecord
|
||||
class PhoneNumberValidation < ApplicationRecord
|
||||
include IgnorableColumns
|
||||
|
||||
# SMS send attempts subsequent to the first one will have wait times of 1
|
||||
|
|
|
|||
|
|
@ -1,91 +0,0 @@
|
|||
# Defaults
|
||||
|
||||
defaults: &defaults
|
||||
adapter: postgresql
|
||||
encoding: unicode
|
||||
host: localhost
|
||||
username: git
|
||||
password: "secure password"
|
||||
|
||||
#
|
||||
# PRODUCTION
|
||||
#
|
||||
production:
|
||||
main: &main
|
||||
<<: *defaults
|
||||
database: gitlabhq_production
|
||||
ci:
|
||||
<<: *defaults
|
||||
database: gitlabhq_production_ci
|
||||
main_clusterwide:
|
||||
<: *main
|
||||
database_tasks: false
|
||||
geo:
|
||||
<<: *defaults
|
||||
database: gitlabhq_geo_production
|
||||
|
||||
#
|
||||
# Development specific
|
||||
#
|
||||
development:
|
||||
main: &main
|
||||
<<: *defaults
|
||||
database: gitlabhq_development
|
||||
username: postgres
|
||||
variables:
|
||||
statement_timeout: 15s
|
||||
ci:
|
||||
<<: *defaults
|
||||
database: gitlabhq_development_ci
|
||||
username: postgres
|
||||
variables:
|
||||
statement_timeout: 15s
|
||||
main_clusterwide:
|
||||
<<: *main
|
||||
database_tasks: false
|
||||
variables:
|
||||
statement_timeout: 15s
|
||||
geo:
|
||||
<<: *defaults
|
||||
database: gitlabhq_geo_development
|
||||
username: postgres
|
||||
|
||||
# Warning: The database defined as "test" will be erased and
|
||||
# re-generated from your development database when you run "rake".
|
||||
# Do not set this db to the same as development or production.
|
||||
test:
|
||||
main: &main
|
||||
<<: *defaults
|
||||
database: gitlabhq_test
|
||||
username: postgres
|
||||
password:
|
||||
prepared_statements: false
|
||||
reaping_frequency: nil
|
||||
variables:
|
||||
statement_timeout: 15s
|
||||
ci:
|
||||
<<: *defaults
|
||||
database: gitlabhq_test_ci
|
||||
username: postgres
|
||||
password:
|
||||
prepared_statements: false
|
||||
reaping_frequency: nil
|
||||
variables:
|
||||
statement_timeout: 15s
|
||||
main_clusterwide:
|
||||
<<: *main
|
||||
database_tasks: false
|
||||
reaping_frequency: nil
|
||||
geo:
|
||||
<<: *defaults
|
||||
database: gitlabhq_geo_test
|
||||
username: postgres
|
||||
password:
|
||||
reaping_frequency: nil
|
||||
embedding:
|
||||
<<: *defaults
|
||||
database: gitlabhq_embedding_test
|
||||
username: postgres
|
||||
password:
|
||||
reaping_frequency: nil
|
||||
|
||||
|
|
@ -145,7 +145,6 @@ InitializerConnections.raise_if_new_database_connection do
|
|||
scope :ide, as: :ide, format: false do
|
||||
get '/', to: 'ide#index'
|
||||
get '/project', to: 'ide#index'
|
||||
# note: This path has a hardcoded reference in the FE `app/assets/javascripts/ide/constants.js`
|
||||
get '/oauth_redirect', to: 'ide#oauth_redirect'
|
||||
|
||||
scope path: 'project/:project_id', as: :project, constraints: { project_id: Gitlab::PathRegex.full_namespace_route_regex } do
|
||||
|
|
|
|||
|
|
@ -6,11 +6,12 @@ gitlab_schemas:
|
|||
- gitlab_main
|
||||
- gitlab_main_cell
|
||||
- gitlab_pm
|
||||
- gitlab_main_clusterwide
|
||||
lock_gitlab_schemas:
|
||||
- gitlab_ci
|
||||
- gitlab_sec
|
||||
# Note that we use ActiveRecord::Base here and not ApplicationRecord.
|
||||
# This is deliberate, as:
|
||||
# This is deliberate, as:
|
||||
# - the load balancer must be enabled for _all_ models
|
||||
# - other models outside of Rails that are using `ActiveRecord::Base`
|
||||
# needs to use `main:`
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
name: main_clusterwide
|
||||
description: Cluster-wide GitLab database holding only data shared globally across Cells.
|
||||
gitlab_schemas:
|
||||
- gitlab_internal
|
||||
- gitlab_shared
|
||||
- gitlab_main_clusterwide
|
||||
klass: MainClusterwide::ApplicationRecord
|
||||
# if Cluster-wide database is not configured, fallback to using main
|
||||
fallback_database: main
|
||||
uses_load_balancing: true
|
||||
|
|
@ -42,7 +42,6 @@ More schemas to be introduced with additional decomposed databases
|
|||
The usage of schema enforces the base class to be used:
|
||||
|
||||
- `ApplicationRecord` for `gitlab_main`/`gitlab_main_cell.`
|
||||
- `MainClusterwide::ApplicationRecord` for `gitlab_main_clusterwide`.
|
||||
- `Ci::ApplicationRecord` for `gitlab_ci`
|
||||
- `Geo::TrackingBase` for `gitlab_geo`
|
||||
- `Gitlab::Database::SharedModel` for `gitlab_shared`
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ For authentication CI/CD variables, see [Authentication](authentication.md).
|
|||
| `DAST_AUTH_FIRST_SUBMIT_FIELD` | [selector](authentication.md#finding-an-elements-selector) | `css:input[type=submit]` | A selector describing the element that is clicked on to submit the username form of a multi-page login process. |
|
||||
| `DAST_AUTH_PASSWORD_FIELD` | [selector](authentication.md#finding-an-elements-selector) | `name:password` | A selector describing the element used to enter the password on the login form. |
|
||||
| `DAST_AUTH_PASSWORD` | string | `P@55w0rd!` | The password to authenticate to in the website. |
|
||||
| `DAST_AUTH_REPORT` | boolean | `true` | Set to `true` to generate a report detailing steps taken during the authentication process. You must also define `gl-dast-debug-auth-report.html` as a CI job artifact to be able to access the generated report. The report's content aids when debugging authentication failures. |
|
||||
| `DAST_AUTH_REPORT` | boolean | `true` | Set to `true` to generate a report detailing steps taken during the authentication process. You must also define `gl-dast-debug-auth-report.html` as a CI job artifact to be able to access the generated report. The report's content aids when debugging authentication failures. Defaults to `false`. |
|
||||
| `DAST_AUTH_SUBMIT_FIELD` | [selector](authentication.md#finding-an-elements-selector) | `css:input[type=submit]` | A selector describing the element clicked on to submit the login form for a single-page login form, or the password form for a multi-page login form. |
|
||||
| `DAST_AUTH_SUCCESS_IF_AT_URL` | URL | `https://www.site.com/welcome` | A URL that is compared to the URL in the browser to determine if authentication has succeeded after the login form is submitted. |
|
||||
| `DAST_AUTH_SUCCESS_IF_ELEMENT_FOUND` | [selector](authentication.md#finding-an-elements-selector) | `css:.user-avatar` | A selector describing an element whose presence is used to determine if authentication has succeeded after the login form is submitted. |
|
||||
|
|
@ -34,31 +34,31 @@ For authentication CI/CD variables, see [Authentication](authentication.md).
|
|||
| `DAST_BROWSER_SCAN` | boolean | `true` | Required to be `true` to run a browser-based scan. |
|
||||
| `DAST_CHECKS_TO_EXCLUDE` | string | `552.2,78.1` | Comma-separated list of check identifiers to exclude from the scan. For identifiers, see [vulnerability checks](../checks/index.md). |
|
||||
| `DAST_CHECKS_TO_RUN` | List of strings | `16.1,16.2,16.3` | Comma-separated list of check identifiers to use for the scan. For identifiers, see [vulnerability checks](../checks/index.md). |
|
||||
| `DAST_CRAWL_EXTRACT_ELEMENT_TIMEOUT` | [Duration string](https://pkg.go.dev/time#ParseDuration) | `5s` | The maximum amount of time to allow the browser to extract newly found elements or navigations. |
|
||||
| `DAST_CRAWL_GRAPH` | boolean | `true` | Set to `true` to generate an SVG graph of navigation paths visited during crawl phase of the scan. You must also define `gl-dast-crawl-graph.svg` as a CI job artifact to be able to access the generated graph. |
|
||||
| `DAST_CRAWL_MAX_ACTIONS` | number | `10000` | The maximum number of actions that the crawler performs. Example actions include selecting a link, or filling a form. |
|
||||
| `DAST_CRAWL_MAX_DEPTH` | number | `10` | The maximum number of chained actions that the crawler takes. For example, `Click -> Form Fill -> Click` is a depth of three. |
|
||||
| `DAST_CRAWL_SEARCH_ELEMENT_TIMEOUT` | [Duration string](https://pkg.go.dev/time#ParseDuration) | `3s` | The maximum amount of time to allow the browser to search for new elements or user actions. |
|
||||
| `DAST_CRAWL_EXTRACT_ELEMENT_TIMEOUT` | [Duration string](https://pkg.go.dev/time#ParseDuration) | `5s` | The maximum amount of time to allow the browser to extract newly found elements or navigations. Defaults to `5s`. |
|
||||
| `DAST_CRAWL_GRAPH` | boolean | `true` | Set to `true` to generate an SVG graph of navigation paths visited during crawl phase of the scan. You must also define `gl-dast-crawl-graph.svg` as a CI job artifact to be able to access the generated graph. Defaults to `false`. |
|
||||
| `DAST_CRAWL_MAX_ACTIONS` | number | `10000` | The maximum number of actions that the crawler performs. Example actions include selecting a link, or filling a form. Defaults to `10000`. |
|
||||
| `DAST_CRAWL_MAX_DEPTH` | number | `10` | The maximum number of chained actions that the crawler takes. For example, `Click -> Form Fill -> Click` is a depth of three. Defaults to `10`. |
|
||||
| `DAST_CRAWL_SEARCH_ELEMENT_TIMEOUT` | [Duration string](https://pkg.go.dev/time#ParseDuration) | `3s` | The maximum amount of time to allow the browser to search for new elements or user actions. Defaults to `3s`. |
|
||||
| `DAST_CRAWL_TIMEOUT` | [Duration string](https://pkg.go.dev/time#ParseDuration) | `5m` | The maximum amount of time to wait for the crawl phase of the scan to complete. Defaults to `24h`. |
|
||||
| `DAST_CRAWL_WORKER_COUNT` | number | `3` | The maximum number of concurrent browser instances to use. For instance runners on GitLab.com, we recommended a maximum of three. Private runners with more resources may benefit from a higher number, but are likely to produce little benefit after five to seven instances. |
|
||||
| `DAST_CRAWL_WORKER_COUNT` | number | `3` | The maximum number of concurrent browser instances to use. For instance runners on GitLab.com, we recommended a maximum of three. Private runners with more resources may benefit from a higher number, but are likely to produce little benefit after five to seven instances. The default value is dynamic, equal to the number of usable logical CPUs. |
|
||||
| `DAST_FULL_SCAN` | boolean | `true` | Set to `true` to run both passive and active checks. Default: `false` |
|
||||
| `DAST_LOG_BROWSER_OUTPUT` | boolean | `true` | Set to `true` to log Chromium `STDOUT` and `STDERR`. |
|
||||
| `DAST_LOG_CONFIG` | List of strings | `brows:debug,auth:debug` | A list of modules and their intended logging level for use in the console log. |
|
||||
| `DAST_LOG_DEVTOOLS_CONFIG` | string | `Default:messageAndBody,truncate:2000` | Set to log protocol messages between DAST and the Chromium browser. |
|
||||
| `DAST_LOG_FILE_CONFIG` | List of strings | `brows:debug,auth:debug` | A list of modules and their intended logging level for use in the file log. |
|
||||
| `DAST_LOG_FILE_PATH` | string | `/output/browserker.log` | Set to the path of the file log. |
|
||||
| `DAST_PAGE_DOM_READY_TIMEOUT` | [Duration string](https://pkg.go.dev/time#ParseDuration) | `7s` | The maximum amount of time to wait for a browser to consider a page loaded and ready for analysis after a navigation completes. Defaults to `800ms`. |
|
||||
| `DAST_LOG_FILE_PATH` | string | `/output/browserker.log` | Set to the path of the file log. Default is `gl-dast-scan.log` |
|
||||
| `DAST_PAGE_DOM_READY_TIMEOUT` | [Duration string](https://pkg.go.dev/time#ParseDuration) | `7s` | The maximum amount of time to wait for a browser to consider a page loaded and ready for analysis after a navigation completes. Defaults to `6s`. |
|
||||
| `DAST_PAGE_DOM_STABLE_WAIT` | [Duration string](https://pkg.go.dev/time#ParseDuration) | `200ms` | Define how long to wait for updates to the DOM before checking a page is stable. Defaults to `500ms`. |
|
||||
| `DAST_PAGE_ELEMENT_READY_TIMEOUT` | [Duration string](https://pkg.go.dev/time#ParseDuration) | `600ms` | The maximum amount of time to wait for an element before determining it is ready for analysis. |
|
||||
| `DAST_PAGE_ELEMENT_READY_TIMEOUT` | [Duration string](https://pkg.go.dev/time#ParseDuration) | `600ms` | The maximum amount of time to wait for an element before determining it is ready for analysis. Defaults to `300ms`. |
|
||||
| `DAST_PAGE_IS_LOADING_ELEMENT` | selector | `css:#page-is-loading` | Selector that when is no longer visible on the page, indicates to the analyzer that the page has finished loading and the scan can continue. Cannot be used with `DAST_PAGE_IS_READY_ELEMENT`. |
|
||||
| `DAST_PAGE_IS_READY_ELEMENT` | selector | `css:#page-is-ready` | Selector that when detected as visible on the page, indicates to the analyzer that the page has finished loading and the scan can continue. Cannot be used with `DAST_PAGE_IS_LOADING_ELEMENT`. |
|
||||
| `DAST_PAGE_MAX_RESPONSE_SIZE_MB` | number | `15` | The maximum size of a HTTP response body. Responses with bodies larger than this are blocked by the browser. Defaults to 10 MB. |
|
||||
| `DAST_PAGE_READY_AFTER_ACTION_TIMEOUT` | [Duration string](https://pkg.go.dev/time#ParseDuration) | `7s` | The maximum amount of time to wait for a browser to consider a page loaded and ready for analysis. |
|
||||
| `DAST_PAGE_READY_AFTER_NAVIGATION_TIMEOUT` | [Duration string](https://pkg.go.dev/time#ParseDuration) | `15s` | The maximum amount of time to wait for a browser to navigate from one page to another. |
|
||||
| `DAST_PAGE_MAX_RESPONSE_SIZE_MB` | number | `15` | The maximum size of a HTTP response body. Responses with bodies larger than this are blocked by the browser. Defaults to `10` MB. |
|
||||
| `DAST_PAGE_READY_AFTER_ACTION_TIMEOUT` | [Duration string](https://pkg.go.dev/time#ParseDuration) | `7s` | The maximum amount of time to wait for a browser to consider a page loaded and ready for analysis. Defaults to `7s`. |
|
||||
| `DAST_PAGE_READY_AFTER_NAVIGATION_TIMEOUT` | [Duration string](https://pkg.go.dev/time#ParseDuration) | `15s` | The maximum amount of time to wait for a browser to navigate from one page to another. Defaults to `15s`. |
|
||||
| `DAST_PASSIVE_SCAN_WORKER_COUNT` | int | `5` | Number of workers that passive scan in parallel. Defaults to the number of available CPUs. |
|
||||
| `DAST_PKCS12_CERTIFICATE_BASE64` | string | `ZGZkZ2p5NGd...` | The PKCS12 certificate used for sites that require Mutual TLS. Must be encoded as base64 text. |
|
||||
| `DAST_PKCS12_PASSWORD` | string | `password` | The password of the certificate used in `DAST_PKCS12_CERTIFICATE_BASE64`. Create sensitive [custom CI/CI variables](../../../../../ci/variables/index.md#define-a-cicd-variable-in-the-ui) using the GitLab UI. |
|
||||
| `DAST_REQUEST_ADVERTISE_SCAN` | boolean | `true` | Set to `true` to add a `Via` header to every request sent, advertising that the request was sent as part of a GitLab DAST scan. |
|
||||
| `DAST_REQUEST_ADVERTISE_SCAN` | boolean | `true` | Set to `true` to add a `Via` header to every request sent, advertising that the request was sent as part of a GitLab DAST scan. Default: `false`. |
|
||||
| `DAST_REQUEST_COOKIES` | dictionary | `abtesting_group:3,region:locked` | A cookie name and value to be added to every request. |
|
||||
| `DAST_REQUEST_HEADERS` | string | `Cache-control:no-cache` | Set to a comma-separated list of request header names and values. |
|
||||
| `DAST_SCOPE_ALLOW_HOSTS` | List of strings | `site.com,another.com` | Hostnames included in this variable are considered in scope when crawled. By default the `DAST_TARGET_URL` hostname is included in the allowed hosts list. Headers set using `DAST_REQUEST_HEADERS` are added to every request made to these hostnames. |
|
||||
|
|
@ -67,7 +67,7 @@ For authentication CI/CD variables, see [Authentication](authentication.md).
|
|||
| `DAST_SCOPE_EXCLUDE_URLS` | URLs | `https://site.com/.*/sign-out` | The URLs to skip during the authenticated scan; comma-separated. Regular expression syntax can be used to match multiple URLs. For example, `.*` matches an arbitrary character sequence. |
|
||||
| `DAST_SCOPE_IGNORE_HOSTS` | List of strings | `site.com,another.com` | Hostnames included in this variable are accessed, not attacked, and not reported against. |
|
||||
| `DAST_TARGET_CHECK_SKIP` | boolean | `true` | Set to `true` to prevent DAST from checking that the target is available before scanning. Default: `false`. |
|
||||
| `DAST_TARGET_CHECK_TIMEOUT` | number | `60` | Time limit in seconds to wait for target availability. |
|
||||
| `DAST_TARGET_CHECK_TIMEOUT` | number | `60` | Time limit in seconds to wait for target availability. Default: `60s`. |
|
||||
| `DAST_TARGET_PATHS_FILE` | string | `/builds/project/urls.txt` | Limit the paths scanned to a provided list. Set to a file path containing a list of URL paths relative to `DAST_TARGET_URL`. The file must be plain text with one path per line. |
|
||||
| `DAST_TARGET_PATHS` | string | `/page1.html,/category1/page3.html` | Limit the paths scanned to a provided list. Set to a comma-separated list of URL paths relative to `DAST_TARGET_URL`. |
|
||||
| `DAST_TARGET_URL` | URL | `https://site.com` | The URL of the website to scan. |
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ module Gitlab
|
|||
# Converts a credit card's expiration_date, last_digits, network & holder_name
|
||||
# to hash and store values in new columns
|
||||
class ConvertCreditCardValidationDataToHashes < BatchedMigrationJob
|
||||
COLUMN_NAMES = %w[last_digits holder_name network expiration_date].freeze
|
||||
|
||||
operation_name :convert_credit_card_data
|
||||
feature_category :user_profile
|
||||
|
||||
|
|
@ -13,6 +15,8 @@ module Gitlab
|
|||
end
|
||||
|
||||
def perform
|
||||
return unless COLUMN_NAMES.all? { |column| CreditCardValidation.column_names.include?(column) }
|
||||
|
||||
each_sub_batch do |sub_batch|
|
||||
credit_cards = CreditCardValidation.where(user_id: sub_batch)
|
||||
|
||||
|
|
|
|||
|
|
@ -6111,9 +6111,6 @@ msgstr ""
|
|||
msgid "Analytics|An error occurred while loading data"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|An error occurred while loading the %{visualizationTitle} visualization."
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Analytics dashboards"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -6312,9 +6309,6 @@ msgstr ""
|
|||
msgid "Analytics|Start by choosing a measure"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Start by choosing a metric"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Statistics on namespace usage. Usage data is a cumulative count, and updated monthly."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -17337,6 +17331,9 @@ msgstr ""
|
|||
msgid "Dates"
|
||||
msgstr ""
|
||||
|
||||
msgid "Day"
|
||||
msgstr ""
|
||||
|
||||
msgid "Day of month"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -26819,15 +26816,15 @@ msgstr ""
|
|||
msgid "IDE|Contact your administrator or try to open the Web IDE again with another domain."
|
||||
msgstr ""
|
||||
|
||||
msgid "IDE|Could not find a callback URL entry for %{expectedCallbackUrl}."
|
||||
msgstr ""
|
||||
|
||||
msgid "IDE|Edit"
|
||||
msgstr ""
|
||||
|
||||
msgid "IDE|Editing this application might affect the functionality of the Web IDE. Ensure the configuration meets the following conditions:"
|
||||
msgstr ""
|
||||
|
||||
msgid "IDE|Error reloading page"
|
||||
msgstr ""
|
||||
|
||||
msgid "IDE|GitLab logo"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -33650,6 +33647,9 @@ msgstr ""
|
|||
msgid "Minimum role required to cancel a pipeline or job"
|
||||
msgstr ""
|
||||
|
||||
msgid "Minute"
|
||||
msgstr ""
|
||||
|
||||
msgid "Minutes"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -43680,6 +43680,9 @@ msgstr ""
|
|||
msgid "QualitySummary|Project quality"
|
||||
msgstr ""
|
||||
|
||||
msgid "Quarter"
|
||||
msgstr ""
|
||||
|
||||
msgid "Queued"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -47490,6 +47493,9 @@ msgstr ""
|
|||
msgid "Seats owed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Second"
|
||||
msgstr ""
|
||||
|
||||
msgid "Secondary email:"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -59968,6 +59974,9 @@ msgstr ""
|
|||
msgid "Wednesday"
|
||||
msgstr ""
|
||||
|
||||
msgid "Week"
|
||||
msgstr ""
|
||||
|
||||
msgid "Weekday"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -61207,6 +61216,9 @@ msgstr ""
|
|||
msgid "YYYY-MM-DD"
|
||||
msgstr ""
|
||||
|
||||
msgid "Year"
|
||||
msgstr ""
|
||||
|
||||
msgid "Yes"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -63,7 +63,6 @@
|
|||
"@babel/preset-env": "^7.23.7",
|
||||
"@csstools/postcss-global-data": "^2.1.1",
|
||||
"@cubejs-client/core": "^0.35.23",
|
||||
"@cubejs-client/vue": "^0.35.23",
|
||||
"@floating-ui/dom": "^1.2.9",
|
||||
"@gitlab/application-sdk-browser": "^0.3.3",
|
||||
"@gitlab/at.js": "1.5.7",
|
||||
|
|
|
|||
|
|
@ -228,6 +228,7 @@ module QA
|
|||
product_group: example.metadata[:product_group],
|
||||
testcase: example.metadata[:testcase],
|
||||
exception_class: example.execution_result.exception&.class&.to_s,
|
||||
branch: branch,
|
||||
**custom_metrics_tags(example.metadata)
|
||||
}.compact
|
||||
end
|
||||
|
|
@ -247,7 +248,7 @@ module QA
|
|||
ui_fabrication: ui_fabrication,
|
||||
total_fabrication: api_fabrication + ui_fabrication,
|
||||
job_url: ci_job_url,
|
||||
pipeline_url: env('CI_PIPELINE_URL'),
|
||||
pipeline_url: ci_pipeline_url,
|
||||
pipeline_id: env('CI_PIPELINE_ID'),
|
||||
job_id: env('CI_JOB_ID'),
|
||||
merge_request_iid: merge_request_iid,
|
||||
|
|
@ -276,14 +277,16 @@ module QA
|
|||
fabrication_method: fabrication_method,
|
||||
http_method: http_method,
|
||||
run_type: run_type,
|
||||
merge_request: merge_request
|
||||
},
|
||||
merge_request: merge_request,
|
||||
branch: branch
|
||||
}.compact,
|
||||
fields: {
|
||||
fabrication_time: fabrication_time,
|
||||
info: info,
|
||||
job_url: ci_job_url,
|
||||
pipeline_url: ci_pipeline_url,
|
||||
timestamp: timestamp
|
||||
}
|
||||
}.compact
|
||||
}
|
||||
end
|
||||
|
||||
|
|
@ -300,12 +303,13 @@ module QA
|
|||
method: name,
|
||||
call_arg: p[:call_arg],
|
||||
run_type: run_type,
|
||||
merge_request: merge_request
|
||||
merge_request: merge_request,
|
||||
branch: branch
|
||||
}.compact,
|
||||
fields: {
|
||||
runtime: (p[:runtime] * 1000).round,
|
||||
job_url: ci_job_url,
|
||||
pipeline_url: env('CI_PIPELINE_URL'),
|
||||
pipeline_url: ci_pipeline_url,
|
||||
filename: p[:filename]
|
||||
}.compact
|
||||
}
|
||||
|
|
@ -334,6 +338,20 @@ module QA
|
|||
(!!merge_request_iid).to_s
|
||||
end
|
||||
|
||||
# Pipeline url
|
||||
#
|
||||
# @return [String]
|
||||
def ci_pipeline_url
|
||||
@ci_pipeline_url ||= env('CI_PIPELINE_URL')
|
||||
end
|
||||
|
||||
# Branch name
|
||||
#
|
||||
# @return [String]
|
||||
def branch
|
||||
@branch ||= env('CI_COMMIT_REF_NAME')
|
||||
end
|
||||
|
||||
# Is spec quarantined
|
||||
#
|
||||
# @param [RSpec::Core::Example] example
|
||||
|
|
|
|||
|
|
@ -25,10 +25,11 @@ describe QA::Support::Formatters::TestMetricsFormatter do
|
|||
let(:gcs_client) { double("Fog::Storage::GoogleJSON::Real", put_object: nil) } # rubocop:disable RSpec/VerifiedDoubles -- instance_double complains put_object is not implemented but it is
|
||||
let(:ci_timestamp) { '2021-02-23T20:58:41Z' }
|
||||
let(:ci_job_name) { 'test-job 1/5' }
|
||||
let(:ci_job_url) { 'url' }
|
||||
let(:ci_pipeline_url) { 'url' }
|
||||
let(:ci_job_url) { 'job-url' }
|
||||
let(:ci_pipeline_url) { 'pipeline-url' }
|
||||
let(:ci_pipeline_id) { '123' }
|
||||
let(:ci_job_id) { '321' }
|
||||
let(:branch) { 'master' }
|
||||
let(:run_type) { 'staging-full' }
|
||||
let(:smoke) { 'false' }
|
||||
let(:blocking) { 'false' }
|
||||
|
|
@ -71,7 +72,8 @@ describe QA::Support::Formatters::TestMetricsFormatter do
|
|||
merge_request: 'false',
|
||||
run_type: run_type,
|
||||
stage: 'manage',
|
||||
testcase: testcase
|
||||
testcase: testcase,
|
||||
branch: branch
|
||||
},
|
||||
fields: {
|
||||
id: './spec/support/formatters/test_metrics_formatter_spec.rb[1:1]',
|
||||
|
|
@ -186,6 +188,7 @@ describe QA::Support::Formatters::TestMetricsFormatter do
|
|||
stub_env('CI_PIPELINE_ID', ci_pipeline_id)
|
||||
stub_env('CI_JOB_ID', ci_job_id)
|
||||
stub_env('CI_MERGE_REQUEST_IID', nil)
|
||||
stub_env('CI_COMMIT_REF_NAME', branch)
|
||||
stub_env('TOP_UPSTREAM_MERGE_REQUEST_IID', nil)
|
||||
stub_env('QA_EXPORT_TEST_METRICS', "true")
|
||||
stub_env('QA_RSPEC_RETRIED', "false")
|
||||
|
|
@ -422,12 +425,14 @@ describe QA::Support::Formatters::TestMetricsFormatter do
|
|||
fabrication_method: :api,
|
||||
http_method: :post,
|
||||
run_type: run_type,
|
||||
merge_request: "false"
|
||||
merge_request: "false",
|
||||
branch: branch
|
||||
},
|
||||
fields: {
|
||||
fabrication_time: 1,
|
||||
info: "with id '1'",
|
||||
job_url: ci_job_url,
|
||||
pipeline_url: ci_pipeline_url,
|
||||
timestamp: Time.now.to_s
|
||||
}
|
||||
}
|
||||
|
|
@ -506,12 +511,14 @@ describe QA::Support::Formatters::TestMetricsFormatter do
|
|||
fabrication_method: :api,
|
||||
http_method: :post,
|
||||
run_type: run_type,
|
||||
merge_request: "false"
|
||||
merge_request: "false",
|
||||
branch: branch
|
||||
},
|
||||
fields: {
|
||||
fabrication_time: 1,
|
||||
info: "with id '1'",
|
||||
job_url: ci_job_url,
|
||||
pipeline_url: ci_pipeline_url,
|
||||
timestamp: Time.now.to_s
|
||||
}
|
||||
}
|
||||
|
|
@ -547,6 +554,7 @@ describe QA::Support::Formatters::TestMetricsFormatter do
|
|||
|
||||
context "with code runtime metrics" do
|
||||
let(:time) { DateTime.strptime(ci_timestamp).to_time }
|
||||
|
||||
let(:method_call_data) do
|
||||
{
|
||||
"has_element?" => [{ runtime: 1, filename: "file.rb", call_arg: "element_for_has" }],
|
||||
|
|
@ -554,19 +562,27 @@ describe QA::Support::Formatters::TestMetricsFormatter do
|
|||
}
|
||||
end
|
||||
|
||||
let(:expected_fields) do
|
||||
{ job_url: ci_job_url, pipeline_url: ci_pipeline_url, runtime: 1000, filename: "file.rb" }
|
||||
end
|
||||
|
||||
let(:expected_tags) do
|
||||
{ run_type: run_type, merge_request: "false", branch: branch }
|
||||
end
|
||||
|
||||
it "exports code runtime metrics to influxdb" do
|
||||
run_spec
|
||||
|
||||
expect(influx_write_api).to have_received(:write).with(data: [
|
||||
{
|
||||
name: "method-call-stats", time: time,
|
||||
tags: { method: "has_element?", call_arg: "element_for_has", run_type: run_type, merge_request: "false" },
|
||||
fields: { job_url: ci_job_url, pipeline_url: ci_pipeline_url, runtime: 1000, filename: "file.rb" }
|
||||
tags: { method: "has_element?", call_arg: "element_for_has", **expected_tags },
|
||||
fields: expected_fields
|
||||
},
|
||||
{
|
||||
name: "method-call-stats", time: time,
|
||||
tags: { method: "click", call_arg: "element_for_click", run_type: run_type, merge_request: "false" },
|
||||
fields: { job_url: ci_job_url, pipeline_url: ci_pipeline_url, runtime: 1000, filename: "file.rb" }
|
||||
tags: { method: "click", call_arg: "element_for_click", **expected_tags },
|
||||
fields: expected_fields
|
||||
}
|
||||
])
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,111 +1,87 @@
|
|||
import { GlButton, GlDisclosureDropdown } from '@gitlab/ui';
|
||||
import { GlButton, GlCollapsibleListbox, GlListboxItem } from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import setWindowLocation from 'helpers/set_window_location_helper';
|
||||
import { TEST_HOST } from 'helpers/test_constants';
|
||||
import { nextTick } from 'vue';
|
||||
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
|
||||
import OAuthDomainMismatchError from '~/ide/components/oauth_domain_mismatch_error.vue';
|
||||
|
||||
const MOCK_CALLBACK_URLS = [
|
||||
{
|
||||
base: 'https://example1.com/',
|
||||
},
|
||||
{
|
||||
base: 'https://example2.com/',
|
||||
},
|
||||
{
|
||||
base: 'https://example3.com/relative-path/',
|
||||
},
|
||||
];
|
||||
const MOCK_CALLBACK_URL = 'https://example.com';
|
||||
const MOCK_PATH_NAME = 'path/to/ide';
|
||||
|
||||
const EXPECTED_DROPDOWN_ITEMS = MOCK_CALLBACK_URLS.map(({ base }) => ({
|
||||
text: base,
|
||||
href: `${base}${MOCK_PATH_NAME}`,
|
||||
}));
|
||||
const MOCK_CALLBACK_URL_ORIGIN = 'https://example1.com';
|
||||
const MOCK_PATH_NAME = '/path/to/ide';
|
||||
|
||||
describe('OAuthDomainMismatchError', () => {
|
||||
useMockLocationHelper();
|
||||
|
||||
let wrapper;
|
||||
let originalLocation;
|
||||
|
||||
const findButton = () => wrapper.findComponent(GlButton);
|
||||
const findDropdown = () => wrapper.findComponent(GlDisclosureDropdown);
|
||||
const findDropdown = () => wrapper.findComponent(GlCollapsibleListbox);
|
||||
const findDropdownItems = () => wrapper.findAllComponents(GlListboxItem);
|
||||
|
||||
const createWrapper = (props = {}) => {
|
||||
wrapper = mount(OAuthDomainMismatchError, {
|
||||
propsData: {
|
||||
expectedCallbackUrl: MOCK_CALLBACK_URL,
|
||||
callbackUrls: MOCK_CALLBACK_URLS,
|
||||
callbackUrlOrigins: [MOCK_CALLBACK_URL_ORIGIN],
|
||||
...props,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
setWindowLocation(`/${MOCK_PATH_NAME}`);
|
||||
originalLocation = window.location;
|
||||
window.location.pathname = MOCK_PATH_NAME;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.location = originalLocation;
|
||||
});
|
||||
|
||||
describe('single callback URL domain passed', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper({
|
||||
callbackUrls: MOCK_CALLBACK_URLS.slice(0, 1),
|
||||
});
|
||||
});
|
||||
|
||||
it('renders expected callback URL message', () => {
|
||||
expect(wrapper.text()).toContain(
|
||||
`Could not find a callback URL entry for ${MOCK_CALLBACK_URL}.`,
|
||||
);
|
||||
createWrapper();
|
||||
});
|
||||
|
||||
it('does not render dropdown', () => {
|
||||
expect(findDropdown().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders button with correct attributes', () => {
|
||||
const button = findButton();
|
||||
expect(button.exists()).toBe(true);
|
||||
const baseUrl = MOCK_CALLBACK_URLS[0].base;
|
||||
expect(button.text()).toContain(baseUrl);
|
||||
expect(button.attributes('href')).toBe(`${baseUrl}${MOCK_PATH_NAME}`);
|
||||
it('reloads page with correct url on button click', async () => {
|
||||
findButton().vm.$emit('click');
|
||||
await nextTick();
|
||||
|
||||
expect(window.location.replace).toHaveBeenCalledTimes(1);
|
||||
expect(window.location.replace).toHaveBeenCalledWith(
|
||||
new URL(MOCK_CALLBACK_URL_ORIGIN + MOCK_PATH_NAME).toString(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('multiple callback URL domains passed', () => {
|
||||
const MOCK_CALLBACK_URL_ORIGINS = [MOCK_CALLBACK_URL_ORIGIN, 'https://example2.com'];
|
||||
|
||||
beforeEach(() => {
|
||||
createWrapper();
|
||||
createWrapper({ callbackUrlOrigins: MOCK_CALLBACK_URL_ORIGINS });
|
||||
});
|
||||
|
||||
it('renders dropdown with correct items', () => {
|
||||
const dropdown = findDropdown();
|
||||
|
||||
expect(dropdown.exists()).toBe(true);
|
||||
expect(dropdown.props('items')).toStrictEqual(EXPECTED_DROPDOWN_ITEMS);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with erroneous callback from current origin', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper({
|
||||
callbackUrls: MOCK_CALLBACK_URLS.concat({
|
||||
base: `${TEST_HOST}/foo`,
|
||||
}),
|
||||
});
|
||||
it('renders dropdown', () => {
|
||||
expect(findDropdown().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('filters out item with current origin', () => {
|
||||
expect(findDropdown().props('items')).toStrictEqual(EXPECTED_DROPDOWN_ITEMS);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when no callback URL passed', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper({
|
||||
callbackUrls: [],
|
||||
});
|
||||
it('renders dropdown items', () => {
|
||||
const dropdownItems = findDropdownItems();
|
||||
expect(dropdownItems.length).toBe(MOCK_CALLBACK_URL_ORIGINS.length);
|
||||
expect(dropdownItems.at(0).text()).toBe(MOCK_CALLBACK_URL_ORIGINS[0]);
|
||||
expect(dropdownItems.at(1).text()).toBe(MOCK_CALLBACK_URL_ORIGINS[1]);
|
||||
});
|
||||
|
||||
it('does not render dropdown or button', () => {
|
||||
expect(findDropdown().exists()).toBe(false);
|
||||
expect(findButton().exists()).toBe(false);
|
||||
it('reloads page with correct url on dropdown item click', async () => {
|
||||
const dropdownItem = findDropdownItems().at(0);
|
||||
dropdownItem.vm.$emit('select', MOCK_CALLBACK_URL_ORIGIN);
|
||||
await nextTick();
|
||||
|
||||
expect(window.location.replace).toHaveBeenCalledTimes(1);
|
||||
expect(window.location.replace).toHaveBeenCalledWith(
|
||||
new URL(MOCK_CALLBACK_URL_ORIGIN + MOCK_PATH_NAME).toString(),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import * as pathUtils from 'path';
|
||||
import { WEB_IDE_OAUTH_CALLBACK_URL_PATH, commitActionTypes } from '~/ide/constants';
|
||||
import { commitActionTypes } from '~/ide/constants';
|
||||
import { decorateData } from '~/ide/stores/utils';
|
||||
import { WEB_IDE_OAUTH_CALLBACK_URL_PATH } from '~/ide/lib/gitlab_web_ide/get_oauth_config';
|
||||
|
||||
export const file = (name = 'name', id = name, type = '', parent = null) =>
|
||||
decorateData({
|
||||
|
|
|
|||
|
|
@ -2,14 +2,12 @@ import { startIde } from '~/ide/index';
|
|||
import { IDE_ELEMENT_ID } from '~/ide/constants';
|
||||
import { OAuthCallbackDomainMismatchErrorApp } from '~/ide/oauth_callback_domain_mismatch_error';
|
||||
import { initGitlabWebIDE } from '~/ide/init_gitlab_web_ide';
|
||||
import setWindowLocation from 'helpers/set_window_location_helper';
|
||||
import { TEST_HOST } from 'helpers/test_constants';
|
||||
|
||||
jest.mock('~/ide/init_gitlab_web_ide');
|
||||
|
||||
const MOCK_MISMATCH_CALLBACK_URL = 'https://example.com/ide/redirect';
|
||||
const MOCK_CALLBACK_URL = `${window.location.origin}/ide/redirect`;
|
||||
const MOCK_DATA_SET = {
|
||||
callbackUrls: JSON.stringify([`${TEST_HOST}/-/ide/oauth_redirect`]),
|
||||
callbackUrls: JSON.stringify([MOCK_CALLBACK_URL]),
|
||||
useNewWebIde: true,
|
||||
};
|
||||
/**
|
||||
|
|
@ -29,20 +27,12 @@ const setupMockIdeElement = (customData = MOCK_DATA_SET) => {
|
|||
};
|
||||
|
||||
describe('startIde', () => {
|
||||
let renderErrorSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
setWindowLocation(`${TEST_HOST}/-/ide/edit/gitlab-org/gitlab`);
|
||||
renderErrorSpy = jest.spyOn(OAuthCallbackDomainMismatchErrorApp.prototype, 'renderError');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.getElementById(IDE_ELEMENT_ID)?.remove();
|
||||
document.getElementById(IDE_ELEMENT_ID).remove();
|
||||
});
|
||||
|
||||
describe('when useNewWebIde feature flag is true', () => {
|
||||
let ideElement;
|
||||
|
||||
beforeEach(async () => {
|
||||
ideElement = setupMockIdeElement();
|
||||
|
||||
|
|
@ -53,14 +43,34 @@ describe('startIde', () => {
|
|||
expect(initGitlabWebIDE).toHaveBeenCalledTimes(1);
|
||||
expect(initGitlabWebIDE).toHaveBeenCalledWith(ideElement);
|
||||
});
|
||||
|
||||
it('does not render error page', () => {
|
||||
expect(renderErrorSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with mismatch callback url', () => {
|
||||
it('renders error page', async () => {
|
||||
describe('OAuth callback origin mismatch check', () => {
|
||||
let renderErrorSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
renderErrorSpy = jest.spyOn(OAuthCallbackDomainMismatchErrorApp.prototype, 'renderError');
|
||||
});
|
||||
|
||||
it('does not render error page if no callbackUrl provided', async () => {
|
||||
setupMockIdeElement({ useNewWebIde: true });
|
||||
await startIde();
|
||||
|
||||
expect(renderErrorSpy).not.toHaveBeenCalled();
|
||||
expect(initGitlabWebIDE).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('does not call renderOAuthDomainMismatchError if no mismatch detected', async () => {
|
||||
setupMockIdeElement();
|
||||
await startIde();
|
||||
|
||||
expect(renderErrorSpy).not.toHaveBeenCalled();
|
||||
expect(initGitlabWebIDE).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('renders error page if OAuth callback origin does not match window.location.origin', async () => {
|
||||
const MOCK_MISMATCH_CALLBACK_URL = 'https://example.com/ide/redirect';
|
||||
renderErrorSpy.mockImplementation(() => {});
|
||||
setupMockIdeElement({
|
||||
callbackUrls: JSON.stringify([MOCK_MISMATCH_CALLBACK_URL]),
|
||||
useNewWebIde: true,
|
||||
|
|
@ -72,17 +82,4 @@ describe('startIde', () => {
|
|||
expect(initGitlabWebIDE).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with relative URL location and mismatch callback url', () => {
|
||||
it('renders error page', async () => {
|
||||
setWindowLocation(`${TEST_HOST}/relative-path/-/ide/edit/project`);
|
||||
|
||||
setupMockIdeElement();
|
||||
|
||||
await startIde();
|
||||
|
||||
expect(renderErrorSpy).toHaveBeenCalledTimes(1);
|
||||
expect(initGitlabWebIDE).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ describe('ide/init_gitlab_web_ide', () => {
|
|||
mrTargetProject: '',
|
||||
forkInfo: null,
|
||||
username: gon.current_username,
|
||||
gitlabUrl: `${TEST_HOST}/`,
|
||||
gitlabUrl: TEST_HOST,
|
||||
nonce: TEST_NONCE,
|
||||
httpHeaders: {
|
||||
'mock-csrf-header': 'mock-csrf-token',
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ describe('~/ide/lib/gitlab_web_ide/get_base_config', () => {
|
|||
|
||||
expect(actual).toEqual({
|
||||
baseUrl: `${TEST_HOST}/${TEST_GITLAB_WEB_IDE_PUBLIC_PATH}`,
|
||||
gitlabUrl: `${TEST_HOST}/`,
|
||||
gitlabUrl: TEST_HOST,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ describe('~/ide/lib/gitlab_web_ide/get_base_config', () => {
|
|||
|
||||
expect(actual).toEqual({
|
||||
baseUrl: `${TEST_HOST}${TEST_RELATIVE_URL_ROOT}/${TEST_GITLAB_WEB_IDE_PUBLIC_PATH}`,
|
||||
gitlabUrl: `${TEST_HOST}${TEST_RELATIVE_URL_ROOT}/`,
|
||||
gitlabUrl: `${TEST_HOST}${TEST_RELATIVE_URL_ROOT}`,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,89 +0,0 @@
|
|||
import {
|
||||
parseCallbackUrls,
|
||||
getOAuthCallbackUrl,
|
||||
} from '~/ide/lib/gitlab_web_ide/oauth_callback_urls';
|
||||
import { logError } from '~/lib/logger';
|
||||
import { joinPaths } from '~/lib/utils/url_utility';
|
||||
import { IDE_PATH, WEB_IDE_OAUTH_CALLBACK_URL_PATH } from '~/ide/constants';
|
||||
import setWindowLocation from 'helpers/set_window_location_helper';
|
||||
|
||||
jest.mock('~/lib/logger');
|
||||
|
||||
const MOCK_IDE_PATH = joinPaths(IDE_PATH, 'some/path');
|
||||
|
||||
describe('ide/lib/oauth_callback_urls', () => {
|
||||
describe('getOAuthCallbackUrl', () => {
|
||||
const mockPath = MOCK_IDE_PATH;
|
||||
const MOCK_RELATIVE_PATH = 'relative-path';
|
||||
const mockPathWithRelative = joinPaths(MOCK_RELATIVE_PATH, MOCK_IDE_PATH);
|
||||
|
||||
const originalHref = window.location.href;
|
||||
|
||||
afterEach(() => {
|
||||
setWindowLocation(originalHref);
|
||||
});
|
||||
|
||||
const expectedBaseUrlWithRelative = joinPaths(window.location.origin, MOCK_RELATIVE_PATH);
|
||||
|
||||
it.each`
|
||||
path | expectedCallbackBaseUrl
|
||||
${mockPath} | ${window.location.origin}
|
||||
${mockPathWithRelative} | ${expectedBaseUrlWithRelative}
|
||||
`(
|
||||
'retrieves expected callback URL based on window url',
|
||||
({ path, expectedCallbackBaseUrl }) => {
|
||||
setWindowLocation(path);
|
||||
|
||||
const actual = getOAuthCallbackUrl();
|
||||
const expected = joinPaths(expectedCallbackBaseUrl, WEB_IDE_OAUTH_CALLBACK_URL_PATH);
|
||||
expect(actual).toEqual(expected);
|
||||
},
|
||||
);
|
||||
});
|
||||
describe('parseCallbackUrls', () => {
|
||||
it('parses the given JSON URL array and returns some metadata for them', () => {
|
||||
const actual = parseCallbackUrls(
|
||||
JSON.stringify([
|
||||
'https://gitlab.com/-/ide/oauth_redirect',
|
||||
'not a url',
|
||||
'https://gdk.test:3443/-/ide/oauth_redirect/',
|
||||
'https://gdk.test:3443/gitlab/-/ide/oauth_redirect#1234?query=foo',
|
||||
'https://example.com/not-a-real-one-/ide/oauth_redirectz',
|
||||
]),
|
||||
);
|
||||
|
||||
expect(actual).toEqual([
|
||||
{
|
||||
base: 'https://gitlab.com/',
|
||||
url: 'https://gitlab.com/-/ide/oauth_redirect',
|
||||
},
|
||||
{
|
||||
base: 'https://gdk.test:3443/',
|
||||
url: 'https://gdk.test:3443/-/ide/oauth_redirect/',
|
||||
},
|
||||
{
|
||||
base: 'https://gdk.test:3443/gitlab/',
|
||||
url: 'https://gdk.test:3443/gitlab/-/ide/oauth_redirect#1234?query=foo',
|
||||
},
|
||||
{
|
||||
base: 'https://example.com/',
|
||||
url: 'https://example.com/not-a-real-one-/ide/oauth_redirectz',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns empty when given empty', () => {
|
||||
expect(parseCallbackUrls('')).toEqual([]);
|
||||
expect(logError).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns empty when not valid JSON', () => {
|
||||
expect(parseCallbackUrls('babar')).toEqual([]);
|
||||
expect(logError).toHaveBeenCalledWith('Failed to parse callback URLs JSON');
|
||||
});
|
||||
|
||||
it('returns empty when not array JSON', () => {
|
||||
expect(parseCallbackUrls('{}')).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -46,7 +46,7 @@ describe('~/ide/mount_oauth_callback', () => {
|
|||
clientId: TEST_OAUTH_CLIENT_ID,
|
||||
protectRefreshToken: true,
|
||||
},
|
||||
gitlabUrl: `${TEST_HOST}/`,
|
||||
gitlabUrl: TEST_HOST,
|
||||
baseUrl: `${TEST_HOST}/${TEST_GITLAB_WEB_IDE_PUBLIC_PATH}`,
|
||||
username: TEST_USERNAME,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1304,21 +1304,4 @@ describe('URL utility', () => {
|
|||
expect(urlUtils.buildURLwithRefType({ base, path, refType })).toBe(output);
|
||||
});
|
||||
});
|
||||
|
||||
describe('stripRelativeUrlRootFromPath', () => {
|
||||
it.each`
|
||||
relativeUrlRoot | path | expectation
|
||||
${''} | ${'/foo/bar'} | ${'/foo/bar'}
|
||||
${'/'} | ${'/foo/bar'} | ${'/foo/bar'}
|
||||
${'/foo'} | ${'/foo/bar'} | ${'/bar'}
|
||||
${'/gitlab/'} | ${'/gitlab/-/ide/foo'} | ${'/-/ide/foo'}
|
||||
`(
|
||||
'with relative_url_root="$relativeUrlRoot", "$path" should return "$expectation"',
|
||||
({ relativeUrlRoot, path, expectation }) => {
|
||||
window.gon.relative_url_root = relativeUrlRoot;
|
||||
|
||||
expect(urlUtils.stripRelativeUrlRootFromPath(path)).toBe(expectation);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -253,7 +253,7 @@ RSpec.describe Banzai::Filter::AutolinkFilter, feature_category: :team_planning
|
|||
doc = "http://#{'&' * 1_000_000}x"
|
||||
|
||||
expect do
|
||||
Timeout.timeout(30.seconds) { filter(doc, context) }
|
||||
Timeout.timeout(BANZAI_FILTER_TIMEOUT_MAX) { filter(doc, context) }
|
||||
end.not_to raise_error
|
||||
end
|
||||
|
||||
|
|
@ -261,7 +261,7 @@ RSpec.describe Banzai::Filter::AutolinkFilter, feature_category: :team_planning
|
|||
doc = "#{'h' * 1_000_000}://example.com"
|
||||
|
||||
expect do
|
||||
Timeout.timeout(30.seconds) { filter(doc, context) }
|
||||
Timeout.timeout(BANZAI_FILTER_TIMEOUT_MAX) { filter(doc, context) }
|
||||
end.not_to raise_error
|
||||
end
|
||||
|
||||
|
|
@ -269,7 +269,7 @@ RSpec.describe Banzai::Filter::AutolinkFilter, feature_category: :team_planning
|
|||
doc = "#{'h' * 1_000_000}://"
|
||||
|
||||
expect do
|
||||
Timeout.timeout(30.seconds) { filter(doc) }
|
||||
Timeout.timeout(BANZAI_FILTER_TIMEOUT_MAX) { filter(doc) }
|
||||
end.not_to raise_error
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ RSpec.describe Banzai::Filter::BlockquoteFenceLegacyFilter, feature_category: :t
|
|||
test_string = ">>>#{"\n```\nfoo\n```" * 20}"
|
||||
|
||||
expect do
|
||||
Timeout.timeout(2.seconds) { filter(test_string, context) }
|
||||
Timeout.timeout(BANZAI_FILTER_TIMEOUT_MAX) { filter(test_string, context) }
|
||||
end.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ RSpec.describe Banzai::Filter::EmojiFilter, feature_category: :team_planning do
|
|||
|
||||
it 'limit keeps it from timing out', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/454749' do
|
||||
expect do
|
||||
Timeout.timeout(1.second) { filter('⏯ :play_pause: ' * 500000) }
|
||||
Timeout.timeout(BANZAI_FILTER_TIMEOUT_MAX) { filter('⏯ :play_pause: ' * 500000) }
|
||||
end.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ RSpec.describe Banzai::Filter::FrontMatterFilter, feature_category: :team_planni
|
|||
content = "coding:" + (" " * 50_000) + ";"
|
||||
|
||||
expect do
|
||||
Timeout.timeout(3.seconds) { filter(content) }
|
||||
Timeout.timeout(BANZAI_FILTER_TIMEOUT_MAX) { filter(content) }
|
||||
end.not_to raise_error
|
||||
end
|
||||
|
||||
|
|
@ -202,7 +202,7 @@ RSpec.describe Banzai::Filter::FrontMatterFilter, feature_category: :team_planni
|
|||
content = "coding:\n" + ";;;" + ("\n" * 10_000) + "x"
|
||||
|
||||
expect do
|
||||
Timeout.timeout(3.seconds) { filter(content) }
|
||||
Timeout.timeout(BANZAI_FILTER_TIMEOUT_MAX) { filter(content) }
|
||||
end.not_to raise_error
|
||||
end
|
||||
|
||||
|
|
@ -210,7 +210,7 @@ RSpec.describe Banzai::Filter::FrontMatterFilter, feature_category: :team_planni
|
|||
content = ("coding:" * 120_000) + ("\n" * 80_000) + ";"
|
||||
|
||||
expect do
|
||||
Timeout.timeout(3.seconds) { filter(content) }
|
||||
Timeout.timeout(BANZAI_FILTER_TIMEOUT_MAX) { filter(content) }
|
||||
end.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ RSpec.describe Banzai::Filter::GollumTagsFilter, feature_category: :wiki do
|
|||
text = "]#{'[[a' * 200000}[]"
|
||||
|
||||
expect do
|
||||
Timeout.timeout(3.seconds) { filter(text, context) }
|
||||
Timeout.timeout(BANZAI_FILTER_TIMEOUT_MAX) { filter(text, context) }
|
||||
end.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ RSpec.describe Banzai::Filter::InlineDiffFilter, feature_category: :source_code_
|
|||
doc = '[-{-' * 250_000
|
||||
|
||||
expect do
|
||||
Timeout.timeout(3.seconds) { filter(doc) }
|
||||
Timeout.timeout(BANZAI_FILTER_TIMEOUT_MAX) { filter(doc) }
|
||||
end.not_to raise_error
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ RSpec.describe Banzai::Filter::References::ProjectReferenceFilter, feature_categ
|
|||
it 'fails fast for long strings' do
|
||||
# took well under 1 second in CI https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/3267#note_172824
|
||||
expect do
|
||||
Timeout.timeout(3.seconds) { reference_filter(ref_string).to_html }
|
||||
Timeout.timeout(BANZAI_FILTER_TIMEOUT_MAX) { reference_filter(ref_string).to_html }
|
||||
end.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ RSpec.describe Banzai::Filter::SpacedLinkFilter, feature_category: :team_plannin
|
|||
end
|
||||
|
||||
it 'does not process malicious input' do
|
||||
Timeout.timeout(10) do
|
||||
Timeout.timeout(BANZAI_FILTER_TIMEOUT_MAX) do
|
||||
doc = filter('[ (](' * 60_000)
|
||||
|
||||
found_links = doc.css('a')
|
||||
|
|
|
|||
|
|
@ -229,7 +229,6 @@ RSpec.describe Banzai::Pipeline::FullPipeline, feature_category: :team_planning
|
|||
end
|
||||
|
||||
context 'when input is malicious' do
|
||||
let_it_be(:duration) { (Banzai::Filter::Concerns::PipelineTimingCheck::MAX_PIPELINE_SECONDS + 10).seconds }
|
||||
let_it_be(:markdown1) { '![a ' * 3 }
|
||||
let_it_be(:markdown2) { "$1$\n" * 190000 }
|
||||
let_it_be(:markdown3) { "[^1]\n[^1]:\n" * 100000 }
|
||||
|
|
@ -253,7 +252,7 @@ RSpec.describe Banzai::Pipeline::FullPipeline, feature_category: :team_planning
|
|||
with_them do
|
||||
it 'is not long running' do
|
||||
expect do
|
||||
Timeout.timeout(duration) { described_class.to_html(markdown, project: nil) }
|
||||
Timeout.timeout(BANZAI_FILTER_TIMEOUT_MAX) { described_class.to_html(markdown, project: nil) }
|
||||
end.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ RSpec.describe Banzai::Pipeline::PlainMarkdownPipeline, feature_category: :team_
|
|||
markdown = "x \\#\n\n#{'mliteralcmliteral-' * 450000}mliteral"
|
||||
|
||||
expect do
|
||||
Timeout.timeout(2.seconds) { described_class.to_html(markdown, project: project) }
|
||||
Timeout.timeout(BANZAI_FILTER_TIMEOUT_MAX) { described_class.to_html(markdown, project: project) }
|
||||
end.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -166,6 +166,60 @@ RSpec.describe 'new tables missing sharding_key', feature_category: :cell do
|
|||
end
|
||||
end
|
||||
|
||||
it 'ensures all organization_id columns are not nullable, have no default, and have a foreign key' do
|
||||
sql = <<~SQL
|
||||
SELECT c.table_name,
|
||||
CASE WHEN c.column_default IS NOT NULL THEN 'has default' ELSE NULL END,
|
||||
CASE WHEN c.is_nullable::boolean THEN 'nullable' ELSE NULL END,
|
||||
CASE WHEN fk.name IS NULL THEN 'no foreign key' ELSE NULL END
|
||||
FROM information_schema.columns c
|
||||
LEFT JOIN postgres_foreign_keys fk
|
||||
ON fk.constrained_table_name = c.table_name AND fk.constrained_columns = '{organization_id}' and fk.referenced_columns = '{id}'
|
||||
WHERE c.column_name = 'organization_id' AND (c.column_default IS NOT NULL OR c.is_nullable::boolean OR fk.name IS NULL)
|
||||
ORDER BY c.table_name;
|
||||
SQL
|
||||
|
||||
# To add a table to this list, create an issue under https://gitlab.com/groups/gitlab-org/-/epics/11670.
|
||||
# Use https://gitlab.com/gitlab-org/gitlab/-/issues/476206 as an example.
|
||||
work_in_progress = {
|
||||
"customer_relations_contacts" => 'https://gitlab.com/gitlab-org/gitlab/-/issues/476206',
|
||||
"dependency_list_export_parts" => 'https://gitlab.com/gitlab-org/gitlab/-/issues/476207',
|
||||
"dependency_list_exports" => 'https://gitlab.com/gitlab-org/gitlab/-/issues/476208',
|
||||
"namespaces" => 'https://gitlab.com/gitlab-org/gitlab/-/issues/476209',
|
||||
"organization_users" => 'https://gitlab.com/gitlab-org/gitlab/-/issues/476210',
|
||||
"projects" => 'https://gitlab.com/gitlab-org/gitlab/-/issues/476211',
|
||||
"push_rules" => 'https://gitlab.com/gitlab-org/gitlab/-/issues/476212',
|
||||
"raw_usage_data" => 'https://gitlab.com/gitlab-org/gitlab/-/issues/476213',
|
||||
"sbom_source_packages" => 'https://gitlab.com/gitlab-org/gitlab/-/issues/476214',
|
||||
"sbom_sources" => 'https://gitlab.com/gitlab-org/gitlab/-/issues/476215',
|
||||
"snippets" => 'https://gitlab.com/gitlab-org/gitlab/-/issues/476216',
|
||||
"upcoming_reconciliations" => 'https://gitlab.com/gitlab-org/gitlab/-/issues/476217',
|
||||
"vulnerability_export_parts" => 'https://gitlab.com/gitlab-org/gitlab/-/issues/476218',
|
||||
"vulnerability_exports" => 'https://gitlab.com/gitlab-org/gitlab/-/issues/476219'
|
||||
}
|
||||
|
||||
organization_id_columns = ApplicationRecord.connection.select_rows(sql)
|
||||
violations = organization_id_columns.reject { |column| work_in_progress[column[0]] }
|
||||
messages = violations.filter_map do |violation|
|
||||
if violation[2]
|
||||
if has_null_check_constraint?(violation[0], 'organization_id')
|
||||
violation.delete_at(2)
|
||||
else
|
||||
violation[2].concat(' / not null constraint missing')
|
||||
end
|
||||
end
|
||||
|
||||
" #{violation[0]} - #{violation[1..].compact.join(', ')}" if violation[1..].any?
|
||||
end
|
||||
|
||||
expect(messages).to be_empty, "Expected all organization_id columns to be not nullable, have no default, " \
|
||||
"and have a foreign key, but the following tables do not meet this criteria:" \
|
||||
"\n#{messages.join("\n")}\n\n" \
|
||||
"If this is a work in progress, please create an issue under " \
|
||||
"https://gitlab.com/groups/gitlab-org/-/epics/11670, " \
|
||||
"and add the table to the work in progress list in this test."
|
||||
end
|
||||
|
||||
it 'only allows `allowed_to_be_missing_sharding_key` to include tables that are missing a sharding_key',
|
||||
:aggregate_failures do
|
||||
allowed_to_be_missing_sharding_key.each do |exempted_table|
|
||||
|
|
|
|||
|
|
@ -75,6 +75,19 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
|
|||
it { is_expected.to delegate_method(:merge_request_ref?).to(:pipeline) }
|
||||
it { is_expected.to delegate_method(:legacy_detached_merge_request_pipeline?).to(:pipeline) }
|
||||
|
||||
describe 'partition query' do
|
||||
subject { build.reload }
|
||||
|
||||
it_behaves_like 'including partition key for relation', :trace_chunks
|
||||
it_behaves_like 'including partition key for relation', :build_source
|
||||
it_behaves_like 'including partition key for relation', :job_artifacts
|
||||
it_behaves_like 'including partition key for relation', :job_annotations
|
||||
it_behaves_like 'including partition key for relation', :runner_manager_build
|
||||
Ci::JobArtifact.file_types.each_key do |key|
|
||||
it_behaves_like 'including partition key for relation', :"job_artifacts_#{key}"
|
||||
end
|
||||
end
|
||||
|
||||
describe 'associations' do
|
||||
it { is_expected.to belong_to(:project_mirror).with_foreign_key('project_id') }
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,11 @@ RSpec.describe CommitStatus, feature_category: :continuous_integration do
|
|||
.with_foreign_key(:commit_id).inverse_of(:statuses)
|
||||
end
|
||||
|
||||
it do
|
||||
is_expected.to belong_to(:ci_stage).class_name('Ci::Stage')
|
||||
.with_foreign_key(:stage_id)
|
||||
end
|
||||
|
||||
it { is_expected.to belong_to(:user) }
|
||||
it { is_expected.to belong_to(:project) }
|
||||
it { is_expected.to belong_to(:auto_canceled_by) }
|
||||
|
|
@ -45,6 +50,13 @@ RSpec.describe CommitStatus, feature_category: :continuous_integration do
|
|||
it { is_expected.not_to be_retried }
|
||||
it { expect(described_class.primary_key).to eq('id') }
|
||||
|
||||
describe 'partition query' do
|
||||
subject { commit_status.reload }
|
||||
|
||||
it_behaves_like 'including partition key for relation', :pipeline
|
||||
it_behaves_like 'including partition key for relation', :ci_stage
|
||||
end
|
||||
|
||||
describe '#author' do
|
||||
subject { commit_status.author }
|
||||
|
||||
|
|
|
|||
|
|
@ -167,7 +167,9 @@ RSpec.describe Ci::Partitionable, feature_category: :continuous_integration do
|
|||
ci_model.include(described_class)
|
||||
end
|
||||
|
||||
subject(:scope_values) { ci_model.in_partition(value).where_values_hash }
|
||||
subject(:scope_values) { ci_model.in_partition(value, **options).where_values_hash }
|
||||
|
||||
let(:options) { {} }
|
||||
|
||||
context 'with integer parameters' do
|
||||
let(:value) { 101 }
|
||||
|
|
@ -184,6 +186,15 @@ RSpec.describe Ci::Partitionable, feature_category: :continuous_integration do
|
|||
expect(scope_values).to include('partition_id' => 101)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with given partition_foreign_key' do
|
||||
let(:options) { { partition_foreign_key: :auto_canceled_by_partition_id } }
|
||||
let(:value) { build_stubbed(:ci_build, auto_canceled_by_partition_id: 102) }
|
||||
|
||||
it 'adds a partition_id filter' do
|
||||
expect(scope_values).to include('partition_id' => 102)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.registered_models' do
|
||||
|
|
|
|||
|
|
@ -1,6 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module MigrationsHelpers
|
||||
def migration_out_of_test_window?(migration_class)
|
||||
milestone = migration_class.try(:milestone)
|
||||
|
||||
# Missing milestone indicates that the migration is pre-16.7,
|
||||
# which is old enough not to execute its tests
|
||||
return true unless milestone
|
||||
|
||||
migration_milestone = Gitlab::VersionInfo.parse_from_milestone(milestone)
|
||||
min_milestone = Gitlab::VersionInfo.parse_from_milestone(
|
||||
::Gitlab::Database::MIN_SCHEMA_GITLAB_VERSION
|
||||
)
|
||||
|
||||
migration_milestone < min_milestone
|
||||
end
|
||||
|
||||
def active_record_base(database: nil)
|
||||
database_name = database || self.class.metadata[:database] || :main
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,14 @@ RSpec.configure do |config|
|
|||
# end
|
||||
# end
|
||||
config.before(:context, :migration) do
|
||||
@migration_out_of_test_window = false
|
||||
|
||||
if migration_out_of_test_window?(described_class)
|
||||
@migration_out_of_test_window = true
|
||||
|
||||
skip "Skipping because migration #{described_class} is outside the test window"
|
||||
end
|
||||
|
||||
schema_migrate_down!
|
||||
end
|
||||
|
||||
|
|
@ -21,7 +29,7 @@ RSpec.configure do |config|
|
|||
end
|
||||
|
||||
config.append_after(:context, :migration) do
|
||||
recreate_databases_and_seed_if_needed || ensure_schema_and_empty_tables
|
||||
recreate_databases_and_seed_if_needed || ensure_schema_and_empty_tables unless @migration_out_of_test_window
|
||||
end
|
||||
|
||||
config.around(:each, :migration) do |example|
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# This is an excessive timeout, however it's meant to ensure that we don't
|
||||
# have flaky timeouts in CI, which can be slow.
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/161969
|
||||
BANZAI_FILTER_TIMEOUT_MAX = 30.seconds
|
||||
|
||||
# These shared_examples require the following variables:
|
||||
# - text: The text to be run through the filter
|
||||
#
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'including partition key for relation' do |relation_name|
|
||||
it "includes partition_id in the query for record's relation #{relation_name}" do
|
||||
reflection = subject.class.reflections[relation_name.to_s]
|
||||
collection_or_self = reflection.collection? ? :to_a : :self
|
||||
|
||||
recorder = ActiveRecord::QueryRecorder.new { subject.try(relation_name).try(collection_or_self) }
|
||||
|
||||
table_name = reflection.klass.table_name
|
||||
foreign_key_name = reflection.options[:foreign_key]
|
||||
partition_foreign_key_name = reflection.options[:partition_foreign_key]
|
||||
|
||||
key_name, key_value, partition_key_name, partition_key_value =
|
||||
case reflection
|
||||
# For `belongs_to` association, we will need to query the association using the foreign key in the current table.
|
||||
# This is because in a `belongs_to` relation, the current table contains a reference to the target table
|
||||
when ActiveRecord::Reflection::BelongsToReflection
|
||||
[:id, subject.try(foreign_key_name), :partition_id, subject.try(partition_foreign_key_name)]
|
||||
# For all the other associations such as `has_many`,
|
||||
# we will need to query the association using the `id` and `partition_id` of the current table.
|
||||
# This is because the target table references the current table.
|
||||
else
|
||||
[foreign_key_name, subject.id, partition_foreign_key_name, subject.partition_id]
|
||||
end
|
||||
|
||||
# Positive lookahead to ensure the string contains this expression e.g. `"p_ci_builds"."id" = 1`
|
||||
expression_matching_key = %{(?=.*"#{table_name}"."#{key_name}" = #{key_value})}
|
||||
# Positive lookahead to ensure the string contains this expression e.g. `"p_ci_builds"."partition_id" = 102` as well
|
||||
expression_matching_partition_key =
|
||||
%{(?=.*"#{table_name}"."#{partition_key_name}" = #{partition_key_value})}
|
||||
expect(recorder.log)
|
||||
.to include(/\A#{expression_matching_key}#{expression_matching_partition_key}.*\z/)
|
||||
end
|
||||
end
|
||||
40
yarn.lock
40
yarn.lock
|
|
@ -1104,15 +1104,6 @@
|
|||
url-search-params-polyfill "^7.0.0"
|
||||
uuid "^8.3.2"
|
||||
|
||||
"@cubejs-client/vue@^0.35.23":
|
||||
version "0.35.23"
|
||||
resolved "https://registry.yarnpkg.com/@cubejs-client/vue/-/vue-0.35.23.tgz#1ad786682db18f6fe7374c2fe720f79d2dcf4b71"
|
||||
integrity sha512-U2LCMHY+FGqH85ehp/qiwdFE5BPUPuODx8kLGyEn1Mtr11ndYPobN4NZCj4kw0yHjcm41eQSH5/GCsZWTutNww==
|
||||
dependencies:
|
||||
"@cubejs-client/core" "^0.35.23"
|
||||
core-js "^3.6.5"
|
||||
ramda "^0.27.2"
|
||||
|
||||
"@discoveryjs/json-ext@0.5.7", "@discoveryjs/json-ext@^0.5.0":
|
||||
version "0.5.7"
|
||||
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
|
||||
|
|
@ -12942,16 +12933,7 @@ string-length@^4.0.1:
|
|||
char-regex "^1.0.2"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0":
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
|
|
@ -13003,7 +12985,7 @@ string_decoder@^1.0.0, string_decoder@^1.1.1, string_decoder@~1.1.1:
|
|||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
|
|
@ -13017,13 +12999,6 @@ strip-ansi@^5.2.0:
|
|||
dependencies:
|
||||
ansi-regex "^4.1.0"
|
||||
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^7.0.1, strip-ansi@^7.1.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
||||
|
|
@ -14703,7 +14678,7 @@ worker-loader@^3.0.8:
|
|||
loader-utils "^2.0.0"
|
||||
schema-utils "^3.0.0"
|
||||
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
|
|
@ -14721,15 +14696,6 @@ wrap-ansi@^6.2.0:
|
|||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||
|
|
|
|||
Loading…
Reference in New Issue