Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implements all orgs page #548

Merged
merged 23 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c901109
pull data for all orgs page
echappen Oct 3, 2024
97f6158
add last visited org link to all orgs page
echappen Oct 4, 2024
5fc3a57
improve error handling for all orgs page
echappen Oct 4, 2024
6224148
Fix: include all org routes in middleware matches; set org id cookie …
echappen Oct 4, 2024
f6253c6
apply UI design to all orgs page
echappen Oct 7, 2024
4640fb5
apply initial mobile styles to all orgs cards
echappen Oct 7, 2024
0a9629e
pull org roles for current user for all orgs page
echappen Oct 9, 2024
ee8187c
use package for converting MB to more human-friendly values
echappen Oct 9, 2024
794b096
refine tablet-ish breakpoints
echappen Oct 9, 2024
ebbdcf2
add real hrefs for "at a glance" links
echappen Oct 9, 2024
4c696f6
refactor org components
echappen Oct 9, 2024
930d917
add some spacing between roles and at a glance sections
echappen Oct 10, 2024
6fe6dec
handle indefinite articles for role names
echappen Oct 10, 2024
44df6cc
remove hyperlink for spaces
echappen Oct 10, 2024
3429878
add timestamp to all orgs page
echappen Oct 10, 2024
9afa2ad
Fix: remove key prop error message
echappen Oct 10, 2024
733ebd0
do not cache usage summary requests
echappen Oct 10, 2024
000fec6
move timestamp to top of org list
echappen Oct 10, 2024
7567bbf
add design for progress bar for when there's no memory limit
echappen Oct 15, 2024
c23d77b
add instructions for how to obtain your own CF user ID
echappen Oct 17, 2024
4661990
modify memory used/allocated language in memory bar
echappen Oct 17, 2024
a0333d0
make timestamp component more reusable
echappen Oct 17, 2024
1ce56ec
remove bottom margin from At a Glance section of org card
echappen Oct 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .env.example.local
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@ UAA_LOGOUT_PATH=/logout.do
# CF API
# Used to connect to the Cloud Foundry API. CF_API_URL should always end
# with /v3 regardless of the environment.
# The CF_API_TOKEN can be populated with `cf oauth-token` or by running
# npm run dev-cf
#
CF_API_URL=https://api.dev.us-gov-west-1.aws-us-gov.cloud.gov/v3
# In a deployed environment, the CF user id comes from the UAA token.
# Locally, use CF_USER_ID to get CAPI info related to the current logged in user.
# You can find your own CF_USER_ID by decoding your CF_API_TOKEN (using JWT).
CF_USER_ID=your-cf-user-guid
# Locally, the CF_API_TOKEN can be populated with `cf oauth-token` or by running
# npm run dev-cf
CF_API_TOKEN=

# S3
Expand Down
1 change: 1 addition & 0 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ UAA_LOGOUT_PATH=/logout.do

CF_API_URL=https://example.com
CF_API_TOKEN=placeholder
CF_USER_ID=placeholder

NEXT_PUBLIC_USER_INVITE_URL=https://account.dev.us-gov-west-1.aws-us-gov.cloud.gov/invite
51 changes: 50 additions & 1 deletion __tests__/api/cf/token.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { cookies } from 'next/headers';
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
import { getToken, isLoggedIn } from '@/api/cf/token';
import { getToken, isLoggedIn, getUserId } from '@/api/cf/token';

/* global jest */
/* eslint no-undef: "off" */
Expand Down Expand Up @@ -64,3 +64,52 @@ describe('cloudfoundry token tests', () => {
});
});
});

