From c1ab8c03ab2d6186691b3c723c531f318ff9244b Mon Sep 17 00:00:00 2001 From: Tibet Sprague Date: Tue, 23 May 2023 16:27:50 -0700 Subject: [PATCH 1/3] Fix filtering by group on the public map --- .gitignore | 1 + api/graphql/filters.js | 3 ++- api/services/Search/forPosts.js | 11 +++++++++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 06801c2e8..9c77ff966 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ nbproject *.iml latest.dump latest.dump.* +*.sql .env.swp *.swp .nyc_output/ diff --git a/api/graphql/filters.js b/api/graphql/filters.js index f8dec59a5..ae48a927c 100644 --- a/api/graphql/filters.js +++ b/api/graphql/filters.js @@ -142,7 +142,8 @@ export const postFilter = (userId, isAdmin) => relation => { q.where('posts.active', true) // If we are loading posts through a group then groups_posts already joined, otherwise we need it - if (!relation.relatedData || relation.relatedData.parentTableName !== 'groups') { + // Also check if we already loaded groups_posts in the forPosts search code + if ((!relation.relatedData || relation.relatedData.parentTableName !== 'groups') && !q.queryContext()?.alreadyJoinedGroupPosts) { q.join('groups_posts', 'groups_posts.post_id', '=', 'posts.id') } diff --git a/api/services/Search/forPosts.js b/api/services/Search/forPosts.js index 7d9c7e8bc..193fe8f0e 100644 --- a/api/services/Search/forPosts.js +++ b/api/services/Search/forPosts.js @@ -96,9 +96,16 @@ export default function forPosts (opts) { if (opts.onlyMyGroups) { const selectIdsForMember = Group.selectIdsForMember(opts.currentUserId) qb.whereIn('groups_posts.group_id', selectIdsForMember) - } else if (opts.groupIds) { + } + + if (opts.groupIds) { qb.whereIn('groups_posts.group_id', opts.groupIds) - } else if (opts.groupSlugs && opts.groupSlugs.length > 0) { + } + + if (opts.groupSlugs && opts.groupSlugs.length > 0) { + // Make sure groups_posts is joined before we try to join groups on it + qb.join('groups_posts', 'groups_posts.post_id', '=', 'posts.id') + qb.queryContext({ alreadyJoinedGroupPosts: true }) // Track this so we don't double join in filters.js qb.join('groups', 'groups_posts.group_id', '=', 'groups.id') qb.whereIn('groups.slug', opts.groupSlugs) } From a2b6f9ceda1eebd1ce1956527a7578c19b99a7cc Mon Sep 17 00:00:00 2001 From: Tibet Sprague Date: Tue, 23 May 2023 17:35:49 -0700 Subject: [PATCH 2/3] CHANGELOG update --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38b092047..21751ca3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## Fixed +- Filtering of public map by a specific group + ## [5.5.0] - 2023-05-12 ### Added @@ -14,7 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [5.4.1] - 2023-04-11 ### Added -- Track locale in user settings +- Track locale in user settings ### Changed - Improved API docs From bb09a505421914857c6138b63d6b8263b2c9cef6 Mon Sep 17 00:00:00 2001 From: Tibet Sprague Date: Tue, 23 May 2023 18:32:51 -0700 Subject: [PATCH 3/3] Track new users joining groups any which way With mixpanel Update to version 5.5.1 --- .env.example | 1 + CHANGELOG.md | 5 +++++ api/graphql/index.js | 3 +++ api/models/Group.js | 22 ++++++++++++++++------ app.js | 2 +- lib/mixpanel.js | 21 +++++++++++++++++++++ package.json | 5 +++-- yarn.lock | 23 +++++++++++++++++++---- 8 files changed, 69 insertions(+), 13 deletions(-) create mode 100644 lib/mixpanel.js diff --git a/.env.example b/.env.example index 8376e7f87..c587e65f0 100644 --- a/.env.example +++ b/.env.example @@ -25,6 +25,7 @@ LINKEDIN_API_SECRET= MAILGUN_DOMAIN= MAILGUN_EMAIL_SALT= MAPBOX_TOKEN= +MIXPANEL_TOKEN= # When a new group is created an email will be sent to this NEW_GROUP_EMAIL= NEW_RELIC_LICENSE_KEY_DISABLED= diff --git a/CHANGELOG.md b/CHANGELOG.md index 21751ca3c..4e2a640cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## [5.5.1] - 2023-05-23 + +### Added +- Mixpanel tracking when a new member joins a group any which way + ## Fixed - Filtering of public map by a specific group diff --git a/api/graphql/index.js b/api/graphql/index.js index 9475b26ed..c4e51f173 100644 --- a/api/graphql/index.js +++ b/api/graphql/index.js @@ -4,6 +4,7 @@ import { readFileSync } from 'fs' import { join } from 'path' import setupBridge from '../../lib/graphql-bookshelf-bridge' import { presentQuerySet } from '../../lib/graphql-bookshelf-bridge/util' +import mixpanel from '../../lib/mixpanel' import { acceptGroupRelationshipInvite, acceptJoinRequest, @@ -147,6 +148,8 @@ function createSchema (expressContext) { // authenticated users // TODO: look for api_client.scope to see what an oAuthed user is allowed to access + mixpanel.people.set(userId) + allResolvers = { Query: makeAuthenticatedQueries(userId, fetchOne, fetchMany), Mutation: makeMutations(expressContext, userId, isAdmin, fetchOne), diff --git a/api/models/Group.js b/api/models/Group.js index 584685643..37c38a417 100644 --- a/api/models/Group.js +++ b/api/models/Group.js @@ -5,11 +5,13 @@ import knexPostgis from 'knex-postgis' import { clone, defaults, difference, flatten, intersection, isEmpty, map, merge, sortBy, pick, omit, omitBy, isUndefined, trim } from 'lodash' import randomstring from 'randomstring' import wkx from 'wkx' -import { LocationHelpers } from 'hylo-shared' +import mixpanel from '../../lib/mixpanel' +import { AnalyticsEvents, LocationHelpers } from 'hylo-shared' import HasSettings from './mixins/HasSettings' import findOrCreateThread from './post/findOrCreateThread' import { groupFilter } from '../graphql/filters' import { inviteGroupToGroup } from '../graphql/mutations/group.js' + import DataType, { getDataTypeForInstance, getDataTypeForModel, getModelForDataType } from './group/DataType' @@ -314,7 +316,7 @@ module.exports = bookshelf.Model.extend(merge({ const newMemberships = [] const defaultTagIds = (await GroupTag.defaults(this.id, transacting)).models.map(t => t.get('tag_id')) - for (let id of newUserIds) { + for (const id of newUserIds) { const membership = await this.memberships().create( Object.assign({}, updatedAttribs, { user_id: id, @@ -549,12 +551,13 @@ module.exports = bookshelf.Model.extend(merge({ // ******* Class methods ******** // // Background task to do additional work/tasks when new members are added to a group - async afterAddMembers({ groupId, newUserIds, reactivatedUserIds }) { + async afterAddMembers ({ groupId, newUserIds, reactivatedUserIds }) { const zapierTriggers = await ZapierTrigger.forTypeAndGroups('new_member', groupId).fetchAll() + const members = await User.query(q => q.whereIn('id', newUserIds.concat(reactivatedUserIds))).fetchAll() + if (zapierTriggers && zapierTriggers.length > 0) { const group = await Group.find(groupId) - const members = await User.query(q => q.whereIn('id', newUserIds.concat(reactivatedUserIds))).fetchAll() for (const trigger of zapierTriggers) { const response = await fetch(trigger.get('target_url'), { method: 'post', @@ -582,6 +585,13 @@ module.exports = bookshelf.Model.extend(merge({ // TODO: what to do with the response? check if succeeded or not? } } + + for (const member of members) { + mixpanel.track(AnalyticsEvents.GROUP_NEW_MEMBER, { + distinct_id: member.id, + groupId: [groupId] + }) + } }, async create (userId, data) { @@ -638,7 +648,7 @@ module.exports = bookshelf.Model.extend(merge({ await group.createStarterPosts(trx) await group.createInitialWidgets(trx) - + await group.createDefaultTopics(group.id, userId, trx) const members = await group.addMembers([userId], @@ -761,7 +771,7 @@ module.exports = bookshelf.Model.extend(merge({ ${locales[locale].CreatorEmail()}: ${creator.get('email')} ${locales[locale].CreatorName()}: ${creator.get('name')} ${locales[locale].CreatorURL()}: ${Frontend.Route.profile(creator)} - `.replace(/^\s+/gm, '').replace(/\n/g, '
\n') + `.replace(/^\s+/gm, '').replace(/\n/g, '
\n') }, { sender: { name: 'Hylobot', diff --git a/app.js b/app.js index 4aa52aeaf..180286cce 100644 --- a/app.js +++ b/app.js @@ -26,7 +26,7 @@ if (process.env.NEW_RELIC_LICENSE_KEY) { } if (process.env.ROLLBAR_SERVER_TOKEN && process.env.NODE_ENV !== 'test') { - var rollbar = require('rollbar') + const rollbar = require('rollbar') rollbar.init({ accessToken: process.env.ROLLBAR_SERVER_TOKEN, captureUncaught: true, diff --git a/lib/mixpanel.js b/lib/mixpanel.js new file mode 100644 index 000000000..fe56a4ec2 --- /dev/null +++ b/lib/mixpanel.js @@ -0,0 +1,21 @@ +const Mixpanel = require('mixpanel') + +if (process.env.MIXPANEL_TOKEN && process.env.NODE_ENV !== 'test') { + console.log("mixpanel init") + const mixpanel = Mixpanel.init(process.env.MIXPANEL_TOKEN) + module.exports = mixpanel +} else { + module.exports = { + disabled: true, + + track: () => {}, + + people: { + set: () => {} + }, + + groups: { + set: () => {} + } + } +} diff --git a/package.json b/package.json index aa504db6c..27a4b9fec 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "author": "Tibet Sprague ", "license": "GNU AFFERO GENERAL PUBLIC LICENSE v3", "private": true, - "version": "5.5.0", + "version": "5.5.1", "repository": { "type": "git", "url": "git://github.com/Hylozoic/hylo-node.git" @@ -104,7 +104,7 @@ "hast-util-to-string": "^1.0.1", "hastscript": "^3.1.0", "he": "^1.2.0", - "hylo-shared": "5.1.0", + "hylo-shared": "5.2.3", "image-size": "^0.3.5", "insane": "^2.6.2", "jade": "^1.11.0", @@ -120,6 +120,7 @@ "mime": "^1.3.4", "mime-types": "^2.1.34", "minimist": "^1.1.0", + "mixpanel": "^0.17.0", "moment-timezone": "^0.5.37", "newrelic": "^7.0.0", "node-fetch": "^2", diff --git a/yarn.lock b/yarn.lock index 69af3bbbc..ee0fcb697 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7060,6 +7060,14 @@ https-proxy-agent@0: debug "2" extend "3" +https-proxy-agent@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" + integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== + dependencies: + agent-base "6" + debug "4" + https-proxy-agent@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b" @@ -7076,10 +7084,10 @@ https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1: agent-base "6" debug "4" -hylo-shared@5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/hylo-shared/-/hylo-shared-5.1.0.tgz#e4cdb43a2d65926d195bb70e6a24b874efc03f52" - integrity sha512-YWXEs8F/Sw6yazUi6Nii8JMoLa9P5HfnUJ8B+JcyZNs3EaDbf04FVZdi1bFYMgzcl+uNNx9ebRWY+2mJPGRqoQ== +hylo-shared@5.2.3: + version "5.2.3" + resolved "https://registry.yarnpkg.com/hylo-shared/-/hylo-shared-5.2.3.tgz#3c814dfece6bedca90800f6f0b30b03cc818f0b1" + integrity sha512-OzcCMMSZ/+6msIHaJc1fWQ4l3RFkKgjwTtaoARV07WOdEhqBaErTKPhRe71Sg2LGWeLPgECxQ2S5RmRITEtHFg== dependencies: coordinate-parser "^1.0.7" html-to-text "^8.1.0" @@ -9039,6 +9047,13 @@ mixme@^0.5.1: resolved "https://registry.yarnpkg.com/mixme/-/mixme-0.5.5.tgz#bf8f67d8caf10fdb49fd23198fd1fa6d8e406627" integrity sha512-/6IupbRx32s7jjEwHcycXikJwFD5UujbVNuJFkeKLYje+92OvtuPniF6JhnFm5JCTDUhS+kYK3W/4BWYQYXz7w== +mixpanel@^0.17.0: + version "0.17.0" + resolved "https://registry.yarnpkg.com/mixpanel/-/mixpanel-0.17.0.tgz#ec57b068598c620cf039a5e504fb37c97ebfe8ce" + integrity sha512-DY5WeOy/hmkPrNiiZugJpWR0iMuOwuj1a3u0bgwB2eUFRV6oIew/pIahhpawdbNjb+Bye4a8ID3gefeNPvL81g== + dependencies: + https-proxy-agent "5.0.0" + mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"