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

Commit 8700bd0

Browse files
JSprumunozemilio
authored andcommitted
Adding luis:application:import cmd (#367)
* Adding luis:application:import cmd * Rewriting if else * Bring stdin reading back into the command * Remove no longer needed var dec * Refactor / show error if no input / add unit test for that
1 parent fba06a0 commit 8700bd0

File tree

5 files changed

+174
-1
lines changed

5 files changed

+174
-1
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*!
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License.
4+
*/
5+
6+
import {CLIError, Command, flags} from '@microsoft/bf-cli-command'
7+
8+
const utils = require('../../../utils/index')
9+
10+
export default class LuisApplicationImport extends Command {
11+
static description = 'Imports an application to LUIS'
12+
13+
static examples = [`
14+
$ bf luis:application:import --endpoint {ENDPOINT} --subscriptionKey {SUBSCRIPTION_KEY} --name {NAME} --in {PATH_TO_JSON}
15+
$ echo {SERIALIZED_JSON} | bf luis:application:import --endpoint {ENDPOINT} --subscriptionKey {SUBSCRIPTION_KEY} --name {NAME}
16+
`]
17+
18+
static flags = {
19+
help: flags.help({char: 'h'}),
20+
endpoint: flags.string({description: 'LUIS endpoint hostname'}),
21+
subscriptionKey: flags.string({description: 'LUIS cognitive services subscription key (aka Ocp-Apim-Subscription-Key)'}),
22+
name: flags.string({description: 'LUIS application name'}),
23+
in: flags.string({char: 'i', description: 'File path containing LUIS application contents'})
24+
}
25+
26+
async run() {
27+
const {flags} = this.parse(LuisApplicationImport)
28+
const flagLabels = Object.keys(LuisApplicationImport.flags)
29+
const configDir = this.config.configDir
30+
const stdin = await this.readStdin()
31+
32+
let {endpoint, subscriptionKey, name, inVal} = await utils.processInputs(flags, flagLabels, configDir)
33+
34+
const requiredProps = {endpoint, subscriptionKey, name}
35+
utils.validateRequiredProps(requiredProps)
36+
37+
inVal = inVal ? inVal.trim() : flags.in
38+
39+
const appJSON = stdin ? stdin : await utils.getInputFromFile(inVal)
40+
if (!appJSON) throw new CLIError('No import data found - please provide input through stdin or the --in flag')
41+
42+
const client = utils.getLUISClient(subscriptionKey, endpoint)
43+
44+
try {
45+
const newAppId = await client.apps.importMethod(JSON.parse(appJSON), undefined)
46+
this.log(`App successfully imported with id ${newAppId}.`)
47+
} catch (err) {
48+
throw new CLIError(`Failed to import app: ${err}`)
49+
}
50+
}
51+
52+
}

packages/luis/src/utils/index.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@ const filterConfig = (config: any, prefix: string) => {
1818
}, {})
1919
}
2020

