@@ -16,9 +16,18 @@ const dataAccess = require("../services/dataAccessLayer");
16
16
const { getDiscordMembers, addRoleToUser, removeRoleFromUser } = require ( "../services/discordService" ) ;
17
17
const discordDeveloperRoleId = config . get ( "discordDeveloperRoleId" ) ;
18
18
const discordMavenRoleId = config . get ( "discordMavenRoleId" ) ;
19
+ const discordMissedUpdatesRoleId = config . get ( "discordMissedUpdatesRoleId" ) ;
20
+
19
21
const userStatusModel = firestore . collection ( "usersStatus" ) ;
20
22
const usersUtils = require ( "../utils/users" ) ;
21
23
const { getUsersBasedOnFilter, fetchUser } = require ( "./users" ) ;
24
+ const { convertDaysToMilliseconds } = require ( "../utils/time" ) ;
25
+ const { chunks } = require ( "../utils/array" ) ;
26
+ const tasksModel = firestore . collection ( "tasks" ) ;
27
+ const { FIRESTORE_IN_CLAUSE_SIZE } = require ( "../constants/users" ) ;
28
+ const discordService = require ( "../services/discordService" ) ;
29
+ const { buildTasksQueryForMissedUpdates } = require ( "../utils/tasks" ) ;
30
+ const { buildProgressQueryForMissedUpdates } = require ( "../utils/progresses" ) ;
22
31
23
32
/**
24
33
*
@@ -836,6 +845,175 @@ const updateUsersWith31DaysPlusOnboarding = async () => {
836
845
}
837
846
} ;
838
847
848
+ const getMissedProgressUpdatesUsers = async ( options = { } ) => {
849
+ const { cursor, size = 500 , excludedDates = [ ] , excludedDays = [ 0 ] , dateGap = 3 } = options ;
850
+ const stats = {
851
+ tasks : 0 ,
852
+ missedUpdatesTasks : 0 ,
853
+ } ;
854
+ try {
855
+ const discordUsersPromise = discordService . getDiscordMembers ( ) ;
856
+ const missedUpdatesRoleId = discordMissedUpdatesRoleId ;
857
+
858
+ let gapWindowStart = Date . now ( ) - convertDaysToMilliseconds ( dateGap ) ;
859
+ const gapWindowEnd = Date . now ( ) ;
860
+ excludedDates . forEach ( ( timestamp ) => {
861
+ if ( timestamp > gapWindowStart && timestamp < gapWindowEnd ) {
862
+ gapWindowStart -= convertDaysToMilliseconds ( 1 ) ;
863
+ }
864
+ } ) ;
865
+
866
+ if ( excludedDays . length === 7 ) {
867
+ return { usersToAddRole : [ ] , ...stats } ;
868
+ }
869
+
870
+ for ( let i = gapWindowEnd ; i >= gapWindowStart ; i -= convertDaysToMilliseconds ( 1 ) ) {
871
+ const day = new Date ( i ) . getDay ( ) ;
872
+ if ( excludedDays . includes ( day ) ) {
873
+ gapWindowStart -= convertDaysToMilliseconds ( 1 ) ;
874
+ }
875
+ }
876
+
877
+ let taskQuery = buildTasksQueryForMissedUpdates ( gapWindowStart , size ) ;
878
+
879
+ if ( cursor ) {
880
+ const data = await tasksModel . doc ( cursor ) . get ( ) ;
881
+ if ( ! data . data ( ) ) {
882
+ return {
883
+ statusCode : 400 ,
884
+ error : "Bad Request" ,
885
+ message : `Invalid cursor: ${ cursor } ` ,
886
+ } ;
887
+ }
888
+ taskQuery = taskQuery . startAfter ( data ) ;
889
+ }
890
+
891
+ const usersMap = new Map ( ) ;
892
+ const progressCountPromise = [ ] ;
893
+ const tasksQuerySnapshot = await taskQuery . get ( ) ;
894
+
895
+ stats . tasks = tasksQuerySnapshot . size ;
896
+ tasksQuerySnapshot . forEach ( ( doc ) => {
897
+ const taskAssignee = doc . data ( ) . assignee ;
898
+ const taskId = doc . id ;
899
+
900
+ if ( usersMap . has ( taskAssignee ) ) {
901
+ const userData = usersMap . get ( taskAssignee ) ;
902
+ userData . tasksCount ++ ;
903
+ } else {
904
+ usersMap . set ( taskAssignee , {
905
+ tasksCount : 1 ,
906
+ latestProgressCount : dateGap + 1 ,
907
+ isActive : false ,
908
+ } ) ;
909
+ }
910
+ const updateTasksIdMap = async ( ) => {
911
+ const progressQuery = buildProgressQueryForMissedUpdates ( taskId , gapWindowStart , gapWindowEnd ) ;
912
+ const progressSnapshot = await progressQuery . get ( ) ;
913
+ const userData = usersMap . get ( taskAssignee ) ;
914
+ userData . latestProgressCount = Math . min ( progressSnapshot . data ( ) . count , userData . latestProgressCount ) ;
915
+
916
+ if ( userData . latestProgressCount === 0 ) {
917
+ stats . missedUpdatesTasks ++ ;
918
+ }
919
+ } ;
920
+ progressCountPromise . push ( updateTasksIdMap ( ) ) ;
921
+ } ) ;
922
+
923
+ const userIdChunks = chunks ( Array . from ( usersMap . keys ( ) ) , FIRESTORE_IN_CLAUSE_SIZE ) ;
924
+ const userStatusSnapshotPromise = userIdChunks . map (
925
+ async ( userIdList ) =>
926
+ await userStatusModel
927
+ . where ( "currentStatus.state" , "==" , userState . ACTIVE )
928
+ . where ( "userId" , "in" , userIdList )
929
+ . get ( )
930
+ ) ;
931
+ const userDetailsPromise = userIdChunks . map (
932
+ async ( userIdList ) =>
933
+ await userModel
934
+ . where ( "roles.archived" , "==" , false )
935
+ . where ( admin . firestore . FieldPath . documentId ( ) , "in" , userIdList )
936
+ . get ( )
937
+ ) ;
938
+
939
+ const userStatusChunks = await Promise . all ( userStatusSnapshotPromise ) ;
940
+
941
+ userStatusChunks . forEach ( ( userStatusList ) =>
942
+ userStatusList . forEach ( ( doc ) => {
943
+ usersMap . get ( doc . data ( ) . userId ) . isActive = true ;
944
+ } )
945
+ ) ;
946
+
947
+ const userDetailsListChunks = await Promise . all ( userDetailsPromise ) ;
948
+ userDetailsListChunks . forEach ( ( userList ) => {
949
+ userList . forEach ( ( doc ) => {
950
+ const userData = usersMap . get ( doc . id ) ;
951
+ userData . discordId = doc . data ( ) . discordId ;
952
+ } ) ;
953
+ } ) ;
954
+
955
+ const discordUserList = await Promise . all ( discordUsersPromise ) ;
956
+
957
+ const discordUserMap = new Map ( ) ;
958
+ discordUserList . forEach ( ( discordUser ) => {
959
+ const discordUserData = { isBot : ! ! discordUser . user . bot } ;
960
+ discordUser . roles . forEach ( ( roleId ) => {
961
+ switch ( roleId ) {
962
+ case discordDeveloperRoleId : {
963
+ discordUserData . isDeveloper = true ;
964
+ break ;
965
+ }
966
+ case discordMavenRoleId : {
967
+ discordUserData . isMaven = true ;
968
+ break ;
969
+ }
970
+ case missedUpdatesRoleId : {
971
+ discordUserData . hasMissedUpdatesRole = true ;
972
+ break ;
973
+ }
974
+ }
975
+ } ) ;
976
+ discordUserMap . set ( discordUser . user . id , discordUserData ) ;
977
+ } ) ;
978
+
979
+ await Promise . all ( progressCountPromise ) ;
980
+
981
+ for ( const [ userId , userData ] of usersMap . entries ( ) ) {
982
+ const discordUserData = discordUserMap . get ( userData . discordId ) ;
983
+ const isDiscordMember = ! ! discordUserData ;
984
+ const shouldAddRole =
985
+ userData . latestProgressCount === 0 &&
986
+ userData . isActive &&
987
+ isDiscordMember &&
988
+ discordUserData . isDeveloper &&
989
+ ! discordUserData . isMaven &&
990
+ ! discordUserData . isBot &&
991
+ ! discordUserData . hasMissedUpdatesRole ;
992
+
993
+ if ( ! shouldAddRole ) {
994
+ usersMap . delete ( userId ) ;
995
+ }
996
+ }
997
+
998
+ const usersToAddRole = [ ] ;
999
+ for ( const userData of usersMap . values ( ) ) {
1000
+ usersToAddRole . push ( userData . discordId ) ;
1001
+ }
1002
+ const resultDataLength = tasksQuerySnapshot . docs . length ;
1003
+ const isLast = size && resultDataLength === size ;
1004
+ const lastVisible = isLast && tasksQuerySnapshot . docs [ resultDataLength - 1 ] ;
1005
+
1006
+ if ( lastVisible ) {
1007
+ stats . cursor = lastVisible . id ;
1008
+ }
1009
+
1010
+ return { usersToAddRole, ...stats } ;
1011
+ } catch ( err ) {
1012
+ logger . error ( "Error while running the add missed roles script" , err ) ;
1013
+ throw err ;
1014
+ }
1015
+ } ;
1016
+
839
1017
const addInviteToInviteModel = async ( inviteObject ) => {
840
1018
try {
841
1019
const invite = await discordInvitesModel . add ( inviteObject ) ;
@@ -878,6 +1056,7 @@ module.exports = {
878
1056
updateUsersNicknameStatus,
879
1057
updateIdle7dUsersOnDiscord,
880
1058
updateUsersWith31DaysPlusOnboarding,
1059
+ getMissedProgressUpdatesUsers,
881
1060
getUserDiscordInvite,
882
1061
addInviteToInviteModel,
883
1062
} ;
0 commit comments