Skip to content

Add flash message with info after login #1952

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

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6bd0143
Add flash message with info after login
dsavchuk-vineti Dec 14, 2019
93d8dbb
Fix prettier errors
dsavchuk-vineti Dec 15, 2019
93105e7
fix: Remove clean to fix error in console
dsavchuk-vineti Dec 26, 2019
d7ac6d7
feat: Add has_token field to user
dsavchuk-vineti Dec 26, 2019
e6403a9
fix(component): Move test to right folder
dsavchuk-vineti Dec 26, 2019
d006ee9
chore: Add welcome-message component
dsavchuk-vineti Dec 26, 2019
56ed394
fix: Remove flash message after user login
dsavchuk-vineti Dec 26, 2019
220e90f
fix: Update user fields after add/remove api tokens
dsavchuk-vineti Dec 26, 2019
ce44d31
Merge upstream/master into feature/add-alert-with-info-after-login
dsavchuk-vineti Dec 26, 2019
9117b74
fix: Fixes after merge from current master
dsavchuk-vineti Dec 26, 2019
9656756
fix: Fix linter errors
dsavchuk-vineti Dec 26, 2019
b9c2856
fix: Fix Rust linter errors
dsavchuk-vineti Dec 26, 2019
6e0936f
Merge upstream/auto into feature/add-alert-with-info-after-login
dsavchuk-vineti Dec 28, 2019
4eb0e95
fix: Change class name in test after rebase
dsavchuk-vineti Dec 28, 2019
ff50263
fix: Remove unneeded space
dsavchuk-vineti Dec 31, 2019
46ab6ee
fix: Change tests to use qunit-dom
dsavchuk-vineti Dec 31, 2019
ba7db55
Merge upstream/auto into feature/add-alert-with-info-after-login
Feb 4, 2020
a64fc2a
fix: Return flash message component to default state
Feb 4, 2020
b8c9d25
fix: Update welcome message component to be align after rebase
Feb 4, 2020
938393f
fix: Refactor tests after rebase
Feb 5, 2020
d7b5a54
fix: Fix linter errors
Feb 5, 2020
dbe7ed4
Merge branch 'master' into feature/add-alert-with-info-after-login
locks Mar 21, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions app/components/api-token-row.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import Component from '@ember/component';
import { inject as service } from '@ember/service';
import { empty, or } from '@ember/object/computed';

