Flake support/frontend (#324)

* Setup flake info extraction

Prepare data model fro derivations (#1)

Add flake info data (#1)

Implement fetching general flake info (#1)

Expose CLI (#1)

Keep cargo happy

Add some doc comments

Pin to local nixpkgs to excessive downloads

Extend visibility of some data objects

Add command to extract infomation about defivations (#1)

Add call new feature in main (#1)

Include more information in derivation (#1)

Add log access

Always debug log stderr from nix

Format nix script

Collect systems per package

Remove unnecessary imports

Create flake

Remove top level version field

Represent collected systems/version pairs in rust

Fix quotation marks in tests

Add correct cargo hash

Add iconv dependency

Return a list from nix script

Export as json

Undo version by platform distinction

Remove nixpkgs override

Apply cargo fmt

Flatten export structure

Allow for complex licenses

Prepare using a central nix file

Implement nix part o accessing apps

Include the correct filename

Add accessor for `all` key

Access all available information by default

Track more information about Apps

Run cargo fmt

Fix: allow local builds

Prepare next version of the flake info tool

Include examples and pull script

Expose flake info as library

Include thiserror for custom errors

Define a source data type

Collects source types and their metadata, collected in a json file

Add command line argument for input files

Mutually exclusive with --flake

Refactor functions to extract information given a flake identifier

Add kind specifier as CLI argument

Amend Argument parsing to require eiteher flake or targets to be defined

Run extraction for specified flake or list of flakes as specified in a json file

Resolves #5
References #7

Use internal tag to distnguich target types

Include target falg usage in examples

Set include provided source if available (resolves #9)

Resolve flake name

Update examples

Dont include empty license or description

Fix a misfomatting in cargot.toml

Add elastic dependencies

Implement a wrapper around the elasticsearch client

Implements pushing exports (#4)

Temporarily skip serializing an unimplemented field in elastic output

Extract reading source list files from binary

Add lazy_static as dependency

Implement createing and pushing to elastic index

Add elastic options

Provide default name and env falbac for elastic index

Modify app binary and type as optionals

App can be a derivation too

Update examples

Add more elastic commands

Supported:
- ensure
- clear
- push

Rename elastic search config struct

Add elastic push support to binary

Rename flag to enable elastic push

Imporve error messages and format binary source

Fix nix file incorrectly expecting meta fields

Changing flake descriotions to an optional field

deserialize git_ref as hash

Implement temporary stores and gc of these

prevents flakes from accessing store paths

Pass extra arguments to nix

Update cargo hash and skip integration tests

Move flake.nix to root folder and add apps for all components

Fix command invocation that fails test

Update README(s)

Add help for extra arguments

(cherry picked from commit be4bc3dd929178bef66114c2201aaa88e47e9add)

* Safely read legacyPackages

* Read nixosOptions from flake

* Update ES Mapping

* Show more detailed error and backtrace if available

* Try reading options only if key  is defined

* Format nix script

* Add error context when attempting deserialization

* Fix derivation representation to fit nix output

* Add push elasticsearch settings

* Add Flake channel

* Rename import module

* Remove Flakes Channel

* Prepare nixpkgs import

* Separate import/export types

* Break up nixpkgs package representation

* Use the same naming scheme for Nixpkgs entries and flake entries

* Document import module

* Remove serialization attributes

* Reversable type and SerDe implemetation

* Add *_reverse fields

* Unpublicating export fields

* Read from  NixOption struct

* serialize empty fields as null

* Tag export json variants

* Serialize a single option-declaration

* Format npkgs parse test

* Define NixOption

Sorry thats too late..

* Parse system key

* Make Package output compatible with the frontend

* Add Url-only licesnse variant

* Add StringOrStruct type

* Add accessor method for elements catched by OneOrMany

* New utility to flatten in homogenous lists recursively

* Add Maintainer type catching maintaiers used in nixpkgs

* Format Implementatio n

* Remove explicit representation of platforms

* Open nixpkgs parser to cover all packages

* Convert all imported fields to their export representation

* Define reverse fields in ES schema

* Format nixpkgs command runner

* Expose shorthand to pull a specific channel by command line

* Extract utility functions into their own module

* Implement AttributeQuery generation

* Integrate query in export

* Implement *_set attributes

* Document purpose of export module

* Use more descriptive github blob route

* Complete Option Export representation

Reuse the same option type for nixpkgs and flakes

* Enable nixpkgs option import

* Expose nixpkgs option import functions and integrate in binary

* Chunk ElasticSearch Bulk operations

* Address Example/Default field formatting

* Add abort strategy for existing indices

* New command line interface using subcommands

* Document new interface

* Bump version

* Add nixpkgs cron job

* More precise name for nixpkgs cron job

* Add Flakes cron job

* Read version from file and fix channel names

* Correct file names for flake group import

* Fix group command

* Run new cron jobs on PR

* Update Cargo sources

* Integrate new flakes route

* Add search type option

* Add flake types and messages

* Add flake types

* Extract Request body builders and expose more types

* Combine Package and Option Search

* Factor out html body

* Dispatch messages by search type to flake page

* Correct type naming

* Remove Debug instructions and unimplemented flake search type

* Do not reload Flake search page while the search subject is unchanged

* Implement switching subjects

* Fix init type signature

* Add url/git based flakes to mapping

* Parse flake info

* Link to flake repo and show flake maintainer

* Fix optional decoded values

* Add group and type as search buckts

* Show search selection in all cases

* Move flake decoding to search

* Show flake information for options

* Hardcode flakesearch to only search a specified flake index

* Improve experimental state notice

* Fix category select not present in some cases

* Change default flake index

* Show correct category title

* Add missing imports

* Serialize more optional fields as null

* elastic-test.rs file not needed anymore

* names of the workflows should be immediately obvious.

* better jobs names

* rename the flakes group

* need to provide --elastic-schema-version via the cli option

* typo

* Fix errors in workflow files

* Install flake enabled nix by default

* Fix variable substitution

* Use string group names

* Provide elastic schema verion only through cli

* Fix scheduled task name

* Improve error reporting

* Allow insecure packages here

* Add missing imports

* Tree-wide: cargo-fmt

* only import nixpkgs for now

* no importing of flakes

* also bump a version

* fixing cron-flakes.yml

* make it obvious that this is for frontend

* missed this when merging

* Extract hashes

- Split out revision info from flakes
- Retrieve current nixpkgs revision from github

* Write aliases, enabled by default

* Filter additional platforms

* Change alias structure

* expect channel like nixpkgs identifiers

* Don't cause error if push is aborted

* Specify correct channel identifier

* Allow options to evaluate unfree packages

* Retrieve and delete specific aliases

* Specify import path correclty

* Fix channel warmup

* Abort push if channel already indexed

* Remove debug pr hook for import action

* Fix indentation by tab

* Make flakes show again

* Fix import group naming in flake wrokflow

* Rename flake group to match imported index

* Run nixpkgs import on pr activity (Debugging behavior)

* Just show literal Examples, resolves #336

* Use actual nixpkgs branch names

* Trim derivation/option declaration path, resolves #335

Remove /nix/store/*-source prefix

* Fix sidebar width and close button position

* Placeholder texts in flake result area

* Show flake install info

* Don't show package/option selection before search

* Make sure install command for nixos is always shown

* Add toml source config support

* Rewrite current example flake group as toml

* Update flake cron job

* flake-info: use saner nix packaging method

When trying to first work in this I naive approached it with
`nix-shell`. That of course lead to the fixed output bollocks failing
with a hash mismatch. By making use of the `cargoLock` attribute on
`buildRustPackage` we can tame the FOD-beast and only have to provide
one hash manually for a single package (that we fetch from a GitHub
repository).

This also means that updating dependencies will be simpler as the native
Cargo.lock file can be used.

(cherry picked from commit c3a0e46d1eb56e128e6923e6c493eb836fc81e85)

* Update flake lock file

* Do not build python import script

* add flake names to the title as well

* Disable debug imports on prs

Co-authored-by: Rok Garbas <rok@garbas.si>
Co-authored-by: Andreas Rammhold <andreas@rammhold.de>
This commit is contained in:
Yannik Sander 2021-08-26 00:40:42 +02:00 committed by GitHub
parent 5ad71362e5
commit af3b494217
Failed to generate hash of commit
25 changed files with 1334 additions and 562 deletions

69
.github/workflows/cron-flakes.yml vendored Normal file
View file

@ -0,0 +1,69 @@
name: "Flakes: Hourly import to Elasticsearch"
on:
schedule:
- cron: '0 * * * *'
jobs:
hourly-import-channel:
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
group:
- "manual"
env:
RUST_LOG: debug
FI_ES_EXISTS_STRATEGY: recreate
FI_ES_URL: ${{ secrets.ELASTICSEARCH_URL }}
steps:
- name: Checking out the repository
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Installing Nix
uses: cachix/install-nix-action@v13
with:
install_url: https://nixos-nix-install-tests.cachix.org/serve/i6laym9jw3wg9mw6ncyrk6gjx4l34vvx/install
install_options: '--tarball-url-prefix https://nixos-nix-install-tests.cachix.org/serve'
extra_nix_config: |
experimental-features = nix-command flakes
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
- uses: cachix/cachix-action@v10
with:
name: nixos-search
signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
- name: Install unstable channel
run: |
nix-channel --add https://nixos.org/channels/nixpkgs-unstable
nix-channel --update
- name: Installing jq
run: |
nix-env -iA nixpkgs.nixFlakes nixpkgs.jq
- name: Building import_scripts
run: |
nix build ./#packages.x86_64-linux.flake_info
- name: Import ${{ matrix.group }} group
run: |
./result/bin/flake-info --push --elastic-schema-version=$(cat ./VERSION) group ./flakes/${{ matrix.group }}.toml ${{ matrix.group }}
if: github.repository == 'NixOS/nixos-search'
- name: Warmup ${{ matrix.group }} channel
run: |
curl ${{ secrets.ELASTICSEARCH_URL }}/$(cat VERSION)-${{ matrix.group }}/_search | jq '.took'
curl ${{ secrets.ELASTICSEARCH_URL }}/$(cat VERSION)-${{ matrix.group }}/_search | jq '.took'
curl ${{ secrets.ELASTICSEARCH_URL }}/$(cat VERSION)-${{ matrix.group }}/_search | jq '.took'
if: github.repository == 'NixOS/nixos-search'

View file

@ -4,6 +4,7 @@ on:
schedule: schedule:
- cron: '0 * * * *' - cron: '0 * * * *'
pull_request:
jobs: jobs:

View file

@ -1,4 +1,4 @@
name: "Build & Deploy to Netlify" name: "Frontend: Build & Deploy to Netlify"
on: on:
pull_request: pull_request:
push: push:
@ -34,10 +34,6 @@ jobs:
cat /etc/nix/nix.conf cat /etc/nix/nix.conf
echo "$HOME/.nix-profile/bin" >> $GITHUB_PATH echo "$HOME/.nix-profile/bin" >> $GITHUB_PATH
- name: Building import_scripts
run: |
nix build ./#packages.x86_64-linux.import_scripts
- name: Building search.nixos.org - name: Building search.nixos.org
run: | run: |
nix build ./#packages.x86_64-linux.frontend nix build ./#packages.x86_64-linux.frontend

View file

@ -1 +1 @@
21 22

10
flake-info/Cargo.lock generated
View file

@ -308,6 +308,7 @@ dependencies = [
"tempfile", "tempfile",
"thiserror", "thiserror",
"tokio", "tokio",
"toml",
] ]
[[package]] [[package]]
@ -1455,6 +1456,15 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "toml"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "tower-service" name = "tower-service"
version = "0.3.1" version = "0.3.1"

View file

@ -10,6 +10,7 @@ edition = "2018"
clap = "^2.33" clap = "^2.33"
serde = {version="1.0", features = ["derive"]} serde = {version="1.0", features = ["derive"]}
serde_json = "1.0" serde_json = "1.0"
toml = "0.5"
anyhow = "1.0" anyhow = "1.0"
thiserror = "1.0" thiserror = "1.0"
structopt = "0.3" structopt = "0.3"

View file

@ -1,12 +1,13 @@
{ pkgs ? import <nixpkgs> { } }: with pkgs; { pkgs ? import <nixpkgs> { } }: with pkgs;
rustPlatform.buildRustPackage {
rustPlatform.buildRustPackage rec {
name = "flake-info"; name = "flake-info";
src = ./.; src = ./.;
cargoSha256 = "sha256-TA1WEvmOfnxQ+rRwkIPN1t4VPrDL6pq+WnPHVu2/CPE="; cargoLock = {
lockFile = ./Cargo.lock;
outputHashes = {
"elasticsearch-8.0.0-alpha.1" = "0x8iw4m16vy6i28mj30aqdwfw4a3hd174l8l9yigddn3cr53cagx";
};
};
nativeBuildInputs = [ pkg-config ]; nativeBuildInputs = [ pkg-config ];
buildInputs = [ openssl openssl.dev ] ++ lib.optional pkgs.stdenv.isDarwin [ libiconv darwin.apple_sdk.frameworks.Security ]; buildInputs = [ openssl openssl.dev ] ++ lib.optional pkgs.stdenv.isDarwin [ libiconv darwin.apple_sdk.frameworks.Security ];
checkFlags = [ checkFlags = [

View file

@ -57,7 +57,7 @@ enum Command {
channel: String, channel: String,
}, },
Group { Group {
#[structopt(help = "Points to a JSON file containing info targets")] #[structopt(help = "Points to a TOML or JSON file containing info targets. If file does not end in 'toml' json is assumed")]
targets: PathBuf, targets: PathBuf,
name: String, name: String,
@ -217,7 +217,7 @@ async fn run_command(
.await .await
.map_err(FlakeInfoError::Nixpkgs)?; .map_err(FlakeInfoError::Nixpkgs)?;
let ident = ( let ident = (
"nixpkgs".to_owned(), "nixos".to_owned(),
nixpkgs.channel.clone(), nixpkgs.channel.clone(),
nixpkgs.git_ref.clone(), nixpkgs.git_ref.clone(),
); );
@ -335,7 +335,6 @@ async fn push_to_elastic(
ensure?; ensure?;
} }
es.push_exports(&config, successes) es.push_exports(&config, successes)
.await .await
.with_context(|| "Failed to push results to elasticsearch".to_string())?; .with_context(|| "Failed to push results to elasticsearch".to_string())?;

View file

@ -73,14 +73,17 @@ let
cleanUpOption = module: opt: cleanUpOption = module: opt:
let let
applyOnAttr = n: f: lib.optionalAttrs (lib.hasAttr n opt) { ${n} = f opt.${n}; }; applyOnAttr = n: f: lib.optionalAttrs (lib.hasAttr n opt) { ${n} = f opt.${n}; };
# mkDeclaration = decl: rec { mkDeclaration = decl:
# path = stripModulePathPrefixes decl; let
# url = mkModuleUrl path; discard = lib.concatStringsSep "/" (lib.take 4 (lib.splitString "/" decl));
# channelPath = "${channelName}/${path}"; path = if lib.hasPrefix builtins.storeDir decl then lib.removePrefix discard decl else decl;
# }; in
path;
# Replace functions by the string <function> # Replace functions by the string <function>
substFunction = x: substFunction = x:
if builtins.isAttrs x then if x ? _type && x._type == "literalExample" then x.text
else if builtins.isAttrs x then
lib.mapAttrs (name: substFunction) x lib.mapAttrs (name: substFunction) x
else if builtins.isList x then else if builtins.isList x then
map substFunction x map substFunction x
@ -93,8 +96,8 @@ let
// applyOnAttr "example" substFunction // applyOnAttr "example" substFunction
// applyOnAttr "default" substFunction // applyOnAttr "default" substFunction
// applyOnAttr "type" substFunction // applyOnAttr "type" substFunction
// applyOnAttr "declarations" (map mkDeclaration)
// lib.optionalAttrs (!isNixOS) { flake = [ flake module ]; }; // lib.optionalAttrs (!isNixOS) { flake = [ flake module ]; };
# // applyOnAttr "declarations" (map mkDeclaration)
options = lib.mapAttrs ( options = lib.mapAttrs (

View file

@ -217,6 +217,14 @@ impl From<import::NixpkgsEntry> for Derivation {
.map(|m| m.name.to_owned().unwrap()) .map(|m| m.name.to_owned().unwrap())
.collect(); .collect();
let position: Option<String> = package.meta.position.map(|p| {
if p.starts_with("/nix/store") {
p.split("/").skip(4).collect::<Vec<&str>>().join("/")
} else {
p
}
});
Derivation::Package { Derivation::Package {
package_attr_name: attribute.clone(), package_attr_name: attribute.clone(),
package_attr_name_reverse: Reverse(attribute.clone()), package_attr_name_reverse: Reverse(attribute.clone()),
@ -247,7 +255,7 @@ impl From<import::NixpkgsEntry> for Derivation {
.meta .meta
.homepage .homepage
.map_or(Default::default(), OneOrMany::into_list), .map_or(Default::default(), OneOrMany::into_list),
package_position: package.meta.position, package_position: position,
} }
} }
import::NixpkgsEntry::Option(option) => option.into(), import::NixpkgsEntry::Option(option) => option.into(),

View file

@ -7,10 +7,7 @@ use super::Source;
/// Holds general infoamtion about a flake /// Holds general infoamtion about a flake
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Flake { pub struct Flake {
#[serde( #[serde(rename(serialize = "flake_description"))]
rename(serialize = "flake_description"),
skip_serializing_if = "Option::is_none"
)]
pub description: Option<String>, pub description: Option<String>,
#[serde(rename(serialize = "flake_path"), skip_serializing)] #[serde(rename(serialize = "flake_path"), skip_serializing)]
pub path: PathBuf, pub path: PathBuf,

View file

@ -2,7 +2,9 @@ use anyhow::{Context, Result};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
fs::{self, File}, fs::{self, File},
io::Read,
path::Path, path::Path,
ffi::OsStr,
}; };
pub type Hash = String; pub type Hash = String;
@ -31,6 +33,12 @@ pub enum Source {
Nixpkgs(Nixpkgs), Nixpkgs(Nixpkgs),
} }
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct TomlDocument {
sources: Vec<Source>
}
impl Source { impl Source {
pub fn to_flake_ref(&self) -> FlakeRef { pub fn to_flake_ref(&self) -> FlakeRef {
match self { match self {
@ -68,9 +76,19 @@ impl Source {
} }
pub fn read_sources_file(path: &Path) -> Result<Vec<Source>> { pub fn read_sources_file(path: &Path) -> Result<Vec<Source>> {
let file = File::open(path).with_context(|| "Failed to open input file")?;
Ok(serde_json::from_reader(file)?) let mut file = File::open(path).with_context(|| "Failed to open input file")?;
let mut buf = String::new();
file.read_to_string(&mut buf)?;
if path.extension() == Some(OsStr::new("toml")) {
let document: TomlDocument = toml::from_str(&buf)?;
Ok(document.sources)
}
else {
Ok(serde_json::from_str(&buf)?)
}
} }
pub async fn nixpkgs(channel: String) -> Result<Nixpkgs> { pub async fn nixpkgs(channel: String) -> Result<Nixpkgs> {

View file

@ -34,6 +34,9 @@ lazy_static! {
"repo": { "repo": {
"type": "keyword" "type": "keyword"
}, },
"url" : {
"type": "keyword"
}
} }
}, },
"flake_source": { "flake_source": {

View file

@ -17,11 +17,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1616779317, "lastModified": 1629618782,
"narHash": "sha256-+mUTkYguFMNGb57JkwauDgjcq65RnOLUhDo4mhb8qAI=", "narHash": "sha256-2K8SSXu3alo/URI3MClGdDSns6Gb4ZaW4LET53UWyKk=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "ad47284f8b01f587e24a4f14e0f93126d8ebecda", "rev": "870959c7fb3a42af1863bed9e1756086a74eb649",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -51,11 +51,11 @@
"nixpkgs": "nixpkgs_2" "nixpkgs": "nixpkgs_2"
}, },
"locked": { "locked": {
"lastModified": 1617110670, "lastModified": 1629413283,
"narHash": "sha256-+GoJjy1Hlpvs2dFjxJsWHur4Jb8gT0Lig3y2MwRn3cI=", "narHash": "sha256-DeyaGvtO/Nureis423Vu4s/X7r0I/h6/ELJLr0sHy2w=",
"owner": "nix-community", "owner": "nix-community",
"repo": "poetry2nix", "repo": "poetry2nix",
"rev": "19bfde46bb9ba980142f2ca99268ef14df8cad5c", "rev": "f0ef3fee8053eb32207761d6d708e217e2aa3d02",
"type": "github" "type": "github"
}, },
"original": { "original": {

29
flakes/manual.toml Normal file
View file

@ -0,0 +1,29 @@
[[sources]]
type = "github"
owner = "ngi-nix"
repo = "offen"
[[sources]]
type = "github"
owner = "ngi-nix"
repo = "pixelfed"
[[sources]]
type = "github"
owner = "ngi-nix"
repo = "lightmeter"
[[sources]]
type = "github"
owner = "ngi-nix"
repo = "openpgp-ca"
[[sources]]
type = "gitlab"
owner = "pi-lar"
repo = "neuropil"
[[sources]]
type = "github"
owner = "tweag"
repo = "nickel"

View file

@ -26,13 +26,17 @@ import Html.Attributes
, src , src
, type_ , type_
) )
import Page.Flakes exposing (Model(..))
import Page.Home import Page.Home
import Page.Options import Page.Options
import Page.Packages import Page.Packages
import Page.Flakes import Route exposing (SearchType(..))
import Route
import Search import Search
import Url import Url
import Search exposing (defaultFlakeId)
import Search exposing (channels)
import Html exposing (sup)
import Html exposing (small)
@ -127,6 +131,7 @@ attemptQuery (( model, _ ) as pair) =
, Cmd.map msg <| , Cmd.map msg <|
makeRequest makeRequest
model.elasticsearch model.elasticsearch
searchModel.searchType
searchModel.channel searchModel.channel
(Maybe.withDefault "" searchModel.query) (Maybe.withDefault "" searchModel.query)
searchModel.from searchModel.from
@ -140,19 +145,40 @@ attemptQuery (( model, _ ) as pair) =
case model.page of case model.page of
Packages searchModel -> Packages searchModel ->
if Search.shouldLoad searchModel then if Search.shouldLoad searchModel then
submitQuery PackagesMsg Page.Packages.makeRequest searchModel submitQuery PackagesMsg Page.Packages.makeRequest { searchModel | searchType = PackageSearch }
else else
noEffects pair noEffects pair
Options searchModel -> Options searchModel ->
if Search.shouldLoad searchModel then if Search.shouldLoad searchModel then
submitQuery OptionsMsg Page.Options.makeRequest searchModel submitQuery OptionsMsg Page.Options.makeRequest { searchModel | searchType = OptionSearch }
else else
noEffects pair noEffects pair
Flakes (OptionModel searchModel) ->
if Search.shouldLoad searchModel then
submitQuery FlakesMsg Page.Flakes.makeRequest {searchModel | channel = defaultFlakeId }
else
noEffects pair
Flakes (PackagesModel searchModel) ->
if Search.shouldLoad searchModel then
-- let
-- _ = Debug.log "main" "submit flake message"
-- in
submitQuery FlakesMsg Page.Flakes.makeRequest {searchModel | channel = defaultFlakeId}
else
-- let _ = Debug.log "main" "should not load flakes" in
noEffects pair
_ -> _ ->
-- let
-- _ = Debug.log "pair" <| Debug.toString pair
-- in
pair pair
@ -171,6 +197,12 @@ pageMatch m1 m2 =
( Options _, Options _ ) -> ( Options _, Options _ ) ->
True True
( Flakes (OptionModel _), Flakes (OptionModel _) ) ->
True
( Flakes (PackagesModel _), Flakes (PackagesModel _) ) ->
True
_ -> _ ->
False False
@ -239,6 +271,7 @@ changeRouteTo currentModel url =
Route.Flakes searchArgs -> Route.Flakes searchArgs ->
let let
-- _ = Debug.log "changeRouteTo" "flakes"
modelPage = modelPage =
case model.page of case model.page of
Flakes x -> Flakes x ->
@ -255,6 +288,8 @@ changeRouteTo currentModel url =
update : Msg -> Model -> ( Model, Cmd Msg ) update : Msg -> Model -> ( Model, Cmd Msg )
update msg model = update msg model =
-- let _ = Debug.log "main" "update"
-- in
case ( msg, model.page ) of case ( msg, model.page ) of
( ClickedLink urlRequest, _ ) -> ( ClickedLink urlRequest, _ ) ->
case urlRequest of case urlRequest of
@ -289,6 +324,10 @@ update msg model =
Page.Options.update model.navKey subMsg subModel Page.Options.update model.navKey subMsg subModel
|> updateWith Options OptionsMsg model |> updateWith Options OptionsMsg model
( FlakesMsg subMsg, Flakes subModel ) ->
Page.Flakes.update model.navKey subMsg subModel
|> updateWith Flakes FlakesMsg model
( _, _ ) -> ( _, _ ) ->
-- Disregard messages that arrived for the wrong page. -- Disregard messages that arrived for the wrong page.
( model, Cmd.none ) ( model, Cmd.none )
@ -375,26 +414,30 @@ viewNavigation route =
Route.Options searchArgs -> Route.Options searchArgs ->
f searchArgs f searchArgs
Route.Flakes searchArgs ->
f searchArgs
_ -> _ ->
f <| Route.SearchArgs Nothing Nothing Nothing Nothing Nothing Nothing Nothing f <| Route.SearchArgs Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing
in in
li [] [ a [ href "https://nixos.org" ] [ text "Back to nixos.org" ] ] li [] [ a [ href "https://nixos.org" ] [ text "Back to nixos.org" ] ]
:: List.map :: List.map
(viewNavigationItem route) (viewNavigationItem route)
[ ( toRoute Route.Packages, "Packages" ) [ ( toRoute Route.Packages, text "Packages" )
, ( toRoute Route.Options, "Options" ) , ( toRoute Route.Options, text "Options" )
--, ( toRoute Route.Flakes, "Flakes (Experimental)" ) , ( toRoute Route.Flakes, span [] [ text "Flakes", sup [] [span [class "label label-info"][small [] [text "Experimental"]]]] )
] ]
viewNavigationItem : viewNavigationItem :
Route.Route Route.Route
-> ( Route.Route, String ) -> ( Route.Route, Html Msg )
-> Html Msg -> Html Msg
viewNavigationItem currentRoute ( route, title ) = viewNavigationItem currentRoute ( route, title ) =
li li
[ classList [ ( "active", currentRoute == route ) ] ] [ classList [ ( "active", currentRoute == route ) ] ]
[ a [ Route.href route ] [ text title ] ] [ a [ Route.href route ] [ title ] ]
viewPage : Model -> Html Msg viewPage : Model -> Html Msg
@ -415,6 +458,8 @@ viewPage model =
Flakes flakesModel -> Flakes flakesModel ->
Html.map (\m -> FlakesMsg m) <| Page.Flakes.view flakesModel Html.map (\m -> FlakesMsg m) <| Page.Flakes.view flakesModel
-- SUBSCRIPTIONS -- SUBSCRIPTIONS

View file

@ -1,52 +1,65 @@
module Page.Flakes exposing (Model, Msg, init, update, view) module Page.Flakes exposing (Model(..), Msg(..), init, makeRequest, update, view)
import Browser.Navigation import Browser.Navigation
import Html exposing (Html, a, code, div, li, pre, strong, text, ul) import Html exposing (Html, a, code, div, li, nav, pre, strong, text, ul)
import Html.Attributes exposing (class, classList, href, target) import Html.Attributes exposing (class, classList, href, target)
import Html.Events exposing (onClick) import Html.Events exposing (onClick)
import Html.Parser import Html.Parser
import Html.Parser.Util import Html.Parser.Util
import Json.Decode import Http exposing (Body)
import Route import Json.Decode exposing (Decoder)
import Page.Options exposing (Msg(..))
import Page.Packages exposing (Msg(..))
import Route exposing (Route(..), SearchArgs, SearchType(..))
import Search import Search
import View.Components
-- MODEL -- MODEL
type alias Model = type Model
Search.Model ResultItemSource ResultAggregations = OptionModel Page.Options.Model
| PackagesModel Page.Packages.Model
type alias ResultItemSource =
{ name : String
, description : Maybe String
, type_ : Maybe String
, default : Maybe String
, example : Maybe String
, source : Maybe String
}
type alias ResultAggregations =
{ all : AggregationsAll
}
type alias AggregationsAll =
{ doc_count : Int
}
init : Route.SearchArgs -> Maybe Model -> ( Model, Cmd Msg ) init : Route.SearchArgs -> Maybe Model -> ( Model, Cmd Msg )
init searchArgs model = init searchArgs model =
let let
-- _ =
-- Debug.log "Flakes" "init"
-- init with respective module or with packages by default
searchType =
Maybe.withDefault PackageSearch searchArgs.type_
mapEitherModel m =
case ( searchType, m ) of
( OptionSearch, OptionModel model_ ) ->
Tuple.mapBoth OptionModel (Cmd.map OptionsMsg) <| Page.Options.init searchArgs <| Just model_
( PackageSearch, PackagesModel model_ ) ->
Tuple.mapBoth PackagesModel (Cmd.map PackagesMsg) <| Page.Packages.init searchArgs <| Just model_
_ ->
default
default =
case searchType of
PackageSearch ->
Tuple.mapBoth PackagesModel (Cmd.map PackagesMsg) <| Page.Packages.init searchArgs Nothing
OptionSearch ->
Tuple.mapBoth OptionModel (Cmd.map OptionsMsg) <| Page.Options.init searchArgs Nothing
( newModel, newCmd ) = ( newModel, newCmd ) =
Search.init searchArgs model Maybe.withDefault default <| Maybe.map mapEitherModel model
-- _ =
-- Debug.log "mapped Model" <| Maybe.map mapEitherModel model
in in
( newModel ( newModel
, Cmd.map SearchMsg newCmd , newCmd
) )
@ -55,7 +68,8 @@ init searchArgs model =
type Msg type Msg
= SearchMsg (Search.Msg ResultItemSource ResultAggregations) = OptionsMsg Page.Options.Msg
| PackagesMsg Page.Packages.Msg
update : update :
@ -64,17 +78,43 @@ update :
-> Model -> Model
-> ( Model, Cmd Msg ) -> ( Model, Cmd Msg )
update navKey msg model = update navKey msg model =
case msg of -- let
SearchMsg subMsg -> -- _ =
-- Debug.log "Flake update" ( msg, model )
-- in
case ( msg, model ) of
( OptionsMsg msg_, OptionModel model_ ) ->
case msg_ of
Page.Options.SearchMsg subMsg ->
let let
-- _ =
-- Debug.log "update - options"
( newModel, newCmd ) = ( newModel, newCmd ) =
Search.update Search.update
Route.Options Route.Flakes
navKey navKey
subMsg subMsg
model model_
in in
( newModel, Cmd.map SearchMsg newCmd ) ( newModel, Cmd.map Page.Options.SearchMsg newCmd ) |> Tuple.mapBoth OptionModel (Cmd.map OptionsMsg)
( PackagesMsg msg_, PackagesModel model_ ) ->
case msg_ of
Page.Packages.SearchMsg subMsg ->
let
-- _ =
-- Debug.log "Flakes" "update - packages"
( newModel, newCmd ) =
Search.update
Route.Flakes
navKey
subMsg
model_
in
( newModel, Cmd.map Page.Packages.SearchMsg newCmd ) |> Tuple.mapBoth PackagesModel (Cmd.map PackagesMsg)
_ ->
( model, Cmd.none )
@ -83,151 +123,26 @@ update navKey msg model =
view : Model -> Html Msg view : Model -> Html Msg
view model = view model =
Search.view { toRoute = Route.Options, categoryName = "options" }
[ text "Search more than "
, strong [] [ text "10 000 options" ]
]
model
viewSuccess
viewBuckets
SearchMsg
viewBuckets :
Maybe String
-> Search.SearchResult ResultItemSource ResultAggregations
-> List (Html Msg)
viewBuckets _ _ =
[]
viewSuccess :
String
-> Bool
-> Maybe String
-> List (Search.ResultItem ResultItemSource)
-> Html Msg
viewSuccess channel showNixOSDetails show hits =
ul []
(List.map
(viewResultItem channel showNixOSDetails show)
hits
)
viewResultItem :
String
-> Bool
-> Maybe String
-> Search.ResultItem ResultItemSource
-> Html Msg
viewResultItem channel _ show item =
let let
showHtml value = mkBody categoryName =
case Html.Parser.run value of View.Components.body { toRoute = Route.Flakes, categoryName = categoryName }
Ok nodes -> [ text "Search packages and options of "
Html.Parser.Util.toVirtualDom nodes , strong []
[ a
Err _ -> [ href "https://github.com/NixOS/nixos-search/blob/main/flakes/manual.toml" ]
[] [ text "public flakes" ]
default =
"Not given"
asPre value =
pre [] [ text value ]
asPreCode value =
div [] [ pre [] [ code [ class "code-block" ] [ text value ] ] ]
githubUrlPrefix branch =
"https://github.com/NixOS/nixpkgs/blob/" ++ branch ++ "/"
cleanPosition value =
if String.startsWith "source/" value then
String.dropLeft 7 value
else
value
asGithubLink value =
case Search.channelDetailsFromId channel of
Just channelDetails ->
a
[ href <| githubUrlPrefix channelDetails.branch ++ (value |> String.replace ":" "#L")
, target "_blank"
] ]
[ text value ]
Nothing ->
text <| cleanPosition value
withEmpty wrapWith maybe =
case maybe of
Nothing ->
asPre default
Just "" ->
asPre default
Just value ->
wrapWith value
wrapped wrapWith value =
case value of
"" ->
wrapWith <| "\"" ++ value ++ "\""
_ ->
wrapWith value
showDetails =
if Just item.source.name == show then
div [ Html.Attributes.map SearchMsg Search.trapClick ]
[ div [] [ text "Name" ]
, div [] [ wrapped asPreCode item.source.name ]
, div [] [ text "Description" ]
, div [] <|
(item.source.description
|> Maybe.map showHtml
|> Maybe.withDefault []
)
, div [] [ text "Default value" ]
, div [] [ withEmpty (wrapped asPreCode) item.source.default ]
, div [] [ text "Type" ]
, div [] [ withEmpty asPre item.source.type_ ]
, div [] [ text "Example" ]
, div [] [ withEmpty (wrapped asPreCode) item.source.example ]
, div [] [ text "Declared in" ]
, div [] [ withEmpty asGithubLink item.source.source ]
] ]
|> Just
else body =
Nothing case model of
OptionModel model_ ->
Html.map OptionsMsg <| mkBody "Options" model_ Page.Options.viewSuccess Page.Options.viewBuckets Page.Options.SearchMsg
toggle = PackagesModel model_ ->
SearchMsg (Search.ShowDetails item.source.name) Html.map PackagesMsg <| mkBody "Packages" model_ Page.Packages.viewSuccess Page.Packages.viewBuckets Page.Packages.SearchMsg
isOpen =
Just item.source.name == show
in in
li body
[ class "option"
, classList [ ( "opened", isOpen ) ]
, Search.elementId item.source.name
]
<|
List.filterMap identity
[ Just <|
Html.a
[ class "search-result-button"
, onClick toggle
, href ""
]
[ text item.source.name ]
, showDetails
]
@ -236,6 +151,7 @@ viewResultItem channel _ show item =
makeRequest : makeRequest :
Search.Options Search.Options
-> SearchType
-> String -> String
-> String -> String
-> Int -> Int
@ -243,55 +159,50 @@ makeRequest :
-> Maybe String -> Maybe String
-> Search.Sort -> Search.Sort
-> Cmd Msg -> Cmd Msg
makeRequest options channel query from size _ sort = makeRequest options searchType index_id query from size maybeBuckets sort =
let
cmd =
case searchType of
PackageSearch ->
Search.makeRequest Search.makeRequest
(Search.makeRequestBody (makeRequestBody searchType query from size maybeBuckets sort)
(String.trim query) index_id
from Page.Packages.decodeResultItemSource
size Page.Packages.decodeResultAggregations
sort options
"option" Search.QueryResponse
"option_name" (Just "query-packages")
[] |> Cmd.map Page.Packages.SearchMsg
[] |> Cmd.map PackagesMsg
[]
"option_name" OptionSearch ->
[ ( "option_name", 6.0 ) Search.makeRequest
, ( "option_name_query", 3.0 ) (makeRequestBody searchType query from size maybeBuckets sort)
, ( "option_description", 1.0 ) index_id
] Page.Options.decodeResultItemSource
) Page.Options.decodeResultAggregations
channel
decodeResultItemSource
decodeResultAggregations
options options
Search.QueryResponse Search.QueryResponse
(Just "query-options") (Just "query-options")
|> Cmd.map SearchMsg |> Cmd.map Page.Options.SearchMsg
|> Cmd.map OptionsMsg
-- FlakeSearch ->
-- Debug.todo "branch 'FlakeSearch' not implemented"
in
cmd
makeRequestBody : SearchType -> String -> Int -> Int -> Maybe String -> Search.Sort -> Body
makeRequestBody searchType query from size maybeBuckets sort =
case searchType of
OptionSearch ->
Page.Options.makeRequestBody query from size sort
PackageSearch ->
Page.Packages.makeRequestBody query from size maybeBuckets sort
-- JSON -- FlakeSearch ->
-- Debug.todo "branch 'FlakeSearch' not implemented"
decodeResultItemSource : Json.Decode.Decoder ResultItemSource
decodeResultItemSource =
Json.Decode.map6 ResultItemSource
(Json.Decode.field "option_name" Json.Decode.string)
(Json.Decode.field "option_description" (Json.Decode.nullable Json.Decode.string))
(Json.Decode.field "option_type" (Json.Decode.nullable Json.Decode.string))
(Json.Decode.field "option_default" (Json.Decode.nullable Json.Decode.string))
(Json.Decode.field "option_example" (Json.Decode.nullable Json.Decode.string))
(Json.Decode.field "option_source" (Json.Decode.nullable Json.Decode.string))
decodeResultAggregations : Json.Decode.Decoder ResultAggregations
decodeResultAggregations =
Json.Decode.map ResultAggregations
(Json.Decode.field "all" decodeResultAggregationsAll)
decodeResultAggregationsAll : Json.Decode.Decoder AggregationsAll
decodeResultAggregationsAll =
Json.Decode.map AggregationsAll
(Json.Decode.field "doc_count" Json.Decode.int)

View file

@ -1,11 +1,17 @@
module Page.Options exposing module Page.Options exposing
( Model ( Model
, Msg , Msg(..)
, ResultAggregations
, ResultItemSource
, decodeResultAggregations
, decodeResultItemSource , decodeResultItemSource
, init , init
, makeRequest , makeRequest
, makeRequestBody
, update , update
, view , view
, viewBuckets
, viewSuccess
) )
import Browser.Navigation import Browser.Navigation
@ -17,6 +23,8 @@ import Html
, div , div
, li , li
, pre , pre
, source
, span
, strong , strong
, text , text
, ul , ul
@ -34,9 +42,13 @@ import Html.Events
) )
import Html.Parser import Html.Parser
import Html.Parser.Util import Html.Parser.Util
import Http exposing (Body)
import Json.Decode import Json.Decode
import Route import Json.Decode.Pipeline
import Search import List exposing (sort)
import Route exposing (SearchType)
import Search exposing (Details, decodeResolvedFlake)
import Url.Parser exposing (query)
@ -54,6 +66,12 @@ type alias ResultItemSource =
, default : Maybe String , default : Maybe String
, example : Maybe String , example : Maybe String
, source : Maybe String , source : Maybe String
-- flake
, flake : Maybe ( String, String )
, flakeName : Maybe String
, flakeDescription : Maybe String
, flakeUrl : Maybe String
} }
@ -119,6 +137,7 @@ view model =
viewSuccess viewSuccess
viewBuckets viewBuckets
SearchMsg SearchMsg
[]
viewBuckets : viewBuckets :
@ -131,21 +150,21 @@ viewBuckets _ _ =
viewSuccess : viewSuccess :
String String
-> Bool -> Details
-> Maybe String -> Maybe String
-> List (Search.ResultItem ResultItemSource) -> List (Search.ResultItem ResultItemSource)
-> Html Msg -> Html Msg
viewSuccess channel showNixOSDetails show hits = viewSuccess channel showInstallDetails show hits =
ul [] ul []
(List.map (List.map
(viewResultItem channel showNixOSDetails show) (viewResultItem channel showInstallDetails show)
hits hits
) )
viewResultItem : viewResultItem :
String String
-> Bool -> Details
-> Maybe String -> Maybe String
-> Search.ResultItem ResultItemSource -> Search.ResultItem ResultItemSource
-> Html Msg -> Html Msg
@ -168,28 +187,6 @@ viewResultItem channel _ show item =
asPreCode value = asPreCode value =
div [] [ pre [] [ code [ class "code-block" ] [ text value ] ] ] div [] [ pre [] [ code [ class "code-block" ] [ text value ] ] ]
githubUrlPrefix branch =
"https://github.com/NixOS/nixpkgs/blob/" ++ branch ++ "/"
cleanPosition value =
if String.startsWith "source/" value then
String.dropLeft 7 value
else
value
asGithubLink value =
case Search.channelDetailsFromId channel of
Just channelDetails ->
a
[ href <| githubUrlPrefix channelDetails.branch ++ (value |> String.replace ":" "#L")
, target "_blank"
]
[ text value ]
Nothing ->
text <| cleanPosition value
withEmpty wrapWith maybe = withEmpty wrapWith maybe =
case maybe of case maybe of
Nothing -> Nothing ->
@ -227,7 +224,7 @@ viewResultItem channel _ show item =
, div [] [ text "Example" ] , div [] [ text "Example" ]
, div [] [ withEmpty (wrapped asPreCode) item.source.example ] , div [] [ withEmpty (wrapped asPreCode) item.source.example ]
, div [] [ text "Declared in" ] , div [] [ text "Declared in" ]
, div [] [ withEmpty asGithubLink item.source.source ] , div [] <| findSource channel item.source
] ]
|> Just |> Just
@ -239,6 +236,44 @@ viewResultItem channel _ show item =
isOpen = isOpen =
Just item.source.name == show Just item.source.name == show
githubUrlPrefix branch =
"https://github.com/NixOS/nixpkgs/blob/" ++ branch ++ "/"
cleanPosition value =
if String.startsWith "source/" value then
String.dropLeft 7 value
else
value
asGithubLink value =
case Search.channelDetailsFromId channel of
Just channelDetails ->
a
[ href <| githubUrlPrefix channelDetails.branch ++ (value |> String.replace ":" "#L")
, target "_blank"
]
[ text value ]
Nothing ->
text <| cleanPosition value
sourceFile =
Maybe.map asGithubLink item.source.source
flakeOrNixpkgs =
case ( item.source.flakeName, item.source.flake, item.source.flakeUrl ) of
-- its a flake
( Just name, Just ( _, module_ ), Just flakeUrl_ ) ->
Just
[ li []
[ a [ href flakeUrl_ ] [ text name ]
]
]
_ ->
Nothing
in in
li li
[ class "option" [ class "option"
@ -248,22 +283,78 @@ viewResultItem channel _ show item =
<| <|
List.filterMap identity List.filterMap identity
[ Just <| [ Just <|
Html.a ul [ class "search-result-button" ]
[ class "search-result-button" (List.append
, onClick toggle (flakeOrNixpkgs |> Maybe.withDefault [])
[ li []
[ a
[ onClick toggle
, href "" , href ""
] ]
[ text item.source.name ] [ text item.source.name ]
]
]
)
, showDetails , showDetails
] ]
findSource : String -> ResultItemSource -> List (Html a)
findSource channel source =
let
githubUrlPrefix branch =
"https://github.com/NixOS/nixpkgs/blob/" ++ branch ++ "/"
cleanPosition value =
if String.startsWith "source/" value then
String.dropLeft 7 value
else
value
asGithubLink value =
case Search.channelDetailsFromId channel of
Just channelDetails ->
a
[ href <| githubUrlPrefix channelDetails.branch ++ (value |> String.replace ":" "#L")
, target "_blank"
]
[ text value ]
Nothing ->
text <| cleanPosition value
sourceFile =
Maybe.map asGithubLink source.source
flakeOrNixpkgs : Maybe (List (Html a))
flakeOrNixpkgs =
case ( source.flake, source.flakeUrl ) of
-- its a flake
( Just ( name, module_ ), Just flakeUrl_ ) ->
Just <|
List.append
(Maybe.withDefault [] <| Maybe.map (\sourceFile_ -> [ sourceFile_, span [] [ text " in " ] ]) sourceFile)
[ span [] [ text "Flake: " ]
, a [ href flakeUrl_ ] [ text <| name ++ "(Module: " ++ module_ ++ ")" ]
]
( Nothing, _ ) ->
Maybe.map (\l -> [ l ]) sourceFile
_ ->
Nothing
in
Maybe.withDefault [ span [] [ text "Not Found" ] ] flakeOrNixpkgs
-- API -- API
makeRequest : makeRequest :
Search.Options Search.Options
-> SearchType
-> String -> String
-> String -> String
-> Int -> Int
@ -271,9 +362,21 @@ makeRequest :
-> Maybe String -> Maybe String
-> Search.Sort -> Search.Sort
-> Cmd Msg -> Cmd Msg
makeRequest options channel query from size _ sort = makeRequest options _ channel query from size _ sort =
Search.makeRequest Search.makeRequest
(Search.makeRequestBody (makeRequestBody query from size sort)
channel
decodeResultItemSource
decodeResultAggregations
options
Search.QueryResponse
(Just "query-options")
|> Cmd.map SearchMsg
makeRequestBody : String -> Int -> Int -> Search.Sort -> Body
makeRequestBody query from size sort =
Search.makeRequestBody
(String.trim query) (String.trim query)
from from
size size
@ -288,14 +391,6 @@ makeRequest options channel query from size _ sort =
, ( "option_name_query", 3.0 ) , ( "option_name_query", 3.0 )
, ( "option_description", 1.0 ) , ( "option_description", 1.0 )
] ]
)
channel
decodeResultItemSource
decodeResultAggregations
options
Search.QueryResponse
(Just "query-options")
|> Cmd.map SearchMsg
@ -304,13 +399,19 @@ makeRequest options channel query from size _ sort =
decodeResultItemSource : Json.Decode.Decoder ResultItemSource decodeResultItemSource : Json.Decode.Decoder ResultItemSource
decodeResultItemSource = decodeResultItemSource =
Json.Decode.map6 ResultItemSource Json.Decode.succeed ResultItemSource
(Json.Decode.field "option_name" Json.Decode.string) |> Json.Decode.Pipeline.required "option_name" Json.Decode.string
(Json.Decode.field "option_description" (Json.Decode.nullable Json.Decode.string)) |> Json.Decode.Pipeline.optional "option_description" (Json.Decode.map Just Json.Decode.string) Nothing
(Json.Decode.field "option_type" (Json.Decode.nullable Json.Decode.string)) |> Json.Decode.Pipeline.optional "option_type" (Json.Decode.map Just Json.Decode.string) Nothing
(Json.Decode.field "option_default" (Json.Decode.nullable Json.Decode.string)) |> Json.Decode.Pipeline.optional "option_default" (Json.Decode.map Just Json.Decode.string) Nothing
(Json.Decode.field "option_example" (Json.Decode.nullable Json.Decode.string)) |> Json.Decode.Pipeline.optional "option_example" (Json.Decode.map Just Json.Decode.string) Nothing
(Json.Decode.field "option_source" (Json.Decode.nullable Json.Decode.string)) |> Json.Decode.Pipeline.optional "option_source" (Json.Decode.map Just Json.Decode.string) Nothing
|> Json.Decode.Pipeline.optional "option_flake"
(Json.Decode.map Just <| Json.Decode.map2 Tuple.pair (Json.Decode.index 0 Json.Decode.string) (Json.Decode.index 1 Json.Decode.string))
Nothing
|> Json.Decode.Pipeline.optional "flake_name" (Json.Decode.map Just Json.Decode.string) Nothing
|> Json.Decode.Pipeline.optional "flake_description" (Json.Decode.map Just Json.Decode.string) Nothing
|> Json.Decode.Pipeline.optional "flake_resolved" (Json.Decode.map Just decodeResolvedFlake) Nothing
decodeResultAggregations : Json.Decode.Decoder ResultAggregations decodeResultAggregations : Json.Decode.Decoder ResultAggregations

View file

@ -1,13 +1,20 @@
module Page.Packages exposing module Page.Packages exposing
( Model ( Model
, Msg , Msg(..)
, decodeResultAggregations
, decodeResultItemSource , decodeResultItemSource
, encodeBuckets
, init , init
, initBuckets
, makeRequest , makeRequest
, makeRequestBody
, update , update
, view , view
, viewBuckets
, viewSuccess
) )
import Browser.Events exposing (Visibility(..))
import Browser.Navigation import Browser.Navigation
import Html import Html
exposing exposing
@ -31,16 +38,19 @@ import Html.Attributes
, href , href
, id , id
, target , target
, type_
) )
import Html.Events exposing (onClick) import Html.Events exposing (onClick)
import Json.Decode import Http exposing (Body)
import Json.Decode exposing (Decoder)
import Json.Decode.Pipeline import Json.Decode.Pipeline
import Json.Encode import Json.Encode
import Maybe
import Regex import Regex
import Route import Route exposing (Route(..), SearchType)
import Search import Search exposing (Details(..), channelDetailsFromId, decodeResolvedFlake)
import Utils import Utils
import Search exposing (channelDetailsFromId) import View.Components.SearchInput exposing (closeButton, viewBucket)
@ -64,6 +74,9 @@ type alias ResultItemSource =
, homepage : List String , homepage : List String
, system : String , system : String
, hydra : Maybe (List ResultPackageHydra) , hydra : Maybe (List ResultPackageHydra)
, flakeName : Maybe String
, flakeDescription : Maybe String
, flakeUrl : Maybe ( String, String )
} }
@ -148,6 +161,9 @@ init searchArgs model =
let let
( newModel, newCmd ) = ( newModel, newCmd ) =
Search.init searchArgs model Search.init searchArgs model
-- _ =
-- Debug.log "New package model" newModel
in in
( newModel ( newModel
, Cmd.map SearchMsg newCmd , Cmd.map SearchMsg newCmd
@ -164,6 +180,7 @@ platforms =
] ]
-- UPDATE -- UPDATE
@ -204,6 +221,7 @@ view model =
viewSuccess viewSuccess
viewBuckets viewBuckets
SearchMsg SearchMsg
[]
viewBuckets : viewBuckets :
@ -256,72 +274,31 @@ viewBuckets bucketsAsString result =
filterPlatformsBucket : List { a | key : String } -> List { a | key : String } filterPlatformsBucket : List { a | key : String } -> List { a | key : String }
filterPlatformsBucket = List.filter (\a -> List.member a.key platforms) filterPlatformsBucket =
List.filter (\a -> List.member a.key platforms)
viewBucket :
String
-> List Search.AggregationsBucketItem
-> (String -> Msg)
-> List String
-> List (Html Msg)
-> List (Html Msg)
viewBucket title buckets searchMsgFor selectedBucket sets =
List.append
sets
(if List.isEmpty buckets then
[]
else
[ li []
[ ul []
(List.append
[ li [ class "header" ] [ text title ] ]
(List.map
(\bucket ->
li []
[ a
[ href "#"
, onClick <| searchMsgFor bucket.key
, classList
[ ( "selected"
, List.member bucket.key selectedBucket
)
]
]
[ span [] [ text bucket.key ]
, span [] [ span [ class "badge" ] [ text <| String.fromInt bucket.doc_count ] ]
]
]
)
buckets
)
)
]
]
)
viewSuccess : viewSuccess :
String String
-> Bool -> Details
-> Maybe String -> Maybe String
-> List (Search.ResultItem ResultItemSource) -> List (Search.ResultItem ResultItemSource)
-> Html Msg -> Html Msg
viewSuccess channel showNixOSDetails show hits = viewSuccess channel showInstallDetails show hits =
ul [] ul []
(List.map (List.map
(viewResultItem channel showNixOSDetails show) (viewResultItem channel showInstallDetails show)
hits hits
) )
viewResultItem : viewResultItem :
String String
-> Bool -> Details
-> Maybe String -> Maybe String
-> Search.ResultItem ResultItemSource -> Search.ResultItem ResultItemSource
-> Html Msg -> Html Msg
viewResultItem channel showNixOSDetails show item = viewResultItem channel showInstallDetails show item =
let let
cleanPosition = cleanPosition =
Regex.fromString "^[0-9a-f]+\\.tar\\.gz\\/" Regex.fromString "^[0-9a-f]+\\.tar\\.gz\\/"
@ -346,23 +323,7 @@ viewResultItem channel showNixOSDetails show item =
shortPackageDetails = shortPackageDetails =
ul [] ul []
((item.source.position (renderSource item channel trapClick createShortDetailsItem createGithubUrl
|> Maybe.map
(\position ->
case Search.channelDetailsFromId channel of
Nothing ->
[]
Just channelDetails ->
[ li [ trapClick ]
[ createShortDetailsItem
"Source"
(createGithubUrl channelDetails.branch position)
]
]
)
|> Maybe.withDefault []
)
|> List.append |> List.append
(item.source.homepage (item.source.homepage
|> List.head |> List.head
@ -420,7 +381,7 @@ viewResultItem channel showNixOSDetails show item =
Nothing -> Nothing ->
"#" "#"
] ]
[ text <| Maybe.withDefault "" maintainer.name ++ " <" ++ Maybe.withDefault "" maintainer.email ++ ">" ] [ text <| Maybe.withDefault "" maintainer.name ++ (Maybe.withDefault "" <| Maybe.map (\email -> " <" ++ email ++ ">") maintainer.email) ]
] ]
showPlatform platform = showPlatform platform =
@ -479,10 +440,11 @@ viewResultItem channel showNixOSDetails show item =
, em [] [ text item.source.attr_name ] , em [] [ text item.source.attr_name ]
, text "?" , text "?"
] ]
, ul [ class "nav nav-tabs" ] , ul [ class "nav nav-tabs" ] <|
Maybe.withDefault
[ li [ li
[ classList [ classList
[ ( "active", showNixOSDetails ) [ ( "active", List.member showInstallDetails [ Search.Unset, Search.FromNixOS, Search.FromFlake ] )
, ( "pull-right", True ) , ( "pull-right", True )
] ]
] ]
@ -490,13 +452,13 @@ viewResultItem channel showNixOSDetails show item =
[ href "#" [ href "#"
, Search.onClickStop <| , Search.onClickStop <|
SearchMsg <| SearchMsg <|
Search.ShowNixOSDetails True Search.ShowInstallDetails Search.FromNixOS
] ]
[ text "On NixOS" ] [ text "On NixOS" ]
] ]
, li , li
[ classList [ classList
[ ( "active", not showNixOSDetails ) [ ( "active", showInstallDetails == Search.FromNixpkgs )
, ( "pull-right", True ) , ( "pull-right", True )
] ]
] ]
@ -504,16 +466,38 @@ viewResultItem channel showNixOSDetails show item =
[ href "#" [ href "#"
, Search.onClickStop <| , Search.onClickStop <|
SearchMsg <| SearchMsg <|
Search.ShowNixOSDetails False Search.ShowInstallDetails Search.FromNixpkgs
] ]
[ text "On non-NixOS" ] [ text "On non-NixOS" ]
] ]
] ]
<|
Maybe.map
(\_ ->
[ li
[ classList
[ ( "active", True )
, ( "pull-right", True )
]
]
[ a
[ href "#"
, Search.onClickStop <|
SearchMsg <|
Search.ShowInstallDetails Search.FromFlake
]
[ text "Install from flake" ]
]
]
)
item.source.flakeUrl
, div , div
[ class "tab-content" ] [ class "tab-content" ]
<|
Maybe.withDefault
[ div [ div
[ classList [ classList
[ ( "active", not showNixOSDetails ) [ ( "active", showInstallDetails == Search.FromNixpkgs )
] ]
, class "tab-pane" , class "tab-pane"
, id "package-details-nixpkgs" , id "package-details-nixpkgs"
@ -526,7 +510,7 @@ viewResultItem channel showNixOSDetails show item =
, div , div
[ classList [ classList
[ ( "tab-pane", True ) [ ( "tab-pane", True )
, ( "active", showNixOSDetails ) , ( "active", List.member showInstallDetails [ Search.Unset, Search.FromNixOS, Search.FromFlake ] )
] ]
] ]
[ pre [ class "code-block" ] [ pre [ class "code-block" ]
@ -535,6 +519,26 @@ viewResultItem channel showNixOSDetails show item =
] ]
] ]
] ]
<|
Maybe.map
(\url ->
[ div
[ classList
[ ( "tab-pane", True )
, ( "active", True )
]
]
[ pre [ class "code-block" ]
[ text "nix build "
, strong [] [ text url ]
, text "#"
, em [] [ text item.source.attr_name ]
]
]
]
)
<|
Maybe.map Tuple.first item.source.flakeUrl
] ]
] ]
) )
@ -551,6 +555,17 @@ viewResultItem channel showNixOSDetails show item =
isOpen = isOpen =
Just item.source.attr_name == show Just item.source.attr_name == show
flakeItem =
Maybe.map Tuple.second item.source.flakeUrl
|> Maybe.map2
(\name resolved ->
[ li [ trapClick ]
[ createShortDetailsItem name resolved ]
]
)
item.source.flakeName
|> Maybe.withDefault []
in in
li li
[ class "package" [ class "package"
@ -560,12 +575,18 @@ viewResultItem channel showNixOSDetails show item =
([] ([]
|> List.append longerPackageDetails |> List.append longerPackageDetails
|> List.append |> List.append
[ Html.a [ ul [ class "search-result-button" ]
[ class "search-result-button" (List.append
, onClick toggle flakeItem
[ li []
[ a
[ onClick toggle
, href "" , href ""
] ]
[ text item.source.attr_name ] [ text item.source.attr_name ]
]
]
)
, div [] [ text <| Maybe.withDefault "" item.source.description ] , div [] [ text <| Maybe.withDefault "" item.source.description ]
, shortPackageDetails , shortPackageDetails
, Search.showMoreButton toggle isOpen , Search.showMoreButton toggle isOpen
@ -573,12 +594,50 @@ viewResultItem channel showNixOSDetails show item =
) )
renderSource : Search.ResultItem ResultItemSource -> String -> Html.Attribute Msg -> (String -> String -> Html Msg) -> (String -> String -> String) -> List (Html Msg)
renderSource item channel trapClick createShortDetailsItem createGithubUrl =
let
postion =
item.source.position
|> Maybe.map
(\position ->
case Search.channelDetailsFromId channel of
Nothing ->
[]
Just channelDetails ->
[ li [ trapClick ]
[ createShortDetailsItem
"Source"
(createGithubUrl channelDetails.branch position)
]
]
)
flakeDef =
Maybe.map2
(\name resolved ->
[ li [ trapClick ]
[ createShortDetailsItem
("Flake: " ++ name)
resolved
]
]
)
item.source.flakeName
<|
Maybe.map Tuple.second item.source.flakeUrl
in
Maybe.withDefault (Maybe.withDefault [] flakeDef) postion
-- API -- API
makeRequest : makeRequest :
Search.Options Search.Options
-> SearchType
-> String -> String
-> String -> String
-> Int -> Int
@ -586,7 +645,20 @@ makeRequest :
-> Maybe String -> Maybe String
-> Search.Sort -> Search.Sort
-> Cmd Msg -> Cmd Msg
makeRequest options channel query from size maybeBuckets sort = makeRequest options _ channel query from size maybeBuckets sort =
Search.makeRequest
(makeRequestBody query from size maybeBuckets sort)
channel
decodeResultItemSource
decodeResultAggregations
options
Search.QueryResponse
(Just "query-packages")
|> Cmd.map SearchMsg
makeRequestBody : String -> Int -> Int -> Maybe String -> Search.Sort -> Body
makeRequestBody query from size maybeBuckets sort =
let let
currentBuckets = currentBuckets =
initBuckets maybeBuckets initBuckets maybeBuckets
@ -637,8 +709,7 @@ makeRequest options channel query from size maybeBuckets sort =
) )
] ]
in in
Search.makeRequest Search.makeRequestBody
(Search.makeRequestBody
(String.trim query) (String.trim query)
from from
size size
@ -659,14 +730,6 @@ makeRequest options channel query from size maybeBuckets sort =
, ( "package_description", 1.3 ) , ( "package_description", 1.3 )
, ( "package_longDescription", 1.0 ) , ( "package_longDescription", 1.0 )
] ]
)
channel
decodeResultItemSource
decodeResultAggregations
options
Search.QueryResponse
(Just "query-packages")
|> Cmd.map SearchMsg
@ -707,6 +770,56 @@ decodeResultItemSource =
|> Json.Decode.Pipeline.required "package_homepage" decodeHomepage |> Json.Decode.Pipeline.required "package_homepage" decodeHomepage
|> Json.Decode.Pipeline.required "package_system" Json.Decode.string |> Json.Decode.Pipeline.required "package_system" Json.Decode.string
|> Json.Decode.Pipeline.required "package_hydra" (Json.Decode.nullable (Json.Decode.list decodeResultPackageHydra)) |> Json.Decode.Pipeline.required "package_hydra" (Json.Decode.nullable (Json.Decode.list decodeResultPackageHydra))
|> Json.Decode.Pipeline.optional "flake_name" (Json.Decode.map Just Json.Decode.string) Nothing
|> Json.Decode.Pipeline.optional "flake_description" (Json.Decode.map Just Json.Decode.string) Nothing
|> Json.Decode.Pipeline.optional "flake_resolved" (Json.Decode.map Just decodeResolvedFlake) Nothing
type alias ResolvedFlake =
{ type_ : String, owner : Maybe String, repo : Maybe String, url : Maybe String }
decodeResolvedFlake : Json.Decode.Decoder ( String, String )
decodeResolvedFlake =
let
resolved =
Json.Decode.succeed ResolvedFlake
|> Json.Decode.Pipeline.required "type" Json.Decode.string
|> Json.Decode.Pipeline.optional "owner" (Json.Decode.map Just Json.Decode.string) Nothing
|> Json.Decode.Pipeline.optional "repo" (Json.Decode.map Just Json.Decode.string) Nothing
|> Json.Decode.Pipeline.optional "url" (Json.Decode.map Just Json.Decode.string) Nothing
in
Json.Decode.map
(\resolved_ ->
let
repoPath =
case ( resolved_.owner, resolved_.repo ) of
( Just owner, Just repo ) ->
Just <| owner ++ "/" ++ repo
_ ->
Nothing
url =
resolved_.url
result =
case resolved_.type_ of
"github" ->
Maybe.map (\repoPath_ -> ( "github:" ++ repoPath_, "https://github.com/" ++ repoPath_ )) repoPath
"gitlab" ->
Maybe.map (\repoPath_ -> ( "gitlab:" ++ repoPath_, "https://gitlab.com/" ++ repoPath_ )) repoPath
"git" ->
Maybe.map (\url_ -> ( url_, url_ )) url
_ ->
Nothing
in
Maybe.withDefault ( "INVALID FLAKE ORIGIN", "INVALID FLAKE ORIGIN" ) result
)
resolved
filterPlatforms : List String -> List String filterPlatforms : List String -> List String
@ -743,7 +856,13 @@ decodeResultPackageLicense =
decodeResultPackageMaintainer : Json.Decode.Decoder ResultPackageMaintainer decodeResultPackageMaintainer : Json.Decode.Decoder ResultPackageMaintainer
decodeResultPackageMaintainer = decodeResultPackageMaintainer =
Json.Decode.map3 ResultPackageMaintainer Json.Decode.map3 ResultPackageMaintainer
(Json.Decode.field "name" (Json.Decode.nullable Json.Decode.string)) (Json.Decode.oneOf
[ Json.Decode.field "name" (Json.Decode.map Just Json.Decode.string)
, Json.Decode.field "email" (Json.Decode.map Just Json.Decode.string)
, Json.Decode.field "github" (Json.Decode.map Just Json.Decode.string)
, Json.Decode.succeed Nothing
]
)
(Json.Decode.field "email" (Json.Decode.nullable Json.Decode.string)) (Json.Decode.field "email" (Json.Decode.nullable Json.Decode.string))
(Json.Decode.field "github" (Json.Decode.nullable Json.Decode.string)) (Json.Decode.field "github" (Json.Decode.nullable Json.Decode.string))

View file

@ -2,13 +2,17 @@ module Route exposing
( Route(..) ( Route(..)
, SearchArgs , SearchArgs
, SearchRoute , SearchRoute
, SearchType(..)
, allTypes
, fromUrl , fromUrl
, href , href
, replaceUrl , replaceUrl
, routeToString , routeToString
, searchTypeToString, searchTypeToTitle
) )
import Browser.Navigation import Browser.Navigation
import Dict
import Html import Html
import Html.Attributes import Html.Attributes
import Route.SearchQuery exposing (SearchQuery) import Route.SearchQuery exposing (SearchQuery)
@ -32,9 +36,61 @@ type alias SearchArgs =
-- TODO: embed sort type -- TODO: embed sort type
, sort : Maybe String , sort : Maybe String
, type_ : Maybe SearchType
} }
type SearchType
= OptionSearch
| PackageSearch
-- | FlakeSearch
allTypes : List SearchType
allTypes =
[ PackageSearch, OptionSearch ]
searchTypeFromString : String -> Maybe SearchType
searchTypeFromString string =
case string of
"options" ->
Just OptionSearch
"packages" ->
Just PackageSearch
-- "flakes" ->
-- Just FlakeSearch
_ ->
Nothing
searchTypeToString : SearchType -> String
searchTypeToString stype =
case stype of
OptionSearch ->
"options"
PackageSearch ->
"packages"
-- FlakeSearch ->
-- "flakes"
searchTypeToTitle : SearchType -> String
searchTypeToTitle stype =
case stype of
OptionSearch ->
"Options"
PackageSearch ->
"Packages"
-- FlakeSearch ->
-- "flakes"
type alias SearchRoute = type alias SearchRoute =
SearchArgs -> Route SearchArgs -> Route
@ -56,6 +112,7 @@ searchQueryParser url =
<?> Url.Parser.Query.int "size" <?> Url.Parser.Query.int "size"
<?> Url.Parser.Query.string "buckets" <?> Url.Parser.Query.string "buckets"
<?> Url.Parser.Query.string "sort" <?> Url.Parser.Query.string "sort"
<?> Url.Parser.Query.map (Maybe.andThen searchTypeFromString) (Url.Parser.Query.string "type")
searchArgsToUrl : SearchArgs -> ( List QueryParameter, Maybe ( String, Route.SearchQuery.SearchQuery ) ) searchArgsToUrl : SearchArgs -> ( List QueryParameter, Maybe ( String, Route.SearchQuery.SearchQuery ) )
@ -67,6 +124,7 @@ searchArgsToUrl args =
, Maybe.map (Url.Builder.int "size") args.size , Maybe.map (Url.Builder.int "size") args.size
, Maybe.map (Url.Builder.string "buckets") args.buckets , Maybe.map (Url.Builder.string "buckets") args.buckets
, Maybe.map (Url.Builder.string "sort") args.sort , Maybe.map (Url.Builder.string "sort") args.sort
, Maybe.map (Url.Builder.string "type") <| Maybe.map searchTypeToString args.type_
] ]
, Maybe.map (Tuple.pair "query") args.query , Maybe.map (Tuple.pair "query") args.query
) )

View file

@ -4,14 +4,19 @@ module Search exposing
, Model , Model
, Msg(..) , Msg(..)
, Options , Options
, ResultHits
, ResultItem , ResultItem
, SearchResult , SearchResult
, Sort(..) , Sort(..)
, channelDetailsFromId , channelDetailsFromId
, channels , channels
, decodeAggregation , decodeAggregation
, decodeResolvedFlake
, decodeResult , decodeResult
, defaultFlakeId
, elementId , elementId
, flakeFromId
, flakes
, fromSortId , fromSortId
, init , init
, makeRequest , makeRequest
@ -22,6 +27,7 @@ module Search exposing
, trapClick , trapClick
, update , update
, view , view
, viewResult, Details(..)
) )
import Base64 import Base64
@ -65,16 +71,19 @@ import Html.Events
) )
import Http import Http
import Json.Decode import Json.Decode
import Json.Decode.Pipeline
import Json.Encode import Json.Encode
import RemoteData import RemoteData
import Route import Route exposing (SearchArgs, SearchType)
import Route.SearchQuery import Route.SearchQuery
import Set import Set
import Task import Task
import Browser.Events exposing (Visibility(..))
type alias Model a b = type alias Model a b =
{ channel : String { channel : String
, flake : String
, query : Maybe String , query : Maybe String
, result : RemoteData.WebData (SearchResult a b) , result : RemoteData.WebData (SearchResult a b)
, show : Maybe String , show : Maybe String
@ -83,7 +92,8 @@ type alias Model a b =
, buckets : Maybe String , buckets : Maybe String
, sort : Sort , sort : Sort
, showSort : Bool , showSort : Bool
, showNixOSDetails : Bool , showInstallDetails : Details
, searchType : Route.SearchType
} }
@ -135,12 +145,75 @@ type Sort
| AlphabeticallyDesc | AlphabeticallyDesc
type alias ResolvedFlake =
{ type_ : String, owner : Maybe String, repo : Maybe String, url : Maybe String }
decodeResolvedFlake : Json.Decode.Decoder String
decodeResolvedFlake =
let
resolved =
Json.Decode.succeed ResolvedFlake
|> Json.Decode.Pipeline.required "type" Json.Decode.string
|> Json.Decode.Pipeline.optional "owner" (Json.Decode.map Just Json.Decode.string) Nothing
|> Json.Decode.Pipeline.optional "repo" (Json.Decode.map Just Json.Decode.string) Nothing
|> Json.Decode.Pipeline.optional "url" (Json.Decode.map Just Json.Decode.string) Nothing
in
Json.Decode.map
(\resolved_ ->
let
repoPath =
case ( resolved_.owner, resolved_.repo ) of
( Just owner, Just repo ) ->
Just <| owner ++ "/" ++ repo
_ ->
Nothing
url =
resolved_.url
result =
case resolved_.type_ of
"github" ->
Maybe.map (\repoPath_ -> "https://github.com/" ++ repoPath_) repoPath
"gitlab" ->
Maybe.map (\repoPath_ -> "https://gitlab.com/" ++ repoPath_) repoPath
"git" ->
url
_ ->
Nothing
in
Maybe.withDefault "INVALID FLAKE ORIGIN" result
)
resolved
init : init :
Route.SearchArgs Route.SearchArgs
-> Maybe (Model a b) -> Maybe (Model a b)
-> ( Model a b, Cmd (Msg a b) ) -> ( Model a b, Cmd (Msg a b) )
init args maybeModel = init args maybeModel =
let let
emptyRoute : Route.SearchArgs
emptyRoute =
{ query = Nothing
, channel = Nothing
, show = Nothing
, from = Nothing
, size = Nothing
, buckets = Nothing
-- TODO= Nothing type
, sort = Nothing
, type_ = Nothing
}
-- args =
-- Maybe.withDefault emptyRoute maybeArgs
getField getFn default = getField getFn default =
maybeModel maybeModel
|> Maybe.map getFn |> Maybe.map getFn
@ -158,6 +231,7 @@ init args maybeModel =
( { channel = ( { channel =
args.channel args.channel
|> Maybe.withDefault modelChannel |> Maybe.withDefault modelChannel
, flake = defaultFlakeId
, query = , query =
args.query args.query
|> Maybe.andThen Route.SearchQuery.searchQueryToString |> Maybe.andThen Route.SearchQuery.searchQueryToString
@ -176,7 +250,8 @@ init args maybeModel =
|> fromSortId |> fromSortId
|> Maybe.withDefault Relevance |> Maybe.withDefault Relevance
, showSort = False , showSort = False
, showNixOSDetails = False , showInstallDetails = Unset
, searchType = Maybe.withDefault Route.PackageSearch args.type_
} }
|> ensureLoading |> ensureLoading
, Browser.Dom.focus "search-query-input" |> Task.attempt (\_ -> NoOp) , Browser.Dom.focus "search-query-input" |> Task.attempt (\_ -> NoOp)
@ -194,7 +269,7 @@ ensureLoading :
Model a b Model a b
-> Model a b -> Model a b
ensureLoading model = ensureLoading model =
if model.query /= Nothing && model.query /= Just "" && List.member model.channel channels then if model.query /= Nothing && model.query /= Just "" && (List.member model.channel channels || List.member model.channel flakeIds) then
{ model | result = RemoteData.Loading } { model | result = RemoteData.Loading }
else else
@ -218,14 +293,20 @@ type Msg a b
| ToggleSort | ToggleSort
| BucketsChange String | BucketsChange String
| ChannelChange String | ChannelChange String
| FlakeChange String
| SubjectChange SearchType
| QueryInput String | QueryInput String
| QueryInputSubmit | QueryInputSubmit
| QueryResponse (RemoteData.WebData (SearchResult a b)) | QueryResponse (RemoteData.WebData (SearchResult a b))
| ShowDetails String | ShowDetails String
| ChangePage Int | ChangePage Int
| ShowNixOSDetails Bool | ShowInstallDetails Details
type Details
= FromNixpkgs
| FromNixOS
| FromFlake
| Unset
scrollToEntry : scrollToEntry :
Maybe String Maybe String
-> Cmd (Msg a b) -> Cmd (Msg a b)
@ -292,6 +373,26 @@ update toRoute navKey msg model =
|> ensureLoading |> ensureLoading
|> pushUrl toRoute navKey |> pushUrl toRoute navKey
FlakeChange flake ->
{ model
| channel = flake
, show = Nothing
, buckets = Nothing
, from = 0
}
|> ensureLoading
|> pushUrl toRoute navKey
SubjectChange subject ->
{ model
| searchType = subject
, show = Nothing
, buckets = Nothing
, from = 0
}
|> ensureLoading
|> pushUrl toRoute navKey
QueryInput query -> QueryInput query ->
( { model | query = Just query } ( { model | query = Just query }
, Cmd.none , Cmd.none
@ -307,6 +408,10 @@ update toRoute navKey msg model =
|> pushUrl toRoute navKey |> pushUrl toRoute navKey
QueryResponse result -> QueryResponse result ->
-- let
-- _ =
-- Debug.log "got query result" result
-- in
( { model ( { model
| result = result | result = result
} }
@ -329,8 +434,8 @@ update toRoute navKey msg model =
|> ensureLoading |> ensureLoading
|> pushUrl toRoute navKey |> pushUrl toRoute navKey
ShowNixOSDetails show -> ShowInstallDetails details ->
{ model | showNixOSDetails = show } { model | showInstallDetails = details }
|> pushUrl toRoute navKey |> pushUrl toRoute navKey
@ -362,6 +467,7 @@ createUrl toRoute model =
, size = Just model.size , size = Just model.size
, buckets = model.buckets , buckets = model.buckets
, sort = Just <| toSortId model.sort , sort = Just <| toSortId model.sort
, type_ = Just model.searchType
} }
@ -395,13 +501,14 @@ channelDetails : Channel -> ChannelDetails
channelDetails channel = channelDetails channel =
case channel of case channel of
Unstable -> Unstable ->
ChannelDetails "unstable" "unstable" "nixos/trunk-combined" "nixpkgs-unstable" ChannelDetails "unstable" "unstable" "nixos/trunk-combined" "nixos-unstable"
Release_20_09 -> Release_20_09 ->
ChannelDetails "20.09" "20.09" "nixos/release-20.09" "nixpkgs-20.09" ChannelDetails "20.09" "20.09" "nixos/release-20.09" "nixos-20.09"
Release_21_05 -> Release_21_05 ->
ChannelDetails "21.05" "21.05" "nixos/release-21.05" "nixpkgs-21.05" ChannelDetails "21.05" "21.05" "nixos/release-21.05" "nixos-21.05"
channelFromId : String -> Maybe Channel channelFromId : String -> Maybe Channel
channelFromId channel_id = channelFromId channel_id =
@ -433,6 +540,68 @@ channels =
] ]
type alias Flake =
{ id : String
, isNixpkgs : Bool
, title : String
, source : String
}
defaultFlakeId : String
defaultFlakeId =
"group-01"
flakeFromId : String -> Maybe Flake
flakeFromId flake_id =
let
find : String -> List Flake -> Maybe Flake
find id_ list =
case list of
flake :: rest ->
if flake.id == id_ then
Just flake
else
find id_ rest
[] ->
Nothing
in
find flake_id flakes
flakeIds : List String
flakeIds =
List.map .id flakes
flakes : List Flake
flakes =
[ { id = "latest-nixos-20.09-latest"
, isNixpkgs = True
, title = "Nixpkgs 20.09"
, source = ""
}
, { id = "nixos-21.05-latest"
, isNixpkgs = True
, title = "Nixpkgs 21.05"
, source = ""
}
, { id = "latest-nixos-unstable"
, isNixpkgs = True
, title = "Nixpkgs Unstable"
, source = ""
}
, { id = "flakes"
, isNixpkgs = False
, title = "Public Flakes"
, source = ""
}
]
sortBy : List Sort sortBy : List Sort
sortBy = sortBy =
[ Relevance [ Relevance
@ -579,7 +748,7 @@ view :
-> Model a b -> Model a b
-> ->
(String (String
-> Bool -> Details
-> Maybe String -> Maybe String
-> List (ResultItem a) -> List (ResultItem a)
-> Html c -> Html c
@ -590,8 +759,9 @@ view :
-> List (Html c) -> List (Html c)
) )
-> (Msg a b -> c) -> (Msg a b -> c)
-> List (Html c)
-> Html c -> Html c
view { toRoute, categoryName } title model viewSuccess viewBuckets outMsg = view { toRoute, categoryName } title model viewSuccess viewBuckets outMsg searchBuckets =
let let
resultStatus = resultStatus =
case model.result of case model.result of
@ -619,7 +789,7 @@ view { toRoute, categoryName } title model viewSuccess viewBuckets outMsg =
) )
[ h1 [] title [ h1 [] title
, viewSearchInput outMsg categoryName model.channel model.query , viewSearchInput outMsg categoryName model.channel model.query
, viewResult outMsg toRoute categoryName model viewSuccess viewBuckets , viewResult outMsg toRoute categoryName model viewSuccess viewBuckets searchBuckets
] ]
@ -630,7 +800,7 @@ viewResult :
-> Model a b -> Model a b
-> ->
(String (String
-> Bool -> Details
-> Maybe String -> Maybe String
-> List (ResultItem a) -> List (ResultItem a)
-> Html c -> Html c
@ -640,15 +810,17 @@ viewResult :
-> SearchResult a b -> SearchResult a b
-> List (Html c) -> List (Html c)
) )
-> List (Html c)
-> Html c -> Html c
viewResult outMsg toRoute categoryName model viewSuccess viewBuckets = viewResult outMsg toRoute categoryName model viewSuccess viewBuckets searchBuckets =
case model.result of case model.result of
RemoteData.NotAsked -> RemoteData.NotAsked ->
div [] [ text "" ] div [] [ ]
RemoteData.Loading -> RemoteData.Loading ->
div [ class "loader-wrapper" ] div [ class "loader-wrapper" ]
[ div [ class "loader" ] [ text "Loading..." ] [ ul [ class "search-sidebar" ] searchBuckets
, div [ class "loader" ] [ text "Loading..." ]
, h2 [] [ text "Searching..." ] , h2 [] [ text "Searching..." ]
] ]
@ -658,18 +830,22 @@ viewResult outMsg toRoute categoryName model viewSuccess viewBuckets =
viewBuckets model.buckets result viewBuckets model.buckets result
in in
if result.hits.total.value == 0 && List.length buckets == 0 then if result.hits.total.value == 0 && List.length buckets == 0 then
viewNoResults categoryName div [ class "search-results" ]
[ ul [ class "search-sidebar" ] searchBuckets
, viewNoResults categoryName
]
else if List.length buckets > 0 then else if List.length buckets > 0 then
div [ class "search-results" ] div [ class "search-results" ]
[ ul [] buckets [ ul [ class "search-sidebar" ] <| List.append searchBuckets buckets
, div [] , div []
(viewResults model result viewSuccess toRoute outMsg categoryName) (viewResults model result viewSuccess toRoute outMsg categoryName)
] ]
else else
div [ class "search-results" ] div [ class "search-results" ]
[ div [] [ ul [ class "search-sidebar" ] searchBuckets
, div []
(viewResults model result viewSuccess toRoute outMsg categoryName) (viewResults model result viewSuccess toRoute outMsg categoryName)
] ]
@ -694,7 +870,8 @@ viewResult outMsg toRoute categoryName model viewSuccess viewBuckets =
in in
div [] div []
[ div [ class "alert alert-error" ] [ div [ class "alert alert-error" ]
[ h4 [] [ text errorTitle ] [ ul [ class "search-sidebar" ] searchBuckets
, h4 [] [ text errorTitle ]
, text errorMessage , text errorMessage
] ]
] ]
@ -793,7 +970,7 @@ viewResults :
-> SearchResult a b -> SearchResult a b
-> ->
(String (String
-> Bool -> Details
-> Maybe String -> Maybe String
-> List (ResultItem a) -> List (ResultItem a)
-> Html c -> Html c
@ -845,7 +1022,7 @@ viewResults model result viewSuccess toRoute outMsg categoryName =
) )
) )
] ]
, viewSuccess model.channel model.showNixOSDetails model.show result.hits.hits , viewSuccess model.channel model.showInstallDetails model.show result.hits.hits
, Html.map outMsg <| viewPager model result.hits.total.value , Html.map outMsg <| viewPager model result.hits.total.value
] ]
@ -1139,7 +1316,7 @@ makeRequest :
-> Maybe String -> Maybe String
-> Cmd (Msg a b) -> Cmd (Msg a b)
makeRequest body channel decodeResultItemSource decodeResultAggregations options responseMsg tracker = makeRequest body channel decodeResultItemSource decodeResultAggregations options responseMsg tracker =
let branch = Maybe.map (\details -> details.branch) (channelDetailsFromId channel) |> Maybe.withDefault "" let branch = Maybe.map (\details -> details.branch) (channelDetailsFromId channel) |> Maybe.withDefault channel
index = "latest-" ++ String.fromInt options.mappingSchemaVersion ++ "-" ++ branch index = "latest-" ++ String.fromInt options.mappingSchemaVersion ++ "-" ++ branch
in in
Http.riskyRequest Http.riskyRequest

29
src/View/Components.elm Normal file
View file

@ -0,0 +1,29 @@
module View.Components exposing (..)
import Html exposing (Html)
import Route exposing (SearchRoute)
import Search exposing (Model, Msg, ResultItem, SearchResult)
import View.Components.Body
import Search exposing (Details)
body :
{ toRoute : SearchRoute, categoryName : String }
-> List (Html c)
-> Model a b
->
(String
-> Details
-> Maybe String
-> List (ResultItem a)
-> Html c
)
->
(Maybe String
-> SearchResult a b
-> List (Html c)
)
-> (Msg a b -> c)
-> Html c
body =
View.Components.Body.view

View file

@ -0,0 +1,65 @@
module View.Components.Body exposing (..)
import Html exposing (Html, div, h1)
import Html.Attributes exposing (class)
import Html.Events exposing (onClick)
import RemoteData exposing (RemoteData(..))
import Route
import Search exposing (Model, Msg(..), ResultItem, SearchResult, viewResult)
import View.Components.SearchInput exposing (viewSearchInput)
import View.Components.SearchInput exposing (viewFlakes)
import Html exposing (details)
import Search exposing (Details)
view :
{ toRoute : Route.SearchRoute
, categoryName : String
}
-> List (Html c)
-> Model a b
->
(String
-> Details
-> Maybe String
-> List (ResultItem a)
-> Html c
)
->
(Maybe String
-> SearchResult a b
-> List (Html c)
)
-> (Msg a b -> c)
-> Html c
view { toRoute, categoryName } title model viewSuccess viewBuckets outMsg =
let
resultStatus =
case model.result of
RemoteData.NotAsked ->
"not-asked"
RemoteData.Loading ->
"loading"
RemoteData.Success _ ->
"success"
RemoteData.Failure _ ->
"failure"
in
div
(List.append
[ class <| "search-page " ++ resultStatus ]
(if model.showSort then
[ onClick (outMsg ToggleSort) ]
else
[]
)
)
[ h1 [] title
, viewSearchInput outMsg model.searchType model.query
, viewResult outMsg toRoute categoryName model viewSuccess viewBuckets <| viewFlakes outMsg model.channel model.searchType
]

View file

@ -0,0 +1,118 @@
module View.Components.SearchInput exposing (..)
import Html exposing (Html, a, button, div, form, h4, input, li, p, span, text, th, ul)
import Html.Attributes exposing (attribute, autofocus, class, classList, href, id, placeholder, selected, type_, value)
import Html.Events exposing (onClick, onInput, onSubmit)
import Route exposing (SearchType, allTypes, searchTypeToString, searchTypeToTitle)
import Search exposing (Msg(..))
viewSearchInput :
(Msg a b -> c)
-> SearchType
-> Maybe String
-> Html c
viewSearchInput outMsg category searchQuery =
let
searchHint =
Maybe.withDefault "Packages and Options" <| Maybe.map (\_ -> searchTypeToString category) searchQuery
in
form
[ onSubmit (outMsg QueryInputSubmit)
, class "search-input"
]
[ div []
[ div []
[ input
[ type_ "text"
, id "search-query-input"
, autofocus True
, placeholder <| "Search for " ++ searchHint
, onInput (outMsg << QueryInput)
, value <| Maybe.withDefault "" searchQuery
]
[]
]
, button [ class "btn", type_ "submit" ]
[ text "Search" ]
]
]
viewFlakes : (Msg a b -> msg) -> String -> SearchType -> List (Html msg)
viewFlakes outMsg selectedFlake selectedCategory =
[ li []
[ ul []
(List.map
(\category ->
li []
[ a
[ href "#"
, onClick <| outMsg (SubjectChange category)
, classList
[ ( "selected"
, category == selectedCategory
)
]
]
[ span [] [ text <| searchTypeToTitle category ]
, closeButton
]
]
)
allTypes
)
]
]
closeButton : Html a
closeButton =
span [] []
viewBucket :
String
-> List Search.AggregationsBucketItem
-> (String -> a)
-> List String
-> List (Html a)
-> List (Html a)
viewBucket title buckets searchMsgFor selectedBucket sets =
List.append
sets
(if List.isEmpty buckets then
[]
else
[ li []
[ ul []
(List.append
[ li [ class "header" ] [ text title ] ]
(List.map
(\bucket ->
li []
[ a
[ href "#"
, onClick <| searchMsgFor bucket.key
, classList
[ ( "selected"
, List.member bucket.key selectedBucket
)
]
]
[ span [] [ text bucket.key ]
, if List.member bucket.key selectedBucket then
closeButton
else
span [] [ span [ class "badge" ] [ text <| String.fromInt bucket.doc_count ] ]
]
]
)
buckets
)
)
]
]
)

View file

@ -86,6 +86,9 @@ header .navbar.navbar-static-top {
} }
ul.nav > li { ul.nav > li {
line-height: 20px; line-height: 20px;
sup {
margin-left: 0.5em;
}
} }
} }
@ -173,17 +176,28 @@ header .navbar.navbar-static-top {
.search-result-button { .search-result-button {
&:hover { list-style: none;
margin: 0;
padding: 0;
& > li {
display: inline-block;
&:nth-child(1):after {
content: "→";
margin: 0 0.2em;
}
& > a:hover {
text-decoration: underline; text-decoration: underline;
} }
} }
}
& > .search-results {
display: flex;
flex-direction: row;
// Buckets // Buckets
& > ul { ul.search-sidebar {
width: 25em;
list-style: none; list-style: none;
margin: 0 1em 0 0; margin: 0 1em 0 0;
@ -208,7 +222,7 @@ header .navbar.navbar-static-top {
& > a { & > a {
display: grid; display: grid;
grid-template-columns: auto auto; grid-template-columns: auto max-content;
color: #333; color: #333;
padding: 0.5em 0.5em 0.5em 1em; padding: 0.5em 0.5em 0.5em 1em;
text-decoration: none; text-decoration: none;
@ -232,30 +246,30 @@ header .navbar.navbar-static-top {
color: #FFF; color: #FFF;
border-radius: 4px; border-radius: 4px;
position: relative; position: relative;
&:after {
content: "\2715";
font-size: 1.7em;
color: #fff;
position: absolute;
top: 4px;
right: 4px;
background: #0081c2;
bottom: 4px;
width: 1em;
padding: 2px;
}
& > span:last-child { & > span:last-child {
display: none; display: none;
} }
} }
& .close {
opacity: 1;
text-shadow: none;
color: inherit;
font-size: inherit;
padding-left: .5em;
padding-right: .5em;
} }
} }
} }
} }
} }
}
& > .search-results {
display: flex;
flex-direction: row;
// Results section // Results section
& > div { & > div {