From 8c56a3a9445eed09b1343a65e9c05bf01e2da4f9 Mon Sep 17 00:00:00 2001 From: Alexandre Monjol Date: Tue, 13 Jun 2023 13:51:52 +0200 Subject: [PATCH] feat: charge paid in advance --- cypress/e2e/t3-create-bm.cy.ts | 6 +- cypress/e2e/t4-create-plan.cy.ts | 12 + ditto/base.json | 25 +- .../BillableMetricCodeSnippet.tsx | 32 +- .../form/ButtonSelector/ButtonSelector.tsx | 1 + .../form/ButtonSelector/TabButton.tsx | 6 +- src/components/plans/ChargeAccordion.tsx | 95 ++++- .../plans/ChargeOptionsAccordion.tsx | 9 + src/components/plans/ChargesSection.tsx | 395 +++++++++++++++--- .../__tests__/serializePlanInput.test.ts | 5 + src/generated/graphql.tsx | 120 ++++-- .../__tests__/useGraduatedChargeForm.test.tsx | 1 + .../__tests__/useVolumeChargeForm.test.tsx | 1 + src/pages/CreateBillableMetric.tsx | 116 +++-- src/pages/CreatePlan.tsx | 9 +- 15 files changed, 619 insertions(+), 214 deletions(-) diff --git a/cypress/e2e/t3-create-bm.cy.ts b/cypress/e2e/t3-create-bm.cy.ts index 324a8c530..d39e867c6 100644 --- a/cypress/e2e/t3-create-bm.cy.ts +++ b/cypress/e2e/t3-create-bm.cy.ts @@ -103,9 +103,9 @@ describe('Create billable metrics', () => { cy.get('textarea[name="description"]').type('I am a description') cy.get('[data-test="submit"]').should('be.disabled') cy.get('input[name="aggregationType"]').click() - cy.get('[data-test="recurring_count_agg"]').click() - cy.get('[data-test="submit"]').should('be.disabled') - cy.get('input[name="fieldName"]').type('whatever') + cy.get('[data-test="count_agg"]').click() + cy.get('input[name="fieldName"]').should('not.exist') + cy.get('[data-test="button-selector-true"]').click() cy.get('[data-test="submit"]').should('not.be.disabled') cy.get('[data-test="submit"]').click() cy.url().should('be.equal', Cypress.config().baseUrl + '/billable-metrics') diff --git a/cypress/e2e/t4-create-plan.cy.ts b/cypress/e2e/t4-create-plan.cy.ts index 488be622d..ed4e2cd87 100644 --- a/cypress/e2e/t4-create-plan.cy.ts +++ b/cypress/e2e/t4-create-plan.cy.ts @@ -43,6 +43,8 @@ describe('Create plan', () => { // Standard cy.get('[data-test="add-charge"]').first().click() + cy.get('[data-test="add-metered-charge"]').first().click() + cy.get('input[name="searchMeteredChargeInput"]').click() cy.get('[data-option-index="0"]').click() cy.get('[data-test="remove-charge"]').should('exist').and('not.be.disabled') cy.get('input[name="chargeModel"]').last().should('have.value', 'Standard pricing') @@ -51,6 +53,8 @@ describe('Create plan', () => { // Graduated cy.get('[data-test="add-charge"]').last().click() + cy.get('[data-test="add-metered-charge"]').last().click() + cy.get('input[name="searchMeteredChargeInput"]').click() cy.get('[data-option-index="1"]').click() cy.get('[data-test="remove-charge"]').should('exist').and('not.be.disabled') cy.get('input[name="chargeModel"]').last().click() @@ -66,6 +70,8 @@ describe('Create plan', () => { // Package cy.get('[data-test="add-charge"]').last().click() + cy.get('[data-test="add-metered-charge"]').last().click() + cy.get('input[name="searchMeteredChargeInput"]').click() cy.get('[data-option-index="1"]').click() cy.get('[data-test="remove-charge"]').should('exist').and('not.be.disabled') cy.get('input[name="chargeModel"]').last().click() @@ -76,6 +82,8 @@ describe('Create plan', () => { // Percentage cy.get('[data-test="add-charge"]').last().click() + cy.get('[data-test="add-metered-charge"]').last().click() + cy.get('input[name="searchMeteredChargeInput"]').click() cy.get('[data-option-index="1"]').click() cy.get('[data-test="remove-charge"]').should('exist').and('not.be.disabled') cy.get('input[name="chargeModel"]').last().click() @@ -94,6 +102,8 @@ describe('Create plan', () => { // Volume cy.get('[data-test="add-charge"]').last().click() + cy.get('[data-test="add-recurring-charge"]').last().click() + cy.get('input[name="searchRecurringChargeInput"]').click() cy.get('[data-option-index="1"]').click() cy.get('[data-test="remove-charge"]').should('exist').and('not.be.disabled') cy.get('input[name="chargeModel"]').last().click() @@ -127,6 +137,8 @@ describe('Create plan', () => { // Config charge cy.get('[data-test="add-charge"]').last().click() + cy.get('[data-test="add-metered-charge"]').last().click() + cy.get('input[name="searchMeteredChargeInput"]').click() cy.get('[data-option-index="1"]').click() cy.get('[data-test="remove-charge"]').should('exist').and('not.be.disabled') cy.get('input[name="chargeModel"]').last().click() diff --git a/ditto/base.json b/ditto/base.json index 56ea71584..6f17bff22 100644 --- a/ditto/base.json +++ b/ditto/base.json @@ -788,6 +788,25 @@ "text_646e2d0cc536351b62ba6f25": "Generate an invoice for each event", "text_646e2d0cc536351b62ba6f35": "Turn it off to avoid generating an invoice for each event.", "text_646e2d0cc536351b62ba6efd": "Charges are invoiced at the end of the billing period and cannot be charged in advance due to their aggregation type being defined as recurring count.", + "text_648c2be974f70300748a4ca1": "Type", + "text_648c2be974f70300748a4ca5": "Metered", + "text_648c2be974f70300748a4cad": "The calculated value is reset to 0 at the beginning of each period", + "text_648c2be974f70300748a4c87": "Recurring", + "text_648c2be974f70300748a4c8c": "The calculated value is not reset to 0, it is persistent over all billable periods", + "text_648c2be974f70300748a4ca6": "This billable metric is metered, the calculated value is reset to 0 at the beginning of each period", + "text_648c2be974f70300748a4cc6": "Add a metered charge", + "text_648c2be974f70300748a4cc8": "Add a recurring charge", + "text_648c2be974f70300748a4cc7": "Metered charges", + "text_648c2be974f70300748a4cc9": "Charges are fully billed, invoiced and reset at the end of each billing period.", + "text_648c2be974f70300748a4cce": "Add a metered charge", + "text_648c2be974f70300748a4ccf": "Recurring charges", + "text_648c2be974f70300748a4cd0": "Charges persist across billing periods and can be billed and invoiced either upon event receipt or at the end of the billing period.", + "text_648c2be974f70300748a4cd4": "Add a recurring charge", + "text_649c54823c90890062476255": "Prorate charge amount", + "text_649c47c0a6c1f200de8ff48d": "Prorated charge", + "text_649c54823c9089006247625a": "Can’t be prorated due to {{chargeModel}} pricing model defined in above.", + "text_649c49bcebd91c0082d84446": "Full charge", + "text_649c54823c90890062476259": "Based between the event reception date and the end date of the period", "text_645bb193927b375079d28a8f": "Taxes", "text_645bb193927b375079d28ab5": "Taxes settings", "text_645bb193927b375079d28ad2": "Add", @@ -984,12 +1003,6 @@ "text_645d071272418a14c1c76b25": "Adyen connection successfully deleted", "text_645d0728ea0a5a7bbf76d5c7": "Create automatically this customer in Adyen", "text_645d0728ea0a5a7bbf76d5c9": "To ensure customer creation, their external ID must contain a minimum of 3 characters, and the payment registration must be completed using the checkout URL sent via webhook.", - "text_6310755befed49627644222b": "Metered", - "text_6310755befed49627644222d": "• The calculated value is reset to 0 at the beginning of each period", - "text_6310755befed49627644222f": "Persistent", - "text_6310755befed496276442231": "• The calculated value is not reset to 0, it persists over all billing periods", - "text_63105dbdd88c7432a3b255eb": "Recurring count", - "text_6435888d7cc86500646d897a": "Add a charge", "text_64639c4d172d7a006ef30516": "Create a tax_rate", "text_64639c4d172d7a006ef30514": "Tax rate", "text_64639c4d172d7a006ef30515": "Search or select a tax rate", diff --git a/src/components/billableMetrics/BillableMetricCodeSnippet.tsx b/src/components/billableMetrics/BillableMetricCodeSnippet.tsx index ea95347b3..fe71441c1 100644 --- a/src/components/billableMetrics/BillableMetricCodeSnippet.tsx +++ b/src/components/billableMetrics/BillableMetricCodeSnippet.tsx @@ -7,7 +7,7 @@ const { apiUrl } = envGlobalVar() const getSnippets = (billableMetric?: CreateBillableMetricInput) => { if (!billableMetric) return '# Fill the form to generate the code snippet' - const { aggregationType, code, fieldName, group } = billableMetric + const { aggregationType, code, fieldName, group, recurring } = billableMetric const isValidJSON = (string: string) => { try { @@ -70,6 +70,7 @@ const getSnippets = (billableMetric?: CreateBillableMetricInput) => { "external_subscription_id": "__EXTERNAL_SUBSCRIPTION_ID__", "external_customer_id": "__EXTERNAL_CUSTOMER_ID__", "code": "${code}", + "recurring": "${recurring}", "timestamp": $(date +%s)${ groupDimension > 0 ? `, @@ -120,7 +121,8 @@ ${groupDimensionMessage} "transaction_id": "__UNIQUE_ID__", "external_subscription_id": "__EXTERNAL_SUBSCRIPTION_ID__", "external_customer_id": "__EXTERNAL_CUSTOMER_ID__", - "code": "${code}", + "code": "${code}", + "recurring": "${recurring}", "timestamp": $(date +%s), "properties": { "${fieldName}": 12${ @@ -135,32 +137,6 @@ ${groupDimensionMessage} # To use the snippet, don’t forget to edit your __YOUR_API_KEY__, __UNIQUE_ID__, __EXTERNAL_SUBSCRIPTION_ID__ and __EXTERNAL_CUSTOMER_ID__ ${groupDimensionMessage} -` - case AggregationTypeEnum.RecurringCountAgg: - return `curl --location --request POST "${apiUrl}/api/v1/events" \\ - --header "Authorization: Bearer $__YOUR_API_KEY__" \\ - --header 'Content-Type: application/json' \\ - --data-raw '{ - "event": { - "transaction_id": "__UNIQUE_ID__", - "external_subscription_id": "__EXTERNAL_SUBSCRIPTION_ID__", - "external_customer_id": "__EXTERNAL_CUSTOMER_ID__", - "code": "${code}", - "timestamp": $(date +%s), - "properties": { - "${fieldName}": "__VALUE__" , - "operation_type": "add"${ - groupDimension > 0 - ? `, - ${propertiesForGroup}` - : '' - } - } - } - }' - -# To use the snippet, don’t forget to edit your __YOUR_API_KEY__, __UNIQUE_ID__, __EXTERNAL_SUBSCRIPTION_ID__, __EXTERNAL_CUSTOMER_ID__ and __VALUE__ -${groupDimensionMessage} ` default: return '# Fill the form to generate the code snippet' diff --git a/src/components/form/ButtonSelector/ButtonSelector.tsx b/src/components/form/ButtonSelector/ButtonSelector.tsx index 1303c1465..1beb36e8f 100644 --- a/src/components/form/ButtonSelector/ButtonSelector.tsx +++ b/src/components/form/ButtonSelector/ButtonSelector.tsx @@ -60,6 +60,7 @@ export const ButtonSelector = ({ title={optionLabel ?? optionValue} active={value === optionValue} onClick={() => onChange(optionValue)} + data-test={`button-selector-${optionValue}`} /> ) })} diff --git a/src/components/form/ButtonSelector/TabButton.tsx b/src/components/form/ButtonSelector/TabButton.tsx index 66f229298..a0bf3ce55 100644 --- a/src/components/form/ButtonSelector/TabButton.tsx +++ b/src/components/form/ButtonSelector/TabButton.tsx @@ -17,7 +17,10 @@ export interface TabButtonProps { } export const TabButton = forwardRef( - ({ active = false, title, icon, className, disabled, onClick }: TabButtonProps, ref) => { + ( + { active = false, title, icon, className, disabled, onClick, ...props }: TabButtonProps, + ref + ) => { const [isLoading, setIsLoading] = useState(false) const mountedRef = useRef(false) @@ -32,6 +35,7 @@ export const TabButton = forwardRef( return ( { - if ( - name === 'chargeModel' && - (value === ChargeModelEnum.Volume || + if (name === 'chargeModel') { + // Reset pay in advance when switching charge model + if ( + value === ChargeModelEnum.Volume || localCharge.billableMetric.aggregationType === AggregationTypeEnum.MaxAgg || - localCharge.billableMetric.aggregationType === AggregationTypeEnum.RecurringCountAgg) - ) { - formikProps.setFieldValue(`charges.${index}.payInAdvance`, false) + localCharge.billableMetric.aggregationType === AggregationTypeEnum.RecurringCountAgg + ) { + formikProps.setFieldValue(`charges.${index}.payInAdvance`, false) + } + + // Reset prorated when switching charge model + if ( + (localCharge.billableMetric.recurring && value === ChargeModelEnum.Graduated) || + value === ChargeModelEnum.Package || + value === ChargeModelEnum.Percentage + ) { + formikProps.setFieldValue(`charges.${index}.prorated`, false) + } } if (name === 'payInAdvance') { @@ -156,11 +169,15 @@ export const ChargeAccordion = memo( formikProps.setFieldValue(`charges.${index}.invoiceable`, true) } } - formikProps.setFieldValue(`charges.${index}.${name}`, value) }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [index, formikProps.setFieldValue] + + [ + formikProps, + index, + localCharge.billableMetric.aggregationType, + localCharge.billableMetric.recurring, + ] ) const chargePayInAdvanceSwitchHelperText = useMemo(() => { @@ -185,6 +202,21 @@ export const ChargeAccordion = memo( translate, ]) + const isProratedOptionDisabled = useMemo(() => { + return ( + localCharge.chargeModel === ChargeModelEnum.Volume || + localCharge.chargeModel === ChargeModelEnum.Package || + localCharge.chargeModel === ChargeModelEnum.Percentage + ) + }, [localCharge.chargeModel]) + + const proratedOptionHelperText = useMemo(() => { + if (isProratedOptionDisabled) + return translate('text_649c54823c9089006247625a', { chargeModel: localCharge.chargeModel }) + + return translate('text_649c54823c90890062476259') + }, [isProratedOptionDisabled, translate, localCharge.chargeModel]) + return ( + + {!!localCharge.billableMetric.recurring && ( + handleUpdate('prorated', Boolean(value))} + /> + )} {localCharge.payInAdvance && ( + {!!charge.billableMetric.recurring && ( + + )} alreadyExistingCharges?: PlanFormInput['charges'] | null @@ -62,38 +76,74 @@ interface ChargesSectionProps { const getNewChargeId = (id: string, index: number) => `plan-charge-${id}-${index}` +const SEARCH_METERED_CHARGE_INPUT_NAME = 'searchMeteredChargeInput' +const SEARCH_RECURRING_CHARGE_INPUT_NAME = 'searchRecurringChargeInput' + export const ChargesSection = memo( ({ canBeEdited, isEdition, + hasAnyMeteredCharge, + hasAnyRecurringCharge, getPropertyShape, formikProps, alreadyExistingCharges, }: ChargesSectionProps) => { const { translate } = useInternationalization() - // const { isPremium } = useCurrentUser() const hasAnyCharge = !!formikProps.values.charges.length - const [showAddCharge, setShowAddCharge] = useState(false) + const [showAddMeteredCharge, setShowAddMeteredCharge] = useState(false) + const [showAddRecurringCharge, setShowAddRecurringCharge] = useState(false) const [newChargeId, setNewChargeId] = useState(null) const premiumWarningDialogRef = useRef(null) const removeChargeWarningDialogRef = useRef(null) const alreadyUsedBmsIds = useRef>(new Map()) - const [getBillableMetrics, { loading: billableMetricsLoading, data: billableMetricsData }] = - useGetBillableMetricsLazyQuery({ - fetchPolicy: 'network-only', - nextFetchPolicy: 'network-only', - variables: { limit: 50 }, + const [ + getMeteredBillableMetrics, + { loading: meteredBillableMetricsLoading, data: meteredBillableMetricsData }, + ] = useGetMeteredBillableMetricsLazyQuery({ + fetchPolicy: 'network-only', + nextFetchPolicy: 'network-only', + variables: { limit: RESULT_LIMIT }, + }) + const [ + getRecurringBillableMetrics, + { loading: recurringBillableMetricsLoading, data: recurringBillableMetricsData }, + ] = useGetRecurringBillableMetricsLazyQuery({ + fetchPolicy: 'network-only', + nextFetchPolicy: 'network-only', + variables: { limit: RESULT_LIMIT }, + }) + + const meteredBillableMetrics = useMemo(() => { + if ( + !meteredBillableMetricsData || + !meteredBillableMetricsData?.billableMetrics || + !meteredBillableMetricsData?.billableMetrics?.collection + ) + return [] + + return meteredBillableMetricsData?.billableMetrics?.collection.map(({ id, name, code }) => { + return { + label: `${name} (${code})`, + labelNode: ( + + {name} ({code}) + + ), + value: id, + } }) + }, [meteredBillableMetricsData]) - const billableMetrics = useMemo(() => { + const recurringBillableMetrics = useMemo(() => { if ( - !billableMetricsData || - !billableMetricsData?.billableMetrics || - !billableMetricsData?.billableMetrics?.collection + !recurringBillableMetricsData || + !recurringBillableMetricsData?.billableMetrics || + !recurringBillableMetricsData?.billableMetrics?.collection ) return [] - return billableMetricsData?.billableMetrics?.collection.map(({ id, name, code }) => { + return recurringBillableMetricsData?.billableMetrics?.collection.map(({ id, name, code }) => { return { label: `${name} (${code})`, labelNode: ( @@ -104,7 +154,7 @@ export const ChargesSection = memo( value: id, } }) - }, [billableMetricsData]) + }, [recurringBillableMetricsData]) useEffect(() => { // When adding a new charge, scroll to the new charge element @@ -141,28 +191,66 @@ export const ChargesSection = memo( {translate('text_6435888d7cc86500646d8977')} - + } > - {translate('text_6435888d7cc86500646d8974')} - + {({ closePopper }) => ( + + + + + )} + + {/* METERED */} {!!hasAnyCharge && formikProps.values.interval === PlanInterval.Yearly && ( )} - {!!hasAnyCharge && ( + {(hasAnyMeteredCharge || showAddMeteredCharge) && ( +
+ + {translate('text_648c2be974f70300748a4cc7')} + + + {translate('text_648c2be974f70300748a4cc9')} + +
+ )} + + {hasAnyMeteredCharge && ( + + {formikProps.values.charges.map((charge, i) => { + // Prevent displaying recurring charges + if (charge.billableMetric.recurring) return + + const id = getNewChargeId(charge.billableMetric.id, i) + const isNew = !alreadyExistingCharges?.find( + (chargeFetched) => chargeFetched?.id === charge.id + ) + const shouldDisplayAlreadyUsedChargeAlert = + (alreadyUsedBmsIds.current.get(charge.billableMetric.id) || 0) > 1 + + return ( + + ) + })} + + )} + {!!showAddMeteredCharge && ( + + { + const previousCharges = [...formikProps.values.charges] + const newId = getNewChargeId(newCharge, previousCharges.length) + const localBillableMetrics = + meteredBillableMetricsData?.billableMetrics?.collection.find( + (bm) => bm.id === newCharge + ) + const lastMeteredIndex = previousCharges.findLastIndex( + (c) => c.billableMetric.recurring === false + ) + const newChargeIndex = lastMeteredIndex < 0 ? 0 : lastMeteredIndex + 1 + + previousCharges.splice(newChargeIndex, 0, { + payInAdvance: false, + invoiceable: true, + billableMetric: localBillableMetrics, + properties: !localBillableMetrics?.flatGroups?.length + ? getPropertyShape({}) + : undefined, + groupProperties: localBillableMetrics?.flatGroups?.length + ? localBillableMetrics?.flatGroups.map((group) => { + return { + groupId: group.id, + values: getPropertyShape({}), + } + }) + : undefined, + chargeModel: ChargeModelEnum.Standard, + amountCents: undefined, + } as LocalChargeInput) + + formikProps.setFieldValue('charges', previousCharges) + setShowAddMeteredCharge(false) + setNewChargeId(newId) + }} + /> + + + )} + {!showAddRecurringCharge && !hasAnyRecurringCharge && ( + + )} + + )} + + {/* RECURRING */} + {(hasAnyRecurringCharge || showAddRecurringCharge) && ( + + + {translate('text_648c2be974f70300748a4ccf')} + + + {translate('text_648c2be974f70300748a4cd0')} + + + )} + + {hasAnyRecurringCharge && ( {formikProps.values.charges.map((charge, i) => { + // Prevent displaying metered charges + if (!charge.billableMetric.recurring) return + const id = getNewChargeId(charge.billableMetric.id, i) const isNew = !alreadyExistingCharges?.find( (chargeFetched) => chargeFetched?.id === charge.id @@ -200,20 +438,20 @@ export const ChargesSection = memo( })} )} - {!!showAddCharge && ( + {!!showAddRecurringCharge && ( { const previousCharges = [...formikProps.values.charges] const newId = getNewChargeId(newCharge, previousCharges.length) const localBillableMetrics = - billableMetricsData?.billableMetrics?.collection.find( + recurringBillableMetricsData?.billableMetrics?.collection.find( (bm) => bm.id === newCharge ) @@ -238,7 +476,7 @@ export const ChargesSection = memo( amountCents: undefined, }, ]) - setShowAddCharge(false) + setShowAddRecurringCharge(false) setNewChargeId(newId) }} /> @@ -247,30 +485,46 @@ export const ChargesSection = memo( icon="trash" variant="quaternary" onClick={() => { - setShowAddCharge(false) + setShowAddRecurringCharge(false) }} /> )} - {!!hasAnyCharge && !showAddCharge && ( - + + {hasAnyCharge && ( + + {!showAddMeteredCharge && !hasAnyMeteredCharge && ( + + )} + {!showAddRecurringCharge && !!hasAnyRecurringCharge && ( + + )} + )}
@@ -313,3 +567,12 @@ const Charges = styled.div` margin-bottom: ${theme.spacing(6)}; } ` + +const InlineButtons = styled.div` + display: flex; +` + +const RecurringSectionTitleWrapper = styled.div<{ $hasAnyAboveSection: boolean }>` + margin-top: ${({ $hasAnyAboveSection }) => + $hasAnyAboveSection ? `${theme.spacing(12)} !important` : 'initial'}; +` diff --git a/src/core/serializers/__tests__/serializePlanInput.test.ts b/src/core/serializers/__tests__/serializePlanInput.test.ts index c3353ddf6..59c575c76 100644 --- a/src/core/serializers/__tests__/serializePlanInput.test.ts +++ b/src/core/serializers/__tests__/serializePlanInput.test.ts @@ -84,6 +84,7 @@ describe('serializePlanInput()', () => { id: '1234', name: 'simpleBM', code: 'simple-bm', + recurring: false, aggregationType: AggregationTypeEnum.CountAgg, }, properties: fullProperty, @@ -152,6 +153,7 @@ describe('serializePlanInput()', () => { id: '1234', name: 'simpleBM', code: 'simple-bm', + recurring: false, aggregationType: AggregationTypeEnum.CountAgg, }, properties: fullProperty, @@ -209,6 +211,7 @@ describe('serializePlanInput()', () => { id: '1234', name: 'simpleBM', code: 'simple-bm', + recurring: false, aggregationType: AggregationTypeEnum.CountAgg, }, properties: fullProperty, @@ -266,6 +269,7 @@ describe('serializePlanInput()', () => { id: '1234', name: 'simpleBM', code: 'simple-bm', + recurring: false, aggregationType: AggregationTypeEnum.CountAgg, }, properties: fullProperty, @@ -323,6 +327,7 @@ describe('serializePlanInput()', () => { id: '1234', name: 'simpleBM', code: 'simple-bm', + recurring: false, aggregationType: AggregationTypeEnum.CountAgg, }, properties: fullProperty, diff --git a/src/generated/graphql.tsx b/src/generated/graphql.tsx index f0998cf20..754269bd9 100644 --- a/src/generated/graphql.tsx +++ b/src/generated/graphql.tsx @@ -154,6 +154,7 @@ export type BillableMetric = { name: Scalars['String']['output']; organization?: Maybe; plansCount: Scalars['Int']['output']; + recurring: Scalars['Boolean']['output']; subscriptionsCount: Scalars['Int']['output']; updatedAt: Scalars['ISO8601DateTime']['output']; }; @@ -181,6 +182,7 @@ export type Charge = { minAmountCents: Scalars['BigInt']['output']; payInAdvance: Scalars['Boolean']['output']; properties?: Maybe; + prorated: Scalars['Boolean']['output']; updatedAt: Scalars['ISO8601DateTime']['output']; }; @@ -193,6 +195,7 @@ export type ChargeInput = { minAmountCents?: InputMaybe; payInAdvance?: InputMaybe; properties?: InputMaybe; + prorated?: InputMaybe; }; export enum ChargeModelEnum { @@ -811,7 +814,7 @@ export type CreateAppliedCouponInput = { percentageRate?: InputMaybe; }; -/** Autogenerated input type of CreateBillableMetric */ +/** Create Billable metric input arguments */ export type CreateBillableMetricInput = { aggregationType: AggregationTypeEnum; /** A unique identifier for the client performing the mutation. */ @@ -821,6 +824,7 @@ export type CreateBillableMetricInput = { fieldName?: InputMaybe; group?: InputMaybe; name: Scalars['String']['input']; + recurring?: InputMaybe; }; /** Autogenerated input type of CreateCoupon */ @@ -2638,6 +2642,7 @@ export type QueryBillableMetricsArgs = { ids?: InputMaybe>; limit?: InputMaybe; page?: InputMaybe; + recurring?: InputMaybe; searchTerm?: InputMaybe; }; @@ -3291,7 +3296,7 @@ export type UpdateAddOnInput = { name: Scalars['String']['input']; }; -/** Autogenerated input type of UpdateBillableMetric */ +/** Update Billable metric input arguments */ export type UpdateBillableMetricInput = { aggregationType: AggregationTypeEnum; /** A unique identifier for the client performing the mutation. */ @@ -3302,6 +3307,7 @@ export type UpdateBillableMetricInput = { group?: InputMaybe; id: Scalars['String']['input']; name: Scalars['String']['input']; + recurring?: InputMaybe; }; /** Autogenerated input type of UpdateCoupon */ @@ -3971,7 +3977,7 @@ export type CustomerMetadatasForInvoiceOverviewFragment = { __typename?: 'Custom export type InvoiceMetadatasForInvoiceOverviewFragment = { __typename?: 'Invoice', id: string, metadata?: Array<{ __typename?: 'InvoiceMetadata', id: string, key: string, value: string }> | null }; -export type ChargeAccordionFragment = { __typename?: 'Charge', id: string, chargeModel: ChargeModelEnum, invoiceable: boolean, minAmountCents: any, payInAdvance: boolean, properties?: { __typename?: 'Properties', amount?: string | null, packageSize?: any | null, freeUnits?: any | null, fixedAmount?: string | null, freeUnitsPerEvents?: any | null, freeUnitsPerTotalAggregation?: string | null, rate?: string | null, graduatedRanges?: Array<{ __typename?: 'GraduatedRange', flatAmount: string, fromValue: any, perUnitAmount: string, toValue?: any | null }> | null, volumeRanges?: Array<{ __typename?: 'VolumeRange', flatAmount: string, fromValue: any, perUnitAmount: string, toValue?: any | null }> | null } | null, groupProperties?: Array<{ __typename?: 'GroupProperties', groupId: string, values: { __typename?: 'Properties', amount?: string | null, packageSize?: any | null, freeUnits?: any | null, fixedAmount?: string | null, freeUnitsPerEvents?: any | null, freeUnitsPerTotalAggregation?: string | null, rate?: string | null, graduatedRanges?: Array<{ __typename?: 'GraduatedRange', flatAmount: string, fromValue: any, perUnitAmount: string, toValue?: any | null }> | null, volumeRanges?: Array<{ __typename?: 'VolumeRange', flatAmount: string, fromValue: any, perUnitAmount: string, toValue?: any | null }> | null } }> | null, billableMetric: { __typename?: 'BillableMetric', id: string, name: string, code: string, aggregationType: AggregationTypeEnum, flatGroups?: Array<{ __typename?: 'Group', id: string, key?: string | null, value: string }> | null } }; +export type ChargeAccordionFragment = { __typename?: 'Charge', id: string, chargeModel: ChargeModelEnum, invoiceable: boolean, minAmountCents: any, payInAdvance: boolean, prorated: boolean, properties?: { __typename?: 'Properties', amount?: string | null, packageSize?: any | null, freeUnits?: any | null, fixedAmount?: string | null, freeUnitsPerEvents?: any | null, freeUnitsPerTotalAggregation?: string | null, rate?: string | null, graduatedRanges?: Array<{ __typename?: 'GraduatedRange', flatAmount: string, fromValue: any, perUnitAmount: string, toValue?: any | null }> | null, volumeRanges?: Array<{ __typename?: 'VolumeRange', flatAmount: string, fromValue: any, perUnitAmount: string, toValue?: any | null }> | null } | null, groupProperties?: Array<{ __typename?: 'GroupProperties', groupId: string, values: { __typename?: 'Properties', amount?: string | null, packageSize?: any | null, freeUnits?: any | null, fixedAmount?: string | null, freeUnitsPerEvents?: any | null, freeUnitsPerTotalAggregation?: string | null, rate?: string | null, graduatedRanges?: Array<{ __typename?: 'GraduatedRange', flatAmount: string, fromValue: any, perUnitAmount: string, toValue?: any | null }> | null, volumeRanges?: Array<{ __typename?: 'VolumeRange', flatAmount: string, fromValue: any, perUnitAmount: string, toValue?: any | null }> | null } }> | null, billableMetric: { __typename?: 'BillableMetric', id: string, name: string, code: string, aggregationType: AggregationTypeEnum, recurring: boolean, flatGroups?: Array<{ __typename?: 'Group', id: string, key?: string | null, value: string }> | null } }; export type ChargeForChargeOptionsAccordionFragment = { __typename?: 'Charge', id: string, invoiceable: boolean, minAmountCents: any, payInAdvance: boolean }; @@ -3979,16 +3985,25 @@ export type PercentageChargeFragment = { __typename?: 'Charge', id: string, prop export type PlanForChargeAccordionFragment = { __typename?: 'Plan', billChargesMonthly?: boolean | null }; -export type BillableMetricForChargeSectionFragment = { __typename?: 'BillableMetric', id: string, name: string, code: string, aggregationType: AggregationTypeEnum, flatGroups?: Array<{ __typename?: 'Group', id: string, key?: string | null, value: string }> | null }; +export type BillableMetricForChargeSectionFragment = { __typename?: 'BillableMetric', id: string, name: string, code: string, aggregationType: AggregationTypeEnum, recurring: boolean, flatGroups?: Array<{ __typename?: 'Group', id: string, key?: string | null, value: string }> | null }; -export type GetBillableMetricsQueryVariables = Exact<{ +export type GetMeteredBillableMetricsQueryVariables = Exact<{ page?: InputMaybe; limit?: InputMaybe; searchTerm?: InputMaybe; }>; -export type GetBillableMetricsQuery = { __typename?: 'Query', billableMetrics: { __typename?: 'BillableMetricCollection', collection: Array<{ __typename?: 'BillableMetric', id: string, name: string, code: string, aggregationType: AggregationTypeEnum, flatGroups?: Array<{ __typename?: 'Group', id: string, key?: string | null, value: string }> | null }> } }; +export type GetMeteredBillableMetricsQuery = { __typename?: 'Query', billableMetrics: { __typename?: 'BillableMetricCollection', collection: Array<{ __typename?: 'BillableMetric', id: string, name: string, code: string, aggregationType: AggregationTypeEnum, recurring: boolean, flatGroups?: Array<{ __typename?: 'Group', id: string, key?: string | null, value: string }> | null }> } }; + +export type GetRecurringBillableMetricsQueryVariables = Exact<{ + page?: InputMaybe; + limit?: InputMaybe; + searchTerm?: InputMaybe; +}>; + + +export type GetRecurringBillableMetricsQuery = { __typename?: 'Query', billableMetrics: { __typename?: 'BillableMetricCollection', collection: Array<{ __typename?: 'BillableMetric', id: string, name: string, code: string, aggregationType: AggregationTypeEnum, recurring: boolean, flatGroups?: Array<{ __typename?: 'Group', id: string, key?: string | null, value: string }> | null }> } }; export type DeletePlanDialogFragment = { __typename?: 'Plan', id: string, name: string, draftInvoicesCount: number, activeSubscriptionsCount: number }; @@ -4227,7 +4242,7 @@ export type GetSinglePlanQueryVariables = Exact<{ }>; -export type GetSinglePlanQuery = { __typename?: 'Query', plan?: { __typename?: 'Plan', id: string, name: string, code: string, description?: string | null, interval: PlanInterval, payInAdvance: boolean, amountCents: any, amountCurrency: CurrencyEnum, trialPeriod?: number | null, subscriptionsCount: number, billChargesMonthly?: boolean | null, charges?: Array<{ __typename?: 'Charge', id: string, minAmountCents: any, payInAdvance: boolean, chargeModel: ChargeModelEnum, invoiceable: boolean, billableMetric: { __typename?: 'BillableMetric', id: string, code: string, name: string, aggregationType: AggregationTypeEnum, flatGroups?: Array<{ __typename?: 'Group', id: string, key?: string | null, value: string }> | null }, properties?: { __typename?: 'Properties', amount?: string | null, packageSize?: any | null, freeUnits?: any | null, fixedAmount?: string | null, freeUnitsPerEvents?: any | null, freeUnitsPerTotalAggregation?: string | null, rate?: string | null, graduatedRanges?: Array<{ __typename?: 'GraduatedRange', flatAmount: string, fromValue: any, perUnitAmount: string, toValue?: any | null }> | null, volumeRanges?: Array<{ __typename?: 'VolumeRange', flatAmount: string, fromValue: any, perUnitAmount: string, toValue?: any | null }> | null } | null, groupProperties?: Array<{ __typename?: 'GroupProperties', groupId: string, values: { __typename?: 'Properties', amount?: string | null, packageSize?: any | null, freeUnits?: any | null, fixedAmount?: string | null, freeUnitsPerEvents?: any | null, freeUnitsPerTotalAggregation?: string | null, rate?: string | null, graduatedRanges?: Array<{ __typename?: 'GraduatedRange', flatAmount: string, fromValue: any, perUnitAmount: string, toValue?: any | null }> | null, volumeRanges?: Array<{ __typename?: 'VolumeRange', flatAmount: string, fromValue: any, perUnitAmount: string, toValue?: any | null }> | null } }> | null }> | null } | null }; +export type GetSinglePlanQuery = { __typename?: 'Query', plan?: { __typename?: 'Plan', id: string, name: string, code: string, description?: string | null, interval: PlanInterval, payInAdvance: boolean, amountCents: any, amountCurrency: CurrencyEnum, trialPeriod?: number | null, subscriptionsCount: number, billChargesMonthly?: boolean | null, charges?: Array<{ __typename?: 'Charge', id: string, minAmountCents: any, payInAdvance: boolean, chargeModel: ChargeModelEnum, invoiceable: boolean, prorated: boolean, billableMetric: { __typename?: 'BillableMetric', id: string, code: string, name: string, aggregationType: AggregationTypeEnum, recurring: boolean, flatGroups?: Array<{ __typename?: 'Group', id: string, key?: string | null, value: string }> | null }, properties?: { __typename?: 'Properties', amount?: string | null, packageSize?: any | null, freeUnits?: any | null, fixedAmount?: string | null, freeUnitsPerEvents?: any | null, freeUnitsPerTotalAggregation?: string | null, rate?: string | null, graduatedRanges?: Array<{ __typename?: 'GraduatedRange', flatAmount: string, fromValue: any, perUnitAmount: string, toValue?: any | null }> | null, volumeRanges?: Array<{ __typename?: 'VolumeRange', flatAmount: string, fromValue: any, perUnitAmount: string, toValue?: any | null }> | null } | null, groupProperties?: Array<{ __typename?: 'GroupProperties', groupId: string, values: { __typename?: 'Properties', amount?: string | null, packageSize?: any | null, freeUnits?: any | null, fixedAmount?: string | null, freeUnitsPerEvents?: any | null, freeUnitsPerTotalAggregation?: string | null, rate?: string | null, graduatedRanges?: Array<{ __typename?: 'GraduatedRange', flatAmount: string, fromValue: any, perUnitAmount: string, toValue?: any | null }> | null, volumeRanges?: Array<{ __typename?: 'VolumeRange', flatAmount: string, fromValue: any, perUnitAmount: string, toValue?: any | null }> | null } }> | null }> | null } | null }; export type CreatePlanMutationVariables = Exact<{ input: CreatePlanInput; @@ -4289,7 +4304,7 @@ export type GetSingleBillableMetricQueryVariables = Exact<{ }>; -export type GetSingleBillableMetricQuery = { __typename?: 'Query', billableMetric?: { __typename?: 'BillableMetric', id: string, name: string, code: string, description?: string | null, group?: any | null, aggregationType: AggregationTypeEnum, fieldName?: string | null, subscriptionsCount: number, plansCount: number } | null }; +export type GetSingleBillableMetricQuery = { __typename?: 'Query', billableMetric?: { __typename?: 'BillableMetric', id: string, name: string, code: string, description?: string | null, group?: any | null, aggregationType: AggregationTypeEnum, fieldName?: string | null, subscriptionsCount: number, plansCount: number, recurring: boolean } | null }; export type CreateBillableMetricMutationVariables = Exact<{ input: CreateBillableMetricInput; @@ -4448,7 +4463,7 @@ export type CouponsQueryVariables = Exact<{ export type CouponsQuery = { __typename?: 'Query', coupons: { __typename?: 'CouponCollection', metadata: { __typename?: 'CollectionMetadata', currentPage: number, totalPages: number }, collection: Array<{ __typename?: 'Coupon', id: string, name: string, customerCount: number, status: CouponStatusEnum, amountCurrency?: CurrencyEnum | null, amountCents?: any | null, appliedCouponsCount: number, expiration: CouponExpiration, expirationAt?: any | null, couponType: CouponTypeEnum, percentageRate?: number | null, frequency: CouponFrequency, frequencyDuration?: number | null }> } }; -export type EditBillableMetricFragment = { __typename?: 'BillableMetric', id: string, name: string, code: string, description?: string | null, group?: any | null, aggregationType: AggregationTypeEnum, fieldName?: string | null, subscriptionsCount: number, plansCount: number }; +export type EditBillableMetricFragment = { __typename?: 'BillableMetric', id: string, name: string, code: string, description?: string | null, group?: any | null, aggregationType: AggregationTypeEnum, fieldName?: string | null, subscriptionsCount: number, plansCount: number, recurring: boolean }; export type CreateCreditNoteInvoiceFragment = { __typename?: 'Invoice', id: string, currency?: CurrencyEnum | null, number: string, paymentStatus: InvoicePaymentStatusTypeEnum, creditableAmountCents: any, refundableAmountCents: any, subTotalIncludingTaxesAmountCents: any, couponsAmountCents: any, feesAmountCents: any, versionNumber: number, fees?: Array<{ __typename?: 'Fee', id: string, appliedTaxes?: Array<{ __typename?: 'FeeAppliedTax', id: string, tax: { __typename?: 'Tax', id: string, name: string, rate: number } }> | null }> | null }; @@ -4475,9 +4490,9 @@ export type GetAddonListForInfoiceQueryVariables = Exact<{ export type GetAddonListForInfoiceQuery = { __typename?: 'Query', addOns: { __typename?: 'AddOnCollection', metadata: { __typename?: 'CollectionMetadata', currentPage: number, totalPages: number }, collection: Array<{ __typename?: 'AddOn', id: string, name: string, description?: string | null, amountCents: any, amountCurrency: CurrencyEnum }> } }; -export type BillableMetricForPlanFragment = { __typename?: 'BillableMetric', id: string, name: string, code: string, aggregationType: AggregationTypeEnum, flatGroups?: Array<{ __typename?: 'Group', id: string, key?: string | null, value: string }> | null }; +export type BillableMetricForPlanFragment = { __typename?: 'BillableMetric', id: string, name: string, code: string, aggregationType: AggregationTypeEnum, recurring: boolean, flatGroups?: Array<{ __typename?: 'Group', id: string, key?: string | null, value: string }> | null }; -export type EditPlanFragment = { __typename?: 'Plan', id: string, name: string, code: string, description?: string | null, interval: PlanInterval, payInAdvance: boolean, amountCents: any, amountCurrency: CurrencyEnum, trialPeriod?: number | null, subscriptionsCount: number, billChargesMonthly?: boolean | null, charges?: Array<{ __typename?: 'Charge', id: string, minAmountCents: any, payInAdvance: boolean, chargeModel: ChargeModelEnum, invoiceable: boolean, billableMetric: { __typename?: 'BillableMetric', id: string, code: string, name: string, aggregationType: AggregationTypeEnum, flatGroups?: Array<{ __typename?: 'Group', id: string, key?: string | null, value: string }> | null }, properties?: { __typename?: 'Properties', amount?: string | null, packageSize?: any | null, freeUnits?: any | null, fixedAmount?: string | null, freeUnitsPerEvents?: any | null, freeUnitsPerTotalAggregation?: string | null, rate?: string | null, graduatedRanges?: Array<{ __typename?: 'GraduatedRange', flatAmount: string, fromValue: any, perUnitAmount: string, toValue?: any | null }> | null, volumeRanges?: Array<{ __typename?: 'VolumeRange', flatAmount: string, fromValue: any, perUnitAmount: string, toValue?: any | null }> | null } | null, groupProperties?: Array<{ __typename?: 'GroupProperties', groupId: string, values: { __typename?: 'Properties', amount?: string | null, packageSize?: any | null, freeUnits?: any | null, fixedAmount?: string | null, freeUnitsPerEvents?: any | null, freeUnitsPerTotalAggregation?: string | null, rate?: string | null, graduatedRanges?: Array<{ __typename?: 'GraduatedRange', flatAmount: string, fromValue: any, perUnitAmount: string, toValue?: any | null }> | null, volumeRanges?: Array<{ __typename?: 'VolumeRange', flatAmount: string, fromValue: any, perUnitAmount: string, toValue?: any | null }> | null } }> | null }> | null }; +export type EditPlanFragment = { __typename?: 'Plan', id: string, name: string, code: string, description?: string | null, interval: PlanInterval, payInAdvance: boolean, amountCents: any, amountCurrency: CurrencyEnum, trialPeriod?: number | null, subscriptionsCount: number, billChargesMonthly?: boolean | null, charges?: Array<{ __typename?: 'Charge', id: string, minAmountCents: any, payInAdvance: boolean, chargeModel: ChargeModelEnum, invoiceable: boolean, prorated: boolean, billableMetric: { __typename?: 'BillableMetric', id: string, code: string, name: string, aggregationType: AggregationTypeEnum, recurring: boolean, flatGroups?: Array<{ __typename?: 'Group', id: string, key?: string | null, value: string }> | null }, properties?: { __typename?: 'Properties', amount?: string | null, packageSize?: any | null, freeUnits?: any | null, fixedAmount?: string | null, freeUnitsPerEvents?: any | null, freeUnitsPerTotalAggregation?: string | null, rate?: string | null, graduatedRanges?: Array<{ __typename?: 'GraduatedRange', flatAmount: string, fromValue: any, perUnitAmount: string, toValue?: any | null }> | null, volumeRanges?: Array<{ __typename?: 'VolumeRange', flatAmount: string, fromValue: any, perUnitAmount: string, toValue?: any | null }> | null } | null, groupProperties?: Array<{ __typename?: 'GroupProperties', groupId: string, values: { __typename?: 'Properties', amount?: string | null, packageSize?: any | null, freeUnits?: any | null, fixedAmount?: string | null, freeUnitsPerEvents?: any | null, freeUnitsPerTotalAggregation?: string | null, rate?: string | null, graduatedRanges?: Array<{ __typename?: 'GraduatedRange', flatAmount: string, fromValue: any, perUnitAmount: string, toValue?: any | null }> | null, volumeRanges?: Array<{ __typename?: 'VolumeRange', flatAmount: string, fromValue: any, perUnitAmount: string, toValue?: any | null }> | null } }> | null }> | null }; export type GetCreditNoteQueryVariables = Exact<{ id: Scalars['ID']['input']; @@ -5111,6 +5126,7 @@ export const BillableMetricForChargeSectionFragmentDoc = gql` name code aggregationType + recurring flatGroups { id key @@ -5753,6 +5769,7 @@ export const EditBillableMetricFragmentDoc = gql` fieldName subscriptionsCount plansCount + recurring } `; export const BillableMetricForPlanFragmentDoc = gql` @@ -5761,6 +5778,7 @@ export const BillableMetricForPlanFragmentDoc = gql` name code aggregationType + recurring flatGroups { id key @@ -5868,6 +5886,7 @@ export const ChargeAccordionFragmentDoc = gql` invoiceable minAmountCents payInAdvance + prorated properties { amount } @@ -5882,6 +5901,7 @@ export const ChargeAccordionFragmentDoc = gql` name code aggregationType + recurring flatGroups { id key @@ -7638,9 +7658,59 @@ export function useRetryInvoicePaymentMutation(baseOptions?: Apollo.MutationHook export type RetryInvoicePaymentMutationHookResult = ReturnType; export type RetryInvoicePaymentMutationResult = Apollo.MutationResult; export type RetryInvoicePaymentMutationOptions = Apollo.BaseMutationOptions; -export const GetBillableMetricsDocument = gql` - query getBillableMetrics($page: Int, $limit: Int, $searchTerm: String) { - billableMetrics(page: $page, limit: $limit, searchTerm: $searchTerm) { +export const GetMeteredBillableMetricsDocument = gql` + query getMeteredBillableMetrics($page: Int, $limit: Int, $searchTerm: String) { + billableMetrics( + page: $page + limit: $limit + searchTerm: $searchTerm + recurring: false + ) { + collection { + id + ...billableMetricForChargeSection + } + } +} + ${BillableMetricForChargeSectionFragmentDoc}`; + +/** + * __useGetMeteredBillableMetricsQuery__ + * + * To run a query within a React component, call `useGetMeteredBillableMetricsQuery` and pass it any options that fit your needs. + * When your component renders, `useGetMeteredBillableMetricsQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetMeteredBillableMetricsQuery({ + * variables: { + * page: // value for 'page' + * limit: // value for 'limit' + * searchTerm: // value for 'searchTerm' + * }, + * }); + */ +export function useGetMeteredBillableMetricsQuery(baseOptions?: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(GetMeteredBillableMetricsDocument, options); + } +export function useGetMeteredBillableMetricsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(GetMeteredBillableMetricsDocument, options); + } +export type GetMeteredBillableMetricsQueryHookResult = ReturnType; +export type GetMeteredBillableMetricsLazyQueryHookResult = ReturnType; +export type GetMeteredBillableMetricsQueryResult = Apollo.QueryResult; +export const GetRecurringBillableMetricsDocument = gql` + query getRecurringBillableMetrics($page: Int, $limit: Int, $searchTerm: String) { + billableMetrics( + page: $page + limit: $limit + searchTerm: $searchTerm + recurring: true + ) { collection { id ...billableMetricForChargeSection @@ -7650,16 +7720,16 @@ export const GetBillableMetricsDocument = gql` ${BillableMetricForChargeSectionFragmentDoc}`; /** - * __useGetBillableMetricsQuery__ + * __useGetRecurringBillableMetricsQuery__ * - * To run a query within a React component, call `useGetBillableMetricsQuery` and pass it any options that fit your needs. - * When your component renders, `useGetBillableMetricsQuery` returns an object from Apollo Client that contains loading, error, and data properties + * To run a query within a React component, call `useGetRecurringBillableMetricsQuery` and pass it any options that fit your needs. + * When your component renders, `useGetRecurringBillableMetricsQuery` returns an object from Apollo Client that contains loading, error, and data properties * you can use to render your UI. * * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; * * @example - * const { data, loading, error } = useGetBillableMetricsQuery({ + * const { data, loading, error } = useGetRecurringBillableMetricsQuery({ * variables: { * page: // value for 'page' * limit: // value for 'limit' @@ -7667,17 +7737,17 @@ export const GetBillableMetricsDocument = gql` * }, * }); */ -export function useGetBillableMetricsQuery(baseOptions?: Apollo.QueryHookOptions) { +export function useGetRecurringBillableMetricsQuery(baseOptions?: Apollo.QueryHookOptions) { const options = {...defaultOptions, ...baseOptions} - return Apollo.useQuery(GetBillableMetricsDocument, options); + return Apollo.useQuery(GetRecurringBillableMetricsDocument, options); } -export function useGetBillableMetricsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { +export function useGetRecurringBillableMetricsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { const options = {...defaultOptions, ...baseOptions} - return Apollo.useLazyQuery(GetBillableMetricsDocument, options); + return Apollo.useLazyQuery(GetRecurringBillableMetricsDocument, options); } -export type GetBillableMetricsQueryHookResult = ReturnType; -export type GetBillableMetricsLazyQueryHookResult = ReturnType; -export type GetBillableMetricsQueryResult = Apollo.QueryResult; +export type GetRecurringBillableMetricsQueryHookResult = ReturnType; +export type GetRecurringBillableMetricsLazyQueryHookResult = ReturnType; +export type GetRecurringBillableMetricsQueryResult = Apollo.QueryResult; export const DeletePlanDocument = gql` mutation deletePlan($input: DestroyPlanInput!) { destroyPlan(input: $input) { diff --git a/src/hooks/plans/__tests__/useGraduatedChargeForm.test.tsx b/src/hooks/plans/__tests__/useGraduatedChargeForm.test.tsx index 49b32958f..95b7b0099 100644 --- a/src/hooks/plans/__tests__/useGraduatedChargeForm.test.tsx +++ b/src/hooks/plans/__tests__/useGraduatedChargeForm.test.tsx @@ -43,6 +43,7 @@ const prepare = async ({ id: '1', name: 'graduated', aggregationType: AggregationTypeEnum.CountAgg, + recurring: false, code: 'graduated', flatGroups: propertyType === 'groupProperties' diff --git a/src/hooks/plans/__tests__/useVolumeChargeForm.test.tsx b/src/hooks/plans/__tests__/useVolumeChargeForm.test.tsx index 6015dd344..1e0161db6 100644 --- a/src/hooks/plans/__tests__/useVolumeChargeForm.test.tsx +++ b/src/hooks/plans/__tests__/useVolumeChargeForm.test.tsx @@ -44,6 +44,7 @@ const prepare = async ({ aggregationType: AggregationTypeEnum.CountAgg, name: 'volume', code: 'volume', + recurring: false, flatGroups: propertyType === 'groupProperties' ? [{ id: '1', key: null, value: 'France' }] diff --git a/src/pages/CreateBillableMetric.tsx b/src/pages/CreateBillableMetric.tsx index e3e4ad5b7..4d6a8c74e 100644 --- a/src/pages/CreateBillableMetric.tsx +++ b/src/pages/CreateBillableMetric.tsx @@ -1,7 +1,7 @@ import { useRef, useEffect } from 'react' import { useNavigate } from 'react-router-dom' import { useFormik } from 'formik' -import { object, string } from 'yup' +import { bool, object, string } from 'yup' import styled from 'styled-components' import { gql } from '@apollo/client' @@ -9,7 +9,12 @@ import { AggregationTypeEnum, CreateBillableMetricInput } from '~/generated/grap import { PageHeader, theme, Card } from '~/styles' import { Typography, Button, Skeleton, Accordion, Alert } from '~/components/designSystem' import { useInternationalization } from '~/hooks/core/useInternationalization' -import { TextInputField, ComboBoxField, JsonEditorField } from '~/components/form' +import { + TextInputField, + ComboBoxField, + JsonEditorField, + ButtonSelectorField, +} from '~/components/form' import { BILLABLE_METRICS_ROUTE } from '~/core/router' import { WarningDialog, WarningDialogRef } from '~/components/WarningDialog' import { useCreateEditBillableMetric } from '~/hooks/useCreateEditBillableMetric' @@ -37,14 +42,10 @@ gql` fieldName subscriptionsCount plansCount + recurring } ` -enum AGGREGATION_GROUP_ENUM { - Metered = 'metered', - Persistent = 'persistent', -} - const CreateBillableMetric = () => { const { translate } = useInternationalization() const { isEdition, loading, billableMetric, errorCode, onSave } = useCreateEditBillableMetric() @@ -59,6 +60,7 @@ const CreateBillableMetric = () => { // @ts-ignore aggregationType: billableMetric?.aggregationType || '', fieldName: billableMetric?.fieldName || undefined, + recurring: billableMetric?.recurring || false, }, validationSchema: object().shape({ name: string().required(''), @@ -69,6 +71,7 @@ const CreateBillableMetric = () => { !!aggregationType && aggregationType !== AggregationTypeEnum.CountAgg, then: (schema) => schema.required(''), }), + recurring: bool().required(''), }), enableReinitialize: true, validateOnMount: true, @@ -86,6 +89,18 @@ const CreateBillableMetric = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [formikProps.values.aggregationType, formikProps.values.fieldName]) + useEffect(() => { + if ( + ![AggregationTypeEnum.SumAgg, AggregationTypeEnum.UniqueCountAgg].includes( + formikProps.values.aggregationType + ) + ) { + formikProps.setFieldValue('recurring', false) + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [formikProps.values.aggregationType]) + useEffect(() => { if (errorCode === FORM_ERRORS_ENUM.existingCode) { formikProps.setFieldError('code', 'text_632a2d437e341dcc76817556') @@ -207,6 +222,7 @@ const CreateBillableMetric = () => { { { label: translate('text_623c4a8c599213014cacc9de'), value: AggregationTypeEnum.CountAgg, - group: AGGREGATION_GROUP_ENUM.Metered, }, { label: translate('text_62694d9181be8d00a33f20f0'), value: AggregationTypeEnum.UniqueCountAgg, - group: AGGREGATION_GROUP_ENUM.Metered, }, { label: translate('text_62694d9181be8d00a33f20f8'), value: AggregationTypeEnum.MaxAgg, - group: AGGREGATION_GROUP_ENUM.Metered, }, { label: translate('text_62694d9181be8d00a33f2100'), value: AggregationTypeEnum.SumAgg, - group: AGGREGATION_GROUP_ENUM.Metered, - }, - - { - label: translate('text_63105dbdd88c7432a3b255eb'), - value: AggregationTypeEnum.RecurringCountAgg, - group: AGGREGATION_GROUP_ENUM.Persistent, }, ]} helperText={ @@ -252,35 +258,6 @@ const CreateBillableMetric = () => { ? translate('text_62694d9181be8d00a33f20ec') : undefined } - renderGroupHeader={{ - [AGGREGATION_GROUP_ENUM.Metered]: ( - - - {translate('text_6310755befed49627644222b')} - - - {translate('text_6310755befed49627644222d')} - - - ), - [AGGREGATION_GROUP_ENUM.Persistent]: ( - - - {translate('text_6310755befed49627644222f')} - - - {translate('text_6310755befed496276442231')} - - - ), - }} - renderGroupInputStartAdornment={{ - [AGGREGATION_GROUP_ENUM.Metered]: translate('text_6310755befed49627644222b'), - [AGGREGATION_GROUP_ENUM.Persistent]: translate( - 'text_6310755befed49627644222f' - ), - }} - formikProps={formikProps} /> {formikProps.values?.aggregationType && @@ -293,6 +270,37 @@ const CreateBillableMetric = () => { formikProps={formikProps} /> )} + + {!![AggregationTypeEnum.SumAgg, AggregationTypeEnum.UniqueCountAgg].includes( + formikProps.values.aggregationType + ) ? ( + <> + + + + ) : !!formikProps.values.aggregationType ? ( + {translate('text_648c2be974f70300748a4ca6')} + ) : null} { ) } -const ComboboxHeader = styled.div` - display: flex; - min-width: 0; - - > * { - white-space: nowrap; - - &:first-child { - margin-right: ${theme.spacing(1)}; - } - &:last-child { - min-width: 0; - } - } -` - const GroupAlert = styled(Alert)` margin-top: ${theme.spacing(6)}; ` diff --git a/src/pages/CreatePlan.tsx b/src/pages/CreatePlan.tsx index 6f39f743d..c48f1f799 100644 --- a/src/pages/CreatePlan.tsx +++ b/src/pages/CreatePlan.tsx @@ -42,6 +42,7 @@ gql` name code aggregationType + recurring flatGroups { id key @@ -169,6 +170,8 @@ const CreatePlan = () => { }) const canBeEdited = !plan?.subscriptionsCount + const hasAnyMeteredCharge = formikProps.values.charges.some((c) => !c.billableMetric.recurring) + const hasAnyRecurringCharge = formikProps.values.charges.some((c) => !!c.billableMetric.recurring) useEffect(() => { if (errorCode === FORM_ERRORS_ENUM.existingCode) { @@ -291,11 +294,13 @@ const CreatePlan = () => { />