Commit 8ff7063b authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 22b272bd 5cc73aac
import { s__ } from '~/locale';
export const TrackingLabels = {
CODE_INSTRUCTION: 'code_instruction',
CONAN_INSTALLATION: 'conan_installation',
MAVEN_INSTALLATION: 'maven_installation',
NPM_INSTALLATION: 'npm_installation',
NUGET_INSTALLATION: 'nuget_installation',
PYPI_INSTALLATION: 'pypi_installation',
COMPOSER_INSTALLATION: 'composer_installation',
};
export const TrackingActions = {
INSTALLATION: 'installation',
REGISTRY_SETUP: 'registry_setup',
COPY_CONAN_COMMAND: 'copy_conan_command',
COPY_CONAN_SETUP_COMMAND: 'copy_conan_setup_command',
COPY_MAVEN_XML: 'copy_maven_xml',
COPY_MAVEN_COMMAND: 'copy_maven_command',
COPY_MAVEN_SETUP: 'copy_maven_setup_xml',
COPY_NPM_INSTALL_COMMAND: 'copy_npm_install_command',
COPY_NPM_SETUP_COMMAND: 'copy_npm_setup_command',
COPY_YARN_INSTALL_COMMAND: 'copy_yarn_install_command',
COPY_YARN_SETUP_COMMAND: 'copy_yarn_setup_command',
COPY_NUGET_INSTALL_COMMAND: 'copy_nuget_install_command',
COPY_NUGET_SETUP_COMMAND: 'copy_nuget_setup_command',
COPY_PIP_INSTALL_COMMAND: 'copy_pip_install_command',
COPY_PYPI_SETUP_COMMAND: 'copy_pypi_setup_command',
COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND: 'copy_composer_registry_include_command',
COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND: 'copy_composer_package_include_command',
COPY_GRADLE_INSTALL_COMMAND: 'copy_gradle_install_command',
COPY_GRADLE_ADD_TO_SOURCE_COMMAND: 'copy_gradle_add_to_source_command',
COPY_KOTLIN_INSTALL_COMMAND: 'copy_kotlin_install_command',
COPY_KOTLIN_ADD_TO_SOURCE_COMMAND: 'copy_kotlin_add_to_source_command',
};
export const NpmManager = {
NPM: 'npm',
YARN: 'yarn',
};
export const FETCH_PACKAGE_VERSIONS_ERROR = s__(
'PackageRegistry|Unable to fetch package version information.',
);
......
import { PackageType } from '../../shared/constants';
import { getPackageTypeLabel } from '../../shared/utils';
import { NpmManager } from '../constants';
export const packagePipeline = ({ packageEntity }) => {
return packageEntity?.pipeline || null;
};
export const packageTypeDisplay = ({ packageEntity }) => {
return getPackageTypeLabel(packageEntity.package_type);
};
export const packageIcon = ({ packageEntity }) => {
if (packageEntity.package_type === PackageType.NUGET) {
return packageEntity.nuget_metadatum?.icon_url || null;
}
return null;
};
export const conanInstallationCommand = ({ packageEntity }) => {
// eslint-disable-next-line @gitlab/require-i18n-strings
return `conan install ${packageEntity.name} --remote=gitlab`;
};
export const conanSetupCommand = ({ conanPath }) =>
// eslint-disable-next-line @gitlab/require-i18n-strings
`conan remote add gitlab ${conanPath}`;
export const mavenInstallationXml = ({ packageEntity = {} }) => {
const {
app_group: appGroup = '',
app_name: appName = '',
app_version: appVersion = '',
} = packageEntity.maven_metadatum;
return `<dependency>
<groupId>${appGroup}</groupId>
<artifactId>${appName}</artifactId>
<version>${appVersion}</version>
</dependency>`;
};
export const mavenInstallationCommand = ({ packageEntity = {} }) => {
const {
app_group: group = '',
app_name: name = '',
app_version: version = '',
} = packageEntity.maven_metadatum;
return `mvn dependency:get -Dartifact=${group}:${name}:${version}`;
};
export const mavenSetupXml = ({ mavenPath }) => `<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>`;
export const npmInstallationCommand = ({ packageEntity }) => (type = NpmManager.NPM) => {
// eslint-disable-next-line @gitlab/require-i18n-strings
const instruction = type === NpmManager.NPM ? 'npm i' : 'yarn add';
return `${instruction} ${packageEntity.name}`;
};
export const npmSetupCommand = ({ packageEntity, npmPath }) => (type = NpmManager.NPM) => {
const scope = packageEntity.name.substring(0, packageEntity.name.indexOf('/'));
if (type === NpmManager.NPM) {
return `echo ${scope}:registry=${npmPath}/ >> .npmrc`;
}
return `echo \\"${scope}:registry\\" \\"${npmPath}/\\" >> .yarnrc`;
};
export const nugetInstallationCommand = ({ packageEntity }) =>
`nuget install ${packageEntity.name} -Source "GitLab"`;
export const nugetSetupCommand = ({ nugetPath }) =>
`nuget source Add -Name "GitLab" -Source "${nugetPath}" -UserName <your_username> -Password <your_token>`;
export const pypiPipCommand = ({ pypiPath, packageEntity }) =>
// eslint-disable-next-line @gitlab/require-i18n-strings
`pip install ${packageEntity.name} --extra-index-url ${pypiPath}`;
export const pypiSetupCommand = ({ pypiSetupPath }) => `[gitlab]
repository = ${pypiSetupPath}
username = __token__
password = <your personal access token>`;
export const composerRegistryInclude = ({ composerPath, composerConfigRepositoryName }) =>
// eslint-disable-next-line @gitlab/require-i18n-strings
`composer config repositories.${composerConfigRepositoryName} '{"type": "composer", "url": "${composerPath}"}'`;
export const composerPackageInclude = ({ packageEntity }) =>
// eslint-disable-next-line @gitlab/require-i18n-strings
`composer req ${[packageEntity.name]}:${packageEntity.version}`;
export const gradleGroovyInstalCommand = ({ packageEntity }) => {
const {
app_group: group = '',
app_name: name = '',
app_version: version = '',
} = packageEntity.maven_metadatum;
// eslint-disable-next-line @gitlab/require-i18n-strings
return `implementation '${group}:${name}:${version}'`;
};
export const gradleGroovyAddSourceCommand = ({ mavenPath }) =>
// eslint-disable-next-line @gitlab/require-i18n-strings
`maven {
url '${mavenPath}'
}`;
export const gradleKotlinInstalCommand = ({ packageEntity }) => {
const {
app_group: group = '',
app_name: name = '',
app_version: version = '',
} = packageEntity.maven_metadatum;
return `implementation("${group}:${name}:${version}")`;
};
export const gradleKotlinAddSourceCommand = ({ mavenPath }) => `maven("${mavenPath}")`;
export const groupExists = ({ groupListUrl }) => groupListUrl.length > 0;
import { TrackingActions } from './constants';
export const trackInstallationTabChange = {
methods: {
trackInstallationTabChange(tabIndex) {
const action = tabIndex === 0 ? TrackingActions.INSTALLATION : TrackingActions.REGISTRY_SETUP;
this.track(action, { label: this.trackingLabel });
},
},
};
......@@ -10,6 +10,8 @@ export const loadViewer = (type) => {
return () => import(/* webpackChunkName: 'blob_download_viewer' */ './download_viewer.vue');
case 'image':
return () => import(/* webpackChunkName: 'blob_image_viewer' */ './image_viewer.vue');
case 'video':
return () => import(/* webpackChunkName: 'blob_video_viewer' */ './video_viewer.vue');
default:
return null;
}
......@@ -31,5 +33,8 @@ export const viewerProps = (type, blob) => {
url: blob.rawPath,
alt: blob.name,
},
video: {
url: blob.rawPath,
},
}[type];
};
<script>
export default {
props: {
url: {
type: String,
required: true,
},
},
};
</script>
<template>
<div class="gl-text-center gl-p-7 gl-bg-gray-50">
<video :src="url" controls data-testid="video" class="gl-max-w-full"></video>
</div>
</template>
......@@ -194,8 +194,7 @@ class IssuableFinder
def use_cte_for_search?
strong_memoize(:use_cte_for_search) do
next false unless search
# Only simple unsorted & simple sorts can use CTE
next false if params[:sort].present? && !params[:sort].in?(klass.simple_sorts.keys)
next false unless default_or_simple_sort?
attempt_group_search_optimizations? || attempt_project_search_optimizations?
end
......@@ -244,6 +243,10 @@ class IssuableFinder
klass.all
end
def default_or_simple_sort?
params[:sort].blank? || params[:sort].to_s.in?(klass.simple_sorts.keys)
end
def attempt_group_search_optimizations?
params[:attempt_group_search_optimizations]
end
......
......@@ -1427,7 +1427,7 @@
:urgency: :high
:resource_boundary: :unknown
:weight: 3
:idempotent:
:idempotent: true
:tags: []
- :name: pipeline_cache:expire_pipeline_cache
:worker_name: ExpirePipelineCacheWorker
......
......@@ -10,11 +10,9 @@ class ExpireJobCacheWorker # rubocop:disable Scalability/IdempotentWorker
queue_namespace :pipeline_cache
urgency :high
# This worker should be idempotent, but we're switching to data_consistency
# :sticky and there is an ongoing incompatibility, so it needs to be disabled for
# now. The following line can be uncommented and this comment removed once
# https://gitlab.com/gitlab-org/gitlab/-/issues/325291 is resolved.
# idempotent!
deduplicate :until_executing, including_scheduled: true
idempotent!
# rubocop: disable CodeReuse/ActiveRecord
def perform(job_id)
......
# frozen_string_literal: true
require 'logger'
desc "GitLab | Packages | Build composer cache"
namespace :gitlab do
namespace :packages do
task build_composer_cache: :environment do
logger = Logger.new($stdout)
logger.info('Starting to build composer cache files')
::Packages::Package.composer.find_in_batches do |packages|
packages.group_by { |pkg| [pkg.project_id, pkg.name] }.each do |(project_id, name), packages|
logger.info("Building cache for #{project_id} -> #{name}")
Gitlab::Composer::Cache.new(project: packages.first.project, name: name).execute
end
end
end
end
end
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe 'CI Lint', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/297782' do
RSpec.describe 'CI Lint', :js do
include Spec::Support::Helpers::Features::SourceEditorSpecHelpers
let(:project) { create(:project, :repository) }
......
......@@ -1199,6 +1199,14 @@ RSpec.describe IssuesFinder do
end
end
context 'when a non-simple sort is given' do
let(:params) { { search: 'foo', attempt_project_search_optimizations: true, sort: 'popularity' } }
it 'returns false' do
expect(finder.use_cte_for_search?).to be_falsey
end
end
context 'when all conditions are met' do
context "uses group search optimization" do
let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
......@@ -1217,6 +1225,24 @@ RSpec.describe IssuesFinder do
expect(finder.execute.to_sql).to match(/^WITH "issues" AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported}/)
end
end
context 'with simple sort' do
let(:params) { { search: 'foo', attempt_project_search_optimizations: true, sort: 'updated_desc' } }
it 'returns true' do
expect(finder.use_cte_for_search?).to be_truthy
expect(finder.execute.to_sql).to match(/^WITH "issues" AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported}/)
end
end
context 'with simple sort as a symbol' do
let(:params) { { search: 'foo', attempt_project_search_optimizations: true, sort: :updated_desc } }
it 'returns true' do
expect(finder.use_cte_for_search?).to be_truthy
expect(finder.execute.to_sql).to match(/^WITH "issues" AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported}/)
end
end
end
end
......
import { NpmManager } from '~/packages/details/constants';
import { packagePipeline } from '~/packages/details/store/getters';
import {
conanInstallationCommand,
conanSetupCommand,
packagePipeline,
packageTypeDisplay,
packageIcon,
mavenInstallationXml,
mavenInstallationCommand,
mavenSetupXml,
npmInstallationCommand,
npmSetupCommand,
nugetInstallationCommand,
nugetSetupCommand,
pypiPipCommand,
pypiSetupCommand,
composerRegistryInclude,
composerPackageInclude,
groupExists,
gradleGroovyInstalCommand,
gradleGroovyAddSourceCommand,
gradleKotlinInstalCommand,
gradleKotlinAddSourceCommand,
} from '~/packages/details/store/getters';
import {
conanPackage,
npmPackage,
nugetPackage,
mockPipelineInfo,
mavenPackage as packageWithoutBuildInfo,
pypiPackage,
rubygemsPackage,
} from '../../mock_data';
import {
generateMavenCommand,
generateXmlCodeBlock,
generateMavenSetupXml,
registryUrl,
pypiSetupCommandStr,
} from '../mock_data';
describe('Getters PackageDetails Store', () => {
let state;
const defaultState = {
packageEntity: packageWithoutBuildInfo,
conanPath: registryUrl,
mavenPath: registryUrl,
npmPath: registryUrl,
nugetPath: registryUrl,
pypiPath: registryUrl,
};
const setupState = (testState = {}) => {
......@@ -58,28 +19,6 @@ describe('Getters PackageDetails Store', () => {
};
};
const conanInstallationCommandStr = `conan install ${conanPackage.name} --remote=gitlab`;
const conanSetupCommandStr = `conan remote add gitlab ${registryUrl}`;
const mavenCommandStr = generateMavenCommand(packageWithoutBuildInfo.maven_metadatum);
const mavenInstallationXmlBlock = generateXmlCodeBlock(packageWithoutBuildInfo.maven_metadatum);
const mavenSetupXmlBlock = generateMavenSetupXml();
const npmInstallStr = `npm i ${npmPackage.name}`;
const npmSetupStr = `echo @Test:registry=${registryUrl}/ >> .npmrc`;
const yarnInstallStr = `yarn add ${npmPackage.name}`;
const yarnSetupStr = `echo \\"@Test:registry\\" \\"${registryUrl}/\\" >> .yarnrc`;
const nugetInstallationCommandStr = `nuget install ${nugetPackage.name} -Source "GitLab"`;
const nugetSetupCommandStr = `nuget source Add -Name "GitLab" -Source "${registryUrl}" -UserName <your_username> -Password <your_token>`;
const pypiPipCommandStr = `pip install ${pypiPackage.name} --extra-index-url ${registryUrl}`;
const composerRegistryIncludeStr =
'composer config repositories.gitlab.com/123 \'{"type": "composer", "url": "foo"}\'';
const composerPackageIncludeStr = `composer req ${[packageWithoutBuildInfo.name]}:${
packageWithoutBuildInfo.version
}`;
describe('packagePipeline', () => {
it('should return the pipeline info when pipeline exists', () => {
setupState({
......@@ -93,203 +32,9 @@ describe('Getters PackageDetails Store', () => {
});
it('should return null when build_info does not exist', () => {
setupState();
setupState({ pipeline: undefined });
expect(packagePipeline(state)).toBe(null);
});
});
describe('packageTypeDisplay', () => {
describe.each`
packageEntity | expectedResult
${conanPackage} | ${'Conan'}
${packageWithoutBuildInfo} | ${'Maven'}
${npmPackage} | ${'npm'}
${nugetPackage} | ${'NuGet'}
${pypiPackage} | ${'PyPI'}
${rubygemsPackage} | ${'RubyGems'}
`(`package type`, ({ packageEntity, expectedResult }) => {
beforeEach(() => setupState({ packageEntity }));
it(`${packageEntity.package_type} should show as ${expectedResult}`, () => {
expect(packageTypeDisplay(state)).toBe(expectedResult);
});
});
});
describe('packageIcon', () => {
describe('nuget packages', () => {
it('should return nuget package icon', () => {
setupState({ packageEntity: nugetPackage });
expect(packageIcon(state)).toBe(nugetPackage.nuget_metadatum.icon_url);
});
it('should return null when nuget package does not have an icon', () => {
setupState({ packageEntity: { ...nugetPackage, nuget_metadatum: {} } });
expect(packageIcon(state)).toBe(null);
});
});
it('should not find icons for other package types', () => {
setupState({ packageEntity: npmPackage });
expect(packageIcon(state)).toBe(null);
});
});
describe('conan string getters', () => {
it('gets the correct conanInstallationCommand', () => {
setupState({ packageEntity: conanPackage });
expect(conanInstallationCommand(state)).toBe(conanInstallationCommandStr);
});
it('gets the correct conanSetupCommand', () => {
setupState({ packageEntity: conanPackage });
expect(conanSetupCommand(state)).toBe(conanSetupCommandStr);
});
});
describe('maven string getters', () => {
it('gets the correct mavenInstallationXml', () => {
setupState();
expect(mavenInstallationXml(state)).toBe(mavenInstallationXmlBlock);
});
it('gets the correct mavenInstallationCommand', () => {
setupState();
expect(mavenInstallationCommand(state)).toBe(mavenCommandStr);
});
it('gets the correct mavenSetupXml', () => {
setupState();
expect(mavenSetupXml(state)).toBe(mavenSetupXmlBlock);
});
});
describe('npm string getters', () => {
it('gets the correct npmInstallationCommand for npm', () => {
setupState({ packageEntity: npmPackage });
expect(npmInstallationCommand(state)(NpmManager.NPM)).toBe(npmInstallStr);
});
it('gets the correct npmSetupCommand for npm', () => {
setupState({ packageEntity: npmPackage });
expect(npmSetupCommand(state)(NpmManager.NPM)).toBe(npmSetupStr);
});
it('gets the correct npmInstallationCommand for Yarn', () => {
setupState({ packageEntity: npmPackage });
expect(npmInstallationCommand(state)(NpmManager.YARN)).toBe(yarnInstallStr);
});
it('gets the correct npmSetupCommand for Yarn', () => {
setupState({ packageEntity: npmPackage });
expect(npmSetupCommand(state)(NpmManager.YARN)).toBe(yarnSetupStr);
});
});
describe('nuget string getters', () => {
it('gets the correct nugetInstallationCommand', () => {
setupState({ packageEntity: nugetPackage });
expect(nugetInstallationCommand(state)).toBe(nugetInstallationCommandStr);
});
it('gets the correct nugetSetupCommand', () => {
setupState({ packageEntity: nugetPackage });
expect(nugetSetupCommand(state)).toBe(nugetSetupCommandStr);
});
});
describe('pypi string getters', () => {
it('gets the correct pypiPipCommand', () => {
setupState({ packageEntity: pypiPackage });
expect(pypiPipCommand(state)).toBe(pypiPipCommandStr);
});
it('gets the correct pypiSetupCommand', () => {
setupState({ pypiSetupPath: 'foo' });
expect(pypiSetupCommand(state)).toBe(pypiSetupCommandStr);
});
});
describe('composer string getters', () => {
it('gets the correct composerRegistryInclude command', () => {
setupState({ composerPath: 'foo', composerConfigRepositoryName: 'gitlab.com/123' });
expect(composerRegistryInclude(state)).toBe(composerRegistryIncludeStr);
});
it('gets the correct composerPackageInclude command', () => {
setupState();
expect(composerPackageInclude(state)).toBe(composerPackageIncludeStr);
});
});
describe('gradle groovy string getters', () => {
it('gets the correct gradleGroovyInstalCommand', () => {
setupState();
expect(gradleGroovyInstalCommand(state)).toMatchInlineSnapshot(
`"implementation 'com.test.app:test-app:1.0-SNAPSHOT'"`,
);
});
it('gets the correct gradleGroovyAddSourceCommand', () => {
setupState();
expect(gradleGroovyAddSourceCommand(state)).toMatchInlineSnapshot(`
"maven {
url 'foo/registry'
}"
`);
});
});
describe('gradle kotlin string getters', () => {
it('gets the correct gradleKotlinInstalCommand', () => {
setupState();
expect(gradleKotlinInstalCommand(state)).toMatchInlineSnapshot(
`"implementation(\\"com.test.app:test-app:1.0-SNAPSHOT\\")"`,
);
});
it('gets the correct gradleKotlinAddSourceCommand', () => {
setupState();
expect(gradleKotlinAddSourceCommand(state)).toMatchInlineSnapshot(
`"maven(\\"foo/registry\\")"`,
);
});
});
describe('check if group', () => {
it('is set', () => {
setupState({ groupListUrl: '/groups/composer/-/packages' });
expect(groupExists(state)).toBe(true);
});
it('is not set', () => {
setupState({ groupListUrl: '' });
expect(groupExists(state)).toBe(false);
});
});
});
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import VideoViewer from '~/repository/components/blob_viewers/video_viewer.vue';
describe('Video Viewer', () => {
let wrapper;
const propsData = { url: 'some/video.mp4' };
const createComponent = () => {
wrapper = shallowMountExtended(VideoViewer, { propsData });
};
const findVideo = () => wrapper.findByTestId('video');
it('renders a Video element', () => {
createComponent();
expect(findVideo().exists()).toBe(true);
expect(findVideo().attributes('src')).toBe(propsData.url);
expect(findVideo().attributes('controls')).not.toBeUndefined();
});
});
# frozen_string_literal: true
require 'rake_helper'
RSpec.describe 'gitlab:packages:build_composer_cache namespace rake task', :silence_stdout do
let_it_be(:package_name) { 'sample-project' }
let_it_be(:package_name2) { 'sample-project2' }
let_it_be(:json) { { 'name' => package_name } }
let_it_be(:json2) { { 'name' => package_name2 } }
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :custom_repo, files: { 'composer.json' => json.to_json }, group: group) }
let_it_be(:project2) { create(:project, :custom_repo, files: { 'composer.json' => json2.to_json }, group: group) }
let!(:package) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '1.0.0', json: json) }
let!(:package2) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '2.0.0', json: json) }
let!(:package3) { create(:composer_package, :with_metadatum, project: project2, name: package_name2, version: '3.0.0', json: json2) }
before :all do
Rake.application.rake_require 'tasks/gitlab/packages/composer'
end
subject do
run_rake_task("gitlab:packages:build_composer_cache")
end
it 'generates the cache files' do
expect { subject }.to change { Packages::Composer::CacheFile.count }.by(2)
end
end
......@@ -13,6 +13,8 @@ RSpec.describe ExpireJobCacheWorker do
let(:job_args) { job.id }
it_behaves_like 'an idempotent worker'
it_behaves_like 'worker with data consistency',
described_class,
data_consistency: :delayed
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment