Skip to content
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

Add Format to the Scala CAPI client #312

Merged
merged 10 commits into from
Mar 1, 2021
Merged

Add Format to the Scala CAPI client #312

merged 10 commits into from
Mar 1, 2021

Conversation

buck06191
Copy link
Contributor

@buck06191 buck06191 commented Feb 24, 2021

What does this change?

Rendering platforms have moved to use the Format type to describe the
format that rendered data should take. This includes three specific
fields:

  • Display
  • Design
  • Theme (Pillar/Special)

Currently the platforms handle the decision logic around how to
construct this object on platform but ideally this will be done upstream
in order to standardise across platforms and to simplify the parsing of
data.

Here I upstream the Format type to the CAPI Scala client.

Changes:

  • Introduce the Display, Theme and Design traits and their
    associated case objects.
  • Refactor CAPIModelEnrichment to move code shared by RichContent
    and Format to the upper scope.
  • Reproduce the decision logic used by platforms to construct the
    Format object.

How to test

I've added unit tests for this and they can be run using whatever testing process you prefer e.g. sbt test

How can we measure success?

Improved DevX for anyone working with the Format type, and ideally tidy refactors of the platform code to use this instead of multiple checks and flags.

Have we considered potential risks?

I don't think there are any risks. This doesn't affect the raw data and shouldn't remove backwards compatibility.

Rendering platforms have moved to use the `Format` type to describe the
format that rendered data should take. This includes three specific
fields:
 - `Display`
 - `Design`
 - `Theme (Pillar/Special)`

Currently the platforms handle the decision logic around how to
construct this object on platform but ideally this will be done upstream
in order to standardise across platforms and to simplify the parsing of
data.

In this commit I upstream the `Format` type to the CAPI Scala client.

Changes:
- Introduce the `Display`, `Theme` and `Design` traits and their
associated `case object`s.
- Refactor `CAPIModelEnrichment` to move code shared by `RichContent`
and `Format` to the upper scope.
- Reproduce the decision logic used by platforms to construct the
`Format` object.
Copy link
Contributor

@oliverlloyd oliverlloyd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like one of those nice clean input > output pieces of work where it might be useful to have some tests laid out at an earlier stage so that you work to make the tests pass.

I'm not normally a strict TDD evangelist but this does feel like a great place to take advantage of that pattern

- Replicate the code used in Frontend and apps-rendering to determine
the `ShowcaseDisplay` `Display`.
- Tidy up code to make it easier to parse.
@buck06191 buck06191 marked this pull request as ready for review February 26, 2021 12:40
@shtukas
Copy link
Contributor

shtukas commented Feb 26, 2021

Looks good +1

@tonytw1
Copy link
Contributor

tonytw1 commented Feb 26, 2021

This textbook rejection of a PR like this is "putting domain logic like this in a language specific API client is bad".

But this PR has been extensively reviewed by the submitting teams and their motive is deduplication of code which is already in CAPI consumers.
This PR does the hard work of getting the different consumers to agree what their domain model should look like.
We should encourage this and offer to upstream these new domain model from the client into CAPI.

CAPI should approve this and we can roll forward to get it into the ideal place.

@tonytw1
Copy link
Contributor

tonytw1 commented Feb 26, 2021

Furthermore.... This will be a versioned release so if the teams submitting this PR can consume it immediately they will be doing the regression testing. Other consumers can stay on or revert back last good release if this causes issues.


def isComment: ContentFilter = c => tagExistsWithId("tone/comment")(c) || tagExistsWithId("tone/letters")(c)
val isDeadBlog: ContentFilter = content => !isLiveBloggingNow(content) && tagExistsWithId("tone/minutebyminute")(content)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll need guardian/types#83 to match up with Types

isReview -> ReviewDesign,
tagExistsWithId("tone/analysis") -> AnalysisDesign,
isCommentDesign -> CommentDesign,
tagExistsWithId("tone/features") -> FeatureDesign,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good question. We don't currently have Letter as a Design in our decideDesign.ts function.
@oliverlloyd do you know if whether @guardian/types is correct or if DCR is?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@guardian/types trumps DCR. We haven't yet updated the version of the /types package we're using but when we do we will add support for Letter

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Letter is a recent addition: guardian/types#69

