diff --git a/api/chalicelib/core/feature_flags.py b/api/chalicelib/core/feature_flags.py index 099eb2f9e..45ace2964 100644 --- a/api/chalicelib/core/feature_flags.py +++ b/api/chalicelib/core/feature_flags.py @@ -156,7 +156,7 @@ def create_feature_flag(project_id: int, user_id: int, feature_flag_data: schema """ if variants_len > 0: - variants_query = f""", + variants_query = f"""{conditions_len > 0 and "," or ""} inserted_variants AS ( INSERT INTO feature_flags_variants(feature_flag_id, value, description, rollout_percentage, payload) VALUES {",".join([f"((SELECT feature_flag_id FROM inserted_flag)," diff --git a/api/schemas.py b/api/schemas.py index 3606ded4f..e30a714c9 100644 --- a/api/schemas.py +++ b/api/schemas.py @@ -1441,7 +1441,7 @@ class FeatureFlagSchema(BaseModel): flag_type: FeatureFlagType = Field(default=FeatureFlagType.single_variant) is_persist: Optional[bool] = Field(default=False) is_active: Optional[bool] = Field(default=True) - conditions: List[FeatureFlagCondition] = Field(default=[], min_items=1) + conditions: List[FeatureFlagCondition] = Field(default=[]) variants: List[FeatureFlagVariant] = Field(default=[]) class Config: diff --git a/frontend/app/components/Client/Client.js b/frontend/app/components/Client/Client.tsx similarity index 68% rename from frontend/app/components/Client/Client.js rename to frontend/app/components/Client/Client.tsx index ebf9db834..44722153f 100644 --- a/frontend/app/components/Client/Client.js +++ b/frontend/app/components/Client/Client.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { withRouter } from 'react-router-dom'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; import { Switch, Route, Redirect } from 'react-router'; import { CLIENT_TABS, client as clientRoute } from 'App/routes'; @@ -17,17 +17,15 @@ import Notifications from './Notifications'; import Roles from './Roles'; import SessionsListingSettings from 'Components/Client/SessionsListingSettings'; -@withRouter -export default class Client extends React.PureComponent { - constructor(props) { - super(props); - } +interface MatchParams { + activeTab?: string; +} - setTab = (tab) => { - this.props.history.push(clientRoute(tab)); - }; +const Client: React.FC> = ({ match }) => { + const { activeTab } = match.params; + const isIntegrations = activeTab === CLIENT_TABS.INTEGRATIONS; - renderActiveTab = () => ( + const renderActiveTab = () => ( @@ -43,23 +41,18 @@ export default class Client extends React.PureComponent { ); - render() { - const { - match: { - params: { activeTab }, - }, - } = this.props; - return ( -
-
- -
-
-
- {activeTab && this.renderActiveTab()} -
-
+ return ( +
+
+
- ); - } -} +
+
+ {activeTab && renderActiveTab()} +
+
+
+ ); +}; + +export default withRouter(Client); diff --git a/frontend/app/components/Client/Integrations/BugsnagForm/BugsnagForm.js b/frontend/app/components/Client/Integrations/BugsnagForm/BugsnagForm.js index 15d8ddef1..f58154f91 100644 --- a/frontend/app/components/Client/Integrations/BugsnagForm/BugsnagForm.js +++ b/frontend/app/components/Client/Integrations/BugsnagForm/BugsnagForm.js @@ -3,31 +3,39 @@ import { tokenRE } from 'Types/integrations/bugsnagConfig'; import IntegrationForm from '../IntegrationForm'; import ProjectListDropdown from './ProjectListDropdown'; import DocLink from 'Shared/DocLink/DocLink'; +import IntegrationModalCard from 'Components/Client/Integrations/IntegrationModalCard'; const BugsnagForm = (props) => ( -
-

Bugsnag

-
-
How to integrate Bugsnag with OpenReplay and see backend errors alongside session recordings.
- -
- tokenRE.test(config.authorizationToken), - component: ProjectListDropdown, - }, - ]} - /> +
+ + +
+
How it works?
+
    +
  1. Generate Bugsnag Auth Token
  2. +
  3. Enter the token below
  4. +
  5. Propagate openReplaySessionToken
  6. +
+
+ tokenRE.test(config.authorizationToken), + component: ProjectListDropdown + } + ]} + /> +
); BugsnagForm.displayName = 'BugsnagForm'; diff --git a/frontend/app/components/Client/Integrations/CloudwatchForm/CloudwatchForm.js b/frontend/app/components/Client/Integrations/CloudwatchForm/CloudwatchForm.js index bd9604b01..ca4e6ae3b 100644 --- a/frontend/app/components/Client/Integrations/CloudwatchForm/CloudwatchForm.js +++ b/frontend/app/components/Client/Integrations/CloudwatchForm/CloudwatchForm.js @@ -4,43 +4,51 @@ import IntegrationForm from '../IntegrationForm'; import LogGroupDropdown from './LogGroupDropdown'; import RegionDropdown from './RegionDropdown'; import DocLink from 'Shared/DocLink/DocLink'; +import IntegrationModalCard from 'Components/Client/Integrations/IntegrationModalCard'; const CloudwatchForm = (props) => ( -
-

Cloud Watch

-
-
How to integrate CloudWatch with OpenReplay and see backend errors alongside session replays.
- -
- - config.awsSecretAccessKey.length === SECRET_ACCESS_KEY_LENGTH && - config.region !== '' && - config.awsAccessKeyId.length === ACCESS_KEY_ID_LENGTH, - }, - ]} - /> +
+ +
+
How it works?
+
    +
  1. Create a Service Account
  2. +
  3. Enter the details below
  4. +
  5. Propagate openReplaySessionToken
  6. +
+
+ + config.awsSecretAccessKey.length === SECRET_ACCESS_KEY_LENGTH && + config.region !== '' && + config.awsAccessKeyId.length === ACCESS_KEY_ID_LENGTH + } + ]} + /> +
); CloudwatchForm.displayName = 'CloudwatchForm'; diff --git a/frontend/app/components/Client/Integrations/DatadogForm.js b/frontend/app/components/Client/Integrations/DatadogForm.js index 46360259c..0835d135e 100644 --- a/frontend/app/components/Client/Integrations/DatadogForm.js +++ b/frontend/app/components/Client/Integrations/DatadogForm.js @@ -1,30 +1,37 @@ import React from 'react'; import IntegrationForm from './IntegrationForm'; import DocLink from 'Shared/DocLink/DocLink'; +import IntegrationModalCard from 'Components/Client/Integrations/IntegrationModalCard'; const DatadogForm = (props) => ( -
-

Datadog

-
-
How to integrate Datadog with OpenReplay and see backend errors alongside session recordings.
- -
- +
+ +
+
How it works?
+
    +
  1. Generate Datadog API Key & Application Key
  2. +
  3. Enter the API key below
  4. +
  5. Propagate openReplaySessionToken
  6. +
+
+ +
); DatadogForm.displayName = 'DatadogForm'; diff --git a/frontend/app/components/Client/Integrations/ElasticsearchForm.js b/frontend/app/components/Client/Integrations/ElasticsearchForm.js index ad33b6302..2c30cea47 100644 --- a/frontend/app/components/Client/Integrations/ElasticsearchForm.js +++ b/frontend/app/components/Client/Integrations/ElasticsearchForm.js @@ -4,85 +4,94 @@ import IntegrationForm from './IntegrationForm'; import { withRequest } from 'HOCs'; import { edit } from 'Duck/integrations/actions'; import DocLink from 'Shared/DocLink/DocLink'; +import IntegrationModalCard from 'Components/Client/Integrations/IntegrationModalCard'; @connect( - (state) => ({ - config: state.getIn(['elasticsearch', 'instance']), - }), - { edit } + (state) => ({ + config: state.getIn(['elasticsearch', 'instance']) + }), + { edit } ) @withRequest({ - dataName: 'isValid', - initialData: false, - dataWrapper: (data) => data.state, - requestName: 'validateConfig', - endpoint: '/integrations/elasticsearch/test', - method: 'POST', + dataName: 'isValid', + initialData: false, + dataWrapper: (data) => data.state, + requestName: 'validateConfig', + endpoint: '/integrations/elasticsearch/test', + method: 'POST' }) export default class ElasticsearchForm extends React.PureComponent { - componentWillReceiveProps(newProps) { - const { - config: { host, port, apiKeyId, apiKey }, - } = this.props; - const { loading, config } = newProps; - const valuesChanged = host !== config.host || port !== config.port || apiKeyId !== config.apiKeyId || apiKey !== config.apiKey; - if (!loading && valuesChanged && newProps.config.validateKeys() && newProps) { - this.validateConfig(newProps); - } + componentWillReceiveProps(newProps) { + const { + config: { host, port, apiKeyId, apiKey } + } = this.props; + const { loading, config } = newProps; + const valuesChanged = host !== config.host || port !== config.port || apiKeyId !== config.apiKeyId || apiKey !== config.apiKey; + if (!loading && valuesChanged && newProps.config.validateKeys() && newProps) { + this.validateConfig(newProps); } + } - validateConfig = (newProps) => { - const { config } = newProps; - this.props - .validateConfig({ - host: config.host, - port: config.port, - apiKeyId: config.apiKeyId, - apiKey: config.apiKey, - }) - .then((res) => { - const { isValid } = this.props; - this.props.edit('elasticsearch', { isValid: isValid }); - }); - }; + validateConfig = (newProps) => { + const { config } = newProps; + this.props + .validateConfig({ + host: config.host, + port: config.port, + apiKeyId: config.apiKeyId, + apiKey: config.apiKey + }) + .then((res) => { + const { isValid } = this.props; + this.props.edit('elasticsearch', { isValid: isValid }); + }); + }; - render() { - const props = this.props; - return ( -
-

Elasticsearch

-
-
How to integrate Elasticsearch with OpenReplay and see backend errors alongside session recordings.
- -
- -
- ); - } + render() { + const props = this.props; + return ( +
+ + +
+
How it works?
+
    +
  1. Create a new Elastic API key
  2. +
  3. Enter the API key below
  4. +
  5. Propagate openReplaySessionToken
  6. +
+ +
+ +
+ ); + } } diff --git a/frontend/app/components/Client/Integrations/GithubForm.js b/frontend/app/components/Client/Integrations/GithubForm.js index 7d140732b..b54343c5f 100644 --- a/frontend/app/components/Client/Integrations/GithubForm.js +++ b/frontend/app/components/Client/Integrations/GithubForm.js @@ -1,29 +1,31 @@ import React from 'react'; import IntegrationForm from './IntegrationForm'; import DocLink from 'Shared/DocLink/DocLink'; +import IntegrationModalCard from 'Components/Client/Integrations/IntegrationModalCard'; const GithubForm = (props) => ( -
-

Github

-
-
Integrate GitHub with OpenReplay and create issues directly from the recording page.
-
- -
-
- +
+ +
+
Integrate GitHub with OpenReplay and create issues directly from the recording page.
+
+ +
+ +
); GithubForm.displayName = 'GithubForm'; diff --git a/frontend/app/components/Client/Integrations/IntegrationFilters.tsx b/frontend/app/components/Client/Integrations/IntegrationFilters.tsx new file mode 100644 index 000000000..5e8d8f7b3 --- /dev/null +++ b/frontend/app/components/Client/Integrations/IntegrationFilters.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { Icon } from 'UI'; +import cn from 'classnames'; + +interface Props { + onChange: any; + activeItem: string; + filters: any; +} + +const allItem = { key: 'all', title: 'All' }; + +function FilterButton(props: { activeItem: string, item: any, onClick: () => any }) { + return
+ {props.item.icon && } + {props.item.title} +
; +} + +function IntegrationFilters(props: Props) { + + return ( +
+ props.onChange(allItem.key)} + /> + {props.filters.map((item: any) => ( + props.onChange(item.key)} /> + ))} +
+ ); +} + +export default IntegrationFilters; \ No newline at end of file diff --git a/frontend/app/components/Client/Integrations/IntegrationItem.tsx b/frontend/app/components/Client/Integrations/IntegrationItem.tsx index efcdefd8a..27f518c5c 100644 --- a/frontend/app/components/Client/Integrations/IntegrationItem.tsx +++ b/frontend/app/components/Client/Integrations/IntegrationItem.tsx @@ -1,35 +1,47 @@ import React from 'react'; import cn from 'classnames'; -import { Icon, Tooltip } from 'UI'; +import { Icon } from 'UI'; import stl from './integrationItem.module.css'; +import { Tooltip } from 'antd'; interface Props { - integration: any; - onClick?: (e: React.MouseEvent) => void; - integrated?: boolean; - hide?: boolean; + integration: any; + onClick?: (e: React.MouseEvent) => void; + integrated?: boolean; + hide?: boolean; } const IntegrationItem = (props: Props) => { - const { integration, integrated, hide = false } = props; - return hide ? <> : ( -
props.onClick(e)}> - {integrated && ( -
- - - -
- )} - {integration.icon.length ? integration : ( - {integration.header} - )} -
-

{integration.title}

- {/*

{integration.subtitle && integration.subtitle}

*/} -
+ const { integration, integrated, hide = false } = props; + return hide ? <> : ( +
props.onClick(e)} + style={{ height: '126px' }} + > +
+ {/*{integration.icon.length ?*/} + {/* integration :*/} + {/* ({integration.header})}*/} +
+ integration
- ); +
+

{integration.title}

+

{integration.subtitle && integration.subtitle}

+
+
+ + {integrated && ( + +
+ + Installed +
+
+ )} +
+ ); }; export default IntegrationItem; diff --git a/frontend/app/components/Client/Integrations/IntegrationModalCard.tsx b/frontend/app/components/Client/Integrations/IntegrationModalCard.tsx new file mode 100644 index 000000000..4864b092b --- /dev/null +++ b/frontend/app/components/Client/Integrations/IntegrationModalCard.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { Icon } from 'UI'; +import DocLink from 'Shared/DocLink'; + +interface Props { + title: string; + icon: string; + description: string; +} + +function IntegrationModalCard(props: Props) { + const { title, icon, description } = props; + return ( +
+
+ integration +
+
+

{title}

+
{description}
+
+
+ ); +} + +export default IntegrationModalCard; \ No newline at end of file diff --git a/frontend/app/components/Client/Integrations/Integrations.tsx b/frontend/app/components/Client/Integrations/Integrations.tsx index 98cbf4f0a..1c6d0f58a 100644 --- a/frontend/app/components/Client/Integrations/Integrations.tsx +++ b/frontend/app/components/Client/Integrations/Integrations.tsx @@ -1,5 +1,25 @@ +import React, { useEffect, useState } from 'react'; +import { connect } from 'react-redux'; import { useModal } from 'App/components/Modal'; -import React, { useEffect } from 'react'; +import cn from 'classnames'; + +import { fetch, init } from 'Duck/integrations/actions'; +import { fetchIntegrationList, setSiteId } from 'Duck/integrations/integrations'; +import SiteDropdown from 'Shared/SiteDropdown'; +import ReduxDoc from './ReduxDoc'; +import VueDoc from './VueDoc'; +import GraphQLDoc from './GraphQLDoc'; +import NgRxDoc from './NgRxDoc'; +import MobxDoc from './MobxDoc'; +import ProfilerDoc from './ProfilerDoc'; +import AssistDoc from './AssistDoc'; +import PiniaDoc from './PiniaDoc'; +import ZustandDoc from './ZustandDoc'; +import MSTeams from './Teams'; +import DocCard from 'Shared/DocCard/DocCard'; +import { PageTitle, Tooltip } from 'UI'; +import withPageTitle from 'HOCs/withPageTitle'; + import BugsnagForm from './BugsnagForm'; import CloudwatchForm from './CloudwatchForm'; import DatadogForm from './DatadogForm'; @@ -13,25 +33,7 @@ import SentryForm from './SentryForm'; import SlackForm from './SlackForm'; import StackdriverForm from './StackdriverForm'; import SumoLogicForm from './SumoLogicForm'; -import { fetch, init } from 'Duck/integrations/actions'; -import { fetchIntegrationList, setSiteId } from 'Duck/integrations/integrations'; -import { connect } from 'react-redux'; -import SiteDropdown from 'Shared/SiteDropdown'; -import ReduxDoc from './ReduxDoc'; -import VueDoc from './VueDoc'; -import GraphQLDoc from './GraphQLDoc'; -import NgRxDoc from './NgRxDoc'; -import MobxDoc from './MobxDoc'; -import ProfilerDoc from './ProfilerDoc'; -import AssistDoc from './AssistDoc'; -import { PageTitle, Tooltip } from 'UI'; -import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; -import withPageTitle from 'HOCs/withPageTitle'; -import PiniaDoc from './PiniaDoc'; -import ZustandDoc from './ZustandDoc'; -import MSTeams from './Teams'; -import DocCard from 'Shared/DocCard/DocCard'; -import cn from 'classnames'; +import IntegrationFilters from 'Components/Client/Integrations/IntegrationFilters'; interface Props { fetch: (name: string, siteId: string) => void; @@ -44,10 +46,12 @@ interface Props { hideHeader?: boolean; loading?: boolean; } + function Integrations(props: Props) { const { initialSiteId, hideHeader = false, loading = false } = props; const { showModal } = useModal(); - const [integratedList, setIntegratedList] = React.useState([]); + const [integratedList, setIntegratedList] = useState([]); + const [activeFilter, setActiveFilter] = useState('all'); useEffect(() => { const list = props.integratedList @@ -68,7 +72,7 @@ function Integrations(props: Props) { showModal( React.cloneElement(integration.component, { - integrated: integratedList.includes(integration.slug), + integrated: integratedList.includes(integration.slug) }), { right: true, width } ); @@ -79,55 +83,55 @@ function Integrations(props: Props) { props.fetchIntegrationList(value.value); }; - return ( -
- {!hideHeader && Integrations
} />} - {integrations.map((cat: any) => ( -
-
-
-

{cat.title}

- {cat.isProject && ( -
-
- -
- {loading && cat.isProject && } -
- )} -
-
{cat.description}
+ const onChange = (key: string) => { + setActiveFilter(key); + }; -
- {cat.integrations.map((integration: any) => ( - - - onClick(integration, cat.title === 'Plugins' ? 500 : 350)} - hide={ - (integration.slug === 'github' && integratedList.includes('jira')) || - (integration.slug === 'jira' && integratedList.includes('github')) - } - /> - - - ))} -
-
- {cat.docs &&
{cat.docs()}
} + const filteredIntegrations = integrations.filter((cat: any) => { + if (activeFilter === 'all') { + return true; + } + + return cat.key === activeFilter; + }); + + const filters = integrations.map((cat: any) => ({ + key: cat.key, + title: cat.title, + icon: cat.icon + })); + + + return ( + <> +
+ {!hideHeader && Integrations
} />} + + +
+ +
+ + {filteredIntegrations.map((cat: any) => ( +
+ {cat.integrations.map((integration: any) => ( + + onClick(integration, cat.title === 'Plugins' ? 500 : 350) + } + hide={ + (integration.slug === 'github' && + integratedList.includes('jira')) || + (integration.slug === 'jira' && + integratedList.includes('github')) + } + /> + ))}
))} -
+ ); } @@ -136,145 +140,228 @@ export default connect( initialSiteId: state.getIn(['site', 'siteId']), integratedList: state.getIn(['integrations', 'list']) || [], loading: state.getIn(['integrations', 'fetchRequest', 'loading']), - siteId: state.getIn(['integrations', 'siteId']), + siteId: state.getIn(['integrations', 'siteId']) }), { fetch, init, fetchIntegrationList, setSiteId } )(withPageTitle('Integrations - OpenReplay Preferences')(Integrations)); + const integrations = [ { - title: 'Issue Reporting and Collaborations', - key: 1, + title: 'Issue Reporting', + key: 'issue-reporting', description: 'Seamlessly report issues or share issues with your team right from OpenReplay.', isProject: false, + icon: 'exclamation-triangle', integrations: [ { title: 'Jira', + subtitle: 'Integrate Jira with OpenReplay to enable the creation of a new ticket directly from a session.', slug: 'jira', category: 'Errors', icon: 'integrations/jira', - component: , + component: }, { title: 'Github', + subtitle: 'Integrate GitHub with OpenReplay to enable the direct creation of a new issue from a session.', slug: 'github', category: 'Errors', icon: 'integrations/github', - component: , + component: }, { title: 'Slack', + subtitle: 'Integrate Slack to empower every user in your org with the ability to send sessions to any Slack channel.', slug: 'slack', category: 'Errors', icon: 'integrations/slack', component: , - shared: true, + shared: true }, { title: 'MS Teams', + subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.', slug: 'msteams', category: 'Errors', icon: 'integrations/teams', component: , - shared: true, - }, - ], + shared: true + } + ] }, { title: 'Backend Logging', - key: 2, + key: 'backend-logging', isProject: true, + icon: 'terminal', description: 'Sync your backend errors with sessions replays and see what happened front-to-back.', docs: () => ( Sync your backend errors with sessions replays and see what happened front-to-back. ), integrations: [ - { title: 'Sentry', slug: 'sentry', icon: 'integrations/sentry', component: }, + { + title: 'Sentry', + subtitle: 'Integrate Sentry with session replays to seamlessly observe backend errors.', + slug: 'sentry', + icon: 'integrations/sentry', + component: + }, { title: 'Bugsnag', + subtitle: 'Integrate Bugsnag to access the OpenReplay session linked to the JS exception within its interface.', slug: 'bugsnag', icon: 'integrations/bugsnag', - component: , + component: }, { title: 'Rollbar', + subtitle: 'Integrate Rollbar with session replays to seamlessly observe backend errors.', slug: 'rollbar', icon: 'integrations/rollbar', - component: , + component: }, { title: 'Elasticsearch', + subtitle: 'Integrate Elasticsearch with session replays to seamlessly observe backend errors.', slug: 'elasticsearch', icon: 'integrations/elasticsearch', - component: , + component: }, { title: 'Datadog', + subtitle: 'Incorporate DataDog to visualize backend errors alongside session replay, for easy troubleshooting.', slug: 'datadog', icon: 'integrations/datadog', - component: , + component: }, { title: 'Sumo Logic', + subtitle: 'Integrate Sumo Logic with session replays to seamlessly observe backend errors.', slug: 'sumologic', icon: 'integrations/sumologic', - component: , + component: }, { - title: 'Stackdriver', + title: 'Google Cloud', + subtitle: 'Integrate Google Cloud to view backend logs and errors in conjunction with session replay', slug: 'stackdriver', icon: 'integrations/google-cloud', - component: , + component: }, { title: 'CloudWatch', + subtitle: 'Integrate CloudWatch to see backend logs and errors alongside session replay.', slug: 'cloudwatch', icon: 'integrations/aws', - component: , + component: }, { title: 'Newrelic', + subtitle: 'Integrate NewRelic with session replays to seamlessly observe backend errors.', slug: 'newrelic', icon: 'integrations/newrelic', - component: , - }, - ], + component: + } + ] + }, + { + title: 'Collaboration', + key: 'collaboration', + isProject: false, + icon: 'file-code', + description: 'Share your sessions with your team and collaborate on issues.', + integrations: [] + }, + { + title: 'State Management', + key: 'state-management', + isProject: true, + icon: 'layers-half', + description: 'Sync your Redux or VueX store with sessions replays and see what happened front-to-back.', + integrations: [] }, { title: 'Plugins', - key: 3, + key: 'plugins', isProject: true, + icon: 'chat-left-text', docs: () => ( Plugins capture your application’s store, monitor queries, track performance issues and even assist your end user through live sessions. ), description: - "Reproduce issues as if they happened in your own browser. Plugins help capture your application's store, HTTP requeets, GraphQL queries, and more.", + 'Reproduce issues as if they happened in your own browser. Plugins help capture your application\'s store, HTTP requeets, GraphQL queries, and more.', integrations: [ - { title: 'Redux', icon: 'integrations/redux', component: }, - { title: 'VueX', icon: 'integrations/vuejs', component: }, - { title: 'Pinia', icon: 'integrations/pinia', component: }, - { title: 'GraphQL', icon: 'integrations/graphql', component: }, - { title: 'NgRx', icon: 'integrations/ngrx', component: }, - { title: 'MobX', icon: 'integrations/mobx', component: }, - { title: 'Profiler', icon: 'integrations/openreplay', component: }, - { title: 'Assist', icon: 'integrations/openreplay', component: }, - { title: 'Zustand', icon: '', header: '🐻', component: }, - ], - }, + { + title: 'Redux', + subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.', + icon: 'integrations/redux', component: + }, + { + title: 'VueX', + subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.', + icon: 'integrations/vuejs', + component: + }, + { + title: 'Pinia', + subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.', + icon: 'integrations/pinia', + component: + }, + { + title: 'GraphQL', + subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.', + icon: 'integrations/graphql', + component: + }, + { + title: 'NgRx', + subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.', + icon: 'integrations/ngrx', + component: + }, + { + title: 'MobX', + subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.', + icon: 'integrations/mobx', + component: + }, + { + title: 'Profiler', + subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.', + icon: 'integrations/openreplay', + component: + }, + { + title: 'Assist', + subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.', + icon: 'integrations/openreplay', + component: + }, + { + title: 'Zustand', + subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.', + icon: '', + header: '🐻', + component: + } + ] + } ]; diff --git a/frontend/app/components/Client/Integrations/JiraForm/JiraForm.js b/frontend/app/components/Client/Integrations/JiraForm/JiraForm.js index 906794155..c417f0626 100644 --- a/frontend/app/components/Client/Integrations/JiraForm/JiraForm.js +++ b/frontend/app/components/Client/Integrations/JiraForm/JiraForm.js @@ -2,43 +2,55 @@ import React from 'react'; import IntegrationForm from '../IntegrationForm'; import DocLink from 'Shared/DocLink/DocLink'; import { useModal } from 'App/components/Modal'; +import { Icon } from 'UI'; +import IntegrationModalCard from 'Components/Client/Integrations/IntegrationModalCard'; const JiraForm = (props) => { - const { hideModal } = useModal(); - return ( -
-

Jira

-
-
How to integrate Jira Cloud with OpenReplay.
-
- -
-
- + const { hideModal } = useModal(); + return ( +
+ + + +
+
How it works?
+
    +
  1. Create a new API token
  2. +
  3. Enter the token below
  4. +
+
+
- ) +
+ + + +
+ ); }; JiraForm.displayName = 'JiraForm'; diff --git a/frontend/app/components/Client/Integrations/NewrelicForm/NewrelicForm.js b/frontend/app/components/Client/Integrations/NewrelicForm/NewrelicForm.js index 670656583..3d3d829b1 100644 --- a/frontend/app/components/Client/Integrations/NewrelicForm/NewrelicForm.js +++ b/frontend/app/components/Client/Integrations/NewrelicForm/NewrelicForm.js @@ -1,34 +1,41 @@ import React from 'react'; import IntegrationForm from '../IntegrationForm'; import DocLink from 'Shared/DocLink/DocLink'; +import IntegrationModalCard from 'Components/Client/Integrations/IntegrationModalCard'; const NewrelicForm = (props) => ( -
-

New Relic

-
-
How to integrate NewRelic with OpenReplay and see backend errors alongside session recordings.
- -
- +
+ +
+
How it works?
+
    +
  1. Create Query Key
  2. +
  3. Enter the details below
  4. +
  5. Propagate openReplaySessionToken
  6. +
+
+ +
); NewrelicForm.displayName = 'NewrelicForm'; diff --git a/frontend/app/components/Client/Integrations/RollbarForm.js b/frontend/app/components/Client/Integrations/RollbarForm.js index 441819323..624ec9bf2 100644 --- a/frontend/app/components/Client/Integrations/RollbarForm.js +++ b/frontend/app/components/Client/Integrations/RollbarForm.js @@ -1,25 +1,32 @@ import React from 'react'; import IntegrationForm from './IntegrationForm'; import DocLink from 'Shared/DocLink/DocLink'; +import IntegrationModalCard from 'Components/Client/Integrations/IntegrationModalCard'; const RollbarForm = (props) => ( -
-

Rollbar

-
-
How to integrate Rollbar with OpenReplay and see backend errors alongside session replays.
- -
- +
+ +
+
How it works?
+
    +
  1. Create Rollbar Access Tokens
  2. +
  3. Enter the token below
  4. +
  5. Propagate openReplaySessionToken
  6. +
+
+ +
); RollbarForm.displayName = 'RollbarForm'; diff --git a/frontend/app/components/Client/Integrations/SentryForm.js b/frontend/app/components/Client/Integrations/SentryForm.js index bd119ba31..506aec48c 100644 --- a/frontend/app/components/Client/Integrations/SentryForm.js +++ b/frontend/app/components/Client/Integrations/SentryForm.js @@ -1,33 +1,40 @@ import React from 'react'; import IntegrationForm from './IntegrationForm'; import DocLink from 'Shared/DocLink/DocLink'; +import IntegrationModalCard from 'Components/Client/Integrations/IntegrationModalCard'; const SentryForm = (props) => ( -
-

Sentry

-
-
How to integrate Sentry with OpenReplay and see backend errors alongside session recordings.
- -
- +
+ +
+
How it works?
+
    +
  1. Generate Sentry Auth Token
  2. +
  3. Enter the token below
  4. +
  5. Propagate openReplaySessionToken
  6. +
+
+ +
); SentryForm.displayName = 'SentryForm'; diff --git a/frontend/app/components/Client/Integrations/StackdriverForm.js b/frontend/app/components/Client/Integrations/StackdriverForm.js index ce137bd99..92935e67b 100644 --- a/frontend/app/components/Client/Integrations/StackdriverForm.js +++ b/frontend/app/components/Client/Integrations/StackdriverForm.js @@ -1,30 +1,38 @@ import React from 'react'; import IntegrationForm from './IntegrationForm'; import DocLink from 'Shared/DocLink/DocLink'; +import IntegrationModalCard from 'Components/Client/Integrations/IntegrationModalCard'; const StackdriverForm = (props) => ( -
-

Stackdriver

-
-
How to integrate Stackdriver with OpenReplay and see backend errors alongside session recordings.
- -
- +
+ +
+
How it works?
+
    +
  1. Create Google Cloud Service Account
  2. +
  3. Enter the details below
  4. +
  5. Propagate openReplaySessionToken
  6. +
+
+ +
); StackdriverForm.displayName = 'StackdriverForm'; diff --git a/frontend/app/components/Client/Integrations/SumoLogicForm/SumoLogicForm.js b/frontend/app/components/Client/Integrations/SumoLogicForm/SumoLogicForm.js index 6aea9fe6e..af72ccbcf 100644 --- a/frontend/app/components/Client/Integrations/SumoLogicForm/SumoLogicForm.js +++ b/frontend/app/components/Client/Integrations/SumoLogicForm/SumoLogicForm.js @@ -2,34 +2,41 @@ import React from 'react'; import IntegrationForm from '../IntegrationForm'; import RegionDropdown from './RegionDropdown'; import DocLink from 'Shared/DocLink/DocLink'; +import IntegrationModalCard from 'Components/Client/Integrations/IntegrationModalCard'; const SumoLogicForm = (props) => ( -
-

Sumologic

-
-
How to integrate SumoLogic with OpenReplay and see backend errors alongside session recordings.
- -
- +
+ +
+
How it works?
+
    +
  1. Create a new Access ID and Access Key
  2. +
  3. Enter the details below
  4. +
  5. Propagate openReplaySessionToken
  6. +
+
+ +
); SumoLogicForm.displayName = 'SumoLogicForm'; diff --git a/frontend/app/components/shared/DocLink/DocLink.js b/frontend/app/components/shared/DocLink/DocLink.js index 004f154ef..3647e79e5 100644 --- a/frontend/app/components/shared/DocLink/DocLink.js +++ b/frontend/app/components/shared/DocLink/DocLink.js @@ -8,7 +8,7 @@ export default function DocLink({ className = '', url, label }) { return (
- diff --git a/frontend/app/components/ui/Button/Button.tsx b/frontend/app/components/ui/Button/Button.tsx index b4c6c6b61..2b0514c87 100644 --- a/frontend/app/components/ui/Button/Button.tsx +++ b/frontend/app/components/ui/Button/Button.tsx @@ -35,7 +35,7 @@ export default (props: Props) => { let iconColor = variant === 'text' || variant === 'default' ? 'gray-dark' : 'teal'; const variantClasses = { - default: 'bg-white hover:bg-gray-light border border-gray-light', + default: 'bg-white hover:!bg-gray-light border border-gray-light', primary: 'bg-teal color-white hover:bg-teal-dark', green: 'bg-green color-white hover:bg-green-dark', text: 'bg-transparent text-black hover:bg-active-blue hover:!text-teal hover-fill-teal', @@ -49,7 +49,8 @@ export default (props: Props) => { variantClasses[variant], { 'opacity-40 pointer-events-none': disabled }, { '!rounded-full h-10 w-10 justify-center': rounded }, - className + className, + 'btn' ); if (variant === 'primary') { diff --git a/frontend/app/components/ui/SVG.tsx b/frontend/app/components/ui/SVG.tsx index 47776ee26..6b840cead 100644 --- a/frontend/app/components/ui/SVG.tsx +++ b/frontend/app/components/ui/SVG.tsx @@ -1,7 +1,7 @@ import React from 'react'; -export type IconNames = 'activity' | 'alarm-clock' | 'alarm-plus' | 'all-sessions' | 'analytics' | 'anchor' | 'arrow-alt-square-right' | 'arrow-bar-left' | 'arrow-clockwise' | 'arrow-counterclockwise' | 'arrow-down-short' | 'arrow-down' | 'arrow-repeat' | 'arrow-right-short' | 'arrow-square-left' | 'arrow-square-right' | 'arrow-up-short' | 'arrow-up' | 'arrows-angle-extend' | 'avatar/icn_bear' | 'avatar/icn_beaver' | 'avatar/icn_bird' | 'avatar/icn_bison' | 'avatar/icn_camel' | 'avatar/icn_chameleon' | 'avatar/icn_deer' | 'avatar/icn_dog' | 'avatar/icn_dolphin' | 'avatar/icn_elephant' | 'avatar/icn_fish' | 'avatar/icn_fox' | 'avatar/icn_gorilla' | 'avatar/icn_hippo' | 'avatar/icn_horse' | 'avatar/icn_hyena' | 'avatar/icn_kangaroo' | 'avatar/icn_lemur' | 'avatar/icn_mammel' | 'avatar/icn_monkey' | 'avatar/icn_moose' | 'avatar/icn_panda' | 'avatar/icn_penguin' | 'avatar/icn_porcupine' | 'avatar/icn_quail' | 'avatar/icn_rabbit' | 'avatar/icn_rhino' | 'avatar/icn_sea_horse' | 'avatar/icn_sheep' | 'avatar/icn_snake' | 'avatar/icn_squirrel' | 'avatar/icn_tapir' | 'avatar/icn_turtle' | 'avatar/icn_vulture' | 'avatar/icn_wild1' | 'avatar/icn_wild_bore' | 'ban' | 'bar-chart-line' | 'bar-pencil' | 'bell-fill' | 'bell-plus' | 'bell-slash' | 'bell' | 'binoculars' | 'book-doc' | 'book' | 'browser/browser' | 'browser/chrome' | 'browser/edge' | 'browser/electron' | 'browser/facebook' | 'browser/firefox' | 'browser/ie' | 'browser/opera' | 'browser/safari' | 'buildings' | 'bullhorn' | 'business-time' | 'calendar-alt' | 'calendar-check' | 'calendar-day' | 'calendar' | 'call' | 'camera-alt' | 'camera-video-off' | 'camera-video' | 'camera' | 'card-checklist' | 'card-text' | 'caret-down-fill' | 'caret-left-fill' | 'caret-right-fill' | 'caret-up-fill' | 'chat-dots' | 'chat-right-text' | 'chat-square-quote' | 'check-circle-fill' | 'check-circle' | 'check' | 'chevron-double-left' | 'chevron-double-right' | 'chevron-down' | 'chevron-left' | 'chevron-right' | 'chevron-up' | 'circle-fill' | 'circle' | 'click-hesitation' | 'click-rage' | 'clipboard-list-check' | 'clock' | 'close' | 'cloud-fog2-fill' | 'code' | 'cog' | 'cogs' | 'collection' | 'columns-gap-filled' | 'columns-gap' | 'console/error' | 'console/exception' | 'console/info' | 'console/warning' | 'console' | 'controller' | 'cookies' | 'copy' | 'credit-card-front' | 'cross' | 'cubes' | 'cursor-trash' | 'dash' | 'dashboard-icn' | 'db-icons/icn-card-clickMap' | 'db-icons/icn-card-errors' | 'db-icons/icn-card-funnel' | 'db-icons/icn-card-funnels' | 'db-icons/icn-card-insights' | 'db-icons/icn-card-library' | 'db-icons/icn-card-mapchart' | 'db-icons/icn-card-performance' | 'db-icons/icn-card-resources' | 'db-icons/icn-card-table' | 'db-icons/icn-card-timeseries' | 'db-icons/icn-card-userPath' | 'db-icons/icn-card-webVitals' | 'desktop' | 'device' | 'diagram-3' | 'dizzy' | 'door-closed' | 'doublecheck' | 'download' | 'drag' | 'edit' | 'ellipsis-v' | 'enter' | 'envelope-check' | 'envelope-x' | 'envelope' | 'errors-icon' | 'event/click' | 'event/click_hesitation' | 'event/clickrage' | 'event/code' | 'event/i-cursor' | 'event/input' | 'event/input_hesitation' | 'event/link' | 'event/location' | 'event/mouse_thrashing' | 'event/resize' | 'event/view' | 'exclamation-circle-fill' | 'exclamation-circle' | 'expand-wide' | 'explosion' | 'external-link-alt' | 'eye-slash-fill' | 'eye-slash' | 'eye' | 'fetch' | 'fflag-multi' | 'fflag-single' | 'file-code' | 'file-medical-alt' | 'file-pdf' | 'file' | 'files' | 'filter' | 'filters/arrow-return-right' | 'filters/browser' | 'filters/click' | 'filters/clickrage' | 'filters/code' | 'filters/console' | 'filters/country' | 'filters/cpu-load' | 'filters/custom' | 'filters/device' | 'filters/dom-complete' | 'filters/duration' | 'filters/error' | 'filters/fetch-failed' | 'filters/fetch' | 'filters/file-code' | 'filters/graphql' | 'filters/i-cursor' | 'filters/input' | 'filters/lcpt' | 'filters/link' | 'filters/location' | 'filters/memory-load' | 'filters/metadata' | 'filters/os' | 'filters/perfromance-network-request' | 'filters/platform' | 'filters/referrer' | 'filters/resize' | 'filters/rev-id' | 'filters/state-action' | 'filters/ttfb' | 'filters/user-alt' | 'filters/userid' | 'filters/view' | 'flag-na' | 'folder-plus' | 'folder2' | 'fullscreen' | 'funnel/cpu-fill' | 'funnel/cpu' | 'funnel/dizzy' | 'funnel/emoji-angry-fill' | 'funnel/emoji-angry' | 'funnel/emoji-dizzy-fill' | 'funnel/exclamation-circle-fill' | 'funnel/exclamation-circle' | 'funnel/file-earmark-break-fill' | 'funnel/file-earmark-break' | 'funnel/file-earmark-minus-fill' | 'funnel/file-earmark-minus' | 'funnel/file-medical-alt' | 'funnel/file-x' | 'funnel/hdd-fill' | 'funnel/hourglass-top' | 'funnel/image-fill' | 'funnel/image' | 'funnel/microchip' | 'funnel/mouse' | 'funnel/patch-exclamation-fill' | 'funnel/sd-card' | 'funnel-fill' | 'funnel-new' | 'funnel' | 'gear-fill' | 'gear' | 'geo-alt-fill-custom' | 'github' | 'graph-up-arrow' | 'graph-up' | 'grid-1x2' | 'grid-3x3' | 'grid-check' | 'grid-horizontal' | 'grid' | 'grip-horizontal' | 'hash' | 'hdd-stack' | 'headset' | 'heart-rate' | 'high-engagement' | 'history' | 'hourglass-start' | 'ic-errors' | 'ic-network' | 'ic-rage' | 'ic-resources' | 'id-card' | 'image' | 'info-circle-fill' | 'info-circle' | 'info-square' | 'info' | 'input-hesitation' | 'inspect' | 'integrations/assist' | 'integrations/bugsnag-text' | 'integrations/bugsnag' | 'integrations/cloudwatch-text' | 'integrations/cloudwatch' | 'integrations/datadog' | 'integrations/elasticsearch-text' | 'integrations/elasticsearch' | 'integrations/github' | 'integrations/graphql' | 'integrations/jira-text' | 'integrations/jira' | 'integrations/mobx' | 'integrations/newrelic-text' | 'integrations/newrelic' | 'integrations/ngrx' | 'integrations/openreplay-text' | 'integrations/openreplay' | 'integrations/redux' | 'integrations/rollbar-text' | 'integrations/rollbar' | 'integrations/segment' | 'integrations/sentry-text' | 'integrations/sentry' | 'integrations/slack-bw' | 'integrations/slack' | 'integrations/stackdriver' | 'integrations/sumologic-text' | 'integrations/sumologic' | 'integrations/teams-white' | 'integrations/teams' | 'integrations/vuejs' | 'journal-code' | 'key' | 'layer-group' | 'lightbulb-on' | 'lightbulb' | 'link-45deg' | 'list-alt' | 'list-arrow' | 'list-ul' | 'list' | 'lock-alt' | 'magic' | 'map-marker-alt' | 'memory' | 'mic-mute' | 'mic' | 'minus' | 'mobile' | 'mouse-alt' | 'network' | 'next1' | 'no-dashboard' | 'no-metrics-chart' | 'no-metrics' | 'no-recordings' | 'os/android' | 'os/chrome_os' | 'os/fedora' | 'os/ios' | 'os/linux' | 'os/mac_os_x' | 'os/other' | 'os/ubuntu' | 'os/windows' | 'os' | 'pause-fill' | 'pause' | 'pdf-download' | 'pencil-stop' | 'pencil' | 'people' | 'percent' | 'performance-icon' | 'person-border' | 'person-fill' | 'person' | 'pie-chart-fill' | 'pin-fill' | 'play-circle-bold' | 'play-circle-light' | 'play-circle' | 'play-fill-new' | 'play-fill' | 'play-hover' | 'play' | 'plug' | 'plus-circle' | 'plus-lg' | 'plus' | 'pointer-sessions-search' | 'prev1' | 'pulse' | 'puzzle-piece' | 'puzzle' | 'question-circle' | 'question-lg' | 'quote-left' | 'quote-right' | 'quotes' | 'record-btn' | 'record-circle' | 'redo-back' | 'redo' | 'remote-control' | 'replay-10' | 'resources-icon' | 'safe-fill' | 'safe' | 'sandglass' | 'search' | 'search_notification' | 'server' | 'share-alt' | 'shield-lock' | 'signpost-split' | 'signup' | 'skip-forward-fill' | 'skip-forward' | 'slack' | 'slash-circle' | 'sleep' | 'sliders' | 'social/slack' | 'social/trello' | 'speedometer2' | 'spinner' | 'star-solid' | 'star' | 'step-forward' | 'stickies' | 'stop-record-circle' | 'stopwatch' | 'store' | 'sync-alt' | 'table-new' | 'table' | 'tablet-android' | 'tachometer-slow' | 'tachometer-slowest' | 'tags' | 'team-funnel' | 'telephone-fill' | 'telephone' | 'text-paragraph' | 'toggles' | 'tools' | 'trash' | 'turtle' | 'user-alt' | 'user-circle' | 'user-friends' | 'users' | 'vendors/graphql' | 'vendors/mobx' | 'vendors/ngrx' | 'vendors/redux' | 'vendors/vuex' | 'web-vitals' | 'wifi' | 'window-alt' | 'window-restore' | 'window-x' | 'window' | 'zoom-in'; +export type IconNames = 'activity' | 'alarm-clock' | 'alarm-plus' | 'all-sessions' | 'analytics' | 'anchor' | 'arrow-alt-square-right' | 'arrow-bar-left' | 'arrow-clockwise' | 'arrow-counterclockwise' | 'arrow-down-short' | 'arrow-down' | 'arrow-repeat' | 'arrow-right-short' | 'arrow-square-left' | 'arrow-square-right' | 'arrow-up-short' | 'arrow-up' | 'arrows-angle-extend' | 'avatar/icn_bear' | 'avatar/icn_beaver' | 'avatar/icn_bird' | 'avatar/icn_bison' | 'avatar/icn_camel' | 'avatar/icn_chameleon' | 'avatar/icn_deer' | 'avatar/icn_dog' | 'avatar/icn_dolphin' | 'avatar/icn_elephant' | 'avatar/icn_fish' | 'avatar/icn_fox' | 'avatar/icn_gorilla' | 'avatar/icn_hippo' | 'avatar/icn_horse' | 'avatar/icn_hyena' | 'avatar/icn_kangaroo' | 'avatar/icn_lemur' | 'avatar/icn_mammel' | 'avatar/icn_monkey' | 'avatar/icn_moose' | 'avatar/icn_panda' | 'avatar/icn_penguin' | 'avatar/icn_porcupine' | 'avatar/icn_quail' | 'avatar/icn_rabbit' | 'avatar/icn_rhino' | 'avatar/icn_sea_horse' | 'avatar/icn_sheep' | 'avatar/icn_snake' | 'avatar/icn_squirrel' | 'avatar/icn_tapir' | 'avatar/icn_turtle' | 'avatar/icn_vulture' | 'avatar/icn_wild1' | 'avatar/icn_wild_bore' | 'ban' | 'bar-chart-line' | 'bar-pencil' | 'bell-fill' | 'bell-plus' | 'bell-slash' | 'bell' | 'binoculars' | 'book-doc' | 'book' | 'browser/browser' | 'browser/chrome' | 'browser/edge' | 'browser/electron' | 'browser/facebook' | 'browser/firefox' | 'browser/ie' | 'browser/opera' | 'browser/safari' | 'buildings' | 'bullhorn' | 'business-time' | 'calendar-alt' | 'calendar-check' | 'calendar-day' | 'calendar' | 'call' | 'camera-alt' | 'camera-video-off' | 'camera-video' | 'camera' | 'card-checklist' | 'card-text' | 'caret-down-fill' | 'caret-left-fill' | 'caret-right-fill' | 'caret-up-fill' | 'chat-dots' | 'chat-left-text' | 'chat-right-text' | 'chat-square-quote' | 'check-circle-fill' | 'check-circle' | 'check' | 'chevron-double-left' | 'chevron-double-right' | 'chevron-down' | 'chevron-left' | 'chevron-right' | 'chevron-up' | 'circle-fill' | 'circle' | 'click-hesitation' | 'click-rage' | 'clipboard-list-check' | 'clock' | 'close' | 'cloud-fog2-fill' | 'code' | 'cog' | 'cogs' | 'collection' | 'columns-gap-filled' | 'columns-gap' | 'console/error' | 'console/exception' | 'console/info' | 'console/warning' | 'console' | 'controller' | 'cookies' | 'copy' | 'credit-card-front' | 'cross' | 'cubes' | 'cursor-trash' | 'dash' | 'dashboard-icn' | 'desktop' | 'device' | 'diagram-3' | 'dizzy' | 'door-closed' | 'doublecheck' | 'download' | 'drag' | 'edit' | 'ellipsis-v' | 'enter' | 'envelope-check' | 'envelope-x' | 'envelope' | 'errors-icon' | 'event/click' | 'event/click_hesitation' | 'event/clickrage' | 'event/code' | 'event/i-cursor' | 'event/input' | 'event/input_hesitation' | 'event/link' | 'event/location' | 'event/mouse_thrashing' | 'event/resize' | 'event/view' | 'exclamation-circle-fill' | 'exclamation-circle' | 'exclamation-triangle' | 'expand-wide' | 'explosion' | 'external-link-alt' | 'eye-slash-fill' | 'eye-slash' | 'eye' | 'fetch' | 'fflag-multi' | 'fflag-single' | 'file-code' | 'file-medical-alt' | 'file-pdf' | 'file' | 'files' | 'filter' | 'filters/arrow-return-right' | 'filters/browser' | 'filters/click' | 'filters/clickrage' | 'filters/code' | 'filters/console' | 'filters/country' | 'filters/cpu-load' | 'filters/custom' | 'filters/device' | 'filters/dom-complete' | 'filters/duration' | 'filters/error' | 'filters/fetch-failed' | 'filters/fetch' | 'filters/file-code' | 'filters/graphql' | 'filters/i-cursor' | 'filters/input' | 'filters/lcpt' | 'filters/link' | 'filters/location' | 'filters/memory-load' | 'filters/metadata' | 'filters/os' | 'filters/perfromance-network-request' | 'filters/platform' | 'filters/referrer' | 'filters/resize' | 'filters/rev-id' | 'filters/state-action' | 'filters/ttfb' | 'filters/user-alt' | 'filters/userid' | 'filters/view' | 'flag-na' | 'folder-plus' | 'folder2' | 'fullscreen' | 'funnel/cpu-fill' | 'funnel/cpu' | 'funnel/dizzy' | 'funnel/emoji-angry-fill' | 'funnel/emoji-angry' | 'funnel/emoji-dizzy-fill' | 'funnel/exclamation-circle-fill' | 'funnel/exclamation-circle' | 'funnel/file-earmark-break-fill' | 'funnel/file-earmark-break' | 'funnel/file-earmark-minus-fill' | 'funnel/file-earmark-minus' | 'funnel/file-medical-alt' | 'funnel/file-x' | 'funnel/hdd-fill' | 'funnel/hourglass-top' | 'funnel/image-fill' | 'funnel/image' | 'funnel/microchip' | 'funnel/mouse' | 'funnel/patch-exclamation-fill' | 'funnel/sd-card' | 'funnel-fill' | 'funnel-new' | 'funnel' | 'gear-fill' | 'gear' | 'geo-alt-fill-custom' | 'github' | 'graph-up-arrow' | 'graph-up' | 'grid-1x2' | 'grid-3x3' | 'grid-check' | 'grid-horizontal' | 'grid' | 'grip-horizontal' | 'hash' | 'hdd-stack' | 'headset' | 'heart-rate' | 'high-engagement' | 'history' | 'hourglass-start' | 'ic-errors' | 'ic-network' | 'ic-rage' | 'ic-resources' | 'id-card' | 'image' | 'info-circle-fill' | 'info-circle' | 'info-square' | 'info' | 'input-hesitation' | 'inspect' | 'integrations/assist' | 'integrations/bugsnag-text' | 'integrations/bugsnag' | 'integrations/cloudwatch-text' | 'integrations/cloudwatch' | 'integrations/datadog' | 'integrations/elasticsearch-text' | 'integrations/elasticsearch' | 'integrations/github' | 'integrations/graphql' | 'integrations/jira-text' | 'integrations/jira' | 'integrations/mobx' | 'integrations/newrelic-text' | 'integrations/newrelic' | 'integrations/ngrx' | 'integrations/openreplay-text' | 'integrations/openreplay' | 'integrations/redux' | 'integrations/rollbar-text' | 'integrations/rollbar' | 'integrations/segment' | 'integrations/sentry-text' | 'integrations/sentry' | 'integrations/slack-bw' | 'integrations/slack' | 'integrations/stackdriver' | 'integrations/sumologic-text' | 'integrations/sumologic' | 'integrations/teams-white' | 'integrations/teams' | 'integrations/vuejs' | 'journal-code' | 'key' | 'layer-group' | 'layers-half' | 'lightbulb-on' | 'lightbulb' | 'link-45deg' | 'list-alt' | 'list-arrow' | 'list-ul' | 'list' | 'lock-alt' | 'magic' | 'map-marker-alt' | 'memory' | 'mic-mute' | 'mic' | 'minus' | 'mobile' | 'mouse-alt' | 'network' | 'next1' | 'no-dashboard' | 'no-metrics-chart' | 'no-metrics' | 'no-recordings' | 'os/android' | 'os/chrome_os' | 'os/fedora' | 'os/ios' | 'os/linux' | 'os/mac_os_x' | 'os/other' | 'os/ubuntu' | 'os/windows' | 'os' | 'pause-fill' | 'pause' | 'pdf-download' | 'pencil-stop' | 'pencil' | 'people' | 'percent' | 'performance-icon' | 'person-border' | 'person-fill' | 'person' | 'pie-chart-fill' | 'pin-fill' | 'play-circle-bold' | 'play-circle-light' | 'play-circle' | 'play-fill-new' | 'play-fill' | 'play-hover' | 'play' | 'plug' | 'plus-circle' | 'plus-lg' | 'plus' | 'pointer-sessions-search' | 'prev1' | 'pulse' | 'puzzle-piece' | 'puzzle' | 'question-circle' | 'question-lg' | 'quote-left' | 'quote-right' | 'quotes' | 'record-btn' | 'record-circle' | 'redo-back' | 'redo' | 'remote-control' | 'replay-10' | 'resources-icon' | 'safe-fill' | 'safe' | 'sandglass' | 'search' | 'search_notification' | 'server' | 'share-alt' | 'shield-lock' | 'signpost-split' | 'signup' | 'skip-forward-fill' | 'skip-forward' | 'slack' | 'slash-circle' | 'sleep' | 'sliders' | 'social/slack' | 'social/trello' | 'speedometer2' | 'spinner' | 'star-solid' | 'star' | 'step-forward' | 'stickies' | 'stop-record-circle' | 'stopwatch' | 'store' | 'sync-alt' | 'table-new' | 'table' | 'tablet-android' | 'tachometer-slow' | 'tachometer-slowest' | 'tags' | 'team-funnel' | 'telephone-fill' | 'telephone' | 'terminal' | 'text-paragraph' | 'toggles' | 'tools' | 'trash' | 'turtle' | 'user-alt' | 'user-circle' | 'user-friends' | 'users' | 'vendors/graphql' | 'vendors/mobx' | 'vendors/ngrx' | 'vendors/redux' | 'vendors/vuex' | 'web-vitals' | 'wifi' | 'window-alt' | 'window-restore' | 'window-x' | 'window' | 'zoom-in'; interface Props { name: IconNames; @@ -72,13 +72,13 @@ const SVG = (props: Props) => { case 'avatar/icn_wild_bore': return ; case 'ban': return ; case 'bar-chart-line': return ; - case 'bar-pencil': return ; + case 'bar-pencil': return ; case 'bell-fill': return ; case 'bell-plus': return ; case 'bell-slash': return ; case 'bell': return ; case 'binoculars': return ; - case 'book-doc': return ; + case 'book-doc': return ; case 'book': return ; case 'browser/browser': return ; case 'browser/chrome': return ; @@ -96,7 +96,7 @@ const SVG = (props: Props) => { case 'calendar-check': return ; case 'calendar-day': return ; case 'calendar': return ; - case 'call': return ; + case 'call': return ; case 'camera-alt': return ; case 'camera-video-off': return ; case 'camera-video': return ; @@ -108,6 +108,7 @@ const SVG = (props: Props) => { case 'caret-right-fill': return ; case 'caret-up-fill': return ; case 'chat-dots': return ; + case 'chat-left-text': return ; case 'chat-right-text': return ; case 'chat-square-quote': return ; case 'check-circle-fill': return ; @@ -121,8 +122,8 @@ const SVG = (props: Props) => { case 'chevron-up': return ; case 'circle-fill': return ; case 'circle': return ; - case 'click-hesitation': return ; - case 'click-rage': return ; + case 'click-hesitation': return ; + case 'click-rage': return ; case 'clipboard-list-check': return ; case 'clock': return ; case 'close': return ; @@ -144,22 +145,9 @@ const SVG = (props: Props) => { case 'credit-card-front': return ; case 'cross': return ; case 'cubes': return ; - case 'cursor-trash': return ; + case 'cursor-trash': return ; case 'dash': return ; - case 'dashboard-icn': return ; - case 'db-icons/icn-card-clickMap': return ; - case 'db-icons/icn-card-errors': return ; - case 'db-icons/icn-card-funnel': return ; - case 'db-icons/icn-card-funnels': return ; - case 'db-icons/icn-card-insights': return ; - case 'db-icons/icn-card-library': return ; - case 'db-icons/icn-card-mapchart': return ; - case 'db-icons/icn-card-performance': return ; - case 'db-icons/icn-card-resources': return ; - case 'db-icons/icn-card-table': return ; - case 'db-icons/icn-card-timeseries': return ; - case 'db-icons/icn-card-userPath': return ; - case 'db-icons/icn-card-webVitals': return ; + case 'dashboard-icn': return ; case 'desktop': return ; case 'device': return ; case 'diagram-3': return ; @@ -176,19 +164,20 @@ const SVG = (props: Props) => { case 'envelope': return ; case 'errors-icon': return ; case 'event/click': return ; - case 'event/click_hesitation': return ; + case 'event/click_hesitation': return ; case 'event/clickrage': return ; case 'event/code': return ; case 'event/i-cursor': return ; case 'event/input': return ; - case 'event/input_hesitation': return ; + case 'event/input_hesitation': return ; case 'event/link': return ; case 'event/location': return ; - case 'event/mouse_thrashing': return ; + case 'event/mouse_thrashing': return ; case 'event/resize': return ; case 'event/view': return ; - case 'exclamation-circle-fill': return ; + case 'exclamation-circle-fill': return ; case 'exclamation-circle': return ; + case 'exclamation-triangle': return ; case 'expand-wide': return ; case 'explosion': return ; case 'external-link-alt': return ; @@ -200,7 +189,7 @@ const SVG = (props: Props) => { case 'fflag-single': return ; case 'file-code': return ; case 'file-medical-alt': return ; - case 'file-pdf': return ; + case 'file-pdf': return ; case 'file': return ; case 'files': return ; case 'filter': return ; @@ -211,13 +200,13 @@ const SVG = (props: Props) => { case 'filters/code': return ; case 'filters/console': return ; case 'filters/country': return ; - case 'filters/cpu-load': return ; + case 'filters/cpu-load': return ; case 'filters/custom': return ; case 'filters/device': return ; case 'filters/dom-complete': return ; case 'filters/duration': return ; case 'filters/error': return ; - case 'filters/fetch-failed': return ; + case 'filters/fetch-failed': return ; case 'filters/fetch': return ; case 'filters/file-code': return ; case 'filters/graphql': return ; @@ -229,13 +218,13 @@ const SVG = (props: Props) => { case 'filters/memory-load': return ; case 'filters/metadata': return ; case 'filters/os': return ; - case 'filters/perfromance-network-request': return ; + case 'filters/perfromance-network-request': return ; case 'filters/platform': return ; case 'filters/referrer': return ; case 'filters/resize': return ; case 'filters/rev-id': return ; case 'filters/state-action': return ; - case 'filters/ttfb': return ; + case 'filters/ttfb': return ; case 'filters/user-alt': return ; case 'filters/userid': return ; case 'filters/view': return ; @@ -297,7 +286,7 @@ const SVG = (props: Props) => { case 'info-circle': return ; case 'info-square': return ; case 'info': return ; - case 'input-hesitation': return ; + case 'input-hesitation': return ; case 'inspect': return ; case 'integrations/assist': return ; case 'integrations/bugsnag-text': return ; @@ -313,17 +302,17 @@ const SVG = (props: Props) => { case 'integrations/jira': return ; case 'integrations/mobx': return ; case 'integrations/newrelic-text': return ; - case 'integrations/newrelic': return ; + case 'integrations/newrelic': return ; case 'integrations/ngrx': return ; case 'integrations/openreplay-text': return ; case 'integrations/openreplay': return ; case 'integrations/redux': return ; case 'integrations/rollbar-text': return ; - case 'integrations/rollbar': return ; + case 'integrations/rollbar': return ; case 'integrations/segment': return ; case 'integrations/sentry-text': return ; case 'integrations/sentry': return ; - case 'integrations/slack-bw': return ; + case 'integrations/slack-bw': return ; case 'integrations/slack': return ; case 'integrations/stackdriver': return ; case 'integrations/sumologic-text': return ; @@ -334,6 +323,7 @@ const SVG = (props: Props) => { case 'journal-code': return ; case 'key': return ; case 'layer-group': return ; + case 'layers-half': return ; case 'lightbulb-on': return ; case 'lightbulb': return ; case 'link-45deg': return ; @@ -350,12 +340,12 @@ const SVG = (props: Props) => { case 'minus': return ; case 'mobile': return ; case 'mouse-alt': return ; - case 'network': return ; + case 'network': return ; case 'next1': return ; - case 'no-dashboard': return ; + case 'no-dashboard': return ; case 'no-metrics-chart': return ; - case 'no-metrics': return ; - case 'no-recordings': return ; + case 'no-metrics': return ; + case 'no-recordings': return ; case 'os/android': return ; case 'os/chrome_os': return ; case 'os/fedora': return ; @@ -369,7 +359,7 @@ const SVG = (props: Props) => { case 'pause-fill': return ; case 'pause': return ; case 'pdf-download': return ; - case 'pencil-stop': return ; + case 'pencil-stop': return ; case 'pencil': return ; case 'people': return ; case 'percent': return ; @@ -390,7 +380,7 @@ const SVG = (props: Props) => { case 'plus-circle': return ; case 'plus-lg': return ; case 'plus': return ; - case 'pointer-sessions-search': return ; + case 'pointer-sessions-search': return ; case 'prev1': return ; case 'pulse': return ; case 'puzzle-piece': return ; @@ -444,8 +434,9 @@ const SVG = (props: Props) => { case 'team-funnel': return ; case 'telephone-fill': return ; case 'telephone': return ; + case 'terminal': return ; case 'text-paragraph': return ; - case 'toggles': return ; + case 'toggles': return ; case 'tools': return ; case 'trash': return ; case 'turtle': return ; diff --git a/frontend/app/svg/icons/chat-left-text.svg b/frontend/app/svg/icons/chat-left-text.svg new file mode 100644 index 000000000..fe60b0c68 --- /dev/null +++ b/frontend/app/svg/icons/chat-left-text.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/frontend/app/svg/icons/exclamation-triangle.svg b/frontend/app/svg/icons/exclamation-triangle.svg new file mode 100644 index 000000000..84b3ce962 --- /dev/null +++ b/frontend/app/svg/icons/exclamation-triangle.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/frontend/app/svg/icons/layers-half.svg b/frontend/app/svg/icons/layers-half.svg new file mode 100644 index 000000000..c7dc94531 --- /dev/null +++ b/frontend/app/svg/icons/layers-half.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/app/svg/icons/terminal.svg b/frontend/app/svg/icons/terminal.svg new file mode 100644 index 000000000..db057fd26 --- /dev/null +++ b/frontend/app/svg/icons/terminal.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 6839bc5ee..871f7d36b 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -35,6 +35,9 @@ module.exports = { 'border-main': `0 0 0 1px ${colors['main']}`, 'border-gray': '0 0 0 1px #999' }, + button: { + 'background-color': 'red' + } } }, variants: { @@ -42,6 +45,6 @@ module.exports = { }, plugins: [], corePlugins: { - preflight: false + // preflight: false } };