From 6f577d88d8096b71190d6a9f4e58b1b5ff2716fc Mon Sep 17 00:00:00 2001 From: Neil Fraser Date: Mon, 3 Feb 2020 14:33:59 -0800 Subject: [PATCH 1/5] =?UTF-8?q?=E2=80=98Upgrade=E2=80=99=20from=20Python?= =?UTF-8?q?=202.7=20to=20Python=203.7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Python is a non-issue. But App Engine took the opportunity to change everything. --- appengine/.gcloudignore | 23 ++++++++++++ appengine/app.yaml | 54 +++++---------------------- appengine/deploy | 16 ++++++++ appengine/index_redirect.py | 3 -- appengine/main.py | 43 ++++++++++++++++++++++ appengine/requirements.txt | 1 + appengine/storage.py | 73 ++++++++++++++++++++----------------- 7 files changed, 132 insertions(+), 81 deletions(-) create mode 100644 appengine/.gcloudignore create mode 100755 appengine/deploy delete mode 100644 appengine/index_redirect.py create mode 100644 appengine/main.py create mode 100644 appengine/requirements.txt diff --git a/appengine/.gcloudignore b/appengine/.gcloudignore new file mode 100644 index 000000000..83d812a47 --- /dev/null +++ b/appengine/.gcloudignore @@ -0,0 +1,23 @@ +# Do not upload these files. +.* +deploy +*.soy +static/appengine/ +static/closure/ +static/demos/plane/soy/*.jar +static/demos/plane/xlf/ +static/externs/ +static/i18n/ +static/msg/json/ +static/node_modules/ +static/package/ +static/theme_scripts/ +static/typings/ + +static/build.py +static/gulpfile.js +static/jsconfig.json +static/LICENSE +static/package-lock.json +static/package.json +static/README.md diff --git a/appengine/app.yaml b/appengine/app.yaml index 3cb448844..2fd93c494 100644 --- a/appengine/app.yaml +++ b/appengine/app.yaml @@ -1,8 +1,4 @@ -application: blockly-demo -version: 20200123 -runtime: python27 -api_version: 1 -threadsafe: no +runtime: python37 handlers: # Redirect obsolete URLs. @@ -19,29 +15,15 @@ handlers: static_files: redirect.html upload: redirect.html - -# Storage API. -- url: /storage - script: storage.py - secure: always -- url: /storage\.js - static_files: storage.js - upload: storage\.js - secure: always - # Blockly files. - url: /static static_dir: static secure: always -# Closure library for uncompressed Blockly. -- url: /closure-library - static_dir: closure-library - secure: always - -# Redirect for root directory. -- url: / - script: index_redirect.py +# Storage API. +- url: /storage\.js + static_files: storage.js + upload: storage\.js secure: always # Favicon. @@ -64,24 +46,8 @@ handlers: upload: robots\.txt secure: always - -skip_files: -# App Engine default patterns. -- ^(.*/)?#.*#$ -- ^(.*/)?.*~$ -- ^(.*/)?.*\.py[co]$ -- ^(.*/)?.*/RCS/.*$ -- ^(.*/)?\..*$ -# Custom skip patterns. -- ^static/appengine/.*$ -- ^static/demos/plane/soy/.+\.jar$ -- ^static/demos/plane/template.soy$ -- ^static/demos/plane/xlf/.*$ -- ^static/i18n/.*$ -- ^static/msg/json/.*$ -- ^.+\.soy$ -- ^closure-library/.*_test.html$ -- ^closure-library/.*_test.js$ -- ^closure-library/closure/bin/.*$ -- ^closure-library/doc/.*$ -- ^closure-library/scripts/.*$ +# Dynamic content. +- url: /.* + script: auto + secure: always + \ No newline at end of file diff --git a/appengine/deploy b/appengine/deploy new file mode 100755 index 000000000..7fb5e6c0c --- /dev/null +++ b/appengine/deploy @@ -0,0 +1,16 @@ +#!/bin/bash + +# Script to deploy on GAE. + +APP=./app.yaml +if ! [ -f $APP ] ; then + echo $APP not found 1>&2 + exit 1 +fi + +PROJECT=blockly-demo +VERSION=37 + +echo 'Beginning deployment...' +gcloud app deploy --project $PROJECT --version $VERSION --no-promote +echo 'Deployment finished.' diff --git a/appengine/index_redirect.py b/appengine/index_redirect.py deleted file mode 100644 index 94cc26226..000000000 --- a/appengine/index_redirect.py +++ /dev/null @@ -1,3 +0,0 @@ -print("Status: 301") -print("Location: /static/demos/index.html") -print("") diff --git a/appengine/main.py b/appengine/main.py new file mode 100644 index 000000000..533552a43 --- /dev/null +++ b/appengine/main.py @@ -0,0 +1,43 @@ +""" +Copyright 2020 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import cgi +import storage +from google.cloud import ndb + + +# Datastore model. +class Grid(ndb.Model): + data = ndb.StringProperty(required=True) + + +# Route to requested handler. +def app(environ, start_response): + if environ["PATH_INFO"] == "/": + return redirect(environ, start_response) + if environ["PATH_INFO"] == "/storage.py": + return storage.app(environ, start_response) + start_response("404 Not Found", []) + return [b"Page not found."] + + +# Redirect for root directory. +def redirect(environ, start_response): + headers = [ + ("Location", "static/demos/index.html") + ] + start_response("301 Found", headers) + return [] diff --git a/appengine/requirements.txt b/appengine/requirements.txt new file mode 100644 index 000000000..99d5d110e --- /dev/null +++ b/appengine/requirements.txt @@ -0,0 +1 @@ +google-cloud-ndb diff --git a/appengine/storage.py b/appengine/storage.py index dc4ee89e6..92c5a7f14 100644 --- a/appengine/storage.py +++ b/appengine/storage.py @@ -23,8 +23,7 @@ __author__ = "q.neutron@gmail.com (Quynh Neutron)" import cgi import hashlib from random import randint -from google.appengine.api import memcache -from google.appengine.ext import ndb +from google.cloud import ndb def keyGen(): @@ -41,46 +40,52 @@ class Xml(ndb.Model): def xmlToKey(xml_content): # Store XML and return a generated key. - xml_hash = long(hashlib.sha1(xml_content).hexdigest(), 16) + xml_hash = int(hashlib.sha1(xml_content.encode("utf-8")).hexdigest(), 16) xml_hash = int(xml_hash % (2 ** 64) - (2 ** 63)) lookup_query = Xml.query(Xml.xml_hash == xml_hash) - lookup_result = lookup_query.get() - if lookup_result: - xml_key = lookup_result.key.string_id() - else: - trials = 0 - result = True - while result: - trials += 1 - if trials == 100: - raise Exception("Sorry, the generator failed to get a key for you.") - xml_key = keyGen() - result = Xml.get_by_id(xml_key) - row = Xml(id = xml_key, xml_hash = xml_hash, xml_content = xml_content) - row.put() + client = ndb.Client() + with client.context(): + lookup_result = lookup_query.get() + if lookup_result: + xml_key = lookup_result.key.string_id() + else: + trials = 0 + result = True + while result: + trials += 1 + if trials == 100: + raise Exception("Sorry, the generator failed to get a key for you.") + xml_key = keyGen() + result = Xml.get_by_id(xml_key) + row = Xml(id = xml_key, xml_hash = xml_hash, xml_content = xml_content) + row.put() return xml_key def keyToXml(key_provided): # Retrieve stored XML based on the provided key. # Normalize the string. key_provided = key_provided.lower().strip() - # Check memcache for a quick match. - xml = memcache.get("XML_" + key_provided) - if xml is None: - # Check datastore for a definitive match. + # Check datastore for a match. + client = ndb.Client() + with client.context(): result = Xml.get_by_id(key_provided) - if not result: - xml = "" - else: - xml = result.xml_content - # Save to memcache for next hit. - memcache.add("XML_" + key_provided, xml, 3600) - return xml.encode("utf-8") + if not result: + xml = "" + else: + xml = result.xml_content + return xml -if __name__ == "__main__": - print("Content-Type: text/plain\n") - forms = cgi.FieldStorage() +def app(environ, start_response): + forms = cgi.FieldStorage(fp=environ['wsgi.input'], environ=environ) if "xml" in forms: - print(xmlToKey(forms["xml"].value)) - if "key" in forms: - print(keyToXml(forms["key"].value)) + out = xmlToKey(forms["xml"].value) + elif "key" in forms: + out = keyToXml(forms["key"].value) + else: + out = "" + + headers = [ + ("Content-Type", "text/plain") + ] + start_response("200 OK", headers) + return [out.encode("utf-8")] From 15675c69c978e991785f1e7b3f63da7a1cd1c977 Mon Sep 17 00:00:00 2001 From: Neil Fraser Date: Mon, 3 Feb 2020 16:00:49 -0800 Subject: [PATCH 2/5] Delete unneeded code. --- appengine/main.py | 7 ------- appengine/storage.py | 12 ++++++++---- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/appengine/main.py b/appengine/main.py index 533552a43..f804c120b 100644 --- a/appengine/main.py +++ b/appengine/main.py @@ -14,14 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. """ -import cgi import storage -from google.cloud import ndb - - -# Datastore model. -class Grid(ndb.Model): - data = ndb.StringProperty(required=True) # Route to requested handler. diff --git a/appengine/storage.py b/appengine/storage.py index 92c5a7f14..a3d351c32 100644 --- a/appengine/storage.py +++ b/appengine/storage.py @@ -26,6 +26,12 @@ from random import randint from google.cloud import ndb +class Xml(ndb.Model): + # A row in the database. + xml_hash = ndb.IntegerProperty() + xml_content = ndb.TextProperty() + + def keyGen(): # Generate a random string of length KEY_LEN. KEY_LEN = 6 @@ -33,10 +39,6 @@ def keyGen(): max_index = len(CHARS) - 1 return "".join([CHARS[randint(0, max_index)] for x in range(KEY_LEN)]) -class Xml(ndb.Model): - # A row in the database. - xml_hash = ndb.IntegerProperty() - xml_content = ndb.TextProperty() def xmlToKey(xml_content): # Store XML and return a generated key. @@ -61,6 +63,7 @@ def xmlToKey(xml_content): row.put() return xml_key + def keyToXml(key_provided): # Retrieve stored XML based on the provided key. # Normalize the string. @@ -75,6 +78,7 @@ def keyToXml(key_provided): xml = result.xml_content return xml + def app(environ, start_response): forms = cgi.FieldStorage(fp=environ['wsgi.input'], environ=environ) if "xml" in forms: From 1a77a5e85938b9cd76a363f4a5b013b7a3324b69 Mon Sep 17 00:00:00 2001 From: Monica Kozbial Date: Mon, 3 Feb 2020 16:49:13 -0800 Subject: [PATCH 3/5] Fix main.py redirect. (#3680) --- appengine/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appengine/main.py b/appengine/main.py index f804c120b..349c4f0c1 100644 --- a/appengine/main.py +++ b/appengine/main.py @@ -21,7 +21,7 @@ import storage def app(environ, start_response): if environ["PATH_INFO"] == "/": return redirect(environ, start_response) - if environ["PATH_INFO"] == "/storage.py": + if environ["PATH_INFO"] == "/storage": return storage.app(environ, start_response) start_response("404 Not Found", []) return [b"Page not found."] From 9a466fe212db47faba8a8bb36ff20e12aa4d27dc Mon Sep 17 00:00:00 2001 From: Nitin Kumar <46647141+snitin315@users.noreply.github.com> Date: Wed, 11 Mar 2020 20:14:35 +0530 Subject: [PATCH 4/5] chore: fix typo (#3736) --- tests/workspace_svg/procedure_svg_test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/workspace_svg/procedure_svg_test.js b/tests/workspace_svg/procedure_svg_test.js index 730deb22c..1c27245d4 100644 --- a/tests/workspace_svg/procedure_svg_test.js +++ b/tests/workspace_svg/procedure_svg_test.js @@ -327,7 +327,7 @@ function test_procedureEnableDisableInteractions() { assertTrue('Callers are disabled when their definition is disabled.', barCalls[0].disabled && barCalls[1].disabled); - assertTrue('Callers in definitions are disabled by inheritence.', + assertTrue('Callers in definitions are disabled by inheritance.', !fooCalls[0].disabled && fooCalls[0].getInheritedDisabled()); fooDef.setEnabled(false); @@ -340,7 +340,7 @@ function test_procedureEnableDisableInteractions() { assertTrue('Callers are reenabled with their definition', !barCalls[0].disabled && !barCalls[0].disabled); - assertTrue('Nested disabled callers remain disabled, not by inheritence.', + assertTrue('Nested disabled callers remain disabled, not by inheritance.', fooCalls[0].disabled && !fooCalls[0].getInheritedDisabled()); bazDef.setEnabled(false); @@ -348,7 +348,7 @@ function test_procedureEnableDisableInteractions() { assertTrue('Caller is disabled with its definition', bazCall.disabled); - assertTrue('Caller in the return is disabled by inheritence.', + assertTrue('Caller in the return is disabled by inheritance.', !barCalls[1].disabled && barCalls[1].getInheritedDisabled()); barDef.setEnabled(false); @@ -357,7 +357,7 @@ function test_procedureEnableDisableInteractions() { bazDef.setEnabled(true); - assertTrue('Caller in the return remains disabled, not by inheritence.', + assertTrue('Caller in the return remains disabled, not by inheritance.', barCalls[1].disabled && !barCalls[1].getInheritedDisabled()); } finally { From a13bc66651d29e07c5e3381b1e6e0728f706e787 Mon Sep 17 00:00:00 2001 From: Rachel Fenichel Date: Fri, 27 Mar 2020 14:45:20 -0700 Subject: [PATCH 5/5] Add browserstack to readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 999f96b26..4e9620a82 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ a few minutes and will help us better support the Blockly community. Cross-browser Testing Platform and Open Source <3 Provided by [Sauce Labs](https://saucelabs.com) +We support IE11 and test it using [BrowserStack](https://browserstack.com) + Want to contribute? Great! First, read [our guidelines for contributors](https://developers.google.com/blockly/guides/modify/contributing). ## Releases