Skip to content

Commit 6297850

Browse files
committed
Merge branch 'main' into nlp-background-audio
2 parents 0c861a4 + 1c7ccea commit 6297850

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+978
-228
lines changed

hub/models/extra_user_detail.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ def save(
3535
if not update_fields or (update_fields and 'data' in update_fields):
3636
self.standardize_json_field('data', 'organization', str)
3737
self.standardize_json_field('data', 'name', str)
38+
if not created:
39+
self._sync_org_name()
3840

3941
super().save(
4042
force_insert=force_insert,
@@ -59,3 +61,22 @@ def save(
5961
self.user.id,
6062
self.validated_password,
6163
)
64+
65+
def _sync_org_name(self):
66+
"""
67+
Synchronizes the `name` field of the Organization model with the
68+
"organization" attribute found in the `data` field of ExtraUserDetail,
69+
but only if the user is the owner.
70+
71+
This ensures that any updates in the metadata are accurately reflected
72+
in the organization's name.
73+
"""
74+
user_organization = self.user.organization
75+
if user_organization.is_owner(self.user):
76+
try:
77+
organization_name = self.data['organization'].strip()
78+
except (KeyError, AttributeError):
79+
organization_name = None
80+
81+
user_organization.name = organization_name
82+
user_organization.save(update_fields=['name'])

jsapp/js/account/accountSidebar.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import {NavLink} from 'react-router-dom';
33
import {observer} from 'mobx-react-lite';
44
import bem from 'js/bem';
55
import Icon from 'js/components/common/icon';
6-
import {IconName} from 'jsapp/fonts/k-icons';
6+
import type {IconName} from 'jsapp/fonts/k-icons';
77
import Badge from '../components/common/badge';
88
import subscriptionStore from 'js/account/subscriptionStore';
99
import './accountSidebar.scss';
1010
import useWhenStripeIsEnabled from 'js/hooks/useWhenStripeIsEnabled.hook';
11-
import {OrganizationContext} from 'js/account/organizations/useOrganization.hook';
1211
import {ACCOUNT_ROUTES} from 'js/account/routes.constants';
12+
import {useOrganizationQuery} from './stripe.api';
1313

1414
interface AccountNavLinkProps {
1515
iconName: IconName;
@@ -34,9 +34,8 @@ function AccountNavLink(props: AccountNavLinkProps) {
3434

3535
function AccountSidebar() {
3636
const [showPlans, setShowPlans] = useState(false);
37-
const [organization, _] = useContext(OrganizationContext);
3837

39-
const isOrgOwner = useMemo(() => organization?.is_owner, [organization]);
38+
const orgQuery = useOrganizationQuery();
4039

4140
useWhenStripeIsEnabled(() => {
4241
if (!subscriptionStore.isInitialised) {
@@ -61,7 +60,7 @@ function AccountSidebar() {
6160
name={t('Security')}
6261
to={ACCOUNT_ROUTES.SECURITY}
6362
/>
64-
{isOrgOwner && (
63+
{orgQuery.data?.is_owner && (
6564
<>
6665
<AccountNavLink
6766
iconName='reports'
@@ -80,7 +79,7 @@ function AccountSidebar() {
8079
iconName='plus'
8180
name={t('Add-ons')}
8281
to={ACCOUNT_ROUTES.ADD_ONS}
83-
isNew={true}
82+
isNew
8483
/>
8584
)}
8685
</>
Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,22 @@
11
import React, {ReactNode} from 'react';
22
import {UsageContext, useUsage} from 'js/account/usage/useUsage.hook';
33
import {ProductsContext, useProducts} from 'js/account/useProducts.hook';
4-
import {
5-
OrganizationContext,
6-
useOrganization,
7-
} from 'js/account/organizations/useOrganization.hook';
84
import sessionStore from 'js/stores/session';
5+
import {useOrganizationQuery} from 'js/account/stripe.api';
96

107
export const BillingContextProvider = (props: {children: ReactNode}) => {
8+
const orgQuery = useOrganizationQuery();
9+
1110
if (!sessionStore.isLoggedIn) {
1211
return <>{props.children}</>;
1312
}
14-
const [organization, reloadOrg, orgStatus] = useOrganization();
15-
const usage = useUsage(organization?.id);
13+
const usage = useUsage(orgQuery.data?.id || null);
1614
const products = useProducts();
1715
return (
18-
<OrganizationContext.Provider value={[organization, reloadOrg, orgStatus]}>
19-
<UsageContext.Provider value={usage}>
20-
<ProductsContext.Provider value={products}>
21-
{props.children}
22-
</ProductsContext.Provider>
23-
</UsageContext.Provider>
24-
</OrganizationContext.Provider>
16+
<UsageContext.Provider value={usage}>
17+
<ProductsContext.Provider value={products}>
18+
{props.children}
19+
</ProductsContext.Provider>
20+
</UsageContext.Provider>
2521
);
2622
};
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type {SubscriptionInfo} from 'jsapp/js/account/stripe.types';
2+
import type {EnvStoreData} from 'jsapp/js/envStore';
3+
4+
/** Only use this directly for complex cases/strings (for example, possessive case).
5+
* Otherwise, use getSimpleMMOLabel.
6+
* @param {EnvStoreData} envStoreData
7+
* @param {SubscriptionInfo} subscription
8+
* @returns boolean indicating whether an MMO should be referred to as a 'team' or as an 'organization
9+
*/
10+
export function shouldUseTeamLabel(
11+
envStoreData: EnvStoreData,
12+
subscription: SubscriptionInfo | null
13+
) {
14+
if (subscription) {
15+
return (
16+
subscription.items[0].price.product.metadata?.plan_type !== 'enterprise'
17+
);
18+
}
19+
20+
return envStoreData.use_team_label;
21+
}
22+
23+
/**
24+
* @param {EnvStoreData} envStoreData
25+
* @param {SubscriptionInfo} subscription
26+
* @param {boolean} plural
27+
* @param {boolean} capitalize
28+
* @returns Translated string for referring to MMO as 'team' or 'organization'
29+
* */
30+
export function getSimpleMMOLabel(
31+
envStoreData: EnvStoreData,
32+
subscription: SubscriptionInfo | null,
33+
plural: boolean = false,
34+
capitalize: boolean = false
35+
) {
36+
if (shouldUseTeamLabel(envStoreData, subscription)) {
37+
if (plural) {
38+
return capitalize ? t('Teams') : t('teams');
39+
}
40+
return capitalize ? t('Team') : t('team');
41+
}
42+
43+
if (plural) {
44+
return capitalize ? t('Organizations') : t('organizations');
45+
}
46+
return capitalize ? t('Organization') : t('organization');
47+
}

jsapp/js/account/organizations/requireOrgOwner.component.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,31 @@
1-
import React, {Suspense, useContext, useEffect} from 'react';
1+
import React, {Suspense, useEffect} from 'react';
22
import {useNavigate} from 'react-router-dom';
3-
import {OrganizationContext} from 'js/account/organizations/useOrganization.hook';
43
import LoadingSpinner from 'js/components/common/loadingSpinner';
54
import {ACCOUNT_ROUTES} from 'js/account/routes.constants';
5+
import {useOrganizationQuery} from 'js/account/stripe.api';
66

77
interface Props {
88
children: React.ReactNode;
99
redirect?: boolean;
1010
}
1111

1212
export const RequireOrgOwner = ({children, redirect = true}: Props) => {
13-
const [organization, _, orgStatus] = useContext(OrganizationContext);
1413
const navigate = useNavigate();
14+
const orgQuery = useOrganizationQuery();
1515

16+
// Redirect to Account Settings if you're not the owner
1617
useEffect(() => {
1718
if (
1819
redirect &&
19-
!orgStatus.pending &&
20-
organization &&
21-
!organization.is_owner
20+
!orgQuery.isPending &&
21+
orgQuery.data &&
22+
!orgQuery.data.is_owner
2223
) {
2324
navigate(ACCOUNT_ROUTES.ACCOUNT_SETTINGS);
2425
}
25-
}, [organization, orgStatus.pending, redirect]);
26+
}, [redirect, orgQuery.isSuccess, orgQuery.data, navigate]);
2627

27-
return redirect && organization?.is_owner ? (
28+
return redirect && orgQuery.data?.is_owner ? (
2829
<Suspense fallback={null}>{children}</Suspense>
2930
) : (
3031
<LoadingSpinner />

jsapp/js/account/organizations/useOrganization.hook.tsx

Lines changed: 0 additions & 26 deletions
This file was deleted.

jsapp/js/account/plans/plan.component.tsx

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import React, {
99
} from 'react';
1010
import {useNavigate, useSearchParams} from 'react-router-dom';
1111
import styles from './plan.module.scss';
12-
import {postCheckout, postCustomerPortal} from '../stripe.api';
12+
import {postCheckout, postCustomerPortal, useOrganizationQuery} from '../stripe.api';
1313
import Button from 'js/components/common/button';
1414
import classnames from 'classnames';
1515
import LoadingSpinner from 'js/components/common/loadingSpinner';
@@ -37,7 +37,6 @@ import type {ConfirmChangeProps} from 'js/account/plans/confirmChangeModal.compo
3737
import ConfirmChangeModal from 'js/account/plans/confirmChangeModal.component';
3838
import {PlanContainer} from 'js/account/plans/planContainer.component';
3939
import {ProductsContext} from '../useProducts.hook';
40-
import {OrganizationContext} from 'js/account/organizations/useOrganization.hook';
4140
import {ACCOUNT_ROUTES} from 'js/account/routes.constants';
4241
import {useRefreshApiFetcher} from 'js/hooks/useRefreshApiFetcher.hook';
4342

@@ -115,8 +114,7 @@ export default function Plan(props: PlanProps) {
115114
>([]);
116115
const [products, loadProducts, productsStatus] = useContext(ProductsContext);
117116
useRefreshApiFetcher(loadProducts, productsStatus);
118-
const [organization, loadOrg, orgStatus] = useContext(OrganizationContext);
119-
useRefreshApiFetcher(loadOrg, orgStatus);
117+
const orgQuery = useOrganizationQuery();
120118
const [confirmModal, setConfirmModal] = useState<ConfirmChangeProps>({
121119
newPrice: null,
122120
products: [],
@@ -149,8 +147,8 @@ export default function Plan(props: PlanProps) {
149147

150148
const isDataLoading = useMemo(
151149
(): boolean =>
152-
!(products.isLoaded && organization && state.subscribedProduct),
153-
[products.isLoaded, organization, state.subscribedProduct]
150+
!(products.isLoaded && orgQuery.data && state.subscribedProduct),
151+
[products.isLoaded, orgQuery.data, state.subscribedProduct]
154152
);
155153

156154
const isDisabled = useMemo(() => isBusy, [isBusy]);
@@ -227,10 +225,10 @@ export default function Plan(props: PlanProps) {
227225

228226
// if the user is not the owner of their org, send them back to the settings page
229227
useEffect(() => {
230-
if (!organization?.is_owner) {
228+
if (!orgQuery.data?.is_owner) {
231229
navigate(ACCOUNT_ROUTES.ACCOUNT_SETTINGS);
232230
}
233-
}, [organization]);
231+
}, [orgQuery.data]);
234232

235233
// Re-fetch data from API and re-enable buttons if displaying from back/forward cache
236234
useEffect(() => {
@@ -372,15 +370,15 @@ export default function Plan(props: PlanProps) {
372370
};
373371

374372
const buySubscription = (price: Price, quantity = 1) => {
375-
if (!price.id || isDisabled || !organization?.id) {
373+
if (!price.id || isDisabled || !orgQuery.data?.id) {
376374
return;
377375
}
378376
setIsBusy(true);
379377
if (activeSubscriptions.length) {
380378
if (!isDowngrade(activeSubscriptions, price, quantity)) {
381379
// if the user is upgrading prices, send them to the customer portal
382380
// this will immediately change their subscription
383-
postCustomerPortal(organization.id, price.id, quantity)
381+
postCustomerPortal(orgQuery.data.id, price.id, quantity)
384382
.then(processCheckoutResponse)
385383
.catch(() => setIsBusy(false));
386384
} else {
@@ -395,7 +393,7 @@ export default function Plan(props: PlanProps) {
395393
}
396394
} else {
397395
// just send the user to the checkout page
398-
postCheckout(price.id, organization.id, quantity)
396+
postCheckout(price.id, orgQuery.data.id, quantity)
399397
.then(processCheckoutResponse)
400398
.catch(() => setIsBusy(false));
401399
}
@@ -441,7 +439,7 @@ export default function Plan(props: PlanProps) {
441439
</div>
442440
);
443441

444-
if (!products.products.length || !organization) {
442+
if (!products.products.length || !orgQuery.data) {
445443
return null;
446444
}
447445

@@ -557,7 +555,7 @@ export default function Plan(props: PlanProps) {
557555
isBusy={isBusy}
558556
setIsBusy={setIsBusy}
559557
products={products.products}
560-
organization={organization}
558+
organization={orgQuery.data}
561559
onClickBuy={buySubscription}
562560
/>
563561
)}

jsapp/js/account/plans/planButton.component.tsx

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
11
import BillingButton from 'js/account/plans/billingButton.component';
22
import React, {useContext} from 'react';
3-
import type {
4-
Price,
5-
Organization,
6-
SinglePricedProduct,
7-
} from 'js/account/stripe.types';
8-
import {postCustomerPortal} from 'js/account/stripe.api';
3+
import type {Price, SinglePricedProduct} from 'js/account/stripe.types';
4+
import {postCustomerPortal, useOrganizationQuery} from 'js/account/stripe.api';
95
import {processCheckoutResponse} from 'js/account/stripe.utils';
10-
import {OrganizationContext} from 'js/account/organizations/useOrganization.hook';
116

127
interface PlanButtonProps {
138
buySubscription: (price: Price, quantity?: number) => void;
@@ -34,15 +29,15 @@ export const PlanButton = ({
3429
quantity,
3530
isSubscribedToPlan,
3631
}: PlanButtonProps) => {
37-
const [organization] = useContext(OrganizationContext);
32+
const orgQuery = useOrganizationQuery();
3833

39-
if (!product || !organization || product.price.unit_amount === 0) {
34+
if (!product || !orgQuery.data || product.price.unit_amount === 0) {
4035
return null;
4136
}
4237

4338
const manageSubscription = (subscriptionPrice?: Price) => {
4439
setIsBusy(true);
45-
postCustomerPortal(organization.id, subscriptionPrice?.id, quantity)
40+
postCustomerPortal(orgQuery.data.id, subscriptionPrice?.id, quantity)
4641
.then(processCheckoutResponse)
4742
.catch(() => setIsBusy(false));
4843
};

jsapp/js/query/queries/accessLogs.query.ts renamed to jsapp/js/account/security/accessLogs/accessLogs.query.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {keepPreviousData, useQuery} from '@tanstack/react-query';
22
import {endpoints} from 'js/api.endpoints';
33
import type {PaginatedResponse} from 'js/dataInterface';
44
import {fetchGet} from 'js/api';
5-
import {QueryKeys} from '../queryKeys';
5+
import {QueryKeys} from 'js/query/queryKeys';
66

77
export interface AccessLog {
88
/** User URL */

jsapp/js/account/security/accessLogs/accessLogsSection.component.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import React from 'react';
66
import PaginatedQueryUniversalTable from 'js/universalTable/paginatedQueryUniversalTable.component';
77

88
// Utilities
9-
import useAccessLogsQuery, {type AccessLog} from 'js/query/queries/accessLogs.query';
9+
import useAccessLogsQuery, {type AccessLog} from './accessLogs.query';
1010
import {formatTime} from 'js/utils';
1111
// import sessionStore from 'js/stores/session';
1212

0 commit comments

Comments
 (0)