diff --git a/src/cli.ts b/src/cli.ts index e26dc9ba..33bbcce4 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -143,11 +143,12 @@ program .option("-p, --path ", "Path to save the component files") .option("-f, --file-name ", "custom name to be used in file(s) name instead of space id") .option("-ppn, --prefix-presets-names", "Prefixes the names of presets with the name of the components") + .option("--rd, --resolve-datasources", "Fill options for single/multiple option field with the linked datasource") .description("Download your space's components schema as json") .action(async (options) => { console.log(`${chalk.blue("-")} Executing pull-components task`); const space = program.space; - const { separateFiles, path, prefixPresetsNames } = options; + const { separateFiles, path, prefixPresetsNames, resolveDatasources } = options; if (!space) { console.log(chalk.red("X") + " Please provide the space as argument --space YOUR_SPACE_ID."); process.exit(0); @@ -161,7 +162,7 @@ program } api.setSpaceId(space); - await tasks.pullComponents(api, { fileName, separateFiles, path, prefixPresetsNames }); + await tasks.pullComponents(api, { fileName, separateFiles, path, prefixPresetsNames, resolveDatasources }); } catch (e) { errorHandler(e, COMMANDS.PULL_COMPONENTS); } @@ -299,11 +300,11 @@ program ) .requiredOption("--source ", "Source space id") .requiredOption("--target ", "Target space id") - .option('--starts-with ', 'Sync only stories that starts with the given string') - .option('--filter', 'Enable filter options to sync only stories that match the given filter. Required options: --keys; --operations; --values') - .option('--keys ', 'Field names in your story object which should be used for filtering. Multiple keys should separated by comma.') - .option('--operations ', 'Operations to be used for filtering. Can be: is, in, not_in, like, not_like, any_in_array, all_in_array, gt_date, lt_date, gt_int, lt_int, gt_float, lt_float. Multiple operations should be separated by comma.') - .option('--values ', 'Values to be used for filtering. Any string or number. If you want to use multiple values, separate them with a comma. Multiple values should be separated by comma.') + .option("--starts-with ", "Sync only stories that starts with the given string") + .option("--filter", "Enable filter options to sync only stories that match the given filter. Required options: --keys; --operations; --values") + .option("--keys ", "Field names in your story object which should be used for filtering. Multiple keys should separated by comma.") + .option("--operations ", "Operations to be used for filtering. Can be: is, in, not_in, like, not_like, any_in_array, all_in_array, gt_date, lt_date, gt_int, lt_int, gt_float, lt_float. Multiple operations should be separated by comma.") + .option("--values ", "Values to be used for filtering. Any string or number. If you want to use multiple values, separate them with a comma. Multiple values should be separated by comma.") .option("--components-groups ", "Synchronize components based on their group UUIDs separated by commas") .option("--components-full-sync", "Synchronize components by overriding any property from source to target") .action(async (options) => { @@ -317,7 +318,7 @@ program const { type, target, - source, + source, startsWith, filter, keys, @@ -329,7 +330,7 @@ program const _componentsGroups = componentsGroups ? componentsGroups.split(",") : null; const _componentsFullSync = !!componentsFullSync; - const filterQuery = filter ? buildFilterQuery(keys, operations, values) : undefined + const filterQuery = filter ? buildFilterQuery(keys, operations, values) : undefined; const token = creds.get().token || null; const _types = type.split(",") || []; diff --git a/src/tasks/pull-components.js b/src/tasks/pull-components.js index f0e85ac3..e3b402e3 100644 --- a/src/tasks/pull-components.js +++ b/src/tasks/pull-components.js @@ -17,22 +17,51 @@ const getNameFromComponentGroups = (groups, uuid) => { return '' } +const resolveDatasourceOptions = async (api, components) => { + const datasources = await api.getDatasources() + + for (const datasource of datasources) { + const datasourceEntries = await api.getDatasourceEntries(datasource.id) + datasource.entries = datasourceEntries + } + + return components.map(component => { + const schema = component.schema + + for (const field in schema) { + if (schema[field].source === 'internal' && schema[field].datasource_slug) { + const datasource = datasources.find(ds => ds.slug === schema[field].datasource_slug) + + if (datasource) { + schema[field].options = datasource.entries.map(entry => ({ value: entry.value, name: entry.name })) + } + } + } + + return component + }) +} + /** * @method pullComponents * @param {Object} api - * @param {Object} options { fileName: string, separateFiles: Boolean, path: String } + * @param {Object} options { fileName: string, separateFiles: Boolean, path: String, resolveDatasources: Boolean } * @return {Promise} */ const pullComponents = async (api, options) => { - const { fileName, separateFiles, path, prefixPresetsNames } = options + const { fileName, separateFiles, path, prefixPresetsNames, resolveDatasources } = options try { const componentGroups = await api.getComponentGroups() - const components = await api.getComponents() + let components = await api.getComponents() const presets = await api.getPresets() + if (resolveDatasources) { + components = await resolveDatasourceOptions(api, components) + } + components.forEach(component => { const groupUuid = component.component_group_uuid if (groupUuid) { diff --git a/src/utils/api.js b/src/utils/api.js index b382b566..cae2ec48 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -268,6 +268,15 @@ export default { .catch(err => Promise.reject(err)) }, + getDatasourceEntries (id) { + const client = this.getClient() + + return client + .get(this.getPath(`datasource_entries?datasource_id=${id}`)) + .then(data => data.data.datasource_entries || []) + .catch(err => Promise.reject(err)) + }, + deleteDatasource (id) { const client = this.getClient() diff --git a/tests/constants.js b/tests/constants.js index b3773850..349d23fc 100644 --- a/tests/constants.js +++ b/tests/constants.js @@ -145,6 +145,55 @@ export const FAKE_COMPONENTS = () => [ preset_id: null, real_name: 'hero', component_group_uuid: null + }, + { + name: 'meta', + display_name: null, + created_at: '2019-11-06T17:07:04.196Z', + updated_at: '2019-11-06T18:12:29.136Z', + id: 4, + schema: { + robot: { + type: "option", + source: "internal", + datasource_slug: "robots", + } + }, + image: null, + preview_field: null, + is_root: false, + preview_tmpl: null, + is_nestable: true, + all_presets: [], + preset_id: null, + real_name: 'meta', + component_group_uuid: null + }, +] + +export const FAKE_DATASOURCES = () => [ + { + id: 1, + name: "Robots", + slug: "robots", + dimensions: [], + created_at: "2019-10-15T17:00:32.212Z", + updated_at: "2019-11-15T17:00:32.212Z", + }, +] + +export const FAKE_DATASOURCE_ENTRIES = () => [ + { + id: 1, + name: "No index", + value: "noindex", + dimension_value: "" + }, + { + id: 2, + name: "No follow", + value: "nofollow", + dimension_value: "" } ] diff --git a/tests/units/pull-components.spec.js b/tests/units/pull-components.spec.js index 4320ad0f..75cccc26 100644 --- a/tests/units/pull-components.spec.js +++ b/tests/units/pull-components.spec.js @@ -1,6 +1,6 @@ import fs from 'fs' import pullComponents from '../../src/tasks/pull-components' -import { FAKE_PRESET, FAKE_COMPONENTS } from '../constants' +import { FAKE_PRESET, FAKE_COMPONENTS, FAKE_DATASOURCES, FAKE_DATASOURCE_ENTRIES } from '../constants' import { jest } from '@jest/globals' jest.spyOn(fs, 'writeFileSync').mockImplementation(jest.fn((key, data, _) => { @@ -126,6 +126,54 @@ describe('testing pullComponents', () => { } }) + it('pull components should be call fs.writeFile correctly and return filled options from datasource entries', async () => { + const SPACE = 12345 + + const api = { + getComponents () { + return Promise.resolve([FAKE_COMPONENTS()[5]]) + }, + getComponentGroups () { + return Promise.resolve([]) + }, + getDatasources () { + return Promise.resolve(FAKE_DATASOURCES()) + }, + getDatasourceEntries () { + return Promise.resolve(FAKE_DATASOURCE_ENTRIES()) + }, + getPresets () { + return Promise.resolve([]) + } + } + + const options = { + fileName: SPACE, + resolveDatasources: true + } + + const expectFileName = `components.${SPACE}.json` + + await pullComponents(api, options) + const [path, data] = fs.writeFile.mock.calls[0] + + expect(fs.writeFile.mock.calls.length).toBe(1) + expect(path).toBe(`./${expectFileName}`) + expect(JSON.parse(data)).toEqual({ + components: [{ + ...FAKE_COMPONENTS()[5], + schema: { + robot: { + type: "option", + source: "internal", + datasource_slug: "robots", + options: FAKE_DATASOURCE_ENTRIES().map(entry => ({ value: entry.value, name: entry.name })) + } + } + }] + }) + }) + it('api.getComponents() when a error ocurred, catch the body response', async () => { const _api = { getComponents (_, fn) {