From f6a62d835aad611ca1d4d8ed1afb964edba47e0d Mon Sep 17 00:00:00 2001 From: sylenien Date: Mon, 2 Jan 2023 17:59:16 +0100 Subject: [PATCH] fix(ui): some issue/assignment refactoring --- .../components/SessionList/SessionList.tsx | 2 +- .../Session_/Issues/IssueDetails.js | 2 +- .../components/Session_/Issues/IssueForm.js | 4 +- .../app/components/Session_/Issues/Issues.js | 20 +--- .../app/components/Session_/PlayerBlock.js | 2 +- .../components/SessionList/SessionList.tsx | 4 +- frontend/app/duck/assignments.js | 33 +++---- frontend/app/duck/issues.js | 22 ++--- frontend/app/duck/sessions.ts | 34 ++++--- frontend/app/types/session/assignment.js | 47 ---------- frontend/app/types/session/assignment.ts | 77 +++++++++++++++ frontend/app/types/session/error.ts | 2 +- frontend/app/types/session/errorStack.ts | 2 +- frontend/app/types/session/event.ts | 2 +- frontend/app/types/session/resource.ts | 12 ++- frontend/app/types/session/session.ts | 8 +- frontend/app/types/session/stackEvent.ts | 94 +++++++++---------- 17 files changed, 188 insertions(+), 179 deletions(-) delete mode 100644 frontend/app/types/session/assignment.js create mode 100644 frontend/app/types/session/assignment.ts diff --git a/frontend/app/components/Assist/components/SessionList/SessionList.tsx b/frontend/app/components/Assist/components/SessionList/SessionList.tsx index ad4eb4df5..da3ffbf06 100644 --- a/frontend/app/components/Assist/components/SessionList/SessionList.tsx +++ b/frontend/app/components/Assist/components/SessionList/SessionList.tsx @@ -36,7 +36,7 @@ function SessionList(props: Props) { diff --git a/frontend/app/components/Session_/Issues/IssueDetails.js b/frontend/app/components/Session_/Issues/IssueDetails.js index 9c50c858b..f2f8f5d0c 100644 --- a/frontend/app/components/Session_/Issues/IssueDetails.js +++ b/frontend/app/components/Session_/Issues/IssueDetails.js @@ -53,5 +53,5 @@ export default connect(state => ({ users: state.getIn(['assignments', 'users']), loading: state.getIn(['assignments', 'fetchAssignment', 'loading']), issueTypeIcons: state.getIn(['assignments', 'issueTypeIcons']), - issuesIntegration: state.getIn([ 'issues', 'list']).first() || {}, + issuesIntegration: state.getIn([ 'issues', 'list'])[0] || {}, }))(IssueDetails); diff --git a/frontend/app/components/Session_/Issues/IssueForm.js b/frontend/app/components/Session_/Issues/IssueForm.js index 43a7f382a..2010a2fe5 100644 --- a/frontend/app/components/Session_/Issues/IssueForm.js +++ b/frontend/app/components/Session_/Issues/IssueForm.js @@ -1,7 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; import { Form, Input, Button, CircularLoader, Loader } from 'UI'; -//import { } from 'Duck/issues'; import { addActivity, init, edit, fetchAssignments, fetchMeta } from 'Duck/assignments'; import Select from 'Shared/Select'; @@ -119,7 +118,6 @@ class IssueForm extends React.PureComponent { selection name="assignee" options={userOptions} - // value={ instance.assignee } fluid onChange={this.writeOption} placeholder="Select a user" @@ -153,7 +151,7 @@ class IssueForm extends React.PureComponent { } - show={!loading && list.size === 0} + show={!loading && list.length === 0} > {list.map((session: any) => (
@@ -188,7 +188,7 @@ function SessionList(props: Props) {
Showing {(currentPage - 1) * pageSize + 1} to{' '} - {(currentPage - 1) * pageSize + list.size} of{' '} + {(currentPage - 1) * pageSize + list.length} of{' '} {numberWithCommas(total)} sessions.
{ const users = state.get('users'); - var issueTypes = [] + let issueTypes = [] switch (action.type) { case INIT: action.instance.issueType = issueTypes.length > 0 ? issueTypes[0].id : ''; - return state.set('instance', Assignment(action.instance)); + return state.set('instance', new Assignment(action.instance)); case EDIT: - return state.mergeIn([ 'instance' ], action.instance); + const inst = state.get('instance') + return state.set('instance', new Assignment({ ...inst, ...action.instance })); case FETCH_PROJECTS.SUCCESS: return state.set('projects', List(action.data)).set('projectsFetched', true); case FETCH_ASSIGNMENTS.SUCCESS: - return state.set('list', List(action.data).map(Assignment)); + return state.set('list', List(action.data).map(as => new Assignment(as))); case FETCH_ASSIGNMENT.SUCCESS: - return state.set('activeIssue', Assignment({ ...action.data, users})); + return state.set('activeIssue', new Assignment({ ...action.data, users})); case FETCH_META.SUCCESS: issueTypes = action.data.issueTypes - var issueTypeIcons = {} + const issueTypeIcons = {} issueTypes.forEach(iss => { issueTypeIcons[iss.id] = iss.iconUrl }) @@ -56,12 +56,12 @@ const reducer = (state = initialState, action = {}) => { .set('users', List(action.data.users)) .set('issueTypeIcons', issueTypeIcons) case ADD_ACTIVITY.SUCCESS: - const instance = Assignment(action.data); + const instance = new Assignment(action.data); return listUpdater(state, instance); case ADD_MESSAGE.SUCCESS: const user = users.filter(user => user.id === action.data.author).first(); const activity = new Activity({ type: 'message', user, ...action.data,}); - return state.updateIn([ 'activeIssue', 'activities' ], list => list.push(activity)); + return state.update([ 'activeIssue' ], issue => issue.activities.push(activity)); default: return state; } @@ -79,7 +79,7 @@ export default withRequestState({ export const init = createInit(name); export const edit = createEdit(name); -export function fetchProjects(sessionId) { +export function fetchProjects() { return { types: FETCH_PROJECTS.toArray(), call: client => client.get(`/integrations/issues/list_projects`) @@ -100,13 +100,6 @@ export function fetchAssignments(sessionId) { } } -export function fetchAssigment(sessionId, id) { - return { - types: FETCH_ASSIGNMENT.toArray(), - call: client => client.get(`/sessions/${ sessionId }/assign/${ id }`) - } -} - export function addActivity(sessionId, params) { const data = { ...params, assignee: params.assignee, issueType: params.issueType } return { diff --git a/frontend/app/duck/issues.js b/frontend/app/duck/issues.js index 886025a16..6cc97a96f 100644 --- a/frontend/app/duck/issues.js +++ b/frontend/app/duck/issues.js @@ -9,7 +9,6 @@ import { createInit, createEdit } from './funcTools/crud'; const idKey = 'id'; const name = 'assignment'; const listUpdater = createListUpdater(idKey); -const itemInListUpdater = createItemInListUpdater(idKey); const FETCH_ASSIGNMENTS = new RequestTypes('asignment/FETCH_ASSIGNMENTS'); const FETCH_ISSUE = new RequestTypes('asignment/FETCH_ISSUE'); @@ -23,8 +22,8 @@ const RESET_ACTIVE_ISSUE = 'assignment/RESET_ACTIVE_ISSUE'; const initialState = Map({ list: List(), - instance: Assignment(), - activeIssue: Assignment(), + instance: new Assignment(), + activeIssue: new Assignment(), issueTypes: List(), issueTypeIcons: Set(), users: List(), @@ -39,9 +38,9 @@ const reducer = (state = initialState, action = {}) => { case FETCH_PROJECTS.SUCCESS: return state.set('projects', List(action.data)); case FETCH_ASSIGNMENTS.SUCCESS: - return state.set('list', List(action.data).map(Assignment)); + return state.set('list', action.data.map(as => new Assignment(as))); case ADD_ACTIVITY.SUCCESS: - const instance = Assignment(action.data); + const instance = new Assignment(action.data); return listUpdater(state, instance); case FETCH_META.SUCCESS: issueTypes = action.data.issueTypes; @@ -53,16 +52,16 @@ const reducer = (state = initialState, action = {}) => { .set('users', List(action.data.users)) .set('issueTypeIcons', issueTypeIcons) case FETCH_ISSUE.SUCCESS: - return state.set('activeIssue', Assignment({ ...action.data, users})); + return state.set('activeIssue', new Assignment({ ...action.data, users})); case RESET_ACTIVE_ISSUE: - return state.set('activeIssue', Assignment()); + return state.set('activeIssue', new Assignment()); case ADD_MESSAGE.SUCCESS: const user = users.filter(user => user.id === action.data.author).first(); const activity = new Activity({ type: 'message', user, ...action.data,}); return state.updateIn([ 'activeIssue', 'activities' ], list => list.push(activity)); case INIT: action.instance.issueType = issueTypes.length > 0 ? issueTypes[0].id : ''; - return state.set('instance', Assignment(action.instance)); + return state.set('instance', new Assignment(action.instance)); case EDIT: return state.mergeIn([ 'instance' ], action.instance); default: @@ -101,13 +100,6 @@ export function fetchProjects() { } } -export function fetchIssue(sessionId, id) { - return { - types: FETCH_ISSUE.toArray(), - call: client => client.get(`/sessions/${ sessionId }/assign/jira/${ id }`) - } -} - export function fetchMeta(projectId) { return { types: FETCH_META.toArray(), diff --git a/frontend/app/duck/sessions.ts b/frontend/app/duck/sessions.ts index 7a4ebd29d..11191f9cd 100644 --- a/frontend/app/duck/sessions.ts +++ b/frontend/app/duck/sessions.ts @@ -1,6 +1,7 @@ import { List, Map } from 'immutable'; import Session from 'Types/session'; import ErrorStack from 'Types/session/errorStack'; +import { Location, InjectedEvent } from 'Types/session/event' import Watchdog from 'Types/watchdog'; import { clean as cleanParams } from 'App/api_client'; import withRequestState, { RequestTypes } from './requestStateCreator'; @@ -46,7 +47,7 @@ const defaultDateFilters = { }; const initObj = { - list: List(), + list: [], sessionIds: [], current: new Session(), total: 0, @@ -61,7 +62,7 @@ const initObj = { filteredEvents: null, eventsQuery: '', showChatWindow: false, - liveSessions: List(), + liveSessions: [], visitedEvents: List(), insights: List(), insightFilters: defaultDateFilters, @@ -76,7 +77,12 @@ const initObj = { const initialState = Map(initObj); -const reducer = (state = initialState, action = {}) => { +interface IAction extends Record{ + type: string; + data: any; +} + +const reducer = (state = initialState, action: IAction) => { switch (action.type) { case FETCH_ERROR_STACK.SUCCESS: return state.set('errorStack', List(action.data.trace).map(es => new ErrorStack(es))).set('sourcemapUploaded', action.data.sourcemapUploaded); @@ -87,13 +93,11 @@ const reducer = (state = initialState, action = {}) => { const { sessions, total } = action.data; const list = sessions.map(s => new Session(s)); + console.log(sessions, list, action) return state .set('list', list) - .set('sessionIds', list.map(({ sessionId }) => sessionId).toJS()) - .set( - 'favoriteList', - list.filter(({ favorite }) => favorite) - ) + .set('sessionIds', list.map(({ sessionId }) => sessionId)) + .set('favoriteList', list.filter(({ favorite }) => favorite)) .set('total', total); case FETCH_AUTOPLAY_LIST.SUCCESS: let sessionIds = state.get('sessionIds'); @@ -125,13 +129,13 @@ const reducer = (state = initialState, action = {}) => { const events = action.filter.events; const session = new Session(action.data); - const matching = []; + const matching: number[] = []; - const visitedEvents = []; - const tmpMap = {}; + const visitedEvents: Location[] = []; + const tmpMap = new Set(); session.events.forEach((event) => { - if (event.type === 'LOCATION' && !tmpMap.hasOwnProperty(event.url)) { - tmpMap[event.url] = event.url; + if (event.type === 'LOCATION' && !tmpMap.has(event.url)) { + tmpMap.add(event.url); visitedEvents.push(event); } }); @@ -156,7 +160,7 @@ const reducer = (state = initialState, action = {}) => { .set('host', visitedEvents[0] && visitedEvents[0].host); } case FETCH_FAVORITE_LIST.SUCCESS: - return state.set('favoriteList', List(action.data).map(s => new Session(s))); + return state.set('favoriteList', action.data.map(s => new Session(s))); case TOGGLE_FAVORITE.SUCCESS: { const id = action.sessionId; let mutableState = state @@ -186,7 +190,7 @@ const reducer = (state = initialState, action = {}) => { diff = diff === 0 ? s1.startedAt - s2.startedAt : diff; return action.sign * diff; }; - return state.update('list', (list) => list.sort(comparator)).update('favoriteList', (list) => list.sort(comparator)); + return state.update('list', (list: Session[]) => list.sort(comparator)).update('favoriteList', (list: Session[]) => list.sort(comparator)); } case REDEFINE_TARGET: { // TODO: update for list diff --git a/frontend/app/types/session/assignment.js b/frontend/app/types/session/assignment.js deleted file mode 100644 index ee3e790a6..000000000 --- a/frontend/app/types/session/assignment.js +++ /dev/null @@ -1,47 +0,0 @@ -import Record from 'Types/Record'; -import Activity from './activity'; -import { List } from 'immutable'; -import { DateTime } from 'luxon'; -import { validateName, notEmptyString } from 'App/validate'; - -export default Record({ - id: undefined, - title: '', - timestamp: undefined, - creatorId: undefined, - sessionId: undefined, - projectId: '', - siteId: undefined, - activities: List(), - closed: false, - assignee: '', - commentsCount: undefined, - issueType: '', - description: '', - iconUrl: '' -}, { - fromJS: (assignment) => ({ - ...assignment, - timestamp: assignment.createdAt ? DateTime.fromISO(assignment.createdAt) : undefined, - activities: assignment.comments ? List(assignment.comments).map(activity => { - if (assignment.users) { - activity.user = assignment.users.filter(user => user.id === activity.author).first(); - } - return new Activity(activity) - }) : List() - }), - methods: { - validate: function() { - return !!this.projectId && !!this.issueType && - notEmptyString(this.title) && notEmptyString(this.description) - }, - toCreate: function() { - return { - title: this.title, - description: this.description, - assignee: this.assignee, - issueType: this.issueType - } - } - } -}) diff --git a/frontend/app/types/session/assignment.ts b/frontend/app/types/session/assignment.ts new file mode 100644 index 000000000..a16361ee8 --- /dev/null +++ b/frontend/app/types/session/assignment.ts @@ -0,0 +1,77 @@ +import Activity, { IActivity } from './activity'; +import { DateTime } from 'luxon'; +import { notEmptyString } from 'App/validate'; + +interface IAssignment { + id: string; + title: string; + timestamp: number; + creatorId: string; + sessionId: string; + projectId: string; + siteId: string; + activities: []; + closed: boolean; + assignee: string; + commentsCount: number; + issueType: string; + description: string; + iconUrl: string; + createdAt?: string; + comments: IActivity[] + users: { id: string }[] +} + +export default class Assignment { + id: IAssignment["id"]; + title: IAssignment["title"] = ''; + timestamp: IAssignment["timestamp"]; + creatorId: IAssignment["creatorId"]; + sessionId: IAssignment["sessionId"]; + projectId: IAssignment["projectId"] = ''; + siteId: IAssignment["siteId"]; + activities: IAssignment["activities"]; + closed: IAssignment["closed"]; + assignee: IAssignment["assignee"] = ''; + commentsCount: IAssignment["commentsCount"]; + issueType: IAssignment["issueType"] = ''; + description: IAssignment["description"] = ''; + iconUrl: IAssignment["iconUrl"] = ''; + + constructor(assignment?: IAssignment) { + if (assignment) { + Object.assign(this, { + ...assignment, + timestamp: assignment.createdAt ? DateTime.fromISO(assignment.createdAt) : undefined, + activities: assignment.comments ? assignment.comments.map(activity => { + if (assignment.users) { + // @ts-ignore ??? + activity.user = assignment.users.filter(user => user.id === activity.author)[0]; + } + return new Activity(activity) + }) : [] + }) + } + } + + toJS() { + return this + } + + validate() { + return !!this.projectId && !!this.issueType && notEmptyString(this.title) && notEmptyString(this.description) + } + + get isValid() { + return !!this.projectId && !!this.issueType && notEmptyString(this.title) && notEmptyString(this.description) + } + + toCreate() { + return { + title: this.title, + description: this.description, + assignee: this.assignee, + issueType: this.issueType + } + } +} \ No newline at end of file diff --git a/frontend/app/types/session/error.ts b/frontend/app/types/session/error.ts index fd66e06a8..66d2db096 100644 --- a/frontend/app/types/session/error.ts +++ b/frontend/app/types/session/error.ts @@ -10,7 +10,7 @@ function getStck0InfoString(stack: Stack) { return s; } -type Stack = { function: string; url: string}[] +type Stack = { function: string; url: string }[] export interface IError { sessionId: string diff --git a/frontend/app/types/session/errorStack.ts b/frontend/app/types/session/errorStack.ts index c0fac6e83..59e63d16f 100644 --- a/frontend/app/types/session/errorStack.ts +++ b/frontend/app/types/session/errorStack.ts @@ -15,7 +15,7 @@ export default class ErrorStack { lineNo: IErrorStack["lineNo"] colNo: IErrorStack["colNo"] offset: IErrorStack["offset"] - context:IErrorStack["context"] + context: IErrorStack["context"] constructor(es: IErrorStack) { Object.assign(this, { diff --git a/frontend/app/types/session/event.ts b/frontend/app/types/session/event.ts index fa62c33d9..575183b93 100644 --- a/frontend/app/types/session/event.ts +++ b/frontend/app/types/session/event.ts @@ -111,7 +111,7 @@ class Input extends Event { } -class Location extends Event { +export class Location extends Event { readonly name = 'Location'; readonly type = LOCATION; url: LocationEvent["url"] diff --git a/frontend/app/types/session/resource.ts b/frontend/app/types/session/resource.ts index 7256a00c0..170e0713d 100644 --- a/frontend/app/types/session/resource.ts +++ b/frontend/app/types/session/resource.ts @@ -12,7 +12,7 @@ const OTHER = 'other' as const; function getResourceStatus(status: number, success: boolean) { if (status != null) return String(status); if (typeof success === 'boolean' || typeof success === 'number') { - return !!success + return !!success ? '2xx-3xx' : '4xx-5xx'; } @@ -20,8 +20,12 @@ function getResourceStatus(status: number, success: boolean) { } function getResourceSuccess(success: boolean, status: number) { - if (success != null) { return !!success } - if (status != null) { return status < 400 } + if (success != null) { + return !!success + } + if (status != null) { + return status < 400 + } return true } @@ -56,7 +60,7 @@ interface IResource { success: boolean, score: number, method: string, - request:string, + request: string, response: string, headerSize: number, encodedBodySize: number, diff --git a/frontend/app/types/session/session.ts b/frontend/app/types/session/session.ts index 182dcfcae..d0bf42936 100644 --- a/frontend/app/types/session/session.ts +++ b/frontend/app/types/session/session.ts @@ -8,6 +8,7 @@ import { Note } from 'App/services/NotesService' const HASH_MOD = 1610612741; const HASH_P = 53; + function hashString(s: string): number { let mul = 1; let hash = 0; @@ -192,7 +193,7 @@ export default class Session { const isMobile = ['console', 'mobile', 'tablet'].includes(userDeviceType); const events: InjectedEvent[] = [] - const rawEvents: (EventData & { key: number})[] = [] + const rawEvents: (EventData & { key: number })[] = [] if (session.events?.length) { (session.events as EventData[]).forEach((event: EventData, k) => { @@ -224,12 +225,13 @@ export default class Session { const exceptions = (errors as IError[]).map(e => new SessionError(e)) || []; - const issuesList = (issues as IIssue[]).map((i, k) => new Issue({ ...i, time: i.timestamp - startedAt, key: k })) || []; + const issuesList = (issues as IIssue[]).map( + (i, k) => new Issue({ ...i, time: i.timestamp - startedAt, key: k })) || []; const rawNotes = notes; const notesWithEvents = [...rawEvents, ...rawNotes].sort((a, b) => { // @ts-ignore just in case - const aTs = a.timestamp || a.time; + const aTs = a.timestamp || a.time; // @ts-ignore const bTs = b.timestamp || b.time; diff --git a/frontend/app/types/session/stackEvent.ts b/frontend/app/types/session/stackEvent.ts index 89dc1a7cc..06910e7d4 100644 --- a/frontend/app/types/session/stackEvent.ts +++ b/frontend/app/types/session/stackEvent.ts @@ -9,62 +9,62 @@ export const CLOUDWATCH = 'cloudwatch'; export const ELASTICSEARCH = 'elasticsearch'; export const SUMOLOGIC = 'sumologic'; -export const typeList = [ OPENREPLAY, SENTRY, DATADOG, STACKDRIVER, ROLLBAR, BUGSNAG, CLOUDWATCH, ELASTICSEARCH, SUMOLOGIC ]; +export const typeList = [OPENREPLAY, SENTRY, DATADOG, STACKDRIVER, ROLLBAR, BUGSNAG, CLOUDWATCH, ELASTICSEARCH, SUMOLOGIC]; export function isRed(event: StackEvent) { - if (!event.payload) return false; - switch(event.source) { - case SENTRY: - return event.payload['event.type'] === 'error'; - case DATADOG: - return true; - case STACKDRIVER: - return false; - case ROLLBAR: - return true; - case NEWRELIC: - return true; - case BUGSNAG: - return true; - case CLOUDWATCH: - return true; - case SUMOLOGIC: - return false; - default: - return event.level === 'error'; - } + if (!event.payload) return false; + switch (event.source) { + case SENTRY: + return event.payload['event.type'] === 'error'; + case DATADOG: + return true; + case STACKDRIVER: + return false; + case ROLLBAR: + return true; + case NEWRELIC: + return true; + case BUGSNAG: + return true; + case CLOUDWATCH: + return true; + case SUMOLOGIC: + return false; + default: + return event.level === 'error'; + } } export interface IStackEvent { - time: number; - timestamp: number; - index: number; - name: string; - message: string; - payload: any; - source: any; - level: string; + time: number; + timestamp: number; + index: number; + name: string; + message: string; + payload: any; + source: any; + level: string; - isRed: () => boolean; + isRed: () => boolean; } export default class StackEvent { - time: IStackEvent["time"] - index: IStackEvent["index"]; - name: IStackEvent["name"]; - message: IStackEvent["message"]; - payload: IStackEvent["payload"]; - source: IStackEvent["source"]; - level: IStackEvent["level"]; + time: IStackEvent["time"] + index: IStackEvent["index"]; + name: IStackEvent["name"]; + message: IStackEvent["message"]; + payload: IStackEvent["payload"]; + source: IStackEvent["source"]; + level: IStackEvent["level"]; - constructor(evt: IStackEvent) { - Object.assign(this, { - ...evt, - source: evt.source || OPENREPLAY - }); - } + constructor(evt: IStackEvent) { + Object.assign(this, { + ...evt, + source: evt.source || OPENREPLAY + }); + } - isRed() { - return isRed(this); - } + isRed() { + return isRed(this); + } }