Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ For details about compatibility between different releases, see the **Commitment

### Changed

- Improve error messages when claiming a managed gateway.

### Deprecated

### Removed
Expand Down
24 changes: 23 additions & 1 deletion pkg/webui/console/containers/gateway-onboarding-form/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ import { useSelector, useDispatch } from 'react-redux'
import { merge } from 'lodash'

import Form from '@ttn-lw/components/form'
import Link from '@ttn-lw/components/link'

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

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

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

onSuccess(cleanValues.target_gateway_id, inputMethod === 'managed')
} catch (error) {
setError(error)
let message = getClaimGatewayErrorMessage(error)

if (message) {
message = {
...message,
values: {
link: content => (
<Link.DocLink
secondary
path="/hardware/gateways/models/thethingsindoorgatewaypro/#subscription"
>
{content}
</Link.DocLink>
),
},
}
setError(message)
} else {
// Fallback for unexpected/unhandled errors
setError(error)
}
}
},
[dispatch, onSuccess, userId],
Expand Down
38 changes: 38 additions & 0 deletions pkg/webui/lib/errors/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import * as Sentry from '@sentry/react'
import { isPlainObject, isObject } from 'lodash'
import { defineMessages } from 'react-intl'

import { error as errorLog, warn } from '@ttn-lw/lib/log'
import interpolate from '@ttn-lw/lib/interpolate'
Expand Down Expand Up @@ -588,3 +589,40 @@ export const ingestError = (error, extras = {}, tags = {}) => {
})
}
}

/**
* Maps claim-related backend errors to appropriate user messages.
*
* @param {object} error - The error object.
* @returns {object|undefined} - The corresponding error message, or undefined if no match.
*/
export const getClaimGatewayErrorMessage = error => {
const m = defineMessages({
notFound: "Gateway doesn't exist. Please confirm that the gateway EUI is correct.",
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.',
activationCodeExpired:
'The activation code has expired. To reactivate it, extend your <link>Gateway Subscription</link>.',
permissionDenied: 'The owner token is invalid.',
})

const rootCause = getBackendErrorRootCause(error)
const errorCode = rootCause?.code
const backendErrorMessage = rootCause?.message_format
switch (errorCode) {
case 5: // NOT_FOUND
return m.notFound
case 9: // FAILED_PRECONDITION
if (backendErrorMessage.includes('gateway subscription not attached and active')) {
return m.subscriptionNotActive
}
if (backendErrorMessage.includes('activation code expired')) {
return m.activationCodeExpired
}
return undefined
case 7: // PERMISSION_DENIED
return m.permissionDenied
default:
return undefined
}
}
2 changes: 1 addition & 1 deletion pkg/webui/lib/prop-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ PropTypes.formatters = PropTypes.shape({
PropTypes.message = PropTypes.oneOfType([
PropTypes.shape({
id: PropTypes.string.isRequired,
value: PropTypes.shape({}),
values: PropTypes.shape({}),
defaultMessage: PropTypes.string,
}),
PropTypes.string,
Expand Down
4 changes: 4 additions & 0 deletions pkg/webui/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1094,6 +1094,10 @@
"lib.errors.status-code-messages.501": "Not implemented",
"lib.errors.status-code-messages.503": "Service unavailable",
"lib.errors.status-code-messages.504": "Gateway timeout",
"lib.errors.utils.notFound": "Gateway doesn't exist. Please confirm that the gateway EUI is correct.",
"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.",
"lib.errors.utils.activationCodeExpired": "The activation code has expired. To reactivate it, extend your <link>Gateway Subscription</link>.",
"lib.errors.utils.permissionDenied": "The owner token is invalid.",
"lib.field-description-messages.idLocation": "Enter a value using lowercase letters, numbers, and dashes. You can choose this freely.",
"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.",
"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.",
Expand Down
4 changes: 4 additions & 0 deletions pkg/webui/locales/ja.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"account.views.authorize.index.modalTitle": "許可申請",
"account.views.authorize.index.modalSubtitle": "{clientName}は、以下の権利の付与を要求しています。",
Expand Down Expand Up @@ -1094,6 +1094,10 @@
"lib.errors.status-code-messages.501": "",
"lib.errors.status-code-messages.503": "",
"lib.errors.status-code-messages.504": "",
"lib.errors.utils.notFound": "",
"lib.errors.utils.subscriptionNotActive": "",
"lib.errors.utils.activationCodeExpired": "",
"lib.errors.utils.permissionDenied": "",
"lib.field-description-messages.idLocation": "",
"lib.field-description-messages.freqPlanDescription": "周波数プランは、エンドデバイスやゲートウェイが使用するように設定されたデータレートを定義します。ゲートウェイとエンドデバイスが通信できるようにするためには、同じ周波数プランを使用することが重要です",
"lib.field-description-messages.freqPlanLocation": "エンドデバイスやゲートウェイのメーカーは、特定のデバイスに適用される周波数プランに関する情報を提供するはずです。場合によっては、デバイス自体に印刷されていることもありますが、常にハードウェアマニュアルまたはデータシートに記載されているはずです",
Expand Down
Loading