Граф коммитов

1274 Коммитов

Автор SHA1 Сообщение Дата
Ben Cooke
bfe90c3704 New pluginapi method for syncables (#30790)
* new pluginapi method for syncables
---------
Co-authored-by: Mattermost Build <build@mattermost.com>
2025-05-21 14:44:34 -04:00
Jesse Hallam
31a8047973 Disable morph logging during TestMain (#30948)
* rm "No TEST_DATABASE... override" log message

Let's only log if this value is actually overridden.

* rm "(Created|Dropped) temporary database" message

* only log "Pinging SQL" on subsequent attempts

* disable morph logging from TestMain

* Fix style issues in store test files

- Add missing parameter to migrate() function calls in tests
- Remove unused log function in settings.go
- Fix formatting with go fmt

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* within sqlstore, use "enable" vs "disable" for clarity

* remove trailing newline from morph logs

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-05-21 17:31:18 +00:00
Jesse Hallam
92db356484 MM-62158: group store no select star, part3 (#30927)
* migrate getGroupsAssociatedToChannelsByTeam

* migrate GetGroups

* migrate teamMembersMinusGroupMembersQuery

* migrate channelMembersMinusGroupMembersQuery
2025-05-21 14:19:38 -03:00
Ben Schumacher
6de3379994 [MM-61099] Fix errcheck issues in server/channels/app/brand.go (#30679)
* [MM-28779] Fix errcheck issues in server/channels/app/brand.go

Remove brand.go from the errcheck exclusion list in .golangci.yml and fixed the error by properly handling the return value from a.MoveFile().

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* [MM-28779] Add test to verify brand image backup functionality

Add a new test that verifies backup of the original brand image happens when a new one is uploaded. This helps to ensure the fix for errcheck issues is working as expected.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* use seperate temporary filestore for each test

* Use FileSettings.Directory instead of finding the dir programatically

* Fix another test

* Fix defer

* Update server/channels/api4/job_test.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fix bad bot commit

* Cleanup logs message

* cleanup file path

* Fix error variable names

* WIP:cleanup panic ussage

* Revert "WIP:cleanup panic ussage"

This reverts commit c3284e4427a41c818acc161926cd2535dee9a6b9.

* cleanup error checks

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2025-05-21 16:35:00 +02:00
Agniva De Sarker
09ea32a03c MM-63300: Fix flaky test TestBusySet (#31116)
(*Busy).Set would set its own timer, and additionally
send a message across the cluster. In this case, the cluster
is mocked locally. But the timer calculation happens again.

We marshal the expiry time with b.expires.Unix() and send that
as part of model.ServerBusyState. This is parsed again in
ClusterEventChanged and converted to duration with time.Until.

Therefore, if it takes longer for the code to reach those lines,
then the new time calculated would have already expired, failing
the test.

To fix this, we increase the timeout. This slows down the test
at the cost of extra reliability. This is a common failure point
with any timer related tests.

Additionally, we also change the condition in compareBusyState
to check for less-than rather than strict equality.

https://mattermost.atlassian.net/browse/MM-63300

```release-note
NONE
```

Co-authored-by: Mattermost Build <build@mattermost.com>
2025-05-21 08:48:46 +05:30
Agniva De Sarker
ad56ab3fdc MM-62365: Remove unused field from MetricSample (#30991)
The Timestamp field was not used at all. Therefore
removing it.

https://mattermost.atlassian.net/browse/MM-62365

```release-note
NONE
```
2025-05-21 08:43:00 +05:30
Harrison Healey
1bfd3a6a6e Slightly improve performance of sidebar update APIs (#31061) 2025-05-20 21:16:28 +00:00
Harrison Healey
b70f1d859d MM-63923/MM-63924/MM-63925 Prevent deadlocks and constraint errors in UpdateSidebarCategories (#30965)
* MM-63925 Remove most nested transactions from channel_store_categories.go

There's one place which still has a nested transaction in
CreateInitialSidebarCategories, but that's because it's calling out to a
different part of the store. The only way to avoid that would be to
break the extraction like UpdateSidebarCategories does to update
preferences, but I chose not to follow that pattern here and leave it
as-is.

* MM-63923 Prevent deadlocks caused by updating multiple categories in a different order

* MM-63923 Prevent deadlocks while deleting from SidebarChannels

This could also have been resolved by sorting the categories, but
combining the queries seems a bit more elegant.

* MM-63924 Ensure adding SidebarChannels rows is idempotent

* Add additional test to cause deadlocks

* Prevent channels from appearing in a single category multiple times

* Other review feedback
2025-05-20 16:02:32 -04:00
Jesse Hallam
fa40a8c5d4 MM-64226: improved post deduplication (#31004)
Require access to a post before allowing PendingPostId to deduplicate.

Fixes: https://mattermost.atlassian.net/browse/MM-64226
2025-05-20 10:05:10 -03:00
Ben Schumacher
c2d08b7540 [MM-63772] Add LDAP setting to re-add removed members (#30787) 2025-05-20 11:15:25 +02:00
Arya Khochare
f54d5b41c1 [MM-58163] Jobs paging/offset refactor (#30343)
Co-authored-by: Ben Schumacher <ben.schumacher@mattermost.com>
2025-05-19 12:31:25 +02:00
Ibrahim Serdar Acikgoz
a344b3225b [MM-61756] Attribute Based Access Control - Phase 1 (#30785)
Attribute Based Access Control - Base
* MM-63662

* MM-63919

* MM-63954

* MM-63955 

* MM-63425

* MM-63426

* MM-63458

* MM-63459

* MM-63603

* MM-63845

* MM-64146

* MM-64199

* MM-64201

* MM-64233

* MM-64247

* MM-64268

---------

Co-authored-by: Harshil Sharma <harshilsharma63@gmail.com>
Co-authored-by: Pablo Andrés Vélez Vidal <pablovv2012@gmail.com>
Co-authored-by: abhijit-singh <abhijitsingh0702@gmail.com>
Co-authored-by: Harrison Healey <harrisonmhealey@gmail.com>
2025-05-15 11:33:08 +02:00
Ben Cooke
1634b57dbd fix flaky test (#31046) 2025-05-14 14:07:08 -04:00
Ben Cooke
6b4ab0a891 [MM-63774] Allow users to leave private channels when there is only 1 member (#30746)
* allow last user to leave private channel
---------
Co-authored-by: Mattermost Build <build@mattermost.com>
2025-05-14 11:58:13 -04:00
Jesse Hallam
d80d575f6c MM-63619: improve Groups API error semantics (#30961)
Instead of 5xx errors, return `http.StatusInvalidRequest` when
adding or deleting invalid user ids from groups.

Fixes: https://mattermost.atlassian.net/browse/MM-63619
2025-05-14 10:17:30 -03:00
Miguel de la Cruz
6ab6a008e6 Adds the capability to retrieve a property field by name (#30859)
* Adds the capability to retrieve a property field by name

Allows to retrieve a property field by name and groupID. As the name
is only unique within the context of a group, and we can have multiple
fields with the same name in the store, for this method the groupID is
directly included in the query instead of being an optional field.

* Adds the targetID parameter to correctly filter fields

* Ensure the method only retrieves non-deleted fields

---------

Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es>
Co-authored-by: Mattermost Build <build@mattermost.com>
2025-05-13 12:45:35 +02:00
Pablo Vélez
64ed8f02dc MM-63912 - enhance validation for channel banner colors (#30981)
* MM-63912 - enhance validation for channel banner colors

* fix tests

* fix unit tests
2025-05-13 09:59:31 +02:00
Ben Cooke
b6b0c269c4 Use error from OnSamlLogin (#30745) 2025-05-12 18:41:54 -04:00
Jesse Hallam
f302b9844e migrate groupsBySyncableBaseQuery (#30926) 2025-05-12 15:03:02 -03:00
Nick Misasi
cf2702f5dd Pass the server's version in trial request payload (#30911)
Co-authored-by: Mattermost Build <build@mattermost.com>
2025-05-12 13:40:45 -04:00
David Krauser
4b64eb0e39 Handle error returned by GetClusterInfos() (#30919)
A recent change to the enterprise cluster code introduced a change to the enterprise API interface. GetClusterInfos() can now return an error. This commit introduces code to handle that error.
2025-05-12 13:37:58 -04:00
Agniva De Sarker
4803892492 MM-56906: Remove redundant calls on team switch (#30771)
On page load, we load ALL channels and channel members from all teams.
But then, on team_switch, we would again load channels and channel
members from that team. This was redundant and mainly kept
because previously the websocket events were considered unreliable.

Now with reliable websockets, and client-side pings, we can detect
broken connections faster and recover without loss.

Additionally, the getAllChannelMembers call would page through
all responses on the client side. This was inefficient and incur
extra latency. To optimize for this, we introduce server-side
streaming of the full response if page is set to -1.

This optimizes the intial response as well.

https://mattermost.atlassian.net/browse/MM-56906

```release-note
Optimize team switch operation by removing calls to get channels
and channel members.
```


Co-authored-by: Mattermost Build <build@mattermost.com>
2025-05-12 20:05:46 +05:30
Ben Schumacher
bb8aabc15e [MM-61765] Fix errcheck issues in server/channels/app/platform/license.go (#30954)
* [MM-61765] Fix errcheck issues in server/channels/app/platform/license.go

- Removed the errcheck exception for license.go from .golangci.yml
- Added proper error handling for RemoveLicense() calls
- Added proper error handling for ReloadConfig() and InvalidateAllCaches() calls
- Updated variable names to avoid conflicts

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* simplify naming

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-05-12 13:41:59 +02:00
Ben Schumacher
4dbff921ba [MM-61499] Fix errcheck issues in server/channels/app/slashcommands/helper_test.go (#30880) 2025-05-12 11:20:14 +02:00
Agniva De Sarker
509b8e9af7 MM-63130: Move to webHub iteration to be alloc-free (#30792)
We switch to using iterators introduced in Go 1.23
to make iteration alloc-free and fast. And since
element removal is allowed while iterating a map,
this also means we don't need to even copy the slice
any more.

While here, we also address the comment https://github.com/mattermost/mattermost/pull/30178#discussion_r1954862151.
I have simply gone back to using []string as the map
entry rather than a type alias or a redirection with
a struct.

https://mattermost.atlassian.net/browse/MM-63130

```release-note
NONE
```

* Changed back nil to len

```release-note
NONE
```

* fixing unused assignment

```release-note
NONE
```

* add benchmark

```
goos: linux
goarch: amd64
pkg: github.com/mattermost/mattermost/server/v8/channels/app/platform
cpu: Intel(R) Core(TM) i5-8265U CPU @ 1.60GHz
                               │   old.txt    │               new.txt               │
                               │    sec/op    │   sec/op     vs base                │
HubConnIndexIterator/2_users-8    93.53n ± 1%   38.09n ± 1%  -59.27% (p=0.000 n=10)
HubConnIndexIterator/3_users-8   106.30n ± 0%   38.41n ± 1%  -63.86% (p=0.000 n=10)
HubConnIndexIterator/4_users-8   111.30n ± 1%   38.66n ± 1%  -65.27% (p=0.000 n=10)
geomean                           103.4n        38.39n       -62.89%

                               │  old.txt   │               new.txt                │
                               │    B/op    │    B/op     vs base                  │
HubConnIndexIterator/2_users-8   16.00 ± 0%   24.00 ± 0%  +50.00% (p=0.000 n=10)
HubConnIndexIterator/3_users-8   24.00 ± 0%   24.00 ± 0%        ~ (p=1.000 n=10) ¹
HubConnIndexIterator/4_users-8   32.00 ± 0%   24.00 ± 0%  -25.00% (p=0.000 n=10)
geomean                          23.08        24.00        +4.00%
¹ all samples are equal

                               │  old.txt   │               new.txt               │
                               │ allocs/op  │ allocs/op   vs base                 │
HubConnIndexIterator/2_users-8   1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
HubConnIndexIterator/3_users-8   1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
HubConnIndexIterator/4_users-8   1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
geomean                          1.000        1.000       +0.00%
¹ all samples are equal
```

```release-note
NONE
```

* ForChannel test as well

```release-note
NONE
```

* review comments

```release-note
NONE
```

* fix lint errors

```release-note
NONE
```

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2025-05-11 12:00:12 +05:30
Ben Schumacher
cd3046773e [MM-61498] Fix errcheck issues in server/channels/app/slashcommands/command_test.go (#30879)
Co-authored-by: Claude <noreply@anthropic.com>
2025-05-09 13:56:35 +02:00
Agniva De Sarker
0ebd3e8085 MM-64209: Optimize completePopulatingCategoryChannelsT for MySQL (#30963)
For our MySQL customers, we have discovered that the query is not
able to choose the right plan by itself without adequate hints.

This is only for MySQL as we have confirmed from multiple customers
that Postgres takes the right index idx_sidebarcategories_userid_teamid
for the sidebarCategories table. And if it doesn't, then a VACUUM ANALYZE
fixes it.

But for MySQL, we have to do two things:
- Pass an index hint to let it use idx_sidebarcategories_userid_teamid.
- Pass an optimizer hint to materialize the sub-query. This is used
to materialize the doesNotHaveSidebarChannel sub-query into a temporary
table, letting MySQL reuse the contents of the table for further processing
in the parent sections of the query.

I have confirmed both locally and in the customer environment
that it gives a clear benefit.

*LOCAL*

OLD:
```
| -> Nested loop antijoin  (cost=2889.85 rows=19767) (actual time=3.355..38.033 rows=15 loops=1)
    -> Nested loop inner join  (cost=66.65 rows=110) (actual time=0.410..1.689 rows=220 loops=1)
        -> Filter: ((Channels.DeleteAt = 0) and (Channels.`Type` in ('O','P')))  (cost=25.25 rows=110) (actual time=0.394..0.886 rows=220 loops=1)
            -> Index lookup on Channels using idx_channels_team_id_display_name (TeamId='team01'), with index condition: (Channels.Id is not null)  (cost=25.25 rows=220) (actual time=0.389..0.793 rows=220 loops=1)
        -> Single-row covering index lookup on ChannelMembers using PRIMARY (ChannelId=Channels.Id, UserId='user000')  (cost=0.28 rows=1) (actual time=0.003..0.003 rows=1 loops=220)
    -> Nested loop inner join  (cost=4967.50 rows=180) (actual time=0.165..0.165 rows=1 loops=220)
        -> Covering index lookup on SidebarChannels using PRIMARY (ChannelId=Channels.Id)  (cost=7.86 rows=180) (actual time=0.055..0.062 rows=13 loops=220)
        -> Filter: ((SidebarCategories.TeamId = 'team01') and (SidebarCategories.UserId = 'user000'))  (cost=44.93 rows=1) (actual time=0.008..0.008 rows=0 loops=2881)
            -> Single-row index lookup on SidebarCategories using PRIMARY (Id=SidebarChannels.CategoryId)  (cost=44.93 rows=1) (actual time=0.006..0.006 rows=1 loops=2881)
 |
```

NEW:
```
 | -> Nested loop antijoin  (cost=5879.73 rows=58021) (actual time=1.544..3.135 rows=15 loops=1)
    -> Nested loop inner join  (cost=66.65 rows=110) (actual time=0.421..1.778 rows=220 loops=1)
        -> Filter: ((Channels.DeleteAt = 0) and (Channels.`Type` in ('O','P')))  (cost=25.25 rows=110) (actual time=0.405..0.945 rows=220 loops=1)
            -> Index lookup on Channels using idx_channels_team_id_display_name (TeamId='team01'), with index condition: (Channels.Id is not null)  (cost=25.25 rows=220) (actual time=0.400..0.859 rows=220 loops=1)
        -> Single-row covering index lookup on ChannelMembers using PRIMARY (ChannelId=Channels.Id, UserId='user000')  (cost=0.28 rows=1) (actual time=0.003..0.004 rows=1 loops=220)
    -> Single-row index lookup on <subquery2> using <auto_distinct_key> (ChannelId=Channels.Id)  (cost=130.37..130.37 rows=1) (actual time=0.006..0.006 rows=1 loops=220)
        -> Materialize with deduplication  (cost=130.35..130.35 rows=527) (actual time=1.118..1.118 rows=205 loops=1)
            -> Filter: (SidebarChannels.ChannelId is not null)  (cost=77.61 rows=527) (actual time=0.059..0.851 rows=523 loops=1)
                -> Nested loop inner join  (cost=77.61 rows=527) (actual time=0.058..0.786 rows=523 loops=1)
                    -> Covering index lookup on SidebarCategories using idx_sidebarcategories_userid_teamid (UserId='user000', TeamId='team01')  (cost=2.81 rows=15) (actual time=0.025..0.031 rows=15 loops=1)
                    -> Covering index lookup on SidebarChannels using idx_sidebarchannels_categoryid (CategoryId=SidebarCategories.Id)  (cost=1.70 rows=35) (actual time=0.032..0.046 rows=35 loops=15)
```

Performance improvement from 38ms to 3ms.

*CUSTOMER ENV* (with sensitive data wiped off)

OLD:
```
| -> Sort: channels.DisplayName  (actual time=512..512 rows=5 loops=1)
    -> Stream results  (cost=3.28 rows=1.44) (actual time=223..512 rows=5 loops=1)
        -> Nested loop antijoin  (cost=3.28 rows=1.44) (actual time=223..512 rows=5 loops=1)
            -> Nested loop inner join  (cost=3.02 rows=0.3) (actual time=0.025..0.0878 rows=5 loops=1)
                -> Covering index lookup on ChannelMembers using idx_channelmembers_user_id_channel_id_last_viewed_at (UserId='')  (cost=0.916 rows=6) (actual time=0.0146..0.023 rows=6 loops=1)
                -> Filter: ((channels.DeleteAt = 0) and (channels.TeamId = '') and (channels.`Type` in ('O','P')))  (cost=0.251 rows=0.05) (actual time=0.00999..0.0102 rows=0.833 loops=6)
                    -> Single-row index lookup on Channels using PRIMARY (Id=channelmembers.ChannelId)  (cost=0.251 rows=1) (actual time=0.00778..0.00785 rows=1 loops=6)
            -> Nested loop inner join  (cost=2.85 rows=4.81) (actual time=102..102 rows=0 loops=5)
                -> Covering index lookup on SidebarChannels using PRIMARY (ChannelId=channelmembers.ChannelId)  (cost=2.01 rows=4.81) (actual time=0.0125..13.8 rows=24134 loops=5)
                -> Filter: ((sidebarcategories.TeamId = '') and (sidebarcategories.UserId = ''))  (cost=1.54 rows=1) (actual time=0.00359..0.00359 rows=0 loops=120671)
                    -> Single-row index lookup on SidebarCategories using PRIMARY (Id=sidebarchannels.CategoryId)  (cost=1.54 rows=1) (actual time=0.00316..0.00319 rows=1 loops=120671)
```

NEW:
```
Here is the output

| -> Sort: channels.DisplayName  (actual time=0.12..0.12 rows=5 loops=1)
    -> Stream results  (cost=3.45 rows=4.01) (actual time=0.0797..0.11 rows=5 loops=1)
        -> Nested loop antijoin  (cost=3.45 rows=4.01) (actual time=0.0769..0.106 rows=5 loops=1)
            -> Nested loop inner join  (cost=3.02 rows=0.3) (actual time=0.0291..0.0555 rows=5 loops=1)
                -> Covering index lookup on ChannelMembers using idx_channelmembers_user_id_channel_id_last_viewed_at (UserId='')  (cost=0.916 rows=6) (actual time=0.0145..0.0162 rows=6 loops=1)
                -> Filter: ((channels.DeleteAt = 0) and (channels.TeamId = '') and (channels.`Type` in ('O','P')))  (cost=0.251 rows=0.05) (actual time=0.00611..0.00619 rows=0.833 loops=6)
                    -> Single-row index lookup on Channels using PRIMARY (Id=channelmembers.ChannelId)  (cost=0.251 rows=1) (actual time=0.0053..0.00534 rows=1 loops=6)
            -> Single-row index lookup on <subquery2> using <auto_distinct_key> (ChannelId=channelmembers.ChannelId)  (cost=7.01..7.01 rows=1) (actual time=0.00956..0.00956 rows=0 loops=5)
                -> Materialize with deduplication  (cost=7..7 rows=13.4) (actual time=0.0451..0.0451 rows=0 loops=1)
                    -> Filter: (sidebarchannels.ChannelId is not null)  (cost=5.66 rows=13.4) (actual time=0.0441..0.0441 rows=0 loops=1)
                        -> Nested loop inner join  (cost=5.66 rows=13.4) (actual time=0.0439..0.0439 rows=0 loops=1)
                            -> Covering index lookup on SidebarCategories using idx_sidebarcategories_userid_teamid (UserId='', TeamId='')  (cost=0.592 rows=3) (actual time=0.0105..0.0134 rows=3 loops=1)
                            -> Covering index lookup on SidebarChannels using idx_sidebarchannels_categoryid (CategoryId=sidebarcategories.Id)  (cost=1.39 rows=4.46) (actual time=0.00999..0.00999 rows=0 loops=3)
```

Performance improvement from 512ms to 0.12ms.

https://mattermost.atlassian.net/browse/MM-64209

```release-note
NONE
```
2025-05-09 09:48:07 +05:30
Ben Schumacher
92011a6c75 [MM-29111] Fix errcheck issues in server/channels/app/upload.go (#30678)
Co-authored-by: Claude <noreply@anthropic.com>
2025-05-07 13:40:51 +02:00
Ben Schumacher
bfb15ab179 [MM-61074] Fix errcheck issues in oauth_test.go and web_test.go (#30707)
Co-authored-by: Claude <noreply@anthropic.com>
2025-05-07 12:41:10 +02:00
Ben Schumacher
dc2827a63e [MM-28923] Fix errcheck issues in server/channels/app/file.go (#30878)
Co-authored-by: Claude <noreply@anthropic.com>
2025-05-07 10:04:17 +02:00
Ben Schumacher
e10a1309c1 [MM-61774] Fix errcheck issues in server/channels/app/platform/status.go (#30876)
Co-authored-by: Claude <noreply@anthropic.com>
2025-05-07 09:04:22 +02:00
Harshil Sharma
a5e68639c2 Channel banner permissions (#30917)
* Fixed save state panel for channel banner

* Defined default background color

* Updated test

* WIP

* wip

* removed unused param

* Updated tests

* CI

* Fixed mmctl test

* Fixed TestDoAdvancedPermissionsMigration test

* Test update

* lint fix

* lint fix

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2025-05-06 14:03:35 +05:30
Harrison Healey
0c38d893e3 Fix errcheck issue on master (#30938) 2025-05-05 16:36:25 +00:00
Ivy Gesare
1bca62dc83 [MM-61505] Fix errcheck issues in server/channels/app/team_test.go (#29146)
Co-authored-by: Ben Schumacher <ben.schumacher@mattermost.com>
2025-05-05 12:43:16 +02:00
kasyap dharanikota
ddb4c4360c fix errcheck in server/channels/app/platform/session.go (#30595)
Co-authored-by: Ben Schumacher <ben.schumacher@mattermost.com>
2025-05-05 12:18:47 +02:00
Harshil Sharma
a76c063d85 Renamed premium SKU to Enterprise Advanced (#30882) 2025-05-02 11:34:46 +05:30
Jesse Hallam
e1f47e22e7 MM-62158: group store no SELECT * (Part 1) (#30276)
* improved test coverage

* initial pass on removing SELECT * from group store
2025-05-01 09:39:05 -03:00
Miguel de la Cruz
2decc2ccdb Prevent synced CPA values to be updated from the API (#30687)
* Prevents the API from updating synced CPA values

The patch functions for CPA values now accept a parameter that checks
if they should allow for synced values to be updated, and prevent
those updates if necessary

* Fix linter

* Fix parameter after merge

---------

Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es>
Co-authored-by: Scott Bishel <scott.bishel@mattermost.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2025-04-30 18:43:05 +02:00
Ben Schumacher
df3560ed9c [MM-61086] Fix errcheck linter issue in batch_worker_test.go (#30644) 2025-04-29 11:08:38 +02:00
Ben Schumacher
f1ddeec2f6 [MM-61778] Fix errcheck issues in web_hub_test.go (#30676)
Co-authored-by: Claude <noreply@anthropic.com>
2025-04-29 10:23:53 +02:00
Ben Schumacher
2f5b70bfa2 [MM-29108] Fix errcheck issues in server/channels/app/team.go (#30683)
Co-authored-by: Claude <noreply@anthropic.com>
2025-04-29 09:03:06 +02:00
Ben Schumacher
d0c4780eb1 [MM-28761] Fix errcheck linter issues in hosted_purchase_screening worker (#30624) 2025-04-29 08:57:52 +02:00
Ben Schumacher
5ee308bd83 [MM-61467] Fix errcheck linter issues in post_test.go (#30686) 2025-04-29 08:50:47 +02:00
Agniva De Sarker
efde5e2717 [AI assisted] MM-62755: Refactor scanning to map to a util (#30780)
With some neat generics, I was able to refactor
the scanning to a util function. I used it to
refactor 3 places and also removed an unnecessary method.

Claude was quite good here.

https://mattermost.atlassian.net/browse/MM-62755
```release-note
NONE
```
2025-04-28 19:21:12 +05:30
Ben Schumacher
cd5523f5fb MM-29092 Fix error handling in auto_environment.go (#30610)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Caleb Roseland <caleb@calebroseland.com>
2025-04-28 15:05:31 +02:00
Ben Schumacher
eea39a5f23 [MM-61087] Fix errcheck linter issues in channels/jobs/helper_test.go (#30643)
Co-authored-by: Claude <noreply@anthropic.com>
2025-04-28 15:02:10 +02:00
Ben Schumacher
6a228877b7 [MM-28762] Fix errcheck issues in jobs.go (#30708)
Co-authored-by: Claude <noreply@anthropic.com>
2025-04-28 14:57:40 +02:00
Ben Schumacher
6fc60c8e5f [MM-61105] Fix errcheck linter errors in config_test.go (#30689) 2025-04-28 14:53:45 +02:00
Agniva De Sarker
29f7c895b8 MM-63878: Fix INSERT IGNORE in saveChannelT (#30850)
INSERT IGNORE will ignore ALL errors in the INSERT statement.
This is not what was intended. The right way is to do a
redundant update on duplicate key.

It's not great, but that's how MySQL wants us to do it.

https://mattermost.atlassian.net/browse/MM-63878
```release-note
NONE
```
2025-04-24 21:29:09 +05:30
Agniva De Sarker
131cf039bb MM-63756: Added index to sidebarchannels table (#30724)
The (s SqlChannelStore) getSidebarCategoriesT gets called quite frequently.
- Team switch
- WS reconnect
- Category created
- Category updated
- Category deleted

Of these 1 and 2 are probably the most commonly called sources. Based on that,
the sidebarChannels table is not that well-optimized. Even though
the query time might be reasonable, without an index, it has to churn a lot of
DB CPU for a sequential scan.

We add a new index to optimize this.

CREATE INDEX idx_sidebarchannels_categoryid ON sidebarchannels(categoryid);

```
Before:
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Sort  (cost=40854.18..40854.19 rows=4 width=193) (actual time=251.635..251.646 rows=204 loops=1)
   Sort Key: sidebarcategories.sortorder, sidebarchannels.sortorder
   Sort Method: quicksort  Memory: 65kB
   Buffers: shared hit=1203 read=23668
   ->  Nested Loop  (cost=8.87..40854.14 rows=4 width=193) (actual time=251.345..251.455 rows=204 loops=1)
         Buffers: shared hit=1203 read=23668
         ->  Nested Loop  (cost=0.41..9.47 rows=1 width=54) (actual time=0.068..0.074 rows=1 loops=1)
               Buffers: shared hit=5
               ->  Seq Scan on teams  (cost=0.00..1.03 rows=1 width=27) (actual time=0.024..0.026 rows=1 loops=1)
                     Filter: (((id)::text = '3ee5y5ok6jgxicrmqstdnghmfr'::text) AND (deleteat = 0))
                     Rows Removed by Filter: 1
                     Buffers: shared hit=1
               ->  Index Scan using teammembers_pkey on teammembers  (cost=0.41..8.43 rows=1 width=27) (actual time=0.039..0.043 rows=1 loops=1)
                     Index Cond: (((teamid)::text = '3ee5y5ok6jgxicrmqstdnghmfr'::text) AND ((userid)::text = 'tc3p1yqw67d8idcp3g98awexqe'::text))
                     Filter: (deleteat = 0)
                     Buffers: shared hit=4
         ->  Hash Right Join  (cost=8.45..40844.62 rows=4 width=193) (actual time=251.274..251.361 rows=204 loops=1)
               Hash Cond: ((sidebarchannels.categoryid)::text = (sidebarcategories.id)::text)
               Buffers: shared hit=1198 read=23668
               ->  Seq Scan on sidebarchannels  (cost=0.00..37514.77 rows=1265277 width=100) (actual time=0.043..99.345 rows=1265444 loops=1)
                     Buffers: shared hit=1194 read=23668
               ->  Hash  (cost=8.44..8.44 rows=1 width=158) (actual time=0.047..0.047 rows=6 loops=1)
                     Buckets: 1024  Batches: 1  Memory Usage: 10kB
                     Buffers: shared hit=4
                     ->  Index Scan using idx_sidebarcategories_userid_teamid on sidebarcategories  (cost=0.42..8.44 rows=1 width=158) (actual time=0.029..0.037 rows=6 loops=1)
                           Index Cond: (((userid)::text = 'tc3p1yqw67d8idcp3g98awexqe'::text) AND ((teamid)::text = '3ee5y5ok6jgxicrmqstdnghmfr'::text))
                           Buffers: shared hit=4
 Planning:
   Buffers: shared hit=9
 Planning Time: 1.215 ms
 Execution Time: 251.755 ms
(31 rows)

After:
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Sort  (cost=1544.53..1544.54 rows=4 width=192) (actual time=0.834..0.859 rows=204 loops=1)
   Sort Key: sidebarcategories.sortorder, sidebarchannels.sortorder
   Sort Method: quicksort  Memory: 65kB
   Buffers: shared hit=58
   ->  Nested Loop Left Join  (cost=8.53..1544.49 rows=4 width=192) (actual time=0.066..0.252 rows=204 loops=1)
         Buffers: shared hit=58
         ->  Nested Loop  (cost=0.83..17.93 rows=1 width=157) (actual time=0.042..0.098 rows=6 loops=1)
               Buffers: shared hit=34
               ->  Nested Loop  (cost=0.42..9.48 rows=1 width=157) (actual time=0.030..0.049 rows=6 loops=1)
                     Buffers: shared hit=10
                     ->  Index Scan using idx_sidebarcategories_userid_teamid on sidebarcategories  (cost=0.42..8.44 rows=1 width=157) (actual time=0.018..0.022 rows=6 loops=1)
                           Index Cond: (((userid)::text = 'tc3p1yqw67d8idcp3g98awexqe'::text) AND ((teamid)::text = '3ee5y5ok6jgxicrmqstdnghmfr'::text))
                           Buffers: shared hit=4
                     ->  Seq Scan on teams  (cost=0.00..1.03 rows=1 width=27) (actual time=0.002..0.003 rows=1 loops=6)
                           Filter: (((id)::text = '3ee5y5ok6jgxicrmqstdnghmfr'::text) AND (deleteat = 0))
                           Rows Removed by Filter: 1
                           Buffers: shared hit=6
               ->  Index Scan using teammembers_pkey on teammembers  (cost=0.41..8.43 rows=1 width=27) (actual time=0.007..0.007 rows=1 loops=6)
                     Index Cond: (((teamid)::text = '3ee5y5ok6jgxicrmqstdnghmfr'::text) AND ((userid)::text = 'tc3p1yqw67d8idcp3g98awexqe'::text))
                     Filter: (deleteat = 0)
                     Buffers: shared hit=24
         ->  Bitmap Heap Scan on sidebarchannels  (cost=7.69..1522.35 rows=421 width=100) (actual time=0.012..0.017 rows=34 loops=6)
               Recheck Cond: ((categoryid)::text = (sidebarcategories.id)::text)
               Heap Blocks: exact=6
               Buffers: shared hit=24
               ->  Bitmap Index Scan on idx_sidebarchannels_categoryid  (cost=0.00..7.58 rows=421 width=0) (actual time=0.010..0.010 rows=34 loops=6)
                     Index Cond: ((categoryid)::text = (sidebarcategories.id)::text)
                     Buffers: shared hit=18
 Planning:
   Buffers: shared hit=18
 Planning Time: 0.543 ms
 Execution Time: 0.968 ms
(32 rows)
```

I have also looked at potentially re-ordering the JOINs to make
sidebarchannels and sidebarcategories JOIN earlier, but that didn't give
a major benefit.

Also looked at adding a compound index with (categoryid, sortorder) to improve
sorting performance, but that didn't give a major benefit from what the single
column index already gives.

The `completePopulatingCategoryChannelsT` query also partially benefits
from this. But the Postgres optimizer sometimes selects the index on categoryId
and sometimes on ChannelId, both giving equivalent performance. So there's no major
improvement there, but at the same time, no regression as well.

```
Original:
[bigdb] # EXPLAIN (ANALYZE, BUFFERS) SELECT Id FROM ChannelMembers LEFT JOIN Channels ON Channels.Id=ChannelMembers.ChannelId WHERE (ChannelMembers.UserId = 'tc3p1yqw67d8idcp3g98awexqe' AND Channels.Type IN ('D'
                                                                                                                                                                                                                ,'G') AND Channels.DeleteAt = 0 AND NOT EXISTS ( SELECT 1 FROM SidebarChannels JOIN SidebarCategories on SidebarChannels.CategoryId=SidebarCategories.Id WHERE (SidebarChannels.ChannelId = ChannelMembers.ChannelI
                                                                                                                                                                                                                                                                                                                                                                                d AND SidebarCategories.UserId = 'tc3p1yqw67d8idcp3g98awexqe' AND SidebarCategories.TeamId = '3ee5y5ok6jgxicrmqstdnghmfr') )) ORDER BY DisplayName ASC;
                                                                                            QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Sort  (cost=5864.68..5865.84 rows=463 width=40) (actual time=9.008..9.022 rows=39 loops=1)
   Sort Key: channels.displayname
   Sort Method: quicksort  Memory: 27kB
   Buffers: shared hit=2112
   ->  Nested Loop Anti Join  (cost=1.96..5844.18 rows=463 width=40) (actual time=0.188..8.932 rows=39 loops=1)
         Buffers: shared hit=2112
         ->  Nested Loop  (cost=0.99..3476.66 rows=463 width=67) (actual time=0.159..7.952 rows=39 loops=1)
               Buffers: shared hit=1956
               ->  Index Only Scan using idx_channelmembers_user_id_channel_id_last_viewed_at on channelmembers  (cost=0.56..40.78 rows=470 width=27) (actual time=0.036..0.467 rows=437 loops=1)
                     Index Cond: (userid = 'tc3p1yqw67d8idcp3g98awexqe'::text)
                     Heap Fetches: 45
                     Buffers: shared hit=208
               ->  Memoize  (cost=0.43..7.69 rows=1 width=40) (actual time=0.016..0.016 rows=0 loops=437)
                     Cache Key: channelmembers.channelid
                     Cache Mode: logical
                     Hits: 0  Misses: 437  Evictions: 0  Overflows: 0  Memory Usage: 42kB
                     Buffers: shared hit=1748
                     ->  Index Scan using channels_pkey on channels  (cost=0.42..7.68 rows=1 width=40) (actual time=0.015..0.015 rows=0 loops=437)
                           Index Cond: ((id)::text = (channelmembers.channelid)::text)
                           Filter: ((type = ANY ('{D,G}'::channel_type[])) AND (deleteat = 0))
                           Rows Removed by Filter: 1
                           Buffers: shared hit=1748
         ->  Nested Loop  (cost=0.97..5.10 rows=1 width=27) (actual time=0.023..0.023 rows=0 loops=39)
               Buffers: shared hit=156
               ->  Index Only Scan using sidebarchannels_pkey on sidebarchannels  (cost=0.55..4.56 rows=1 width=92) (actual time=0.022..0.022 rows=0 loops=39)
                     Index Cond: (channelid = (channelmembers.channelid)::text)
                     Heap Fetches: 0
                     Buffers: shared hit=156
               ->  Index Scan using sidebarcategories_pkey on sidebarcategories  (cost=0.42..0.48 rows=1 width=65) (never executed)
                     Index Cond: ((id)::text = (sidebarchannels.categoryid)::text)
                     Filter: (((userid)::text = 'tc3p1yqw67d8idcp3g98awexqe'::text) AND ((teamid)::text = '3ee5y5ok6jgxicrmqstdnghmfr'::text))
 Planning:
   Buffers: shared hit=48 dirtied=1
 Planning Time: 2.222 ms
 Execution Time: 9.142 ms
(35 rows)

New:
[bigdb] # EXPLAIN (ANALYZE, BUFFERS) SELECT Id FROM ChannelMembers LEFT JOIN Channels ON Channels.Id=ChannelMembers.ChannelId WHERE (ChannelMembers.UserId = 'tc3p1yqw67d8idcp3g98awexqe' AND Channels.Type IN ('D'
                                                                                                                                                                                                                ,'G') AND Channels.DeleteAt = 0 AND NOT EXISTS ( SELECT 1 FROM SidebarChannels JOIN SidebarCategories on SidebarChannels.CategoryId=SidebarCategories.Id WHERE (SidebarChannels.ChannelId = ChannelMembers.ChannelId AND SidebarCategories.UserId = 'tc3p1yqw67d8idcp3g98awexqe' AND SidebarCategories.TeamId = '3ee5y5ok6jgxicrmqstdnghmfr') )) ORDER BY DisplayName ASC;
                                                                                            QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Sort  (cost=5059.95..5061.11 rows=463 width=40) (actual time=12.072..12.086 rows=39 loops=1)
   Sort Key: channels.displayname
   Sort Method: quicksort  Memory: 27kB
   Buffers: shared hit=1984
   ->  Nested Loop Anti Join  (cost=9.10..5039.45 rows=463 width=40) (actual time=0.751..12.009 rows=39 loops=1)
         Join Filter: ((sidebarchannels.channelid)::text = (channelmembers.channelid)::text)
         Rows Removed by Join Filter: 7839
         Buffers: shared hit=1984
         ->  Nested Loop  (cost=0.99..3476.66 rows=463 width=67) (actual time=0.161..7.579 rows=39 loops=1)
               Buffers: shared hit=1956
               ->  Index Only Scan using idx_channelmembers_user_id_channel_id_last_viewed_at on channelmembers  (cost=0.56..40.78 rows=470 width=27) (actual time=0.036..0.449 rows=437 loops=1)
                     Index Cond: (userid = 'tc3p1yqw67d8idcp3g98awexqe'::text)
                     Heap Fetches: 45
                     Buffers: shared hit=208
               ->  Memoize  (cost=0.43..7.69 rows=1 width=40) (actual time=0.016..0.016 rows=0 loops=437)
                     Cache Key: channelmembers.channelid
                     Cache Mode: logical
                     Hits: 0  Misses: 437  Evictions: 0  Overflows: 0  Memory Usage: 42kB
                     Buffers: shared hit=1748
                     ->  Index Scan using channels_pkey on channels  (cost=0.42..7.68 rows=1 width=40) (actual time=0.014..0.014 rows=0 loops=437)
                           Index Cond: ((id)::text = (channelmembers.channelid)::text)
                           Filter: ((type = ANY ('{D,G}'::channel_type[])) AND (deleteat = 0))
                           Rows Removed by Filter: 1
                           Buffers: shared hit=1748
         ->  Materialize  (cost=8.11..1535.03 rows=4 width=27) (actual time=0.003..0.046 rows=201 loops=39)
               Buffers: shared hit=28
               ->  Nested Loop  (cost=8.11..1535.01 rows=4 width=27) (actual time=0.099..0.383 rows=201 loops=1)
                     Buffers: shared hit=28
                     ->  Index Scan using idx_sidebarcategories_userid_teamid on sidebarcategories  (cost=0.42..8.44 rows=1 width=65) (actual time=0.047..0.057 rows=6 loops=1)
                           Index Cond: (((userid)::text = 'tc3p1yqw67d8idcp3g98awexqe'::text) AND ((teamid)::text = '3ee5y5ok6jgxicrmqstdnghmfr'::text))
                           Buffers: shared hit=4
                     ->  Bitmap Heap Scan on sidebarchannels  (cost=7.69..1522.35 rows=421 width=92) (actual time=0.028..0.040 rows=34 loops=6)
                           Recheck Cond: ((categoryid)::text = (sidebarcategories.id)::text)
                           Heap Blocks: exact=6
                           Buffers: shared hit=24
                           ->  Bitmap Index Scan on idx_sidebarchannels_categoryid  (cost=0.00..7.58 rows=421 width=0) (actual time=0.023..0.023 rows=34 loops=6)
                                 Index Cond: ((categoryid)::text = (sidebarcategories.id)::text)
                                 Buffers: shared hit=18
 Planning:
   Buffers: shared hit=51
 Planning Time: 2.240 ms
 Execution Time: 12.210 ms
(42 rows)
```

Analysis on MySQL for completion:
```
Before:
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Sort: SidebarCategories.SortOrder, SidebarChannels.SortOrder  (actual time=277.675..277.675 rows=4 loops=1)
    -> Stream results  (cost=138558.36 rows=1287808) (actual time=242.506..277.650 rows=4 loops=1)
        -> Left hash join (<hash>(SidebarChannels.CategoryId)=<hash>(SidebarCategories.Id)), extra conditions: (SidebarChannels.CategoryId = SidebarCategories.Id)  (cost=138558.36 rows=1287808) (actual time=242.498..277.626 rows=4 loops=1)
            -> Index lookup on SidebarCategories using idx_sidebarcategories_userid_teamid (UserId='qdggj9pyobgkjpj8htwzizks1r', TeamId='xmh7bupzajnudqf3h4mm76qapy')  (cost=1.40 rows=4) (actual time=0.092..0.094 rows=4 loops=1)
            -> Hash
                -> Table scan on SidebarChannels  (cost=8394.55 rows=321952) (actual time=0.123..106.334 rows=300002 loops=1)
 |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

After:
----------------------------------------------------------------+
| -> Sort: SidebarCategories.SortOrder, SidebarChannels.SortOrder  (actual time=0.739..0.742 rows=4 loops=1)
    -> Stream results  (cost=6.80 rows=7) (actual time=0.468..0.703 rows=4 loops=1)
        -> Nested loop left join  (cost=6.80 rows=7) (actual time=0.456..0.673 rows=4 loops=1)
            -> Index lookup on SidebarCategories using idx_sidebarcategories_userid_teamid (UserId='qdggj9pyobgkjpj8htwzizks1r', TeamId='xmh7bupzajnudqf3h4mm76qapy')  (cost=4.38 rows=4) (actual time=0.302..0.313 rows=4 loops=1)
            -> Index lookup on SidebarChannels using idx_sidebarchannels_categoryid (CategoryId=SidebarCategories.Id)  (cost=0.48 rows=2) (actual time=0.085..0.087 rows=0 loops=4)
 |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
```

Timing wise, it takes around 2s to add the index on a table with 1.2M rows
for Postgres. And it takes around 5s on MySQL on a table with 300K rows.
It looks like it takes longer on MySQL, but since both migrations are
non-locking, it should be fine.

https://mattermost.atlassian.net/browse/MM-63756
```release-note
NONE
```
2025-04-24 12:11:28 +05:30