Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: threads 2.0 #1204

Merged
merged 12 commits into from
Feb 2, 2024
Merged

feat: threads 2.0 #1204

merged 12 commits into from
Feb 2, 2024

Conversation

vishalnarkhede
Copy link
Contributor

@vishalnarkhede vishalnarkhede commented Dec 19, 2023

CLA

  • I have signed the Stream CLA (required).
  • Code changes are tested

Description of the changes, What, Why and How?

So we are introducing threads 2.0, which will help SDKs build "Threads" section like slack.

Basic Usage

const client = StreamChat.getInstance('api_key');
await client.connectUser({ id: 'user_id' }, 'user_token');
await threads = await client.queryThreads({ watch: true, limit: 20, participant_limit: 5 });

for (const thread of threads) {
  console.log(thread.participants);
  console.log(thread.latestReplies);
  console.log(thread.parentMessage.text)
}

QA Tests for thread list might be helpful to understand the usage as well

Basic usage of queryThreads in sdk:

  • client.queryThreads endpoint returns list of threads sorted in descending order of latest reply date time.
  • Its important to note that you can provide watch param to this endpoint. This will subscribe the user to corresponding channels of the threads that are returned. So channel may not exist in ChannelList but user will be watching this channel due to threads endpoint.
const [threads, setThreads] = useState([]);
const [nextCursor, setNextCursor] = useState(undefined);

const loadThreads = async () => {
  const { next, threads } = await client.queryThreads();
  setThreads(threads);
  setNextCursor(next);
}

const loadNextPage = async () => {
  const { next, threads: newThreads } = await client.queryThreads({ next: nextCursor });
  setThreads((threads) => [...threads, ...newThreads]);
  setNextCursor(next);
};

// Render the threads in UI

Handling a new reply on thread

Only participants of the thread receive an event whenever there is a new reply on the thread - notification.thread_message_new

client.on('notification.thread_message_new', (event) => {
  const message = event.message;
  if (!message?.parent_id) return threads;

  const parentInThreadsIdx = threads.findIndex((thread) => thread.id === message.parent_id);
  // If the parent message is not in threads, then don't do anything.
  // Slack doesn't append a new thread dynamically to the list, only shows a notification dot.
  // But one can use `client.getThread()` to fetch the new thread.
  if (parentInThreadsIdx === -1) {
    return;
  }

  setThreads((threads) => {
    const newThreads = [...threads];
    newThreads[parentInThreadsIdx].addReply(message);
    return newThreads;
  });
});

Handling message updated on thread

Updates to messages should be handled as usual via message.updated and message.deleted event:

client.on('message.updated', handleEvent);
client.on('message.deleted', handleEvent);
client.on('reaction.new', handleEvent);
client.on('reaction.deleted', handleEvent);

const handleEvent = (event) => {
  const message = event.message;
  if (message.parent_id) {
    // check if one of the thread replies was deleted or updated
    const thread = threads.find((thread) => thread.message.id === message.parent_id);
    if (!thread) return;
    thread.updateReply(message);
  } else {
    // check if one of the thread parent messages was deleted or updated
    const thread = threads.find((thread) => thread.message.id === message.id);
    if (!thread) return;
    thread.message = formatMessage(message);
  }

  setThreads([...threads]);
};

Changelog

Copy link
Contributor

github-actions bot commented Dec 19, 2023

Size Change: +10.9 kB (+3%)

Total Size: 343 kB

Filename Size Change
dist/browser.es.js 74.1 kB +2.52 kB (+4%)
dist/browser.full-bundle.min.js 44.9 kB +804 B (+2%)
dist/browser.js 75 kB +2.54 kB (+4%)
dist/index.es.js 74.1 kB +2.51 kB (+4%)
dist/index.js 75.1 kB +2.54 kB (+3%)

compressed-size-action

tbarbugli
tbarbugli previously approved these changes Feb 2, 2024
@tbarbugli tbarbugli marked this pull request as ready for review February 2, 2024 10:15
@vishalnarkhede vishalnarkhede changed the title feat: threads 2.0 (DO NOT MERGE) feat: threads 2.0 Feb 2, 2024
oliverlaz
oliverlaz previously approved these changes Feb 2, 2024
src/client.ts Outdated Show resolved Hide resolved
src/client.ts Outdated Show resolved Hide resolved
src/client.ts Outdated Show resolved Hide resolved
src/client.ts Outdated Show resolved Hide resolved
src/client.ts Outdated Show resolved Hide resolved
} from './types';
import { addToMessageList, formatMessage } from './utils';

type ThreadReadStatus<StreamChatGenerics extends ExtendableGenerics = DefaultGenerics> = Record<
Copy link
Contributor

Choose a reason for hiding this comment

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

Where is the thread read status updated / based on what WS event?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hmm right now I am delegating this part to SDK implementation. But I will think about it for 2nd round, maybe we can handle it by default within js client.

id: string;
latestReplies: FormatMessageResponse<StreamChatGenerics>[] = [];
participants: ThreadResponse['thread_participants'] = [];
message: FormatMessageResponse<StreamChatGenerics>;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this the thread parent message?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes

Copy link
Contributor

Choose a reason for hiding this comment

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

don't we want to call it parentMessage as the thread reply contains parent_id?

Copy link
Contributor Author

@vishalnarkhede vishalnarkhede Feb 2, 2024

Choose a reason for hiding this comment

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

I actually called it parentMessage before, but somehow felt it easier to just call it a message. I mean it is assumed that thread will have 1 message and multiple replies.

But feel free to change it later during sdk integration if everyone agrees on it. :)

src/client.ts Outdated
@@ -2595,6 +2599,85 @@ export class StreamChat<StreamChatGenerics extends ExtendableGenerics = DefaultG
);
}

async queryThreads(options?: {
Copy link
Contributor

@MartinCupela MartinCupela Feb 2, 2024

Choose a reason for hiding this comment

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

Is there a plan to update client.activeChannels[key].state.threads with the latest date with this query or with any other API call in this class?

How will we then update the state of activeChannels if we let's say click on a message that should take us to that thread in a given channel?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

latest date?

Copy link
Contributor

Choose a reason for hiding this comment

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

sorry latest data - typo

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. If needed we can, didn't feel any need at the moment.

@vishalnarkhede vishalnarkhede dismissed stale reviews from oliverlaz and tbarbugli via 20b4726 February 2, 2024 13:03
@vishalnarkhede
Copy link
Contributor Author

vishalnarkhede commented Feb 2, 2024

@MartinCupela I have made the suggested changes and I am going to merge this one to unblock backend PR. But feel free to suggest more changes, happy to do them on a separate PR. During SDK integration we will need probably bunch new methods and logic for event handlign.

@vishalnarkhede vishalnarkhede merged commit 7bb64a8 into master Feb 2, 2024
5 of 6 checks passed
@vishalnarkhede vishalnarkhede deleted the threads branch February 2, 2024 13:30
@MartinCupela
Copy link
Contributor

@MartinCupela I have made the suggested changes and I am going to merge this one to unblock backend PR. But feel free to suggest more changes, happy to do them on a separate PR. During SDK integration we will need probably bunch new methods and logic for event handlign.

In general I think we will need to

  1. solve the merging of the data queried from the API to the client state
  2. WS event handling (notification.thread_message_new vs. message.new, message.deleted, message.updated,
  3. thread read state notification from BE and updates

@vishalnarkhede
Copy link
Contributor Author

@MartinCupela I have made the suggested changes and I am going to merge this one to unblock backend PR. But feel free to suggest more changes, happy to do them on a separate PR. During SDK integration we will need probably bunch new methods and logic for event handlign.

In general I think we will need to

  1. solve the merging of the data queried from the API to the client state
  2. WS event handling (notification.thread_message_new vs. message.new, message.deleted, message.updated,
  3. thread read state notification from BE and updates

Yeah thats correct.

I think one more challenging part is to build a wrapper component similar to Channel, specifically for new ThreadList component. Threads inside ThreadList will be from multiple channels, so single Channel component won't work. It should be able to provide all the necessary functions via context 😅

@vishalnarkhede vishalnarkhede restored the threads branch February 2, 2024 14:39
This was referenced Feb 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants