Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
weblate committed May 24, 2022
2 parents 07163f8 + 64de2cd commit d15de32
Show file tree
Hide file tree
Showing 10 changed files with 272 additions and 66 deletions.
2 changes: 1 addition & 1 deletion twake/backend/node/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 8 additions & 3 deletions twake/backend/node/src/services/channels/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ import { DirectChannel } from "./entities/direct-channel";
import { ChannelActivity } from "./entities/channel-activity";
import { Observable } from "rxjs";
import { ChannelPendingEmailsListQueryParameters } from "./web/types";
import { NewUserInWorkspaceNotification, SearchChannelOptions } from "./services/channel/types";
import {
ChannelObject,
NewUserInWorkspaceNotification,
SearchChannelOptions,
} from "./services/channel/types";
import { ChannelCounterPrimaryKey } from "./entities/channel-counters";
import { UserPrimaryKey } from "../user/entities/user";
import { WorkspacePrimaryKey } from "../workspaces/entities/workspace";
Expand Down Expand Up @@ -123,6 +127,7 @@ export interface ChannelService
): Promise<UpdateResult<ChannelActivity>>;

getChannelActivity(channel: Channel): Promise<number>;
fillChannelActivities(channel: Channel[]): Promise<ChannelObject[]>;

/**
* Get the list of all default channels for the given workspace.
Expand Down Expand Up @@ -150,11 +155,11 @@ export interface ChannelService
/**
* Include users to channel itself, also generate the channel name
* @param channel
* @param context
* @param excludeUserId
*/
includeUsersInDirectChannel(
channel: Channel,
context?: WorkspaceExecutionContext,
excludeUserId: string,
): Promise<UsersIncludedChannel>;

search(pagination: Pagination, options: SearchChannelOptions): Promise<ListResult<Channel>>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,17 @@ export class ChannelServiceImpl implements ChannelService {
return result;
}

public async fillChannelActivities(channels: Channel[]): Promise<ChannelObject[]> {
const filledChannels: ChannelObject[] = [];
for (const channel of channels) {
const activity = await this.getChannelActivity(channel);
const chObj = ChannelObject.mapTo(channel);
chObj.last_activity = activity;
filledChannels.push(chObj);
}
return filledChannels;
}

async list(
pagination: Pagination,
options: ChannelListOptions,
Expand Down Expand Up @@ -642,7 +653,7 @@ export class ChannelServiceImpl implements ChannelService {

public async includeUsersInDirectChannel(
channel: Channel,
context?: WorkspaceExecutionContext,
excludeUserId?: string,
): Promise<UsersIncludedChannel> {
const channelWithUsers: UsersIncludedChannel = { users: [], ...channel };
if (isDirectChannel(channel)) {
Expand All @@ -655,7 +666,7 @@ export class ChannelServiceImpl implements ChannelService {
}
channelWithUsers.users = users;
channelWithUsers.name = users
.filter(u => u.id != context?.user?.id)
.filter(u => u.id != excludeUserId)
.map(u => u.full_name)
.join(", ");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { merge } from "lodash";
import { Channel } from "../../entities/channel";
import { ChannelActivity } from "../../entities/channel-activity";
import { ChannelMember } from "../../entities";
import { UserObject } from "../../../user/web/types";

export type NewUserInWorkspaceNotification = {
user_id: string;
Expand Down Expand Up @@ -49,7 +50,7 @@ export class ChannelObject extends Channel {
default: boolean;
type: ChannelType;
user_member: ChannelMemberObject;

users: UserObject[];
stats: ChannelStats;

constructor() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import gr from "../../../global-resolver";
import { checkCompanyAndWorkspaceForUser } from "../middleware";
import { checkUserBelongsToCompany } from "../../../../utils/company";
import { Message } from "../../../messages/entities/messages";
import { orderBy } from "lodash";

const logger = getLogger("channel.controller");

Expand Down Expand Up @@ -94,7 +95,7 @@ export class ChannelCrudController
if (request.query.include_users)
channel = await gr.services.channels.channels.includeUsersInDirectChannel(
channel,
getExecutionContext(request),
getExecutionContext(request).user.id,
);

const member = await gr.services.channels.members.get(
Expand Down Expand Up @@ -244,7 +245,7 @@ export class ChannelCrudController
if (request.query.include_users)
entityWithUsers = await gr.services.channels.channels.includeUsersInDirectChannel(
entityWithUsers,
context,
context.user.id,
);

const resultEntity = {
Expand Down Expand Up @@ -330,7 +331,9 @@ export class ChannelCrudController
if (request.query.include_users) {
entities = [];
for (const e of list.getEntities()) {
entities.push(await gr.services.channels.channels.includeUsersInDirectChannel(e, context));
entities.push(
await gr.services.channels.channels.includeUsersInDirectChannel(e, context.user.id),
);
}
} else {
entities = list.getEntities();
Expand Down Expand Up @@ -513,7 +516,7 @@ export class ChannelCrudController
channel =>
gr.services.channels.channels.includeUsersInDirectChannel(
channel,
context,
context.user.id,
) as Promise<UsersIncludedChannel>,
),
);
Expand All @@ -528,23 +531,10 @@ export class ChannelCrudController
res = res.concat(ch);
});

const activities = await Promise.all(
res.map(channel => gr.services.channels.channels.getChannelActivity(channel)),
);

const response: ChannelObject[] = [];

for (let i = 0; i < res.length; i++) {
const channel = res[i];
const chObj = ChannelObject.mapTo(channel);
chObj.last_activity = activities[i];
response.push(chObj);
}
const filledChannels = await gr.services.channels.channels.fillChannelActivities(res);

return {
resources: response.sort(
(a: ChannelObject, b: ChannelObject) => b.last_activity - a.last_activity,
),
resources: orderBy(filledChannels, "last_activity", "desc"),
};
}
}
Expand Down
39 changes: 39 additions & 0 deletions twake/backend/node/src/services/user/web/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
ListResult,
Pagination,
} from "../../../core/platform/framework/api/crud-service";
import { uniq, orderBy } from "lodash";

import { CrudController } from "../../../core/platform/services/webserver/types";
import {
Expand Down Expand Up @@ -34,6 +35,8 @@ import { getCompanyRooms, getUserRooms } from "../realtime";
import { formatCompany, getCompanyStats } from "../utils";
import { formatUser } from "../../../utils/users";
import gr from "../../global-resolver";
import { UsersIncludedChannel } from "../../channels/entities";
import { ChannelObject } from "../../channels/services/channel/types";

export class UsersCrudController
implements
Expand Down Expand Up @@ -278,6 +281,42 @@ export class UsersCrudController
status: "success",
};
}

async recent(
request: FastifyRequest<{ Params: CompanyParameters; Querystring: { limit: 100 } }>,
reply: FastifyReply,
): Promise<ResourceListResponse<UserObject>> {
const companyId = request.params.id;

let channels = await gr.services.channels.channels.getAllChannelsInWorkspace(
companyId,
"direct",
);

channels = await Promise.all(
channels.map(
channel =>
gr.services.channels.channels.includeUsersInDirectChannel(
channel,
request.currentUser.id,
) as Promise<UsersIncludedChannel>,
),
);

const filledChannels = await gr.services.channels.channels.fillChannelActivities(channels);

const users: UserObject[] = [];

for (const channel of orderBy(filledChannels, "last_activity", "desc")) {
for (const user of channel.users) {
if (user.id != request.currentUser.id) users.push(user);
}
}

return {
resources: [...uniq(users).slice(0, request.query.limit)],
};
}
}

