README
@reuters-graphics/server-client
The server-client uploads graphics to the sphinx graphics server.
Quickstart
Install
$ yarn add @reuters-graphics/server-client
Creating a graphic
const ServerClient = require('@reuters-graphics/server-client');
// Initiate a new client with your server credentials.
const client = new ServerClient({
username, // Your graphics server username
password, // Your graphics server password
apiKey, // The sphinx API key for your environment: test, UAT or prod
});
// Create some metadata for the graphic pack.
const packMetadata = {
rootSlug: 'HEALTH-CORONAVIRUS',
wildSlug: 'MAP',
desk: 'london',
language: 'en',
title: 'My test project',
description: 'A coming-of-age story with explosive action.',
byline: 'Jon McClure',
contactEmail: 'jon.mcclure@thomsonreuters.com',
};
// Create a graphic pack w/ your metadata.
await client.createGraphic(packMetadata);
// Create some metadata for an edition
const editionMetadata = {
language: 'en',
title: 'My test project',
description: 'A coming-of-age story with explosive action.',
embed: {
declaration: '<div id="embed"></div><script type="text/javascript">new pym.Parent("embed", "https:/.../embed.html", {});</script>',
dependencies: '<script type="text/javascript" src="//graphics.thomsonreuters.com/pym.min.js"></script>',
},
};
// Read in an archive with your graphic to a buffer
const fileBuffer = fs.readFileSync('public-de.zip');
// Create editions from the archive
const editions = await client.createEditions('public-de.zip', fileBuffer, editionMetadata);
// The returned object will have the edition IDs and
// any public URLs
editions['public-en.zip']
// {
// interactive: {
// id: '...',
// url: 'https://...'
// }
// }
Updating a graphic
// For existing graphics, pass a graphic object with an
// ID when initializing the client.
const client = new ServerClient({
username,
password,
apiKey,
graphic: {
id: 'XXXXXXXX-XXXX...', // Existing graphic UUID
},
});
// Update your metadata for the graphic pack
const packMetadata = {
rootSlug: 'HEALTH-CORONAVIRUS',
wildSlug: 'MAP',
desk: 'london',
language: 'en',
title: 'My updated test project',
description: 'A coming-of-age story of revenge with explosive action.',
byline: 'Jon McClure, Matthew Weber',
contactEmail: 'jon.mcclure@thomsonreuters.com',
};
// Update your graphic with new pack metadata.
await client.updateGraphic(packMetadata);
// Update metadata for editions.
const editionMetadata = {
language: 'de',
title: 'Mein Testprojekt',
description: 'Eine Coming-of-Age-Geschichte mit explosiver Action.',
embed: {
declaration: '<div id="embed"></div><script type="text/javascript">new pym.Parent("embed", "https:/.../embed.html", {});</script>',
dependencies: '<script type="text/javascript" src="//graphics.thomsonreuters.com/pym.min.js"></script>',
},
};
// Read in updated archive.
const fileBuffer = fs.readFileSync('public-de.zip');
// Update editions from archive.
const editionURLs = await updateClient.updateEditions('public-de.zip', fileBuffer, editionMetadata);
Publishing a graphic
const client = new ServerClient({
username,
password,
apiKey,
graphic: {
id: 'XXXXXXXX-XXXX...', // Existing graphic UUID
},
});
// Publish all editions in the graphic
await client.publishGraphic();
// Or publish only those editions made from particular archives
await client.publishGraphic(['public-de.zip', 'media-de.zip']);
// Or pass additional publishing locations and correction status
const publishToMedia = true; // default false
const publishToLynx = false; // default false
const isCorrection = false; // default null which will prompt a user
await client.publishGraphic([], publishToMedia, publishToLynx, isCorrection);
// Or you can specify specific editions by name to publish to Media and Lynx
await client.publishGraphic([], ['media-interactive'], ['interactive'], isCorrection);
Metadata reference
Pack
rootSlug
: A general topic slug. Required.wildSlug
: A more specific slug.language
: Two-letter ISO 639-1 code for the default language for the pack. Defaults toen
but may also bear
,fr
,es
,de
,it
,ja
,pt
orru
.group
: One of eitherrngs
oreditorcharts
.desk
: One oflondon
,singapore
,bengaluru
ornew york
. Required.title
: Title in English. Generally, write for SEO. Required.description
: A longer description in English. Generally, write for SEO. Required.byline
: Authors for the piece. Separate multiple with commas. Required.contactEmail
: Thomson Reuters email for primary contact for the piece; generally, the author. Required.
Edition
language
: Two-letter ISO 639-1 code for the language the edition is written in. Must be one ofen
,ar
,fr
,es
,de
,it
,ja
,pt
orru
. Required.title
: Title in the matching language to overwrite the pack title. Generally, write for SEO. Will automatically be set tonull
if same as pack title. Required.description
: A longer description in the matching language to override the pack description. Generally, write for SEO. Will be automatically set tonull
if same as pack description. Required.embed
declaration
: Embed code for parent page, usually a div and pym script to fill it.dependencies
: Any embed dependency scripts that should be injected in the head, for example, the pym.js script.
Making your archive
Structure
Your archive should have the following structure:
- public-en/
- interactive/
- index.html
...
Your archive should contain:
A root folder named after the archive, usually indicating the language of the edition, for example,
en
,de
, etc.An edition folder within the root folder. If the edition represents a public page, name it
interactive
. If it is a package of source code for media clients, name itmedia-interactive
. Anything else, name the folder something that describes what that edition contains, for example,PDF
orPNG
. You may have more than one edition folder in your archive.A root edition file within the edition folder. The type of file this is will drive particular behavior and validation rules on the graphics server. There is also a hierarchy for determining what is the root edition file if multiple file types are present; generally,
.html
files take precedence. Other commonly used root edition file types:.jpg
,.png
,.pdf
. Note that if multiple files of the same type are present at the root, the first file added to the zip will be the root edition file. At present, it is not possible to control which file is added first.
interactive
editions
For The root file should be index.html
.
In most cases, additional HTML files should be at least one directory deeper than the root edition file. CSS, JS and images can be at the same level as index.html
. You may put an embed HTML file at the same level as index.html
, but at present, it is not possible to control which is the root file.
Include a _gfxpreview.png
image in the edition folder, which will be used to preview the edition in the graphics server and Connect.
media-interactive
editions
For The root file should be a README.txt
file.
Include a _gfxpreview.png
image in the edition folder, which will be used to preview the edition in the graphics server and Connect.
Creating the archive in node
If you are creating the archive in node, use the archiver node package.
Known issue
The server is very finicky about the archive file it will accept. For example, you cannot create an archive in memory and then add or change files using archiver's .append
method. (🤷)
Instead, build out your archive's directory and file structure on your local file system first, then create the archive from that directory.
Here's a very simple example of doing that:
const fs = require('fs');
const path = require('path');
const archiver = require('archiver');
// The directory with your built files
const SRC_DIR = path.resolve(process.cwd(), 'dist');
// The directory for your archive
const DEST_DIR = path.resolve(process.cwd(), 'public-en');
const createZip = (resolve, reject) => {
const writer = new Stream.Writable();
const chunks = [];
writer._write = (chunk, encoding, next) => {
chunks.push(chunk); next();
};
const archive = archiver('zip');
archive.on('error', e => reject(e));
// We'll return a buffer from this function
archive.on('end', () => resolve(Buffer.concat(chunks)));
archive.pipe(writer);
// Construct your archive locally
fs.copyFileSync(path.join(SRC_DIR, 'index.html'), path.join(DEST_DIR, 'interactive/index.html'))
// ... then use the directory to create your archive
archive.directory(path.join(DEST_DIR, 'public-en'), 'public-en');
archive.finalize();
};
module.exports = async() => new Promise((resolve, reject) =>
createZip(resolve, reject));
Testing
Clone this repository, if you haven't, and install dependencies:
$ yarn
To test, make a copy of .env.example
at .env
and fill in the environment variables:
USERNAME
: Sphinx usernamePASSWORD
: Sphinx passwordAPI_KEY
: Sphinx API keySPHINX_ENV
: one ofTEST
,UAT
,CI
orPROD
Then build the library and run tests:
$ yarn build && yarn test