Skip to content
This repository has been archived by the owner on Jan 15, 2025. It is now read-only.

Commit

Permalink
Adding luis:version:export cmd (#356)
Browse files Browse the repository at this point in the history
* Adding luis:version:export cmd

* Remove dependency

* Update example

* Only store certain values in config, per specs

* Refactor / keep logging and flag parsing in cmd only

* Refactor write file error handling
  • Loading branch information
JSpru authored and ninggao committed Nov 18, 2019
1 parent 9cb3c7d commit 222e30c
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 3 deletions.
1 change: 1 addition & 0 deletions packages/luis/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@types/chai": "^4.2.4",
"@types/mocha": "^5.2.7",
"@types/node": "^10.17.4",
"@types/rimraf": "^2.0.3",
"chai": "^4.2.0",
"globby": "^10.0.1",
"mocha": "^5.2.0",
Expand Down
60 changes: 60 additions & 0 deletions packages/luis/src/commands/luis/version/export.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*!
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/

import {CLIError, Command, flags} from '@microsoft/bf-cli-command'

const utils = require('../../../utils/index')

export default class LuisVersionExport extends Command {
static description = 'Exports a LUIS application to JSON format'

static examples = [`
$ bf luis:version:export --appId {APP_ID} --versionId {VERSION_ID} --out {FILENAME.json or PATH/FILENAME.json} --endpoint {ENDPOINT} --subscriptionKey {SUBSCRIPTION_KEY}
`]

static flags = {
help: flags.help({char: 'h'}),
appId: flags.string({description: 'LUIS application Id'}),
versionId: flags.string({description: 'LUIS application version Id'}),
out: flags.string({char: 'o', description: 'Path to the directory where the exported file will be placed.'}),
force: flags.boolean({char: 'f', description: 'If --out flag is provided with the path to an existing file, overwrites that file', default: false}),
endpoint: flags.string({description: 'LUIS endpoint hostname'}),
subscriptionKey: flags.string({description: 'LUIS cognitive services subscription key (aka Ocp-Apim-Subscription-Key)'}),
}

async run() {
const {flags} = this.parse(LuisVersionExport)
const flagLabels = Object.keys(LuisVersionExport.flags)
const configDir = this.config.configDir

let {
appId,
versionId,
endpoint,
force,
out,
subscriptionKey,
} = await utils.processInputs(flags, flagLabels, configDir)

const requiredProps = {appId, versionId, endpoint, subscriptionKey}
utils.validateRequiredProps(requiredProps)

const client = utils.getLUISClient(subscriptionKey, endpoint)

try {
const appJSON = await client.versions.exportMethod(appId, versionId)
if (!appJSON) throw new CLIError('Failed to export file')
if (out) {
const writtenFilePath: string = await utils.writeToFile(out, appJSON, force)
this.log(`File successfully written: ${writtenFilePath}`)
} else {
await utils.writeToConsole(appJSON)
this.log('App successfully exported\n')
}
} catch (error) {
throw new CLIError(error)
}
}
}
34 changes: 31 additions & 3 deletions packages/luis/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License.
*/

import {CLIError} from '@microsoft/bf-cli-command'
import {CLIError, utils} from '@microsoft/bf-cli-command'
const path = require('path')
const fs = require('fs-extra')
const msRest = require('ms-rest')
Expand Down Expand Up @@ -36,17 +36,27 @@ const getLUISClient = (subscriptionKey: string, endpoint: string) => {
return luisClient
}

const filterByAllowedConfigValues = (configObj: any, prefix: string) => {
const allowedConfigValues = [`${prefix}appId`, `${prefix}region`, `${prefix}subscriptionKey`, `${prefix}versionId`]
const filtered = Object.keys(configObj)
.filter(key => allowedConfigValues.includes(key))
.reduce((filteredConfigObj: any, key) => {
filteredConfigObj[key] = configObj[key]
return filteredConfigObj
}, {})
return filtered
}

const processInputs = async (flags: any, flagLabels: string[], configDir: string) => {
const configPrefix = 'luis__'
let config = await getUserConfig(configDir)
let config = filterByAllowedConfigValues(await getUserConfig(configDir), configPrefix)
config = config ? filterConfig(config, configPrefix) : config
const input: any = {}
flagLabels
.filter(flag => flag !== 'help')
.map((flag: string) => {
input[flag] = flags[flag] || (config ? config[configPrefix + flag] : null)
})

return input
}

Expand All @@ -58,7 +68,25 @@ const validateRequiredProps = (configObj: any) => {
})
}

const writeToConsole = (outputContents: string) => {
const output = JSON.stringify(outputContents, null, 2)
process.stdout.write(output, 'utf-8')
}

const writeToFile = async (outputLocation: string, content: any, force: boolean) => {
const validatedPath = utils.validatePath(outputLocation, '', force)
try {
await fs.ensureFile(outputLocation)
await fs.writeJson(validatedPath, content, {spaces: 2})
} catch (error) {
throw new CLIError(error)
}
return validatedPath
}

