Skip to content

Commit e84e171

Browse files
authored
landing_job: add landed_revisions field (bug 1834885) (#318)
- add diff_id to revision_landing_job table - add method to set diff IDs and call it at landing time - add landed_revisions helper method to fetch revision_to_diff_ids - add a test that lands two versions of a revision - fix variable names in transplants.warning_previously_landed
1 parent 5ea6294 commit e84e171

File tree

6 files changed

+164
-8
lines changed

6 files changed

+164
-8
lines changed

landoapi/api/transplants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,7 @@ def post(phab: PhabricatorClient, data: dict):
401401

402402
# Submit landing job.
403403
job.status = LandingJobStatus.SUBMITTED
404+
job.set_landed_revision_diffs()
404405
db.session.commit()
405406

406407
logger.info(f"New landing job {job.id} created for {landing_repo.tree} repo.")

landoapi/models/landing_job.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,20 @@ class LandingJob(Base):
135135
)
136136

137137
@property
138-
def landing_path(self) -> list[tuple[int, int]]:
139-
return [(revision.revision_id, revision.diff_id) for revision in self.revisions]
138+
def landed_revisions(self) -> dict:
139+
"""Return revision and diff ID mapping associated with the landing job."""
140+
revision_ids = [revision.id for revision in self.revisions]
141+
revision_to_diff_ids_query = (
142+
revision_landing_job.select()
143+
.join(Revision)
144+
.where(
145+
revision_landing_job.c.revision_id.in_(revision_ids),
146+
revision_landing_job.c.landing_job_id == self.id,
147+
)
148+
.with_only_columns(Revision.revision_id, revision_landing_job.c.diff_id)
149+
.order_by(revision_landing_job.c.index)
150+
)
151+
return dict(list(db.session.execute(revision_to_diff_ids_query)))
140152

141153
@property
142154
def serialized_landing_path(self):
@@ -147,7 +159,7 @@ def serialized_landing_path(self):
147159
"revision_id": "D{}".format(revision_id),
148160
"diff_id": diff_id,
149161
}
150-
for revision_id, diff_id in self.landing_path
162+
for revision_id, diff_id in self.landed_revisions.items()
151163
]
152164
else:
153165
return [
@@ -249,6 +261,19 @@ def sort_revisions(self, revisions: list[Revision]):
249261
.values(index=index)
250262
)
251263

