Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
121 changes: 120 additions & 1 deletion QuickRepliesApp.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
IAppAccessors,
IAppInstallationContext,
IConfigurationExtend,
IEnvironmentRead,
IHttp,
Expand All @@ -9,7 +10,7 @@ import {
IRead,
} from '@rocket.chat/apps-engine/definition/accessors';
import { App } from '@rocket.chat/apps-engine/definition/App';
import { IAppInfo } from '@rocket.chat/apps-engine/definition/metadata';
import { IAppInfo, RocketChatAssociationModel, RocketChatAssociationRecord } from '@rocket.chat/apps-engine/definition/metadata';
import { QuickCommand } from './src/commands/QuickCommand';
import {
IUIKitResponse,
Expand All @@ -32,10 +33,15 @@ import {
import { ActionButton } from './src/enum/modals/common/ActionButtons';
import { ExecuteActionButtonHandler } from './src/handlers/ExecuteActionButtonHandler';
import { settings } from './src/config/settings';
import { ReplyStorage } from './src/storage/ReplyStorage';
import { getDefaultReplies } from './src/data/DefaultReplies';
import { IUser } from '@rocket.chat/apps-engine/definition/users';
import { Language } from './src/lib/Translation/translation';

export class QuickRepliesApp extends App {
private elementBuilder: ElementBuilder;
private blockBuilder: BlockBuilder;

constructor(info: IAppInfo, logger: ILogger, accessors: IAppAccessors) {
super(info, logger, accessors);
}
Expand Down Expand Up @@ -91,13 +97,119 @@ export class QuickRepliesApp extends App {
blockBuilder: this.blockBuilder,
};
}

/**
* Get the association records for tracking user initialization status
*/
private getInitAssociations(userId: string): RocketChatAssociationRecord[] {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need this methods ?

return [
new RocketChatAssociationRecord(
RocketChatAssociationModel.USER,
userId,
),
new RocketChatAssociationRecord(
RocketChatAssociationModel.MISC,
'initialized_replies'
),
];
}

/**
* Check if a user has been initialized with default replies
*/
private async isUserInitialized(user: IUser, read: IRead): Promise<boolean> {
try {
const association = this.getInitAssociations(user.id);
const result = await read.getPersistenceReader().readByAssociations(association);
return result && result.length > 0;
} catch (error) {
this.getLogger().error(`Error checking initialization status: ${error}`);
return false;
}
}

/**
* Mark a user as initialized in persistent storage
*/
private async markUserAsInitialized(user: IUser, persistence: IPersistence): Promise<void> {
try {
const association = this.getInitAssociations(user.id);
await persistence.updateByAssociations(
association,
{ initialized: true, timestamp: new Date().toISOString() },
true
);
this.getLogger().debug(`User ${user.id} marked as initialized in persistence`);
} catch (error) {
this.getLogger().error(`Error marking user as initialized: ${error}`);
}
}

/**
* Initialize default quick replies for a user who hasn't used the app before

*/
public async initializeDefaultRepliesForUser(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To much login in the root of the app can we define thing outside and use here ?

user: IUser,
read: IRead,
persistence: IPersistence
): Promise<void> {
try {
// Check if the user has already been initialized using persistent storage
if (await this.isUserInitialized(user, read)) {
this.getLogger().debug(`User ${user.id} already initialized, skipping`);
return;
}

const replyStorage = new ReplyStorage(persistence, read.getPersistenceReader());
const existingReplies = await replyStorage.getReplyForUser(user);

// Only initialize if the user doesn't have any replies yet
if (existingReplies.length === 0) {
const defaultReplies = getDefaultReplies(user.id);

for (const reply of defaultReplies) {
await replyStorage.createReply(
user,
reply.name,
reply.body,
Language.en
);
}

this.getLogger().info(`Initialized default quick replies for user: ${user.id}`);
}

await this.markUserAsInitialized(user, persistence);
} catch (error) {
this.getLogger().error(`Error initializing default replies for user: ${error}`);
}
}

public async onInstall(
context: IAppInstallationContext,
read: IRead,
http: IHttp,
persistence: IPersistence,
modify: IModify
): Promise<void> {
try {
// Initialize for the admin/installer user
await this.initializeDefaultRepliesForUser(context.user, read, persistence);
this.getLogger().info('Successfully initialized default replies for admin during installation');
} catch (error) {
this.getLogger().error(`Error in onInstall: ${error}`);
}
}

public async executeViewSubmitHandler(
context: UIKitViewSubmitInteractionContext,
read: IRead,
http: IHttp,
persistence: IPersistence,
modify: IModify,
) {

const handler = new ExecuteViewSubmitHandler(
this,
read,
Expand All @@ -109,6 +221,7 @@ export class QuickRepliesApp extends App {

return await handler.handleActions();
}

public async executeViewClosedHandler(
context: UIKitViewCloseInteractionContext,
read: IRead,
Expand All @@ -135,6 +248,9 @@ export class QuickRepliesApp extends App {
persistence: IPersistence,
modify: IModify,
): Promise<IUIKitResponse> {
// Check and initialize default replies for the user
await this.initializeDefaultRepliesForUser(context.getInteractionData().user, read, persistence);

const handler = new ExecuteBlockActionHandler(
this,
read,
Expand All @@ -154,6 +270,9 @@ export class QuickRepliesApp extends App {
persistence: IPersistence,
modify: IModify,
): Promise<IUIKitResponse> {
// Check and initialize default replies for the user
await this.initializeDefaultRepliesForUser(context.getInteractionData().user, read, persistence);

const handler = new ExecuteActionButtonHandler(
this,
read,
Expand Down
34 changes: 34 additions & 0 deletions src/data/DefaultReplies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { IReply } from '../definition/reply/IReply';

/**
* Collection of pre-built default quick replies that will be added for new users
*/
export const getDefaultReplies = (userId: string): IReply[] => {
return [
{
name: 'Greeting',
body: 'Hello! How may I assist you today?',
id: `${userId}-${(Date.now() - 10).toString(36)}`,
},
{
name: 'Acknowledgment',
body: 'Thank you for reaching out. I will get back to you shortly.',
id: `${userId}-${(Date.now() - 5).toString(36)}`,
},
{
name: 'Follow-up',
body: 'I wanted to follow up on our previous discussion. Please let me know how you\'d like to proceed.',
id: `${userId}-${Date.now().toString(36)}`,
},
{
name: 'Apology',
body: 'I sincerely apologize for any inconvenience. We are looking into this and will resolve it as soon as possible.',
id: `${userId}-${(Date.now() + 5).toString(36)}`,
},
{
name: 'Closing',
body: 'It was a pleasure assisting you. Please feel free to reach out for any further queries.',
id: `${userId}-${(Date.now() + 10).toString(36)}`,
},
];
};