Commit Graph

30035 Commits

Author SHA1 Message Date
Kelv
5f0bc4557f
FEATURE: Count only approved flagged posts in user pages (#22799)
FEATURE: Only approved flags for post counters

* Why was this change necessary?
The counters for flagged posts in the user's profile and user index from
the admin view include flags that were rejected, ignored or pending
review. This introduces unnecessary noise. Also the flagged posts
counter in the user's profile includes custom flags which add further
noise to this signal.

* How does it address the problem?

* Modifying User#flags_received_count to return posts with only approved
  standard flags
* Refactoring User#number_of_flagged_posts to alias to
  User#flags_received_count
* Updating the flagged post staff counter hyperlink to navigate to a
  filtered view of that user's approved flagged posts to maintain
  consistency with the counter
* Adding system tests for the profile page to cover the flagged posts
  staff counter
2023-07-31 13:33:10 +08:00
Alan Guo Xiang Tan
2f5e66b6f8
PERF: Optimise TopicTrackingState.report query to speed up query (#22871)
In the query generated by `TopicTrackingState.report`, there are two
subqueies being executed. The first subquery fetches all the topics
that are new for a given user while the second subquery fetches all the topics with
unread posts for a given user. For the second subquery, there is a
filter `topics.updated_at >= user_stats.first_unread_at` which is used
as a performance optimisation to reduce the number of rows that PG has
to scan through the `topics` table.

However, we started to notice in production that the PG planner doesn't
always execute the filter first to reduce the number of rows that it has
to scan through. Running the following query in one of our production
instance,

```
EXPLAIN ANALYZE
SELECT
           DISTINCT topics.id as topic_id,
           u.id as user_id,
           topics.created_at,
           topics.updated_at,
           topics.highest_staff_post_number AS highest_post_number,
           last_read_post_number,
           c.id as category_id,
           c.topic_id AS category_topic_id,
           tu.notification_level,
           us.first_unread_at,
           GREATEST(
              CASE
              WHEN COALESCE(uo.new_topic_duration_minutes, 2880) = -1 THEN u.created_at
              WHEN COALESCE(uo.new_topic_duration_minutes, 2880) = -2 THEN COALESCE(
                u.previous_visit_at,u.created_at
              )
              ELSE ('2023-07-31 03:29:45.737630'::timestamp - INTERVAL '1 MINUTE' * COALESCE(uo.new_topic_duration_minutes, 2880))
              END, u.created_at, '2023-07-25 15:06:44'
           ) AS treat_as_new_topic_start_date
FROM topics
JOIN users u on u.id = 13455
JOIN user_stats AS us ON us.user_id = u.id
JOIN user_options AS uo ON uo.user_id = u.id
JOIN categories c ON c.id = topics.category_id
LEFT JOIN topic_users tu ON tu.topic_id = topics.id AND tu.user_id = u.id

WHERE u.id = 13455 AND
       topics.updated_at >= us.first_unread_at AND
      topics.archetype <> 'private_message' AND
      (("topics"."deleted_at" IS NULL AND (tu.last_read_post_number < topics.highest_staff_post_number) AND (COALESCE(tu.notification_level, 1) >= 2)) OR (1=0)) AND

      NOT (
  COALESCE((select array_agg(tag_id) from topic_tags where topic_tags.topic_id = topics.id), ARRAY[]::int[]) && ARRAY[451,452,453]
) AND

      topics.deleted_at IS NULL AND

      NOT (
        last_read_post_number IS NULL AND
        (
          topics.category_id IN (SELECT "categories"."id" FROM "categories" LEFT JOIN categories categories2 ON categories2.id = categories.parent_category_id LEFT JOIN category_users ON category_users.category_id = categories.id AND category_users.user_id = 13455 LEFT JOIN category_users category_users2 ON category_users2.category_id = categories2.id AND category_users2.user_id = 13455 WHERE ((category_users.id IS NULL AND COALESCE(category_users2.notification_level, 1) = 0) OR COALESCE(category_users.notification_level, 1) = 0))
          AND tu.notification_level <= 1
        )
      )
```

we get the following

```
                                                                                                                                                                                                                                                                                                                          QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Unique  (cost=201606.06..201608.15 rows=76 width=60) (actual time=91.279..91.294 rows=14 loops=1)
   ->  Sort  (cost=201606.06..201606.25 rows=76 width=60) (actual time=91.278..91.284 rows=14 loops=1)
         Sort Key: topics.id, topics.created_at, topics.updated_at, topics.highest_staff_post_number, tu.last_read_post_number, c.id, c.topic_id, tu.notification_level, us.first_unread_at, (GREATEST(CASE WHEN (COALESCE(uo.new_topic_duration_minutes, 2880) = '-1'::integer) THEN u.created_at WHEN (COALESCE(uo.new_topic_duration_minutes, 2880) = '-2'::integer) THEN COALESCE(u.previous_visit_at, u.created_at) ELSE ('2023-07-31 03:29:45.73763'::timestamp without time zone - ('00:01:00'::interval * (COALESCE(uo.new_topic_duration_minutes, 2880))::double precision)) END, u.created_at, '2023-07-25 15:06:44'::timestamp without time zone))
         Sort Method: quicksort  Memory: 26kB
         ->  Hash Join  (cost=97519.51..201603.69 rows=76 width=60) (actual time=87.662..91.268 rows=14 loops=1)
               Hash Cond: (topics.id = tu.topic_id)
               Join Filter: ((tu.last_read_post_number < topics.highest_staff_post_number) AND ((tu.last_read_post_number IS NOT NULL) OR (NOT (hashed SubPlan 2)) OR (tu.notification_level > 1)))
               Rows Removed by Join Filter: 10
               ->  Nested Loop  (cost=1.54..104075.36 rows=3511 width=68) (actual time=0.055..3.609 rows=548 loops=1)
                     ->  Nested Loop  (cost=1.13..25.20 rows=1 width=32) (actual time=0.027..0.033 rows=1 loops=1)
                           ->  Nested Loop  (cost=0.71..16.76 rows=1 width=28) (actual time=0.020..0.023 rows=1 loops=1)
                                 ->  Index Scan using users_pkey on users u  (cost=0.42..8.44 rows=1 width=20) (actual time=0.010..0.012 rows=1 loops=1)
                                       Index Cond: (id = 13455)
                                 ->  Index Scan using user_stats_pkey on user_stats us  (cost=0.29..8.31 rows=1 width=12) (actual time=0.008..0.010 rows=1 loops=1)
                                       Index Cond: (user_id = 13455)
                           ->  Index Scan using index_user_options_on_user_id_and_default_calendar on user_options uo  (cost=0.42..8.44 rows=1 width=8) (actual time=0.007..0.008 rows=1 loops=1)
                                 Index Cond: (user_id = 13455)
                     ->  Nested Loop  (cost=0.41..104015.12 rows=3504 width=36) (actual time=0.026..3.503 rows=548 loops=1)
                           ->  Seq Scan on categories c  (cost=0.00..13.73 rows=73 width=8) (actual time=0.003..0.039 rows=73 loops=1)
                           ->  Index Only Scan using index_topics_on_updated_at_public on topics  (cost=0.41..1424.20 rows=48 width=28) (actual time=0.012..0.046 rows=8 loops=73)
                                 Index Cond: ((updated_at >= us.first_unread_at) AND (category_id = c.id))
                                 Filter: (NOT (COALESCE((SubPlan 1), '{}'::integer[]) && '{451,452,453}'::integer[]))
                                 Heap Fetches: 553
                                 SubPlan 1
                                   ->  Aggregate  (cost=4.31..4.32 rows=1 width=32) (actual time=0.002..0.002 rows=1 loops=548)
                                         ->  Index Only Scan using index_topic_tags_on_topic_id_and_tag_id on topic_tags  (cost=0.29..4.31 rows=1 width=4) (actual time=0.002..0.002 rows=1 loops=548)
                                               Index Cond: (topic_id = topics.id)
                                               Heap Fetches: 178
               ->  Hash  (cost=97222.14..97222.14 rows=19914 width=16) (actual time=87.545..87.546 rows=42884 loops=1)
                     Buckets: 65536 (originally 32768)  Batches: 1 (originally 1)  Memory Usage: 2387kB
                     ->  Bitmap Heap Scan on topic_users tu  (cost=1217.47..97222.14 rows=19914 width=16) (actual time=14.419..78.286 rows=42884 loops=1)
                           Recheck Cond: (user_id = 13455)
                           Filter: (COALESCE(notification_level, 1) >= 2)
                           Rows Removed by Filter: 15839
                           Heap Blocks: exact=45285
                           ->  Bitmap Index Scan on index_topic_users_on_user_id_and_topic_id  (cost=0.00..1212.49 rows=59741 width=0) (actual time=6.448..6.448 rows=58723 loops=1)
                                 Index Cond: (user_id = 13455)
               SubPlan 2
                 ->  Nested Loop Left Join  (cost=0.74..46.90 rows=1 width=4) (never executed)
                       Join Filter: (category_users2.category_id = categories2.id)
                       Filter: (((category_users.id IS NULL) AND (COALESCE(category_users2.notification_level, 1) = 0)) OR (COALESCE(category_users.notification_level, 1) = 0))
                       ->  Nested Loop Left Join  (cost=0.45..32.31 rows=73 width=16) (never executed)
                             Join Filter: (category_users.category_id = categories.id)
                             ->  Nested Loop Left Join  (cost=0.15..18.45 rows=73 width=8) (never executed)
                                   ->  Seq Scan on categories  (cost=0.00..13.73 rows=73 width=8) (never executed)
                                   ->  Memoize  (cost=0.15..0.28 rows=1 width=4) (never executed)
                                         Cache Key: categories.parent_category_id
                                         Cache Mode: logical
                                         ->  Index Only Scan using categories_pkey on categories categories2  (cost=0.14..0.27 rows=1 width=4) (never executed)
                                               Index Cond: (id = categories.parent_category_id)
                                               Heap Fetches: 0
                             ->  Materialize  (cost=0.29..11.69 rows=2 width=12) (never executed)
                                   ->  Index Scan using idx_category_users_user_id_category_id on category_users  (cost=0.29..11.68 rows=2 width=12) (never executed)
                                         Index Cond: (user_id = 13455)
                       ->  Materialize  (cost=0.29..11.69 rows=2 width=8) (never executed)
                             ->  Index Scan using idx_category_users_user_id_category_id on category_users category_users2  (cost=0.29..11.68 rows=2 width=8) (never executed)
                                   Index Cond: (user_id = 13455)
 Planning Time: 1.740 ms
 Execution Time: 91.414 ms
(59 rows)
```

From the execution plan, we can see the most of the time is spent
joining about 42888 rows in the `topics` table to the `topic_users` table.
However, we know that we only have to scan through a
subset of the `topics` table because the user's last unread at is '2023-07-20 11:33:05'.
If we filter the `topics` table with `topics.updated_at >= '2023-07-20 11:33:05'`, this would only
return about 1500 rows.

From our testing in production, the PG planner is able to execute a
better query plan when we avoid the unnecessary joins on `user_stats` just to be
able to get the user's `UserStat#first_unread_at`. Instead, we can just
pass the value of `UserStat#first_unread_at` directly as a query
parameter.

```
EXPLAIN ANALYZE
SELECT
           DISTINCT topics.id as topic_id,
           u.id as user_id,
           topics.created_at,
           topics.updated_at,
           topics.highest_staff_post_number AS highest_post_number,
           last_read_post_number,
           c.id as category_id,
           c.topic_id AS category_topic_id,
           tu.notification_level,
           GREATEST(
              CASE
              WHEN COALESCE(uo.new_topic_duration_minutes, 2880) = -1 THEN u.created_at
              WHEN COALESCE(uo.new_topic_duration_minutes, 2880) = -2 THEN COALESCE(
                u.previous_visit_at,u.created_at
              )
              ELSE ('2023-07-31 03:29:45.737630'::timestamp - INTERVAL '1 MINUTE' * COALESCE(uo.new_topic_duration_minutes, 2880))
              END, u.created_at, '2023-07-25 15:06:44'
           ) AS treat_as_new_topic_start_date
FROM topics
JOIN users u on u.id = 13455
JOIN user_options AS uo ON uo.user_id = u.id
JOIN categories c ON c.id = topics.category_id
LEFT JOIN topic_users tu ON tu.topic_id = topics.id AND tu.user_id = u.id

WHERE u.id = 13455 AND
       topics.updated_at >= '2023-07-20 11:33:05' AND
      topics.archetype <> 'private_message' AND
      (("topics"."deleted_at" IS NULL AND (tu.last_read_post_number < topics.highest_staff_post_number) AND (COALESCE(tu.notification_level, 1) >= 2)) OR (1=0)) AND

      NOT (
  COALESCE((select array_agg(tag_id) from topic_tags where topic_tags.topic_id = topics.id), ARRAY[]::int[]) && ARRAY[451,452,453]
) AND

      topics.deleted_at IS NULL AND

      NOT (
        last_read_post_number IS NULL AND
        (
          topics.category_id IN (SELECT "categories"."id" FROM "categories" LEFT JOIN categories categories2 ON categories2.id = categories.parent_category_id LEFT JOIN category_users ON category_users.category_id = categories.id AND category_users.user_id = 13455 LEFT JOIN category_users category_users2 ON category_users2.category_id = categories2.id AND category_users2.user_id = 13455 WHERE ((category_users.id IS NULL AND COALESCE(category_users2.notification_level, 1) = 0) OR COALESCE(category_users.notification_level, 1) = 0))
          AND tu.notification_level <= 1
        )
      );
```

Note how the filter is now `topics.updated_at >= '2023-07-20 11:33:05'`
instead of `topics.updated_at >= us.first_unread_at`. The modified query
above generates the following execution plan.

```
                                                                                                                                                                                                                                                                                                                QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Unique  (cost=5189.86..5189.88 rows=1 width=52) (actual time=4.991..5.002 rows=14 loops=1)
   ->  Sort  (cost=5189.86..5189.86 rows=1 width=52) (actual time=4.990..4.994 rows=14 loops=1)
         Sort Key: topics.id, topics.created_at, topics.updated_at, topics.highest_staff_post_number, tu.last_read_post_number, c.id, c.topic_id, tu.notification_level, (GREATEST(CASE WHEN (COALESCE(uo.new_topic_duration_minutes, 2880) = '-1'::integer) THEN u.created_at WHEN (COALESCE(uo.new_topic_duration_minutes, 2880) = '-2'::integer) THEN COALESCE(u.previous_visit_at, u.created_at) ELSE ('2023-07-31 03:29:45.73763'::timestamp without time zone - ('00:01:00'::interval * (COALESCE(uo.new_topic_duration_minutes, 2880))::double precision)) END, u.created_at, '2023-07-25 15:06:44'::timestamp without time zone))
         Sort Method: quicksort  Memory: 26kB
         ->  Nested Loop  (cost=52.11..5189.85 rows=1 width=52) (actual time=0.093..4.974 rows=14 loops=1)
               ->  Nested Loop  (cost=51.70..5181.39 rows=1 width=60) (actual time=0.084..4.931 rows=14 loops=1)
                     ->  Nested Loop  (cost=51.28..5172.94 rows=1 width=44) (actual time=0.076..4.887 rows=14 loops=1)
                           ->  Nested Loop  (cost=0.41..1698.46 rows=59 width=36) (actual time=0.029..3.537 rows=548 loops=1)
                                 ->  Seq Scan on categories c  (cost=0.00..13.73 rows=73 width=8) (actual time=0.005..0.039 rows=73 loops=1)
                                 ->  Index Only Scan using index_topics_on_updated_at_public on topics  (cost=0.41..23.07 rows=1 width=28) (actual time=0.012..0.047 rows=8 loops=73)
                                       Index Cond: ((updated_at >= '2023-07-20 11:33:05'::timestamp without time zone) AND (category_id = c.id))
                                       Filter: (NOT (COALESCE((SubPlan 1), '{}'::integer[]) && '{451,452,453}'::integer[]))
                                       Heap Fetches: 552
                                       SubPlan 1
                                         ->  Aggregate  (cost=4.31..4.32 rows=1 width=32) (actual time=0.002..0.002 rows=1 loops=548)
                                               ->  Index Only Scan using index_topic_tags_on_topic_id_and_tag_id on topic_tags  (cost=0.29..4.31 rows=1 width=4) (actual time=0.002..0.002 rows=1 loops=548)
                                                     Index Cond: (topic_id = topics.id)
                                                     Heap Fetches: 178
                           ->  Index Scan using index_topic_users_on_user_id_and_topic_id on topic_users tu  (cost=50.86..58.88 rows=1 width=16) (actual time=0.002..0.002 rows=0 loops=548)
                                 Index Cond: ((user_id = 13455) AND (topic_id = topics.id))
                                 Filter: ((COALESCE(notification_level, 1) >= 2) AND (last_read_post_number < topics.highest_staff_post_number) AND ((last_read_post_number IS NOT NULL) OR (NOT (hashed SubPlan 2)) OR (notification_level > 1)))
                                 Rows Removed by Filter: 0
                                 SubPlan 2
                                   ->  Nested Loop Left Join  (cost=0.74..50.43 rows=1 width=4) (never executed)
                                         Join Filter: (category_users2.category_id = categories2.id)
                                         Filter: (((category_users.id IS NULL) AND (COALESCE(category_users2.notification_level, 1) = 0)) OR (COALESCE(category_users.notification_level, 1) = 0))
                                         ->  Nested Loop Left Join  (cost=0.45..35.84 rows=73 width=16) (never executed)
                                               Join Filter: (category_users.category_id = categories.id)
                                               ->  Nested Loop Left Join  (cost=0.15..21.97 rows=73 width=8) (never executed)
                                                     ->  Seq Scan on categories  (cost=0.00..13.73 rows=73 width=8) (never executed)
                                                     ->  Memoize  (cost=0.15..0.61 rows=1 width=4) (never executed)
                                                           Cache Key: categories.parent_category_id
                                                           Cache Mode: logical
                                                           ->  Index Only Scan using categories_pkey on categories categories2  (cost=0.14..0.60 rows=1 width=4) (never executed)
                                                                 Index Cond: (id = categories.parent_category_id)
                                                                 Heap Fetches: 0
                                               ->  Materialize  (cost=0.29..11.69 rows=2 width=12) (never executed)
                                                     ->  Index Scan using idx_category_users_user_id_category_id on category_users  (cost=0.29..11.68 rows=2 width=12) (never executed)
                                                           Index Cond: (user_id = 13455)
                                         ->  Materialize  (cost=0.29..11.69 rows=2 width=8) (never executed)
                                               ->  Index Scan using idx_category_users_user_id_category_id on category_users category_users2  (cost=0.29..11.68 rows=2 width=8) (never executed)
                                                     Index Cond: (user_id = 13455)
                     ->  Index Scan using users_pkey on users u  (cost=0.42..8.44 rows=1 width=20) (actual time=0.003..0.003 rows=1 loops=14)
                           Index Cond: (id = 13455)
               ->  Index Scan using index_user_options_on_user_id_and_default_calendar on user_options uo  (cost=0.42..8.44 rows=1 width=8) (actual time=0.002..0.002 rows=1 loops=14)
                     Index Cond: (user_id = 13455)
 Planning Time: 1.281 ms
 Execution Time: 5.092 ms
(48 rows)
```

With the new query, PG first does an index scan using the `index_topics_on_updated_at_public` index to filter away most of the topics making the subsequent joins much cheaper. Total query time has been reduced from ~90ms to ~5ms.

This optimisation will mostly affect users with very few/recent unread topics since a large `UserStat#firsts_unread_at` value will still mean scanning through a large portion of the `topics` table.
2023-07-31 12:21:41 +08:00
Alan Guo Xiang Tan
fff578f5fb
FIX: Can't dismiss unread posts in topics of a sub-subcategory (#22870)
This is a similar fix to 32d4810e2b

Why this change?

Prior to this change, there is a bug in `TopicsController#bulk`
where it does not dismiss new unred posts in sub-subcategories when the
`category_id` and `include_subcategories=true` params are present. This
is because the controller did not account for sub-subcategories when
fetching the category ids of the new topics that should be dismissed.

This commit fixes the problem by relying on the `Category.subcategory_ids` class
method which accounts for sub-subcategories.
2023-07-31 11:22:16 +08:00
Ted Johansson
c4d0bbce62
DEV: Delete upload references upon deleting draft (#22851)
We currently are accumulating orphaned upload references whenever drafts are deleted.

This change deals with future cases by adding a dependent strategy of delete_all on the Draft#upload_references association. (We don't really need destroy strategy here, since UploadReference is a simple data bag and there are no validations or callbacks on the model.)

It deals with existing cases through a migration that deletes all existing, orphaned draft upload references.
2023-07-31 10:16:23 +08:00
dependabot[bot]
db423ba153
Build(deps-dev): Bump eslint from 8.45.0 to 8.46.0 in /app/assets/javascripts (#22868)
Bumps [eslint](https://github.com/eslint/eslint) from 8.45.0 to 8.46.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.45.0...v8.46.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-31 09:17:31 +08:00
Kris
8c75fa5dfd
UX: increase experimental lightbox z-index (#22865) 2023-07-28 18:06:48 -04:00
David Taylor
27cdd724ce
PERF: Add exponential backoff for /presence/update errors (#22864)
We already handled 429 rate limit errors correctly. This commit adds backoff logic to other types of error to avoid requests being retried every second.
2023-07-28 22:42:46 +01:00
Jordan Vidrine
0f1479e896
UX: Refactor AI summarizing animation (#22839) 2023-07-28 14:02:26 -05:00
Kris
331507f6b0
UX: allow composer title to shrink when needed (#22863) 2023-07-28 14:48:23 -04:00
Carson Chang
98e3bbd6bf
FIX: Breaking typo, missing closing parenthesis in topic-post.scss (#22852)
* Breaking typo, Update topic-post.scss

* Update topic-post.scss
2023-07-28 09:55:04 -07:00
Selase Krakani
81cf481b16
FIX: Missing pending queued posts from topic view (#22838)
A previous change updated `ReviewableQueuedPost`'s `created_by`
to be consistent with other reviewable types. It assigns
the the creator of the post being queued to `target_created_by` and sets
the `created_by` to the creator of the reviewable itself.

This fix updates some of the `created_by` references missed during the
intial fix.
2023-07-28 16:16:23 +00:00
Penar Musaraj
3bee2a41f4
UX: More tweaks to compact tag picker (#22859)
Input font size was too small.
2023-07-28 12:11:24 -04:00
Selase Krakani
4471eabd49
FIX: Hydration of embedded records (#22809)
The store expects values for property names ending with `_id` to be a resource id
and `_ids` to be an array of resource ids.

This change ensures the store gracefully handles situations where an
embedded field with incompliant data structure sneaks its way to production.
2023-07-28 15:58:55 +00:00
Ella E
9e4c747657
FIX: Tweak right arrow position; set other arrow placements to default (#22848) 2023-07-28 08:58:34 -06:00
Selase Krakani
e67fe1d07c
FIX: Member Highlights on Group Cards (#22828)
By default, only 10 members are highlighted on group cards. However,
joining/leaving a big group via the buttons on the group card results in
up to 50 members being highlighted. For large groups, this causes the card
to move off-screen.

This happens because, while the initial render explicitly fetches only 10
members, we don't seem to apply the same limit as part of the member
reload performed when a user leaves/joins via the buttons on the card.

This PR fixes that by only making the first 10 users available for
highlight regardless of the number of members loaded in the store.
2023-07-28 14:33:42 +00:00
Bianca Nenciu
0736611423
SECURITY: Hide restricted tags in noscript view
The hidden tags are usually filtered out by the serializer, but the
noscript view uses the topic objects instead of the serialized objects.
2023-07-28 12:53:50 +01:00
Penar Musaraj
dcc825bda5
SECURITY: Limit length of edit reason column 2023-07-28 12:53:49 +01:00
Blake Erickson
62a609ea2d
SECURITY: Handle concurrent invite accepts
Raise an error on concurrent invite accept attempts.
2023-07-28 12:53:48 +01:00
Alan Guo Xiang Tan
bfc3132bb2
SECURITY: Impose a upper bound on limit params in various controllers
What is the problem here?

In multiple controllers, we are accepting a `limit` params but do not
impose any upper bound on the values being accepted. Without an upper
bound, we may be allowing arbituary users from generating DB queries
which may end up exhausing the resources on the server.

What is the fix here?

A new `fetch_limit_from_params` helper method is introduced in
`ApplicationController` that can be used by controller actions to safely
get the limit from the params as a default limit and maximum limit has
to be set. When an invalid limit params is encountered, the server will
respond with the 400 response code.
2023-07-28 12:53:46 +01:00
OsamaSayegh
0976c8fad6
SECURITY: Don't reuse CSP nonce between anonymous requests 2023-07-28 12:53:44 +01:00
Alan Guo Xiang Tan
672f3e7e41
Revert "DEV: Skip flaky QUnit tests (#22847)" (#22850)
This reverts commit 26fc5d2d1f.

Flaky test has been fixed in e7208ab4c6
2023-07-28 17:37:31 +08:00
David Battersby
383f48c688
FIX: reset scrollbar position for mobile on lightbox images (#22822)
Using pinch-zoom on mobile devices with lightbox images can lead to scrolling of background content.

This change handles this by capturing the window.scrollY value when opening the lightbox, then when exiting we check if the scroll position has changed and reset it.
2023-07-28 15:03:04 +08:00
Alan Guo Xiang Tan
32d4810e2b
FIX: Can't dismiss new topics that belong to a sub-sub category (#22849)
What is the context for this change?

Prior to this change, there is a bug in `TopicsController#reset_new`
where it does not dismiss new topics in sub-subcategories when the
`category_id` and `include_subcategories=true` params are present. This
is because the controller did not account for sub-subcategories when
fetching the category ids of the new topics that should be dismissed.

This commit fixes the problem by relying on the `Category.subcategory_ids` class
method which accounts for sub-subcategories.
2023-07-28 12:06:42 +08:00
Alan Guo Xiang Tan
26fc5d2d1f
DEV: Skip flaky QUnit tests (#22847)
See
https://github.com/discourse/discourse/actions/runs/5683868303/job/15405355263
and https://github.com/discourse/discourse/actions/runs/5684998320/job/15408941916
2023-07-28 11:05:15 +08:00
dependabot[bot]
3c236fb44d
Build(deps-dev): Bump patch-package in /app/assets/javascripts (#22844)
Bumps [patch-package](https://github.com/ds300/patch-package) from 7.0.2 to 8.0.0.
- [Release notes](https://github.com/ds300/patch-package/releases)
- [Changelog](https://github.com/ds300/patch-package/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ds300/patch-package/commits)

---
updated-dependencies:
- dependency-name: patch-package
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-28 00:58:36 +02:00
marstall
e7208ab4c6
FIX: fix flaky test from settings editor PR (discouse:main:80f5018) (#22834) 2023-07-27 17:46:58 -04:00
Isaac Janzen
de6070fac4
DEV: Convert delete-topic-disallowed modal to component-based API (#22830) 2023-07-27 15:08:37 -05:00
Natalie Tay
173de8afe6
DEV: Add logging and rescue when user already exists and connecting via DiscourseConnect (#22833)
This is happening because despite the user already existing in the forum, the `SingleSignOnRecord` doesn't exist and "require_activation" is set on the provider, causing us to skip looking for the email, and resulting in us creating a new User then seeing Validation failed: Primary email has already been taken when DiscourseConnect is attempting to make a new account.
2023-07-28 02:53:33 +08:00
marstall
80f5018924
FEATURE: JSON editor for theme settings (#21647)
provide the ability to edit theme settings in the json editor, and also copy them as a text file so they can be pasted into another instance.

Reference: /t/65023
2023-07-27 13:48:59 -04:00
Jarek Radosz
a44378a1b6
FIX: Toggling overridden settings broke in #21572 (#22831) 2023-07-27 19:41:42 +02:00
Isaac Janzen
291629834d
DEV: Convert delete-topic-confirm modal to component-based API (#22813)
<img width="677" alt="Screenshot 2023-07-26 at 4 19 52 PM" src="https://github.com/discourse/discourse/assets/50783505/5b969d38-0283-4217-9f70-de3561f82dd5">
2023-07-27 11:52:12 -05:00
Joffrey JAFFEUX
db0aef1192
DEV: removes unused group_manager model (#22827)
The associated table has been removed in 2015: 6dd4bc7d57 (diff-53b8234c51c429b92eb91d0212e15bbab16fcd5d1cbd3db64509977c9e1a060d)
2023-07-27 17:10:58 +02:00
David Taylor
507433f45c
FIX: Share topic shortcut (shift+s) (#22826)
This has been broken for a long time (since 04a63cfaaa)
2023-07-27 15:39:42 +01:00
Krzysztof Kotlarek
7fb4bd3f43
FIX: move main sidebar panel name to constant (#22819)
Small improvement of moving panel name to constant.
2023-07-27 13:17:13 +08:00
Jarek Radosz
83043bd453
DEV: Enable some of the skipped Firefox tests (#22800) 2023-07-27 10:56:50 +08:00
dependabot[bot]
0369839bd3
Build(deps-dev): Bump ember-cached-decorator-polyfill from 1.0.1 to 1.0.2 in /app/assets/javascripts (#22816)
Bumps [ember-cached-decorator-polyfill](https://github.com/ember-polyfills/ember-cached-decorator-polyfill) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/ember-polyfills/ember-cached-decorator-polyfill/releases)
- [Changelog](https://github.com/ember-polyfills/ember-cached-decorator-polyfill/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ember-polyfills/ember-cached-decorator-polyfill/compare/v1.0.1...v1.0.2)

---
updated-dependencies:
- dependency-name: ember-cached-decorator-polyfill
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-27 10:56:03 +08:00
Alan Guo Xiang Tan
fe5cd479eb
PERF: Add index on topic_id and created_at to posts table (#22818)
Why this change?

In `PostDestroyer#make_previous_post_the_last_one` and
`Topic.reset_highest`, we have a query that looks something like this:

```
SELECT user_id FROM posts
WHERE topic_id = :topic_id AND
      deleted_at IS NULL AND
      post_type <> 4
      #{post_type}
ORDER BY created_at desc
LIMIT 1
```

However, we currently don't have an index that caters directly to this
query. As a result, we have seen this query performing poorly on large
sites if the PG planner ends up using an index that is suboptimal for
the query.

This commit adds an index to the `posts` table on `topic_id` and then
`created_at`. For the query above, PG will be able to do a backwards
index scan efficiently.
2023-07-27 10:55:10 +08:00
Alan Guo Xiang Tan
0a56274596
FIX: Seed all categories and tags configured as defaults for nav menu (#22793)
Context of this change:

There are two site settings which an admin can configured to set the
default categories and tags that are shown for a new user. `default_navigation_menu_categories`
is used to determine the default categories while
`default_navigation_menu_tags` is used to determine the default tags.

Prior to this change when seeding the defaults, we will filter out the
categories/tags that the user do not have permission to see. However,
this means that when the user does eventually gain permission down the
line, the default categories and tags do not appear.

What does this change do?

With this commit, we have changed it such that all the categories and tags
configured in the `default_navigation_menu_categories` and
`default_navigation_menu_tags` site settings are seeded regardless of
whether the user's visibility of the categories or tags. During
serialization, we will then filter out the categories and tags which the
user does not have visibility of.
2023-07-27 10:52:33 +08:00
Penar Musaraj
f3db8579d6
UX: Minor alignment fix for compact tag selector (#22810) 2023-07-26 14:13:39 -04:00
Jarek Radosz
92d2ea008e
DEV: Fix no-negated-condition linting issues (#22808) 2023-07-26 19:53:37 +02:00
Isaac Janzen
f3b7351ff6
DEV: Convert theme-upload modal to component-based API (#22699) 2023-07-26 12:46:02 -05:00
Isaac Janzen
a5b810fe18
FIX: Flashing history modal when changing versions (#22785)
## Problem
History modal is flashing when changing revision versions

## Context
This was introduced in https://github.com/discourse/discourse/pull/22666

We need to have a conditional loading spinner for the initial paint of the history modal as we don't have the revision yet loaded, so this can cause some odd rendering issues. At the same time we don't want to display the loading spinner each time we toggle between the revision versions, because the loading spinner replaces the revision body causing the modal sizes to be drastically different resulting in _jumping_ or _flashing_.

## Fix
Render the loading spinner only on the first paint of the modal.

https://github.com/discourse/discourse/assets/50783505/8d19275e-86a5-4132-8a1f-af4b4f5301a6
2023-07-26 11:08:59 -05:00
Penar Musaraj
f2048eeb4c
UX: Minor change to compact tag chooser (#22796)
Followup to f5e8e73.

This switches the placeholder label to the existing string "optional
tags" and only shows it if there are no items picked.

Co-authored-by: Jarek Radosz <jradosz@gmail.com>
2023-07-26 11:43:46 -04:00
Jarek Radosz
c6a23b90f6
DEV: Don't stub an imported module (#22805)
Stub a service's getter instead (embroider compat issue)
2023-07-26 13:01:29 +02:00
Jarek Radosz
1de0fa020e
DEV: Use waitFor instead of waitUntil (#22803)
Short and to the point 😉
2023-07-26 12:59:17 +02:00
Jarek Radosz
d06431ba9b
DEV: Fix random typos (#22804)
A fresh batch of stashed changes :P
2023-07-26 12:45:35 +02:00
Jarek Radosz
62d3979870
DEV: Convert quote-button to a glimmer component (#22787)
Native class, glimmer, no jQ, async/await

Co-authored-by: Isaac Janzen <50783505+janzenisaac@users.noreply.github.com>
2023-07-26 11:50:55 +02:00
Krzysztof Kotlarek
6e3da7c07d
FIX: distinguish between scroll and drag for sidebar (#22794)
Change to drag move event handling. When position of mouse changed, we can assume it is not drag and drop, and we should keep default behaviour.

Otherwise, we stop propagation of the event to handle drag and drop correctly. s
2023-07-26 11:56:54 +10:00
dependabot[bot]
6a4666456e
Build(deps-dev): Bump node-fetch from 3.3.1 to 3.3.2 in /app/assets/javascripts (#22792)
Bumps [node-fetch](https://github.com/node-fetch/node-fetch) from 3.3.1 to 3.3.2.
- [Release notes](https://github.com/node-fetch/node-fetch/releases)
- [Commits](https://github.com/node-fetch/node-fetch/compare/v3.3.1...v3.3.2)

---
updated-dependencies:
- dependency-name: node-fetch
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-26 09:21:25 +08:00
Krzysztof Kotlarek
85e2f67367
FEATURE: API to set combined/separated sidebar mode. (#22753)
New API to change sidebar mode. We defined two:
Separated - only sections belonging to specific panel are displayed, and buttons to switch the panel are available as well.
Combined - all sections are displayed together and switch panel buttons are not visible.

In addition, as a part of refactoring, a new service called SidebarState was introduced.
2023-07-26 10:15:56 +10:00