21+
const getInputFromFile = async (path: string): Promise<string> => {
22+
if (path) {
23+
try {
24+
return await utils.readTextFile(path)
25+
} catch (error) {
26+
throw new CLIError(`Failed to read app JSON: ${error}`)
27+
}
28+
}
29+
return ''
30+
}
31+
2132
const getUserConfig = async (configPath: string) => {
2233
if (fs.existsSync(path.join(configPath, 'config.json'))) {
2334
return fs.readJSON(path.join(configPath, 'config.json'), {throws: false})
@@ -55,6 +66,10 @@ const processInputs = async (flags: any, flagLabels: string[], configDir: string
5566
flagLabels
5667
.filter(flag => flag !== 'help')
5768
.map((flag: string) => {
69+
if (flag === 'in') {
70+
// rename property since 'in' is a reserved keyword in Javascript
71+
input[`${flag}Val`] = flags[flag]
72+
}
5873
input[flag] = flags[flag] || (config ? config[configPrefix + flag] : null)
5974
})
6075
return input
@@ -84,6 +99,7 @@ const writeToFile = async (outputLocation: string, content: any, force: boolean)
8499
return validatedPath
85100
}
86101

102+
module.exports.getInputFromFile = getInputFromFile
87103
module.exports.getLUISClient = getLUISClient
88104
module.exports.getUserConfig = getUserConfig
89105
module.exports.processInputs = processInputs

packages/luis/test/commands/luis/application/create.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,4 @@ describe('luis:application:create', () => {
5959
expect(ctx.stderr).to.contain('Access denied due to invalid subscription key or wrong API endpoint.')
6060
})
6161

62-
})
62+
})
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import {expect, test} from '@oclif/test'
2+
import * as cp from 'child_process';
3+
const sinon = require('sinon')
4+
const uuidv1 = require('uuid/v1')
5+
const utils = require('../../../../src/utils/index')
6+
7+
describe('luis:application:import', () => {
8+
9+
beforeEach(() => {
10+
sinon.stub(utils, 'processInputs').returnsArg(0)
11+
12+
})
13+
14+
afterEach(() => {
15+
sinon.restore();
16+
});
17+
18+
test
19+
.stdout()
20+
.command(['luis:application:import', '--help'])
21+
.it('should print the help contents when --help is passed as an argument', ctx => {
22+
expect(ctx.stdout).to.contain('Imports an application to LUIS')
23+
})
24+
25+
test
26+
.stdout()
27+
.stderr()
28+
.command(['luis:application:import', '--endpoint', 'https://westus.api.cognitive.microsoft.com', '--subscriptionKey', uuidv1()])
29+
.it('displays an error if any required input parameters are missing', ctx => {
30+
expect(ctx.stderr).to.contain(`Required input property 'name' missing.`)
31+
})
32+
33+
test
34+
.stdout()
35+
.stderr()
36+
.command(['luis:application:import', '--endpoint', 'https://westus.api.cognitive.microsoft.com', '--name', 'sample_app'])
37+
.it('displays an error if any required input parameters are missing', ctx => {
38+
expect(ctx.stderr).to.contain(`Required input property 'subscriptionKey' missing.`)
39+
})
40+
41+
test
42+
.nock('https://westus.api.cognitive.microsoft.com', api => api
43+
.post(uri => uri.includes('apps'))
44+
.reply(201, '99999')
45+
)
46+
.stdout()
47+
.stderr()
48+
.command(['luis:application:import', '--name', 'Sample', '--in', './test/fixtures/sample-app.json', '--endpoint', 'https://westus.api.cognitive.microsoft.com', '--subscriptionKey', uuidv1()])
49+
.it('imports a luis app from a file and returns the app\'s id', ctx => {
50+
expect(ctx.stdout).to.contain('App successfully imported with id 99999')
51+
})
52+
53+
test
54+
.stdout()
55+
.stderr()
56+
.command(['luis:application:import', '--name', 'Sample', '--in', './test/fixtures/xyz.json', '--endpoint', 'https://westus.api.cognitive.microsoft.com', '--subscriptionKey', uuidv1()])
57+
.it('displays an error message if the import file cannot be found', ctx => {
58+
expect(ctx.stderr).to.contain('Failed to read app JSON')
59+
})
60+
61+
test
62+
.stdout()
63+
.stderr()
64+
.command(['luis:application:import', '--name', 'Sample', '--endpoint', 'https://westus.api.cognitive.microsoft.com', '--subscriptionKey', uuidv1()])
65+
.it('displays an error message if no input data detected', ctx => {
66+
expect(ctx.stderr).to.contain('No import data found - please provide input through stdin or the --in flag')
67+
})
68+
69+
test
70+
.stdin('{"luis_schema_version": "4.0.0","versionId": "0.1","name": "sampleapp","desc": "test description","culture": "en-us","tokenizerVersion": "1.0.0","intents": [{"name": "None"}],"entities": [],"composites": [],"closedLists": [],"patternAnyEntities": [],"regex_entities": [],"prebuiltEntities": [],"model_features": [],"regex_features": [],"patterns": [],"utterances": [],"settings": []}')
71+
.stdout()
72+
.stderr()
73+
.command(['luis:application:import', '--name', 'sampleapp', '--endpoint', 'https://westus.api.cognitive.microsoft.com', '--subscriptionKey', uuidv1()])
74+
.it('imports a luis app from stdin and returns the app\'s id', ctx => {
75+
process.stdin.setEncoding('utf8')
76+
process.stdin.once('data', data => {
77+
expect(ctx.stderr).to.contain('App successfully imported with id 99999')
78+
})
79+
})
80+
81+
})
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"luis_schema_version": "4.0.0",
3+
"versionId": "0.1",
4+
"name": "sample_app",
5+
"desc": "test description",
6+
"culture": "en-us",
7+
"tokenizerVersion": "1.0.0",
8+
"intents": [
9+
{
10+
"name": "None"
11+
}
12+
],
13+
"entities": [],
14+
"composites": [],
15+
"closedLists": [],
16+
"patternAnyEntities": [],
17+
"regex_entities": [],
18+
"prebuiltEntities": [],
19+
"model_features": [],
20+
"regex_features": [],
21+
"patterns": [],
22+
"utterances": [],
23+
"settings": []
24+
}

0 commit comments

Comments
 (0)