Device manager - display client information in device details (PSG-682) (#9315)

* record device client inforamtion events on app start

* matrix-client-information -> matrix_client_information

* fix types

* remove another unused export

* add docs link

* display device client information in device details

* update snapshots

* integration-ish test client information in metadata

* tests

* fix tests

* export helper

* DeviceClientInformation type
This commit is contained in:
Kerry 2022-10-04 15:12:23 +02:00 committed by GitHub
parent c7a3209b84
commit 16c3efead5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 210 additions and 109 deletions

View file

@ -29,8 +29,8 @@ import { isSecretStorageBeingAccessed } from "../src/SecurityManager";
import dis from "../src/dispatcher/dispatcher";
import { Action } from "../src/dispatcher/actions";
import SettingsStore from "../src/settings/SettingsStore";
import { mockPlatformPeg } from "./test-utils";
import { SettingLevel } from "../src/settings/SettingLevel";
import { mockPlatformPeg } from "./test-utils";
// don't litter test console with logs
jest.mock("matrix-js-sdk/src/logger");

View file

@ -58,6 +58,7 @@ describe('<DeviceDetails />', () => {
display_name: 'My Device',
last_seen_ip: '123.456.789',
last_seen_ts: now - 60000000,
clientName: 'Element Web',
};
const { container } = render(getComponent({ device }));
expect(container).toMatchSnapshot();

View file

@ -76,6 +76,7 @@ HTMLCollection [
</p>
<table
class="mx_DeviceDetails_metadataTable"
data-testid="device-detail-metadata-session"
>
<tbody>
<tr>
@ -90,39 +91,6 @@ HTMLCollection [
alices_device
</td>
</tr>
<tr>
<td
class="mxDeviceDetails_metadataLabel"
>
Last activity
</td>
<td
class="mxDeviceDetails_metadataValue"
/>
</tr>
</tbody>
</table>
<table
class="mx_DeviceDetails_metadataTable"
>
<thead>
<tr>
<th>
Device
</th>
</tr>
</thead>
<tbody>
<tr>
<td
class="mxDeviceDetails_metadataLabel"
>
IP address
</td>
<td
class="mxDeviceDetails_metadataValue"
/>
</tr>
</tbody>
</table>
</section>

View file

@ -64,6 +64,7 @@ exports[`<DeviceDetails /> renders a verified device 1`] = `
</p>
<table
class="mx_DeviceDetails_metadataTable"
data-testid="device-detail-metadata-session"
>
<tbody>
<tr>
@ -78,39 +79,6 @@ exports[`<DeviceDetails /> renders a verified device 1`] = `
my-device
</td>
</tr>
<tr>
<td
class="mxDeviceDetails_metadataLabel"
>
Last activity
</td>
<td
class="mxDeviceDetails_metadataValue"
/>
</tr>
</tbody>
</table>
<table
class="mx_DeviceDetails_metadataTable"
>
<thead>
<tr>
<th>
Device
</th>
</tr>
</thead>
<tbody>
<tr>
<td
class="mxDeviceDetails_metadataLabel"
>
IP address
</td>
<td
class="mxDeviceDetails_metadataValue"
/>
</tr>
</tbody>
</table>
</section>
@ -198,6 +166,7 @@ exports[`<DeviceDetails /> renders device with metadata 1`] = `
</p>
<table
class="mx_DeviceDetails_metadataTable"
data-testid="device-detail-metadata-session"
>
<tbody>
<tr>
@ -228,6 +197,33 @@ exports[`<DeviceDetails /> renders device with metadata 1`] = `
</table>
<table
class="mx_DeviceDetails_metadataTable"
data-testid="device-detail-metadata-application"
>
<thead>
<tr>
<th>
Application
</th>
</tr>
</thead>
<tbody>
<tr>
<td
class="mxDeviceDetails_metadataLabel"
>
Name
</td>
<td
class="mxDeviceDetails_metadataValue"
>
Element Web
</td>
</tr>
</tbody>
</table>
<table
class="mx_DeviceDetails_metadataTable"
data-testid="device-detail-metadata-device"
>
<thead>
<tr>
@ -336,6 +332,7 @@ exports[`<DeviceDetails /> renders device without metadata 1`] = `
</p>
<table
class="mx_DeviceDetails_metadataTable"
data-testid="device-detail-metadata-session"
>
<tbody>
<tr>
@ -350,39 +347,6 @@ exports[`<DeviceDetails /> renders device without metadata 1`] = `
my-device
</td>
</tr>
<tr>
<td
class="mxDeviceDetails_metadataLabel"
>
Last activity
</td>
<td
class="mxDeviceDetails_metadataValue"
/>
</tr>
</tbody>
</table>
<table
class="mx_DeviceDetails_metadataTable"
>
<thead>
<tr>
<th>
Device
</th>
</tr>
</thead>
<tbody>
<tr>
<td
class="mxDeviceDetails_metadataLabel"
>
IP address
</td>
<td
class="mxDeviceDetails_metadataValue"
/>
</tr>
</tbody>
</table>
</section>

View file

@ -87,10 +87,10 @@ describe('<SessionManagerTab />', () => {
deleteMultipleDevices: jest.fn(),
generateClientSecret: jest.fn(),
setDeviceDetails: jest.fn(),
getAccountData: jest.fn(),
doesServerSupportUnstableFeature: jest.fn().mockResolvedValue(true),
getPushers: jest.fn(),
setPusher: jest.fn(),
getAccountData: jest.fn(),
setLocalNotificationSettings: jest.fn(),
});
@ -243,6 +243,48 @@ describe('<SessionManagerTab />', () => {
expect(getByTestId(`device-tile-${alicesDevice.device_id}`)).toMatchSnapshot();
});
it('extends device with client information when available', async () => {
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] });
mockClient.getAccountData.mockImplementation((eventType: string) => {
const content = {
name: 'Element Web',
version: '1.2.3',
url: 'test.com',
};
return new MatrixEvent({
type: eventType,
content,
});
});
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
});
// twice for each device
expect(mockClient.getAccountData).toHaveBeenCalledTimes(4);
toggleDeviceDetails(getByTestId, alicesDevice.device_id);
// application metadata section rendered
expect(getByTestId('device-detail-metadata-application')).toBeTruthy();
});
it('renders devices without available client information without error', async () => {
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] });
const { getByTestId, queryByTestId } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
});
toggleDeviceDetails(getByTestId, alicesDevice.device_id);
// application metadata section not rendered
expect(queryByTestId('device-detail-metadata-application')).toBeFalsy();
});
it('renders current session section with an unverified session', async () => {
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] });
const { getByTestId } = render(getComponent());

