diff options
63 files changed, 1021 insertions, 376 deletions
diff --git a/.rubocop.yml b/.rubocop.yml index 0853b418a4c..7b2b8ca70f5 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -37,6 +37,7 @@ AllCops: - 'file_hooks/**/*' - 'workhorse/**/*' - 'spec/support/*.git/**/*' # e.g. spec/support/gitlab-git-test.git + - 'db/ci_migrate/*.rb' # since the `db/ci_migrate` is a symlinked to `db/migrate` CacheRootDirectory: tmp MaxFilesInCache: 25000 diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index df0556ead12..a7802efdf73 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -6430f0f4df82aecc0b282d8fb620d1d9219a6aee +59dfc252c79b7f9d290a3ede54c9ba8a3b12d4bd @@ -339,7 +339,7 @@ gem 'warning', '~> 1.2.0' group :development do gem 'lefthook', '~> 0.7.0', require: false - gem 'solargraph', '~> 0.42', require: false + gem 'solargraph', '~> 0.43', require: false gem 'letter_opener_web', '~> 1.4.0' diff --git a/Gemfile.lock b/Gemfile.lock index 02298bca3c8..4fc082bbe1b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1206,7 +1206,7 @@ GEM slack-messenger (2.3.4) snowplow-tracker (0.6.1) contracts (~> 0.7, <= 0.11) - solargraph (0.42.3) + solargraph (0.43.0) backport (~> 1.2) benchmark bundler (>= 1.17.2) @@ -1631,7 +1631,7 @@ DEPENDENCIES simplecov-cobertura (~> 1.3.1) slack-messenger (~> 2.3.4) snowplow-tracker (~> 0.6.1) - solargraph (~> 0.42) + solargraph (~> 0.43) spamcheck (~> 0.1.0) spring (~> 2.1.0) spring-commands-rspec (~> 1.0.4) diff --git a/app/assets/javascripts/issues_list/components/issuables_list_app.vue b/app/assets/javascripts/issues_list/components/issuables_list_app.vue index b13a389b963..62b52afdaca 100644 --- a/app/assets/javascripts/issues_list/components/issuables_list_app.vue +++ b/app/assets/javascripts/issues_list/components/issuables_list_app.vue @@ -9,8 +9,7 @@ import { toNumber, omit } from 'lodash'; import createFlash from '~/flash'; import axios from '~/lib/utils/axios_utils'; import { scrollToElement, historyPushState } from '~/lib/utils/common_utils'; -// eslint-disable-next-line import/no-deprecated -import { setUrlParams, urlParamsToObject, getParameterByName } from '~/lib/utils/url_utility'; +import { setUrlParams, queryToObject, getParameterByName } from '~/lib/utils/url_utility'; import { __ } from '~/locale'; import initManualOrdering from '~/manual_ordering'; import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; @@ -264,8 +263,7 @@ export default { }); }, getQueryObject() { - // eslint-disable-next-line import/no-deprecated - return urlParamsToObject(window.location.search); + return queryToObject(window.location.search, { gatherArrays: true }); }, onPaginate(newPage) { if (newPage === this.page) return; diff --git a/app/assets/javascripts/jobs/store/actions.js b/app/assets/javascripts/jobs/store/actions.js index a8be5d8d039..53e3dbbad0d 100644 --- a/app/assets/javascripts/jobs/store/actions.js +++ b/app/assets/javascripts/jobs/store/actions.js @@ -13,6 +13,7 @@ import { scrollUp, } from '~/lib/utils/scroll_utils'; import { __ } from '~/locale'; +import { reportToSentry } from '../utils'; import * as types from './mutation_types'; export const init = ({ dispatch }, { endpoint, logState, pagePath }) => { @@ -175,11 +176,14 @@ export const fetchTrace = ({ dispatch, state }) => dispatch('startPollingTrace'); } }) - .catch((e) => - e.response.status === httpStatusCodes.FORBIDDEN - ? dispatch('receiveTraceUnauthorizedError') - : dispatch('receiveTraceError'), - ); + .catch((e) => { + if (e.response.status === httpStatusCodes.FORBIDDEN) { + dispatch('receiveTraceUnauthorizedError'); + } else { + reportToSentry('job_actions', e); + dispatch('receiveTraceError'); + } + }); export const startPollingTrace = ({ dispatch, commit }) => { const traceTimeout = setTimeout(() => { diff --git a/app/assets/javascripts/jobs/utils.js b/app/assets/javascripts/jobs/utils.js index 1ccecf3eb53..bb27658369f 100644 --- a/app/assets/javascripts/jobs/utils.js +++ b/app/assets/javascripts/jobs/utils.js @@ -1,3 +1,5 @@ +import * as Sentry from '@sentry/browser'; + /** * capture anything starting with http:// or https:// * https?:\/\/ @@ -10,3 +12,10 @@ */ export const linkRegex = /(https?:\/\/[^"<>()\\^`{|}\s]+[^"<>()\\^`{|}\s.,:;!?])/g; export default { linkRegex }; + +export const reportToSentry = (component, failureType) => { + Sentry.withScope((scope) => { + scope.setTag('component', component); + Sentry.captureException(failureType); + }); +}; diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/composer_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/composer_installation.vue index b3979a620f0..cc629ae394c 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/composer_installation.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/composer_installation.vue @@ -1,9 +1,12 @@ <script> import { GlLink, GlSprintf } from '@gitlab/ui'; -import { mapGetters, mapState } from 'vuex'; import { s__ } from '~/locale'; -import { TrackingActions, TrackingLabels } from '~/packages/details/constants'; import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue'; +import { + TRACKING_ACTION_COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND, + TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND, + TRACKING_LABEL_CODE_INSTRUCTION, +} from '~/packages_and_registries/package_registry/constants'; import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue'; export default { @@ -14,9 +17,25 @@ export default { GlLink, GlSprintf, }, + inject: ['composerHelpPath', 'composerConfigRepositoryName', 'composerPath', 'groupListUrl'], + props: { + packageEntity: { + type: Object, + required: true, + }, + }, computed: { - ...mapState(['composerHelpPath']), - ...mapGetters(['composerRegistryInclude', 'composerPackageInclude', 'groupExists']), + composerRegistryInclude() { + // eslint-disable-next-line @gitlab/require-i18n-strings + return `composer config repositories.${this.composerConfigRepositoryName} '{"type": "composer", "url": "${this.composerPath}"}'`; + }, + composerPackageInclude() { + // eslint-disable-next-line @gitlab/require-i18n-strings + return `composer req ${[this.packageEntity.name]}:${this.packageEntity.version}`; + }, + groupExists() { + return this.groupListUrl?.length > 0; + }, }, i18n: { registryInclude: s__('PackageRegistry|Add composer registry'), @@ -27,8 +46,11 @@ export default { 'PackageRegistry|For more information on Composer packages in GitLab, %{linkStart}see the documentation.%{linkEnd}', ), }, - trackingActions: { ...TrackingActions }, - TrackingLabels, + tracking: { + TRACKING_ACTION_COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND, + TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND, + TRACKING_LABEL_CODE_INSTRUCTION, + }, installOptions: [{ value: 'composer', label: s__('PackageRegistry|Show Composer commands') }], }; </script> @@ -41,8 +63,8 @@ export default { :label="$options.i18n.registryInclude" :instruction="composerRegistryInclude" :copy-text="$options.i18n.copyRegistryInclude" - :tracking-action="$options.trackingActions.COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND" - :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION" + :tracking-action="$options.tracking.TRACKING_ACTION_COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND" + :tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION" data-testid="registry-include" /> @@ -50,8 +72,8 @@ export default { :label="$options.i18n.packageInclude" :instruction="composerPackageInclude" :copy-text="$options.i18n.copyPackageInclude" - :tracking-action="$options.trackingActions.COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND" - :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION" + :tracking-action="$options.tracking.TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND" + :tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION" data-testid="package-include" /> <span data-testid="help-text"> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/conan_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/conan_installation.vue index 59b446e46b5..99e27c9d44a 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/conan_installation.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/conan_installation.vue @@ -1,9 +1,12 @@ <script> import { GlLink, GlSprintf } from '@gitlab/ui'; -import { mapGetters, mapState } from 'vuex'; import { s__ } from '~/locale'; -import { TrackingActions, TrackingLabels } from '~/packages/details/constants'; import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue'; +import { + TRACKING_ACTION_COPY_CONAN_COMMAND, + TRACKING_ACTION_COPY_CONAN_SETUP_COMMAND, + TRACKING_LABEL_CODE_INSTRUCTION, +} from '~/packages_and_registries/package_registry/constants'; import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue'; export default { @@ -14,17 +17,34 @@ export default { GlLink, GlSprintf, }, + inject: ['conanHelpPath', 'conanPath'], + props: { + packageEntity: { + type: Object, + required: true, + }, + }, computed: { - ...mapState(['conanHelpPath']), - ...mapGetters(['conanInstallationCommand', 'conanSetupCommand']), + conanInstallationCommand() { + // eslint-disable-next-line @gitlab/require-i18n-strings + return `conan install ${this.packageEntity.name} --remote=gitlab`; + }, + conanSetupCommand() { + // eslint-disable-next-line @gitlab/require-i18n-strings + return `conan remote add gitlab ${this.conanPath}`; + }, }, i18n: { helpText: s__( 'PackageRegistry|For more information on the Conan registry, %{linkStart}see the documentation%{linkEnd}.', ), }, - trackingActions: { ...TrackingActions }, - TrackingLabels, + tracking: { + TRACKING_ACTION_COPY_CONAN_COMMAND, + TRACKING_ACTION_COPY_CONAN_SETUP_COMMAND, + TRACKING_LABEL_CODE_INSTRUCTION, + }, + installOptions: [{ value: 'conan', label: s__('PackageRegistry|Show Conan commands') }], }; </script> @@ -37,8 +57,8 @@ export default { :label="s__('PackageRegistry|Conan Command')" :instruction="conanInstallationCommand" :copy-text="s__('PackageRegistry|Copy Conan Command')" - :tracking-action="$options.trackingActions.COPY_CONAN_COMMAND" - :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION" + :tracking-action="$options.tracking.TRACKING_ACTION_COPY_CONAN_COMMAND" + :tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION" /> <h3 class="gl-font-lg">{{ __('Registry setup') }}</h3> @@ -47,8 +67,8 @@ export default { :label="s__('PackageRegistry|Add Conan Remote')" :instruction="conanSetupCommand" :copy-text="s__('PackageRegistry|Copy Conan Setup Command')" - :tracking-action="$options.trackingActions.COPY_CONAN_SETUP_COMMAND" - :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION" + :tracking-action="$options.tracking.TRACKING_ACTION_COPY_CONAN_SETUP_COMMAND" + :tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION" /> <gl-sprintf :message="$options.i18n.helpText"> <template #link="{ content }"> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/installation_commands.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/installation_commands.vue index 9ebfbbbf9e5..122d444e859 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/installation_commands.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/installation_commands.vue @@ -1,6 +1,12 @@ <script> -import { PackageType, TERRAFORM_PACKAGE_TYPE } from '~/packages/shared/constants'; -import TerraformInstallation from '~/packages_and_registries/infrastructure_registry/components/terraform_installation.vue'; +import { + PACKAGE_TYPE_CONAN, + PACKAGE_TYPE_MAVEN, + PACKAGE_TYPE_NPM, + PACKAGE_TYPE_NUGET, + PACKAGE_TYPE_PYPI, + PACKAGE_TYPE_COMPOSER, +} from '~/packages_and_registries/package_registry/constants'; import ComposerInstallation from './composer_installation.vue'; import ConanInstallation from './conan_installation.vue'; import MavenInstallation from './maven_installation.vue'; @@ -11,33 +17,22 @@ import PypiInstallation from './pypi_installation.vue'; export default { name: 'InstallationCommands', components: { - [PackageType.CONAN]: ConanInstallation, - [PackageType.MAVEN]: MavenInstallation, - [PackageType.NPM]: NpmInstallation, - [PackageType.NUGET]: NugetInstallation, - [PackageType.PYPI]: PypiInstallation, - [PackageType.COMPOSER]: ComposerInstallation, - [TERRAFORM_PACKAGE_TYPE]: TerraformInstallation, + [PACKAGE_TYPE_CONAN]: ConanInstallation, + [PACKAGE_TYPE_MAVEN]: MavenInstallation, + [PACKAGE_TYPE_NPM]: NpmInstallation, + [PACKAGE_TYPE_NUGET]: NugetInstallation, + [PACKAGE_TYPE_PYPI]: PypiInstallation, + [PACKAGE_TYPE_COMPOSER]: ComposerInstallation, }, props: { packageEntity: { type: Object, required: true, }, - npmPath: { - type: String, - required: false, - default: '', - }, - npmHelpPath: { - type: String, - required: false, - default: '', - }, }, computed: { installationComponent() { - return this.$options.components[this.packageEntity.package_type]; + return this.$options.components[this.packageEntity.packageType]; }, }, }; @@ -45,11 +40,6 @@ export default { <template> <div v-if="installationComponent"> - <component - :is="installationComponent" - :name="packageEntity.name" - :registry-url="npmPath" - :help-url="npmHelpPath" - /> + <component :is="installationComponent" :package-entity="packageEntity" /> </div> </template> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/maven_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/maven_installation.vue index b035e557d21..2070f0bbca0 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/maven_installation.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/maven_installation.vue @@ -1,9 +1,18 @@ <script> import { GlLink, GlSprintf } from '@gitlab/ui'; -import { mapGetters, mapState } from 'vuex'; import { s__ } from '~/locale'; -import { TrackingActions, TrackingLabels } from '~/packages/details/constants'; import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue'; +import { + TRACKING_ACTION_COPY_MAVEN_XML, + TRACKING_ACTION_COPY_MAVEN_COMMAND, + TRACKING_ACTION_COPY_MAVEN_SETUP, + TRACKING_ACTION_COPY_GRADLE_INSTALL_COMMAND, + TRACKING_ACTION_COPY_GRADLE_ADD_TO_SOURCE_COMMAND, + TRACKING_ACTION_COPY_KOTLIN_INSTALL_COMMAND, + TRACKING_ACTION_COPY_KOTLIN_ADD_TO_SOURCE_COMMAND, + TRACKING_LABEL_CODE_INSTRUCTION, + TRACKING_LABEL_MAVEN_INSTALLATION, +} from '~/packages_and_registries/package_registry/constants'; import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue'; export default { @@ -14,22 +23,80 @@ export default { GlLink, GlSprintf, }, + inject: ['mavenHelpPath', 'mavenPath'], + props: { + packageEntity: { + type: Object, + required: true, + }, + }, data() { return { instructionType: 'maven', }; }, computed: { - ...mapState(['mavenHelpPath']), - ...mapGetters([ - 'mavenInstallationXml', - 'mavenInstallationCommand', - 'mavenSetupXml', - 'gradleGroovyInstalCommand', - 'gradleGroovyAddSourceCommand', - 'gradleKotlinInstalCommand', - 'gradleKotlinAddSourceCommand', - ]), + appGroup() { + return this.packageEntity.metadata.appGroup; + }, + appName() { + return this.packageEntity.metadata.appName; + }, + appVersion() { + return this.packageEntity.metadata.appVersion; + }, + mavenInstallationXml() { + return `<dependency> + <groupId>${this.appGroup}</groupId> + <artifactId>${this.appName}</artifactId> + <version>${this.appVersion}</version> +</dependency>`; + }, + + mavenInstallationCommand() { + return `mvn dependency:get -Dartifact=${this.appGroup}:${this.appName}:${this.appVersion}`; + }, + + mavenSetupXml() { + return `<repositories> + <repository> + <id>gitlab-maven</id> + <url>${this.mavenPath}</url> + </repository> +</repositories> + +<distributionManagement> + <repository> + <id>gitlab-maven</id> + <url>${this.mavenPath}</url> + </repository> + + <snapshotRepository> + <id>gitlab-maven</id> + <url>${this.mavenPath}</url> + </snapshotRepository> +</distributionManagement>`; + }, + + gradleGroovyInstalCommand() { + // eslint-disable-next-line @gitlab/require-i18n-strings + return `implementation '${this.appGroup}:${this.appName}:${this.appVersion}'`; + }, + + gradleGroovyAddSourceCommand() { + // eslint-disable-next-line @gitlab/require-i18n-strings + return `maven { + url '${this.mavenPath}' +}`; + }, + + gradleKotlinInstalCommand() { + return `implementation("${this.appGroup}:${this.appName}:${this.appVersion}")`; + }, + + gradleKotlinAddSourceCommand() { + return `maven("${this.mavenPath}")`; + }, showMaven() { return this.instructionType === 'maven'; }, @@ -48,8 +115,18 @@ export default { 'PackageRegistry|For more information on the Maven registry, %{linkStart}see the documentation%{linkEnd}.', ), }, - trackingActions: { ...TrackingActions }, - TrackingLabels, + tracking: { + TRACKING_ACTION_COPY_MAVEN_XML, + TRACKING_ACTION_COPY_MAVEN_COMMAND, + TRACKING_ACTION_COPY_MAVEN_SETUP, + TRACKING_ACTION_COPY_GRADLE_INSTALL_COMMAND, + TRACKING_ACTION_COPY_GRADLE_ADD_TO_SOURCE_COMMAND, + TRACKING_ACTION_COPY_KOTLIN_INSTALL_COMMAND, + TRACKING_ACTION_COPY_KOTLIN_ADD_TO_SOURCE_COMMAND, + TRACKING_LABEL_CODE_INSTRUCTION, + TRACKING_LABEL_MAVEN_INSTALLATION, + }, + installOptions: [ { value: 'maven', label: s__('PackageRegistry|Maven XML') }, { value: 'groovy', label: s__('PackageRegistry|Gradle Groovy DSL') }, @@ -78,8 +155,8 @@ export default { <code-instruction :instruction="mavenInstallationXml" :copy-text="s__('PackageRegistry|Copy Maven XML')" - :tracking-action="$options.trackingActions.COPY_MAVEN_XML" - :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION" + :tracking-action="$options.tracking.TRACKING_ACTION_COPY_MAVEN_XML" + :tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION" multiline /> @@ -87,8 +164,8 @@ export default { :label="s__('PackageRegistry|Maven Command')" :instruction="mavenInstallationCommand" :copy-text="s__('PackageRegistry|Copy Maven command')" - :tracking-action="$options.trackingActions.COPY_MAVEN_COMMAND" - :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION" + :tracking-action="$options.tracking.TRACKING_ACTION_COPY_MAVEN_COMMAND" + :tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION" /> <h3 class="gl-font-lg">{{ s__('PackageRegistry|Registry setup') }}</h3> @@ -102,8 +179,8 @@ export default { <code-instruction :instruction="mavenSetupXml" :copy-text="s__('PackageRegistry|Copy Maven registry XML')" - :tracking-action="$options.trackingActions.COPY_MAVEN_SETUP" - :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION" + :tracking-action="$options.tracking.TRACKING_ACTION_COPY_MAVEN_SETUP" + :tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION" multiline /> <gl-sprintf :message="$options.i18n.helpText"> @@ -118,15 +195,15 @@ export default { :label="s__('PackageRegistry|Gradle Groovy DSL install command')" :instruction="gradleGroovyInstalCommand" :copy-text="s__('PackageRegistry|Copy Gradle Groovy DSL install command')" - :tracking-action="$options.trackingActions.COPY_GRADLE_INSTALL_COMMAND" - :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION" + :tracking-action="$options.tracking.TRACKING_ACTION_COPY_GRADLE_INSTALL_COMMAND" + :tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION" /> <code-instruction :label="s__('PackageRegistry|Add Gradle Groovy DSL repository command')" :instruction="gradleGroovyAddSourceCommand" :copy-text="s__('PackageRegistry|Copy add Gradle Groovy DSL repository command')" - :tracking-action="$options.trackingActions.COPY_GRADLE_ADD_TO_SOURCE_COMMAND" - :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION" + :tracking-action="$options.tracking.TRACKING_ACTION_COPY_GRADLE_ADD_TO_SOURCE_COMMAND" + :tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION" multiline /> </template> @@ -136,15 +213,15 @@ export default { :label="s__('PackageRegistry|Gradle Kotlin DSL install command')" :instruction="gradleKotlinInstalCommand" :copy-text="s__('PackageRegistry|Copy Gradle Kotlin DSL install command')" - :tracking-action="$options.trackingActions.COPY_KOTLIN_INSTALL_COMMAND" - :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION" + :tracking-action="$options.tracking.TRACKING_ACTION_COPY_KOTLIN_INSTALL_COMMAND" + :tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION" /> <code-instruction :label="s__('PackageRegistry|Add Gradle Kotlin DSL repository command')" :instruction="gradleKotlinAddSourceCommand" :copy-text="s__('PackageRegistry|Copy add Gradle Kotlin DSL repository command')" - :tracking-action="$options.trackingActions.COPY_KOTLIN_ADD_TO_SOURCE_COMMAND" - :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION" + :tracking-action="$options.tracking.TRACKING_ACTION_COPY_KOTLIN_ADD_TO_SOURCE_COMMAND" + :tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION" multiline /> </template> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/constants.js b/app/assets/javascripts/packages_and_registries/package_registry/constants.js index 7a63543540e..0008e5f0c98 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/constants.js +++ b/app/assets/javascripts/packages_and_registries/package_registry/constants.js @@ -10,7 +10,6 @@ export const PACKAGE_TYPE_RUBYGEMS = 'RUBYGEMS'; export const PACKAGE_TYPE_GENERIC = 'GENERIC'; export const PACKAGE_TYPE_DEBIAN = 'DEBIAN'; export const PACKAGE_TYPE_HELM = 'HELM'; -export const PACKAGE_TYPE_TERRAFORM = 'terraform_module'; export const DELETE_PACKAGE_TRACKING_ACTION = 'delete_package'; export const REQUEST_DELETE_PACKAGE_TRACKING_ACTION = 'request_delete_package'; @@ -20,6 +19,46 @@ export const DELETE_PACKAGE_FILE_TRACKING_ACTION = 'delete_package_file'; export const REQUEST_DELETE_PACKAGE_FILE_TRACKING_ACTION = 'request_delete_package_file'; export const CANCEL_DELETE_PACKAGE_FILE_TRACKING_ACTION = 'cancel_delete_package_file'; +export const TRACKING_LABEL_CODE_INSTRUCTION = 'code_instruction'; +export const TRACKING_LABEL_CONAN_INSTALLATION = 'conan_installation'; +export const TRACKING_LABEL_MAVEN_INSTALLATION = 'maven_installation'; +export const TRACKING_LABEL_NPM_INSTALLATION = 'npm_installation'; +export const TRACKING_LABEL_NUGET_INSTALLATION = 'nuget_installation'; +export const TRACKING_LABEL_PYPI_INSTALLATION = 'pypi_installation'; +export const TRACKING_LABEL_COMPOSER_INSTALLATION = 'composer_installation'; + +export const TRACKING_ACTION_INSTALLATION = 'installation'; +export const TRACKING_ACTION_REGISTRY_SETUP = 'registry_setup'; + +export const TRACKING_ACTION_COPY_CONAN_COMMAND = 'copy_conan_command'; +export const TRACKING_ACTION_COPY_CONAN_SETUP_COMMAND = 'copy_conan_setup_command'; + +export const TRACKING_ACTION_COPY_MAVEN_XML = 'copy_maven_xml'; +export const TRACKING_ACTION_COPY_MAVEN_COMMAND = 'copy_maven_command'; +export const TRACKING_ACTION_COPY_MAVEN_SETUP = 'copy_maven_setup_xml'; +export const TRACKING_ACTION_COPY_GRADLE_INSTALL_COMMAND = 'copy_gradle_install_command'; +export const TRACKING_ACTION_COPY_GRADLE_ADD_TO_SOURCE_COMMAND = + 'copy_gradle_add_to_source_command'; +export const TRACKING_ACTION_COPY_KOTLIN_INSTALL_COMMAND = 'copy_kotlin_install_command'; +export const TRACKING_ACTION_COPY_KOTLIN_ADD_TO_SOURCE_COMMAND = + 'copy_kotlin_add_to_source_command'; + +export const TRACKING_ACTION_COPY_NPM_INSTALL_COMMAND = 'copy_npm_install_command'; +export const TRACKING_ACTION_COPY_NPM_SETUP_COMMAND = 'copy_npm_setup_command'; +export const TRACKING_ACTION_COPY_YARN_INSTALL_COMMAND = 'copy_yarn_install_command'; +export const TRACKING_ACTION_COPY_YARN_SETUP_COMMAND = 'copy_yarn_setup_command'; + +export const TRACKING_ACTION_COPY_NUGET_INSTALL_COMMAND = 'copy_nuget_install_command'; +export const TRACKING_ACTION_COPY_NUGET_SETUP_COMMAND = 'copy_nuget_setup_command'; + +export const TRACKING_ACTION_COPY_PIP_INSTALL_COMMAND = 'copy_pip_install_command'; +export const TRACKING_ACTION_COPY_PYPI_SETUP_COMMAND = 'copy_pypi_setup_command'; + +export const TRACKING_ACTION_COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND = + 'copy_composer_registry_include_command'; +export const TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND = + 'copy_composer_package_include_command'; + export const TrackingCategories = { [PACKAGE_TYPE_MAVEN]: 'MavenPackages', [PACKAGE_TYPE_NPM]: 'NpmPackages', diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue b/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue index a948a57c144..5d51d97eaee 100644 --- a/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue +++ b/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue @@ -7,6 +7,7 @@ import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { DEFAULT, DRAW_FAILURE, LOAD_FAILURE } from '../../constants'; import DismissPipelineGraphCallout from '../../graphql/mutations/dismiss_pipeline_notification.graphql'; +import getPipelineQuery from '../../graphql/queries/get_pipeline_header_data.query.graphql'; import { reportToSentry, reportMessageToSentry } from '../../utils'; import { listByLayers } from '../parsing_utils'; import { IID_FAILURE, LAYER_VIEW, STAGE_VIEW, VIEW_TYPE_KEY } from './constants'; @@ -51,6 +52,7 @@ export default { alertType: null, callouts: [], currentViewType: STAGE_VIEW, + canRefetchHeaderPipeline: false, pipeline: null, pipelineLayers: null, showAlert: false, @@ -78,6 +80,26 @@ export default { ); }, }, + headerPipeline: { + query: getPipelineQuery, + // this query is already being called in header_component.vue, which shares the same cache as this component + // the skip here is to prevent sending double network requests on page load + skip() { + return !this.canRefetchHeaderPipeline; + }, + variables() { + return { + fullPath: this.pipelineProjectPath, + iid: this.pipelineIid, + }; + }, + update(data) { + return data.project?.pipeline || {}; + }, + error() { + this.reportFailure({ type: LOAD_FAILURE, skipSentry: true }); + }, + }, pipeline: { context() { return getQueryHeaders(this.graphqlResourceEtag); @@ -217,6 +239,10 @@ export default { }, refreshPipelineGraph() { this.$apollo.queries.pipeline.refetch(); + + // this will update the status in header_component since they share the same cache + this.canRefetchHeaderPipeline = true; + this.$apollo.queries.headerPipeline.refetch(); }, /* eslint-disable @gitlab/require-i18n-strings */ reportFailure({ type, err = 'No error string passed.', skipSentry = false }) { diff --git a/app/assets/javascripts/pipelines/components/header_component.vue b/app/assets/javascripts/pipelines/components/header_component.vue index b7500ef00b0..5db2b604956 100644 --- a/app/assets/javascripts/pipelines/components/header_component.vue +++ b/app/assets/javascripts/pipelines/components/header_component.vue @@ -143,13 +143,6 @@ export default { return cancelable && userPermissions.updatePipeline; }, }, - watch: { - isFinished(finished) { - if (finished) { - this.$apollo.queries.pipeline.stopPolling(); - } - }, - }, methods: { reportFailure(errorType) { this.failureType = errorType; @@ -218,7 +211,7 @@ export default { }; </script> <template> - <div class="pipeline-header-container"> + <div class="js-pipeline-header-container"> <gl-alert v-if="hasError" :variant="failure.variant">{{ failure.text }}</gl-alert> <ci-header v-if="shouldRenderContent" diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index bdb645e1934..9aa1ed65fa5 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -54,6 +54,8 @@ class ProjectsController < Projects::ApplicationController # rubocop: disable CodeReuse/ActiveRecord def new + return access_denied! unless current_user.can_create_project? + @namespace = Namespace.find_by(id: params[:namespace_id]) if params[:namespace_id] return access_denied! if @namespace && !can?(current_user, :create_projects, @namespace) diff --git a/app/graphql/resolvers/paginated_tree_resolver.rb b/app/graphql/resolvers/paginated_tree_resolver.rb new file mode 100644 index 00000000000..d1b4e75169c --- /dev/null +++ b/app/graphql/resolvers/paginated_tree_resolver.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Resolvers + class PaginatedTreeResolver < BaseResolver + type Types::Tree::TreeType.connection_type, null: true + extension Gitlab::Graphql::Extensions::ExternallyPaginatedArrayExtension + + calls_gitaly! + + argument :path, GraphQL::Types::String, + required: false, + default_value: '', # root of the repository + description: 'The path to get the tree for. Default value is the root of the repository.' + argument :ref, GraphQL::Types::String, + required: false, + default_value: :head, + description: 'The commit ref to get the tree for. Default value is HEAD.' + argument :recursive, GraphQL::Types::Boolean, + required: false, + default_value: false, + description: 'Used to get a recursive tree. Default is false.' + + alias_method :repository, :object + + def resolve(**args) + return unless repository.exists? + + cursor = args.delete(:after) + + pagination_params = { + limit: @field.max_page_size || 100, + page_token: cursor + } + + tree = repository.tree(args[:ref], args[:path], recursive: args[:recursive], pagination_params: pagination_params) + + next_cursor = tree.cursor&.next_cursor + Gitlab::Graphql::ExternallyPaginatedArray.new(cursor, next_cursor, *tree) + rescue Gitlab::Git::CommandError => e + raise Gitlab::Graphql::Errors::ArgumentError, e + end + + def self.field_options + super.merge(connection: false) # we manage the pagination manually, so opt out of the connection field extension + end + end +end diff --git a/app/graphql/types/repository_type.rb b/app/graphql/types/repository_type.rb index 466e3e45cb9..63d1eef5b59 100644 --- a/app/graphql/types/repository_type.rb +++ b/app/graphql/types/repository_type.rb @@ -14,6 +14,10 @@ module Types description: 'Indicates a corresponding Git repository exists on disk.' field :tree, Types::Tree::TreeType, null: true, resolver: Resolvers::TreeResolver, calls_gitaly: true, description: 'Tree of the repository.' + field :paginated_tree, Types::Tree::TreeType.connection_type, null: true, resolver: Resolvers::PaginatedTreeResolver, calls_gitaly: true, + max_page_size: 100, + description: 'Paginated tree of the repository.', + feature_flag: :paginated_tree_graphql_query field :blobs, Types::Repository::BlobType.connection_type, null: true, resolver: Resolvers::BlobsResolver, calls_gitaly: true, description: 'Blobs contained within the repository' field :branch_names, [GraphQL::Types::String], null: true, calls_gitaly: true, diff --git a/app/helpers/nav/top_nav_helper.rb b/app/helpers/nav/top_nav_helper.rb index 052b8339ebd..7e200ebc8a8 100644 --- a/app/helpers/nav/top_nav_helper.rb +++ b/app/helpers/nav/top_nav_helper.rb @@ -267,7 +267,11 @@ module Nav builder.add_primary_menu_item(id: 'your', title: _('Your projects'), href: dashboard_projects_path) builder.add_primary_menu_item(id: 'starred', title: _('Starred projects'), href: starred_dashboard_projects_path) builder.add_primary_menu_item(id: 'explore', title: _('Explore projects'), href: explore_root_path) - builder.add_secondary_menu_item(id: 'create', title: _('Create new project'), href: new_project_path) + + if current_user.can_create_project? + builder.add_secondary_menu_item(id: 'create', title: _('Create new project'), href: new_project_path) + end + builder.build end diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 8d65865e7da..0984238517e 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -527,6 +527,12 @@ class IssuableBaseService < ::BaseProjectService def allowed_update_params(params) params end + + def update_issuable_sla(issuable) + return unless issuable_sla = issuable.issuable_sla + + issuable_sla.update(issuable_closed: issuable.closed?) + end end IssuableBaseService.prepend_mod_with('IssuableBaseService') diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb index cc4ad1a9c85..ea64239dd99 100644 --- a/app/services/issues/close_service.rb +++ b/app/services/issues/close_service.rb @@ -32,7 +32,7 @@ module Issues notification_service.async.close_issue(issue, current_user, { closed_via: closed_via }) if notifications todo_service.close_issue(issue, current_user) - resolve_alert(issue) + perform_incident_management_actions(issue) execute_hooks(issue, 'close') invalidate_cache_counts(issue, users: issue.assignees) issue.update_project_counter_caches @@ -51,6 +51,10 @@ module Issues private + def perform_incident_management_actions(issue) + resolve_alert(issue) + end + def close_external_issue(issue, closed_via) return unless project.external_issue_tracker&.support_close_issue? @@ -89,3 +93,5 @@ module Issues end end end + +Issues::CloseService.prepend_mod_with('Issues::CloseService') diff --git a/app/services/issues/reopen_service.rb b/app/services/issues/reopen_service.rb index e2b1b5400c7..977b924ed72 100644 --- a/app/services/issues/reopen_service.rb +++ b/app/services/issues/reopen_service.rb @@ -9,6 +9,7 @@ module Issues event_service.reopen_issue(issue, current_user) create_note(issue, 'reopened') notification_service.async.reopen_issue(issue, current_user) + perform_incident_management_actions(issue) execute_hooks(issue, 'reopen') invalidate_cache_counts(issue, users: issue.assignees) issue.update_project_counter_caches @@ -21,8 +22,13 @@ module Issues private + def perform_incident_management_actions(issue) + end + def create_note(issue, state = issue.state) SystemNoteService.change_status(issue, issue.project, current_user, state, nil) end end end + +Issues::ReopenService.prepend_mod_with('Issues::ReopenService') diff --git a/app/views/admin/application_settings/_plantuml.html.haml b/app/views/admin/application_settings/_plantuml.html.haml index 632aeec6ce3..39de15dc38d 100644 --- a/app/views/admin/application_settings/_plantuml.html.haml +++ b/app/views/admin/application_settings/_plantuml.html.haml @@ -6,7 +6,8 @@ %button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' } = expanded ? _('Collapse') : _('Expand') %p - = _('Allow rendering of PlantUML diagrams in Asciidoc documents.') + = _('Render diagrams in your documents using PlantUML.') + = link_to _('Learn more.'), help_page_path('administration/integration/plantuml.md'), target: '_blank', rel: 'noopener noreferrer' .settings-content = form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-plantuml-settings'), html: { class: 'fieldset-form', id: 'plantuml-settings' } do |f| = form_errors(@application_setting) if expanded @@ -20,8 +21,6 @@ = f.label :plantuml_url, _('PlantUML URL'), class: 'label-bold' = f.text_field :plantuml_url, class: 'form-control gl-form-input', placeholder: 'http://your-plantuml-instance:8080' .form-text.text-muted - Allow rendering of - = link_to "PlantUML", "http://plantuml.com" - diagrams in Asciidoc documents using an external PlantUML service. + = _('The hostname of your PlantUML server.') = f.submit _('Save changes'), class: "gl-button btn btn-confirm" diff --git a/config/feature_flags/development/paginated_tree_graphql_query.yml b/config/feature_flags/development/paginated_tree_graphql_query.yml new file mode 100644 index 00000000000..13096412f25 --- /dev/null +++ b/config/feature_flags/development/paginated_tree_graphql_query.yml @@ -0,0 +1,8 @@ +--- +name: paginated_tree_graphql_query +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66751 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/337214 +milestone: '14.2' +type: development +group: group::source code +default_enabled: false diff --git a/config/initializers/check_decomposition_database_config.rb b/config/initializers/check_decomposition_database_config.rb deleted file mode 100644 index f32c72efa75..00000000000 --- a/config/initializers/check_decomposition_database_config.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -ci_db_config = Gitlab::Application.config.database_configuration[Rails.env]["ci"] - -if ci_db_config.present? - raise "migrations_paths setting for ci database must be `db/ci_migrate`" unless ci_db_config["migrations_paths"] == 'db/ci_migrate' -end diff --git a/db/ci_migrate b/db/ci_migrate new file mode 120000 index 00000000000..1f0710ccbe7 --- /dev/null +++ b/db/ci_migrate @@ -0,0 +1 @@ +migrate
\ No newline at end of file diff --git a/db/ci_migrate/20210617101848_create_ci_instance_variables_on_ci.rb b/db/ci_migrate/20210617101848_create_ci_instance_variables_on_ci.rb deleted file mode 100644 index 7274e6bcdf2..00000000000 --- a/db/ci_migrate/20210617101848_create_ci_instance_variables_on_ci.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -class CreateCiInstanceVariablesOnCi < ActiveRecord::Migration[6.1] - include Gitlab::Database::MigrationHelpers - - disable_ddl_transaction! - - def up - unless table_exists?(:ci_instance_variables) - create_table :ci_instance_variables do |t| - t.integer :variable_type, null: false, limit: 2, default: 1 - t.boolean :masked, default: false, allow_null: false - t.boolean :protected, default: false, allow_null: false - t.text :key, null: false - t.text :encrypted_value - t.text :encrypted_value_iv - - t.index [:key], name: 'index_ci_instance_variables_on_key', unique: true, using: :btree - end - end - - add_text_limit(:ci_instance_variables, :key, 255) - # Use constraint_name generated from db/migrate/20200625193358_increase_size_on_instance_level_variable_values.rb - add_text_limit(:ci_instance_variables, :encrypted_value, 13_579, constraint_name: 'check_956afd70f1') - add_text_limit(:ci_instance_variables, :encrypted_value_iv, 255) - end - - def down - drop_table :ci_instance_variables - end -end diff --git a/db/ci_schema_migrations/20210617101848 b/db/ci_schema_migrations/20210617101848 deleted file mode 100644 index 2969c694fa2..00000000000 --- a/db/ci_schema_migrations/20210617101848 +++ /dev/null @@ -1 +0,0 @@ -1b74312f59f6f8937cd0dd754d22dc72e9bdc7302e6254a2fda5762afebe303c
\ No newline at end of file diff --git a/db/ci_structure.sql b/db/ci_structure.sql index 1b898012f46..b402facb598 100644..120000 --- a/db/ci_structure.sql +++ b/db/ci_structure.sql @@ -1,45 +1 @@ -CREATE TABLE ar_internal_metadata ( - key character varying NOT NULL, - value character varying, - created_at timestamp(6) without time zone NOT NULL, - updated_at timestamp(6) without time zone NOT NULL -); - -CREATE TABLE ci_instance_variables ( - id bigint NOT NULL, - variable_type smallint DEFAULT 1 NOT NULL, - masked boolean DEFAULT false, - protected boolean DEFAULT false, - key text NOT NULL, - encrypted_value text, - encrypted_value_iv text, - CONSTRAINT check_07a45a5bcb CHECK ((char_length(encrypted_value_iv) <= 255)), - CONSTRAINT check_5aede12208 CHECK ((char_length(key) <= 255)), - CONSTRAINT check_956afd70f1 CHECK ((char_length(encrypted_value) <= 13579)) -); - -CREATE SEQUENCE ci_instance_variables_id_seq - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - -ALTER SEQUENCE ci_instance_variables_id_seq OWNED BY ci_instance_variables.id; - -CREATE TABLE schema_migrations ( - version character varying NOT NULL -); - -ALTER TABLE ONLY ci_instance_variables ALTER COLUMN id SET DEFAULT nextval('ci_instance_variables_id_seq'::regclass); - -ALTER TABLE ONLY ar_internal_metadata - ADD CONSTRAINT ar_internal_metadata_pkey PRIMARY KEY (key); - -ALTER TABLE ONLY ci_instance_variables - ADD CONSTRAINT ci_instance_variables_pkey PRIMARY KEY (id); - -ALTER TABLE ONLY schema_migrations - ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version); - -CREATE UNIQUE INDEX index_ci_instance_variables_on_key ON ci_instance_variables USING btree (key); +structure.sql
\ No newline at end of file diff --git a/db/migrate/20210712052519_add_label_applied_issuable_closed_to_issuable_sla.rb b/db/migrate/20210712052519_add_label_applied_issuable_closed_to_issuable_sla.rb new file mode 100644 index 00000000000..216d43dd62d --- /dev/null +++ b/db/migrate/20210712052519_add_label_applied_issuable_closed_to_issuable_sla.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class AddLabelAppliedIssuableClosedToIssuableSla < ActiveRecord::Migration[6.1] + def change + add_column :issuable_slas, :label_applied, :boolean, default: false, null: false + add_column :issuable_slas, :issuable_closed, :boolean, default: false, null: false + end +end diff --git a/db/migrate/20210714043818_add_index_for_label_applied_to_issuable_sla.rb b/db/migrate/20210714043818_add_index_for_label_applied_to_issuable_sla.rb new file mode 100644 index 00000000000..5931941a95e --- /dev/null +++ b/db/migrate/20210714043818_add_index_for_label_applied_to_issuable_sla.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class AddIndexForLabelAppliedToIssuableSla < ActiveRecord::Migration[6.1] + include Gitlab::Database::MigrationHelpers + + disable_ddl_transaction! + + INDEX_NAME = 'index_issuable_slas_on_due_at_id_label_applied_issuable_closed' + + def up + add_concurrent_index :issuable_slas, [:due_at, :id], name: INDEX_NAME, where: 'label_applied = FALSE AND issuable_closed = FALSE' + end + + def down + remove_concurrent_index_by_name :issuable_slas, INDEX_NAME + end +end diff --git a/db/migrate/20210729123101_confirm_security_bot.rb b/db/migrate/20210729123101_confirm_security_bot.rb new file mode 100644 index 00000000000..2184cc4e193 --- /dev/null +++ b/db/migrate/20210729123101_confirm_security_bot.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class ConfirmSecurityBot < ActiveRecord::Migration[6.0] + class User < ActiveRecord::Base + self.table_name = 'users' + SECURITY_BOT_TYPE = 8 + end + + def up + User.where(user_type: User::SECURITY_BOT_TYPE, confirmed_at: nil) + .update_all(confirmed_at: Time.current) + end + + # no-op + # Security Bot should be always confirmed + def down + end +end diff --git a/db/post_migrate/20210722042939_update_issuable_slas_where_issue_closed.rb b/db/post_migrate/20210722042939_update_issuable_slas_where_issue_closed.rb new file mode 100644 index 00000000000..b611b51e3ff --- /dev/null +++ b/db/post_migrate/20210722042939_update_issuable_slas_where_issue_closed.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +class UpdateIssuableSlasWhereIssueClosed < ActiveRecord::Migration[6.1] + ISSUE_CLOSED_STATUS = 2 + + class IssuableSla < ActiveRecord::Base + include EachBatch + + self.table_name = 'issuable_slas' + + belongs_to :issue, class_name: 'Issue' + end + + class Issue < ActiveRecord::Base + self.table_name = 'issues' + + has_one :issuable_sla, class_name: 'IssuableSla' + end + + def up + IssuableSla.each_batch(of: 50) do |relation| + relation.joins(:issue) + .where(issues: { state_id: ISSUE_CLOSED_STATUS } ) + .update_all(issuable_closed: true) + end + end + + def down + # no-op + end +end diff --git a/db/schema_migrations/20210712052519 b/db/schema_migrations/20210712052519 new file mode 100644 index 00000000000..3c0874b338f --- /dev/null +++ b/db/schema_migrations/20210712052519 @@ -0,0 +1 @@ +f3959b7a6f7ac95019f2f85c6383ddd11294562e94936ef3b5704bd4de7c5910
\ No newline at end of file diff --git a/db/schema_migrations/20210714043818 b/db/schema_migrations/20210714043818 new file mode 100644 index 00000000000..21c46a2608f --- /dev/null +++ b/db/schema_migrations/20210714043818 @@ -0,0 +1 @@ +344736284dc18b5f7516ec2062bef99b2444ae31720691e56b4e8687d5566b31
\ No newline at end of file diff --git a/db/schema_migrations/20210722042939 b/db/schema_migrations/20210722042939 new file mode 100644 index 00000000000..fe5a3820bf9 --- /dev/null +++ b/db/schema_migrations/20210722042939 @@ -0,0 +1 @@ +dd3b35b87c2f015895d807ede2521c9672fb41ec7a3b0b1a2f7abdc009950b6e
\ No newline at end of file diff --git a/db/schema_migrations/20210729123101 b/db/schema_migrations/20210729123101 new file mode 100644 index 00000000000..77f5bfba94e --- /dev/null +++ b/db/schema_migrations/20210729123101 @@ -0,0 +1 @@ +8522eaf951d87de04aea82fe8e1a9577e6665c8d08245282239476e49b02bc7d
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index c8130d5488f..1bd388b01af 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -14200,7 +14200,9 @@ ALTER SEQUENCE issuable_severities_id_seq OWNED BY issuable_severities.id; CREATE TABLE issuable_slas ( id bigint NOT NULL, issue_id bigint NOT NULL, - due_at timestamp with time zone NOT NULL + due_at timestamp with time zone NOT NULL, + label_applied boolean DEFAULT false NOT NULL, + issuable_closed boolean DEFAULT false NOT NULL ); CREATE SEQUENCE issuable_slas_id_seq @@ -24047,6 +24049,8 @@ CREATE INDEX index_issuable_metric_images_on_issue_id ON issuable_metric_images CREATE UNIQUE INDEX index_issuable_severities_on_issue_id ON issuable_severities USING btree (issue_id); +CREATE INDEX index_issuable_slas_on_due_at_id_label_applied_issuable_closed ON issuable_slas USING btree (due_at, id) WHERE ((label_applied = false) AND (issuable_closed = false)); + CREATE UNIQUE INDEX index_issuable_slas_on_issue_id ON issuable_slas USING btree (issue_id); CREATE INDEX index_issue_assignees_on_user_id ON issue_assignees USING btree (user_id); diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 0090a732f4e..5e8e53f73c4 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -7039,6 +7039,29 @@ The edge type for [`Todo`](#todo). | <a id="todoedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. | | <a id="todoedgenode"></a>`node` | [`Todo`](#todo) | The item at the end of the edge. | +#### `TreeConnection` + +The connection type for [`Tree`](#tree). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="treeconnectionedges"></a>`edges` | [`[TreeEdge]`](#treeedge) | A list of edges. | +| <a id="treeconnectionnodes"></a>`nodes` | [`[Tree]`](#tree) | A list of nodes. | +| <a id="treeconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. | + +#### `TreeEdge` + +The edge type for [`Tree`](#tree). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="treeedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. | +| <a id="treeedgenode"></a>`node` | [`Tree`](#tree) | The item at the end of the edge. | + #### `TreeEntryConnection` The connection type for [`TreeEntry`](#treeentry). @@ -12714,6 +12737,24 @@ Returns [`[String!]`](#string). | <a id="repositorybranchnamesoffset"></a>`offset` | [`Int!`](#int) | The number of branch names to skip. | | <a id="repositorybranchnamessearchpattern"></a>`searchPattern` | [`String!`](#string) | The pattern to search for branch names by. | +##### `Repository.paginatedTree` + +Paginated tree of the repository. Available only when feature flag `paginated_tree_graphql_query` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. + +Returns [`TreeConnection`](#treeconnection). + +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="repositorypaginatedtreepath"></a>`path` | [`String`](#string) | The path to get the tree for. Default value is the root of the repository. | +| <a id="repositorypaginatedtreerecursive"></a>`recursive` | [`Boolean`](#boolean) | Used to get a recursive tree. Default is false. | +| <a id="repositorypaginatedtreeref"></a>`ref` | [`String`](#string) | The commit ref to get the tree for. Default value is HEAD. | + ##### `Repository.tree` Tree of the repository. diff --git a/doc/development/database/multiple_databases.md b/doc/development/database/multiple_databases.md index 515a58fcb89..a44807a1b05 100644 --- a/doc/development/database/multiple_databases.md +++ b/doc/development/database/multiple_databases.md @@ -61,7 +61,6 @@ development: adapter: postgresql encoding: unicode database: gitlabhq_development_ci - migrations_paths: db/ci_migrate host: /path/to/gdk/postgresql pool: 10 prepared_statements: false @@ -82,7 +81,6 @@ test: &test adapter: postgresql encoding: unicode database: gitlabhq_test_ci - migrations_paths: db/ci_migrate host: /path/to/gdk/postgresql pool: 10 prepared_statements: false diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md index c7ff7faed57..feaa2df26f5 100644 --- a/doc/user/profile/index.md +++ b/doc/user/profile/index.md @@ -133,7 +133,8 @@ To specify your pronouns: > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/25742) in GitLab 14.2. -You can add your name pronunciation to your GitLab account. This will be displayed in your profile, below your name. +You can add your name pronunciation to your GitLab account. This is displayed in your profile, below +your name. To add your name pronunciation: diff --git a/lib/gitlab/database/schema_migrations/context.rb b/lib/gitlab/database/schema_migrations/context.rb index bd8b9bed2c1..35105121bbd 100644 --- a/lib/gitlab/database/schema_migrations/context.rb +++ b/lib/gitlab/database/schema_migrations/context.rb @@ -6,17 +6,14 @@ module Gitlab class Context attr_reader :connection + DEFAULT_SCHEMA_MIGRATIONS_PATH = "db/schema_migrations" + def initialize(connection) @connection = connection end def schema_directory - @schema_directory ||= - if ActiveRecord::Base.configurations.primary?(database_name) - File.join(db_dir, 'schema_migrations') - else - File.join(db_dir, "#{database_name}_schema_migrations") - end + @schema_directory ||= Rails.root.join(database_schema_migrations_path).to_s end def versions_to_create @@ -32,8 +29,8 @@ module Gitlab @database_name ||= @connection.pool.db_config.name end - def db_dir - @db_dir ||= Rails.application.config.paths["db"].first + def database_schema_migrations_path + @connection.pool.db_config.configuration_hash[:schema_migrations_path] || DEFAULT_SCHEMA_MIGRATIONS_PATH end end end diff --git a/lib/gitlab/usage/docs/renderer.rb b/lib/gitlab/usage/docs/renderer.rb index 7a7c58005bb..fe00ab21bbb 100644 --- a/lib/gitlab/usage/docs/renderer.rb +++ b/lib/gitlab/usage/docs/renderer.rb @@ -5,7 +5,7 @@ module Gitlab module Docs class Renderer include Gitlab::Usage::Docs::Helper - DICTIONARY_PATH = Rails.root.join('doc', 'development', 'usage_ping') + DICTIONARY_PATH = Rails.root.join('doc', 'development', 'service_ping') TEMPLATE_PATH = Rails.root.join('lib', 'gitlab', 'usage', 'docs', 'templates', 'default.md.haml') def initialize(metrics_definitions) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 3307529c3e3..0a5985925ea 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3342,9 +3342,6 @@ msgstr "" msgid "Allow public access to pipelines and job details, including output logs and artifacts." msgstr "" -msgid "Allow rendering of PlantUML diagrams in Asciidoc documents." -msgstr "" - msgid "Allow requests to the local network from hooks and services." msgstr "" @@ -27724,6 +27721,9 @@ msgstr "" msgid "Rename/Move" msgstr "" +msgid "Render diagrams in your documents using PlantUML." +msgstr "" + msgid "Renew subscription" msgstr "" @@ -33016,6 +33016,9 @@ msgstr "" msgid "The group_project_ids parameter is only allowed for a group" msgstr "" +msgid "The hostname of your PlantUML server." +msgstr "" + msgid "The hostname of your Snowplow collector." msgstr "" diff --git a/package.json b/package.json index e9119753584..dca2e2add59 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "@babel/preset-env": "^7.10.1", "@gitlab/at.js": "1.5.7", "@gitlab/favicon-overlay": "2.0.0", - "@gitlab/svgs": "1.208.0", + "@gitlab/svgs": "1.209.0", "@gitlab/tributejs": "1.0.0", "@gitlab/ui": "32.0.0", "@gitlab/visual-review-tools": "1.6.1", diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 8afb80d9cc5..92df35f9d10 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -42,6 +42,32 @@ RSpec.describe ProjectsController do expect(response).not_to render_template('new') end end + + context 'when user is an external user' do + let_it_be(:user) { create(:user, external: true) } + + it 'responds with status 404' do + group.add_owner(user) + + get :new, params: { namespace_id: group.id } + + expect(response).to have_gitlab_http_status(:not_found) + expect(response).not_to render_template('new') + end + end + + context 'when user is a group guest' do + let_it_be(:user) { create(:user) } + + it 'responds with status 404' do + group.add_guest(user) + + get :new, params: { namespace_id: group.id } + + expect(response).to have_gitlab_http_status(:not_found) + expect(response).not_to render_template('new') + end + end end end end diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index ee1ff550697..06da2a64be9 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -240,10 +240,14 @@ RSpec.describe 'Pipeline', :js do end end - it 'is possible to retry the success job' do + it 'is possible to retry the success job', :sidekiq_might_not_need_inline do find('#ci-badge-build .ci-action-icon-container').click + wait_for_requests expect(page).not_to have_content('Retry job') + within('.js-pipeline-header-container') do + expect(page).to have_selector('.js-ci-status-icon-running') + end end end @@ -282,10 +286,14 @@ RSpec.describe 'Pipeline', :js do end end - it 'is possible to retry the failed build' do + it 'is possible to retry the failed build', :sidekiq_might_not_need_inline do find('#ci-badge-test .ci-action-icon-container').click + wait_for_requests expect(page).not_to have_content('Retry job') + within('.js-pipeline-header-container') do + expect(page).to have_selector('.js-ci-status-icon-running') + end end it 'includes the failure reason' do @@ -308,10 +316,14 @@ RSpec.describe 'Pipeline', :js do end end - it 'is possible to play the manual job' do + it 'is possible to play the manual job', :sidekiq_might_not_need_inline do find('#ci-badge-manual-build .ci-action-icon-container').click + wait_for_requests expect(page).not_to have_content('Play job') + within('.js-pipeline-header-container') do + expect(page).to have_selector('.js-ci-status-icon-running') + end end end @@ -411,11 +423,18 @@ RSpec.describe 'Pipeline', :js do context 'when retrying' do before do find('[data-testid="retryPipeline"]').click + wait_for_requests end it 'does not show a "Retry" button', :sidekiq_might_not_need_inline do expect(page).not_to have_content('Retry') end + + it 'shows running status in pipeline header', :sidekiq_might_not_need_inline do + within('.js-pipeline-header-container') do + expect(page).to have_selector('.js-ci-status-icon-running') + end + end end end @@ -770,7 +789,7 @@ RSpec.describe 'Pipeline', :js do it 'shows deploy job as created' do subject - within('.pipeline-header-container') do + within('.js-pipeline-header-container') do expect(page).to have_content('pending') end @@ -795,7 +814,7 @@ RSpec.describe 'Pipeline', :js do it 'shows deploy job as pending' do subject - within('.pipeline-header-container') do + within('.js-pipeline-header-container') do expect(page).to have_content('running') end @@ -817,7 +836,7 @@ RSpec.describe 'Pipeline', :js do it 'shows deploy job as created' do subject - within('.pipeline-header-container') do + within('.js-pipeline-header-container') do expect(page).to have_content('pending') end @@ -842,7 +861,7 @@ RSpec.describe 'Pipeline', :js do it 'shows deploy job as pending' do subject - within('.pipeline-header-container') do + within('.js-pipeline-header-container') do expect(page).to have_content('running') end @@ -871,7 +890,7 @@ RSpec.describe 'Pipeline', :js do it 'shows deploy job as waiting for resource' do subject - within('.pipeline-header-container') do + within('.js-pipeline-header-container') do expect(page).to have_content('waiting') end @@ -893,7 +912,7 @@ RSpec.describe 'Pipeline', :js do it 'shows deploy job as waiting for resource' do subject - within('.pipeline-header-container') do + within('.js-pipeline-header-container') do expect(page).to have_content('waiting') end @@ -914,7 +933,7 @@ RSpec.describe 'Pipeline', :js do it 'shows deploy job as pending' do subject - within('.pipeline-header-container') do + within('.js-pipeline-header-container') do expect(page).to have_content('running') end @@ -936,7 +955,7 @@ RSpec.describe 'Pipeline', :js do it 'shows deploy job as pending' do subject - within('.pipeline-header-container') do + within('.js-pipeline-header-container') do expect(page).to have_content('running') end @@ -959,7 +978,7 @@ RSpec.describe 'Pipeline', :js do it 'shows deploy job as waiting for resource' do subject - within('.pipeline-header-container') do + within('.js-pipeline-header-container') do expect(page).to have_content('waiting') end @@ -981,7 +1000,7 @@ RSpec.describe 'Pipeline', :js do it 'shows deploy job as waiting for resource' do subject - within('.pipeline-header-container') do + within('.js-pipeline-header-container') do expect(page).to have_content('waiting') end diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/conan_installation_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/conan_installation_spec.js.snap index a3423e3f4d7..e9f80d5f512 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/conan_installation_spec.js.snap +++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/conan_installation_spec.js.snap @@ -9,7 +9,7 @@ exports[`ConanInstallation renders all the messages 1`] = ` <code-instruction-stub copytext="Copy Conan Command" - instruction="foo/command" + instruction="conan install @gitlab-org/package-15 --remote=gitlab" label="Conan Command" trackingaction="copy_conan_command" trackinglabel="code_instruction" @@ -23,7 +23,7 @@ exports[`ConanInstallation renders all the messages 1`] = ` <code-instruction-stub copytext="Copy Conan Setup Command" - instruction="foo/setup" + instruction="conan remote add gitlab conanPath" label="Add Conan Remote" trackingaction="copy_conan_setup_command" trackinglabel="code_instruction" diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/maven_installation_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/maven_installation_spec.js.snap index 8a2793c0010..4865b8205ab 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/maven_installation_spec.js.snap +++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/maven_installation_spec.js.snap @@ -10,7 +10,7 @@ exports[`MavenInstallation groovy renders all the messages 1`] = ` <code-instruction-stub class="gl-mb-5" copytext="Copy Gradle Groovy DSL install command" - instruction="foo/gradle/groovy/install" + instruction="implementation 'appGroup:appName:appVersion'" label="Gradle Groovy DSL install command" trackingaction="copy_gradle_install_command" trackinglabel="code_instruction" @@ -18,7 +18,9 @@ exports[`MavenInstallation groovy renders all the messages 1`] = ` <code-instruction-stub copytext="Copy add Gradle Groovy DSL repository command" - instruction="foo/gradle/groovy/add/source" + instruction="maven { + url 'mavenPath' +}" label="Add Gradle Groovy DSL repository command" multiline="true" trackingaction="copy_gradle_add_to_source_command" @@ -37,7 +39,7 @@ exports[`MavenInstallation kotlin renders all the messages 1`] = ` <code-instruction-stub class="gl-mb-5" copytext="Copy Gradle Kotlin DSL install command" - instruction="foo/gradle/kotlin/install" + instruction="implementation(\\"appGroup:appName:appVersion\\")" label="Gradle Kotlin DSL install command" trackingaction="copy_kotlin_install_command" trackinglabel="code_instruction" @@ -45,7 +47,7 @@ exports[`MavenInstallation kotlin renders all the messages 1`] = ` <code-instruction-stub copytext="Copy add Gradle Kotlin DSL repository command" - instruction="foo/gradle/kotlin/add/source" + instruction="maven(\\"mavenPath\\")" label="Add Gradle Kotlin DSL repository command" multiline="true" trackingaction="copy_kotlin_add_to_source_command" @@ -69,7 +71,11 @@ exports[`MavenInstallation maven renders all the messages 1`] = ` <code-instruction-stub copytext="Copy Maven XML" - instruction="foo/xml" + instruction="<dependency> + <groupId>appGroup</groupId> + <artifactId>appName</artifactId> + <version>appVersion</version> +</dependency>" label="" multiline="true" trackingaction="copy_maven_xml" @@ -78,7 +84,7 @@ exports[`MavenInstallation maven renders all the messages 1`] = ` <code-instruction-stub copytext="Copy Maven command" - instruction="foo/command" + instruction="mvn dependency:get -Dartifact=appGroup:appName:appVersion" label="Maven Command" trackingaction="copy_maven_command" trackinglabel="code_instruction" @@ -98,7 +104,24 @@ exports[`MavenInstallation maven renders all the messages 1`] = ` <code-instruction-stub copytext="Copy Maven registry XML" - instruction="foo/setup" + instruction="<repositories> + <repository> + <id>gitlab-maven</id> + <url>mavenPath</url> + </repository> +</repositories> + +<distributionManagement> + <repository> + <id>gitlab-maven</id> + <url>mavenPath</url> + </repository> + + <snapshotRepository> + <id>gitlab-maven</id> + <url>mavenPath</url> + </snapshotRepository> +</distributionManagement>" label="" multiline="true" trackingaction="copy_maven_setup_xml" diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/composer_installation_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/composer_installation_spec.js index 0afe69dd467..aedf20e873a 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/composer_installation_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/composer_installation_spec.js @@ -1,44 +1,35 @@ import { GlSprintf, GlLink } from '@gitlab/ui'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import Vuex from 'vuex'; -import { registryUrl as composerHelpPath } from 'jest/packages/details/mock_data'; -import { composerPackage as packageEntity } from 'jest/packages/mock_data'; -import { TrackingActions } from '~/packages/details/constants'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { packageData } from 'jest/packages_and_registries/package_registry/mock_data'; import ComposerInstallation from '~/packages_and_registries/package_registry/components/details/composer_installation.vue'; import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue'; +import { + TRACKING_ACTION_COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND, + TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND, + PACKAGE_TYPE_COMPOSER, +} from '~/packages_and_registries/package_registry/constants'; -const localVue = createLocalVue(); -localVue.use(Vuex); +const packageEntity = { ...packageData(), packageType: PACKAGE_TYPE_COMPOSER }; describe('ComposerInstallation', () => { let wrapper; - let store; - - const composerRegistryIncludeStr = 'foo/registry'; - const composerPackageIncludeStr = 'foo/package'; - - const createStore = (groupExists = true) => { - store = new Vuex.Store({ - state: { packageEntity, composerHelpPath }, - getters: { - composerRegistryInclude: () => composerRegistryIncludeStr, - composerPackageInclude: () => composerPackageIncludeStr, - groupExists: () => groupExists, - }, - }); - }; - const findRootNode = () => wrapper.find('[data-testid="root-node"]'); - const findRegistryInclude = () => wrapper.find('[data-testid="registry-include"]'); - const findPackageInclude = () => wrapper.find('[data-testid="package-include"]'); - const findHelpText = () => wrapper.find('[data-testid="help-text"]'); - const findHelpLink = () => wrapper.find(GlLink); + const findRootNode = () => wrapper.findByTestId('root-node'); + const findRegistryInclude = () => wrapper.findByTestId('registry-include'); + const findPackageInclude = () => wrapper.findByTestId('package-include'); + const findHelpText = () => wrapper.findByTestId('help-text'); + const findHelpLink = () => wrapper.findComponent(GlLink); const findInstallationTitle = () => wrapper.findComponent(InstallationTitle); - function createComponent() { - wrapper = shallowMount(ComposerInstallation, { - localVue, - store, + function createComponent(groupListUrl = 'groupListUrl') { + wrapper = shallowMountExtended(ComposerInstallation, { + provide: { + composerHelpPath: 'composerHelpPath', + composerConfigRepositoryName: 'composerConfigRepositoryName', + composerPath: 'composerPath', + groupListUrl, + }, + propsData: { packageEntity }, stubs: { GlSprintf, }, @@ -51,7 +42,6 @@ describe('ComposerInstallation', () => { describe('install command switch', () => { it('has the installation title component', () => { - createStore(); createComponent(); expect(findInstallationTitle().exists()).toBe(true); @@ -64,7 +54,6 @@ describe('ComposerInstallation', () => { describe('registry include command', () => { beforeEach(() => { - createStore(); createComponent(); }); @@ -72,9 +61,9 @@ describe('ComposerInstallation', () => { const registryIncludeCommand = findRegistryInclude(); expect(registryIncludeCommand.exists()).toBe(true); expect(registryIncludeCommand.props()).toMatchObject({ - instruction: composerRegistryIncludeStr, + instruction: `composer config repositories.composerConfigRepositoryName '{"type": "composer", "url": "composerPath"}'`, copyText: 'Copy registry include', - trackingAction: TrackingActions.COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND, + trackingAction: TRACKING_ACTION_COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND, }); }); @@ -85,7 +74,6 @@ describe('ComposerInstallation', () => { describe('package include command', () => { beforeEach(() => { - createStore(); createComponent(); }); @@ -93,9 +81,9 @@ describe('ComposerInstallation', () => { const registryIncludeCommand = findPackageInclude(); expect(registryIncludeCommand.exists()).toBe(true); expect(registryIncludeCommand.props()).toMatchObject({ - instruction: composerPackageIncludeStr, + instruction: 'composer req @gitlab-org/package-15:1.0.0', copyText: 'Copy require package include', - trackingAction: TrackingActions.COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND, + trackingAction: TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND, }); }); @@ -108,7 +96,7 @@ describe('ComposerInstallation', () => { 'For more information on Composer packages in GitLab, see the documentation.', ); expect(findHelpLink().attributes()).toMatchObject({ - href: composerHelpPath, + href: 'composerHelpPath', target: '_blank', }); }); @@ -116,15 +104,13 @@ describe('ComposerInstallation', () => { describe('root node', () => { it('is normally rendered', () => { - createStore(); createComponent(); expect(findRootNode().exists()).toBe(true); }); it('is not rendered when the group does not exist', () => { - createStore(false); - createComponent(); + createComponent(''); expect(findRootNode().exists()).toBe(false); }); diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/conan_installation_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/conan_installation_spec.js index 08dd21c4496..6b642cc21b7 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/conan_installation_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/conan_installation_spec.js @@ -1,38 +1,27 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import Vuex from 'vuex'; -import { registryUrl as conanPath } from 'jest/packages/details/mock_data'; -import { conanPackage as packageEntity } from 'jest/packages/mock_data'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { packageData } from 'jest/packages_and_registries/package_registry/mock_data'; import ConanInstallation from '~/packages_and_registries/package_registry/components/details/conan_installation.vue'; import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue'; +import { PACKAGE_TYPE_CONAN } from '~/packages_and_registries/package_registry/constants'; import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue'; -const localVue = createLocalVue(); -localVue.use(Vuex); +const packageEntity = { ...packageData(), packageType: PACKAGE_TYPE_CONAN }; describe('ConanInstallation', () => { let wrapper; - const conanInstallationCommandStr = 'foo/command'; - const conanSetupCommandStr = 'foo/setup'; - - const store = new Vuex.Store({ - state: { - packageEntity, - conanPath, - }, - getters: { - conanInstallationCommand: () => conanInstallationCommandStr, - conanSetupCommand: () => conanSetupCommandStr, - }, - }); - - const findCodeInstructions = () => wrapper.findAll(CodeInstructions); + const findCodeInstructions = () => wrapper.findAllComponents(CodeInstructions); const findInstallationTitle = () => wrapper.findComponent(InstallationTitle); function createComponent() { - wrapper = shallowMount(ConanInstallation, { - localVue, - store, + wrapper = shallowMountExtended(ConanInstallation, { + provide: { + conanHelpPath: 'conanHelpPath', + conanPath: 'conanPath', + }, + propsData: { + packageEntity, + }, }); } @@ -60,13 +49,17 @@ describe('ConanInstallation', () => { describe('installation commands', () => { it('renders the correct command', () => { - expect(findCodeInstructions().at(0).props('instruction')).toBe(conanInstallationCommandStr); + expect(findCodeInstructions().at(0).props('instruction')).toBe( + 'conan install @gitlab-org/package-15 --remote=gitlab', + ); }); }); describe('setup commands', () => { it('renders the correct command', () => { - expect(findCodeInstructions().at(1).props('instruction')).toBe(conanSetupCommandStr); + expect(findCodeInstructions().at(1).props('instruction')).toBe( + 'conan remote add gitlab conanPath', + ); }); }); }); diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/installations_commands_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/installations_commands_spec.js index b5b795ccc5a..b24946c8638 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/installations_commands_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/installations_commands_spec.js @@ -1,14 +1,5 @@ import { shallowMount } from '@vue/test-utils'; -import { - conanPackage, - mavenPackage, - npmPackage, - nugetPackage, - pypiPackage, - composerPackage, - terraformModule, -} from 'jest/packages/mock_data'; -import TerraformInstallation from '~/packages_and_registries/infrastructure_registry/components/terraform_installation.vue'; +import { packageData } from 'jest/packages_and_registries/package_registry/mock_data'; import ComposerInstallation from '~/packages_and_registries/package_registry/components/details/composer_installation.vue'; import ConanInstallation from '~/packages_and_registries/package_registry/components/details/conan_installation.vue'; import InstallationCommands from '~/packages_and_registries/package_registry/components/details/installation_commands.vue'; @@ -17,6 +8,21 @@ import MavenInstallation from '~/packages_and_registries/package_registry/compon import NpmInstallation from '~/packages_and_registries/package_registry/components/details/npm_installation.vue'; import NugetInstallation from '~/packages_and_registries/package_registry/components/details/nuget_installation.vue'; import PypiInstallation from '~/packages_and_registries/package_registry/components/details/pypi_installation.vue'; +import { + PACKAGE_TYPE_CONAN, + PACKAGE_TYPE_MAVEN, + PACKAGE_TYPE_NPM, + PACKAGE_TYPE_NUGET, + PACKAGE_TYPE_PYPI, + PACKAGE_TYPE_COMPOSER, +} from '~/packages_and_registries/package_registry/constants'; + +const conanPackage = { ...packageData(), packageType: PACKAGE_TYPE_CONAN }; +const mavenPackage = { ...packageData(), packageType: PACKAGE_TYPE_MAVEN }; +const npmPackage = { ...packageData(), packageType: PACKAGE_TYPE_NPM }; +const nugetPackage = { ...packageData(), packageType: PACKAGE_TYPE_NUGET }; +const pypiPackage = { ...packageData(), packageType: PACKAGE_TYPE_PYPI }; +const composerPackage = { ...packageData(), packageType: PACKAGE_TYPE_COMPOSER }; describe('InstallationCommands', () => { let wrapper; @@ -33,7 +39,6 @@ describe('InstallationCommands', () => { const nugetInstallation = () => wrapper.find(NugetInstallation); const pypiInstallation = () => wrapper.find(PypiInstallation); const composerInstallation = () => wrapper.find(ComposerInstallation); - const terraformInstallation = () => wrapper.findComponent(TerraformInstallation); afterEach(() => { wrapper.destroy(); @@ -48,9 +53,8 @@ describe('InstallationCommands', () => { ${nugetPackage} | ${nugetInstallation} ${pypiPackage} | ${pypiInstallation} ${composerPackage} | ${composerInstallation} - ${terraformModule} | ${terraformInstallation} `('renders', ({ packageEntity, selector }) => { - it(`${packageEntity.package_type} instructions exist`, () => { + it(`${packageEntity.packageType} instructions exist`, () => { createComponent({ packageEntity }); expect(selector()).toExist(); diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/maven_installation_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/maven_installation_spec.js index d2ee0ea8bad..eed7e903833 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/maven_installation_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/maven_installation_spec.js @@ -1,50 +1,79 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; import { nextTick } from 'vue'; -import Vuex from 'vuex'; -import { registryUrl as mavenPath } from 'jest/packages/details/mock_data'; -import { mavenPackage as packageEntity } from 'jest/packages/mock_data'; -import { TrackingActions } from '~/packages/details/constants'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; + +import { + packageData, + mavenMetadata, +} from 'jest/packages_and_registries/package_registry/mock_data'; import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue'; import MavenInstallation from '~/packages_and_registries/package_registry/components/details/maven_installation.vue'; +import { + TRACKING_ACTION_COPY_MAVEN_XML, + TRACKING_ACTION_COPY_MAVEN_COMMAND, + TRACKING_ACTION_COPY_MAVEN_SETUP, + TRACKING_ACTION_COPY_GRADLE_INSTALL_COMMAND, + TRACKING_ACTION_COPY_GRADLE_ADD_TO_SOURCE_COMMAND, + TRACKING_ACTION_COPY_KOTLIN_INSTALL_COMMAND, + TRACKING_ACTION_COPY_KOTLIN_ADD_TO_SOURCE_COMMAND, + PACKAGE_TYPE_MAVEN, +} from '~/packages_and_registries/package_registry/constants'; import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue'; -const localVue = createLocalVue(); -localVue.use(Vuex); - describe('MavenInstallation', () => { let wrapper; - const xmlCodeBlock = 'foo/xml'; - const mavenCommandStr = 'foo/command'; - const mavenSetupXml = 'foo/setup'; - const gradleGroovyInstallCommandText = 'foo/gradle/groovy/install'; - const gradleGroovyAddSourceCommandText = 'foo/gradle/groovy/add/source'; - const gradleKotlinInstallCommandText = 'foo/gradle/kotlin/install'; - const gradleKotlinAddSourceCommandText = 'foo/gradle/kotlin/add/source'; - - const store = new Vuex.Store({ - state: { - packageEntity, - mavenPath, - }, - getters: { - mavenInstallationXml: () => xmlCodeBlock, - mavenInstallationCommand: () => mavenCommandStr, - mavenSetupXml: () => mavenSetupXml, - gradleGroovyInstalCommand: () => gradleGroovyInstallCommandText, - gradleGroovyAddSourceCommand: () => gradleGroovyAddSourceCommandText, - gradleKotlinInstalCommand: () => gradleKotlinInstallCommandText, - gradleKotlinAddSourceCommand: () => gradleKotlinAddSourceCommandText, - }, - }); - - const findCodeInstructions = () => wrapper.findAll(CodeInstructions); + const packageEntity = { + ...packageData(), + packageType: PACKAGE_TYPE_MAVEN, + metadata: mavenMetadata(), + }; + + const mavenHelpPath = 'mavenHelpPath'; + const mavenPath = 'mavenPath'; + + const xmlCodeBlock = `<dependency> + <groupId>appGroup</groupId> + <artifactId>appName</artifactId> + <version>appVersion</version> +</dependency>`; + const mavenCommandStr = 'mvn dependency:get -Dartifact=appGroup:appName:appVersion'; + const mavenSetupXml = `<repositories> + <repository> + <id>gitlab-maven</id> + <url>${mavenPath}</url> + </repository> +</repositories> + +<distributionManagement> + <repository> + <id>gitlab-maven</id> + <url>${mavenPath}</url> + </repository> + + <snapshotRepository> + <id>gitlab-maven</id> + <url>${mavenPath}</url> + </snapshotRepository> +</distributionManagement>`; + const gradleGroovyInstallCommandText = `implementation 'appGroup:appName:appVersion'`; + const gradleGroovyAddSourceCommandText = `maven { + url '${mavenPath}' +}`; + const gradleKotlinInstallCommandText = `implementation("appGroup:appName:appVersion")`; + const gradleKotlinAddSourceCommandText = `maven("${mavenPath}")`; + + const findCodeInstructions = () => wrapper.findAllComponents(CodeInstructions); const findInstallationTitle = () => wrapper.findComponent(InstallationTitle); function createComponent({ data = {} } = {}) { - wrapper = shallowMount(MavenInstallation, { - localVue, - store, + wrapper = shallowMountExtended(MavenInstallation, { + provide: { + mavenHelpPath, + mavenPath, + }, + propsData: { + packageEntity, + }, data() { return data; }, @@ -98,7 +127,7 @@ describe('MavenInstallation', () => { expect(findCodeInstructions().at(0).props()).toMatchObject({ instruction: xmlCodeBlock, multiline: true, - trackingAction: TrackingActions.COPY_MAVEN_XML, + trackingAction: TRACKING_ACTION_COPY_MAVEN_XML, }); }); @@ -106,7 +135,7 @@ describe('MavenInstallation', () => { expect(findCodeInstructions().at(1).props()).toMatchObject({ instruction: mavenCommandStr, multiline: false, - trackingAction: TrackingActions.COPY_MAVEN_COMMAND, + trackingAction: TRACKING_ACTION_COPY_MAVEN_COMMAND, }); }); }); @@ -116,7 +145,7 @@ describe('MavenInstallation', () => { expect(findCodeInstructions().at(2).props()).toMatchObject({ instruction: mavenSetupXml, multiline: true, - trackingAction: TrackingActions.COPY_MAVEN_SETUP, + trackingAction: TRACKING_ACTION_COPY_MAVEN_SETUP, }); }); }); @@ -136,7 +165,7 @@ describe('MavenInstallation', () => { expect(findCodeInstructions().at(0).props()).toMatchObject({ instruction: gradleGroovyInstallCommandText, multiline: false, - trackingAction: TrackingActions.COPY_GRADLE_INSTALL_COMMAND, + trackingAction: TRACKING_ACTION_COPY_GRADLE_INSTALL_COMMAND, }); }); }); @@ -146,7 +175,7 @@ describe('MavenInstallation', () => { expect(findCodeInstructions().at(1).props()).toMatchObject({ instruction: gradleGroovyAddSourceCommandText, multiline: true, - trackingAction: TrackingActions.COPY_GRADLE_ADD_TO_SOURCE_COMMAND, + trackingAction: TRACKING_ACTION_COPY_GRADLE_ADD_TO_SOURCE_COMMAND, }); }); }); @@ -166,7 +195,7 @@ describe('MavenInstallation', () => { expect(findCodeInstructions().at(0).props()).toMatchObject({ instruction: gradleKotlinInstallCommandText, multiline: false, - trackingAction: TrackingActions.COPY_KOTLIN_INSTALL_COMMAND, + trackingAction: TRACKING_ACTION_COPY_KOTLIN_INSTALL_COMMAND, }); }); }); @@ -176,7 +205,7 @@ describe('MavenInstallation', () => { expect(findCodeInstructions().at(1).props()).toMatchObject({ instruction: gradleKotlinAddSourceCommandText, multiline: true, - trackingAction: TrackingActions.COPY_KOTLIN_ADD_TO_SOURCE_COMMAND, + trackingAction: TRACKING_ACTION_COPY_KOTLIN_ADD_TO_SOURCE_COMMAND, }); }); }); diff --git a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js index bb7e27b5ec2..ce507c2413e 100644 --- a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js +++ b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js @@ -18,6 +18,8 @@ import GraphViewSelector from '~/pipelines/components/graph/graph_view_selector. import StageColumnComponent from '~/pipelines/components/graph/stage_column_component.vue'; import LinksLayer from '~/pipelines/components/graph_shared/links_layer.vue'; import * as parsingUtils from '~/pipelines/components/parsing_utils'; +import getPipelineHeaderData from '~/pipelines/graphql/queries/get_pipeline_header_data.query.graphql'; +import { mockRunningPipelineHeaderData } from '../mock_data'; import { mapCallouts, mockCalloutsResponse, mockPipelineResponse } from './mock_data'; const defaultProvide = { @@ -72,8 +74,10 @@ describe('Pipeline graph wrapper', () => { } = {}) => { const callouts = mapCallouts(calloutsList); const getUserCalloutsHandler = jest.fn().mockResolvedValue(mockCalloutsResponse(callouts)); + const getPipelineHeaderDataHandler = jest.fn().mockResolvedValue(mockRunningPipelineHeaderData); const requestHandlers = [ + [getPipelineHeaderData, getPipelineHeaderDataHandler], [getPipelineDetails, getPipelineDetailsHandler], [getUserCallouts, getUserCalloutsHandler], ]; @@ -111,6 +115,11 @@ describe('Pipeline graph wrapper', () => { createComponentWithApollo(); expect(getGraph().exists()).toBe(false); }); + + it('skips querying headerPipeline', () => { + createComponentWithApollo(); + expect(wrapper.vm.$apollo.queries.headerPipeline.skip).toBe(true); + }); }); describe('when data has loaded', () => { @@ -190,12 +199,15 @@ describe('Pipeline graph wrapper', () => { describe('when refresh action is emitted', () => { beforeEach(async () => { createComponentWithApollo(); + jest.spyOn(wrapper.vm.$apollo.queries.headerPipeline, 'refetch'); jest.spyOn(wrapper.vm.$apollo.queries.pipeline, 'refetch'); await wrapper.vm.$nextTick(); getGraph().vm.$emit('refreshPipelineGraph'); }); it('calls refetch', () => { + expect(wrapper.vm.$apollo.queries.headerPipeline.skip).toBe(false); + expect(wrapper.vm.$apollo.queries.headerPipeline.refetch).toHaveBeenCalled(); expect(wrapper.vm.$apollo.queries.pipeline.refetch).toHaveBeenCalled(); }); }); diff --git a/spec/frontend/pipelines/header_component_spec.js b/spec/frontend/pipelines/header_component_spec.js index 31f0e72c279..e531e26a858 100644 --- a/spec/frontend/pipelines/header_component_spec.js +++ b/spec/frontend/pipelines/header_component_spec.js @@ -99,24 +99,6 @@ describe('Pipeline details header', () => { ); }); - describe('polling', () => { - it('is stopped when pipeline is finished', async () => { - wrapper = createComponent({ ...mockRunningPipelineHeader }); - - await wrapper.setData({ - pipeline: { ...mockCancelledPipelineHeader }, - }); - - expect(wrapper.vm.$apollo.queries.pipeline.stopPolling).toHaveBeenCalled(); - }); - - it('is not stopped when pipeline is not finished', () => { - wrapper = createComponent(); - - expect(wrapper.vm.$apollo.queries.pipeline.stopPolling).not.toHaveBeenCalled(); - }); - }); - describe('actions', () => { describe('Retry action', () => { beforeEach(() => { diff --git a/spec/frontend/pipelines/mock_data.js b/spec/frontend/pipelines/mock_data.js index 7e3c3727c9d..fdc78d48901 100644 --- a/spec/frontend/pipelines/mock_data.js +++ b/spec/frontend/pipelines/mock_data.js @@ -127,6 +127,28 @@ export const mockSuccessfulPipelineHeader = { }, }; +export const mockRunningPipelineHeaderData = { + data: { + project: { + pipeline: { + ...mockRunningPipelineHeader, + iid: '28', + user: { + name: 'Foo', + username: 'foobar', + webPath: '/foo', + email: 'foo@bar.com', + avatarUrl: 'link', + status: null, + __typename: 'UserCore', + }, + __typename: 'Pipeline', + }, + __typename: 'Project', + }, + }, +}; + export const stageReply = { name: 'deploy', title: 'deploy: running', diff --git a/spec/graphql/resolvers/paginated_tree_resolver_spec.rb b/spec/graphql/resolvers/paginated_tree_resolver_spec.rb new file mode 100644 index 00000000000..82b05937aa3 --- /dev/null +++ b/spec/graphql/resolvers/paginated_tree_resolver_spec.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::PaginatedTreeResolver do + include GraphqlHelpers + + let_it_be(:project) { create(:project, :repository) } + let_it_be(:repository) { project.repository } + + specify do + expect(described_class).to have_nullable_graphql_type(Types::Tree::TreeType.connection_type) + end + + describe '#resolve', :aggregate_failures do + subject { resolve_repository(args, opts) } + + let(:args) { { ref: 'master' } } + let(:opts) { {} } + + let(:start_cursor) { subject.start_cursor } + let(:end_cursor) { subject.end_cursor } + let(:items) { subject.items } + let(:entries) { items.first.entries } + + it 'resolves to a collection with a tree object' do + expect(items.first).to be_an_instance_of(Tree) + + expect(start_cursor).to be_nil + expect(end_cursor).to be_blank + expect(entries.count).to eq(repository.tree.entries.count) + end + + context 'with recursive option' do + let(:args) { super().merge(recursive: true) } + + it 'resolve to a recursive tree' do + expect(entries[4].path).to eq('files/html') + end + end + + context 'with limited max_page_size' do + let(:opts) { { max_page_size: 5 } } + + it 'resolves to a pagination collection with a tree object' do + expect(items.first).to be_an_instance_of(Tree) + + expect(start_cursor).to be_nil + expect(end_cursor).to be_present + expect(entries.count).to eq(5) + end + end + + context 'when repository does not exist' do + before do + allow(repository).to receive(:exists?).and_return(false) + end + + it 'returns nil' do + is_expected.to be(nil) + end + end + + describe 'Cursor pagination' do + context 'when cursor is invalid' do + let(:args) { super().merge(after: 'invalid') } + + it { expect { subject }.to raise_error(Gitlab::Graphql::Errors::ArgumentError) } + end + + it 'returns all tree entries during cursor pagination' do + cursor = nil + + expected_entries = repository.tree.entries.map(&:path) + collected_entries = [] + + loop do + result = resolve_repository(args.merge(after: cursor), max_page_size: 10) + + collected_entries += result.items.first.entries.map(&:path) + + expect(result.start_cursor).to eq(cursor) + cursor = result.end_cursor + + break if cursor.blank? + end + + expect(collected_entries).to match_array(expected_entries) + end + end + end + + def resolve_repository(args, opts = {}) + field_options = described_class.field_options.merge( + owner: resolver_parent, + name: 'field_value' + ).merge(opts) + + field = ::Types::BaseField.new(**field_options) + resolve_field(field, repository, args: args, object_type: resolver_parent) + end +end diff --git a/spec/graphql/types/repository_type_spec.rb b/spec/graphql/types/repository_type_spec.rb index ee0cc4361da..5488d78b720 100644 --- a/spec/graphql/types/repository_type_spec.rb +++ b/spec/graphql/types/repository_type_spec.rb @@ -11,6 +11,8 @@ RSpec.describe GitlabSchema.types['Repository'] do specify { expect(described_class).to have_graphql_field(:tree) } + specify { expect(described_class).to have_graphql_field(:paginated_tree, calls_gitaly?: true, max_page_size: 100) } + specify { expect(described_class).to have_graphql_field(:exists, calls_gitaly?: true, complexity: 2) } specify { expect(described_class).to have_graphql_field(:blobs) } diff --git a/spec/lib/gitlab/database/schema_migrations/context_spec.rb b/spec/lib/gitlab/database/schema_migrations/context_spec.rb index 31cba9290db..1f1943d00a3 100644 --- a/spec/lib/gitlab/database/schema_migrations/context_spec.rb +++ b/spec/lib/gitlab/database/schema_migrations/context_spec.rb @@ -3,7 +3,8 @@ require 'spec_helper' RSpec.describe Gitlab::Database::SchemaMigrations::Context do - let(:connection) { ActiveRecord::Base.connection } + let(:connection_class) { ActiveRecord::Base } + let(:connection) { connection_class.connection } let(:context) { described_class.new(connection) } @@ -12,13 +13,65 @@ RSpec.describe Gitlab::Database::SchemaMigrations::Context do expect(context.schema_directory).to eq(File.join(Rails.root, 'db/schema_migrations')) end - context 'multiple databases' do - let(:connection) { Ci::CiDatabaseRecord.connection } + context 'CI database' do + let(:connection_class) { Ci::CiDatabaseRecord } it 'returns a directory path that is database specific' do skip_if_multiple_databases_not_setup - expect(context.schema_directory).to eq(File.join(Rails.root, 'db/ci_schema_migrations')) + expect(context.schema_directory).to eq(File.join(Rails.root, 'db/schema_migrations')) + end + end + + context 'multiple databases' do + let(:connection_class) do + Class.new(::ApplicationRecord) do + self.abstract_class = true + + def self.name + 'Gitlab::Database::SchemaMigrations::Context::TestConnection' + end + end + end + + let(:configuration_overrides) { {} } + + before do + connection_class.establish_connection( + ActiveRecord::Base + .connection_pool + .db_config + .configuration_hash + .merge(configuration_overrides) + ) + end + + after do + connection_class.remove_connection + end + + context 'when `schema_migrations_path` is configured as string' do + let(:configuration_overrides) do + { "schema_migrations_path" => "db/ci_schema_migrations" } + end + + it 'returns a configured directory path that' do + skip_if_multiple_databases_not_setup + + expect(context.schema_directory).to eq(File.join(Rails.root, 'db/ci_schema_migrations')) + end + end + + context 'when `schema_migrations_path` is configured as symbol' do + let(:configuration_overrides) do + { schema_migrations_path: "db/ci_schema_migrations" } + end + + it 'returns a configured directory path that' do + skip_if_multiple_databases_not_setup + + expect(context.schema_directory).to eq(File.join(Rails.root, 'db/ci_schema_migrations')) + end end end end diff --git a/spec/migrations/20210722042939_update_issuable_slas_where_issue_closed_spec.rb b/spec/migrations/20210722042939_update_issuable_slas_where_issue_closed_spec.rb new file mode 100644 index 00000000000..a0aae00776d --- /dev/null +++ b/spec/migrations/20210722042939_update_issuable_slas_where_issue_closed_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration!('update_issuable_slas_where_issue_closed') + +RSpec.describe UpdateIssuableSlasWhereIssueClosed, :migration do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:issues) { table(:issues) } + let(:issuable_slas) { table(:issuable_slas) } + let(:issue_params) { { title: 'title', project_id: project.id } } + let(:issue_closed_state) { 2 } + + let!(:namespace) { namespaces.create!(name: 'foo', path: 'foo') } + let!(:project) { projects.create!(namespace_id: namespace.id) } + let!(:issue_open) { issues.create!(issue_params) } + let!(:issue_closed) { issues.create!(issue_params.merge(state_id: issue_closed_state)) } + + let!(:issuable_sla_open_issue) { issuable_slas.create!(issue_id: issue_open.id, due_at: Time.now) } + let!(:issuable_sla_closed_issue) { issuable_slas.create!(issue_id: issue_closed.id, due_at: Time.now) } + + it 'sets the issuable_closed attribute to false' do + expect(issuable_sla_open_issue.issuable_closed).to eq(false) + expect(issuable_sla_closed_issue.issuable_closed).to eq(false) + + migrate! + + expect(issuable_sla_open_issue.reload.issuable_closed).to eq(false) + expect(issuable_sla_closed_issue.reload.issuable_closed).to eq(true) + end +end diff --git a/spec/migrations/confirm_security_bot_spec.rb b/spec/migrations/confirm_security_bot_spec.rb new file mode 100644 index 00000000000..19ca81f92f3 --- /dev/null +++ b/spec/migrations/confirm_security_bot_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe ConfirmSecurityBot, :migration do + let(:users) { table(:users) } + + let(:user_type) { 8 } + + context 'when bot is not created' do + it 'skips migration' do + migrate! + + bot = users.find_by(user_type: user_type) + + expect(bot).to be_nil + end + end + + context 'when bot is confirmed' do + let(:bot) { table(:users).create!(user_type: user_type, confirmed_at: Time.current, projects_limit: 1) } + + it 'skips migration' do + expect { migrate! }.not_to change { bot.reload.confirmed_at } + end + end + + context 'when bot is not confirmed' do + let(:bot) { table(:users).create!(user_type: user_type, projects_limit: 1) } + + it 'update confirmed_at' do + freeze_time do + expect { migrate! }.to change { bot.reload.confirmed_at }.from(nil).to(Time.current) + end + end + end +end diff --git a/spec/requests/api/graphql/project/repository_spec.rb b/spec/requests/api/graphql/project/repository_spec.rb index bddd300e27f..8810f2fa3d5 100644 --- a/spec/requests/api/graphql/project/repository_spec.rb +++ b/spec/requests/api/graphql/project/repository_spec.rb @@ -83,4 +83,26 @@ RSpec.describe 'getting a repository in a project' do expect(graphql_data['project']['repository']).to be_nil end end + + context 'when paginated tree requested' do + let(:fields) do + %( + paginatedTree { + nodes { + trees { + nodes { + path + } + } + } + } + ) + end + + it 'returns paginated tree' do + post_graphql(query, current_user: current_user) + + expect(graphql_data['project']['repository']['paginatedTree']).to be_present + end + end end diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb index 9a70de80123..b1d4877e138 100644 --- a/spec/services/issues/close_service_spec.rb +++ b/spec/services/issues/close_service_spec.rb @@ -222,7 +222,7 @@ RSpec.describe Issues::CloseService do it 'verifies the number of queries' do recorded = ActiveRecord::QueryRecorder.new { close_issue } - expected_queries = 24 + expected_queries = 25 expect(recorded.count).to be <= expected_queries expect(recorded.cached_count).to eq(0) diff --git a/yarn.lock b/yarn.lock index 7870aa23b58..158d46b1679 100644 --- a/yarn.lock +++ b/yarn.lock @@ -898,10 +898,10 @@ stylelint-declaration-strict-value "1.7.7" stylelint-scss "3.18.0" -"@gitlab/svgs@1.208.0": - version "1.208.0" - resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.208.0.tgz#e298b4d38ac38b7186000cc21bafebebe0766a83" - integrity sha512-dK2fXLCPOg0bchIqiNpVPY8RmEIpmR9wo/BHeLpf8NTcEjtAok87hJ+cwySbDvKeu10bSePsgl8NcST61GlNaA== +"@gitlab/svgs@1.209.0": + version "1.209.0" + resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.209.0.tgz#06020b74668df17b9bdaa33b561b4800ca34cc10" + integrity sha512-V6NaXDhu899tNCuU4+VepY7Rv2Ge3ViOctrUAS3NQtGcXV1THDhGAg6+OCFpgHr1fhmxYNwpxQ8OTh+HaPvtIA== "@gitlab/tributejs@1.0.0": version "1.0.0" |