-
Notifications
You must be signed in to change notification settings - Fork 326
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(183557950): Add ProjectsGrid View for Cloud Dashboard #3857
Conversation
decf54f
to
971da45
Compare
971da45
to
879d7b5
Compare
// ============= | ||
// === Error === | ||
// ============= | ||
|
||
/// Type alias for a thread-safe boxed [`Error`]. | ||
/// | ||
/// Convert your error to this type when your error can not be recovered from, or no further context | ||
/// can be added to it. | ||
/// | ||
/// [`Error`]: ::std::error::Error | ||
pub type Error = Box<dyn error::Error + Send + Sync>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's refactor this to a common place, pls.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
/// This enum shouldn't be used directly by users of this crate, and instead they should interact | ||
/// with the Cloud API via the [`Client`] struct. | ||
/// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets keep it private then :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
/// HTTP client for the Cloud API. | ||
#[derive(Clone, Debug)] | ||
pub struct Client { | ||
base_url: String, | ||
token: String, | ||
http: reqwest::Client, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move the docs about fields here, pls.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
/// * `api_gateway_id` - The unique identifier of the Amazon API Gateway that is fronting the | ||
/// Cloud API. This is used to disambiguate between requests to development, staging, | ||
/// production, etc. environments. This is obtained from the Terraform output after the API is | ||
/// deployed. | ||
/// * `aws_region` - The AWS region in which the API is deployed. This is obtained from the | ||
/// Terraform output after the API is deployed. | ||
/// * `token` - The authentication token to use when making requests to the API. This is | ||
/// obtained from the browser storage after the user is logged in. | ||
/// | ||
/// [`Client`]: crate::Client | ||
pub fn new(api_gateway_id: &str, aws_region: &str, token: String) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we can use real types here instead of &str, I would do it. According to our phone call, this can be done by importing a lib, but please check if the lib does not increase the binary size drastically.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
// ==================== | ||
|
||
/// A response for a successful request to the [`ListProjects`] [`Route`]. | ||
/// | ||
/// [`ListProjects`]: crate::Route::ListProjects | ||
/// [`Route`]: crate::Route | ||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] | ||
#[allow(missing_docs)] | ||
pub struct ListProjects { | ||
/// A list of all [`Project`]s that the user has access to. The list may be empty, if the | ||
/// user has access to no [`Project`]s or no [`Project`]s have been created. | ||
/// | ||
/// [`Project`]: ::enso_cloud_view::project::Project | ||
pub projects: Vec<view::project::Project>, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file contain responses, so we can generate here the Routs enum automatically.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
// represented by an arrow icon. | ||
text: column.to_string().into(), | ||
disabled: Immutable(true), | ||
override_width: Immutable(if col == 1 && row == 5 { Some(180.0) } else { None }), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
magic numbers
|
||
fn get_projects( | ||
client: enso_cloud_http::Client, | ||
input: crate::projects_grid::api::public::Input, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can pass here set_projects: frp::Source<...>
and then you'd need to do set_projects.emit(...)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when I tried this I got:
Checking debug-scene-cloud-dashboard v0.1.0 (/home/indiv0/src/enso/app/gui/view/debug_scene/cloud-dashboard)
error[E0308]: mismatched types
--> app/gui/view/debug_scene/cloud-dashboard/src/lib.rs:554:30
|
554 | get_projects(client, input.set_projects);
| ------------ ^^^^^^^^^^^^^^^^^^ expected struct `enso_frp::SourceData`, found struct `enso_frp::AnyData`
| |
| arguments to this function are incorrect
|
= note: expected struct `enso_frp::stream::WeakNode<enso_frp::SourceData<std::rc::Rc<std::vec::Vec<enso_cloud_view::project::Project>>>>`
found struct `enso_frp::stream::WeakNode<enso_frp::AnyData<std::rc::Rc<std::vec::Vec<enso_cloud_view::project::Project>>>>`
note: function defined here
--> app/gui/view/debug_scene/cloud-dashboard/src/lib.rs:581:8
|
581 | fn get_projects(
| ^^^^^^^^^^^^
582 | client: enso_cloud_http::Client,
583 | set_projects: frp::Source<Rc<Vec<view::project::Project>>>,
| ----------------------------------------------------------
For more information about this error, try `rustc --explain E0308`.
error: could not compile `debug-scene-cloud-dashboard` due to previous error
How do I convert AnyData
-marked Source
to a SourceData
-marked Source
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see these lines in this file anymore, where are they now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
get_projects
is now in the projects_grid
module
mem::forget(app); | ||
mem::forget(panel); | ||
mem::forget(projects_grid); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rm these
let panel = app.new_view::<sample_component::View>(); | ||
panel.set_size(Vector2::new(200.0, 200.0)); | ||
scene.add_child(&panel); | ||
let _main_layer = &app.display.default_scene.layers.node_searcher; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not needed
@@ -211,13 +563,16 @@ pub fn main() { | |||
theme::builtin::light::register(&app); | |||
theme::builtin::light::enable(&app); | |||
|
|||
let world = &app.display; | |||
let _world = &app.display; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not needed
302dadf
to
136588d
Compare
|
requested_section <- header_frp.section_info_needed.map(|&(row, col)| { | ||
let sections_size = 2 + col; | ||
let section_start = row - (row % sections_size); | ||
let section_end = section_start + sections_size; | ||
let position = (section_start, col).into(); | ||
let model = header_entry_model(position); | ||
(section_start..section_end, col, model) | ||
}); | ||
header_frp.section_info <+ requested_section; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@farmaazon this code comes from one of the existing header view examples, but I'm having some trouble understanding what it's doing. What is the logic here supposed to be doing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally, the grid view wants to know what to display as a header of the top of the viewport in the current column. Therefore, it asks about information about the section the topmost visible entry is in.
section_info_needed
endpoint is exactly that: a question about the section the given entry is in.
IT also assumes that there may be more than one section, therefore it needs to know the span of rows belonging to the returned section (so it knows when to ask about another one).
So, the implementation copied from the example does not make much sense, of course, because you have only one section, so the range should be always 1..=self.rows()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perfect, thank you :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I checked just the one file with the grid view. The header requesting system shall be fixed (it should actually crash a lot if I understand the code correctly).
Also, there are many TODO or FIXME notes. Each shall be annotated with the pivotal issue, where the thing is planned to be done/fixed.
/// The row and column coordinates of a cell in the [`ProjectsTable`]. | ||
#[derive(Clone, Copy, Debug, Default)] | ||
pub struct Position { | ||
row: Row, | ||
column: Col, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We may consider making it a part of the grid-view crate.
But after reading more of your code I see that you usually construct it from row and col only to decompose it again to two separate variables. So perhaps we could stay with (row: Row, col: Col) everywhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@farmaazon I suggested that because in the code there was .0
and .1
in many places. It's a little bit hard to understand and its way easier to read code if you have .row
and .column
. Do you agree with this?
/// rendered. | ||
/// | ||
/// [`Project`]: ::enso_cloud_view::project::Project | ||
pub mod projects_table { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This module is so long I would put it in a separate file.
// The first row is the header row, which is displayed with a different entry model, so | ||
// we should ignore it here. | ||
if row == 0 { | ||
return None; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a big function with several returning points. We avoid those as they make the code more obscure. Please refactor it, perhaps making some sub-methods.
let screen_size = Vector2::from(shape); | ||
let margin = Vector2::new(HORIZONTAL_MARGIN, VERTICAL_MARGIN); | ||
let size = screen_size - margin; | ||
self.grid.scroll_frp().resize(size); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The preferred way for calling FRP inputs is to assign them to FRP outputs. Usually I do it by making the model method returning the event payload (size in this case), and then assign the method output in frp::extend!
with <+
operator (see my next comment for example).
|
||
let width = shape.width / Columns::LEN as f32; | ||
let size = Vector2(width, ENTRY_HEIGHT); | ||
self.grid.set_entries_size(size); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar case: we could make this function return size, and have in FRP something like:
model.grid.set_entries_size <+ scene.frp.shape.map(f!((shape) model.refit_entries_to_shape(shape));
This way frp events have more informative backtraces.
// FIXME [NP]: How do we stop the grid from calling `model_for_entry` on the | ||
// header row? It should be handled via the later extension to the network. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's impossible. And for the header row, you should just return headers. The "header" API is meant for the headers "pushed down" to be visible even when scrolled down.
ensogl_core::define_endpoints_2! { | ||
Input { | ||
set_projects(Rc<Vec<view::project::Project>>), | ||
model_for_entry(Position, EntryModel), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this output is here? Is it used?
// FIXME [NP]: Should we be assuming that the `row, col` are always in bounds? If we do so, we | ||
// can avoid having to return `Option` here, but we run the risk of panicking in the event of an | ||
// out of bounds `row, col`. Though this should only happen on implementation error. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean, receive with section_info_needed
? It should not emit any position outside the last size set by reset_entries
or resize
. But still, one could just put the wrong size on it.
And as I understand, we still have a rule of avoiding panicking at all cost, so you may assume that the position is valid, but still not panic ;) let's return some dummy entry, for example labeled "INVALID" instead.
requested_section <- header_frp.section_info_needed.map(|&(row, col)| { | ||
let sections_size = 2 + col; | ||
let section_start = row - (row % sections_size); | ||
let section_end = section_start + sections_size; | ||
let position = (section_start, col).into(); | ||
let model = header_entry_model(position); | ||
(section_start..section_end, col, model) | ||
}); | ||
header_frp.section_info <+ requested_section; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally, the grid view wants to know what to display as a header of the top of the viewport in the current column. Therefore, it asks about information about the section the topmost visible entry is in.
section_info_needed
endpoint is exactly that: a question about the section the given entry is in.
IT also assumes that there may be more than one section, therefore it needs to know the span of rows belonging to the returned section (so it knows when to ask about another one).
So, the implementation copied from the example does not make much sense, of course, because you have only one section, so the range should be always 1..=self.rows()
.
/// The width of a single entry in the [`ProjectsTable`], in pixels. | ||
const ENTRY_WIDTH: f32 = 130.0; | ||
/// The height of a single entry in the [`ProjectsTable`], in pixels. | ||
const ENTRY_HEIGHT: f32 = 28.0; | ||
/// A placeholder value for the initial height of the viewport. This value is a placeholder | ||
/// because the value must be provided when creating the viewport, but is not used since the | ||
/// viewport is resized to fit the [`ProjectsTable`] when the graphics context is initialized. | ||
const VIEWPORT_HEIGHT: f32 = 300.0; | ||
/// A placeholder value for the initial width of the viewport. This value is a placeholder | ||
/// because the value must be provided when creating the viewport, but is not used since the | ||
/// viewport is resized to fit the [`ProjectsTable`] when the graphics context is initialized. | ||
const VIEWPORT_WIDTH: f32 = 400.0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ENTRY_WIDTH ENTRY_HEIGHT and GRID_HEIGHT_RATIO should be taken from the styles.
And isn't the VIEWPORT_WIDTH calculated basing on the scene shape anyway?
aws_region: AwsRegion, | ||
token: AccessToken, | ||
) -> Self { | ||
let base_url = format!("https://{api_gateway_id}.execute-api.{aws_region}.amazonaws.com"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
imho its better to have base_url as a parameter. We will move from apig generated api addresses in few days (the main will be api.cloud.enso.org so personal ones can be api.npekin.cloud.enso.org). This way you can set url as a env variable
This reverts commit a620f5e.
Pull Request Description
This PR is a draft PR while I learn EnsoGL. The eventual goal is to implement the projects list portion of the cloud dashboard in this PR. This PR will implement part of https://www.pivotaltracker.com/n/projects/2539513/stories/183557950
Important Notes
This PR is still really rough and contains a lot of hacks & hard-coded values. The FRP usage is also likely to be suboptimal and need fixing.
Checklist
Please include the following checklist in your PR:
Scala,
Java,
and
Rust
style guides.
./run ide build
and./run ide watch
.