Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -510,11 +510,6 @@
'url' => '/api/textBlocks/{id}/shares',
'verb' => 'GET',
],
[
'name' => 'actionStep#findAllStepsForAction',
'url' => '/api/action-step/{actionId}/steps',
'verb' => 'GET'
],
],
'resources' => [
'accounts' => ['url' => '/api/accounts'],
Expand Down
15 changes: 0 additions & 15 deletions lib/Controller/ActionStepController.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,6 @@ public function __construct(
$this->uid = $userId;
}

/**
* @NoAdminRequired
*
* @return JsonResponse
*/
#[TrapError]
public function findAllStepsForAction(int $actionId): JsonResponse {
if ($this->uid === null) {
return JsonResponse::error('User not found', Http::STATUS_UNAUTHORIZED);
}
$actionSteps = $this->quickActionsService->findAllActionSteps($actionId, $this->uid);

return JsonResponse::success($actionSteps);
}

/**
* @NoAdminRequired
* @param string $name
Expand Down
12 changes: 12 additions & 0 deletions lib/Db/Actions.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,30 @@
class Actions extends Entity implements JsonSerializable {
protected $name;
protected $accountId;
protected $actionSteps = [];
protected $icon = '';

public function __construct() {
$this->addType('name', 'string');
$this->addType('accountId', 'integer');
}

public function setActionSteps(array $actionSteps): void {
$this->actionSteps = $actionSteps;
}

public function setIcon(string $icon): void {
$this->icon = $icon;
}

#[ReturnTypeWillChange]
public function jsonSerialize() {
return [
'id' => $this->getId(),
'name' => $this->getName(),
'accountId' => $this->getAccountId(),
'actionSteps' => $this->actionSteps,
'icon' => $this->icon,

];
}
Expand Down
17 changes: 7 additions & 10 deletions lib/Service/QuickActionsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,18 @@ public function __construct(
private ActionStepMapper $actionStepMapper,
) {
}

/**
* @param string $userId
* @return Actions[]
*/
public function findAll(string $userId): array {
return $this->actionsMapper->findAll($userId);
$actions = $this->actionsMapper->findAll($userId);
foreach ($actions as $action) {
$actionSteps = $this->actionStepMapper->findAllStepsForOneAction($action->getId(), $userId);
Comment on lines +43 to +45
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perf: n+1 problem

you could fetch the steps in bulk with a WHERE action_id IN (...)

$action->setActionSteps($actionSteps);
$action->setIcon($actionSteps[0]->getName());
}
return $actions;
}

/**
Expand Down Expand Up @@ -74,14 +79,6 @@ public function delete(int $actionId, string $userId): void {
$this->actionsMapper->delete($action);
}

/**
* @param string $userId
* @return ActionStep[]
*/
public function findAllActionSteps(int $actionId, string $userId): array {
return $this->actionStepMapper->findAllStepsForOneAction($actionId, $userId);
}

/**
* @throws DoesNotExistException
*/
Expand Down
63 changes: 25 additions & 38 deletions src/components/Envelope.vue
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,6 @@ import NoTrashMailboxConfiguredError
from '../errors/NoTrashMailboxConfiguredError.js'
import logger from '../logger.js'
import { buildRecipients as buildReplyRecipients } from '../ReplyBuilder.js'
import { findAllStepsForAction } from '../service/QuickActionsService.js'
import { FOLLOW_UP_TAG_LABEL } from '../store/constants.js'
import useMainStore from '../store/mainStore.js'
import { mailboxHasRights } from '../util/acl.js'
Expand Down Expand Up @@ -589,7 +588,6 @@ export default {
customSnoozeDateTime: new Date(moment().add(2, 'hours').minute(0).second(0).valueOf()),
overwriteOneLineMobile: false,
hoveringAvatar: false,
filteredQuickActions: [],
quickActionLoading: false,
}
},
Expand Down Expand Up @@ -846,20 +844,36 @@ export default {
},
].filter((option) => option.timestamp !== null)
},
},

watch: {
storeActions() {
this.filterAndEnrichQuickActions()
filteredQuickActions() {
const filteredQuickActions = []
const quickActions = this.mainStore.getQuickActions().filter((action) => action.accountId === this.data.accountId)
for (const action of quickActions) {
const check = action.actionSteps.every((step) => {
if (['markAsSpam', 'applyTag', 'markAsImportant', 'markAsFavorite'].includes(step.name) && !this.hasWriteAcl) {
return false
}
if (['markAsRead', 'markAsUnread'].includes(step.name) && !this.hasSeenAcl) {
return false
}
if (['moveThread', 'deleteThread'].includes(step.name) && !this.hasDeleteAcl) {
return false
}
return true
})
if (check) {
filteredQuickActions.push({
...action,
})
}
}
return filteredQuickActions
},
},

async mounted() {
mounted() {
this.onWindowResize()
window.addEventListener('resize', this.onWindowResize)
if (this.filteredQuickActions.length === 0) {
await this.filterAndEnrichQuickActions()
}
},

methods: {
Expand All @@ -874,38 +888,11 @@ export default {
return shortRelativeDatetime(new Date(this.data.dateInt * 1000))
},

async filterAndEnrichQuickActions() {
this.filteredQuickActions = []
const quickActions = this.mainStore.getQuickActions().filter((action) => action.accountId === this.data.accountId)
for (const action of quickActions) {
const steps = await findAllStepsForAction(action.id)
const check = steps.every((step) => {
if (['markAsSpam', 'applyTag', 'markAsImportant', 'markAsFavorite'].includes(step.type) && !this.hasWriteAcl) {
return false
}
if (['markAsRead', 'markAsUnread'].includes(step.type) && !this.hasSeenAcl) {
return false
}
if (['moveThread', 'deleteThread'].includes(step.type) && !this.hasDeleteAcl) {
return false
}
return true
})
if (check) {
this.filteredQuickActions.push({
...action,
steps,
icon: steps[0]?.name,
})
}
}
},

async executeQuickAction(action) {
this.closeQuickActionsMenu()
this.quickActionLoading = true
try {
for (const step of action.steps) {
for (const step of action.actionSteps) {
switch (step.name) {
case 'markAsSpam':
if (this.layoutMessageViewThreaded) {
Expand Down
24 changes: 17 additions & 7 deletions src/components/quickActions/Settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ import TagIcon from 'vue-material-design-icons/TagOutline.vue'
import IconDelete from 'vue-material-design-icons/TrashCanOutline.vue'
import Action from './Action.vue'
import logger from '../../logger.js'
import { createActionStep, deleteActionStep, findAllStepsForAction, updateActionStep } from '../../service/QuickActionsService.js'
import { createActionStep, deleteActionStep, updateActionStep } from '../../service/QuickActionsService.js'
import useMainStore from '../../store/mainStore.js'

export default {
Expand Down Expand Up @@ -228,8 +228,8 @@ export default {
this.localAction = { id: null, name: '' }
this.actions = []
} else {
this.localAction = { ...action }
this.actions = await findAllStepsForAction(action.id)
this.localAction = { id: action.id, name: action.name, accountId: action.accountId }
this.actions = action.actionSteps
this.highestOrder = Math.max(...this.actions.map((a) => a.order), 0)
this.editMode = true
}
Expand Down Expand Up @@ -260,7 +260,7 @@ export default {
for (const [index, action] of this.actions.entries()) {
if (action?.id !== null && action?.id !== undefined) {
try {
await updateActionStep(action.id, action.name, action.order, action?.tagId, action?.mailboxId)
this.actions[index] = await updateActionStep(action.id, action.name, action.order, action?.tagId, action?.mailboxId)
} catch (error) {
logger.error('Could not update quick action step', {
error,
Expand All @@ -273,10 +273,12 @@ export default {
this.actions[index] = createdStep
}
}
this.localAction = quickAction
}
showSuccess(t('mail', 'Quick action updated'))
} else {
let quickAction
const createdSteps = []
try {
quickAction = await this.mainStore.createQuickAction(this.localAction.name, this.account.id)
} catch (error) {
Expand All @@ -288,17 +290,23 @@ export default {
}
try {
for (const action of this.actions) {
await createActionStep(action.name, action.order, quickAction.id, action?.tagId, action?.mailboxId)
const createdStep = await createActionStep(action.name, action.order, quickAction.id, action?.tagId, action?.mailboxId)
if (createdStep) {
createdSteps.push(createdStep)
}
}
this.actions = createdSteps
} catch (error) {
logger.error('Could not add step to quick action', {
error,
})
showError(t('mail', 'Failed to add steps to quick action'))
this.closeEditModal()
}
this.localAction = quickAction
showSuccess(t('mail', 'Quick action created'))
}
this.mainStore.patchActionStepsLocally(this.localAction.id, this.actions)
this.closeEditModal()
},

Expand Down Expand Up @@ -338,9 +346,13 @@ export default {
},

async deleteAction(item) {
this.actions = this.actions.filter((action) => action.order !== item.order).map((action, index) => ({ ...action, order: index + 1 }))
this.highestOrder = Math.max(...this.actions.map((a) => a.order), 0)
if (item.id) {
try {
await deleteActionStep(item.id)
const actions = this.actions.filter((action) => action.id)
this.mainStore.patchActionStepsLocally(this.localAction.id, actions)
} catch (error) {
logger.error('Could not delete action step', {
error,
Expand All @@ -349,8 +361,6 @@ export default {
return
}
}
this.actions = this.actions.filter((action) => action.order !== item.order).map((action, index) => ({ ...action, order: index + 1 }))
this.highestOrder = Math.max(...this.actions.map((a) => a.order), 0)
},
},
}
Expand Down
8 changes: 0 additions & 8 deletions src/service/QuickActionsService.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,6 @@ export async function deleteQuickAction(id) {
})
}

export async function findAllStepsForAction(actionId) {
const url = generateUrl('/apps/mail/api/action-step/{id}/steps', { id: actionId })
return handleHttpAuthErrors(async () => {
const response = await axios.get(url)
return response.data.data
})
}

export async function createActionStep(name, order, actionId, tagId = null, mailboxId = null) {
const url = generateUrl('/apps/mail/api/action-step')
return handleHttpAuthErrors(async () => {
Expand Down
8 changes: 8 additions & 0 deletions src/store/mainStore/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -2322,6 +2322,14 @@ export default function mainStoreActions() {
Vue.set(this.quickActions, index, quickAction)
}
},
patchActionStepsLocally(id, steps) {
const index = this.quickActions.findIndex((s) => s.id === id)
if (index !== -1) {
const updatedQuickAction = this.quickActions[index]
updatedQuickAction.actionSteps = steps
Vue.set(this.quickActions, index, updatedQuickAction)
}
},
deleteQuickActionLocally(id) {
const index = this.quickActions.findIndex((s) => s.id === id)
if (index !== -1) {
Expand Down
Loading