View file

@ -14,9 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import BasePlatform from "../../../src/BasePlatform";
import { IConfigOptions } from "../../../src/IConfigOptions";
import { recordClientInformation } from "../../../src/utils/device/clientInformation";
import {
getDeviceClientInformation,
recordClientInformation,
} from "../../../src/utils/device/clientInformation";
import { getMockClientWithEventEmitter } from "../../test-utils";
describe('recordClientInformation()', () => {
@ -84,3 +89,58 @@ describe('recordClientInformation()', () => {
);
});
});
describe('getDeviceClientInformation()', () => {
const deviceId = 'my-device-id';
const mockClient = getMockClientWithEventEmitter({
getAccountData: jest.fn(),
});
beforeEach(() => {
jest.resetAllMocks();
});
it('returns an empty object when no event exists for the device', () => {
expect(getDeviceClientInformation(mockClient, deviceId)).toEqual({});
expect(mockClient.getAccountData).toHaveBeenCalledWith(
`io.element.matrix_client_information.${deviceId}`,
);
});
it('returns client information for the device', () => {
const eventContent = {
name: 'Element Web',
version: '1.2.3',
url: 'test.com',
};
const event = new MatrixEvent({
type: `io.element.matrix_client_information.${deviceId}`,
content: eventContent,
});
mockClient.getAccountData.mockReturnValue(event);
expect(getDeviceClientInformation(mockClient, deviceId)).toEqual(eventContent);
});
it('excludes values with incorrect types', () => {
const eventContent = {
extraField: 'hello',
name: 'Element Web',
// wrong format
version: { value: '1.2.3' },
url: 'test.com',
};
const event = new MatrixEvent({
type: `io.element.matrix_client_information.${deviceId}`,
content: eventContent,
});
mockClient.getAccountData.mockReturnValue(event);
// invalid fields excluded
expect(getDeviceClientInformation(mockClient, deviceId)).toEqual({
name: eventContent.name,
url: eventContent.url,
});
});
});