264+
def set_landed_revision_diffs(self):
265+
"""Assign diff_ids, if available, to each association row."""
266+
# Update association table records with current diff_id values.
267+
for revision in self.revisions:
268+
db.session.execute(
269+
revision_landing_job.update()
270+
.where(
271+
revision_landing_job.c.landing_job_id == self.id,
272+
revision_landing_job.c.revision_id == revision.id,
273+
)
274+
.values(diff_id=revision.diff_id)
275+
)
276+
252277
def transition_status(
253278
self,
254279
action: LandingJobAction,

landoapi/models/revisions.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,13 @@ class DiffWarningGroup(enum.Enum):
3636

3737

3838
# Association table with custom "index" column to guarantee sorting of revisions.
39+
# The diff_id column is used as a transaction record of the diff_id at landing time.
3940
revision_landing_job = db.Table(
4041
"revision_landing_job",
4142
db.Column("landing_job_id", db.ForeignKey("landing_job.id")),
4243
db.Column("revision_id", db.ForeignKey("revision.id")),
4344
db.Column("index", db.Integer),
45+
db.Column("diff_id", db.Integer, nullable=True),
4446
)
4547

4648

landoapi/transplants.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -224,13 +224,11 @@ def warning_previously_landed(*, revision, diff, **kwargs):
224224
if job is None:
225225
return None
226226

227-
revision_to_diff_id = {
228-
revision.revision_id: revision.diff_id for revision in job.revisions
229-
}
227+
revision_to_diff_id = job.landed_revisions
230228
if job.revision_to_diff_id:
231229
legacy_data = {
232-
int(revision_id): int(diff_id)
233-
for revision_id, diff_id in job.revision_to_diff_id.items()
230+
int(legacy_revision_id): int(legacy_diff_id)
231+
for legacy_revision_id, legacy_diff_id in job.revision_to_diff_id.items()
234232
}
235233
revision_to_diff_id.update(legacy_data)
236234
landed_diff_id = revision_to_diff_id[revision_id]
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""add revision_landing_job.diff_id
2+
3+
Revision ID: 50ffadceca83
4+
Revises: a8cd4d43c4c3
5+
Create Date: 2023-05-25 01:50:36.755884
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
12+
# revision identifiers, used by Alembic.
13+
revision = "50ffadceca83"
14+
down_revision = "a8cd4d43c4c3"
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
op.add_column(
22+
"revision_landing_job", sa.Column("diff_id", sa.Integer(), nullable=True)
23+
)
24+
# ### end Alembic commands ###
25+
26+
27+
def downgrade():
28+
# ### commands auto generated by Alembic - please adjust! ###
29+
op.drop_column("revision_landing_job", "diff_id")
30+
# ### end Alembic commands ###

tests/test_transplants.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,106 @@ def test_integrated_transplant_simple_stack_saves_data_in_db(
711711
(r3["id"], d3["id"]),
712712
]
713713
assert job.status == LandingJobStatus.SUBMITTED
714+
assert job.landed_revisions == {1: 1, 2: 2, 3: 3}
715+
716+
717+
def test_integrated_transplant_updated_diff_id_reflected_in_landed_revisions(
718+
db,
719+
client,
720+
phabdouble,
721+
auth0_mock,
722+
release_management_project,
723+
register_codefreeze_uri,
724+
):
725+
"""
726+
Perform a simple test but with two landing jobs for the same revision.
727+
728+
The test is similar to the one in
729+
test_integrated_transplant_simple_stack_saves_data_in_db but submits an additional
730+
landing job for an updated revision diff.
731+
"""
732+
repo = phabdouble.repo()
733+
user = phabdouble.user(username="reviewer")
734+
735+
d1a = phabdouble.diff()
736+
r1 = phabdouble.revision(diff=d1a, repo=repo)
737+
phabdouble.reviewer(r1, user)
738+
739+
response = client.post(
740+
"/transplants",
741+
json={
742+
"landing_path": [
743+
{"revision_id": "D{}".format(r1["id"]), "diff_id": d1a["id"]},
744+
]
745+
},
746+
headers=auth0_mock.mock_headers,
747+
)
748+
assert response.status_code == 202
749+
assert response.content_type == "application/json"
750+
assert "id" in response.json
751+
job_1_id = response.json["id"]
752+
753+
# Ensure DB access isn't using uncommitted data.
754+
db.session.close()
755+
756+
# Get LandingJob object by its id.
757+
job = LandingJob.query.get(job_1_id)
758+
assert job.id == job_1_id
759+
assert [(revision.revision_id, revision.diff_id) for revision in job.revisions] == [
760+
(r1["id"], d1a["id"]),
761+
]
762+
assert job.status == LandingJobStatus.SUBMITTED
763+
assert job.landed_revisions == {r1["id"]: d1a["id"]}
764+
765+
# Cancel job.
766+
response = client.put(
767+
f"/landing_jobs/{job.id}",
768+
json={"status": "CANCELLED"},
769+
headers=auth0_mock.mock_headers,
770+
)
771+
772+
job = LandingJob.query.get(job_1_id)
773+
assert job.status == LandingJobStatus.CANCELLED
774+
775+
d1b = phabdouble.diff(revision=r1)
776+
phabdouble.reviewer(r1, user)
777+
response = client.post(
778+
"/transplants",
779+
json={
780+
"landing_path": [
781+
{"revision_id": "D{}".format(r1["id"]), "diff_id": d1b["id"]},
782+
]
783+
},
784+
headers=auth0_mock.mock_headers,
785+
)
786+
787+
job_2_id = response.json["id"]
788+
789+
# Ensure DB access isn't using uncommitted data.
790+
db.session.close()
791+
792+
# Get LandingJob objects by their ids.
793+
job_1 = LandingJob.query.get(job_1_id)
794+
job_2 = LandingJob.query.get(job_2_id)
795+
796+
# The Revision objects always track the latest revisions.
797+
assert [
798+
(revision.revision_id, revision.diff_id) for revision in job_1.revisions
799+
] == [
800+
(r1["id"], d1b["id"]),
801+
]
802+
803+
assert [
804+
(revision.revision_id, revision.diff_id) for revision in job_2.revisions
805+
] == [
806+
(r1["id"], d1b["id"]),
807+
]
808+
809+
assert job_1.status == LandingJobStatus.CANCELLED
810+
assert job_2.status == LandingJobStatus.SUBMITTED
811+
812+
assert job_1.landed_revisions == {r1["id"]: d1a["id"]}
813+
assert job_2.landed_revisions == {r1["id"]: d1b["id"]}
714814

715815

716816
def test_integrated_transplant_with_flags(

0 commit comments

Comments
 (0)