Skip to content

Commit 32508ea

Browse files
Added publish and unpublish methods in entry variants
1 parent 1a3d370 commit 32508ea

6 files changed

Lines changed: 272 additions & 7 deletions

File tree

lib/entity.js

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import ContentstackCollection from './contentstackCollection'
1313
* await publishFn.call(entryInstance, { publishDetails: {...}, locale: 'en-us' })
1414
*/
1515
export const publish = (http, type) => {
16-
return async function ({ publishDetails, locale = null, version = null, scheduledAt = null }) {
16+
return async function ({ publishDetails, locale = null, version = null, scheduledAt = null, headers: extraHeaders = {}, params = {} }) {
1717
const url = this.urlPath + '/publish'
1818
const headers = {
1919
headers: {
@@ -22,7 +22,10 @@ export const publish = (http, type) => {
2222
} || {}
2323
const httpBody = {}
2424
httpBody[type] = cloneDeep(publishDetails)
25-
return publishUnpublish(http, url, httpBody, headers, locale, version, scheduledAt)
25+
return publishUnpublish(http, url, httpBody, headers, locale, version, scheduledAt, {
26+
headers: extraHeaders,
27+
params
28+
})
2629
}
2730
}
2831

@@ -36,7 +39,7 @@ export const publish = (http, type) => {
3639
* await unpublishFn.call(entryInstance, { publishDetails: {...}, locale: 'en-us' })
3740
*/
3841
export const unpublish = (http, type) => {
39-
return async function ({ publishDetails, locale = null, version = null, scheduledAt = null }) {
42+
return async function ({ publishDetails, locale = null, version = null, scheduledAt = null, headers: extraHeaders = {}, params = {} }) {
4043
const url = this.urlPath + '/unpublish'
4144
const headers = {
4245
headers: {
@@ -45,7 +48,10 @@ export const unpublish = (http, type) => {
4548
} || {}
4649
const httpBody = {}
4750
httpBody[type] = cloneDeep(publishDetails)
48-
return publishUnpublish(http, url, httpBody, headers, locale, version, scheduledAt)
51+
return publishUnpublish(http, url, httpBody, headers, locale, version, scheduledAt, {
52+
headers: extraHeaders,
53+
params
54+
})
4955
}
5056
}
5157

@@ -58,11 +64,12 @@ export const unpublish = (http, type) => {
5864
* @param {string|null} locale - Locale code.
5965
* @param {number|null} version - Version number.
6066
* @param {string|null} scheduledAt - Scheduled date/time in ISO format.
67+
* @param {Object} [configExtras={}] - Optional `{ headers, params }` merged into the axios request (stack headers stay in `headers.headers`).
6168
* @returns {Promise<Object>} Promise that resolves to response data.
6269
* @async
6370
* @private
6471
*/
65-
export const publishUnpublish = async (http, url, httpBody, headers, locale = null, version = null, scheduledAt = null) => {
72+
export const publishUnpublish = async (http, url, httpBody, headers, locale = null, version = null, scheduledAt = null, configExtras = {}) => {
6673
if (locale !== null) {
6774
httpBody.locale = locale
6875
}
@@ -72,8 +79,18 @@ export const publishUnpublish = async (http, url, httpBody, headers, locale = nu
7279
if (scheduledAt !== null) {
7380
httpBody.scheduled_at = scheduledAt
7481
}
82+
const requestConfig = {
83+
headers: {
84+
...cloneDeep(headers?.headers || {}),
85+
...cloneDeep(configExtras?.headers || {})
86+
},
87+
params: {
88+
...cloneDeep(headers?.params || {}),
89+
...cloneDeep(configExtras?.params || {})
90+
}
91+
}
7592
try {
76-
const response = await http.post(url, httpBody, headers)
93+
const response = await http.post(url, httpBody, requestConfig)
7794
if (response.data) {
7895
const data = response.data || {}
7996
if (http?.httpClientParams?.headers?.api_version) {

lib/stack/contentType/entry/variants/index.js

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import cloneDeep from 'lodash/cloneDeep'
22
import {
33
deleteEntity,
44
fetch,
5-
query
5+
query,
6+
publishUnpublish
67
}
78
from '../../../../entity'
89
import error from '../../../../core/contentstackError'
@@ -26,6 +27,7 @@ export function Variants (http, data) {
2627
}
2728
if (variantPathSegment) {
2829
this.urlPath += `/${variantPathSegment}`
30+
const entryBaseUrlPath = `/content_types/${this.content_type_uid}/entries/${this.entry_uid}`
2931
/**
3032
* @description The Update a variant call updates an existing variant for the selected content type.
3133
* @memberof Variants
@@ -131,6 +133,88 @@ export function Variants (http, data) {
131133
return error(err)
132134
}
133135
}
136+
137+
/**
138+
* @description Publishes via the entry publish endpoint (POST .../entries/{entry_uid}/publish). Pass `publishDetails` as the object nested under `entry` (environments, locales, variants, variant_rules, etc.). Optional `headers` and `params` are merged into the HTTP request.
139+
* @memberof Variants
140+
* @func publish
141+
* @param {Object} options
142+
* @param {Object} options.publishDetails - Payload for the `entry` property (e.g. environments, locales, variants, variant_rules).
143+
* @param {String|null} [options.locale] - Top-level `locale` on the request body.
144+
* @param {Number|null} [options.version] - Top-level `version` on the request body.
145+
* @param {String|null} [options.scheduledAt] - Top-level `scheduled_at` (ISO) on the request body.
146+
* @param {Object} [options.headers={}] - Extra request headers merged with stack headers.
147+
* @param {Object} [options.params={}] - Query string parameters for the request.
148+
* @returns {Promise<Object>} Response data (e.g. notice, job_id).
149+
* @example
150+
* import * as contentstack from '@contentstack/management'
151+
* const client = contentstack.client()
152+
* await client.stack({ api_key: 'api_key' }).contentType('ct').entry('entry_uid').variants('variant_uid').publish({
153+
* publishDetails: {
154+
* environments: ['production'],
155+
* locales: ['en-us'],
156+
* variants: [{ uid: 'variant_uid', version: 1 }],
157+
* variant_rules: { publish_latest_base: false, publish_latest_base_conditionally: true }
158+
* },
159+
* locale: 'en-us'
160+
* })
161+
*/
162+
this.publish = async ({
163+
publishDetails,
164+
locale = null,
165+
version = null,
166+
scheduledAt = null,
167+
headers: extraHeaders = {},
168+
params = {}
169+
}) => {
170+
const url = `${entryBaseUrlPath}/publish`
171+
const httpBody = {}
172+
httpBody.entry = cloneDeep(publishDetails)
173+
const baseHeaders = {
174+
headers: {
175+
...cloneDeep(this.stackHeaders)
176+
}
177+
}
178+
return publishUnpublish(http, url, httpBody, baseHeaders, locale, version, scheduledAt, {
179+
headers: extraHeaders,
180+
params
181+
})
182+
}
183+
184+
/**
185+
* @description Unpublishes via the entry unpublish endpoint (POST .../entries/{entry_uid}/unpublish). Pass `publishDetails` as the object nested under `entry`. Optional `headers` and `params` are merged into the HTTP request.
186+
* @memberof Variants
187+
* @func unpublish
188+
* @param {Object} options
189+
* @param {Object} options.publishDetails - Payload for the `entry` property (e.g. environments, locales, variants).
190+
* @param {String|null} [options.locale] - Top-level `locale` on the request body.
191+
* @param {Number|null} [options.version] - Top-level `version` on the request body.
192+
* @param {String|null} [options.scheduledAt] - Top-level `scheduled_at` (ISO) on the request body.
193+
* @param {Object} [options.headers={}] - Extra request headers merged with stack headers.
194+
* @param {Object} [options.params={}] - Query string parameters for the request.
195+
* @returns {Promise<Object>} Response data (e.g. notice, job_id).
196+
*/
197+
this.unpublish = async ({
198+
publishDetails,
199+
locale = null,
200+
version = null,
201+
scheduledAt = null,
202+
headers: extraHeaders = {},
203+
params = {}
204+
}) => {
205+
const url = `${entryBaseUrlPath}/unpublish`
206+
const httpBody = {}
207+
httpBody.entry = cloneDeep(publishDetails)
208+
const baseHeaders = {
209+
headers: {
210+
...cloneDeep(this.stackHeaders)
211+
}
212+
}
213+
return publishUnpublish(http, url, httpBody, baseHeaders, locale, version, scheduledAt, {
214+
headers: extraHeaders,
215+
params
216+
})
217+
}
134218
} else {
135219
/**
136220
* @description The Query on Variants will allow you to fetch details of all or specific Variants.

test/sanity-check/api/entryVariants-test.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,46 @@ describe('Entry Variants API Tests', () => {
452452
}
453453
})
454454

455+
it('should publish entry variant via variants(uid).publish()', async function () {
456+
this.timeout(15000)
457+
458+
if (!contentTypeUid || !entryUid || !variantUid) {
459+
this.skip()
460+
}
461+
462+
const publishDetails = {
463+
environments: [environmentName],
464+
locales: ['en-us'],
465+
variants: [{
466+
uid: variantUid,
467+
version: 1
468+
}],
469+
variant_rules: {
470+
publish_latest_base: false,
471+
publish_latest_base_conditionally: true
472+
}
473+
}
474+
475+
try {
476+
const response = await stack
477+
.contentType(contentTypeUid)
478+
.entry(entryUid)
479+
.variants(variantUid)
480+
.publish({
481+
publishDetails,
482+
locale: 'en-us'
483+
})
484+
485+
expect(response.notice).to.not.equal(undefined)
486+
} catch (error) {
487+
if (error.status === 403 || error.status === 422) {
488+
this.skip()
489+
} else {
490+
console.log('variants().publish warning:', error.message)
491+
}
492+
}
493+
})
494+
455495
it('should publish entry variant with api_version', async function () {
456496
this.timeout(15000)
457497

@@ -521,6 +561,42 @@ describe('Entry Variants API Tests', () => {
521561
}
522562
}
523563
})
564+
565+
it('should unpublish entry variant via variants(uid).unpublish()', async function () {
566+
this.timeout(15000)
567+
568+
if (!contentTypeUid || !entryUid || !variantUid) {
569+
this.skip()
570+
}
571+
572+
const unpublishDetails = {
573+
environments: [environmentName],
574+
locales: ['en-us'],
575+
variants: [{
576+
uid: variantUid,
577+
version: 1
578+
}]
579+
}
580+
581+
try {
582+
const response = await stack
583+
.contentType(contentTypeUid)
584+
.entry(entryUid)
585+
.variants(variantUid)
586+
.unpublish({
587+
publishDetails: unpublishDetails,
588+
locale: 'en-us'
589+
})
590+
591+
expect(response.notice).to.not.equal(undefined)
592+
} catch (error) {
593+
if (error.status === 403 || error.status === 422) {
594+
this.skip()
595+
} else {
596+
console.log('variants().unpublish warning:', error.message)
597+
}
598+
}
599+
})
524600
})
525601

526602
describe('Entry Variant Deletion', () => {

test/unit/variants-entry-test.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,76 @@ describe('Contentstack Variants entry test', () => {
239239
.catch(done)
240240
})
241241

242+
it('Variants entry publish posts to entry publish URL with optional headers and params', (done) => {
243+
const mock = new MockAdapter(Axios)
244+
mock.onPost('/content_types/content_type_uid/entries/entry_uid/publish').reply((config) => {
245+
const body = typeof config.data === 'string' ? JSON.parse(config.data) : config.data
246+
expect(body.entry).to.be.an('object')
247+
expect(body.entry.variants).to.be.an('array')
248+
expect(body.entry.variants[0].uid).to.equal('variant_uid')
249+
expect(body.locale).to.equal('en-us')
250+
expect(config.headers['x-custom']).to.equal('1')
251+
expect(config.params.t).to.equal('1')
252+
return [200, { notice: 'Entry sent for publishing.', job_id: 'jid' }]
253+
})
254+
makeEntryVariants({
255+
content_type_uid: 'content_type_uid',
256+
entry_uid: 'entry_uid',
257+
variants_uid: 'variant_uid',
258+
stackHeaders: { api_key: 'k' }
259+
})
260+
.publish({
261+
publishDetails: {
262+
environments: ['development'],
263+
locales: ['en-us'],
264+
variants: [{ uid: 'variant_uid', version: 1 }],
265+
variant_rules: { publish_latest_base_conditionally: true }
266+
},
267+
locale: 'en-us',
268+
headers: { 'x-custom': '1' },
269+
params: { t: '1' }
270+
})
271+
.then((res) => {
272+
expect(res.notice).to.include('publish')
273+
done()
274+
})
275+
.catch(done)
276+
})
277+
278+
it('Variants entry unpublish posts to entry unpublish URL with optional headers and params', (done) => {
279+
const mock = new MockAdapter(Axios)
280+
mock.onPost('/content_types/content_type_uid/entries/entry_uid/unpublish').reply((config) => {
281+
const body = typeof config.data === 'string' ? JSON.parse(config.data) : config.data
282+
expect(body.entry).to.be.an('object')
283+
expect(body.entry.variants).to.be.an('array')
284+
expect(body.locale).to.equal('en-us')
285+
expect(config.headers['x-unpub']).to.equal('y')
286+
expect(config.params.q).to.equal('2')
287+
return [200, { notice: 'Entry sent for unpublishing.' }]
288+
})
289+
makeEntryVariants({
290+
content_type_uid: 'content_type_uid',
291+
entry_uid: 'entry_uid',
292+
variants_uid: 'variant_uid',
293+
stackHeaders: { api_key: 'k' }
294+
})
295+
.unpublish({
296+
publishDetails: {
297+
environments: ['development'],
298+
locales: ['en-us'],
299+
variants: [{ uid: 'variant_uid', version: 1 }]
300+
},
301+
locale: 'en-us',
302+
headers: { 'x-unpub': 'y' },
303+
params: { q: '2' }
304+
})
305+
.then((res) => {
306+
expect(res.notice).to.include('unpublish')
307+
done()
308+
})
309+
.catch(done)
310+
})
311+
242312
it('Entry variants fetch sends optional branch header', (done) => {
243313
const mock = new MockAdapter(Axios)
244314
mock

types/stack/contentType/variants.d.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,21 @@ import { Response } from "../../contentstackCollection";
22
import { AnyProperty, SystemFields } from "../../utility/fields";
33
import { Queryable, SystemFunction } from "../../utility/operations";
44

5+
/** Options for {@link Variant.publish} and {@link Variant.unpublish} (entry publish/unpublish APIs with variant payload). */
6+
export interface VariantPublishUnpublishOptions {
7+
publishDetails: AnyProperty
8+
locale?: string | null
9+
version?: number | null
10+
scheduledAt?: string | null
11+
/** Merged with stack headers on the request */
12+
headers?: Record<string, string>
13+
/** Query string parameters */
14+
params?: Record<string, unknown>
15+
}
16+
517
export interface Variant extends SystemFields, SystemFunction<Variant> {
18+
publish(options: VariantPublishUnpublishOptions): Promise<Response>
19+
unpublish(options: VariantPublishUnpublishOptions): Promise<Response>
620
}
721
export interface Variants extends Queryable<Variants, {Variants: VariantData}> {
822
}

types/utility/publish.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ export interface PublishConfig {
1111
locale?: string
1212
version?: number
1313
scheduledAt?: string
14+
/** Extra HTTP headers merged with stack headers (publish / unpublish). */
15+
headers?: Record<string, string>
16+
/** Query string parameters (publish / unpublish). */
17+
params?: Record<string, unknown>
1418
}
1519

1620
export interface Publishable {

0 commit comments

Comments
 (0)