Skip to content

Commit

Permalink
[TGA-39] capi: Embed AuthorProfiles to item resources (#8)
Browse files Browse the repository at this point in the history
* client: Add `exclude_from_content_api` to filter out AuthorProfile fields in ContentAPI

* api: Populate authors field on item update

* capi: Add AuthorProfiles resource

* capi: Override items resource to exclude Author Profiles

* Add capi tests

* Add `URN_DOMAIN` to config and use with with AuthorProfiles
  • Loading branch information
MarkLark86 authored Jan 17, 2023
1 parent 3c54dbb commit 19d28c5
Show file tree
Hide file tree
Showing 11 changed files with 553 additions and 116 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,40 @@ import {IProfileTextFieldConfig} from './interfaces';

type IProps = IConfigComponentProps<IProfileTextFieldConfig>;

const {Spacer} = superdesk.components;


export class ProfileTextFieldConfig extends React.PureComponent<IProps> {
render() {
const config = this.props.config ?? {use_editor_3: false};
const config = this.props.config ?? {
use_editor_3: false,
exclude_from_content_api: false,
};
const {gettext} = superdesk.localization;

return (
<Switch
label={{text: gettext('Use Editor 3')}}
value={config?.use_editor_3 == true}
onChange={(use_editor_3) => {
this.props.onChange({
...config,
use_editor_3: use_editor_3,
});
}}
/>
<Spacer type="vertical" spacing="medium">
<Switch
label={{text: gettext('Use Editor 3')}}
value={config?.use_editor_3 == true}
onChange={(use_editor_3) => {
this.props.onChange({
...config,
use_editor_3: use_editor_3,
});
}}
/>
<Switch
label={{text: gettext('Exclude from ContentAPI')}}
value={config?.exclude_from_content_api == true}
onChange={(exclude_from_content_api) => {
this.props.onChange({
...config,
exclude_from_content_api: exclude_from_content_api,
})
}}
/>
</Spacer>
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {IEditorComponentProps} from 'superdesk-api';

export interface IProfileTextFieldConfig {
use_editor_3: boolean;
exclude_from_content_api: boolean;
}

export type IProfileTextFieldProps = IEditorComponentProps<string | undefined, IProfileTextFieldConfig>;
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export class VocabularyFieldConfig extends React.Component<IProps, IState> {
this.props.onChange({
vocabulary_name: cvs[0]._id,
allow_freetext: this.props.config?.allow_freetext ?? false,
exclude_from_content_api: this.props.config?.exclude_from_content_api ?? false,
});
}
});
Expand All @@ -52,6 +53,7 @@ export class VocabularyFieldConfig extends React.Component<IProps, IState> {
const config = this.props.config ?? {
vocabulary_name: '',
allow_freetext: false,
exclude_from_content_api: false,
};
const {Spacer} = superdesk.components;
const {gettext} = superdesk.localization;
Expand Down Expand Up @@ -82,6 +84,16 @@ export class VocabularyFieldConfig extends React.Component<IProps, IState> {
});
}}
/>
<Switch
label={{text: gettext('Exclude from ContentAPI')}}
value={config?.exclude_from_content_api == true}
onChange={(exclude_from_content_api) => {
this.props.onChange({
...config,
exclude_from_content_api: exclude_from_content_api,
})
}}
/>
</Spacer>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {IEditorComponentProps, IVocabularyItem} from 'superdesk-api';
export interface IVocabularyFieldConfig {
vocabulary_name: string;
allow_freetext: boolean;
exclude_from_content_api: boolean;
}

export type IVocabularyFieldProps = IEditorComponentProps<IVocabularyItem | null | undefined, IVocabularyFieldConfig>;
11 changes: 11 additions & 0 deletions server/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from pathlib import Path
from superdesk.default_settings import strtobool, env
from content_api.app.settings import CONTENTAPI_INSTALLED_APPS

ABS_PATH = str(Path(__file__).resolve().parent)

Expand Down Expand Up @@ -162,3 +163,13 @@
SLACK_BOT_TOKEN = env('SLACK_BOT_TOKEN', '')

APM_SERVICE_NAME = "360info"
URN_DOMAIN = "360info:superdesk"

CONTENTAPI_INSTALLED_APPS = [
module
for module in CONTENTAPI_INSTALLED_APPS
if module != "content_api.items"
] + [
"tga.content_api.items",
"tga.content_api.author_profiles",
]
143 changes: 143 additions & 0 deletions server/tests/author_profiles_capi_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
from copy import copy
from flask import json

from superdesk import get_resource_service
from superdesk.tests import TestCase

from content_api.app import get_app
from content_api.publish import MONGO_PREFIX

from settings import CONTENTAPI_INSTALLED_APPS
from .author_profiles_test import VOCABULARIES, CONTENT_TYPES
from tga.author_profiles import AUTHOR_PROFILE_ROLE