export default Component.extend({
emptyName: empty('api_token.name'),
disableCreate: or('api_token.isSaving', 'emptyName'),
serverError: null,
session: service(),
store: service(),

didInsertElement() {
let input = this.element.querySelector('input');
Expand All @@ -17,6 +20,7 @@ export default Component.extend({
async saveToken() {
try {
await this.api_token.save();
this.set('session.currentUser.has_tokens', true);
this.set('serverError', null);
} catch (err) {
let msg;
Expand All @@ -31,6 +35,11 @@ export default Component.extend({

async revokeToken() {
try {
// To avoid error on destroy we need to set before destroying of api-token
// that's why we need to set length of api-tokens to 1 in check
if ((await this.store.query('api-token', {})).length == 1) {
this.set('session.currentUser.has_tokens', false);
}
await this.api_token.destroyRecord();
} catch (err) {
let msg;
Expand Down
3 changes: 3 additions & 0 deletions app/components/welcome-message.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<p local-class="welcome-message {{if this.showMessage "shown info"}}" ...attributes data-test-welcome-message>
Welcome to crates.io! Visit <a href='me'>account settings</a> to {{this.text}}
</p>
26 changes: 26 additions & 0 deletions app/components/welcome-message.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Component from '@ember/component';
import { inject as service } from '@ember/service';
import { computed } from '@ember/object';
import { notEmpty } from '@ember/object/computed';

export default Component.extend({
session: service(),

text: computed('session.currentUser.{email_verified,has_tokens}', function() {
const user = this.get('session.currentUser');
if (!user || (user.email_verified && user.has_tokens)) return '';

const textArray = [
!user.email_verified && 'verify your email address',
!user.email_verified && !user.has_tokens && ' and ',
!user.has_tokens && 'create an API token',
'!',
].filter(e => !!e);
Copy link
Member

Choose a reason for hiding this comment

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

the code logic here seems a bit hard to read. do you think it could be simplified in some way?

Copy link
Author

Choose a reason for hiding this comment

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

I tried some variants before come to this. For example, a variant with ternary operators looks more ugly:

const verifyText = user.email_verified ? '' : 'verify your email address ';
const andText = user.email_verified && user.has_tokens ? '' : 'and ';
const tokenText = user.has_tokens ? '' : 'create an API token';

return `${verifyText}${andText}{$tokenText}`;

Moving this calculation logic to template is not right.
I'm open to any ideas on how to improve this code.


return textArray.join('');
}),
Copy link
Member

Choose a reason for hiding this comment

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

I think I would prefer it if we moved that code into the index controller and not instantiate the component at all if we don't have anything to display

Copy link
Author

Choose a reason for hiding this comment

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

This logic has nothing in common with the index controller. This is common arch mistake in all MVC frameworks. Trying to move business logic inside of controllers fail by design.


showMessage: notEmpty('text').readOnly(),

tagName: '',
});
18 changes: 18 additions & 0 deletions app/components/welcome-message.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.welcome-message {
display: none;
font-weight: bold;
font-size: 110%;
text-align: center;
margin: 0 0 10px 0;
padding: 10px;
border-radius: 5px;

&.shown {
display: block;
}

&.info {
background-color: $main-bg-dark;
border: 2px solid #62865f;
}
}
1 change: 1 addition & 0 deletions app/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export default Model.extend({
avatar: attr('string'),
url: attr('string'),
kind: attr('string'),
has_tokens: attr('boolean'),

stats() {
return this.store.adapterFor('user').stats(this.id);
Expand Down
10 changes: 4 additions & 6 deletions app/routes/me/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@ import AuthenticatedRoute from '../../mixins/authenticated-route';
export default Route.extend(AuthenticatedRoute, {
actions: {
willTransition: function() {
this.controller
.setProperties({
emailNotificationsSuccess: false,
emailNotificationsError: false,
})
.clear();
this.controller.setProperties({
emailNotificationsSuccess: false,
emailNotificationsError: false,
});
},
},
model() {
Expand Down
3 changes: 2 additions & 1 deletion app/services/flash-messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ export default class FlashMessagesService extends Service {
message = null;
_nextMessage = null;

show(message) {
show(message, options = { type: 'warning' }) {
this.set('message', message);
this.set('options', options);
}

queue(message) {
Expand Down
13 changes: 3 additions & 10 deletions app/templates/catch-all.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,6 @@
<p local-class='p404'>
Perhaps a search of the site may help?

<Input
@type="text"
local-class="search-field"
class="search"
placeholder="Search"
@value={{this.search}}
@enter={{action "search"}}
required={{true}}
/>
</p>
<Input @type="text" local-class="search-field" class="search" placeholder="Search" @value={{this.search}}
@enter={{action "search"}} required={{true}} />
</p>
18 changes: 12 additions & 6 deletions app/templates/index.hbs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
<WelcomeMessage />
<div local-class='title-header'>
<h1>The Rust community&rsquo;s crate registry</h1>

<div local-class='links'>
<a href="https://doc.rust-lang.org/cargo/getting-started/installation.html" class='yellow-button' data-test-install-cargo-link>
<a href="https://doc.rust-lang.org/cargo/getting-started/installation.html" class='yellow-button'
data-test-install-cargo-link>
{{svg-jar "button-download"}}
Install Cargo
</a>
Expand All @@ -24,12 +26,14 @@
<div local-class='stats'>
<div local-class='downloads'>
{{svg-jar "download"}}
<span local-class='num' data-test-total-downloads>{{if this.hasData (format-num this.model.num_downloads) "---,---,---"}}</span>
<span local-class='num'
data-test-total-downloads>{{if this.hasData (format-num this.model.num_downloads) "---,---,---"}}</span>
<span class='desc small'>Downloads</span>
</div>
<div local-class='crates'>
{{svg-jar "crate"}}
<span local-class='num' data-test-total-crates>{{if this.hasData (format-num this.model.num_crates) "---,---"}}</span>
<span local-class='num'
data-test-total-crates>{{if this.hasData (format-num this.model.num_crates) "---,---"}}</span>
<span class='desc small'>Crates in stock</span>
</div>
</div>
Expand All @@ -53,11 +57,13 @@
<CrateListNameOnly @crates={{this.model.most_recently_downloaded}} />
</section>
<section id='keywords' data-test-keywords aria-busy="{{this.dataTask.isRunning}}">
<h2>Popular Keywords <LinkTo @route="keywords">(see all)</LinkTo></h2>
<h2>Popular Keywords <LinkTo @route="keywords">(see all)</LinkTo>
</h2>
<KeywordList @keywords={{this.model.popular_keywords}} />
</section>
<section id='categories' data-test-categories aria-busy="{{this.dataTask.isRunning}}">
<h2>Popular Categories <LinkTo @route="categories">(see all)</LinkTo></h2>
<h2>Popular Categories <LinkTo @route="categories">(see all)</LinkTo>
</h2>
<CategoryList @categories={{this.model.popular_categories}} />
</section>
</div>
</div>
29 changes: 21 additions & 8 deletions mirage/factories/user.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
import { Factory } from 'ember-cli-mirage';
import { dasherize } from '@ember/string';
import { Factory, trait } from 'ember-cli-mirage';
import faker from 'faker';

export default Factory.extend({
name: i => `User ${i + 1}`,

email_verified: false,
email_verification_sent: true,
name() {
return faker.name.findName();
},
login() {
return dasherize(this.name);
return faker.internet.userName();
},
avatar() {
return faker.image.imageUrl();
},

url() {
return `https://github.com/${this.login}`;
return faker.internet.url();
},
kind: 'user',
has_tokens: false,

withVerifiedEmail: trait({
email_verified: true,
}),

avatar: 'https://avatars1.githubusercontent.com/u/14631425?v=4',
withTokens: trait({
has_tokens: true,
}),
});
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"eslint-config-prettier": "^6.10.0",
"eslint-plugin-ember": "^7.10.1",
"eslint-plugin-prettier": "^3.1.2",
"faker": "^4.1.0",
"loader.js": "^4.7.0",
"normalize.css": "^8.0.1",
"nyc": "^15.0.0",
Expand Down
11 changes: 8 additions & 3 deletions src/controllers/user/me.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ use crate::controllers::helpers::*;
use crate::email;

use crate::models::{
CrateOwner, Email, Follow, NewEmail, OwnerKind, User, Version, VersionOwnerAction,
ApiToken, CrateOwner, Email, Follow, NewEmail, OwnerKind, User, Version, VersionOwnerAction,
};
use crate::schema::{crate_owners, crates, emails, follows, users, versions};
use crate::schema::{api_tokens, crate_owners, crates, emails, follows, users, versions};
use crate::views::{EncodableMe, EncodableVersion, OwnedCrate};

/// Handles the `GET /me` route.
Expand All @@ -27,6 +27,11 @@ pub fn me(req: &mut dyn Request) -> AppResult<Response> {
))
.first::<(User, Option<bool>, Option<String>, bool)>(&*conn)?;

let tokens: Vec<ApiToken> = ApiToken::belonging_to(req.user()?)
.filter(api_tokens::revoked.eq(false))
.load(&*conn)?;
let has_tokens = !tokens.is_empty();

let owned_crates = CrateOwner::by_owner_kind(OwnerKind::User)
.inner_join(crates::table)
.filter(crate_owners::owner_id.eq(user_id))
Expand All @@ -44,7 +49,7 @@ pub fn me(req: &mut dyn Request) -> AppResult<Response> {
let verified = verified.unwrap_or(false);
let verification_sent = verified || verification_sent;
Ok(req.json(&EncodableMe {
user: user.encodable_private(email, verified, verification_sent),
user: user.encodable_private(email, verified, verification_sent, has_tokens),
owned_crates,
}))
}
Expand Down
2 changes: 2 additions & 0 deletions src/models/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ impl User {
email: Option<String>,
email_verified: bool,
email_verification_sent: bool,
has_tokens: bool,
) -> EncodablePrivateUser {
let User {
id,
Expand All @@ -186,6 +187,7 @@ impl User {
email,
email_verified,
email_verification_sent,
has_tokens,
avatar: gh_avatar,
login: gh_login,
name,
Expand Down
18 changes: 17 additions & 1 deletion src/tests/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
OkBool, TestApp,
};
use cargo_registry::{
models::{Email, NewUser, User},
models::{ApiToken, Email, NewUser, User},
schema::crate_owners,
views::{EncodablePrivateUser, EncodablePublicUser, EncodableVersion, OwnedCrate},
};
Expand Down Expand Up @@ -745,3 +745,19 @@ fn test_update_email_notifications_not_owned() {
// There should be no change to the `email_notifications` value for a crate not belonging to me
assert!(email_notifications);
}

#[test]
fn shows_that_user_has_tokens() {
let (app, _, user) = TestApp::init().with_user();

let user_id = user.as_model().id;
app.db(|conn| {
vec![
t!(ApiToken::insert(conn, user_id, "bar")),
t!(ApiToken::insert(conn, user_id, "baz")),
]
});

let json = user.show_me();
assert!(json.user.has_tokens);
}
1 change: 1 addition & 0 deletions src/views.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ pub struct EncodablePrivateUser {
pub email: Option<String>,
pub avatar: Option<String>,
pub url: Option<String>,
pub has_tokens: bool,
}

/// The serialization format for the `User` model.
Expand Down
17 changes: 17 additions & 0 deletions tests/integration/components/flesh-message-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';

module('Integration | Component | flesh-message', function(hooks) {
setupRenderingTest(hooks);

test('it renders', async function(assert) {
assert.expect(2);

await render(hbs`<FlashMessage @message="test text" />`);

assert.dom('[data-test-flash-message]').hasText('test text');
assert.dom('[data-test-flash-message]').isVisible();
});
});
Loading