From 52610ab3c18c7642f1c35bb31cbfc95001eb2e24 Mon Sep 17 00:00:00 2001 From: mattkrick Date: Tue, 28 Feb 2017 06:51:25 -0800 Subject: [PATCH 1/3] establish loading empty pattern fix #757 --- .../graphql/models/Action/actionQuery.js | 28 +++++++++++++++++++ .../models/Action/actionSubscription.js | 5 ++++ .../models/Organization/organizationQuery.js | 24 ++++++++++++++-- .../Organization/organizationSubscription.js | 2 +- src/server/graphql/rootQuery.js | 2 ++ .../NewTeamForm/NewTeamFormContainer.js | 16 ++++++----- .../UserActionList/UserActionList.js | 1 - .../UserActionList/UserActionListContainer.js | 9 ++++-- 8 files changed, 74 insertions(+), 13 deletions(-) create mode 100644 src/server/graphql/models/Action/actionQuery.js diff --git a/src/server/graphql/models/Action/actionQuery.js b/src/server/graphql/models/Action/actionQuery.js new file mode 100644 index 00000000000..76475e846ec --- /dev/null +++ b/src/server/graphql/models/Action/actionQuery.js @@ -0,0 +1,28 @@ +import getRethink from 'server/database/rethinkDriver'; +import {GraphQLNonNull, GraphQLID, GraphQLInt} from 'graphql'; +import {Action} from './actionSchema'; +import {requireSUOrSelf} from 'server/utils/authorization'; + +export default { + actionCount: { + type: GraphQLInt, + args: { + userId: { + type: new GraphQLNonNull(GraphQLID), + description: 'userId that owns all the actions' + } + }, + async resolve(source, {userId}, {authToken}) { + const r = getRethink(); + + // AUTH + requireSUOrSelf(authToken, userId); + + // RESOLUTION + return await r.table('Action') + .getAll(userId, {index: 'userId'}) + .filter({isComplete: false}) + .count(); + } + } +}; diff --git a/src/server/graphql/models/Action/actionSubscription.js b/src/server/graphql/models/Action/actionSubscription.js index 54fcbb22049..f57f6a15fca 100644 --- a/src/server/graphql/models/Action/actionSubscription.js +++ b/src/server/graphql/models/Action/actionSubscription.js @@ -16,7 +16,12 @@ export default { }, async resolve(source, {userId}, {authToken, socket, subbedChannelName}, refs) { const r = getRethink(); + + // AUTH requireSUOrSelf(authToken, userId); + + + // RESOLUTION const requestedFields = getRequestedFields(refs); const changefeedHandler = makeChangefeedHandler(socket, subbedChannelName); r.table('Action') diff --git a/src/server/graphql/models/Organization/organizationQuery.js b/src/server/graphql/models/Organization/organizationQuery.js index 283b4cdadc8..c1feb74192b 100644 --- a/src/server/graphql/models/Organization/organizationQuery.js +++ b/src/server/graphql/models/Organization/organizationQuery.js @@ -1,9 +1,29 @@ import getRethink from 'server/database/rethinkDriver'; -import {GraphQLNonNull, GraphQLID} from 'graphql'; +import {GraphQLNonNull, GraphQLID, GraphQLInt} from 'graphql'; import {Organization} from './organizationSchema'; -import {requireSUOrTeamMember} from 'server/utils/authorization'; +import {requireSUOrTeamMember, requireSUOrSelf} from 'server/utils/authorization'; export default { + orgCount: { + type: GraphQLInt, + args: { + userId: { + type: new GraphQLNonNull(GraphQLID), + description: 'the user ID that belongs to all the orgs' + } + }, + async resolve(source, {userId}, {authToken}) { + const r = getRethink(); + + // AUTH + requireSUOrSelf(authToken, userId); + + // RESOLUTION + return await r.table('Organization') + .getAll(userId, {index: 'orgUsers'}) + .count() + } + }, orgDetails: { type: Organization, args: { diff --git a/src/server/graphql/models/Organization/organizationSubscription.js b/src/server/graphql/models/Organization/organizationSubscription.js index a526422734f..dd8a9747acd 100644 --- a/src/server/graphql/models/Organization/organizationSubscription.js +++ b/src/server/graphql/models/Organization/organizationSubscription.js @@ -100,7 +100,7 @@ export default { const r = getRethink(); // AUTH - await requireSUOrSelf(authToken, userId); + requireSUOrSelf(authToken, userId); // RESOLUTION const requestedFields = getRequestedFields(refs); diff --git a/src/server/graphql/rootQuery.js b/src/server/graphql/rootQuery.js index bed7a0ee0db..7ff10005428 100644 --- a/src/server/graphql/rootQuery.js +++ b/src/server/graphql/rootQuery.js @@ -1,4 +1,5 @@ import {GraphQLObjectType} from 'graphql'; +import action from './models/Action/actionQuery'; import invoice from './models/Invoice/invoiceQuery'; import meeting from './models/Meeting/meetingQuery'; import organization from './models/Organization/organizationQuery'; @@ -8,6 +9,7 @@ import team from './models/Team/teamQuery'; import user from './models/User/userQuery'; const rootFields = Object.assign({}, + action, invoice, meeting, organization, diff --git a/src/universal/modules/newTeam/containers/NewTeamForm/NewTeamFormContainer.js b/src/universal/modules/newTeam/containers/NewTeamForm/NewTeamFormContainer.js index d6b834aec05..1958dbfb5fd 100644 --- a/src/universal/modules/newTeam/containers/NewTeamForm/NewTeamFormContainer.js +++ b/src/universal/modules/newTeam/containers/NewTeamForm/NewTeamFormContainer.js @@ -9,11 +9,10 @@ import addOrgSchema from 'universal/validation/addOrgSchema'; import {segmentEventTrack} from 'universal/redux/segmentActions'; import {connect} from 'react-redux'; import NewTeamForm from 'universal/modules/newTeam/components/NewTeamForm/NewTeamForm'; -import LoadingView from 'universal/components/LoadingView/LoadingView'; - const orgDropdownMenuItemsQuery = ` query { + orgCount(userId: $userId) organizations(userId: $userId) @live { id name @@ -24,7 +23,7 @@ query { const mapStateToProps = (state, props) => { const {newOrgRoute} = props; const userId = state.auth.obj.sub; - const {organizations} = cashay.query(orgDropdownMenuItemsQuery, { + const {orgCount, organizations} = cashay.query(orgDropdownMenuItemsQuery, { op: 'newTeamFormContainer', key: userId, sort: { @@ -37,6 +36,7 @@ const mapStateToProps = (state, props) => { const defaultOrg = organizations[0]; const orgId = defaultOrg && defaultOrg.id; return { + initialOrgCount: orgCount, organizations, initialValues: { orgId @@ -114,13 +114,15 @@ class NewTeamFormContainer extends Component { }; render() { - const {isNewOrg, organizations} = this.props; - if (organizations.length === 0) { - // more than looks, this is required because initialValues can only be passed in once - return ; + const {initialOrgCount, initialValues, isNewOrg, organizations, router} = this.props; + if (initialOrgCount === 0) { + router.push('/newteam/1') + } else if (!initialValues.orgId) { + return null; } return ( { actions={actions} dispatch={dispatch} teams={teams} - actionCount={actionCount} userId={userId} /> : diff --git a/src/universal/modules/userDashboard/containers/UserActionList/UserActionListContainer.js b/src/universal/modules/userDashboard/containers/UserActionList/UserActionListContainer.js index 35069ea6b15..13c747593de 100644 --- a/src/universal/modules/userDashboard/containers/UserActionList/UserActionListContainer.js +++ b/src/universal/modules/userDashboard/containers/UserActionList/UserActionListContainer.js @@ -5,6 +5,7 @@ import UserActionList from 'universal/modules/userDashboard/components/UserActio const userActionListQuery = ` query { + actionCount(userId: $id) actions(userId: $id) @live { content id @@ -47,7 +48,7 @@ const mapStateToProps = (state) => { const teamsFilterFn = teamFilterId ? (doc) => doc.id === teamFilterId : () => true; const userId = state.auth.obj.sub; const queryKey = teamFilterId || ''; - const {actions, teams} = cashay.query(userActionListQuery, { + const {actionCount, actions, teams} = cashay.query(userActionListQuery, { op: 'userActions', variables: {id: userId}, key: queryKey, @@ -66,6 +67,7 @@ const mapStateToProps = (state) => { mutationHandlers }).data; return { + initialActionCount: actionCount, actions, teams, userId: state.auth.obj.sub, @@ -75,7 +77,10 @@ const mapStateToProps = (state) => { }; const UserActionListContainer = (props) => { - const {actions, dispatch, queryKey, selectingNewActionTeam, teams, userId} = props; + const {initialActionCount, actions, dispatch, queryKey, selectingNewActionTeam, teams, userId} = props; + if (initialActionCount === null) { + return null; + } return ( Date: Tue, 28 Feb 2017 07:50:26 -0800 Subject: [PATCH 2/3] fix #753 --- src/server/graphql/models/Invoice/invoiceQuery.js | 4 +++- .../components/OrgBilling/OrgBilling.js | 12 ++++++++++-- .../containers/OrgBilling/OrgBillingContainer.js | 11 +++++++---- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/server/graphql/models/Invoice/invoiceQuery.js b/src/server/graphql/models/Invoice/invoiceQuery.js index c2a3729b523..55470c4f071 100644 --- a/src/server/graphql/models/Invoice/invoiceQuery.js +++ b/src/server/graphql/models/Invoice/invoiceQuery.js @@ -74,8 +74,9 @@ export default { // RESOLUTION if (after) { + const dbAfter = after === 0 ? r.minval : after; return r.table('Invoice') - .between([orgId, after], [orgId, r.maxval], {index: 'orgIdStartAt', leftBound: 'open'}) + .between([orgId, dbAfter], [orgId, r.maxval], {index: 'orgIdStartAt', leftBound: 'open'}) .orderBy(r.desc('startAt')) .limit(first) .merge((doc) => ({ @@ -97,6 +98,7 @@ export default { const upcomingInvoice = { id: `upcoming_${orgId}`, amountDue: stripeInvoice.amount_due, + cursor: 0, total: stripeInvoice.total, endAt: fromEpochSeconds(stripeInvoice.period_end), invoiceDate: fromEpochSeconds(stripeInvoice.date), diff --git a/src/universal/modules/userDashboard/components/OrgBilling/OrgBilling.js b/src/universal/modules/userDashboard/components/OrgBilling/OrgBilling.js index e10a3889541..da3050ad276 100644 --- a/src/universal/modules/userDashboard/components/OrgBilling/OrgBilling.js +++ b/src/universal/modules/userDashboard/components/OrgBilling/OrgBilling.js @@ -14,6 +14,7 @@ import appTheme from 'universal/styles/theme/appTheme'; const OrgBilling = (props) => { const { invoices, + invoicesReady, styles, org } = props; @@ -48,14 +49,21 @@ const OrgBilling = (props) => { }
- {invoices.length === 0 ? + {invoicesReady && invoices.length === 0 &&
No invoices yet! Keep doing good work, and we'll do the accounting. -
: +
+ } + {invoicesReady && invoices.length > 0 && invoices.map((invoice, idx) => ) } + {!invoicesReady && +
+ Loading Invoices... +
+ }
diff --git a/src/universal/modules/userDashboard/containers/OrgBilling/OrgBillingContainer.js b/src/universal/modules/userDashboard/containers/OrgBilling/OrgBillingContainer.js index d19412e9faa..6c7f6140f91 100644 --- a/src/universal/modules/userDashboard/containers/OrgBilling/OrgBillingContainer.js +++ b/src/universal/modules/userDashboard/containers/OrgBilling/OrgBillingContainer.js @@ -45,22 +45,24 @@ query { const mapStateToProps = (state, props) => { const {orgId} = props; - const {organization: org, invoiceList} = cashay.query(organizationContainerQuery, { + const res = cashay.query(organizationContainerQuery, { op: 'orgBillingContainer', key: orgId, variables: { orgId, first: 5 } - }).data; + }); + const {data: {organization: org, invoiceList}, status} = res; return { invoiceList, - org + org, + invoicesReady: status === 'complete' }; }; const OrgBillingContainer = (props) => { - const {dispatch, invoiceList, org} = props; + const {dispatch, invoiceList, invoicesReady, org} = props; if (!org.id) { return ; } @@ -68,6 +70,7 @@ const OrgBillingContainer = (props) => { ); From fbc6983b067a0269f3f2de997e82e3d1d4572e6d Mon Sep 17 00:00:00 2001 From: mattkrick Date: Tue, 28 Feb 2017 09:32:10 -0800 Subject: [PATCH 3/3] lint --- src/server/graphql/models/Action/actionQuery.js | 1 - src/server/graphql/models/Organization/organizationQuery.js | 2 +- .../newTeam/containers/NewTeamForm/NewTeamFormContainer.js | 4 +++- .../modules/userDashboard/components/OrgBilling/OrgBilling.js | 1 + .../containers/CreditCardModal/CreditCardModalContainer.js | 2 +- .../containers/OrgBilling/OrgBillingContainer.js | 1 + .../containers/UserActionList/UserActionListContainer.js | 1 + 7 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/server/graphql/models/Action/actionQuery.js b/src/server/graphql/models/Action/actionQuery.js index 76475e846ec..9048e757f22 100644 --- a/src/server/graphql/models/Action/actionQuery.js +++ b/src/server/graphql/models/Action/actionQuery.js @@ -1,6 +1,5 @@ import getRethink from 'server/database/rethinkDriver'; import {GraphQLNonNull, GraphQLID, GraphQLInt} from 'graphql'; -import {Action} from './actionSchema'; import {requireSUOrSelf} from 'server/utils/authorization'; export default { diff --git a/src/server/graphql/models/Organization/organizationQuery.js b/src/server/graphql/models/Organization/organizationQuery.js index c1feb74192b..644a9c14304 100644 --- a/src/server/graphql/models/Organization/organizationQuery.js +++ b/src/server/graphql/models/Organization/organizationQuery.js @@ -21,7 +21,7 @@ export default { // RESOLUTION return await r.table('Organization') .getAll(userId, {index: 'orgUsers'}) - .count() + .count(); } }, orgDetails: { diff --git a/src/universal/modules/newTeam/containers/NewTeamForm/NewTeamFormContainer.js b/src/universal/modules/newTeam/containers/NewTeamForm/NewTeamFormContainer.js index 1958dbfb5fd..abd36b64a6e 100644 --- a/src/universal/modules/newTeam/containers/NewTeamForm/NewTeamFormContainer.js +++ b/src/universal/modules/newTeam/containers/NewTeamForm/NewTeamFormContainer.js @@ -116,7 +116,7 @@ class NewTeamFormContainer extends Component { render() { const {initialOrgCount, initialValues, isNewOrg, organizations, router} = this.props; if (initialOrgCount === 0) { - router.push('/newteam/1') + router.push('/newteam/1'); } else if (!initialValues.orgId) { return null; } @@ -136,6 +136,8 @@ class NewTeamFormContainer extends Component { NewTeamFormContainer.propTypes = { dispatch: PropTypes.func.isRequired, + initialOrgCount: PropTypes.func.number, + initialValues: PropTypes.object, isNewOrg: PropTypes.bool, organizations: PropTypes.array, router: PropTypes.object.isRequired, diff --git a/src/universal/modules/userDashboard/components/OrgBilling/OrgBilling.js b/src/universal/modules/userDashboard/components/OrgBilling/OrgBilling.js index da3050ad276..81b96433fc2 100644 --- a/src/universal/modules/userDashboard/components/OrgBilling/OrgBilling.js +++ b/src/universal/modules/userDashboard/components/OrgBilling/OrgBilling.js @@ -72,6 +72,7 @@ const OrgBilling = (props) => { OrgBilling.propTypes = { invoices: PropTypes.array, + invoicesReady: PropTypes.bool, styles: PropTypes.object, org: PropTypes.object }; diff --git a/src/universal/modules/userDashboard/containers/CreditCardModal/CreditCardModalContainer.js b/src/universal/modules/userDashboard/containers/CreditCardModal/CreditCardModalContainer.js index 9dc13b4fca8..064ab4a47a8 100644 --- a/src/universal/modules/userDashboard/containers/CreditCardModal/CreditCardModalContainer.js +++ b/src/universal/modules/userDashboard/containers/CreditCardModal/CreditCardModalContainer.js @@ -96,7 +96,7 @@ class CreditCardModalContainer extends Component { checkCardType = (e) => { const {stripeCard} = this.props; - if (e.currentTarget.value) { + if (stripeCard && e.currentTarget.value) { const type = stripeCard.cardType(e.currentTarget.value); const cardTypeIcon = cardTypeLookup[type]; if (cardTypeIcon !== this.state.cardTypeIcon) { diff --git a/src/universal/modules/userDashboard/containers/OrgBilling/OrgBillingContainer.js b/src/universal/modules/userDashboard/containers/OrgBilling/OrgBillingContainer.js index 6c7f6140f91..c4f9a11294e 100644 --- a/src/universal/modules/userDashboard/containers/OrgBilling/OrgBillingContainer.js +++ b/src/universal/modules/userDashboard/containers/OrgBilling/OrgBillingContainer.js @@ -79,6 +79,7 @@ const OrgBillingContainer = (props) => { OrgBillingContainer.propTypes = { dispatch: PropTypes.func, invoiceList: PropTypes.array, + invoicesReady: PropTypes.bool, org: PropTypes.object }; diff --git a/src/universal/modules/userDashboard/containers/UserActionList/UserActionListContainer.js b/src/universal/modules/userDashboard/containers/UserActionList/UserActionListContainer.js index 13c747593de..2518929ff92 100644 --- a/src/universal/modules/userDashboard/containers/UserActionList/UserActionListContainer.js +++ b/src/universal/modules/userDashboard/containers/UserActionList/UserActionListContainer.js @@ -96,6 +96,7 @@ const UserActionListContainer = (props) => { UserActionListContainer.propTypes = { actions: PropTypes.array, dispatch: PropTypes.func, + initialActionCount: PropTypes.number, queryKey: PropTypes.string, selectingNewActionTeam: PropTypes.bool, teams: PropTypes.array,