Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert "Revert "Show correct state of sign-in button in dashboard"" #43782

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
42 changes: 39 additions & 3 deletions apps/src/code-studio/header.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@ import {
refreshProjectName,
setShowTryAgainDialog
} from './headerRedux';

import React from 'react';
import ReactDOM from 'react-dom';

import {Provider} from 'react-redux';
import progress from './progress';
import {getStore} from '../redux';
import {asyncLoadUserData} from '@cdo/apps/templates/currentUserRedux';
import {
setUserSignedIn,
setInitialData
} from '@cdo/apps/templates/currentUserRedux';

import {PUZZLE_PAGE_NONE} from '@cdo/apps/templates/progress/progressTypes';
import HeaderMiddle from '@cdo/apps/code-studio/components/header/HeaderMiddle';
Expand Down Expand Up @@ -168,10 +170,44 @@ function setupReduxSubscribers(store) {
setupReduxSubscribers(getStore());

function setUpGlobalData(store) {
store.dispatch(asyncLoadUserData());
fetch('/api/v1/users/current', {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we document this somewhere? I can't tell how widespread this application of fetch might be but if we're moving toward fetch over ajax and the default credentials are still different for older browsers I imagine we'll run into this again?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I definitely agree. One way to handle this would be to not use fetch directly but write our own wrapper around it so that these kind of configurations will be set for it every time it's used. I'll start a conversation about this in apps-devs tomorrow!

credentials: 'same-origin'
})
.then(response => response.json())
.then(data => {
store.dispatch(setUserSignedIn(data.is_signed_in));
if (data.is_signed_in) {
store.dispatch(setInitialData(data));
ensureHeaderSigninState(true, data.short_name);
} else {
ensureHeaderSigninState(false);
}
})
.catch(err => {
console.log(err);
});
}
setUpGlobalData(getStore());

// Some of our cached pages can become cached by the browser with the
// wrong sign-in state. This is a temporary patch to ensure that the header
// displays the correct sign-in state for the user.
function ensureHeaderSigninState(isSignedIn, shortName) {
const userMenu = document.querySelector('#header_user_menu');
const signinButton = document.querySelector('#signin_button');

if (isSignedIn && userMenu.style.display === 'none') {
userMenu.style.display = 'block';
signinButton.style.display = 'none';

const displayName = document.querySelector('#header_display_name');
displayName.textContent = shortName;
} else if (!isSignedIn && signinButton.style.display === 'none') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It strikes me that this would be the default- this is a NIT but it might be organizes as just a default 'let' for those two vars with a single 'if' instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand what you mean by default 'let' for those two vars. I may follow-up with you on this and get these changes in separately!

userMenu.style.display = 'none';
signinButton.style.display = 'inline';
}
}

