mirror of
https://github.com/gitpod-io/gitpod.git
synced 2025-12-08 17:36:30 +00:00
[admin] allow usage adjustments
This commit is contained in:
parent
2fef214976
commit
e044c1d49f
@ -15,14 +15,22 @@ import Label from "./Label";
|
|||||||
import Property from "./Property";
|
import Property from "./Property";
|
||||||
import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution";
|
import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution";
|
||||||
import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode";
|
import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode";
|
||||||
|
import { CostCenterJSON, CostCenter_BillingStrategy } from "@gitpod/gitpod-protocol/lib/usage";
|
||||||
|
import Modal from "../components/Modal";
|
||||||
|
|
||||||
export default function TeamDetail(props: { team: Team }) {
|
export default function TeamDetail(props: { team: Team }) {
|
||||||
const { team } = props;
|
const { team } = props;
|
||||||
const [teamMembers, setTeamMembers] = useState<TeamMemberInfo[] | undefined>(undefined);
|
const [teamMembers, setTeamMembers] = useState<TeamMemberInfo[] | undefined>(undefined);
|
||||||
const [billingMode, setBillingMode] = useState<BillingMode | undefined>(undefined);
|
const [billingMode, setBillingMode] = useState<BillingMode | undefined>(undefined);
|
||||||
const [searchText, setSearchText] = useState<string>("");
|
const [searchText, setSearchText] = useState<string>("");
|
||||||
|
const [costCenter, setCostCenter] = useState<CostCenterJSON>();
|
||||||
|
const [usageBalance, setUsageBalance] = useState<number>(0);
|
||||||
|
const [usageLimit, setUsageLimit] = useState<number>();
|
||||||
|
const [editSpendingLimit, setEditSpendingLimit] = useState<boolean>(false);
|
||||||
|
const [creditNote, setCreditNote] = useState<{ credits: number; note?: string }>({ credits: 0 });
|
||||||
|
const [editAddCreditNote, setEditAddCreditNote] = useState<boolean>(false);
|
||||||
|
|
||||||
useEffect(() => {
|
const initialize = () => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const members = await getGitpodService().server.adminGetTeamMembers(team.id);
|
const members = await getGitpodService().server.adminGetTeamMembers(team.id);
|
||||||
if (members.length > 0) {
|
if (members.length > 0) {
|
||||||
@ -30,9 +38,22 @@ export default function TeamDetail(props: { team: Team }) {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
getGitpodService()
|
getGitpodService()
|
||||||
.server.adminGetBillingMode(AttributionId.render({ kind: "team", teamId: props.team.id }))
|
.server.adminGetBillingMode(AttributionId.render({ kind: "team", teamId: team.id }))
|
||||||
.then((bm) => setBillingMode(bm));
|
.then((bm) => setBillingMode(bm));
|
||||||
}, [team]);
|
const attributionId = AttributionId.render(AttributionId.create(team));
|
||||||
|
getGitpodService().server.adminGetBillingMode(attributionId).then(setBillingMode);
|
||||||
|
getGitpodService().server.adminGetCostCenter(attributionId).then(setCostCenter);
|
||||||
|
getGitpodService().server.adminGetUsageBalance(attributionId).then(setUsageBalance);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(initialize, [team]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!costCenter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setUsageLimit(costCenter.spendingLimit);
|
||||||
|
}, [costCenter]);
|
||||||
|
|
||||||
const filteredMembers = teamMembers?.filter((m) => {
|
const filteredMembers = teamMembers?.filter((m) => {
|
||||||
const memberSearchText = `${m.fullName || ""}${m.primaryEmail || ""}`.toLocaleLowerCase();
|
const memberSearchText = `${m.fullName || ""}${m.primaryEmail || ""}`.toLocaleLowerCase();
|
||||||
@ -64,8 +85,53 @@ export default function TeamDetail(props: { team: Team }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex mt-6">
|
<div className="flex mt-6">
|
||||||
{!team.markedDeleted && teamMembers && <Property name="Members">{teamMembers.length}</Property>}
|
{!team.markedDeleted && <Property name="Members">{teamMembers?.length || "?"}</Property>}
|
||||||
{!team.markedDeleted && <Property name="Billing Mode">{billingMode?.mode || "---"}</Property>}
|
{!team.markedDeleted && <Property name="Billing Mode">{billingMode?.mode || "---"}</Property>}
|
||||||
|
{costCenter && (
|
||||||
|
<Property name="Stripe Subscription" actions={[]}>
|
||||||
|
<span>
|
||||||
|
{costCenter?.billingStrategy === CostCenter_BillingStrategy.BILLING_STRATEGY_STRIPE
|
||||||
|
? "Active"
|
||||||
|
: "Inactive"}
|
||||||
|
</span>
|
||||||
|
</Property>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex mt-6">
|
||||||
|
{costCenter && (
|
||||||
|
<Property name="Current Cycle" actions={[]}>
|
||||||
|
<span>
|
||||||
|
{dayjs(costCenter?.billingCycleStart).format("MMM D")} -{" "}
|
||||||
|
{dayjs(costCenter?.nextBillingTime).format("MMM D")}
|
||||||
|
</span>
|
||||||
|
</Property>
|
||||||
|
)}
|
||||||
|
{costCenter && (
|
||||||
|
<Property
|
||||||
|
name="Available Credits"
|
||||||
|
actions={[
|
||||||
|
{
|
||||||
|
label: "Add Credits",
|
||||||
|
onClick: () => setEditAddCreditNote(true),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<span>{usageBalance * -1 + (costCenter?.spendingLimit || 0)} Credits</span>
|
||||||
|
</Property>
|
||||||
|
)}
|
||||||
|
{costCenter && (
|
||||||
|
<Property
|
||||||
|
name="Usage Limit"
|
||||||
|
actions={[
|
||||||
|
{
|
||||||
|
label: "Change Usage Limit",
|
||||||
|
onClick: () => setEditSpendingLimit(true),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<span>{costCenter?.spendingLimit} Credits</span>
|
||||||
|
</Property>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex mt-4">
|
<div className="flex mt-4">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
@ -151,6 +217,91 @@ export default function TeamDetail(props: { team: Team }) {
|
|||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
</ItemsList>
|
</ItemsList>
|
||||||
|
<Modal
|
||||||
|
visible={editSpendingLimit}
|
||||||
|
onClose={() => setEditSpendingLimit(false)}
|
||||||
|
title="Change Usage Limit"
|
||||||
|
onEnter={() => false}
|
||||||
|
buttons={[
|
||||||
|
<button
|
||||||
|
disabled={usageLimit === costCenter?.spendingLimit}
|
||||||
|
onClick={async () => {
|
||||||
|
if (usageLimit !== undefined) {
|
||||||
|
await getGitpodService().server.adminSetUsageLimit(
|
||||||
|
AttributionId.render(AttributionId.create(team)),
|
||||||
|
usageLimit || 0,
|
||||||
|
);
|
||||||
|
setUsageLimit(undefined);
|
||||||
|
initialize();
|
||||||
|
setEditSpendingLimit(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Change
|
||||||
|
</button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<p className="pb-4 text-gray-500 text-base">Change the usage limit in credits per month.</p>
|
||||||
|
<label>Credits</label>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
className="w-full"
|
||||||
|
min={Math.max(usageBalance, 0)}
|
||||||
|
max={500000}
|
||||||
|
title="Change Usage Limit"
|
||||||
|
value={usageLimit}
|
||||||
|
onChange={(event) => setUsageLimit(Number.parseInt(event.target.value))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
<Modal
|
||||||
|
onEnter={() => false}
|
||||||
|
visible={editAddCreditNote}
|
||||||
|
onClose={() => setEditAddCreditNote(false)}
|
||||||
|
title="Add Credits"
|
||||||
|
buttons={[
|
||||||
|
<button
|
||||||
|
disabled={creditNote.credits === 0 || !creditNote.note}
|
||||||
|
onClick={async () => {
|
||||||
|
if (creditNote.credits !== 0 && !!creditNote.note) {
|
||||||
|
await getGitpodService().server.adminAddUsageCreditNote(
|
||||||
|
AttributionId.render(AttributionId.create(team)),
|
||||||
|
creditNote.credits,
|
||||||
|
creditNote.note,
|
||||||
|
);
|
||||||
|
setEditAddCreditNote(false);
|
||||||
|
setCreditNote({ credits: 0 });
|
||||||
|
initialize();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add Credits
|
||||||
|
</button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<p>Adds or subtracts the amount of credits from this account.</p>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<label className="mt-4">Credits</label>
|
||||||
|
<input
|
||||||
|
className="w-full"
|
||||||
|
type="number"
|
||||||
|
min={-50000}
|
||||||
|
max={50000}
|
||||||
|
title="Credits"
|
||||||
|
value={creditNote.credits}
|
||||||
|
onChange={(event) =>
|
||||||
|
setCreditNote({ credits: Number.parseInt(event.target.value), note: creditNote.note })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label className="mt-4">Note</label>
|
||||||
|
<textarea
|
||||||
|
className="w-full"
|
||||||
|
title="Note"
|
||||||
|
onChange={(event) => setCreditNote({ credits: creditNote.credits, note: event.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,12 +26,19 @@ import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode";
|
|||||||
import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution";
|
import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution";
|
||||||
import CaretDown from "../icons/CaretDown.svg";
|
import CaretDown from "../icons/CaretDown.svg";
|
||||||
import ContextMenu from "../components/ContextMenu";
|
import ContextMenu from "../components/ContextMenu";
|
||||||
|
import { CostCenterJSON, CostCenter_BillingStrategy } from "@gitpod/gitpod-protocol/lib/usage";
|
||||||
|
|
||||||
export default function UserDetail(p: { user: User }) {
|
export default function UserDetail(p: { user: User }) {
|
||||||
const [activity, setActivity] = useState(false);
|
const [activity, setActivity] = useState(false);
|
||||||
const [user, setUser] = useState(p.user);
|
const [user, setUser] = useState(p.user);
|
||||||
const [billingMode, setBillingMode] = useState<BillingMode | undefined>(undefined);
|
const [billingMode, setBillingMode] = useState<BillingMode | undefined>(undefined);
|
||||||
const [accountStatement, setAccountStatement] = useState<AccountStatement>();
|
const [accountStatement, setAccountStatement] = useState<AccountStatement>();
|
||||||
|
const [costCenter, setCostCenter] = useState<CostCenterJSON>();
|
||||||
|
const [usageBalance, setUsageBalance] = useState<number>(0);
|
||||||
|
const [usageLimit, setUsageLimit] = useState<number>();
|
||||||
|
const [editSpendingLimit, setEditSpendingLimit] = useState<boolean>(false);
|
||||||
|
const [creditNote, setCreditNote] = useState<{ credits: number; note?: string }>({ credits: 0 });
|
||||||
|
const [editAddCreditNote, setEditAddCreditNote] = useState<boolean>(false);
|
||||||
const [isStudent, setIsStudent] = useState<boolean>();
|
const [isStudent, setIsStudent] = useState<boolean>();
|
||||||
const [editFeatureFlags, setEditFeatureFlags] = useState(false);
|
const [editFeatureFlags, setEditFeatureFlags] = useState(false);
|
||||||
const [editRoles, setEditRoles] = useState(false);
|
const [editRoles, setEditRoles] = useState(false);
|
||||||
@ -40,21 +47,28 @@ export default function UserDetail(p: { user: User }) {
|
|||||||
const isProfessionalOpenSource =
|
const isProfessionalOpenSource =
|
||||||
accountStatement && accountStatement.subscriptions.some((s) => s.planId === Plans.FREE_OPEN_SOURCE.chargebeeId);
|
accountStatement && accountStatement.subscriptions.some((s) => s.planId === Plans.FREE_OPEN_SOURCE.chargebeeId);
|
||||||
|
|
||||||
useEffect(() => {
|
const initialize = () => {
|
||||||
setUser(p.user);
|
setUser(user);
|
||||||
getGitpodService()
|
getGitpodService()
|
||||||
.server.adminGetAccountStatement(p.user.id)
|
.server.adminGetAccountStatement(user.id)
|
||||||
.then((as) => setAccountStatement(as))
|
.then(setAccountStatement)
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
});
|
});
|
||||||
getGitpodService()
|
getGitpodService().server.adminIsStudent(user.id).then(setIsStudent);
|
||||||
.server.adminIsStudent(p.user.id)
|
const attributionId = AttributionId.render(AttributionId.create(user));
|
||||||
.then((isStud) => setIsStudent(isStud));
|
getGitpodService().server.adminGetBillingMode(attributionId).then(setBillingMode);
|
||||||
getGitpodService()
|
getGitpodService().server.adminGetCostCenter(attributionId).then(setCostCenter);
|
||||||
.server.adminGetBillingMode(AttributionId.render({ kind: "user", userId: p.user.id }))
|
getGitpodService().server.adminGetUsageBalance(attributionId).then(setUsageBalance);
|
||||||
.then((bm) => setBillingMode(bm));
|
};
|
||||||
}, [p.user]);
|
useEffect(initialize, [user]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!costCenter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setUsageLimit(costCenter.spendingLimit);
|
||||||
|
}, [costCenter, user]);
|
||||||
|
|
||||||
const email = User.getPrimaryEmail(p.user);
|
const email = User.getPrimaryEmail(p.user);
|
||||||
const emailDomain = email ? email.split("@")[email.split("@").length - 1] : undefined;
|
const emailDomain = email ? email.split("@")[email.split("@").length - 1] : undefined;
|
||||||
@ -208,7 +222,47 @@ export default function UserDetail(p: { user: User }) {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "usage-based":
|
case "usage-based":
|
||||||
// TODO(gpl) Add info about Stripe plan, etc.
|
properties.push(
|
||||||
|
<Property name="Stripe Subscription" actions={[]}>
|
||||||
|
<span>
|
||||||
|
{costCenter?.billingStrategy === CostCenter_BillingStrategy.BILLING_STRATEGY_STRIPE
|
||||||
|
? "Active"
|
||||||
|
: "Inactive"}
|
||||||
|
</span>
|
||||||
|
</Property>,
|
||||||
|
);
|
||||||
|
properties.push(
|
||||||
|
<Property name="Current Cycle" actions={[]}>
|
||||||
|
<span>
|
||||||
|
{dayjs(costCenter?.billingCycleStart).format("MMM D")} -{" "}
|
||||||
|
{dayjs(costCenter?.nextBillingTime).format("MMM D")}
|
||||||
|
</span>
|
||||||
|
</Property>,
|
||||||
|
);
|
||||||
|
properties.push(
|
||||||
|
<Property
|
||||||
|
name="Available Credits"
|
||||||
|
actions={[
|
||||||
|
{
|
||||||
|
label: "Add Credits",
|
||||||
|
onClick: () => setEditAddCreditNote(true),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<span>{usageBalance * -1 + (costCenter?.spendingLimit || 0)} Credits</span>
|
||||||
|
</Property>,
|
||||||
|
<Property
|
||||||
|
name="Usage Limit"
|
||||||
|
actions={[
|
||||||
|
{
|
||||||
|
label: "Change Usage Limit",
|
||||||
|
onClick: () => setEditSpendingLimit(true),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<span>{costCenter?.spendingLimit} Credits</span>
|
||||||
|
</Property>,
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@ -290,8 +344,91 @@ export default function UserDetail(p: { user: User }) {
|
|||||||
{renderUserBillingProperties()}
|
{renderUserBillingProperties()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<WorkspaceSearch user={user} />
|
<WorkspaceSearch user={user} />
|
||||||
</PageWithAdminSubMenu>
|
</PageWithAdminSubMenu>
|
||||||
|
<Modal
|
||||||
|
visible={editSpendingLimit}
|
||||||
|
onClose={() => setEditSpendingLimit(false)}
|
||||||
|
title="Change Usage Limit"
|
||||||
|
onEnter={() => false}
|
||||||
|
buttons={[
|
||||||
|
<button
|
||||||
|
disabled={usageLimit === costCenter?.spendingLimit}
|
||||||
|
onClick={async () => {
|
||||||
|
if (usageLimit !== undefined) {
|
||||||
|
await getGitpodService().server.adminSetUsageLimit(
|
||||||
|
AttributionId.render(AttributionId.create(user)),
|
||||||
|
usageLimit || 0,
|
||||||
|
);
|
||||||
|
setUsageLimit(undefined);
|
||||||
|
initialize();
|
||||||
|
setEditSpendingLimit(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Change
|
||||||
|
</button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<p className="pb-4 text-gray-500 text-base">Change the usage limit in credits per month.</p>
|
||||||
|
<label>Credits</label>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min={Math.max(usageBalance, 0)}
|
||||||
|
max={500000}
|
||||||
|
title="Change Usage Limit"
|
||||||
|
value={usageLimit}
|
||||||
|
onChange={(event) => setUsageLimit(Number.parseInt(event.target.value))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
<Modal
|
||||||
|
onEnter={() => false}
|
||||||
|
visible={editAddCreditNote}
|
||||||
|
onClose={() => setEditAddCreditNote(false)}
|
||||||
|
title="Add Credits"
|
||||||
|
buttons={[
|
||||||
|
<button
|
||||||
|
disabled={creditNote.credits === 0 || !creditNote.note}
|
||||||
|
onClick={async () => {
|
||||||
|
if (creditNote.credits !== 0 && !!creditNote.note) {
|
||||||
|
await getGitpodService().server.adminAddUsageCreditNote(
|
||||||
|
AttributionId.render(AttributionId.create(user)),
|
||||||
|
creditNote.credits,
|
||||||
|
creditNote.note,
|
||||||
|
);
|
||||||
|
setEditAddCreditNote(false);
|
||||||
|
setCreditNote({ credits: 0 });
|
||||||
|
initialize();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add Credits
|
||||||
|
</button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<p>Adds or subtracts the amount of credits from this account.</p>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<label className="mt-4">Credits</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min={-50000}
|
||||||
|
max={50000}
|
||||||
|
title="Credits"
|
||||||
|
value={creditNote.credits}
|
||||||
|
onChange={(event) =>
|
||||||
|
setCreditNote({ credits: Number.parseInt(event.target.value), note: creditNote.note })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label className="mt-4">Note</label>
|
||||||
|
<textarea
|
||||||
|
title="Note"
|
||||||
|
onChange={(event) => setCreditNote({ credits: creditNote.credits, note: event.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
<Modal
|
<Modal
|
||||||
visible={editFeatureFlags}
|
visible={editFeatureFlags}
|
||||||
onClose={() => setEditFeatureFlags(false)}
|
onClose={() => setEditFeatureFlags(false)}
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import { Link } from "react-router-dom";
|
|||||||
import { Appearance, loadStripe, Stripe } from "@stripe/stripe-js";
|
import { Appearance, loadStripe, Stripe } from "@stripe/stripe-js";
|
||||||
import { Elements, PaymentElement, useElements, useStripe } from "@stripe/react-stripe-js";
|
import { Elements, PaymentElement, useElements, useStripe } from "@stripe/react-stripe-js";
|
||||||
import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution";
|
import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution";
|
||||||
import { Ordering } from "@gitpod/gitpod-protocol/lib/usage";
|
|
||||||
import { ReactComponent as Spinner } from "../icons/Spinner.svg";
|
import { ReactComponent as Spinner } from "../icons/Spinner.svg";
|
||||||
import { ReactComponent as Check } from "../images/check-circle.svg";
|
import { ReactComponent as Check } from "../images/check-circle.svg";
|
||||||
import { ThemeContext } from "../theme-context";
|
import { ThemeContext } from "../theme-context";
|
||||||
@ -37,8 +36,8 @@ export default function UsageBasedBillingConfig({ attributionId }: Props) {
|
|||||||
const [showBillingSetupModal, setShowBillingSetupModal] = useState<boolean>(false);
|
const [showBillingSetupModal, setShowBillingSetupModal] = useState<boolean>(false);
|
||||||
const [stripeSubscriptionId, setStripeSubscriptionId] = useState<string | undefined>();
|
const [stripeSubscriptionId, setStripeSubscriptionId] = useState<string | undefined>();
|
||||||
const [isLoadingStripeSubscription, setIsLoadingStripeSubscription] = useState<boolean>(true);
|
const [isLoadingStripeSubscription, setIsLoadingStripeSubscription] = useState<boolean>(true);
|
||||||
const [currentUsage, setCurrentUsage] = useState<number | undefined>();
|
const [currentUsage, setCurrentUsage] = useState<number>(0);
|
||||||
const [usageLimit, setUsageLimit] = useState<number | undefined>();
|
const [usageLimit, setUsageLimit] = useState<number>(0);
|
||||||
const [stripePortalUrl, setStripePortalUrl] = useState<string | undefined>();
|
const [stripePortalUrl, setStripePortalUrl] = useState<string | undefined>();
|
||||||
const [pollStripeSubscriptionTimeout, setPollStripeSubscriptionTimeout] = useState<NodeJS.Timeout | undefined>();
|
const [pollStripeSubscriptionTimeout, setPollStripeSubscriptionTimeout] = useState<NodeJS.Timeout | undefined>();
|
||||||
const [pendingStripeSubscription, setPendingStripeSubscription] = useState<PendingStripeSubscription | undefined>();
|
const [pendingStripeSubscription, setPendingStripeSubscription] = useState<PendingStripeSubscription | undefined>();
|
||||||
@ -55,7 +54,7 @@ export default function UsageBasedBillingConfig({ attributionId }: Props) {
|
|||||||
try {
|
try {
|
||||||
getGitpodService().server.findStripeSubscriptionId(attributionId).then(setStripeSubscriptionId);
|
getGitpodService().server.findStripeSubscriptionId(attributionId).then(setStripeSubscriptionId);
|
||||||
const costCenter = await getGitpodService().server.getCostCenter(attributionId);
|
const costCenter = await getGitpodService().server.getCostCenter(attributionId);
|
||||||
setUsageLimit(costCenter?.spendingLimit);
|
setUsageLimit(costCenter?.spendingLimit || 0);
|
||||||
setBillingCycleFrom(dayjs(costCenter?.billingCycleStart || now.startOf("month")).utc(true));
|
setBillingCycleFrom(dayjs(costCenter?.billingCycleStart || now.startOf("month")).utc(true));
|
||||||
setBillingCycleTo(dayjs(costCenter?.nextBillingTime || now.endOf("month")).utc(true));
|
setBillingCycleTo(dayjs(costCenter?.nextBillingTime || now.endOf("month")).utc(true));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -175,15 +174,7 @@ export default function UsageBasedBillingConfig({ attributionId }: Props) {
|
|||||||
if (!attributionId) {
|
if (!attributionId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
(async () => {
|
getGitpodService().server.getUsageBalance(attributionId).then(setCurrentUsage);
|
||||||
const response = await getGitpodService().server.listUsage({
|
|
||||||
attributionId,
|
|
||||||
order: Ordering.ORDERING_DESCENDING,
|
|
||||||
from: billingCycleFrom.toDate().getTime(),
|
|
||||||
to: Date.now(),
|
|
||||||
});
|
|
||||||
setCurrentUsage(response.creditsUsed);
|
|
||||||
})();
|
|
||||||
}, [attributionId, billingCycleFrom]);
|
}, [attributionId, billingCycleFrom]);
|
||||||
|
|
||||||
const showSpinner = !attributionId || isLoadingStripeSubscription || !!pendingStripeSubscription;
|
const showSpinner = !attributionId || isLoadingStripeSubscription || !!pendingStripeSubscription;
|
||||||
@ -208,6 +199,10 @@ export default function UsageBasedBillingConfig({ attributionId }: Props) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const used = currentUsage >= 0 ? currentUsage : 0;
|
||||||
|
const totalAvailable = usageLimit + (currentUsage < 0 ? currentUsage * -1 : 0);
|
||||||
|
const percentage = Math.min(Math.max(Math.round((100 * used) / totalAvailable), 0), 100);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-16">
|
<div className="mb-16">
|
||||||
<h2 className="text-gray-500">Manage usage-based billing, usage limit, and payment method.</h2>
|
<h2 className="text-gray-500">Manage usage-based billing, usage limit, and payment method.</h2>
|
||||||
@ -227,13 +222,8 @@ export default function UsageBasedBillingConfig({ attributionId }: Props) {
|
|||||||
<div className="flex flex-col mt-4 p-4 rounded-xl bg-gray-50 dark:bg-gray-800">
|
<div className="flex flex-col mt-4 p-4 rounded-xl bg-gray-50 dark:bg-gray-800">
|
||||||
<div className="uppercase text-sm text-gray-400 dark:text-gray-500">Balance Used</div>
|
<div className="uppercase text-sm text-gray-400 dark:text-gray-500">Balance Used</div>
|
||||||
<div className="mt-1 text-xl font-semibold flex-grow">
|
<div className="mt-1 text-xl font-semibold flex-grow">
|
||||||
<span className="text-gray-900 dark:text-gray-100">
|
<span className="text-gray-900 dark:text-gray-100">{used}</span>
|
||||||
{typeof currentUsage === "number" ? Math.round(currentUsage) : "?"}
|
<span className="text-gray-400 dark:text-gray-500"> / {totalAvailable} Credits</span>
|
||||||
</span>
|
|
||||||
<span className="text-gray-400 dark:text-gray-500">
|
|
||||||
{" "}
|
|
||||||
/ {usageLimit} Credit{usageLimit === 1 ? "" : "s"}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 text-sm flex">
|
<div className="mt-4 text-sm flex">
|
||||||
<span className="flex-grow">
|
<span className="flex-grow">
|
||||||
@ -244,9 +234,7 @@ export default function UsageBasedBillingConfig({ attributionId }: Props) {
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
{typeof currentUsage === "number" && typeof usageLimit === "number" && usageLimit > 0 && (
|
{typeof currentUsage === "number" && typeof usageLimit === "number" && usageLimit > 0 && (
|
||||||
<span className="text-gray-400 dark:text-gray-500">
|
<span className="text-gray-400 dark:text-gray-500">{percentage}% used</span>
|
||||||
{Math.round((100 * currentUsage) / usageLimit)}% used
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 flex">
|
<div className="mt-2 flex">
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { RoleOrPermission } from "./permission";
|
|||||||
import { AccountStatement } from "./accounting-protocol";
|
import { AccountStatement } from "./accounting-protocol";
|
||||||
import { InstallationAdminSettings } from "./installation-admin-protocol";
|
import { InstallationAdminSettings } from "./installation-admin-protocol";
|
||||||
import { BillingMode } from "./billing-mode";
|
import { BillingMode } from "./billing-mode";
|
||||||
|
import { CostCenterJSON, ListUsageRequest, ListUsageResponse } from "./usage";
|
||||||
|
|
||||||
export interface AdminServer {
|
export interface AdminServer {
|
||||||
adminGetUsers(req: AdminGetListRequest<User>): Promise<AdminGetListResult<User>>;
|
adminGetUsers(req: AdminGetListRequest<User>): Promise<AdminGetListResult<User>>;
|
||||||
@ -54,6 +55,13 @@ export interface AdminServer {
|
|||||||
|
|
||||||
adminGetSettings(): Promise<InstallationAdminSettings>;
|
adminGetSettings(): Promise<InstallationAdminSettings>;
|
||||||
adminUpdateSettings(settings: InstallationAdminSettings): Promise<void>;
|
adminUpdateSettings(settings: InstallationAdminSettings): Promise<void>;
|
||||||
|
|
||||||
|
adminGetCostCenter(attributionId: string): Promise<CostCenterJSON | undefined>;
|
||||||
|
adminSetUsageLimit(attributionId: string, usageLimit: number): Promise<void>;
|
||||||
|
|
||||||
|
adminListUsage(req: ListUsageRequest): Promise<ListUsageResponse>;
|
||||||
|
adminAddUsageCreditNote(attributionId: string, credits: number, note: string): Promise<void>;
|
||||||
|
adminGetUsageBalance(attributionId: string): Promise<number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdminGetListRequest<T> {
|
export interface AdminGetListRequest<T> {
|
||||||
|
|||||||
@ -282,6 +282,7 @@ export interface GitpodServer extends JsonRpcServer<GitpodClient>, AdminServer,
|
|||||||
getStripePortalUrl(attributionId: string): Promise<string>;
|
getStripePortalUrl(attributionId: string): Promise<string>;
|
||||||
getCostCenter(attributionId: string): Promise<CostCenterJSON | undefined>;
|
getCostCenter(attributionId: string): Promise<CostCenterJSON | undefined>;
|
||||||
setUsageLimit(attributionId: string, usageLimit: number): Promise<void>;
|
setUsageLimit(attributionId: string, usageLimit: number): Promise<void>;
|
||||||
|
getUsageBalance(attributionId: string): Promise<number>;
|
||||||
|
|
||||||
listUsage(req: ListUsageRequest): Promise<ListUsageResponse>;
|
listUsage(req: ListUsageRequest): Promise<ListUsageResponse>;
|
||||||
|
|
||||||
|
|||||||
@ -71,8 +71,9 @@ import { BlockedRepository } from "@gitpod/gitpod-protocol/lib/blocked-repositor
|
|||||||
import { EligibilityService } from "../user/eligibility-service";
|
import { EligibilityService } from "../user/eligibility-service";
|
||||||
import { AccountStatementProvider } from "../user/account-statement-provider";
|
import { AccountStatementProvider } from "../user/account-statement-provider";
|
||||||
import { GithubUpgradeURL, PlanCoupon } from "@gitpod/gitpod-protocol/lib/payment-protocol";
|
import { GithubUpgradeURL, PlanCoupon } from "@gitpod/gitpod-protocol/lib/payment-protocol";
|
||||||
import { ListUsageRequest, ListUsageResponse } from "@gitpod/gitpod-protocol/lib/usage";
|
import { CostCenterJSON, ListUsageRequest, ListUsageResponse } from "@gitpod/gitpod-protocol/lib/usage";
|
||||||
import {
|
import {
|
||||||
|
CostCenter,
|
||||||
CostCenter_BillingStrategy,
|
CostCenter_BillingStrategy,
|
||||||
ListUsageRequest_Ordering,
|
ListUsageRequest_Ordering,
|
||||||
UsageServiceClient,
|
UsageServiceClient,
|
||||||
@ -122,7 +123,6 @@ import {
|
|||||||
} from "@gitpod/usage-api/lib/usage/v1/billing.pb";
|
} from "@gitpod/usage-api/lib/usage/v1/billing.pb";
|
||||||
import { IncrementalPrebuildsService } from "../prebuilds/incremental-prebuilds-service";
|
import { IncrementalPrebuildsService } from "../prebuilds/incremental-prebuilds-service";
|
||||||
import { ConfigProvider } from "../../../src/workspace/config-provider";
|
import { ConfigProvider } from "../../../src/workspace/config-provider";
|
||||||
import { CostCenterJSON } from "@gitpod/gitpod-protocol/src/usage";
|
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class GitpodServerEEImpl extends GitpodServerImpl {
|
export class GitpodServerEEImpl extends GitpodServerImpl {
|
||||||
@ -2282,6 +2282,10 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
|
|||||||
await this.guardCostCenterAccess(ctx, user.id, attrId, "get");
|
await this.guardCostCenterAccess(ctx, user.id, attrId, "get");
|
||||||
|
|
||||||
const { costCenter } = await this.usageService.getCostCenter({ attributionId });
|
const { costCenter } = await this.usageService.getCostCenter({ attributionId });
|
||||||
|
return this.translateCostCenter(costCenter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private translateCostCenter(costCenter?: CostCenter): CostCenterJSON | undefined {
|
||||||
return costCenter
|
return costCenter
|
||||||
? {
|
? {
|
||||||
...costCenter,
|
...costCenter,
|
||||||
@ -2302,7 +2306,6 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
|
|||||||
if (typeof usageLimit !== "number" || usageLimit < 0) {
|
if (typeof usageLimit !== "number" || usageLimit < 0) {
|
||||||
throw new ResponseError(ErrorCodes.BAD_REQUEST, `Unexpected usageLimit value: ${usageLimit}`);
|
throw new ResponseError(ErrorCodes.BAD_REQUEST, `Unexpected usageLimit value: ${usageLimit}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = this.checkAndBlockUser("setUsageLimit");
|
const user = this.checkAndBlockUser("setUsageLimit");
|
||||||
await this.guardCostCenterAccess(ctx, user.id, attrId, "update");
|
await this.guardCostCenterAccess(ctx, user.id, attrId, "update");
|
||||||
|
|
||||||
@ -2377,6 +2380,31 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async listUsage(ctx: TraceContext, req: ListUsageRequest): Promise<ListUsageResponse> {
|
async listUsage(ctx: TraceContext, req: ListUsageRequest): Promise<ListUsageResponse> {
|
||||||
|
const attributionId = AttributionId.parse(req.attributionId);
|
||||||
|
if (!attributionId) {
|
||||||
|
throw new ResponseError(ErrorCodes.INVALID_COST_CENTER, "Bad attribution ID", {
|
||||||
|
attributionId: req.attributionId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const user = this.checkAndBlockUser("listUsage");
|
||||||
|
await this.guardCostCenterAccess(ctx, user.id, attributionId, "get");
|
||||||
|
return this.internalListUsage(ctx, req);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUsageBalance(ctx: TraceContext, attributionId: string): Promise<number> {
|
||||||
|
const user = this.checkAndBlockUser("listUsage");
|
||||||
|
const parsedAttributionId = AttributionId.parse(attributionId);
|
||||||
|
if (!parsedAttributionId) {
|
||||||
|
throw new ResponseError(ErrorCodes.INVALID_COST_CENTER, "Bad attribution ID", {
|
||||||
|
attributionId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await this.guardCostCenterAccess(ctx, user.id, parsedAttributionId, "get");
|
||||||
|
const result = await this.usageService.getBalance({ attributionId });
|
||||||
|
return result.credits;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async internalListUsage(ctx: TraceContext, req: ListUsageRequest): Promise<ListUsageResponse> {
|
||||||
const { from, to } = req;
|
const { from, to } = req;
|
||||||
const attributionId = AttributionId.parse(req.attributionId);
|
const attributionId = AttributionId.parse(req.attributionId);
|
||||||
if (!attributionId) {
|
if (!attributionId) {
|
||||||
@ -2385,9 +2413,6 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
traceAPIParams(ctx, { attributionId });
|
traceAPIParams(ctx, { attributionId });
|
||||||
const user = this.checkAndBlockUser("listUsage");
|
|
||||||
await this.guardCostCenterAccess(ctx, user.id, attributionId, "get");
|
|
||||||
|
|
||||||
const response = await this.usageService.listUsage({
|
const response = await this.usageService.listUsage({
|
||||||
attributionId: AttributionId.render(attributionId),
|
attributionId: AttributionId.render(attributionId),
|
||||||
from: from ? new Date(from) : undefined,
|
from: from ? new Date(from) : undefined,
|
||||||
@ -2745,4 +2770,84 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
|
|||||||
);
|
);
|
||||||
return project;
|
return project;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async adminGetCostCenter(ctx: TraceContext, attributionId: string): Promise<CostCenterJSON | undefined> {
|
||||||
|
const attrId = AttributionId.parse(attributionId);
|
||||||
|
if (attrId === undefined) {
|
||||||
|
log.error(`Invalid attribution id: ${attributionId}`);
|
||||||
|
throw new ResponseError(ErrorCodes.BAD_REQUEST, `Invalid attibution id: ${attributionId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = this.checkAndBlockUser("adminGetCostCenter");
|
||||||
|
await this.guardAdminAccess("adminGetCostCenter", { id: user.id }, Permission.ADMIN_USERS);
|
||||||
|
|
||||||
|
const { costCenter } = await this.usageService.getCostCenter({ attributionId });
|
||||||
|
return this.translateCostCenter(costCenter);
|
||||||
|
}
|
||||||
|
|
||||||
|
async adminSetUsageLimit(ctx: TraceContext, attributionId: string, usageLimit: number): Promise<void> {
|
||||||
|
const attrId = AttributionId.parse(attributionId);
|
||||||
|
if (attrId === undefined) {
|
||||||
|
log.error(`Invalid attribution id: ${attributionId}`);
|
||||||
|
throw new ResponseError(ErrorCodes.BAD_REQUEST, `Invalid attibution id: ${attributionId}`);
|
||||||
|
}
|
||||||
|
if (typeof usageLimit !== "number" || usageLimit < 0) {
|
||||||
|
throw new ResponseError(ErrorCodes.BAD_REQUEST, `Unexpected usageLimit value: ${usageLimit}`);
|
||||||
|
}
|
||||||
|
const user = this.checkAndBlockUser("adminSetUsageLimit");
|
||||||
|
await this.guardAdminAccess("adminSetUsageLimit", { id: user.id }, Permission.ADMIN_USERS);
|
||||||
|
|
||||||
|
const response = await this.usageService.getCostCenter({ attributionId });
|
||||||
|
|
||||||
|
// backward compatibility for cost centers that were created before introduction of BillingStrategy
|
||||||
|
if (!response.costCenter) {
|
||||||
|
throw new ResponseError(ErrorCodes.NOT_FOUND, `Coudln't find cost center with id ${attributionId}`);
|
||||||
|
}
|
||||||
|
const stripeSubscriptionId = await this.findStripeSubscriptionId(ctx, attributionId);
|
||||||
|
if (stripeSubscriptionId != undefined) {
|
||||||
|
response.costCenter.billingStrategy = CostCenter_BillingStrategy.BILLING_STRATEGY_STRIPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.usageService.setCostCenter({
|
||||||
|
costCenter: {
|
||||||
|
attributionId,
|
||||||
|
spendingLimit: usageLimit,
|
||||||
|
billingStrategy: response.costCenter.billingStrategy,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.messageBus.notifyOnSubscriptionUpdate(ctx, attrId).catch((e) => log.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
async adminListUsage(ctx: TraceContext, req: ListUsageRequest): Promise<ListUsageResponse> {
|
||||||
|
traceAPIParams(ctx, { req });
|
||||||
|
const user = this.checkAndBlockUser("adminListUsage");
|
||||||
|
await this.guardAdminAccess("adminListUsage", { id: user.id }, Permission.ADMIN_USERS);
|
||||||
|
return this.internalListUsage(ctx, req);
|
||||||
|
}
|
||||||
|
|
||||||
|
async adminGetUsageBalance(ctx: TraceContext, attributionId: string): Promise<number> {
|
||||||
|
traceAPIParams(ctx, { attributionId });
|
||||||
|
const user = this.checkAndBlockUser("adminGetUsageBalance");
|
||||||
|
await this.guardAdminAccess("adminGetUsageBalance", { id: user.id }, Permission.ADMIN_USERS);
|
||||||
|
const result = await this.usageService.getBalance({ attributionId });
|
||||||
|
return result.credits;
|
||||||
|
}
|
||||||
|
|
||||||
|
async adminAddUsageCreditNote(
|
||||||
|
ctx: TraceContext,
|
||||||
|
attributionId: string,
|
||||||
|
credits: number,
|
||||||
|
description: string,
|
||||||
|
): Promise<void> {
|
||||||
|
traceAPIParams(ctx, { attributionId, credits, note: description });
|
||||||
|
const user = this.checkAndBlockUser("adminAddUsageCreditNote");
|
||||||
|
await this.guardAdminAccess("adminAddUsageCreditNote", { id: user.id }, Permission.ADMIN_USERS);
|
||||||
|
await this.usageService.addUsageCreditNote({
|
||||||
|
attributionId,
|
||||||
|
credits,
|
||||||
|
description,
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -129,6 +129,7 @@ const defaultFunctions: FunctionsConfig = {
|
|||||||
waitForSnapshot: { group: "default", points: 1 },
|
waitForSnapshot: { group: "default", points: 1 },
|
||||||
getSnapshots: { group: "default", points: 1 },
|
getSnapshots: { group: "default", points: 1 },
|
||||||
guessGitTokenScopes: { group: "default", points: 1 },
|
guessGitTokenScopes: { group: "default", points: 1 },
|
||||||
|
getUsageBalance: { group: "default", points: 1 },
|
||||||
|
|
||||||
adminGetUsers: { group: "default", points: 1 },
|
adminGetUsers: { group: "default", points: 1 },
|
||||||
adminGetUser: { group: "default", points: 1 },
|
adminGetUser: { group: "default", points: 1 },
|
||||||
@ -157,6 +158,11 @@ const defaultFunctions: FunctionsConfig = {
|
|||||||
adminCreateBlockedRepository: { group: "default", points: 1 },
|
adminCreateBlockedRepository: { group: "default", points: 1 },
|
||||||
adminDeleteBlockedRepository: { group: "default", points: 1 },
|
adminDeleteBlockedRepository: { group: "default", points: 1 },
|
||||||
adminGetBillingMode: { group: "default", points: 1 },
|
adminGetBillingMode: { group: "default", points: 1 },
|
||||||
|
adminGetCostCenter: { group: "default", points: 1 },
|
||||||
|
adminSetUsageLimit: { group: "default", points: 1 },
|
||||||
|
adminListUsage: { group: "default", points: 1 },
|
||||||
|
adminAddUsageCreditNote: { group: "default", points: 1 },
|
||||||
|
adminGetUsageBalance: { group: "default", points: 1 },
|
||||||
|
|
||||||
validateLicense: { group: "default", points: 1 },
|
validateLicense: { group: "default", points: 1 },
|
||||||
getLicenseInfo: { group: "default", points: 1 },
|
getLicenseInfo: { group: "default", points: 1 },
|
||||||
|
|||||||
@ -3136,6 +3136,35 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
|
|||||||
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
|
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getUsageBalance(ctx: TraceContext, attributionId: string): Promise<number> {
|
||||||
|
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async adminGetCostCenter(ctx: TraceContext, attributionId: string): Promise<CostCenterJSON | undefined> {
|
||||||
|
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async adminSetUsageLimit(ctx: TraceContext, attributionId: string, usageLimit: number): Promise<void> {
|
||||||
|
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async adminListUsage(ctx: TraceContext, req: ListUsageRequest): Promise<ListUsageResponse> {
|
||||||
|
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async adminGetUsageBalance(ctx: TraceContext, attributionId: string): Promise<number> {
|
||||||
|
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async adminAddUsageCreditNote(
|
||||||
|
ctx: TraceContext,
|
||||||
|
attributionId: string,
|
||||||
|
credits: number,
|
||||||
|
note: string,
|
||||||
|
): Promise<void> {
|
||||||
|
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
|
||||||
|
}
|
||||||
|
|
||||||
async setUsageAttribution(ctx: TraceContext, usageAttributionId: string): Promise<void> {
|
async setUsageAttribution(ctx: TraceContext, usageAttributionId: string): Promise<void> {
|
||||||
const user = this.checkAndBlockUser("setUsageAttribution");
|
const user = this.checkAndBlockUser("setUsageAttribution");
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -1086,8 +1086,11 @@ type AddUsageCreditNoteRequest struct {
|
|||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
AttributionId string `protobuf:"bytes,1,opt,name=attribution_id,json=attributionId,proto3" json:"attribution_id,omitempty"`
|
AttributionId string `protobuf:"bytes,1,opt,name=attribution_id,json=attributionId,proto3" json:"attribution_id,omitempty"`
|
||||||
|
// the amount of credits to add to the given account
|
||||||
Credits int32 `protobuf:"varint,2,opt,name=credits,proto3" json:"credits,omitempty"`
|
Credits int32 `protobuf:"varint,2,opt,name=credits,proto3" json:"credits,omitempty"`
|
||||||
Note string `protobuf:"bytes,3,opt,name=note,proto3" json:"note,omitempty"`
|
// a human readable description for the reason this credit note exists
|
||||||
|
Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"`
|
||||||
|
// the id of the user (admin) who created the note
|
||||||
UserId string `protobuf:"bytes,4,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
|
UserId string `protobuf:"bytes,4,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1137,9 +1140,9 @@ func (x *AddUsageCreditNoteRequest) GetCredits() int32 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *AddUsageCreditNoteRequest) GetNote() string {
|
func (x *AddUsageCreditNoteRequest) GetDescription() string {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Note
|
return x.Description
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -1326,57 +1329,58 @@ var file_usage_v1_usage_proto_rawDesc = []byte{
|
|||||||
0x48, 0x45, 0x52, 0x10, 0x01, 0x22, 0x13, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x65, 0x74, 0x55, 0x73,
|
0x48, 0x45, 0x52, 0x10, 0x01, 0x22, 0x13, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x65, 0x74, 0x55, 0x73,
|
||||||
0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x14, 0x0a, 0x12, 0x52, 0x65,
|
0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x14, 0x0a, 0x12, 0x52, 0x65,
|
||||||
0x73, 0x65, 0x74, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
0x73, 0x65, 0x74, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||||
0x22, 0x89, 0x01, 0x0a, 0x19, 0x41, 0x64, 0x64, 0x55, 0x73, 0x61, 0x67, 0x65, 0x43, 0x72, 0x65,
|
0x22, 0x97, 0x01, 0x0a, 0x19, 0x41, 0x64, 0x64, 0x55, 0x73, 0x61, 0x67, 0x65, 0x43, 0x72, 0x65,
|
||||||
0x64, 0x69, 0x74, 0x4e, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25,
|
0x64, 0x69, 0x74, 0x4e, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25,
|
||||||
0x0a, 0x0e, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64,
|
0x0a, 0x0e, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64,
|
||||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74,
|
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74,
|
||||||
0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x73,
|
0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x73,
|
||||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x73, 0x12,
|
0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x73, 0x12,
|
||||||
0x12, 0x0a, 0x04, 0x6e, 0x6f, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
|
0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03,
|
||||||
0x6f, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04,
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x1c, 0x0a, 0x1a,
|
0x6e, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01,
|
||||||
0x41, 0x64, 0x64, 0x55, 0x73, 0x61, 0x67, 0x65, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x4e, 0x6f,
|
0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x1c, 0x0a, 0x1a, 0x41, 0x64,
|
||||||
0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xce, 0x04, 0x0a, 0x0c, 0x55,
|
0x64, 0x55, 0x73, 0x61, 0x67, 0x65, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x4e, 0x6f, 0x74, 0x65,
|
||||||
0x73, 0x61, 0x67, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x47,
|
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xce, 0x04, 0x0a, 0x0c, 0x55, 0x73, 0x61,
|
||||||
0x65, 0x74, 0x43, 0x6f, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x75,
|
0x67, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x47, 0x65, 0x74,
|
||||||
0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x73, 0x74, 0x43,
|
0x43, 0x6f, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x75, 0x73, 0x61,
|
||||||
0x65, 0x6e, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x75,
|
0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x73, 0x74, 0x43, 0x65, 0x6e,
|
||||||
0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x73, 0x74, 0x43,
|
0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x75, 0x73, 0x61,
|
||||||
0x65, 0x6e, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12,
|
0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x73, 0x74, 0x43, 0x65, 0x6e,
|
||||||
0x52, 0x0a, 0x0d, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72,
|
0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x52, 0x0a,
|
||||||
0x12, 0x1e, 0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x43,
|
0x0d, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x1e,
|
||||||
0x6f, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x73,
|
||||||
0x1a, 0x1f, 0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x43,
|
0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f,
|
||||||
0x6f, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x73,
|
||||||
0x65, 0x22, 0x00, 0x12, 0x55, 0x0a, 0x0e, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65,
|
0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
||||||
0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31,
|
0x00, 0x12, 0x55, 0x0a, 0x0e, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x55, 0x73,
|
||||||
0x2e, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52,
|
0x61, 0x67, 0x65, 0x12, 0x1f, 0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52,
|
||||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76,
|
0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71,
|
||||||
0x31, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x55, 0x73, 0x61, 0x67, 0x65,
|
0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e,
|
||||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x49, 0x0a, 0x0a, 0x52, 0x65,
|
0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65,
|
||||||
0x73, 0x65, 0x74, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x2e, 0x75, 0x73, 0x61, 0x67, 0x65,
|
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x49, 0x0a, 0x0a, 0x52, 0x65, 0x73, 0x65,
|
||||||
0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x74, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65,
|
0x74, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76,
|
||||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31,
|
0x31, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x74, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75,
|
||||||
0x2e, 0x52, 0x65, 0x73, 0x65, 0x74, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52,
|
||||||
0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x61,
|
0x65, 0x73, 0x65, 0x74, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||||
0x67, 0x65, 0x12, 0x1a, 0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69,
|
0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x61, 0x67, 0x65,
|
||||||
0x73, 0x74, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b,
|
0x12, 0x1a, 0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74,
|
||||||
0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73,
|
0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x75,
|
||||||
0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x49, 0x0a,
|
0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x61, 0x67,
|
||||||
0x0a, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1b, 0x2e, 0x75, 0x73,
|
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x49, 0x0a, 0x0a, 0x47,
|
||||||
0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63,
|
0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1b, 0x2e, 0x75, 0x73, 0x61, 0x67,
|
||||||
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x75, 0x73, 0x61, 0x67, 0x65,
|
0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52,
|
||||||
0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65,
|
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76,
|
||||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x61, 0x0a, 0x12, 0x41, 0x64, 0x64, 0x55,
|
0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70,
|
||||||
0x73, 0x61, 0x67, 0x65, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x4e, 0x6f, 0x74, 0x65, 0x12, 0x23,
|
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x61, 0x0a, 0x12, 0x41, 0x64, 0x64, 0x55, 0x73, 0x61,
|
||||||
0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x55, 0x73, 0x61,
|
0x67, 0x65, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x4e, 0x6f, 0x74, 0x65, 0x12, 0x23, 0x2e, 0x75,
|
||||||
0x67, 0x65, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x4e, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75,
|
0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x55, 0x73, 0x61, 0x67, 0x65,
|
||||||
0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41,
|
0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x4e, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||||
0x64, 0x64, 0x55, 0x73, 0x61, 0x67, 0x65, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x4e, 0x6f, 0x74,
|
0x74, 0x1a, 0x24, 0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64,
|
||||||
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x2a, 0x5a, 0x28, 0x67,
|
0x55, 0x73, 0x61, 0x67, 0x65, 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x4e, 0x6f, 0x74, 0x65, 0x52,
|
||||||
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64,
|
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x2a, 0x5a, 0x28, 0x67, 0x69, 0x74,
|
||||||
0x2d, 0x69, 0x6f, 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2f, 0x75, 0x73, 0x61, 0x67, 0x65,
|
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2d, 0x69,
|
||||||
0x2d, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x6f, 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2f, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2d, 0x61,
|
||||||
|
0x70, 0x69, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@ -251,8 +251,11 @@ export interface ResetUsageResponse {
|
|||||||
|
|
||||||
export interface AddUsageCreditNoteRequest {
|
export interface AddUsageCreditNoteRequest {
|
||||||
attributionId: string;
|
attributionId: string;
|
||||||
|
/** the amount of credits to add to the given account */
|
||||||
credits: number;
|
credits: number;
|
||||||
note: string;
|
/** a human readable description for the reason this credit note exists */
|
||||||
|
description: string;
|
||||||
|
/** the id of the user (admin) who created the note */
|
||||||
userId: string;
|
userId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1255,7 +1258,7 @@ export const ResetUsageResponse = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function createBaseAddUsageCreditNoteRequest(): AddUsageCreditNoteRequest {
|
function createBaseAddUsageCreditNoteRequest(): AddUsageCreditNoteRequest {
|
||||||
return { attributionId: "", credits: 0, note: "", userId: "" };
|
return { attributionId: "", credits: 0, description: "", userId: "" };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AddUsageCreditNoteRequest = {
|
export const AddUsageCreditNoteRequest = {
|
||||||
@ -1266,8 +1269,8 @@ export const AddUsageCreditNoteRequest = {
|
|||||||
if (message.credits !== 0) {
|
if (message.credits !== 0) {
|
||||||
writer.uint32(16).int32(message.credits);
|
writer.uint32(16).int32(message.credits);
|
||||||
}
|
}
|
||||||
if (message.note !== "") {
|
if (message.description !== "") {
|
||||||
writer.uint32(26).string(message.note);
|
writer.uint32(26).string(message.description);
|
||||||
}
|
}
|
||||||
if (message.userId !== "") {
|
if (message.userId !== "") {
|
||||||
writer.uint32(34).string(message.userId);
|
writer.uint32(34).string(message.userId);
|
||||||
@ -1289,7 +1292,7 @@ export const AddUsageCreditNoteRequest = {
|
|||||||
message.credits = reader.int32();
|
message.credits = reader.int32();
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
message.note = reader.string();
|
message.description = reader.string();
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
message.userId = reader.string();
|
message.userId = reader.string();
|
||||||
@ -1306,7 +1309,7 @@ export const AddUsageCreditNoteRequest = {
|
|||||||
return {
|
return {
|
||||||
attributionId: isSet(object.attributionId) ? String(object.attributionId) : "",
|
attributionId: isSet(object.attributionId) ? String(object.attributionId) : "",
|
||||||
credits: isSet(object.credits) ? Number(object.credits) : 0,
|
credits: isSet(object.credits) ? Number(object.credits) : 0,
|
||||||
note: isSet(object.note) ? String(object.note) : "",
|
description: isSet(object.description) ? String(object.description) : "",
|
||||||
userId: isSet(object.userId) ? String(object.userId) : "",
|
userId: isSet(object.userId) ? String(object.userId) : "",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -1315,7 +1318,7 @@ export const AddUsageCreditNoteRequest = {
|
|||||||
const obj: any = {};
|
const obj: any = {};
|
||||||
message.attributionId !== undefined && (obj.attributionId = message.attributionId);
|
message.attributionId !== undefined && (obj.attributionId = message.attributionId);
|
||||||
message.credits !== undefined && (obj.credits = Math.round(message.credits));
|
message.credits !== undefined && (obj.credits = Math.round(message.credits));
|
||||||
message.note !== undefined && (obj.note = message.note);
|
message.description !== undefined && (obj.description = message.description);
|
||||||
message.userId !== undefined && (obj.userId = message.userId);
|
message.userId !== undefined && (obj.userId = message.userId);
|
||||||
return obj;
|
return obj;
|
||||||
},
|
},
|
||||||
@ -1324,7 +1327,7 @@ export const AddUsageCreditNoteRequest = {
|
|||||||
const message = createBaseAddUsageCreditNoteRequest();
|
const message = createBaseAddUsageCreditNoteRequest();
|
||||||
message.attributionId = object.attributionId ?? "";
|
message.attributionId = object.attributionId ?? "";
|
||||||
message.credits = object.credits ?? 0;
|
message.credits = object.credits ?? 0;
|
||||||
message.note = object.note ?? "";
|
message.description = object.description ?? "";
|
||||||
message.userId = object.userId ?? "";
|
message.userId = object.userId ?? "";
|
||||||
return message;
|
return message;
|
||||||
},
|
},
|
||||||
|
|||||||
@ -143,8 +143,8 @@ message AddUsageCreditNoteRequest {
|
|||||||
string attribution_id = 1;
|
string attribution_id = 1;
|
||||||
// the amount of credits to add to the given account
|
// the amount of credits to add to the given account
|
||||||
int32 credits = 2;
|
int32 credits = 2;
|
||||||
// a human readable note for the reason this credit note exists
|
// a human readable description for the reason this credit note exists
|
||||||
string note = 3;
|
string description = 3;
|
||||||
// the id of the user (admin) who created the note
|
// the id of the user (admin) who created the note
|
||||||
string user_id = 4;
|
string user_id = 4;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -459,7 +459,7 @@ func (s *UsageService) AddUsageCreditNote(ctx context.Context, req *v1.AddUsageC
|
|||||||
WithField("attribution_id", req.AttributionId).
|
WithField("attribution_id", req.AttributionId).
|
||||||
WithField("credits", req.Credits).
|
WithField("credits", req.Credits).
|
||||||
WithField("user", req.UserId).
|
WithField("user", req.UserId).
|
||||||
WithField("note", req.Note).
|
WithField("note", req.Description).
|
||||||
Info("Adding usage credit note.")
|
Info("Adding usage credit note.")
|
||||||
|
|
||||||
attributionId, err := db.ParseAttributionID(req.AttributionId)
|
attributionId, err := db.ParseAttributionID(req.AttributionId)
|
||||||
@ -467,9 +467,9 @@ func (s *UsageService) AddUsageCreditNote(ctx context.Context, req *v1.AddUsageC
|
|||||||
return nil, status.Errorf(codes.InvalidArgument, "AttributionID '%s' couldn't be parsed (error: %s).", req.AttributionId, err)
|
return nil, status.Errorf(codes.InvalidArgument, "AttributionID '%s' couldn't be parsed (error: %s).", req.AttributionId, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
note := strings.TrimSpace(req.Note)
|
description := strings.TrimSpace(req.Description)
|
||||||
if note == "" {
|
if description == "" {
|
||||||
return nil, status.Error(codes.InvalidArgument, "The note must not be empty.")
|
return nil, status.Error(codes.InvalidArgument, "The description must not be empty.")
|
||||||
}
|
}
|
||||||
|
|
||||||
userId, err := uuid.Parse(req.UserId)
|
userId, err := uuid.Parse(req.UserId)
|
||||||
@ -480,7 +480,7 @@ func (s *UsageService) AddUsageCreditNote(ctx context.Context, req *v1.AddUsageC
|
|||||||
usage := db.Usage{
|
usage := db.Usage{
|
||||||
ID: uuid.New(),
|
ID: uuid.New(),
|
||||||
AttributionID: attributionId,
|
AttributionID: attributionId,
|
||||||
Description: note,
|
Description: description,
|
||||||
CreditCents: db.NewCreditCents(float64(req.Credits * -1)),
|
CreditCents: db.NewCreditCents(float64(req.Credits * -1)),
|
||||||
EffectiveTime: db.NewVarCharTime(time.Now()),
|
EffectiveTime: db.NewVarCharTime(time.Now()),
|
||||||
Kind: db.CreditNoteKind,
|
Kind: db.CreditNoteKind,
|
||||||
|
|||||||
@ -351,7 +351,7 @@ func TestAddUSageCreditNote(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
credits int32
|
credits int32
|
||||||
userId string
|
userId string
|
||||||
note string
|
description string
|
||||||
// expectations
|
// expectations
|
||||||
expectedError bool
|
expectedError bool
|
||||||
}{
|
}{
|
||||||
@ -366,7 +366,7 @@ func TestAddUSageCreditNote(t *testing.T) {
|
|||||||
_, err := usageService.AddUsageCreditNote(context.Background(), &v1.AddUsageCreditNoteRequest{
|
_, err := usageService.AddUsageCreditNote(context.Background(), &v1.AddUsageCreditNoteRequest{
|
||||||
AttributionId: string(attributionID),
|
AttributionId: string(attributionID),
|
||||||
Credits: test.credits,
|
Credits: test.credits,
|
||||||
Note: test.note,
|
Description: test.description,
|
||||||
UserId: test.userId,
|
UserId: test.userId,
|
||||||
})
|
})
|
||||||
if test.expectedError {
|
if test.expectedError {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user