diff --git a/cypress/plugins/tasks.js b/cypress/plugins/tasks.js index 9596d21a6d..454cb6b9b5 100644 --- a/cypress/plugins/tasks.js +++ b/cypress/plugins/tasks.js @@ -77,17 +77,10 @@ const sqlTask = on => { }, dropAndSeedDatabase: async () => { const exec = util.promisify(childProcess.exec) - const res = await Promise.all([ + await Promise.all([ exec('tools/bin/mage dev:sqlRestore', { cwd: '..' }), exec('tools/bin/mage dev:redisFlush', { cwd: '..' }), ]) - const err = res - .filter(e => Boolean(e.stderr)) - .map(e => e.stderr) - .join(', ') - if (err) { - // Throw new Error(err) - } return null }, }) diff --git a/pkg/webui/console/components/payload-formatters-form/index.js b/pkg/webui/console/components/payload-formatters-form/index.js index 27b54823c1..707a5ae171 100644 --- a/pkg/webui/console/components/payload-formatters-form/index.js +++ b/pkg/webui/console/components/payload-formatters-form/index.js @@ -424,6 +424,7 @@ class PayloadFormattersForm extends React.Component { {this.formatter} {/* // TODO: Refactor to use data API and re-enable prompt. + // https://github.com/TheThingsNetwork/lorawan-stack/issues/6384 // NOTE: Unfortunately react router v6 requires us to do further // refactoring to use the data API to be able to use `usePrompt` // again, which is required to make the Prompt component work. 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']