From e0a929179a9dc76e035ca7fda2b61d5ff46afbc5 Mon Sep 17 00:00:00 2001 From: Rigel Kent Date: Sun, 19 Apr 2020 14:11:40 +0200 Subject: [PATCH] Add filter inputs for blacklisted videos and muted accounts/servers --- .../instance-account-blocklist.component.html | 12 ++++++- .../instance-account-blocklist.component.ts | 11 +++++-- .../instance-server-blocklist.component.html | 10 ++++-- .../instance-server-blocklist.component.ts | 11 +++++-- .../moderation/moderation.component.scss | 8 +++++ .../video-abuse-list.component.scss | 8 ----- .../video-blacklist-list.component.html | 26 ++++++++++++--- .../video-blacklist-list.component.ts | 12 +++++-- .../app/shared/blocklist/blocklist.service.ts | 12 +++++-- .../video-blacklist.service.ts | 14 +++++--- .../api/server/server-blocklist.ts | 16 ++++++++-- server/controllers/api/users/my-blocklist.ts | 16 ++++++++-- server/controllers/api/videos/blacklist.ts | 8 ++++- .../validators/videos/video-blacklist.ts | 4 +++ server/models/account/account-blocklist.ts | 32 +++++++++++++++---- server/models/server/server-blocklist.ts | 17 ++++++++-- server/models/utils.ts | 15 +++++++-- server/models/video/video-abuse.ts | 18 ++++------- server/models/video/video-blacklist.ts | 13 ++++++-- 19 files changed, 202 insertions(+), 61 deletions(-) diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html index 6d77c8290..d340b5e57 100644 --- a/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html +++ b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html @@ -1,9 +1,19 @@ + +
+
+ +
+
+
diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.ts b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.ts index a3910177f..607b1cbe0 100644 --- a/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.ts +++ b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.ts @@ -7,13 +7,14 @@ import { AccountBlock, BlocklistService } from '@app/shared/blocklist' @Component({ selector: 'my-instance-account-blocklist', - styleUrls: [ './instance-account-blocklist.component.scss' ], + styleUrls: [ '../moderation.component.scss', './instance-account-blocklist.component.scss' ], templateUrl: './instance-account-blocklist.component.html' }) export class InstanceAccountBlocklistComponent extends RestTable implements OnInit { blockedAccounts: AccountBlock[] = [] totalRecords = 0 - rowsPerPage = 10 + rowsPerPageOptions = [ 20, 50, 100 ] + rowsPerPage = this.rowsPerPageOptions[0] sort: SortMeta = { field: 'createdAt', order: -1 } pagination: RestPagination = { count: this.rowsPerPage, start: 0 } @@ -49,7 +50,11 @@ export class InstanceAccountBlocklistComponent extends RestTable implements OnIn } protected loadData () { - return this.blocklistService.getInstanceAccountBlocklist(this.pagination, this.sort) + return this.blocklistService.getInstanceAccountBlocklist({ + pagination: this.pagination, + sort: this.sort, + search: this.search + }) .subscribe( resultList => { this.blockedAccounts = resultList.data diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html index 19b33a0f5..b6c87fdc8 100644 --- a/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html +++ b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html @@ -1,12 +1,18 @@
- +
+ +
+
Mute domain diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.ts b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.ts index 431729ef2..4efadc386 100644 --- a/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.ts +++ b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.ts @@ -9,7 +9,7 @@ import { BatchDomainsModalComponent } from '@app/+admin/config/shared/batch-doma @Component({ selector: 'my-instance-server-blocklist', - styleUrls: [ './instance-server-blocklist.component.scss' ], + styleUrls: [ '../moderation.component.scss', './instance-server-blocklist.component.scss' ], templateUrl: './instance-server-blocklist.component.html' }) export class InstanceServerBlocklistComponent extends RestTable implements OnInit { @@ -17,7 +17,8 @@ export class InstanceServerBlocklistComponent extends RestTable implements OnIni blockedServers: ServerBlock[] = [] totalRecords = 0 - rowsPerPage = 10 + rowsPerPageOptions = [ 20, 50, 100 ] + rowsPerPage = this.rowsPerPageOptions[0] sort: SortMeta = { field: 'createdAt', order: -1 } pagination: RestPagination = { count: this.rowsPerPage, start: 0 } @@ -72,7 +73,11 @@ export class InstanceServerBlocklistComponent extends RestTable implements OnIni } protected loadData () { - return this.blocklistService.getInstanceServerBlocklist(this.pagination, this.sort) + return this.blocklistService.getInstanceServerBlocklist({ + pagination: this.pagination, + sort: this.sort, + search: this.search + }) .subscribe( resultList => { this.blockedServers = resultList.data diff --git a/client/src/app/+admin/moderation/moderation.component.scss b/client/src/app/+admin/moderation/moderation.component.scss index a4ee65423..97af74541 100644 --- a/client/src/app/+admin/moderation/moderation.component.scss +++ b/client/src/app/+admin/moderation/moderation.component.scss @@ -7,6 +7,14 @@ margin-right: 30px; } +.caption { + justify-content: flex-end; + + input { + @include peertube-input-text(250px); + } +} + .moderation-expanded { font-size: 90%; diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.scss b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.scss index a9251e2f0..d6bc34935 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.scss +++ b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.scss @@ -1,14 +1,6 @@ @import 'mixins'; @import 'miniature'; -.caption { - justify-content: flex-end; - - input { - @include peertube-input-text(250px); - } -} - .video-details-date-updated { font-size: 90%; margin-top: .1rem; diff --git a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html index 90a786ad0..6375dacd9 100644 --- a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html +++ b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html @@ -1,9 +1,20 @@ + +
+
+ +
+
+
+ @@ -33,7 +44,7 @@
{{ videoBlacklist.video.name }} - +
by {{ videoBlacklist.video.channel?.displayName }} on {{ videoBlacklist.video.channel?.host }}
@@ -53,7 +64,10 @@ - +
@@ -61,8 +75,10 @@ - Blacklist reason: - +
+ Blacklist reason: + +
diff --git a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts index c38d30865..e9925f9bf 100644 --- a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts +++ b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts @@ -17,7 +17,8 @@ import { MarkdownService } from '@app/shared/renderer' export class VideoBlacklistListComponent extends RestTable implements OnInit { blacklist: (VideoBlacklist & { reasonHtml?: string })[] = [] totalRecords = 0 - rowsPerPage = 10 + rowsPerPageOptions = [ 20, 50, 100 ] + rowsPerPage = this.rowsPerPageOptions[0] sort: SortMeta = { field: 'createdAt', order: -1 } pagination: RestPagination = { count: this.rowsPerPage, start: 0 } listBlacklistTypeFilter: VideoBlacklistType = undefined @@ -38,7 +39,7 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit { ngOnInit () { this.serverService.getConfig() .subscribe(config => { - // don't filter if auto-blacklist not enabled as this will be the only list + // don't filter if auto-blacklist is not enabled as this will be the only list if (config.autoBlacklist.videos.ofUsers.enabled) { this.listBlacklistTypeFilter = VideoBlacklistType.MANUAL } @@ -91,7 +92,12 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit { } protected loadData () { - this.videoBlacklistService.listBlacklist(this.pagination, this.sort, this.listBlacklistTypeFilter) + this.videoBlacklistService.listBlacklist({ + pagination: this.pagination, + sort: this.sort, + search: this.search, + type: this.listBlacklistTypeFilter + }) .subscribe( async resultList => { this.totalRecords = resultList.total diff --git a/client/src/app/shared/blocklist/blocklist.service.ts b/client/src/app/shared/blocklist/blocklist.service.ts index c1f7312f0..5cf265bc1 100644 --- a/client/src/app/shared/blocklist/blocklist.service.ts +++ b/client/src/app/shared/blocklist/blocklist.service.ts @@ -76,10 +76,14 @@ export class BlocklistService { /*********************** Instance -> Account blocklist ***********************/ - getInstanceAccountBlocklist (pagination: RestPagination, sort: SortMeta) { + getInstanceAccountBlocklist (options: { pagination: RestPagination, sort: SortMeta, search: string }) { + const { pagination, sort, search } = options + let params = new HttpParams() params = this.restService.addRestGetParams(params, pagination, sort) + if (search) params = params.append('search', search) + return this.authHttp.get>(BlocklistService.BASE_SERVER_BLOCKLIST_URL + '/accounts', { params }) .pipe( map(res => this.restExtractor.convertResultListDateToHuman(res)), @@ -104,10 +108,14 @@ export class BlocklistService { /*********************** Instance -> Server blocklist ***********************/ - getInstanceServerBlocklist (pagination: RestPagination, sort: SortMeta) { + getInstanceServerBlocklist (options: { pagination: RestPagination, sort: SortMeta, search: string }) { + const { pagination, sort, search } = options + let params = new HttpParams() params = this.restService.addRestGetParams(params, pagination, sort) + if (search) params = params.append('search', search) + return this.authHttp.get>(BlocklistService.BASE_SERVER_BLOCKLIST_URL + '/servers', { params }) .pipe( map(res => this.restExtractor.convertResultListDateToHuman(res)), diff --git a/client/src/app/shared/video-blacklist/video-blacklist.service.ts b/client/src/app/shared/video-blacklist/video-blacklist.service.ts index 116177c4a..c0e13a651 100644 --- a/client/src/app/shared/video-blacklist/video-blacklist.service.ts +++ b/client/src/app/shared/video-blacklist/video-blacklist.service.ts @@ -19,13 +19,19 @@ export class VideoBlacklistService { private restExtractor: RestExtractor ) {} - listBlacklist (pagination: RestPagination, sort: SortMeta, type?: VideoBlacklistType): Observable> { + listBlacklist (options: { + pagination: RestPagination, + sort: SortMeta, + search?: string + type?: VideoBlacklistType + }): Observable> { + const { pagination, sort, search, type } = options + let params = new HttpParams() params = this.restService.addRestGetParams(params, pagination, sort) - if (type) { - params = params.set('type', type.toString()) - } + if (search) params = params.append('search', search) + if (type) params = params.append('type', type.toString()) return this.authHttp.get>(VideoBlacklistService.BASE_VIDEOS_URL + 'blacklist', { params }) .pipe( diff --git a/server/controllers/api/server/server-blocklist.ts b/server/controllers/api/server/server-blocklist.ts index d165db191..ffb7814fa 100644 --- a/server/controllers/api/server/server-blocklist.ts +++ b/server/controllers/api/server/server-blocklist.ts @@ -82,7 +82,13 @@ export { async function listBlockedAccounts (req: express.Request, res: express.Response) { const serverActor = await getServerActor() - const resultList = await AccountBlocklistModel.listForApi(serverActor.Account.id, req.query.start, req.query.count, req.query.sort) + const resultList = await AccountBlocklistModel.listForApi({ + start: req.query.start, + count: req.query.count, + sort: req.query.sort, + search: req.query.search, + accountId: serverActor.Account.id + }) return res.json(getFormattedObjects(resultList.data, resultList.total)) } @@ -107,7 +113,13 @@ async function unblockAccount (req: express.Request, res: express.Response) { async function listBlockedServers (req: express.Request, res: express.Response) { const serverActor = await getServerActor() - const resultList = await ServerBlocklistModel.listForApi(serverActor.Account.id, req.query.start, req.query.count, req.query.sort) + const resultList = await ServerBlocklistModel.listForApi({ + start: req.query.start, + count: req.query.count, + sort: req.query.sort, + search: req.query.search, + accountId: serverActor.Account.id + }) return res.json(getFormattedObjects(resultList.data, resultList.total)) } diff --git a/server/controllers/api/users/my-blocklist.ts b/server/controllers/api/users/my-blocklist.ts index 713c16022..3a44376f2 100644 --- a/server/controllers/api/users/my-blocklist.ts +++ b/server/controllers/api/users/my-blocklist.ts @@ -74,7 +74,13 @@ export { async function listBlockedAccounts (req: express.Request, res: express.Response) { const user = res.locals.oauth.token.User - const resultList = await AccountBlocklistModel.listForApi(user.Account.id, req.query.start, req.query.count, req.query.sort) + const resultList = await AccountBlocklistModel.listForApi({ + start: req.query.start, + count: req.query.count, + sort: req.query.sort, + search: req.query.search, + accountId: user.Account.id + }) return res.json(getFormattedObjects(resultList.data, resultList.total)) } @@ -99,7 +105,13 @@ async function unblockAccount (req: express.Request, res: express.Response) { async function listBlockedServers (req: express.Request, res: express.Response) { const user = res.locals.oauth.token.User - const resultList = await ServerBlocklistModel.listForApi(user.Account.id, req.query.start, req.query.count, req.query.sort) + const resultList = await ServerBlocklistModel.listForApi({ + start: req.query.start, + count: req.query.count, + sort: req.query.sort, + search: req.query.search, + accountId: user.Account.id + }) return res.json(getFormattedObjects(resultList.data, resultList.total)) } diff --git a/server/controllers/api/videos/blacklist.ts b/server/controllers/api/videos/blacklist.ts index 2a667480d..c4aa79cd2 100644 --- a/server/controllers/api/videos/blacklist.ts +++ b/server/controllers/api/videos/blacklist.ts @@ -102,7 +102,13 @@ async function updateVideoBlacklistController (req: express.Request, res: expres } async function listBlacklist (req: express.Request, res: express.Response) { - const resultList = await VideoBlacklistModel.listForApi(req.query.start, req.query.count, req.query.sort, req.query.type) + const resultList = await VideoBlacklistModel.listForApi({ + start: req.query.start, + count: req.query.count, + sort: req.query.sort, + search: req.query.search, + type: req.query.type + }) return res.json(getFormattedObjects(resultList.data, resultList.total)) } diff --git a/server/middlewares/validators/videos/video-blacklist.ts b/server/middlewares/validators/videos/video-blacklist.ts index 5440e57e7..4bd6a8333 100644 --- a/server/middlewares/validators/videos/video-blacklist.ts +++ b/server/middlewares/validators/videos/video-blacklist.ts @@ -69,6 +69,10 @@ const videosBlacklistFiltersValidator = [ query('type') .optional() .custom(isVideoBlacklistTypeValid).withMessage('Should have a valid video blacklist type attribute'), + query('search') + .optional() + .not() + .isEmpty().withMessage('Should have a valid search'), (req: express.Request, res: express.Response, next: express.NextFunction) => { logger.debug('Checking videos blacklist filters query', { parameters: req.query }) diff --git a/server/models/account/account-blocklist.ts b/server/models/account/account-blocklist.ts index e2f66d733..fe2d5d010 100644 --- a/server/models/account/account-blocklist.ts +++ b/server/models/account/account-blocklist.ts @@ -1,6 +1,6 @@ import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' import { AccountModel } from './account' -import { getSort } from '../utils' +import { getSort, searchAttribute } from '../utils' import { AccountBlock } from '../../../shared/models/blocklist' import { Op } from 'sequelize' import * as Bluebird from 'bluebird' @@ -111,16 +111,36 @@ export class AccountBlocklistModel extends Model { return AccountBlocklistModel.findOne(query) } - static listForApi (accountId: number, start: number, count: number, sort: string) { + static listForApi (parameters: { + start: number + count: number + sort: string + search?: string + accountId: number + }) { + const { start, count, sort, search, accountId } = parameters + const query = { offset: start, limit: count, - order: getSort(sort), - where: { - accountId - } + order: getSort(sort) } + const where = { + accountId + } + + if (search) { + Object.assign(where, { + [Op.or]: [ + { ...searchAttribute(search, '$BlockedAccount.name$') }, + { ...searchAttribute(search, '$BlockedAccount.Actor.url$') } + ] + }) + } + + Object.assign(query, { where }) + return AccountBlocklistModel .scope([ ScopeNames.WITH_ACCOUNTS ]) .findAndCountAll(query) diff --git a/server/models/server/server-blocklist.ts b/server/models/server/server-blocklist.ts index 883ae47ab..764203d2c 100644 --- a/server/models/server/server-blocklist.ts +++ b/server/models/server/server-blocklist.ts @@ -2,7 +2,7 @@ import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, Updated import { AccountModel } from '../account/account' import { ServerModel } from './server' import { ServerBlock } from '../../../shared/models/blocklist' -import { getSort } from '../utils' +import { getSort, searchAttribute } from '../utils' import * as Bluebird from 'bluebird' import { MServerBlocklist, MServerBlocklistAccountServer, MServerBlocklistFormattable } from '@server/typings/models' import { Op } from 'sequelize' @@ -120,16 +120,27 @@ export class ServerBlocklistModel extends Model { return ServerBlocklistModel.findOne(query) } - static listForApi (accountId: number, start: number, count: number, sort: string) { + static listForApi (parameters: { + start: number + count: number + sort: string + search?: string + accountId: number + }) { + const { start, count, sort, search, accountId } = parameters + const query = { offset: start, limit: count, order: getSort(sort), where: { - accountId + accountId, + ...searchAttribute(search, '$BlockedServer.host$') } } + console.log(search) + return ServerBlocklistModel .scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_SERVER ]) .findAndCountAll(query) diff --git a/server/models/utils.ts b/server/models/utils.ts index 06ff05864..7137419a2 100644 --- a/server/models/utils.ts +++ b/server/models/utils.ts @@ -1,7 +1,7 @@ import { Model, Sequelize } from 'sequelize-typescript' import validator from 'validator' import { Col } from 'sequelize/types/lib/utils' -import { literal, OrderItem } from 'sequelize' +import { literal, OrderItem, Op } from 'sequelize' type Primitive = string | Function | number | boolean | Symbol | undefined | null type DeepOmitHelper = { @@ -207,6 +207,16 @@ function buildDirectionAndField (value: string) { return { direction, field } } +function searchAttribute (sourceField, targetField) { + return sourceField + ? { + [targetField]: { + [Op.iLike]: `%${sourceField}%` + } + } + : {} +} + // --------------------------------------------------------------------------- export { @@ -228,7 +238,8 @@ export { parseAggregateResult, getFollowsSort, buildDirectionAndField, - createSafeIn + createSafeIn, + searchAttribute } // --------------------------------------------------------------------------- diff --git a/server/models/video/video-abuse.ts b/server/models/video/video-abuse.ts index d68608ca6..e8c3bd823 100644 --- a/server/models/video/video-abuse.ts +++ b/server/models/video/video-abuse.ts @@ -9,7 +9,7 @@ import { isVideoAbuseStateValid } from '../../helpers/custom-validators/video-abuses' import { AccountModel } from '../account/account' -import { buildBlockedAccountSQL, getSort, throwIfNotValid } from '../utils' +import { buildBlockedAccountSQL, getSort, throwIfNotValid, searchAttribute } from '../utils' import { VideoModel } from './video' import { VideoAbuseState, VideoDetails } from '../../../shared' import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants' @@ -17,8 +17,8 @@ import { MUserAccountId, MVideoAbuse, MVideoAbuseFormattable, MVideoAbuseVideo } import * as Bluebird from 'bluebird' import { literal, Op } from 'sequelize' import { ThumbnailModel } from './thumbnail' -import { VideoChannelModel } from './video-channel' import { VideoBlacklistModel } from './video-blacklist' +import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel' export enum ScopeNames { FOR_API = 'FOR_API' @@ -33,12 +33,6 @@ export enum ScopeNames { serverAccountId: number userAccountId: any }) => { - const search = (sourceField, targetField) => sourceField ? ({ - [targetField]: { - [Op.iLike]: `%${sourceField}%` - } - }) : {} - let where = { reporterAccountId: { [Op.notIn]: literal('(' + buildBlockedAccountSQL(options.serverAccountId, options.userAccountId) + ')') @@ -148,19 +142,19 @@ export enum ScopeNames { { model: AccountModel, required: true, - where: { ...search(options.searchReporter, 'name') } + where: { ...searchAttribute(options.searchReporter, 'name') } }, { model: VideoModel, required: false, - where: { ...search(options.searchVideo, 'name') }, + where: { ...searchAttribute(options.searchVideo, 'name') }, include: [ { model: ThumbnailModel }, { - model: VideoChannelModel.scope([ 'WITH_ACTOR', 'WITH_ACCOUNT' ]), - where: { ...search(options.searchVideoChannel, 'name') } + model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, { withAccount: true } as SummaryOptions ] }), + where: { ...searchAttribute(options.searchVideoChannel, 'name') } }, { attributes: [ 'id', 'reason', 'unfederated' ], diff --git a/server/models/video/video-blacklist.ts b/server/models/video/video-blacklist.ts index 694983cb3..680eba471 100644 --- a/server/models/video/video-blacklist.ts +++ b/server/models/video/video-blacklist.ts @@ -1,5 +1,5 @@ import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' -import { getBlacklistSort, SortType, throwIfNotValid } from '../utils' +import { getBlacklistSort, SortType, throwIfNotValid, searchAttribute } from '../utils' import { VideoModel } from './video' import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel' import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../helpers/custom-validators/video-blacklist' @@ -54,7 +54,15 @@ export class VideoBlacklistModel extends Model { }) Video: VideoModel - static listForApi (start: number, count: number, sort: SortType, type?: VideoBlacklistType) { + static listForApi (parameters: { + start: number + count: number + sort: SortType + search?: string + type?: VideoBlacklistType + }) { + const { start, count, sort, search, type } = parameters + function buildBaseQuery (): FindOptions { return { offset: start, @@ -70,6 +78,7 @@ export class VideoBlacklistModel extends Model { { model: VideoModel, required: true, + where: { ...searchAttribute(search, 'name') }, include: [ { model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, { withAccount: true } as SummaryOptions ] }),