describe('cloudfoundry user id tests', () => {
describe('when CF_USER_ID environment variable is set', () => {
beforeEach(() => {
process.env.CF_USER_ID = 'foo-user-id';
});
afterEach(() => {
delete process.env.CF_USER_ID;
});
it('getUserId() returns a manual token', () => {
expect(getUserId()).toBe('foo-user-id');
});
});

describe('when CF_USER_ID environment variable is not set', () => {
describe('when auth cookie is set', () => {
beforeEach(() => {
cookies.mockImplementation(() => ({
get: () => ({ value: '{"user_id":"foo-user-id"}' }),
}));
});
it('getUserId() returns a token from a cookie', () => {
expect(getUserId()).toBe('foo-user-id');
});
});
describe('when auth cookie is not set', () => {
beforeEach(() => {
cookies.mockImplementation(() => ({
get: () => undefined,
}));
});
it('getToken() throws an error when no cookie is set', () => {
expect(() => getUserId()).toThrow('please confirm you are logged in');
});
});
describe('when auth cookie is not in an expected format', () => {
beforeEach(() => {
cookies.mockImplementation(() => ({
get: () => 'unexpected format',
}));
});
it('getToken() throws an error', () => {
expect(() => getUserId()).toThrow(
'unable to parse authsession user_id'
);
});
});
});
});
112 changes: 112 additions & 0 deletions __tests__/api/mocks/orgQuotas.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
export const mockOrgQuotas = {
pagination: {
total_results: 1,
total_pages: 1,
first: {
href: 'https://api.dev.us-gov-west-1.aws-us-gov.cloud.gov/v3/organization_quotas?organization_guids=470bd8ff-ed0e-4d11-95c4-cf765202cebd&page=1&per_page=50',
},
last: {
href: 'https://api.dev.us-gov-west-1.aws-us-gov.cloud.gov/v3/organization_quotas?organization_guids=470bd8ff-ed0e-4d11-95c4-cf765202cebd&page=1&per_page=50',
},
next: null,
previous: null,
},
resources: [
{
guid: '3564fac5-c405-480e-b758-57912da29f9e',
created_at: '2017-04-27T19:12:50Z',
updated_at: '2022-07-18T21:01:25Z',
name: 'default',
apps: {
total_memory_in_mb: 10240,
per_process_memory_in_mb: null,
total_instances: null,
per_app_tasks: null,
log_rate_limit_in_bytes_per_second: null,
},
services: {
paid_services_allowed: true,
total_service_instances: 100,
total_service_keys: 1000,
},
routes: {
total_routes: 1000,
total_reserved_ports: 5,
},
domains: {
total_domains: null,
},
relationships: {
organizations: {
data: [
{
guid: 'orgId1',
},
{
guid: 'foo',
},
{
guid: 'bar',
},
{
guid: 'baz',
},
],
},
},
links: {
self: {
href: 'https://api.dev.us-gov-west-1.aws-us-gov.cloud.gov/v3/organization_quotas/3564fac5-c405-480e-b758-57912da29f9e',
},
},
},
{
guid: '3564fac5-c405-480e-b758-57912da29f9f',
created_at: '2017-04-27T19:12:50Z',
updated_at: '2022-07-18T21:01:25Z',
name: 'staging',
apps: {
total_memory_in_mb: 500,
per_process_memory_in_mb: null,
total_instances: null,
per_app_tasks: null,
log_rate_limit_in_bytes_per_second: null,
},
services: {
paid_services_allowed: true,
total_service_instances: 100,
total_service_keys: 1000,
},
routes: {
total_routes: 1000,
total_reserved_ports: 5,
},
domains: {
total_domains: null,
},
relationships: {
organizations: {
data: [
{
guid: 'orgId2',
},
{
guid: 'foo',
},
{
guid: 'bar',
},
{
guid: 'baz',
},
],
},
},
links: {
self: {
href: 'https://api.dev.us-gov-west-1.aws-us-gov.cloud.gov/v3/organization_quotas/3564fac5-c405-480e-b758-57912da29f9e',
},
},
},
],
};
40 changes: 40 additions & 0 deletions __tests__/components/LastViewedOrgLink.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* @jest-environment jsdom
*/
import { cookies } from 'next/headers';
import { describe, expect, it, beforeEach } from '@jest/globals';
import { render } from '@testing-library/react';
import { LastViewedOrgLink } from '@/components/LastViewedOrgLink';

/* global jest */
/* eslint no-undef: "off" */
jest.mock('next/headers', () => ({
cookies: jest.fn(),
}));
/* eslint no-undef: "error" */

describe.skip('<LastViewedOrgLink />', () => {
describe('when no org id cookie is found', () => {
beforeEach(() => {
// TODO: figure out how to mock cookies (we're mocking same as token.test.js but it doesn't work here)
cookies.mockImplementation(() => ({
get: () => null,
}));
});
it('returns nothing', async () => {
// act
const component = render(await LastViewedOrgLink());
// assert
const link = component.queryByRole('link');
expect(link).not.toBeInTheDocument();
});
});

describe('when get org request fails', () => {
it.todo('returns nothing');
});

describe('when org cookie is present and get org succeeds', () => {
it.todo('shows correct hyperlink and org name');
});
});
38 changes: 38 additions & 0 deletions __tests__/components/MemoryBar.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* @jest-environment jsdom
*/
import { describe, expect, it, beforeEach } from '@jest/globals';
import { render, screen } from '@testing-library/react';
import { MemoryBar } from '@/components/MemoryBar';

describe('<MemoryBar />', () => {
describe('when allocated memory is null', () => {
render(<MemoryBar memoryUsed={50} memoryAllocated={null} />);

it('says no upper limit', () => {
const text = screen.queryByText(/no upper limit/);
expect(text).toBeInTheDocument();
});

it('hides memory remaining', () => {
const text = screen.queryByText(/remaining/);
expect(text).not.toBeInTheDocument();
});
});

describe('when allocated memory', () => {
beforeEach(() => {
render(<MemoryBar memoryUsed={40} memoryAllocated={100} />);
});

it('returns content', () => {
const component = screen.queryByTestId('memory-bar');
expect(component).toBeInTheDocument();
});

it('shows correct amount remaining', () => {
const remainingText = screen.queryByText(/60MB remaining/);
expect(remainingText).toBeInTheDocument();
});
});
});

This file was deleted.

50 changes: 50 additions & 0 deletions __tests__/components/ProgressBar.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* @jest-environment jsdom
*/
import { describe, expect, it } from '@jest/globals';
import { render } from '@testing-library/react';
import { ProgressBar } from '@/components/ProgressBar';

describe('<ProgressBar />', () => {
describe('when changeColors is false', () => {
it('keeps progress bar green', () => {
// act
const { container } = render(
<ProgressBar total={100} fill={99} changeColors={false} />
);
// assert
const progressDiv = container.querySelector('.bg-mint');
expect(progressDiv).toBeInTheDocument();
});
});

describe('when percentage is less than threshold1', () => {
it('shows a green bar', () => {
// act
const { container } = render(<ProgressBar total={100} fill={25} />);
// assert
const progressDiv = container.querySelector('.bg-mint');
expect(progressDiv).toBeInTheDocument();
});
});

describe('when percentage is above threshold1 but below threshold2', () => {
it('shows an light red bar', () => {
// act
const { container } = render(<ProgressBar total={100} fill={76} />);
// assert
const progressDiv = container.querySelector('.bg-red-30v');
expect(progressDiv).toBeInTheDocument();
});
});

describe('when percentage is above threshold2', () => {
it('shows a deep red bar', () => {
// act
const { container } = render(<ProgressBar total={100} fill={95} />);
// assert
const progressDiv = container.querySelector('.bg-red-40v');
expect(progressDiv).toBeInTheDocument();
});
});
});
Loading