diff --git a/react-native/schema.graphql b/react-native/schema.graphql index a1cdedf..ee96696 100644 --- a/react-native/schema.graphql +++ b/react-native/schema.graphql @@ -1,7 +1,5 @@ type Query { - mobileEntrypointSections( - id: String! # TODO: limit to only supported pages? (com.yaComiste.Explore) - ): [SDUISection!]! + mobileEntrypointSections(id: String!): [SDUISection!]! } type SDUISection { diff --git a/rust/README.md b/rust/README.md index 69c07f3..5721c2e 100644 --- a/rust/README.md +++ b/rust/README.md @@ -33,7 +33,16 @@ Example query: { mobileEntrypointSections(key: "com.yaComiste.Explore") { id - title + __typename + component(supported: ["SDUIScrollViewHorizontalComponent"]) { + __typename + ... on SDUICardComponent { + id + } + ... on SDUIScrollViewHorizontalComponent { + id + } + } } } ``` @@ -42,6 +51,8 @@ Example query: - setup CI for Rust - DB strings and source-code translations +- dataloaders +- DB schema validations # ArangoDB diff --git a/rust/arangodb/dump/_system/dump.json b/rust/arangodb/dump/_system/dump.json index c05f33f..4124d15 100644 --- a/rust/arangodb/dump/_system/dump.json +++ b/rust/arangodb/dump/_system/dump.json @@ -1 +1 @@ -{"database":"_system","lastTickAtDumpStart":"90475","properties":{"id":"1","name":"_system","isSystem":true}} \ No newline at end of file +{"database":"_system","lastTickAtDumpStart":"132102","properties":{"id":"1","name":"_system","isSystem":true}} \ No newline at end of file diff --git a/rust/arangodb/dump/ya-comiste/dump.json b/rust/arangodb/dump/ya-comiste/dump.json index c227335..5f22db1 100644 --- a/rust/arangodb/dump/ya-comiste/dump.json +++ b/rust/arangodb/dump/ya-comiste/dump.json @@ -1 +1 @@ -{"database":"ya-comiste","lastTickAtDumpStart":"90477","properties":{"id":"84144","name":"ya-comiste","isSystem":false}} \ No newline at end of file +{"database":"ya-comiste","lastTickAtDumpStart":"132104","properties":{"id":"84144","name":"ya-comiste","isSystem":false}} \ No newline at end of file diff --git a/rust/arangodb/dump/ya-comiste/entrypoint_sections_6c1f9f90c0181a0fe73698084cda2c0b.data.json b/rust/arangodb/dump/ya-comiste/entrypoint_sections_6c1f9f90c0181a0fe73698084cda2c0b.data.json index cdaf05d..b7725eb 100644 --- a/rust/arangodb/dump/ya-comiste/entrypoint_sections_6c1f9f90c0181a0fe73698084cda2c0b.data.json +++ b/rust/arangodb/dump/ya-comiste/entrypoint_sections_6c1f9f90c0181a0fe73698084cda2c0b.data.json @@ -1,2 +1,2 @@ {"type":2300,"data":{"_key":"89538","_id":"entrypoint_sections/89538","_from":"entrypoints/com.yaComiste.Explore","_to":"sections/10771","_rev":"_bff4636---"}} -{"type":2300,"data":{"_key":"89458","_id":"entrypoint_sections/89458","_from":"entrypoints/com.yaComiste.Explore","_to":"sections/9810","_rev":"_bfgGf26---","user":"users/87047"}} +{"type":2300,"data":{"_key":"89458","_id":"entrypoint_sections/89458","_from":"entrypoints/com.yaComiste.Explore","_to":"sections/9810","_rev":"_bgJE1wa---"}} diff --git a/rust/arangodb/dump/ya-comiste/sections_ff4dee88db82e98f0e0d524d965b9aa7.data.json b/rust/arangodb/dump/ya-comiste/sections_ff4dee88db82e98f0e0d524d965b9aa7.data.json index 3c74a2d..c3706ce 100644 --- a/rust/arangodb/dump/ya-comiste/sections_ff4dee88db82e98f0e0d524d965b9aa7.data.json +++ b/rust/arangodb/dump/ya-comiste/sections_ff4dee88db82e98f0e0d524d965b9aa7.data.json @@ -1,2 +1,2 @@ -{"type":2300,"data":{"_key":"9810","_id":"sections/9810","_rev":"_bff1zcW---","section_order":1,"title":"Places nearby","type":"SDUIScrollViewHorizontalComponent"}} -{"type":2300,"data":{"_key":"10771","_id":"sections/10771","_rev":"_bff2ICi---","section_order":2,"title":"Bakeries nearby","type":"SDUIScrollViewHorizontalComponent"}} +{"type":2300,"data":{"_key":"9810","_id":"sections/9810","_rev":"_bgLK4YO---","order":1,"component":{"typename":"SDUIScrollViewHorizontalComponent"}}} +{"type":2300,"data":{"_key":"10771","_id":"sections/10771","_rev":"_bgLL-8W---","order":2,"component":{"typename":"SDUIScrollViewHorizontalComponent"}}} diff --git a/rust/arangodb/dump/ya-comiste/sections_ff4dee88db82e98f0e0d524d965b9aa7.structure.json b/rust/arangodb/dump/ya-comiste/sections_ff4dee88db82e98f0e0d524d965b9aa7.structure.json index 1b5e670..19da399 100644 --- a/rust/arangodb/dump/ya-comiste/sections_ff4dee88db82e98f0e0d524d965b9aa7.structure.json +++ b/rust/arangodb/dump/ya-comiste/sections_ff4dee88db82e98f0e0d524d965b9aa7.structure.json @@ -1 +1 @@ -{"indexes":[],"parameters":{"allowUserKeys":true,"cacheEnabled":false,"cid":"85191","deleted":false,"globallyUniqueId":"hC8B4EB76885F/9751","id":"85191","isDisjoint":false,"isSmart":false,"isSmartChild":false,"isSystem":false,"keyOptions":{"allowUserKeys":true,"type":"traditional","lastValue":11596},"minReplicationFactor":1,"name":"sections","numberOfShards":1,"planId":"9751","replicationFactor":1,"schema":{"message":"","level":"strict","rule":{"type":"object","oneOf":[{"properties":{"section_order":{"type":"number"},"type":{"const":"SDUIScrollViewHorizontalComponent"},"title":{"type":"string"}},"additionalProperties":false,"required":["section_order","type","title"]},{"properties":{"section_order":{"type":"number"},"type":{"const":"SDUICardComponent"}},"additionalProperties":false,"required":["section_order","type"]}]}},"shardKeys":["_key"],"shards":{},"status":3,"tempObjectId":"0","type":2,"version":9,"waitForSync":false,"writeConcern":1}} \ No newline at end of file +{"indexes":[],"parameters":{"allowUserKeys":true,"cacheEnabled":false,"cid":"85191","deleted":false,"globallyUniqueId":"hC8B4EB76885F/9751","id":"85191","isDisjoint":false,"isSmart":false,"isSmartChild":false,"isSystem":false,"keyOptions":{"allowUserKeys":true,"type":"traditional","lastValue":11596},"minReplicationFactor":1,"name":"sections","numberOfShards":1,"planId":"9751","replicationFactor":1,"schema":null,"shardKeys":["_key"],"shards":{},"status":3,"tempObjectId":"0","type":2,"version":9,"waitForSync":false,"writeConcern":1}} \ No newline at end of file diff --git a/rust/crates/sdui/src/entrypoint.rs b/rust/crates/sdui/src/entrypoint.rs index 242d273..8977a16 100644 --- a/rust/crates/sdui/src/entrypoint.rs +++ b/rust/crates/sdui/src/entrypoint.rs @@ -1,15 +1,6 @@ -use graphql::graphql_context::Context; - /// TODO: maybe `MobileEntrypoint` would be a better name so we are future proof? #[derive(Clone, Debug, serde::Deserialize)] pub struct Entrypoint { - pub _id: juniper::ID, + pub _id: String, pub _key: String, } - -#[juniper::graphql_object(context = Context)] -impl Entrypoint { - fn id(&self) -> &str { - &self._id - } -} diff --git a/rust/crates/sdui/src/errors.rs b/rust/crates/sdui/src/errors.rs index eb2e4e7..943143e 100644 --- a/rust/crates/sdui/src/errors.rs +++ b/rust/crates/sdui/src/errors.rs @@ -4,6 +4,7 @@ /// use sdui::errors::ModelError; /// assert_eq!(format!("{}", ModelError::LogicError("ups".to_string())), "Logic error: ups") /// ``` +#[derive(Debug)] pub enum ModelError { // TODO: naming (SDUIError?) DatabaseError(arangors::ClientError), diff --git a/rust/crates/sdui/src/lib.rs b/rust/crates/sdui/src/lib.rs index 49140e6..50c13a5 100644 --- a/rust/crates/sdui/src/lib.rs +++ b/rust/crates/sdui/src/lib.rs @@ -2,3 +2,9 @@ pub mod entrypoint; pub mod errors; pub mod model; pub mod sdui_section; + +mod sdui_card_component; +mod sdui_component; +mod sdui_description_component; +mod sdui_jumbotron_component; +mod sdui_scrollview_horizontal_component; diff --git a/rust/crates/sdui/src/model/mod.rs b/rust/crates/sdui/src/model/mod.rs index 4ddd749..fa412cf 100644 --- a/rust/crates/sdui/src/model/mod.rs +++ b/rust/crates/sdui/src/model/mod.rs @@ -1 +1,2 @@ pub mod entrypoints; +pub mod sdui_sections; diff --git a/rust/crates/sdui/src/model/sdui_sections.rs b/rust/crates/sdui/src/model/sdui_sections.rs new file mode 100644 index 0000000..4c751ec --- /dev/null +++ b/rust/crates/sdui/src/model/sdui_sections.rs @@ -0,0 +1,71 @@ +use crate::errors::ModelError; +use crate::model::entrypoints::get_entrypoint; +use crate::sdui_component::SDUIComponent; +use crate::sdui_section::SDUISection; +use arangodb::connection; + +/// Note: this function doesn't return `section.component` on purpose. +pub async fn get_all_sections_for_entrypoint_key( + entrypoint_key: String, +) -> Result, ModelError> { + let conn = connection().await; + let db = conn.db("ya-comiste").await.unwrap(); + + let entrypoint = get_entrypoint(&entrypoint_key).await?; + let aql = arangors::AqlQuery::builder() + .query( + " + FOR section IN 1..1 OUTBOUND @entrypoint_id entrypoint_sections + LET component = section.component + SORT section.order ASC + RETURN { + id: section._id, + } + ", + ) + .bind_var("entrypoint_id", entrypoint._id.to_string()) + .batch_size(1) + .build(); + + match db.aql_query::(aql).await { + Ok(r) => Ok(r), + Err(e) => Err(ModelError::DatabaseError(e)), + } +} + +pub async fn get_section_component( + section_id: String, + supported: Vec, +) -> Result { + let conn = connection().await; + let db = conn.db("ya-comiste").await.unwrap(); + + // TODO: change the component ID to be unique (how to 🤔) + let aql = arangors::AqlQuery::builder() + .query( + " + LET section = DOCUMENT(@section_id) + LET component = section.component + FILTER component.typename IN @supported_typenames + LIMIT 1 + RETURN { + typename: component.typename, + id: CONCAT(section._id, '~', component.typename), + } + ", + ) + .bind_var("section_id", section_id) + .bind_var("supported_typenames", supported) + .batch_size(1) + .build(); + + match db.aql_query::(aql).await { + Ok(r) => match r.first() { + Some(first) => Ok(first.to_owned()), + None => Err(ModelError::LogicError( + "no supported component available".to_string(), + )), + }, + Err(e) => Err(ModelError::DatabaseError(e)), + } +} diff --git a/rust/crates/sdui/src/sdui_card_component.rs b/rust/crates/sdui/src/sdui_card_component.rs new file mode 100644 index 0000000..1620791 --- /dev/null +++ b/rust/crates/sdui/src/sdui_card_component.rs @@ -0,0 +1,13 @@ +use graphql::graphql_context::Context; + +#[derive(Clone, Debug, serde::Deserialize)] +pub struct SDUICardComponent { + pub id: String, +} + +#[juniper::graphql_object(context = Context)] +impl SDUICardComponent { + fn id(&self) -> juniper::ID { + juniper::ID::new(&self.id) + } +} diff --git a/rust/crates/sdui/src/sdui_component.rs b/rust/crates/sdui/src/sdui_component.rs new file mode 100644 index 0000000..6b33628 --- /dev/null +++ b/rust/crates/sdui/src/sdui_component.rs @@ -0,0 +1,16 @@ +use crate::sdui_card_component::SDUICardComponent; +use crate::sdui_description_component::SDUIDescriptionComponent; +use crate::sdui_jumbotron_component::SDUIJumbotronComponent; +use crate::sdui_scrollview_horizontal_component::SDUIScrollViewHorizontalComponent; +use graphql::graphql_context::Context; +use juniper::GraphQLUnion; + +#[derive(Clone, Debug, serde::Deserialize, GraphQLUnion)] +#[graphql(Context = Context)] +#[serde(tag = "typename")] +pub enum SDUIComponent { + SDUICardComponent(SDUICardComponent), + SDUIDescriptionComponent(SDUIDescriptionComponent), + SDUIJumbotronComponent(SDUIJumbotronComponent), + SDUIScrollViewHorizontalComponent(SDUIScrollViewHorizontalComponent), +} diff --git a/rust/crates/sdui/src/sdui_description_component.rs b/rust/crates/sdui/src/sdui_description_component.rs new file mode 100644 index 0000000..0fc646f --- /dev/null +++ b/rust/crates/sdui/src/sdui_description_component.rs @@ -0,0 +1,13 @@ +use graphql::graphql_context::Context; + +#[derive(Clone, Debug, serde::Deserialize)] +pub struct SDUIDescriptionComponent { + pub id: String, +} + +#[juniper::graphql_object(context = Context)] +impl SDUIDescriptionComponent { + fn id(&self) -> juniper::ID { + juniper::ID::new(&self.id) + } +} diff --git a/rust/crates/sdui/src/sdui_jumbotron_component.rs b/rust/crates/sdui/src/sdui_jumbotron_component.rs new file mode 100644 index 0000000..f25f7d8 --- /dev/null +++ b/rust/crates/sdui/src/sdui_jumbotron_component.rs @@ -0,0 +1,13 @@ +use graphql::graphql_context::Context; + +#[derive(Clone, Debug, serde::Deserialize)] +pub struct SDUIJumbotronComponent { + pub id: String, +} + +#[juniper::graphql_object(context = Context)] +impl SDUIJumbotronComponent { + fn id(&self) -> juniper::ID { + juniper::ID::new(&self.id) + } +} diff --git a/rust/crates/sdui/src/sdui_scrollview_horizontal_component.rs b/rust/crates/sdui/src/sdui_scrollview_horizontal_component.rs new file mode 100644 index 0000000..6152f8a --- /dev/null +++ b/rust/crates/sdui/src/sdui_scrollview_horizontal_component.rs @@ -0,0 +1,13 @@ +use graphql::graphql_context::Context; + +#[derive(Clone, Debug, serde::Deserialize)] +pub struct SDUIScrollViewHorizontalComponent { + pub id: String, +} + +#[juniper::graphql_object(context = Context)] +impl SDUIScrollViewHorizontalComponent { + fn id(&self) -> juniper::ID { + juniper::ID::new(&self.id) + } +} diff --git a/rust/crates/sdui/src/sdui_section.rs b/rust/crates/sdui/src/sdui_section.rs index 22f653c..b983978 100644 --- a/rust/crates/sdui/src/sdui_section.rs +++ b/rust/crates/sdui/src/sdui_section.rs @@ -1,9 +1,11 @@ +use crate::model::sdui_sections::get_section_component; +use crate::sdui_component::SDUIComponent; use graphql::graphql_context::Context; #[derive(Clone, Debug, serde::Deserialize)] pub struct SDUISection { - pub id: juniper::ID, - pub title: String, + pub id: String, + pub component: Option, } // impl SDUISection { @@ -14,12 +16,15 @@ pub struct SDUISection { #[juniper::graphql_object(context = Context)] impl SDUISection { - #[graphql(description = "GraphQL description 🤯")] - fn id(&self) -> &str { - &self.id + fn id(&self) -> juniper::ID { + juniper::ID::new(&self.id) } - fn title(&self) -> &str { - &self.title + async fn component(&self, supported: Vec) -> Option { + // We might get a component which is not supported by the client yet (defined by `supported`). + match get_section_component(self.id.to_string(), supported).await { + Ok(component) => Some(component), + Err(_) => None, + } } } diff --git a/rust/crates/server/src/graphql_schema.rs b/rust/crates/server/src/graphql_schema.rs index 2d3fb9d..7cfcc4f 100644 --- a/rust/crates/server/src/graphql_schema.rs +++ b/rust/crates/server/src/graphql_schema.rs @@ -1,7 +1,7 @@ -use crate::sdui_blocks::get_all_sections; use graphql::graphql_context::Context; use juniper::{EmptyMutation, EmptySubscription, FieldError, FieldResult, RootNode}; use sdui::errors::ModelError; +use sdui::model::sdui_sections::get_all_sections_for_entrypoint_key; use sdui::sdui_section::SDUISection; #[derive(Clone, Copy, Debug)] @@ -10,11 +10,11 @@ pub struct Query; #[juniper::graphql_object(context = Context)] impl Query { async fn mobile_entrypoint_sections(key: String) -> FieldResult> { - match get_all_sections(key).await { + match get_all_sections_for_entrypoint_key(key).await { Ok(s) => Ok(s), // Err(e) => Err(FieldError::from(e)), Err(e) => match e { - ModelError::DatabaseError(_) => Err(FieldError::from("opaque database error")), // TODO log these errors + ModelError::DatabaseError(e) => Err(FieldError::from(e)), // TODO: hide and log these errors ModelError::LogicError(e) => Err(FieldError::from(e)), }, } diff --git a/rust/crates/server/src/main.rs b/rust/crates/server/src/main.rs index 869d4b6..e8781dc 100644 --- a/rust/crates/server/src/main.rs +++ b/rust/crates/server/src/main.rs @@ -1,7 +1,6 @@ // This declaration will look for a file named `XXX.rs` or `XXX/mod.rs` and will // insert its contents inside a module named `XXX` under this scope mod graphql_schema; -mod sdui_blocks; use crate::graphql_schema::create_graphql_schema; use graphql::graphql_context::Context; diff --git a/rust/crates/server/src/sdui_blocks.rs b/rust/crates/server/src/sdui_blocks.rs deleted file mode 100644 index b4ddc2b..0000000 --- a/rust/crates/server/src/sdui_blocks.rs +++ /dev/null @@ -1,35 +0,0 @@ -use arangodb::connection; -use sdui::errors::ModelError; -use sdui::model::entrypoints::get_entrypoint; -use sdui::sdui_section::SDUISection; - -pub async fn get_all_sections(entrypoint_key: String) -> Result, ModelError> { - let conn = connection().await; - let db = conn.db("ya-comiste").await.unwrap(); - - let entrypoint = get_entrypoint(&entrypoint_key).await?; - let aql = arangors::AqlQuery::builder() - .query( - " - FOR vertex IN 1..1 OUTBOUND @entrypoint_id entrypoint_sections - FILTER vertex.type IN @supported_vertex_types - SORT vertex.section_order ASC - RETURN { - id: vertex._id, - title: vertex.title, - } - ", - ) - .bind_var("entrypoint_id", entrypoint._id.to_string()) - .bind_var( - "supported_vertex_types", - vec!["SDUIScrollViewHorizontalComponent"], // TODO: filter by actual `supported` arg - ) - .batch_size(1) - .build(); - - match db.aql_query::(aql).await { - Ok(r) => Ok(r), - Err(e) => Err(ModelError::DatabaseError(e)), - } -} diff --git a/rust/crates/server/src/snapshots/server__graphql_schema__tests__schema_snapshot.snap b/rust/crates/server/src/snapshots/server__graphql_schema__tests__schema_snapshot.snap index 55dd8a1..e1568d3 100644 --- a/rust/crates/server/src/snapshots/server__graphql_schema__tests__schema_snapshot.snap +++ b/rust/crates/server/src/snapshots/server__graphql_schema__tests__schema_snapshot.snap @@ -2,16 +2,33 @@ source: crates/server/src/graphql_schema.rs expression: "super::create_graphql_schema().as_schema_language()" --- +type SDUIScrollViewHorizontalComponent { + id: ID! +} + +type SDUIDescriptionComponent { + id: ID! +} + +type SDUIJumbotronComponent { + id: ID! +} + +type SDUICardComponent { + id: ID! +} + type Query { mobileEntrypointSections(key: String!): [SDUISection!]! } type SDUISection { - "GraphQL description \u129327" - id: String! - title: String! + id: ID! + component(supported: [String!]!): SDUIComponent } +union SDUIComponent = SDUICardComponent | SDUIDescriptionComponent | SDUIJumbotronComponent | SDUIScrollViewHorizontalComponent + schema { query: Query }