MarkUs Blog

MarkUs Developers Blog About Their Project

Archive for the ‘Uncategorized’ Category

Custom Sort Functions for React Tables

with one comment

Recently, we discovered that some columns in the new React.js Tables were not sorting the rows as expected. In particular, the GROUP NAME and REPOSITORY columns in the Submissions and Summaries views exhibited consistent but very strange behaviour, as shown in the figures below.

sort_by_group_name

sort_by_repo_name

(The COMMIT DATE column was also sorting incorrectly, but this issue was less mysterious in that it was obviously performing string comparison on the dates. In order to correctly parse the date strings into a numerically comparable value, a customized sorting method was definitely required.) I was assigned the task of refactoring the React Table class (and related classes) in order to allow customized sorting methods for columns that required special treatment.

As it was, sorting was handled by sort_by_column(). The cell values we converted to lowercase and used the dangerouslySetInnerHTML.__html property for React Components. Values of types other than string were not interpreted.

function sort_by_column(data, column, direction) {
  function makeSortable(a)
  {
    if (typeof a == 'string') {
      return a.toLowerCase().replace(' ', '');
    } else if (a.hasOwnProperty('props')) {
      // Is a react component, get innerHTML
      return a.props.dangerouslySetInnerHTML.__html.toLowerCase();
    } else {
      return a;
    }
  }
  // sorts column id
  var r = data.sort(function(a, b) {
    if (makeSortable(a[column]) > makeSortable(b[column])) {
      return 1;
    } else if (makeSortable(b[column]) > makeSortable(a[column])) {
      return -1;
    }
    return 0;
  });
  // flips order if direction descending.
  if (direction == 'desc') r.reverse();

  return r;
}

 

table.js ]

In some table columns, we use a React <span> components with dangerouslySetInnerHTML.__html set in order to properly render html contents such as links and icons in a cell. (Though not shown above, the cells in the COMMIT DATE column will contain an icon if the submission commit date is past due.)

Of course, when we attempt to sort by dangerouslySetInnerHTML.__html, we are comparing strings of html code rather than the text displayed in the table cells. Naturally, the unexpected behaviours exhibited by the GROUP_NAME and REPOSITORY columns were the result of string comparison between the hrefs for the anchor elements, rather than the hyperlink text. Since both links differed by group number, we were effectively performing an alphanumeric sort on the unformatted group numbers.

For example, group_0007 would appear below group_0011 on an ascending sort because we were comparing “11” and “7” rather than the repository names. In the first figure, the group names did not appear to follow any kind of logical order because the display text is completely unrelated to group number.

My solution was to leave the existing function makeComparable() intact, for  primary processing of sortable data, while optionally allowing a custom compare function to be passed in as needed for any particular column.

function sort_by_column(data, column, direction, compare) {
  //...
  // determine sort behaviour
  compare = compare || compare_values;

  // sort row by column id
  var sorted = data.sort(function(a, b) {
    return compare(makeComparable(a[column]), makeComparable(b[column]));
  });

  // flip order if direction descending
  if (direction == 'desc') {
      sorted.reverse();
  }

  return sorted;
}

table.js ]

The compare function to use when the table rows are rendered is stored in the Table state variable sort_compare. When the selected sort column changes sort_compare is updated to the compare member of the appropriate column.

// Header col was clicked. Adjust state accordingly.
synchronizeHeaderColumn: function(sort_column, sort_direction) {
  var compare_func = this.props.columns.filter(function(col) {
      return col.id == sort_column;
  })[0].compare;
  this.setState({
    sort_column: sort_column,
    sort_direction: sort_direction,
    sort_compare: compare_func
  });
}

table.js ]

Since we have the default comparison function compare_values, we are able to leave compare undefined for any given column.

function compare_values(a, b) {
  if (!b || a > b) {
    return 1;
  } else if (!a || a < b) {
    return -1;
  }
  return 0;
}

table_sorts.js ]

Now we define functions such as compare_anchor_text() to handle special cases, and assign the custom sort functions to column objects. In the code below, notice that the SECTION column does not define a compare member since the data is just a string and so the default compare_values will suffice.

function compare_anchor_text(a, b) {
  function parse_anchor(a) {
    var open_tag_end = a.indexOf('>', a.indexOf('<a'));
    var close_tag_start = a.indexOf('</a', open_tag_end + 1);
    if (open_tag_end !== -1 && close_tag_start !== -1) {
      return a.substring(open_tag_end + 1, close_tag_start);
    }
    return a;
  }

  return compare_values(parse_anchor(a), parse_anchor(b));
}

table_sorts.js ]

var SubmissionsTable = React.createClass({
  getDefaultProps: function() {
    // Defines the columns used for the table and whether they
    // are sortable searchable. The default initially sorted
    // column is the first sortable column in the array.
    return {
      columns: [
        {
          id: 'group_name',
          content: '<%= j raw I18n.t(:'browse_submissions.group_name') %>',
          sortable: true,
          compare: compare_anchor_text,
          searchable: true
        },
        {
          id: 'repository',
          content: '<%= j raw I18n.t(:'browse_submissions.repository') %>',
          sortable: true,
          compare: compare_anchor_text,
          searchable: true
        },
        {
          id: 'section',
          content: '<%= j raw I18n.t(:'browse_submissions.section') %>',
          sortable: true,
          searchable: true
        },
//...

_submissions_table.js.jsx.erb ]

Written by Victoria Verlysdonk

November 25th, 2014 at 10:51 pm

Posted in Uncategorized

MarkUs Repository Animation

without comments

I came across a tool called Gource that creates a visualization for a Git, SVN, CVS or Mercurial repository. I thought it would be interesting to see how Markus has evolved over the years. This animation shows files being created, edited and deleted as well as the people making the changes.

Video Link: Markus Gource

Written by Daniyal Liaqat

October 17th, 2013 at 4:01 pm

Posted in Uncategorized

Introducing our Fall 2010 Team

with 3 comments

We are happy to have 7 team members working on MarkUs us for the fall 2010 term!  Since we are currently spread across the country, this blog post will hopefully serve to allow ourselves to get to know each other before our first opportunity to meet in person.

I’d also like to mention Mike Conley and Severin Gehwolf, two MarkUs alumni who continue to provide an enormous amount of support and advice!

Here we are, in alphabetical order:

Evan Browning
U of T

My name is Evan Browning, and I’m a third year Computer Science student at U of T.  I worked on MarkUs over the summer months, and previously I worked at the Ontario Ministry of Natural Resources and for the Assistive Technology Resource Centre at U of T (now the Inclusive Design Institute).  Besides programming I love to do film and video stuff, and especially things related to visual effects.  I also love to explore new places, both in the city and in the wilderness 🙂


Horatiu Halmaghi

SFU

My name is Horatiu Halmaghi and I am nearing the completion of my Computing Science degree from Simon Fraser University. I currently live in Burnaby, Canada but at different times in my life I’ve also called Montreal, Sibiu (Romania), and Prague (Czech Republic) my home.

I started programming in my first year of university, but in that time I’ve come to enjoy a number of different languages and technologies. I have a great deal of experience working on web projects, having worked on a few applications in Django as well as some more fun work in JavaScript and AJAX. The most fun project I’ve worked on was creating a Live Draft system for a hockey pool application.

Which reminds me: one of my greatest passions is hockey! I love to watch the sport and I play regularly on a roller hockey team in Vancouver. The only two other things I love as much as hockey are photography and traveling the globe.


Victor Ivri
York

My name’s Victor, I’m in the last year of a Computer Science and Cognitive Science double major here at York U. Been working in the industry as a C++ and C# developer on and off for a while, did a Google Summer of Code in ’08 (greatest experience ever, highly recommended!). Lately my interests have brought me to web development, so here I am :).

Besides that I do all sorts of things – especially if it’s outdoors, and especially if it’s far away from the city.

Misa Sakamoto
U of T

My name is Misa and I’m a 4th year in CS: Software Engineering at  UofT.  This summer term, I worked on a web-based repository browser project for Basie, using Pinax/Django.  I also just finished a 16month  internship at IBM. Aside from programming, I’m also interested in psychology, teaching, and dogs 🙂

Kurtis Schmidt
U of M

My name is Kurtis Schmidt.  I am currently completing an Honours Degree in Computer Science at the University of Manitoba in Winnipeg.

I began University working towards a degree in Computer Engineering.  However, after two years I realized I preferred writing software to designing hardware so I changed majors to Computer Science.  Since then I have worked at three different software companies.  At Frantic Films VFX (now Prime Focus VFX) I was a 3D software developer.  My specific work involved mesh manipulation and implicit surface mathematics.  My second position was at Research in Motion in Waterloo, ON.  I worked on the Network and Protocol Analysis team creating automated test suites.  Finally, my last position was at Electronic Arts in Burnaby, BC.  I worked for an internal tools team who created and maintained a procedural animation tool used across EA.

In my spare time I develop OSX/ios software using the Cocoa frameworks. As well, I enjoy film, television, and video games; playing hockey; and swimming.


Vivien Suen
U of T

My name is Vivien Suen, and I’m a fourth year Computer Science student  specializing in Software Engineering here at the University of  Toronto. Previously, I did a bit of research on reversible computing  at the University of Lethbridge, and I helped with research on social  networking as well as develop a Facebook application this past summer  at the University of Toronto.

As for interests, I really enjoy web programming and web design, I  love music and my piano, and playing badminton and baking are two of  my favourite hobbies. One of my life goals is to open a cake shop of  my own one day. Last but not least, I also enjoy my occasional dose of  WoW and SC2.

I’m excited to be working on MarkUs, and I’m looking forward to  working with you this term!

Jiahui Xu
SFU

My name is Jiahui(Sherry) Xu, and I’m in the fourth year of my Computing Science degree at SFU. I’m from mainland China. I live in Vancouver, close to Metrotown skytrain. I spent my first two years of this degree in Zhejiang University (China), and joined a Dual Degree Program which transferred me to SFU.

In terms of Web development experiences, I’m most familiar with Django framework and jQuery. I usually work on my Mac but are also good with other OS. I’ve also learnt PHP and CodeIgniter on my own before and have done a project based on that. Ruby on Rails is pretty new to me, but that’s also the reason I chose this project because I would like to learn some new cool tools. Other than the above, I’m good at C and C++ and have done a bunch of projects for my courses.

I like to sit in cafes and program, that’s my ideal spot for programming. I used to love shopping for clothes online in China because they offer them at very low prices. Now I like swimming in order to stay healthy since life as a programmer is stressful and sometimes tiring. Hopefully if I have time I can expolor the swimming pools at Toronto 😀

Written by Evan

September 18th, 2010 at 3:23 pm

Posted in Uncategorized

Dashboard

with 2 comments

MarkUs has a new elegant dashboard. The dash board displays

–       A description of the assignment.

–       Average and median (both in percentage and out of the total mark)

–       Number of groupings that submitted the assignment and how many had been marked.

There are three graphs:

1.      TA progress: each TA which is assigned to work in this assignment has stacked bars to show how many groupings have he/she completely marked and how many left.

2.      Assignment marks:  there are ten bars. The bars are labeled form 0-10% … 90-10% and each bar presents the number of groupings that have marks fall in that category.

3.      Number of submission: each bar presents number of submissions done by each groupings.

If a graph does not have any data to display (for example if no marking has been done or no submission done by any grouping) then the relative graph is not going to appear.

The assignments are ordered by the last time they were updated.

Written by g9sabied

August 22nd, 2010 at 10:56 am

Posted in Uncategorized

Tagged with

MarkUs Performance Enhancements

with one comment

This summer, one of my more interesting tasks was to speed up MarkUs, namely to hasten the loading of the dreaded submissions view. Going into this, I had a semi-decent understanding of rails, and very little knowledge of databases. I had started by looking at ticket #174 about indexing foreign keys. Although most of the indexing has already been done, I learned about a certain ‘N+1 query’ problem that is common in many applications using databases.

A short description of the problem:

Picture a ‘student’ object having N ‘lecture’ objects that they attend daily. Our application keeps track of all the students, and stores them in a database. Say we want to change the time of each ‘lecture’ for a given ‘student’. Our application logic would loop over student.lectures and perform an action on each ‘lecture’. The app performs 1 database query to select the ‘student’, and N more database queries, one to select each ‘lecture’. This is the essence of the problem, as database queries are expensive.

In the case of MarkUs, the submissions table had hundreds of rows. Each row represented a grouping, and in order to populate the table (each column – group name, final mark, grace credits, etc… – required more information about the grouping), each row needed about 7 extra queries. With database queries in the thousands, it could take over a minute for the page to load.

The solution for this is Rails’ eager loading. When you know that with each grouping you will need info about their submissions, results, members, etc… you simply tell Rails to include all that information in a single large query. Eager loading in rails is done by adding the :include option option to any ‘find’ method calls. Simply by preloading all the required information for the submissions view, I was able to speed up the loading of the page by several times. Of course redundant eager loading can actually slow down the application, but a discussion about eager loading is not the point of this post.

Finding N+1 Queries in Code:

In ticket #174 Mike Gunderloy suggests using a gem called bullet to find the queries. It is very simple to install, and gives feedback right in the console running the server. The gem notices when you have N+1 queries, gives you a specific :include statement and tells you where to place it in the code. I began using this gem and it did work a few times, however in many cases it simply led me in circles, first saying to add an :include somewhere to get rid of N+1 queries, and then saying I am using redundant eager loading in the same place.

Bullet was something I used at first to get me started, however I discontinued using it because the console already displayed to me each SQL query that MarkUs was doing, and I found this to be helpful enough. N+1 queries usually happen on pages which are iterative in nature (large tables for example), but are not limited to them. A good way to detect them is to look at the console, and see if there are a lot of very similar database queries that differ only by the id. For example,  200

Grouping Load (1.5ms)   SELECT * FROM “groupings” WHERE (“groupings”.”id” = some number)

would suggest that it is possible to load all the groupings with one query (usually by adding an :include somewhere).

Other Issues:

Apart from N+1 queries, there were some other issues which slowed down the application, but most of them had to do with excessive database queries. For example, in the submissions view, each grouping’s total mark would be re-calculated every time the page was loaded (the calculation involved several queries per grouping). This was a case where it was much better to simply add a ‘total_mark’ column in the groupings database table, which would be updated every time a mark is changed (thanks to Rails’ wonderful before_save callback).

I have discussed some of the more significant issues I have encountered, however these are not the only ones that will happen. When tweaking our code to optimize it, I was always aiming for as little database queries as possible. I found that in many cases, it is more desirable to have a bit of extra processing done if it involved reducing the number of database queries.

Grouping Load (1.2ms)   SELECT * FROM “groupings” WHERE (“groupings”.”is_collected” = ‘t’) AND (“groupings”.assignment_id = 1)

Grouping Load (1.2ms)   SELECT * FROM “groupings” WHERE (“groupings”.”is_collected” = ‘t’) AND (“groupings”.assignment_id = 1)

Written by c8braver

August 16th, 2010 at 10:33 am

Posted in Uncategorized

PDF Conversion Heavy on Memory Use

without comments

We use ImageMagick to convert pdfs to jpgs to display them within the browser. Originally, pdf to jpg conversion was done with the RMagick gem for ruby, which was a nice api for ImageMagick. This worked fine until we started testing with pdf files containing hundreds of pages. The problem that went unnoticed until then was the amount of memory ImageMagick actually required to convert files. It was quickly gobbling up gigabytes of RAM and taking over the entire swap memory. Fortunately, we noticed this and managed to kill the process in time before our computers crashed.

After doing some research, i found a blog post at http://www.salas.com/2009/06/19/geeky-rmagick-and-memory-leaks/. This talks about the issues of using RMagick, namely the memory leaks it creates as ruby’s garbage collector doesn’t play nice with it. Upon further investigation, I found that this leak made MarkUs use 3 times as much memory as was needed for conversion. Furthermore, the post described a function designed to free memory. However due to the nature of RMagick, this meant that we would be using at least twice necessary memory used to convert the files by ImageMagick, as RMagick would save an object corresponding to an image, and create a new one for every manipulation performed on it. Thus there was always an unavoidable window after a modification and before the original image object can be destroyed to free its memory.

As a result, I have opted for issuing a direct call to ImageMagick’s convert function via ruby.  This is a much better alternative, as it performs all the modifications in one line. The memory use issue still wasn’t solved here though. After more digging I found this formula on ImageMagick’s site http://www.imagemagick.org/script/advanced-unix-installation.php

Memory use = (5 * Quantum Depth * Rows * Columns) / 8

Where Rows and Columns were the size of the pdf file, and quantum depth was a setting configured at the pre-compilation phase of ImageMagick. It was impractical to reduce the quantum depth for several reasons. Firstly, having to recompile ImageMagick would not be very fun, especially since this would have to be done for every server MarkUs is hosted on. Secondly, picture quality would be severely compromised, as quantum depth is responsible for the number of colours in the image (setting it to 8 would impose a 256 colour restriction and we live in 2010 now…). Lastly, the quantum depth was currently set at 16, and its minimal value was 8. All that extra headache would only cut memory use in half, and still not prevent malicious users from crashing server simply by using a pdf file twice as big.

After more headaches, (I blame myself for them, as the solution should have been so obvious) I took a close look at all the optional arguments the convert command takes. The -limit option was exactly what I was looking for – being responsible developers, the ImageMagick team knew of its heavy memory requirements and created a solution for cases just like ours. The documentation can be found here http://www.imagemagick.org/script/command-line-options.php#limit. Essentially, this option allowed me to limit the amount of memory that the convert command was allowed to use. Now, instead of eating up all the server’s RAM and swap, convert gets all the memory it needs from the hard disk, where space is not an issue anymore.

The downside of course, is significantly slower conversion times when the hard disk is used. To counteract this, I have added an option in the markus configuration that will allow sysadmins to set the amount of RAM they want to be accessible by ImageMagick. We found that 100 megabytes will take care of most conversion needs, and should be enough to convert a 10 page US letter sized pdf, and the hard disk only needs to kick in for those extra big files.

This post is meant to be a guide to anyone who decides to tinker with the pdf conversion system in the future, as well as aiding admins in the (highly unlikely…. hopefully) case something goes wrong.

Written by c8braver

July 19th, 2010 at 4:38 pm

Posted in Uncategorized

Making Development Faster on Firefox

without comments

I noticed that loading MarkUs from localhost with Firefox was taking much much longer than it took with Chrome or even IE.  I thought this was just due to my many development add-ons but when I looked at the page loading with firebug I noticed that most of the time was spent on DNS lookup.  From searching online, it seems that this is a known issue that occurs because Firefox is trying to find an IPV6 address for localhost but only receiving an IPV4 address.

To make things faster, go to about:config and set network.dns.ipv4OnlyDomains to the value “localhost”.  Now Firefox only tries to find an IPV4 address 🙂

Written by Evan

June 29th, 2010 at 11:02 am

Posted in Uncategorized

Notes from Accessibility Meeting with Dan

with one comment

I met with Dan today to discuss his experiences using MarkUs and what suggestions he would have for improving its accessibility.  His first comment was that MarkUs is a mess, enough so that he ended up hacking the databases instead of using the web interface for most things.  However, it’s encouraging that MarkUs isn’t the grading system that he finds the most frustrating 😉

Here are the most important issues that he would like dealt with:

  • Currently, in order to read annotations, you have to mouse over the corresponding line of code.  It would be great to have an alternate format, perhaps available for download by instructors, that inserts the annotations as comments into the code files themselves.  This would make it easy for blind users to review annotations in context.
  • There is a problem with the groups and graders tab that prevents him from adding, removing or moving students between groups.  Since we are planning to redo this section of MarkUs anyway, that is a perfect opportunity to make sure the new groups page is accessible!  It would also be good to be able to download and upload group lists in a more palatable format, something that begins by listing the students in the group for example.  The format (this may not be the same as the new csv format) that Dan was using had the “group_####” name followed by a large list of TAs, before finally eventually getting to the students that are actually in the group, so Dan found it difficult to navigate.
  • It would be great to give instructors a link to the SVN repositories of groups, and maybe a way to download a file containing all the repositories of every student.  Dan would be much more able to commit a file with comments it to a student’s repository than he would be able to use our visual annotation tools.

It might not be feasible to do everything that would help with accessibility, but if we can do some of these things then maybe we can stop Dan from editing databases!  He is very interested in helping us out and will set up a development environment on his machine so that he can keep track of our progress and make suggestions.

It was good to hear that some issues have already been fixed.  Dan wanted a way to re-upload rubrics after he had downloaded them, and the latest MarkUs supports that.  He also very much wanted to be able to reorder criteria without using a mouse, and that fix is currently on ReviewBoard.  I’m really happy that someone will be able to immediately benefit from my code!

Written by Evan

June 11th, 2010 at 11:10 am

Posted in Uncategorized

March 19th Punchlines

without comments

Victoria

Status

  • Brainstormed ideas for the Dashboard.
  • Went through draft Dashboard contents with Karen and Farah and got some good and constructive feedback

Next Steps

  • With the feedback I got back regarding the potential Dashboard contents, come up with a preliminary prototype — one that we can take apart and help establish a better idea as to how we’d like to design the Dashboard.

Roadblocks

  • Swamped with work (from my full-time position).

Mike

Status

  • Applied 0.6 patches to trunk
  • Also caught some missing translations and brought them over from 0.6
  • Reviewed a bunch of code
  • Email / IRC development support

Next Steps

  • Same as always – review, review, review.  Answer email.  Help where I can.

Roadblocks

  • None

Farah

Status

Next Steps

  • Work on functional tests for the student UI and releasing the marks
  • Implement CSV upload/download for grade entry forms
  • Tests for CSV upload/download

Roadblocks

  • None this week

Joseph

Status

Next Steps

  • continue with the testing
  • finish of the suggestions from reviewers
  • get a start on the detailed submissions view

Roadblocks

  • assignments

Bryan

Status

  • Wrote a draft version of user stories: assigning graders to criteria.
  • Introduce factory data preloader, which can be used in conjunction with Machinist and improve its speed. Modified the results_controller_test.

Next Steps

  • Finish the class diagram and database schema for the feature “assigning graders to criteria”
  • Plan how to use single table inheritance to implement flexible and rubric criterion
  • Find a good pattern for factory data preloader

Roadblocks

  • None

Robert

Status

  • The ability to create Notes on Assignments, and Students from the new notes page is complete and awaiting final approval.
  • Added tests for the new noteables to the Notes Controller Test
  • Converted Notes Controller Tests to Machinist
  • Added a Machinist blueprint for Notes.

Next Steps

  • Add the notes dialog links to the Student pages and the Assignment pages.
  • Convert the Student and Assignment tests to Machinist and Shoulda.

Roadblocks

  • None!

Brian

Status

  • Fix a small bug Ticket #622
  • Working on refactoring database, doing some test
  • Improving flexible schemes

Next Steps

  • make flexible scheme ready to ship
  • make prototype of new database design

Roadblocks

  • none

Written by jmate

March 19th, 2010 at 2:24 pm

Posted in Uncategorized

March 12th Punchlines

without comments

Victoria

Status

  • Met up with Karen in person on Monday to discuss feedback we got back from usability session held 2 weeks ago.
  • We also discussed the basis for the Dashboard view — what should be included, what to look into..etc.

Next Steps

  • Come up with a list of design and function requirements for Dashboard view.
  • Draft possible designs/layouts that could be used for the Dashboard view.
  • Possibly go through more UI/UX Review Board requests as needed.

Roadblocks

  • Things are a little crazy at work.  Hopefully everything will start settling down in a few weeks.

Mike

Status

  • Finally merged changes from 0.6 into trunk
  • Reviewed a ton of code
  • Some support via email

Next Steps

  • Continue what I’m doing – I’m bracing myself for the end of term crunch

Roadblocks

  • None

Farah

Status

  • Made averages for grade entry forms also appear on a student’s main page
  • Submitted a review request for the student UI (see Review Request #430 )
  • Started to work on releasing the marks

Next Steps

  • Finish implementing the ability to release the marks
  • Work on unit tests and functional tests for the student interface and releasing the marks
  • Implement CSV upload/download for the grades table

Roadblocks

  • Ran into issues in a couple other courses. Will spend extra time on MarkUs this weekend.

Brian

Status

  • Update Flexible scheme alpha 1.0 with all desired functions

Next Steps

  • Changing database scheme for criteria

Roadblocks

  • None

Joseph

Status

  • Completed a bunch of reviews
  • went over the recommendations from the review: http://review.markusproject.org/r/407/ – Switching to Serverside Pagination

Next Steps

  • testing

Roadblocks

  • figuring out the test and creating the proper groupings

Bryan

Status

  • Opened review request for ResultControllerTest. Considering how to reduce the performance overhead of Machinist.
  • Wrote a tutorial of using Machinist in Markus.
  • Thinking of some problems related with Assigning TAs to Rubrics

Next Steps

  • Discuss with Karen about the use cases of Assigning TAs to Rubrics
  • Finish the ResultControllerTest

Roadblocks

  • None

Robert

Status

  • Have offers from Google, Amazon, and Microsoft to consider!
  • Kept up with reviews, but didn’t do very many.

Next Steps

  • Do/Finish notables, as it has been all term.

Roadblocks

  • Interviews in Seattle. -> From here on in, I have guaranteed certified Free Time(tm).

Written by Robert B

March 12th, 2010 at 4:58 pm

Posted in Uncategorized