-
Notifications
You must be signed in to change notification settings - Fork 44
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
Mkaleem.neslit.10140.email digest #10338
base: master
Are you sure you want to change the base?
Changes from all commits
1a5fe9d
c343fef
186f66f
366ba57
fb81950
826595f
adec139
13fd08a
408e7e9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -101,9 +101,15 @@ export const EnrichedThread = Thread.extend({ | |
icon_url: z | ||
.string() | ||
.describe('The icon url of the community that the thread belongs to'), | ||
author: z.string().nullish().optional(), | ||
}); | ||
|
||
export const GetDigestEmailData = { | ||
input: z.object({}), | ||
output: z.record(z.string(), z.array(EnrichedThread)), | ||
input: z.object({ | ||
user_id: z.string(), | ||
}), | ||
output: z.object({ | ||
threads: z.array(EnrichedThread), | ||
numberOfThreads: z.number(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. snake_case as per api convention |
||
}), | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
/* eslint-disable @typescript-eslint/no-unused-vars */ | ||
import { | ||
EnrichedThread, | ||
ExternalServiceUserIds, | ||
|
@@ -14,44 +15,38 @@ export function GetDigestEmailDataQuery(): Query<typeof GetDigestEmailData> { | |
auth: [], | ||
secure: true, | ||
authStrategy: { name: 'authtoken', userId: ExternalServiceUserIds.Knock }, | ||
body: async () => { | ||
const sevenDaysAgo = new Date(); | ||
sevenDaysAgo.setDate(new Date().getDate() - 7); | ||
body: async ({ payload }) => { | ||
// TODO User payload for unSubscribe once Recap email pr merge | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this still a todo? If not plz implement this or create a ticket to followup |
||
const threads = await models.sequelize.query< | ||
z.infer<typeof EnrichedThread> | ||
>( | ||
` | ||
SELECT communities.name, communities.icon_url, top_threads.* | ||
SELECT communities.name, communities.icon_url, top_threads.*, users.profile->>'name' AS author | ||
FROM (SELECT C.id, name, icon_url | ||
FROM "Communities" C | ||
WHERE C.include_in_digest_email = true) communities | ||
JOIN LATERAL ( | ||
SELECT * | ||
FROM "Threads" T | ||
WHERE T.community_id = communities.id | ||
AND created_at > NOW() - INTERVAL '10 months' | ||
AND created_at > NOW() - INTERVAL '7 days' | ||
ORDER BY T.view_count DESC | ||
LIMIT 2 | ||
) top_threads ON true; | ||
) top_threads ON true | ||
LEFT JOIN "Users" users ON users.id = top_threads.address_id | ||
ORDER BY communities.id; | ||
`, | ||
{ | ||
type: QueryTypes.SELECT, | ||
raw: true, | ||
}, | ||
); | ||
|
||
if (!threads.length) return {}; | ||
|
||
const result: z.infer<typeof GetDigestEmailData['output']> = {}; | ||
for (const thread of threads) { | ||
if (!result[thread.community_id]) { | ||
result[thread.community_id] = [thread]; | ||
} else { | ||
result[thread.community_id].push(thread); | ||
} | ||
} | ||
|
||
return result; | ||
return { | ||
threads: threads, | ||
numberOfThreads: threads.length, | ||
// unsubscribe_link: TODO : will add once email recap PR got merged | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. plz link pr number |
||
}; | ||
}, | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { type Command } from '@hicommonwealth/core'; | ||
import * as schemas from '@hicommonwealth/schemas'; | ||
import { models } from '../database'; | ||
import { isSuperAdmin } from '../middleware'; | ||
import { mustExist } from '../middleware/guards'; | ||
|
||
export function EnableDigestEmail(): Command<any> { | ||
return { | ||
...schemas.enableEmailDigest, | ||
auth: [isSuperAdmin], | ||
body: async ({ payload }) => { | ||
const community = await models.Community.findOne({ | ||
where: { | ||
id: payload.communityId, | ||
}, | ||
}); | ||
mustExist('community', community); | ||
|
||
const [affectedCount] = await models.Community.update( | ||
{ include_in_digest_email: true }, | ||
{ | ||
where: { id: payload.communityId }, | ||
}, | ||
); | ||
return { | ||
success: affectedCount > 0, | ||
}; | ||
}, | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from './EnableDigestEmail'; | ||
export * from './TriggerNotificationsWorkflow.command'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
import { ExternalServiceUserIds, dispose, query } from '@hicommonwealth/core'; | ||
import { models } from '@hicommonwealth/model'; | ||
import { Community } from '@hicommonwealth/schemas'; | ||
import { Community, User } from '@hicommonwealth/schemas'; | ||
|
||
import { expect } from 'chai'; | ||
import { afterAll, afterEach, beforeAll, describe, test } from 'vitest'; | ||
import { z } from 'zod'; | ||
|
@@ -12,8 +13,13 @@ describe('Digest email lifecycle', () => { | |
let communityOne: z.infer<typeof Community> | undefined; | ||
let communityTwo: z.infer<typeof Community> | undefined; | ||
let communityThree: z.infer<typeof Community> | undefined; | ||
let recipientUser: z.infer<typeof User> | undefined; | ||
|
||
beforeAll(async () => { | ||
[recipientUser] = await seed('User', { | ||
isAdmin: false, | ||
selected_community_id: null, | ||
}); | ||
const [authorUser] = await seed('User', { | ||
isAdmin: false, | ||
selected_community_id: null, | ||
|
@@ -85,9 +91,14 @@ describe('Digest email lifecycle', () => { | |
email: '[email protected]', | ||
}, | ||
}, | ||
payload: {}, | ||
payload: { | ||
user_id: String(recipientUser!.id), | ||
}, | ||
}); | ||
expect(res).to.deep.equal({ | ||
threads: [], | ||
numberOfThreads: 0, | ||
}); | ||
expect(res).to.deep.equal({}); | ||
await models.Community.update( | ||
{ | ||
include_in_digest_email: false, | ||
|
@@ -119,9 +130,14 @@ describe('Digest email lifecycle', () => { | |
email: '[email protected]', | ||
}, | ||
}, | ||
payload: {}, | ||
payload: { | ||
user_id: String(recipientUser!.id), | ||
}, | ||
}); | ||
expect(res).to.deep.equal({ | ||
threads: [], | ||
numberOfThreads: 0, | ||
}); | ||
expect(res).to.deep.equal({}); | ||
}); | ||
|
||
test('should return enriched threads for each community', async () => { | ||
|
@@ -148,11 +164,20 @@ describe('Digest email lifecycle', () => { | |
email: '[email protected]', | ||
}, | ||
}, | ||
payload: {}, | ||
payload: { | ||
user_id: String(recipientUser!.id), | ||
}, | ||
}); | ||
|
||
expect(res![communityOne!.id!]!.length).to.equal(2); | ||
expect(res![communityTwo!.id!]!.length).to.equal(1); | ||
const filtercommunityOne = res?.threads.filter( | ||
(thread) => thread.community_id == communityOne!.id, | ||
); | ||
const filtercommunityTwo = res?.threads.filter( | ||
(thread) => thread.community_id == communityTwo!.id, | ||
); | ||
|
||
expect(filtercommunityOne!.length).to.equal(2); | ||
expect(filtercommunityTwo!.length).to.equal(1); | ||
|
||
delete threadOne?.Address; | ||
delete threadOne?.collaborators; | ||
|
@@ -167,20 +192,30 @@ describe('Digest email lifecycle', () => { | |
delete threadFour?.reactions; | ||
delete threadFour?.ThreadVersionHistories; | ||
|
||
expect(res![communityOne!.id!]![0]!).to.deep.equal({ | ||
name: communityOne!.name, | ||
icon_url: communityOne!.icon_url, | ||
...threadOne, | ||
}); | ||
expect(res![communityOne!.id!]![1]!).to.deep.equal({ | ||
name: communityOne!.name, | ||
icon_url: communityOne!.icon_url, | ||
...threadTwo, | ||
}); | ||
expect(res![communityTwo!.id!]![0]!).to.deep.equal({ | ||
name: communityTwo!.name, | ||
icon_url: communityTwo!.icon_url, | ||
...threadFour, | ||
}); | ||
if (filtercommunityOne && filtercommunityOne.length > 0) { | ||
expect(filtercommunityOne[0]).to.deep.equal({ | ||
name: communityOne!.name, | ||
icon_url: communityOne!.icon_url, | ||
author: null, | ||
...threadOne, | ||
}); | ||
} | ||
|
||
if (filtercommunityOne && filtercommunityOne.length >= 1) { | ||
expect(filtercommunityOne[1]).to.deep.equal({ | ||
name: communityOne!.name, | ||
icon_url: communityOne!.icon_url, | ||
author: null, | ||
...threadTwo, | ||
}); | ||
} | ||
if (filtercommunityTwo && filtercommunityTwo.length > 0) { | ||
expect(filtercommunityTwo[0]).to.deep.equal({ | ||
name: communityTwo!.name, | ||
icon_url: communityTwo!.icon_url, | ||
author: null, | ||
...threadFour, | ||
}); | ||
} | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,4 +17,13 @@ export const TriggerNotificationsWorkflow = { | |
}), | ||
}; | ||
|
||
export const enableEmailDigest = { | ||
input: z.object({ | ||
communityId: z.string(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
}), | ||
output: z.object({ | ||
success: z.boolean(), | ||
}), | ||
}; | ||
|
||
export type Type1 = z.infer<typeof TriggerNotificationsWorkflow.input>; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { trpc } from 'utils/trpcClient'; | ||
const useEnableDigestEmail = () => { | ||
return trpc.superAdmin.enableDigestEmail.useMutation({}); | ||
}; | ||
export default useEnableDigestEmail; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
import useEnableDigestEmail from './enableDigestEmail'; | ||
import useTriggerNotificationsWorkflowMutation from './triggerNotificationsWorkflow'; | ||
|
||
export { useTriggerNotificationsWorkflowMutation }; | ||
export { useEnableDigestEmail, useTriggerNotificationsWorkflowMutation }; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import { useEnableDigestEmail } from 'client/scripts/state/api/superAdmin'; | ||
import { notifyError, notifySuccess } from 'controllers/app/notifications'; | ||
import React, { useState } from 'react'; | ||
import { useGetCommunityByIdQuery } from 'state/api/communities'; | ||
import { useDebounce } from 'usehooks-ts'; | ||
import { CWText } from '../../components/component_kit/cw_text'; | ||
import { CWButton } from '../../components/component_kit/new_designs/CWButton'; | ||
import { CWTextInput } from '../../components/component_kit/new_designs/CWTextInput'; | ||
import './AdminPanel.scss'; | ||
|
||
const EnableDigestEmail = () => { | ||
const [communityId, setCommunityId] = useState<string>(''); | ||
const debouncedCommunityLookupId = useDebounce<string | undefined>( | ||
communityId, | ||
500, | ||
); | ||
const { mutateAsync: triggerEnableDigestEmail } = useEnableDigestEmail(); | ||
|
||
const { data: communityLookupData, isLoading: isLoadingCommunityLookupData } = | ||
useGetCommunityByIdQuery({ | ||
id: debouncedCommunityLookupId || '', | ||
enabled: !!debouncedCommunityLookupId, | ||
}); | ||
|
||
const setCommunityIdInput = (e) => { | ||
setCommunityId(e?.target?.value?.trim() || ''); | ||
}; | ||
|
||
const communityNotFound = | ||
!isLoadingCommunityLookupData && | ||
(!communityLookupData || | ||
Object.keys(communityLookupData || {})?.length === 0); | ||
|
||
const communityIdInputError = (() => { | ||
if (communityNotFound) return 'Community not found'; | ||
return ''; | ||
})(); | ||
|
||
const buttonEnabled = | ||
!isLoadingCommunityLookupData && | ||
communityId.length > 0 && | ||
!communityNotFound; | ||
|
||
const update = () => { | ||
if (Object.keys(communityLookupData || {}).length > 0) { | ||
try { | ||
triggerEnableDigestEmail({ communityId }); | ||
notifySuccess('Success'); | ||
} catch (error) { | ||
notifyError('Error'); | ||
console.error(error); | ||
} | ||
} | ||
}; | ||
|
||
return ( | ||
<div className="TaskGroup"> | ||
<CWText type="h4">Enable Digest email for Admin</CWText> | ||
<div className="TaskRow"> | ||
<CWTextInput | ||
label="Community Id" | ||
value={communityId} | ||
onInput={setCommunityIdInput} | ||
customError={communityIdInputError} | ||
placeholder="Enter a community id" | ||
/> | ||
<CWButton | ||
label="Enable" | ||
className="TaskButton" | ||
disabled={!buttonEnabled} | ||
onClick={update} | ||
/> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default EnableDigestEmail; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
numberOfThreads seems redundant if same as the length of the threads array
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we need this to add condition in knock . i did not find way to check array length there