Skip to content

Commit

Permalink
feat(MyPortal): [#174801195] Service detail CTA (#2217)
Browse files Browse the repository at this point in the history
* [#174859517] Checks token_name on CTA actions

* [#174859517] Connects cta component to redux for reading right metadata

* Fixes

* Removes dispatch from component props

* [#174801195] Draft component for service CTA

* Refactoring getCta to handle valid CTA

* Fixes

* [#174801195] Adds CTA bar support on Service Detail screen

* Refactoring

* Updates comment

* Update tests

* Add tests on extended validation logic

* Refactoring and some tests

* fixes

* minor refactoring

* Minor refactoring

Co-authored-by: Matteo Boschi <[email protected]>
  • Loading branch information
CrisTofani and Undermaken authored Sep 25, 2020
1 parent 89859af commit 06fbb95
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 9 deletions.
17 changes: 16 additions & 1 deletion ts/screens/services/ServiceDetailsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { ServicePublic } from "../../../definitions/backend/ServicePublic";
import iOSStoreBadge from "../../../img/badges/app-store-badge.png";
import playStoreBadge from "../../../img/badges/google-play-badge.png";
import ButtonDefaultOpacity from "../../components/ButtonDefaultOpacity";
import MessageNestedCTABar from "../../components/messages/MessageNestedCTABar";
import OrganizationHeader from "../../components/OrganizationHeader";
import BaseScreenComponent, {
ContextualHelpPropsMarkdown
Expand Down Expand Up @@ -50,6 +51,7 @@ import {
} from "../../store/reducers/profile";
import { GlobalState } from "../../store/reducers/types";
import customVariables from "../../theme/variables";
import { getServiceCTA } from "../../utils/messages";
import {
EnabledChannels,
getBlockedChannels,
Expand Down Expand Up @@ -494,7 +496,8 @@ class ServiceDetailsScreen extends React.Component<Props, State> {
);
};

private hasChannel = (channel: NotificationChannelEnum) => fromNullable(this.service.available_notification_channels)
private hasChannel = (channel: NotificationChannelEnum) =>
fromNullable(this.service.available_notification_channels)
.map(anc => anc.indexOf(channel) !== -1)
.getOrElse(true);

Expand Down Expand Up @@ -650,6 +653,7 @@ class ServiceDetailsScreen extends React.Component<Props, State> {
const potServiceMetadata =
this.props.content.servicesMetadata.byId[serviceId] || pot.none;

const maybeCTA = getServiceCTA(potServiceMetadata);
return (
<BaseScreenComponent
goBack={this.props.navigation.goBack}
Expand Down Expand Up @@ -695,6 +699,17 @@ class ServiceDetailsScreen extends React.Component<Props, State> {
)}
<View spacer={true} extralarge={true} />
</Content>
{maybeCTA.isSome() && (
<View footer={true} style={styles.flexRow}>
<MessageNestedCTABar
ctas={maybeCTA.value}
xsmall={false}
dispatch={this.props.dispatch}
serviceMetadata={potServiceMetadata}
service={service}
/>
</View>
)}
</BaseScreenComponent>
);
}
Expand Down
59 changes: 59 additions & 0 deletions ts/utils/__tests__/messages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { CTA, CTAS } from "../../types/MessageCTA";
import {
cleanMarkdownFromCTAs,
getCTA,
getServiceCTA,
isCtaActionValid,
MaybePotMetadata
} from "../messages";
Expand Down Expand Up @@ -256,6 +257,64 @@ const test2CTA = (
}
};

describe("getServiceCTA", () => {
it("Should extract a valid CTA for the service", () => {
const CTA_SERVICE = `---
it:
cta_1:
text: "Interno con params"
action: "ioit://SERVICE_WEBVIEW?url=http://192.168.1.10:3000/myportal_playground.html"
en:
cta_1:
text: "Internal with params"
action: "ioit://SERVICE_WEBVIEW?url=http://192.168.1.10:3000/myportal_playground.html"
---`;
const validServiceMetadata: MaybePotMetadata = pot.some({
...serviceMetadataBase,
token_name: "myPortalToken",
cta: CTA_SERVICE
});
const maybeCTA = getServiceCTA(validServiceMetadata);
expect(maybeCTA.isSome()).toBeTruthy();
if (maybeCTA.isSome()) {
const ctas = maybeCTA.value;
expect(ctas.cta_1).toBeDefined();
expect(ctas.cta_1.text).toEqual("Interno con params");
expect(ctas.cta_1.action).toEqual(
"ioit://SERVICE_WEBVIEW?url=http://192.168.1.10:3000/myportal_playground.html"
);
}
});

it("Should not extract a CTA for the service without cta attribute", () => {
const invalidServiceMetadata: MaybePotMetadata = pot.some({
...serviceMetadataBase,
token_name: "myPortalToken"
});
const maybeCTA = getServiceCTA(invalidServiceMetadata);
expect(maybeCTA.isSome()).toBeFalsy();
});

it("Should not extract a CTA for the service without token_name attribute", () => {
const CTA_SERVICE = `---
it:
cta_1:
text: "Interno con params"
action: "ioit://SERVICE_WEBVIEW?url=http://192.168.1.10:3000/myportal_playground.html"
en:
cta_1:
text: "Internal with params"
action: "ioit://SERVICE_WEBVIEW?url=http://192.168.1.10:3000/myportal_playground.html"
---`;
const invalidServiceMetadata: MaybePotMetadata = pot.some({
...serviceMetadataBase,
cta: CTA_SERVICE
});
const maybeCTA = getServiceCTA(invalidServiceMetadata);
expect(maybeCTA.isSome()).toBeFalsy();
});
});

describe("isCtaActionValid", () => {
it("should be a valid action for service", () => {
const validServiceMetadata: MaybePotMetadata = pot.some({
Expand Down
43 changes: 35 additions & 8 deletions ts/utils/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,20 @@ const internalRoutePredicates: Map<
[ROUTES.SERVICE_WEBVIEW, hasMetadataTokenName]
]);

const extractCTA = (
text: string,
serviceMetadata: MaybePotMetadata
): Option<CTAS> =>
fromPredicate((t: string) => FM.test(t))(text)
.map(m => FM<MessageCTA>(m).attributes)
.chain(attrs =>
CTAS.decode(attrs[getLocalePrimaryWithFallback()]).fold(
_ => none,
// check if the decoded actions are valid
cta => (hasCtaValidActions(cta, serviceMetadata) ? some(cta) : none)
)
);

/**
* extract the CTAs if they are nested inside the message markdown content
* if some CTAs are been found, the localized version will be returned
Expand All @@ -223,16 +237,29 @@ const internalRoutePredicates: Map<
export const getCTA = (
message: CreatedMessageWithContent,
serviceMetadata?: MaybePotMetadata
): Option<CTAS> => extractCTA(message.content.markdown, serviceMetadata);

/**
* extract the CTAs from a string given in serviceMetadata such as the front-matter of the message
* if some CTAs are been found, the localized version will be returned
* @param cta
* @param serviceMetadata
*/
export const getServiceCTA = (
serviceMetadata: MaybePotMetadata
): Option<CTAS> =>
fromPredicate((t: string) => FM.test(t))(message.content.markdown)
.map(m => FM<MessageCTA>(m).attributes)
.chain(attrs =>
CTAS.decode(attrs[getLocalePrimaryWithFallback()]).fold(
_ => none,
// check if the decoded actions are valid
cta => (hasCtaValidActions(cta, serviceMetadata) ? some(cta) : none)
fromNullable(serviceMetadata)
.map(s =>
pot.getOrElse(
pot.map(s, metadata =>
fromNullable(metadata)
.chain(m => fromNullable(m.cta))
.getOrElse("")
),
""
)
);
)
.chain(cta => extractCTA(cta, serviceMetadata));

/**
* return a boolean indicating if the cta action is valid or not
Expand Down

0 comments on commit 06fbb95

Please sign in to comment.