Commit 3b90b9d9 authored by liuzhe's avatar liuzhe
Browse files

Merge branch 'master' into v2.0-merge

parents e21a6984 77dac12b
...@@ -86,6 +86,8 @@ def update_training_service_config(args): ...@@ -86,6 +86,8 @@ def update_training_service_config(args):
config[args.ts]['machineList'][0]['port'] = args.remote_port config[args.ts]['machineList'][0]['port'] = args.remote_port
if args.remote_pwd is not None: if args.remote_pwd is not None:
config[args.ts]['machineList'][0]['passwd'] = args.remote_pwd config[args.ts]['machineList'][0]['passwd'] = args.remote_pwd
if args.remote_reuse is not None:
config[args.ts]['remoteConfig']['reuse'] = args.remote_reuse.lower() == 'true'
dump_yml_content(TRAINING_SERVICE_FILE, config) dump_yml_content(TRAINING_SERVICE_FILE, config)
...@@ -119,6 +121,7 @@ if __name__ == '__main__': ...@@ -119,6 +121,7 @@ if __name__ == '__main__':
parser.add_argument("--remote_pwd", type=str) parser.add_argument("--remote_pwd", type=str)
parser.add_argument("--remote_host", type=str) parser.add_argument("--remote_host", type=str)
parser.add_argument("--remote_port", type=int) parser.add_argument("--remote_port", type=int)
parser.add_argument("--remote_reuse", type=str)
args = parser.parse_args() args = parser.parse_args()
update_training_service_config(args) update_training_service_config(args)
...@@ -62,7 +62,7 @@ jobs: ...@@ -62,7 +62,7 @@ jobs:
- script: | - script: |
set -e set -e
cd test cd test
python3 nni_test/nnitest/generate_ts_config.py --ts remote --remote_user $(docker_user) --remote_host $(remote_host) \ python3 nni_test/nnitest/generate_ts_config.py --ts remote --remote_reuse $(remote_reuse) --remote_user $(docker_user) --remote_host $(remote_host) \
--remote_port $(cat port) --remote_pwd $(docker_pwd) --nni_manager_ip $(nni_manager_ip) --remote_port $(cat port) --remote_pwd $(docker_pwd) --nni_manager_ip $(nni_manager_ip)
cat config/training_service.yml cat config/training_service.yml
PATH=$HOME/.local/bin:$PATH python3 nni_test/nnitest/run_tests.py --config config/integration_tests.yml --ts remote PATH=$HOME/.local/bin:$PATH python3 nni_test/nnitest/run_tests.py --config config/integration_tests.yml --ts remote
......
...@@ -48,7 +48,7 @@ jobs: ...@@ -48,7 +48,7 @@ jobs:
displayName: 'Get docker port' displayName: 'Get docker port'
- powershell: | - powershell: |
cd test cd test
python nni_test/nnitest/generate_ts_config.py --ts remote --remote_user $(docker_user) --remote_host $(remote_host) --remote_port $(Get-Content port) --remote_pwd $(docker_pwd) --nni_manager_ip $(nni_manager_ip) python nni_test/nnitest/generate_ts_config.py --ts remote --remote_reuse $(remote_reuse) --remote_user $(docker_user) --remote_host $(remote_host) --remote_port $(Get-Content port) --remote_pwd $(docker_pwd) --nni_manager_ip $(nni_manager_ip)
Get-Content config/training_service.yml Get-Content config/training_service.yml
python nni_test/nnitest/run_tests.py --config config/integration_tests.yml --ts remote --exclude cifar10 python nni_test/nnitest/run_tests.py --config config/integration_tests.yml --ts remote --exclude cifar10
displayName: 'integration test' displayName: 'integration test'
......
...@@ -136,6 +136,10 @@ class LinuxCommands extends OsCommands { ...@@ -136,6 +136,10 @@ class LinuxCommands extends OsCommands {
return `${preCommand} && ${command}`; return `${preCommand} && ${command}`;
} }
} }
public fileExistCommand(filePath: string): string {
return `test -e ${filePath} && echo True || echo False`;
}
} }
export { LinuxCommands }; export { LinuxCommands };
...@@ -130,6 +130,10 @@ class WindowsCommands extends OsCommands { ...@@ -130,6 +130,10 @@ class WindowsCommands extends OsCommands {
return `${preCommand} && set prePath=%path% && ${command}`; return `${preCommand} && set prePath=%path% && ${command}`;
} }
} }
public fileExistCommand(filePath: string): string {
return `powershell Test-Path ${filePath} -PathType Leaf`;
}
} }
export { WindowsCommands }; export { WindowsCommands };
...@@ -29,6 +29,7 @@ abstract class OsCommands { ...@@ -29,6 +29,7 @@ abstract class OsCommands {
public abstract extractFile(tarFileName: string, targetFolder: string): string; public abstract extractFile(tarFileName: string, targetFolder: string): string;
public abstract executeScript(script: string, isFile: boolean): string; public abstract executeScript(script: string, isFile: boolean): string;
public abstract addPreCommand(preCommand: string | undefined, command: string | undefined): string | undefined; public abstract addPreCommand(preCommand: string | undefined, command: string | undefined): string | undefined;
public abstract fileExistCommand(filePath: string): string | undefined;
public joinPath(...paths: string[]): string { public joinPath(...paths: string[]): string {
let dir: string = paths.filter((path: any) => path !== '').join(this.pathSpliter); let dir: string = paths.filter((path: any) => path !== '').join(this.pathSpliter);
......
...@@ -238,6 +238,12 @@ class ShellExecutor { ...@@ -238,6 +238,12 @@ class ShellExecutor {
return commandResult.exitCode == 0; return commandResult.exitCode == 0;
} }
public async fileExist(filePath: string): Promise<boolean> {
const commandText = this.osCommands && this.osCommands.fileExistCommand(filePath);
const commandResult = await this.execute(commandText);
return commandResult.stdout !== undefined && commandResult.stdout.trim() === 'True';
}
public async extractFile(tarFileName: string, targetFolder: string): Promise<boolean> { public async extractFile(tarFileName: string, targetFolder: string): Promise<boolean> {
const commandText = this.osCommands && this.osCommands.extractFile(tarFileName, targetFolder); const commandText = this.osCommands && this.osCommands.extractFile(tarFileName, targetFolder);
const commandResult = await this.execute(commandText); const commandResult = await this.execute(commandText);
......
...@@ -137,40 +137,43 @@ export class RemoteEnvironmentService extends EnvironmentService { ...@@ -137,40 +137,43 @@ export class RemoteEnvironmentService extends EnvironmentService {
private async refreshEnvironment(environment: EnvironmentInformation): Promise<void> { private async refreshEnvironment(environment: EnvironmentInformation): Promise<void> {
const executor = await this.getExecutor(environment.id); const executor = await this.getExecutor(environment.id);
const jobpidPath: string = `${environment.runnerWorkingFolder}/pid`; const jobpidPath: string = `${environment.runnerWorkingFolder}/pid`;
const runnerReturnCodeFilePath: string = `${environment.runnerWorkingFolder}/code`; const runnerReturnCodeFilePath: string = `${environment.runnerWorkingFolder}/code`;
if (fs.existsSync(jobpidPath)) { /* eslint-disable require-atomic-updates */
/* eslint-disable require-atomic-updates */ try {
try { // check if pid file exist
const isAlive = await executor.isProcessAlive(jobpidPath); const pidExist = await executor.fileExist(jobpidPath);
// if the process of jobpid is not alive any more if (!pidExist) {
if (!isAlive) { return;
const remoteEnvironment: RemoteMachineEnvironmentInformation = environment as RemoteMachineEnvironmentInformation; }
if (remoteEnvironment.rmMachineMeta === undefined) { const isAlive = await executor.isProcessAlive(jobpidPath);
throw new Error(`${remoteEnvironment.id} machine meta not initialized!`); environment.status = 'RUNNING';
} // if the process of jobpid is not alive any more
this.log.info(`pid in ${remoteEnvironment.rmMachineMeta.ip}:${jobpidPath} is not alive!`); if (!isAlive) {
if (fs.existsSync(runnerReturnCodeFilePath)) { const remoteEnvironment: RemoteMachineEnvironmentInformation = environment as RemoteMachineEnvironmentInformation;
const runnerReturnCode: string = await executor.getRemoteFileContent(runnerReturnCodeFilePath); if (remoteEnvironment.rmMachineMeta === undefined) {
const match: RegExpMatchArray | null = runnerReturnCode.trim() throw new Error(`${remoteEnvironment.id} machine meta not initialized!`);
.match(/^-?(\d+)\s+(\d+)$/); }
if (match !== null) { this.log.info(`pid in ${remoteEnvironment.rmMachineMeta.ip}:${jobpidPath} is not alive!`);
const { 1: code } = match; if (fs.existsSync(runnerReturnCodeFilePath)) {
// Update trial job's status based on result code const runnerReturnCode: string = await executor.getRemoteFileContent(runnerReturnCodeFilePath);
if (parseInt(code, 10) === 0) { const match: RegExpMatchArray | null = runnerReturnCode.trim()
environment.setStatus('SUCCEEDED'); .match(/^-?(\d+)\s+(\d+)$/);
} else { if (match !== null) {
environment.setStatus('FAILED'); const { 1: code } = match;
} // Update trial job's status based on result code
this.releaseEnvironmentResource(environment); if (parseInt(code, 10) === 0) {
} environment.setStatus('SUCCEEDED');
} else {
environment.setStatus('FAILED');
} }
this.releaseEnvironmentResource(environment);
} }
} catch (error) {
this.releaseEnvironmentResource(environment);
this.log.error(`Update job status exception, error is ${error.message}`);
} }
} }
} catch (error) {
this.log.error(`Update job status exception, error is ${error.message}`);
}
} }
public async refreshEnvironmentsStatus(environments: EnvironmentInformation[]): Promise<void> { public async refreshEnvironmentsStatus(environments: EnvironmentInformation[]): Promise<void> {
...@@ -245,6 +248,7 @@ export class RemoteEnvironmentService extends EnvironmentService { ...@@ -245,6 +248,7 @@ export class RemoteEnvironmentService extends EnvironmentService {
'envs', environment.id) 'envs', environment.id)
environment.command = `cd ${environment.runnerWorkingFolder} && \ environment.command = `cd ${environment.runnerWorkingFolder} && \
${environment.command} --job_pid_file ${environment.runnerWorkingFolder}/pid \ ${environment.command} --job_pid_file ${environment.runnerWorkingFolder}/pid \
1>${environment.runnerWorkingFolder}/trialrunner_stdout 2>${environment.runnerWorkingFolder}/trialrunner_stderr \
&& echo $? \`date +%s%3N\` >${environment.runnerWorkingFolder}/code`; && echo $? \`date +%s%3N\` >${environment.runnerWorkingFolder}/code`;
return Promise.resolve(true); return Promise.resolve(true);
} }
...@@ -266,7 +270,6 @@ ${environment.command} --job_pid_file ${environment.runnerWorkingFolder}/pid \ ...@@ -266,7 +270,6 @@ ${environment.command} --job_pid_file ${environment.runnerWorkingFolder}/pid \
// Execute command in remote machine // Execute command in remote machine
executor.executeScript(executor.joinPath(environment.runnerWorkingFolder, executor.executeScript(executor.joinPath(environment.runnerWorkingFolder,
executor.getScriptName("run")), true, false); executor.getScriptName("run")), true, false);
environment.status = 'RUNNING';
if (environment.rmMachineMeta === undefined) { if (environment.rmMachineMeta === undefined) {
throw new Error(`${environment.id} rmMachineMeta not initialized!`); throw new Error(`${environment.id} rmMachineMeta not initialized!`);
} }
......
...@@ -663,19 +663,22 @@ class TrialDispatcher implements TrainingService { ...@@ -663,19 +663,22 @@ class TrialDispatcher implements TrainingService {
trial.status = "RUNNING"; trial.status = "RUNNING";
await this.commandChannel.sendCommand(trial.environment, NEW_TRIAL_JOB, trial.settings); await this.commandChannel.sendCommand(trial.environment, NEW_TRIAL_JOB, trial.settings);
} }
/**
* release the trial assigned environment resources
* @param trial
*/
private releaseEnvironment(trial: TrialDetail): void { private releaseEnvironment(trial: TrialDetail): void {
if (undefined === trial.environment) { if (trial.environment !== undefined) {
throw new Error(`TrialDispatcher: environment is not assigned to trial ${trial.id}, and cannot be released!`); if (trial.environment.runningTrialCount <= 0) {
} throw new Error(`TrialDispatcher: environment ${trial.environment.id} has no counted running trial!`);
if (trial.environment.runningTrialCount <= 0) { }
throw new Error(`TrialDispatcher: environment ${trial.environment.id} has no counted running trial!`); trial.environment.runningTrialCount--;
trial.environment = undefined;
} }
if (true === this.enableGpuScheduler) { if (true === this.enableGpuScheduler) {
this.gpuScheduler.removeGpuReservation(trial); this.gpuScheduler.removeGpuReservation(trial);
} }
trial.environment.runningTrialCount--;
trial.environment = undefined;
} }
private async handleMetricData(trialId: string, data: any): Promise<void> { private async handleMetricData(trialId: string, data: any): Promise<void> {
......
...@@ -1096,10 +1096,10 @@ cli-width@^2.0.0: ...@@ -1096,10 +1096,10 @@ cli-width@^2.0.0:
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
cliui@^7.0.0: cliui@^7.0.2:
version "7.0.1" version "7.0.3"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.1.tgz#a4cb67aad45cd83d8d05128fc9f4d8fbb887e6b3" resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.3.tgz#ef180f26c8d9bff3927ee52428bfec2090427981"
integrity sha512-rcvHOWyGyid6I1WjT/3NatKj2kDt9OdSHSXpyLXaMWFbKpGACNW8pRhhdPUq9MWUOdwn8Rz9AVETjF4105rZZQ== integrity sha512-Gj3QHTkVMPKqwP3f7B4KPkBZRMR9r4rfi5bXFpg1a+Svvj8l7q5CnkBkVQzfxT5DFSsGk2+PascOgL0JYkL2kw==
dependencies: dependencies:
string-width "^4.2.0" string-width "^4.2.0"
strip-ansi "^6.0.0" strip-ansi "^6.0.0"
...@@ -1336,7 +1336,7 @@ debug@^3.1.0: ...@@ -1336,7 +1336,7 @@ debug@^3.1.0:
dependencies: dependencies:
ms "^2.1.1" ms "^2.1.1"
debuglog@^1.0.1: debuglog@*, debuglog@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
...@@ -1606,10 +1606,10 @@ es6-promisify@^5.0.0: ...@@ -1606,10 +1606,10 @@ es6-promisify@^5.0.0:
dependencies: dependencies:
es6-promise "^4.0.3" es6-promise "^4.0.3"
escalade@^3.0.2: escalade@^3.1.1:
version "3.1.0" version "3.1.1"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.0.tgz#e8e2d7c7a8b76f6ee64c2181d6b8151441602d4e" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
integrity sha512-mAk+hPSO8fLDkhV7V0dXazH5pDc6MrjBTPyD3VeKzxnVFjH1MIxbCdqGZB9O8+EwWakZs3ZCbDS4IpRt79V1ig== integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
escape-html@~1.0.3: escape-html@~1.0.3:
version "1.0.3" version "1.0.3"
...@@ -2392,7 +2392,7 @@ import-lazy@^2.1.0: ...@@ -2392,7 +2392,7 @@ import-lazy@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43"
imurmurhash@^0.1.4: imurmurhash@*, imurmurhash@^0.1.4:
version "0.1.4" version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
...@@ -3074,6 +3074,11 @@ lockfile@^1.0.4: ...@@ -3074,6 +3074,11 @@ lockfile@^1.0.4:
dependencies: dependencies:
signal-exit "^3.0.2" signal-exit "^3.0.2"
lodash._baseindexof@*:
version "3.1.0"
resolved "https://registry.yarnpkg.com/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz#fe52b53a1c6761e42618d654e4a25789ed61822c"
integrity sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw=
lodash._baseuniq@~4.6.0: lodash._baseuniq@~4.6.0:
version "4.6.0" version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8" resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8"
...@@ -3081,10 +3086,32 @@ lodash._baseuniq@~4.6.0: ...@@ -3081,10 +3086,32 @@ lodash._baseuniq@~4.6.0:
lodash._createset "~4.0.0" lodash._createset "~4.0.0"
lodash._root "~3.0.0" lodash._root "~3.0.0"
lodash._bindcallback@*:
version "3.0.1"
resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e"
integrity sha1-5THCdkTPi1epnhftlbNcdIeJOS4=
lodash._cacheindexof@*:
version "3.0.2"
resolved "https://registry.yarnpkg.com/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz#3dc69ac82498d2ee5e3ce56091bafd2adc7bde92"
integrity sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI=
lodash._createcache@*:
version "3.1.2"
resolved "https://registry.yarnpkg.com/lodash._createcache/-/lodash._createcache-3.1.2.tgz#56d6a064017625e79ebca6b8018e17440bdcf093"
integrity sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=
dependencies:
lodash._getnative "^3.0.0"
lodash._createset@~4.0.0: lodash._createset@~4.0.0:
version "4.0.3" version "4.0.3"
resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26" resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26"
lodash._getnative@*, lodash._getnative@^3.0.0:
version "3.9.1"
resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=
lodash._root@~3.0.0: lodash._root@~3.0.0:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692" resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692"
...@@ -3133,6 +3160,11 @@ lodash.pick@^4.4.0: ...@@ -3133,6 +3160,11 @@ lodash.pick@^4.4.0:
version "4.4.0" version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
lodash.restparam@*:
version "3.6.1"
resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805"
integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=
lodash.unescape@4.0.1: lodash.unescape@4.0.1:
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c" resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c"
...@@ -3723,8 +3755,9 @@ npm-run-path@^2.0.0: ...@@ -3723,8 +3755,9 @@ npm-run-path@^2.0.0:
path-key "^2.0.0" path-key "^2.0.0"
npm-user-validate@~1.0.0: npm-user-validate@~1.0.0:
version "1.0.0" version "1.0.1"
resolved "https://registry.yarnpkg.com/npm-user-validate/-/npm-user-validate-1.0.0.tgz#8ceca0f5cea04d4e93519ef72d0557a75122e951" resolved "https://registry.yarnpkg.com/npm-user-validate/-/npm-user-validate-1.0.1.tgz#31428fc5475fe8416023f178c0ab47935ad8c561"
integrity sha512-uQwcd/tY+h1jnEaze6cdX/LrhWhoBxfSknxentoqmIuStxUExxjWd3ULMLFPiFUrZKbOVMowH6Jq2FRWfmhcEw==
npm@5.1.0, npm@>=6.14.8: npm@5.1.0, npm@>=6.14.8:
version "6.14.8" version "6.14.8"
...@@ -5724,10 +5757,10 @@ y18n@^4.0.0: ...@@ -5724,10 +5757,10 @@ y18n@^4.0.0:
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
y18n@^5.0.1: y18n@^5.0.2:
version "5.0.1" version "5.0.4"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.1.tgz#1ad2a7eddfa8bce7caa2e1f6b5da96c39d99d571" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.4.tgz#0ab2db89dd5873b5ec4682d8e703e833373ea897"
integrity sha512-/jJ831jEs4vGDbYPQp4yGKDYPSCCEQ45uZWJHE1AoYBzqdZi8+LDWas0z4HrmJXmKdpFsTiowSHXdxyFhpmdMg== integrity sha512-deLOfD+RvFgrpAmSZgfGdWYE+OKyHcVHaRQ7NphG/63scpRvTHHeQMAxGGvaLVGJ+HYVcCXlzcTK0ZehFf+eHQ==
yallist@^2.1.2: yallist@^2.1.2:
version "2.1.2" version "2.1.2"
...@@ -5746,10 +5779,10 @@ yallist@^4.0.0: ...@@ -5746,10 +5779,10 @@ yallist@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
yargs-parser@13.1.2, yargs-parser@>=20.2.0, yargs-parser@^20.0.0: yargs-parser@13.1.2, yargs-parser@>=20.2.0, yargs-parser@^20.2.2:
version "20.2.2" version "20.2.3"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.2.tgz#84562c6b1c41ccec2f13d346c7dd83f8d1a0dc70" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.3.tgz#92419ba867b858c868acf8bae9bf74af0dd0ce26"
integrity sha512-XmrpXaTl6noDsf1dKpBuUNCOHqjs0g3jRMXf/ztRxdOmb+er8kE5z5b55Lz3p5u2T8KJ59ENBnASS8/iapVJ5g== integrity sha512-emOFRT9WVHw03QSvN5qor9QQT9+sw5vwxfYweivSMHTcAXPefwVae2FjO7JJjj8hCE4CzPOPeFM83VwT29HCww==
yargs-unparser@1.6.1: yargs-unparser@1.6.1:
version "1.6.1" version "1.6.1"
...@@ -5763,17 +5796,17 @@ yargs-unparser@1.6.1: ...@@ -5763,17 +5796,17 @@ yargs-unparser@1.6.1:
yargs "^14.2.3" yargs "^14.2.3"
yargs@13.3.2, yargs@>=16.0.3, yargs@^11.0.0, yargs@^14.2.3, yargs@^15.0.2, yargs@^8.0.2: yargs@13.3.2, yargs@>=16.0.3, yargs@^11.0.0, yargs@^14.2.3, yargs@^15.0.2, yargs@^8.0.2:
version "16.0.3" version "16.1.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.0.3.tgz#7a919b9e43c90f80d4a142a89795e85399a7e54c" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.1.0.tgz#fc333fe4791660eace5a894b39d42f851cd48f2a"
integrity sha512-6+nLw8xa9uK1BOEOykaiYAJVh6/CjxWXK/q9b5FpRgNslt8s22F2xMBqVIKgCRjNgGvGPBy8Vog7WN7yh4amtA== integrity sha512-upWFJOmDdHN0syLuESuvXDmrRcWd1QafJolHskzaw79uZa7/x53gxQKiR07W59GWY1tFhhU/Th9DrtSfpS782g==
dependencies: dependencies:
cliui "^7.0.0" cliui "^7.0.2"
escalade "^3.0.2" escalade "^3.1.1"
get-caller-file "^2.0.5" get-caller-file "^2.0.5"
require-directory "^2.1.1" require-directory "^2.1.1"
string-width "^4.2.0" string-width "^4.2.0"
y18n "^5.0.1" y18n "^5.0.2"
yargs-parser "^20.0.0" yargs-parser "^20.2.2"
yn@^2.0.0: yn@^2.0.0:
version "2.0.0" version "2.0.0"
......
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
.ms-Callout-main { .ms-Callout-main {
p { p {
font-weight: 500; font-weight: 500;
color: #333; color: #fff;
} }
} }
......
...@@ -12,6 +12,7 @@ interface AppState { ...@@ -12,6 +12,7 @@ interface AppState {
columnList: string[]; columnList: string[];
experimentUpdateBroadcast: number; experimentUpdateBroadcast: number;
trialsUpdateBroadcast: number; trialsUpdateBroadcast: number;
maxDurationUnit: string;
metricGraphMode: 'max' | 'min'; // tuner's optimize_mode filed metricGraphMode: 'max' | 'min'; // tuner's optimize_mode filed
isillegalFinal: boolean; isillegalFinal: boolean;
expWarningMessage: string; expWarningMessage: string;
...@@ -26,11 +27,14 @@ export const AppContext = React.createContext({ ...@@ -26,11 +27,14 @@ export const AppContext = React.createContext({
trialsUpdateBroadcast: 0, trialsUpdateBroadcast: 0,
metricGraphMode: 'max', metricGraphMode: 'max',
bestTrialEntries: '10', bestTrialEntries: '10',
maxDurationUnit: 'm',
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
changeColumn: (val: string[]) => {}, changeColumn: (val: string[]) => {},
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
changeMetricGraphMode: (val: 'max' | 'min') => {}, changeMetricGraphMode: (val: 'max' | 'min') => {},
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
changeMaxDurationUnit: (val: string) => {},
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
changeEntries: (val: string) => {}, changeEntries: (val: string) => {},
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
updateOverviewPage: () => {} updateOverviewPage: () => {}
...@@ -47,6 +51,7 @@ class App extends React.Component<{}, AppState> { ...@@ -47,6 +51,7 @@ class App extends React.Component<{}, AppState> {
experimentUpdateBroadcast: 0, experimentUpdateBroadcast: 0,
trialsUpdateBroadcast: 0, trialsUpdateBroadcast: 0,
metricGraphMode: 'max', metricGraphMode: 'max',
maxDurationUnit: 'm',
isillegalFinal: false, isillegalFinal: false,
expWarningMessage: '', expWarningMessage: '',
bestTrialEntries: '10', bestTrialEntries: '10',
...@@ -91,6 +96,11 @@ class App extends React.Component<{}, AppState> { ...@@ -91,6 +96,11 @@ class App extends React.Component<{}, AppState> {
this.setState({ bestTrialEntries: entries }); this.setState({ bestTrialEntries: entries });
}; };
// overview max duration unit
changeMaxDurationUnit = (unit: string): void => {
this.setState({ maxDurationUnit: unit });
};
updateOverviewPage = (): void => { updateOverviewPage = (): void => {
this.setState(state => ({ this.setState(state => ({
experimentUpdateBroadcast: state.experimentUpdateBroadcast + 1 experimentUpdateBroadcast: state.experimentUpdateBroadcast + 1
...@@ -114,7 +124,8 @@ class App extends React.Component<{}, AppState> { ...@@ -114,7 +124,8 @@ class App extends React.Component<{}, AppState> {
metricGraphMode, metricGraphMode,
isillegalFinal, isillegalFinal,
expWarningMessage, expWarningMessage,
bestTrialEntries bestTrialEntries,
maxDurationUnit
} = this.state; } = this.state;
if (experimentUpdateBroadcast === 0 || trialsUpdateBroadcast === 0) { if (experimentUpdateBroadcast === 0 || trialsUpdateBroadcast === 0) {
return null; // TODO: render a loading page return null; // TODO: render a loading page
...@@ -137,7 +148,24 @@ class App extends React.Component<{}, AppState> { ...@@ -137,7 +148,24 @@ class App extends React.Component<{}, AppState> {
<Stack className='contentBox'> <Stack className='contentBox'>
<Stack className='content'> <Stack className='content'>
{/* search space & config */} {/* search space & config */}
<TrialConfigButton /> <AppContext.Provider
value={{
interval,
columnList,
changeColumn: this.changeColumn,
experimentUpdateBroadcast,
trialsUpdateBroadcast,
metricGraphMode,
maxDurationUnit,
changeMaxDurationUnit: this.changeMaxDurationUnit,
changeMetricGraphMode: this.changeMetricGraphMode,
bestTrialEntries,
changeEntries: this.changeEntries,
updateOverviewPage: this.updateOverviewPage
}}
>
<TrialConfigButton />
</AppContext.Provider>
{/* if api has error field, show error message */} {/* if api has error field, show error message */}
{errorList.map( {errorList.map(
(item, key) => (item, key) =>
...@@ -160,6 +188,8 @@ class App extends React.Component<{}, AppState> { ...@@ -160,6 +188,8 @@ class App extends React.Component<{}, AppState> {
experimentUpdateBroadcast, experimentUpdateBroadcast,
trialsUpdateBroadcast, trialsUpdateBroadcast,
metricGraphMode, metricGraphMode,
maxDurationUnit,
changeMaxDurationUnit: this.changeMaxDurationUnit,
changeMetricGraphMode: this.changeMetricGraphMode, changeMetricGraphMode: this.changeMetricGraphMode,
bestTrialEntries, bestTrialEntries,
changeEntries: this.changeEntries, changeEntries: this.changeEntries,
......
...@@ -10,7 +10,8 @@ import { ReBasicInfo } from './overview/experiment/BasicInfo'; ...@@ -10,7 +10,8 @@ import { ReBasicInfo } from './overview/experiment/BasicInfo';
import { ExpDuration } from './overview/count/ExpDuration'; import { ExpDuration } from './overview/count/ExpDuration';
import { ExpDurationContext } from './overview/count/ExpDurationContext'; import { ExpDurationContext } from './overview/count/ExpDurationContext';
import { TrialCount } from './overview/count/TrialCount'; import { TrialCount } from './overview/count/TrialCount';
import { Command } from './overview/experiment/Command'; import { Command1 } from './overview/command/Command1';
import { Command2 } from './overview/command/Command2';
import { TitleContext } from './overview/TitleContext'; import { TitleContext } from './overview/TitleContext';
import { itemStyle1, itemStyleSucceed, itemStyle2, entriesOption } from './overview/overviewConst'; import { itemStyle1, itemStyleSucceed, itemStyle2, entriesOption } from './overview/overviewConst';
import '../static/style/overview/overview.scss'; import '../static/style/overview/overview.scss';
...@@ -66,7 +67,13 @@ class Overview extends React.Component<{}, OverviewState> { ...@@ -66,7 +67,13 @@ class Overview extends React.Component<{}, OverviewState> {
return ( return (
<AppContext.Consumer> <AppContext.Consumer>
{(value): React.ReactNode => { {(value): React.ReactNode => {
const { metricGraphMode, bestTrialEntries, updateOverviewPage } = value; const {
metricGraphMode,
bestTrialEntries,
maxDurationUnit,
updateOverviewPage,
changeMaxDurationUnit
} = value;
const maxActive = metricGraphMode === 'max' ? 'active' : ''; const maxActive = metricGraphMode === 'max' ? 'active' : '';
const minActive = metricGraphMode === 'min' ? 'active' : ''; const minActive = metricGraphMode === 'min' ? 'active' : '';
return ( return (
...@@ -88,18 +95,29 @@ class Overview extends React.Component<{}, OverviewState> { ...@@ -88,18 +95,29 @@ class Overview extends React.Component<{}, OverviewState> {
<Title /> <Title />
</TitleContext.Provider> </TitleContext.Provider>
<ExpDurationContext.Provider <ExpDurationContext.Provider
value={{ maxExecDuration, execDuration, updateOverviewPage }} value={{
maxExecDuration,
execDuration,
updateOverviewPage,
maxDurationUnit,
changeMaxDurationUnit
}}
> >
<ExpDuration /> <ExpDuration />
</ExpDurationContext.Provider> </ExpDurationContext.Provider>
</div> </div>
<div className='empty' />
<div className='trialCount'> <div className='trialCount'>
<TitleContext.Provider value={{ text: 'Trial numbers', icon: 'NumberSymbol' }}> <TitleContext.Provider value={{ text: 'Trial numbers', icon: 'NumberSymbol' }}>
<Title /> <Title />
</TitleContext.Provider> </TitleContext.Provider>
<ExpDurationContext.Provider <ExpDurationContext.Provider
value={{ maxExecDuration, execDuration, updateOverviewPage }} value={{
maxExecDuration,
execDuration,
updateOverviewPage,
maxDurationUnit,
changeMaxDurationUnit
}}
> >
<TrialCount /> <TrialCount />
</ExpDurationContext.Provider> </ExpDurationContext.Provider>
...@@ -114,7 +132,6 @@ class Overview extends React.Component<{}, OverviewState> { ...@@ -114,7 +132,6 @@ class Overview extends React.Component<{}, OverviewState> {
</TitleContext.Provider> </TitleContext.Provider>
</div> </div>
<div className='topTrialTitle'> <div className='topTrialTitle'>
{/* <Stack horizontal horizontalAlign='space-between'> */}
<Stack horizontal horizontalAlign='end'> <Stack horizontal horizontalAlign='end'>
<DefaultButton <DefaultButton
onClick={this.clickMaxTop} onClick={this.clickMaxTop}
...@@ -152,8 +169,11 @@ class Overview extends React.Component<{}, OverviewState> { ...@@ -152,8 +169,11 @@ class Overview extends React.Component<{}, OverviewState> {
</Stack> </Stack>
<SuccessTable trialIds={bestTrials.map(trial => trial.info.id)} /> <SuccessTable trialIds={bestTrials.map(trial => trial.info.id)} />
</div> </div>
<div className='overviewCommand'> <div className='overviewCommand1'>
<Command /> <Command1 />
</div>
<div className='overviewCommand2'>
<Command2 />
</div> </div>
<div className='overviewChart'> <div className='overviewChart'>
<Stack horizontal> <Stack horizontal>
......
...@@ -82,7 +82,7 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { ...@@ -82,7 +82,7 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> {
</Pivot> </Pivot>
</div> </div>
{/* trial table list */} {/* trial table list */}
<div style={{ backgroundColor: '#fff' }}> <div style={{ backgroundColor: '#fff', marginTop: 10 }}>
<TableList <TableList
tableSource={source} tableSource={source}
trialsUpdateBroadcast={this.context.trialsUpdateBroadcast} trialsUpdateBroadcast={this.context.trialsUpdateBroadcast}
......
...@@ -60,7 +60,10 @@ class Customize extends React.Component<CustomizeProps, CustomizeState> { ...@@ -60,7 +60,10 @@ class Customize extends React.Component<CustomizeProps, CustomizeState> {
Object.keys(customized).map(item => { Object.keys(customized).map(item => {
if (item !== 'tag') { if (item !== 'tag') {
// unified data type // unified data type
if (typeof copyTrialParameter[item] === 'number' && typeof customized[item] === 'string') { if (
(typeof copyTrialParameter[item] === 'number' && typeof customized[item] === 'string') ||
(typeof copyTrialParameter[item] === 'boolean' && typeof customized[item] === 'string')
) {
customized[item] = JSON.parse(customized[item]); customized[item] = JSON.parse(customized[item]);
} }
if (searchSpace[item] === undefined) { if (searchSpace[item] === undefined) {
......
...@@ -112,11 +112,15 @@ class KillJob extends React.Component<KillJobProps, KillJobState> { ...@@ -112,11 +112,15 @@ class KillJob extends React.Component<KillJobProps, KillJobState> {
setInitialFocus={true} setInitialFocus={true}
> >
<div className={styles.header}> <div className={styles.header}>
<p className={styles.title}>Kill trial</p> <p className={styles.title} style={{ color: '#333' }}>
Kill trial
</p>
</div> </div>
<div className={styles.inner}> <div className={styles.inner}>
<div> <div>
<p className={styles.subtext}>{prompString}</p> <p className={styles.subtext} style={{ color: '#333' }}>
{prompString}
</p>
</div> </div>
</div> </div>
<FocusZone> <FocusZone>
......
import React from 'react'; import React from 'react';
import { TooltipHost, Stack } from '@fluentui/react';
import { EXPERIMENT } from '../../../static/datamodel'; import { EXPERIMENT } from '../../../static/datamodel';
import '../../../static/style/overview/command.scss'; import '../../../static/style/overview/command.scss';
export const Command = (): any => { export const Command1 = (): any => {
const clusterMetaData = EXPERIMENT.profile.params.clusterMetaData;
const tuner = EXPERIMENT.profile.params.tuner; const tuner = EXPERIMENT.profile.params.tuner;
const advisor = EXPERIMENT.profile.params.advisor; const advisor = EXPERIMENT.profile.params.advisor;
const assessor = EXPERIMENT.profile.params.assessor; const assessor = EXPERIMENT.profile.params.assessor;
let title = ''; let title = '';
let builtinName = ''; let builtinName = '';
let trialCommand = 'unknown';
if (tuner !== undefined) { if (tuner !== undefined) {
title = title.concat('Tuner'); title = title.concat('Tuner');
if (tuner.builtinTunerName !== undefined) { if (tuner.builtinTunerName !== undefined) {
...@@ -29,35 +26,15 @@ export const Command = (): any => { ...@@ -29,35 +26,15 @@ export const Command = (): any => {
builtinName = builtinName.concat(assessor.builtinAssessorName); builtinName = builtinName.concat(assessor.builtinAssessorName);
} }
} }
if (clusterMetaData !== undefined) {
for (const item of clusterMetaData) {
if (item.key === 'command') {
trialCommand = item.value;
}
}
}
return ( return (
<div className='command basic'> <div className='basic'>
<div className='command1'> <div>
<p>Training platform</p> <p className='command'>Training platform</p>
<div className='nowrap'>{EXPERIMENT.profile.params.trainingServicePlatform}</div> <div className='nowrap'>{EXPERIMENT.profile.params.trainingServicePlatform}</div>
<p className='lineMargin'>{title}</p> <p className='lineMargin'>{title}</p>
<div className='nowrap'>{builtinName}</div> <div className='nowrap'>{builtinName}</div>
</div> </div>
<Stack className='command2'>
<p>Log directory</p>
<div className='nowrap'>
<TooltipHost content={EXPERIMENT.profile.logDir || 'unknown'} className='nowrap'>
{EXPERIMENT.profile.logDir || 'unknown'}
</TooltipHost>
</div>
<p className='lineMargin'>Trial command</p>
<div className='nowrap'>
<TooltipHost content={trialCommand || 'unknown'} className='nowrap'>
{trialCommand || 'unknown'}
</TooltipHost>
</div>
</Stack>
</div> </div>
); );
}; };
import React from 'react';
import { TooltipHost, DirectionalHint } from '@fluentui/react';
import { EXPERIMENT } from '../../../static/datamodel';
import { TOOLTIP_BACKGROUND_COLOR } from '../../../static/const';
import '../../../static/style/overview/command.scss';
export const Command2 = (): any => {
const clusterMetaData = EXPERIMENT.profile.params.clusterMetaData;
let trialCommand = 'unknown';
if (clusterMetaData !== undefined) {
for (const item of clusterMetaData) {
if (item.key === 'command') {
trialCommand = item.value as string;
}
if (item.key === 'trial_config') {
if (typeof item.value === 'object' && 'command' in item.value) {
trialCommand = item.value.command as string;
}
}
}
}
return (
<div className='basic'>
<p className='command'>Log directory</p>
<div className='nowrap'>
<TooltipHost
content={EXPERIMENT.profile.logDir || 'unknown'}
className='nowrap'
directionalHint={DirectionalHint.bottomCenter}
tooltipProps={{
calloutProps: {
styles: {
beak: { background: TOOLTIP_BACKGROUND_COLOR },
beakCurtain: { background: TOOLTIP_BACKGROUND_COLOR },
calloutMain: { background: TOOLTIP_BACKGROUND_COLOR }
}
}
}}
>
{EXPERIMENT.profile.logDir || 'unknown'}
</TooltipHost>
</div>
<p className='lineMargin'>Trial command</p>
<div className='nowrap'>
<TooltipHost
content={trialCommand || 'unknown'}
className='nowrap'
directionalHint={DirectionalHint.bottomCenter}
tooltipProps={{
calloutProps: {
styles: {
beak: { background: TOOLTIP_BACKGROUND_COLOR },
beakCurtain: { background: TOOLTIP_BACKGROUND_COLOR },
calloutMain: { background: TOOLTIP_BACKGROUND_COLOR }
}
}
}}
>
{trialCommand || 'unknown'}
</TooltipHost>
</div>
</div>
);
};
import React, { useState, useCallback, useContext } from 'react'; import React, { useState, useCallback, useContext } from 'react';
import axios from 'axios'; import axios from 'axios';
import { Dropdown } from '@fluentui/react';
import { EXPERIMENT } from '../../../static/datamodel'; import { EXPERIMENT } from '../../../static/datamodel';
import { AppContext } from '../../../App';
import { EditExpeParamContext } from './context'; import { EditExpeParamContext } from './context';
import { MANAGER_IP } from '../../../static/const'; import { durationUnit } from '../overviewConst';
import { convertTimeToSecond } from '../../../static/function'; import { MANAGER_IP, MAX_TRIAL_NUMBERS } from '../../../static/const';
import { Edit, CheckMark, Cancel } from '../../buttons/Icon'; import { Edit, CheckMark, Cancel } from '../../buttons/Icon';
import MessageInfo from '../../modals/MessageInfo'; import MessageInfo from '../../modals/MessageInfo';
import '../../../static/style/overview/count.scss'; import '../../../static/style/overview/count.scss';
...@@ -28,12 +30,14 @@ export const EditExperimentParam = (): any => { ...@@ -28,12 +30,14 @@ export const EditExperimentParam = (): any => {
const { title, field, editType, maxExecDuration, maxTrialNum, trialConcurrency, updateOverviewPage } = useContext( const { title, field, editType, maxExecDuration, maxTrialNum, trialConcurrency, updateOverviewPage } = useContext(
EditExpeParamContext EditExpeParamContext
); );
const { maxDurationUnit, changeMaxDurationUnit } = useContext(AppContext);
const [unit, setUnit] = useState(maxDurationUnit);
let defaultVal = ''; let defaultVal = '';
let editVal = ''; let editVal = '';
if (title === 'Max duration') { if (title === 'Max duration') {
defaultVal = maxExecDuration; defaultVal = maxExecDuration;
editVal = maxExecDuration; editVal = maxExecDuration;
} else if (title === 'Max trial numbers') { } else if (title === MAX_TRIAL_NUMBERS) {
defaultVal = maxTrialNum.toString(); defaultVal = maxTrialNum.toString();
editVal = maxTrialNum.toString(); editVal = maxTrialNum.toString();
} else { } else {
...@@ -46,32 +50,64 @@ export const EditExperimentParam = (): any => { ...@@ -46,32 +50,64 @@ export const EditExperimentParam = (): any => {
setEditValInput(event.target.value); setEditValInput(event.target.value);
} }
function cancelEdit(): void { function showMessageInfo(info: string, typeInfo: string): any {
setEditValInput(defaultVal); setInfo(info);
showPencil(); setTypeInfo(typeInfo);
showSucceedInfo();
setTimeout(hideSucceedInfo, 2000);
}
function updateUnit(event: React.FormEvent<HTMLDivElement>, item: any): void {
if (item !== undefined) {
setUnit(item.key);
}
} }
async function confirmEdit(): Promise<void> { async function confirmEdit(): Promise<void> {
const isMaxDuration = title === 'Max duration'; const isMaxDuration = title === 'Max duration';
const newProfile = Object.assign({}, EXPERIMENT.profile); const newProfile = Object.assign({}, EXPERIMENT.profile);
let beforeParam = ''; let beforeParam = '';
if (!isMaxDuration && !editInputVal.match(/^[1-9]\d*$/)) { if (isMaxDuration) {
showMessageInfo('Please enter a positive integer!', 'error'); if (!editInputVal.match(/^\d+(?=\.{0,1}\d+$|$)/)) {
return; showMessageInfo('Please enter a number!', 'error');
setEditValInput(defaultVal);
return;
}
} else {
if (!editInputVal.match(/^[1-9]\d*$/)) {
showMessageInfo('Please enter a positive integer!', 'error');
setEditValInput(defaultVal);
return;
}
} }
if (isMaxDuration) { if (isMaxDuration) {
beforeParam = maxExecDuration; beforeParam = maxExecDuration;
} else if (title === 'Max trial numbers') { } else if (title === MAX_TRIAL_NUMBERS) {
beforeParam = maxTrialNum.toString(); beforeParam = maxTrialNum.toString();
} else { } else {
beforeParam = trialConcurrency.toString(); beforeParam = trialConcurrency.toString();
} }
if (editInputVal === beforeParam) { if (editInputVal === beforeParam) {
showMessageInfo(`Trial ${field} has not changed`, 'error'); if (isMaxDuration) {
return; if (maxDurationUnit === unit) {
showMessageInfo(`Trial ${field} has not changed`, 'error');
return;
}
} else {
showMessageInfo(`Trial ${field} has not changed`, 'error');
return;
}
} }
if (isMaxDuration) { if (isMaxDuration) {
newProfile.params[field] = convertTimeToSecond(editInputVal); const maxDura = JSON.parse(editInputVal);
if (unit === 'm') {
newProfile.params[field] = maxDura * 60;
} else if (unit === 'h') {
newProfile.params[field] = maxDura * 3600;
} else {
newProfile.params[field] = maxDura * 24 * 60 * 60;
}
} else { } else {
newProfile.params[field] = parseInt(editInputVal, 10); newProfile.params[field] = parseInt(editInputVal, 10);
} }
...@@ -82,7 +118,8 @@ export const EditExperimentParam = (): any => { ...@@ -82,7 +118,8 @@ export const EditExperimentParam = (): any => {
params: { update_type: editType } params: { update_type: editType }
}); });
if (res.status === 200) { if (res.status === 200) {
showMessageInfo(`Successfully updated ${field}`, 'success'); showMessageInfo(`Successfully updated experiment's ${field}`, 'success');
changeMaxDurationUnit(unit);
} }
} catch (error) { } catch (error) {
if (error.response && error.response.data.error) { if (error.response && error.response.data.error) {
...@@ -94,54 +131,88 @@ export const EditExperimentParam = (): any => { ...@@ -94,54 +131,88 @@ export const EditExperimentParam = (): any => {
} else { } else {
showMessageInfo(`Failed to update trial ${field}\nUnknown error`, 'error'); showMessageInfo(`Failed to update trial ${field}\nUnknown error`, 'error');
} }
setEditValInput(defaultVal);
} }
showPencil(); showPencil();
updateOverviewPage(); updateOverviewPage();
} }
function showMessageInfo(info: string, typeInfo: string): any { function cancelEdit(): void {
setInfo(info); setEditValInput(defaultVal);
setTypeInfo(typeInfo); showPencil();
showSucceedInfo(); setUnit(maxDurationUnit);
setTimeout(hideSucceedInfo, 2000); }
function convertUnit(val: string): string {
if (val === 'd') {
return 'day';
} else if (val === 'h') {
return 'hour';
} else if (val === 'm') {
return 'min';
} else {
return val;
}
} }
return ( return (
<EditExpeParamContext.Consumer> <AppContext.Consumer>
{(value): React.ReactNode => { {(values): React.ReactNode => {
return ( return (
<React.Fragment> <EditExpeParamContext.Consumer>
<p>{value.title}</p> {(value): React.ReactNode => {
<div> let editClassName = '';
<input if (value.field === 'maxExecDuration') {
className={`${value.field} durationInput`} editClassName = isShowPencil ? 'noEditDuration' : 'editDuration';
ref={DurationInputRef} }
disabled={isShowPencil ? true : false} return (
value={editInputVal} <React.Fragment>
onChange={setInputVal} <div className={`${editClassName} editparam`}>
/> <span>{value.title}</span>
{isShowPencil && ( <input
<span className='edit' onClick={hidePencil}> className={`${value.field} editparam-Input`}
{Edit} ref={DurationInputRef}
</span> disabled={isShowPencil ? true : false}
)} value={editInputVal}
onChange={setInputVal}
{!isShowPencil && ( />
<span className='series'> {isShowPencil && title === 'Max duration' && (
<span className='confirm' onClick={confirmEdit}> <span>{convertUnit(values.maxDurationUnit)}</span>
{CheckMark} )}
</span> {!isShowPencil && title === 'Max duration' && (
<span className='cancel' onClick={cancelEdit}> <Dropdown
{Cancel} selectedKey={unit}
</span> options={durationUnit}
</span> className='editparam-dropdown'
)} onChange={updateUnit}
/>
)}
{isShowPencil && (
<span className='edit' onClick={hidePencil}>
{Edit}
</span>
)}
{!isShowPencil && (
<span className='series'>
<span className='confirm' onClick={confirmEdit}>
{CheckMark}
</span>
<span className='cancel' onClick={cancelEdit}>
{Cancel}
</span>
</span>
)}
{isShowSucceedInfo && <MessageInfo className='info' typeInfo={typeInfo} info={info} />} {isShowSucceedInfo && (
</div> <MessageInfo className='info' typeInfo={typeInfo} info={info} />
</React.Fragment> )}
</div>
</React.Fragment>
);
}}
</EditExpeParamContext.Consumer>
); );
}} }}
</EditExpeParamContext.Consumer> </AppContext.Consumer>
); );
}; };
import React from 'react'; import React from 'react';
import { Stack, TooltipHost, ProgressIndicator } from '@fluentui/react'; import { Stack, ProgressIndicator, TooltipHost, DirectionalHint } from '@fluentui/react';
import { EXPERIMENT } from '../../../static/datamodel'; import { EXPERIMENT } from '../../../static/datamodel';
import { CONTROLTYPE } from '../../../static/const'; import { CONTROLTYPE, TOOLTIP_BACKGROUND_COLOR } from '../../../static/const';
import { convertDuration } from '../../../static/function'; import { convertDuration, convertTimeAsUnit } from '../../../static/function';
import { EditExperimentParam } from './EditExperimentParam'; import { EditExperimentParam } from './EditExperimentParam';
import { ExpDurationContext } from './ExpDurationContext'; import { ExpDurationContext } from './ExpDurationContext';
import { EditExpeParamContext } from './context'; import { EditExpeParamContext } from './context';
import { durationItem1, durationItem2 } from './commonStyle';
import '../../../static/style/overview/count.scss'; import '../../../static/style/overview/count.scss';
const itemStyle1: React.CSSProperties = {
width: '62%',
height: 80
};
const itemStyle2: React.CSSProperties = {
width: '63%',
height: 80,
textAlign: 'right'
};
export const ExpDuration = (): any => ( export const ExpDuration = (): any => (
<ExpDurationContext.Consumer> <ExpDurationContext.Consumer>
{(value): React.ReactNode => { {(value): React.ReactNode => {
const { maxExecDuration, execDuration, updateOverviewPage } = value; const { maxExecDuration, execDuration, maxDurationUnit, updateOverviewPage } = value;
const tooltip = maxExecDuration - execDuration; const tooltip = maxExecDuration - execDuration;
const maxExecDurationStr = convertDuration(maxExecDuration);
const percent = execDuration / maxExecDuration; const percent = execDuration / maxExecDuration;
const execDurationStr = convertDuration(execDuration);
const maxExecDurationStr = convertTimeAsUnit(maxDurationUnit, maxExecDuration).toString();
return ( return (
<Stack horizontal className='ExpDuration'> <Stack horizontal className='ExpDuration'>
<div style={itemStyle1}> <div style={durationItem1}>
<TooltipHost content={`${convertDuration(tooltip)} remaining`}> <TooltipHost
<ProgressIndicator percentComplete={percent} barHeight={15} /> content={`${convertDuration(tooltip)} remaining`}
directionalHint={DirectionalHint.bottomCenter}
tooltipProps={{
calloutProps: {
styles: {
beak: { background: TOOLTIP_BACKGROUND_COLOR },
beakCurtain: { background: TOOLTIP_BACKGROUND_COLOR },
calloutMain: { background: TOOLTIP_BACKGROUND_COLOR }
}
}
}}
>
<ProgressIndicator className={EXPERIMENT.status} percentComplete={percent} barHeight={15} />
</TooltipHost> </TooltipHost>
{/* execDuration / maxDuration: 20min / 1h */}
<div className='exp-progress'>
<span className={`${EXPERIMENT.status} bold`}>{execDurationStr}</span>
<span className='joiner'>/</span>
<span>{`${maxExecDurationStr} ${maxDurationUnit}`}</span>
</div>
</div> </div>
<div style={itemStyle2}> <div style={durationItem2}>
<Stack horizontal></Stack>
<EditExpeParamContext.Provider <EditExpeParamContext.Provider
value={{ value={{
editType: CONTROLTYPE[0], editType: CONTROLTYPE[0],
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment