diff --git a/api/chalicelib/core/autocomplete.py b/api/chalicelib/core/autocomplete.py
index 06741cbe5..00eff7d61 100644
--- a/api/chalicelib/core/autocomplete.py
+++ b/api/chalicelib/core/autocomplete.py
@@ -25,24 +25,24 @@ def __get_autocomplete_table(value, project_id):
if e == schemas.FilterType.user_country:
c_list = countries.get_country_code_autocomplete(value)
if len(c_list) > 0:
- sub_queries.append(f"""(SELECT DISTINCT ON(value) type, value
+ sub_queries.append(f"""(SELECT DISTINCT ON(value) '{e.value}' AS _type, value
FROM {TABLE}
WHERE project_id = %(project_id)s
- AND type= '{e}'
+ AND type= '{e.value.upper()}'
AND value IN %(c_list)s)""")
continue
- sub_queries.append(f"""(SELECT type, value
+ sub_queries.append(f"""(SELECT '{e.value}' AS _type, value
FROM {TABLE}
WHERE project_id = %(project_id)s
- AND type= '{e}'
+ AND type= '{e.value.upper()}'
AND value ILIKE %(svalue)s
ORDER BY value
LIMIT 5)""")
if len(value) > 2:
- sub_queries.append(f"""(SELECT type, value
+ sub_queries.append(f"""(SELECT '{e.value}' AS _type, value
FROM {TABLE}
WHERE project_id = %(project_id)s
- AND type= '{e}'
+ AND type= '{e.value.upper()}'
AND value ILIKE %(value)s
ORDER BY value
LIMIT 5)""")
@@ -62,8 +62,11 @@ def __get_autocomplete_table(value, project_id):
print(value)
print("--------------------")
raise err
- results = helper.list_to_camel_case(cur.fetchall())
- return results
+ results = cur.fetchall()
+ for r in results:
+ r["type"] = r.pop("_type")
+ results = helper.list_to_camel_case(results)
+ return results
def __generic_query(typename, value_length=None):
diff --git a/ee/api/chalicelib/core/autocomplete_exp.py b/ee/api/chalicelib/core/autocomplete_exp.py
index 2e1a3f002..03ca85957 100644
--- a/ee/api/chalicelib/core/autocomplete_exp.py
+++ b/ee/api/chalicelib/core/autocomplete_exp.py
@@ -25,24 +25,24 @@ def __get_autocomplete_table(value, project_id):
if e == schemas.FilterType.user_country:
c_list = countries.get_country_code_autocomplete(value)
if len(c_list) > 0:
- sub_queries.append(f"""(SELECT DISTINCT ON(value) type, value
+ sub_queries.append(f"""(SELECT DISTINCT ON(value) '{e.value}' AS _type, value
FROM {TABLE}
WHERE project_id = %(project_id)s
- AND type= '{e}'
+ AND type= '{e.value.upper()}'
AND value IN %(c_list)s)""")
continue
- sub_queries.append(f"""(SELECT type, value
+ sub_queries.append(f"""(SELECT '{e.value}' AS _type, value
FROM {TABLE}
WHERE project_id = %(project_id)s
- AND type= '{e}'
+ AND type= '{e.value.upper()}'
AND value ILIKE %(svalue)s
ORDER BY value
LIMIT 5)""")
if len(value) > 2:
- sub_queries.append(f"""(SELECT type, value
+ sub_queries.append(f"""(SELECT '{e.value}' AS _type, value
FROM {TABLE}
WHERE project_id = %(project_id)s
- AND type= '{e}'
+ AND type= '{e.value.upper()}'
AND value ILIKE %(value)s
ORDER BY value
LIMIT 5)""")
@@ -64,7 +64,10 @@ def __get_autocomplete_table(value, project_id):
print(value)
print("--------------------")
raise err
- return results
+ for r in results:
+ r["type"] = r.pop("_type")
+ results = helper.list_to_camel_case(results)
+ return results
def __generic_query(typename, value_length=None):
diff --git a/frontend/app/api_client.js b/frontend/app/api_client.js
index a9e092486..c9a041cc9 100644
--- a/frontend/app/api_client.js
+++ b/frontend/app/api_client.js
@@ -95,13 +95,11 @@ export default class APIClient {
edp = `${ edp }/${ this.siteId }`
}
return fetch(edp + path, this.init)
- .then(response => {
+ .then((response) => {
if (response.ok) {
return response
} else {
- throw new Error(
- `! ${this.init.method} error on ${path}; ${response.status}`
- )
+ return Promise.reject({ message: `! ${this.init.method} error on ${path}; ${response.status}`, response });
}
})
}
diff --git a/frontend/app/api_middleware.js b/frontend/app/api_middleware.js
index f98ad344b..9ef371f01 100644
--- a/frontend/app/api_middleware.js
+++ b/frontend/app/api_middleware.js
@@ -33,9 +33,10 @@ export default () => (next) => (action) => {
next({ type: UPDATE_JWT, data: jwt });
}
})
- .catch((e) => {
+ .catch(async (e) => {
+ const data = await e.response.json();
logger.error('Error during API request. ', e);
- return next({ type: FAILURE, errors: parseError(e) });
+ return next({ type: FAILURE, errors: parseError(data.errors) });
});
};
diff --git a/frontend/app/components/Client/CustomFields/CustomFieldForm.js b/frontend/app/components/Client/CustomFields/CustomFieldForm.js
index 22108deee..15d24be3d 100644
--- a/frontend/app/components/Client/CustomFields/CustomFieldForm.js
+++ b/frontend/app/components/Client/CustomFields/CustomFieldForm.js
@@ -39,17 +39,6 @@ class CustomFieldForm extends React.PureComponent {
placeholder="Field Name"
/>
-
- {/* TODO: errors state is not updating properly */}
- {/* {errors && (
-
- {errors.map((error, i) => (
-
- {error.message}
-
- ))}
-
- )} */}
diff --git a/frontend/app/components/Client/CustomFields/CustomFields.js b/frontend/app/components/Client/CustomFields/CustomFields.js
index 30844dec2..d5843425a 100644
--- a/frontend/app/components/Client/CustomFields/CustomFields.js
+++ b/frontend/app/components/Client/CustomFields/CustomFields.js
@@ -11,6 +11,7 @@ import ListItem from './ListItem';
import { confirm } from 'UI';
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
import { useModal } from 'App/components/Modal';
+import { toast } from 'react-toastify';
function CustomFields(props) {
const [currentSite, setCurrentSite] = React.useState(props.sites.get(0));
@@ -25,10 +26,11 @@ function CustomFields(props) {
}, []);
const save = (field) => {
- props.save(currentSite.id, field).then(() => {
- const { errors } = props;
- if (!errors || errors.size === 0) {
+ props.save(currentSite.id, field).then((response) => {
+ if (!response || !response.errors || response.errors.size === 0) {
hideModal();
+ } else {
+ toast.error(response.errors[0]);
}
});
};
@@ -73,7 +75,7 @@ function CustomFields(props) {
-
+
diff --git a/frontend/app/components/Client/Integrations/Integrations.tsx b/frontend/app/components/Client/Integrations/Integrations.tsx
index d1f53f56d..f1851f919 100644
--- a/frontend/app/components/Client/Integrations/Integrations.tsx
+++ b/frontend/app/components/Client/Integrations/Integrations.tsx
@@ -64,7 +64,7 @@ function Integrations(props: Props) {
}, []);
const onClick = (integration: any, width: number) => {
- if (integration.slug) {
+ if (integration.slug && integration.slug !== 'slack' && integration.slug !== 'msteams') {
props.fetch(integration.slug, props.siteId);
}
@@ -241,30 +241,15 @@ const integrations = [
description:
"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', slug: 'redux', icon: 'integrations/redux', component: },
- { title: 'VueX', slug: 'vuex', icon: 'integrations/vuejs', component: },
- { title: 'Pinia', slug: 'pinia', icon: 'integrations/pinia', component: },
- {
- title: 'GraphQL',
- slug: 'graphql',
- icon: 'integrations/graphql',
- component: ,
- },
- { title: 'NgRx', slug: 'ngrx', icon: 'integrations/ngrx', component: },
- { title: 'MobX', slug: 'mobx', icon: 'integrations/mobx', component: },
- {
- title: 'Profiler',
- slug: 'profiler',
- icon: 'integrations/openreplay',
- component: ,
- },
- {
- title: 'Assist',
- slug: 'assist',
- icon: 'integrations/openreplay',
- component: ,
- },
- { title: 'Zustand', slug: 'zustand', icon: '', header: '🐻', component: },
+ { 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: },
],
},
];
diff --git a/frontend/app/components/Client/ProfileSettings/ChangePassword.js b/frontend/app/components/Client/ProfileSettings/ChangePassword.js
index d0222abf0..2640ab612 100644
--- a/frontend/app/components/Client/ProfileSettings/ChangePassword.js
+++ b/frontend/app/components/Client/ProfileSettings/ChangePassword.js
@@ -3,6 +3,7 @@ import { connect } from 'react-redux';
import { Button, Message, Form, Input } from 'UI';
import styles from './profileSettings.module.css';
import { updatePassword } from 'Duck/user';
+import { toast } from 'react-toastify';
const ERROR_DOESNT_MATCH = "Passwords doesn't match";
const MIN_LENGTH = 8;
@@ -48,12 +49,15 @@ export default class ChangePassword extends React.PureComponent {
oldPassword,
newPassword,
})
- .then(() => {
- if (this.props.passwordErrors.size === 0) {
- this.setState({
- ...defaultState,
- success: true,
- });
+ .then((e) => {
+ const success = !e || !e.errors || e.errors.length === 0;
+ this.setState({
+ ...defaultState,
+ success: success,
+ show: !success
+ });
+ if (success) {
+ toast.success(`Successfully changed password`);
}
});
};
@@ -98,7 +102,7 @@ export default class ChangePassword extends React.PureComponent {
{ERROR_DOESNT_MATCH}
-
+
@@ -107,9 +111,6 @@ export default class ChangePassword extends React.PureComponent {
Cancel
-
- {'Successfully changed the password!'}
-
) : (
this.setState({ show: true })}>
diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventGroupWrapper.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventGroupWrapper.js
index 1ec053462..924be9f2c 100644
--- a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventGroupWrapper.js
+++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventGroupWrapper.js
@@ -88,7 +88,6 @@ class EventGroupWrapper extends React.Component {
)}
{isNote ? (
m.id === event.userId)?.email || event.userId}
note={event}
filterOutNote={filterOutNote}
onEdit={this.props.setEditNoteTooltip}
diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/NoteEvent.tsx b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/NoteEvent.tsx
index 676b1f901..a09869ba5 100644
--- a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/NoteEvent.tsx
+++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/NoteEvent.tsx
@@ -14,7 +14,6 @@ import { TeamBadge } from 'Shared/SessionListContainer/components/Notes';
interface Props {
note: Note;
noEdit: boolean;
- userEmail: string;
filterOutNote: (id: number) => void;
onEdit: (noteTooltipObj: Record) => void;
}
@@ -86,7 +85,7 @@ function NoteEvent(props: Props) {
whiteSpace: 'nowrap',
}}
>
- {props.userEmail}, {props.userEmail}
+ {props.note.userName}
{formatTimeOrDate(props.note.createdAt as unknown as number, timezone)}
diff --git a/frontend/app/components/Session/WebPlayer.tsx b/frontend/app/components/Session/WebPlayer.tsx
index 0c2bb89ab..eb4fbf8db 100644
--- a/frontend/app/components/Session/WebPlayer.tsx
+++ b/frontend/app/components/Session/WebPlayer.tsx
@@ -9,7 +9,6 @@ import withLocationHandlers from 'HOCs/withLocationHandlers';
import { useStore } from 'App/mstore';
import PlayerBlockHeader from './Player/ReplayPlayer/PlayerBlockHeader';
import ReadNote from '../Session_/Player/Controls/components/ReadNote';
-import { fetchList as fetchMembers } from 'Duck/member';
import PlayerContent from './Player/ReplayPlayer/PlayerContent';
import { IPlayerContext, PlayerContext, defaultContextValue } from './playerContext';
import { observer } from 'mobx-react-lite';
@@ -47,8 +46,6 @@ function WebPlayer(props: any) {
);
setContextValue({ player: WebPlayerInst, store: PlayerStore });
- props.fetchMembers();
-
notesStore.fetchSessionNotes(session.sessionId).then((r) => {
const note = props.query.get('note');
if (note) {
@@ -58,7 +55,6 @@ function WebPlayer(props: any) {
}
})
-
const jumpToTime = props.query.get('jumpto');
const freeze = props.query.get('freeze')
if (jumpToTime) {
@@ -112,10 +108,6 @@ function WebPlayer(props: any) {
{showNoteModal ? (
) => m.id === noteItem?.userId)?.email
- || ''
- }
note={noteItem}
onClose={onNoteClose}
notFound={!noteItem}
@@ -140,6 +132,5 @@ export default connect(
toggleFullscreen,
closeBottomBlock,
fetchList,
- fetchMembers,
}
)(withLocationHandlers()(observer(WebPlayer)));
diff --git a/frontend/app/components/Session_/BugReport/components/Steps.tsx b/frontend/app/components/Session_/BugReport/components/Steps.tsx
index 8307eea47..2f1517d24 100644
--- a/frontend/app/components/Session_/BugReport/components/Steps.tsx
+++ b/frontend/app/components/Session_/BugReport/components/Steps.tsx
@@ -46,6 +46,12 @@ function Steps({ xrayProps, notes, members }: Props) {
bugReportStore.resetSteps();
};
+ React.useEffect(() => {
+ if (bugReportStore.sessionEventSteps.length < RADIUS && bugReportStore.sessionEventSteps.length > 0) {
+ setRadius(bugReportStore.sessionEventSteps.length);
+ }
+ }, [bugReportStore.sessionEventSteps])
+
return (
Steps to reproduce
@@ -63,7 +69,7 @@ function Steps({ xrayProps, notes, members }: Props) {
STEPS
{timePointer > 0 ? (
-
+
) : null}
diff --git a/frontend/app/components/Session_/BugReport/components/StepsComponents/StepRadius.tsx b/frontend/app/components/Session_/BugReport/components/StepsComponents/StepRadius.tsx
index 543d2c803..afa8b15c7 100644
--- a/frontend/app/components/Session_/BugReport/components/StepsComponents/StepRadius.tsx
+++ b/frontend/app/components/Session_/BugReport/components/StepsComponents/StepRadius.tsx
@@ -4,9 +4,10 @@ import { Tooltip } from 'UI'
interface Props {
pickRadius: number;
setRadius: (v: number) => void;
+ stepsNum: number;
}
-function StepRadius({ pickRadius, setRadius }: Props) {
+function StepRadius({ pickRadius, setRadius, stepsNum }: Props) {
return (
@@ -18,7 +19,7 @@ function StepRadius({ pickRadius, setRadius }: Props) {
setRadius(pickRadius + 1)}
+ onClick={() => pickRadius < Math.floor(stepsNum/2) ? setRadius(pickRadius + 1) : null}
>
+1
diff --git a/frontend/app/components/Session_/BugReport/utils.ts b/frontend/app/components/Session_/BugReport/utils.ts
index a97e275d7..053ece364 100644
--- a/frontend/app/components/Session_/BugReport/utils.ts
+++ b/frontend/app/components/Session_/BugReport/utils.ts
@@ -62,7 +62,7 @@ export function getClosestEventStep(time: number, arr: Step[]) {
export const selectEventSteps = (steps: Step[], targetTime: number, radius: number) => {
const { targetStep, index } = getClosestEventStep(targetTime, steps)
- const stepsBeforeEvent = steps.slice(index - radius, index)
+ const stepsBeforeEvent = steps.slice(Math.max(index - radius, 0), index)
const stepsAfterEvent = steps.slice(index + 1, index + 1 + radius)
return [...stepsBeforeEvent, targetStep, ...stepsAfterEvent]
diff --git a/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js b/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js
index 1ec053462..924be9f2c 100644
--- a/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js
+++ b/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js
@@ -88,7 +88,6 @@ class EventGroupWrapper extends React.Component {
)}
{isNote ? (
m.id === event.userId)?.email || event.userId}
note={event}
filterOutNote={filterOutNote}
onEdit={this.props.setEditNoteTooltip}
diff --git a/frontend/app/components/Session_/EventsBlock/NoteEvent.tsx b/frontend/app/components/Session_/EventsBlock/NoteEvent.tsx
index 676b1f901..a09869ba5 100644
--- a/frontend/app/components/Session_/EventsBlock/NoteEvent.tsx
+++ b/frontend/app/components/Session_/EventsBlock/NoteEvent.tsx
@@ -14,7 +14,6 @@ import { TeamBadge } from 'Shared/SessionListContainer/components/Notes';
interface Props {
note: Note;
noEdit: boolean;
- userEmail: string;
filterOutNote: (id: number) => void;
onEdit: (noteTooltipObj: Record) => void;
}
@@ -86,7 +85,7 @@ function NoteEvent(props: Props) {
whiteSpace: 'nowrap',
}}
>
- {props.userEmail}, {props.userEmail}
+ {props.note.userName}
{formatTimeOrDate(props.note.createdAt as unknown as number, timezone)}
diff --git a/frontend/app/components/Session_/Player/Controls/components/ReadNote.tsx b/frontend/app/components/Session_/Player/Controls/components/ReadNote.tsx
index 988412b2d..550c8f99f 100644
--- a/frontend/app/components/Session_/Player/Controls/components/ReadNote.tsx
+++ b/frontend/app/components/Session_/Player/Controls/components/ReadNote.tsx
@@ -58,7 +58,7 @@ function ReadNote(props: Props) {
-
{props.userEmail}
+
{props.note.userName}
{formatTimeOrDate(props.note.createdAt as unknown as number, timezone)}
diff --git a/frontend/app/components/shared/SessionListContainer/SessionListContainer.tsx b/frontend/app/components/shared/SessionListContainer/SessionListContainer.tsx
index 6a863e69b..67b1e12e7 100644
--- a/frontend/app/components/shared/SessionListContainer/SessionListContainer.tsx
+++ b/frontend/app/components/shared/SessionListContainer/SessionListContainer.tsx
@@ -15,9 +15,6 @@ function SessionListContainer({
fetchMembers: () => void;
members: object[];
}) {
- React.useEffect(() => {
- fetchMembers();
- }, []);
return (
diff --git a/frontend/app/components/shared/SessionListContainer/components/Notes/NoteItem.tsx b/frontend/app/components/shared/SessionListContainer/components/Notes/NoteItem.tsx
index 1ece07d7f..05113f523 100644
--- a/frontend/app/components/shared/SessionListContainer/components/Notes/NoteItem.tsx
+++ b/frontend/app/components/shared/SessionListContainer/components/Notes/NoteItem.tsx
@@ -13,7 +13,6 @@ import TeamBadge from './TeamBadge';
interface Props {
note: Note;
- userEmail: string;
}
function NoteItem(props: Props) {
@@ -69,7 +68,7 @@ function NoteItem(props: Props) {
) : null}
By
- {props.userEmail},{' '}
+ {props.note.userName},{' '}
{formatTimeOrDate(props.note.createdAt as unknown as number, timezone)}
{!props.note.isPublic ? null :
}
diff --git a/frontend/app/components/shared/SessionListContainer/components/Notes/NoteList.tsx b/frontend/app/components/shared/SessionListContainer/components/Notes/NoteList.tsx
index 0a66cd0eb..3c48d2af0 100644
--- a/frontend/app/components/shared/SessionListContainer/components/Notes/NoteList.tsx
+++ b/frontend/app/components/shared/SessionListContainer/components/Notes/NoteList.tsx
@@ -35,10 +35,7 @@ function NotesList({ members }: { members: Array
> }) {
{sliceListPerPage(list, notesStore.page - 1, notesStore.pageSize).map((note) => (
- m.id === note.userId)?.email || note.userId}
- />
+
))}
diff --git a/frontend/app/components/shared/SharePopup/SharePopup.js b/frontend/app/components/shared/SharePopup/SharePopup.js
index 1df16cf6d..8e09f819c 100644
--- a/frontend/app/components/shared/SharePopup/SharePopup.js
+++ b/frontend/app/components/shared/SharePopup/SharePopup.js
@@ -91,15 +91,13 @@ export default class SharePopup extends React.PureComponent {
const { trigger, channels, msTeamsChannels, showCopyLink = false } = this.props;
const { comment, channelId, teamsChannel, loading } = this.state;
- // const slackOptions = channels
- // .map(({ webhookId, name }) => ({ value: webhookId, label: name }))
- // .toJS();
+ const slackOptions = channels
+ .map(({ webhookId, name }) => ({ value: webhookId, label: name }))
+ .toJS();
- // const msTeamsOptions = msTeamsChannels
- // .map(({ webhookId, name }) => ({ value: webhookId, label: name }))
- // .toJS();
-
- const slackOptions = [], msTeamsOptions = [];
+ const msTeamsOptions = msTeamsChannels
+ .map(({ webhookId, name }) => ({ value: webhookId, label: name }))
+ .toJS();
return (
{
case RESET_PASSWORD.SUCCESS:
case UPDATE_PASSWORD.SUCCESS:
case LOGIN.SUCCESS:
- state.set('account', Account({...action.data.user })).set('loginRequest', { loading: false, errors: [] })
+ state.set('account', Account({...action.data.user })).set('loginRequest', { loading: false, errors: [] }).set('passwordErrors', List())
case SIGNUP.SUCCESS:
state.set('account', Account(action.data.user)).set('onboarding', true);
case REQUEST_RESET_PASSWORD.SUCCESS:
diff --git a/frontend/app/mstore/settingsStore.ts b/frontend/app/mstore/settingsStore.ts
index d9dfb0dd3..7dd584fd3 100644
--- a/frontend/app/mstore/settingsStore.ts
+++ b/frontend/app/mstore/settingsStore.ts
@@ -71,11 +71,16 @@ export default class SettingsStore {
return webhookService.saveWebhook(inst)
.then(data => {
this.webhookInst = new Webhook(data)
- this.webhooks = [...this.webhooks, this.webhookInst]
+ if (inst.webhookId === undefined) this.setWebhooks([...this.webhooks, this.webhookInst])
+ else this.setWebhooks([...this.webhooks.filter(hook => hook.webhookId !== data.webhookId), this.webhookInst])
this.hooksLoading = false
})
}
+ setWebhooks = (webhooks: Webhook[]) => {
+ this.webhooks = webhooks
+ }
+
removeWebhook = (hookId: string) => {
this.hooksLoading = true
return webhookService.removeWebhook(hookId)
diff --git a/frontend/app/services/NotesService.ts b/frontend/app/services/NotesService.ts
index cdecb7c3f..d66316129 100644
--- a/frontend/app/services/NotesService.ts
+++ b/frontend/app/services/NotesService.ts
@@ -32,6 +32,7 @@ export interface Note {
tag: iTag
timestamp: number
userId: number
+ userName: string
}
export interface NotesFilter {