Skip to content

Commit

Permalink
Merge pull request #20923 from code-dot-org/dtp_candidate_0a203be5
Browse files Browse the repository at this point in the history
DTP (Test > Production: 0a203be)
  • Loading branch information
aoby committed Feb 27, 2018
2 parents 565f5b5 + 36905c5 commit 6d23b1c
Show file tree
Hide file tree
Showing 74 changed files with 991 additions and 224 deletions.
10 changes: 5 additions & 5 deletions .circleci/Dockerfile
Expand Up @@ -46,15 +46,15 @@ RUN apt-get update && \
python python-dev

# install ruby
RUN wget https://cache.ruby-lang.org/pub/ruby/2.5/ruby-2.5.0.tar.gz && \
tar -xzvf ruby-2.5.0.tar.gz && \
rm ruby-2.5.0.tar.gz && \
cd ruby-2.5.0 && \
RUN wget https://cache.ruby-lang.org/pub/ruby/2.2/ruby-2.2.3.tar.gz && \
tar -xzvf ruby-2.2.3.tar.gz && \
rm ruby-2.2.3.tar.gz && \
cd ruby-2.2.3 && \
./configure && \
make -j"$(nproc)" && \
make install && \
cd .. && \
rm -r ruby-2.5.0
rm -r ruby-2.2.3

# install bundler
RUN gem install bundler && \
Expand Down
2 changes: 1 addition & 1 deletion .circleci/config.yml
Expand Up @@ -10,7 +10,7 @@ jobs:
build:
parallelism: 2
docker:
- image: wjordan/code-dot-org:ruby-2.5
- image: wjordan/code-dot-org:trusty
environment:
RAILS_ENV: test
RACK_ENV: test
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Expand Up @@ -326,7 +326,7 @@ GEM
redis (~> 3.2)
faraday (0.9.2)
multipart-post (>= 1.2, < 3)
ffi (1.9.21)
ffi (1.9.23)
firebase (0.2.6)
httpclient
json
Expand Down
2 changes: 2 additions & 0 deletions apps/src/code-studio/pd/permission.js
Expand Up @@ -14,6 +14,8 @@ export default class Permission {

this.isWorkshopAdmin = this.hasPermission('workshop_admin');
this.isFacilitator = this.hasPermission('facilitator');
// CSF Facilitators can create workshops, other facilitators cannot
this.isCsfFacilitator = this.hasPermission('csf_facilitator');
this.isOrganizer = this.hasPermission('workshop_organizer');
this.isPartner = this.hasPermission('partner');
}
Expand Down
Expand Up @@ -33,6 +33,7 @@ import {
DATE_FORMAT,
DATETIME_FORMAT
} from '../workshopConstants';
import Permission from '../../permission';

