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

Major core rewrite! #431

Merged
merged 11 commits into from
Aug 3, 2018
Merged

Major core rewrite! #431

merged 11 commits into from
Aug 3, 2018

Conversation

taoeffect
Copy link
Member

@taoeffect taoeffect commented Jun 20, 2018

Removed dependencies

  • protobufjs
  • objection
  • knex
  • sqlite3
  • superagent (pure fetch-based codebase now)

shared/events.js rewrite

  • New GIMessage core class
  • Now virtually everything moved under model/contracts/*
  • SBP is now used to contruct messages

Database improvements

  • Now Group Income contract chains are designed to work purely
    with key-value stores
  • backend/database.js and model/database.js are now virtually
    identical and in the future can be made to use the same library

Misc. improvements

  • Simplified backend/routes.js API
  • Re-added explicit .js to imports (please always include it!)
  • Replaced all Vue.events with SBP 'okTurtles.events/*'
  • Various code improvements
  • Backend tests now use same pubsub and backend API code as frontend
  • Deleted lots of classes and converted more parts of the app to SBP
  • Greatly simplified handleEvent action in state.js
  • Changed okTurtles.events API to match Vue.events behavior

### Removed dependencies

- protobufjs
- objection
- sqlite3
- superagent (pure `fetch`-based codebase now)

### `shared/events.js` rewrite

- New `GIMessage` core class
- Now virtually everything moved under `model/contracts/*`
- SBP is now used to contruct messages

### Database improvements

- Now Group Income contract chains are is designed to work purely
  with key-value stores
- `backend/database.js` and `model/database.js` are now virtually
  identical and in the future can be made to use the same library

### Misc. improvements

- Simplified `backend/routes.js` API
- Re-added explicit `.js` to imports (please always include it!)
- Replaced all `Vue.events` with SBP `'okTurtles.events/*'
- Various code improvements
- Backend tests now use same pubsub and backend API code as frontend
- Deleted lots of classes and converted more parts of the app to SBP
- Greatly simplified `handleEvent` action in `state.js`
- Changed `okTurtles.events` API to match `Vue.events` behavior
@taoeffect taoeffect requested review from hubudibu and sandrina-p June 20, 2018 05:36
@taoeffect taoeffect changed the title [WIP] Major core rewrite! Major core rewrite! Jun 22, 2018
@taoeffect
Copy link
Member Author

Copying from Slack:

- Now passes all `test/backend.js` tests
- Passes most `test/frontend.js` tests
- Is ready for your review
- Could use @aniko's help for figuring out why the frontend tests aren't passing ... and it might be because of some of the proposal stuff anyway, which has been rewritten in @aniko's PR
- So therefore I think it should be safe for @aniko to begin rebasing off of it, since she needs to change some of the proposals stuff anyway, and is working on tests for that, and in the process can maybe figure out or fix the issue with mine

var idx = subscriptions.indexOf(contractID)
var method = data.add ? 'sub' : 'unsub'
if ((data.add && idx > -1) || (data.remove && idx === -1)) {
return // if already subscribed or already unsubscribed
Copy link
Contributor

Choose a reason for hiding this comment

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

Usually a function should always return something, even if false only, that I believe it's what's intended here. return false

Copy link
Member Author

Choose a reason for hiding this comment

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

This function doesn't return anything, so returning undefined is perfectly fine

if (state.proposals[data.proposalHash]) {
state.proposals[data.proposalHash].for.push(data.username)
let threshold = Math.ceil(state.proposals[data.proposalHash].threshold * Object.keys(state.profiles).length)
if (state.proposals[data.proposalHash].for.length >= threshold) {
Copy link
Contributor

Choose a reason for hiding this comment

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

state.proposals[data.proposalHash] is used 4 times here. what about having a variable?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm avoiding messing with the old proposals code as I hope @hubudibu will be able to rewrite it and improve upon it, so my messing with it right now probably wouldn't be worthwhile

'okTurtles.events/on': function (event: string, handler: Function) {
sbp('okTurtles.data/add', `events/${event}/listeners`, handler)
sbp('okTurtles.data/add', listenKey(event), handler)
Copy link
Contributor

Choose a reason for hiding this comment

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

Cool work here! The new logs on the console can be helpful!

I played a little with sbp() locally during this CR and some questions came to my mind:

  • Does sbp have a naming conventions for the selectors? I saw selectores in camelCase and kebab-case, (backend/publishLogEntry vs gi/contract/create-action) and I believe we should sticky with one, similar to what Vue did with events names (kebab-case).

  • Do we have guidelines to decide the scope of a selector? backend, gi, okturtles, namespace, state, etc... It's not hard to give the same name twice. How do you plan to avoid this besides using an SBP logging facility?

  • I registered my first sbp() using 'sbp/selectors/register' and 'okTurtles.events/on' with ease!

// GroupDashboard.vue

created () {
  sbp('sbp/selectors/register', {
    'test/selector-1': (origin) =>
      console.log('My first selector from ' + origin)
  })

  sbp('okTurtles.events/on', 'hello', (origin) =>
    console.log('My first hello emit from ' + origin)
  )
}
// Voting.vue

handleVoteFor () {
  sbp('test/selector-1', 'selector-voting')
  sbp('okTurtles.events/emit', 'hello', 'emit-voting')

  // ...
},

And both logged

> My first selector from selector-voting
> My first hello emit from emit-voting

So, it looks like they both do the same but with different usages. Can you tell/explain me the core difference between both and when should I use one or another?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes indeed they look somewhat similar but the semantics are rather different, for example:

  • You can emit an event that has no listeners but you cannot call a selector that hasn't been registered
  • A single event can have multiple listeners but a selector can have only one function registered for it
  • When you emit an event there is no return value, but when you can a selector there is

etc.

Generally speaking, for all Vue related things you should probably use events only. Selectors should be registered only for things that can be conceptualized as services. In this way events are a bit more "light weight" than selectors.

By separating events and selectors we also have the ability to do things like add SBP filters just for events that are emitted, so for example, if we wanted to re-integrate the Vue.js Chrome debugger extension with our events system, so that okTurtles.events appear in the Vue events debugger, we could add an SBP filter to emit Vue events for every okTurtles.events that were emitted, while ensuring that SBP calls do not appear in that list.

Does sbp have a naming conventions for the selectors? I saw selectores in camelCase and kebab-case, (backend/publishLogEntry vs gi/contract/create-action) and I believe we should sticky with one, similar to what Vue did with events names (kebab-case).

I will think on this. I agree it's important but it's also a decision that requires a lot of thought and I wanted to get this PR in quickly.

Do we have guidelines to decide the scope of a selector? backend, gi, okturtles, namespace, state, etc... It's not hard to give the same name twice

So that would be called the "domain" of a selectors. And no, at the moment we haven't figured out good guidelines for that, besides making sure to only register domains for clearly defined services, and avoid name collisions.

I am considering prefixing backend etc. under the gi domain, or gi.frontend domain, but this is something that I'll need to give more thought to.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thank you for the extended clarification. It would be cool to add the emit vs register sbp usage somewhere on our docs :)

shared/sbp.js Outdated
for (const selector in sels) {
// TODO: debug log (using an SBP logging facility) if we're already registered
if (!selectors[selector]) {
selectors[selector] = sels[selector]
registered.push(selector)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we add a quick solution to warn when a selector already exists

      } else {
        console.warn(`The selector ${selector} already exists`)
      }

targetArray.push(data)
const array = _store.get(key)
if (array) {
array.push(data)
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to verify if data already exists on array value, or can it have the same data twice?

var array = store.get('bar') // ['foo', 'foo2']
array.push('foo2');

console.log(store.get('bar')); // ['foo', 'foo2', 'foo2']  // Same data twice!

Copy link
Member Author

Choose a reason for hiding this comment

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

Here it's fine to have the same data twice for performance reasons. If it's an issue for the caller they can be the ones to check for duplicate data.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oki

} else {
subscriptions.splice(idx, 1)
}
var res = await primus[method](contractID)
Copy link
Contributor

Choose a reason for hiding this comment

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

this seems like a risky few lines! how about doing & awaiting the primus sub/unsub and updating the subscriptions array after that?

shared/events.js Outdated
return blake32Hash(data)
}

export class GIMessage {
Copy link
Contributor

Choose a reason for hiding this comment

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

can we rename this file to GIMessage or something alike? events doesn't really say much about this file any more :)

}

var subscriptions = []
var primus
Copy link
Contributor

Choose a reason for hiding this comment

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

I’ve been thinking why it was so hard for me to process this file and I think we can make it much easier to understand with a few naming changes.

  • primus for me means a) the god of transformers b) a tool that we use in another file. a name saying what it does here would be more helpful, eg. socket or, more specifically, contractSocket, as this seems to be for syncing contracts?
  • similarly, I had an aha moment when I realized that subscriptions is storing contracts and that’s what is referred to as rooms at other places, so contractSubscriptions or contractRooms or something alike could be more helpful?

// and when that GIMessage is received and is applied as a mutation
vuexModule.mutations[name] = function (state, data) {
validate(data.data)
vuex.mutation(state, data)
Copy link
Contributor

Choose a reason for hiding this comment

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

the naming here is a bit misleading, I just spent a considerable amount of time trying to figure out what's this secret vuex feature that somehow constructs mutations :) where this is just a key in our contract structure. I would suggest using a name describing the concept it's for, and not the actual tool name. like contractStore or at least forVuex or something?

in general, I don't find this utility function that useful, it seems like most of what it does is make the contracts look significantly different from standard vuex modules with all these transformations, thus making them harder to read & understand.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, using vuexModuleConfig, sound ok?

Copy link
Contributor

Choose a reason for hiding this comment

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

sounds much better!


// NOTE: must check explicitely if 'constructor' is true
// because that key is defined by default on objects as some function...
if (constructor === true) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would suggest using a different name here, like isConstructor, as constructor is like a 'reserved word' in js land, and as the comment already notes, we are in some cases overwriting existing functions this way

@@ -258,79 +277,47 @@ const actions = {
// mirrors `handleEvent` in backend/server.js
async handleEvent (
Copy link
Contributor

Choose a reason for hiding this comment

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

this comment explaining what this method does seems no longer to be true

@hubudibu
Copy link
Contributor

hubudibu commented Aug 2, 2018

test fix 1 in test/frontend.js line 429:

      await n
        // .goto(page('mailbox'))
        // TODO: navigation gets redirected on login guard but nav click doesn't?
        // we might have logged in state problems
        .wait(elT('mailboxLink'))
        .click(elT('mailboxLink'))

also, what's the workflow if I want to push to someone else's PR?

@@ -21,7 +21,7 @@ exports.register = function (
if (scheme !== 'gi') return reply(Boom.badRequest('Bad authentication'))

try {
json = JSON.parse(b642str(json))
json = JSON.parse(b64ToStr(json))
Copy link
Contributor

Choose a reason for hiding this comment

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

this is definitely a way more fortunate naming convention for this function.

Copy link
Member Author

Choose a reason for hiding this comment

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

... that's a book. Can you be more specific?

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, I misread your comment, thanks!

Copy link
Contributor

Choose a reason for hiding this comment

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

I was just confirming that b64ToStr() is way better (less cryptic) than b642str().
Although the best could be base64ToString() - the less it makes me think, the less i have to decipher mnemonics in my brain the easier it is to understand.

// delete the test database if it exists
!production && fs.existsSync('test.db') && fs.unlinkSync('test.db')
Copy link
Contributor

Choose a reason for hiding this comment

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

glad to see this go too! =)

@hubudibu
Copy link
Contributor

hubudibu commented Aug 3, 2018

next test fix step: we didn't clean up properly after logout, so the message contracts got borked together. next step here: hubudibu@457cfa2

@vijayee
Copy link
Contributor

vijayee commented Aug 3, 2018

In the template of GroupDashboard.vue on line 11 there needs to be conditional rendering that checks for the current group state before trying to render its properties

 <div class="column container gi-main" v-if="currentGroupState" >
        <div class="gi-header">
          <div class='is-pulled-right'>
            <groups-min-income :group="currentGroupState" />
          </div>

          <h1 class="title is-1" data-test="groupName">
            {{ currentGroupState.groupName }}
          </h1>
          <p data-test="sharedValues">
            {{ currentGroupState.sharedValues }}
          </p>
        </div>

        <dashboard-section title="Proposals">
          <proposals :proposals="currentGroupState.proposals" />
        </dashboard-section>

        <dashboard-section title="Members" data-test="groupMembers">
          <group-members />
        </dashboard-section>

        <dashboard-section title="July Overview">
          <progress-overview />
        </dashboard-section>

        <dashboard-section title="Support History">
          <support-history :history="[1.2, 1, .85, .95, 1.05, .35]" />
        </dashboard-section>

        <dashboard-section title="Group Settings">
          <group-settings :group="currentGroupState" />
        </dashboard-section>
      </div>
    </div>

@hubudibu hubudibu merged commit f561818 into okTurtles:master Aug 3, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants