fix: bulk edit mixed channel warning not showing confirmation dialog
The response interceptor in client.ts transforms errors into plain
objects {status, code, message}, but catch blocks were checking
error.response?.status (AxiosError format) which never matched.
- Add error field passthrough in client.ts interceptor
- Refactor BulkEditAccountModal to use pre-check API (checkMixedChannelRisk)
before submit, matching the single edit flow
- Fix EditAccountModal catch blocks to use interceptor error format
- Add bulk-update mixed channel unit tests
This commit is contained in:
@@ -19,6 +19,7 @@ func setupAccountMixedChannelRouter(adminSvc *stubAdminService) *gin.Engine {
|
||||
router.POST("/api/v1/admin/accounts/check-mixed-channel", accountHandler.CheckMixedChannel)
|
||||
router.POST("/api/v1/admin/accounts", accountHandler.Create)
|
||||
router.PUT("/api/v1/admin/accounts/:id", accountHandler.Update)
|
||||
router.POST("/api/v1/admin/accounts/bulk-update", accountHandler.BulkUpdate)
|
||||
return router
|
||||
}
|
||||
|
||||
@@ -145,3 +146,53 @@ func TestAccountHandlerUpdateMixedChannelConflictSimplifiedResponse(t *testing.T
|
||||
require.False(t, hasDetails)
|
||||
require.False(t, hasRequireConfirmation)
|
||||
}
|
||||
|
||||
func TestAccountHandlerBulkUpdateMixedChannelConflict(t *testing.T) {
|
||||
adminSvc := newStubAdminService()
|
||||
adminSvc.bulkUpdateAccountErr = &service.MixedChannelError{
|
||||
GroupID: 27,
|
||||
GroupName: "claude-max",
|
||||
CurrentPlatform: "Antigravity",
|
||||
OtherPlatform: "Anthropic",
|
||||
}
|
||||
router := setupAccountMixedChannelRouter(adminSvc)
|
||||
|
||||
body, _ := json.Marshal(map[string]any{
|
||||
"account_ids": []int64{1, 2, 3},
|
||||
"group_ids": []int64{27},
|
||||
})
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/accounts/bulk-update", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusConflict, rec.Code)
|
||||
var resp map[string]any
|
||||
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
|
||||
require.Equal(t, "mixed_channel_warning", resp["error"])
|
||||
require.Contains(t, resp["message"], "claude-max")
|
||||
}
|
||||
|
||||
func TestAccountHandlerBulkUpdateMixedChannelConfirmSkips(t *testing.T) {
|
||||
adminSvc := newStubAdminService()
|
||||
router := setupAccountMixedChannelRouter(adminSvc)
|
||||
|
||||
body, _ := json.Marshal(map[string]any{
|
||||
"account_ids": []int64{1, 2},
|
||||
"group_ids": []int64{27},
|
||||
"confirm_mixed_channel_risk": true,
|
||||
})
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/accounts/bulk-update", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, rec.Code)
|
||||
var resp map[string]any
|
||||
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
|
||||
require.Equal(t, float64(0), resp["code"])
|
||||
data, ok := resp["data"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, float64(2), data["success"])
|
||||
require.Equal(t, float64(0), data["failed"])
|
||||
}
|
||||
|
||||
@@ -22,9 +22,10 @@ type stubAdminService struct {
|
||||
updatedProxyIDs []int64
|
||||
updatedProxies []*service.UpdateProxyInput
|
||||
testedProxyIDs []int64
|
||||
createAccountErr error
|
||||
updateAccountErr error
|
||||
checkMixedErr error
|
||||
createAccountErr error
|
||||
updateAccountErr error
|
||||
bulkUpdateAccountErr error
|
||||
checkMixedErr error
|
||||
lastMixedCheck struct {
|
||||
accountID int64
|
||||
platform string
|
||||
@@ -235,7 +236,10 @@ func (s *stubAdminService) SetAccountSchedulable(ctx context.Context, id int64,
|
||||
}
|
||||
|
||||
func (s *stubAdminService) BulkUpdateAccounts(ctx context.Context, input *service.BulkUpdateAccountsInput) (*service.BulkUpdateAccountsResult, error) {
|
||||
return &service.BulkUpdateAccountsResult{Success: 1, Failed: 0, SuccessIDs: []int64{1}}, nil
|
||||
if s.bulkUpdateAccountErr != nil {
|
||||
return nil, s.bulkUpdateAccountErr
|
||||
}
|
||||
return &service.BulkUpdateAccountsResult{Success: len(input.AccountIDs), Failed: 0, SuccessIDs: input.AccountIDs}, nil
|
||||
}
|
||||
|
||||
func (s *stubAdminService) CheckMixedChannelRisk(ctx context.Context, currentAccountID int64, currentAccountPlatform string, groupIDs []int64) error {
|
||||
|
||||
@@ -267,6 +267,7 @@ apiClient.interceptors.response.use(
|
||||
return Promise.reject({
|
||||
status,
|
||||
code: apiData.code,
|
||||
error: apiData.error,
|
||||
message: apiData.message || apiData.detail || error.message
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1252,14 +1252,43 @@ const buildUpdatePayload = (): Record<string, unknown> | null => {
|
||||
return Object.keys(updates).length > 0 ? updates : null
|
||||
}
|
||||
|
||||
const mixedChannelConfirmed = ref(false)
|
||||
|
||||
const needsMixedChannelCheck = () =>
|
||||
enableGroups.value &&
|
||||
props.selectedPlatforms.length === 1 &&
|
||||
(props.selectedPlatforms[0] === 'antigravity' || props.selectedPlatforms[0] === 'anthropic')
|
||||
|
||||
const handleClose = () => {
|
||||
showMixedChannelWarning.value = false
|
||||
mixedChannelWarningMessage.value = ''
|
||||
pendingUpdatesForConfirm.value = null
|
||||
mixedChannelConfirmed.value = false
|
||||
emit('close')
|
||||
}
|
||||
|
||||
const handleSubmit = async (confirmMixedChannel = false) => {
|
||||
const checkMixedChannelRisk = async (): Promise<boolean> => {
|
||||
if (!needsMixedChannelCheck()) return true
|
||||
if (mixedChannelConfirmed.value) return true
|
||||
if (groupIds.value.length === 0) return true
|
||||
|
||||
try {
|
||||
const result = await adminAPI.accounts.checkMixedChannelRisk({
|
||||
platform: props.selectedPlatforms[0],
|
||||
group_ids: groupIds.value
|
||||
})
|
||||
if (!result.has_risk) return true
|
||||
|
||||
mixedChannelWarningMessage.value = result.message || t('admin.accounts.bulkEdit.failed')
|
||||
showMixedChannelWarning.value = true
|
||||
return false
|
||||
} catch (error: any) {
|
||||
appStore.showError(error.message || t('admin.accounts.bulkEdit.failed'))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (props.accountIds.length === 0) {
|
||||
appStore.showError(t('admin.accounts.bulkEdit.noSelection'))
|
||||
return
|
||||
@@ -1283,16 +1312,24 @@ const handleSubmit = async (confirmMixedChannel = false) => {
|
||||
return
|
||||
}
|
||||
|
||||
let updates: Record<string, unknown>
|
||||
if (confirmMixedChannel && pendingUpdatesForConfirm.value) {
|
||||
updates = { ...pendingUpdatesForConfirm.value, confirm_mixed_channel_risk: true }
|
||||
} else {
|
||||
const built = buildUpdatePayload()
|
||||
if (!built) {
|
||||
appStore.showError(t('admin.accounts.bulkEdit.noFieldsSelected'))
|
||||
return
|
||||
}
|
||||
updates = built
|
||||
const built = buildUpdatePayload()
|
||||
if (!built) {
|
||||
appStore.showError(t('admin.accounts.bulkEdit.noFieldsSelected'))
|
||||
return
|
||||
}
|
||||
|
||||
const canContinue = await checkMixedChannelRisk()
|
||||
if (!canContinue) {
|
||||
pendingUpdatesForConfirm.value = built
|
||||
return
|
||||
}
|
||||
|
||||
await submitBulkUpdate(built)
|
||||
}
|
||||
|
||||
const submitBulkUpdate = async (updates: Record<string, unknown>) => {
|
||||
if (mixedChannelConfirmed.value && needsMixedChannelCheck()) {
|
||||
updates = { ...updates, confirm_mixed_channel_risk: true }
|
||||
}
|
||||
|
||||
submitting.value = true
|
||||
@@ -1316,14 +1353,8 @@ const handleSubmit = async (confirmMixedChannel = false) => {
|
||||
handleClose()
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 409 && error.response?.data?.error === 'mixed_channel_warning') {
|
||||
pendingUpdatesForConfirm.value = updates
|
||||
mixedChannelWarningMessage.value = error.response.data.message
|
||||
showMixedChannelWarning.value = true
|
||||
} else {
|
||||
appStore.showError(error.response?.data?.detail || t('admin.accounts.bulkEdit.failed'))
|
||||
console.error('Error bulk updating accounts:', error)
|
||||
}
|
||||
appStore.showError(error.message || t('admin.accounts.bulkEdit.failed'))
|
||||
console.error('Error bulk updating accounts:', error)
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
@@ -1331,7 +1362,10 @@ const handleSubmit = async (confirmMixedChannel = false) => {
|
||||
|
||||
const handleMixedChannelConfirm = async () => {
|
||||
showMixedChannelWarning.value = false
|
||||
await handleSubmit(true)
|
||||
mixedChannelConfirmed.value = true
|
||||
if (pendingUpdatesForConfirm.value) {
|
||||
await submitBulkUpdate(pendingUpdatesForConfirm.value)
|
||||
}
|
||||
}
|
||||
|
||||
const handleMixedChannelCancel = () => {
|
||||
@@ -1376,6 +1410,7 @@ watch(
|
||||
showMixedChannelWarning.value = false
|
||||
mixedChannelWarningMessage.value = ''
|
||||
pendingUpdatesForConfirm.value = null
|
||||
mixedChannelConfirmed.value = false
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1961,7 +1961,7 @@ const ensureAntigravityMixedChannelConfirmed = async (onConfirm: () => Promise<v
|
||||
})
|
||||
return false
|
||||
} catch (error: any) {
|
||||
appStore.showError(error.response?.data?.message || error.response?.data?.detail || t('admin.accounts.failedToUpdate'))
|
||||
appStore.showError(error.message || t('admin.accounts.failedToUpdate'))
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -1984,9 +1984,9 @@ const submitUpdateAccount = async (accountID: number, updatePayload: Record<stri
|
||||
emit('updated', updatedAccount)
|
||||
handleClose()
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 409 && error.response?.data?.error === 'mixed_channel_warning' && needsMixedChannelCheck()) {
|
||||
if (error.status === 409 && error.error === 'mixed_channel_warning' && needsMixedChannelCheck()) {
|
||||
openMixedChannelDialog({
|
||||
message: error.response?.data?.message,
|
||||
message: error.message,
|
||||
onConfirm: async () => {
|
||||
antigravityMixedChannelConfirmed.value = true
|
||||
await submitUpdateAccount(accountID, updatePayload)
|
||||
@@ -1994,7 +1994,7 @@ const submitUpdateAccount = async (accountID: number, updatePayload: Record<stri
|
||||
})
|
||||
return
|
||||
}
|
||||
appStore.showError(error.response?.data?.message || error.response?.data?.detail || t('admin.accounts.failedToUpdate'))
|
||||
appStore.showError(error.message || t('admin.accounts.failedToUpdate'))
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
@@ -2245,7 +2245,7 @@ const handleSubmit = async () => {
|
||||
|
||||
await submitUpdateAccount(accountID, updatePayload)
|
||||
} catch (error: any) {
|
||||
appStore.showError(error.response?.data?.message || error.response?.data?.detail || t('admin.accounts.failedToUpdate'))
|
||||
appStore.showError(error.message || t('admin.accounts.failedToUpdate'))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user