Skip to content

Commit

Permalink
feat: disable new apps if no auth strategies and display banner (#383)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidma415 authored Feb 1, 2024
1 parent 79eba77 commit f8672f8
Show file tree
Hide file tree
Showing 14 changed files with 166 additions and 10 deletions.
59 changes: 59 additions & 0 deletions cypress/e2e/specs/application_registration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,65 @@ describe('Application Registration', () => {

cy.get('[data-testid="reference-id-input"]').should('not.have.value', '')
})

it('appregv2 - create application form shows banner if no auth strategies and flag enabled', () => {
cy.mockLaunchDarklyFlags([
{
name: 'tdx-3531-app-reg-v2',
value: true
}
])
cy.mockApplicationAuthStrategies([], 0)
cy.visit('/application/create')

cy.get('[data-testid="application-name-input"]').type(apps[0].name, { delay: 0 })
cy.get('#description').type(apps[0].description, { delay: 0 })
cy.get('[data-testid="reference-id-input"]').type(apps[0].reference_id, { delay: 0 })
cy.get(submitButton).should('be.disabled')
cy.get('[data-testid="no-auth-strategies-warning"]').should('be.visible')
})

it('appregv2 - create application form does not show banner if flag disabled', () => {
cy.mockLaunchDarklyFlags([
{
name: 'tdx-3531-app-reg-v2',
value: false
}
])
cy.mockApplicationAuthStrategies([], 0)
cy.visit('/application/create')

cy.get('[data-testid="no-auth-strategies-warning"]').should('not.exist')
})
it('appregv2 - does not show warning banner if flag is not on', () => {
cy.mockLaunchDarklyFlags([
{
name: 'tdx-3531-app-reg-v2',
value: false
}
])
cy.mockApplications([], 0)
cy.mockApplicationAuthStrategies([], 0)
cy.visit('/my-apps')

cy.get('[data-testid="create-application-button"]').should('not.be.disabled')
cy.get('[data-testid="no-auth-strategies-warning"]').should('not.exist')
})

it('appregv2 - shows warning banner if no available auth strategies', () => {
cy.mockLaunchDarklyFlags([
{
name: 'tdx-3531-app-reg-v2',
value: true
}
])
cy.mockApplications([], 0)
cy.mockApplicationAuthStrategies([], 0)
cy.visit('/my-apps')

cy.get('[data-testid="create-application-button"]').should('have.attr', 'disabled', 'disabled')
cy.get('[data-testid="no-auth-strategies-warning"]').should('be.visible')
})
})

