diff --git a/.github/codecov.yml b/.github/codecov.yml
index cbc4185fa81..36d71df8cf2 100644
--- a/.github/codecov.yml
+++ b/.github/codecov.yml
@@ -1,17 +1,42 @@
-comment:
- layout: "condensed_header, condensed_files, condensed_footer"
- behavior: default
- require_changes: "uncovered_patch" # only post comment if the patch has uncovered lines
- hide_project_coverage: true # only show coverage on the git diff
+codecov:
+ require_ci_to_pass: false
+ # Wait for all coverage uploads (4 server shards + 1 webapp) before
+ # computing status. Without this, Codecov may report partial coverage
+ # from the first shard to finish, showing a misleading drop on the PR.
+ notify:
+ after_n_builds: 5
+
coverage:
status:
- changes: false
- patch: false
project:
default:
- threshold: 1.0
-codecov:
- notify:
- after_n_builds: 2 # Server and webapp at this point
-ignore:
- - ^store/storetest.*
+ target: auto
+ threshold: 1%
+ informational: true
+ patch:
+ default:
+ target: 50%
+ informational: true
+
+ # Exclude generated code, mocks, and test infrastructure from reporting.
+ # Go compiles these into the test binary, so they appear in cover.out,
+ # but they aren't production code and inflate the denominator.
+ ignore:
+ - "server/**/retrylayer/**"
+ - "server/**/timerlayer/**"
+ - "server/**/*_serial_gen.go"
+ - "server/**/mocks/**"
+ - "server/**/storetest/**"
+ - "server/**/plugintest/**"
+ - "server/**/searchtest/**"
+
+flags:
+ server:
+ after_n_builds: 4 # 4 server test shards
+ webapp:
+ after_n_builds: 1 # 1 merged webapp upload
+
+comment:
+ layout: "condensed_header,diff,flags"
+ behavior: default
+ require_changes: true
diff --git a/.github/workflows/server-ci.yml b/.github/workflows/server-ci.yml
index 8c9f3980708..e0efe7c27db 100644
--- a/.github/workflows/server-ci.yml
+++ b/.github/workflows/server-ci.yml
@@ -208,6 +208,7 @@ jobs:
logsartifact: postgres-binary-server-test-logs
go-version: ${{ needs.go.outputs.version }}
fips-enabled: false
+ fullyparallel: false
# -- Sharded into 4 parallel runners for ~88% wall-time improvement --
test-postgres-normal:
name: Postgres (shard ${{ matrix.shard }})
@@ -270,22 +271,27 @@ jobs:
artifact-name: postgres-server-fips-test-logs
test-coverage:
- name: Generate Test Coverage
- # Disabled: Running out of memory and causing spurious failures.
- # Old condition: ${{ github.event_name != 'pull_request' || !startsWith(github.event.pull_request.base.ref, 'release-') }}
- if: false
+ name: "Coverage (shard ${{ matrix.shard }})"
+ if: ${{ github.event_name != 'pull_request' || !startsWith(github.event.pull_request.base.ref, 'release-') }}
needs: go
+ strategy:
+ fail-fast: false
+ matrix:
+ shard: [0, 1, 2, 3]
uses: ./.github/workflows/server-test-template.yml
secrets: inherit
with:
- name: Generate Test Coverage
+ name: "Coverage (shard ${{ matrix.shard }})"
datasource: postgres://mmuser:mostest@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10
drivername: postgres
- logsartifact: coverage-server-test-logs
+ logsartifact: "coverage-server-test-logs-shard-${{ matrix.shard }}"
fullyparallel: true
allow-failure: true
enablecoverage: true
go-version: ${{ needs.go.outputs.version }}
+ fips-enabled: false
+ shard-index: ${{ matrix.shard }}
+ shard-total: 4
test-mmctl:
name: Run mmctl tests
needs: go
diff --git a/.github/workflows/server-test-template.yml b/.github/workflows/server-test-template.yml
index 2e67e79aa2a..3d0f9abd7f4 100644
--- a/.github/workflows/server-test-template.yml
+++ b/.github/workflows/server-test-template.yml
@@ -222,6 +222,7 @@ jobs:
token: ${{ secrets.CODECOV_TOKEN }}
disable_search: true
files: server/cover.out
+ flags: server
- name: Stop docker compose
run: |
diff --git a/.github/workflows/webapp-ci.yml b/.github/workflows/webapp-ci.yml
index 65fa9ab9f8d..869413d7e58 100644
--- a/.github/workflows/webapp-ci.yml
+++ b/.github/workflows/webapp-ci.yml
@@ -223,6 +223,7 @@ jobs:
token: ${{ secrets.CODECOV_TOKEN }}
disable_search: true
files: ./webapp/channels/coverage/merged/lcov.info
+ flags: webapp
build:
needs: check-lint
diff --git a/e2e-tests/cypress/tests/integration/channels/auth_sso/authentication_4_spec.ts b/e2e-tests/cypress/tests/integration/channels/auth_sso/authentication_4_spec.ts
index 64234aecb66..24d8a049b89 100644
--- a/e2e-tests/cypress/tests/integration/channels/auth_sso/authentication_4_spec.ts
+++ b/e2e-tests/cypress/tests/integration/channels/auth_sso/authentication_4_spec.ts
@@ -209,12 +209,8 @@ describe('Authentication', () => {
// # Go to front page
cy.visit('/login');
- // * Assert that create account button is visible
- cy.findByText('Don\'t have an account?', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible').click();
-
- // * Verify redirection to access problem page since account creation is disabled
- cy.url().should('include', '/access_problem');
- cy.findByText('Contact your workspace admin');
+ // * Assert that create account button is not visible
+ cy.findByText('Don\'t have an account?', {timeout: TIMEOUTS.ONE_MIN}).should('not.exist');
// # Go to sign up with email page
cy.visit('/signup_user_complete');
diff --git a/e2e-tests/cypress/tests/integration/channels/signin_authentication/login_close_server_spec.js b/e2e-tests/cypress/tests/integration/channels/signin_authentication/login_close_server_spec.js
index db4a64eebb9..83011c91130 100644
--- a/e2e-tests/cypress/tests/integration/channels/signin_authentication/login_close_server_spec.js
+++ b/e2e-tests/cypress/tests/integration/channels/signin_authentication/login_close_server_spec.js
@@ -36,8 +36,7 @@ describe('Login page with close server', () => {
// Restore backed up settings
cy.apiAdminLogin().apiUpdateConfig(oldSettings);
});
- it('MM-47222 Should verify access problem page can be reached', () => {
- cy.findByText('Don\'t have an account?').should('be.visible').click();
- cy.findByText('Contact your workspace admin').should('be.visible');
+ it('MM-47222 Should verify signup link not visible', () => {
+ cy.findByText('Don\'t have an account?').should('not.exist');
});
});
diff --git a/e2e-tests/cypress/tests/integration/channels/system_console/ui_and_api/customization_spec.js b/e2e-tests/cypress/tests/integration/channels/system_console/ui_and_api/customization_spec.js
index d956613d9b8..8873927b318 100644
--- a/e2e-tests/cypress/tests/integration/channels/system_console/ui_and_api/customization_spec.js
+++ b/e2e-tests/cypress/tests/integration/channels/system_console/ui_and_api/customization_spec.js
@@ -49,10 +49,28 @@ describe('Customization', () => {
// # Save setting
saveSetting();
- // # Verify that after page reload image exist
cy.reload();
cy.findByTestId('CustomBrandImage').should('be.visible').within(() => {
+ // * Verify that after page reload image exist
cy.get('img').should('have.attr', 'src').and('include', '/api/v4/brand/image?t=');
+
+ // * Verify that there's an option to delete the image.
+ cy.findByTestId('remove-image__btn').should('be.visible');
+
+ // # delete the image
+ cy.findByTestId('remove-image__btn').click();
+ });
+
+ // # Save setting
+ saveSetting();
+
+ cy.reload();
+ cy.findByTestId('CustomBrandImage').should('be.visible').within(() => {
+ // * Verify that after page reload, the image doesn't exist.
+ cy.findByAltText('brand image').should('not.exist');
+
+ // * Verify there's no option to delete the image.
+ cy.findByTestId('remove-image__btn').should('not.exist');
});
});
diff --git a/server/Makefile b/server/Makefile
index d345fc31f19..d125a89dbd0 100644
--- a/server/Makefile
+++ b/server/Makefile
@@ -165,7 +165,7 @@ PLUGIN_PACKAGES += mattermost-plugin-playbooks-v2.8.0
PLUGIN_PACKAGES += mattermost-plugin-servicenow-v2.4.0
PLUGIN_PACKAGES += mattermost-plugin-zoom-v1.12.0
PLUGIN_PACKAGES += mattermost-plugin-agents-v1.7.2
-PLUGIN_PACKAGES += mattermost-plugin-boards-v9.2.2
+PLUGIN_PACKAGES += mattermost-plugin-boards-v9.2.4
PLUGIN_PACKAGES += mattermost-plugin-user-survey-v1.1.1
PLUGIN_PACKAGES += mattermost-plugin-mscalendar-v1.6.0
PLUGIN_PACKAGES += mattermost-plugin-msteams-meetings-v2.4.1
diff --git a/server/channels/api4/team.go b/server/channels/api4/team.go
index 4d731c7e8b8..8676c26cce3 100644
--- a/server/channels/api4/team.go
+++ b/server/channels/api4/team.go
@@ -657,6 +657,10 @@ func getTeamMember(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
+ if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), c.Params.TeamId, model.PermissionManageTeamRoles) {
+ team.SanitizeRoleData(c.AppContext.Session().UserId)
+ }
+
if err := json.NewEncoder(w).Encode(team); err != nil {
c.Logger.Warn("Error while writing response", mlog.Err(err))
}
@@ -695,6 +699,13 @@ func getTeamMembers(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
+ currentUserId := c.AppContext.Session().UserId
+ if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), c.Params.TeamId, model.PermissionManageTeamRoles) {
+ for _, m := range members {
+ m.SanitizeRoleData(currentUserId)
+ }
+ }
+
js, err := json.Marshal(members)
if err != nil {
c.Err = model.NewAppError("getTeamMembers", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
@@ -734,6 +745,13 @@ func getTeamMembersForUser(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
+ currentUserId := c.AppContext.Session().UserId
+ for _, m := range members {
+ if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), m.TeamId, model.PermissionManageTeamRoles) {
+ m.SanitizeRoleData(currentUserId)
+ }
+ }
+
js, err := json.Marshal(members)
if err != nil {
c.Err = model.NewAppError("getTeamMembersForUser", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
@@ -777,6 +795,13 @@ func getTeamMembersByIds(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
+ currentUserId := c.AppContext.Session().UserId
+ if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), c.Params.TeamId, model.PermissionManageTeamRoles) {
+ for _, m := range members {
+ m.SanitizeRoleData(currentUserId)
+ }
+ }
+
js, err := json.Marshal(members)
if err != nil {
c.Err = model.NewAppError("getTeamMembersByIds", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
@@ -883,6 +908,10 @@ func addTeamMember(c *Context, w http.ResponseWriter, r *http.Request) {
auditRec.AddEventObjectType("team_member") // TODO verify this is the final state. should it be the team instead?
auditRec.Success()
+ if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), c.Params.TeamId, model.PermissionManageTeamRoles) {
+ tm.SanitizeRoleData(c.AppContext.Session().UserId)
+ }
+
w.WriteHeader(http.StatusCreated)
if err := json.NewEncoder(w).Encode(tm); err != nil {
c.Logger.Warn("Error while writing response", mlog.Err(err))
@@ -1037,6 +1066,15 @@ func addTeamMembers(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
+ currentUserId := c.AppContext.Session().UserId
+ if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), c.Params.TeamId, model.PermissionManageTeamRoles) {
+ for _, m := range membersWithErrors {
+ if m.Member != nil {
+ m.Member.SanitizeRoleData(currentUserId)
+ }
+ }
+ }
+
var (
js []byte
err error
diff --git a/server/channels/api4/team_test.go b/server/channels/api4/team_test.go
index 4070366030c..06305a34363 100644
--- a/server/channels/api4/team_test.go
+++ b/server/channels/api4/team_test.go
@@ -4609,3 +4609,247 @@ func TestInvalidateAllEmailInvites(t *testing.T) {
CheckOKStatus(t, res)
})
}
+
+func setupTeamWithAdminAndMember(t *testing.T, th *TestHelper) *model.Client4 {
+ t.Helper()
+ th.UpdateUserToTeamAdmin(t, th.BasicUser2, th.BasicTeam)
+ require.Nil(t, th.App.Srv().InvalidateAllCaches())
+ teamAdminClient := th.CreateClient()
+ _, _, err := teamAdminClient.Login(context.Background(), th.BasicUser2.Email, th.BasicUser2.Password)
+ require.NoError(t, err)
+ return teamAdminClient
+}
+
+func assertRoleDataSanitized(t *testing.T, m *model.TeamMember) {
+ t.Helper()
+ assert.Empty(t, m.Roles)
+ assert.Empty(t, m.ExplicitRoles)
+ assert.False(t, m.SchemeAdmin)
+ assert.False(t, m.SchemeGuest)
+ assert.False(t, m.SchemeUser)
+ assert.Equal(t, int64(-1), m.DeleteAt)
+}
+
+func TestGetTeamMembersRoleDataSanitization(t *testing.T) {
+ mainHelper.Parallel(t)
+ th := Setup(t).InitBasic(t)
+ teamAdminClient := setupTeamWithAdminAndMember(t, th)
+
+ t.Run("non-admin cannot see role data of others", func(t *testing.T) {
+ members, _, err := th.Client.GetTeamMembers(context.Background(), th.BasicTeam.Id, 0, 100, "")
+ require.NoError(t, err)
+
+ for _, m := range members {
+ if m.UserId != th.BasicUser.Id {
+ assertRoleDataSanitized(t, m)
+ }
+ }
+ })
+
+ t.Run("non-admin sees own role data", func(t *testing.T) {
+ members, _, err := th.Client.GetTeamMembers(context.Background(), th.BasicTeam.Id, 0, 100, "")
+ require.NoError(t, err)
+
+ for _, m := range members {
+ if m.UserId == th.BasicUser.Id {
+ assert.True(t, m.SchemeUser)
+ return
+ }
+ }
+ require.Fail(t, "current user not found in members")
+ })
+
+ t.Run("team admin sees full role data for other user", func(t *testing.T) {
+ members, _, err := teamAdminClient.GetTeamMembers(context.Background(), th.BasicTeam.Id, 0, 100, "")
+ require.NoError(t, err)
+
+ for _, m := range members {
+ if m.UserId == th.BasicUser.Id {
+ assert.True(t, m.SchemeUser)
+ return
+ }
+ }
+ require.Fail(t, "target user not found in members")
+ })
+
+ t.Run("system admin sees full role data", func(t *testing.T) {
+ members, _, err := th.SystemAdminClient.GetTeamMembers(context.Background(), th.BasicTeam.Id, 0, 100, "")
+ require.NoError(t, err)
+
+ for _, m := range members {
+ if m.UserId == th.BasicUser2.Id {
+ assert.True(t, m.SchemeAdmin)
+ return
+ }
+ }
+ require.Fail(t, "team admin not found in members")
+ })
+}
+
+func TestGetTeamMemberRoleDataSanitization(t *testing.T) {
+ mainHelper.Parallel(t)
+ th := Setup(t).InitBasic(t)
+ teamAdminClient := setupTeamWithAdminAndMember(t, th)
+
+ t.Run("non-admin cannot see role data of others", func(t *testing.T) {
+ member, _, err := th.Client.GetTeamMember(context.Background(), th.BasicTeam.Id, th.BasicUser2.Id, "")
+ require.NoError(t, err)
+ assertRoleDataSanitized(t, member)
+ })
+
+ t.Run("non-admin sees own role data", func(t *testing.T) {
+ member, _, err := th.Client.GetTeamMember(context.Background(), th.BasicTeam.Id, th.BasicUser.Id, "")
+ require.NoError(t, err)
+ assert.True(t, member.SchemeUser)
+ })
+
+ t.Run("team admin sees full role data for other user", func(t *testing.T) {
+ member, _, err := teamAdminClient.GetTeamMember(context.Background(), th.BasicTeam.Id, th.BasicUser.Id, "")
+ require.NoError(t, err)
+ assert.True(t, member.SchemeUser)
+ })
+
+ t.Run("system admin sees full role data", func(t *testing.T) {
+ member, _, err := th.SystemAdminClient.GetTeamMember(context.Background(), th.BasicTeam.Id, th.BasicUser2.Id, "")
+ require.NoError(t, err)
+ assert.True(t, member.SchemeAdmin)
+ })
+}
+
+func TestGetTeamMembersByIdsRoleDataSanitization(t *testing.T) {
+ mainHelper.Parallel(t)
+ th := Setup(t).InitBasic(t)
+ teamAdminClient := setupTeamWithAdminAndMember(t, th)
+
+ t.Run("non-admin cannot see role data of others", func(t *testing.T) {
+ members, _, err := th.Client.GetTeamMembersByIds(context.Background(), th.BasicTeam.Id, []string{th.BasicUser2.Id})
+ require.NoError(t, err)
+ require.Len(t, members, 1)
+ assertRoleDataSanitized(t, members[0])
+ })
+
+ t.Run("non-admin sees own role data", func(t *testing.T) {
+ members, _, err := th.Client.GetTeamMembersByIds(context.Background(), th.BasicTeam.Id, []string{th.BasicUser.Id})
+ require.NoError(t, err)
+ require.Len(t, members, 1)
+ assert.True(t, members[0].SchemeUser)
+ })
+
+ t.Run("team admin sees full role data for other user", func(t *testing.T) {
+ members, _, err := teamAdminClient.GetTeamMembersByIds(context.Background(), th.BasicTeam.Id, []string{th.BasicUser.Id})
+ require.NoError(t, err)
+ require.Len(t, members, 1)
+ assert.True(t, members[0].SchemeUser)
+ })
+
+ t.Run("system admin sees full role data", func(t *testing.T) {
+ members, _, err := th.SystemAdminClient.GetTeamMembersByIds(context.Background(), th.BasicTeam.Id, []string{th.BasicUser2.Id})
+ require.NoError(t, err)
+ require.Len(t, members, 1)
+ assert.True(t, members[0].SchemeAdmin)
+ })
+}
+
+func TestAddTeamMemberRoleDataSanitization(t *testing.T) {
+ mainHelper.Parallel(t)
+ th := Setup(t).InitBasic(t)
+ teamAdminClient := setupTeamWithAdminAndMember(t, th)
+
+ t.Run("team admin adding user sees full role data in response", func(t *testing.T) {
+ newUser := th.CreateUser(t)
+ tm, _, err := teamAdminClient.AddTeamMember(context.Background(), th.BasicTeam.Id, newUser.Id)
+ require.NoError(t, err)
+ assert.True(t, tm.SchemeUser)
+ })
+
+ t.Run("non-admin adding user sees sanitized role data in response", func(t *testing.T) {
+ defaultRolePermissions := th.SaveDefaultRolePermissions(t)
+ defer th.RestoreDefaultRolePermissions(t, defaultRolePermissions)
+ th.AddPermissionToRole(t, model.PermissionAddUserToTeam.Id, model.TeamUserRoleId)
+
+ newUser := th.CreateUser(t)
+ tm, _, err := th.Client.AddTeamMember(context.Background(), th.BasicTeam.Id, newUser.Id)
+ require.NoError(t, err)
+ assertRoleDataSanitized(t, tm)
+ })
+}
+
+func TestAddTeamMembersRoleDataSanitization(t *testing.T) {
+ mainHelper.Parallel(t)
+ th := Setup(t).InitBasic(t)
+ teamAdminClient := setupTeamWithAdminAndMember(t, th)
+
+ t.Run("team admin adding users sees full role data in response", func(t *testing.T) {
+ newUser := th.CreateUser(t)
+ members, _, err := teamAdminClient.AddTeamMembers(context.Background(), th.BasicTeam.Id, []string{newUser.Id})
+ require.NoError(t, err)
+ require.Len(t, members, 1)
+ assert.True(t, members[0].SchemeUser)
+ })
+
+ t.Run("non-admin adding users sees sanitized role data in response", func(t *testing.T) {
+ defaultRolePermissions := th.SaveDefaultRolePermissions(t)
+ defer th.RestoreDefaultRolePermissions(t, defaultRolePermissions)
+ th.AddPermissionToRole(t, model.PermissionAddUserToTeam.Id, model.TeamUserRoleId)
+
+ newUser := th.CreateUser(t)
+ members, _, err := th.Client.AddTeamMembers(context.Background(), th.BasicTeam.Id, []string{newUser.Id})
+ require.NoError(t, err)
+ require.Len(t, members, 1)
+ assertRoleDataSanitized(t, members[0])
+ })
+}
+
+func TestGetTeamMembersForUserRoleDataSanitization(t *testing.T) {
+ mainHelper.Parallel(t)
+ th := Setup(t).InitBasic(t)
+ teamAdminClient := setupTeamWithAdminAndMember(t, th)
+
+ t.Run("user sees own role data", func(t *testing.T) {
+ members, _, err := th.Client.GetTeamMembersForUser(context.Background(), th.BasicUser.Id, "")
+ require.NoError(t, err)
+ require.NotEmpty(t, members)
+ for _, m := range members {
+ assert.True(t, m.SchemeUser)
+ }
+ })
+
+ t.Run("non-admin cannot see role data of another user", func(t *testing.T) {
+ defaultRolePermissions := th.SaveDefaultRolePermissions(t)
+ defer th.RestoreDefaultRolePermissions(t, defaultRolePermissions)
+ th.AddPermissionToRole(t, model.PermissionReadOtherUsersTeams.Id, model.SystemUserRoleId)
+
+ members, _, err := th.Client.GetTeamMembersForUser(context.Background(), th.BasicUser2.Id, "")
+ require.NoError(t, err)
+ require.NotEmpty(t, members)
+ for _, m := range members {
+ assertRoleDataSanitized(t, m)
+ }
+ })
+
+ t.Run("team admin sees full role data for other user in managed team", func(t *testing.T) {
+ members, _, err := teamAdminClient.GetTeamMembersForUser(context.Background(), th.BasicUser.Id, "")
+ require.NoError(t, err)
+ require.NotEmpty(t, members)
+ for _, m := range members {
+ if m.TeamId == th.BasicTeam.Id {
+ assert.True(t, m.SchemeUser)
+ return
+ }
+ }
+ require.Fail(t, "basic team membership not found")
+ })
+
+ t.Run("system admin sees full role data", func(t *testing.T) {
+ members, _, err := th.SystemAdminClient.GetTeamMembersForUser(context.Background(), th.BasicUser2.Id, "")
+ require.NoError(t, err)
+ require.NotEmpty(t, members)
+ for _, m := range members {
+ if m.TeamId == th.BasicTeam.Id {
+ assert.True(t, m.SchemeAdmin)
+ return
+ }
+ }
+ require.Fail(t, "basic team membership not found")
+ })
+}
diff --git a/server/public/model/team_member.go b/server/public/model/team_member.go
index 53cf25f072b..59c73936794 100644
--- a/server/public/model/team_member.go
+++ b/server/public/model/team_member.go
@@ -142,3 +142,14 @@ func (o *TeamMember) PreUpdate() {
func (o *TeamMember) GetRoles() []string {
return strings.Fields(o.Roles)
}
+
+func (o *TeamMember) SanitizeRoleData(currentUserId string) {
+ if o.UserId != currentUserId {
+ o.Roles = ""
+ o.ExplicitRoles = ""
+ o.SchemeAdmin = false
+ o.SchemeGuest = false
+ o.SchemeUser = false
+ o.DeleteAt = -1
+ }
+}
diff --git a/server/scripts/run-shard-tests.sh b/server/scripts/run-shard-tests.sh
index 57c55ab059d..688e3f2019e 100755
--- a/server/scripts/run-shard-tests.sh
+++ b/server/scripts/run-shard-tests.sh
@@ -108,6 +108,20 @@ fi
cat gotestsum-*.json > gotestsum.json 2>/dev/null || true
+# ── Merge coverage profiles within this shard (if coverage is enabled) ──
+# A single shard may run multiple gotestsum invocations (light packages +
+# heavy package splits), each producing its own cover-N.out. This merges
+# them into one cover.out per shard. The cross-shard merge (combining all
+# shards into a single report) is handled by Codecov's after_n_builds.
+if [[ "${ENABLE_COVERAGE:-false}" == "true" ]] && ls cover-*.out 1>/dev/null 2>&1; then
+ echo "Merging coverage profiles..."
+ {
+ head -1 cover-0.out # "mode: atomic" header
+ tail -q -n +2 cover-*.out # data lines from all files
+ } > cover.out
+ echo "Merged $(ls cover-*.out | wc -l) coverage files into cover.out"
+fi
+
if [[ $FAILURES -gt 0 ]]; then
echo "Shard complete: $RUN_IDX gotestsum runs, $FAILURES failed"
exit 1
diff --git a/webapp/channels/src/components/access_problem/access_problem.scss b/webapp/channels/src/components/access_problem/access_problem.scss
deleted file mode 100644
index 7386478f691..00000000000
--- a/webapp/channels/src/components/access_problem/access_problem.scss
+++ /dev/null
@@ -1,40 +0,0 @@
-.header-footer-route .header-footer-route-container {
- display: flex;
- justify-content: space-between;
-}
-
-.AccessProblem {
- &__body {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- }
-
- &__title {
- display: flex;
- align-items: center;
- margin: 32px 0 16px 0;
- color: var(--portal-denim);
- font-family: 'Metropolis';
- font-size: 22px;
- font-style: normal;
- font-weight: 600;
- line-height: 28px;
- text-align: center;
- }
-
- &__description {
- display: flex;
- max-width: 640px;
- align-items: center;
- padding: 0 40px;
- color: var(--portal-denim);
- font-family: 'Open Sans';
- font-size: 14px;
- font-style: normal;
- font-weight: 400;
- line-height: 20px;
- text-align: center;
- }
-}
diff --git a/webapp/channels/src/components/access_problem/index.tsx b/webapp/channels/src/components/access_problem/index.tsx
deleted file mode 100644
index 05429c7fbe0..00000000000
--- a/webapp/channels/src/components/access_problem/index.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See LICENSE.txt for license information.
-
-import React, {useCallback, useEffect} from 'react';
-import {useIntl} from 'react-intl';
-import {useHistory} from 'react-router-dom';
-
-import AccessProblemSVG from 'components/common/svg_images_components/access_problem_svg';
-import type {CustomizeHeaderType} from 'components/header_footer_route/header_footer_route';
-
-import './access_problem.scss';
-
-type AccessProblemProps = {
- onCustomizeHeader?: CustomizeHeaderType;
-}
-
-const AccessProblem = ({
- onCustomizeHeader,
-}: AccessProblemProps) => {
- const {formatMessage} = useIntl();
- const history = useHistory();
-
- const handleHeaderBackButtonOnClick = useCallback(() => {
- history.goBack();
- }, [history]);
-
- useEffect(() => {
- if (onCustomizeHeader) {
- onCustomizeHeader({
- onBackButtonClick: handleHeaderBackButtonOnClick,
- });
- }
- }, [onCustomizeHeader, handleHeaderBackButtonOnClick]);
-
- return (
-
-
-
- {formatMessage({id: 'login.contact_admin.title', defaultMessage: 'Contact your workspace admin'})}
-
-
- {formatMessage({id: 'login.contact_admin.detail', defaultMessage: "To access your team's workspace, contact your workspace admin. If you've been invited already, check your email inbox for a Mattermost workspace invite."})}
-