diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json index b871629e1d..b3d7b828c5 100644 --- a/webapp/i18n/en.json +++ b/webapp/i18n/en.json @@ -2,8 +2,6 @@ "+/x2FM": "Select a playbook", "+6DCr9": "As a participant, you can post status updates, assign and complete tasks, and perform retrospectives.", "+8G9qr": "Default text for the retrospective.", - "+PMJAg": "Begin following for {followers, plural, =1 {one user} other {# users}}", - "+QgvjN": "Assign the owner role to", "+Tmpup": "You automatically receive updates when this playbook is run.", "+hddg7": "Add to run timeline", "+qDKgW": "View all updates", @@ -13,8 +11,6 @@ "/MaJux": "Start retrospective", "/RnCQb": "Send outgoing webhook", "/YZ/sw": "Start trial", - "/ZsEUy": "Are you sure you want to delete this checklist? It will be removed from this run but will not affect the playbook.", - "/fU9y/": "You can check out different sections of the playbook in detail on this page.", "/gbqA6": "{duration} before run started", "/jUtaM": "ACTIVE RUNS per day over the last 14 days", "/qDObA": "Browse Runs", @@ -62,7 +58,6 @@ "4ltHYh": "Go to playbook", "4mCpAv": "It was not possible to change the owner", "4vuNrq": "{duration} after run started", - "5A46pW": "Add a slash command", "5AJmOz": "When a user joins the channel", "5BUxvl": "Everyone in this team can view this playbook.", "5CI3KH": "Contact support", @@ -76,10 +71,8 @@ "69nlA3": "Please enter a duration in the format: dd:hh:mm (e.g., 12:00:00).", "6CGo3o": "Status / Last update", "6D6ffM": "Please enter a duration in the format: dd:hh:mm (e.g., 12:00:00), or leave the target blank.", - "6GTzTR": "See what’s in this playbook at any time", "6uhSSw": "Select a channel", "7P5T3W": "Restore checklist", - "8hDbW6": "Send an outgoing webhook", "8n24G2": "View run details in a side panel", "9+Ddtu": "Next", "91Hr5f": "Drag me to reorder", @@ -117,7 +110,6 @@ "CSts8B": "Team Icon", "CUhlqp": "tutorial tour tip product image", "CV1ddt": "Participate in the run", - "CjNrqO": "Retrospective report template", "CkYhdY": "Add the channel to a sidebar category", "CwwzAU": "Add checklist name", "Cy1AK/": "View run details", @@ -125,7 +117,6 @@ "D2CE02": "Enter webhook", "D55vrs": "Your license could not be generated", "DCl7Vv": "inline code", - "DPj6DM": "Select Run to see it in action.", "DSVJjB": "Currently running the {playbookTitle} playbook", "DXACD6": "Publish retrospective report and access the timeline", "DaHpK1": "Search for a channel", @@ -156,7 +147,6 @@ "HXvk56": "Post status updates", "HhLp57": "quote", "HvAcYh": "{text}{rest, plural, =0 {} one { and other} other { and {rest} others}}", - "Hzwzgs": "Broadcast updates in the {oneChannel, plural, one {channel} other {channels}}", "I2zEie": "Celebrate success and learn from mistakes with retrospective reports. Filter timeline events for process review, stakeholder engagement, and auditing purposes.", "I5NMJ8": "More", "I7+d55": "Specify date/time (“in 4 hours”, “May 1”...)", @@ -171,9 +161,6 @@ "JeqL8w": "Retrospective canceled by {name}", "JqKASQ": "Add @{displayName} to Channel", "JrZ2th": "Add Metric", - "K3r6DQ": "Delete", - "KUr+sG": "Update run summary", - "KXVV4+": "Welcome to the playbook preview page!", "KeO51o": "Channel", "KiXNvz": "Run", "KjNfA8": "Invalid time duration", @@ -181,7 +168,6 @@ "L6k6aT": "…or start with a template", "LDYFkN": "Duration (in dd:hh:mm)", "LI7YlB": "Add details on what this metric is about and how it should be filled in. This description will be available on the retrospective page for each run where values for these metrics will be input.", - "LRFvqz": "Announce in the {oneChannel, plural, one {channel} other {channels}}", "LcC/pi": "Send a welcome message…", "LfhTNW": "Browse or create Playbooks and Runs", "Lg3I1b": "@{targetUsername}, please provide a status update.", @@ -203,7 +189,6 @@ "MyIJbr": "Contents", "N1U/QR": "Task state changes", "N2IrpM": "Confirm", - "NA7Cw1": "Copy link to playbook", "NFyWnZ": "Work more effectively", "NJ9uPu": "Key metrics", "NLeFGn": "to", @@ -211,7 +196,6 @@ "NYTGIb": "Got it", "Nh91Us": "{from, number}–{to, number} of {total, number} total", "NiAH1z": "Target value", - "OINwWS": "Create a {isPublic, select, true {public} other {private}} channel", "OK8u0r": "Create a playbook to prescribe the workflow that your teams and tools should follow, including everything from checklists, actions, templates, and retrospectives.", "OQplDX": "A status update is expected every . New updates will be posted to {channelCount, plural, =0 {no channels} one {# channel} other {# channels}} and {webhookCount, plural, =0 {no outgoing webhooks} one {# outgoing webhook} other {# outgoing webhooks}}.", "Ob5cSv": "Changes that you made will not be saved if you leave this page. Are you sure you want to discard changes and leave?", @@ -241,7 +225,6 @@ "QbGfqo": "Broadcast to stakeholders in multiple places and keep a paper trail for retrospective with just one post.", "QegBKq": "Join playbook", "QiKcO7": "Enter retrospective template", - "QnZAit": "Add optional description", "QpUBDr": "{members, plural, =0 {No one} =1 {One person} other {# people}} can access this playbook.", "Qrl6bQ": "Streamline your processes with playbooks", "QywYDe": "Also mark the run as finished", @@ -250,7 +233,6 @@ "RCT0Px": "Add {displayName} to Channel", "RO+BaS": "Copy link to run", "RQl8IW": "Snooze for…", - "RUlvbf": "Test your new playbook out!", "RnOiCg": "It was not possible to {isFollowing, select, true {unfollow} other {follow}} the run", "RoGxij": "Runs active on {date}", "RrCui3": "Summary", @@ -295,7 +277,6 @@ "WIxhrv": "Run name must have at least two characters", "WTQpnI": "Take action now using playbooks", "X/koAN": "Invalid entry: the maximum number of webhooks allowed is 64", - "X2K92H": "Checklist name", "XF8rrh": "Copy link to ''{name}''", "XRyRzf": "Status updates are not expected.", "XS4umx": "{name} snoozed a status update", @@ -305,12 +286,10 @@ "XnICdK": "It wasn't possible to join the run", "XpDetT": "Opt out of these tips.", "Xx0WZV": "Send message", - "Y4MU/9": "Select Start a test run to see it in action.", "YDuW/T": "{num_runs, plural, =0 {Not run yet} one {# run} other {# total runs}}", "YKn+7s": "This channel is not running any playbook.", "YORRGQ": "Post update", "YQOmSf": "Enter one webhook per line", - "Z/hwEf": "The channel will be reminded to perform the retrospective {reminderEnabled, select, true {every} other {}}", "Z2Hfu4": "Add a run summary", "Z3ybv/": "Add the channel to a sidebar category for the user", "Z7vWDQ": "There was an error", @@ -333,9 +312,7 @@ "b+DwLA": "Request to participate in this run.", "b/QBNs": "Update due", "b3TdyZ": "By clicking Start trial, I agree to the Mattermost Software Evaluation Agreement, Privacy Policy, and receiving product emails.", - "b5FaCc": "Add the channel to the sidebar category", "bE1Cro": "My runs only", - "bGhCLX": "When an update is posted", "bLK+Kr": "Reminds the channel at a specified interval to fill out the retrospective.", "bPLen5": "Runs finished in the last 30 days", "bTgMQ2": "This playbook is archived.", @@ -351,10 +328,8 @@ "d4g2r8": "Deleted: {timestamp}", "d8KvXJ": "Your trial license expires on {expiryDate}. You can purchase a license at any time through the Customer Portal to avoid any disruption.", "d9epHh": "Export channel log", - "dCtjdj": "Ready to run your playbook?", "dSC1YD": "Skip task", "dZmYk6": "Successfully duplicated playbook", - "djALPR": "{activeRuns, number} {activeRuns, plural, one {run} other {runs}} in progress", "dvhvum": "(Optional) Describe how this playbook should be used", "dxyZg3": "Let me explore for myself", "e/AZL5": "Your 30-day trial has started", @@ -381,7 +356,6 @@ "gt6BhE": "Run details", "guunZt": "Assign", "gy/Kkr": "(edited)", - "hO9EdA": "Invite {numInvitedUsers, plural, =0 {no members} =1 {one member} other {# members}} to the channel", "hVFgh4": "Include finished", "hXIYHG": "Install and enable the Channel Export plugin to support exporting the channel", "hfrrC7": "Team Initials", @@ -404,13 +378,10 @@ "j940pJ": "This update will be saved to overview page.", "jIIWN+": "preformatted", "jIgqRa": "Owner / Participants", - "jS/UOn": "Update template", - "jnmORb": "In this playbook", "jvo0vs": "Save", "jwimQJ": "Ok", "k1djnL": "Delete checklist", "k5EChD": "Are you sure you want to restart the run?", - "kDcpd/": "{numKeywords, plural, other {# keywords}}", "kEMvwX": "There are no runs matching those filters.", "kV5GkX": "When a status update is posted", "kXFojL": "You can also create a playbook ahead of time so it’s available when you need it.", @@ -432,7 +403,6 @@ "lkv547": "Due date (Available in the Professional plan)", "lr1CUA": "Browse Playbooks", "lrbrjv": "Yes, start retrospective", - "lxfpbh": "The owner will {reminderEnabled, select, true {be prompted to provide a status update every} other {not be prompted to provide a status update}}", "lyXljU": "Duplicate task", "m/KtHt": "You have no permissions to change the owner", "m/Q4ye": "Rename checklist", @@ -442,7 +412,6 @@ "mVpO8u": "Seen this before?", "mkLeuq": "Broadcast update to selected channels", "mm5vL8": "Only invited members", - "mvZUm3": "This is where you can explore your playbook components in detail. Select Edit to customize your playbook to fit your processes and models.", "mw9jVA": "Add a title", "nc8QpJ": "Recent Activity", "nkCCM2": "You will not be reminded again.", @@ -464,7 +433,6 @@ "pKLw8O": "Are you sure you want to delete this event? Deleted events will be permanently removed from the timeline.", "pzTOmv": "Followers", "q/Qo8l": "Private playbooks are only available in Mattermost Enterprise", - "q0cpUe": "Add checklist", "q6f8x9": "Change since last update", "qGlwfc": "Start run", "qyJtWy": "Show less", @@ -522,7 +490,6 @@ "waVyVY": "Participants currently active", "wbdGb5": "Assign, check off, or skip tasks to ensure the team is clear on how to move toward the finish line together.", "wbsq7O": "Usage", - "wbwhbH": "Task name", "wcWpGs": "Invalid webhook URLs", "wylJpv": "Everyone in {team} can view this playbook.", "x1phlu": "No time frame", diff --git a/webapp/src/components/backstage/playbooks/bar_graph.tsx b/webapp/src/components/backstage/bar_graph.tsx similarity index 100% rename from webapp/src/components/backstage/playbooks/bar_graph.tsx rename to webapp/src/components/backstage/bar_graph.tsx diff --git a/webapp/src/components/backstage/playbooks/line_graph.tsx b/webapp/src/components/backstage/line_graph.tsx similarity index 100% rename from webapp/src/components/backstage/playbooks/line_graph.tsx rename to webapp/src/components/backstage/line_graph.tsx diff --git a/webapp/src/components/backstage/main_body.tsx b/webapp/src/components/backstage/main_body.tsx index 56879f0bc1..57a01a1964 100644 --- a/webapp/src/components/backstage/main_body.tsx +++ b/webapp/src/components/backstage/main_body.tsx @@ -13,8 +13,6 @@ import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users'; import PlaybookRun from 'src/components/backstage/playbook_runs/playbook_run/playbook_run'; -import Playbook from 'src/components/backstage/playbooks/playbook'; - import PlaybookList from 'src/components/backstage/playbook_list'; import PlaybookEditor from 'src/components/backstage/playbook_editor/playbook_editor'; import {ErrorPageTypes} from 'src/constants'; @@ -84,9 +82,6 @@ const MainBody = () => { return ( - - - diff --git a/webapp/src/components/backstage/playbooks/metrics/metrics_card.tsx b/webapp/src/components/backstage/metrics/metrics_card.tsx similarity index 99% rename from webapp/src/components/backstage/playbooks/metrics/metrics_card.tsx rename to webapp/src/components/backstage/metrics/metrics_card.tsx index 477c31a703..dc270ac262 100644 --- a/webapp/src/components/backstage/playbooks/metrics/metrics_card.tsx +++ b/webapp/src/components/backstage/metrics/metrics_card.tsx @@ -6,11 +6,12 @@ import styled from 'styled-components'; import {Duration} from 'luxon'; import {FormattedNumber, useIntl} from 'react-intl'; +import BarGraph from 'src/components/backstage/bar_graph'; + import {Metric, MetricType} from 'src/types/playbook'; import {HorizontalSpacer} from 'src/components/backstage/styles'; import {NullNumber, PlaybookStats} from 'src/types/stats'; import {formatDuration} from 'src/components/formatted_duration'; -import BarGraph from 'src/components/backstage/playbooks/bar_graph'; interface Props { playbookMetrics: Metric[]; diff --git a/webapp/src/components/backstage/playbooks/metrics/metrics_row.tsx b/webapp/src/components/backstage/metrics/metrics_row.tsx similarity index 97% rename from webapp/src/components/backstage/playbooks/metrics/metrics_row.tsx rename to webapp/src/components/backstage/metrics/metrics_row.tsx index f1d2e8f9fe..c2173788e5 100644 --- a/webapp/src/components/backstage/playbooks/metrics/metrics_row.tsx +++ b/webapp/src/components/backstage/metrics/metrics_row.tsx @@ -7,11 +7,12 @@ import styled from 'styled-components'; import {FormattedMessage} from 'react-intl'; +import {MetricsInfo} from 'src/components/backstage/metrics/metrics_run_list'; + import {PlaybookRun} from 'src/types/playbook_run'; import {formatDuration} from 'src/components/formatted_duration'; import {navigateToPluginUrl} from 'src/browser_routing'; import {MetricType} from 'src/types/playbook'; -import {MetricsInfo} from 'src/components/backstage/playbooks/metrics/metrics_run_list'; interface Props { metricsInfo: MetricsInfo[]; diff --git a/webapp/src/components/backstage/playbooks/metrics/metrics_run_list.tsx b/webapp/src/components/backstage/metrics/metrics_run_list.tsx similarity index 95% rename from webapp/src/components/backstage/playbooks/metrics/metrics_run_list.tsx rename to webapp/src/components/backstage/metrics/metrics_run_list.tsx index 2c657e141b..34e82490a9 100644 --- a/webapp/src/components/backstage/playbooks/metrics/metrics_run_list.tsx +++ b/webapp/src/components/backstage/metrics/metrics_run_list.tsx @@ -10,8 +10,9 @@ import {FetchPlaybookRunsParams, PlaybookRun} from 'src/types/playbook_run'; import Spinner from 'src/components/assets/icons/spinner'; import Filters from 'src/components/backstage/runs_list/filters'; import {Metric, MetricType} from 'src/types/playbook'; -import MetricsRow from 'src/components/backstage/playbooks/metrics/metrics_row'; -import MetricsRunListHeader from 'src/components/backstage/playbooks/metrics/metrics_run_list_header'; + +import MetricsRunListHeader from './metrics_run_list_header'; +import MetricsRow from './metrics_row'; export interface MetricsInfo { type: MetricType; diff --git a/webapp/src/components/backstage/playbooks/metrics/metrics_run_list_header.tsx b/webapp/src/components/backstage/metrics/metrics_run_list_header.tsx similarity index 97% rename from webapp/src/components/backstage/playbooks/metrics/metrics_run_list_header.tsx rename to webapp/src/components/backstage/metrics/metrics_run_list_header.tsx index 0cd989b726..ed84ce9c24 100644 --- a/webapp/src/components/backstage/playbooks/metrics/metrics_run_list_header.tsx +++ b/webapp/src/components/backstage/metrics/metrics_run_list_header.tsx @@ -5,9 +5,10 @@ import React from 'react'; import styled from 'styled-components'; import {useIntl} from 'react-intl'; +import {MetricsInfo} from 'src/components/backstage/metrics/metrics_run_list'; + import {SortableColHeader} from 'src/components/sortable_col_header'; import {MetricType} from 'src/types/playbook'; -import {MetricsInfo} from 'src/components/backstage/playbooks/metrics/metrics_run_list'; import {FetchPlaybookRunsParams} from 'src/types/playbook_run'; interface Props { diff --git a/webapp/src/components/backstage/playbooks/metrics/metrics_stats_view.tsx b/webapp/src/components/backstage/metrics/metrics_stats_view.tsx similarity index 96% rename from webapp/src/components/backstage/playbooks/metrics/metrics_stats_view.tsx rename to webapp/src/components/backstage/metrics/metrics_stats_view.tsx index b671314eb1..dbd32c02bb 100644 --- a/webapp/src/components/backstage/playbooks/metrics/metrics_stats_view.tsx +++ b/webapp/src/components/backstage/metrics/metrics_stats_view.tsx @@ -4,11 +4,12 @@ import React from 'react'; import styled from 'styled-components'; -import MetricsCard from 'src/components/backstage/playbooks/metrics/metrics_card'; import {PlaybookStats} from 'src/types/stats'; import {Metric, MetricType} from 'src/types/playbook'; import {ClockOutline, DollarSign, PoundSign} from 'src/components/backstage/playbook_edit/styles'; +import MetricsCard from './metrics_card'; + interface Props { playbookMetrics: Metric[]; stats: PlaybookStats; diff --git a/webapp/src/components/backstage/playbooks/metrics/no_metrics_placeholder.tsx b/webapp/src/components/backstage/metrics/no_metrics_placeholder.tsx similarity index 100% rename from webapp/src/components/backstage/playbooks/metrics/no_metrics_placeholder.tsx rename to webapp/src/components/backstage/metrics/no_metrics_placeholder.tsx diff --git a/webapp/src/components/backstage/playbooks/metrics/playbook_key_metrics.tsx b/webapp/src/components/backstage/metrics/playbook_key_metrics.tsx similarity index 88% rename from webapp/src/components/backstage/playbooks/metrics/playbook_key_metrics.tsx rename to webapp/src/components/backstage/metrics/playbook_key_metrics.tsx index 6bcad20cff..e1af6b9865 100644 --- a/webapp/src/components/backstage/playbooks/metrics/playbook_key_metrics.tsx +++ b/webapp/src/components/backstage/metrics/playbook_key_metrics.tsx @@ -6,16 +6,19 @@ import styled from 'styled-components'; import {PlaybookStats} from 'src/types/stats'; import {useAllowPlaybookAndRunMetrics, useRunsList} from 'src/hooks'; -import UpgradeKeyMetricsPlaceholder from 'src/components/backstage/playbooks/metrics/upgrade_key_metrics_placeholder'; -import MetricsStatsView from 'src/components/backstage/playbooks/metrics/metrics_stats_view'; + import {BACKSTAGE_LIST_PER_PAGE} from 'src/constants'; import {PlaybookRunStatus} from 'src/types/playbook_run'; -import MetricsRunList from 'src/components/backstage/playbooks/metrics/metrics_run_list'; -import NoMetricsPlaceholder from 'src/components/backstage/playbooks/metrics/no_metrics_placeholder'; + import {Metric} from 'src/types/playbook'; import {usePlaybookViewTelemetry} from 'src/hooks/telemetry'; import {PlaybookViewTarget} from 'src/types/telemetry'; +import NoMetricsPlaceholder from './no_metrics_placeholder'; +import MetricsRunList from './metrics_run_list'; +import MetricsStatsView from './metrics_stats_view'; +import UpgradeKeyMetricsPlaceholder from './upgrade_key_metrics_placeholder'; + const defaultPlaybookFetchParams = { page: 0, per_page: BACKSTAGE_LIST_PER_PAGE, diff --git a/webapp/src/components/backstage/playbooks/metrics/upgrade_key_metrics_placeholder.tsx b/webapp/src/components/backstage/metrics/upgrade_key_metrics_placeholder.tsx similarity index 100% rename from webapp/src/components/backstage/playbooks/metrics/upgrade_key_metrics_placeholder.tsx rename to webapp/src/components/backstage/metrics/upgrade_key_metrics_placeholder.tsx diff --git a/webapp/src/components/backstage/playbook_editor/outline/section_retrospective.tsx b/webapp/src/components/backstage/playbook_editor/outline/section_retrospective.tsx index 1f7c9010ae..75c944947b 100644 --- a/webapp/src/components/backstage/playbook_editor/outline/section_retrospective.tsx +++ b/webapp/src/components/backstage/playbook_editor/outline/section_retrospective.tsx @@ -6,7 +6,7 @@ import {FormattedMessage, useIntl} from 'react-intl'; import {useAllowRetrospectiveAccess} from 'src/hooks'; -import {Card} from 'src/components/backstage/playbooks/playbook_preview_cards'; +import {Card} from 'src/components/backstage/playbook_preview_cards'; import {FullPlaybook, Loaded, useUpdatePlaybook} from 'src/graphql/hooks'; import {Metric, PlaybookWithChecklist} from 'src/types/playbook'; diff --git a/webapp/src/components/backstage/playbook_editor/playbook_editor.tsx b/webapp/src/components/backstage/playbook_editor/playbook_editor.tsx index e9fe3f8ee5..84172c9192 100644 --- a/webapp/src/components/backstage/playbook_editor/playbook_editor.tsx +++ b/webapp/src/components/backstage/playbook_editor/playbook_editor.tsx @@ -25,8 +25,8 @@ import { import {telemetryEventForPlaybook} from 'src/client'; import {ErrorPageTypes} from 'src/constants'; -import PlaybookUsage from 'src/components/backstage/playbooks/playbook_usage'; -import PlaybookKeyMetrics from 'src/components/backstage/playbooks/metrics/playbook_key_metrics'; +import PlaybookUsage from 'src/components/backstage/playbook_usage'; +import PlaybookKeyMetrics from 'src/components/backstage/metrics/playbook_key_metrics'; import {SemiBoldHeading} from 'src/styles/headings'; diff --git a/webapp/src/components/backstage/playbooks/playbook_preview_cards.tsx b/webapp/src/components/backstage/playbook_preview_cards.tsx similarity index 100% rename from webapp/src/components/backstage/playbooks/playbook_preview_cards.tsx rename to webapp/src/components/backstage/playbook_preview_cards.tsx diff --git a/webapp/src/components/backstage/playbooks/playbook_usage.tsx b/webapp/src/components/backstage/playbook_usage.tsx similarity index 95% rename from webapp/src/components/backstage/playbooks/playbook_usage.tsx rename to webapp/src/components/backstage/playbook_usage.tsx index 994f48055a..da198f4a73 100644 --- a/webapp/src/components/backstage/playbooks/playbook_usage.tsx +++ b/webapp/src/components/backstage/playbook_usage.tsx @@ -4,15 +4,18 @@ import styled from 'styled-components'; import React, {useEffect, useState, ReactNode, HTMLAttributes} from 'react'; +import StatsView from 'src/components/backstage/stats_view'; + import {BACKSTAGE_LIST_PER_PAGE} from 'src/constants'; import {PlaybookStats} from 'src/types/stats'; -import StatsView from 'src/components/backstage/playbooks/stats_view'; import {useRunsList} from 'src/hooks'; -import RunList from '../runs_list/runs_list'; + import {PlaybookRunStatus} from 'src/types/playbook_run'; import {usePlaybookViewTelemetry} from 'src/hooks/telemetry'; import {PlaybookViewTarget} from 'src/types/telemetry'; +import RunList from './runs_list/runs_list'; + const defaultPlaybookFetchParams = { page: 0, per_page: BACKSTAGE_LIST_PER_PAGE, diff --git a/webapp/src/components/backstage/playbooks/playbook.tsx b/webapp/src/components/backstage/playbooks/playbook.tsx deleted file mode 100644 index 02e62e1123..0000000000 --- a/webapp/src/components/backstage/playbooks/playbook.tsx +++ /dev/null @@ -1,537 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import styled, {css} from 'styled-components'; -import React, {useEffect, useState} from 'react'; -import {useDispatch, useSelector} from 'react-redux'; -import {Switch, Route, Redirect, NavLink, useRouteMatch} from 'react-router-dom'; - -import Icon from '@mdi/react'; -import {mdiClipboardPlayOutline} from '@mdi/js'; - -import {Tooltip, OverlayTrigger} from 'react-bootstrap'; -import {Client4} from 'mattermost-redux/client'; - -import {getTeam} from 'mattermost-redux/selectors/entities/teams'; -import {Team} from '@mattermost/types/teams'; -import {GlobalState} from '@mattermost/types/store'; -import {getCurrentUserId, getCurrentUser} from 'mattermost-redux/selectors/entities/users'; - -import {FormattedMessage, useIntl} from 'react-intl'; - -import {navigateToUrl, navigateToPluginUrl, pluginErrorUrl} from 'src/browser_routing'; -import {useForceDocumentTitle, useHasPlaybookPermission, useStats} from 'src/hooks'; -import PlaybookUsage from 'src/components/backstage/playbooks/playbook_usage'; -import PlaybookPreview from 'src/components/backstage/playbooks/playbook_preview'; -import {useToaster} from '../toast_banner'; - -import { - clientFetchPlaybookFollowers, - clientFetchPlaybook, - duplicatePlaybook as clientDuplicatePlaybook, - autoFollowPlaybook, - autoUnfollowPlaybook, - telemetryEventForPlaybook, - playbookExportProps, - archivePlaybook, - createPlaybookRun, - getSiteUrl, -} from 'src/client'; -import {ErrorPageTypes, OVERLAY_DELAY} from 'src/constants'; -import {PlaybookWithChecklist} from 'src/types/playbook'; -import {PrimaryButton} from 'src/components/assets/buttons'; -import {RegularHeading} from 'src/styles/headings'; -import CheckboxInput from '../runs_list/checkbox_input'; -import {SecondaryButtonLargerRight} from '../playbook_runs/shared'; -import StatusBadge, {BadgeType} from 'src/components/backstage/status_badge'; - -import CopyLink from 'src/components/widgets/copy_link'; -import {displayEditPlaybookAccessModal} from 'src/actions'; -import {PlaybookPermissionGeneral} from 'src/types/permissions'; -import DotMenu, {DropdownMenuItem, DropdownMenuItemStyled} from 'src/components/dot_menu'; -import useConfirmPlaybookArchiveModal from '../archive_playbook_modal'; -import {useMeasurePunchouts, useShowTutorialStep} from 'src/components/tutorial/tutorial_tour_tip/hooks'; -import {PlaybookPreviewTutorialSteps, TutorialTourCategories} from 'src/components/tutorial/tours'; -import TutorialTourTip from 'src/components/tutorial/tutorial_tour_tip'; -import PlaybookKeyMetrics from 'src/components/backstage/playbooks/metrics/playbook_key_metrics'; - -interface MatchParams { - playbookId: string -} - -const LEARN_PLAYBOOKS_TITLE = 'Learn how to use playbooks'; - -const FetchingStateType = { - loading: 'loading', - fetched: 'fetched', - notFound: 'notfound', -}; - -const MembersIcon = styled.div` - display: inline-block; - font-size: 12px; - border-radius: 4px; - padding: 0 8px; - font-weight: 600; - margin: 2px; - color: rgba(var(--center-channel-color-rgb), 0.56); - height: 28px; - line-height: 28px; - cursor: pointer; - - &:hover { - background: rgba(var(--center-channel-color-rgb), 0.08); - color: rgba(var(--center-channel-color-rgb), 0.72); - } -`; - -const TitleButton = styled.div` - margin-left: 20px; - display: inline-flex; - border-radius: 4px; - color: rgba(var(--center-channel-color-rgb), 0.64); - fill: rgba(var(--center-channel-color-rgb), 0.64); - - &:hover { - background: rgba(var(--link-color-rgb), 0.08); - color: rgba(var(--link-color-rgb), 0.72); - } -`; - -const RedText = styled.div` - color: var(--error-text); -`; - -const StyledCopyLink = styled(CopyLink)` - border-radius: 4px; - font-size: 18px; - width: 24px; - height: 24px; - line-height: 18px; - margin-left: 8px; -`; - -/** @deprecated this page and potentially some inner sections will be deprecated in the future. See `playbook_editor/playbook_editor.tsx`. */ -const Playbook = () => { - const dispatch = useDispatch(); - const {formatMessage} = useIntl(); - const match = useRouteMatch(); - const [playbook, setPlaybook] = useState(); - const [followerIds, setFollowerIds] = useState([]); - const [fetchingState, setFetchingState] = useState(FetchingStateType.loading); - const team = useSelector((state) => getTeam(state, playbook?.team_id || '')); - const stats = useStats(match.params.playbookId); - const [isFollowed, setIsFollowed] = useState(false); - const currentUserId = useSelector(getCurrentUserId); - const currentUser = useSelector(getCurrentUser); - const [modal, openDeletePlaybookModal] = useConfirmPlaybookArchiveModal(() => { - if (playbook) { - archivePlaybook(playbook.id); - navigateToPluginUrl('/playbooks'); - } - }); - const addToast = useToaster().add; - const punchoutTitleRow = useMeasurePunchouts(['title-row'], [], {y: -5, height: 10, x: -5, width: 10}); - const showRunButtonTutorial = useShowTutorialStep(PlaybookPreviewTutorialSteps.RunButton, TutorialTourCategories.PLAYBOOK_PREVIEW); - - const changeFollowing = (check: boolean) => { - if (playbook?.id) { - if (check) { - autoFollowPlaybook(playbook.id, currentUserId); - } else { - autoUnfollowPlaybook(playbook.id, currentUserId); - } - setIsFollowed(check); - } - }; - - const hasPermissionToRunPlaybook = useHasPlaybookPermission(PlaybookPermissionGeneral.RunCreate, playbook); - - const isTutorialPlaybook = playbook?.title === LEARN_PLAYBOOKS_TITLE; - - useForceDocumentTitle(playbook?.title ? (playbook.title + ' - Playbooks') : 'Playbooks'); - - const goToPlaybooks = () => { - navigateToPluginUrl('/playbooks'); - }; - - const runPlaybook = async () => { - if (playbook && isTutorialPlaybook) { - const playbookRun = await createPlaybookRun(playbook.id, currentUserId, playbook.team_id, `${currentUser.username}'s onboarding run`, playbook.description); - const channel = await Client4.getChannel(playbookRun.channel_id); - const pathname = `/${team.name}/channels/${channel.name}`; - const search = '?forceRHSOpen&openTakeATourDialog'; - navigateToUrl({pathname, search}); - return; - } - if (playbook?.id) { - telemetryEventForPlaybook(playbook.id, 'playbook_dashboard_run_clicked'); - navigateToUrl(`/${team.name || ''}/_playbooks/${playbook?.id || ''}/run`); - } - }; - - useEffect(() => { - const fetchData = async () => { - const playbookId = match.params.playbookId; - if (playbookId) { - try { - const fetchedPlaybook = await clientFetchPlaybook(playbookId); - setPlaybook(fetchedPlaybook!); - setFetchingState(FetchingStateType.fetched); - } catch { - setFetchingState(FetchingStateType.notFound); - } - } - }; - fetchData(); - }, [match.params.playbookId, currentUserId]); - - useEffect(() => { - const fetchData = async () => { - const playbookId = match.params.playbookId; - if (playbookId) { - try { - const fetchedFollowerIds = await clientFetchPlaybookFollowers(playbookId); - setFollowerIds(fetchedFollowerIds); - setIsFollowed(fetchedFollowerIds.includes(currentUserId)); - } catch { - setIsFollowed(false); - } - } - }; - fetchData(); - }, [match.params.playbookId, currentUserId, isFollowed]); - - if (fetchingState === FetchingStateType.loading) { - return null; - } - - if (fetchingState === FetchingStateType.notFound || !playbook) { - return ; - } - - let accessIconClass; - if (playbook.public) { - accessIconClass = 'icon-globe'; - } else { - accessIconClass = 'icon-lock-outline'; - } - - let toolTipText = formatMessage({defaultMessage: 'Select this to automatically receive updates when this playbook is run.'}); - if (isFollowed) { - toolTipText = formatMessage({defaultMessage: 'You automatically receive updates when this playbook is run.'}); - } - - const tooltip = ( - - {toolTipText} - - ); - - const archived = playbook?.delete_at !== 0; - const enableRunPlaybook = !archived && hasPermissionToRunPlaybook; - const [exportHref, exportFilename] = playbookExportProps(playbook); - - return ( - <> - - - - - - {playbook.title} - - - } - > - dispatch(displayEditPlaybookAccessModal(playbook.id))} - > - - - { - const newID = await clientDuplicatePlaybook(playbook.id); - navigateToPluginUrl(`/playbooks/${newID}`); - addToast(formatMessage({defaultMessage: 'Successfully duplicated playbook'})); - telemetryEventForPlaybook(playbook.id, 'playbook_duplicate_clicked_in_playbook'); - }} - > - - - telemetryEventForPlaybook(playbook.id, 'playbook_export_clicked_in_playbook')} - > - - - {!archived && - openDeletePlaybookModal(playbook)} - > - - - - - } - - dispatch(displayEditPlaybookAccessModal(playbook.id))} - > - - {playbook.members.length} - - { - archived && - - } - - - -
- -
-
-
- - - {isTutorialPlaybook ? formatMessage({defaultMessage: 'Start a test run'}) : formatMessage({defaultMessage: 'Run'})} - - {showRunButtonTutorial && - Start a test run to see it in action.'}, {strong: (x) => {x}}), - } : { - title: formatMessage({defaultMessage: 'Ready to run your playbook?'}), - screen: formatMessage({defaultMessage: 'Select Run to see it in action.'}, {strong: (x) => {x}}), - }} - tutorialCategory={TutorialTourCategories.PLAYBOOK_PREVIEW} - step={PlaybookPreviewTutorialSteps.RunButton} - placement='bottom-end' - pulsatingDotPlacement='right' - pulsatingDotTranslate={{x: -90, y: 15}} - autoTour={true} - width={352} - punchOut={punchoutTitleRow} - telemetryTag={`tutorial_tip_Playbook_Preview_${PlaybookPreviewTutorialSteps.RunButton}_RunButton`} - /> - } -
-
- - telemetryEventForPlaybook(playbook.id, 'playbook_preview_tab_clicked')} - > - {formatMessage({defaultMessage: 'Preview'})} - - telemetryEventForPlaybook(playbook.id, 'playbook_usage_tab_clicked')} - > - {formatMessage({defaultMessage: 'Usage'})} - - telemetryEventForPlaybook(playbook.id, 'playbook_metrics_tab_clicked')} - > - {formatMessage({defaultMessage: 'Key metrics'})} - - - - - - - - - - - - - - - - - {modal} - - ); -}; - -const TopContainer = styled.div` - position: sticky; - z-index: 2; - top: 0; - background: var(--center-channel-bg); - width: 100%; - box-shadow: inset 0px -1px 0px rgba(var(--center-channel-color-rgb), 0.16); -`; - -const TitleRow = styled.div` - display: flex; - align-items: center; - margin: 0 32px; - height: 82px; -`; - -const LeftArrow = styled.button` - display: block; - padding: 0; - border: none; - background: transparent; - font-size: 24px; - line-height: 24px; - cursor: pointer; - color: rgba(var(--center-channel-color-rgb), 0.56); - - &:hover { - background: rgba(var(--button-bg-rgb), 0.08); - color: var(--button-bg); - } -`; -const Title = styled.div` - ${RegularHeading} { - } - - font-size: 20px; - line-height: 28px; - color: var(--center-channel-color); - margin-left: 6px; - margin-right: 6px; -`; - -const PrimaryButtonLarger = styled(PrimaryButton)` - padding: 0 16px; - height: 36px; - margin-left: 12px; -`; - -const CheckboxInputStyled = styled(CheckboxInput)` - padding-right: 4px; - padding-left: 4px; - font-size: 14px; - - &:hover { - background-color: transparent; - } -`; - -interface CheckedProps { - checked: boolean; -} - -const SecondaryButtonLargerRightStyled = styled(SecondaryButtonLargerRight) ` - border: 1px solid rgba(var(--center-channel-color-rgb), 0.24); - color: rgba(var(--center-channel-color-rgb), 0.56); - - &:hover:enabled { - background-color: rgba(var(--center-channel-color-rgb), 0.08); - } - - ${(props: CheckedProps) => props.checked && css` - border: 1px solid var(--button-bg); - color: var(--button-bg); - - &:hover:enabled { - background-color: rgba(var(--button-bg-rgb), 0.12); - } - `}`; - -const Navbar = styled.nav` - background: var(--center-channel-bg); - height: 55px; - width: 100%; - box-shadow: inset 0px -1px 0px 0px rgba(var(--center-channel-color-rgb), 0.16); - - display: flex; - flex-direction: row; - padding-left: 80px; - margin: 0; -`; - -const NavItem = styled(NavLink)` - display: flex; - align-items: center; - text-align: center; - padding: 0 25px; - font-weight: 600; - - && { - color: rgba(var(--center-channel-color-rgb), 0.64); - - :hover { - color: var(--button-bg); - } - - :hover, :focus { - text-decoration: none; - } - } -`; - -const RightMarginedIcon = styled(Icon)` - margin-right: 0.5rem; -`; - -const activeNavItemStyle = { - color: 'var(--button-bg)', - boxShadow: 'inset 0px -2px 0px 0px var(--button-bg)', -}; - -export default Playbook; diff --git a/webapp/src/components/backstage/playbooks/playbook_preview.tsx b/webapp/src/components/backstage/playbooks/playbook_preview.tsx deleted file mode 100644 index 882de940e6..0000000000 --- a/webapp/src/components/backstage/playbooks/playbook_preview.tsx +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import styled from 'styled-components'; -import React from 'react'; - -import {PlaybookWithChecklist} from 'src/types/playbook'; - -import renderActions from 'src/components/backstage/playbooks/playbook_preview_actions'; -import renderChecklists from 'src/components/backstage/playbooks/playbook_preview_checklists'; -import renderDescription from 'src/components/backstage/playbooks/playbook_preview_description'; -import renderRetrospective from 'src/components/backstage/playbooks/playbook_preview_retrospective'; -import renderStatusUpdates from 'src/components/backstage/playbooks/playbook_preview_status_updates'; -import Navbar, {SectionID} from 'src/components/backstage/playbooks/playbook_preview_navbar'; - -interface Props { - playbook: PlaybookWithChecklist; - runsInProgress: number; - followerIds: string[]; -} - -/** @deprecated this page and some inner sections will be deprecated in the future. See `playbook_editor/outline.tsx`.*/ -const PlaybookPreview = (props: Props) => { - const description = renderDescription({ - id: SectionID.Description, - playbook: props.playbook, - }); - - const checklists = renderChecklists({ - id: SectionID.Checklists, - playbook: props.playbook, - }); - - const actions = renderActions({ - id: SectionID.Actions, - playbook: props.playbook, - followerIds: props.followerIds, - }); - - const statusUpdates = renderStatusUpdates({ - id: SectionID.StatusUpdates, - playbook: props.playbook, - }); - - const retrospective = renderRetrospective({ - id: SectionID.Retrospective, - playbook: props.playbook, - }); - - return ( - - - {description} - {checklists} - {actions} - {statusUpdates} - {retrospective} - - - - ); -}; - -const Container = styled.main` - height: 100%; - display: flex; - flex-direction: row; - justify-content: center; - flex-grow: 1; - - column-gap: 114px; - - padding-top: 40px; - - background-color: rgba(var(--center-channel-color-rgb),0.04); - - z-index: 1; -`; - -const Content = styled.div` - display: flex; - flex-direction: column; - max-width: 780px; - margin-bottom: 40px; - - flex-grow: 1; -`; - -export default PlaybookPreview; diff --git a/webapp/src/components/backstage/playbooks/playbook_preview_actions.tsx b/webapp/src/components/backstage/playbooks/playbook_preview_actions.tsx deleted file mode 100644 index 5b8cc9faeb..0000000000 --- a/webapp/src/components/backstage/playbooks/playbook_preview_actions.tsx +++ /dev/null @@ -1,269 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import styled from 'styled-components'; -import React from 'react'; -import {useIntl} from 'react-intl'; - -import FormattedMarkdown from 'src/components/formatted_markdown'; -import {PlaybookWithChecklist} from 'src/types/playbook'; - -import {EllipsizedText, TextBadge, ChannelBadge} from 'src/components/backstage/playbooks/playbook_preview_badges'; -import {Card, CardEntry, CardSubEntry} from 'src/components/backstage/playbooks/playbook_preview_cards'; -import Section from 'src/components/backstage/playbooks/playbook_preview_section'; -import ProfileSelector from 'src/components/profile/profile_selector'; -import {UserList} from 'src/components/rhs/rhs_participants'; -import Tooltip from 'src/components/widgets/tooltip'; - -interface Props { - id: string; - playbook: PlaybookWithChecklist; - followerIds: string[]; -} - -const PlaybookPreviewActions = (props: Props) => { - const {formatMessage} = useIntl(); - - // The following booleans control the rendering of each of the CardEntry components in this section, - // hiding them if they don't have any visible subentries. - // If a new CardSubEntry is added or the conditions are changed, these booleans need to be updated. - - const createChannelEnabled = true; - const autofollowsEnabled = props.followerIds.length > 0; - const inviteUsersEnabled = props.playbook.invite_users_enabled && props.playbook.invited_user_ids.length !== 0; - const defaultOwnerEnabled = props.playbook.default_owner_enabled && props.playbook.default_owner_id !== ''; - const broadcastEnabled = props.playbook.broadcast_enabled && props.playbook.broadcast_channel_ids.length !== 0; - const runSummaryEnabled = props.playbook.run_summary_template !== ''; - const webhookOnCreationEnabled = props.playbook.webhook_on_creation_enabled && props.playbook.webhook_on_creation_urls.length !== 0; - - const showRunStartCardEntry = - createChannelEnabled || - inviteUsersEnabled || - broadcastEnabled || - defaultOwnerEnabled || - runSummaryEnabled || - webhookOnCreationEnabled || - autofollowsEnabled; - - const messageOnJoinEnabled = props.playbook.message_on_join_enabled && props.playbook.message_on_join !== ''; - const categorizeChannelEnabled = props.playbook.categorize_channel_enabled && props.playbook.category_name !== ''; - - const showNewMemberCardEntry = - messageOnJoinEnabled || - categorizeChannelEnabled; - - const allCardEntriesEmpty = - !showRunStartCardEntry && - !showNewMemberCardEntry; - - if (allCardEntriesEmpty) { - return null; - } - - return ( -
- - - - {props.playbook.channel_name_template} - - )} - /> - - - - )} - enabled={inviteUsersEnabled} - /> - - - - )} - enabled={autofollowsEnabled} - /> - Promise.resolve([])} - getUsersInTeam={() => Promise.resolve([])} - /> - )} - /> - ( - - ))} - /> - - - - - {props.playbook.webhook_on_creation_urls.map((url) => (

{url}

))} -
-
- - - - - - {props.playbook.category_name} - - )} - /> - -
-
- ); -}; - -const StyledProfileSelector = styled(ProfileSelector)` - margin-top: 0; - height: 20px; - - .Assigned-button { - border-radius: 16px; - max-width: 100%; - height: 20px; - padding: 2px; - padding-right: 6px; - margin-top: 0; - background: rgba(var(--center-channel-color-rgb), 0.08); - - :hover { - background: rgba(var(--center-channel-color-rgb), 0.16); - } - - .image { - width: 16px; - height: 16px; - } - - font-weight: 600; - font-size: 11px; - line-height: 16px; - - display: flex; - align-items: center; - - } - - .name { - color: rgba(var(--center-channel-color-rgb), 0.72); - } -`; - -const UserRow = styled.div` - display: flex; - flex-direction: row; -`; - -const KeywordsExtraInfo = ({keywords}: {keywords: string[]}) => { - const {formatMessage} = useIntl(); - - if (keywords.length === 1) { - return ( - - - {keywords[0]} - - - ); - } - - return ( - - - {formatMessage( - {defaultMessage: '{numKeywords, plural, other {# keywords}}'}, - {numKeywords: keywords.length}, - )} - - - ); -}; - -const ShortTextBadge = styled(TextBadge)` - max-width: 150px; -`; - -export default PlaybookPreviewActions; diff --git a/webapp/src/components/backstage/playbooks/playbook_preview_badges.tsx b/webapp/src/components/backstage/playbooks/playbook_preview_badges.tsx deleted file mode 100644 index bebd885061..0000000000 --- a/webapp/src/components/backstage/playbooks/playbook_preview_badges.tsx +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import styled from 'styled-components'; -import React from 'react'; - -import {PillBox} from 'src/components/widgets/pill'; -import {useChannel} from 'src/hooks/general'; - -const Badge = styled(PillBox)` - font-size: 11px; - height: 20px; - line-height: 16px; - display: flex; - align-items: center; - color: rgba(var(--center-channel-color-rgb), 0.72); - - padding-left: 1px; - padding-right: 8px; - - :not(:last-child) { - margin-right: 8px; - } - - i { - color: rgba(var(--center-channel-color-rgb), 0.72); - margin-top: -1px; - margin-right: 3px; - } - - font-weight: 600; - font-size: 11px; -`; - -export const TextBadge = styled(Badge)` - text-transform: uppercase; - - padding: 0 6px; -`; - -export const EllipsizedText = styled.span` - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -`; - -export const ChannelBadge = ({channelId} : { channelId: string }) => { - const [channel] = useChannel(channelId); - - return ( - - - {channel?.display_name} - - ); -}; - diff --git a/webapp/src/components/backstage/playbooks/playbook_preview_checklists.tsx b/webapp/src/components/backstage/playbooks/playbook_preview_checklists.tsx deleted file mode 100644 index 376e6121d3..0000000000 --- a/webapp/src/components/backstage/playbooks/playbook_preview_checklists.tsx +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {useState} from 'react'; -import {useIntl} from 'react-intl'; - -import styled from 'styled-components'; - -import {Checklist, ChecklistItem as ChecklistItemType, PlaybookWithChecklist} from 'src/types/playbook'; -import Section from 'src/components/backstage/playbooks/playbook_preview_section'; -import {ChecklistItem} from 'src/components/checklist_item/checklist_item'; -import CollapsibleChecklist, {TitleHelpTextWrapper} from 'src/components/collapsible_checklist'; - -interface Props { - id: string; - playbook: PlaybookWithChecklist; -} - -const PlaybookPreviewChecklists = (props: Props) => { - const {formatMessage} = useIntl(); - - const initialArray = Array(props.playbook.checklists.length).fill(false); - const [checklistsCollapsed, setChecklistsCollapsed] = useState(initialArray); - - if (props.playbook.checklists.length === 0) { - return null; - } - - return ( -
- {props.playbook.checklists.map((checklist: Checklist, checklistIndex: number) => ( - { - const newArr = {...checklistsCollapsed}; - newArr[checklistIndex] = newState; - setChecklistsCollapsed(newArr); - }} - titleHelpText={( - - {formatMessage( - {defaultMessage: '{numTasks, number} {numTasks, plural, one {task} other {tasks}}'}, - {numTasks: checklist.items.length}, - )} - - )} - disabledOrRunID={true} - > - - {checklist.items.map((checklistItem: ChecklistItemType, index: number) => { - return ( - - ); - })} - - - ))} -
- ); -}; - -const ChecklistContainer = styled.div` - background-color: var(--center-channel-bg); - padding: 16px 12px; -`; - -export default PlaybookPreviewChecklists; diff --git a/webapp/src/components/backstage/playbooks/playbook_preview_description.tsx b/webapp/src/components/backstage/playbooks/playbook_preview_description.tsx deleted file mode 100644 index 5f9ef74c32..0000000000 --- a/webapp/src/components/backstage/playbooks/playbook_preview_description.tsx +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import {useIntl} from 'react-intl'; - -import styled from 'styled-components'; - -import {useDefaultMarkdownOptions} from 'src/components/formatted_markdown'; -import {PlaybookWithChecklist} from 'src/types/playbook'; -import {messageHtmlToComponent, formatText} from 'src/webapp_globals'; - -import Section from 'src/components/backstage/playbooks/playbook_preview_section'; - -interface Props { - id: string; - playbook: PlaybookWithChecklist; -} - -const PlaybookPreviewDescription = (props: Props) => { - const {formatMessage} = useIntl(); - const markdownOptions = useDefaultMarkdownOptions({team: props.playbook.team_id}); - const renderMarkdown = (msg: string) => messageHtmlToComponent(formatText(msg, markdownOptions), true, {}); - - if (props.playbook.description.trim() === '') { - return null; - } - - return ( -
- - {renderMarkdown(props.playbook.description)} - -
- ); -}; - -const Description = styled.div` - font-size: 14px; - line-height: 20px; - - color: rgba(var(--center-channel-color-rgb), 0.72); - - white-space: pre-wrap; - - p:last-child { - margin-bottom: 0; - } -`; - -export default PlaybookPreviewDescription; diff --git a/webapp/src/components/backstage/playbooks/playbook_preview_navbar.tsx b/webapp/src/components/backstage/playbooks/playbook_preview_navbar.tsx deleted file mode 100644 index 5009118b5e..0000000000 --- a/webapp/src/components/backstage/playbooks/playbook_preview_navbar.tsx +++ /dev/null @@ -1,388 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import styled, {css} from 'styled-components'; -import React, {useEffect, useState} from 'react'; -import {FormattedMessage, useIntl} from 'react-intl'; -import {useRouteMatch} from 'react-router-dom'; - -import Icon from '@mdi/react'; -import {mdiClipboardPlayMultipleOutline} from '@mdi/js'; - -import {navigateToUrl} from 'src/browser_routing'; -import {telemetryEventForPlaybook} from 'src/client'; -import {SecondaryButtonLargerRight} from 'src/components/backstage/playbook_runs/shared'; -import {BackstageID} from 'src/components/backstage/backstage'; -import {useHasPlaybookPermission} from 'src/hooks'; -import {PlaybookPermissionGeneral} from 'src/types/permissions'; -import {PlaybookWithChecklist} from 'src/types/playbook'; -import TutorialTourTip from 'src/components/tutorial/tutorial_tour_tip/tutorial_tour_tip'; -import {PlaybookPreviewTutorialSteps, TutorialTourCategories} from 'src/components/tutorial/tours'; -import {useMeasurePunchouts, useShowTutorialStep} from 'src/components/tutorial/tutorial_tour_tip/hooks'; - -const prefix = 'playbooks-playbookPreview-'; - -export enum SectionID { - Description = 'playbooks-playbookPreview-description', - Checklists = 'playbooks-playbookPreview-checklists', - Actions = 'playbooks-playbookPreview-actions', - StatusUpdates = 'playbooks-playbookPreview-statusUpdates', - Retrospective = 'playbooks-playbookPreview-retrospective', -} - -interface Props { - playbook: PlaybookWithChecklist; - runsInProgress: number; - archived: boolean; - showElements: { - description: boolean, - checklists: boolean, - actions: boolean, - statusUpdates: boolean, - retrospective: boolean, - }; -} - -// Height of the headers in pixels -const headersOffset = 140; - -const PlaybookPreviewNavbar = ({playbook, runsInProgress, archived, showElements}: Props) => { - const {formatMessage} = useIntl(); - const match = useRouteMatch(); - const [activeId, setActiveId] = useState(SectionID.Description); - const punchoutEdit = useMeasurePunchouts(['edit-playbook'], [], {y: -5, height: 10, x: -5, width: 10}); - const punchoutNavbar = useMeasurePunchouts(['playbook-preview-navbar'], [], {y: -5, height: 10, x: -5, width: 10}); - const showEditTutorial = useShowTutorialStep(PlaybookPreviewTutorialSteps.EditButton, TutorialTourCategories.PLAYBOOK_PREVIEW); - const showNavbarTutorial = useShowTutorialStep(PlaybookPreviewTutorialSteps.Navbar, TutorialTourCategories.PLAYBOOK_PREVIEW); - - const hasEditPermissions = useHasPlaybookPermission(PlaybookPermissionGeneral.ManageProperties, playbook); - - const updateActiveSection = () => { - const threshold = (window.innerHeight / 2) - headersOffset; - - let finalId : SectionID | null = null; - let finalPos = Number.NEGATIVE_INFINITY; - - // Get the section whose top border is over the middle of the window (the threshold) and closer to it. - Object.values(SectionID).forEach((id) => { - const top = document.getElementById(id)?.getBoundingClientRect().top || Number.POSITIVE_INFINITY; - const pos = top - headersOffset; - - if (pos < threshold && pos > finalPos) { - finalId = id; - finalPos = pos; - } - }); - - if (finalId !== null) { - setActiveId(finalId); - } - }; - - useEffect(updateActiveSection, []); - - useEffect(() => { - const root = document.getElementById(BackstageID); - - if (root === null) { - return () => { /* do nothing*/ }; - } - - root.addEventListener('scroll', updateActiveSection); - - return () => { - root.removeEventListener('scroll', updateActiveSection); - }; - }, [updateActiveSection]); - - const scrollToSection = (id: SectionID) => { - const idWithoutPrefix = String(id).replace(prefix, ''); - telemetryEventForPlaybook(playbook.id, `playbook_preview_navbar_section_${idWithoutPrefix}_clicked`); - - if (isSectionActive(id)) { - return; - } - - const root = document.getElementById(BackstageID); - const section = document.getElementById(id); - - if (!section || !root) { - return; - } - - const amount = section.getBoundingClientRect().top - headersOffset; - - // If there is no need to scroll, simply set the section item as active - const reachedTop = root.scrollTop === 0; - const reachedBottom = root.scrollHeight - Math.abs(root.scrollTop) === root.clientHeight; - if ((amount > 0 && reachedBottom) || (amount < 0 && reachedTop) || amount === 0) { - setActiveId(id); - return; - } - - root.scrollBy({ - top: amount, - behavior: 'smooth', - }); - - // At this point, we know we are certain scrollBy will generate an actual scroll, - // so we can listen to the 'scroll' event that was fired because of scrollBy - // and set the active ID only when it's finished. - // This is needed because short sections at the bottom may be positioned below - // the middle of the window, so we need to wait for the scroll event to finish - // and manually mark the section as active, instead of relying on the automatic - // updateActiveSection. - let timer : NodeJS.Timeout; - const callback = () => { - clearTimeout(timer); - timer = setTimeout(() => { - setActiveId(id); - root.removeEventListener('scroll', callback); - }, 150); - }; - - root.addEventListener('scroll', callback, {passive: true}); - }; - - const isSectionActive = (id: SectionID) => { - return activeId === id; - }; - - const Item = generateItemComponent(isSectionActive, scrollToSection); - - return ( - - {showEditTutorial && - } - screen={} - tutorialCategory={TutorialTourCategories.PLAYBOOK_PREVIEW} - step={PlaybookPreviewTutorialSteps.EditButton} - placement='left' - pulsatingDotPlacement='left' - pulsatingDotTranslate={{x: -10, y: -135}} - autoTour={true} - width={370} - punchOut={punchoutEdit} - telemetryTag={`tutorial_tip_Playbook_Preview_${PlaybookPreviewTutorialSteps.EditButton}_EditButton`} - /> - } - {showNavbarTutorial && - } - screen={} - tutorialCategory={TutorialTourCategories.PLAYBOOK_PREVIEW} - step={PlaybookPreviewTutorialSteps.Navbar} - placement='left' - pulsatingDotPlacement='left' - pulsatingDotTranslate={{x: -10, y: -50}} - autoTour={true} - width={360} - punchOut={punchoutNavbar} - telemetryTag={`tutorial_tip_Playbook_Preview_${PlaybookPreviewTutorialSteps.Navbar}_Navbar`} - /> - } - navigateToUrl(match.url.replace('/preview', '/edit'))} - disabled={!hasEditPermissions || archived} - title={hasEditPermissions ? formatMessage({defaultMessage: 'Edit'}) : formatMessage({defaultMessage: 'You do not have permissions'})} - id='edit-playbook' - data-testid='edit-playbook' - > - - {formatMessage({defaultMessage: 'Edit'})} - -
-
- {formatMessage({defaultMessage: 'In this playbook'})} -
- - - - - - - -
- -
- ); -}; - -const Wrapper = styled.nav` - width: 172px; - - position: sticky; - align-self: flex-start; - top: 116px; -`; - -const EditButton = styled(SecondaryButtonLargerRight)` - width: 100%; - justify-content: center; - margin-bottom: 16px; - font-size: 14px; -`; - -const Header = styled.div` - height: 32px; - border-bottom: 1px solid rgba(var(--center-channel-color-rgb), 0.16); - text-transform: uppercase; - - font-weight: 600; - font-size: 11px; - line-height: 16px; - - color: rgba(var(--center-channel-color-rgb), 0.56); - - padding-left: 12px; - padding-top: 4px; - - margin-bottom: 8px; -`; - -const Items = styled.div` - display: flex; - flex-direction: column; - margin-bottom: 16px; -`; - -const generateItemComponent = (isSectionActive: (id: SectionID) => boolean, scrollToSection: (id: SectionID) => void) => { - return (props: {id: SectionID, iconName: string, title: string, show: boolean}) => { - if (!props.show) { - return null; - } - - return ( - scrollToSection(props.id)} - > - - {props.title} - - ); - }; -}; - -const ItemWrapper = styled.div<{active: boolean}>` - display: flex; - flex-direction: row; - align-items: center; - padding: 8px 12px; - cursor: pointer; - - border-radius: 4px; - - margin: 0; - - :not(:last-child) { - margin-bottom: 8px; - } - - font-weight: 600; - font-size: 14px; - line-height: 14px; - - background: transparent; - color: rgba(var(--center-channel-color-rgb), 0.56); - - ${({active}) => active && css` - background: rgba(var(--button-bg-rgb), 0.08); - color: var(--button-bg); - `} - - :hover { - background: rgba(var(--center-channel-color-rgb), 0.08); - color: rgba(var(--center-channel-color-rgb), 0.72); - } - - i { - margin-right: 2px; - } -`; - -const UsageButton = ({playbookId, activeRuns}: {playbookId: string, activeRuns: number}) => { - const match = useRouteMatch(); - const {formatMessage} = useIntl(); - - const onClick = () => { - telemetryEventForPlaybook(playbookId, 'playbook_preview_navbar_runs_in_progress_button_clicked'); - navigateToUrl(match.url.replace('/preview', '/usage')); - }; - - return ( - - - {formatMessage( - {defaultMessage: '{activeRuns, number} {activeRuns, plural, one {run} other {runs}} in progress'}, - {activeRuns}, - )} - - ); -}; - -const UsageButtonWrapper = styled.div` - display: flex; - flex-direction: row; - align-items: center; - padding: 8px 10px; - cursor: pointer; - - background: rgba(var(--center-channel-color-rgb), 0.04); - border-radius: 4px; - - font-size: 14px; - line-height: 20px; - - color: rgba(var(--center-channel-color-rgb), 0.64); - - i { - margin-left: auto; - opacity: 0.48; - } - - svg { - margin-right: 7px; - } - - :hover { - background: rgba(var(--center-channel-color-rgb), 0.08); - color: rgba(var(--center-channel-color-rgb), 0.72); - } -`; - -export default PlaybookPreviewNavbar; diff --git a/webapp/src/components/backstage/playbooks/playbook_preview_retrospective.tsx b/webapp/src/components/backstage/playbooks/playbook_preview_retrospective.tsx deleted file mode 100644 index d660d1cfed..0000000000 --- a/webapp/src/components/backstage/playbooks/playbook_preview_retrospective.tsx +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import {useIntl} from 'react-intl'; - -import {Duration} from 'luxon'; - -import FormattedMarkdown from 'src/components/formatted_markdown'; -import {useAllowRetrospectiveAccess} from 'src/hooks'; -import {PlaybookWithChecklist} from 'src/types/playbook'; - -import {TextBadge} from 'src/components/backstage/playbooks/playbook_preview_badges'; -import {Card, CardEntry, CardSubEntry} from 'src/components/backstage/playbooks/playbook_preview_cards'; -import Section from 'src/components/backstage/playbooks/playbook_preview_section'; -import {formatDuration} from 'src/components/formatted_duration'; - -interface Props { - id: string; - playbook: PlaybookWithChecklist; -} - -const PlaybookPreviewRetrospective = (props: Props) => { - const {formatMessage} = useIntl(); - const retrospectiveAccess = useAllowRetrospectiveAccess(); - - if (!retrospectiveAccess || !props.playbook.retrospective_enabled) { - return null; - } - - return ( -
- - - {props.playbook.retrospective_reminder_interval_seconds === 0 ? 'ONCE' : formatDuration(Duration.fromObject({seconds: props.playbook.retrospective_reminder_interval_seconds}), 'long')} - - )} - enabled={true} - > - - - - - -
- ); -}; - -export default PlaybookPreviewRetrospective; diff --git a/webapp/src/components/backstage/playbooks/playbook_preview_section.tsx b/webapp/src/components/backstage/playbooks/playbook_preview_section.tsx deleted file mode 100644 index 615999e7b3..0000000000 --- a/webapp/src/components/backstage/playbooks/playbook_preview_section.tsx +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import styled from 'styled-components'; -import React from 'react'; - -interface Props { - id: string; - title: string; - children?: React.ReactNode; -} - -const Section = ({id, title, children}: Props) => { - return ( - - {title} - {children} - - ); -}; - -const Wrapper = styled.div` - :not(:last-child) { - margin-bottom: 40px; - } -`; - -const Title = styled.div` - font-family: Metropolis, sans-serif; - font-size: 20px; - font-weight: 600; - line-height: 28px; - - margin-bottom: 16px; -`; - -export default Section; diff --git a/webapp/src/components/backstage/playbooks/playbook_preview_status_updates.tsx b/webapp/src/components/backstage/playbooks/playbook_preview_status_updates.tsx deleted file mode 100644 index ec5ec12119..0000000000 --- a/webapp/src/components/backstage/playbooks/playbook_preview_status_updates.tsx +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import {useIntl} from 'react-intl'; - -import {Duration} from 'luxon'; - -import FormattedMarkdown from 'src/components/formatted_markdown'; -import {PlaybookWithChecklist} from 'src/types/playbook'; - -import {TextBadge, ChannelBadge} from 'src/components/backstage/playbooks/playbook_preview_badges'; -import {Card, CardEntry, CardSubEntry} from 'src/components/backstage/playbooks/playbook_preview_cards'; -import Section from 'src/components/backstage/playbooks/playbook_preview_section'; -import {formatDuration} from 'src/components/formatted_duration'; - -interface Props { - id: string; - playbook: PlaybookWithChecklist; -} - -const PlaybookPreviewStatusUpdates = (props: Props) => { - const {formatMessage} = useIntl(); - - // The following booleans control the rendering of each of the CardEntry comopnents in this section, - // hiding them if they don't have any visible subentries. - // If a new CardSubEntry is added or the conditions are changed, these booleans need to be updated. - - const updateReminderEnabled = props.playbook.reminder_timer_default_seconds !== 0; - const updateTemplateEnabled = props.playbook.reminder_message_template !== ''; - - const showReminderCardEntry = - updateReminderEnabled || - updateTemplateEnabled; - - const broadcastEnabled = props.playbook.broadcast_enabled && props.playbook.broadcast_channel_ids.length !== 0; - const webhookOnStatusUpdateEnabled = props.playbook.webhook_on_status_update_enabled && props.playbook.webhook_on_status_update_urls.length !== 0; - - const showUpdatePostCardEntryemptyUpdatePostedEntry = - broadcastEnabled || - webhookOnStatusUpdateEnabled; - - const allEmpty = - !showReminderCardEntry && - !showUpdatePostCardEntryemptyUpdatePostedEntry; - - if (allEmpty || !props.playbook.status_update_enabled) { - return null; - } - - return ( -
- - - {formatDuration(Duration.fromObject({seconds: props.playbook.reminder_timer_default_seconds}), 'long')} - - )} - enabled={showReminderCardEntry} - > - - - - - - ( - - ))} - /> - - {props.playbook.webhook_on_status_update_urls.map((url) => (

{url}

))} -
-
-
-
- ); -}; - -export default PlaybookPreviewStatusUpdates; diff --git a/webapp/src/components/backstage/playbooks/stats_view.tsx b/webapp/src/components/backstage/stats_view.tsx similarity index 97% rename from webapp/src/components/backstage/playbooks/stats_view.tsx rename to webapp/src/components/backstage/stats_view.tsx index 7a3ab54ac6..3779e353bf 100644 --- a/webapp/src/components/backstage/playbooks/stats_view.tsx +++ b/webapp/src/components/backstage/stats_view.tsx @@ -8,24 +8,27 @@ import {FormattedNumber, useIntl} from 'react-intl'; import {DateTime} from 'luxon'; -import LineGraph from 'src/components/backstage/playbooks/line_graph'; import { DefaultFetchPlaybookRunsParamsTime, fetchParamsTimeEqual, FetchPlaybookRunsParams, } from 'src/types/playbook_run'; -import BarGraph from 'src/components/backstage/playbooks/bar_graph'; import ClipboardsPlay from 'src/components/assets/icons/clipboards_play'; import Profiles from 'src/components/assets/icons/profiles'; import ClipboardsCheckmark from 'src/components/assets/icons/clipboards_checkmark'; import {PlaybookStats} from 'src/types/stats'; import {useAllowPlaybookStatsView} from 'src/hooks'; -import UpgradePlaybookPlaceholder - from 'src/components/backstage/playbooks/upgrade_playbook_placeholder'; + import Pill from 'src/components/widgets/pill'; import {Timestamp} from 'src/webapp_globals'; import {DateTimeFormats} from 'src/constants'; +import UpgradePlaybookPlaceholder from './upgrade_playbook_placeholder'; + +import BarGraph from './bar_graph'; + +import LineGraph from './line_graph'; + interface Props { stats: PlaybookStats fetchParams: FetchPlaybookRunsParams diff --git a/webapp/src/components/backstage/playbooks/upgrade_playbook_placeholder.tsx b/webapp/src/components/backstage/upgrade_playbook_placeholder.tsx similarity index 100% rename from webapp/src/components/backstage/playbooks/upgrade_playbook_placeholder.tsx rename to webapp/src/components/backstage/upgrade_playbook_placeholder.tsx diff --git a/webapp/src/components/checklist_item_input.tsx b/webapp/src/components/checklist_item_input.tsx deleted file mode 100644 index 826f6d7d4e..0000000000 --- a/webapp/src/components/checklist_item_input.tsx +++ /dev/null @@ -1,203 +0,0 @@ -import React, {useState} from 'react'; -import {useIntl} from 'react-intl'; - -import styled from 'styled-components'; - -import {TertiaryButton, GrayTertiaryButton} from 'src/components/assets/buttons'; -import {ChecklistItem, ChecklistItemState} from 'src/types/playbook'; - -import CommandInput from './command_input'; -import {StyledTextarea} from './backstage/styles'; -import {InputTrashIcon, BaseInput} from './assets/inputs'; - -interface ChecklistItemCommandProps { - command: string; - setCommand: (command: string) => void; - autocompleteOnBottom: boolean; -} - -const SlashCommandContainer = styled.div` - flex: 0.5; - margin-left: 16px; -`; - -export const ChecklistItemCommand = (props: ChecklistItemCommandProps) => { - const {formatMessage} = useIntl(); - - const [commandOpen, setCommandOpen] = useState(props.command.length > 0); - - const setCommand = (command: string) => { - if (command === '') { - setCommandOpen(false); - } - props.setCommand(command); - }; - - let slashCommandBox = ( - { - setCommandOpen(true); - }} - > - - {formatMessage({defaultMessage: 'Add a slash command'})} - - ); - - if (commandOpen) { - slashCommandBox = ( - - ); - } - - return ( - - {slashCommandBox} - - ); -}; - -const DescriptionContainer = styled.div` - position: relative; - width: 100%; - margin: 16px 0 0 0; - line-height: 40px; -`; - -interface ChecklistItemDescriptionProps { - description: string; - setDescription: (description: string) => void; -} - -export const ChecklistItemDescription = (props: ChecklistItemDescriptionProps) => { - const {formatMessage} = useIntl(); - - const [description, setDescription] = useState(props.description); - const [descriptionOpen, setDescriptionOpen] = useState(props.description.length > 0); - const [hover, setHover] = useState(false); - - const save = () => { - props.setDescription(description); - }; - - let descriptionBox = ( - { - setDescriptionOpen(true); - }} - > - - {formatMessage({defaultMessage: 'Add optional description'})} - - ); - if (descriptionOpen) { - descriptionBox = ( - <> - { - setDescription(e.target.value); - }} - /> - - { - setDescriptionOpen(false); - setDescription(''); - props.setDescription(''); - }} - /> - - - ); - } - - return ( - setHover(true)} - onMouseLeave={() => setHover(false)} - > - {descriptionBox} - - ); -}; - -interface ChecklistItemButtonProps { - onChange: (item: ChecklistItemState) => void; - item: ChecklistItem; - disabled: boolean; -} - -export const ChecklistItemButton = (props: ChecklistItemButtonProps) => { - const isChecked = props.item.state === ChecklistItemState.Closed; - - return ( - { - if (isChecked) { - props.onChange(ChecklistItemState.Open); - } else { - props.onChange(ChecklistItemState.Closed); - } - }} - />); -}; - -const ChecklistItemInput = styled.input` - :disabled:hover { - cursor: default; - } -`; - -interface ChecklistItemTitleProps { - title: string; - setTitle: (title: string) => void; -} - -const StyledBaseInput = styled(BaseInput)` - flex: 0.5; -`; - -export const ChecklistItemTitle = (props: ChecklistItemTitleProps) => { - const {formatMessage} = useIntl(); - - const [title, setTitle] = useState(props.title); - - const save = () => { - if (title.trim().length === 0) { - // Keep the original title from the props. - setTitle(props.title); - return; - } - - props.setTitle(title); - }; - - return ( - setTitle(e.target.value)} - onBlur={save} - autoFocus={!title} - onKeyDown={(e) => { - if (e.key === 'Enter' || e.key === 'Escape') { - save(); - } - }} - /> - ); -}; diff --git a/webapp/src/components/collapsible_checklist.tsx b/webapp/src/components/collapsible_checklist.tsx deleted file mode 100644 index 627227723e..0000000000 --- a/webapp/src/components/collapsible_checklist.tsx +++ /dev/null @@ -1,329 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {useRef, useState} from 'react'; -import styled from 'styled-components'; -import {DraggableProvided} from 'react-beautiful-dnd'; - -import {FormattedMessage, useIntl} from 'react-intl'; - -import { - clientRenameChecklist, -} from 'src/client'; -import {ChecklistItem, ChecklistItemState} from 'src/types/playbook'; -import TextWithTooltipWhenEllipsis from 'src/components/widgets/text_with_tooltip_when_ellipsis'; -import HoverMenu from 'src/components/collapsible_checklist_hover_menu'; -import {CancelSaveButtons} from 'src/components/checklist_item/inputs'; - -export interface Props { - title: string; - index: number; - numChecklists: number; - collapsed: boolean; - setCollapsed: (newState: boolean) => void; - items: ChecklistItem[]; - children: React.ReactNode; - disabledOrRunID: true | string; - titleHelpText?: React.ReactNode; - draggableProvided?: DraggableProvided; -} - -const CollapsibleChecklist = ({ - title, - index, - numChecklists, - collapsed, - setCollapsed, - items, - children, - disabledOrRunID, - titleHelpText, - draggableProvided, -}: Props) => { - const titleRef = useRef(null); - const [showMenu, setShowMenu] = useState(false); - const [isRenaming, setIsRenaming] = useState(false); - const [newChecklistTitle, setNewChecklistTitle] = useState(title); - - const icon = collapsed ? 'icon-chevron-right' : 'icon-chevron-down'; - const [completed, total] = tasksCompleted(items); - const percentage = total === 0 ? 0 : (completed / total) * 100; - - const disabled = typeof disabledOrRunID !== 'string'; - const playbookRunID = typeof disabledOrRunID === 'string' ? disabledOrRunID : ''; - - let borderProps = {}; - if (draggableProvided) { - borderProps = { - ...draggableProvided.draggableProps, - ref: draggableProvided.innerRef, - }; - } - - const areAllTasksSkipped = items.every((item) => item.state === ChecklistItemState.Skip); - const isChecklistSkipped = items.length > 0 && areAllTasksSkipped; - - let titleText = ( - - ); - if (isChecklistSkipped) { - titleText = ({title}); - } - - let titleComp = ( - - {titleText} - - ); - if (isRenaming) { - titleComp = ( - { - setIsRenaming(false); - setNewChecklistTitle(title); - }} - onSave={() => { - clientRenameChecklist(playbookRunID, index, newChecklistTitle); - setTimeout(() => setNewChecklistTitle(''), 300); - setIsRenaming(false); - }} - /> - ); - } - - const renderTitleHelpText = () => { - if (isRenaming) { - return null; - } - if (titleHelpText) { - return titleHelpText; - } - return ( - - - - ); - }; - - const renderHoverMenu = () => { - if (isRenaming || disabled) { - return null; - } - if (!showMenu) { - return null; - } - return ( - setIsRenaming(true)} - dragHandleProps={draggableProvided?.dragHandleProps} - isChecklistSkipped={isChecklistSkipped} - /> - ); - }; - - return ( - - - !isRenaming && setCollapsed(!collapsed)} - onMouseEnter={() => setShowMenu(true)} - onMouseLeave={() => setShowMenu(false)} - > - - {titleComp} - {renderTitleHelpText()} - {renderHoverMenu()} - - - - - - {!collapsed && children} - - ); -}; - -const StrikeThrough = styled.text` - text-decoration: line-through; -`; - -const Border = styled.div` - margin-bottom: 12px; - background-color: rgba(var(--center-channel-color-rgb), 0.04); - border-radius: 4px; -`; - -const ProgressBackground = styled.div` - position: relative; - - &:after { - border-bottom: 2px solid rgba(var(--center-channel-color-rgb), 0.08); - content: ''; - display: block; - width: 100%; - } -`; - -const ProgressLine = styled.div<{width: number}>` - position: absolute; - width: 100%; - - &:after { - border-bottom: 2px solid var(--online-indicator); - content: ''; - display: block; - width: ${(props) => props.width}%; - } -`; - -export const HorizontalBG = styled.div<{checklistIndex: number, numChecklists: number}>` - background-color: var(--center-channel-bg); - z-index: ${({checklistIndex, numChecklists}) => 1 + (numChecklists - checklistIndex)}; - position: sticky; - top: 48px; // height of rhs_checklists MainTitle -`; - -const Horizontal = styled.div` - background-color: rgba(var(--center-channel-color-rgb), 0.04); - border-radius: 4px 4px 0 0; - border: 1px solid rgba(var(--center-channel-color-rgb), 0.08); - display: flex; - flex-direction: row; - align-items: baseline; - cursor: pointer; -`; - -const Icon = styled.i` - position: relative; - top: 2px; - margin: 0 0 0 6px; - - font-size: 18px; - color: rgba(var(--center-channel-color-rgb), 0.56); - - ${Horizontal}:hover & { - color: rgba(var(--center-channel-color-rgb), 0.64); - } -`; - -const Title = styled.div` - margin: 0 6px 0 0; - - font-weight: 600; - font-size: 14px; - line-height: 44px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - color: rgba(var(--center-channel-color-rgb), 0.72); - - ${Horizontal}:hover & { - color: rgba(var(--center-channel-color-rgb), 0.80); - } -`; - -export const TitleHelpTextWrapper = styled.div` - margin-right: 16px; - - font-weight: 600; - font-size: 12px; - white-space: nowrap; - color: rgba(var(--center-channel-color-rgb), 0.48); - - ${Horizontal}:hover & { - color: rgba(var(--center-channel-color-rgb), 0.56); - } -`; - -const tasksCompleted = (items: ChecklistItem[]) => { - let completed = 0; - let total = 0; - - for (const item of items) { - if (item.state !== ChecklistItemState.Skip) { - total++; - } - if (item.state === ChecklistItemState.Closed) { - completed++; - } - } - - return [completed, total]; -}; - -export default CollapsibleChecklist; - -interface ChecklistInputProps { - onCancel: () => void; - onSave: () => void; - title: string; - setTitle: (title: string) => void; -} - -export const ChecklistInputComponent = (props: ChecklistInputProps) => { - const {formatMessage} = useIntl(); - - return ( - <> - props.setTitle(e.target.value)} - value={props.title} - autoFocus={true} - onFocus={(e) => { - const val = e.target.value; - e.target.value = ''; - e.target.value = val; - }} - placeholder={formatMessage({defaultMessage: 'Add checklist name'})} - onKeyDown={(e) => { - if (e.key === 'Enter') { - props.onSave(); - } else if (e.key === 'Escape') { - props.onCancel(); - } - }} - /> - - - ); -}; - -const ChecklistInput = styled.input` - height: 32px; - background: var(--center-channel-bg); - border: 1px solid var(--center-channel-color-16); - box-sizing: border-box; - border-radius: 4px; - width: 100%; - padding: 0 6px; - - font-weight: 600; - font-size: 14px; - line-height: 44px; - - ::placeholder { - font-weight: 400; - font-style: italic; - } -`; diff --git a/webapp/src/components/collapsible_checklist_hover_menu.tsx b/webapp/src/components/collapsible_checklist_hover_menu.tsx deleted file mode 100644 index 789b50861b..0000000000 --- a/webapp/src/components/collapsible_checklist_hover_menu.tsx +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import styled from 'styled-components'; -import {useIntl} from 'react-intl'; -import {DraggableProvidedDragHandleProps} from 'react-beautiful-dnd'; - -import { - clientSkipChecklist, - clientRestoreChecklist, - clientDuplicateChecklist, -} from 'src/client'; -import {HamburgerButton} from 'src/components/assets/icons/three_dots_icon'; -import DotMenu, {DotMenuButton, DropdownMenu, DropdownMenuItem} from 'src/components/dot_menu'; -import {HoverMenuButton} from 'src/components/rhs/rhs_shared'; - -export interface Props { - playbookRunID: string; - checklistIndex: number; - checklistTitle: string; - onRenameChecklist: () => void; - dragHandleProps: DraggableProvidedDragHandleProps | undefined; - isChecklistSkipped: boolean; -} - -const CollapsibleChecklistHoverMenu = (props: Props) => { - const {formatMessage} = useIntl(); - - let lastComponent = ( - { - e.stopPropagation(); - clientRestoreChecklist(props.playbookRunID, props.checklistIndex); - }} - /> - ); - if (!props.isChecklistSkipped) { - lastComponent = ( - } - dotMenuButton={StyledDotMenuButton} - dropdownMenu={StyledDropdownMenu} - placement='bottom-end' - title={formatMessage({defaultMessage: 'More'})} - > - - - {formatMessage({defaultMessage: 'Rename checklist'})} - - { - clientDuplicateChecklist(props.playbookRunID, props.checklistIndex); - }} - > - - {formatMessage({defaultMessage: 'Duplicate checklist'})} - - { - clientSkipChecklist(props.playbookRunID, props.checklistIndex); - }} - > - - {formatMessage({defaultMessage: 'Skip checklist'})} - - - ); - } - - return ( - - {props.dragHandleProps && - - } - {lastComponent} - - ); -}; - -const Handle = styled(HoverMenuButton)` - border-radius: 4px; - margin-right: 8px; - &:hover { - background: rgba(var(--center-channel-color-rgb), 0.08) - } -`; - -const ButtonRow = styled.div` - display: flex; - flex-direction: row; - align-items: center; - - margin-left: auto; - margin-right: 8px; -`; - -export const DotMenuIcon = styled(HamburgerButton)` - font-size: 14.4px; -`; - -export const StyledDotMenuButton = styled(DotMenuButton)` - align-items: center; - justify-content: center; - - width: 28px; - height: 28px; -`; - -export const StyledDropdownMenu = styled(DropdownMenu)` - padding: 8px 0; -`; - -export const StyledDropdownMenuItem = styled(DropdownMenuItem)` - padding: 8px 0; -`; - -const StyledDropdownMenuItemRed = styled(DropdownMenuItem)` - padding: 8px 0; - && { - color: #D24B4E; - } - &&:hover { - color: #D24B4E; - } -`; - -export const DropdownIcon = styled.i` - color: rgba(var(--center-channel-color-rgb), 0.56); - margin-right: 11px; -`; - -const DropdownIconRed = styled.i` - color: #D24B4E; - margin-right: 11px; -`; - -export default CollapsibleChecklistHoverMenu; diff --git a/webapp/src/components/rhs/rhs_checklists_add_dialog.tsx b/webapp/src/components/rhs/rhs_checklists_add_dialog.tsx deleted file mode 100644 index 03083930dc..0000000000 --- a/webapp/src/components/rhs/rhs_checklists_add_dialog.tsx +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {useState} from 'react'; -import {useIntl} from 'react-intl'; -import styled from 'styled-components'; - -import {Modal} from 'react-bootstrap'; - -import {ChecklistItem} from 'src/types/playbook'; -import {clientAddChecklist} from 'src/client'; -import {FormContainer, ModalField} from 'src/components/assets/modal'; -import GenericModal from 'src/components/widgets/generic_modal'; - -// disable all react-beautiful-dnd development warnings -const AddChecklistDialog = ({playbookRunID, show, onHide}: {playbookRunID: string, show: boolean, onHide: () => void}) => { - const {formatMessage} = useIntl(); - const [title, setTitle] = useState(''); - - const onConfirm = () => { - clientAddChecklist(playbookRunID, {title, items: [] as ChecklistItem[]}); - setTimeout(() => setTitle(''), 300); - }; - - return ( - - - ) => setTitle(e.target.value)} - autoFocus={true} - /> - - - ); -}; - -const AddChecklistDialogHeader = styled(Modal.Header)` - &&&& { - margin-bottom: 22px; - } -`; - -export default AddChecklistDialog; diff --git a/webapp/src/components/rhs/rhs_checklists_delete_dialog.tsx b/webapp/src/components/rhs/rhs_checklists_delete_dialog.tsx deleted file mode 100644 index 1c867c943f..0000000000 --- a/webapp/src/components/rhs/rhs_checklists_delete_dialog.tsx +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import {useIntl} from 'react-intl'; -import styled from 'styled-components'; - -import {clientRemoveChecklist} from 'src/client'; -import GenericModal, {DefaultFooterContainer} from 'src/components/widgets/generic_modal'; - -interface Props { - playbookRunID: string; - checklistIndex: number; - show: boolean; - onHide: () => void; -} - -const DeleteChecklistDialog = ({playbookRunID, checklistIndex, show, onHide}: Props) => { - const {formatMessage} = useIntl(); - - return ( - clientRemoveChecklist(playbookRunID, checklistIndex)} - handleCancel={onHide} - onHide={onHide} - components={{FooterContainer: DeleteModalFooter}} - isConfirmDestructive={true} - > - - {formatMessage({defaultMessage: 'Delete checklist'})} - - - {formatMessage({defaultMessage: 'Are you sure you want to delete this checklist? It will be removed from this run but will not affect the playbook.'})} - - - ); -}; - -const DeleteModal = styled(GenericModal)` - width: 512px; -`; - -const DeleteModalTitle = styled.h1` - font-family: Metropolis; - font-size: 22px; - line-height: 28px; - - text-align: center; - color: var(--center-channel-color); -`; - -const DeleteModalContent = styled.div` - font-size: 14px; - text-align: center; - - padding: 0 16px; - margin: 0; - margin-top: 8px; - margin-bottom: 12px; -`; - -const DeleteModalFooter = styled(DefaultFooterContainer)` - align-items: center; - margin-bottom: 24px; -`; - -export default DeleteChecklistDialog; diff --git a/webapp/src/components/rhs/rhs_checklists_rename_dialog.tsx b/webapp/src/components/rhs/rhs_checklists_rename_dialog.tsx deleted file mode 100644 index 97cba598ad..0000000000 --- a/webapp/src/components/rhs/rhs_checklists_rename_dialog.tsx +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {useEffect, useState} from 'react'; -import {useIntl} from 'react-intl'; -import styled from 'styled-components'; - -import {Modal} from 'react-bootstrap'; - -import {clientRenameChecklist} from 'src/client'; -import {FormContainer, ModalField} from 'src/components/assets/modal'; -import GenericModal from 'src/components/widgets/generic_modal'; - -interface Props { - playbookRunID: string; - checklistNumber: number; - initialTitle: string; - show: boolean; - onHide: () => void; -} - -const RenameChecklistDialog = ({playbookRunID, checklistNumber, initialTitle, show, onHide}: Props) => { - const {formatMessage} = useIntl(); - const [title, setTitle] = useState(initialTitle); - - useEffect(() => { - setTitle(initialTitle); - }, [initialTitle]); - - const onConfirm = () => { - clientRenameChecklist(playbookRunID, checklistNumber, title); - }; - - return ( - - - ) => setTitle(e.target.value)} - autoFocus={true} - /> - - - ); -}; - -const AddChecklistDialogHeader = styled(Modal.Header)` - &&&& { - margin-bottom: 22px; - } -`; - -export default RenameChecklistDialog;