function getExecutionContext(request: FastifyRequest): ExecutionContext {
Expand Down
8 changes: 8 additions & 0 deletions twake/backend/node/src/services/user/web/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,14 @@ const routes: FastifyPluginCallback = (fastify: FastifyInstance, options, next)
handler: usersController.deregisterUserDevice.bind(usersController),
});

// recent users the current user has interacted with
fastify.route({
method: "GET",
url: "/companies/:id/users/recent",
preValidation: [fastify.authenticateOptional],
handler: usersController.recent.bind(usersController),
});

next();
};

Expand Down
97 changes: 96 additions & 1 deletion twake/backend/node/test/e2e/users/users.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,17 @@ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from
import { init, TestPlatform } from "../setup";
import { TestDbService } from "../utils.prepare.db";
import { v1 as uuidv1 } from "uuid";
import { CompanyLimitsEnum } from "../../../src/services/user/web/types";
import { CompanyLimitsEnum, UserObject } from "../../../src/services/user/web/types";
import { Channel } from "../../../src/services/channels/entities";
import { ChannelVisibility } from "../../../src/services/channels/types";
import gr from "../../../src/services/global-resolver";
import { ResourceListResponse, Workspace } from "../../../src/utils/types";
import { ChannelSaveOptions } from "../../../src/services/channels/web/types";
import { createMessage, e2e_createThread } from "../messages/utils";
import { ChannelObject } from "../../../src/services/channels/services/channel/types";
import { deserialize } from "class-transformer";
import { ChannelUtils, get as getChannelUtils } from "../channels/utils";
import { Api } from "../utils.api";

describe("The /users API", () => {
const url = "/internal/services/users/v1";
Expand Down Expand Up @@ -550,4 +560,89 @@ describe("The /users API", () => {
});
});
});

describe("Recent contacts", () => {
it("should return list of recent contacts of user", async done => {
// api = new Api(platform);
const channelUtils = getChannelUtils(platform);

await testDbService.createDefault(platform);

const channels = [];

for (let i = 0; i < 5; i++) {
// const channel = channelUtils.getChannel();
const directChannelIn = channelUtils.getDirectChannel();

const nextUser = await testDbService.createUser(
[{ id: platform.workspace.workspace_id, company_id: platform.workspace.company_id }],
{ firstName: "FirstName" + i, lastName: "LastName" + i },
);

const members = [platform.currentUser.id, nextUser.id];
const directWorkspace: Workspace = {
company_id: platform.workspace.company_id,
workspace_id: ChannelVisibility.DIRECT,
};
await Promise.all([
// gr.services.channels.channels.save(channel, {}, getContext()),
gr.services.channels.channels.save<ChannelSaveOptions>(
directChannelIn,
{
members,
},
{
...{
workspace: platform.workspace,
user: platform.currentUser,
},
...{ workspace: directWorkspace },
},
),
]);
channels.push(directChannelIn);
}

await new Promise(resolve => setTimeout(resolve, 1000));

await e2e_createThread(
platform,
[
{
company_id: platform.workspace.company_id,
created_at: 0,
created_by: "",
id: channels[2].id,
type: "channel",
workspace_id: "direct",
},
],
createMessage({ text: "Some message" }),
);

await new Promise(resolve => setTimeout(resolve, 1000));

const jwtToken = await platform.auth.getJWTToken();

const response = await platform.app.inject({
method: "GET",
url: `${url}/companies/${platform.workspace.company_id}/users/recent`,
headers: {
authorization: `Bearer ${jwtToken}`,
},
});

expect(response.statusCode).toBe(200);

const result: ResourceListResponse<UserObject> = deserialize(
ResourceListResponse,
response.body,
);

expect(result.resources.length).toEqual(5);
expect(result.resources[0].first_name).toEqual("FirstName2");

done();
});
});
});
Loading

0 comments on commit d15de32

Please sign in to comment.