Skip to content

Commit

Permalink
fix: Display error w/o token on auth failure (#6576)
Browse files Browse the repository at this point in the history
* Display error w/o token on auth failure

* Fixed mounted checks

Co-authored-by: Ben Brown <benbro@microsoft.com>
  • Loading branch information
GeoffCoxMSFT and benbrown authored Mar 31, 2021
1 parent 586d33d commit c96f912
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 29 deletions.
19 changes: 16 additions & 3 deletions extensions/azurePublish/src/components/api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.
/* eslint-disable no-underscore-dangle */
import axios from 'axios';
import formatMessage from 'format-message';
import { SubscriptionClient } from '@azure/arm-subscriptions';
import { Subscription } from '@azure/arm-subscriptions/esm/models';
import { ResourceManagementClient } from '@azure/arm-resources';
Expand All @@ -27,6 +28,11 @@ import * as Images from './images';

const logger = debug('composer:extension:azureProvision');

/**
* Retrieves the list of subscriptions from Azure
* @param token The authentication token
* @returns The list of subscriptions or throws
*/
export const getSubscriptions = async (token: string): Promise<Array<Subscription>> => {
const tokenCredentials = new TokenCredentials(token);
try {
Expand All @@ -37,15 +43,22 @@ export const getSubscriptions = async (token: string): Promise<Array<Subscriptio
status: AzureAPIStatus.ERROR,
message: subscriptionsResult._response.bodyAsText,
});
return [];
throw new Error(subscriptionsResult._response.bodyAsText);
}
return subscriptionsResult._response.parsedBody;
} catch (err) {
let message = JSON.stringify(err, Object.getOwnPropertyNames(err));
if (err?.code === 12 && err?.message?.match(/Bearer/gi)) {
message = formatMessage(
'There was an authentication problem retrieving subscriptions. Verify your login session has not expired and you have permission to list subscriptions in this account.'
);
}

logger({
status: AzureAPIStatus.ERROR,
message: JSON.stringify(err, Object.getOwnPropertyNames(err)),
message,
});
return [];
throw new Error(message);
}
};

Expand Down
78 changes: 52 additions & 26 deletions extensions/azurePublish/src/components/azureProvisionDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ const AddResourcesSectionName = styled(Text)`
font-size: ${FluentTheme.fonts.mediumPlus.fontSize};
`;

const labelTooltipStyles = {
root: {
userSelect: 'none',
},
};

