Commit 4d3f72e9 authored by Robert Knight's avatar Robert Knight

Show URLs for all connected frames in help panel

Show the URLs of all frames connected to the sidebar in the Version info
tab of the Help panel. Previously only the URL of the main frame was
shown.

Initially this will mainly be useful for client developers to check that
the expected set of frames are connected to the sidebar in different
scenarios.
parent 806f0f85
...@@ -53,6 +53,7 @@ function HelpPanelTab({ linkText, url }) { ...@@ -53,6 +53,7 @@ function HelpPanelTab({ linkText, url }) {
*/ */
function HelpPanel({ auth, session }) { function HelpPanel({ auth, session }) {
const store = useStoreProxy(); const store = useStoreProxy();
const frames = store.frames();
const mainFrame = store.mainFrame(); const mainFrame = store.mainFrame();
// Should this panel be auto-opened at app launch? Note that the actual // Should this panel be auto-opened at app launch? Note that the actual
...@@ -68,9 +69,21 @@ function HelpPanel({ auth, session }) { ...@@ -68,9 +69,21 @@ function HelpPanel({ auth, session }) {
// Build version details about this session/app // Build version details about this session/app
const versionData = useMemo(() => { const versionData = useMemo(() => {
const userInfo = auth || { status: 'logged-out' }; const userInfo = auth || { status: 'logged-out' };
const documentInfo = mainFrame || {};
// Sort frames so the main frame is listed first. Other frames will retain
// their original order, assuming a stable sort.
const documentInfo = [...frames].sort((a, b) => {
if (a === mainFrame) {
return -1;
} else if (b === mainFrame) {
return 1;
} else {
return 0;
}
});
return new VersionData(userInfo, documentInfo); return new VersionData(userInfo, documentInfo);
}, [auth, mainFrame]); }, [auth, frames, mainFrame]);
// The support ticket URL encodes some version info in it to pre-fill in the // The support ticket URL encodes some version info in it to pre-fill in the
// create-new-ticket form // create-new-ticket form
......
...@@ -32,7 +32,7 @@ function VersionInfo({ toastMessenger, versionData }) { ...@@ -32,7 +32,7 @@ function VersionInfo({ toastMessenger, versionData }) {
<dt className="VersionInfo__key">User Agent</dt> <dt className="VersionInfo__key">User Agent</dt>
<dd className="VersionInfo__value">{versionData.userAgent}</dd> <dd className="VersionInfo__value">{versionData.userAgent}</dd>
<dt className="VersionInfo__key">URL</dt> <dt className="VersionInfo__key">URL</dt>
<dd className="VersionInfo__value">{versionData.url}</dd> <dd className="VersionInfo__value">{versionData.urls}</dd>
<dt className="VersionInfo__key">Fingerprint</dt> <dt className="VersionInfo__key">Fingerprint</dt>
<dd className="VersionInfo__value">{versionData.fingerprint}</dd> <dd className="VersionInfo__value">{versionData.fingerprint}</dd>
<dt className="VersionInfo__key">Account</dt> <dt className="VersionInfo__key">Account</dt>
......
...@@ -11,8 +11,10 @@ describe('HelpPanel', () => { ...@@ -11,8 +11,10 @@ describe('HelpPanel', () => {
let fakeAuth; let fakeAuth;
let fakeSessionService; let fakeSessionService;
let fakeStore; let fakeStore;
let frames;
let FakeVersionData;
let fakeVersionData; let fakeVersionData;
let fakeVersionDataObject;
function createComponent(props) { function createComponent(props) {
return mount( return mount(
...@@ -21,23 +23,25 @@ describe('HelpPanel', () => { ...@@ -21,23 +23,25 @@ describe('HelpPanel', () => {
} }
beforeEach(() => { beforeEach(() => {
frames = [];
fakeAuth = {}; fakeAuth = {};
fakeSessionService = { dismissSidebarTutorial: sinon.stub() }; fakeSessionService = { dismissSidebarTutorial: sinon.stub() };
fakeStore = { fakeStore = {
mainFrame: sinon.stub().returns(null), frames: () => frames,
mainFrame: () => frames.find(f => !f.id) ?? null,
profile: sinon.stub().returns({ profile: sinon.stub().returns({
preferences: { show_sidebar_tutorial: true }, preferences: { show_sidebar_tutorial: true },
}), }),
}; };
fakeVersionDataObject = { fakeVersionData = {
asEncodedURLString: sinon.stub().returns('fakeURLString'), asEncodedURLString: sinon.stub().returns('fakeURLString'),
}; };
fakeVersionData = sinon.stub().returns(fakeVersionDataObject); FakeVersionData = sinon.stub().returns(fakeVersionData);
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'../store/use-store': { useStoreProxy: () => fakeStore }, '../store/use-store': { useStoreProxy: () => fakeStore },
'../helpers/version-data': fakeVersionData, '../helpers/version-data': FakeVersionData,
}); });
}); });
...@@ -73,13 +77,52 @@ describe('HelpPanel', () => { ...@@ -73,13 +77,52 @@ describe('HelpPanel', () => {
assert.isTrue(wrapper.find('VersionInfo').exists()); assert.isTrue(wrapper.find('VersionInfo').exists());
assert.equal( assert.equal(
wrapper.find('VersionInfo').prop('versionData'), wrapper.find('VersionInfo').prop('versionData'),
fakeVersionDataObject fakeVersionData
); );
assert.isFalse(wrapper.find('Tutorial').exists()); assert.isFalse(wrapper.find('Tutorial').exists());
}); });
}); });
context('when viewing versionInfo sub-panel', () => { context('when viewing versionInfo sub-panel', () => {
it('shows document info for current frames', () => {
// Unsorted frames
frames = [
{
id: 'subframe',
uri: 'https://example.com/child-frame',
},
{
id: null,
uri: 'https://example.com/',
},
{
id: 'subframe2',
uri: 'https://example.com/child-frame-2',
},
];
createComponent();
assert.calledOnce(FakeVersionData);
const docInfo = FakeVersionData.getCall(0).args[1];
// Frames should be passed to `VersionData` with the main frame first.
assert.deepEqual(docInfo, [
{
id: null,
uri: 'https://example.com/',
},
{
id: 'subframe',
uri: 'https://example.com/child-frame',
},
{
id: 'subframe2',
uri: 'https://example.com/child-frame-2',
},
]);
});
it('should show navigation link back to tutorial sub-panel', () => { it('should show navigation link back to tutorial sub-panel', () => {
const wrapper = createComponent(); const wrapper = createComponent();
wrapper.find('.HelpPanel__sub-panel-navigation-button').simulate('click'); wrapper.find('.HelpPanel__sub-panel-navigation-button').simulate('click');
......
...@@ -40,7 +40,7 @@ describe('VersionInfo', () => { ...@@ -40,7 +40,7 @@ describe('VersionInfo', () => {
fakeVersionData = { fakeVersionData = {
version: 'fakeVersion', version: 'fakeVersion',
userAgent: 'fakeUserAgent', userAgent: 'fakeUserAgent',
url: 'fakeUrl', urls: 'fakeUrl',
fingerprint: 'fakeFingerprint', fingerprint: 'fakeFingerprint',
account: 'fakeAccount', account: 'fakeAccount',
timestamp: 'fakeTimestamp', timestamp: 'fakeTimestamp',
......
...@@ -13,29 +13,27 @@ describe('sidebar/helpers/version-data', () => { ...@@ -13,29 +13,27 @@ describe('sidebar/helpers/version-data', () => {
describe('constructor', () => { describe('constructor', () => {
it('sets `timestamp` to string of current date', () => { it('sets `timestamp` to string of current date', () => {
const versionData = new VersionData({}, {}); const versionData = new VersionData({}, []);
assert.equal(versionData.timestamp, new Date().toString()); assert.equal(versionData.timestamp, new Date().toString());
}); });
it('sets `version`', () => { it('sets `version`', () => {
const versionData = new VersionData({}, {}); const versionData = new VersionData({}, []);
assert.equal(versionData.version, '1.0.0-dummy-version'); assert.equal(versionData.version, '1.0.0-dummy-version');
}); });
it('sets `userAgent`', () => { it('sets `userAgent`', () => {
const versionData = new VersionData( const versionData = new VersionData({}, [], {
{}, navigator: { userAgent: 'fakeUserAgent' },
{}, });
{ navigator: { userAgent: 'fakeUserAgent' } }
);
assert.equal(versionData.userAgent, 'fakeUserAgent'); assert.equal(versionData.userAgent, 'fakeUserAgent');
}); });
context('empty `userInfo` and `documentInfo` objects', () => { context('empty `userInfo` and `documentInfo` objects', () => {
// This can happen early in app lifecycle // This can happen early in app lifecycle
it('sets properties to "N/A" for missing values', () => { it('sets properties to "N/A" for missing values', () => {
const versionData = new VersionData({}, {}); const versionData = new VersionData({}, []);
['url', 'fingerprint', 'account'].forEach(prop => { ['urls', 'fingerprint', 'account'].forEach(prop => {
assert.equal(versionData[prop], 'N/A'); assert.equal(versionData[prop], 'N/A');
}); });
}); });
...@@ -48,31 +46,38 @@ describe('sidebar/helpers/version-data', () => { ...@@ -48,31 +46,38 @@ describe('sidebar/helpers/version-data', () => {
userid: 'acct:foo@bar.com', userid: 'acct:foo@bar.com',
displayName: 'Fred Hubert', displayName: 'Fred Hubert',
}, },
{} []
); );
assert.equal(versionData.account, 'Fred Hubert (acct:foo@bar.com)'); assert.equal(versionData.account, 'Fred Hubert (acct:foo@bar.com)');
}); });
it('only includes userid if no display name', () => { it('only includes userid if no display name', () => {
const versionData = new VersionData({ userid: 'acct:foo@bar.com' }, {}); const versionData = new VersionData({ userid: 'acct:foo@bar.com' }, []);
assert.equal(versionData.account, 'acct:foo@bar.com'); assert.equal(versionData.account, 'acct:foo@bar.com');
}); });
}); });
describe('document information', () => { describe('document information', () => {
it('sets `url`', () => { [
const versionData = new VersionData( {
{}, docInfo: [{ uri: 'https://foo.com' }],
{ uri: 'https://www.whatbadgerseat.com' } formattedURLs: 'https://foo.com',
); },
assert.equal(versionData.url, 'https://www.whatbadgerseat.com'); {
docInfo: [{ uri: 'https://foo.com' }, { uri: 'https://bar.com' }],
formattedURLs: 'https://foo.com, https://bar.com',
},
].forEach(({ docInfo, formattedURLs }) => {
it('sets `urls`', () => {
const versionData = new VersionData({}, docInfo);
assert.equal(versionData.urls, formattedURLs);
});
}); });
it('sets `fingerprint`', () => { it('sets `fingerprint`', () => {
const versionData = new VersionData( const versionData = new VersionData({}, [
{}, { metadata: { documentFingerprint: 'DEADBEEF' } },
{ metadata: { documentFingerprint: 'DEADBEEF' } } ]);
);
assert.equal(versionData.fingerprint, 'DEADBEEF'); assert.equal(versionData.fingerprint, 'DEADBEEF');
}); });
}); });
...@@ -82,13 +87,13 @@ describe('sidebar/helpers/version-data', () => { ...@@ -82,13 +87,13 @@ describe('sidebar/helpers/version-data', () => {
[ [
['Version', 'version'], ['Version', 'version'],
['User Agent', 'userAgent'], ['User Agent', 'userAgent'],
['URL', 'url'], ['URL', 'urls'],
['Fingerprint', 'fingerprint'], ['Fingerprint', 'fingerprint'],
['Account', 'account'], ['Account', 'account'],
['Date', 'timestamp'], ['Date', 'timestamp'],
].forEach(prop => { ].forEach(prop => {
it(`includes a line for the value of ${prop[1]} in the string`, () => { it(`includes a line for the value of ${prop[1]} in the string`, () => {
const versionData = new VersionData({}, {}); const versionData = new VersionData({}, []);
const formatted = versionData.asFormattedString(); const formatted = versionData.asFormattedString();
const subStr = `${prop[0]}: ${versionData[prop[1]]}\n`; const subStr = `${prop[0]}: ${versionData[prop[1]]}\n`;
assert.include(formatted, subStr); assert.include(formatted, subStr);
...@@ -100,13 +105,13 @@ describe('sidebar/helpers/version-data', () => { ...@@ -100,13 +105,13 @@ describe('sidebar/helpers/version-data', () => {
[ [
['Version', 'version'], ['Version', 'version'],
['User Agent', 'userAgent'], ['User Agent', 'userAgent'],
['URL', 'url'], ['URL', 'urls'],
['Fingerprint', 'fingerprint'], ['Fingerprint', 'fingerprint'],
['Account', 'account'], ['Account', 'account'],
['Date', 'timestamp'], ['Date', 'timestamp'],
].forEach(prop => { ].forEach(prop => {
it(`includes encoded value for ${prop[1]} in URL string`, () => { it(`includes encoded value for ${prop[1]} in URL string`, () => {
const versionData = new VersionData({}, {}); const versionData = new VersionData({}, []);
const encoded = versionData.asEncodedURLString(); const encoded = versionData.asEncodedURLString();
const subStr = encodeURIComponent( const subStr = encodeURIComponent(
`${prop[0]}: ${versionData[prop[1]]}\n` `${prop[0]}: ${versionData[prop[1]]}\n`
......
...@@ -22,12 +22,12 @@ ...@@ -22,12 +22,12 @@
export default class VersionData { export default class VersionData {
/** /**
* @param {AuthState} userInfo * @param {AuthState} userInfo
* @param {DocumentInfo} documentInfo * @param {DocumentInfo[]} documentInfo - Metadata for connected frames.
* If there are multiple frames, the "main" one should be listed first.
* @param {Window} window_ - test seam * @param {Window} window_ - test seam
*/ */
constructor(userInfo, documentInfo, window_ = window) { constructor(userInfo, documentInfo, window_ = window) {
const noValueString = 'N/A'; const noValueString = 'N/A';
const docMeta = documentInfo.metadata;
let accountString = noValueString; let accountString = noValueString;
if (userInfo.userid) { if (userInfo.userid) {
...@@ -39,11 +39,12 @@ export default class VersionData { ...@@ -39,11 +39,12 @@ export default class VersionData {
this.version = '__VERSION__'; // replaced by versionify this.version = '__VERSION__'; // replaced by versionify
this.userAgent = window_.navigator.userAgent; this.userAgent = window_.navigator.userAgent;
this.url = documentInfo.uri || noValueString; this.urls = documentInfo.map(di => di.uri).join(', ') || noValueString;
// We currently assume that only the main (first) frame may have a fingerprint.
this.fingerprint = this.fingerprint =
docMeta && docMeta.documentFingerprint documentInfo[0]?.metadata?.documentFingerprint ?? noValueString;
? docMeta.documentFingerprint
: noValueString;
this.account = accountString; this.account = accountString;
this.timestamp = new Date().toString(); this.timestamp = new Date().toString();
} }
...@@ -57,7 +58,7 @@ export default class VersionData { ...@@ -57,7 +58,7 @@ export default class VersionData {
asFormattedString() { asFormattedString() {
return `Version: ${this.version} return `Version: ${this.version}
User Agent: ${this.userAgent} User Agent: ${this.userAgent}
URL: ${this.url} URL: ${this.urls}
Fingerprint: ${this.fingerprint} Fingerprint: ${this.fingerprint}
Account: ${this.account} Account: ${this.account}
Date: ${this.timestamp} Date: ${this.timestamp}
......
...@@ -83,6 +83,8 @@ function updateFrameAnnotationFetchStatus(uri, isFetchComplete) { ...@@ -83,6 +83,8 @@ function updateFrameAnnotationFetchStatus(uri, isFetchComplete) {
/** /**
* Return the list of frames currently connected to the sidebar app. * Return the list of frames currently connected to the sidebar app.
*
* @return {Frame[]}
*/ */
function frames(state) { function frames(state) {
return state; return state;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment