add popular crates to index

This commit is contained in:
Sam Rijs 2018-02-17 14:41:09 +11:00
parent e0352539bd
commit 37a11c17b5
4 changed files with 135 additions and 34 deletions

View file

@ -21,7 +21,7 @@ use ::utils::cache::Cache;
use ::models::repo::{Repository, RepoPath};
use ::models::crates::{CrateName, CratePath, CrateRelease, AnalyzedDependencies};
use ::interactors::crates::QueryCrate;
use ::interactors::crates::{QueryCrate, GetPopularCrates};
use ::interactors::RetrieveFileAtPath;
use ::interactors::github::{GetPopularRepos};
@ -36,6 +36,7 @@ pub struct Engine {
logger: Logger,
query_crate: Arc<Cache<QueryCrate<HttpClient>>>,
get_popular_crates: Arc<Cache<GetPopularCrates<HttpClient>>>,
get_popular_repos: Arc<Cache<GetPopularRepos<HttpClient>>>,
retrieve_file_at_path: Arc<RetrieveFileAtPath<HttpClient>>
}
@ -43,12 +44,14 @@ pub struct Engine {
impl Engine {
pub fn new(client: Client<HttpsConnector<HttpConnector>>, logger: Logger) -> Engine {
let query_crate = Cache::new(QueryCrate(client.clone()), Duration::from_secs(300), 500);
let get_popular_crates = Cache::new(GetPopularCrates(client.clone()), Duration::from_secs(10), 1);
let get_popular_repos = Cache::new(GetPopularRepos(client.clone()), Duration::from_secs(10), 1);
Engine {
client: client.clone(), logger,
query_crate: Arc::new(query_crate),
get_popular_crates: Arc::new(get_popular_crates),
get_popular_repos: Arc::new(get_popular_repos),
retrieve_file_at_path: Arc::new(RetrieveFileAtPath(client))
}
@ -84,6 +87,13 @@ impl Engine {
})
}
pub fn get_popular_crates(&self) ->
impl Future<Item=Vec<CratePath>, Error=Error>
{
self.get_popular_crates.call(())
.from_err().map(|crates| crates.clone())
}
pub fn analyze_repo_dependencies(&self, repo_path: RepoPath) ->
impl Future<Item=AnalyzeDependenciesOutcome, Error=Error>
{
@ -180,7 +190,8 @@ lazy_static! {
RepoPath::from_parts("github", "google", "xi-editor"),
RepoPath::from_parts("github", "lk-geimfari", "awesomo"),
RepoPath::from_parts("github", "redox-os", "tfs"),
RepoPath::from_parts("github", "carols10cents", "rustlings")
RepoPath::from_parts("github", "carols10cents", "rustlings"),
RepoPath::from_parts("github", "rust-unofficial", "awesome-rust")
].into_iter().collect::<Result<HashSet<_>, _>>().unwrap()
};
}

View file

@ -7,9 +7,10 @@ use tokio_service::Service;
use semver::{Version, VersionReq};
use serde_json;
use ::models::crates::{CrateName, CrateRelease, CrateDeps, CrateDep};
use ::models::crates::{CrateName, CrateRelease, CrateDeps, CrateDep, CratePath};
const CRATES_INDEX_BASE_URI: &str = "https://raw.githubusercontent.com/rust-lang/crates.io-index";
const CRATES_API_BASE_URI: &str = "https://crates.io/api/v1";
#[derive(Deserialize, Debug)]
struct RegistryPackageDep {
@ -109,3 +110,59 @@ impl<S> Service for QueryCrate<S>
}))
}
}
#[derive(Deserialize)]
struct SummaryResponseDetail {
name: String,
max_version: Version
}
#[derive(Deserialize)]
struct SummaryResponse {
most_downloaded: Vec<SummaryResponseDetail>
}
fn convert_summary(response: SummaryResponse) -> Result<Vec<CratePath>, Error> {
response.most_downloaded.into_iter().map(|detail| {
let name = detail.name.parse()?;
Ok(CratePath { name, version: detail.max_version })
}).collect()
}
#[derive(Debug, Clone)]
pub struct GetPopularCrates<S>(pub S);
impl<S> Service for GetPopularCrates<S>
where S: Service<Request=Request, Response=Response, Error=HyperError> + Clone + 'static,
S::Future: 'static
{
type Request = ();
type Response = Vec<CratePath>;
type Error = Error;
type Future = Box<Future<Item=Self::Response, Error=Self::Error>>;
fn call(&self, _req: ()) -> Self::Future {
let service = self.0.clone();
let uri_future = format!("{}/summary", CRATES_API_BASE_URI)
.parse::<Uri>().into_future().from_err();
Box::new(uri_future.and_then(move |uri| {
let request = Request::new(Method::Get, uri.clone());
service.call(request).from_err().and_then(move |response| {
let status = response.status();
if !status.is_success() {
future::Either::A(future::err(format_err!("Status code {} for URI {}", status, uri)))
} else {
let body_future = response.body().concat2().from_err();
let decode_future = body_future.and_then(|body| {
let summary = serde_json::from_slice::<SummaryResponse>(&body)?;
convert_summary(summary)
});
future::Either::B(decode_future)
}
})
}))
}
}

View file

@ -113,16 +113,18 @@ impl Server {
fn index(&self, _req: Request, _params: Params, logger: Logger) ->
impl Future<Item=Response, Error=HyperError>
{
self.engine.get_popular_repos().then(move |popular_result| {
self.engine.get_popular_repos()
.join(self.engine.get_popular_crates())
.then(move |popular_result| {
match popular_result {
Err(err) => {
error!(logger, "error: {}", err);
let mut response = views::html::error::render("Could not retrieve popular repositories", "");
let mut response = views::html::error::render("Could not retrieve popular items", "");
response.set_status(StatusCode::InternalServerError);
future::ok(response)
},
Ok(popular) =>
future::ok(views::html::index::render(popular))
Ok((popular_repos, popular_crates)) =>
future::ok(views::html::index::render(popular_repos, popular_crates))
}
})
}

View file

@ -2,9 +2,12 @@ use hyper::Response;
use maud::{Markup, html};
use ::models::repo::Repository;
use ::models::crates::CratePath;
fn popular_table(popular: Vec<Repository>) -> Markup {
fn popular_table(popular_repos: Vec<Repository>, popular_crates: Vec<CratePath>) -> Markup {
html! {
div class="columns" {
div class="column" {
h2 class="title is-3" "Popular Repositories"
table class="table is-fullwidth is-striped is-hoverable" {
@ -15,7 +18,7 @@ fn popular_table(popular: Vec<Repository>) -> Markup {
}
}
tbody {
@for repo in popular {
@for repo in popular_repos.into_iter().take(10) {
tr {
td {
a href=(format!("{}/repo/{}/{}/{}", &super::SELF_BASE_URL as &str, repo.path.site.as_ref(), repo.path.qual.as_ref(), repo.path.name.as_ref())) {
@ -30,9 +33,37 @@ fn popular_table(popular: Vec<Repository>) -> Markup {
}
}
}
div class="column" {
h2 class="title is-3" "Popular Crates"
table class="table is-fullwidth is-striped is-hoverable" {
thead {
tr {
th "Crate"
th class="has-text-right" "Status"
}
}
tbody {
@for crate_path in popular_crates {
tr {
td {
a href=(format!("{}/crate/{}/{}", &super::SELF_BASE_URL as &str, crate_path.name.as_ref(), crate_path.version)) {
(format!("{}", crate_path.name.as_ref()))
}
}
td class="has-text-right" {
img src=(format!("{}/crate/{}/{}/status.svg", &super::SELF_BASE_URL as &str, crate_path.name.as_ref(), crate_path.version));
}
}
}
}
}
}
}
}
}
pub fn render(popular: Vec<Repository>) -> Response {
pub fn render(popular_repos: Vec<Repository>, popular_crates: Vec<CratePath>) -> Response {
super::render_html("Keep your dependencies up-to-date", html! {
section class="hero is-light" {
div class="hero-head" (super::render_navbar())
@ -48,7 +79,7 @@ pub fn render(popular: Vec<Repository>) -> Response {
}
}
section class="section" {
div class="container" (popular_table(popular))
div class="container" (popular_table(popular_repos, popular_crates))
}
(super::render_footer(None))
})