const iconStyle = (required) => {
return {
root: {
Expand Down Expand Up @@ -124,7 +130,7 @@ const onRenderLabel = (props) => {
{' '}
{props.label}{' '}
</div>
<TooltipHost content={props.ariaLabel}>
<TooltipHost content={props.ariaLabel} styles={labelTooltipStyles}>
<Icon iconName="Info" styles={iconStyle(props.required)} />
</TooltipHost>
</div>
Expand Down Expand Up @@ -229,6 +235,7 @@ export const AzureProvisionDialog: React.FC = () => {
const extensionState = { ...defaultExtensionState, ...getItem(profileName) };

const [subscriptions, setSubscriptions] = useState<Subscription[] | undefined>();
const [subscriptionsErrorMessage, setSubscriptionsErrorMessage] = useState<string>();
const [deployLocations, setDeployLocations] = useState<DeployLocation[]>([]);
const [luisLocations, setLuisLocations] = useState<DeployLocation[]>([]);

Expand Down Expand Up @@ -366,8 +373,10 @@ export const AzureProvisionDialog: React.FC = () => {

const getResources = async () => {
try {
const resources = await getResourceList(currentProjectId(), publishType);
setExtensionResourceOptions(resources);
if (isMounted.current) {
const resources = await getResourceList(currentProjectId(), publishType);
setExtensionResourceOptions(resources);
}
} catch (err) {
// todo: how do we handle API errors in this component
console.log('ERROR', err);
Expand All @@ -376,11 +385,26 @@ export const AzureProvisionDialog: React.FC = () => {

useEffect(() => {
if (token) {
getSubscriptions(token).then((data) => {
if (isMounted.current) {
setSubscriptions(data);
}
});
setSubscriptionsErrorMessage(undefined);
getSubscriptions(token)
.then((data) => {
if (isMounted.current) {
setSubscriptions(data);
if (data.length === 0) {
setSubscriptionsErrorMessage(
formatMessage(
'Your subscription list is empty, please add your subscription, or login with another account.'
)
);
}
}
})
.catch((err) => {
if (isMounted.current) {
setSubscriptionsErrorMessage(err.message);
}
});

getResources();
}
}, [token]);
Expand All @@ -389,14 +413,18 @@ export const AzureProvisionDialog: React.FC = () => {
if (token && currentSubscription) {
try {
const resourceGroups = await getResourceGroups(token, currentSubscription);
setResourceGroups(resourceGroups);
if (isMounted.current) {
setResourceGroups(resourceGroups);

// After the resource groups load, isNewResourceGroupName can be determined
setIsNewResourceGroupName(!resourceGroups?.some((r) => r.name === currentResourceGroupName));
// After the resource groups load, isNewResourceGroupName can be determined
setIsNewResourceGroupName(!resourceGroups?.some((r) => r.name === currentResourceGroupName));
}
} catch (err) {
// todo: how do we handle API errors in this component
console.log('ERROR', err);
setResourceGroups(undefined);
if (isMounted.current) {
setResourceGroups(undefined);
}
}
} else {
setResourceGroups(undefined);
Expand All @@ -407,7 +435,7 @@ export const AzureProvisionDialog: React.FC = () => {
loadResourceGroups();
}, [token, currentSubscription]);

const subscriptionOption = useMemo(() => {
const subscriptionOptions = useMemo(() => {
return subscriptions?.map((t) => ({ key: t.subscriptionId, text: t.displayName }));
}, [subscriptions]);

Expand Down Expand Up @@ -563,6 +591,7 @@ export const AzureProvisionDialog: React.FC = () => {
!currentResourceGroupName ||
!currentHostName ||
!currentLocation ||
subscriptionsErrorMessage ||
errorResourceGroupName ||
errorHostName !== ''
);
Expand Down Expand Up @@ -599,10 +628,12 @@ export const AzureProvisionDialog: React.FC = () => {
</div>
<div style={{ flex: 1, height: '100%' }}>
<Suspense fallback={<Spinner label={formatMessage('Loading')} />}>
{subscriptionOption?.length > 0 && choice.key === 'create' && (
{choice.key === 'create' && (
<form style={{ width: '100%' }}>
<Dropdown
required
ariaLabel={formatMessage(
'The Azure AD directory includes the tenant’s users, groups, and apps and is used to perform identity and access management functions for tenant resources.'
)}
disabled={allTenants.length === 1 || currentConfig?.tenantId}
errorMessage={loginErrorMsg}
label={formatMessage('Azure Directory')}
Expand All @@ -612,22 +643,17 @@ export const AzureProvisionDialog: React.FC = () => {
onChange={(_e, o) => {
setSelectedTenant(o.key as string);
}}
onRenderLabel={onRenderLabel}
/>
<Dropdown
required
ariaLabel={formatMessage('All resources in an Azure subscription are billed together')}
defaultSelectedKey={currentSubscription}
disabled={currentConfig?.subscriptionId}
errorMessage={
currentUser && subscriptionOption?.length < 1
? formatMessage(
'Your subscription list is empty, please add your subscription, or login with another account.'
)
: undefined
}
errorMessage={subscriptionsErrorMessage}
label={formatMessage('Subscription')}
options={subscriptionOption}
options={subscriptionOptions}
placeholder={formatMessage('Select one')}
selectedKey={currentSubscription}
styles={{ root: { paddingBottom: '8px' } }}
onChange={(_e, o: IDropdownOption) => {
setCurrentSubscription(o.key as string);
Expand Down Expand Up @@ -766,7 +792,7 @@ export const AzureProvisionDialog: React.FC = () => {
items={optionalListItems}
selectedKeys={selectedResourceKeys}
onSelectionChanged={(keys) => {
const newSelection = listItems.filter((item) => item.required === true || keys.includes(item.key));
const newSelection = optionalListItems.filter((item) => keys.includes(item.key));
setEnabledResources(newSelection);
}}
/>
Expand Down Expand Up @@ -885,7 +911,7 @@ export const AzureProvisionDialog: React.FC = () => {
onClick={() => {
setPage(PageTypes.ReviewResource);
setTitle(DialogTitle.REVIEW);
let selectedResources = enabledResources.slice();
let selectedResources = requireResources.concat(enabledResources);
selectedResources = selectedResources.map((item) => {
let region = currentConfig?.region || currentLocation;
if (item.key.includes('luis')) {
Expand Down

0 comments on commit c96f912

Please sign in to comment.