module.exports.getLUISClient = getLUISClient
module.exports.getUserConfig = getUserConfig
module.exports.processInputs = processInputs
module.exports.validateRequiredProps = validateRequiredProps
module.exports.writeToConsole = writeToConsole
module.exports.writeToFile = writeToFile
109 changes: 109 additions & 0 deletions packages/luis/test/commands/luis/version/export.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import {expect, test} from '@oclif/test'
const sinon = require('sinon')
const uuidv1 = require('uuid/v1')
const utils = require('../../../../src/utils/index')
const fs = require('fs-extra')
import * as rimraf from 'rimraf'

describe('luis:version:export', () => {

before(() => {
fs.mkdirSync('./testout');
});

after(() => {
rimraf('./testout', (err) => {
if (err) console.log(err);
})
});

beforeEach(() => {
sinon.stub(utils, 'processInputs').returnsArg(0)
})

afterEach(() => {
sinon.restore();
});

test
.stdout()
.command(['luis:version:export', '--help'])
.it('should print the help contents when --help is passed as an argument', ctx => {
expect(ctx.stdout).to.contain('Exports a LUIS application to JSON format')
})

test
.stdout()
.stderr()
.command(['luis:version:export', '--versionId', '0.1', '--subscriptionKey', uuidv1(), '--endpoint', 'https://westus.api.cognitive.microsoft.com'])
.it('displays an error if any required input parameters are missing', ctx => {
expect(ctx.stderr).to.contain(`Required input property 'appId' missing.`)
})

test
.stdout()
.stderr()
.command(['luis:version:export', '--appId', uuidv1(), '--endpoint', 'https://westus.api.cognitive.microsoft.com', '--subscriptionKey', uuidv1()])
.it('displays an error if any required input parameters are missing', ctx => {
expect(ctx.stderr).to.contain(`Required input property 'versionId' missing.`)
})

test
.nock('https://westus.api.cognitive.microsoft.com', api => api
.get(uri => uri.includes('export'))
.reply(200, {name: 'testname'})
)
.stdout()
.command(['luis:version:export', '--appId', uuidv1(), '--versionId', '0.1', '--subscriptionKey', uuidv1(), '--endpoint', 'https://westus.api.cognitive.microsoft.com'])
.it('exports a luis app and displays a success message and the export contents in the console', ctx => {
expect(ctx.stdout).to.contain('App successfully exported')
})

test
.nock('https://westus.api.cognitive.microsoft.com', api => api
.get(uri => uri.includes('export'))
.reply(200, {name: 'testname'})
)
.stdout()
.command(['luis:version:export', '--appId', uuidv1(), '--out', './testout/test.json', '--versionId', '0.1', '--subscriptionKey', uuidv1(), '--endpoint', 'https://westus.api.cognitive.microsoft.com'])
.it('exports a luis app, displays a success message in the console and the export contents to the specified file', ctx => {
expect(ctx.stdout).to.contain('File successfully written:')
})

test
.nock('https://westus.api.cognitive.microsoft.com', api => api
.get(uri => uri.includes('export'))
.reply(200, {name: 'testname'})
)
.stdout()
.stderr()
.command(['luis:version:export', '--appId', uuidv1(), '--out', 'xyz', '--versionId', '0.1', '--subscriptionKey', uuidv1(), '--endpoint', 'https://westus.api.cognitive.microsoft.com'])
.it('exports a luis app and displays a success message and the export contents in the console (since the target path provided is invalid)', ctx => {
expect(ctx.stderr).to.contain('Target directory path doesn\'t exist:')
})

test
.nock('https://westus.api.cognitive.microsoft.com', api => api
.get(uri => uri.includes('export'))
.reply(200, {name: 'testname'})
)
.stdout()
.stderr()
.command(['luis:version:export', '--appId', uuidv1(), '--out', './testout/test.json', '--force', '--versionId', '0.1', '--subscriptionKey', uuidv1(), '--endpoint', 'https://westus.api.cognitive.microsoft.com'])
.it('exports a luis app, displays a success message in the console and the export contents to the specified file, overwriting the existing file of the same name', ctx => {
expect(ctx.stdout).to.contain('File successfully written')
})

test
.nock('https://westus.api.cognitive.microsoft.com', api => api
.get(uri => uri.includes('export'))
.reply(200, {name: 'testname'})
)
.stdout()
.command(['luis:version:export', '--appId', uuidv1(), '--out', './testout/test.json', '--versionId', '0.1', '--subscriptionKey', uuidv1(), '--endpoint', 'https://westus.api.cognitive.microsoft.com'])
.it('exports a luis app, displays a success message in the console and the export contents to the specified file, incrementing the filename', ctx => {
expect(ctx.stdout).to.contain('File successfully written')
expect(ctx.stdout).to.contain('test(1).json')
})

})

0 comments on commit 222e30c

Please sign in to comment.