diff --git a/.sqlx/query-0f51891df12ccdbecbdffef1588695c7a88206171881f2d9951eb2f40125b244.json b/.sqlx/query-0f51891df12ccdbecbdffef1588695c7a88206171881f2d9951eb2f40125b244.json deleted file mode 100644 index f7341988f..000000000 --- a/.sqlx/query-0f51891df12ccdbecbdffef1588695c7a88206171881f2d9951eb2f40125b244.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT\n target_name,\n rustdoc_status\n FROM releases\n WHERE releases.id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "target_name", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "rustdoc_status", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Int4" - ] - }, - "nullable": [ - false, - false - ] - }, - "hash": "0f51891df12ccdbecbdffef1588695c7a88206171881f2d9951eb2f40125b244" -} diff --git a/.sqlx/query-192d91c85fff00d311fa28dd7f5781ea30207795b833c4951f3c8303c49e699e.json b/.sqlx/query-192d91c85fff00d311fa28dd7f5781ea30207795b833c4951f3c8303c49e699e.json new file mode 100644 index 000000000..1cb245393 --- /dev/null +++ b/.sqlx/query-192d91c85fff00d311fa28dd7f5781ea30207795b833c4951f3c8303c49e699e.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM builds", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "192d91c85fff00d311fa28dd7f5781ea30207795b833c4951f3c8303c49e699e" +} diff --git a/.sqlx/query-2b8b1aae3740a05cb7179be9c7d5af30e8362c3cba0b07bc18fa32ff1a2232cc.json b/.sqlx/query-2b8b1aae3740a05cb7179be9c7d5af30e8362c3cba0b07bc18fa32ff1a2232cc.json new file mode 100644 index 000000000..d4b4d31a5 --- /dev/null +++ b/.sqlx/query-2b8b1aae3740a05cb7179be9c7d5af30e8362c3cba0b07bc18fa32ff1a2232cc.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT releases.files\n FROM releases\n INNER JOIN crates ON crates.id = releases.crate_id\n WHERE crates.name = $1 AND releases.version = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "files", + "type_info": "Json" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + true + ] + }, + "hash": "2b8b1aae3740a05cb7179be9c7d5af30e8362c3cba0b07bc18fa32ff1a2232cc" +} diff --git a/.sqlx/query-ba20564dd8e17d01c5e30e429f5bd641aed9bbabb94ccc5877cd37ea1b540b5e.json b/.sqlx/query-2e8ab3494453908d26decae9f9f64741c42f9476ed8573511fe141883b86482d.json similarity index 59% rename from .sqlx/query-ba20564dd8e17d01c5e30e429f5bd641aed9bbabb94ccc5877cd37ea1b540b5e.json rename to .sqlx/query-2e8ab3494453908d26decae9f9f64741c42f9476ed8573511fe141883b86482d.json index 3d1324d1f..201281b82 100644 --- a/.sqlx/query-ba20564dd8e17d01c5e30e429f5bd641aed9bbabb94ccc5877cd37ea1b540b5e.json +++ b/.sqlx/query-2e8ab3494453908d26decae9f9f64741c42f9476ed8573511fe141883b86482d.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT\n crates.id AS crate_id,\n releases.id AS release_id,\n crates.name,\n releases.version,\n releases.description,\n releases.dependencies,\n releases.readme,\n releases.description_long,\n releases.release_time,\n releases.build_status,\n (\n SELECT id\n FROM builds\n WHERE\n builds.rid = releases.id AND\n builds.build_status = TRUE\n ORDER BY build_time DESC\n LIMIT 1\n ) AS latest_build_id,\n releases.rustdoc_status,\n releases.archive_storage,\n releases.repository_url,\n releases.homepage_url,\n releases.keywords,\n releases.have_examples,\n releases.target_name,\n repositories.host as \"repo_host?\",\n repositories.stars as \"repo_stars?\",\n repositories.forks as \"repo_forks?\",\n repositories.issues as \"repo_issues?\",\n repositories.name as \"repo_name?\",\n releases.is_library,\n releases.yanked,\n releases.doc_targets,\n releases.license,\n releases.documentation_url,\n releases.default_target,\n releases.doc_rustc_version,\n doc_coverage.total_items,\n doc_coverage.documented_items,\n doc_coverage.total_items_needing_examples,\n doc_coverage.items_with_examples\n FROM releases\n INNER JOIN crates ON releases.crate_id = crates.id\n LEFT JOIN doc_coverage ON doc_coverage.release_id = releases.id\n LEFT JOIN repositories ON releases.repository_id = repositories.id\n WHERE crates.name = $1 AND releases.version = $2;", + "query": "SELECT\n crates.id AS crate_id,\n releases.id AS release_id,\n crates.name,\n releases.version,\n releases.description,\n releases.dependencies,\n releases.readme,\n releases.description_long,\n releases.release_time,\n release_build_status.build_status as \"build_status!: BuildStatus\",\n (\n -- this is the latest build ID that generated content\n -- it's used to invalidate some blob storage related caches.\n SELECT id\n FROM builds\n WHERE\n builds.rid = releases.id AND\n builds.build_status = 'success'\n ORDER BY build_time DESC\n LIMIT 1\n ) AS latest_build_id,\n releases.rustdoc_status,\n releases.archive_storage,\n releases.repository_url,\n releases.homepage_url,\n releases.keywords,\n releases.have_examples,\n releases.target_name,\n repositories.host as \"repo_host?\",\n repositories.stars as \"repo_stars?\",\n repositories.forks as \"repo_forks?\",\n repositories.issues as \"repo_issues?\",\n repositories.name as \"repo_name?\",\n releases.is_library,\n releases.yanked,\n releases.doc_targets,\n releases.license,\n releases.documentation_url,\n releases.default_target,\n (\n -- we're using the rustc version here to set the correct CSS file\n -- in the metadata.\n -- So we're only interested in successful builds here.\n SELECT rustc_version\n FROM builds\n WHERE\n builds.rid = releases.id AND\n builds.build_status = 'success'\n ORDER BY builds.build_time\n DESC LIMIT 1\n ) as \"rustc_version?\",\n doc_coverage.total_items,\n doc_coverage.documented_items,\n doc_coverage.total_items_needing_examples,\n doc_coverage.items_with_examples\n FROM releases\n INNER JOIN release_build_status ON releases.id = release_build_status.id\n INNER JOIN crates ON releases.crate_id = crates.id\n LEFT JOIN doc_coverage ON doc_coverage.release_id = releases.id\n LEFT JOIN repositories ON releases.repository_id = repositories.id\n WHERE crates.name = $1 AND releases.version = $2;", "describe": { "columns": [ { @@ -50,8 +50,19 @@ }, { "ordinal": 9, - "name": "build_status", - "type_info": "Bool" + "name": "build_status!: BuildStatus", + "type_info": { + "Custom": { + "name": "build_status", + "kind": { + "Enum": [ + "in_progress", + "success", + "failure" + ] + } + } + } }, { "ordinal": 10, @@ -150,7 +161,7 @@ }, { "ordinal": 29, - "name": "doc_rustc_version", + "name": "rustc_version?", "type_info": "Varchar" }, { @@ -190,7 +201,7 @@ true, true, false, - false, + true, null, false, false, @@ -210,12 +221,12 @@ true, true, false, - false, + null, true, true, true, true ] }, - "hash": "ba20564dd8e17d01c5e30e429f5bd641aed9bbabb94ccc5877cd37ea1b540b5e" + "hash": "2e8ab3494453908d26decae9f9f64741c42f9476ed8573511fe141883b86482d" } diff --git a/.sqlx/query-340670c2844a0e5df3b5384a8c3a92c3ad35bd1484a6ef9fa02d508f65c7c3ec.json b/.sqlx/query-340670c2844a0e5df3b5384a8c3a92c3ad35bd1484a6ef9fa02d508f65c7c3ec.json deleted file mode 100644 index 55f00b3cc..000000000 --- a/.sqlx/query-340670c2844a0e5df3b5384a8c3a92c3ad35bd1484a6ef9fa02d508f65c7c3ec.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "WITH dates AS (\n -- we need this series so that days in the statistic that don't have any releases are included\n SELECT generate_series(\n CURRENT_DATE - INTERVAL '30 days',\n CURRENT_DATE - INTERVAL '1 day',\n '1 day'::interval\n )::date AS date_\n ),\n release_stats AS (\n SELECT\n release_time::date AS date_,\n COUNT(*) AS counts,\n SUM(CAST((is_library = TRUE AND build_status = FALSE) AS INT)) AS failures\n FROM\n releases\n WHERE\n release_time >= CURRENT_DATE - INTERVAL '30 days' AND\n release_time < CURRENT_DATE\n GROUP BY\n release_time::date\n )\n SELECT\n dates.date_ AS \"date!\",\n COALESCE(rs.counts, 0) AS \"counts!\",\n COALESCE(rs.failures, 0) AS \"failures!\"\n FROM\n dates\n LEFT OUTER JOIN Release_stats AS rs ON dates.date_ = rs.date_\n\n ORDER BY\n dates.date_\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "date!", - "type_info": "Date" - }, - { - "ordinal": 1, - "name": "counts!", - "type_info": "Int8" - }, - { - "ordinal": 2, - "name": "failures!", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null, - null, - null - ] - }, - "hash": "340670c2844a0e5df3b5384a8c3a92c3ad35bd1484a6ef9fa02d508f65c7c3ec" -} diff --git a/.sqlx/query-42b5d5684d3813768048b4770997087ffc214cdaf08feb7bd25fa2b3ec7618b0.json b/.sqlx/query-42b5d5684d3813768048b4770997087ffc214cdaf08feb7bd25fa2b3ec7618b0.json new file mode 100644 index 000000000..552902659 --- /dev/null +++ b/.sqlx/query-42b5d5684d3813768048b4770997087ffc214cdaf08feb7bd25fa2b3ec7618b0.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT crate_id\n FROM releases\n WHERE id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "crate_id", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Int4" + ] + }, + "nullable": [ + false + ] + }, + "hash": "42b5d5684d3813768048b4770997087ffc214cdaf08feb7bd25fa2b3ec7618b0" +} diff --git a/.sqlx/query-5ecd90db215d7bf3178869a320176af53e2e45f860edd6b2bb42ed5b45ae4efa.json b/.sqlx/query-5ecd90db215d7bf3178869a320176af53e2e45f860edd6b2bb42ed5b45ae4efa.json new file mode 100644 index 000000000..3641dd849 --- /dev/null +++ b/.sqlx/query-5ecd90db215d7bf3178869a320176af53e2e45f860edd6b2bb42ed5b45ae4efa.json @@ -0,0 +1,34 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT build_status as \"build_status!: BuildStatus\"\n FROM crates\n INNER JOIN releases ON crates.id = releases.crate_id\n INNER JOIN release_build_status ON releases.id = release_build_status.id\n WHERE crates.name = $1 AND releases.version = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "build_status!: BuildStatus", + "type_info": { + "Custom": { + "name": "build_status", + "kind": { + "Enum": [ + "in_progress", + "success", + "failure" + ] + } + } + } + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + true + ] + }, + "hash": "5ecd90db215d7bf3178869a320176af53e2e45f860edd6b2bb42ed5b45ae4efa" +} diff --git a/.sqlx/query-96817014a0af7e5946ecd00000ff08b5deaa12d85e1e0bb7bd845beafb0f2702.json b/.sqlx/query-913903768e8fd453e3246370a8147fb9828c88b1e5b8195bc0a0bcc037219923.json similarity index 53% rename from .sqlx/query-96817014a0af7e5946ecd00000ff08b5deaa12d85e1e0bb7bd845beafb0f2702.json rename to .sqlx/query-913903768e8fd453e3246370a8147fb9828c88b1e5b8195bc0a0bcc037219923.json index 6cfb1e1a8..3911e806e 100644 --- a/.sqlx/query-96817014a0af7e5946ecd00000ff08b5deaa12d85e1e0bb7bd845beafb0f2702.json +++ b/.sqlx/query-913903768e8fd453e3246370a8147fb9828c88b1e5b8195bc0a0bcc037219923.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT\n builds.id,\n builds.rustc_version,\n builds.docsrs_version,\n builds.build_status,\n builds.build_time\n FROM builds\n INNER JOIN releases ON releases.id = builds.rid\n INNER JOIN crates ON releases.crate_id = crates.id\n WHERE crates.name = $1 AND releases.version = $2\n ORDER BY id DESC", + "query": "SELECT\n builds.id,\n builds.rustc_version,\n builds.docsrs_version,\n builds.build_status as \"build_status: BuildStatus\",\n builds.build_time\n FROM builds\n INNER JOIN releases ON releases.id = builds.rid\n INNER JOIN crates ON releases.crate_id = crates.id\n WHERE crates.name = $1 AND releases.version = $2\n ORDER BY id DESC", "describe": { "columns": [ { @@ -20,8 +20,19 @@ }, { "ordinal": 3, - "name": "build_status", - "type_info": "Bool" + "name": "build_status: BuildStatus", + "type_info": { + "Custom": { + "name": "build_status", + "kind": { + "Enum": [ + "in_progress", + "success", + "failure" + ] + } + } + } }, { "ordinal": 4, @@ -43,5 +54,5 @@ false ] }, - "hash": "96817014a0af7e5946ecd00000ff08b5deaa12d85e1e0bb7bd845beafb0f2702" + "hash": "913903768e8fd453e3246370a8147fb9828c88b1e5b8195bc0a0bcc037219923" } diff --git a/.sqlx/query-99e8ab430c8daa11932aef105538ed7d6199078f5189e41aadc5cf7256f15b93.json b/.sqlx/query-99e8ab430c8daa11932aef105538ed7d6199078f5189e41aadc5cf7256f15b93.json deleted file mode 100644 index 513f77e80..000000000 --- a/.sqlx/query-99e8ab430c8daa11932aef105538ed7d6199078f5189e41aadc5cf7256f15b93.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT\n id,\n version,\n build_status,\n yanked,\n is_library,\n rustdoc_status,\n target_name\n FROM releases\n WHERE\n releases.crate_id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int4" - }, - { - "ordinal": 1, - "name": "version", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "build_status", - "type_info": "Bool" - }, - { - "ordinal": 3, - "name": "yanked", - "type_info": "Bool" - }, - { - "ordinal": 4, - "name": "is_library", - "type_info": "Bool" - }, - { - "ordinal": 5, - "name": "rustdoc_status", - "type_info": "Bool" - }, - { - "ordinal": 6, - "name": "target_name", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [ - "Int4" - ] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - false - ] - }, - "hash": "99e8ab430c8daa11932aef105538ed7d6199078f5189e41aadc5cf7256f15b93" -} diff --git a/.sqlx/query-9f1181d51fde9d63ffafbf397e5a5af66cdd3764872cd5a3a401ffd5ca075de8.json b/.sqlx/query-9f1181d51fde9d63ffafbf397e5a5af66cdd3764872cd5a3a401ffd5ca075de8.json deleted file mode 100644 index 60cabbe94..000000000 --- a/.sqlx/query-9f1181d51fde9d63ffafbf397e5a5af66cdd3764872cd5a3a401ffd5ca075de8.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT crates.name,\n releases.version,\n releases.description,\n releases.target_name,\n releases.rustdoc_status,\n releases.files,\n releases.default_target,\n releases.doc_targets,\n releases.yanked,\n releases.doc_rustc_version\n FROM releases\n INNER JOIN crates ON crates.id = releases.crate_id\n WHERE crates.name = $1 AND releases.version = $2", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "name", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "version", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "description", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "target_name", - "type_info": "Varchar" - }, - { - "ordinal": 4, - "name": "rustdoc_status", - "type_info": "Bool" - }, - { - "ordinal": 5, - "name": "files", - "type_info": "Json" - }, - { - "ordinal": 6, - "name": "default_target", - "type_info": "Varchar" - }, - { - "ordinal": 7, - "name": "doc_targets", - "type_info": "Json" - }, - { - "ordinal": 8, - "name": "yanked", - "type_info": "Bool" - }, - { - "ordinal": 9, - "name": "doc_rustc_version", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [ - "Text", - "Text" - ] - }, - "nullable": [ - false, - false, - true, - false, - false, - true, - false, - false, - false, - false - ] - }, - "hash": "9f1181d51fde9d63ffafbf397e5a5af66cdd3764872cd5a3a401ffd5ca075de8" -} diff --git a/.sqlx/query-a7cbfecb1e270232061de05fa4a53f926d9e289dcb4b03cba3e3eeebce74dfe0.json b/.sqlx/query-b29681d013e952b90a2fe01db50da562d6ef7ab5ab19c6c992235db5bd931e8c.json similarity index 52% rename from .sqlx/query-a7cbfecb1e270232061de05fa4a53f926d9e289dcb4b03cba3e3eeebce74dfe0.json rename to .sqlx/query-b29681d013e952b90a2fe01db50da562d6ef7ab5ab19c6c992235db5bd931e8c.json index 469fc6e1e..6d50f853f 100644 --- a/.sqlx/query-a7cbfecb1e270232061de05fa4a53f926d9e289dcb4b03cba3e3eeebce74dfe0.json +++ b/.sqlx/query-b29681d013e952b90a2fe01db50da562d6ef7ab5ab19c6c992235db5bd931e8c.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT\n builds.rustc_version,\n builds.docsrs_version,\n builds.build_status,\n builds.build_time,\n builds.output,\n releases.default_target\n FROM builds\n INNER JOIN releases ON releases.id = builds.rid\n INNER JOIN crates ON releases.crate_id = crates.id\n WHERE builds.id = $1 AND crates.name = $2 AND releases.version = $3", + "query": "SELECT\n builds.rustc_version,\n builds.docsrs_version,\n builds.build_status as \"build_status: BuildStatus\",\n builds.build_time,\n builds.output,\n releases.default_target\n FROM builds\n INNER JOIN releases ON releases.id = builds.rid\n INNER JOIN crates ON releases.crate_id = crates.id\n WHERE builds.id = $1 AND crates.name = $2 AND releases.version = $3", "describe": { "columns": [ { @@ -15,8 +15,19 @@ }, { "ordinal": 2, - "name": "build_status", - "type_info": "Bool" + "name": "build_status: BuildStatus", + "type_info": { + "Custom": { + "name": "build_status", + "kind": { + "Enum": [ + "in_progress", + "success", + "failure" + ] + } + } + } }, { "ordinal": 3, @@ -50,5 +61,5 @@ false ] }, - "hash": "a7cbfecb1e270232061de05fa4a53f926d9e289dcb4b03cba3e3eeebce74dfe0" + "hash": "b29681d013e952b90a2fe01db50da562d6ef7ab5ab19c6c992235db5bd931e8c" } diff --git a/.sqlx/query-b823051d59855fc332ad37ecbcd015344ee7f1d6add8cdeb3b71aee4dfd95eba.json b/.sqlx/query-b823051d59855fc332ad37ecbcd015344ee7f1d6add8cdeb3b71aee4dfd95eba.json new file mode 100644 index 000000000..728b0b892 --- /dev/null +++ b/.sqlx/query-b823051d59855fc332ad37ecbcd015344ee7f1d6add8cdeb3b71aee4dfd95eba.json @@ -0,0 +1,32 @@ +{ + "db_name": "PostgreSQL", + "query": "WITH dates AS (\n -- we need this series so that days in the statistic that don't have any releases are included\n SELECT generate_series(\n CURRENT_DATE - INTERVAL '30 days',\n CURRENT_DATE - INTERVAL '1 day',\n '1 day'::interval\n )::date AS date_\n ),\n release_stats AS (\n SELECT\n release_time::date AS date_,\n SUM(CAST(\n release_build_status.build_status != 'in_progress' AS INT\n )) AS counts,\n SUM(CAST((\n is_library = TRUE AND\n release_build_status.build_status = 'failure'\n ) AS INT)) AS failures\n FROM releases\n INNER JOIN release_build_status ON releases.id = release_build_status.id\n\n WHERE\n release_time >= CURRENT_DATE - INTERVAL '30 days' AND\n release_time < CURRENT_DATE\n GROUP BY\n release_time::date\n )\n SELECT\n dates.date_ AS \"date!\",\n COALESCE(rs.counts, 0) AS \"counts!\",\n COALESCE(rs.failures, 0) AS \"failures!\"\n FROM\n dates\n LEFT OUTER JOIN Release_stats AS rs ON dates.date_ = rs.date_\n\n ORDER BY\n dates.date_\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "date!", + "type_info": "Date" + }, + { + "ordinal": 1, + "name": "counts!", + "type_info": "Int8" + }, + { + "ordinal": 2, + "name": "failures!", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null, + null, + null + ] + }, + "hash": "b823051d59855fc332ad37ecbcd015344ee7f1d6add8cdeb3b71aee4dfd95eba" +} diff --git a/.sqlx/query-5d8e187d604de870d347be77abae3a272114732644975e9dbf396f5ffe689f6b.json b/.sqlx/query-d471c22a41a040f720ac678b085265f616eef21a09ffaaa243fc26f107ad6c87.json similarity index 72% rename from .sqlx/query-5d8e187d604de870d347be77abae3a272114732644975e9dbf396f5ffe689f6b.json rename to .sqlx/query-d471c22a41a040f720ac678b085265f616eef21a09ffaaa243fc26f107ad6c87.json index 9e4817ed6..f67066cdb 100644 --- a/.sqlx/query-5d8e187d604de870d347be77abae3a272114732644975e9dbf396f5ffe689f6b.json +++ b/.sqlx/query-d471c22a41a040f720ac678b085265f616eef21a09ffaaa243fc26f107ad6c87.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT\n crates.name,\n releases.version,\n releases.description,\n releases.target_name,\n releases.rustdoc_status,\n releases.default_target,\n releases.doc_targets,\n releases.yanked,\n releases.doc_rustc_version\n FROM releases\n INNER JOIN crates ON crates.id = releases.crate_id\n WHERE crates.name = $1 AND releases.version = $2", + "query": "SELECT\n crates.name,\n releases.version,\n releases.description,\n releases.target_name,\n releases.rustdoc_status,\n releases.default_target,\n releases.doc_targets,\n releases.yanked,\n builds.rustc_version as \"rustc_version?\"\n FROM releases\n INNER JOIN crates ON crates.id = releases.crate_id\n LEFT JOIN LATERAL (\n SELECT * FROM builds\n WHERE builds.rid = releases.id\n ORDER BY builds.build_time\n DESC LIMIT 1\n ) AS builds ON true\n WHERE crates.name = $1 AND releases.version = $2", "describe": { "columns": [ { @@ -45,7 +45,7 @@ }, { "ordinal": 8, - "name": "doc_rustc_version", + "name": "rustc_version?", "type_info": "Varchar" } ], @@ -67,5 +67,5 @@ false ] }, - "hash": "5d8e187d604de870d347be77abae3a272114732644975e9dbf396f5ffe689f6b" + "hash": "d471c22a41a040f720ac678b085265f616eef21a09ffaaa243fc26f107ad6c87" } diff --git a/.sqlx/query-d672be5ae066efe9678be495580f63660684cdb61af270631fe29dfca2ed466c.json b/.sqlx/query-d672be5ae066efe9678be495580f63660684cdb61af270631fe29dfca2ed466c.json deleted file mode 100644 index 5a4059bfb..000000000 --- a/.sqlx/query-d672be5ae066efe9678be495580f63660684cdb61af270631fe29dfca2ed466c.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO releases (\n crate_id, version, release_time,\n dependencies, target_name, yanked, build_status,\n rustdoc_status, test_status, license, repository_url,\n homepage_url, description, description_long, readme,\n keywords, have_examples, downloads, files,\n doc_targets, is_library, doc_rustc_version,\n documentation_url, default_target, features,\n repository_id, archive_storage\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9,\n $10, $11, $12, $13, $14, $15, $16, $17, $18,\n $19, $20, $21, $22, $23, $24, $25, $26, $27\n )\n ON CONFLICT (crate_id, version) DO UPDATE\n SET release_time = $3,\n dependencies = $4,\n target_name = $5,\n yanked = $6,\n build_status = $7,\n rustdoc_status = $8,\n test_status = $9,\n license = $10,\n repository_url = $11,\n homepage_url = $12,\n description = $13,\n description_long = $14,\n readme = $15,\n keywords = $16,\n have_examples = $17,\n downloads = $18,\n files = $19,\n doc_targets = $20,\n is_library = $21,\n doc_rustc_version = $22,\n documentation_url = $23,\n default_target = $24,\n features = $25,\n repository_id = $26,\n archive_storage = $27\n RETURNING id", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int4" - } - ], - "parameters": { - "Left": [ - "Int4", - "Varchar", - "Timestamptz", - "Json", - "Varchar", - "Bool", - "Bool", - "Bool", - "Bool", - "Varchar", - "Varchar", - "Varchar", - "Varchar", - "Varchar", - "Varchar", - "Json", - "Bool", - "Int4", - "Json", - "Json", - "Bool", - "Varchar", - "Varchar", - "Varchar", - { - "Custom": { - "name": "_feature", - "kind": { - "Array": { - "Custom": { - "name": "feature", - "kind": { - "Composite": [ - [ - "name", - "Text" - ], - [ - "subfeatures", - "TextArray" - ] - ] - } - } - } - } - } - }, - "Int4", - "Bool" - ] - }, - "nullable": [ - false - ] - }, - "hash": "d672be5ae066efe9678be495580f63660684cdb61af270631fe29dfca2ed466c" -} diff --git a/.sqlx/query-d896b69c6f6061b0652862e2baa958d5cee193e28ec6532bc1b1fbb98cfc3f16.json b/.sqlx/query-d896b69c6f6061b0652862e2baa958d5cee193e28ec6532bc1b1fbb98cfc3f16.json new file mode 100644 index 000000000..35d0d9706 --- /dev/null +++ b/.sqlx/query-d896b69c6f6061b0652862e2baa958d5cee193e28ec6532bc1b1fbb98cfc3f16.json @@ -0,0 +1,69 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO releases (\n crate_id, version, release_time,\n dependencies, target_name, yanked,\n rustdoc_status, test_status, license, repository_url,\n homepage_url, description, description_long, readme,\n keywords, have_examples, downloads, files,\n doc_targets, is_library,\n documentation_url, default_target, features,\n repository_id, archive_storage\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9,\n $10, $11, $12, $13, $14, $15, $16, $17, $18,\n $19, $20, $21, $22, $23, $24, $25\n )\n ON CONFLICT (crate_id, version) DO UPDATE\n SET release_time = $3,\n dependencies = $4,\n target_name = $5,\n yanked = $6,\n rustdoc_status = $7,\n test_status = $8,\n license = $9,\n repository_url = $10,\n homepage_url = $11,\n description = $12,\n description_long = $13,\n readme = $14,\n keywords = $15,\n have_examples = $16,\n downloads = $17,\n files = $18,\n doc_targets = $19,\n is_library = $20,\n documentation_url = $21,\n default_target = $22,\n features = $23,\n repository_id = $24,\n archive_storage = $25\n RETURNING id", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Int4", + "Varchar", + "Timestamptz", + "Json", + "Varchar", + "Bool", + "Bool", + "Bool", + "Varchar", + "Varchar", + "Varchar", + "Varchar", + "Varchar", + "Varchar", + "Json", + "Bool", + "Int4", + "Json", + "Json", + "Bool", + "Varchar", + "Varchar", + { + "Custom": { + "name": "_feature", + "kind": { + "Array": { + "Custom": { + "name": "feature", + "kind": { + "Composite": [ + [ + "name", + "Text" + ], + [ + "subfeatures", + "TextArray" + ] + ] + } + } + } + } + } + }, + "Int4", + "Bool" + ] + }, + "nullable": [ + false + ] + }, + "hash": "d896b69c6f6061b0652862e2baa958d5cee193e28ec6532bc1b1fbb98cfc3f16" +} diff --git a/.sqlx/query-d9f0166f4e46d6a601f0668606e1487eab21d8900c096f7e7e4629c210b3a371.json b/.sqlx/query-d9f0166f4e46d6a601f0668606e1487eab21d8900c096f7e7e4629c210b3a371.json index dd273b7db..624b78abb 100644 --- a/.sqlx/query-d9f0166f4e46d6a601f0668606e1487eab21d8900c096f7e7e4629c210b3a371.json +++ b/.sqlx/query-d9f0166f4e46d6a601f0668606e1487eab21d8900c096f7e7e4629c210b3a371.json @@ -14,7 +14,18 @@ "Int4", "Varchar", "Varchar", - "Bool", + { + "Custom": { + "name": "build_status", + "kind": { + "Enum": [ + "in_progress", + "success", + "failure" + ] + } + } + }, "Text" ] }, diff --git a/.sqlx/query-e8f8c3649576bcba99393a2e78c9f892efba0a451bb14fff77876ca250b0fab1.json b/.sqlx/query-e8f8c3649576bcba99393a2e78c9f892efba0a451bb14fff77876ca250b0fab1.json new file mode 100644 index 000000000..319634d31 --- /dev/null +++ b/.sqlx/query-e8f8c3649576bcba99393a2e78c9f892efba0a451bb14fff77876ca250b0fab1.json @@ -0,0 +1,69 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n releases.id,\n releases.version,\n release_build_status.build_status as \"build_status!: BuildStatus\",\n releases.yanked,\n releases.is_library,\n releases.rustdoc_status,\n releases.target_name\n FROM releases\n INNER JOIN release_build_status ON releases.id = release_build_status.id\n WHERE\n releases.crate_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "version", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "build_status!: BuildStatus", + "type_info": { + "Custom": { + "name": "build_status", + "kind": { + "Enum": [ + "in_progress", + "success", + "failure" + ] + } + } + } + }, + { + "ordinal": 3, + "name": "yanked", + "type_info": "Bool" + }, + { + "ordinal": 4, + "name": "is_library", + "type_info": "Bool" + }, + { + "ordinal": 5, + "name": "rustdoc_status", + "type_info": "Bool" + }, + { + "ordinal": 6, + "name": "target_name", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Int4" + ] + }, + "nullable": [ + false, + false, + true, + false, + false, + false, + false + ] + }, + "hash": "e8f8c3649576bcba99393a2e78c9f892efba0a451bb14fff77876ca250b0fab1" +} diff --git a/migrations/20240221104457_drop_releases_build_status.down.sql b/migrations/20240221104457_drop_releases_build_status.down.sql new file mode 100644 index 000000000..393aa0acf --- /dev/null +++ b/migrations/20240221104457_drop_releases_build_status.down.sql @@ -0,0 +1,9 @@ +ALTER TABLE releases ADD COLUMN build_status BOOL; + +UPDATE releases SET build_status = ( + SELECT builds.build_status + FROM builds + WHERE builds.rid = releases.id + ORDER BY builds.build_time DESC + LIMIT 1 +); diff --git a/migrations/20240221104457_drop_releases_build_status.up.sql b/migrations/20240221104457_drop_releases_build_status.up.sql new file mode 100644 index 000000000..bf5910b3d --- /dev/null +++ b/migrations/20240221104457_drop_releases_build_status.up.sql @@ -0,0 +1 @@ +ALTER TABLE releases DROP COLUMN build_status; diff --git a/migrations/20240221113734_drop_releases_rustc_version.down.sql b/migrations/20240221113734_drop_releases_rustc_version.down.sql new file mode 100644 index 000000000..01ab85057 --- /dev/null +++ b/migrations/20240221113734_drop_releases_rustc_version.down.sql @@ -0,0 +1,11 @@ +ALTER TABLE releases ADD COLUMN doc_rustc_version VARCHAR(100); + +UPDATE releases SET doc_rustc_version = ( + SELECT builds.rustc_version + FROM builds + WHERE builds.rid = releases.id + ORDER BY builds.build_time DESC + LIMIT 1 +); + +ALTER TABLE releases ALTER COLUMN doc_rustc_version SET NOT NULL; diff --git a/migrations/20240221113734_drop_releases_rustc_version.up.sql b/migrations/20240221113734_drop_releases_rustc_version.up.sql new file mode 100644 index 000000000..f86fbb02f --- /dev/null +++ b/migrations/20240221113734_drop_releases_rustc_version.up.sql @@ -0,0 +1 @@ +ALTER TABLE releases DROP COLUMN doc_rustc_version; diff --git a/migrations/20240221114302_ensure_no_buildless_releases_exist.down.sql b/migrations/20240221114302_ensure_no_buildless_releases_exist.down.sql new file mode 100644 index 000000000..098fd994b --- /dev/null +++ b/migrations/20240221114302_ensure_no_buildless_releases_exist.down.sql @@ -0,0 +1,19 @@ +DELETE FROM compression_rels WHERE NOT EXISTS ( + SELECT 1 + FROM releases + INNER JOIN builds ON builds.rid = releases.id + WHERE releases.id = compression_rels.release +); + +DELETE FROM keyword_rels WHERE NOT EXISTS ( + SELECT 1 + FROM releases + INNER JOIN builds ON builds.rid = releases.id + WHERE releases.id = keyword_rels.rid +); + +DELETE FROM releases WHERE NOT EXISTS ( + SELECT * + FROM builds + WHERE builds.rid = releases.id +); diff --git a/migrations/20240221114302_ensure_no_buildless_releases_exist.up.sql b/migrations/20240221114302_ensure_no_buildless_releases_exist.up.sql new file mode 100644 index 000000000..6eaf10e16 --- /dev/null +++ b/migrations/20240221114302_ensure_no_buildless_releases_exist.up.sql @@ -0,0 +1 @@ +-- no up-migration, we only have a down-migration as validation diff --git a/migrations/20240221124844_multi_stage_build_status.down.sql b/migrations/20240221124844_multi_stage_build_status.down.sql new file mode 100644 index 000000000..20e57bb0b --- /dev/null +++ b/migrations/20240221124844_multi_stage_build_status.down.sql @@ -0,0 +1,9 @@ +LOCK builds, releases; + +DELETE FROM builds WHERE build_status = 'in_progress'; + +ALTER TABLE builds ALTER build_status +TYPE BOOL +USING build_status = 'success'; + +DROP TYPE BUILD_STATUS; diff --git a/migrations/20240221124844_multi_stage_build_status.up.sql b/migrations/20240221124844_multi_stage_build_status.up.sql new file mode 100644 index 000000000..b5b733032 --- /dev/null +++ b/migrations/20240221124844_multi_stage_build_status.up.sql @@ -0,0 +1,13 @@ +CREATE TYPE build_status AS ENUM ( + 'in_progress', + 'success', + 'failure' +); + +ALTER TABLE builds ALTER build_status +TYPE build_status +USING CASE + WHEN build_status + THEN 'success'::build_status + ELSE 'failure'::build_status +END; diff --git a/migrations/20240309082057_release_status_view.sql.down.sql b/migrations/20240309082057_release_status_view.sql.down.sql new file mode 100644 index 000000000..ff27e9eef --- /dev/null +++ b/migrations/20240309082057_release_status_view.sql.down.sql @@ -0,0 +1 @@ +DROP VIEW release_build_status; diff --git a/migrations/20240309082057_release_status_view.sql.up.sql b/migrations/20240309082057_release_status_view.sql.up.sql new file mode 100644 index 000000000..ac2ceb025 --- /dev/null +++ b/migrations/20240309082057_release_status_view.sql.up.sql @@ -0,0 +1,22 @@ +CREATE OR REPLACE VIEW release_build_status AS ( + SELECT + summary.id, + summary.last_build_time, + CASE + WHEN summary.success_count > 0 THEN 'success'::build_status + WHEN summary.failure_count > 0 THEN 'failure'::build_status + ELSE 'in_progress'::build_status + END as build_status + + FROM ( + SELECT + r.id, + MAX(b.build_time) as last_build_time, + SUM(CASE WHEN b.build_status = 'success' THEN 1 ELSE 0 END) as success_count, + SUM(CASE WHEN b.build_status = 'failure' THEN 1 ELSE 0 END) as failure_count + FROM + releases as r + LEFT OUTER JOIN builds AS b on b.rid = r.id + GROUP BY r.id + ) as summary +); diff --git a/src/db/add_package.rs b/src/db/add_package.rs index d83c2b901..6fcfaaacf 100644 --- a/src/db/add_package.rs +++ b/src/db/add_package.rs @@ -1,6 +1,6 @@ use crate::{ - db::types::Feature, - docbuilder::{BuildResult, DocCoverage}, + db::types::{BuildStatus, Feature}, + docbuilder::DocCoverage, error::Result, registry_api::{CrateData, CrateOwner, ReleaseData}, storage::CompressionAlgorithm, @@ -30,7 +30,6 @@ pub(crate) async fn add_package_into_database( conn: &mut sqlx::PgConnection, metadata_pkg: &MetadataPackage, source_dir: &Path, - res: &BuildResult, default_target: &str, source_files: Value, doc_targets: Vec, @@ -52,45 +51,43 @@ pub(crate) async fn add_package_into_database( let release_id: i32 = sqlx::query_scalar!( "INSERT INTO releases ( crate_id, version, release_time, - dependencies, target_name, yanked, build_status, + dependencies, target_name, yanked, rustdoc_status, test_status, license, repository_url, homepage_url, description, description_long, readme, keywords, have_examples, downloads, files, - doc_targets, is_library, doc_rustc_version, + doc_targets, is_library, documentation_url, default_target, features, repository_id, archive_storage ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, - $19, $20, $21, $22, $23, $24, $25, $26, $27 + $19, $20, $21, $22, $23, $24, $25 ) ON CONFLICT (crate_id, version) DO UPDATE SET release_time = $3, dependencies = $4, target_name = $5, yanked = $6, - build_status = $7, - rustdoc_status = $8, - test_status = $9, - license = $10, - repository_url = $11, - homepage_url = $12, - description = $13, - description_long = $14, - readme = $15, - keywords = $16, - have_examples = $17, - downloads = $18, - files = $19, - doc_targets = $20, - is_library = $21, - doc_rustc_version = $22, - documentation_url = $23, - default_target = $24, - features = $25, - repository_id = $26, - archive_storage = $27 + rustdoc_status = $7, + test_status = $8, + license = $9, + repository_url = $10, + homepage_url = $11, + description = $12, + description_long = $13, + readme = $14, + keywords = $15, + have_examples = $16, + downloads = $17, + files = $18, + doc_targets = $19, + is_library = $20, + documentation_url = $21, + default_target = $22, + features = $23, + repository_id = $24, + archive_storage = $25 RETURNING id", crate_id, &metadata_pkg.version, @@ -98,7 +95,6 @@ pub(crate) async fn add_package_into_database( serde_json::to_value(dependencies)?, metadata_pkg.package_name(), registry_data.yanked, - res.successful, has_docs, false, // TODO: Add test status somehow metadata_pkg.license, @@ -113,7 +109,6 @@ pub(crate) async fn add_package_into_database( source_files, serde_json::to_value(doc_targets)?, is_library, - &res.rustc_version, metadata_pkg.documentation, default_target, features as Vec, @@ -149,6 +144,17 @@ pub async fn update_latest_version_id(conn: &mut sqlx::PgConnection, crate_id: i Ok(()) } +async fn crate_id_from_release_id(conn: &mut sqlx::PgConnection, release_id: i32) -> Result { + Ok(sqlx::query_scalar!( + "SELECT crate_id + FROM releases + WHERE id = $1", + release_id, + ) + .fetch_one(&mut *conn) + .await?) +} + pub(crate) async fn add_doc_coverage( conn: &mut sqlx::PgConnection, release_id: i32, @@ -182,22 +188,32 @@ pub(crate) async fn add_doc_coverage( pub(crate) async fn add_build_into_database( conn: &mut sqlx::PgConnection, release_id: i32, - res: &BuildResult, + rustc_version: &str, + docsrs_version: &str, + build_status: BuildStatus, ) -> Result { debug!("Adding build into database"); let hostname = hostname::get()?; - Ok(sqlx::query_scalar!( + + let build_id = sqlx::query_scalar!( "INSERT INTO builds (rid, rustc_version, docsrs_version, build_status, build_server) VALUES ($1, $2, $3, $4, $5) RETURNING id", release_id, - res.rustc_version, - res.docsrs_version, - res.successful, + rustc_version, + docsrs_version, + build_status as BuildStatus, hostname.to_str().unwrap_or(""), ) .fetch_one(&mut *conn) - .await?) + .await?; + + let crate_id = crate_id_from_release_id(&mut *conn, release_id).await?; + update_latest_version_id(&mut *conn, crate_id) + .await + .context("couldn't update latest version id")?; + + Ok(build_id) } async fn initialize_package_in_database( diff --git a/src/db/types.rs b/src/db/types.rs index 7936ce807..17140c595 100644 --- a/src/db/types.rs +++ b/src/db/types.rs @@ -1,5 +1,5 @@ use postgres_types::{FromSql, ToSql}; -use serde::Serialize; +use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, FromSql, ToSql, sqlx::Type)] #[postgres(name = "feature")] @@ -23,3 +23,42 @@ impl sqlx::postgres::PgHasArrayType for Feature { sqlx::postgres::PgTypeInfo::with_name("_feature") } } + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, sqlx::Type)] +#[sqlx(type_name = "build_status", rename_all = "snake_case")] +#[serde(rename_all = "snake_case")] +pub(crate) enum BuildStatus { + Success, + Failure, + InProgress, +} + +impl BuildStatus { + pub(crate) fn is_success(&self) -> bool { + matches!(self, BuildStatus::Success) + } +} + +impl sqlx::postgres::PgHasArrayType for BuildStatus { + fn array_type_info() -> sqlx::postgres::PgTypeInfo { + sqlx::postgres::PgTypeInfo::with_name("_build_status") + } +} + +#[cfg(test)] +mod tests { + use super::*; + use test_case::test_case; + + #[test_case(BuildStatus::Success, "success")] + #[test_case(BuildStatus::Failure, "failure")] + #[test_case(BuildStatus::InProgress, "in_progress")] + fn test_build_status_serialization(status: BuildStatus, expected: &str) { + let serialized = serde_json::to_string(&status).unwrap(); + assert_eq!(serialized, format!("\"{}\"", expected)); + assert_eq!( + serde_json::from_str::(&serialized).unwrap(), + status + ); + } +} diff --git a/src/docbuilder/mod.rs b/src/docbuilder/mod.rs index 2378b3c36..9328c2203 100644 --- a/src/docbuilder/mod.rs +++ b/src/docbuilder/mod.rs @@ -2,5 +2,5 @@ mod limits; mod rustwide_builder; pub(crate) use self::limits::Limits; -pub(crate) use self::rustwide_builder::{BuildResult, DocCoverage}; +pub(crate) use self::rustwide_builder::DocCoverage; pub use self::rustwide_builder::{PackageKind, RustwideBuilder}; diff --git a/src/docbuilder/rustwide_builder.rs b/src/docbuilder/rustwide_builder.rs index 2fbe248e4..55f21c98f 100644 --- a/src/docbuilder/rustwide_builder.rs +++ b/src/docbuilder/rustwide_builder.rs @@ -1,7 +1,7 @@ use crate::db::file::add_path_into_database; use crate::db::{ add_build_into_database, add_doc_coverage, add_package_into_database, - add_path_into_remote_archive, update_crate_data_in_database, Pool, + add_path_into_remote_archive, types::BuildStatus, update_crate_data_in_database, Pool, }; use crate::docbuilder::Limits; use crate::error::Result; @@ -563,7 +563,6 @@ impl RustwideBuilder { &mut async_conn, cargo_metadata, &build.host_source_dir(), - &res.result, &res.target, files_list, successful_targets, @@ -583,11 +582,19 @@ impl RustwideBuilder { ))?; } + let build_status = if res.result.successful { + BuildStatus::Success + } else { + BuildStatus::Failure + }; let build_id = self.runtime.block_on(add_build_into_database( &mut async_conn, release_id, - &res.result, + &res.result.rustc_version, + &res.result.docsrs_version, + build_status, ))?; + let build_log_path = format!("build-logs/{build_id}/{default_target}.txt"); self.storage.store_one(build_log_path, res.build_log)?; for (target, log) in target_build_logs { diff --git a/src/test/fakes.rs b/src/test/fakes.rs index 2b465f967..3d33d2444 100644 --- a/src/test/fakes.rs +++ b/src/test/fakes.rs @@ -1,6 +1,7 @@ use super::TestDatabase; -use crate::docbuilder::{BuildResult, DocCoverage}; +use crate::db::types::BuildStatus; +use crate::docbuilder::DocCoverage; use crate::error::Result; use crate::registry_api::{CrateData, CrateOwner, ReleaseData}; use crate::storage::{ @@ -22,7 +23,7 @@ pub(crate) struct FakeRelease<'a> { storage: Arc, runtime: Arc, package: MetadataPackage, - builds: Vec, + builds: Option>, /// name, content source_files: Vec<(&'a str, &'a [u8])>, /// name, content @@ -45,7 +46,9 @@ pub(crate) struct FakeBuild { s3_build_log: Option, other_build_logs: HashMap, db_build_log: Option, - result: BuildResult, + rustc_version: String, + docsrs_version: String, + build_status: BuildStatus, } const DEFAULT_CONTENT: &[u8] = @@ -90,7 +93,7 @@ impl<'a> FakeRelease<'a> { .cloned() .collect::>>(), }, - builds: vec![], + builds: None, source_files: Vec::new(), rustdoc_files: Vec::new(), doc_targets: Vec::new(), @@ -147,20 +150,31 @@ impl<'a> FakeRelease<'a> { // TODO: How should `has_docs` actually be handled? pub(crate) fn build_result_failed(self) -> Self { assert!( - self.builds.is_empty(), + self.builds.is_none(), "cannot use custom builds with build_result_failed" ); Self { has_docs: false, - builds: vec![FakeBuild::default().successful(false)], + builds: Some(vec![FakeBuild::default().successful(false)]), ..self } } pub(crate) fn builds(self, builds: Vec) -> Self { - assert!(self.builds.is_empty()); + assert!(self.builds.is_none()); assert!(!builds.is_empty()); - Self { builds, ..self } + Self { + builds: Some(builds), + ..self + } + } + + pub(crate) fn no_builds(self) -> Self { + assert!(self.builds.is_none()); + Self { + builds: Some(vec![]), + ..self + } } pub(crate) fn yanked(mut self, new: bool) -> Self { @@ -281,7 +295,7 @@ impl<'a> FakeRelease<'a> { } /// Returns the release_id - pub(crate) async fn create_async(mut self) -> Result { + pub(crate) async fn create_async(self) -> Result { use std::fs; use std::path::Path; @@ -417,12 +431,9 @@ impl<'a> FakeRelease<'a> { debug!("added source files {}", source_meta); // If the test didn't add custom builds, inject a default one - if self.builds.is_empty() { - self.builds.push(FakeBuild::default()); - } - let last_build_result = &self.builds.last().unwrap().result; + let builds = self.builds.unwrap_or_else(|| vec![FakeBuild::default()]); - if last_build_result.successful { + if builds.last().map(|b| b.build_status) == Some(BuildStatus::Success) { let index = [&package.name, "index.html"].join("/"); if package.is_library() && !rustdoc_files.iter().any(|(path, _)| path == &index) { rustdoc_files.push((&index, DEFAULT_CONTENT)); @@ -477,7 +488,6 @@ impl<'a> FakeRelease<'a> { &mut async_conn, &package, crate_dir, - last_build_result, default_target, source_meta, self.doc_targets, @@ -495,7 +505,7 @@ impl<'a> FakeRelease<'a> { &self.registry_crate_data, ) .await?; - for build in &self.builds { + for build in builds { build .create(&mut async_conn, &storage, release_id, default_target) .await?; @@ -537,20 +547,14 @@ impl FakeGithubStats { impl FakeBuild { pub(crate) fn rustc_version(self, rustc_version: impl Into) -> Self { Self { - result: BuildResult { - rustc_version: rustc_version.into(), - ..self.result - }, + rustc_version: rustc_version.into(), ..self } } pub(crate) fn docsrs_version(self, docsrs_version: impl Into) -> Self { Self { - result: BuildResult { - docsrs_version: docsrs_version.into(), - ..self.result - }, + docsrs_version: docsrs_version.into(), ..self } } @@ -587,11 +591,16 @@ impl FakeBuild { } pub(crate) fn successful(self, successful: bool) -> Self { + self.build_status(if successful { + BuildStatus::Success + } else { + BuildStatus::Failure + }) + } + + pub(crate) fn build_status(self, build_status: BuildStatus) -> Self { Self { - result: BuildResult { - successful, - ..self.result - }, + build_status, ..self } } @@ -603,8 +612,14 @@ impl FakeBuild { release_id: i32, default_target: &str, ) -> Result<()> { - let build_id = - crate::db::add_build_into_database(&mut *conn, release_id, &self.result).await?; + let build_id = crate::db::add_build_into_database( + &mut *conn, + release_id, + &self.rustc_version, + &self.docsrs_version, + self.build_status, + ) + .await?; if let Some(db_build_log) = self.db_build_log.as_deref() { sqlx::query!( @@ -641,11 +656,9 @@ impl Default for FakeBuild { s3_build_log: Some("It works!".into()), db_build_log: None, other_build_logs: HashMap::new(), - result: BuildResult { - rustc_version: "rustc 2.0.0-nightly (000000000 1970-01-01)".into(), - docsrs_version: "docs.rs 1.0.0 (000000000 1970-01-01)".into(), - successful: true, - }, + rustc_version: "rustc 2.0.0-nightly (000000000 1970-01-01)".into(), + docsrs_version: "docs.rs 1.0.0 (000000000 1970-01-01)".into(), + build_status: BuildStatus::Success, } } } diff --git a/src/web/build_details.rs b/src/web/build_details.rs index 9c2060e1e..119414f0d 100644 --- a/src/web/build_details.rs +++ b/src/web/build_details.rs @@ -1,4 +1,5 @@ use crate::{ + db::types::BuildStatus, impl_axum_webpage, web::{ error::{AxumNope, AxumResult}, @@ -21,7 +22,7 @@ pub(crate) struct BuildDetails { id: i32, rustc_version: String, docsrs_version: String, - build_status: bool, + build_status: BuildStatus, build_time: DateTime, output: String, } @@ -56,17 +57,17 @@ pub(crate) async fn build_details_handler( let id: i32 = params.id.parse().map_err(|_| AxumNope::BuildNotFound)?; let row = sqlx::query!( - "SELECT + r#"SELECT builds.rustc_version, builds.docsrs_version, - builds.build_status, + builds.build_status as "build_status: BuildStatus", builds.build_time, builds.output, releases.default_target FROM builds INNER JOIN releases ON releases.id = builds.rid INNER JOIN crates ON releases.crate_id = crates.id - WHERE builds.id = $1 AND crates.name = $2 AND releases.version = $3", + WHERE builds.id = $1 AND crates.name = $2 AND releases.version = $3"#, id, params.name, params.version.to_string(), diff --git a/src/web/builds.rs b/src/web/builds.rs index 1f4621c0f..4a481fe22 100644 --- a/src/web/builds.rs +++ b/src/web/builds.rs @@ -1,5 +1,6 @@ use super::{cache::CachePolicy, error::AxumNope, headers::CanonicalUrl}; use crate::{ + db::types::BuildStatus, docbuilder::Limits, impl_axum_webpage, web::{ @@ -23,7 +24,7 @@ pub(crate) struct Build { id: i32, rustc_version: String, docsrs_version: String, - build_status: bool, + build_status: BuildStatus, build_time: DateTime, } @@ -84,7 +85,28 @@ pub(crate) async fn build_list_json_handler( Ok(( Extension(CachePolicy::NoStoreMustRevalidate), [(ACCESS_CONTROL_ALLOW_ORIGIN, "*")], - Json(get_builds(&mut conn, &name, &version).await?), + Json( + get_builds(&mut conn, &name, &version) + .await? + .iter() + .filter_map(|build| { + // for backwards compatibility in this API, we + // * filter out in-progress builds + // * convert the build status to a boolean + if build.build_status != BuildStatus::InProgress { + Some(serde_json::json!({ + "id": build.id, + "rustc_version": build.rustc_version, + "docsrs_version": build.docsrs_version, + "build_status": build.build_status.is_success(), + "build_time": build.build_time, + })) + } else { + None + } + }) + .collect::>(), + ), ) .into_response()) } @@ -96,17 +118,17 @@ async fn get_builds( ) -> Result> { Ok(sqlx::query_as!( Build, - "SELECT + r#"SELECT builds.id, builds.rustc_version, builds.docsrs_version, - builds.build_status, + builds.build_status as "build_status: BuildStatus", builds.build_time FROM builds INNER JOIN releases ON releases.id = builds.rid INNER JOIN crates ON releases.crate_id = crates.id WHERE crates.name = $1 AND releases.version = $2 - ORDER BY id DESC", + ORDER BY id DESC"#, name, version.to_string(), ) @@ -116,6 +138,7 @@ async fn get_builds( #[cfg(test)] mod tests { + use super::BuildStatus; use crate::{ test::{assert_cache_control, wrapper, FakeBuild}, web::cache::CachePolicy, @@ -182,6 +205,10 @@ mod tests { FakeBuild::default() .rustc_version("rustc (blabla 2021-01-01)") .docsrs_version("docs.rs 3.0.0"), + FakeBuild::default() + .build_status(BuildStatus::InProgress) + .rustc_version("rustc (blabla 2022-01-01)") + .docsrs_version("docs.rs 4.0.0"), ]) .create()?; @@ -189,6 +216,8 @@ mod tests { assert_cache_control(&response, CachePolicy::NoStoreMustRevalidate, &env.config()); let value: serde_json::Value = serde_json::from_str(&response.text()?)?; + assert_eq!(value.as_array().unwrap().len(), 3); + assert_eq!(value.pointer("/0/build_status"), Some(&true.into())); assert_eq!( value.pointer("/0/docsrs_version"), @@ -247,6 +276,40 @@ mod tests { }); } + #[test] + fn build_empty_list() { + wrapper(|env| { + env.fake_release() + .name("foo") + .version("0.1.0") + .no_builds() + .create()?; + + let response = env.frontend().get("/crate/foo/0.1.0/builds").send()?; + assert_cache_control(&response, CachePolicy::NoCaching, &env.config()); + let page = kuchikiki::parse_html().one(response.text()?); + + let rows: Vec<_> = page + .select("ul > li a.release") + .unwrap() + .map(|row| row.text_contents()) + .collect(); + + assert!(rows.is_empty()); + + let warning = page + .select_first(".warning") + .expect("missing warning element") + .text_contents(); + + assert!(warning.contains("has not built")); + assert!(warning.contains("queued")); + assert!(warning.contains("open an issue")); + + Ok(()) + }); + } + #[test] fn limits() { wrapper(|env| { diff --git a/src/web/crate_details.rs b/src/web/crate_details.rs index 6d45bf6d7..7e896a01e 100644 --- a/src/web/crate_details.rs +++ b/src/web/crate_details.rs @@ -2,6 +2,7 @@ use super::{markdown, match_version, MetaData}; use crate::utils::{get_correct_docsrs_style_file, report_error}; use crate::web::rustdoc::RustdocHtmlParams; use crate::{ + db::types::BuildStatus, impl_axum_webpage, storage::PathNotFoundError, web::{ @@ -30,7 +31,7 @@ use std::sync::Arc; // TODO: Add target name and versions #[derive(Debug, Clone, PartialEq, Serialize)] -pub struct CrateDetails { +pub(crate) struct CrateDetails { name: String, pub version: Version, description: Option, @@ -41,7 +42,7 @@ pub struct CrateDetails { #[serde(serialize_with = "optional_markdown")] rustdoc: Option, // this is description_long in database release_time: DateTime, - build_status: bool, + build_status: BuildStatus, pub latest_build_id: Option, last_successful_build: Option, pub rustdoc_status: bool, @@ -86,10 +87,21 @@ where } #[derive(Debug, Clone, Eq, PartialEq, Serialize)] -pub struct Release { +pub(crate) struct Release { pub id: i32, pub version: semver::Version, - pub build_status: bool, + /// Aggregated build status of the release. + /// * no builds -> build In progress + /// * any build is successful -> Success + /// -> even with failed or in-progress builds we have docs to show + /// * any build is failed -> Failure + /// -> we can only have Failure or InProgress here, so the Failure is the + /// important part on this aggregation level. + /// * the rest is all builds are in-progress -> InProgress + /// -> if we have any builds, and the previous conditions don't match, we end + /// up here, but we still check. + /// calculated in a database view : `release_build_status` + pub build_status: BuildStatus, pub yanked: bool, pub is_library: bool, pub rustdoc_status: bool, @@ -131,13 +143,15 @@ impl CrateDetails { releases.readme, releases.description_long, releases.release_time, - releases.build_status, + release_build_status.build_status as "build_status!: BuildStatus", ( + -- this is the latest build ID that generated content + -- it's used to invalidate some blob storage related caches. SELECT id FROM builds WHERE builds.rid = releases.id AND - builds.build_status = TRUE + builds.build_status = 'success' ORDER BY build_time DESC LIMIT 1 ) AS latest_build_id, @@ -159,12 +173,24 @@ impl CrateDetails { releases.license, releases.documentation_url, releases.default_target, - releases.doc_rustc_version, + ( + -- we're using the rustc version here to set the correct CSS file + -- in the metadata. + -- So we're only interested in successful builds here. + SELECT rustc_version + FROM builds + WHERE + builds.rid = releases.id AND + builds.build_status = 'success' + ORDER BY builds.build_time + DESC LIMIT 1 + ) as "rustc_version?", doc_coverage.total_items, doc_coverage.documented_items, doc_coverage.total_items_needing_examples, doc_coverage.items_with_examples FROM releases + INNER JOIN release_build_status ON releases.id = release_build_status.id INNER JOIN crates ON releases.crate_id = crates.id LEFT JOIN doc_coverage ON doc_coverage.release_id = releases.id LEFT JOIN repositories ON releases.repository_id = repositories.id @@ -196,7 +222,11 @@ impl CrateDetails { default_target: krate.default_target, doc_targets: MetaData::parse_doc_targets(krate.doc_targets), yanked: krate.yanked, - rustdoc_css_file: get_correct_docsrs_style_file(&krate.doc_rustc_version)?, + rustdoc_css_file: krate + .rustc_version + .as_deref() + .map(get_correct_docsrs_style_file) + .transpose()?, }; let mut crate_details = CrateDetails { @@ -245,11 +275,11 @@ impl CrateDetails { .try_collect() .await?; - if !crate_details.build_status { + if crate_details.build_status != BuildStatus::Success { crate_details.last_successful_build = crate_details .releases .iter() - .filter(|release| release.build_status && !release.yanked) + .filter(|release| release.build_status == BuildStatus::Success && !release.yanked) .map(|release| release.version.to_string()) .next(); } @@ -322,13 +352,16 @@ impl CrateDetails { } pub(crate) fn latest_release(releases: &[Release]) -> Option<&Release> { - if let Some(release) = releases - .iter() - .find(|release| release.version.pre.is_empty() && !release.yanked) - { + if let Some(release) = releases.iter().find(|release| { + release.version.pre.is_empty() + && !release.yanked + && release.build_status != BuildStatus::InProgress + }) { Some(release) } else { - releases.first() + releases + .iter() + .find(|release| release.build_status != BuildStatus::InProgress) } } @@ -338,43 +371,44 @@ pub(crate) async fn releases_for_crate( crate_id: i32, ) -> Result, anyhow::Error> { let mut releases: Vec = sqlx::query!( - "SELECT - id, - version, - build_status, - yanked, - is_library, - rustdoc_status, - target_name + r#"SELECT + releases.id, + releases.version, + release_build_status.build_status as "build_status!: BuildStatus", + releases.yanked, + releases.is_library, + releases.rustdoc_status, + releases.target_name FROM releases + INNER JOIN release_build_status ON releases.id = release_build_status.id WHERE - releases.crate_id = $1", + releases.crate_id = $1"#, crate_id, ) .fetch(&mut *conn) .try_filter_map(|row| async move { - Ok( - match semver::Version::parse(&row.version).with_context(|| { - format!( - "invalid semver in database for crate {crate_id}: {}", - row.version - ) - }) { - Ok(semversion) => Some(Release { - id: row.id, - version: semversion, - build_status: row.build_status, - yanked: row.yanked, - is_library: row.is_library, - rustdoc_status: row.rustdoc_status, - target_name: row.target_name, - }), - Err(err) => { - report_error(&err); - None - } - }, - ) + let semversion = match semver::Version::parse(&row.version).with_context(|| { + format!( + "invalid semver in database for crate {crate_id}: {}", + row.version + ) + }) { + Ok(semver) => semver, + Err(err) => { + report_error(&err); + return Ok(None); + } + }; + + Ok(Some(Release { + id: row.id, + version: semversion, + build_status: row.build_status, + yanked: row.yanked, + is_library: row.is_library, + rustdoc_status: row.rustdoc_status, + target_name: row.target_name, + })) }) .try_collect() .await?; @@ -632,7 +666,7 @@ pub(crate) async fn get_all_platforms_inner( format!("{}/{inner_path}", krate.name) }; - let current_target = if latest_release.build_status { + let current_target = if latest_release.build_status.is_success() { if target.is_empty() { krate.default_target } else { @@ -674,17 +708,46 @@ pub(crate) async fn get_all_platforms( #[cfg(test)] mod tests { use super::*; - use crate::registry_api::CrateOwner; use crate::test::{ assert_cache_control, assert_redirect, assert_redirect_cached, async_wrapper, wrapper, - TestDatabase, TestEnvironment, + FakeBuild, TestDatabase, TestEnvironment, }; + use crate::{db::types::BuildStatus, registry_api::CrateOwner}; use anyhow::Error; use kuchikiki::traits::TendrilSink; use reqwest::StatusCode; use semver::Version; use std::collections::HashMap; + async fn release_build_status( + conn: &mut sqlx::PgConnection, + name: &str, + version: &str, + ) -> BuildStatus { + let status = sqlx::query_scalar!( + r#" + SELECT build_status as "build_status!: BuildStatus" + FROM crates + INNER JOIN releases ON crates.id = releases.crate_id + INNER JOIN release_build_status ON releases.id = release_build_status.id + WHERE crates.name = $1 AND releases.version = $2"#, + name, + version + ) + .fetch_one(&mut *conn) + .await + .unwrap(); + + assert_eq!( + crate_details(&mut *conn, name, version, None) + .await + .build_status, + status + ); + + status + } + async fn crate_details( conn: &mut sqlx::PgConnection, name: &str, @@ -710,6 +773,7 @@ mod tests { .unwrap() } + #[fn_error_context::context("assert_last_successful_build_equals({package}, {version}, {expected_last_successful_build:?})")] async fn assert_last_successful_build_equals( db: &TestDatabase, package: &str, @@ -719,10 +783,12 @@ mod tests { let mut conn = db.async_conn().await; let details = crate_details(&mut conn, package, version, None).await; - assert_eq!( + anyhow::ensure!( + details.last_successful_build.as_deref() == expected_last_successful_build, + "didn't expect {:?}", details.last_successful_build, - expected_last_successful_build.map(|s| s.to_string()), ); + Ok(()) } @@ -890,7 +956,7 @@ mod tests { vec![ Release { version: semver::Version::parse("1.0.0")?, - build_status: true, + build_status: BuildStatus::Success, yanked: false, is_library: true, rustdoc_status: true, @@ -899,7 +965,7 @@ mod tests { }, Release { version: semver::Version::parse("0.12.0")?, - build_status: true, + build_status: BuildStatus::Success, yanked: false, is_library: true, rustdoc_status: true, @@ -908,7 +974,7 @@ mod tests { }, Release { version: semver::Version::parse("0.3.0")?, - build_status: false, + build_status: BuildStatus::Failure, yanked: false, is_library: true, rustdoc_status: false, @@ -917,7 +983,7 @@ mod tests { }, Release { version: semver::Version::parse("0.2.0")?, - build_status: true, + build_status: BuildStatus::Success, yanked: true, is_library: true, rustdoc_status: true, @@ -926,7 +992,7 @@ mod tests { }, Release { version: semver::Version::parse("0.2.0-alpha")?, - build_status: true, + build_status: BuildStatus::Success, yanked: false, is_library: true, rustdoc_status: true, @@ -935,7 +1001,7 @@ mod tests { }, Release { version: semver::Version::parse("0.1.1")?, - build_status: true, + build_status: BuildStatus::Success, yanked: false, is_library: true, rustdoc_status: true, @@ -944,7 +1010,7 @@ mod tests { }, Release { version: semver::Version::parse("0.1.0")?, - build_status: true, + build_status: BuildStatus::Success, yanked: false, is_library: true, rustdoc_status: true, @@ -953,7 +1019,7 @@ mod tests { }, Release { version: semver::Version::parse("0.0.1")?, - build_status: false, + build_status: BuildStatus::Failure, yanked: false, is_library: false, rustdoc_status: false, @@ -1104,7 +1170,36 @@ mod tests { } #[test] - fn releases_dropdowns_is_correct() { + fn test_latest_version_in_progress() { + wrapper(|env| { + let db = env.db(); + + env.fake_release().name("foo").version("0.0.1").create()?; + env.fake_release() + .name("foo") + .version("0.0.2") + .builds(vec![ + FakeBuild::default().build_status(BuildStatus::InProgress) + ]) + .create()?; + + for version in &["0.0.1", "0.0.2"] { + let details = env.runtime().block_on(async move { + let mut conn = db.async_conn().await; + crate_details(&mut conn, "foo", version, None).await + }); + assert_eq!( + details.latest_release().unwrap().version, + semver::Version::parse("0.0.1")? + ); + } + + Ok(()) + }) + } + + #[test] + fn releases_dropdowns_show_binary_warning() { wrapper(|env| { env.fake_release() .name("binary") @@ -1113,12 +1208,13 @@ mod tests { .create()?; let page = kuchikiki::parse_html() - .one(env.frontend().get("/crate/binary/0.1.0").send()?.text()?); - let warning = page.select_first("a.pure-menu-link.warn").unwrap(); + .one(env.frontend().get("/crate/binary/latest").send()?.text()?); + let link = page + .select_first("a.pure-menu-link[href='/crate/binary/0.1.0']") + .unwrap(); assert_eq!( - warning - .as_node() + link.as_node() .as_element() .unwrap() .attributes @@ -1132,6 +1228,38 @@ mod tests { }); } + #[test] + fn releases_dropdowns_show_in_progress() { + wrapper(|env| { + env.fake_release() + .name("foo") + .version("0.1.0") + .builds(vec![ + FakeBuild::default().build_status(BuildStatus::InProgress) + ]) + .create()?; + + let page = kuchikiki::parse_html() + .one(env.frontend().get("/crate/foo/latest").send()?.text()?); + let link = page + .select_first("a.pure-menu-link[href='/crate/foo/0.1.0']") + .unwrap(); + + assert_eq!( + link.as_node() + .as_element() + .unwrap() + .attributes + .borrow() + .get("title") + .unwrap(), + "foo-0.1.0 is currently being built" + ); + + Ok(()) + }); + } + #[test] fn test_updating_owners() { wrapper(|env| { @@ -1685,4 +1813,103 @@ mod tests { Ok(()) }) } + + #[test] + fn test_build_status_no_builds() { + async_wrapper(|env| async move { + env.async_fake_release() + .await + .name("dummy") + .version("0.1.0") + .create_async() + .await?; + + let mut conn = env.async_db().await.async_conn().await; + sqlx::query!("DELETE FROM builds") + .execute(&mut *conn) + .await?; + + assert_eq!( + release_build_status(&mut conn, "dummy", "0.1.0").await, + BuildStatus::InProgress + ); + + Ok(()) + }) + } + + #[test] + fn test_build_status_successful() { + async_wrapper(|env| async move { + env.async_fake_release() + .await + .name("dummy") + .version("0.1.0") + .builds(vec![ + FakeBuild::default().build_status(BuildStatus::Success), + FakeBuild::default().build_status(BuildStatus::Failure), + FakeBuild::default().build_status(BuildStatus::InProgress), + ]) + .create_async() + .await?; + + let mut conn = env.async_db().await.async_conn().await; + + assert_eq!( + release_build_status(&mut conn, "dummy", "0.1.0").await, + BuildStatus::Success + ); + + Ok(()) + }) + } + + #[test] + fn test_build_status_failed() { + async_wrapper(|env| async move { + env.async_fake_release() + .await + .name("dummy") + .version("0.1.0") + .builds(vec![ + FakeBuild::default().build_status(BuildStatus::Failure), + FakeBuild::default().build_status(BuildStatus::InProgress), + ]) + .create_async() + .await?; + + let mut conn = env.async_db().await.async_conn().await; + + assert_eq!( + release_build_status(&mut conn, "dummy", "0.1.0").await, + BuildStatus::Failure + ); + + Ok(()) + }) + } + + #[test] + fn test_build_status_in_progress() { + async_wrapper(|env| async move { + env.async_fake_release() + .await + .name("dummy") + .version("0.1.0") + .builds(vec![ + FakeBuild::default().build_status(BuildStatus::InProgress) + ]) + .create_async() + .await?; + + let mut conn = env.async_db().await.async_conn().await; + + assert_eq!( + release_build_status(&mut conn, "dummy", "0.1.0").await, + BuildStatus::InProgress + ); + + Ok(()) + }) + } } diff --git a/src/web/mod.rs b/src/web/mod.rs index 8d5b9c3d5..0a4d008a8 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -620,18 +620,19 @@ pub(crate) struct MetaData { pub(crate) yanked: bool, /// CSS file to use depending on the rustdoc version used to generate this version of this /// crate. - pub(crate) rustdoc_css_file: String, + pub(crate) rustdoc_css_file: Option, } impl MetaData { + #[fn_error_context::context("getting metadata for {name} {version}")] async fn from_crate( conn: &mut sqlx::PgConnection, name: &str, version: &Version, req_version: Option, ) -> Result { - sqlx::query!( - "SELECT + let row = sqlx::query!( + r#"SELECT crates.name, releases.version, releases.description, @@ -640,17 +641,24 @@ impl MetaData { releases.default_target, releases.doc_targets, releases.yanked, - releases.doc_rustc_version + builds.rustc_version as "rustc_version?" FROM releases INNER JOIN crates ON crates.id = releases.crate_id - WHERE crates.name = $1 AND releases.version = $2", + LEFT JOIN LATERAL ( + SELECT * FROM builds + WHERE builds.rid = releases.id + ORDER BY builds.build_time + DESC LIMIT 1 + ) AS builds ON true + WHERE crates.name = $1 AND releases.version = $2"#, name, version.to_string(), ) - .fetch_optional(&mut *conn) + .fetch_one(&mut *conn) .await - .context("error fetching crate metadata")? - .map(|row| MetaData { + .context("error fetching crate metadata")?; + + Ok(MetaData { name: row.name, version: version.clone(), req_version: req_version.unwrap_or_else(|| ReqVersion::Exact(version.clone())), @@ -660,9 +668,13 @@ impl MetaData { default_target: row.default_target, doc_targets: MetaData::parse_doc_targets(row.doc_targets), yanked: row.yanked, - rustdoc_css_file: get_correct_docsrs_style_file(&row.doc_rustc_version).unwrap(), + // rustdoc_css_file: get_correct_docsrs_style_file(&row.doc_rustc_version).unwrap(), + rustdoc_css_file: row + .rustc_version + .as_deref() + .map(get_correct_docsrs_style_file) + .transpose()?, }) - .ok_or_else(|| anyhow!("missing metadata for {} {}", name, version)) } fn parse_doc_targets(targets: Value) -> Vec { @@ -1068,7 +1080,7 @@ mod test { "arm64-unknown-linux-gnu".to_string(), ], yanked: false, - rustdoc_css_file: "rustdoc.css".to_string(), + rustdoc_css_file: Some("rustdoc.css".to_string()), }; let correct_json = json!({ @@ -1152,7 +1164,7 @@ mod test { default_target: "x86_64-unknown-linux-gnu".to_string(), doc_targets: vec![], yanked: false, - rustdoc_css_file: "rustdoc.css".to_string(), + rustdoc_css_file: Some("rustdoc.css".to_string()), }, ); Ok(()) diff --git a/src/web/page/templates.rs b/src/web/page/templates.rs index 9c15360e2..aa4aeebd4 100644 --- a/src/web/page/templates.rs +++ b/src/web/page/templates.rs @@ -271,14 +271,22 @@ impl tera::Filter for IconType { }; let icon_file_string = font_awesome_as_a_crate::svg(type_, &icon_name[..]).unwrap_or(""); - let (space, class_extra) = match args.get("extra").and_then(|s| s.as_str()) { - Some(extra) => (" ", extra), - None => ("", ""), - }; + let mut classes = vec!["fa-svg", "fa-svg-fw"]; + if args + .get("spin") + .and_then(|spin| spin.as_bool()) + .unwrap_or(false) + { + classes.push("fa-svg-spin"); + }; + if let Some(extra) = args.get("extra").and_then(|s| s.as_str()) { + classes.push(extra); + } let icon = format!( "\ -{icon_file_string}" +{icon_file_string}", + class = classes.join(" "), ); Ok(Value::String(icon)) diff --git a/src/web/releases.rs b/src/web/releases.rs index 16988d059..36f179e2e 100644 --- a/src/web/releases.rs +++ b/src/web/releases.rs @@ -77,9 +77,9 @@ pub(crate) async fn get_releases( // WARNING: it is _crucial_ that this always be hard-coded and NEVER be user input let (ordering, filter_failed): (&'static str, _) = match order { - Order::ReleaseTime => ("builds.build_time", false), + Order::ReleaseTime => ("release_build_status.last_build_time", false), Order::GithubStars => ("repositories.stars", false), - Order::RecentFailures => ("builds.build_time", true), + Order::RecentFailures => ("release_build_status.last_build_time", true), Order::FailuresByGithubStars => ("repositories.stars", true), }; @@ -89,14 +89,14 @@ pub(crate) async fn get_releases( releases.description, releases.target_name, releases.rustdoc_status, - builds.build_time, + release_build_status.last_build_time AS build_time, repositories.stars FROM crates {1} - INNER JOIN builds ON releases.id = builds.rid + INNER JOIN release_build_status ON releases.id = release_build_status.id LEFT JOIN repositories ON releases.repository_id = repositories.id WHERE - ((NOT $3) OR (releases.build_status = FALSE AND releases.is_library = TRUE)) + ((NOT $3) OR (release_build_status.build_status = 'failure' AND releases.is_library = TRUE)) AND {0} IS NOT NULL ORDER BY {0} DESC @@ -686,10 +686,16 @@ pub(crate) async fn activity_handler(mut conn: DbConnection) -> AxumResult= CURRENT_DATE - INTERVAL '30 days' AND release_time < CURRENT_DATE diff --git a/src/web/rustdoc.rs b/src/web/rustdoc.rs index 25cb6f2a2..56a0986e5 100644 --- a/src/web/rustdoc.rs +++ b/src/web/rustdoc.rs @@ -550,7 +550,7 @@ pub(crate) async fn rustdoc_html_server_handler( // Find the path of the latest version for the `Go to latest` and `Permalink` links let mut current_target = String::new(); - let target_redirect = if latest_release.build_status { + let target_redirect = if latest_release.build_status.is_success() { let target = if target.is_empty() { current_target = krate.metadata.default_target.clone(); &krate.metadata.default_target @@ -709,10 +709,8 @@ pub(crate) async fn target_redirect_handler( let storage_location_for_path = { let mut pieces: Vec<_> = req_path.split('/').map(str::to_owned).collect(); - if let Some(target) = pieces.first() { - if target == &crate_details.metadata.default_target { - pieces.remove(0); - } + if pieces.first() == Some(&crate_details.metadata.default_target) { + pieces.remove(0); } if let Some(last) = pieces.last_mut() { diff --git a/src/web/source.rs b/src/web/source.rs index 1f14dc659..8e843530b 100644 --- a/src/web/source.rs +++ b/src/web/source.rs @@ -3,7 +3,6 @@ use crate::{ db::Pool, impl_axum_webpage, storage::PathNotFoundError, - utils::get_correct_docsrs_style_file, web::{ cache::CachePolicy, error::AxumNope, extractors::Path, file::File as DbFile, headers::CanonicalUrl, MetaData, ReqVersion, @@ -76,16 +75,7 @@ impl FileList { folder: &str, ) -> Result> { let row = match sqlx::query!( - "SELECT crates.name, - releases.version, - releases.description, - releases.target_name, - releases.rustdoc_status, - releases.files, - releases.default_target, - releases.doc_targets, - releases.yanked, - releases.doc_rustc_version + "SELECT releases.files FROM releases INNER JOIN crates ON crates.id = releases.crate_id WHERE crates.name = $1 AND releases.version = $2", @@ -147,18 +137,7 @@ impl FileList { }); Ok(Some(FileList { - metadata: MetaData { - name: row.name, - version: version.clone(), - req_version: req_version.unwrap_or_else(|| ReqVersion::Exact(version.clone())), - description: row.description, - target_name: Some(row.target_name), - rustdoc_status: row.rustdoc_status, - default_target: row.default_target, - doc_targets: MetaData::parse_doc_targets(row.doc_targets), - yanked: row.yanked, - rustdoc_css_file: get_correct_docsrs_style_file(&row.doc_rustc_version)?, - }, + metadata: MetaData::from_crate(conn, name, version, req_version).await?, files: file_list, })) } else { diff --git a/templates/crate/builds.html b/templates/crate/builds.html index 72c97f13c..775b48626 100644 --- a/templates/crate/builds.html +++ b/templates/crate/builds.html @@ -30,30 +30,44 @@ {%- block body -%}
-
- Builds -
+ {%- if builds -%} +
+ Builds +
- + + + {%- endfor -%} + + {%- else -%} +
+ docs.rs has not built {{ metadata.name }}-{{ metadata.version }} +
+ It may still be queued or in-progress. +
+ If you believe this is docs.rs' fault, open an issue. +
+ {%- endif -%}
{# BuildsPage.metadata is an `Option`, so accessing it can fail #} diff --git a/templates/crate/details.html b/templates/crate/details.html index 9688cb1b5..c21d7b459 100644 --- a/templates/crate/details.html +++ b/templates/crate/details.html @@ -153,14 +153,14 @@
{# If the build succeeded, isn't yanked and is a library #} - {%- elif details.build_status -%} + {%- elif details.build_status == "success" -%} {# If there are no docs display a warning #} {%- if not details.rustdoc_status -%}
{{ details.name }}-{{ details.version }} doesn't have any documentation.
{%- endif -%} {# If the build failed, the release isn't yanked and the release is a library #} - {%- else -%} + {%- elif details.build_status == "failure" -%} {# Display a warning telling the user we failed to build the docs #}
docs.rs failed to build {{ details.name }}-{{ details.version }} @@ -173,16 +173,21 @@
If you believe this is docs.rs' fault, open an issue.
+ {%- elif details.build_status == "in_progress" -%} +
+ {{ "gear" | fas(spin=true) }} + Build is in progress, it will be available soon +
+ {%- endif -%} - {# If there is one, display the most next most recent successful build #} - {%- if details.last_successful_build -%} - - {%- endif -%} + {# If there is one, display the next most recent successful build #} + {%- if details.last_successful_build -%} + {%- endif -%} {# If there's a readme, display it #} diff --git a/templates/macros.html b/templates/macros.html index e0e754f98..dab2e7d0c 100644 --- a/templates/macros.html +++ b/templates/macros.html @@ -84,7 +84,10 @@ * `releases` A list of crate releases where each release has the following fields: * `version` A string of the release's version * `yanked` A boolean of the release's yanked status - * `build_status` A boolean of the crate's build status (true for built, false for failed build) + * `build_status` A string of the crate's build status + * "success" for built + * "failure" for failed build + * "in_progress" for in progress * `is_library` A boolean that's true if the crate is a library and false if it's a binary * `target` The target platform (empty string if the default or a `/crate` page) * `inner_path` The current rustdoc page (empty string if a `/crate` page) @@ -106,31 +109,40 @@ {%- if not release.is_library -%} {# If the release isn't a library, then display that warning #} {%- set warning = release_name ~ " is not a library" -%} - {%- elif release.yanked and release.build_status -%} - {# If the release has been yanked and failed to build, display a warning #} + {%- elif release.yanked and release.build_status == "success" -%} + {# If the release has been yanked and successfully built, display a warning #} {%- set warning = release_name ~ " is yanked" -%} - {%- elif release.yanked and not release.build_status -%} + {%- elif release.yanked and release.build_status == "failure" -%} {# If the release has been yanked and failed to build, display a warning #} {%- set warning = release_name ~ " is yanked and docs.rs failed to build it" -%} - {%- elif not release.build_status -%} + {%- elif release.build_status == "failure" -%} {# If the release failed to build, display a warning #} {%- set warning = "docs.rs failed to build " ~ release_name -%} + {%- elif release.build_status == "in_progress" -%} + {%- set title = release_name ~ " is currently being built" -%} {%- else -%} {%- set warning = false -%} {%- endif -%} + {%- if warning -%} + {%- set title = warning -%} + {%- endif -%} +
  • {% if warning %} {{ "triangle-exclamation" | fas }} {% endif %} + {% if release.build_status == "in_progress" %} + {{ "gear" | fas(fw=true, spin=true) }} + {% endif %} {{ release.version }}
  • diff --git a/templates/rustdoc/head.html b/templates/rustdoc/head.html index f3678c940..827111009 100644 --- a/templates/rustdoc/head.html +++ b/templates/rustdoc/head.html @@ -1,5 +1,7 @@ {%- import "macros.html" as macros -%} + {% if metadata.rustdoc_css_file %} + {% endif %} diff --git a/templates/style/_fa.scss b/templates/style/_fa.scss index cc532dec2..2e2592fe1 100644 --- a/templates/style/_fa.scss +++ b/templates/style/_fa.scss @@ -1,13 +1,7 @@ -.fa-svg-fw { - width: 1.25em; - text-align: center; +.fa-svg { display: inline-block; } -.fa-svg.left-margin { - margin-left: 1.25em; -} - .fa-svg svg { width: 1em; height: 1em; @@ -16,3 +10,20 @@ margin-bottom: -0.1em; } +.fa-svg.left-margin { + margin-left: 1.25em; +} + +.fa-svg-fw { + width: 1.25em; + text-align: center; +} + +.fa-svg-spin { + animation: 2s infinite linear fa-svg-spin; +} + +@keyframes fa-svg-spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +}