12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793 |
- import {Component, Fragment} from 'react';
- import {css} from '@emotion/react';
- import styled from '@emotion/styled';
- import type {ModalRenderProps} from 'sentry/actionCreators/modal';
- import {openModal} from 'sentry/actionCreators/modal';
- import type {Client} from 'sentry/api';
- import {Alert} from 'sentry/components/core/alert';
- import BooleanField from 'sentry/components/deprecatedforms/booleanField';
- import DateTimeField from 'sentry/components/deprecatedforms/dateTimeField';
- import Form from 'sentry/components/deprecatedforms/form';
- import InputField from 'sentry/components/deprecatedforms/inputField';
- import NumberField from 'sentry/components/deprecatedforms/numberField';
- import SelectField from 'sentry/components/deprecatedforms/selectField';
- import {space} from 'sentry/styles/space';
- import withApi from 'sentry/utils/withApi';
- import {prettyDate} from 'admin/utils';
- import {CPE_MULTIPLIER_TO_CENTS, RESERVED_BUDGET_QUOTA} from 'getsentry/constants';
- import type {ReservedBudgetMetricHistory, Subscription} from 'getsentry/types';
- import {
- isAm3DsPlan,
- isAm3Plan,
- isAmEnterprisePlan,
- isAmPlan,
- } from 'getsentry/utils/billing';
- const CPE_DECIMAL_PRECISION = 8;
- // TODO: replace with modern fields so we don't need these workarounds
- class DateField extends DateTimeField {
- getType() {
- return 'date';
- }
- }
- type DollarsAndCentsFieldProps = {
- max?: number;
- min?: number;
- step?: any;
- } & NumberField['props'];
- class DollarsField extends NumberField {
- getField() {
- return (
- <div className="dollars-field-container">
- <span className="dollar-sign">$</span>
- {super.getField()}
- </div>
- );
- }
- }
- class DollarsAndCentsField extends InputField<DollarsAndCentsFieldProps> {
- getField() {
- return (
- <div className="dollars-cents-field-container">
- <span className="dollar-sign">$</span>
- {super.getField()}
- </div>
- );
- }
- coerceValue(value: any): number | '' {
- const floatValue = parseFloat(value);
- if (isNaN(floatValue)) {
- return '';
- }
- return floatValue;
- }
- getType() {
- return 'number';
- }
- getAttributes() {
- return {
- min: this.props.min || undefined,
- max: this.props.max || undefined,
- step: this.props.step || undefined,
- };
- }
- }
- type Props = {
- api: Client;
- onSuccess: () => void;
- orgId: string;
- subscription: Subscription;
- canProvisionDsPlan?: boolean; // TODO(DS Spans): remove once we need to provision DS plans
- };
- type ModalProps = ModalRenderProps & Props;
- type ModalState = {
- data: any; // TODO(ts), TODO:categories get data.plan categories to dynamically create fields
- effectiveAtDisabled: boolean;
- };
- function toAnnualDollars(
- cents: number | null | undefined,
- billingInterval: string | null | undefined,
- decimals = 0
- ) {
- if (typeof cents !== 'number') {
- return cents;
- }
- if (billingInterval === 'monthly') {
- return parseFloat(((cents * 12) / 100).toFixed(decimals));
- }
- return parseFloat((cents / 100).toFixed(decimals));
- }
- /**
- * Convert dollars to 0.000001 cents
- * @param dollars - dollars to convert
- * @returns dollars in units of 0.000001 cents
- */
- function toCpeCents(dollars: number | null | undefined) {
- if (typeof dollars !== 'number') {
- return dollars;
- }
- return parseInt(((dollars * 100) / CPE_MULTIPLIER_TO_CENTS).toFixed(0), 10);
- }
- function toCents(dollars: number | null | undefined, decimals = 0) {
- if (typeof dollars !== 'number') {
- return dollars;
- }
- return parseFloat((dollars * 100).toFixed(decimals));
- }
- class ProvisionSubscriptionModal extends Component<ModalProps, ModalState> {
- state: ModalState = {
- data: {},
- effectiveAtDisabled: false,
- };
- componentDidMount() {
- const {subscription} = this.props;
- const existingPlanWithoutSuffix = subscription.plan.endsWith('_auf')
- ? subscription.plan.slice(0, subscription.plan.length - 4)
- : subscription.plan.endsWith('_ac')
- ? subscription.plan.slice(0, subscription.plan.length - 3)
- : subscription.plan;
- const existingPlanIsEnterprise = this.provisionablePlans.some(
- plan => plan[0] === existingPlanWithoutSuffix
- );
- const reservedBudgets = subscription.reservedBudgets;
- const reservedBudgetMetricHistories: Record<string, ReservedBudgetMetricHistory> = {};
- reservedBudgets?.forEach(budget => {
- Object.entries(budget.categories).forEach(([category, info]) => {
- reservedBudgetMetricHistories[category] = info;
- });
- });
- if (existingPlanIsEnterprise) {
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- plan: existingPlanWithoutSuffix,
- billingInterval: subscription.billingInterval,
- retainOnDemandBudget: false,
- type: subscription.type,
- onDemandInvoicedManual: subscription.onDemandInvoicedManual
- ? subscription.onDemandBudgets?.budgetMode.toString().toUpperCase()
- : subscription.onDemandInvoicedManual === null
- ? null
- : 'DISABLE',
- managed: subscription.isManaged,
- reservedErrors: subscription.categories.errors?.reserved,
- reservedTransactions: subscription.categories.transactions?.reserved,
- reservedReplays: subscription.categories.replays?.reserved,
- reservedMonitorSeats: subscription.categories.monitorSeats?.reserved,
- reservedUptime: subscription.categories.uptime?.reserved,
- reservedSpans: subscription.categories.spans?.reserved,
- reservedSpansIndexed: subscription.categories.spansIndexed?.reserved,
- reservedAttachments: subscription.categories.attachments?.reserved,
- reservedProfileDuration: subscription.categories.profileDuration?.reserved,
- softCapTypeErrors: subscription.categories.errors?.softCapType,
- softCapTypeTransactions: subscription.categories.transactions?.softCapType,
- softCapTypeReplays: subscription.categories.replays?.softCapType,
- softCapTypeMonitorSeats: subscription.categories.monitorSeats?.softCapType,
- softCapTypeUptime: subscription.categories.uptime?.softCapType,
- softCapTypeSpans: subscription.categories.spans?.softCapType,
- softCapTypeSpansIndexed: subscription.categories.spansIndexed?.softCapType,
- softCapTypeAttachments: subscription.categories.attachments?.softCapType,
- softCapTypeProfileDuration:
- subscription.categories.profileDuration?.softCapType,
- customPriceErrors: toAnnualDollars(
- subscription.categories.errors?.customPrice,
- subscription.billingInterval
- ),
- customPriceTransactions: toAnnualDollars(
- subscription.categories.transactions?.customPrice,
- subscription.billingInterval
- ),
- customPriceReplays: toAnnualDollars(
- subscription.categories.replays?.customPrice,
- subscription.billingInterval
- ),
- customPriceMonitorSeats: toAnnualDollars(
- subscription.categories.monitorSeats?.customPrice,
- subscription.billingInterval
- ),
- customPriceUptime: toAnnualDollars(
- subscription.categories.uptime?.customPrice,
- subscription.billingInterval
- ),
- customPriceSpans: toAnnualDollars(
- subscription.categories.spans?.customPrice,
- subscription.billingInterval
- ),
- customPriceSpansIndexed: toAnnualDollars(
- subscription.categories.spansIndexed?.customPrice,
- subscription.billingInterval
- ),
- customPriceAttachments: toAnnualDollars(
- subscription.categories.attachments?.customPrice,
- subscription.billingInterval
- ),
- customPriceProfileDuration: toAnnualDollars(
- subscription.categories.profileDuration?.customPrice,
- subscription.billingInterval
- ),
- customPricePcss: toAnnualDollars(
- subscription.customPricePcss,
- subscription.billingInterval
- ),
- customPrice: toAnnualDollars(
- subscription.customPrice,
- subscription.billingInterval
- ),
- onDemandCpeErrors: toAnnualDollars(
- subscription.categories.errors?.onDemandCpe,
- null,
- CPE_DECIMAL_PRECISION
- ),
- onDemandCpeTransactions: toAnnualDollars(
- subscription.categories.transactions?.onDemandCpe,
- null,
- CPE_DECIMAL_PRECISION
- ),
- onDemandCpeReplays: toAnnualDollars(
- subscription.categories.replays?.onDemandCpe,
- null,
- CPE_DECIMAL_PRECISION
- ),
- onDemandCpeMonitorSeats: toAnnualDollars(
- subscription.categories.monitorSeats?.onDemandCpe,
- null,
- CPE_DECIMAL_PRECISION
- ),
- onDemandCpeUptime: toAnnualDollars(
- subscription.categories.uptime?.onDemandCpe,
- null,
- CPE_DECIMAL_PRECISION
- ),
- onDemandCpeSpans: toAnnualDollars(
- subscription.categories.spans?.onDemandCpe,
- null,
- CPE_DECIMAL_PRECISION
- ),
- onDemandCpeSpansIndexed: toAnnualDollars(
- subscription.categories.spansIndexed?.onDemandCpe,
- null,
- CPE_DECIMAL_PRECISION
- ),
- onDemandCpeAttachments: toAnnualDollars(
- subscription.categories.attachments?.onDemandCpe,
- null,
- CPE_DECIMAL_PRECISION
- ),
- onDemandCpeProfileDuration: toAnnualDollars(
- subscription.categories.profileDuration?.onDemandCpe,
- null,
- CPE_DECIMAL_PRECISION
- ),
- // coming from the API, reservedCpe is in cents
- reservedCpeSpans: toAnnualDollars(
- reservedBudgetMetricHistories.spans?.reservedCpe,
- null,
- CPE_DECIMAL_PRECISION
- ),
- reservedCpeSpansIndexed: toAnnualDollars(
- reservedBudgetMetricHistories.spansIndexed?.reservedCpe,
- null,
- CPE_DECIMAL_PRECISION
- ),
- },
- }));
- }
- }
- get endpoint() {
- return `/customers/${this.props.orgId}/provision-subscription/`;
- }
- isEnablingOnDemandMaxSpend = () =>
- this.state.data.onDemandInvoicedManual === 'SHARED' ||
- this.state.data.onDemandInvoicedManual === 'PER_CATEGORY';
- isEnablingSoftCap = () =>
- this.state.data.softCapTypeErrors ||
- this.state.data.softCapTypeTransactions ||
- this.state.data.softCapTypeReplays ||
- this.state.data.softCapTypeMonitorSeats ||
- this.state.data.softCapTypeUptime ||
- this.state.data.softCapTypeSpans ||
- this.state.data.softCapTypeSpansIndexed ||
- this.state.data.softCapTypeAttachments;
- isSettingSpansBudget = () =>
- isAm3DsPlan(this.state.data.plan) &&
- this.state.data.reservedCpeSpans &&
- this.state.data.reservedCpeSpansIndexed;
- hasCompleteSpansBudget = () =>
- this.isSettingSpansBudget() &&
- this.state.data.reservedSpans === RESERVED_BUDGET_QUOTA &&
- this.state.data.reservedSpansIndexed === RESERVED_BUDGET_QUOTA &&
- this.state.data.customPriceSpans;
- disableRetainOnDemand = () => {
- if (this.state.data.onDemandInvoicedManual === null) {
- // don't show the toggle if there is no ondemand type
- return true;
- }
- const original = this.props.subscription.onDemandInvoicedManual
- ? this.props.subscription.onDemandBudgets?.budgetMode.toString().toUpperCase()
- : this.props.subscription.onDemandInvoicedManual === null
- ? null
- : 'DISABLE';
- return (
- this.state.data.onDemandInvoicedManual !== original ||
- this.state.data.onDemandInvoicedManual === 'DISABLE'
- );
- };
- onSubmit: Form['props']['onSubmit'] = (formData, _onSubmitSuccess, onSubmitError) => {
- const postData = {...this.state.data};
- for (const k in formData) {
- if (formData[k] !== '' && formData[k] !== null) {
- postData[k] = formData[k];
- }
- }
- // clear disabled fields
- if (postData.atPeriodEnd || postData.coterm) {
- delete postData.effectiveAt;
- }
- if (!postData.coterm) {
- delete postData.coterm;
- }
- const hasCustomSkuPrices = isAmEnterprisePlan(postData.plan);
- if (!hasCustomSkuPrices) {
- delete postData.customPriceErrors;
- delete postData.customPriceTransactions;
- delete postData.customPriceAttachments;
- delete postData.customPricePcss;
- delete postData.customPriceReplays;
- delete postData.customPriceMonitorSeats;
- delete postData.customPriceUptime;
- delete postData.customPriceSpans;
- delete postData.customPriceSpansIndexed;
- delete postData.customPriceProfileDuration;
- }
- // only set reserved & custom price for spans OR transactions
- if (isAm3Plan(postData.plan)) {
- delete postData.reservedTransactions;
- delete postData.customPriceTransactions;
- } else {
- delete postData.reservedSpans;
- delete postData.customPriceSpans;
- }
- if (postData.type !== 'invoiced') {
- delete postData.onDemandInvoicedManual;
- delete postData.onDemandCpeErrors;
- delete postData.onDemandCpeTransactions;
- delete postData.onDemandCpeReplays;
- delete postData.onDemandCpeAttachments;
- delete postData.onDemandCpeMonitorSeats;
- delete postData.onDemandCpeUptime;
- delete postData.onDemandCpeSpans;
- delete postData.onDemandCpeProfileDuration;
- // clear corresponding state
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- onDemandInvoicedManual: null,
- },
- }));
- }
- if (this.isEnablingOnDemandMaxSpend()) {
- postData.softCapTypeErrors = null;
- postData.softCapTypeTransactions = null;
- postData.softCapTypeReplays = null;
- postData.softCapTypeAttachments = null;
- postData.softCapTypeMonitorSeats = null;
- postData.softCapTypeUptime = null;
- postData.softCapTypeSpans = null;
- postData.softCapTypeProfileDuration = null;
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- softCapTypeErrors: null,
- softCapTypeTransactions: null,
- softCapTypeReplays: null,
- softCapTypeAttachments: null,
- softCapTypeMonitorSeats: null,
- softCapTypeUptime: null,
- softCapTypeSpans: null,
- softCapTypeProfileDuration: null,
- },
- }));
- } else {
- delete postData.onDemandCpeErrors;
- delete postData.onDemandCpeTransactions;
- delete postData.onDemandCpeReplays;
- delete postData.onDemandCpeAttachments;
- delete postData.onDemandCpeMonitorSeats;
- delete postData.onDemandCpeUptime;
- delete postData.onDemandCpeSpans;
- delete postData.onDemandCpeProfileDuration;
- }
- if (this.isEnablingSoftCap()) {
- postData.onDemandInvoicedManual = 'DISABLE';
- delete postData.onDemandCpeErrors;
- delete postData.onDemandCpeTransactions;
- delete postData.onDemandCpeReplays;
- delete postData.onDemandCpeAttachments;
- delete postData.onDemandCpeMonitorSeats;
- delete postData.onDemandCpeUptime;
- delete postData.onDemandCpeSpans;
- delete postData.onDemandCpeProfileDuration;
- }
- if (!isNaN(postData.onDemandCpeErrors)) {
- postData.onDemandCpeErrors = toCents(
- postData.onDemandCpeErrors,
- CPE_DECIMAL_PRECISION
- );
- }
- if (!isNaN(postData.onDemandCpeTransactions)) {
- postData.onDemandCpeTransactions = toCents(
- postData.onDemandCpeTransactions,
- CPE_DECIMAL_PRECISION
- );
- }
- if (!isNaN(postData.onDemandCpeReplays)) {
- postData.onDemandCpeReplays = toCents(
- postData.onDemandCpeReplays,
- CPE_DECIMAL_PRECISION
- );
- }
- if (!isNaN(postData.onDemandCpeAttachments)) {
- postData.onDemandCpeAttachments = toCents(
- postData.onDemandCpeAttachments,
- CPE_DECIMAL_PRECISION
- );
- }
- if (!isNaN(postData.onDemandCpeMonitorSeats)) {
- postData.onDemandCpeMonitorSeats = toCents(
- postData.onDemandCpeMonitorSeats,
- CPE_DECIMAL_PRECISION
- );
- }
- if (!isNaN(postData.onDemandCpeUptime)) {
- postData.onDemandCpeUptime = toCents(
- postData.onDemandCpeUptime,
- CPE_DECIMAL_PRECISION
- );
- }
- if (!isNaN(postData.onDemandCpeSpans)) {
- postData.onDemandCpeSpans = toCents(
- postData.onDemandCpeSpans,
- CPE_DECIMAL_PRECISION
- );
- }
- if (!isNaN(postData.onDemandCpeSpansIndexed)) {
- postData.onDemandCpeSpansIndexed = toCents(
- postData.onDemandCpeSpansIndexed,
- CPE_DECIMAL_PRECISION
- );
- }
- if (!isNaN(postData.onDemandCpeProfileDuration)) {
- postData.onDemandCpeProfileDuration = toCents(
- postData.onDemandCpeProfileDuration,
- CPE_DECIMAL_PRECISION
- );
- }
- if (!isNaN(postData.reservedCpeSpans)) {
- postData.reservedCpeSpans = toCpeCents(postData.reservedCpeSpans);
- }
- if (!isNaN(postData.reservedCpeSpansIndexed)) {
- postData.reservedCpeSpansIndexed = toCpeCents(postData.reservedCpeSpansIndexed);
- }
- postData.retainOnDemandBudget = postData.retainOnDemandBudget
- ? !this.disableRetainOnDemand()
- : false;
- const hasCustomPrice = hasCustomSkuPrices || postData.managed;
- if (!hasCustomPrice) {
- delete postData.hasCustomPrice;
- }
- if (!isNaN(postData.customPriceErrors)) {
- postData.customPriceErrors *= 100; // Price should be in cents
- }
- if (!isNaN(postData.customPriceTransactions)) {
- postData.customPriceTransactions *= 100; // Price should be in cents
- }
- if (!isNaN(postData.customPriceReplays)) {
- postData.customPriceReplays *= 100; // Price should be in cents
- }
- if (!isNaN(postData.customPriceSpans)) {
- postData.customPriceSpans *= 100; // Price should be in cents
- }
- if (!isNaN(postData.customPriceSpansIndexed)) {
- postData.customPriceSpansIndexed *= 100; // Price should be in cents
- }
- if (!isNaN(postData.customPriceMonitorSeats)) {
- postData.customPriceMonitorSeats *= 100; // Price should be in cents
- }
- if (!isNaN(postData.customPriceUptime)) {
- postData.customPriceUptime *= 100; // Price should be in cents
- }
- if (!isNaN(postData.customPriceAttachments)) {
- postData.customPriceAttachments *= 100; // Price should be in cents
- }
- if (!isNaN(postData.customPricePcss)) {
- postData.customPricePcss *= 100; // Price should be in cents
- }
- if (!isNaN(postData.customPriceProfileDuration)) {
- postData.customPriceProfileDuration *= 100; // Price should be in cents
- }
- if (!isNaN(postData.customPrice)) {
- postData.customPrice *= 100; // Price should be in cents
- // For AM only: If customPrice is set, ensure that it is equal to sum of SKU prices
- if (
- hasCustomSkuPrices &&
- postData.customPrice !==
- postData.customPriceErrors +
- (isAm3Plan(postData.plan)
- ? (postData.customPriceSpans ?? 0)
- : (postData.customPriceTransactions ?? 0)) +
- (postData.customPriceReplays ?? 0) +
- (postData.customPriceMonitorSeats ?? 0) +
- (postData.customPriceUptime ?? 0) +
- postData.customPriceAttachments +
- postData.customPricePcss +
- (postData.customPriceProfileDuration ?? 0) +
- (isAm3DsPlan(postData.plan) ? (postData.customPriceSpansIndexed ?? 0) : 0)
- ) {
- onSubmitError({
- responseJSON: {
- customPrice: ['Custom Price must be equal to sum of SKU prices'],
- },
- });
- return;
- }
- }
- if (isAmPlan(postData.plan)) {
- // Setting soft cap types to null if not `ON_DEMAND` or `TRUE_FORWARD` ensures soft cap type
- // is disabled if it was set but is not set with the new provisioning request.
- if (!postData.softCapTypeErrors) {
- postData.softCapTypeErrors = null;
- }
- if (!postData.softCapTypeReplays) {
- postData.softCapTypeReplays = null;
- }
- if (!postData.softCapTypeAttachments) {
- postData.softCapTypeAttachments = null;
- }
- if (!postData.softCapTypeMonitorSeats) {
- postData.softCapTypeMonitorSeats = null;
- }
- if (!postData.softCapTypeUptime) {
- postData.softCapTypeUptime = null;
- }
- if (!postData.softCapTypeProfileDuration) {
- postData.softCapTypeProfileDuration = null;
- }
- // If a data category has a set soft cap type, trueForwad will also need to be set to true for that category
- // until the true forward fields are fully deprecated and soft cap types are used in their place.
- postData.trueForward = {
- errors: postData.softCapTypeErrors ? true : false,
- replays: postData.softCapTypeReplays ? true : false,
- attachments: postData.softCapTypeAttachments ? true : false,
- monitor_seats: postData.softCapTypeMonitorSeats ? true : false,
- uptime: postData.softCapTypeUptime ? true : false,
- profile_duration: postData.softCapTypeProfileDuration ? true : false,
- };
- if (isAm3Plan(postData.plan)) {
- postData.trueForward = {
- ...postData.trueForward,
- spans: postData.softCapTypeSpans ? true : false,
- };
- delete postData.softCapTypeTransactions;
- if (!postData.softCapTypeSpans) {
- postData.softCapTypeSpans = null;
- }
- } else {
- postData.trueForward = {
- ...postData.trueForward,
- transactions: postData.softCapTypeTransactions ? true : false,
- };
- delete postData.softCapTypeSpans;
- delete postData.softCapTypeSpansIndexed;
- if (!postData.softCapTypeTransactions) {
- postData.softCapTypeTransactions = null;
- }
- }
- }
- if (isAm3DsPlan(postData.plan)) {
- postData.trueForward = {
- ...postData.trueForward,
- spansIndexed: postData.softCapTypeSpansIndexed ? true : false,
- };
- if (!postData.softCapTypeSpansIndexed) {
- postData.softCapTypeSpansIndexed = null;
- }
- if (this.hasCompleteSpansBudget()) {
- postData.reservedBudgets = [
- {
- categories: ['spans', 'spansIndexed'],
- budget: postData.customPriceSpans,
- },
- ];
- } else {
- onSubmitError({
- responseJSON: {
- customPriceSpans: [
- 'Dynamic Sampling plans require reserved spans budget with reserved CPEs for both accepted and stored spans',
- ],
- },
- });
- return;
- }
- } else {
- for (const k in postData) {
- if (k.endsWith('SpansIndexed')) {
- delete postData[k];
- }
- }
- delete postData.reservedCpeSpans;
- }
- this.props.api.request(this.endpoint, {
- method: 'POST',
- data: postData,
- success: () => {
- this.props.onSuccess();
- this.props.closeModal();
- },
- error: error => {
- onSubmitError({
- responseJSON: error.responseJSON,
- });
- },
- });
- };
- provisionablePlans = [
- ['am3_business_ent_ds', 'Business with Dynamic Sampling (am3)'],
- ['am3_team_ent_ds', 'Team with Dynamic Sampling (am3)'],
- ['am3_business_ent', 'Business (am3)'],
- ['am3_team_ent', 'Team (am3)'],
- ['am2_business_ent', 'Business (am2)'],
- ['am2_team_ent', 'Team (am2)'],
- ['am1_business_ent', 'Business (am1)'],
- ['am1_team_ent', 'Team (am1)'],
- ['mm2_a', 'Business (mm2)'],
- ['mm2_b', 'Team (mm2)'],
- ['e1', 'Enterprise (mm1)'],
- ];
- render() {
- const {Header, Body, closeModal, canProvisionDsPlan = false} = this.props;
- const {data} = this.state;
- const isAmEnt = isAmEnterprisePlan(data.plan);
- const isAm3 = isAm3Plan(data.plan);
- const isAm3Ds = isAm3DsPlan(data.plan);
- const hasCustomSkuPrices = isAmEnt;
- const hasCustomPrice = hasCustomSkuPrices || !!data.managed; // Refers to ACV
- if (!canProvisionDsPlan) {
- this.provisionablePlans = this.provisionablePlans.filter(
- plan => !isAm3DsPlan(plan[0])
- );
- }
- return (
- <Fragment>
- <Header>Provision Subscription Changes</Header>
- <Body>
- <Form
- onSubmit={this.onSubmit}
- onCancel={closeModal}
- submitLabel="Submit"
- cancelLabel="Cancel"
- footerClass="modal-footer"
- >
- <Columns>
- <div>
- <SelectField
- label="Plan"
- name="plan"
- clearable={false}
- choices={this.provisionablePlans}
- onChange={v => {
- // Reset price fields if next plan is not AM Enterprise
- const isManagedPlan = isAmEnterprisePlan(v as string);
- const chosenPlanIsAm3Ds = isAm3DsPlan(v as string);
- const nextPrices = isManagedPlan
- ? {}
- : {
- customPriceErrors: '',
- customPriceTransactions: '',
- customPriceReplays: '',
- customPriceMonitorSeats: '',
- customPriceUptime: '',
- customPriceSpans: '',
- customPriceSpansIndexed: '',
- customPriceAttachments: '',
- customPricePcss: '',
- customPrice: '',
- };
- const nextReservedCpes = chosenPlanIsAm3Ds
- ? {}
- : {
- reservedCpeSpans: '',
- reservedCpeSpansIndexed: '',
- };
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- plan: v,
- ...nextPrices,
- ...nextReservedCpes,
- },
- }));
- }}
- value={this.state.data.plan}
- />
- <BooleanField
- label={`Apply Changes at the End of the Current Billing Period (${prettyDate(
- this.props.subscription.contractPeriodEnd
- )})`}
- name="atPeriodEnd"
- disabled={this.state.data.coterm}
- onChange={v =>
- this.setState(state => ({
- ...state,
- effectiveAtDisabled: !!v,
- data: {...state.data, atPeriodEnd: v},
- }))
- }
- />
- <BooleanField
- label="Apply Changes To Current Subscription"
- name="coterm"
- disabled={this.state.data.atPeriodEnd}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {...state.data, coterm: v},
- effectiveAtDisabled: !!v,
- }))
- }
- />
- <DateField
- label="Start Date"
- name="effectiveAt"
- help="The date at which this change should take effect."
- disabled={this.state.effectiveAtDisabled}
- required={!this.state.effectiveAtDisabled}
- />
- <SelectField
- label="Billing Interval"
- name="billingInterval"
- choices={[
- ['annual', 'Annual'],
- ['monthly', 'Monthly'],
- ]}
- disabled={!this.state.data.plan}
- required={!!this.state.data.plan}
- value={this.state.data.billingInterval}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {
- ...this.state.data,
- billingInterval: v,
- },
- }))
- }
- />
- <BooleanField
- label="Managed Subscription"
- name="managed"
- value={hasCustomSkuPrices || this.state.data.managed}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- managed: v,
- customPrice: v ? state.data.customPrice : '',
- },
- }))
- }
- />
- <SelectField
- label="Billing Type"
- name="type"
- choices={[
- ['invoiced', 'Invoiced'],
- ['credit_card', 'Credit Card'],
- ]}
- onChange={v => {
- if (v === 'credit_card') {
- this.setState(state => ({
- ...state,
- data: {...state.data, onDemandInvoicedManual: ''},
- }));
- }
- this.setState(state => ({...state, data: {...state.data, type: v}}));
- }}
- value={this.state.data.type}
- />
- {this.state.data.type === 'invoiced' && (
- <StyledSelectFieldWithHelpText
- label="On-Demand Max Spend Type"
- name="onDemandInvoicedManual"
- choices={[
- ['SHARED', 'Shared'],
- ['PER_CATEGORY', 'Per Category'],
- ['DISABLE', 'Disable'],
- ]}
- help="Used to enable (Shared or Per Category) or disable on-demand max spend for invoiced customers. Cannot be provisioned with soft cap."
- clearable
- disabled={
- this.state.data.type === 'credit_card' || this.isEnablingSoftCap()
- }
- value={this.state.data.onDemandInvoicedManual}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {...state.data, onDemandInvoicedManual: v},
- }))
- }
- />
- )}
- {!this.disableRetainOnDemand() && (
- <BooleanField
- label="Retain On-Demand Budget"
- name="retainOnDemandBudget"
- value={this.state.data.retainOnDemandBudget}
- help="Check to retain the customer's current On-Demand Budget. Otherwise, the customer's On-Demand Budget will be set based on the default calculations (0.5 times the monthly plan price)."
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- retainOnDemandBudget: v,
- },
- }))
- }
- />
- )}
- <SectionHeader>Plan Quotas</SectionHeader>
- <SectionHeaderDescription>
- Monthly quantities for each SKU
- </SectionHeaderDescription>
- <NumberField
- label="Reserved Errors"
- name="reservedErrors"
- required={!!data.plan}
- value={this.state.data.reservedErrors}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {...state.data, reservedErrors: v},
- }))
- }
- />
- <SelectField
- label="Soft Cap Type Errors"
- name="softCapTypeErrors"
- clearable
- required={false}
- choices={[
- ['ON_DEMAND', 'On Demand'],
- ['TRUE_FORWARD', 'True Forward'],
- ]}
- disabled={this.isEnablingOnDemandMaxSpend()}
- value={this.state.data.softCapTypeErrors}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {...state.data, softCapTypeErrors: v},
- }))
- }
- />
- <NumberField
- label="Reserved Performance Units"
- name="reservedTransactions"
- required={isAmEnt}
- disabled={!isAmEnt || isAm3}
- value={this.state.data.reservedTransactions}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {...state.data, reservedTransactions: v},
- }))
- }
- />
- <SelectField
- label="Soft Cap Type Performance Units"
- name="softCapTypeTransactions"
- clearable
- required={false}
- choices={[
- ['ON_DEMAND', 'On Demand'],
- ['TRUE_FORWARD', 'True Forward'],
- ]}
- disabled={this.isEnablingOnDemandMaxSpend() || !isAmEnt || isAm3}
- value={this.state.data.softCapTypeTransactions}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {...state.data, softCapTypeTransactions: v},
- }))
- }
- />
- <NumberField
- label="Reserved Replays"
- name="reservedReplays"
- required={isAmEnt}
- disabled={!isAmEnt}
- value={this.state.data.reservedReplays}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {...state.data, reservedReplays: v},
- }))
- }
- />
- <SelectField
- label="Soft Cap Type Replays"
- name="softCapTypeReplays"
- clearable
- required={false}
- choices={[
- ['ON_DEMAND', 'On Demand'],
- ['TRUE_FORWARD', 'True Forward'],
- ]}
- disabled={this.isEnablingOnDemandMaxSpend() || !isAmEnt}
- value={this.state.data.softCapTypeReplays}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {...state.data, softCapTypeReplays: v},
- }))
- }
- />
- <NumberField
- label={`Reserved ${isAm3Ds ? 'Accepted Spans' : 'Spans'}`}
- name="reservedSpans"
- required={isAmEnt}
- disabled={!isAm3 || this.state.data.reservedCpeSpans}
- value={this.state.data.reservedSpans}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {...state.data, reservedSpans: v},
- }))
- }
- />
- <SelectField
- label={`Soft Cap Type ${isAm3Ds ? 'Accepted Spans' : 'Spans'}`}
- name="softCapTypeSpans"
- clearable
- required={false}
- choices={[
- ['ON_DEMAND', 'On Demand'],
- ['TRUE_FORWARD', 'True Forward'],
- ]}
- disabled={this.isEnablingOnDemandMaxSpend() || !isAm3}
- value={this.state.data.softCapTypeSpans}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {...state.data, softCapTypeSpans: v},
- }))
- }
- />
- {isAm3Ds && (
- <StyledDollarsAndCentsField
- label={`Reserved Cost-Per-${isAm3Ds ? 'Accepted Span' : 'Span'}`}
- name="reservedCpeSpans"
- disabled={!isAm3Ds}
- value={data.reservedCpeSpans}
- step={0.00000001}
- min={0.00000001}
- max={1}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- reservedCpeSpans: v,
- reservedSpans: RESERVED_BUDGET_QUOTA,
- },
- }))
- }
- onBlur={() => {
- const currentValue = parseFloat(this.state.data.reservedCpeSpans);
- if (!isNaN(currentValue)) {
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- reservedCpeSpans: currentValue.toFixed(CPE_DECIMAL_PRECISION),
- },
- }));
- }
- }}
- />
- )}
- {isAm3Ds && (
- <Fragment>
- <NumberField
- label="Reserved Stored Spans"
- name="reservedSpansIndexed"
- required={isAmEnt}
- disabled={!isAm3Ds || this.state.data.reservedCpeSpansIndexed}
- value={this.state.data.reservedSpansIndexed}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {...state.data, reservedSpansIndexed: v},
- }))
- }
- />
- <SelectField
- label="Soft Cap Type Stored Spans"
- name="softCapTypeSpansIndexed"
- clearable
- required={false}
- choices={[
- ['ON_DEMAND', 'On Demand'],
- ['TRUE_FORWARD', 'True Forward'],
- ]}
- disabled={this.isEnablingOnDemandMaxSpend() || !isAm3Ds}
- value={this.state.data.softCapTypeSpansIndexed}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {...state.data, softCapTypeSpansIndexed: v},
- }))
- }
- />
- <StyledDollarsAndCentsField
- label="Reserved Cost-Per-Stored Span"
- name="reservedCpeSpansIndexed"
- disabled={!isAm3Ds}
- value={data.reservedCpeSpansIndexed}
- step={0.00000001}
- min={0.00000001}
- max={1}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- reservedCpeSpansIndexed: v,
- reservedSpansIndexed: RESERVED_BUDGET_QUOTA,
- },
- }))
- }
- onBlur={() => {
- const currentValue = parseFloat(
- this.state.data.reservedCpeSpansIndexed
- );
- if (!isNaN(currentValue)) {
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- reservedCpeSpansIndexed:
- currentValue.toFixed(CPE_DECIMAL_PRECISION),
- },
- }));
- }
- }}
- />
- </Fragment>
- )}
- <NumberField
- label="Reserved Monitor Seats"
- name="reservedMonitorSeats"
- required={isAmEnt}
- disabled={!isAmEnt}
- value={this.state.data.reservedMonitorSeats}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {...state.data, reservedMonitorSeats: v},
- }))
- }
- />
- <SelectField
- label="Soft Cap Type Monitor Seats"
- name="softCapTypeMonitorSeats"
- clearable
- required={false}
- choices={[
- ['ON_DEMAND', 'On Demand'],
- ['TRUE_FORWARD', 'True Forward'],
- ]}
- disabled={this.isEnablingOnDemandMaxSpend() || !isAmEnt}
- value={this.state.data.softCapTypeMonitorSeats}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {...state.data, softCapTypeMonitorSeats: v},
- }))
- }
- />
- <Fragment>
- <NumberField
- label="Reserved Uptime"
- name="reservedUptime"
- required={isAmEnt}
- disabled={!isAmEnt}
- value={this.state.data.reservedUptime}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {...state.data, reservedUptime: v},
- }))
- }
- />
- <SelectField
- label="Soft Cap Type Uptime"
- name="softCapTypeUptime"
- clearable
- required={false}
- choices={[
- ['ON_DEMAND', 'On Demand'],
- ['TRUE_FORWARD', 'True Forward'],
- ]}
- disabled={this.isEnablingOnDemandMaxSpend() || !isAmEnt}
- value={this.state.data.softCapTypeUptime}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {...state.data, softCapTypeUptime: v},
- }))
- }
- />
- </Fragment>
- <NumberField
- label="Reserved Attachments (in GB)"
- name="reservedAttachments"
- required={isAmEnt}
- disabled={!isAmEnt}
- value={this.state.data.reservedAttachments}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {...state.data, reservedAttachments: v},
- }))
- }
- />
- <SelectField
- label="Soft Cap Type Attachments"
- name="softCapTypeAttachments"
- clearable
- required={false}
- choices={[
- ['ON_DEMAND', 'On Demand'],
- ['TRUE_FORWARD', 'True Forward'],
- ]}
- disabled={this.isEnablingOnDemandMaxSpend() || !isAmEnt}
- value={this.state.data.softCapTypeAttachments}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {...state.data, softCapTypeAttachments: v},
- }))
- }
- />
- <NumberField
- label="Reserved Profile Duration (in hours)"
- name="reservedProfileDuration"
- required={isAmEnt}
- disabled={!isAmEnt || true} // TODO: remove this when profile duration is enabled
- value={this.state.data.reservedProfileDuration}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {...state.data, reservedProfileDuration: v},
- }))
- }
- />
- <SelectField
- label="Soft Cap Type Profile Duration"
- name="softCapTypeProfileDuration"
- clearable
- required={false}
- choices={[
- ['ON_DEMAND', 'On Demand'],
- ['TRUE_FORWARD', 'True Forward'],
- ]}
- disabled={this.isEnablingOnDemandMaxSpend() || !isAmEnt || true} // TODO: remove this when profile duration is enabled
- value={this.state.data.softCapTypeProfileDuration}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {...state.data, softCapTypeProfileDuration: v},
- }))
- }
- />
- </div>
- <div>
- <SectionHeader>Reserved Volume Prices</SectionHeader>
- <SectionHeaderDescription>
- Annual prices for reserved volumes, in whole dollars.
- </SectionHeaderDescription>
- <StyledDollarsField
- label="Price for Errors"
- name="customPriceErrors"
- disabled={!hasCustomSkuPrices}
- required={hasCustomSkuPrices}
- value={data.customPriceErrors}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- customPriceErrors: v,
- },
- }))
- }
- />
- <StyledDollarsField
- label="Price for Performance Units"
- name="customPriceTransactions"
- disabled={!hasCustomSkuPrices || isAm3}
- required={hasCustomSkuPrices}
- value={data.customPriceTransactions}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- customPriceTransactions: v,
- },
- }))
- }
- />
- <StyledDollarsField
- label="Price for Replays"
- name="customPriceReplays"
- disabled={!hasCustomSkuPrices}
- required={hasCustomSkuPrices}
- value={data.customPriceReplays}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- customPriceReplays: v,
- },
- }))
- }
- />
- <StyledDollarsField
- label={`Price for ${isAm3Ds ? 'Accepted Spans' : 'Spans'}${this.isSettingSpansBudget() ? ' (Reserved Spans Budget)' : ''}`}
- name="customPriceSpans"
- disabled={!hasCustomSkuPrices || !isAm3}
- required={hasCustomSkuPrices}
- value={data.customPriceSpans}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- customPriceSpans: v,
- },
- }))
- }
- />
- {isAm3Ds && (
- <StyledDollarsField
- label={`Price for Stored Spans`}
- name="customPriceSpansIndexed"
- disabled={
- !hasCustomSkuPrices || !isAm3Ds || this.isSettingSpansBudget()
- }
- required={hasCustomSkuPrices}
- value={this.isSettingSpansBudget() ? 0 : data.customPriceSpansIndexed}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- customPriceSpansIndexed: v,
- },
- }))
- }
- />
- )}
- <StyledDollarsField
- label="Price for Monitor Seats"
- name="customPriceMonitorSeats"
- disabled={!hasCustomSkuPrices}
- required={hasCustomSkuPrices}
- value={data.customPriceMonitorSeats}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- customPriceMonitorSeats: v,
- },
- }))
- }
- />
- <StyledDollarsField
- label="Price for Uptime"
- name="customPriceUptime"
- disabled={!hasCustomSkuPrices}
- required={hasCustomSkuPrices}
- value={data.customPriceUptime}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- customPriceUptime: v,
- },
- }))
- }
- />
- <StyledDollarsField
- label="Price for Attachments"
- name="customPriceAttachments"
- disabled={!hasCustomSkuPrices}
- required={hasCustomSkuPrices}
- value={data.customPriceAttachments}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- customPriceAttachments: v,
- },
- }))
- }
- />
- <StyledDollarsField
- label="Price for Profile Duration"
- name="customPriceProfileDuration"
- disabled={!hasCustomSkuPrices || true} // TODO: remove this when profile duration is enabled
- required={hasCustomSkuPrices}
- value={data.customPriceProfileDuration}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- customPriceProfileDuration: v,
- },
- }))
- }
- />
- <StyledDollarsField
- label="Price for PCSS"
- name="customPricePcss"
- disabled={!hasCustomSkuPrices}
- required={hasCustomSkuPrices}
- value={data.customPricePcss}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- customPricePcss: v,
- },
- }))
- }
- />
- <StyledDollarsField
- label="Annual Contract Value"
- name="customPrice"
- help="Used as a checksum, must be equal to sum of prices above"
- disabled={!hasCustomPrice}
- value={data.customPrice}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- customPrice: v,
- },
- }))
- }
- />
- {this.isEnablingOnDemandMaxSpend() && (
- <Fragment>
- <SectionHeader>On-Demand Cost-Per-Event (CPE)</SectionHeader>
- <SectionHeaderDescription>
- The cost of on-demand units, in dollars, for invoiced customers with
- on-demand max spend. If not set, the on-demand spend will be
- calculated with the self-serve on-demand pricing.
- </SectionHeaderDescription>
- <Alert.Container>
- <Alert type="warning">
- If the subscription already has on-demand spend in the current
- period, and the new cost-per-event overrides would cause the spend
- to exceed the on-demand budget, the request will fail.
- </Alert>
- </Alert.Container>
- <StyledDollarsAndCentsField
- label="On-Demand Cost-Per-Error"
- name="onDemandCpeErrors"
- disabled={!this.isEnablingOnDemandMaxSpend()}
- value={data.onDemandCpeErrors}
- step={0.00000001}
- min={0.00000001}
- max={1}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- onDemandCpeErrors: v,
- },
- }))
- }
- onBlur={() => {
- const currentValue = parseFloat(
- this.state.data.onDemandCpeErrors
- );
- if (!isNaN(currentValue)) {
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- onDemandCpeErrors:
- currentValue.toFixed(CPE_DECIMAL_PRECISION),
- },
- }));
- }
- }}
- />
- <StyledDollarsAndCentsField
- label="On-Demand Cost-Per-Performance Unit"
- name="onDemandCpeTransactions"
- disabled={!this.isEnablingOnDemandMaxSpend() || isAm3}
- value={data.onDemandCpeTransactions}
- step={0.00000001}
- min={0.00000001}
- max={1}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- onDemandCpeTransactions: v,
- },
- }))
- }
- onBlur={() => {
- const currentValue = parseFloat(
- this.state.data.onDemandCpeTransactions
- );
- if (!isNaN(currentValue)) {
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- onDemandCpeTransactions:
- currentValue.toFixed(CPE_DECIMAL_PRECISION),
- },
- }));
- }
- }}
- />
- <StyledDollarsAndCentsField
- label="On-Demand Cost-Per-Replay"
- name="onDemandCpeReplays"
- disabled={!this.isEnablingOnDemandMaxSpend()}
- value={data.onDemandCpeReplays}
- step={0.00000001}
- min={0.00000001}
- max={1}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- onDemandCpeReplays: v,
- },
- }))
- }
- onBlur={() => {
- const currentValue = parseFloat(
- this.state.data.onDemandCpeReplays
- );
- if (!isNaN(currentValue)) {
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- onDemandCpeReplays:
- currentValue.toFixed(CPE_DECIMAL_PRECISION),
- },
- }));
- }
- }}
- />
- <StyledDollarsAndCentsField
- label="On-Demand Cost-Per-Span"
- name="onDemandCpeSpans"
- disabled={!this.isEnablingOnDemandMaxSpend() || !isAm3}
- value={data.onDemandCpeSpans}
- step={0.00000001}
- min={0.00000001}
- max={1}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- onDemandCpeSpans: v,
- },
- }))
- }
- onBlur={() => {
- const currentValue = parseFloat(this.state.data.onDemandCpeSpans);
- if (!isNaN(currentValue)) {
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- onDemandCpeSpans:
- currentValue.toFixed(CPE_DECIMAL_PRECISION),
- },
- }));
- }
- }}
- />
- <StyledDollarsAndCentsField
- label="On-Demand Cost-Per-Attachment"
- name="onDemandCpeAttachments"
- disabled={!this.isEnablingOnDemandMaxSpend()}
- value={data.onDemandCpeAttachments}
- step={0.00000001}
- min={0.00000001}
- max={1}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- onDemandCpeAttachments: v,
- },
- }))
- }
- onBlur={() => {
- const currentValue = parseFloat(
- this.state.data.onDemandCpeAttachments
- );
- if (!isNaN(currentValue)) {
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- onDemandCpeAttachments:
- currentValue.toFixed(CPE_DECIMAL_PRECISION),
- },
- }));
- }
- }}
- />
- <StyledDollarsAndCentsField
- label="On-Demand Cost-Per-Profile Duration"
- name="onDemandCpeProfileDuration"
- disabled={!this.isEnablingOnDemandMaxSpend() || true} // TODO: remove this when profile duration is enabled
- value={data.onDemandCpeProfileDuration}
- step={0.00000001}
- min={0.00000001}
- max={1}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- onDemandCpeProfileDuration: v,
- },
- }))
- }
- onBlur={() => {
- const currentValue = parseFloat(
- this.state.data.onDemandCpeProfileDuration
- );
- if (!isNaN(currentValue)) {
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- onDemandCpeProfileDuration:
- currentValue.toFixed(CPE_DECIMAL_PRECISION),
- },
- }));
- }
- }}
- />
- <StyledDollarsAndCentsField
- label="On-Demand Cost-Per-Cron Monitor"
- name="onDemandCpeMonitorSeats"
- disabled={!this.isEnablingOnDemandMaxSpend()}
- step={0.00000001}
- min={0.00000001}
- max={1}
- value={data.onDemandCpeMonitorSeats}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- onDemandCpeMonitorSeats: v,
- },
- }))
- }
- onBlur={() => {
- const currentValue = parseFloat(
- this.state.data.onDemandCpeMonitorSeats
- );
- if (!isNaN(currentValue)) {
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- onDemandCpeMonitorSeats:
- currentValue.toFixed(CPE_DECIMAL_PRECISION),
- },
- }));
- }
- }}
- />
- <StyledDollarsAndCentsField
- label="On-Demand Cost-Per-Uptime Monitor"
- name="onDemandCpeUptime"
- disabled={!this.isEnablingOnDemandMaxSpend()}
- step={0.00000001}
- min={0.00000001}
- max={1}
- value={data.onDemandCpeUptime}
- onChange={v =>
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- onDemandCpeUptime: v,
- },
- }))
- }
- onBlur={() => {
- const currentValue = parseFloat(
- this.state.data.onDemandCpeUptime
- );
- if (!isNaN(currentValue)) {
- this.setState(state => ({
- ...state,
- data: {
- ...state.data,
- onDemandCpeUptime:
- currentValue.toFixed(CPE_DECIMAL_PRECISION),
- },
- }));
- }
- }}
- />
- </Fragment>
- )}
- </div>
- </Columns>
- </Form>
- </Body>
- </Fragment>
- );
- }
- }
- const Columns = styled('div')`
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: ${space(3)};
- `;
- const SectionHeader = styled('h5')`
- margin-bottom: 0;
- `;
- const SectionHeaderDescription = styled('small')`
- display: block;
- margin-bottom: ${space(3)};
- `;
- const modalCss = css`
- width: 100%;
- max-width: 1200px;
- `;
- const StyledSelectFieldWithHelpText = styled(SelectField)`
- margin-bottom: 15px;
- div[class*='StyledSelectControl'] {
- margin-bottom: 0;
- }
- `;
- const StyledDollarsField = styled(DollarsField)`
- div[class='dollars-field-container'] {
- display: flex;
- }
- span[class='dollar-sign'] {
- padding: 12px;
- }
- `;
- const StyledDollarsAndCentsField = styled(DollarsAndCentsField)`
- div[class='dollars-cents-field-container'] {
- display: flex;
- }
- span[class='dollar-sign'] {
- padding: 12px;
- }
- `;
- const Modal = withApi(ProvisionSubscriptionModal);
- type Options = Pick<Props, 'orgId' | 'subscription' | 'onSuccess' | 'canProvisionDsPlan'>;
- const triggerProvisionSubscription = (opts: Options) =>
- openModal(deps => <Modal {...deps} {...opts} />, {modalCss});
- export default triggerProvisionSubscription;
|