Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix subscriptions #2261

Merged
merged 1 commit into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 21 additions & 12 deletions app/editor/src/features/admin/products/ProductSubRequests.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useFormikContext } from 'formik';
import React from 'react';
import { IProductModel, ProductRequestStatusName, Show } from 'tno-core';
import { Col, IProductModel, ProductRequestStatusName, Row, Show } from 'tno-core';

import * as styled from './styled';
import { UserApproveDeny } from './UserApproveDeny';
Expand Down Expand Up @@ -39,17 +39,26 @@ export const ProductSubRequests: React.FC = () => {
{values.subscribers.map((user, index) => {
if (user.status !== ProductRequestStatusName.RequestUnsubscribe) return null;
return (
<UserApproveDeny
key={`user-request-cancellation-${user.userId}`}
user={user}
onChange={(approve) =>
setFieldValue(`subscribers.${index}`, {
...user,
status: ProductRequestStatusName.NA,
isSubscribed: !approve,
})
}
/>
<Row key={`user-request-cancellation-${user.userId}`} gap="1rem">
<Col flex="1">
<UserApproveDeny
user={user}
onChange={(approve) =>
setFieldValue(`subscribers.${index}`, {
...user,
status: ProductRequestStatusName.NA,
isSubscribed: !approve,
})
}
/>
</Col>
<Col flex="2">
<p>
If this user is currently subscribed through a distribution list, you will need to
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a warning as the code will take a lot longer to write.

first remove them from the list and then approve this request.
</p>
</Col>
</Row>
);
})}
<Show
Expand Down
60 changes: 41 additions & 19 deletions app/subscriber/src/features/my-products/MyProducts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export const MyProducts: React.FC = () => {
userId={userId}
product={product}
onToggleSubscription={(userProduct) => {
console.debug(userProduct);
selectProduct(product, userProduct);
toggle();
}}
Expand All @@ -140,7 +141,7 @@ export const MyProducts: React.FC = () => {
{products
.filter((product) =>
// products which the user *IS NOT* unsubscribed to
product.subscribers.some(
product.subscribers.every(
(s) =>
!s.isSubscribed && s.status !== ProductRequestStatusName.RequestSubscription,
),
Expand All @@ -152,6 +153,7 @@ export const MyProducts: React.FC = () => {
userId={userInfo?.id}
product={product}
onToggleSubscription={(userProduct) => {
console.debug(userProduct);
selectProduct(product, userProduct);
toggle();
}}
Expand All @@ -163,27 +165,11 @@ export const MyProducts: React.FC = () => {
</PageSection>
<Modal
headerText={`Confirm change`}
body={
(active?.userProduct.status === ProductRequestStatusName.RequestSubscription
? `Are you sure you wish to ${
active.userProduct.isSubscribed ? 'unsubscribe from' : 'subscribe to'
}`
: `Are you sure you wish to cancel your pending request to ${
active?.userProduct.isSubscribed ? 'unsubscribe from' : 'subscribe to'
}`) + `"${active?.product.name}"?`
}
body={active && modalBody(active)}
isShowing={isShowing}
hide={toggle}
type="default"
confirmText={
active?.userProduct.status === ProductRequestStatusName.RequestSubscription
? `Yes, ${
active.userProduct.isSubscribed ? 'request to unsubscribe' : 'request to subscribe'
}`
: `Yes, cancel my pending request to ${
active?.userProduct.isSubscribed ? 'unsubscribe' : 'subscribe'
}`
}
confirmText={active && modalConfirmText(active)}
onConfirm={() => {
if (active) handleToggleSubscription(active.product, active.userProduct);
toggle();
Expand All @@ -192,3 +178,39 @@ export const MyProducts: React.FC = () => {
</styled.MyProducts>
);
};

const modalBody = (active: ISelectedProduct) => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed text based on status. This all got messy due to distribution lists.

if (active.userProduct.isSubscribed) {
switch (active.userProduct.status) {
case ProductRequestStatusName.RequestUnsubscribe:
return `Are you sure you wish to unsubscribe to ${active.product.name}`;
default:
return `Are you sure you wish to cancel your pending request to unsubscribe to ${active.product.name}`;
}
} else {
switch (active.userProduct.status) {
case ProductRequestStatusName.RequestSubscription:
return `Are you sure you wish to subscribe to ${active.product.name}`;
default:
return `Are you sure you wish to cancel your pending request to subscribe to ${active.product.name}`;
}
}
};

const modalConfirmText = (active: ISelectedProduct) => {
if (active.userProduct.isSubscribed) {
switch (active.userProduct.status) {
case ProductRequestStatusName.RequestUnsubscribe:
return `Yes, request to unsubscribe`;
default:
return `Yes, cancel my pending request to unsubscribe`;
}
} else {
switch (active.userProduct.status) {
case ProductRequestStatusName.RequestSubscription:
return `Yes, request to subscribe`;
default:
return `Yes, cancel my pending request to subscribe`;
}
}
};
10 changes: 9 additions & 1 deletion app/subscriber/src/features/my-products/ProductCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ export const ProductCard: React.FC<IProductCardProps> = ({
}) => {
const [{ userInfo }] = useApp();

const userProduct: IUserProductModel = product.subscribers.find((s) => s.userId === userId) ?? {
let userProduct: IUserProductModel | undefined = product.subscribers.find(
(s) => s.userId === userId,
) ?? {
userId: userId ?? 0,
productId: product.id,
status: ProductRequestStatusName.NA,
Expand All @@ -48,6 +50,12 @@ export const ProductCard: React.FC<IProductCardProps> = ({
isEnabled: userInfo?.isEnabled ?? true,
accountType: UserAccountTypeName.Direct,
};
// Check a distribution list to see if user is subscribed this way.
if (product.subscribers.length > 1 && !userProduct?.isSubscribed) {
const distribution = product.subscribers.find((s) => s.isSubscribed);
// Switch the subscription to the user from the distribution list.
if (distribution) userProduct = { ...distribution, userId: userId ?? 0 };
}
const isSubscribed = userProduct.isSubscribed;
const isRequesting =
!userProduct.isSubscribed &&
Expand Down
29 changes: 21 additions & 8 deletions libs/net/dal/Services/ProductService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ public IEnumerable<Product> FindPublic()
/// <returns></returns>
public IEnumerable<Product> Find(ProductFilter filter)
{
int[] distributionIds = Array.Empty<int>();
var query = filter.IsAvailableToUserId.HasValue == true ?
this.Context.Products.AsNoTracking() :
this.Context.Products.Include(r => r.SubscribersManyToMany).ThenInclude(s => s.User).AsNoTracking();
Expand All @@ -91,7 +92,16 @@ public IEnumerable<Product> Find(ProductFilter filter)
if (filter.SubscriberUserId.HasValue == true)
query = query.Where(r => r.SubscribersManyToMany.Any(s => s.UserId == filter.SubscriberUserId.Value));
if (filter.IsAvailableToUserId.HasValue == true)
query = query.Where(r => r.IsPublic || r.SubscribersManyToMany.Any(s => s.UserId == filter.IsAvailableToUserId.Value));
{
// Get all distribution lists this user is part of.
distributionIds = this.Context.UserDistributions
.Where(ud => ud.LinkedUserId == filter.IsAvailableToUserId.Value)
.Select(ud => ud.UserId).ToArray();

query = query.Where(r => r.IsPublic ||
r.SubscribersManyToMany.Any(s => s.UserId == filter.IsAvailableToUserId.Value) ||
r.SubscribersManyToMany.Any(s => distributionIds.Contains(s.UserId)));
}

if (filter.Sort?.Any() == true)
{
Expand Down Expand Up @@ -126,19 +136,22 @@ public IEnumerable<Product> Find(ProductFilter filter)
.FirstOrDefault(u => u.Id == filter.IsAvailableToUserId.Value) ?? throw new NoContentException();

// Fetch product subscribers only for the specified user.
var subscribers = this.Context.UserProducts.AsNoTracking().Where(up => up.UserId == filter.IsAvailableToUserId.Value).ToArray();
var subscribers = this.Context.UserProducts
.AsNoTracking()
.Include(s => s.Product)
.Include(s => s.User)
.Include(s => s.User).ThenInclude(u => u!.ReportSubscriptionsManyToMany)
.Include(s => s.User).ThenInclude(u => u!.NotificationSubscriptionsManyToMany)
.Include(s => s.User).ThenInclude(u => u!.AVOverviewSubscriptionsManyToMany)
.Where(up => up.UserId == filter.IsAvailableToUserId.Value || distributionIds.Contains(up.UserId))
.ToArray();

// For each product add the actual subscription records.
foreach (var product in products)
{
product.SubscribersManyToMany.AddRange(subscribers.Where(s => s.ProductId == product.Id));
var subscriber = product.SubscribersManyToMany.FirstOrDefault(s => s.UserId == filter.IsAvailableToUserId.Value);
if (subscriber != null)
{
subscriber.Product = product;
subscriber.User = user;
}
else
if (!product.SubscribersManyToMany.Any())
product.SubscribersManyToMany.Add(new UserProduct(user, product));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ public UserProductModel() { }

/// <summary>
/// Creates a new instance of an UserProductModel, initializes with specified parameter.
/// An assumption is made in this model. If a subscription exists for either the user or a distribution group, we assume the user is subscribed.
/// </summary>
/// <param name="entity"></param>
public UserProductModel(Entities.UserProduct entity) : base(entity)
Expand Down Expand Up @@ -109,15 +110,13 @@ public UserProductModel(Entities.UserProduct entity) : base(entity)
else if (entity.Product.ProductType == Entities.ProductType.Notification)
{
var subscription = entity.User.NotificationSubscriptionsManyToMany
.FirstOrDefault(s => s.UserId == entity.UserId &&
s.NotificationId == entity.Product!.TargetProductId);
.FirstOrDefault(s => s.IsSubscribed && s.NotificationId == entity.Product!.TargetProductId);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love this, but since a user can be subscribed through a distribution list it's currently necessary.

this.IsSubscribed = subscription?.IsSubscribed ?? false;
}
else if (entity.Product.ProductType == Entities.ProductType.EveningOverview)
{
var subscription = entity.User.AVOverviewSubscriptionsManyToMany
.FirstOrDefault(s => s.UserId == entity.UserId &&
(int)s.TemplateType == entity.Product!.TargetProductId);
.FirstOrDefault(s => s.IsSubscribed && (int)s.TemplateType == entity.Product!.TargetProductId);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love this, but since a user can be subscribed through a distribution list it's currently necessary.

this.IsSubscribed = subscription?.IsSubscribed ?? false;
this.SendTo = subscription?.SendTo;
}
Expand Down
Loading