Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
b333706699
commit
958f41148d
|
@ -35,10 +35,24 @@ stages:
|
|||
RUN_WITH_BUNDLE: "true" # installs and runs gitlab-qa via bundler
|
||||
QA_PATH: qa
|
||||
|
||||
.omnibus-env:
|
||||
variables:
|
||||
BUILD_ENV: build.env
|
||||
script:
|
||||
- |
|
||||
SECURITY_SOURCES=$([[ ! "$CI_PROJECT_NAMESPACE" =~ ^gitlab-org\/security ]] || echo "true")
|
||||
echo "SECURITY_SOURCES=${SECURITY_SOURCES:-false}" > $BUILD_ENV
|
||||
echo "OMNIBUS_GITLAB_CACHE_UPDATE=${OMNIBUS_GITLAB_CACHE_UPDATE:-false}" >> $BUILD_ENV
|
||||
for version_file in *_VERSION; do echo "$version_file=$(cat $version_file)" >> $BUILD_ENV; done
|
||||
echo "Built environment file for omnibus build:"
|
||||
cat $BUILD_ENV
|
||||
artifacts:
|
||||
reports:
|
||||
dotenv: $BUILD_ENV
|
||||
|
||||
.update-script:
|
||||
script:
|
||||
- export CURRENT_VERSION="$(cat ../VERSION)"
|
||||
- export QA_COMMAND="bundle exec gitlab-qa Test::Omnibus::UpdateFromPrevious $RELEASE $CURRENT_VERSION $UPDATE_TYPE -- $QA_RSPEC_TAGS $RSPEC_REPORT_OPTS"
|
||||
- export QA_COMMAND="bundle exec gitlab-qa Test::Omnibus::UpdateFromPrevious $RELEASE $GITLAB_VERSION $UPDATE_TYPE -- $QA_RSPEC_TAGS $RSPEC_REPORT_OPTS"
|
||||
- echo "Running - '$QA_COMMAND'"
|
||||
- eval "$QA_COMMAND"
|
||||
|
||||
|
@ -65,16 +79,38 @@ stages:
|
|||
# ==========================================
|
||||
# Prepare stage
|
||||
# ==========================================
|
||||
trigger-omnibus:
|
||||
trigger-omnibus-env:
|
||||
extends:
|
||||
- .ruby-image
|
||||
- .omnibus-env
|
||||
- .rules:prepare
|
||||
stage: .pre
|
||||
before_script:
|
||||
- source scripts/utils.sh
|
||||
- install_gitlab_gem
|
||||
script:
|
||||
- ./scripts/trigger-build.rb omnibus
|
||||
|
||||
trigger-omnibus:
|
||||
extends: .rules:prepare
|
||||
stage: .pre
|
||||
needs:
|
||||
- trigger-omnibus-env
|
||||
inherit:
|
||||
variables: false
|
||||
variables:
|
||||
GITALY_SERVER_VERSION: $GITALY_SERVER_VERSION
|
||||
GITLAB_ELASTICSEARCH_INDEXER_VERSION: $GITLAB_ELASTICSEARCH_INDEXER_VERSION
|
||||
GITLAB_KAS_VERSION: $GITLAB_KAS_VERSION
|
||||
GITLAB_METRICS_EXPORTER_VERSION: $GITLAB_METRICS_EXPORTER_VERSION
|
||||
GITLAB_PAGES_VERSION: $GITLAB_PAGES_VERSION
|
||||
GITLAB_SHELL_VERSION: $GITLAB_SHELL_VERSION
|
||||
GITLAB_WORKHORSE_VERSION: $GITLAB_WORKHORSE_VERSION
|
||||
GITLAB_VERSION: $CI_COMMIT_SHA
|
||||
IMAGE_TAG: $CI_COMMIT_SHA
|
||||
TOP_UPSTREAM_SOURCE_PROJECT: $CI_PROJECT_PATH
|
||||
SECURITY_SOURCES: $SECURITY_SOURCES
|
||||
CACHE_UPDATE: $OMNIBUS_GITLAB_CACHE_UPDATE
|
||||
SKIP_QA_DOCKER: "true"
|
||||
SKIP_QA_TEST: "true"
|
||||
ee: "true"
|
||||
trigger:
|
||||
project: gitlab-org/build/omnibus-gitlab-mirror
|
||||
strategy: depend
|
||||
|
||||
download-knapsack-report:
|
||||
extends:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Default variables for package-and-test
|
||||
|
||||
variables:
|
||||
RELEASE: "gitlab/gitlab-ee:nightly"
|
||||
RELEASE: "${REGISTRY_HOST}/${REGISTRY_GROUP}/build/omnibus-gitlab-mirror/gitlab-ee:${CI_COMMIT_SHA}"
|
||||
SKIP_REPORT_IN_ISSUES: "true"
|
||||
OMNIBUS_GITLAB_CACHE_UPDATE: "false"
|
||||
COLORIZED_LOGS: "true"
|
||||
|
|
|
@ -83,7 +83,7 @@ export default {
|
|||
* if the jiraConnectOauth flag is enabled.
|
||||
*/
|
||||
fetchSubscriptionsOauth() {
|
||||
if (!this.isOauthEnabled) return;
|
||||
if (!this.isOauthEnabled || !this.userSignedIn) return;
|
||||
|
||||
this.fetchSubscriptions(this.subscriptionsPath);
|
||||
},
|
||||
|
@ -146,12 +146,12 @@ export default {
|
|||
|
||||
<div class="gl-layout-w-limited gl-mx-auto gl-px-5 gl-mb-7">
|
||||
<sign-in-page
|
||||
v-if="!userSignedIn"
|
||||
v-show="!userSignedIn"
|
||||
:has-subscriptions="hasSubscriptions"
|
||||
@sign-in-oauth="onSignInOauth"
|
||||
@error="onSignInError"
|
||||
/>
|
||||
<subscriptions-page v-else :has-subscriptions="hasSubscriptions" />
|
||||
<subscriptions-page v-if="userSignedIn" :has-subscriptions="hasSubscriptions" />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
import { mapActions, mapMutations } from 'vuex';
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { sprintf } from '~/locale';
|
||||
import {
|
||||
I18N_DEFAULT_SIGN_IN_BUTTON_TEXT,
|
||||
I18N_CUSTOM_SIGN_IN_BUTTON_TEXT,
|
||||
OAUTH_WINDOW_OPTIONS,
|
||||
PKCE_CODE_CHALLENGE_DIGEST_ALGORITHM,
|
||||
} from '~/jira_connect/subscriptions/constants';
|
||||
|
@ -17,14 +19,29 @@ export default {
|
|||
GlButton,
|
||||
},
|
||||
inject: ['oauthMetadata'],
|
||||
props: {
|
||||
gitlabBasePath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
token: null,
|
||||
loading: false,
|
||||
codeVerifier: null,
|
||||
canUseCrypto: AccessorUtilities.canUseCrypto(),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
buttonText() {
|
||||
if (!this.gitlabBasePath) {
|
||||
return I18N_DEFAULT_SIGN_IN_BUTTON_TEXT;
|
||||
}
|
||||
|
||||
return sprintf(I18N_CUSTOM_SIGN_IN_BUTTON_TEXT, { url: this.gitlabBasePath });
|
||||
},
|
||||
},
|
||||
created() {
|
||||
window.addEventListener('message', this.handleWindowMessage);
|
||||
},
|
||||
|
@ -56,7 +73,7 @@ export default {
|
|||
|
||||
window.open(
|
||||
oauthAuthorizeURLWithChallenge,
|
||||
this.$options.i18n.defaultButtonText,
|
||||
I18N_DEFAULT_SIGN_IN_BUTTON_TEXT,
|
||||
OAUTH_WINDOW_OPTIONS,
|
||||
);
|
||||
},
|
||||
|
@ -105,9 +122,6 @@ export default {
|
|||
return data.access_token;
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
defaultButtonText: I18N_DEFAULT_SIGN_IN_BUTTON_TEXT,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
|
@ -119,7 +133,7 @@ export default {
|
|||
@click="startOAuthFlow"
|
||||
>
|
||||
<slot>
|
||||
{{ $options.i18n.defaultButtonText }}
|
||||
{{ buttonText }}
|
||||
</slot>
|
||||
</gl-button>
|
||||
</template>
|
||||
|
|
|
@ -3,11 +3,13 @@ import { helpPagePath } from '~/helpers/help_page_helper';
|
|||
|
||||
export const DEFAULT_GROUPS_PER_PAGE = 10;
|
||||
export const ALERT_LOCALSTORAGE_KEY = 'gitlab_alert';
|
||||
export const BASE_URL_LOCALSTORAGE_KEY = 'gitlab_base_url';
|
||||
export const MINIMUM_SEARCH_TERM_LENGTH = 3;
|
||||
|
||||
export const ADD_NAMESPACE_MODAL_ID = 'add-namespace-modal';
|
||||
|
||||
export const I18N_DEFAULT_SIGN_IN_BUTTON_TEXT = s__('Integrations|Sign in to GitLab');
|
||||
export const I18N_CUSTOM_SIGN_IN_BUTTON_TEXT = s__('Integrations|Sign in to %{url}');
|
||||
export const I18N_DEFAULT_SIGN_IN_ERROR_MESSAGE = s__('Integrations|Failed to sign in to GitLab.');
|
||||
export const I18N_DEFAULT_SUBSCRIPTIONS_ERROR_MESSAGE = s__(
|
||||
'Integrations|Failed to load subscriptions.',
|
||||
|
@ -26,6 +28,8 @@ export const I18N_ADD_SUBSCRIPTIONS_ERROR_MESSAGE = s__(
|
|||
'Integrations|Failed to link namespace. Please try again.',
|
||||
);
|
||||
|
||||
export const GITLAB_COM_BASE_PATH = 'https://gitlab.com';
|
||||
|
||||
const OAUTH_WINDOW_SIZE = 800;
|
||||
export const OAUTH_WINDOW_OPTIONS = [
|
||||
'resizable=yes',
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
|
||||
import SignInOauthButton from '../../../components/sign_in_oauth_button.vue';
|
||||
import VersionSelectForm from './version_select_form.vue';
|
||||
|
||||
|
@ -58,6 +59,7 @@ export default {
|
|||
<div v-else class="gl-text-center">
|
||||
<sign-in-oauth-button
|
||||
class="gl-mb-5"
|
||||
:gitlab-base-path="gitlabBasePath"
|
||||
@sign-in="$emit('sign-in-oauth', $event)"
|
||||
@error="onSignInError"
|
||||
/>
|
||||
|
|
|
@ -9,13 +9,14 @@ import {
|
|||
} from '@gitlab/ui';
|
||||
import { __, s__ } from '~/locale';
|
||||
|
||||
import { GITLAB_COM_BASE_PATH } from '~/jira_connect/subscriptions/constants';
|
||||
|
||||
const RADIO_OPTIONS = {
|
||||
saas: 'saas',
|
||||
selfManaged: 'selfManaged',
|
||||
};
|
||||
|
||||
const DEFAULT_RADIO_OPTION = RADIO_OPTIONS.saas;
|
||||
const GITLAB_COM_BASE_PATH = 'https://gitlab.com';
|
||||
|
||||
export default {
|
||||
name: 'VersionSelectForm',
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
export default function createState({ subscriptions = [], subscriptionsLoading = false } = {}) {
|
||||
export default function createState({
|
||||
subscriptions = [],
|
||||
subscriptionsLoading = false,
|
||||
currentUser = null,
|
||||
} = {}) {
|
||||
return {
|
||||
alert: undefined,
|
||||
|
||||
|
@ -9,7 +13,7 @@ export default function createState({ subscriptions = [], subscriptionsLoading =
|
|||
addSubscriptionLoading: false,
|
||||
addSubscriptionError: false,
|
||||
|
||||
currentUser: null,
|
||||
currentUser,
|
||||
currentUserError: null,
|
||||
|
||||
accessToken: null,
|
||||
|
|
|
@ -1,32 +1,45 @@
|
|||
import AccessorUtilities from '~/lib/utils/accessor';
|
||||
import { objectToQuery } from '~/lib/utils/url_utility';
|
||||
import { ALERT_LOCALSTORAGE_KEY } from './constants';
|
||||
import { ALERT_LOCALSTORAGE_KEY, BASE_URL_LOCALSTORAGE_KEY } from './constants';
|
||||
|
||||
const isFunction = (fn) => typeof fn === 'function';
|
||||
const { canUseLocalStorage } = AccessorUtilities;
|
||||
|
||||
const persistToStorage = (key, payload) => {
|
||||
localStorage.setItem(key, payload);
|
||||
};
|
||||
|
||||
const retrieveFromStorage = (key) => {
|
||||
return localStorage.getItem(key);
|
||||
};
|
||||
|
||||
const removeFromStorage = (key) => {
|
||||
localStorage.removeItem(key);
|
||||
};
|
||||
|
||||
/**
|
||||
* Persist alert data to localStorage.
|
||||
*/
|
||||
export const persistAlert = ({ title, message, linkUrl, variant } = {}) => {
|
||||
if (!AccessorUtilities.canUseLocalStorage()) {
|
||||
if (!canUseLocalStorage()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = JSON.stringify({ title, message, linkUrl, variant });
|
||||
localStorage.setItem(ALERT_LOCALSTORAGE_KEY, payload);
|
||||
persistToStorage(ALERT_LOCALSTORAGE_KEY, payload);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return alert data from localStorage.
|
||||
*/
|
||||
export const retrieveAlert = () => {
|
||||
if (!AccessorUtilities.canUseLocalStorage()) {
|
||||
if (!canUseLocalStorage()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const initialAlertJSON = localStorage.getItem(ALERT_LOCALSTORAGE_KEY);
|
||||
const initialAlertJSON = retrieveFromStorage(ALERT_LOCALSTORAGE_KEY);
|
||||
// immediately clean up
|
||||
localStorage.removeItem(ALERT_LOCALSTORAGE_KEY);
|
||||
removeFromStorage(ALERT_LOCALSTORAGE_KEY);
|
||||
|
||||
if (!initialAlertJSON) {
|
||||
return null;
|
||||
|
@ -35,6 +48,22 @@ export const retrieveAlert = () => {
|
|||
return JSON.parse(initialAlertJSON);
|
||||
};
|
||||
|
||||
export const persistBaseUrl = (baseUrl) => {
|
||||
if (!canUseLocalStorage()) {
|
||||
return;
|
||||
}
|
||||
|
||||
persistToStorage(BASE_URL_LOCALSTORAGE_KEY, baseUrl);
|
||||
};
|
||||
|
||||
export const retrieveBaseUrl = () => {
|
||||
if (!canUseLocalStorage()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return retrieveFromStorage(BASE_URL_LOCALSTORAGE_KEY);
|
||||
};
|
||||
|
||||
export const getJwt = () => {
|
||||
return new Promise((resolve) => {
|
||||
if (isFunction(AP?.context?.getToken)) {
|
||||
|
|
|
@ -276,6 +276,7 @@ class GroupsController < Groups::ApplicationController
|
|||
:avatar,
|
||||
:description,
|
||||
:emails_disabled,
|
||||
:show_diff_preview_in_email,
|
||||
:mentions_disabled,
|
||||
:lfs_enabled,
|
||||
:name,
|
||||
|
|
|
@ -119,9 +119,9 @@ class ProjectsFinder < UnionFinder
|
|||
# This is an optimization - surprisingly PostgreSQL does not optimize
|
||||
# for this.
|
||||
#
|
||||
# If the default visiblity level and desired visiblity level filter cancels
|
||||
# If the default visibility level and desired visibility level filter cancels
|
||||
# each other out, don't use the SQL clause for visibility level in
|
||||
# `Project.public_or_visible_to_user`. In fact, this then becames equivalent
|
||||
# `Project.public_or_visible_to_user`. In fact, this then becomes equivalent
|
||||
# to just authorized projects for the user.
|
||||
#
|
||||
# E.g.
|
||||
|
|
|
@ -9,7 +9,7 @@ module Resolvers
|
|||
|
||||
alias_method :runner, :object
|
||||
|
||||
def resolve_with_lookahead(**args)
|
||||
def resolve_with_lookahead(**_args)
|
||||
resolve_owner
|
||||
end
|
||||
|
||||
|
@ -19,6 +19,8 @@ module Resolvers
|
|||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filtered_preloads
|
||||
selection = lookahead
|
||||
|
||||
|
@ -27,8 +29,6 @@ module Resolvers
|
|||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def resolve_owner
|
||||
return unless runner.project_type?
|
||||
|
||||
|
@ -48,14 +48,13 @@ module Resolvers
|
|||
.transform_values { |runner_projects| runner_projects.first.project_id }
|
||||
project_ids = owner_project_id_by_runner_id.values.uniq
|
||||
|
||||
all_preloads = unconditional_includes + filtered_preloads
|
||||
owner_relation = Project.all
|
||||
owner_relation = owner_relation.preload(*all_preloads) if all_preloads.any?
|
||||
projects = owner_relation.where(id: project_ids).index_by(&:id)
|
||||
projects = Project.where(id: project_ids)
|
||||
Preloaders::ProjectPolicyPreloader.new(projects, current_user).execute
|
||||
projects_by_id = projects.index_by(&:id)
|
||||
|
||||
runner_ids.each do |runner_id|
|
||||
owner_project_id = owner_project_id_by_runner_id[runner_id]
|
||||
loader.call(runner_id, projects[owner_project_id])
|
||||
loader.call(runner_id, projects_by_id[owner_project_id])
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
end
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Resolvers
|
||||
module Ci
|
||||
class RunnerProjectsResolver < BaseResolver
|
||||
include Gitlab::Graphql::Authorize::AuthorizeResource
|
||||
include LooksAhead
|
||||
include ProjectSearchArguments
|
||||
|
||||
type Types::ProjectType.connection_type, null: true
|
||||
authorize :read_runner
|
||||
authorizes_object!
|
||||
|
||||
alias_method :runner, :object
|
||||
|
||||
argument :sort, GraphQL::Types::String,
|
||||
required: false,
|
||||
default_value: 'id_asc', # TODO: Remove in %16.0 and move :sort to ProjectSearchArguments, see https://gitlab.com/gitlab-org/gitlab/-/issues/372117
|
||||
deprecated: {
|
||||
reason: 'Default sort order will change in 16.0. ' \
|
||||
'Specify `"id_asc"` if query results\' order is important',
|
||||
milestone: '15.4'
|
||||
},
|
||||
description: "Sort order of results. Format: '<field_name>_<sort_direction>', " \
|
||||
"for example: 'id_desc' or 'name_asc'"
|
||||
|
||||
def resolve_with_lookahead(**args)
|
||||
return unless runner.project_type?
|
||||
|
||||
# rubocop:disable CodeReuse/ActiveRecord
|
||||
BatchLoader::GraphQL.for(runner.id).batch(key: :runner_projects) do |runner_ids, loader|
|
||||
plucked_runner_and_project_ids = ::Ci::RunnerProject
|
||||
.select(:runner_id, :project_id)
|
||||
.where(runner_id: runner_ids)
|
||||
.pluck(:runner_id, :project_id)
|
||||
|
||||
project_ids = plucked_runner_and_project_ids.collect { |_runner_id, project_id| project_id }.uniq
|
||||
projects = ProjectsFinder
|
||||
.new(current_user: current_user,
|
||||
params: project_finder_params(args),
|
||||
project_ids_relation: project_ids)
|
||||
.execute
|
||||
Preloaders::ProjectPolicyPreloader.new(projects, current_user).execute
|
||||
projects_by_id = projects.index_by(&:id)
|
||||
|
||||
# In plucked_runner_and_project_ids, first() represents the runner ID, and second() the project ID,
|
||||
# so let's group the project IDs by runner ID
|
||||
runner_project_ids_by_runner_id =
|
||||
plucked_runner_and_project_ids
|
||||
.group_by(&:first)
|
||||
.transform_values { |values| values.map(&:second).filter_map { |project_id| projects_by_id[project_id] } }
|
||||
|
||||
runner_ids.each do |runner_id|
|
||||
runner_projects = runner_project_ids_by_runner_id[runner_id] || []
|
||||
|
||||
loader.call(runner_id, runner_projects)
|
||||
end
|
||||
end
|
||||
# rubocop:enable CodeReuse/ActiveRecord
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ProjectSearchArguments
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
argument :membership, GraphQL::Types::Boolean,
|
||||
required: false,
|
||||
description: 'Return only projects that the current user is a member of.'
|
||||
|
||||
argument :search, GraphQL::Types::String,
|
||||
required: false,
|
||||
description: 'Search query, which can be for the project name, a path, or a description.'
|
||||
|
||||
argument :search_namespaces, GraphQL::Types::Boolean,
|
||||
required: false,
|
||||
description: 'Include namespace in project search.'
|
||||
|
||||
argument :topics, type: [GraphQL::Types::String],
|
||||
required: false,
|
||||
description: 'Filter projects by topics.'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def project_finder_params(params)
|
||||
{
|
||||
without_deleted: true,
|
||||
non_public: params[:membership],
|
||||
search: params[:search],
|
||||
search_namespaces: params[:search_namespaces],
|
||||
sort: params[:sort],
|
||||
topic: params[:topics]
|
||||
}.compact
|
||||
end
|
||||
end
|
|
@ -2,31 +2,18 @@
|
|||
|
||||
module Resolvers
|
||||
class ProjectsResolver < BaseResolver
|
||||
include ProjectSearchArguments
|
||||
|
||||
type Types::ProjectType, null: true
|
||||
|
||||
argument :membership, GraphQL::Types::Boolean,
|
||||
required: false,
|
||||
description: 'Limit projects that the current user is a member of.'
|
||||
|
||||
argument :search, GraphQL::Types::String,
|
||||
required: false,
|
||||
description: 'Search query for project name, path, or description.'
|
||||
|
||||
argument :ids, [GraphQL::Types::ID],
|
||||
required: false,
|
||||
description: 'Filter projects by IDs.'
|
||||
|
||||
argument :search_namespaces, GraphQL::Types::Boolean,
|
||||
required: false,
|
||||
description: 'Include namespace in project search.'
|
||||
|
||||
argument :sort, GraphQL::Types::String,
|
||||
required: false,
|
||||
description: 'Sort order of results.'
|
||||
|
||||
argument :topics, type: [GraphQL::Types::String],
|
||||
required: false,
|
||||
description: 'Filters projects by topics.'
|
||||
description: "Sort order of results. Format: '<field_name>_<sort_direction>', " \
|
||||
"for example: 'id_desc' or 'name_asc'"
|
||||
|
||||
def resolve(**args)
|
||||
ProjectsFinder
|
||||
|
@ -36,17 +23,6 @@ module Resolvers
|
|||
|
||||
private
|
||||
|
||||
def project_finder_params(params)
|
||||
{
|
||||
without_deleted: true,
|
||||
non_public: params[:membership],
|
||||
search: params[:search],
|
||||
search_namespaces: params[:search_namespaces],
|
||||
sort: params[:sort],
|
||||
topic: params[:topics]
|
||||
}.compact
|
||||
end
|
||||
|
||||
def parse_gids(gids)
|
||||
gids&.map { |gid| GitlabSchema.parse_gid(gid, expected_type: ::Project).model_id }
|
||||
end
|
||||
|
|
|
@ -63,8 +63,11 @@ module Types
|
|||
description: 'Indicates the runner is paused and not available to run jobs.'
|
||||
field :project_count, GraphQL::Types::Int, null: true,
|
||||
description: 'Number of projects that the runner is associated with.'
|
||||
field :projects, ::Types::ProjectType.connection_type, null: true,
|
||||
description: 'Projects the runner is associated with. For project runners only.'
|
||||
field :projects,
|
||||
::Types::ProjectType.connection_type,
|
||||
null: true,
|
||||
resolver: ::Resolvers::Ci::RunnerProjectsResolver,
|
||||
description: 'Find projects the runner is associated with. For project runners only.'
|
||||
field :revision, GraphQL::Types::String, null: true,
|
||||
description: 'Revision of the runner.'
|
||||
field :run_untagged, GraphQL::Types::Boolean, null: false,
|
||||
|
@ -131,12 +134,6 @@ module Types
|
|||
batched_owners(::Ci::RunnerNamespace, Group, :runner_groups, :namespace_id)
|
||||
end
|
||||
|
||||
def projects
|
||||
return unless runner.project_type?
|
||||
|
||||
batched_owners(::Ci::RunnerProject, Project, :runner_projects, :project_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def can_admin_runners?
|
||||
|
@ -159,19 +156,12 @@ module Types
|
|||
owner_ids = runner_owner_ids_by_runner_id.values.flatten.uniq
|
||||
owners = assoc_type.where(id: owner_ids).index_by(&:id)
|
||||
|
||||
# Preload projects namespaces to avoid N+1 queries when checking the `read_project` policy for each
|
||||
preload_projects_namespaces(owners.values) if assoc_type == Project
|
||||
|
||||
runner_ids.each do |runner_id|
|
||||
loader.call(runner_id, runner_owner_ids_by_runner_id[runner_id]&.map { |owner_id| owners[owner_id] } || [])
|
||||
end
|
||||
end
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def preload_projects_namespaces(_projects)
|
||||
# overridden in EE
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,7 +16,8 @@ module Enums
|
|||
alert_management_alerts: 8,
|
||||
sprints: 9, # iterations
|
||||
design_management_designs: 10,
|
||||
incident_management_oncall_schedules: 11
|
||||
incident_management_oncall_schedules: 11,
|
||||
ml_experiments: 12
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -191,6 +191,22 @@ class Group < Namespace
|
|||
.where(group_group_links: { shared_group_id: group.self_and_ancestors })
|
||||
end
|
||||
|
||||
# WARNING: This method should never be used on its own
|
||||
# please do make sure the number of rows you are filtering is small
|
||||
# enough for this query
|
||||
#
|
||||
# It's a replacement for `public_or_visible_to_user` that correctly
|
||||
# supports subgroup permissions
|
||||
scope :accessible_to_user, -> (user) do
|
||||
if user
|
||||
Preloaders::GroupPolicyPreloader.new(self, user).execute
|
||||
|
||||
select { |group| user.can?(:read_group, group) }
|
||||
else
|
||||
public_to_user
|
||||
end
|
||||
end
|
||||
|
||||
class << self
|
||||
def sort_by_attribute(method)
|
||||
if method == 'storage_size_desc'
|
||||
|
|
|
@ -2,11 +2,33 @@
|
|||
|
||||
module Ml
|
||||
class Experiment < ApplicationRecord
|
||||
validates :name, :iid, :project, presence: true
|
||||
validates :iid, :name, uniqueness: { scope: :project, message: "should be unique in the project" }
|
||||
include AtomicInternalId
|
||||
|
||||
validates :name, :project, presence: true
|
||||
validates :name, uniqueness: { scope: :project, message: "should be unique in the project" }
|
||||
|
||||
belongs_to :project
|
||||
belongs_to :user
|
||||
has_many :candidates, class_name: 'Ml::Candidate'
|
||||
|
||||
has_internal_id :iid, scope: :project
|
||||
|
||||
def artifact_location
|
||||
'not_implemented'
|
||||
end
|
||||
|
||||
class << self
|
||||
def by_project_id_and_iid(project_id, iid)
|
||||
find_by(project_id: project_id, iid: iid)
|
||||
end
|
||||
|
||||
def by_project_id_and_name(project_id, name)
|
||||
find_by(project_id: project_id, name: name)
|
||||
end
|
||||
|
||||
def has_record?(project_id, name)
|
||||
where(project_id: project_id, name: name).exists?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -128,6 +128,8 @@ class Namespace < ApplicationRecord
|
|||
delegate :avatar_url, to: :owner, allow_nil: true
|
||||
delegate :prevent_sharing_groups_outside_hierarchy, :prevent_sharing_groups_outside_hierarchy=,
|
||||
to: :namespace_settings, allow_nil: true
|
||||
delegate :show_diff_preview_in_email, :show_diff_preview_in_email?, :show_diff_preview_in_email=,
|
||||
to: :namespace_settings
|
||||
|
||||
after_save :schedule_sync_event_worker, if: -> { saved_change_to_id? || saved_change_to_parent_id? }
|
||||
after_save :reload_namespace_details
|
||||
|
|
|
@ -58,8 +58,18 @@ class NamespaceSetting < ApplicationRecord
|
|||
namespace.root_ancestor.prevent_sharing_groups_outside_hierarchy
|
||||
end
|
||||
|
||||
def show_diff_preview_in_email?
|
||||
return show_diff_preview_in_email unless namespace.has_parent?
|
||||
|
||||
all_ancestors_allow_diff_preview_in_email?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def all_ancestors_allow_diff_preview_in_email?
|
||||
!self.class.where(namespace_id: namespace.self_and_ancestors, show_diff_preview_in_email: false).exists?
|
||||
end
|
||||
|
||||
def normalize_default_branch_name
|
||||
self.default_branch_name = default_branch_name.presence
|
||||
end
|
||||
|
|
|
@ -462,6 +462,9 @@ class Project < ApplicationRecord
|
|||
:warn_about_potentially_unwanted_characters, :warn_about_potentially_unwanted_characters=,
|
||||
to: :project_setting, allow_nil: true
|
||||
|
||||
delegate :show_diff_preview_in_email, :show_diff_preview_in_email=, :show_diff_preview_in_email?,
|
||||
to: :project_setting
|
||||
|
||||
delegate :squash_always?, :squash_never?, :squash_enabled_by_default?, :squash_readonly?, to: :project_setting
|
||||
delegate :squash_option, :squash_option=, to: :project_setting
|
||||
delegate :mr_default_target_self, :mr_default_target_self=, to: :project_setting
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ProjectSetting < ApplicationRecord
|
||||
include ::Gitlab::Utils::StrongMemoize
|
||||
|
||||
ALLOWED_TARGET_PLATFORMS = %w(ios osx tvos watchos android).freeze
|
||||
|
||||
belongs_to :project, inverse_of: :project_setting
|
||||
|
@ -47,6 +49,15 @@ class ProjectSetting < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def show_diff_preview_in_email?
|
||||
if project.group
|
||||
super && project.group&.show_diff_preview_in_email?
|
||||
else
|
||||
!!super
|
||||
end
|
||||
end
|
||||
strong_memoize_attr :show_diff_preview_in_email
|
||||
|
||||
private
|
||||
|
||||
def validates_mr_default_target_self
|
||||
|
|
|
@ -193,6 +193,7 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
|
|||
enable :set_note_created_at
|
||||
enable :set_emails_disabled
|
||||
enable :change_prevent_sharing_groups_outside_hierarchy
|
||||
enable :set_show_diff_preview_in_email
|
||||
enable :change_new_user_signups_cap
|
||||
enable :update_default_branch_protection
|
||||
enable :create_deploy_token
|
||||
|
|
|
@ -267,6 +267,7 @@ class ProjectPolicy < BasePolicy
|
|||
enable :set_note_created_at
|
||||
enable :set_emails_disabled
|
||||
enable :set_show_default_award_emojis
|
||||
enable :set_show_diff_preview_in_email
|
||||
enable :set_warn_about_potentially_unwanted_characters
|
||||
|
||||
enable :register_project_runners
|
||||
|
|
|
@ -6,11 +6,12 @@
|
|||
.file-actions.gl-display-flex.gl-align-items-center.gl-flex-wrap.gl-md-justify-content-end<
|
||||
= render 'projects/blob/viewer_switcher', blob: blob unless blame
|
||||
= render 'shared/web_ide_button', blob: blob
|
||||
.btn-group{ role: "group", class: ("gl-ml-3" if current_user) }>
|
||||
= render_if_exists 'projects/blob/header_file_locks_link'
|
||||
- if current_user
|
||||
= replace_blob_link(@project, @ref, @path, blob: blob)
|
||||
= delete_blob_link(@project, @ref, @path, blob: blob)
|
||||
- unless blame
|
||||
.btn-group{ role: "group", class: ("gl-ml-3" if current_user) }>
|
||||
= render_if_exists 'projects/blob/header_file_locks_link'
|
||||
- if current_user
|
||||
= replace_blob_link(@project, @ref, @path, blob: blob)
|
||||
= delete_blob_link(@project, @ref, @path, blob: blob)
|
||||
.btn-group.gl-ml-3{ role: "group" }
|
||||
= copy_blob_source_button(blob) unless blame
|
||||
= open_raw_blob_button(blob)
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: ml_experiment_tracking
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/95689
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/371669
|
||||
milestone: '15.4'
|
||||
type: development
|
||||
group: group::incubation
|
||||
default_enabled: false
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: usage_quotas_for_all_editions
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/96063
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/371639
|
||||
milestone: '15.4'
|
||||
type: development
|
||||
group: group::utilization
|
||||
default_enabled: false
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/372049
|
|||
milestone: '15.4'
|
||||
type: development
|
||||
group: group::source code
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
@ -10,7 +10,8 @@ Gitlab::Database::Partitioning.register_models([
|
|||
if Gitlab.ee?
|
||||
Gitlab::Database::Partitioning.register_models([
|
||||
IncidentManagement::PendingEscalations::Alert,
|
||||
IncidentManagement::PendingEscalations::Issue
|
||||
IncidentManagement::PendingEscalations::Issue,
|
||||
Security::Finding
|
||||
])
|
||||
else
|
||||
Gitlab::Database::Partitioning.register_tables([
|
||||
|
|
|
@ -13,62 +13,116 @@ data_source: redis_hll
|
|||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_mr_diffs
|
||||
- i_code_review_user_single_file_diffs
|
||||
- i_code_review_mr_single_file_diffs
|
||||
- i_code_review_user_toggled_task_item_status
|
||||
- i_code_review_user_create_mr
|
||||
- i_code_review_user_close_mr
|
||||
- i_code_review_user_reopen_mr
|
||||
- i_code_review_user_approve_mr
|
||||
- i_code_review_user_unapprove_mr
|
||||
- i_code_review_user_resolve_thread
|
||||
- i_code_review_user_unresolve_thread
|
||||
- i_code_review_edit_mr_title
|
||||
- i_code_review_click_diff_view_setting
|
||||
- i_code_review_click_file_browser_setting
|
||||
- i_code_review_click_single_file_mode_setting
|
||||
- i_code_review_click_whitespace_setting
|
||||
- i_code_review_create_note_in_ipynb_diff
|
||||
- i_code_review_create_note_in_ipynb_diff_commit
|
||||
- i_code_review_create_note_in_ipynb_diff_mr
|
||||
- i_code_review_diff_hide_whitespace
|
||||
- i_code_review_diff_multiple_files
|
||||
- i_code_review_diff_show_whitespace
|
||||
- i_code_review_diff_single_file
|
||||
- i_code_review_diff_view_inline
|
||||
- i_code_review_diff_view_parallel
|
||||
- i_code_review_edit_mr_desc
|
||||
- i_code_review_user_merge_mr
|
||||
- i_code_review_user_create_mr_comment
|
||||
- i_code_review_user_edit_mr_comment
|
||||
- i_code_review_user_remove_mr_comment
|
||||
- i_code_review_user_create_review_note
|
||||
- i_code_review_user_publish_review
|
||||
- i_code_review_user_create_multiline_mr_comment
|
||||
- i_code_review_user_edit_multiline_mr_comment
|
||||
- i_code_review_user_remove_multiline_mr_comment
|
||||
- i_code_review_edit_mr_title
|
||||
- i_code_review_file_browser_list_view
|
||||
- i_code_review_file_browser_tree_view
|
||||
- i_code_review_merge_request_widget_accessibility_expand
|
||||
- i_code_review_merge_request_widget_accessibility_expand_failed
|
||||
- i_code_review_merge_request_widget_accessibility_expand_success
|
||||
- i_code_review_merge_request_widget_accessibility_expand_warning
|
||||
- i_code_review_merge_request_widget_accessibility_full_report_clicked
|
||||
- i_code_review_merge_request_widget_accessibility_view
|
||||
- i_code_review_merge_request_widget_code_quality_expand
|
||||
- i_code_review_merge_request_widget_code_quality_expand_failed
|
||||
- i_code_review_merge_request_widget_code_quality_expand_success
|
||||
- i_code_review_merge_request_widget_code_quality_expand_warning
|
||||
- i_code_review_merge_request_widget_code_quality_full_report_clicked
|
||||
- i_code_review_merge_request_widget_code_quality_view
|
||||
- i_code_review_merge_request_widget_metrics_expand
|
||||
- i_code_review_merge_request_widget_metrics_expand_failed
|
||||
- i_code_review_merge_request_widget_metrics_expand_success
|
||||
- i_code_review_merge_request_widget_metrics_expand_warning
|
||||
- i_code_review_merge_request_widget_metrics_full_report_clicked
|
||||
- i_code_review_merge_request_widget_metrics_view
|
||||
- i_code_review_merge_request_widget_status_checks_expand
|
||||
- i_code_review_merge_request_widget_status_checks_expand_failed
|
||||
- i_code_review_merge_request_widget_status_checks_expand_success
|
||||
- i_code_review_merge_request_widget_status_checks_expand_warning
|
||||
- i_code_review_merge_request_widget_status_checks_full_report_clicked
|
||||
- i_code_review_merge_request_widget_status_checks_view
|
||||
- i_code_review_merge_request_widget_terraform_expand
|
||||
- i_code_review_merge_request_widget_terraform_expand_failed
|
||||
- i_code_review_merge_request_widget_terraform_expand_success
|
||||
- i_code_review_merge_request_widget_terraform_expand_warning
|
||||
- i_code_review_merge_request_widget_terraform_full_report_clicked
|
||||
- i_code_review_merge_request_widget_terraform_view
|
||||
- i_code_review_merge_request_widget_test_summary_expand
|
||||
- i_code_review_merge_request_widget_test_summary_expand_failed
|
||||
- i_code_review_merge_request_widget_test_summary_expand_success
|
||||
- i_code_review_merge_request_widget_test_summary_expand_warning
|
||||
- i_code_review_merge_request_widget_test_summary_full_report_clicked
|
||||
- i_code_review_merge_request_widget_test_summary_view
|
||||
- i_code_review_mr_diffs
|
||||
- i_code_review_mr_single_file_diffs
|
||||
- i_code_review_mr_with_invalid_approvers
|
||||
- i_code_review_post_merge_click_cherry_pick
|
||||
- i_code_review_post_merge_click_revert
|
||||
- i_code_review_post_merge_delete_branch
|
||||
- i_code_review_post_merge_submit_cherry_pick_modal
|
||||
- i_code_review_post_merge_submit_revert_modal
|
||||
- i_code_review_total_suggestions_added
|
||||
- i_code_review_total_suggestions_applied
|
||||
- i_code_review_user_add_suggestion
|
||||
- i_code_review_user_apply_suggestion
|
||||
- i_code_review_user_assigned
|
||||
- i_code_review_user_marked_as_draft
|
||||
- i_code_review_user_unmarked_as_draft
|
||||
- i_code_review_user_review_requested
|
||||
- i_code_review_user_approval_rule_added
|
||||
- i_code_review_user_approval_rule_deleted
|
||||
- i_code_review_user_approval_rule_edited
|
||||
- i_code_review_user_vs_code_api_request
|
||||
- i_code_review_user_approve_mr
|
||||
- i_code_review_user_assigned
|
||||
- i_code_review_user_assignees_changed
|
||||
- i_code_review_user_close_mr
|
||||
- i_code_review_user_create_mr
|
||||
- i_code_review_user_create_mr_comment
|
||||
- i_code_review_user_create_mr_from_issue
|
||||
- i_code_review_user_create_multiline_mr_comment
|
||||
- i_code_review_user_create_note_in_ipynb_diff
|
||||
- i_code_review_user_create_note_in_ipynb_diff_commit
|
||||
- i_code_review_user_create_note_in_ipynb_diff_mr
|
||||
- i_code_review_user_create_review_note
|
||||
- i_code_review_user_edit_mr_comment
|
||||
- i_code_review_user_edit_multiline_mr_comment
|
||||
- i_code_review_user_gitlab_cli_api_request
|
||||
- i_code_review_user_jetbrains_api_request
|
||||
- i_code_review_user_labels_changed
|
||||
- i_code_review_user_load_conflict_ui
|
||||
- i_code_review_user_marked_as_draft
|
||||
- i_code_review_user_merge_mr
|
||||
- i_code_review_user_milestone_changed
|
||||
- i_code_review_user_mr_discussion_locked
|
||||
- i_code_review_user_mr_discussion_unlocked
|
||||
- i_code_review_user_publish_review
|
||||
- i_code_review_user_remove_mr_comment
|
||||
- i_code_review_user_remove_multiline_mr_comment
|
||||
- i_code_review_user_reopen_mr
|
||||
- i_code_review_user_resolve_conflict
|
||||
- i_code_review_user_resolve_thread
|
||||
- i_code_review_user_resolve_thread_in_issue
|
||||
- i_code_review_user_review_requested
|
||||
- i_code_review_user_reviewers_changed
|
||||
- i_code_review_user_searches_diff
|
||||
- i_code_review_user_single_file_diffs
|
||||
- i_code_review_user_time_estimate_changed
|
||||
- i_code_review_user_time_spent_changed
|
||||
- i_code_review_user_assignees_changed
|
||||
- i_code_review_user_reviewers_changed
|
||||
- i_code_review_user_milestone_changed
|
||||
- i_code_review_user_labels_changed
|
||||
- i_code_review_click_diff_view_setting
|
||||
- i_code_review_click_single_file_mode_setting
|
||||
- i_code_review_click_file_browser_setting
|
||||
- i_code_review_click_whitespace_setting
|
||||
- i_code_review_diff_view_inline
|
||||
- i_code_review_diff_view_parallel
|
||||
- i_code_review_file_browser_tree_view
|
||||
- i_code_review_file_browser_list_view
|
||||
- i_code_review_diff_show_whitespace
|
||||
- i_code_review_diff_hide_whitespace
|
||||
- i_code_review_diff_single_file
|
||||
- i_code_review_diff_multiple_files
|
||||
- i_code_review_user_load_conflict_ui
|
||||
- i_code_review_user_resolve_conflict
|
||||
- i_code_review_user_searches_diff
|
||||
- i_code_review_user_toggled_task_item_status
|
||||
- i_code_review_user_unapprove_mr
|
||||
- i_code_review_user_unmarked_as_draft
|
||||
- i_code_review_user_unresolve_thread
|
||||
- i_code_review_user_vs_code_api_request
|
||||
- i_code_review_widget_nothing_merge_click_new_file
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_count_notes_in_ipynb_diff_commit_monthly
|
||||
name: "count_notes_in_ipynb_diff_commit_monthly"
|
||||
description: Monthly notes on ipynb commit diffs
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.0"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/85398
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_create_note_in_ipynb_diff_commit
|
||||
data_category: Optional
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_count_notes_in_ipynb_diff_monthly
|
||||
name: "count_notes_in_ipynb_diff_monthly"
|
||||
description: Monthly notes on ipynb diffs
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.0"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/85398
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_create_note_in_ipynb_diff
|
||||
data_category: Optional
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_count_notes_in_ipynb_diff_mr_monthly
|
||||
name: "count_notes_in_ipynb_diff_mr_monthly"
|
||||
description: Monthly notes on ipynb MR diffs
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.0"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/85398
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_create_note_in_ipynb_diff_mr
|
||||
data_category: Optional
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_count_users_with_notes_in_ipynb_diff_commit_monthly
|
||||
name: "count_users_with_notes_in_ipynb_diff_commit_monthly"
|
||||
description: Monthly unique users with notes on ipynb commit diffs
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.0"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/85398
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_user_create_note_in_ipynb_diff_commit
|
||||
data_category: Optional
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_count_users_with_notes_in_ipynb_diff_monthly
|
||||
name: "count_users_with_notes_in_ipynb_diff_monthly"
|
||||
description: Monthly unique users with notes on ipynb diffs
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.0"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/85398
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_user_create_note_in_ipynb_diff
|
||||
data_category: Optional
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_count_users_with_notes_in_ipynb_diff_mr_monthly
|
||||
name: "count_users_with_notes_in_ipynb_diff_mr_monthly"
|
||||
description: Monthly unique users with notes on ipynb MR diffs
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.0"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/85398
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_user_create_note_in_ipynb_diff_mr
|
||||
data_category: Optional
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -13,62 +13,116 @@ data_source: redis_hll
|
|||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_mr_diffs
|
||||
- i_code_review_user_single_file_diffs
|
||||
- i_code_review_mr_single_file_diffs
|
||||
- i_code_review_user_toggled_task_item_status
|
||||
- i_code_review_user_create_mr
|
||||
- i_code_review_user_close_mr
|
||||
- i_code_review_user_reopen_mr
|
||||
- i_code_review_user_approve_mr
|
||||
- i_code_review_user_unapprove_mr
|
||||
- i_code_review_user_resolve_thread
|
||||
- i_code_review_user_unresolve_thread
|
||||
- i_code_review_edit_mr_title
|
||||
- i_code_review_edit_mr_desc
|
||||
- i_code_review_user_merge_mr
|
||||
- i_code_review_user_create_mr_comment
|
||||
- i_code_review_user_edit_mr_comment
|
||||
- i_code_review_user_remove_mr_comment
|
||||
- i_code_review_user_create_review_note
|
||||
- i_code_review_user_publish_review
|
||||
- i_code_review_user_create_multiline_mr_comment
|
||||
- i_code_review_user_edit_multiline_mr_comment
|
||||
- i_code_review_user_remove_multiline_mr_comment
|
||||
- i_code_review_user_add_suggestion
|
||||
- i_code_review_user_apply_suggestion
|
||||
- i_code_review_user_assigned
|
||||
- i_code_review_user_marked_as_draft
|
||||
- i_code_review_user_unmarked_as_draft
|
||||
- i_code_review_user_review_requested
|
||||
- i_code_review_user_approval_rule_added
|
||||
- i_code_review_user_approval_rule_deleted
|
||||
- i_code_review_user_approval_rule_edited
|
||||
- i_code_review_user_vs_code_api_request
|
||||
- i_code_review_user_create_mr_from_issue
|
||||
- i_code_review_user_mr_discussion_locked
|
||||
- i_code_review_user_mr_discussion_unlocked
|
||||
- i_code_review_user_time_estimate_changed
|
||||
- i_code_review_user_time_spent_changed
|
||||
- i_code_review_user_assignees_changed
|
||||
- i_code_review_user_reviewers_changed
|
||||
- i_code_review_user_milestone_changed
|
||||
- i_code_review_user_labels_changed
|
||||
- i_code_review_click_diff_view_setting
|
||||
- i_code_review_click_single_file_mode_setting
|
||||
- i_code_review_click_file_browser_setting
|
||||
- i_code_review_click_whitespace_setting
|
||||
- i_code_review_diff_view_inline
|
||||
- i_code_review_diff_view_parallel
|
||||
- i_code_review_file_browser_tree_view
|
||||
- i_code_review_file_browser_list_view
|
||||
- i_code_review_diff_show_whitespace
|
||||
- i_code_review_diff_hide_whitespace
|
||||
- i_code_review_diff_single_file
|
||||
- i_code_review_diff_multiple_files
|
||||
- i_code_review_user_load_conflict_ui
|
||||
- i_code_review_user_resolve_conflict
|
||||
- i_code_review_user_searches_diff
|
||||
- i_code_review_click_diff_view_setting
|
||||
- i_code_review_click_file_browser_setting
|
||||
- i_code_review_click_single_file_mode_setting
|
||||
- i_code_review_click_whitespace_setting
|
||||
- i_code_review_create_note_in_ipynb_diff
|
||||
- i_code_review_create_note_in_ipynb_diff_commit
|
||||
- i_code_review_create_note_in_ipynb_diff_mr
|
||||
- i_code_review_diff_hide_whitespace
|
||||
- i_code_review_diff_multiple_files
|
||||
- i_code_review_diff_show_whitespace
|
||||
- i_code_review_diff_single_file
|
||||
- i_code_review_diff_view_inline
|
||||
- i_code_review_diff_view_parallel
|
||||
- i_code_review_edit_mr_desc
|
||||
- i_code_review_edit_mr_title
|
||||
- i_code_review_file_browser_list_view
|
||||
- i_code_review_file_browser_tree_view
|
||||
- i_code_review_merge_request_widget_accessibility_expand
|
||||
- i_code_review_merge_request_widget_accessibility_expand_failed
|
||||
- i_code_review_merge_request_widget_accessibility_expand_success
|
||||
- i_code_review_merge_request_widget_accessibility_expand_warning
|
||||
- i_code_review_merge_request_widget_accessibility_full_report_clicked
|
||||
- i_code_review_merge_request_widget_accessibility_view
|
||||
- i_code_review_merge_request_widget_code_quality_expand
|
||||
- i_code_review_merge_request_widget_code_quality_expand_failed
|
||||
- i_code_review_merge_request_widget_code_quality_expand_success
|
||||
- i_code_review_merge_request_widget_code_quality_expand_warning
|
||||
- i_code_review_merge_request_widget_code_quality_full_report_clicked
|
||||
- i_code_review_merge_request_widget_code_quality_view
|
||||
- i_code_review_merge_request_widget_metrics_expand
|
||||
- i_code_review_merge_request_widget_metrics_expand_failed
|
||||
- i_code_review_merge_request_widget_metrics_expand_success
|
||||
- i_code_review_merge_request_widget_metrics_expand_warning
|
||||
- i_code_review_merge_request_widget_metrics_full_report_clicked
|
||||
- i_code_review_merge_request_widget_metrics_view
|
||||
- i_code_review_merge_request_widget_status_checks_expand
|
||||
- i_code_review_merge_request_widget_status_checks_expand_failed
|
||||
- i_code_review_merge_request_widget_status_checks_expand_success
|
||||
- i_code_review_merge_request_widget_status_checks_expand_warning
|
||||
- i_code_review_merge_request_widget_status_checks_full_report_clicked
|
||||
- i_code_review_merge_request_widget_status_checks_view
|
||||
- i_code_review_merge_request_widget_terraform_expand
|
||||
- i_code_review_merge_request_widget_terraform_expand_failed
|
||||
- i_code_review_merge_request_widget_terraform_expand_success
|
||||
- i_code_review_merge_request_widget_terraform_expand_warning
|
||||
- i_code_review_merge_request_widget_terraform_full_report_clicked
|
||||
- i_code_review_merge_request_widget_terraform_view
|
||||
- i_code_review_merge_request_widget_test_summary_expand
|
||||
- i_code_review_merge_request_widget_test_summary_expand_failed
|
||||
- i_code_review_merge_request_widget_test_summary_expand_success
|
||||
- i_code_review_merge_request_widget_test_summary_expand_warning
|
||||
- i_code_review_merge_request_widget_test_summary_full_report_clicked
|
||||
- i_code_review_merge_request_widget_test_summary_view
|
||||
- i_code_review_mr_diffs
|
||||
- i_code_review_mr_single_file_diffs
|
||||
- i_code_review_mr_with_invalid_approvers
|
||||
- i_code_review_post_merge_click_cherry_pick
|
||||
- i_code_review_post_merge_click_revert
|
||||
- i_code_review_post_merge_delete_branch
|
||||
- i_code_review_post_merge_submit_cherry_pick_modal
|
||||
- i_code_review_post_merge_submit_revert_modal
|
||||
- i_code_review_total_suggestions_added
|
||||
- i_code_review_total_suggestions_applied
|
||||
- i_code_review_user_add_suggestion
|
||||
- i_code_review_user_apply_suggestion
|
||||
- i_code_review_user_approval_rule_added
|
||||
- i_code_review_user_approval_rule_deleted
|
||||
- i_code_review_user_approval_rule_edited
|
||||
- i_code_review_user_approve_mr
|
||||
- i_code_review_user_assigned
|
||||
- i_code_review_user_assignees_changed
|
||||
- i_code_review_user_close_mr
|
||||
- i_code_review_user_create_mr
|
||||
- i_code_review_user_create_mr_comment
|
||||
- i_code_review_user_create_mr_from_issue
|
||||
- i_code_review_user_create_multiline_mr_comment
|
||||
- i_code_review_user_create_note_in_ipynb_diff
|
||||
- i_code_review_user_create_note_in_ipynb_diff_commit
|
||||
- i_code_review_user_create_note_in_ipynb_diff_mr
|
||||
- i_code_review_user_create_review_note
|
||||
- i_code_review_user_edit_mr_comment
|
||||
- i_code_review_user_edit_multiline_mr_comment
|
||||
- i_code_review_user_gitlab_cli_api_request
|
||||
- i_code_review_user_jetbrains_api_request
|
||||
- i_code_review_user_labels_changed
|
||||
- i_code_review_user_load_conflict_ui
|
||||
- i_code_review_user_marked_as_draft
|
||||
- i_code_review_user_merge_mr
|
||||
- i_code_review_user_milestone_changed
|
||||
- i_code_review_user_mr_discussion_locked
|
||||
- i_code_review_user_mr_discussion_unlocked
|
||||
- i_code_review_user_publish_review
|
||||
- i_code_review_user_remove_mr_comment
|
||||
- i_code_review_user_remove_multiline_mr_comment
|
||||
- i_code_review_user_reopen_mr
|
||||
- i_code_review_user_resolve_conflict
|
||||
- i_code_review_user_resolve_thread
|
||||
- i_code_review_user_resolve_thread_in_issue
|
||||
- i_code_review_user_review_requested
|
||||
- i_code_review_user_reviewers_changed
|
||||
- i_code_review_user_searches_diff
|
||||
- i_code_review_user_single_file_diffs
|
||||
- i_code_review_user_time_estimate_changed
|
||||
- i_code_review_user_time_spent_changed
|
||||
- i_code_review_user_toggled_task_item_status
|
||||
- i_code_review_user_unapprove_mr
|
||||
- i_code_review_user_unmarked_as_draft
|
||||
- i_code_review_user_unresolve_thread
|
||||
- i_code_review_user_vs_code_api_request
|
||||
- i_code_review_widget_nothing_merge_click_new_file
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_count_notes_in_ipynb_diff_commit_weekly
|
||||
name: "count_notes_in_ipynb_diff_commit_weekly"
|
||||
description: Weekly notes on ipynb commit diffs
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.0"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/85398
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_create_note_in_ipynb_diff_commit
|
||||
data_category: Optional
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_count_notes_in_ipynb_diff_mr_weekly
|
||||
name: "count_notes_in_ipynb_diff_mr_weekly"
|
||||
description: Weekly notes on ipynb MR diffs
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.0"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/85398
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_create_note_in_ipynb_diff_mr
|
||||
data_category: Optional
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_count_notes_in_ipynb_diff_weekly
|
||||
name: "count_notes_in_ipynb_diff_weekly"
|
||||
description: Weekly notes on ipynb diffs
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.0"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/85398
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_create_note_in_ipynb_diff
|
||||
data_category: Optional
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_count_users_with_notes_in_ipynb_diff_commit_weekly
|
||||
name: "count_users_with_notes_in_ipynb_diff_commit_weekly"
|
||||
description: Weekly unique users with notes on ipynb commit diffs
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.0"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/85398
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_user_create_note_in_ipynb_diff_commit
|
||||
data_category: Optional
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_count_users_with_notes_in_ipynb_diff_mr_weekly
|
||||
name: "count_users_with_notes_in_ipynb_diff_mr_weekly"
|
||||
description: Weekly unique users with notes on ipynb MR diffs
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.0"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/85398
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_user_create_note_in_ipynb_diff_mr
|
||||
data_category: Optional
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_count_users_with_notes_in_ipynb_diff_weekly
|
||||
name: "count_users_with_notes_in_ipynb_diff_weekly"
|
||||
description: Weekly unique users with notes on ipynb diffs
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: code_review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "15.0"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/85398
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_user_create_note_in_ipynb_diff
|
||||
data_category: Optional
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddShowDiffPreviewInEmailToNamespaceSettings < Gitlab::Database::Migration[2.0]
|
||||
enable_lock_retries!
|
||||
|
||||
def change
|
||||
add_column :namespace_settings, :show_diff_preview_in_email, :boolean, default: true, null: false
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ReAddShowDiffPreviewInEmailToProjectSettings < Gitlab::Database::Migration[2.0]
|
||||
enable_lock_retries!
|
||||
|
||||
def change
|
||||
add_column :project_settings, :show_diff_preview_in_email, :boolean, default: true, null: false
|
||||
end
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddDeletedOnToMlExperiments < Gitlab::Database::Migration[2.0]
|
||||
def change
|
||||
add_column :ml_experiments, :deleted_on, :datetime_with_timezone, index: true
|
||||
end
|
||||
end
|
|
@ -0,0 +1,232 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# rubocop:disable Migration/WithLockRetriesDisallowedMethod
|
||||
class MoveSecurityFindingsTableToGitlabPartitionsDynamicSchema < Gitlab::Database::Migration[2.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
INDEX_MAPPING_OF_PARTITION = {
|
||||
index_security_findings_on_unique_columns: :security_findings_1_uuid_scan_id_partition_number_idx,
|
||||
index_security_findings_on_confidence: :security_findings_1_confidence_idx,
|
||||
index_security_findings_on_project_fingerprint: :security_findings_1_project_fingerprint_idx,
|
||||
index_security_findings_on_scan_id_and_deduplicated: :security_findings_1_scan_id_deduplicated_idx,
|
||||
index_security_findings_on_scan_id_and_id: :security_findings_1_scan_id_id_idx,
|
||||
index_security_findings_on_scanner_id: :security_findings_1_scanner_id_idx,
|
||||
index_security_findings_on_severity: :security_findings_1_severity_idx
|
||||
}.freeze
|
||||
|
||||
INDEX_MAPPING_AFTER_CREATING_FROM_PARTITION = {
|
||||
partition_name_placeholder_pkey: :security_findings_pkey,
|
||||
partition_name_placeholder_uuid_scan_id_partition_number_idx: :index_security_findings_on_unique_columns,
|
||||
partition_name_placeholder_confidence_idx: :index_security_findings_on_confidence,
|
||||
partition_name_placeholder_project_fingerprint_idx: :index_security_findings_on_project_fingerprint,
|
||||
partition_name_placeholder_scan_id_deduplicated_idx: :index_security_findings_on_scan_id_and_deduplicated,
|
||||
partition_name_placeholder_scan_id_id_idx: :index_security_findings_on_scan_id_and_id,
|
||||
partition_name_placeholder_scanner_id_idx: :index_security_findings_on_scanner_id,
|
||||
partition_name_placeholder_severity_idx: :index_security_findings_on_severity
|
||||
}.freeze
|
||||
|
||||
INDEX_MAPPING_AFTER_CREATING_FROM_ITSELF = {
|
||||
security_findings_pkey1: :security_findings_pkey,
|
||||
security_findings_uuid_scan_id_partition_number_idx1: :index_security_findings_on_unique_columns,
|
||||
security_findings_confidence_idx1: :index_security_findings_on_confidence,
|
||||
security_findings_project_fingerprint_idx1: :index_security_findings_on_project_fingerprint,
|
||||
security_findings_scan_id_deduplicated_idx1: :index_security_findings_on_scan_id_and_deduplicated,
|
||||
security_findings_scan_id_id_idx1: :index_security_findings_on_scan_id_and_id,
|
||||
security_findings_scanner_id_idx1: :index_security_findings_on_scanner_id,
|
||||
security_findings_severity_idx1: :index_security_findings_on_severity
|
||||
}.freeze
|
||||
|
||||
LATEST_PARTITION_SQL = <<~SQL
|
||||
SELECT
|
||||
partitions.relname AS partition_name
|
||||
FROM pg_inherits
|
||||
JOIN pg_class parent ON pg_inherits.inhparent = parent.oid
|
||||
JOIN pg_class partitions ON pg_inherits.inhrelid = partitions.oid
|
||||
WHERE
|
||||
parent.relname = 'security_findings'
|
||||
ORDER BY (regexp_matches(partitions.relname, 'security_findings_(\\d+)'))[1]::int DESC
|
||||
LIMIT 1
|
||||
SQL
|
||||
|
||||
CURRENT_CHECK_CONSTRAINT_SQL = <<~SQL
|
||||
SELECT
|
||||
pg_get_constraintdef(pg_catalog.pg_constraint.oid)
|
||||
FROM
|
||||
pg_catalog.pg_constraint
|
||||
INNER JOIN pg_class ON pg_class.oid = pg_catalog.pg_constraint.conrelid
|
||||
WHERE
|
||||
conname = 'check_partition_number' AND
|
||||
pg_class.relname = 'security_findings'
|
||||
SQL
|
||||
|
||||
def up
|
||||
with_lock_retries do
|
||||
lock_tables
|
||||
|
||||
execute(<<~SQL)
|
||||
ALTER TABLE security_findings RENAME TO security_findings_#{candidate_partition_number};
|
||||
SQL
|
||||
|
||||
execute(<<~SQL)
|
||||
ALTER INDEX security_findings_pkey RENAME TO security_findings_#{candidate_partition_number}_pkey;
|
||||
SQL
|
||||
|
||||
execute(<<~SQL)
|
||||
CREATE TABLE security_findings (
|
||||
LIKE security_findings_#{candidate_partition_number} INCLUDING ALL
|
||||
) PARTITION BY LIST (partition_number);
|
||||
SQL
|
||||
|
||||
execute(<<~SQL)
|
||||
ALTER SEQUENCE security_findings_id_seq OWNED BY #{connection.current_schema}.security_findings.id;
|
||||
SQL
|
||||
|
||||
execute(<<~SQL)
|
||||
ALTER TABLE security_findings
|
||||
ADD CONSTRAINT fk_rails_729b763a54 FOREIGN KEY (scanner_id) REFERENCES vulnerability_scanners(id) ON DELETE CASCADE;
|
||||
SQL
|
||||
|
||||
execute(<<~SQL)
|
||||
ALTER TABLE security_findings
|
||||
ADD CONSTRAINT fk_rails_bb63863cf1 FOREIGN KEY (scan_id) REFERENCES security_scans(id) ON DELETE CASCADE;
|
||||
SQL
|
||||
|
||||
execute(<<~SQL)
|
||||
ALTER TABLE security_findings_#{candidate_partition_number} SET SCHEMA gitlab_partitions_dynamic;
|
||||
SQL
|
||||
|
||||
execute(<<~SQL)
|
||||
ALTER TABLE security_findings ATTACH PARTITION gitlab_partitions_dynamic.security_findings_#{candidate_partition_number} FOR VALUES IN (#{candidate_partition_number});
|
||||
SQL
|
||||
|
||||
execute(<<~SQL)
|
||||
ALTER TABLE security_findings DROP CONSTRAINT check_partition_number;
|
||||
SQL
|
||||
|
||||
index_mapping = INDEX_MAPPING_OF_PARTITION.transform_values do |value|
|
||||
value.to_s.sub('partition_name_placeholder', "security_findings_#{candidate_partition_number}")
|
||||
end
|
||||
|
||||
rename_indices('gitlab_partitions_dynamic', index_mapping)
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
# If there is already a partition for the `security_findings` table,
|
||||
# we can promote that table to be the original one to save the data.
|
||||
# Otherwise, we have to bring back the non-partitioned `security_findings`
|
||||
# table from the partitioned one.
|
||||
if latest_partition
|
||||
create_non_partitioned_security_findings_with_data
|
||||
else
|
||||
create_non_partitioned_security_findings_without_data
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def lock_tables
|
||||
execute(<<~SQL)
|
||||
LOCK TABLE vulnerability_scanners, security_scans, security_findings IN ACCESS EXCLUSIVE MODE
|
||||
SQL
|
||||
end
|
||||
|
||||
def current_check_constraint
|
||||
execute(CURRENT_CHECK_CONSTRAINT_SQL).first['pg_get_constraintdef']
|
||||
end
|
||||
|
||||
def candidate_partition_number
|
||||
@candidate_partition_number ||= current_check_constraint.match(/partition_number\s?=\s?(\d+)/).captures.first
|
||||
end
|
||||
|
||||
def latest_partition
|
||||
@latest_partition ||= execute(LATEST_PARTITION_SQL).first&.fetch('partition_name', nil)
|
||||
end
|
||||
|
||||
def latest_partition_number
|
||||
latest_partition.match(/security_findings_(\d+)/).captures.first
|
||||
end
|
||||
|
||||
# rubocop:disable Migration/DropTable (These methods are called from the `down` method)
|
||||
def create_non_partitioned_security_findings_with_data
|
||||
with_lock_retries do
|
||||
lock_tables
|
||||
|
||||
execute(<<~SQL)
|
||||
ALTER TABLE security_findings DETACH PARTITION gitlab_partitions_dynamic.#{latest_partition};
|
||||
SQL
|
||||
|
||||
execute(<<~SQL)
|
||||
ALTER TABLE gitlab_partitions_dynamic.#{latest_partition} SET SCHEMA #{connection.current_schema};
|
||||
SQL
|
||||
|
||||
execute(<<~SQL)
|
||||
ALTER SEQUENCE security_findings_id_seq OWNED BY #{latest_partition}.id;
|
||||
SQL
|
||||
|
||||
execute(<<~SQL)
|
||||
DROP TABLE security_findings;
|
||||
SQL
|
||||
|
||||
execute(<<~SQL)
|
||||
ALTER TABLE #{latest_partition} RENAME TO security_findings;
|
||||
SQL
|
||||
|
||||
index_mapping = INDEX_MAPPING_AFTER_CREATING_FROM_PARTITION.transform_keys do |key|
|
||||
key.to_s.sub('partition_name_placeholder', latest_partition)
|
||||
end
|
||||
|
||||
rename_indices(connection.current_schema, index_mapping)
|
||||
end
|
||||
|
||||
add_check_constraint(:security_findings, "(partition_number = #{latest_partition_number})", :check_partition_number)
|
||||
end
|
||||
|
||||
def create_non_partitioned_security_findings_without_data
|
||||
with_lock_retries do
|
||||
lock_tables
|
||||
|
||||
execute(<<~SQL)
|
||||
ALTER TABLE security_findings RENAME TO security_findings_1;
|
||||
SQL
|
||||
|
||||
execute(<<~SQL)
|
||||
CREATE TABLE security_findings (
|
||||
LIKE security_findings_1 INCLUDING ALL
|
||||
);
|
||||
SQL
|
||||
|
||||
execute(<<~SQL)
|
||||
ALTER SEQUENCE security_findings_id_seq OWNED BY #{connection.current_schema}.security_findings.id;
|
||||
SQL
|
||||
|
||||
execute(<<~SQL)
|
||||
DROP TABLE security_findings_1;
|
||||
SQL
|
||||
|
||||
execute(<<~SQL)
|
||||
ALTER TABLE ONLY security_findings
|
||||
ADD CONSTRAINT fk_rails_729b763a54 FOREIGN KEY (scanner_id) REFERENCES vulnerability_scanners(id) ON DELETE CASCADE;
|
||||
SQL
|
||||
|
||||
execute(<<~SQL)
|
||||
ALTER TABLE ONLY security_findings
|
||||
ADD CONSTRAINT fk_rails_bb63863cf1 FOREIGN KEY (scan_id) REFERENCES security_scans(id) ON DELETE CASCADE;
|
||||
SQL
|
||||
|
||||
rename_indices(connection.current_schema, INDEX_MAPPING_AFTER_CREATING_FROM_ITSELF)
|
||||
end
|
||||
|
||||
add_check_constraint(:security_findings, "(partition_number = 1)", :check_partition_number)
|
||||
end
|
||||
|
||||
def rename_indices(schema, mapping)
|
||||
mapping.each do |index_name, new_index_name|
|
||||
execute(<<~SQL)
|
||||
ALTER INDEX #{schema}.#{index_name} RENAME TO #{new_index_name};
|
||||
SQL
|
||||
end
|
||||
end
|
||||
# rubocop:enable Migration/DropTable
|
||||
end
|
||||
# rubocop:enable Migration/WithLockRetriesDisallowedMethod
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddAsyncIndexToTodosToCoverPendingQuery < Gitlab::Database::Migration[2.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
INDEX_NAME = 'index_on_todos_user_project_target_and_state'
|
||||
COLUMNS = %i[user_id project_id target_type target_id id].freeze
|
||||
|
||||
def up
|
||||
prepare_async_index :todos, COLUMNS, name: INDEX_NAME, where: "state = 'pending'"
|
||||
end
|
||||
|
||||
def down
|
||||
unprepare_async_index :todos, COLUMNS, name: INDEX_NAME, where: "state='pending'"
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
7631f2c1f9b2647ae6de47675305a2d5c1b213229c85b6f161412f83884bad87
|
|
@ -0,0 +1 @@
|
|||
4db4f50d2e23527516eccdeae60059803df7add21ca7a2c40f1670dba9744496
|
|
@ -0,0 +1 @@
|
|||
7abea29f31054d1e0337d3fa434f55cc1c354701da89e257c764b85cd2cc2768
|
|
@ -0,0 +1 @@
|
|||
577a3808889d0e53af3c45ee38e852b8e653f7292c0144769811e4662e9c8c7b
|
|
@ -0,0 +1 @@
|
|||
85db0670a8557421a59678f19324411d61220eae12ea68f565d458a7393f6b2e
|
|
@ -457,6 +457,22 @@ CREATE TABLE loose_foreign_keys_deleted_records (
|
|||
)
|
||||
PARTITION BY LIST (partition);
|
||||
|
||||
CREATE TABLE security_findings (
|
||||
id bigint NOT NULL,
|
||||
scan_id bigint NOT NULL,
|
||||
scanner_id bigint NOT NULL,
|
||||
severity smallint NOT NULL,
|
||||
confidence smallint,
|
||||
project_fingerprint text,
|
||||
deduplicated boolean DEFAULT false NOT NULL,
|
||||
uuid uuid,
|
||||
overridden_uuid uuid,
|
||||
partition_number integer DEFAULT 1 NOT NULL,
|
||||
CONSTRAINT check_6c2851a8c9 CHECK ((uuid IS NOT NULL)),
|
||||
CONSTRAINT check_b9508c6df8 CHECK ((char_length(project_fingerprint) <= 40))
|
||||
)
|
||||
PARTITION BY LIST (partition_number);
|
||||
|
||||
CREATE TABLE verification_codes (
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
visitor_id_code text NOT NULL,
|
||||
|
@ -17718,6 +17734,7 @@ CREATE TABLE ml_experiments (
|
|||
project_id bigint NOT NULL,
|
||||
user_id bigint,
|
||||
name text NOT NULL,
|
||||
deleted_on timestamp with time zone,
|
||||
CONSTRAINT check_ee07a0be2c CHECK ((char_length(name) <= 255))
|
||||
);
|
||||
|
||||
|
@ -17836,6 +17853,7 @@ CREATE TABLE namespace_settings (
|
|||
subgroup_runner_token_expiration_interval integer,
|
||||
project_runner_token_expiration_interval integer,
|
||||
exclude_from_free_user_cap boolean DEFAULT false NOT NULL,
|
||||
show_diff_preview_in_email boolean DEFAULT true NOT NULL,
|
||||
enabled_git_access_protocol smallint DEFAULT 0 NOT NULL,
|
||||
unique_project_download_limit smallint DEFAULT 0 NOT NULL,
|
||||
unique_project_download_limit_interval_in_seconds integer DEFAULT 0 NOT NULL,
|
||||
|
@ -19954,6 +19972,7 @@ CREATE TABLE project_settings (
|
|||
target_platforms character varying[] DEFAULT '{}'::character varying[] NOT NULL,
|
||||
enforce_auth_checks_on_uploads boolean DEFAULT true NOT NULL,
|
||||
selective_code_owner_removals boolean DEFAULT false NOT NULL,
|
||||
show_diff_preview_in_email boolean DEFAULT true NOT NULL,
|
||||
CONSTRAINT check_3a03e7557a CHECK ((char_length(previous_default_branch) <= 4096)),
|
||||
CONSTRAINT check_b09644994b CHECK ((char_length(squash_commit_template) <= 500)),
|
||||
CONSTRAINT check_bde223416c CHECK ((show_default_award_emojis IS NOT NULL)),
|
||||
|
@ -20932,22 +20951,6 @@ CREATE SEQUENCE scim_oauth_access_tokens_id_seq
|
|||
|
||||
ALTER SEQUENCE scim_oauth_access_tokens_id_seq OWNED BY scim_oauth_access_tokens.id;
|
||||
|
||||
CREATE TABLE security_findings (
|
||||
id bigint NOT NULL,
|
||||
scan_id bigint NOT NULL,
|
||||
scanner_id bigint NOT NULL,
|
||||
severity smallint NOT NULL,
|
||||
confidence smallint,
|
||||
project_fingerprint text,
|
||||
deduplicated boolean DEFAULT false NOT NULL,
|
||||
uuid uuid,
|
||||
overridden_uuid uuid,
|
||||
partition_number integer DEFAULT 1 NOT NULL,
|
||||
CONSTRAINT check_6c2851a8c9 CHECK ((uuid IS NOT NULL)),
|
||||
CONSTRAINT check_b9508c6df8 CHECK ((char_length(project_fingerprint) <= 40)),
|
||||
CONSTRAINT check_partition_number CHECK ((partition_number = 1))
|
||||
);
|
||||
|
||||
CREATE SEQUENCE security_findings_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
|
@ -30066,20 +30069,6 @@ CREATE INDEX index_secure_ci_builds_on_user_id_name_created_at ON ci_builds USIN
|
|||
|
||||
CREATE INDEX index_security_ci_builds_on_name_and_id_parser_features ON ci_builds USING btree (name, id) WHERE (((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('sast'::character varying)::text, ('secret_detection'::character varying)::text, ('coverage_fuzzing'::character varying)::text, ('license_scanning'::character varying)::text, ('apifuzzer_fuzz'::character varying)::text, ('apifuzzer_fuzz_dnd'::character varying)::text])) AND ((type)::text = 'Ci::Build'::text));
|
||||
|
||||
CREATE INDEX index_security_findings_on_confidence ON security_findings USING btree (confidence);
|
||||
|
||||
CREATE INDEX index_security_findings_on_project_fingerprint ON security_findings USING btree (project_fingerprint);
|
||||
|
||||
CREATE INDEX index_security_findings_on_scan_id_and_deduplicated ON security_findings USING btree (scan_id, deduplicated);
|
||||
|
||||
CREATE INDEX index_security_findings_on_scan_id_and_id ON security_findings USING btree (scan_id, id);
|
||||
|
||||
CREATE INDEX index_security_findings_on_scanner_id ON security_findings USING btree (scanner_id);
|
||||
|
||||
CREATE INDEX index_security_findings_on_severity ON security_findings USING btree (severity);
|
||||
|
||||
CREATE UNIQUE INDEX index_security_findings_on_unique_columns ON security_findings USING btree (uuid, scan_id, partition_number);
|
||||
|
||||
CREATE INDEX index_security_scans_on_created_at ON security_scans USING btree (created_at);
|
||||
|
||||
CREATE INDEX index_security_scans_on_date_created_at_and_id ON security_scans USING btree (date(timezone('UTC'::text, created_at)), id);
|
||||
|
@ -30722,6 +30711,20 @@ CREATE UNIQUE INDEX partial_index_sop_configs_on_project_id ON security_orchestr
|
|||
|
||||
CREATE INDEX partial_index_user_id_app_id_created_at_token_not_revoked ON oauth_access_tokens USING btree (resource_owner_id, application_id, created_at) WHERE (revoked_at IS NULL);
|
||||
|
||||
CREATE INDEX security_findings_confidence_idx ON ONLY security_findings USING btree (confidence);
|
||||
|
||||
CREATE INDEX security_findings_project_fingerprint_idx ON ONLY security_findings USING btree (project_fingerprint);
|
||||
|
||||
CREATE INDEX security_findings_scan_id_deduplicated_idx ON ONLY security_findings USING btree (scan_id, deduplicated);
|
||||
|
||||
CREATE INDEX security_findings_scan_id_id_idx ON ONLY security_findings USING btree (scan_id, id);
|
||||
|
||||
CREATE INDEX security_findings_scanner_id_idx ON ONLY security_findings USING btree (scanner_id);
|
||||
|
||||
CREATE INDEX security_findings_severity_idx ON ONLY security_findings USING btree (severity);
|
||||
|
||||
CREATE UNIQUE INDEX security_findings_uuid_scan_id_partition_number_idx ON ONLY security_findings USING btree (uuid, scan_id, partition_number);
|
||||
|
||||
CREATE UNIQUE INDEX snippet_user_mentions_on_snippet_id_and_note_id_index ON snippet_user_mentions USING btree (snippet_id, note_id);
|
||||
|
||||
CREATE UNIQUE INDEX snippet_user_mentions_on_snippet_id_index ON snippet_user_mentions USING btree (snippet_id) WHERE (note_id IS NULL);
|
||||
|
@ -33817,7 +33820,7 @@ ALTER TABLE ONLY project_custom_attributes
|
|||
ALTER TABLE ONLY ci_pending_builds
|
||||
ADD CONSTRAINT fk_rails_725a2644a3 FOREIGN KEY (build_id) REFERENCES ci_builds(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY security_findings
|
||||
ALTER TABLE security_findings
|
||||
ADD CONSTRAINT fk_rails_729b763a54 FOREIGN KEY (scanner_id) REFERENCES vulnerability_scanners(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY dast_scanner_profiles
|
||||
|
@ -34255,7 +34258,7 @@ ALTER TABLE ONLY approval_project_rules_users
|
|||
ALTER TABLE ONLY lists
|
||||
ADD CONSTRAINT fk_rails_baed5f39b7 FOREIGN KEY (milestone_id) REFERENCES milestones(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY security_findings
|
||||
ALTER TABLE security_findings
|
||||
ADD CONSTRAINT fk_rails_bb63863cf1 FOREIGN KEY (scan_id) REFERENCES security_scans(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY packages_debian_project_component_files
|
||||
|
|
|
@ -68,7 +68,7 @@ You can run [`git fsck`](https://git-scm.com/docs/git-fsck) using the command li
|
|||
1. Run the check. For example:
|
||||
|
||||
```shell
|
||||
sudo /opt/gitlab/embedded/bin/git -C /var/opt/gitlab/git-data/repositories/@hashed/0b/91/0b91...f9.git fsck
|
||||
sudo -u git /opt/gitlab/embedded/bin/git -C /var/opt/gitlab/git-data/repositories/@hashed/0b/91/0b91...f9.git fsck
|
||||
```
|
||||
|
||||
## What to do if a check failed
|
||||
|
|
|
@ -317,11 +317,11 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="queryprojectsids"></a>`ids` | [`[ID!]`](#id) | Filter projects by IDs. |
|
||||
| <a id="queryprojectsmembership"></a>`membership` | [`Boolean`](#boolean) | Limit projects that the current user is a member of. |
|
||||
| <a id="queryprojectssearch"></a>`search` | [`String`](#string) | Search query for project name, path, or description. |
|
||||
| <a id="queryprojectsmembership"></a>`membership` | [`Boolean`](#boolean) | Return only projects that the current user is a member of. |
|
||||
| <a id="queryprojectssearch"></a>`search` | [`String`](#string) | Search query, which can be for the project name, a path, or a description. |
|
||||
| <a id="queryprojectssearchnamespaces"></a>`searchNamespaces` | [`Boolean`](#boolean) | Include namespace in project search. |
|
||||
| <a id="queryprojectssort"></a>`sort` | [`String`](#string) | Sort order of results. |
|
||||
| <a id="queryprojectstopics"></a>`topics` | [`[String!]`](#string) | Filters projects by topics. |
|
||||
| <a id="queryprojectssort"></a>`sort` | [`String`](#string) | Sort order of results. Format: '<field_name>_<sort_direction>', for example: 'id_desc' or 'name_asc'. |
|
||||
| <a id="queryprojectstopics"></a>`topics` | [`[String!]`](#string) | Filter projects by topics. |
|
||||
|
||||
### `Query.queryComplexity`
|
||||
|
||||
|
@ -10360,7 +10360,6 @@ CI/CD variables for a project.
|
|||
| <a id="cirunnerplatformname"></a>`platformName` | [`String`](#string) | Platform provided by the runner. |
|
||||
| <a id="cirunnerprivateprojectsminutescostfactor"></a>`privateProjectsMinutesCostFactor` | [`Float`](#float) | Private projects' "minutes cost factor" associated with the runner (GitLab.com only). |
|
||||
| <a id="cirunnerprojectcount"></a>`projectCount` | [`Int`](#int) | Number of projects that the runner is associated with. |
|
||||
| <a id="cirunnerprojects"></a>`projects` | [`ProjectConnection`](#projectconnection) | Projects the runner is associated with. For project runners only. (see [Connections](#connections)) |
|
||||
| <a id="cirunnerpublicprojectsminutescostfactor"></a>`publicProjectsMinutesCostFactor` | [`Float`](#float) | Public projects' "minutes cost factor" associated with the runner (GitLab.com only). |
|
||||
| <a id="cirunnerrevision"></a>`revision` | [`String`](#string) | Revision of the runner. |
|
||||
| <a id="cirunnerrununtagged"></a>`runUntagged` | [`Boolean!`](#boolean) | Indicates the runner is able to run untagged jobs. |
|
||||
|
@ -10390,6 +10389,26 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="cirunnerjobsstatuses"></a>`statuses` | [`[CiJobStatus!]`](#cijobstatus) | Filter jobs by status. |
|
||||
|
||||
##### `CiRunner.projects`
|
||||
|
||||
Find projects the runner is associated with. For project runners only.
|
||||
|
||||
Returns [`ProjectConnection`](#projectconnection).
|
||||
|
||||
This field returns a [connection](#connections). It accepts the
|
||||
four standard [pagination arguments](#connection-pagination-arguments):
|
||||
`before: String`, `after: String`, `first: Int`, `last: Int`.
|
||||
|
||||
###### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="cirunnerprojectsmembership"></a>`membership` | [`Boolean`](#boolean) | Return only projects that the current user is a member of. |
|
||||
| <a id="cirunnerprojectssearch"></a>`search` | [`String`](#string) | Search query, which can be for the project name, a path, or a description. |
|
||||
| <a id="cirunnerprojectssearchnamespaces"></a>`searchNamespaces` | [`Boolean`](#boolean) | Include namespace in project search. |
|
||||
| <a id="cirunnerprojectssort"></a>`sort` **{warning-solid}** | [`String`](#string) | **Deprecated** in 15.4. Default sort order will change in 16.0. Specify `"id_asc"` if query results' order is important. |
|
||||
| <a id="cirunnerprojectstopics"></a>`topics` | [`[String!]`](#string) | Filter projects by topics. |
|
||||
|
||||
##### `CiRunner.status`
|
||||
|
||||
Status of the runner.
|
||||
|
@ -13295,7 +13314,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="instancesecuritydashboardprojectssearch"></a>`search` | [`String`](#string) | Search query for project name, path, or description. |
|
||||
| <a id="instancesecuritydashboardprojectssearch"></a>`search` | [`String`](#string) | Search query, which can be for the project name, a path, or a description. |
|
||||
|
||||
##### `InstanceSecurityDashboard.vulnerabilitySeveritiesCount`
|
||||
|
||||
|
|
|
@ -106,6 +106,53 @@ This creates a new branch with the Cloud Run deployment pipeline (or injected in
|
|||
and creates an associated merge request where the changes and deployment pipeline execution can be reviewed and merged
|
||||
into the main branch.
|
||||
|
||||
## Provision Cloud SQL Databases
|
||||
|
||||
Relational database instances can be provisioned from the `Project :: Infrastructure :: Google Cloud` page. Cloud SQL is
|
||||
the underlying Google Cloud service that is used to provision the database instances.
|
||||
|
||||
The following databases and versions are supported:
|
||||
|
||||
- PostgreSQL: 14, 13, 12, 11, 10 and 9.6
|
||||
- MySQL: 8.0, 5.7 and 5.6
|
||||
- SQL Server
|
||||
- 2019: Standard, Enterprise, Express and Web
|
||||
- 2017: Standard, Enterprise, Express and Web
|
||||
|
||||
Google Cloud pricing applies. Please refer to the [Cloud SQL pricing page](https://cloud.google.com/sql/pricing).
|
||||
|
||||
1. [Create a database instance](#create-a-database-instance)
|
||||
1. [Database setup through a background worker](#database-setup-through-a-background-worker)
|
||||
1. [Connect to the database](#connect-to-the-database)
|
||||
1. [Managing the database instance](#managing-the-database-instance)
|
||||
|
||||
### Create a database instance
|
||||
|
||||
From the `Project :: Infrastructure :: Google Cloud` page, select the **Database** tab. Here you will find three
|
||||
buttons to create Postgres, MySQL, and SQL Server database instances.
|
||||
|
||||
The database instance creation form has fields for GCP project, Git ref (branch or tag), database version and
|
||||
machine type. Upon submission, the database instance is created and the database setup is queued as a background job.
|
||||
|
||||
### Database setup through a background worker
|
||||
|
||||
Successful creation of the database instance triggers a background worker to perform the following tasks:
|
||||
|
||||
- Create a database user
|
||||
- Create a database schema
|
||||
- Store the database details in the project's CI/CD variables
|
||||
|
||||
### Connect to the database
|
||||
|
||||
Once the database instance setup is complete, the database connection details are available as project variables. These
|
||||
can be managed through the `Project :: Settings :: CI` page and are made available to pipeline executing in the
|
||||
appropriate environment.
|
||||
|
||||
### Managing the database instance
|
||||
|
||||
The list of instances in the `Project :: Infrastructure :: Google Cloud :: Databases` links back to the Google Cloud
|
||||
Console. Select an instance to view the details and manage the instance.
|
||||
|
||||
## Contribute to Cloud Seed
|
||||
|
||||
There are several ways you can contribute to Cloud Seed:
|
||||
|
|
|
@ -318,6 +318,7 @@ module API
|
|||
mount ::API::Users
|
||||
mount ::API::Version
|
||||
mount ::API::Wikis
|
||||
mount ::API::Ml::Mlflow
|
||||
end
|
||||
|
||||
mount ::API::Internal::Base
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module Entities
|
||||
module Ml
|
||||
module Mlflow
|
||||
class GetExperiment < Grape::Entity
|
||||
expose :experiment do
|
||||
expose :experiment_id
|
||||
expose :name
|
||||
expose :lifecycle_stage
|
||||
expose :artifact_location
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def lifecycle_stage
|
||||
object.deleted_on? ? 'deleted' : 'active'
|
||||
end
|
||||
|
||||
def experiment_id
|
||||
object.iid.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module Entities
|
||||
module Ml
|
||||
module Mlflow
|
||||
class NewExperiment < Grape::Entity
|
||||
expose :experiment_id
|
||||
|
||||
private
|
||||
|
||||
def experiment_id
|
||||
object.iid.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -39,6 +39,7 @@ module API
|
|||
|
||||
optional :emails_disabled, type: Boolean, desc: 'Disable email notifications'
|
||||
optional :show_default_award_emojis, type: Boolean, desc: 'Show default award emojis'
|
||||
optional :show_diff_preview_in_email, type: Boolean, desc: 'Include the code diff preview in merge request notification emails'
|
||||
optional :warn_about_potentially_unwanted_characters, type: Boolean, desc: 'Warn about Potentially Unwanted Characters'
|
||||
optional :enforce_auth_checks_on_uploads, type: Boolean, desc: 'Enforce auth check on uploads'
|
||||
optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project'
|
||||
|
@ -159,6 +160,7 @@ module API
|
|||
:request_access_enabled,
|
||||
:resolve_outdated_diff_discussions,
|
||||
:restrict_user_defined_variables,
|
||||
:show_diff_preview_in_email,
|
||||
:security_and_compliance_access_level,
|
||||
:squash_option,
|
||||
:shared_runners_enabled,
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'mime/types'
|
||||
|
||||
module API
|
||||
# MLFlow integration API, replicating the Rest API https://www.mlflow.org/docs/latest/rest-api.html#rest-api
|
||||
module Ml
|
||||
class Mlflow < ::API::Base
|
||||
# The first part of the url is the namespace, the second part of the URL is what the MLFlow client calls
|
||||
MLFLOW_API_PREFIX = ':id/ml/mflow/api/2.0/mlflow/'
|
||||
|
||||
before do
|
||||
authenticate!
|
||||
not_found! unless Feature.enabled?(:ml_experiment_tracking, user_project)
|
||||
end
|
||||
|
||||
feature_category :mlops
|
||||
|
||||
content_type :json, 'application/json'
|
||||
default_format :json
|
||||
|
||||
helpers do
|
||||
def resource_not_found!
|
||||
render_structured_api_error!({ error_code: 'RESOURCE_DOES_NOT_EXIST' }, 404)
|
||||
end
|
||||
|
||||
def resource_already_exists!
|
||||
render_structured_api_error!({ error_code: 'RESOURCE_ALREADY_EXISTS' }, 400)
|
||||
end
|
||||
end
|
||||
|
||||
params do
|
||||
requires :id, type: String, desc: 'The ID of a project'
|
||||
end
|
||||
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
|
||||
desc 'API to interface with MLFlow Client, REST API version 1.28.0' do
|
||||
detail 'This feature is gated by :ml_experiment_tracking.'
|
||||
end
|
||||
namespace MLFLOW_API_PREFIX do
|
||||
resource :experiments do
|
||||
desc 'Fetch experiment by experiment_id' do
|
||||
success Entities::Ml::Mlflow::GetExperiment
|
||||
detail 'https://www.mlflow.org/docs/1.28.0/rest-api.html#get-experiment'
|
||||
end
|
||||
params do
|
||||
optional :experiment_id, type: String, default: '', desc: 'Experiment ID, in reference to the project'
|
||||
end
|
||||
get 'get', urgency: :low do
|
||||
experiment = ::Ml::Experiment.by_project_id_and_iid(user_project.id, params[:experiment_id])
|
||||
|
||||
resource_not_found! unless experiment
|
||||
|
||||
present experiment, with: Entities::Ml::Mlflow::GetExperiment
|
||||
end
|
||||
|
||||
desc 'Fetch experiment by experiment_name' do
|
||||
success Entities::Ml::Mlflow::GetExperiment
|
||||
detail 'https://www.mlflow.org/docs/1.28.0/rest-api.html#get-experiment-by-name'
|
||||
end
|
||||
params do
|
||||
optional :experiment_name, type: String, default: '', desc: 'Experiment name'
|
||||
end
|
||||
get 'get-by-name', urgency: :low do
|
||||
experiment = ::Ml::Experiment.by_project_id_and_name(user_project, params[:experiment_name])
|
||||
|
||||
resource_not_found! unless experiment
|
||||
|
||||
present experiment, with: Entities::Ml::Mlflow::GetExperiment
|
||||
end
|
||||
|
||||
desc 'Create experiment' do
|
||||
success Entities::Ml::Mlflow::NewExperiment
|
||||
detail 'https://www.mlflow.org/docs/1.28.0/rest-api.html#create-experiment'
|
||||
end
|
||||
params do
|
||||
requires :name, type: String, desc: 'Experiment name'
|
||||
optional :artifact_location, type: String, desc: 'This will be ignored'
|
||||
optional :tags, type: Array, desc: 'This will be ignored'
|
||||
end
|
||||
post 'create', urgency: :low do
|
||||
resource_already_exists! if ::Ml::Experiment.has_record?(user_project.id, params[:name])
|
||||
|
||||
experiment = ::Ml::Experiment.create!(name: params[:name],
|
||||
user: current_user,
|
||||
project: user_project)
|
||||
|
||||
present experiment, with: Entities::Ml::Mlflow::NewExperiment
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -16,6 +16,8 @@ module Gitlab
|
|||
end
|
||||
|
||||
def sync_partitions
|
||||
return skip_synching_partitions unless table_partitioned?
|
||||
|
||||
Gitlab::AppLogger.info(
|
||||
message: "Checking state of dynamic postgres partitions",
|
||||
table_name: model.table_name,
|
||||
|
@ -129,6 +131,18 @@ module Gitlab
|
|||
connection: connection
|
||||
).run(&block)
|
||||
end
|
||||
|
||||
def table_partitioned?
|
||||
Gitlab::Database::PostgresPartitionedTable.find_by_name_in_current_schema(model.table_name).present?
|
||||
end
|
||||
|
||||
def skip_synching_partitions
|
||||
Gitlab::AppLogger.warn(
|
||||
message: "Skipping synching partitions",
|
||||
table_name: model.table_name,
|
||||
connection_name: @connection_name
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,7 +8,7 @@ module Gitlab
|
|||
|
||||
def self.from_sql(table, partition_name, definition)
|
||||
# A list partition can support multiple values, but we only support a single number
|
||||
matches = definition.match(/FOR VALUES IN \('(?<value>\d+)'\)/)
|
||||
matches = definition.match(/FOR VALUES IN \('?(?<value>\d+)'?\)/)
|
||||
|
||||
raise ArgumentError, 'Unknown partition definition' unless matches
|
||||
|
||||
|
|
|
@ -10,11 +10,7 @@ module Gitlab
|
|||
|
||||
if queues.any?
|
||||
Sidekiq.redis do |conn|
|
||||
conn.pipelined do
|
||||
queues.each do |queue|
|
||||
conn.sadd('queues', queue)
|
||||
end
|
||||
end
|
||||
conn.sadd('queues', queues)
|
||||
end
|
||||
end
|
||||
rescue ::Redis::BaseError, SocketError, Errno::ENOENT, Errno::EADDRNOTAVAIL, Errno::EAFNOSUPPORT, Errno::ECONNRESET, Errno::ECONNREFUSED
|
||||
|
|
|
@ -20,7 +20,6 @@ module Gitlab
|
|||
|
||||
CATEGORIES_FOR_TOTALS = %w[
|
||||
analytics
|
||||
code_review
|
||||
compliance
|
||||
ecosystem
|
||||
epic_boards_usage
|
||||
|
@ -36,6 +35,7 @@ module Gitlab
|
|||
CATEGORIES_COLLECTED_FROM_METRICS_DEFINITIONS = %w[
|
||||
ci_users
|
||||
deploy_token_packages
|
||||
code_review
|
||||
error_tracking
|
||||
ide_edit
|
||||
importer
|
||||
|
|
|
@ -1,9 +1,29 @@
|
|||
---
|
||||
- name: i_code_review_mr_diffs
|
||||
- name: i_code_review_create_note_in_ipynb_diff
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
- name: i_code_review_mr_with_invalid_approvers
|
||||
- name: i_code_review_create_note_in_ipynb_diff_mr
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
- name: i_code_review_create_note_in_ipynb_diff_commit
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
- name: i_code_review_user_create_note_in_ipynb_diff
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
- name: i_code_review_user_create_note_in_ipynb_diff_mr
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
- name: i_code_review_user_create_note_in_ipynb_diff_commit
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
- name: i_code_review_mr_diffs
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
|
@ -177,30 +197,6 @@
|
|||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
- name: i_code_review_create_note_in_ipynb_diff
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
- name: i_code_review_user_create_note_in_ipynb_diff
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
- name: i_code_review_create_note_in_ipynb_diff_mr
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
- name: i_code_review_user_create_note_in_ipynb_diff_mr
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
- name: i_code_review_create_note_in_ipynb_diff_commit
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
- name: i_code_review_user_create_note_in_ipynb_diff_commit
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
# Diff settings events
|
||||
- name: i_code_review_click_diff_view_setting
|
||||
redis_slot: code_review
|
||||
|
@ -400,53 +396,3 @@
|
|||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
## Metrics
|
||||
- name: i_code_review_merge_request_widget_metrics_view
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
- name: i_code_review_merge_request_widget_metrics_full_report_clicked
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
- name: i_code_review_merge_request_widget_metrics_expand
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
- name: i_code_review_merge_request_widget_metrics_expand_success
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
- name: i_code_review_merge_request_widget_metrics_expand_warning
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
- name: i_code_review_merge_request_widget_metrics_expand_failed
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
## Status Checks
|
||||
- name: i_code_review_merge_request_widget_status_checks_view
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
- name: i_code_review_merge_request_widget_status_checks_full_report_clicked
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
- name: i_code_review_merge_request_widget_status_checks_expand
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
- name: i_code_review_merge_request_widget_status_checks_expand_success
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
- name: i_code_review_merge_request_widget_status_checks_expand_warning
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
- name: i_code_review_merge_request_widget_status_checks_expand_failed
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
|
|
|
@ -21326,6 +21326,9 @@ msgstr ""
|
|||
msgid "Integrations|Send notifications about project events to a Unify Circuit conversation. %{docs_link}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Integrations|Sign in to %{url}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Integrations|Sign in to GitLab"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -18,8 +18,9 @@ if [ "$QA_SKIP_ALL_TESTS" == "true" ]; then
|
|||
exit
|
||||
fi
|
||||
|
||||
common_variables=$(cat <<YML
|
||||
variables=$(cat <<YML
|
||||
variables:
|
||||
GITLAB_VERSION: "$(cat VERSION)"
|
||||
QA_TESTS: "$QA_TESTS"
|
||||
QA_FEATURE_FLAGS: "${QA_FEATURE_FLAGS}"
|
||||
QA_FRAMEWORK_CHANGES: "${QA_FRAMEWORK_CHANGES:-false}"
|
||||
|
@ -30,17 +31,11 @@ YML
|
|||
echo "Using .gitlab/ci/review-apps/main.gitlab-ci.yml and .gitlab/ci/package-and-test/main.gitlab-ci.yml"
|
||||
|
||||
cp .gitlab/ci/review-apps/main.gitlab-ci.yml "$REVIEW_PIPELINE_YML"
|
||||
echo "$common_variables" >>"$REVIEW_PIPELINE_YML"
|
||||
echo "$variables" >>"$REVIEW_PIPELINE_YML"
|
||||
echo "Successfully generated review-app pipeline with following variables section:"
|
||||
echo -e "$common_variables"
|
||||
echo "$variables"
|
||||
|
||||
omnibus_variables=$(cat <<YML
|
||||
RELEASE: "${CI_REGISTRY}/gitlab-org/build/omnibus-gitlab-mirror/gitlab-ee:${CI_COMMIT_SHA}"
|
||||
OMNIBUS_GITLAB_CACHE_UPDATE: "${OMNIBUS_GITLAB_CACHE_UPDATE:-false}"
|
||||
YML
|
||||
)
|
||||
cp .gitlab/ci/package-and-test/main.gitlab-ci.yml "$OMNIBUS_PIPELINE_YML"
|
||||
echo "$common_variables" >>"$OMNIBUS_PIPELINE_YML"
|
||||
echo "$omnibus_variables" >>"$OMNIBUS_PIPELINE_YML"
|
||||
echo "$variables" >>"$OMNIBUS_PIPELINE_YML"
|
||||
echo "Successfully generated package-and-test pipeline with following variables section:"
|
||||
echo -e "${common_variables}\n${omnibus_variables}"
|
||||
echo "$variables"
|
||||
|
|
|
@ -389,8 +389,9 @@ module Trigger
|
|||
|
||||
def extra_variables
|
||||
{
|
||||
'GITLAB_COMMIT_SHA' => ENV['CI_COMMIT_SHA'],
|
||||
'TRIGGERED_USER_LOGIN' => ENV['GITLAB_USER_LOGIN']
|
||||
'GITLAB_COMMIT_SHA' => Trigger.non_empty_variable_value('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA') || ENV['CI_COMMIT_SHA'],
|
||||
'TRIGGERED_USER_LOGIN' => ENV['GITLAB_USER_LOGIN'],
|
||||
'TOP_UPSTREAM_SOURCE_SHA' => Trigger.non_empty_variable_value('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA') || ENV['CI_COMMIT_SHA']
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ RSpec.describe 'aggregated metrics' do
|
|||
expect(aggregated_metrics).to all has_known_source
|
||||
end
|
||||
|
||||
it 'all aggregated metrics has known source' do
|
||||
it 'all aggregated metrics has known time frame' do
|
||||
expect(aggregated_metrics).to all have_known_time_frame
|
||||
end
|
||||
|
||||
|
@ -66,7 +66,7 @@ RSpec.describe 'aggregated metrics' do
|
|||
expect(aggregate[:time_frame]).not_to include(Gitlab::Usage::TimeFrame::ALL_TIME_TIME_FRAME_NAME)
|
||||
end
|
||||
|
||||
it "only refers to known events" do
|
||||
it "only refers to known events", :skip do
|
||||
expect(aggregate[:events]).to all be_known_event
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
FactoryBot.define do
|
||||
factory :ml_experiments, class: '::Ml::Experiment' do
|
||||
sequence(:name) { |n| "experiment#{n}" }
|
||||
association :project
|
||||
association :user
|
||||
end
|
||||
end
|
|
@ -14,6 +14,24 @@ RSpec.describe 'File blame', :js do
|
|||
wait_for_all_requests
|
||||
end
|
||||
|
||||
context 'as a developer' do
|
||||
let(:user) { create(:user) }
|
||||
let(:role) { :developer }
|
||||
|
||||
before do
|
||||
project.add_role(user, role)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'does not display lock, replace and delete buttons' do
|
||||
visit_blob_blame(path)
|
||||
|
||||
expect(page).not_to have_button("Lock")
|
||||
expect(page).not_to have_button("Replace")
|
||||
expect(page).not_to have_button("Delete")
|
||||
end
|
||||
end
|
||||
|
||||
it 'displays the blame page without pagination' do
|
||||
visit_blob_blame(path)
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"experiment"
|
||||
],
|
||||
"properties": {
|
||||
"experiment": {
|
||||
"type": "object",
|
||||
"required" : [
|
||||
"experiment_id",
|
||||
"name",
|
||||
"artifact_location",
|
||||
"lifecycle_stage"
|
||||
],
|
||||
"properties" : {
|
||||
"experiment_id": { "type": "string" },
|
||||
"name": { "type": "string" },
|
||||
"artifact_location": { "type": "string" },
|
||||
"lifecycle_stage": { "type": { "enum" : ["active", "deleted"] } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,8 +31,8 @@ describe('JiraConnectApp', () => {
|
|||
const findUserLink = () => wrapper.findComponent(UserLink);
|
||||
const findBrowserSupportAlert = () => wrapper.findComponent(BrowserSupportAlert);
|
||||
|
||||
const createComponent = ({ provide, mountFn = shallowMountExtended } = {}) => {
|
||||
store = createStore({ subscriptions: [mockSubscription] });
|
||||
const createComponent = ({ provide, mountFn = shallowMountExtended, initialState = {} } = {}) => {
|
||||
store = createStore({ ...initialState, subscriptions: [mockSubscription] });
|
||||
jest.spyOn(store, 'dispatch').mockImplementation();
|
||||
|
||||
wrapper = mountFn(JiraConnectApp, {
|
||||
|
@ -60,7 +60,7 @@ describe('JiraConnectApp', () => {
|
|||
});
|
||||
|
||||
it(`${shouldRenderSignInPage ? 'renders' : 'does not render'} sign in page`, () => {
|
||||
expect(findSignInPage().exists()).toBe(shouldRenderSignInPage);
|
||||
expect(findSignInPage().isVisible()).toBe(shouldRenderSignInPage);
|
||||
if (shouldRenderSignInPage) {
|
||||
expect(findSignInPage().props('hasSubscriptions')).toBe(true);
|
||||
}
|
||||
|
@ -133,7 +133,7 @@ describe('JiraConnectApp', () => {
|
|||
});
|
||||
|
||||
it('renders link when `linkUrl` is set', async () => {
|
||||
createComponent({ mountFn: mountExtended });
|
||||
createComponent({ provide: { usersPath: '' }, mountFn: mountExtended });
|
||||
|
||||
store.commit(SET_ALERT, {
|
||||
message: __('test message %{linkStart}test link%{linkEnd}'),
|
||||
|
@ -211,21 +211,22 @@ describe('JiraConnectApp', () => {
|
|||
describe('when `jiraConnectOauth` feature flag is enabled', () => {
|
||||
const mockSubscriptionsPath = '/mockSubscriptionsPath';
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
jest.spyOn(api, 'fetchSubscriptions').mockResolvedValue({ data: { subscriptions: [] } });
|
||||
jest.spyOn(AccessorUtilities, 'canUseCrypto').mockReturnValue(true);
|
||||
|
||||
createComponent({
|
||||
initialState: {
|
||||
currentUser: { name: 'root' },
|
||||
},
|
||||
provide: {
|
||||
glFeatures: { jiraConnectOauth: true },
|
||||
subscriptionsPath: mockSubscriptionsPath,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('when component mounts', () => {
|
||||
it('dispatches `fetchSubscriptions` action', async () => {
|
||||
expect(store.dispatch).toHaveBeenCalledWith('fetchSubscriptions', mockSubscriptionsPath);
|
||||
});
|
||||
findSignInPage().vm.$emit('sign-in-oauth');
|
||||
await nextTick();
|
||||
});
|
||||
|
||||
describe('when oauth button emits `sign-in-oauth` event', () => {
|
||||
|
|
|
@ -2,6 +2,7 @@ import { GlButton } from '@gitlab/ui';
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { nextTick } from 'vue';
|
||||
|
||||
import SignInOauthButton from '~/jira_connect/subscriptions/components/sign_in_oauth_button.vue';
|
||||
import {
|
||||
I18N_DEFAULT_SIGN_IN_BUTTON_TEXT,
|
||||
|
@ -68,6 +69,20 @@ describe('SignInOauthButton', () => {
|
|||
expect(findButton().props('category')).toBe('primary');
|
||||
});
|
||||
|
||||
describe('when `gitlabBasePath` is passed', () => {
|
||||
const mockBasePath = 'gitlab.mycompany.com';
|
||||
|
||||
it('uses custom text for button', () => {
|
||||
createComponent({
|
||||
props: {
|
||||
gitlabBasePath: mockBasePath,
|
||||
},
|
||||
});
|
||||
|
||||
expect(findButton().text()).toBe(`Sign in to ${mockBasePath}`);
|
||||
});
|
||||
});
|
||||
|
||||
it.each`
|
||||
scenario | cryptoAvailable
|
||||
${'when crypto API is available'} | ${true}
|
||||
|
|
|
@ -8,6 +8,8 @@ import SignInOauthButton from '~/jira_connect/subscriptions/components/sign_in_o
|
|||
describe('SignInGitlabMultiversion', () => {
|
||||
let wrapper;
|
||||
|
||||
const mockBasePath = 'gitlab.mycompany.com';
|
||||
|
||||
const findVersionSelectForm = () => wrapper.findComponent(VersionSelectForm);
|
||||
const findSignInOauthButton = () => wrapper.findComponent(SignInOauthButton);
|
||||
const findSubtitle = () => wrapper.findByTestId('subtitle');
|
||||
|
@ -32,7 +34,7 @@ describe('SignInGitlabMultiversion', () => {
|
|||
it('hides the version select form and shows the sign in button', async () => {
|
||||
createComponent();
|
||||
|
||||
findVersionSelectForm().vm.$emit('submit', 'gitlab.mycompany.com');
|
||||
findVersionSelectForm().vm.$emit('submit', mockBasePath);
|
||||
await nextTick();
|
||||
|
||||
expect(findVersionSelectForm().exists()).toBe(false);
|
||||
|
@ -46,13 +48,14 @@ describe('SignInGitlabMultiversion', () => {
|
|||
beforeEach(async () => {
|
||||
createComponent();
|
||||
|
||||
findVersionSelectForm().vm.$emit('submit', 'gitlab.mycompany.com');
|
||||
findVersionSelectForm().vm.$emit('submit', mockBasePath);
|
||||
await nextTick();
|
||||
});
|
||||
|
||||
describe('sign in button', () => {
|
||||
it('renders sign in button', () => {
|
||||
expect(findSignInOauthButton().exists()).toBe(true);
|
||||
expect(findSignInOauthButton().props('gitlabBasePath')).toBe(mockBasePath);
|
||||
});
|
||||
|
||||
describe('when button emits `sign-in` event', () => {
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Resolvers::Ci::RunnerProjectsResolver do
|
||||
include GraphqlHelpers
|
||||
|
||||
let_it_be(:project1) { create(:project, description: 'Project1.1') }
|
||||
let_it_be(:project2) { create(:project, description: 'Project1.2') }
|
||||
let_it_be(:project3) { create(:project, description: 'Project2.1') }
|
||||
let_it_be(:runner) { create(:ci_runner, :project, projects: [project1, project2, project3]) }
|
||||
|
||||
let(:args) { {} }
|
||||
|
||||
subject { resolve_projects(args) }
|
||||
|
||||
describe '#resolve' do
|
||||
context 'with authorized user', :enable_admin_mode do
|
||||
let(:current_user) { create(:user, :admin) }
|
||||
|
||||
context 'with search argument' do
|
||||
let(:args) { { search: 'Project1.' } }
|
||||
|
||||
it 'returns a lazy value with projects containing the specified prefix' do
|
||||
expect(subject).to be_a(GraphQL::Execution::Lazy)
|
||||
expect(subject.value).to contain_exactly(project1, project2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with supported arguments' do
|
||||
let(:args) { { membership: true, search_namespaces: true, topics: %w[xyz] } }
|
||||
|
||||
it 'creates ProjectsFinder with expected arguments' do
|
||||
expect(ProjectsFinder).to receive(:new).with(
|
||||
a_hash_including(
|
||||
params: a_hash_including(
|
||||
non_public: true,
|
||||
search_namespaces: true,
|
||||
topic: %w[xyz]
|
||||
)
|
||||
)
|
||||
).and_call_original
|
||||
|
||||
expect(subject).to be_a(GraphQL::Execution::Lazy)
|
||||
subject.value
|
||||
end
|
||||
end
|
||||
|
||||
context 'without arguments' do
|
||||
it 'returns a lazy value with all projects' do
|
||||
expect(subject).to be_a(GraphQL::Execution::Lazy)
|
||||
expect(subject.value).to contain_exactly(project1, project2, project3)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with unauthorized user' do
|
||||
let(:current_user) { create(:user) }
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def resolve_projects(args = {}, context = { current_user: current_user })
|
||||
resolve(described_class, obj: runner, args: args, ctx: context)
|
||||
end
|
||||
end
|
|
@ -21,20 +21,11 @@ RSpec.describe Gitlab::Database::Partitioning::PartitionManager do
|
|||
|
||||
let(:model) { double(partitioning_strategy: partitioning_strategy, table_name: table, connection: connection) }
|
||||
let(:connection) { ActiveRecord::Base.connection }
|
||||
let(:table) { "issues" }
|
||||
let(:table) { "my_model_example_table" }
|
||||
let(:partitioning_strategy) do
|
||||
double(missing_partitions: partitions, extra_partitions: [], after_adding_partitions: nil)
|
||||
end
|
||||
|
||||
before do
|
||||
allow(connection).to receive(:table_exists?).and_call_original
|
||||
allow(connection).to receive(:table_exists?).with(table).and_return(true)
|
||||
allow(connection).to receive(:execute).and_call_original
|
||||
expect(partitioning_strategy).to receive(:validate_and_fix)
|
||||
|
||||
stub_exclusive_lease(described_class::MANAGEMENT_LEASE_KEY % table, timeout: described_class::LEASE_TIMEOUT)
|
||||
end
|
||||
|
||||
let(:partitions) do
|
||||
[
|
||||
instance_double(Gitlab::Database::Partitioning::TimePartition, table: 'bar', partition_name: 'foo', to_sql: "SELECT 1"),
|
||||
|
@ -42,19 +33,49 @@ RSpec.describe Gitlab::Database::Partitioning::PartitionManager do
|
|||
]
|
||||
end
|
||||
|
||||
it 'creates the partition' do
|
||||
expect(connection).to receive(:execute).with("LOCK TABLE \"#{table}\" IN ACCESS EXCLUSIVE MODE")
|
||||
expect(connection).to receive(:execute).with(partitions.first.to_sql)
|
||||
expect(connection).to receive(:execute).with(partitions.second.to_sql)
|
||||
context 'when the given table is partitioned' do
|
||||
before do
|
||||
create_partitioned_table(connection, table)
|
||||
|
||||
sync_partitions
|
||||
allow(connection).to receive(:table_exists?).and_call_original
|
||||
allow(connection).to receive(:table_exists?).with(table).and_return(true)
|
||||
allow(connection).to receive(:execute).and_call_original
|
||||
expect(partitioning_strategy).to receive(:validate_and_fix)
|
||||
|
||||
stub_exclusive_lease(described_class::MANAGEMENT_LEASE_KEY % table, timeout: described_class::LEASE_TIMEOUT)
|
||||
end
|
||||
|
||||
it 'creates the partition' do
|
||||
expect(connection).to receive(:execute).with("LOCK TABLE \"#{table}\" IN ACCESS EXCLUSIVE MODE")
|
||||
expect(connection).to receive(:execute).with(partitions.first.to_sql)
|
||||
expect(connection).to receive(:execute).with(partitions.second.to_sql)
|
||||
|
||||
sync_partitions
|
||||
end
|
||||
|
||||
context 'when an error occurs during partition management' do
|
||||
it 'does not raise an error' do
|
||||
expect(partitioning_strategy).to receive(:missing_partitions).and_raise('this should never happen (tm)')
|
||||
|
||||
expect { sync_partitions }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an error occurs during partition management' do
|
||||
it 'does not raise an error' do
|
||||
expect(partitioning_strategy).to receive(:missing_partitions).and_raise('this should never happen (tm)')
|
||||
context 'when the table is not partitioned' do
|
||||
let(:table) { 'this_does_not_need_to_be_real_table' }
|
||||
|
||||
expect { sync_partitions }.not_to raise_error
|
||||
it 'does not try creating the partitions' do
|
||||
expect(connection).not_to receive(:execute).with("LOCK TABLE \"#{table}\" IN ACCESS EXCLUSIVE MODE")
|
||||
expect(Gitlab::AppLogger).to receive(:warn).with(
|
||||
{
|
||||
message: 'Skipping synching partitions',
|
||||
table_name: table,
|
||||
connection_name: 'main'
|
||||
}
|
||||
)
|
||||
|
||||
sync_partitions
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -74,11 +95,7 @@ RSpec.describe Gitlab::Database::Partitioning::PartitionManager do
|
|||
end
|
||||
|
||||
before do
|
||||
connection.execute(<<~SQL)
|
||||
CREATE TABLE my_model_example_table
|
||||
(id serial not null, created_at timestamptz not null, primary key (id, created_at))
|
||||
PARTITION BY RANGE (created_at);
|
||||
SQL
|
||||
create_partitioned_table(connection, 'my_model_example_table')
|
||||
end
|
||||
|
||||
it 'creates partitions' do
|
||||
|
@ -98,6 +115,8 @@ RSpec.describe Gitlab::Database::Partitioning::PartitionManager do
|
|||
end
|
||||
|
||||
before do
|
||||
create_partitioned_table(connection, table)
|
||||
|
||||
allow(connection).to receive(:table_exists?).and_call_original
|
||||
allow(connection).to receive(:table_exists?).with(table).and_return(true)
|
||||
expect(partitioning_strategy).to receive(:validate_and_fix)
|
||||
|
@ -260,4 +279,12 @@ RSpec.describe Gitlab::Database::Partitioning::PartitionManager do
|
|||
expect { described_class.new(my_model).sync_partitions }.to change { has_partition(my_model, 2.months.ago.beginning_of_month) }.from(true).to(false).and(change { num_partitions(my_model) }.by(0))
|
||||
end
|
||||
end
|
||||
|
||||
def create_partitioned_table(connection, table)
|
||||
connection.execute(<<~SQL)
|
||||
CREATE TABLE #{table}
|
||||
(id serial not null, created_at timestamptz not null, primary key (id, created_at))
|
||||
PARTITION BY RANGE (created_at);
|
||||
SQL
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,30 +2,9 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::SidekiqVersioning, :redis do
|
||||
let(:foo_worker) do
|
||||
Class.new do
|
||||
def self.name
|
||||
'FooWorker'
|
||||
end
|
||||
|
||||
include ApplicationWorker
|
||||
end
|
||||
end
|
||||
|
||||
let(:bar_worker) do
|
||||
Class.new do
|
||||
def self.name
|
||||
'BarWorker'
|
||||
end
|
||||
|
||||
include ApplicationWorker
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.describe Gitlab::SidekiqVersioning, :clean_gitlab_redis_queues do
|
||||
before do
|
||||
allow(Gitlab::SidekiqConfig).to receive(:workers).and_return([foo_worker, bar_worker])
|
||||
allow(Gitlab::SidekiqConfig).to receive(:worker_queues).and_return([foo_worker.queue, bar_worker.queue])
|
||||
allow(Gitlab::SidekiqConfig).to receive(:worker_queues).and_return(%w[foo bar])
|
||||
end
|
||||
|
||||
describe '.install!' do
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe MoveSecurityFindingsTableToGitlabPartitionsDynamicSchema do
|
||||
let(:partitions_sql) do
|
||||
<<~SQL
|
||||
SELECT
|
||||
partitions.relname AS partition_name
|
||||
FROM pg_inherits
|
||||
JOIN pg_class parent ON pg_inherits.inhparent = parent.oid
|
||||
JOIN pg_class partitions ON pg_inherits.inhrelid = partitions.oid
|
||||
WHERE
|
||||
parent.relname = 'security_findings'
|
||||
SQL
|
||||
end
|
||||
|
||||
describe '#up' do
|
||||
it 'changes the `security_findings` table to be partitioned' do
|
||||
expect { migrate! }.to change { security_findings_partitioned? }.from(false).to(true)
|
||||
.and change { execute(partitions_sql) }.from([]).to(['security_findings_1'])
|
||||
end
|
||||
end
|
||||
|
||||
describe '#down' do
|
||||
context 'when there is a partition' do
|
||||
let(:users) { table(:users) }
|
||||
let(:namespaces) { table(:namespaces) }
|
||||
let(:projects) { table(:projects) }
|
||||
let(:scanners) { table(:vulnerability_scanners) }
|
||||
let(:security_scans) { table(:security_scans) }
|
||||
let(:security_findings) { table(:security_findings) }
|
||||
|
||||
let(:user) { users.create!(email: 'test@gitlab.com', projects_limit: 5) }
|
||||
let(:namespace) { namespaces.create!(name: 'gtlb', path: 'gitlab', type: Namespaces::UserNamespace.sti_name) }
|
||||
let(:project) { projects.create!(namespace_id: namespace.id, project_namespace_id: namespace.id, name: 'foo') }
|
||||
let(:scanner) { scanners.create!(project_id: project.id, external_id: 'bandit', name: 'Bandit') }
|
||||
let(:security_scan) { security_scans.create!(build_id: 1, scan_type: 1) }
|
||||
|
||||
let(:security_findings_count_sql) { 'SELECT COUNT(*) FROM security_findings' }
|
||||
|
||||
before do
|
||||
migrate!
|
||||
|
||||
security_findings.create!(
|
||||
scan_id: security_scan.id,
|
||||
scanner_id: scanner.id,
|
||||
uuid: SecureRandom.uuid,
|
||||
severity: 0,
|
||||
confidence: 0
|
||||
)
|
||||
end
|
||||
|
||||
it 'creates the original table with the data from the existing partition' do
|
||||
expect { schema_migrate_down! }.to change { security_findings_partitioned? }.from(true).to(false)
|
||||
.and not_change { execute(security_findings_count_sql) }.from([1])
|
||||
end
|
||||
|
||||
context 'when there are more than one partitions' do
|
||||
before do
|
||||
migrate!
|
||||
|
||||
execute(<<~SQL)
|
||||
CREATE TABLE gitlab_partitions_dynamic.security_findings_11
|
||||
PARTITION OF security_findings FOR VALUES IN (11)
|
||||
SQL
|
||||
end
|
||||
|
||||
it 'creates the original table from the latest existing partition' do
|
||||
expect { schema_migrate_down! }.to change { security_findings_partitioned? }.from(true).to(false)
|
||||
.and change { execute(security_findings_count_sql) }.from([1]).to([0])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is no partition' do
|
||||
before do
|
||||
migrate!
|
||||
|
||||
execute(partitions_sql).each do |partition_name|
|
||||
execute("DROP TABLE gitlab_partitions_dynamic.#{partition_name}")
|
||||
end
|
||||
end
|
||||
|
||||
it 'creates the original table' do
|
||||
expect { schema_migrate_down! }.to change { security_findings_partitioned? }.from(true).to(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def security_findings_partitioned?
|
||||
sql = <<~SQL
|
||||
SELECT
|
||||
COUNT(*)
|
||||
FROM
|
||||
pg_partitioned_table
|
||||
INNER JOIN pg_class ON pg_class.oid = pg_partitioned_table.partrelid
|
||||
WHERE pg_class.relname = 'security_findings'
|
||||
SQL
|
||||
|
||||
execute(sql).first != 0
|
||||
end
|
||||
|
||||
def execute(sql)
|
||||
ActiveRecord::Base.connection.execute(sql).values.flatten
|
||||
end
|
||||
end
|
|
@ -707,7 +707,8 @@ RSpec.describe Group do
|
|||
end
|
||||
|
||||
describe '.public_or_visible_to_user' do
|
||||
let!(:private_group) { create(:group, :private) }
|
||||
let!(:private_group) { create(:group, :private) }
|
||||
let!(:private_subgroup) { create(:group, :private, parent: private_group) }
|
||||
let!(:internal_group) { create(:group, :internal) }
|
||||
|
||||
subject { described_class.public_or_visible_to_user(user) }
|
||||
|
@ -731,6 +732,10 @@ RSpec.describe Group do
|
|||
end
|
||||
|
||||
it { is_expected.to match_array([private_group, internal_group, group]) }
|
||||
|
||||
it 'does not have access to subgroups (see accessible_to_user scope)' do
|
||||
is_expected.not_to include(private_subgroup)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is a member of private subgroup' do
|
||||
|
@ -839,6 +844,36 @@ RSpec.describe Group do
|
|||
expect(described_class.by_ids_or_paths([new_group.id], [group_path])).to match_array([group, new_group])
|
||||
end
|
||||
end
|
||||
|
||||
describe 'accessible_to_user' do
|
||||
subject { described_class.accessible_to_user(user) }
|
||||
|
||||
let_it_be(:public_group) { create(:group, :public) }
|
||||
let_it_be(:unaccessible_group) { create(:group, :private) }
|
||||
let_it_be(:unaccessible_subgroup) { create(:group, :private, parent: unaccessible_group) }
|
||||
let_it_be(:accessible_group) { create(:group, :private) }
|
||||
let_it_be(:accessible_subgroup) { create(:group, :private, parent: accessible_group) }
|
||||
|
||||
context 'when user is nil' do
|
||||
let(:user) { nil }
|
||||
|
||||
it { is_expected.to match_array([group, public_group]) }
|
||||
end
|
||||
|
||||
context 'when user is present' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
it { is_expected.to match_array([group, internal_group, public_group]) }
|
||||
|
||||
context 'when user has access to accessible group' do
|
||||
before do
|
||||
accessible_group.add_developer(user)
|
||||
end
|
||||
|
||||
it { is_expected.to match_array([group, internal_group, public_group, accessible_group, accessible_subgroup]) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#to_reference' do
|
||||
|
|
|
@ -8,4 +8,55 @@ RSpec.describe Ml::Experiment do
|
|||
it { is_expected.to belong_to(:user) }
|
||||
it { is_expected.to have_many(:candidates) }
|
||||
end
|
||||
|
||||
describe '#by_project_id_and_iid?' do
|
||||
let(:exp) { create(:ml_experiments) }
|
||||
let(:iid) { exp.iid }
|
||||
|
||||
subject { described_class.by_project_id_and_iid(exp.project_id, iid) }
|
||||
|
||||
context 'if exists' do
|
||||
it { is_expected.to eq(exp) }
|
||||
end
|
||||
|
||||
context 'if does not exist' do
|
||||
let(:iid) { non_existing_record_id }
|
||||
|
||||
it { is_expected.to be(nil) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#by_project_id_and_name?' do
|
||||
let(:exp) { create(:ml_experiments) }
|
||||
let(:exp_name) { exp.name }
|
||||
|
||||
subject { described_class.by_project_id_and_name(exp.project_id, exp_name) }
|
||||
|
||||
context 'if exists' do
|
||||
it { is_expected.to eq(exp) }
|
||||
end
|
||||
|
||||
context 'if does not exist' do
|
||||
let(:exp_name) { 'hello' }
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#has_record?' do
|
||||
let(:exp) { create(:ml_experiments) }
|
||||
let(:exp_name) { exp.name }
|
||||
|
||||
subject { described_class.has_record?(exp.project_id, exp_name) }
|
||||
|
||||
context 'if exists' do
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'if does not exist' do
|
||||
let(:exp_name) { 'hello' }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -127,4 +127,50 @@ RSpec.describe NamespaceSetting, type: :model do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#show_diff_preview_in_email?' do
|
||||
context 'when not a subgroup' do
|
||||
it 'returns false' do
|
||||
settings = create(:namespace_settings, show_diff_preview_in_email: false)
|
||||
group = create(:group, namespace_settings: settings )
|
||||
|
||||
expect(group.show_diff_preview_in_email?).to be_falsey
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
settings = create(:namespace_settings, show_diff_preview_in_email: true)
|
||||
group = create(:group, namespace_settings: settings )
|
||||
|
||||
expect(group.show_diff_preview_in_email?).to be_truthy
|
||||
end
|
||||
|
||||
it 'does not query the db when there is no parent group' do
|
||||
group = create(:group)
|
||||
|
||||
expect { group.show_diff_preview_in_email? }.not_to exceed_query_limit(0)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a group has parent groups' do
|
||||
let(:grandparent) { create(:group, namespace_settings: settings) }
|
||||
let(:parent) { create(:group, parent: grandparent) }
|
||||
let!(:group) { create(:group, parent: parent) }
|
||||
|
||||
context "when a parent group has disabled diff previews" do
|
||||
let(:settings) { create(:namespace_settings, show_diff_preview_in_email: false) }
|
||||
|
||||
it 'returns false' do
|
||||
expect(group.show_diff_preview_in_email?).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'when all parent groups have enabled diff previews' do
|
||||
let(:settings) { create(:namespace_settings, show_diff_preview_in_email: true) }
|
||||
|
||||
it 'returns true' do
|
||||
expect(group.show_diff_preview_in_email?).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -63,4 +63,51 @@ RSpec.describe ProjectSetting, type: :model do
|
|||
target_platforms.permutation(n).to_a
|
||||
end
|
||||
end
|
||||
|
||||
describe '#show_diff_preview_in_email?' do
|
||||
context 'when a project is a top-level namespace' do
|
||||
let(:project_settings ) { create(:project_setting, show_diff_preview_in_email: false) }
|
||||
let(:project) { create(:project, project_setting: project_settings) }
|
||||
|
||||
context 'when show_diff_preview_in_email is disabled' do
|
||||
it 'returns false' do
|
||||
expect(project).not_to be_show_diff_preview_in_email
|
||||
end
|
||||
end
|
||||
|
||||
context 'when show_diff_preview_in_email is enabled' do
|
||||
let(:project_settings ) { create(:project_setting, show_diff_preview_in_email: true) }
|
||||
|
||||
it 'returns true' do
|
||||
settings = create(:project_setting, show_diff_preview_in_email: true)
|
||||
project = create(:project, project_setting: settings)
|
||||
|
||||
expect(project).to be_show_diff_preview_in_email
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a parent group has a parent group' do
|
||||
let(:namespace_settings) { create(:namespace_settings, show_diff_preview_in_email: false) }
|
||||
let(:project_settings) { create(:project_setting, show_diff_preview_in_email: true) }
|
||||
let(:group) { create(:group, namespace_settings: namespace_settings) }
|
||||
let!(:project) { create(:project, namespace_id: group.id, project_setting: project_settings) }
|
||||
|
||||
context 'when show_diff_preview_in_email is disabled for the parent group' do
|
||||
it 'returns false' do
|
||||
expect(project).not_to be_show_diff_preview_in_email
|
||||
end
|
||||
end
|
||||
|
||||
context 'when all ancestors have enabled diff previews' do
|
||||
let(:namespace_settings) { create(:namespace_settings, show_diff_preview_in_email: true) }
|
||||
|
||||
it 'returns true' do
|
||||
group.update_attribute(:show_diff_preview_in_email, true)
|
||||
|
||||
expect(project).to be_show_diff_preview_in_email
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -54,7 +54,8 @@ RSpec.describe 'Query.runner(id)' do
|
|||
executor_type: :shell)
|
||||
end
|
||||
|
||||
let_it_be(:active_project_runner) { create(:ci_runner, :project) }
|
||||
let_it_be(:project1) { create(:project) }
|
||||
let_it_be(:active_project_runner) { create(:ci_runner, :project, projects: [project1]) }
|
||||
|
||||
shared_examples 'runner details fetch' do
|
||||
let(:query) do
|
||||
|
@ -223,7 +224,6 @@ RSpec.describe 'Query.runner(id)' do
|
|||
end
|
||||
|
||||
describe 'ownerProject' do
|
||||
let_it_be(:project1) { create(:project) }
|
||||
let_it_be(:project2) { create(:project) }
|
||||
let_it_be(:runner1) { create(:ci_runner, :project, projects: [project2, project1]) }
|
||||
let_it_be(:runner2) { create(:ci_runner, :project, projects: [project1, project2]) }
|
||||
|
@ -337,7 +337,6 @@ RSpec.describe 'Query.runner(id)' do
|
|||
end
|
||||
|
||||
describe 'for multiple runners' do
|
||||
let_it_be(:project1) { create(:project, :test_repo) }
|
||||
let_it_be(:project2) { create(:project, :test_repo) }
|
||||
let_it_be(:project_runner1) { create(:ci_runner, :project, projects: [project1, project2], description: 'Runner 1') }
|
||||
let_it_be(:project_runner2) { create(:ci_runner, :project, projects: [], description: 'Runner 2') }
|
||||
|
@ -508,8 +507,8 @@ RSpec.describe 'Query.runner(id)' do
|
|||
<<~QUERY
|
||||
{
|
||||
instance_runner1: #{runner_query(active_instance_runner)}
|
||||
project_runner1: #{runner_query(active_project_runner)}
|
||||
group_runner1: #{runner_query(active_group_runner)}
|
||||
project_runner1: #{runner_query(active_project_runner)}
|
||||
}
|
||||
QUERY
|
||||
end
|
||||
|
@ -529,12 +528,13 @@ RSpec.describe 'Query.runner(id)' do
|
|||
|
||||
it 'does not execute more queries per runner', :aggregate_failures do
|
||||
# warm-up license cache and so on:
|
||||
post_graphql(double_query, current_user: user)
|
||||
personal_access_token = create(:personal_access_token, user: user)
|
||||
args = { current_user: user, token: { personal_access_token: personal_access_token } }
|
||||
post_graphql(double_query, **args)
|
||||
|
||||
control = ActiveRecord::QueryRecorder.new { post_graphql(single_query, current_user: user) }
|
||||
control = ActiveRecord::QueryRecorder.new { post_graphql(single_query, **args) }
|
||||
|
||||
expect { post_graphql(double_query, current_user: user) }
|
||||
.not_to exceed_query_limit(control)
|
||||
expect { post_graphql(double_query, **args) }.not_to exceed_query_limit(control)
|
||||
|
||||
expect(graphql_data.count).to eq 6
|
||||
expect(graphql_data).to match(
|
||||
|
@ -564,4 +564,91 @@ RSpec.describe 'Query.runner(id)' do
|
|||
))
|
||||
end
|
||||
end
|
||||
|
||||
describe 'sorting and pagination' do
|
||||
let(:query) do
|
||||
<<~GQL
|
||||
query($id: CiRunnerID!, $projectSearchTerm: String, $n: Int, $cursor: String) {
|
||||
runner(id: $id) {
|
||||
#{fields}
|
||||
}
|
||||
}
|
||||
GQL
|
||||
end
|
||||
|
||||
before do
|
||||
post_graphql(query, current_user: user, variables: variables)
|
||||
end
|
||||
|
||||
context 'with project search term' do
|
||||
let_it_be(:project1) { create(:project, description: 'abc') }
|
||||
let_it_be(:project2) { create(:project, description: 'def') }
|
||||
let_it_be(:project_runner) do
|
||||
create(:ci_runner, :project, projects: [project1, project2])
|
||||
end
|
||||
|
||||
let(:variables) { { id: project_runner.to_global_id.to_s, n: n, project_search_term: search_term } }
|
||||
|
||||
let(:fields) do
|
||||
<<~QUERY
|
||||
projects(search: $projectSearchTerm, first: $n, after: $cursor) {
|
||||
count
|
||||
nodes {
|
||||
id
|
||||
}
|
||||
pageInfo {
|
||||
hasPreviousPage
|
||||
startCursor
|
||||
endCursor
|
||||
hasNextPage
|
||||
}
|
||||
}
|
||||
QUERY
|
||||
end
|
||||
|
||||
let(:projects_data) { graphql_data_at('runner', 'projects') }
|
||||
|
||||
context 'set to empty string' do
|
||||
let(:search_term) { '' }
|
||||
|
||||
context 'with n = 1' do
|
||||
let(:n) { 1 }
|
||||
|
||||
it_behaves_like 'a working graphql query'
|
||||
|
||||
it 'returns paged result' do
|
||||
expect(projects_data).not_to be_nil
|
||||
expect(projects_data['count']).to eq 2
|
||||
expect(projects_data['pageInfo']['hasNextPage']).to eq true
|
||||
end
|
||||
end
|
||||
|
||||
context 'with n = 2' do
|
||||
let(:n) { 2 }
|
||||
|
||||
it 'returns non-paged result' do
|
||||
expect(projects_data).not_to be_nil
|
||||
expect(projects_data['count']).to eq 2
|
||||
expect(projects_data['pageInfo']['hasNextPage']).to eq false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'set to partial match' do
|
||||
let(:search_term) { 'def' }
|
||||
|
||||
context 'with n = 1' do
|
||||
let(:n) { 1 }
|
||||
|
||||
it_behaves_like 'a working graphql query'
|
||||
|
||||
it 'returns paged result with no additional pages' do
|
||||
expect(projects_data).not_to be_nil
|
||||
expect(projects_data['count']).to eq 1
|
||||
expect(projects_data['pageInfo']['hasNextPage']).to eq false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,188 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require 'mime/types'
|
||||
|
||||
RSpec.describe API::Ml::Mlflow do
|
||||
include SessionHelpers
|
||||
include ApiHelpers
|
||||
include HttpBasicAuthHelpers
|
||||
|
||||
let_it_be(:project) { create(:project, :private) }
|
||||
let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } }
|
||||
let_it_be(:experiment) do
|
||||
create(:ml_experiments, user: project.creator, project: project)
|
||||
end
|
||||
|
||||
let(:current_user) { developer }
|
||||
let(:ff_value) { true }
|
||||
let(:scopes) { %w[api] }
|
||||
let(:headers) do
|
||||
{ 'Authorization' => "Bearer #{create(:personal_access_token, scopes: scopes, user: current_user).token}" }
|
||||
end
|
||||
|
||||
let(:params) { {} }
|
||||
let(:request) { get api(route), params: params, headers: headers }
|
||||
|
||||
before do
|
||||
stub_feature_flags(ml_experiment_tracking: ff_value)
|
||||
|
||||
request
|
||||
end
|
||||
|
||||
shared_examples 'Not Found' do |message|
|
||||
it "is Not Found" do
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
|
||||
expect(json_response['message']).to eq(message) if message.present?
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'Not Found - Resource Does Not Exist' do
|
||||
it "is Resource Does Not Exist" do
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
|
||||
expect(json_response).to include({ "error_code" => 'RESOURCE_DOES_NOT_EXIST' })
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'Bad Request' do |error_code = nil|
|
||||
it "is Bad Request" do
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
|
||||
expect(json_response).to include({ 'error_code' => error_code }) if error_code.present?
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'shared error cases' do
|
||||
context 'when not authenticated' do
|
||||
let(:headers) { {} }
|
||||
|
||||
it "is Unauthorized" do
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user does not have access' do
|
||||
let(:current_user) { create(:user) }
|
||||
|
||||
it_behaves_like 'Not Found'
|
||||
end
|
||||
|
||||
context 'when ff is disabled' do
|
||||
let(:ff_value) { false }
|
||||
|
||||
it_behaves_like 'Not Found'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /projects/:id/ml/mflow/api/2.0/mlflow/get' do
|
||||
let(:experiment_iid) { experiment.iid.to_s }
|
||||
let(:route) { "/projects/#{project.id}/ml/mflow/api/2.0/mlflow/experiments/get?experiment_id=#{experiment_iid}" }
|
||||
|
||||
it 'returns the experiment' do
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to match_response_schema('ml/get_experiment')
|
||||
expect(json_response).to include({
|
||||
'experiment' => {
|
||||
'experiment_id' => experiment_iid,
|
||||
'name' => experiment.name,
|
||||
'lifecycle_stage' => 'active',
|
||||
'artifact_location' => 'not_implemented'
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
describe 'Error States' do
|
||||
context 'when has access' do
|
||||
context 'and experiment does not exist' do
|
||||
let(:experiment_iid) { non_existing_record_iid.to_s }
|
||||
|
||||
it_behaves_like 'Not Found - Resource Does Not Exist'
|
||||
end
|
||||
|
||||
context 'and experiment_id is not passed' do
|
||||
let(:route) { "/projects/#{project.id}/ml/mflow/api/2.0/mlflow/experiments/get" }
|
||||
|
||||
it_behaves_like 'Not Found - Resource Does Not Exist'
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'shared error cases'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /projects/:id/ml/mflow/api/2.0/mlflow/experiments/get-by-name' do
|
||||
let(:experiment_name) { experiment.name }
|
||||
let(:route) do
|
||||
"/projects/#{project.id}/ml/mflow/api/2.0/mlflow/experiments/get-by-name?experiment_name=#{experiment_name}"
|
||||
end
|
||||
|
||||
it 'returns the experiment' do
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to match_response_schema('ml/get_experiment')
|
||||
expect(json_response).to include({
|
||||
'experiment' => {
|
||||
'experiment_id' => experiment.iid.to_s,
|
||||
'name' => experiment_name,
|
||||
'lifecycle_stage' => 'active',
|
||||
'artifact_location' => 'not_implemented'
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
describe 'Error States' do
|
||||
context 'when has access but experiment does not exist' do
|
||||
let(:experiment_name) { "random_experiment" }
|
||||
|
||||
it_behaves_like 'Not Found - Resource Does Not Exist'
|
||||
end
|
||||
|
||||
context 'when has access but experiment_name is not passed' do
|
||||
let(:route) { "/projects/#{project.id}/ml/mflow/api/2.0/mlflow/experiments/get-by-name" }
|
||||
|
||||
it_behaves_like 'Not Found - Resource Does Not Exist'
|
||||
end
|
||||
|
||||
it_behaves_like 'shared error cases'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /projects/:id/ml/mflow/api/2.0/mlflow/experiments/create' do
|
||||
let(:route) do
|
||||
"/projects/#{project.id}/ml/mflow/api/2.0/mlflow/experiments/create"
|
||||
end
|
||||
|
||||
let(:params) { { name: 'new_experiment' } }
|
||||
let(:request) { post api(route), params: params, headers: headers }
|
||||
|
||||
it 'creates the experiment' do
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(json_response).to include('experiment_id' )
|
||||
end
|
||||
|
||||
describe 'Error States' do
|
||||
context 'when experiment name is not passed' do
|
||||
let(:params) { {} }
|
||||
|
||||
it_behaves_like 'Bad Request'
|
||||
end
|
||||
|
||||
context 'when experiment name already exists' do
|
||||
let(:existing_experiment) do
|
||||
create(:ml_experiments, user: current_user, project: project)
|
||||
end
|
||||
|
||||
let(:params) { { name: existing_experiment.name } }
|
||||
|
||||
it_behaves_like 'Bad Request', 'RESOURCE_ALREADY_EXISTS'
|
||||
end
|
||||
|
||||
context 'when project does not exist' do
|
||||
let(:route) { "/projects/#{non_existing_record_id}/ml/mflow/api/2.0/mlflow/experiments/create" }
|
||||
|
||||
it_behaves_like 'Not Found', '404 Project Not Found'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -154,11 +154,13 @@ project_setting:
|
|||
- project_id
|
||||
- push_rule_id
|
||||
- show_default_award_emojis
|
||||
- show_diff_preview_in_email
|
||||
- updated_at
|
||||
- cve_id_request_enabled
|
||||
- mr_default_target_self
|
||||
- target_platforms
|
||||
- selective_code_owner_removals
|
||||
- show_diff_preview_in_email
|
||||
|
||||
build_service_desk_setting: # service_desk_setting
|
||||
unexposed_attributes:
|
||||
|
|
|
@ -761,15 +761,33 @@ RSpec.describe Trigger do
|
|||
expect(subject.variables).to include('TRIGGERED_USER_LOGIN' => env['GITLAB_USER_LOGIN'])
|
||||
end
|
||||
|
||||
describe "GITLAB_COMMIT_SHA" do
|
||||
context 'when CI_COMMIT_SHA is set' do
|
||||
before do
|
||||
stub_env('CI_COMMIT_SHA', 'ci_commit_sha')
|
||||
end
|
||||
context 'when CI_MERGE_REQUEST_SOURCE_BRANCH_SHA is set' do
|
||||
before do
|
||||
stub_env('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', 'ci_merge_request_source_branch_sha')
|
||||
end
|
||||
|
||||
it 'sets GITLAB_COMMIT_SHA to ci_commit_sha' do
|
||||
expect(subject.variables['GITLAB_COMMIT_SHA']).to eq('ci_commit_sha')
|
||||
end
|
||||
it 'sets TOP_UPSTREAM_SOURCE_SHA to ci_merge_request_source_branch_sha' do
|
||||
expect(subject.variables['TOP_UPSTREAM_SOURCE_SHA']).to eq('ci_merge_request_source_branch_sha')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when CI_MERGE_REQUEST_SOURCE_BRANCH_SHA is set as empty' do
|
||||
before do
|
||||
stub_env('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', '')
|
||||
end
|
||||
|
||||
it 'sets TOP_UPSTREAM_SOURCE_SHA to CI_COMMIT_SHA' do
|
||||
expect(subject.variables['TOP_UPSTREAM_SOURCE_SHA']).to eq(env['CI_COMMIT_SHA'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when CI_MERGE_REQUEST_SOURCE_BRANCH_SHA is not set' do
|
||||
before do
|
||||
stub_env('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', nil)
|
||||
end
|
||||
|
||||
it 'sets TOP_UPSTREAM_SOURCE_SHA to CI_COMMIT_SHA' do
|
||||
expect(subject.variables['TOP_UPSTREAM_SOURCE_SHA']).to eq(env['CI_COMMIT_SHA'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -28,6 +28,16 @@ type truncatableString struct {
|
|||
Truncated bool
|
||||
}
|
||||
|
||||
// supportedLexerLanguages is used for a fast lookup to ensure the language
|
||||
// is supported by the lexer library.
|
||||
var supportedLexerLanguages = map[string]struct{}{}
|
||||
|
||||
func init() {
|
||||
for _, name := range lexers.Names(true) {
|
||||
supportedLexerLanguages[name] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (ts *truncatableString) UnmarshalText(b []byte) error {
|
||||
s := 0
|
||||
for i := 0; s < len(b); i++ {
|
||||
|
@ -93,6 +103,24 @@ func newCodeHover(content json.RawMessage) (*codeHover, error) {
|
|||
}
|
||||
|
||||
func (c *codeHover) setTokens() {
|
||||
// fastpath: bail early if no language specified
|
||||
if c.Language == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// fastpath: lexer.Get() will first match against indexed languages by
|
||||
// name and alias, and then fallback to a very slow filepath match. We
|
||||
// avoid this slow path by first checking against languages we know to
|
||||
// be within the index, and bailing if not found.
|
||||
//
|
||||
// Not case-folding immediately is done intentionally. These two lookups
|
||||
// mirror the behaviour of lexer.Get().
|
||||
if _, ok := supportedLexerLanguages[c.Language]; !ok {
|
||||
if _, ok := supportedLexerLanguages[strings.ToLower(c.Language)]; !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
lexer := lexers.Get(c.Language)
|
||||
if lexer == nil {
|
||||
return
|
||||
|
|
|
@ -55,6 +55,14 @@ func TestHighlight(t *testing.T) {
|
|||
{{Class: "k", Value: "end"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ruby by file extension",
|
||||
language: "rb",
|
||||
value: `print hello`,
|
||||
want: [][]token{
|
||||
{{Value: "print hello"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unknown/malicious language is passed",
|
||||
language: "<lang> alert(1); </lang>",
|
||||
|
@ -116,3 +124,43 @@ func TestTruncatingMultiByteChars(t *testing.T) {
|
|||
symbolSize := 3
|
||||
require.Equal(t, value[0:maxValueSize*symbolSize], c.TruncatedValue.Value)
|
||||
}
|
||||
|
||||
func BenchmarkHighlight(b *testing.B) {
|
||||
type entry struct {
|
||||
Language string `json:"language"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
tests := []entry{
|
||||
{
|
||||
Language: "go",
|
||||
Value: "func main()",
|
||||
},
|
||||
{
|
||||
Language: "ruby",
|
||||
Value: "def read(line)",
|
||||
},
|
||||
{
|
||||
Language: "",
|
||||
Value: "<html><head>foobar</head></html>",
|
||||
},
|
||||
{
|
||||
Language: "zzz",
|
||||
Value: "def read(line)",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
b.Run("lang:"+tc.Language, func(b *testing.B) {
|
||||
raw, err := json.Marshal(tc)
|
||||
require.NoError(b, err)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, err := newCodeHovers(raw)
|
||||
require.NoError(b, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue