From 81b051f95025862bc400aa02d63ca15142e23fd3 Mon Sep 17 00:00:00 2001 From: cstns Date: Mon, 9 Dec 2024 11:51:10 +0200 Subject: [PATCH 01/24] Applications routing follow up - add a back button to the app instance creation page - replace device group infocard with custom component which does not miss align with the page header - add multiple breadcrumb entries for the device group page --- .../components/drawers/navigation/MainNav.vue | 9 ++- .../pages/application/DeviceGroup/index.vue | 66 +++++++++++-------- frontend/src/pages/application/routes.js | 14 ++-- frontend/src/pages/team/routes.js | 4 +- 4 files changed, 60 insertions(+), 33 deletions(-) diff --git a/frontend/src/components/drawers/navigation/MainNav.vue b/frontend/src/components/drawers/navigation/MainNav.vue index 43248804c2..25f024dd8f 100644 --- a/frontend/src/components/drawers/navigation/MainNav.vue +++ b/frontend/src/components/drawers/navigation/MainNav.vue @@ -87,7 +87,14 @@ export default { return { ...defaultBackToRoute, ...this.nearestMetaMenu.backTo } case isNearestMenuAnObject && hasBackToProp && typeof this.nearestMetaMenu.backTo === 'function': - return { ...defaultBackToRoute, ...this.nearestMetaMenu.backTo({ team_slug: this.team.slug }) } + return { + ...defaultBackToRoute, + ...this.nearestMetaMenu.backTo({ + params: this.$route.params, + query: this.$route.query, + team: this.team + }) + } case typeof this.nearestMetaMenu === 'string': default: diff --git a/frontend/src/pages/application/DeviceGroup/index.vue b/frontend/src/pages/application/DeviceGroup/index.vue index 2f6f94a689..629ed5562e 100644 --- a/frontend/src/pages/application/DeviceGroup/index.vue +++ b/frontend/src/pages/application/DeviceGroup/index.vue @@ -2,7 +2,7 @@
-
+
You are viewing this device group as an Administrator @@ -13,28 +13,31 @@
@@ -116,9 +116,16 @@ export default { const creds = await deviceApi.generateCredentials(this.device.id) this.device.credentials = creds }, - close () { + close (event) { + if (event.custom) return // Ignore synthetic Shepherd events + this.$refs.dialog.close() this.device.credentials = undefined + + // Re-dispatch the click event for Shepherd + const newEvent = new Event('click', { bubbles: false, cancelable: true }) + newEvent.custom = true + event.originalTarget.dispatchEvent(newEvent) }, copy (text) { this.copyToClipboard(text).then(() => { diff --git a/frontend/src/tours/tour-first-device.js b/frontend/src/tours/tour-first-device.js new file mode 100644 index 0000000000..5e096effa3 --- /dev/null +++ b/frontend/src/tours/tour-first-device.js @@ -0,0 +1,61 @@ +export default [ + { + title: 'Application: Remote Instances', + text: '

Here we can manage the Remote Instances managed by our Application.

', + attachTo: { + element: 'a[data-nav="application-devices-overview"]', + on: 'bottom' + }, + modalOverlayOpeningPadding: 12 + }, + { + title: 'Add Remote Instance', + text: "

Let's add our first Remote Instance to FlowFuse.

We have two steps to complete here:

  1. Add your Remote Instance to FlowFuse.
  2. Install and run the FlowFuse Device Agent on the hardware where you want the Instance to run.

After this, you'll be able to update and deploy flows to your hardware from anywhere in the world.", + attachTo: { + element: 'button[data-action="register-device"]', + on: 'top' + }, + modalOverlayOpeningPadding: 6, + modalOverlayOpeningRadius: 6, + noActions: true, + advanceOn: { + selector: 'button[data-action="register-device"]', + event: 'click' + } + }, + { + title: 'Add Details', + text: '

Define a name and type for your new Remote Instance.

  • Name: A Unique identifier for your Remote Instance
  • Type: Use this field to make your Instance more easily identifiable, and group together similar Instances.
', + attachTo: { + element: 'div[data-el="team-device-create-dialog"] .ff-dialog-box', + on: 'bottom' + }, + classes: 'shepherd-hidden', + advanceOn: { + selector: 'div[data-el="team-device-create-dialog"] .ff-dialog-box button[data-action="dialog-confirm"]', + event: 'click' + } + }, + { + title: 'Connect Your Remote Instance', + text: "

Now you can install the FlowFuse Device Agent onto your hardware, e.g. your own laptop, Raspberry Pi or PLC. Use this command to connect the Device Agent to FlowFuse.

Once you've done this, you'll have a remotely managed Node-RED Instance all setup and running!

", + attachTo: { + element: '[data-el="team-device-config-dialog"].ff-dialog-container--open .ff-dialog-box', + on: 'bottom' + }, + classes: 'shepherd-hidden', + advanceOn: { + selector: '[data-el="team-device-config-dialog"].ff-dialog-container--open .ff-dialog-actions button', + event: 'click' + }, + beforeShowPromise: () => { + return new Promise((resolve) => { + const interval = setInterval(() => { + if (document.querySelector('[data-el="team-device-config-dialog"].ff-dialog-container--open .ff-dialog-actions button')) { + clearInterval(interval) + resolve() + } + }, 100) + }) + } + }] diff --git a/frontend/src/tours/tour-first-device.json b/frontend/src/tours/tour-first-device.json deleted file mode 100644 index add99310ae..0000000000 --- a/frontend/src/tours/tour-first-device.json +++ /dev/null @@ -1,41 +0,0 @@ -[{ - "title": "Application: Remote Instances", - "text": "

Here we can manage the Remote Instances managed by our Application.

", - "attachTo": { - "element": "a[data-nav=\"application-devices-overview\"]", - "on": "bottom" - }, - "modalOverlayOpeningPadding": 12 -}, { - "title": "Add Remote Instance", - "text": "

Let's add our first Remote Instance to FlowFuse.

We have two steps to complete here:

  1. Add your Remote Instance to FlowFuse.
  2. Install and run the FlowFuse Device Agent on the hardware where you want the Instance to run.

After this, you'll be able to update and deploy flows to your hardware from anywhere in the world.", - "attachTo": { - "element": "button[data-action=\"register-device\"]", - "on": "top" - }, - "modalOverlayOpeningPadding": 6, - "modalOverlayOpeningRadius": 6, - "noActions": true, - "advanceOn": { - "selector": "button[data-action=\"register-device\"]", - "event": "click" - } -}, { - "title": "Add Details", - "text": "

Define a name and type for your new Remote Instance.

  • Name: A Unique identifier for your Remote Instance
  • Type: Use this field to make your Instance more easily identifiable, and group together similar Instances.
", - "attachTo": { - "element": "div[data-el=\"team-device-create-dialog\"] .ff-dialog-box", - "on": "bottom" - }, - "advanceOn": { - "selector": "div[data-el=\"team-device-create-dialog\"] .ff-dialog-box button[data-action=\"dialog-confirm\"]", - "event": "click" - } -}, { - "title": "Connect Your Remote Instance", - "text": "

Now you can install the FlowFuse Device Agent onto your hardware, e.g. your own laptop, Raspberry Pi or PLC. Use this command to connect the Device Agent to FlowFuse.

Once you've done this, you'll have a remotely managed Node-RED Instance all setup and running!

", - "attachTo": { - "element": "div[data-el=\"team-device-config-dialog\"] .ff-dialog-box", - "on": "bottom" - } -}] \ No newline at end of file diff --git a/frontend/src/tours/tour-theme.scss b/frontend/src/tours/tour-theme.scss index 2099671638..a4f9dbce9f 100644 --- a/frontend/src/tours/tour-theme.scss +++ b/frontend/src/tours/tour-theme.scss @@ -44,8 +44,11 @@ } } } + &.shepherd-hidden { + display: none + } } .shepherd-modal-overlay-container.shepherd-modal-is-visible path { transition: 0.3s all; -} \ No newline at end of file +} From 655fdb56d438d550a8746e4386668fbbfb344fa2 Mon Sep 17 00:00:00 2001 From: cstns Date: Tue, 28 Jan 2025 18:57:44 +0200 Subject: [PATCH 04/24] display the getting started menu entry for freemium users --- frontend/src/components/PageHeader.vue | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/PageHeader.vue b/frontend/src/components/PageHeader.vue index 15e7acfa8d..dd2ca53701 100644 --- a/frontend/src/components/PageHeader.vue +++ b/frontend/src/components/PageHeader.vue @@ -99,7 +99,7 @@ export default { }, ...mapState('account', ['user', 'team', 'teams']), ...mapState('ux', ['leftDrawer']), - ...mapGetters('account', ['notifications', 'hasAvailableTeams', 'defaultUserTeam', 'canCreateTeam', 'isTrialAccount']), + ...mapGetters('account', ['notifications', 'hasAvailableTeams', 'defaultUserTeam', 'canCreateTeam', 'isTrialAccount', 'featuresCheck']), ...mapGetters('ux', ['hiddenLeftDrawer']), navigationOptions () { return [ @@ -126,7 +126,7 @@ export default { onclick: (route) => window.open(route.url, '_blank'), onclickparams: { url: 'https://flowfuse.com/docs/' } }, - this.isTrialAccount + this.isTrialAccount || !this.featuresCheck?.isHostedInstancesEnabledForTeam ? { label: 'Getting Started', icon: AcademicCapIcon, @@ -178,7 +178,6 @@ export default { }, methods: { ...mapActions('ux', ['toggleLeftDrawer', 'activateTour']), - ...mapGetters('account', ['featuresCheck']), openEducationModal () { this.activateTour('education') product.capture('clicked-open-education-modal') From 4dd0142b36f07990f40d7829b5518e9b29fed2d3 Mon Sep 17 00:00:00 2001 From: cstns Date: Tue, 28 Jan 2025 18:58:30 +0200 Subject: [PATCH 05/24] force return a boolean for the isTrialAccount account getter --- frontend/src/store/account.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/store/account.js b/frontend/src/store/account.js index 2eff7b81b5..257f1d09d3 100644 --- a/frontend/src/store/account.js +++ b/frontend/src/store/account.js @@ -81,7 +81,7 @@ const getters = { !state.team?.billing?.active }, isTrialAccount (state) { - return state.team?.billing?.trial + return !!state.team?.billing?.trial }, isAdminUser: (state) => !!state.user.admin, defaultUserTeam: (state, getters) => { From 0fb68646192dbb518bf9e2b60d1c0949dbded531 Mon Sep 17 00:00:00 2001 From: cstns Date: Tue, 28 Jan 2025 18:59:44 +0200 Subject: [PATCH 06/24] alter the free welcome tour to accommodate attachment to the applications devices if empty or not --- frontend/src/tours/tour-welcome-free.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/tours/tour-welcome-free.json b/frontend/src/tours/tour-welcome-free.json index 7b2fe9738b..a02effd492 100644 --- a/frontend/src/tours/tour-welcome-free.json +++ b/frontend/src/tours/tour-welcome-free.json @@ -20,7 +20,7 @@ "title": "Concept: Remote Instances", "text": "

Remote Instances are Node-RED instances that are managed and deployed onto your own hardware.

This could be Instances running in factories or on a Raspberry Pi on your desk.

", "attachTo": { - "element": "section[data-el=\"application-devices-none\"]", + "element": "section[data-el=\"application-devices-none\"], section[data-el=\"application-devices\"]", "on": "bottom" } }, { @@ -62,4 +62,4 @@ "selector": "a[data-action=\"view-application\"]", "event": "click" } -}] \ No newline at end of file +}] From 6697bd0e91b8468524307531d82905857c4322e1 Mon Sep 17 00:00:00 2001 From: cstns Date: Tue, 28 Jan 2025 19:02:31 +0200 Subject: [PATCH 07/24] add a delay when finding the devices overview in the tutorial so the tutorial won't glitch --- frontend/src/tours/tour-first-device.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/frontend/src/tours/tour-first-device.js b/frontend/src/tours/tour-first-device.js index 5e096effa3..2962be16f3 100644 --- a/frontend/src/tours/tour-first-device.js +++ b/frontend/src/tours/tour-first-device.js @@ -6,7 +6,17 @@ export default [ element: 'a[data-nav="application-devices-overview"]', on: 'bottom' }, - modalOverlayOpeningPadding: 12 + modalOverlayOpeningPadding: 12, + beforeShowPromise: () => { + return new Promise((resolve) => { + const interval = setInterval(() => { + if (document.querySelector('a[data-nav="application-devices-overview"]')) { + clearInterval(interval) + resolve() + } + }, 100) + }) + } }, { title: 'Add Remote Instance', From c07b7e678aa1b10594967cd33d130524380f8b43 Mon Sep 17 00:00:00 2001 From: cstns Date: Tue, 28 Jan 2025 19:03:19 +0200 Subject: [PATCH 08/24] remove the tutorial trigger from the application devices page --- frontend/src/pages/application/Devices.vue | 9 --------- 1 file changed, 9 deletions(-) diff --git a/frontend/src/pages/application/Devices.vue b/frontend/src/pages/application/Devices.vue index 55d67e8fbc..9d835ccb68 100644 --- a/frontend/src/pages/application/Devices.vue +++ b/frontend/src/pages/application/Devices.vue @@ -20,9 +20,6 @@ import { mapState } from 'vuex' import DevicesBrowser from '../../components/DevicesBrowser.vue' import SectionTopMenu from '../../components/SectionTopMenu.vue' -import Tours from '../../tours/Tours.js' - -import TourFirstDevice from '../../tours/tour-first-device.js' export default { name: 'ApplicationDevices', @@ -38,12 +35,6 @@ export default { }, computed: { ...mapState('ux', ['tours']) - }, - mounted () { - if (this.tours['first-device']) { - const tour = Tours.create('first-device', TourFirstDevice, this.$store) - tour.start() - } } } From bb92cc39a1bb65438d0b41ad405b3ce1f351c9dc Mon Sep 17 00:00:00 2001 From: cstns Date: Tue, 28 Jan 2025 19:04:51 +0200 Subject: [PATCH 09/24] start the device tour first for freemium type teams followed by the regular welcome tour. Needed to redo the conditions in which tours get triggered as well --- .../src/pages/team/Applications/index.vue | 124 +++++++++++++----- 1 file changed, 94 insertions(+), 30 deletions(-) diff --git a/frontend/src/pages/team/Applications/index.vue b/frontend/src/pages/team/Applications/index.vue index 0c8d011202..269f6a8d16 100644 --- a/frontend/src/pages/team/Applications/index.vue +++ b/frontend/src/pages/team/Applications/index.vue @@ -102,7 +102,7 @@ From 3da0be9fd7d84f73398765b578c2c165c623d553 Mon Sep 17 00:00:00 2001 From: cstns Date: Tue, 28 Jan 2025 19:51:40 +0200 Subject: [PATCH 10/24] enable the freemium tier tour to no billing setups --- .../src/pages/team/Applications/index.vue | 126 ++++++++---------- frontend/src/store/ux.js | 2 + 2 files changed, 55 insertions(+), 73 deletions(-) diff --git a/frontend/src/pages/team/Applications/index.vue b/frontend/src/pages/team/Applications/index.vue index 269f6a8d16..7f663bde42 100644 --- a/frontend/src/pages/team/Applications/index.vue +++ b/frontend/src/pages/team/Applications/index.vue @@ -136,7 +136,7 @@ export default { } }, computed: { - ...mapState('ux', ['tours']), + ...mapState('ux', ['tours', 'completeTours']), ...mapGetters('account', ['featuresCheck', 'team']), applicationsList () { return Array.from(this.applications.values()).map(app => { @@ -234,65 +234,13 @@ export default { this.$router.replace({ query: '' }) // allow the Alerts service to have subscription by wrapping in nextTick Alerts.emit('Thanks for signing up to FlowFuse!', 'confirmation') - - // we just 'paid' for a freemium account - if (!this.featuresCheck?.isHostedInstancesEnabledForTeam && this.tours.welcome) { - if (this.applicationsList[0]) { - // we'll redirect to the application devices page and start the device-tour - return this.$store.dispatch('ux/activateTour', 'first-device') - .then(() => this.$router.push({ - name: 'ApplicationDevices', - params: { team_slug: this.team.slug, id: this.applicationsList[0].id } - })) - .then(() => Tours.create( - 'first-device', - TourFirstDevice, - this.$store - )) - .then((tour) => tour.start()) - .catch(e => e) - } - } }) } - this.$nextTick(() => { - // First time here? - if (this.tours.welcome) { - switch (true) { - case !this.tours['first-device'] && this.isFreemiumTeamType: - // we're starting the delayed free tour for freemium tiers (without any instances pre-created) - (Tours.create('welcome', - TourWelcomeFree, - this.$store, - () => { - if (this.deviceCount === 0) { - this.$store.dispatch('ux/activateTour', 'first-device') - } else { - this.$store.dispatch('ux/activateTour', 'education') - } - })).start() - break - case this.instanceCount > 0 && !this.isFreemiumTeamType: - // Running with an Instance pre-configured (Trial team types) - (Tours.create('welcome', TourWelcome, this.$store, () => { - this.$store.dispatch('ux/activateTour', 'education') - })).start() - break - case !this.isFreemiumTeamType: - // any regular team type - (Tours.create('welcome', TourWelcomeFree, this.$store, () => { - if (this.deviceCount === 0) { - this.$store.dispatch('ux/activateTour', 'first-device') - } - })).start() - break - default: - // no tours - break - } - } - }) + // First time here? + if (this.tours.welcome) { + this.dispatchTour() + } this.setSearchQuery() }, @@ -382,23 +330,55 @@ export default { if (this.$route?.query && Object.prototype.hasOwnProperty.call(this.$route.query, 'searchQuery')) { this.filterTerm = this.$route.query.searchQuery } + }, + dispatchTour () { + switch (true) { + case this.isFreemiumTeamType && !this.completeTours.includes('first-device'): + // freemium users must first undergo the first-device tour on the ApplicationDevices page + return this.$store.dispatch('ux/activateTour', 'first-device') + .then(() => this.$router.push({ + name: 'ApplicationDevices', + params: { team_slug: this.team.slug, id: this.applicationsList[0].id } + })) + .then(() => Tours.create( + 'first-device', + TourFirstDevice, + this.$store + )) + .then((tour) => tour.start()) + .catch(e => e) + + case this.isFreemiumTeamType && this.completeTours.includes('first-device'): + // we're starting the delayed free tour for freemium tiers (without any instances pre-created) + return (Tours.create('welcome', + TourWelcomeFree, + this.$store, + () => { + if (this.deviceCount === 0) { + this.$store.dispatch('ux/activateTour', 'first-device') + } else { + this.$store.dispatch('ux/activateTour', 'education') + } + })).start() + + case !this.isFreemiumTeamType && this.instanceCount > 0: + // Running with an Instance pre-configured (Trial team types) + return (Tours.create('welcome', TourWelcome, this.$store, () => { + this.$store.dispatch('ux/activateTour', 'education') + })).start() + + case !this.isFreemiumTeamType: + // any regular team type + return (Tours.create('welcome', TourWelcomeFree, this.$store, () => { + if (this.deviceCount === 0) { + this.$store.dispatch('ux/activateTour', 'first-device') + } + })).start() + + default: + // no tours + } } - // startFreemiumTour () { - // // we'll redirect to the application devices page to start the tour - // return this.$store.dispatch('ux/activateTour', 'first-device') - // .then(() => this.$router.push({ - // name: 'ApplicationDevices', - // params: { team_slug: this.team.slug, id: this.applicationsList[0].id } - // })) - // .then(() => Tours.create( - // 'first-device', - // TourFirstDevice, - // this.$store, - // () => this.$store.dispatch('ux/activateTour', 'education') - // )) - // .then((tour) => tour.start()) - // .catch(e => e) - // } } } diff --git a/frontend/src/store/ux.js b/frontend/src/store/ux.js index ac8ac93915..9e8ccd8add 100644 --- a/frontend/src/store/ux.js +++ b/frontend/src/store/ux.js @@ -25,6 +25,7 @@ const state = () => ({ education: false, 'first-device': false }, + completeTours: [], mainNav: { context: 'team', backToButton: null @@ -440,6 +441,7 @@ const mutations = { }, deactivateTour (state, tour) { state.tours[tour] = false + state.completeTours.push(tour) } } From 543b746974b7a3a0d1b6e1dcb5ba613ee0e7fab1 Mon Sep 17 00:00:00 2001 From: cstns Date: Tue, 28 Jan 2025 19:59:38 +0200 Subject: [PATCH 11/24] qf event dispatch to accommodate chrome --- .../src/pages/team/Devices/dialogs/DeviceCredentialsDialog.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/team/Devices/dialogs/DeviceCredentialsDialog.vue b/frontend/src/pages/team/Devices/dialogs/DeviceCredentialsDialog.vue index 147a54a313..2198d1badf 100644 --- a/frontend/src/pages/team/Devices/dialogs/DeviceCredentialsDialog.vue +++ b/frontend/src/pages/team/Devices/dialogs/DeviceCredentialsDialog.vue @@ -125,7 +125,7 @@ export default { // Re-dispatch the click event for Shepherd const newEvent = new Event('click', { bubbles: false, cancelable: true }) newEvent.custom = true - event.originalTarget.dispatchEvent(newEvent) + event.target.dispatchEvent(newEvent) }, copy (text) { this.copyToClipboard(text).then(() => { From 0a38bc640146d8da7aaf35e94c89271ba7698f0e Mon Sep 17 00:00:00 2001 From: ppawlowski Date: Tue, 28 Jan 2025 19:35:08 +0100 Subject: [PATCH 12/24] Build nr-launcher with custom nr-assistant --- .github/workflows/branch-deploy.yaml | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/.github/workflows/branch-deploy.yaml b/.github/workflows/branch-deploy.yaml index 394881e3b4..9c85b470c5 100644 --- a/.github/workflows/branch-deploy.yaml +++ b/.github/workflows/branch-deploy.yaml @@ -22,6 +22,10 @@ on: description: 'flowfuse/nr-file-nodes branch name' required: true default: 'main' + nr_assistant_branch: + description: 'flowfuse/nr-assistant branch name' + required: true + default: 'main' pull_request: types: - opened @@ -120,16 +124,34 @@ jobs: secrets: npm_registry_token: ${{ secrets.NPM_PUBLISH_TOKEN }} + publish_nr_assistant: + name: Build and publish nr-assistant package + needs: validate-user + if: | + needs.validate-user.outputs.is_org_member == 'true' && + github.event_name == 'workflow_dispatch' && + inputs.nr_assistant_branch != 'main' + uses: 'flowfuse/github-actions-workflows/.github/workflows/publish_node_package.yml@v0.38.0' + with: + package_name: nr-assistant + publish_package: true + repository_name: 'FlowFuse/nr-assistant' + branch_name: ${{ inputs.nr_assistant_branch }} + release_name: "pre-staging-${{ inputs.nr_assistant_branch }}" + secrets: + npm_registry_token: ${{ secrets.NPM_PUBLISH_TOKEN }} + publish_nr_launcher: name: Build and publish nr-launcher package needs: - validate-user - publish_nr_project_nodes - publish_nr_file_nodes + - publish_nr_assistant if: | needs.validate-user.outputs.is_org_member == 'true' && github.event_name == 'workflow_dispatch' && - (always() && inputs.nr_launcher_branch != 'main') || needs.publish_nr_project_nodes.result == 'success' || needs.publish_nr_file_nodes.result == 'success' + (always() && inputs.nr_launcher_branch != 'main') || needs.publish_nr_project_nodes.result == 'success' || needs.publish_nr_file_nodes.result == 'success' || needs.publish_nr_assistant.result == 'success' uses: 'flowfuse/github-actions-workflows/.github/workflows/publish_node_package.yml@v0.38.0' with: package_name: flowfuse-nr-launcher @@ -140,7 +162,7 @@ jobs: package_dependencies: | @flowfuse/nr-project-nodes=${{ inputs.nr_project_nodes_branch != 'main' && needs.publish_nr_project_nodes.outputs.release_name || 'nightly' }} @flowfuse/nr-file-nodes=${{ inputs.nr_file_nodes_branch != 'main' && needs.publish_nr_file_nodes.outputs.release_name || 'nightly' }} - @flowfuse/nr-assistant=nightly + @flowfuse/nr-assistant=${{ inputs.nr_assistant_branch != 'main' && needs.publish_nr_assistant.outputs.release_name || 'nightly' }} secrets: npm_registry_token: ${{ secrets.NPM_PUBLISH_TOKEN }} From 8c6b38fa220c9cdc97118f9f4f612dd764656a62 Mon Sep 17 00:00:00 2001 From: cstns Date: Tue, 28 Jan 2025 20:37:04 +0200 Subject: [PATCH 13/24] qf starting the freemium tour without an application present --- frontend/src/pages/team/Applications/index.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/team/Applications/index.vue b/frontend/src/pages/team/Applications/index.vue index 7f663bde42..2aa0b4d552 100644 --- a/frontend/src/pages/team/Applications/index.vue +++ b/frontend/src/pages/team/Applications/index.vue @@ -333,7 +333,7 @@ export default { }, dispatchTour () { switch (true) { - case this.isFreemiumTeamType && !this.completeTours.includes('first-device'): + case this.isFreemiumTeamType && !this.completeTours.includes('first-device') && this.applicationsList[0]: // freemium users must first undergo the first-device tour on the ApplicationDevices page return this.$store.dispatch('ux/activateTour', 'first-device') .then(() => this.$router.push({ From e5e9566d876b274e316654cd9de508591a70ec7c Mon Sep 17 00:00:00 2001 From: cstns Date: Tue, 28 Jan 2025 21:08:05 +0200 Subject: [PATCH 14/24] qf starting the freemium tour without an application present --- frontend/src/pages/team/Applications/index.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/team/Applications/index.vue b/frontend/src/pages/team/Applications/index.vue index 2aa0b4d552..b515a85b46 100644 --- a/frontend/src/pages/team/Applications/index.vue +++ b/frontend/src/pages/team/Applications/index.vue @@ -333,7 +333,7 @@ export default { }, dispatchTour () { switch (true) { - case this.isFreemiumTeamType && !this.completeTours.includes('first-device') && this.applicationsList[0]: + case this.isFreemiumTeamType && !this.completeTours.includes('first-device') && !!this.applicationsList[0]: // freemium users must first undergo the first-device tour on the ApplicationDevices page return this.$store.dispatch('ux/activateTour', 'first-device') .then(() => this.$router.push({ From 5779a3501ece98e45b9e2ceb9b1ffd9634237860 Mon Sep 17 00:00:00 2001 From: cstns Date: Tue, 28 Jan 2025 21:31:33 +0200 Subject: [PATCH 15/24] adds support to add an alert icon next to nav-item entries --- frontend/src/components/NavItem.vue | 12 ++++++++++-- .../src/components/drawers/navigation/MainNav.vue | 1 + frontend/src/store/ux.js | 3 ++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/NavItem.vue b/frontend/src/components/NavItem.vue index 55cf81e893..e7c4772ef3 100644 --- a/frontend/src/components/NavItem.vue +++ b/frontend/src/components/NavItem.vue @@ -7,6 +7,9 @@ + + +
@@ -14,12 +17,13 @@