diff --git a/app/assets/javascripts/ci/catalog/components/details/ci_resource_components.vue b/app/assets/javascripts/ci/catalog/components/details/ci_resource_components.vue index 85dfa12c7566c900ca488aabbc55e043df23e3f2..f494a3bd224f23d80e408103c668a4d04df277c6 100644 --- a/app/assets/javascripts/ci/catalog/components/details/ci_resource_components.vue +++ b/app/assets/javascripts/ci/catalog/components/details/ci_resource_components.vue @@ -1,11 +1,12 @@ <script> -import { GlLoadingIcon, GlTableLite } from '@gitlab/ui'; +import { GlButton, GlLoadingIcon, GlTableLite } from '@gitlab/ui'; import { createAlert } from '~/alert'; import { __, s__ } from '~/locale'; import getCiCatalogResourceComponents from '../../graphql/queries/get_ci_catalog_resource_components.query.graphql'; export default { components: { + GlButton, GlLoadingIcon, GlTableLite, }, @@ -70,6 +71,8 @@ export default { }, ], i18n: { + copyText: __('Copy value'), + copyAriaText: __('Copy to clipboard'), inputTitle: s__('CiCatalogComponent|Inputs'), fetchError: s__("CiCatalogComponent|There was an error fetching this resource's components"), }, @@ -88,7 +91,24 @@ export default { > <h3 class="gl-font-size-h2" data-testid="component-name">{{ component.name }}</h3> <p class="gl-mt-5">{{ component.description }}</p> - <pre class="gl-w-85p gl-py-4">{{ generateSnippet(component.path) }}</pre> + <div class="gl-display-flex"> + <pre + class="gl-w-85p gl-py-4 gl-display-flex gl-justify-content-space-between gl-m-0 gl-border-r-none" + ><span>{{ generateSnippet(component.path) }}</span> + </pre> + <div class="gl--flex-center gl-bg-gray-10 gl-border gl-border-l-none"> + <gl-button + class="gl-p-4! gl-mr-3!" + category="tertiary" + icon="copy-to-clipboard" + size="small" + :title="$options.i18n.copyText" + :data-clipboard-text="generateSnippet(component.path)" + data-testid="copy-to-clipboard" + :aria-label="$options.i18n.copyAriaText" + /> + </div> + </div> <div class="gl-mt-5"> <b class="gl-display-block gl-mb-4"> {{ $options.i18n.inputTitle }}</b> <gl-table-lite :items="component.inputs.nodes" :fields="$options.fields"> diff --git a/spec/frontend/ci/catalog/components/details/ci_resource_components_spec.js b/spec/frontend/ci/catalog/components/details/ci_resource_components_spec.js index a41996d20b3af5974a73cf69bb65b14775e3c044..93f9f0b7e2307e285424a97c189c8a40a3014385 100644 --- a/spec/frontend/ci/catalog/components/details/ci_resource_components_spec.js +++ b/spec/frontend/ci/catalog/components/details/ci_resource_components_spec.js @@ -38,6 +38,7 @@ describe('CiResourceComponents', () => { }; const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); + const findCopyToClipboardButton = (i) => wrapper.findAllByTestId('copy-to-clipboard').at(i); const findComponents = () => wrapper.findAllByTestId('component-section'); beforeEach(() => { @@ -98,6 +99,15 @@ describe('CiResourceComponents', () => { }); }); + it('adds a copy-to-clipboard button', () => { + components.forEach((component, i) => { + const button = findCopyToClipboardButton(i); + + expect(button.props().icon).toBe('copy-to-clipboard'); + expect(button.attributes('data-clipboard-text')).toContain(component.path); + }); + }); + describe('inputs', () => { it('renders the component parameter attributes', () => { const [firstComponent] = components;