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

Add basic functionality (WIP) #1

Merged
merged 11 commits into from
Nov 13, 2017
Merged

Add basic functionality (WIP) #1

merged 11 commits into from
Nov 13, 2017

Conversation

SaladRaider
Copy link
Collaborator

Starter code.

@SaladRaider
Copy link
Collaborator Author

@yuvipanda basic functionality seems to be done. I haven't added any tests yet. Wanted to wait on your opinion of how it would be best to write those for this extension. IMO, it looks like unit tests might not be too helpful.

My initial thoughts were to run selenium tests with travis, and then have it check the correctness of the zipped contents.

Copy link
Contributor

@yuvipanda yuvipanda left a comment

Choose a reason for hiding this comment

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

This is pretty awesome :)

I think the way to test this would be not to use selenium, but:

  1. Set up a directory layout
  2. Start notebook process with the extension installed in the background
  3. Call the eventstream API
  4. Make sure that the output is what we expect, including finally built zipfile.

This I think will be a lot easier than using selenium!

$(".col-sm-4.no-padding.tree-buttons").attr('class', 'col-sm-8 no-padding tree-buttons');

$('#notebook_toolbar .pull-right').prepend(
$('<div>').addClass('btn-group').attr('id', 'nbgdrive-link').prepend(
Copy link
Contributor

Choose a reason for hiding this comment

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

should be nbzip-link maybe?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

oops, yeah your right.



var substatus_messages = [
"Adding Hidden Agendas",
Copy link
Contributor

Choose a reason for hiding this comment

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

hahaha this is cool :D

@SaladRaider
Copy link
Collaborator Author

@yuvipanda thank you for the great idea of how to test this! I tried implementing this approach in the past few commits. The test first tries to check the json output of the eventstream and then checks to see if it can unzip the zipped file and sees if all the correct files are there with the right contents, and there aren't any extra files.

@yuvipanda
Copy link
Contributor

yuvipanda commented Oct 9, 2017

Thanks @SaladRaider. The tests look great!

I just realized (thanks to a conversation with @mpacer who pointed it out) there's a better way to do this: On clicking the download button, just generate the zip file in memory and stream it out directly! Does not need intermediate UI, and does not need temporary files to be stored on disk (which will create problems when two zip files are attempted to be downloaded simultaneously).

https://stackoverflow.com/a/21361057 has some example code on how to do this, and I think that'll let us eliminate the whole eventstream + intermediate UI business.

Sorry I didn't think of this earlier, and this might end up undoing some of your work. What do you think of this approach?

@SaladRaider
Copy link
Collaborator Author

@yuvipanda yeah, that makes a lot of sense. Should we be worried at all about the files in the notebook exceeding the amount of memory allocated to each user pod? If not, I think this approach will def be better.

@yuvipanda
Copy link
Contributor

@SaladRaider ah good question. I think adapting https://stackoverflow.com/a/19121257 to work with tornado's self.write (rather than stdout.write) should solve that problem, allowing us to stream the data out as we construct the zip file!

@SaladRaider
Copy link
Collaborator Author

@yuvipanda ok, nbzip should now stream the zip file to the user. The tests are changed as well. I'm also thinking of adding a smaller progress bar to replace the button when you press it to show progress. Or maybe, have a rotating swirly progress icon or something.

@yuvipanda
Copy link
Contributor

Cool! Does this need the intermetdiate page at all? Can we completely get rid of it?

@SaladRaider
Copy link
Collaborator Author

@yuvipanda the intermediate page is completely gone. I was talking about the visual progress stuff on the /tree page.

@mpacer
Copy link

mpacer commented Oct 16, 2017

Could you explain the use case a bit more… This sounds like it would follow directly from jupyter/notebook#2413 which we are still working on documenting before merging.

@yuvipanda
Copy link
Contributor

yuvipanda commented Oct 16, 2017 via email

@mpacer
Copy link

mpacer commented Oct 16, 2017

this chunk of code might be of particular use.

zip_filename = os.path.splitext(name)[0] + '.zip'
handler.set_attachment_header(zip_filename)
handler.set_header('Content-Type', 'application/zip')

# create zip file
buff = io.BytesIO()
with zipfile.ZipFile(buff, mode='w', compression=zipfile.ZIP_STORED) as zipf:
    output_filename = os.path.splitext(name)[0] + resources['output_extension']
    zipf.writestr(output_filename, cast_bytes(output, 'utf-8'))
    for filename, data in output_files.items():
        zipf.writestr(filename, data)

    # pass zip file back
    buff.seek(0)
    handler.finish(buff.getvalue())

# We gonna send out event streams!
self.set_header('content-type', 'application/octet-stream')
self.set_header('cache-control', 'no-cache')
self.set_header('content-disposition', 'attachment; filename=\"{}\"'.format(TEMP_ZIP_NAME))
Copy link
Contributor

Choose a reason for hiding this comment

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

This should probably be the name of the directory or somesuch?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yeah, that makes sense.


self.emit({'output': 'Zipping files:\n', 'phase': 'zipping'})

q = Queue()
Copy link
Contributor

Choose a reason for hiding this comment

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

If we have no intermediate page, what is this queue being used for? Am a bit confused :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

i left it for logging, but I guess we don't really need it. I'll remove it.

) {

// inspired by: https://stackoverflow.com/questions/1106377/detect-when-browser-receives-file-download
currToken = null;
Copy link
Contributor

Choose a reason for hiding this comment

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

var, otherwise this becomes a global variable :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ok, sounds good. thank you for the catch.

return new Date().getTime();
}

getCookie = function ( name ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can just use the web.authenticated decorator in the python code, and not implement all this code manually.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

oh, i'm using the cookie to check if the zipping is done to make the zip button reappear. But, adding the decorator for authentication is a good idea.

@mpacer
Copy link

mpacer commented Oct 16, 2017

In addition, that PR includes code so that the file can be downloaded in the background without needing to use a GET request by using a DataURI attached to a POST request that is downloaded via a simulated click on a hidden anchor triggered once the zipping has been completed.

That way you don't need to implement a download progress bar, you can rely on the native browser's download manager.

You need to use basic xhr requests because otherwise jquery will corrupt the resulting zip file by trying to interpret its encoding in a weird way.

        var xsrf_token = utils._get_cookie("_xsrf");
        var xhr = new XMLHttpRequest();
        xhr.open("POST", url, true);
        xhr.responseType = "blob";
        xhr.withCredentials = true;
        xhr.setRequestHeader("X-XSRFToken", xsrf_token);
        xhr.onreadystatechange = function() {
          if (xhr.readyState === 4) {
            var blob = xhr.response;
            var content_disposition = xhr.getResponseHeader(
              "Content-Disposition"
            );
            var filename = content_disposition.match(/filename\*=utf-8''(.+)/)[1];
            saveData(blob, filename);
          }
        };
        var data = JSON.stringify(that.json_content);
        xhr.send(data);
        return true; // close the dialog
        // return false to keep it open.
      };
      var saveData = (function() {
        var a = document.createElement("a");
        document.body.appendChild(a);
        a.style = "display: none";
        return (
          function(data, fileName) {
            var blob = new Blob([data], { type: "octet/stream" }),
              url = window.URL.createObjectURL(blob);
            a.href = url;
            a.download = fileName;
            a.click();
            window.URL.revokeObjectURL(url);
            document.body.removeChild(a);
          }
        )
      })();

@SaladRaider
Copy link
Collaborator Author

@yuvipanda I addressed some of the issues you brought up in this last commit, as well as changed the function of the button to zip the directory the user is currently browsing in.

@yuvipanda yuvipanda merged commit d3dba4b into master Nov 13, 2017
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.

None yet

3 participants