feat(api/client): use server response as flash msg for c# (#51551)

Co-authored-by: Shaun Hamilton <shauhami020@gmail.com>
Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
Tom
2023-09-25 08:24:53 -05:00
committed by GitHub
parent 5cc501bb0e
commit 0f49460547
9 changed files with 109 additions and 55 deletions

View File

@@ -701,7 +701,9 @@ function createMsTrophyChallengeCompleted(app) {
});
if (!msUser || !msUser.msUsername) {
throw new Error('Microsoft username not found.');
return res
.status(403)
.json({ type: 'error', message: 'flash.ms.trophy.err-1' });
}
const { msUsername } = msUser;
@@ -711,15 +713,24 @@ function createMsTrophyChallengeCompleted(app) {
);
if (!challenge) {
throw new Error('Challenge not found');
return res
.status(400)
.json({ type: 'error', message: 'flash.ms.trophy.err-2' });
}
const { msTrophyId = '' } = challenge;
const msTrophyApiUrl = `https://learn.microsoft.com/api/gamestatus/achievements/${msTrophyId}?username=${msUsername}&locale=en-us`;
const msApiRes = await fetch(msTrophyApiUrl);
const msTrophyJson = await msApiRes.json();
if (!msApiRes.ok) {
throw new Error('Unable to validate trophy');
if (!msApiRes.ok || msTrophyJson.awardType !== 'Trophy') {
return res.status(403).json({
type: 'error',
message: 'flash.ms.trophy.err-3',
variables: {
msUsername
}
});
}
const completedChallenge = pick(body, ['id']);
@@ -751,9 +762,10 @@ function createMsTrophyChallengeCompleted(app) {
});
});
} catch (e) {
log(e);
return res.status(500).json({
type: 'error',
message: e.message
message: 'flash.ms.trophy.err-4'
});
}
};

View File

@@ -123,9 +123,10 @@ function createPostMsUsername(app) {
const { msTranscriptUrl } = req.body;
if (!msTranscriptUrl) {
return res
.status(400)
.send('Please include a Microsoft transcript URL in request');
return res.status(400).json({
type: 'error',
message: 'flash.ms.transcript.link-err-1'
});
}
const msTranscriptId = msTranscriptUrl.split('/').pop();
@@ -135,19 +136,17 @@ function createPostMsUsername(app) {
const msApiRes = await fetch(msTranscriptApiUrl);
if (!msApiRes.ok) {
res.status(500);
throw new Error(
'An error occurred trying to get your Microsoft transcript'
);
return res
.status(404)
.json({ type: 'error', message: 'flash.ms.transcript.link-err-2' });
}
const { userName } = await msApiRes.json();
if (!userName) {
res.status(500);
throw new Error(
'An error occured trying to link your Microsoft account'
);
return res
.status(500)
.json({ type: 'error', message: 'flash.ms.transcript.link-err-3' });
}
// Don't create if username is used by another fCC account
@@ -156,7 +155,9 @@ function createPostMsUsername(app) {
});
if (usernameUsed) {
throw new Error('That username is already used');
return res
.status(403)
.json({ type: 'error', message: 'flash.ms.transcript.link-err-4' });
}
await MsUsername.destroyAll({ userId: req.user.id });
@@ -169,25 +170,27 @@ function createPostMsUsername(app) {
});
if (!newMsUsername?.id) {
res.status(500);
throw new Error(
'An error occured trying to link your Microsoft account'
);
return res
.status(500)
.json({ type: 'error', message: 'flash.ms.transcript.link-err-5' });
}
return res.json({ msUsername: userName });
} catch (e) {
log(e);
return res.send(e.message);
return res
.status(500)
.json({ type: 'error', message: 'flash.ms.transcript.link-err-6' });
}
};
}
function deleteMsUsernameResponse(req, res) {
if (!req.msUsernameDeleted) {
return res
.status(500)
.send('An error occurred trying to unlink your Microsoft username');
return res.status(500).json({
type: 'error',
message: 'flash.ms.transcript.unlink-err'
});
}
return res.send({ msUsername: null });

View File

@@ -718,12 +718,26 @@
"challenge-submit-too-big": "Sorry, you cannot submit your code. Your code is {{user-size}} bytes. We allow a maximum of {{max-size}} bytes. Please make your code smaller and try again or request assistance on https://forum.freecodecamp.org",
"invalid-update-flag": "You are attempting to access forbidden resources. Please request assistance on https://forum.freecodecamp.org if this is a valid request.",
"generate-exam-error": "An error occurred trying to generate your exam.",
"ms-trophy-err": "We were unable to verify your trophy from Microsoft's learning platform.",
"ms-trophy-verified": "Your trophy from Microsoft's learning platform was verified.",
"ms-linked": "Your Microsoft username been linked to your freeCodeCamp account.",
"ms-link-err": "An error occurred trying to link your Microsoft username to your freeCodeCamp account.",
"ms-unlinked": "The link to your Microsoft username has been removed.",
"ms-unlink-err": "An error occurred trying to remove the link to your Microsoft username."
"ms": {
"transcript": {
"link-err-1": "Please include a Microsoft transcript URL in the request.",
"link-err-2": "Something went wrong trying to get your transcript from Microsoft.",
"link-err-3": "A username was not found in your Microsoft transcript.",
"link-err-4": "That Microsoft username is being used by another freeCodeCamp account.",
"link-err-5": "Something went wrong trying to save your Microsoft account.",
"link-err-6": "Something went wrong trying to link your Microsoft username to your freeCodeCamp account.",
"linked": "Your Microsoft username has been linked to your freeCodeCamp account.",
"unlinked": "The link to your Microsoft username has been removed.",
"unlink-err": "Something went wrong trying to remove the link to your Microsoft username."
},
"trophy": {
"err-1": "We could not find a Microsoft username associated with your freeCodeCamp account.",
"err-2": "You are trying to submit a challenge that does not appear to be a trophy challenge.",
"err-3": "It appears that the Microsoft user \"{{msUsername}}\" has not earned this trophy.",
"err-4": "Something went wrong trying to verify your trophy. Please check and try again.",
"verified": "Your trophy from Microsoft's learning platform was verified."
}
}
},
"validation": {
"max-characters": "There is a maximum limit of 288 characters, you have {{charsLeft}} left",

View File

@@ -20,11 +20,13 @@ function Flash({ flashMessage, removeFlashMessage }: FlashProps): JSX.Element {
removeFlashMessage();
}
const flashStyle = type === 'error' ? 'danger' : type;
return (
<TransitionGroup>
<CSSTransition classNames='flash-message' key={id} timeout={500}>
<Alert
bsStyle={type}
bsStyle={flashStyle}
className='flash-message'
closeLabel={t('buttons.close')}
onDismiss={handleClose}

View File

@@ -18,12 +18,20 @@ export enum FlashMessages {
IncompleteSteps = 'flash.incomplete-steps',
LocalCodeSaved = 'flash.local-code-saved',
LocalCodeSaveError = 'flash.local-code-save-error',
MsLinked = 'flash.ms-linked',
MsLinkErr = 'flash.ms-link-err',
MsTrophyErr = 'flash.ms-trophy-err',
MsTrophyVerified = 'flash.ms-trophy-verified',
MsUnlinked = 'flash.ms-unlinked',
MsUnlinkErr = 'flash.ms-unlink-err',
MsTranscriptErr1 = 'flash.ms.transcript.link-err-1',
MsTranscriptErr2 = 'flash.ms.transcript.link-err-2',
MsTranscriptErr3 = 'flash.ms.transcript.link-err-3',
MsTranscriptErr4 = 'flash.ms.transcript.link-err-4',
MsTranscriptErr5 = 'flash.ms.transcript.link-err-5',
MsTranscriptErr6 = 'flash.ms.transcript.link-err-6',
MsTranscriptLinked = 'flash.ms.transcript.linked',
MsTranscriptUnlinked = 'flash.ms.transcript.unlinked',
MsTranscriptUnlinkErr = 'flash.ms.transcript.unlink-err',
MsTrophyErr1 = 'flash.ms.trophy.err-1',
MsTrophyErr2 = 'flash.ms.trophy.err-2',
MsTrophyErr3 = 'flash.ms.trophy.err-3',
MsTrophyErr4 = 'flash.ms.trophy.err-4',
MsTrophyVerified = 'flash.ms.trophy.verified',
NameNeeded = 'flash.name-needed',
None = '',
NotEligible = 'flash.not-eligible',

View File

@@ -8,19 +8,19 @@ import { setMsUsername, setIsProcessing } from './actions';
const message = {
linked: {
type: 'success',
message: FlashMessages.MsLinked
message: FlashMessages.MsTranscriptLinked
},
linkErr: {
type: 'danger',
message: FlashMessages.MsLinkErr
message: FlashMessages.MsTranscriptErr6
},
unlinked: {
type: 'info',
message: FlashMessages.MsUnlinked
message: FlashMessages.MsTranscriptUnlinked
},
unlinkErr: {
type: 'danger',
message: FlashMessages.MsUnlinkErr
message: FlashMessages.MsTranscriptUnlinkErr
}
};
@@ -28,12 +28,14 @@ function* linkMsUsernameSaga({ payload: { msTranscriptUrl } }) {
try {
const { data } = yield call(postMsUsername, { msTranscriptUrl });
if (data && Object.prototype.hasOwnProperty.call(data, 'msUsername')) {
yield put(setIsProcessing(false));
if (data?.message) {
yield put(createFlashMessage(data));
} else if (data?.msUsername) {
yield put(setMsUsername(data.msUsername));
yield put(setIsProcessing(false));
yield put(createFlashMessage(message.linked));
} else {
yield put(setIsProcessing(false));
yield put(createFlashMessage(message.linkErr));
}
} catch {
@@ -46,7 +48,12 @@ function* unlinkMsUsernameSaga() {
try {
const { data } = yield call(deleteMsUsername);
if (data && Object.prototype.hasOwnProperty.call(data, 'msUsername')) {
if (data?.message) {
yield put(createFlashMessage(data));
} else if (
data &&
Object.prototype.hasOwnProperty.call(data, 'msUsername')
) {
yield put(setMsUsername(data.msUsername));
yield put(createFlashMessage(message.unlinked));
} else {

View File

@@ -13,7 +13,6 @@ import {
import { createFlashMessage } from '../../../components/Flash/redux';
import {
standardErrorMessage,
msTrophyError,
msTrophyVerified
} from '../../../utils/error-messages';
import {
@@ -58,7 +57,7 @@ function postChallenge(update, username) {
const saveChallenge = postUpdate$(update).pipe(
retry(3),
switchMap(({ data }) => {
const { savedChallenges, points, type, examResults } = data;
const { savedChallenges, points, message, examResults } = data;
const payloadWithClientProperties = {
...omit(update.payload, ['files'])
};
@@ -85,8 +84,8 @@ function postChallenge(update, username) {
submitChallengeComplete()
];
if (challengeType === challengeTypes.msTrophy && type === 'error') {
actions = [createFlashMessage(msTrophyError), submitChallengeError()];
if (message && challengeType === challengeTypes.msTrophy) {
actions = [createFlashMessage(data), submitChallengeError()];
} else if (challengeType === challengeTypes.msTrophy) {
actions.push(createFlashMessage(msTrophyVerified));
}

View File

@@ -5,11 +5,6 @@ export const standardErrorMessage = {
message: FlashMessages.WentWrong
};
export const msTrophyError = {
type: 'danger',
message: FlashMessages.MsTrophyErr
};
export const reallyWeirdErrorMessage = {
type: 'danger',
message: FlashMessages.ReallyWeird

View File

@@ -33,6 +33,20 @@ const toneUrls = {
[FlashMessages.IncompleteSteps]: TRY_AGAIN,
[FlashMessages.LocalCodeSaved]: CHAL_COMP,
[FlashMessages.LocalCodeSaveError]: TRY_AGAIN,
[FlashMessages.MsTranscriptErr1]: TRY_AGAIN,
[FlashMessages.MsTranscriptErr2]: TRY_AGAIN,
[FlashMessages.MsTranscriptErr3]: TRY_AGAIN,
[FlashMessages.MsTranscriptErr4]: TRY_AGAIN,
[FlashMessages.MsTranscriptErr5]: TRY_AGAIN,
[FlashMessages.MsTranscriptErr6]: TRY_AGAIN,
[FlashMessages.MsTranscriptLinked]: CHAL_COMP,
[FlashMessages.MsTranscriptUnlinked]: CHAL_COMP,
[FlashMessages.MsTranscriptUnlinkErr]: TRY_AGAIN,
[FlashMessages.MsTrophyErr1]: TRY_AGAIN,
[FlashMessages.MsTrophyErr2]: TRY_AGAIN,
[FlashMessages.MsTrophyErr3]: TRY_AGAIN,
[FlashMessages.MsTrophyErr4]: TRY_AGAIN,
[FlashMessages.MsTrophyVerified]: CHAL_COMP,
[FlashMessages.NameNeeded]: TRY_AGAIN,
// [FlashMessages.None]: '',
[FlashMessages.NotEligible]: TRY_AGAIN,