Commit afdfa282 authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera Committed by David O'Regan

Customise Terraform modules details page

parent bd3bdd14
......@@ -24,7 +24,6 @@ import DependencyRow from './dependency_row.vue';
import InstallationCommands from './installation_commands.vue';
import PackageFiles from './package_files.vue';
import PackageHistory from './package_history.vue';
import PackageTitle from './package_title.vue';
export default {
name: 'PackagesApp',
......@@ -36,7 +35,9 @@ export default {
GlTab,
GlTabs,
GlSprintf,
PackageTitle,
PackageTitle: () => import('./package_title.vue'),
TerraformTitle: () =>
import('~/packages_and_registries/infrastructure_registry/components/details_title.vue'),
PackagesListLoader,
PackageListRow,
DependencyRow,
......@@ -50,6 +51,12 @@ export default {
GlModal: GlModalDirective,
},
mixins: [Tracking.mixin()],
inject: {
titleComponent: {
default: 'PackageTitle',
from: 'titleComponent',
},
},
trackingActions: { ...TrackingActions },
data() {
return {
......@@ -160,7 +167,7 @@ export default {
/>
<div v-else class="packages-app">
<package-title>
<component :is="titleComponent">
<template #delete-button>
<gl-button
v-if="canDelete"
......@@ -173,7 +180,7 @@ export default {
{{ __('Delete') }}
</gl-button>
</template>
</package-title>
</component>
<gl-tabs>
<gl-tab :title="__('Detail')">
......
<script>
import { PackageType } from '../../shared/constants';
import TerraformInstallation from '~/packages_and_registries/infrastructure_registry/components/terraform_installation.vue';
import { PackageType, TERRAFORM_PACKAGE_TYPE } from '../../shared/constants';
import ComposerInstallation from './composer_installation.vue';
import ConanInstallation from './conan_installation.vue';
import MavenInstallation from './maven_installation.vue';
......@@ -16,6 +17,7 @@ export default {
[PackageType.NUGET]: NugetInstallation,
[PackageType.PYPI]: PypiInstallation,
[PackageType.COMPOSER]: ComposerInstallation,
[TERRAFORM_PACKAGE_TYPE]: TerraformInstallation,
},
props: {
packageEntity: {
......
......@@ -11,6 +11,9 @@ export const PackageType = {
GENERIC: 'generic',
};
// we want this separated from the main dictionary to avoid it being pulled in the search of package
export const TERRAFORM_PACKAGE_TYPE = 'terraform_module';
export const TrackingActions = {
DELETE_PACKAGE: 'delete_package',
REQUEST_DELETE_PACKAGE: 'request_delete_package',
......
<script>
import { GlIcon, GlSprintf, GlTooltipDirective } from '@gitlab/ui';
import { mapState, mapGetters } from 'vuex';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import { __ } from '~/locale';
import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
export default {
name: 'DetailsTitle',
components: {
TitleArea,
GlIcon,
GlSprintf,
MetadataItem,
},
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [timeagoMixin],
i18n: {
packageInfo: __('v%{version} published %{timeAgo}'),
},
computed: {
...mapState(['packageEntity', 'packageFiles']),
...mapGetters(['packagePipeline']),
totalSize() {
return numberToHumanSize(this.packageFiles.reduce((acc, p) => acc + p.size, 0));
},
},
methods: {
dynamicSlotName(index) {
return `metadata-tag${index}`;
},
},
};
</script>
<template>
<title-area :title="packageEntity.name" data-qa-selector="package_title">
<template #sub-header>
<gl-icon name="eye" class="gl-mr-3" />
<gl-sprintf :message="$options.i18n.packageInfo">
<template #version>
{{ packageEntity.version }}
</template>
<template #timeAgo>
<span v-gl-tooltip :title="tooltipTitle(packageEntity.created_at)">
&nbsp;{{ timeFormatted(packageEntity.created_at) }}
</span>
</template>
</gl-sprintf>
</template>
<template #metadata-type>
<metadata-item data-testid="package-type" icon="infrastructure-registry" text="Terraform" />
</template>
<template #metadata-size>
<metadata-item data-testid="package-size" icon="disk" :text="totalSize" />
</template>
<template v-if="packagePipeline" #metadata-pipeline>
<metadata-item
data-testid="pipeline-project"
icon="review-list"
:text="packagePipeline.project.name"
:link="packagePipeline.project.web_url"
/>
</template>
<template v-if="packagePipeline" #metadata-ref>
<metadata-item data-testid="package-ref" icon="branch" :text="packagePipeline.ref" />
</template>
<template #right-actions>
<slot name="delete-button"></slot>
</template>
</title-area>
</template>
<script>
import { GlLink, GlSprintf } from '@gitlab/ui';
import { mapState } from 'vuex';
import { s__ } from '~/locale';
import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
export default {
name: 'ConanInstallation',
components: {
CodeInstruction,
GlLink,
GlSprintf,
},
computed: {
...mapState(['packageEntity', 'terraformHelpPath', 'projectPath']),
provisionInstructions() {
// eslint-disable-next-line @gitlab/require-i18n-strings
return `module "${this.packageEntity.name}" {
source = "${this.projectPath}/${this.packageEntity.name}"
version = "${this.packageEntity.version}"
}`;
},
registrySetup() {
return `credentials "gitlab.com" {
token = "<TOKEN>"
}`;
},
},
i18n: {
helpText: s__(
'InfrastructureRegistry|For more information on the Terraform registry, %{linkStart}see our documentation%{linkEnd}.',
),
},
};
</script>
<template>
<div>
<h3 class="gl-font-lg">{{ __('Provision instructions') }}</h3>
<code-instruction
:label="
s__(
'InfrastructureRegistry|Copy and paste into your Terraform configuration, insert the variables, and run Terraform init:',
)
"
:instruction="provisionInstructions"
:copy-text="s__('InfrastructureRegistry|Copy Terraform Command')"
multiline
/>
<h3 class="gl-font-lg">{{ __('Registry setup') }}</h3>
<code-instruction
:label="s__('InfrastructureRegistry|To authorize access to the Terraform registry:')"
:instruction="registrySetup"
:copy-text="s__('InfrastructureRegistry|Copy Terraform Setup Command')"
multiline
/>
<gl-sprintf :message="$options.i18n.helpText">
<template #link="{ content }">
<gl-link :href="terraformHelpPath">{{ content }}</gl-link>
</template>
</gl-sprintf>
</div>
</template>
......@@ -22,6 +22,9 @@ export default () => {
return new Vue({
el,
store,
provide: {
titleComponent: 'TerraformTitle',
},
render(createElement) {
return createElement(PackagesApp);
},
......
......@@ -10,4 +10,6 @@
can_delete: can?(current_user, :destroy_package, @project).to_s,
svg_path: image_path('illustrations/no-packages.svg'),
project_name: @project.name,
project_path: expose_url(@project.full_path),
terraform_help_path: help_page_path('user/infrastructure/index'),
project_list_url: project_infrastructure_registry_index_path(@project)} }
......@@ -17444,6 +17444,18 @@ msgstr ""
msgid "Infrastructure Registry"
msgstr ""
msgid "InfrastructureRegistry|Copy Terraform Command"
msgstr ""
msgid "InfrastructureRegistry|Copy Terraform Setup Command"
msgstr ""
msgid "InfrastructureRegistry|Copy and paste into your Terraform configuration, insert the variables, and run Terraform init:"
msgstr ""
msgid "InfrastructureRegistry|For more information on the Terraform registry, %{linkStart}see our documentation%{linkEnd}."
msgstr ""
msgid "InfrastructureRegistry|Infrastructure Registry"
msgstr ""
......@@ -17456,6 +17468,9 @@ msgstr ""
msgid "InfrastructureRegistry|Terraform modules are the main way to package and reuse resource configurations with Terraform. Learn more about how to %{noPackagesLinkStart}create Terraform modules%{noPackagesLinkEnd} in GitLab."
msgstr ""
msgid "InfrastructureRegistry|To authorize access to the Terraform registry:"
msgstr ""
msgid "InfrastructureRegistry|You have no Terraform modules in your project"
msgstr ""
......@@ -26597,6 +26612,9 @@ msgstr ""
msgid "Provider"
msgstr ""
msgid "Provision instructions"
msgstr ""
msgid "Provisioned by:"
msgstr ""
......
import { GlEmptyState } from '@gitlab/ui';
import { mount, createLocalVue } from '@vue/test-utils';
import { nextTick } from 'vue';
import Vuex from 'vuex';
import stubChildren from 'helpers/stub_children';
......@@ -109,9 +110,11 @@ describe('PackagesApp', () => {
window.location = location;
});
it('renders the app and displays the package title', () => {
it('renders the app and displays the package title', async () => {
createComponent();
await nextTick();
expect(packageTitle().exists()).toBe(true);
});
......
......@@ -7,6 +7,7 @@ import MavenInstallation from '~/packages/details/components/maven_installation.
import NpmInstallation from '~/packages/details/components/npm_installation.vue';
import NugetInstallation from '~/packages/details/components/nuget_installation.vue';
import PypiInstallation from '~/packages/details/components/pypi_installation.vue';
import TerraformInstallation from '~/packages_and_registries/infrastructure_registry/components/terraform_installation.vue';
import {
conanPackage,
......@@ -15,6 +16,7 @@ import {
nugetPackage,
pypiPackage,
composerPackage,
terraformModule,
} from '../../mock_data';
describe('InstallationCommands', () => {
......@@ -32,6 +34,7 @@ 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();
......@@ -46,6 +49,7 @@ describe('InstallationCommands', () => {
${nugetPackage} | ${nugetInstallation}
${pypiPackage} | ${pypiInstallation}
${composerPackage} | ${composerInstallation}
${terraformModule} | ${terraformInstallation}
`('renders', ({ packageEntity, selector }) => {
it(`${packageEntity.package_type} instructions exist`, () => {
createComponent({ packageEntity });
......
......@@ -178,6 +178,20 @@ export const composerPackage = {
version: '1.0.0',
};
export const terraformModule = {
created_at: '2015-12-10',
id: 2,
name: 'Test/system-22',
package_type: 'terraform_module',
project_path: 'foo/bar/baz',
projectPathName: 'foo/bar/baz',
project_id: 1,
updated_at: '2015-12-10',
version: '0.1',
versions: [],
_links,
};
export const mockTags = [
{
name: 'foo-1',
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TerraformInstallation renders all the messages 1`] = `
<div>
<h3
class="gl-font-lg"
>
Provision instructions
</h3>
<code-instruction-stub
copytext="Copy Terraform Command"
instruction="module \\"Test/system-22\\" {
source = \\"foo/Test/system-22\\"
version = \\"0.1\\"
}"
label="Copy and paste into your Terraform configuration, insert the variables, and run Terraform init:"
multiline="true"
trackingaction=""
trackinglabel=""
/>
<h3
class="gl-font-lg"
>
Registry setup
</h3>
<code-instruction-stub
copytext="Copy Terraform Setup Command"
instruction="credentials \\"gitlab.com\\" {
token = \\"<TOKEN>\\"
}"
label="To authorize access to the Terraform registry:"
multiline="true"
trackingaction=""
trackinglabel=""
/>
<gl-sprintf-stub
message="For more information on the Terraform registry, %{linkStart}see our documentation%{linkEnd}."
/>
</div>
`;
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { terraformModule, mavenFiles, npmPackage } from 'jest/packages/mock_data';
import component from '~/packages_and_registries/infrastructure_registry/components/details_title.vue';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('PackageTitle', () => {
let wrapper;
let store;
function createComponent({ packageFiles = mavenFiles, packageEntity = terraformModule } = {}) {
store = new Vuex.Store({
state: {
packageEntity,
packageFiles,
},
getters: {
packagePipeline: ({ packageEntity: { pipeline = null } }) => pipeline,
},
});
wrapper = shallowMount(component, {
localVue,
store,
stubs: {
TitleArea,
},
});
return wrapper.vm.$nextTick();
}
const findTitleArea = () => wrapper.findComponent(TitleArea);
const packageSize = () => wrapper.find('[data-testid="package-size"]');
const pipelineProject = () => wrapper.find('[data-testid="pipeline-project"]');
const packageRef = () => wrapper.find('[data-testid="package-ref"]');
afterEach(() => {
wrapper.destroy();
});
describe('module title', () => {
it('is correctly bound', async () => {
await createComponent();
expect(findTitleArea().props('title')).toBe(terraformModule.name);
});
});
describe('calculates the package size', () => {
it('correctly calculates the size', async () => {
await createComponent();
expect(packageSize().props('text')).toBe('300 bytes');
});
});
describe('package ref', () => {
it('does not display the ref if missing', async () => {
await createComponent();
expect(packageRef().exists()).toBe(false);
});
it('correctly shows the package ref if there is one', async () => {
await createComponent({ packageEntity: npmPackage });
expect(packageRef().props()).toMatchObject({
text: npmPackage.pipeline.ref,
icon: 'branch',
});
});
});
describe('pipeline project', () => {
it('does not display the project if missing', async () => {
await createComponent();
expect(pipelineProject().exists()).toBe(false);
});
it('correctly shows the pipeline project if there is one', async () => {
await createComponent({ packageEntity: npmPackage });
expect(pipelineProject().props()).toMatchObject({
text: npmPackage.pipeline.project.name,
icon: 'review-list',
link: npmPackage.pipeline.project.web_url,
});
});
});
});
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { terraformModule as packageEntity } from 'jest/packages/mock_data';
import TerraformInstallation from '~/packages_and_registries/infrastructure_registry/components/terraform_installation.vue';
import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('TerraformInstallation', () => {
let wrapper;
const store = new Vuex.Store({
state: {
packageEntity,
projectPath: 'foo',
},
});
const findCodeInstructions = () => wrapper.findAllComponents(CodeInstructions);
function createComponent() {
wrapper = shallowMount(TerraformInstallation, {
localVue,
store,
});
}
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
it('renders all the messages', () => {
expect(wrapper.element).toMatchSnapshot();
});
describe('installation commands', () => {
it('renders the correct command', () => {
expect(findCodeInstructions().at(0).props('instruction')).toMatchInlineSnapshot(`
"module \\"Test/system-22\\" {
source = \\"foo/Test/system-22\\"
version = \\"0.1\\"
}"
`);
});
});
describe('setup commands', () => {
it('renders the correct command', () => {
expect(findCodeInstructions().at(1).props('instruction')).toMatchInlineSnapshot(`
"credentials \\"gitlab.com\\" {
token = \\"<TOKEN>\\"
}"
`);
});
});
});
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