feat(demodata): Notify on demodata success and failure. (#17902)
* feat(demodata): Do not delete dashboard when delete demodata bucket * feat(demodata): Refactor demodata dropdown * feat(demodata): Fetch DD_Buckets in DD_Dropdown * feat(demodata): Refactor notifications Co-authored-by: alexpaxton <thealexpaxton@gmail.com> * feat(demodata): Style demo data dropdown * feat(demodata): Remove double console logging of error * feat(demodata): Error on success and failure * feat(demodata): Remove duplicate console.error * feat(demodata): fix lint errors * feat(demodata): Reintroduce Notification Style and add testID * feat(demodata): Add dd success notification and links to notifications Co-authored-by: alexpaxton <thealexpaxton@gmail.com> * feat(demodata): fix test * feat(demodata): Simplify getDemoDataBucketMembership function * feat(demodata): Remove unused notification copy * feat(demodata): Extend notification duration * feat(demodata): Return null instead of false * feat(demodata): Do not console.error if also notifying user * feat(demodata): Add todo item Co-authored-by: alexpaxton <thealexpaxton@gmail.com>pull/17915/head
parent
463c8bab1f
commit
080d77751b
|
@ -61,7 +61,7 @@ describe('The Query Builder', () => {
|
|||
// wait for the notification since it's highly animated
|
||||
// we close the notification since it contains the name of the dashboard and interfers with cy.contains
|
||||
cy.wait(250)
|
||||
cy.get('.notification-close').click()
|
||||
cy.get('.cf-notification--dismiss').click()
|
||||
cy.wait(250)
|
||||
|
||||
// force a click on the hidden dashboard nav item (cypress can't do the hover)
|
||||
|
|
|
@ -33,16 +33,10 @@ import {
|
|||
checkBucketLimits as checkBucketLimitsAction,
|
||||
LimitStatus,
|
||||
} from 'src/cloud/actions/limits'
|
||||
import {
|
||||
getDemoDataBuckets as getDemoDataBucketsAction,
|
||||
getDemoDataBucketMembership as getDemoDataBucketMembershipAction,
|
||||
} from 'src/cloud/actions/demodata'
|
||||
|
||||
// Utils
|
||||
import {getNewDemoBuckets} from 'src/cloud/selectors/demodata'
|
||||
import {extractBucketLimits} from 'src/cloud/utils/limits'
|
||||
import {getAll} from 'src/resources/selectors'
|
||||
import {isFlagEnabled} from 'src/shared/utils/featureFlag'
|
||||
import {SortTypes} from 'src/shared/utils/sort'
|
||||
|
||||
// Types
|
||||
|
@ -52,7 +46,6 @@ import {BucketSortKey} from 'src/shared/components/resource_sort_dropdown/genera
|
|||
interface StateProps {
|
||||
buckets: Bucket[]
|
||||
limitStatus: LimitStatus
|
||||
demoDataBuckets: Bucket[]
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
|
@ -60,8 +53,6 @@ interface DispatchProps {
|
|||
updateBucket: typeof updateBucket
|
||||
deleteBucket: typeof deleteBucket
|
||||
checkBucketLimits: typeof checkBucketLimitsAction
|
||||
getDemoDataBuckets: typeof getDemoDataBucketsAction
|
||||
getDemoDataBucketMembership: typeof getDemoDataBucketMembershipAction
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
@ -90,18 +81,10 @@ class BucketsTab extends PureComponent<Props, State> {
|
|||
|
||||
public componentDidMount() {
|
||||
this.props.checkBucketLimits()
|
||||
if (isFlagEnabled('demodata')) {
|
||||
this.props.getDemoDataBuckets()
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {
|
||||
buckets,
|
||||
limitStatus,
|
||||
demoDataBuckets,
|
||||
getDemoDataBucketMembership,
|
||||
} = this.props
|
||||
const {buckets, limitStatus} = this.props
|
||||
const {searchTerm, sortKey, sortDirection, sortType} = this.state
|
||||
|
||||
const leftHeaderItems = (
|
||||
|
@ -125,12 +108,7 @@ class BucketsTab extends PureComponent<Props, State> {
|
|||
const rightHeaderItems = (
|
||||
<>
|
||||
<FeatureFlag name="demodata">
|
||||
{demoDataBuckets.length > 0 && (
|
||||
<DemoDataDropdown
|
||||
buckets={demoDataBuckets}
|
||||
getMembership={getDemoDataBucketMembership}
|
||||
/>
|
||||
)}
|
||||
<DemoDataDropdown />
|
||||
</FeatureFlag>
|
||||
<CreateBucketButton />
|
||||
</>
|
||||
|
@ -229,7 +207,6 @@ const mstp = (state: AppState): StateProps => {
|
|||
return {
|
||||
buckets,
|
||||
limitStatus: extractBucketLimits(state.cloud.limits),
|
||||
demoDataBuckets: getNewDemoBuckets(state, buckets),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -238,8 +215,6 @@ const mdtp: DispatchProps = {
|
|||
updateBucket,
|
||||
deleteBucket,
|
||||
checkBucketLimits: checkBucketLimitsAction,
|
||||
getDemoDataBuckets: getDemoDataBucketsAction,
|
||||
getDemoDataBucketMembership: getDemoDataBucketMembershipAction,
|
||||
}
|
||||
|
||||
export default connect<StateProps, DispatchProps, {}>(
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
.demodata-dropdown--item-contents {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.demodata-dropdown--item-icon {
|
||||
margin-right: $cf-marg-b;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.demodata-dropdown--item__added,
|
||||
.demodata-dropdown--item__added:hover {
|
||||
.demodata-dropdown--item-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
cursor: default;
|
||||
background: none !important;
|
||||
color: $c-honeydew !important;
|
||||
}
|
|
@ -1,31 +1,97 @@
|
|||
// Libraries
|
||||
import React, {FC} from 'react'
|
||||
import _ from 'lodash'
|
||||
import React, {FC, useEffect} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
import {get, sortBy} from 'lodash'
|
||||
|
||||
// Utils
|
||||
import {getAll} from 'src/resources/selectors'
|
||||
|
||||
// Actions
|
||||
import {
|
||||
getDemoDataBucketMembership as getDemoDataBucketMembershipAction,
|
||||
getDemoDataBuckets as getDemoDataBucketsAction,
|
||||
} from 'src/cloud/actions/demodata'
|
||||
|
||||
// Components
|
||||
import {IconFont, ComponentColor, Dropdown} from '@influxdata/clockface'
|
||||
import {ComponentColor, Dropdown, Icon, IconFont} from '@influxdata/clockface'
|
||||
|
||||
// Types
|
||||
import {Bucket} from 'src/types'
|
||||
import {getDemoDataBucketMembership} from 'src/cloud/actions/demodata'
|
||||
import {AppState, Bucket, ResourceType} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
buckets: Bucket[]
|
||||
getMembership: typeof getDemoDataBucketMembership
|
||||
interface StateProps {
|
||||
ownBuckets: Bucket[]
|
||||
demoDataBuckets: Bucket[]
|
||||
}
|
||||
|
||||
const DemoDataDropdown: FC<Props> = ({buckets, getMembership}) => {
|
||||
const demoDataItems = buckets.map(b => (
|
||||
<Dropdown.Item
|
||||
testID={`dropdown-item--demodata-${b.name}`}
|
||||
id={b.id}
|
||||
key={b.id}
|
||||
value={b}
|
||||
onClick={getMembership}
|
||||
>
|
||||
{b.name}
|
||||
</Dropdown.Item>
|
||||
))
|
||||
interface DispatchProps {
|
||||
getDemoDataBucketMembership: typeof getDemoDataBucketMembershipAction
|
||||
getDemoDataBuckets: typeof getDemoDataBucketsAction
|
||||
}
|
||||
|
||||
type Props = DispatchProps & StateProps
|
||||
|
||||
const DemoDataDropdown: FC<Props> = ({
|
||||
ownBuckets,
|
||||
demoDataBuckets,
|
||||
getDemoDataBucketMembership,
|
||||
getDemoDataBuckets,
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
getDemoDataBuckets()
|
||||
}, [])
|
||||
|
||||
if (!demoDataBuckets.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
const ownBucketNames = ownBuckets.map(o => o.name.toLocaleLowerCase())
|
||||
|
||||
const sortedBuckets = sortBy(demoDataBuckets, d => {
|
||||
return d.name.toLocaleLowerCase()
|
||||
})
|
||||
|
||||
const dropdownItems = sortedBuckets.map(b => {
|
||||
if (ownBucketNames.includes(b.name.toLocaleLowerCase())) {
|
||||
return (
|
||||
<Dropdown.Item
|
||||
testID={`dropdown-item--demodata-${b.name}`}
|
||||
className="demodata-dropdown--item__added"
|
||||
id={b.id}
|
||||
key={b.id}
|
||||
value={b}
|
||||
selected={true}
|
||||
>
|
||||
<div className="demodata-dropdown--item-contents">
|
||||
<Icon
|
||||
glyph={IconFont.Checkmark}
|
||||
className="demodata-dropdown--item-icon"
|
||||
/>
|
||||
{b.name}
|
||||
</div>
|
||||
</Dropdown.Item>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropdown.Item
|
||||
testID={`dropdown-item--demodata-${b.name}`}
|
||||
className="demodata-dropdown--item"
|
||||
id={b.id}
|
||||
key={b.id}
|
||||
value={b}
|
||||
onClick={getDemoDataBucketMembership}
|
||||
selected={false}
|
||||
>
|
||||
<div className="demodata-dropdown--item-contents">
|
||||
<Icon
|
||||
glyph={IconFont.Checkmark}
|
||||
className="demodata-dropdown--item-icon"
|
||||
/>
|
||||
{b.name}
|
||||
</div>
|
||||
</Dropdown.Item>
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
|
@ -43,10 +109,23 @@ const DemoDataDropdown: FC<Props> = ({buckets, getMembership}) => {
|
|||
</Dropdown.Button>
|
||||
)}
|
||||
menu={onCollapse => (
|
||||
<Dropdown.Menu onCollapse={onCollapse}>{demoDataItems}</Dropdown.Menu>
|
||||
<Dropdown.Menu onCollapse={onCollapse}>{dropdownItems}</Dropdown.Menu>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default DemoDataDropdown
|
||||
const mstp = (state: AppState): StateProps => ({
|
||||
ownBuckets: getAll<Bucket>(state, ResourceType.Buckets),
|
||||
demoDataBuckets: get(state, 'cloud.demoData.buckets', []) as Bucket[],
|
||||
})
|
||||
|
||||
const mdtp: DispatchProps = {
|
||||
getDemoDataBucketMembership: getDemoDataBucketMembershipAction,
|
||||
getDemoDataBuckets: getDemoDataBucketsAction,
|
||||
}
|
||||
|
||||
export default connect<StateProps, DispatchProps, {}>(
|
||||
mstp,
|
||||
mdtp
|
||||
)(DemoDataDropdown)
|
||||
|
|
|
@ -3,21 +3,26 @@ import {
|
|||
getDemoDataBuckets as getDemoDataBucketsAJAX,
|
||||
getDemoDataBucketMembership as getDemoDataBucketMembershipAJAX,
|
||||
deleteDemoDataBucketMembership as deleteDemoDataBucketMembershipAJAX,
|
||||
getNormalizedDemoDataBucket,
|
||||
} from 'src/cloud/apis/demodata'
|
||||
import {createDashboardFromTemplate} from 'src/templates/api'
|
||||
import {deleteDashboard, getBucket} from 'src/client'
|
||||
import {getBucket} from 'src/client'
|
||||
|
||||
// Actions
|
||||
import {getDashboards} from 'src/dashboards/actions/thunks'
|
||||
import {addBucket, removeBucket} from 'src/buckets/actions/creators'
|
||||
import {notify} from 'src/shared/actions/notifications'
|
||||
|
||||
// Selectors
|
||||
import {getOrg} from 'src/organizations/selectors'
|
||||
import {getAll} from 'src/resources/selectors/getAll'
|
||||
import {normalize} from 'normalizr'
|
||||
import {getAll} from 'src/resources/selectors'
|
||||
|
||||
// Constants
|
||||
import {DemoDataTemplates, DemoDataDashboards} from 'src/cloud/constants'
|
||||
import {
|
||||
demoDataAddBucketFailed,
|
||||
demoDataDeleteBucketFailed,
|
||||
demoDataSucceeded,
|
||||
} from 'src/shared/copy/notifications'
|
||||
|
||||
// Types
|
||||
import {
|
||||
|
@ -25,11 +30,10 @@ import {
|
|||
RemoteDataState,
|
||||
GetState,
|
||||
DemoBucket,
|
||||
Dashboard,
|
||||
ResourceType,
|
||||
BucketEntities,
|
||||
Dashboard,
|
||||
} from 'src/types'
|
||||
import {bucketSchema} from 'src/schemas'
|
||||
import {reportError} from 'src/shared/utils/errors'
|
||||
|
||||
export type Actions =
|
||||
| ReturnType<typeof setDemoDataStatus>
|
||||
|
@ -57,88 +61,74 @@ export const getDemoDataBuckets = () => async (
|
|||
if (status === RemoteDataState.NotStarted) {
|
||||
dispatch(setDemoDataStatus(RemoteDataState.Loading))
|
||||
}
|
||||
|
||||
try {
|
||||
const buckets = await getDemoDataBucketsAJAX()
|
||||
|
||||
dispatch(setDemoDataStatus(RemoteDataState.Done))
|
||||
dispatch(setDemoDataBuckets(buckets))
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
reportError(error, {
|
||||
name: 'getDemoDataBuckets function',
|
||||
})
|
||||
|
||||
dispatch(setDemoDataStatus(RemoteDataState.Error))
|
||||
}
|
||||
}
|
||||
|
||||
export const getDemoDataBucketMembership = (bucket: DemoBucket) => async (
|
||||
dispatch,
|
||||
getState: GetState
|
||||
) => {
|
||||
export const getDemoDataBucketMembership = ({
|
||||
name: bucketName,
|
||||
id: bucketID,
|
||||
}) => async (dispatch, getState: GetState) => {
|
||||
const state = getState()
|
||||
|
||||
const {
|
||||
me: {id: userID},
|
||||
} = state
|
||||
|
||||
const {id: orgID} = getOrg(state)
|
||||
|
||||
try {
|
||||
await getDemoDataBucketMembershipAJAX(bucket.id, userID)
|
||||
await getDemoDataBucketMembershipAJAX(bucketID, userID)
|
||||
|
||||
const template = await DemoDataTemplates[bucket.name]
|
||||
if (template) {
|
||||
await createDashboardFromTemplate(template, orgID)
|
||||
} else {
|
||||
const normalizedBucket = await getNormalizedDemoDataBucket(bucketID)
|
||||
dispatch(addBucket(normalizedBucket))
|
||||
|
||||
const template = await DemoDataTemplates[bucketName]
|
||||
if (!template) {
|
||||
throw new Error(
|
||||
`Could not find template for demodata bucket ${bucket.name}`
|
||||
`Could not find dashboard template for demodata bucket ${bucketName}`
|
||||
)
|
||||
}
|
||||
|
||||
const resp = await getBucket({bucketID: bucket.id})
|
||||
|
||||
if (resp.status !== 200) {
|
||||
throw new Error('Request for demo data bucket membership did not succeed')
|
||||
}
|
||||
|
||||
const newBucket = {
|
||||
...resp.data,
|
||||
type: 'demodata' as 'demodata',
|
||||
labels: [],
|
||||
} as DemoBucket
|
||||
|
||||
const normalizedBucket = normalize<Bucket, BucketEntities, string>(
|
||||
newBucket,
|
||||
bucketSchema
|
||||
)
|
||||
|
||||
dispatch(addBucket(normalizedBucket))
|
||||
|
||||
// TODO: notify success and error appropriately
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
export const deleteDemoDataDashboard = (dashboardName: string) => async (
|
||||
dispatch,
|
||||
getState: GetState
|
||||
) => {
|
||||
try {
|
||||
await dispatch(getDashboards())
|
||||
|
||||
await createDashboardFromTemplate(template, orgID)
|
||||
const updatedState = getState()
|
||||
|
||||
const ddDashboard = getAll(updatedState, ResourceType.Dashboards).find(
|
||||
d => {
|
||||
d.name === dashboardName
|
||||
}
|
||||
) as Dashboard
|
||||
const allDashboards = getAll<Dashboard>(
|
||||
updatedState,
|
||||
ResourceType.Dashboards
|
||||
)
|
||||
|
||||
if (ddDashboard) {
|
||||
const deleteResp = await deleteDashboard({
|
||||
dashboardID: ddDashboard.id,
|
||||
})
|
||||
if (deleteResp.status !== 204) {
|
||||
throw new Error(deleteResp.data.message)
|
||||
}
|
||||
const createdDashboard = allDashboards.find(
|
||||
d => d.name === DemoDataDashboards[bucketName]
|
||||
)
|
||||
|
||||
if (!createdDashboard) {
|
||||
throw new Error(
|
||||
`Could not create dashboard for demodata bucket ${bucketName}`
|
||||
)
|
||||
}
|
||||
|
||||
const url = `/orgs/${orgID}/dashboards/${createdDashboard.id}`
|
||||
|
||||
dispatch(notify(demoDataSucceeded(bucketName, url)))
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
dispatch(notify(demoDataAddBucketFailed(error)))
|
||||
|
||||
reportError(error, {
|
||||
name: 'getDemoDataBucketMembership function',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -160,18 +150,11 @@ export const deleteDemoDataBucketMembership = (bucket: DemoBucket) => async (
|
|||
}
|
||||
|
||||
dispatch(removeBucket(bucket.id))
|
||||
|
||||
const demoDashboardName = DemoDataDashboards[bucket.name]
|
||||
|
||||
if (!demoDashboardName) {
|
||||
throw new Error(
|
||||
`Could not find dashboard name for demo data bucket ${bucket.name}`
|
||||
)
|
||||
}
|
||||
|
||||
dispatch(deleteDemoDataDashboard(demoDashboardName))
|
||||
// TODO: notify for success and error appropriately
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
dispatch(notify(demoDataDeleteBucketFailed(bucket.name, error)))
|
||||
|
||||
reportError(error, {
|
||||
name: 'deleteDemoDataBucketMembership function',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,56 +1,50 @@
|
|||
// Libraries
|
||||
import {get} from 'lodash'
|
||||
import {getBuckets} from 'src/client'
|
||||
import {getBuckets, getBucket} from 'src/client'
|
||||
import AJAX from 'src/utils/ajax'
|
||||
|
||||
//Utils
|
||||
import {isFlagEnabled} from 'src/shared/utils/featureFlag'
|
||||
|
||||
//Types
|
||||
import {Bucket, DemoBucket} from 'src/types'
|
||||
import {Bucket, DemoBucket, BucketEntities} from 'src/types'
|
||||
import {LIMIT} from 'src/resources/constants'
|
||||
import {normalize} from 'normalizr'
|
||||
import {bucketSchema} from 'src/schemas'
|
||||
import {NormalizedSchema} from 'normalizr'
|
||||
|
||||
const baseURL = '/api/v2/experimental/sampledata'
|
||||
|
||||
export const getDemoDataBuckets = async (): Promise<Bucket[]> => {
|
||||
try {
|
||||
const {data} = await AJAX({
|
||||
method: 'GET',
|
||||
url: `${baseURL}/buckets`,
|
||||
})
|
||||
//todo (deniz) convert to fetch
|
||||
const {data} = await AJAX({
|
||||
method: 'GET',
|
||||
url: `${baseURL}/buckets`,
|
||||
})
|
||||
|
||||
// if sampledata endpoints are not available in a cluster
|
||||
// gateway responds with a list of links where 'buckets' field is a string
|
||||
const buckets = get(data, 'buckets', false)
|
||||
if (!Array.isArray(buckets)) {
|
||||
throw new Error('Could not reach demodata endpoint')
|
||||
}
|
||||
|
||||
return buckets.filter(b => b.type == 'user') as Bucket[] // remove returned _tasks and _monitoring buckets
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
throw error
|
||||
// if sampledata endpoints are not available in a cluster
|
||||
// gateway responds with a list of links where 'buckets' field is a string
|
||||
const buckets = get(data, 'buckets', null)
|
||||
if (!Array.isArray(buckets)) {
|
||||
throw new Error('Could not reach demodata endpoint')
|
||||
}
|
||||
|
||||
return buckets.filter(b => b.type == 'user') as Bucket[] // remove returned _tasks and _monitoring buckets
|
||||
}
|
||||
|
||||
export const getDemoDataBucketMembership = async (
|
||||
bucketID: string,
|
||||
userID: string
|
||||
) => {
|
||||
try {
|
||||
const response = await AJAX({
|
||||
method: 'POST',
|
||||
url: `${baseURL}/buckets/${bucketID}/members`,
|
||||
data: {userID},
|
||||
})
|
||||
const response = await AJAX({
|
||||
method: 'POST',
|
||||
url: `${baseURL}/buckets/${bucketID}/members`,
|
||||
data: {userID},
|
||||
})
|
||||
|
||||
if (response.status === '200') {
|
||||
// a failed or successful membership POST to sampledata should return 204
|
||||
throw new Error('Could not reach demodata endpoint')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
throw error
|
||||
if (response.status === '200') {
|
||||
// a failed or successful membership POST to sampledata should return 204
|
||||
throw new Error('Could not reach demodata endpoint')
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,3 +100,30 @@ export const fetchDemoDataBuckets = async (): Promise<Bucket[]> => {
|
|||
return [] // demodata bucket fetching errors should not effect regular bucket fetching
|
||||
}
|
||||
}
|
||||
|
||||
export const getNormalizedDemoDataBucket = async (
|
||||
bucketID: string
|
||||
): Promise<NormalizedSchema<BucketEntities, string>> => {
|
||||
const resp = await getBucket({bucketID})
|
||||
|
||||
if (resp.status !== 200) {
|
||||
throw new Error(
|
||||
`Request for demo data bucket membership did not succeed: ${
|
||||
resp.data.message
|
||||
}`
|
||||
)
|
||||
}
|
||||
|
||||
const newBucket = {
|
||||
...resp.data,
|
||||
type: 'demodata' as 'demodata',
|
||||
labels: [],
|
||||
} as DemoBucket
|
||||
|
||||
const normalizedBucket = normalize<Bucket, BucketEntities, string>(
|
||||
newBucket,
|
||||
bucketSchema
|
||||
)
|
||||
|
||||
return normalizedBucket
|
||||
}
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
import {get, differenceBy, sortBy} from 'lodash'
|
||||
import {AppState, Bucket, DemoBucket} from 'src/types'
|
||||
|
||||
export const getNewDemoBuckets = (state: AppState, ownBuckets: Bucket[]) => {
|
||||
const demoDataBuckets = get(
|
||||
state,
|
||||
'cloud.demoData.buckets',
|
||||
[]
|
||||
) as DemoBucket[]
|
||||
|
||||
const newDemoDataBuckets = differenceBy(
|
||||
demoDataBuckets,
|
||||
ownBuckets,
|
||||
b => b.id
|
||||
)
|
||||
|
||||
return sortBy(newDemoDataBuckets, d => {
|
||||
return d.name.toLocaleLowerCase()
|
||||
})
|
||||
}
|
|
@ -104,7 +104,7 @@ export class OnboardingWizardPage extends PureComponent<Props, State> {
|
|||
loading={this.state.loading}
|
||||
spinnerComponent={<TechnoSpinner />}
|
||||
>
|
||||
<Notifications inPresentationMode={true} />
|
||||
<Notifications />
|
||||
<OnboardingWizard
|
||||
onDecrementCurrentStepIndex={this.handleDecrementStepIndex}
|
||||
onIncrementCurrentStepIndex={this.handleIncrementStepIndex}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
.notification--button {
|
||||
display: inline-block;
|
||||
margin: $cf-marg-a;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.notification--message {
|
||||
display: inline-block;
|
||||
margin-right: $cf-marg-a;
|
||||
}
|
|
@ -1,137 +0,0 @@
|
|||
import React, {Component, CSSProperties} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
import {bindActionCreators} from 'redux'
|
||||
import {Notification as NotificationType} from 'src/types/notifications'
|
||||
|
||||
import classnames from 'classnames'
|
||||
|
||||
import {dismissNotification as dismissNotificationAction} from 'src/shared/actions/notifications'
|
||||
|
||||
import {NOTIFICATION_TRANSITION} from 'src/shared/constants/index'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
interface Props {
|
||||
notification: NotificationType
|
||||
dismissNotification: (id: string) => void
|
||||
}
|
||||
|
||||
interface State {
|
||||
opacity: number
|
||||
height: number
|
||||
dismissed: boolean
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class Notification extends Component<Props, State> {
|
||||
private notificationRef: HTMLElement
|
||||
private dismissalTimer: number
|
||||
private deletionTimer: number
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
opacity: 1,
|
||||
height: 0,
|
||||
dismissed: false,
|
||||
}
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
const {
|
||||
notification: {duration},
|
||||
} = this.props
|
||||
|
||||
this.updateHeight()
|
||||
|
||||
if (duration >= 0) {
|
||||
// Automatically dismiss notification after duration prop
|
||||
this.dismissalTimer = window.setTimeout(this.handleDismiss, duration)
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
clearTimeout(this.dismissalTimer)
|
||||
clearTimeout(this.deletionTimer)
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {
|
||||
notification: {message, icon},
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<div className={this.containerClassname} style={this.notificationStyle}>
|
||||
<div
|
||||
className={this.notificationClassname}
|
||||
ref={this.handleNotificationRef}
|
||||
data-testid={this.dataTestID}
|
||||
>
|
||||
<span className={`icon ${icon}`} />
|
||||
<div className="notification-message">{message}</div>
|
||||
<button className="notification-close" onClick={this.handleDismiss} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private get dataTestID(): string {
|
||||
const {style} = this.props.notification
|
||||
return `notification-${style}`
|
||||
}
|
||||
|
||||
private get notificationClassname(): string {
|
||||
const {
|
||||
notification: {style},
|
||||
} = this.props
|
||||
|
||||
return `notification notification-${style}`
|
||||
}
|
||||
|
||||
private get containerClassname(): string {
|
||||
const {height, dismissed} = this.state
|
||||
|
||||
return classnames('notification-container', {
|
||||
show: !!height,
|
||||
'notification-dismissed': dismissed,
|
||||
})
|
||||
}
|
||||
|
||||
private get notificationStyle(): CSSProperties {
|
||||
return {height: '100%'}
|
||||
}
|
||||
|
||||
private updateHeight = (): void => {
|
||||
if (this.notificationRef) {
|
||||
const {height} = this.notificationRef.getBoundingClientRect()
|
||||
this.setState({height})
|
||||
}
|
||||
}
|
||||
|
||||
private handleDismiss = (): void => {
|
||||
const {
|
||||
notification: {id},
|
||||
dismissNotification,
|
||||
} = this.props
|
||||
|
||||
this.setState({dismissed: true})
|
||||
this.deletionTimer = window.setTimeout(
|
||||
() => dismissNotification(id),
|
||||
NOTIFICATION_TRANSITION
|
||||
)
|
||||
}
|
||||
|
||||
private handleNotificationRef = (ref: HTMLElement): void => {
|
||||
this.notificationRef = ref
|
||||
this.updateHeight()
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
dismissNotification: bindActionCreators(dismissNotificationAction, dispatch),
|
||||
})
|
||||
|
||||
export default connect(
|
||||
null,
|
||||
mapDispatchToProps
|
||||
)(Notification)
|
|
@ -1,196 +0,0 @@
|
|||
/*
|
||||
Notifications
|
||||
-----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
$notification-margin: 12px;
|
||||
|
||||
.notification-center {
|
||||
position: fixed;
|
||||
right: $notification-margin;
|
||||
width: 360px;
|
||||
top: $chronograf-page-header-height + $notification-margin;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.notification-center__presentation-mode {
|
||||
@extend .notification-center;
|
||||
top: $notification-margin;
|
||||
}
|
||||
|
||||
.notification {
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
border-radius: $ix-radius;
|
||||
position: relative;
|
||||
padding: 12px 40px;
|
||||
@extend %no-user-select;
|
||||
transform: translateX(105%);
|
||||
transition: transform 0.25s ease 0.25s, opacity 0.25s ease;
|
||||
|
||||
> span.icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 20px;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: $ix-text-base-2;
|
||||
}
|
||||
}
|
||||
|
||||
.notification-message {
|
||||
&:first-letter {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.notification-close {
|
||||
outline: none;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
transform: translateY(-50%);
|
||||
right: ($ix-marg-c - $ix-marg-a);
|
||||
font-size: $ix-text-base;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
opacity: 0.25;
|
||||
transition: opacity 0.25s ease;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 16px;
|
||||
height: 2px;
|
||||
border-radius: 1px;
|
||||
background-color: $g20-white;
|
||||
}
|
||||
&:before {
|
||||
transform: translate(-50%, -50%) rotate(-45deg);
|
||||
}
|
||||
&:after {
|
||||
transform: translate(-50%, -50%) rotate(45deg);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.notification-container {
|
||||
overflow: hidden;
|
||||
height: 0;
|
||||
margin-bottom: $ix-marg-a;
|
||||
transition: height 0.25s ease;
|
||||
|
||||
&.show .notification {
|
||||
transform: translateX(0);
|
||||
}
|
||||
&.notification-dismissed {
|
||||
height: 0 !important;
|
||||
.notification {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mixin for Alert Themes
|
||||
// ----------------------------------------------------------------------------
|
||||
@mixin notification-styles(
|
||||
$bg-color,
|
||||
$bg-color-2,
|
||||
$text-color,
|
||||
$link-color,
|
||||
$link-hover
|
||||
) {
|
||||
font-size: 16px;
|
||||
|
||||
@include gradient-h($bg-color, $bg-color-2);
|
||||
color: $text-color;
|
||||
|
||||
a:link,
|
||||
a:visited {
|
||||
color: $link-color;
|
||||
font-weight: 700;
|
||||
text-decoration: underline;
|
||||
transition: color 0.25s ease;
|
||||
}
|
||||
a:hover {
|
||||
color: $link-hover;
|
||||
border-color: $link-hover;
|
||||
}
|
||||
span.icon {
|
||||
color: $text-color;
|
||||
}
|
||||
.notification-close:before,
|
||||
.notification-close:after {
|
||||
background-color: $text-color;
|
||||
}
|
||||
}
|
||||
|
||||
// Alert Themes
|
||||
// ----------------------------------------------------------------------------
|
||||
.notification-success {
|
||||
@include notification-styles(
|
||||
$c-rainforest,
|
||||
$c-pool,
|
||||
$g20-white,
|
||||
$c-wasabi,
|
||||
$g20-white
|
||||
);
|
||||
}
|
||||
.notification-primary {
|
||||
@include notification-styles(
|
||||
$c-pool,
|
||||
$c-ocean,
|
||||
$g20-white,
|
||||
$c-neutrino,
|
||||
$g20-white
|
||||
);
|
||||
}
|
||||
.notification-warning {
|
||||
@include notification-styles(
|
||||
$c-star,
|
||||
$c-pool,
|
||||
$g20-white,
|
||||
$c-neutrino,
|
||||
$g20-white
|
||||
);
|
||||
}
|
||||
.notification-error {
|
||||
@include notification-styles(
|
||||
$c-curacao,
|
||||
$c-star,
|
||||
$g20-white,
|
||||
$c-marmelade,
|
||||
$g20-white
|
||||
);
|
||||
}
|
||||
.notification-info {
|
||||
@include notification-styles(
|
||||
$g20-white,
|
||||
$g16-pearl,
|
||||
$g8-storm,
|
||||
$ix-link-default,
|
||||
$ix-link-default-hover
|
||||
);
|
||||
}
|
||||
.notification-dark {
|
||||
@include notification-styles(
|
||||
$c-sapphire,
|
||||
$c-shadow,
|
||||
$c-moonstone,
|
||||
$ix-link-default,
|
||||
$ix-link-default-hover
|
||||
);
|
||||
}
|
||||
|
||||
.endpoint-description--textarea {
|
||||
max-height: 150;
|
||||
}
|
|
@ -1,16 +1,42 @@
|
|||
import React, {PureComponent} from 'react'
|
||||
import {Link} from 'react-router'
|
||||
import {connect} from 'react-redux'
|
||||
import {Notification as NotificationType} from 'src/types/notifications'
|
||||
import Notification from 'src/shared/components/notifications/Notification'
|
||||
import {get} from 'lodash'
|
||||
|
||||
interface Props {
|
||||
//Actions
|
||||
import {dismissNotification as dismissNotificationAction} from 'src/shared/actions/notifications'
|
||||
|
||||
import {Notification, ComponentSize, Gradients} from '@influxdata/clockface'
|
||||
|
||||
//Types
|
||||
import {
|
||||
Notification as NotificationType,
|
||||
NotificationStyle,
|
||||
} from 'src/types/notifications'
|
||||
|
||||
interface StateProps {
|
||||
notifications: NotificationType[]
|
||||
inPresentationMode: boolean
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
dismissNotification: typeof dismissNotificationAction
|
||||
}
|
||||
|
||||
type Props = StateProps & DispatchProps
|
||||
|
||||
const matchGradientToColor = (style: NotificationStyle): Gradients => {
|
||||
const converter = {
|
||||
[NotificationStyle.Primary]: Gradients.Primary,
|
||||
[NotificationStyle.Warning]: Gradients.WarningLight,
|
||||
[NotificationStyle.Success]: Gradients.HotelBreakfast,
|
||||
[NotificationStyle.Error]: Gradients.DangerDark,
|
||||
[NotificationStyle.Info]: Gradients.DefaultLight,
|
||||
}
|
||||
return get(converter, style, Gradients.DefaultLight)
|
||||
}
|
||||
|
||||
class Notifications extends PureComponent<Props> {
|
||||
public static defaultProps = {
|
||||
inPresentationMode: false,
|
||||
notifications: [],
|
||||
}
|
||||
|
||||
|
@ -18,36 +44,56 @@ class Notifications extends PureComponent<Props> {
|
|||
const {notifications} = this.props
|
||||
|
||||
return (
|
||||
<div className={this.className}>
|
||||
{notifications.map(n => (
|
||||
<Notification key={n.id} notification={n} />
|
||||
))}
|
||||
</div>
|
||||
<>
|
||||
{notifications.map(
|
||||
({id, style, icon, duration, message, link, linkText}) => {
|
||||
const gradient = matchGradientToColor(style)
|
||||
|
||||
let button
|
||||
|
||||
if (link && linkText) {
|
||||
button = (
|
||||
<Link
|
||||
to={link}
|
||||
className="notification--button cf-button cf-button-xs cf-button-default"
|
||||
>
|
||||
{linkText}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Notification
|
||||
key={id}
|
||||
id={id}
|
||||
icon={icon}
|
||||
duration={duration}
|
||||
size={ComponentSize.ExtraSmall}
|
||||
gradient={gradient}
|
||||
onTimeout={this.props.dismissNotification}
|
||||
onDismiss={this.props.dismissNotification}
|
||||
testID={`notification-${style}`}
|
||||
>
|
||||
<span className="notification--message">{message}</span>
|
||||
{button}
|
||||
</Notification>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
private get className(): string {
|
||||
const {inPresentationMode} = this.props
|
||||
|
||||
if (inPresentationMode) {
|
||||
return 'notification-center__presentation-mode'
|
||||
}
|
||||
|
||||
return 'notification-center'
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = ({
|
||||
const mapStateToProps = ({notifications}): StateProps => ({
|
||||
notifications,
|
||||
app: {
|
||||
ephemeral: {inPresentationMode},
|
||||
},
|
||||
}): Props => ({
|
||||
notifications,
|
||||
inPresentationMode,
|
||||
})
|
||||
|
||||
const mdtp: DispatchProps = {
|
||||
dismissNotification: dismissNotificationAction,
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
null
|
||||
mdtp
|
||||
)(Notifications)
|
||||
|
|
|
@ -36,7 +36,7 @@ export const DASHBOARD_LAYOUT_ROW_HEIGHT = 83.5
|
|||
export const NOTIFICATION_TRANSITION = 250
|
||||
export const FIVE_SECONDS = 5000
|
||||
export const TEN_SECONDS = 10000
|
||||
export const INFINITE = -1
|
||||
export const FIFTEEN_SECONDS = 15000
|
||||
|
||||
export const HOMEPAGE_PATHNAME = 'me'
|
||||
|
||||
|
|
|
@ -2,13 +2,17 @@
|
|||
import {binaryPrefixFormatter} from '@influxdata/giraffe'
|
||||
|
||||
// Types
|
||||
import {Notification} from 'src/types'
|
||||
import {NotificationStyle} from 'src/types/notifications'
|
||||
import {Notification, NotificationStyle} from 'src/types'
|
||||
|
||||
// Constants
|
||||
import {FIVE_SECONDS, TEN_SECONDS, INFINITE} from 'src/shared/constants/index'
|
||||
import {
|
||||
FIVE_SECONDS,
|
||||
TEN_SECONDS,
|
||||
FIFTEEN_SECONDS,
|
||||
} from 'src/shared/constants/index'
|
||||
import {QUICKSTART_SCRAPER_TARGET_URL} from 'src/dataLoaders/constants/pluginConfigs'
|
||||
import {QUICKSTART_DASHBOARD_NAME} from 'src/onboarding/constants/index'
|
||||
import {IconFont} from '@influxdata/clockface'
|
||||
|
||||
const bytesFormatter = binaryPrefixFormatter({
|
||||
suffix: 'B',
|
||||
|
@ -23,19 +27,19 @@ type NotificationExcludingMessage = Pick<
|
|||
|
||||
const defaultErrorNotification: NotificationExcludingMessage = {
|
||||
style: NotificationStyle.Error,
|
||||
icon: 'alert-triangle',
|
||||
icon: IconFont.AlertTriangle,
|
||||
duration: TEN_SECONDS,
|
||||
}
|
||||
|
||||
const defaultSuccessNotification: NotificationExcludingMessage = {
|
||||
style: NotificationStyle.Success,
|
||||
icon: 'checkmark',
|
||||
icon: IconFont.Checkmark,
|
||||
duration: FIVE_SECONDS,
|
||||
}
|
||||
|
||||
const defaultDeletionNotification: NotificationExcludingMessage = {
|
||||
style: NotificationStyle.Primary,
|
||||
icon: 'trash',
|
||||
icon: IconFont.Trash,
|
||||
duration: FIVE_SECONDS,
|
||||
}
|
||||
|
||||
|
@ -44,8 +48,7 @@ const defaultDeletionNotification: NotificationExcludingMessage = {
|
|||
|
||||
export const newVersion = (version: string): Notification => ({
|
||||
style: NotificationStyle.Info,
|
||||
icon: 'cubo-uniform',
|
||||
duration: INFINITE,
|
||||
icon: IconFont.Cubouniform,
|
||||
message: `Welcome to the latest Chronograf${version}. Local settings cleared.`,
|
||||
})
|
||||
|
||||
|
@ -56,21 +59,20 @@ export const loadLocalSettingsFailed = (error: string): Notification => ({
|
|||
|
||||
export const presentationMode = (): Notification => ({
|
||||
style: NotificationStyle.Primary,
|
||||
icon: 'expand-b',
|
||||
icon: IconFont.ExpandB,
|
||||
duration: 7500,
|
||||
message: 'Press ESC to exit Presentation Mode.',
|
||||
})
|
||||
|
||||
export const sessionTimedOut = (): Notification => ({
|
||||
style: NotificationStyle.Primary,
|
||||
icon: 'triangle',
|
||||
duration: INFINITE,
|
||||
icon: IconFont.Triangle,
|
||||
message: 'Your session has timed out. Log in again to continue.',
|
||||
})
|
||||
|
||||
export const resultTooLarge = (bytesRead: number): Notification => ({
|
||||
style: NotificationStyle.Error,
|
||||
icon: 'triangle',
|
||||
icon: IconFont.Triangle,
|
||||
duration: FIVE_SECONDS,
|
||||
message: `Large response truncated to first ${bytesFormatter(bytesRead)}`,
|
||||
})
|
||||
|
@ -145,19 +147,19 @@ export const dashboardGetFailed = (
|
|||
error: string
|
||||
): Notification => ({
|
||||
...defaultErrorNotification,
|
||||
icon: 'dash-h',
|
||||
icon: IconFont.DashH,
|
||||
message: `Failed to load dashboard with id "${dashboardID}": ${error}`,
|
||||
})
|
||||
|
||||
export const dashboardUpdateFailed = (): Notification => ({
|
||||
...defaultErrorNotification,
|
||||
icon: 'dash-h',
|
||||
icon: IconFont.DashH,
|
||||
message: 'Could not update dashboard',
|
||||
})
|
||||
|
||||
export const dashboardDeleted = (name: string): Notification => ({
|
||||
...defaultSuccessNotification,
|
||||
icon: 'dash-h',
|
||||
icon: IconFont.DashH,
|
||||
message: `Dashboard ${name} deleted successfully.`,
|
||||
})
|
||||
|
||||
|
@ -194,7 +196,7 @@ export const cellAdded = (
|
|||
dashboardName?: string
|
||||
): Notification => ({
|
||||
...defaultSuccessNotification,
|
||||
icon: 'dash-h',
|
||||
icon: IconFont.DashH,
|
||||
message: `Added new cell ${cellName + ' '}to dashboard ${dashboardName}`,
|
||||
})
|
||||
|
||||
|
@ -217,7 +219,7 @@ export const cellUpdateFailed = (): Notification => ({
|
|||
|
||||
export const cellDeleted = (): Notification => ({
|
||||
...defaultDeletionNotification,
|
||||
icon: 'dash-h',
|
||||
icon: IconFont.DashH,
|
||||
duration: 1900,
|
||||
message: `Cell deleted from dashboard.`,
|
||||
})
|
||||
|
@ -235,7 +237,7 @@ export const removedDashboardLabelFailed = (): Notification => ({
|
|||
// Variables & URL Queries
|
||||
export const invalidTimeRangeValueInURLQuery = (): Notification => ({
|
||||
...defaultErrorNotification,
|
||||
icon: 'cube',
|
||||
icon: IconFont.Cube,
|
||||
message: `Invalid URL query value supplied for lower or upper time range.`,
|
||||
})
|
||||
|
||||
|
@ -251,37 +253,37 @@ export const getVariableFailed = (): Notification => ({
|
|||
|
||||
export const createVariableFailed = (error: string): Notification => ({
|
||||
...defaultErrorNotification,
|
||||
icon: 'cube',
|
||||
icon: IconFont.Cube,
|
||||
message: `Failed to create variable: ${error}`,
|
||||
})
|
||||
|
||||
export const createVariableSuccess = (name: string): Notification => ({
|
||||
...defaultSuccessNotification,
|
||||
icon: 'cube',
|
||||
icon: IconFont.Cube,
|
||||
message: `Successfully created new variable: ${name}.`,
|
||||
})
|
||||
|
||||
export const deleteVariableFailed = (error: string): Notification => ({
|
||||
...defaultErrorNotification,
|
||||
icon: 'cube',
|
||||
icon: IconFont.Cube,
|
||||
message: `Failed to delete variable: ${error}`,
|
||||
})
|
||||
|
||||
export const deleteVariableSuccess = (): Notification => ({
|
||||
...defaultSuccessNotification,
|
||||
icon: 'cube',
|
||||
icon: IconFont.Cube,
|
||||
message: 'Successfully deleted the variable',
|
||||
})
|
||||
|
||||
export const updateVariableFailed = (error: string): Notification => ({
|
||||
...defaultErrorNotification,
|
||||
icon: 'cube',
|
||||
icon: IconFont.Cube,
|
||||
message: `Failed to update variable: ${error}`,
|
||||
})
|
||||
|
||||
export const updateVariableSuccess = (name: string): Notification => ({
|
||||
...defaultSuccessNotification,
|
||||
icon: 'cube',
|
||||
icon: IconFont.Cube,
|
||||
message: `Successfully updated variable: ${name}.`,
|
||||
})
|
||||
|
||||
|
@ -290,7 +292,7 @@ export const copyToClipboardSuccess = (
|
|||
title: string = ''
|
||||
): Notification => ({
|
||||
...defaultSuccessNotification,
|
||||
icon: 'dash-h',
|
||||
icon: IconFont.Cube,
|
||||
type: 'copyToClipboardSuccess',
|
||||
message: `${title} '${text}' has been copied to clipboard.`,
|
||||
})
|
||||
|
@ -448,6 +450,32 @@ export const getBucketFailed = (
|
|||
message: `Failed to fetch bucket with id ${bucketID}: ${error}`,
|
||||
})
|
||||
|
||||
// Demodata buckets
|
||||
|
||||
export const demoDataAddBucketFailed = (error: string): Notification => ({
|
||||
...defaultErrorNotification,
|
||||
message: error,
|
||||
})
|
||||
|
||||
export const demoDataDeleteBucketFailed = (
|
||||
bucketName: string,
|
||||
error: string
|
||||
): Notification => ({
|
||||
...defaultErrorNotification,
|
||||
message: `Failed to delete demo data bucket: ${bucketName}: ${error}`,
|
||||
})
|
||||
|
||||
export const demoDataSucceeded = (
|
||||
bucketName: string,
|
||||
link: string
|
||||
): Notification => ({
|
||||
...defaultSuccessNotification,
|
||||
message: `Successfully added demodata bucket ${bucketName}, and demodata dashboard.`,
|
||||
duration: FIFTEEN_SECONDS,
|
||||
linkText: 'Go to dashboard',
|
||||
link,
|
||||
})
|
||||
|
||||
// Limits
|
||||
export const readWriteCardinalityLimitReached = (
|
||||
message: string
|
||||
|
@ -537,7 +565,7 @@ export const taskUpdateSuccess = (): Notification => ({
|
|||
|
||||
export const taskImportFailed = (errorMessage: string): Notification => ({
|
||||
...defaultErrorNotification,
|
||||
duration: INFINITE,
|
||||
duration: undefined,
|
||||
message: `Failed to import Task: ${errorMessage}.`,
|
||||
})
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@ import {
|
|||
import {notify, dismissNotification} from 'src/shared/actions/notifications'
|
||||
|
||||
import {FIVE_SECONDS} from 'src/shared/constants/index'
|
||||
|
||||
import {IconFont} from '@influxdata/clockface'
|
||||
import {NotificationStyle} from 'src/types/notifications'
|
||||
|
||||
const notificationID = '000'
|
||||
|
@ -15,7 +17,7 @@ const exampleNotification = {
|
|||
style: NotificationStyle.Success,
|
||||
message: 'Hell yeah you are a real notification!',
|
||||
duration: FIVE_SECONDS,
|
||||
icon: 'zap',
|
||||
icon: IconFont.Zap,
|
||||
}
|
||||
|
||||
const exampleNotifications = [exampleNotification]
|
||||
|
@ -41,7 +43,7 @@ describe('Shared.Reducers.notifications', () => {
|
|||
style: NotificationStyle.Error,
|
||||
message: 'new notification',
|
||||
duration: FIVE_SECONDS,
|
||||
icon: 'zap',
|
||||
icon: IconFont.Zap,
|
||||
}
|
||||
|
||||
const actual = notificationsReducer(
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
@import 'src/shared/components/ColorDropdown.scss';
|
||||
@import 'src/shared/components/avatar/Avatar.scss';
|
||||
@import 'src/shared/components/tables/TableGraphs.scss';
|
||||
@import 'src/shared/components/notifications/Notifications.scss';
|
||||
@import 'src/shared/components/graph_tips/GraphTips.scss';
|
||||
@import 'src/shared/components/cells/Dashboards.scss';
|
||||
@import 'src/shared/components/code_mirror/CodeMirror.scss';
|
||||
|
@ -123,12 +122,13 @@
|
|||
@import 'src/clientLibraries/components/ClientLibraryOverlay.scss';
|
||||
@import 'src/dashboards/components/DashboardsCardGrid.scss';
|
||||
@import 'src/dashboards/components/DashboardLightMode.scss';
|
||||
@import 'src/buckets/components/DemoDataDropdown.scss';
|
||||
@import 'src/shared/components/notifications/Notification.scss';
|
||||
|
||||
// External
|
||||
@import '../../node_modules/@influxdata/react-custom-scrollbars/dist/styles.css';
|
||||
|
||||
|
||||
// TODO: delete this later when it's addressed in Clockface
|
||||
.cf-resource-card {
|
||||
margin-bottom: $cf-border;
|
||||
margin-bottom: $cf-border;
|
||||
}
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
import {Action} from 'src/shared/actions/notifications'
|
||||
import {IconFont} from '@influxdata/clockface'
|
||||
|
||||
export type NotificationAction = Action
|
||||
|
||||
export interface Notification {
|
||||
id?: string
|
||||
style: NotificationStyle
|
||||
icon: string
|
||||
duration: number
|
||||
icon: IconFont
|
||||
duration?: number
|
||||
message: string
|
||||
type?: string
|
||||
link?: string
|
||||
linkText?: string
|
||||
}
|
||||
|
||||
export enum NotificationStyle {
|
||||
|
|
Loading…
Reference in New Issue