• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Metatavu / meta-assistant-lambda / 14080603094

08 Jan 2024 12:39PM UTC coverage: 95.509%. Remained the same
14080603094

push

github

Jdallos
Temporary hardcoded minimum billable rate added

55 of 62 branches covered (88.71%)

Branch coverage included in aggregate %.

2 of 2 new or added lines in 1 file covered. (100.0%)

4 existing lines in 1 file now uncovered.

264 of 272 relevant lines covered (97.06%)

11.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.75
/src/features/slackapi/slackapi-utils.ts
1
import { DailyCombinedData, WeeklyCombinedData, TimeRegistrations, PreviousWorkdayDates, NonProjectTime, DailyMessageData, DailyMessageResult, WeeklyMessageData, WeeklyMessageResult } from "@functions/schema";
2
import { ChatPostMessageResponse, LogLevel, WebClient } from "@slack/web-api";
5✔
3
import { Member } from "@slack/web-api/dist/response/UsersListResponse";
4
import { DateTime } from "luxon";
5✔
5
import TimeUtilities from "../generic/time-utils";
5✔
6

7
/**
8
 * Namespace for Slack API utilities
9
 */
10
namespace SlackApiUtilities {
5✔
11

12
  export const client = new WebClient(process.env.METATAVU_BOT_TOKEN, {
5✔
13
    logLevel: LogLevel.DEBUG
14
  });
15

16
  const listOfIds = process.env.STAGING_IDS ? process.env.STAGING_IDS.split(",") : undefined;
5!
17

18
  /**
19
   * Get list of slack users
20
   *
21
   * @returns Promise of slack user data
22
   */
23
  export const getSlackUsers = async (): Promise<Member[]> => {
5✔
24
    const result = await client.users.list();
16✔
25

26
    if (!result.ok) throw new Error(`Error while loading slack users list, ${result.error}`);
16✔
27

28
    return result.members;
14✔
29
  };
30

31
  /**
32
   * Create message based on specific users timebank data
33
   *
34
   * @param user timebank data
35
   * @param numberOfToday Todays number
36
   * @returns string message if id match
37
   */
38
  const constructDailyMessage = (user: DailyCombinedData, numberOfToday: number): DailyMessageData => {
5✔
39
    const { name, date, firstName, minimumBillableRate } = user;
7✔
40

41
    const displayDate = DateTime.fromISO(date).toFormat("dd.MM.yyyy");
7✔
42

43
    const {
44
      logged,
45
      loggedProject,
46
      expected,
47
      internal,
48
      billableProject,
49
      nonBillableProject
50
    } = TimeUtilities.handleTimeConversion(user);
7✔
51

52
    const {
53
      message,
54
      billableHoursPercentage
55
    } = TimeUtilities.calculateWorkedTimeAndBillableHours(user);
7✔
56

57
    const customMessage = `
7✔
58
Hi ${firstName},
59
${numberOfToday === 1 ? "Last friday" :"Yesterday"} (${displayDate}) you worked ${logged} with an expected time of ${expected}.
7!
60
${message}
61
Logged project time: ${loggedProject}, Billable project time: ${billableProject}, Non billable project time: ${nonBillableProject}, Internal time: ${internal}.
62
Your percentage of billable hours was: ${billableHoursPercentage}% ${parseInt(billableHoursPercentage) >= minimumBillableRate ? ":+1:" : ":-1:"}
7!
63
Have a great rest of the day!
64
    `;
65

66
    return {
7✔
67
      message: customMessage,
68
      name: name,
69
      displayDate: displayDate,
70
      displayLogged: logged,
71
      displayLoggedProject: loggedProject,
72
      displayExpected: expected,
73
      displayBillableProject: billableProject,
74
      displayNonBillableProject: nonBillableProject,
75
      displayInternal: internal,
76
      billableHoursPercentage: billableHoursPercentage
77
    };
78
  };
79

80
  /**
81
   * Create weekly message from users timebank data
82
   *
83
   * @param user timebank data
84
   * @param weekStart date for data
85
   * @param weekEnd date for data
86
   * @returns message
87
   */
88
  const constructWeeklySummaryMessage = (user: WeeklyCombinedData, weekStart: string, weekEnd: string): WeeklyMessageData => {
5✔
89
    const { name, firstName } = user;
9✔
90
    const week = Number(user.selectedWeek.timePeriod.split(",")[2]);
9✔
91
    // TODO: minimumBillableRate should come from the user but this needs to be updated on the back end for most users, so using this for now
92
    const minimumBillableRate = 75;
9✔
93

94
    const startDate = DateTime.fromISO(weekStart).toFormat("dd.MM.yyyy");
9✔
95
    const endDate = DateTime.fromISO(weekEnd).toFormat("dd.MM.yyyy");
9✔
96

97
    const {
98
      logged,
99
      loggedProject,
100
      expected,
101
      internal,
102
      billableProject,
103
      nonBillableProject
104
    } = TimeUtilities.handleTimeConversion(user.selectedWeek);
9✔
105

106
    const {
107
      message,
108
      billableHoursPercentage
109
    } = TimeUtilities.calculateWorkedTimeAndBillableHours(user.selectedWeek);
9✔
110

111
    const customMessage = `
9✔
112
Hi ${firstName},
113
Last week (week: ${week}, ${startDate} - ${endDate}) you worked ${logged} with an expected time of ${expected}.
114
${message}
115
Logged project time: ${loggedProject}, Billable project time: ${billableProject}, Non billable project time: ${nonBillableProject}, Internal time: ${internal}.
116
Your percentage of billable hours was: ${billableHoursPercentage}%
117
You ${+parseInt(billableHoursPercentage) >= minimumBillableRate ? `worked the target ${minimumBillableRate}% billable hours last week:+1:` : `did not work the target ${minimumBillableRate}% billable hours last week:-1:`}.
9✔
118
Have a great week!
119
    `;
120

121
    return {
9✔
122
      message: customMessage,
123
      name: name,
124
      week: week,
125
      startDate: startDate,
126
      endDate: endDate,
127
      displayLogged: logged,
128
      displayLoggedProject: loggedProject,
129
      displayExpected: expected,
130
      displayBillableProject: billableProject,
131
      displayNonBillableProject: nonBillableProject,
132
      displayInternal: internal,
133
      billableHoursPercentage: billableHoursPercentage
134
    };
135
  };
136

137
  /**
138
   * Sends given message to given slack channel
139
   *
140
   * @param channelId channel ID
141
   * @param message message to be send
142
   * @returns Promise of ChatPostMessageResponse
143
   */
144
  const sendMessage = (channelId: string, message: string): Promise<ChatPostMessageResponse> => (
5✔
145
    client.chat.postMessage({
10✔
146
      channel: channelId,
147
      text: message
148
    })
149
  );
150

151
  /**
152
   * Post a daily slack message to users
153
   *
154
   * @param dailyCombinedData list of combined timebank and slack user data
155
   * @param timeRegistrations all time registrations after yesterday
156
   * @param previousWorkDays dates and the number of today
157
   * @param nonProjectTimes all non project times
158
   */
159
  export const postDailyMessageToUsers = async (
5✔
160
    dailyCombinedData: DailyCombinedData[],
161
    timeRegistrations: TimeRegistrations[],
162
    previousWorkDays: PreviousWorkdayDates,
163
    nonProjectTimes: NonProjectTime[]
164
  ): Promise<DailyMessageResult[]> => {
165
    const { numberOfToday, yesterday, today } = previousWorkDays;
5✔
166

167
    let messageResults: DailyMessageResult[] = [];
5✔
168
    for (const userData of dailyCombinedData) {
5✔
169
      const { slackId, personId, expected } = userData;
7✔
170

171
      const isAway = TimeUtilities.checkIfUserIsAwayOrIsItFirstDayBack(timeRegistrations, personId, expected, today, nonProjectTimes);
7✔
172
      const firstDayBack= TimeUtilities.checkIfUserIsAwayOrIsItFirstDayBack(timeRegistrations, personId, expected, yesterday, nonProjectTimes);
7✔
173

174
      const message = constructDailyMessage(userData, numberOfToday);
7✔
175

176
      if (!isAway && !firstDayBack) {
7✔
177
        if (!listOfIds) {
4!
178
          messageResults.push({
4✔
179
            message: message,
180
            response: await sendMessage(slackId, message.message)
181
          });
182
        }
183
        else {
UNCOV
184
          for (const stagingid of listOfIds) {
×
UNCOV
185
            messageResults.push({
×
186
              message: message,
187
              response: await sendMessage(stagingid, message.message)
188
            });
189
          }
190
        }
191
      }
192
    }
193
    return messageResults;
5✔
194
  };
195

196
  /**
197
   * Post a weekly summary slack message to users
198
   *
199
   * @param weeklyCombinedData list of combined timebank and slack user data
200
   * @param nonProjectTimes all non project times
201
   * @param timeRegistrations all time registrations after yesterday
202
   * @param previousWorkDays dates and the number of today
203
   */
204
  export const postWeeklyMessageToUsers = async (
5✔
205
    weeklyCombinedData: WeeklyCombinedData[],
206
    timeRegistrations:TimeRegistrations[],
207
    previousWorkDays: PreviousWorkdayDates,
208
    nonProjectTimes: NonProjectTime[]
209
  ): Promise<WeeklyMessageResult[]> => {
210
    const { weekStartDate, weekEndDate } = TimeUtilities.lastWeekDateProvider();
5✔
211
    const { yesterday, today } = previousWorkDays;
5✔
212

213
    const messageResults: WeeklyMessageResult[] = [];
5✔
214

215
    for (const userData of weeklyCombinedData) {
5✔
216
      const { slackId, personId, expected } = userData;
9✔
217

218
      const isAway = TimeUtilities.checkIfUserIsAwayOrIsItFirstDayBack(timeRegistrations, personId, expected, today, nonProjectTimes);
9✔
219
      const firstDayBack = TimeUtilities.checkIfUserIsAwayOrIsItFirstDayBack(timeRegistrations, personId, expected, yesterday, nonProjectTimes);
9✔
220

221
      const message = constructWeeklySummaryMessage(userData, weekStartDate.toISODate(), weekEndDate.toISODate());
9✔
222

223
      if (!isAway && !firstDayBack) {
9✔
224
        if (!listOfIds) {
6!
225
          messageResults.push({
6✔
226
            message: message,
227
            response: await sendMessage(slackId, message.message)
228
          });
229
        } else {
UNCOV
230
          for (const stagingid of listOfIds){
×
UNCOV
231
            messageResults.push({
×
232
              message: message,
233
              response: await sendMessage(stagingid, message.message)
234
            });
235
          }
236
        }
237
      }
238
    }
239
    return messageResults;
5✔
240
  };
241
}
242

243
export default SlackApiUtilities;
5✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc