Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
32c4b25
EagleEye contents with actions (#483)
epipav Feb 8, 2023
536ef3f
Eagle eye onboarding (#496)
joanagmaia Feb 9, 2023
b845b7c
Fixes
Feb 9, 2023
f11dec3
Transactions in upsert content
Feb 9, 2023
4512a32
First version of eagle eye feed (#507)
joanagmaia Feb 13, 2023
514adc4
Eagle eye feed settings drawer (#517)
gaspergrom Feb 13, 2023
fd4e569
Make eagle eye form components reusable (#524)
joanagmaia Feb 14, 2023
44fc413
[C-750] Email digest settings (#498)
gaspergrom Feb 14, 2023
e6b28aa
Fix settings update action (#526)
joanagmaia Feb 14, 2023
7bf8c5e
Event tracking for the new EagleEye (#525)
Feb 14, 2023
7da7587
Eagle eye drawers fixes (#536)
gaspergrom Feb 15, 2023
5a4a08b
Eagle eye content wrong results with offset and limit (#537)
epipav Feb 15, 2023
3aedbcb
Fix feed settings saving issues (#539)
gaspergrom Feb 16, 2023
04da5b5
Fix email digest disable button state (#540)
gaspergrom Feb 16, 2023
295aa6c
Fix issues raised in eagle eye feed (#535)
joanagmaia Feb 16, 2023
fbb731d
Eagle eye email digest drawer on update (#546)
joanagmaia Feb 20, 2023
05cfe9c
Eagle eye email digest (#545)
epipav Feb 20, 2023
6475a2b
email already sent check for full queue scenerios in eagle eye digest
epipav Feb 20, 2023
fbb3677
formatting
epipav Feb 20, 2023
2775616
Email digest local timezone (#550)
gaspergrom Feb 20, 2023
b0e8eb0
Improve loading state for eagle eye (#549)
joanagmaia Feb 20, 2023
83bf5c1
Eagle eye 24h local time (#551)
gaspergrom Feb 20, 2023
62f8082
AI replies for EagleEye (#548)
Feb 20, 2023
9102e2e
Fix loading state
joanagmaia Feb 20, 2023
c21b345
Merge remote-tracking branch 'origin/main' into feature/eagle-eye-2
joanagmaia Feb 20, 2023
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
7 changes: 2 additions & 5 deletions .github/workflows/lint-frontend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,5 @@ jobs:
uses: actions/checkout@v2

- name: Lint code
uses: reviewdog/action-eslint@v1
with:
reporter: github-pr-review
fail_on_error: true
workdir: 'frontend/'
run: npm i && npm run lint
working-directory: frontend
6 changes: 5 additions & 1 deletion backend/.env.dist.local
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,8 @@ CROWD_QDRANT_PORT=6333

# Enrichment settings
CROWD_ENRICHMENT_URL=
CROWD_ENRICHMENT_API_KEY=
CROWD_ENRICHMENT_API_KEY=

# EagleEye settings
CROWD_EAGLE_EYE_URL=
CROWD_EAGLE_EYE_API_KEY=
5 changes: 5 additions & 0 deletions backend/config/custom-environment-variables.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
"templateWeeklyAnalytics": "CROWD_SENDGRID_TEMPLATE_WEEKLY_ANALYTICS",
"templateIntegrationDone": "CROWD_SENDGRID_TEMPLATE_INTEGRATION_DONE",
"templateCsvExport": "CROWD_SENDGRID_TEMPLATE_CSV_EXPORT",
"templateEagleEyeDigest": "CROWD_SENDGRID_TEMPLATE_EAGLE_EYE_DIGEST",
"weeklyAnalyticsUnsubscribeGroupId": "CROWD_SENDGRID_WEEKLY_ANALYTICS_UNSUBSCRIBE_GROUP_ID"
},
"plans": {
Expand Down Expand Up @@ -135,5 +136,9 @@
"enrichment": {
"url": "CROWD_ENRICHMENT_URL",
"apiKey": "CROWD_ENRICHMENT_API_KEY"
},
"eagleEye": {
"url": "CROWD_EAGLE_EYE_URL",
"apiKey": "CROWD_EAGLE_EYE_API_KEY"
}
}
3 changes: 2 additions & 1 deletion backend/config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,6 @@
"maxRetrospectInSeconds": 3600
},
"github": {},
"enrichment": {}
"enrichment": {},
"eagleEye": {}
}
11 changes: 11 additions & 0 deletions backend/src/api/eagleEyeContent/eagleEyeActionCreate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Permissions from '../../security/permissions'
import EagleEyeActionService from '../../services/eagleEyeActionService'
import PermissionChecker from '../../services/user/permissionChecker'

export default async (req, res) => {
new PermissionChecker(req).validateHas(Permissions.values.eagleEyeActionCreate)

const payload = await new EagleEyeActionService(req).create(req.body, req.params.contentId)

await req.responseHandler.success(req, res, payload)
}
11 changes: 11 additions & 0 deletions backend/src/api/eagleEyeContent/eagleEyeActionDestroy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Permissions from '../../security/permissions'
import EagleEyeActionService from '../../services/eagleEyeActionService'
import PermissionChecker from '../../services/user/permissionChecker'

export default async (req, res) => {
new PermissionChecker(req).validateHas(Permissions.values.eagleEyeActionDestroy)

const payload = await new EagleEyeActionService(req).destroy(req.params.actionId)

await req.responseHandler.success(req, res, payload)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ import EagleEyeContentService from '../../services/eagleEyeContentService'
import PermissionChecker from '../../services/user/permissionChecker'

export default async (req, res) => {
new PermissionChecker(req).validateHas(Permissions.values.eagleEyeContentRead)
const payload = await new EagleEyeContentService(req).findAndCountAll(req.query)
new PermissionChecker(req).validateHas(Permissions.values.eagleEyeActionCreate)

if (req.query.filter && Object.keys(req.query.filter).length > 0) {
const platforms = req.query.filter.platforms ? req.query.filter.platforms.split(',') : []
const nDays = req.query.filter.nDays
track('Eagle Eye Filter', { filter: req.query.filter, platforms, nDays }, { ...req })
}
const payload = await EagleEyeContentService.reply(req.query.title, req.query.description)

track(
'Eagle Eye reply generated',
{
title: req.query.title,
description: req.query.description,
reply: payload.reply,
},
{ ...req },
)
await req.responseHandler.success(req, res, payload)
}
8 changes: 3 additions & 5 deletions backend/src/api/eagleEyeContent/eagleEyeContentSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ import EagleEyeContentService from '../../services/eagleEyeContentService'
import PermissionChecker from '../../services/user/permissionChecker'

export default async (req, res) => {
new PermissionChecker(req).validateHas(Permissions.values.eagleEyeContentSearch)

const payload = await new EagleEyeContentService(req).search(req.body)

track('EagleEyeSearch', { ...req.body }, { ...req })
new PermissionChecker(req).validateHas(Permissions.values.eagleEyeActionCreate)

const payload = await new EagleEyeContentService(req).search()
track('EagleEye backend search', { ...req.body }, { ...req })
await req.responseHandler.success(req, res, payload)
}
99 changes: 99 additions & 0 deletions backend/src/api/eagleEyeContent/eagleEyeContentTrack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import Permissions from '../../security/permissions'
import PermissionChecker from '../../services/user/permissionChecker'
import track from '../../segment/track'
import Error404 from '../../errors/Error404'

export default async (req, res) => {
new PermissionChecker(req).validateHas(Permissions.values.eagleEyeContentRead)

const event = req.body.event
const params = req.body.params

switch (event) {
case 'postClicked':
console.log('Eagle Eye post clicked', {
url: params.url,
platform: params.platform,
})
track(
'Eagle Eye post clicked',
{
url: params.url,
platform: params.platform,
},
{ ...req },
)
break
case 'generatedReply':
console.log('Eagle Eye AI reply generated', {
title: params.title,
description: params.description,
platform: params.platform,
reply: params.reply,
url: params.url,
})
track(
'Eagle Eye AI reply generated',
{
title: params.title,
description: params.description,
platform: params.platform,
reply: params.reply,
url: params.url,
},
{ ...req },
)
break
case 'generatedReplyFeedback':
console.log('Eagle Eye AI reply feedback', {
type: params.type,
title: params.title,
description: params.description,
platform: params.platform,
reply: params.reply,
url: params.url,
})
track(
'Eagle Eye AI reply feedback',
{
type: params.type,
title: params.title,
description: params.description,
platform: params.platform,
reply: params.reply,
url: params.url,
},
{ ...req },
)
break
case 'generatedReplyCopied':
console.log('Eagle Eye AI reply copied', {
title: params.title,
description: params.description,
platform: params.platform,
url: params.url,
reply: params.reply,
})
track(
'Eagle Eye AI reply copied',
{
title: params.title,
description: params.description,
platform: params.platform,
url: params.url,
reply: params.reply,
},
{ ...req },
)
break

default:
throw new Error404('en', 'erros.eagleEye.invlaidEvent')
}

const out = {
Success: true,
}

await req.responseHandler.success(req, res, out)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import EagleEyeContentService from '../../services/eagleEyeContentService'
import PermissionChecker from '../../services/user/permissionChecker'

export default async (req, res) => {
new PermissionChecker(req).validateHas(Permissions.values.eagleEyeContentEdit)
new PermissionChecker(req).validateHas(Permissions.values.eagleEyeContentCreate)

const payload = await new EagleEyeContentService(req).update(req.params.id, req.body)
const payload = await new EagleEyeContentService(req).upsert(req.body)

await req.responseHandler.success(req, res, payload)
}
11 changes: 11 additions & 0 deletions backend/src/api/eagleEyeContent/eagleEyeSettingsUpdate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Permissions from '../../security/permissions'
import EagleEyeSettingsService from '../../services/eagleEyeSettingsService'
import PermissionChecker from '../../services/user/permissionChecker'

export default async (req, res) => {
new PermissionChecker(req).validateHas(Permissions.values.eagleEyeActionCreate)

const payload = await new EagleEyeSettingsService(req).update(req.body)

await req.responseHandler.success(req, res, payload)
}
52 changes: 45 additions & 7 deletions backend/src/api/eagleEyeContent/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,59 @@
import { safeWrap } from '../../middlewares/errorMiddleware'
import { featureFlagMiddleware } from '../../middlewares/featureFlagMiddleware'
import { FeatureFlag } from '../../types/common'

export default (app) => {
app.post(
`/tenant/:tenantId/eagleEyeContent/query`,
featureFlagMiddleware(FeatureFlag.EAGLE_EYE, 'entities.eagleEye.errors.planLimitExceeded'),
safeWrap(require('./eagleEyeContentQuery').default),
)

app.post(
`/tenant/:tenantId/eagleEyeContent`,
safeWrap(require('./eagleEyeContentSearch').default),
featureFlagMiddleware(FeatureFlag.EAGLE_EYE, 'entities.eagleEye.errors.planLimitExceeded'),
safeWrap(require('./eagleEyeContentUpsert').default),
)

app.post(
`/tenant/:tenantId/eagleEyeContent/query`,
safeWrap(require('./eagleEyeContentQuery').default),
`/tenant/:tenantId/eagleEyeContent/track`,
featureFlagMiddleware(FeatureFlag.EAGLE_EYE, 'entities.eagleEye.errors.planLimitExceeded'),
safeWrap(require('./eagleEyeContentTrack').default),
)
app.put(
`/tenant/:tenantId/eagleEyeContent/:id`,
safeWrap(require('./eagleEyeContentUpdate').default),

app.get(
`/tenant/:tenantId/eagleEyeContent/reply`,
featureFlagMiddleware(FeatureFlag.EAGLE_EYE, 'entities.eagleEye.errors.planLimitExceeded'),
safeWrap(require('./eagleEyeContentReply').default),
)
app.get(`/tenant/:tenantId/eagleEyeContent`, safeWrap(require('./eagleEyeContentList').default))

app.get(
`/tenant/:tenantId/eagleEyeContent/search`,
featureFlagMiddleware(FeatureFlag.EAGLE_EYE, 'entities.eagleEye.errors.planLimitExceeded'),
safeWrap(require('./eagleEyeContentSearch').default),
)

app.get(
`/tenant/:tenantId/eagleEyeContent/:id`,
featureFlagMiddleware(FeatureFlag.EAGLE_EYE, 'entities.eagleEye.errors.planLimitExceeded'),
safeWrap(require('./eagleEyeContentFind').default),
)

app.post(
`/tenant/:tenantId/eagleEyeContent/:contentId/action`,
featureFlagMiddleware(FeatureFlag.EAGLE_EYE, 'entities.eagleEye.errors.planLimitExceeded'),
safeWrap(require('./eagleEyeActionCreate').default),
)

app.put(
`/tenant/:tenantId/eagleEyeContent/settings`,
featureFlagMiddleware(FeatureFlag.EAGLE_EYE, 'entities.eagleEye.errors.planLimitExceeded'),
safeWrap(require('./eagleEyeSettingsUpdate').default),
)

app.delete(
`/tenant/:tenantId/eagleEyeContent/:contentId/action/:actionId`,
featureFlagMiddleware(FeatureFlag.EAGLE_EYE, 'entities.eagleEye.errors.planLimitExceeded'),
safeWrap(require('./eagleEyeActionDestroy').default),
)
}
55 changes: 55 additions & 0 deletions backend/src/bin/jobs/eagleEyeEmailDigestTicks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Op } from 'sequelize'
import moment from 'moment'
import SequelizeRepository from '../../database/repositories/sequelizeRepository'
import { CrowdJob } from '../../types/jobTypes'
import { sendNodeWorkerMessage } from '../../serverless/utils/nodeWorkerSQS'
import { NodeWorkerMessageType } from '../../serverless/types/workerTypes'
import { NodeWorkerMessageBase } from '../../types/mq/nodeWorkerMessageBase'

const job: CrowdJob = {
name: 'Eagle Eye Email Digest Ticker',
// every half hour
cronTime: '*/30 * * * *',
onTrigger: async () => {
const options = await SequelizeRepository.getDefaultIRepositoryOptions()
const users = (
await options.database.user.findAll({
where: {
[Op.and]: [
{
'eagleEyeSettings.emailDigestActive': {
[Op.ne]: null,
},
},
{
'eagleEyeSettings.emailDigestActive': {
[Op.eq]: true,
},
},
],
},
include: [
{
model: options.database.tenantUser,
as: 'tenants',
},
],
})
).filter(
(u) =>
u.eagleEyeSettings &&
u.eagleEyeSettings.emailDigestActive &&
moment() > moment(u.eagleEyeSettings.emailDigest.nextEmailAt),
)

for (const user of users) {
await sendNodeWorkerMessage(user.id, {
type: NodeWorkerMessageType.NODE_MICROSERVICE,
user: user.id,
service: 'eagle-eye-email-digest',
} as NodeWorkerMessageBase)
}
},
}

export default job
2 changes: 2 additions & 0 deletions backend/src/bin/jobs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import memberScoreCoordinator from './memberScoreCoordinator'
import checkSqsQueues from './checkSqsQueues'
import refreshMaterializedViews from './refreshMaterializedViews'
import downgradeExpiredPlans from './downgradeExpiredPlans'
import eagleEyeEmailDigestTicks from './eagleEyeEmailDigestTicks'

const jobs: CrowdJob[] = [
weeklyAnalyticsEmailsCoordinator,
Expand All @@ -13,6 +14,7 @@ const jobs: CrowdJob[] = [
checkSqsQueues,
refreshMaterializedViews,
downgradeExpiredPlans,
eagleEyeEmailDigestTicks,
]

export default jobs
Loading