Skip to content

Commit

Permalink
feat(vscode): Improve status step indicator for export experience (#4305
Browse files Browse the repository at this point in the history
)

* Add spinner and checbox icon

* Add styles to final text

* Fix spinner

* Update intl text to  constants
  • Loading branch information
ccastrotrejo committed Mar 4, 2024
1 parent 3ee7bc2 commit 19473ee
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 28 deletions.
46 changes: 32 additions & 14 deletions apps/vs-code-designer/src/app/commands/workflows/exportLogicApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,22 @@ interface Deployment {
}

class ExportEngine {
private intlText = {
SUCESSFULL_EXPORTED_MESSAGE: localize('workflowsExportedSuccessfully', 'The selected workflows exported successfully.'),
DONE: localize('done', 'Done.'),
DEPLOYING_CONNECTIONS: localize('deployConnections', 'Deploying connections ...'),
DOWNLOADING_PACKAGE: localize('downloadingPackage', 'Downloading package ...'),
UNZIP_PACKAGE: localize('unzipPackage', 'Unzipping package ...'),
FETCH_CONNECTION: localize('fetchConnectionKeys', 'Retrieving connection keys ...'),
UPDATE_FILES: localize('updateFiles', 'Updating parameters and settings ...'),
};

private finalStatus = {
InProgress: 'InProgress',
Succeeded: 'Succeeded',
Failed: 'Failed',
};

public constructor(
private getAccessToken: () => string,
private packageUrl: string,
Expand All @@ -64,31 +80,32 @@ class ExportEngine {

public async export(): Promise<void> {
try {
this.setFinalStatus('InProgress');
this.addStatus(localize('downloadPackage', 'Downloading package ...'));
this.setFinalStatus(this.finalStatus.InProgress);
this.addStatus(this.intlText.DOWNLOADING_PACKAGE);
const flatFile = await axios.get(this.packageUrl, {
responseType: 'arraybuffer',
responseEncoding: 'binary',
});

const buffer = Buffer.from(flatFile.data);
this.addStatus(localize('done', 'Done.'));
this.addStatus(localize('unzipPackage', 'Unzipping package ...'));
this.addStatus(this.intlText.DONE);
this.addStatus(this.intlText.UNZIP_PACKAGE);
const zip = new AdmZip(buffer);
zip.extractAllTo(/*target path*/ this.targetDirectory, /*overwrite*/ true);
this.addStatus(localize('done', 'Done.'));
this.addStatus(this.intlText.DONE);

const templatePath = `${this.targetDirectory}/.development/deployment/LogicAppStandardConnections.template.json`;

const templateExists = await fse.pathExists(templatePath);
if (!this.resourceGroupName || !templateExists) {
this.setFinalStatus('Succeeded');
this.setFinalStatus(this.finalStatus.Succeeded);
this.addStatus(this.intlText.SUCESSFULL_EXPORTED_MESSAGE);
const uri: vscode.Uri = vscode.Uri.file(this.targetDirectory);
vscode.commands.executeCommand('vscode.openFolder', uri, { forceNewWindow: true });
return;
}

this.addStatus(localize('deployConnections', 'Deploying connections ...'));
this.addStatus(this.intlText.DEPLOYING_CONNECTIONS);

const connectionsTemplate = await fse.readJSON(templatePath);
const parametersFile = await fse.readJSON(`${this.targetDirectory}/parameters.json`);
Expand All @@ -101,17 +118,18 @@ class ExportEngine {
}

const output = await this.deployConnectionsTemplate(connectionsTemplate);
this.addStatus(localize('done', 'Done.'));
this.addStatus(this.intlText.DONE);

await this.fetchConnectionKeys(output);
await this.updateParametersAndSettings(output, parametersFile, localSettingsFile);

this.setFinalStatus('Succeeded');
this.setFinalStatus(this.finalStatus.Succeeded);
this.addStatus(this.intlText.SUCESSFULL_EXPORTED_MESSAGE);
const uri: vscode.Uri = vscode.Uri.file(this.targetDirectory);
vscode.commands.executeCommand('vscode.openFolder', uri, { forceNewWindow: true });
} catch (error) {
this.addStatus(localize('exportFailed', 'Export failed. {0}', error?.message ?? ''));
this.setFinalStatus('Failed');
this.setFinalStatus(this.finalStatus.Failed);
}
}

Expand Down Expand Up @@ -205,12 +223,12 @@ class ExportEngine {
}

private async fetchConnectionKeys(output: ConnectionsDeploymentOutput): Promise<void> {
this.addStatus(localize('fetchConnectionKeys', 'Retrieving connection keys ...'));
this.addStatus(this.intlText.FETCH_CONNECTION);
for (const connectionKey of Object.keys(output?.connections?.value || {})) {
const connectionItem = output.connections.value[connectionKey];
connectionItem.authKey = await this.getConnectionKey(connectionItem.connectionId);
}
this.addStatus(localize('done', 'Done.'));
this.addStatus(this.intlText.DONE);
}

private async getConnectionKey(connectionId: string): Promise<string> {
Expand Down Expand Up @@ -260,7 +278,7 @@ class ExportEngine {
parametersFile: any,
localSettingsFile: any
): Promise<void> {
this.addStatus(localize('updateFiles', 'Updating parameters and settings ...'));
this.addStatus(this.intlText.UPDATE_FILES);

const { value } = output.connections;
for (const key of Object.keys(value)) {
Expand All @@ -278,7 +296,7 @@ class ExportEngine {

writeFileSync(`${this.targetDirectory}/parameters.json`, JSON.stringify(parametersFile, null, 4));
writeFileSync(`${this.targetDirectory}/local.settings.json`, JSON.stringify(localSettingsFile, null, 4));
this.addStatus(localize('done', 'Done.'));
this.addStatus(this.intlText.DONE);
}
}

Expand Down
8 changes: 8 additions & 0 deletions apps/vs-code-react/src/app/export/export.less
Original file line number Diff line number Diff line change
Expand Up @@ -221,4 +221,12 @@
}
}
}

&-status {
&--item {
display: inline-flex;
align-items: center;
margin: 10px 0;
}
}
}
50 changes: 36 additions & 14 deletions apps/vs-code-react/src/app/export/status/status.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Status as FinalStatus } from '../../../state/WorkflowSlice';
import type { RootState } from '../../../state/store';
import { Text, List } from '@fluentui/react';
import { Text, List, Icon, Spinner, SpinnerSize } from '@fluentui/react';
import { useIntl } from 'react-intl';
import { useSelector } from 'react-redux';

