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 }) {
*/
function HelpPanel({ auth, session }) {
const store = useStoreProxy();
const frames = store.frames();
const mainFrame = store.mainFrame();
// Should this panel be auto-opened at app launch? Note that the actual
......@@ -68,9 +69,21 @@ function HelpPanel({ auth, session }) {
// Build version details about this session/app
const versionData = useMemo(() => {
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);
}, [auth, mainFrame]);
}, [auth, frames, mainFrame]);
// The support ticket URL encodes some version info in it to pre-fill in the
// create-new-ticket form
......
......@@ -32,7 +32,7 @@ function VersionInfo({ toastMessenger, versionData }) {
<dt className="VersionInfo__key">User Agent</dt>
<dd className="VersionInfo__value">{versionData.userAgent}</dd>
<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>
<dd className="VersionInfo__value">{versionData.fingerprint}</dd>
<dt className="VersionInfo__key">Account</dt>
......
......@@ -11,8 +11,10 @@ describe('HelpPanel', () => {
let fakeAuth;
let fakeSessionService;
let fakeStore;
let frames;
let FakeVersionData;
let fakeVersionData;
let fakeVersionDataObject;
function createComponent(props) {
return mount(
......@@ -21,23 +23,25 @@ describe('HelpPanel', () => {
}
beforeEach(() => {
frames = [];
fakeAuth = {};
fakeSessionService = { dismissSidebarTutorial: sinon.stub() };
fakeStore = {
mainFrame: sinon.stub().returns(null),
frames: () => frames,
mainFrame: () => frames.find(f => !f.id) ?? null,
profile: sinon.stub().returns({
preferences: { show_sidebar_tutorial: true },
}),
};
fakeVersionDataObject = {
fakeVersionData = {
asEncodedURLString: sinon.stub().returns('fakeURLString'),
};
fakeVersionData = sinon.stub().returns(fakeVersionDataObject);
FakeVersionData = sinon.stub().returns(fakeVersionData);
$imports.$mock(mockImportedComponents());
$imports.$mock({
'../store/use-store': { useStoreProxy: () => fakeStore },
'../helpers/version-data': fakeVersionData,
'../helpers/version-data': FakeVersionData,
});
});
......@@ -73,13 +77,52 @@ describe('HelpPanel', () => {
assert.isTrue(wrapper.find('VersionInfo').exists());
assert.equal(
wrapper.find('VersionInfo').prop('versionData'),
fakeVersionDataObject
fakeVersionData
);
assert.isFalse(wrapper.find('Tutorial').exists());
});
});
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', () => {
const wrapper = createComponent();
wrapper.find('.HelpPanel__sub-panel-navigation-button').simulate('click');
......
......@@ -40,7 +40,7 @@ describe('VersionInfo', () => {
fakeVersionData = {
version: 'fakeVersion',
userAgent: 'fakeUserAgent',
url: 'fakeUrl',
urls: 'fakeUrl',
fingerprint: 'fakeFingerprint',
account: 'fakeAccount',
timestamp: 'fakeTimestamp',
......
......@@ -13,29 +13,27 @@ describe('sidebar/helpers/version-data', () => {
describe('constructor', () => {
it('sets `timestamp` to string of current date', () => {
const versionData = new VersionData({}, {});
const versionData = new VersionData({}, []);
assert.equal(versionData.timestamp, new Date().toString());
});
it('sets `version`', () => {
const versionData = new VersionData({}, {});
const versionData = new VersionData({}, []);
assert.equal(versionData.version, '1.0.0-dummy-version');
});
it('sets `userAgent`', () => {
const versionData = new VersionData(
{},
{},
{ navigator: { userAgent: 'fakeUserAgent' } }
);
const versionData = new VersionData({}, [], {
navigator: { userAgent: 'fakeUserAgent' },
});
assert.equal(versionData.userAgent, 'fakeUserAgent');
});
context('empty `userInfo` and `documentInfo` objects', () => {
// This can happen early in app lifecycle
it('sets properties to "N/A" for missing values', () => {
const versionData = new VersionData({}, {});
['url', 'fingerprint', 'account'].forEach(prop => {
const versionData = new VersionData({}, []);
['urls', 'fingerprint', 'account'].forEach(prop => {
assert.equal(versionData[prop], 'N/A');
});
});
......@@ -48,31 +46,38 @@ describe('sidebar/helpers/version-data', () => {
userid: 'acct:foo@bar.com',
displayName: 'Fred Hubert',
},
{}
[]
);
assert.equal(versionData.account, 'Fred Hubert (acct:foo@bar.com)');
});
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');
});
});
describe('document information', () => {
it('sets `url`', () => {
const versionData = new VersionData(
{},
{ uri: 'https://www.whatbadgerseat.com' }
);
assert.equal(versionData.url, 'https://www.whatbadgerseat.com');
[
{
docInfo: [{ uri: 'https://foo.com' }],
formattedURLs: 'https://foo.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`', () => {
const versionData = new VersionData(
{},
{ metadata: { documentFingerprint: 'DEADBEEF' } }
);
const versionData = new VersionData({}, [
{ metadata: { documentFingerprint: 'DEADBEEF' } },
]);
assert.equal(versionData.fingerprint, 'DEADBEEF');
});
});
......@@ -82,13 +87,13 @@ describe('sidebar/helpers/version-data', () => {
[
['Version', 'version'],
['User Agent', 'userAgent'],
['URL', 'url'],
['URL', 'urls'],
['Fingerprint', 'fingerprint'],
['Account', 'account'],
['Date', 'timestamp'],
].forEach(prop => {
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 subStr = `${prop[0]}: ${versionData[prop[1]]}\n`;
assert.include(formatted, subStr);
......@@ -100,13 +105,13 @@ describe('sidebar/helpers/version-data', () => {
[
['Version', 'version'],
['User Agent', 'userAgent'],
['URL', 'url'],
['URL', 'urls'],
['Fingerprint', 'fingerprint'],
['Account', 'account'],
['Date', 'timestamp'],
].forEach(prop => {
it(`includes encoded value for ${prop[1]} in URL string`, () => {
const versionData = new VersionData({}, {});
const versionData = new VersionData({}, []);
const encoded = versionData.asEncodedURLString();
const subStr = encodeURIComponent(
`${prop[0]}: ${versionData[prop[1]]}\n`
......
......@@ -22,12 +22,12 @@
export default class VersionData {
/**
* @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
*/
constructor(userInfo, documentInfo, window_ = window) {
const noValueString = 'N/A';
const docMeta = documentInfo.metadata;
let accountString = noValueString;
if (userInfo.userid) {
......@@ -39,11 +39,12 @@ export default class VersionData {
this.version = '__VERSION__'; // replaced by versionify
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 =
docMeta && docMeta.documentFingerprint
? docMeta.documentFingerprint
: noValueString;
documentInfo[0]?.metadata?.documentFingerprint ?? noValueString;
this.account = accountString;
this.timestamp = new Date().toString();
}
......@@ -57,7 +58,7 @@ export default class VersionData {
asFormattedString() {
return `Version: ${this.version}
User Agent: ${this.userAgent}
URL: ${this.url}
URL: ${this.urls}
Fingerprint: ${this.fingerprint}
Account: ${this.account}
Date: ${this.timestamp}
......
......@@ -83,6 +83,8 @@ function updateFrameAnnotationFetchStatus(uri, isFetchComplete) {
/**
* Return the list of frames currently connected to the sidebar app.
*
* @return {Frame[]}
*/
function frames(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