const styles = {
readOnlyInput: {
Expand Down Expand Up @@ -80,6 +81,7 @@ export default class WorkshopForm extends React.Component {
constructor(props) {
super(props);
this.state = this.computeInitialState(props);
this.permission = new Permission();
}

computeInitialState(props) {
Expand Down Expand Up @@ -121,9 +123,10 @@ export default class WorkshopForm extends React.Component {
);
initialState.sessions = this.prepareSessionsForForm(props.workshop.sessions);
this.loadAvailableFacilitators(props.workshop.course);
this.loadRegionalPartners();
}

this.loadRegionalPartners();

return initialState;
}

Expand Down Expand Up @@ -367,7 +370,8 @@ export default class WorkshopForm extends React.Component {
onChange={this.handleFieldChange}
style={this.getInputStyle()}
value={this.state.regional_partner_id || ''}
disabled={this.props.readOnly}
// Facilitators (who are not organizers, partners, nor admins) cannot edit this field
disabled={this.props.readOnly || (!this.permission.isWorkshopAdmin && !this.permission.isOrganizer && !this.permission.isPartner)}
>
<option value="">None</option>
{
Expand Down
8 changes: 5 additions & 3 deletions apps/src/code-studio/pd/workshop_dashboard/workshop_index.jsx
Expand Up @@ -65,12 +65,14 @@ export default class WorkshopIndex extends React.Component {

render() {
const showOrganizer = this.permission.isWorkshopAdmin;
const canDelete = this.permission.isWorkshopAdmin || this.permission.isOrganizer;
const canCreate = (this.permission.isWorkshopAdmin || this.permission.isOrganizer || this.permission.isCsfFacilitator);

return (
<div>
<h1>Your Workshops</h1>
<ButtonToolbar>
{(this.permission.isWorkshopAdmin || this.permission.isOrganizer) &&
{canCreate &&
(
<Button className="btn-primary" onClick={this.handleNewWorkshopClick}>
New Workshop
Expand All @@ -91,7 +93,7 @@ export default class WorkshopIndex extends React.Component {
<ServerSortWorkshopTable
queryUrl={FILTER_API_URL}
queryParams={filterParams.inProgress}
canDelete
canDelete={canDelete}
tableId="inProgressWorkshopsTable"
showOrganizer={showOrganizer}
moreUrl={this.generateFilterUrl('In Progress')}
Expand All @@ -100,7 +102,7 @@ export default class WorkshopIndex extends React.Component {
<ServerSortWorkshopTable
queryUrl={FILTER_API_URL}
queryParams={filterParams.notStarted}
canDelete
canDelete={canDelete}
tableId="notStartedWorkshopsTable"
showSignupUrl
showOrganizer={showOrganizer}
Expand Down
37 changes: 20 additions & 17 deletions apps/src/gamelab/AnimationPicker/animationPickerModule.js
Expand Up @@ -107,30 +107,33 @@ export function beginUpload(filename) {
*/
export function handleUploadComplete(result) {
return function (dispatch, getState) {
const { goal, uploadFilename } = getState().animationPicker;
const key = result.filename.replace(/\.png$/i, '');
const sourceUrl = animationsApi.basePath(key + '.png');

loadImageMetadata(sourceUrl, metadata => {
const animation = _.assign({}, metadata, {
name: uploadFilename,
sourceUrl: sourceUrl,
size: result.size,
version: result.versionId
});

if (goal === Goal.NEW_ANIMATION) {
dispatch(addAnimation(key, animation));
} else if (goal === Goal.NEW_FRAME) {
dispatch(appendCustomFrames(animation));
}
dispatch(hide());
}, () => {
const onImageMetadataLoaded = buildOnImageMetadataLoaded(key, result, dispatch, getState);
loadImageMetadata(sourceUrl, onImageMetadataLoaded, () => {
dispatch(handleUploadError(gamelabMsg.animationPicker_failedToParseImage()));
});
};
}

export function buildOnImageMetadataLoaded(key, result, dispatch, getState) {
return (metadata) => {
const { goal, uploadFilename } = getState().animationPicker;
const animation = _.assign({}, metadata, {
name: uploadFilename,
sourceUrl: null,
size: result.size,
version: result.versionId
});
if (goal === Goal.NEW_ANIMATION) {
dispatch(addAnimation(key, animation));
} else if (goal === Goal.NEW_FRAME) {
dispatch(appendCustomFrames(animation));
}
dispatch(hide());
};
}

/**
* Asynchronously loads an image file as an Image, then derives appropriate
* animation metadata from that Image and returns the metadata to a callback.
Expand Down
4 changes: 2 additions & 2 deletions apps/src/lib/kits/maker/Button.js
@@ -1,7 +1,7 @@
/** @file Wrapper around Johnny-Five Button component */
import five from '@code-dot-org/johnny-five';
import '../../../utils'; // For Function.prototype.inherits
import {TOUCH_PINS} from "./PlaygroundConstants";
import {EXTERNAL_PINS} from "./PlaygroundConstants";

/**
* Wrap Johnny-Five's Button component to add attributes and customize behavior.
Expand All @@ -11,7 +11,7 @@ import {TOUCH_PINS} from "./PlaygroundConstants";
*/
export default function Button(opts) {
// For Circuit Playground, treat touch pin buttons as pullups.
opts.pullup = TOUCH_PINS.includes(opts.pin);
opts.pullup = EXTERNAL_PINS.includes(opts.pin);
five.Button.call(this, opts);

// Add a read-only `isPressed` property
Expand Down
3 changes: 2 additions & 1 deletion apps/src/lib/kits/maker/PlaygroundConstants.js
@@ -1,6 +1,7 @@
import experiments from '../../../util/experiments';
export const N_COLOR_LEDS = 10;
export const TOUCH_PINS = [0, 1, 2, 3, 6, 9, 10, 12];
export const EXTERNAL_PINS = [0, 1, 2, 3, 6, 9, 10, 12];
export const TOUCH_PINS = [0, 2, 3, 6, 9, 10, 12];
export const J5_CONSTANTS = {
INPUT: 0,
OUTPUT: 1,
Expand Down
5 changes: 4 additions & 1 deletion apps/src/sites/studio/pages/projects/index.js
Expand Up @@ -46,10 +46,13 @@ $(document).ready(() => {
const isPublic = window.location.pathname.startsWith('/projects/public');
const initialState = isPublic ? Galleries.PUBLIC : Galleries.PRIVATE;
store.dispatch(selectGallery(initialState));
const showFeatured = window.location.href.includes("showFeatured");
const originalUrl = `/api/v1/projects/gallery/public/all/${MAX_PROJECTS_PER_CATEGORY}`;
const url = showFeatured ? originalUrl + `?showFeatured=1` : originalUrl;

$.ajax({
method: 'GET',
url: `/api/v1/projects/gallery/public/all/${MAX_PROJECTS_PER_CATEGORY}`,
url: url,
dataType: 'json'
}).done(projectLists => {
store.dispatch(setProjectLists(projectLists));
Expand Down
6 changes: 5 additions & 1 deletion apps/src/sites/studio/pages/projects/public.js
Expand Up @@ -11,10 +11,14 @@ import { MAX_PROJECTS_PER_CATEGORY } from '@cdo/apps/templates/projects/projectC
import StartNewProject from '@cdo/apps/templates/projects/StartNewProject';

$(document).ready(() => {
const showFeatured = window.location.href.includes("showFeatured");
const originalUrl = `/api/v1/projects/gallery/public/all/${MAX_PROJECTS_PER_CATEGORY}`;
const url = showFeatured ? originalUrl + `?showFeatured=1` : originalUrl;

registerReducers({projects});
$.ajax({
method: 'GET',
url: `/api/v1/projects/gallery/public/all/${MAX_PROJECTS_PER_CATEGORY}`,
url: url,
dataType: 'json'
}).done(projectLists => {
getStore().dispatch(setProjectLists(projectLists));
Expand Down
43 changes: 43 additions & 0 deletions apps/test/integration/levelSolutions/turtle/1_1.js
Expand Up @@ -30,6 +30,49 @@ module.exports = {
solution +
'</xml>'
},
{
description: "Solution using more than the ideal number of blocks",
expected: {
result: true,
testResult: TestResults.TOO_MANY_BLOCKS_FAIL
},
missingBlocks: [],
xml:
`<xml>
<block type="when_run">
<next>
<block type="jump_by_constant_dropdown">
<title name="DIR">jumpBackward</title>
<title name="VALUE">50</title>
<next>
<block type="jump_by_constant_dropdown">
<title name="DIR">jumpForward</title>
<title name="VALUE">50</title>
<next>
<block type="draw_move_by_constant">
<title name="DIR">moveForward</title>
<title name="VALUE">100</title>
<next>
<block type="draw_turn_by_constant_restricted">
<title name="DIR">turnRight</title>
<title name="VALUE">90</title>
<next>
<block type="draw_move_by_constant">
<title name="DIR">moveForward</title>
<title name="VALUE">100</title>
</block>
</next>
</block>
</next>
</block>
</next>
</block>
</next>
</block>
</next>
</block>
</xml>`
},
{
description: "User doesnt add any blocks. Should fail.",
expected: {
Expand Down
Expand Up @@ -28,6 +28,14 @@ describe("WorkshopIndex", () => {
"Filter View"
]
],
[
"csf_facilitator",
[
"New Workshop",
"Facilitator Survey Results",
"Filter View"
]
],
[
"workshop_organizer",
[
Expand Down
@@ -1,4 +1,8 @@
import sinon from 'sinon';
import reducer, * as animationPickerModule from '@cdo/apps/gamelab/AnimationPicker/animationPickerModule';
import listReducer from '@cdo/apps/gamelab/animationListModule';
import {combineReducers} from 'redux';
import {createStore} from '../../../util/redux';
import {expect} from '../../../util/configuredChai';
var Goal = animationPickerModule.Goal;

Expand Down Expand Up @@ -102,5 +106,46 @@ describe('animationPickerModule', function () {
expect(newState.uploadError).to.equal(status);
});
});

describe('action: handleUploadComplete', function () {

it('sets sourceUrl to null', function () {
const fakeDispatch = sinon.spy();
const newState = {animationPicker: { uploadFilename: "filename.jpg", goal: Goal.NEW_ANIMATION }};
var store = createStore(combineReducers({animationList: listReducer, animationPicker: reducer}), newState);

var onMetadataLoaded = animationPickerModule.buildOnImageMetadataLoaded(
"filename.jpg",
{
filename: "filename.jpg",
result: 0,
versionId: "string"
},
fakeDispatch,
store.getState
);
onMetadataLoaded({});
expect(fakeDispatch).to.have.been.calledTwice;

const addAnimation = fakeDispatch.firstCall.args[0];
expect(addAnimation).to.be.a('function');
expect(fakeDispatch.secondCall.args[0]).to.deep.equal(animationPickerModule.hide());
fakeDispatch.reset();

addAnimation(fakeDispatch, store.getState);
expect(fakeDispatch.firstCall.args[0]).to.deep.equal({
type: 'AnimationList/ADD_ANIMATION',
key: 'filename.jpg',
props:
{
name: 'filename.jpg',
sourceUrl: null,
size: undefined,
version: 'string',
looping: true
}
});
});
});
});
});
10 changes: 5 additions & 5 deletions apps/test/unit/lib/kits/maker/ButtonTest.js
Expand Up @@ -4,7 +4,7 @@ import {expect} from '../../../../util/configuredChai';
import five from '@code-dot-org/johnny-five';
import makeStubBoard from './makeStubBoard';
import Button from '@cdo/apps/lib/kits/maker/Button';
import {TOUCH_PINS} from '@cdo/apps/lib/kits/maker/PlaygroundConstants';
import {EXTERNAL_PINS} from '@cdo/apps/lib/kits/maker/PlaygroundConstants';

describe('Button', function () {
it('is a johnny-five Button component', function () {
Expand Down Expand Up @@ -35,8 +35,8 @@ describe('Button', function () {
});
});

it('becomes a pullup when assigned to a touch pin', () => {
TOUCH_PINS.forEach((pin) => {
it('becomes a pullup when assigned to an external pin', () => {
EXTERNAL_PINS.forEach((pin) => {
const button = new Button({
board: makeStubBoard(),
pin
Expand All @@ -45,9 +45,9 @@ describe('Button', function () {
});
});

it('does not become a pullup when assigned to a non-touch pin', () => {
it('does not become a pullup when assigned to a non-external pin', () => {
_.range(21)
.filter(pin => !TOUCH_PINS.includes(pin))
.filter(pin => !EXTERNAL_PINS.includes(pin))
.forEach((pin) => {
const button = new Button({
board: makeStubBoard(),
Expand Down

0 comments on commit 6d23b1c

Please sign in to comment.