export const Status: React.FC = () => {
const intl = useIntl();
const workflowState = useSelector((state: RootState) => state.workflow);
const statuses = workflowState.statuses ?? [];
const finalStatus = workflowState.finalStatus;

const intlText = {
EXPORT_STATUS_TITLE: intl.formatMessage({
Expand All @@ -16,8 +17,20 @@ export const Status: React.FC = () => {
}),
};

const renderStatus = (status?: string): JSX.Element => {
return <Text block>{status}</Text>;
const renderStatus = (status?: string, index?: number): JSX.Element => {
const icon =
index === statuses.length - 1 && finalStatus !== FinalStatus.Succeeded ? (
<Spinner size={SpinnerSize.small} />
) : (
<Icon iconName={'SkypeCheck'} />
);

return (
<div key={`index-${finalStatus}`} className="msla-export-status--item">
{icon}
<Text style={{ marginLeft: '5px' }}>{status}</Text>
</div>
);
};

return (
Expand All @@ -26,28 +39,37 @@ export const Status: React.FC = () => {
{intlText.EXPORT_STATUS_TITLE}
</Text>
<List items={statuses} onRenderCell={renderStatus} />
<FinalStatusGadget />
<FinalStatusGadget finalStatus={finalStatus} />
</div>
);
};

const FinalStatusGadget: React.FC = () => {
interface FinalStatusGadgetProps {
finalStatus: string | undefined;
}

const FinalStatusGadget: React.FC<FinalStatusGadgetProps> = ({ finalStatus }) => {
const intl = useIntl();
const workflowState = useSelector((state: RootState) => state.workflow);
const status = workflowState.finalStatus;
const { targetDirectory } = workflowState.exportData;

const message = intl.formatMessage({
defaultMessage: 'The selected workflows exported successfully. For next steps, review the ',
description: 'The success message.',
});
const exportNextStepsPath = intl.formatMessage(
{
defaultMessage: 'For next steps, review the {path} file.',
description: 'Message for next steps after export',
},
{
path: `${targetDirectory.path}/.logs/export/README.md`,
}
);

switch (status) {
switch (finalStatus) {
case FinalStatus.Succeeded:
return (
<Text block>
{message} {targetDirectory.path}/.logs/export/README.md
</Text>
<div className="msla-export-status--item">
<Icon iconName={'SkypeCheck'} />
<Text style={{ marginLeft: '5px' }}>{exportNextStepsPath}</Text>
</div>
);
default:
return null;
Expand Down

0 comments on commit 19473ee

Please sign in to comment.