Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
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
35 changes: 31 additions & 4 deletions src/Umbraco.Web.UI.Client/mocks/db/document-publishing.manager.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type { UmbMockDocumentModel } from '../data/mock-data-set.types.js';

Check warning on line 1 in src/Umbraco.Web.UI.Client/mocks/db/document-publishing.manager.ts

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (v17/improvement/save-and-publish-take-three)

❌ New issue: String Heavy Function Arguments

In this module, 46.7% of all arguments to its 9 functions are strings. The threshold for string arguments is 39.0%. The functions in this file have a high ratio of strings as arguments. Avoid adding more.
import type { UmbDocumentMockDB } from './document.db.js';
import type {
CreateAndPublishDocumentRequestModel,
PublishDocumentRequestModel,
PublishDocumentWithDescendantsRequestModel,
UnpublishDocumentRequestModel,
UpdateAndPublishDocumentRequestModel,
} from '@umbraco-cms/backoffice/external/backend-api';
import { UmbId } from '@umbraco-cms/backoffice/id';
import type { DocumentVariantResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
Expand Down Expand Up @@ -55,6 +57,34 @@
this.#documentDb.detail.update(id, document);
}

createAndPublish(data: CreateAndPublishDocumentRequestModel) {
const id = this.#documentDb.detail.create(data);
this.#publishCultures(id, data.culturesToPublish);
return id;
}

updateAndPublish(id: string, data: UpdateAndPublishDocumentRequestModel) {
this.#documentDb.detail.update(id, data);
this.#publishCultures(id, data.culturesToPublish);
}

#publishCultures(id: string, culturesToPublish: Array<string>) {
const document: UmbMockDocumentModel = this.#documentDb.detail.read(id);

// Invariant content types publish with an empty cultures array; publish the invariant variant in that case.
const cultures: Array<string | null> = culturesToPublish.length > 0 ? culturesToPublish : [null];

cultures.forEach((culture) => {
const variant = document.variants.find((x) => x.culture === culture);
if (variant) {
variant.state = 'Published' as UmbDocumentVariantState;
variant.updateDate = new Date().toISOString();
}
});

this.#documentDb.detail.update(id, document);
}

publishWithDescendants(id: string, data: PublishDocumentWithDescendantsRequestModel) {
const document: UmbMockDocumentModel = this.#documentDb.detail.read(id);
const documents = this.getDescendants(id, []);
Expand All @@ -64,10 +94,7 @@
for (const culture of data.cultures) {
for (const d of documents) {
const variant = document.variants.find((x) => x.culture === culture);
if (
variant &&
(data.includeUnpublishedDescendants || variant.state !== 'Published')
) {
if (variant && (data.includeUnpublishedDescendants || variant.state !== 'Published')) {
variant.state = 'Published' as UmbDocumentVariantState;
variant.updateDate = new Date().toISOString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import { umbMockManager } from '../../mock-manager.js';
import { umbDocumentMockDb } from '../../db/document.db.js';
import { UMB_SLUG } from './slug.js';
import type {
CreateAndPublishDocumentRequestModel,
CreateDocumentRequestModel,
DefaultReferenceResponseModel,
GetDocumentByIdAvailableSegmentOptionsResponse,
GetDocumentByIdReferencedDescendantsResponse,
PagedIReferenceResponseModel,
UpdateAndPublishDocumentRequestModel,
UpdateDocumentRequestModel,
} from '@umbraco-cms/backoffice/external/backend-api';
import { umbracoPath } from '@umbraco-cms/backoffice/utils';
Expand Down Expand Up @@ -38,6 +40,21 @@ export const detailHandlers = [
});
}),

http.post(umbracoPath(`${UMB_SLUG}/create-and-publish`), async ({ request }) => {
const requestBody = (await request.json()) as CreateAndPublishDocumentRequestModel;
if (!requestBody) return new HttpResponse(null, { status: 400, statusText: 'no body found' });

const id = umbDocumentMockDb.publishing.createAndPublish(requestBody);

return HttpResponse.json(null, {
status: 201,
headers: {
Location: request.url + '/' + id,
'Umb-Generated-Resource': id,
},
});
}),

http.get(umbracoPath(`${UMB_SLUG}/configuration`), () => {
return HttpResponse.json(umbDocumentMockDb.getConfiguration());
}),
Expand Down Expand Up @@ -155,6 +172,19 @@ export const detailHandlers = [
}
}),

http.put(umbracoPath(`${UMB_SLUG}/:id/update-and-publish`), async ({ request, params }) => {
const id = params.id as string;
if (!id) return new HttpResponse(null, { status: 400 });
if (id === 'forbidden') {
// Simulate a forbidden response
return new HttpResponse(null, { status: 403 });
}
const requestBody = (await request.json()) as UpdateAndPublishDocumentRequestModel;
if (!requestBody) return new HttpResponse(null, { status: 400, statusText: 'no body found' });
umbDocumentMockDb.publishing.updateAndPublish(id, requestBody);
return new HttpResponse(null, { status: 200 });
}),

