From bbe5a07d3ac2d6fd1a8f87f8b9a7c21124f7dd02 Mon Sep 17 00:00:00 2001 From: Manjiri Tapaswi Date: Wed, 29 Nov 2023 06:53:40 -0800 Subject: [PATCH] feat(*): add support to EntityBaseTable to use action button outside [KHCP-9618] (#913) * feat(*): add support to EntityBaseTable to use action button outside * fix(*): apply pr feedback and add props to a poc entity * fix(*): fix conditional logic * fix(*): apply pr feedback * fix(*): apply pr feedback * fix(*): use static id for consuming app * fix(*): use Teleport in routes and plugins * feat(*): add useActionOutside prop and Teleport to all entities * feat(*): add useActionOutside prop and Teleport to ConsumerCredential * feat(*): add tooltip to each entity create new button * fix(*): fix indentation * fix(*): remove tooltip from create new entities button * fix(*): hide teleported create button in empty state for all entities * fix(*): avoid jump of cta button for all entities * fix(*): show teleported create button by default for all entities * fix(*): use data length instead as fetcherState doesnt update --- .../src/components/CACertificateList.vue | 40 +++++++++++++----- .../src/components/CertificateList.vue | 40 +++++++++++++----- .../src/components/ConsumerCredentialList.vue | 41 +++++++++++++----- .../src/components/ConsumerGroupList.vue | 42 ++++++++++++++----- .../src/components/ConsumerList.vue | 42 ++++++++++++++----- .../src/components/GatewayServiceList.vue | 39 +++++++++++++---- .../src/components/KeySetList.vue | 40 +++++++++++++----- .../entities-keys/src/components/KeyList.vue | 40 +++++++++++++----- .../src/components/PluginList.vue | 40 +++++++++++++----- .../src/components/RouteList.vue | 40 +++++++++++++----- .../entities-snis/src/components/SniList.vue | 40 +++++++++++++----- .../src/components/TargetsList.vue | 42 ++++++++++++++----- .../src/components/UpstreamsList.vue | 40 +++++++++++++----- .../src/components/VaultList.vue | 40 +++++++++++++----- 14 files changed, 424 insertions(+), 142 deletions(-) diff --git a/packages/entities/entities-certificates/src/components/CACertificateList.vue b/packages/entities/entities-certificates/src/components/CACertificateList.vue index 73305d2dd7..760837a2fe 100644 --- a/packages/entities/entities-certificates/src/components/CACertificateList.vue +++ b/packages/entities/entities-certificates/src/components/CACertificateList.vue @@ -13,6 +13,7 @@ preferences-storage-key="kong-ui-entities-ca-certificates-list" :query="filterQuery" :table-headers="tableHeaders" + :use-action-outside="useActionOutside" @clear-search-input="clearFilter" @click:row="(row: any) => rowClick(row as EntityRow)" @sort="resetPagination" @@ -26,16 +27,23 @@ @@ -215,6 +223,11 @@ const props = defineProps({ required: false, default: async () => true, }, + /** default to false, setting to true will teleport the toolbar button to the destination in the consuming app */ + useActionOutside: { + type: Boolean, + default: false, + }, }) const { i18n: { t, formatUnixTimeStamp }, i18nT } = composables.useI18n() @@ -415,10 +428,17 @@ const confirmDelete = async (): Promise => { } } +const hasData = ref(true) + /** * Watchers */ watch(fetcherState, (state) => { + // if table is empty, hide the teleported Create button + if (state?.response?.data?.length === 0) { + hasData.value = false + } + if (state.status === FetcherStatus.Error) { errorMessage.value = { title: t('ca-certificates.errors.general'), diff --git a/packages/entities/entities-certificates/src/components/CertificateList.vue b/packages/entities/entities-certificates/src/components/CertificateList.vue index c214f79e93..a754edbf21 100644 --- a/packages/entities/entities-certificates/src/components/CertificateList.vue +++ b/packages/entities/entities-certificates/src/components/CertificateList.vue @@ -13,6 +13,7 @@ preferences-storage-key="kong-ui-entities-certificates-list" :query="filterQuery" :table-headers="tableHeaders" + :use-action-outside="useActionOutside" @clear-search-input="clearFilter" @click:row="(row: any) => rowClick(row as EntityRow)" @sort="resetPagination" @@ -26,16 +27,23 @@ @@ -249,6 +257,11 @@ const props = defineProps({ required: false, default: async () => true, }, + /** default to false, setting to true will teleport the toolbar button to the destination in the consuming app */ + useActionOutside: { + type: Boolean, + default: false, + }, }) const { i18n: { t, formatUnixTimeStamp }, i18nT } = composables.useI18n() @@ -482,10 +495,17 @@ const confirmDelete = async (): Promise => { } } +const hasData = ref(true) + /** * Watchers */ watch(fetcherState, (state) => { + // if table is empty, hide the teleported Create button + if (state?.response?.data?.length === 0) { + hasData.value = false + } + if (state.status === FetcherStatus.Error) { errorMessage.value = { title: t('certificates.errors.general'), diff --git a/packages/entities/entities-consumer-credentials/src/components/ConsumerCredentialList.vue b/packages/entities/entities-consumer-credentials/src/components/ConsumerCredentialList.vue index 754e864c9e..41c2a676c3 100644 --- a/packages/entities/entities-consumer-credentials/src/components/ConsumerCredentialList.vue +++ b/packages/entities/entities-consumer-credentials/src/components/ConsumerCredentialList.vue @@ -13,20 +13,28 @@ pagination-type="offset" preferences-storage-key="kong-ui-entities-consumer-credentials-list" :table-headers="tableHeaders" + :use-action-outside="useActionOutside" @sort="resetPagination" > @@ -251,6 +259,11 @@ const props = defineProps({ required: false, default: async () => true, }, + /** default to false, setting to true will teleport the toolbar button to the destination in the consuming app */ + useActionOutside: { + type: Boolean, + default: false, + }, }) const { i18n: { t, formatUnixTimeStamp } } = composables.useI18n() @@ -443,10 +456,18 @@ const confirmDelete = async (): Promise => { } } +// Remount the table when hasData changes +const hasData = ref(true) + /** * Watchers */ watch(fetcherState, (state) => { + // if table is empty, hide the teleported Create button + if (state?.response?.data?.length === 0) { + hasData.value = false + } + if (state.status === FetcherStatus.Error) { errorMessage.value = { title: t('credentials.error.general'), diff --git a/packages/entities/entities-consumer-groups/src/components/ConsumerGroupList.vue b/packages/entities/entities-consumer-groups/src/components/ConsumerGroupList.vue index 035951c2c3..3ff125b87b 100644 --- a/packages/entities/entities-consumer-groups/src/components/ConsumerGroupList.vue +++ b/packages/entities/entities-consumer-groups/src/components/ConsumerGroupList.vue @@ -15,6 +15,7 @@ :query="filterQuery" :row-attributes="rowAttributes" :table-headers="tableHeaders" + :use-action-outside="useActionOutside" @clear-search-input="clearFilter" @click:row="(row: any) => rowClick(row as EntityRow)" @empty-state-cta-clicked="handleEmptyStateCtaClicked" @@ -30,17 +31,24 @@ @@ -252,6 +260,11 @@ const props = defineProps({ required: false, default: async () => true, }, + /** default to false, setting to true will teleport the toolbar button to the destination in the consuming app */ + useActionOutside: { + type: Boolean, + default: false, + }, }) const { i18nT, i18n: { t } } = composables.useI18n() @@ -543,10 +556,17 @@ const exitGroups = async (): Promise => { } } +const hasData = ref(true) + /** * Watchers */ watch(fetcherState, (state) => { + // if table is empty, hide the teleported Create button + if (state?.response?.data?.length === 0) { + hasData.value = false + } + if (state.status === FetcherStatus.Error) { errorMessage.value = { title: t('consumer_groups.errors.general'), diff --git a/packages/entities/entities-consumers/src/components/ConsumerList.vue b/packages/entities/entities-consumers/src/components/ConsumerList.vue index e821e3cee7..86ec250824 100644 --- a/packages/entities/entities-consumers/src/components/ConsumerList.vue +++ b/packages/entities/entities-consumers/src/components/ConsumerList.vue @@ -15,6 +15,7 @@ :query="filterQuery" :row-attributes="rowAttributes" :table-headers="tableHeaders" + :use-action-outside="useActionOutside" @clear-search-input="clearFilter" @click:row="(row: any) => rowClick(row as EntityRow)" @empty-state-cta-clicked="handleEmptyStateCtaClicked" @@ -30,17 +31,24 @@ @@ -252,6 +260,11 @@ const props = defineProps({ required: false, default: async () => true, }, + /** default to false, setting to true will teleport the toolbar button to the destination in the consuming app */ + useActionOutside: { + type: Boolean, + default: false, + }, }) const { i18nT, i18n: { t } } = composables.useI18n() @@ -553,10 +566,17 @@ const removeConsumers = async (): Promise => { } } +const hasData = ref(true) + /** * Watchers */ watch(fetcherState, (state) => { + // if table is empty, hide the teleported Create button + if (state?.response?.data?.length === 0) { + hasData.value = false + } + if (state.status === FetcherStatus.Error) { errorMessage.value = { title: t('consumers.errors.general'), diff --git a/packages/entities/entities-gateway-services/src/components/GatewayServiceList.vue b/packages/entities/entities-gateway-services/src/components/GatewayServiceList.vue index f8eeeb38eb..35e0204727 100644 --- a/packages/entities/entities-gateway-services/src/components/GatewayServiceList.vue +++ b/packages/entities/entities-gateway-services/src/components/GatewayServiceList.vue @@ -13,6 +13,7 @@ preferences-storage-key="kong-ui-entities-gateway-services-list" :query="filterQuery" :table-headers="tableHeaders" + :use-action-outside="useActionOutside" @clear-search-input="clearFilter" @click:row="(row: any) => rowClick(row as EntityRow)" @sort="resetPagination" @@ -26,15 +27,23 @@ @@ -226,6 +235,11 @@ const props = defineProps({ required: false, default: async () => true, }, + /** default to false, setting to true will teleport the toolbar button to the destination in the consuming app */ + useActionOutside: { + type: Boolean, + default: false, + }, }) const { i18n: { t } } = composables.useI18n() @@ -504,10 +518,17 @@ const deleteRow = async (): Promise => { } } +const hasData = ref(true) + /** * Watchers */ watch(fetcherState, (state) => { + // if table is empty, hide the teleported Create button + if (state?.response?.data?.length === 0) { + hasData.value = false + } + if (state.status === FetcherStatus.Error) { errorMessage.value = { title: t('errors.general'), diff --git a/packages/entities/entities-key-sets/src/components/KeySetList.vue b/packages/entities/entities-key-sets/src/components/KeySetList.vue index bd74f5c603..1445114816 100644 --- a/packages/entities/entities-key-sets/src/components/KeySetList.vue +++ b/packages/entities/entities-key-sets/src/components/KeySetList.vue @@ -13,6 +13,7 @@ preferences-storage-key="kong-ui-entities-key-sets-list" :query="filterQuery" :table-headers="tableHeaders" + :use-action-outside="useActionOutside" @clear-search-input="clearFilter" @click:row="(row: any) => rowClick(row as EntityRow)" @sort="resetPagination" @@ -26,16 +27,23 @@ @@ -204,6 +212,11 @@ const props = defineProps({ required: false, default: async () => true, }, + /** default to false, setting to true will teleport the toolbar button to the destination in the consuming app */ + useActionOutside: { + type: Boolean, + default: false, + }, }) const { i18n: { t } } = composables.useI18n() @@ -418,10 +431,17 @@ const confirmDelete = async (): Promise => { } } +const hasData = ref(true) + /** * Watchers */ watch(fetcherState, (state) => { + // if table is empty, hide the teleported Create button + if (state?.response?.data?.length === 0) { + hasData.value = false + } + if (state.status === FetcherStatus.Error) { errorMessage.value = { title: t('keySets.errors.general'), diff --git a/packages/entities/entities-keys/src/components/KeyList.vue b/packages/entities/entities-keys/src/components/KeyList.vue index 701a9f5ff6..3cf47cd1e4 100644 --- a/packages/entities/entities-keys/src/components/KeyList.vue +++ b/packages/entities/entities-keys/src/components/KeyList.vue @@ -13,6 +13,7 @@ preferences-storage-key="kong-ui-entities-keys-list" :query="filterQuery" :table-headers="tableHeaders" + :use-action-outside="useActionOutside" @clear-search-input="clearFilter" @click:row="(row: any) => rowClick(row as EntityRow)" @sort="resetPagination" @@ -26,16 +27,23 @@ @@ -210,6 +218,11 @@ const props = defineProps({ required: false, default: async () => true, }, + /** default to false, setting to true will teleport the toolbar button to the destination in the consuming app */ + useActionOutside: { + type: Boolean, + default: false, + }, }) const { i18n: { t } } = composables.useI18n() @@ -427,10 +440,17 @@ const confirmDelete = async (): Promise => { } } +const hasData = ref(true) + /** * Watchers */ watch(fetcherState, (state) => { + // if table is empty, hide the teleported Create button + if (state?.response?.data?.length === 0) { + hasData.value = false + } + if (state.status === FetcherStatus.Error) { errorMessage.value = { title: t('keys.errors.general'), diff --git a/packages/entities/entities-plugins/src/components/PluginList.vue b/packages/entities/entities-plugins/src/components/PluginList.vue index 748b1366ab..ffffe79a8f 100644 --- a/packages/entities/entities-plugins/src/components/PluginList.vue +++ b/packages/entities/entities-plugins/src/components/PluginList.vue @@ -15,6 +15,7 @@ :query="filterQuery" :table-headers="tableHeaders" :title="title" + :use-action-outside="useActionOutside" @clear-search-input="clearFilter" @click:row="(row: any) => rowClick(row as EntityRow)" @sort="resetPagination" @@ -28,16 +29,23 @@ @@ -335,6 +343,11 @@ const props = defineProps({ type: String, default: '', }, + /** default to false, setting to true will teleport the toolbar button to the destination in the consuming app */ + useActionOutside: { + type: Boolean, + default: false, + }, }) const { i18n: { t } } = composables.useI18n() @@ -705,10 +718,17 @@ const confirmDelete = async (): Promise => { } } +const hasData = ref(true) + /** * Watchers */ watch(fetcherState, (state) => { + // if table is empty, hide the teleported Create button + if (state?.response?.data?.length === 0) { + hasData.value = false + } + if (state.status === FetcherStatus.Error) { errorMessage.value = { title: t('errors.general'), diff --git a/packages/entities/entities-routes/src/components/RouteList.vue b/packages/entities/entities-routes/src/components/RouteList.vue index bc335181f2..0c189aa79e 100644 --- a/packages/entities/entities-routes/src/components/RouteList.vue +++ b/packages/entities/entities-routes/src/components/RouteList.vue @@ -14,6 +14,7 @@ :query="filterQuery" :table-headers="tableHeaders" :title="title" + :use-action-outside="useActionOutside" @clear-search-input="clearFilter" @click:row="(row: any) => rowClick(row as EntityRow)" @sort="resetPagination" @@ -27,16 +28,23 @@ @@ -264,6 +272,11 @@ const props = defineProps({ type: String, default: '', }, + /** default to false, setting to true will teleport the toolbar button to the destination in the consuming app */ + useActionOutside: { + type: Boolean, + default: false, + }, }) const { i18n: { t } } = composables.useI18n() @@ -479,10 +492,17 @@ const confirmDelete = async (): Promise => { } } +const hasData = ref(true) + /** * Watchers */ watch(fetcherState, (state) => { + // if table is empty, hide the teleported Create button + if (state?.response?.data?.length === 0) { + hasData.value = false + } + if (state.status === FetcherStatus.Error) { errorMessage.value = { title: t('errors.general'), diff --git a/packages/entities/entities-snis/src/components/SniList.vue b/packages/entities/entities-snis/src/components/SniList.vue index 91a4b2d825..9cb445dca5 100644 --- a/packages/entities/entities-snis/src/components/SniList.vue +++ b/packages/entities/entities-snis/src/components/SniList.vue @@ -15,6 +15,7 @@ preferences-storage-key="kong-ui-entities-snis-list" :query="filterQuery" :table-headers="tableHeaders" + :use-action-outside="useActionOutside" @clear-search-input="clearFilter" @sort="resetPagination" > @@ -27,16 +28,23 @@ @@ -204,6 +212,11 @@ const props = defineProps({ required: false, default: async () => true, }, + /** default to false, setting to true will teleport the toolbar button to the destination in the consuming app */ + useActionOutside: { + type: Boolean, + default: false, + }, }) const { i18n: { t } } = composables.useI18n() @@ -424,10 +437,17 @@ const confirmDelete = async (): Promise => { } } +const hasData = ref(true) + /** * Watchers */ watch(fetcherState, (state) => { + // if table is empty, hide the teleported Create button + if (state?.response?.data?.length === 0) { + hasData.value = false + } + if (state.status === FetcherStatus.Error) { errorMessage.value = { title: t('errors.general'), diff --git a/packages/entities/entities-upstreams-targets/src/components/TargetsList.vue b/packages/entities/entities-upstreams-targets/src/components/TargetsList.vue index 7e03546a66..d3385687f8 100644 --- a/packages/entities/entities-upstreams-targets/src/components/TargetsList.vue +++ b/packages/entities/entities-upstreams-targets/src/components/TargetsList.vue @@ -12,22 +12,30 @@ pagination-type="offset" preferences-storage-key="kong-ui-entities-targets-list" :table-headers="tableHeaders" + :use-action-outside="useActionOutside" @empty-state-cta-clicked="() => !props.config.createRoute ? handleCreateTarget() : undefined" @sort="resetPagination" > @@ -210,6 +218,11 @@ const props = defineProps({ required: false, default: async () => true, }, + /** default to false, setting to true will teleport the toolbar button to the destination in the consuming app */ + useActionOutside: { + type: Boolean, + default: false, + }, }) const { i18n: { t } } = composables.useI18n() @@ -389,10 +402,17 @@ const confirmDelete = async (): Promise => { } } +const hasData = ref(true) + /** * Watchers */ watch(fetcherState, (state) => { + // if table is empty, hide the teleported Create button + if (state?.response?.data?.length === 0) { + hasData.value = false + } + if (state.status === FetcherStatus.Error) { errorMessage.value = { title: t('targets.errors.general'), diff --git a/packages/entities/entities-upstreams-targets/src/components/UpstreamsList.vue b/packages/entities/entities-upstreams-targets/src/components/UpstreamsList.vue index 4c6bdc5ee7..83f79fa7eb 100644 --- a/packages/entities/entities-upstreams-targets/src/components/UpstreamsList.vue +++ b/packages/entities/entities-upstreams-targets/src/components/UpstreamsList.vue @@ -13,6 +13,7 @@ preferences-storage-key="kong-ui-entities-upstreams-list" :query="filterQuery" :table-headers="tableHeaders" + :use-action-outside="useActionOutside" @clear-search-input="clearFilter" @click:row="(row: any) => rowClick(row as EntityRow)" @sort="resetPagination" @@ -27,16 +28,23 @@ @@ -196,6 +204,11 @@ const props = defineProps({ required: false, default: async () => true, }, + /** default to false, setting to true will teleport the toolbar button to the destination in the consuming app */ + useActionOutside: { + type: Boolean, + default: false, + }, }) const { i18n: { t } } = composables.useI18n() @@ -398,10 +411,17 @@ const confirmDelete = async (): Promise => { } } +const hasData = ref(true) + /** * Watchers */ watch(fetcherState, (state) => { + // if table is empty, hide the teleported Create button + if (state?.response?.data?.length === 0) { + hasData.value = false + } + if (state.status === FetcherStatus.Error) { errorMessage.value = { title: t('upstreams.errors.general'), diff --git a/packages/entities/entities-vaults/src/components/VaultList.vue b/packages/entities/entities-vaults/src/components/VaultList.vue index 7a19bcf3f9..5568d0ed31 100644 --- a/packages/entities/entities-vaults/src/components/VaultList.vue +++ b/packages/entities/entities-vaults/src/components/VaultList.vue @@ -13,6 +13,7 @@ preferences-storage-key="kong-ui-entities-vaults-list" :query="filterQuery" :table-headers="tableHeaders" + :use-action-outside="useActionOutside" @clear-search-input="clearFilter" @click:row="(row: any) => rowClick(row as EntityRow)" @sort="resetPagination" @@ -26,16 +27,23 @@ @@ -211,6 +219,11 @@ const props = defineProps({ required: false, default: async () => true, }, + /** default to false, setting to true will teleport the toolbar button to the destination in the consuming app */ + useActionOutside: { + type: Boolean, + default: false, + }, }) const { i18n: { t } } = composables.useI18n() @@ -416,10 +429,17 @@ const confirmDelete = async (): Promise => { } } +const hasData = ref(true) + /** * Watchers */ watch(fetcherState, (state) => { + // if table is empty, hide the teleported Create button + if (state?.response?.data?.length === 0) { + hasData.value = false + } + if (state.status === FetcherStatus.Error) { errorMessage.value = { title: t('errors.general'),