Merge pull request #10636 from BerkeleyTrue/fix/beta-in-production
Fix(challenges): beta challenges in production
This commit is contained in:
@ -1,4 +1,5 @@
|
|||||||
import { Observable } from 'rx';
|
import { Observable } from 'rx';
|
||||||
|
import debug from 'debug';
|
||||||
import { challengeSelector } from './selectors';
|
import { challengeSelector } from './selectors';
|
||||||
import types from './types';
|
import types from './types';
|
||||||
import {
|
import {
|
||||||
@ -7,13 +8,18 @@ import {
|
|||||||
updateCurrentChallenge,
|
updateCurrentChallenge,
|
||||||
initMap
|
initMap
|
||||||
} from './actions';
|
} from './actions';
|
||||||
import { createMapUi } from '../utils';
|
import {
|
||||||
|
createMapUi,
|
||||||
|
filterComingSoonBetaFromEntities
|
||||||
|
} from '../utils';
|
||||||
import {
|
import {
|
||||||
delayedRedirect,
|
delayedRedirect,
|
||||||
createErrorObservable
|
createErrorObservable
|
||||||
} from '../../../redux/actions';
|
} from '../../../redux/actions';
|
||||||
import createNameIdMap from '../../../../utils/create-name-id-map';
|
import createNameIdMap from '../../../../utils/create-name-id-map';
|
||||||
|
|
||||||
|
const isDev = debug.enabled('fcc:*');
|
||||||
|
|
||||||
const { fetchChallenge, fetchChallenges, replaceChallenge } = types;
|
const { fetchChallenge, fetchChallenges, replaceChallenge } = types;
|
||||||
|
|
||||||
export default function fetchChallengesSaga(action$, getState, { services }) {
|
export default function fetchChallengesSaga(action$, getState, { services }) {
|
||||||
@ -58,12 +64,16 @@ export default function fetchChallengesSaga(action$, getState, { services }) {
|
|||||||
redirect ? delayedRedirect(redirect) : null
|
redirect ? delayedRedirect(redirect) : null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const filteredEntities = filterComingSoonBetaFromEntities(
|
||||||
|
entities,
|
||||||
|
isDev
|
||||||
|
);
|
||||||
return Observable.of(
|
return Observable.of(
|
||||||
fetchChallengesCompleted(
|
fetchChallengesCompleted(
|
||||||
createNameIdMap(entities),
|
createNameIdMap(filteredEntities),
|
||||||
result
|
result
|
||||||
),
|
),
|
||||||
initMap(createMapUi(entities, result)),
|
initMap(createMapUi(filteredEntities, result)),
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.catch(createErrorObservable);
|
.catch(createErrorObservable);
|
||||||
|
@ -78,7 +78,11 @@ export function getNextChallenge(
|
|||||||
// skip is used to skip isComingSoon challenges
|
// skip is used to skip isComingSoon challenges
|
||||||
block.challenges[ index + 1 + skip ]
|
block.challenges[ index + 1 + skip ]
|
||||||
];
|
];
|
||||||
if (!isDev && nextChallenge && nextChallenge.isComingSoon) {
|
if (
|
||||||
|
!isDev &&
|
||||||
|
nextChallenge &&
|
||||||
|
(nextChallenge.isComingSoon || nextChallenge.isBeta)
|
||||||
|
) {
|
||||||
// if we find a next challenge and it is a coming soon
|
// if we find a next challenge and it is a coming soon
|
||||||
// recur with plus one to skip this challenge
|
// recur with plus one to skip this challenge
|
||||||
return getNextChallenge(current, entities, { isDev, skip: skip + 1 });
|
return getNextChallenge(current, entities, { isDev, skip: skip + 1 });
|
||||||
@ -248,6 +252,31 @@ export function getMouse(e, [dx, dy]) {
|
|||||||
return [pageX - dx, pageY - dy];
|
return [pageX - dx, pageY - dy];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function filterCommingSoonBetaChallenge(
|
||||||
|
isDev = false,
|
||||||
|
{ isComingSoon, isBeta }
|
||||||
|
) {
|
||||||
|
return !(isComingSoon || isBeta) ||
|
||||||
|
isDev;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function filterComingSoonBetaFromEntities(
|
||||||
|
{ challenge: challengeMap, ...rest },
|
||||||
|
isDev = false
|
||||||
|
) {
|
||||||
|
const filter = filterCommingSoonBetaChallenge.bind(null, isDev);
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
challenge: Object.keys(challengeMap)
|
||||||
|
.map(dashedName => challengeMap[dashedName])
|
||||||
|
.filter(filter)
|
||||||
|
.reduce((challengeMap, challenge) => {
|
||||||
|
challengeMap[challenge.dashedName] = challenge;
|
||||||
|
return challengeMap;
|
||||||
|
}, {})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// interface Node {
|
// interface Node {
|
||||||
// isHidden: Boolean,
|
// isHidden: Boolean,
|
||||||
// children: Void|[ ...Node ],
|
// children: Void|[ ...Node ],
|
||||||
|
@ -4,6 +4,8 @@ import {
|
|||||||
getNextChallenge,
|
getNextChallenge,
|
||||||
getFirstChallengeOfNextBlock,
|
getFirstChallengeOfNextBlock,
|
||||||
getFirstChallengeOfNextSuperBlock,
|
getFirstChallengeOfNextSuperBlock,
|
||||||
|
filterCommingSoonBetaChallenge,
|
||||||
|
filterComingSoonBetaFromEntities,
|
||||||
createMapUi,
|
createMapUi,
|
||||||
traverseMapUi,
|
traverseMapUi,
|
||||||
getNode,
|
getNode,
|
||||||
@ -18,7 +20,7 @@ import {
|
|||||||
|
|
||||||
test('common/app/routes/challenges/utils', function(t) {
|
test('common/app/routes/challenges/utils', function(t) {
|
||||||
t.test('getNextChallenge', t => {
|
t.test('getNextChallenge', t => {
|
||||||
t.plan(5);
|
t.plan(7);
|
||||||
t.test('should return falsey when current challenge is not found', t => {
|
t.test('should return falsey when current challenge is not found', t => {
|
||||||
t.plan(1);
|
t.plan(1);
|
||||||
const entities = {
|
const entities = {
|
||||||
@ -166,6 +168,80 @@ test('common/app/routes/challenges/utils', function(t) {
|
|||||||
comingSoon
|
comingSoon
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
t.test('should skip isBeta challenge', t => {
|
||||||
|
t.plan(1);
|
||||||
|
const currentChallenge = {
|
||||||
|
dashedName: 'current-challenge',
|
||||||
|
block: 'current-block'
|
||||||
|
};
|
||||||
|
const beta = {
|
||||||
|
dashedName: 'beta-challenge',
|
||||||
|
isBeta: true,
|
||||||
|
block: 'current-block'
|
||||||
|
};
|
||||||
|
const nextChallenge = {
|
||||||
|
dashedName: 'next-challenge',
|
||||||
|
block: 'current-block'
|
||||||
|
};
|
||||||
|
const shouldBeNext = getNextChallenge(
|
||||||
|
'current-challenge',
|
||||||
|
{
|
||||||
|
challenge: {
|
||||||
|
'current-challenge': currentChallenge,
|
||||||
|
'next-challenge': nextChallenge,
|
||||||
|
'beta-challenge': beta,
|
||||||
|
'beta-challenge2': beta
|
||||||
|
},
|
||||||
|
block: {
|
||||||
|
'current-block': {
|
||||||
|
challenges: [
|
||||||
|
'current-challenge',
|
||||||
|
'beta-challenge',
|
||||||
|
'beta-challenge2',
|
||||||
|
'next-challenge'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
t.isEqual(shouldBeNext, nextChallenge);
|
||||||
|
});
|
||||||
|
t.test('should not skip isBeta challenge if in dev', t => {
|
||||||
|
t.plan(1);
|
||||||
|
const currentChallenge = {
|
||||||
|
dashedName: 'current-challenge',
|
||||||
|
block: 'current-block'
|
||||||
|
};
|
||||||
|
const beta = {
|
||||||
|
dashedName: 'beta-challenge',
|
||||||
|
isBeta: true,
|
||||||
|
block: 'current-block'
|
||||||
|
};
|
||||||
|
const nextChallenge = {
|
||||||
|
dashedName: 'next-challenge',
|
||||||
|
block: 'current-block'
|
||||||
|
};
|
||||||
|
const entities = {
|
||||||
|
challenge: {
|
||||||
|
'current-challenge': currentChallenge,
|
||||||
|
'next-challenge': nextChallenge,
|
||||||
|
'beta-challenge': beta
|
||||||
|
},
|
||||||
|
block: {
|
||||||
|
'current-block': {
|
||||||
|
challenges: [
|
||||||
|
'current-challenge',
|
||||||
|
'beta-challenge',
|
||||||
|
'next-challenge'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
t.isEqual(
|
||||||
|
getNextChallenge('current-challenge', entities, { isDev: true }),
|
||||||
|
beta
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
t.test('getFirstChallengeOfNextBlock', t => {
|
t.test('getFirstChallengeOfNextBlock', t => {
|
||||||
@ -784,6 +860,91 @@ test('common/app/routes/challenges/utils', function(t) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
t.test('filterCommingSoonBetaChallenge', t => {
|
||||||
|
t.plan(4);
|
||||||
|
t.test('should return true when not coming-soon/beta', t => {
|
||||||
|
let isDev;
|
||||||
|
t.ok(filterCommingSoonBetaChallenge(isDev, {}));
|
||||||
|
t.ok(filterCommingSoonBetaChallenge(true, {}));
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.test('should return false when isComingSoon', t => {
|
||||||
|
let isDev;
|
||||||
|
t.notOk(filterCommingSoonBetaChallenge(isDev, { isComingSoon: true }));
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.test('should return false when isBeta', t => {
|
||||||
|
let isDev;
|
||||||
|
t.notOk(filterCommingSoonBetaChallenge(isDev, { isBeta: true }));
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.test('should always return true when in dev', t => {
|
||||||
|
let isDev = true;
|
||||||
|
t.ok(filterCommingSoonBetaChallenge(isDev, { isBeta: true }));
|
||||||
|
t.ok(filterCommingSoonBetaChallenge(isDev, { isComingSoon: true }));
|
||||||
|
t.ok(filterCommingSoonBetaChallenge(
|
||||||
|
isDev,
|
||||||
|
{ isBeta: true, isCompleted: true }
|
||||||
|
));
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
t.test('filterComingSoonBetaFromEntities', t => {
|
||||||
|
t.plan(2);
|
||||||
|
t.test('should filter isBeta|coming-soon by default', t => {
|
||||||
|
t.plan(2);
|
||||||
|
const normalChallenge = { dashedName: 'normal-challenge' };
|
||||||
|
const entities = {
|
||||||
|
challenge: {
|
||||||
|
'coming-soon': {
|
||||||
|
isComingSoon: true
|
||||||
|
},
|
||||||
|
'is-beta': {
|
||||||
|
isBeta: true
|
||||||
|
},
|
||||||
|
[normalChallenge.dashedName]: normalChallenge
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const actual = filterComingSoonBetaFromEntities(entities);
|
||||||
|
t.isEqual(
|
||||||
|
Object.keys(actual.challenge).length,
|
||||||
|
1,
|
||||||
|
'did not filter the correct amount of challenges'
|
||||||
|
);
|
||||||
|
t.isEqual(
|
||||||
|
actual.challenge[normalChallenge.dashedName],
|
||||||
|
normalChallenge,
|
||||||
|
'did not return the correct challenge'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
t.test('should not filter isBeta|coming-soon when isDev', t => {
|
||||||
|
t.plan(1);
|
||||||
|
const normalChallenge = { dashedName: 'normal-challenge' };
|
||||||
|
const entities = {
|
||||||
|
challenge: {
|
||||||
|
'coming-soon': {
|
||||||
|
dashedName: 'coming-soon',
|
||||||
|
isComingSoon: true
|
||||||
|
},
|
||||||
|
'is-beta': {
|
||||||
|
dashedName: 'is-beta',
|
||||||
|
isBeta: true
|
||||||
|
},
|
||||||
|
'is-both': {
|
||||||
|
dashedName: 'is-both',
|
||||||
|
isBeta: true
|
||||||
|
},
|
||||||
|
[normalChallenge.dashedName]: normalChallenge
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const actual = filterComingSoonBetaFromEntities(entities, true);
|
||||||
|
t.isEqual(
|
||||||
|
Object.keys(actual.challenge).length,
|
||||||
|
4,
|
||||||
|
'filtered challenges'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
t.test('createMapUi', t => {
|
t.test('createMapUi', t => {
|
||||||
t.plan(3);
|
t.plan(3);
|
||||||
t.test('should return an `{}` when proper args not supplied', t => {
|
t.test('should return an `{}` when proper args not supplied', t => {
|
||||||
|
@ -8,10 +8,15 @@ const isBeta = !!process.env.BETA;
|
|||||||
const challengesRegex = /^(bonfire|waypoint|zipline|basejump|checkpoint)/i;
|
const challengesRegex = /^(bonfire|waypoint|zipline|basejump|checkpoint)/i;
|
||||||
const log = debug('fcc:services:map');
|
const log = debug('fcc:services:map');
|
||||||
|
|
||||||
function shouldNotFilterComingSoon({ isComingSoon, isBeta: challengeIsBeta }) {
|
// if challenge is not isComingSoon or isBeta => load
|
||||||
return isDev ||
|
// if challenge is commingSoon we are in beta||dev => load
|
||||||
!isComingSoon ||
|
// if challenge is beta and we are in beta||dev => load
|
||||||
(isBeta && challengeIsBeta);
|
// else hide
|
||||||
|
function loadComingSoonOrBetaChallenge({
|
||||||
|
isComingSoon,
|
||||||
|
isBeta: challengeIsBeta
|
||||||
|
}) {
|
||||||
|
return !(isComingSoon || challengeIsBeta) || isDev || isBeta;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFirstChallenge(challengeMap$) {
|
function getFirstChallenge(challengeMap$) {
|
||||||
@ -42,7 +47,7 @@ function getChallengeAndBlock(
|
|||||||
if (
|
if (
|
||||||
!block ||
|
!block ||
|
||||||
!challenge ||
|
!challenge ||
|
||||||
!shouldNotFilterComingSoon(challenge)
|
!loadComingSoonOrBetaChallenge(challenge)
|
||||||
) {
|
) {
|
||||||
return getChallengeByDashedName(
|
return getChallengeByDashedName(
|
||||||
challengeDashedName,
|
challengeDashedName,
|
||||||
@ -80,7 +85,7 @@ function getChallengeByDashedName(dashedName, challengeMap$, lang) {
|
|||||||
.map(key => challengeMap[key]);
|
.map(key => challengeMap[key]);
|
||||||
})
|
})
|
||||||
.filter(challenge => {
|
.filter(challenge => {
|
||||||
return shouldNotFilterComingSoon(challenge) &&
|
return loadComingSoonOrBetaChallenge(challenge) &&
|
||||||
testChallengeName.test(challenge.name);
|
testChallengeName.test(challenge.name);
|
||||||
})
|
})
|
||||||
.last({ defaultValue: null })
|
.last({ defaultValue: null })
|
||||||
|
Reference in New Issue
Block a user