Skip to content

Commit

Permalink
Merge pull request #43 from Financial-Times/feature/UPPSF-836-change-…
Browse files Browse the repository at this point in the history
…replace-endpoint

Feature/uppsf 836 change replace endpoint
  • Loading branch information
ivan-p-nikolov authored Oct 31, 2019
2 parents 4126bc7 + fa39a18 commit bbc057f
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 16 deletions.
16 changes: 7 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,9 @@ Draft Annotations API is a microservice that provides access to draft annotation
Download the source code, dependencies and test dependencies:

```
mkdir $GOPATH/src/github.com/Financial-Times/draft-annotations-api
cd $GOPATH/src/github.com/Financial-Times
git clone https://github.com/Financial-Times/draft-annotations-api.git
cd draft-annotations-api
GO111MODULE=on go build -mod=readonly
go build -mod=readonly
```

## Running locally
Expand Down Expand Up @@ -51,11 +49,11 @@ Options:

1. Either using curl:

curl http://localhost:8080/draft/content/b7b871f6-8a89-11e4-8e24-00144feabdc0/annotations | json_pp
curl http://localhost:8080/drafts/content/b7b871f6-8a89-11e4-8e24-00144feabdc0/annotations | json_pp

1. Or using [httpie](https://github.com/jkbrzt/httpie):

http GET http://localhost:8080/draft/content/b7b871f6-8a89-11e4-8e24-00144feabdc0/annotations
http GET http://localhost:8080/drafts/content/b7b871f6-8a89-11e4-8e24-00144feabdc0/annotations

## Build and deployment

Expand All @@ -73,7 +71,7 @@ For a full description of API endpoints for the service, please see the [Open AP
Using curl:

```
curl http://localhost:8080/draft/content/{content-uuid}/annotations | jq
curl http://localhost:8080/drafts/content/{content-uuid}/annotations | jq
```

A GET request on this endpoint fetches the draft annotations for a specific piece of content by calling
Expand Down Expand Up @@ -121,7 +119,7 @@ This is an example response body:
Using curl:

```
curl http://localhost:8080/draft/content/{content-uuid}/annotations -X POST --data '{
curl http://localhost:8080/drafts/content/{content-uuid}/annotations -X POST --data '{
"id": "http://www.ft.com/thing/d7de27f8-1633-3fcc-b308-c95a2ad7d1cd"
"predicate": "http://www.ft.com/ontology/annotation/about",
}'
Expand Down Expand Up @@ -214,7 +212,7 @@ The listings below shows an example of a canonicalized response.
Using curl:

```
curl http://localhost:8080/draft/content/{content-uuid}/annotations/{concept-uuid} | jq
curl http://localhost:8080/drafts/content/{content-uuid}/annotations/{concept-uuid} | jq
```

A DELETE request on this endpoint deletes all the annotations for a single concept from the editorially curated published annotations for a specific piece of content. To retrieve these specific annotations it calls [UPP Public Annotations API](https://github.com/Financial-Times/public-annotations-api) using the "lifecycle" parameter.
Expand Down Expand Up @@ -245,7 +243,7 @@ This is an example response body:
Using curl:

```
curl http://localhost:8080/draft/content/{content-uuid}/annotations/{concept-uuid} -X PATCH --data '{
curl http://localhost:8080/drafts/content/{content-uuid}/annotations/{concept-uuid} -X PATCH --data '{
"id": "http://www.ft.com/thing/d7de27f8-1633-3fcc-b308-c95a2ad7d1cd"
}'
```
Expand Down
63 changes: 60 additions & 3 deletions _ft/api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ paths:
- http://www.ft.com/ontology/annotation/mentions
- http://www.ft.com/ontology/classification/isClassifiedBy
- http://www.ft.com/ontology/implicitlyClassifiedBy
- http://www.ft.com/ontology/hasBrand
type:
type: string
description: The type of concept, i.e. Person, Organisation, Topic
Expand Down Expand Up @@ -169,7 +170,7 @@ paths:
description: The content with the specified UUID was not found.
500:
description: Internal server error
/drafts/content/{uuid}/annotations/{conceptUuid}:
/drafts/content/{uuid}/annotations/{conceptUUID}:
delete:
summary: Delete all annotations with a given concept from the draft annotations for a specified content
description: Returns the draft annotations for the content after the delete operation.
Expand All @@ -184,7 +185,7 @@ paths:
required: true
type: string
x-example: 8df16ae8-0dfd-4859-a5ff-eeb9644bed35
- name: conceptUuid
- name: conceptUUID
in: path
description: The UUID of the concept to be deleted
required: true
Expand All @@ -204,6 +205,62 @@ paths:
description: Content with the specified UUID was not found
500:
description: Internal server error
patch:
summary: Replace all annotations with given conceptUUID from the draft annotations for a specified content with new annotation provided in the body
description: Returns the draft annotations for the content after the replace operation.
tags:
- Public API
consumes:
- application/json
produces:
- application/json
parameters:
- name: uuid
in: path
description: The UUID of the content
required: true
type: string
x-example: 8df16ae8-0dfd-4859-a5ff-eeb9644bed35
- name: conceptUUID
in: path
description: The UUID of the concept to be replaced
required: true
type: string
x-example: ababe00a-d732-4690-b283-585e7f264d2f
- name: body
in: body
description: An annotation for specific content
required: true
schema:
type: object
properties:
id:
type: string
description: The UUID of the new concept
x-example: http://www.ft.com/thing/d7113d1d-ed66-3adf-9910-1f62b2c40e6a
predicate:
type: string
description: The relationship between the concept and this piece of FT content
x-example: http://www.ft.com/ontology/annotation/mentions
example:
id: http://www.ft.com/thing/d7113d1d-ed66-3adf-9910-1f62b2c40e6a
predicate: http://www.ft.com/ontology/annotation/mentions
required:
- id
responses:
200:
description: Returns the canonicalized array of annotations that have been successfully saved in PAC.
examples:
application/json:
annotations:
- id: http://api.ft.com/things/d7113d1d-ed66-3adf-9910-1f62b2c40e6a
predicate: http://www.ft.com/ontology/annotation/mentions
400:
description: Invalid content or concept UUID supplied
404:
description: Content with the specified UUID was not found
500:
description: Internal server error
/__health:
get:
summary: Healthchecks
Expand Down Expand Up @@ -231,7 +288,7 @@ paths:
severity: 1
businessImpact: A business impact this failure might have
technicalSummary: A technical description of what's gone wrong
panicGuide: https://dewey.ft.com/dewey-system-code.html
panicGuide: https://runbooks.in.ft.com/draft-annotations-api
checkOutput: Technical output from the check
lastUpdated: 2017-08-03T10:44:32.324709638+01:00
ok: true
Expand Down
11 changes: 10 additions & 1 deletion handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ func validatePredicate(pr string) error {
"http://www.ft.com/ontology/hasContributor",
"http://www.ft.com/ontology/hasDisplayTag",
"http://www.ft.com/ontology/classification/isClassifiedBy",
"http://www.ft.com/ontology/hasBrand",
}
for _, item := range predicates {
if pr == item {
Expand Down Expand Up @@ -376,7 +377,12 @@ func (h *Handler) ReplaceAnnotation(w http.ResponseWriter, r *http.Request) {
handleWriteErrors("Error decoding request body", err, writeLog, w, http.StatusBadRequest)
return
}

if addedAnnotation.Predicate != "" {
if err = validatePredicate(addedAnnotation.Predicate); err != nil {
handleWriteErrors("Invalid request", err, writeLog, w, http.StatusBadRequest)
return
}
}
writeLog.Debug("Validating input and reading annotations from UPP...")
uppList, httpStatus, err := h.prepareUPPAnnotations(ctx, contentUUID, addedAnnotation.ConceptId)
if err != nil {
Expand All @@ -387,6 +393,9 @@ func (h *Handler) ReplaceAnnotation(w http.ResponseWriter, r *http.Request) {
for i := range uppList {
if uppList[i].ConceptId == conceptUUID {
uppList[i].ConceptId = addedAnnotation.ConceptId
if addedAnnotation.Predicate != "" {
uppList[i].Predicate = addedAnnotation.Predicate
}
}
}

Expand Down
93 changes: 93 additions & 0 deletions handler/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1321,6 +1321,70 @@ func TestHappyReplaceAnnotation(t *testing.T) {
assert.Equal(t, http.StatusOK, resp.StatusCode)
}

func TestHappyReplaceAnnotationWithPredicate(t *testing.T) {
rw := new(RWMock)
annAPI := new(AnnotationsAPIMock)
aug := new(AugmenterMock)

oldHash := randomdata.RandStringRunes(56)
newHash := randomdata.RandStringRunes(56)

const contentID = "83a201c6-60cd-11e7-91a7-502f7ee26895"
fromAnnotationAPI := []annotations.Annotation{
{
Predicate: "http://www.ft.com/ontology/annotation/mentions",
ConceptId: "http://www.ft.com/thing/0a619d71-9af5-3755-90dd-f789b686c67a",
ApiUrl: "http://api.ft.com/people/0a619d71-9af5-3755-90dd-f789b686c67a",
Type: "http://www.ft.com/ontology/person/Person",
PrefLabel: "Barack H. Obama",
},
{
Predicate: "http://www.ft.com/ontology/annotation/about",
ConceptId: "http://www.ft.com/thing/9577c6d4-b09e-4552-b88f-e52745abe02b",
ApiUrl: "http://api.ft.com/concepts/9577c6d4-b09e-4552-b88f-e52745abe02b",
Type: "http://www.ft.com/ontology/Topic",
PrefLabel: "US interest rates",
},
}
afterReplace := []annotations.Annotation{
{
Predicate: "http://www.ft.com/ontology/annotation/mentions",
ConceptId: "http://www.ft.com/thing/0a619d71-9af5-3755-90dd-f789b686c67a",
},
{
Predicate: "http://www.ft.com/ontology/hasBrand",
ConceptId: "http://www.ft.com/thing/100e3cc0-aecc-4458-8ebd-6b1fbc7345ed",
},
}

rw.On("Write", mock.AnythingOfType("*context.valueCtx"), contentID, &annotations.Annotations{Annotations: afterReplace}, oldHash).Return(newHash, nil)
annAPI.On("GetAllButV2", mock.Anything, contentID).Return(fromAnnotationAPI, nil)

h := New(rw, annAPI, annotations.NewCanonicalizer(annotations.NewCanonicalAnnotationSorter), aug, time.Second)
r := vestigo.NewRouter()

r.Patch("/drafts/content/:uuid/annotations/:cuuid", h.ReplaceAnnotation)

ann := annotations.Annotation{
ConceptId: "http://www.ft.com/thing/100e3cc0-aecc-4458-8ebd-6b1fbc7345ed",
Predicate: "http://www.ft.com/ontology/hasBrand",
}
b, _ := json.Marshal(ann)

req := httptest.NewRequest(
"PATCH",
"/drafts/content/83a201c6-60cd-11e7-91a7-502f7ee26895/annotations/9577c6d4-b09e-4552-b88f-e52745abe02b",
bytes.NewBuffer(b))

req.Header.Set(tidutils.TransactionIDHeader, testTID)
req.Header.Set(annotations.PreviousDocumentHashHeader, oldHash)
w := httptest.NewRecorder()

r.ServeHTTP(w, req)
resp := w.Result()
assert.Equal(t, http.StatusOK, resp.StatusCode)
}

func TestHappyReplaceExistingAnnotation(t *testing.T) {
rw := new(RWMock)
annAPI := new(AnnotationsAPIMock)
Expand Down Expand Up @@ -1468,6 +1532,35 @@ func TestUnHappyReplaceAnnotationInvalidConceptIdInBody(t *testing.T) {
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
}

func TestUnHappyReplaceAnnotationInvalidPredicate(t *testing.T) {
rw := new(RWMock)
annAPI := new(AnnotationsAPIMock)
aug := new(AugmenterMock)

h := New(rw, annAPI, nil, aug, time.Second)
r := vestigo.NewRouter()
r.Patch("/drafts/content/:uuid/annotations/:cuuid", h.ReplaceAnnotation)

ann := annotations.Annotation{
ConceptId: "http://www.ft.com/thing/9577c6d4-b09e-4552-b88f-e52745abe02b",
Predicate: "foo",
}
b, _ := json.Marshal(ann)

req := httptest.NewRequest(
"PATCH",
"/drafts/content/83a201c6-60cd-11e7-91a7-502f7ee26895/annotations/9577c6d4-b09e-4552-b88f-e52745abe02b",
bytes.NewBuffer(b))

req.Header.Set(tidutils.TransactionIDHeader, testTID)
w := httptest.NewRecorder()

r.ServeHTTP(w, req)
resp := w.Result()

assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
}

func TestUnhappyReplaceAnnotationWhenWritingAnnotationsFails(t *testing.T) {
rw := new(RWMock)
annAPI := new(AnnotationsAPIMock)
Expand Down
6 changes: 3 additions & 3 deletions health/healthcheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (service *HealthService) rwCheck() fthealth.Check {
ID: "check-generic-rw-aurora-health",
BusinessImpact: "Impossible to read and/or write annotations in PAC",
Name: "Check Generic RW Aurora Health",
PanicGuide: "https://dewey.ft.com/draft-annotations-api.html",
PanicGuide: "https://runbooks.in.ft.com/draft-annotations-api",
Severity: 1,
TechnicalSummary: fmt.Sprintf("Generic RW Aurora is not available at %v", service.rw.Endpoint()),
Checker: service.rwChecker,
Expand All @@ -68,7 +68,7 @@ func (service *HealthService) annotationsAPICheck() fthealth.Check {
ID: "check-annotations-api-health",
BusinessImpact: "Impossible to serve annotations through PAC",
Name: "Check UPP Public Annotations API Health",
PanicGuide: "https://dewey.ft.com/draft-annotations-api.html",
PanicGuide: "https://runbooks.in.ft.com/draft-annotations-api",
Severity: 1,
TechnicalSummary: fmt.Sprintf("UPP Public Annotations API is not available at %v", service.annotationsAPI.Endpoint()),
Checker: service.annotationsAPIChecker,
Expand All @@ -87,7 +87,7 @@ func (service *HealthService) conceptSearchAPICheck() fthealth.Check {
ID: "check-internal-concordances-api-health",
BusinessImpact: "Impossible to serve annotations with enriched concept data to clients",
Name: "Check UPP Internal Concordances API Health",
PanicGuide: "https://dewey.ft.com/draft-annotations-api.html",
PanicGuide: "https://runbooks.in.ft.com/draft-annotations-api",
Severity: 1,
TechnicalSummary: fmt.Sprintf("UPP Internal Concordances API is not available at %v", service.conceptSearchAPI.Endpoint()),
Checker: service.conceptSearchAPIChecker,
Expand Down

0 comments on commit bbc057f

Please sign in to comment.