From 034b43bb6bcaf400c7fd10d99d95db5332fc05b6 Mon Sep 17 00:00:00 2001 From: Mikita Taukachou Date: Wed, 14 Jun 2023 20:03:08 +0300 Subject: [PATCH] Enable automatic exclusion of non-required dependencies in the Publishing Wizard #6415 --- .../main/resources/services/config/config.js | 6 +- .../assets/js/app/dialog/BasePublishDialog.ts | 4 + .../js/app/issue/IssueDialogsManager.ts | 16 +- .../js/app/issue/view/IssueDetailsDialog.ts | 4 +- .../js/app/publish/PublishDialogItemList.ts | 12 +- .../assets/js/app/publish/PublishProcessor.ts | 165 ++++++++++++++---- .../app/resource/FindIdsByParentsRequest.ts | 29 +++ .../js/app/resource/FindIdsByParentsResult.ts | 37 ++++ .../json/FindIdsByParentsResultJson.ts | 5 + .../resource/content/ContentResource.java | 34 ++-- .../json/FindIdsByParentsResultJson.java | 49 ++++++ .../resource/content/ContentResourceTest.java | 18 ++ .../resource/content/find_ids_by_parents.json | 10 ++ .../content/find_ids_by_parents_params.json | 5 + .../specs/issue/issue.invalid.content.spec.js | 7 +- 15 files changed, 339 insertions(+), 62 deletions(-) create mode 100644 modules/lib/src/main/resources/assets/js/app/resource/FindIdsByParentsRequest.ts create mode 100644 modules/lib/src/main/resources/assets/js/app/resource/FindIdsByParentsResult.ts create mode 100644 modules/lib/src/main/resources/assets/js/app/resource/json/FindIdsByParentsResultJson.ts create mode 100644 modules/rest/src/main/java/com/enonic/xp/app/contentstudio/rest/resource/content/json/FindIdsByParentsResultJson.java create mode 100644 modules/rest/src/test/resources/com/enonic/xp/app/contentstudio/rest/resource/content/find_ids_by_parents.json create mode 100644 modules/rest/src/test/resources/com/enonic/xp/app/contentstudio/rest/resource/content/find_ids_by_parents_params.json diff --git a/modules/app/src/main/resources/services/config/config.js b/modules/app/src/main/resources/services/config/config.js index 5535216389..1ce9783363 100644 --- a/modules/app/src/main/resources/services/config/config.js +++ b/modules/app/src/main/resources/services/config/config.js @@ -8,6 +8,7 @@ function handleGet() { const context = contextLib.get(); const branch = context.branch; const allowContentUpdate = app.config['publishingWizard.allowContentUpdate'] !== 'false'; + const excludeDependencies = app.config['publishingWizard.excludeDependencies'] === 'true' || false; const allowPathTransliteration = app.config['contentWizard.allowPathTransliteration'] !== 'false'; const enableCollaboration = app.config['contentWizard.enableCollaboration'] !== 'false'; const hideDefaultProject = app.config['settings.hideDefaultProject'] !== 'false'; @@ -17,14 +18,15 @@ function handleGet() { contentType: 'application/json', body: { allowContentUpdate, + excludeDependencies, allowPathTransliteration, adminUrl: admin.getBaseUri(), assetsUri: portal.assetUrl({ path: '' }), toolUri: admin.getToolUrl( - app.name, - 'main' + app.name, + 'main' ), appId: app.name, appVersion: app.version, diff --git a/modules/lib/src/main/resources/assets/js/app/dialog/BasePublishDialog.ts b/modules/lib/src/main/resources/assets/js/app/dialog/BasePublishDialog.ts index a57a64d8ef..ee2dadbe79 100644 --- a/modules/lib/src/main/resources/assets/js/app/dialog/BasePublishDialog.ts +++ b/modules/lib/src/main/resources/assets/js/app/dialog/BasePublishDialog.ts @@ -166,6 +166,10 @@ export abstract class BasePublishDialog this.lockControls(); } + setKeepDependencies(keepDependencies: boolean): void { + this.publishProcessor.setKeepDependencies(keepDependencies); + } + getButtonRow(): DropdownButtonRow { return super.getButtonRow() as DropdownButtonRow; } diff --git a/modules/lib/src/main/resources/assets/js/app/issue/IssueDialogsManager.ts b/modules/lib/src/main/resources/assets/js/app/issue/IssueDialogsManager.ts index b4dac1c311..f305cdbcbc 100644 --- a/modules/lib/src/main/resources/assets/js/app/issue/IssueDialogsManager.ts +++ b/modules/lib/src/main/resources/assets/js/app/issue/IssueDialogsManager.ts @@ -1,14 +1,14 @@ import {ModalDialog} from '@enonic/lib-admin-ui/ui/dialog/ModalDialog'; -import {IssueDetailsDialog} from './view/IssueDetailsDialog'; -import {IssueListDialog} from './view/IssueListDialog'; -import {Issue} from './Issue'; -import {CreateIssueDialog} from './view/CreateIssueDialog'; -import {GetIssueRequest} from './resource/GetIssueRequest'; +import {ContentPublishPromptEvent} from '../browse/ContentPublishPromptEvent'; import {ContentSummaryAndCompareStatus} from '../content/ContentSummaryAndCompareStatus'; import {ContentPublishDialog} from '../publish/ContentPublishDialog'; -import {ContentPublishPromptEvent} from '../browse/ContentPublishPromptEvent'; -import {IssueServerEventsHandler} from './event/IssueServerEventsHandler'; import {RequestContentPublishDialog} from '../publish/RequestContentPublishDialog'; +import {IssueServerEventsHandler} from './event/IssueServerEventsHandler'; +import {Issue} from './Issue'; +import {GetIssueRequest} from './resource/GetIssueRequest'; +import {CreateIssueDialog} from './view/CreateIssueDialog'; +import {IssueDetailsDialog} from './view/IssueDetailsDialog'; +import {IssueListDialog} from './view/IssueListDialog'; export class IssueDialogsManager { @@ -56,6 +56,7 @@ export class IssueDialogsManager { if (this.detailsDialog.isOpen()) { this.detailsDialog.getEl().focus(); } + this.publishDialog.setKeepDependencies(false); this.publishDialog.unClosed(this.publishDialogCloseHandler); this.detailsDialog.onClosed(this.detailsDialogCloseHandler); IssueServerEventsHandler.getInstance().unIssueUpdated(this.issueUpdateHandler); @@ -142,6 +143,7 @@ export class IssueDialogsManager { ContentPublishPromptEvent.on(() => { if (this.detailsDialog.isOpen()) { this.detailsDialog.unClosed(this.detailsDialogCloseHandler); + this.publishDialog.setKeepDependencies(true); this.publishDialog.onCloseButtonClicked(this.publishDialogBeforeClosedHandler); this.publishDialog.onClosed(this.publishDialogCloseHandler); this.issue = this.detailsDialog.getIssue(); diff --git a/modules/lib/src/main/resources/assets/js/app/issue/view/IssueDetailsDialog.ts b/modules/lib/src/main/resources/assets/js/app/issue/view/IssueDetailsDialog.ts index 3738703f4c..6eabebdf4a 100644 --- a/modules/lib/src/main/resources/assets/js/app/issue/view/IssueDetailsDialog.ts +++ b/modules/lib/src/main/resources/assets/js/app/issue/view/IssueDetailsDialog.ts @@ -165,7 +165,7 @@ export class IssueDetailsDialog this.initActions(); - this.publishProcessor = new PublishProcessor(this.getItemList(), this.getDependantList()); + this.publishProcessor = new PublishProcessor(this.getItemList(), this.getDependantList(), true); this.publishProcessor.setIgnoreSilent(true); this.commentTextArea = new IssueCommentTextArea(); @@ -813,7 +813,7 @@ export class IssueDetailsDialog private initItemListTogglers(itemList: PublishDialogItemList): boolean { return itemList.getItemViews().reduce((wasAnyIncluded, itemView) => { - const isIncluded = itemView.toggleIncludeChildren(this.areChildrenIncludedInIssue(itemView.getContentId())); + const isIncluded = itemView.toggleIncludeChildren(this.areChildrenIncludedInIssue(itemView.getContentId())); // return isIncluded || wasAnyIncluded; }, false); } diff --git a/modules/lib/src/main/resources/assets/js/app/publish/PublishDialogItemList.ts b/modules/lib/src/main/resources/assets/js/app/publish/PublishDialogItemList.ts index b011e1c6fd..cd5a30476d 100644 --- a/modules/lib/src/main/resources/assets/js/app/publish/PublishDialogItemList.ts +++ b/modules/lib/src/main/resources/assets/js/app/publish/PublishDialogItemList.ts @@ -91,7 +91,7 @@ export class PublishDialogItemList return !!this.config.allowOnlyItemRemoval || this.getItemCount() > 1; } - public setExcludeChildrenIds(ids: ContentId[]) { + setExcludeChildrenIds(ids: ContentId[]) { this.excludeChildrenIds = ids; this.getItemViews().forEach(itemView => { @@ -103,17 +103,21 @@ export class PublishDialogItemList this.debounceNotifyListChanged(); } - public getExcludeChildrenIds(): ContentId[] { + getExcludeChildrenIds(): ContentId[] { return this.excludeChildrenIds.slice(); } - public clearExcludeChildrenIds() { + clearExcludeChildrenIds() { this.excludeChildrenIds = []; } + getIncludeChildrenIds(): ContentId[] { + return this.getItemsIds().filter(id => !ArrayHelper.contains(this.excludeChildrenIds, id)); + } + removeItemsByIds(contentIds: ContentId[]) { contentIds.forEach((id: ContentId) => { - const item: ContentSummaryAndCompareStatus = this.getItem(id.toString()); + const item: ContentSummaryAndCompareStatus = this.getItem(id.toString()); if (item) { this.removeItem(item, true); diff --git a/modules/lib/src/main/resources/assets/js/app/publish/PublishProcessor.ts b/modules/lib/src/main/resources/assets/js/app/publish/PublishProcessor.ts index 4f2bb7b958..c26b808d68 100644 --- a/modules/lib/src/main/resources/assets/js/app/publish/PublishProcessor.ts +++ b/modules/lib/src/main/resources/assets/js/app/publish/PublishProcessor.ts @@ -1,6 +1,7 @@ import {DefaultErrorHandler} from '@enonic/lib-admin-ui/DefaultErrorHandler'; import {NotifyManager} from '@enonic/lib-admin-ui/notify/NotifyManager'; import {AppHelper} from '@enonic/lib-admin-ui/util/AppHelper'; +import {CONFIG} from '@enonic/lib-admin-ui/util/Config'; import {i18n} from '@enonic/lib-admin-ui/util/Messages'; import * as Q from 'q'; import {CompareStatus} from '../content/CompareStatus'; @@ -9,6 +10,7 @@ import {ContentSummaryAndCompareStatus} from '../content/ContentSummaryAndCompar import {SelectionType} from '../dialog/DialogDependantItemsList'; import {EditContentEvent} from '../event/EditContentEvent'; import {ContentSummaryAndCompareStatusFetcher} from '../resource/ContentSummaryAndCompareStatusFetcher'; +import {FindIdsByParentsRequest} from '../resource/FindIdsByParentsRequest'; import {GetDescendantsOfContentsRequest} from '../resource/GetDescendantsOfContentsRequest'; import {ResolvePublishDependenciesRequest} from '../resource/ResolvePublishDependenciesRequest'; import {ResolvePublishDependenciesResult} from '../resource/ResolvePublishDependenciesResult'; @@ -21,6 +23,11 @@ interface ReloadDependenciesParams { silent?: boolean; } +interface LoadDependenciesParams + extends ReloadDependenciesParams { + ids: ContentId[]; +} + export type LoadingStartedListener = (checking: boolean) => void; export class PublishProcessor { @@ -63,14 +70,19 @@ export class PublishProcessor { private instanceId: number; + private cleanLoad: boolean = true; + + private keepDependencies: boolean; + readonly reloadDependenciesDebounced: (params: ReloadDependenciesParams) => void; private static debug: boolean = false; - constructor(itemList: PublishDialogItemList, dependantList: PublishDialogDependantList) { + constructor(itemList: PublishDialogItemList, dependantList: PublishDialogDependantList, keepDependencies = false) { this.instanceId = 0; this.itemList = itemList; this.dependantList = dependantList; + this.keepDependencies = keepDependencies; this.reloadDependenciesDebounced = AppHelper.debounce(this.reloadPublishDependencies.bind(this), 100); this.initListeners(); @@ -101,7 +113,7 @@ export class PublishProcessor { this.itemList.onChildrenListChanged((childrenRemoved) => { this.reloadDependenciesDebounced({ resetDependantItems: true, - resetExclusions: childrenRemoved, + resetExclusions: !this.keepDependencies || childrenRemoved, silent: !this.ignoreSilent && !this.itemList.isVisible(), }); }); @@ -151,13 +163,22 @@ export class PublishProcessor { }); } - reloadPublishDependencies({resetDependantItems, resetExclusions, silent}: ReloadDependenciesParams): void { + setKeepDependencies(keepDependencies: boolean): void { + this.keepDependencies = keepDependencies; + } + + reloadPublishDependencies(params: ReloadDependenciesParams): void { + const {resetDependantItems, resetExclusions, silent} = params; + const excludeNonRequired = CONFIG.isTrue('excludeDependencies') && !this.keepDependencies; + if (!silent) { this.notifyLoadingStarted(true); } const ids = this.getContentToPublishIds(); const isNoItemsToPublish = ids.length === 0; + const needExcludeNonRequired = excludeNonRequired && (this.cleanLoad || resetExclusions) && !isNoItemsToPublish; + const isNothingToExclude = !resetExclusions && this.isSomeOrNoneExcluded(); if (resetExclusions) { this.resetExcludedIds(); @@ -165,8 +186,10 @@ export class PublishProcessor { if (isNoItemsToPublish) { this.handleNoPublishItemsToLoad(resetDependantItems, silent); + } else if (needExcludeNonRequired && !isNothingToExclude) { + this.cleanLoadPublishDependencies({...params, ids}); } else { - this.loadPublishDependencies(ids, resetDependantItems, silent); + this.loadPublishDependencies({...params, ids}); } } @@ -192,7 +215,8 @@ export class PublishProcessor { console.debug('PublishProcessor.reloadPublishDependencies: resolved dependants = ', dependants); } - if (resetDependantItems) { // just opened or first time loading children + // just opened or first time loading children + if (resetDependantItems) { this.dependantList.setItems(dependants); } else { this.filterDependantItems(dependants); @@ -201,38 +225,91 @@ export class PublishProcessor { this.dependantList.refresh(); } - private loadPublishDependencies(ids: ContentId[], resetDependantItems?: boolean, silent?: boolean): void { + private async cleanLoadPublishDependencies({ids, resetDependantItems, silent}: LoadDependenciesParams): Promise { + const instanceId = this.instanceId; + this.cleanLoad = false; + + try { + const [maxResult, childrenIds] = await Q.all([ + this.createResolveDependenciesRequest(ids).sendAndParse(), + this.findIncludedChildrenIds(), + ]); + + const potentialExcludedIds = maxResult.getDependants().filter(id => !this.itemsIncludeId(childrenIds, id)); + + const minResult = await this.createResolveDependenciesRequest(ids, potentialExcludedIds).sendAndParse(); + + const excludedIds = potentialExcludedIds.filter(id => !this.itemsIncludeId(minResult.getRequired(), id)); + + if (instanceId !== this.instanceId) { + return; + } + + this.setExcludedIds(excludedIds); + + this.processResolveDependenciesResult(minResult); + this.handleExclusionResult(); + + this.allDependantIds = maxResult.getDependants(); + + const descendants = await this.loadDescendants(); + + if (instanceId !== this.instanceId) { + return; + } + + this.processResolveDescendantsResult(descendants, resetDependantItems); + if (!silent) { + this.notifyLoadingFinished(); + } + } catch (reason) { + if (instanceId === this.instanceId) { + this.notifyLoadingFailed(); + DefaultErrorHandler.handle(reason); + } + } + } + + private async loadPublishDependencies({ids, resetDependantItems, silent}: LoadDependenciesParams): Promise { const instanceId = this.instanceId; - this.createResolveDependenciesRequest(ids, this.getExcludedIds()).sendAndParse() - .then((result: ResolvePublishDependenciesResult) => { - this.processResolveDependenciesResult(result); - this.handleExclusionResult(); - }).then(() => { + this.cleanLoad = false; + + try { + const result = await this.createResolveDependenciesRequest(ids, this.getExcludedIds()).sendAndParse(); + + if (instanceId !== this.instanceId) { + return; + } + + this.processResolveDependenciesResult(result); + this.handleExclusionResult(); + const hasExcluded = this.getExcludedIds().length > 0; - if (hasExcluded) { - return this.createResolveDependenciesRequest(ids, []).sendAndParse().then((result: ResolvePublishDependenciesResult) => { - if (instanceId === this.instanceId) { - this.allDependantIds = [...result.getDependants()]; - } - }); - } else if (instanceId === this.instanceId) { - this.allDependantIds = [...this.dependantIds]; + const allDependantIds = hasExcluded ? await this.createResolveDependenciesRequest(ids).sendAndParse().then( + r => r.getDependants()) : this.dependantIds; + + if (instanceId !== this.instanceId) { + return; } - }).then(() => { - return this.loadDescendants().then((descendants: ContentSummaryAndCompareStatus[]) => { - if (instanceId === this.instanceId) { - this.processResolveDescendantsResult(descendants, resetDependantItems); - if (!silent) { - this.notifyLoadingFinished(); - } - } - }); - }).catch((reason) => { + + this.allDependantIds = [...allDependantIds]; + + const descendants = await this.loadDescendants(); + + if (instanceId !== this.instanceId) { + return; + } + + this.processResolveDescendantsResult(descendants, resetDependantItems); + if (!silent) { + this.notifyLoadingFinished(); + } + } catch (reason) { if (instanceId === this.instanceId) { this.notifyLoadingFailed(); DefaultErrorHandler.handle(reason); } - }); + } } updateLoadExcluded(loadExcluded: boolean): void { @@ -259,7 +336,7 @@ export class PublishProcessor { const instanceId = this.instanceId; this.notifyLoadingStarted(false); - this.createResolveDependenciesRequest(ids, []).sendAndParse().then((result: ResolvePublishDependenciesResult) => { + this.createResolveDependenciesRequest(ids).sendAndParse().then((result: ResolvePublishDependenciesResult) => { if (this.instanceId === instanceId) { this.allDependantIds = [...result.getDependants()]; @@ -278,11 +355,21 @@ export class PublishProcessor { }); } - private createResolveDependenciesRequest(ids: ContentId[], excludedIds: ContentId[]): ResolvePublishDependenciesRequest { + private findIncludedChildrenIds(): Q.Promise { + const parentIds = this.itemList.getIncludeChildrenIds(); + return parentIds.length === 0 ? Q([]) : new FindIdsByParentsRequest(parentIds).sendAndParse(); + } + + private createResolveDependenciesRequest( + ids: ContentId[], + excludedIds: ContentId[] = [], + excludedChildrenIds?: ContentId[], + ): ResolvePublishDependenciesRequest { + return ResolvePublishDependenciesRequest.create() .setIds(ids) .setExcludedIds(excludedIds) - .setExcludeChildrenIds(this.itemList.getExcludeChildrenIds()) + .setExcludeChildrenIds(excludedChildrenIds ?? this.getExcludeChildrenIds()) .build(); } @@ -314,6 +401,12 @@ export class PublishProcessor { if (this.isAnyExcluded(inProgressIds) || this.isAnyExcluded(invalidIds)) { NotifyManager.get().showFeedback(i18n('dialog.publish.notAllExcluded')); } + + const missingExcludedIds = this.dependantList.getItemsIds().filter( + id => !this.itemsIncludeId(this.dependantIds, id) && !this.itemsIncludeId(this.excludedIds, id)); + if (missingExcludedIds.length > 0) { + this.setExcludedIds([...this.excludedIds, ...missingExcludedIds]); + } } private itemsIncludeId(sourceIds: ContentId[], targetId: ContentId): boolean { @@ -328,6 +421,10 @@ export class PublishProcessor { return this.getExcludedIds().some((excludedId: ContentId) => this.itemsIncludeId(ids, excludedId)); } + private isSomeOrNoneExcluded(): boolean { + return !this.allDependantIds.every((dependantId: ContentId) => this.itemsIncludeId(this.getExcludedIds(), dependantId)); + } + getInProgressCount(dependantOnly = false): number { const ids = this.getInProgressIdsWithoutInvalid(); return dependantOnly ? ids.filter(id => this.itemsIncludeId(this.dependantIds, id)).length : ids.length; @@ -368,6 +465,8 @@ export class PublishProcessor { reset(): void { this.instanceId += 1; + this.cleanLoad = true; + this.itemList.setExcludeChildrenIds([]); this.itemList.setItems([]); this.itemList.setReadOnly(false); diff --git a/modules/lib/src/main/resources/assets/js/app/resource/FindIdsByParentsRequest.ts b/modules/lib/src/main/resources/assets/js/app/resource/FindIdsByParentsRequest.ts new file mode 100644 index 0000000000..2477e3239e --- /dev/null +++ b/modules/lib/src/main/resources/assets/js/app/resource/FindIdsByParentsRequest.ts @@ -0,0 +1,29 @@ +import {HttpMethod} from '@enonic/lib-admin-ui/rest/HttpMethod'; +import {JsonResponse} from '@enonic/lib-admin-ui/rest/JsonResponse'; +import {ContentId} from '../content/ContentId'; +import {CmsContentResourceRequest} from './CmsContentResourceRequest'; +import {FindIdsByParentsResult} from './FindIdsByParentsResult'; +import {FindIdsByParentsResultJson} from './json/FindIdsByParentsResultJson'; + +export class FindIdsByParentsRequest + extends CmsContentResourceRequest { + + private readonly ids: string[]; + + constructor(ids: ContentId[]) { + super(); + this.setMethod(HttpMethod.POST); + this.addRequestPathElements('findIdsByParents'); + this.ids = ids.map(id => id.toString()); + } + + getParams(): Object { + return { + contentIds: this.ids, + }; + } + + protected parseResponse(response: JsonResponse): ContentId[] { + return FindIdsByParentsResult.fromJson(response.getResult()).getIds(); + } +} diff --git a/modules/lib/src/main/resources/assets/js/app/resource/FindIdsByParentsResult.ts b/modules/lib/src/main/resources/assets/js/app/resource/FindIdsByParentsResult.ts new file mode 100644 index 0000000000..32aa8055f4 --- /dev/null +++ b/modules/lib/src/main/resources/assets/js/app/resource/FindIdsByParentsResult.ts @@ -0,0 +1,37 @@ +import {ContentId} from '../content/ContentId'; +import {FindIdsByParentsResultJson} from './json/FindIdsByParentsResultJson'; + +export class FindIdsByParentsResult { + + ids: ContentId[]; + + constructor(builder: Builder) { + this.ids = builder.ids; + } + + getIds(): ContentId[] { + return this.ids; + } + + static fromJson(json: FindIdsByParentsResultJson): FindIdsByParentsResult { + const dependants: ContentId[] = json.ids?.map(dependant => new ContentId(dependant.id)) ?? []; + return FindIdsByParentsResult.create().setDependentContents(dependants).build(); + } + + static create(): Builder { + return new Builder(); + } +} + +export class Builder { + ids: ContentId[]; + + setDependentContents(value: ContentId[]): Builder { + this.ids = value; + return this; + } + + build(): FindIdsByParentsResult { + return new FindIdsByParentsResult(this); + } +} diff --git a/modules/lib/src/main/resources/assets/js/app/resource/json/FindIdsByParentsResultJson.ts b/modules/lib/src/main/resources/assets/js/app/resource/json/FindIdsByParentsResultJson.ts new file mode 100644 index 0000000000..0dd6fa92ee --- /dev/null +++ b/modules/lib/src/main/resources/assets/js/app/resource/json/FindIdsByParentsResultJson.ts @@ -0,0 +1,5 @@ +import {ContentIdBaseItemJson} from './ContentIdBaseItemJson'; + +export interface FindIdsByParentsResultJson { + ids: ContentIdBaseItemJson[]; +} diff --git a/modules/rest/src/main/java/com/enonic/xp/app/contentstudio/rest/resource/content/ContentResource.java b/modules/rest/src/main/java/com/enonic/xp/app/contentstudio/rest/resource/content/ContentResource.java index c0f69256e4..d79c068aa1 100644 --- a/modules/rest/src/main/java/com/enonic/xp/app/contentstudio/rest/resource/content/ContentResource.java +++ b/modules/rest/src/main/java/com/enonic/xp/app/contentstudio/rest/resource/content/ContentResource.java @@ -82,6 +82,7 @@ import com.enonic.xp.app.contentstudio.rest.resource.content.json.EffectivePermissionAccessJson; import com.enonic.xp.app.contentstudio.rest.resource.content.json.EffectivePermissionJson; import com.enonic.xp.app.contentstudio.rest.resource.content.json.EffectivePermissionMemberJson; +import com.enonic.xp.app.contentstudio.rest.resource.content.json.FindIdsByParentsResultJson; import com.enonic.xp.app.contentstudio.rest.resource.content.json.GetContentVersionsJson; import com.enonic.xp.app.contentstudio.rest.resource.content.json.GetDependenciesResultJson; import com.enonic.xp.app.contentstudio.rest.resource.content.json.GetDescendantsOfContents; @@ -651,6 +652,19 @@ public HasUnpublishedChildrenResultJson hasUnpublishedChildren( final ContentIds return result.build(); } + @POST + @Path("findIdsByParents") + public FindIdsByParentsResultJson findIdsByParents( final ContentIdsJson ids ) + { + final ArrayList childrenIds = new ArrayList<>(); + ids.getContentIds().stream().forEach( id -> { + FindContentByParentParams params = FindContentByParentParams.create().parentId( id ).recursive( true ).build(); + childrenIds.addAll( this.contentService.findIdsByParent( params ).getContentIds().getSet() ); + } ); + + return FindIdsByParentsResultJson.create().setRequestedContents( ContentIds.from( childrenIds ) ).build(); + } + @POST @Path("resolvePublishContent") public ResolvePublishContentResultJson resolvePublishContent( final ResolvePublishDependenciesJson params ) @@ -661,20 +675,16 @@ public ResolvePublishContentResultJson resolvePublishContent( final ResolvePubli final ContentIds excludeChildrenIds = ContentIds.from( params.getExcludeChildrenIds() ); //Resolves the publish dependencies - final CompareContentResults compareResults = contentService.resolvePublishDependencies( ResolvePublishDependenciesParams.create() - .target( - ContentConstants.BRANCH_MASTER ) - .contentIds( requestedContentIds ) - .excludedContentIds( excludeContentIds ) - .excludeChildrenIds( - excludeChildrenIds ) - .build() ); + final ResolvePublishDependenciesParams resolveParams = + ResolvePublishDependenciesParams.create().target( ContentConstants.BRANCH_MASTER ).contentIds( + requestedContentIds ).excludedContentIds( excludeContentIds ).excludeChildrenIds( excludeChildrenIds ).build(); + final CompareContentResults compareResults = contentService.resolvePublishDependencies( resolveParams ); //Resolved the dependent ContentPublishItem - final ContentIds dependentContentIds = ContentIds.from( compareResults.contentIds() - .stream() - .filter( contentId -> !requestedContentIds.contains( contentId ) ).collect( - Collectors.toList() ) ); + final List contentIds = + compareResults.contentIds().stream().filter( contentId -> !requestedContentIds.contains( contentId ) ).collect( + Collectors.toList() ); + final ContentIds dependentContentIds = ContentIds.from( contentIds ); final ContentIds fullPublishList = ContentIds.create().addAll( dependentContentIds ).addAll( requestedContentIds ).build(); diff --git a/modules/rest/src/main/java/com/enonic/xp/app/contentstudio/rest/resource/content/json/FindIdsByParentsResultJson.java b/modules/rest/src/main/java/com/enonic/xp/app/contentstudio/rest/resource/content/json/FindIdsByParentsResultJson.java new file mode 100644 index 0000000000..c8b28af17c --- /dev/null +++ b/modules/rest/src/main/java/com/enonic/xp/app/contentstudio/rest/resource/content/json/FindIdsByParentsResultJson.java @@ -0,0 +1,49 @@ +package com.enonic.xp.app.contentstudio.rest.resource.content.json; + +import java.util.List; +import java.util.stream.Collectors; + +import com.enonic.xp.app.contentstudio.json.content.ContentIdJson; +import com.enonic.xp.content.ContentIds; + +public class FindIdsByParentsResultJson +{ + private final List ids; + + private FindIdsByParentsResultJson( Builder builder ) + { + ids = builder.ids.stream().map( ContentIdJson::new ).collect( Collectors.toList() ); + } + + public static Builder create() + { + return new Builder(); + } + + @SuppressWarnings("unused") + public List getIds() + { + return ids; + } + + public static final class Builder + { + + private ContentIds ids; + + private Builder() + { + } + + public Builder setRequestedContents( final ContentIds ids ) + { + this.ids = ids; + return this; + } + + public FindIdsByParentsResultJson build() + { + return new FindIdsByParentsResultJson( this ); + } + } +} diff --git a/modules/rest/src/test/java/com/enonic/xp/app/contentstudio/rest/resource/content/ContentResourceTest.java b/modules/rest/src/test/java/com/enonic/xp/app/contentstudio/rest/resource/content/ContentResourceTest.java index 987c13fa58..8e55caf359 100644 --- a/modules/rest/src/test/java/com/enonic/xp/app/contentstudio/rest/resource/content/ContentResourceTest.java +++ b/modules/rest/src/test/java/com/enonic/xp/app/contentstudio/rest/resource/content/ContentResourceTest.java @@ -1180,6 +1180,24 @@ public void has_unpublished_children() true ); } + @Test + public void find_ids_by_parents() + throws Exception + { + final ContentId childId1 = ContentId.from( "childId1" ); + final ContentId childId2 = ContentId.from( "childId2" ); + final ContentIds childrenIds = ContentIds.from( childId1, childId2 ); + + Mockito.when( contentService.findIdsByParent( Mockito.isA( FindContentByParentParams.class ) ) ).thenReturn( + FindContentIdsByParentResult.create().contentIds( childrenIds ).build() ); + + String jsonString = request().path( "content/findIdsByParents" ).entity( readFromFile( "find_ids_by_parents_params.json" ), + MediaType.APPLICATION_JSON_TYPE ).post().getAsString(); + + assertJson( "find_ids_by_parents.json", jsonString ); + + } + @Test public void resolve_publish_contents() throws Exception diff --git a/modules/rest/src/test/resources/com/enonic/xp/app/contentstudio/rest/resource/content/find_ids_by_parents.json b/modules/rest/src/test/resources/com/enonic/xp/app/contentstudio/rest/resource/content/find_ids_by_parents.json new file mode 100644 index 0000000000..6fea74f9b6 --- /dev/null +++ b/modules/rest/src/test/resources/com/enonic/xp/app/contentstudio/rest/resource/content/find_ids_by_parents.json @@ -0,0 +1,10 @@ +{ + "ids": [ + { + "id": "childId1" + }, + { + "id": "childId2" + } + ] +} diff --git a/modules/rest/src/test/resources/com/enonic/xp/app/contentstudio/rest/resource/content/find_ids_by_parents_params.json b/modules/rest/src/test/resources/com/enonic/xp/app/contentstudio/rest/resource/content/find_ids_by_parents_params.json new file mode 100644 index 0000000000..20786ed949 --- /dev/null +++ b/modules/rest/src/test/resources/com/enonic/xp/app/contentstudio/rest/resource/content/find_ids_by_parents_params.json @@ -0,0 +1,5 @@ +{ + "contentIds": [ + "parentId" + ] +} \ No newline at end of file diff --git a/testing/specs/issue/issue.invalid.content.spec.js b/testing/specs/issue/issue.invalid.content.spec.js index 64ddab0e40..2cd6cdf501 100644 --- a/testing/specs/issue/issue.invalid.content.spec.js +++ b/testing/specs/issue/issue.invalid.content.spec.js @@ -138,9 +138,12 @@ describe('issue.invalid.content.spec: create a issue with invalid content', func // 3. Exclude a dependant item: click on the checkbox: await issueDetailsDialogItemsTab.clickOnCheckboxInDependentItem(TEST_CONTENT_NAME); await issueDetailsDialogItemsTab.clickOnApplySelectionButton(); - // 4. Click on Publish button, 'Publish Wizard' should be loaded: + await issueDetailsDialogItemsTab.waitForNotificationMessage(); + await studioUtils.saveScreenshot('publish_wizard_content_excluded_0'); + // 4. Click on 'Publish' button in the 'Issue Details' dialog, 'Publish Wizard' should be loaded: let contentPublishDialog = await issueDetailsDialogItemsTab.clickOnPublishAndOpenPublishWizard(); - // 6. Verify that removed dependant item is not present in the list in 'Content Publish' dialog: + await studioUtils.saveScreenshot('publish_wizard_content_excluded_1'); + // 5. Verify that removed dependant item is not present in the list in 'Content Publish' dialog: let result = await contentPublishDialog.getDisplayNameInDependentItems(); // returns a truthy value for at least one element in the array contains the name. Otherwise, false. let isPresent = result.some(el => el.includes(TEST_CONTENT_NAME));