it('can return to My Apps from application details via breadcrumb', () => {
Expand Down
3 changes: 2 additions & 1 deletion cypress/e2e/support/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable no-console */
// Import commands.js using ES2015 syntax:
import { GetApplicationResponse, GetRegistrationResponse, ListCredentialsResponseDataInner, PortalAppearance, PortalContext, Product, ProductCatalogIndexSource, ProductVersion, ProductVersionSpecOperationsOperationsInner } from '@kong/sdk-portal-js'
import { GetApplicationResponse, GetRegistrationResponse, ListAuthStrategiesItem, ListCredentialsResponseDataInner, PortalAppearance, PortalContext, Product, ProductCatalogIndexSource, ProductVersion, ProductVersionSpecOperationsOperationsInner } from '@kong/sdk-portal-js'
import './mock-commands'
import { SinonStub } from 'cypress/types/sinon'

Expand All @@ -26,6 +26,7 @@ declare global {
mockProduct(productId?: string, mockProduct?: Product, mockVersions?: ProductVersion[]): Chainable<JQuery<HTMLElement>>
mockProductVersion(productId?: string, versionId?: string, mockVersion?: ProductVersion): Chainable<JQuery<HTMLElement>>
mockApplications(searchResults?: Array<GetApplicationResponse>, totalCount?: number, pageSize?: number, pageNumber?: number): Chainable<JQuery<HTMLElement>>
mockApplicationAuthStrategies(authStrategyItems?: Array<ListAuthStrategiesItem>, totalCount?: number, pageSize?: number, pageNumber?: number): Chainable<JQuery<HTMLElement>>
mockApplicationWithCredAndReg(data: GetApplicationResponse, credentials?: ListCredentialsResponseDataInner[], registrations?: Array<GetRegistrationResponse>): Chainable<JQuery<HTMLElement>>,
mockContextualAnalytics(): Chainable<JQuery<HTMLElement>>
mockRegistrations(applicationId?: string, registrations?: Array<GetRegistrationResponse>, totalCount?: number): Chainable<JQuery<HTMLElement>>
Expand Down
19 changes: 19 additions & 0 deletions cypress/e2e/support/mock-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import petstoreOperationsV2 from '../fixtures/v2/petstoreOperations.json'
import {
GetApplicationResponse,
ListApplicationsResponse,
ListAuthStrategiesResponse,
ListCredentialsResponse,
ListDocumentsTree,
ListRegistrationsResponse,
Expand Down Expand Up @@ -333,6 +334,24 @@ Cypress.Commands.add('mockApplications', (applications, totalCount, pageSize = 1
}).as('getApplications')
})

Cypress.Commands.add('mockApplicationAuthStrategies', (applicationAuthStrategies, totalCount, pageSize = 1, pageNumber = 10) => {
const responseBody: ListAuthStrategiesResponse = {
data: applicationAuthStrategies,
meta: {
page: {
total: totalCount,
number: pageNumber,
size: pageSize

}
}
}

return cy.intercept('GET', '**/api/v2/applications/auth-strategies*', {
body: responseBody
}).as('getApplicationAuthStrategies')
})

Cypress.Commands.add('mockRegistrations', (applicationId = '*', registrations = [], pageNumber = 1, pageSize = 10, totalCount = 0) => {
return cy.intercept('GET', `**/api/v2/applications/${applicationId}/registrations*`, {
body: {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"@kong-ui-public/spec-renderer": "0.13.5",
"@kong/kong-auth-elements": "2.11.1",
"@kong/kongponents": "8.127.0",
"@kong/sdk-portal-js": "2.5.0",
"@kong/sdk-portal-js": "2.6.1",
"@xstate/vue": "2.0.0",
"axios": "1.6.0",
"date-fns": "3.3.0",
Expand Down
3 changes: 2 additions & 1 deletion src/constants/feature-flags.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export enum FeatureFlags {
DeveloperManagedScopes = 'tdx-3460-developer-managed-scopes'
DeveloperManagedScopes = 'tdx-3460-developer-managed-scopes',
AppRegV2 = 'tdx-3531-app-reg-v2'
}
2 changes: 2 additions & 0 deletions src/locales/ca_ES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export const ca_ES: I18nType = {
delete: 'Eliminar',
proceed: 'Continuar',
applicationName: "Nom de l'aplicació ",
authStrategyWarning: translationNeeded(en.application.authStrategyWarning),
clientID: 'ID de client: ',
clientSecret: 'Clau secreta de client: ',
reqField: ' indica un camp obligatori',
Expand Down Expand Up @@ -285,6 +286,7 @@ export const ca_ES: I18nType = {
logoAlt: 'logotip'
},
myApp: {
authStrategyWarning: translationNeeded(en.application.authStrategyWarning),
newApp: 'Nova aplicació',
plus: 'Més',
myApps: 'Les meves aplicacions',
Expand Down
2 changes: 2 additions & 0 deletions src/locales/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export const de: I18nType = {
delete: 'Löschen',
proceed: 'Weiter',
applicationName: 'Name der Applikation',
authStrategyWarning: translationNeeded(en.application.authStrategyWarning),
clientID: translationNeeded(en.application.clientID),
clientSecret: translationNeeded(en.application.clientSecret),
reqField: ' Pflichtfeld',
Expand Down Expand Up @@ -285,6 +286,7 @@ export const de: I18nType = {
logoAlt: 'Logo'
},
myApp: {
authStrategyWarning: translationNeeded(en.application.authStrategyWarning),
newApp: 'Neue Applikation',
plus: 'Plus',
myApps: 'Meine Applikationen',
Expand Down
2 changes: 2 additions & 0 deletions src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export const en = {
delete: 'Delete',
proceed: 'Proceed',
applicationName: 'Application Name ',
authStrategyWarning: 'You cannot create an application as this developer portal has no available application auth strategies. Please contact a developer portal admin.',
clientID: 'Client ID: ',
clientSecret: 'Client Secret: ',
reqField: ' indicates required field',
Expand Down Expand Up @@ -281,6 +282,7 @@ export const en = {
logoAlt: 'logo'
},
myApp: {
authStrategyWarning: 'You cannot create an application as this developer portal has no available application auth strategies. Please contact a developer portal admin.',
newApp: 'New App',
plus: 'Plus',
myApps: 'My Apps',
Expand Down
2 changes: 2 additions & 0 deletions src/locales/es_ES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export const es_ES: I18nType = {
delete: 'Eliminar',
proceed: 'Continuar',
applicationName: 'Nombre de la aplicación ',
authStrategyWarning: translationNeeded(en.application.authStrategyWarning),
clientID: 'ID de cliente: ',
clientSecret: 'Clave secreta de cliente: ',
reqField: ' indica campo obligatorio',
Expand Down Expand Up @@ -285,6 +286,7 @@ export const es_ES: I18nType = {
logoAlt: 'logo'
},
myApp: {
authStrategyWarning: translationNeeded(en.application.authStrategyWarning),
newApp: 'Nueva aplicación',
plus: 'Plus',
myApps: 'Mis aplicaciones',
Expand Down
2 changes: 2 additions & 0 deletions src/locales/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export const fr: I18nType = {
delete: 'Supprimer',
proceed: 'Continuer',
applicationName: 'Nom de l\'application ',
authStrategyWarning: translationNeeded(en.application.authStrategyWarning),
clientID: 'Client ID : ',
clientSecret: 'Client Secret : ',
reqField: ' indique un champ obligatoire',
Expand Down Expand Up @@ -285,6 +286,7 @@ export const fr: I18nType = {
logoAlt: 'logo'
},
myApp: {
authStrategyWarning: translationNeeded(en.application.authStrategyWarning),
newApp: 'Nouvelle application',
plus: 'Plus',
myApps: 'Mes applications',
Expand Down
2 changes: 2 additions & 0 deletions src/locales/i18n-type.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export interface I18nType {
delete: string;
proceed: string;
applicationName: string;
authStrategyWarning: string;
clientID: string;
clientSecret: string;
reqField: string;
Expand Down Expand Up @@ -281,6 +282,7 @@ export interface I18nType {
logoAlt: string;
};
myApp: {
authStrategyWarning: string;
newApp: string;
plus: string;
myApps: string;
Expand Down
34 changes: 33 additions & 1 deletion src/views/Applications/ApplicationForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@
<p class="text-sm mb-5">
<span class="text-danger">*</span> {{ helpText.application.reqField }}
</p>
<KAlert
v-if="appRegV2Enabled && !hasAppAuthStrategies && formMode === 'create'"
:alert-message="helpText.application.authStrategyWarning"
appearance="warning"
class="no-auth-strategies-warning"
data-testid="no-auth-strategies-warning"
/>
<form @submit.prevent="formMethod">
<div class="mb-5">
<KLabel for="applicationName">
Expand Down Expand Up @@ -207,6 +214,8 @@ import cleanupEmptyFields from '@/helpers/cleanupEmptyFields'
import useToaster from '@/composables/useToaster'
import { useI18nStore, useAppStore } from '@/stores'
import { CreateApplicationPayload, UpdateApplicationPayload } from '@kong/sdk-portal-js'
import { FeatureFlags } from '@/constants/feature-flags'
import useLDFeatureFlag from '@/hooks/useLDFeatureFlag'
export default defineComponent({
name: 'ApplicationForm',
Expand Down Expand Up @@ -238,6 +247,8 @@ export default defineComponent({
const applicationId = ref('')
const applicationName = ref('')
const secretModalIsVisible = ref(false)
const appRegV2Enabled = useLDFeatureFlag(FeatureFlags.AppRegV2, false)
const hasAppAuthStrategies = ref(false)
const defaultFormData: UpdateApplicationPayload = makeDefaultFormData(isDcr.value)
const formData = ref(defaultFormData)
Expand Down Expand Up @@ -274,6 +285,7 @@ export default defineComponent({
() =>
!currentState.value.matches('pending') &&
formData.value.name.length &&
(appRegV2Enabled && formMode.value !== 'edit' ? hasAppAuthStrategies.value : true) &&
(isDcr.value ? true : formData.value.reference_id?.length)
)
const modalTitle = computed(() => `Delete ${formData.value?.name}`)
Expand All @@ -291,10 +303,24 @@ export default defineComponent({
const { portalApiV2 } = usePortalApi()
onMounted(() => {
onMounted(async () => {
if (id.value) {
fetchApplication()
}
if (appRegV2Enabled) {
try {
const appAuthStrategies = await portalApiV2.value.service.applicationsApi.listApplicationAuthStrategies()
if (appAuthStrategies.data?.data?.length) {
hasAppAuthStrategies.value = true
}
} catch (err) {
notify({
appearance: 'danger',
message: `Error fetching application auth strategies: ${err}`
})
}
}
})
const copyTokenToClipboard = (executeCopy, copyItem) => {
Expand Down Expand Up @@ -459,6 +485,8 @@ export default defineComponent({
copyTokenToClipboard,
secretModalIsVisible,
handleAcknowledgeSecret,
hasAppAuthStrategies,
appRegV2Enabled,
send,
buttonText,
formMode,
Expand Down Expand Up @@ -490,4 +518,8 @@ export default defineComponent({
top: 4px;
margin-left: 16px;
}
.no-auth-strategies-warning {
margin-bottom: 8px;
}
</style>
36 changes: 34 additions & 2 deletions src/views/MyApps.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<KButton
data-testid="create-application-button"
appearance="primary"
:disabled="appRegV2Enabled && !hasAppAuthStrategies"
:is-rounded="false"
:to="{ name: 'create-application' }"
>
Expand All @@ -31,6 +32,13 @@
</KButton>
</template>
</PageTitle>
<KAlert
v-if="appRegV2Enabled && !hasAppAuthStrategies"
:alert-message="helpText.authStrategyWarning"
appearance="warning"
class="no-auth-strategies-warning"
data-testid="no-auth-strategies-warning"
/>
<div
v-if="!vitalsLoading && myAppsReady"
>
Expand Down Expand Up @@ -205,6 +213,8 @@ import '@kong-ui-public/analytics-metric-provider/dist/style.css'
import { EXPLORE_V2_DIMENSIONS, EXPLORE_V2_FILTER_TYPES, MetricsConsumer } from '@kong-ui-public/analytics-metric-provider'
import { storeToRefs } from 'pinia'
import { FeatureFlags } from '@/constants/feature-flags'
import useLDFeatureFlag from '@/hooks/useLDFeatureFlag'
export default defineComponent({
name: 'MyApps',
Expand All @@ -221,6 +231,8 @@ export default defineComponent({
const showSecretModal = ref(false)
const token = ref(null)
const { portalApiV2 } = usePortalApi()
const appRegV2Enabled = useLDFeatureFlag(FeatureFlags.AppRegV2, false)
const hasAppAuthStrategies = ref(false)
const appStore = useAppStore()
const { isDcr } = storeToRefs(appStore)
Expand Down Expand Up @@ -352,8 +364,22 @@ export default defineComponent({
]
}))
onMounted(() => {
onMounted(async () => {
vitalsLoading.value = false
if (appRegV2Enabled) {
try {
const appAuthStrategies = await portalApiV2.value.service.applicationsApi.listApplicationAuthStrategies()
if (appAuthStrategies.data?.data?.length) {
hasAppAuthStrategies.value = true
}
} catch (err) {
notify({
appearance: 'danger',
message: `Error fetching application auth strategies: ${err}`
})
}
}
})
return {
Expand All @@ -366,6 +392,8 @@ export default defineComponent({
isDcr,
deleteItem,
showSecretModal,
appRegV2Enabled,
hasAppAuthStrategies,
token,
onModalClose,
handleRefreshSecret,
Expand All @@ -384,9 +412,13 @@ export default defineComponent({
})
</script>

<style lang="scss">
<style lang="scss" scoped>
.delete-modal, .refresh-secret-modal {
--KModalHeaderColor: var(--text_colors-headings);
--KModalColor: var(--text_colors-primary);
}
.no-auth-strategies-warning {
margin-bottom: 8px;
}
</style>
Loading

0 comments on commit f8672f8

Please sign in to comment.