diff --git a/frontend/src/composables/EditorLandingTab.js b/frontend/src/composables/EditorLandingTab.js new file mode 100644 index 0000000000..1d15a3e134 --- /dev/null +++ b/frontend/src/composables/EditorLandingTab.js @@ -0,0 +1,35 @@ +import { useRoute, useRouter } from 'vue-router' + +import { useAccountSettingsStore } from '@/stores/account-settings.js' + +/** + * The immersive editor routes choose their landing tab (Expert or Overview) in a route + * redirect, which runs before the instance or device has been loaded. At that point the + * active team is whatever the session last had, or nothing at all, rather than the team + * that owns the entity being opened. + * + * Call `syncLandingTab` once the entity and its team are loaded. It moves away from the + * Expert tab when the owning team has Expert disabled, and onto it when the redirect + * landed on Overview only because the owning team was not yet known. + */ +export function useEditorLandingTab ({ editorRouteName, expertRouteName, overviewRouteName }) { + const route = useRoute() + const router = useRouter() + const accountSettings = useAccountSettingsStore() + + function syncLandingTab () { + const { isExpertAssistantFeatureEnabled, isExpertInsightsFeatureEnabled } = accountSettings.featuresCheck + const expertEnabled = isExpertAssistantFeatureEnabled || isExpertInsightsFeatureEnabled + const onExpert = route.name === expertRouteName + const landedViaRedirect = route.redirectedFrom?.name === editorRouteName + + if (onExpert && !expertEnabled) { + return router.replace({ name: overviewRouteName, params: route.params }) + } + if (!onExpert && expertEnabled && landedViaRedirect) { + return router.replace({ name: expertRouteName, params: route.params }) + } + } + + return { syncLandingTab } +} diff --git a/frontend/src/mixins/Instance.js b/frontend/src/mixins/Instance.js index 01e0bf0aee..9a217877c8 100644 --- a/frontend/src/mixins/Instance.js +++ b/frontend/src/mixins/Instance.js @@ -78,8 +78,10 @@ export default { } try { const data = await InstanceApi.getInstance(instanceId) + // Load the owning team before exposing the instance, so anything + // reacting to the instance sees the right team and feature checks. + await useAccountStore().setTeam(data.team.slug) this.instance = { ...{ deviceSettings: {} }, ...this.instance, ...data } - useAccountStore().setTeam(this.instance.team.slug) this.instance.deviceSettings = await InstanceApi.getInstanceDeviceSettings(instanceId) if (this.instance.deviceSettings?.targetSnapshot) { this.instance.targetSnapshot = await SnapshotApi.getSnapshot(instanceId, this.instance.deviceSettings.targetSnapshot) diff --git a/frontend/src/pages/device/Editor/index.vue b/frontend/src/pages/device/Editor/index.vue index 3b2052fb20..bdb3449cbb 100644 --- a/frontend/src/pages/device/Editor/index.vue +++ b/frontend/src/pages/device/Editor/index.vue @@ -71,6 +71,7 @@ import DrawerTrigger from '../../../components/immersive-editor/DrawerTrigger.vu import EditorDrawer from '../../../components/immersive-editor/EditorDrawer.vue' import EditorWrapper from '../../../components/immersive-editor/RemoteInstanceEditorWrapper.vue' import { useDeviceHelper } from '../../../composables/DeviceHelper.js' +import { useEditorLandingTab } from '../../../composables/EditorLandingTab.js' import usePermissions from '../../../composables/Permissions.js' import Alerts from '../../../services/alerts.js' import { isInstanceOnNR5Plus } from '../../../utils/instanceVersion' @@ -115,7 +116,14 @@ export default { getDeviceEditorProxy } = useDeviceHelper() + const { syncLandingTab } = useEditorLandingTab({ + editorRouteName: 'device-editor', + expertRouteName: 'device-editor-expert', + overviewRouteName: 'device-editor-overview' + }) + return { + syncLandingTab, device, hasPermission, restartDevice, @@ -303,8 +311,13 @@ export default { } } + // Load the owning team before exposing the device, so the device + // watcher and the landing tab check see the right feature checks. + await useAccountStore().setTeam(device.team.slug) this.device = device - await useAccountStore().setTeam(this.device.team.slug) + if (this.isEditorAvailable) { + this.syncLandingTab() + } }, showConfirmDeleteDialog () { this.showDeleteDeviceDialog() diff --git a/frontend/src/pages/instance/Editor/index.vue b/frontend/src/pages/instance/Editor/index.vue index eab5c62dcc..8f431dd645 100644 --- a/frontend/src/pages/instance/Editor/index.vue +++ b/frontend/src/pages/instance/Editor/index.vue @@ -59,6 +59,7 @@ import DrawerTrigger from '../../../components/immersive-editor/DrawerTrigger.vu import EditorDrawer from '../../../components/immersive-editor/EditorDrawer.vue' import EditorWrapper from '../../../components/immersive-editor/HostedInstanceEditorWrapper.vue' import InstanceActionsButton from '../../../components/instance/ActionButton.vue' +import { useEditorLandingTab } from '../../../composables/EditorLandingTab.js' import usePermissions from '../../../composables/Permissions.js' import instanceMixin from '../../../mixins/Instance.js' @@ -85,10 +86,16 @@ export default { mixins: [instanceMixin], setup () { const { hasAMinimumTeamRoleOf, isVisitingAdmin } = usePermissions() + const { syncLandingTab } = useEditorLandingTab({ + editorRouteName: 'instance-editor', + expertRouteName: 'instance-editor-expert', + overviewRouteName: 'instance-editor-overview' + }) return { isVisitingAdmin, - hasAMinimumTeamRoleOf + hasAMinimumTeamRoleOf, + syncLandingTab } }, data () { @@ -177,6 +184,9 @@ export default { watch: { instance (instance) { this.setInstance(instance) + }, + 'instance.id' () { + this.syncLandingTab() } }, unmounted () {