tagExistsWithId("tone/matchreports") -> MatchReportDesign,
tagExistsWithId("tone/interview") -> InterviewDesign,
tagExistsWithId("tone/editorials") -> EditorialDesign,
tagExistsWithId("tone/quizzes") -> QuizDesign,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we also missing Interactive design? https://github.com/guardian/types/blob/main/src/format.ts#L32

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we technically support an Interactive design on DCR? It's not in our decideDesign function at least. If it does need supporting, I can add it in here and that way we'll just need to add it to the decideDesign function to translate it from the Scala JSON output to whatever Typescript needs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the be an Interactive Design type? I'm not sure what it represents? @JamieB-gu is there a need for this in Apps or could we remove it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's in the apps-rendering decision functions, so I'll add it in. If it's in here and then removed, it's not a difficult thing to rip out (which is part of the motivation for this work in the first place).

I think that the work here should be done with a focus on targeting a specific version of @guardian/types, and at this point this is ever so slightly ahead of the current version by having different Designs for dead and live blogs, as well as removing the Column display type. But after this initial step of aligning the two, the goal should be to update the two in parallel, with references to the appropriate version of each within the changelog

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Design.Interactive is at the moment derived from ContentType.INTERACTIVE. I believe it's there so that we can build the empty placeholder articles for full-page interactives (ng-interactive/type/interactive - example here). Note that we explicitly render these without any styles because the interactive code takes over.

I'm curious as to how DCR models these kinds of articles if it doesn't use the Design 🤔?

Comment on lines +189 to +191
isImmersive -> ImmersiveDisplay,
isShowcase -> ShowcaseDisplay,
isNumberedList -> NumberedListDisplay

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A note here that we need to update https://github.com/guardian/types/blob/main/src/format.ts#L37-L43 to remove Column Display.

Copy link
Member

@JustinPinner JustinPinner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with the commentary where we acknowledge that this isn't the ideal resting place for this functionality, and I have no objections to using it as a temporary measure prior to relocating into tools and CAPI. It looks like a well enough isolated change and test coverage looks good too. I can't comment on the intricacies of relationships between types / DCR / dotcom etc, so I'll approve on the basis that you're all happy that everything is covered before you merge 👍

@tonytw1 tonytw1 merged commit 0c40ed2 into guardian:master Mar 1, 2021
@JamieB-gu
Copy link
Contributor

We should encourage this and offer to upstream these new domain model from the client into CAPI.

temporary measure prior to relocating into tools and CAPI

Does this mean moving it into the Thrift definitions?

Copy link
Contributor

@JamieB-gu JamieB-gu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really nice! Very pleased that this is starting to make its way upstream 😀


val isImmersive: ContentFilter = c => c.fields.flatMap(_.displayHint).contains("immersive")
val isMedia: ContentFilter = content => tagExistsWithId("type/audio")(content) || tagExistsWithId("type/video")(content) || tagExistsWithId("type/gallery")(content)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AR also has type/picture in this category, see guardian/apps-rendering#1146.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this. I'll add it to a card for changes to make on a future version


val defaultDesign: Design = ArticleDesign

val isPhotoEssay: ContentFilter = content => content.fields.flatMap(_.displayHint).contains("photoessay")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you want to reuse your displayHintExistsWithName function here?

val isPhotoEssay: ContentFilter = content => displayHintExistsWithName("photoessay")(content)

tagExistsWithId("tone/editorials") -> EditorialDesign,
tagExistsWithId("tone/quizzes") -> QuizDesign,
isInteractive -> InteractiveDesign,
isPhotoEssay -> PhotoEssayDesign,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@oliverlloyd are we keeping Photo Essays as a separate Design, or is this temporary until we subsume MultiImage into a normal body element?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is still at least one design aspects of Photo Essays that is unique: coloured captions. So unless we lose that it's here to stay


def isPillar(pillar: String): ContentFilter = content => content.pillarName.contains(pillar)

val isSpecialReport: ContentFilter = content => content.tags.exists(t => specialReportTags(t.id))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens for special reports assigned in the targeting tool?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I did this off the back of different decision functions I found across the different codebases. If there's one missing, then we'll need to implement it in a future version. I imagine the process of integrating this all into the platforms will highlight missing areas and then we can update this to reflect that .

@buck06191
Copy link
Contributor Author

We should encourage this and offer to upstream these new domain model from the client into CAPI.

temporary measure prior to relocating into tools and CAPI

Does this mean moving it into the Thrift definitions?

It should do, yes. I guess we (platforms) all need to have a discussion as to how we do this, then standardise on it and move forwards

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants