From 6ff87be8e0c4c5ba0aacc1a72ab60acbf5744f2d Mon Sep 17 00:00:00 2001 From: ebb-tide Date: Mon, 18 Jun 2018 13:45:11 -0700 Subject: [PATCH 01/22] Actually type notifications --- ui/src/dashboards/actions/index.ts | 4 +- ui/src/dashboards/containers/DashboardPage.js | 2 +- ui/src/kapacitor/components/AlertTabs.tsx | 2 +- ui/src/shared/copy/notifications.ts | 408 +++++++++++------- ui/src/sources/containers/ManageSources.tsx | 2 +- ui/src/sources/containers/SourcePage.tsx | 302 +++++++++++++ ui/src/status/actions/index.ts | 76 ++++ ui/src/status/components/GettingStarted.tsx | 97 +++++ ui/src/status/components/JSONFeedReader.tsx | 45 ++ ui/src/status/components/NewsFeed.tsx | 83 ++++ ui/src/status/constants/index.ts | 1 + ui/src/status/containers/StatusPage.tsx | 108 +++++ ui/src/status/reducers/JSONFeed.ts | 51 +++ ui/src/status/reducers/index.ts | 4 + ui/src/types/notifications.ts | 2 +- ui/src/types/status.ts | 22 + ui/test/normalizers/dashboardTime.test.js | 2 +- 17 files changed, 1043 insertions(+), 168 deletions(-) create mode 100644 ui/src/sources/containers/SourcePage.tsx create mode 100644 ui/src/status/actions/index.ts create mode 100644 ui/src/status/components/GettingStarted.tsx create mode 100644 ui/src/status/components/JSONFeedReader.tsx create mode 100644 ui/src/status/components/NewsFeed.tsx create mode 100644 ui/src/status/constants/index.ts create mode 100644 ui/src/status/containers/StatusPage.tsx create mode 100644 ui/src/status/reducers/JSONFeed.ts create mode 100644 ui/src/status/reducers/index.ts create mode 100644 ui/src/types/status.ts diff --git a/ui/src/dashboards/actions/index.ts b/ui/src/dashboards/actions/index.ts index cc2c61f35d..1ce5f04871 100644 --- a/ui/src/dashboards/actions/index.ts +++ b/ui/src/dashboards/actions/index.ts @@ -417,7 +417,7 @@ export const getDashboardsNamesAsync = (sourceID: string) => async ( } } -export const getDashboardAsync = (dashboardID: string) => async ( +export const getDashboardAsync = (dashboardID: number) => async ( dispatch ): Promise => { try { @@ -813,7 +813,7 @@ const syncDashboardFromURLQueryParams = ( } export const getDashboardWithHydratedAndSyncedTempVarsAsync = ( - dashboardID: string, + dashboardID: number, source: Source, router: InjectedRouter, location: Location diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index 5cfbaffbcc..e0a385597f 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -526,7 +526,7 @@ DashboardPage.propTypes = { sources: arrayOf(shape({})).isRequired, params: shape({ sourceID: string.isRequired, - dashboardID: string.isRequired, + dashboardID: number.isRequired, }).isRequired, location: shape({ pathname: string.isRequired, diff --git a/ui/src/kapacitor/components/AlertTabs.tsx b/ui/src/kapacitor/components/AlertTabs.tsx index 4cda960e8c..8772c549db 100644 --- a/ui/src/kapacitor/components/AlertTabs.tsx +++ b/ui/src/kapacitor/components/AlertTabs.tsx @@ -134,7 +134,7 @@ class AlertTabs extends PureComponent { this.setState({services}) } catch (error) { this.setState({services: null}) - this.props.notify(notifyCouldNotRetrieveKapacitorServices(kapacitor)) + this.props.notify(notifyCouldNotRetrieveKapacitorServices(kapacitor.name)) } } diff --git a/ui/src/shared/copy/notifications.ts b/ui/src/shared/copy/notifications.ts index 4e99e9a747..8cfecc36d5 100644 --- a/ui/src/shared/copy/notifications.ts +++ b/ui/src/shared/copy/notifications.ts @@ -1,22 +1,28 @@ // All copy for notifications should be stored here for easy editing // and ensuring stylistic consistency +import {Notification} from 'src/types' + +type NotificationExcludingMessage = Pick< + Notification, + Exclude +> import {FIVE_SECONDS, TEN_SECONDS, INFINITE} from 'src/shared/constants/index' import {MAX_RESPONSE_BYTES} from 'src/flux/constants' -const defaultErrorNotification = { +const defaultErrorNotification: NotificationExcludingMessage = { type: 'error', icon: 'alert-triangle', duration: TEN_SECONDS, } -const defaultSuccessNotification = { +const defaultSuccessNotification: NotificationExcludingMessage = { type: 'success', icon: 'checkmark', duration: FIVE_SECONDS, } -const defaultDeletionNotification = { +const defaultDeletionNotification: NotificationExcludingMessage = { type: 'primary', icon: 'trash', duration: FIVE_SECONDS, @@ -24,209 +30,240 @@ const defaultDeletionNotification = { // Misc Notifications // ---------------------------------------------------------------------------- -export const notifyGenericFail = () => 'Could not communicate with server.' +export const notifyGenericFail = (): string => + 'Could not communicate with server.' -export const notifyNewVersion = version => ({ +export const notifyNewVersion = (version: string): Notification => ({ type: 'info', icon: 'cubo-uniform', duration: INFINITE, message: `Welcome to the latest Chronograf${version}. Local settings cleared.`, }) -export const notifyLoadLocalSettingsFailed = error => ({ +export const notifyLoadLocalSettingsFailed = (error: string): Notification => ({ ...defaultErrorNotification, message: `Loading local settings failed: ${error}`, }) -export const notifyErrorWithAltText = (type, message) => ({ +export const notifyErrorWithAltText = ( + type: string, + message: string +): Notification => ({ type, icon: 'triangle', duration: TEN_SECONDS, message, }) -export const notifyPresentationMode = () => ({ +export const notifyPresentationMode = (): Notification => ({ type: 'primary', icon: 'expand-b', duration: 7500, message: 'Press ESC to exit Presentation Mode.', }) -export const notifyDataWritten = () => ({ +export const notifyDataWritten = (): Notification => ({ ...defaultSuccessNotification, message: 'Data was written successfully.', }) -export const notifyDataWriteFailed = errorMessage => ({ +export const notifyDataWriteFailed = (errorMessage: string): Notification => ({ ...defaultErrorNotification, message: `Data write failed: ${errorMessage}`, }) -export const notifySessionTimedOut = () => ({ +export const notifySessionTimedOut = (): Notification => ({ type: 'primary', icon: 'triangle', duration: INFINITE, message: 'Your session has timed out. Log in again to continue.', }) -export const notifyServerError = { +export const notifyServerError: Notification = { ...defaultErrorNotification, message: 'Internal Server Error. Check API Logs.', } -export const notifyCouldNotRetrieveKapacitors = sourceID => ({ +export const notifyCouldNotRetrieveKapacitors = ( + sourceID: string +): Notification => ({ ...defaultErrorNotification, message: `Internal Server Error. Could not retrieve Kapacitor Connections for source ${sourceID}.`, }) -export const notifyCouldNotRetrieveKapacitorServices = kapacitor => ({ +export const notifyCouldNotRetrieveKapacitorServices = ( + kapacitor: string +): Notification => ({ ...defaultErrorNotification, - message: `Interanl Server Error. Could not retrieve services for Kapacitor ${kapacitor}`, + message: `Internal Server Error. Could not retrieve services for Kapacitor ${kapacitor}`, }) -export const notifyCouldNotDeleteKapacitor = () => ({ +export const notifyCouldNotDeleteKapacitor = (): Notification => ({ ...defaultErrorNotification, message: 'Internal Server Error. Could not delete Kapacitor Connection.', }) -export const notifyCSVDownloadFailed = () => ({ +export const notifyCSVDownloadFailed = (): Notification => ({ ...defaultErrorNotification, message: 'Unable to download .CSV file', }) // Hosts Page Notifications // ---------------------------------------------------------------------------- -export const notifyUnableToGetHosts = () => ({ +export const notifyUnableToGetHosts = (): Notification => ({ ...defaultErrorNotification, message: 'Unable to get Hosts.', }) -export const notifyUnableToGetApps = () => ({ +export const notifyUnableToGetApps = (): Notification => ({ ...defaultErrorNotification, message: 'Unable to get Apps for Hosts.', }) // InfluxDB Sources Notifications // ---------------------------------------------------------------------------- -export const notifySourceCreationSucceeded = sourceName => ({ +export const notifySourceCreationSucceeded = ( + sourceName: string +): Notification => ({ ...defaultSuccessNotification, icon: 'server2', message: `Connected to InfluxDB ${sourceName} successfully.`, }) -export const notifySourceCreationFailed = (sourceName, errorMessage) => ({ +export const notifySourceCreationFailed = ( + sourceName: string, + errorMessage: string +): Notification => ({ ...defaultErrorNotification, icon: 'server2', message: `Unable to connect to InfluxDB ${sourceName}: ${errorMessage}`, }) -export const notifySourceUdpated = sourceName => ({ +export const notifySourceUdpated = (sourceName: string): Notification => ({ ...defaultSuccessNotification, icon: 'server2', message: `Updated InfluxDB ${sourceName} Connection successfully.`, }) -export const notifySourceUdpateFailed = (sourceName, errorMessage) => ({ +export const notifySourceUdpateFailed = ( + sourceName: string, + errorMessage: string +): Notification => ({ ...defaultErrorNotification, icon: 'server2', message: `Failed to update InfluxDB ${sourceName} Connection: ${errorMessage}`, }) -export const notifySourceDeleted = (sourceName: string) => ({ +export const notifySourceDeleted = (sourceName: string): Notification => ({ ...defaultSuccessNotification, icon: 'server2', message: `${sourceName} deleted successfully.`, }) -export const notifySourceDeleteFailed = sourceName => ({ +export const notifySourceDeleteFailed = (sourceName: string): Notification => ({ ...defaultErrorNotification, icon: 'server2', message: `There was a problem deleting ${sourceName}.`, }) -export const notifySourceNoLongerAvailable = sourceName => - `Source ${sourceName} is no longer available. Please ensure InfluxDB is running.` +export const notifySourceNoLongerAvailable = ( + sourceName: string +): Notification => ({ + ...defaultErrorNotification, + icon: 'server2', + message: `Source ${sourceName} is no longer available. Please ensure InfluxDB is running.`, +}) -export const notifyNoSourcesAvailable = sourceName => - `Unable to connect to source ${sourceName}. No other sources available.` - -export const notifyUnableToRetrieveSources = () => 'Unable to retrieve sources.' - -export const notifyUnableToConnectSource = sourceName => - `Unable to connect to source ${sourceName}.` - -export const notifyErrorConnectingToSource = errorMessage => - `Unable to connect to InfluxDB source: ${errorMessage}` +export const notifyErrorConnectingToSource = ( + errorMessage: string +): Notification => ({ + ...defaultErrorNotification, + icon: 'server2', + message: `Unable to connect to InfluxDB source: ${errorMessage}`, +}) // Multitenancy User Notifications // ---------------------------------------------------------------------------- -export const notifyUserRemovedFromAllOrgs = () => ({ +export const notifyUserRemovedFromAllOrgs = (): Notification => ({ ...defaultErrorNotification, duration: INFINITE, message: 'You have been removed from all organizations. Please contact your administrator.', }) -export const notifyUserRemovedFromCurrentOrg = () => ({ +export const notifyUserRemovedFromCurrentOrg = (): Notification => ({ ...defaultErrorNotification, duration: INFINITE, message: 'You were removed from your current organization.', }) -export const notifyOrgHasNoSources = () => ({ +export const notifyOrgHasNoSources = (): Notification => ({ ...defaultErrorNotification, duration: INFINITE, message: 'Organization has no sources configured.', }) -export const notifyUserSwitchedOrgs = (orgName, roleName) => ({ +export const notifyUserSwitchedOrgs = ( + orgName: string, + roleName: string +): Notification => ({ ...defaultSuccessNotification, type: 'primary', message: `Now logged in to '${orgName}' as '${roleName}'.`, }) -export const notifyOrgIsPrivate = () => ({ +export const notifyOrgIsPrivate = (): Notification => ({ ...defaultErrorNotification, duration: INFINITE, message: 'This organization is private. To gain access, you must be explicitly added by an administrator.', }) -export const notifyCurrentOrgDeleted = () => ({ +export const notifyCurrentOrgDeleted = (): Notification => ({ ...defaultErrorNotification, duration: INFINITE, message: 'Your current organization was deleted.', }) -export const notifyJSONFeedFailed = url => ({ +export const notifyJSONFeedFailed = (url: string): Notification => ({ ...defaultErrorNotification, message: `Failed to fetch JSON Feed for News Feed from '${url}'`, }) // Chronograf Admin Notifications // ---------------------------------------------------------------------------- -export const notifyMappingDeleted = (id, scheme) => ({ +export const notifyMappingDeleted = ( + id: string, + scheme: string +): Notification => ({ ...defaultSuccessNotification, message: `Mapping ${id}/${scheme} deleted successfully.`, }) -export const notifyChronografUserAddedToOrg = (user, organization) => - `${user} has been added to ${organization} successfully.` +export const notifyChronografUserAddedToOrg = ( + user: string, + organization: string +): string => `${user} has been added to ${organization} successfully.` -export const notifyChronografUserRemovedFromOrg = (user, organization) => - `${user} has been removed from ${organization} successfully.` +export const notifyChronografUserRemovedFromOrg = ( + user: string, + organization: string +): string => `${user} has been removed from ${organization} successfully.` -export const notifyChronografUserUpdated = message => ({ +export const notifyChronografUserUpdated = (message: string): Notification => ({ ...defaultSuccessNotification, message, }) -export const notifyChronografOrgDeleted = orgName => ({ +export const notifyChronografOrgDeleted = (orgName: string): Notification => ({ ...defaultSuccessNotification, message: `Organization ${orgName} deleted successfully.`, }) -export const notifyChronografUserDeleted = (user, isAbsoluteDelete) => ({ +export const notifyChronografUserDeleted = ( + user: string, + isAbsoluteDelete: boolean +): Notification => ({ ...defaultSuccessNotification, message: `${user} has been removed from ${ isAbsoluteDelete @@ -235,7 +272,7 @@ export const notifyChronografUserDeleted = (user, isAbsoluteDelete) => ({ }`, }) -export const notifyChronografUserMissingNameAndProvider = () => ({ +export const notifyChronografUserMissingNameAndProvider = (): Notification => ({ ...defaultErrorNotification, type: 'warning', message: 'User must have a Name and Provider.', @@ -243,220 +280,238 @@ export const notifyChronografUserMissingNameAndProvider = () => ({ // InfluxDB Admin Notifications // ---------------------------------------------------------------------------- -export const notifyDBUserCreated = () => ({ +export const notifyDBUserCreated = (): Notification => ({ ...defaultSuccessNotification, message: 'User created successfully.', }) -export const notifyDBUserCreationFailed = errorMessage => +export const notifyDBUserCreationFailed = (errorMessage: string): string => `Failed to create User: ${errorMessage}` -export const notifyDBUserDeleted = userName => ({ +export const notifyDBUserDeleted = (userName: string): Notification => ({ ...defaultSuccessNotification, message: `User "${userName}" deleted successfully.`, }) -export const notifyDBUserDeleteFailed = errorMessage => +export const notifyDBUserDeleteFailed = (errorMessage: string): string => `Failed to delete User: ${errorMessage}` -export const notifyDBUserPermissionsUpdated = () => ({ +export const notifyDBUserPermissionsUpdated = (): Notification => ({ ...defaultSuccessNotification, message: 'User Permissions updated successfully.', }) -export const notifyDBUserPermissionsUpdateFailed = errorMessage => - `Failed to update User Permissions: ${errorMessage}` +export const notifyDBUserPermissionsUpdateFailed = ( + errorMessage: string +): string => `Failed to update User Permissions: ${errorMessage}` -export const notifyDBUserRolesUpdated = () => ({ +export const notifyDBUserRolesUpdated = (): Notification => ({ ...defaultSuccessNotification, message: 'User Roles updated successfully.', }) -export const notifyDBUserRolesUpdateFailed = errorMessage => +export const notifyDBUserRolesUpdateFailed = (errorMessage: string): string => `Failed to update User Roles: ${errorMessage}` -export const notifyDBUserPasswordUpdated = () => ({ +export const notifyDBUserPasswordUpdated = (): Notification => ({ ...defaultSuccessNotification, message: 'User Password updated successfully.', }) -export const notifyDBUserPasswordUpdateFailed = errorMessage => - `Failed to update User Password: ${errorMessage}` +export const notifyDBUserPasswordUpdateFailed = ( + errorMessage: string +): string => `Failed to update User Password: ${errorMessage}` -export const notifyDatabaseCreated = () => ({ +export const notifyDatabaseCreated = (): Notification => ({ ...defaultSuccessNotification, message: 'Database created successfully.', }) -export const notifyDBCreationFailed = errorMessage => +export const notifyDBCreationFailed = (errorMessage: string): string => `Failed to create Database: ${errorMessage}` -export const notifyDBDeleted = databaseName => ({ +export const notifyDBDeleted = (databaseName: string): Notification => ({ ...defaultSuccessNotification, message: `Database "${databaseName}" deleted successfully.`, }) -export const notifyDBDeleteFailed = errorMessage => +export const notifyDBDeleteFailed = (errorMessage: string): string => `Failed to delete Database: ${errorMessage}` -export const notifyRoleCreated = () => ({ +export const notifyRoleCreated = (): Notification => ({ ...defaultSuccessNotification, message: 'Role created successfully.', }) -export const notifyRoleCreationFailed = errorMessage => +export const notifyRoleCreationFailed = (errorMessage: string): string => `Failed to create Role: ${errorMessage}` -export const notifyRoleDeleted = roleName => ({ +export const notifyRoleDeleted = (roleName: string): Notification => ({ ...defaultSuccessNotification, message: `Role "${roleName}" deleted successfully.`, }) -export const notifyRoleDeleteFailed = errorMessage => +export const notifyRoleDeleteFailed = (errorMessage: string): string => `Failed to delete Role: ${errorMessage}` -export const notifyRoleUsersUpdated = () => ({ +export const notifyRoleUsersUpdated = (): Notification => ({ ...defaultSuccessNotification, message: 'Role Users updated successfully.', }) -export const notifyRoleUsersUpdateFailed = errorMessage => +export const notifyRoleUsersUpdateFailed = (errorMessage: string): string => `Failed to update Role Users: ${errorMessage}` -export const notifyRolePermissionsUpdated = () => ({ +export const notifyRolePermissionsUpdated = (): Notification => ({ ...defaultSuccessNotification, message: 'Role Permissions updated successfully.', }) -export const notifyRolePermissionsUpdateFailed = errorMessage => - `Failed to update Role Permissions: ${errorMessage}` +export const notifyRolePermissionsUpdateFailed = ( + errorMessage: string +): string => `Failed to update Role Permissions: ${errorMessage}` -export const notifyRetentionPolicyCreated = () => ({ +export const notifyRetentionPolicyCreated = (): Notification => ({ ...defaultSuccessNotification, message: 'Retention Policy created successfully.', }) -export const notifyRetentionPolicyCreationError = () => ({ +export const notifyRetentionPolicyCreationError = (): Notification => ({ ...defaultErrorNotification, message: 'Failed to create Retention Policy. Please check name and duration.', }) -export const notifyRetentionPolicyCreationFailed = errorMessage => - `Failed to create Retention Policy: ${errorMessage}` +export const notifyRetentionPolicyCreationFailed = ( + errorMessage: string +): string => `Failed to create Retention Policy: ${errorMessage}` -export const notifyRetentionPolicyDeleted = rpName => ({ +export const notifyRetentionPolicyDeleted = (rpName: string): Notification => ({ ...defaultSuccessNotification, message: `Retention Policy "${rpName}" deleted successfully.`, }) -export const notifyRetentionPolicyDeleteFailed = errorMessage => - `Failed to delete Retention Policy: ${errorMessage}` +export const notifyRetentionPolicyDeleteFailed = ( + errorMessage: string +): string => `Failed to delete Retention Policy: ${errorMessage}` -export const notifyRetentionPolicyUpdated = () => ({ +export const notifyRetentionPolicyUpdated = (): Notification => ({ ...defaultSuccessNotification, message: 'Retention Policy updated successfully.', }) -export const notifyRetentionPolicyUpdateFailed = errorMessage => - `Failed to update Retention Policy: ${errorMessage}` +export const notifyRetentionPolicyUpdateFailed = ( + errorMessage: string +): string => `Failed to update Retention Policy: ${errorMessage}` -export const notifyQueriesError = errorMessage => ({ +export const notifyQueriesError = (errorMessage: string): Notification => ({ ...defaultErrorNotification, - errorMessage, + message: errorMessage, }) -export const notifyRetentionPolicyCantHaveEmptyFields = () => ({ +export const notifyRetentionPolicyCantHaveEmptyFields = (): Notification => ({ ...defaultErrorNotification, message: 'Fields cannot be empty.', }) -export const notifyDatabaseDeleteConfirmationRequired = databaseName => ({ +export const notifyDatabaseDeleteConfirmationRequired = ( + databaseName: string +): Notification => ({ ...defaultErrorNotification, message: `Type "DELETE ${databaseName}" to confirm. This action cannot be undone.`, }) -export const notifyDBUserNamePasswordInvalid = () => ({ +export const notifyDBUserNamePasswordInvalid = (): Notification => ({ ...defaultErrorNotification, message: 'Username and/or Password too short.', }) -export const notifyRoleNameInvalid = () => ({ +export const notifyRoleNameInvalid = (): Notification => ({ ...defaultErrorNotification, message: 'Role name is too short.', }) -export const notifyDatabaseNameInvalid = () => ({ +export const notifyDatabaseNameInvalid = (): Notification => ({ ...defaultErrorNotification, message: 'Database name cannot be blank.', }) -export const notifyDatabaseNameAlreadyExists = () => ({ +export const notifyDatabaseNameAlreadyExists = (): Notification => ({ ...defaultErrorNotification, message: 'A Database by this name already exists.', }) // Dashboard Notifications // ---------------------------------------------------------------------------- -export const notifyTempVarAlreadyExists = tempVarName => ({ +export const notifyTempVarAlreadyExists = ( + tempVarName: string +): Notification => ({ ...defaultErrorNotification, icon: 'cube', message: `Variable '${tempVarName}' already exists. Please enter a new value.`, }) -export const notifyDashboardNotFound = dashboardID => ({ +export const notifyDashboardNotFound = (dashboardID: number): Notification => ({ ...defaultErrorNotification, icon: 'dash-h', message: `Dashboard ${dashboardID} could not be found`, }) -export const notifyDashboardDeleted = name => ({ +export const notifyDashboardDeleted = (name: string): Notification => ({ ...defaultSuccessNotification, icon: 'dash-h', message: `Dashboard ${name} deleted successfully.`, }) -export const notifyDashboardExported = name => ({ +export const notifyDashboardExported = (name: string): Notification => ({ ...defaultSuccessNotification, icon: 'dash-h', message: `Dashboard ${name} exported successfully.`, }) -export const notifyDashboardExportFailed = (name, errorMessage) => ({ +export const notifyDashboardExportFailed = ( + name: string, + errorMessage: string +): Notification => ({ ...defaultErrorNotification, duration: INFINITE, message: `Failed to export Dashboard ${name}: ${errorMessage}.`, }) -export const notifyDashboardImported = name => ({ +export const notifyDashboardImported = (name: string): Notification => ({ ...defaultSuccessNotification, icon: 'dash-h', message: `Dashboard ${name} imported successfully.`, }) -export const notifyDashboardImportFailed = (fileName, errorMessage) => ({ +export const notifyDashboardImportFailed = ( + fileName: string, + errorMessage: string +): Notification => ({ ...defaultErrorNotification, duration: INFINITE, message: `Failed to import Dashboard from file ${fileName}: ${errorMessage}.`, }) -export const notifyDashboardDeleteFailed = (name, errorMessage) => - `Failed to delete Dashboard ${name}: ${errorMessage}.` +export const notifyDashboardDeleteFailed = ( + name: string, + errorMessage: string +): string => `Failed to delete Dashboard ${name}: ${errorMessage}.` -export const notifyCellAdded = name => ({ +export const notifyCellAdded = (name: string): Notification => ({ ...defaultSuccessNotification, icon: 'dash-h', duration: 1900, message: `Added "${name}" to dashboard.`, }) -export const notifyCellDeleted = name => ({ +export const notifyCellDeleted = (name: string): Notification => ({ ...defaultDeletionNotification, icon: 'dash-h', duration: 1900, message: `Deleted "${name}" from dashboard.`, }) -export const notifyBuilderDisabled = () => ({ +export const notifyBuilderDisabled = (): Notification => ({ type: 'info', icon: 'graphline', duration: 7500, @@ -465,249 +520,280 @@ export const notifyBuilderDisabled = () => ({ // Template Variables & URL Queries // ---------------------------------------------------------------------------- -export const notifyInvalidTempVarValueInURLQuery = ({key, value}) => ({ +interface KeyValueString { + key: string + value: string +} +export const notifyInvalidTempVarValueInURLQuery = ({ + key, + value, +}: KeyValueString): Notification => ({ ...defaultErrorNotification, icon: 'cube', message: `Invalid URL query value of '${value}' supplied for template variable '${key}'.`, }) -export const notifyInvalidTimeRangeValueInURLQuery = () => ({ +export const notifyInvalidTimeRangeValueInURLQuery = (): Notification => ({ ...defaultErrorNotification, icon: 'cube', message: `Invalid URL query value supplied for lower or upper time range.`, }) -export const notifyInvalidZoomedTimeRangeValueInURLQuery = () => ({ +export const notifyInvalidZoomedTimeRangeValueInURLQuery = (): Notification => ({ ...defaultErrorNotification, icon: 'cube', message: `Invalid URL query value supplied for zoomed lower or zoomed upper time range.`, }) -export const notifyViewerUnauthorizedToSetTempVars = () => ({ +export const notifyViewerUnauthorizedToSetTempVars = (): Notification => ({ ...defaultErrorNotification, message: `Viewer role unauthorized to override template variable values from URL.`, }) // Rule Builder Notifications // ---------------------------------------------------------------------------- -export const notifyAlertRuleCreated = ruleName => ({ +export const notifyAlertRuleCreated = (ruleName: string): Notification => ({ ...defaultSuccessNotification, message: `${ruleName} created successfully.`, }) -export const notifyAlertRuleCreateFailed = (ruleName, errorMessage) => ({ +export const notifyAlertRuleCreateFailed = ( + ruleName: string, + errorMessage: string +): Notification => ({ ...defaultErrorNotification, message: `There was a problem creating ${ruleName}: ${errorMessage}`, }) -export const notifyAlertRuleUpdated = ruleName => ({ +export const notifyAlertRuleUpdated = (ruleName: string): Notification => ({ ...defaultSuccessNotification, message: `${ruleName} saved successfully.`, }) -export const notifyAlertRuleUpdateFailed = (ruleName, errorMessage) => ({ +export const notifyAlertRuleUpdateFailed = ( + ruleName: string, + errorMessage: string +): Notification => ({ ...defaultErrorNotification, message: `There was a problem saving ${ruleName}: ${errorMessage}`, }) -export const notifyAlertRuleDeleted = ruleName => ({ +export const notifyAlertRuleDeleted = (ruleName: string): Notification => ({ ...defaultSuccessNotification, message: `${ruleName} deleted successfully.`, }) -export const notifyAlertRuleDeleteFailed = ruleName => ({ +export const notifyAlertRuleDeleteFailed = ( + ruleName: string +): Notification => ({ ...defaultErrorNotification, message: `${ruleName} could not be deleted.`, }) -export const notifyAlertRuleStatusUpdated = (ruleName, updatedStatus) => ({ +export const notifyAlertRuleStatusUpdated = ( + ruleName: string, + updatedStatus: string +): Notification => ({ ...defaultSuccessNotification, message: `${ruleName} ${updatedStatus} successfully.`, }) -export const notifyAlertRuleStatusUpdateFailed = (ruleName, updatedStatus) => ({ +export const notifyAlertRuleStatusUpdateFailed = ( + ruleName: string, + updatedStatus: string +): Notification => ({ ...defaultSuccessNotification, message: `${ruleName} could not be ${updatedStatus}.`, }) -export const notifyAlertRuleRequiresQuery = () => +export const notifyAlertRuleRequiresQuery = (): string => 'Please select a Database, Measurement, and Field.' -export const notifyAlertRuleRequiresConditionValue = () => +export const notifyAlertRuleRequiresConditionValue = (): string => 'Please enter a value in the Conditions section.' -export const notifyAlertRuleDeadmanInvalid = () => +export const notifyAlertRuleDeadmanInvalid = (): string => 'Deadman rules require a Database and Measurement.' // Kapacitor Configuration Notifications // ---------------------------------------------------------------------------- -export const notifyKapacitorNameAlreadyTaken = kapacitorName => ({ +export const notifyKapacitorNameAlreadyTaken = ( + kapacitorName: string +): Notification => ({ ...defaultErrorNotification, message: `There is already a Kapacitor Connection named "${kapacitorName}".`, }) -export const notifyCouldNotFindKapacitor = () => ({ +export const notifyCouldNotFindKapacitor = (): Notification => ({ ...defaultErrorNotification, message: 'We could not find a Kapacitor configuration for this source.', }) -export const notifyRefreshKapacitorFailed = () => ({ +export const notifyRefreshKapacitorFailed = (): Notification => ({ ...defaultErrorNotification, message: 'There was an error getting the Kapacitor configuration.', }) -export const notifyAlertEndpointSaved = endpoint => ({ +export const notifyAlertEndpointSaved = (endpoint: string): Notification => ({ ...defaultSuccessNotification, message: `Alert configuration for ${endpoint} saved successfully.`, }) -export const notifyAlertEndpointSaveFailed = (endpoint, errorMessage) => ({ +export const notifyAlertEndpointSaveFailed = ( + endpoint: string, + errorMessage: string +): Notification => ({ ...defaultErrorNotification, message: `There was an error saving the alert configuration for ${endpoint}: ${errorMessage}`, }) export const notifyAlertEndpointDeleteFailed = ( - endpoint, - config, - errorMessage -) => ({ + endpoint: string, + config: string, + errorMessage: string +): Notification => ({ ...defaultErrorNotification, message: `There was an error deleting the alert configuration for ${endpoint}/${config}: ${errorMessage}`, }) -export const notifyAlertEndpointDeleted = (endpoint, config) => ({ +export const notifyAlertEndpointDeleted = ( + endpoint: string, + config: string +): Notification => ({ ...defaultSuccessNotification, message: `Alert configuration for ${endpoint}/${config} deleted successfully.`, }) -export const notifyTestAlertSent = endpoint => ({ +export const notifyTestAlertSent = (endpoint: string): Notification => ({ ...defaultSuccessNotification, duration: TEN_SECONDS, message: `Test Alert sent to ${endpoint}. If the Alert does not reach its destination, please check your endpoint configuration settings.`, }) -export const notifyTestAlertFailed = (endpoint, errorMessage?) => ({ +export const notifyTestAlertFailed = ( + endpoint: string, + errorMessage: string = '' +): Notification => ({ ...defaultErrorNotification, - message: `There was an error sending a Test Alert to ${endpoint}${ - errorMessage ? `: ${errorMessage}` : '.' - }`, + message: `There was an error sending a Test Alert to ${endpoint} ${errorMessage}.`, }) -export const notifyInvalidBatchSizeValue = () => ({ +export const notifyInvalidBatchSizeValue = (): Notification => ({ ...defaultErrorNotification, message: 'Batch Size cannot be empty.', }) -export const notifyKapacitorConnectionFailed = () => ({ +export const notifyKapacitorConnectionFailed = (): Notification => ({ ...defaultErrorNotification, message: 'Could not connect to Kapacitor. Check your connection settings in the Configuration page.', }) -export const notifyKapacitorCreated = () => ({ +export const notifyKapacitorCreated = (): Notification => ({ ...defaultSuccessNotification, message: 'Connected to Kapacitor successfully! Configuring endpoints is optional.', }) -export const notifyKapacitorCreateFailed = () => ({ +export const notifyKapacitorCreateFailed = (): Notification => ({ ...defaultErrorNotification, message: 'There was a problem connecting to Kapacitor.', }) -export const notifyKapacitorUpdated = () => ({ +export const notifyKapacitorUpdated = (): Notification => ({ ...defaultSuccessNotification, message: 'Kapacitor Connection updated successfully.', }) -export const notifyKapacitorUpdateFailed = () => ({ +export const notifyKapacitorUpdateFailed = (): Notification => ({ ...defaultErrorNotification, message: 'There was a problem updating the Kapacitor Connection.', }) // TICKscript Notifications // ---------------------------------------------------------------------------- -export const notifyTickScriptCreated = () => ({ +export const notifyTickScriptCreated = (): Notification => ({ ...defaultSuccessNotification, message: 'TICKscript successfully created.', }) -export const notifyTickscriptCreationFailed = () => +export const notifyTickscriptCreationFailed = (): string => 'Failed to create TICKscript.' -export const notifyTickscriptUpdated = () => ({ +export const notifyTickscriptUpdated = (): Notification => ({ ...defaultSuccessNotification, message: 'TICKscript successfully updated.', }) -export const notifyTickscriptUpdateFailed = () => 'Failed to update TICKscript.' +export const notifyTickscriptUpdateFailed = (): string => + 'Failed to update TICKscript.' -export const notifyTickscriptLoggingUnavailable = () => ({ +export const notifyTickscriptLoggingUnavailable = (): Notification => ({ type: 'warning', icon: 'alert-triangle', duration: INFINITE, message: 'Kapacitor version 1.4 required to view TICKscript logs', }) -export const notifyTickscriptLoggingError = () => ({ +export const notifyTickscriptLoggingError = (): Notification => ({ ...defaultErrorNotification, message: 'Could not collect kapacitor logs', }) -export const notifyKapacitorNotFound = () => ({ +export const notifyKapacitorNotFound = (): Notification => ({ ...defaultErrorNotification, message: 'We could not find a Kapacitor configuration for this source.', }) // Flux notifications -export const validateSuccess = () => ({ +export const validateSuccess = (): Notification => ({ ...defaultSuccessNotification, message: 'No errors found. Happy Happy Joy Joy!', }) -export const notifyCopyToClipboardSuccess = text => ({ +export const notifyCopyToClipboardSuccess = (text: string): Notification => ({ ...defaultSuccessNotification, icon: 'dash-h', message: `'${text}' has been copied to clipboard.`, }) -export const notifyCopyToClipboardFailed = text => ({ +export const notifyCopyToClipboardFailed = (text: string): Notification => ({ ...defaultErrorNotification, message: `'${text}' was not copied to clipboard.`, }) // Service notifications -export const couldNotGetServices = { +export const couldNotGetServices: Notification = { ...defaultErrorNotification, message: 'We could not get services', } -export const fluxCreated = { +export const fluxCreated: Notification = { ...defaultSuccessNotification, message: 'Flux Connection Created. Script your heart out!', } -export const fluxNotCreated = (message: string) => ({ +export const fluxNotCreated = (message: string): Notification => ({ ...defaultErrorNotification, message, }) -export const fluxNotUpdated = (message: string) => ({ +export const fluxNotUpdated = (message: string): Notification => ({ ...defaultErrorNotification, message, }) -export const fluxUpdated = { +export const fluxUpdated: Notification = { ...defaultSuccessNotification, message: 'Connection Updated. Rejoice!', } -export const fluxTimeSeriesError = (message: string) => ({ +export const fluxTimeSeriesError = (message: string): Notification => ({ ...defaultErrorNotification, message: `Could not get data: ${message}`, }) -export const fluxResponseTruncatedError = () => { +export const fluxResponseTruncatedError = (): Notification => { const BYTES_TO_MB = 1 / 1e6 const APPROX_MAX_RESPONSE_MB = +(MAX_RESPONSE_BYTES * BYTES_TO_MB).toFixed(2) diff --git a/ui/src/sources/containers/ManageSources.tsx b/ui/src/sources/containers/ManageSources.tsx index 8a701210f6..d6336d47a3 100644 --- a/ui/src/sources/containers/ManageSources.tsx +++ b/ui/src/sources/containers/ManageSources.tsx @@ -19,7 +19,7 @@ import {Source, NotificationFunc} from 'src/types' interface Props { source: Source sources: Source[] - notify: NotificationFunc + notify: (n: NotificationFunc) => void deleteKapacitor: actions.DeleteKapacitorAsync fetchKapacitors: actions.FetchKapacitorsAsync removeAndLoadSources: actions.RemoveAndLoadSources diff --git a/ui/src/sources/containers/SourcePage.tsx b/ui/src/sources/containers/SourcePage.tsx new file mode 100644 index 0000000000..9212b9acfb --- /dev/null +++ b/ui/src/sources/containers/SourcePage.tsx @@ -0,0 +1,302 @@ +import React, {Component} from 'react' +import PropTypes from 'prop-types' +import {withRouter} from 'react-router' +import _ from 'lodash' +import {getSource} from 'src/shared/apis' +import {createSource, updateSource} from 'src/shared/apis' +import { + addSource as addSourceAction, + updateSource as updateSourceAction, +} from 'src/shared/actions/sources' +import {notify as notifyAction} from 'src/shared/actions/notifications' +import {connect} from 'react-redux' +import {bindActionCreators} from 'redux' + +import Notifications from 'src/shared/components/Notifications' +import SourceForm from 'src/sources/components/SourceForm' +import FancyScrollbar from 'src/shared/components/FancyScrollbar' +import SourceIndicator from 'src/shared/components/SourceIndicator' +import {DEFAULT_SOURCE} from 'src/shared/constants' +const initialPath = '/sources/new' + +import { + notifyErrorConnectingToSource, + notifySourceCreationSucceeded, + notifySourceCreationFailed, + notifySourceUdpated, + notifySourceUdpateFailed, +} from 'src/shared/copy/notifications' +import {ErrorHandling} from 'src/shared/decorators/errors' +import {Source} from 'src/types' + +interface Props { + params: shape({ + id: string, + sourceID: string, + }), + router: shape({ + push: func.isRequired, + }).isRequired, + location: shape({ + query: shape({ + redirectPath: string, + }).isRequired, + }).isRequired, + notify: func.isRequired, + addSource: func.isRequired, + updateSource: func.isRequired,} + +interface State { + isLoading: boolean + source: Source + editMode: boolean + isInitialSource: boolean} + +@ErrorHandling +class SourcePage extends Component { + constructor(props:Props) { + super(props) + + this.state = { + isLoading: true, + source: DEFAULT_SOURCE, + editMode: props.params.id !== undefined, + isInitialSource: props.router.location.pathname === initialPath, + } + } + + componentDidMount() { + const {editMode} = this.state + const {params, notify} = this.props + + if (!editMode) { + return this.setState({isLoading: false}) + } + + getSource(params.id) + .then(({data: source}) => { + this.setState({ + source: {...DEFAULT_SOURCE, ...source}, + isLoading: false, + }) + }) + .catch(error => { + notify(notifyErrorConnectingToSource(this._parseError(error))) + this.setState({isLoading: false}) + }) + } + + handleInputChange = e => { + let val = e.target.value + const name = e.target.name + + if (e.target.type === 'checkbox') { + val = e.target.checked + } + + this.setState(prevState => { + const source = { + ...prevState.source, + [name]: val, + } + + return {...prevState, source} + }) + } + + handleBlurSourceURL = () => { + const {source, editMode} = this.state + if (editMode) { + this.setState(this._normalizeSource) + return + } + + if (!source.url) { + return + } + + this.setState(this._normalizeSource, this._createSourceOnBlur) + } + + handleSubmit = e => { + e.preventDefault() + const {isCreated, editMode} = this.state + const isNewSource = !editMode + + if (!isCreated && isNewSource) { + return this.setState(this._normalizeSource, this._createSource) + } + + this.setState(this._normalizeSource, this._updateSource) + } + + gotoPurgatory = () => { + const {router} = this.props + router.push('/purgatory') + } + + _normalizeSource({source}) { + const url = source.url.trim() + if (source.url.startsWith('http')) { + return {source: {...source, url}} + } + return {source: {...source, url: `http://${url}`}} + } + + _createSourceOnBlur = () => { + const {source} = this.state + // if there is a type on source it has already been created + if (source.type) { + return + } + createSource(source) + .then(({data: sourceFromServer}) => { + this.props.addSource(sourceFromServer) + this.setState({ + source: {...DEFAULT_SOURCE, ...sourceFromServer}, + isCreated: true, + }) + }) + .catch(err => { + // dont want to flash this until they submit + const error = this._parseError(err) + console.error('Error creating InfluxDB connection: ', error) + }) + } + + _createSource = () => { + const {source} = this.state + const {notify} = this.props + createSource(source) + .then(({data: sourceFromServer}) => { + this.props.addSource(sourceFromServer) + this._redirect(sourceFromServer) + notify(notifySourceCreationSucceeded(source.name)) + }) + .catch(error => { + notify(notifySourceCreationFailed(source.name, this._parseError(error))) + }) + } + + _updateSource = () => { + const {source} = this.state + const {notify} = this.props + updateSource(source) + .then(({data: sourceFromServer}) => { + this.props.updateSource(sourceFromServer) + this._redirect(sourceFromServer) + notify(notifySourceUdpated(source.name)) + }) + .catch(error => { + notify(notifySourceUdpateFailed(source.name, this._parseError(error))) + }) + } + + _redirect = source => { + const {isInitialSource} = this.state + const {params, router} = this.props + + if (isInitialSource) { + return this._redirectToApp(source) + } + + router.push(`/sources/${params.sourceID}/manage-sources`) + } + + _redirectToApp = source => { + const {location, router} = this.props + const {redirectPath} = location.query + + if (!redirectPath) { + return router.push(`/sources/${source.id}/hosts`) + } + + const fixedPath = redirectPath.replace( + /\/sources\/[^/]*/, + `/sources/${source.id}` + ) + return router.push(fixedPath) + } + + _parseError = error => { + return _.get(error, ['data', 'message'], error) + } + + render() { + const {isLoading, source, editMode, isInitialSource} = this.state + + if (isLoading) { + return
+ } + + return ( +
+ +
+
+
+
+

+ {editMode + ? 'Configure InfluxDB Connection' + : 'Add a New InfluxDB Connection'} +

+
+ {isInitialSource ? null : ( +
+ +
+ )} +
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+ ) + } +} + +const {func, shape, string} = PropTypes + +SourcePage.propTypes = { + params: shape({ + id: string, + sourceID: string, + }), + router: shape({ + push: func.isRequired, + }).isRequired, + location: shape({ + query: shape({ + redirectPath: string, + }).isRequired, + }).isRequired, + notify: func.isRequired, + addSource: func.isRequired, + updateSource: func.isRequired, +} + +const mapDispatchToProps = dispatch => ({ + notify: bindActionCreators(notifyAction, dispatch), + addSource: bindActionCreators(addSourceAction, dispatch), + updateSource: bindActionCreators(updateSourceAction, dispatch), +}) +export default connect(null, mapDispatchToProps)(withRouter(SourcePage)) diff --git a/ui/src/status/actions/index.ts b/ui/src/status/actions/index.ts new file mode 100644 index 0000000000..40b8cb3f88 --- /dev/null +++ b/ui/src/status/actions/index.ts @@ -0,0 +1,76 @@ +// he is a library for safely encoding and decoding HTML Entities +import he from 'he' + +import {fetchJSONFeed as fetchJSONFeedAJAX} from 'src/status/apis' + +import {notify} from 'src/shared/actions/notifications' +import {notifyJSONFeedFailed} from 'src/shared/copy/notifications' + +import {JSONFeedData} from 'src/types' + +export enum ActionTypes { + FETCH_JSON_FEED_REQUESTED = 'FETCH_JSON_FEED_REQUESTED', + FETCH_JSON_FEED_COMPLETED = 'FETCH_JSON_FEED_COMPLETED', + FETCH_JSON_FEED_FAILED = 'FETCH_JSON_FEED_FAILED', +} + +interface FetchJSONFeedRequestedAction { + type: ActionTypes.FETCH_JSON_FEED_REQUESTED +} + +interface FetchJSONFeedCompletedAction { + type: ActionTypes.FETCH_JSON_FEED_COMPLETED + payload: {data: JSONFeedData} +} + +interface FetchJSONFeedFailedAction { + type: ActionTypes.FETCH_JSON_FEED_FAILED +} + +export type Action = + | FetchJSONFeedRequestedAction + | FetchJSONFeedCompletedAction + | FetchJSONFeedFailedAction + +const fetchJSONFeedRequested = (): FetchJSONFeedRequestedAction => ({ + type: ActionTypes.FETCH_JSON_FEED_REQUESTED, +}) + +const fetchJSONFeedCompleted = ( + data: JSONFeedData +): FetchJSONFeedCompletedAction => ({ + type: ActionTypes.FETCH_JSON_FEED_COMPLETED, + payload: {data}, +}) + +const fetchJSONFeedFailed = (): FetchJSONFeedFailedAction => ({ + type: ActionTypes.FETCH_JSON_FEED_FAILED, +}) + +export const fetchJSONFeedAsync = (url: string) => async ( + dispatch +): Promise => { + dispatch(fetchJSONFeedRequested()) + try { + const {data} = await fetchJSONFeedAJAX(url) + // data could be from a webpage, and thus would be HTML + if (typeof data === 'string' || !data) { + dispatch(fetchJSONFeedFailed()) + } else { + // decode HTML entities from response text + const decodedData = { + ...data, + items: data.items.map(item => { + item.title = he.decode(item.title) + item.content_text = he.decode(item.content_text) + return item + }), + } + dispatch(fetchJSONFeedCompleted(decodedData)) + } + } catch (error) { + console.error(error) + dispatch(fetchJSONFeedFailed()) + dispatch(notify(notifyJSONFeedFailed(url))) + } +} diff --git a/ui/src/status/components/GettingStarted.tsx b/ui/src/status/components/GettingStarted.tsx new file mode 100644 index 0000000000..cc3919a26e --- /dev/null +++ b/ui/src/status/components/GettingStarted.tsx @@ -0,0 +1,97 @@ +import React, {Component} from 'react' + +import FancyScrollbar from 'src/shared/components/FancyScrollbar' +import {ErrorHandling} from 'src/shared/decorators/errors' + +@ErrorHandling +class GettingStarted extends Component { + public render() { + return ( + +
+
+
+ Welcome to Chronograf! +
+

Follow the links below to explore Chronograf’s features.

+
+
+

+ Install the TICK Stack +
Save some time and use this handy tool to install the rest + of the stack: +

+

+ + TICK Sandbox + +

+
+ +
+

+ Questions & Comments +

+

+ If you have any product feedback please open a GitHub issue and + we'll take a look. For any questions or other issues try posting + on our  + + Community Forum + . +

+
+
+
+ ) + } +} + +export default GettingStarted diff --git a/ui/src/status/components/JSONFeedReader.tsx b/ui/src/status/components/JSONFeedReader.tsx new file mode 100644 index 0000000000..8b1baab967 --- /dev/null +++ b/ui/src/status/components/JSONFeedReader.tsx @@ -0,0 +1,45 @@ +import React, {SFC} from 'react' +import {JSONFeedData} from 'src/types' + +import moment from 'moment' + +interface Props { + data: JSONFeedData +} + +const JSONFeedReader: SFC = ({data}) => + data && data.items ? ( +
+ {data.items + ? data.items.map( + ({ + id, + date_published: datePublished, + url, + title, + author: {name}, + image, + content_text: contentText, + }) => ( +
+
+ {`${moment(datePublished).format('MMM DD')}`} +
+
+ +
{title}
+
+ by {name} +
+
+ {image ? : null} +

{contentText}

+
+
+ ) + ) + : null} +
+ ) : null + +export default JSONFeedReader diff --git a/ui/src/status/components/NewsFeed.tsx b/ui/src/status/components/NewsFeed.tsx new file mode 100644 index 0000000000..09877f97e7 --- /dev/null +++ b/ui/src/status/components/NewsFeed.tsx @@ -0,0 +1,83 @@ +import React, {Component} from 'react' +import {connect} from 'react-redux' + +import {fetchJSONFeedAsync} from 'src/status/actions' + +import FancyScrollbar from 'src/shared/components/FancyScrollbar' +import JSONFeedReader from 'src/status/components/JSONFeedReader' +import {ErrorHandling} from 'src/shared/decorators/errors' + +import {JSONFeedData} from 'src/types' + +interface Props { + hasCompletedFetchOnce: boolean + isFetching: boolean + isFailed: boolean + data: JSONFeedData + fetchJSONFeed: (statusFeedURL: string) => void + statusFeedURL: string +} + +@ErrorHandling +class NewsFeed extends Component { + public render() { + const {hasCompletedFetchOnce, isFetching, isFailed, data} = this.props + + if (!hasCompletedFetchOnce) { + return isFailed ? ( +
+

Failed to load News Feed

+
+ ) : ( + // TODO: Factor this out of here and AutoRefresh +
+
+
+ ) + } + + return ( + + {isFetching ? ( + // TODO: Factor this out of here and AutoRefresh +
+
+
+
+
+ ) : null} + {isFailed ? ( +
+

Failed to refresh News Feed

+
+ ) : null} + + + ) + } + + // TODO: implement interval polling a la AutoRefresh + public componentDidMount() { + const {statusFeedURL, fetchJSONFeed} = this.props + + fetchJSONFeed(statusFeedURL) + } +} +const mstp = ({ + links: { + external: {statusFeed: statusFeedURL}, + }, + JSONFeed: {hasCompletedFetchOnce, isFetching, isFailed, data}, +}) => ({ + hasCompletedFetchOnce, + isFetching, + isFailed, + data, + statusFeedURL, +}) + +const mdtp = { + fetchJSONFeed: fetchJSONFeedAsync, +} + +export default connect(mstp, mdtp)(NewsFeed) diff --git a/ui/src/status/constants/index.ts b/ui/src/status/constants/index.ts new file mode 100644 index 0000000000..01421d10c4 --- /dev/null +++ b/ui/src/status/constants/index.ts @@ -0,0 +1 @@ +export const RECENT_ALERTS_LIMIT = 30 diff --git a/ui/src/status/containers/StatusPage.tsx b/ui/src/status/containers/StatusPage.tsx new file mode 100644 index 0000000000..348f69b1e9 --- /dev/null +++ b/ui/src/status/containers/StatusPage.tsx @@ -0,0 +1,108 @@ +import React, {Component} from 'react' +import {connect} from 'react-redux' + +import SourceIndicator from 'src/shared/components/SourceIndicator' +import FancyScrollbar from 'src/shared/components/FancyScrollbar' +import LayoutRenderer from 'src/shared/components/LayoutRenderer' + +import {fixtureStatusPageCells} from 'src/status/fixtures' +import {ErrorHandling} from 'src/shared/decorators/errors' +import { + TEMP_VAR_DASHBOARD_TIME, + TEMP_VAR_UPPER_DASHBOARD_TIME, +} from 'src/shared/constants' +import {Source, TimeRange, Cell} from 'src/types' + +interface State { + cells: Cell[] +} + +interface Props { + source: Source + autoRefresh: number + timeRange: TimeRange +} + +@ErrorHandling +class StatusPage extends Component { + constructor(props: Props) { + super(props) + + this.state = { + cells: fixtureStatusPageCells, + } + } + + public render() { + const {source, autoRefresh, timeRange} = this.props + const {cells} = this.state + + const dashboardTime = { + id: 'dashtime', + tempVar: TEMP_VAR_DASHBOARD_TIME, + type: 'constant', + values: [ + { + value: timeRange.lower, + type: 'constant', + selected: true, + }, + ], + } + + const upperDashboardTime = { + id: 'upperdashtime', + tempVar: TEMP_VAR_UPPER_DASHBOARD_TIME, + type: 'constant', + values: [ + { + value: 'now()', + type: 'constant', + selected: true, + }, + ], + } + + const templates = [dashboardTime, upperDashboardTime] + + return ( +
+
+
+
+

Status

+
+
+ +
+
+
+ +
+ {cells.length ? ( + + ) : ( + Loading Status Page... + )} +
+
+
+ ) + } +} + +const mstp = ({statusUI: {autoRefresh, timeRange}}) => ({ + autoRefresh, + timeRange, +}) + +export default connect(mstp, null)(StatusPage) diff --git a/ui/src/status/reducers/JSONFeed.ts b/ui/src/status/reducers/JSONFeed.ts new file mode 100644 index 0000000000..6f5a65a9d9 --- /dev/null +++ b/ui/src/status/reducers/JSONFeed.ts @@ -0,0 +1,51 @@ +import {JSONFeedData} from 'src/types' +import {Action, ActionTypes} from 'src/status/actions' + +export interface State { + hasCompletedFetchOnce: boolean + isFetching: boolean + isFailed: boolean + data: JSONFeedData +} + +const initialState: State = { + hasCompletedFetchOnce: false, + isFetching: false, + isFailed: false, + data: null, +} + +const JSONFeedReducer = (state: State = initialState, action: Action) => { + switch (action.type) { + case ActionTypes.FETCH_JSON_FEED_REQUESTED: { + return {...state, isFetching: true, isFailed: false} + } + + case ActionTypes.FETCH_JSON_FEED_COMPLETED: { + const {data} = action.payload + + return { + ...state, + hasCompletedFetchOnce: true, + isFetching: false, + isFailed: false, + data, + } + } + + case ActionTypes.FETCH_JSON_FEED_FAILED: { + return { + ...state, + isFetching: false, + isFailed: true, + data: null, + } + } + + default: { + return state + } + } +} + +export default JSONFeedReducer diff --git a/ui/src/status/reducers/index.ts b/ui/src/status/reducers/index.ts new file mode 100644 index 0000000000..ee4264828e --- /dev/null +++ b/ui/src/status/reducers/index.ts @@ -0,0 +1,4 @@ +import JSONFeed from 'src/status/reducers/JSONFeed' +export default { + JSONFeed, +} diff --git a/ui/src/types/notifications.ts b/ui/src/types/notifications.ts index 622b1b3b29..10f1b556c2 100644 --- a/ui/src/types/notifications.ts +++ b/ui/src/types/notifications.ts @@ -6,6 +6,6 @@ export interface Notification { message: string } -export type NotificationFunc = (message: any) => Notification +export type NotificationFunc = (message: string) => Notification export type NotificationAction = (message: Notification) => void diff --git a/ui/src/types/status.ts b/ui/src/types/status.ts new file mode 100644 index 0000000000..66efacedd4 --- /dev/null +++ b/ui/src/types/status.ts @@ -0,0 +1,22 @@ +interface JSONFeedDataItem { + id: string + url: string + title: string + content_text: string + date_published: string + date_modified: string + image: string + author: { + name: string + } +} + +export interface JSONFeedData { + version: string + user_comment: string + home_page_url: string + feed_url: string + title: string + description: string + items: JSONFeedDataItem[] +} diff --git a/ui/test/normalizers/dashboardTime.test.js b/ui/test/normalizers/dashboardTime.test.js index d630552017..ab439144e7 100644 --- a/ui/test/normalizers/dashboardTime.test.js +++ b/ui/test/normalizers/dashboardTime.test.js @@ -30,7 +30,7 @@ describe('Normalizers.DashboardTime', () => { }) it('can remove timeRanges with incorrect dashboardID', () => { - const ranges = [{dashboardID: '1', upper, lower}, timeRange] + const ranges = [{dashboardID: 1, upper, lower}, timeRange] const actual = normalizer(ranges) const expected = [timeRange] From 74be7fdf8cab4600af50cd251ac93a45387f1bfc Mon Sep 17 00:00:00 2001 From: ebb-tide Date: Mon, 18 Jun 2018 16:57:56 -0700 Subject: [PATCH 02/22] Typescriptify Status Page and children --- .../containers/chronograf/AllUsersPage.tsx | 2 +- ui/src/alerts/apis/index.ts | 2 +- ui/src/shared/actions/timeSeries.ts | 45 ++- ui/src/shared/components/Dygraph.tsx | 6 +- ui/src/shared/constants/index.tsx | 21 ++ ui/src/shared/data/timeRanges.ts | 8 +- ui/src/sources/containers/ManageSources.tsx | 4 +- ui/src/sources/containers/SourcePage.js | 278 -------------- ui/src/sources/containers/SourcePage.tsx | 348 +++++++++--------- ui/src/status/actions/index.js | 49 --- ui/src/status/actions/index.ts | 3 +- ui/src/status/apis/index.ts | 5 +- ui/src/status/components/GettingStarted.js | 101 ----- ui/src/status/components/JSONFeedReader.js | 61 --- ui/src/status/components/NewsFeed.js | 92 ----- ui/src/status/constants/actionTypes.js | 6 - ui/src/status/constants/index.js | 1 - ui/src/status/containers/StatusPage.js | 113 ------ ui/src/status/containers/StatusPage.tsx | 19 +- ui/src/status/fixtures.ts | 105 +++--- ui/src/status/reducers/JSONFeed.js | 43 --- ui/src/status/reducers/index.js | 7 - ui/src/status/reducers/ui.js | 36 -- ui/src/types/dashboard.ts | 3 +- ui/src/types/index.ts | 2 + 25 files changed, 294 insertions(+), 1066 deletions(-) delete mode 100644 ui/src/sources/containers/SourcePage.js delete mode 100644 ui/src/status/actions/index.js delete mode 100644 ui/src/status/components/GettingStarted.js delete mode 100644 ui/src/status/components/JSONFeedReader.js delete mode 100644 ui/src/status/components/NewsFeed.js delete mode 100644 ui/src/status/constants/actionTypes.js delete mode 100644 ui/src/status/constants/index.js delete mode 100644 ui/src/status/containers/StatusPage.js delete mode 100644 ui/src/status/reducers/JSONFeed.js delete mode 100644 ui/src/status/reducers/index.js delete mode 100644 ui/src/status/reducers/ui.js diff --git a/ui/src/admin/containers/chronograf/AllUsersPage.tsx b/ui/src/admin/containers/chronograf/AllUsersPage.tsx index 8f42680e5e..c2d514132d 100644 --- a/ui/src/admin/containers/chronograf/AllUsersPage.tsx +++ b/ui/src/admin/containers/chronograf/AllUsersPage.tsx @@ -48,7 +48,7 @@ interface State { @ErrorHandling export class AllUsersPage extends PureComponent { - constructor(props) { + constructor(props: Props) { super(props) this.state = { diff --git a/ui/src/alerts/apis/index.ts b/ui/src/alerts/apis/index.ts index 4222e7fe78..4d8ae08330 100644 --- a/ui/src/alerts/apis/index.ts +++ b/ui/src/alerts/apis/index.ts @@ -1,5 +1,5 @@ import {proxy} from 'src/utils/queryUrlGenerator' -import {TimeRange} from '../../types' +import {TimeRange} from 'src/types' export const getAlerts = ( source: string, diff --git a/ui/src/shared/actions/timeSeries.ts b/ui/src/shared/actions/timeSeries.ts index 7f5c852ee2..8b1f2fc69a 100644 --- a/ui/src/shared/actions/timeSeries.ts +++ b/ui/src/shared/actions/timeSeries.ts @@ -3,15 +3,38 @@ import {noop} from 'src/shared/actions/app' import _ from 'lodash' import {errorThrown} from 'src/shared/actions/errors' +import {TimeSeriesResponse} from 'src/types/series' -export const handleLoading = (query, editQueryStatus) => { +interface Query { + host: string | string[] + text: string + id: string + database?: string + db?: string + rp?: string +} + +interface Payload { + source: string + query: Query + tempVars: any[] + db?: string + rp?: string + resolution?: number +} + +export const handleLoading = (query: Query, editQueryStatus) => { editQueryStatus(query.id, { loading: true, }) } // {results: [{}]} -export const handleSuccess = (data, query, editQueryStatus) => { +export const handleSuccess = ( + data: TimeSeriesResponse, + query: Query, + editQueryStatus +) => { const {results} = data const error = _.get(results, ['0', 'error'], false) const series = _.get(results, ['0', 'series'], false) @@ -51,24 +74,6 @@ export const handleError = (error, query, editQueryStatus) => { }) } -interface Query { - host: string | string[] - text: string - id: string - database?: string - db?: string - rp?: string -} - -interface Payload { - source: string - query: Query - tempVars: any[] - db?: string - rp?: string - resolution?: number -} - export const fetchTimeSeriesAsync = async ( {source, db, rp, query, tempVars, resolution}: Payload, editQueryStatus = noop diff --git a/ui/src/shared/components/Dygraph.tsx b/ui/src/shared/components/Dygraph.tsx index 852802527a..a9a275d898 100644 --- a/ui/src/shared/components/Dygraph.tsx +++ b/ui/src/shared/components/Dygraph.tsx @@ -175,8 +175,10 @@ class Dygraph extends Component { } public componentWillUnmount() { - this.dygraph.destroy() - delete this.dygraph + if (this.dygraph) { + this.dygraph.destroy() + delete this.dygraph + } } public shouldComponentUpdate(nextProps: Props, nextState: State) { diff --git a/ui/src/shared/constants/index.tsx b/ui/src/shared/constants/index.tsx index 647848f3ba..bc93c1a82d 100644 --- a/ui/src/shared/constants/index.tsx +++ b/ui/src/shared/constants/index.tsx @@ -427,7 +427,23 @@ export const DYGRAPH_CONTAINER_H_MARGIN = 16 export const DYGRAPH_CONTAINER_V_MARGIN = 8 export const DYGRAPH_CONTAINER_XLABEL_MARGIN = 20 +export const DEFAULT_SOURCE_LINKS = { + self: '', + kapacitors: '', + proxy: '', + queries: '', + write: '', + permissions: '', + users: '', + roles: '', + databases: '', + annotations: '', + health: '', + services: '', +} + export const DEFAULT_SOURCE = { + id: '', url: 'http://localhost:8086', name: 'Influx 1', username: '', @@ -436,6 +452,11 @@ export const DEFAULT_SOURCE = { telegraf: 'telegraf', insecureSkipVerify: false, metaUrl: '', + organization: '', + role: '', + defaultRP: '', + links: DEFAULT_SOURCE_LINKS, + type: '', } export const defaultIntervalValue = '333' diff --git a/ui/src/shared/data/timeRanges.ts b/ui/src/shared/data/timeRanges.ts index f711a56ebf..0b6f91b474 100644 --- a/ui/src/shared/data/timeRanges.ts +++ b/ui/src/shared/data/timeRanges.ts @@ -6,6 +6,8 @@ interface TimeRangeOption extends TimeRange { menuOption: string } +const nowminus30d = 'now() - 30d' + export const timeRanges: TimeRangeOption[] = [ { defaultGroupBy: '10s', @@ -75,7 +77,7 @@ export const timeRanges: TimeRangeOption[] = [ defaultGroupBy: '6h', seconds: 2592000, inputValue: 'Past 30d', - lower: 'now() - 30d', + lower: nowminus30d, upper: null, menuOption: 'Past 30d', }, @@ -89,3 +91,7 @@ export const defaultTimeRange = { seconds: 900, format: FORMAT_INFLUXQL, } + +export const STATUS_PAGE_TIME_RANGE = timeRanges.find( + tr => tr.lower === nowminus30d +) diff --git a/ui/src/sources/containers/ManageSources.tsx b/ui/src/sources/containers/ManageSources.tsx index d6336d47a3..e1aa156792 100644 --- a/ui/src/sources/containers/ManageSources.tsx +++ b/ui/src/sources/containers/ManageSources.tsx @@ -14,12 +14,12 @@ import { notifySourceDeleteFailed, } from 'src/shared/copy/notifications' -import {Source, NotificationFunc} from 'src/types' +import {Source, Notification} from 'src/types' interface Props { source: Source sources: Source[] - notify: (n: NotificationFunc) => void + notify: (n: Notification) => void deleteKapacitor: actions.DeleteKapacitorAsync fetchKapacitors: actions.FetchKapacitorsAsync removeAndLoadSources: actions.RemoveAndLoadSources diff --git a/ui/src/sources/containers/SourcePage.js b/ui/src/sources/containers/SourcePage.js deleted file mode 100644 index 7d44ace13b..0000000000 --- a/ui/src/sources/containers/SourcePage.js +++ /dev/null @@ -1,278 +0,0 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' -import {withRouter} from 'react-router' -import _ from 'lodash' -import {getSource} from 'shared/apis' -import {createSource, updateSource} from 'shared/apis' -import { - addSource as addSourceAction, - updateSource as updateSourceAction, -} from 'shared/actions/sources' -import {notify as notifyAction} from 'shared/actions/notifications' -import {connect} from 'react-redux' -import {bindActionCreators} from 'redux' - -import Notifications from 'shared/components/Notifications' -import SourceForm from 'src/sources/components/SourceForm' -import FancyScrollbar from 'shared/components/FancyScrollbar' -import SourceIndicator from 'shared/components/SourceIndicator' -import {DEFAULT_SOURCE} from 'shared/constants' -const initialPath = '/sources/new' - -import { - notifyErrorConnectingToSource, - notifySourceCreationSucceeded, - notifySourceCreationFailed, - notifySourceUdpated, - notifySourceUdpateFailed, -} from 'shared/copy/notifications' -import {ErrorHandling} from 'src/shared/decorators/errors' - -@ErrorHandling -class SourcePage extends Component { - constructor(props) { - super(props) - - this.state = { - isLoading: true, - source: DEFAULT_SOURCE, - editMode: props.params.id !== undefined, - isInitialSource: props.router.location.pathname === initialPath, - } - } - - componentDidMount() { - const {editMode} = this.state - const {params, notify} = this.props - - if (!editMode) { - return this.setState({isLoading: false}) - } - - getSource(params.id) - .then(({data: source}) => { - this.setState({ - source: {...DEFAULT_SOURCE, ...source}, - isLoading: false, - }) - }) - .catch(error => { - notify(notifyErrorConnectingToSource(this._parseError(error))) - this.setState({isLoading: false}) - }) - } - - handleInputChange = e => { - let val = e.target.value - const name = e.target.name - - if (e.target.type === 'checkbox') { - val = e.target.checked - } - - this.setState(prevState => { - const source = { - ...prevState.source, - [name]: val, - } - - return {...prevState, source} - }) - } - - handleBlurSourceURL = () => { - const {source, editMode} = this.state - if (editMode) { - this.setState(this._normalizeSource) - return - } - - if (!source.url) { - return - } - - this.setState(this._normalizeSource, this._createSourceOnBlur) - } - - handleSubmit = e => { - e.preventDefault() - const {isCreated, editMode} = this.state - const isNewSource = !editMode - - if (!isCreated && isNewSource) { - return this.setState(this._normalizeSource, this._createSource) - } - - this.setState(this._normalizeSource, this._updateSource) - } - - gotoPurgatory = () => { - const {router} = this.props - router.push('/purgatory') - } - - _normalizeSource({source}) { - const url = source.url.trim() - if (source.url.startsWith('http')) { - return {source: {...source, url}} - } - return {source: {...source, url: `http://${url}`}} - } - - _createSourceOnBlur = () => { - const {source} = this.state - // if there is a type on source it has already been created - if (source.type) { - return - } - createSource(source) - .then(({data: sourceFromServer}) => { - this.props.addSource(sourceFromServer) - this.setState({ - source: {...DEFAULT_SOURCE, ...sourceFromServer}, - isCreated: true, - }) - }) - .catch(err => { - // dont want to flash this until they submit - const error = this._parseError(err) - console.error('Error creating InfluxDB connection: ', error) - }) - } - - _createSource = () => { - const {source} = this.state - const {notify} = this.props - createSource(source) - .then(({data: sourceFromServer}) => { - this.props.addSource(sourceFromServer) - this._redirect(sourceFromServer) - notify(notifySourceCreationSucceeded(source.name)) - }) - .catch(error => { - notify(notifySourceCreationFailed(source.name, this._parseError(error))) - }) - } - - _updateSource = () => { - const {source} = this.state - const {notify} = this.props - updateSource(source) - .then(({data: sourceFromServer}) => { - this.props.updateSource(sourceFromServer) - this._redirect(sourceFromServer) - notify(notifySourceUdpated(source.name)) - }) - .catch(error => { - notify(notifySourceUdpateFailed(source.name, this._parseError(error))) - }) - } - - _redirect = source => { - const {isInitialSource} = this.state - const {params, router} = this.props - - if (isInitialSource) { - return this._redirectToApp(source) - } - - router.push(`/sources/${params.sourceID}/manage-sources`) - } - - _redirectToApp = source => { - const {location, router} = this.props - const {redirectPath} = location.query - - if (!redirectPath) { - return router.push(`/sources/${source.id}/hosts`) - } - - const fixedPath = redirectPath.replace( - /\/sources\/[^/]*/, - `/sources/${source.id}` - ) - return router.push(fixedPath) - } - - _parseError = error => { - return _.get(error, ['data', 'message'], error) - } - - render() { - const {isLoading, source, editMode, isInitialSource} = this.state - - if (isLoading) { - return
- } - - return ( -
- -
-
-
-
-

- {editMode - ? 'Configure InfluxDB Connection' - : 'Add a New InfluxDB Connection'} -

-
- {isInitialSource ? null : ( -
- -
- )} -
-
-
- -
-
-
-
- -
-
-
-
-
-
- ) - } -} - -const {func, shape, string} = PropTypes - -SourcePage.propTypes = { - params: shape({ - id: string, - sourceID: string, - }), - router: shape({ - push: func.isRequired, - }).isRequired, - location: shape({ - query: shape({ - redirectPath: string, - }).isRequired, - }).isRequired, - notify: func.isRequired, - addSource: func.isRequired, - updateSource: func.isRequired, -} - -const mapDispatchToProps = dispatch => ({ - notify: bindActionCreators(notifyAction, dispatch), - addSource: bindActionCreators(addSourceAction, dispatch), - updateSource: bindActionCreators(updateSourceAction, dispatch), -}) -export default connect(null, mapDispatchToProps)(withRouter(SourcePage)) diff --git a/ui/src/sources/containers/SourcePage.tsx b/ui/src/sources/containers/SourcePage.tsx index 9212b9acfb..c0a00dd28b 100644 --- a/ui/src/sources/containers/SourcePage.tsx +++ b/ui/src/sources/containers/SourcePage.tsx @@ -1,7 +1,6 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' -import {withRouter} from 'react-router' -import _ from 'lodash' +import React, {Component, ChangeEvent, FormEvent} from 'react' +import {withRouter, InjectedRouter} from 'react-router' +import {Location} from 'history' import {getSource} from 'src/shared/apis' import {createSource, updateSource} from 'src/shared/apis' import { @@ -27,45 +26,47 @@ import { notifySourceUdpateFailed, } from 'src/shared/copy/notifications' import {ErrorHandling} from 'src/shared/decorators/errors' -import {Source} from 'src/types' +import {Source, Notification, NotificationFunc} from 'src/types' +import {getDeep} from 'src/utils/wrappers' + +interface Params { + id: string + hash: string + sourceID: string +} interface Props { - params: shape({ - id: string, - sourceID: string, - }), - router: shape({ - push: func.isRequired, - }).isRequired, - location: shape({ - query: shape({ - redirectPath: string, - }).isRequired, - }).isRequired, - notify: func.isRequired, - addSource: func.isRequired, - updateSource: func.isRequired,} + location: Location + router: InjectedRouter + params: Params + notify: (notification: Notification | NotificationFunc) => void + addSource: (s: Source) => void + updateSource: (s: Source) => void +} -interface State { +interface State { isLoading: boolean + isCreated: boolean source: Source editMode: boolean - isInitialSource: boolean} - + isInitialSource: boolean +} + @ErrorHandling class SourcePage extends Component { - constructor(props:Props) { + constructor(props: Props) { super(props) this.state = { isLoading: true, + isCreated: false, source: DEFAULT_SOURCE, editMode: props.params.id !== undefined, - isInitialSource: props.router.location.pathname === initialPath, + isInitialSource: props.location.pathname === initialPath, } } - componentDidMount() { + public componentDidMount() { const {editMode} = this.state const {params, notify} = this.props @@ -81,148 +82,12 @@ class SourcePage extends Component { }) }) .catch(error => { - notify(notifyErrorConnectingToSource(this._parseError(error))) + notify(notifyErrorConnectingToSource(this.parseError(error))) this.setState({isLoading: false}) }) } - handleInputChange = e => { - let val = e.target.value - const name = e.target.name - - if (e.target.type === 'checkbox') { - val = e.target.checked - } - - this.setState(prevState => { - const source = { - ...prevState.source, - [name]: val, - } - - return {...prevState, source} - }) - } - - handleBlurSourceURL = () => { - const {source, editMode} = this.state - if (editMode) { - this.setState(this._normalizeSource) - return - } - - if (!source.url) { - return - } - - this.setState(this._normalizeSource, this._createSourceOnBlur) - } - - handleSubmit = e => { - e.preventDefault() - const {isCreated, editMode} = this.state - const isNewSource = !editMode - - if (!isCreated && isNewSource) { - return this.setState(this._normalizeSource, this._createSource) - } - - this.setState(this._normalizeSource, this._updateSource) - } - - gotoPurgatory = () => { - const {router} = this.props - router.push('/purgatory') - } - - _normalizeSource({source}) { - const url = source.url.trim() - if (source.url.startsWith('http')) { - return {source: {...source, url}} - } - return {source: {...source, url: `http://${url}`}} - } - - _createSourceOnBlur = () => { - const {source} = this.state - // if there is a type on source it has already been created - if (source.type) { - return - } - createSource(source) - .then(({data: sourceFromServer}) => { - this.props.addSource(sourceFromServer) - this.setState({ - source: {...DEFAULT_SOURCE, ...sourceFromServer}, - isCreated: true, - }) - }) - .catch(err => { - // dont want to flash this until they submit - const error = this._parseError(err) - console.error('Error creating InfluxDB connection: ', error) - }) - } - - _createSource = () => { - const {source} = this.state - const {notify} = this.props - createSource(source) - .then(({data: sourceFromServer}) => { - this.props.addSource(sourceFromServer) - this._redirect(sourceFromServer) - notify(notifySourceCreationSucceeded(source.name)) - }) - .catch(error => { - notify(notifySourceCreationFailed(source.name, this._parseError(error))) - }) - } - - _updateSource = () => { - const {source} = this.state - const {notify} = this.props - updateSource(source) - .then(({data: sourceFromServer}) => { - this.props.updateSource(sourceFromServer) - this._redirect(sourceFromServer) - notify(notifySourceUdpated(source.name)) - }) - .catch(error => { - notify(notifySourceUdpateFailed(source.name, this._parseError(error))) - }) - } - - _redirect = source => { - const {isInitialSource} = this.state - const {params, router} = this.props - - if (isInitialSource) { - return this._redirectToApp(source) - } - - router.push(`/sources/${params.sourceID}/manage-sources`) - } - - _redirectToApp = source => { - const {location, router} = this.props - const {redirectPath} = location.query - - if (!redirectPath) { - return router.push(`/sources/${source.id}/hosts`) - } - - const fixedPath = redirectPath.replace( - /\/sources\/[^/]*/, - `/sources/${source.id}` - ) - return router.push(fixedPath) - } - - _parseError = error => { - return _.get(error, ['data', 'message'], error) - } - - render() { + public render() { const {isLoading, source, editMode, isInitialSource} = this.state if (isLoading) { @@ -272,26 +137,143 @@ class SourcePage extends Component {
) } -} -const {func, shape, string} = PropTypes + private handleInputChange = (e: ChangeEvent) => { + let val: string | boolean = e.target.value + const name = e.target.name -SourcePage.propTypes = { - params: shape({ - id: string, - sourceID: string, - }), - router: shape({ - push: func.isRequired, - }).isRequired, - location: shape({ - query: shape({ - redirectPath: string, - }).isRequired, - }).isRequired, - notify: func.isRequired, - addSource: func.isRequired, - updateSource: func.isRequired, + if (e.target.type === 'checkbox') { + val = e.target.checked + } + + this.setState(prevState => { + const source = { + ...prevState.source, + [name]: val, + } + + return {...prevState, source} + }) + } + + private handleBlurSourceURL = () => { + const {source, editMode} = this.state + if (editMode) { + this.setState(this.normalizeSource) + return + } + + if (!source.url) { + return + } + + this.setState(this.normalizeSource, this.createSourceOnBlur) + } + + private handleSubmit = (e: FormEvent) => { + e.preventDefault() + const {isCreated, editMode} = this.state + const isNewSource = !editMode + + if (!isCreated && isNewSource) { + return this.setState(this.normalizeSource, this.createSource) + } + + this.setState(this.normalizeSource, this.updateSource) + } + + private gotoPurgatory = () => { + const {router} = this.props + router.push('/purgatory') + } + + private normalizeSource() { + const {source} = this.state + const url = source.url.trim() + if (source.url.startsWith('http')) { + return {source: {...source, url}} + } + return {source: {...source, url: `http://${url}`}} + } + + private createSourceOnBlur = () => { + const {source} = this.state + // if there is a type on source it has already been created + if (source.type) { + return + } + createSource(source) + .then(({data: sourceFromServer}) => { + this.props.addSource(sourceFromServer) + this.setState({ + source: {...DEFAULT_SOURCE, ...sourceFromServer}, + isCreated: true, + }) + }) + .catch(err => { + // dont want to flash this until they submit + const error = this.parseError(err) + console.error('Error creating InfluxDB connection: ', error) + }) + } + + private createSource = () => { + const {source} = this.state + const {notify} = this.props + createSource(source) + .then(({data: sourceFromServer}) => { + this.props.addSource(sourceFromServer) + this.redirect(sourceFromServer) + notify(notifySourceCreationSucceeded(source.name)) + }) + .catch(error => { + notify(notifySourceCreationFailed(source.name, this.parseError(error))) + }) + } + + private updateSource = () => { + const {source} = this.state + const {notify} = this.props + updateSource(source) + .then(({data: sourceFromServer}) => { + this.props.updateSource(sourceFromServer) + this.redirect(sourceFromServer) + notify(notifySourceUdpated(source.name)) + }) + .catch(error => { + notify(notifySourceUdpateFailed(source.name, this.parseError(error))) + }) + } + + private redirect = (source: Source) => { + const {isInitialSource} = this.state + const {params, router} = this.props + + if (isInitialSource) { + return this.redirectToApp(source) + } + + router.push(`/sources/${params.sourceID}/manage-sources`) + } + + private redirectToApp = (source: Source) => { + const {location, router} = this.props + const {redirectPath} = location.query + + if (!redirectPath) { + return router.push(`/sources/${source.id}/hosts`) + } + + const fixedPath = redirectPath.replace( + /\/sources\/[^/]*/, + `/sources/${source.id}` + ) + return router.push(fixedPath) + } + + private parseError = error => { + return getDeep(error, 'data.message', '') + } } const mapDispatchToProps = dispatch => ({ @@ -299,4 +281,4 @@ const mapDispatchToProps = dispatch => ({ addSource: bindActionCreators(addSourceAction, dispatch), updateSource: bindActionCreators(updateSourceAction, dispatch), }) -export default connect(null, mapDispatchToProps)(withRouter(SourcePage)) +export default withRouter(connect(null, mapDispatchToProps)(SourcePage)) diff --git a/ui/src/status/actions/index.js b/ui/src/status/actions/index.js deleted file mode 100644 index 0aa93af113..0000000000 --- a/ui/src/status/actions/index.js +++ /dev/null @@ -1,49 +0,0 @@ -// he is a library for safely encoding and decoding HTML Entities -import he from 'he' - -import {fetchJSONFeed as fetchJSONFeedAJAX} from 'src/status/apis' - -import {notify} from 'src/shared/actions/notifications' -import {notifyJSONFeedFailed} from 'src/shared/copy/notifications' - -import * as actionTypes from 'src/status/constants/actionTypes' - -const fetchJSONFeedRequested = () => ({ - type: actionTypes.FETCH_JSON_FEED_REQUESTED, -}) - -const fetchJSONFeedCompleted = data => ({ - type: actionTypes.FETCH_JSON_FEED_COMPLETED, - payload: {data}, -}) - -const fetchJSONFeedFailed = () => ({ - type: actionTypes.FETCH_JSON_FEED_FAILED, -}) - -export const fetchJSONFeedAsync = url => async dispatch => { - dispatch(fetchJSONFeedRequested()) - try { - const {data} = await fetchJSONFeedAJAX(url) - // data could be from a webpage, and thus would be HTML - if (typeof data === 'string' || !data) { - dispatch(fetchJSONFeedFailed()) - } else { - // decode HTML entities from response text - const decodedData = { - ...data, - items: data.items.map(item => { - item.title = he.decode(item.title) - item.content_text = he.decode(item.content_text) - return item - }), - } - - dispatch(fetchJSONFeedCompleted(decodedData)) - } - } catch (error) { - console.error(error) - dispatch(fetchJSONFeedFailed()) - dispatch(notify(notifyJSONFeedFailed(url))) - } -} diff --git a/ui/src/status/actions/index.ts b/ui/src/status/actions/index.ts index 40b8cb3f88..b8e37416e9 100644 --- a/ui/src/status/actions/index.ts +++ b/ui/src/status/actions/index.ts @@ -7,6 +7,7 @@ import {notify} from 'src/shared/actions/notifications' import {notifyJSONFeedFailed} from 'src/shared/copy/notifications' import {JSONFeedData} from 'src/types' +import {AxiosResponse} from 'axios' export enum ActionTypes { FETCH_JSON_FEED_REQUESTED = 'FETCH_JSON_FEED_REQUESTED', @@ -52,7 +53,7 @@ export const fetchJSONFeedAsync = (url: string) => async ( ): Promise => { dispatch(fetchJSONFeedRequested()) try { - const {data} = await fetchJSONFeedAJAX(url) + const {data} = (await fetchJSONFeedAJAX(url)) as AxiosResponse // data could be from a webpage, and thus would be HTML if (typeof data === 'string' || !data) { dispatch(fetchJSONFeedFailed()) diff --git a/ui/src/status/apis/index.ts b/ui/src/status/apis/index.ts index 7bb8642f16..1ac19fa75d 100644 --- a/ui/src/status/apis/index.ts +++ b/ui/src/status/apis/index.ts @@ -1,9 +1,10 @@ import AJAX from 'src/utils/ajax' +import {JSONFeedData} from 'src/types' const excludeBasepath = true // don't prefix route of external link with basepath/ -export const fetchJSONFeed = url => - AJAX( +export const fetchJSONFeed = (url: string) => + AJAX( { method: 'GET', url, diff --git a/ui/src/status/components/GettingStarted.js b/ui/src/status/components/GettingStarted.js deleted file mode 100644 index a3d1d179ef..0000000000 --- a/ui/src/status/components/GettingStarted.js +++ /dev/null @@ -1,101 +0,0 @@ -import React, {Component} from 'react' - -import FancyScrollbar from 'shared/components/FancyScrollbar' -import {ErrorHandling} from 'src/shared/decorators/errors' - -@ErrorHandling -class GettingStarted extends Component { - constructor(props) { - super(props) - } - - render() { - return ( - -
-
-
- Welcome to Chronograf! -
-

Follow the links below to explore Chronograf’s features.

-
-
-

- Install the TICK Stack -
Save some time and use this handy tool to install the rest - of the stack: -

-

- - TICK Sandbox - -

-
- -
-

- Questions & Comments -

-

- If you have any product feedback please open a GitHub issue and - we'll take a look. For any questions or other issues try posting - on our  - - Community Forum - . -

-
-
-
- ) - } -} - -export default GettingStarted diff --git a/ui/src/status/components/JSONFeedReader.js b/ui/src/status/components/JSONFeedReader.js deleted file mode 100644 index a5adbf60c6..0000000000 --- a/ui/src/status/components/JSONFeedReader.js +++ /dev/null @@ -1,61 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' - -import moment from 'moment' - -const JSONFeedReader = ({data}) => - data && data.items ? ( -
- {data.items - ? data.items.map( - ({ - id, - date_published: datePublished, - url, - title, - author: {name}, - image, - content_text: contentText, - }) => ( -
-
- {`${moment(datePublished).format('MMM DD')}`} -
-
- -
{title}
-
- by {name} -
-
- {image ? : null} -

{contentText}

-
-
- ) - ) - : null} -
- ) : null - -const {arrayOf, shape, string} = PropTypes - -JSONFeedReader.propTypes = { - data: shape({ - items: arrayOf( - shape({ - author: shape({ - name: string.isRequired, - }).isRequired, - content_text: string.isRequired, - date_published: string.isRequired, - id: string.isRequired, - image: string, - title: string.isRequired, - url: string.isRequired, - }) - ), - }).isRequired, -} - -export default JSONFeedReader diff --git a/ui/src/status/components/NewsFeed.js b/ui/src/status/components/NewsFeed.js deleted file mode 100644 index 8aa84fb77f..0000000000 --- a/ui/src/status/components/NewsFeed.js +++ /dev/null @@ -1,92 +0,0 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' -import {connect} from 'react-redux' -import {bindActionCreators} from 'redux' - -import {fetchJSONFeedAsync} from 'src/status/actions' - -import FancyScrollbar from 'shared/components/FancyScrollbar' -import JSONFeedReader from 'src/status/components/JSONFeedReader' -import {ErrorHandling} from 'src/shared/decorators/errors' - -@ErrorHandling -class NewsFeed extends Component { - constructor(props) { - super(props) - } - - // TODO: implement shouldComponentUpdate based on fetching conditions - - render() { - const {hasCompletedFetchOnce, isFetching, isFailed, data} = this.props - - if (!hasCompletedFetchOnce) { - return isFailed ? ( -
-

Failed to load News Feed

-
- ) : ( - // TODO: Factor this out of here and AutoRefresh -
-
-
- ) - } - - return ( - - {isFetching ? ( - // TODO: Factor this out of here and AutoRefresh -
-
-
-
-
- ) : null} - {isFailed ? ( -
-

Failed to refresh News Feed

-
- ) : null} - - - ) - } - - // TODO: implement interval polling a la AutoRefresh - componentDidMount() { - const {statusFeedURL, fetchJSONFeed} = this.props - - fetchJSONFeed(statusFeedURL) - } -} - -const {bool, func, shape, string} = PropTypes - -NewsFeed.propTypes = { - hasCompletedFetchOnce: bool.isRequired, - isFetching: bool.isRequired, - isFailed: bool.isRequired, - data: shape(), - fetchJSONFeed: func.isRequired, - statusFeedURL: string, -} - -const mapStateToProps = ({ - links: { - external: {statusFeed: statusFeedURL}, - }, - JSONFeed: {hasCompletedFetchOnce, isFetching, isFailed, data}, -}) => ({ - hasCompletedFetchOnce, - isFetching, - isFailed, - data, - statusFeedURL, -}) - -const mapDispatchToProps = dispatch => ({ - fetchJSONFeed: bindActionCreators(fetchJSONFeedAsync, dispatch), -}) - -export default connect(mapStateToProps, mapDispatchToProps)(NewsFeed) diff --git a/ui/src/status/constants/actionTypes.js b/ui/src/status/constants/actionTypes.js deleted file mode 100644 index d49669bb2d..0000000000 --- a/ui/src/status/constants/actionTypes.js +++ /dev/null @@ -1,6 +0,0 @@ -export const SET_STATUS_PAGE_AUTOREFRESH = 'SET_STATUS_PAGE_AUTOREFRESH' -export const SET_STATUS_PAGE_TIME_RANGE = 'SET_STATUS_PAGE_TIME_RANGE' - -export const FETCH_JSON_FEED_REQUESTED = 'FETCH_JSON_FEED_REQUESTED' -export const FETCH_JSON_FEED_COMPLETED = 'FETCH_JSON_FEED_COMPLETED' -export const FETCH_JSON_FEED_FAILED = 'FETCH_JSON_FEED_FAILED' diff --git a/ui/src/status/constants/index.js b/ui/src/status/constants/index.js deleted file mode 100644 index 01421d10c4..0000000000 --- a/ui/src/status/constants/index.js +++ /dev/null @@ -1 +0,0 @@ -export const RECENT_ALERTS_LIMIT = 30 diff --git a/ui/src/status/containers/StatusPage.js b/ui/src/status/containers/StatusPage.js deleted file mode 100644 index 157e7a6581..0000000000 --- a/ui/src/status/containers/StatusPage.js +++ /dev/null @@ -1,113 +0,0 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' -import {connect} from 'react-redux' - -import SourceIndicator from 'shared/components/SourceIndicator' -import FancyScrollbar from 'shared/components/FancyScrollbar' -import LayoutRenderer from 'shared/components/LayoutRenderer' - -import {fixtureStatusPageCells} from 'src/status/fixtures' -import {ErrorHandling} from 'src/shared/decorators/errors' -import { - TEMP_VAR_DASHBOARD_TIME, - TEMP_VAR_UPPER_DASHBOARD_TIME, -} from 'src/shared/constants' - -@ErrorHandling -class StatusPage extends Component { - constructor(props) { - super(props) - - this.state = { - cells: fixtureStatusPageCells, - } - } - - render() { - const {source, autoRefresh, timeRange} = this.props - const {cells} = this.state - - const dashboardTime = { - id: 'dashtime', - tempVar: TEMP_VAR_DASHBOARD_TIME, - type: 'constant', - values: [ - { - value: timeRange.lower, - type: 'constant', - selected: true, - }, - ], - } - - const upperDashboardTime = { - id: 'upperdashtime', - tempVar: TEMP_VAR_UPPER_DASHBOARD_TIME, - type: 'constant', - values: [ - { - value: 'now()', - type: 'constant', - selected: true, - }, - ], - } - - const templates = [dashboardTime, upperDashboardTime] - - return ( -
-
-
-
-

Status

-
-
- -
-
-
- -
- {cells.length ? ( - - ) : ( - Loading Status Page... - )} -
-
-
- ) - } -} - -const {number, shape, string} = PropTypes - -StatusPage.propTypes = { - source: shape({ - name: string.isRequired, - links: shape({ - proxy: string.isRequired, - }).isRequired, - }).isRequired, - autoRefresh: number.isRequired, - timeRange: shape({ - lower: string.isRequired, - }).isRequired, -} - -const mapStateToProps = ({statusUI: {autoRefresh, timeRange}}) => ({ - autoRefresh, - timeRange, -}) - -export default connect(mapStateToProps, null)(StatusPage) diff --git a/ui/src/status/containers/StatusPage.tsx b/ui/src/status/containers/StatusPage.tsx index 348f69b1e9..6492ee48b7 100644 --- a/ui/src/status/containers/StatusPage.tsx +++ b/ui/src/status/containers/StatusPage.tsx @@ -1,9 +1,10 @@ import React, {Component} from 'react' -import {connect} from 'react-redux' import SourceIndicator from 'src/shared/components/SourceIndicator' import FancyScrollbar from 'src/shared/components/FancyScrollbar' import LayoutRenderer from 'src/shared/components/LayoutRenderer' +import {STATUS_PAGE_TIME_RANGE} from 'src/shared/data/timeRanges' +import {AUTOREFRESH_DEFAULT} from 'src/shared/constants' import {fixtureStatusPageCells} from 'src/status/fixtures' import {ErrorHandling} from 'src/shared/decorators/errors' @@ -11,7 +12,7 @@ import { TEMP_VAR_DASHBOARD_TIME, TEMP_VAR_UPPER_DASHBOARD_TIME, } from 'src/shared/constants' -import {Source, TimeRange, Cell} from 'src/types' +import {Source, Cell} from 'src/types' interface State { cells: Cell[] @@ -19,10 +20,11 @@ interface State { interface Props { source: Source - autoRefresh: number - timeRange: TimeRange } +const autoRefresh = AUTOREFRESH_DEFAULT +const timeRange = STATUS_PAGE_TIME_RANGE + @ErrorHandling class StatusPage extends Component { constructor(props: Props) { @@ -34,7 +36,7 @@ class StatusPage extends Component { } public render() { - const {source, autoRefresh, timeRange} = this.props + const {source} = this.props const {cells} = this.state const dashboardTime = { @@ -100,9 +102,4 @@ class StatusPage extends Component { } } -const mstp = ({statusUI: {autoRefresh, timeRange}}) => ({ - autoRefresh, - timeRange, -}) - -export default connect(mstp, null)(StatusPage) +export default StatusPage diff --git a/ui/src/status/fixtures.ts b/ui/src/status/fixtures.ts index 67bdcbf92d..5bf0b171df 100644 --- a/ui/src/status/fixtures.ts +++ b/ui/src/status/fixtures.ts @@ -1,9 +1,40 @@ import {DEFAULT_LINE_COLORS} from 'src/shared/constants/graphColorPalettes' import {TEMP_VAR_DASHBOARD_TIME} from 'src/shared/constants' +import {NEW_DEFAULT_DASHBOARD_CELL} from 'src/dashboards/constants/index' +import {DEFAULT_AXIS} from 'src/dashboards/constants/cellEditor' +import {Cell, CellQuery} from 'src/types' +import {CellType} from 'src/types/dashboard' -export const fixtureStatusPageCells = [ +const emptyQuery: CellQuery = { + query: '', + source: '', + queryConfig: { + database: '', + measurement: '', + retentionPolicy: '', + fields: [], + tags: {}, + groupBy: {}, + areTagsAccepted: false, + rawText: null, + range: null, + }, +} + +const emptyAxes = { + axes: { + x: DEFAULT_AXIS, + y: DEFAULT_AXIS, + y2: DEFAULT_AXIS, + }, +} + +export const fixtureStatusPageCells: Cell[] = [ { + ...NEW_DEFAULT_DASHBOARD_CELL, + ...emptyAxes, i: 'alerts-bar-graph', + type: CellType.Bar, isWidget: false, x: 0, y: 0, @@ -15,7 +46,7 @@ export const fixtureStatusPageCells = [ queries: [ { query: `SELECT count("value") AS "count_value" FROM "chronograf"."autogen"."alerts" WHERE time > ${TEMP_VAR_DASHBOARD_TIME} GROUP BY time(1d)`, - label: 'Events', + source: '', queryConfig: { database: 'chronograf', measurement: 'alerts', @@ -44,90 +75,56 @@ export const fixtureStatusPageCells = [ }, }, ], - type: 'bar', links: { self: '/chronograf/v1/status/23/cells/c-bar-graphs-fly', }, }, { + ...NEW_DEFAULT_DASHBOARD_CELL, + ...emptyAxes, i: 'recent-alerts', + type: CellType.Alerts, isWidget: true, name: 'Alerts – Last 30 Days', - type: 'alerts', x: 0, y: 5, w: 6.5, h: 6, legend: {}, - queries: [ - { - query: '', - queryConfig: { - database: '', - measurement: '', - retentionPolicy: '', - fields: [], - tags: {}, - groupBy: {}, - areTagsAccepted: false, - rawText: null, - range: null, - }, - }, - ], + queries: [emptyQuery], + colors: DEFAULT_LINE_COLORS, + links: {self: ''}, }, { + ...NEW_DEFAULT_DASHBOARD_CELL, + ...emptyAxes, i: 'news-feed', + type: CellType.News, isWidget: true, name: 'News Feed', - type: 'news', x: 6.5, y: 5, w: 3, h: 6, legend: {}, - queries: [ - { - query: '', - queryConfig: { - database: '', - measurement: '', - retentionPolicy: '', - fields: [], - tags: {}, - groupBy: {}, - areTagsAccepted: false, - rawText: null, - range: null, - }, - }, - ], + queries: [emptyQuery], + colors: DEFAULT_LINE_COLORS, + links: {self: ''}, }, { + ...NEW_DEFAULT_DASHBOARD_CELL, + ...emptyAxes, i: 'getting-started', + type: CellType.Guide, isWidget: true, name: 'Getting Started', - type: 'guide', x: 9.5, y: 5, w: 2.5, h: 6, legend: {}, - queries: [ - { - query: '', - queryConfig: { - database: '', - measurement: '', - retentionPolicy: '', - fields: [], - tags: {}, - groupBy: {}, - areTagsAccepted: false, - rawText: null, - range: null, - }, - }, - ], + queries: [emptyQuery], + colors: DEFAULT_LINE_COLORS, + links: {self: ''}, }, ] diff --git a/ui/src/status/reducers/JSONFeed.js b/ui/src/status/reducers/JSONFeed.js deleted file mode 100644 index 76d52d9593..0000000000 --- a/ui/src/status/reducers/JSONFeed.js +++ /dev/null @@ -1,43 +0,0 @@ -import * as actionTypes from 'src/status/constants/actionTypes' - -const initialState = { - hasCompletedFetchOnce: false, - isFetching: false, - isFailed: false, - data: null, -} - -const JSONFeedReducer = (state = initialState, action) => { - switch (action.type) { - case actionTypes.FETCH_JSON_FEED_REQUESTED: { - return {...state, isFetching: true, isFailed: false} - } - - case actionTypes.FETCH_JSON_FEED_COMPLETED: { - const {data} = action.payload - - return { - ...state, - hasCompletedFetchOnce: true, - isFetching: false, - isFailed: false, - data, - } - } - - case actionTypes.FETCH_JSON_FEED_FAILED: { - return { - ...state, - isFetching: false, - isFailed: true, - data: null, - } - } - - default: { - return state - } - } -} - -export default JSONFeedReducer diff --git a/ui/src/status/reducers/index.js b/ui/src/status/reducers/index.js deleted file mode 100644 index d0941c6546..0000000000 --- a/ui/src/status/reducers/index.js +++ /dev/null @@ -1,7 +0,0 @@ -import statusUI from './ui' -import JSONFeed from './JSONFeed' - -export default { - statusUI, - JSONFeed, -} diff --git a/ui/src/status/reducers/ui.js b/ui/src/status/reducers/ui.js deleted file mode 100644 index a2a8ac7cd8..0000000000 --- a/ui/src/status/reducers/ui.js +++ /dev/null @@ -1,36 +0,0 @@ -import {AUTOREFRESH_DEFAULT} from 'shared/constants' -import {timeRanges} from 'shared/data/timeRanges' - -import * as actionTypes from 'src/status/constants/actionTypes' - -const {lower, upper} = timeRanges.find(tr => tr.lower === 'now() - 30d') - -const initialState = { - autoRefresh: AUTOREFRESH_DEFAULT, - timeRange: {lower, upper}, -} - -const statusUI = (state = initialState, action) => { - switch (action.type) { - case actionTypes.SET_STATUS_PAGE_AUTOREFRESH: { - const {milliseconds} = action.payload - - return { - ...state, - autoRefresh: milliseconds, - } - } - - case actionTypes.SET_STATUS_PAGE_TIME_RANGE: { - const {timeRange} = action.payload - - return {...state, timeRange} - } - - default: { - return state - } - } -} - -export default statusUI diff --git a/ui/src/types/dashboard.ts b/ui/src/types/dashboard.ts index 0f3a31ea28..cd2141cb1b 100644 --- a/ui/src/types/dashboard.ts +++ b/ui/src/types/dashboard.ts @@ -3,12 +3,12 @@ import {ColorString} from 'src/types/colors' import {Template} from 'src/types/tempVars' export interface Axis { - bounds: [string, string] label: string prefix: string suffix: string base: string scale: string + bounds?: [string, string] } export type TimeSeriesValue = string | number | null | undefined @@ -76,6 +76,7 @@ export interface Cell { decimalPlaces: DecimalPlaces links: CellLinks legend: Legend + isWidget?: boolean } export enum CellType { diff --git a/ui/src/types/index.ts b/ui/src/types/index.ts index 60da481c9f..d2443a5205 100644 --- a/ui/src/types/index.ts +++ b/ui/src/types/index.ts @@ -40,6 +40,7 @@ import { DygraphClass, DygraphData, } from './dygraphs' +import {JSONFeedData} from './status' export { Me, @@ -96,4 +97,5 @@ export { SchemaFilter, RemoteDataState, URLQueryParams, + JSONFeedData, } From 93e72efb08dee3592cdb3cab1d04a75deef6c14c Mon Sep 17 00:00:00 2001 From: Iris Scholten Date: Fri, 15 Jun 2018 10:11:13 -0700 Subject: [PATCH 03/22] Convert Tickscript tree to typescript --- .../{LogItemHTTP.js => LogItemHTTP.tsx} | 24 ++--- ...gItemHTTPError.js => LogItemHTTPError.tsx} | 22 ++-- ...luxDBDebug.js => LogItemInfluxDBDebug.tsx} | 22 ++-- ...itorDebug.js => LogItemKapacitorDebug.tsx} | 21 ++-- ...itorError.js => LogItemKapacitorError.tsx} | 21 ++-- .../components/LogItemKapacitorPoint.js | 51 --------- .../components/LogItemKapacitorPoint.tsx | 55 ++++++++++ .../{LogItemSession.js => LogItemSession.tsx} | 21 ++-- .../{LogsTable.js => LogsTable.tsx} | 24 ++--- .../{LogsTableRow.js => LogsTableRow.tsx} | 22 ++-- ui/src/kapacitor/components/Tickscript.js | 89 --------------- ui/src/kapacitor/components/Tickscript.tsx | 101 ++++++++++++++++++ ...ckscriptEditor.js => TickscriptEditor.tsx} | 28 ++--- ...Console.js => TickscriptEditorConsole.tsx} | 20 ++-- .../components/TickscriptEditorControls.js | 48 --------- .../components/TickscriptEditorControls.tsx | 67 ++++++++++++ .../{TickscriptID.js => TickscriptID.tsx} | 28 +++-- ui/src/kapacitor/components/TickscriptType.js | 30 ------ .../kapacitor/components/TickscriptType.tsx | 40 +++++++ .../kapacitor/containers/TickscriptPage.tsx | 14 +-- ui/src/types/kapacitor.ts | 19 +++- 21 files changed, 389 insertions(+), 378 deletions(-) rename ui/src/kapacitor/components/{LogItemHTTP.js => LogItemHTTP.tsx} (58%) rename ui/src/kapacitor/components/{LogItemHTTPError.js => LogItemHTTPError.tsx} (63%) rename ui/src/kapacitor/components/{LogItemInfluxDBDebug.js => LogItemInfluxDBDebug.tsx} (65%) rename ui/src/kapacitor/components/{LogItemKapacitorDebug.js => LogItemKapacitorDebug.tsx} (64%) rename ui/src/kapacitor/components/{LogItemKapacitorError.js => LogItemKapacitorError.tsx} (64%) delete mode 100644 ui/src/kapacitor/components/LogItemKapacitorPoint.js create mode 100644 ui/src/kapacitor/components/LogItemKapacitorPoint.tsx rename ui/src/kapacitor/components/{LogItemSession.js => LogItemSession.tsx} (58%) rename ui/src/kapacitor/components/{LogsTable.js => LogsTable.tsx} (63%) rename ui/src/kapacitor/components/{LogsTableRow.js => LogsTableRow.tsx} (87%) delete mode 100644 ui/src/kapacitor/components/Tickscript.js create mode 100644 ui/src/kapacitor/components/Tickscript.tsx rename ui/src/kapacitor/components/{TickscriptEditor.js => TickscriptEditor.tsx} (74%) rename ui/src/kapacitor/components/{TickscriptEditorConsole.js => TickscriptEditorConsole.tsx} (68%) delete mode 100644 ui/src/kapacitor/components/TickscriptEditorControls.js create mode 100644 ui/src/kapacitor/components/TickscriptEditorControls.tsx rename ui/src/kapacitor/components/{TickscriptID.js => TickscriptID.tsx} (59%) delete mode 100644 ui/src/kapacitor/components/TickscriptType.js create mode 100644 ui/src/kapacitor/components/TickscriptType.tsx diff --git a/ui/src/kapacitor/components/LogItemHTTP.js b/ui/src/kapacitor/components/LogItemHTTP.tsx similarity index 58% rename from ui/src/kapacitor/components/LogItemHTTP.js rename to ui/src/kapacitor/components/LogItemHTTP.tsx index 89607e1518..b0e64c206f 100644 --- a/ui/src/kapacitor/components/LogItemHTTP.js +++ b/ui/src/kapacitor/components/LogItemHTTP.tsx @@ -1,7 +1,12 @@ -import React from 'react' -import PropTypes from 'prop-types' +import React, {SFC} from 'react' -const LogItemHTTP = ({logItem}) => ( +import {LogItem} from 'src/types/kapacitor' + +interface Props { + logItem: LogItem +} + +const LogItemHTTP: SFC = ({logItem}) => (
@@ -16,17 +21,4 @@ const LogItemHTTP = ({logItem}) => (
) -const {shape, string} = PropTypes - -LogItemHTTP.propTypes = { - logItem: shape({ - lvl: string.isRequired, - ts: string.isRequired, - method: string.isRequired, - username: string.isRequired, - host: string.isRequired, - duration: string.isRequired, - }), -} - export default LogItemHTTP diff --git a/ui/src/kapacitor/components/LogItemHTTPError.js b/ui/src/kapacitor/components/LogItemHTTPError.tsx similarity index 63% rename from ui/src/kapacitor/components/LogItemHTTPError.js rename to ui/src/kapacitor/components/LogItemHTTPError.tsx index ecaba7233c..4803e8d020 100644 --- a/ui/src/kapacitor/components/LogItemHTTPError.js +++ b/ui/src/kapacitor/components/LogItemHTTPError.tsx @@ -1,7 +1,12 @@ -import React from 'react' -import PropTypes from 'prop-types' +import React, {SFC} from 'react' -const LogItemHTTPError = ({logItem}) => ( +import {LogItem} from 'src/types/kapacitor' + +interface Props { + logItem: LogItem +} + +const LogItemHTTPError: SFC = ({logItem}) => (
@@ -16,15 +21,4 @@ const LogItemHTTPError = ({logItem}) => (
) -const {shape, string} = PropTypes - -LogItemHTTPError.propTypes = { - logItem: shape({ - key: string.isRequired, - lvl: string.isRequired, - ts: string.isRequired, - msg: string.isRequired, - }), -} - export default LogItemHTTPError diff --git a/ui/src/kapacitor/components/LogItemInfluxDBDebug.js b/ui/src/kapacitor/components/LogItemInfluxDBDebug.tsx similarity index 65% rename from ui/src/kapacitor/components/LogItemInfluxDBDebug.js rename to ui/src/kapacitor/components/LogItemInfluxDBDebug.tsx index 0a2a833ed4..3ba2e37a01 100644 --- a/ui/src/kapacitor/components/LogItemInfluxDBDebug.js +++ b/ui/src/kapacitor/components/LogItemInfluxDBDebug.tsx @@ -1,7 +1,12 @@ -import React from 'react' -import PropTypes from 'prop-types' +import React, {SFC} from 'react' -const LogItemInfluxDBDebug = ({logItem}) => ( +import {LogItem} from 'src/types/kapacitor' + +interface Props { + logItem: LogItem +} + +const LogItemInfluxDBDebug: SFC = ({logItem}) => (
@@ -20,15 +25,4 @@ const LogItemInfluxDBDebug = ({logItem}) => (
) -const {shape, string} = PropTypes - -LogItemInfluxDBDebug.propTypes = { - logItem: shape({ - lvl: string.isRequired, - ts: string.isRequired, - msg: string.isRequired, - cluster: string.isRequired, - }), -} - export default LogItemInfluxDBDebug diff --git a/ui/src/kapacitor/components/LogItemKapacitorDebug.js b/ui/src/kapacitor/components/LogItemKapacitorDebug.tsx similarity index 64% rename from ui/src/kapacitor/components/LogItemKapacitorDebug.js rename to ui/src/kapacitor/components/LogItemKapacitorDebug.tsx index 5d550d4efd..9694cb71ae 100644 --- a/ui/src/kapacitor/components/LogItemKapacitorDebug.js +++ b/ui/src/kapacitor/components/LogItemKapacitorDebug.tsx @@ -1,7 +1,12 @@ -import React from 'react' -import PropTypes from 'prop-types' +import React, {SFC} from 'react' -const LogItemKapacitorDebug = ({logItem}) => ( +import {LogItem} from 'src/types/kapacitor' + +interface Props { + logItem: LogItem +} + +const LogItemKapacitorDebug: SFC = ({logItem}) => (
@@ -16,14 +21,4 @@ const LogItemKapacitorDebug = ({logItem}) => (
) -const {shape, string} = PropTypes - -LogItemKapacitorDebug.propTypes = { - logItem: shape({ - lvl: string.isRequired, - ts: string.isRequired, - msg: string.isRequired, - }), -} - export default LogItemKapacitorDebug diff --git a/ui/src/kapacitor/components/LogItemKapacitorError.js b/ui/src/kapacitor/components/LogItemKapacitorError.tsx similarity index 64% rename from ui/src/kapacitor/components/LogItemKapacitorError.js rename to ui/src/kapacitor/components/LogItemKapacitorError.tsx index d07c5b7452..cb37daa0f4 100644 --- a/ui/src/kapacitor/components/LogItemKapacitorError.js +++ b/ui/src/kapacitor/components/LogItemKapacitorError.tsx @@ -1,7 +1,12 @@ -import React from 'react' -import PropTypes from 'prop-types' +import React, {SFC} from 'react' -const LogItemKapacitorError = ({logItem}) => ( +import {LogItem} from 'src/types/kapacitor' + +interface Props { + logItem: LogItem +} + +const LogItemKapacitorError: SFC = ({logItem}) => (
@@ -16,14 +21,4 @@ const LogItemKapacitorError = ({logItem}) => (
) -const {shape, string} = PropTypes - -LogItemKapacitorError.propTypes = { - logItem: shape({ - lvl: string.isRequired, - ts: string.isRequired, - msg: string.isRequired, - }), -} - export default LogItemKapacitorError diff --git a/ui/src/kapacitor/components/LogItemKapacitorPoint.js b/ui/src/kapacitor/components/LogItemKapacitorPoint.js deleted file mode 100644 index 205d0eb572..0000000000 --- a/ui/src/kapacitor/components/LogItemKapacitorPoint.js +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' - -const renderKeysAndValues = (object, name) => { - if (!object) { - return -- - } - - const sortedObjKeys = Object.keys(object).sort() - - return ( -
-

{`${sortedObjKeys.length} ${name}`}

-
- {sortedObjKeys.map(objKey => ( -
- {objKey}: {object[objKey]} -
- ))} -
-
- ) -} -const LogItemKapacitorPoint = ({logItem}) => ( -
-
-
-
{logItem.ts}
-
-
-
Kapacitor Point
-
- {renderKeysAndValues(logItem.tag, 'Tags')} - {renderKeysAndValues(logItem.field, 'Fields')} -
-
-
-) - -const {shape, string} = PropTypes - -LogItemKapacitorPoint.propTypes = { - logItem: shape({ - lvl: string.isRequired, - ts: string.isRequired, - tag: shape.isRequired, - field: shape.isRequired, - }), -} - -export default LogItemKapacitorPoint diff --git a/ui/src/kapacitor/components/LogItemKapacitorPoint.tsx b/ui/src/kapacitor/components/LogItemKapacitorPoint.tsx new file mode 100644 index 0000000000..4e0c054e9b --- /dev/null +++ b/ui/src/kapacitor/components/LogItemKapacitorPoint.tsx @@ -0,0 +1,55 @@ +import React, {PureComponent} from 'react' +import _ from 'lodash' + +import {ErrorHandling} from 'src/shared/decorators/errors' + +import {LogItem} from 'src/types/kapacitor' + +interface Props { + logItem: LogItem +} + +@ErrorHandling +class LogItemKapacitorPoint extends PureComponent { + public render() { + const {logItem} = this.props + return ( +
+
+
+
{logItem.ts}
+
+
+
Kapacitor Point
+
+ {this.renderKeysAndValues(logItem.tag, 'Tags')} + {this.renderKeysAndValues(logItem.field, 'Fields')} +
+
+
+ ) + } + + private renderKeysAndValues = (object: any, name: string) => { + if (_.isEmpty(object)) { + return -- + } + + const sortedObjKeys = Object.keys(object).sort() + + return ( +
+

{`${sortedObjKeys.length} ${name}`}

+
+ {sortedObjKeys.map(objKey => ( +
+ {objKey}: {object[objKey]} +
+ ))} +
+
+ ) + } +} + +export default LogItemKapacitorPoint diff --git a/ui/src/kapacitor/components/LogItemSession.js b/ui/src/kapacitor/components/LogItemSession.tsx similarity index 58% rename from ui/src/kapacitor/components/LogItemSession.js rename to ui/src/kapacitor/components/LogItemSession.tsx index b3f45e9fb5..cd62042734 100644 --- a/ui/src/kapacitor/components/LogItemSession.js +++ b/ui/src/kapacitor/components/LogItemSession.tsx @@ -1,7 +1,12 @@ -import React from 'react' -import PropTypes from 'prop-types' +import React, {SFC} from 'react' -const LogItemSession = ({logItem}) => ( +import {LogItem} from 'src/types/kapacitor' + +interface Props { + logItem: LogItem +} + +const LogItemSession: SFC = ({logItem}) => (
@@ -13,14 +18,4 @@ const LogItemSession = ({logItem}) => (
) -const {shape, string} = PropTypes - -LogItemSession.propTypes = { - logItem: shape({ - lvl: string.isRequired, - ts: string.isRequired, - msg: string.isRequired, - }), -} - export default LogItemSession diff --git a/ui/src/kapacitor/components/LogsTable.js b/ui/src/kapacitor/components/LogsTable.tsx similarity index 63% rename from ui/src/kapacitor/components/LogsTable.js rename to ui/src/kapacitor/components/LogsTable.tsx index 96a1a75c59..083f432ec6 100644 --- a/ui/src/kapacitor/components/LogsTable.js +++ b/ui/src/kapacitor/components/LogsTable.tsx @@ -1,12 +1,17 @@ -import React from 'react' -import PropTypes from 'prop-types' +import React, {SFC} from 'react' import LogsTableRow from 'src/kapacitor/components/LogsTableRow' import FancyScrollbar from 'src/shared/components/FancyScrollbar' +import {LogItem} from 'src/types/kapacitor' + const numLogsToRender = 200 -const LogsTable = ({logs}) => ( +interface Props { + logs: LogItem[] +} + +const LogsTable: SFC = ({logs}) => (
{`${numLogsToRender} Most Recent Logs`} @@ -22,17 +27,4 @@ const LogsTable = ({logs}) => (
) -const {arrayOf, shape, string} = PropTypes - -LogsTable.propTypes = { - logs: arrayOf( - shape({ - key: string.isRequired, - ts: string.isRequired, - lvl: string.isRequired, - msg: string.isRequired, - }) - ).isRequired, -} - export default LogsTable diff --git a/ui/src/kapacitor/components/LogsTableRow.js b/ui/src/kapacitor/components/LogsTableRow.tsx similarity index 87% rename from ui/src/kapacitor/components/LogsTableRow.js rename to ui/src/kapacitor/components/LogsTableRow.tsx index 74dec4b69d..0a1dacaff9 100644 --- a/ui/src/kapacitor/components/LogsTableRow.js +++ b/ui/src/kapacitor/components/LogsTableRow.tsx @@ -1,5 +1,4 @@ -import React from 'react' -import PropTypes from 'prop-types' +import React, {SFC} from 'react' import LogItemSession from 'src/kapacitor/components/LogItemSession' import LogItemHTTP from 'src/kapacitor/components/LogItemHTTP' @@ -9,7 +8,13 @@ import LogItemKapacitorError from 'src/kapacitor/components/LogItemKapacitorErro import LogItemKapacitorDebug from 'src/kapacitor/components/LogItemKapacitorDebug' import LogItemInfluxDBDebug from 'src/kapacitor/components/LogItemInfluxDBDebug' -const LogsTableRow = ({logItem}) => { +import {LogItem} from 'src/types/kapacitor' + +interface Props { + logItem: LogItem +} + +const LogsTableRow: SFC = ({logItem}) => { if (logItem.service === 'sessions') { return } @@ -51,15 +56,4 @@ const LogsTableRow = ({logItem}) => { ) } -const {shape, string} = PropTypes - -LogsTableRow.propTypes = { - logItem: shape({ - key: string.isRequired, - ts: string.isRequired, - lvl: string.isRequired, - msg: string.isRequired, - }).isRequired, -} - export default LogsTableRow diff --git a/ui/src/kapacitor/components/Tickscript.js b/ui/src/kapacitor/components/Tickscript.js deleted file mode 100644 index 020fb98bc8..0000000000 --- a/ui/src/kapacitor/components/Tickscript.js +++ /dev/null @@ -1,89 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' - -import TickscriptHeader from 'src/kapacitor/components/TickscriptHeader' -import TickscriptEditor from 'src/kapacitor/components/TickscriptEditor' -import TickscriptEditorControls from 'src/kapacitor/components/TickscriptEditorControls' -import TickscriptEditorConsole from 'src/kapacitor/components/TickscriptEditorConsole' -import LogsTable from 'src/kapacitor/components/LogsTable' - -const Tickscript = ({ - onSave, - onExit, - task, - logs, - consoleMessage, - onSelectDbrps, - onChangeScript, - onChangeType, - onChangeID, - unsavedChanges, - isNewTickscript, - areLogsVisible, - areLogsEnabled, - onToggleLogsVisibility, -}) => ( -
- -
-
- - - -
- {areLogsVisible ? : null} -
-
-) - -const {arrayOf, bool, func, shape, string} = PropTypes - -Tickscript.propTypes = { - logs: arrayOf(shape()).isRequired, - onSave: func.isRequired, - onExit: func.isRequired, - source: shape({ - id: string, - }), - areLogsVisible: bool, - areLogsEnabled: bool, - onToggleLogsVisibility: func.isRequired, - task: shape({ - id: string, - script: string, - dbsrps: arrayOf(shape()), - }).isRequired, - onChangeScript: func.isRequired, - onSelectDbrps: func.isRequired, - consoleMessage: string, - onChangeType: func.isRequired, - onChangeID: func.isRequired, - isNewTickscript: bool.isRequired, - unsavedChanges: bool, -} - -export default Tickscript diff --git a/ui/src/kapacitor/components/Tickscript.tsx b/ui/src/kapacitor/components/Tickscript.tsx new file mode 100644 index 0000000000..96eb847e53 --- /dev/null +++ b/ui/src/kapacitor/components/Tickscript.tsx @@ -0,0 +1,101 @@ +import React, {PureComponent, MouseEvent, ChangeEvent} from 'react' + +import TickscriptHeader from 'src/kapacitor/components/TickscriptHeader' +import TickscriptEditor from 'src/kapacitor/components/TickscriptEditor' +import TickscriptEditorControls from 'src/kapacitor/components/TickscriptEditorControls' +import TickscriptEditorConsole from 'src/kapacitor/components/TickscriptEditorConsole' +import LogsTable from 'src/kapacitor/components/LogsTable' + +import {ErrorHandling} from 'src/shared/decorators/errors' + +import {Task} from 'src/types' +import {LogItem, DBRP} from 'src/types/kapacitor' + +interface Props { + logs: LogItem[] + onSave: () => void + onExit: () => void + areLogsVisible: boolean + areLogsEnabled: boolean + onToggleLogsVisibility: () => void + task: Task + onChangeScript: (tickscript: string) => void + onSelectDbrps: (dbrps: DBRP[]) => void + consoleMessage: string + onChangeType: (type: string) => (event: MouseEvent) => void + onChangeID: (e: ChangeEvent) => void + isNewTickscript: boolean + unsavedChanges: boolean +} + +@ErrorHandling +class Tickscript extends PureComponent { + public render() { + const { + onSave, + onExit, + task, + consoleMessage, + onSelectDbrps, + onChangeScript, + onChangeType, + onChangeID, + unsavedChanges, + isNewTickscript, + areLogsVisible, + areLogsEnabled, + onToggleLogsVisibility, + } = this.props + return ( +
+ +
+
+ + + +
+ {this.logsTable} +
+
+ ) + } + + private get style() { + const {areLogsVisible} = this.props + if (areLogsVisible) { + return {maxWidth: '50%'} + } + } + + private get logsTable() { + const {areLogsVisible, logs} = this.props + + if (areLogsVisible) { + return + } + } +} + +export default Tickscript diff --git a/ui/src/kapacitor/components/TickscriptEditor.js b/ui/src/kapacitor/components/TickscriptEditor.tsx similarity index 74% rename from ui/src/kapacitor/components/TickscriptEditor.js rename to ui/src/kapacitor/components/TickscriptEditor.tsx index 77bb2ce54e..4a5b1a14d6 100644 --- a/ui/src/kapacitor/components/TickscriptEditor.js +++ b/ui/src/kapacitor/components/TickscriptEditor.tsx @@ -1,20 +1,24 @@ import React, {Component} from 'react' -import PropTypes from 'prop-types' + import {Controlled as CodeMirror} from 'react-codemirror2' import 'src/external/codemirror' + import {ErrorHandling} from 'src/shared/decorators/errors' +interface Props { + onChangeScript: (tickscript: string) => void + script: string +} + +const NOOP = () => {} + @ErrorHandling -class TickscriptEditor extends Component { +class TickscriptEditor extends Component { constructor(props) { super(props) } - updateCode = (_, __, script) => { - this.props.onChangeScript(script) - } - - render() { + public render() { const {script} = this.props const options = { @@ -31,17 +35,15 @@ class TickscriptEditor extends Component { value={script} onBeforeChange={this.updateCode} options={options} + onTouchStart={NOOP} />
) } -} -const {func, string} = PropTypes - -TickscriptEditor.propTypes = { - onChangeScript: func, - script: string, + private updateCode = (_, __, script) => { + this.props.onChangeScript(script) + } } export default TickscriptEditor diff --git a/ui/src/kapacitor/components/TickscriptEditorConsole.js b/ui/src/kapacitor/components/TickscriptEditorConsole.tsx similarity index 68% rename from ui/src/kapacitor/components/TickscriptEditorConsole.js rename to ui/src/kapacitor/components/TickscriptEditorConsole.tsx index 29841d02e7..b7b10c44ed 100644 --- a/ui/src/kapacitor/components/TickscriptEditorConsole.js +++ b/ui/src/kapacitor/components/TickscriptEditorConsole.tsx @@ -1,7 +1,14 @@ -import React from 'react' -import PropTypes from 'prop-types' +import React, {SFC} from 'react' -const TickscriptEditorConsole = ({consoleMessage, unsavedChanges}) => { +interface Props { + consoleMessage: string + unsavedChanges: boolean +} + +const TickscriptEditorConsole: SFC = ({ + consoleMessage, + unsavedChanges, +}) => { let consoleOutput = 'TICKscript is valid' let consoleClass = 'tickscript-console--valid' @@ -20,11 +27,4 @@ const TickscriptEditorConsole = ({consoleMessage, unsavedChanges}) => { ) } -const {bool, string} = PropTypes - -TickscriptEditorConsole.propTypes = { - consoleMessage: string, - unsavedChanges: bool, -} - export default TickscriptEditorConsole diff --git a/ui/src/kapacitor/components/TickscriptEditorControls.js b/ui/src/kapacitor/components/TickscriptEditorControls.js deleted file mode 100644 index d8b123b4d1..0000000000 --- a/ui/src/kapacitor/components/TickscriptEditorControls.js +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import TickscriptType from 'src/kapacitor/components/TickscriptType' -import MultiSelectDBDropdown from 'shared/components/MultiSelectDBDropdown' -import TickscriptID, { - TickscriptStaticID, -} from 'src/kapacitor/components/TickscriptID' - -const addName = list => list.map(l => ({...l, name: `${l.db}.${l.rp}`})) - -const TickscriptEditorControls = ({ - isNewTickscript, - onSelectDbrps, - onChangeType, - onChangeID, - task, -}) => ( -
- {isNewTickscript ? ( - - ) : ( - - )} -
- - -
-
-) - -const {arrayOf, bool, func, shape, string} = PropTypes - -TickscriptEditorControls.propTypes = { - isNewTickscript: bool.isRequired, - onSelectDbrps: func.isRequired, - onChangeType: func.isRequired, - onChangeID: func.isRequired, - task: shape({ - id: string, - script: string, - dbsrps: arrayOf(shape()), - }).isRequired, -} - -export default TickscriptEditorControls diff --git a/ui/src/kapacitor/components/TickscriptEditorControls.tsx b/ui/src/kapacitor/components/TickscriptEditorControls.tsx new file mode 100644 index 0000000000..079b949229 --- /dev/null +++ b/ui/src/kapacitor/components/TickscriptEditorControls.tsx @@ -0,0 +1,67 @@ +import React, {Component, MouseEvent, ChangeEvent} from 'react' + +import TickscriptType from 'src/kapacitor/components/TickscriptType' +import MultiSelectDBDropdown from 'src/shared/components/MultiSelectDBDropdown' +import TickscriptID, { + TickscriptStaticID, +} from 'src/kapacitor/components/TickscriptID' + +import {Task} from 'src/types' +import {DBRP} from 'src/types/kapacitor' + +interface DBRPDropdownItem extends DBRP { + name: string +} + +interface Props { + isNewTickscript: boolean + onSelectDbrps: (dbrps: DBRP[]) => void + onChangeType: (type: string) => (event: MouseEvent) => void + onChangeID: (e: ChangeEvent) => void + task: Task +} + +class TickscriptEditorControls extends Component { + public render() { + const {onSelectDbrps, onChangeType, task} = this.props + return ( +
+ {this.tickscriptID} +
+ + +
+
+ ) + } + + private get tickscriptID() { + const {isNewTickscript, onChangeID, task} = this.props + + if (isNewTickscript) { + return + } + + return + } + + private get taskID() { + const { + task: {name, id}, + } = this.props + if (name) { + return name + } + return id + } + + private addName = (list: DBRP[]): DBRPDropdownItem[] => { + const listWithName = list.map(l => ({...l, name: `${l.db}.${l.rp}`})) + return listWithName + } +} + +export default TickscriptEditorControls diff --git a/ui/src/kapacitor/components/TickscriptID.js b/ui/src/kapacitor/components/TickscriptID.tsx similarity index 59% rename from ui/src/kapacitor/components/TickscriptID.js rename to ui/src/kapacitor/components/TickscriptID.tsx index 93750f2d58..59ea539906 100644 --- a/ui/src/kapacitor/components/TickscriptID.js +++ b/ui/src/kapacitor/components/TickscriptID.tsx @@ -1,14 +1,18 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' +import React, {Component, SFC, ChangeEvent} from 'react' import {ErrorHandling} from 'src/shared/decorators/errors' +interface TickscriptIDProps { + onChangeID: (e: ChangeEvent) => void + id: string +} + @ErrorHandling -class TickscriptID extends Component { +class TickscriptID extends Component { constructor(props) { super(props) } - render() { + public render() { const {onChangeID, id} = this.props return ( @@ -25,19 +29,11 @@ class TickscriptID extends Component { } } -export const TickscriptStaticID = ({id}) => ( +interface TickscriptStaticIDProps { + id: string +} +export const TickscriptStaticID: SFC = ({id}) => (

{id}

) -const {func, string} = PropTypes - -TickscriptID.propTypes = { - onChangeID: func.isRequired, - id: string.isRequired, -} - -TickscriptStaticID.propTypes = { - id: string.isRequired, -} - export default TickscriptID diff --git a/ui/src/kapacitor/components/TickscriptType.js b/ui/src/kapacitor/components/TickscriptType.js deleted file mode 100644 index f708be3c78..0000000000 --- a/ui/src/kapacitor/components/TickscriptType.js +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -const STREAM = 'stream' -const BATCH = 'batch' - -const TickscriptType = ({type, onChangeType}) => ( -
    -
  • - Stream -
  • -
  • - Batch -
  • -
-) - -const {string, func} = PropTypes - -TickscriptType.propTypes = { - type: string, - onChangeType: func, -} - -export default TickscriptType diff --git a/ui/src/kapacitor/components/TickscriptType.tsx b/ui/src/kapacitor/components/TickscriptType.tsx new file mode 100644 index 0000000000..105a660c53 --- /dev/null +++ b/ui/src/kapacitor/components/TickscriptType.tsx @@ -0,0 +1,40 @@ +import React, {PureComponent, MouseEvent} from 'react' + +import {ErrorHandling} from 'src/shared/decorators/errors' + +interface Props { + type: string + onChangeType: (type: string) => (event: MouseEvent) => void +} + +const STREAM = 'stream' +const BATCH = 'batch' + +@ErrorHandling +class TickscriptType extends PureComponent { + public render() { + const {onChangeType} = this.props + return ( +
    +
  • + Stream +
  • +
  • + Batch +
  • +
+ ) + } + + private getClassName(type: string) { + if (type === this.props.type) { + return 'active' + } + return '' + } +} + +export default TickscriptType diff --git a/ui/src/kapacitor/containers/TickscriptPage.tsx b/ui/src/kapacitor/containers/TickscriptPage.tsx index 422d7896a3..0b8a8c152b 100644 --- a/ui/src/kapacitor/containers/TickscriptPage.tsx +++ b/ui/src/kapacitor/containers/TickscriptPage.tsx @@ -1,4 +1,4 @@ -import React, {PureComponent} from 'react' +import React, {PureComponent, ChangeEvent} from 'react' import {connect} from 'react-redux' import {bindActionCreators} from 'redux' import uuid from 'uuid' @@ -18,7 +18,7 @@ import { Notification, NotificationFunc, } from 'src/types' - +import {LogItem, DBRP} from 'src/types/kapacitor' import { notifyTickscriptLoggingUnavailable, notifyTickscriptLoggingError, @@ -76,7 +76,7 @@ interface State { task: Task consoleMessage: string isEditingID: boolean - logs: object[] + logs: LogItem[] areLogsVisible: boolean areLogsEnabled: boolean failStr: string @@ -247,22 +247,22 @@ export class TickscriptPage extends PureComponent { return router.push(`/sources/${sourceID}/alert-rules`) } - private handleChangeScript = tickscript => { + private handleChangeScript = (tickscript: string) => { this.setState({ task: {...this.state.task, tickscript}, unsavedChanges: true, }) } - private handleSelectDbrps = dbrps => { + private handleSelectDbrps = (dbrps: DBRP[]) => { this.setState({task: {...this.state.task, dbrps}, unsavedChanges: true}) } - private handleChangeType = type => () => { + private handleChangeType = (type: string) => () => { this.setState({task: {...this.state.task, type}, unsavedChanges: true}) } - private handleChangeID = e => { + private handleChangeID = (e: ChangeEvent) => { this.setState({ task: {...this.state.task, id: e.target.value}, unsavedChanges: true, diff --git a/ui/src/types/kapacitor.ts b/ui/src/types/kapacitor.ts index 96e2ba829f..653d54906f 100644 --- a/ui/src/types/kapacitor.ts +++ b/ui/src/types/kapacitor.ts @@ -186,7 +186,7 @@ interface OpsGenie { } // Talk sends alerts to Jane Talk (https://jianliao.com/site) -interface Talk {} // tslint:disable-line +interface Talk { } // tslint:disable-line // TriggerValues specifies the alerting logic for a specific trigger type interface TriggerValues { @@ -411,3 +411,20 @@ export interface RuleValues { rangeValue?: string | null operator?: string } + +export interface LogItem { + key: string + service: string + lvl: string + ts: string + msg: string + id: string + tags: string + method?: string + username?: string + host?: string + duration?: string + tag?: object + field?: object + cluster?: string +} From 7b5d315f9fd22c711fa677de1f089d74b41b6661 Mon Sep 17 00:00:00 2001 From: ebb-tide Date: Tue, 19 Jun 2018 11:05:18 -0700 Subject: [PATCH 04/22] Fix merge errors and rollback Source object updates --- ui/src/shared/constants/index.tsx | 21 ------ ui/src/sources/containers/SourcePage.tsx | 96 ++++++++++++------------ 2 files changed, 47 insertions(+), 70 deletions(-) diff --git a/ui/src/shared/constants/index.tsx b/ui/src/shared/constants/index.tsx index abf21ed5bc..216ed85f15 100644 --- a/ui/src/shared/constants/index.tsx +++ b/ui/src/shared/constants/index.tsx @@ -428,23 +428,7 @@ export const DYGRAPH_CONTAINER_H_MARGIN = 16 export const DYGRAPH_CONTAINER_V_MARGIN = 8 export const DYGRAPH_CONTAINER_XLABEL_MARGIN = 20 -export const DEFAULT_SOURCE_LINKS = { - self: '', - kapacitors: '', - proxy: '', - queries: '', - write: '', - permissions: '', - users: '', - roles: '', - databases: '', - annotations: '', - health: '', - services: '', -} - export const DEFAULT_SOURCE = { - id: '', url: 'http://localhost:8086', name: 'Influx 1', username: '', @@ -453,11 +437,6 @@ export const DEFAULT_SOURCE = { telegraf: 'telegraf', insecureSkipVerify: false, metaUrl: '', - organization: '', - role: '', - defaultRP: '', - links: DEFAULT_SOURCE_LINKS, - type: '', } export const defaultIntervalValue = '333' diff --git a/ui/src/sources/containers/SourcePage.tsx b/ui/src/sources/containers/SourcePage.tsx index 950e1c8ff4..decee0215e 100644 --- a/ui/src/sources/containers/SourcePage.tsx +++ b/ui/src/sources/containers/SourcePage.tsx @@ -1,5 +1,6 @@ -import React, {PureComponent, ChangeEvent, FormEvent} from 'react' +import React, {PureComponent, MouseEvent, ChangeEvent} from 'react' import {withRouter, WithRouterProps} from 'react-router' +import _ from 'lodash' import {getSource} from 'src/shared/apis' import {createSource, updateSource} from 'src/shared/apis' import { @@ -19,6 +20,9 @@ import SourceForm from 'src/sources/components/SourceForm' import FancyScrollbar from 'src/shared/components/FancyScrollbar' import SourceIndicator from 'src/shared/components/SourceIndicator' import {DEFAULT_SOURCE} from 'src/shared/constants' +import {Source} from 'src/types' + +const INITIAL_PATH = '/sources/new' import { notifySourceUdpated, @@ -28,10 +32,6 @@ import { notifySourceCreationSucceeded, } from 'src/shared/copy/notifications' import {ErrorHandling} from 'src/shared/decorators/errors' -import {Source} from 'src/types' -import {getDeep} from 'src/utils/wrappers' - -const INITIAL_PATH = '/sources/new' interface Props extends WithRouterProps { notify: PublishNotification @@ -40,8 +40,8 @@ interface Props extends WithRouterProps { } interface State { - isLoading: boolean isCreated: boolean + isLoading: boolean source: Partial editMode: boolean isInitialSource: boolean @@ -56,8 +56,8 @@ class SourcePage extends PureComponent { isLoading: true, isCreated: false, source: DEFAULT_SOURCE, - editMode: this.props.params.id !== undefined, - isInitialSource: this.props.location.pathname === INITIAL_PATH, + editMode: props.params.id !== undefined, + isInitialSource: props.router.location.pathname === INITIAL_PATH, } } @@ -132,39 +132,7 @@ class SourcePage extends PureComponent { ) } - private handleInputChange = (e: ChangeEvent) => { - let val: string | boolean = e.target.value - const name = e.target.name - - if (e.target.type === 'checkbox') { - val = e.target.checked - } - - this.setState(prevState => { - const source = { - ...prevState.source, - [name]: val, - } - - return {...prevState, source} - }) - } - - private handleBlurSourceURL = () => { - const {source, editMode} = this.state - if (editMode) { - this.setState(this.normalizeSource) - return - } - - if (!source.url) { - return - } - - this.setState(this.normalizeSource, this.createSourceOnBlur) - } - - private handleSubmit = (e: FormEvent) => { + private handleSubmit = (e: MouseEvent): void => { e.preventDefault() const {isCreated, editMode} = this.state const isNewSource = !editMode @@ -181,10 +149,8 @@ class SourcePage extends PureComponent { router.push('/purgatory') } - private normalizeSource() { - const {source} = this.state - // private normalizeSource({source}) { - // const url = source.url.trim() + private normalizeSource({source}) { + const url = source.url.trim() if (source.url.startsWith('http')) { return {source: {...source, url}} } @@ -250,7 +216,11 @@ class SourcePage extends PureComponent { router.push(`/sources/${params.sourceID}/manage-sources`) } - private redirectToApp = (source: Source) => { + private parseError = (error): string => { + return _.get(error, ['data', 'message'], error) + } + + private redirectToApp = source => { const {location, router} = this.props const {redirectPath} = location.query @@ -265,8 +235,36 @@ class SourcePage extends PureComponent { return router.push(fixedPath) } - private parseError = error => { - return getDeep(error, 'data.message', '') + private handleInputChange = (e: ChangeEvent) => { + let val = e.target.value + const name = e.target.name + + if (e.target.type === 'checkbox') { + val = e.target.checked as any + } + + this.setState(prevState => { + const source = { + ...prevState.source, + [name]: val, + } + + return {...prevState, source} + }) + } + + private handleBlurSourceURL = () => { + const {source, editMode} = this.state + if (editMode) { + this.setState(this.normalizeSource) + return + } + + if (!source.url) { + return + } + + this.setState(this.normalizeSource, this.createSourceOnBlur) } } @@ -276,4 +274,4 @@ const mdtp = { updateSource: updateSourceAction, } -export default withRouter(connect(null, mdtp)(SourcePage)) +export default connect(null, mdtp)(withRouter(SourcePage)) From 598b7997549e9bba37b33a451e6d7bdab6cf9bf8 Mon Sep 17 00:00:00 2001 From: ebb-tide Date: Tue, 19 Jun 2018 11:16:43 -0700 Subject: [PATCH 05/22] Fix failing test --- ui/test/normalizers/dashboardTime.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/test/normalizers/dashboardTime.test.js b/ui/test/normalizers/dashboardTime.test.js index ab439144e7..d630552017 100644 --- a/ui/test/normalizers/dashboardTime.test.js +++ b/ui/test/normalizers/dashboardTime.test.js @@ -30,7 +30,7 @@ describe('Normalizers.DashboardTime', () => { }) it('can remove timeRanges with incorrect dashboardID', () => { - const ranges = [{dashboardID: 1, upper, lower}, timeRange] + const ranges = [{dashboardID: '1', upper, lower}, timeRange] const actual = normalizer(ranges) const expected = [timeRange] From 7c0a2db03af4882d638d6889684a9f95af8822cb Mon Sep 17 00:00:00 2001 From: Christopher Henn Date: Tue, 19 Jun 2018 11:38:40 -0700 Subject: [PATCH 06/22] Preserve template variable name when editing type --- .../tempVars/components/TemplateVariableEditor.tsx | 11 ++++++++--- ui/src/tempVars/constants/index.ts | 12 ++++++------ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/ui/src/tempVars/components/TemplateVariableEditor.tsx b/ui/src/tempVars/components/TemplateVariableEditor.tsx index 8d15b5edc3..296c3fb23c 100644 --- a/ui/src/tempVars/components/TemplateVariableEditor.tsx +++ b/ui/src/tempVars/components/TemplateVariableEditor.tsx @@ -182,10 +182,10 @@ class TemplateVariableEditor extends PureComponent { private handleChooseType = ({type}) => { const { - nextTemplate: {id}, + nextTemplate: {id, tempVar}, } = this.state - const nextNextTemplate = {...DEFAULT_TEMPLATES[type](), id} + const nextNextTemplate = {...DEFAULT_TEMPLATES[type](), id, tempVar} this.setState({nextTemplate: nextNextTemplate}) } @@ -204,7 +204,12 @@ class TemplateVariableEditor extends PureComponent { private formatName = (): void => { const {nextTemplate} = this.state - const tempVar = formatName(nextTemplate.tempVar) + + let tempVar = formatName(nextTemplate.tempVar) + + if (tempVar === '::') { + tempVar = '' + } this.setState({nextTemplate: {...nextTemplate, tempVar}}) } diff --git a/ui/src/tempVars/constants/index.ts b/ui/src/tempVars/constants/index.ts index 6fe11741c0..414c2e546e 100644 --- a/ui/src/tempVars/constants/index.ts +++ b/ui/src/tempVars/constants/index.ts @@ -62,7 +62,7 @@ export const DEFAULT_TEMPLATES: DefaultTemplates = { [TemplateType.Databases]: () => { return { id: uuid.v4(), - tempVar: ':my-databases:', + tempVar: '', values: [ { value: '_internal', @@ -80,7 +80,7 @@ export const DEFAULT_TEMPLATES: DefaultTemplates = { [TemplateType.Measurements]: () => { return { id: uuid.v4(), - tempVar: ':my-measurements:', + tempVar: '', values: [], type: TemplateType.Measurements, label: '', @@ -93,7 +93,7 @@ export const DEFAULT_TEMPLATES: DefaultTemplates = { [TemplateType.CSV]: () => { return { id: uuid.v4(), - tempVar: ':my-values:', + tempVar: '', values: [], type: TemplateType.CSV, label: '', @@ -103,7 +103,7 @@ export const DEFAULT_TEMPLATES: DefaultTemplates = { [TemplateType.TagKeys]: () => { return { id: uuid.v4(), - tempVar: ':my-tag-keys:', + tempVar: '', values: [], type: TemplateType.TagKeys, label: '', @@ -115,7 +115,7 @@ export const DEFAULT_TEMPLATES: DefaultTemplates = { [TemplateType.FieldKeys]: () => { return { id: uuid.v4(), - tempVar: ':my-field-keys:', + tempVar: '', values: [], type: TemplateType.FieldKeys, label: '', @@ -127,7 +127,7 @@ export const DEFAULT_TEMPLATES: DefaultTemplates = { [TemplateType.TagValues]: () => { return { id: uuid.v4(), - tempVar: ':my-tag-values:', + tempVar: '', values: [], type: TemplateType.TagValues, label: '', From 19e6821e30b228b8a5089d05aab3042af0b98017 Mon Sep 17 00:00:00 2001 From: Christopher Henn Date: Tue, 19 Jun 2018 13:21:27 -0700 Subject: [PATCH 07/22] Improve save button copy in TemplateVariableEditor --- .../components/TemplateVariableEditor.tsx | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/ui/src/tempVars/components/TemplateVariableEditor.tsx b/ui/src/tempVars/components/TemplateVariableEditor.tsx index 296c3fb23c..724f758af5 100644 --- a/ui/src/tempVars/components/TemplateVariableEditor.tsx +++ b/ui/src/tempVars/components/TemplateVariableEditor.tsx @@ -100,7 +100,9 @@ class TemplateVariableEditor extends PureComponent { return (
-

Edit Template Variable

+

+ {isNew ? 'Create' : 'Edit'} Template Variable +

@@ -277,6 +279,24 @@ class TemplateVariableEditor extends PureComponent { ) } + private get saveButtonText(): string { + const {isNew} = this.state + + if (this.isSaving && isNew) { + return 'Creating...' + } + + if (this.isSaving && !isNew) { + return 'Saving...' + } + + if (!this.isSaving && isNew) { + return 'Create' + } + + return 'Save' + } + private handleDelete = (): void => { const {onDelete} = this.props From 9932764fe2296698f97bde2dfee9e3435ba8866b Mon Sep 17 00:00:00 2001 From: Iris Scholten Date: Tue, 19 Jun 2018 14:50:16 -0700 Subject: [PATCH 08/22] Add map as a valid type of template variables in api --- bolt/internal/internal.go | 2 + bolt/internal/internal.pb.go | 222 ++++++++++++++++++----------------- bolt/internal/internal.proto | 1 + chronograf.go | 9 +- server/templates.go | 6 +- server/templates_test.go | 30 +++++ 6 files changed, 158 insertions(+), 112 deletions(-) diff --git a/bolt/internal/internal.go b/bolt/internal/internal.go index 07dc5adcad..b9a59fc94a 100644 --- a/bolt/internal/internal.go +++ b/bolt/internal/internal.go @@ -341,6 +341,7 @@ func MarshalDashboard(d chronograf.Dashboard) ([]byte, error) { Selected: v.Selected, Type: v.Type, Value: v.Value, + Key: v.Key, } } @@ -522,6 +523,7 @@ func UnmarshalDashboard(data []byte, d *chronograf.Dashboard) error { Selected: v.Selected, Type: v.Type, Value: v.Value, + Key: v.Key, } } diff --git a/bolt/internal/internal.pb.go b/bolt/internal/internal.pb.go index 5c7cbe97ff..19a76f7811 100644 --- a/bolt/internal/internal.pb.go +++ b/bolt/internal/internal.pb.go @@ -640,6 +640,7 @@ type TemplateValue struct { Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` Selected bool `protobuf:"varint,3,opt,name=selected,proto3" json:"selected,omitempty"` + Key string `protobuf:"bytes,4,opt,name=key,proto3" json:"key,omitempty"` } func (m *TemplateValue) Reset() { *m = TemplateValue{} } @@ -668,6 +669,13 @@ func (m *TemplateValue) GetSelected() bool { return false } +func (m *TemplateValue) GetKey() string { + if m != nil { + return m.Key + } + return "" +} + type TemplateQuery struct { Command string `protobuf:"bytes,1,opt,name=command,proto3" json:"command,omitempty"` Db string `protobuf:"bytes,2,opt,name=db,proto3" json:"db,omitempty"` @@ -1388,111 +1396,111 @@ func init() { func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) } var fileDescriptorInternal = []byte{ - // 1685 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0x5b, 0x8f, 0xe3, 0x48, - 0x15, 0x96, 0x93, 0x38, 0x89, 0x4f, 0xd2, 0xbd, 0x2d, 0x33, 0xda, 0x35, 0x0b, 0x42, 0xc1, 0xe2, - 0xd2, 0x5c, 0x76, 0x58, 0xf5, 0x0a, 0x09, 0xad, 0x76, 0x57, 0xea, 0xcb, 0xce, 0xd0, 0x73, 0xed, - 0xa9, 0x74, 0x0f, 0x4f, 0x68, 0x55, 0xb1, 0x2b, 0x49, 0x69, 0x1d, 0xdb, 0x94, 0xcb, 0xdd, 0x6d, - 0x9e, 0xf9, 0x1d, 0x48, 0x48, 0xf0, 0x8e, 0x10, 0x8f, 0x48, 0xbc, 0xf3, 0x03, 0xf8, 0x2b, 0xbc, - 0xa2, 0x53, 0x17, 0xa7, 0xdc, 0x9d, 0x19, 0x0d, 0x12, 0xda, 0xb7, 0xfa, 0xce, 0x39, 0x39, 0x55, - 0x75, 0x2e, 0x5f, 0x1d, 0x07, 0xf6, 0x79, 0x2e, 0x99, 0xc8, 0x69, 0xf6, 0xb0, 0x14, 0x85, 0x2c, - 0xc2, 0xb1, 0xc5, 0xf1, 0x1f, 0xfa, 0x30, 0x9c, 0x17, 0xb5, 0x48, 0x58, 0xb8, 0x0f, 0xbd, 0xf3, - 0xb3, 0xc8, 0x9b, 0x79, 0x87, 0x7d, 0xd2, 0x3b, 0x3f, 0x0b, 0x43, 0x18, 0xbc, 0xa0, 0x1b, 0x16, - 0xf5, 0x66, 0xde, 0x61, 0x40, 0xd4, 0x1a, 0x65, 0x97, 0x4d, 0xc9, 0xa2, 0xbe, 0x96, 0xe1, 0x3a, - 0xfc, 0x10, 0xc6, 0x57, 0x15, 0x7a, 0xdb, 0xb0, 0x68, 0xa0, 0xe4, 0x2d, 0x46, 0xdd, 0x05, 0xad, - 0xaa, 0x9b, 0x42, 0xa4, 0x91, 0xaf, 0x75, 0x16, 0x87, 0x07, 0xd0, 0xbf, 0x22, 0xcf, 0xa2, 0xa1, - 0x12, 0xe3, 0x32, 0x8c, 0x60, 0x74, 0xc6, 0x96, 0xb4, 0xce, 0x64, 0x34, 0x9a, 0x79, 0x87, 0x63, - 0x62, 0x21, 0xfa, 0xb9, 0x64, 0x19, 0x5b, 0x09, 0xba, 0x8c, 0xc6, 0xda, 0x8f, 0xc5, 0xe1, 0x43, - 0x08, 0xcf, 0xf3, 0x8a, 0x25, 0xb5, 0x60, 0xf3, 0xaf, 0x79, 0xf9, 0x9a, 0x09, 0xbe, 0x6c, 0xa2, - 0x40, 0x39, 0xd8, 0xa1, 0xc1, 0x5d, 0x9e, 0x33, 0x49, 0x71, 0x6f, 0x50, 0xae, 0x2c, 0x0c, 0x63, - 0x98, 0xce, 0xd7, 0x54, 0xb0, 0x74, 0xce, 0x12, 0xc1, 0x64, 0x34, 0x51, 0xea, 0x8e, 0x0c, 0x6d, - 0x5e, 0x8a, 0x15, 0xcd, 0xf9, 0xef, 0xa9, 0xe4, 0x45, 0x1e, 0x4d, 0xb5, 0x8d, 0x2b, 0xc3, 0x28, - 0x91, 0x22, 0x63, 0xd1, 0x9e, 0x8e, 0x12, 0xae, 0xc3, 0xef, 0x42, 0x60, 0x2e, 0x43, 0x2e, 0xa2, - 0x7d, 0xa5, 0xd8, 0x0a, 0xe2, 0xbf, 0x7b, 0x10, 0x9c, 0xd1, 0x6a, 0xbd, 0x28, 0xa8, 0x48, 0xdf, - 0x29, 0x13, 0x1f, 0x81, 0x9f, 0xb0, 0x2c, 0xab, 0xa2, 0xfe, 0xac, 0x7f, 0x38, 0x39, 0xfa, 0xe0, - 0x61, 0x9b, 0xe2, 0xd6, 0xcf, 0x29, 0xcb, 0x32, 0xa2, 0xad, 0xc2, 0x8f, 0x21, 0x90, 0x6c, 0x53, - 0x66, 0x54, 0xb2, 0x2a, 0x1a, 0xa8, 0x9f, 0x84, 0xdb, 0x9f, 0x5c, 0x1a, 0x15, 0xd9, 0x1a, 0xdd, - 0xbb, 0xa8, 0x7f, 0xff, 0xa2, 0xf1, 0xbf, 0x07, 0xb0, 0xd7, 0xd9, 0x2e, 0x9c, 0x82, 0x77, 0xab, - 0x4e, 0xee, 0x13, 0xef, 0x16, 0x51, 0xa3, 0x4e, 0xed, 0x13, 0xaf, 0x41, 0x74, 0xa3, 0x2a, 0xc7, - 0x27, 0xde, 0x0d, 0xa2, 0xb5, 0xaa, 0x17, 0x9f, 0x78, 0xeb, 0xf0, 0x27, 0x30, 0xfa, 0x5d, 0xcd, - 0x04, 0x67, 0x55, 0xe4, 0xab, 0xd3, 0xbd, 0xb7, 0x3d, 0xdd, 0xab, 0x9a, 0x89, 0x86, 0x58, 0x3d, - 0x46, 0x43, 0xd5, 0x9a, 0x2e, 0x1c, 0xb5, 0x46, 0x99, 0xc4, 0xba, 0x1c, 0x69, 0x19, 0xae, 0x4d, - 0x14, 0x75, 0xb5, 0x60, 0x14, 0x7f, 0x09, 0x03, 0x7a, 0xcb, 0xaa, 0x28, 0x50, 0xfe, 0xbf, 0xff, - 0x86, 0x80, 0x3d, 0x3c, 0xbe, 0x65, 0xd5, 0x97, 0xb9, 0x14, 0x0d, 0x51, 0xe6, 0xe1, 0x8f, 0x61, - 0x98, 0x14, 0x59, 0x21, 0xaa, 0x08, 0xee, 0x1e, 0xec, 0x14, 0xe5, 0xc4, 0xa8, 0xc3, 0x43, 0x18, - 0x66, 0x6c, 0xc5, 0xf2, 0x54, 0xd5, 0xcd, 0xe4, 0xe8, 0x60, 0x6b, 0xf8, 0x4c, 0xc9, 0x89, 0xd1, - 0x87, 0x9f, 0xc2, 0x54, 0xd2, 0x45, 0xc6, 0x5e, 0x96, 0x18, 0xc5, 0x4a, 0xd5, 0xd0, 0xe4, 0xe8, - 0x7d, 0x27, 0x1f, 0x8e, 0x96, 0x74, 0x6c, 0xc3, 0xcf, 0x60, 0xba, 0xe4, 0x2c, 0x4b, 0xed, 0x6f, - 0xf7, 0xd4, 0xa1, 0xa2, 0xed, 0x6f, 0x09, 0xcb, 0xe9, 0x06, 0x7f, 0xf1, 0x08, 0xcd, 0x48, 0xc7, - 0x3a, 0xfc, 0x1e, 0x80, 0xe4, 0x1b, 0xf6, 0xa8, 0x10, 0x1b, 0x2a, 0x4d, 0x19, 0x3a, 0x92, 0xf0, - 0x73, 0xd8, 0x4b, 0x59, 0xc2, 0x37, 0x34, 0xbb, 0xc8, 0x68, 0xc2, 0xaa, 0xe8, 0x3d, 0x75, 0x34, - 0xb7, 0xba, 0x5c, 0x35, 0xe9, 0x5a, 0x7f, 0xf8, 0x18, 0x82, 0x36, 0x7c, 0xd8, 0xdf, 0x5f, 0xb3, - 0x46, 0x15, 0x43, 0x40, 0x70, 0x19, 0xfe, 0x00, 0xfc, 0x6b, 0x9a, 0xd5, 0xba, 0x90, 0x27, 0x47, - 0xfb, 0x5b, 0xaf, 0xc7, 0xb7, 0xbc, 0x22, 0x5a, 0xf9, 0x69, 0xef, 0x57, 0x5e, 0xfc, 0x18, 0xf6, - 0x3a, 0x1b, 0xe1, 0xc1, 0x79, 0xf5, 0x65, 0xbe, 0x2c, 0x44, 0xc2, 0x52, 0xe5, 0x73, 0x4c, 0x1c, - 0x49, 0xf8, 0x3e, 0x0c, 0x53, 0xbe, 0xe2, 0xb2, 0x32, 0xe5, 0x66, 0x50, 0xfc, 0x0f, 0x0f, 0xa6, - 0x6e, 0x34, 0xc3, 0x9f, 0xc2, 0xc1, 0x35, 0x13, 0x92, 0x27, 0x34, 0xbb, 0xe4, 0x1b, 0x86, 0x1b, - 0xab, 0x9f, 0x8c, 0xc9, 0x3d, 0x79, 0xf8, 0x31, 0x0c, 0xab, 0x42, 0xc8, 0x93, 0x46, 0x55, 0xed, - 0xdb, 0xa2, 0x6c, 0xec, 0x90, 0xa7, 0x6e, 0x04, 0x2d, 0x4b, 0x9e, 0xaf, 0x2c, 0x17, 0x5a, 0x1c, - 0xfe, 0x08, 0xf6, 0x97, 0xfc, 0xf6, 0x11, 0x17, 0x95, 0x3c, 0x2d, 0xb2, 0x7a, 0x93, 0xab, 0x0a, - 0x1e, 0x93, 0x3b, 0xd2, 0x27, 0x83, 0xb1, 0x77, 0xd0, 0x7b, 0x32, 0x18, 0xfb, 0x07, 0xc3, 0xb8, - 0x84, 0xfd, 0xee, 0x4e, 0xd8, 0x96, 0xf6, 0x10, 0x8a, 0x13, 0x74, 0x78, 0x3b, 0xb2, 0x70, 0x06, - 0x93, 0x94, 0x57, 0x65, 0x46, 0x1b, 0x87, 0x36, 0x5c, 0x11, 0x72, 0xe0, 0x35, 0xaf, 0xf8, 0x22, - 0xd3, 0x54, 0x3e, 0x26, 0x16, 0xc6, 0x2b, 0xf0, 0x55, 0x59, 0x3b, 0x24, 0x14, 0x58, 0x12, 0x52, - 0xd4, 0xdf, 0x73, 0xa8, 0xff, 0x00, 0xfa, 0xbf, 0x66, 0xb7, 0xe6, 0x35, 0xc0, 0x65, 0x4b, 0x55, - 0x03, 0x87, 0xaa, 0x1e, 0x80, 0xff, 0x5a, 0xa5, 0x5d, 0x53, 0x88, 0x06, 0xf1, 0x17, 0x30, 0xd4, - 0x6d, 0xd1, 0x7a, 0xf6, 0x1c, 0xcf, 0x33, 0x98, 0xbc, 0x14, 0x9c, 0xe5, 0x52, 0x93, 0x8f, 0xb9, - 0x82, 0x23, 0x8a, 0xff, 0xe6, 0xc1, 0x40, 0x65, 0x29, 0x86, 0x69, 0xc6, 0x56, 0x34, 0x69, 0x4e, - 0x8a, 0x3a, 0x4f, 0xab, 0xc8, 0x9b, 0xf5, 0x0f, 0xfb, 0xa4, 0x23, 0xc3, 0xf2, 0x58, 0x68, 0x6d, - 0x6f, 0xd6, 0x3f, 0x0c, 0x88, 0x41, 0x78, 0xb4, 0x8c, 0x2e, 0x58, 0x66, 0xae, 0xa0, 0x01, 0x5a, - 0x97, 0x82, 0x2d, 0xf9, 0xad, 0xb9, 0x86, 0x41, 0x28, 0xaf, 0xea, 0x25, 0xca, 0xf5, 0x4d, 0x0c, - 0xc2, 0x0b, 0x2c, 0x68, 0xd5, 0x32, 0x12, 0xae, 0xd1, 0x73, 0x95, 0xd0, 0xcc, 0x52, 0x92, 0x06, - 0xf1, 0x3f, 0x3d, 0x7c, 0xc8, 0x34, 0xc5, 0xde, 0x8b, 0xf0, 0xb7, 0x61, 0x8c, 0xf4, 0xfb, 0xd5, - 0x35, 0x15, 0xe6, 0xc2, 0x23, 0xc4, 0xaf, 0xa9, 0x08, 0x7f, 0x01, 0x43, 0xd5, 0x1c, 0x3b, 0xe8, - 0xde, 0xba, 0x53, 0x51, 0x25, 0xc6, 0xac, 0x25, 0xc4, 0x81, 0x43, 0x88, 0xed, 0x65, 0x7d, 0xf7, - 0xb2, 0x1f, 0x81, 0x8f, 0xcc, 0xda, 0xa8, 0xd3, 0xef, 0xf4, 0xac, 0xf9, 0x57, 0x5b, 0xc5, 0x57, - 0xb0, 0xd7, 0xd9, 0xb1, 0xdd, 0xc9, 0xeb, 0xee, 0xb4, 0x6d, 0xf4, 0xc0, 0x34, 0x36, 0x36, 0x47, - 0xc5, 0x32, 0x96, 0x48, 0x96, 0x9a, 0xaa, 0x6b, 0x71, 0xfc, 0x27, 0x6f, 0xeb, 0x57, 0xed, 0x87, - 0x25, 0x9a, 0x14, 0x9b, 0x0d, 0xcd, 0x53, 0xe3, 0xda, 0x42, 0x8c, 0x5b, 0xba, 0x30, 0xae, 0x7b, - 0xe9, 0x02, 0xb1, 0x28, 0x4d, 0x06, 0x7b, 0xa2, 0xc4, 0xda, 0xd9, 0x30, 0x5a, 0xd5, 0x82, 0x6d, - 0x58, 0x2e, 0x4d, 0x08, 0x5c, 0x51, 0xf8, 0x01, 0x8c, 0x24, 0x5d, 0x7d, 0x85, 0xf4, 0x64, 0x32, - 0x29, 0xe9, 0xea, 0x29, 0x6b, 0xc2, 0xef, 0x40, 0xa0, 0xf8, 0x52, 0xa9, 0x74, 0x3a, 0xc7, 0x4a, - 0xf0, 0x94, 0x35, 0xf1, 0x5f, 0x7b, 0x30, 0x9c, 0x33, 0x71, 0xcd, 0xc4, 0x3b, 0xbd, 0xd0, 0xee, - 0x5c, 0xd4, 0x7f, 0xcb, 0x5c, 0x34, 0xd8, 0x3d, 0x17, 0xf9, 0xdb, 0xb9, 0xe8, 0x01, 0xf8, 0x73, - 0x91, 0x9c, 0x9f, 0xa9, 0x13, 0xf5, 0x89, 0x06, 0x58, 0x8d, 0xc7, 0x89, 0xe4, 0xd7, 0xcc, 0x0c, - 0x4b, 0x06, 0xdd, 0x7b, 0xb8, 0xc7, 0x3b, 0x26, 0x94, 0xff, 0x75, 0x66, 0xb2, 0x2d, 0x0a, 0x4e, - 0x8b, 0xc6, 0x30, 0xc5, 0xc1, 0x29, 0xa5, 0x92, 0x3e, 0x99, 0xbf, 0x7c, 0x61, 0xa7, 0x25, 0x57, - 0x16, 0xff, 0xd1, 0x83, 0xe1, 0x33, 0xda, 0x14, 0xb5, 0xbc, 0x57, 0xed, 0x33, 0x98, 0x1c, 0x97, - 0x65, 0xc6, 0x93, 0x4e, 0x87, 0x3b, 0x22, 0xb4, 0x78, 0xee, 0xe4, 0x51, 0xc7, 0xd0, 0x15, 0xe1, - 0x83, 0x72, 0xaa, 0x86, 0x20, 0x3d, 0xd1, 0x38, 0x0f, 0x8a, 0x9e, 0x7d, 0x94, 0x12, 0x83, 0x7d, - 0x5c, 0xcb, 0x62, 0x99, 0x15, 0x37, 0x2a, 0xaa, 0x63, 0xd2, 0xe2, 0xf8, 0x5f, 0x3d, 0x18, 0x7c, - 0x53, 0x83, 0xcb, 0x14, 0x3c, 0x6e, 0x8a, 0xca, 0xe3, 0xed, 0x18, 0x33, 0x72, 0xc6, 0x98, 0x08, - 0x46, 0x8d, 0xa0, 0xf9, 0x8a, 0x55, 0xd1, 0x58, 0xb1, 0x98, 0x85, 0x4a, 0xa3, 0xfa, 0x55, 0xcf, - 0x2f, 0x01, 0xb1, 0xb0, 0xed, 0x3f, 0x70, 0xfa, 0xef, 0xe7, 0x66, 0xd4, 0x99, 0xdc, 0x1d, 0x0e, - 0x76, 0x4d, 0x38, 0xff, 0xbf, 0x57, 0xfb, 0x3f, 0x1e, 0xf8, 0x6d, 0xf3, 0x9e, 0x76, 0x9b, 0xf7, - 0x74, 0xdb, 0xbc, 0x67, 0x27, 0xb6, 0x79, 0xcf, 0x4e, 0x10, 0x93, 0x0b, 0xdb, 0xbc, 0xe4, 0x02, - 0x93, 0xf5, 0x58, 0x14, 0x75, 0x79, 0xd2, 0xe8, 0xac, 0x06, 0xa4, 0xc5, 0x58, 0xf1, 0xbf, 0x59, - 0x33, 0x61, 0x42, 0x1d, 0x10, 0x83, 0xb0, 0x3f, 0x9e, 0x29, 0x62, 0xd3, 0xc1, 0xd5, 0x20, 0xfc, - 0x21, 0xf8, 0x04, 0x83, 0xa7, 0x22, 0xdc, 0xc9, 0x8b, 0x12, 0x13, 0xad, 0x45, 0xa7, 0xfa, 0x03, - 0xc8, 0x34, 0x8a, 0xfd, 0x1c, 0xfa, 0x19, 0x0c, 0xe7, 0x6b, 0xbe, 0x94, 0x76, 0x60, 0xfc, 0x96, - 0x43, 0x8c, 0x7c, 0xc3, 0x94, 0x8e, 0x18, 0x93, 0xf8, 0x15, 0x04, 0xad, 0x70, 0x7b, 0x1c, 0xcf, - 0x3d, 0x4e, 0x08, 0x83, 0xab, 0x9c, 0x4b, 0x4b, 0x11, 0xb8, 0xc6, 0xcb, 0xbe, 0xaa, 0x69, 0x2e, - 0xb9, 0x6c, 0x2c, 0x45, 0x58, 0x1c, 0x7f, 0x62, 0x8e, 0x8f, 0xee, 0xae, 0xca, 0x92, 0x09, 0x43, - 0x37, 0x1a, 0xa8, 0x4d, 0x8a, 0x1b, 0xa6, 0x5f, 0x8a, 0x3e, 0xd1, 0x20, 0xfe, 0x2d, 0x04, 0xc7, - 0x19, 0x13, 0x92, 0xd4, 0x19, 0xdb, 0xf5, 0x82, 0xab, 0x46, 0x35, 0x27, 0xc0, 0xf5, 0x96, 0x5a, - 0xfa, 0x77, 0xa8, 0xe5, 0x29, 0x2d, 0xe9, 0xf9, 0x99, 0xaa, 0xf3, 0x3e, 0x31, 0x28, 0xfe, 0xb3, - 0x07, 0x03, 0xe4, 0x30, 0xc7, 0xf5, 0xe0, 0x6d, 0xfc, 0x77, 0x21, 0x8a, 0x6b, 0x9e, 0x32, 0x61, - 0x2f, 0x67, 0xb1, 0x0a, 0x7a, 0xb2, 0x66, 0xed, 0xa0, 0x60, 0x10, 0xd6, 0x1a, 0x7e, 0x2d, 0xd9, - 0x5e, 0x72, 0x6a, 0x0d, 0xc5, 0x44, 0x2b, 0x71, 0x18, 0x9c, 0xd7, 0x25, 0x13, 0xc7, 0xe9, 0x86, - 0xdb, 0x29, 0xca, 0x91, 0xc4, 0x5f, 0xe8, 0xef, 0xaf, 0x7b, 0x4c, 0xe8, 0xed, 0xfe, 0x56, 0xbb, - 0x7b, 0xf2, 0xf8, 0x2f, 0x1e, 0x8c, 0x9e, 0x9b, 0xa9, 0xcd, 0xbd, 0x85, 0xf7, 0xc6, 0x5b, 0xf4, - 0x3a, 0xb7, 0x38, 0x82, 0x07, 0xd6, 0xa6, 0xb3, 0xbf, 0x8e, 0xc2, 0x4e, 0x9d, 0x89, 0xe8, 0xa0, - 0x4d, 0xd6, 0xbb, 0x7c, 0x7e, 0x5d, 0x76, 0x6d, 0x76, 0x25, 0xfc, 0x5e, 0x56, 0x66, 0x30, 0xb1, - 0x9f, 0x9d, 0x45, 0x66, 0x1f, 0x26, 0x57, 0x14, 0x1f, 0xc1, 0xf0, 0xb4, 0xc8, 0x97, 0x7c, 0x15, - 0x1e, 0xc2, 0xe0, 0xb8, 0x96, 0x6b, 0xe5, 0x71, 0x72, 0xf4, 0xc0, 0x69, 0xfc, 0x5a, 0xae, 0xb5, - 0x0d, 0x51, 0x16, 0xf1, 0x67, 0x00, 0x5b, 0x19, 0xbe, 0x2e, 0xdb, 0x6c, 0xbc, 0x60, 0x37, 0x58, - 0x32, 0x95, 0x19, 0xda, 0x77, 0x68, 0xe2, 0xcf, 0x21, 0x38, 0xa9, 0x79, 0x96, 0x9e, 0xe7, 0xcb, - 0x02, 0xa9, 0xe3, 0x35, 0x13, 0xd5, 0x36, 0x5f, 0x16, 0x62, 0xb8, 0x91, 0x45, 0xda, 0x1e, 0x32, - 0x68, 0x31, 0x54, 0x7f, 0x6a, 0x7c, 0xf2, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xd3, 0xfb, 0xbd, - 0x7b, 0xe6, 0x10, 0x00, 0x00, + // 1691 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0xdd, 0x8f, 0xe3, 0x48, + 0x11, 0x97, 0x13, 0x3b, 0x89, 0x2b, 0x99, 0xb9, 0x91, 0x59, 0xdd, 0x99, 0x03, 0xa1, 0x60, 0xf1, + 0x31, 0x7c, 0xdc, 0x72, 0x9a, 0x13, 0x12, 0x3a, 0xdd, 0x9d, 0x34, 0x1f, 0xb7, 0xcb, 0xec, 0xe7, + 0x6c, 0x67, 0x76, 0x78, 0x42, 0xa7, 0x4e, 0xdc, 0x49, 0x5a, 0xe7, 0xd8, 0xa6, 0xdd, 0x9e, 0x19, + 0xf3, 0xcc, 0xdf, 0x81, 0x84, 0x04, 0xef, 0x08, 0xf1, 0x88, 0xc4, 0x3b, 0x7f, 0x00, 0xff, 0x0a, + 0xaf, 0xa8, 0xfa, 0xc3, 0x69, 0xcf, 0x64, 0x57, 0x8b, 0x84, 0xee, 0xad, 0x7f, 0x55, 0x95, 0xea, + 0xea, 0xea, 0xaa, 0x5f, 0x97, 0x03, 0xfb, 0x3c, 0x97, 0x4c, 0xe4, 0x34, 0x7b, 0x58, 0x8a, 0x42, + 0x16, 0xd1, 0xc8, 0xe2, 0xe4, 0x0f, 0x7d, 0x18, 0xcc, 0x8a, 0x5a, 0x2c, 0x58, 0xb4, 0x0f, 0xbd, + 0xf3, 0xb3, 0xd8, 0x9b, 0x7a, 0x87, 0x7d, 0xd2, 0x3b, 0x3f, 0x8b, 0x22, 0xf0, 0x5f, 0xd0, 0x0d, + 0x8b, 0x7b, 0x53, 0xef, 0x30, 0x24, 0x6a, 0x8d, 0xb2, 0xcb, 0xa6, 0x64, 0x71, 0x5f, 0xcb, 0x70, + 0x1d, 0x7d, 0x08, 0xa3, 0xd7, 0x15, 0x7a, 0xdb, 0xb0, 0xd8, 0x57, 0xf2, 0x16, 0xa3, 0xee, 0x82, + 0x56, 0xd5, 0x4d, 0x21, 0xd2, 0x38, 0xd0, 0x3a, 0x8b, 0xa3, 0x03, 0xe8, 0xbf, 0x26, 0xcf, 0xe2, + 0x81, 0x12, 0xe3, 0x32, 0x8a, 0x61, 0x78, 0xc6, 0x96, 0xb4, 0xce, 0x64, 0x3c, 0x9c, 0x7a, 0x87, + 0x23, 0x62, 0x21, 0xfa, 0xb9, 0x64, 0x19, 0x5b, 0x09, 0xba, 0x8c, 0x47, 0xda, 0x8f, 0xc5, 0xd1, + 0x43, 0x88, 0xce, 0xf3, 0x8a, 0x2d, 0x6a, 0xc1, 0x66, 0x5f, 0xf3, 0xf2, 0x8a, 0x09, 0xbe, 0x6c, + 0xe2, 0x50, 0x39, 0xd8, 0xa1, 0xc1, 0x5d, 0x9e, 0x33, 0x49, 0x71, 0x6f, 0x50, 0xae, 0x2c, 0x8c, + 0x12, 0x98, 0xcc, 0xd6, 0x54, 0xb0, 0x74, 0xc6, 0x16, 0x82, 0xc9, 0x78, 0xac, 0xd4, 0x1d, 0x19, + 0xda, 0xbc, 0x14, 0x2b, 0x9a, 0xf3, 0xdf, 0x53, 0xc9, 0x8b, 0x3c, 0x9e, 0x68, 0x1b, 0x57, 0x86, + 0x59, 0x22, 0x45, 0xc6, 0xe2, 0x3d, 0x9d, 0x25, 0x5c, 0x47, 0xdf, 0x85, 0xd0, 0x1c, 0x86, 0x5c, + 0xc4, 0xfb, 0x4a, 0xb1, 0x15, 0x24, 0x7f, 0xf7, 0x20, 0x3c, 0xa3, 0xd5, 0x7a, 0x5e, 0x50, 0x91, + 0xbe, 0xd3, 0x4d, 0x7c, 0x04, 0xc1, 0x82, 0x65, 0x59, 0x15, 0xf7, 0xa7, 0xfd, 0xc3, 0xf1, 0xd1, + 0x07, 0x0f, 0xdb, 0x2b, 0x6e, 0xfd, 0x9c, 0xb2, 0x2c, 0x23, 0xda, 0x2a, 0xfa, 0x18, 0x42, 0xc9, + 0x36, 0x65, 0x46, 0x25, 0xab, 0x62, 0x5f, 0xfd, 0x24, 0xda, 0xfe, 0xe4, 0xd2, 0xa8, 0xc8, 0xd6, + 0xe8, 0xde, 0x41, 0x83, 0xfb, 0x07, 0x4d, 0xfe, 0xed, 0xc3, 0x5e, 0x67, 0xbb, 0x68, 0x02, 0xde, + 0xad, 0x8a, 0x3c, 0x20, 0xde, 0x2d, 0xa2, 0x46, 0x45, 0x1d, 0x10, 0xaf, 0x41, 0x74, 0xa3, 0x2a, + 0x27, 0x20, 0xde, 0x0d, 0xa2, 0xb5, 0xaa, 0x97, 0x80, 0x78, 0xeb, 0xe8, 0x27, 0x30, 0xfc, 0x5d, + 0xcd, 0x04, 0x67, 0x55, 0x1c, 0xa8, 0xe8, 0xde, 0xdb, 0x46, 0xf7, 0xaa, 0x66, 0xa2, 0x21, 0x56, + 0x8f, 0xd9, 0x50, 0xb5, 0xa6, 0x0b, 0x47, 0xad, 0x51, 0x26, 0xb1, 0x2e, 0x87, 0x5a, 0x86, 0x6b, + 0x93, 0x45, 0x5d, 0x2d, 0x98, 0xc5, 0x5f, 0x82, 0x4f, 0x6f, 0x59, 0x15, 0x87, 0xca, 0xff, 0xf7, + 0xdf, 0x90, 0xb0, 0x87, 0xc7, 0xb7, 0xac, 0xfa, 0x32, 0x97, 0xa2, 0x21, 0xca, 0x3c, 0xfa, 0x31, + 0x0c, 0x16, 0x45, 0x56, 0x88, 0x2a, 0x86, 0xbb, 0x81, 0x9d, 0xa2, 0x9c, 0x18, 0x75, 0x74, 0x08, + 0x83, 0x8c, 0xad, 0x58, 0x9e, 0xaa, 0xba, 0x19, 0x1f, 0x1d, 0x6c, 0x0d, 0x9f, 0x29, 0x39, 0x31, + 0xfa, 0xe8, 0x53, 0x98, 0x48, 0x3a, 0xcf, 0xd8, 0xcb, 0x12, 0xb3, 0x58, 0xa9, 0x1a, 0x1a, 0x1f, + 0xbd, 0xef, 0xdc, 0x87, 0xa3, 0x25, 0x1d, 0xdb, 0xe8, 0x33, 0x98, 0x2c, 0x39, 0xcb, 0x52, 0xfb, + 0xdb, 0x3d, 0x15, 0x54, 0xbc, 0xfd, 0x2d, 0x61, 0x39, 0xdd, 0xe0, 0x2f, 0x1e, 0xa1, 0x19, 0xe9, + 0x58, 0x47, 0xdf, 0x03, 0x90, 0x7c, 0xc3, 0x1e, 0x15, 0x62, 0x43, 0xa5, 0x29, 0x43, 0x47, 0x12, + 0x7d, 0x0e, 0x7b, 0x29, 0x5b, 0xf0, 0x0d, 0xcd, 0x2e, 0x32, 0xba, 0x60, 0x55, 0xfc, 0x9e, 0x0a, + 0xcd, 0xad, 0x2e, 0x57, 0x4d, 0xba, 0xd6, 0x1f, 0x3e, 0x86, 0xb0, 0x4d, 0x1f, 0xf6, 0xf7, 0xd7, + 0xac, 0x51, 0xc5, 0x10, 0x12, 0x5c, 0x46, 0x3f, 0x80, 0xe0, 0x9a, 0x66, 0xb5, 0x2e, 0xe4, 0xf1, + 0xd1, 0xfe, 0xd6, 0xeb, 0xf1, 0x2d, 0xaf, 0x88, 0x56, 0x7e, 0xda, 0xfb, 0x95, 0x97, 0x3c, 0x86, + 0xbd, 0xce, 0x46, 0x18, 0x38, 0xaf, 0xbe, 0xcc, 0x97, 0x85, 0x58, 0xb0, 0x54, 0xf9, 0x1c, 0x11, + 0x47, 0x12, 0xbd, 0x0f, 0x83, 0x94, 0xaf, 0xb8, 0xac, 0x4c, 0xb9, 0x19, 0x94, 0xfc, 0xc3, 0x83, + 0x89, 0x9b, 0xcd, 0xe8, 0xa7, 0x70, 0x70, 0xcd, 0x84, 0xe4, 0x0b, 0x9a, 0x5d, 0xf2, 0x0d, 0xc3, + 0x8d, 0xd5, 0x4f, 0x46, 0xe4, 0x9e, 0x3c, 0xfa, 0x18, 0x06, 0x55, 0x21, 0xe4, 0x49, 0xa3, 0xaa, + 0xf6, 0x6d, 0x59, 0x36, 0x76, 0xc8, 0x53, 0x37, 0x82, 0x96, 0x25, 0xcf, 0x57, 0x96, 0x0b, 0x2d, + 0x8e, 0x7e, 0x04, 0xfb, 0x4b, 0x7e, 0xfb, 0x88, 0x8b, 0x4a, 0x9e, 0x16, 0x59, 0xbd, 0xc9, 0x55, + 0x05, 0x8f, 0xc8, 0x1d, 0xe9, 0x13, 0x7f, 0xe4, 0x1d, 0xf4, 0x9e, 0xf8, 0xa3, 0xe0, 0x60, 0x90, + 0x94, 0xb0, 0xdf, 0xdd, 0x09, 0xdb, 0xd2, 0x06, 0xa1, 0x38, 0x41, 0xa7, 0xb7, 0x23, 0x8b, 0xa6, + 0x30, 0x4e, 0x79, 0x55, 0x66, 0xb4, 0x71, 0x68, 0xc3, 0x15, 0x21, 0x07, 0x5e, 0xf3, 0x8a, 0xcf, + 0x33, 0x4d, 0xe5, 0x23, 0x62, 0x61, 0xb2, 0x82, 0x40, 0x95, 0xb5, 0x43, 0x42, 0xa1, 0x25, 0x21, + 0x45, 0xfd, 0x3d, 0x87, 0xfa, 0x0f, 0xa0, 0xff, 0x6b, 0x76, 0x6b, 0x5e, 0x03, 0x5c, 0xb6, 0x54, + 0xe5, 0x3b, 0x54, 0xf5, 0x00, 0x82, 0x2b, 0x75, 0xed, 0x9a, 0x42, 0x34, 0x48, 0xbe, 0x80, 0x81, + 0x6e, 0x8b, 0xd6, 0xb3, 0xe7, 0x78, 0x9e, 0xc2, 0xf8, 0xa5, 0xe0, 0x2c, 0x97, 0x9a, 0x7c, 0xcc, + 0x11, 0x1c, 0x51, 0xf2, 0x37, 0x0f, 0x7c, 0x75, 0x4b, 0x09, 0x4c, 0x32, 0xb6, 0xa2, 0x8b, 0xe6, + 0xa4, 0xa8, 0xf3, 0xb4, 0x8a, 0xbd, 0x69, 0xff, 0xb0, 0x4f, 0x3a, 0x32, 0x2c, 0x8f, 0xb9, 0xd6, + 0xf6, 0xa6, 0xfd, 0xc3, 0x90, 0x18, 0x84, 0xa1, 0x65, 0x74, 0xce, 0x32, 0x73, 0x04, 0x0d, 0xd0, + 0xba, 0x14, 0x6c, 0xc9, 0x6f, 0xcd, 0x31, 0x0c, 0x42, 0x79, 0x55, 0x2f, 0x51, 0xae, 0x4f, 0x62, + 0x10, 0x1e, 0x60, 0x4e, 0xab, 0x96, 0x91, 0x70, 0x8d, 0x9e, 0xab, 0x05, 0xcd, 0x2c, 0x25, 0x69, + 0x90, 0xfc, 0xd3, 0xc3, 0x87, 0x4c, 0x53, 0xec, 0xbd, 0x0c, 0x7f, 0x1b, 0x46, 0x48, 0xbf, 0x5f, + 0x5d, 0x53, 0x61, 0x0e, 0x3c, 0x44, 0x7c, 0x45, 0x45, 0xf4, 0x0b, 0x18, 0xa8, 0xe6, 0xd8, 0x41, + 0xf7, 0xd6, 0x9d, 0xca, 0x2a, 0x31, 0x66, 0x2d, 0x21, 0xfa, 0x0e, 0x21, 0xb6, 0x87, 0x0d, 0xdc, + 0xc3, 0x7e, 0x04, 0x01, 0x32, 0x6b, 0xa3, 0xa2, 0xdf, 0xe9, 0x59, 0xf3, 0xaf, 0xb6, 0x4a, 0x56, + 0xb0, 0xd7, 0xd9, 0xb1, 0xdd, 0xc9, 0xeb, 0xee, 0xb4, 0x6d, 0xf4, 0xd0, 0x34, 0x36, 0x36, 0x47, + 0xc5, 0x32, 0xb6, 0x90, 0x2c, 0x35, 0x55, 0xd7, 0x62, 0x4b, 0x16, 0x7e, 0x4b, 0x16, 0xc9, 0x9f, + 0xbc, 0xed, 0x4e, 0x2a, 0x02, 0x2c, 0xda, 0x45, 0xb1, 0xd9, 0xd0, 0x3c, 0x35, 0x9b, 0x59, 0x88, + 0x99, 0x4c, 0xe7, 0x66, 0xb3, 0x5e, 0x3a, 0x47, 0x2c, 0x4a, 0x73, 0xa7, 0x3d, 0x51, 0x62, 0x35, + 0x6d, 0x18, 0xad, 0x6a, 0xc1, 0x36, 0x2c, 0x97, 0x66, 0x17, 0x57, 0x14, 0x7d, 0x00, 0x43, 0x49, + 0x57, 0x5f, 0x61, 0x0c, 0xe6, 0x6e, 0x25, 0x5d, 0x3d, 0x65, 0x4d, 0xf4, 0x1d, 0x08, 0x15, 0x83, + 0x2a, 0x95, 0xbe, 0xe0, 0x91, 0x12, 0x3c, 0x65, 0x4d, 0xf2, 0xd7, 0x1e, 0x0c, 0x66, 0x4c, 0x5c, + 0x33, 0xf1, 0x4e, 0x6f, 0xb6, 0x3b, 0x29, 0xf5, 0xdf, 0x32, 0x29, 0xf9, 0xbb, 0x27, 0xa5, 0x60, + 0x3b, 0x29, 0x3d, 0x80, 0x60, 0x26, 0x16, 0xe7, 0x67, 0x2a, 0xa2, 0x3e, 0xd1, 0x00, 0xeb, 0xf3, + 0x78, 0x21, 0xf9, 0x35, 0x33, 0xe3, 0x93, 0x41, 0xf7, 0x9e, 0xf2, 0xd1, 0x8e, 0x99, 0xe5, 0x7f, + 0x9d, 0xa2, 0x6c, 0xd3, 0x82, 0xd3, 0xb4, 0x09, 0x4c, 0x70, 0x94, 0x4a, 0xa9, 0xa4, 0x4f, 0x66, + 0x2f, 0x5f, 0xd8, 0xf9, 0xc9, 0x95, 0x25, 0x7f, 0xf4, 0x60, 0xf0, 0x8c, 0x36, 0x45, 0x2d, 0xef, + 0xd5, 0xff, 0x14, 0xc6, 0xc7, 0x65, 0x99, 0xf1, 0x45, 0xa7, 0xe7, 0x1d, 0x11, 0x5a, 0x3c, 0x77, + 0xee, 0x51, 0xe7, 0xd0, 0x15, 0xe1, 0x13, 0x73, 0xaa, 0xc6, 0x22, 0x3d, 0xe3, 0x38, 0x4f, 0x8c, + 0x9e, 0x86, 0x94, 0x12, 0x93, 0x7d, 0x5c, 0xcb, 0x62, 0x99, 0x15, 0x37, 0x2a, 0xab, 0x23, 0xd2, + 0xe2, 0xe4, 0x5f, 0x3d, 0xf0, 0xbf, 0xa9, 0x51, 0x66, 0x02, 0x1e, 0x37, 0x45, 0xe5, 0xf1, 0x76, + 0xb0, 0x19, 0x3a, 0x83, 0x4d, 0x0c, 0xc3, 0x46, 0xd0, 0x7c, 0xc5, 0xaa, 0x78, 0xa4, 0x78, 0xcd, + 0x42, 0xa5, 0x51, 0x1d, 0xac, 0x27, 0x9a, 0x90, 0x58, 0xd8, 0x76, 0x24, 0x38, 0x1d, 0xf9, 0x73, + 0x33, 0xfc, 0x8c, 0xef, 0x8e, 0x0b, 0xbb, 0x66, 0x9e, 0xff, 0xdf, 0x3b, 0xfe, 0x1f, 0x0f, 0x82, + 0xb6, 0x79, 0x4f, 0xbb, 0xcd, 0x7b, 0xba, 0x6d, 0xde, 0xb3, 0x13, 0xdb, 0xbc, 0x67, 0x27, 0x88, + 0xc9, 0x85, 0x6d, 0x5e, 0x72, 0x81, 0x97, 0xf5, 0x58, 0x14, 0x75, 0x79, 0xd2, 0xe8, 0x5b, 0x0d, + 0x49, 0x8b, 0xb1, 0xe2, 0x7f, 0xb3, 0x66, 0xc2, 0xa4, 0x3a, 0x24, 0x06, 0x61, 0x7f, 0x3c, 0x53, + 0x54, 0xa7, 0x93, 0xab, 0x41, 0xf4, 0x43, 0x08, 0x08, 0x26, 0x4f, 0x65, 0xb8, 0x73, 0x2f, 0x4a, + 0x4c, 0xb4, 0x16, 0x9d, 0xea, 0x4f, 0x22, 0xd3, 0x28, 0xf6, 0x03, 0xe9, 0x67, 0x30, 0x98, 0xad, + 0xf9, 0x52, 0xda, 0x11, 0xf2, 0x5b, 0x0e, 0x55, 0xf2, 0x0d, 0x53, 0x3a, 0x62, 0x4c, 0x92, 0x57, + 0x10, 0xb6, 0xc2, 0x6d, 0x38, 0x9e, 0x1b, 0x4e, 0x04, 0xfe, 0xeb, 0x9c, 0x4b, 0x4b, 0x11, 0xb8, + 0xc6, 0xc3, 0xbe, 0xaa, 0x69, 0x2e, 0xb9, 0x6c, 0x2c, 0x45, 0x58, 0x9c, 0x7c, 0x62, 0xc2, 0x47, + 0x77, 0xaf, 0xcb, 0x92, 0x09, 0x43, 0x37, 0x1a, 0xa8, 0x4d, 0x8a, 0x1b, 0xa6, 0xdf, 0x8e, 0x3e, + 0xd1, 0x20, 0xf9, 0x2d, 0x84, 0xc7, 0x19, 0x13, 0x92, 0xd4, 0x19, 0xdb, 0xf5, 0xa6, 0xab, 0x46, + 0x35, 0x11, 0xe0, 0x7a, 0x4b, 0x2d, 0xfd, 0x3b, 0xd4, 0xf2, 0x94, 0x96, 0xf4, 0xfc, 0x4c, 0xd5, + 0x79, 0x9f, 0x18, 0x94, 0xfc, 0xd9, 0x03, 0x1f, 0x39, 0xcc, 0x71, 0xed, 0xbf, 0x8d, 0xff, 0x2e, + 0x44, 0x71, 0xcd, 0x53, 0x26, 0xec, 0xe1, 0x2c, 0x56, 0x49, 0x5f, 0xac, 0x59, 0x3b, 0x3a, 0x18, + 0x84, 0xb5, 0x86, 0xdf, 0x4f, 0xb6, 0x97, 0x9c, 0x5a, 0x43, 0x31, 0xd1, 0x4a, 0x1c, 0x0f, 0x67, + 0x75, 0xc9, 0xc4, 0x71, 0xba, 0xe1, 0x76, 0xae, 0x72, 0x24, 0xc9, 0x17, 0xfa, 0x8b, 0xec, 0x1e, + 0x13, 0x7a, 0xbb, 0xbf, 0xde, 0xee, 0x46, 0x9e, 0xfc, 0xc5, 0x83, 0xe1, 0x73, 0x33, 0xc7, 0xb9, + 0xa7, 0xf0, 0xde, 0x78, 0x8a, 0x5e, 0xe7, 0x14, 0x47, 0xf0, 0xc0, 0xda, 0x74, 0xf6, 0xd7, 0x59, + 0xd8, 0xa9, 0x33, 0x19, 0xf5, 0xdb, 0xcb, 0x7a, 0x97, 0x0f, 0xb2, 0xcb, 0xae, 0xcd, 0xae, 0x0b, + 0xbf, 0x77, 0x2b, 0x53, 0x18, 0xdb, 0x0f, 0xd1, 0x22, 0xb3, 0x0f, 0x93, 0x2b, 0x4a, 0x8e, 0x60, + 0x70, 0x5a, 0xe4, 0x4b, 0xbe, 0x8a, 0x0e, 0xc1, 0x3f, 0xae, 0xe5, 0x5a, 0x79, 0x1c, 0x1f, 0x3d, + 0x70, 0x1a, 0xbf, 0x96, 0x6b, 0x6d, 0x43, 0x94, 0x45, 0xf2, 0x19, 0xc0, 0x56, 0x86, 0xaf, 0xcb, + 0xf6, 0x36, 0x5e, 0xb0, 0x1b, 0x2c, 0x99, 0xca, 0x8c, 0xf1, 0x3b, 0x34, 0xc9, 0xe7, 0x10, 0x9e, + 0xd4, 0x3c, 0x4b, 0xcf, 0xf3, 0x65, 0x81, 0xd4, 0x71, 0xc5, 0x44, 0xb5, 0xbd, 0x2f, 0x0b, 0x31, + 0xdd, 0xc8, 0x22, 0x6d, 0x0f, 0x19, 0x34, 0x1f, 0xa8, 0xbf, 0x39, 0x3e, 0xf9, 0x6f, 0x00, 0x00, + 0x00, 0xff, 0xff, 0x5f, 0x2f, 0x55, 0x31, 0xf8, 0x10, 0x00, 0x00, } diff --git a/bolt/internal/internal.proto b/bolt/internal/internal.proto index 8fb2b33b6c..27dec2513c 100644 --- a/bolt/internal/internal.proto +++ b/bolt/internal/internal.proto @@ -100,6 +100,7 @@ message TemplateValue { string type = 1; // Type can be tagKey, tagValue, fieldKey, csv, measurement, database, constant string value = 2; // Value is the specific value used to replace a template in an InfluxQL query bool selected = 3; // Selected states that this variable has been picked to use for replacement + string key = 4; // Key is the key for a specific Value if the Template Type is map (optional) } message TemplateQuery { diff --git a/chronograf.go b/chronograf.go index 6a63a02b5a..3faeabe030 100644 --- a/chronograf.go +++ b/chronograf.go @@ -158,9 +158,10 @@ type Range struct { // TemplateValue is a value use to replace a template in an InfluxQL query type TemplateValue struct { - Value string `json:"value"` // Value is the specific value used to replace a template in an InfluxQL query - Type string `json:"type"` // Type can be tagKey, tagValue, fieldKey, csv, measurement, database, constant - Selected bool `json:"selected"` // Selected states that this variable has been picked to use for replacement + Value string `json:"value"` // Value is the specific value used to replace a template in an InfluxQL query + Type string `json:"type"` // Type can be tagKey, tagValue, fieldKey, csv, measurement, database, constant + Selected bool `json:"selected"` // Selected states that this variable has been picked to use for replacement + Key string `json:"key,omitempty"` // Key is the key for the Value if the Template Type is 'map' } // TemplateVar is a named variable within an InfluxQL query to be replaced with Values @@ -176,7 +177,7 @@ type TemplateID string type Template struct { TemplateVar ID TemplateID `json:"id"` // ID is the unique ID associated with this template - Type string `json:"type"` // Type can be fieldKeys, tagKeys, tagValues, CSV, constant, query, measurements, databases + Type string `json:"type"` // Type can be fieldKeys, tagKeys, tagValues, CSV, constant, query, measurements, databases, map Label string `json:"label"` // Label is a user-facing description of the Template Query *TemplateQuery `json:"query,omitempty"` // Query is used to generate the choices for a template } diff --git a/server/templates.go b/server/templates.go index 8b525f6ad2..e70c1dab96 100644 --- a/server/templates.go +++ b/server/templates.go @@ -15,7 +15,7 @@ func ValidTemplateRequest(template *chronograf.Template) error { switch template.Type { default: return fmt.Errorf("Unknown template type %s", template.Type) - case "query", "constant", "csv", "fieldKeys", "tagKeys", "tagValues", "measurements", "databases": + case "query", "constant", "csv", "fieldKeys", "tagKeys", "tagValues", "measurements", "databases", "map": } for _, v := range template.Values { @@ -24,6 +24,10 @@ func ValidTemplateRequest(template *chronograf.Template) error { return fmt.Errorf("Unknown template variable type %s", v.Type) case "csv", "fieldKey", "tagKey", "tagValue", "measurement", "database", "constant": } + + if template.Type == "map" && v.Key == "" { + return fmt.Errorf("Templates of type 'map' require a 'key'") + } } if template.Type == "query" && template.Query == nil { diff --git a/server/templates_test.go b/server/templates_test.go index 8a9bec46f6..83813342bb 100644 --- a/server/templates_test.go +++ b/server/templates_test.go @@ -60,6 +60,36 @@ func TestValidTemplateRequest(t *testing.T) { Type: "query", }, }, + { + name: "Valid Map type", + template: &chronograf.Template{ + Type: "map", + TemplateVar: chronograf.TemplateVar{ + Values: []chronograf.TemplateValue{ + { + Key: "key", + Value: "value", + Type: "constant", + }, + }, + }, + }, + }, + { + name: "Map without Key", + wantErr: true, + template: &chronograf.Template{ + Type: "map", + TemplateVar: chronograf.TemplateVar{ + Values: []chronograf.TemplateValue{ + { + Value: "value", + Type: "constant", + }, + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From 6476e603a303c3c902134ce5ae13cd6e55bbc513 Mon Sep 17 00:00:00 2001 From: Iris Scholten Date: Tue, 19 Jun 2018 14:57:48 -0700 Subject: [PATCH 09/22] Update swagger --- server/swagger.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/swagger.json b/server/swagger.json index 8c3f36db58..ea89a0b958 100644 --- a/server/swagger.json +++ b/server/swagger.json @@ -4090,6 +4090,10 @@ "enum": ["csv", "tagKey", "tagValue", "fieldKey", "timeStamp"], "description": "The type will change the format of the output value. tagKey/fieldKey are double quoted; tagValue are single quoted; csv and timeStamp are not quoted." + }, + "key": { + "type": "string", + "description":"This will be the key for a specific value of a template variable. Used if the templateVar type is 'map'" } } }, From 68aa388028fd34c6cd34124d2cab3947f3aaa852 Mon Sep 17 00:00:00 2001 From: Iris Scholten Date: Tue, 19 Jun 2018 16:19:15 -0700 Subject: [PATCH 10/22] Change Ascending arrows to point up instead of down --- ui/src/style/components/table-graph.scss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/src/style/components/table-graph.scss b/ui/src/style/components/table-graph.scss index e5052dfc75..b79d9a5844 100644 --- a/ui/src/style/components/table-graph.scss +++ b/ui/src/style/components/table-graph.scss @@ -86,7 +86,7 @@ position: absolute; top: 50%; right: 6px; - transform: translateY(-50%); + transform: translateY(-50%) rotate(180deg); font-size: 13px; opacity: 0; transition: opacity 0.25s ease, color 0.25s ease, transform 0.25s ease; @@ -107,10 +107,10 @@ } } &__sort-asc:before { - transform: translateY(-50%) rotate(0deg); + transform: translateY(-50%) rotate(180deg); } &__sort-desc:before { - transform: translateY(-50%) rotate(180deg); + transform: translateY(-50%) rotate(0deg); } } From 23a5acc959855a29e121ef1820c2443f6742c3c2 Mon Sep 17 00:00:00 2001 From: Alirie Gray Date: Mon, 18 Jun 2018 16:43:16 -0700 Subject: [PATCH 11/22] Implement custom meta query template variable type Co-authored-by: Chris Henn Co-authored-by: Alirie Gray --- ui/src/shared/parsing/showSeries.ts | 26 +++ .../components/MetaQueryTemplateBuilder.tsx | 166 ++++++++++++++++++ .../components/TemplateVariableEditor.tsx | 2 + ui/src/tempVars/constants/index.ts | 16 ++ ui/src/tempVars/utils/parsing.ts | 84 +++++++++ ui/src/types/tempVars.ts | 1 + 6 files changed, 295 insertions(+) create mode 100644 ui/src/shared/parsing/showSeries.ts create mode 100644 ui/src/tempVars/components/MetaQueryTemplateBuilder.tsx create mode 100644 ui/src/tempVars/utils/parsing.ts diff --git a/ui/src/shared/parsing/showSeries.ts b/ui/src/shared/parsing/showSeries.ts new file mode 100644 index 0000000000..3cad741e04 --- /dev/null +++ b/ui/src/shared/parsing/showSeries.ts @@ -0,0 +1,26 @@ +interface ParseShowSeriesResponse { + errors: string[] + series: string[] +} + +const parseShowSeries = (response): ParseShowSeriesResponse => { + const results = response.results[0] + + if (results.error) { + return {errors: [results.error], series: []} + } + + const series = results.series[0] + + if (!series.values) { + return {errors: [], series: []} + } + + const seriesValues = series.values.map(s => { + return s[0] + }) + + return {errors: [], series: seriesValues} +} + +export default parseShowSeries diff --git a/ui/src/tempVars/components/MetaQueryTemplateBuilder.tsx b/ui/src/tempVars/components/MetaQueryTemplateBuilder.tsx new file mode 100644 index 0000000000..b78d64b5fd --- /dev/null +++ b/ui/src/tempVars/components/MetaQueryTemplateBuilder.tsx @@ -0,0 +1,166 @@ +import React, {PureComponent, ChangeEvent} from 'react' +import _ from 'lodash' + +import {proxy} from 'src/utils/queryUrlGenerator' +import {ErrorHandling} from 'src/shared/decorators/errors' +import TemplateMetaQueryPreview from 'src/tempVars/components/TemplateMetaQueryPreview' +import {parseMetaQuery, isInvalidMetaQuery} from 'src/tempVars/utils/parsing' + +import { + TemplateBuilderProps, + RemoteDataState, + TemplateValueType, +} from 'src/types' + +// TODO: Ensure save is wired up to changes + +const DEBOUNCE_DELAY = 750 + +interface State { + metaQueryInput: string // bound to input + metaQuery: string // debounced view of metaQueryInput + metaQueryResults: string[] + metaQueryResultsStatus: RemoteDataState +} + +@ErrorHandling +class CustomMetaQueryTemplateBuilder extends PureComponent< + TemplateBuilderProps, + State +> { + private handleMetaQueryChange: () => void = _.debounce(() => { + const {metaQuery, metaQueryInput} = this.state + const {template, onUpdateTemplate} = this.props + + if (metaQuery === metaQueryInput) { + return + } + + this.setState({metaQuery: metaQueryInput}, this.executeQuery) + + const nextTemplate = {...template, query: {influxql: metaQueryInput}} + + onUpdateTemplate(nextTemplate) + }, DEBOUNCE_DELAY) + + constructor(props) { + super(props) + + this.state = { + metaQueryInput: '', + metaQuery: '', + metaQueryResults: [], + metaQueryResultsStatus: RemoteDataState.NotStarted, + } + } + + public render() { + const {metaQueryInput} = this.state + + return ( +
+
+ +
+