Skip to content

Commit d81b069

Browse files
subirjollySubir Jollyasalem1
authored
feat: add payg usage panel (#3178)
* feat: add payg usage panel * chore: import fixes * chore: minor changes * chore: added technospinner and review changes * chore: remove unused var * chore: update per review * chore: update per review Co-authored-by: asalem1 <19984220+asalem1@users.noreply.github.com> * chore: update per review Co-authored-by: asalem1 <19984220+asalem1@users.noreply.github.com> * chore: remove not required dependency Co-authored-by: Subir Jolly <subirjolly@Subirs-MacBook-Pro.local> Co-authored-by: asalem1 <19984220+asalem1@users.noreply.github.com>
1 parent a5d9870 commit d81b069

File tree

7 files changed

+216
-45
lines changed

7 files changed

+216
-45
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"cy": "CYPRESS_dexUrl=https://$INGRESS_HOST:$PORT_HTTPS CYPRESS_baseUrl=http://localhost:9999 cypress open",
5353
"cy:dev": "source ../monitor-ci/.env && CYPRESS_dexUrl=CLOUD CYPRESS_baseUrl=https://$INGRESS_HOST:$PORT_HTTPS cypress open --config testFiles='{cloud,shared}/**/*.*'",
5454
"cy:dev-oss": "source ../monitor-ci/.env && CYPRESS_dexUrl=OSS CYPRESS_baseUrl=https://$INGRESS_HOST:$PORT_HTTPS cypress open --config testFiles='{oss,shared}/**/*.*'",
55-
"generate": "export SHA=ecdcd4b1baace6942e567771518ba19caf381652 && export REMOTE=https://raw.githubusercontent.com/influxdata/openapi/${SHA}/ && yarn generate-meta",
55+
"generate": "export SHA=6557f398011dc0d86826ffee1542e3a4bcbf47d7 && export REMOTE=https://raw.githubusercontent.com/influxdata/openapi/${SHA}/ && yarn generate-meta",
5656
"generate-local": "export REMOTE=../openapi/ && yarn generate-meta",
5757
"generate-meta": "if [ -z \"${CLOUD_URL}\" ]; then yarn generate-meta-oss; else yarn generate-meta-cloud; fi",
5858
"generate-meta-oss": "yarn oss-api && yarn notebooks && yarn unity && yarn annotations-oss && yarn pinned && yarn mapsd-oss && yarn cloudPriv",

src/me/components/Resources.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// Libraries
2-
import React, {FC, memo} from 'react'
3-
2+
import React, {FC, memo, useContext} from 'react'
43
import {
54
Panel,
65
FlexBox,
@@ -11,19 +10,25 @@ import {
1110
HeadingElement,
1211
FontWeight,
1312
} from '@influxdata/clockface'
14-
import VersionInfo from 'src/shared/components/VersionInfo'
1513

1614
// Types
15+
import {ResourceType} from 'src/types'
1716

18-
import DocSearchWidget from 'src/me/components/DocSearchWidget'
17+
// Utils
1918
import {isFlagEnabled} from 'src/shared/utils/featureFlag'
19+
import {UsageContext} from 'src/usage/context/usage'
2020

21+
// Components
22+
import UsagePanel from 'src/me/components/UsagePanel'
23+
import DocSearchWidget from 'src/me/components/DocSearchWidget'
2124
import LogoutButton from 'src/me/components/LogoutButton'
2225
import DashboardsList from 'src/me/components/DashboardsList'
2326
import GetResources from 'src/resources/components/GetResources'
24-
import {ResourceType} from 'src/types'
27+
import VersionInfo from 'src/shared/components/VersionInfo'
2528

2629
const ResourceLists: FC = () => {
30+
const {paygCreditEnabled} = useContext(UsageContext)
31+
2732
return (
2833
<FlexBox
2934
direction={FlexDirection.Column}
@@ -64,6 +69,9 @@ const ResourceLists: FC = () => {
6469
</Panel>
6570
</>
6671
)}
72+
{isFlagEnabled('uiUnificationFlag') &&
73+
isFlagEnabled('paygCheckoutCredit') &&
74+
paygCreditEnabled && <UsagePanel />}
6775
<Panel>
6876
<Panel.Footer>
6977
<VersionInfo />

src/me/components/UsagePanel.tsx

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,44 @@
1-
import React, {FC} from 'react'
2-
import {ProgressBar, Gradients, InfluxColors} from '@influxdata/clockface'
1+
// Libraries
2+
import React, {FC, useContext} from 'react'
33
import {
4-
BUCKET_LIMIT,
5-
RULE_LIMIT,
6-
TASK_LIMIT,
7-
DASHBOARD_LIMIT,
8-
} from 'src/resources/constants'
4+
Panel,
5+
HeadingElement,
6+
Heading,
7+
TechnoSpinner,
8+
JustifyContent,
9+
AlignItems,
10+
} from '@influxdata/clockface'
11+
12+
// Contexts
13+
import {UsageContext} from 'src/usage/context/usage'
14+
15+
// Constants
16+
17+
// Types
18+
import {RemoteDataState} from 'src/types'
19+
import UsagePanelDetails from 'src/me/components/UsagePanelDetails'
920

1021
const UsagePanel: FC = () => {
22+
const {creditUsage} = useContext(UsageContext)
23+
1124
return (
12-
<div className="usagepanel--container">
13-
<ProgressBar
14-
value={50}
15-
max={BUCKET_LIMIT}
16-
barGradient={Gradients.SavannaHeat}
17-
color={InfluxColors.Ruby}
18-
label="Buckets"
19-
/>
20-
<ProgressBar
21-
value={50}
22-
max={RULE_LIMIT}
23-
barGradient={Gradients.SavannaHeat}
24-
color={InfluxColors.Ruby}
25-
label="Rules"
26-
/>
27-
<ProgressBar
28-
value={50}
29-
max={TASK_LIMIT}
30-
barGradient={Gradients.MillennialAvocado}
31-
color={InfluxColors.Rainforest}
32-
label="Tasks"
33-
/>
34-
<ProgressBar
35-
value={50}
36-
max={DASHBOARD_LIMIT}
37-
barGradient={Gradients.GoldenHour}
38-
color={InfluxColors.Pineapple}
39-
label="Dashboards"
40-
/>
41-
</div>
25+
<Panel>
26+
<Panel.Header>
27+
<Heading element={HeadingElement.H3}>
28+
<label htmlFor="usagepanel--title">Usage</label>
29+
</Heading>
30+
</Panel.Header>
31+
<Panel.Body
32+
justifyContent={JustifyContent.Center}
33+
alignItems={AlignItems.Center}
34+
>
35+
{creditUsage.status === RemoteDataState.Loading ? (
36+
<TechnoSpinner />
37+
) : (
38+
<UsagePanelDetails />
39+
)}
40+
</Panel.Body>
41+
</Panel>
4242
)
4343
}
4444

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Libraries
2+
import React, {FC, useContext} from 'react'
3+
import {ProgressBar, Gradients, InfluxColors} from '@influxdata/clockface'
4+
5+
// Contexts
6+
import {UsageContext} from 'src/usage/context/usage'
7+
8+
// Constants
9+
import {PAYG_CREDIT_DAYS, PAYG_MAX_CREDIT} from 'src/shared/constants'
10+
11+
// Types
12+
13+
const UsagePanelDetails: FC = () => {
14+
const {creditUsage, creditDaysRemaining} = useContext(UsageContext)
15+
16+
return (
17+
<>
18+
<ProgressBar
19+
value={creditUsage?.amount}
20+
max={PAYG_MAX_CREDIT}
21+
barGradient={Gradients.HotelBreakfast}
22+
color={InfluxColors.Wasabi}
23+
label="Credit Usage"
24+
valueText={`$${creditUsage?.amount}`}
25+
maxText={`$${PAYG_MAX_CREDIT} credit`}
26+
/>
27+
<ProgressBar
28+
value={PAYG_CREDIT_DAYS - creditDaysRemaining}
29+
max={PAYG_CREDIT_DAYS}
30+
barGradient={
31+
creditDaysRemaining > 15
32+
? Gradients.HotelBreakfast
33+
: Gradients.DangerDark
34+
}
35+
color={
36+
creditDaysRemaining > 15 ? InfluxColors.Wasabi : InfluxColors.Curacao
37+
}
38+
label="Credit Period"
39+
valueText={`${creditDaysRemaining}`}
40+
maxText={`${PAYG_CREDIT_DAYS} days remaining`}
41+
/>
42+
</>
43+
)
44+
}
45+
46+
export default UsagePanelDetails

src/me/containers/MePage.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import {CLOUD} from 'src/shared/constants'
4343

4444
// Context
4545
import PinnedItemsProvider from 'src/shared/contexts/pinneditems'
46+
import UsageProvider from 'src/usage/context/usage'
4647

4748
const QUERY_WRITE_LIMIT_HITS = 100
4849

@@ -130,7 +131,14 @@ export class MePage extends PureComponent<Props> {
130131
</FlexBox>
131132
</Grid.Column>
132133
<Grid.Column widthSM={Columns.Four} widthMD={Columns.Three}>
133-
<Resources />
134+
{isFlagEnabled('uiUnificationFlag') &&
135+
isFlagEnabled('paygCheckoutCredit') ? (
136+
<UsageProvider>
137+
<Resources />
138+
</UsageProvider>
139+
) : (
140+
<Resources />
141+
)}
134142
</Grid.Column>
135143
</Grid.Row>
136144
</Grid>

src/shared/constants/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,6 @@ export const GLOBALSEARCH_APP_ID = formatConstant(
161161
export const GLOBALSEARCH_API_KEY = formatConstant(
162162
process.env.GLOBALSEARCH_API_KEY
163163
)
164+
165+
export const PAYG_CREDIT_DAYS = 30
166+
export const PAYG_MAX_CREDIT = 250

src/usage/context/usage.tsx

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Libraries
2-
import React, {FC, useCallback, useEffect, useState} from 'react'
2+
import React, {FC, useCallback, useEffect, useMemo, useState} from 'react'
33
import {fromFlux, FromFluxResult} from '@influxdata/giraffe'
4+
import {useSelector} from 'react-redux'
45

56
// Utils
67
import {
@@ -12,6 +13,7 @@ import {
1213
} from 'src/client/unityRoutes'
1314

1415
// Constants
16+
import {PAYG_CREDIT_DAYS} from 'src/shared/constants'
1517
import {DEFAULT_USAGE_TIME_RANGE} from 'src/shared/constants/timeRanges'
1618

1719
// Types
@@ -20,11 +22,22 @@ import {
2022
SelectableDurationTimeRange,
2123
UsageVector,
2224
} from 'src/types'
25+
import {getMe} from 'src/me/selectors'
2326

2427
export type Props = {
2528
children: JSX.Element
2629
}
2730

31+
interface Usage {
32+
amount: number
33+
status: RemoteDataState
34+
}
35+
36+
const DEFAULT_USAGE: Usage = {
37+
amount: 0,
38+
status: RemoteDataState.NotStarted,
39+
}
40+
2841
export interface UsageContextType {
2942
billingDateTime: string
3043
billingStats: FromFluxResult[]
@@ -38,6 +51,9 @@ export interface UsageContextType {
3851
usageStats: FromFluxResult
3952
usageStatsStatus: RemoteDataState
4053
usageVectors: UsageVector[]
54+
creditUsage: Usage
55+
creditDaysRemaining: number
56+
paygCreditEnabled: boolean
4157
}
4258

4359
export const DEFAULT_CONTEXT: UsageContextType = {
@@ -53,6 +69,9 @@ export const DEFAULT_CONTEXT: UsageContextType = {
5369
usageStats: null,
5470
usageStatsStatus: RemoteDataState.NotStarted,
5571
usageVectors: [],
72+
creditUsage: DEFAULT_USAGE,
73+
creditDaysRemaining: 0,
74+
paygCreditEnabled: false,
5675
}
5776

5877
export const UsageContext = React.createContext<UsageContextType>(
@@ -75,9 +94,23 @@ export const UsageProvider: FC<Props> = React.memo(({children}) => {
7594
const [rateLimitsStatus, setRateLimitsStatus] = useState(
7695
RemoteDataState.NotStarted
7796
)
97+
98+
const [creditUsage, setCreditUsage] = useState<Usage>(DEFAULT_USAGE)
7899
const [timeRange, setTimeRange] = useState<SelectableDurationTimeRange>(
79100
DEFAULT_USAGE_TIME_RANGE
80101
)
102+
const {quartzMe} = useSelector(getMe)
103+
const creditDaysRemaining = useMemo(() => {
104+
const startDate = new Date(quartzMe?.paygCreditStartDate)
105+
const current = new Date()
106+
const diffTime = Math.abs(current.getTime() - startDate.getTime())
107+
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
108+
109+
return PAYG_CREDIT_DAYS - diffDays
110+
}, [quartzMe?.paygCreditStartDate])
111+
112+
const paygCreditEnabled =
113+
creditDaysRemaining > 0 && creditDaysRemaining <= PAYG_CREDIT_DAYS
81114

82115
const handleSetTimeRange = useCallback(
83116
(range: SelectableDurationTimeRange) => {
@@ -132,6 +165,76 @@ export const UsageProvider: FC<Props> = React.memo(({children}) => {
132165
handleGetBillingDate()
133166
}, [handleGetBillingDate])
134167

168+
const getComputedUsage = (vector_name: string, csvData: string) => {
169+
// Calculation Formula
170+
// Credit usage: $250 - (
171+
// (sum of 30-day writes * $0.002) +
172+
// (sum of 30-day query count * $0.01 / 100) +
173+
// (sum of 30-day query storage * $0.02) +
174+
// (sum of 30-day data out * $0.09)
175+
// )
176+
const vectors = {
177+
storage_gb: v => v * 0.02,
178+
writes_mb: v => v * 0.002,
179+
reads_gb: v => v * 0.09,
180+
query_count: v => (v * 0.01) / 100,
181+
}
182+
183+
const values = fromFlux(csvData).table.getColumn(vector_name) as Array<any>
184+
if (!values) {
185+
return 0
186+
}
187+
188+
const tot = values.reduce((a, b) => a + b, 0)
189+
return vectors[vector_name](tot)
190+
}
191+
192+
const handleGetCreditUsage = useCallback(() => {
193+
try {
194+
setCreditUsage(prev => ({
195+
...prev,
196+
status: RemoteDataState.Loading,
197+
}))
198+
const vectors = ['storage_gb', 'writes_mb', 'reads_gb', 'query_count']
199+
const promises = []
200+
201+
vectors.forEach(vector_name => {
202+
promises.push(
203+
getUsage({vector_name, query: {range: `${PAYG_CREDIT_DAYS}d`}}).then(
204+
resp => {
205+
if (resp.status !== 200) {
206+
throw new Error(resp.data.message)
207+
}
208+
209+
return new Promise(resolve =>
210+
resolve(getComputedUsage(vector_name, resp.data))
211+
)
212+
}
213+
)
214+
)
215+
})
216+
217+
Promise.all(promises).then(result => {
218+
const amount: number = result
219+
.reduce((a: number, b) => a + parseFloat(b), 0)
220+
.toFixed(2)
221+
setCreditUsage({
222+
amount,
223+
status: RemoteDataState.Done,
224+
})
225+
})
226+
} catch (error) {
227+
setCreditUsage(prev => ({
228+
...prev,
229+
status: RemoteDataState.Done,
230+
}))
231+
}
232+
}, [])
233+
234+
useEffect(() => {
235+
handleGetCreditUsage()
236+
}, [creditUsage?.amount, handleGetCreditUsage])
237+
135238
const handleGetBillingStats = useCallback(async () => {
136239
try {
137240
setBillingStatsStatus(RemoteDataState.Loading)
@@ -234,6 +337,9 @@ export const UsageProvider: FC<Props> = React.memo(({children}) => {
234337
usageStats,
235338
usageStatsStatus,
236339
usageVectors,
340+
creditUsage,
341+
creditDaysRemaining,
342+
paygCreditEnabled,
237343
}}
238344
>
239345
{children}

0 commit comments

Comments
 (0)