diff --git a/api/Makefile b/api/Makefile index c151e94736..17db4e4a76 100644 --- a/api/Makefile +++ b/api/Makefile @@ -11,7 +11,6 @@ build-v4: node_modules playbooks @echo Building mattermost openapi yaml for v4 @if [ -r $(PLAYBOOKS_SRC)/merged-tags.yaml ]; then cat $(PLAYBOOKS_SRC)/merged-tags.yaml > $(V4_YAML); else cat $(V4_SRC)/introduction.yaml > $(V4_YAML); fi - @cat $(V4_SRC)/insights.yaml >> $(V4_YAML) @cat $(V4_SRC)/users.yaml >> $(V4_YAML) @cat $(V4_SRC)/status.yaml >> $(V4_YAML) @cat $(V4_SRC)/teams.yaml >> $(V4_YAML) diff --git a/api/v4/source/definitions.yaml b/api/v4/source/definitions.yaml index 1754b75f51..e36868ba63 100644 --- a/api/v4/source/definitions.yaml +++ b/api/v4/source/definitions.yaml @@ -797,16 +797,6 @@ components: description: The time in milliseconds this reaction was made type: integer format: int64 - TopReaction: - type: object - properties: - emoji_name: - description: The name of the emoji used for this reaction. - type: string - count: - description: The number of the times this emoji has been used. - type: integer - format: int64 NewTeamMember: type: object properties: @@ -827,17 +817,6 @@ components: create_at: description: The creation timestamp of the team member record. type: integer - TopReactionList: - type: object - properties: - has_next: - description: Indicates if there is another page of reactions that can be fetched. - type: boolean - items: - description: List of reactions. - type: array - items: - $ref: "#/components/schemas/TopReaction" NewTeamMembersList: type: object properties: @@ -852,109 +831,6 @@ components: total_count: description: The total count of new team members for the given time range. type: integer - TopChannel: - type: object - properties: - id: - type: string - type: - type: string - display_name: - type: string - name: - type: string - team_id: - type: string - message_count: - description: The number of messages posted in the channel by users over the given time period (not including messages posted by bots). - type: string - TopChannelList: - type: object - properties: - has_next: - description: Indicates if there is another page of channels that can be fetched. - type: boolean - items: - description: List of channels. - type: array - items: - $ref: "#/components/schemas/TopChannel" - InsightUserInformation: - type: object - properties: - id: - type: string - first_name: - type: string - last_name: - type: string - nickname: - type: string - username: - type: string - last_picture_update: - type: string - create_at: - type: integer - format: int64 - TopThread: - type: object - properties: - post: - $ref: "#/components/schemas/Post" - channel_id: - type: string - channel_display_name: - type: string - channel_name: - type: string - Participants: - type: array - items: - type: string - user_information: - description: User who created the post - $ref: "#/components/schemas/InsightUserInformation" - TopThreadList: - type: object - properties: - has_next: - description: Indicates if there is another page of top threads that can be fetched. - type: boolean - items: - description: List of top threads. - type: array - items: - $ref: "#/components/schemas/TopThread" - TopDMInsightUserInformation: - allOf: - - $ref: "#/components/schemas/InsightUserInformation" - - type: object - properties: - position: - type: string - TopDM: - type: object - properties: - post_count: - type: integer - format: int64 - outgoing_message_count: - type: integer - format: int64 - second_participant: - $ref: "#/components/schemas/TopDMInsightUserInformation" - TopDMList: - type: object - properties: - has_next: - description: Indicates if there is another page of top DMs that can be fetched. - type: boolean - items: - description: List of top DMs. - type: array - items: - $ref: "#/components/schemas/TopDM" Emoji: type: object properties: diff --git a/api/v4/source/insights.yaml b/api/v4/source/insights.yaml deleted file mode 100644 index a0026c2013..0000000000 --- a/api/v4/source/insights.yaml +++ /dev/null @@ -1,439 +0,0 @@ - /api/v4/teams/{team_id}/top/reactions: - get: - tags: - - insights - summary: Get a list of the top reactions for a team. - description: | - Get a list of the top reactions across all public and private channels (the user is a member of) for a given team. - ##### Permissions - Must have `view_team` permission for the team. - operationId: GetTopReactionsForTeam - parameters: - - name: team_id - in: path - description: Team GUID - required: true - schema: - type: string - - name: time_range - in: query - description: > - Time range can be "today", "7_day", or "28_day". - - - `today`: reactions posted on the current day. - - - `7_day`: reactions posted in the last 7 days. - - - `28_day`: reactions posted in the last 28 days. - required: true - schema: - type: string - - name: page - in: query - description: The page to select. - schema: - type: integer - default: 0 - - name: per_page - in: query - description: The number of items per page, up to a maximum of 200. - schema: - type: integer - default: 60 - responses: - "200": - description: Top reactions retrieved successfully. - content: - application/json: - schema: - type: object - $ref: "#/components/schemas/TopReactionList" - "400": - $ref: "#/components/responses/BadRequest" - "403": - $ref: "#/components/responses/Forbidden" - /api/v4/users/me/top/reactions: - get: - tags: - - insights - summary: Get a list of the top reactions for a user. - description: | - Get a list of the top reactions across all public and private channels (the user is a member of) for a given user. - If no `team_id` is provided, this will also include reactions posted by the given user in direct and group messages. - ##### Permissions - Must be logged in as the user. - operationId: GetTopReactionsForUser - parameters: - - name: time_range - in: query - description: > - Time range can be "today", "7_day", or "28_day". - - - `today`: reactions posted on the current day. - - - `7_day`: reactions posted in the last 7 days. - - - `28_day`: reactions posted in the last 28 days. - required: true - schema: - type: string - - name: page - in: query - description: The page to select. - schema: - type: integer - default: 0 - - name: per_page - in: query - description: The number of items per page, up to a maximum of 200. - schema: - type: integer - default: 60 - - name: team_id - in: query - description: > - Team ID will scope the response to a given team and exclude direct and group messages. - - ##### Permissions - - Must have `view_team` permission for the team. - schema: - type: string - responses: - "200": - description: Top reactions retrieved successfully. - content: - application/json: - schema: - type: object - $ref: "#/components/schemas/TopReactionList" - "400": - $ref: "#/components/responses/BadRequest" - "403": - $ref: "#/components/responses/Forbidden" - /api/v4/teams/{team_id}/top/channels: - get: - tags: - - insights - summary: Get a list of the top channels for a team. - description: | - Get a list of the top public and private channels (the user is a member of) for a given team. - ##### Permissions - Must have `view_team` permission for the team. - operationId: GetTopChannelsForTeam - parameters: - - name: team_id - in: path - description: Team GUID - required: true - schema: - type: string - - name: time_range - in: query - description: > - Time range can be "today", "7_day", or "28_day". - - - `today`: channels with posts on the current day. - - - `7_day`: channels with posts in the last 7 days. - - - `28_day`: channels with posts in the last 28 days. - required: true - schema: - type: string - - name: page - in: query - description: The page to select. - schema: - type: integer - default: 0 - - name: per_page - in: query - description: The number of items per page, up to a maximum of 200. - schema: - type: integer - default: 60 - responses: - "200": - description: Top channels retrieved successfully. - content: - application/json: - schema: - type: object - $ref: "#/components/schemas/TopChannelList" - "400": - $ref: "#/components/responses/BadRequest" - "403": - $ref: "#/components/responses/Forbidden" - /api/v4/users/me/top/channels: - get: - tags: - - insights - summary: Get a list of the top channels for a user. - description: | - Get a list of the top public and private channels (the user is a member of) for a given user. - ##### Permissions - Must be logged in as the user. - operationId: GetTopChannelsForUser - parameters: - - name: time_range - in: query - description: > - Time range can be "today", "7_day", or "28_day". - - - `today`: channels with posts on the current day. - - - `7_day`: channels with posts in the last 7 days. - - - `28_day`: channels with posts in the last 28 days. - required: true - schema: - type: string - - name: page - in: query - description: The page to select. - schema: - type: integer - default: 0 - - name: per_page - in: query - description: The number of items per page, up to a maximum of 200. - schema: - type: integer - default: 60 - - name: team_id - in: query - description: > - Team ID will scope the response to a given team. - - ##### Permissions - - Must have `view_team` permission for the team. - schema: - type: string - responses: - "200": - description: Top channels retrieved successfully. - content: - application/json: - schema: - type: object - $ref: "#/components/schemas/TopChannelList" - "400": - $ref: "#/components/responses/BadRequest" - "403": - $ref: "#/components/responses/Forbidden" - /api/v4/teams/{team_id}/top/team_members: - get: - tags: - - insights - summary: Get a list of new team members. - description: | - Get a list of all of the new team members that have joined the given team during the given time period. - ##### Permissions - Must have `view_team` permission for the team. - operationId: GetNewTeamMembers - parameters: - - name: team_id - in: path - description: Team GUID - required: true - schema: - type: string - - name: time_range - in: query - description: > - Time range can be "today", "7_day", or "28_day". - - - `today`: team members who joined during the current day. - - - `7_day`: team members who joined in the last 7 days. - - - `28_day`: team members who joined in the last 28 days. - required: true - schema: - type: string - - name: page - in: query - description: The page to select. - schema: - type: integer - default: 0 - - name: per_page - in: query - description: The number of items per page. - schema: - type: integer - default: 60 - responses: - "200": - description: New team members retrieved successfully. - content: - application/json: - schema: - type: object - $ref: "#/components/schemas/NewTeamMembersList" - "400": - $ref: "#/components/responses/BadRequest" - "403": - $ref: "#/components/responses/Forbidden" - /api/v4/teams/{team_id}/top/threads: - get: - tags: - - insights - summary: Get a list of the top threads for a team. - description: | - Get a list of the top threads from public and private channels (the user is a member of) for a given team. - ##### Permissions - Must have `view_team` permission for the team. - operationId: GetTopThreadsForTeam - parameters: - - name: team_id - in: path - description: Team GUID - required: true - schema: - type: string - - name: time_range - in: query - description: > - Time range can be "today", "7_day", or "28_day". - - - `today`: threads with activity on the current day. - - - `7_day`: threads with activity in the last 7 days. - - - `28_day`: threads with activity in the last 28 days. - required: true - schema: - type: string - - name: page - in: query - description: The page to select. - schema: - type: integer - default: 0 - - name: per_page - in: query - description: The number of items per page, up to a maximum of 200. - schema: - type: integer - default: 60 - responses: - "200": - description: Top threads retrieved successfully. - content: - application/json: - schema: - type: object - $ref: "#/components/schemas/TopThreadList" - "400": - $ref: "#/components/responses/BadRequest" - "403": - $ref: "#/components/responses/Forbidden" - /api/v4/users/me/top/threads: - get: - tags: - - insights - summary: Get a list of the top threads for a user. - description: | - Get a list of the top threads from public and private channels (the user is a member of and participating in the thread) for a given user. - ##### Permissions - Must be logged in as the user. - operationId: GetTopThreadsForUser - parameters: - - name: time_range - in: query - description: > - Time range can be "today", "7_day", or "28_day". - - - `today`: threads with activity on the current day. - - - `7_day`: threads with activity in the last 7 days. - - - `28_day`: threads with activity in the last 28 days. - required: true - schema: - type: string - - name: page - in: query - description: The page to select. - schema: - type: integer - default: 0 - - name: per_page - in: query - description: The number of items per page, up to a maximum of 200. - schema: - type: integer - default: 60 - - name: team_id - in: query - description: > - Team ID will scope the response to a given team. - - ##### Permissions - - Must have `view_team` permission for the team. - schema: - type: string - responses: - "200": - description: Top threads retrieved successfully. - content: - application/json: - schema: - type: object - $ref: "#/components/schemas/TopThreadList" - "400": - $ref: "#/components/responses/BadRequest" - "403": - $ref: "#/components/responses/Forbidden" - /api/v4/users/me/top/dms: - get: - tags: - - insights - summary: Get a list of the top dms for a user. - description: | - Get a list of the top dms for a given user. - ##### Permissions - Must be logged in as the user. - operationId: GetTopDMsForUser - parameters: - - name: time_range - in: query - description: > - Time range can be "today", "7_day", or "28_day". - - - `today`: threads with activity on the current day. - - - `7_day`: threads with activity in the last 7 days. - - - `28_day`: threads with activity in the last 28 days. - required: true - schema: - type: string - - name: page - in: query - description: The page to select. - schema: - type: integer - default: 0 - - name: per_page - in: query - description: The number of items per page, up to a maximum of 200. - schema: - type: integer - default: 60 - responses: - "200": - description: Top dms retrieved successfully. - content: - application/json: - schema: - type: object - $ref: "#/components/schemas/TopDMList" - "400": - $ref: "#/components/responses/BadRequest" - "403": - $ref: "#/components/responses/Forbidden" diff --git a/api/v4/source/introduction.yaml b/api/v4/source/introduction.yaml index c64278ef8a..346f10612e 100644 --- a/api/v4/source/introduction.yaml +++ b/api/v4/source/introduction.yaml @@ -579,8 +579,6 @@ tags: description: Endpoints for interactive actions for use by integrations. - name: shared channels description: Endpoints for getting information about shared channels. - - name: insights - description: Endpoints for getting insights into teams and users. - name: terms of service description: Endpoints for getting and updating custom terms of service. - name: imports @@ -635,7 +633,6 @@ x-tagGroups: - schemes - integration_actions - shared channels - - insights - terms of service - imports - permissions diff --git a/e2e-tests/.ci/server.override.yml b/e2e-tests/.ci/server.override.yml index 1de8e43354..1e90a245b2 100644 --- a/e2e-tests/.ci/server.override.yml +++ b/e2e-tests/.ci/server.override.yml @@ -27,7 +27,6 @@ services: MM_SERVICESETTINGS_ENABLEONBOARDINGFLOW: "false" MM_FEATUREFLAGS_ONBOARDINGTOURTIPS: "false" MM_SERVICEENVIRONMENT: "test" - MM_FEATUREFLAGS_INSIGHTSENABLED: "true" volumes: - "server-config:/mattermost/config" ports: diff --git a/e2e-tests/cypress/tests/integration/channels/accessibility/accessibility_sidebar_spec.ts b/e2e-tests/cypress/tests/integration/channels/accessibility/accessibility_sidebar_spec.ts index 232f62acdf..36d179cef8 100644 --- a/e2e-tests/cypress/tests/integration/channels/accessibility/accessibility_sidebar_spec.ts +++ b/e2e-tests/cypress/tests/integration/channels/accessibility/accessibility_sidebar_spec.ts @@ -76,9 +76,6 @@ describe('Verify Accessibility Support in Channel Sidebar Navigation', () => { cy.findByRole('button', {name: 'Find Channels'}).should('be.focused'); cy.focused().tab(); - // * Verify if Insights has focus - cy.focused().should('have.text', 'Insights').tab(); - // * Verify if focus changes to different channels in Unread section cy.get('.SidebarChannel.unread').each((el) => { cy.wrap(el).find('.unread-title').should('be.focused'); @@ -99,7 +96,7 @@ describe('Verify Accessibility Support in Channel Sidebar Navigation', () => { it('MM-T1473 Verify Tab Support in Unreads section', () => { // # Press tab from the Main Menu button - cy.uiGetLHSAddChannelButton().focus().tab().tab().tab(); + cy.uiGetLHSAddChannelButton().focus().tab().tab(); // * Verify if focus changes to different channels in Unread section cy.get('.SidebarChannel.unread').each((el) => { @@ -114,7 +111,7 @@ describe('Verify Accessibility Support in Channel Sidebar Navigation', () => { markAsFavorite(testChannel.name); // # Press tab from the add channel button down to all unread channels - cy.uiGetLHSAddChannelButton().focus().tab().tab().tab(); + cy.uiGetLHSAddChannelButton().focus().tab().tab(); cy.get('.SidebarChannel.unread').each(() => { cy.focused().tab().tab(); }); diff --git a/e2e-tests/cypress/tests/integration/channels/insights/insights_spec.ts b/e2e-tests/cypress/tests/integration/channels/insights/insights_spec.ts deleted file mode 100644 index 9d3de5d6d1..0000000000 --- a/e2e-tests/cypress/tests/integration/channels/insights/insights_spec.ts +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -// *************************************************************** -// - [#] indicates a test step (e.g. # Go to a page) -// - [*] indicates an assertion (e.g. * Check the title) -// - Use element ID when selecting an element. Create one if none. -// *************************************************************** -// Stage: @prod - -describe('Insights', () => { - let teamA; - - before(() => { - cy.shouldHaveFeatureFlag('InsightsEnabled', true); - - cy.apiInitSetup().then(({team}) => { - teamA = team; - }); - }); - it('Check all the cards exist', () => { - cy.apiAdminLogin(); - - // # Go to the Insights view - cy.visit(`/${teamA.name}/activity-and-insights`); - - // * Check top channels exists - cy.get('.top-channels-card').should('exist'); - - // * Check top threads exists - cy.get('.top-threads-card').should('exist'); - - // * Check top boards exists because product mode is enabled - cy.get('.top-boards-card').should('exist'); - - // * Check top reactions exists - cy.get('.top-reactions-card').should('exist'); - - // * Check top dms exists - cy.get('.top-dms-card').should('exist'); - - // * Check least active channels exists - cy.get('.least-active-channels-card').should('exist'); - - // * Check top playbooks exists because product mode is enabled - cy.get('.top-playbooks-card').should('exist'); - }); -}); diff --git a/e2e-tests/cypress/tests/integration/channels/insights/last_viewed_spec.js b/e2e-tests/cypress/tests/integration/channels/insights/last_viewed_spec.js deleted file mode 100644 index 3c2efa8efe..0000000000 --- a/e2e-tests/cypress/tests/integration/channels/insights/last_viewed_spec.js +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -// *************************************************************** -// - [#] indicates a test step (e.g. # Go to a page) -// - [*] indicates an assertion (e.g. * Check the title) -// - Use element ID when selecting an element. Create one if none. -// *************************************************************** -// Stage: @prod -import * as TIMEOUTS from '../../../fixtures/timeouts'; - -describe('Insights as last viewed channel', () => { - let userA; // Member of team A and B - let teamA; - let teamB; - let offTopicUrlA; - let testChannel; - - before(() => { - cy.shouldHaveFeatureFlag('InsightsEnabled', true); - - cy.apiInitSetup().then(({team, user, offTopicUrl: url}) => { - userA = user; - teamA = team; - offTopicUrlA = url; - - cy.apiCreateUser().then(() => { - return cy.apiCreateTeam('team', 'Team'); - }).then(({team: otherTeam}) => { - teamB = otherTeam; - return cy.apiAddUserToTeam(teamB.id, userA.id); - }).then(() => { - return cy.apiCreateChannel(teamA.id, 'test', 'Test'); - }).then(({channel}) => { - testChannel = channel; - return cy.apiAddUserToChannel(testChannel.id, userA.id); - }); - }); - }); - - beforeEach(() => { - // # Log in to Team A with an account that has joined multiple teams. - cy.apiLogin(userA); - - // # On an account on two teams, view Team A - cy.visit(offTopicUrlA); - }); - - it('MM-T4902_1 Should go to insights view when switching a team if that was the last view on that team', () => { - // # Go to the Insights view on Team A - cy.uiGetSidebarInsightsButton().click().wait(TIMEOUTS.FIVE_SEC); - - // # Switch to Team B - cy.get(`#${teamB.name}TeamButton`, {timeout: TIMEOUTS.ONE_MIN}).should('be.visible').click(); - - // * Verify team display name changes correctly. - cy.uiGetLHSHeader().findByText(teamB.display_name); - - // # Switch back to Team A - cy.get(`#${teamA.name}TeamButton`, {timeout: TIMEOUTS.ONE_MIN}).should('be.visible').click(); - - // * Verify url is set up for insights view - cy.url().should('include', `${teamA.name}/activity-and-insights`); - }); - - it('MM-T4902_2 Should go to insights view when insights view is the penultimate view and leave the current channel', () => { - // # Go to the Insights view on Team A - cy.uiGetSidebarInsightsButton().click().wait(TIMEOUTS.FIVE_SEC); - - // # Switch to Test Channel - cy.uiClickSidebarItem(testChannel.name); - - // # Leave the current channel - cy.uiLeaveChannel(); - - // * Verify url is set up for insights view when insights view is the penultimate view - cy.url().should('include', `${teamA.name}/activity-and-insights`); - }); -}); diff --git a/e2e-tests/cypress/tests/integration/channels/keyboard_shortcuts/alt_option_plus_up_down_spec.js b/e2e-tests/cypress/tests/integration/channels/keyboard_shortcuts/alt_option_plus_up_down_spec.js index 81636afec4..e0d474b2ed 100644 --- a/e2e-tests/cypress/tests/integration/channels/keyboard_shortcuts/alt_option_plus_up_down_spec.js +++ b/e2e-tests/cypress/tests/integration/channels/keyboard_shortcuts/alt_option_plus_up_down_spec.js @@ -20,8 +20,6 @@ describe('Keyboard Shortcuts', () => { let testTeam; let publicChannel; let privateChannel; - const insights = 'Insights'; - const insightsSidebar = {name: insights}; before(() => { cy.apiGetMe().then(({user: adminUser}) => { @@ -63,11 +61,8 @@ describe('Keyboard Shortcuts', () => { verifyChannelSwitch(testTeam.name, offTopic, privateChannel, '{uparrow}'); verifyChannelSwitch(testTeam.name, publicChannel, offTopic, '{uparrow}'); - // * Should switch to Insights - verifyChannelSwitch(testTeam.name, insightsSidebar, publicChannel, '{uparrow}'); - // * Should switch to bottom of channel list when current channel is at the very top - verifyChannelSwitch(testTeam.name, dmWithSysadmin, insightsSidebar, '{uparrow}'); + verifyChannelSwitch(testTeam.name, dmWithSysadmin, publicChannel, '{uparrow}'); }); it('MM-T1230 Alt/Option + Down', () => { @@ -81,10 +76,6 @@ describe('Keyboard Shortcuts', () => { verifyChannelSwitch(testTeam.name, privateChannel, offTopic, '{downarrow}'); verifyChannelSwitch(testTeam.name, townSquare, privateChannel, '{downarrow}'); verifyChannelSwitch(testTeam.name, dmWithSysadmin, townSquare, '{downarrow}'); - - // * Should switch to top (Insights) when current channel is at the very bottom - verifyChannelSwitch(testTeam.name, insightsSidebar, dmWithSysadmin, '{downarrow}'); - verifyChannelSwitch(testTeam.name, publicChannel, insightsSidebar, '{downarrow}'); }); function verifyChannelSwitch(teamName, toChannel, fromChannel, arrowKey) { @@ -94,8 +85,6 @@ describe('Keyboard Shortcuts', () => { // * Verify that it redirects into expected URL if (toChannel.type === 'D') { cy.url().should('include', `/${teamName}/messages/@${toChannel.name}`); - } else if (toChannel.name === insights) { - cy.url().should('include', `/${teamName}/activity-and-insights`); } else { cy.url().should('include', `/${teamName}/channels/${toChannel.name}`); } @@ -109,21 +98,16 @@ describe('Keyboard Shortcuts', () => { }); function verifyClass(channel, assertion) { - if (channel.type) { - let label; - if (channel.type === 'O') { - label = channel.display_name.toLowerCase() + ' public channel'; - } else if (channel.type === 'P') { - label = channel.display_name.toLowerCase() + ' private channel'; - } else if (channel.type === 'D') { - label = channel.display_name.toLowerCase(); - } - - cy.findByLabelText(label).parent().should(assertion, 'active'); - } else { - // For Insights - cy.findByText(channel.name).parent().parent().parent().should(assertion, 'active'); + let label; + if (channel.type === 'O') { + label = channel.display_name.toLowerCase() + ' public channel'; + } else if (channel.type === 'P') { + label = channel.display_name.toLowerCase() + ' private channel'; + } else if (channel.type === 'D') { + label = channel.display_name.toLowerCase(); } + + cy.findByLabelText(label).parent().should(assertion, 'active'); } } }); diff --git a/e2e-tests/cypress/tests/integration/channels/message_forwarding/forward_message_from_dm_spec.js b/e2e-tests/cypress/tests/integration/channels/message_forwarding/forward_message_from_dm_spec.js index d86e808c03..bd4be4fc01 100644 --- a/e2e-tests/cypress/tests/integration/channels/message_forwarding/forward_message_from_dm_spec.js +++ b/e2e-tests/cypress/tests/integration/channels/message_forwarding/forward_message_from_dm_spec.js @@ -25,10 +25,6 @@ describe('Forward Message', () => { const commentMessage = 'Comment for the forwarded message'; before(() => { - // # Testing Forwarding from Insights view requires a license - cy.apiRequireLicense(); - cy.shouldHaveFeatureFlag('InsightsEnabled', true); - cy.apiUpdateConfig({ ServiceSettings: { ThreadAutoFollow: true, diff --git a/e2e-tests/cypress/tests/integration/channels/message_forwarding/forward_message_from_gm_spec.js b/e2e-tests/cypress/tests/integration/channels/message_forwarding/forward_message_from_gm_spec.js index e1961bf0ef..2ba22ceeb5 100644 --- a/e2e-tests/cypress/tests/integration/channels/message_forwarding/forward_message_from_gm_spec.js +++ b/e2e-tests/cypress/tests/integration/channels/message_forwarding/forward_message_from_gm_spec.js @@ -27,10 +27,6 @@ describe('Forward Message', () => { const commentMessage = 'Comment for the forwarded message'; before(() => { - // # Testing Forwarding from Insights view requires a license - cy.apiRequireLicense(); - cy.shouldHaveFeatureFlag('InsightsEnabled', true); - cy.apiUpdateConfig({ ServiceSettings: { ThreadAutoFollow: true, diff --git a/e2e-tests/cypress/tests/integration/channels/message_forwarding/forward_message_from_private_channel_spec.js b/e2e-tests/cypress/tests/integration/channels/message_forwarding/forward_message_from_private_channel_spec.js index 827b488d4b..a38be06c07 100644 --- a/e2e-tests/cypress/tests/integration/channels/message_forwarding/forward_message_from_private_channel_spec.js +++ b/e2e-tests/cypress/tests/integration/channels/message_forwarding/forward_message_from_private_channel_spec.js @@ -23,9 +23,7 @@ describe('Forward Message', () => { const replyMessage = 'Forward this reply'; before(() => { - // # Testing Forwarding from Insights view requires a license cy.apiRequireLicense(); - cy.shouldHaveFeatureFlag('InsightsEnabled', true); cy.apiUpdateConfig({ ServiceSettings: { diff --git a/e2e-tests/cypress/tests/integration/channels/message_forwarding/forward_message_from_public_channel_spec.js b/e2e-tests/cypress/tests/integration/channels/message_forwarding/forward_message_from_public_channel_spec.js index e17cf20b9c..ee7bed859b 100644 --- a/e2e-tests/cypress/tests/integration/channels/message_forwarding/forward_message_from_public_channel_spec.js +++ b/e2e-tests/cypress/tests/integration/channels/message_forwarding/forward_message_from_public_channel_spec.js @@ -30,10 +30,6 @@ describe('Forward Message', () => { const replyMessage = 'Forward this reply'; before(() => { - // # Testing Forwarding from Insights view requires a license - cy.apiRequireLicense(); - cy.shouldHaveFeatureFlag('InsightsEnabled', true); - cy.apiUpdateConfig({ ServiceSettings: { ThreadAutoFollow: true, @@ -201,32 +197,6 @@ describe('Forward Message', () => { verifyForwardedMessage({post: replyPost}); }); - it('MM-T4934_5 Forward public channel post while viewing Insights', () => { - // # Open the RHS with replies to the root post - cy.uiClickPostDropdownMenu(testPost.id, 'Reply', 'CENTER'); - - // * Assert RHS is open - cy.get('#rhsContainer').should('be.visible'); - - // # Visit global threads - cy.uiClickSidebarItem('insights'); - - // # Click on ... button of reply post - cy.clickPostDotMenu(replyPost.id, 'RHS_COMMENT'); - - // * Assert availability of the Forward menu-item - cy.findByText('Forward').click(); - - // * Forward Post - forwardPost({channelId: privateChannel.id}); - - // * Assert switch to testchannel - cy.get('#channelHeaderTitle', {timeout: TIMEOUTS.HALF_MIN}).should('be.visible').should('contain', privateChannel.display_name); - - // * Assert post has been forwarded - verifyForwardedMessage({post: replyPost}); - }); - it('MM-T4934_6 Forward public channel post to Private channel', () => { // # Check if ... button is visible in last post right side cy.get(`#CENTER_button_${testPost.id}`).should('not.be.visible'); diff --git a/e2e-tests/cypress/tests/support/api/preference.js b/e2e-tests/cypress/tests/support/api/preference.js index 5dc5713b4e..f13aebcfeb 100644 --- a/e2e-tests/cypress/tests/support/api/preference.js +++ b/e2e-tests/cypress/tests/support/api/preference.js @@ -426,12 +426,6 @@ Cypress.Commands.add('apiDisableTutorials', (userId) => { name: 'actions_menu_tutorial_state', value: '{"actions_menu_modal_viewed":true}', }, - { - user_id: userId, - category: 'insights', - name: 'insights_tutorial_state', - value: '{"insights_modal_viewed":true}', - }, { user_id: userId, category: 'drafts', diff --git a/e2e-tests/cypress/tests/support/ui/sidebar_left.ts b/e2e-tests/cypress/tests/support/ui/sidebar_left.ts index 44877544e6..683c172b91 100644 --- a/e2e-tests/cypress/tests/support/ui/sidebar_left.ts +++ b/e2e-tests/cypress/tests/support/ui/sidebar_left.ts @@ -116,11 +116,6 @@ function uiGetSidebarThreadsButton(): ChainableT { } Cypress.Commands.add('uiGetSidebarThreadsButton', uiGetSidebarThreadsButton); -function uiGetSidebarInsightsButton(): ChainableT { - return cy.get('#sidebar-insights-button').should('be.visible'); -} -Cypress.Commands.add('uiGetSidebarInsightsButton', uiGetSidebarInsightsButton); - Cypress.Commands.add('uiGetChannelSidebarMenu', (channelName, isChannelId = false) => { cy.uiGetLHS().within(() => { if (isChannelId) { @@ -273,8 +268,6 @@ declare global { uiGetSystemConsoleMenu: typeof uiGetSystemConsoleMenu; uiGetSidebarThreadsButton: typeof uiGetSidebarThreadsButton; - - uiGetSidebarInsightsButton: typeof uiGetSidebarInsightsButton; } } } diff --git a/e2e-tests/playwright/support/server/default_config.ts b/e2e-tests/playwright/support/server/default_config.ts index 1362aa5cde..c57222c8a7 100644 --- a/e2e-tests/playwright/support/server/default_config.ts +++ b/e2e-tests/playwright/support/server/default_config.ts @@ -672,7 +672,6 @@ const defaultServerConfig: AdminConfig = { BoardsDataRetention: false, NormalizeLdapDNs: false, GraphQL: false, - InsightsEnabled: true, CommandPalette: false, SendWelcomePost: true, PostPriority: true, diff --git a/server/channels/api4/api.go b/server/channels/api4/api.go index 8068d8b8a8..868dd8d2c2 100644 --- a/server/channels/api4/api.go +++ b/server/channels/api4/api.go @@ -135,9 +135,6 @@ type Routes struct { Permissions *mux.Router // 'api/v4/permissions' - InsightsForTeam *mux.Router // 'api/v4/teams/{team_id:[A-Za-z0-9]+}/top' - InsightsForUser *mux.Router // 'api/v4/users/me/top' - Usage *mux.Router // 'api/v4/usage' HostedCustomer *mux.Router // 'api/v4/hosted_customer' @@ -264,9 +261,6 @@ func Init(srv *app.Server) (*API, error) { api.BaseRoutes.Permissions = api.BaseRoutes.APIRoot.PathPrefix("/permissions").Subrouter() - api.BaseRoutes.InsightsForTeam = api.BaseRoutes.Team.PathPrefix("/top").Subrouter() - api.BaseRoutes.InsightsForUser = api.BaseRoutes.Users.PathPrefix("/me/top").Subrouter() - api.BaseRoutes.Usage = api.BaseRoutes.APIRoot.PathPrefix("/usage").Subrouter() api.BaseRoutes.HostedCustomer = api.BaseRoutes.APIRoot.PathPrefix("/hosted_customer").Subrouter() @@ -314,7 +308,6 @@ func Init(srv *app.Server) (*API, error) { api.InitSharedChannels() api.InitPermissions() api.InitExport() - api.InitInsights() api.InitUsage() api.InitHostedCustomer() api.InitDrafts() diff --git a/server/channels/api4/handlers.go b/server/channels/api4/handlers.go index 9005395877..e5bbd3115b 100644 --- a/server/channels/api4/handlers.go +++ b/server/channels/api4/handlers.go @@ -216,11 +216,3 @@ func minimumProfessionalLicense(c *Context) *model.AppError { } return nil } - -func rejectGuests(c *Context) *model.AppError { - if c.AppContext.Session().Props[model.SessionPropIsGuest] == "true" { - err := model.NewAppError("", model.NoTranslation, nil, "insufficient permissions as a guest user", http.StatusNotImplemented) - return err - } - return nil -} diff --git a/server/channels/api4/insights.go b/server/channels/api4/insights.go deleted file mode 100644 index 48974716ac..0000000000 --- a/server/channels/api4/insights.go +++ /dev/null @@ -1,637 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -package api4 - -import ( - "encoding/json" - "net/http" - "time" - - "github.com/mattermost/mattermost/server/public/model" -) - -func (api *API) InitInsights() { - // Reactions - api.BaseRoutes.InsightsForTeam.Handle("/reactions", api.APISessionRequired(getTopReactionsForTeamSince)).Methods("GET") - api.BaseRoutes.InsightsForUser.Handle("/reactions", api.APISessionRequired(getTopReactionsForUserSince)).Methods("GET") - - // Channels - api.BaseRoutes.InsightsForTeam.Handle("/channels", api.APISessionRequired(getTopChannelsForTeamSince)).Methods("GET") - api.BaseRoutes.InsightsForUser.Handle("/channels", api.APISessionRequired(getTopChannelsForUserSince)).Methods("GET") - - // Threads - api.BaseRoutes.InsightsForTeam.Handle("/threads", api.APISessionRequired(getTopThreadsForTeamSince)).Methods("GET") - api.BaseRoutes.InsightsForUser.Handle("/threads", api.APISessionRequired(getTopThreadsForUserSince)).Methods("GET") - - // user DMs - api.BaseRoutes.InsightsForUser.Handle("/dms", api.APISessionRequired(getTopDMsForUserSince)).Methods("GET") - - // Inactive channels - api.BaseRoutes.InsightsForTeam.Handle("/inactive_channels", api.APISessionRequired(getTopInactiveChannelsForTeamSince)).Methods("GET") - api.BaseRoutes.InsightsForUser.Handle("/inactive_channels", api.APISessionRequired(getTopInactiveChannelsForUserSince)).Methods("GET") - - // New teammembers - api.BaseRoutes.InsightsForTeam.Handle("/team_members", api.APISessionRequired(getNewTeamMembersSince)).Methods("GET") -} - -// Top Reactions - -func getTopReactionsForTeamSince(c *Context, w http.ResponseWriter, r *http.Request) { - - // license and guest user check - permissionErr := minimumProfessionalLicense(c) - if permissionErr != nil { - c.Err = permissionErr - return - } - permissionErr = rejectGuests(c) - if permissionErr != nil { - c.Err = permissionErr - return - } - - c.RequireTeamId() - if c.Err != nil { - return - } - - team, appErr := c.App.GetTeam(c.Params.TeamId) - if appErr != nil { - c.Err = appErr - return - } - - if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), team.Id, model.PermissionViewTeam) { - c.SetPermissionError(model.PermissionViewTeam) - return - } - - user, appErr := c.App.GetUser(c.AppContext.Session().UserId) - if appErr != nil { - c.Err = appErr - return - } - - startTime, appErr := model.GetStartOfDayForTimeRange(c.Params.TimeRange, user.GetTimezoneLocation()) - if appErr != nil { - c.Err = appErr - return - } - - topReactionList, appErr := c.App.GetTopReactionsForTeamSince(c.Params.TeamId, c.AppContext.Session().UserId, &model.InsightsOpts{ - StartUnixMilli: startTime.UnixMilli(), - Page: c.Params.Page, - PerPage: c.Params.PerPage, - }) - if appErr != nil { - c.Err = appErr - return - } - - if err := json.NewEncoder(w).Encode(topReactionList); err != nil { - c.Err = model.NewAppError("getTopReactionsForTeamSince", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError) - return - } -} - -func getTopReactionsForUserSince(c *Context, w http.ResponseWriter, r *http.Request) { - // guest user check - permissionErr := rejectGuests(c) - if permissionErr != nil { - c.Err = permissionErr - return - } - - c.Params.TeamId = r.URL.Query().Get("team_id") - - // TeamId is an optional parameter - if c.Params.TeamId != "" { - if !model.IsValidId(c.Params.TeamId) { - c.SetInvalidURLParam("team_id") - return - } - - team, appErr := c.App.GetTeam(c.Params.TeamId) - if appErr != nil { - c.Err = appErr - return - } - - if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), team.Id, model.PermissionViewTeam) { - c.SetPermissionError(model.PermissionViewTeam) - return - } - } - - user, appErr := c.App.GetUser(c.AppContext.Session().UserId) - if appErr != nil { - c.Err = appErr - return - } - - startTime, appErr := model.GetStartOfDayForTimeRange(c.Params.TimeRange, user.GetTimezoneLocation()) - if appErr != nil { - c.Err = appErr - return - } - - topReactionList, appErr := c.App.GetTopReactionsForUserSince(c.AppContext.Session().UserId, c.Params.TeamId, &model.InsightsOpts{ - StartUnixMilli: startTime.UnixMilli(), - Page: c.Params.Page, - PerPage: c.Params.PerPage, - }) - if appErr != nil { - c.Err = appErr - return - } - - if err := json.NewEncoder(w).Encode(topReactionList); err != nil { - c.Err = model.NewAppError("getTopReactionsForUserSince", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError) - return - } -} - -// Top Channels - -func getTopChannelsForTeamSince(c *Context, w http.ResponseWriter, r *http.Request) { - // license and guest user check - permissionErr := minimumProfessionalLicense(c) - if permissionErr != nil { - c.Err = permissionErr - return - } - permissionErr = rejectGuests(c) - if permissionErr != nil { - c.Err = permissionErr - return - } - - c.RequireTeamId() - if c.Err != nil { - return - } - - team, appErr := c.App.GetTeam(c.Params.TeamId) - if appErr != nil { - c.Err = appErr - return - } - - if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), team.Id, model.PermissionViewTeam) { - c.SetPermissionError(model.PermissionViewTeam) - return - } - - user, appErr := c.App.GetUser(c.AppContext.Session().UserId) - if appErr != nil { - c.Err = appErr - return - } - - loc := user.GetTimezoneLocation() - startTime, appErr := model.GetStartOfDayForTimeRange(c.Params.TimeRange, loc) - if appErr != nil { - c.Err = appErr - return - } - - topChannels, appErr := c.App.GetTopChannelsForTeamSince(c.AppContext, c.Params.TeamId, c.AppContext.Session().UserId, &model.InsightsOpts{ - StartUnixMilli: startTime.UnixMilli(), - Page: c.Params.Page, - PerPage: c.Params.PerPage, - }) - if appErr != nil { - c.Err = appErr - return - } - - topChannels.PostCountByDuration, appErr = postCountByDurationViewModel(c, topChannels, startTime, c.Params.TimeRange, nil, loc) - if appErr != nil { - c.Err = appErr - return - } - - if err := json.NewEncoder(w).Encode(topChannels); err != nil { - c.Err = model.NewAppError("getTopChannelsForTeamSince", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError) - return - } -} - -func getTopChannelsForUserSince(c *Context, w http.ResponseWriter, r *http.Request) { - // guest user check - permissionErr := rejectGuests(c) - if permissionErr != nil { - c.Err = permissionErr - return - } - - c.Params.TeamId = r.URL.Query().Get("team_id") - - // TeamId is an optional parameter - if c.Params.TeamId != "" { - if !model.IsValidId(c.Params.TeamId) { - c.SetInvalidURLParam("team_id") - return - } - - team, appErr := c.App.GetTeam(c.Params.TeamId) - if appErr != nil { - c.Err = appErr - return - } - - if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), team.Id, model.PermissionViewTeam) { - c.SetPermissionError(model.PermissionViewTeam) - return - } - } - - user, appErr := c.App.GetUser(c.AppContext.Session().UserId) - if appErr != nil { - c.Err = appErr - return - } - - loc := user.GetTimezoneLocation() - startTime, appErr := model.GetStartOfDayForTimeRange(c.Params.TimeRange, loc) - if appErr != nil { - c.Err = appErr - return - } - - topChannels, appErr := c.App.GetTopChannelsForUserSince(c.AppContext, c.AppContext.Session().UserId, c.Params.TeamId, &model.InsightsOpts{ - StartUnixMilli: startTime.UnixMilli(), - Page: c.Params.Page, - PerPage: c.Params.PerPage, - }) - if appErr != nil { - c.Err = appErr - return - } - - topChannels.PostCountByDuration, appErr = postCountByDurationViewModel(c, topChannels, startTime, c.Params.TimeRange, &c.AppContext.Session().UserId, loc) - if appErr != nil { - c.Err = appErr - return - } - - if err := json.NewEncoder(w).Encode(topChannels); err != nil { - c.Err = model.NewAppError("getTopChannelsForUserSince", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError) - return - } -} - -// Top Threads -func getTopThreadsForTeamSince(c *Context, w http.ResponseWriter, r *http.Request) { - // license and guest user check - permissionErr := minimumProfessionalLicense(c) - if permissionErr != nil { - c.Err = permissionErr - return - } - permissionErr = rejectGuests(c) - if permissionErr != nil { - c.Err = permissionErr - return - } - - c.RequireTeamId() - if c.Err != nil { - return - } - - team, appErr := c.App.GetTeam(c.Params.TeamId) - if appErr != nil { - c.Err = appErr - return - } - - // restrict users with no access to team - user, err := c.App.GetUser(c.AppContext.Session().UserId) - if err != nil { - c.Err = err - return - } - - if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), team.Id, model.PermissionViewTeam) { - c.SetPermissionError(model.PermissionViewTeam) - return - } - - startTime, appErr := model.GetStartOfDayForTimeRange(c.Params.TimeRange, user.GetTimezoneLocation()) - if appErr != nil { - c.Err = appErr - return - } - - topThreads, appErr := c.App.GetTopThreadsForTeamSince(c.AppContext, c.Params.TeamId, c.AppContext.Session().UserId, &model.InsightsOpts{ - StartUnixMilli: startTime.UnixMilli(), - Page: c.Params.Page, - PerPage: c.Params.PerPage, - }) - if appErr != nil { - c.Err = appErr - return - } - - if err := json.NewEncoder(w).Encode(topThreads); err != nil { - c.Err = model.NewAppError("getTopThreadsForTeamSince", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError) - return - } -} - -func getTopThreadsForUserSince(c *Context, w http.ResponseWriter, r *http.Request) { - // guest user check - permissionErr := rejectGuests(c) - if permissionErr != nil { - c.Err = permissionErr - return - } - - c.Params.TeamId = r.URL.Query().Get("team_id") - - // restrict users with no access to team - user, err := c.App.GetUser(c.AppContext.Session().UserId) - if err != nil { - c.Err = err - return - } - // TeamId is an optional parameter - if c.Params.TeamId != "" { - if !model.IsValidId(c.Params.TeamId) { - c.SetInvalidURLParam("team_id") - return - } - - team, teamErr := c.App.GetTeam(c.Params.TeamId) - if teamErr != nil { - c.Err = teamErr - return - } - - if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), team.Id, model.PermissionViewTeam) { - c.SetPermissionError(model.PermissionViewTeam) - return - } - } - - startTime, appErr := model.GetStartOfDayForTimeRange(c.Params.TimeRange, user.GetTimezoneLocation()) - if appErr != nil { - c.Err = appErr - return - } - - topThreads, appErr := c.App.GetTopThreadsForUserSince(c.AppContext, c.Params.TeamId, c.AppContext.Session().UserId, &model.InsightsOpts{ - StartUnixMilli: startTime.UnixMilli(), - Page: c.Params.Page, - PerPage: c.Params.PerPage, - }) - if appErr != nil { - c.Err = appErr - return - } - - if err := json.NewEncoder(w).Encode(topThreads); err != nil { - c.Err = model.NewAppError("getTopThreadsForUserSince", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError) - return - } -} - -// Top DMs -func getTopDMsForUserSince(c *Context, w http.ResponseWriter, r *http.Request) { - // guest user check - permissionErr := rejectGuests(c) - if permissionErr != nil { - c.Err = permissionErr - return - } - - user, err := c.App.GetUser(c.AppContext.Session().UserId) - if err != nil { - c.Err = err - return - } - - startTime, appErr := model.GetStartOfDayForTimeRange(c.Params.TimeRange, user.GetTimezoneLocation()) - if appErr != nil { - c.Err = appErr - return - } - - topDMs, err := c.App.GetTopDMsForUserSince(user.Id, &model.InsightsOpts{ - StartUnixMilli: startTime.UnixMilli(), - Page: c.Params.Page, - PerPage: c.Params.PerPage, - }) - - if err != nil { - c.Err = err - return - } - - if err := json.NewEncoder(w).Encode(topDMs); err != nil { - c.Err = model.NewAppError("getTopDMsForUserSince", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError) - return - } -} - -// Top Channels - -func getTopInactiveChannelsForTeamSince(c *Context, w http.ResponseWriter, r *http.Request) { - // license and guest user check - permissionErr := minimumProfessionalLicense(c) - if permissionErr != nil { - c.Err = permissionErr - return - } - permissionErr = rejectGuests(c) - if permissionErr != nil { - c.Err = permissionErr - return - } - - c.RequireTeamId() - if c.Err != nil { - return - } - - team, err := c.App.GetTeam(c.Params.TeamId) - if err != nil { - c.Err = err - return - } - - if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), team.Id, model.PermissionViewTeam) { - c.SetPermissionError(model.PermissionViewTeam) - return - } - - user, err := c.App.GetUser(c.AppContext.Session().UserId) - if err != nil { - c.Err = err - return - } - - loc := user.GetTimezoneLocation() - startTime, appErr := model.GetStartOfDayForTimeRange(c.Params.TimeRange, loc) - if appErr != nil { - c.Err = appErr - return - } - - topChannels, err := c.App.GetTopInactiveChannelsForTeamSince(c.AppContext, c.Params.TeamId, c.AppContext.Session().UserId, &model.InsightsOpts{ - StartUnixMilli: startTime.UnixMilli(), - Page: c.Params.Page, - PerPage: c.Params.PerPage, - }) - if err != nil { - c.Err = err - return - } - - if err := json.NewEncoder(w).Encode(topChannels); err != nil { - c.Err = model.NewAppError("getTopInactiveChannelsForTeamSince", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError) - return - } -} - -// top inactive channels - -func getTopInactiveChannelsForUserSince(c *Context, w http.ResponseWriter, r *http.Request) { - // guest user check - permissionErr := rejectGuests(c) - if permissionErr != nil { - c.Err = permissionErr - return - } - - c.Params.TeamId = r.URL.Query().Get("team_id") - - // TeamId is an optional parameter - if c.Params.TeamId != "" { - if !model.IsValidId(c.Params.TeamId) { - c.SetInvalidURLParam("team_id") - return - } - - team, teamErr := c.App.GetTeam(c.Params.TeamId) - if teamErr != nil { - c.Err = teamErr - return - } - - if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), team.Id, model.PermissionViewTeam) { - c.SetPermissionError(model.PermissionViewTeam) - return - } - } - - user, err := c.App.GetUser(c.AppContext.Session().UserId) - if err != nil { - c.Err = err - return - } - - loc := user.GetTimezoneLocation() - startTime, appErr := model.GetStartOfDayForTimeRange(c.Params.TimeRange, loc) - if appErr != nil { - c.Err = appErr - return - } - - topChannels, err := c.App.GetTopInactiveChannelsForUserSince(c.AppContext, c.Params.TeamId, c.AppContext.Session().UserId, &model.InsightsOpts{ - StartUnixMilli: startTime.UnixMilli(), - Page: c.Params.Page, - PerPage: c.Params.PerPage, - }) - - if err != nil { - c.Err = err - return - } - - if err := json.NewEncoder(w).Encode(topChannels); err != nil { - c.Err = model.NewAppError("getTopInactiveChannelsForUserSince", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError) - return - } -} - -// postCountByDurationViewModel expects a list of channels that are pre-authorized for the given user to view. -func postCountByDurationViewModel(c *Context, topChannelList *model.TopChannelList, startTime *time.Time, timeRange string, userID *string, location *time.Location) (model.ChannelPostCountByDuration, *model.AppError) { - if len(topChannelList.Items) == 0 { - return nil, nil - } - var postCountsByDay []*model.DurationPostCount - channelIDs := topChannelList.ChannelIDs() - var grouping model.PostCountGrouping - if timeRange == model.TimeRangeToday { - grouping = model.PostsByHour - } else { - grouping = model.PostsByDay - } - postCountsByDay, err := c.App.PostCountsByDuration(c.AppContext, channelIDs, startTime.UnixMilli(), userID, grouping, location) - if err != nil { - return nil, err - } - return model.ToDailyPostCountViewModel(postCountsByDay, startTime, model.TimeRangeToNumberDays(timeRange), channelIDs), nil -} - -func getNewTeamMembersSince(c *Context, w http.ResponseWriter, r *http.Request) { - // license and guest user check - permissionErr := minimumProfessionalLicense(c) - if permissionErr != nil { - c.Err = permissionErr - return - } - permissionErr = rejectGuests(c) - if permissionErr != nil { - c.Err = permissionErr - return - } - - c.RequireTeamId() - if c.Err != nil { - return - } - - team, err := c.App.GetTeam(c.Params.TeamId) - if err != nil { - c.Err = err - return - } - - if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), team.Id, model.PermissionViewTeam) { - c.SetPermissionError(model.PermissionViewTeam) - return - } - - user, err := c.App.GetUser(c.AppContext.Session().UserId) - if err != nil { - c.Err = err - return - } - loc := user.GetTimezoneLocation() - startTime, appErr := model.GetStartOfDayForTimeRange(c.Params.TimeRange, loc) - if appErr != nil { - c.Err = appErr - return - } - - ntms, count, err := c.App.GetNewTeamMembersSince(c.AppContext, c.Params.TeamId, &model.InsightsOpts{ - StartUnixMilli: startTime.UnixMilli(), - Page: c.Params.Page, - PerPage: c.Params.PerPage, - }) - if err != nil { - c.Err = err - return - } - - ntms.TotalCount = count - - if err := json.NewEncoder(w).Encode(ntms); err != nil { - c.Err = model.NewAppError("getNewTeamembersForTeamSince", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError) - return - } -} diff --git a/server/channels/api4/insights_test.go b/server/channels/api4/insights_test.go deleted file mode 100644 index bbbd3a1cf7..0000000000 --- a/server/channels/api4/insights_test.go +++ /dev/null @@ -1,1288 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -package api4 - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/mattermost/mattermost/server/public/model" - "github.com/mattermost/mattermost/server/v8/channels/store" - "github.com/mattermost/mattermost/server/v8/channels/utils/testutils" -) - -// Top Reactions - -func TestGetTopReactionsForTeamSince(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - - th.ConfigStore.SetReadOnlyFF(false) - defer th.ConfigStore.SetReadOnlyFF(true) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true }) - th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional)) - - th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional)) - - client := th.Client - - userId := th.BasicUser.Id - user2Id := th.BasicUser2.Id - - post1 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"} - post2 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"} - post3 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"} - post4 := &model.Post{UserId: user2Id, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"} - post5 := &model.Post{UserId: user2Id, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"} - - post1, _, _ = client.CreatePost(context.Background(), post1) - post2, _, _ = client.CreatePost(context.Background(), post2) - post3, _, _ = client.CreatePost(context.Background(), post3) - post4, _, _ = client.CreatePost(context.Background(), post4) - post5, _, _ = client.CreatePost(context.Background(), post5) - - userReactions := []*model.Reaction{ - { - UserId: userId, - PostId: post1.Id, - EmojiName: "happy", - }, - { - UserId: user2Id, - PostId: post1.Id, - EmojiName: "happy", - }, - { - UserId: userId, - PostId: post1.Id, - EmojiName: "sad", - }, - { - UserId: user2Id, - PostId: post1.Id, - EmojiName: "sad", - }, - { - UserId: userId, - PostId: post1.Id, - EmojiName: "smile", - }, - { - UserId: userId, - PostId: post1.Id, - EmojiName: "joy", - }, - { - UserId: userId, - PostId: post1.Id, - EmojiName: "100", - }, - { - UserId: userId, - PostId: post2.Id, - EmojiName: "sad", - }, - { - UserId: userId, - PostId: post2.Id, - EmojiName: "smile", - }, - { - UserId: userId, - PostId: post2.Id, - EmojiName: "joy", - }, - { - UserId: userId, - PostId: post2.Id, - EmojiName: "100", - }, - { - UserId: userId, - PostId: post3.Id, - EmojiName: "smile", - }, - { - UserId: user2Id, - PostId: post3.Id, - EmojiName: "smile", - }, - { - UserId: userId, - PostId: post3.Id, - EmojiName: "joy", - }, - { - UserId: userId, - PostId: post3.Id, - EmojiName: "100", - }, - { - UserId: userId, - PostId: post4.Id, - EmojiName: "joy", - }, - { - UserId: user2Id, - PostId: post4.Id, - EmojiName: "joy", - }, - { - UserId: userId, - PostId: post4.Id, - EmojiName: "100", - }, - { - UserId: userId, - PostId: post5.Id, - EmojiName: "100", - }, - { - UserId: user2Id, - PostId: post5.Id, - EmojiName: "100", - }, - { - UserId: user2Id, - PostId: post5.Id, - EmojiName: "+1", - }, - { - UserId: userId, - PostId: post1.Id, - EmojiName: "100", - CreateAt: model.GetMillisForTime(time.Now().Add(time.Hour * time.Duration(-25))), - }, - } - - for _, userReaction := range userReactions { - _, err := th.App.Srv().Store().Reaction().Save(userReaction) - require.NoError(t, err) - } - - teamId := th.BasicChannel.TeamId - - var expectedTopReactions [5]*model.TopReaction - expectedTopReactions[0] = &model.TopReaction{EmojiName: "100", Count: int64(6)} - expectedTopReactions[1] = &model.TopReaction{EmojiName: "joy", Count: int64(5)} - expectedTopReactions[2] = &model.TopReaction{EmojiName: "smile", Count: int64(4)} - expectedTopReactions[3] = &model.TopReaction{EmojiName: "sad", Count: int64(3)} - expectedTopReactions[4] = &model.TopReaction{EmojiName: "happy", Count: int64(2)} - - t.Run("get-top-reactions-for-team-since", func(t *testing.T) { - topReactions, _, err := client.GetTopReactionsForTeamSince(context.Background(), teamId, model.TimeRangeToday, 0, 5) - require.NoError(t, err) - reactions := topReactions.Items - - for i, reaction := range reactions { - assert.Equal(t, expectedTopReactions[i].EmojiName, reaction.EmojiName) - assert.Equal(t, expectedTopReactions[i].Count, reaction.Count) - } - - topReactions, _, err = client.GetTopReactionsForTeamSince(context.Background(), teamId, model.TimeRangeToday, 1, 5) - require.NoError(t, err) - reactions = topReactions.Items - - assert.Equal(t, "+1", reactions[0].EmojiName) - assert.Equal(t, int64(1), reactions[0].Count) - }) - - t.Run("get-top-reactions-for-team-since exclude channels user is not member of", func(t *testing.T) { - excludedChannel := th.CreatePrivateChannel() - - for i := 0; i < 10; i++ { - post, _, err := client.CreatePost(context.Background(), &model.Post{UserId: userId, ChannelId: excludedChannel.Id, Message: "zz" + model.NewId() + "a"}) - require.NoError(t, err) - - reaction := &model.Reaction{ - UserId: userId, - PostId: post.Id, - EmojiName: "confused", - } - - _, err = th.App.Srv().Store().Reaction().Save(reaction) - require.NoError(t, err) - } - - th.RemoveUserFromChannel(th.BasicUser, excludedChannel) - - topReactions, _, err := client.GetTopReactionsForTeamSince(context.Background(), teamId, model.TimeRangeToday, 0, 5) - require.NoError(t, err) - reactions := topReactions.Items - - for i, reaction := range reactions { - assert.Equal(t, expectedTopReactions[i].EmojiName, reaction.EmojiName) - assert.Equal(t, expectedTopReactions[i].Count, reaction.Count) - } - - topReactions, _, err = client.GetTopReactionsForTeamSince(context.Background(), teamId, model.TimeRangeToday, 1, 5) - require.NoError(t, err) - reactions = topReactions.Items - - assert.Equal(t, "+1", reactions[0].EmojiName) - assert.Equal(t, int64(1), reactions[0].Count) - }) - - t.Run("get-top-reactions-for-team-since invalid team id", func(t *testing.T) { - _, resp, err := client.GetTopReactionsForTeamSince(context.Background(), "12345", model.TimeRangeToday, 0, 5) - assert.Error(t, err) - CheckBadRequestStatus(t, resp) - - _, resp, err = client.GetTopReactionsForTeamSince(context.Background(), model.NewId(), model.TimeRangeToday, 0, 5) - assert.Error(t, err) - CheckNotFoundStatus(t, resp) - }) - - t.Run("get-top-reactions-for-team-since invalid time range", func(t *testing.T) { - _, resp, err := client.GetTopReactionsForTeamSince(context.Background(), teamId, "7_days", 0, 5) - require.Error(t, err) - CheckBadRequestStatus(t, resp) - }) - - t.Run("get-top-reactions-for-team-since not a member of team", func(t *testing.T) { - th.UnlinkUserFromTeam(th.BasicUser, th.BasicTeam) - _, resp, err := client.GetTopReactionsForTeamSince(context.Background(), teamId, model.TimeRangeToday, 0, 5) - assert.Error(t, err) - CheckForbiddenStatus(t, resp) - }) - - t.Run("get-top-reactions-for-team-since invalid license", func(t *testing.T) { - th.App.Srv().SetLicense(model.NewTestLicense("")) - - _, resp, err := client.GetTopReactionsForTeamSince(context.Background(), teamId, model.TimeRangeToday, 0, 5) - assert.Error(t, err) - CheckNotImplementedStatus(t, resp) - }) -} - -func TestGetTopReactionsForUserSince(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - - th.ConfigStore.SetReadOnlyFF(false) - defer th.ConfigStore.SetReadOnlyFF(true) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true }) - th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional)) - - client := th.Client - - userId := th.BasicUser.Id - - post1 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"} - post2 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"} - post3 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"} - post4 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"} - post5 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"} - post6 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"} - - post1, _, _ = client.CreatePost(context.Background(), post1) - post2, _, _ = client.CreatePost(context.Background(), post2) - post3, _, _ = client.CreatePost(context.Background(), post3) - post4, _, _ = client.CreatePost(context.Background(), post4) - post5, _, _ = client.CreatePost(context.Background(), post5) - post6, _, _ = client.CreatePost(context.Background(), post6) - - userReactions := []*model.Reaction{ - { - UserId: userId, - PostId: post1.Id, - EmojiName: "happy", - }, - { - UserId: userId, - PostId: post2.Id, - EmojiName: "happy", - }, - { - UserId: userId, - PostId: post3.Id, - EmojiName: "happy", - }, - { - UserId: userId, - PostId: post4.Id, - EmojiName: "happy", - }, - { - UserId: userId, - PostId: post5.Id, - EmojiName: "happy", - }, - { - UserId: userId, - PostId: post6.Id, - EmojiName: "happy", - }, - { - UserId: userId, - PostId: post1.Id, - EmojiName: "smile", - }, - { - UserId: userId, - PostId: post2.Id, - EmojiName: "smile", - }, - { - UserId: userId, - PostId: post3.Id, - EmojiName: "smile", - }, - { - UserId: userId, - PostId: post4.Id, - EmojiName: "smile", - }, - { - UserId: userId, - PostId: post5.Id, - EmojiName: "smile", - }, - { - UserId: userId, - PostId: post1.Id, - EmojiName: "+1", - }, - { - UserId: userId, - PostId: post2.Id, - EmojiName: "+1", - }, - { - UserId: userId, - PostId: post3.Id, - EmojiName: "+1", - }, - { - UserId: userId, - PostId: post4.Id, - EmojiName: "+1", - }, - { - UserId: userId, - PostId: post1.Id, - EmojiName: "heart", - }, - { - UserId: userId, - PostId: post2.Id, - EmojiName: "heart", - }, - { - UserId: userId, - PostId: post3.Id, - EmojiName: "heart", - }, - { - UserId: userId, - PostId: post1.Id, - EmojiName: "blush", - }, - { - UserId: userId, - PostId: post2.Id, - EmojiName: "blush", - }, - { - UserId: userId, - PostId: post1.Id, - EmojiName: "100", - }, - { - UserId: userId, - PostId: post1.Id, - EmojiName: "100", - CreateAt: model.GetMillisForTime(time.Now().Add(time.Hour * time.Duration(-25))), - }, - } - - for _, userReaction := range userReactions { - _, err := th.App.Srv().Store().Reaction().Save(userReaction) - require.NoError(t, err) - } - - teamId := th.BasicChannel.TeamId - - var expectedTopReactions [5]*model.TopReaction - expectedTopReactions[0] = &model.TopReaction{EmojiName: "happy", Count: int64(6)} - expectedTopReactions[1] = &model.TopReaction{EmojiName: "smile", Count: int64(5)} - expectedTopReactions[2] = &model.TopReaction{EmojiName: "+1", Count: int64(4)} - expectedTopReactions[3] = &model.TopReaction{EmojiName: "heart", Count: int64(3)} - expectedTopReactions[4] = &model.TopReaction{EmojiName: "blush", Count: int64(2)} - - t.Run("get-top-reactions-for-user-since", func(t *testing.T) { - topReactions, _, err := client.GetTopReactionsForUserSince(context.Background(), teamId, model.TimeRangeToday, 0, 5) - require.NoError(t, err) - reactions := topReactions.Items - - for i, reaction := range reactions { - assert.Equal(t, expectedTopReactions[i].EmojiName, reaction.EmojiName) - assert.Equal(t, expectedTopReactions[i].Count, reaction.Count) - } - - topReactions, _, err = client.GetTopReactionsForUserSince(context.Background(), teamId, model.TimeRangeToday, 1, 5) - require.NoError(t, err) - reactions = topReactions.Items - assert.Equal(t, "100", reactions[0].EmojiName) - assert.Equal(t, int64(1), reactions[0].Count) - }) - - t.Run("get-top-reactions-for-user-since invalid team id", func(t *testing.T) { - _, resp, err := client.GetTopReactionsForUserSince(context.Background(), "invalid_team_id", model.TimeRangeToday, 0, 5) - assert.Error(t, err) - CheckBadRequestStatus(t, resp) - - _, resp, err = client.GetTopReactionsForUserSince(context.Background(), model.NewId(), model.TimeRangeToday, 0, 5) - assert.Error(t, err) - CheckNotFoundStatus(t, resp) - }) - - t.Run("get-top-reactions-for-user-since invalid time range", func(t *testing.T) { - _, resp, err := client.GetTopReactionsForUserSince(context.Background(), teamId, "7_days", 0, 5) - require.Error(t, err) - CheckBadRequestStatus(t, resp) - }) - - t.Run("get-top-reactions-for-user-since not a member of team", func(t *testing.T) { - th.UnlinkUserFromTeam(th.BasicUser, th.BasicTeam) - _, resp, err := client.GetTopReactionsForUserSince(context.Background(), teamId, model.TimeRangeToday, 0, 5) - assert.Error(t, err) - CheckForbiddenStatus(t, resp) - }) -} - -// Top Channels - -func TestGetTopChannelsForTeamSince(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - - th.ConfigStore.SetReadOnlyFF(false) - defer th.ConfigStore.SetReadOnlyFF(true) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true }) - th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional)) - - th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional)) - - client := th.Client - userId := th.BasicUser.Id - - channel4 := th.CreatePublicChannel() - channel5 := th.CreatePrivateChannel() - channel6 := th.CreatePrivateChannel() - th.App.AddUserToChannel(th.Context, th.BasicUser, channel4, false) - th.App.AddUserToChannel(th.Context, th.BasicUser, channel5, false) - th.App.AddUserToChannel(th.Context, th.BasicUser, channel6, false) - - channelIDs := [6]string{th.BasicChannel.Id, th.BasicChannel2.Id, th.BasicPrivateChannel.Id, channel4.Id, channel5.Id, channel6.Id} - - i := len(channelIDs) - for _, channelID := range channelIDs { - for j := i; j > 0; j-- { - _, _, err := client.CreatePost(context.Background(), &model.Post{UserId: userId, ChannelId: channelID, Message: "zz" + model.NewId() + "a"}) - require.NoError(t, err) - } - i-- - } - - teamId := th.BasicChannel.TeamId - - expectedTopChannels := []struct { - ID string - MessageCount int64 - }{ - {ID: th.BasicChannel.Id, MessageCount: 7}, - {ID: th.BasicChannel2.Id, MessageCount: 5}, - {ID: th.BasicPrivateChannel.Id, MessageCount: 4}, - {ID: channel4.Id, MessageCount: 3}, - {ID: channel5.Id, MessageCount: 2}, - } - - t.Run("get-top-channels-for-team-since", func(t *testing.T) { - topChannels, _, err := client.GetTopChannelsForTeamSince(context.Background(), teamId, model.TimeRangeToday, 0, 5) - require.NoError(t, err) - - for i, channel := range topChannels.Items { - assert.Equal(t, expectedTopChannels[i].ID, channel.ID) - assert.Equal(t, expectedTopChannels[i].MessageCount, channel.MessageCount) - } - - topChannels, _, err = client.GetTopChannelsForTeamSince(context.Background(), teamId, model.TimeRangeToday, 1, 5) - require.NoError(t, err) - assert.Equal(t, channel6.Id, topChannels.Items[0].ID) - assert.Equal(t, int64(1), topChannels.Items[0].MessageCount) - - t.Run("has post count by day", func(t *testing.T) { - require.NotNil(t, topChannels.PostCountByDuration) - }) - }) - - t.Run("get-top-channels-for-user-since exclude channels user is not member of", func(t *testing.T) { - excludedChannel := th.CreatePrivateChannel() - - for i := 0; i < 10; i++ { - _, _, err := client.CreatePost(context.Background(), &model.Post{UserId: userId, ChannelId: excludedChannel.Id, Message: "zz" + model.NewId() + "a"}) - require.NoError(t, err) - } - - th.RemoveUserFromChannel(th.BasicUser, excludedChannel) - - topChannels, _, err := client.GetTopChannelsForTeamSince(context.Background(), teamId, model.TimeRangeToday, 0, 5) - require.NoError(t, err) - - for i, channel := range topChannels.Items { - assert.Equal(t, expectedTopChannels[i].ID, channel.ID) - assert.Equal(t, expectedTopChannels[i].MessageCount, channel.MessageCount) - } - }) - - t.Run("get-top-channels-for-team-since invalid team id", func(t *testing.T) { - _, resp, err := client.GetTopChannelsForTeamSince(context.Background(), "12345", model.TimeRangeToday, 0, 5) - assert.Error(t, err) - CheckBadRequestStatus(t, resp) - - _, resp, err = client.GetTopChannelsForTeamSince(context.Background(), model.NewId(), model.TimeRangeToday, 0, 5) - assert.Error(t, err) - CheckNotFoundStatus(t, resp) - }) - - t.Run("get-top-channels-for-team-since invalid time range", func(t *testing.T) { - _, resp, err := client.GetTopChannelsForTeamSince(context.Background(), teamId, "7_days", 0, 5) - assert.Error(t, err) - CheckBadRequestStatus(t, resp) - }) - - t.Run("get-top-channels-for-team-since not a member of team", func(t *testing.T) { - th.UnlinkUserFromTeam(th.BasicUser, th.BasicTeam) - _, resp, err := client.GetTopChannelsForTeamSince(context.Background(), teamId, model.TimeRangeToday, 0, 5) - assert.Error(t, err) - CheckForbiddenStatus(t, resp) - }) - - t.Run("get-top-channels-for-team-since invalid license", func(t *testing.T) { - th.App.Srv().SetLicense(model.NewTestLicense("")) - - _, resp, err := client.GetTopChannelsForTeamSince(context.Background(), teamId, model.TimeRangeToday, 0, 5) - assert.Error(t, err) - CheckNotImplementedStatus(t, resp) - }) -} - -func TestGetTopChannelsForUserSince(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - - th.ConfigStore.SetReadOnlyFF(false) - defer th.ConfigStore.SetReadOnlyFF(true) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true }) - th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional)) - - client := th.Client - userId := th.BasicUser.Id - - channel4 := th.CreatePublicChannel() - channel5 := th.CreatePrivateChannel() - channel6 := th.CreatePrivateChannel() - th.App.AddUserToChannel(th.Context, th.BasicUser, channel4, false) - th.App.AddUserToChannel(th.Context, th.BasicUser, channel5, false) - th.App.AddUserToChannel(th.Context, th.BasicUser, channel6, false) - - channelIDs := [6]string{th.BasicChannel.Id, th.BasicChannel2.Id, th.BasicPrivateChannel.Id, channel4.Id, channel5.Id, channel6.Id} - - i := len(channelIDs) - for _, channelID := range channelIDs { - for j := i; j > 0; j-- { - _, _, err := client.CreatePost(context.Background(), &model.Post{UserId: userId, ChannelId: channelID, Message: "zz" + model.NewId() + "a"}) - require.NoError(t, err) - } - i-- - } - - teamId := th.BasicChannel.TeamId - - expectedTopChannels := []struct { - ID string - MessageCount int64 - }{ - {ID: th.BasicChannel.Id, MessageCount: 6}, - {ID: th.BasicChannel2.Id, MessageCount: 5}, - {ID: th.BasicPrivateChannel.Id, MessageCount: 4}, - {ID: channel4.Id, MessageCount: 3}, - {ID: channel5.Id, MessageCount: 2}, - } - - t.Run("get-top-channels-for-user-since", func(t *testing.T) { - topChannels, _, err := client.GetTopChannelsForUserSince(context.Background(), teamId, model.TimeRangeToday, 0, 5) - require.NoError(t, err) - - for i, channel := range topChannels.Items { - assert.Equal(t, expectedTopChannels[i].ID, channel.ID) - assert.Equal(t, expectedTopChannels[i].MessageCount, channel.MessageCount) - } - - topChannels, _, err = client.GetTopChannelsForUserSince(context.Background(), "", model.TimeRangeToday, 1, 5) - require.NoError(t, err) - assert.Equal(t, channel6.Id, topChannels.Items[0].ID) - assert.Equal(t, int64(1), topChannels.Items[0].MessageCount) - - t.Run("has post count by day", func(t *testing.T) { - require.NotNil(t, topChannels.PostCountByDuration) - }) - }) - - t.Run("get-top-channels-for-user-since invalid team id", func(t *testing.T) { - _, resp, err := client.GetTopChannelsForUserSince(context.Background(), "12345", model.TimeRangeToday, 0, 5) - assert.Error(t, err) - CheckBadRequestStatus(t, resp) - - _, resp, err = client.GetTopChannelsForUserSince(context.Background(), model.NewId(), model.TimeRangeToday, 0, 5) - assert.Error(t, err) - CheckNotFoundStatus(t, resp) - }) - - t.Run("get-top-channels-for-user-since invalid time range", func(t *testing.T) { - _, resp, err := client.GetTopChannelsForUserSince(context.Background(), teamId, "7_days", 0, 5) - assert.Error(t, err) - CheckBadRequestStatus(t, resp) - }) - - t.Run("get-top-channels-for-user-since not a member of team", func(t *testing.T) { - th.UnlinkUserFromTeam(th.BasicUser, th.BasicTeam) - _, resp, err := client.GetTopChannelsForUserSince(context.Background(), teamId, model.TimeRangeToday, 0, 5) - assert.Error(t, err) - CheckForbiddenStatus(t, resp) - }) -} - -func TestGetTopThreadsForTeamSince(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional)) - - th.ConfigStore.SetReadOnlyFF(false) - defer th.ConfigStore.SetReadOnlyFF(true) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true }) - th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional)) - - th.LoginBasic() - client := th.Client - - // create a public channel, a private channel - - channelPublic := th.BasicChannel - channelPrivate := th.BasicPrivateChannel - th.App.AddUserToChannel(th.Context, th.BasicUser, channelPublic, false) - th.App.AddUserToChannel(th.Context, th.BasicUser, channelPrivate, false) - th.App.AddUserToChannel(th.Context, th.BasicUser2, channelPublic, false) - th.App.RemoveUserFromChannel(th.Context, th.BasicUser2.Id, th.BasicUser.Id, channelPrivate) - - // create two threads: one in public channel, one in private - // post in public channel has both users interacting, post in private only has user1 interacting - - rootPostPublicChannel, appErr := th.App.CreatePost(th.Context, &model.Post{ - UserId: th.BasicUser.Id, - ChannelId: channelPublic.Id, - Message: "root post pub", - }, channelPublic, false, true) - require.Nil(t, appErr) - - _, appErr = th.App.CreatePost(th.Context, &model.Post{ - UserId: th.BasicUser2.Id, - ChannelId: channelPublic.Id, - RootId: rootPostPublicChannel.Id, - Message: "reply post 1", - }, channelPublic, false, true) - require.Nil(t, appErr) - - rootPostPrivateChannel, appErr := th.App.CreatePost(th.Context, &model.Post{ - UserId: th.BasicUser.Id, - ChannelId: channelPrivate.Id, - Message: "root post priv", - }, channelPrivate, false, true) - require.Nil(t, appErr) - - _, appErr = th.App.CreatePost(th.Context, &model.Post{ - UserId: th.BasicUser.Id, - ChannelId: channelPrivate.Id, - RootId: rootPostPrivateChannel.Id, - Message: "reply post 1", - }, channelPrivate, false, true) - require.Nil(t, appErr) - - _, appErr = th.App.CreatePost(th.Context, &model.Post{ - UserId: th.BasicUser.Id, - ChannelId: channelPrivate.Id, - RootId: rootPostPrivateChannel.Id, - Message: "reply post 2", - }, channelPrivate, false, true) - - require.Nil(t, appErr) - - // get top threads for team, as user 1 and user 2 - // user 1, 2 should see both threads - - topTeamThreadsByUser1, _, _ := client.GetTopThreadsForTeamSince(context.Background(), th.BasicTeam.Id, model.TimeRangeToday, 0, 10) - require.Nil(t, appErr) - require.Len(t, topTeamThreadsByUser1.Items, 2) - require.Equal(t, topTeamThreadsByUser1.Items[0].Post.Id, rootPostPrivateChannel.Id) - require.Equal(t, topTeamThreadsByUser1.Items[1].Post.Id, rootPostPublicChannel.Id) - - client.Logout(context.Background()) - - th.LoginBasic2() - - client = th.Client - - topTeamThreadsByUser2, _, _ := client.GetTopThreadsForTeamSince(context.Background(), th.BasicTeam.Id, model.TimeRangeToday, 0, 10) - require.Nil(t, appErr) - require.Len(t, topTeamThreadsByUser2.Items, 1) - require.Equal(t, topTeamThreadsByUser2.Items[0].Post.Id, rootPostPublicChannel.Id) - - // add user2 to private channel and it can see 2 top threads. - th.AddUserToChannel(th.BasicUser2, channelPrivate) - topTeamThreadsByUser2IncludingPrivate, _, _ := client.GetTopThreadsForTeamSince(context.Background(), th.BasicTeam.Id, model.TimeRangeToday, 0, 10) - require.Nil(t, appErr) - require.Len(t, topTeamThreadsByUser2IncludingPrivate.Items, 2) - - t.Run("get-top-threads-for-team-since invalid license", func(t *testing.T) { - th.App.Srv().SetLicense(model.NewTestLicense("")) - - _, resp, err := client.GetTopThreadsForTeamSince(context.Background(), th.BasicTeam.Id, model.TimeRangeToday, 0, 5) - assert.Error(t, err) - CheckNotImplementedStatus(t, resp) - }) -} - -func TestGetTopThreadsForUserSince(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - - th.ConfigStore.SetReadOnlyFF(false) - defer th.ConfigStore.SetReadOnlyFF(true) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true }) - th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional)) - - th.LoginBasic() - client := th.Client - - // create a public channel, a private channel - - channelPublic := th.BasicChannel - channelPrivate := th.BasicPrivateChannel - th.App.AddUserToChannel(th.Context, th.BasicUser, channelPublic, false) - th.App.AddUserToChannel(th.Context, th.BasicUser, channelPrivate, false) - th.App.AddUserToChannel(th.Context, th.BasicUser2, channelPublic, false) - - // create two threads: one in public channel, one in private - // post in public channel has both users interacting, post in private only has user1 interacting - - rootPostPublicChannel, appErr := th.App.CreatePost(th.Context, &model.Post{ - UserId: th.BasicUser.Id, - ChannelId: channelPublic.Id, - Message: "root post pub", - }, channelPublic, false, true) - require.Nil(t, appErr) - - _, appErr = th.App.CreatePost(th.Context, &model.Post{ - UserId: th.BasicUser2.Id, - ChannelId: channelPublic.Id, - RootId: rootPostPublicChannel.Id, - Message: "reply post 1", - }, channelPublic, false, true) - require.Nil(t, appErr) - - rootPostPrivateChannel, appErr := th.App.CreatePost(th.Context, &model.Post{ - UserId: th.BasicUser.Id, - ChannelId: channelPrivate.Id, - Message: "root post priv", - }, channelPrivate, false, true) - require.Nil(t, appErr) - - _, appErr = th.App.CreatePost(th.Context, &model.Post{ - UserId: th.BasicUser.Id, - ChannelId: channelPrivate.Id, - RootId: rootPostPrivateChannel.Id, - Message: "reply post 1", - }, channelPrivate, false, true) - require.Nil(t, appErr) - - _, appErr = th.App.CreatePost(th.Context, &model.Post{ - UserId: th.BasicUser.Id, - ChannelId: channelPrivate.Id, - RootId: rootPostPrivateChannel.Id, - Message: "reply post 2", - }, channelPrivate, false, true) - - require.Nil(t, appErr) - - // get top threads for user, as user 1 and user 2 - // user 1 should see both threads, while user 2 should see only thread in public channel - // (even if user2 is in the private channel it hasn't interacted with the thread there.) - - topUser1Threads, _, _ := client.GetTopThreadsForUserSince(context.Background(), th.BasicTeam.Id, model.TimeRangeToday, 0, 10) - require.Nil(t, appErr) - require.Len(t, topUser1Threads.Items, 2) - require.Equal(t, topUser1Threads.Items[0].Post.Id, rootPostPrivateChannel.Id) - require.Equal(t, topUser1Threads.Items[0].Post.ReplyCount, int64(2)) - require.Equal(t, topUser1Threads.Items[1].Post.Id, rootPostPublicChannel.Id) - require.Contains(t, topUser1Threads.Items[1].Participants, th.BasicUser2.Id) - require.Equal(t, topUser1Threads.Items[1].Post.ReplyCount, int64(1)) - - client.Logout(context.Background()) - - th.LoginBasic2() - - client = th.Client - - topUser2Threads, _, _ := client.GetTopThreadsForUserSince(context.Background(), th.BasicTeam.Id, model.TimeRangeToday, 0, 10) - require.Nil(t, appErr) - require.Len(t, topUser2Threads.Items, 1) - require.Equal(t, topUser2Threads.Items[0].Post.Id, rootPostPublicChannel.Id) - require.Equal(t, topUser2Threads.Items[0].Post.ReplyCount, int64(1)) - - // deleting the root post results in the thread not making it to top threads list - _, appErr = th.App.DeletePost(th.Context, rootPostPublicChannel.Id, th.BasicUser.Id) - require.Nil(t, appErr) - - client.Logout(context.Background()) - - th.LoginBasic() - - client = th.Client - - topUser1ThreadsAfterPost1Delete, _, _ := client.GetTopThreadsForUserSince(context.Background(), th.BasicTeam.Id, model.TimeRangeToday, 0, 10) - require.Nil(t, appErr) - require.Len(t, topUser1ThreadsAfterPost1Delete.Items, 1) - - client.Logout(context.Background()) - - th.LoginBasic2() - - client = th.Client - - // reply with user2 in thread2. deleting that reply, shouldn't give any top thread for user2 if the user2 unsubscribes to the thread after deleting the comment - replyPostUser2InPrivate, appErr := th.App.CreatePost(th.Context, &model.Post{ - UserId: th.BasicUser2.Id, - ChannelId: channelPrivate.Id, - RootId: rootPostPrivateChannel.Id, - Message: "reply post 3", - }, channelPrivate, false, true) - require.Nil(t, appErr) - - topUser2ThreadsAfterPrivateReply, _, _ := client.GetTopThreadsForUserSince(context.Background(), th.BasicTeam.Id, model.TimeRangeToday, 0, 10) - require.Nil(t, appErr) - require.Len(t, topUser2ThreadsAfterPrivateReply.Items, 1) - - // deleting reply, and unfollowing thread - _, appErr = th.App.DeletePost(th.Context, replyPostUser2InPrivate.Id, th.BasicUser2.Id) - require.Nil(t, appErr) - // unfollow thread - _, err := th.App.Srv().Store().Thread().MaintainMembership(th.BasicUser2.Id, rootPostPrivateChannel.Id, store.ThreadMembershipOpts{ - Following: false, - UpdateFollowing: true, - }) - require.NoError(t, err) - - topUser2ThreadsAfterPrivateReplyDelete, _, _ := client.GetTopThreadsForUserSince(context.Background(), th.BasicTeam.Id, model.TimeRangeToday, 0, 10) - require.Nil(t, appErr) - require.Len(t, topUser2ThreadsAfterPrivateReplyDelete.Items, 0) -} - -func TestGetTopInactiveChannelsForTeamSince(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - - th.ConfigStore.SetReadOnlyFF(false) - defer th.ConfigStore.SetReadOnlyFF(true) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true }) - th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional)) - - // delete offtopic, town-square, th.basicchannel channel - which interferes with 'least' active channel results - offTopicChannel, appErr := th.App.GetChannelByName(th.Context, "off-topic", th.BasicTeam.Id, false) - require.Nil(t, appErr, "Expected nil, didn't receive nil") - appErr = th.App.PermanentDeleteChannel(th.Context, offTopicChannel) - require.Nil(t, appErr) - townSquareChannel, appErr := th.App.GetChannelByName(th.Context, "town-square", th.BasicTeam.Id, false) - require.Nil(t, appErr, "Expected nil, didn't receive nil") - appErr = th.App.PermanentDeleteChannel(th.Context, townSquareChannel) - require.Nil(t, appErr) - basicChannel, appErr := th.App.GetChannel(th.Context, th.BasicChannel.Id) - require.Nil(t, appErr, "Expected nil, didn't receive nil") - appErr = th.App.PermanentDeleteChannel(th.Context, basicChannel) - require.Nil(t, appErr) - basicChannel2, appErr := th.App.GetChannel(th.Context, th.BasicChannel2.Id) - require.Nil(t, appErr, "Expected nil, didn't receive nil") - appErr = th.App.PermanentDeleteChannel(th.Context, basicChannel2) - require.Nil(t, appErr) - basicPrivateChannel, appErr := th.App.GetChannel(th.Context, th.BasicPrivateChannel.Id) - require.Nil(t, appErr, "Expected nil, didn't receive nil") - appErr = th.App.PermanentDeleteChannel(th.Context, basicPrivateChannel) - require.Nil(t, appErr) - - th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional)) - - client := th.Client - userId := th.BasicUser.Id - - channel4Req := &model.Channel{ - DisplayName: "channel4", - Name: GenerateTestChannelName(), - Type: model.ChannelTypeOpen, - TeamId: th.BasicTeam.Id, - CreateAt: 1, - } - channel4, _, err := client.CreateChannel(context.Background(), channel4Req) - require.NoError(t, err) - - channel5Req := &model.Channel{ - DisplayName: "channel4", - Name: GenerateTestChannelName(), - Type: model.ChannelTypePrivate, - TeamId: th.BasicTeam.Id, - CreateAt: 1, - } - channel5, _, err := client.CreateChannel(context.Background(), channel5Req) - require.NoError(t, err) - - channel6Req := &model.Channel{ - DisplayName: "channel4", - Name: GenerateTestChannelName(), - Type: model.ChannelTypePrivate, - TeamId: th.BasicTeam.Id, - CreateAt: 1, - } - channel6, _, err := client.CreateChannel(context.Background(), channel6Req) - require.NoError(t, err) - - th.App.AddUserToChannel(th.Context, th.BasicUser, channel4, false) - th.App.AddUserToChannel(th.Context, th.BasicUser, channel5, false) - th.App.AddUserToChannel(th.Context, th.BasicUser, channel6, false) - - channelIDs := [3]string{channel4.Id, channel5.Id, channel6.Id} - - i := len(channelIDs) - for _, channelID := range channelIDs { - for j := i; j > 0; j-- { - _, _, err := client.CreatePost(context.Background(), &model.Post{UserId: userId, ChannelId: channelID, Message: "zz" + model.NewId() + "a"}) - require.NoError(t, err) - } - i-- - } - - teamId := th.BasicChannel.TeamId - - expectedTopChannels := []struct { - ID string - MessageCount int64 - }{{ - ID: channel6.Id, MessageCount: 1}, - {ID: channel5.Id, MessageCount: 2}, - {ID: channel4.Id, MessageCount: 3}, - } - - t.Run("get-top-inactive-channels-for-team-since", func(t *testing.T) { - topInactiveChannels, _, err := client.GetTopInactiveChannelsForTeamSince(context.Background(), teamId, model.TimeRangeToday, 0, 2) - require.NoError(t, err) - - for i, channel := range topInactiveChannels.Items { - assert.Equal(t, expectedTopChannels[i].ID, channel.ID) - } - - topInactiveChannels, _, err = client.GetTopInactiveChannelsForTeamSince(context.Background(), teamId, model.TimeRangeToday, 1, 2) - require.NoError(t, err) - assert.Equal(t, channel4.Id, topInactiveChannels.Items[0].ID) - }) - - t.Run("get-top-channels-for-user-since exclude channels user is not member of", func(t *testing.T) { - excludedChannel := th.CreatePrivateChannel() - - for i := 0; i < 10; i++ { - _, _, err := client.CreatePost(context.Background(), &model.Post{UserId: userId, ChannelId: excludedChannel.Id, Message: "zz" + model.NewId() + "a"}) - require.NoError(t, err) - } - - th.RemoveUserFromChannel(th.BasicUser, excludedChannel) - - topInactiveChannels, _, err := client.GetTopInactiveChannelsForUserSince(context.Background(), teamId, model.TimeRangeToday, 0, 3) - require.NoError(t, err) - - for i, channel := range topInactiveChannels.Items { - assert.Equal(t, expectedTopChannels[i].ID, channel.ID) - } - }) - - t.Run("get-top-inactive-channels-for-team-since invalid license", func(t *testing.T) { - th.App.Srv().SetLicense(model.NewTestLicense("")) - - _, resp, err := client.GetTopInactiveChannelsForTeamSince(context.Background(), teamId, model.TimeRangeToday, 0, 5) - assert.Error(t, err) - CheckNotImplementedStatus(t, resp) - }) -} - -func TestGetTopDMsForUserSince(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - - th.ConfigStore.SetReadOnlyFF(false) - defer th.ConfigStore.SetReadOnlyFF(true) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true }) - th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional)) - - th.ConfigStore.SetReadOnlyFF(false) - defer th.ConfigStore.SetReadOnlyFF(true) - th.App.UpdateConfig(func(c *model.Config) { - *c.TeamSettings.EnableUserDeactivation = true - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableBotAccountCreation = true }) - - // basicuser1 - bu1, basicuser - bu - // create dm channels for bu-bu, bu1-bu1, bu-bu1, bot-bu - basicUser := th.BasicUser - basicUser1 := th.BasicUser2 - - th.LoginBasic2() - client := th.Client - channelBu1Bu1, _, err := client.CreateDirectChannel(context.Background(), basicUser1.Id, basicUser1.Id) - require.NoError(t, err) - - th.LoginBasic() - client = th.Client - channelBuBu, _, err := client.CreateDirectChannel(context.Background(), basicUser.Id, basicUser.Id) - require.NoError(t, err) - channelBuBu1, _, err := client.CreateDirectChannel(context.Background(), basicUser.Id, basicUser1.Id) - require.NoError(t, err) - - // bot creation with permission - th.AddPermissionToRole(model.PermissionCreateBot.Id, model.TeamUserRoleId) - th.App.UpdateUserRoles(th.Context, th.BasicUser.Id, model.TeamUserRoleId+" "+model.SystemUserRoleId, false) - bot := &model.Bot{ - Username: GenerateTestUsername(), - DisplayName: "a bot", - Description: "bot", - UserId: model.NewId(), - } - - createdBot, resp, err := th.Client.CreateBot(context.Background(), bot) - require.NoError(t, err) - CheckCreatedStatus(t, resp) - defer th.App.PermanentDeleteBot(createdBot.UserId) - channelBuBot, _, err := client.CreateDirectChannel(context.Background(), basicUser.Id, createdBot.UserId) - require.NoError(t, err) - - // create 2 posts in channelBu, 1 in channelBu1, 3 in channelBu12 - postsGenConfig := []map[string]interface{}{ - { - "chId": channelBuBu.Id, - "postCount": 2, - }, - { - "chId": channelBu1Bu1.Id, - "postCount": 1, - }, - { - "chId": channelBuBu1.Id, - "postCount": 3, - }, - { - "chId": channelBuBot.Id, - "postCount": 4, - }, - } - - for _, postGen := range postsGenConfig { - postCount := postGen["postCount"].(int) - for i := 0; i < postCount; i++ { - if postGen["chId"] == channelBu1Bu1.Id { - th.LoginBasic2() - client = th.Client - userId := basicUser1.Id - post := &model.Post{UserId: userId, ChannelId: postGen["chId"].(string), Message: "zz" + model.NewId() + "a"} - _, _, err = client.CreatePost(context.Background(), post) - require.NoError(t, err) - } else { - th.LoginBasic() - client = th.Client - userId := basicUser.Id - post := &model.Post{UserId: userId, ChannelId: postGen["chId"].(string), Message: "zz" + model.NewId() + "a"} - _, _, err = client.CreatePost(context.Background(), post) - require.NoError(t, err) - } - } - } - - // get top dms for bu - t.Run("get top dms for basic user 1", func(t *testing.T) { - th.LoginBasic() - client = th.Client - topDMs, _, topDmsErr := client.GetTopDMsForUserSince(context.Background(), "today", 0, 100) - require.NoError(t, topDmsErr) - require.Len(t, topDMs.Items, 1) - require.Equal(t, topDMs.Items[0].MessageCount, int64(3)) - require.Equal(t, topDMs.Items[0].SecondParticipant.Id, basicUser1.Id) - - // test pagination - topDMsPage0PerPage1, _, topDmsErr := client.GetTopDMsForUserSince(context.Background(), "today", 0, 2) - require.NoError(t, topDmsErr) - require.Len(t, topDMsPage0PerPage1.Items, 1) - require.Equal(t, topDMsPage0PerPage1.HasNext, false) - require.Equal(t, topDMsPage0PerPage1.Items[0].SecondParticipant.Id, basicUser1.Id) - }) - - // get top dms for bu1 - t.Run("get top dms for basic user 2", func(t *testing.T) { - th.LoginBasic2() - client = th.Client - topDMs, _, topDmsErr := client.GetTopDMsForUserSince(context.Background(), "today", 0, 100) - require.NoError(t, topDmsErr) - require.Len(t, topDMs.Items, 1) - require.Equal(t, topDMs.Items[0].MessageCount, int64(3)) - }) - // deactivate basicuser1 - _, err = th.Client.DeleteUser(context.Background(), basicUser1.Id) - require.NoError(t, err) - // deactivated users DMs should show in topDMs - t.Run("get top dms for basic user 1", func(t *testing.T) { - th.LoginBasic() - client = th.Client - topDMs, _, topDmsErr := client.GetTopDMsForUserSince(context.Background(), "today", 0, 100) - require.NoError(t, topDmsErr) - require.Len(t, topDMs.Items, 1) - require.Equal(t, topDMs.Items[0].MessageCount, int64(3)) - }) -} - -func TestNewTeamMembersSince(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - - th.ConfigStore.SetReadOnlyFF(false) - defer th.ConfigStore.SetReadOnlyFF(true) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true }) - - th.LoginBasic() - - team := th.CreateTeam() - - t.Run("accepts only starter or professional license skus", func(t *testing.T) { - _, resp, _ := th.Client.GetNewTeamMembersSince(context.Background(), team.Id, model.TimeRangeToday, 0, 5) - CheckNotImplementedStatus(t, resp) - - th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuE10)) - _, resp, _ = th.Client.GetNewTeamMembersSince(context.Background(), team.Id, model.TimeRangeToday, 0, 5) - CheckNotImplementedStatus(t, resp) - - th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuE20)) - _, resp, _ = th.Client.GetNewTeamMembersSince(context.Background(), team.Id, model.TimeRangeToday, 0, 5) - CheckNotImplementedStatus(t, resp) - - th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional)) - _, resp, err := th.Client.GetNewTeamMembersSince(context.Background(), team.Id, model.TimeRangeToday, 0, 5) - require.NoError(t, err) - CheckOKStatus(t, resp) - - th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterprise)) - _, resp, err = th.Client.GetNewTeamMembersSince(context.Background(), team.Id, model.TimeRangeToday, 0, 5) - require.NoError(t, err) - CheckOKStatus(t, resp) - }) - - t.Run("rejects guests", func(t *testing.T) { - _, resp, err := th.Client.GetNewTeamMembersSince(context.Background(), team.Id, model.TimeRangeToday, 0, 5) - require.NoError(t, err) - CheckOKStatus(t, resp) - - th.App.DemoteUserToGuest(th.Context, th.BasicUser) - defer th.App.PromoteGuestToUser(th.Context, th.BasicUser, "") - - _, resp, _ = th.Client.GetNewTeamMembersSince(context.Background(), team.Id, model.TimeRangeToday, 0, 5) - CheckNotImplementedStatus(t, resp) - }) - - t.Run("includes all data for the user", func(t *testing.T) { - checkUser := func(ntm *model.NewTeamMember, hasProfilePicture bool) { - require.Equal(t, th.BasicUser.Id, ntm.Id) - require.Equal(t, th.BasicUser.Username, ntm.Username) - require.Equal(t, th.BasicUser.FirstName, ntm.FirstName) - require.Equal(t, th.BasicUser.LastName, ntm.LastName) - require.Equal(t, th.BasicUser.Position, ntm.Position) - require.Equal(t, th.BasicUser.Nickname, ntm.Nickname) - member, err := th.App.GetTeamMember(team.Id, th.BasicUser.Id) - require.Nil(t, err) - require.Equal(t, member.CreateAt, ntm.CreateAt) - if hasProfilePicture { - require.Truef(t, ntm.LastPictureUpdate > int64(0), "should be greater than 0, but was %d", ntm.LastPictureUpdate) - } else { - require.Equal(t, int64(0), ntm.LastPictureUpdate) - } - } - - th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional)) - list, resp, err := th.Client.GetNewTeamMembersSince(context.Background(), team.Id, model.TimeRangeToday, 0, 5) - require.NoError(t, err) - CheckOKStatus(t, resp) - checkUser(list.Items[0], false) - - data, err := testutils.ReadTestFile("test.png") - require.NoError(t, err) - _, err = th.Client.SetProfileImage(context.Background(), th.BasicUser.Id, data) - require.NoError(t, err) - - list, resp, err = th.Client.GetNewTeamMembersSince(context.Background(), team.Id, model.TimeRangeToday, 0, 5) - require.NoError(t, err) - CheckOKStatus(t, resp) - checkUser(list.Items[0], true) - }) - - t.Run("implements pagination", func(t *testing.T) { - // check the first page of results - list, resp, err := th.Client.GetNewTeamMembersSince(context.Background(), team.Id, model.TimeRangeToday, 0, 2) - require.NoError(t, err) - CheckOKStatus(t, resp) - - require.Equal(t, int(list.TotalCount), 1) - require.Len(t, list.Items, 1) - require.False(t, list.HasNext) - - // check the 2nd page - list, resp, err = th.Client.GetNewTeamMembersSince(context.Background(), team.Id, model.TimeRangeToday, 1, 2) - require.NoError(t, err) - CheckOKStatus(t, resp) - - require.GreaterOrEqual(t, len(list.Items), 0) - - // add a few new team members and re-test the pagination - user := th.CreateUser() - _, appErr := th.App.AddTeamMember(th.Context, team.Id, th.BasicUser2.Id) - require.Nil(t, appErr) - _, appErr = th.App.AddTeamMember(th.Context, team.Id, user.Id) - require.Nil(t, appErr) - - list, resp, err = th.Client.GetNewTeamMembersSince(context.Background(), team.Id, model.TimeRangeToday, 0, 2) - require.NoError(t, err) - CheckOKStatus(t, resp) - require.Equal(t, 3, int(list.TotalCount)) - require.Len(t, list.Items, 2) - require.True(t, list.HasNext) - - list, resp, err = th.Client.GetNewTeamMembersSince(context.Background(), team.Id, model.TimeRangeToday, 1, 2) - require.NoError(t, err) - CheckOKStatus(t, resp) - require.Equal(t, int(list.TotalCount), 3) - require.Len(t, list.Items, 1) - require.False(t, list.HasNext) - }) - - t.Run("get-new-team-members-since invalid license", func(t *testing.T) { - th.App.Srv().SetLicense(model.NewTestLicense("")) - - _, resp, err := th.Client.GetNewTeamMembersSince(context.Background(), team.Id, model.TimeRangeToday, 0, 2) - assert.Error(t, err) - CheckNotImplementedStatus(t, resp) - }) -} diff --git a/server/channels/api4/preference_test.go b/server/channels/api4/preference_test.go index 5f41afd3f2..4c2c15673a 100644 --- a/server/channels/api4/preference_test.go +++ b/server/channels/api4/preference_test.go @@ -50,8 +50,8 @@ func TestGetPreferences(t *testing.T) { prefs, _, err := client.GetPreferences(context.Background(), user1.Id) require.NoError(t, err) - // 6 because we have 3 initial preferences insights, tutorial_step and recommended_next_steps added when creating a new user - require.Equal(t, len(prefs), 6, "received the wrong number of preferences") + // 5 because we have 2 initial preferences tutorial_step and recommended_next_steps added when creating a new user + require.Equal(t, len(prefs), 5, "received the wrong number of preferences") for _, preference := range prefs { require.Equal(t, preference.UserId, th.BasicUser.Id, "user id does not match") diff --git a/server/channels/app/app_iface.go b/server/channels/app/app_iface.go index eac41abf0a..58dc603fe6 100644 --- a/server/channels/app/app_iface.go +++ b/server/channels/app/app_iface.go @@ -285,12 +285,6 @@ type AppIface interface { // PopulateWebConnConfig checks if the connection id already exists in the hub, // and if so, accordingly populates the other fields of the webconn. PopulateWebConnConfig(s *model.Session, cfg *platform.WebConnConfig, seqVal string) (*platform.WebConnConfig, error) - // PostCountsByDuration returns the post counts for the given channels, grouped by day, starting at the given time. - // Unless one is specifically itending to omit results from part of the calendar day, it will typically makes the most sense to - // use a sinceUnixMillis parameter value as returned by model.GetStartOfDayMillis. - // - // WARNING: PostCountsByDuration PERFORMS NO AUTHORIZATION CHECKS ON THE GIVEN CHANNELS. - PostCountsByDuration(c request.CTX, channelIDs []string, sinceUnixMillis int64, userID *string, grouping model.PostCountGrouping, groupingLocation *time.Location) ([]*model.DurationPostCount, *model.AppError) // PromoteGuestToUser Convert user's roles and all his membership's roles from // guest roles to regular user roles. PromoteGuestToUser(c *request.Context, user *model.User, requestorId string) *model.AppError @@ -696,7 +690,6 @@ type AppIface interface { GetMemberCountsByGroup(ctx context.Context, channelID string, includeTimezones bool) ([]*model.ChannelMemberCountByGroup, *model.AppError) GetMessageForNotification(post *model.Post, translateFunc i18n.TranslateFunc) string GetMultipleEmojiByName(c request.CTX, names []string) ([]*model.Emoji, *model.AppError) - GetNewTeamMembersSince(c request.CTX, teamID string, opts *model.InsightsOpts) (*model.NewTeamMembersList, int64, *model.AppError) GetNewUsersForTeamPage(teamID string, page, perPage int, asAdmin bool, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) GetNextPostIdFromPostList(postList *model.PostList, collapsedThreads bool) string GetNotificationNameFormat(user *model.User) string @@ -817,15 +810,6 @@ type AppIface interface { GetThreadMembershipsForUser(userID, teamID string) ([]*model.ThreadMembership, error) GetThreadsForUser(userID, teamID string, options model.GetUserThreadsOpts) (*model.Threads, *model.AppError) GetTokenById(token string) (*model.Token, *model.AppError) - GetTopChannelsForTeamSince(c request.CTX, teamID, userID string, opts *model.InsightsOpts) (*model.TopChannelList, *model.AppError) - GetTopChannelsForUserSince(c request.CTX, userID, teamID string, opts *model.InsightsOpts) (*model.TopChannelList, *model.AppError) - GetTopDMsForUserSince(userID string, opts *model.InsightsOpts) (*model.TopDMList, *model.AppError) - GetTopInactiveChannelsForTeamSince(c request.CTX, teamID, userID string, opts *model.InsightsOpts) (*model.TopInactiveChannelList, *model.AppError) - GetTopInactiveChannelsForUserSince(c request.CTX, teamID, userID string, opts *model.InsightsOpts) (*model.TopInactiveChannelList, *model.AppError) - GetTopReactionsForTeamSince(teamID string, userID string, opts *model.InsightsOpts) (*model.TopReactionList, *model.AppError) - GetTopReactionsForUserSince(userID string, teamID string, opts *model.InsightsOpts) (*model.TopReactionList, *model.AppError) - GetTopThreadsForTeamSince(c request.CTX, teamID, userID string, opts *model.InsightsOpts) (*model.TopThreadList, *model.AppError) - GetTopThreadsForUserSince(c request.CTX, teamID, userID string, opts *model.InsightsOpts) (*model.TopThreadList, *model.AppError) GetTrueUpProfile() (map[string]any, error) GetUploadSession(c request.CTX, uploadId string) (*model.UploadSession, *model.AppError) GetUploadSessionsForUser(userID string) ([]*model.UploadSession, *model.AppError) diff --git a/server/channels/app/channel.go b/server/channels/app/channel.go index 3e6362ab26..e8def4b305 100644 --- a/server/channels/app/channel.go +++ b/server/channels/app/channel.go @@ -10,7 +10,6 @@ import ( "fmt" "net/http" "strings" - "time" "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/plugin" @@ -3518,66 +3517,3 @@ func (s *Server) getDirectChannel(c request.CTX, userID, otherUserID string) (*m return channel, nil } - -func (a *App) GetTopChannelsForTeamSince(c request.CTX, teamID, userID string, opts *model.InsightsOpts) (*model.TopChannelList, *model.AppError) { - if !a.Config().FeatureFlags.InsightsEnabled { - return nil, model.NewAppError("GetTopChannelsForTeamSince", "api.insights.feature_disabled", nil, "", http.StatusNotImplemented) - } - - topChannels, err := a.Srv().Store().Channel().GetTopChannelsForTeamSince(teamID, userID, opts.StartUnixMilli, opts.Page*opts.PerPage, opts.PerPage) - if err != nil { - return nil, model.NewAppError("GetTopChannelsForTeamSince", model.NoTranslation, nil, "", http.StatusInternalServerError).Wrap(err) - } - return topChannels, nil -} - -func (a *App) GetTopChannelsForUserSince(c request.CTX, userID, teamID string, opts *model.InsightsOpts) (*model.TopChannelList, *model.AppError) { - if !a.Config().FeatureFlags.InsightsEnabled { - return nil, model.NewAppError("GetTopChannelsForUserSince", "api.insights.feature_disabled", nil, "", http.StatusNotImplemented) - } - - topChannels, err := a.Srv().Store().Channel().GetTopChannelsForUserSince(userID, teamID, opts.StartUnixMilli, opts.Page*opts.PerPage, opts.PerPage) - if err != nil { - return nil, model.NewAppError("GetTopChannelsForUserSince", model.NoTranslation, nil, "", http.StatusInternalServerError).Wrap(err) - } - return topChannels, nil -} - -// PostCountsByDuration returns the post counts for the given channels, grouped by day, starting at the given time. -// Unless one is specifically itending to omit results from part of the calendar day, it will typically makes the most sense to -// use a sinceUnixMillis parameter value as returned by model.GetStartOfDayMillis. -// -// WARNING: PostCountsByDuration PERFORMS NO AUTHORIZATION CHECKS ON THE GIVEN CHANNELS. -func (a *App) PostCountsByDuration(c request.CTX, channelIDs []string, sinceUnixMillis int64, userID *string, grouping model.PostCountGrouping, groupingLocation *time.Location) ([]*model.DurationPostCount, *model.AppError) { - if !a.Config().FeatureFlags.InsightsEnabled { - return nil, model.NewAppError("PostCountsByDuration", "api.insights.feature_disabled", nil, "", http.StatusNotImplemented) - } - postCountByDay, err := a.Srv().Store().Channel().PostCountsByDuration(channelIDs, sinceUnixMillis, userID, grouping, groupingLocation) - if err != nil { - return nil, model.NewAppError("PostCountsByDuration", model.NoTranslation, nil, "", http.StatusInternalServerError).Wrap(err) - } - return postCountByDay, nil -} - -func (a *App) GetTopInactiveChannelsForTeamSince(c request.CTX, teamID, userID string, opts *model.InsightsOpts) (*model.TopInactiveChannelList, *model.AppError) { - if !a.Config().FeatureFlags.InsightsEnabled { - return nil, model.NewAppError("GetTopChannelsForTeamSince", "api.insights.feature_disabled", nil, "", http.StatusNotImplemented) - } - topChannels, err := a.Srv().Store().Channel().GetTopInactiveChannelsForTeamSince(teamID, userID, opts.StartUnixMilli, opts.Page*opts.PerPage, opts.PerPage) - if err != nil { - return nil, model.NewAppError("GetTopInactiveChannelsForTeamSince", model.NoTranslation, nil, "", http.StatusInternalServerError).Wrap(err) - } - return topChannels, nil -} - -func (a *App) GetTopInactiveChannelsForUserSince(c request.CTX, teamID, userID string, opts *model.InsightsOpts) (*model.TopInactiveChannelList, *model.AppError) { - if !a.Config().FeatureFlags.InsightsEnabled { - return nil, model.NewAppError("GetTopChannelsForUserSince", "api.insights.feature_disabled", nil, "", http.StatusNotImplemented) - } - - topChannels, err := a.Srv().Store().Channel().GetTopInactiveChannelsForUserSince(teamID, userID, opts.StartUnixMilli, opts.Page*opts.PerPage, opts.PerPage) - if err != nil { - return nil, model.NewAppError("GetTopInactiveChannelsForUserSince", model.NoTranslation, nil, "", http.StatusInternalServerError).Wrap(err) - } - return topChannels, nil -} diff --git a/server/channels/app/channel_test.go b/server/channels/app/channel_test.go index 9b9d5ff44e..a6c3c57ad4 100644 --- a/server/channels/app/channel_test.go +++ b/server/channels/app/channel_test.go @@ -12,7 +12,6 @@ import ( "strings" "sync" "testing" - "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -2495,523 +2494,3 @@ func TestIsCRTEnabledForUser(t *testing.T) { }) } } - -func TestGetTopChannelsForTeamSince(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - - th.ConfigStore.SetReadOnlyFF(false) - defer th.ConfigStore.SetReadOnlyFF(true) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true }) - - channel2 := th.CreateChannel(th.Context, th.BasicTeam) - - // add a bot post to ensure it's not counted - _, err := th.Server.Store().Post().Save(&model.Post{ - Message: "hello from a bot", - ChannelId: channel2.Id, - UserId: th.BasicUser.Id, - Props: model.StringInterface{ - "from_bot": true, - }, - }) - require.NoError(t, err) - - channel3 := th.CreatePrivateChannel(th.Context, th.BasicTeam) - - // add a webhook post to ensure it's not counted - _, err = th.Server.Store().Post().Save(&model.Post{ - Message: "hello from a webhook", - ChannelId: channel3.Id, - UserId: th.BasicUser.Id, - Props: model.StringInterface{ - "from_webhook": true, - }, - }) - require.NoError(t, err) - - // add an oauth app post to ensure it's not counted - _, err = th.Server.Store().Post().Save(&model.Post{ - Message: "hello from an ouath app", - ChannelId: channel3.Id, - UserId: th.BasicUser.Id, - Props: model.StringInterface{ - "from_oauth_app": true, - }, - }) - require.NoError(t, err) - - // add a plugin post to ensure it's not counted - _, err = th.Server.Store().Post().Save(&model.Post{ - Message: "hello from a plugin", - ChannelId: channel3.Id, - UserId: th.BasicUser.Id, - Props: model.StringInterface{ - "from_plugin": true, - }, - }) - require.NoError(t, err) - - // add a system post to ensure it's not counted - _, err = th.Server.Store().Post().Save(&model.Post{ - Message: "system message", - Type: "system_join_channel", - ChannelId: channel3.Id, - UserId: th.BasicUser.Id, - Props: model.StringInterface{ - "from_oauth_app": true, - }, - }) - require.NoError(t, err) - - channel4 := th.CreatePrivateChannel(th.Context, th.BasicTeam) - channel5 := th.CreateChannel(th.Context, th.BasicTeam) - channel6 := th.CreatePrivateChannel(th.Context, th.BasicTeam) - th.AddUserToChannel(th.BasicUser, channel2) - th.AddUserToChannel(th.BasicUser, channel3) - th.AddUserToChannel(th.BasicUser, channel4) - th.AddUserToChannel(th.BasicUser, channel5) - th.AddUserToChannel(th.BasicUser, channel6) - - channels := [6]*model.Channel{th.BasicChannel, channel2, channel3, channel4, channel5, channel6} - - i := len(channels) - for _, channel := range channels { - for j := i; j > 0; j-- { - th.CreatePost(channel) - } - i-- - } - - expectedTopChannels := []struct { - ID string - MessageCount int64 - }{ - {ID: th.BasicChannel.Id, MessageCount: 7}, - {ID: channel2.Id, MessageCount: 5}, - {ID: channel3.Id, MessageCount: 4}, - {ID: channel4.Id, MessageCount: 3}, - {ID: channel5.Id, MessageCount: 2}, - } - - timeRange, _ := model.GetStartOfDayForTimeRange(model.TimeRangeToday, time.Now().Location()) - - t.Run("get-top-channels-for-team-since", func(t *testing.T) { - topChannels, err := th.App.GetTopChannelsForTeamSince(th.Context, th.BasicChannel.TeamId, th.BasicUser.Id, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 0, PerPage: 5}) - require.Nil(t, err) - - for i, channel := range topChannels.Items { - assert.Equal(t, expectedTopChannels[i].ID, channel.ID) - assert.Equal(t, expectedTopChannels[i].MessageCount, channel.MessageCount) - } - - topChannels, err = th.App.GetTopChannelsForTeamSince(th.Context, th.BasicChannel.TeamId, th.BasicUser.Id, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 1, PerPage: 5}) - require.Nil(t, err) - assert.Equal(t, channel6.Id, topChannels.Items[0].ID) - assert.Equal(t, int64(1), topChannels.Items[0].MessageCount) - }) -} - -func TestGetTopChannelsForUserSince(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - - th.ConfigStore.SetReadOnlyFF(false) - defer th.ConfigStore.SetReadOnlyFF(true) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true }) - - channel2 := th.CreateChannel(th.Context, th.BasicTeam) - - // add a bot post to ensure it's not counted - _, err := th.Server.Store().Post().Save(&model.Post{ - Message: "hello from a bot", - ChannelId: channel2.Id, - UserId: th.BasicUser.Id, - Props: model.StringInterface{ - "from_bot": true, - }, - }) - require.NoError(t, err) - - channel3 := th.CreatePrivateChannel(th.Context, th.BasicTeam) - - // add a webhook post to ensure it's not counted - _, err = th.Server.Store().Post().Save(&model.Post{ - Message: "hello from a webhook", - ChannelId: channel3.Id, - UserId: th.BasicUser.Id, - Props: model.StringInterface{ - "from_webhook": true, - }, - }) - require.NoError(t, err) - - channel4 := th.CreatePrivateChannel(th.Context, th.BasicTeam) - channel5 := th.CreateChannel(th.Context, th.BasicTeam) - channel6 := th.CreatePrivateChannel(th.Context, th.BasicTeam) - th.AddUserToChannel(th.BasicUser, channel2) - th.AddUserToChannel(th.BasicUser, channel3) - th.AddUserToChannel(th.BasicUser, channel4) - th.AddUserToChannel(th.BasicUser, channel5) - th.AddUserToChannel(th.BasicUser, channel6) - - channels := [6]*model.Channel{th.BasicChannel, channel2, channel3, channel4, channel5, channel6} - - i := len(channels) - for _, channel := range channels { - for j := i; j > 0; j-- { - th.CreatePost(channel) - } - i-- - } - - expectedTopChannels := []struct { - ID string - MessageCount int64 - }{ - {ID: th.BasicChannel.Id, MessageCount: 7}, - {ID: channel2.Id, MessageCount: 5}, - {ID: channel3.Id, MessageCount: 4}, - {ID: channel4.Id, MessageCount: 3}, - {ID: channel5.Id, MessageCount: 2}, - } - - timeRange, _ := model.GetStartOfDayForTimeRange(model.TimeRangeToday, time.Now().Location()) - - t.Run("get-top-channels-for-user-since", func(t *testing.T) { - topChannels, err := th.App.GetTopChannelsForUserSince(th.Context, th.BasicUser.Id, "", &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 0, PerPage: 5}) - require.Nil(t, err) - - for i, channel := range topChannels.Items { - assert.Equal(t, expectedTopChannels[i].ID, channel.ID) - assert.Equal(t, expectedTopChannels[i].MessageCount, channel.MessageCount) - } - - topChannels, err = th.App.GetTopChannelsForUserSince(th.Context, th.BasicUser.Id, th.BasicChannel.TeamId, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 1, PerPage: 5}) - require.Nil(t, err) - assert.Equal(t, channel6.Id, topChannels.Items[0].ID) - assert.Equal(t, int64(1), topChannels.Items[0].MessageCount) - }) -} - -func TestPostCountsByDuration(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - - th.ConfigStore.SetReadOnlyFF(false) - defer th.ConfigStore.SetReadOnlyFF(true) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true }) - - channel2 := th.CreateChannel(th.Context, th.BasicTeam) - channel3 := th.CreatePrivateChannel(th.Context, th.BasicTeam) - channel4 := th.CreatePrivateChannel(th.Context, th.BasicTeam) - channel5 := th.CreateChannel(th.Context, th.BasicTeam) - channel6 := th.CreatePrivateChannel(th.Context, th.BasicTeam) - defer func() { - th.App.PermanentDeleteChannel(th.Context, channel2) - th.App.PermanentDeleteChannel(th.Context, channel3) - th.App.PermanentDeleteChannel(th.Context, channel4) - th.App.PermanentDeleteChannel(th.Context, channel5) - th.App.PermanentDeleteChannel(th.Context, channel6) - }() - th.AddUserToChannel(th.BasicUser, channel2) - th.AddUserToChannel(th.BasicUser, channel3) - th.AddUserToChannel(th.BasicUser, channel4) - th.AddUserToChannel(th.BasicUser, channel5) - th.AddUserToChannel(th.BasicUser, channel6) - - channels := [6]*model.Channel{th.BasicChannel, channel2, channel3, channel4, channel5, channel6} - channelIDs := []string{th.BasicChannel.Id, channel2.Id, channel3.Id, channel4.Id, channel5.Id, channel6.Id} - - i := len(channels) - for ci, channel := range channels { - for j := i; j > 0; j-- { - d1 := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).AddDate(0, 0, ci) - d2 := time.Date(2009, time.November, 10, 9, 0, 0, 0, time.UTC).AddDate(0, 0, ci) - _, err := th.App.CreatePost(th.Context, &model.Post{ - UserId: th.BasicUser.Id, - ChannelId: channel.Id, - CreateAt: d1.Unix() * 1000, - }, channel, false, false) - require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ - UserId: th.BasicUser2.Id, - ChannelId: channel.Id, - CreateAt: d2.Unix() * 1000, - }, channel, false, false) - require.Nil(t, err) - } - i-- - } - - expectedDayGrouping := map[string]map[string]int{ - "2009-11-10": { - th.BasicChannel.Id: 6, - }, - "2009-11-11": { - channel2.Id: 5, - }, - "2009-11-12": { - channel3.Id: 4, - }, - "2009-11-13": { - channel4.Id: 3, - }, - "2009-11-14": { - channel5.Id: 2, - }, - "2009-11-15": { - channel6.Id: 1, - }, - } - - expectedHourGrouping := map[string]map[string]int{ - "2009-11-15T09": { - channel6.Id: 1, - }, - "2009-11-15T23": { - channel6.Id: 1, - }, - } - - sinceUnixMillis := time.Date(2009, time.November, 9, 23, 0, 0, 0, time.UTC).UnixMilli() - - t.Run("get-post-counts-by-day scoped by user, grouped by day", func(t *testing.T) { - dailyPostCount, err := th.App.PostCountsByDuration(th.Context, channelIDs, sinceUnixMillis, &th.BasicUser.Id, model.PostsByDay, time.Now().UTC().Location()) - require.Nil(t, err) - require.GreaterOrEqual(t, len(dailyPostCount), 6) - - for _, item := range dailyPostCount { - if strings.HasPrefix(item.Duration, "2009") { - expectedCount := expectedDayGrouping[item.Duration][item.ChannelID] - assert.Equal(t, expectedCount, item.PostCount) - } - } - }) - - t.Run("get-post-counts-by-day all users, grouped by day", func(t *testing.T) { - dailyPostCount, err := th.App.PostCountsByDuration(th.Context, channelIDs, sinceUnixMillis, nil, model.PostsByDay, time.Now().UTC().Location()) - require.Nil(t, err) - require.GreaterOrEqual(t, len(dailyPostCount), 6) - - for _, item := range dailyPostCount { - if strings.HasPrefix(item.Duration, "2009") { - expectedCount := expectedDayGrouping[item.Duration][item.ChannelID] - assert.Equal(t, expectedCount*2, item.PostCount) - } - } - }) - - t.Run("get-post-counts-by-day all users, grouped by hour", func(t *testing.T) { - oneDaySince := time.Date(2009, time.November, 14, 23, 0, 0, 0, time.UTC).UnixMilli() - dailyPostCount, err := th.App.PostCountsByDuration(th.Context, channelIDs, oneDaySince, nil, model.PostsByHour, time.Now().UTC().Location()) - require.Nil(t, err) - require.GreaterOrEqual(t, len(dailyPostCount), 1) - - for _, item := range dailyPostCount { - if strings.HasPrefix(item.Duration, "2009") { - expectedCount := expectedHourGrouping[item.Duration][item.ChannelID] - assert.Equal(t, expectedCount, item.PostCount) - } - } - }) -} - -// Top inactive channels - -func TestGetTopInactiveChannelsForTeamSince(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - - th.ConfigStore.SetReadOnlyFF(false) - defer th.ConfigStore.SetReadOnlyFF(true) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true }) - - channel2 := th.CreateChannel(th.Context, th.BasicTeam, WithCreateAt(1)) - channel3 := th.CreateChannel(th.Context, th.BasicTeam, WithCreateAt(1)) - channel4 := th.CreatePrivateChannel(th.Context, th.BasicTeam, WithCreateAt(1)) - channel5 := th.CreateChannel(th.Context, th.BasicTeam, WithCreateAt(1)) - channel6 := th.CreatePrivateChannel(th.Context, th.BasicTeam, WithCreateAt(1)) - th.AddUserToChannel(th.BasicUser, channel2) - th.AddUserToChannel(th.BasicUser, channel3) - th.AddUserToChannel(th.BasicUser, channel4) - th.AddUserToChannel(th.BasicUser, channel5) - th.AddUserToChannel(th.BasicUser, channel6) - - // delete offtopic, town square, basicChannel channel - which interferes with 'least' active channel results - offTopicChannel, appErr := th.App.GetChannelByName(th.Context, "off-topic", th.BasicTeam.Id, false) - require.Nil(t, appErr, "Expected nil, didn't receive nil") - appErr = th.App.PermanentDeleteChannel(th.Context, offTopicChannel) - require.Nil(t, appErr) - townSquareChannel, appErr := th.App.GetChannelByName(th.Context, "town-square", th.BasicTeam.Id, false) - require.Nil(t, appErr, "Expected nil, didn't receive nil") - appErr = th.App.PermanentDeleteChannel(th.Context, townSquareChannel) - require.Nil(t, appErr) - basicChannel, appErr := th.App.GetChannel(th.Context, th.BasicChannel.Id) - require.Nil(t, appErr, "Expected nil, didn't receive nil") - appErr = th.App.PermanentDeleteChannel(th.Context, basicChannel) - require.Nil(t, appErr) - - // add a bot post to ensure it's counted - _, err := th.Server.Store().Post().Save(&model.Post{ - Message: "hello from a bot", - ChannelId: channel2.Id, - UserId: th.BasicUser.Id, - Props: model.StringInterface{ - "from_bot": true, - }, - }) - require.NoError(t, err) - - // add a webhook post to ensure it's counted - _, err = th.Server.Store().Post().Save(&model.Post{ - Message: "hello from a webhook", - ChannelId: channel3.Id, - UserId: th.BasicUser.Id, - Props: model.StringInterface{ - "from_webhook": true, - }, - }) - require.NoError(t, err) - - channels := [5]*model.Channel{channel2, channel3, channel4, channel5, channel6} - - i := len(channels) - for _, channel := range channels { - for j := i; j > 0; j-- { - th.CreatePost(channel) - } - i-- - } - - expectedTopChannels := []struct { - ID string - MessageCount int64 - }{ - {ID: channel6.Id, MessageCount: 1}, - {ID: channel5.Id, MessageCount: 2}, - {ID: channel4.Id, MessageCount: 3}, - {ID: channel3.Id, MessageCount: 5}, - {ID: channel2.Id, MessageCount: 6}, - } - - timeRange, _ := model.GetStartOfDayForTimeRange(model.TimeRangeToday, time.Now().Location()) - - t.Run("get-top-channels-for-team-since", func(t *testing.T) { - topChannels, err := th.App.GetTopInactiveChannelsForTeamSince(th.Context, th.BasicChannel.TeamId, th.BasicUser.Id, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 0, PerPage: 5}) - require.Nil(t, err) - - for i, channel := range topChannels.Items { - assert.Equal(t, expectedTopChannels[i].ID, channel.ID) - assert.Equal(t, expectedTopChannels[i].MessageCount, channel.MessageCount) - } - - topChannels, err = th.App.GetTopInactiveChannelsForTeamSince(th.Context, th.BasicChannel.TeamId, th.BasicUser.Id, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 1, PerPage: 4}) - require.Nil(t, err) - assert.Equal(t, channel2.Id, topChannels.Items[0].ID) - assert.Equal(t, int64(6), topChannels.Items[0].MessageCount) - - // it simulates channel being created recently - _ = th.CreatePrivateChannel(th.Context, th.BasicTeam) - topChannels, err = th.App.GetTopInactiveChannelsForTeamSince(th.Context, th.BasicChannel.TeamId, th.BasicUser.Id, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 0, PerPage: 6}) - require.Nil(t, err) - assert.Equal(t, 5, len(topChannels.Items)) - }) -} - -func TestGetTopInactiveChannelsForUserSince(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - - th.ConfigStore.SetReadOnlyFF(false) - defer th.ConfigStore.SetReadOnlyFF(true) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true }) - - // delete offtopic, town-square, th.basicchannel channels - which interferes with 'least' active channel results - offTopicChannel, appErr := th.App.GetChannelByName(th.Context, "off-topic", th.BasicTeam.Id, false) - require.Nil(t, appErr, "Expected nil, didn't receive nil") - appErr = th.App.PermanentDeleteChannel(th.Context, offTopicChannel) - require.Nil(t, appErr) - townSquareChannel, appErr := th.App.GetChannelByName(th.Context, "town-square", th.BasicTeam.Id, false) - require.Nil(t, appErr, "Expected nil, didn't receive nil") - appErr = th.App.PermanentDeleteChannel(th.Context, townSquareChannel) - require.Nil(t, appErr) - basicChannel, appErr := th.App.GetChannel(th.Context, th.BasicChannel.Id) - require.Nil(t, appErr, "Expected nil, didn't receive nil") - appErr = th.App.PermanentDeleteChannel(th.Context, basicChannel) - require.Nil(t, appErr) - - channel2 := th.CreateChannel(th.Context, th.BasicTeam, WithCreateAt(1)) - - // add a bot post to ensure it's counted - _, err := th.Server.Store().Post().Save(&model.Post{ - Message: "hello from a bot", - ChannelId: channel2.Id, - UserId: th.BasicUser.Id, - Props: model.StringInterface{ - "from_bot": true, - }, - }) - require.NoError(t, err) - - channel3 := th.CreatePrivateChannel(th.Context, th.BasicTeam, WithCreateAt(1)) - - // add a webhook post to ensure it's counted - _, err = th.Server.Store().Post().Save(&model.Post{ - Message: "hello from a webhook", - ChannelId: channel3.Id, - UserId: th.BasicUser.Id, - Props: model.StringInterface{ - "from_webhook": true, - }, - }) - require.NoError(t, err) - - channel4 := th.CreatePrivateChannel(th.Context, th.BasicTeam, WithCreateAt(1)) - channel5 := th.CreateChannel(th.Context, th.BasicTeam, WithCreateAt(1)) - channel6 := th.CreatePrivateChannel(th.Context, th.BasicTeam, WithCreateAt(1)) - th.AddUserToChannel(th.BasicUser, channel2) - th.AddUserToChannel(th.BasicUser, channel3) - th.AddUserToChannel(th.BasicUser, channel4) - th.AddUserToChannel(th.BasicUser, channel5) - th.AddUserToChannel(th.BasicUser, channel6) - - channels := [5]*model.Channel{channel2, channel3, channel4, channel5, channel6} - - i := len(channels) - for _, channel := range channels { - for j := i; j > 0; j-- { - th.CreatePost(channel) - } - i-- - } - - expectedTopChannels := []struct { - ID string - MessageCount int64 - }{ - {ID: channel6.Id, MessageCount: 1}, - {ID: channel5.Id, MessageCount: 2}, - {ID: channel4.Id, MessageCount: 3}, - {ID: channel3.Id, MessageCount: 5}, - {ID: channel2.Id, MessageCount: 6}, - } - - timeRange, _ := model.GetStartOfDayForTimeRange(model.TimeRangeToday, time.Now().Location()) - - t.Run("get-top-channels-for-user-since", func(t *testing.T) { - topChannels, err := th.App.GetTopInactiveChannelsForUserSince(th.Context, th.BasicChannel.TeamId, th.BasicUser.Id, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 0, PerPage: 4}) - require.Nil(t, err) - require.Equal(t, len(topChannels.Items), 4) - for i, channel := range topChannels.Items { - assert.Equal(t, expectedTopChannels[i].ID, channel.ID) - assert.Equal(t, expectedTopChannels[i].MessageCount, channel.MessageCount) - } - - topChannels, err = th.App.GetTopInactiveChannelsForUserSince(th.Context, th.BasicChannel.TeamId, th.BasicUser.Id, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 1, PerPage: 4}) - require.Nil(t, err) - require.Equal(t, len(topChannels.Items), 1) - assert.Equal(t, channel2.Id, topChannels.Items[0].ID) - assert.Equal(t, int64(6), topChannels.Items[0].MessageCount) - }) -} diff --git a/server/channels/app/opentracing/opentracing_layer.go b/server/channels/app/opentracing/opentracing_layer.go index 473381a73b..70a2a07d75 100644 --- a/server/channels/app/opentracing/opentracing_layer.go +++ b/server/channels/app/opentracing/opentracing_layer.go @@ -7381,28 +7381,6 @@ func (a *OpenTracingAppLayer) GetMultipleEmojiByName(c request.CTX, names []stri return resultVar0, resultVar1 } -func (a *OpenTracingAppLayer) GetNewTeamMembersSince(c request.CTX, teamID string, opts *model.InsightsOpts) (*model.NewTeamMembersList, int64, *model.AppError) { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetNewTeamMembersSince") - - a.ctx = newCtx - a.app.Srv().Store().SetContext(newCtx) - defer func() { - a.app.Srv().Store().SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - resultVar0, resultVar1, resultVar2 := a.app.GetNewTeamMembersSince(c, teamID, opts) - - if resultVar2 != nil { - span.LogFields(spanlog.Error(resultVar2)) - ext.Error.Set(span, true) - } - - return resultVar0, resultVar1, resultVar2 -} - func (a *OpenTracingAppLayer) GetNewUsersForTeamPage(teamID string, page int, perPage int, asAdmin bool, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetNewUsersForTeamPage") @@ -10291,204 +10269,6 @@ func (a *OpenTracingAppLayer) GetTokenById(token string) (*model.Token, *model.A return resultVar0, resultVar1 } -func (a *OpenTracingAppLayer) GetTopChannelsForTeamSince(c request.CTX, teamID string, userID string, opts *model.InsightsOpts) (*model.TopChannelList, *model.AppError) { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetTopChannelsForTeamSince") - - a.ctx = newCtx - a.app.Srv().Store().SetContext(newCtx) - defer func() { - a.app.Srv().Store().SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - resultVar0, resultVar1 := a.app.GetTopChannelsForTeamSince(c, teamID, userID, opts) - - if resultVar1 != nil { - span.LogFields(spanlog.Error(resultVar1)) - ext.Error.Set(span, true) - } - - return resultVar0, resultVar1 -} - -func (a *OpenTracingAppLayer) GetTopChannelsForUserSince(c request.CTX, userID string, teamID string, opts *model.InsightsOpts) (*model.TopChannelList, *model.AppError) { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetTopChannelsForUserSince") - - a.ctx = newCtx - a.app.Srv().Store().SetContext(newCtx) - defer func() { - a.app.Srv().Store().SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - resultVar0, resultVar1 := a.app.GetTopChannelsForUserSince(c, userID, teamID, opts) - - if resultVar1 != nil { - span.LogFields(spanlog.Error(resultVar1)) - ext.Error.Set(span, true) - } - - return resultVar0, resultVar1 -} - -func (a *OpenTracingAppLayer) GetTopDMsForUserSince(userID string, opts *model.InsightsOpts) (*model.TopDMList, *model.AppError) { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetTopDMsForUserSince") - - a.ctx = newCtx - a.app.Srv().Store().SetContext(newCtx) - defer func() { - a.app.Srv().Store().SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - resultVar0, resultVar1 := a.app.GetTopDMsForUserSince(userID, opts) - - if resultVar1 != nil { - span.LogFields(spanlog.Error(resultVar1)) - ext.Error.Set(span, true) - } - - return resultVar0, resultVar1 -} - -func (a *OpenTracingAppLayer) GetTopInactiveChannelsForTeamSince(c request.CTX, teamID string, userID string, opts *model.InsightsOpts) (*model.TopInactiveChannelList, *model.AppError) { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetTopInactiveChannelsForTeamSince") - - a.ctx = newCtx - a.app.Srv().Store().SetContext(newCtx) - defer func() { - a.app.Srv().Store().SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - resultVar0, resultVar1 := a.app.GetTopInactiveChannelsForTeamSince(c, teamID, userID, opts) - - if resultVar1 != nil { - span.LogFields(spanlog.Error(resultVar1)) - ext.Error.Set(span, true) - } - - return resultVar0, resultVar1 -} - -func (a *OpenTracingAppLayer) GetTopInactiveChannelsForUserSince(c request.CTX, teamID string, userID string, opts *model.InsightsOpts) (*model.TopInactiveChannelList, *model.AppError) { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetTopInactiveChannelsForUserSince") - - a.ctx = newCtx - a.app.Srv().Store().SetContext(newCtx) - defer func() { - a.app.Srv().Store().SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - resultVar0, resultVar1 := a.app.GetTopInactiveChannelsForUserSince(c, teamID, userID, opts) - - if resultVar1 != nil { - span.LogFields(spanlog.Error(resultVar1)) - ext.Error.Set(span, true) - } - - return resultVar0, resultVar1 -} - -func (a *OpenTracingAppLayer) GetTopReactionsForTeamSince(teamID string, userID string, opts *model.InsightsOpts) (*model.TopReactionList, *model.AppError) { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetTopReactionsForTeamSince") - - a.ctx = newCtx - a.app.Srv().Store().SetContext(newCtx) - defer func() { - a.app.Srv().Store().SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - resultVar0, resultVar1 := a.app.GetTopReactionsForTeamSince(teamID, userID, opts) - - if resultVar1 != nil { - span.LogFields(spanlog.Error(resultVar1)) - ext.Error.Set(span, true) - } - - return resultVar0, resultVar1 -} - -func (a *OpenTracingAppLayer) GetTopReactionsForUserSince(userID string, teamID string, opts *model.InsightsOpts) (*model.TopReactionList, *model.AppError) { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetTopReactionsForUserSince") - - a.ctx = newCtx - a.app.Srv().Store().SetContext(newCtx) - defer func() { - a.app.Srv().Store().SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - resultVar0, resultVar1 := a.app.GetTopReactionsForUserSince(userID, teamID, opts) - - if resultVar1 != nil { - span.LogFields(spanlog.Error(resultVar1)) - ext.Error.Set(span, true) - } - - return resultVar0, resultVar1 -} - -func (a *OpenTracingAppLayer) GetTopThreadsForTeamSince(c request.CTX, teamID string, userID string, opts *model.InsightsOpts) (*model.TopThreadList, *model.AppError) { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetTopThreadsForTeamSince") - - a.ctx = newCtx - a.app.Srv().Store().SetContext(newCtx) - defer func() { - a.app.Srv().Store().SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - resultVar0, resultVar1 := a.app.GetTopThreadsForTeamSince(c, teamID, userID, opts) - - if resultVar1 != nil { - span.LogFields(spanlog.Error(resultVar1)) - ext.Error.Set(span, true) - } - - return resultVar0, resultVar1 -} - -func (a *OpenTracingAppLayer) GetTopThreadsForUserSince(c request.CTX, teamID string, userID string, opts *model.InsightsOpts) (*model.TopThreadList, *model.AppError) { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetTopThreadsForUserSince") - - a.ctx = newCtx - a.app.Srv().Store().SetContext(newCtx) - defer func() { - a.app.Srv().Store().SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - resultVar0, resultVar1 := a.app.GetTopThreadsForUserSince(c, teamID, userID, opts) - - if resultVar1 != nil { - span.LogFields(spanlog.Error(resultVar1)) - ext.Error.Set(span, true) - } - - return resultVar0, resultVar1 -} - func (a *OpenTracingAppLayer) GetTotalUsersStats(viewRestrictions *model.ViewUsersRestrictions) (*model.UsersStats, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetTotalUsersStats") @@ -13413,28 +13193,6 @@ func (a *OpenTracingAppLayer) PostAddToChannelMessage(c request.CTX, user *model return resultVar0 } -func (a *OpenTracingAppLayer) PostCountsByDuration(c request.CTX, channelIDs []string, sinceUnixMillis int64, userID *string, grouping model.PostCountGrouping, groupingLocation *time.Location) ([]*model.DurationPostCount, *model.AppError) { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.PostCountsByDuration") - - a.ctx = newCtx - a.app.Srv().Store().SetContext(newCtx) - defer func() { - a.app.Srv().Store().SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - resultVar0, resultVar1 := a.app.PostCountsByDuration(c, channelIDs, sinceUnixMillis, userID, grouping, groupingLocation) - - if resultVar1 != nil { - span.LogFields(spanlog.Error(resultVar1)) - ext.Error.Set(span, true) - } - - return resultVar0, resultVar1 -} - func (a *OpenTracingAppLayer) PostPatchWithProxyRemovedFromImageURLs(patch *model.PostPatch) *model.PostPatch { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.PostPatchWithProxyRemovedFromImageURLs") diff --git a/server/channels/app/plugin_api_test.go b/server/channels/app/plugin_api_test.go index 27c2ce4d0c..05c7d44c94 100644 --- a/server/channels/app/plugin_api_test.go +++ b/server/channels/app/plugin_api_test.go @@ -178,22 +178,17 @@ func TestPluginAPIGetUserPreferences(t *testing.T) { preferences, err := api.GetPreferencesForUser(user1.Id) require.Nil(t, err) - assert.Equal(t, 3, len(preferences)) + assert.Equal(t, 2, len(preferences)) assert.Equal(t, user1.Id, preferences[0].UserId) - assert.Equal(t, model.PreferenceCategoryInsights, preferences[0].Category) - assert.Equal(t, model.PreferenceNameInsights, preferences[0].Name) - assert.Equal(t, "{\"insights_modal_viewed\":false}", preferences[0].Value) + assert.Equal(t, model.PreferenceRecommendedNextSteps, preferences[0].Category) + assert.Equal(t, "hide", preferences[0].Name) + assert.Equal(t, "false", preferences[0].Value) assert.Equal(t, user1.Id, preferences[1].UserId) - assert.Equal(t, model.PreferenceRecommendedNextSteps, preferences[1].Category) - assert.Equal(t, "hide", preferences[1].Name) - assert.Equal(t, "false", preferences[1].Value) - - assert.Equal(t, user1.Id, preferences[2].UserId) - assert.Equal(t, model.PreferenceCategoryTutorialSteps, preferences[2].Category) - assert.Equal(t, user1.Id, preferences[2].Name) - assert.Equal(t, "0", preferences[2].Value) + assert.Equal(t, model.PreferenceCategoryTutorialSteps, preferences[1].Category) + assert.Equal(t, user1.Id, preferences[1].Name) + assert.Equal(t, "0", preferences[1].Value) } func TestPluginAPIDeleteUserPreferences(t *testing.T) { @@ -245,10 +240,9 @@ func TestPluginAPIDeleteUserPreferences(t *testing.T) { require.Nil(t, err) preferences, err = api.GetPreferencesForUser(user2.Id) require.Nil(t, err) - assert.Equal(t, 3, len(preferences)) - assert.Equal(t, model.PreferenceCategoryInsights, preferences[0].Category) - assert.Equal(t, model.PreferenceRecommendedNextSteps, preferences[1].Category) - assert.Equal(t, model.PreferenceCategoryTutorialSteps, preferences[2].Category) + assert.Equal(t, 2, len(preferences)) + assert.Equal(t, model.PreferenceRecommendedNextSteps, preferences[0].Category) + assert.Equal(t, model.PreferenceCategoryTutorialSteps, preferences[1].Category) } func TestPluginAPIUpdateUserPreferences(t *testing.T) { @@ -266,22 +260,16 @@ func TestPluginAPIUpdateUserPreferences(t *testing.T) { preferences, err := api.GetPreferencesForUser(user1.Id) require.Nil(t, err) - assert.Equal(t, 3, len(preferences)) + assert.Equal(t, 2, len(preferences)) assert.Equal(t, user1.Id, preferences[0].UserId) - assert.Equal(t, model.PreferenceCategoryInsights, preferences[0].Category) - assert.Equal(t, model.PreferenceNameInsights, preferences[0].Name) - assert.Equal(t, "{\"insights_modal_viewed\":false}", preferences[0].Value) - + assert.Equal(t, model.PreferenceRecommendedNextSteps, preferences[0].Category) + assert.Equal(t, "hide", preferences[0].Name) + assert.Equal(t, "false", preferences[0].Value) assert.Equal(t, user1.Id, preferences[1].UserId) - assert.Equal(t, model.PreferenceRecommendedNextSteps, preferences[1].Category) - assert.Equal(t, "hide", preferences[1].Name) - assert.Equal(t, "false", preferences[1].Value) - - assert.Equal(t, user1.Id, preferences[2].UserId) - assert.Equal(t, model.PreferenceCategoryTutorialSteps, preferences[2].Category) - assert.Equal(t, user1.Id, preferences[2].Name) - assert.Equal(t, "0", preferences[2].Value) + assert.Equal(t, model.PreferenceCategoryTutorialSteps, preferences[1].Category) + assert.Equal(t, user1.Id, preferences[1].Name) + assert.Equal(t, "0", preferences[1].Value) preference := model.Preference{ Name: user1.Id, @@ -296,8 +284,8 @@ func TestPluginAPIUpdateUserPreferences(t *testing.T) { preferences, err = api.GetPreferencesForUser(user1.Id) require.Nil(t, err) - assert.Equal(t, 4, len(preferences)) - expectedCategories := []string{model.PreferenceCategoryTutorialSteps, model.PreferenceCategoryTheme, model.PreferenceRecommendedNextSteps, model.PreferenceCategoryInsights} + assert.Equal(t, 3, len(preferences)) + expectedCategories := []string{model.PreferenceCategoryTutorialSteps, model.PreferenceCategoryTheme, model.PreferenceRecommendedNextSteps} for _, pref := range preferences { assert.Contains(t, expectedCategories, pref.Category) assert.Equal(t, user1.Id, pref.UserId) diff --git a/server/channels/app/post.go b/server/channels/app/post.go index 8809a92af2..48651dc8e8 100644 --- a/server/channels/app/post.go +++ b/server/channels/app/post.go @@ -2045,49 +2045,6 @@ func (a *App) GetEditHistoryForPost(postID string) ([]*model.Post, *model.AppErr return posts, nil } -func (a *App) GetTopThreadsForTeamSince(c request.CTX, teamID, userID string, opts *model.InsightsOpts) (*model.TopThreadList, *model.AppError) { - if !a.Config().FeatureFlags.InsightsEnabled { - return nil, model.NewAppError("GetTopChannelsForTeamSince", "app.insights.feature_disabled", nil, "", http.StatusNotImplemented) - } - - topThreads, err := a.Srv().Store().Thread().GetTopThreadsForTeamSince(teamID, userID, opts.StartUnixMilli, opts.Page*opts.PerPage, opts.PerPage) - if err != nil { - return nil, model.NewAppError("GetTopChannelsForTeamSince", "app.post.get_top_threads_for_team_since.app_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - topThreadsWithEmbedAndImage, err := includeEmbedsAndImages(a, c, topThreads, userID) - if err != nil { - return nil, model.NewAppError("GetTopChannelsForTeamSince", "app.post.get_top_threads_for_team_since.app_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return topThreadsWithEmbedAndImage, nil -} - -func (a *App) GetTopThreadsForUserSince(c request.CTX, teamID, userID string, opts *model.InsightsOpts) (*model.TopThreadList, *model.AppError) { - if !a.Config().FeatureFlags.InsightsEnabled { - return nil, model.NewAppError("GetTopChannelsForTeamSince", "app.insights.feature_disabled", nil, "", http.StatusNotImplemented) - } - - topThreads, err := a.Srv().Store().Thread().GetTopThreadsForUserSince(teamID, userID, opts.StartUnixMilli, opts.Page*opts.PerPage, opts.PerPage) - if err != nil { - return nil, model.NewAppError("GetTopChannelsForTeamSince", "app.post.get_top_threads_for_team_since.app_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - topThreadsWithEmbedAndImage, err := includeEmbedsAndImages(a, c, topThreads, userID) - if err != nil { - return nil, model.NewAppError("GetTopChannelsForUserSince", "app.post.get_top_threads_for_user_since.app_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return topThreadsWithEmbedAndImage, nil -} - -func (a *App) GetTopDMsForUserSince(userID string, opts *model.InsightsOpts) (*model.TopDMList, *model.AppError) { - if !a.Config().FeatureFlags.InsightsEnabled { - return nil, model.NewAppError("GetTopDMsForUserSince", "app.insights.feature_disabled", nil, "", http.StatusNotImplemented) - } - topDMs, err := a.Srv().Store().Post().GetTopDMsForUserSince(userID, opts.StartUnixMilli, opts.Page*opts.PerPage, opts.PerPage) - if err != nil { - return nil, model.NewAppError("GetTopDMsForUserSince", "app.post.get_top_dms_for_user_since.app_error", nil, err.Error(), http.StatusInternalServerError) - } - return topDMs, nil -} - func (a *App) SetPostReminder(postID, userID string, targetTime int64) *model.AppError { // Store the reminder in the DB reminder := &model.PostReminder{ @@ -2285,15 +2242,3 @@ func (a *App) GetPostInfo(c request.CTX, postID string) (*model.PostInfo, *model } return &info, nil } - -func includeEmbedsAndImages(a *App, c request.CTX, topThreadList *model.TopThreadList, userID string) (*model.TopThreadList, error) { - for _, topThread := range topThreadList.Items { - topThread.Post = a.PreparePostForClientWithEmbedsAndImages(c, topThread.Post, false, false, true) - sanitizedPost, err := a.SanitizePostMetadataForUser(c, topThread.Post, userID) - if err != nil { - return nil, err - } - topThread.Post = sanitizedPost - } - return topThreadList, nil -} diff --git a/server/channels/app/post_test.go b/server/channels/app/post_test.go index f93978cf89..6c1110675e 100644 --- a/server/channels/app/post_test.go +++ b/server/channels/app/post_test.go @@ -3028,195 +3028,6 @@ func TestComputeLastAccessiblePostTime(t *testing.T) { }) } -func TestGetTopThreadsForTeamSince(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - - th.ConfigStore.SetReadOnlyFF(false) - defer th.ConfigStore.SetReadOnlyFF(true) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true }) - - // create a public channel, a private channel - channelPublic := th.CreateChannel(th.Context, th.BasicTeam) - channelPrivate := th.CreatePrivateChannel(th.Context, th.BasicTeam) - th.AddUserToChannel(th.BasicUser, channelPublic) - th.AddUserToChannel(th.BasicUser, channelPrivate) - th.AddUserToChannel(th.BasicUser2, channelPublic) - - // create two threads: one in public channel, one in private with only basicUser1 - - rootPostPublicChannel, appErr := th.App.CreatePost(th.Context, &model.Post{ - UserId: th.BasicUser.Id, - ChannelId: channelPublic.Id, - Message: "root post", - }, channelPublic, false, true) - require.Nil(t, appErr) - - _, appErr = th.App.CreatePost(th.Context, &model.Post{ - UserId: th.BasicUser2.Id, - ChannelId: channelPublic.Id, - RootId: rootPostPublicChannel.Id, - Message: fmt.Sprintf("@%s", th.BasicUser2.Username), - }, channelPublic, false, true) - require.Nil(t, appErr) - - rootPostPrivateChannel, appErr := th.App.CreatePost(th.Context, &model.Post{ - UserId: th.BasicUser.Id, - ChannelId: channelPrivate.Id, - Message: "root post", - }, channelPrivate, false, true) - require.Nil(t, appErr) - - _, appErr = th.App.CreatePost(th.Context, &model.Post{ - UserId: th.BasicUser.Id, - ChannelId: channelPrivate.Id, - RootId: rootPostPrivateChannel.Id, - Message: fmt.Sprintf("@%s", th.BasicUser2.Username), - }, channelPrivate, false, true) - require.Nil(t, appErr) - - _, appErr = th.App.CreatePost(th.Context, &model.Post{ - UserId: th.BasicUser.Id, - ChannelId: channelPrivate.Id, - RootId: rootPostPrivateChannel.Id, - Message: fmt.Sprintf("@%s", th.BasicUser2.Username), - }, channelPrivate, false, true) - - require.Nil(t, appErr) - - // get top threads for team, as user 1 and user 2 - // user 1 should see both threads, while user 2 should see only thread in public channel. - - topTeamThreadsByUser1, appErr := th.App.GetTopThreadsForTeamSince(th.Context, th.BasicTeam.Id, th.BasicUser.Id, &model.InsightsOpts{StartUnixMilli: 200, PerPage: 100}) - require.Nil(t, appErr) - require.Len(t, topTeamThreadsByUser1.Items, 2) - require.Equal(t, topTeamThreadsByUser1.Items[0].Post.Id, rootPostPrivateChannel.Id) - require.Equal(t, topTeamThreadsByUser1.Items[1].Post.Id, rootPostPublicChannel.Id) - - topTeamThreadsByUser2, appErr := th.App.GetTopThreadsForTeamSince(th.Context, th.BasicTeam.Id, th.BasicUser2.Id, &model.InsightsOpts{StartUnixMilli: 200, PerPage: 100}) - require.Nil(t, appErr) - require.Len(t, topTeamThreadsByUser2.Items, 1) - require.Equal(t, topTeamThreadsByUser2.Items[0].Post.Id, rootPostPublicChannel.Id) - - // add user2 to private channel and it can see 2 top threads. - th.AddUserToChannel(th.BasicUser2, channelPrivate) - topTeamThreadsByUser2IncludingPrivate, appErr := th.App.GetTopThreadsForTeamSince(th.Context, th.BasicTeam.Id, th.BasicUser2.Id, &model.InsightsOpts{StartUnixMilli: 200, PerPage: 100}) - require.Nil(t, appErr) - require.Len(t, topTeamThreadsByUser2IncludingPrivate.Items, 2) -} -func TestGetTopThreadsForUserSince(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - - th.ConfigStore.SetReadOnlyFF(false) - defer th.ConfigStore.SetReadOnlyFF(true) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true }) - - // create a public channel, a private channel - channelPublic := th.CreateChannel(th.Context, th.BasicTeam) - channelPrivate := th.CreatePrivateChannel(th.Context, th.BasicTeam) - th.AddUserToChannel(th.BasicUser, channelPublic) - th.AddUserToChannel(th.BasicUser, channelPrivate) - th.AddUserToChannel(th.BasicUser2, channelPublic) - th.AddUserToChannel(th.BasicUser2, channelPrivate) - - // create two threads: one in public channel, one in private - // post in public channel has both users interacting, post in private only has user1 interacting - - rootPostPublicChannel, appErr := th.App.CreatePost(th.Context, &model.Post{ - UserId: th.BasicUser.Id, - ChannelId: channelPublic.Id, - Message: "root post pub", - }, channelPublic, false, true) - require.Nil(t, appErr) - - _, appErr = th.App.CreatePost(th.Context, &model.Post{ - UserId: th.BasicUser2.Id, - ChannelId: channelPublic.Id, - RootId: rootPostPublicChannel.Id, - Message: "reply post 1", - }, channelPublic, false, true) - require.Nil(t, appErr) - - rootPostPrivateChannel, appErr := th.App.CreatePost(th.Context, &model.Post{ - UserId: th.BasicUser.Id, - ChannelId: channelPrivate.Id, - Message: "root post priv", - }, channelPrivate, false, true) - require.Nil(t, appErr) - - _, appErr = th.App.CreatePost(th.Context, &model.Post{ - UserId: th.BasicUser.Id, - ChannelId: channelPrivate.Id, - RootId: rootPostPrivateChannel.Id, - Message: "reply post 1", - }, channelPrivate, false, true) - require.Nil(t, appErr) - - _, appErr = th.App.CreatePost(th.Context, &model.Post{ - UserId: th.BasicUser.Id, - ChannelId: channelPrivate.Id, - RootId: rootPostPrivateChannel.Id, - Message: "reply post 2", - }, channelPrivate, false, true) - - require.Nil(t, appErr) - - // get top threads for user, as user 1 and user 2 - // user 1 should see both threads, while user 2 should see only thread in public channel - // (even if user2 is in the private channel it hasn't interacted with the thread there.) - - topUser1Threads, appErr := th.App.GetTopThreadsForUserSince(th.Context, th.BasicTeam.Id, th.BasicUser.Id, &model.InsightsOpts{StartUnixMilli: 200, PerPage: 100}) - require.Nil(t, appErr) - require.Len(t, topUser1Threads.Items, 2) - require.Equal(t, topUser1Threads.Items[0].Post.Id, rootPostPrivateChannel.Id) - require.Equal(t, topUser1Threads.Items[0].ReplyCount, int64(2)) - require.Equal(t, topUser1Threads.Items[1].Post.Id, rootPostPublicChannel.Id) - require.Contains(t, topUser1Threads.Items[1].Participants, th.BasicUser2.Id) - require.Equal(t, topUser1Threads.Items[1].ReplyCount, int64(1)) - - topUser2Threads, appErr := th.App.GetTopThreadsForUserSince(th.Context, th.BasicTeam.Id, th.BasicUser2.Id, &model.InsightsOpts{StartUnixMilli: 200, PerPage: 100}) - require.Nil(t, appErr) - require.Len(t, topUser2Threads.Items, 1) - require.Equal(t, topUser2Threads.Items[0].Post.Id, rootPostPublicChannel.Id) - require.Equal(t, topUser2Threads.Items[0].ReplyCount, int64(1)) - - // deleting the root post results in the thread not making it to top threads list - _, appErr = th.App.DeletePost(th.Context, rootPostPublicChannel.Id, th.BasicUser.Id) - require.Nil(t, appErr) - - topUser1ThreadsAfterPost1Delete, appErr := th.App.GetTopThreadsForUserSince(th.Context, th.BasicTeam.Id, th.BasicUser.Id, &model.InsightsOpts{StartUnixMilli: 200, PerPage: 100}) - require.Nil(t, appErr) - require.Len(t, topUser1ThreadsAfterPost1Delete.Items, 1) - - // reply with user2 in thread2. deleting that reply, shouldn't give any top thread for user2 if the user2 unsubscribes to the thread after deleting the comment - replyPostUser2InPrivate, appErr := th.App.CreatePost(th.Context, &model.Post{ - UserId: th.BasicUser2.Id, - ChannelId: channelPrivate.Id, - RootId: rootPostPrivateChannel.Id, - Message: "reply post 3", - }, channelPrivate, false, true) - require.Nil(t, appErr) - - topUser2ThreadsAfterPrivateReply, appErr := th.App.GetTopThreadsForUserSince(th.Context, th.BasicTeam.Id, th.BasicUser2.Id, &model.InsightsOpts{StartUnixMilli: 200, PerPage: 100}) - require.Nil(t, appErr) - require.Len(t, topUser2ThreadsAfterPrivateReply.Items, 1) - - // deleting reply, and unfollowing thread - _, appErr = th.App.DeletePost(th.Context, replyPostUser2InPrivate.Id, th.BasicUser2.Id) - require.Nil(t, appErr) - // unfollow thread - _, err := th.App.Srv().Store().Thread().MaintainMembership(th.BasicUser2.Id, rootPostPrivateChannel.Id, store.ThreadMembershipOpts{ - Following: false, - UpdateFollowing: true, - }) - require.NoError(t, err) - - topUser2ThreadsAfterPrivateReplyDelete, appErr := th.App.GetTopThreadsForUserSince(th.Context, th.BasicTeam.Id, th.BasicUser2.Id, &model.InsightsOpts{StartUnixMilli: 200, PerPage: 100}) - require.Nil(t, appErr) - require.Len(t, topUser2ThreadsAfterPrivateReplyDelete.Items, 0) -} - func TestGetEditHistoryForPost(t *testing.T) { t.Skip("This needs fixing, OriginalId seems to be empty for all posts") th := Setup(t).InitBasic() @@ -3262,120 +3073,3 @@ func TestGetEditHistoryForPost(t *testing.T) { require.Empty(t, edits) }) } - -func TestGetTopDMsForUserSince(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - - th.ConfigStore.SetReadOnlyFF(false) - defer th.ConfigStore.SetReadOnlyFF(true) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true }) - - // users - user := th.CreateUser() - u1 := th.CreateUser() - u2 := th.CreateUser() - u3 := th.CreateUser() - u4 := th.CreateUser() - // user direct messages - chUser1, nErr := th.App.createDirectChannel(th.Context, u1.Id, user.Id) - fmt.Println(chUser1, nErr) - require.Nil(t, nErr) - chUser2, nErr := th.App.createDirectChannel(th.Context, u2.Id, user.Id) - require.Nil(t, nErr) - chUser3, nErr := th.App.createDirectChannel(th.Context, u3.Id, user.Id) - require.Nil(t, nErr) - // other user direct message - chUser3User4, nErr := th.App.createDirectChannel(th.Context, u3.Id, u4.Id) - require.Nil(t, nErr) - - // sample post data - // for u1 - _, err := th.App.CreatePostAsUser(th.Context, &model.Post{ - ChannelId: chUser1.Id, - UserId: u1.Id, - }, "", false) - require.Nil(t, err) - _, err = th.App.CreatePostAsUser(th.Context, &model.Post{ - ChannelId: chUser1.Id, - UserId: user.Id, - }, "", false) - require.Nil(t, err) - // for u2: 1 post - _, err = th.App.CreatePostAsUser(th.Context, &model.Post{ - ChannelId: chUser2.Id, - UserId: u2.Id, - }, "", false) - require.Nil(t, err) - // for user-u3: 3 posts - for i := 0; i < 3; i++ { - _, err = th.App.CreatePostAsUser(th.Context, &model.Post{ - ChannelId: chUser3.Id, - UserId: user.Id, - }, "", false) - require.Nil(t, err) - } - // for u4-u3: 4 posts - _, err = th.App.CreatePostAsUser(th.Context, &model.Post{ - ChannelId: chUser3User4.Id, - UserId: u3.Id, - }, "", false) - require.Nil(t, err) - _, err = th.App.CreatePostAsUser(th.Context, &model.Post{ - ChannelId: chUser3User4.Id, - UserId: u4.Id, - }, "", false) - require.Nil(t, err) - _, err = th.App.CreatePostAsUser(th.Context, &model.Post{ - ChannelId: chUser3User4.Id, - UserId: u3.Id, - }, "", false) - require.Nil(t, err) - - _, err = th.App.CreatePostAsUser(th.Context, &model.Post{ - ChannelId: chUser3User4.Id, - UserId: u4.Id, - }, "", false) - require.Nil(t, err) - - t.Run("should return topDMs when userid is specified ", func(t *testing.T) { - topDMs, err := th.App.GetTopDMsForUserSince(user.Id, &model.InsightsOpts{StartUnixMilli: 100, Page: 0, PerPage: 100}) - require.Nil(t, err) - // len of topDMs.Items should be 3 - require.Len(t, topDMs.Items, 3) - // check order, magnitude of items - // fmt.Println(topDMs.Items[0].MessageCount, topDMs.Items[1].MessageCount, topDMs.Items[2].MessageCount) - require.Equal(t, topDMs.Items[0].SecondParticipant.Id, u3.Id) - require.Equal(t, topDMs.Items[0].MessageCount, int64(3)) - require.Equal(t, topDMs.Items[1].SecondParticipant.Id, u1.Id) - require.Equal(t, topDMs.Items[1].MessageCount, int64(2)) - require.Equal(t, topDMs.Items[2].SecondParticipant.Id, u2.Id) - require.Equal(t, topDMs.Items[2].MessageCount, int64(1)) - // this also ensures that u3-u4 conversation doesn't show up in others' top DMs. - }) - t.Run("topDMs should only consider user's DM channels ", func(t *testing.T) { - // u4 only takes part in one conversation - topDMs, err := th.App.GetTopDMsForUserSince(u4.Id, &model.InsightsOpts{StartUnixMilli: 100, Page: 0, PerPage: 100}) - require.Nil(t, err) - // len of topDMs.Items should be 3 - require.Len(t, topDMs.Items, 1) - // check order, magnitude of items - require.Equal(t, topDMs.Items[0].SecondParticipant.Id, u3.Id) - require.Equal(t, topDMs.Items[0].MessageCount, int64(4)) - }) - - t.Run("topDMs will not consider deleted second user", func(t *testing.T) { - // u4 only takes part in one conversation - topDMs, err := th.App.GetTopDMsForUserSince(u4.Id, &model.InsightsOpts{StartUnixMilli: 100, Page: 0, PerPage: 100}) - require.Nil(t, err) - // len of topDMs.Items should be 1 - require.Len(t, topDMs.Items, 1) - // delete user3 - err = th.App.PermanentDeleteUser(th.Context, u3) - require.Nil(t, err) - topDMs, err = th.App.GetTopDMsForUserSince(u4.Id, &model.InsightsOpts{StartUnixMilli: 100, Page: 0, PerPage: 100}) - require.Nil(t, err) - // len of topDMs.Items should be 0 since u3 is deleted - require.Len(t, topDMs.Items, 0) - }) -} diff --git a/server/channels/app/reaction.go b/server/channels/app/reaction.go index 98deb00f7a..56e1bdc211 100644 --- a/server/channels/app/reaction.go +++ b/server/channels/app/reaction.go @@ -100,30 +100,6 @@ func populateEmptyReactions(postIDs []string, reactions map[string][]*model.Reac return reactions } -func (a *App) GetTopReactionsForTeamSince(teamID string, userID string, opts *model.InsightsOpts) (*model.TopReactionList, *model.AppError) { - if !a.Config().FeatureFlags.InsightsEnabled { - return nil, model.NewAppError("GetTopReactionsForTeamSince", "api.insights.feature_disabled", nil, "", http.StatusNotImplemented) - } - - topReactionList, err := a.Srv().Store().Reaction().GetTopForTeamSince(teamID, userID, opts.StartUnixMilli, opts.Page*opts.PerPage, opts.PerPage) - if err != nil { - return nil, model.NewAppError("GetTopReactionsForTeamSince", model.NoTranslation, nil, "", http.StatusInternalServerError).Wrap(err) - } - return topReactionList, nil -} - -func (a *App) GetTopReactionsForUserSince(userID string, teamID string, opts *model.InsightsOpts) (*model.TopReactionList, *model.AppError) { - if !a.Config().FeatureFlags.InsightsEnabled { - return nil, model.NewAppError("GetTopReactionsForUserSince", "api.insights.feature_disabled", nil, "", http.StatusNotImplemented) - } - - topReactionList, err := a.Srv().Store().Reaction().GetTopForUserSince(userID, teamID, opts.StartUnixMilli, opts.Page*opts.PerPage, opts.PerPage) - if err != nil { - return nil, model.NewAppError("GetTopReactionsForUserSince", model.NoTranslation, nil, "", http.StatusInternalServerError).Wrap(err) - } - return topReactionList, nil -} - func (a *App) DeleteReactionForPost(c *request.Context, reaction *model.Reaction) *model.AppError { post, err := a.GetSinglePost(reaction.PostId, false) if err != nil { diff --git a/server/channels/app/reaction_test.go b/server/channels/app/reaction_test.go index d28d49ebad..dd2f244a53 100644 --- a/server/channels/app/reaction_test.go +++ b/server/channels/app/reaction_test.go @@ -5,7 +5,6 @@ package app import ( "testing" - "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -85,347 +84,3 @@ func TestSharedChannelSyncForReactionActions(t *testing.T) { assert.Equal(t, channel.Id, sharedChannelService.channelNotifications[1]) }) } - -func TestGetTopReactionsForTeamSince(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - - th.Server.platform.SetConfigReadOnlyFF(false) - defer th.Server.platform.SetConfigReadOnlyFF(true) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true }) - - userId := th.BasicUser.Id - user2Id := th.BasicUser2.Id - - post1 := th.CreatePost(th.BasicChannel) - post2 := th.CreatePost(th.BasicChannel) - post3 := th.CreatePost(th.BasicChannel) - post4 := th.CreatePost(th.BasicChannel) - post5 := th.CreatePost(th.BasicChannel) - - userReactions := []*model.Reaction{ - { - UserId: userId, - PostId: post1.Id, - EmojiName: "happy", - }, - { - UserId: user2Id, - PostId: post1.Id, - EmojiName: "happy", - }, - { - UserId: userId, - PostId: post1.Id, - EmojiName: "sad", - }, - { - UserId: user2Id, - PostId: post1.Id, - EmojiName: "sad", - }, - { - UserId: userId, - PostId: post1.Id, - EmojiName: "smile", - }, - { - UserId: userId, - PostId: post1.Id, - EmojiName: "joy", - }, - { - UserId: userId, - PostId: post1.Id, - EmojiName: "100", - }, - { - UserId: userId, - PostId: post2.Id, - EmojiName: "sad", - }, - { - UserId: userId, - PostId: post2.Id, - EmojiName: "smile", - }, - { - UserId: userId, - PostId: post2.Id, - EmojiName: "joy", - }, - { - UserId: userId, - PostId: post2.Id, - EmojiName: "100", - }, - { - UserId: userId, - PostId: post3.Id, - EmojiName: "smile", - }, - { - UserId: user2Id, - PostId: post3.Id, - EmojiName: "smile", - }, - { - UserId: userId, - PostId: post3.Id, - EmojiName: "joy", - }, - { - UserId: userId, - PostId: post3.Id, - EmojiName: "100", - }, - { - UserId: userId, - PostId: post4.Id, - EmojiName: "joy", - }, - { - UserId: user2Id, - PostId: post4.Id, - EmojiName: "joy", - }, - { - UserId: userId, - PostId: post4.Id, - EmojiName: "100", - }, - { - UserId: userId, - PostId: post5.Id, - EmojiName: "100", - }, - { - UserId: user2Id, - PostId: post5.Id, - EmojiName: "100", - }, - { - UserId: user2Id, - PostId: post5.Id, - EmojiName: "+1", - }, - { - UserId: userId, - PostId: post1.Id, - EmojiName: "100", - CreateAt: model.GetMillisForTime(time.Now().Add(time.Hour * time.Duration(-25))), - }, - } - - for _, userReaction := range userReactions { - _, err := th.App.Srv().Store().Reaction().Save(userReaction) - require.NoError(t, err) - } - - teamId := th.BasicChannel.TeamId - - var expectedTopReactions [5]*model.TopReaction - expectedTopReactions[0] = &model.TopReaction{EmojiName: "100", Count: int64(6)} - expectedTopReactions[1] = &model.TopReaction{EmojiName: "joy", Count: int64(5)} - expectedTopReactions[2] = &model.TopReaction{EmojiName: "smile", Count: int64(4)} - expectedTopReactions[3] = &model.TopReaction{EmojiName: "sad", Count: int64(3)} - expectedTopReactions[4] = &model.TopReaction{EmojiName: "happy", Count: int64(2)} - - timeRange, _ := model.GetStartOfDayForTimeRange(model.TimeRangeToday, time.Now().Location()) - - t.Run("get-top-reactions-for-team-since", func(t *testing.T) { - topReactions, err := th.App.GetTopReactionsForTeamSince(teamId, userId, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 0, PerPage: 5}) - require.Nil(t, err) - reactions := topReactions.Items - - for i, reaction := range reactions { - assert.Equal(t, expectedTopReactions[i].EmojiName, reaction.EmojiName) - assert.Equal(t, expectedTopReactions[i].Count, reaction.Count) - } - topReactions, err = th.App.GetTopReactionsForTeamSince(teamId, userId, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 1, PerPage: 5}) - require.Nil(t, err) - reactions = topReactions.Items - - assert.Equal(t, "+1", reactions[0].EmojiName) - assert.Equal(t, int64(1), reactions[0].Count) - }) - - t.Run("get-top-reactions-for-team-since feature flag", func(t *testing.T) { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = false }) - defer th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true }) - _, err := th.App.GetTopReactionsForTeamSince(userId, teamId, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 0, PerPage: 5}) - assert.NotNil(t, err) - }) -} - -func TestGetTopReactionsForUserSince(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - - th.Server.platform.SetConfigReadOnlyFF(false) - defer th.Server.platform.SetConfigReadOnlyFF(true) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true }) - - userId := th.BasicUser.Id - - post1 := th.CreatePost(th.BasicChannel) - post2 := th.CreatePost(th.BasicChannel) - post3 := th.CreatePost(th.BasicChannel) - post4 := th.CreatePost(th.BasicChannel) - post5 := th.CreatePost(th.BasicChannel) - post6 := th.CreatePost(th.BasicChannel) - - userReactions := []*model.Reaction{ - { - UserId: userId, - PostId: post1.Id, - EmojiName: "happy", - }, - { - UserId: userId, - PostId: post2.Id, - EmojiName: "happy", - }, - { - UserId: userId, - PostId: post3.Id, - EmojiName: "happy", - }, - { - UserId: userId, - PostId: post4.Id, - EmojiName: "happy", - }, - { - UserId: userId, - PostId: post5.Id, - EmojiName: "happy", - }, - { - UserId: userId, - PostId: post6.Id, - EmojiName: "happy", - }, - { - UserId: userId, - PostId: post1.Id, - EmojiName: "smile", - }, - { - UserId: userId, - PostId: post2.Id, - EmojiName: "smile", - }, - { - UserId: userId, - PostId: post3.Id, - EmojiName: "smile", - }, - { - UserId: userId, - PostId: post4.Id, - EmojiName: "smile", - }, - { - UserId: userId, - PostId: post5.Id, - EmojiName: "smile", - }, - { - UserId: userId, - PostId: post1.Id, - EmojiName: "+1", - }, - { - UserId: userId, - PostId: post2.Id, - EmojiName: "+1", - }, - { - UserId: userId, - PostId: post3.Id, - EmojiName: "+1", - }, - { - UserId: userId, - PostId: post4.Id, - EmojiName: "+1", - }, - { - UserId: userId, - PostId: post1.Id, - EmojiName: "heart", - }, - { - UserId: userId, - PostId: post2.Id, - EmojiName: "heart", - }, - { - UserId: userId, - PostId: post3.Id, - EmojiName: "heart", - }, - { - UserId: userId, - PostId: post1.Id, - EmojiName: "blush", - }, - { - UserId: userId, - PostId: post2.Id, - EmojiName: "blush", - }, - { - UserId: userId, - PostId: post1.Id, - EmojiName: "100", - }, - { - UserId: userId, - PostId: post1.Id, - EmojiName: "100", - CreateAt: model.GetMillisForTime(time.Now().Add(time.Hour * time.Duration(-25))), - }, - } - - for _, userReaction := range userReactions { - _, err := th.App.Srv().Store().Reaction().Save(userReaction) - require.NoError(t, err) - } - - teamId := th.BasicChannel.TeamId - - var expectedTopReactions [5]*model.TopReaction - expectedTopReactions[0] = &model.TopReaction{EmojiName: "happy", Count: int64(6)} - expectedTopReactions[1] = &model.TopReaction{EmojiName: "smile", Count: int64(5)} - expectedTopReactions[2] = &model.TopReaction{EmojiName: "+1", Count: int64(4)} - expectedTopReactions[3] = &model.TopReaction{EmojiName: "heart", Count: int64(3)} - expectedTopReactions[4] = &model.TopReaction{EmojiName: "blush", Count: int64(2)} - - timeRange, _ := model.GetStartOfDayForTimeRange(model.TimeRangeToday, time.Now().Location()) - - t.Run("get-top-reactions-for-user-since", func(t *testing.T) { - topReactions, err := th.App.GetTopReactionsForUserSince(userId, teamId, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 0, PerPage: 5}) - require.Nil(t, err) - reactions := topReactions.Items - - for i, reaction := range reactions { - assert.Equal(t, expectedTopReactions[i].EmojiName, reaction.EmojiName) - assert.Equal(t, expectedTopReactions[i].Count, reaction.Count) - } - - topReactions, err = th.App.GetTopReactionsForUserSince(userId, teamId, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 1, PerPage: 5}) - require.Nil(t, err) - reactions = topReactions.Items - assert.Equal(t, "100", reactions[0].EmojiName) - assert.Equal(t, int64(1), reactions[0].Count) - }) - - t.Run("get-top-reactions-for-user-since feature flag", func(t *testing.T) { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = false }) - defer th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true }) - _, err := th.App.GetTopReactionsForUserSince(userId, teamId, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 0, PerPage: 5}) - assert.NotNil(t, err) - }) -} diff --git a/server/channels/app/team.go b/server/channels/app/team.go index 2d99d22594..f95a5c1889 100644 --- a/server/channels/app/team.go +++ b/server/channels/app/team.go @@ -2116,17 +2116,3 @@ func (a *App) ClearTeamMembersCache(teamID string) error { } return nil } - -func (a *App) GetNewTeamMembersSince(c request.CTX, teamID string, opts *model.InsightsOpts) (*model.NewTeamMembersList, int64, *model.AppError) { - if !a.Config().FeatureFlags.InsightsEnabled { - return nil, 0, model.NewAppError("GetNewTeamMembersSince", "app.insights.feature_disabled", nil, "", http.StatusNotImplemented) - } - - showFullName := *a.Config().PrivacySettings.ShowFullName || a.SessionHasPermissionTo(*c.Session(), model.PermissionManageSystem) - ntms, count, err := a.Srv().Store().Team().GetNewTeamMembersSince(teamID, opts.StartUnixMilli, opts.Page*opts.PerPage, opts.PerPage, showFullName) - if err != nil { - return nil, 0, model.NewAppError("GetNewTeamMembersSince", model.NoTranslation, nil, "", http.StatusInternalServerError).Wrap(err) - } - - return ntms, count, nil -} diff --git a/server/channels/app/team_test.go b/server/channels/app/team_test.go index 97367bdaa6..33478752f0 100644 --- a/server/channels/app/team_test.go +++ b/server/channels/app/team_test.go @@ -1620,200 +1620,6 @@ func TestInviteGuestsToChannelsGracefully(t *testing.T) { }) } -func TestGetNewTeamMembersSince(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - - th.ConfigStore.SetReadOnlyFF(false) - defer th.ConfigStore.SetReadOnlyFF(true) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true }) - - team := th.CreateTeam() - - t.Run("counts team members", func(t *testing.T) { - var originalExpectedCount int64 - var newTeamMemberJoinTime int64 - var anotherUser *model.User - - t.Run("since time 0", func(t *testing.T) { - teamMembers, err := th.App.Srv().Store().Team().GetMembers(team.Id, 0, 1000, nil) - require.NoError(t, err) - originalExpectedCount = int64(len(teamMembers)) - _, actualCount, appErr := th.App.GetNewTeamMembersSince(th.Context, team.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000}) - require.Nil(t, appErr) - require.Equal(t, originalExpectedCount, actualCount) - }) - - t.Run("after a new team member was added", func(t *testing.T) { - anotherUser = th.CreateUser() - newTeamMember, appErr := th.App.JoinUserToTeam(th.Context, team, anotherUser, "") - newTeamMemberJoinTime = newTeamMember.CreateAt - require.Nil(t, appErr) - _, actualCount, appErr := th.App.GetNewTeamMembersSince(th.Context, team.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000}) - require.Nil(t, appErr) - require.Equal(t, originalExpectedCount+1, actualCount) - }) - - t.Run("after a team member was added to a different team, ensuring the wrong team's member count isn't incremented", func(t *testing.T) { - anotherUser2 := th.CreateUser() - anotherTeam := th.CreateTeam() - _, appErr := th.App.JoinUserToTeam(th.Context, anotherTeam, anotherUser2, "") - require.Nil(t, appErr) - _, actualCount, appErr := th.App.GetNewTeamMembersSince(th.Context, team.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000}) - require.Nil(t, appErr) - require.Equal(t, originalExpectedCount+1, actualCount) - }) - - t.Run("since a given time", func(t *testing.T) { - _, actualCount, appErr := th.App.GetNewTeamMembersSince(th.Context, team.Id, &model.InsightsOpts{StartUnixMilli: newTeamMemberJoinTime, Page: 0, PerPage: 1000}) - require.Nil(t, appErr) - require.Equal(t, int64(1), actualCount) - }) - - t.Run("after a team member was removed", func(t *testing.T) { - th.RemoveUserFromTeam(anotherUser, team) - _, actualCount, appErr := th.App.GetNewTeamMembersSince(th.Context, team.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000}) - require.Nil(t, appErr) - require.Equal(t, originalExpectedCount, actualCount) - }) - - t.Run("after a user was deactivated", func(t *testing.T) { - _, appErr := th.App.JoinUserToTeam(th.Context, team, anotherUser, "") - require.Nil(t, appErr) - _, beforeCount, appErr := th.App.GetNewTeamMembersSince(th.Context, team.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000}) - require.Nil(t, appErr) - _, appErr = th.App.UpdateActive(th.Context, anotherUser, false) - defer th.App.UpdateActive(th.Context, anotherUser, true) - require.Nil(t, appErr) - _, afterCount, appErr := th.App.GetNewTeamMembersSince(th.Context, team.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000}) - require.Nil(t, appErr) - require.Equal(t, beforeCount-1, afterCount) - }) - - t.Run("after a user was permanently deleted", func(t *testing.T) { - _, beforeCount, appErr := th.App.GetNewTeamMembersSince(th.Context, team.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000}) - require.Nil(t, appErr) - appErr = th.App.PermanentDeleteUser(th.Context, anotherUser) - require.Nil(t, appErr) - _, afterCount, appErr := th.App.GetNewTeamMembersSince(th.Context, team.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000}) - require.Nil(t, appErr) - require.Equal(t, beforeCount-1, afterCount) - }) - - t.Run("exclude bots", func(t *testing.T) { - user := th.CreateUser() - _, appErr := th.App.ConvertUserToBot(user) - require.Nil(t, appErr) - _, appErr = th.App.JoinUserToTeam(th.Context, team, user, "") - require.Nil(t, appErr) - _, actualCount, appErr := th.App.GetNewTeamMembersSince(th.Context, team.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000}) - require.Nil(t, appErr) - require.Equal(t, originalExpectedCount, actualCount) - }) - }) - - t.Run("returns the correct team members", func(t *testing.T) { - var originalExpectedMembers []*model.TeamMember - var newTeamMemberJoinTime int64 - var anotherUser *model.User - - uIDs := func(members []*model.TeamMember) []string { - ids := []string{} - for _, member := range members { - ids = append(ids, member.UserId) - } - return ids - } - - nUIDs := func(members []*model.NewTeamMember) []string { - ids := []string{} - for _, member := range members { - ids = append(ids, member.Id) - } - return ids - } - - t.Run("since time 0", func(t *testing.T) { - var err error - originalExpectedMembers, err = th.App.Srv().Store().Team().GetMembers(th.BasicTeam.Id, 0, 1000, nil) - require.NoError(t, err) - actualMembersList, _, appErr := th.App.GetNewTeamMembersSince(th.Context, th.BasicTeam.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000}) - require.Nil(t, appErr) - require.ElementsMatch(t, uIDs(originalExpectedMembers), nUIDs(actualMembersList.Items)) - }) - - t.Run("after a new team member was added", func(t *testing.T) { - anotherUser = th.CreateUser() - newTeamMember, appErr := th.App.JoinUserToTeam(th.Context, th.BasicTeam, anotherUser, "") - newTeamMemberJoinTime = newTeamMember.CreateAt - require.Nil(t, appErr) - actualMembersList, _, appErr := th.App.GetNewTeamMembersSince(th.Context, th.BasicTeam.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000}) - require.Nil(t, appErr) - require.ElementsMatch(t, append(uIDs(originalExpectedMembers), anotherUser.Id), nUIDs(actualMembersList.Items)) - }) - - t.Run("after a team member was added to a different team, ensuring the wrong team's member count isn't incremented", func(t *testing.T) { - anotherUser2 := th.CreateUser() - anotherTeam := th.CreateTeam() - _, appErr := th.App.JoinUserToTeam(th.Context, anotherTeam, anotherUser2, "") - require.Nil(t, appErr) - actualMembersList, _, appErr := th.App.GetNewTeamMembersSince(th.Context, th.BasicTeam.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000}) - require.Nil(t, appErr) - require.ElementsMatch(t, append(uIDs(originalExpectedMembers), anotherUser.Id), nUIDs(actualMembersList.Items)) - }) - - t.Run("since a given time", func(t *testing.T) { - actualMembersList, _, appErr := th.App.GetNewTeamMembersSince(th.Context, th.BasicTeam.Id, &model.InsightsOpts{StartUnixMilli: newTeamMemberJoinTime, Page: 0, PerPage: 1000}) - require.Nil(t, appErr) - require.Len(t, actualMembersList.Items, 1) - require.Equal(t, anotherUser.Id, actualMembersList.Items[0].Id) - }) - - t.Run("after a team member was removed", func(t *testing.T) { - th.RemoveUserFromTeam(anotherUser, th.BasicTeam) - actualMembersList, _, appErr := th.App.GetNewTeamMembersSince(th.Context, th.BasicTeam.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000}) - require.Nil(t, appErr) - require.ElementsMatch(t, uIDs(originalExpectedMembers), nUIDs(actualMembersList.Items)) - }) - - t.Run("after a user was deactivated", func(t *testing.T) { - _, appErr := th.App.JoinUserToTeam(th.Context, th.BasicTeam, anotherUser, "") - require.Nil(t, appErr) - beforeMembersList, _, appErr := th.App.GetNewTeamMembersSince(th.Context, th.BasicTeam.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000}) - require.Nil(t, appErr) - require.Contains(t, nUIDs(beforeMembersList.Items), anotherUser.Id) - _, appErr = th.App.UpdateActive(th.Context, anotherUser, false) - defer th.App.UpdateActive(th.Context, anotherUser, true) - require.Nil(t, appErr) - afterMembersList, _, appErr := th.App.GetNewTeamMembersSince(th.Context, th.BasicTeam.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000}) - require.Nil(t, appErr) - require.NotContains(t, nUIDs(afterMembersList.Items), anotherUser.Id) - }) - - t.Run("after a user was permanently deleted", func(t *testing.T) { - beforeMembersList, _, appErr := th.App.GetNewTeamMembersSince(th.Context, th.BasicTeam.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000}) - require.Nil(t, appErr) - require.Contains(t, nUIDs(beforeMembersList.Items), anotherUser.Id) - appErr = th.App.PermanentDeleteUser(th.Context, anotherUser) - require.Nil(t, appErr) - afterMembersList, _, appErr := th.App.GetNewTeamMembersSince(th.Context, th.BasicTeam.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000}) - require.Nil(t, appErr) - require.NotContains(t, nUIDs(afterMembersList.Items), anotherUser.Id) - }) - - t.Run("exclude bots", func(t *testing.T) { - user := th.CreateUser() - _, appErr := th.App.ConvertUserToBot(user) - require.Nil(t, appErr) - _, appErr = th.App.JoinUserToTeam(th.Context, th.BasicTeam, user, "") - require.Nil(t, appErr) - actualMembersList, _, appErr := th.App.GetNewTeamMembersSince(th.Context, th.BasicTeam.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000}) - require.Nil(t, appErr) - require.ElementsMatch(t, uIDs(originalExpectedMembers), nUIDs(actualMembersList.Items)) - }) - }) -} - func TestTeamSendEvents(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown() diff --git a/server/channels/app/user.go b/server/channels/app/user.go index e1f67de1cc..e062b31a37 100644 --- a/server/channels/app/user.go +++ b/server/channels/app/user.go @@ -284,14 +284,6 @@ func (a *App) createUserOrGuest(c request.CTX, user *model.User, guest bool) (*m tutorialStepPref := model.Preference{UserId: ruser.Id, Category: model.PreferenceCategoryTutorialSteps, Name: ruser.Id, Value: "0"} preferences := model.Preferences{recommendedNextStepsPref, tutorialStepPref} - - if a.Config().FeatureFlags.InsightsEnabled { - // We don't want to show the insights intro modal for new users - preferences = append(preferences, model.Preference{UserId: ruser.Id, Category: model.PreferenceCategoryInsights, Name: model.PreferenceNameInsights, Value: "{\"insights_modal_viewed\":true}"}) - } else { - preferences = append(preferences, model.Preference{UserId: ruser.Id, Category: model.PreferenceCategoryInsights, Name: model.PreferenceNameInsights, Value: "{\"insights_modal_viewed\":false}"}) - } - if err := a.Srv().Store().Preference().Save(preferences); err != nil { c.Logger().Warn("Encountered error saving user preferences", mlog.Err(err)) } diff --git a/server/channels/app/user_test.go b/server/channels/app/user_test.go index 7e310a6df3..0fd0d439be 100644 --- a/server/channels/app/user_test.go +++ b/server/channels/app/user_test.go @@ -1810,16 +1810,10 @@ func TestCreateUserWithInitialPreferences(t *testing.T) { t.Run("successfully create a user with initial tutorial and recommended steps preferences", func(t *testing.T) { th.ConfigStore.SetReadOnlyFF(false) defer th.ConfigStore.SetReadOnlyFF(true) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true }) testUser := th.CreateUser() defer th.App.PermanentDeleteUser(th.Context, testUser) - insightsPref, appErr := th.App.GetPreferenceByCategoryAndNameForUser(testUser.Id, model.PreferenceCategoryInsights, model.PreferenceNameInsights) - require.Nil(t, appErr) - assert.Equal(t, "insights_tutorial_state", insightsPref.Name) - assert.Equal(t, "{\"insights_modal_viewed\":true}", insightsPref.Value) - tutorialStepPref, appErr := th.App.GetPreferenceByCategoryAndNameForUser(testUser.Id, model.PreferenceCategoryTutorialSteps, testUser.Id) require.Nil(t, appErr) assert.Equal(t, testUser.Id, tutorialStepPref.Name) @@ -1831,38 +1825,12 @@ func TestCreateUserWithInitialPreferences(t *testing.T) { assert.Equal(t, "false", recommendedNextStepsPref[0].Value) }) - t.Run("successfully create a user with insights feature flag disabled", func(t *testing.T) { + t.Run("successfully create a guest user with initial tutorial and recommended steps preferences", func(t *testing.T) { th.Server.platform.SetConfigReadOnlyFF(false) defer th.Server.platform.SetConfigReadOnlyFF(true) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = false }) - defer th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true }) - testUser := th.CreateUser() - defer th.App.PermanentDeleteUser(th.Context, testUser) - - insightsPref, appErr := th.App.GetPreferenceByCategoryAndNameForUser(testUser.Id, model.PreferenceCategoryInsights, model.PreferenceNameInsights) - require.Nil(t, appErr) - assert.Equal(t, "insights_tutorial_state", insightsPref.Name) - assert.Equal(t, "{\"insights_modal_viewed\":false}", insightsPref.Value) - - recommendedNextStepsPref, appErr := th.App.GetPreferenceByCategoryForUser(testUser.Id, model.PreferenceRecommendedNextSteps) - require.Nil(t, appErr) - assert.Equal(t, model.PreferenceRecommendedNextSteps, recommendedNextStepsPref[0].Category) - assert.Equal(t, "hide", recommendedNextStepsPref[0].Name) - assert.Equal(t, "false", recommendedNextStepsPref[0].Value) - }) - - t.Run("successfully create a guest user with initial tutorial, insights and recommended steps preferences", func(t *testing.T) { - th.Server.platform.SetConfigReadOnlyFF(false) - defer th.Server.platform.SetConfigReadOnlyFF(true) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true }) testUser := th.CreateGuest() defer th.App.PermanentDeleteUser(th.Context, testUser) - insightsPref, appErr := th.App.GetPreferenceByCategoryAndNameForUser(testUser.Id, model.PreferenceCategoryInsights, model.PreferenceNameInsights) - require.Nil(t, appErr) - assert.Equal(t, "insights_tutorial_state", insightsPref.Name) - assert.Equal(t, "{\"insights_modal_viewed\":true}", insightsPref.Value) - tutorialStepPref, appErr := th.App.GetPreferenceByCategoryAndNameForUser(testUser.Id, model.PreferenceCategoryTutorialSteps, testUser.Id) require.Nil(t, appErr) assert.Equal(t, testUser.Id, tutorialStepPref.Name) diff --git a/server/channels/store/layer_generators/opentracing_layer.go.tmpl b/server/channels/store/layer_generators/opentracing_layer.go.tmpl index e744f116b8..02d7408df9 100644 --- a/server/channels/store/layer_generators/opentracing_layer.go.tmpl +++ b/server/channels/store/layer_generators/opentracing_layer.go.tmpl @@ -8,7 +8,6 @@ package opentracinglayer import ( "context" - "time" "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/v8/platform/services/tracing" diff --git a/server/channels/store/layer_generators/retry_layer.go.tmpl b/server/channels/store/layer_generators/retry_layer.go.tmpl index 4a0872dcb5..dd8fdac7b2 100644 --- a/server/channels/store/layer_generators/retry_layer.go.tmpl +++ b/server/channels/store/layer_generators/retry_layer.go.tmpl @@ -9,7 +9,6 @@ package retrylayer import ( "context" timepkg "time" - "time" "github.com/lib/pq" "github.com/mattermost/mattermost/server/public/model" diff --git a/server/channels/store/opentracinglayer/opentracinglayer.go b/server/channels/store/opentracinglayer/opentracinglayer.go index d71e322124..98deb2f11a 100644 --- a/server/channels/store/opentracinglayer/opentracinglayer.go +++ b/server/channels/store/opentracinglayer/opentracinglayer.go @@ -8,7 +8,6 @@ package opentracinglayer import ( "context" - "time" "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/v8/channels/store" @@ -1876,78 +1875,6 @@ func (s *OpenTracingLayerChannelStore) GetTeamMembersForChannel(channelID string return result, err } -func (s *OpenTracingLayerChannelStore) GetTopChannelsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopChannelList, error) { - origCtx := s.Root.Store.Context() - span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.GetTopChannelsForTeamSince") - s.Root.Store.SetContext(newCtx) - defer func() { - s.Root.Store.SetContext(origCtx) - }() - - defer span.Finish() - result, err := s.ChannelStore.GetTopChannelsForTeamSince(teamID, userID, since, offset, limit) - if err != nil { - span.LogFields(spanlog.Error(err)) - ext.Error.Set(span, true) - } - - return result, err -} - -func (s *OpenTracingLayerChannelStore) GetTopChannelsForUserSince(userID string, teamID string, since int64, offset int, limit int) (*model.TopChannelList, error) { - origCtx := s.Root.Store.Context() - span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.GetTopChannelsForUserSince") - s.Root.Store.SetContext(newCtx) - defer func() { - s.Root.Store.SetContext(origCtx) - }() - - defer span.Finish() - result, err := s.ChannelStore.GetTopChannelsForUserSince(userID, teamID, since, offset, limit) - if err != nil { - span.LogFields(spanlog.Error(err)) - ext.Error.Set(span, true) - } - - return result, err -} - -func (s *OpenTracingLayerChannelStore) GetTopInactiveChannelsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopInactiveChannelList, error) { - origCtx := s.Root.Store.Context() - span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.GetTopInactiveChannelsForTeamSince") - s.Root.Store.SetContext(newCtx) - defer func() { - s.Root.Store.SetContext(origCtx) - }() - - defer span.Finish() - result, err := s.ChannelStore.GetTopInactiveChannelsForTeamSince(teamID, userID, since, offset, limit) - if err != nil { - span.LogFields(spanlog.Error(err)) - ext.Error.Set(span, true) - } - - return result, err -} - -func (s *OpenTracingLayerChannelStore) GetTopInactiveChannelsForUserSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopInactiveChannelList, error) { - origCtx := s.Root.Store.Context() - span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.GetTopInactiveChannelsForUserSince") - s.Root.Store.SetContext(newCtx) - defer func() { - s.Root.Store.SetContext(origCtx) - }() - - defer span.Finish() - result, err := s.ChannelStore.GetTopInactiveChannelsForUserSince(teamID, userID, since, offset, limit) - if err != nil { - span.LogFields(spanlog.Error(err)) - ext.Error.Set(span, true) - } - - return result, err -} - func (s *OpenTracingLayerChannelStore) GroupSyncedChannelCount() (int64, error) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.GroupSyncedChannelCount") @@ -2178,24 +2105,6 @@ func (s *OpenTracingLayerChannelStore) PermanentDeleteMembersByUser(userID strin return err } -func (s *OpenTracingLayerChannelStore) PostCountsByDuration(channelIDs []string, sinceUnixMillis int64, userID *string, duration model.PostCountGrouping, groupingLocation *time.Location) ([]*model.DurationPostCount, error) { - origCtx := s.Root.Store.Context() - span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.PostCountsByDuration") - s.Root.Store.SetContext(newCtx) - defer func() { - s.Root.Store.SetContext(origCtx) - }() - - defer span.Finish() - result, err := s.ChannelStore.PostCountsByDuration(channelIDs, sinceUnixMillis, userID, duration, groupingLocation) - if err != nil { - span.LogFields(spanlog.Error(err)) - ext.Error.Set(span, true) - } - - return result, err -} - func (s *OpenTracingLayerChannelStore) RemoveAllDeactivatedMembers(channelID string) error { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.RemoveAllDeactivatedMembers") @@ -6458,24 +6367,6 @@ func (s *OpenTracingLayerPostStore) GetSingle(id string, inclDeleted bool) (*mod return result, err } -func (s *OpenTracingLayerPostStore) GetTopDMsForUserSince(userID string, since int64, offset int, limit int) (*model.TopDMList, error) { - origCtx := s.Root.Store.Context() - span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.GetTopDMsForUserSince") - s.Root.Store.SetContext(newCtx) - defer func() { - s.Root.Store.SetContext(origCtx) - }() - - defer span.Finish() - result, err := s.PostStore.GetTopDMsForUserSince(userID, since, offset, limit) - if err != nil { - span.LogFields(spanlog.Error(err)) - ext.Error.Set(span, true) - } - - return result, err -} - func (s *OpenTracingLayerPostStore) HasAutoResponsePostByUserSince(options model.GetPostsSinceOptions, userId string) (bool, error) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.HasAutoResponsePostByUserSince") @@ -7353,42 +7244,6 @@ func (s *OpenTracingLayerReactionStore) GetForPostSince(postId string, since int return result, err } -func (s *OpenTracingLayerReactionStore) GetTopForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopReactionList, error) { - origCtx := s.Root.Store.Context() - span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ReactionStore.GetTopForTeamSince") - s.Root.Store.SetContext(newCtx) - defer func() { - s.Root.Store.SetContext(origCtx) - }() - - defer span.Finish() - result, err := s.ReactionStore.GetTopForTeamSince(teamID, userID, since, offset, limit) - if err != nil { - span.LogFields(spanlog.Error(err)) - ext.Error.Set(span, true) - } - - return result, err -} - -func (s *OpenTracingLayerReactionStore) GetTopForUserSince(userID string, teamID string, since int64, offset int, limit int) (*model.TopReactionList, error) { - origCtx := s.Root.Store.Context() - span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ReactionStore.GetTopForUserSince") - s.Root.Store.SetContext(newCtx) - defer func() { - s.Root.Store.SetContext(origCtx) - }() - - defer span.Finish() - result, err := s.ReactionStore.GetTopForUserSince(userID, teamID, since, offset, limit) - if err != nil { - span.LogFields(spanlog.Error(err)) - ext.Error.Set(span, true) - } - - return result, err -} - func (s *OpenTracingLayerReactionStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ReactionStore.PermanentDeleteBatch") @@ -9634,24 +9489,6 @@ func (s *OpenTracingLayerTeamStore) GetMembersByIds(teamID string, userIds []str return result, err } -func (s *OpenTracingLayerTeamStore) GetNewTeamMembersSince(teamID string, since int64, offset int, limit int, showFullName bool) (*model.NewTeamMembersList, int64, error) { - origCtx := s.Root.Store.Context() - span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "TeamStore.GetNewTeamMembersSince") - s.Root.Store.SetContext(newCtx) - defer func() { - s.Root.Store.SetContext(origCtx) - }() - - defer span.Finish() - result, resultVar1, err := s.TeamStore.GetNewTeamMembersSince(teamID, since, offset, limit, showFullName) - if err != nil { - span.LogFields(spanlog.Error(err)) - ext.Error.Set(span, true) - } - - return result, resultVar1, err -} - func (s *OpenTracingLayerTeamStore) GetTeamMembersForExport(userID string) ([]*model.TeamMemberForExport, error) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "TeamStore.GetTeamMembersForExport") @@ -10421,42 +10258,6 @@ func (s *OpenTracingLayerThreadStore) GetThreadsForUser(userId string, teamID st return result, err } -func (s *OpenTracingLayerThreadStore) GetTopThreadsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopThreadList, error) { - origCtx := s.Root.Store.Context() - span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.GetTopThreadsForTeamSince") - s.Root.Store.SetContext(newCtx) - defer func() { - s.Root.Store.SetContext(origCtx) - }() - - defer span.Finish() - result, err := s.ThreadStore.GetTopThreadsForTeamSince(teamID, userID, since, offset, limit) - if err != nil { - span.LogFields(spanlog.Error(err)) - ext.Error.Set(span, true) - } - - return result, err -} - -func (s *OpenTracingLayerThreadStore) GetTopThreadsForUserSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopThreadList, error) { - origCtx := s.Root.Store.Context() - span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.GetTopThreadsForUserSince") - s.Root.Store.SetContext(newCtx) - defer func() { - s.Root.Store.SetContext(origCtx) - }() - - defer span.Finish() - result, err := s.ThreadStore.GetTopThreadsForUserSince(teamID, userID, since, offset, limit) - if err != nil { - span.LogFields(spanlog.Error(err)) - ext.Error.Set(span, true) - } - - return result, err -} - func (s *OpenTracingLayerThreadStore) GetTotalThreads(userId string, teamID string, opts model.GetUserThreadsOpts) (int64, error) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.GetTotalThreads") diff --git a/server/channels/store/retrylayer/retrylayer.go b/server/channels/store/retrylayer/retrylayer.go index 4dc9633ded..f84a28457a 100644 --- a/server/channels/store/retrylayer/retrylayer.go +++ b/server/channels/store/retrylayer/retrylayer.go @@ -8,7 +8,6 @@ package retrylayer import ( "context" - "time" timepkg "time" "github.com/go-sql-driver/mysql" @@ -2105,90 +2104,6 @@ func (s *RetryLayerChannelStore) GetTeamMembersForChannel(channelID string) ([]s } -func (s *RetryLayerChannelStore) GetTopChannelsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopChannelList, error) { - - tries := 0 - for { - result, err := s.ChannelStore.GetTopChannelsForTeamSince(teamID, userID, since, offset, limit) - if err == nil { - return result, nil - } - if !isRepeatableError(err) { - return result, err - } - tries++ - if tries >= 3 { - err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") - return result, err - } - timepkg.Sleep(100 * timepkg.Millisecond) - } - -} - -func (s *RetryLayerChannelStore) GetTopChannelsForUserSince(userID string, teamID string, since int64, offset int, limit int) (*model.TopChannelList, error) { - - tries := 0 - for { - result, err := s.ChannelStore.GetTopChannelsForUserSince(userID, teamID, since, offset, limit) - if err == nil { - return result, nil - } - if !isRepeatableError(err) { - return result, err - } - tries++ - if tries >= 3 { - err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") - return result, err - } - timepkg.Sleep(100 * timepkg.Millisecond) - } - -} - -func (s *RetryLayerChannelStore) GetTopInactiveChannelsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopInactiveChannelList, error) { - - tries := 0 - for { - result, err := s.ChannelStore.GetTopInactiveChannelsForTeamSince(teamID, userID, since, offset, limit) - if err == nil { - return result, nil - } - if !isRepeatableError(err) { - return result, err - } - tries++ - if tries >= 3 { - err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") - return result, err - } - timepkg.Sleep(100 * timepkg.Millisecond) - } - -} - -func (s *RetryLayerChannelStore) GetTopInactiveChannelsForUserSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopInactiveChannelList, error) { - - tries := 0 - for { - result, err := s.ChannelStore.GetTopInactiveChannelsForUserSince(teamID, userID, since, offset, limit) - if err == nil { - return result, nil - } - if !isRepeatableError(err) { - return result, err - } - tries++ - if tries >= 3 { - err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") - return result, err - } - timepkg.Sleep(100 * timepkg.Millisecond) - } - -} - func (s *RetryLayerChannelStore) GroupSyncedChannelCount() (int64, error) { tries := 0 @@ -2384,27 +2299,6 @@ func (s *RetryLayerChannelStore) PermanentDeleteMembersByUser(userID string) err } -func (s *RetryLayerChannelStore) PostCountsByDuration(channelIDs []string, sinceUnixMillis int64, userID *string, duration model.PostCountGrouping, groupingLocation *time.Location) ([]*model.DurationPostCount, error) { - - tries := 0 - for { - result, err := s.ChannelStore.PostCountsByDuration(channelIDs, sinceUnixMillis, userID, duration, groupingLocation) - if err == nil { - return result, nil - } - if !isRepeatableError(err) { - return result, err - } - tries++ - if tries >= 3 { - err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") - return result, err - } - timepkg.Sleep(100 * timepkg.Millisecond) - } - -} - func (s *RetryLayerChannelStore) RemoveAllDeactivatedMembers(channelID string) error { tries := 0 @@ -7313,27 +7207,6 @@ func (s *RetryLayerPostStore) GetSingle(id string, inclDeleted bool) (*model.Pos } -func (s *RetryLayerPostStore) GetTopDMsForUserSince(userID string, since int64, offset int, limit int) (*model.TopDMList, error) { - - tries := 0 - for { - result, err := s.PostStore.GetTopDMsForUserSince(userID, since, offset, limit) - if err == nil { - return result, nil - } - if !isRepeatableError(err) { - return result, err - } - tries++ - if tries >= 3 { - err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") - return result, err - } - timepkg.Sleep(100 * timepkg.Millisecond) - } - -} - func (s *RetryLayerPostStore) HasAutoResponsePostByUserSince(options model.GetPostsSinceOptions, userId string) (bool, error) { tries := 0 @@ -8348,48 +8221,6 @@ func (s *RetryLayerReactionStore) GetForPostSince(postId string, since int64, ex } -func (s *RetryLayerReactionStore) GetTopForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopReactionList, error) { - - tries := 0 - for { - result, err := s.ReactionStore.GetTopForTeamSince(teamID, userID, since, offset, limit) - if err == nil { - return result, nil - } - if !isRepeatableError(err) { - return result, err - } - tries++ - if tries >= 3 { - err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") - return result, err - } - timepkg.Sleep(100 * timepkg.Millisecond) - } - -} - -func (s *RetryLayerReactionStore) GetTopForUserSince(userID string, teamID string, since int64, offset int, limit int) (*model.TopReactionList, error) { - - tries := 0 - for { - result, err := s.ReactionStore.GetTopForUserSince(userID, teamID, since, offset, limit) - if err == nil { - return result, nil - } - if !isRepeatableError(err) { - return result, err - } - tries++ - if tries >= 3 { - err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") - return result, err - } - timepkg.Sleep(100 * timepkg.Millisecond) - } - -} - func (s *RetryLayerReactionStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) { tries := 0 @@ -11000,27 +10831,6 @@ func (s *RetryLayerTeamStore) GetMembersByIds(teamID string, userIds []string, r } -func (s *RetryLayerTeamStore) GetNewTeamMembersSince(teamID string, since int64, offset int, limit int, showFullName bool) (*model.NewTeamMembersList, int64, error) { - - tries := 0 - for { - result, resultVar1, err := s.TeamStore.GetNewTeamMembersSince(teamID, since, offset, limit, showFullName) - if err == nil { - return result, resultVar1, nil - } - if !isRepeatableError(err) { - return result, resultVar1, err - } - tries++ - if tries >= 3 { - err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") - return result, resultVar1, err - } - timepkg.Sleep(100 * timepkg.Millisecond) - } - -} - func (s *RetryLayerTeamStore) GetTeamMembersForExport(userID string) ([]*model.TeamMemberForExport, error) { tries := 0 @@ -11909,48 +11719,6 @@ func (s *RetryLayerThreadStore) GetThreadsForUser(userId string, teamID string, } -func (s *RetryLayerThreadStore) GetTopThreadsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopThreadList, error) { - - tries := 0 - for { - result, err := s.ThreadStore.GetTopThreadsForTeamSince(teamID, userID, since, offset, limit) - if err == nil { - return result, nil - } - if !isRepeatableError(err) { - return result, err - } - tries++ - if tries >= 3 { - err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") - return result, err - } - timepkg.Sleep(100 * timepkg.Millisecond) - } - -} - -func (s *RetryLayerThreadStore) GetTopThreadsForUserSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopThreadList, error) { - - tries := 0 - for { - result, err := s.ThreadStore.GetTopThreadsForUserSince(teamID, userID, since, offset, limit) - if err == nil { - return result, nil - } - if !isRepeatableError(err) { - return result, err - } - tries++ - if tries >= 3 { - err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") - return result, err - } - timepkg.Sleep(100 * timepkg.Millisecond) - } - -} - func (s *RetryLayerThreadStore) GetTotalThreads(userId string, teamID string, opts model.GetUserThreadsOpts) (int64, error) { tries := 0 diff --git a/server/channels/store/sqlstore/channel_store.go b/server/channels/store/sqlstore/channel_store.go index a6db92f595..00c9bf31ad 100644 --- a/server/channels/store/sqlstore/channel_store.go +++ b/server/channels/store/sqlstore/channel_store.go @@ -4342,414 +4342,3 @@ func (s SqlChannelStore) GetTeamForChannel(channelID string) (*model.Team, error } return &team, nil } - -// GetTopChannelsForTeamSince returns the filtered post counts of the following Channels sets: -// a) those that are private channels in the given user's membership graph on the given team, and -// b) those that are public channels in the given team. -func (s SqlChannelStore) GetTopChannelsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopChannelList, error) { - channels := make([]*model.TopChannel, 0) - var args []any - postgresPropQuery := `AND (Posts.Props ->> 'from_bot' IS NULL OR Posts.Props ->> 'from_bot' = 'false') AND (Posts.Props ->> 'from_webhook' IS NULL OR Posts.Props ->> 'from_webhook' = 'false') AND (Posts.Props ->> 'from_oauth_app' IS NULL OR Posts.Props ->> 'from_oauth_app' = 'false') AND (Posts.Props ->> 'from_plugin' IS NULL OR Posts.Props ->> 'from_plugin' = 'false')` - mySqlPropsQuery := `AND (JSON_EXTRACT(Posts.Props, '$.from_bot') IS NULL OR JSON_EXTRACT(Posts.Props, '$.from_bot') = 'false') AND (JSON_EXTRACT(Posts.Props, '$.from_webhook') IS NULL OR JSON_EXTRACT(Posts.Props, '$.from_webhook') = 'false') AND (JSON_EXTRACT(Posts.Props, '$.from_plugin') IS NULL OR JSON_EXTRACT(Posts.Props, '$.from_plugin') = 'false') AND (JSON_EXTRACT(Posts.Props, '$.from_oauth_app') IS NULL OR JSON_EXTRACT(Posts.Props, '$.from_oauth_app') = 'false')` - - query := ` - SELECT - ID, - Type, - DisplayName, - Name, - TeamID, - MessageCount - FROM - ((SELECT - Posts.ChannelId AS ID, - 'O' AS Type, - PublicChannels.DisplayName AS DisplayName, - PublicChannels.Name AS Name, - PublicChannels.TeamId AS TeamID, - count(Posts.Id) AS MessageCount, - PublicChannels.DeleteAt AS DeleteAt - FROM - Posts - LEFT JOIN PublicChannels on Posts.ChannelId = PublicChannels.Id - WHERE - Posts.DeleteAt = 0 - AND Posts.CreateAt > ? - AND Posts.Type = ''` - args = []any{since} - - if s.DriverName() == model.DatabaseDriverMysql { - query += mySqlPropsQuery - } else if s.DriverName() == model.DatabaseDriverPostgres { - query += postgresPropQuery - } - - query += ` - AND PublicChannels.TeamId = ? - GROUP BY - Posts.ChannelId, - PublicChannels.DisplayName, - PublicChannels.Name, - PublicChannels.TeamId, - PublicChannels.DeleteAt) - UNION ALL - (SELECT - Posts.ChannelId AS ID, - Channels.Type AS Type, - Channels.DisplayName AS DisplayName, - Channels.Name AS Name, - Channels.TeamId AS TeamID, - count(Posts.Id) AS MessageCount, - Channels.DeleteAt AS DeleteAt - FROM - Posts - LEFT JOIN Channels on Posts.ChannelId = Channels.Id - LEFT JOIN ChannelMembers on Posts.ChannelId = ChannelMembers.ChannelId - WHERE - Posts.DeleteAt = 0 - AND Posts.CreateAt > ? - AND Posts.Type = ''` - args = append(args, teamID, since) - - if s.DriverName() == model.DatabaseDriverMysql { - query += mySqlPropsQuery - } else if s.DriverName() == model.DatabaseDriverPostgres { - query += postgresPropQuery - } - - query += ` - AND Channels.TeamId = ? - AND Channels.Type = 'P' - AND ChannelMembers.UserId = ? - GROUP BY - Posts.ChannelId, - Channels.Type, - Channels.DisplayName, - Channels.Name, - Channels.TeamId, - Channels.DeleteAt)) AS A - WHERE - DeleteAt = 0 - ORDER BY - MessageCount DESC, - Name ASC - LIMIT ? - OFFSET ?` - args = append(args, teamID, userID, limit+1, offset) - - if err := s.GetReplicaX().Select(&channels, query, args...); err != nil { - return nil, errors.Wrap(err, "failed to get top Channels") - } - - return model.GetTopChannelListWithPagination(channels, limit), nil -} - -// GetTopChannelsForUserSince returns the filtered post counts of channels with with posts created by the user -// after the given timestamp within the given team (or across the workspace if no team is given). Excludes DM and GM channels. -func (s SqlChannelStore) GetTopChannelsForUserSince(userID string, teamID string, since int64, offset int, limit int) (*model.TopChannelList, error) { - channels := make([]*model.TopChannel, 0) - var args []any - var query string - - var propsQuery string - if s.DriverName() == model.DatabaseDriverMysql { - propsQuery = `AND (JSON_EXTRACT(Posts.Props, '$.from_bot') IS NULL OR JSON_EXTRACT(Posts.Props, '$.from_bot') = 'false') AND (JSON_EXTRACT(Posts.Props, '$.from_webhook') IS NULL OR JSON_EXTRACT(Posts.Props, '$.from_webhook') = 'false') AND (JSON_EXTRACT(Posts.Props, '$.from_plugin') IS NULL OR JSON_EXTRACT(Posts.Props, '$.from_plugin') = 'false') AND (JSON_EXTRACT(Posts.Props, '$.from_oauth_app') IS NULL OR JSON_EXTRACT(Posts.Props, '$.from_oauth_app') = 'false')` - } else if s.DriverName() == model.DatabaseDriverPostgres { - propsQuery = `AND (Posts.Props ->> 'from_bot' IS NULL OR Posts.Props ->> 'from_bot' = 'false') AND (Posts.Props ->> 'from_webhook' IS NULL OR Posts.Props ->> 'from_webhook' = 'false') AND (Posts.Props ->> 'from_oauth_app' IS NULL OR Posts.Props ->> 'from_oauth_app' = 'false') AND (Posts.Props ->> 'from_plugin' IS NULL OR Posts.Props ->> 'from_plugin' = 'false')` - } - - query = ` - SELECT - Posts.ChannelId AS ID, - Channels.Type AS Type, - Channels.DisplayName AS DisplayName, - Channels.Name AS Name, - Channels.TeamId AS TeamID, - count(Posts.Id) AS MessageCount - FROM - Posts - LEFT JOIN Channels on Posts.ChannelId = Channels.Id - LEFT JOIN ChannelMembers on Posts.ChannelId = ChannelMembers.ChannelId - WHERE - Posts.DeleteAt = 0 - AND Posts.CreateAt > ? - AND Posts.Type = '' - AND Posts.UserID = ? - AND Channels.DeleteAt = 0 - AND (Channels.Type = 'O' OR Channels.Type = 'P') - AND ChannelMembers.UserId = ? ` - - query += propsQuery - - args = []any{since, userID, userID} - - if teamID != "" { - query += ` - AND Channels.TeamID = ?` - args = append(args, teamID) - } - - query += ` - Group By - Posts.ChannelId, - Channels.Type, - Channels.DisplayName, - Channels.Name, - Channels.TeamId - ORDER BY - MessageCount DESC, - Name ASC - LIMIT ? - OFFSET ?` - args = append(args, limit+1, offset) - - if err := s.GetReplicaX().Select(&channels, query, args...); err != nil { - return nil, errors.Wrap(err, "failed to get top Channels") - } - - return model.GetTopChannelListWithPagination(channels, limit), nil -} - -// GetTopInactiveChannelsForTeamSince returns the filtered post counts of the following Channels sets: -// a) those that are private channels in the given user's membership graph on the given team, and -// b) those that are public channels in the given team. -func (s SqlChannelStore) GetTopInactiveChannelsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopInactiveChannelList, error) { - channels := make([]*model.TopInactiveChannel, 0) - var args []any - - query := ` - SELECT - ID, - Type, - DisplayName, - Name, - MessageCount, - LastActivityAt - FROM - ((SELECT - PublicChannels.Id AS ID, - 'O' AS Type, - PublicChannels.DisplayName AS DisplayName, - PublicChannels.Name AS Name, - COALESCE(count(Posts.Id), 0) AS MessageCount, - COALESCE(max(Posts.CreateAt), 0) AS LastActivityAt - FROM - PublicChannels - LEFT JOIN Posts on Posts.ChannelId = PublicChannels.Id AND Posts.Type = '' AND Posts.CreateAt > ? AND Posts.DeleteAt = 0 - LEFT JOIN Channels on Channels.Id = PublicChannels.Id - WHERE - PublicChannels.TeamId = ? - AND PublicChannels.DeleteAt = 0 - AND Channels.CreateAt < ? - GROUP BY - PublicChannels.Id, - PublicChannels.DisplayName, - PublicChannels.Name, - PublicChannels.TeamId) - UNION ALL - (SELECT - Channels.Id AS ID, - Channels.Type AS Type, - Channels.DisplayName AS DisplayName, - Channels.Name AS Name, - COALESCE(count(Posts.Id), 0) AS MessageCount, - COALESCE(max(Posts.CreateAt), 0) AS LastActivityAt - FROM - Channels - LEFT JOIN Posts on Posts.ChannelId = Channels.Id AND Posts.Type = '' AND Posts.CreateAt > ? AND Posts.DeleteAt = 0 - LEFT JOIN ChannelMembers on Channels.Id = ChannelMembers.ChannelId - WHERE - Channels.TeamId = ? - AND Channels.CreateAt < ? - AND Channels.Type = 'P' - AND Channels.DeleteAt = 0 - AND ChannelMembers.UserId = ? - GROUP BY - Channels.Id, - Channels.Type, - Channels.DisplayName, - Channels.Name)) AS A - ORDER BY - MessageCount ASC, - Name ASC - LIMIT ? - OFFSET ?` - args = append(args, since, teamID, since, since, teamID, since, userID, limit+1, offset) - if err := s.GetReplicaX().Select(&channels, query, args...); err != nil { - return nil, errors.Wrap(err, "failed to get top Channels") - } - - channels, err := postProcessTopInactiveChannels(s, channels) - - if err != nil { - return nil, err - } - - return model.GetTopInactiveChannelListWithPagination(channels, limit), nil -} - -// GetTopInactiveChannelsForUserSince returns the filtered post counts of channels with with posts created by the user -// after the given timestamp within the given team (or across the workspace if no team is given). Excludes DM and GM channels. -func (s SqlChannelStore) GetTopInactiveChannelsForUserSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopInactiveChannelList, error) { - channels := make([]*model.TopInactiveChannel, 0) - var args []any - var query string - - query = ` - SELECT - Channels.Id AS ID, - Channels.Type AS Type, - Channels.DisplayName AS DisplayName, - Channels.Name AS Name, - COALESCE(count(Posts.Id), 0) AS MessageCount, - COALESCE(max(Posts.CreateAt), 0) AS LastActivityAt - FROM - Channels - LEFT JOIN Posts on Posts.ChannelId = Channels.Id AND Posts.Type = '' AND Posts.CreateAt > ? AND Posts.DeleteAt = 0 - LEFT JOIN ChannelMembers on Channels.Id = ChannelMembers.ChannelId - WHERE - Channels.DeleteAt = 0 - AND Channels.CreateAt < ? - AND (Channels.Type = 'O' OR Channels.Type = 'P') - AND ChannelMembers.UserId = ? ` - - args = []any{since, since, userID} - - if teamID != "" { - query += ` - AND Channels.TeamID = ?` - args = append(args, teamID) - } - - query += ` - Group By - Channels.Id, - Channels.Type, - Channels.DisplayName, - Channels.Name - ORDER BY - MessageCount ASC, - Name ASC - LIMIT ? - OFFSET ?` - args = append(args, limit+1, offset) - - if err := s.GetReplicaX().Select(&channels, query, args...); err != nil { - return nil, errors.Wrap(err, "failed to get top Inactive Channels") - } - - channels, err := postProcessTopInactiveChannels(s, channels) - if err != nil { - return nil, err - } - - return model.GetTopInactiveChannelListWithPagination(channels, limit), nil -} - -func postProcessTopInactiveChannels(s SqlChannelStore, channels []*model.TopInactiveChannel) ([]*model.TopInactiveChannel, error) { - // query channel members for Ids - var conditionalAggrSelector string - if s.DriverName() == model.DatabaseDriverMysql { - conditionalAggrSelector = "GROUP_CONCAT(UserId SEPARATOR ',') as UserIds" - } else if s.DriverName() == model.DatabaseDriverPostgres { - conditionalAggrSelector = "string_agg(UserId, ',') as UserIds" - } - - var channelIds []string - for _, channel := range channels { - channelIds = append(channelIds, channel.ID) - } - q := s.getQueryBuilder().Select("ChannelId", conditionalAggrSelector).From("ChannelMembers"). - Where(sq.Eq{ - "ChannelId": channelIds, - }).GroupBy("ChannelId") - - channelsUserIdsMap := make(map[string]string, len(channels)) - type ChannelUserIdsResult struct { - ChannelId string - UserIds string - } - - channelsUserIdsResultList := make([]ChannelUserIdsResult, len(channels)) - sql, args, err := q.ToSql() - if err != nil { - return nil, errors.Wrap(err, "failed to stringify squirrel query") - } - if err := s.GetReplicaX().Select(&channelsUserIdsResultList, sql, args...); err != nil { - return nil, errors.Wrap(err, "failed to get top Inactive Channels users") - } - - for _, channelUserIds := range channelsUserIdsResultList { - channelsUserIdsMap[channelUserIds.ChannelId] = channelUserIds.UserIds - } - for index, channel := range channels { - userIds := channelsUserIdsMap[channel.ID] - userIdsSlice := strings.Split(userIds, ",") - - channels[index].Participants = userIdsSlice - - // handle channels with 0 participants - if len(userIdsSlice) == 1 && userIdsSlice[0] == "" { - channels[index].Participants = make([]string, 0) - } - } - return channels, nil -} - -func (s SqlChannelStore) PostCountsByDuration(channelIDs []string, sinceUnixMillis int64, userID *string, duration model.PostCountGrouping, atLocation *time.Location) ([]*model.DurationPostCount, error) { - var unixSelect string - var propsQuery string - loc := atLocation.String() - if loc == "Local" { - loc = "UTC" - } - var format string - if s.DriverName() == model.DatabaseDriverMysql { - if duration == model.PostsByDay { - format = `%Y-%m-%d` - } else { - format = `%Y-%m-%dT%H` - } - unixSelect = fmt.Sprintf(`DATE_FORMAT( - COALESCE( - CONVERT_TZ(FROM_UNIXTIME(Posts.CreateAt / 1000), 'GMT', '%s'), - FROM_UNIXTIME(Posts.CreateAt / 1000) - ), - '%s') AS duration`, loc, format) - propsQuery = `(JSON_EXTRACT(Posts.Props, '$.from_bot') IS NULL OR JSON_EXTRACT(Posts.Props, '$.from_bot') = 'false') AND (JSON_EXTRACT(Posts.Props, '$.from_webhook') IS NULL OR JSON_EXTRACT(Posts.Props, '$.from_webhook') = 'false') AND (JSON_EXTRACT(Posts.Props, '$.from_plugin') IS NULL OR JSON_EXTRACT(Posts.Props, '$.from_plugin') = 'false') AND (JSON_EXTRACT(Posts.Props, '$.from_oauth_app') IS NULL OR JSON_EXTRACT(Posts.Props, '$.from_oauth_app') = 'false')` - } else if s.DriverName() == model.DatabaseDriverPostgres { - if duration == model.PostsByDay { - format = "YYYY-MM-DD" - } else { - format = `YYYY-MM-DD"T"HH24` - } - unixSelect = fmt.Sprintf(`TO_CHAR(TO_TIMESTAMP(Posts.CreateAt / 1000) AT TIME ZONE '%s', '%s') AS duration`, loc, format) - propsQuery = `(Posts.Props ->> 'from_bot' IS NULL OR Posts.Props ->> 'from_bot' = 'false') AND (Posts.Props ->> 'from_webhook' IS NULL OR Posts.Props ->> 'from_webhook' = 'false') AND (Posts.Props ->> 'from_oauth_app' IS NULL OR Posts.Props ->> 'from_oauth_app' = 'false') AND (Posts.Props ->> 'from_plugin' IS NULL OR Posts.Props ->> 'from_plugin' = 'false')` - } - query := sq. - Select("Posts.ChannelId AS channelid", unixSelect, "count(Posts.Id) AS postcount"). - From("Posts"). - LeftJoin("Channels ON Posts.ChannelId = Channels.Id"). - Where(sq.And{ - sq.Eq{"Posts.DeleteAt": 0}, - sq.Gt{"Posts.CreateAt": sinceUnixMillis}, - sq.Eq{"Posts.Type": ""}, - sq.Eq{"Channels.Id": channelIDs}, - }). - Where(propsQuery). - GroupBy("channelid", "duration"). - OrderBy("channelid", "duration") - if userID != nil && model.IsValidId(*userID) { - query = query.Where(sq.And{sq.Eq{"Posts.UserId": *userID}}) - } - queryString, args, err := query.ToSql() - if err != nil { - return nil, errors.Wrap(err, "failed to parse query") - } - dailyPostCounts := make([]*model.DurationPostCount, 0) - if err := s.GetReplicaX().Select(&dailyPostCounts, queryString, args...); err != nil { - return nil, errors.Wrap(err, "failed to get post counts by duration") - } - - return dailyPostCounts, nil -} diff --git a/server/channels/store/sqlstore/post_store.go b/server/channels/store/sqlstore/post_store.go index 62322f7dad..f5128f6a70 100644 --- a/server/channels/store/sqlstore/post_store.go +++ b/server/channels/store/sqlstore/post_store.go @@ -3059,163 +3059,6 @@ func (s *SqlPostStore) updateThreadsFromPosts(transaction *sqlxTxWrapper, posts return nil } -func (s *SqlPostStore) GetTopDMsForUserSince(userID string, since int64, offset int, limit int) (*model.TopDMList, error) { - var botsFilterExpr string - /* - Channel.Name is of the format userId1__userId2. - Using this, self dms, and bot dms can be filtered. - */ - if s.DriverName() == model.DatabaseDriverPostgres { - botsFilterExpr = `SPLIT_PART(Channels.Name, '__', 1) NOT IN (SELECT UserId FROM Bots) - AND SPLIT_PART(Channels.Name, '__', 2) NOT IN (SELECT UserId FROM Bots) - ` - } else if s.DriverName() == model.DatabaseDriverMysql { - botsFilterExpr = `SUBSTRING_INDEX(Channels.Name, '__', 1) NOT IN (SELECT UserId FROM Bots) - AND SUBSTRING_INDEX(Channels.Name, '__', -1) NOT IN (SELECT UserId FROM Bots) - ` - } - - channelSelector := s.getQueryBuilder().Select("Id", "TotalMsgCount").From("Channels").Join("ChannelMembers as cm on cm.ChannelId = Channels.Id"). - Where(sq.And{ - sq.Expr("Channels.Type = 'D'"), - sq.Eq{"cm.UserId": userID}, - sq.NotEq{"Channels.Name": fmt.Sprintf("%s__%s", userID, userID)}, - sq.Expr(botsFilterExpr), - }) - var aggregator string - - if s.DriverName() == model.DatabaseDriverMysql { - aggregator = "group_concat(distinct cm.UserId) as Participants" - } else { - aggregator = "string_agg(distinct cm.UserId, ',') as Participants" - } - - topDMsBuilder := s.getQueryBuilder().Select("count(p.Id) as MessageCount", aggregator, "vch.Id as ChannelId").FromSelect(channelSelector, "vch"). - Join("ChannelMembers as cm on cm.ChannelId = vch.Id"). - Join("Posts as p on p.ChannelId = vch.Id"). - Where(sq.And{ - sq.Gt{ - "p.UpdateAt": since, - }, - sq.Eq{ - "p.DeleteAt": 0, - }, - }).GroupBy("vch.id") - - // following where clause filters out all archived DMs with "deleted" users, that has only 1 user-id in Participants column. - archivedDMsFilter := s.getQueryBuilder().Select("MessageCount", "Participants", "ChannelId").FromSelect(topDMsBuilder, "top_dms"). - Where(sq.Expr("POSITION(',' IN Participants) > 0")) - - archivedDMsFilter = archivedDMsFilter.OrderBy("MessageCount DESC").Limit(uint64(limit + 1)).Offset(uint64(offset)) - - topDMs := make([]*model.TopDM, 0) - sql, args, err := archivedDMsFilter.ToSql() - if err != nil { - return nil, errors.Wrap(err, "GetTopDMsForUserSince_ToSql") - } - err = s.GetReplicaX().Select(&topDMs, sql, args...) - if err != nil { - return nil, errors.Wrapf(err, "failed to find top DMs for user-id: %s", userID) - } - - // fill SecondParticipant column - topDMs, err = postProcessTopDMs(s, userID, topDMs, since) - if err != nil { - return nil, err - } - return model.GetTopDMListWithPagination(topDMs, limit), nil -} - -func postProcessTopDMs(s *SqlPostStore, userID string, topDMs []*model.TopDM, since int64) ([]*model.TopDM, error) { - var topDMsFiltered = []*model.TopDM{} - var secondParticipantIds []string - var channelIds []string - - // identify second participant in a list of participants - for _, topDM := range topDMs { - participants := strings.Split(topDM.Participants, ",") - var secondParticipantId string - // divide message count by 2, because it's counted twice due to channel memberships being 2 for dms. - topDM.MessageCount = topDM.MessageCount / 2 - if participants[0] == userID { - secondParticipantId = participants[1] - } else { - secondParticipantId = participants[0] - } - secondParticipantIds = append(secondParticipantIds, secondParticipantId) - channelIds = append(channelIds, topDM.ChannelId) - } - - // get user profiles - users, err := s.User().GetProfileByIds(context.Background(), secondParticipantIds, &store.UserGetByIdsOpts{}, true) - if err != nil { - return nil, errors.Wrapf(err, "failed to get second participants' information") - } - - // get outgoing message count for userId - outgoingMessagesQuery := s.getQueryBuilder().Select("ch.Id as ChannelId, count(p.Id) as MessageCount").From("Channels as ch"). - Join("Posts as p on p.ChannelId=ch.Id").Where( - sq.And{ - sq.Gt{ - "p.UpdateAt": since, - }, - sq.Eq{ - "p.DeleteAt": 0, - }, - sq.Eq{ - "ch.Id": channelIds, - }, - sq.Eq{ - "p.UserId": userID, - }, - }).GroupBy("ch.Id") - - outgoingMessages := make([]*model.OutgoingMessageQueryResult, 0) - sql, args, err := outgoingMessagesQuery.ToSql() - if err != nil { - return nil, errors.Wrap(err, "GetTopDMsForUserSince_outgoingMessagesQuery_ToSql") - } - err = s.GetReplicaX().Select(&outgoingMessages, sql, args...) - if err != nil { - return nil, errors.Wrapf(err, "failed to find top DMs for user-id: %s", userID) - } - - // create map of channelId -> MessageCount - outgoingMessagesMap := make(map[string]int) - for _, outgoingMessage := range outgoingMessages { - outgoingMessagesMap[outgoingMessage.ChannelId] = outgoingMessage.MessageCount - } - - // create map of userId -> User - usersMap := make(map[string]*model.User) - for _, user := range users { - usersMap[user.Id] = user - } - - for index, topDM := range topDMs { - if secondParticipantIds[index] == "-1" { - return nil, errors.Wrapf(err, "failed to find second user for topDM: %s", userID) - } - user := usersMap[secondParticipantIds[index]] - topDM.SecondParticipant = &model.TopDMInsightUserInformation{ - InsightUserInformation: model.InsightUserInformation{ - Id: user.Id, - LastPictureUpdate: user.LastPictureUpdate, - FirstName: user.FirstName, - LastName: user.LastName, - Username: user.Username, - NickName: user.Nickname, - }, - Position: user.Position, - } - - topDM.OutgoingMessageCount = int64(outgoingMessagesMap[topDM.ChannelId]) - topDMsFiltered = append(topDMsFiltered, topDM) - } - - return topDMsFiltered, nil -} - func (s *SqlPostStore) SetPostReminder(reminder *model.PostReminder) error { transaction, err := s.GetMasterX().Beginx() if err != nil { diff --git a/server/channels/store/sqlstore/reaction_store.go b/server/channels/store/sqlstore/reaction_store.go index cefa57f71d..1d9183cc2f 100644 --- a/server/channels/store/sqlstore/reaction_store.go +++ b/server/channels/store/sqlstore/reaction_store.go @@ -245,124 +245,6 @@ func (s *SqlReactionStore) PermanentDeleteBatch(endTime int64, limit int64) (int return rowsAffected, nil } -// GetTopForTeamSince returns the instance counts of the following Reactions sets: -// a) those created by anyone in private channels in the given user's membership graph on the given team, and -// b) those created by anyone in public channels on the given team. -func (s *SqlReactionStore) GetTopForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopReactionList, error) { - reactions := make([]*model.TopReaction, 0) - - query := ` - SELECT - EmojiName, - sum(EmojiCount) AS Count - FROM (( - SELECT - EmojiName, - count(EmojiName) AS EmojiCount, - Reactions.DeleteAt AS DeleteAt, - Reactions.CreateAt AS CreateAt - FROM - ChannelMembers - INNER JOIN Channels ON ChannelMembers.ChannelId = Channels.Id - INNER JOIN Reactions ON Channels.Id = Reactions.ChannelId - WHERE - ChannelMembers.UserId = ? - AND Channels.Type = 'P' - AND Channels.TeamId = ? - GROUP BY - Reactions.EmojiName, - Reactions.DeleteAt, - Reactions.CreateAt) - UNION ALL ( - SELECT - EmojiName, - count(EmojiName) AS EmojiCount, - Reactions.DeleteAt AS DeleteAt, - Reactions.CreateAt AS CreateAt - FROM - Reactions - INNER JOIN PublicChannels ON Reactions.ChannelId = PublicChannels.Id - WHERE - PublicChannels.TeamId = ? - GROUP BY - Reactions.EmojiName, - Reactions.DeleteAt, - Reactions.CreateAt)) AS A - WHERE - DeleteAt = 0 - AND CreateAt > ? - GROUP BY - EmojiName - ORDER BY - Count DESC, - EmojiName ASC - LIMIT ? - OFFSET ?` - - if err := s.GetReplicaX().Select(&reactions, query, userID, teamID, teamID, since, limit+1, offset); err != nil { - return nil, errors.Wrap(err, "failed to get top Reactions") - } - - return model.GetTopReactionListWithPagination(reactions, limit), nil -} - -// GetTopForUserSince returns the instance counts of the following Reactions sets: -// a) those created by the given user in any channel type on the given team (across the workspace if no team is given), and -// b) those created by the given user in DM or group channels. -func (s *SqlReactionStore) GetTopForUserSince(userID string, teamID string, since int64, offset int, limit int) (*model.TopReactionList, error) { - reactions := make([]*model.TopReaction, 0) - var args []any - var query string - - if teamID != "" { - query = ` - SELECT - EmojiName, - count(EmojiName) AS Count - FROM - Reactions - INNER JOIN Channels ON Channels.Id = Reactions.ChannelId - WHERE - Reactions.DeleteAt = 0 - AND Reactions.UserId = ? - AND (Channels.TeamId = ? OR Channels.Type = 'D' OR Channels.Type = 'G') - AND Reactions.CreateAt > ? - GROUP BY - EmojiName - ORDER BY - Count DESC, - EmojiName ASC - LIMIT ? - OFFSET ?` - args = []any{userID, teamID, since, limit + 1, offset} - } else { - query = ` - SELECT - EmojiName, - count(EmojiName) AS Count - FROM - Reactions - WHERE - Reactions.DeleteAt = 0 - AND Reactions.UserId = ? - AND Reactions.CreateAt > ? - GROUP BY - Reactions.EmojiName - ORDER BY - Count DESC, - EmojiName ASC - LIMIT ? - OFFSET ?` - args = []any{userID, since, limit + 1, offset} - } - - if err := s.GetReplicaX().Select(&reactions, query, args...); err != nil { - return nil, errors.Wrap(err, "failed to get top Reactions") - } - - return model.GetTopReactionListWithPagination(reactions, limit), nil -} - func (s *SqlReactionStore) saveReactionAndUpdatePost(transaction *sqlxTxWrapper, reaction *model.Reaction) error { reaction.DeleteAt = 0 diff --git a/server/channels/store/sqlstore/team_store.go b/server/channels/store/sqlstore/team_store.go index c74aea84ed..146dd3b230 100644 --- a/server/channels/store/sqlstore/team_store.go +++ b/server/channels/store/sqlstore/team_store.go @@ -1652,46 +1652,3 @@ func (s SqlTeamStore) GroupSyncedTeamCount() (int64, error) { return count, nil } - -func (s SqlTeamStore) GetNewTeamMembersSince(teamID string, since int64, offset int, limit int, showFullName bool) (*model.NewTeamMembersList, int64, error) { - builderF := func(selectClause string) sq.SelectBuilder { - return s.getQueryBuilder(). - Select(selectClause). - From("TeamMembers"). - Join("Users ON Users.id = TeamMembers.userid"). - LeftJoin("Bots ON Bots.userid = Users.id"). - Where(sq.GtOrEq{"TeamMembers.createat": since}). - Where(sq.Eq{"TeamMembers.deleteat": 0, "teamid": teamID, "Users.deleteat": 0, "Bots.userid": nil}) - } - - countBuilder := builderF("count(*)") - query, args, err := countBuilder.ToSql() - if err != nil { - return nil, 0, errors.Wrap(err, "team_tosql") - } - var totalCount int64 - err = s.GetReplicaX().Get(&totalCount, query, args...) - if err != nil { - return nil, 0, errors.Wrap(err, "failed to count team members since") - } - - selectClause := "Users.Id, Users.Username, Users.Position, Users.LastPictureUpdate, TeamMembers.CreateAt, Users.Nickname" - if showFullName { - selectClause += ", Users.FirstName, Users.LastName" - } - - newTeamMembersBuilder := builderF(selectClause). - Limit(uint64(limit + 1)). - Offset(uint64(offset)) - query, args, err = newTeamMembersBuilder.ToSql() - if err != nil { - return nil, 0, errors.Wrap(err, "team_tosql") - } - var ntms []*model.NewTeamMember - err = s.GetReplicaX().Select(&ntms, query, args...) - if err != nil { - return nil, 0, errors.Wrap(err, "failed to get team members since") - } - - return model.GetNewTeamMembersListWithPagination(ntms, limit), totalCount, nil -} diff --git a/server/channels/store/sqlstore/thread_store.go b/server/channels/store/sqlstore/thread_store.go index 4ba954252e..8a7627a6de 100644 --- a/server/channels/store/sqlstore/thread_store.go +++ b/server/channels/store/sqlstore/thread_store.go @@ -1021,209 +1021,3 @@ func (s *SqlThreadStore) GetThreadUnreadReplyCount(threadMembership *model.Threa return unreadReplies, nil } - -// Top threads in all public channels and private channels userID is a member of. Returns a list of threads ranked by interactions. -func (s *SqlThreadStore) GetTopThreadsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopThreadList, error) { - var args []any - query := `select - threads_list.PostId, - threads_list.ReplyCount, - threads_list.ChannelId, - threads_list.DisplayName, - threads_list.Name, - threads_list.Participants, - p.UserId - from(( - SELECT - t.PostId, - t.ReplyCount, - t.ChannelId, - t.Participants, - c.DisplayName, - c.Name - FROM - Threads t - LEFT JOIN PublicChannels c ON t.ChannelId = c.Id - WHERE - t.threaddeleteat IS NULL - AND t.LastReplyAt > ? - AND c.TeamId = ? - GROUP BY - t.PostId, - c.DisplayName, - c.Name, - t.Participants - ) - UNION - ALL ( - SELECT - t.PostId, - t.ReplyCount, - t.ChannelId, - t.Participants, - c.DisplayName, - c.Name - FROM - Threads t - LEFT JOIN ChannelMembers cm ON t.ChannelId = cm.ChannelId - LEFT JOIN Channels c ON t.ChannelId = c.Id - WHERE - t.threaddeleteat IS NULL - AND cm.UserId = ? - AND c.Type = 'P' - AND c.TeamId = ? - AND t.LastReplyAt > ? - GROUP BY - t.PostId, - c.DisplayName, - c.Name, - t.Participants - )) as threads_list - LEFT JOIN Posts as p on p.Id = threads_list.PostId - ORDER BY ReplyCount DESC - limit ? offset ?` - - args = append(args, since, teamID, userID, teamID, since, limit+1, offset) - - topThreads := make([]*model.TopThread, 0) - err := s.GetReplicaX().Select(&topThreads, query, args...) - if err != nil { - return nil, errors.Wrapf(err, "failed to get top threads=%s", teamID) - } - topThreads, err = postProcessTopThreads(topThreads, s, teamID) - if err != nil { - return nil, err - } - return model.GetTopThreadListWithPagination(topThreads, limit), nil -} - -func (s *SqlThreadStore) GetTopThreadsForUserSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopThreadList, error) { - var args []any - - // gets all threads within the team which user follows. - query := `select - threads_list.PostId, - threads_list.ReplyCount, - threads_list.ChannelId, - threads_list.DisplayName, - threads_list.Name, - threads_list.Participants, - p.UserId - from(( - SELECT - t.PostId, - t.ReplyCount, - t.ChannelId, - t.Participants, - c.DisplayName, - c.Name - FROM - Threads t - LEFT JOIN PublicChannels c ON t.ChannelId = c.Id - LEFT JOIN ThreadMemberships as tm on t.PostId = tm.PostId - WHERE - t.threaddeleteat IS NULL - AND t.LastReplyAt > ? - AND c.TeamId = ? - AND tm.UserId = ? - AND tm.Following = TRUE - GROUP BY - t.PostId, - c.DisplayName, - c.Name, - t.Participants - ) - UNION - ALL ( - SELECT - t.PostId, - t.ReplyCount, - t.ChannelId, - t.Participants, - c.DisplayName, - c.Name - FROM - Threads t - LEFT JOIN ChannelMembers cm ON t.ChannelId = cm.ChannelId - LEFT JOIN Channels c ON t.ChannelId = c.Id - LEFT JOIN ThreadMemberships as tm on t.PostId = tm.PostId - WHERE - cm.UserId = ? - AND c.Type = 'P' - AND c.TeamId = ? - AND t.threaddeleteat IS NULL - AND t.LastReplyAt > ? - AND tm.UserId = ? - AND tm.Following = TRUE - GROUP BY - t.PostId, - c.DisplayName, - c.Name, - t.Participants - )) as threads_list - LEFT JOIN Posts as p on p.Id = threads_list.PostId - ORDER BY ReplyCount DESC - limit ? offset ?` - - args = append(args, since, teamID, userID, userID, teamID, since, userID, limit+1, offset) - - topThreads := make([]*model.TopThread, 0) - err := s.GetReplicaX().Select(&topThreads, query, args...) - if err != nil { - return nil, errors.Wrapf(err, "failed to get top threads=%s", teamID) - } - topThreads, err = postProcessTopThreads(topThreads, s, teamID) - if err != nil { - return nil, err - } - return model.GetTopThreadListWithPagination(topThreads, limit), nil -} - -func userContains(userIDs []string, searchedUserID string) bool { - for _, userID := range userIDs { - if userID == searchedUserID { - return true - } - } - return false -} - -func postProcessTopThreads(topThreads []*model.TopThread, s *SqlThreadStore, teamID string) ([]*model.TopThread, error) { - // create list of userIDs - var userIDs []string - for _, topThread := range topThreads { - userID := topThread.UserId - if !userContains(userIDs, userID) { - userIDs = append(userIDs, userID) - } - } - - usersMap := map[string]*model.User{} - - users, err := s.User().GetProfileByIds(context.Background(), userIDs, &store.UserGetByIdsOpts{}, true) - if err != nil { - return nil, errors.Wrapf(err, "failed to get users for top threads in team=%s", teamID) - } - for _, user := range users { - usersMap[user.Id] = user - } - - // resolve user, root post for each top thread - for _, topThread := range topThreads { - postCreator := usersMap[topThread.UserId] - topThread.UserInformation = &model.InsightUserInformation{ - Id: postCreator.Id, - LastPictureUpdate: postCreator.LastPictureUpdate, - FirstName: postCreator.FirstName, - LastName: postCreator.LastName, - Username: postCreator.Username, - NickName: postCreator.Nickname, - } - post, err := s.Post().GetSingle(topThread.PostId, false) - if err != nil { - return nil, errors.Wrapf(err, "failed to get extended post for post id=%s", topThread.PostId) - } - topThread.Post = post - } - return topThreads, nil -} diff --git a/server/channels/store/store.go b/server/channels/store/store.go index 81215b164e..2828c91527 100644 --- a/server/channels/store/store.go +++ b/server/channels/store/store.go @@ -170,8 +170,6 @@ type TeamStore interface { // GetCommonTeamIDsForTwoUsers returns the intersection of all the teams to which the specified // users belong. GetCommonTeamIDsForTwoUsers(userID, otherUserID string) ([]string, error) - - GetNewTeamMembersSince(teamID string, since int64, offset int, limit int, showFullName bool) (*model.NewTeamMembersList, int64, error) } type ChannelStore interface { @@ -300,15 +298,6 @@ type ChannelStore interface { SetShared(channelId string, shared bool) error // GetTeamForChannel returns the team for a given channelID. GetTeamForChannel(channelID string) (*model.Team, error) - - // Insights - channels - GetTopChannelsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopChannelList, error) - GetTopChannelsForUserSince(userID string, teamID string, since int64, offset int, limit int) (*model.TopChannelList, error) - PostCountsByDuration(channelIDs []string, sinceUnixMillis int64, userID *string, duration model.PostCountGrouping, groupingLocation *time.Location) ([]*model.DurationPostCount, error) - - // Insights - inactive channels - GetTopInactiveChannelsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopInactiveChannelList, error) - GetTopInactiveChannelsForUserSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopInactiveChannelList, error) } type ChannelMemberHistoryStore interface { @@ -347,10 +336,6 @@ type ThreadStore interface { DeleteOrphanedRows(limit int) (deleted int64, err error) GetThreadUnreadReplyCount(threadMembership *model.ThreadMembership) (int64, error) DeleteMembershipsForChannel(userID, channelID string) error - - // Insights - threads - GetTopThreadsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopThreadList, error) - GetTopThreadsForUserSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopThreadList, error) } type PostStore interface { @@ -404,9 +389,6 @@ type PostStore interface { GetPostReminderMetadata(postID string) (*PostReminderMetadata, error) // GetNthRecentPostTime returns the CreateAt time of the nth most recent post. GetNthRecentPostTime(n int64) (int64, error) - - // Insights - top DMs - GetTopDMsForUserSince(userID string, since int64, offset int, limit int) (*model.TopDMList, error) } type UserStore interface { @@ -727,8 +709,6 @@ type ReactionStore interface { BulkGetForPosts(postIds []string) ([]*model.Reaction, error) DeleteOrphanedRows(limit int) (int64, error) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) - GetTopForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopReactionList, error) - GetTopForUserSince(userID string, teamID string, since int64, offset int, limit int) (*model.TopReactionList, error) } type JobStore interface { diff --git a/server/channels/store/storetest/channel_store.go b/server/channels/store/storetest/channel_store.go index ffc8eaf4a9..70c27e9e95 100644 --- a/server/channels/store/storetest/channel_store.go +++ b/server/channels/store/storetest/channel_store.go @@ -149,8 +149,6 @@ func TestChannelStore(t *testing.T, ss store.Store, s SqlStore) { t.Run("UpdateSidebarChannelsByPreferences", func(t *testing.T) { testUpdateSidebarChannelsByPreferences(t, ss) }) t.Run("SetShared", func(t *testing.T) { testSetShared(t, ss) }) t.Run("GetTeamForChannel", func(t *testing.T) { testGetTeamForChannel(t, ss) }) - t.Run("PostCountsByDuration", func(t *testing.T) { testChannelPostCountsByDuration(t, ss) }) - t.Run("GetTopInactiveChannels", func(t *testing.T) { testGetTopInactiveChannels(t, ss) }) } func testChannelStoreSave(t *testing.T, ss store.Store) { @@ -8067,235 +8065,3 @@ func testGetTeamForChannel(t *testing.T, ss store.Store) { var nfErr *store.ErrNotFound require.True(t, errors.As(err, &nfErr)) } - -func testChannelPostCountsByDuration(t *testing.T, ss store.Store) { - team, err := ss.Team().Save(&model.Team{ - Name: model.NewId(), - DisplayName: "DisplayName", - Email: MakeEmail(), - Type: model.TeamOpen, - }) - require.NoError(t, err) - defer func() { ss.Team().PermanentDelete(team.Id) }() - - channel := &model.Channel{ - TeamId: team.Id, - DisplayName: "test_share_flag", - Name: "test_share_flag", - Type: model.ChannelTypeOpen, - } - channelSaved, err := ss.Channel().Save(channel, 999) - require.NoError(t, err) - defer func() { ss.Channel().PermanentDelete(channelSaved.Id) }() - - userID := model.NewId() - _, err = ss.Post().Save(&model.Post{ - UserId: userID, - ChannelId: channel.Id, - Message: "test", - }) - require.NoError(t, err) - - _, err = ss.Post().Save(&model.Post{ - UserId: userID, - ChannelId: channel.Id, - Message: "test", - Props: model.StringInterface{ - "from_bot": true, - }, - }) - require.NoError(t, err) - - _, err = ss.Post().Save(&model.Post{ - UserId: userID, - ChannelId: channel.Id, - Message: "test", - Props: model.StringInterface{ - "from_webhook": true, - }, - }) - require.NoError(t, err) - - dpc, err := ss.Channel().PostCountsByDuration([]string{channelSaved.Id}, 0, &userID, model.PostsByDay, time.Now().Location()) - require.NoError(t, err) - require.Len(t, dpc, 1) - require.Equal(t, channel.Id, dpc[0].ChannelID) - require.Equal(t, 1, dpc[0].PostCount) -} - -func testGetTopInactiveChannels(t *testing.T, ss store.Store) { - team, err := ss.Team().Save(&model.Team{ - Name: model.NewId(), - DisplayName: "DisplayName", - Email: MakeEmail(), - Type: model.TeamOpen, - }) - require.NoError(t, err) - defer func() { ss.Team().PermanentDelete(team.Id) }() - - channelPublic0 := &model.Channel{ - TeamId: team.Id, - DisplayName: "test_share_flag asdf", - Name: "test_share_flag_public0", - Type: model.ChannelTypeOpen, - CreateAt: 1, - } - - channelSaved0, err := ss.Channel().Save(channelPublic0, 999) - require.NoError(t, err) - defer func() { ss.Channel().PermanentDelete(channelSaved0.Id) }() - - channelPublic1 := &model.Channel{ - TeamId: team.Id, - DisplayName: "test_share_flag", - Name: "test_share_flag", - Type: model.ChannelTypeOpen, - CreateAt: 1, - } - - channelSaved1, err := ss.Channel().Save(channelPublic1, 999) - require.NoError(t, err) - defer func() { ss.Channel().PermanentDelete(channelSaved1.Id) }() - - // create private channel - c3 := model.Channel{} - c3.TeamId = team.Id - c3.DisplayName = "Channel3" + model.NewId() - c3.Name = NewTestId() - c3.Type = model.ChannelTypePrivate - c3.CreateAt = 1 - channelPrivate, nErr := ss.Channel().Save(&c3, -1) - require.NoError(t, nErr) - - // create private channel with post - c3NoPost := model.Channel{} - c3NoPost.TeamId = team.Id - c3NoPost.DisplayName = "Channel3" + model.NewId() - c3NoPost.Name = NewTestId() - c3NoPost.Type = model.ChannelTypePrivate - c3NoPost.CreateAt = 1 - channelPrivateNoPost, nErr := ss.Channel().Save(&c3NoPost, -1) - require.NoError(t, nErr) - - // create dm channel - u1 := model.User{} - u1.Email = MakeEmail() - u1.Nickname = model.NewId() - _, err = ss.User().Save(&u1) - require.NoError(t, err) - - u2 := model.User{} - u2.Email = MakeEmail() - u2.Nickname = model.NewId() - _, err = ss.User().Save(&u2) - require.NoError(t, err) - - uBot := model.User{Id: model.NewId()} - _, nErr = ss.Channel().CreateDirectChannel(&u1, &u2) - require.NoError(t, nErr) - - // add u1, u2 to channels - - cm1 := &model.ChannelMember{ChannelId: channelPrivate.Id, UserId: u1.Id, NotifyProps: model.GetDefaultChannelNotifyProps()} - _, err = ss.Channel().SaveMember(cm1) - require.NoError(t, err) - cm1NoPost := &model.ChannelMember{ChannelId: channelPrivateNoPost.Id, UserId: u1.Id, NotifyProps: model.GetDefaultChannelNotifyProps()} - _, err = ss.Channel().SaveMember(cm1NoPost) - require.NoError(t, err) - cm1Public := &model.ChannelMember{ChannelId: channelPublic1.Id, UserId: u1.Id, NotifyProps: model.GetDefaultChannelNotifyProps()} - _, err = ss.Channel().SaveMember(cm1Public) - require.NoError(t, err) - cm2 := &model.ChannelMember{ChannelId: channelPublic0.Id, UserId: u2.Id, NotifyProps: model.GetDefaultChannelNotifyProps()} - _, err = ss.Channel().SaveMember(cm2) - require.NoError(t, err) - cmBot := &model.ChannelMember{ChannelId: channelPublic0.Id, UserId: uBot.Id, NotifyProps: model.GetDefaultChannelNotifyProps()} - _, err = ss.Channel().SaveMember(cmBot) - require.NoError(t, err) - - _, err = ss.Post().Save(&model.Post{ - UserId: u1.Id, - ChannelId: channelPrivate.Id, - Message: "test", - }) - require.NoError(t, err) - - _, err = ss.Post().Save(&model.Post{ - UserId: u1.Id, - ChannelId: channelPrivate.Id, - Message: "test1", - }) - require.NoError(t, err) - - // create posts in channel public 0 - postToCheckLastUpdateAt, err := ss.Post().Save(&model.Post{ - UserId: u2.Id, - ChannelId: channelSaved0.Id, - Message: "test", - }) - require.NoError(t, err) - - _, err = ss.Post().Save(&model.Post{ - UserId: model.NewId(), - ChannelId: channelPublic1.Id, - Message: "test", - Props: model.StringInterface{ - "from_bot": true, - }, - }) - require.NoError(t, err) - - // create posts in channel public 1 - for i := 0; i < 3; i++ { - _, err = ss.Post().Save(&model.Post{ - UserId: model.NewId(), - ChannelId: channelPublic1.Id, - Message: "test", - }) - require.NoError(t, err) - } - - // for u1 - t.Run("top inactive channels for team - u1 ", func(t *testing.T) { - topInactiveChannels, err := ss.Channel().GetTopInactiveChannelsForTeamSince(team.Id, u1.Id, 2, 0, 10) - require.NoError(t, err) - require.Len(t, topInactiveChannels.Items, 4) - require.Equal(t, topInactiveChannels.Items[0].ID, channelPrivateNoPost.Id) - require.Equal(t, topInactiveChannels.Items[1].ID, channelSaved0.Id) - require.Equal(t, topInactiveChannels.Items[1].LastActivityAt, postToCheckLastUpdateAt.CreateAt) - require.Equal(t, topInactiveChannels.Items[2].ID, channelPrivate.Id) - require.Equal(t, topInactiveChannels.Items[3].ID, channelPublic1.Id) - // test bot posts are counted - require.Equal(t, topInactiveChannels.Items[3].MessageCount, int64(4)) - - // participants - require.Equal(t, topInactiveChannels.Items[2].Participants[0], u1.Id) - require.Equal(t, topInactiveChannels.Items[3].Participants[0], u1.Id) - }) - - t.Run("top inactive channels for user - u1 ", func(t *testing.T) { - topInactiveChannels, err := ss.Channel().GetTopInactiveChannelsForUserSince(team.Id, u1.Id, 2, 0, 10) - require.NoError(t, err) - require.Len(t, topInactiveChannels.Items, 3) - require.Equal(t, topInactiveChannels.Items[0].ID, channelPrivateNoPost.Id) - require.Equal(t, topInactiveChannels.Items[1].ID, channelPrivate.Id) - require.Equal(t, topInactiveChannels.Items[2].ID, channelPublic1.Id) - }) - - // for u2 - t.Run("top inactive channels for team - u2 ", func(t *testing.T) { - topInactiveChannels, err := ss.Channel().GetTopInactiveChannelsForTeamSince(team.Id, u2.Id, 2, 0, 10) - require.NoError(t, err) - require.Len(t, topInactiveChannels.Items, 2) - require.Equal(t, topInactiveChannels.Items[0].ID, channelSaved0.Id) - require.Equal(t, topInactiveChannels.Items[0].LastActivityAt, postToCheckLastUpdateAt.CreateAt) - require.Equal(t, topInactiveChannels.Items[1].ID, channelPublic1.Id) - }) - - t.Run("top inactive channels for user - u2 ", func(t *testing.T) { - topInactiveChannels, err := ss.Channel().GetTopInactiveChannelsForUserSince(team.Id, u2.Id, 2, 0, 10) - require.NoError(t, err) - require.Len(t, topInactiveChannels.Items, 1) - require.Equal(t, topInactiveChannels.Items[0].ID, channelPublic0.Id) - }) - -} diff --git a/server/channels/store/storetest/mocks/ChannelStore.go b/server/channels/store/storetest/mocks/ChannelStore.go index 64520f4df9..4d3500da1c 100644 --- a/server/channels/store/storetest/mocks/ChannelStore.go +++ b/server/channels/store/storetest/mocks/ChannelStore.go @@ -11,8 +11,6 @@ import ( mock "github.com/stretchr/testify/mock" store "github.com/mattermost/mattermost/server/v8/channels/store" - - time "time" ) // ChannelStore is an autogenerated mock type for the ChannelStore type @@ -1774,110 +1772,6 @@ func (_m *ChannelStore) GetTeamMembersForChannel(channelID string) ([]string, er return r0, r1 } -// GetTopChannelsForTeamSince provides a mock function with given fields: teamID, userID, since, offset, limit -func (_m *ChannelStore) GetTopChannelsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopChannelList, error) { - ret := _m.Called(teamID, userID, since, offset, limit) - - var r0 *model.TopChannelList - var r1 error - if rf, ok := ret.Get(0).(func(string, string, int64, int, int) (*model.TopChannelList, error)); ok { - return rf(teamID, userID, since, offset, limit) - } - if rf, ok := ret.Get(0).(func(string, string, int64, int, int) *model.TopChannelList); ok { - r0 = rf(teamID, userID, since, offset, limit) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*model.TopChannelList) - } - } - - if rf, ok := ret.Get(1).(func(string, string, int64, int, int) error); ok { - r1 = rf(teamID, userID, since, offset, limit) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetTopChannelsForUserSince provides a mock function with given fields: userID, teamID, since, offset, limit -func (_m *ChannelStore) GetTopChannelsForUserSince(userID string, teamID string, since int64, offset int, limit int) (*model.TopChannelList, error) { - ret := _m.Called(userID, teamID, since, offset, limit) - - var r0 *model.TopChannelList - var r1 error - if rf, ok := ret.Get(0).(func(string, string, int64, int, int) (*model.TopChannelList, error)); ok { - return rf(userID, teamID, since, offset, limit) - } - if rf, ok := ret.Get(0).(func(string, string, int64, int, int) *model.TopChannelList); ok { - r0 = rf(userID, teamID, since, offset, limit) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*model.TopChannelList) - } - } - - if rf, ok := ret.Get(1).(func(string, string, int64, int, int) error); ok { - r1 = rf(userID, teamID, since, offset, limit) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetTopInactiveChannelsForTeamSince provides a mock function with given fields: teamID, userID, since, offset, limit -func (_m *ChannelStore) GetTopInactiveChannelsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopInactiveChannelList, error) { - ret := _m.Called(teamID, userID, since, offset, limit) - - var r0 *model.TopInactiveChannelList - var r1 error - if rf, ok := ret.Get(0).(func(string, string, int64, int, int) (*model.TopInactiveChannelList, error)); ok { - return rf(teamID, userID, since, offset, limit) - } - if rf, ok := ret.Get(0).(func(string, string, int64, int, int) *model.TopInactiveChannelList); ok { - r0 = rf(teamID, userID, since, offset, limit) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*model.TopInactiveChannelList) - } - } - - if rf, ok := ret.Get(1).(func(string, string, int64, int, int) error); ok { - r1 = rf(teamID, userID, since, offset, limit) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetTopInactiveChannelsForUserSince provides a mock function with given fields: teamID, userID, since, offset, limit -func (_m *ChannelStore) GetTopInactiveChannelsForUserSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopInactiveChannelList, error) { - ret := _m.Called(teamID, userID, since, offset, limit) - - var r0 *model.TopInactiveChannelList - var r1 error - if rf, ok := ret.Get(0).(func(string, string, int64, int, int) (*model.TopInactiveChannelList, error)); ok { - return rf(teamID, userID, since, offset, limit) - } - if rf, ok := ret.Get(0).(func(string, string, int64, int, int) *model.TopInactiveChannelList); ok { - r0 = rf(teamID, userID, since, offset, limit) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*model.TopInactiveChannelList) - } - } - - if rf, ok := ret.Get(1).(func(string, string, int64, int, int) error); ok { - r1 = rf(teamID, userID, since, offset, limit) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // GroupSyncedChannelCount provides a mock function with given fields: func (_m *ChannelStore) GroupSyncedChannelCount() (int64, error) { ret := _m.Called() @@ -2047,32 +1941,6 @@ func (_m *ChannelStore) PermanentDeleteMembersByUser(userID string) error { return r0 } -// PostCountsByDuration provides a mock function with given fields: channelIDs, sinceUnixMillis, userID, duration, groupingLocation -func (_m *ChannelStore) PostCountsByDuration(channelIDs []string, sinceUnixMillis int64, userID *string, duration model.PostCountGrouping, groupingLocation *time.Location) ([]*model.DurationPostCount, error) { - ret := _m.Called(channelIDs, sinceUnixMillis, userID, duration, groupingLocation) - - var r0 []*model.DurationPostCount - var r1 error - if rf, ok := ret.Get(0).(func([]string, int64, *string, model.PostCountGrouping, *time.Location) ([]*model.DurationPostCount, error)); ok { - return rf(channelIDs, sinceUnixMillis, userID, duration, groupingLocation) - } - if rf, ok := ret.Get(0).(func([]string, int64, *string, model.PostCountGrouping, *time.Location) []*model.DurationPostCount); ok { - r0 = rf(channelIDs, sinceUnixMillis, userID, duration, groupingLocation) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*model.DurationPostCount) - } - } - - if rf, ok := ret.Get(1).(func([]string, int64, *string, model.PostCountGrouping, *time.Location) error); ok { - r1 = rf(channelIDs, sinceUnixMillis, userID, duration, groupingLocation) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // RemoveAllDeactivatedMembers provides a mock function with given fields: channelID func (_m *ChannelStore) RemoveAllDeactivatedMembers(channelID string) error { ret := _m.Called(channelID) diff --git a/server/channels/store/storetest/mocks/PostStore.go b/server/channels/store/storetest/mocks/PostStore.go index 880b12882b..92e7c56db8 100644 --- a/server/channels/store/storetest/mocks/PostStore.go +++ b/server/channels/store/storetest/mocks/PostStore.go @@ -840,32 +840,6 @@ func (_m *PostStore) GetSingle(id string, inclDeleted bool) (*model.Post, error) return r0, r1 } -// GetTopDMsForUserSince provides a mock function with given fields: userID, since, offset, limit -func (_m *PostStore) GetTopDMsForUserSince(userID string, since int64, offset int, limit int) (*model.TopDMList, error) { - ret := _m.Called(userID, since, offset, limit) - - var r0 *model.TopDMList - var r1 error - if rf, ok := ret.Get(0).(func(string, int64, int, int) (*model.TopDMList, error)); ok { - return rf(userID, since, offset, limit) - } - if rf, ok := ret.Get(0).(func(string, int64, int, int) *model.TopDMList); ok { - r0 = rf(userID, since, offset, limit) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*model.TopDMList) - } - } - - if rf, ok := ret.Get(1).(func(string, int64, int, int) error); ok { - r1 = rf(userID, since, offset, limit) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // HasAutoResponsePostByUserSince provides a mock function with given fields: options, userId func (_m *PostStore) HasAutoResponsePostByUserSince(options model.GetPostsSinceOptions, userId string) (bool, error) { ret := _m.Called(options, userId) diff --git a/server/channels/store/storetest/mocks/ReactionStore.go b/server/channels/store/storetest/mocks/ReactionStore.go index 4fd5c57734..2ca3f86e0d 100644 --- a/server/channels/store/storetest/mocks/ReactionStore.go +++ b/server/channels/store/storetest/mocks/ReactionStore.go @@ -156,58 +156,6 @@ func (_m *ReactionStore) GetForPostSince(postId string, since int64, excludeRemo return r0, r1 } -// GetTopForTeamSince provides a mock function with given fields: teamID, userID, since, offset, limit -func (_m *ReactionStore) GetTopForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopReactionList, error) { - ret := _m.Called(teamID, userID, since, offset, limit) - - var r0 *model.TopReactionList - var r1 error - if rf, ok := ret.Get(0).(func(string, string, int64, int, int) (*model.TopReactionList, error)); ok { - return rf(teamID, userID, since, offset, limit) - } - if rf, ok := ret.Get(0).(func(string, string, int64, int, int) *model.TopReactionList); ok { - r0 = rf(teamID, userID, since, offset, limit) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*model.TopReactionList) - } - } - - if rf, ok := ret.Get(1).(func(string, string, int64, int, int) error); ok { - r1 = rf(teamID, userID, since, offset, limit) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetTopForUserSince provides a mock function with given fields: userID, teamID, since, offset, limit -func (_m *ReactionStore) GetTopForUserSince(userID string, teamID string, since int64, offset int, limit int) (*model.TopReactionList, error) { - ret := _m.Called(userID, teamID, since, offset, limit) - - var r0 *model.TopReactionList - var r1 error - if rf, ok := ret.Get(0).(func(string, string, int64, int, int) (*model.TopReactionList, error)); ok { - return rf(userID, teamID, since, offset, limit) - } - if rf, ok := ret.Get(0).(func(string, string, int64, int, int) *model.TopReactionList); ok { - r0 = rf(userID, teamID, since, offset, limit) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*model.TopReactionList) - } - } - - if rf, ok := ret.Get(1).(func(string, string, int64, int, int) error); ok { - r1 = rf(userID, teamID, since, offset, limit) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // PermanentDeleteBatch provides a mock function with given fields: endTime, limit func (_m *ReactionStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) { ret := _m.Called(endTime, limit) diff --git a/server/channels/store/storetest/mocks/TeamStore.go b/server/channels/store/storetest/mocks/TeamStore.go index 509e4a8cf3..9ac7094b16 100644 --- a/server/channels/store/storetest/mocks/TeamStore.go +++ b/server/channels/store/storetest/mocks/TeamStore.go @@ -549,39 +549,6 @@ func (_m *TeamStore) GetMembersByIds(teamID string, userIds []string, restrictio return r0, r1 } -// GetNewTeamMembersSince provides a mock function with given fields: teamID, since, offset, limit, showFullName -func (_m *TeamStore) GetNewTeamMembersSince(teamID string, since int64, offset int, limit int, showFullName bool) (*model.NewTeamMembersList, int64, error) { - ret := _m.Called(teamID, since, offset, limit, showFullName) - - var r0 *model.NewTeamMembersList - var r1 int64 - var r2 error - if rf, ok := ret.Get(0).(func(string, int64, int, int, bool) (*model.NewTeamMembersList, int64, error)); ok { - return rf(teamID, since, offset, limit, showFullName) - } - if rf, ok := ret.Get(0).(func(string, int64, int, int, bool) *model.NewTeamMembersList); ok { - r0 = rf(teamID, since, offset, limit, showFullName) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*model.NewTeamMembersList) - } - } - - if rf, ok := ret.Get(1).(func(string, int64, int, int, bool) int64); ok { - r1 = rf(teamID, since, offset, limit, showFullName) - } else { - r1 = ret.Get(1).(int64) - } - - if rf, ok := ret.Get(2).(func(string, int64, int, int, bool) error); ok { - r2 = rf(teamID, since, offset, limit, showFullName) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - // GetTeamMembersForExport provides a mock function with given fields: userID func (_m *TeamStore) GetTeamMembersForExport(userID string) ([]*model.TeamMemberForExport, error) { ret := _m.Called(userID) diff --git a/server/channels/store/storetest/mocks/ThreadStore.go b/server/channels/store/storetest/mocks/ThreadStore.go index 72d9b6206d..54b0d092e3 100644 --- a/server/channels/store/storetest/mocks/ThreadStore.go +++ b/server/channels/store/storetest/mocks/ThreadStore.go @@ -273,58 +273,6 @@ func (_m *ThreadStore) GetThreadsForUser(userId string, teamID string, opts mode return r0, r1 } -// GetTopThreadsForTeamSince provides a mock function with given fields: teamID, userID, since, offset, limit -func (_m *ThreadStore) GetTopThreadsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopThreadList, error) { - ret := _m.Called(teamID, userID, since, offset, limit) - - var r0 *model.TopThreadList - var r1 error - if rf, ok := ret.Get(0).(func(string, string, int64, int, int) (*model.TopThreadList, error)); ok { - return rf(teamID, userID, since, offset, limit) - } - if rf, ok := ret.Get(0).(func(string, string, int64, int, int) *model.TopThreadList); ok { - r0 = rf(teamID, userID, since, offset, limit) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*model.TopThreadList) - } - } - - if rf, ok := ret.Get(1).(func(string, string, int64, int, int) error); ok { - r1 = rf(teamID, userID, since, offset, limit) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetTopThreadsForUserSince provides a mock function with given fields: teamID, userID, since, offset, limit -func (_m *ThreadStore) GetTopThreadsForUserSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopThreadList, error) { - ret := _m.Called(teamID, userID, since, offset, limit) - - var r0 *model.TopThreadList - var r1 error - if rf, ok := ret.Get(0).(func(string, string, int64, int, int) (*model.TopThreadList, error)); ok { - return rf(teamID, userID, since, offset, limit) - } - if rf, ok := ret.Get(0).(func(string, string, int64, int, int) *model.TopThreadList); ok { - r0 = rf(teamID, userID, since, offset, limit) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*model.TopThreadList) - } - } - - if rf, ok := ret.Get(1).(func(string, string, int64, int, int) error); ok { - r1 = rf(teamID, userID, since, offset, limit) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // GetTotalThreads provides a mock function with given fields: userId, teamID, opts func (_m *ThreadStore) GetTotalThreads(userId string, teamID string, opts model.GetUserThreadsOpts) (int64, error) { ret := _m.Called(userId, teamID, opts) diff --git a/server/channels/store/storetest/post_store.go b/server/channels/store/storetest/post_store.go index ad0001a197..5e0324189a 100644 --- a/server/channels/store/storetest/post_store.go +++ b/server/channels/store/storetest/post_store.go @@ -62,7 +62,6 @@ func TestPostStore(t *testing.T, ss store.Store, s SqlStore) { t.Run("GetPostReminders", func(t *testing.T) { testGetPostReminders(t, ss, s) }) t.Run("GetPostReminderMetadata", func(t *testing.T) { testGetPostReminderMetadata(t, ss, s) }) t.Run("GetNthRecentPostTime", func(t *testing.T) { testGetNthRecentPostTime(t, ss) }) - t.Run("GetTopDMsForUserSince", func(t *testing.T) { testGetTopDMsForUserSince(t, ss, s) }) t.Run("GetEditHistoryForPost", func(t *testing.T) { testGetEditHistoryForPost(t, ss) }) } @@ -4862,180 +4861,6 @@ func testGetNthRecentPostTime(t *testing.T, ss store.Store) { assert.IsType(t, &store.ErrNotFound{}, err) } -func testGetTopDMsForUserSince(t *testing.T, ss store.Store, s SqlStore) { - // users - user := model.User{Email: MakeEmail(), Username: model.NewId()} - u1 := model.User{Email: MakeEmail(), Username: model.NewId()} - u2 := model.User{Email: MakeEmail(), Username: model.NewId()} - u3 := model.User{Email: MakeEmail(), Username: model.NewId()} - u4 := model.User{Email: MakeEmail(), Username: model.NewId()} - u5 := model.User{Email: MakeEmail(), Username: model.NewId()} - - _, err := ss.User().Save(&user) - require.NoError(t, err) - _, err = ss.User().Save(&u1) - require.NoError(t, err) - _, err = ss.User().Save(&u2) - require.NoError(t, err) - _, err = ss.User().Save(&u3) - require.NoError(t, err) - _, err = ss.User().Save(&u4) - require.NoError(t, err) - _, err = ss.User().Save(&u5) - require.NoError(t, err) - bot := &model.Bot{ - Username: "bot_user", - Description: "bot", - OwnerId: model.NewId(), - UserId: u5.Id, - } - - savedBot, nErr := ss.Bot().Save(bot) - require.NoError(t, nErr) - // user direct messages - chUser1, nErr := ss.Channel().CreateDirectChannel(&u1, &user) - require.NoError(t, nErr) - chUser2, nErr := ss.Channel().CreateDirectChannel(&u2, &user) - require.NoError(t, nErr) - chUser3, nErr := ss.Channel().CreateDirectChannel(&u3, &user) - require.NoError(t, nErr) - // other user direct message - chUser3User4, nErr := ss.Channel().CreateDirectChannel(&u3, &u4) - require.NoError(t, nErr) - - // bot direct message - should be ignored by top DMs - botUser, err := ss.User().Get(context.Background(), savedBot.UserId) - require.NoError(t, err) - chBot, nErr := ss.Channel().CreateDirectChannel(&user, botUser) - require.NoError(t, nErr) - _, err = ss.Post().Save(&model.Post{ - ChannelId: chBot.Id, - UserId: botUser.Id, - }) - require.NoError(t, err) - - // sample post data - // for u1 - _, err = ss.Post().Save(&model.Post{ - ChannelId: chUser1.Id, - UserId: u1.Id, - }) - require.NoError(t, err) - _, err = ss.Post().Save(&model.Post{ - ChannelId: chUser1.Id, - UserId: user.Id, - }) - require.NoError(t, err) - // for u2: 1 post - postToDelete, err := ss.Post().Save(&model.Post{ - ChannelId: chUser2.Id, - UserId: u2.Id, - }) - require.NoError(t, err) - // create second post for u2: modify create at to a very old date to make sure it isn't counted - _, err = ss.Post().Save(&model.Post{ - ChannelId: chUser2.Id, - UserId: u2.Id, - CreateAt: 100, - }) - require.NoError(t, err) - // for user-u3: 3 posts - for i := 0; i < 3; i++ { - _, err = ss.Post().Save(&model.Post{ - ChannelId: chUser3.Id, - UserId: user.Id, - }) - require.NoError(t, err) - } - // for u4-u3: 4 posts - u3u4Post1, err := ss.Post().Save(&model.Post{ - ChannelId: chUser3User4.Id, - UserId: u3.Id, - }) - require.NoError(t, err) - _, err = ss.Post().Save(&model.Post{ - ChannelId: chUser3User4.Id, - UserId: u4.Id, - }) - require.NoError(t, err) - u3u4Post2, err := ss.Post().Save(&model.Post{ - ChannelId: chUser3User4.Id, - UserId: u3.Id, - }) - require.NoError(t, err) - - _, err = ss.Post().Save(&model.Post{ - ChannelId: chUser3User4.Id, - UserId: u4.Id, - }) - require.NoError(t, err) - t.Run("should return topDMs when userid is specified ", func(t *testing.T) { - topDMs, storeErr := ss.Post().GetTopDMsForUserSince(user.Id, 100, 0, 100) - require.NoError(t, storeErr) - // len of topDMs.Items should be 3 - require.Len(t, topDMs.Items, 3) - // check order, magnitude of items - require.Equal(t, topDMs.Items[0].SecondParticipant.Id, u3.Id) - require.Equal(t, topDMs.Items[0].MessageCount, int64(3)) - require.Equal(t, topDMs.Items[0].OutgoingMessageCount, int64(3)) - require.Equal(t, topDMs.Items[1].SecondParticipant.Id, u1.Id) - require.Equal(t, topDMs.Items[1].MessageCount, int64(2)) - require.Equal(t, topDMs.Items[1].OutgoingMessageCount, int64(1)) - require.Equal(t, topDMs.Items[2].SecondParticipant.Id, u2.Id) - require.Equal(t, topDMs.Items[2].MessageCount, int64(1)) - require.Equal(t, topDMs.Items[2].OutgoingMessageCount, int64(0)) - // this also ensures that u3-u4 conversation doesn't show up in others' top DMs. - }) - t.Run("topDMs should only consider user's DM channels ", func(t *testing.T) { - // u4 only takes part in one conversation - topDMs, storeErr := ss.Post().GetTopDMsForUserSince(u4.Id, 100, 0, 100) - require.NoError(t, storeErr) - // len of topDMs.Items should be 3 - require.Len(t, topDMs.Items, 1) - // check order, magnitude of items - require.Equal(t, topDMs.Items[0].SecondParticipant.Id, u3.Id) - require.Equal(t, topDMs.Items[0].MessageCount, int64(4)) - }) - t.Run("topDMs will not consider self dms", func(t *testing.T) { - chUser, nErr := ss.Channel().CreateDirectChannel(&user, &user) - require.NoError(t, nErr) - _, err = ss.Post().Save(&model.Post{ - ChannelId: chUser.Id, - UserId: user.Id, - }) - // delete u2 post - err := ss.Post().Delete(postToDelete.Id, 200, user.Id) - require.NoError(t, err) - // u4 only takes part in one conversation - topDMs, err := ss.Post().GetTopDMsForUserSince(user.Id, 100, 0, 100) - require.NoError(t, err) - // len of topDMs.Items should be 3 - require.Len(t, topDMs.Items, 2) - }) - t.Run("topDMs will not consider deleted second user", func(t *testing.T) { - // u4 only takes part in one conversation - topDMs, err := ss.Post().GetTopDMsForUserSince(u4.Id, 100, 0, 100) - require.NoError(t, err) - // len of topDMs.Items should be 1 - require.Len(t, topDMs.Items, 1) - // delete user3 - err = ss.User().PermanentDelete(u3.Id) - require.NoError(t, err) - // delete user3 posts - err = ss.Post().Delete(u3u4Post1.Id, 200, u3.Id) - require.NoError(t, err) - err = ss.Post().Delete(u3u4Post2.Id, 200, u3.Id) - require.NoError(t, err) - // delete channel memberships - err = ss.Channel().PermanentDeleteMembersByUser(u3.Id) - require.NoError(t, err) - topDMs, err = ss.Post().GetTopDMsForUserSince(u4.Id, 100, 0, 100) - require.NoError(t, err) - // len of topDMs.Items should be 0 since u3 is deleted - require.Len(t, topDMs.Items, 0) - }) -} - func testGetEditHistoryForPost(t *testing.T, ss store.Store) { t.Run("should return edit history for post", func(t *testing.T) { // create a post diff --git a/server/channels/store/storetest/team_store.go b/server/channels/store/storetest/team_store.go index 79423fa79a..ed706c1e9b 100644 --- a/server/channels/store/storetest/team_store.go +++ b/server/channels/store/storetest/team_store.go @@ -73,7 +73,6 @@ func TestTeamStore(t *testing.T, ss store.Store) { t.Run("GetTeamMembersForExport", func(t *testing.T) { testTeamStoreGetTeamMembersForExport(t, ss) }) t.Run("GetTeamsForUserWithPagination", func(t *testing.T) { testTeamMembersWithPagination(t, ss) }) t.Run("GroupSyncedTeamCount", func(t *testing.T) { testGroupSyncedTeamCount(t, ss) }) - t.Run("GetNewTeamMembersSince", func(t *testing.T) { testGetNewTeamMembersSince(t, ss) }) } func testTeamStoreSave(t *testing.T, ss store.Store) { @@ -3619,17 +3618,3 @@ func testGroupSyncedTeamCount(t *testing.T, ss store.Store) { require.NoError(t, err) require.GreaterOrEqual(t, countAfter, count+1) } - -func testGetNewTeamMembersSince(t *testing.T, ss store.Store) { - team, err := ss.Team().Save(&model.Team{ - DisplayName: NewTestId(), - Name: NewTestId(), - Email: MakeEmail(), - Type: model.TeamInvite, - GroupConstrained: model.NewBool(true), - }) - require.NoError(t, err) - - _, _, err = ss.Team().GetNewTeamMembersSince(team.Id, 0, 0, 1000, false) - require.NoError(t, err) -} diff --git a/server/channels/store/storetest/thread_store.go b/server/channels/store/storetest/thread_store.go index e09ab99cc3..3845df0aa7 100644 --- a/server/channels/store/storetest/thread_store.go +++ b/server/channels/store/storetest/thread_store.go @@ -27,7 +27,6 @@ func TestThreadStore(t *testing.T, ss store.Store, s SqlStore) { t.Run("GetTeamsUnreadForUser", func(t *testing.T) { testGetTeamsUnreadForUser(t, ss) }) t.Run("GetVarious", func(t *testing.T) { testVarious(t, ss) }) t.Run("MarkAllAsReadByChannels", func(t *testing.T) { testMarkAllAsReadByChannels(t, ss) }) - t.Run("GetTopThreads", func(t *testing.T) { testGetTopThreads(t, ss) }) t.Run("MarkAllAsReadByTeam", func(t *testing.T) { testMarkAllAsReadByTeam(t, ss) }) t.Run("DeleteMembershipsForChannel", func(t *testing.T) { testDeleteMembershipsForChannel(t, ss) }) } @@ -1349,347 +1348,6 @@ func testMarkAllAsReadByChannels(t *testing.T, ss store.Store) { }) } -func testGetTopThreads(t *testing.T, ss store.Store) { - // create two users - u1 := model.User{ - Email: MakeEmail(), - Username: model.NewId(), - } - - _, err := ss.User().Save(&u1) - require.NoError(t, err) - - u2 := model.User{ - Email: MakeEmail(), - Username: model.NewId(), - } - - _, err = ss.User().Save(&u2) - require.NoError(t, err) - - u3 := model.User{ - Email: MakeEmail(), - Username: model.NewId(), - } - - _, err = ss.User().Save(&u3) - require.NoError(t, err) - - t.Run("test get top team threads", func(t *testing.T) { - const limit = 10 - team, err := ss.Team().Save(&model.Team{ - DisplayName: "DisplayName", - Name: "team" + model.NewId(), - Email: MakeEmail(), - Type: model.TeamOpen, - }) - require.NoError(t, err) - channel, err := ss.Channel().Save(&model.Channel{ - TeamId: team.Id, - DisplayName: "DisplayName", - Name: "channel" + model.NewId(), - Type: model.ChannelTypeOpen, - }, -1) - require.NoError(t, err) - - post1, err := ss.Post().Save(&model.Post{ - ChannelId: channel.Id, - UserId: u1.Id, - }) - require.NoError(t, err) - post2, err := ss.Post().Save(&model.Post{ - ChannelId: channel.Id, - UserId: u2.Id, - }) - require.NoError(t, err) - threadStoreCreateReply(t, ss, channel.Id, post1.Id, post1.UserId, 2000) - threadStoreCreateReply(t, ss, channel.Id, post1.Id, post1.UserId, 2000) - - threadStoreCreateReply(t, ss, channel.Id, post2.Id, post1.UserId, 2000) - - // get top threads - topThreadsInTeam, err := ss.Thread().GetTopThreadsForTeamSince(team.Id, model.NewId(), 12, 0, limit) - require.NoError(t, err) - // require length of top threads to be 2 - require.Len(t, topThreadsInTeam.Items, 2) - - // require first element to be post1 with 2 replyCount=2 - require.Equal(t, topThreadsInTeam.Items[0].PostId, post1.Id) - require.Equal(t, topThreadsInTeam.Items[0].UserId, post1.UserId) - require.Equal(t, topThreadsInTeam.Items[0].UserInformation.Id, post1.UserId) - require.Equal(t, topThreadsInTeam.Items[0].Post.ReplyCount, int64(2)) - require.Equal(t, topThreadsInTeam.Items[0].Post.Message, post1.Message) - // require second element to be post2 with 2 replyCount=2 - require.Equal(t, topThreadsInTeam.Items[1].PostId, post2.Id) - require.Equal(t, topThreadsInTeam.Items[1].Post.ReplyCount, int64(1)) - require.Equal(t, topThreadsInTeam.Items[1].UserId, post2.UserId) - require.Equal(t, topThreadsInTeam.Items[1].UserInformation.Id, post2.UserId) - require.Equal(t, topThreadsInTeam.Items[1].Post.Message, post2.Message) - - // require topThreads[i].Post is not null - require.Equal(t, topThreadsInTeam.Items[0].Post.Id, post1.Id) - require.Equal(t, topThreadsInTeam.Items[1].Post.Id, post2.Id) - }) - t.Run("test get top user threads", func(t *testing.T) { - const limit = 10 - team, err := ss.Team().Save(&model.Team{ - DisplayName: "DisplayName", - Name: "team" + model.NewId(), - Email: MakeEmail(), - Type: model.TeamOpen, - }) - require.NoError(t, err) - channel, err := ss.Channel().Save(&model.Channel{ - TeamId: team.Id, - DisplayName: "DisplayName", - Name: "channel" + model.NewId(), - Type: model.ChannelTypeOpen, - }, -1) - require.NoError(t, err) - - post1, err := ss.Post().Save(&model.Post{ - ChannelId: channel.Id, - UserId: u1.Id, - }) - require.NoError(t, err) - post2, err := ss.Post().Save(&model.Post{ - ChannelId: channel.Id, - UserId: u2.Id, - }) - require.NoError(t, err) - post3, err := ss.Post().Save(&model.Post{ - ChannelId: channel.Id, - UserId: u3.Id, - }) - require.NoError(t, err) - threadStoreCreateReply(t, ss, channel.Id, post1.Id, post1.UserId, 2000) - threadStoreCreateReply(t, ss, channel.Id, post1.Id, post1.UserId, 2000) - - threadStoreCreateReply(t, ss, channel.Id, post2.Id, post2.UserId, 2000) - threadStoreCreateReply(t, ss, channel.Id, post2.Id, post2.UserId, 2000) - threadStoreCreateReply(t, ss, channel.Id, post3.Id, post3.UserId, 2000) - opts := store.ThreadMembershipOpts{ - Following: true, - IncrementMentions: false, - UpdateFollowing: true, - UpdateViewedTimestamp: false, - UpdateParticipants: false, - } - - // create threadmemberships entries. - _, err = ss.Thread().MaintainMembership(post1.UserId, post1.Id, opts) - require.NoError(t, err) - _, err = ss.Thread().MaintainMembership(post2.UserId, post2.Id, opts) - require.NoError(t, err) - _, err = ss.Thread().MaintainMembership(post2.UserId, post3.Id, opts) - require.NoError(t, err) - - // get top threads by user - topThreadsByUser1, err := ss.Thread().GetTopThreadsForUserSince(team.Id, post1.UserId, 12, 0, limit) - require.NoError(t, err) - topThreadsByUser2, err := ss.Thread().GetTopThreadsForUserSince(team.Id, post2.UserId, 12, 0, limit) - require.NoError(t, err) - // require length of top threads by users to be 1,2 respectively - require.Len(t, topThreadsByUser1.Items, 1) - require.Len(t, topThreadsByUser2.Items, 2) - - // require first element of topThreadsByUser1 to be post1 with 2 replyCount=2 - require.Equal(t, topThreadsByUser1.Items[0].PostId, post1.Id) - require.Equal(t, topThreadsByUser1.Items[0].Post.ReplyCount, int64(2)) - require.Equal(t, topThreadsByUser1.Items[0].Post.Message, post1.Message) - require.Equal(t, topThreadsByUser1.Items[0].UserId, post1.UserId) - require.Equal(t, topThreadsByUser1.Items[0].UserInformation.Id, post1.UserId) - // require elements of topThreadsByUser2 to be post2 and post3 respectively - require.Equal(t, topThreadsByUser2.Items[0].PostId, post2.Id) - require.Equal(t, topThreadsByUser2.Items[0].Post.ReplyCount, int64(2)) - require.Equal(t, topThreadsByUser2.Items[0].Post.Message, post2.Message) - require.Equal(t, topThreadsByUser2.Items[0].UserId, post2.UserId) - require.Equal(t, topThreadsByUser2.Items[0].UserInformation.Id, post2.UserId) - - require.Equal(t, topThreadsByUser2.Items[1].PostId, post3.Id) - require.Equal(t, topThreadsByUser2.Items[1].Post.ReplyCount, int64(1)) - require.Equal(t, topThreadsByUser2.Items[1].Post.Message, post3.Message) - require.Equal(t, topThreadsByUser2.Items[1].UserId, post3.UserId) - require.Equal(t, topThreadsByUser2.Items[1].UserInformation.Id, post3.UserId) - - // require topThreads[i].Post is not null - require.Equal(t, topThreadsByUser1.Items[0].Post.Id, post1.Id) - require.Equal(t, topThreadsByUser2.Items[1].Post.Id, post3.Id) - }) - t.Run("test get top threads only from given teamid", func(t *testing.T) { - const limit = 10 - team1, err := ss.Team().Save(&model.Team{ - DisplayName: "DisplayName", - Name: "team" + model.NewId(), - Email: MakeEmail(), - Type: model.TeamOpen, - }) - require.NoError(t, err) - team2, err := ss.Team().Save(&model.Team{ - DisplayName: "DisplayName", - Name: "team" + model.NewId(), - Email: MakeEmail(), - Type: model.TeamOpen, - }) - require.NoError(t, err) - channel1, err := ss.Channel().Save(&model.Channel{ - TeamId: team1.Id, - DisplayName: "DisplayName", - Name: "channel" + model.NewId(), - Type: model.ChannelTypeOpen, - }, -1) - require.NoError(t, err) - - channel2, err := ss.Channel().Save(&model.Channel{ - TeamId: team2.Id, - DisplayName: "DisplayName", - Name: "channel" + model.NewId(), - Type: model.ChannelTypeOpen, - }, -1) - require.NoError(t, err) - - post1, err := ss.Post().Save(&model.Post{ - ChannelId: channel1.Id, - UserId: u1.Id, - }) - require.NoError(t, err) - post2, err := ss.Post().Save(&model.Post{ - ChannelId: channel2.Id, - UserId: u2.Id, - }) - require.NoError(t, err) - threadStoreCreateReply(t, ss, channel1.Id, post1.Id, post1.UserId, 2000) - threadStoreCreateReply(t, ss, channel1.Id, post1.Id, post1.UserId, 2000) - - threadStoreCreateReply(t, ss, channel2.Id, post2.Id, post2.UserId, 2000) - - // assert that getting top threads from teamid 1 doesn't have post1.Id - - topThreadsTeam2, err := ss.Thread().GetTopThreadsForTeamSince(team2.Id, u1.Id, 12, 0, limit) - require.NoError(t, err) - require.Len(t, topThreadsTeam2.Items, 1) - require.Equal(t, topThreadsTeam2.Items[0].Post.Id, post2.Id) - }) - t.Run("test get top threads only from non-direct channels", func(t *testing.T) { - const limit = 10 - team1, err := ss.Team().Save(&model.Team{ - DisplayName: "DisplayName", - Name: "team" + model.NewId(), - Email: MakeEmail(), - Type: model.TeamOpen, - }) - require.NoError(t, err) - channel1, err := ss.Channel().CreateDirectChannel(&u1, &u2) - require.NoError(t, err) - - channel2, err := ss.Channel().Save(&model.Channel{ - TeamId: team1.Id, - DisplayName: "DisplayName", - Name: "channel" + model.NewId(), - Type: model.ChannelTypeOpen, - }, -1) - require.NoError(t, err) - - post1, err := ss.Post().Save(&model.Post{ - ChannelId: channel1.Id, - UserId: u1.Id, - }) - require.NoError(t, err) - post2, err := ss.Post().Save(&model.Post{ - ChannelId: channel2.Id, - UserId: u2.Id, - }) - require.NoError(t, err) - threadStoreCreateReply(t, ss, channel1.Id, post1.Id, post1.UserId, 2000) - threadStoreCreateReply(t, ss, channel1.Id, post1.Id, post1.UserId, 2000) - - threadStoreCreateReply(t, ss, channel2.Id, post2.Id, u1.Id, 2000) - - opts := store.ThreadMembershipOpts{ - Following: true, - IncrementMentions: false, - UpdateFollowing: true, - UpdateViewedTimestamp: false, - UpdateParticipants: false, - } - - // create threadmemberships entries. - _, err = ss.Thread().MaintainMembership(u1.Id, post1.Id, opts) - require.NoError(t, err) - _, err = ss.Thread().MaintainMembership(u1.Id, post2.Id, opts) - require.NoError(t, err) - _, err = ss.Thread().MaintainMembership(u2.Id, post1.Id, opts) - require.NoError(t, err) - _, err = ss.Thread().MaintainMembership(u2.Id, post2.Id, opts) - require.NoError(t, err) - - // assert that getting top threads from teamid 1 doesn't have DMs - - topThreadsTeam1, err := ss.Thread().GetTopThreadsForTeamSince(team1.Id, u1.Id, 12, 0, limit) - require.NoError(t, err) - require.Len(t, topThreadsTeam1.Items, 1) - require.Equal(t, topThreadsTeam1.Items[0].Post.Id, post2.Id) - - // assert that getting top threads from user 1 doesn't contain dm threads. - topUserThreads, err := ss.Thread().GetTopThreadsForUserSince(team1.Id, u1.Id, 12, 0, limit) - require.NoError(t, err) - require.Len(t, topUserThreads.Items, 1) - require.Equal(t, topUserThreads.Items[0].Post.Id, post2.Id) - }) - t.Run("test get top threads doesn't exceed duration", func(t *testing.T) { - const limit = 10 - team, err := ss.Team().Save(&model.Team{ - DisplayName: "DisplayName", - Name: "team" + model.NewId(), - Email: MakeEmail(), - Type: model.TeamOpen, - }) - require.NoError(t, err) - channel, err := ss.Channel().Save(&model.Channel{ - TeamId: team.Id, - DisplayName: "DisplayName", - Name: "channel" + model.NewId(), - Type: model.ChannelTypeOpen, - }, -1) - require.NoError(t, err) - - post1, err := ss.Post().Save(&model.Post{ - ChannelId: channel.Id, - UserId: u1.Id, - }) - require.NoError(t, err) - // post 2 has replies after 10 ms unix time. - post2, err := ss.Post().Save(&model.Post{ - ChannelId: channel.Id, - UserId: u2.Id, - CreateAt: 1, - }) - require.NoError(t, err) - threadStoreCreateReply(t, ss, channel.Id, post1.Id, post1.UserId, 2000) - threadStoreCreateReply(t, ss, channel.Id, post1.Id, post1.UserId, 2000) - - threadStoreCreateReply(t, ss, channel.Id, post2.Id, post1.UserId, 10) - - // get top threads - topThreadsInTeamNewer, err := ss.Thread().GetTopThreadsForTeamSince(team.Id, model.NewId(), 12, 0, limit) - require.NoError(t, err) - // require length of top threads to be 2 - require.Len(t, topThreadsInTeamNewer.Items, 1) - - // require first element to be post1 with 2 replyCount=2 - require.Equal(t, topThreadsInTeamNewer.Items[0].PostId, post1.Id) - - // get top threads - topThreadsInTeamOlder, err := ss.Thread().GetTopThreadsForTeamSince(team.Id, model.NewId(), 9, 0, limit) - require.NoError(t, err) - // require length of top threads to be 2 - require.Len(t, topThreadsInTeamOlder.Items, 2) - - // require first element to be post1 with 2 replyCount=2 - require.Equal(t, topThreadsInTeamOlder.Items[1].PostId, post2.Id) - }) -} - func testMarkAllAsReadByTeam(t *testing.T, ss store.Store) { createThreadMembership := func(userID, postID string) { t.Helper() diff --git a/server/channels/store/timerlayer/timerlayer.go b/server/channels/store/timerlayer/timerlayer.go index 21e6655a83..6b08162fe8 100644 --- a/server/channels/store/timerlayer/timerlayer.go +++ b/server/channels/store/timerlayer/timerlayer.go @@ -1728,70 +1728,6 @@ func (s *TimerLayerChannelStore) GetTeamMembersForChannel(channelID string) ([]s return result, err } -func (s *TimerLayerChannelStore) GetTopChannelsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopChannelList, error) { - start := time.Now() - - result, err := s.ChannelStore.GetTopChannelsForTeamSince(teamID, userID, since, offset, limit) - - elapsed := float64(time.Since(start)) / float64(time.Second) - if s.Root.Metrics != nil { - success := "false" - if err == nil { - success = "true" - } - s.Root.Metrics.ObserveStoreMethodDuration("ChannelStore.GetTopChannelsForTeamSince", success, elapsed) - } - return result, err -} - -func (s *TimerLayerChannelStore) GetTopChannelsForUserSince(userID string, teamID string, since int64, offset int, limit int) (*model.TopChannelList, error) { - start := time.Now() - - result, err := s.ChannelStore.GetTopChannelsForUserSince(userID, teamID, since, offset, limit) - - elapsed := float64(time.Since(start)) / float64(time.Second) - if s.Root.Metrics != nil { - success := "false" - if err == nil { - success = "true" - } - s.Root.Metrics.ObserveStoreMethodDuration("ChannelStore.GetTopChannelsForUserSince", success, elapsed) - } - return result, err -} - -func (s *TimerLayerChannelStore) GetTopInactiveChannelsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopInactiveChannelList, error) { - start := time.Now() - - result, err := s.ChannelStore.GetTopInactiveChannelsForTeamSince(teamID, userID, since, offset, limit) - - elapsed := float64(time.Since(start)) / float64(time.Second) - if s.Root.Metrics != nil { - success := "false" - if err == nil { - success = "true" - } - s.Root.Metrics.ObserveStoreMethodDuration("ChannelStore.GetTopInactiveChannelsForTeamSince", success, elapsed) - } - return result, err -} - -func (s *TimerLayerChannelStore) GetTopInactiveChannelsForUserSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopInactiveChannelList, error) { - start := time.Now() - - result, err := s.ChannelStore.GetTopInactiveChannelsForUserSince(teamID, userID, since, offset, limit) - - elapsed := float64(time.Since(start)) / float64(time.Second) - if s.Root.Metrics != nil { - success := "false" - if err == nil { - success = "true" - } - s.Root.Metrics.ObserveStoreMethodDuration("ChannelStore.GetTopInactiveChannelsForUserSince", success, elapsed) - } - return result, err -} - func (s *TimerLayerChannelStore) GroupSyncedChannelCount() (int64, error) { start := time.Now() @@ -2025,22 +1961,6 @@ func (s *TimerLayerChannelStore) PermanentDeleteMembersByUser(userID string) err return err } -func (s *TimerLayerChannelStore) PostCountsByDuration(channelIDs []string, sinceUnixMillis int64, userID *string, duration model.PostCountGrouping, groupingLocation *time.Location) ([]*model.DurationPostCount, error) { - start := time.Now() - - result, err := s.ChannelStore.PostCountsByDuration(channelIDs, sinceUnixMillis, userID, duration, groupingLocation) - - elapsed := float64(time.Since(start)) / float64(time.Second) - if s.Root.Metrics != nil { - success := "false" - if err == nil { - success = "true" - } - s.Root.Metrics.ObserveStoreMethodDuration("ChannelStore.PostCountsByDuration", success, elapsed) - } - return result, err -} - func (s *TimerLayerChannelStore) RemoveAllDeactivatedMembers(channelID string) error { start := time.Now() @@ -5845,22 +5765,6 @@ func (s *TimerLayerPostStore) GetSingle(id string, inclDeleted bool) (*model.Pos return result, err } -func (s *TimerLayerPostStore) GetTopDMsForUserSince(userID string, since int64, offset int, limit int) (*model.TopDMList, error) { - start := time.Now() - - result, err := s.PostStore.GetTopDMsForUserSince(userID, since, offset, limit) - - elapsed := float64(time.Since(start)) / float64(time.Second) - if s.Root.Metrics != nil { - success := "false" - if err == nil { - success = "true" - } - s.Root.Metrics.ObserveStoreMethodDuration("PostStore.GetTopDMsForUserSince", success, elapsed) - } - return result, err -} - func (s *TimerLayerPostStore) HasAutoResponsePostByUserSince(options model.GetPostsSinceOptions, userId string) (bool, error) { start := time.Now() @@ -6644,38 +6548,6 @@ func (s *TimerLayerReactionStore) GetForPostSince(postId string, since int64, ex return result, err } -func (s *TimerLayerReactionStore) GetTopForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopReactionList, error) { - start := time.Now() - - result, err := s.ReactionStore.GetTopForTeamSince(teamID, userID, since, offset, limit) - - elapsed := float64(time.Since(start)) / float64(time.Second) - if s.Root.Metrics != nil { - success := "false" - if err == nil { - success = "true" - } - s.Root.Metrics.ObserveStoreMethodDuration("ReactionStore.GetTopForTeamSince", success, elapsed) - } - return result, err -} - -func (s *TimerLayerReactionStore) GetTopForUserSince(userID string, teamID string, since int64, offset int, limit int) (*model.TopReactionList, error) { - start := time.Now() - - result, err := s.ReactionStore.GetTopForUserSince(userID, teamID, since, offset, limit) - - elapsed := float64(time.Since(start)) / float64(time.Second) - if s.Root.Metrics != nil { - success := "false" - if err == nil { - success = "true" - } - s.Root.Metrics.ObserveStoreMethodDuration("ReactionStore.GetTopForUserSince", success, elapsed) - } - return result, err -} - func (s *TimerLayerReactionStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) { start := time.Now() @@ -8675,22 +8547,6 @@ func (s *TimerLayerTeamStore) GetMembersByIds(teamID string, userIds []string, r return result, err } -func (s *TimerLayerTeamStore) GetNewTeamMembersSince(teamID string, since int64, offset int, limit int, showFullName bool) (*model.NewTeamMembersList, int64, error) { - start := time.Now() - - result, resultVar1, err := s.TeamStore.GetNewTeamMembersSince(teamID, since, offset, limit, showFullName) - - elapsed := float64(time.Since(start)) / float64(time.Second) - if s.Root.Metrics != nil { - success := "false" - if err == nil { - success = "true" - } - s.Root.Metrics.ObserveStoreMethodDuration("TeamStore.GetNewTeamMembersSince", success, elapsed) - } - return result, resultVar1, err -} - func (s *TimerLayerTeamStore) GetTeamMembersForExport(userID string) ([]*model.TeamMemberForExport, error) { start := time.Now() @@ -9378,38 +9234,6 @@ func (s *TimerLayerThreadStore) GetThreadsForUser(userId string, teamID string, return result, err } -func (s *TimerLayerThreadStore) GetTopThreadsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopThreadList, error) { - start := time.Now() - - result, err := s.ThreadStore.GetTopThreadsForTeamSince(teamID, userID, since, offset, limit) - - elapsed := float64(time.Since(start)) / float64(time.Second) - if s.Root.Metrics != nil { - success := "false" - if err == nil { - success = "true" - } - s.Root.Metrics.ObserveStoreMethodDuration("ThreadStore.GetTopThreadsForTeamSince", success, elapsed) - } - return result, err -} - -func (s *TimerLayerThreadStore) GetTopThreadsForUserSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopThreadList, error) { - start := time.Now() - - result, err := s.ThreadStore.GetTopThreadsForUserSince(teamID, userID, since, offset, limit) - - elapsed := float64(time.Since(start)) / float64(time.Second) - if s.Root.Metrics != nil { - success := "false" - if err == nil { - success = "true" - } - s.Root.Metrics.ObserveStoreMethodDuration("ThreadStore.GetTopThreadsForUserSince", success, elapsed) - } - return result, err -} - func (s *TimerLayerThreadStore) GetTotalThreads(userId string, teamID string, opts model.GetUserThreadsOpts) (int64, error) { start := time.Now() diff --git a/server/config/client.go b/server/config/client.go index 9654647fc5..50f9c048a3 100644 --- a/server/config/client.go +++ b/server/config/client.go @@ -134,7 +134,6 @@ func GenerateClientConfig(c *model.Config, telemetryID string, license *model.Li props["ExperimentalSharedChannels"] = "false" props["CollapsedThreads"] = *c.ServiceSettings.CollapsedThreads props["EnableCustomGroups"] = "false" - props["InsightsEnabled"] = strconv.FormatBool(c.FeatureFlags.InsightsEnabled) props["PostPriority"] = strconv.FormatBool(*c.ServiceSettings.PostPriority) props["AllowPersistentNotifications"] = strconv.FormatBool(*c.ServiceSettings.AllowPersistentNotifications) props["AllowPersistentNotificationsForGuests"] = strconv.FormatBool(*c.ServiceSettings.AllowPersistentNotificationsForGuests) diff --git a/server/config/client_test.go b/server/config/client_test.go index 2405d6281a..7c1f7212bf 100644 --- a/server/config/client_test.go +++ b/server/config/client_test.go @@ -150,70 +150,6 @@ func TestGetClientConfig(t *testing.T) { "ShowFullName": "true", }, }, - { - "Insights professional license", - &model.Config{ - FeatureFlags: &model.FeatureFlags{ - InsightsEnabled: true, - }, - }, - "", - &model.License{ - Features: &model.Features{}, - SkuShortName: model.LicenseShortSkuProfessional, - }, - map[string]string{ - "InsightsEnabled": "true", - }, - }, - { - "Insights enterprise license", - &model.Config{ - FeatureFlags: &model.FeatureFlags{ - InsightsEnabled: true, - }, - }, - "", - &model.License{ - Features: &model.Features{}, - SkuShortName: model.LicenseShortSkuEnterprise, - }, - map[string]string{ - "InsightsEnabled": "true", - }, - }, - { - "Insights other license", - &model.Config{ - FeatureFlags: &model.FeatureFlags{ - InsightsEnabled: true, - }, - }, - "", - &model.License{ - Features: &model.Features{}, - SkuShortName: "other", - }, - map[string]string{ - "InsightsEnabled": "true", - }, - }, - { - "Insights professional license, feature flag disabled", - &model.Config{ - FeatureFlags: &model.FeatureFlags{ - InsightsEnabled: false, - }, - }, - "", - &model.License{ - Features: &model.Features{}, - SkuShortName: model.LicenseShortSkuProfessional, - }, - map[string]string{ - "InsightsEnabled": "false", - }, - }, { "Custom groups professional license", &model.Config{}, @@ -240,11 +176,7 @@ func TestGetClientConfig(t *testing.T) { }, { "Custom groups other license", - &model.Config{ - FeatureFlags: &model.FeatureFlags{ - InsightsEnabled: true, - }, - }, + &model.Config{}, "", &model.License{ Features: &model.Features{}, diff --git a/server/i18n/de.json b/server/i18n/de.json index e8334349f6..73a679d0e1 100644 --- a/server/i18n/de.json +++ b/server/i18n/de.json @@ -9466,10 +9466,6 @@ "id": "api.team.invite_guests_to_channels.disabled.error", "translation": "Gastkonten sind deaktiviert" }, - { - "id": "model.insights.get_start_of_day_for_time_range.time_range.app_error", - "translation": "Ungültiger Zeitbereich." - }, { "id": "app.collection.add_topic.exists.app_error", "translation": "Thementyp existiert schon." diff --git a/server/i18n/en.json b/server/i18n/en.json index 451fed068a..afcf77a824 100644 --- a/server/i18n/en.json +++ b/server/i18n/en.json @@ -1977,10 +1977,6 @@ "id": "api.incoming_webhook.invalid_username.app_error", "translation": "Invalid username." }, - { - "id": "api.insights.feature_disabled", - "translation": "Insights is behind a feature flag which is not enabled." - }, { "id": "api.invalid_channel", "translation": "Channel listed in the request doesn't belong to the user" @@ -5831,10 +5827,6 @@ "id": "app.insert_error", "translation": "insert error" }, - { - "id": "app.insights.feature_disabled", - "translation": "Insights feature is disabled." - }, { "id": "app.job.download_export_results_not_enabled", "translation": "DownloadExportResults in config.json is false. Please set this to true to download the results of this job." @@ -6295,18 +6287,6 @@ "id": "app.post.get_root_posts.app_error", "translation": "Unable to get the posts for the channel." }, - { - "id": "app.post.get_top_dms_for_user_since.app_error", - "translation": "Unable to get top DMs for user." - }, - { - "id": "app.post.get_top_threads_for_team_since.app_error", - "translation": "Unable to get top threads for team." - }, - { - "id": "app.post.get_top_threads_for_user_since.app_error", - "translation": "Unable to get top threads for user." - }, { "id": "app.post.marshal.app_error", "translation": "Failed to marshal post." @@ -9071,10 +9051,6 @@ "id": "model.incoming_hook.username.app_error", "translation": "Invalid username." }, - { - "id": "model.insights.get_start_of_day_for_time_range.time_range.app_error", - "translation": "Invalid time range." - }, { "id": "model.job.is_valid.create_at.app_error", "translation": "Create at must be a valid time." diff --git a/server/i18n/en_AU.json b/server/i18n/en_AU.json index 434cd420b1..2fe9fe9c8f 100644 --- a/server/i18n/en_AU.json +++ b/server/i18n/en_AU.json @@ -9462,10 +9462,6 @@ "id": "app.file.cloud.get.app_error", "translation": "Unable to retrieve file: cloud storage limit exceeded." }, - { - "id": "model.insights.get_start_of_day_for_time_range.time_range.app_error", - "translation": "Invalid time range." - }, { "id": "api.team.invite_guests_to_channels.license.error", "translation": "Your workspace licence does not support guest accounts" diff --git a/server/i18n/es.json b/server/i18n/es.json index f35908d924..3b8f797621 100644 --- a/server/i18n/es.json +++ b/server/i18n/es.json @@ -9467,10 +9467,6 @@ "id": "app.cloud.trial_plan_bot_message_single", "translation": "el miembro {{.UsersNum}} del workspace {{.WorkspaceName}} ha solicitado comenzar la prueba Enterprise para acceder a: " }, - { - "id": "model.insights.get_start_of_day_for_time_range.time_range.app_error", - "translation": "Intervalo de tiempo inválido." - }, { "id": "app.collection.add_topic.exists.app_error", "translation": "El tipo de tema ya existe." diff --git a/server/i18n/ja.json b/server/i18n/ja.json index 19a9b60839..31ca619103 100644 --- a/server/i18n/ja.json +++ b/server/i18n/ja.json @@ -9439,10 +9439,6 @@ "id": "app.job.error", "translation": "ジョブ実行中にエラーが発生しました。" }, - { - "id": "model.insights.get_start_of_day_for_time_range.time_range.app_error", - "translation": "時間の範囲が無効です。" - }, { "id": "model.group.name.reserved_name.app_error", "translation": "グループ名は予約語として既に登録されています" diff --git a/server/i18n/nl.json b/server/i18n/nl.json index 85b9706ab5..e19a5ac8a8 100644 --- a/server/i18n/nl.json +++ b/server/i18n/nl.json @@ -9466,10 +9466,6 @@ "id": "api.team.invite_guests_to_channels.disabled.error", "translation": "Gastaccounts zijn uitgeschakeld" }, - { - "id": "model.insights.get_start_of_day_for_time_range.time_range.app_error", - "translation": "Ongeldig tijdsbereik." - }, { "id": "app.collection.add_topic.exists.app_error", "translation": "Topictype bestaat al." diff --git a/server/i18n/pl.json b/server/i18n/pl.json index a725490f3c..062568ddc7 100644 --- a/server/i18n/pl.json +++ b/server/i18n/pl.json @@ -9467,10 +9467,6 @@ "id": "api.team.invite_guests_to_channels.disabled.error", "translation": "Konta gości są wyłączone" }, - { - "id": "model.insights.get_start_of_day_for_time_range.time_range.app_error", - "translation": "Nieprawidłowy zakres czasu." - }, { "id": "app.collection.add_topic.exists.app_error", "translation": "Typ tematu już istnieje." diff --git a/server/i18n/ru.json b/server/i18n/ru.json index 6a8010cb18..bd1b5c309a 100644 --- a/server/i18n/ru.json +++ b/server/i18n/ru.json @@ -9187,10 +9187,6 @@ "id": "api.cloud.delinquency_email.missing_email_to_trigger", "translation": "Отсутствуют обязательные поля для отправки электронного письма о просрочке." }, - { - "id": "model.insights.get_start_of_day_for_time_range.time_range.app_error", - "translation": "Неверный диапазон времени." - }, { "id": "model.group.name.reserved_name.app_error", "translation": "имя группы уже существует как зарезервированное имя" diff --git a/server/i18n/sv.json b/server/i18n/sv.json index f71be221a6..9fe7e79296 100644 --- a/server/i18n/sv.json +++ b/server/i18n/sv.json @@ -9466,10 +9466,6 @@ "id": "api.team.invite_guests_to_channels.disabled.error", "translation": "Gäståtkomst har inaktiverats" }, - { - "id": "model.insights.get_start_of_day_for_time_range.time_range.app_error", - "translation": "Ogiltigt tidsintervall." - }, { "id": "app.collection.add_collection.exists.app_error", "translation": "Samlingstypen finns redan." diff --git a/server/i18n/tr.json b/server/i18n/tr.json index 6d9dca0c91..e89bf2c462 100644 --- a/server/i18n/tr.json +++ b/server/i18n/tr.json @@ -9466,10 +9466,6 @@ "id": "api.team.invite_guests_to_channels.disabled.error", "translation": "Konuk hesapları devre dışı bırakılmış" }, - { - "id": "model.insights.get_start_of_day_for_time_range.time_range.app_error", - "translation": "Zaman aralığı geçersiz." - }, { "id": "app.collection.add_topic.exists.app_error", "translation": "Konu türü zaten var." diff --git a/server/i18n/zh-CN.json b/server/i18n/zh-CN.json index 7e32f32a11..b0f6ed6109 100644 --- a/server/i18n/zh-CN.json +++ b/server/i18n/zh-CN.json @@ -9467,10 +9467,6 @@ "id": "worktemplate.category.companywide", "translation": "全公司" }, - { - "id": "model.insights.get_start_of_day_for_time_range.time_range.app_error", - "translation": "不正确的时间范围。" - }, { "id": "model.group.name.reserved_name.app_error", "translation": "群组名称已作为保留名称存在" diff --git a/server/public/model/client4.go b/server/public/model/client4.go index 5f306fe234..af991fde8c 100644 --- a/server/public/model/client4.go +++ b/server/public/model/client4.go @@ -3670,76 +3670,6 @@ func (c *Client4) AutocompleteChannelsForTeamForSearch(ctx context.Context, team return ch, BuildResponse(r), nil } -// GetTopChannelsForTeamSince will return an ordered list of the top channels in a given team. -func (c *Client4) GetTopChannelsForTeamSince(ctx context.Context, teamId string, timeRange string, page int, perPage int) (*TopChannelList, *Response, error) { - query := fmt.Sprintf("?time_range=%v&page=%v&per_page=%v", timeRange, page, perPage) - r, err := c.DoAPIGet(ctx, c.teamRoute(teamId)+"/top/channels"+query, "") - if err != nil { - return nil, BuildResponse(r), err - } - defer closeBody(r) - var topChannels *TopChannelList - if err := json.NewDecoder(r.Body).Decode(&topChannels); err != nil { - return nil, nil, NewAppError("GetTopChannelsForTeamSince", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return topChannels, BuildResponse(r), nil -} - -// GetTopChannelsForUserSince will return an ordered list of your top channels in a given team. -func (c *Client4) GetTopChannelsForUserSince(ctx context.Context, teamId string, timeRange string, page int, perPage int) (*TopChannelList, *Response, error) { - query := fmt.Sprintf("?time_range=%v&page=%v&per_page=%v", timeRange, page, perPage) - - if teamId != "" { - query += fmt.Sprintf("&team_id=%v", teamId) - } - - r, err := c.DoAPIGet(ctx, c.usersRoute()+"/me/top/channels"+query, "") - if err != nil { - return nil, BuildResponse(r), err - } - defer closeBody(r) - var topChannels *TopChannelList - if err := json.NewDecoder(r.Body).Decode(&topChannels); err != nil { - return nil, nil, NewAppError("GetTopChannelsForUserSince", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return topChannels, BuildResponse(r), nil -} - -// GetTopInactiveChannelsForTeamSince will return an ordered list of the top channels in a given team. -func (c *Client4) GetTopInactiveChannelsForTeamSince(ctx context.Context, teamId string, timeRange string, page int, perPage int) (*TopInactiveChannelList, *Response, error) { - query := fmt.Sprintf("?time_range=%v&page=%v&per_page=%v", timeRange, page, perPage) - r, err := c.DoAPIGet(ctx, c.teamRoute(teamId)+"/top/inactive_channels"+query, "") - if err != nil { - return nil, BuildResponse(r), err - } - defer closeBody(r) - var topInactiveChannels *TopInactiveChannelList - if jsonErr := json.NewDecoder(r.Body).Decode(&topInactiveChannels); jsonErr != nil { - return nil, nil, NewAppError("GetTopInactiveChannelsForTeamSince", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError) - } - return topInactiveChannels, BuildResponse(r), nil -} - -// GetTopInactiveChannelsForUserSince will return an ordered list of your top channels in a given team. -func (c *Client4) GetTopInactiveChannelsForUserSince(ctx context.Context, teamId string, timeRange string, page int, perPage int) (*TopInactiveChannelList, *Response, error) { - query := fmt.Sprintf("?time_range=%v&page=%v&per_page=%v", timeRange, page, perPage) - - if teamId != "" { - query += fmt.Sprintf("&team_id=%v", teamId) - } - - r, err := c.DoAPIGet(ctx, c.usersRoute()+"/me/top/inactive_channels"+query, "") - if err != nil { - return nil, BuildResponse(r), err - } - defer closeBody(r) - var topInactiveChannels *TopInactiveChannelList - if jsonErr := json.NewDecoder(r.Body).Decode(&topInactiveChannels); jsonErr != nil { - return nil, nil, NewAppError("GetTopInactiveChannelsForUserSince", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError) - } - return topInactiveChannels, BuildResponse(r), nil -} - // Post Section // CreatePost creates a post based on the provided post struct. @@ -4322,41 +4252,6 @@ func (c *Client4) DoPostActionWithCookie(ctx context.Context, postId, actionId, return BuildResponse(r), nil } -// GetTopThreadsForTeamSince will return an ordered list of the top channels in a given team. -func (c *Client4) GetTopThreadsForTeamSince(ctx context.Context, teamId string, timeRange string, page int, perPage int) (*TopThreadList, *Response, error) { - query := fmt.Sprintf("?time_range=%v&page=%v&per_page=%v", timeRange, page, perPage) - r, err := c.DoAPIGet(ctx, c.teamRoute(teamId)+"/top/threads"+query, "") - if err != nil { - return nil, BuildResponse(r), err - } - defer closeBody(r) - var topThreads *TopThreadList - if err := json.NewDecoder(r.Body).Decode(&topThreads); err != nil { - return nil, nil, NewAppError("GetTopThreadsForTeamSince", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return topThreads, BuildResponse(r), nil -} - -// GetTopThreadsForUserSince will return an ordered list of your top channels in a given team. -func (c *Client4) GetTopThreadsForUserSince(ctx context.Context, teamId string, timeRange string, page int, perPage int) (*TopThreadList, *Response, error) { - query := fmt.Sprintf("?time_range=%v&page=%v&per_page=%v", timeRange, page, perPage) - - if teamId != "" { - query += fmt.Sprintf("&team_id=%v", teamId) - } - - r, err := c.DoAPIGet(ctx, c.usersRoute()+"/me/top/threads"+query, "") - if err != nil { - return nil, BuildResponse(r), err - } - defer closeBody(r) - var topThreads *TopThreadList - if err := json.NewDecoder(r.Body).Decode(&topThreads); err != nil { - return nil, nil, NewAppError("GetTopThreadsForUserSince", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return topThreads, BuildResponse(r), nil -} - // OpenInteractiveDialog sends a WebSocket event to a user's clients to // open interactive dialogs, based on the provided trigger ID and other // provided data. Used with interactive message buttons, menus and @@ -6849,54 +6744,6 @@ func (c *Client4) GetBulkReactions(ctx context.Context, postIds []string) (map[s return reactions, BuildResponse(r), nil } -func (c *Client4) GetTopReactionsForTeamSince(ctx context.Context, teamId string, timeRange string, page int, perPage int) (*TopReactionList, *Response, error) { - query := fmt.Sprintf("?time_range=%v&page=%v&per_page=%v", timeRange, page, perPage) - r, err := c.DoAPIGet(ctx, c.teamRoute(teamId)+"/top/reactions"+query, "") - if err != nil { - return nil, BuildResponse(r), err - } - defer closeBody(r) - var topReactions *TopReactionList - if err := json.NewDecoder(r.Body).Decode(&topReactions); err != nil { - return nil, nil, NewAppError("GetTopReactionsForTeamSince", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return topReactions, BuildResponse(r), nil -} - -func (c *Client4) GetTopReactionsForUserSince(ctx context.Context, teamId string, timeRange string, page int, perPage int) (*TopReactionList, *Response, error) { - query := fmt.Sprintf("?time_range=%v&page=%v&per_page=%v", timeRange, page, perPage) - - if teamId != "" { - query += fmt.Sprintf("&team_id=%v", teamId) - } - - r, err := c.DoAPIGet(ctx, c.usersRoute()+"/me/top/reactions"+query, "") - if err != nil { - return nil, BuildResponse(r), err - } - defer closeBody(r) - var topReactions *TopReactionList - if err := json.NewDecoder(r.Body).Decode(&topReactions); err != nil { - return nil, nil, NewAppError("GetTopReactionsForUserSince", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return topReactions, BuildResponse(r), nil -} - -func (c *Client4) GetTopDMsForUserSince(ctx context.Context, timeRange string, page int, perPage int) (*TopDMList, *Response, error) { - query := fmt.Sprintf("?time_range=%v&page=%v&per_page=%v", timeRange, page, perPage) - - r, err := c.DoAPIGet(ctx, c.usersRoute()+"/me/top/dms"+query, "") - if err != nil { - return nil, BuildResponse(r), err - } - defer closeBody(r) - var topDMs *TopDMList - if jsonErr := json.NewDecoder(r.Body).Decode(&topDMs); jsonErr != nil { - return nil, nil, NewAppError("GetTopReactionsForUserSince", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError) - } - return topDMs, BuildResponse(r), nil -} - // Timezone Section // GetSupportedTimezone returns a page of supported timezones on the system. @@ -8645,20 +8492,6 @@ func (c *Client4) GetTeamsUsage(ctx context.Context) (*TeamsUsage, *Response, er return usage, BuildResponse(r), err } -func (c *Client4) GetNewTeamMembersSince(ctx context.Context, teamID string, timeRange string, page int, perPage int) (*NewTeamMembersList, *Response, error) { - query := fmt.Sprintf("?time_range=%v&page=%v&per_page=%v", timeRange, page, perPage) - r, err := c.DoAPIGet(ctx, c.teamRoute(teamID)+"/top/team_members"+query, "") - if err != nil { - return nil, BuildResponse(r), err - } - defer closeBody(r) - var newTeamMembersList *NewTeamMembersList - if jsonErr := json.NewDecoder(r.Body).Decode(&newTeamMembersList); jsonErr != nil { - return nil, nil, NewAppError("GetNewTeamMembersSince", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError) - } - return newTeamMembersList, BuildResponse(r), nil -} - func (c *Client4) SelfHostedSignupAvailable(ctx context.Context) (*Response, error) { r, err := c.DoAPIGet(ctx, c.hostedCustomerRoute()+"/signup_available", "") diff --git a/server/public/model/feature_flags.go b/server/public/model/feature_flags.go index 4b79d48a3d..442f47de24 100644 --- a/server/public/model/feature_flags.go +++ b/server/public/model/feature_flags.go @@ -41,8 +41,6 @@ type FeatureFlags struct { // Enable GraphQL feature GraphQL bool - InsightsEnabled bool - PostPriority bool // Enable WYSIWYG text editor @@ -71,7 +69,6 @@ func (f *FeatureFlags) SetDefaults() { f.BoardsDataRetention = false f.NormalizeLdapDNs = false f.GraphQL = false - f.InsightsEnabled = false f.CallsEnabled = true f.PeopleProduct = false f.DeprecateCloudFree = false diff --git a/server/public/model/insights.go b/server/public/model/insights.go deleted file mode 100644 index 22d105f867..0000000000 --- a/server/public/model/insights.go +++ /dev/null @@ -1,363 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -package model - -import ( - "net/http" - "time" -) - -type PostCountGrouping string - -const ( - TimeRangeToday string = "today" - TimeRange7Day string = "7_day" - TimeRange28Day string = "28_day" - - PostsByHour PostCountGrouping = "hour" - PostsByDay PostCountGrouping = "day" -) - -type InsightsOpts struct { - StartUnixMilli int64 - Page int - PerPage int -} - -type InsightsListData struct { - HasNext bool `json:"has_next"` -} - -// Top Reactions -type TopReactionList struct { - InsightsListData - Items []*TopReaction `json:"items"` -} - -type TopReaction struct { - EmojiName string `json:"emoji_name"` - Count int64 `json:"count"` -} - -// Top Channels -type TopChannelList struct { - InsightsListData - Items []*TopChannel `json:"items"` - PostCountByDuration ChannelPostCountByDuration `json:"channel_post_counts_by_duration"` -} - -func (t *TopChannelList) ChannelIDs() []string { - var ids []string - for _, item := range t.Items { - ids = append(ids, item.ID) - } - return ids -} - -type TopChannel struct { - ID string `json:"id"` - Type ChannelType `json:"type"` - DisplayName string `json:"display_name"` - Name string `json:"name"` - TeamID string `json:"team_id"` - MessageCount int64 `json:"message_count"` -} - -// Top Channels -type TopInactiveChannelList struct { - InsightsListData - Items []*TopInactiveChannel `json:"items"` -} - -type TopInactiveChannel struct { - ID string `json:"id"` - Type ChannelType `json:"type"` - DisplayName string `json:"display_name"` - Name string `json:"name"` - LastActivityAt int64 `json:"last_activity_at"` - Participants StringArray `json:"participants"` - MessageCount int64 `json:"-"` -} - -// Top Threads -type TopThreadList struct { - InsightsListData - Items []*TopThread `json:"items"` -} - -type TopThread struct { - PostId string `json:"-"` - ReplyCount int64 `json:"-"` - ChannelId string `json:"channel_id"` - DisplayName string `json:"channel_display_name"` - Name string `json:"channel_name"` - Participants StringArray `json:"participants"` - UserId string `json:"-"` - UserInformation *InsightUserInformation `json:"user_information"` - Post *Post `json:"post"` -} - -type InsightUserInformation struct { - Id string `json:"id"` - LastPictureUpdate int64 `json:"last_picture_update"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - NickName string `json:"nickname"` - Username string `json:"username"` -} - -type TopDMInsightUserInformation struct { - InsightUserInformation - Position string `json:"position"` -} -type NewTeamMembersList struct { - InsightsListData - Items []*NewTeamMember `json:"items"` - TotalCount int64 `json:"total_count"` -} - -type NewTeamMember struct { - Id string `json:"id"` - Username string `json:"username"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - Position string `json:"position"` - Nickname string `json:"nickname"` - LastPictureUpdate int64 `json:"last_picture_update,omitempty"` - CreateAt int64 `json:"create_at"` -} - -type DurationPostCount struct { - ChannelID string `db:"channelid"` - // Duration is an ISO8601 date string. - Duration string `db:"duration"` - PostCount int `db:"postcount"` -} - -// Top DMs -type TopDM struct { - MessageCount int64 `json:"post_count"` - OutgoingMessageCount int64 `json:"outgoing_message_count"` - Participants string `json:"-"` - ChannelId string `json:"-"` - SecondParticipant *TopDMInsightUserInformation `json:"second_participant"` -} - -type OutgoingMessageQueryResult struct { - ChannelId string - MessageCount int -} - -type TopDMList struct { - InsightsListData - Items []*TopDM `json:"items"` -} - -func TimeRangeToNumberDays(timeRange string) int { - var n int - switch timeRange { - case TimeRangeToday: - n = 1 - case TimeRange7Day: - n = 7 - case TimeRange28Day: - n = 28 - } - return n -} - -// ChannelPostCountByDuration contains a count of posts by channel id, grouped by ISO8601 date string. -// Example 1 (grouped by day): -// -// cpc := model.ChannelPostCountByDuration{ -// "2009-11-11": { -// "ezbp7nqxzjgdir8riodyafr9ww": 90, -// "p949c1xdojfgzffxma3p3s3ikr": 201, -// }, -// "2009-11-12": { -// "ezbp7nqxzjgdir8riodyafr9ww": 45, -// "p949c1xdojfgzffxma3p3s3ikr": 68, -// }, -// } -// -// Example 2 (grouped by hour): -// -// cpc := model.ChannelPostCountByDuration{ -// "2009-11-11T01": { -// "ezbp7nqxzjgdir8riodyafr9ww": 90, -// "p949c1xdojfgzffxma3p3s3ikr": 201, -// }, -// "2009-11-11T02": { -// "ezbp7nqxzjgdir8riodyafr9ww": 45, -// "p949c1xdojfgzffxma3p3s3ikr": 68, -// }, -// } -type ChannelPostCountByDuration map[string]map[string]int - -func blankChannelCountsMap(channelIDs []string) map[string]int { - blankChannelCounts := map[string]int{} - for _, id := range channelIDs { - blankChannelCounts[id] = 0 - } - return blankChannelCounts -} - -func ToDailyPostCountViewModel(dpc []*DurationPostCount, startTime *time.Time, numDays int, channelIDs []string) ChannelPostCountByDuration { - viewModel := ChannelPostCountByDuration{} - - keyTime := *startTime - nowAtLocation := time.Now().In(startTime.Location()) - - if numDays == 1 { - for keyTime.Before(nowAtLocation) { - dateTimeKey := keyTime.Format(time.RFC3339) - viewModel[dateTimeKey] = blankChannelCountsMap(channelIDs) - keyTime = keyTime.Add(time.Hour) - } - } else { - for keyTime.Before(nowAtLocation) { - dateTimeKey := keyTime.Format("2006-01-02") - viewModel[dateTimeKey] = blankChannelCountsMap(channelIDs) - keyTime = keyTime.Add(24 * time.Hour) - } - } - - for _, item := range dpc { - var parseFormat string - var keyFormat string - if numDays == 1 { - parseFormat = "2006-01-02T15 " - keyFormat = time.RFC3339 - } else { - parseFormat = "2006-01-02" - keyFormat = parseFormat - } - durTime, err := time.ParseInLocation(parseFormat, item.Duration, startTime.Location()) - if err != nil { - continue - } - localizedKey := durTime.Format(keyFormat) - _, hasKey := viewModel[localizedKey] - if !hasKey { - viewModel[localizedKey] = map[string]int{} - } - viewModel[localizedKey][item.ChannelID] = item.PostCount - } - - return viewModel -} - -// Deprecated: This method doesn't perform error checking. -// Use GetStartOfDayForTimeRange instead. -// -// StartOfDayForTimeRange gets the unix start time in milliseconds from the given time range. -// Time range can be one of: "today", "7_day", or "28_day". -func StartOfDayForTimeRange(timeRange string, location *time.Location) *time.Time { - now := time.Now().In(location) - resultTime := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, location) - switch timeRange { - case TimeRange7Day: - resultTime = resultTime.Add(time.Hour * time.Duration(-144)) - case TimeRange28Day: - resultTime = resultTime.Add(time.Hour * time.Duration(-648)) - } - return &resultTime -} - -// GetStartOfDayForTimeRange gets the unix start time in milliseconds from the given time range. -// Time range can be one of: "today", "7_day", or "28_day". -func GetStartOfDayForTimeRange(timeRange string, location *time.Location) (*time.Time, *AppError) { - now := time.Now().In(location) - resultTime := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, location) - switch timeRange { - case TimeRangeToday: - case TimeRange7Day: - resultTime = resultTime.Add(time.Hour * time.Duration(-144)) - case TimeRange28Day: - resultTime = resultTime.Add(time.Hour * time.Duration(-648)) - default: - return nil, NewAppError("GetStartOfDayForTimeRange", "model.insights.get_start_of_day_for_time_range.time_range.app_error", nil, "", http.StatusBadRequest) - } - return &resultTime, nil -} - -// GetTopReactionListWithPagination adds a rank to each item in the given list of TopReaction and checks if there is -// another page that can be fetched based on the given limit and offset. The given list of TopReaction is assumed to be -// sorted by Count. Returns a TopReactionList. -func GetTopReactionListWithPagination(reactions []*TopReaction, limit int) *TopReactionList { - // Add pagination support - var hasNext bool - if (limit != 0) && (len(reactions) == limit+1) { - hasNext = true - reactions = reactions[:len(reactions)-1] - } - - return &TopReactionList{InsightsListData: InsightsListData{HasNext: hasNext}, Items: reactions} -} - -// GetTopChannelListWithPagination adds a rank to each item in the given list of TopChannel and checks if there is -// another page that can be fetched based on the given limit and offset. The given list of TopChannel is assumed to be -// sorted by Score. Returns a TopChannelList. -func GetTopChannelListWithPagination(channels []*TopChannel, limit int) *TopChannelList { - // Add pagination support - var hasNext bool - if (limit != 0) && (len(channels) == limit+1) { - hasNext = true - channels = channels[:len(channels)-1] - } - - return &TopChannelList{InsightsListData: InsightsListData{HasNext: hasNext}, Items: channels} -} - -// GetTopThreadListWithPagination adds a rank to each item in the given list of TopThread and checks if there is -// another page that can be fetched based on the given limit and offset. The given list of TopThread is assumed to be -// sorted by ReplyCount(score). Returns a TopThreadList. -func GetTopThreadListWithPagination(threads []*TopThread, limit int) *TopThreadList { - // Add pagination support - var hasNext bool - if (limit != 0) && (len(threads) == limit+1) { - hasNext = true - threads = threads[:len(threads)-1] - } - - return &TopThreadList{InsightsListData: InsightsListData{HasNext: hasNext}, Items: threads} -} - -// GetTopInactiveChannelListWithPagination adds a rank to each item in the given list of TopInactiveChannel and checks if there is -// another page that can be fetched based on the given limit and offset. The given list of TopInactiveChannel is assumed to be -// sorted by Score. Returns a TopInactiveChannelList. -func GetTopInactiveChannelListWithPagination(channels []*TopInactiveChannel, limit int) *TopInactiveChannelList { - // Add pagination support - var hasNext bool - if (limit != 0) && (len(channels) == limit+1) { - hasNext = true - channels = channels[:len(channels)-1] - } - - return &TopInactiveChannelList{InsightsListData: InsightsListData{HasNext: hasNext}, Items: channels} -} - -// GetTopDMListWithPagination adds a rank to each item in the given list of TopDM and checks if there is -// another page that can be fetched based on the given limit and offset. The given list of TopDM is assumed to be -// sorted by MessageCount(score). Returns a TopDMList. -func GetTopDMListWithPagination(dms []*TopDM, limit int) *TopDMList { - // Add pagination support - var hasNext bool - if (limit != 0) && (len(dms) == limit+1) { - hasNext = true - dms = dms[:len(dms)-1] - } - - return &TopDMList{InsightsListData: InsightsListData{HasNext: hasNext}, Items: dms} -} - -func GetNewTeamMembersListWithPagination(teamMembers []*NewTeamMember, limit int) *NewTeamMembersList { - var hasNext bool - if (limit != 0) && (len(teamMembers) == limit+1) { - hasNext = true - teamMembers = teamMembers[:len(teamMembers)-1] - } - - return &NewTeamMembersList{InsightsListData: InsightsListData{HasNext: hasNext}, Items: teamMembers} -} diff --git a/server/public/model/insights_test.go b/server/public/model/insights_test.go deleted file mode 100644 index f95fbeb006..0000000000 --- a/server/public/model/insights_test.go +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -package model - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestGetTopReactionListWithPagination(t *testing.T) { - reactions := []*TopReaction{ - {EmojiName: "smile", Count: 200}, - {EmojiName: "+1", Count: 190}, - {EmojiName: "100", Count: 100}, - {EmojiName: "-1", Count: 75}, - {EmojiName: "checkmark", Count: 50}, - {EmojiName: "mattermost", Count: 49}} - - hasNextTC := []struct { - Description string - Limit int - Offset int - Expected *TopReactionList - }{ - { - Description: "has one page", - Limit: len(reactions), - Offset: 0, - Expected: &TopReactionList{InsightsListData: InsightsListData{HasNext: false}, Items: reactions}, - }, - { - Description: "has more than one page", - Limit: len(reactions) - 1, - Offset: 0, - Expected: &TopReactionList{InsightsListData: InsightsListData{HasNext: true}, Items: reactions}, - }, - } - - for _, test := range hasNextTC { - t.Run(test.Description, func(t *testing.T) { - actual := GetTopReactionListWithPagination(reactions, test.Limit) - assert.Equal(t, test.Expected.HasNext, actual.HasNext) - }) - } -} - -func TestGetTopChannelListWithPagination(t *testing.T) { - channels := []*TopChannel{ - {ID: NewId(), MessageCount: 200}, - {ID: NewId(), MessageCount: 150}, - {ID: NewId(), MessageCount: 120}, - {ID: NewId(), MessageCount: 105}, - {ID: NewId(), MessageCount: 5}, - {ID: NewId(), MessageCount: 2}} - - hasNextTC := []struct { - Description string - Limit int - Offset int - Expected *TopChannelList - }{ - { - Description: "has one page", - Limit: len(channels), - Offset: 0, - Expected: &TopChannelList{InsightsListData: InsightsListData{HasNext: false}, Items: channels}, - }, - { - Description: "has more than one page", - Limit: len(channels) - 1, - Offset: 0, - Expected: &TopChannelList{InsightsListData: InsightsListData{HasNext: true}, Items: channels}, - }, - } - - for _, test := range hasNextTC { - t.Run(test.Description, func(t *testing.T) { - actual := GetTopChannelListWithPagination(channels, test.Limit) - assert.Equal(t, test.Expected.HasNext, actual.HasNext) - }) - } -} - -func TestGetTopThreadListWithPagination(t *testing.T) { - threads := []*TopThread{ - {PostId: NewId(), ReplyCount: 100}, - {PostId: NewId(), ReplyCount: 80}, - {PostId: NewId(), ReplyCount: 90}, - {PostId: NewId(), ReplyCount: 76}, - {PostId: NewId(), ReplyCount: 43}, - {PostId: NewId(), ReplyCount: 2}, - {PostId: NewId(), ReplyCount: 1}, - } - hasNextTT := []struct { - Description string - Limit int - Offset int - Expected *TopThreadList - }{ - { - Description: "has one page", - Limit: len(threads), - Offset: 0, - Expected: &TopThreadList{InsightsListData: InsightsListData{HasNext: false}, Items: threads}, - }, - { - Description: "has more than one page", - Limit: len(threads) - 1, - Offset: 0, - Expected: &TopThreadList{InsightsListData: InsightsListData{HasNext: true}, Items: threads}, - }, - } - - for _, test := range hasNextTT { - t.Run(test.Description, func(t *testing.T) { - actual := GetTopThreadListWithPagination(threads, test.Limit) - assert.Equal(t, test.Expected.HasNext, actual.HasNext) - }) - } -} - -func TestGetTopInactiveChannelListWithPagination(t *testing.T) { - channels := []*TopInactiveChannel{ - {ID: NewId(), MessageCount: 2}, - {ID: NewId(), MessageCount: 5}, - {ID: NewId(), MessageCount: 7}, - {ID: NewId(), MessageCount: 80}, - {ID: NewId(), MessageCount: 85}, - {ID: NewId(), MessageCount: 92}} - - hasNextTC := []struct { - Description string - Limit int - Offset int - Expected *TopInactiveChannelList - }{ - { - Description: "has one page", - Limit: len(channels), - Offset: 0, - Expected: &TopInactiveChannelList{InsightsListData: InsightsListData{HasNext: false}, Items: channels}, - }, - { - Description: "has more than one page", - Limit: len(channels) - 1, - Offset: 0, - Expected: &TopInactiveChannelList{InsightsListData: InsightsListData{HasNext: true}, Items: channels}, - }, - } - - for _, test := range hasNextTC { - t.Run(test.Description, func(t *testing.T) { - actual := GetTopInactiveChannelListWithPagination(channels, test.Limit) - assert.Equal(t, test.Expected.HasNext, actual.HasNext) - }) - } -} - -func TestGetTopDMsListWithPagination(t *testing.T) { - dms := []*TopDM{ - {SecondParticipant: &TopDMInsightUserInformation{InsightUserInformation: InsightUserInformation{Id: NewId()}}, MessageCount: 100}, - {SecondParticipant: &TopDMInsightUserInformation{InsightUserInformation: InsightUserInformation{Id: NewId()}}, MessageCount: 80}, - {SecondParticipant: &TopDMInsightUserInformation{InsightUserInformation: InsightUserInformation{Id: NewId()}}, MessageCount: 90}, - {SecondParticipant: &TopDMInsightUserInformation{InsightUserInformation: InsightUserInformation{Id: NewId()}}, MessageCount: 76}, - {SecondParticipant: &TopDMInsightUserInformation{InsightUserInformation: InsightUserInformation{Id: NewId()}}, MessageCount: 43}, - {SecondParticipant: &TopDMInsightUserInformation{InsightUserInformation: InsightUserInformation{Id: NewId()}}, MessageCount: 2}, - {SecondParticipant: &TopDMInsightUserInformation{InsightUserInformation: InsightUserInformation{Id: NewId()}}, MessageCount: 1}, - } - hasNextTT := []struct { - Description string - Limit int - Offset int - Expected *TopDMList - }{ - { - Description: "has one page", - Limit: len(dms), - Offset: 0, - Expected: &TopDMList{InsightsListData: InsightsListData{HasNext: false}, Items: dms}, - }, - { - Description: "has more than one page", - Limit: len(dms) - 1, - Offset: 0, - Expected: &TopDMList{InsightsListData: InsightsListData{HasNext: true}, Items: dms}, - }, - } - - for _, test := range hasNextTT { - t.Run(test.Description, func(t *testing.T) { - actual := GetTopDMListWithPagination(dms, test.Limit) - assert.Equal(t, test.Expected.HasNext, actual.HasNext) - }) - } -} diff --git a/server/public/model/preference.go b/server/public/model/preference.go index 98e0a08c5a..a73e19dad6 100644 --- a/server/public/model/preference.go +++ b/server/public/model/preference.go @@ -19,7 +19,6 @@ const ( PreferenceCategoryFlaggedPost = "flagged_post" PreferenceCategoryFavoriteChannel = "favorite_channel" PreferenceCategorySidebarSettings = "sidebar_settings" - PreferenceCategoryInsights = "insights" PreferenceCategoryDisplaySettings = "display_settings" PreferenceNameCollapsedThreadsEnabled = "collapsed_reply_threads" @@ -31,7 +30,6 @@ const ( PreferenceNameNameFormat = "name_format" PreferenceNameUseMilitaryTime = "use_military_time" PreferenceRecommendedNextSteps = "recommended_next_steps" - PreferenceNameInsights = "insights_tutorial_state" PreferenceCategoryTheme = "theme" // the name for theme props is the team id diff --git a/webapp/channels/src/actions/views/channel.ts b/webapp/channels/src/actions/views/channel.ts index e1b40ff0be..96eb19045d 100644 --- a/webapp/channels/src/actions/views/channel.ts +++ b/webapp/channels/src/actions/views/channel.ts @@ -133,8 +133,6 @@ export function switchToChannel(channel: Channel & {userId?: string}) { getHistory().push(`${teamUrl}/channels/${gmChannel.name}`); } else if (channel.type === Constants.THREADS) { getHistory().push(`${teamUrl}/${channel.name}`); - } else if (channel.type === Constants.INSIGHTS) { - getHistory().push(`${teamUrl}/${channel.name}`); } else { getHistory().push(`${teamUrl}/channels/${channel.name}`); } diff --git a/webapp/channels/src/components/activity_and_insights/activity_and_insights.scss b/webapp/channels/src/components/activity_and_insights/activity_and_insights.scss deleted file mode 100644 index 0a4c44d848..0000000000 --- a/webapp/channels/src/components/activity_and_insights/activity_and_insights.scss +++ /dev/null @@ -1,921 +0,0 @@ -@mixin channel-display-name { - overflow: hidden; - margin-left: 7px; - color: var(--center-channel-color); - font-family: 'Open Sans'; - font-size: 12px; - font-style: normal; - font-weight: 600; - line-height: 16px; - text-overflow: ellipsis; - white-space: nowrap; -} - -@mixin channel-icon { - width: 14px; - height: 14px; - color: rgba(var(--center-channel-color-rgb), 0.56); - font-size: 14px; -} - -.ActivityAndInsights { - background-color: rgba(var(--center-channel-color-rgb), 0.04); - - .insights-body { - padding: 24px; - overflow-x: hidden; - overflow-y: auto; - - @media screen and (min-width: 1680px) { - display: flex; - flex-direction: column; - - .card-row { - width: 1280px; - margin: auto; - } - } - - .card-row { - &::after { - display: block; - height: 0; - clear: both; - content: " "; - } - } - - @media screen and (max-width: 768px) { - padding: 16px; - } - - .top-channels-card { - .Card__body { - padding-right: 12px; - - @media screen and (max-width: 1019px) { - padding-left: 12px; - } - } - } - - .top-reactions-card { - &.small { - @media screen and (max-width: 1279px) and (min-width: 769px) { - width: calc(100% - 16px); - } - } - - .Card__header { - padding-bottom: 16px; - } - } - - .top-playbooks-card, - .top-boards-card, - .least-active-channels-card, - .top-threads-card { - &.small { - @media screen and (max-width: 1279px) and (min-width: 1020px) { - width: calc((100%/2) - 16px); - } - - @media screen and (max-width: 1019px) and (min-width: 769px) { - width: calc(100% - 16px); - } - } - - .Card__header { - padding-bottom: 16px; - } - - .Card__body { - padding: 0 12px 12px; - } - } - - .top-boards-card { - .Card__body { - padding: 0 12px 18px; - } - } - - .top-playbooks-card, - .least-active-channels-card { - &.medium { - @media screen and (max-width: 1279px) and (min-width: 769px) { - width: calc(100% - 16px); - } - } - } - - .top-playbooks-card, - .top-dms-card { - .Card__body { - padding: 0 12px 12px; - } - } - - .top-channel-container { - display: flex; - - @media screen and (max-width: 1019px) { - flex-direction: column; - } - - .top-channel-line-chart { - flex: 0.67; - margin-right: 16px; - - @media screen and (max-width: 1019px) { - flex: 1; - padding: 12px; - margin-right: 0; - } - - .col-sm-12 { - padding: 0; - - .total-count { - position: relative; - - .title { - display: none; - } - - .content { - position: relative; - } - - .chartjs-render-monitor { - width: 100% !important; - min-height: 200px !important; - max-height: 200px; - - &:hover { - cursor: pointer; - } - } - } - } - } - - .top-channel-list { - flex: 0.33; - margin-left: 16px; - - @media screen and (max-width: 1019px) { - flex: 1; - margin-left: 0; - } - - .top-channel-loading-row { - display: flex; - align-items: center; - padding: 12px 12px 12px 0; - } - - .channel-row { - display: flex; - align-items: center; - padding: 10px 12px; - - &:nth-of-type(1) { - span.horizontal-bar { - background-color: var(--button-bg); - } - } - - &:nth-of-type(2) { - span.horizontal-bar { - background-color: var(--online-indicator); - } - } - - &:nth-of-type(3) { - span.horizontal-bar { - background-color: var(--away-indicator); - } - } - - &:nth-of-type(4) { - span.horizontal-bar { - background-color: var(--dnd-indicator); - } - } - - &:nth-of-type(5) { - span.horizontal-bar { - background-color: var(--new-message-separator); - } - } - - &:hover { - background-color: rgba(var(--center-channel-color-rgb), 0.08); - border-radius: 4px; - text-decoration: none; - } - - &:focus { - text-decoration: none; - } - - .channel-display-name { - display: flex; - overflow: hidden; - flex: 1 0; - - i.icon { - @include channel-icon; - } - - .display-name { - @include channel-display-name; - } - } - - .channel-message-count { - display: flex; - flex: 1 0; - align-items: center; - - .message-count { - display: inline-flex; - flex: 0.2 0; - justify-content: flex-end; - color: rgba(var(--center-channel-color-rgb), 0.72); - font-family: 'Open Sans'; - font-size: 11px; - font-style: normal; - font-weight: 400; - line-height: 16px; - text-align: right; - } - - span.horizontal-bar { - height: 8px; - margin-left: 16px; - border-radius: 6px; - } - } - } - } - } - - .top-reaction-container { - display: flex; - align-items: center; - justify-content: center; - margin: 0 18px; - - .bar-chart-entry { - display: flex; - flex: 0.2; - flex-direction: column; - align-items: center; - margin-top: auto; - - &:nth-of-type(1) { - .bar-chart-data { - background-color: var(--button-bg); - } - } - - &:nth-of-type(2) { - .bar-chart-data { - background-color: var(--online-indicator); - } - } - - &:nth-of-type(3) { - .bar-chart-data { - background-color: var(--dnd-indicator); - } - } - - &:nth-of-type(4) { - .bar-chart-data { - background-color: var(--new-message-separator); - } - } - - &:nth-of-type(5) { - .bar-chart-data { - background-color: var(--away-indicator); - } - } - - .reaction-count { - margin-bottom: 6px; - font-family: 'Open Sans'; - font-size: 11px; - font-style: normal; - font-weight: 600; - line-height: 16px; - } - - .bar-chart-data { - width: 8px; - margin-bottom: 6px; - border-radius: 6px; - } - } - } - - .top-thread-container { - .top-thread-loading-container { - padding: 0 12px; - - &:nth-of-type(2) { - margin-top: 20px; - } - - &:nth-of-type(3) { - margin-top: 20px; - } - - .top-thread-loading-row { - display: flex; - align-items: center; - padding-bottom: 8px; - } - } - - .thread-list { - .thread-item { - padding: 8px 12px; - - &:nth-of-type(3) { - .preview { - margin-bottom: 0; - } - } - - &:hover { - background: rgba(var(--center-channel-color-rgb), 0.08); - border-radius: 4px; - cursor: pointer; - } - } - } - } - - .top-board-container { - margin-bottom: -3px; - - .top-board-loading-container { - display: flex; - align-items: center; - padding: 0 12px; - margin-bottom: 20px; - - &:nth-of-type(4) { - margin-bottom: 4px; - } - - .loading-lines { - display: flex; - flex: 1; - flex-direction: column; - margin-left: 10px; - } - } - - .board-list { - .board-item { - display: flex; - align-items: center; - padding: 10px 12px; - - &:hover { - background: rgba(var(--center-channel-color-rgb), 0.08); - border-radius: 4px; - cursor: pointer; - text-decoration: none; - } - - &:focus { - text-decoration: none; - } - - span.board-icon { - display: flex; - width: 32px; - height: 32px; - align-items: center; - justify-content: center; - float: left; - font-size: 32px; - } - - .display-info { - display: flex; - overflow: hidden; - flex-direction: column; - margin-left: 12px; - - .display-name { - overflow: hidden; - margin-right: 10px; - color: var(--center-channel-color); - float: left; - font-family: 'Open Sans'; - font-size: 12px; - font-style: normal; - font-weight: 600; - line-height: 16px; - text-overflow: ellipsis; - white-space: nowrap; - } - - .update-counts { - color: rgba(var(--center-channel-color-rgb), 0.56); - font-family: 'Open Sans'; - font-size: 12px; - font-style: normal; - font-weight: 400; - line-height: 16px; - } - } - - .Avatars.Avatars___xs { - margin-left: auto; - float: right; - } - } - } - } - - .least-active-channels-container { - .least-active-channels-loading-container { - display: flex; - width: 100%; - align-items: center; - padding: 0 12px; - margin-top: 24px; - - &:nth-of-type(1) { - margin-top: 10px; - } - - &:nth-of-type(4) { - margin-bottom: 10px; - } - } - - .channel-list { - .channel-row { - display: flex; - align-items: center; - padding: 10px 12px; - - &:hover { - background: rgba(var(--center-channel-color-rgb), 0.08); - border-radius: 4px; - cursor: pointer; - text-decoration: none; - } - - &:focus { - text-decoration: none; - } - - .channel-info { - display: flex; - overflow: hidden; - flex: 0.4; - flex-direction: column; - - .channel-display-name { - display: flex; - overflow: hidden; - - i.icon { - @include channel-icon; - } - - .display-name { - @include channel-display-name; - } - } - - .last-activity { - color: rgba(var(--center-channel-color-rgb), 0.56); - font-family: 'Open Sans'; - font-size: 11px; - font-style: normal; - font-weight: 400; - line-height: 16px; - } - } - - .Avatars { - margin-left: auto; - } - } - } - - .empty-state { - margin: 38px 0; - } - } - - .top-playbooks-container { - .top-playbooks-loading-container { - padding: 0 12px; - - &:nth-of-type(1) { - margin-top: 12px; - } - - &:nth-of-type(2) { - margin-top: 24px; - margin-bottom: 24px; - } - - &:nth-of-type(3) { - margin-bottom: 12px; - - > div { - &:nth-of-type(2) { - margin-bottom: 0; - } - } - } - } - - .playbooks-list { - .playbook-item { - display: flex; - align-items: center; - padding: 10px 12px; - - &:hover { - background: rgba(var(--center-channel-color-rgb), 0.08); - border-radius: 4px; - cursor: pointer; - text-decoration: none; - } - - &:focus { - text-decoration: none; - } - - &:nth-of-type(2) { - .horizontal-bar { - opacity: 0.7; - } - } - - &:nth-of-type(3) { - .horizontal-bar { - opacity: 0.5; - } - } - - .display-info { - display: flex; - overflow: hidden; - flex: 0.6 0; - flex-direction: column; - - &.run-info { - flex: 0.4 0; - } - - .display-name { - overflow: hidden; - margin-right: 10px; - color: var(--center-channel-color); - float: left; - font-family: 'Open Sans'; - font-size: 12px; - font-style: normal; - font-weight: 600; - line-height: 16px; - text-overflow: ellipsis; - white-space: nowrap; - } - - .last-run-time { - color: rgba(var(--center-channel-color-rgb), 0.56); - font-family: 'Open Sans'; - font-size: 12px; - font-style: normal; - font-weight: 400; - line-height: 16px; - } - } - - .horizontal-bar { - height: 8px; - margin-top: 4px; - margin-bottom: 4px; - background-color: var(--button-bg); - border-radius: 6px; - } - } - } - - .empty-state { - margin: 38px 0; - } - } - - .top-dms-container { - display: flex; - align-items: center; - - @media screen and (max-width: 1279px) { - flex-direction: row; - flex-wrap: wrap; - } - - .dms-loading-container { - display: flex; - flex: 0.2; - flex-direction: column; - align-items: left; - margin-top: auto; - - .title-line { - width: 124px; - margin-top: 16px; - margin-bottom: 8px; - } - } - - .top-dms-item { - display: flex; - width: 16.66%; - flex-direction: column; - padding: 12px; - margin-top: auto; - font-family: 'Open Sans'; - - &:nth-of-type(1) { - margin-left: 0; - } - - &:nth-of-type(6) { - margin-right: 0; - } - - &:hover { - background: rgba(var(--center-channel-color-rgb), 0.08); - border-radius: 4px; - cursor: pointer; - text-decoration: none; - } - - @media screen and (max-width: 1279px) { - width: 100%; - flex-direction: row; - align-items: center; - margin: 0; - - &:nth-of-type(1) { - margin-top: 0; - } - } - - @media screen and (max-width: 1279px) and (min-width: 880px) { - overflow: hidden; - width: 50%; - - &:nth-of-type(2) { - margin-top: 0; - } - } - - @media screen and (max-width: 768px) and (min-width: 576px) { - overflow: hidden; - width: 50%; - - &:nth-of-type(2) { - margin-top: 0; - } - } - - .Avatar-xl { - width: 72px; - min-width: 72px; - height: 72px; - } - - .dm-info { - display: flex; - flex-direction: column; - margin-top: 12px; - - @media screen and (max-width: 1279px) { - overflow: hidden; - width: 90%; - margin-top: 0; - margin-left: 12px; - text-overflow: ellipsis; - white-space: nowrap; - } - - .dm-name { - overflow: hidden; - color: var(--center-channel-color); - font-size: 14px; - font-weight: 600; - line-height: 20px; - text-overflow: ellipsis; - white-space: nowrap; - } - - .dm-role { - overflow: hidden; - min-height: 16px; - margin-top: 4px; - color: rgba(var(--center-channel-color-rgb), 0.72); - font-size: 12px; - font-weight: 400; - line-height: 16px; - text-overflow: ellipsis; - white-space: nowrap; - } - - .channel-message-count { - display: flex; - align-items: center; - margin-top: 4px; - - .message-count { - color: rgba(var(--center-channel-color-rgb), 0.72); - font-size: 11px; - font-weight: 400; - line-height: 16px; - } - - .horizontal-bar { - min-height: 8px; - margin-left: 8px; - background-color: var(--online-indicator); - border-radius: 6px; - } - - .say-hello { - margin-left: 6px; - color: var(--button-bg); - font-family: 'Open Sans'; - font-size: 11px; - font-style: normal; - font-weight: 600; - line-height: 10px; - - &:hover { - text-decoration: none; - } - } - } - } - } - - .new-members-item { - width: 16.66%; - - @media screen and (max-width: 1279px) { - width: auto; - width: 100%; - flex-direction: row; - align-items: center; - margin: 0; - - &:nth-of-type(1) { - margin-top: 0; - } - } - - @media screen and (max-width: 1279px) and (min-width: 1020px) { - width: 50%; - - &:nth-of-type(2) { - margin-top: 0; - } - } - - @media screen and (max-width: 1279px) and (min-width: 880px) { - overflow: hidden; - width: 50%; - - &:nth-of-type(2) { - margin-top: 0; - } - } - - @media screen and (max-width: 768px) and (min-width: 576px) { - overflow: hidden; - width: 50%; - - &:nth-of-type(2) { - margin-top: 0; - } - } - - &.new-members-info { - display: flex; - flex-direction: column; - align-self: flex-start; - padding: 12px; - - @media screen and (max-width: 1279px) { - flex-direction: row; - } - - span.total-count { - color: var(--center-channel-color); - font-family: 'Metropolis'; - font-size: 52px; - font-style: normal; - font-weight: 400; - line-height: 48px; - - @media screen and (max-width: 1279px) { - margin-top: 8px; - margin-left: 12px; - } - } - - .members-info { - display: flex; - flex-direction: column; - - @media screen and (max-width: 1279px) { - margin-top: 0; - margin-left: 24px; - } - - .time-range-info { - margin-top: 2px; - color: rgba(var(--center-channel-color-rgb), 0.64); - font-family: 'Open Sans'; - font-size: 12px; - font-style: normal; - font-weight: 400; - line-height: 16px; - } - - button.see-all-button { - display: flex; - align-items: center; - align-self: flex-start; - padding: 0; - border: none; - margin-top: 5px; - background: none; - color: var(--button-bg); - font-family: 'Open Sans'; - font-size: 12px; - font-style: normal; - font-weight: 600; - line-height: 10px; - - .icon { - width: 14px; - height: 14px; - font-size: 14px; - } - } - } - } - } - } - - .empty-state { - display: flex; - flex: 1; - flex-direction: column; - align-items: center; - margin: 65px 0; - - .empty-state-emoticon { - display: flex; - padding: 10px; - margin-bottom: 8px; - background: rgba(var(--center-channel-color-rgb), 0.08); - border-radius: 50px; - - i.icon { - width: 20px; - color: rgba(var(--center-channel-color-rgb), 0.56); - font-size: 20px; - } - } - - .empty-state-text { - max-width: 120px; - color: rgba(var(--center-channel-color-rgb), 0.72); - font-family: 'Open Sans'; - font-size: 12px; - font-style: normal; - font-weight: 400; - line-height: 16px; - text-align: center; - } - } - } -} diff --git a/webapp/channels/src/components/activity_and_insights/activity_and_insights.tsx b/webapp/channels/src/components/activity_and_insights/activity_and_insights.tsx deleted file mode 100644 index 875d70d191..0000000000 --- a/webapp/channels/src/components/activity_and_insights/activity_and_insights.tsx +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo} from 'react'; - -import classNames from 'classnames'; - -import './activity_and_insights.scss'; - -import Insights from './insights/insights'; - -const ActivityAndInsights = () => { - /** - * Here we can do a check to see if both insights and activity are enabled. If that condition is true we can render the tabbed header. - * Otherwise we can render either insights or activity based on which flag is enabled. - */ - return ( -
- -
- ); -}; - -export default memo(ActivityAndInsights); diff --git a/webapp/channels/src/components/activity_and_insights/activity_and_insights_link/activity_and_insights_link.scss b/webapp/channels/src/components/activity_and_insights/activity_and_insights_link/activity_and_insights_link.scss deleted file mode 100644 index c2f77c411f..0000000000 --- a/webapp/channels/src/components/activity_and_insights/activity_and_insights_link/activity_and_insights_link.scss +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -#SidebarContainer .SidebarInsights { - .SidebarChannel { - height: 100%; - } - - .SidebarLink { - width: 100%; - - .icon { - padding: 3px; - margin-right: 10px; - margin-left: 1px; - - i { - vertical-align: middle; - } - } - - &:hover { - padding-right: 16px; - - .badge { - position: initial; - visibility: visible; - } - } - } -} - -// legacy -.sidebar--left .SidebarInsights { - padding-bottom: 0; - margin-top: 13px; - - .icon { - width: auto; - padding-left: 1px; - margin-top: 1px; - } - - .SidebarChannelLinkLabel_wrapper { - display: flex; - flex: 1; - } - - .badge { - margin: 0 12px 0 0; - } -} - -.tippy-box[data-placement^=right] { - &.tour-tip__box { - &.insights-tip { - .tour-tip__body { - img.insights-img { - margin-top: 2.4rem; - } - } - - .tippy-arrow::before { - border-width: 0; - transform-origin: 6px 6px; - } - } - } -} diff --git a/webapp/channels/src/components/activity_and_insights/activity_and_insights_link/activity_and_insights_link.tsx b/webapp/channels/src/components/activity_and_insights/activity_and_insights_link/activity_and_insights_link.tsx deleted file mode 100644 index 04293386d6..0000000000 --- a/webapp/channels/src/components/activity_and_insights/activity_and_insights_link/activity_and_insights_link.tsx +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {useCallback} from 'react'; -import {Link, useRouteMatch, useLocation, matchPath} from 'react-router-dom'; -import {useIntl} from 'react-intl'; -import {useDispatch, useSelector} from 'react-redux'; -import classNames from 'classnames'; -import {ChartLineIcon} from '@mattermost/compass-icons/components'; -import {insightsAreEnabled} from 'mattermost-redux/selectors/entities/preferences'; -import {getIsRhsOpen, getRhsState} from 'selectors/rhs'; - -import {t} from 'utils/i18n'; -import {RHSStates} from 'utils/constants'; - -import {trackEvent} from 'actions/telemetry_actions'; -import {closeRightHandSide} from 'actions/views/rhs'; - -import InsightsTourTip from './insights_tour_tip/insights_tour_tip'; - -import './activity_and_insights_link.scss'; - -const ActivityAndInsightsLink = () => { - const {formatMessage} = useIntl(); - const dispatch = useDispatch(); - const insightsEnabled = useSelector(insightsAreEnabled); - const rhsOpen = useSelector(getIsRhsOpen); - const rhsState = useSelector(getRhsState); - - const {url} = useRouteMatch(); - const {pathname} = useLocation(); - const inInsights = matchPath(pathname, {path: '/:team/activity-and-insights'}) != null; - - const openInsights = useCallback((e) => { - e.stopPropagation(); - trackEvent('insights', 'sidebar_open_insights'); - if (rhsOpen && rhsState === RHSStates.EDIT_HISTORY) { - dispatch(closeRightHandSide()); - } - }, [rhsOpen, rhsState]); - - if (!insightsEnabled) { - return null; - } - - return ( - - ); -}; - -export default ActivityAndInsightsLink; diff --git a/webapp/channels/src/components/activity_and_insights/activity_and_insights_link/insights_tour_tip/insights_tour_tip.tsx b/webapp/channels/src/components/activity_and_insights/activity_and_insights_link/insights_tour_tip/insights_tour_tip.tsx deleted file mode 100644 index 8402157f06..0000000000 --- a/webapp/channels/src/components/activity_and_insights/activity_and_insights_link/insights_tour_tip/insights_tour_tip.tsx +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useCallback, useEffect, useState} from 'react'; -import {FormattedMessage} from 'react-intl'; -import {useDispatch, useSelector} from 'react-redux'; -import {useHistory} from 'react-router-dom'; - -import {setInsightsInitialisationState} from 'mattermost-redux/actions/preferences'; - -import {getBool} from 'mattermost-redux/selectors/entities/preferences'; -import {getCurrentRelativeTeamUrl} from 'mattermost-redux/selectors/entities/teams'; - -import {GlobalState as EntitiesGlobalState} from '@mattermost/types/store'; - -import {Preferences} from 'mattermost-redux/constants'; - -import {showInsightsPulsatingDot} from 'selectors/insights'; - -import {GlobalState} from 'types/store'; - -import insightsPreview from 'images/Insights-Preview-Image.jpg'; - -import {TourTip, useMeasurePunchouts} from '@mattermost/components'; -import {OnboardingTaskCategory, OnboardingTaskList} from 'components/onboarding_tasks'; - -const title = ( - -); - -const screen = ( - <> - - - - -); - -const prevBtn = ( - -); - -const nextBtn = ( - -); - -const InsightsTourTip = () => { - const dispatch = useDispatch(); - const history = useHistory(); - - const showTip = useSelector(showInsightsPulsatingDot); - const showTaskList = useSelector((state: EntitiesGlobalState) => getBool(state, OnboardingTaskCategory, OnboardingTaskList.ONBOARDING_TASK_LIST_SHOW)); - const teamUrl = useSelector((state: GlobalState) => getCurrentRelativeTeamUrl(state)); - const nextUrl = `${teamUrl}/activity-and-insights`; - - const [tipOpened, setTipOpened] = useState(showTip); - - const handleDismiss = useCallback((e: React.MouseEvent) => { - e.stopPropagation(); - dispatch(setInsightsInitialisationState({[Preferences.INSIGHTS_VIEWED]: true})); - setTipOpened(false); - }, []); - - const handleNext = useCallback(() => { - dispatch(setInsightsInitialisationState({[Preferences.INSIGHTS_VIEWED]: true})); - setTipOpened(false); - history.push(nextUrl); - }, []); - - const handleOpen = useCallback((e: React.MouseEvent) => { - e.stopPropagation(); - e.preventDefault(); - - if (tipOpened) { - dispatch(setInsightsInitialisationState({[Preferences.INSIGHTS_VIEWED]: true})); - setTipOpened(false); - } else { - setTipOpened(true); - } - }, []); - - const isOnboardingOngoing = useCallback(() => { - if (showTaskList) { - return true; - } - return false; - }, [showTaskList]); - - const overlayPunchOut = useMeasurePunchouts(['sidebar-insights-button'], []); - - useEffect(() => { - // If the user has ongoing onboarding steps we want to just remove the insights intro modal in order to not overburden with tips - if (showTip && isOnboardingOngoing()) { - dispatch(setInsightsInitialisationState({[Preferences.INSIGHTS_VIEWED]: true})); - } - }, [showTip, isOnboardingOngoing]); - - return ( - <> - { - (showTip && !isOnboardingOngoing()) && - - } - - ); -}; - -export default memo(InsightsTourTip); diff --git a/webapp/channels/src/components/activity_and_insights/insights/__snapshots__/insights.test.tsx.snap b/webapp/channels/src/components/activity_and_insights/insights/__snapshots__/insights.test.tsx.snap deleted file mode 100644 index 5b26d74e24..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/__snapshots__/insights.test.tsx.snap +++ /dev/null @@ -1,37 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`components/activity_and_insights/insights should match snapshot 1`] = ` - - - -`; diff --git a/webapp/channels/src/components/activity_and_insights/insights/card/__snapshots__/card.test.tsx.snap b/webapp/channels/src/components/activity_and_insights/insights/card/__snapshots__/card.test.tsx.snap deleted file mode 100644 index aac505806d..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/card/__snapshots__/card.test.tsx.snap +++ /dev/null @@ -1,79 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`components/activity_and_insights/insights/insights_title should match snapshot with data 1`] = ` - - -
-
-

- Top channels -

-
-
- Top channels subtitle -
-
- -
-
- test data -
-
-`; - -exports[`components/activity_and_insights/insights/insights_title should match snapshot with no data 1`] = ` - - -
-
-

-

-
-
- - -
- -`; diff --git a/webapp/channels/src/components/activity_and_insights/insights/card/card.scss b/webapp/channels/src/components/activity_and_insights/insights/card/card.scss deleted file mode 100644 index 9f60b70495..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/card/card.scss +++ /dev/null @@ -1,129 +0,0 @@ -.Card.insights-card { - border: 1px solid rgba(var(--center-channel-color-rgb), 0.08); - margin: 8px; - border-radius: 8px; - box-shadow: none; - - .Card__header { - position: relative; - display: flex; - align-items: center; - padding: 24px; - - &:hover { - cursor: pointer; - } - - .title-and-subtitle { - display: flex; - flex-direction: column; - - .text-top { - display: block; - - h2 { - margin: 0; - font-size: 16px; - font-weight: 600; - line-height: 24px; - } - } - - .text-bottom { - display: block; - color: rgba(var(--center-channel-color-rgb), 0.72); - font-size: 12px; - font-weight: 400; - line-height: 16px; - } - } - - button.icon { - position: absolute; - top: 20px; - right: 20px; - padding: 8px; - border: none; - background: none; - border-radius: 4px; - color: rgba(var(--center-channel-color-rgb), 0.56); - - &:hover { - background-color: rgba(var(--center-channel-color-rgb), 0.08); - color: rgba(var(--center-channel-color-rgb), 0.56); - } - - i.icon-chevron-right { - width: 16px; - height: 16px; - font-size: 16px; - } - } - - .Card__hr { - display: none; - } - } - - .Card__body { - padding: 0 24px 24px; - - &.expanding { - transition: none; - } - - &.expanded { - overflow: visible; - margin: 0; - } - } - - &:hover { - border-color: rgba(var(--center-channel-color-rgb), 0.16); - box-shadow: 0 6px 14px rgb(0 0 0 / 12%); - } - - &.large { - width: auto; - clear: both; - - .Card__header { - padding-bottom: 16px; - } - - @media screen and (min-width: 1680px) { - width: 1264px; - align-self: center; - } - - @media screen and (max-width: 1019px) { - width: calc(100% - 16px); - } - } - - &.medium { - width: calc((100%/2) - 16px); - float: left; - } - - &.small { - width: calc((100%/3) - 16px); - min-height: 310px; - float: left; - } - - @media (max-width: 768px) { - margin: 8px 0; - - &:nth-of-type(4) { - margin: 8px 0 0; - } - - &.large, - &.medium, - &.small { - width: 100%; - min-height: auto; - } - } -} diff --git a/webapp/channels/src/components/activity_and_insights/insights/card/card.test.tsx b/webapp/channels/src/components/activity_and_insights/insights/card/card.test.tsx deleted file mode 100644 index 0d3af319fa..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/card/card.test.tsx +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; - -import {shallow} from 'enzyme'; - -import {CardSizes} from '@mattermost/types/insights'; - -import InsightsCard from './card'; - -describe('components/activity_and_insights/insights/insights_title', () => { - const props = { - class: '', - children: <>, - title: '', - subTitle: '', - size: CardSizes.small, - onClick: jest.fn(), - }; - - test('should match snapshot with no data', () => { - const wrapper = shallow( - , - ); - expect(wrapper).toMatchSnapshot(); - }); - - test('should match snapshot with data', () => { - const wrapper = shallow( - - {'test data'} - , - ); - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/webapp/channels/src/components/activity_and_insights/insights/card/card.tsx b/webapp/channels/src/components/activity_and_insights/insights/card/card.tsx deleted file mode 100644 index 6fb3f40c7d..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/card/card.tsx +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo} from 'react'; -import {useIntl} from 'react-intl'; -import classNames from 'classnames'; - -import {CardSize, CardSizes} from '@mattermost/types/insights'; - -import Card from 'components/card/card'; -import CardHeader from 'components/card/card_header'; - -import './card.scss'; - -type Props = { - class: string; - children: React.ReactNode; - title: string; - subTitle?: string; - size: CardSize; - onClick: () => void; -} - -const InsightsCard = (props: Props) => { - const {formatMessage} = useIntl(); - - return ( - - -
-
-

- {props.title} -

-
-
- {props.subTitle} -
-
- -
-
- {props.children} -
-
- ); -}; - -export default memo(InsightsCard); diff --git a/webapp/channels/src/components/activity_and_insights/insights/hooks.ts b/webapp/channels/src/components/activity_and_insights/insights/hooks.ts deleted file mode 100644 index 544d42df3e..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/hooks.ts +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import {useSelector} from 'react-redux'; - -import {getCloudSubscription, getSubscriptionProduct} from 'mattermost-redux/selectors/entities/cloud'; -import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general'; - -import {isCloudLicense} from 'utils/license_utils'; -import {CloudProducts, InsightsScopes} from 'utils/constants'; - -import {setGlobalItem} from 'actions/storage'; - -import {useGlobalState} from 'stores/hooks'; - -/** - * Returns some checks for free trial users or starter licenses - */ -export function useLicenseChecks(): {isStarterFree: boolean; isFreeTrial: boolean; isEnterpriseReady: boolean} { - const subscription = useSelector(getCloudSubscription); - const license = useSelector(getLicense); - const subscriptionProduct = useSelector(getSubscriptionProduct); - const config = useSelector(getConfig); - - const isCloud = isCloudLicense(license); - const isCloudStarterFree = isCloud && subscriptionProduct?.sku === CloudProducts.STARTER; - const isCloudFreeTrial = isCloud && subscription?.is_free_trial === 'true'; - - const isEnterpriseReady = config.BuildEnterpriseReady === 'true'; - const isSelfHostedStarter = isEnterpriseReady && (license.IsLicensed === 'false'); - const isSelfHostedFreeTrial = license.IsTrial === 'true'; - - const isStarterFree = isCloudStarterFree || isSelfHostedStarter; - const isFreeTrial = isCloudFreeTrial || isSelfHostedFreeTrial; - - return { - isStarterFree, - isFreeTrial, - isEnterpriseReady, - }; -} - -export function useGetFilterType(): [string, (value: string) => ReturnType] { - const {isStarterFree, isEnterpriseReady} = useLicenseChecks(); - const [filterType, setFilterType] = useGlobalState(InsightsScopes.TEAM, 'insightsScope'); - - let returnedFilterType = filterType; - - // Force MY Insights for free trial users - if (isStarterFree || !isEnterpriseReady) { - returnedFilterType = InsightsScopes.MY; - } - - return [ - returnedFilterType, - setFilterType, - ]; -} diff --git a/webapp/channels/src/components/activity_and_insights/insights/insights.test.tsx b/webapp/channels/src/components/activity_and_insights/insights/insights.test.tsx deleted file mode 100644 index f927727c29..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/insights.test.tsx +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import {shallow} from 'enzyme'; -import React from 'react'; -import * as redux from 'react-redux'; - -import mockStore from 'tests/test_store'; - -import Insights from './insights'; - -describe('components/activity_and_insights/insights', () => { - const store = mockStore({}); - - test('should match snapshot', () => { - const wrapper = shallow( - - - , - ); - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/webapp/channels/src/components/activity_and_insights/insights/insights.tsx b/webapp/channels/src/components/activity_and_insights/insights/insights.tsx deleted file mode 100644 index c9c614a15d..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/insights.tsx +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useEffect, useCallback} from 'react'; -import {useDispatch, useSelector} from 'react-redux'; - -import {trackEvent} from 'actions/telemetry_actions'; -import {suppressRHS, unsuppressRHS} from 'actions/views/rhs'; -import LocalStorageStore from 'stores/local_storage_store'; -import {useGlobalState} from 'stores/hooks'; - -import {InsightsScopes, PreviousViewedTypes, suitePluginIds} from 'utils/constants'; -import {useProducts} from 'utils/products'; - -import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users'; -import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; - -import {selectLhsItem} from 'actions/views/lhs'; -import {LhsItemType, LhsPage} from 'types/store/lhs'; - -import {CardSizes, InsightsWidgetTypes, TimeFrame, TimeFrames} from '@mattermost/types/insights'; - -import InsightsHeader from './insights_header/insights_header'; -import TopChannels from './top_channels/top_channels'; -import TopReactions from './top_reactions/top_reactions'; -import TopThreads from './top_threads/top_threads'; -import TopBoards from './top_boards/top_boards'; -import LeastActiveChannels from './least_active_channels/least_active_channels'; -import TopPlaybooks from './top_playbooks/top_playbooks'; -import TopDMsAndNewMembers from './top_dms_and_new_members/top_dms_and_new_members'; - -import {useGetFilterType} from './hooks'; - -import './../activity_and_insights.scss'; - -type SelectOption = { - value: string; - label: string; -} - -const Insights = () => { - const dispatch = useDispatch(); - const products = useProducts(); - - const focalboardEnabled = false; - let playbooksEnabled = false; - if (products) { - products.forEach((product) => { - if (product.pluginId === suitePluginIds.playbooks) { - playbooksEnabled = true; - } - }); - } - - const currentUserId = useSelector(getCurrentUserId); - const currentTeamId = useSelector(getCurrentTeamId); - - const [filterType, setFilterType] = useGetFilterType(); - const [timeFrame, setTimeFrame] = useGlobalState(TimeFrames.INSIGHTS_7_DAYS as string, 'insightsTimeFrame'); - - const setFilterTypeTeam = useCallback(() => { - trackEvent('insights', 'change_scope_to_team_insights'); - setFilterType(InsightsScopes.TEAM); - }, []); - - const setFilterTypeMy = useCallback(() => { - trackEvent('insights', 'change_scope_to_my_insights'); - setFilterType(InsightsScopes.MY); - }, []); - - const setTimeFrameValue = useCallback((value: SelectOption) => { - setTimeFrame(value.value); - }, []); - - useEffect(() => { - dispatch(selectLhsItem(LhsItemType.Page, LhsPage.Insights)); - dispatch(suppressRHS); - const penultimateType = LocalStorageStore.getPreviousViewedType(currentUserId, currentTeamId); - - if (penultimateType !== PreviousViewedTypes.INSIGHTS) { - LocalStorageStore.setPenultimateViewedType(currentUserId, currentTeamId, penultimateType); - LocalStorageStore.setPreviousViewedType(currentUserId, currentTeamId, PreviousViewedTypes.INSIGHTS); - } - - return () => { - dispatch(unsuppressRHS); - }; - }, []); - - return ( - <> - -
- -
- - { - focalboardEnabled && - - } - -
- -
- - { - playbooksEnabled && - - } -
- -
- - ); -}; - -export default memo(Insights); diff --git a/webapp/channels/src/components/activity_and_insights/insights/insights_header/insights_header.scss b/webapp/channels/src/components/activity_and_insights/insights/insights_header/insights_header.scss deleted file mode 100644 index 072fd1865a..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/insights_header/insights_header.scss +++ /dev/null @@ -1,81 +0,0 @@ -.Insights___header { - &.Header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 15px 12px; - border-bottom: 1px solid rgba(var(--center-channel-color-rgb), 0.16); - background-color: var(--center-channel-bg); - grid-area: header; - - .insights-title { - display: flex; - align-items: center; - padding: 2px 6px 2px 8px; - border: none; - margin: 0; - background-color: transparent; - border-radius: 4px; - color: rgba(var(--center-channel-color-rgb), 1); - font-family: 'Metropolis', sans-serif; - font-size: 16px; - font-weight: 600; - line-height: 24px; - } - - button.insights-title { - i.icon { - width: 16px; - height: 16px; - - &::before { - margin: 0; - } - } - - &:hover { - background-color: rgba(var(--center-channel-color-rgb), 0.08); - cursor: pointer; - } - } - - .MenuWrapper--open { - button.insights-title { - background-color: rgba(var(--button-bg-rgb), 0.08); - } - } - - .MenuItem { - i.icon { - width: 16px; - height: 16px; - color: rgba(var(--center-channel-color-rgb), 0.56); - - &::before { - margin: 0; - } - } - - .RestrictedIndicator__icon-tooltip-container { - align-self: center; - padding: 7px 10px; - } - } - - .react-select { - .react-select__control { - height: 32px; - min-height: 32px; - } - - .react-select__value-container { - padding-left: 12px; - } - - .react-select__indicators { - padding-right: 10px; - color: rgba(var(--center-channel-color-rgb), 0.64); - } - } - } -} diff --git a/webapp/channels/src/components/activity_and_insights/insights/insights_header/insights_header.tsx b/webapp/channels/src/components/activity_and_insights/insights/insights_header/insights_header.tsx deleted file mode 100644 index 02c21ec22f..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/insights_header/insights_header.tsx +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo} from 'react'; - -import classNames from 'classnames'; - -import InsightsTitle from '../insights_title/insights_title'; -import TimeFrameDropdown from '../time_frame_dropdown/time_frame_dropdown'; - -import './insights_header.scss'; - -type SelectOption = { - value: string; - label: string; -} - -type Props = { - filterType: string; - setFilterTypeTeam: () => void; - setFilterTypeMy: () => void; - timeFrame: string; - setTimeFrame: (value: SelectOption) => void; -} - -const InsightsHeader = (props: Props) => { - return ( -
-
- -
-
- -
-
- ); -}; - -export default memo(InsightsHeader); diff --git a/webapp/channels/src/components/activity_and_insights/insights/insights_modal/__snapshots__/insights_modal.test.tsx.snap b/webapp/channels/src/components/activity_and_insights/insights/insights_modal/__snapshots__/insights_modal.test.tsx.snap deleted file mode 100644 index 5c6f81aed9..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/insights_modal/__snapshots__/insights_modal.test.tsx.snap +++ /dev/null @@ -1,139 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`components/activity_and_insights/insights/insights_modal should match snapshot with My insights 1`] = ` - - -
- - My top reactions - -
- Reactions I've used the most -
-
- -
- - - -
-`; - -exports[`components/activity_and_insights/insights/insights_modal should match snapshot with team 1`] = ` - - -
- - Top reactions - -
- The team's most-used reactions -
-
- -
- - - -
-`; diff --git a/webapp/channels/src/components/activity_and_insights/insights/insights_modal/insights_modal.scss b/webapp/channels/src/components/activity_and_insights/insights/insights_modal/insights_modal.scss deleted file mode 100644 index 7dd84714f5..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/insights_modal/insights_modal.scss +++ /dev/null @@ -1,428 +0,0 @@ -.insights-modal { - &.modal-dialog { - margin-top: calc(50vh - 340px); - } - - &.join-channel-modal { - max-width: 600px; - } - - .modal-content { - .modal-header { - align-items: flex-start; - padding: 22px 32px 26px; - - .title-section { - display: flex; - flex: 1; - flex-direction: column; - - .subtitle { - margin-top: 6px; - margin-bottom: 0; - color: rgba(var(--center-channel-color-rgb), 0.72); - font-family: 'Open Sans'; - font-size: 12px; - font-style: normal; - font-weight: 400; - line-height: 16px; - } - } - - .react-select { - margin-right: 40px; - } - - .close { - top: 16px; - } - } - - .modal-body { - padding: 4px 32px 28px; - - .InsightsTable { - padding: 0; - background: var(--center-channel-bg); - - &.TopPlaybooksTable, - &.TopBoardsTable, - &.TopThreadsTable { - .DataGrid_row { - &:hover { - cursor: pointer; - } - } - } - - &.TopDMsTable { - .DataGrid_header { - .DataGrid_cell { - &:nth-of-type(3), - &:nth-of-type(4) { - text-align: right; - } - } - } - } - - &.LeastActiveChannelsTable { - .DataGrid_header { - .DataGrid_cell { - &:nth-of-type(4) { - text-align: right; - } - } - } - } - - .DataGrid_header { - padding: 12px 0; - border-bottom: 1px solid rgba(var(--center-channel-color-rgb), 0.16); - background-color: rgba(var(--center-channel-color-rgb), 0.04); - border-radius: 4px 4px 0 0; - color: var(--center-channel-color); - font-family: 'Open Sans'; - font-size: 14px; - font-style: normal; - font-weight: 600; - line-height: 20px; - - &:last-child { - border-bottom: 1px solid rgba(var(--center-channel-color-rgb), 0.08); - } - - .DataGrid_cell { - padding: 0 16px; - } - } - - .DataGrid_rows { - background: transparent; - - .DataGrid_loading { - padding: 234px 0; - } - - .DataGrid_row { - display: flex; - flex-direction: row; - padding: 0; - border-bottom: 1px solid rgba(var(--center-channel-color-rgb), 0.08); - border-left: none; - background-color: var(--center-channel-bg); - color: var(--center-channel-color); - - &:hover { - border-left: none; - background-color: rgba(var(--center-channel-color-rgb), 0.08); - color: var(--center-channel-color); - } - - &:nth-of-type(2) { - span.horizontal-bar { - opacity: 0.9; - } - } - - &:nth-of-type(3) { - span.horizontal-bar { - opacity: 0.8; - } - } - - &:nth-of-type(4) { - span.horizontal-bar { - opacity: 0.7; - } - } - - &:nth-of-type(5) { - span.horizontal-bar { - opacity: 0.6; - } - } - - &:nth-of-type(6) { - span.horizontal-bar { - opacity: 0.5; - } - } - - &:nth-of-type(7) { - span.horizontal-bar { - opacity: 0.5; - } - } - - &:nth-of-type(8) { - span.horizontal-bar { - opacity: 0.4; - } - } - - &:nth-of-type(9) { - span.horizontal-bar { - opacity: 0.3; - } - } - - &:nth-of-type(10) { - span.horizontal-bar { - opacity: 0.3; - } - } - - .DataGrid_cell { - display: flex; - align-items: center; - padding: 0 16px; - margin: 0; - white-space: normal; - - &.message-count { - justify-content: end; - } - - &.rankCell { - flex: 0.2 0; - } - - .emoticon { - margin: 12px 12px 12px 0; - } - - span.replies, - span.cell-text { - display: inline-block; - padding: 14px 0; - font-family: 'Open Sans'; - font-size: 14px; - font-style: normal; - font-weight: 400; - line-height: 20px; - } - - .channel-display-name { - display: flex; - overflow: hidden; - color: var(--center-channel-color); - - &:hover { - text-decoration: none; - } - - .icon { - color: rgba(var(--center-channel-color-rgb), 0.56); - } - - span.cell-text { - overflow: hidden; - margin-left: 4px; - font-weight: 600; - text-overflow: ellipsis; - white-space: nowrap; - } - } - - .times-used-container { - display: flex; - width: 100%; - align-items: center; - - span.cell-text { - flex: 0.1 0; - text-align: right; - } - - span.horizontal-bar { - height: 8px; - margin-left: 16px; - background: var(--button-bg); - border-radius: 6px; - - &.top-dms { - background: var(--online-indicator); - opacity: 1; - } - } - } - - .thread-item { - width: 100%; - padding: 12px 0; - - .preview { - height: auto; - } - } - - .board-item { - display: flex; - align-items: center; - - span.board-icon { - display: flex; - width: 24px; - height: 24px; - align-items: center; - justify-content: center; - font-size: 24px; - } - - span.board-title { - overflow: hidden; - max-width: 409px; - margin-left: 12px; - font-family: 'Open Sans'; - font-size: 14px; - font-style: normal; - font-weight: 600; - line-height: 20px; - text-overflow: ellipsis; - white-space: nowrap; - } - } - - .replies, - span.board-updates, - .Avatars___xs { - margin-left: auto; - } - - &.last-activity { - justify-content: flex-end; - margin-right: 8px; - - .timestamp { - overflow: hidden; - color: rgba(var(--center-channel-color-rgb), 0.72); - text-overflow: ellipsis; - - time { - white-space: nowrap; - } - } - } - - .role { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - &.actions { - overflow: visible; - } - - .user-info { - display: flex; - overflow: hidden; - align-items: center; - - &:hover { - text-decoration: none; - } - - .display-name { - overflow: hidden; - margin-left: 12px; - color: var(--center-channel-color); - font-family: 'Open Sans'; - font-size: 14px; - font-style: normal; - font-weight: 600; - line-height: 20px; - text-overflow: ellipsis; - white-space: nowrap; - } - } - - time { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - } - } - } - } - - .button-footer { - padding-top: 24px; - text-align: right; - - button { - height: 40px; - border-radius: 4px; - font-size: 14px; - font-weight: 600; - - &.join-channel-cancel { - padding: 13px 20px; - margin-right: 8px; - background: rgba(var(--button-bg-rgb), 0.08); - color: rgb(var(--button-bg-rgb)); - line-height: 14px; - } - - &.save-button { - padding: 0 20px; - } - } - } - - .pagination-buttons { - display: flex; - align-items: center; - justify-content: end; - - .pagination { - margin-right: 4px; - color: var(--center-channel-color); - font-family: 'Open Sans'; - font-size: 14px; - font-style: normal; - font-weight: 400; - line-height: 20px; - } - - .pagination-button { - padding: 8px; - border: none; - background: none; - border-radius: 4px; - color: rgba(var(--center-channel-color-rgb), 0.56); - - &:hover { - background-color: rgba(var(--center-channel-color-rgb), 0.08); - color: rgba(var(--center-channel-color-rgb), 0.56); - } - - &:disabled { - &:hover { - background-color: transparent; - color: rgba(var(--center-channel-color-rgb), 0.56); - } - } - - &:nth-of-type(1) { - margin-right: 4px; - } - - i { - width: 16px; - height: 16px; - font-size: 16px; - } - } - } - } - } -} - -@media (min-width: 768px) { - .insights-modal { - width: 800px; - } -} diff --git a/webapp/channels/src/components/activity_and_insights/insights/insights_modal/insights_modal.test.tsx b/webapp/channels/src/components/activity_and_insights/insights/insights_modal/insights_modal.test.tsx deleted file mode 100644 index eef1ba37ee..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/insights_modal/insights_modal.test.tsx +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; - -import {shallow} from 'enzyme'; - -import {InsightsWidgetTypes, TimeFrames} from '@mattermost/types/insights'; - -import InsightsModal from './insights_modal'; - -describe('components/activity_and_insights/insights/insights_modal', () => { - const props = { - filterType: 'TEAM', - onExited: jest.fn(), - widgetType: InsightsWidgetTypes.TOP_REACTIONS, - title: 'Top reactions', - subtitle: 'The team\'s most-used reactions', - timeFrame: TimeFrames.INSIGHTS_7_DAYS, - setShowModal: jest.fn(), - }; - - test('should match snapshot with team', () => { - const wrapper = shallow( - , - ); - expect(wrapper).toMatchSnapshot(); - }); - - test('should match snapshot with My insights', () => { - const wrapper = shallow( - , - ); - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/webapp/channels/src/components/activity_and_insights/insights/insights_modal/insights_modal.tsx b/webapp/channels/src/components/activity_and_insights/insights/insights_modal/insights_modal.tsx deleted file mode 100644 index 67e31d51f8..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/insights_modal/insights_modal.tsx +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useState, useCallback} from 'react'; - -import {Modal} from 'react-bootstrap'; - -import {InsightsWidgetTypes, TimeFrame} from '@mattermost/types/insights'; - -import TimeFrameDropdown from '../time_frame_dropdown/time_frame_dropdown'; -import TopReactionsTable from '../top_reactions/top_reactions_table/top_reactions_table'; -import TopChannelsTable from '../top_channels/top_channels_table/top_channels_table'; -import TopThreadsTable from '../top_threads/top_threads_table/top_threads_table'; -import TopBoardsTable from '../top_boards/top_boards_table/top_boards_table'; -import LeastActiveChannelsTable from '../least_active_channels/least_active_channels_table/least_active_channels_table'; -import TopPlaybooksTable from '../top_playbooks/top_playbooks_table/top_playbooks_table'; -import TopDMsTable from '../top_dms_and_new_members/top_dms_table/top_dms_table'; -import NewMembersTable from '../top_dms_and_new_members/new_members_table/new_members_table'; - -import './../../activity_and_insights.scss'; -import './insights_modal.scss'; - -type Props = { - onExited: () => void; - widgetType: InsightsWidgetTypes; - title: string; - subtitle: string; - filterType: string; - timeFrame: TimeFrame; - setShowModal?: (show: boolean) => void; -} - -const InsightsModal = (props: Props) => { - const [show, setShow] = useState(true); - const [timeFrame, setTimeFrame] = useState(props.timeFrame); - const [offset, setOffset] = useState(0); - - const setTimeFrameValue = useCallback((value) => { - setTimeFrame(value.value); - setOffset(0); - }, []); - - const doHide = useCallback(() => { - props.setShowModal?.(false); - setShow(false); - }, []); - - const modalContent = useCallback(() => { - switch (props.widgetType) { - case InsightsWidgetTypes.TOP_CHANNELS: - return ( - - ); - case InsightsWidgetTypes.TOP_REACTIONS: - return ( - - ); - case InsightsWidgetTypes.TOP_THREADS: - return ( - - ); - case InsightsWidgetTypes.TOP_BOARDS: - return ( - - ); - case InsightsWidgetTypes.LEAST_ACTIVE_CHANNELS: - return ( - - ); - case InsightsWidgetTypes.TOP_PLAYBOOKS: - return ( - - ); - case InsightsWidgetTypes.TOP_DMS: - return ( - - ); - case InsightsWidgetTypes.NEW_TEAM_MEMBERS: - return ( - - ); - default: - return null; - } - }, [props.widgetType, timeFrame, offset]); - - return ( - - -
- - {props.title} - -
- {props.subtitle} -
-
- -
- - {modalContent()} - -
- ); -}; - -export default memo(InsightsModal); diff --git a/webapp/channels/src/components/activity_and_insights/insights/insights_title/insights_title.test.tsx b/webapp/channels/src/components/activity_and_insights/insights/insights_title/insights_title.test.tsx deleted file mode 100644 index fd053d3698..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/insights_title/insights_title.test.tsx +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; - -import {renderWithFullContext, screen} from 'tests/react_testing_utils'; - -import InsightsTitle from './insights_title'; - -describe('components/activity_and_insights/insights/insights_title', () => { - const props = { - filterType: 'TEAM', - setFilterTypeMy: jest.fn(), - setFilterTypeTeam: jest.fn(), - }; - - const initialState = { - entities: { - teams: { - currentTeamId: 'team_id1', - teams: { - team_id1: { - id: 'team_id1', - name: 'team1', - }, - }, - }, - users: { - currentUserId: 'current_user_id', - profiles: { - current_user_id: {}, - }, - }, - }, - }; - - test('should match snapshot with My insights', () => { - renderWithFullContext( - , - initialState, - ); - - expect(screen.getByText('My insights')).toBeInTheDocument(); - }); - - test('should match snapshot with Team insights', () => { - renderWithFullContext( - , - initialState, - ); - - expect(screen.getByText('Team insights')).toBeInTheDocument(); - }); -}); diff --git a/webapp/channels/src/components/activity_and_insights/insights/insights_title/insights_title.tsx b/webapp/channels/src/components/activity_and_insights/insights/insights_title/insights_title.tsx deleted file mode 100644 index a3c195ff8a..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/insights_title/insights_title.tsx +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useCallback} from 'react'; -import {FormattedMessage, useIntl} from 'react-intl'; - -import {trackEvent} from 'actions/telemetry_actions'; - -import MenuWrapper from 'components/widgets/menu/menu_wrapper'; -import Menu from 'components/widgets/menu/menu'; -import RestrictedIndicator from 'components/widgets/menu/menu_items/restricted_indicator'; - -import * as Utils from 'utils/utils'; -import {InsightsScopes, LicenseSkus, MattermostFeatures} from 'utils/constants'; -import {FREEMIUM_TO_ENTERPRISE_TRIAL_LENGTH_DAYS} from 'utils/cloud_utils'; - -import {useLicenseChecks} from '../hooks'; -import ExternalLink from 'components/external_link'; - -type Props = { - filterType: string; - setFilterTypeTeam: () => void; - setFilterTypeMy: () => void; -} - -const InsightsTitle = (props: Props) => { - const {formatMessage} = useIntl(); - - const {isStarterFree, isFreeTrial, isEnterpriseReady} = useLicenseChecks(); - - const title = useCallback(() => { - if (props.filterType === InsightsScopes.TEAM) { - return ( - formatMessage({ - id: 'insights.teamHeading', - defaultMessage: 'Team insights', - }) - ); - } - return ( - formatMessage({ - id: 'insights.myHeading', - defaultMessage: 'My insights', - }) - ); - }, [props.filterType]); - - const openInsightsDoc = useCallback(() => { - trackEvent('insights', 'open_insights_doc'); - }, []); - - const openTeamInsightsDoc = useCallback(() => { - trackEvent('insights', 'open_team_insights_doc'); - }, []); - - const openContactSales = useCallback(() => { - trackEvent('insights', 'open_contact_sales_from_insights'); - }, []); - - if (!isEnterpriseReady) { - return ( -
- {title()} -
- ); - } - - return ( - - - - - - - } - text={Utils.localizeMessage('insights.filter.myInsights', 'My insights')} - /> - - - - } - text={Utils.localizeMessage('insights.filter.teamInsights', 'Team insights')} - disabled={isStarterFree} - sibling={(isStarterFree || isFreeTrial) && ( - - - - ), - }, - )} - titleAdminPostTrial={formatMessage({ - id: 'insights.accessModal.titleAdminPostTrial', - defaultMessage: 'Upgrade to access team insights', - })} - messageAdminPostTrial={ - formatMessage( - { - id: 'insights.accessModal.messageAdminPostTrial', - defaultMessage: 'To access your complete {insightsDoc} dashboard, including {teamInsights}, please upgrade your plan to Professional or Enterprise. For questions on upgrading your plan, please {contactSales} for support.', - }, - { - insightsDoc: ( - - - - ), - teamInsights: ( - - - - ), - contactSales: ( - - - - ), - }, - )} - titleEndUser={formatMessage({ - id: 'insights.accessModal.titleEndUser', - defaultMessage: 'Team insights are available in paid plans', - })} - messageEndUser={ - formatMessage( - { - id: 'insights.accessModal.messageEndUser', - defaultMessage: 'To access your complete {insightsDoc} dashboard, including {teamInsights}, please notify your Admin to upgrade your plan to Professional or Enterprise. For questions on upgrading your plan, please {contactSales} for support.', - }, - { - insightsDoc: ( - - - - ), - teamInsights: ( - - - - ), - contactSales: ( - - - - ), - }, - ) - } - /> - )} - /> - - - ); -}; - -export default memo(InsightsTitle); diff --git a/webapp/channels/src/components/activity_and_insights/insights/join_channel_modal/join_channel_modal.test.tsx b/webapp/channels/src/components/activity_and_insights/insights/join_channel_modal/join_channel_modal.test.tsx deleted file mode 100644 index 0c6a26ff6a..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/join_channel_modal/join_channel_modal.test.tsx +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; - -import {Provider} from 'react-redux'; - -import {ReactWrapper} from 'enzyme'; - -import {BrowserRouter} from 'react-router-dom'; - -import {act} from 'tests/react_testing_utils'; -import {mountWithIntl} from 'tests/helpers/intl-test-helper'; -import mockStore from 'tests/test_store'; - -import {PostType} from '@mattermost/types/posts'; - -import JoinChannelModal from './join_channel_modal'; - -const actImmediate = (wrapper: ReactWrapper) => - act( - () => - new Promise((resolve) => { - setImmediate(() => { - wrapper.update(); - resolve(); - }); - }), - ); - -describe('components/activity_and_insights/insights/join_channel_modal', () => { - const props = { - thread: { - channel_id: 'channel1', - channel_display_name: 'nostrum', - channel_name: 'channel1', - participants: [ - 'user1', - ], - user_information: { - id: 'user1', - last_picture_update: 0, - first_name: 'Kathryn', - last_name: 'Mills', - }, - post: { - id: 'post1', - create_at: 1653488972484, - update_at: 1653489070820, - edit_at: 0, - delete_at: 0, - is_pinned: false, - user_id: 'user1', - channel_id: 'channel1', - root_id: '', - original_id: '', - message: 'ducimus sed aut sunt corrupti necessitatibus quasi.\nreiciendis ipsa consequuntur fugiat a eaque.', - type: '' as PostType, - props: {}, - hashtags: '', - pending_post_id: '', - reply_count: 18, - last_reply_at: 0, - participants: null, - metadata: { - embeds: [], - emojis: [], - files: [], - images: {}, - reactions: [], - }, - }, - }, - onExited: jest.fn(), - currentTeamId: 'team_id1', - }; - - const initialState = { - entities: { - teams: { - currentTeamId: 'team_id1', - teams: { - team_id1: { - id: 'team_id1', - name: 'team1', - }, - }, - }, - channels: { - channels: { - channel1: { - id: 'channel1', - team_id: 'team_id1', - name: 'channel1', - }, - }, - myMembers: {}, - }, - general: { - config: {}, - }, - users: { - currentUserId: 'current_user_id', - profiles: { - current_user_id: { - id: 'current_user_id', - }, - user1: { - id: 'user1', - }, - }, - }, - preferences: { - myPreferences: {}, - }, - groups: { - groups: {}, - myGroups: [], - }, - emojis: { - customEmoji: {}, - }, - }, - }; - - test('should match snapshot', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - expect(wrapper.text().includes('You\'ll need to join the nostrum channel to see this thread. Do you want to join nostrum now?')).toBe(true); - }); -}); diff --git a/webapp/channels/src/components/activity_and_insights/insights/join_channel_modal/join_channel_modal.tsx b/webapp/channels/src/components/activity_and_insights/insights/join_channel_modal/join_channel_modal.tsx deleted file mode 100644 index e4b00b6953..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/join_channel_modal/join_channel_modal.tsx +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useState, useCallback} from 'react'; -import {useDispatch, useSelector} from 'react-redux'; -import {FormattedMessage} from 'react-intl'; - -import {Modal} from 'react-bootstrap'; - -import {joinChannel} from 'mattermost-redux/actions/channels'; - -import {TopThread} from '@mattermost/types/insights'; - -import {ActionResult} from 'mattermost-redux/types/actions'; - -import {getCurrentUserId} from 'mattermost-redux/selectors/entities/common'; - -import {selectPost} from 'actions/views/rhs'; - -import {localizeMessage} from 'utils/utils'; - -import SaveButton from 'components/save_button'; - -import './../../activity_and_insights.scss'; - -type Props = { - onExited: () => void; - thread: TopThread; - currentTeamId: string; -} - -const JoinChannelModal = (props: Props) => { - const dispatch = useDispatch(); - - const [show, setShow] = useState(true); - const [saving, setSaving] = useState(false); - - const currentUserId = useSelector(getCurrentUserId); - - const doHide = useCallback(() => { - setShow(false); - }, []); - - const openRHS = useCallback(async () => { - setSaving(true); - const {error} = await dispatch(joinChannel(currentUserId, props.currentTeamId, props.thread.channel_id, props.thread.channel_name)) as ActionResult; - if (!error) { - await dispatch(selectPost(props.thread.post)); - } - doHide(); - }, []); - - return ( - - -
- - - -
-
- - {props.thread.channel_display_name}, - }} - /> -
- - { - e.preventDefault(); - openRHS(); - }} - defaultMessage={localizeMessage('joinChannel.JoinButton', 'Join')} - savingMessage={localizeMessage('joinChannel.joiningButton', 'Joining...')} - /> -
-
-
- ); -}; - -export default memo(JoinChannelModal); diff --git a/webapp/channels/src/components/activity_and_insights/insights/least_active_channels/channel_actions_menu/channel_actions_menu.scss b/webapp/channels/src/components/activity_and_insights/insights/least_active_channels/channel_actions_menu/channel_actions_menu.scss deleted file mode 100644 index 70d60a2b05..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/least_active_channels/channel_actions_menu/channel_actions_menu.scss +++ /dev/null @@ -1,35 +0,0 @@ -.channel-action { - margin-left: auto; - - button.action-wrapper { - display: flex; - width: 32px; - height: 32px; - flex-direction: column; - align-items: center; - padding: 6px 8px; - border: none; - margin-left: auto; - background: transparent; - border-radius: 4px; - color: rgba(var(--center-channel-color-rgb), 0.56); - text-decoration: none; - - &:hover { - background: rgba(var(--center-channel-color-rgb), 0.08); - border-radius: 4px; - color: rgba(var(--center-channel-color-rgb), 0.72); - fill: rgba(var(--center-channel-color-rgb), 0.72); - } - - &:active { - background-color: rgba(var(--button-bg-rgb), 0.08); - color: var(--button-bg); - } - - i.icon { - font-size: 18px; - line-height: 18px; - } - } -} diff --git a/webapp/channels/src/components/activity_and_insights/insights/least_active_channels/channel_actions_menu/channel_actions_menu.tsx b/webapp/channels/src/components/activity_and_insights/insights/least_active_channels/channel_actions_menu/channel_actions_menu.tsx deleted file mode 100644 index 073b6ca5c0..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/least_active_channels/channel_actions_menu/channel_actions_menu.tsx +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useCallback} from 'react'; -import {useDispatch, useSelector} from 'react-redux'; -import {useIntl} from 'react-intl'; - -import {trackEvent} from 'actions/telemetry_actions'; - -import {openModal} from 'actions/views/modals'; - -import {General} from 'mattermost-redux/constants'; - -import {leaveChannel} from 'mattermost-redux/actions/channels'; - -import {getCurrentRelativeTeamUrl} from 'mattermost-redux/selectors/entities/teams'; -import {getMyChannelMembership} from 'mattermost-redux/selectors/entities/channels'; - -import {LeastActiveChannel} from '@mattermost/types/insights'; -import {GlobalState} from '@mattermost/types/store'; - -import Constants, {ModalIdentifiers} from 'utils/constants'; -import {copyToClipboard} from 'utils/utils'; -import {getSiteURL} from 'utils/url'; - -import MenuWrapper from 'components/widgets/menu/menu_wrapper'; -import Menu from 'components/widgets/menu/menu'; -import LeaveChannelModal from 'components/leave_channel_modal'; - -import './channel_actions_menu.scss'; - -type Props = { - channel: LeastActiveChannel; - actionCallback?: () => Promise; -} - -const ChannelActionsMenu = ({channel, actionCallback}: Props) => { - const dispatch = useDispatch(); - const {formatMessage} = useIntl(); - const currentTeamUrl = useSelector(getCurrentRelativeTeamUrl); - const isChannelMember = useSelector((state: GlobalState) => getMyChannelMembership(state, channel.id)); - const isDefault = channel.name === General.DEFAULT_CHANNEL; - - const handleLeave = useCallback(async (e: Event) => { - e.preventDefault(); - trackEvent('insights', 'leave_channel_action'); - - if (channel.type === Constants.PRIVATE_CHANNEL) { - dispatch(openModal({ - modalId: ModalIdentifiers.LEAVE_PRIVATE_CHANNEL_MODAL, - dialogType: LeaveChannelModal, - dialogProps: { - channel, - callback: actionCallback, - }, - })); - } else { - await dispatch(leaveChannel(channel.id)); - actionCallback?.(); - } - }, [channel]); - - const copyLink = useCallback(() => { - trackEvent('insights', 'copy_channel_link_action'); - copyToClipboard(`${getSiteURL()}${currentTeamUrl}/channels/${channel.name}`); - }, [currentTeamUrl, channel]); - - return ( -
- - - - - } - text={formatMessage({id: 'insights.leastActiveChannels.leaveChannel', defaultMessage: 'Leave channel'})} - disabled={false} - isDangerous={true} - show={Boolean(isChannelMember) && !isDefault} - /> - - - } - text={formatMessage({id: 'insights.leastActiveChannels.copyLink', defaultMessage: 'Copy link'})} - disabled={false} - /> - - - -
- ); -}; - -export default memo(ChannelActionsMenu); diff --git a/webapp/channels/src/components/activity_and_insights/insights/least_active_channels/least_active_channels.test.tsx b/webapp/channels/src/components/activity_and_insights/insights/least_active_channels/least_active_channels.test.tsx deleted file mode 100644 index cae270d3a4..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/least_active_channels/least_active_channels.test.tsx +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import {Provider} from 'react-redux'; - -import {ReactWrapper} from 'enzyme'; - -import {BrowserRouter} from 'react-router-dom'; - -import {CardSizes, InsightsWidgetTypes, TimeFrames} from '@mattermost/types/insights'; - -import {mountWithIntl} from 'tests/helpers/intl-test-helper'; -import {act} from 'tests/react_testing_utils'; -import mockStore from 'tests/test_store'; - -import LeastActiveChannels from './least_active_channels'; - -const actImmediate = (wrapper: ReactWrapper) => - act( - () => - new Promise((resolve) => { - setImmediate(() => { - wrapper.update(); - resolve(); - }); - }), - ); - -jest.mock('mattermost-redux/actions/insights', () => ({ - ...jest.requireActual('mattermost-redux/actions/insights'), - getMyLeastActiveChannels: () => ({type: 'adsf', data: {}}), - getLeastActiveChannelsForTeam: () => ({type: 'adsf', - data: { - has_next: false, - items: [ - { - id: 'ztbmh49z7pgtbbuximwxrogxzr', - type: 'O', - display_name: 'ut', - name: 'ratione-1', - last_activity_at: 1660175452131, - participants: [], - }, - { - id: 'fbgmxnxxmfy1z8m855d9m88ipe', - type: 'O', - display_name: 'veritatis', - name: 'minima-3', - last_activity_at: 1660175525869, - participants: [], - }, - { - id: 'uziynciroprq3g6ohhnednoeuw', - type: 'O', - display_name: 'autem', - name: 'aut-8', - last_activity_at: 1660175775169, - participants: [], - }, - { - id: 'uziynciroprq4g6ohhnednoeuw', - type: 'O', - display_name: 'dolor', - name: 'aut-9', - last_activity_at: 0, - participants: [], - }, - ], - }}), -})); - -describe('components/activity_and_insights/insights/top_boards', () => { - const props = { - filterType: 'TEAM', - timeFrame: TimeFrames.INSIGHTS_7_DAYS, - size: CardSizes.small, - widgetType: InsightsWidgetTypes.LEAST_ACTIVE_CHANNELS, - class: 'least-active-channels-card', - timeFrameLabel: 'Last 7 days', - showModal: false, - }; - - const initialState = { - entities: { - teams: { - currentTeamId: 'team_id1', - teams: { - team_id1: { - id: 'team_id1', - name: 'team1', - }, - }, - }, - channels: { - channels: { - ztbmh49z7pgtbbuximwxrogxzr: { - id: 'ztbmh49z7pgtbbuximwxrogxzr', - team_id: 'team_id1', - type: 'O', - display_name: 'ut', - name: 'ratione-1', - last_activity_at: 1660175452131, - }, - fbgmxnxxmfy1z8m855d9m88ipe: { - id: 'fbgmxnxxmfy1z8m855d9m88ipe', - type: 'O', - display_name: 'veritatis', - name: 'minima-3', - last_activity_at: 1660175525869, - team_id: 'team_id1', - }, - uziynciroprq3g6ohhnednoeuw: { - id: 'uziynciroprq3g6ohhnednoeuw', - type: 'O', - display_name: 'autem', - name: 'aut-8', - last_activity_at: 1660175775169, - team_id: 'team_id1', - }, - uziynciroprq4g6ohhnednoeuw: { - id: 'uziynciroprq4g6ohhnednoeuw', - type: 'O', - display_name: 'dolor', - name: 'aut-9', - last_activity_at: 0, - participants: [], - }, - }, - myMembers: {}, - }, - general: { - config: {}, - }, - users: { - currentUserId: 'current_user_id', - profiles: { - current_user_id: { - id: 'current_user_id', - }, - user1: { - id: 'user1', - }, - }, - }, - preferences: { - myPreferences: {}, - }, - groups: { - groups: {}, - myGroups: [], - }, - emojis: { - customEmoji: {}, - }, - }, - }; - - test('check if 4 channels render', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - - expect(wrapper.find('a.channel-row').length).toEqual(4); - }); - - test('check if 0 channels render', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - expect(wrapper.find('.empty-state').length).toEqual(1); - }); -}); diff --git a/webapp/channels/src/components/activity_and_insights/insights/least_active_channels/least_active_channels.tsx b/webapp/channels/src/components/activity_and_insights/insights/least_active_channels/least_active_channels.tsx deleted file mode 100644 index 11668b7fad..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/least_active_channels/least_active_channels.tsx +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useState, useCallback, useEffect, useMemo} from 'react'; -import {useDispatch, useSelector} from 'react-redux'; - -import {LeastActiveChannel} from '@mattermost/types/insights'; - -import {CircleSkeletonLoader, RectangleSkeletonLoader} from '@mattermost/components'; - -import {getMyLeastActiveChannels, getLeastActiveChannelsForTeam} from 'mattermost-redux/actions/insights'; -import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; - -import {InsightsScopes} from 'utils/constants'; - -import widgetHoc, {WidgetHocProps} from '../widget_hoc/widget_hoc'; -import WidgetEmptyState from '../widget_empty_state/widget_empty_state'; - -import LeastActiveChannelsItem from './least_active_channels_item/least_active_channels_item'; - -import './../../activity_and_insights.scss'; - -interface Props { - showModal?: boolean; -} - -const LeastActiveChannels = (props: WidgetHocProps & Props) => { - const dispatch = useDispatch(); - - const [loading, setLoading] = useState(true); - const [leastActiveChannels, setLeastActiveChannels] = useState([] as LeastActiveChannel[]); - - const currentTeamId = useSelector(getCurrentTeamId); - - const getInactiveChannels = useCallback(async () => { - if (props.showModal === false) { - setLoading(true); - if (props.filterType === InsightsScopes.TEAM) { - const data: any = await dispatch(getLeastActiveChannelsForTeam(currentTeamId, 0, 3, props.timeFrame)); - if (data.data?.items) { - setLeastActiveChannels(data.data.items); - } - } else { - const data: any = await dispatch(getMyLeastActiveChannels(currentTeamId, 0, 3, props.timeFrame)); - if (data.data?.items) { - setLeastActiveChannels(data.data.items); - } - } - setLoading(false); - } - }, [props.timeFrame, currentTeamId, props.filterType, props.showModal]); - - useEffect(() => { - getInactiveChannels(); - }, [getInactiveChannels]); - - const skeletonLoader = useMemo(() => { - const entries = []; - for (let i = 0; i < 4; i++) { - entries.push( -
- - - - -
, - ); - } - return entries; - }, []); - - const getListItems = useCallback(() => { - return ( - leastActiveChannels.map((channel, i) => { - return ( - - ); - }) - ); - }, [leastActiveChannels]); - - return ( -
- { - loading && - skeletonLoader - } - { - (leastActiveChannels && !loading) && -
- {getListItems()} -
- } - { - - (leastActiveChannels.length === 0 && !loading) && - - } -
- ); -}; - -export default memo(widgetHoc(LeastActiveChannels)); diff --git a/webapp/channels/src/components/activity_and_insights/insights/least_active_channels/least_active_channels_item/least_active_channels_item.tsx b/webapp/channels/src/components/activity_and_insights/insights/least_active_channels/least_active_channels_item/least_active_channels_item.tsx deleted file mode 100644 index c12e8d3dbe..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/least_active_channels/least_active_channels_item/least_active_channels_item.tsx +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useCallback} from 'react'; -import {useSelector} from 'react-redux'; - -import {FormattedMessage} from 'react-intl'; - -import {Link} from 'react-router-dom'; - -import {trackEvent} from 'actions/telemetry_actions'; - -import {getCurrentRelativeTeamUrl} from 'mattermost-redux/selectors/entities/teams'; - -import {LeastActiveChannel} from '@mattermost/types/insights'; - -import Constants from 'utils/constants'; - -import Avatars from 'components/widgets/users/avatars'; -import Timestamp from 'components/timestamp'; - -import ChannelActionsMenu from '../channel_actions_menu/channel_actions_menu'; - -import './../../../activity_and_insights.scss'; - -type Props = { - channel: LeastActiveChannel; - actionCallback: () => Promise; -} - -const LeastActiveChannelsItem = ({channel, actionCallback}: Props) => { - const currentTeamUrl = useSelector(getCurrentRelativeTeamUrl); - - const trackClickEvent = useCallback(() => { - trackEvent('insights', 'open_channel_from_least_active_channels_widget'); - }, []); - - const iconToDisplay = useCallback(() => { - let iconToDisplay = ; - - if (channel.type === Constants.PRIVATE_CHANNEL) { - iconToDisplay = ; - } - return iconToDisplay; - }, [channel]); - - let timeMessage = ( - - ), - }} - /> - ); - if (channel.last_activity_at === 0) { - timeMessage = ( - - ); - } - return ( - -
-
- - {iconToDisplay()} - - {channel.display_name} -
- - {timeMessage} - -
- - - - ); -}; - -export default memo(LeastActiveChannelsItem); diff --git a/webapp/channels/src/components/activity_and_insights/insights/least_active_channels/least_active_channels_table/least_active_channels_table.scss b/webapp/channels/src/components/activity_and_insights/insights/least_active_channels/least_active_channels_table/least_active_channels_table.scss deleted file mode 100644 index 20a70711a4..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/least_active_channels/least_active_channels_table/least_active_channels_table.scss +++ /dev/null @@ -1,9 +0,0 @@ -.LeastActiveChannelsTable { - .DataGrid_header { - .DataGrid_cell { - &:nth-of-type(3) { - text-align: right; - } - } - } -} diff --git a/webapp/channels/src/components/activity_and_insights/insights/least_active_channels/least_active_channels_table/least_active_channels_table.test.tsx b/webapp/channels/src/components/activity_and_insights/insights/least_active_channels/least_active_channels_table/least_active_channels_table.test.tsx deleted file mode 100644 index 21aa792a52..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/least_active_channels/least_active_channels_table/least_active_channels_table.test.tsx +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import {Provider} from 'react-redux'; - -import {ReactWrapper} from 'enzyme'; - -import {BrowserRouter} from 'react-router-dom'; - -import {TimeFrames} from '@mattermost/types/insights'; - -import {act} from 'tests/react_testing_utils'; -import {mountWithIntl} from 'tests/helpers/intl-test-helper'; -import mockStore from 'tests/test_store'; - -import LeastActiveChannelsTable from './least_active_channels_table'; - -const actImmediate = (wrapper: ReactWrapper) => - act( - () => - new Promise((resolve) => { - setImmediate(() => { - wrapper.update(); - resolve(); - }); - }), - ); - -jest.mock('mattermost-redux/actions/insights', () => ({ - ...jest.requireActual('mattermost-redux/actions/insights'), - getMyLeastActiveChannels: () => ({type: 'adsf', data: {}}), - getLeastActiveChannelsForTeam: () => ({type: 'adsf', - data: { - has_next: false, - items: [ - { - id: 'ztbmh49z7pgtbbuximwxrogxzr', - type: 'O', - display_name: 'ut', - name: 'ratione-1', - last_activity_at: 1660175452131, - participants: [], - }, - { - id: 'fbgmxnxxmfy1z8m855d9m88ipe', - type: 'O', - display_name: 'veritatis', - name: 'minima-3', - last_activity_at: 1660175525869, - participants: [], - }, - { - id: 'uziynciroprq3g6ohhnednoeuw', - type: 'O', - display_name: 'autem', - name: 'aut-8', - last_activity_at: 1660175775169, - participants: [], - }, - ], - }}), -})); - -describe('components/activity_and_insights/insights/top_boards', () => { - const props = { - filterType: 'TEAM', - timeFrame: TimeFrames.INSIGHTS_7_DAYS, - closeModal: jest.fn(), - }; - - const initialState = { - entities: { - teams: { - currentTeamId: 'team_id1', - teams: { - team_id1: { - id: 'team_id1', - name: 'team1', - }, - }, - }, - channels: { - channels: { - ztbmh49z7pgtbbuximwxrogxzr: { - id: 'ztbmh49z7pgtbbuximwxrogxzr', - team_id: 'team_id1', - type: 'O', - display_name: 'ut', - name: 'ratione-1', - last_activity_at: 1660175452131, - }, - fbgmxnxxmfy1z8m855d9m88ipe: { - id: 'fbgmxnxxmfy1z8m855d9m88ipe', - type: 'O', - display_name: 'veritatis', - name: 'minima-3', - last_activity_at: 1660175525869, - team_id: 'team_id1', - }, - uziynciroprq3g6ohhnednoeuw: { - id: 'uziynciroprq3g6ohhnednoeuw', - type: 'O', - display_name: 'autem', - name: 'aut-8', - last_activity_at: 1660175775169, - team_id: 'team_id1', - }, - }, - myMembers: {}, - }, - general: { - config: {}, - }, - users: { - currentUserId: 'current_user_id', - profiles: { - current_user_id: { - id: 'current_user_id', - }, - user1: { - id: 'user1', - }, - }, - }, - preferences: { - myPreferences: {}, - }, - groups: { - groups: {}, - myGroups: [], - }, - emojis: { - customEmoji: {}, - }, - }, - }; - - test('check if 3 channels render', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - - expect(wrapper.find('.DataGrid_row').length).toEqual(3); - }); - - test('check if 0 channels render', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - expect(wrapper.find('.DataGrid_row').length).toEqual(0); - }); -}); diff --git a/webapp/channels/src/components/activity_and_insights/insights/least_active_channels/least_active_channels_table/least_active_channels_table.tsx b/webapp/channels/src/components/activity_and_insights/insights/least_active_channels/least_active_channels_table/least_active_channels_table.tsx deleted file mode 100644 index 23b909cfaa..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/least_active_channels/least_active_channels_table/least_active_channels_table.tsx +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useCallback, useEffect, useMemo, useState} from 'react'; -import {FormattedMessage} from 'react-intl'; -import {useDispatch, useSelector} from 'react-redux'; -import {useHistory} from 'react-router-dom'; - -import classNames from 'classnames'; - -import {trackEvent} from 'actions/telemetry_actions'; - -import {getLeastActiveChannelsForTeam, getMyLeastActiveChannels} from 'mattermost-redux/actions/insights'; - -import {LeastActiveChannel, TimeFrame} from '@mattermost/types/insights'; - -import {getCurrentRelativeTeamUrl, getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; - -import Constants, {InsightsScopes} from 'utils/constants'; - -import Avatars from 'components/widgets/users/avatars'; -import DataGrid, {Row, Column} from 'components/admin_console/data_grid/data_grid'; -import Timestamp from 'components/timestamp'; - -import ChannelActionsMenu from '../channel_actions_menu/channel_actions_menu'; - -import './../../../activity_and_insights.scss'; -import './least_active_channels_table.scss'; - -type Props = { - filterType: string; - timeFrame: TimeFrame; - closeModal: () => void; - -} - -const LeastActiveChannelsTable = (props: Props) => { - const dispatch = useDispatch(); - const history = useHistory(); - - const [loading, setLoading] = useState(true); - const [leastActiveChannels, setLeastActiveChannels] = useState([] as LeastActiveChannel[]); - const currentTeamUrl = useSelector(getCurrentRelativeTeamUrl); - - const currentTeamId = useSelector(getCurrentTeamId); - - const getInactiveChannels = useCallback(async () => { - setLoading(true); - if (props.filterType === InsightsScopes.TEAM) { - const data: any = await dispatch(getLeastActiveChannelsForTeam(currentTeamId, 0, 10, props.timeFrame)); - if (data.data?.items) { - setLeastActiveChannels(data.data.items); - } - } else { - const data: any = await dispatch(getMyLeastActiveChannels(currentTeamId, 0, 10, props.timeFrame)); - if (data.data?.items) { - setLeastActiveChannels(data.data.items); - } - } - setLoading(false); - }, [props.timeFrame, currentTeamId, props.filterType]); - - useEffect(() => { - getInactiveChannels(); - }, [getInactiveChannels]); - - const goToChannel = useCallback((channel: LeastActiveChannel) => { - props.closeModal(); - trackEvent('insights', 'open_channel_from_least_active_channels_modal'); - history.push(`${currentTeamUrl}/channels/${channel.name}`); - }, [props.closeModal]); - - const getColumns = useMemo((): Column[] => { - const columns: Column[] = [ - { - name: ( - - ), - field: 'rank', - className: 'rankCell', - width: 0.07, - }, - { - name: ( - - ), - field: 'channel', - width: 0.48, - }, - { - name: ( - - ), - className: 'last-activity', - field: 'lastActivity', - width: 0.2, - }, - { - name: ( - - ), - className: 'participants', - field: 'participants', - width: 0.15, - }, - { - name: (''), - field: 'actions', - width: 0.1, - className: 'actions', - }, - ]; - return columns; - }, []); - - const getRows = useMemo((): Row[] => { - return leastActiveChannels.map((channel, i) => { - let iconToDisplay = ; - - if (channel.type === Constants.PRIVATE_CHANNEL) { - iconToDisplay = ; - } - - return ( - { - cells: { - rank: ( - - {i + 1} - - ), - channel: ( -
- - {iconToDisplay} - - - {channel.display_name} - -
- ), - lastActivity: ( - - { - channel.last_activity_at === 0 ? - : - - } - - ), - participants: ( - <> - {channel.participants && channel.participants.length > 0 ? ( - - ) : null} - - - ), - actions: ( - - ), - }, - onClick: () => goToChannel(channel), - } - ); - }); - }, [leastActiveChannels]); - - return ( - {}} - previousPage={() => {}} - startCount={1} - endCount={10} - total={0} - className={classNames('InsightsTable', 'LeastActiveChannelsTable')} - /> - ); -}; - -export default memo(LeastActiveChannelsTable); diff --git a/webapp/channels/src/components/activity_and_insights/insights/modal_pagination/modal_pagination.test.tsx b/webapp/channels/src/components/activity_and_insights/insights/modal_pagination/modal_pagination.test.tsx deleted file mode 100644 index 0289ac9d37..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/modal_pagination/modal_pagination.test.tsx +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import {Provider} from 'react-redux'; - -import {BrowserRouter} from 'react-router-dom'; - -import {mountWithIntl} from 'tests/helpers/intl-test-helper'; -import mockStore from 'tests/test_store'; - -import ModalPagination from './modal_pagination'; - -describe('components/activity_and_insights/insights/modal_pagination', () => { - const props = { - hasNext: false, - offset: 0, - setOffset: jest.fn(), - }; - - test('check if 1 - 10 renders', async () => { - const store = await mockStore({}); - const wrapper = mountWithIntl( - - - - - , - ); - expect(wrapper.text().includes('1 - 10')).toBe(true); - }); - - test('check if 20 - 30 renders', async () => { - const store = await mockStore({}); - const wrapper = mountWithIntl( - - - - - , - ); - expect(wrapper.text().includes('20 - 30')).toBe(true); - }); -}); diff --git a/webapp/channels/src/components/activity_and_insights/insights/modal_pagination/modal_pagination.tsx b/webapp/channels/src/components/activity_and_insights/insights/modal_pagination/modal_pagination.tsx deleted file mode 100644 index 3378064111..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/modal_pagination/modal_pagination.tsx +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useCallback} from 'react'; - -type Props = { - offset: number; - hasNext: boolean; - setOffset: (offset: number) => void; -} - -const ModalPagination = ({offset, hasNext, setOffset}: Props) => { - const incrementOffset = useCallback(() => { - setOffset(offset + 1); - }, [offset]); - - const decrementOffset = useCallback(() => { - setOffset(offset - 1); - }, [offset]); - - const pagination = useCallback(() => { - let min = offset + 1; - const max = min * 10; - - if (offset !== 0) { - min = offset * 10; - } - - return ( - - {`${min} - ${max}`} - - ); - }, [offset]); - - return ( -
- {pagination()} - - -
- ); -}; - -export default memo(ModalPagination); diff --git a/webapp/channels/src/components/activity_and_insights/insights/time_frame_dropdown/__snapshots__/time_frame_dropdown.test.tsx.snap b/webapp/channels/src/components/activity_and_insights/insights/time_frame_dropdown/__snapshots__/time_frame_dropdown.test.tsx.snap deleted file mode 100644 index 8e4cb6b41c..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/time_frame_dropdown/__snapshots__/time_frame_dropdown.test.tsx.snap +++ /dev/null @@ -1,52 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`components/activity_and_insights/insights/insights_title should match snapshot with data 1`] = ` -} - onChange={[Function]} - options={ - Array [ - Object { - "label": "Today", - "value": "today", - }, - Object { - "label": "Last 7 days", - "value": "7_day", - }, - Object { - "label": "Last 28 days", - "value": "28_day", - }, - ] - } - styles={ - Object { - "control": [Function], - "indicatorSeparator": [Function], - "menuPortal": [Function], - "option": [Function], - } - } - value={ - Object { - "label": "Last 7 days", - "value": "7_day", - } - } -/> -`; diff --git a/webapp/channels/src/components/activity_and_insights/insights/time_frame_dropdown/time_frame_dropdown.scss b/webapp/channels/src/components/activity_and_insights/insights/time_frame_dropdown/time_frame_dropdown.scss deleted file mode 100644 index be6b3be138..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/time_frame_dropdown/time_frame_dropdown.scss +++ /dev/null @@ -1,24 +0,0 @@ -.ActivityAndInsights, -.insights-modal { - .react-select { - .react-select__control { - height: 32px; - min-height: 32px; - border-color: rgba(var(--center-channel-color-rgb), 0.2); - background: var(--center-channel-bg); - } - - .react-select__value-container { - padding-left: 12px; - } - - .react-select__indicators { - padding-right: 10px; - color: rgba(var(--center-channel-color-rgb), 0.64); - } - - .react-select__input { - color: var(--center-channel-color); - } - } -} diff --git a/webapp/channels/src/components/activity_and_insights/insights/time_frame_dropdown/time_frame_dropdown.test.tsx b/webapp/channels/src/components/activity_and_insights/insights/time_frame_dropdown/time_frame_dropdown.test.tsx deleted file mode 100644 index edd97d3d9a..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/time_frame_dropdown/time_frame_dropdown.test.tsx +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; - -import {shallow} from 'enzyme'; - -import {TimeFrames} from '@mattermost/types/insights'; - -import TimeFrameDropdown from './time_frame_dropdown'; - -describe('components/activity_and_insights/insights/insights_title', () => { - const props = { - timeFrame: TimeFrames.INSIGHTS_7_DAYS, - setTimeFrame: jest.fn(), - }; - - test('should match snapshot with data', () => { - const wrapper = shallow( - , - ); - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/webapp/channels/src/components/activity_and_insights/insights/time_frame_dropdown/time_frame_dropdown.tsx b/webapp/channels/src/components/activity_and_insights/insights/time_frame_dropdown/time_frame_dropdown.tsx deleted file mode 100644 index b7bf7c91ef..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/time_frame_dropdown/time_frame_dropdown.tsx +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useCallback} from 'react'; - -import ReactSelect, {ValueType} from 'react-select'; -import ChevronDownIcon from '@mattermost/compass-icons/components/chevron-down'; - -import {trackEvent} from 'actions/telemetry_actions'; - -import {TimeFrames} from '@mattermost/types/insights'; - -import {localizeMessage} from 'utils/utils'; - -import './time_frame_dropdown.scss'; - -type SelectOption = { - value: string; - label: string; -} -type Props = { - timeFrame: string; - setTimeFrame: (value: SelectOption) => void; -} - -const TimeFrameDropdown = (props: Props) => { - const reactStyles = { - control: (provided: React.CSSProperties) => ({ - ...provided, - width: '140px', - cursor: 'pointer', - fontSize: '12px', - lineHeight: '16px', - }), - indicatorSeparator: (provided: React.CSSProperties) => ({ - ...provided, - display: 'none', - }), - option: (provided: React.CSSProperties) => ({ - ...provided, - cursor: 'pointer', - }), - menuPortal: (provided: React.CSSProperties) => ({ - ...provided, - zIndex: 1100, - }), - }; - - const onTimeFrameChange = (selectedOption: ValueType) => { - if (selectedOption && 'value' in selectedOption) { - trackEvent('insights', `time_frame_selected_${selectedOption.value}`); - props.setTimeFrame(selectedOption); - } - }; - - const CustomDropwdown = () => { - return ( - - - - ); - }; - - const getCurrentTimeFrame = useCallback((): SelectOption => { - const timeFrame = props.timeFrame; - - if (timeFrame === TimeFrames.INSIGHTS_1_DAY) { - return { - value: TimeFrames.INSIGHTS_1_DAY, - label: localizeMessage('insights.timeFrame.today', 'Today'), - }; - } - - if (timeFrame === TimeFrames.INSIGHTS_28_DAYS) { - return { - value: TimeFrames.INSIGHTS_28_DAYS, - label: localizeMessage('insights.timeFrame.longRange', 'Last 28 days'), - }; - } - - return { - value: TimeFrames.INSIGHTS_7_DAYS, - label: localizeMessage('insights.timeFrame.mediumRange', 'Last 7 days'), - }; - }, [props.timeFrame]); - - return ( - - ); -}; - -export default memo(TimeFrameDropdown); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_boards/top_boards.test.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_boards/top_boards.test.tsx deleted file mode 100644 index 97d6db5db7..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_boards/top_boards.test.tsx +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import {Provider} from 'react-redux'; - -import {ReactWrapper} from 'enzyme'; - -import {BrowserRouter} from 'react-router-dom'; - -import {CardSizes, InsightsWidgetTypes, TimeFrames} from '@mattermost/types/insights'; - -import {mountWithIntl} from 'tests/helpers/intl-test-helper'; -import {act} from 'tests/react_testing_utils'; -import mockStore from 'tests/test_store'; - -import TopBoards from './top_boards'; - -jest.mock('react-redux', () => ({ - ...jest.requireActual('react-redux'), - useDispatch: jest.fn().mockReturnValue(() => {}), -})); - -const actImmediate = (wrapper: ReactWrapper) => - act( - () => - new Promise((resolve) => { - setImmediate(() => { - wrapper.update(); - resolve(); - }); - }), - ); - -describe('components/activity_and_insights/insights/top_boards', () => { - const props = { - filterType: 'TEAM', - timeFrame: TimeFrames.INSIGHTS_7_DAYS, - size: CardSizes.small, - widgetType: InsightsWidgetTypes.TOP_BOARDS, - class: 'top-baords-card', - timeFrameLabel: 'Last 7 days', - }; - - const initialState = { - entities: { - teams: { - currentTeamId: 'team_id1', - teams: { - team_id1: { - id: 'team_id1', - name: 'team1', - }, - }, - }, - channels: { - channels: { - channel1: { - id: 'channel1', - team_id: 'team_id1', - name: 'channel1', - }, - }, - }, - general: { - config: {}, - }, - users: { - currentUserId: 'current_user_id', - profiles: { - current_user_id: { - id: 'current_user_id', - }, - user1: { - id: 'user1', - }, - }, - }, - preferences: { - myPreferences: {}, - }, - groups: { - groups: {}, - myGroups: [], - }, - emojis: { - customEmoji: {}, - }, - }, - plugins: { - insightsHandlers: { - focalboard: async () => { - return { - items: [ - { - boardID: 'b8i4hjy9z6igjjbs68fudzr6z8h', - icon: '📅', - title: 'Test calendar ', - activityCount: 32, - activeUsers: ['9qobtrxa93dhfg1fqmhcq5wj4o'], - createdBy: '9qobtrxa93dhfg1fqmhcq5wj4o', - }, - { - boardID: 'bf3mmu7hjgprpmp1ekiozyggrjh', - icon: '📅', - title: 'Content Calendar ', - activityCount: 24, - activeUsers: ['9qobtrxa93dhfg1fqmhcq5wj4o', '9x4to68xqiyfzb8dxwfpbqopie'], - createdBy: '9qobtrxa93dhfg1fqmhcq5wj4o', - }, - { - boardID: 'bf3mmu7hjgprpmp1ekiozyggrjh', - icon: '📅', - title: 'Content Calendar ', - activityCount: 24, - - // MM-49023 - activeUsers: '9qobtrxa93dhfg1fqmhcq5wj4o,9x4to68xqiyfzb8dxwfpbqopie', - createdBy: '9qobtrxa93dhfg1fqmhcq5wj4o', - }, - ], - }; - }, - }, - }, - }; - - test('check if 3 team top boards render', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - - // Link causes the class to render 3 times for each item - expect(wrapper.find('.board-item').length).toEqual(9); - }); - - test('check if 0 top boards render', async () => { - const state = { - ...initialState, - plugins: { - insightsHandlers: { - focalboard: async () => { - return { - items: [], - }; - }, - }, - }, - }; - const store = await mockStore(state); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - expect(wrapper.find('.empty-state').length).toEqual(1); - }); -}); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_boards/top_boards.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_boards/top_boards.tsx deleted file mode 100644 index fd29ca55c8..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_boards/top_boards.tsx +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useState, useCallback, useEffect, useMemo} from 'react'; -import {useSelector} from 'react-redux'; -import {FormattedMessage} from 'react-intl'; -import {Link} from 'react-router-dom'; - -import {TopBoard} from '@mattermost/types/insights'; - -import {CircleSkeletonLoader, RectangleSkeletonLoader} from '@mattermost/components'; - -import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; - -import {trackEvent} from 'actions/telemetry_actions'; - -import {GlobalState} from 'types/store'; - -import Avatars from 'components/widgets/users/avatars'; - -import widgetHoc, {WidgetHocProps} from '../widget_hoc/widget_hoc'; -import WidgetEmptyState from '../widget_empty_state/widget_empty_state'; - -import './../../activity_and_insights.scss'; - -const TopBoards = (props: WidgetHocProps) => { - const [loading, setLoading] = useState(true); - const [topBoards, setTopBoards] = useState([] as TopBoard[]); - - const currentTeamId = useSelector(getCurrentTeamId); - const boardsHandler = useSelector((state: GlobalState) => state.plugins.insightsHandlers.focalboard || state.plugins.insightsHandlers.boards); - - const getTopBoards = useCallback(async () => { - setLoading(true); - const data: any = await boardsHandler(props.timeFrame, 0, 4, currentTeamId, props.filterType); - if (data && data.items) { - setTopBoards(data.items); - } - setLoading(false); - }, [props.timeFrame, currentTeamId, props.filterType]); - - useEffect(() => { - getTopBoards(); - }, [getTopBoards]); - - const skeletonLoader = useMemo(() => { - const entries = []; - for (let i = 0; i < 4; i++) { - entries.push( -
- -
- - -
-
, - ); - } - return entries; - }, []); - - const trackClickEvent = useCallback(() => { - trackEvent('insights', 'open_board_from_top_boards_widget'); - }, []); - - return ( -
- { - loading && - skeletonLoader - } - { - (topBoards && !loading) && -
- { - topBoards.map((board, i) => { - return ( - - {board.icon} -
- {board.title} - - - -
- - - ); - }) - } -
- } - { - - (topBoards.length === 0 && !loading) && - - } -
- ); -}; - -export default memo(widgetHoc(TopBoards)); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_boards/top_boards_table/top_boards_table.test.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_boards/top_boards_table/top_boards_table.test.tsx deleted file mode 100644 index 2d91854c58..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_boards/top_boards_table/top_boards_table.test.tsx +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import {Provider} from 'react-redux'; - -import {ReactWrapper} from 'enzyme'; - -import {BrowserRouter} from 'react-router-dom'; - -import {TimeFrames} from '@mattermost/types/insights'; - -import {mountWithIntl} from 'tests/helpers/intl-test-helper'; -import {act} from 'tests/react_testing_utils'; -import mockStore from 'tests/test_store'; - -import TopBoardsTable from './top_boards_table'; - -const actImmediate = (wrapper: ReactWrapper) => - act( - () => - new Promise((resolve) => { - setImmediate(() => { - wrapper.update(); - resolve(); - }); - }), - ); - -describe('components/activity_and_insights/insights/top_boards_table', () => { - const props = { - filterType: 'TEAM', - timeFrame: TimeFrames.INSIGHTS_7_DAYS, - closeModal: jest.fn(), - }; - - const initialState = { - entities: { - teams: { - currentTeamId: 'team_id1', - teams: { - team_id1: { - id: 'team_id1', - name: 'team1', - }, - }, - }, - channels: { - channels: { - channel1: { - id: 'channel1', - team_id: 'team_id1', - name: 'channel1', - }, - }, - }, - general: { - config: {}, - }, - users: { - currentUserId: 'current_user_id', - profiles: { - current_user_id: { - id: 'current_user_id', - }, - user1: { - id: 'user1', - }, - }, - }, - preferences: { - myPreferences: {}, - }, - groups: { - groups: {}, - myGroups: [], - }, - emojis: { - customEmoji: {}, - }, - }, - plugins: { - insightsHandlers: { - focalboard: async () => { - return { - items: [ - { - boardID: 'b8i4hjy9z6igjjbs68fudzr6z8h', - icon: '📅', - title: 'Test calendar ', - activityCount: 32, - activeUsers: ['9qobtrxa93dhfg1fqmhcq5wj4o'], - createdBy: '9qobtrxa93dhfg1fqmhcq5wj4o', - }, - { - boardID: 'bf3mmu7hjgprpmp1ekiozyggrjh', - icon: '📅', - title: 'Content Calendar ', - activityCount: 24, - activeUsers: ['9qobtrxa93dhfg1fqmhcq5wj4o', '9x4to68xqiyfzb8dxwfpbqopie'], - createdBy: '9qobtrxa93dhfg1fqmhcq5wj4o', - }, - { - boardID: 'bf3mmu7hjgprpmp1ekiozyggrjh', - icon: '📅', - title: 'Content Calendar ', - activityCount: 24, - - // MM-49023 - activeUsers: '9qobtrxa93dhfg1fqmhcq5wj4o,9x4to68xqiyfzb8dxwfpbqopie', - createdBy: '9qobtrxa93dhfg1fqmhcq5wj4o', - }, - ], - }; - }, - }, - }, - }; - - test('check if 3 team top boards render', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - expect(wrapper.find('.DataGrid_row').length).toEqual(3); - }); - - test('check if 0 top boards render', async () => { - const state = { - ...initialState, - plugins: { - insightsHandlers: { - focalboard: async () => { - return { - items: [], - }; - }, - }, - }, - }; - const store = await mockStore(state); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - expect(wrapper.find('.DataGrid_row').length).toEqual(0); - }); -}); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_boards/top_boards_table/top_boards_table.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_boards/top_boards_table/top_boards_table.tsx deleted file mode 100644 index 81bcf05129..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_boards/top_boards_table/top_boards_table.tsx +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useState, useMemo, useEffect, useCallback} from 'react'; -import {useSelector} from 'react-redux'; -import {FormattedMessage} from 'react-intl'; -import {useHistory} from 'react-router-dom'; - -import classNames from 'classnames'; - -import {trackEvent} from 'actions/telemetry_actions'; - -import {GlobalState} from 'types/store'; - -import {TimeFrame, TopBoard} from '@mattermost/types/insights'; - -import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; - -import DataGrid, {Row, Column} from 'components/admin_console/data_grid/data_grid'; -import Avatars from 'components/widgets/users/avatars'; - -import './../../../activity_and_insights.scss'; - -type Props = { - filterType: string; - timeFrame: TimeFrame; - closeModal: () => void; -} - -const TopBoardsTable = (props: Props) => { - const history = useHistory(); - - const [loading, setLoading] = useState(true); - const [topBoards, setTopBoards] = useState([] as TopBoard[]); - - const currentTeamId = useSelector(getCurrentTeamId); - const boardsHandler = useSelector((state: GlobalState) => state.plugins.insightsHandlers.focalboard || state.plugins.insightsHandlers.boards); - - const getTopBoards = useCallback(async () => { - setLoading(true); - const data: any = await boardsHandler(props.timeFrame, 0, 10, currentTeamId, props.filterType); - if (data && data.items) { - setTopBoards(data.items); - } - setLoading(false); - }, [props.timeFrame, currentTeamId, props.filterType]); - - useEffect(() => { - getTopBoards(); - }, [getTopBoards]); - - const goToBoard = useCallback((board: TopBoard) => { - props.closeModal(); - trackEvent('insights', 'open_board_from_top_boards_modal'); - history.push(`/boards/team/${currentTeamId}/${board.boardID}`); - }, [props.closeModal]); - - const getColumns = useMemo((): Column[] => { - const columns: Column[] = [ - { - name: ( - - ), - field: 'rank', - className: 'rankCell', - width: 0.07, - }, - { - name: ( - - ), - field: 'board', - width: 0.7, - }, - { - name: ( - - ), - field: 'updates', - width: 0.08, - }, - { - name: ( - - ), - field: 'participants', - width: 0.15, - }, - ]; - return columns; - }, []); - - const getRows = useMemo((): Row[] => { - return topBoards.map((board, i) => { - return ( - { - cells: { - rank: ( - - {i + 1} - - ), - board: ( -
- {board.icon} - {board.title} -
- ), - updates: ( - {board.activityCount} - ), - participants: ( - - ), - }, - onClick: () => { - goToBoard(board); - }, - } - ); - }); - }, [topBoards]); - - return ( - {}} - previousPage={() => {}} - startCount={1} - endCount={10} - total={0} - className={classNames('InsightsTable', 'TopBoardsTable')} - /> - ); -}; - -export default memo(TopBoardsTable); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_channels/top_channels.test.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_channels/top_channels.test.tsx deleted file mode 100644 index 00a70c3c7d..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_channels/top_channels.test.tsx +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import {Provider} from 'react-redux'; - -import {ReactWrapper} from 'enzyme'; - -import {BrowserRouter} from 'react-router-dom'; - -import {CardSizes, InsightsWidgetTypes, TimeFrames} from '@mattermost/types/insights'; - -import {mountWithIntl} from 'tests/helpers/intl-test-helper'; -import {act} from 'tests/react_testing_utils'; -import mockStore from 'tests/test_store'; - -import TopChannels from './top_channels'; - -const actImmediate = (wrapper: ReactWrapper) => - act( - () => - new Promise((resolve) => { - setImmediate(() => { - wrapper.update(); - resolve(); - }); - }), - ); - -jest.mock('mattermost-redux/actions/insights', () => ({ - ...jest.requireActual('mattermost-redux/actions/insights'), - getMyTopChannels: () => ({type: 'adsf', data: {}}), - getTopChannelsForTeam: () => ({type: 'adsf', - data: { - has_next: false, - items: [ - { - id: '4r98uzxe4b8t5g9ntt9zcdzktw', - type: 'P', - display_name: 'nesciunt', - name: 'sequi-7', - team_id: 'team_id1', - message_count: 1, - }, - { - id: '4r98uzxe4b8t5gfdsdfggs', - type: 'P', - display_name: 'test', - name: 'test-7', - team_id: 'team_id1', - message_count: 2, - }, - ], - }}), -})); - -describe('components/activity_and_insights/insights/top_channels', () => { - const props = { - filterType: 'TEAM', - timeFrame: TimeFrames.INSIGHTS_7_DAYS, - size: CardSizes.large, - widgetType: InsightsWidgetTypes.TOP_CHANNELS, - class: 'top-channels-card', - timeFrameLabel: 'Last 7 days', - }; - - const initialState = { - entities: { - teams: { - currentTeamId: 'team_id1', - teams: { - team_id1: { - id: 'team_id1', - name: 'team1', - }, - }, - }, - general: { - config: {}, - }, - users: { - currentUserId: 'current_user_id', - profiles: { - current_user_id: {}, - }, - }, - preferences: { - myPreferences: {}, - }, - }, - }; - - test('check if 2 team top channels render', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - expect(wrapper.find('.channel-message-count').length).toEqual(2); - }); - - test('check if 0 my top channels render', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - expect(wrapper.find('.empty-state').length).toEqual(1); - }); -}); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_channels/top_channels.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_channels/top_channels.tsx deleted file mode 100644 index 8d2dbb23e1..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_channels/top_channels.tsx +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useCallback, useEffect, useMemo, useState} from 'react'; -import {useDispatch, useSelector} from 'react-redux'; -import {Link} from 'react-router-dom'; -import {FormattedMessage} from 'react-intl'; - -import {TopChannel, TopChannelGraphData} from '@mattermost/types/insights'; - -import {CircleSkeletonLoader, RectangleSkeletonLoader} from '@mattermost/components'; - -import {getCurrentRelativeTeamUrl, getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; -import {getCurrentTimezone} from 'mattermost-redux/selectors/entities/timezone'; -import {getMyTopChannels, getTopChannelsForTeam} from 'mattermost-redux/actions/insights'; - -import Constants, {InsightsScopes} from 'utils/constants'; - -import {trackEvent} from 'actions/telemetry_actions'; - -import OverlayTrigger from 'components/overlay_trigger'; -import Tooltip from 'components/tooltip'; - -import widgetHoc, {WidgetHocProps} from '../widget_hoc/widget_hoc'; -import WidgetEmptyState from '../widget_empty_state/widget_empty_state'; - -import TopChannelsLineChart from './top_channels_line_chart/top_channels_line_chart'; - -import './../../activity_and_insights.scss'; - -const TopChannels = (props: WidgetHocProps) => { - const dispatch = useDispatch(); - - const [loading, setLoading] = useState(true); - const [topChannels, setTopChannels] = useState([] as TopChannel[]); - const [channelLineChartData, setChannelLineChartData] = useState({} as TopChannelGraphData); - - const currentTeamId = useSelector(getCurrentTeamId); - const currentTeamUrl = useSelector(getCurrentRelativeTeamUrl); - const timeZone = useSelector(getCurrentTimezone); - - const getTopTeamChannels = useCallback(async () => { - if (props.filterType === InsightsScopes.TEAM) { - setLoading(true); - const data: any = await dispatch(getTopChannelsForTeam(currentTeamId, 0, 5, props.timeFrame)); - if (data.data?.items) { - setTopChannels(data.data.items); - } - if (data.data?.channel_post_counts_by_duration) { - setChannelLineChartData(data.data.channel_post_counts_by_duration); - } - setLoading(false); - } - }, [props.timeFrame, currentTeamId, props.filterType]); - - useEffect(() => { - getTopTeamChannels(); - }, [getTopTeamChannels]); - - const getMyTeamChannels = useCallback(async () => { - if (props.filterType === InsightsScopes.MY) { - setLoading(true); - const data: any = await dispatch(getMyTopChannels(currentTeamId, 0, 5, props.timeFrame)); - if (data.data?.items) { - setTopChannels(data.data.items); - } - if (data.data?.channel_post_counts_by_duration) { - setChannelLineChartData(data.data.channel_post_counts_by_duration); - } - setLoading(false); - } - }, [props.timeFrame, props.filterType]); - - useEffect(() => { - getMyTeamChannels(); - }, [getMyTeamChannels]); - - const skeletonTitle = useMemo(() => { - const skeletonFlexes = ['1', '0.85', '0.9', '0.5', '0.7']; - const titles = []; - for (let i = 0; i < 5; i++) { - titles.push( -
- - -
, - ); - } - return titles; - }, []); - - const tooltip = useCallback((messageCount: number) => { - return ( - - - - ); - }, []); - - const trackClickEvent = useCallback(() => { - trackEvent('insights', 'open_channel_from_top_channels_widget'); - }, []); - - return ( - <> -
-
- { - loading && - - } - { - (!loading && topChannels.length !== 0) && - <> - - - } -
-
- { - loading && - skeletonTitle - } - { - (!loading && topChannels.length !== 0) && -
- { - topChannels.map((channel) => { - const barSize = ((channel.message_count / topChannels[0].message_count) * 0.8); - - let iconToDisplay = ; - - if (channel.type === Constants.PRIVATE_CHANNEL) { - iconToDisplay = ; - } - return ( - -
- - {iconToDisplay} - - {channel.display_name} -
-
- - {channel.message_count} - - -
- - ); - }) - } -
- } -
-
- { - (topChannels.length === 0 && !loading) && - - } - - - ); -}; - -export default memo(widgetHoc(TopChannels)); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_channels/top_channels_line_chart/__snapshots__/top_channels_line_chart.test.tsx.snap b/webapp/channels/src/components/activity_and_insights/insights/top_channels/top_channels_line_chart/__snapshots__/top_channels_line_chart.test.tsx.snap deleted file mode 100644 index 7ece176a46..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_channels/top_channels_line_chart/__snapshots__/top_channels_line_chart.test.tsx.snap +++ /dev/null @@ -1,273 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`components/activity_and_insights/insights/top_channels should match snapshot 1`] = ` - - - - - - } - > -
-
-
- - - Top Channels - - -
-
- -
-
-
-
-
-
-
-
-`; diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_channels/top_channels_line_chart/top_channels_line_chart.test.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_channels/top_channels_line_chart/top_channels_line_chart.test.tsx deleted file mode 100644 index 1797fb1f12..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_channels/top_channels_line_chart/top_channels_line_chart.test.tsx +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; - -import {Provider} from 'react-redux'; - -import {BrowserRouter} from 'react-router-dom'; - -import {TimeFrames, TopChannel} from '@mattermost/types/insights'; - -import {mountWithIntl} from 'tests/helpers/intl-test-helper'; -import mockStore from 'tests/test_store'; - -import TopChannelsLineChart from './top_channels_line_chart'; - -jest.mock('mattermost-redux/actions/insights', () => ({ - ...jest.requireActual('mattermost-redux/actions/insights'), - getMyTopChannels: () => ({type: 'adsf', data: {}}), - getTopChannelsForTeam: () => ({type: 'adsf', - data: { - has_next: false, - items: [ - { - id: '4r98uzxe4b8t5g9ntt9zcdzktw', - type: 'P', - display_name: 'nesciunt', - name: 'sequi-7', - team_id: 'team_id1', - message_count: 1, - }, - { - id: '4r98uzxe4b8t5gfdsdfggs', - type: 'P', - display_name: 'test', - name: 'test-7', - team_id: 'team_id1', - message_count: 2, - }, - ], - }}), -})); - -describe('components/activity_and_insights/insights/top_channels', () => { - const props = { - timeFrame: TimeFrames.INSIGHTS_7_DAYS, - topChannels: [ - { - id: '4r98uzxe4b8t5g9ntt9zcdzktw', - type: 'P', - display_name: 'nesciunt', - name: 'sequi-7', - team_id: 'team_id1', - message_count: 100, - } as TopChannel, - { - id: '4r98uzxe4b8t5gfdsdfggsdfgs', - type: 'P', - display_name: 'test', - name: 'test-7', - team_id: 'team_id1', - message_count: 200, - } as TopChannel, - ], - channelLineChartData: { - '2022-05-01': { - '4r98uzxe4b8t5g9ntt9zcdzktw': 10, - '4r98uzxe4b8t5gfdsdfggsdfgs': 20, - }, - '2022-05-02': { - '4r98uzxe4b8t5g9ntt9zcdzktw': 20, - '4r98uzxe4b8t5gfdsdfggsdfgs': 40, - }, - '2022-05-03': { - '4r98uzxe4b8t5g9ntt9zcdzktw': 15, - '4r98uzxe4b8t5gfdsdfggsdfgs': 25, - }, - '2022-05-04': { - '4r98uzxe4b8t5g9ntt9zcdzktw': 15, - '4r98uzxe4b8t5gfdsdfggsdfgs': 10, - }, - '2022-05-05': { - '4r98uzxe4b8t5g9ntt9zcdzktw': 10, - '4r98uzxe4b8t5gfdsdfggsdfgs': 15, - }, - '2022-05-06': { - '4r98uzxe4b8t5g9ntt9zcdzktw': 20, - '4r98uzxe4b8t5gfdsdfggsdfgs': 50, - }, - '2022-05-07': { - '4r98uzxe4b8t5g9ntt9zcdzktw': 10, - '4r98uzxe4b8t5gfdsdfggsdfgs': 40, - }, - }, - timeZone: 'America/Toronto', - }; - - const store = mockStore({ - entities: { - teams: { - currentTeamId: 'team_id1', - teams: { - team_id1: { - id: 'team_id1', - name: 'team1', - }, - }, - }, - general: { - config: {}, - }, - users: { - currentUserId: 'current_user_id', - }, - preferences: { - myPreferences: {}, - }, - }, - }); - - test('should match snapshot', () => { - const wrapper = mountWithIntl( - - - - - , - ); - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_channels/top_channels_line_chart/top_channels_line_chart.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_channels/top_channels_line_chart/top_channels_line_chart.tsx deleted file mode 100644 index aa2d3d5bb4..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_channels/top_channels_line_chart/top_channels_line_chart.tsx +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useMemo} from 'react'; -import {useSelector} from 'react-redux'; -import moment from 'moment-timezone'; - -import {FormattedMessage, useIntl} from 'react-intl'; - -import {TimeFrame, TimeFrames, TopChannel, TopChannelGraphData} from '@mattermost/types/insights'; -import {GlobalState} from '@mattermost/types/store'; - -import {getBool, getTheme} from 'mattermost-redux/selectors/entities/preferences'; - -import {changeOpacity} from 'mattermost-redux/utils/theme_utils'; - -import {Preferences} from 'mattermost-redux/constants'; - -import LineChart from 'components/analytics/line_chart'; - -import './../../../activity_and_insights.scss'; - -type Props = { - topChannels: TopChannel[]; - timeFrame: TimeFrame; - channelLineChartData: TopChannelGraphData; - timeZone: string; -} - -const TopChannelsLineChart = ({topChannels, timeFrame, channelLineChartData, timeZone}: Props) => { - const theme = useSelector(getTheme); - const intl = useIntl(); - const isMilitaryTime = useSelector((state: GlobalState) => getBool(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.USE_MILITARY_TIME, false)); - - const getLabels = useMemo(() => { - const labels: any[] = Object.keys(channelLineChartData); - - for (let i = 0; i < labels.length; i++) { - const label = labels[i]; - if (timeFrame === TimeFrames.INSIGHTS_1_DAY) { - labels[i] = intl.formatTime(Date.parse(label), { - hour12: isMilitaryTime ? undefined : true, - hour: '2-digit', - minute: '2-digit', - hourCycle: 'h23', - timeZone, - }); - } else { - labels[i] = intl.formatDate(moment.tz(label, timeZone).toDate(), { - month: 'short', - day: '2-digit', - }); - } - } - return labels; - }, [channelLineChartData, timeZone]); - - const sortGraphData = useMemo(() => { - const labels = getLabels; - - const values = {} as Record; - const keys = Object.keys(channelLineChartData); - - for (let i = 0, len = keys.length; i < len; i++) { - const item = channelLineChartData[keys[i]]; - - const channelIds = Object.keys(item); - - for (let j = 0, channelIdsLength = channelIds.length; j < channelIdsLength; j++) { - const count = item[channelIds[j]]; - if (values[channelIds[j]]) { - values[channelIds[j]].push(count); - } else { - values[channelIds[j]] = [count]; - } - } - } - return { - labels, - values, - }; - }, []); - - const getGraphData = useMemo(() => { - const data = sortGraphData; - const dataset = []; - const colours = [theme.buttonBg, theme.onlineIndicator, theme.awayIndicator, theme.dndIndicator, theme.newMessageSeparator]; - - for (let i = 0; i < 5; i++) { - if (topChannels[i]) { - const colour = colours[i]; - dataset.push({ - fillColor: colour, - borderColor: colour, - pointBackgroundColor: colour, - pointBorderColor: 'transparent', - backgroundColor: 'transparent', - pointRadius: 0, - hoverBackgroundColor: colour, - label: topChannels[i].display_name, - data: data.values[topChannels[i].id], - hitRadius: 10, - tension: 0.4, - }); - } - } - - return { - datasets: dataset, - labels: data.labels, - }; - }, []); - - return ( - - } - id='totalPostsPerChannel' - height={200} - options={{ - responsive: true, - hover: { - mode: 'point', - }, - onHover: (e, activeElements) => { - if (e.native && e.native.target) { - (e.native.target as HTMLElement).style.cursor = activeElements?.length > 0 ? 'pointer' : 'auto'; - } - }, - maintainAspectRatio: false, - scales: { - xAxis: { - grid: { - drawOnChartArea: false, - }, - ticks: { - callback(_, index) { - const labels = getLabels; - const value = labels[index]; - - // Hide every 4th tick label for 28 day time frame - if (timeFrame === TimeFrames.INSIGHTS_28_DAYS) { - return index % 4 === 0 ? value : ''; - } - - if (timeFrame === TimeFrames.INSIGHTS_1_DAY) { - if (labels.length > 8 && labels.length <= 12) { - return index % 2 === 0 ? value : ''; - } - - if (labels.length > 12) { - return index % 4 === 0 ? value : ''; - } - } - return value; - }, - font: { - family: 'Open Sans', - size: 10, - }, - color: changeOpacity(theme.centerChannelColor, 0.72), - }, - }, - yAxis: { - grid: { - drawOnChartArea: true, - }, - beginAtZero: true, - ticks: { - maxTicksLimit: 5, - font: { - family: 'Open Sans', - size: 10, - }, - precision: 0, - color: changeOpacity(theme.centerChannelColor, 0.72), - }, - }, - }, - plugins: { - legend: { - display: false, - }, - tooltip: { - callbacks: { - label(context) { - const index = context.datasetIndex; - if (typeof index !== 'undefined' && context.dataset && context.dataset?.label) { - const label = context.dataset?.label || ''; - if (label.length > 16) { - return ` ${label.slice(0, 16)}...`; - } - return ` ${label}`; - } - return ''; - }, - title() { - return ''; - }, - footer(tooltipItems) { - return `${tooltipItems[0].parsed.y} messages`; - }, - }, - bodyFont: { - weight: 'bold', - family: 'Open Sans', - }, - bodyAlign: 'left', - bodySpacing: 10, - footerFont: { - family: 'Open Sans', - size: 11, - style: 'normal', - }, - footerAlign: 'center', - footerSpacing: 10, - footerColor: 'rgba(255, 255, 255, .64)', - multiKeyBackground: 'transparent', - }, - }, - }} - data={getGraphData} - /> - ); -}; - -export default memo(TopChannelsLineChart); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_channels/top_channels_table/top_channels_table.test.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_channels/top_channels_table/top_channels_table.test.tsx deleted file mode 100644 index f181a5d2d8..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_channels/top_channels_table/top_channels_table.test.tsx +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import {Provider} from 'react-redux'; - -import {ReactWrapper} from 'enzyme'; - -import {BrowserRouter} from 'react-router-dom'; - -import {TimeFrames} from '@mattermost/types/insights'; - -import {mountWithIntl} from 'tests/helpers/intl-test-helper'; -import {act} from 'tests/react_testing_utils'; -import mockStore from 'tests/test_store'; - -import TopChannelsTable from './top_channels_table'; - -const actImmediate = (wrapper: ReactWrapper) => - act( - () => - new Promise((resolve) => { - setImmediate(() => { - wrapper.update(); - resolve(); - }); - }), - ); - -jest.mock('mattermost-redux/actions/insights', () => ({ - ...jest.requireActual('mattermost-redux/actions/insights'), - getMyTopChannels: () => ({type: 'adsf', data: {}}), - getTopChannelsForTeam: () => ({type: 'adsf', - data: { - has_next: false, - items: [ - { - id: '4r98uzxe4b8t5g9ntt9zcdzktw', - type: 'P', - display_name: 'nesciunt', - name: 'sequi-7', - team_id: 'team_id1', - message_count: 1, - }, - { - id: '4r98uzxe4b8t5gfdsdfggs', - type: 'P', - display_name: 'test', - name: 'test-7', - team_id: 'team_id1', - message_count: 2, - }, - ], - }}), -})); - -describe('components/activity_and_insights/insights/top_channels', () => { - const props = { - filterType: 'TEAM', - timeFrame: TimeFrames.INSIGHTS_7_DAYS, - closeModal: jest.fn(), - }; - - const initialState = { - entities: { - teams: { - currentTeamId: 'team_id1', - teams: { - team_id1: { - id: 'team_id1', - name: 'team1', - }, - }, - }, - general: { - config: {}, - }, - users: { - currentUserId: 'current_user_id', - }, - }, - }; - - test('check if 2 team top channels render', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - expect(wrapper.find('.DataGrid_row').length).toEqual(2); - }); - - test('check if 0 my top channels render', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - expect(wrapper.find('.DataGrid_row').length).toEqual(0); - }); -}); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_channels/top_channels_table/top_channels_table.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_channels/top_channels_table/top_channels_table.tsx deleted file mode 100644 index 503d2a189c..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_channels/top_channels_table/top_channels_table.tsx +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useCallback, useEffect, useMemo, useState} from 'react'; -import {FormattedMessage} from 'react-intl'; -import {useDispatch, useSelector} from 'react-redux'; -import {Link} from 'react-router-dom'; - -import {trackEvent} from 'actions/telemetry_actions'; - -import {TimeFrame, TopChannel} from '@mattermost/types/insights'; - -import DataGrid, {Row, Column} from 'components/admin_console/data_grid/data_grid'; - -import Constants, {InsightsScopes} from 'utils/constants'; -import {getCurrentRelativeTeamUrl, getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; -import {getMyTopChannels, getTopChannelsForTeam} from 'mattermost-redux/actions/insights'; - -import './../../../activity_and_insights.scss'; - -type Props = { - filterType: string; - timeFrame: TimeFrame; - closeModal: () => void; -} - -const TopChannelsTable = (props: Props) => { - const dispatch = useDispatch(); - - const [loading, setLoading] = useState(true); - const [topChannels, setTopChannels] = useState([] as TopChannel[]); - - const currentTeamId = useSelector(getCurrentTeamId); - const currentTeamUrl = useSelector(getCurrentRelativeTeamUrl); - - const getTopTeamChannels = useCallback(async () => { - if (props.filterType === InsightsScopes.TEAM) { - setLoading(true); - const data: any = await dispatch(getTopChannelsForTeam(currentTeamId, 0, 10, props.timeFrame)); - if (data.data && data.data.items) { - setTopChannels(data.data.items); - } - setLoading(false); - } - }, [props.timeFrame, currentTeamId, props.filterType]); - - useEffect(() => { - getTopTeamChannels(); - }, [getTopTeamChannels]); - - const getMyTeamChannels = useCallback(async () => { - if (props.filterType === InsightsScopes.MY) { - setLoading(true); - const data: any = await dispatch(getMyTopChannels(currentTeamId, 0, 10, props.timeFrame)); - if (data.data && data.data.items) { - setTopChannels(data.data.items); - } - setLoading(false); - } - }, [props.timeFrame, props.filterType]); - - useEffect(() => { - getMyTeamChannels(); - }, [getMyTeamChannels]); - - const closeModal = useCallback(() => { - trackEvent('insights', 'open_channel_from_top_channels_modal'); - props.closeModal(); - }, [props.closeModal]); - - const getColumns = useMemo((): Column[] => { - const columns: Column[] = [ - { - name: ( - - ), - field: 'rank', - className: 'rankCell', - width: 0.2, - }, - { - name: ( - - ), - field: 'channel', - }, - { - name: ( - - ), - field: 'message_count', - }, - ]; - return columns; - }, []); - - const getRows = useMemo((): Row[] => { - return topChannels.map((channel, i) => { - const barSize = (channel.message_count / topChannels[0].message_count); - - let iconToDisplay = ; - - if (channel.type === Constants.PRIVATE_CHANNEL) { - iconToDisplay = ; - } - return ( - { - cells: { - rank: ( - - {i + 1} - - ), - channel: ( - - - {iconToDisplay} - - - {channel.display_name} - - - ), - message_count: ( -
- - {channel.message_count} - - -
- ), - }, - } - ); - }); - }, [topChannels]); - - return ( - {}} - previousPage={() => {}} - startCount={1} - endCount={10} - total={0} - className={'InsightsTable'} - /> - ); -}; - -export default memo(TopChannelsTable); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/new_members_item/new_members_item.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/new_members_item/new_members_item.tsx deleted file mode 100644 index 9075ec92ad..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/new_members_item/new_members_item.tsx +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useCallback} from 'react'; -import {useSelector} from 'react-redux'; -import {FormattedMessage} from 'react-intl'; -import {Link} from 'react-router-dom'; - -import {trackEvent} from 'actions/telemetry_actions'; - -import {getTeammateNameDisplaySetting} from 'mattermost-redux/selectors/entities/preferences'; -import {getUser} from 'mattermost-redux/selectors/entities/users'; - -import {UserProfile} from '@mattermost/types/users'; -import {NewMember} from '@mattermost/types/insights'; -import {GlobalState} from '@mattermost/types/store'; -import {Team} from '@mattermost/types/teams'; - -import {displayUsername} from 'mattermost-redux/utils/user_utils'; - -import Avatar from 'components/widgets/users/avatar'; -import RenderEmoji from 'components/emoji/render_emoji'; - -import {imageURLForUser} from 'utils/utils'; - -import './../../../activity_and_insights.scss'; - -type Props = { - newMember: NewMember; - team: Team; -} - -const NewMembersItem = ({newMember, team}: Props) => { - const teammateNameDisplaySetting = useSelector(getTeammateNameDisplaySetting); - const user = useSelector((state: GlobalState) => getUser(state, newMember.id)) as UserProfile; - - let member = newMember; - if (user) { - member = { - ...member, - username: user.username, - first_name: user.first_name, - last_name: user.last_name, - nickname: user.nickname, - last_picture_update: user.last_picture_update, - }; - } - - const trackClick = useCallback(() => { - trackEvent('insights', 'open_new_members_from_new_members_widget'); - }, []); - - return ( - - -
-
- {displayUsername(member as UserProfile, teammateNameDisplaySetting)} -
- {member.position} -
- -
- -
-
-
- - ); -}; - -export default memo(NewMembersItem); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/new_members_table/new_members_table.test.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/new_members_table/new_members_table.test.tsx deleted file mode 100644 index f54b74d98b..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/new_members_table/new_members_table.test.tsx +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import {Provider} from 'react-redux'; - -import {ReactWrapper} from 'enzyme'; - -import {BrowserRouter} from 'react-router-dom'; - -import {TimeFrames} from '@mattermost/types/insights'; - -import {mountWithIntl} from 'tests/helpers/intl-test-helper'; -import {act} from 'tests/react_testing_utils'; -import mockStore from 'tests/test_store'; - -import NewMembersTable from './new_members_table'; - -const actImmediate = (wrapper: ReactWrapper) => - act( - () => - new Promise((resolve) => { - setImmediate(() => { - wrapper.update(); - resolve(); - }); - }), - ); - -jest.mock('mattermost-redux/actions/insights', () => ({ - ...jest.requireActual('mattermost-redux/actions/insights'), - getNewTeamMembers: () => ({type: 'adsf', - data: { - has_next: false, - total_count: 2, - items: [ - { - id: 'q7bgtp9dtfdwxriz1e8p8zon3e', - first_name: 'Aaron', - last_name: 'Medina', - nickname: 'Centidel', - username: 'aaron.medina', - position: 'Systems Administrator I', - create_at: 1659641095563, - }, - { - id: 'gqzxf4ibfiddfy5xitc9gtjykc', - first_name: 'Peter', - last_name: 'Jones', - nickname: '', - username: 'peter.jones', - position: 'Account Executive', - create_at: 1659641095563, - }, - ], - }}), -})); - -describe('components/activity_and_insights/insights/top_dms_and_new_members/new_members_table', () => { - const props = { - filterType: 'TEAM', - timeFrame: TimeFrames.INSIGHTS_7_DAYS, - closeModal: jest.fn(), - offset: 0, - setOffset: jest.fn(), - }; - - const initialState = { - entities: { - teams: { - currentTeamId: 'team_id1', - teams: { - team_id1: { - id: 'team_id1', - name: 'team1', - }, - }, - }, - channels: { - channels: { - channel1: { - id: 'channel1', - team_id: 'team_id1', - name: 'channel1', - }, - }, - myMembers: { - channel1: { - id: 'channel1', - team_id: 'team_id1', - name: 'channel1', - }, - }, - }, - general: { - config: {}, - }, - users: { - currentUserId: 'current_user_id', - profiles: { - current_user_id: { - id: 'current_user_id', - }, - user1: { - id: 'user1', - }, - }, - }, - preferences: { - myPreferences: {}, - }, - groups: { - groups: {}, - myGroups: [], - }, - emojis: { - customEmoji: {}, - }, - }, - }; - - test('check if new members render', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - expect(wrapper.find('.DataGrid_row').length).toEqual(2); - }); - - test('check if 0 new members render', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - expect(wrapper.find('.DataGrid_row').length).toEqual(0); - }); -}); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/new_members_table/new_members_table.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/new_members_table/new_members_table.tsx deleted file mode 100644 index 76055fadd9..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/new_members_table/new_members_table.tsx +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useCallback, useEffect, useMemo, useState} from 'react'; -import {FormattedDate, FormattedMessage} from 'react-intl'; -import {useDispatch, useSelector} from 'react-redux'; -import {Link} from 'react-router-dom'; - -import classNames from 'classnames'; - -import {trackEvent} from 'actions/telemetry_actions'; - -import {getNewTeamMembers} from 'mattermost-redux/actions/insights'; - -import {NewMember, TimeFrame} from '@mattermost/types/insights'; -import {UserProfile} from '@mattermost/types/users'; - -import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams'; -import {getTeammateNameDisplaySetting} from 'mattermost-redux/selectors/entities/preferences'; - -import {displayUsername} from 'mattermost-redux/utils/user_utils'; - -import {InsightsScopes} from 'utils/constants'; -import {imageURLForUser} from 'utils/utils'; - -import Avatar from 'components/widgets/users/avatar'; -import DataGrid, {Row, Column} from 'components/admin_console/data_grid/data_grid'; -import ModalPagination from 'components/activity_and_insights/insights/modal_pagination/modal_pagination'; - -import './../../../activity_and_insights.scss'; - -type Props = { - filterType: string; - timeFrame: TimeFrame; - offset: number; - setOffset: (offset: number) => void; - closeModal: () => void; -} - -const NewMembersTable = (props: Props) => { - const dispatch = useDispatch(); - - const [loading, setLoading] = useState(false); - const [newMembers, setNewMembers] = useState([] as NewMember[]); - const [hasNext, setHasNext] = useState(false); - - const teammateNameDisplaySetting = useSelector(getTeammateNameDisplaySetting); - const currentTeam = useSelector(getCurrentTeam); - - const getNewTeamMembersList = useCallback(async () => { - if (props.filterType === InsightsScopes.TEAM) { - setLoading(true); - const data: any = await dispatch(getNewTeamMembers(currentTeam.id, props.offset, 10, props.timeFrame)); - if (data.data?.items) { - setNewMembers(data.data.items); - } - - // Workaround for null response from API - if (data.data?.items === null) { - setNewMembers([]); - } - if ('has_next' in data.data) { - setHasNext(data.data?.has_next); - } - setLoading(false); - } - }, [props.timeFrame, props.filterType, props.offset]); - - useEffect(() => { - getNewTeamMembersList(); - }, [getNewTeamMembersList]); - - const closeModal = useCallback(() => { - trackEvent('insights', 'open_new_members_from_new_members_modal'); - props.closeModal(); - }, [props.closeModal]); - - const getColumns = useMemo((): Column[] => { - const columns: Column[] = [ - { - name: ( - - ), - field: 'teamMember', - width: 0.4, - }, - { - name: ( - - ), - field: 'position', - width: 0.4, - }, - { - name: ( - - ), - field: 'joined', - width: 0.2, - }, - ]; - return columns; - }, []); - - const getRows = useMemo((): Row[] => { - return newMembers.map((member) => { - return ( - { - cells: { - teamMember: ( - - - {displayUsername(member as UserProfile, teammateNameDisplaySetting)} - - - ), - position: ( - - {member.position} - - ), - joined: ( - - - - ), - }, - } - ); - }); - }, [newMembers]); - - return ( - <> - {}} - previousPage={() => {}} - startCount={1} - endCount={10} - total={0} - className={classNames('InsightsTable', 'NewMembersTable')} - /> - { - (props.offset !== 0 || newMembers.length === 10) && - - } - - - - ); -}; - -export default memo(NewMembersTable); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/new_members_total/__snapshots__/new_members_total.test.tsx.snap b/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/new_members_total/__snapshots__/new_members_total.test.tsx.snap deleted file mode 100644 index c4d0456779..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/new_members_total/__snapshots__/new_members_total.test.tsx.snap +++ /dev/null @@ -1,31 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`components/activity_and_insights/insights/top_dms_and_new_members/new_members_total should match snapshot with team 1`] = ` -
- - 2 - -
- - Joined the team in the last 7 days - - -
-
-`; diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/new_members_total/new_members_total.test.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/new_members_total/new_members_total.test.tsx deleted file mode 100644 index e98fe26a6e..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/new_members_total/new_members_total.test.tsx +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import {shallow} from 'enzyme'; - -import {TimeFrames} from '@mattermost/types/insights'; - -import NewMembersTotal from './new_members_total'; - -describe('components/activity_and_insights/insights/top_dms_and_new_members/new_members_total', () => { - const props = { - total: 2, - timeFrame: TimeFrames.INSIGHTS_7_DAYS, - openInsightsModal: jest.fn(), - }; - - test('should match snapshot with team', () => { - const wrapper = shallow( - , - ); - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/new_members_total/new_members_total.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/new_members_total/new_members_total.tsx deleted file mode 100644 index 2c87e3d8d0..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/new_members_total/new_members_total.tsx +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useCallback} from 'react'; - -import {TimeFrame, TimeFrames} from '@mattermost/types/insights'; - -import {localizeMessage} from 'utils/utils'; - -import './../../../activity_and_insights.scss'; - -type Props = { - total: number; - timeFrame: TimeFrame; - openInsightsModal: () => void; -} - -const NewMembersTotal = ({total, timeFrame, openInsightsModal}: Props) => { - const timeFrameInfo = useCallback(() => { - switch (timeFrame) { - case TimeFrames.INSIGHTS_1_DAY: - return localizeMessage('insights.newMembers.today', 'Joined the team today'); - case TimeFrames.INSIGHTS_7_DAYS: - return localizeMessage('insights.newMembers.lastSevenDays', 'Joined the team in the last 7 days'); - case TimeFrames.INSIGHTS_28_DAYS: - return localizeMessage('insights.newMembers.lastTwentyEightDays', 'Joined the team in the last 28 days'); - default: - return localizeMessage('insights.newMembers.lastSevenDays', 'Joined the team in the last 7 days'); - } - }, [timeFrame]); - - return ( -
- {total} -
- - {timeFrameInfo()} - - -
- -
- ); -}; - -export default memo(NewMembersTotal); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/top_dms_and_new_members.test.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/top_dms_and_new_members.test.tsx deleted file mode 100644 index c6a478f0e8..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/top_dms_and_new_members.test.tsx +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import {Provider} from 'react-redux'; - -import {ReactWrapper} from 'enzyme'; - -import {BrowserRouter} from 'react-router-dom'; - -import {CardSizes, InsightsWidgetTypes, TimeFrames} from '@mattermost/types/insights'; - -import {mountWithIntl} from 'tests/helpers/intl-test-helper'; -import {act} from 'tests/react_testing_utils'; -import mockStore from 'tests/test_store'; - -import TopDMsAndNewMembers from './top_dms_and_new_members'; - -const actImmediate = (wrapper: ReactWrapper) => - act( - () => - new Promise((resolve) => { - setImmediate(() => { - wrapper.update(); - resolve(); - }); - }), - ); - -jest.mock('mattermost-redux/actions/insights', () => ({ - ...jest.requireActual('mattermost-redux/actions/insights'), - getMyTopDMs: () => ({type: 'adsf', - data: { - has_next: false, - items: [ - { - post_count: 17, - outgoing_message_count: 12, - second_participant: { - id: 'q7bgtp9dtfdwxriz1e8p8zon3e', - last_picture_update: 0, - first_name: 'Aaron', - last_name: 'Medina', - nickname: 'Centidel', - username: 'aaron.medina', - position: 'Systems Administrator I', - }, - }, - ], - }}), -})); - -describe('components/activity_and_insights/insights/top_dms_and_new_members', () => { - const props = { - filterType: 'MY', - timeFrame: TimeFrames.INSIGHTS_7_DAYS, - size: CardSizes.large, - widgetType: InsightsWidgetTypes.TOP_DMS, - class: 'top-dms-card', - timeFrameLabel: 'Last 7 days', - }; - - const initialState = { - entities: { - teams: { - currentTeamId: 'team_id1', - teams: { - team_id1: { - id: 'team_id1', - name: 'team1', - }, - }, - }, - channels: { - channels: { - channel1: { - id: 'channel1', - team_id: 'team_id1', - name: 'channel1', - }, - }, - myMembers: { - channel1: { - id: 'channel1', - team_id: 'team_id1', - name: 'channel1', - }, - }, - }, - general: { - config: {}, - }, - users: { - currentUserId: 'current_user_id', - profiles: { - current_user_id: { - id: 'current_user_id', - }, - user1: { - id: 'user1', - }, - }, - }, - preferences: { - myPreferences: {}, - }, - groups: { - groups: {}, - myGroups: [], - }, - emojis: { - customEmoji: {}, - }, - }, - }; - - test('check if top dms render', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - expect(wrapper.find('a.top-dms-item').length).toEqual(1); - }); - - test('new members should render', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - expect(wrapper.text().includes('New team members')).toBe(true); - }); -}); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/top_dms_and_new_members.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/top_dms_and_new_members.tsx deleted file mode 100644 index 87c18d5dda..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/top_dms_and_new_members.tsx +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useEffect, useState, useCallback, useMemo} from 'react'; -import {useDispatch, useSelector} from 'react-redux'; - -import {NewMember, TopDM} from '@mattermost/types/insights'; - -import {CircleSkeletonLoader, RectangleSkeletonLoader} from '@mattermost/components'; - -import {getMyTopDMs, getNewTeamMembers} from 'mattermost-redux/actions/insights'; -import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams'; - -import {openModal} from 'actions/views/modals'; -import {trackEvent} from 'actions/telemetry_actions'; - -import {InsightsScopes, ModalIdentifiers} from 'utils/constants'; -import {localizeMessage} from 'utils/utils'; - -import widgetHoc, {WidgetHocProps} from '../widget_hoc/widget_hoc'; -import WidgetEmptyState from '../widget_empty_state/widget_empty_state'; -import InsightsModal from '../insights_modal/insights_modal'; - -import TopDMsItem from './top_dms_item/top_dms_item'; -import NewMembersItem from './new_members_item/new_members_item'; -import NewMembersTotal from './new_members_total/new_members_total'; - -import './../../activity_and_insights.scss'; - -const TopDMsAndNewMembers = (props: WidgetHocProps) => { - const dispatch = useDispatch(); - - const [loading, setLoading] = useState(false); - const [topDMs, setTopDMs] = useState([] as TopDM[]); - const [newMembers, setNewMembers] = useState([] as NewMember[]); - const [totalNewMembers, setTotalNewMembers] = useState(0); - - const currentTeam = useSelector(getCurrentTeam); - - const getMyTopTeamDMs = useCallback(async () => { - if (props.filterType === InsightsScopes.MY) { - setLoading(true); - const data: any = await dispatch(getMyTopDMs(currentTeam.id, 0, 6, props.timeFrame)); - if (data.data?.items) { - setTopDMs(data.data.items); - } - setLoading(false); - } - }, [props.timeFrame, props.filterType]); - - const getNewTeamMembersList = useCallback(async () => { - if (props.filterType === InsightsScopes.TEAM) { - setLoading(true); - const data: any = await dispatch(getNewTeamMembers(currentTeam.id, 0, 5, props.timeFrame)); - if (data.data?.items) { - setNewMembers(data.data.items); - } - - // Workaround for null response from API - if (data.data?.items === null) { - setNewMembers([]); - } - - if (data.data?.total_count) { - setTotalNewMembers(data.data.total_count); - } - setLoading(false); - } - }, [props.timeFrame, props.filterType]); - - useEffect(() => { - getNewTeamMembersList(); - }, [getNewTeamMembersList]); - - useEffect(() => { - getMyTopTeamDMs(); - }, [getMyTopTeamDMs]); - - const skeletonLoader = useMemo(() => { - const entries = []; - for (let i = 0; i < 5; i++) { - entries.push( -
- -
- -
-
- - -
-
, - ); - } - return entries; - }, []); - - const openInsightsModal = useCallback(() => { - trackEvent('insights', `open_modal_${props.widgetType.toLowerCase()}`); - dispatch(openModal({ - modalId: ModalIdentifiers.INSIGHTS, - dialogType: InsightsModal, - dialogProps: { - widgetType: props.widgetType, - title: localizeMessage('insights.newTeamMembers.title', 'New team members'), - subtitle: '', - filterType: props.filterType, - timeFrame: props.timeFrame, - }, - })); - }, [props.widgetType, props.filterType, props.timeFrame]); - - return ( -
- { - loading && - skeletonLoader - } - { - (!loading && topDMs && props.filterType === InsightsScopes.MY) && - topDMs.map((topDM: TopDM, index: number) => { - const barSize = ((topDM.post_count / topDMs[0].post_count) * 0.75); - return ( - - ); - }) - } - { - (!loading && props.filterType === InsightsScopes.TEAM && newMembers.length > 0) && ( - <> - - {newMembers.map((newMember, index) => { - return ( - - ); - })} - - ) - } - { - (((topDMs.length === 0 && props.filterType === InsightsScopes.MY) || (newMembers.length === 0 && props.filterType === InsightsScopes.TEAM)) && !loading) && - - } -
- ); -}; - -export default memo(widgetHoc(TopDMsAndNewMembers)); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/top_dms_item/top_dms_item.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/top_dms_item/top_dms_item.tsx deleted file mode 100644 index 864c3c13bf..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/top_dms_item/top_dms_item.tsx +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useCallback} from 'react'; -import {useSelector} from 'react-redux'; -import {FormattedMessage} from 'react-intl'; -import {Link} from 'react-router-dom'; - -import {trackEvent} from 'actions/telemetry_actions'; - -import {getTeammateNameDisplaySetting} from 'mattermost-redux/selectors/entities/preferences'; - -import {UserProfile} from '@mattermost/types/users'; -import {TopDM} from '@mattermost/types/insights'; -import {Team} from '@mattermost/types/teams'; - -import {displayUsername} from 'mattermost-redux/utils/user_utils'; - -import OverlayTrigger from 'components/overlay_trigger'; -import Avatar from 'components/widgets/users/avatar'; -import Tooltip from 'components/tooltip'; - -import {imageURLForUser} from 'utils/utils'; -import Constants from 'utils/constants'; - -import './../../../activity_and_insights.scss'; - -type Props = { - dm: TopDM; - barSize: number; - team: Team; -} - -const TopDMsItem = ({dm, barSize, team}: Props) => { - const teammateNameDisplaySetting = useSelector(getTeammateNameDisplaySetting); - - const tooltip = useCallback((messageCount: number) => { - return ( - - - - ); - }, []); - - const trackClick = useCallback(() => { - trackEvent('insights', 'open_dm_from_top_dms_widget'); - }, []); - - return ( - - -
-
- {displayUsername(dm.second_participant as UserProfile, teammateNameDisplaySetting)} -
- {dm.second_participant.position} -
- - {dm.post_count} - - -
-
- - - ); -}; - -export default memo(TopDMsItem); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/top_dms_table/top_dms_table.test.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/top_dms_table/top_dms_table.test.tsx deleted file mode 100644 index 28ab685c65..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/top_dms_table/top_dms_table.test.tsx +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import {Provider} from 'react-redux'; - -import {ReactWrapper} from 'enzyme'; - -import {BrowserRouter} from 'react-router-dom'; - -import {TimeFrames} from '@mattermost/types/insights'; - -import {mountWithIntl} from 'tests/helpers/intl-test-helper'; -import {act} from 'tests/react_testing_utils'; -import mockStore from 'tests/test_store'; - -import TopDMsTable from './top_dms_table'; - -const actImmediate = (wrapper: ReactWrapper) => - act( - () => - new Promise((resolve) => { - setImmediate(() => { - wrapper.update(); - resolve(); - }); - }), - ); - -jest.mock('mattermost-redux/actions/insights', () => ({ - ...jest.requireActual('mattermost-redux/actions/insights'), - getMyTopDMs: () => ({type: 'adsf', - data: { - has_next: false, - items: [ - { - post_count: 17, - second_participant: { - id: 'q7bgtp9dtfdwxriz1e8p8zon3e', - last_picture_update: 0, - first_name: 'Aaron', - last_name: 'Medina', - nickname: 'Centidel', - username: 'aaron.medina', - position: 'Systems Administrator I', - }, - }, - ], - }}), -})); - -describe('components/activity_and_insights/insights/top_dms_and_new_members', () => { - const props = { - filterType: 'MY', - timeFrame: TimeFrames.INSIGHTS_7_DAYS, - closeModal: jest.fn(), - }; - - const initialState = { - entities: { - teams: { - currentTeamId: 'team_id1', - teams: { - team_id1: { - id: 'team_id1', - name: 'team1', - }, - }, - }, - channels: { - channels: { - channel1: { - id: 'channel1', - team_id: 'team_id1', - name: 'channel1', - }, - }, - myMembers: { - channel1: { - id: 'channel1', - team_id: 'team_id1', - name: 'channel1', - }, - }, - }, - general: { - config: {}, - }, - users: { - currentUserId: 'current_user_id', - profiles: { - current_user_id: { - id: 'current_user_id', - }, - user1: { - id: 'user1', - }, - }, - }, - preferences: { - myPreferences: {}, - }, - groups: { - groups: {}, - myGroups: [], - }, - emojis: { - customEmoji: {}, - }, - }, - }; - - test('check if top dms render', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - expect(wrapper.find('.DataGrid_row').length).toEqual(1); - }); - - test('check if 0 top dms render', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - expect(wrapper.find('.DataGrid_row').length).toEqual(0); - }); -}); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/top_dms_table/top_dms_table.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/top_dms_table/top_dms_table.tsx deleted file mode 100644 index 08707cb41e..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_dms_and_new_members/top_dms_table/top_dms_table.tsx +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useCallback, useEffect, useMemo, useState} from 'react'; -import {FormattedMessage} from 'react-intl'; -import {useDispatch, useSelector} from 'react-redux'; -import {Link} from 'react-router-dom'; - -import classNames from 'classnames'; - -import {trackEvent} from 'actions/telemetry_actions'; - -import {getMyTopDMs} from 'mattermost-redux/actions/insights'; - -import {TimeFrame, TopDM} from '@mattermost/types/insights'; -import {UserProfile} from '@mattermost/types/users'; - -import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams'; -import {getTeammateNameDisplaySetting} from 'mattermost-redux/selectors/entities/preferences'; - -import {displayUsername} from 'mattermost-redux/utils/user_utils'; - -import {InsightsScopes} from 'utils/constants'; -import {imageURLForUser} from 'utils/utils'; - -import Avatar from 'components/widgets/users/avatar'; -import DataGrid, {Row, Column} from 'components/admin_console/data_grid/data_grid'; - -import './../../../activity_and_insights.scss'; - -type Props = { - filterType: string; - timeFrame: TimeFrame; - closeModal: () => void; -} - -const TopDMsTable = (props: Props) => { - const dispatch = useDispatch(); - - const [loading, setLoading] = useState(false); - const [topDMs, setTopDMs] = useState([] as TopDM[]); - - const teammateNameDisplaySetting = useSelector(getTeammateNameDisplaySetting); - const currentTeam = useSelector(getCurrentTeam); - - const getMyTopTeamDMs = useCallback(async () => { - if (props.filterType === InsightsScopes.MY) { - setLoading(true); - const data: any = await dispatch(getMyTopDMs(currentTeam.id, 0, 10, props.timeFrame)); - if (data.data?.items) { - setTopDMs(data.data.items); - } - setLoading(false); - } - }, [props.timeFrame, props.filterType]); - - useEffect(() => { - getMyTopTeamDMs(); - }, [getMyTopTeamDMs]); - - const closeModal = useCallback(() => { - trackEvent('insights', 'open_dm_from_top_dms_modal'); - props.closeModal(); - }, [props.closeModal]); - - const getColumns = useMemo((): Column[] => { - const columns: Column[] = [ - { - name: ( - - ), - field: 'rank', - className: 'rankCell', - width: 0.05, - }, - { - name: ( - - ), - field: 'user', - width: 0.4, - }, - { - name: ( - - ), - field: 'sent', - className: 'message-count', - width: 0.15, - }, - { - name: ( - - ), - field: 'received', - className: 'message-count', - width: 0.15, - }, - { - name: ( - - ), - field: 'total', - width: 0.25, - }, - ]; - return columns; - }, []); - - const getRows = useMemo((): Row[] => { - return topDMs.map((dm, i) => { - const barSize = (dm.post_count / topDMs[0].post_count); - return ( - { - cells: { - rank: ( - - {i + 1} - - ), - user: ( - - - {displayUsername(dm.second_participant as UserProfile, teammateNameDisplaySetting)} - - - ), - sent: ( - <> - {dm.outgoing_message_count} - - ), - received: ( - <> - {dm.post_count - dm.outgoing_message_count} - - ), - total: ( -
- - {dm.post_count} - - -
- ), - }, - } - ); - }); - }, [topDMs]); - - return ( - {}} - previousPage={() => {}} - startCount={1} - endCount={10} - total={0} - className={classNames('InsightsTable', 'TopDMsTable')} - /> - ); -}; - -export default memo(TopDMsTable); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_playbooks/top_playbooks.test.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_playbooks/top_playbooks.test.tsx deleted file mode 100644 index 57b9d5709f..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_playbooks/top_playbooks.test.tsx +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import {Provider} from 'react-redux'; - -import {ReactWrapper} from 'enzyme'; - -import {BrowserRouter} from 'react-router-dom'; - -import {CardSizes, InsightsWidgetTypes, TimeFrames} from '@mattermost/types/insights'; - -import {mountWithIntl} from 'tests/helpers/intl-test-helper'; -import {act} from 'tests/react_testing_utils'; -import mockStore from 'tests/test_store'; - -import TopPlaybooks from './top_playbooks'; - -jest.mock('react-redux', () => ({ - ...jest.requireActual('react-redux'), - useDispatch: jest.fn().mockReturnValue(() => {}), -})); - -const actImmediate = (wrapper: ReactWrapper) => - act( - () => - new Promise((resolve) => { - setImmediate(() => { - wrapper.update(); - resolve(); - }); - }), - ); - -describe('components/activity_and_insights/insights/top_playbooks', () => { - const props = { - filterType: 'TEAM', - timeFrame: TimeFrames.INSIGHTS_7_DAYS, - size: CardSizes.small, - widgetType: InsightsWidgetTypes.TOP_PLAYBOOKS, - class: 'top-playbooks-card', - timeFrameLabel: 'Last 7 days', - }; - - const initialState = { - entities: { - teams: { - currentTeamId: 'team_id1', - teams: { - team_id1: { - id: 'team_id1', - name: 'team1', - }, - }, - }, - channels: { - channels: { - channel1: { - id: 'channel1', - team_id: 'team_id1', - name: 'channel1', - }, - }, - }, - general: { - config: {}, - }, - users: { - currentUserId: 'current_user_id', - profiles: { - current_user_id: { - id: 'current_user_id', - }, - user1: { - id: 'user1', - }, - }, - }, - preferences: { - myPreferences: {}, - }, - groups: { - groups: {}, - myGroups: [], - }, - emojis: { - customEmoji: {}, - }, - }, - plugins: { - insightsHandlers: { - playbooks: async () => { - return { - items: [ - { - playbook_id: '6nrnwj5hfty8787mos9h6dtx6y', - num_runs: 3, - title: 'sth', - last_run_at: 1658827931570, - }, - { - playbook_id: '3ii9fwq47pdtupqdd6ajo3axuc', - num_runs: 2, - title: 'Test playbook', - last_run_at: 1659986623440, - }, - { - playbook_id: '3ii9fwq47pdtupqdd6ajo3axuc', - num_runs: 1, - title: 'Another playbook', - last_run_at: 1659555849231, - }, - ], - }; - }, - }, - }, - }; - - test('check if 3 team top playbooks render', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - expect(wrapper.find('a.playbook-item').length).toEqual(3); - }); - - test('check if 0 top boards render', async () => { - const state = { - ...initialState, - plugins: { - insightsHandlers: { - playbooks: async () => { - return { - items: [], - }; - }, - }, - }, - }; - const store = await mockStore(state); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - expect(wrapper.find('.empty-state').length).toEqual(1); - }); -}); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_playbooks/top_playbooks.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_playbooks/top_playbooks.tsx deleted file mode 100644 index aa6e82aabb..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_playbooks/top_playbooks.tsx +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useState, useCallback, useEffect, useMemo, ComponentProps} from 'react'; -import {useSelector} from 'react-redux'; -import {Link} from 'react-router-dom'; -import {FormattedMessage} from 'react-intl'; - -import {TopPlaybook} from '@mattermost/types/insights'; - -import {RectangleSkeletonLoader} from '@mattermost/components'; - -import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; - -import {trackEvent} from 'actions/telemetry_actions'; - -import {GlobalState} from 'types/store'; - -import Timestamp from 'components/timestamp'; - -import widgetHoc, {WidgetHocProps} from '../widget_hoc/widget_hoc'; -import WidgetEmptyState from '../widget_empty_state/widget_empty_state'; - -import './../../activity_and_insights.scss'; - -const TIME_SPEC: Partial> = { - units: [ - 'now', - 'minute', - ['hour', -48], - ['day', -30], - 'month', - 'year', - ], - useTime: false, - day: 'numeric', - style: 'long', -}; - -const TopPlaybooks = (props: WidgetHocProps) => { - const [loading, setLoading] = useState(false); - const [topPlaybooks, setPlaybooks] = useState([] as TopPlaybook[]); - - const currentTeamId = useSelector(getCurrentTeamId); - const playbooksHandler = useSelector((state: GlobalState) => state.plugins.insightsHandlers.playbooks); - - const getTopPlaybooks = useCallback(async () => { - setLoading(true); - const data: any = await playbooksHandler(props.timeFrame, 0, 3, currentTeamId, props.filterType); - if (data.items) { - setPlaybooks(data.items); - } - setLoading(false); - }, [props.timeFrame, currentTeamId, props.filterType]); - - useEffect(() => { - getTopPlaybooks(); - }, [getTopPlaybooks]); - - const skeletonLoader = useMemo(() => { - const entries = []; - for (let i = 0; i < 3; i++) { - entries.push( -
- - -
, - ); - } - return entries; - }, []); - - const trackClickEvent = useCallback(() => { - trackEvent('insights', 'open_playbook_from_top_playbooks_widget'); - }, []); - - return ( -
- { - loading && - skeletonLoader - } - { - (topPlaybooks && !loading) && -
- { - topPlaybooks.map((playbook, key) => { - const barSize = (playbook.num_runs / topPlaybooks[0].num_runs) * 100; - - return ( - -
- {playbook.title} - - - ), - }} - /> - -
-
- - - - -
- - ); - }) - } - -
- } - { - - (topPlaybooks.length === 0 && !loading) && - - } -
- ); -}; - -export default memo(widgetHoc(TopPlaybooks)); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_playbooks/top_playbooks_table/top_playbooks_table.test.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_playbooks/top_playbooks_table/top_playbooks_table.test.tsx deleted file mode 100644 index 3ec50b24a6..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_playbooks/top_playbooks_table/top_playbooks_table.test.tsx +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import {Provider} from 'react-redux'; - -import {ReactWrapper} from 'enzyme'; - -import {BrowserRouter} from 'react-router-dom'; - -import {TimeFrames} from '@mattermost/types/insights'; - -import {mountWithIntl} from 'tests/helpers/intl-test-helper'; -import {act} from 'tests/react_testing_utils'; -import mockStore from 'tests/test_store'; - -import TopPlaybooksTable from './top_playbooks_table'; - -const actImmediate = (wrapper: ReactWrapper) => - act( - () => - new Promise((resolve) => { - setImmediate(() => { - wrapper.update(); - resolve(); - }); - }), - ); - -describe('components/activity_and_insights/insights/top_playbooks_table', () => { - const props = { - filterType: 'TEAM', - timeFrame: TimeFrames.INSIGHTS_7_DAYS, - closeModal: jest.fn(), - }; - - const initialState = { - entities: { - teams: { - currentTeamId: 'team_id1', - teams: { - team_id1: { - id: 'team_id1', - name: 'team1', - }, - }, - }, - channels: { - channels: { - channel1: { - id: 'channel1', - team_id: 'team_id1', - name: 'channel1', - }, - }, - }, - general: { - config: {}, - }, - users: { - currentUserId: 'current_user_id', - profiles: { - current_user_id: { - id: 'current_user_id', - }, - user1: { - id: 'user1', - }, - }, - }, - preferences: { - myPreferences: {}, - }, - groups: { - groups: {}, - myGroups: [], - }, - emojis: { - customEmoji: {}, - }, - }, - plugins: { - insightsHandlers: { - playbooks: async () => { - return { - items: [ - { - playbook_id: '6nrnwj5hfty8787mos9h6dtx6y', - num_runs: 3, - title: 'sth', - last_run_at: 1658827931570, - }, - { - playbook_id: '3ii9fwq47pdtupqdd6ajo3axuc', - num_runs: 2, - title: 'Test playbook', - last_run_at: 1659986623440, - }, - { - playbook_id: '3ii9fwq47pdtupqdd6ajo3axuc', - num_runs: 1, - title: 'Another playbook', - last_run_at: 1659555849231, - }, - ], - }; - }, - }, - }, - }; - - test('check if 3 team top boards render', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - expect(wrapper.find('.DataGrid_row').length).toEqual(3); - }); - - test('check if 0 top boards render', async () => { - const state = { - ...initialState, - plugins: { - insightsHandlers: { - playbooks: async () => { - return { - items: [], - }; - }, - }, - }, - }; - const store = await mockStore(state); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - expect(wrapper.find('.DataGrid_row').length).toEqual(0); - }); -}); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_playbooks/top_playbooks_table/top_playbooks_table.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_playbooks/top_playbooks_table/top_playbooks_table.tsx deleted file mode 100644 index ccb26ee45e..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_playbooks/top_playbooks_table/top_playbooks_table.tsx +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useState, useMemo, useEffect, useCallback, ComponentProps} from 'react'; -import {useSelector} from 'react-redux'; -import {FormattedMessage} from 'react-intl'; -import {useHistory} from 'react-router-dom'; - -import classNames from 'classnames'; - -import {trackEvent} from 'actions/telemetry_actions'; - -import {GlobalState} from 'types/store'; - -import {TimeFrame, TopPlaybook} from '@mattermost/types/insights'; - -import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; - -import DataGrid, {Row, Column} from 'components/admin_console/data_grid/data_grid'; -import Timestamp from 'components/timestamp'; - -import './../../../activity_and_insights.scss'; - -type Props = { - filterType: string; - timeFrame: TimeFrame; - closeModal: () => void; -} - -const TIME_SPEC: Partial> = { - units: [ - 'now', - 'minute', - ['hour', -48], - ['day', -30], - 'month', - 'year', - ], - useTime: false, - day: 'numeric', - style: 'long', -}; - -const TopPlaybooksTable = (props: Props) => { - const history = useHistory(); - - const [loading, setLoading] = useState(false); - const [topPlaybooks, setTopPlaybooks] = useState([] as TopPlaybook[]); - - const currentTeamId = useSelector(getCurrentTeamId); - const playbooksHandler = useSelector((state: GlobalState) => state.plugins.insightsHandlers.playbooks); - - const getTopPlaybooks = useCallback(async () => { - setLoading(true); - const data: any = await playbooksHandler(props.timeFrame, 0, 10, currentTeamId, props.filterType); - if (data.items) { - setTopPlaybooks(data.items); - } - setLoading(false); - }, [props.timeFrame, currentTeamId, props.filterType]); - - useEffect(() => { - getTopPlaybooks(); - }, [getTopPlaybooks]); - - const goToPlaybook = useCallback((playbook: TopPlaybook) => { - props.closeModal(); - trackEvent('insights', 'open_playbook_from_top_playbooks_modal'); - history.push(`/playbooks/playbooks/${playbook.playbook_id}`); - }, [props.closeModal]); - - const getColumns = useMemo((): Column[] => { - const columns: Column[] = [ - { - name: ( - - ), - field: 'rank', - className: 'rankCell', - width: 0.07, - }, - { - name: ( - - ), - field: 'playbook', - width: 0.4, - }, - { - name: ( - - ), - field: 'lastRun', - width: 0.23, - }, - { - name: ( - - ), - field: 'totalRuns', - width: 0.3, - }, - ]; - return columns; - }, []); - - const getRows = useMemo((): Row[] => { - return topPlaybooks.map((playbook, i) => { - const barSize = (playbook.num_runs / topPlaybooks[0].num_runs); - - return ( - { - cells: { - rank: ( - - {i + 1} - - ), - playbook: ( -
- - {playbook.title} - -
- ), - lastRun: ( - - <> - - - ), - totalRuns: ( -
- - {playbook.num_runs} - - -
- ), - }, - onClick: () => { - goToPlaybook(playbook); - }, - } - ); - }); - }, [topPlaybooks]); - - return ( - {}} - previousPage={() => {}} - startCount={1} - endCount={10} - total={0} - className={classNames('InsightsTable', 'TopPlaybooksTable')} - /> - ); -}; - -export default memo(TopPlaybooksTable); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_reactions/top_reactions.test.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_reactions/top_reactions.test.tsx deleted file mode 100644 index d775f947c4..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_reactions/top_reactions.test.tsx +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import {Provider} from 'react-redux'; - -import {ReactWrapper} from 'enzyme'; - -import {CardSizes, InsightsWidgetTypes, TimeFrames} from '@mattermost/types/insights'; - -import {mountWithIntl} from 'tests/helpers/intl-test-helper'; -import {act} from 'tests/react_testing_utils'; -import mockStore from 'tests/test_store'; - -import TopReactions from './top_reactions'; - -const actImmediate = (wrapper: ReactWrapper) => - act( - () => - new Promise((resolve) => { - setImmediate(() => { - wrapper.update(); - resolve(); - }); - }), - ); - -describe('components/activity_and_insights/insights/top_reactions', () => { - const props = { - filterType: 'TEAM', - timeFrame: TimeFrames.INSIGHTS_7_DAYS, - size: CardSizes.small, - widgetType: InsightsWidgetTypes.TOP_REACTIONS, - class: 'top-reactions-card', - timeFrameLabel: 'Last 7 days', - }; - - const initialState = { - entities: { - teams: { - currentTeamId: 'team_id1', - teams: { - team_id1: { - id: 'team_id1', - name: 'team1', - }, - }, - }, - general: { - config: {}, - }, - emojis: { - customEmoji: {}, - }, - users: { - currentUserId: 'current_user_id', - }, - insights: { - myTopReactions: { - team_id1: { - today: {}, - '7_day': { - grinning: { - emoji_name: 'grinning', - count: 190, - }, - tada: { - emoji_name: 'tada', - count: 180, - }, - heart: { - emoji_name: 'heart', - count: 110, - }, - laughing: { - emoji_name: 'laughing', - count: 80, - }, - }, - '28_day': {}, - }, - }, - topReactions: { - team_id1: { - today: {}, - '7_day': { - grinning: { - emoji_name: 'grinning', - count: 145, - }, - tada: { - emoji_name: 'tada', - count: 100, - }, - }, - '28_day': {}, - }, - }, - }, - }, - }; - - test('check if empty', async () => { - const store = await mockStore({ - entities: { - teams: { - currentTeamId: 'team_id1', - }, - users: { - currentUserId: 'current_user_id', - }, - insights: { - topReactions: {}, - myTopReactions: {}, - }, - }, - }); - const wrapper = mountWithIntl( - - - , - ); - await actImmediate(wrapper); - expect(wrapper.find('.empty-state').length).toEqual(1); - }); - - test('check if bar chart renders', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - , - ); - await actImmediate(wrapper); - expect(wrapper.find('.bar-chart-entry').length).toEqual(2); - }); -}); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_reactions/top_reactions.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_reactions/top_reactions.tsx deleted file mode 100644 index 1f87085d54..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_reactions/top_reactions.tsx +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useEffect, useState, useCallback, useMemo} from 'react'; -import {shallowEqual, useDispatch, useSelector} from 'react-redux'; - -import {GlobalState} from '@mattermost/types/store'; -import {TopReaction} from '@mattermost/types/insights'; - -import {CircleSkeletonLoader, RectangleSkeletonLoader} from '@mattermost/components'; - -import {getTopReactionsForTeam, getMyTopReactions} from 'mattermost-redux/actions/insights'; -import {getTopReactionsForCurrentTeam, getMyTopReactionsForCurrentTeam} from 'mattermost-redux/selectors/entities/insights'; -import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; - -import {loadCustomEmojisIfNeeded} from 'actions/emoji_actions'; -import {InsightsScopes} from 'utils/constants'; - -import widgetHoc, {WidgetHocProps} from '../widget_hoc/widget_hoc'; - -import WidgetEmptyState from '../widget_empty_state/widget_empty_state'; - -import TopReactionsBarChart from './top_reactions_bar_chart/top_reactions_bar_chart'; - -import './../../activity_and_insights.scss'; - -const TopReactions = (props: WidgetHocProps) => { - const dispatch = useDispatch(); - - const [loading, setLoading] = useState(true); - const [topReactions, setTopReactions] = useState([] as TopReaction[]); - - const teamTopReactions = useSelector((state: GlobalState) => getTopReactionsForCurrentTeam(state, props.timeFrame, 5), shallowEqual); - const myTopReactions = useSelector((state: GlobalState) => getMyTopReactionsForCurrentTeam(state, props.timeFrame, 5), shallowEqual); - - useEffect(() => { - const reactions = props.filterType === InsightsScopes.TEAM ? teamTopReactions : myTopReactions; - setTopReactions(reactions); - dispatch(loadCustomEmojisIfNeeded(reactions.map((reaction) => reaction.emoji_name))); - }, [props.filterType, teamTopReactions, myTopReactions]); - - const currentTeamId = useSelector(getCurrentTeamId); - - const getTopTeamReactions = useCallback(async () => { - if (props.filterType === InsightsScopes.TEAM) { - setLoading(true); - await dispatch(getTopReactionsForTeam(currentTeamId, 0, 10, props.timeFrame)); - setLoading(false); - } - }, [props.timeFrame, currentTeamId, props.filterType]); - - useEffect(() => { - getTopTeamReactions(); - }, [getTopTeamReactions]); - - const getMyTeamReactions = useCallback(async () => { - if (props.filterType === InsightsScopes.MY) { - setLoading(true); - await dispatch(getMyTopReactions(currentTeamId, 0, 10, props.timeFrame)); - setLoading(false); - } - }, [props.timeFrame, props.filterType]); - - useEffect(() => { - getMyTeamReactions(); - }, [getMyTeamReactions]); - - const skeletonLoader = useMemo(() => { - const barChartHeights = [140, 178, 140, 120, 140]; - const entries = []; - - for (let i = 0; i < 5; i++) { - entries.push( -
- - -
, - ); - } - return entries; - }, []); - - return ( -
- { - loading && - skeletonLoader - } - { - (topReactions && !loading) && - - } - { - (topReactions.length === 0 && !loading) && - - } -
- ); -}; - -export default memo(widgetHoc(TopReactions)); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_reactions/top_reactions_bar_chart/__snapshots__/top_reactions_bar_chart.test.tsx.snap b/webapp/channels/src/components/activity_and_insights/insights/top_reactions/top_reactions_bar_chart/__snapshots__/top_reactions_bar_chart.test.tsx.snap deleted file mode 100644 index 52ba86542e..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_reactions/top_reactions_bar_chart/__snapshots__/top_reactions_bar_chart.test.tsx.snap +++ /dev/null @@ -1,151 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`components/activity_and_insights/insights/top_reactions/top_reactions_bar_chart should match snapshot with team 1`] = ` - -
- - 190 - -
- - - - - -
-
- - 180 - -
- - - - - -
-
- - 110 - -
- - - - - -
-
- - 80 - -
- - - - - -
-
- - 40 - -
- - - - - -
- -`; diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_reactions/top_reactions_bar_chart/top_reactions_bar_chart.test.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_reactions/top_reactions_bar_chart/top_reactions_bar_chart.test.tsx deleted file mode 100644 index 13b533418a..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_reactions/top_reactions_bar_chart/top_reactions_bar_chart.test.tsx +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; - -import {shallow} from 'enzyme'; - -import TopReactionsBarChart from './top_reactions_bar_chart'; - -describe('components/activity_and_insights/insights/top_reactions/top_reactions_bar_chart', () => { - const props = { - reactions: [ - { - emoji_name: 'grinning', - count: 190, - }, - { - emoji_name: 'tada', - count: 180, - }, - { - emoji_name: 'heart', - count: 110, - }, - { - emoji_name: 'laughing', - count: 80, - }, - { - emoji_name: '+1', - count: 40, - }, - ], - }; - - test('should match snapshot with team', () => { - const wrapper = shallow( - , - ); - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_reactions/top_reactions_bar_chart/top_reactions_bar_chart.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_reactions/top_reactions_bar_chart/top_reactions_bar_chart.tsx deleted file mode 100644 index 3e2cda8703..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_reactions/top_reactions_bar_chart/top_reactions_bar_chart.tsx +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useCallback} from 'react'; - -import {TopReaction} from '@mattermost/types/insights'; - -import RenderEmoji from 'components/emoji/render_emoji'; -import SimpleTooltip from 'components/widgets/simple_tooltip'; - -type Props = { - reactions: TopReaction[]; -} - -const TopReactionsBarChart = (props: Props) => { - const barChartEntries = useCallback(() => { - const reactions = props.reactions.map((reaction) => { - const highestCount = props.reactions[0].count; - const maxHeight = 156; - - let barHeight = (reaction.count / highestCount) * maxHeight; - - if (highestCount === reaction.count) { - barHeight = maxHeight; - } - - return ( -
- {reaction.count} -
- - - - - -
- ); - }); - - return reactions; - }, [props.reactions]); - - return ( - <> - {barChartEntries()} - - ); -}; - -export default memo(TopReactionsBarChart); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_reactions/top_reactions_table/top_reactions_table.test.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_reactions/top_reactions_table/top_reactions_table.test.tsx deleted file mode 100644 index b8f0a31933..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_reactions/top_reactions_table/top_reactions_table.test.tsx +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import {Provider} from 'react-redux'; - -import {ReactWrapper} from 'enzyme'; - -import {TimeFrames} from '@mattermost/types/insights'; - -import {mountWithIntl} from 'tests/helpers/intl-test-helper'; -import {act} from 'tests/react_testing_utils'; -import mockStore from 'tests/test_store'; - -import TopReactionsTable from './top_reactions_table'; - -const actImmediate = (wrapper: ReactWrapper) => - act( - () => - new Promise((resolve) => { - setImmediate(() => { - wrapper.update(); - resolve(); - }); - }), - ); - -describe('components/activity_and_insights/insights/top_reactions/top_reactions_table', () => { - const props = { - filterType: 'TEAM', - timeFrame: TimeFrames.INSIGHTS_7_DAYS, - }; - - const initialState = { - entities: { - general: {config: {}}, - teams: { - currentTeamId: 'team_id1', - teams: { - team_id1: { - id: 'team_id1', - name: 'team1', - }, - }, - }, - users: { - currentUserId: 'current_user_id', - }, - insights: { - myTopReactions: { - team_id1: { - today: {}, - '7_day': { - grinning: { - emoji_name: 'grinning', - count: 190, - }, - tada: { - emoji_name: 'tada', - count: 180, - }, - heart: { - emoji_name: 'heart', - count: 110, - }, - laughing: { - emoji_name: 'laughing', - count: 80, - }, - }, - '28_day': {}, - }, - }, - topReactions: { - team_id1: { - today: {}, - '7_day': { - grinning: { - emoji_name: 'grinning', - count: 145, - }, - tada: { - emoji_name: 'tada', - count: 100, - }, - }, - '28_day': {}, - }, - }, - }, - }, - }; - - test('should be 2 rows for team reactions', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - , - ); - await actImmediate(wrapper); - expect(wrapper.find('.DataGrid_row').length).toEqual(2); - }); - - test('should be 4 rows for my reactions', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - , - ); - await actImmediate(wrapper); - expect(wrapper.find('.DataGrid_row').length).toEqual(4); - }); - - test('should be 0 rows for my reactions today', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - , - ); - await actImmediate(wrapper); - expect(wrapper.find('.DataGrid_row').length).toEqual(0); - }); -}); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_reactions/top_reactions_table/top_reactions_table.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_reactions/top_reactions_table/top_reactions_table.tsx deleted file mode 100644 index 3368ed6637..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_reactions/top_reactions_table/top_reactions_table.tsx +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useCallback, useEffect, useMemo, useState} from 'react'; - -import {FormattedMessage} from 'react-intl'; - -import {shallowEqual, useDispatch, useSelector} from 'react-redux'; - -import {TimeFrame} from '@mattermost/types/insights'; - -import {loadCustomEmojisIfNeeded} from 'actions/emoji_actions'; -import DataGrid, {Row, Column} from 'components/admin_console/data_grid/data_grid'; -import RenderEmoji from 'components/emoji/render_emoji'; - -import {InsightsScopes} from 'utils/constants'; -import {GlobalState} from '@mattermost/types/store'; -import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; -import {getMyTopReactionsForCurrentTeam, getTopReactionsForCurrentTeam} from 'mattermost-redux/selectors/entities/insights'; -import {getTopReactionsForTeam, getMyTopReactions} from 'mattermost-redux/actions/insights'; - -import './../../../activity_and_insights.scss'; - -type Props = { - filterType: string; - timeFrame: TimeFrame; -} - -const TopReactionsTable = (props: Props) => { - const dispatch = useDispatch(); - - const [loading, setLoading] = useState(true); - - const teamTopReactions = useSelector((state: GlobalState) => getTopReactionsForCurrentTeam(state, props.timeFrame, 10), shallowEqual); - const myTopReactions = useSelector((state: GlobalState) => getMyTopReactionsForCurrentTeam(state, props.timeFrame, 10), shallowEqual); - - const currentTeamId = useSelector(getCurrentTeamId); - - const getTopTeamReactions = useCallback(async () => { - if (props.filterType === InsightsScopes.TEAM) { - setLoading(true); - await dispatch(getTopReactionsForTeam(currentTeamId, 0, 10, props.timeFrame)); - setLoading(false); - } - }, [props.timeFrame, currentTeamId, props.filterType]); - - useEffect(() => { - getTopTeamReactions(); - }, [getTopTeamReactions]); - - const getMyTeamReactions = useCallback(async () => { - if (props.filterType === InsightsScopes.MY) { - setLoading(true); - await dispatch(getMyTopReactions(currentTeamId, 0, 10, props.timeFrame)); - setLoading(false); - } - }, [props.timeFrame, props.filterType]); - - useEffect(() => { - getMyTeamReactions(); - }, [getMyTeamReactions]); - - useEffect(() => { - const reactions = props.filterType === InsightsScopes.TEAM ? teamTopReactions : myTopReactions; - dispatch(loadCustomEmojisIfNeeded(reactions.map((reaction) => reaction.emoji_name))); - }, [props.filterType, teamTopReactions, myTopReactions]); - - const getColumns = useMemo((): Column[] => { - const columns: Column[] = [ - { - name: ( - - ), - field: 'rank', - className: 'rankCell', - width: 0.2, - }, - { - name: ( - - ), - field: 'reaction', - }, - { - name: ( - - ), - field: 'times_used', - }, - ]; - return columns; - }, []); - - const getRows = useMemo((): Row[] => { - const topReactions = props.filterType === InsightsScopes.TEAM ? teamTopReactions : myTopReactions; - - return topReactions.map((reaction, i) => { - const barSize = (reaction.count / topReactions[0].count); - return ( - { - cells: { - rank: ( - - {i + 1} - - ), - reaction: ( - <> - - - {reaction.emoji_name} - - - ), - times_used: ( -
- - {reaction.count} - - -
- ), - }, - } - ); - }); - }, [teamTopReactions, myTopReactions]); - - return ( - {}} - previousPage={() => {}} - startCount={1} - endCount={10} - total={0} - className={'InsightsTable'} - /> - ); -}; - -export default memo(TopReactionsTable); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_threads/top_threads.scss b/webapp/channels/src/components/activity_and_insights/insights/top_threads/top_threads.scss deleted file mode 100644 index fede65eca3..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_threads/top_threads.scss +++ /dev/null @@ -1,82 +0,0 @@ -.InsightsTable, -.top-thread-container { - .thread-details { - display: flex; - align-items: center; - margin-bottom: 4px; - gap: 8px; - - .display-name { - overflow: hidden; - color: var(--center-channel-color); - font-family: 'Open Sans', sans-serif; - font-size: 12px; - font-style: normal; - font-weight: 600; - line-height: 16px; - text-overflow: ellipsis; - white-space: nowrap; - } - - .reply-count { - flex: 1; - margin-left: auto; - text-align: end; - - i.icon { - width: 12px; - height: 12px; - color: rgba(var(--center-channel-color-rgb), 0.56); - font-size: 12px; - } - - span { - color: rgba(var(--center-channel-color-rgb), 0.64); - font-family: 'Open Sans', sans-serif; - font-size: 11px; - font-style: normal; - font-weight: 600; - line-height: 16px; - } - } - } - - .preview { - display: -webkit-box; - overflow: hidden; - height: 32px; - max-height: 32px; - margin: 0; - -webkit-box-orient: vertical; - color: rgba(var(--center-channel-color-rgb), 0.72); - font-family: 'Open Sans', sans-serif; - font-size: 12px; - font-style: normal; - font-weight: 400; - -webkit-line-clamp: 2; - line-height: 16px; - - .compliance-information { - font-style: italic; - } - - .markdown__heading { - margin: inherit; - font-size: inherit; - line-height: inherit; - } - - .file_card { - width: auto; - height: 32px; - margin: 0; - - img.file_card__image { - min-width: 24px; - max-width: 24px; - min-height: 24px; - max-height: 24px; - } - } - } -} diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_threads/top_threads.test.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_threads/top_threads.test.tsx deleted file mode 100644 index 900e57e122..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_threads/top_threads.test.tsx +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import {Provider} from 'react-redux'; - -import {ReactWrapper} from 'enzyme'; - -import {BrowserRouter} from 'react-router-dom'; - -import {CardSizes, InsightsWidgetTypes, TimeFrames} from '@mattermost/types/insights'; - -import {mountWithIntl} from 'tests/helpers/intl-test-helper'; -import {act} from 'tests/react_testing_utils'; -import mockStore from 'tests/test_store'; - -import TopThreads from './top_threads'; - -const actImmediate = (wrapper: ReactWrapper) => - act( - () => - new Promise((resolve) => { - setImmediate(() => { - wrapper.update(); - resolve(); - }); - }), - ); - -jest.mock('mattermost-redux/actions/insights', () => ({ - ...jest.requireActual('mattermost-redux/actions/insights'), - getMyTopThreads: () => ({type: 'adsf', data: {}}), - getTopThreadsForTeam: () => ({type: 'adsf', - data: { - has_next: false, - items: [ - { - post_id: 'post1', - reply_count: 18, - channel_id: 'channel1', - channel_display_name: 'nostrum', - channel_name: 'channel1', - message: 'ducimus sed aut sunt corrupti necessitatibus quasi.\nreiciendis ipsa consequuntur fugiat a eaque.', - participants: [ - 'user1', - ], - user_id: 'user1', - user_information: { - id: 'user1', - last_picture_update: 0, - first_name: 'Kathryn', - last_name: 'Mills', - }, - post: { - id: 'post1', - create_at: 1653488972484, - update_at: 1653489070820, - edit_at: 0, - delete_at: 0, - is_pinned: false, - user_id: 'user1', - channel_id: 'channel1', - root_id: '', - original_id: '', - message: 'ducimus sed aut sunt corrupti necessitatibus quasi.\nreiciendis ipsa consequuntur fugiat a eaque.', - type: '', - props: {}, - hashtags: '', - pending_post_id: '', - reply_count: 18, - last_reply_at: 0, - participants: null, - metadata: {}, - }, - }, - ], - }}), -})); - -describe('components/activity_and_insights/insights/top_threads', () => { - const props = { - filterType: 'TEAM', - timeFrame: TimeFrames.INSIGHTS_7_DAYS, - size: CardSizes.small, - widgetType: InsightsWidgetTypes.TOP_THREADS, - class: 'top-threads-card', - timeFrameLabel: 'Last 7 days', - }; - - const initialState = { - entities: { - teams: { - currentTeamId: 'team_id1', - teams: { - team_id1: { - id: 'team_id1', - name: 'team1', - }, - }, - }, - channels: { - channels: { - channel1: { - id: 'channel1', - team_id: 'team_id1', - name: 'channel1', - }, - }, - myMembers: { - channel1: { - id: 'channel1', - team_id: 'team_id1', - name: 'channel1', - }, - }, - }, - general: { - config: {}, - }, - users: { - currentUserId: 'current_user_id', - profiles: { - current_user_id: { - id: 'current_user_id', - }, - user1: { - id: 'user1', - }, - }, - }, - preferences: { - myPreferences: {}, - }, - groups: { - groups: {}, - myGroups: [], - }, - emojis: { - customEmoji: {}, - }, - }, - }; - - test('check if 1 team top threads render', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - expect(wrapper.find('.thread-item').length).toEqual(1); - }); - - test('check if 0 my top channels render', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - expect(wrapper.find('.empty-state').length).toEqual(1); - }); -}); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_threads/top_threads.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_threads/top_threads.tsx deleted file mode 100644 index f650834a07..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_threads/top_threads.tsx +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useEffect, useState, useCallback, useMemo} from 'react'; -import {useDispatch, useSelector} from 'react-redux'; - -import {TopThread} from '@mattermost/types/insights'; -import {GlobalState} from '@mattermost/types/store'; - -import {CircleSkeletonLoader, RectangleSkeletonLoader} from '@mattermost/components'; - -import {getMyTopThreads, getTopThreadsForTeam} from 'mattermost-redux/actions/insights'; -import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; - -import {InsightsScopes} from 'utils/constants'; - -import widgetHoc, {WidgetHocProps} from '../widget_hoc/widget_hoc'; -import WidgetEmptyState from '../widget_empty_state/widget_empty_state'; - -import TopThreadsItem from './top_threads_item/top_threads_item'; - -import './../../activity_and_insights.scss'; -import './top_threads.scss'; - -const TopThreads = (props: WidgetHocProps) => { - const dispatch = useDispatch(); - - const [loading, setLoading] = useState(true); - const [topThreads, setTopThreads] = useState([] as TopThread[]); - - const currentTeamId = useSelector(getCurrentTeamId); - const complianceExportEnabled = useSelector((state: GlobalState) => state.entities.general.config.EnableComplianceExport); - - const getTopTeamThreads = useCallback(async () => { - if (props.filterType === InsightsScopes.TEAM) { - setLoading(true); - const data: any = await dispatch(getTopThreadsForTeam(currentTeamId, 0, 3, props.timeFrame)); - if (data.data?.items) { - setTopThreads(data.data.items); - } - setLoading(false); - } - }, [props.timeFrame, currentTeamId, props.filterType]); - - useEffect(() => { - getTopTeamThreads(); - }, [getTopTeamThreads]); - - const getMyTeamThreads = useCallback(async () => { - if (props.filterType === InsightsScopes.MY) { - setLoading(true); - const data: any = await dispatch(getMyTopThreads(currentTeamId, 0, 3, props.timeFrame)); - if (data.data?.items) { - setTopThreads(data.data.items); - } - setLoading(false); - } - }, [props.timeFrame, props.filterType]); - - useEffect(() => { - getMyTeamThreads(); - }, [getMyTeamThreads]); - - const skeletonLoader = useMemo(() => { - const entries = []; - for (let i = 0; i < 3; i++) { - entries.push( -
-
- - -
-
- - -
-
, - ); - } - return entries; - }, []); - - return ( -
- { - loading && - skeletonLoader - } - { - (topThreads && !loading) && -
- { - topThreads.map((thread, key) => { - return ( - - ); - }) - } -
- - } - { - (topThreads.length === 0 && !loading) && - - } -
- ); -}; - -export default memo(widgetHoc(TopThreads)); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_threads/top_threads_item/top_threads_item.test.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_threads/top_threads_item/top_threads_item.test.tsx deleted file mode 100644 index 653ccced43..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_threads/top_threads_item/top_threads_item.test.tsx +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import {Provider} from 'react-redux'; - -import {ReactWrapper} from 'enzyme'; - -import {BrowserRouter} from 'react-router-dom'; - -import {PostType} from '@mattermost/types/posts'; - -import {mountWithIntl} from 'tests/helpers/intl-test-helper'; -import {act} from 'tests/react_testing_utils'; -import mockStore from 'tests/test_store'; - -import TopThreadsItem from './top_threads_item'; - -const actImmediate = (wrapper: ReactWrapper) => - act( - () => - new Promise((resolve) => { - setImmediate(() => { - wrapper.update(); - resolve(); - }); - }), - ); - -describe('components/activity_and_insights/insights/top_threads/top_threads_item', () => { - const props = { - thread: { - channel_id: 'channel1', - channel_display_name: 'nostrum', - channel_name: 'channel1', - participants: [ - 'user1', - ], - user_information: { - id: 'user1', - last_picture_update: 0, - first_name: 'Kathryn', - last_name: 'Mills', - }, - post: { - id: 'post1', - create_at: 1653488972484, - update_at: 1653489070820, - edit_at: 0, - delete_at: 0, - is_pinned: false, - user_id: 'user1', - channel_id: 'channel1', - root_id: '', - original_id: '', - message: 'ducimus sed aut sunt corrupti necessitatibus quasi.\nreiciendis ipsa consequuntur fugiat a eaque.', - type: '' as PostType, - props: {}, - hashtags: '', - pending_post_id: '', - reply_count: 18, - last_reply_at: 0, - participants: null, - metadata: { - embeds: [], - emojis: [], - files: [], - images: {}, - reactions: [], - }, - }, - }, - complianceExportEnabled: 'false', - }; - - const initialState = { - entities: { - teams: { - currentTeamId: 'team_id1', - teams: { - team_id1: { - id: 'team_id1', - name: 'team1', - }, - }, - }, - channels: { - channels: { - channel1: { - id: 'channel1', - team_id: 'team_id1', - name: 'channel1', - }, - }, - myMembers: {}, - }, - general: { - config: {}, - license: { - Compliance: 'true', - }, - }, - users: { - currentUserId: 'current_user_id', - profiles: { - current_user_id: { - id: 'current_user_id', - }, - user1: { - id: 'user1', - }, - }, - }, - preferences: { - myPreferences: {}, - }, - groups: { - groups: {}, - myGroups: [], - }, - emojis: { - customEmoji: {}, - }, - }, - }; - - test('check if thread item renders', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - expect(wrapper.find('.thread-item').length).toEqual(1); - }); - - test('check compliance preview does not render', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - expect(wrapper.text().includes('You\'ll need to join the nostrum channel to see this thread.')).toBe(false); - }); - - test('check if compliance preview renders', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - expect(wrapper.text().includes('You\'ll need to join the nostrum channel to see this thread.')).toBe(true); - }); - - test('check if compliance preview does not render when not licensed for it', async () => { - const store = await mockStore({ - entities: { - teams: { - currentTeamId: 'team_id1', - teams: { - team_id1: { - id: 'team_id1', - name: 'team1', - }, - }, - }, - channels: { - channels: { - channel1: { - id: 'channel1', - team_id: 'team_id1', - name: 'channel1', - }, - }, - myMembers: {}, - }, - general: { - config: {}, - license: { - Compliance: 'false', - }, - }, - users: { - currentUserId: 'current_user_id', - profiles: { - current_user_id: { - id: 'current_user_id', - }, - user1: { - id: 'user1', - }, - }, - }, - preferences: { - myPreferences: {}, - }, - groups: { - groups: {}, - myGroups: [], - }, - emojis: { - customEmoji: {}, - }, - }, - }); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - expect(wrapper.text().includes('You\'ll need to join the nostrum channel to see this thread.')).toBe(false); - }); -}); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_threads/top_threads_item/top_threads_item.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_threads/top_threads_item/top_threads_item.tsx deleted file mode 100644 index 822af1c450..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_threads/top_threads_item/top_threads_item.tsx +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useCallback} from 'react'; -import {useDispatch, useSelector} from 'react-redux'; - -import {FormattedMessage} from 'react-intl'; - -import Tag from 'components/widgets/tag/tag'; - -import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; -import {getTeammateNameDisplaySetting} from 'mattermost-redux/selectors/entities/preferences'; -import {displayUsername} from 'mattermost-redux/utils/user_utils'; - -import {TopThread} from '@mattermost/types/insights'; -import {UserProfile} from '@mattermost/types/users'; - -import {selectPostAndParentChannel} from 'actions/views/rhs'; -import {trackEvent} from 'actions/telemetry_actions'; -import {openModal} from 'actions/views/modals'; - -import Avatar from 'components/widgets/users/avatar'; -import Markdown from 'components/markdown'; -import Attachment from 'components/threading/global_threads/thread_item/attachments'; - -import {ModalIdentifiers} from 'utils/constants'; -import {imageURLForUser} from 'utils/utils'; - -import JoinChannelModal from '../../join_channel_modal/join_channel_modal'; -import {getMyChannelMembership} from 'mattermost-redux/selectors/entities/channels'; -import {GlobalState} from '@mattermost/types/store'; -import {getLicense} from 'mattermost-redux/selectors/entities/general'; - -type Props = { - thread: TopThread; - complianceExportEnabled?: string; -} - -const TopThreadsItem = ({thread, complianceExportEnabled}: Props) => { - const dispatch = useDispatch(); - - const isChannelMember = useSelector((state: GlobalState) => getMyChannelMembership(state, thread.channel_id)); - const currentTeamId = useSelector(getCurrentTeamId); - const teammateNameDisplaySetting = useSelector(getTeammateNameDisplaySetting); - const license = useSelector(getLicense); - - const openRHSOrJoinChannel = useCallback(() => { - if (!isChannelMember && (license.Compliance === 'true' && complianceExportEnabled === 'true')) { - dispatch(openModal({ - modalId: ModalIdentifiers.INSIGHTS, - dialogType: JoinChannelModal, - dialogProps: { - thread, - currentTeamId, - }, - })); - } else { - dispatch(selectPostAndParentChannel(thread.post)); - } - }, [thread, isChannelMember, currentTeamId]); - - const getPreview = useCallback(() => { - if (!isChannelMember && (license.Compliance === 'true' && complianceExportEnabled === 'true')) { - return ( - - {thread.channel_display_name}, - }} - /> - - ); - } - - if (thread.post.message) { - return ( - - ); - } - - return ( - - ); - }, [thread, isChannelMember]); - - return ( -
{ - trackEvent('insights', 'open_thread_from_top_threads_widget'); - openRHSOrJoinChannel(); - }} - key={thread.post.id} - > -
- - {displayUsername(thread.user_information as UserProfile, teammateNameDisplaySetting)} - -
- - {thread.post.reply_count} -
-
-
- {getPreview()} -
-
- ); -}; - -export default memo(TopThreadsItem); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_threads/top_threads_table/top_threads_table.test.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_threads/top_threads_table/top_threads_table.test.tsx deleted file mode 100644 index 6781eb88b5..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_threads/top_threads_table/top_threads_table.test.tsx +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import {Provider} from 'react-redux'; - -import {ReactWrapper} from 'enzyme'; - -import {BrowserRouter} from 'react-router-dom'; - -import {TimeFrames} from '@mattermost/types/insights'; - -import {mountWithIntl} from 'tests/helpers/intl-test-helper'; -import {act} from 'tests/react_testing_utils'; -import mockStore from 'tests/test_store'; - -import TopThreadsTable from './top_threads_table'; - -const actImmediate = (wrapper: ReactWrapper) => - act( - () => - new Promise((resolve) => { - setImmediate(() => { - wrapper.update(); - resolve(); - }); - }), - ); - -jest.mock('mattermost-redux/actions/insights', () => ({ - ...jest.requireActual('mattermost-redux/actions/insights'), - getMyTopThreads: () => ({type: 'adsf', data: {}}), - getTopThreadsForTeam: () => ({type: 'adsf', - data: { - has_next: false, - items: [ - { - post_id: 'post1', - reply_count: 18, - channel_id: 'channel1', - channel_display_name: 'nostrum', - channel_name: 'channel1', - message: 'ducimus sed aut sunt corrupti necessitatibus quasi.\nreiciendis ipsa consequuntur fugiat a eaque.', - participants: [ - 'user1', - ], - user_id: 'user1', - user_information: { - id: 'user1', - last_picture_update: 0, - first_name: 'Kathryn', - last_name: 'Mills', - }, - post: { - id: 'post1', - create_at: 1653488972484, - update_at: 1653489070820, - edit_at: 0, - delete_at: 0, - is_pinned: false, - user_id: 'user1', - channel_id: 'channel1', - root_id: '', - original_id: '', - message: 'ducimus sed aut sunt corrupti necessitatibus quasi.\nreiciendis ipsa consequuntur fugiat a eaque.', - type: '', - props: {}, - hashtags: '', - pending_post_id: '', - reply_count: 18, - last_reply_at: 0, - participants: null, - metadata: {}, - }, - }, - ], - }}), -})); - -describe('components/activity_and_insights/insights/top_threads/top_threads_table', () => { - const props = { - filterType: 'TEAM', - timeFrame: TimeFrames.INSIGHTS_7_DAYS, - closeModal: jest.fn(), - }; - - const initialState = { - entities: { - teams: { - currentTeamId: 'team_id1', - teams: { - team_id1: { - id: 'team_id1', - name: 'team1', - }, - }, - }, - channels: { - channels: { - channel1: { - id: 'channel1', - team_id: 'team_id1', - name: 'channel1', - }, - }, - myMembers: { - channel1: { - id: 'channel1', - team_id: 'team_id1', - name: 'channel1', - }, - }, - }, - general: { - config: {}, - license: { - Compliance: 'true', - }, - }, - users: { - currentUserId: 'current_user_id', - profiles: { - current_user_id: { - id: 'current_user_id', - }, - user1: { - id: 'user1', - }, - }, - }, - preferences: { - myPreferences: {}, - }, - groups: { - groups: {}, - myGroups: [], - }, - emojis: { - customEmoji: {}, - }, - }, - }; - - test('check if 1 team top thread renders', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - expect(wrapper.find('.DataGrid_row').length).toEqual(1); - }); - - test('check if 0 my top threads render', async () => { - const store = await mockStore(initialState); - const wrapper = mountWithIntl( - - - - - , - ); - await actImmediate(wrapper); - expect(wrapper.find('.DataGrid_row').length).toEqual(0); - }); -}); diff --git a/webapp/channels/src/components/activity_and_insights/insights/top_threads/top_threads_table/top_threads_table.tsx b/webapp/channels/src/components/activity_and_insights/insights/top_threads/top_threads_table/top_threads_table.tsx deleted file mode 100644 index 9de3667041..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/top_threads/top_threads_table/top_threads_table.tsx +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo, useCallback, useEffect, useMemo, useState} from 'react'; -import {FormattedMessage} from 'react-intl'; -import {useDispatch, useSelector} from 'react-redux'; - -import classNames from 'classnames'; - -import Tag from 'components/widgets/tag/tag'; - -import {selectPostAndParentChannel} from 'actions/views/rhs'; -import {trackEvent} from 'actions/telemetry_actions'; -import {openModal} from 'actions/views/modals'; - -import {getMyTopThreads as fetchMyTopThreads, getTopThreadsForTeam} from 'mattermost-redux/actions/insights'; - -import {TimeFrame, TopThread} from '@mattermost/types/insights'; -import {UserProfile} from '@mattermost/types/users'; -import {GlobalState} from '@mattermost/types/store'; - -import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; -import {getTeammateNameDisplaySetting} from 'mattermost-redux/selectors/entities/preferences'; -import {getMyChannelMemberships} from 'mattermost-redux/selectors/entities/channels'; -import {getLicense} from 'mattermost-redux/selectors/entities/general'; - -import {displayUsername} from 'mattermost-redux/utils/user_utils'; - -import {InsightsScopes, ModalIdentifiers} from 'utils/constants'; -import {imageURLForUser} from 'utils/utils'; - -import Avatar from 'components/widgets/users/avatar'; -import Avatars from 'components/widgets/users/avatars'; -import Markdown from 'components/markdown'; -import Attachment from 'components/threading/global_threads/thread_item/attachments'; -import DataGrid, {Row, Column} from 'components/admin_console/data_grid/data_grid'; - -import JoinChannelModal from '../../join_channel_modal/join_channel_modal'; - -import './../../../activity_and_insights.scss'; -import '../top_threads.scss'; - -type Props = { - filterType: string; - timeFrame: TimeFrame; - closeModal: () => void; -} - -const TopThreadsTable = (props: Props) => { - const dispatch = useDispatch(); - - const [loading, setLoading] = useState(true); - const [topThreads, setTopThreads] = useState([] as TopThread[]); - - const currentTeamId = useSelector(getCurrentTeamId); - const teammateNameDisplaySetting = useSelector(getTeammateNameDisplaySetting); - const myChannelMemberships = useSelector(getMyChannelMemberships); - const complianceExportEnabled = useSelector((state: GlobalState) => state.entities.general.config.EnableComplianceExport); - const license = useSelector(getLicense); - - const getTopTeamThreads = useCallback(async () => { - if (props.filterType === InsightsScopes.TEAM) { - setLoading(true); - const data: any = await dispatch(getTopThreadsForTeam(currentTeamId, 0, 5, props.timeFrame)); - if (data.data && data.data.items) { - setTopThreads(data.data.items); - } - setLoading(false); - } - }, [props.timeFrame, currentTeamId, props.filterType]); - - useEffect(() => { - getTopTeamThreads(); - }, [getTopTeamThreads]); - - const getMyTopThreads = useCallback(async () => { - if (props.filterType === InsightsScopes.MY) { - setLoading(true); - const data: any = await dispatch(fetchMyTopThreads(currentTeamId, 0, 5, props.timeFrame)); - if (data.data && data.data.items) { - setTopThreads(data.data.items); - } - setLoading(false); - } - }, [props.timeFrame, props.filterType]); - - useEffect(() => { - getMyTopThreads(); - }, [getMyTopThreads]); - - const imageProps = useMemo(() => ({ - onImageHeightChanged: () => {}, - onImageLoaded: () => {}, - }), []); - - const closeModal = useCallback(() => { - props.closeModal(); - }, [props.closeModal]); - - const openRHSOrJoinChannel = useCallback((thread: TopThread, isChannelMember: boolean) => { - if (!isChannelMember && (license.Compliance === 'true' && complianceExportEnabled === 'true')) { - dispatch(openModal({ - modalId: ModalIdentifiers.INSIGHTS, - dialogType: JoinChannelModal, - dialogProps: { - thread, - currentTeamId, - }, - })); - } else { - trackEvent('insights', 'open_thread_from_top_threads_modal'); - dispatch(selectPostAndParentChannel(thread.post)); - } - closeModal(); - }, [currentTeamId]); - - const getPreview = useCallback((thread: TopThread, isChannelMember: boolean) => { - if (!isChannelMember && (license.Compliance === 'true' && complianceExportEnabled === 'true')) { - return ( - - {thread.channel_display_name}, - }} - /> - - ); - } - - if (thread.post.message) { - return ( - - ); - } - - return ( - - ); - }, []); - - const getColumns = useMemo((): Column[] => { - const columns: Column[] = [ - { - name: ( - - ), - field: 'rank', - className: 'rankCell', - width: 0.07, - }, - { - name: ( - - ), - field: 'thread', - width: 0.7, - }, - { - name: ( - - ), - field: 'participants', - width: 0.15, - }, - { - name: ( - - ), - field: 'replies', - width: 0.08, - }, - ]; - return columns; - }, []); - - const getRows = useMemo((): Row[] => { - return topThreads.map((thread, i) => { - const channelMembership = myChannelMemberships[thread.channel_id]; - let isChannelMember = false; - if (typeof channelMembership !== 'undefined') { - isChannelMember = true; - } - - return ( - { - cells: { - rank: ( - - {i + 1} - - ), - thread: ( -
-
- - {displayUsername(thread.user_information as UserProfile, teammateNameDisplaySetting)} - -
-
- {getPreview(thread, isChannelMember)} -
-
- ), - participants: ( - <> - {thread.participants && thread.participants.length > 0 ? ( - - ) : null} - - - ), - replies: ( - {thread.post.reply_count} - ), - }, - onClick: () => { - openRHSOrJoinChannel(thread, isChannelMember); - }, - } - ); - }); - }, [topThreads, myChannelMemberships]); - - return ( - {}} - previousPage={() => {}} - startCount={1} - endCount={10} - total={0} - className={classNames('InsightsTable', 'TopThreadsTable')} - /> - ); -}; - -export default memo(TopThreadsTable); diff --git a/webapp/channels/src/components/activity_and_insights/insights/widget_empty_state/widget_empty_state.tsx b/webapp/channels/src/components/activity_and_insights/insights/widget_empty_state/widget_empty_state.tsx deleted file mode 100644 index f2f2baa73c..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/widget_empty_state/widget_empty_state.tsx +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {memo} from 'react'; -import {FormattedMessage} from 'react-intl'; - -type Props = { - icon: string; -} - -const WidgetEmptyState = (props: Props) => { - return ( -
-
- -
-
- -
-
- ); -}; - -export default memo(WidgetEmptyState); diff --git a/webapp/channels/src/components/activity_and_insights/insights/widget_hoc/widget_hoc.test.tsx b/webapp/channels/src/components/activity_and_insights/insights/widget_hoc/widget_hoc.test.tsx deleted file mode 100644 index 2b43e5ff47..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/widget_hoc/widget_hoc.test.tsx +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {ComponentType} from 'react'; -import {Provider} from 'react-redux'; - -import {CardSizes, InsightsWidgetTypes, TimeFrames} from '@mattermost/types/insights'; - -import {renderWithIntl, screen} from 'tests/react_testing_utils'; -import mockStore from 'tests/test_store'; - -import {InsightsScopes} from 'utils/constants'; - -import widgetHoc from './widget_hoc'; - -describe('components/activity_and_insights/insights/widget_hoc', () => { - let TestComponent: ComponentType; - const store = mockStore({}); - - const props = { - size: CardSizes.small, - widgetType: InsightsWidgetTypes.TOP_REACTIONS, - filterType: InsightsScopes.MY, - class: 'top-reactions-card', - timeFrame: TimeFrames.INSIGHTS_7_DAYS, - timeFrameLabel: 'Last 7 days', - }; - - beforeEach(() => { - TestComponent = () =>
; - }); - - test('should display my top reactions', () => { - const EnhancedComponent = widgetHoc(TestComponent); - - renderWithIntl( - - - , - ); - - expect(screen.getByText('My top reactions')).toBeInTheDocument(); - expect(screen.getByText('Reactions I\'ve used the most')).toBeInTheDocument(); - }); - - test('should display team top reactions', () => { - const EnhancedComponent = widgetHoc(TestComponent); - - renderWithIntl( - - - , - ); - - expect(screen.getByText('Top reactions')).toBeInTheDocument(); - expect(screen.getByText('The team\'s most-used reactions')).toBeInTheDocument(); - }); -}); diff --git a/webapp/channels/src/components/activity_and_insights/insights/widget_hoc/widget_hoc.tsx b/webapp/channels/src/components/activity_and_insights/insights/widget_hoc/widget_hoc.tsx deleted file mode 100644 index 8b5454db81..0000000000 --- a/webapp/channels/src/components/activity_and_insights/insights/widget_hoc/widget_hoc.tsx +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {ComponentType, useCallback, useState} from 'react'; -import {useDispatch} from 'react-redux'; -import {useIntl} from 'react-intl'; - -import {openModal} from 'actions/views/modals'; - -import {trackEvent} from 'actions/telemetry_actions'; - -import {CardSize, InsightsWidgetTypes, TimeFrame} from '@mattermost/types/insights'; - -import InsightsCard from '../card/card'; -import InsightsModal from '../insights_modal/insights_modal'; - -import {InsightsScopes, InsightsCardTitles, ModalIdentifiers} from 'utils/constants'; -import {DispatchFunc} from 'mattermost-redux/types/actions'; - -export interface WidgetHocProps { - size: CardSize; - widgetType: InsightsWidgetTypes; - filterType: string; - class: string; - timeFrame: TimeFrame; -} - -function widgetHoc(WrappedComponent: ComponentType) { - const Component = (props: T & WidgetHocProps) => { - const {formatMessage} = useIntl(); - const dispatch = useDispatch(); - const [showModal, setShowModal] = useState(false); - - const title = useCallback(() => { - if (props.filterType === InsightsScopes.MY && Object.keys(InsightsCardTitles[props.widgetType].myTitle).length !== 0) { - return formatMessage(InsightsCardTitles[props.widgetType].myTitle); - } - if (props.filterType === InsightsScopes.TEAM && Object.keys(InsightsCardTitles[props.widgetType].teamTitle).length !== 0) { - return formatMessage(InsightsCardTitles[props.widgetType].teamTitle); - } - return ''; - }, [props.filterType, props.widgetType]); - - const subTitle = useCallback(() => { - if (props.filterType === InsightsScopes.MY && Object.keys(InsightsCardTitles[props.widgetType].mySubTitle).length !== 0) { - return formatMessage(InsightsCardTitles[props.widgetType].mySubTitle); - } - if (props.filterType === InsightsScopes.TEAM && Object.keys(InsightsCardTitles[props.widgetType].teamSubTitle).length !== 0) { - return formatMessage(InsightsCardTitles[props.widgetType].teamSubTitle); - } - return ''; - }, [props.filterType, props.widgetType]); - - const openInsightsModal = useCallback(() => { - trackEvent('insights', `open_modal_${props.widgetType.toLowerCase()}`); - setShowModal(true); - dispatch(openModal({ - modalId: ModalIdentifiers.INSIGHTS, - dialogType: InsightsModal, - dialogProps: { - widgetType: props.widgetType, - title: title(), - subtitle: subTitle(), - filterType: props.filterType, - timeFrame: props.timeFrame, - setShowModal, - }, - })); - }, [props.widgetType, title, subTitle, props.filterType, props.timeFrame]); - - return ( - - - - ); - }; - - return Component; -} - -export default widgetHoc; diff --git a/webapp/channels/src/components/channel_layout/center_channel/center_channel.test.tsx b/webapp/channels/src/components/channel_layout/center_channel/center_channel.test.tsx index e7d6386830..37d9bdf21e 100644 --- a/webapp/channels/src/components/channel_layout/center_channel/center_channel.test.tsx +++ b/webapp/channels/src/components/channel_layout/center_channel/center_channel.test.tsx @@ -23,7 +23,6 @@ describe('components/channel_layout/CenterChannel', () => { rhsMenuOpen: true, isCollapsedThreadsEnabled: true, currentUserId: 'testUserId', - insightsAreEnabled: true, isMobileView: false, actions: { getProfiles: jest.fn, diff --git a/webapp/channels/src/components/channel_layout/center_channel/center_channel.tsx b/webapp/channels/src/components/channel_layout/center_channel/center_channel.tsx index acc2c4d0b2..e42818c48a 100644 --- a/webapp/channels/src/components/channel_layout/center_channel/center_channel.tsx +++ b/webapp/channels/src/components/channel_layout/center_channel/center_channel.tsx @@ -38,16 +38,6 @@ const LazyDrafts = makeAsyncComponent( ), ); -const LazyActivityAndInsights = makeAsyncComponent( - 'LazyActivityAndInsights', - React.lazy(() => import('components/activity_and_insights/activity_and_insights')), - ( -
- -
- ), -); - type Props = PropsFromRedux & OwnProps; type State = { @@ -80,7 +70,7 @@ export default class CenterChannel extends React.PureComponent { } render() { - const {lastChannelPath, isCollapsedThreadsEnabled, insightsAreEnabled, isMobileView} = this.props; + const {lastChannelPath, isCollapsedThreadsEnabled, isMobileView} = this.props; const url = this.props.match.url; return ( @@ -129,12 +119,7 @@ export default class CenterChannel extends React.PureComponent { path='/:team/drafts' component={LazyDrafts} /> - {insightsAreEnabled ? ( - - ) : null} +
diff --git a/webapp/channels/src/components/channel_layout/center_channel/index.ts b/webapp/channels/src/components/channel_layout/center_channel/index.ts index 2fe94af351..a62531034f 100644 --- a/webapp/channels/src/components/channel_layout/center_channel/index.ts +++ b/webapp/channels/src/components/channel_layout/center_channel/index.ts @@ -9,7 +9,7 @@ import {ActionFunc, GenericAction} from 'mattermost-redux/types/actions'; import {getProfiles} from 'mattermost-redux/actions/users'; import {getTeamByName} from 'mattermost-redux/selectors/entities/teams'; import {getRedirectChannelNameForTeam} from 'mattermost-redux/selectors/entities/channels'; -import {isCollapsedThreadsEnabled, insightsAreEnabled} from 'mattermost-redux/selectors/entities/preferences'; +import {isCollapsedThreadsEnabled} from 'mattermost-redux/selectors/entities/preferences'; import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users'; import {getIsMobileView} from 'selectors/views/browser'; @@ -49,8 +49,6 @@ const mapStateToProps = (state: GlobalState, ownProps: OwnProps) => { let lastChannelPath; if (isCollapsedThreadsEnabled(state) && (previousTeamLastViewedType === PreviousViewedTypes.THREADS || lastViewedType === PreviousViewedTypes.THREADS)) { lastChannelPath = `${ownProps.match.url}/threads`; - } else if (insightsAreEnabled(state) && lastViewedType === PreviousViewedTypes.INSIGHTS) { - lastChannelPath = `${ownProps.match.url}/activity-and-insights`; } else { lastChannelPath = `${ownProps.match.url}/channels/${channelName}`; } @@ -62,7 +60,6 @@ const mapStateToProps = (state: GlobalState, ownProps: OwnProps) => { rhsMenuOpen: getIsRhsMenuOpen(state), isCollapsedThreadsEnabled: isCollapsedThreadsEnabled(state), currentUserId: getCurrentUserId(state), - insightsAreEnabled: insightsAreEnabled(state), isMobileView: getIsMobileView(state), }; }; diff --git a/webapp/channels/src/components/favicon_title_handler/favicon_title_handler.test.tsx b/webapp/channels/src/components/favicon_title_handler/favicon_title_handler.test.tsx index fe067542de..0d9072ff4a 100644 --- a/webapp/channels/src/components/favicon_title_handler/favicon_title_handler.test.tsx +++ b/webapp/channels/src/components/favicon_title_handler/favicon_title_handler.test.tsx @@ -46,7 +46,6 @@ describe('components/FaviconTitleHandler', () => { currentTeammate: null, inGlobalThreads: false, inDrafts: false, - inActivityAndInsights: false, }; test('set correctly the title when needed', () => { diff --git a/webapp/channels/src/components/favicon_title_handler/favicon_title_handler.tsx b/webapp/channels/src/components/favicon_title_handler/favicon_title_handler.tsx index 2957eea26a..38883a130c 100644 --- a/webapp/channels/src/components/favicon_title_handler/favicon_title_handler.tsx +++ b/webapp/channels/src/components/favicon_title_handler/favicon_title_handler.tsx @@ -48,7 +48,6 @@ type Props = { currentTeammate: Channel | null; inGlobalThreads: boolean; inDrafts: boolean; - inActivityAndInsights: boolean; }; export class FaviconTitleHandlerClass extends React.PureComponent { @@ -84,7 +83,6 @@ export class FaviconTitleHandlerClass extends React.PureComponent { unreadStatus, inGlobalThreads, inDrafts, - inActivityAndInsights, } = this.props; const {formatMessage} = this.props.intl; @@ -121,14 +119,6 @@ export class FaviconTitleHandlerClass extends React.PureComponent { displayName: currentTeam.display_name, siteName: currentSiteName, }); - } else if (currentTeam && inActivityAndInsights) { - document.title = formatMessage({ - id: 'activityAndInsights.title', - defaultMessage: 'Activity and Insights - {displayName} {siteName}', - }, { - displayName: currentTeam.display_name, - siteName: currentSiteName, - }); } else { document.title = formatMessage({id: 'sidebar.team_select', defaultMessage: '{siteName} - Join a team'}, {siteName: currentSiteName || 'Mattermost'}); } diff --git a/webapp/channels/src/components/favicon_title_handler/index.ts b/webapp/channels/src/components/favicon_title_handler/index.ts index e0287c9d7a..7a3f74c7ae 100644 --- a/webapp/channels/src/components/favicon_title_handler/index.ts +++ b/webapp/channels/src/components/favicon_title_handler/index.ts @@ -30,7 +30,6 @@ function mapStateToProps(state: GlobalState, {location: {pathname}}: Props): Com unreadStatus: getUnreadStatus(state), inGlobalThreads: matchPath(pathname, {path: '/:team/threads/:threadIdentifier?'}) != null, inDrafts: matchPath(pathname, {path: '/:team/drafts'}) != null, - inActivityAndInsights: matchPath(pathname, {path: '/:team/activity-and-insights'}) != null, }; } diff --git a/webapp/channels/src/components/leave_channel_modal/leave_channel_modal.tsx b/webapp/channels/src/components/leave_channel_modal/leave_channel_modal.tsx index dec0b92e8b..04a3fdd201 100644 --- a/webapp/channels/src/components/leave_channel_modal/leave_channel_modal.tsx +++ b/webapp/channels/src/components/leave_channel_modal/leave_channel_modal.tsx @@ -5,14 +5,13 @@ import React, {useState} from 'react'; import {FormattedMessage} from 'react-intl'; import {Channel} from '@mattermost/types/channels'; -import {LeastActiveChannel} from '@mattermost/types/insights'; import Constants from 'utils/constants'; import ConfirmModal from 'components/confirm_modal'; type Props = { - channel: Channel | LeastActiveChannel; + channel: Channel; onExited: () => void; callback?: () => any; actions: { diff --git a/webapp/channels/src/components/sidebar/sidebar_list/__snapshots__/sidebar_list.test.tsx.snap b/webapp/channels/src/components/sidebar/sidebar_list/__snapshots__/sidebar_list.test.tsx.snap index a3c08318c4..c50ea9f180 100644 --- a/webapp/channels/src/components/sidebar/sidebar_list/__snapshots__/sidebar_list.test.tsx.snap +++ b/webapp/channels/src/components/sidebar/sidebar_list/__snapshots__/sidebar_list.test.tsx.snap @@ -2,7 +2,6 @@ exports[`SidebarList should match snapshot 1`] = ` -
{ // NOTE: id attribute added to temporarily support the desktop app's at-mention DOM scraping of the old sidebar <> -
{ terms: expectedOrder, })); }); - - it('Should show insights as the first item in the list if search term matches', async () => { - const modifiedState = { - ...defaultState, - entities: { - ...defaultState.entities, - general: { - config: { - CollapsedThreads: 'default_off', - FeatureFlagInsightsEnabled: 'true', - InsightsEnabled: 'true', - }, - }, - threads: { - countsIncludingDirect: { - currentTeamId: { - total: 0, - total_unread_threads: 0, - total_unread_mentions: 0, - }, - }, - counts: { - currentTeamId: { - total: 0, - total_unread_threads: 0, - total_unread_mentions: 0, - }, - }, - }, - preferences: { - ...defaultState.entities.preferences, - myPreferences: { - ...defaultState.entities.preferences.myPreferences, - [`${Preferences.CATEGORY_DISPLAY_SETTINGS}--${Preferences.COLLAPSED_REPLY_THREADS}`]: { - value: 'on', - }, - }, - }, - channels: { - ...defaultState.entities.channels, - myMembers: { - current_channel_id: { - channel_id: 'current_channel_id', - user_id: 'current_user_id', - roles: 'channel_role', - mention_count: 1, - msg_count: 9, - }, - insight_gm_channel: { - channel_id: 'insight_gm_channel', - msg_count: 1, - last_viewed_at: 3, - }, - thread_user1: {}, - }, - channels: { - insight_gm_channel: { - id: 'insight_gm_channel', - msg_count: 1, - last_viewed_at: 3, - type: 'G', - name: 'insight_gm_channel', - delete_at: 0, - display_name: 'insight_gm_channel', - }, - }, - channelsInTeam: { - '': ['insight_gm_channel'], - }, - }, - }, - }; - - getState.mockClear(); - - const switchProvider = new SwitchChannelProvider(); - const store = mockStore(modifiedState); - - getState.mockImplementation(store.getState); - const searchText = 'insight'; - const resultsCallback = jest.fn(); - - switchProvider.startNewRequest(searchText); - await switchProvider.fetchUsersAndChannels(searchText, resultsCallback); - const expectedOrder = [ - 'insights', - 'insight_gm_channel', - ]; - - expect(resultsCallback).toBeCalledWith(expect.objectContaining({ - terms: expectedOrder, - })); - }); }); diff --git a/webapp/channels/src/components/suggestion/switch_channel_provider.tsx b/webapp/channels/src/components/suggestion/switch_channel_provider.tsx index 070a843dcf..d8aa290ae6 100644 --- a/webapp/channels/src/components/suggestion/switch_channel_provider.tsx +++ b/webapp/channels/src/components/suggestion/switch_channel_provider.tsx @@ -28,7 +28,7 @@ import { getAllTeamsUnreadChannelIds, } from 'mattermost-redux/selectors/entities/channels'; -import {getMyPreferences, isGroupChannelManuallyVisible, isCollapsedThreadsEnabled, insightsAreEnabled} from 'mattermost-redux/selectors/entities/preferences'; +import {getMyPreferences, isGroupChannelManuallyVisible, isCollapsedThreadsEnabled} from 'mattermost-redux/selectors/entities/preferences'; import {getConfig} from 'mattermost-redux/selectors/entities/general'; import { getCurrentTeamId, @@ -73,14 +73,6 @@ const ThreadsChannel: FakeChannel = { delete_at: 0, }; -const InsightsChannel: FakeChannel = { - id: 'insights', - name: 'activity-and-insights', - display_name: 'Insights', - type: Constants.INSIGHTS, - delete_at: 0, -}; - type FakeChannel = Pick & { type: string; } @@ -183,12 +175,6 @@ const SwitchChannelSuggestion = React.forwardRef((props, ); - } else if (channel.type === Constants.INSIGHTS) { - icon = ( - - - - ); } else if (channel.type === Constants.GM_CHANNEL) { icon = ( @@ -457,7 +443,7 @@ export default class SwitchChannelProvider extends Provider { // Dispatch suggestions for local data (filter out deleted and archived channels from local store data) const channels = getChannelsInAllTeams(getState()).concat(getDirectAndGroupChannels(getState())).filter((c) => c.delete_at === 0); const users = searchProfilesMatchingWithTerm(getState(), channelPrefix, false); - const formattedData = this.formatList(channelPrefix, [ThreadsChannel, InsightsChannel, ...channels], users, true, true); + const formattedData = this.formatList(channelPrefix, [ThreadsChannel, ...channels], users, true, true); if (formattedData) { resultsCallback(formattedData); } @@ -510,7 +496,7 @@ export default class SwitchChannelProvider extends Provider { // filter out deleted and archived channels from local store data const localChannelData = getChannelsInAllTeams(state).concat(getDirectAndGroupChannels(state)).filter((c) => c.delete_at === 0) || []; const localUserData = searchProfilesMatchingWithTerm(state, channelPrefix, false); - const localFormattedData = this.formatList(channelPrefix, [ThreadsChannel, InsightsChannel, ...localChannelData], localUserData); + const localFormattedData = this.formatList(channelPrefix, [ThreadsChannel, ...localChannelData], localUserData); const remoteChannelData = channelsFromServer.concat(getGroupChannels(state)) || []; const remoteUserData = usersFromServer.users || []; const remoteFormattedData = this.formatList(channelPrefix, remoteChannelData, remoteUserData, false); @@ -593,7 +579,7 @@ export default class SwitchChannelProvider extends Provider { let wrappedChannel: WrappedChannel = {channel: newChannel, name: newChannel.name, deactivated: false}; if (members[channel.id]) { wrappedChannel.last_viewed_at = members[channel.id].last_viewed_at; - } else if (skipNotMember && (newChannel.type !== Constants.THREADS && newChannel.type !== Constants.INSIGHTS)) { + } else if (skipNotMember && (newChannel.type !== Constants.THREADS)) { continue; } @@ -614,13 +600,6 @@ export default class SwitchChannelProvider extends Provider { } else { continue; } - } else if (newChannel.type === Constants.INSIGHTS) { - const insightsItem = this.getInsightsItem(); - if (insightsItem) { - wrappedChannel = insightsItem; - } else { - continue; - } } else if (newChannel.type === Constants.GM_CHANNEL) { newChannel.name = newChannel.display_name; wrappedChannel.name = newChannel.name; @@ -767,27 +746,6 @@ export default class SwitchChannelProvider extends Provider { return null; } - getInsightsItem() { - const state = getState(); - const insightsEnabled = insightsAreEnabled(state); - - // adding last viewed at equal to Date.now() to push it to the top of the list - const insightsItem = { - channel: InsightsChannel, - name: InsightsChannel.name, - unread: false, - unread_mentions: 0, - deactivated: false, - last_viewed_at: Date.now(), - }; - - if (insightsEnabled) { - return insightsItem; - } - - return null; - } - getTimestampFromPrefs(myPreferences: Record, category: string, name: string) { const pref = myPreferences[getPreferenceKey(category, name)]; const prefValue = pref ? pref.value : '0'; diff --git a/webapp/channels/src/i18n/en.json b/webapp/channels/src/i18n/en.json index e4e75213af..db067ec0e7 100644 --- a/webapp/channels/src/i18n/en.json +++ b/webapp/channels/src/i18n/en.json @@ -66,12 +66,6 @@ "activity_log.os": "OS: {os}", "activity_log.sessionId": "Session ID: {id}", "activity_log.sessionsDescription": "Sessions are created when you log in through a new browser on a device. Sessions let you use Mattermost without having to log in again for a time period specified by the System Admin. To end the session sooner, use the 'Log Out' button.", - "activityAndInsights.sidebarLink": "Insights", - "activityAndInsights.title": "Activity and Insights - {displayName} {siteName}", - "activityAndInsights.tutorial_tip.notNow": "Not now", - "activityAndInsights.tutorial_tip.viewInsights": "View insights", - "activityAndInsights.tutorialTip.description": "Check out the new Insights feature added to your workspace. See what content is most active, and learn how you and your teammates are using your workspace.", - "activityAndInsights.tutorialTip.title": "Introducing: Insights", "add_channels_to_scheme.title": "Add Channels to **Channel Selection** List", "add_command.autocomplete": "Autocomplete", "add_command.autocomplete.help": "(Optional) Show your slash command on the autocomplete list when someone types / in the input box.", @@ -2617,7 +2611,6 @@ "analytics.system.skippedIntensiveQueries": "To maximize performance, some statistics are disabled. You can re-enable them in config.json.", "analytics.system.textPosts": "Posts with Text-only", "analytics.system.title": "System Statistics", - "analytics.system.topChannels": "Top Channels", "analytics.system.totalBotPosts": "Total Posts from Bots", "analytics.system.totalChannels": "Total Channels", "analytics.system.totalCommands": "Total Commands", @@ -3652,92 +3645,6 @@ "incoming_webhooks.header": "Incoming Webhooks", "inProduct_notices.adminOnlyMessage": "Visible to Admins only", "input.clear": "Clear", - "insights.accessModal.cloudFreeTrial": "During your trial you are able to view Team Insights.", - "insights.accessModal.contactSales": "contact our Sales team", - "insights.accessModal.docsLink": "Insights", - "insights.accessModal.messageAdminPostTrial": "To access your complete {insightsDoc} dashboard, including {teamInsights}, please upgrade your plan to Professional or Enterprise. For questions on upgrading your plan, please {contactSales} for support.", - "insights.accessModal.messageAdminPreTrial": "Use {teamInsights} with one of our paid plans. Get the full experience of Enterprise when you start a free, {trialLength} day trial.", - "insights.accessModal.messageEndUser": "To access your complete {insightsDoc} dashboard, including {teamInsights}, please notify your Admin to upgrade your plan to Professional or Enterprise. For questions on upgrading your plan, please {contactSales} for support.", - "insights.accessModal.teamDocsLink": "Team Insights", - "insights.accessModal.titleAdminPostTrial": "Upgrade to access team insights", - "insights.accessModal.titleAdminPreTrial": "Try team insights with a free trial", - "insights.accessModal.titleEndUser": "Team insights are available in paid plans", - "insights.card.ariaLabel": "Open modal for {title}", - "insights.filter.ariaLabel": "Insights Filter Menu", - "insights.filter.myInsights": "My insights", - "insights.filter.teamInsights": "Team insights", - "insights.leastActiveChannels.channel": "Channel", - "insights.leastActiveChannels.copyLink": "Copy link", - "insights.leastActiveChannels.lastActivity": "Last activity: {time}", - "insights.leastActiveChannels.lastActivityCell": "Last activity", - "insights.leastActiveChannels.lastActivityNone": "No activity", - "insights.leastActiveChannels.leaveChannel": "Leave channel", - "insights.leastActiveChannels.members": "Members", - "insights.leastActiveChannels.menuAriaLabel": "Manage channel menu", - "insights.leastActiveChannels.menuButtonAriaLabel": "Open manage channel menu", - "insights.leastActiveChannels.mySubTitle": "My channels with the least posts", - "insights.leastActiveChannels.myTitle": "My least active channels", - "insights.leastActiveChannels.subTitle": "Channels with the least posts", - "insights.leastActiveChannels.title": "Least active channels", - "insights.myHeading": "My insights", - "insights.newMembers.joined": "Date joined", - "insights.newMembers.lastSevenDays": "Joined the team in the last 7 days", - "insights.newMembers.lastTwentyEightDays": "Joined the team in the last 28 days", - "insights.newMembers.member": "Team member", - "insights.newMembers.position": "Position", - "insights.newMembers.sayHello": "Say hello", - "insights.newMembers.seeAll": "See all", - "insights.newMembers.today": "Joined the team today", - "insights.newTeamMembers.title": "New team members", - "insights.teamHeading": "Team insights", - "insights.timeFrame.longRange": "Last 28 days", - "insights.timeFrame.mediumRange": "Last 7 days", - "insights.timeFrame.today": "Today", - "insights.topBoards.mySubTitle": "Most active boards I've participated in", - "insights.topBoards.myTitle": "My top boards", - "insights.topBoards.subTitle": "Most active boards for the team", - "insights.topBoards.title": "Top boards", - "insights.topBoards.updates": "{updateCount} updates", - "insights.topBoardsTable.board": "Board", - "insights.topBoardsTable.participants": "Participants", - "insights.topBoardsTable.updates": "Updates", - "insights.topChannels.channel": "Channel", - "insights.topChannels.messageCount": "{messageCount} total messages", - "insights.topChannels.mySubTitle": "Most active channels that I'm a member of", - "insights.topChannels.myTitle": "My top channels", - "insights.topChannels.subTitle": "Most active channels for the team", - "insights.topChannels.title": "Top channels", - "insights.topChannels.totalMessages": "Total messages", - "insights.topDMs.myTitle": "My most active direct messages", - "insights.topDMs.receivedMessages": "Received", - "insights.topDMs.sentMessages": "Sent", - "insights.topDMs.totalMessages": "Total messages", - "insights.topDMs.user": "User", - "insights.topPlaybooks.lastRun": "Last run: {relativeTime}", - "insights.topPlaybooks.mySubTitle": "Playbooks I've used with the most runs", - "insights.topPlaybooks.myTitle": "My top playbooks", - "insights.topPlaybooks.subTitle": "Playbooks with the most runs", - "insights.topPlaybooks.title": "Top playbooks", - "insights.topPlaybooks.totalRuns": "{total} runs", - "insights.topPlaybooksTable.participants": "Total runs", - "insights.topPlaybooksTable.playbook": "Playbook", - "insights.topPlaybooksTable.updates": "Last run", - "insights.topReactions.empty": "Not enough data yet for this insight", - "insights.topReactions.mySubTitle": "Reactions I've used the most", - "insights.topReactions.myTitle": "My top reactions", - "insights.topReactions.rank": "Rank", - "insights.topReactions.reaction": "Reaction", - "insights.topReactions.subTitle": "The team's most-used reactions", - "insights.topReactions.timesUsed": "Times used", - "insights.topReactions.title": "Top reactions", - "insights.topThreadItem.notChannelMember": "You'll need to join the {channel} channel to see this thread.", - "insights.topThreads.mySubTitle": "Most active threads I've followed", - "insights.topThreads.myTitle": "My top threads", - "insights.topThreads.replies": "Replies", - "insights.topThreads.subTitle": "Most active threads for the team", - "insights.topThreads.thread": "Thread", - "insights.topThreads.title": "Top threads", - "insights.topThreads.totalMessages": "Participants", "installed_command.header": "Slash Commands", "installed_commands.add": "Add Slash Command", "installed_commands.delete.confirm": "This action permanently deletes the slash command and breaks any integrations using it. Are you sure you want to delete it?", @@ -3911,11 +3818,8 @@ "invite.rate-limit-exceeded": "Invite emails rate limit exceeded.", "join_team_group_constrained_denied": "You need to be a member of a linked group to join this team.", "join_team_group_constrained_denied_admin": "You need to be a member of a linked group to join this team. You can add a group to this team [here]({siteURL}/admin_console/user_management/groups).", - "joinChannel.cancelButton": "Cancel", - "joinChannel.desciption": "You'll need to join the {channel} channel to see this thread. Do you want to join {channel} now?", "joinChannel.JoinButton": "Join", "joinChannel.joiningButton": "Joining...", - "joinChannel.title": "Join channel?", "katex.error": "Couldn't compile your Latex code. Please review the syntax and try again.", "last_users_message.added_to_channel.type": "were **added to the channel** by {actor}.", "last_users_message.added_to_team.type": "were **added to the team** by {actor}.", diff --git a/webapp/channels/src/packages/mattermost-redux/src/action_types/index.ts b/webapp/channels/src/packages/mattermost-redux/src/action_types/index.ts index cf7d754232..d02794dcc1 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/action_types/index.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/action_types/index.ts @@ -25,7 +25,6 @@ import ChannelCategoryTypes from './channel_categories'; import CloudTypes from './cloud'; import AppsTypes from './apps'; import ThreadTypes from './threads'; -import InsightTypes from './insights'; import HostedCustomerTypes from './hosted_customer'; import PlaybookType from './playbooks'; @@ -53,7 +52,6 @@ export { CloudTypes, AppsTypes, ThreadTypes, - InsightTypes, HostedCustomerTypes, DraftTypes, PlaybookType, diff --git a/webapp/channels/src/packages/mattermost-redux/src/action_types/insights.ts b/webapp/channels/src/packages/mattermost-redux/src/action_types/insights.ts deleted file mode 100644 index 96398d24ac..0000000000 --- a/webapp/channels/src/packages/mattermost-redux/src/action_types/insights.ts +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import keyMirror from 'mattermost-redux/utils/key_mirror'; - -export default keyMirror({ - RECEIVED_TOP_REACTIONS: null, - RECEIVED_MY_TOP_REACTIONS: null, - - RECEIVED_TOP_THREADS: null, - RECEIVED_MY_TOP_THREADS: null, -}); diff --git a/webapp/channels/src/packages/mattermost-redux/src/actions/index.ts b/webapp/channels/src/packages/mattermost-redux/src/actions/index.ts index 2d6b86dfc0..a9481f06ab 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/actions/index.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/actions/index.ts @@ -23,7 +23,6 @@ import * as teams from './teams'; import * as timezone from './timezone'; import * as websocket from './websocket'; import * as users from './users'; -import * as insights from './insights'; export { admin, @@ -48,6 +47,5 @@ export { timezone, websocket, users, - insights, }; diff --git a/webapp/channels/src/packages/mattermost-redux/src/actions/insights.ts b/webapp/channels/src/packages/mattermost-redux/src/actions/insights.ts deleted file mode 100644 index b3988f4966..0000000000 --- a/webapp/channels/src/packages/mattermost-redux/src/actions/insights.ts +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import {InsightTypes} from 'mattermost-redux/action_types'; -import {GetStateFunc, DispatchFunc, ActionFunc} from 'mattermost-redux/types/actions'; -import {Client4} from 'mattermost-redux/client'; -import {LeastActiveChannelsActionResult, LeastActiveChannelsResponse, TimeFrame, TopChannelActionResult, TopChannelResponse, TopThreadActionResult, TopThreadResponse, TopDMsActionResult, TopDMsResponse} from '@mattermost/types/insights'; - -import {forceLogoutIfNecessary} from './helpers'; -import {logError} from './errors'; - -export function getTopReactionsForTeam(teamId: string, page: number, perPage: number, timeFrame: TimeFrame): ActionFunc { - return async (dispatch: DispatchFunc, getState: GetStateFunc) => { - let data; - try { - data = await Client4.getTopReactionsForTeam(teamId, page, perPage, timeFrame); - } catch (error) { - forceLogoutIfNecessary(error, dispatch, getState); - dispatch(logError(error)); - return {error}; - } - - dispatch({ - type: InsightTypes.RECEIVED_TOP_REACTIONS, - data: {data, timeFrame}, - id: teamId, - }); - - return {data}; - }; -} - -export function getMyTopReactions(teamId: string, page: number, perPage: number, timeFrame: TimeFrame): ActionFunc { - return async (dispatch: DispatchFunc, getState: GetStateFunc) => { - let data; - try { - data = await Client4.getMyTopReactions(teamId, page, perPage, timeFrame); - } catch (error) { - forceLogoutIfNecessary(error, dispatch, getState); - dispatch(logError(error)); - return {error}; - } - - dispatch({ - type: InsightTypes.RECEIVED_MY_TOP_REACTIONS, - data: {data, timeFrame}, - id: teamId, - }); - - return {data}; - }; -} - -export function getTopChannelsForTeam(teamId: string, page: number, perPage: number, timeFrame: TimeFrame): (dispatch: DispatchFunc, getState: GetStateFunc) => Promise | TopChannelActionResult { - return async (dispatch: DispatchFunc, getState: GetStateFunc) => { - let data: TopChannelResponse; - try { - data = await Client4.getTopChannelsForTeam(teamId, page, perPage, timeFrame); - } catch (error) { - forceLogoutIfNecessary(error, dispatch, getState); - dispatch(logError(error)); - return {error}; - } - - return {data}; - }; -} - -export function getMyTopChannels(teamId: string, page: number, perPage: number, timeFrame: TimeFrame): (dispatch: DispatchFunc, getState: GetStateFunc) => Promise | TopChannelActionResult { - return async (dispatch: DispatchFunc, getState: GetStateFunc) => { - let data: TopChannelResponse; - try { - data = await Client4.getMyTopChannels(teamId, page, perPage, timeFrame); - } catch (error) { - forceLogoutIfNecessary(error, dispatch, getState); - dispatch(logError(error)); - return {error}; - } - - return {data}; - }; -} - -export function getTopThreadsForTeam(teamId: string, page: number, perPage: number, timeFrame: TimeFrame): (dispatch: DispatchFunc, getState: GetStateFunc) => Promise | TopThreadActionResult { - return async (dispatch: DispatchFunc, getState: GetStateFunc) => { - let data: TopThreadResponse; - try { - data = await Client4.getTopThreadsForTeam(teamId, page, perPage, timeFrame); - } catch (error) { - forceLogoutIfNecessary(error, dispatch, getState); - dispatch(logError(error)); - return {error}; - } - - dispatch({ - type: InsightTypes.RECEIVED_TOP_THREADS, - data, - }); - - return {data}; - }; -} - -export function getMyTopThreads(teamId: string, page: number, perPage: number, timeFrame: TimeFrame): (dispatch: DispatchFunc, getState: GetStateFunc) => Promise | TopThreadActionResult { - return async (dispatch: DispatchFunc, getState: GetStateFunc) => { - let data: TopThreadResponse; - try { - data = await Client4.getMyTopThreads(teamId, page, perPage, timeFrame); - } catch (error) { - forceLogoutIfNecessary(error, dispatch, getState); - dispatch(logError(error)); - return {error}; - } - - dispatch({ - type: InsightTypes.RECEIVED_MY_TOP_THREADS, - data, - }); - - return {data}; - }; -} - -export function getLeastActiveChannelsForTeam(teamId: string, page: number, perPage: number, timeFrame: TimeFrame): (dispatch: DispatchFunc, getState: GetStateFunc) => Promise | LeastActiveChannelsActionResult { - return async (dispatch: DispatchFunc, getState: GetStateFunc) => { - let data: LeastActiveChannelsResponse; - try { - data = await Client4.getLeastActiveChannelsForTeam(teamId, page, perPage, timeFrame); - } catch (error) { - forceLogoutIfNecessary(error, dispatch, getState); - dispatch(logError(error)); - return {error}; - } - - return {data}; - }; -} -export function getMyTopDMs(teamId: string, page: number, perPage: number, timeFrame: TimeFrame): (dispatch: DispatchFunc, getState: GetStateFunc) => Promise | TopDMsActionResult { - return async (dispatch: DispatchFunc, getState: GetStateFunc) => { - let data: TopDMsResponse; - try { - data = await Client4.getMyTopDMs(teamId, page, perPage, timeFrame); - } catch (error) { - forceLogoutIfNecessary(error, dispatch, getState); - dispatch(logError(error)); - return {error}; - } - - return {data}; - }; -} - -export function getMyLeastActiveChannels(teamId: string, page: number, perPage: number, timeFrame: TimeFrame): (dispatch: DispatchFunc, getState: GetStateFunc) => Promise | LeastActiveChannelsActionResult { - return async (dispatch: DispatchFunc, getState: GetStateFunc) => { - let data: LeastActiveChannelsResponse; - try { - data = await Client4.getMyLeastActiveChannels(teamId, page, perPage, timeFrame); - } catch (error) { - forceLogoutIfNecessary(error, dispatch, getState); - dispatch(logError(error)); - return {error}; - } - - return {data}; - }; -} -export function getNewTeamMembers(teamId: string, page: number, perPage: number, timeFrame: TimeFrame): (dispatch: DispatchFunc, getState: GetStateFunc) => Promise | TopDMsActionResult { - return async (dispatch: DispatchFunc, getState: GetStateFunc) => { - let data: TopDMsResponse; - try { - data = await Client4.getNewTeamMembers(teamId, page, perPage, timeFrame); - } catch (error) { - forceLogoutIfNecessary(error, dispatch, getState); - dispatch(logError(error)); - return {error}; - } - - return {data}; - }; -} diff --git a/webapp/channels/src/packages/mattermost-redux/src/actions/preferences.ts b/webapp/channels/src/packages/mattermost-redux/src/actions/preferences.ts index 8e61d36acf..119ae2fbe1 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/actions/preferences.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/actions/preferences.ts @@ -121,20 +121,6 @@ export function setActionsMenuInitialisationState(initializationState: Record) { - return async (dispatch: DispatchFunc, getState: GetStateFunc) => { - const state = getState(); - const currentUserId = getCurrentUserId(state); - const preference: PreferenceType = { - user_id: currentUserId, - category: Preferences.CATEGORY_INSIGHTS, - name: Preferences.NAME_INSIGHTS_TUTORIAL_STATE, - value: JSON.stringify(initializationState), - }; - await dispatch(savePreferences(currentUserId, [preference])); - }; -} - export function setCustomStatusInitialisationState(initializationState: Record) { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { const state = getState(); diff --git a/webapp/channels/src/packages/mattermost-redux/src/constants/preferences.ts b/webapp/channels/src/packages/mattermost-redux/src/constants/preferences.ts index b403b90af2..423c6625a9 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/constants/preferences.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/constants/preferences.ts @@ -67,10 +67,6 @@ const Preferences = { UNREAD_SCROLL_POSITION_START_FROM_LEFT: 'start_from_left_off', UNREAD_SCROLL_POSITION_START_FROM_NEWEST: 'start_from_newest', - CATEGORY_INSIGHTS: 'insights', - NAME_INSIGHTS_TUTORIAL_STATE: 'insights_tutorial_state', - INSIGHTS_VIEWED: 'insights_modal_viewed', - CATEGORY_UPGRADE_CLOUD: 'upgrade_cloud', SYSTEM_CONSOLE_LIMIT_REACHED: 'system_console_limit_reached', diff --git a/webapp/channels/src/packages/mattermost-redux/src/reducers/entities/files.ts b/webapp/channels/src/packages/mattermost-redux/src/reducers/entities/files.ts index d1c771a11d..7032759471 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/reducers/entities/files.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/reducers/entities/files.ts @@ -3,11 +3,10 @@ import {combineReducers} from 'redux'; -import {FileTypes, InsightTypes, PostTypes, UserTypes} from 'mattermost-redux/action_types'; +import {FileTypes, PostTypes, UserTypes} from 'mattermost-redux/action_types'; import {GenericAction} from 'mattermost-redux/types/actions'; import {Post} from '@mattermost/types/posts'; import {FileInfo, FileSearchResultItem} from '@mattermost/types/files'; -import {TopThread} from '@mattermost/types/insights'; export function files(state: Record = {}, action: GenericAction) { switch (action.type) { @@ -30,15 +29,6 @@ export function files(state: Record = {}, action: GenericActio return storeAllFilesForPost(storeFilesForPost, state, post); } - case InsightTypes.RECEIVED_TOP_THREADS: - case InsightTypes.RECEIVED_MY_TOP_THREADS: { - const threads: TopThread[] = Object.values(action.data.items); - - return threads.reduce((nextState, thread) => { - return storeAllFilesForPost(storeFilesForPost, nextState, thread.post); - }, state); - } - case PostTypes.RECEIVED_POSTS: { const posts: Post[] = Object.values(action.data.posts); diff --git a/webapp/channels/src/packages/mattermost-redux/src/reducers/entities/index.ts b/webapp/channels/src/packages/mattermost-redux/src/reducers/entities/index.ts index d57ebae2bf..c03ffd864a 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/reducers/entities/index.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/reducers/entities/index.ts @@ -27,7 +27,6 @@ import cloud from './cloud'; import hostedCustomer from './hosted_customer'; import usage from './usage'; import threads from './threads'; -import insights from './insights'; export default combineReducers({ general, @@ -52,7 +51,6 @@ export default combineReducers({ channelCategories, apps, cloud, - insights, usage, hostedCustomer, }); diff --git a/webapp/channels/src/packages/mattermost-redux/src/reducers/entities/insights.ts b/webapp/channels/src/packages/mattermost-redux/src/reducers/entities/insights.ts deleted file mode 100644 index a0d78e038d..0000000000 --- a/webapp/channels/src/packages/mattermost-redux/src/reducers/entities/insights.ts +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import {combineReducers} from 'redux'; - -import {InsightTypes} from 'mattermost-redux/action_types'; -import {GenericAction} from 'mattermost-redux/types/actions'; -import {TopReaction, TimeFrame} from '@mattermost/types/insights'; - -const sortReactionsIntoState = (data: TopReaction[]) => { - const newItems: Record = {}; - - for (let i = 0; i < data.length; i++) { - const emojiObj = data[i]; - newItems[emojiObj.emoji_name] = emojiObj; - } - - return newItems; -}; - -function topReactions(state: Record>> = {}, action: GenericAction) { - switch (action.type) { - case InsightTypes.RECEIVED_TOP_REACTIONS: { - const results = action.data.data.items || []; - const timeFrame = action.data.timeFrame as TimeFrame; - - const newItems = sortReactionsIntoState(results); - - return { - ...state, - [action.id]: { - ...(state[action.id] || {}), - [timeFrame]: newItems, - }, - }; - } - default: - return state; - } -} - -function myTopReactions(state: Record>> = {}, action: GenericAction) { - switch (action.type) { - case InsightTypes.RECEIVED_MY_TOP_REACTIONS: { - const results = action.data.data.items || []; - const timeFrame = action.data.timeFrame as TimeFrame; - - const newItems = sortReactionsIntoState(results); - - return { - ...state, - [action.id]: { - ...(state[action.id] || {}), - [timeFrame]: newItems, - }, - }; - } - default: - return state; - } -} - -export default combineReducers({ - - // Object where every key is the team id, another nested object where the key is TimeFrame and that TimeFrame key has an object of reactions where the key is the emoji_name - topReactions, - - myTopReactions, -}); diff --git a/webapp/channels/src/packages/mattermost-redux/src/reducers/entities/posts.ts b/webapp/channels/src/packages/mattermost-redux/src/reducers/entities/posts.ts index e42c212c45..b4e4df5082 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/reducers/entities/posts.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/reducers/entities/posts.ts @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {ChannelTypes, GeneralTypes, PostTypes, UserTypes, ThreadTypes, InsightTypes, CloudTypes} from 'mattermost-redux/action_types'; +import {ChannelTypes, GeneralTypes, PostTypes, UserTypes, ThreadTypes, CloudTypes} from 'mattermost-redux/action_types'; import {comparePosts, isPermalink, shouldUpdatePost} from 'mattermost-redux/utils/post_utils'; import {Posts} from 'mattermost-redux/constants'; @@ -27,8 +27,6 @@ import { RelationOneToMany, } from '@mattermost/types/utilities'; -import {TopThread} from '@mattermost/types/insights'; - export function removeUnneededMetadata(post: Post) { if (!post.metadata) { return post; @@ -307,23 +305,6 @@ export function handlePosts(state: RelationOneToOne = {}, action: Ge }; } - case InsightTypes.RECEIVED_TOP_THREADS: - case InsightTypes.RECEIVED_MY_TOP_THREADS: { - const topThreads = Object.values(action.data.items) as TopThread[]; - - if (topThreads.length === 0) { - return state; - } - - const nextState = {...state}; - - for (const thread of topThreads) { - handlePostReceived(nextState, thread.post); - } - - return nextState; - } - case UserTypes.LOGOUT_SUCCESS: return {}; default: diff --git a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/insights.ts b/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/insights.ts deleted file mode 100644 index 446f416549..0000000000 --- a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/insights.ts +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import {createSelector} from 'mattermost-redux/selectors/create_selector'; - -import {GlobalState} from '@mattermost/types/store'; -import {TimeFrame, TimeFrames, TopReaction} from '@mattermost/types/insights'; - -import {getCurrentTeamId} from './teams'; - -function sortTopReactions(reactions: TopReaction[] = []): TopReaction[] { - return reactions.sort((a, b) => { - return b.count - a.count; - }); -} - -export function getTeamReactions(state: GlobalState) { - return state.entities.insights.topReactions; -} - -export const getReactionTimeFramesForCurrentTeam: (state: GlobalState) => Record> = createSelector( - 'getReactionTimeFramesForCurrentTeam', - getCurrentTeamId, - getTeamReactions, - (currentTeamId, reactions) => { - return reactions[currentTeamId]; - }, -); - -export const getTopReactionsForCurrentTeam: (state: GlobalState, timeFrame: TimeFrame, maxResults?: number) => TopReaction[] = createSelector( - 'getTopReactionsForCurrentTeam', - getReactionTimeFramesForCurrentTeam, - (state: GlobalState, timeFrame: TimeFrames) => timeFrame, - (state: GlobalState, timeFrame: TimeFrames, maxResults = 5) => maxResults, - (reactions, timeFrame, maxResults) => { - if (reactions && reactions[timeFrame]) { - const reactionArr = Object.values(reactions[timeFrame]); - sortTopReactions(reactionArr); - - return reactionArr.slice(0, maxResults); - } - return []; - }, -); - -export function getMyTopReactions(state: GlobalState) { - return state.entities.insights.myTopReactions; -} - -export const getMyReactionTimeFramesForCurrentTeam: (state: GlobalState) => Record> = createSelector( - 'getMyReactionTimeFramesForCurrentTeam', - getCurrentTeamId, - getMyTopReactions, - (currentTeamId, reactions) => { - return reactions[currentTeamId]; - }, -); - -export const getMyTopReactionsForCurrentTeam: (state: GlobalState, timeFrame: TimeFrame, maxResults?: number) => TopReaction[] = createSelector( - 'getMyTopReactionsForCurrentTeam', - getMyReactionTimeFramesForCurrentTeam, - (state: GlobalState, timeFrame: TimeFrames) => timeFrame, - (state: GlobalState, timeFrame: TimeFrames, maxResults = 5) => maxResults, - (reactions, timeFrame, maxResults) => { - if (reactions && reactions[timeFrame]) { - const reactionArr = Object.values(reactions[timeFrame]); - sortTopReactions(reactionArr); - - return reactionArr.slice(0, maxResults); - } - return []; - }, -); diff --git a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/preferences.ts b/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/preferences.ts index c09d4f60f0..1dc6b8f9f2 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/preferences.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/preferences.ts @@ -5,8 +5,6 @@ import {General, Preferences} from 'mattermost-redux/constants'; import {createSelector} from 'mattermost-redux/selectors/create_selector'; import {getConfig, getFeatureFlagValue, getLicense} from 'mattermost-redux/selectors/entities/general'; -import {getCurrentUser} from 'mattermost-redux/selectors/entities/users'; -import {isGuest} from 'mattermost-redux/utils/user_utils'; import {PreferenceType} from '@mattermost/types/preferences'; import {GlobalState} from '@mattermost/types/store'; @@ -248,13 +246,6 @@ export function getIsOnboardingFlowEnabled(state: GlobalState): boolean { return getConfig(state).EnableOnboardingFlow === 'true'; } -export function insightsAreEnabled(state: GlobalState): boolean { - const isConfiguredForFeature = getConfig(state).InsightsEnabled === 'true'; - const featureIsEnabled = getFeatureFlagValue(state, 'InsightsEnabled') === 'true'; - const currentUserIsGuest = isGuest(getCurrentUser(state).roles); - return featureIsEnabled && isConfiguredForFeature && !currentUserIsGuest; -} - export function isGraphQLEnabled(state: GlobalState): boolean { return getFeatureFlagValue(state, 'GraphQL') === 'true'; } diff --git a/webapp/channels/src/packages/mattermost-redux/src/store/initial_state.ts b/webapp/channels/src/packages/mattermost-redux/src/store/initial_state.ts index 8c27c13f41..60a1adfb7f 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/store/initial_state.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/store/initial_state.ts @@ -253,10 +253,6 @@ const state: GlobalState = { teamsLoaded: false, }, }, - insights: { - topReactions: {}, - myTopReactions: {}, - }, }, errors: [], requests: { diff --git a/webapp/channels/src/plugins/registry.ts b/webapp/channels/src/plugins/registry.ts index 8afd659a5d..a8a51c9433 100644 --- a/webapp/channels/src/plugins/registry.ts +++ b/webapp/channels/src/plugins/registry.ts @@ -1154,62 +1154,4 @@ export default class PluginRegistry { data, }); }); - - registerInsightsHandler = reArg(['handler'], ({handler}) => { - store.dispatch({ - type: ActionTypes.RECEIVED_PLUGIN_INSIGHT, - data: { - pluginId: this.id, - handler, - }, - }); - }); - - // Register a hook to intercept desktop notifications before they occur. - // Accepts a function to run before the desktop notification is triggered. - // The function has the following signature: - // (post: Post, msgProps: NewPostMessageProps, channel: Channel, - // teamId: string, args: DesktopNotificationArgs) => Promise<{ - // error?: string; - // args?: DesktopNotificationArgs; - // }>) - // - // DesktopNotificationArgs is the following type: - // export type DesktopNotificationArgs = { - // title: string; - // body: string; - // silent: boolean; - // soundName: string; - // url: string; - // notify: boolean; - // }; - // - // To stop a desktop notification and allow subsequent hooks to process the notification, return: - // {args: {...args, notify: false}} - // To enable a desktop notification and allow subsequent hooks to process the notification, return: - // {args: {...args, notify: true}} - // To stop a desktop notification and prevent subsequent hooks from processing the notification, return either: - // {error: 'log this error'}, or {} - // To allow subsequent hooks to process the notification, return: - // {args}, or null or undefined (thanks js) - // - // The args returned by the hook will be used as the args for the next hook, until all hooks are - // completed. The resulting args will be used as the arguments for the `notifyMe` function. - // - // Returns a unique identifier. - registerDesktopNotificationHook = reArg(['hook'], ({hook}) => { - const id = generateId(); - - store.dispatch({ - type: ActionTypes.RECEIVED_PLUGIN_COMPONENT, - name: 'DesktopNotificationHooks', - data: { - id, - pluginId: this.id, - hook, - }, - }); - - return id; - }); } diff --git a/webapp/channels/src/reducers/plugins/index.ts b/webapp/channels/src/reducers/plugins/index.ts index 20f0ba78cb..f0e0801a37 100644 --- a/webapp/channels/src/reducers/plugins/index.ts +++ b/webapp/channels/src/reducers/plugins/index.ts @@ -381,29 +381,6 @@ function siteStatsHandlers(state: PluginsState['siteStatsHandlers'] = {}, action } } -function insightsHandlers(state: PluginsState['insightsHandlers'] = {}, action: GenericAction) { - switch (action.type) { - case ActionTypes.RECEIVED_PLUGIN_INSIGHT: - if (action.data) { - const nextState = {...state}; - nextState[action.data.pluginId] = action.data.handler; - return nextState; - } - return state; - case ActionTypes.REMOVED_WEBAPP_PLUGIN: - if (action.data) { - const nextState = {...state}; - delete nextState[action.data.id]; - return nextState; - } - return state; - case UserTypes.LOGOUT_SUCCESS: - return {}; - default: - return state; - } -} - export default combineReducers({ // object where every key is a plugin id and values are webapp plugin manifests @@ -432,8 +409,4 @@ export default combineReducers({ // objects where every key is a plugin id and the value is a promise to fetch stats from // a plugin to render on system console siteStatsHandlers, - - // object where every key is a plugin id and the value is a promise to fetch insights from - // a plugin to render on the insights page - insightsHandlers, }); diff --git a/webapp/channels/src/sass/components/_modal.scss b/webapp/channels/src/sass/components/_modal.scss index 702b4912e5..52530fa7c5 100644 --- a/webapp/channels/src/sass/components/_modal.scss +++ b/webapp/channels/src/sass/components/_modal.scss @@ -1161,7 +1161,6 @@ } } -.insights-modal, .user-groups-modal-create, .user-groups-modal-update, .view-user-groups-modal, diff --git a/webapp/channels/src/sass/responsive/_desktop.scss b/webapp/channels/src/sass/responsive/_desktop.scss index 0b13068f19..70a25651c5 100644 --- a/webapp/channels/src/sass/responsive/_desktop.scss +++ b/webapp/channels/src/sass/responsive/_desktop.scss @@ -1,19 +1,5 @@ @charset "utf-8"; -@media screen and (min-width: 1680px) and (max-width: 2135px) { - .inner-wrap { - &.move--left { - .ActivityAndInsights { - .insights-card { - &.large { - align-self: flex-start; - } - } - } - } - } -} - @media screen and (min-width: 1680px) { .sidebar--right, .sidebar--right--width-holder { diff --git a/webapp/channels/src/selectors/insights.ts b/webapp/channels/src/selectors/insights.ts deleted file mode 100644 index 82975c571d..0000000000 --- a/webapp/channels/src/selectors/insights.ts +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import {GlobalState} from 'types/store'; -import {get} from 'mattermost-redux/selectors/entities/preferences'; -import {Preferences} from 'mattermost-redux/constants'; -import {getIsMobileView} from 'selectors/views/browser'; - -export function showInsightsPulsatingDot(state: GlobalState): boolean { - if (getIsMobileView(state)) { - return false; - } - const insightsTutorialState = get(state, Preferences.CATEGORY_INSIGHTS, Preferences.NAME_INSIGHTS_TUTORIAL_STATE, false); - const modalAlreadyViewed = insightsTutorialState && JSON.parse(insightsTutorialState)[Preferences.INSIGHTS_VIEWED]; - return !modalAlreadyViewed; -} diff --git a/webapp/channels/src/selectors/lhs.test.ts b/webapp/channels/src/selectors/lhs.test.ts index d5a411c3b1..c0a2776662 100644 --- a/webapp/channels/src/selectors/lhs.test.ts +++ b/webapp/channels/src/selectors/lhs.test.ts @@ -11,7 +11,6 @@ jest.mock('selectors/drafts', () => ({ })); jest.mock('mattermost-redux/selectors/entities/preferences', () => ({ - insightsAreEnabled: jest.fn(), isCollapsedThreadsEnabled: jest.fn(), })); @@ -55,28 +54,13 @@ describe('Selectors.Lhs', () => { }); it('handles nothing enabled', () => { - jest.spyOn(PreferencesSelectors, 'insightsAreEnabled').mockImplementationOnce(() => false); jest.spyOn(PreferencesSelectors, 'isCollapsedThreadsEnabled').mockImplementationOnce(() => false); jest.spyOn(Lhs, 'getDraftsCount').mockImplementationOnce(() => 0); const items = Lhs.getVisibleStaticPages(state as GlobalState); expect(items).toEqual([]); }); - it('handles insights', () => { - jest.spyOn(PreferencesSelectors, 'insightsAreEnabled').mockImplementation(() => true); - jest.spyOn(PreferencesSelectors, 'isCollapsedThreadsEnabled').mockImplementation(() => false); - jest.spyOn(Lhs, 'getDraftsCount').mockImplementationOnce(() => 0); - const items = Lhs.getVisibleStaticPages(state as GlobalState); - expect(items).toEqual([ - { - id: 'activity-and-insights', - isVisible: true, - }, - ]); - }); - it('handles threads - default off', () => { - jest.spyOn(PreferencesSelectors, 'insightsAreEnabled').mockImplementation(() => false); jest.spyOn(PreferencesSelectors, 'isCollapsedThreadsEnabled').mockImplementation(() => true); jest.spyOn(Lhs, 'getDraftsCount').mockImplementationOnce(() => 0); const items = Lhs.getVisibleStaticPages(state as GlobalState); @@ -89,7 +73,6 @@ describe('Selectors.Lhs', () => { }); it('should not return drafts when empty', () => { - jest.spyOn(PreferencesSelectors, 'insightsAreEnabled').mockImplementation(() => false); jest.spyOn(PreferencesSelectors, 'isCollapsedThreadsEnabled').mockImplementation(() => false); jest.spyOn(Lhs, 'getDraftsCount').mockImplementationOnce(() => 0); const items = Lhs.getVisibleStaticPages(state as GlobalState); @@ -97,7 +80,6 @@ describe('Selectors.Lhs', () => { }); it('should return drafts when there are available', () => { - jest.spyOn(PreferencesSelectors, 'insightsAreEnabled').mockImplementation(() => false); jest.spyOn(PreferencesSelectors, 'isCollapsedThreadsEnabled').mockImplementation(() => false); jest.spyOn(Lhs, 'getDraftsCount').mockImplementationOnce(() => 1); const items = Lhs.getVisibleStaticPages(state as GlobalState); diff --git a/webapp/channels/src/selectors/lhs.ts b/webapp/channels/src/selectors/lhs.ts index ce2ea89698..d1f9765368 100644 --- a/webapp/channels/src/selectors/lhs.ts +++ b/webapp/channels/src/selectors/lhs.ts @@ -7,7 +7,6 @@ import {StaticPage} from 'types/store/lhs'; import {createSelector} from 'mattermost-redux/selectors/create_selector'; import {makeGetDraftsCount} from 'selectors/drafts'; import { - insightsAreEnabled, isCollapsedThreadsEnabled, } from 'mattermost-redux/selectors/entities/preferences'; @@ -23,19 +22,11 @@ export const getDraftsCount = makeGetDraftsCount(); export const getVisibleStaticPages = createSelector( 'getVisibleSidebarStaticPages', - insightsAreEnabled, isCollapsedThreadsEnabled, getDraftsCount, - (insightsEnabled, collapsedThreadsEnabled, draftsCount) => { + (collapsedThreadsEnabled, draftsCount) => { const staticPages: StaticPage[] = []; - if (insightsEnabled) { - staticPages.push({ - id: 'activity-and-insights', - isVisible: true, - }); - } - if (collapsedThreadsEnabled) { staticPages.push({ id: 'threads', diff --git a/webapp/channels/src/types/store/lhs.ts b/webapp/channels/src/types/store/lhs.ts index f56caf4c00..61fa131a7d 100644 --- a/webapp/channels/src/types/store/lhs.ts +++ b/webapp/channels/src/types/store/lhs.ts @@ -4,7 +4,7 @@ export type LhsViewState = { isOpen: boolean; - // Static pages (e.g. Threads, Insights, etc.) + // Static pages (e.g. Threads, etc.) currentStaticPageId: string; } @@ -16,7 +16,6 @@ export enum LhsItemType { export enum LhsPage { Drafts = 'drafts', - Insights = 'activity-and-insights', Threads = 'threads', } diff --git a/webapp/channels/src/types/store/plugins.ts b/webapp/channels/src/types/store/plugins.ts index 63b82121b2..cb49b7263a 100644 --- a/webapp/channels/src/types/store/plugins.ts +++ b/webapp/channels/src/types/store/plugins.ts @@ -9,7 +9,6 @@ import {PluginAnalyticsRow} from '@mattermost/types/admin'; import {FileInfo} from '@mattermost/types/files'; import {Post, PostEmbed} from '@mattermost/types/posts'; import {IDMappedObjects} from '@mattermost/types/utilities'; -import {TopBoardResponse} from '@mattermost/types/insights'; import {IconGlyphTypes} from '@mattermost/compass-icons/IconGlyphs'; import {WebSocketClient} from '@mattermost/client'; @@ -60,9 +59,6 @@ export type PluginsState = { siteStatsHandlers: { [pluginId: string]: PluginSiteStatsHandler; }; - insightsHandlers: { - [pluginId: string]: (timeRange: string, page: number, perPage: number, teamId: string, insightType: string) => Promise; - }; }; export type Menu = { diff --git a/webapp/channels/src/utils/constants.tsx b/webapp/channels/src/utils/constants.tsx index 5518bc1ff8..fb19606eca 100644 --- a/webapp/channels/src/utils/constants.tsx +++ b/webapp/channels/src/utils/constants.tsx @@ -60,7 +60,6 @@ export const InviteTypes = { export const PreviousViewedTypes = { CHANNELS: 'channels', THREADS: 'threads', - INSIGHTS: 'insights', }; export const Preferences = { @@ -315,7 +314,6 @@ export const ActionTypes = keyMirror({ FIRST_CHANNEL_NAME: null, - RECEIVED_PLUGIN_INSIGHT: null, SET_EDIT_CHANNEL_MEMBERS: null, NEEDS_LOGGED_IN_LIMIT_REACHED_CHECK: null, @@ -419,7 +417,6 @@ export const ModalIdentifiers = { DELETE_DRAFT: 'delete_draft_modal', SEND_DRAFT: 'send_draft_modal', UPLOAD_LICENSE: 'upload_license', - INSIGHTS: 'insights', CLOUD_LIMITS: 'cloud_limits', THREE_DAYS_LEFT_TRIAL_MODAL: 'three_days_left_trial_modal', REQUEST_BUSINESS_EMAIL_MODAL: 'request_business_email_modal', @@ -506,7 +503,6 @@ export const MattermostFeatures = { PLAYBOOKS_RETRO: 'mattermost.feature.playbooks_retro', UNLIMITED_MESSAGES: 'mattermost.feature.unlimited_messages', UNLIMITED_FILE_STORAGE: 'mattermost.feature.unlimited_file_storage', - TEAM_INSIGHTS: 'mattermost.feature.team_insights', ALL_PROFESSIONAL_FEATURES: 'mattermost.feature.all_professional', ALL_ENTERPRISE_FEATURES: 'mattermost.feature.all_enterprise', UPGRADE_DOWNGRADED_WORKSPACE: 'mattermost.feature.upgrade_downgraded_workspace', @@ -1575,7 +1571,6 @@ export const Constants = { INVITE_TEAM: 'I', OPEN_TEAM: 'O', THREADS: 'threads', - INSIGHTS: 'insights', MAX_POST_LEN: 4000, EMOJI_SIZE: 16, DEFAULT_EMOJI_PICKER_LEFT_OFFSET: 87, @@ -2143,141 +2138,6 @@ export const durationValues = { defaultMessage: 'Custom Date and Time', }, }; - -export const InsightsScopes = { - MY: 'MY', - TEAM: 'TEAM', -}; - -export const InsightsCardTitles = { - TOP_CHANNELS: { - teamTitle: { - id: t('insights.topChannels.title'), - defaultMessage: 'Top channels', - }, - myTitle: { - id: t('insights.topChannels.myTitle'), - defaultMessage: 'My top channels', - }, - teamSubTitle: { - id: t('insights.topChannels.subTitle'), - defaultMessage: 'Most active channels for the team', - }, - mySubTitle: { - id: t('insights.topChannels.mySubTitle'), - defaultMessage: 'Most active channels that I\'m a member of', - }, - }, - TOP_REACTIONS: { - teamTitle: { - id: t('insights.topReactions.title'), - defaultMessage: 'Top reactions', - }, - myTitle: { - id: t('insights.topReactions.myTitle'), - defaultMessage: 'My top reactions', - }, - teamSubTitle: { - id: t('insights.topReactions.subTitle'), - defaultMessage: 'The team\'s most-used reactions', - }, - mySubTitle: { - id: t('insights.topReactions.mySubTitle'), - defaultMessage: 'Reactions I\'ve used the most', - }, - }, - TOP_THREADS: { - teamTitle: { - id: t('insights.topThreads.title'), - defaultMessage: 'Top threads', - }, - myTitle: { - id: t('insights.topThreads.myTitle'), - defaultMessage: 'My top threads', - }, - teamSubTitle: { - id: t('insights.topThreads.subTitle'), - defaultMessage: 'Most active threads for the team', - }, - mySubTitle: { - id: t('insights.topThreads.mySubTitle'), - defaultMessage: 'Most active threads I\'ve followed', - }, - }, - TOP_BOARDS: { - teamTitle: { - id: t('insights.topBoards.title'), - defaultMessage: 'Top boards', - }, - myTitle: { - id: t('insights.topBoards.myTitle'), - defaultMessage: 'My top boards', - }, - teamSubTitle: { - id: t('insights.topBoards.subTitle'), - defaultMessage: 'Most active boards for the team', - }, - mySubTitle: { - id: t('insights.topBoards.mySubTitle'), - defaultMessage: 'Most active boards I\'ve participated in', - }, - }, - LEAST_ACTIVE_CHANNELS: { - teamTitle: { - id: t('insights.leastActiveChannels.title'), - defaultMessage: 'Least active channels', - }, - myTitle: { - id: t('insights.leastActiveChannels.myTitle'), - defaultMessage: 'My least active channels', - }, - teamSubTitle: { - id: t('insights.leastActiveChannels.subTitle'), - defaultMessage: 'Channels with the least posts', - }, - mySubTitle: { - id: t('insights.leastActiveChannels.mySubTitle'), - defaultMessage: 'My channels with the least posts', - }, - }, - TOP_PLAYBOOKS: { - teamTitle: { - id: t('insights.topPlaybooks.title'), - defaultMessage: 'Top playbooks', - }, - myTitle: { - id: t('insights.topPlaybooks.myTitle'), - defaultMessage: 'My top playbooks', - }, - teamSubTitle: { - id: t('insights.topPlaybooks.subTitle'), - defaultMessage: 'Playbooks with the most runs', - }, - mySubTitle: { - id: t('insights.topPlaybooks.mySubTitle'), - defaultMessage: 'Playbooks I\'ve used with the most runs', - }, - }, - TOP_DMS: { - teamTitle: {}, - myTitle: { - id: t('insights.topDMs.myTitle'), - defaultMessage: 'My most active direct messages', - }, - teamSubTitle: {}, - mySubTitle: {}, - }, - NEW_TEAM_MEMBERS: { - teamTitle: { - id: t('insights.newTeamMembers.title'), - defaultMessage: 'New team members', - }, - myTitle: {}, - teamSubTitle: {}, - mySubTitle: {}, - }, -}; - export enum ClaimErrors { MFA_VALIDATE_TOKEN_AUTHENTICATE = 'mfa.validate_token.authenticate.app_error', ENT_LDAP_LOGIN_USER_NOT_REGISTERED = 'ent.ldap.do_login.user_not_registered.app_error', diff --git a/webapp/platform/client/src/client4.ts b/webapp/platform/client/src/client4.ts index 822f97cdbb..910c58d3b4 100644 --- a/webapp/platform/client/src/client4.ts +++ b/webapp/platform/client/src/client4.ts @@ -138,7 +138,6 @@ import { } from '@mattermost/types/data_retention'; import {CompleteOnboardingRequest} from '@mattermost/types/setup'; import {UserThreadList, UserThread, UserThreadWithPost} from '@mattermost/types/threads'; -import {LeastActiveChannelsResponse, TopChannelResponse, TopReactionResponse, TopThreadResponse, TopDMsResponse} from '@mattermost/types/insights'; import {cleanUrlForLogging} from './errors'; import {buildQueryString} from './helpers'; @@ -2177,74 +2176,6 @@ export default class Client4 { ); }; - getTopReactionsForTeam = (teamId: string, page: number, perPage: number, timeRange: string) => { - return this.doFetch( - `${this.getTeamRoute(teamId)}/top/reactions${buildQueryString({page, per_page: perPage, time_range: timeRange})}`, - {method: 'get'}, - ); - } - - getMyTopReactions = (teamId: string, page: number, perPage: number, timeRange: string) => { - return this.doFetch( - `${this.getUsersRoute()}/me/top/reactions${buildQueryString({page, per_page: perPage, time_range: timeRange, team_id: teamId})}`, - {method: 'get'}, - ); - } - - getTopChannelsForTeam = (teamId: string, page: number, perPage: number, timeRange: string) => { - return this.doFetch( - `${this.getTeamRoute(teamId)}/top/channels${buildQueryString({page, per_page: perPage, time_range: timeRange})}`, - {method: 'get'}, - ); - } - - getMyTopChannels = (teamId: string, page: number, perPage: number, timeRange: string) => { - return this.doFetch( - `${this.getUsersRoute()}/me/top/channels${buildQueryString({page, per_page: perPage, time_range: timeRange, team_id: teamId})}`, - {method: 'get'}, - ); - } - - getTopThreadsForTeam = (teamId: string, page: number, perPage: number, timeRange: string) => { - return this.doFetch( - `${this.getTeamRoute(teamId)}/top/threads${buildQueryString({page, per_page: perPage, time_range: timeRange})}`, - {method: 'get'}, - ); - } - - getMyTopThreads = (teamId: string, page: number, perPage: number, timeRange: string) => { - return this.doFetch( - `${this.getUsersRoute()}/me/top/threads${buildQueryString({page, per_page: perPage, time_range: timeRange, team_id: teamId})}`, - {method: 'get'}, - ); - } - - getLeastActiveChannelsForTeam = (teamId: string, page: number, perPage: number, timeRange: string) => { - return this.doFetch( - `${this.getTeamRoute(teamId)}/top/inactive_channels${buildQueryString({page, per_page: perPage, time_range: timeRange})}`, - {method: 'get'}, - ); - } - getMyTopDMs = (teamId: string, page: number, perPage: number, timeRange: string) => { - return this.doFetch( - `${this.getUsersRoute()}/me/top/dms${buildQueryString({page, per_page: perPage, time_range: timeRange, team_id: teamId})}`, - {method: 'get'}, - ); - } - - getMyLeastActiveChannels = (teamId: string, page: number, perPage: number, timeRange: string) => { - return this.doFetch( - `${this.getUsersRoute()}/me/top/inactive_channels${buildQueryString({page, per_page: perPage, time_range: timeRange, team_id: teamId})}`, - {method: 'get'}, - ); - } - getNewTeamMembers = (teamId: string, page: number, perPage: number, timeRange: string) => { - return this.doFetch( - `${this.getTeamRoute(teamId)}/top/team_members${buildQueryString({page, per_page: perPage, time_range: timeRange})}`, - {method: 'get'}, - ); - } - searchPostsWithParams = (teamId: string, params: any) => { this.trackEvent('api', 'api_posts_search', {team_id: teamId}); diff --git a/webapp/platform/types/src/config.ts b/webapp/platform/types/src/config.ts index 27112bd504..12edcd1dc3 100644 --- a/webapp/platform/types/src/config.ts +++ b/webapp/platform/types/src/config.ts @@ -131,7 +131,6 @@ export type ClientConfig = { IosAppDownloadLink: string; IosLatestVersion: string; IosMinVersion: string; - InsightsEnabled: string; InstallationDate: string; IsDefaultMarketplace: string; LdapFirstNameAttributeSet: string; diff --git a/webapp/platform/types/src/insights.ts b/webapp/platform/types/src/insights.ts deleted file mode 100644 index 0b31ca3d40..0000000000 --- a/webapp/platform/types/src/insights.ts +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import {ChannelType} from './channels'; -import {Post} from './posts'; -import {UserProfile} from './users'; - -export enum InsightsWidgetTypes { - TOP_CHANNELS = 'TOP_CHANNELS', - TOP_REACTIONS = 'TOP_REACTIONS', - TOP_THREADS = 'TOP_THREADS', - TOP_BOARDS = 'TOP_BOARDS', - LEAST_ACTIVE_CHANNELS = 'LEAST_ACTIVE_CHANNELS', - TOP_PLAYBOOKS = 'TOP_PLAYBOOKS', - TOP_DMS = 'TOP_DMS', - NEW_TEAM_MEMBERS = 'NEW_TEAM_MEMBERS', -} - -export enum CardSizes { - large = 'lg', - medium = 'md', - small = 'sm', -} -export type CardSize = CardSizes; - -export enum TimeFrames { - INSIGHTS_1_DAY = 'today', - INSIGHTS_7_DAYS = '7_day', - INSIGHTS_28_DAYS = '28_day', -} - -export type TimeFrame = TimeFrames; - -export type TopReaction = { - emoji_name: string; - count: number; -} - -export type TopReactionResponse = { - has_next: boolean; - items: TopReaction[]; - timeFrame?: TimeFrame; -} - -export type TopChannel = { - id: string; - type: ChannelType; - display_name: string; - name: string; - team_id: string; - message_count: number; -} - -export type TopChannelGraphData = Record>; - -export type TopChannelResponse = { - has_next: boolean; - items: TopChannel[]; - daily_channel_post_counts: TopChannelGraphData; -}; - -export type InsightsState = { - topReactions: Record>>; - myTopReactions: Record>>; -} - -export type TopChannelActionResult = { - data?: TopChannelResponse; - error?: any; -}; - -export type TopThread = { - channel_id: string; - channel_display_name: string; - channel_name: string; - participants: string[]; - user_information: { - id: string; - first_name: string; - last_name: string; - last_picture_update: number; - }; - post: Post; -}; - -export type TopThreadResponse = { - has_next: boolean; - items: TopThread[]; -}; - -export type TopThreadActionResult = { - data?: TopThreadResponse; - error?: any; -}; - -export type TopBoard = { - boardID: string; - icon: string; - title: string; - activityCount: number; - - // MM-49023: community bugfix to maintain backwards compatibility - activeUsers: Array | string; - createdBy: string; -}; - -export type TopBoardResponse = { - has_next: boolean; - items: TopBoard[]; -}; - -export type LeastActiveChannel = { - id: string; - display_name: string; - name: string; - participants: string[]; - last_activity_at: number; - type: ChannelType; - team_id: string; - message_count: number; -}; - -export type LeastActiveChannelsResponse = { - has_next: boolean; - items: LeastActiveChannel[]; -}; - -export type LeastActiveChannelsActionResult = { - data?: LeastActiveChannelsResponse; - error?: any; -}; -export type TopPlaybook = { - playbook_id: string; - num_runs: number; - title: string; - last_run_at: number; -}; - -export type TopPlaybookResponse = { - has_next: boolean; - items: TopPlaybook[]; -}; - -type MinUserProfile = { - id: string; - first_name: string; - last_name: string; - last_picture_update: number; - nickname: string; - position: string; - username: string; -}; - -export type TopDM = { - outgoing_message_count: number; - post_count: number; - second_participant: MinUserProfile; -}; - -export type TopDMsResponse = { - has_next: boolean; - items: TopDM[]; -}; - -export type TopDMsActionResult = { - data?: TopDMsResponse; - error?: any; -}; - -export type NewMember = MinUserProfile & { - create_at: number; -}; - -export type NewMembersResponse = { - has_next: boolean; - items: NewMember[]; - total_count: number; -}; - -export type NewMembersActionResult = { - data?: NewMembersResponse; - error?: any; -}; diff --git a/webapp/platform/types/src/store.ts b/webapp/platform/types/src/store.ts index 5ba84c65eb..4b1a14ef6e 100644 --- a/webapp/platform/types/src/store.ts +++ b/webapp/platform/types/src/store.ts @@ -29,7 +29,6 @@ import {ThreadsState} from './threads'; import {Typing} from './typing'; import {UsersState} from './users'; import {AppsState} from './apps'; -import {InsightsState} from './insights'; import {GifsState} from './gifs'; export type GlobalState = { @@ -69,7 +68,6 @@ export type GlobalState = { cloud: CloudState; hostedCustomer: HostedCustomerState; usage: CloudUsage; - insights: InsightsState; }; errors: any[]; requests: {