diff --git a/ee/app/assets/javascripts/integrations/edit/components/google_cloud_iam/constants.js b/ee/app/assets/javascripts/integrations/edit/components/google_cloud_iam/constants.js new file mode 100644 index 0000000000000000000000000000000000000000..80b3e6ffd4953b357656e082916cde39b32d0d47 --- /dev/null +++ b/ee/app/assets/javascripts/integrations/edit/components/google_cloud_iam/constants.js @@ -0,0 +1,5 @@ +export const STATE_EMPTY = 'empty'; +export const STATE_FORM = 'form'; +export const STATE_GUIDED = 'guided'; +export const STATE_INITIAL = 'initial'; +export const STATE_MANUAL = 'manual'; diff --git a/ee/app/assets/javascripts/integrations/edit/components/google_cloud_iam/empty_state.vue b/ee/app/assets/javascripts/integrations/edit/components/google_cloud_iam/empty_state.vue new file mode 100644 index 0000000000000000000000000000000000000000..6f57128ddcaa3671228a30aa179ac138e469358e --- /dev/null +++ b/ee/app/assets/javascripts/integrations/edit/components/google_cloud_iam/empty_state.vue @@ -0,0 +1,70 @@ +<script> +import { GlButton, GlEmptyState, GlIcon, GlLink, GlSprintf } from '@gitlab/ui'; +import EmptyAdminAppsSvg from '@gitlab/svgs/dist/illustrations/empty-state/empty-admin-apps-md.svg'; +import InviteMembersTrigger from '~/invite_members/components/invite_members_trigger.vue'; +import { STATE_GUIDED, STATE_MANUAL } from './constants'; + +export default { + components: { + GlButton, + GlIcon, + GlEmptyState, + GlLink, + InviteMembersTrigger, + GlSprintf, + }, + methods: { + show(type) { + this.$emit('show', type); + }, + }, + EmptyAdminAppsSvg, + STATE_GUIDED, + STATE_MANUAL, +}; +</script> + +<template> + <gl-empty-state + :svg-path="$options.EmptyAdminAppsSvg" + :title="s__('GoogleCloudPlatformService|Connect to Google Cloud')" + > + <template #description> + <gl-sprintf + :message=" + s__( + 'GoogleCloudPlatformService|Connect to Google Cloud with workload identity federation. Select %{strongStart}Guided setup%{strongEnd} if you can manage workload identity federation in Google Cloud. %{linkStart}What are the required permissions?%{linkEnd}', + ) + " + > + <template #strong="{ content }"> + <strong>{{ content }}</strong> + </template> + <template #link="{ content }"> + <gl-link + target="_blank" + rel="noopener noreferrer" + href="https://cloud.google.com/iam/docs/manage-workload-identity-pools-providers#required-roles" + > + {{ content }} + <gl-icon name="external-link" :aria-label="__('(external link)')" /> + </gl-link> + </template> + </gl-sprintf> + </template> + <template #actions> + <div class="gl-display-flex gl-gap-3"> + <gl-button variant="confirm" @click="show($options.STATE_GUIDED)">{{ + s__('GoogleCloudPlatformService|Guided setup') + }}</gl-button> + <gl-button @click="show($options.STATE_MANUAL)">{{ + s__('GoogleCloudPlatformService|Manual setup') + }}</gl-button> + <invite-members-trigger + :display-text="s__('GoogleCloudPlatformService|Invite member to set up')" + trigger-source="google_cloud_artifact_registry_setup" + /> + </div> + </template> + </gl-empty-state> +</template> diff --git a/ee/app/assets/javascripts/integrations/edit/components/google_cloud_iam/guided_setup.vue b/ee/app/assets/javascripts/integrations/edit/components/google_cloud_iam/guided_setup.vue new file mode 100644 index 0000000000000000000000000000000000000000..6feddc337a37926e3f38f18cf4feb790d998e760 --- /dev/null +++ b/ee/app/assets/javascripts/integrations/edit/components/google_cloud_iam/guided_setup.vue @@ -0,0 +1,154 @@ +<script> +import { GlButton, GlIcon, GlLink, GlSprintf } from '@gitlab/ui'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; +import { STATE_MANUAL } from './constants'; + +export default { + components: { + GlButton, + GlIcon, + GlLink, + GlSprintf, + ClipboardButton, + }, + curlLine: `export GL_PAT=<your_access_token> +curl --request GET + --data 'google_cloud_project_id=<your_google_cloud_project_id>' + --header "PRIVATE-TOKEN: $GL_PAT" + --url "https://gitlab.com/api/v4/projects/<your_gitlab_project_id>/google_cloud/setup/wlif.sh" +| bash`, + STATE_MANUAL, +}; +</script> + +<template> + <div> + <h3>{{ s__('GoogleCloudPlatformService|Guided setup') }}</h3> + <p> + <gl-sprintf + :message=" + s__( + 'GoogleCloudPlatformService|%{linkStart}Switch to the manual setup%{linkEnd} if you cannot manage workload identity federation in Google Cloud. %{link2Start}What are the required permissions?%{link2End}', + ) + " + > + <template #link="{ content }"> + <gl-link @click="$emit('show', $options.STATE_MANUAL)">{{ content }}</gl-link> + </template> + > + <template #link2="{ content }"> + <gl-link + target="_blank" + rel="noopener noreferrer" + href="https://cloud.google.com/iam/docs/manage-workload-identity-pools-providers#required-roles" + > + {{ content }} + <gl-icon name="external-link" :aria-label="__('(external link)')" /> + </gl-link> + </template> + </gl-sprintf> + </p> + + <h4>{{ s__('GoogleCloudPlatformService|Instructions') }}</h4> + <p> + <gl-sprintf + :message=" + s__( + 'GoogleCloudPlatformService|Before you begin, %{linkStart}install the Google Cloud CLI%{linkEnd}.', + ) + " + > + <template #link="{ content }"> + <gl-link + target="_blank" + rel="noopener noreferrer" + href="https://cloud.google.com/sdk/docs/install#installation_instructions" + > + {{ content }} + <gl-icon name="external-link" :aria-label="__('(external link)')" /> + </gl-link> + </template> + </gl-sprintf> + </p> + <p> + {{ + s__( + 'GoogleCloudPlatformService|Run the following command to set up and connect to your Google Cloud project with workload identity federation.', + ) + }} + </p> + <ul> + <li> + <gl-sprintf + :message=" + s__( + 'GoogleCloudPlatformService|Replace %{codeStart}your_access_token%{codeEnd} with a %{linkStart}new personal access token%{linkEnd} with the %{strongStart}api%{strongEnd} scope. This token sets up your Google Cloud IAM integration in GitLab.', + ) + " + > + <template #code="{ content }"> + <code><{{ content }}></code> + </template> + <template #link="{ content }"> + <gl-link target="_blank" href="/-/user_settings/personal_access_tokens">{{ + content + }}</gl-link> + </template> + <template #strong="{ content }"> + <strong>{{ content }}</strong> + </template> + </gl-sprintf> + </li> + <li> + <gl-sprintf + :message=" + s__( + 'GoogleCloudPlatformService|Replace %{codeStart}your_google_cloud_project_id%{codeEnd} with your Google Cloud project ID. To improve security, use a dedicated project for identity management, separate from resources and CI/CD projects. %{linkStart}Where’s my project ID?%{linkEnd}', + ) + " + > + <template #code="{ content }"> + <code><{{ content }}></code> + </template> + <template #link="{ content }"> + <gl-link + href="https://cloud.google.com/resource-manager/docs/creating-managing-projects#identifying_projects" + target="_blank" + rel="noopener noreferrer" + >{{ content }} + <gl-icon name="external-link" :aria-label="__('(external link)')" /> + </gl-link> + </template> + </gl-sprintf> + </li> + <li>{{ s__('GoogleCloudPlatformService|You might be prompted to sign in to Google.') }}</li> + </ul> + <div class="position-relative"> + <pre class="gl-w-full">{{ $options.curlLine }}</pre> + <clipboard-button + :text="$options.curlLine" + :title="__('Copy')" + category="tertiary" + class="gl-display-none gl-md-display-flex position-absolute position-top-0 position-right-0 gl-m-3" + /> + </div> + <p> + <gl-sprintf + :message=" + s__( + 'GoogleCloudPlatformService|After Google Cloud workload identity federation has been set up, select %{strongStart}Continue%{strongEnd}.', + ) + " + > + <template #strong="{ content }"> + <strong>{{ content }}</strong> + </template> + </gl-sprintf> + </p> + + <div class="gl-mt-6 gl-display-flex gl-gap-3"> + <gl-button variant="confirm" @click="$emit('show', 'form')">{{ __('Continue') }}</gl-button> + <gl-button @click="$emit('show', 'empty')">{{ __('Cancel') }}</gl-button> + </div> + </div> +</template> diff --git a/ee/app/assets/javascripts/integrations/edit/components/google_cloud_iam/manual_setup.vue b/ee/app/assets/javascripts/integrations/edit/components/google_cloud_iam/manual_setup.vue new file mode 100644 index 0000000000000000000000000000000000000000..7fd0709ffbfdbb7dd8f8fc4ef4f3e9c140562a01 --- /dev/null +++ b/ee/app/assets/javascripts/integrations/edit/components/google_cloud_iam/manual_setup.vue @@ -0,0 +1,88 @@ +<script> +import { GlIcon, GlLink, GlSprintf } from '@gitlab/ui'; +import InviteMembersTrigger from '~/invite_members/components/invite_members_trigger.vue'; +import { STATE_GUIDED } from './constants'; + +export default { + components: { + GlIcon, + GlLink, + GlSprintf, + InviteMembersTrigger, + }, + STATE_GUIDED, +}; +</script> + +<template> + <div> + <h3>{{ s__('GoogleCloudPlatformService|Manual setup') }}</h3> + <p> + <gl-sprintf + :message=" + s__( + 'GoogleCloudPlatformService|%{linkStart}Switch to the guided setup%{linkEnd} if you can manage workload identity federation in Google Cloud. %{link2Start}What are the required permissions?%{link2End}', + ) + " + > + <template #link="{ content }"> + <gl-link @click="$emit('show', $options.STATE_GUIDED)">{{ content }}</gl-link> + </template> + > + <template #link2="{ content }"> + <gl-link + target="_blank" + rel="noopener noreferrer" + href="https://cloud.google.com/iam/docs/manage-workload-identity-pools-providers#required-roles" + > + {{ content }} + <gl-icon name="external-link" :aria-label="__('(external link)')" /> + </gl-link> + </template> + </gl-sprintf> + </p> + <h4>{{ s__('GoogleCloudPlatformService|Instructions') }}</h4> + <ol> + <li> + <gl-sprintf + :message=" + s__( + 'GoogleCloudPlatformService|Share the following information with someone that can manage Google Cloud workload identity federation. Or %{linkStart}invite them%{linkEnd} to set up.', + ) + " + > + <template #link="{ content }"> + <invite-members-trigger + :display-text="content" + class="gl-vertical-align-baseline" + variant="link" + trigger-source="google_cloud_artifact_registry_setup" + /> + </template> + > + </gl-sprintf> + </li> + <ol type="a"> + <li> + {{ s__('GoogleCloudPlatformService|The setup instructions page') }} + </li> + <li> + {{ s__('GoogleCloudPlatformService|Your GitLab project ID') }} + </li> + <li> + {{ + s__('GoogleCloudPlatformService|Your workload identify federation (WLIF) issuer URL.') + }} + </li> + </ol> + <li> + {{ + s__( + 'GoogleCloudPlatformService|After Google Cloud workload identity federation has been set up, enter the details in the following form.', + ) + }} + </li> + </ol> + <hr class="gl-border-gray-100" /> + </div> +</template> diff --git a/ee/app/assets/javascripts/integrations/edit/components/sections/google_cloud_iam.vue b/ee/app/assets/javascripts/integrations/edit/components/sections/google_cloud_iam.vue index 0492f3bea907795c1716800e941da414a2b6d56d..de067fc6ae2e501bf2801e9a606ec607df7bd259 100644 --- a/ee/app/assets/javascripts/integrations/edit/components/sections/google_cloud_iam.vue +++ b/ee/app/assets/javascripts/integrations/edit/components/sections/google_cloud_iam.vue @@ -1,24 +1,71 @@ <script> // eslint-disable-next-line no-restricted-imports import { mapGetters } from 'vuex'; +import { + STATE_EMPTY, + STATE_FORM, + STATE_GUIDED, + STATE_INITIAL, + STATE_MANUAL, +} from '../google_cloud_iam/constants'; +import EmptyState from '../google_cloud_iam/empty_state.vue'; import GcIamForm from '../google_cloud_iam/form.vue'; +import GuidedSetup from '../google_cloud_iam/guided_setup.vue'; +import ManualSetup from '../google_cloud_iam/manual_setup.vue'; export default { name: 'IntegrationSectionGoogleCloudIAM', components: { + EmptyState, GcIamForm, + GuidedSetup, + ManualSetup, + }, + data() { + return { + show: STATE_INITIAL, + }; }, computed: { ...mapGetters(['propsSource']), dynamicFields() { return this.propsSource.fields; }, + isEditable() { + return [STATE_FORM, STATE_MANUAL].includes(this.show); + }, + isFormEmpty() { + return this.propsSource.fields.every((field) => !field.value); + }, + }, + watch: { + isEditable: { + handler(editable) { + this.propsSource.editable = editable; + }, + immediate: true, + }, }, + created() { + this.show = this.isFormEmpty ? 'empty' : 'form'; + }, + methods: { + onShow(type) { + this.show = type; + }, + }, + STATE_EMPTY, + STATE_GUIDED, + STATE_MANUAL, }; </script> <template> - <div> - <gc-iam-form :fields="dynamicFields" /> + <div aria-live="polite"> + <empty-state v-if="show === $options.STATE_EMPTY" @show="onShow" /> + <guided-setup v-else-if="show === $options.STATE_GUIDED" @show="onShow" /> + <manual-setup v-else-if="show === $options.STATE_MANUAL" @show="onShow" /> + + <gc-iam-form v-if="isEditable" :fields="dynamicFields" /> </div> </template> diff --git a/ee/spec/features/groups/integrations/google_cloud_platform/user_activates_workload_identity_federation_spec.rb b/ee/spec/features/groups/integrations/google_cloud_platform/user_activates_workload_identity_federation_spec.rb index 4566df1aefd4c77038b005d82da5bbc7e20b0b2d..6b21207b6d6c6b130cdc9d632492785464831ba6 100644 --- a/ee/spec/features/groups/integrations/google_cloud_platform/user_activates_workload_identity_federation_spec.rb +++ b/ee/spec/features/groups/integrations/google_cloud_platform/user_activates_workload_identity_federation_spec.rb @@ -13,6 +13,7 @@ it 'activates integration', :js do visit_group_integration(integration.title) + click_on s_('GoogleCloudPlatformService|Manual setup') fill_in( s_('GoogleCloudPlatformService|Project ID'), diff --git a/ee/spec/features/projects/integrations/google_cloud_platform/user_activates_workload_identity_federation_spec.rb b/ee/spec/features/projects/integrations/google_cloud_platform/user_activates_workload_identity_federation_spec.rb index 5fb107cdcc9df5a5210378ca7779c5d0bfb85be6..98cc0b5ebb400e847fcb98e12a5fdb54e9162cfa 100644 --- a/ee/spec/features/projects/integrations/google_cloud_platform/user_activates_workload_identity_federation_spec.rb +++ b/ee/spec/features/projects/integrations/google_cloud_platform/user_activates_workload_identity_federation_spec.rb @@ -13,6 +13,7 @@ it 'activates integration', :js do visit_project_integration(integration.title) + click_on s_('GoogleCloudPlatformService|Manual setup') fill_in( s_('GoogleCloudPlatformService|Project ID'), diff --git a/ee/spec/frontend/integrations/edit/components/google_cloud_iam/empty_state_spec.js b/ee/spec/frontend/integrations/edit/components/google_cloud_iam/empty_state_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..7a03fc284ce1c0a2ab2c1c3a0b4db43bbb72d436 --- /dev/null +++ b/ee/spec/frontend/integrations/edit/components/google_cloud_iam/empty_state_spec.js @@ -0,0 +1,102 @@ +import { GlButton, GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui'; +import EmptyAdminAppsSvg from '@gitlab/svgs/dist/illustrations/empty-state/empty-admin-apps-md.svg'; +import { shallowMount } from '@vue/test-utils'; +import { + STATE_GUIDED, + STATE_MANUAL, +} from 'ee/integrations/edit/components/google_cloud_iam/constants'; +import EmptyState from 'ee/integrations/edit/components/google_cloud_iam/empty_state.vue'; +import InviteMembersTrigger from '~/invite_members/components/invite_members_trigger.vue'; + +describe('EmptyState', () => { + let wrapper; + const createComponent = () => { + wrapper = shallowMount(EmptyState, { + stubs: { + GlEmptyState, + GlSprintf, + }, + }); + }; + + const findGlEmptyState = () => wrapper.findComponent(GlEmptyState); + const findDescription = () => wrapper.find('p'); + const findLink = () => wrapper.findComponent(GlLink); + const findInviteMembersTrigger = () => wrapper.findComponent(InviteMembersTrigger); + const findButton = (variant) => + wrapper.findAllComponents(GlButton).filter((button) => button.props('variant') === variant); + + beforeEach(() => { + createComponent(); + }); + + it('renders GlEmptyState', () => { + expect(findGlEmptyState().props()).toMatchObject({ + svgPath: EmptyAdminAppsSvg, + title: 'Connect to Google Cloud', + }); + }); + + it('renders description', () => { + expect(findDescription().text()).toContain( + 'Connect to Google Cloud with workload identity federation. Select Guided setup if you can manage workload identity federation in Google Cloud.', + ); + }); + + it('renders link to Google Cloud documentation', () => { + const link = findLink(); + expect(link.attributes()).toMatchObject({ + href: + 'https://cloud.google.com/iam/docs/manage-workload-identity-pools-providers#required-roles', + rel: 'noopener noreferrer', + target: '_blank', + }); + expect(link.text()).toBe('What are the required permissions?'); + }); + + describe('Guided setup button', () => { + let guidedButton; + + beforeEach(() => { + guidedButton = findButton('confirm').at(0); + }); + + it('renders variant confirm button', () => { + expect(guidedButton.text()).toBe('Guided setup'); + }); + + it('emits `show` event', () => { + expect(wrapper.emitted().show).toBeUndefined(); + + guidedButton.vm.$emit('click'); + + expect(wrapper.emitted().show).toHaveLength(1); + expect(wrapper.emitted().show[0]).toContain(STATE_GUIDED); + }); + }); + + describe('Manual setup button', () => { + let manualButton; + + beforeEach(() => { + manualButton = findButton('default').at(0); + }); + + it('renders variant default button', () => { + expect(manualButton.text()).toBe('Manual setup'); + }); + + it('emits `show` event', () => { + expect(wrapper.emitted().show).toBeUndefined(); + + manualButton.vm.$emit('click'); + + expect(wrapper.emitted().show).toHaveLength(1); + expect(wrapper.emitted().show[0]).toContain(STATE_MANUAL); + }); + }); + + it('renders an InviteMembersTrigger component', () => { + expect(findInviteMembersTrigger().exists()).toBe(true); + }); +}); diff --git a/ee/spec/frontend/integrations/edit/components/google_cloud_iam/form_spec.js b/ee/spec/frontend/integrations/edit/components/google_cloud_iam/form_spec.js index 033dc76e7559622206f0e106b29a976571b61141..ce4ec5bf4ffb758d398147b86f64fed63a87c7ee 100644 --- a/ee/spec/frontend/integrations/edit/components/google_cloud_iam/form_spec.js +++ b/ee/spec/frontend/integrations/edit/components/google_cloud_iam/form_spec.js @@ -1,5 +1,5 @@ import { shallowMount } from '@vue/test-utils'; -import GcIamForm from 'ee_component/integrations/edit/components/google_cloud_iam/form.vue'; +import GcIamForm from 'ee/integrations/edit/components/google_cloud_iam/form.vue'; import Configuration from '~/integrations/edit/components/sections/configuration.vue'; import Connection from '~/integrations/edit/components/sections/connection.vue'; import { createStore } from '~/integrations/edit/store'; diff --git a/ee/spec/frontend/integrations/edit/components/google_cloud_iam/guided_setup_spec.js b/ee/spec/frontend/integrations/edit/components/google_cloud_iam/guided_setup_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..3745af017e1997f3cdd5ce629867c7bff9fbc176 --- /dev/null +++ b/ee/spec/frontend/integrations/edit/components/google_cloud_iam/guided_setup_spec.js @@ -0,0 +1,82 @@ +import { GlButton, GlLink, GlSprintf } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import { STATE_MANUAL } from 'ee/integrations/edit/components/google_cloud_iam/constants'; +import GuidedSetup from 'ee/integrations/edit/components/google_cloud_iam/guided_setup.vue'; + +describe('GuidedSetup', () => { + let wrapper; + const createComponent = () => { + wrapper = shallowMount(GuidedSetup, { stubs: { GlSprintf } }); + }; + + const findFirstLink = () => wrapper.findAllComponents(GlLink).at(0); + const findButton = (variant) => + wrapper.findAllComponents(GlButton).filter((button) => button.props('variant') === variant); + + beforeEach(() => { + createComponent(); + }); + + describe('Switch to manual setup link', () => { + let switchLink; + + beforeEach(() => { + switchLink = findFirstLink(); + }); + + it('renders link', () => { + expect(switchLink.text()).toBe('Switch to the manual setup'); + }); + + it('emits `show` event', () => { + expect(wrapper.emitted().show).toBeUndefined(); + + switchLink.vm.$emit('click'); + + expect(wrapper.emitted().show).toHaveLength(1); + expect(wrapper.emitted().show[0]).toContain(STATE_MANUAL); + }); + }); + + describe('Continue button', () => { + let continueButton; + + beforeEach(() => { + continueButton = findButton('confirm').at(0); + }); + + it('renders variant confirm button', () => { + expect(continueButton.text()).toBe('Continue'); + }); + + it('emits `show` event', () => { + expect(wrapper.emitted().show).toBeUndefined(); + + continueButton.vm.$emit('click'); + + expect(wrapper.emitted().show).toHaveLength(1); + expect(wrapper.emitted().show[0]).toContain('form'); + }); + }); + + describe('Cancel button', () => { + let cancelButton; + + beforeEach(() => { + cancelButton = findButton('default').at(0); + }); + + it('renders variant confirm button', () => { + expect(cancelButton.text()).toBe('Cancel'); + }); + + it('emits `show` event', () => { + expect(wrapper.emitted().show).toBeUndefined(); + + cancelButton.vm.$emit('click'); + + expect(wrapper.emitted().show).toHaveLength(1); + expect(wrapper.emitted().show[0]).toContain('empty'); + }); + }); +}); diff --git a/ee/spec/frontend/integrations/edit/components/google_cloud_iam/manual_setup_spec.js b/ee/spec/frontend/integrations/edit/components/google_cloud_iam/manual_setup_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..7aace69f03d84ad33a3f9b1598a87f79a0420eda --- /dev/null +++ b/ee/spec/frontend/integrations/edit/components/google_cloud_iam/manual_setup_spec.js @@ -0,0 +1,38 @@ +import { GlLink, GlSprintf } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import { STATE_GUIDED } from 'ee/integrations/edit/components/google_cloud_iam/constants'; +import ManualSetup from 'ee/integrations/edit/components/google_cloud_iam/manual_setup.vue'; + +describe('ManualSetup', () => { + let wrapper; + const createComponent = () => { + wrapper = shallowMount(ManualSetup, { stubs: { GlSprintf } }); + }; + + const findFirstLink = () => wrapper.findAllComponents(GlLink).at(0); + + beforeEach(() => { + createComponent(); + }); + + describe('Switch to guided setup link', () => { + let switchLink; + + beforeEach(() => { + switchLink = findFirstLink(); + }); + + it('renders link', () => { + expect(switchLink.text()).toBe('Switch to the guided setup'); + }); + + it('emits `show` event', () => { + expect(wrapper.emitted().show).toBeUndefined(); + + switchLink.vm.$emit('click'); + + expect(wrapper.emitted().show).toHaveLength(1); + expect(wrapper.emitted().show[0]).toContain(STATE_GUIDED); + }); + }); +}); diff --git a/ee/spec/frontend/integrations/edit/components/sections/google_cloud_iam_spec.js b/ee/spec/frontend/integrations/edit/components/sections/google_cloud_iam_spec.js index f937481068d4856a3d0da3cd3823f7327bf86c5a..648a3ee7d8577a5ee3f10ad4c0eaffbbfd5ae774 100644 --- a/ee/spec/frontend/integrations/edit/components/sections/google_cloud_iam_spec.js +++ b/ee/spec/frontend/integrations/edit/components/sections/google_cloud_iam_spec.js @@ -1,15 +1,24 @@ +import { nextTick } from 'vue'; import { shallowMount } from '@vue/test-utils'; import IntegrationSectionGoogleCloudIAM from 'ee_component/integrations/edit/components/sections/google_cloud_iam.vue'; -import GcIamForm from 'ee_component/integrations/edit/components/google_cloud_iam/form.vue'; +import { + STATE_EMPTY, + STATE_GUIDED, + STATE_MANUAL, +} from 'ee/integrations/edit/components/google_cloud_iam/constants'; +import EmptyState from 'ee/integrations/edit/components/google_cloud_iam/empty_state.vue'; +import GcIamForm from 'ee/integrations/edit/components/google_cloud_iam/form.vue'; +import GuidedSetup from 'ee/integrations/edit/components/google_cloud_iam/guided_setup.vue'; +import ManualSetup from 'ee/integrations/edit/components/google_cloud_iam/manual_setup.vue'; import { createStore } from '~/integrations/edit/store'; describe('IntegrationSectionGoogleCloudIAM', () => { let wrapper; - const createComponent = () => { + const createComponent = ({ fields = [] } = {}) => { const store = createStore({ customState: { - fields: [], + fields, }, }); @@ -18,11 +27,61 @@ describe('IntegrationSectionGoogleCloudIAM', () => { }); }; + const findEmptyState = () => wrapper.findComponent(EmptyState); const findGcIamForm = () => wrapper.findComponent(GcIamForm); + const findGuidedSetup = () => wrapper.findComponent(GuidedSetup); + const findManualSetup = () => wrapper.findComponent(ManualSetup); - it('renders Google Cloud IAM form', () => { - createComponent(); + describe('when Google Cloud IAM form is empty', () => { + it('renders the empty state', () => { + createComponent(); - expect(findGcIamForm().exists()).toBe(true); + expect(findEmptyState().exists()).toBe(true); + expect(findGcIamForm().exists()).toBe(false); + }); + }); + + describe('when Google Cloud IAM form is not empty', () => { + it('renders the Google Cloud IAM form', () => { + createComponent({ fields: [{ value: '' }, { value: '1' }] }); + + expect(findEmptyState().exists()).toBe(false); + expect(findGcIamForm().exists()).toBe(true); + }); + }); + + describe('when `show` events are emitted', () => { + it.each` + initialState | event | componentEmitting | hasEmptyState | hasGuidedSetup | hasManualSetup | hasGcIamForm + ${STATE_EMPTY} | ${STATE_GUIDED} | ${findEmptyState} | ${false} | ${true} | ${false} | ${false} + ${STATE_EMPTY} | ${STATE_MANUAL} | ${findEmptyState} | ${false} | ${false} | ${true} | ${true} + ${STATE_GUIDED} | ${STATE_MANUAL} | ${findGuidedSetup} | ${false} | ${false} | ${true} | ${true} + ${STATE_MANUAL} | ${STATE_GUIDED} | ${findManualSetup} | ${false} | ${true} | ${false} | ${false} + `( + "render correct components for the '$event' event", + async ({ + initialState, + event, + componentEmitting, + hasEmptyState, + hasGuidedSetup, + hasManualSetup, + hasGcIamForm, + }) => { + createComponent(); + + // Initial state + findEmptyState().vm.$emit('show', initialState); + await nextTick(); + + componentEmitting().vm.$emit('show', event); + await nextTick(); + + expect(findEmptyState().exists()).toBe(hasEmptyState); + expect(findGuidedSetup().exists()).toBe(hasGuidedSetup); + expect(findManualSetup().exists()).toBe(hasManualSetup); + expect(findGcIamForm().exists()).toBe(hasGcIamForm); + }, + ); }); }); diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 5b5783df36c5d8dc65afaf1a65ad24dc21520f5b..8ddbfd173f1fc09a77deb3171cdaff869bea70ce 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -23619,12 +23619,33 @@ msgstr "" msgid "GoogleArtifactRegistry|Your Google Cloud project must have specific Identity and Access Management (IAM) policies to use the Artifact Registry repository in this GitLab project." msgstr "" +msgid "GoogleCloudPlatformService|%{linkStart}Switch to the guided setup%{linkEnd} if you can manage workload identity federation in Google Cloud. %{link2Start}What are the required permissions?%{link2End}" +msgstr "" + +msgid "GoogleCloudPlatformService|%{linkStart}Switch to the manual setup%{linkEnd} if you cannot manage workload identity federation in Google Cloud. %{link2Start}What are the required permissions?%{link2End}" +msgstr "" + msgid "GoogleCloudPlatformService|%{link_start}Explore Google Cloud integration with GitLab%{link_end}, for CI/CD and more." msgstr "" +msgid "GoogleCloudPlatformService|After Google Cloud workload identity federation has been set up, enter the details in the following form." +msgstr "" + +msgid "GoogleCloudPlatformService|After Google Cloud workload identity federation has been set up, select %{strongStart}Continue%{strongEnd}." +msgstr "" + +msgid "GoogleCloudPlatformService|Before you begin, %{linkStart}install the Google Cloud CLI%{linkEnd}." +msgstr "" + +msgid "GoogleCloudPlatformService|Connect to Google Cloud" +msgstr "" + msgid "GoogleCloudPlatformService|Connect to Google Cloud with workload identity federation for secure access without accounts or keys." msgstr "" +msgid "GoogleCloudPlatformService|Connect to Google Cloud with workload identity federation. Select %{strongStart}Guided setup%{strongEnd} if you can manage workload identity federation in Google Cloud. %{linkStart}What are the required permissions?%{linkEnd}" +msgstr "" + msgid "GoogleCloudPlatformService|Example: %{code_open}314053285323%{code_close}" msgstr "" @@ -23649,6 +23670,9 @@ msgstr "" msgid "GoogleCloudPlatformService|Google Cloud project number for the Workload Identity Federation." msgstr "" +msgid "GoogleCloudPlatformService|Guided setup" +msgstr "" + msgid "GoogleCloudPlatformService|ID of the Workload Identity Pool provider." msgstr "" @@ -23658,6 +23682,12 @@ msgstr "" msgid "GoogleCloudPlatformService|Identify the Google Cloud project with the workload identity pool and provider. %{linkStart}Where are my project ID and project number?%{linkEnd}" msgstr "" +msgid "GoogleCloudPlatformService|Instructions" +msgstr "" + +msgid "GoogleCloudPlatformService|Invite member to set up" +msgstr "" + msgid "GoogleCloudPlatformService|Manage permissions for Google Cloud resources with Identity and Access Management (IAM)." msgstr "" @@ -23667,6 +23697,9 @@ msgstr "" msgid "GoogleCloudPlatformService|Manage your artifacts in Google Artifact Registry." msgstr "" +msgid "GoogleCloudPlatformService|Manual setup" +msgstr "" + msgid "GoogleCloudPlatformService|Pool ID" msgstr "" @@ -23682,6 +23715,12 @@ msgstr "" msgid "GoogleCloudPlatformService|Provider ID" msgstr "" +msgid "GoogleCloudPlatformService|Replace %{codeStart}your_access_token%{codeEnd} with a %{linkStart}new personal access token%{linkEnd} with the %{strongStart}api%{strongEnd} scope. This token sets up your Google Cloud IAM integration in GitLab." +msgstr "" + +msgid "GoogleCloudPlatformService|Replace %{codeStart}your_google_cloud_project_id%{codeEnd} with your Google Cloud project ID. To improve security, use a dedicated project for identity management, separate from resources and CI/CD projects. %{linkStart}Where’s my project ID?%{linkEnd}" +msgstr "" + msgid "GoogleCloudPlatformService|Repository location" msgstr "" @@ -23691,6 +23730,12 @@ msgstr "" msgid "GoogleCloudPlatformService|Repository name" msgstr "" +msgid "GoogleCloudPlatformService|Run the following command to set up and connect to your Google Cloud project with workload identity federation." +msgstr "" + +msgid "GoogleCloudPlatformService|Share the following information with someone that can manage Google Cloud workload identity federation. Or %{linkStart}invite them%{linkEnd} to set up." +msgstr "" + msgid "GoogleCloudPlatformService|The Google Cloud Identity and Access Management integration is not configured for this project" msgstr "" @@ -23700,12 +23745,24 @@ msgstr "" msgid "GoogleCloudPlatformService|The google_cloud_support feature is not available" msgstr "" +msgid "GoogleCloudPlatformService|The setup instructions page" +msgstr "" + msgid "GoogleCloudPlatformService|To improve security, use a dedicated project for resources, separate from CI/CD and identity management projects. %{link_start}Where’s my project ID? %{icon}%{link_end}" msgstr "" msgid "GoogleCloudPlatformService|Workload identity federation" msgstr "" +msgid "GoogleCloudPlatformService|You might be prompted to sign in to Google." +msgstr "" + +msgid "GoogleCloudPlatformService|Your GitLab project ID" +msgstr "" + +msgid "GoogleCloudPlatformService|Your workload identify federation (WLIF) issuer URL." +msgstr "" + msgid "GoogleCloud|Cancel" msgstr ""