-
Notifications
You must be signed in to change notification settings - Fork 234
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
Render past successful deploys on the releases overview #2897
base: master
Are you sure you want to change the base?
Conversation
app/helpers/application_helper.rb
Outdated
next unless deploy.references?(reference) | ||
label = (deploy.active? ? "label-warning" : "label-success") | ||
# The first deploy is the most recent one. | ||
next unless deploy = stage.deploys.where(reference: reference).first |
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.
Instead of doing n where queries, would it be better to do something like:
deploys_hash = Deploy.joins(:stage).where(stage_id: stages.pluck(:id)).where(reference: 'master').group_by(&:stage_id)
stages.each do |stage|
next unless deploy = deploys_hash[stage.id]&.first
...
end
?
samson(dev)> puts Benchmark.measure { deployed_or_running_list(stages, reference) }
Deploy Load (0.6ms) SELECT `deploys`.* FROM `deploys` WHERE `deploys`.`deleted_at` IS NULL AND `deploys`.`stage_id` = 4 AND `deploys`.`reference` = 'master' ORDER BY `deploys`.`id` DESC LIMIT 1
Deploy Load (0.5ms) SELECT `deploys`.* FROM `deploys` WHERE `deploys`.`deleted_at` IS NULL AND `deploys`.`stage_id` = 2 AND `deploys`.`reference` = 'master' ORDER BY `deploys`.`id` DESC LIMIT 1
Deploy Load (0.4ms) SELECT `deploys`.* FROM `deploys` WHERE `deploys`.`deleted_at` IS NULL AND `deploys`.`stage_id` = 3 AND `deploys`.`reference` = 'master' ORDER BY `deploys`.`id` DESC LIMIT 1
Deploy Load (0.4ms) SELECT `deploys`.* FROM `deploys` WHERE `deploys`.`deleted_at` IS NULL AND `deploys`.`stage_id` = 1 AND `deploys`.`reference` = 'master' ORDER BY `deploys`.`id` DESC LIMIT 1
Deploy Load (0.4ms) SELECT `deploys`.* FROM `deploys` WHERE `deploys`.`deleted_at` IS NULL AND `deploys`.`stage_id` = 5 AND `deploys`.`reference` = 'master' ORDER BY `deploys`.`id` DESC LIMIT 1
Deploy Load (0.3ms) SELECT `deploys`.* FROM `deploys` WHERE `deploys`.`deleted_at` IS NULL AND `deploys`.`stage_id` = 6 AND `deploys`.`reference` = 'master' ORDER BY `deploys`.`id` DESC LIMIT 1
Deploy Load (0.4ms) SELECT `deploys`.* FROM `deploys` WHERE `deploys`.`deleted_at` IS NULL AND `deploys`.`stage_id` = 7 AND `deploys`.`reference` = 'master' ORDER BY `deploys`.`id` DESC LIMIT 1
0.005582 0.000476 0.006058 ( 0.008085)
=> nil
samson(dev)> puts Benchmark.measure { deployed_or_running_list2(stages, reference) }
Deploy Load (1.0ms) SELECT `deploys`.* FROM `deploys` INNER JOIN `stages` ON `stages`.`id` = `deploys`.`stage_id` AND `stages`.`deleted_at` IS NULL WHERE `deploys`.`deleted_at` IS NULL AND `deploys`.`stage_id` IN (4, 2, 3, 1, 5, 6, 7) AND `deploys`.`reference` = 'master' ORDER BY `deploys`.`id` DESC
0.002177 0.000184 0.002361 ( 0.003161)
=> nil
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 depends – if we cache each row in the table based on the Release, and make sure to touch
the release whenever it's deployed, then an n + 1 makes sense, as the common case will be that almost all of the releases will be cached and therefore won't trigger any queries. That's preferable to naively preloading everything.
I'd like to add that caching in a separate PR, though, unless it's a blocker for this one.
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.
something like this ?
referenced = stage.deploys.where(ref).first
deploy = stage.last_deploy
if referenced == deploy
if deploy.success?
'success'
elsif deploy.running?
'warning'
else
'default'
end
end
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 quite – I'll push an updated version.
@@ -700,11 +700,23 @@ def render | |||
assert html.html_safe? | |||
end | |||
|
|||
it "renders succeeded deploys" do | |||
it "renders current, succeeded deploys" do | |||
html = deployed_or_running_list(stage_list, "staging") |
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.
Might be nice to use assert_sql_queries
here to lock in the number of queries in case someone comes along and changes this later.
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.
👍
app/helpers/application_helper.rb
Outdated
end | ||
end | ||
|
||
if label.present? |
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.
prefer consistent if thing
instead of if thing.nil?
+ if thing.present?
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.
👍
app/helpers/application_helper.rb
Outdated
end | ||
|
||
if label.present? | ||
text = stage.name.html_safe |
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 smells like xss ... I can name my stage <script>foo</script>
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.
👍
app/helpers/application_helper.rb
Outdated
html << " " | ||
pieces = stages.map do |stage| | ||
# The first deploy is the most recent one. | ||
deploy = stage.deploys.where(reference: reference).first |
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 should fetch all the deploys in a single query with stage.project.deploys.select('max(id) as id').where(stage_id: stages.map(&:id), reference: reference).group(:stage_id)
?
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.
Won't that just give the Deploy id, not the full deploy?
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.
Okay, I've pushed an optimized version that only does 1 query per row.
It's valuable to be able to see at a glance whether a given release has been successfully deployed to a stage in the past, e.g. when you want to know if a build stage has completed.
Don't use a reference that's identical to the stage name, that's confusing.
443898b
to
e52df04
Compare
@grosser one more review? |
We need two queries.
select("MAX(id) AS id, stage_id") | ||
|
||
# Fetch the actual deploys. | ||
deploys = Deploy.where(id: deploys_and_stages.map(&:id)) |
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 only use the id and stage_id ... so we could avoid an extra query ?
app/models/deploy.rb
Outdated
end | ||
|
||
# Returns a Hash of Stage => Deploy for the given reference and stages. | ||
def self.of_reference_in_stages(reference, stages) |
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.
grouped hashes are usually built from methods with by
not of
text << stage.name | ||
html << content_tag(:span, text, class: "label #{label} release-stage") | ||
html << " " | ||
deploys = Deploy.of_reference_in_stages(reference, stages) |
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.
deploy_lookup = Deploy.where(reference: reference).where(stage_id: stages.map(&:id)).group(:stage_id).select("MAX(id) AS id, stage_id").group_by(&:stage_id)
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 really dislike putting SQL in the view layer – it makes testing so much harder. I'd very much prefer to keep this in the model.
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 query logic is closely tied to the compare logic, so could lead to bugs/get out of sync when it's extracted to the model ... but I don't care that much
more important: the group_by line seems much easier to read (assuming it works as before) ?
[stage, deploy] | ||
end.to_h | ||
end | ||
|
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.
all pretty funky and not reused, so I'd keep it inline
app/models/deploy.rb
Outdated
@@ -171,6 +171,30 @@ def self.after(deploy) | |||
where("#{table_name}.id > ?", deploy.id) | |||
end | |||
|
|||
def self.in_stages(stages) | |||
where(stage_id: stages.map(&:id)) | |||
end |
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.
kinda simple query I'd rather keep inline
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.
👍
elsif deploy.running? | ||
"label-warning" | ||
elsif deploy.succeeded? | ||
if deploy == stage.last_deploy |
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.
can't we build the same map for all last deploys from these stages
... so we end up with 2 queries ? (+ job fetching queries)
... for another PR: we could also prefetch the jobs for all deploys to save the job lookups that are caused by the status calls
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 mean – once you've called Stage#last_deploy
, that should be memoized, right? If it's not, I can easily add that. The stages don't change between the release rows, so we shouldn't need to do any fancy work there.
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.
yes, but it's being done once per stage -> n+1
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.
How many stages do we typically have? Is it worth the added complexity?
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.
projects can have tons of stages ... this PR is not making it worse, so keep it like it is and open a BRE issue to make this more performant ?
It's valuable to be able to see at a glance whether a given release has been successfully deployed to a stage in the past, e.g. when you want to know if a build stage has completed.
The past, successful deploys are displayed with a grey background.
/cc @zendesk/samson
Tasks
Risks