diff --git a/pkg/webui/console/containers/webhooks-table/index.js b/pkg/webui/console/containers/webhooks-table/index.js
index 9131ed99c7..93bf22ea1c 100644
--- a/pkg/webui/console/containers/webhooks-table/index.js
+++ b/pkg/webui/console/containers/webhooks-table/index.js
@@ -1,4 +1,4 @@
-// Copyright © 2019 The Things Network Foundation, The Things Industries B.V.
+// Copyright © 2023 The Things Network Foundation, The Things Industries B.V.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,9 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import React from 'react'
-import { connect } from 'react-redux'
+import React, { useCallback } from 'react'
+import { useSelector, useDispatch } from 'react-redux'
import { defineMessages } from 'react-intl'
+import { createSelector } from 'reselect'
+import { useParams } from 'react-router-dom'
import Status from '@ttn-lw/components/status'
@@ -24,7 +26,6 @@ import Message from '@ttn-lw/lib/components/message'
import DateTime from '@ttn-lw/lib/components/date-time'
import sharedMessages from '@ttn-lw/lib/shared-messages'
-import PropTypes from '@ttn-lw/lib/prop-types'
import { getWebhooksList } from '@console/store/actions/webhooks'
@@ -46,101 +47,92 @@ const m = defineMessages({
requestsFailing: 'Requests failing',
})
-@connect(state => ({
- healthStatusEnabled: selectWebhooksHealthStatusEnabled(state),
-}))
-export default class WebhooksTable extends React.Component {
- static propTypes = {
- appId: PropTypes.string.isRequired,
- healthStatusEnabled: PropTypes.bool.isRequired,
- }
-
- constructor(props) {
- super(props)
-
- const { appId } = props
- this.getWebhooksList = () => getWebhooksList(appId, ['template_ids', 'health_status'])
- }
-
- baseDataSelector(state) {
- return {
- webhooks: selectWebhooks(state),
- totalCount: selectWebhooksTotalCount(state),
- fetching: selectWebhooksFetching(state),
- }
- }
-
- render() {
- const { healthStatusEnabled } = this.props
-
- const headers = [
- {
- name: 'ids.webhook_id',
- displayName: sharedMessages.id,
- width: 30,
- sortable: true,
- },
- {
- name: 'base_url',
- displayName: m.baseUrl,
- width: 40,
- sortable: true,
- },
- {
- name: 'template_ids.template_id',
- displayName: m.templateId,
- width: 12,
- render: value => value || ,
- sortable: true,
- },
- ]
-
- if (healthStatusEnabled) {
- headers.push({
- name: 'health_status',
- displayName: sharedMessages.status,
- width: 8,
- render: value => {
- let indicator = 'unknown'
- let label = sharedMessages.unknown
-
- if (value && value.healthy) {
- indicator = 'good'
- label = m.healthy
- } else if (value && value.unhealthy) {
- indicator = 'bad'
- label = m.requestsFailing
- } else {
- indicator = 'mediocre'
- label = m.pending
- }
-
- return
- },
- })
- }
+const WebhooksTable = () => {
+ const { appId } = useParams()
+ const dispatch = useDispatch()
+ const healthStatusEnabled = useSelector(selectWebhooksHealthStatusEnabled)
+ const getWebhooksListCallback = useCallback(
+ () => dispatch(getWebhooksList(appId, ['template_ids', 'health_status'])),
+ [appId, dispatch],
+ )
+
+ const baseDataSelector = createSelector(
+ [selectWebhooks, selectWebhooksTotalCount, selectWebhooksFetching],
+ (webhooks, totalCount, fetching) => ({
+ webhooks,
+ totalCount,
+ fetching,
+ }),
+ )
+
+ const headers = [
+ {
+ name: 'ids.webhook_id',
+ displayName: sharedMessages.id,
+ width: 30,
+ sortable: true,
+ },
+ {
+ name: 'base_url',
+ displayName: m.baseUrl,
+ width: 40,
+ sortable: true,
+ },
+ {
+ name: 'template_ids.template_id',
+ displayName: m.templateId,
+ width: 12,
+ render: value => value || ,
+ sortable: true,
+ },
+ ]
+ if (healthStatusEnabled) {
headers.push({
- name: 'created_at',
- displayName: sharedMessages.createdAt,
- width: 10,
- sortable: true,
- render: date => ,
+ name: 'health_status',
+ displayName: sharedMessages.status,
+ width: 8,
+ render: value => {
+ let indicator = 'unknown'
+ let label = sharedMessages.unknown
+
+ if (value && value.healthy) {
+ indicator = 'good'
+ label = m.healthy
+ } else if (value && value.unhealthy) {
+ indicator = 'bad'
+ label = m.requestsFailing
+ } else {
+ indicator = 'mediocre'
+ label = m.pending
+ }
+
+ return
+ },
})
-
- return (
- }
- paginated={false}
- handlesSorting
- {...this.props}
- />
- )
}
+
+ headers.push({
+ name: 'created_at',
+ displayName: sharedMessages.createdAt,
+ width: 10,
+ sortable: true,
+ render: date => ,
+ })
+
+ return (
+ }
+ paginated={false}
+ handlesSorting
+ />
+ )
}
+
+export default WebhooksTable
diff --git a/pkg/webui/console/store/selectors/webhook-templates.js b/pkg/webui/console/store/selectors/webhook-templates.js
index 1129a1bada..e891066190 100644
--- a/pkg/webui/console/store/selectors/webhook-templates.js
+++ b/pkg/webui/console/store/selectors/webhook-templates.js
@@ -1,4 +1,4 @@
-// Copyright © 2020 The Things Network Foundation, The Things Industries B.V.
+// Copyright © 2023 The Things Network Foundation, The Things Industries B.V.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import { createSelector } from 'reselect'
+
import { createFetchingSelector } from '@ttn-lw/lib/store/selectors/fetching'
import { createErrorSelector } from '@ttn-lw/lib/store/selectors/error'
@@ -32,12 +34,12 @@ export const selectWebhookTemplateById = (state, id) => {
export const selectWebhookTemplateError = createErrorSelector(GET_WEBHOOK_TEMPLATE_BASE)
export const selectWebhookTemplateFetching = createFetchingSelector(GET_WEBHOOK_TEMPLATE_BASE)
-export const selectWebhookTemplates = state => {
- const { entities } = selectWebhookTemplatesStore(state)
+export const selectWebhookTemplates = createSelector([selectWebhookTemplatesStore], state => {
+ const { entities } = state
if (!Boolean(entities)) return undefined
return Object.keys(entities).map(key => entities[key])
-}
+})
export const selectWebhookTemplatesError = createErrorSelector(LIST_WEBHOOK_TEMPLATES_BASE)
export const selectWebhookTemplatesFetching = createFetchingSelector(LIST_WEBHOOK_TEMPLATES_BASE)
diff --git a/pkg/webui/console/views/application-integrations-lora-cloud/index.js b/pkg/webui/console/views/application-integrations-lora-cloud/index.js
index a18ef39666..6fe2ebbcc4 100644
--- a/pkg/webui/console/views/application-integrations-lora-cloud/index.js
+++ b/pkg/webui/console/views/application-integrations-lora-cloud/index.js
@@ -23,7 +23,7 @@ import LoRaCloudImage from '@assets/misc/lora-cloud.png'
import PageTitle from '@ttn-lw/components/page-title'
import Breadcrumb from '@ttn-lw/components/breadcrumbs/breadcrumb'
-import { withBreadcrumb } from '@ttn-lw/components/breadcrumbs/context'
+import { useBreadcrumbs, withBreadcrumb } from '@ttn-lw/components/breadcrumbs/context'
import Link from '@ttn-lw/components/link'
import Collapse from '@ttn-lw/components/collapse'
@@ -60,6 +60,13 @@ const m = defineMessages({
})
const LoRaCloud = () => {
+ useBreadcrumbs(
+ 'apps.single.integrations.lora-cloud',
+ ,
+ )
const appId = useSelector(selectSelectedApplicationId)
const selector = ['data']