Suggestions for search field (#74)
This commit is contained in:
parent
b06bac6187
commit
1f8939b3af
45
.gitignore
vendored
45
.gitignore
vendored
|
@ -1,35 +1,20 @@
|
||||||
# Logs
|
|
||||||
logs
|
|
||||||
*.log
|
*.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
|
.DS_Store
|
||||||
example/dist
|
.cache
|
||||||
|
.idea
|
||||||
ignore
|
.node_repl_history
|
||||||
|
.npm
|
||||||
|
build/Release
|
||||||
dist
|
dist
|
||||||
package-lock.json
|
elm-stuff/
|
||||||
result
|
|
||||||
scripts/eval-*
|
|
||||||
eval-*
|
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
|
src-url
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{ pkgs ? import <nixpkgs> { }
|
{ pkgs ? import <nixpkgs> { }
|
||||||
, version ? "0"
|
, version ? pkgs.lib.removeSuffix "\n" (builtins.readFile ./VERSION)
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
package = builtins.fromJSON (builtins.readFile ./package.json);
|
package = builtins.fromJSON (builtins.readFile ./package.json);
|
||||||
|
|
35
elm-srcs.nix
35
elm-srcs.nix
|
@ -10,11 +10,21 @@
|
||||||
version = "1.1.3";
|
version = "1.1.3";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
"ohanhi/keyboard" = {
|
||||||
|
sha256 = "10sbq8v2kydnc3lkydl367g36q2b0xizxl031xyakrgl4zlh07ic";
|
||||||
|
version = "2.0.1";
|
||||||
|
};
|
||||||
|
|
||||||
"truqu/elm-base64" = {
|
"truqu/elm-base64" = {
|
||||||
sha256 = "12w68b4idbs2vn0gm0lj354pm745jb7n0fj69408mpvh5r1z4m1b";
|
sha256 = "12w68b4idbs2vn0gm0lj354pm745jb7n0fj69408mpvh5r1z4m1b";
|
||||||
version = "2.0.4";
|
version = "2.0.4";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
"elm/regex" = {
|
||||||
|
sha256 = "0lijsp50w7n1n57mjg6clpn9phly8vvs07h0qh2rqcs0f1jqvsa2";
|
||||||
|
version = "1.0.0";
|
||||||
|
};
|
||||||
|
|
||||||
"elm/html" = {
|
"elm/html" = {
|
||||||
sha256 = "1n3gpzmpqqdsldys4ipgyl1zacn0kbpc3g4v3hdpiyfjlgh8bf3k";
|
sha256 = "1n3gpzmpqqdsldys4ipgyl1zacn0kbpc3g4v3hdpiyfjlgh8bf3k";
|
||||||
version = "1.0.0";
|
version = "1.0.0";
|
||||||
|
@ -25,6 +35,16 @@
|
||||||
version = "1.0.2";
|
version = "1.0.2";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
"Gizra/elm-debouncer" = {
|
||||||
|
sha256 = "009yw0rb418ar2a458ilr25m8gxrxsv5nvs3ld3l6sy12v12n0yn";
|
||||||
|
version = "2.0.0";
|
||||||
|
};
|
||||||
|
|
||||||
|
"Skinney/keyboard-events" = {
|
||||||
|
sha256 = "10qjlpa4byk78sra071w4ghc7b9p2brnppx7aqyy9cmbrmp5nf86";
|
||||||
|
version = "2.0.1";
|
||||||
|
};
|
||||||
|
|
||||||
"elm/core" = {
|
"elm/core" = {
|
||||||
sha256 = "0gyk7lx3b6vx2jlfbxdsb4xffn0wdvg5yxldq50jr2kk5dzc2prj";
|
sha256 = "0gyk7lx3b6vx2jlfbxdsb4xffn0wdvg5yxldq50jr2kk5dzc2prj";
|
||||||
version = "1.0.4";
|
version = "1.0.4";
|
||||||
|
@ -60,11 +80,6 @@
|
||||||
version = "1.0.5";
|
version = "1.0.5";
|
||||||
};
|
};
|
||||||
|
|
||||||
"elm/regex" = {
|
|
||||||
sha256 = "0lijsp50w7n1n57mjg6clpn9phly8vvs07h0qh2rqcs0f1jqvsa2";
|
|
||||||
version = "1.0.0";
|
|
||||||
};
|
|
||||||
|
|
||||||
"rtfeldman/elm-hex" = {
|
"rtfeldman/elm-hex" = {
|
||||||
sha256 = "1y0aa16asvwdqmgbskh5iba6psp43lkcjjw9mgzj3gsrg33lp00d";
|
sha256 = "1y0aa16asvwdqmgbskh5iba6psp43lkcjjw9mgzj3gsrg33lp00d";
|
||||||
version = "1.0.0";
|
version = "1.0.0";
|
||||||
|
@ -75,6 +90,11 @@
|
||||||
version = "1.1.0";
|
version = "1.1.0";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
"elm-community/list-extra" = {
|
||||||
|
sha256 = "1rvr1c8cfb3dwf3li17l9ziax6d1fshkliasspnw6rviva38lw34";
|
||||||
|
version = "8.2.4";
|
||||||
|
};
|
||||||
|
|
||||||
"elm/time" = {
|
"elm/time" = {
|
||||||
sha256 = "0vch7i86vn0x8b850w1p69vplll1bnbkp8s383z7pinyg94cm2z1";
|
sha256 = "0vch7i86vn0x8b850w1p69vplll1bnbkp8s383z7pinyg94cm2z1";
|
||||||
version = "1.0.0";
|
version = "1.0.0";
|
||||||
|
@ -90,6 +110,11 @@
|
||||||
version = "1.2.2";
|
version = "1.2.2";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
"elm/svg" = {
|
||||||
|
sha256 = "1cwcj73p61q45wqwgqvrvz3aypjyy3fw732xyxdyj6s256hwkn0k";
|
||||||
|
version = "1.0.1";
|
||||||
|
};
|
||||||
|
|
||||||
"elm/random" = {
|
"elm/random" = {
|
||||||
sha256 = "138n2455wdjwa657w6sjq18wx2r0k60ibpc4frhbqr50sncxrfdl";
|
sha256 = "138n2455wdjwa657w6sjq18wx2r0k60ibpc4frhbqr50sncxrfdl";
|
||||||
version = "1.0.0";
|
version = "1.0.0";
|
||||||
|
|
7
elm.json
7
elm.json
|
@ -6,7 +6,9 @@
|
||||||
"elm-version": "0.19.1",
|
"elm-version": "0.19.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"direct": {
|
"direct": {
|
||||||
|
"Gizra/elm-debouncer": "2.0.0",
|
||||||
"NoRedInk/elm-json-decode-pipeline": "1.0.0",
|
"NoRedInk/elm-json-decode-pipeline": "1.0.0",
|
||||||
|
"Skinney/keyboard-events": "2.0.1",
|
||||||
"elm/browser": "1.0.2",
|
"elm/browser": "1.0.2",
|
||||||
"elm/core": "1.0.4",
|
"elm/core": "1.0.4",
|
||||||
"elm/html": "1.0.0",
|
"elm/html": "1.0.0",
|
||||||
|
@ -16,6 +18,7 @@
|
||||||
"elm/url": "1.0.0",
|
"elm/url": "1.0.0",
|
||||||
"hecrj/html-parser": "2.3.4",
|
"hecrj/html-parser": "2.3.4",
|
||||||
"krisajenkins/remotedata": "6.0.1",
|
"krisajenkins/remotedata": "6.0.1",
|
||||||
|
"ohanhi/keyboard": "2.0.1",
|
||||||
"truqu/elm-base64": "2.0.4"
|
"truqu/elm-base64": "2.0.4"
|
||||||
},
|
},
|
||||||
"indirect": {
|
"indirect": {
|
||||||
|
@ -24,6 +27,7 @@
|
||||||
"elm/parser": "1.1.0",
|
"elm/parser": "1.1.0",
|
||||||
"elm/time": "1.0.0",
|
"elm/time": "1.0.0",
|
||||||
"elm/virtual-dom": "1.0.2",
|
"elm/virtual-dom": "1.0.2",
|
||||||
|
"elm-community/list-extra": "8.2.4",
|
||||||
"rtfeldman/elm-hex": "1.0.0"
|
"rtfeldman/elm-hex": "1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -32,7 +36,8 @@
|
||||||
"elm-explorations/test": "1.2.2"
|
"elm-explorations/test": "1.2.2"
|
||||||
},
|
},
|
||||||
"indirect": {
|
"indirect": {
|
||||||
"elm/random": "1.0.0"
|
"elm/random": "1.0.0",
|
||||||
|
"elm/svg": "1.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,14 +18,13 @@
|
||||||
poetry2nix.overlay
|
poetry2nix.overlay
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
version = pkgs.lib.removeSuffix "\n" (builtins.readFile "${self}/VERSION");
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
import_scripts = import ./import-scripts {
|
import_scripts = import ./import-scripts {
|
||||||
inherit pkgs version;
|
inherit pkgs;
|
||||||
};
|
};
|
||||||
frontend = import ./. {
|
frontend = import ./. {
|
||||||
inherit pkgs version;
|
inherit pkgs;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{ pkgs ? import <nixpkgs> { }
|
{ pkgs ? import <nixpkgs> { }
|
||||||
, version ? "0"
|
, version ? pkgs.lib.removeSuffix "\n" (builtins.readFile ./../VERSION)
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
inherit (pkgs.poetry2nix) mkPoetryApplication overrides;
|
inherit (pkgs.poetry2nix) mkPoetryApplication overrides;
|
||||||
|
@ -13,11 +13,21 @@ mkPoetryApplication {
|
||||||
'';
|
'';
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
nativeBuildInputs = [
|
||||||
|
pkgs.poetry
|
||||||
|
];
|
||||||
checkPhase = ''
|
checkPhase = ''
|
||||||
black --diff --check ./import_scripts
|
export PYTHONPATH=$PWD:$PYTHONPATH
|
||||||
flake8 --ignore W503,E501,E265,E203 ./import_scripts
|
black --diff --check import_scripts/ tests/
|
||||||
|
flake8 --ignore W503,E501,E265,E203 import_scripts/ tests/
|
||||||
|
mypy import_scripts/ tests/
|
||||||
|
pytest -vv tests/
|
||||||
'';
|
'';
|
||||||
postInstall = ''
|
postInstall = ''
|
||||||
wrapProgram $out/bin/import-channel --set INDEX_SCHEMA_VERSION "${version}"
|
wrapProgram $out/bin/import-channel --set INDEX_SCHEMA_VERSION "${version}"
|
||||||
'';
|
'';
|
||||||
|
shellHook = ''
|
||||||
|
cd import-scripts/
|
||||||
|
export PYTHONPATH=$PWD:$PYTHONPATH
|
||||||
|
'';
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,22 @@
|
||||||
import boto3
|
import boto3 # type: ignore
|
||||||
import botocore
|
import botocore # type: ignore
|
||||||
import botocore.client
|
import botocore.client # type: ignore
|
||||||
import click
|
import click
|
||||||
import click_log
|
import click_log # type: ignore
|
||||||
import elasticsearch
|
import elasticsearch # type: ignore
|
||||||
import elasticsearch.helpers
|
import elasticsearch.helpers # type: ignore
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
import pypandoc
|
import pypandoc # type: ignore
|
||||||
import re
|
import re
|
||||||
import requests
|
import requests
|
||||||
import shlex
|
import shlex
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tqdm
|
import tqdm # type: ignore
|
||||||
|
import typing
|
||||||
import xml.etree.ElementTree
|
import xml.etree.ElementTree
|
||||||
|
|
||||||
logger = logging.getLogger("import-channel")
|
logger = logging.getLogger("import-channel")
|
||||||
|
@ -55,6 +56,12 @@ MAPPING = {
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {"type": "keyword"},
|
"type": {"type": "keyword"},
|
||||||
# Package fields
|
# Package fields
|
||||||
|
"package_suggestions": {
|
||||||
|
"type": "completion",
|
||||||
|
"analyzer": "lowercase",
|
||||||
|
"search_analyzer": "lowercase",
|
||||||
|
"preserve_position_increments": False,
|
||||||
|
},
|
||||||
"package_hydra_build": {
|
"package_hydra_build": {
|
||||||
"type": "nested",
|
"type": "nested",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -98,6 +105,12 @@ MAPPING = {
|
||||||
"package_homepage": {"type": "keyword"},
|
"package_homepage": {"type": "keyword"},
|
||||||
"package_system": {"type": "keyword"},
|
"package_system": {"type": "keyword"},
|
||||||
# Options fields
|
# Options fields
|
||||||
|
"option_suggestions": {
|
||||||
|
"type": "completion",
|
||||||
|
"analyzer": "lowercase",
|
||||||
|
"search_analyzer": "lowercase",
|
||||||
|
"preserve_position_increments": False,
|
||||||
|
},
|
||||||
"option_name": {"type": "keyword", "normalizer": "lowercase"},
|
"option_name": {"type": "keyword", "normalizer": "lowercase"},
|
||||||
"option_name_query": {"type": "keyword", "normalizer": "lowercase"},
|
"option_name_query": {"type": "keyword", "normalizer": "lowercase"},
|
||||||
"option_description": {"type": "text"},
|
"option_description": {"type": "text"},
|
||||||
|
@ -109,11 +122,33 @@ MAPPING = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def split_query(text):
|
def parse_suggestions(text: str) -> typing.List[typing.Dict[str, object]]:
|
||||||
"""Tokenize package attr_name
|
"""Tokenize option_name
|
||||||
|
|
||||||
Example:
|
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
|
python37Packages.test_name-test
|
||||||
= index: 0
|
= index: 0
|
||||||
- python37Packages.test1_name-test2
|
- python37Packages.test1_name-test2
|
||||||
|
@ -345,9 +380,10 @@ def get_packages(evaluation, evaluation_builds):
|
||||||
|
|
||||||
yield dict(
|
yield dict(
|
||||||
type="package",
|
type="package",
|
||||||
|
package_suggestions=parse_suggestions(attr_name),
|
||||||
package_hydra=hydra,
|
package_hydra=hydra,
|
||||||
package_attr_name=attr_name,
|
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_attr_set=attr_set,
|
||||||
package_pname=remove_attr_set(data["pname"]),
|
package_pname=remove_attr_set(data["pname"]),
|
||||||
package_pversion=data["version"],
|
package_pversion=data["version"],
|
||||||
|
@ -411,8 +447,9 @@ def get_options(evaluation):
|
||||||
|
|
||||||
yield dict(
|
yield dict(
|
||||||
type="option",
|
type="option",
|
||||||
|
option_suggestions=parse_suggestions(name),
|
||||||
option_name=name,
|
option_name=name,
|
||||||
option_name_query=split_query(name),
|
option_name_query=parse_query(name),
|
||||||
option_description=description,
|
option_description=description,
|
||||||
option_type=option.get("type"),
|
option_type=option.get("type"),
|
||||||
option_default=default,
|
option_default=default,
|
||||||
|
@ -539,5 +576,3 @@ def run(es_url, channel, force, verbose):
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
run()
|
run()
|
||||||
|
|
||||||
# vi:ft=python
|
|
||||||
|
|
368
import-scripts/poetry.lock
generated
368
import-scripts/poetry.lock
generated
File diff suppressed because one or more lines are too long
|
@ -25,6 +25,9 @@ ipdb = "^0.13.2"
|
||||||
black = "^19.10b0"
|
black = "^19.10b0"
|
||||||
flake8 = "^3.8.3"
|
flake8 = "^3.8.3"
|
||||||
mypy = "^0.780"
|
mypy = "^0.780"
|
||||||
|
pytest = "^5.4.3"
|
||||||
|
setuptools = "^47.3.1"
|
||||||
|
boto3-stubs = "^1.14.6"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry>=0.12"]
|
requires = ["poetry>=0.12"]
|
||||||
|
|
52
import-scripts/tests/Example.elm
Normal file
52
import-scripts/tests/Example.elm
Normal file
|
@ -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 <https://github.com/elm-community/elm-test>
|
||||||
|
-}
|
||||||
|
unitTest : Test
|
||||||
|
unitTest =
|
||||||
|
describe "simple unit test"
|
||||||
|
[ test "Inc adds one" <|
|
||||||
|
\() ->
|
||||||
|
update Inc (Model 0 "")
|
||||||
|
|> Tuple.first
|
||||||
|
|> .counter
|
||||||
|
|> Expect.equal 1
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
{-| See <https://github.com/elm-community/elm-test>
|
||||||
|
-}
|
||||||
|
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 <https://github.com/eeue56/elm-html-test>
|
||||||
|
-}
|
||||||
|
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" ]
|
||||||
|
]
|
104
import-scripts/tests/test_channel.py
Normal file
104
import-scripts/tests/test_channel.py
Normal file
|
@ -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)
|
BIN
registry.dat
BIN
registry.dat
Binary file not shown.
|
@ -236,11 +236,11 @@ update msg model =
|
||||||
|> updateWith Home HomeMsg model
|
|> updateWith Home HomeMsg model
|
||||||
|
|
||||||
( PackagesMsg subMsg, Packages subModel ) ->
|
( PackagesMsg subMsg, Packages subModel ) ->
|
||||||
Page.Packages.update model.navKey subMsg subModel
|
Page.Packages.update model.navKey model.elasticsearch subMsg subModel
|
||||||
|> updateWith Packages PackagesMsg model
|
|> updateWith Packages PackagesMsg model
|
||||||
|
|
||||||
( OptionsMsg subMsg, Options subModel ) ->
|
( OptionsMsg subMsg, Options subModel ) ->
|
||||||
Page.Options.update model.navKey subMsg subModel
|
Page.Options.update model.navKey model.elasticsearch subMsg subModel
|
||||||
|> updateWith Options OptionsMsg model
|
|> updateWith Options OptionsMsg model
|
||||||
|
|
||||||
( _, _ ) ->
|
( _, _ ) ->
|
||||||
|
|
|
@ -87,13 +87,20 @@ type Msg
|
||||||
= SearchMsg (Search.Msg ResultItemSource)
|
= SearchMsg (Search.Msg ResultItemSource)
|
||||||
|
|
||||||
|
|
||||||
update : Browser.Navigation.Key -> Msg -> Model -> ( Model, Cmd Msg )
|
update : Browser.Navigation.Key -> Search.Options -> Msg -> Model -> ( Model, Cmd Msg )
|
||||||
update navKey msg model =
|
update navKey options msg model =
|
||||||
case msg of
|
case msg of
|
||||||
SearchMsg subMsg ->
|
SearchMsg subMsg ->
|
||||||
let
|
let
|
||||||
( newModel, newCmd ) =
|
( newModel, newCmd ) =
|
||||||
Search.update "options" navKey subMsg model
|
Search.update
|
||||||
|
"options"
|
||||||
|
navKey
|
||||||
|
"option"
|
||||||
|
options
|
||||||
|
decodeResultItemSource
|
||||||
|
subMsg
|
||||||
|
model
|
||||||
in
|
in
|
||||||
( newModel, Cmd.map SearchMsg newCmd )
|
( newModel, Cmd.map SearchMsg newCmd )
|
||||||
|
|
||||||
|
@ -115,7 +122,7 @@ view model =
|
||||||
viewSuccess :
|
viewSuccess :
|
||||||
String
|
String
|
||||||
-> Maybe String
|
-> Maybe String
|
||||||
-> Search.Result ResultItemSource
|
-> Search.SearchResult ResultItemSource
|
||||||
-> Html Msg
|
-> Html Msg
|
||||||
viewSuccess channel show result =
|
viewSuccess channel show result =
|
||||||
div [ class "search-result" ]
|
div [ class "search-result" ]
|
||||||
|
@ -380,9 +387,8 @@ makeRequest options channel queryRaw from size =
|
||||||
("latest-" ++ String.fromInt options.mappingSchemaVersion ++ "-" ++ channel)
|
("latest-" ++ String.fromInt options.mappingSchemaVersion ++ "-" ++ channel)
|
||||||
decodeResultItemSource
|
decodeResultItemSource
|
||||||
options
|
options
|
||||||
query
|
Search.QueryResponse
|
||||||
from
|
(Just "query-options")
|
||||||
size
|
|
||||||
|> Cmd.map SearchMsg
|
|> Cmd.map SearchMsg
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -122,13 +122,20 @@ type Msg
|
||||||
= SearchMsg (Search.Msg ResultItemSource)
|
= SearchMsg (Search.Msg ResultItemSource)
|
||||||
|
|
||||||
|
|
||||||
update : Browser.Navigation.Key -> Msg -> Model -> ( Model, Cmd Msg )
|
update : Browser.Navigation.Key -> Search.Options -> Msg -> Model -> ( Model, Cmd Msg )
|
||||||
update navKey msg model =
|
update navKey options msg model =
|
||||||
case msg of
|
case msg of
|
||||||
SearchMsg subMsg ->
|
SearchMsg subMsg ->
|
||||||
let
|
let
|
||||||
( newModel, newCmd ) =
|
( newModel, newCmd ) =
|
||||||
Search.update "packages" navKey subMsg model
|
Search.update
|
||||||
|
"packages"
|
||||||
|
navKey
|
||||||
|
"package"
|
||||||
|
options
|
||||||
|
decodeResultItemSource
|
||||||
|
subMsg
|
||||||
|
model
|
||||||
in
|
in
|
||||||
( newModel, Cmd.map SearchMsg newCmd )
|
( newModel, Cmd.map SearchMsg newCmd )
|
||||||
|
|
||||||
|
@ -150,7 +157,7 @@ view model =
|
||||||
viewSuccess :
|
viewSuccess :
|
||||||
String
|
String
|
||||||
-> Maybe String
|
-> Maybe String
|
||||||
-> Search.Result ResultItemSource
|
-> Search.SearchResult ResultItemSource
|
||||||
-> Html Msg
|
-> Html Msg
|
||||||
viewSuccess channel show result =
|
viewSuccess channel show result =
|
||||||
div [ class "search-result" ]
|
div [ class "search-result" ]
|
||||||
|
@ -495,9 +502,8 @@ makeRequest options channel queryRaw from size =
|
||||||
("latest-" ++ String.fromInt options.mappingSchemaVersion ++ "-" ++ channel)
|
("latest-" ++ String.fromInt options.mappingSchemaVersion ++ "-" ++ channel)
|
||||||
decodeResultItemSource
|
decodeResultItemSource
|
||||||
options
|
options
|
||||||
query
|
Search.QueryResponse
|
||||||
from
|
(Just "query-packages")
|
||||||
size
|
|
||||||
|> Cmd.map SearchMsg
|
|> Cmd.map SearchMsg
|
||||||
|
|
||||||
|
|
||||||
|
|
489
src/Search.elm
489
src/Search.elm
|
@ -2,8 +2,8 @@ module Search exposing
|
||||||
( Model
|
( Model
|
||||||
, Msg(..)
|
, Msg(..)
|
||||||
, Options
|
, Options
|
||||||
, Result
|
|
||||||
, ResultItem
|
, ResultItem
|
||||||
|
, SearchResult
|
||||||
, channelDetailsFromId
|
, channelDetailsFromId
|
||||||
, decodeResult
|
, decodeResult
|
||||||
, init
|
, init
|
||||||
|
@ -13,8 +13,12 @@ module Search exposing
|
||||||
, view
|
, view
|
||||||
)
|
)
|
||||||
|
|
||||||
|
import Array
|
||||||
import Base64
|
import Base64
|
||||||
|
import Browser.Dom
|
||||||
import Browser.Navigation
|
import Browser.Navigation
|
||||||
|
import Debouncer.Messages
|
||||||
|
import Dict
|
||||||
import Html
|
import Html
|
||||||
exposing
|
exposing
|
||||||
( Html
|
( Html
|
||||||
|
@ -25,12 +29,10 @@ import Html
|
||||||
, form
|
, form
|
||||||
, h1
|
, h1
|
||||||
, h4
|
, h4
|
||||||
|
, i
|
||||||
, input
|
, input
|
||||||
, li
|
, li
|
||||||
, option
|
|
||||||
, p
|
, p
|
||||||
, select
|
|
||||||
, span
|
|
||||||
, strong
|
, strong
|
||||||
, text
|
, text
|
||||||
, ul
|
, ul
|
||||||
|
@ -41,6 +43,7 @@ import Html.Attributes
|
||||||
, class
|
, class
|
||||||
, classList
|
, classList
|
||||||
, href
|
, href
|
||||||
|
, id
|
||||||
, type_
|
, type_
|
||||||
, value
|
, value
|
||||||
)
|
)
|
||||||
|
@ -50,27 +53,50 @@ import Html.Events
|
||||||
, onClick
|
, onClick
|
||||||
, onInput
|
, onInput
|
||||||
, onSubmit
|
, onSubmit
|
||||||
, preventDefaultOn
|
|
||||||
)
|
)
|
||||||
import Http
|
import Http
|
||||||
import Json.Decode
|
import Json.Decode
|
||||||
import Json.Encode
|
import Json.Encode
|
||||||
|
import Keyboard
|
||||||
|
import Keyboard.Events
|
||||||
import RemoteData
|
import RemoteData
|
||||||
|
import Task
|
||||||
import Url.Builder
|
import Url.Builder
|
||||||
|
|
||||||
|
|
||||||
|
type alias Char =
|
||||||
|
String
|
||||||
|
|
||||||
|
|
||||||
type alias Model a =
|
type alias Model a =
|
||||||
{ channel : String
|
{ channel : String
|
||||||
, query : Maybe 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
|
, show : Maybe String
|
||||||
, from : Int
|
, from : Int
|
||||||
, size : Int
|
, size : Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type alias Result a =
|
type alias SearchResult a =
|
||||||
{ hits : ResultHits 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 =
|
type alias ResultHitsTotal =
|
||||||
{ value : Int
|
{ value : Int
|
||||||
, relation : String -- TODO: this should probably be Enum
|
, relation : String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -92,10 +118,19 @@ type alias ResultItem a =
|
||||||
, id : String
|
, id : String
|
||||||
, score : Float
|
, score : Float
|
||||||
, source : a
|
, source : a
|
||||||
|
, text : Maybe String
|
||||||
, matched_queries : Maybe (List String)
|
, matched_queries : Maybe (List String)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
itemHtml : Char -> Html Never
|
||||||
|
itemHtml char =
|
||||||
|
div []
|
||||||
|
[ i [ class "fa fa-rebel" ] []
|
||||||
|
, text (" " ++ char)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
init :
|
init :
|
||||||
Maybe String
|
Maybe String
|
||||||
-> Maybe String
|
-> Maybe String
|
||||||
|
@ -122,7 +157,13 @@ init channel query show from size model =
|
||||||
|> Maybe.withDefault 15
|
|> Maybe.withDefault 15
|
||||||
in
|
in
|
||||||
( { channel = Maybe.withDefault defaultChannel channel
|
( { channel = Maybe.withDefault defaultChannel channel
|
||||||
|
, queryDebounce =
|
||||||
|
Debouncer.Messages.manual
|
||||||
|
|> Debouncer.Messages.settleWhenQuietFor (Just <| Debouncer.Messages.fromSeconds 0.6)
|
||||||
|
|> Debouncer.Messages.toDebouncer
|
||||||
, query = query
|
, query = query
|
||||||
|
, querySuggest = RemoteData.NotAsked
|
||||||
|
, querySelectedSuggestion = Nothing
|
||||||
, result =
|
, result =
|
||||||
model
|
model
|
||||||
|> Maybe.map (\x -> x.result)
|
|> Maybe.map (\x -> x.result)
|
||||||
|
@ -144,19 +185,34 @@ init channel query show from size model =
|
||||||
type Msg a
|
type Msg a
|
||||||
= NoOp
|
= NoOp
|
||||||
| ChannelChange String
|
| ChannelChange String
|
||||||
|
| QueryInputDebounce (Debouncer.Messages.Msg (Msg a))
|
||||||
| QueryInput String
|
| QueryInput String
|
||||||
|
| QueryInputSuggestionsSubmit
|
||||||
|
| QueryInputSuggestionsResponse (RemoteData.WebData (SearchResult a))
|
||||||
| QuerySubmit
|
| QuerySubmit
|
||||||
| QueryResponse (RemoteData.WebData (Result a))
|
| QueryResponse (RemoteData.WebData (SearchResult a))
|
||||||
| ShowDetails String
|
| ShowDetails String
|
||||||
|
| SuggestionsMoveDown
|
||||||
|
| SuggestionsMoveUp
|
||||||
|
| SuggestionsSelect
|
||||||
|
| SuggestionsClickSelect String
|
||||||
|
| SuggestionsClose
|
||||||
|
|
||||||
|
|
||||||
update :
|
update :
|
||||||
String
|
String
|
||||||
-> Browser.Navigation.Key
|
-> Browser.Navigation.Key
|
||||||
|
-> String
|
||||||
|
-> Options
|
||||||
|
-> Json.Decode.Decoder a
|
||||||
-> Msg a
|
-> Msg a
|
||||||
-> Model a
|
-> Model a
|
||||||
-> ( Model a, Cmd (Msg 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
|
case msg of
|
||||||
NoOp ->
|
NoOp ->
|
||||||
( model
|
( model
|
||||||
|
@ -187,8 +243,84 @@ update path navKey msg model =
|
||||||
|> Browser.Navigation.pushUrl navKey
|
|> 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 ->
|
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
|
, Cmd.none
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -226,6 +358,147 @@ update path navKey msg model =
|
||||||
|> Browser.Navigation.pushUrl navKey
|
|> 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 :
|
createUrl :
|
||||||
String
|
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 :
|
view :
|
||||||
String
|
String
|
||||||
-> String
|
-> String
|
||||||
-> Model a
|
-> Model a
|
||||||
-> (String -> Maybe String -> Result a -> Html b)
|
-> (String -> Maybe String -> SearchResult a -> Html b)
|
||||||
-> (Msg a -> b)
|
-> (Msg a -> b)
|
||||||
-> Html b
|
-> Html b
|
||||||
view path title model viewSuccess outMsg =
|
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" ]
|
div [ class "search-page" ]
|
||||||
[ h1 [ class "page-header" ] [ text title ]
|
[ 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) ]
|
[ form [ onSubmit (outMsg QuerySubmit) ]
|
||||||
[ p
|
[ p
|
||||||
[]
|
[]
|
||||||
|
@ -364,15 +690,56 @@ view path title model viewSuccess outMsg =
|
||||||
[ class "input-append"
|
[ class "input-append"
|
||||||
]
|
]
|
||||||
[ input
|
[ input
|
||||||
[ type_ "text"
|
([ type_ "text"
|
||||||
, onInput (\x -> outMsg (QueryInput x))
|
, onInput (\x -> outMsg (QueryInput x))
|
||||||
, value <| Maybe.withDefault "" model.query
|
, 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" ]
|
, div [ class "btn-group" ]
|
||||||
[ button [ class "btn" ] [ text "Search" ]
|
[ 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
|
, case model.result of
|
||||||
|
@ -448,10 +815,10 @@ view path title model viewSuccess outMsg =
|
||||||
viewPager :
|
viewPager :
|
||||||
(Msg a -> b)
|
(Msg a -> b)
|
||||||
-> Model a
|
-> Model a
|
||||||
-> Result a
|
-> SearchResult a
|
||||||
-> String
|
-> String
|
||||||
-> Html b
|
-> Html b
|
||||||
viewPager outMsg model result path =
|
viewPager _ model result path =
|
||||||
ul [ class "pager" ]
|
ul [ class "pager" ]
|
||||||
[ li
|
[ li
|
||||||
[ classList
|
[ classList
|
||||||
|
@ -652,37 +1019,16 @@ makeRequestBody :
|
||||||
-> String
|
-> String
|
||||||
-> List (List ( String, Json.Encode.Value ))
|
-> List (List ( String, Json.Encode.Value ))
|
||||||
-> Http.Body
|
-> Http.Body
|
||||||
makeRequestBody query from size type_ query_field should_queries =
|
makeRequestBody query from sizeRaw type_ query_field should_queries =
|
||||||
-- TODO: rescore how close the query is to the root of the name
|
let
|
||||||
-- |> List.append
|
-- you can not request more then 10000 results otherwise it will return 404
|
||||||
-- ("""int i = 1;
|
size =
|
||||||
-- for (token in doc['option_name.raw'][0].splitOnToken('.')) {
|
if from + sizeRaw > 10000 then
|
||||||
-- if (token == '"""
|
10000 - from
|
||||||
-- ++ query
|
|
||||||
-- ++ """') {
|
else
|
||||||
-- return 10000 - (i * 100);
|
sizeRaw
|
||||||
-- }
|
in
|
||||||
-- 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
|
|
||||||
Http.jsonBody
|
Http.jsonBody
|
||||||
(Json.Encode.object
|
(Json.Encode.object
|
||||||
[ ( "from"
|
[ ( "from"
|
||||||
|
@ -718,20 +1064,10 @@ makeRequest :
|
||||||
-> String
|
-> String
|
||||||
-> Json.Decode.Decoder a
|
-> Json.Decode.Decoder a
|
||||||
-> Options
|
-> Options
|
||||||
-> String
|
-> (RemoteData.WebData (SearchResult a) -> Msg a)
|
||||||
-> Int
|
-> Maybe String
|
||||||
-> Int
|
|
||||||
-> Cmd (Msg a)
|
-> Cmd (Msg a)
|
||||||
makeRequest body index decodeResultItemSource options query from sizeRaw =
|
makeRequest body index decodeResultItemSource options responseMsg tracker =
|
||||||
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.riskyRequest
|
Http.riskyRequest
|
||||||
{ method = "POST"
|
{ method = "POST"
|
||||||
, headers =
|
, headers =
|
||||||
|
@ -741,10 +1077,10 @@ makeRequest body index decodeResultItemSource options query from sizeRaw =
|
||||||
, body = body
|
, body = body
|
||||||
, expect =
|
, expect =
|
||||||
Http.expectJson
|
Http.expectJson
|
||||||
(RemoteData.fromResult >> QueryResponse)
|
(RemoteData.fromResult >> responseMsg)
|
||||||
(decodeResult decodeResultItemSource)
|
(decodeResult decodeResultItemSource)
|
||||||
, timeout = Nothing
|
, timeout = Nothing
|
||||||
, tracker = Nothing
|
, tracker = tracker
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -754,10 +1090,26 @@ makeRequest body index decodeResultItemSource options query from sizeRaw =
|
||||||
|
|
||||||
decodeResult :
|
decodeResult :
|
||||||
Json.Decode.Decoder a
|
Json.Decode.Decoder a
|
||||||
-> Json.Decode.Decoder (Result a)
|
-> Json.Decode.Decoder (SearchResult a)
|
||||||
decodeResult decodeResultItemSource =
|
decodeResult decodeResultItemSource =
|
||||||
Json.Decode.map Result
|
Json.Decode.map2 SearchResult
|
||||||
(Json.Decode.field "hits" (decodeResultHits decodeResultItemSource))
|
(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)
|
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 : Json.Decode.Decoder a -> Json.Decode.Decoder (ResultItem a)
|
||||||
decodeResultItem decodeResultItemSource =
|
decodeResultItem decodeResultItemSource =
|
||||||
Json.Decode.map5 ResultItem
|
Json.Decode.map6 ResultItem
|
||||||
(Json.Decode.field "_index" Json.Decode.string)
|
(Json.Decode.field "_index" Json.Decode.string)
|
||||||
(Json.Decode.field "_id" Json.Decode.string)
|
(Json.Decode.field "_id" Json.Decode.string)
|
||||||
(Json.Decode.field "_score" Json.Decode.float)
|
(Json.Decode.field "_score" Json.Decode.float)
|
||||||
(Json.Decode.field "_source" decodeResultItemSource)
|
(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)))
|
(Json.Decode.maybe (Json.Decode.field "matched_queries" (Json.Decode.list Json.Decode.string)))
|
||||||
|
|
|
@ -4,7 +4,6 @@ require("./index.scss");
|
||||||
|
|
||||||
const {Elm} = require('./Main');
|
const {Elm} = require('./Main');
|
||||||
|
|
||||||
console.log("WORKS: " + process.env.ELASTICSEARCH_MAPPING_SCHEMA_VERSION);
|
|
||||||
Elm.Main.init({
|
Elm.Main.init({
|
||||||
flags: {
|
flags: {
|
||||||
elasticsearchMappingSchemaVersion: parseInt(process.env.ELASTICSEARCH_MAPPING_SCHEMA_VERSION) || 0,
|
elasticsearchMappingSchemaVersion: parseInt(process.env.ELASTICSEARCH_MAPPING_SCHEMA_VERSION) || 0,
|
||||||
|
|
|
@ -30,18 +30,68 @@ header .navbar.navbar-static-top {
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-page {
|
.search-page {
|
||||||
.search-input {
|
.search-input.with-suggestions-loading {
|
||||||
text-align: center;
|
.input-append div.loader {
|
||||||
.input-append input {
|
display: block;
|
||||||
font-size: 24px;
|
|
||||||
height: 40px;
|
|
||||||
width: auto;
|
|
||||||
max-width: 15em;
|
|
||||||
}
|
}
|
||||||
.input-append button {
|
}
|
||||||
font-size: 24px;
|
.search-input.with-suggestions {
|
||||||
height: 50px;
|
ul.dropdown-menu {
|
||||||
min-width: 4em;
|
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 {
|
form > p > strong {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
@ -84,7 +134,7 @@ header .navbar.navbar-static-top {
|
||||||
.loader,
|
.loader,
|
||||||
.loader:before,
|
.loader:before,
|
||||||
.loader:after {
|
.loader:after {
|
||||||
background: #ffffff;
|
background: transparent;
|
||||||
-webkit-animation: load1 1s infinite ease-in-out;
|
-webkit-animation: load1 1s infinite ease-in-out;
|
||||||
animation: load1 1s infinite ease-in-out;
|
animation: load1 1s infinite ease-in-out;
|
||||||
width: 1em;
|
width: 1em;
|
||||||
|
|
Loading…
Reference in a new issue