From 1f8939b3af26b733570ce15303b8e9e1e3369c99 Mon Sep 17 00:00:00 2001 From: adisbladis Date: Thu, 2 Jul 2020 14:27:49 +0200 Subject: [PATCH] Suggestions for search field (#74) --- .gitignore | 45 +-- VERSION | 2 +- default.nix | 2 +- elm-srcs.nix | 35 +- elm.json | 7 +- flake.nix | 5 +- import-scripts/default.nix | 16 +- import-scripts/import_scripts/channel.py | 63 ++- import-scripts/poetry.lock | 368 ++++++++++++++++- import-scripts/pyproject.toml | 3 + import-scripts/tests/Example.elm | 52 +++ import-scripts/tests/test_channel.py | 104 +++++ registry.dat | Bin 96375 -> 97265 bytes src/Main.elm | 4 +- src/Page/Options.elm | 20 +- src/Page/Packages.elm | 20 +- src/Search.elm | 489 +++++++++++++++++++---- src/index.js | 1 - src/index.scss | 74 +++- 19 files changed, 1152 insertions(+), 158 deletions(-) create mode 100644 import-scripts/tests/Example.elm create mode 100644 import-scripts/tests/test_channel.py diff --git a/.gitignore b/.gitignore index 9dd30ac..9268950 100644 --- a/.gitignore +++ b/.gitignore @@ -1,35 +1,20 @@ -# Logs -logs *.log -.idea -.cache -npm-debug.log* - - -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release - -# Dependency directory -node_modules - -# Optional npm cache directory -.npm - -# Optional REPL history -.node_repl_history - -# elm-package generated files -elm-stuff/ -# elm-repl generated files -repl-temp-* - .DS_Store -example/dist - -ignore +.cache +.idea +.node_repl_history +.npm +build/Release dist -package-lock.json -result -scripts/eval-* +elm-stuff/ eval-* +ignore +import-scripts/import_scripts/__pycache__/ +import-scripts/tests/__pycache__/ +logs +node_modules +npm-debug.log* +package-lock.json +repl-temp-* +result src-url diff --git a/VERSION b/VERSION index 45a4fb7..ec63514 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8 +9 diff --git a/default.nix b/default.nix index af48efd..0ec400c 100644 --- a/default.nix +++ b/default.nix @@ -1,5 +1,5 @@ { pkgs ? import { } -, version ? "0" +, version ? pkgs.lib.removeSuffix "\n" (builtins.readFile ./VERSION) }: let package = builtins.fromJSON (builtins.readFile ./package.json); diff --git a/elm-srcs.nix b/elm-srcs.nix index 654724d..2cb6b94 100644 --- a/elm-srcs.nix +++ b/elm-srcs.nix @@ -10,11 +10,21 @@ version = "1.1.3"; }; + "ohanhi/keyboard" = { + sha256 = "10sbq8v2kydnc3lkydl367g36q2b0xizxl031xyakrgl4zlh07ic"; + version = "2.0.1"; + }; + "truqu/elm-base64" = { sha256 = "12w68b4idbs2vn0gm0lj354pm745jb7n0fj69408mpvh5r1z4m1b"; version = "2.0.4"; }; + "elm/regex" = { + sha256 = "0lijsp50w7n1n57mjg6clpn9phly8vvs07h0qh2rqcs0f1jqvsa2"; + version = "1.0.0"; + }; + "elm/html" = { sha256 = "1n3gpzmpqqdsldys4ipgyl1zacn0kbpc3g4v3hdpiyfjlgh8bf3k"; version = "1.0.0"; @@ -25,6 +35,16 @@ version = "1.0.2"; }; + "Gizra/elm-debouncer" = { + sha256 = "009yw0rb418ar2a458ilr25m8gxrxsv5nvs3ld3l6sy12v12n0yn"; + version = "2.0.0"; + }; + + "Skinney/keyboard-events" = { + sha256 = "10qjlpa4byk78sra071w4ghc7b9p2brnppx7aqyy9cmbrmp5nf86"; + version = "2.0.1"; + }; + "elm/core" = { sha256 = "0gyk7lx3b6vx2jlfbxdsb4xffn0wdvg5yxldq50jr2kk5dzc2prj"; version = "1.0.4"; @@ -60,11 +80,6 @@ version = "1.0.5"; }; - "elm/regex" = { - sha256 = "0lijsp50w7n1n57mjg6clpn9phly8vvs07h0qh2rqcs0f1jqvsa2"; - version = "1.0.0"; - }; - "rtfeldman/elm-hex" = { sha256 = "1y0aa16asvwdqmgbskh5iba6psp43lkcjjw9mgzj3gsrg33lp00d"; version = "1.0.0"; @@ -75,6 +90,11 @@ version = "1.1.0"; }; + "elm-community/list-extra" = { + sha256 = "1rvr1c8cfb3dwf3li17l9ziax6d1fshkliasspnw6rviva38lw34"; + version = "8.2.4"; + }; + "elm/time" = { sha256 = "0vch7i86vn0x8b850w1p69vplll1bnbkp8s383z7pinyg94cm2z1"; version = "1.0.0"; @@ -90,6 +110,11 @@ version = "1.2.2"; }; + "elm/svg" = { + sha256 = "1cwcj73p61q45wqwgqvrvz3aypjyy3fw732xyxdyj6s256hwkn0k"; + version = "1.0.1"; + }; + "elm/random" = { sha256 = "138n2455wdjwa657w6sjq18wx2r0k60ibpc4frhbqr50sncxrfdl"; version = "1.0.0"; diff --git a/elm.json b/elm.json index db9d1a3..abdd889 100644 --- a/elm.json +++ b/elm.json @@ -6,7 +6,9 @@ "elm-version": "0.19.1", "dependencies": { "direct": { + "Gizra/elm-debouncer": "2.0.0", "NoRedInk/elm-json-decode-pipeline": "1.0.0", + "Skinney/keyboard-events": "2.0.1", "elm/browser": "1.0.2", "elm/core": "1.0.4", "elm/html": "1.0.0", @@ -16,6 +18,7 @@ "elm/url": "1.0.0", "hecrj/html-parser": "2.3.4", "krisajenkins/remotedata": "6.0.1", + "ohanhi/keyboard": "2.0.1", "truqu/elm-base64": "2.0.4" }, "indirect": { @@ -24,6 +27,7 @@ "elm/parser": "1.1.0", "elm/time": "1.0.0", "elm/virtual-dom": "1.0.2", + "elm-community/list-extra": "8.2.4", "rtfeldman/elm-hex": "1.0.0" } }, @@ -32,7 +36,8 @@ "elm-explorations/test": "1.2.2" }, "indirect": { - "elm/random": "1.0.0" + "elm/random": "1.0.0", + "elm/svg": "1.0.1" } } } diff --git a/flake.nix b/flake.nix index dc1d3f8..5ff5858 100644 --- a/flake.nix +++ b/flake.nix @@ -18,14 +18,13 @@ poetry2nix.overlay ]; }; - version = pkgs.lib.removeSuffix "\n" (builtins.readFile "${self}/VERSION"); in { import_scripts = import ./import-scripts { - inherit pkgs version; + inherit pkgs; }; frontend = import ./. { - inherit pkgs version; + inherit pkgs; }; }; in diff --git a/import-scripts/default.nix b/import-scripts/default.nix index f1d92e2..111b275 100644 --- a/import-scripts/default.nix +++ b/import-scripts/default.nix @@ -1,5 +1,5 @@ { pkgs ? import { } -, version ? "0" +, version ? pkgs.lib.removeSuffix "\n" (builtins.readFile ./../VERSION) }: let inherit (pkgs.poetry2nix) mkPoetryApplication overrides; @@ -13,11 +13,21 @@ mkPoetryApplication { ''; }); }); + nativeBuildInputs = [ + pkgs.poetry + ]; checkPhase = '' - black --diff --check ./import_scripts - flake8 --ignore W503,E501,E265,E203 ./import_scripts + export PYTHONPATH=$PWD:$PYTHONPATH + black --diff --check import_scripts/ tests/ + flake8 --ignore W503,E501,E265,E203 import_scripts/ tests/ + mypy import_scripts/ tests/ + pytest -vv tests/ ''; postInstall = '' wrapProgram $out/bin/import-channel --set INDEX_SCHEMA_VERSION "${version}" ''; + shellHook = '' + cd import-scripts/ + export PYTHONPATH=$PWD:$PYTHONPATH + ''; } diff --git a/import-scripts/import_scripts/channel.py b/import-scripts/import_scripts/channel.py index 69359e0..119c017 100644 --- a/import-scripts/import_scripts/channel.py +++ b/import-scripts/import_scripts/channel.py @@ -1,21 +1,22 @@ -import boto3 -import botocore -import botocore.client +import boto3 # type: ignore +import botocore # type: ignore +import botocore.client # type: ignore import click -import click_log -import elasticsearch -import elasticsearch.helpers +import click_log # type: ignore +import elasticsearch # type: ignore +import elasticsearch.helpers # type: ignore import json import logging import os import os.path -import pypandoc +import pypandoc # type: ignore import re import requests import shlex import subprocess import sys -import tqdm +import tqdm # type: ignore +import typing import xml.etree.ElementTree logger = logging.getLogger("import-channel") @@ -55,6 +56,12 @@ MAPPING = { "properties": { "type": {"type": "keyword"}, # Package fields + "package_suggestions": { + "type": "completion", + "analyzer": "lowercase", + "search_analyzer": "lowercase", + "preserve_position_increments": False, + }, "package_hydra_build": { "type": "nested", "properties": { @@ -98,6 +105,12 @@ MAPPING = { "package_homepage": {"type": "keyword"}, "package_system": {"type": "keyword"}, # Options fields + "option_suggestions": { + "type": "completion", + "analyzer": "lowercase", + "search_analyzer": "lowercase", + "preserve_position_increments": False, + }, "option_name": {"type": "keyword", "normalizer": "lowercase"}, "option_name_query": {"type": "keyword", "normalizer": "lowercase"}, "option_description": {"type": "text"}, @@ -109,11 +122,33 @@ MAPPING = { } -def split_query(text): - """Tokenize package attr_name +def parse_suggestions(text: str) -> typing.List[typing.Dict[str, object]]: + """Tokenize option_name Example: + services.nginx.extraConfig + - services.nginx.extraConfig + - services.nginx. + - services. + """ + results: typing.List[typing.Dict[str, object]] = [ + {"input": text, "weight": 1000 - (((len(text.split(".")) - 1) * 10))}, + ] + for i in range(len(text.split(".")) - 1): + result = { + "input": ".".join(text.split(".")[: -(i + 1)]) + ".", + "weight": 1000 - ((len(text.split(".")) - 2 - i) * 10) + 1, + } + results.append(result) + return results + + +def parse_query(text): + """Tokenize package attr_name + + Example package: + python37Packages.test_name-test = index: 0 - python37Packages.test1_name-test2 @@ -345,9 +380,10 @@ def get_packages(evaluation, evaluation_builds): yield dict( type="package", + package_suggestions=parse_suggestions(attr_name), package_hydra=hydra, package_attr_name=attr_name, - package_attr_name_query=list(split_query(attr_name)), + package_attr_name_query=list(parse_query(attr_name)), package_attr_set=attr_set, package_pname=remove_attr_set(data["pname"]), package_pversion=data["version"], @@ -411,8 +447,9 @@ def get_options(evaluation): yield dict( type="option", + option_suggestions=parse_suggestions(name), option_name=name, - option_name_query=split_query(name), + option_name_query=parse_query(name), option_description=description, option_type=option.get("type"), option_default=default, @@ -539,5 +576,3 @@ def run(es_url, channel, force, verbose): if __name__ == "__main__": run() - -# vi:ft=python diff --git a/import-scripts/poetry.lock b/import-scripts/poetry.lock index d7fb2a4..166623f 100644 --- a/import-scripts/poetry.lock +++ b/import-scripts/poetry.lock @@ -15,6 +15,15 @@ optional = false python-versions = "*" version = "0.1.0" +[[package]] +category = "dev" +description = "Atomic file writes." +marker = "sys_platform == \"win32\"" +name = "atomicwrites" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.4.0" + [[package]] category = "dev" description = "Classes Without Boilerplate" @@ -71,6 +80,244 @@ botocore = ">=1.17.5,<1.18.0" jmespath = ">=0.7.1,<1.0.0" s3transfer = ">=0.3.0,<0.4.0" +[[package]] +category = "dev" +description = "Type annotations for boto3 1.14.6, generated by mypy-boto3-buider 2.2.0" +name = "boto3-stubs" +optional = false +python-versions = ">=3.6" +version = "1.14.6.0" + +[package.dependencies] +mypy-boto3 = "1.14.6.0" + +[package.extras] +accessanalyzer = ["mypy-boto3-accessanalyzer (1.14.6.0)"] +acm = ["mypy-boto3-acm (1.14.6.0)"] +acm-pca = ["mypy-boto3-acm-pca (1.14.6.0)"] +alexaforbusiness = ["mypy-boto3-alexaforbusiness (1.14.6.0)"] +all = ["mypy-boto3-accessanalyzer (1.14.6.0)", "mypy-boto3-acm (1.14.6.0)", "mypy-boto3-acm-pca (1.14.6.0)", "mypy-boto3-alexaforbusiness (1.14.6.0)", "mypy-boto3-amplify (1.14.6.0)", "mypy-boto3-apigateway (1.14.6.0)", "mypy-boto3-apigatewaymanagementapi (1.14.6.0)", "mypy-boto3-apigatewayv2 (1.14.6.0)", "mypy-boto3-appconfig (1.14.6.0)", "mypy-boto3-application-autoscaling (1.14.6.0)", "mypy-boto3-application-insights (1.14.6.0)", "mypy-boto3-appmesh (1.14.6.0)", "mypy-boto3-appstream (1.14.6.0)", "mypy-boto3-appsync (1.14.6.0)", "mypy-boto3-athena (1.14.6.0)", "mypy-boto3-autoscaling (1.14.6.0)", "mypy-boto3-autoscaling-plans (1.14.6.0)", "mypy-boto3-backup (1.14.6.0)", "mypy-boto3-batch (1.14.6.0)", "mypy-boto3-budgets (1.14.6.0)", "mypy-boto3-ce (1.14.6.0)", "mypy-boto3-chime (1.14.6.0)", "mypy-boto3-cloud9 (1.14.6.0)", "mypy-boto3-clouddirectory (1.14.6.0)", "mypy-boto3-cloudformation (1.14.6.0)", "mypy-boto3-cloudfront (1.14.6.0)", "mypy-boto3-cloudhsm (1.14.6.0)", "mypy-boto3-cloudhsmv2 (1.14.6.0)", "mypy-boto3-cloudsearch (1.14.6.0)", "mypy-boto3-cloudsearchdomain (1.14.6.0)", "mypy-boto3-cloudtrail (1.14.6.0)", "mypy-boto3-cloudwatch (1.14.6.0)", "mypy-boto3-codeartifact (1.14.6.0)", "mypy-boto3-codebuild (1.14.6.0)", "mypy-boto3-codecommit (1.14.6.0)", "mypy-boto3-codedeploy (1.14.6.0)", "mypy-boto3-codeguru-reviewer (1.14.6.0)", "mypy-boto3-codeguruprofiler (1.14.6.0)", "mypy-boto3-codepipeline (1.14.6.0)", "mypy-boto3-codestar (1.14.6.0)", "mypy-boto3-codestar-connections (1.14.6.0)", "mypy-boto3-codestar-notifications (1.14.6.0)", "mypy-boto3-cognito-identity (1.14.6.0)", "mypy-boto3-cognito-idp (1.14.6.0)", "mypy-boto3-cognito-sync (1.14.6.0)", "mypy-boto3-comprehend (1.14.6.0)", "mypy-boto3-comprehendmedical (1.14.6.0)", "mypy-boto3-compute-optimizer (1.14.6.0)", "mypy-boto3-config (1.14.6.0)", "mypy-boto3-connect (1.14.6.0)", "mypy-boto3-connectparticipant (1.14.6.0)", "mypy-boto3-cur (1.14.6.0)", "mypy-boto3-dataexchange (1.14.6.0)", "mypy-boto3-datapipeline (1.14.6.0)", "mypy-boto3-datasync (1.14.6.0)", "mypy-boto3-dax (1.14.6.0)", "mypy-boto3-detective (1.14.6.0)", "mypy-boto3-devicefarm (1.14.6.0)", "mypy-boto3-directconnect (1.14.6.0)", "mypy-boto3-discovery (1.14.6.0)", "mypy-boto3-dlm (1.14.6.0)", "mypy-boto3-dms (1.14.6.0)", "mypy-boto3-docdb (1.14.6.0)", "mypy-boto3-ds (1.14.6.0)", "mypy-boto3-dynamodb (1.14.6.0)", "mypy-boto3-dynamodbstreams (1.14.6.0)", "mypy-boto3-ebs (1.14.6.0)", "mypy-boto3-ec2 (1.14.6.0)", "mypy-boto3-ec2-instance-connect (1.14.6.0)", "mypy-boto3-ecr (1.14.6.0)", "mypy-boto3-ecs (1.14.6.0)", "mypy-boto3-efs (1.14.6.0)", "mypy-boto3-eks (1.14.6.0)", "mypy-boto3-elastic-inference (1.14.6.0)", "mypy-boto3-elasticache (1.14.6.0)", "mypy-boto3-elasticbeanstalk (1.14.6.0)", "mypy-boto3-elastictranscoder (1.14.6.0)", "mypy-boto3-elb (1.14.6.0)", "mypy-boto3-elbv2 (1.14.6.0)", "mypy-boto3-emr (1.14.6.0)", "mypy-boto3-es (1.14.6.0)", "mypy-boto3-events (1.14.6.0)", "mypy-boto3-firehose (1.14.6.0)", "mypy-boto3-fms (1.14.6.0)", "mypy-boto3-forecast (1.14.6.0)", "mypy-boto3-forecastquery (1.14.6.0)", "mypy-boto3-frauddetector (1.14.6.0)", "mypy-boto3-fsx (1.14.6.0)", "mypy-boto3-gamelift (1.14.6.0)", "mypy-boto3-glacier (1.14.6.0)", "mypy-boto3-globalaccelerator (1.14.6.0)", "mypy-boto3-glue (1.14.6.0)", "mypy-boto3-greengrass (1.14.6.0)", "mypy-boto3-groundstation (1.14.6.0)", "mypy-boto3-guardduty (1.14.6.0)", "mypy-boto3-health (1.14.6.0)", "mypy-boto3-iam (1.14.6.0)", "mypy-boto3-imagebuilder (1.14.6.0)", "mypy-boto3-importexport (1.14.6.0)", "mypy-boto3-inspector (1.14.6.0)", "mypy-boto3-iot (1.14.6.0)", "mypy-boto3-iot-data (1.14.6.0)", "mypy-boto3-iot-jobs-data (1.14.6.0)", "mypy-boto3-iot1click-devices (1.14.6.0)", "mypy-boto3-iot1click-projects (1.14.6.0)", "mypy-boto3-iotanalytics (1.14.6.0)", "mypy-boto3-iotevents (1.14.6.0)", "mypy-boto3-iotevents-data (1.14.6.0)", "mypy-boto3-iotsecuretunneling (1.14.6.0)", "mypy-boto3-iotsitewise (1.14.6.0)", "mypy-boto3-iotthingsgraph (1.14.6.0)", "mypy-boto3-kafka (1.14.6.0)", "mypy-boto3-kendra (1.14.6.0)", "mypy-boto3-kinesis (1.14.6.0)", "mypy-boto3-kinesis-video-archived-media (1.14.6.0)", "mypy-boto3-kinesis-video-media (1.14.6.0)", "mypy-boto3-kinesis-video-signaling (1.14.6.0)", "mypy-boto3-kinesisanalytics (1.14.6.0)", "mypy-boto3-kinesisanalyticsv2 (1.14.6.0)", "mypy-boto3-kinesisvideo (1.14.6.0)", "mypy-boto3-kms (1.14.6.0)", "mypy-boto3-lakeformation (1.14.6.0)", "mypy-boto3-lambda (1.14.6.0)", "mypy-boto3-lex-models (1.14.6.0)", "mypy-boto3-lex-runtime (1.14.6.0)", "mypy-boto3-license-manager (1.14.6.0)", "mypy-boto3-lightsail (1.14.6.0)", "mypy-boto3-logs (1.14.6.0)", "mypy-boto3-machinelearning (1.14.6.0)", "mypy-boto3-macie (1.14.6.0)", "mypy-boto3-macie2 (1.14.6.0)", "mypy-boto3-managedblockchain (1.14.6.0)", "mypy-boto3-marketplace-catalog (1.14.6.0)", "mypy-boto3-marketplace-entitlement (1.14.6.0)", "mypy-boto3-marketplacecommerceanalytics (1.14.6.0)", "mypy-boto3-mediaconnect (1.14.6.0)", "mypy-boto3-mediaconvert (1.14.6.0)", "mypy-boto3-medialive (1.14.6.0)", "mypy-boto3-mediapackage (1.14.6.0)", "mypy-boto3-mediapackage-vod (1.14.6.0)", "mypy-boto3-mediastore (1.14.6.0)", "mypy-boto3-mediastore-data (1.14.6.0)", "mypy-boto3-mediatailor (1.14.6.0)", "mypy-boto3-meteringmarketplace (1.14.6.0)", "mypy-boto3-mgh (1.14.6.0)", "mypy-boto3-migrationhub-config (1.14.6.0)", "mypy-boto3-mobile (1.14.6.0)", "mypy-boto3-mq (1.14.6.0)", "mypy-boto3-mturk (1.14.6.0)", "mypy-boto3-neptune (1.14.6.0)", "mypy-boto3-networkmanager (1.14.6.0)", "mypy-boto3-opsworks (1.14.6.0)", "mypy-boto3-opsworkscm (1.14.6.0)", "mypy-boto3-organizations (1.14.6.0)", "mypy-boto3-outposts (1.14.6.0)", "mypy-boto3-personalize (1.14.6.0)", "mypy-boto3-personalize-events (1.14.6.0)", "mypy-boto3-personalize-runtime (1.14.6.0)", "mypy-boto3-pi (1.14.6.0)", "mypy-boto3-pinpoint (1.14.6.0)", "mypy-boto3-pinpoint-email (1.14.6.0)", "mypy-boto3-pinpoint-sms-voice (1.14.6.0)", "mypy-boto3-polly (1.14.6.0)", "mypy-boto3-pricing (1.14.6.0)", "mypy-boto3-qldb (1.14.6.0)", "mypy-boto3-qldb-session (1.14.6.0)", "mypy-boto3-quicksight (1.14.6.0)", "mypy-boto3-ram (1.14.6.0)", "mypy-boto3-rds (1.14.6.0)", "mypy-boto3-rds-data (1.14.6.0)", "mypy-boto3-redshift (1.14.6.0)", "mypy-boto3-rekognition (1.14.6.0)", "mypy-boto3-resource-groups (1.14.6.0)", "mypy-boto3-resourcegroupstaggingapi (1.14.6.0)", "mypy-boto3-robomaker (1.14.6.0)", "mypy-boto3-route53 (1.14.6.0)", "mypy-boto3-route53domains (1.14.6.0)", "mypy-boto3-route53resolver (1.14.6.0)", "mypy-boto3-s3 (1.14.6.0)", "mypy-boto3-s3control (1.14.6.0)", "mypy-boto3-sagemaker (1.14.6.0)", "mypy-boto3-sagemaker-a2i-runtime (1.14.6.0)", "mypy-boto3-sagemaker-runtime (1.14.6.0)", "mypy-boto3-savingsplans (1.14.6.0)", "mypy-boto3-schemas (1.14.6.0)", "mypy-boto3-sdb (1.14.6.0)", "mypy-boto3-secretsmanager (1.14.6.0)", "mypy-boto3-securityhub (1.14.6.0)", "mypy-boto3-serverlessrepo (1.14.6.0)", "mypy-boto3-service-quotas (1.14.6.0)", "mypy-boto3-servicecatalog (1.14.6.0)", "mypy-boto3-servicediscovery (1.14.6.0)", "mypy-boto3-ses (1.14.6.0)", "mypy-boto3-sesv2 (1.14.6.0)", "mypy-boto3-shield (1.14.6.0)", "mypy-boto3-signer (1.14.6.0)", "mypy-boto3-sms (1.14.6.0)", "mypy-boto3-sms-voice (1.14.6.0)", "mypy-boto3-snowball (1.14.6.0)", "mypy-boto3-sns (1.14.6.0)", "mypy-boto3-sqs (1.14.6.0)", "mypy-boto3-ssm (1.14.6.0)", "mypy-boto3-sso (1.14.6.0)", "mypy-boto3-sso-oidc (1.14.6.0)", "mypy-boto3-stepfunctions (1.14.6.0)", "mypy-boto3-storagegateway (1.14.6.0)", "mypy-boto3-sts (1.14.6.0)", "mypy-boto3-support (1.14.6.0)", "mypy-boto3-swf (1.14.6.0)", "mypy-boto3-synthetics (1.14.6.0)", "mypy-boto3-textract (1.14.6.0)", "mypy-boto3-transcribe (1.14.6.0)", "mypy-boto3-transfer (1.14.6.0)", "mypy-boto3-translate (1.14.6.0)", "mypy-boto3-waf (1.14.6.0)", "mypy-boto3-waf-regional (1.14.6.0)", "mypy-boto3-wafv2 (1.14.6.0)", "mypy-boto3-workdocs (1.14.6.0)", "mypy-boto3-worklink (1.14.6.0)", "mypy-boto3-workmail (1.14.6.0)", "mypy-boto3-workmailmessageflow (1.14.6.0)", "mypy-boto3-workspaces (1.14.6.0)", "mypy-boto3-xray (1.14.6.0)"] +amplify = ["mypy-boto3-amplify (1.14.6.0)"] +apigateway = ["mypy-boto3-apigateway (1.14.6.0)"] +apigatewaymanagementapi = ["mypy-boto3-apigatewaymanagementapi (1.14.6.0)"] +apigatewayv2 = ["mypy-boto3-apigatewayv2 (1.14.6.0)"] +appconfig = ["mypy-boto3-appconfig (1.14.6.0)"] +application-autoscaling = ["mypy-boto3-application-autoscaling (1.14.6.0)"] +application-insights = ["mypy-boto3-application-insights (1.14.6.0)"] +appmesh = ["mypy-boto3-appmesh (1.14.6.0)"] +appstream = ["mypy-boto3-appstream (1.14.6.0)"] +appsync = ["mypy-boto3-appsync (1.14.6.0)"] +athena = ["mypy-boto3-athena (1.14.6.0)"] +autoscaling = ["mypy-boto3-autoscaling (1.14.6.0)"] +autoscaling-plans = ["mypy-boto3-autoscaling-plans (1.14.6.0)"] +backup = ["mypy-boto3-backup (1.14.6.0)"] +batch = ["mypy-boto3-batch (1.14.6.0)"] +budgets = ["mypy-boto3-budgets (1.14.6.0)"] +ce = ["mypy-boto3-ce (1.14.6.0)"] +chime = ["mypy-boto3-chime (1.14.6.0)"] +cloud9 = ["mypy-boto3-cloud9 (1.14.6.0)"] +clouddirectory = ["mypy-boto3-clouddirectory (1.14.6.0)"] +cloudformation = ["mypy-boto3-cloudformation (1.14.6.0)"] +cloudfront = ["mypy-boto3-cloudfront (1.14.6.0)"] +cloudhsm = ["mypy-boto3-cloudhsm (1.14.6.0)"] +cloudhsmv2 = ["mypy-boto3-cloudhsmv2 (1.14.6.0)"] +cloudsearch = ["mypy-boto3-cloudsearch (1.14.6.0)"] +cloudsearchdomain = ["mypy-boto3-cloudsearchdomain (1.14.6.0)"] +cloudtrail = ["mypy-boto3-cloudtrail (1.14.6.0)"] +cloudwatch = ["mypy-boto3-cloudwatch (1.14.6.0)"] +codeartifact = ["mypy-boto3-codeartifact (1.14.6.0)"] +codebuild = ["mypy-boto3-codebuild (1.14.6.0)"] +codecommit = ["mypy-boto3-codecommit (1.14.6.0)"] +codedeploy = ["mypy-boto3-codedeploy (1.14.6.0)"] +codeguru-reviewer = ["mypy-boto3-codeguru-reviewer (1.14.6.0)"] +codeguruprofiler = ["mypy-boto3-codeguruprofiler (1.14.6.0)"] +codepipeline = ["mypy-boto3-codepipeline (1.14.6.0)"] +codestar = ["mypy-boto3-codestar (1.14.6.0)"] +codestar-connections = ["mypy-boto3-codestar-connections (1.14.6.0)"] +codestar-notifications = ["mypy-boto3-codestar-notifications (1.14.6.0)"] +cognito-identity = ["mypy-boto3-cognito-identity (1.14.6.0)"] +cognito-idp = ["mypy-boto3-cognito-idp (1.14.6.0)"] +cognito-sync = ["mypy-boto3-cognito-sync (1.14.6.0)"] +comprehend = ["mypy-boto3-comprehend (1.14.6.0)"] +comprehendmedical = ["mypy-boto3-comprehendmedical (1.14.6.0)"] +compute-optimizer = ["mypy-boto3-compute-optimizer (1.14.6.0)"] +config = ["mypy-boto3-config (1.14.6.0)"] +connect = ["mypy-boto3-connect (1.14.6.0)"] +connectparticipant = ["mypy-boto3-connectparticipant (1.14.6.0)"] +cur = ["mypy-boto3-cur (1.14.6.0)"] +dataexchange = ["mypy-boto3-dataexchange (1.14.6.0)"] +datapipeline = ["mypy-boto3-datapipeline (1.14.6.0)"] +datasync = ["mypy-boto3-datasync (1.14.6.0)"] +dax = ["mypy-boto3-dax (1.14.6.0)"] +detective = ["mypy-boto3-detective (1.14.6.0)"] +devicefarm = ["mypy-boto3-devicefarm (1.14.6.0)"] +directconnect = ["mypy-boto3-directconnect (1.14.6.0)"] +discovery = ["mypy-boto3-discovery (1.14.6.0)"] +dlm = ["mypy-boto3-dlm (1.14.6.0)"] +dms = ["mypy-boto3-dms (1.14.6.0)"] +docdb = ["mypy-boto3-docdb (1.14.6.0)"] +ds = ["mypy-boto3-ds (1.14.6.0)"] +dynamodb = ["mypy-boto3-dynamodb (1.14.6.0)"] +dynamodbstreams = ["mypy-boto3-dynamodbstreams (1.14.6.0)"] +ebs = ["mypy-boto3-ebs (1.14.6.0)"] +ec2 = ["mypy-boto3-ec2 (1.14.6.0)"] +ec2-instance-connect = ["mypy-boto3-ec2-instance-connect (1.14.6.0)"] +ecr = ["mypy-boto3-ecr (1.14.6.0)"] +ecs = ["mypy-boto3-ecs (1.14.6.0)"] +efs = ["mypy-boto3-efs (1.14.6.0)"] +eks = ["mypy-boto3-eks (1.14.6.0)"] +elastic-inference = ["mypy-boto3-elastic-inference (1.14.6.0)"] +elasticache = ["mypy-boto3-elasticache (1.14.6.0)"] +elasticbeanstalk = ["mypy-boto3-elasticbeanstalk (1.14.6.0)"] +elastictranscoder = ["mypy-boto3-elastictranscoder (1.14.6.0)"] +elb = ["mypy-boto3-elb (1.14.6.0)"] +elbv2 = ["mypy-boto3-elbv2 (1.14.6.0)"] +emr = ["mypy-boto3-emr (1.14.6.0)"] +es = ["mypy-boto3-es (1.14.6.0)"] +essential = ["mypy-boto3-cloudformation (1.14.6.0)", "mypy-boto3-dynamodb (1.14.6.0)", "mypy-boto3-ec2 (1.14.6.0)", "mypy-boto3-lambda (1.14.6.0)", "mypy-boto3-rds (1.14.6.0)", "mypy-boto3-s3 (1.14.6.0)", "mypy-boto3-sqs (1.14.6.0)"] +events = ["mypy-boto3-events (1.14.6.0)"] +firehose = ["mypy-boto3-firehose (1.14.6.0)"] +fms = ["mypy-boto3-fms (1.14.6.0)"] +forecast = ["mypy-boto3-forecast (1.14.6.0)"] +forecastquery = ["mypy-boto3-forecastquery (1.14.6.0)"] +frauddetector = ["mypy-boto3-frauddetector (1.14.6.0)"] +fsx = ["mypy-boto3-fsx (1.14.6.0)"] +gamelift = ["mypy-boto3-gamelift (1.14.6.0)"] +glacier = ["mypy-boto3-glacier (1.14.6.0)"] +globalaccelerator = ["mypy-boto3-globalaccelerator (1.14.6.0)"] +glue = ["mypy-boto3-glue (1.14.6.0)"] +greengrass = ["mypy-boto3-greengrass (1.14.6.0)"] +groundstation = ["mypy-boto3-groundstation (1.14.6.0)"] +guardduty = ["mypy-boto3-guardduty (1.14.6.0)"] +health = ["mypy-boto3-health (1.14.6.0)"] +iam = ["mypy-boto3-iam (1.14.6.0)"] +imagebuilder = ["mypy-boto3-imagebuilder (1.14.6.0)"] +importexport = ["mypy-boto3-importexport (1.14.6.0)"] +inspector = ["mypy-boto3-inspector (1.14.6.0)"] +iot = ["mypy-boto3-iot (1.14.6.0)"] +iot-data = ["mypy-boto3-iot-data (1.14.6.0)"] +iot-jobs-data = ["mypy-boto3-iot-jobs-data (1.14.6.0)"] +iot1click-devices = ["mypy-boto3-iot1click-devices (1.14.6.0)"] +iot1click-projects = ["mypy-boto3-iot1click-projects (1.14.6.0)"] +iotanalytics = ["mypy-boto3-iotanalytics (1.14.6.0)"] +iotevents = ["mypy-boto3-iotevents (1.14.6.0)"] +iotevents-data = ["mypy-boto3-iotevents-data (1.14.6.0)"] +iotsecuretunneling = ["mypy-boto3-iotsecuretunneling (1.14.6.0)"] +iotsitewise = ["mypy-boto3-iotsitewise (1.14.6.0)"] +iotthingsgraph = ["mypy-boto3-iotthingsgraph (1.14.6.0)"] +kafka = ["mypy-boto3-kafka (1.14.6.0)"] +kendra = ["mypy-boto3-kendra (1.14.6.0)"] +kinesis = ["mypy-boto3-kinesis (1.14.6.0)"] +kinesis-video-archived-media = ["mypy-boto3-kinesis-video-archived-media (1.14.6.0)"] +kinesis-video-media = ["mypy-boto3-kinesis-video-media (1.14.6.0)"] +kinesis-video-signaling = ["mypy-boto3-kinesis-video-signaling (1.14.6.0)"] +kinesisanalytics = ["mypy-boto3-kinesisanalytics (1.14.6.0)"] +kinesisanalyticsv2 = ["mypy-boto3-kinesisanalyticsv2 (1.14.6.0)"] +kinesisvideo = ["mypy-boto3-kinesisvideo (1.14.6.0)"] +kms = ["mypy-boto3-kms (1.14.6.0)"] +lakeformation = ["mypy-boto3-lakeformation (1.14.6.0)"] +lambda = ["mypy-boto3-lambda (1.14.6.0)"] +lex-models = ["mypy-boto3-lex-models (1.14.6.0)"] +lex-runtime = ["mypy-boto3-lex-runtime (1.14.6.0)"] +license-manager = ["mypy-boto3-license-manager (1.14.6.0)"] +lightsail = ["mypy-boto3-lightsail (1.14.6.0)"] +logs = ["mypy-boto3-logs (1.14.6.0)"] +machinelearning = ["mypy-boto3-machinelearning (1.14.6.0)"] +macie = ["mypy-boto3-macie (1.14.6.0)"] +macie2 = ["mypy-boto3-macie2 (1.14.6.0)"] +managedblockchain = ["mypy-boto3-managedblockchain (1.14.6.0)"] +marketplace-catalog = ["mypy-boto3-marketplace-catalog (1.14.6.0)"] +marketplace-entitlement = ["mypy-boto3-marketplace-entitlement (1.14.6.0)"] +marketplacecommerceanalytics = ["mypy-boto3-marketplacecommerceanalytics (1.14.6.0)"] +mediaconnect = ["mypy-boto3-mediaconnect (1.14.6.0)"] +mediaconvert = ["mypy-boto3-mediaconvert (1.14.6.0)"] +medialive = ["mypy-boto3-medialive (1.14.6.0)"] +mediapackage = ["mypy-boto3-mediapackage (1.14.6.0)"] +mediapackage-vod = ["mypy-boto3-mediapackage-vod (1.14.6.0)"] +mediastore = ["mypy-boto3-mediastore (1.14.6.0)"] +mediastore-data = ["mypy-boto3-mediastore-data (1.14.6.0)"] +mediatailor = ["mypy-boto3-mediatailor (1.14.6.0)"] +meteringmarketplace = ["mypy-boto3-meteringmarketplace (1.14.6.0)"] +mgh = ["mypy-boto3-mgh (1.14.6.0)"] +migrationhub-config = ["mypy-boto3-migrationhub-config (1.14.6.0)"] +mobile = ["mypy-boto3-mobile (1.14.6.0)"] +mq = ["mypy-boto3-mq (1.14.6.0)"] +mturk = ["mypy-boto3-mturk (1.14.6.0)"] +neptune = ["mypy-boto3-neptune (1.14.6.0)"] +networkmanager = ["mypy-boto3-networkmanager (1.14.6.0)"] +opsworks = ["mypy-boto3-opsworks (1.14.6.0)"] +opsworkscm = ["mypy-boto3-opsworkscm (1.14.6.0)"] +organizations = ["mypy-boto3-organizations (1.14.6.0)"] +outposts = ["mypy-boto3-outposts (1.14.6.0)"] +personalize = ["mypy-boto3-personalize (1.14.6.0)"] +personalize-events = ["mypy-boto3-personalize-events (1.14.6.0)"] +personalize-runtime = ["mypy-boto3-personalize-runtime (1.14.6.0)"] +pi = ["mypy-boto3-pi (1.14.6.0)"] +pinpoint = ["mypy-boto3-pinpoint (1.14.6.0)"] +pinpoint-email = ["mypy-boto3-pinpoint-email (1.14.6.0)"] +pinpoint-sms-voice = ["mypy-boto3-pinpoint-sms-voice (1.14.6.0)"] +polly = ["mypy-boto3-polly (1.14.6.0)"] +pricing = ["mypy-boto3-pricing (1.14.6.0)"] +qldb = ["mypy-boto3-qldb (1.14.6.0)"] +qldb-session = ["mypy-boto3-qldb-session (1.14.6.0)"] +quicksight = ["mypy-boto3-quicksight (1.14.6.0)"] +ram = ["mypy-boto3-ram (1.14.6.0)"] +rds = ["mypy-boto3-rds (1.14.6.0)"] +rds-data = ["mypy-boto3-rds-data (1.14.6.0)"] +redshift = ["mypy-boto3-redshift (1.14.6.0)"] +rekognition = ["mypy-boto3-rekognition (1.14.6.0)"] +resource-groups = ["mypy-boto3-resource-groups (1.14.6.0)"] +resourcegroupstaggingapi = ["mypy-boto3-resourcegroupstaggingapi (1.14.6.0)"] +robomaker = ["mypy-boto3-robomaker (1.14.6.0)"] +route53 = ["mypy-boto3-route53 (1.14.6.0)"] +route53domains = ["mypy-boto3-route53domains (1.14.6.0)"] +route53resolver = ["mypy-boto3-route53resolver (1.14.6.0)"] +s3 = ["mypy-boto3-s3 (1.14.6.0)"] +s3control = ["mypy-boto3-s3control (1.14.6.0)"] +sagemaker = ["mypy-boto3-sagemaker (1.14.6.0)"] +sagemaker-a2i-runtime = ["mypy-boto3-sagemaker-a2i-runtime (1.14.6.0)"] +sagemaker-runtime = ["mypy-boto3-sagemaker-runtime (1.14.6.0)"] +savingsplans = ["mypy-boto3-savingsplans (1.14.6.0)"] +schemas = ["mypy-boto3-schemas (1.14.6.0)"] +sdb = ["mypy-boto3-sdb (1.14.6.0)"] +secretsmanager = ["mypy-boto3-secretsmanager (1.14.6.0)"] +securityhub = ["mypy-boto3-securityhub (1.14.6.0)"] +serverlessrepo = ["mypy-boto3-serverlessrepo (1.14.6.0)"] +service-quotas = ["mypy-boto3-service-quotas (1.14.6.0)"] +servicecatalog = ["mypy-boto3-servicecatalog (1.14.6.0)"] +servicediscovery = ["mypy-boto3-servicediscovery (1.14.6.0)"] +ses = ["mypy-boto3-ses (1.14.6.0)"] +sesv2 = ["mypy-boto3-sesv2 (1.14.6.0)"] +shield = ["mypy-boto3-shield (1.14.6.0)"] +signer = ["mypy-boto3-signer (1.14.6.0)"] +sms = ["mypy-boto3-sms (1.14.6.0)"] +sms-voice = ["mypy-boto3-sms-voice (1.14.6.0)"] +snowball = ["mypy-boto3-snowball (1.14.6.0)"] +sns = ["mypy-boto3-sns (1.14.6.0)"] +sqs = ["mypy-boto3-sqs (1.14.6.0)"] +ssm = ["mypy-boto3-ssm (1.14.6.0)"] +sso = ["mypy-boto3-sso (1.14.6.0)"] +sso-oidc = ["mypy-boto3-sso-oidc (1.14.6.0)"] +stepfunctions = ["mypy-boto3-stepfunctions (1.14.6.0)"] +storagegateway = ["mypy-boto3-storagegateway (1.14.6.0)"] +sts = ["mypy-boto3-sts (1.14.6.0)"] +support = ["mypy-boto3-support (1.14.6.0)"] +swf = ["mypy-boto3-swf (1.14.6.0)"] +synthetics = ["mypy-boto3-synthetics (1.14.6.0)"] +textract = ["mypy-boto3-textract (1.14.6.0)"] +transcribe = ["mypy-boto3-transcribe (1.14.6.0)"] +transfer = ["mypy-boto3-transfer (1.14.6.0)"] +translate = ["mypy-boto3-translate (1.14.6.0)"] +waf = ["mypy-boto3-waf (1.14.6.0)"] +waf-regional = ["mypy-boto3-waf-regional (1.14.6.0)"] +wafv2 = ["mypy-boto3-wafv2 (1.14.6.0)"] +workdocs = ["mypy-boto3-workdocs (1.14.6.0)"] +worklink = ["mypy-boto3-worklink (1.14.6.0)"] +workmail = ["mypy-boto3-workmail (1.14.6.0)"] +workmailmessageflow = ["mypy-boto3-workmailmessageflow (1.14.6.0)"] +workspaces = ["mypy-boto3-workspaces (1.14.6.0)"] +xray = ["mypy-boto3-xray (1.14.6.0)"] + [[package]] category = "main" description = "Low-level, data-driven core of boto 3." @@ -126,7 +373,7 @@ click = "*" [[package]] category = "dev" description = "Cross-platform colored terminal text." -marker = "python_version >= \"3.4\" and sys_platform == \"win32\"" +marker = "python_version >= \"3.4\" and sys_platform == \"win32\" or sys_platform == \"win32\"" name = "colorama" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" @@ -277,6 +524,14 @@ optional = false python-versions = "*" version = "0.6.1" +[[package]] +category = "dev" +description = "More routines for operating on iterables, beyond itertools" +name = "more-itertools" +optional = false +python-versions = ">=3.5" +version = "8.4.0" + [[package]] category = "dev" description = "Optional static typing for Python" @@ -293,6 +548,17 @@ typing-extensions = ">=3.7.4" [package.extras] dmypy = ["psutil (>=4.0)"] +[[package]] +category = "dev" +description = "Type annotations for boto3 1.14.6 master module, generated by mypy-boto3-buider 2.2.0" +name = "mypy-boto3" +optional = false +python-versions = ">=3.6" +version = "1.14.6.0" + +[package.dependencies] +boto3 = "*" + [[package]] category = "dev" description = "Experimental type system extensions for programs checked with the mypy typechecker." @@ -301,6 +567,18 @@ optional = false python-versions = "*" version = "0.4.3" +[[package]] +category = "dev" +description = "Core utilities for Python packages" +name = "packaging" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "20.4" + +[package.dependencies] +pyparsing = ">=2.0.2" +six = "*" + [[package]] category = "dev" description = "A Python Parser" @@ -342,6 +620,17 @@ optional = false python-versions = "*" version = "0.7.5" +[[package]] +category = "dev" +description = "plugin and hook calling mechanisms for python" +name = "pluggy" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.13.1" + +[package.extras] +dev = ["pre-commit", "tox"] + [[package]] category = "dev" description = "Library for building powerful interactive command lines in Python" @@ -363,6 +652,14 @@ optional = false python-versions = "*" version = "0.6.0" +[[package]] +category = "dev" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +name = "py" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.8.2" + [[package]] category = "dev" description = "Python style guide checker" @@ -401,6 +698,36 @@ pip = ">=8.1.0" setuptools = "*" wheel = ">=0.25.0" +[[package]] +category = "dev" +description = "Python parsing module" +name = "pyparsing" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +version = "2.4.7" + +[[package]] +category = "dev" +description = "pytest: simple powerful testing with Python" +name = "pytest" +optional = false +python-versions = ">=3.5" +version = "5.4.3" + +[package.dependencies] +atomicwrites = ">=1.0" +attrs = ">=17.4.0" +colorama = "*" +more-itertools = ">=4.0.0" +packaging = "*" +pluggy = ">=0.12,<1.0" +py = ">=1.5.0" +wcwidth = "*" + +[package.extras] +checkqa-mypy = ["mypy (v0.761)"] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + [[package]] category = "main" description = "Extensions to the standard Python datetime module" @@ -525,7 +852,6 @@ socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] [[package]] category = "dev" description = "Measures the displayed width of unicode strings in a terminal" -marker = "python_version >= \"3.4\"" name = "wcwidth" optional = false python-versions = "*" @@ -543,7 +869,7 @@ version = "0.34.2" test = ["pytest (>=3.0.0)", "pytest-cov"] [metadata] -content-hash = "b75343832402daab2b4e4c515ff32a0acbe74e1b1f22703e58060a14ea231904" +content-hash = "86c9444e6f5b2b6c3b41e4ae84bb0c03c42cee762683fed5b735cf92d92e74c1" python-versions = "^3.8" [metadata.files] @@ -555,6 +881,10 @@ appnope = [ {file = "appnope-0.1.0-py2.py3-none-any.whl", hash = "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0"}, {file = "appnope-0.1.0.tar.gz", hash = "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"}, ] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] attrs = [ {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, @@ -571,6 +901,10 @@ boto3 = [ {file = "boto3-1.14.5-py2.py3-none-any.whl", hash = "sha256:ed9b0852c0dc8ca88f5f851357acf238b954d1cced44f48e0d930d306150de7c"}, {file = "boto3-1.14.5.tar.gz", hash = "sha256:7098937f6432e3ae4e5fe9b93f561b06117c5df736effb8cc166f6fb2bb41ab8"}, ] +boto3-stubs = [ + {file = "boto3-stubs-1.14.6.0.tar.gz", hash = "sha256:28112a2a22d7f8d1191690372160f218b84f087797e3e2c24b12f937e3ec8865"}, + {file = "boto3_stubs-1.14.6.0-py3-none-any.whl", hash = "sha256:a8b1d9ffb47deb10d9b0ea3c6c0737689e29facf553ca38c2ea99e9b8d0ba3d2"}, +] botocore = [ {file = "botocore-1.17.5-py2.py3-none-any.whl", hash = "sha256:072c82c64906996f1d7953da1a61d8e6debf0ee5acaa267ec777f05b30755b66"}, {file = "botocore-1.17.5.tar.gz", hash = "sha256:fcfc3762472aa1d758583d818faaa59b933d839a87f372688836d49d66ad9a7a"}, @@ -639,6 +973,10 @@ mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] +more-itertools = [ + {file = "more-itertools-8.4.0.tar.gz", hash = "sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5"}, + {file = "more_itertools-8.4.0-py3-none-any.whl", hash = "sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2"}, +] mypy = [ {file = "mypy-0.780-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:d3b4941de44341227ece1caaf5b08b23e42ad4eeb8b603219afb11e9d4cfb437"}, {file = "mypy-0.780-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:1f3976a945ad7f0a0727aafdc5651c2d3278e3c88dee94e2bf75cd3386b7b2f4"}, @@ -655,10 +993,18 @@ mypy = [ {file = "mypy-0.780-py3-none-any.whl", hash = "sha256:127de5a9b817a03a98c5ae8a0c46a20dc44442af6dcfa2ae7f96cb519b312efa"}, {file = "mypy-0.780.tar.gz", hash = "sha256:4ef13b619a289aa025f2273e05e755f8049bb4eaba6d703a425de37d495d178d"}, ] +mypy-boto3 = [ + {file = "mypy-boto3-1.14.6.0.tar.gz", hash = "sha256:f4c6ba46dfca27f8b2d30ed46889e204f355ec2c1332bb7934be13b6df7fae32"}, + {file = "mypy_boto3-1.14.6.0-py3-none-any.whl", hash = "sha256:e9744bdce6891ee07c9db75d782dd158fdc5d6ec218f1cb61553f78aa2f79e56"}, +] mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] +packaging = [ + {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, + {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, +] parso = [ {file = "parso-0.7.0-py2.py3-none-any.whl", hash = "sha256:158c140fc04112dc45bca311633ae5033c2c2a7b732fa33d0955bad8152a8dd0"}, {file = "parso-0.7.0.tar.gz", hash = "sha256:908e9fae2144a076d72ae4e25539143d40b8e3eafbaeae03c1bfe226f4cdf12c"}, @@ -675,6 +1021,10 @@ pickleshare = [ {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, ] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] prompt-toolkit = [ {file = "prompt_toolkit-3.0.5-py3-none-any.whl", hash = "sha256:df7e9e63aea609b1da3a65641ceaf5bc7d05e0a04de5bd45d05dbeffbabf9e04"}, {file = "prompt_toolkit-3.0.5.tar.gz", hash = "sha256:563d1a4140b63ff9dd587bda9557cffb2fe73650205ab6f4383092fb882e7dc8"}, @@ -683,6 +1033,10 @@ ptyprocess = [ {file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"}, {file = "ptyprocess-0.6.0.tar.gz", hash = "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0"}, ] +py = [ + {file = "py-1.8.2-py2.py3-none-any.whl", hash = "sha256:a673fa23d7000440cc885c17dbd34fafcb7d7a6e230b29f6766400de36a33c44"}, + {file = "py-1.8.2.tar.gz", hash = "sha256:f3b3a4c36512a4c4f024041ab51866f11761cc169670204b235f6b20523d4e6b"}, +] pycodestyle = [ {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, @@ -698,6 +1052,14 @@ pygments = [ pypandoc = [ {file = "pypandoc-1.5.tar.gz", hash = "sha256:14a49977ab1fbc9b14ef3087dcb101f336851837fca55ca79cf33846cc4976ff"}, ] +pyparsing = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] +pytest = [ + {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, + {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, +] python-dateutil = [ {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, diff --git a/import-scripts/pyproject.toml b/import-scripts/pyproject.toml index 7d5fdbe..5e76057 100644 --- a/import-scripts/pyproject.toml +++ b/import-scripts/pyproject.toml @@ -25,6 +25,9 @@ ipdb = "^0.13.2" black = "^19.10b0" flake8 = "^3.8.3" mypy = "^0.780" +pytest = "^5.4.3" +setuptools = "^47.3.1" +boto3-stubs = "^1.14.6" [build-system] requires = ["poetry>=0.12"] diff --git a/import-scripts/tests/Example.elm b/import-scripts/tests/Example.elm new file mode 100644 index 0000000..1bcd7b0 --- /dev/null +++ b/import-scripts/tests/Example.elm @@ -0,0 +1,52 @@ +module Example exposing (fuzzTest, unitTest, viewTest) + +import Expect exposing (Expectation) +import Fuzz exposing (Fuzzer, int, list, string) +import Main exposing (..) +import Test exposing (..) +import Test.Html.Query as Query +import Test.Html.Selector exposing (tag, text) + + +{-| See +-} +unitTest : Test +unitTest = + describe "simple unit test" + [ test "Inc adds one" <| + \() -> + update Inc (Model 0 "") + |> Tuple.first + |> .counter + |> Expect.equal 1 + ] + + +{-| See +-} +fuzzTest : Test +fuzzTest = + describe "simple fuzz test" + [ fuzz int "Inc ALWAYS adds one" <| + \ct -> + update Inc (Model ct "") + |> Tuple.first + |> .counter + |> Expect.equal (ct + 1) + ] + + +{-| see +-} +viewTest : Test +viewTest = + describe "Testing view function" + [ test "Button has the expected text" <| + \() -> + Model 0 "" + |> view + |> Query.fromHtml + |> Query.findAll [ tag "button" ] + |> Query.first + |> Query.has [ text "+ 1" ] + ] diff --git a/import-scripts/tests/test_channel.py b/import-scripts/tests/test_channel.py new file mode 100644 index 0000000..cab755e --- /dev/null +++ b/import-scripts/tests/test_channel.py @@ -0,0 +1,104 @@ +import pytest # type: ignore + + +@pytest.mark.parametrize( + "text,expected", + [ + ( + "services.grafana.analytics.reporting.enable", + [ + {"input": "services.grafana.analytics.reporting.enable", "weight": 960}, + {"input": "services.grafana.analytics.reporting.", "weight": 971}, + {"input": "services.grafana.analytics.", "weight": 981}, + {"input": "services.grafana.", "weight": 991}, + {"input": "services.", "weight": 1001}, + ], + ), + ( + "services.nginx.extraConfig", + [ + {"input": "services.nginx.extraConfig", "weight": 980}, + {"input": "services.nginx.", "weight": 991}, + {"input": "services.", "weight": 1001}, + ], + ), + ( + "python37Packages.test1_name-test2", + [ + {"input": "python37Packages.test1_name-test2", "weight": 990}, + {"input": "python37Packages.", "weight": 1001}, + ], + ), + ], +) +def test_parse_suggestions(text, expected): + import import_scripts.channel + + assert import_scripts.channel.parse_suggestions(text) == expected + + +@pytest.mark.parametrize( + "text,expected", + [ + ( + "services.nginx.extraConfig", + [ + "services.nginx.extraConfig", + "services.nginx.extra", + "services.nginx", + "services", + "nginx.extraConfig", + "nginx.extra", + "nginx", + "extraConfig", + "extra", + "Config", + ], + ), + ( + "python37Packages.test1_name-test2", + [ + "python37Packages.test1_name-test2", + "python37Packages.test1_name-test", + "python37Packages.test1_name", + "python37Packages.test1", + "python37Packages.test", + "python37Packages", + "python37", + "python", + "37Packages.test1_name-test2", + "37Packages.test1_name-test", + "37Packages.test1_name", + "37Packages.test1", + "37Packages.test", + "37Packages", + "37", + "Packages.test1_name-test2", + "Packages.test1_name-test", + "Packages.test1_name", + "Packages.test1", + "Packages.test", + "Packages", + "test1_name-test2", + "test1_name-test", + "test1_name", + "test1", + "test", + "1_name-test2", + "1_name-test", + "1_name", + "1", + "name-test2", + "name-test", + "name", + "test2", + "test", + "2", + ], + ), + ], +) +def test_parse_query(text, expected): + import import_scripts.channel + + assert sorted(import_scripts.channel.parse_query(text)) == sorted(expected) diff --git a/registry.dat b/registry.dat index 6eef874d401fa5148e6d700433df4d61bb75bdf7..800d95db3e01b36744c1faa95a247542f9bb5104 100644 GIT binary patch delta 1452 zcmaJ>U2NM_6u!qlv7I<+yQXQgr0eLmB4CEHg^0kzqzNb*2&9Rh!6fLdb7`XZC$gQT zAn`y2;(3BsU`z;&>oj(Q1YAE@IzHz+ z-}%mWeeDNRl~_0BEg=3AaPHQZE#PacOI^mVnwtNdmhWqHK;r-x3axqbjC{yXz! z`ttDYA_pi~%R!!A82d>K+#|5f|Fm@5RQizJunu+>E#jn!VQMD!oaPe5_4HC{M#?2{ zhyy@$V%J`2GM(hSmqtiYkRpR3BSk_=3eiVz-AJi%MzzQxdf%NLJ`+a}fWKC`u9o|K zc5tZVuiw?06i|OgloOC3AYMS6fIvWq2aeXN=ePt_VH5e^UoQF+wR@7F$};KE+7UI; zBpqq8djAE67ax&lL##aTU3sU7Vn#CSyVVN;lax(OyZVQUKR;Wb_nK>|ve|S9&MtHe z6KlGGU6;6VJ)*z6Z;z_6QaOf9PrUjfKhQSA+WIrEZS2m}JBG1yic6)?375NXI`L4_h(lG=bvEs<>4sXuyt&>p6%d85>sHV4_BXrJNU%OECS)LPs56E zuxB-_vkL7w2b-j8JFTQ0LNfbv=+Em0DfhViGf(pfb7*jC9>$N}^q=3cN3@1#8vlm6 z=YOkA?6m53H*7r%XJAB$-GGWR;$#HjEQZT4l!(cOVhV=4bvVTK7~H5sF?@xEFVU5J$nj-nKVmehrUq}{l2TeQ}SNT`AuY-ZwYhI{9nd(S!Zt;TBWEJpsF zuIbI&v7g3c|?@j3<~1v%vNMasu@}zC`Jnb+CycxY0wqk~zP)-;e`vykgu(SvgJt@KiIX(v0_+L2ZjVu5F diff --git a/src/Main.elm b/src/Main.elm index ef84da1..a1c6a4b 100644 --- a/src/Main.elm +++ b/src/Main.elm @@ -236,11 +236,11 @@ update msg model = |> updateWith Home HomeMsg model ( PackagesMsg subMsg, Packages subModel ) -> - Page.Packages.update model.navKey subMsg subModel + Page.Packages.update model.navKey model.elasticsearch subMsg subModel |> updateWith Packages PackagesMsg model ( OptionsMsg subMsg, Options subModel ) -> - Page.Options.update model.navKey subMsg subModel + Page.Options.update model.navKey model.elasticsearch subMsg subModel |> updateWith Options OptionsMsg model ( _, _ ) -> diff --git a/src/Page/Options.elm b/src/Page/Options.elm index e7ac584..3f4d435 100644 --- a/src/Page/Options.elm +++ b/src/Page/Options.elm @@ -87,13 +87,20 @@ type Msg = SearchMsg (Search.Msg ResultItemSource) -update : Browser.Navigation.Key -> Msg -> Model -> ( Model, Cmd Msg ) -update navKey msg model = +update : Browser.Navigation.Key -> Search.Options -> Msg -> Model -> ( Model, Cmd Msg ) +update navKey options msg model = case msg of SearchMsg subMsg -> let ( newModel, newCmd ) = - Search.update "options" navKey subMsg model + Search.update + "options" + navKey + "option" + options + decodeResultItemSource + subMsg + model in ( newModel, Cmd.map SearchMsg newCmd ) @@ -115,7 +122,7 @@ view model = viewSuccess : String -> Maybe String - -> Search.Result ResultItemSource + -> Search.SearchResult ResultItemSource -> Html Msg viewSuccess channel show result = div [ class "search-result" ] @@ -380,9 +387,8 @@ makeRequest options channel queryRaw from size = ("latest-" ++ String.fromInt options.mappingSchemaVersion ++ "-" ++ channel) decodeResultItemSource options - query - from - size + Search.QueryResponse + (Just "query-options") |> Cmd.map SearchMsg diff --git a/src/Page/Packages.elm b/src/Page/Packages.elm index 7dcc50b..df77d90 100644 --- a/src/Page/Packages.elm +++ b/src/Page/Packages.elm @@ -122,13 +122,20 @@ type Msg = SearchMsg (Search.Msg ResultItemSource) -update : Browser.Navigation.Key -> Msg -> Model -> ( Model, Cmd Msg ) -update navKey msg model = +update : Browser.Navigation.Key -> Search.Options -> Msg -> Model -> ( Model, Cmd Msg ) +update navKey options msg model = case msg of SearchMsg subMsg -> let ( newModel, newCmd ) = - Search.update "packages" navKey subMsg model + Search.update + "packages" + navKey + "package" + options + decodeResultItemSource + subMsg + model in ( newModel, Cmd.map SearchMsg newCmd ) @@ -150,7 +157,7 @@ view model = viewSuccess : String -> Maybe String - -> Search.Result ResultItemSource + -> Search.SearchResult ResultItemSource -> Html Msg viewSuccess channel show result = div [ class "search-result" ] @@ -495,9 +502,8 @@ makeRequest options channel queryRaw from size = ("latest-" ++ String.fromInt options.mappingSchemaVersion ++ "-" ++ channel) decodeResultItemSource options - query - from - size + Search.QueryResponse + (Just "query-packages") |> Cmd.map SearchMsg diff --git a/src/Search.elm b/src/Search.elm index 7f4160c..d76151a 100644 --- a/src/Search.elm +++ b/src/Search.elm @@ -2,8 +2,8 @@ module Search exposing ( Model , Msg(..) , Options - , Result , ResultItem + , SearchResult , channelDetailsFromId , decodeResult , init @@ -13,8 +13,12 @@ module Search exposing , view ) +import Array import Base64 +import Browser.Dom import Browser.Navigation +import Debouncer.Messages +import Dict import Html exposing ( Html @@ -25,12 +29,10 @@ import Html , form , h1 , h4 + , i , input , li - , option , p - , select - , span , strong , text , ul @@ -41,6 +43,7 @@ import Html.Attributes , class , classList , href + , id , type_ , value ) @@ -50,27 +53,50 @@ import Html.Events , onClick , onInput , onSubmit - , preventDefaultOn ) import Http import Json.Decode import Json.Encode +import Keyboard +import Keyboard.Events import RemoteData +import Task import Url.Builder +type alias Char = + String + + type alias Model a = { channel : String , query : Maybe String - , result : RemoteData.WebData (Result a) + , queryDebounce : Debouncer.Messages.Debouncer (Msg a) + , querySuggest : RemoteData.WebData (SearchResult a) + , querySelectedSuggestion : Maybe String + , result : RemoteData.WebData (SearchResult a) , show : Maybe String , from : Int , size : Int } -type alias Result a = +type alias SearchResult a = { hits : ResultHits a + , suggest : Maybe (SearchSuggest a) + } + + +type alias SearchSuggest a = + { query : Maybe (List (SearchSuggestQuery a)) + } + + +type alias SearchSuggestQuery a = + { text : String + , offset : Int + , length : Int + , options : List (ResultItem a) } @@ -83,7 +109,7 @@ type alias ResultHits a = type alias ResultHitsTotal = { value : Int - , relation : String -- TODO: this should probably be Enum + , relation : String } @@ -92,10 +118,19 @@ type alias ResultItem a = , id : String , score : Float , source : a + , text : Maybe String , matched_queries : Maybe (List String) } +itemHtml : Char -> Html Never +itemHtml char = + div [] + [ i [ class "fa fa-rebel" ] [] + , text (" " ++ char) + ] + + init : Maybe String -> Maybe String @@ -122,7 +157,13 @@ init channel query show from size model = |> Maybe.withDefault 15 in ( { channel = Maybe.withDefault defaultChannel channel + , queryDebounce = + Debouncer.Messages.manual + |> Debouncer.Messages.settleWhenQuietFor (Just <| Debouncer.Messages.fromSeconds 0.6) + |> Debouncer.Messages.toDebouncer , query = query + , querySuggest = RemoteData.NotAsked + , querySelectedSuggestion = Nothing , result = model |> Maybe.map (\x -> x.result) @@ -144,19 +185,34 @@ init channel query show from size model = type Msg a = NoOp | ChannelChange String + | QueryInputDebounce (Debouncer.Messages.Msg (Msg a)) | QueryInput String + | QueryInputSuggestionsSubmit + | QueryInputSuggestionsResponse (RemoteData.WebData (SearchResult a)) | QuerySubmit - | QueryResponse (RemoteData.WebData (Result a)) + | QueryResponse (RemoteData.WebData (SearchResult a)) | ShowDetails String + | SuggestionsMoveDown + | SuggestionsMoveUp + | SuggestionsSelect + | SuggestionsClickSelect String + | SuggestionsClose update : String -> Browser.Navigation.Key + -> String + -> Options + -> Json.Decode.Decoder a -> Msg a -> Model a -> ( Model a, Cmd (Msg a) ) -update path navKey msg model = +update path navKey result_type options decodeResultItemSource msg model = + let + requestQuerySuggestionsTracker = + "query-" ++ result_type ++ "-suggestions" + in case msg of NoOp -> ( model @@ -187,8 +243,84 @@ update path navKey msg model = |> Browser.Navigation.pushUrl navKey ) + QueryInputDebounce subMsg -> + Debouncer.Messages.update + (update path navKey result_type options decodeResultItemSource) + { mapMsg = QueryInputDebounce + , getDebouncer = .queryDebounce + , setDebouncer = \debouncer m -> { m | queryDebounce = debouncer } + } + subMsg + model + QueryInput query -> - ( { model | query = Just query } + update path + navKey + result_type + options + decodeResultItemSource + (QueryInputDebounce (Debouncer.Messages.provideInput QueryInputSuggestionsSubmit)) + { model + | query = Just query + , querySuggest = RemoteData.NotAsked + , querySelectedSuggestion = Nothing + } + |> Tuple.mapSecond + (\cmd -> + if RemoteData.isLoading model.querySuggest then + Cmd.batch + [ cmd + , Http.cancel requestQuerySuggestionsTracker + ] + + else + cmd + ) + + QueryInputSuggestionsSubmit -> + let + body = + Http.jsonBody + (Json.Encode.object + [ ( "from", Json.Encode.int 0 ) + , ( "size", Json.Encode.int 0 ) + , ( "suggest" + , Json.Encode.object + [ ( "query" + , Json.Encode.object + [ ( "text", Json.Encode.string (Maybe.withDefault "" model.query) ) + , ( "completion" + , Json.Encode.object + [ ( "field", Json.Encode.string (result_type ++ "_suggestions") ) + , ( "skip_duplicates", Json.Encode.bool True ) + , ( "size", Json.Encode.int 1000 ) + ] + ) + ] + ) + ] + ) + ] + ) + in + ( { model + | querySuggest = RemoteData.Loading + , querySelectedSuggestion = Nothing + } + , makeRequest + body + ("latest-" ++ String.fromInt options.mappingSchemaVersion ++ "-" ++ model.channel) + decodeResultItemSource + options + QueryInputSuggestionsResponse + (Just requestQuerySuggestionsTracker) + ) + + QueryInputSuggestionsResponse querySuggest -> + ( { model + | querySuggest = querySuggest + , querySelectedSuggestion = Nothing + } , Cmd.none ) @@ -226,6 +358,147 @@ update path navKey msg model = |> Browser.Navigation.pushUrl navKey ) + SuggestionsMoveDown -> + ( { model + | querySelectedSuggestion = + getMovedSuggestion + model.query + model.querySuggest + model.querySelectedSuggestion + (\x -> x + 1) + } + , scrollToSelected "dropdown-menu" + ) + + SuggestionsMoveUp -> + ( { model + | querySelectedSuggestion = + getMovedSuggestion + model.query + model.querySuggest + model.querySelectedSuggestion + (\x -> x - 1) + } + , scrollToSelected "dropdown-menu" + ) + + SuggestionsSelect -> + case model.querySelectedSuggestion of + Just selected -> + update path + navKey + result_type + options + decodeResultItemSource + (SuggestionsClickSelect selected) + model + + Nothing -> + ( model + , Cmd.none + ) + + SuggestionsClickSelect selected -> + ( { model + | querySuggest = RemoteData.NotAsked + , querySelectedSuggestion = Nothing + , query = Just selected + } + , Task.attempt (\_ -> QueryInputSuggestionsSubmit) (Task.succeed ()) + ) + + SuggestionsClose -> + ( { model + | querySuggest = RemoteData.NotAsked + , querySelectedSuggestion = Nothing + } + , Cmd.none + ) + + +scrollToSelected : + String + -> Cmd (Msg a) +scrollToSelected id = + let + scroll y = + Browser.Dom.setViewportOf id 0 y + |> Task.onError (\_ -> Task.succeed ()) + in + Task.sequence + [ Browser.Dom.getElement (id ++ "-selected") + |> Task.map (\x -> ( x.element.y, x.element.height )) + , Browser.Dom.getElement id + |> Task.map (\x -> ( x.element.y, x.element.height )) + , Browser.Dom.getViewportOf id + |> Task.map (\x -> ( x.viewport.y, x.viewport.height )) + ] + |> Task.andThen + (\x -> + case x of + ( elementY, elementHeight ) :: ( viewportY, viewportHeight ) :: ( viewportScrollTop, _ ) :: [] -> + let + scrollTop = + scroll (viewportScrollTop + (elementY - viewportY)) + + scrollBottom = + scroll (viewportScrollTop + (elementY - viewportY) + (elementHeight - viewportHeight)) + in + if elementHeight > viewportHeight then + scrollTop + + else if elementY < viewportY then + scrollTop + + else if elementY + elementHeight > viewportY + viewportHeight then + scrollBottom + + else + Task.succeed () + + _ -> + Task.succeed () + ) + |> Task.attempt (\_ -> NoOp) + + +getMovedSuggestion : + Maybe String + -> RemoteData.WebData (SearchResult a) + -> Maybe String + -> (Int -> Int) + -> Maybe String +getMovedSuggestion query querySuggest querySelectedSuggestion moveIndex = + let + suggestions = + getSuggestions query querySuggest + |> List.filterMap .text + + getIndex key = + suggestions + |> List.indexedMap (\i a -> ( a, i )) + |> Dict.fromList + |> Dict.get key + |> Maybe.map moveIndex + |> Maybe.map + (\x -> + if x < 0 then + x + List.length suggestions + + else + x + ) + + getKey index = + suggestions + |> Array.fromList + |> Array.get index + in + querySelectedSuggestion + |> Maybe.andThen getIndex + |> Maybe.withDefault 0 + |> getKey + createUrl : String @@ -321,17 +594,70 @@ channels = ] +getSuggestions : + Maybe String + -> RemoteData.WebData (SearchResult a) + -> List (ResultItem a) +getSuggestions query querySuggest = + let + maybeList f x = + x + |> Maybe.map f + |> Maybe.withDefault [] + in + case querySuggest of + RemoteData.Success result -> + result.suggest + |> maybeList (\x -> x.query |> maybeList (List.map .options)) + |> List.concat + |> List.filter (\x -> x.text /= query) + + _ -> + [] + + view : String -> String -> Model a - -> (String -> Maybe String -> Result a -> Html b) + -> (String -> Maybe String -> SearchResult a -> Html b) -> (Msg a -> b) -> Html b view path title model viewSuccess outMsg = + let + suggestions = + getSuggestions model.query model.querySuggest + + viewSuggestion x = + li + [] + [ a + ([ href "#" ] + |> List.append + (x.text + |> Maybe.map (\text -> [ onClick <| outMsg (SuggestionsClickSelect text) ]) + |> Maybe.withDefault [] + ) + |> List.append + (if x.text == model.querySelectedSuggestion then + [ id "dropdown-menu-selected" ] + + else + [] + ) + ) + [ text <| Maybe.withDefault "" x.text ] + ] + in div [ class "search-page" ] [ h1 [ class "page-header" ] [ text title ] - , div [ class "search-input" ] + , div + [ classList + [ ( "search-input", True ) + , ( "with-suggestions", RemoteData.isSuccess model.querySuggest && List.length suggestions > 0 ) + , ( "with-suggestions-loading", RemoteData.isLoading model.querySuggest ) + ] + ] [ form [ onSubmit (outMsg QuerySubmit) ] [ p [] @@ -364,15 +690,56 @@ view path title model viewSuccess outMsg = [ class "input-append" ] [ input - [ type_ "text" - , onInput (\x -> outMsg (QueryInput x)) - , value <| Maybe.withDefault "" model.query - ] + ([ type_ "text" + , onInput (\x -> outMsg (QueryInput x)) + , value <| Maybe.withDefault "" model.query + ] + |> List.append + (if RemoteData.isSuccess model.querySuggest && List.length suggestions > 0 then + [ Keyboard.Events.custom Keyboard.Events.Keydown + { preventDefault = True + , stopPropagation = True + } + ([ ( Keyboard.ArrowDown, SuggestionsMoveDown ) + , ( Keyboard.ArrowUp, SuggestionsMoveUp ) + , ( Keyboard.Tab, SuggestionsMoveDown ) + , ( Keyboard.Enter, SuggestionsSelect ) + , ( Keyboard.Escape, SuggestionsClose ) + ] + |> List.map (\( k, m ) -> ( k, outMsg m )) + ) + ] + + else if RemoteData.isNotAsked model.querySuggest then + [ Keyboard.Events.custom Keyboard.Events.Keydown + { preventDefault = True + , stopPropagation = True + } + ([ ( Keyboard.ArrowDown, QueryInputSuggestionsSubmit ) + , ( Keyboard.ArrowUp, QueryInputSuggestionsSubmit ) + ] + |> List.map (\( k, m ) -> ( k, outMsg m )) + ) + ] + + else + [] + ) + ) [] + , div [ class "loader" ] [] , div [ class "btn-group" ] [ button [ class "btn" ] [ text "Search" ] ] ] + , ul + [ id "dropdown-menu", class "dropdown-menu" ] + (if RemoteData.isSuccess model.querySuggest && List.length suggestions > 0 then + List.map viewSuggestion suggestions + + else + [] + ) ] ] , case model.result of @@ -448,10 +815,10 @@ view path title model viewSuccess outMsg = viewPager : (Msg a -> b) -> Model a - -> Result a + -> SearchResult a -> String -> Html b -viewPager outMsg model result path = +viewPager _ model result path = ul [ class "pager" ] [ li [ classList @@ -652,37 +1019,16 @@ makeRequestBody : -> String -> List (List ( String, Json.Encode.Value )) -> Http.Body -makeRequestBody query from size type_ query_field should_queries = - -- TODO: rescore how close the query is to the root of the name - -- |> List.append - -- ("""int i = 1; - -- for (token in doc['option_name.raw'][0].splitOnToken('.')) { - -- if (token == '""" - -- ++ query - -- ++ """') { - -- return 10000 - (i * 100); - -- } - -- i++; - -- } - -- return 10; - -- """ - -- |> stringIn "source" - -- |> objectIn "script" - -- |> objectIn "script_score" - -- |> objectIn "function_score" - -- |> objectIn "rescore_query" - -- |> List.append ("total" |> stringIn "score_mode") - -- |> List.append ("total" |> stringIn "score_mode") - -- |> objectIn "query" - -- |> List.append [ ( "window_size", Json.Encode.int 1000 ) ] - -- |> objectIn "rescore" - -- ) - -- |> List.append - -- [ ( "from", Json.Encode.int from ) - -- , ( "size", Json.Encode.int size ) - -- ] - -- |> Json.Encode.object - -- |> Http.jsonBody +makeRequestBody query from sizeRaw type_ query_field should_queries = + let + -- you can not request more then 10000 results otherwise it will return 404 + size = + if from + sizeRaw > 10000 then + 10000 - from + + else + sizeRaw + in Http.jsonBody (Json.Encode.object [ ( "from" @@ -718,20 +1064,10 @@ makeRequest : -> String -> Json.Decode.Decoder a -> Options - -> String - -> Int - -> Int + -> (RemoteData.WebData (SearchResult a) -> Msg a) + -> Maybe String -> Cmd (Msg a) -makeRequest body index decodeResultItemSource options query from sizeRaw = - let - -- you can not request more then 10000 results otherwise it will return 404 - size = - if from + sizeRaw > 10000 then - 10000 - from - - else - sizeRaw - in +makeRequest body index decodeResultItemSource options responseMsg tracker = Http.riskyRequest { method = "POST" , headers = @@ -741,10 +1077,10 @@ makeRequest body index decodeResultItemSource options query from sizeRaw = , body = body , expect = Http.expectJson - (RemoteData.fromResult >> QueryResponse) + (RemoteData.fromResult >> responseMsg) (decodeResult decodeResultItemSource) , timeout = Nothing - , tracker = Nothing + , tracker = tracker } @@ -754,10 +1090,26 @@ makeRequest body index decodeResultItemSource options query from sizeRaw = decodeResult : Json.Decode.Decoder a - -> Json.Decode.Decoder (Result a) + -> Json.Decode.Decoder (SearchResult a) decodeResult decodeResultItemSource = - Json.Decode.map Result + Json.Decode.map2 SearchResult (Json.Decode.field "hits" (decodeResultHits decodeResultItemSource)) + (Json.Decode.maybe (Json.Decode.field "suggest" (decodeSuggest decodeResultItemSource))) + + +decodeSuggest : Json.Decode.Decoder a -> Json.Decode.Decoder (SearchSuggest a) +decodeSuggest decodeResultItemSource = + Json.Decode.map SearchSuggest + (Json.Decode.maybe (Json.Decode.field "query" (Json.Decode.list (decodeSuggestQuery decodeResultItemSource)))) + + +decodeSuggestQuery : Json.Decode.Decoder a -> Json.Decode.Decoder (SearchSuggestQuery a) +decodeSuggestQuery decodeResultItemSource = + Json.Decode.map4 SearchSuggestQuery + (Json.Decode.field "text" Json.Decode.string) + (Json.Decode.field "offset" Json.Decode.int) + (Json.Decode.field "length" Json.Decode.int) + (Json.Decode.field "options" (Json.Decode.list (decodeResultItem decodeResultItemSource))) decodeResultHits : Json.Decode.Decoder a -> Json.Decode.Decoder (ResultHits a) @@ -777,9 +1129,10 @@ decodeResultHitsTotal = decodeResultItem : Json.Decode.Decoder a -> Json.Decode.Decoder (ResultItem a) decodeResultItem decodeResultItemSource = - Json.Decode.map5 ResultItem + Json.Decode.map6 ResultItem (Json.Decode.field "_index" Json.Decode.string) (Json.Decode.field "_id" Json.Decode.string) (Json.Decode.field "_score" Json.Decode.float) (Json.Decode.field "_source" decodeResultItemSource) + (Json.Decode.maybe (Json.Decode.field "text" Json.Decode.string)) (Json.Decode.maybe (Json.Decode.field "matched_queries" (Json.Decode.list Json.Decode.string))) diff --git a/src/index.js b/src/index.js index c2aa0d1..88ac5c5 100644 --- a/src/index.js +++ b/src/index.js @@ -4,7 +4,6 @@ require("./index.scss"); const {Elm} = require('./Main'); -console.log("WORKS: " + process.env.ELASTICSEARCH_MAPPING_SCHEMA_VERSION); Elm.Main.init({ flags: { elasticsearchMappingSchemaVersion: parseInt(process.env.ELASTICSEARCH_MAPPING_SCHEMA_VERSION) || 0, diff --git a/src/index.scss b/src/index.scss index 617a6a3..3dca6a4 100644 --- a/src/index.scss +++ b/src/index.scss @@ -30,18 +30,68 @@ header .navbar.navbar-static-top { } .search-page { - .search-input { - text-align: center; - .input-append input { - font-size: 24px; - height: 40px; - width: auto; - max-width: 15em; + .search-input.with-suggestions-loading { + .input-append div.loader { + display: block; } - .input-append button { - font-size: 24px; - height: 50px; - min-width: 4em; + } + .search-input.with-suggestions { + ul.dropdown-menu { + display: block; + } + .input-append input { + border-radius: 4px 0 0 0; + } + } + .search-input { + position: relative; + ul.dropdown-menu { + font-size: 18px; + width: 25.6em; + height: auto; + max-height: 200px; + border-radius: 0; + margin-top: -10px; + border-top: 0; + overflow-y: scroll; + li > a { + font-size: 14px; + } + li > a#dropdown-menu-selected { + color: #fff; + text-decoration: none; + background-color: #0081c2; + background-image: -moz-linear-gradient(top,#08c,#0077b3); + background-image: -webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3)); + background-image: -webkit-linear-gradient(top,#08c,#0077b3); + background-image: -o-linear-gradient(top,#08c,#0077b3); + background-image: linear-gradient(to bottom,#08c,#0077b3); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0); + } + } + .input-append { + position: relative; + input { + font-size: 18px; + height: 40px; + width: 25em; + } + div.loader { + display: none; + z-index: 100; + position: absolute; + margin: 0; + top: 37px; + right: 125px; + font-size: 6px; + color: #999999; + } + button { + font-size: 24px; + height: 50px; + min-width: 4em; + } } form > p > strong { vertical-align: middle; @@ -84,7 +134,7 @@ header .navbar.navbar-static-top { .loader, .loader:before, .loader:after { - background: #ffffff; + background: transparent; -webkit-animation: load1 1s infinite ease-in-out; animation: load1 1s infinite ease-in-out; width: 1em;