Repository: mattermost/mattermost-plugin-github
Pull request #664 (2023-04-04 10:59:12)

[GH-378]:Fixed issue #378 'Check for webhook while subscribing'
**Summary** 

- When a user creates a subscription to a repo the plugin is now checking the repo (or parent Organization for Enterprise) to see if a webhook is in place to deliver the events.

- If no webhook is found on the GitHub side, an ephemeral post is sent to remind the user that a webhook must be set up..

- [Issue #378](https://github.com/mattermost/mattermost-plugin-github/issues/378) 

mattermost-build: Hello @Kshitij-Katiyar,

Thanks for your pull request! A Core Committer will review your pull request soon. For code contributions, you can learn more about the review process [here](https://developers.mattermost.com/contribute/getting-started/code-review/).
codecov-commenter: ## [Codecov](https://codecov.io/gh/mattermost/mattermost-plugin-github/pull/664?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mattermost) Report
Patch coverage has no change and project coverage change: **`-0.16`** :warning:
> Comparison is base [(`757c9c1`)](https://codecov.io/gh/mattermost/mattermost-plugin-github/commit/757c9c1b18713035e3216ec96b6be1453a4c7478?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mattermost) 15.54% compared to head [(`17ade21`)](https://codecov.io/gh/mattermost/mattermost-plugin-github/pull/664?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mattermost) 15.39%.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master     #664      +/-   ##
==========================================
- Coverage   15.54%   15.39%   -0.16%     
==========================================
  Files          15       15              
  Lines        5473     5527      +54     
==========================================
  Hits          851      851              
- Misses       4579     4633      +54     
  Partials       43       43              
```


| [Impacted Files](https://codecov.io/gh/mattermost/mattermost-plugin-github/pull/664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mattermost) | Coverage Δ | |
|---|---|---|
| [server/plugin/command.go](https://codecov.io/gh/mattermost/mattermost-plugin-github/pull/664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mattermost#diff-c2VydmVyL3BsdWdpbi9jb21tYW5kLmdv) | `7.58% <0.00%> (-0.58%)` | :arrow_down: |
| [server/plugin/plugin.go](https://codecov.io/gh/mattermost/mattermost-plugin-github/pull/664?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mattermost#diff-c2VydmVyL3BsdWdpbi9wbHVnaW4uZ28=) | `5.95% <0.00%> (-0.05%)` | :arrow_down: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mattermost). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mattermost)

</details>

[:umbrella: View full report in Codecov by Sentry](https://codecov.io/gh/mattermost/mattermost-plugin-github/pull/664?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mattermost).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mattermost).

mattermost-build: This PR has been automatically labelled "stale" because it hasn't had recent activity.
A core team member will check in on the status of the PR to help with questions.
Thank you for your contribution!


mattermost-build: This PR has been automatically labelled "stale" because it hasn't had recent activity.
A core team member will check in on the status of the PR to help with questions.
Thank you for your contribution!


hanzei: @Kshitij-Katiyar Would you please merge `master` into your PR?

Patch:
diff --git a/.github/workflows/issue_add.yml b/.github/workflows/issue_add.yml
new file mode 100644
index 0000000..cbd857b
--- /dev/null
+++ b/.github/workflows/issue_add.yml
@@ -0,0 +1,26 @@
+name: issue_add
+
+on:
+  issues:
+    types:
+      - opened
+
+permissions:
+  repository-projects: write
+
+jobs:
+  add-to-project:
+    if: ${{ github.repository_owner == 'mattermost' }}
+    name: Add issue to project
+    runs-on: ubuntu-latest
+    steps:
+      - uses: mattermost/github-app-installation-token-action@181cd1b8c94b158428c4facfe043d6e28c20be55
+        id: ghapp
+        with:
+          appId: "${{ vars.UNIFIED_CI_APP_ID }}"
+          installationId: "${{ vars.UNIFIED_CI_INSTALLATION_ID }}"
+          privateKey: ${{ secrets.UNIFIED_CI_PRIVATE_KEY }}
+      - uses: actions/add-to-project@31b3f3ccdc584546fc445612dec3f38ff5edb41c #v0.5.0
+        with:
+          project-url: https://github.com/orgs/mattermost/projects/9
+          github-token: ${{ steps.ghapp.outputs.token }}
diff --git a/README.md b/README.md
index 35c94db..368308f 100644
--- a/README.md
+++ b/README.md
@@ -142,7 +142,7 @@ When you’ve tested the plugin and confirmed it’s working, notify your team s
 
 ## Slash Commands
 
-* __Autocomplete slash commands__ - Explore all the available slash commands by typing `/` in the text input box - the autocomplete suggestions help by providing a format example in black text and a short description of the slash command in grey text. Visit the [executing commands](https://docs.mattermost.com/help/messaging/executing-commands.html) documentation for more details.
+* __Autocomplete slash commands__ - Explore all the available slash commands by typing `/` in the text input box - the autocomplete suggestions help by providing a format example in black text and a short description of the slash command in grey text. Visit the [documentation](https://docs.mattermost.com/channels/run-slash-commands.html) for more details.
 * __Subscribe to a repository__ - Use `/github subscriptions add` to subscribe a Mattermost channel to receive notifications for new pull requests, issues, branch creation, and more in a GitHub repository.
 
    - For instance, to post notifications for issues, issue comments, and pull requests matching the label `Help Wanted` from `mattermost/mattermost-server`, use:
diff --git a/go.mod b/go.mod
index 02bbd74..306d504 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module github.com/mattermost/mattermost-plugin-github
 
-go 1.18
+go 1.21
 
 require (
 	github.com/Masterminds/sprig/v3 v3.2.2
@@ -12,7 +12,7 @@ require (
 	github.com/microcosm-cc/bluemonday v1.0.19
 	github.com/pkg/errors v0.9.1
 	github.com/stretchr/testify v1.8.0
-	golang.org/x/oauth2 v0.4.0
+	golang.org/x/oauth2 v0.7.0
 )
 
 require (
@@ -28,7 +28,7 @@ require (
 	github.com/francoispqt/gojay v1.2.13 // indirect
 	github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect
 	github.com/go-sql-driver/mysql v1.6.0 // indirect
-	github.com/golang/protobuf v1.5.2 // indirect
+	github.com/golang/protobuf v1.5.3 // indirect
 	github.com/google/go-querystring v1.1.0 // indirect
 	github.com/google/uuid v1.3.0 // indirect
 	github.com/gorilla/css v1.0.0 // indirect
@@ -65,6 +65,8 @@ require (
 	github.com/rudderlabs/analytics-go v3.3.2+incompatible // indirect
 	github.com/segmentio/backo-go v1.0.1 // indirect
 	github.com/shopspring/decimal v1.2.0 // indirect
+	github.com/shurcooL/githubv4 v0.0.0-20230424031643-6cea62ecd5a9
+	github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29 // indirect
 	github.com/sirupsen/logrus v1.9.0 // indirect
 	github.com/spf13/cast v1.3.1 // indirect
 	github.com/stretchr/objx v0.4.0 // indirect
@@ -78,14 +80,14 @@ require (
 	github.com/wiggin77/srslog v1.0.1 // indirect
 	github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
 	github.com/yuin/goldmark v1.4.13 // indirect
-	golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 // indirect
-	golang.org/x/net v0.7.0 // indirect
-	golang.org/x/sys v0.5.0 // indirect
-	golang.org/x/text v0.7.0 // indirect
+	golang.org/x/crypto v0.14.0 // indirect
+	golang.org/x/net v0.17.0 // indirect
+	golang.org/x/sys v0.13.0 // indirect
+	golang.org/x/text v0.13.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
-	google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
-	google.golang.org/grpc v1.53.0 // indirect
-	google.golang.org/protobuf v1.28.1 // indirect
+	google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
+	google.golang.org/grpc v1.56.3 // indirect
+	google.golang.org/protobuf v1.30.0 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
 	gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
diff --git a/go.sum b/go.sum
index 4c5461d..2c77d3d 100644
--- a/go.sum
+++ b/go.sum
@@ -24,6 +24,7 @@ github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb
 github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
 github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
 github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
+github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
 github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
 github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
@@ -58,12 +59,13 @@ github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200j
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
+github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
-github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -71,6 +73,7 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
 github.com/google/go-github/v41 v41.0.0 h1:HseJrM2JFf2vfiZJ8anY2hqBjdfY1Vlj/K27ueww4gg=
 github.com/google/go-github/v41 v41.0.0/go.mod h1:XgmCA5H323A9rtgExdTcnDkcqp6S30AVACCBDOonIxg=
@@ -109,6 +112,7 @@ github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
 github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
 github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE=
+github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
@@ -122,6 +126,7 @@ github.com/klauspost/cpuid/v2 v2.1.0 h1:eyi1Ad2aNJMW95zcSbmGg7Cg6cq3ADwLpMAP96d8
 github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
@@ -193,6 +198,7 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:
 github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
 github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
+github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
 github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
 github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
 github.com/rudderlabs/analytics-go v3.3.2+incompatible h1:bDajEJTYhfHjNYxbQFMA/2dHlOjyeSgxS7GPIdMZ52Q=
@@ -206,10 +212,14 @@ github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFR
 github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
 github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
 github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
+github.com/shurcooL/githubv4 v0.0.0-20230424031643-6cea62ecd5a9 h1:nCBaIs5/R0HFP5+aPW/SzFUF8z0oKuCXmuDmHWaxzjY=
+github.com/shurcooL/githubv4 v0.0.0-20230424031643-6cea62ecd5a9/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo=
 github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
 github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
 github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
 github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
+github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29 h1:B1PEwpArrNp4dkQrfxh/abbBAOZBVp0ds+fBEOUOqOc=
+github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg=
 github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
 github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
 github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
@@ -281,8 +291,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 h1:GIAS/yBem/gq2MUqgNIzUHW7cJMmx3TGZOrnyYaNQ6c=
-golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
+golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -302,14 +312,14 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
-golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M=
-golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
+golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
+golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
 golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -335,8 +345,8 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
-golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -344,8 +354,8 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
-golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -373,18 +383,18 @@ google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoA
 google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
 google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=
-google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
+google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
+google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
 google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
 google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
 google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
-google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
+google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
+google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
-google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
+google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
diff --git a/server/plugin/api.go b/server/plugin/api.go
index 97103c1..8dd636d 100644
--- a/server/plugin/api.go
+++ b/server/plugin/api.go
@@ -57,6 +57,18 @@ type PRDetails struct {
 	Reviews            []*github.PullRequestReview `json:"reviews"`
 }
 
+type FilteredNotification struct {
+	github.Notification
+	HTMLURL string `json:"html_url"`
+}
+
+type SidebarContent struct {
+	PRs         []*github.Issue         `json:"prs"`
+	Reviews     []*github.Issue         `json:"reviews"`
+	Assignments []*github.Issue         `json:"assignments"`
+	Unreads     []*FilteredNotification `json:"unreads"`
+}
+
 type Context struct {
 	Ctx    context.Context
 	UserID string
@@ -134,15 +146,11 @@ func (p *Plugin) initializeAPI() {
 
 	apiRouter.HandleFunc("/user", p.checkAuth(p.attachContext(p.getGitHubUser), ResponseTypeJSON)).Methods(http.MethodPost)
 	apiRouter.HandleFunc("/todo", p.checkAuth(p.attachUserContext(p.postToDo), ResponseTypeJSON)).Methods(http.MethodPost)
-	apiRouter.HandleFunc("/reviews", p.checkAuth(p.attachUserContext(p.getReviews), ResponseTypePlain)).Methods(http.MethodGet)
-	apiRouter.HandleFunc("/yourprs", p.checkAuth(p.attachUserContext(p.getYourPrs), ResponseTypePlain)).Methods(http.MethodGet)
 	apiRouter.HandleFunc("/prsdetails", p.checkAuth(p.attachUserContext(p.getPrsDetails), ResponseTypePlain)).Methods(http.MethodPost)
 	apiRouter.HandleFunc("/searchissues", p.checkAuth(p.attachUserContext(p.searchIssues), ResponseTypePlain)).Methods(http.MethodGet)
-	apiRouter.HandleFunc("/yourassignments", p.checkAuth(p.attachUserContext(p.getYourAssignments), ResponseTypePlain)).Methods(http.MethodGet)
 	apiRouter.HandleFunc("/createissue", p.checkAuth(p.attachUserContext(p.createIssue), ResponseTypePlain)).Methods(http.MethodPost)
 	apiRouter.HandleFunc("/createissuecomment", p.checkAuth(p.attachUserContext(p.createIssueComment), ResponseTypePlain)).Methods(http.MethodPost)
 	apiRouter.HandleFunc("/mentions", p.checkAuth(p.attachUserContext(p.getMentions), ResponseTypePlain)).Methods(http.MethodGet)
-	apiRouter.HandleFunc("/unreads", p.checkAuth(p.attachUserContext(p.getUnreads), ResponseTypePlain)).Methods(http.MethodGet)
 	apiRouter.HandleFunc("/labels", p.checkAuth(p.attachUserContext(p.getLabels), ResponseTypePlain)).Methods(http.MethodGet)
 	apiRouter.HandleFunc("/milestones", p.checkAuth(p.attachUserContext(p.getMilestones), ResponseTypePlain)).Methods(http.MethodGet)
 	apiRouter.HandleFunc("/assignees", p.checkAuth(p.attachUserContext(p.getAssignees), ResponseTypePlain)).Methods(http.MethodGet)
@@ -150,6 +158,7 @@ func (p *Plugin) initializeAPI() {
 	apiRouter.HandleFunc("/settings", p.checkAuth(p.attachUserContext(p.updateSettings), ResponseTypePlain)).Methods(http.MethodPost)
 	apiRouter.HandleFunc("/issue", p.checkAuth(p.attachUserContext(p.getIssueByNumber), ResponseTypePlain)).Methods(http.MethodGet)
 	apiRouter.HandleFunc("/pr", p.checkAuth(p.attachUserContext(p.getPrByNumber), ResponseTypePlain)).Methods(http.MethodGet)
+	apiRouter.HandleFunc("/lhs-content", p.checkAuth(p.attachUserContext(p.getSidebarContent), ResponseTypePlain)).Methods(http.MethodGet)
 
 	apiRouter.HandleFunc("/config", checkPluginRequest(p.getConfig)).Methods(http.MethodGet)
 	apiRouter.HandleFunc("/token", checkPluginRequest(p.getToken)).Methods(http.MethodGet)
@@ -651,22 +660,16 @@ func (p *Plugin) getMentions(c *UserContext, w http.ResponseWriter, r *http.Requ
 	p.writeJSON(w, result.Issues)
 }
 
-func (p *Plugin) getUnreads(c *UserContext, w http.ResponseWriter, r *http.Request) {
+func (p *Plugin) getUnreadsData(c *UserContext) []*FilteredNotification {
 	githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo)
 
 	notifications, _, err := githubClient.Activity.ListNotifications(c.Ctx, &github.NotificationListOptions{})
 	if err != nil {
 		c.Log.WithError(err).Warnf("Failed to list notifications")
-		return
-	}
-
-	type filteredNotification struct {
-		github.Notification
-
-		HTMLUrl string `json:"html_url"`
+		return nil
 	}
 
-	filteredNotifications := []*filteredNotification{}
+	filteredNotifications := []*FilteredNotification{}
 	for _, n := range notifications {
 		if n.GetReason() == notificationReasonSubscribed {
 			continue
@@ -684,45 +687,13 @@ func (p *Plugin) getUnreads(c *UserContext, w http.ResponseWriter, r *http.Reque
 			subjectURL = n.GetSubject().GetLatestCommentURL()
 		}
 
-		filteredNotifications = append(filteredNotifications, &filteredNotification{
+		filteredNotifications = append(filteredNotifications, &FilteredNotification{
 			Notification: *n,
-			HTMLUrl:      fixGithubNotificationSubjectURL(subjectURL, issueNum),
+			HTMLURL:      fixGithubNotificationSubjectURL(subjectURL, issueNum),
 		})
 	}
 
-	p.writeJSON(w, filteredNotifications)
-}
-
-func (p *Plugin) getReviews(c *UserContext, w http.ResponseWriter, r *http.Request) {
-	config := p.getConfiguration()
-
-	githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo)
-	username := c.GHInfo.GitHubUsername
-
-	query := getReviewSearchQuery(username, config.GitHubOrg)
-	result, _, err := githubClient.Search.Issues(c.Ctx, query, &github.SearchOptions{})
-	if err != nil {
-		c.Log.WithError(err).With(logger.LogContext{"query": query}).Warnf("Failed to search for review")
-		return
-	}
-
-	p.writeJSON(w, result.Issues)
-}
-
-func (p *Plugin) getYourPrs(c *UserContext, w http.ResponseWriter, r *http.Request) {
-	config := p.getConfiguration()
-
-	githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo)
-	username := c.GHInfo.GitHubUsername
-
-	query := getYourPrsSearchQuery(username, config.GitHubOrg)
-	result, _, err := githubClient.Search.Issues(c.Ctx, query, &github.SearchOptions{})
-	if err != nil {
-		c.Log.WithError(err).With(logger.LogContext{"query": query}).Warnf("Failed to search for PRs")
-		return
-	}
-
-	p.writeJSON(w, result.Issues)
+	return filteredNotifications
 }
 
 func (p *Plugin) getPrsDetails(c *UserContext, w http.ResponseWriter, r *http.Request) {
@@ -967,20 +938,39 @@ func (p *Plugin) createIssueComment(c *UserContext, w http.ResponseWriter, r *ht
 	p.writeJSON(w, result)
 }
 
-func (p *Plugin) getYourAssignments(c *UserContext, w http.ResponseWriter, r *http.Request) {
-	config := p.getConfiguration()
+func (p *Plugin) getLHSData(c *UserContext) (reviewResp []*github.Issue, assignmentResp []*github.Issue, openPRResp []*github.Issue, err error) {
+	graphQLClient := p.graphQLConnect(c.GHInfo)
 
-	githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo)
+	reviewResp, assignmentResp, openPRResp, err = graphQLClient.GetLHSData(c.Context.Ctx)
+	if err != nil {
+		return []*github.Issue{}, []*github.Issue{}, []*github.Issue{}, err
+	}
 
-	username := c.GHInfo.GitHubUsername
-	query := getYourAssigneeSearchQuery(username, config.GitHubOrg)
-	result, _, err := githubClient.Search.Issues(c.Ctx, query, &github.SearchOptions{})
+	return reviewResp, assignmentResp, openPRResp, nil
+}
+
+func (p *Plugin) getSidebarData(c *UserContext) (*SidebarContent, error) {
+	reviewResp, assignmentResp, openPRResp, err := p.getLHSData(c)
 	if err != nil {
-		c.Log.WithError(err).With(logger.LogContext{"query": query}).Warnf("Failed to search for assignments")
+		return nil, err
+	}
+
+	return &SidebarContent{
+		PRs:         openPRResp,
+		Assignments: assignmentResp,
+		Reviews:     reviewResp,
+		Unreads:     p.getUnreadsData(c),
+	}, nil
+}
+
+func (p *Plugin) getSidebarContent(c *UserContext, w http.ResponseWriter, r *http.Request) {
+	sidebarContent, err := p.getSidebarData(c)
+	if err != nil {
+		c.Log.WithError(err).Warnf("Failed to search for the sidebar data")
 		return
 	}
 
-	p.writeJSON(w, result.Issues)
+	p.writeJSON(w, sidebarContent)
 }
 
 func (p *Plugin) postToDo(c *UserContext, w http.ResponseWriter, r *http.Request) {
diff --git a/server/plugin/command.go b/server/plugin/command.go
index 4e344da..f0e4029 100644
--- a/server/plugin/command.go
+++ b/server/plugin/command.go
@@ -6,6 +6,7 @@ import (
 	"strings"
 	"unicode"
 
+	"github.com/google/go-github/v41/github"
 	"github.com/mattermost/mattermost-plugin-api/experimental/command"
 	"github.com/mattermost/mattermost-server/v6/model"
 	"github.com/mattermost/mattermost-server/v6/plugin"
@@ -25,6 +26,10 @@ const (
 	featureStars         = "stars"
 )
 
+const (
+	PerPageValue = 50
+)
+
 var validFeatures = map[string]bool{
 	featureIssueCreation: true,
 	featureIssues:        true,
@@ -274,7 +279,48 @@ func (p *Plugin) handleSubscriptionsList(_ *plugin.Context, args *model.CommandA
 	return txt
 }
 
+func (p *Plugin) checkIfConfiguredWebhookExists(ctx context.Context, githubClient *github.Client, repo, owner string) (bool, error) {
+	found := false
+	opt := &github.ListOptions{
+		PerPage: PerPageValue,
+	}
+	siteURL := *p.client.Configuration.GetConfig().ServiceSettings.SiteURL
+
+	for {
+		var githubHooks []*github.Hook
+		var githubResponse *github.Response
+		var err error
+
+		if repo == "" {
+			githubHooks, githubResponse, err = githubClient.Organizations.ListHooks(ctx, owner, opt)
+		} else {
+			githubHooks, githubResponse, err = githubClient.Repositories.ListHooks(ctx, owner, repo, opt)
+		}
+
+		if err != nil {
+			p.API.LogWarn("Not able to get the list of webhooks", "Owner", owner, "Repo", repo, "Error", err.Error())
+			return found, err
+		}
+
+		for _, hook := range githubHooks {
+			if strings.Contains(hook.Config["url"].(string), siteURL) {
+				found = true
+				break
+			}
+		}
+
+		if githubResponse.NextPage == 0 {
+			break
+		}
+		opt.Page = githubResponse.NextPage
+	}
+
+	return found, nil
+}
+
 func (p *Plugin) handleSubscribesAdd(_ *plugin.Context, args *model.CommandArgs, parameters []string, userInfo *GitHubUserInfo) string {
+	const errorNoWebhookFound = "\nNo webhook was found for this repository or organization. To create one, enter the following slash command `/github setup webhook`"
+	const errorWebhookToUser = "\nNot able to get the list of webhooks. This feature is not available for subscription to a user."
 	if len(parameters) == 0 {
 		return "Please specify a repository."
 	}
@@ -334,7 +380,20 @@ func (p *Plugin) handleSubscribesAdd(_ *plugin.Context, args *model.CommandArgs,
 			return err.Error()
 		}
 
-		return fmt.Sprintf("Successfully subscribed to organization %s.", owner)
+		subOrgMsg := fmt.Sprintf("Successfully subscribed to organization %s.", owner)
+
+		found, err := p.checkIfConfiguredWebhookExists(ctx, githubClient, repo, owner)
+		if err != nil {
+			if strings.Contains(err.Error(), "404 Not Found") {
+				return errorWebhookToUser
+			}
+			return errors.Wrap(err, "failed to get the list of webhooks").Error()
+		}
+
+		if !found {
+			subOrgMsg += errorNoWebhookFound
+		}
+		return subOrgMsg
 	}
 
 	if err := p.Subscribe(ctx, githubClient, args.UserId, owner, repo, args.ChannelId, features, flags); err != nil {
@@ -351,6 +410,18 @@ func (p *Plugin) handleSubscribesAdd(_ *plugin.Context, args *model.CommandArgs,
 		msg += "\n\n**Warning:** You subscribed to a private repository. Anyone with access to this channel will be able to read the events getting posted here."
 	}
 
+	found, err := p.checkIfConfiguredWebhookExists(ctx, githubClient, repo, owner)
+	if err != nil {
+		if strings.Contains(err.Error(), "404 Not Found") {
+			return errorWebhookToUser
+		}
+		return errors.Wrap(err, "failed to get the list of webhooks").Error()
+	}
+
+	if !found {
+		msg += errorNoWebhookFound
+	}
+
 	return msg
 }
 
diff --git a/server/plugin/flows.go b/server/plugin/flows.go
index b8e2674..87e9593 100644
--- a/server/plugin/flows.go
+++ b/server/plugin/flows.go
@@ -84,7 +84,6 @@ func (p *Plugin) NewFlowManager() *FlowManager {
 	fm.oauthFlow = fm.newFlow("oauth").WithSteps(
 		fm.stepEnterprise(),
 		fm.stepOAuthInfo(),
-		fm.stepOAuthInfo(),
 		fm.stepOAuthInput(),
 		fm.stepOAuthConnect().Terminal(),
 
@@ -474,7 +473,7 @@ You must first register the Mattermost GitHub Plugin as an authorized OAuth app.
 		"	- Authorization callback URL: `%s/oauth/complete`\n"+
 		"3. Select **Register application**\n"+
 		"4. Select **Generate a new client secret**.\n"+
-		"5. Enter your **GitHub password**. (if prompted)",
+		"5. If prompted, complete 2FA.",
 		fm.pluginURL,
 	)
 
diff --git a/server/plugin/graphql/client.go b/server/plugin/graphql/client.go
new file mode 100644
index 0000000..6563cd7
--- /dev/null
+++ b/server/plugin/graphql/client.go
@@ -0,0 +1,62 @@
+package graphql
+
+import (
+	"context"
+	"net/url"
+	"path"
+
+	pluginapi "github.com/mattermost/mattermost-plugin-api"
+	"github.com/pkg/errors"
+	"github.com/shurcooL/githubv4"
+	"golang.org/x/oauth2"
+)
+
+// Client encapsulates the third party package that communicates with Github GraphQL API
+type Client struct {
+	client   *githubv4.Client
+	org      string
+	username string
+	logger   pluginapi.LogService
+}
+
+// NewClient creates and returns Client. The third party package that queries GraphQL is initialized here.
+func NewClient(logger pluginapi.LogService, token oauth2.Token, username, orgName, enterpriseBaseURL string) *Client {
+	ts := oauth2.StaticTokenSource(&token)
+	httpClient := oauth2.NewClient(context.Background(), ts)
+	var client Client
+
+	if enterpriseBaseURL == "" {
+		client = Client{
+			username: username,
+			client:   githubv4.NewClient(httpClient),
+			logger:   logger,
+			org:      orgName,
+		}
+	} else {
+		baseURL, err := url.Parse(enterpriseBaseURL)
+		if err != nil {
+			logger.Debug("Not able to parse the URL", "Error", err.Error())
+			return nil
+		}
+
+		baseURL.Path = path.Join(baseURL.Path, "api", "graphql")
+
+		client = Client{
+			client:   githubv4.NewEnterpriseClient(baseURL.String(), httpClient),
+			username: username,
+			org:      orgName,
+			logger:   logger,
+		}
+	}
+
+	return &client
+}
+
+// executeQuery takes a query struct and sends it to Github GraphQL API via helper package.
+func (c *Client) executeQuery(ctx context.Context, qry interface{}, params map[string]interface{}) error {
+	if err := c.client.Query(ctx, qry, params); err != nil {
+		return errors.Wrap(err, "error in executing query")
+	}
+
+	return nil
+}
diff --git a/server/plugin/graphql/lhs_query.go b/server/plugin/graphql/lhs_query.go
new file mode 100644
index 0000000..29c17a7
--- /dev/null
+++ b/server/plugin/graphql/lhs_query.go
@@ -0,0 +1,91 @@
+package graphql
+
+import (
+	"github.com/shurcooL/githubv4"
+)
+
+type (
+	repositoryQuery struct {
+		Name          githubv4.String
+		NameWithOwner githubv4.String
+		URL           githubv4.URI
+	}
+
+	authorQuery struct {
+		Login githubv4.String
+	}
+
+	prSearchNodes struct {
+		PullRequest struct {
+			Body              githubv4.String
+			Number            githubv4.Int
+			AuthorAssociation githubv4.String
+			CreatedAt         githubv4.DateTime
+			UpdatedAt         githubv4.DateTime
+			Repository        repositoryQuery
+			State             githubv4.String
+			Title             githubv4.String
+			Author            authorQuery
+			URL               githubv4.URI
+		} `graphql:"... on PullRequest"`
+	}
+)
+
+type (
+	assignmentSearchNodes struct {
+		Issue struct {
+			Body              githubv4.String
+			Number            githubv4.Int
+			AuthorAssociation githubv4.String
+			CreatedAt         githubv4.DateTime
+			UpdatedAt         githubv4.DateTime
+			Repository        repositoryQuery
+			State             githubv4.String
+			Title             githubv4.String
+			Author            authorQuery
+			URL               githubv4.URI
+		} `graphql:"... on Issue"`
+
+		PullRequest struct {
+			Body              githubv4.String
+			Number            githubv4.Int
+			AuthorAssociation githubv4.String
+			CreatedAt         githubv4.DateTime
+			UpdatedAt         githubv4.DateTime
+			Repository        repositoryQuery
+			State             githubv4.String
+			Title             githubv4.String
+			Author            authorQuery
+			URL               githubv4.URI
+		} `graphql:"... on PullRequest"`
+	}
+)
+
+var mainQuery struct {
+	ReviewRequests struct {
+		IssueCount int
+		Nodes      []prSearchNodes
+		PageInfo   struct {
+			EndCursor   githubv4.String
+			HasNextPage bool
+		}
+	} `graphql:"pullRequest: search(first:100, after:$reviewsCursor, query: $prReviewQueryArg, type: ISSUE)"`
+
+	Assignments struct {
+		IssueCount int
+		Nodes      []assignmentSearchNodes
+		PageInfo   struct {
+			EndCursor   githubv4.String
+			HasNextPage bool
+		}
+	} `graphql:"assignee: search(first:100, after:$assignmentsCursor, query: $assigneeQueryArg, type: ISSUE)"`
+
+	OpenPullRequests struct {
+		IssueCount int
+		Nodes      []prSearchNodes
+		PageInfo   struct {
+			EndCursor   githubv4.String
+			HasNextPage bool
+		}
+	} `graphql:"graphql: search(first:100, after:$openPrsCursor, query: $prOpenQueryArg, type: ISSUE)"`
+}
diff --git a/server/plugin/graphql/lhs_request.go b/server/plugin/graphql/lhs_request.go
new file mode 100644
index 0000000..8c7d2db
--- /dev/null
+++ b/server/plugin/graphql/lhs_request.go
@@ -0,0 +1,128 @@
+package graphql
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/google/go-github/v41/github"
+	"github.com/pkg/errors"
+	"github.com/shurcooL/githubv4"
+)
+
+const (
+	queryParamReviewsCursor     = "reviewsCursor"
+	queryParamAssignmentsCursor = "assignmentsCursor"
+	queryParamOpenPRsCursor     = "openPrsCursor"
+
+	queryParamOpenPRQueryArg   = "prOpenQueryArg"
+	queryParamReviewPRQueryArg = "prReviewQueryArg"
+	queryParamAssigneeQueryArg = "assigneeQueryArg"
+)
+
+func (c *Client) GetLHSData(ctx context.Context) ([]*github.Issue, []*github.Issue, []*github.Issue, error) {
+	params := map[string]interface{}{
+		queryParamOpenPRQueryArg:    githubv4.String(fmt.Sprintf("author:%s is:pr is:%s archived:false", c.username, githubv4.PullRequestStateOpen)),
+		queryParamReviewPRQueryArg:  githubv4.String(fmt.Sprintf("review-requested:%s is:pr is:%s archived:false", c.username, githubv4.PullRequestStateOpen)),
+		queryParamAssigneeQueryArg:  githubv4.String(fmt.Sprintf("assignee:%s is:%s archived:false", c.username, githubv4.PullRequestStateOpen)),
+		queryParamReviewsCursor:     (*githubv4.String)(nil),
+		queryParamAssignmentsCursor: (*githubv4.String)(nil),
+		queryParamOpenPRsCursor:     (*githubv4.String)(nil),
+	}
+
+	if c.org != "" {
+		params[queryParamOpenPRQueryArg] = githubv4.String(fmt.Sprintf("org:%s %s", c.org, params[queryParamOpenPRQueryArg]))
+		params[queryParamReviewPRQueryArg] = githubv4.String(fmt.Sprintf("org:%s %s", c.org, params[queryParamReviewPRQueryArg]))
+		params[queryParamAssigneeQueryArg] = githubv4.String(fmt.Sprintf("org:%s %s", c.org, params[queryParamAssigneeQueryArg]))
+	}
+
+	var resultReview, resultAssignee, resultOpenPR []*github.Issue
+	allReviewRequestsFetched, allAssignmentsFetched, allOpenPRsFetched := false, false, false
+
+	for {
+		if allReviewRequestsFetched && allAssignmentsFetched && allOpenPRsFetched {
+			break
+		}
+
+		if err := c.executeQuery(ctx, &mainQuery, params); err != nil {
+			return nil, nil, nil, errors.Wrap(err, "Not able to excute the query")
+		}
+
+		if !allReviewRequestsFetched {
+			for i := range mainQuery.ReviewRequests.Nodes {
+				resp := mainQuery.ReviewRequests.Nodes[i]
+				pr := getPR(&resp)
+				resultReview = append(resultReview, pr)
+			}
+
+			if !mainQuery.ReviewRequests.PageInfo.HasNextPage {
+				allReviewRequestsFetched = true
+			}
+
+			params[queryParamReviewsCursor] = githubv4.NewString(mainQuery.ReviewRequests.PageInfo.EndCursor)
+		}
+
+		if !allAssignmentsFetched {
+			for i := range mainQuery.Assignments.Nodes {
+				resp := mainQuery.Assignments.Nodes[i]
+				issue := getIssue(&resp)
+				resultAssignee = append(resultAssignee, issue)
+			}
+
+			if !mainQuery.Assignments.PageInfo.HasNextPage {
+				allAssignmentsFetched = true
+			}
+
+			params[queryParamAssignmentsCursor] = githubv4.NewString(mainQuery.Assignments.PageInfo.EndCursor)
+		}
+
+		if !allOpenPRsFetched {
+			for i := range mainQuery.OpenPullRequests.Nodes {
+				resp := mainQuery.OpenPullRequests.Nodes[i]
+				pr := getPR(&resp)
+				resultOpenPR = append(resultOpenPR, pr)
+			}
+
+			if !mainQuery.OpenPullRequests.PageInfo.HasNextPage {
+				allOpenPRsFetched = true
+			}
+
+			params[queryParamOpenPRsCursor] = githubv4.NewString(mainQuery.OpenPullRequests.PageInfo.EndCursor)
+		}
+	}
+
+	return resultReview, resultAssignee, resultOpenPR, nil
+}
+
+func getPR(prResp *prSearchNodes) *github.Issue {
+	resp := prResp.PullRequest
+
+	return getGithubIssue(resp.Number, resp.Title, resp.Author.Login, resp.Repository.URL, resp.URL, resp.CreatedAt, resp.UpdatedAt)
+}
+
+func getIssue(assignmentResp *assignmentSearchNodes) *github.Issue {
+	resp := assignmentResp.PullRequest
+
+	return getGithubIssue(resp.Number, resp.Title, resp.Author.Login, resp.Repository.URL, resp.URL, resp.CreatedAt, resp.UpdatedAt)
+}
+
+func getGithubIssue(prNumber githubv4.Int, title, login githubv4.String, repositoryURL, htmlURL githubv4.URI, createdAt, updatedAt githubv4.DateTime) *github.Issue {
+	number := int(prNumber)
+	repoURL := repositoryURL.String()
+	issuetitle := string(title)
+	userLogin := (string)(login)
+	url := htmlURL.String()
+	createdAtTime := createdAt.Time
+	updatedAtTime := updatedAt.Time
+
+	return &github.Issue{
+		Number:        &number,
+		RepositoryURL: &repoURL,
+		Title:         &issuetitle,
+		CreatedAt:     &createdAtTime,
+		UpdatedAt:     &updatedAtTime,
+		User: &github.User{
+			Login: &userLogin,
+		},
+		HTMLURL: &url,
+	}
+}
diff --git a/server/plugin/plugin.go b/server/plugin/plugin.go
index 74748ff..d3b1e3f 100644
--- a/server/plugin/plugin.go
+++ b/server/plugin/plugin.go
@@ -2,6 +2,7 @@ package plugin
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
 	"net/http"
 	"net/url"
@@ -14,6 +15,7 @@ import (
 	"github.com/google/go-github/v41/github"
 	"github.com/gorilla/mux"
 	pluginapi "github.com/mattermost/mattermost-plugin-api"
+	"github.com/mattermost/mattermost-plugin-api/experimental/bot/logger"
 	"github.com/mattermost/mattermost-plugin-api/experimental/bot/poster"
 	"github.com/mattermost/mattermost-plugin-api/experimental/telemetry"
 	"github.com/mattermost/mattermost-server/v6/model"
@@ -22,6 +24,7 @@ import (
 	"golang.org/x/oauth2"
 
 	root "github.com/mattermost/mattermost-plugin-github"
+	"github.com/mattermost/mattermost-plugin-github/server/plugin/graphql"
 )
 
 const (
@@ -158,6 +161,11 @@ func (p *Plugin) githubConnectUser(ctx context.Context, info *GitHubUserInfo) *g
 	return p.githubConnectToken(tok)
 }
 
+func (p *Plugin) graphQLConnect(info *GitHubUserInfo) *graphql.Client {
+	conf := p.getConfiguration()
+	return graphql.NewClient(p.client.Log, *info.Token, info.GitHubUsername, conf.GitHubOrg, conf.EnterpriseBaseURL)
+}
+
 func (p *Plugin) githubConnectToken(token oauth2.Token) *github.Client {
 	config := p.getConfiguration()
 
@@ -831,7 +839,7 @@ func (p *Plugin) GetToDo(ctx context.Context, username string, githubClient *git
 
 			notificationTitle := notificationSubject.GetTitle()
 			notificationURL := fixGithubNotificationSubjectURL(subjectURL, issueNum)
-			notificationContent += getToDoDisplayText(baseURL, notificationTitle, notificationURL, notificationType)
+			notificationContent += getToDoDisplayText(baseURL, notificationTitle, notificationURL, notificationType, n.GetRepository())
 		}
 
 		notificationCount++
@@ -852,7 +860,7 @@ func (p *Plugin) GetToDo(ctx context.Context, username string, githubClient *git
 		text += fmt.Sprintf("You have %v pull requests awaiting your review:\n", issueResults.GetTotal())
 
 		for _, pr := range issueResults.Issues {
-			text += getToDoDisplayText(baseURL, pr.GetTitle(), pr.GetHTMLURL(), "")
+			text += getToDoDisplayText(baseURL, pr.GetTitle(), pr.GetHTMLURL(), "", nil)
 		}
 	}
 
@@ -864,7 +872,7 @@ func (p *Plugin) GetToDo(ctx context.Context, username string, githubClient *git
 		text += fmt.Sprintf("You have %v open pull requests:\n", yourPrs.GetTotal())
 
 		for _, pr := range yourPrs.Issues {
-			text += getToDoDisplayText(baseURL, pr.GetTitle(), pr.GetHTMLURL(), "")
+			text += getToDoDisplayText(baseURL, pr.GetTitle(), pr.GetHTMLURL(), "", nil)
 		}
 	}
 
@@ -876,7 +884,7 @@ func (p *Plugin) GetToDo(ctx context.Context, username string, githubClient *git
 		text += fmt.Sprintf("You have %v assignments:\n", yourAssignments.GetTotal())
 
 		for _, assign := range yourAssignments.Issues {
-			text += getToDoDisplayText(baseURL, assign.GetTitle(), assign.GetHTMLURL(), "")
+			text += getToDoDisplayText(baseURL, assign.GetTitle(), assign.GetHTMLURL(), "", nil)
 		}
 	}
 
@@ -975,13 +983,64 @@ func (p *Plugin) isOrganizationLocked() bool {
 }
 
 func (p *Plugin) sendRefreshEvent(userID string) {
+	eventLogger := logger.New(p.API).With(logger.LogContext{
+		"userid": userID,
+	})
+
+	ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
+
+	context := &Context{
+		Ctx:    ctx,
+		UserID: userID,
+		Log:    eventLogger,
+	}
+
+	defer cancel()
+
+	info, apiErr := p.getGitHubUserInfo(context.UserID)
+	if apiErr != nil {
+		p.API.LogWarn("Failed to get github user info", "error", apiErr.Error())
+		return
+	}
+
+	userContext := &UserContext{
+		Context: *context,
+		GHInfo:  info,
+	}
+
+	sidebarContent, err := p.getSidebarData(userContext)
+	if err != nil {
+		p.API.LogWarn("Failed to get the sidebar data", "error", err.Error())
+		return
+	}
+
+	contentMap, err := sidebarContent.toMap()
+	if err != nil {
+		p.API.LogWarn("Failed to convert sidebar content to map", "error", err.Error())
+		return
+	}
+
 	p.client.Frontend.PublishWebSocketEvent(
 		wsEventRefresh,
-		nil,
+		contentMap,
 		&model.WebsocketBroadcast{UserId: userID},
 	)
 }
 
+func (s *SidebarContent) toMap() (map[string]interface{}, error) {
+	var m map[string]interface{}
+	bytes, err := json.Marshal(&s)
+	if err != nil {
+		return nil, err
+	}
+
+	if err = json.Unmarshal(bytes, &m); err != nil {
+		return nil, err
+	}
+
+	return m, nil
+}
+
 // getUsername returns the GitHub username for a given Mattermost user,
 // if the user is connected to GitHub via this plugin.
 // Otherwise it return the Mattermost username. It will be escaped via backticks.
diff --git a/server/plugin/template.go b/server/plugin/template.go
index 0c8d09d..cc48216 100644
--- a/server/plugin/template.go
+++ b/server/plugin/template.go
@@ -181,6 +181,10 @@ Labels: {{range $i, $el := .Labels -}}` + "{{- if $i}}, {{end}}[`{{ $el.Name }}`
 {{- if .Assignees }}
 Assignees: {{range $i, $el := .Assignees -}} {{- if $i}}, {{end}}{{template "user" $el}}{{end -}}
 {{- end -}}
+`))
+
+	template.Must(masterTemplate.New("newDraftPR").Funcs(funcMap).Parse(`
+{{template "repo" .Event.GetRepo}} New draft pull request {{template "pullRequest" .Event.GetPullRequest}} was opened by {{template "user" .Event.GetSender}}.
 `))
 
 	template.Must(masterTemplate.New("newPR").Funcs(funcMap).Parse(`
@@ -194,6 +198,22 @@ Assignees: {{range $i, $el := .Assignees -}} {{- if $i}}, {{end}}{{template "use
 {{- template "labels" dict "Labels" .Event.GetPullRequest.Labels "RepositoryURL" .Event.GetRepo.GetHTMLURL  }}
 {{- template "assignee" .Event.GetPullRequest }}
 
+{{.Event.GetPullRequest.GetBody | removeComments | replaceAllGitHubUsernames}}
+{{- end -}}
+{{- end }}
+`))
+
+	template.Must(masterTemplate.New("markedReadyToReviewPR").Funcs(funcMap).Parse(`
+{{ if eq .Config.Style "collapsed" -}}
+{{template "repo" .Event.GetRepo}} Pull request {{template "pullRequest" .Event.GetPullRequest}} was marked ready for review by {{template "user" .Event.GetSender}}.
+{{- else -}}
+#### {{.Event.GetPullRequest.GetTitle}}
+##### {{template "eventRepoPullRequest" .Event}}
+#new-pull-request by {{template "user" .Event.GetSender}}
+{{- if ne .Config.Style "skip-body" -}}
+{{- template "labels" dict "Labels" .Event.GetPullRequest.Labels "RepositoryURL" .Event.GetRepo.GetHTMLURL  }}
+{{- template "assignee" .Event.GetPullRequest }}
+
 {{.Event.GetPullRequest.GetBody | removeComments | replaceAllGitHubUsernames}}
 {{- end -}}
 {{- end }}
diff --git a/server/plugin/utils.go b/server/plugin/utils.go
index 5c5ae93..3faa109 100644
--- a/server/plugin/utils.go
+++ b/server/plugin/utils.go
@@ -15,6 +15,7 @@ import (
 	"strings"
 	"unicode"
 
+	"github.com/google/go-github/v41/github"
 	"github.com/pkg/errors"
 )
 
@@ -313,9 +314,17 @@ func getCodeMarkdown(user, repo, repoPath, word, lines string, isTruncated bool)
 }
 
 // getToDoDisplayText returns the text to be displayed in todo listings.
-func getToDoDisplayText(baseURL, title, url, notifType string) string {
-	owner, repo := parseOwnerAndRepo(url, baseURL)
-	repoURL := fmt.Sprintf("%s%s/%s", baseURL, owner, repo)
+func getToDoDisplayText(baseURL, title, url, notifType string, repository *github.Repository) string {
+	var owner, repo, repoURL, titlePart string
+	if repository == nil {
+		owner, repo = parseOwnerAndRepo(url, baseURL)
+		repoURL = fmt.Sprintf("%s%s/%s", baseURL, owner, repo)
+	} else {
+		owner = repository.GetOwner().GetLogin()
+		repo = repository.GetName()
+		repoURL = repository.GetHTMLURL()
+	}
+
 	repoWords := strings.Split(repo, "-")
 	if len(repo) > 20 && len(repoWords) > 1 {
 		repo = "..." + repoWords[len(repoWords)-1]
@@ -325,7 +334,11 @@ func getToDoDisplayText(baseURL, title, url, notifType string) string {
 	if len(title) > 80 {
 		title = strings.TrimSpace(title[:80]) + "..."
 	}
-	titlePart := fmt.Sprintf("[%s](%s)", title, url)
+
+	titlePart = fmt.Sprintf(": %s", title)
+	if url != "" {
+		titlePart = fmt.Sprintf(": [%s](%s)", title, url)
+	}
 
 	if notifType == "" {
 		return fmt.Sprintf("* %s %s\n", repoPart, titlePart)
diff --git a/server/plugin/webhook.go b/server/plugin/webhook.go
index 1c8f117..d9044cb 100644
--- a/server/plugin/webhook.go
+++ b/server/plugin/webhook.go
@@ -6,6 +6,7 @@ import (
 	"crypto/sha1" //nolint:gosec // GitHub webhooks are signed using sha1 https://developer.github.com/webhooks/.
 	"encoding/hex"
 	"encoding/json"
+	"html"
 	"io"
 	"net/http"
 	"strings"
@@ -18,12 +19,13 @@ import (
 )
 
 const (
-	actionOpened    = "opened"
-	actionClosed    = "closed"
-	actionReopened  = "reopened"
-	actionSubmitted = "submitted"
-	actionLabeled   = "labeled"
-	actionAssigned  = "assigned"
+	actionOpened               = "opened"
+	actionMarkedReadyForReview = "ready_for_review"
+	actionClosed               = "closed"
+	actionReopened             = "reopened"
+	actionSubmitted            = "submitted"
+	actionLabeled              = "labeled"
+	actionAssigned             = "assigned"
 
 	actionCreated = "created"
 	actionDeleted = "deleted"
@@ -351,11 +353,11 @@ func (p *Plugin) postPullRequestEvent(event *github.PullRequestEvent) {
 	}
 
 	action := event.GetAction()
-	if action != actionOpened && action != actionLabeled && action != actionClosed {
+	if action != actionOpened && action != actionMarkedReadyForReview && action != actionLabeled && action != actionClosed {
 		return
 	}
-
 	pr := event.GetPullRequest()
+	isPRInDraftState := pr.GetDraft()
 	eventLabel := event.GetLabel().GetName()
 	labels := make([]string, len(pr.Labels))
 	for i, v := range pr.Labels {
@@ -421,7 +423,11 @@ func (p *Plugin) postPullRequestEvent(event *github.PullRequestEvent) {
 		}
 
 		if action == actionOpened {
-			newPRMessage, err := renderTemplate("newPR", GetEventWithRenderConfig(event, sub))
+			prNotificationType := "newPR"
+			if isPRInDraftState {
+				prNotificationType = "newDraftPR"
+			}
+			newPRMessage, err := renderTemplate(prNotificationType, GetEventWithRenderConfig(event, sub))
 			if err != nil {
 				p.client.Log.Warn("Failed to render template", "error", err.Error())
 				return
@@ -430,6 +436,16 @@ func (p *Plugin) postPullRequestEvent(event *github.PullRequestEvent) {
 			post.Message = p.sanitizeDescription(newPRMessage)
 		}
 
+		if action == actionMarkedReadyForReview {
+			markedReadyToReviewPRMessage, err := renderTemplate("markedReadyToReviewPR", GetEventWithRenderConfig(event, sub))
+			if err != nil {
+				p.client.Log.Warn("Failed to render template", "error", err.Error())
+				return
+			}
+
+			post.Message = p.sanitizeDescription(markedReadyToReviewPRMessage)
+		}
+
 		if action == actionClosed {
 			post.Message = closedPRMessage
 		}
@@ -442,9 +458,12 @@ func (p *Plugin) postPullRequestEvent(event *github.PullRequestEvent) {
 }
 
 func (p *Plugin) sanitizeDescription(description string) string {
-	var policy = bluemonday.StrictPolicy()
-	policy.SkipElementsContent("details")
-	return strings.TrimSpace(policy.Sanitize(description))
+	if strings.Contains(description, "<details>") {
+		var policy = bluemonday.StrictPolicy()
+		policy.SkipElementsContent("details")
+		description = html.UnescapeString(policy.Sanitize(description))
+	}
+	return strings.TrimSpace(description)
 }
 
 func (p *Plugin) handlePRDescriptionMentionNotification(event *github.PullRequestEvent) {
@@ -824,7 +843,7 @@ func (p *Plugin) postPullRequestReviewEvent(event *github.PullRequestReviewEvent
 		return
 	}
 
-	switch event.GetReview().GetState() {
+	switch strings.ToUpper(event.GetReview().GetState()) {
 	case "APPROVED":
 	case "COMMENTED":
 	case "CHANGES_REQUESTED":
diff --git a/webapp/package-lock.json b/webapp/package-lock.json
index 129ae34..1aa9262 100644
--- a/webapp/package-lock.json
+++ b/webapp/package-lock.json
@@ -4204,12 +4204,6 @@
       "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
       "dev": true
     },
-    "bn.js": {
-      "version": "5.1.2",
-      "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.2.tgz",
-      "integrity": "sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA==",
-      "dev": true
-    },
     "boolbase": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@@ -4323,26 +4317,75 @@
       }
     },
     "browserify-sign": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.0.tgz",
-      "integrity": "sha512-hEZC1KEeYuoHRqhGhTy6gWrpJA3ZDjFWv0DE61643ZnOXAKJb3u7yWcrU0mMc9SwAqK1n7myPGndkp0dFG7NFA==",
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.2.tgz",
+      "integrity": "sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==",
       "dev": true,
       "requires": {
-        "bn.js": "^5.1.1",
-        "browserify-rsa": "^4.0.1",
+        "bn.js": "^5.2.1",
+        "browserify-rsa": "^4.1.0",
         "create-hash": "^1.2.0",
         "create-hmac": "^1.1.7",
-        "elliptic": "^6.5.2",
+        "elliptic": "^6.5.4",
         "inherits": "^2.0.4",
-        "parse-asn1": "^5.1.5",
-        "readable-stream": "^3.6.0",
-        "safe-buffer": "^5.2.0"
+        "parse-asn1": "^5.1.6",
+        "readable-stream": "^3.6.2",
+        "safe-buffer": "^5.2.1"
       },
       "dependencies": {
+        "asn1.js": {
+          "version": "5.4.1",
+          "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
+          "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
+          "dev": true,
+          "requires": {
+            "bn.js": "^4.0.0",
+            "inherits": "^2.0.1",
+            "minimalistic-assert": "^1.0.0",
+            "safer-buffer": "^2.1.0"
+          },
+          "dependencies": {
+            "bn.js": {
+              "version": "4.12.0",
+              "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
+              "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
+              "dev": true
+            }
+          }
+        },
+        "bn.js": {
+          "version": "5.2.1",
+          "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
+          "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==",
+          "dev": true
+        },
+        "browserify-rsa": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz",
+          "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==",
+          "dev": true,
+          "requires": {
+            "bn.js": "^5.0.0",
+            "randombytes": "^2.0.1"
+          }
+        },
+        "parse-asn1": {
+          "version": "5.1.6",
+          "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz",
+          "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==",
+          "dev": true,
+          "requires": {
+            "asn1.js": "^5.2.0",
+            "browserify-aes": "^1.0.0",
+            "evp_bytestokey": "^1.0.0",
+            "pbkdf2": "^3.0.3",
+            "safe-buffer": "^5.1.1"
+          }
+        },
         "readable-stream": {
-          "version": "3.6.0",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
-          "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+          "version": "3.6.2",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+          "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
           "dev": true,
           "requires": {
             "inherits": "^2.0.3",
@@ -10557,18 +10600,6 @@
           "dev": true,
           "optional": true
         },
-        "tough-cookie": {
-          "version": "4.1.2",
-          "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz",
-          "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==",
-          "dev": true,
-          "requires": {
-            "psl": "^1.1.33",
-            "punycode": "^2.1.1",
-            "universalify": "^0.2.0",
-            "url-parse": "^1.5.3"
-          }
-        },
         "tr46": {
           "version": "2.1.0",
           "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz",
@@ -15322,6 +15353,18 @@
         "to-no-case": "^1.0.0"
       }
     },
+    "tough-cookie": {
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
+      "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
+      "dev": true,
+      "requires": {
+        "psl": "^1.1.33",
+        "punycode": "^2.1.1",
+        "universalify": "^0.2.0",
+        "url-parse": "^1.5.3"
+      }
+    },
     "tr46": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz",
@@ -16291,9 +16334,9 @@
       }
     },
     "word-wrap": {
-      "version": "1.2.3",
-      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
-      "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
+      "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
       "dev": true
     },
     "worker-farm": {
diff --git a/webapp/src/action_types/index.js b/webapp/src/action_types/index.js
index 88da099..24480e9 100644
--- a/webapp/src/action_types/index.js
+++ b/webapp/src/action_types/index.js
@@ -5,13 +5,10 @@ import {id as pluginId} from '../manifest';
 
 export default {
     RECEIVED_REPOSITORIES: pluginId + '_received_repositories',
-    RECEIVED_REVIEWS: pluginId + '_received_reviews',
     RECEIVED_REVIEWS_DETAILS: pluginId + '_received_reviews_details',
-    RECEIVED_YOUR_PRS: pluginId + '_received_your_prs',
     RECEIVED_YOUR_PRS_DETAILS: pluginId + '_received_your_prs_details',
-    RECEIVED_YOUR_ASSIGNMENTS: pluginId + '_received_your_assignments',
+    RECEIVED_SIDEBAR_CONTENT: pluginId + '_received_sidebar_content',
     RECEIVED_MENTIONS: pluginId + '_received_mentions',
-    RECEIVED_UNREADS: pluginId + '_received_unreads',
     RECEIVED_CONNECTED: pluginId + '_received_connected',
     RECEIVED_CONFIGURATION: pluginId + '_received_configuration',
     RECEIVED_GITHUB_USER: pluginId + '_received_github_user',
diff --git a/webapp/src/actions/index.js b/webapp/src/actions/index.js
index 1788ae9..16998ec 100644
--- a/webapp/src/actions/index.js
+++ b/webapp/src/actions/index.js
@@ -42,29 +42,6 @@ function checkAndHandleNotConnected(data) {
     };
 }
 
-export function getReviews() {
-    return async (dispatch, getState) => {
-        let data;
-        try {
-            data = await Client.getReviews();
-        } catch (error) {
-            return {error};
-        }
-
-        const connected = await checkAndHandleNotConnected(data)(dispatch, getState);
-        if (!connected) {
-            return {error: data};
-        }
-
-        dispatch({
-            type: ActionTypes.RECEIVED_REVIEWS,
-            data,
-        });
-
-        return {data};
-    };
-}
-
 export function getReviewsDetails(prList) {
     return async (dispatch, getState) => {
         let data;
@@ -111,11 +88,11 @@ export function getRepos() {
     };
 }
 
-export function getYourPrs() {
+export function getSidebarContent() {
     return async (dispatch, getState) => {
         let data;
         try {
-            data = await Client.getYourPrs();
+            data = await Client.getSidebarContent();
         } catch (error) {
             return {error};
         }
@@ -126,7 +103,7 @@ export function getYourPrs() {
         }
 
         dispatch({
-            type: ActionTypes.RECEIVED_YOUR_PRS,
+            type: ActionTypes.RECEIVED_SIDEBAR_CONTENT,
             data,
         });
 
@@ -211,29 +188,6 @@ export function getMilestoneOptions(repo) {
     };
 }
 
-export function getYourAssignments() {
-    return async (dispatch, getState) => {
-        let data;
-        try {
-            data = await Client.getYourAssignments();
-        } catch (error) {
-            return {error};
-        }
-
-        const connected = await checkAndHandleNotConnected(data)(dispatch, getState);
-        if (!connected) {
-            return {error: data};
-        }
-
-        dispatch({
-            type: ActionTypes.RECEIVED_YOUR_ASSIGNMENTS,
-            data,
-        });
-
-        return {data};
-    };
-}
-
 export function getMentions() {
     return async (dispatch, getState) => {
         let data;
@@ -257,29 +211,6 @@ export function getMentions() {
     };
 }
 
-export function getUnreads() {
-    return async (dispatch, getState) => {
-        let data;
-        try {
-            data = await Client.getUnreads();
-        } catch (error) {
-            return {error};
-        }
-
-        const connected = await checkAndHandleNotConnected(data)(dispatch, getState);
-        if (!connected) {
-            return {error: data};
-        }
-
-        dispatch({
-            type: ActionTypes.RECEIVED_UNREADS,
-            data,
-        });
-
-        return {data};
-    };
-}
-
 const GITHUB_USER_GET_TIMEOUT_MILLISECONDS = 1000 * 60 * 60; // 1 hour
 
 export function getGitHubUser(userID) {
diff --git a/webapp/src/client/client.js b/webapp/src/client/client.js
index 25d6f3f..7358dbf 100644
--- a/webapp/src/client/client.js
+++ b/webapp/src/client/client.js
@@ -15,30 +15,18 @@ export default class Client {
         return this.doGet(`${this.url}/connected?reminder=${reminder}`);
     }
 
-    getReviews = async () => {
-        return this.doGet(`${this.url}/reviews`);
-    }
-
-    getYourPrs = async () => {
-        return this.doGet(`${this.url}/yourprs`);
+    getSidebarContent = async () => {
+        return this.doGet(`${this.url}/lhs-content`);
     }
 
     getPrsDetails = async (prList) => {
         return this.doPost(`${this.url}/prsdetails`, prList);
     }
 
-    getYourAssignments = async () => {
-        return this.doGet(`${this.url}/yourassignments`);
-    }
-
     getMentions = async () => {
         return this.doGet(`${this.url}/mentions`);
     }
 
-    getUnreads = async () => {
-        return this.doGet(`${this.url}/unreads`);
-    }
-
     getGitHubUser = async (userID) => {
         return this.doPost(`${this.url}/user`, {user_id: userID});
     }
diff --git a/webapp/src/components/sidebar_buttons/index.js b/webapp/src/components/sidebar_buttons/index.js
index 0ebdff1..862a1d0 100644
--- a/webapp/src/components/sidebar_buttons/index.js
+++ b/webapp/src/components/sidebar_buttons/index.js
@@ -4,7 +4,7 @@
 import {connect} from 'react-redux';
 import {bindActionCreators} from 'redux';
 
-import {getConnected, getReviews, getUnreads, getYourAssignments, getYourPrs, updateRhsState} from '../../actions';
+import {getConnected, updateRhsState, getSidebarContent} from '../../actions';
 
 import {id as pluginId} from '../../manifest';
 
@@ -14,10 +14,10 @@ function mapStateToProps(state) {
     return {
         connected: state[`plugins-${pluginId}`].connected,
         clientId: state[`plugins-${pluginId}`].clientId,
-        reviews: state[`plugins-${pluginId}`].reviews,
-        yourPrs: state[`plugins-${pluginId}`].yourPrs,
-        yourAssignments: state[`plugins-${pluginId}`].yourAssignments,
-        unreads: state[`plugins-${pluginId}`].unreads,
+        reviews: state[`plugins-${pluginId}`].sidebarContent.reviews,
+        yourPrs: state[`plugins-${pluginId}`].sidebarContent.prs,
+        yourAssignments: state[`plugins-${pluginId}`].sidebarContent.assignments,
+        unreads: state[`plugins-${pluginId}`].sidebarContent.unreads,
         enterpriseURL: state[`plugins-${pluginId}`].enterpriseURL,
         showRHSPlugin: state[`plugins-${pluginId}`].rhsPluginAction,
     };
@@ -27,11 +27,8 @@ function mapDispatchToProps(dispatch) {
     return {
         actions: bindActionCreators({
             getConnected,
-            getReviews,
-            getUnreads,
-            getYourPrs,
-            getYourAssignments,
             updateRhsState,
+            getSidebarContent,
         }, dispatch),
     };
 }
diff --git a/webapp/src/components/sidebar_buttons/sidebar_buttons.jsx b/webapp/src/components/sidebar_buttons/sidebar_buttons.jsx
index 079f9cd..63d1d5c 100644
--- a/webapp/src/components/sidebar_buttons/sidebar_buttons.jsx
+++ b/webapp/src/components/sidebar_buttons/sidebar_buttons.jsx
@@ -22,10 +22,7 @@ export default class SidebarButtons extends React.PureComponent {
         showRHSPlugin: PropTypes.func.isRequired,
         actions: PropTypes.shape({
             getConnected: PropTypes.func.isRequired,
-            getReviews: PropTypes.func.isRequired,
-            getUnreads: PropTypes.func.isRequired,
-            getYourPrs: PropTypes.func.isRequired,
-            getYourAssignments: PropTypes.func.isRequired,
+            getSidebarContent: PropTypes.func.isRequired,
             updateRhsState: PropTypes.func.isRequired,
         }).isRequired,
     };
@@ -72,12 +69,7 @@ export default class SidebarButtons extends React.PureComponent {
         }
 
         this.setState({refreshing: true});
-        await Promise.all([
-            this.props.actions.getReviews(),
-            this.props.actions.getUnreads(),
-            this.props.actions.getYourPrs(),
-            this.props.actions.getYourAssignments(),
-        ]);
+        await this.props.actions.getSidebarContent();
         this.setState({refreshing: false});
     }
 
diff --git a/webapp/src/components/sidebar_right/index.jsx b/webapp/src/components/sidebar_right/index.jsx
index 746b27b..f96a08a 100644
--- a/webapp/src/components/sidebar_right/index.jsx
+++ b/webapp/src/components/sidebar_right/index.jsx
@@ -5,46 +5,22 @@ import {connect} from 'react-redux';
 import {bindActionCreators} from 'redux';
 
 import {getReviewsDetails, getYourPrsDetails} from '../../actions';
-import {id as pluginId} from '../../manifest';
 
-import SidebarRight from './sidebar_right.jsx';
-
-function mapPrsToDetails(prs, details) {
-    if (!prs) {
-        return [];
-    }
-
-    return prs.map((pr) => {
-        let foundDetails;
-        if (details) {
-            foundDetails = details.find((prDetails) => {
-                return (pr.repository_url === prDetails.url) && (pr.number === prDetails.number);
-            });
-        }
-        if (!foundDetails) {
-            return pr;
-        }
+import {getSidebarData} from 'src/selectors';
 
-        return {
-            ...pr,
-            status: foundDetails.status,
-            mergeable: foundDetails.mergeable,
-            requestedReviewers: foundDetails.requestedReviewers,
-            reviews: foundDetails.reviews,
-        };
-    });
-}
+import SidebarRight from './sidebar_right.jsx';
 
 function mapStateToProps(state) {
+    const {username, reviews, yourPrs, yourAssignments, unreads, enterpriseURL, org, rhsState} = getSidebarData(state);
     return {
-        username: state[`plugins-${pluginId}`].username,
-        reviews: mapPrsToDetails(state[`plugins-${pluginId}`].reviews, state[`plugins-${pluginId}`].reviewsDetails),
-        yourPrs: mapPrsToDetails(state[`plugins-${pluginId}`].yourPrs, state[`plugins-${pluginId}`].yourPrsDetails),
-        yourAssignments: state[`plugins-${pluginId}`].yourAssignments,
-        unreads: state[`plugins-${pluginId}`].unreads,
-        enterpriseURL: state[`plugins-${pluginId}`].enterpriseURL,
-        org: state[`plugins-${pluginId}`].organization,
-        rhsState: state[`plugins-${pluginId}`].rhsState,
+        username,
+        reviews,
+        yourPrs,
+        yourAssignments,
+        unreads,
+        enterpriseURL,
+        org,
+        rhsState,
     };
 }
 
diff --git a/webapp/src/components/sidebar_right/sidebar_right.jsx b/webapp/src/components/sidebar_right/sidebar_right.jsx
index 162efdf..71bb121 100644
--- a/webapp/src/components/sidebar_right/sidebar_right.jsx
+++ b/webapp/src/components/sidebar_right/sidebar_right.jsx
@@ -103,37 +103,38 @@ export default class SidebarRight extends React.PureComponent {
     render() {
         const baseURL = this.props.enterpriseURL ? this.props.enterpriseURL : 'https://github.com';
         const orgQuery = this.props.org ? '+org%3A' + this.props.org : '';
+        const {yourPrs, reviews, unreads, yourAssignments, username, rhsState} = this.props;
 
         let title = '';
         let githubItems = [];
         let listUrl = '';
 
-        switch (this.props.rhsState) {
+        switch (rhsState) {
         case RHSStates.PRS:
 
-            githubItems = this.props.yourPrs;
+            githubItems = yourPrs;
             title = 'Your Open Pull Requests';
-            listUrl = baseURL + '/pulls?q=is%3Aopen+is%3Apr+author%3A' + this.props.username + '+archived%3Afalse' + orgQuery;
+            listUrl = baseURL + '/pulls?q=is%3Aopen+is%3Apr+author%3A' + username + '+archived%3Afalse' + orgQuery;
 
             break;
         case RHSStates.REVIEWS:
 
-            githubItems = this.props.reviews;
-            listUrl = baseURL + '/pulls?q=is%3Aopen+is%3Apr+review-requested%3A' + this.props.username + '+archived%3Afalse' + orgQuery;
+            githubItems = reviews;
+            listUrl = baseURL + '/pulls?q=is%3Aopen+is%3Apr+review-requested%3A' + username + '+archived%3Afalse' + orgQuery;
             title = 'Pull Requests Needing Review';
 
             break;
         case RHSStates.UNREADS:
 
-            githubItems = this.props.unreads;
+            githubItems = unreads;
             title = 'Unread Messages';
             listUrl = baseURL + '/notifications';
             break;
         case RHSStates.ASSIGNMENTS:
 
-            githubItems = this.props.yourAssignments;
+            githubItems = yourAssignments;
             title = 'Your Assignments';
-            listUrl = baseURL + '/pulls?q=is%3Aopen+archived%3Afalse+assignee%3A' + this.props.username + orgQuery;
+            listUrl = baseURL + '/pulls?q=is%3Aopen+archived%3Afalse+assignee%3A' + username + orgQuery;
             break;
         default:
             break;
diff --git a/webapp/src/reducers/index.js b/webapp/src/reducers/index.js
index 0ebcf51..39f8f29 100644
--- a/webapp/src/reducers/index.js
+++ b/webapp/src/reducers/index.js
@@ -77,16 +77,7 @@ function clientId(state = '', action) {
     }
 }
 
-function reviews(state = [], action) {
-    switch (action.type) {
-    case ActionTypes.RECEIVED_REVIEWS:
-        return action.data;
-    default:
-        return state;
-    }
-}
-
-function reviewsDetails(state = [], action) {
+function reviewDetails(state = [], action) {
     switch (action.type) {
     case ActionTypes.RECEIVED_REVIEWS_DETAILS:
         return action.data;
@@ -95,9 +86,16 @@ function reviewsDetails(state = [], action) {
     }
 }
 
-function yourPrs(state = [], action) {
+const defaultSidebarContent = {
+    reviews: [],
+    prs: [],
+    assignments: [],
+    unreads: [],
+};
+
+function sidebarContent(state = defaultSidebarContent, action) {
     switch (action.type) {
-    case ActionTypes.RECEIVED_YOUR_PRS:
+    case ActionTypes.RECEIVED_SIDEBAR_CONTENT:
         return action.data;
     default:
         return state;
@@ -113,7 +111,7 @@ function yourRepos(state = [], action) {
     }
 }
 
-function yourPrsDetails(state = [], action) {
+function yourPrDetails(state = [], action) {
     switch (action.type) {
     case ActionTypes.RECEIVED_YOUR_PRS_DETAILS:
         return action.data;
@@ -122,15 +120,6 @@ function yourPrsDetails(state = [], action) {
     }
 }
 
-function yourAssignments(state = [], action) {
-    switch (action.type) {
-    case ActionTypes.RECEIVED_YOUR_ASSIGNMENTS:
-        return action.data;
-    default:
-        return state;
-    }
-}
-
 function mentions(state = [], action) {
     switch (action.type) {
     case ActionTypes.RECEIVED_MENTIONS:
@@ -140,15 +129,6 @@ function mentions(state = [], action) {
     }
 }
 
-function unreads(state = [], action) {
-    switch (action.type) {
-    case ActionTypes.RECEIVED_UNREADS:
-        return action.data;
-    default:
-        return state;
-    }
-}
-
 function githubUsers(state = {}, action) {
     switch (action.type) {
     case ActionTypes.RECEIVED_GITHUB_USER: {
@@ -238,14 +218,10 @@ export default combineReducers({
     userSettings,
     configuration,
     clientId,
-    reviews,
-    reviewsDetails,
-    yourPrs,
+    reviewDetails,
     yourRepos,
-    yourPrsDetails,
-    yourAssignments,
+    yourPrDetails,
     mentions,
-    unreads,
     githubUsers,
     rhsPluginAction,
     rhsState,
@@ -253,4 +229,5 @@ export default combineReducers({
     createIssueModal,
     attachCommentToIssueModalVisible,
     attachCommentToIssueModalForPostId,
+    sidebarContent,
 });
diff --git a/webapp/src/selectors.js b/webapp/src/selectors.js
index a8d3f26..d6de652 100644
--- a/webapp/src/selectors.js
+++ b/webapp/src/selectors.js
@@ -1,7 +1,11 @@
 import {getConfig} from 'mattermost-redux/selectors/entities/general';
 
+import {createSelector} from 'reselect';
+
 import {id as pluginId} from './manifest';
 
+const emptyArray = [];
+
 const getPluginState = (state) => state['plugins-' + pluginId] || {};
 
 export const isEnabled = (state) => getPluginState(state).enabled;
@@ -19,4 +23,46 @@ export const getServerRoute = (state) => {
     return basePath;
 };
 
+function mapPrsToDetails(prs, details) {
+    if (!prs) {
+        return [];
+    }
+
+    return prs.map((pr) => {
+        let foundDetails;
+        if (details) {
+            foundDetails = details.find((prDetails) => {
+                return (pr.repository_url === prDetails.url) && (pr.number === prDetails.number);
+            });
+        }
+        if (!foundDetails) {
+            return pr;
+        }
+
+        return {
+            ...pr,
+            status: foundDetails.status,
+            mergeable: foundDetails.mergeable,
+            requestedReviewers: foundDetails.requestedReviewers,
+            reviews: foundDetails.reviews,
+        };
+    });
+}
+
+export const getSidebarData = createSelector(
+    getPluginState,
+    (pluginState) => {
+        const {username, sidebarContent, reviewDetails, yourPrDetails, organization, rhsState} = pluginState;
+        return {
+            username,
+            reviews: mapPrsToDetails(sidebarContent.reviews || emptyArray, reviewDetails),
+            yourPrs: mapPrsToDetails(sidebarContent.prs || emptyArray, yourPrDetails),
+            yourAssignments: sidebarContent.assignments || emptyArray,
+            unreads: sidebarContent.unreads || emptyArray,
+            org: organization,
+            rhsState,
+        };
+    },
+);
+
 export const configuration = (state) => getPluginState(state).configuration;
diff --git a/webapp/src/websocket/index.js b/webapp/src/websocket/index.js
index 15021af..7e871b6 100644
--- a/webapp/src/websocket/index.js
+++ b/webapp/src/websocket/index.js
@@ -5,15 +5,14 @@ import ActionTypes from '../action_types';
 import Constants from '../constants';
 import {
     getConnected,
-    getReviews,
-    getUnreads,
-    getYourAssignments,
-    getYourPrs,
+    getSidebarContent,
     openCreateIssueModalWithoutPost,
 } from '../actions';
 
 import {id as pluginId} from '../manifest';
 
+let timeoutId;
+const RECONNECT_JITTER_MAX_TIME_IN_SEC = 10;
 export function handleConnect(store) {
     return (msg) => {
         if (!msg.data) {
@@ -66,21 +65,28 @@ export function handleReconnect(store, reminder = false) {
     return async () => {
         const {data} = await getConnected(reminder)(store.dispatch, store.getState);
         if (data && data.connected) {
-            getReviews()(store.dispatch, store.getState);
-            getUnreads()(store.dispatch, store.getState);
-            getYourPrs()(store.dispatch, store.getState);
-            getYourAssignments()(store.dispatch, store.getState);
+            if (typeof timeoutId === 'number') {
+                clearTimeout(timeoutId);
+            }
+
+            const rand = Math.floor(Math.random() * RECONNECT_JITTER_MAX_TIME_IN_SEC) + 1;
+            timeoutId = setTimeout(() => {
+                getSidebarContent()(store.dispatch, store.getState);
+                timeoutId = undefined; //eslint-disable-line no-undefined
+            }, rand * 1000);
         }
     };
 }
 
 export function handleRefresh(store) {
-    return () => {
+    return (msg) => {
         if (store.getState()[`plugins-${pluginId}`].connected) {
-            getReviews()(store.dispatch, store.getState);
-            getUnreads()(store.dispatch, store.getState);
-            getYourPrs()(store.dispatch, store.getState);
-            getYourAssignments()(store.dispatch, store.getState);
+            const {data} = msg;
+
+            store.dispatch({
+                type: ActionTypes.RECEIVED_SIDEBAR_CONTENT,
+                data,
+            });
         }
     };
 }


Test patch:
diff --git a/e2e/playwright/package-lock.json b/e2e/playwright/package-lock.json
index f1e1649..136d252 100644
--- a/e2e/playwright/package-lock.json
+++ b/e2e/playwright/package-lock.json
@@ -2950,9 +2950,9 @@
       }
     },
     "word-wrap": {
-      "version": "1.2.3",
-      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
-      "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
+      "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
       "dev": true
     },
     "wrappy": {
diff --git a/server/plugin/api_test.go b/server/plugin/api_test.go
index c1e9ad8..e2f7d7f 100644
--- a/server/plugin/api_test.go
+++ b/server/plugin/api_test.go
@@ -88,7 +88,7 @@ func TestPlugin_ServeHTTP(t *testing.T) {
 			httpTest: httpTestString,
 			request: testutils.Request{
 				Method: http.MethodGet,
-				URL:    "/api/v1/reviews",
+				URL:    "/api/v1/lhs-content",
 				Body:   nil,
 			},
 			expectedResponse: testutils.ExpectedResponse{
diff --git a/server/plugin/utils_test.go b/server/plugin/utils_test.go
index 65ae625..61cfce3 100644
--- a/server/plugin/utils_test.go
+++ b/server/plugin/utils_test.go
@@ -4,6 +4,8 @@ import (
 	"fmt"
 	"testing"
 
+	"github.com/google/go-github/v41/github"
+	"github.com/mattermost/mattermost-server/v6/model"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -218,9 +220,10 @@ func TestInsideLink(t *testing.T) {
 
 func TestGetToDoDisplayText(t *testing.T) {
 	type input struct {
-		title     string
-		url       string
-		notifType string
+		title      string
+		url        string
+		notifType  string
+		repository *github.Repository
 	}
 	tcs := []struct {
 		name string
@@ -233,8 +236,9 @@ func TestGetToDoDisplayText(t *testing.T) {
 				"Issue title with less than 80 characters",
 				"https://github.com/mattermost/repo/issues/42",
 				"",
+				nil,
 			},
-			want: "* [mattermost/repo](https://github.com/mattermost/repo) [Issue title with less than 80 characters](https://github.com/mattermost/repo/issues/42)\n",
+			want: "* [mattermost/repo](https://github.com/mattermost/repo) : [Issue title with less than 80 characters](https://github.com/mattermost/repo/issues/42)\n",
 		},
 		{
 			name: "title longer than threshold, multi-word repo name & Issue notification type",
@@ -242,14 +246,31 @@ func TestGetToDoDisplayText(t *testing.T) {
 				"This is an issue title which has with more than 80 characters and is completely random",
 				"https://github.com/mattermost/mattermost-plugin-github/issues/42",
 				"Issue",
+				nil,
 			},
-			want: "* [mattermost/...github](https://github.com/mattermost/mattermost-plugin-github) Issue [This is an issue title which has with more than 80 characters and is completely...](https://github.com/mattermost/mattermost-plugin-github/issues/42)\n",
+			want: "* [mattermost/...github](https://github.com/mattermost/mattermost-plugin-github) Issue : [This is an issue title which has with more than 80 characters and is completely...](https://github.com/mattermost/mattermost-plugin-github/issues/42)\n",
+		},
+		{
+			name: "title longer than threshold, multi-word repo name & Issue notification type",
+			in: input{
+				"Test discussion title!",
+				"",
+				"Discussion",
+				&github.Repository{
+					HTMLURL: model.NewString("https://github.com/mattermost/mattermost-plugin-github"),
+					Owner: &github.User{
+						Login: model.NewString("mattermost"),
+					},
+					Name: model.NewString("mattermost-plugin-github"),
+				},
+			},
+			want: "* [mattermost/...github](https://github.com/mattermost/mattermost-plugin-github) Discussion : Test discussion title!\n",
 		},
 	}
 
 	for _, tc := range tcs {
 		t.Run(tc.name, func(t *testing.T) {
-			got := getToDoDisplayText("https://github.com/", tc.in.title, tc.in.url, tc.in.notifType)
+			got := getToDoDisplayText("https://github.com/", tc.in.title, tc.in.url, tc.in.notifType, tc.in.repository)
 			assert.Equal(t, tc.want, got)
 		})
 	}