header.showMinimalProjectHeader = function() {
getStore().dispatch(refreshProjectName());
getStore().dispatch(showMinimalProjectHeader());
Expand Down
30 changes: 4 additions & 26 deletions apps/src/templates/currentUserRedux.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ export const setUserSignedIn = isSignedIn => ({
isSignedIn
});
export const setUserType = userType => ({type: SET_USER_TYPE, userType});
const setInitialData = serverUser => ({type: SET_INITIAL_DATA, serverUser});
export const setInitialData = serverUser => ({
type: SET_INITIAL_DATA,
serverUser
});

const initialState = {
userId: null,
Expand Down Expand Up @@ -73,28 +76,3 @@ export default function currentUser(state = initialState, action) {

return state;
}

export const asyncLoadUserData = () => dispatch => {
currentUserFromServer(dispatch);
};

const currentUserFromServer = dispatch => {
return fetch('/api/v1/users/current')
.then(response => response.json())
.then(data => {
dispatch(setUserSignedIn(data.is_signed_in));
if (data.is_signed_in) {
dispatch(setInitialData(data));
}
})
.catch(err => {
console.log(err);
});
};

// export private function(s) to expose to unit testing
export const __testonly__ = IN_UNIT_TEST
? {
currentUserFromServer
}
: {};
51 changes: 14 additions & 37 deletions apps/test/unit/code-studio/currentUserReduxTest.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import {assert} from '../../util/reconfiguredChai';
import sinon from 'sinon';
import currentUser, {
SignInState,
setUserSignedIn,
setUserType,
setCurrentUserHasSeenStandardsReportInfo,
setCurrentUserName,
__testonly__
setInitialData
} from '@cdo/apps/templates/currentUserRedux';

describe('currentUserRedux', () => {
Expand Down Expand Up @@ -52,40 +51,18 @@ describe('currentUserRedux', () => {
});
});

describe('asyncLoadUserData', () => {
const {currentUserFromServer} = __testonly__;

it('calls /users/current and sets user data to state', async () => {
const dispatchSpy = sinon.spy();
const serverUser = {
id: 1,
username: 'test_user',
user_type: 'teacher',
is_signed_in: true
};

function mockApiResponse() {
return new window.Response(JSON.stringify(serverUser), {
status: 200,
headers: {'Content-type': 'application/json'}
});
}

const fetchStub = sinon.stub(window, 'fetch');
fetchStub
.withArgs('/api/v1/users/current')
.returns(Promise.resolve(mockApiResponse()));

await currentUserFromServer(dispatchSpy);

const dispatchCalls = dispatchSpy.getCalls();
const action1 = dispatchCalls[0].args[0];
assert.equal('currentUser/SET_USER_SIGNED_IN', action1.type);
assert.equal(true, action1.isSignedIn);

const action2 = dispatchCalls[1].args[0];
assert.equal('currentUser/SET_INITIAL_DATA', action2.type);
assert.deepEqual(serverUser, action2.serverUser);
});
describe('setInitialData', () => {
const serverUser = {
id: 1,
username: 'test_user',
user_type: 'teacher',
is_signed_in: true
};
const action = setInitialData(serverUser);
const nextState = currentUser(initialState, action);

assert.equal(nextState.userId, 1);
assert.equal(nextState.userName, 'test_user');
assert.equal(nextState.userType, 'teacher');
});
});
3 changes: 2 additions & 1 deletion dashboard/app/controllers/api/v1/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ def current
id: current_user.id,
username: current_user.username,
user_type: current_user.user_type,
is_signed_in: true
is_signed_in: true,
short_name: current_user.short_name
}
else
render json: {
Expand Down
2 changes: 1 addition & 1 deletion dashboard/app/views/layouts/_header.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@

sign_in_user = current_user || user_type && OpenStruct.new(
id: nil,
short_name: I18n.t("nav.user.#{user_type}"),
short_name: I18n.t("nav.user.loading"),
can_pair?: false
)
show_pairing_dialog = !!session.delete(:show_pairing_dialog)
Expand Down
1 change: 1 addition & 0 deletions dashboard/config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ en:
starwars: 'Star Wars'
starwarsblocks: 'Star Wars (Blocks)'
starwarsblocks_hour: 'Star Wars (Blocks)'
loading: 'Loading...'
weblab: 'Web Lab'
view_all: 'View all projects...'

Expand Down
1 change: 1 addition & 0 deletions dashboard/test/controllers/api/v1/users_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ class Api::V1::UsersControllerTest < ActionController::TestCase
assert_equal teacher.id, response["id"]
assert_equal teacher.username, response["username"]
assert_equal "teacher", response["user_type"]
assert_equal teacher.short_name, response["short_name"]
end

test "a get request to get school_name returns school object" do
Expand Down
24 changes: 12 additions & 12 deletions dashboard/test/ui/features/foundations/signing_in.feature
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ Scenario: Student sign in from code.org
And I sign out
Given I am on "http://code.org/"
And I reload the page
Then I wait to see ".header_user"
Then I click ".header_user"
Then I wait to see "#header_user_signin"
Then I click "#header_user_signin"
And I wait to see "#signin"
And I fill in username and password for "Bob"
And I click "#signin-button" to load a new page
Then I wait until I am on "http://studio.code.org/home"
Then I wait to see ".user_menu"
Then I wait to see "#header_user_menu"
And I wait until element ".display_name" is visible
And element ".display_name" contains text "Bob"

Expand All @@ -22,13 +22,13 @@ Scenario: Student sign in from studio.code.org
And I sign out
Given I am on "http://studio.code.org/"
And I reload the page
Then I wait to see ".header_user"
Then I click ".header_user"
Then I wait to see "#header_user_signin"
Then I click "#header_user_signin"
And I wait to see "#signin"
And I fill in username and password for "Alice"
And I click "#signin-button" to load a new page
Then I wait until I am on "http://studio.code.org/home"
Then I wait to see ".user_menu"
Then I wait to see "#header_user_menu"
And I wait until element ".display_name" is visible
And element ".display_name" contains text "Alice"

Expand All @@ -37,13 +37,13 @@ Scenario: Student sign in from studio.code.org in the eu
And I sign out
Given I am on "http://studio.code.org/"
And I reload the page
Then I wait to see ".header_user"
Then I click ".header_user"
Then I wait to see "#header_user_signin"
Then I click "#header_user_signin"
And I wait to see "#signin"
And I fill in username and password for "Alice"
And I click "#signin-button" to load a new page
Then I wait until I am on "http://studio.code.org/home"
Then I wait to see ".user_menu"
Then I wait to see "#header_user_menu"
And I wait until element ".display_name" is visible
And element ".display_name" contains text "Alice"

Expand All @@ -52,13 +52,13 @@ Scenario: Teacher sign in from studio.code.org
And I sign out
Given I am on "http://studio.code.org/"
And I reload the page
Then I wait to see ".header_user"
Then I click ".header_user"
Then I wait to see "#header_user_signin"
Then I click "#header_user_signin"
And I wait to see "#signin"
And I fill in username and password for "Casey"
And I click "#signin-button" to load a new page
Then I wait until I am on "http://studio.code.org/home"
Then I wait to see ".user_menu"
Then I wait to see "#header_user_menu"
And I wait until element ".display_name" is visible
And element ".display_name" contains text "Casey"

Expand Down
74 changes: 39 additions & 35 deletions shared/haml/user_header.haml
Original file line number Diff line number Diff line change
Expand Up @@ -34,40 +34,44 @@
%a.project_link_box{href: CDO.studio_url('projects')}
.project_link#view_all_projects
= I18n.t("#{loc_prefix}view_all")
- if current_user
.header_button.header_user.user_menu{tabindex: 0, role: "button"}
- if current_user.can_pair? && session_pairings.present?
%i.fa.fa-users.pairing_icon
%span.pairing_name= I18n.t("#{loc_prefix}team")
- else
- short_name = ERB::Util.h(current_user.short_name)
%span.display_name{'data-id': current_user.id, 'data-shortname': short_name}= short_name
&nbsp;
%i.user_menu_arrow_down{class: "fa fa-caret-down"}
%i.user_menu_arrow_up{class: "fa fa-caret-up", style: "display: none"}
.user_options{style: 'display: none', dir: language_dir_class}
%a.linktag#my-projects{href: CDO.studio_url('projects')}= I18n.t("#{loc_prefix}my_projects")
- if current_user.can_pair?
- if session_pairings.present?
= link_to '#', {id: 'pairing_link', style: 'display: none'} do
= I18n.t("#{loc_prefix}pair_programming")
.pairing_summary
#{I18n.t("#{loc_prefix}driver")}:
= h(current_user.short_name)
- session_pairings.map do |id|
%br
#{I18n.t("#{loc_prefix}navigator")}:
= h(User.find(id).short_name)
- else
= link_to '#', {id: 'pairing_link', style: 'display: none'} do
= I18n.t("#{loc_prefix}pair_programming")
%a.linktag#user-edit{href: CDO.studio_url('users/edit')}= I18n.t("#{loc_prefix}settings")
%a.linktag#user-signout{href: CDO.studio_url('users/sign_out')}= I18n.t("#{loc_prefix}logout")
- else
%a.linktag{href: CDO.studio_url('users/sign_in'), class: 'button-signin', id: 'signin_button'}
.header_button.header_user
%span= I18n.t("#{loc_prefix}signin")
.signin_callout_wrapper
-# The user menu button is hidden using css so that we can verify the login state of the user on the client
-# and adjust as needed for cached pages (see header.js in apps)
- user_dropdown_display = current_user.present? ? "block" : "none"
.header_button.header_user.user_menu{id: "header_user_menu", tabindex: 0, role: "button", style: "display: #{user_dropdown_display}"}
- if current_user&.can_pair? && session_pairings.present?
%i.fa.fa-users.pairing_icon
%span.pairing_name= I18n.t("#{loc_prefix}team")
- else
- short_name = ERB::Util.h(current_user&.short_name)
%span.display_name{id: 'header_display_name', 'data-id': current_user&.id, 'data-shortname': short_name}= short_name
&nbsp;
%i.user_menu_arrow_down{class: "fa fa-caret-down"}
%i.user_menu_arrow_up{class: "fa fa-caret-up", style: "display: none"}
.user_options{style: 'display: none', dir: language_dir_class}
%a.linktag#my-projects{href: CDO.studio_url('projects')}= I18n.t("#{loc_prefix}my_projects")
- if current_user&.can_pair?
- if session_pairings.present?
= link_to '#', {id: 'pairing_link', style: 'display: none'} do
= I18n.t("#{loc_prefix}pair_programming")
.pairing_summary
#{I18n.t("#{loc_prefix}driver")}:
= h(current_user&.short_name)
- session_pairings.map do |id|
%br
#{I18n.t("#{loc_prefix}navigator")}:
= h(User.find(id).short_name)
- else
= link_to '#', {id: 'pairing_link', style: 'display: none'} do
= I18n.t("#{loc_prefix}pair_programming")
%a.linktag#user-edit{href: CDO.studio_url('users/edit')}= I18n.t("#{loc_prefix}settings")
%a.linktag#user-signout{href: CDO.studio_url('users/sign_out')}= I18n.t("#{loc_prefix}logout")
-# The sign-in button is hidden using css so that we can verify the login state of the user on the client
-# and adjust as needed for cached pages (see header.js in apps)
- signin_display = current_user.present? ? "none" : "inline"
%a.linktag{href: CDO.studio_url('users/sign_in'), class: 'button-signin', id: 'signin_button', style: "display: #{signin_display}"}
.header_button.header_user#header_user_signin
%span= I18n.t("#{loc_prefix}signin")
.signin_callout_wrapper

:javascript
window.cookieEnvSuffix = '#{rack_env?(:production) ? '' : "_#{rack_env}"}';
Expand All @@ -82,7 +86,7 @@
// Provide current_user.short_name to cached pages via session cookie.
// There is apps code that also depends on this query-selector, so if changes are made
// here we should be sure to also update other locations.
var displayNameSpan = document.querySelector('.header_button.header_user.user_menu .display_name');
var displayNameSpan = document.querySelector('#header_display_name');

function retrieveUserShortName(element) {
if (element) {
Expand Down