-
Notifications
You must be signed in to change notification settings - Fork 37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(condo): DOMA-10516 reassign employee ticket #5563
feat(condo): DOMA-10516 reassign employee ticket #5563
Conversation
tolmachev21
commented
Dec 2, 2024
![Screenshot 2024-12-02 at 21 06 23](https://private-user-images.githubusercontent.com/119614372/391618895-41d7e407-df4e-4449-ae43-cd6a7e11033c.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3Mzk0NTUxOTIsIm5iZiI6MTczOTQ1NDg5MiwicGF0aCI6Ii8xMTk2MTQzNzIvMzkxNjE4ODk1LTQxZDdlNDA3LWRmNGUtNDQ0OS1hZTQzLWNkNmE3ZTExMDMzYy5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjUwMjEzJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI1MDIxM1QxMzU0NTJaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT0xOWY3YTRhOWJhNTExYzgzYjg0YTllZGQ2ZGY2NmNiZTg4MTE3ODJmZDkzNDBkNDkxYzY0NWNiNDMyYzVlNGNhJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.-kKjl4VQK_z2dbFSqPul9BPIv2Uky0T1H4DnJjVvXJE)
@@ -259,8 +259,31 @@ query getContactTickets ($contactId: ID!) { | |||
} | |||
} | |||
|
|||
query getOrganizationEmployeeTickets($where: TicketWhereInput!, $first: Int, $skip: Int) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd have named it getTicketIdsByEmployeeId, seems more true to its nature
@@ -12,6 +12,7 @@ const { | |||
expectToThrowAuthenticationErrorToObjects, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should add more tests, right now added tests do not cover added bulk operations
Really you should just take all tests for CRUD operations and duplicate it for bulk cases.
For example:
- I as employee try to update tickets from my org
- I as employee try to update tickets from my org, but I do not have the role (i'm not Administrator and do not have canManageTickets permission)
- I as employee try to update tickets from other org ...
- I as support try to update multiple tickets
And so on
Do not forget about complex cases, like this:
- I as hacker try to update multiple tickets: 2 tickets are from my organization, and 1 ticket is from other organization
- I try to update stragne random fields
apps/condo/lang/en/en.json
Outdated
@@ -702,7 +702,7 @@ | |||
"employee.AllSpecializations": "all specializations", | |||
"employee.BlockUser": "Block user", | |||
"employee.CanNotBlockYourself": "(you can't block yourself)", | |||
"employee.ConfirmDeleteMessage": "All data about him will be erased forever", | |||
"employee.ConfirmDeleteMessage": "The data will not be recoverable and the employee will not be able to log in to the Doma.ai platform.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, you should not mention Doma.ai in open source translations.
apps/condo/lang/en/en.json
Outdated
@@ -719,6 +719,15 @@ | |||
"employee.emptyList.description": "Your employer has blocked you from the organization", | |||
"employee.emptyList.title": "You are blocked", | |||
"employee.isBlocked": "blocked", | |||
"employee.reassignTickets.title": "Who should I assign employee requests to?", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tickets
apps/condo/lang/ru/ru.json
Outdated
@@ -702,7 +702,7 @@ | |||
"employee.AllSpecializations": "Все типы работ", | |||
"employee.BlockUser": "Заблокировать пользователя", | |||
"employee.CanNotBlockYourself": "(вы не можете заблокировать себя)", | |||
"employee.ConfirmDeleteMessage": "Все данные о нём сотрутся навсегда", | |||
"employee.ConfirmDeleteMessage": "Данные нельзя будет восстановить, а сотрудник не сможет войти на платформу Doma.ai", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...
updateEmployeeAction={updateEmployeeAction} | ||
activeTicketsOrganizationEmployeeCount={get(activeTicketsOrganizationEmployeeCount, 'meta.count', 0)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why 0 is default here? Default means something went wrong with the query, is it not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this mean that there should be no default value or should there be another value? Or something else?
if (!itemId) return false | ||
const ticket = await getById('Ticket', itemId) | ||
organizationId = get(ticket, 'organization', null) | ||
if (isBulkRequest) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are there any other checks for singular update case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These cases are below. There are cases when updating and creating one ticket - the property must belong to the same organization for which the ticket is created
message: notificationTitle('Ошибка'), | ||
description: notificationMessage('Повторите попытку'), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
^^
console.log(e) | ||
console.error(e.friendlyDescription) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
^^
|
||
const [notificationApi, contextHolder] = notification.useNotification() | ||
|
||
const filterTicket = `{"search":"${employeeName}", "status":["new_or_reopened","processing","completed","deferred"]}` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The search
filter includes a search across multiple fields (e.g clientName, details). If they contain the substring ${employeeName}
, then the ticket will also be displayed.
Mb add new custom filter in useTicketTableFilters
which filters by the id of the executor or assignee?
Or mb remove this logic?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And you can use enum from '@app/condo/schema' for status types
@@ -259,8 +259,31 @@ query getContactTickets ($contactId: ID!) { | |||
} | |||
} | |||
|
|||
query getOrganizationEmployeeTickets($where: TicketWhereInput!, $first: Int, $skip: Int) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make a more specific name for your use, for example getOrganizationEmployeeTicketsForReassign
. And specific arguments instead where: ($userId: ID!, !organizationId: ID!, $first ... )
organization: { id: organizationId }, | ||
OR: [ | ||
{ assignee: { id: employeeUserId } }, | ||
{ executor: { id: employeeUserId } }, | ||
], | ||
status: { | ||
type_in: [ | ||
TicketStatusTypeType.NewOrReopened, | ||
TicketStatusTypeType.Processing, | ||
TicketStatusTypeType.Completed, | ||
TicketStatusTypeType.Deferred, | ||
], | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can pass userId
and organizationId
as query arguments and describe this where
in graphql query
}, | ||
fetchPolicy: 'no-cache', | ||
}) | ||
const [updateTicketsWithReassignOrganizationEmployee] = useUpdateTicketsMutation() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const [updateTicketsWithReassignOrganizationEmployee] = useUpdateTicketsMutation() | |
const [updateTickets] = useUpdateTicketsMutation() |
message: notificationTitle('Ошибка'), | ||
description: notificationMessage('Повторите попытку'), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use intl
id_in: itemIds || [itemId], | ||
deletedAt: null, | ||
}) | ||
organizationId = uniq(tickets.map(ticket => get(ticket, 'organization', null))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now it is organizationIds
array
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like changes that are not related to this pr. This and icons below
setIsDeleting(false) | ||
notificationApi.success({ | ||
message: notificationTitle(NotificationSuccessLabel), | ||
description: notificationMessage(NotificationTitleLabel, updatedTicketsCount), | ||
duration: 0, | ||
key: 'reassignTicket', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
runMutation
has onCompleted
and OnCompletedMsg
args, look at them
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't use lodash.get
in typescript (except when it's really necessary). It outputs bad types and doesn't show errors during development. You'll only learn about errors in production from real users.
And since we use typescript, please add good types. It's not that hard :)
@@ -278,13 +296,14 @@ export const EmployeePageContent = ({ | |||
) | |||
} | |||
|
|||
export const EmployeeInfoPage: PageComponentType = () => { | |||
export const EmployeeInfoPage = () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bring the types back
const intl = useIntl() | ||
const UpdateEmployeeMessage = intl.formatMessage({ id: 'employee.UpdateTitle' }) | ||
const ErrorMessage = intl.formatMessage({ id: 'errors.LoadingError' }) | ||
|
||
const organizationId = get(organization, 'id', null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should not use lodash.get
, you can write the same thing using optional chains
const { objs: organizationEmployeeSpecializations } = OrganizationEmployeeSpecialization.useObjects({ | ||
where: { | ||
employee: { id: employeeId }, | ||
}, | ||
}) | ||
|
||
const employeeUserId = get(employee, 'user.id', null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't use get
in typescript
employeeUserId, | ||
organizationId, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this for?
There is no need to pass this data from the parent element. The employee is already passed.
The organization can also be get on the spot from an employee or hook (you can only delete an employee from active organization)
const { data: activeTicketsOrganizationEmployeeCount } = useGetTicketsCountQuery({ | ||
variables: { | ||
where: { | ||
organization: { id: organizationId }, | ||
OR: [ | ||
{ assignee: { id: employeeUserId } }, | ||
{ executor: { id: employeeUserId } }, | ||
], | ||
status: { | ||
type_in: [ | ||
TicketStatusTypeType.NewOrReopened, | ||
TicketStatusTypeType.Processing, | ||
TicketStatusTypeType.Completed, | ||
TicketStatusTypeType.Deferred, | ||
], | ||
}, | ||
}, | ||
}, | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better make a separate request for this in .graphql
file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
like getOrganizationEmployeeTicketsForReassign
, but for count
} | ||
|
||
const tickets = await find('Ticket', { | ||
id_in: itemIds || [itemId], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
id_in: itemIds || [itemId], | |
id_in: isBulkRequest ? itemIds : [itemId], |
originalInput.forEach((updatedTicket) => { | ||
const propertyId = get(updatedTicket, ['property', 'connect', 'id']) | ||
if (propertyId) propertyIds.push(itemId) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not just use map?
originalInput.forEach((updatedTicket) => { | |
const propertyId = get(updatedTicket, ['property', 'connect', 'id']) | |
if (propertyId) propertyIds.push(itemId) | |
}) | |
propertyIds = originalInput.map(ticket => get(ticket, ['property', 'connect', 'id'], null)).filter(Boolean) |
const propertyId = get(updatedTicket, ['property', 'connect', 'id']) | ||
if (propertyId) propertyIds.push(itemId) | ||
}) | ||
if (!isEmpty(propertyIds) && propertyIds.length !== originalInput.length) return false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this definitely the correct condition?
You can pass data without a property
if (!isEmpty(propertyIds) && propertyIds.length !== originalInput.length) return false | |
if (!isEmpty(propertyIds) return false |
|
||
return organizationId === get(property, 'organization') | ||
return isEqual(organizationIds, propertyOrganizationIds) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This condition will not work correctly if the order of the elements is different.
You should fix this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to add tests that check different cases. There aren't any now :(
const TICKET_OPEN_STATUS_ID = '6ef3abc4-022f-481b-90fb-8430345ebfc2' | ||
const TICKET_OTHER_SOURCE_ID = '7da1e3be-06ba-4c9e-bba6-f97f278ac6e4' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we have STATUS_IDS
const, use it
7ce74a9
to
8885c69
Compare
@@ -111,6 +115,30 @@ async function canManageTickets (args) { | |||
return ticket.client === user.id | |||
} | |||
} else { | |||
if (isBulkRequest && operation === 'create') return false | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add a TODO here with task DOMA-10832
@@ -91,6 +91,24 @@ query getTicketsCount ($where: TicketWhereInput!) { | |||
} | |||
} | |||
|
|||
query getOrganizationEmployeeTicketsCountForReassign ($userId: ID!, $organizationId: ID!) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do I need to change to?
query getOrganizationEmployeeTicketsCountForReassign ($userId: ID!, $organizationId: ID!) { | |
query getOrganizationEmployeeTicketsCountForReassignment ($userId: ID!, $organizationId: ID!) { |
@@ -1448,6 +1464,528 @@ describe('Ticket', () => { | |||
}) | |||
}) | |||
|
|||
describe('Bulk-operation', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's normal that I put the logic in a separate describe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll also add a commit with the changes to callcenter here
@@ -111,6 +115,30 @@ async function canManageTickets (args) { | |||
return ticket.client === user.id | |||
} | |||
} else { | |||
if (isBulkRequest && operation === 'create') return false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you already check it in line 73
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's right. I thought about leaving the check for better understanding and easier scaling of the code in the future. But if it's unnecessary, I'll remove it. Let me know
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's better to remove it and leave in one place
if (!originalInput.every((updateItem) => { | ||
for (const key in updateItem.data) { | ||
if (!BULK_UPDATE_ALLOWED_FIELDS.includes(key)) return false | ||
} | ||
return true | ||
})) return false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we really need this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now, with a single update, we have a check of the address for its compliance with the organization. With a bulk update, we must also support this logic. However, the task of updating and maintaining all possible scenarios of bulk operations is a different task. Therefore, we decided to temporarily limit the update to only two fields - executor and assignee
useEffect(() => { | ||
if (isFirstRender.current) { | ||
isFirstRender.current = false | ||
return | ||
} | ||
if (!loading && !employee) { | ||
push('/employee/') | ||
} | ||
}, [employee, loading, push]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is something strange, better tell the user that the employee was not found.
This will be much clearer than a redirect
I haven't seen similar logic with redirect
} | ||
return true | ||
})) return false | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We may have crooked bulk updates of employees. This is true. I want to roll out this feature and then take on the task of updating accesses. However, if this is really bad, then I can make a crutch. We will check that only one user is updated. And only he will be in the check for compliance with the employee's organization - the ticket organization
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me know what is the best way to proceed
return resultObj | ||
} | ||
|
||
const notificationTitle = useCallback((notificationStatus: React.ReactNode) => <Typography.Text strong>{notificationStatus}</Typography.Text>, []) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if this is a function, its name should start from verb, like getNotificationTitle
If this is a component – just make a component, maybe in this file
@@ -91,6 +91,24 @@ query getTicketsCount ($where: TicketWhereInput!) { | |||
} | |||
} | |||
|
|||
query getOrganizationEmployeeTicketsCountForReassign ($userId: ID!, $organizationId: ID!) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
query getOrganizationEmployeeTicketsCountForReassign ($userId: ID!, $organizationId: ID!) { | |
query getOrganizationEmployeeTicketsCountByUserAndOrgId ($userId: ID!, $organizationId: ID!) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but this name is ok for me
const notificationTitle = useCallback((notificationStatus: React.ReactNode) => <Typography.Text strong>{notificationStatus}</Typography.Text>, []) | ||
const notificationMessage = useCallback((notificationTitleLabel: React.ReactNode, updatedTicketsCount = null) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it is a function (which useCallback is) then it should be named as a function – getNotificationMessage / getNotificationTitle
Otherwise you should use useMemo
runMutation( | ||
{ | ||
action: softDeleteAction, | ||
onError: (e) => { throw e }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, if error happens, whole page will fail?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, then it will work OnErrorMsg
}) | ||
|
||
updatedTicketsCount += reassignedTickets?.tickets?.length | ||
await sleep(1000) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move this to const so this is more readable
@@ -55,6 +55,25 @@ const MAX_COMMENT_LENGTH = 700 | |||
|
|||
const DEFAULT_DEFERRED_DAYS = 30 | |||
|
|||
const TICKET_OTHER_SOURCE_ID = '7da1e3be-06ba-4c9e-bba6-f97f278ac6e4' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this const?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In tests, I need to check operations for bulk ticket creation. There is no function for creating bulk tickets in test utilities. And I did not write it, because we decided not to support bulk ticket creation. And I need this constant only for creating a ticket that cannot be created
}) | ||
|
||
// Should an admin have access to bulk create tickets? | ||
test.skip('admin: cannot bulk create Tickets', async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
skip here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why it so?
apps/condo/lang/en/en.json
Outdated
"employee.reassignTickets.title": "Who should I assign employee requests to tickets?", | ||
"employee.reassignTickets.buttonContent.deleteUser": "Remove employee without reassignment", | ||
"employee.reassignTickets.buttonContent.reassignTickets": "Delete employee and reassign requests", | ||
"employee.reassignTickets.alert.title": "{employeeName} is assigned to multiple tickets ({activeTicketsOrganizationEmployeeCount}", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(
apps/condo/lang/ru/ru.json
Outdated
"employee.reassignTickets.title": "На кого назначить заявки сотрудника?", | ||
"employee.reassignTickets.buttonContent.deleteUser": "Удалить сотрудника без переназначения", | ||
"employee.reassignTickets.buttonContent.reassignTickets": "Удалить сотрудника и переназначить заявки", | ||
"employee.reassignTickets.alert.title": "{employeeName} назначен на несколько заявок ({activeTicketsOrganizationEmployeeCount}", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(
const { objs: organizationEmployeeSpecializations } = OrganizationEmployeeSpecialization.useObjects({ | ||
where: { | ||
employee: { id: employeeId }, | ||
}, | ||
}) | ||
|
||
const { data: activeTicketsOrganizationEmployeeCount } = useGetOrganizationEmployeeTicketsCountForReassignQuery({ | ||
variables: { | ||
userId: employee?.user?.id, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this means that if userId is undefined for some reason, we will fire a gql request and will get 400 bad request error
@@ -28,6 +28,7 @@ const NEWS_SHARING_TEMPLATES = 'news-sharing-templates' | |||
const SERVICE_PROBLEMS_ALERT = 'service-problems-alert' | |||
const TICKET_AUTO_ASSIGNMENT_MANAGEMENT = 'ticket-auto-assignment-management' | |||
const POLL_TICKET_COMMENTS = 'poll-ticket-comments' | |||
const REASSIGN_EMPLOYEE_TICKETS = 'reassign-employee-tickets' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just a question, why do we want to use feature flags for this feature?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If they start dudosing us with this feature, we can turn it off
buttonContent: string | ||
softDeleteAction: IUseSoftDeleteActionType<OrganizationEmployee> | ||
disabled?: boolean | ||
employee: OrganizationEmployee |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
employee: OrganizationEmployee | |
employee: Pick<OrganizationEmployee, 'name'> & { organization?: Pick<OrganizationEmployee['organization'], 'id'>, user?: Pick<OrganizationEmployee['organization'], 'id'> } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When using new hooks this type will be automatically generated
import { searchEmployeeUser } from '@condo/domains/ticket/utils/clientSchema/search' | ||
|
||
|
||
interface IDeleteEmployeeButtonWithReassignmentModel { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
interface IDeleteEmployeeButtonWithReassignmentModel { | |
type DeleteEmployeeButtonWithReassignmentModelProps = { |
|
||
const [notificationApi, contextHolder] = notification.useNotification() | ||
|
||
const employeeUserId = employee?.user?.id |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const employeeUserId = employee?.user?.id | |
const employeeUserId = employee?.user?.id || null |
const [notificationApi, contextHolder] = notification.useNotification() | ||
|
||
const employeeUserId = employee?.user?.id | ||
const employeeOrganizationId = employee?.organization?.id |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const employeeOrganizationId = employee?.organization?.id | |
const employeeOrganizationId = employee?.organization?.id || null |
const { data: activeTicketsOrganizationEmployeeCount } = useGetOrganizationEmployeeTicketsCountForReassignQuery({ | ||
variables: { | ||
userId: employee?.user?.id, | ||
organizationId: employee?.organization?.id, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
organizationId: employee?.organization?.id, | |
organizationId: employee?.organization?.id || null, |
const { data: activeTicketsOrganizationEmployeeCount } = useGetOrganizationEmployeeTicketsCountForReassignQuery({ | ||
variables: { | ||
userId: employee?.user?.id, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add skip if userId or organizationId is empty
const { objs: organizationEmployeeSpecializations } = OrganizationEmployeeSpecialization.useObjects({ | ||
where: { | ||
employee: { id: employeeId }, | ||
}, | ||
}) | ||
|
||
const { data: activeTicketsOrganizationEmployeeCount } = useGetOrganizationEmployeeTicketsCountForReassignQuery({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about waiting for the data to load?
if (error || loading || (!loading && !employee)) { | ||
const errorToPrint = error ? ErrorMessage : (!loading && !employee) ? NotFoundMsg : '' | ||
return <LoadingOrErrorPage title={loading ? LoadingInProgressMessage : errorToPrint} loading={loading} error={errorToPrint}/> | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (error || loading || (!loading && !employee)) { | |
const errorToPrint = error ? ErrorMessage : (!loading && !employee) ? NotFoundMsg : '' | |
return <LoadingOrErrorPage title={loading ? LoadingInProgressMessage : errorToPrint} loading={loading} error={errorToPrint}/> | |
} | |
if (error || loading) { | |
const errorToPrint = error ? ErrorMessage : '' | |
return <LoadingOrErrorPage title={loading ? LoadingInProgressMessage : errorToPrint} loading={loading} error={errorToPrint}/> | |
} | |
if (!employee) { | |
return <LoadingOrErrorPage title={errorToPrint} loading={false} error={NotFoundMsg}/> | |
} |
It's a little easier to read this way
} | ||
|
||
return ( | ||
<EmployeePageContent | ||
employee={employeeWithSpecializations} | ||
updateEmployeeAction={updateEmployeeAction} | ||
activeTicketsOrganizationEmployeeCount={activeTicketsOrganizationEmployeeCount?.meta?.count} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
activeTicketsOrganizationEmployeeCount={activeTicketsOrganizationEmployeeCount?.meta?.count} | |
activeTicketsOrganizationEmployeeCount={activeTicketsOrganizationEmployeeCount?.meta?.count || 0} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to wait for the data to load also
apps/callcenter
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we provide the entire list of organizations for a call center request?
const { objs: organizationEmployeeSpecializations } = OrganizationEmployeeSpecialization.useObjects({ | ||
where: { | ||
employee: { id: employeeId }, | ||
}, | ||
}) | ||
|
||
const { data: activeTicketsOrganizationEmployeeCount } = useGetOrganizationEmployeeTicketsCountForReassignQuery({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your request is saved in the cache, so you should wait for the cache to load from local storage.
Let me remind you that the request to get the meta is cached for a minute.
wait persisor
const { persistor } = useCachePersistor()
...
skip: !persistor || ...,
@@ -961,8 +964,10 @@ const Ticket = new GQLListSchema('Ticket', { | |||
] | |||
)(...args) | |||
|
|||
/* NOTE: this sends different kinds of notifications on ticket create/update */ | |||
await sendTicketChangedNotifications.delay({ ticketId: updatedItem.id, existingItem, operation }) | |||
/* NOTE: this sends different kinds of notifications on ticket create/update except bulk update operation */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add please same comment on page
@@ -55,6 +55,25 @@ const MAX_COMMENT_LENGTH = 700 | |||
|
|||
const DEFAULT_DEFERRED_DAYS = 30 | |||
|
|||
const TICKET_OTHER_SOURCE_ID = '7da1e3be-06ba-4c9e-bba6-f97f278ac6e4' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's better to leave it in the test file, or just generate a random id.
* updateTicketsForReassignmentEmployee - Query name in query/Ticket.graphql. Usage in DeleteEmployeeButtonWithReassignmentModal.jsx | ||
*/ | ||
const DISABLE_PUSH_NOTIFICATION_FOR_OPERATIONS = [ | ||
'updateTickets', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't have this query
'updateTickets', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need this query for tests. Frontend queries cannot be used in tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need this query for tests. Frontend queries cannot be used in tests
You can write the query you need for tests. You should to make sure that "updateTicketsForReassignmentEmployee" works correctly
132b856
to
ec520ce
Compare
const employeeUserId = employee?.user?.id || null | ||
const employeeOrganizationId = employee?.organization?.id || null | ||
|
||
const getTicketReassignData = (ticket: Ticket) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This parameter is not of type Ticket
. Use the correct generated type for your request (from @app/condo/gql
)
return resultObj | ||
} | ||
|
||
const getNotificationInfo = useCallback((notificationType: string, updatedTicketsCount = null) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const getNotificationInfo = useCallback((notificationType: string, updatedTicketsCount = null) => { | |
const getNotificationInfo = useCallback((notificationType: 'error' | 'warning' | 'success', updatedTicketsCount = null) => { |
It would be better to specify specific values in the type
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and then you won't need to put these values into constants.
(but if you want, you can leave them)
} | ||
|
||
const getNotificationInfo = useCallback((notificationType: string, updatedTicketsCount = null) => { | ||
if (notificationType === ERROR_NOTIFICATION_TYPE) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be better to use switch/case
} | ||
} | ||
|
||
if (activeTicketsOrganizationEmployeeCount === updatedTicketsCount) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we really need this condition? If the number of tickets for an employee has changed up or down (not because of our feature), then an error will return, do we need this behavior?
* updateTicketsForReassignmentEmployee - Query name in query/Ticket.graphql. Usage in DeleteEmployeeButtonWithReassignmentModal.jsx | ||
*/ | ||
const DISABLE_PUSH_NOTIFICATION_FOR_OPERATIONS = [ | ||
'updateTickets', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need this query for tests. Frontend queries cannot be used in tests
You can write the query you need for tests. You should to make sure that "updateTicketsForReassignmentEmployee" works correctly
apps/callcenter
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check comments in cc pr
} | ||
const errorToPrint = error ? ErrorMessage : '' | ||
|
||
if (error || loading || loadingTicketsOrganizationEmployeeCount) return <LoadingOrErrorPage title={loading ? LoadingInProgressMessage : errorToPrint} loading={loading} error={errorToPrint}/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait persistor?
|
}) | ||
|
||
// TODO: DOMA-10832 add check employee organization in Ticket access | ||
test.skip('admin: cannot bulk update tickets executor field with user from different organization ', async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why it so?
}) | ||
}) | ||
|
||
test('admin: cannot bulk delete Tickets', async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why?
}) | ||
}) | ||
|
||
test('employee with role "canManageTickets": can bulk update Tickets', async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bulk create test for employee with role "canManageTickets"
expect(get(updatedTicket2, ['assignee', 'id'], null)).toEqual(client.user.id) | ||
}) | ||
|
||
test('employee without role "canManageTickets": cannot bulk update Tickets', async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bulk create test for employee without role "canManageTickets"
}) | ||
}) | ||
|
||
test('organization "from" employee: can bulk update organization "to" tickets', async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bulk create for organization "from" employee
?
/* NOTE: this sends different kinds of notifications on ticket create/update */ | ||
await sendTicketChangedNotifications.delay({ ticketId: updatedItem.id, existingItem, operation }) | ||
/* NOTE: this sends different kinds of notifications on ticket create/update except bulk update operation */ | ||
if (!DISABLE_PUSH_NOTIFICATION_FOR_OPERATIONS.includes(get(context, ['req', 'body', 'operationName'], null))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is impossible to assume that the logic of notifications will be tied to the name of the GQL mutation. This solution seems non-obvious and requires separate discussion.