Skip to content

Commit 5001068

Browse files
Merge pull request #7603 from TheThingsNetwork/issue/claim-error-messages
Improve error messages when claiming a managed gateway
2 parents f282a04 + babab3c commit 5001068

6 files changed

Lines changed: 72 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ For details about compatibility between different releases, see the **Commitment
1717

1818
### Changed
1919

20+
- Improve error messages when claiming a managed gateway.
21+
2022
### Deprecated
2123

2224
### Removed

pkg/webui/console/containers/gateway-onboarding-form/index.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ import { useSelector, useDispatch } from 'react-redux'
1717
import { merge } from 'lodash'
1818

1919
import Form from '@ttn-lw/components/form'
20+
import Link from '@ttn-lw/components/link'
2021

2122
import GatewayApiKeysModal from '@console/components/gateway-api-keys-modal'
2223

2324
import { composeDataUri, downloadDataUriAsFile } from '@ttn-lw/lib/data-uri'
2425
import PropTypes from '@ttn-lw/lib/prop-types'
2526
import attachPromise from '@ttn-lw/lib/store/actions/attach-promise'
27+
import { getClaimGatewayErrorMessage } from '@ttn-lw/lib/errors/utils'
2628

2729
import { createGateway, claimGateway, updateGateway } from '@console/store/actions/gateways'
2830
import { createGatewayApiKey } from '@console/store/actions/api-keys'
@@ -141,7 +143,27 @@ const GatewayOnboardingForm = props => {
141143

142144
onSuccess(cleanValues.target_gateway_id, inputMethod === 'managed')
143145
} catch (error) {
144-
setError(error)
146+
let message = getClaimGatewayErrorMessage(error)
147+
148+
if (message) {
149+
message = {
150+
...message,
151+
values: {
152+
link: content => (
153+
<Link.DocLink
154+
secondary
155+
path="/hardware/gateways/models/thethingsindoorgatewaypro/#subscription"
156+
>
157+
{content}
158+
</Link.DocLink>
159+
),
160+
},
161+
}
162+
setError(message)
163+
} else {
164+
// Fallback for unexpected/unhandled errors
165+
setError(error)
166+
}
145167
}
146168
},
147169
[dispatch, onSuccess, userId],

pkg/webui/lib/errors/utils.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import * as Sentry from '@sentry/react'
1616
import { isPlainObject, isObject } from 'lodash'
17+
import { defineMessages } from 'react-intl'
1718

1819
import { error as errorLog, warn } from '@ttn-lw/lib/log'
1920
import interpolate from '@ttn-lw/lib/interpolate'
@@ -588,3 +589,40 @@ export const ingestError = (error, extras = {}, tags = {}) => {
588589
})
589590
}
590591
}
592+
593+
/**
594+
* Maps claim-related backend errors to appropriate user messages.
595+
*
596+
* @param {object} error - The error object.
597+
* @returns {object|undefined} - The corresponding error message, or undefined if no match.
598+
*/
599+
export const getClaimGatewayErrorMessage = error => {
600+
const m = defineMessages({
601+
notFound: "Gateway doesn't exist. Please confirm that the gateway EUI is correct.",
602+
subscriptionNotActive:
603+
'There is no gateway subscription attached or active. Please get a <link>Gateway Subscription</link> or activate your subscription following the steps in the documentation and try again.',
604+
activationCodeExpired:
605+
'The activation code has expired. To reactivate it, extend your <link>Gateway Subscription</link>.',
606+
permissionDenied: 'The owner token is invalid.',
607+
})
608+
609+
const rootCause = getBackendErrorRootCause(error)
610+
const errorCode = rootCause?.code
611+
const backendErrorMessage = rootCause?.message_format
612+
switch (errorCode) {
613+
case 5: // NOT_FOUND
614+
return m.notFound
615+
case 9: // FAILED_PRECONDITION
616+
if (backendErrorMessage.includes('gateway subscription not attached and active')) {
617+
return m.subscriptionNotActive
618+
}
619+
if (backendErrorMessage.includes('activation code expired')) {
620+
return m.activationCodeExpired
621+
}
622+
return undefined
623+
case 7: // PERMISSION_DENIED
624+
return m.permissionDenied
625+
default:
626+
return undefined
627+
}
628+
}

pkg/webui/lib/prop-types.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ PropTypes.formatters = PropTypes.shape({
3030
PropTypes.message = PropTypes.oneOfType([
3131
PropTypes.shape({
3232
id: PropTypes.string.isRequired,
33-
value: PropTypes.shape({}),
33+
values: PropTypes.shape({}),
3434
defaultMessage: PropTypes.string,
3535
}),
3636
PropTypes.string,

pkg/webui/locales/en.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,6 +1094,10 @@
10941094
"lib.errors.status-code-messages.501": "Not implemented",
10951095
"lib.errors.status-code-messages.503": "Service unavailable",
10961096
"lib.errors.status-code-messages.504": "Gateway timeout",
1097+
"lib.errors.utils.notFound": "Gateway doesn't exist. Please confirm that the gateway EUI is correct.",
1098+
"lib.errors.utils.subscriptionNotActive": "There is no gateway subscription attached or active. Please get a <link>Gateway Subscription</link> or activate your subscription following the steps in the documentation and try again.",
1099+
"lib.errors.utils.activationCodeExpired": "The activation code has expired. To reactivate it, extend your <link>Gateway Subscription</link>.",
1100+
"lib.errors.utils.permissionDenied": "The owner token is invalid.",
10971101
"lib.field-description-messages.idLocation": "Enter a value using lowercase letters, numbers, and dashes. You can choose this freely.",
10981102
"lib.field-description-messages.freqPlanDescription": "A frequency plan defines data rates that your end device or gateway is setup to use. It is important that gateways and end devices within reach use the same frequency plan to be able to communicate.",
10991103
"lib.field-description-messages.freqPlanLocation": "Your end device or gateway manufacturer should provide information about the applicable frequency plan for a particular device. In some cases they are printed on the device itself but they should always be in the hardware manual or data sheet.",

pkg/webui/locales/ja.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,6 +1094,10 @@
10941094
"lib.errors.status-code-messages.501": "",
10951095
"lib.errors.status-code-messages.503": "",
10961096
"lib.errors.status-code-messages.504": "",
1097+
"lib.errors.utils.notFound": "",
1098+
"lib.errors.utils.subscriptionNotActive": "",
1099+
"lib.errors.utils.activationCodeExpired": "",
1100+
"lib.errors.utils.permissionDenied": "",
10971101
"lib.field-description-messages.idLocation": "",
10981102
"lib.field-description-messages.freqPlanDescription": "周波数プランは、エンドデバイスやゲートウェイが使用するように設定されたデータレートを定義します。ゲートウェイとエンドデバイスが通信できるようにするためには、同じ周波数プランを使用することが重要です",
10991103
"lib.field-description-messages.freqPlanLocation": "エンドデバイスやゲートウェイのメーカーは、特定のデバイスに適用される周波数プランに関する情報を提供するはずです。場合によっては、デバイス自体に印刷されていることもありますが、常にハードウェアマニュアルまたはデータシートに記載されているはずです",

0 commit comments

Comments
 (0)