http.put(umbracoPath(`${UMB_SLUG}/:id`), async ({ request, params }) => {
const id = params.id as string;
if (!id) return new HttpResponse(null, { status: 400 });
Expand Down

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,38 @@ import type { UmbVariantId } from '@umbraco-cms/backoffice/variant';
export class UmbDocumentPublishingRepository extends UmbRepositoryBase {
#publishingDataSource = new UmbDocumentPublishingServerDataSource(this);

/**
* Creates and publishes a new Document in a single operation
* @param {UmbDocumentDetailModel} model - The Document to create
* @param {Array<UmbVariantId>} variantIds - The variants to publish after creating
* @param {string | null} parentUnique - The unique of the parent to create under
* @returns {*}
* @memberof UmbDocumentPublishingRepository
*/
async createAndPublish(
model: UmbDocumentDetailModel,
variantIds: Array<UmbVariantId>,
parentUnique: string | null = null,
) {
if (!model) throw new Error('Document is missing');
if (!model.unique) throw new Error('Document unique is missing');

return this.#publishingDataSource.createAndPublish(model, variantIds, parentUnique);
}

/**
* Updates and publishes an existing Document in a single operation
* @param {UmbDocumentDetailModel} model - The Document to update
* @param {Array<UmbVariantId>} variantIds - The variants to publish after updating
* @returns {*}
* @memberof UmbDocumentPublishingRepository
*/
async updateAndPublish(model: UmbDocumentDetailModel, variantIds: Array<UmbVariantId>) {
if (!model.unique) throw new Error('Unique is missing');

return this.#publishingDataSource.updateAndPublish(model, variantIds);
}

/**
* Publish one or more variants of a Document
* @param {string} id
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { expect } from '@open-wc/testing';
import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
import { useMockSet } from '@umbraco-cms/internal/mock-manager';
import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api';
import { customElement } from '@umbraco-cms/backoffice/external/lit';
import { UmbId } from '@umbraco-cms/backoffice/id';
import { UmbDocumentServerDataSource } from '../../repository/detail/document-detail.server.data-source.js';
import { UmbDocumentPublishingServerDataSource } from './document-publishing.server.data-source.js';

const VARIANT_DOCUMENT_ID = 'variant-documents-variant-document-id';
const INVARIANT_DOCUMENT_ID = 'variant-documents-invariant-document-id';

@customElement('umb-test-document-publishing-data-source-host')
class UmbTestHostElement extends UmbControllerHostElementMixin(HTMLElement) {}

describe('UmbDocumentPublishingServerDataSource (create/update-and-publish)', () => {
let hostElement: UmbTestHostElement;
// The detail data source is used only to read the document back and assert the published outcome,
// since the and-publish endpoints return no document body.
let detailDataSource: UmbDocumentServerDataSource;
let publishingDataSource: UmbDocumentPublishingServerDataSource;

beforeEach(async () => {
await useMockSet('documents');
hostElement = new UmbTestHostElement();
document.body.appendChild(hostElement);
detailDataSource = new UmbDocumentServerDataSource(hostElement);
publishingDataSource = new UmbDocumentPublishingServerDataSource(hostElement);
});

afterEach(() => {
document.body.innerHTML = '';
});

describe('createAndPublish', () => {
it('creates a new document and publishes only the requested culture', async () => {
// Use an existing document as a valid model template, with a fresh unique so it is created anew.
const { data: template } = await detailDataSource.read(VARIANT_DOCUMENT_ID);
expect(template, 'precondition: template document loads').to.exist;
const newId = UmbId.new();
const newModel = { ...template!, unique: newId };

const da = UmbVariantId.Create({ culture: 'da', segment: null });
const { error } = await publishingDataSource.createAndPublish(newModel, [da], null);
expect(error).to.be.undefined;

const { data: created } = await detailDataSource.read(newId);
const daVariant = created!.variants.find((v) => v.culture === 'da');
expect(daVariant?.state, 'the requested culture (da) is Published').to.equal('Published');
});
});

describe('updateAndPublish', () => {
it('publishes only the requested culture', async () => {
const { data: model } = await detailDataSource.read(VARIANT_DOCUMENT_ID);
expect(model, 'precondition: document loads').to.exist;

// da starts as Draft; publishing only da should leave en-US untouched.
const daVariantId = UmbVariantId.Create({ culture: 'da', segment: null });
const { error } = await publishingDataSource.updateAndPublish(model!, [daVariantId]);
expect(error).to.be.undefined;

const { data: updated } = await detailDataSource.read(VARIANT_DOCUMENT_ID);
const da = updated!.variants.find((v) => v.culture === 'da');
const enUs = updated!.variants.find((v) => v.culture === 'en-US');
expect(da?.state, 'da becomes Published').to.equal('Published');
expect(enUs?.state, 'en-US is unaffected (was already Published)').to.equal('Published');
});

it('does not publish a culture that was not requested', async () => {
// A fresh mock set where da is Draft.
const { data: model } = await detailDataSource.read(VARIANT_DOCUMENT_ID);
const enUs = UmbVariantId.Create({ culture: 'en-US', segment: null });

const { error } = await publishingDataSource.updateAndPublish(model!, [enUs]);
expect(error).to.be.undefined;

const { data: updated } = await detailDataSource.read(VARIANT_DOCUMENT_ID);
const da = updated!.variants.find((v) => v.culture === 'da');
expect(da?.state, 'da stays Draft when only en-US is published').to.equal('Draft');
});
});

describe('invariant update-and-publish', () => {
it('publishes the invariant variant using an empty culturesToPublish array', async () => {
const { data: model } = await detailDataSource.read(INVARIANT_DOCUMENT_ID);
expect(model, 'precondition: invariant document loads').to.exist;

const invariant = UmbVariantId.CreateInvariant();
const { error } = await publishingDataSource.updateAndPublish(model!, [invariant]);
expect(error).to.be.undefined;

const { data: updated } = await detailDataSource.read(INVARIANT_DOCUMENT_ID);
const variant = updated!.variants.find((v) => v.culture === null);
expect(variant?.state, 'the invariant variant is Published').to.equal('Published');
});
});
});
Loading