TEST_USER = {
"_id": "abcd123",
"username": "foobar",
"first_name": "Foo",
"last_name": "Bar",
"user_type": "user",
"display_name": "Foo Bar",
"is_enabled": True,
"is_active": True,
}

TEST_SUBSCRIBER = {"_id": "sub1"}


class ContentAPITestCase(TestCase):
def setUp(self):
self.content_api = get_resource_service("content_api")
self.db = self.app.data.mongo.pymongo(prefix=MONGO_PREFIX).db
self.app.config["SECRET_KEY"] = "secret"
config = copy(self.app.config)
config["AMAZON_CONTAINER_NAME"] = None # force gridfs
config["URL_PREFIX"] = ""
config["MEDIA_PREFIX"] = "/assets"
config["CONTENTAPI_INSTALLED_APPS"] = CONTENTAPI_INSTALLED_APPS
self.capi = get_app(config)
self.capi.testing = True
self.subscriber = {"_id": "sub1"}

self.app.data.insert("vocabularies", VOCABULARIES)
self.app.data.insert("content_types", CONTENT_TYPES)
self.app.data.insert("users", [TEST_USER])

def _auth_headers(self, sub=None):
if sub is None:
sub = self.subscriber
service = get_resource_service("subscriber_token")
payload = {"subscriber": sub.get("_id")}
service.create([payload])
token = payload["_id"]
headers = {"Authorization": "Token " + token}
return headers

def _publish_user_profile(self):
item = {
"guid": "foo",
"type": "text",
"authors": [{
"code": [TEST_USER["_id"], "Author Profile"],
"role": AUTHOR_PROFILE_ROLE,
"name": TEST_USER["display_name"],
"parent": TEST_USER["_id"],
"sub_label": TEST_USER["display_name"],
}],
"extra": {
"profile_id": TEST_USER["_id"],
"profile_first_name": "Fooey",
"profile_last_name": "Barey",
"profile_job_title": {
"qcode": "DIRECTOR",
"name": "Director",
"is_active": True,
},
"profile_private_text": "This should not be included in the ContentAPI",
},
}
self.content_api.publish(item, [TEST_SUBSCRIBER])

def _publish_content_item(self):
item = {
"guid": "content_bar",
"type": "text",
"authors": [{
"code": [TEST_USER["_id"], "Writer"],
"role": "writer",
"name": TEST_USER["display_name"],
"parent": TEST_USER["_id"],
"sub_label": TEST_USER["display_name"],
}],
"slugline": "test-content",
"headling": "Test Content",
"body_html": "<p>Test Content</p>",
}
self.content_api.publish(item, [TEST_SUBSCRIBER])

def test_author_profiles_endpoint(self):
headers = self._auth_headers(TEST_SUBSCRIBER)
self._publish_user_profile()
self._publish_content_item()

def assertUser(data):
self.assertEqual(data["first_name"], "Fooey")
self.assertEqual(data["last_name"], "Barey")
self.assertEqual(data["job_title"], "Director")
self.assertEqual(data["profile_id"], TEST_USER["_id"])
self.assertEqual(data["uri"], "http://localhost:5400/author_profiles/abcd123")
self.assertNotIn("private_text", data)

with self.capi.test_client() as c:
response = c.get("author_profiles", headers=headers)
self.assertEqual(200, response.status_code)
data = json.loads(response.data)
self.assertEqual(1, data["_meta"]["total"])
self.assertEqual("foo", data["_items"][0]["original_id"])
assertUser(data["_items"][0])

response = c.get("author_profiles/abcd123", headers=headers)
self.assertEqual(200, response.status_code)
data = json.loads(response.data)
self.assertEqual("foo", data["original_id"])
assertUser(data)

# User Profiles not available through the Content Items endpoint
self.assertEqual(200, c.get("items/content_bar", headers=headers).status_code)
self.assertEqual(404, c.get("items/abcd123", headers=headers).status_code)

response = c.get("items", headers=headers)
self.assertEqual(200, response.status_code)
data = json.loads(response.data)
self.assertEqual(1, data["_meta"]["total"])
self.assertEqual("content_bar", data["_items"][0]["original_id"])

# Make sure the Authors metadata was enhanced using User Profiles
author = data["_items"][0]["authors"][0]
self.assertEqual("abcd123", author["code"])
self.assertEqual("Fooey", author["first_name"])
self.assertEqual("Barey", author["last_name"])
self.assertEqual("Director", author["job_title"])
self.assertEqual("writer", author["role"])
self.assertEqual("urn:360info:superdesk:user:abcd123", author["uri"])
self.assertNotIn("private_text", author)
Loading

0 comments on commit 19d28c5

Please sign in to comment.