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

WX-915 Restore Job Lists Page #780

Merged
merged 37 commits into from
Mar 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
ecb7980
wx-922 saving current progress, linter working on vscode
JVThomas Feb 2, 2023
9e68b5f
WX-922 updated circle jobs, added scripts to package.json, disabled c…
JVThomas Feb 3, 2023
1784cfb
Merge branch 'master' into wx-922
JVThomas Feb 3, 2023
9ef2c43
WX-922 added tsc job, added ES2022 lib for compilation
JVThomas Feb 3, 2023
2468cdc
WX-922 testing if tsc and eslint jobs are configured
JVThomas Feb 3, 2023
88d8463
wx-922 corrected pkg manager version to that referenced in repo
JVThomas Feb 3, 2023
f8bec7c
wx-723 initial setup of oidc library, checking to see if setup works
JVThomas Feb 15, 2023
b2e101b
WX-723 library up and running, need to figure out oauth uri configura…
JVThomas Feb 17, 2023
64ead76
wx-723 redirect works, user profile being read in, need to come up wi…
JVThomas Feb 21, 2023
acf3704
wx-723 code cleanup, moving to implement silent refresh
JVThomas Feb 22, 2023
103545f
wx-723 pulled async methods out of synchronous constructors
JVThomas Feb 23, 2023
d647465
wx-723 updated tests, running async auth service method at the start …
JVThomas Feb 24, 2023
95961b4
wx-723 some code cleanup
JVThomas Feb 27, 2023
08ed04a
wx-723 switched to localStorage, updates to redirect logic
JVThomas Feb 27, 2023
a2db960
wx-723 code corrections to storage and redirect method call
JVThomas Feb 28, 2023
eb75841
wx-723 removed silent refresh files, code cleanup
JVThomas Mar 2, 2023
39f69dd
wx-723 removed unused static files and scripts
JVThomas Mar 2, 2023
2a80a5a
wx-723 added tests for redirect component, syntax corrections
JVThomas Mar 3, 2023
9a7b5b6
wx-723 resolved merge conflict
JVThomas Mar 3, 2023
89e5a10
wx-723 updated README.md
JVThomas Mar 6, 2023
f7d8138
wx-915 restored job list page, working through pagination issues on b…
JVThomas Mar 8, 2023
1bc8e8d
wx-915 fixed encode/decode logic for jobs page, updated default redir…
JVThomas Mar 13, 2023
af1faf7
wx-915 updated decode tests so that incoming tokens are strings
JVThomas Mar 13, 2023
19b3bd7
wx-915 more updates to tests to ensure that strings, not bytes, are b…
JVThomas Mar 13, 2023
5203d4f
wx-915 removed comment
JVThomas Mar 14, 2023
3d6329e
wx-915 added conditionals to get_response_message method
JVThomas Mar 15, 2023
890fcc9
wx-915 updating tests (one more test left to fix)
JVThomas Mar 16, 2023
3e5a037
wx-915 updated table column test
JVThomas Mar 16, 2023
ea1971c
wx-915 restored back to jobs button and associated tests
JVThomas Mar 16, 2023
bffa382
wx-915 restored redirect from root to job list
JVThomas Mar 16, 2023
68dd731
Update TERRA_QUICKSTART.md
jgainerdewar Mar 20, 2023
3600b8c
wx-915 fixed pagination button styling and missing column selection
JVThomas Mar 21, 2023
ba51ea1
wx-915 updated removed fuction reference in mat-chip components
JVThomas Mar 21, 2023
161eef3
wx-915 updated test for filter chip
JVThomas Mar 21, 2023
5b1349e
wx-915 resolved merge conflicts
JVThomas Mar 21, 2023
89dea67
wx-915 added front end solution for display value updates, css update…
JVThomas Mar 22, 2023
698e892
wx-915 matched table count border color with pagination buttons' bord…
JVThomas Mar 22, 2023
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
13 changes: 6 additions & 7 deletions TERRA_QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,14 @@ work the same as local testing.
## Releasing JobManager in Terra

Start by following release instructions in [the README](README.md#build-docker-images-and-releases).
Once the new version exists in GCR and Github, update the dev environment and BEE template in Beehive.
For each of these:
* broad.io/beehive/r/chart-release/swatomation/jobmanager
* broad.io/beehive/r/chart-release/dev/jobmanager
Follow these steps:
Once the new version exists in GCR and Github, update the dev environment in Beehive.
[Go here](broad.io/beehive/r/chart-release/dev/jobmanager) and follow these steps:
* Click on `On Demand Deployment > Change Versions`
* In the `Specify App Version > Set Exact Version` box, change the version to the one you just pushed
* In the `Specify App Version > Set Exact Version` box, change the version to the one you just pushed.
Make sure you include the EXACT tag you pushed with, it's easy to forget the `v`.
* Click `Calculate and Preview`
* After reviewing changes, click `Apply 1 change` on the right
In dev, this will kick off a Github Action that will update the dev k8s cluster.
This will kick off a Github Action that will update the dev k8s cluster. Test in dev and ensure that JM is functional
and your changes are present.

The version should automatically roll out to prod with the new monolith release.
12 changes: 6 additions & 6 deletions servers/cromwell/jobs/controllers/jobs_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ def get_job(id, **kwargs):

:rtype: JobMetadataResponse
"""

url = '{cromwell_url}/{id}/metadata?{query}'.format(
cromwell_url=_get_base_url(),
id=id,
Expand Down Expand Up @@ -456,7 +455,6 @@ def query_jobs(body, **kwargs):
page = page_from_offset(offset, query_page_size)

has_auth = headers is not None

response = requests.post(_get_base_url() + '/query',
json=cromwell_query_params(
query, page, query_page_size, has_auth),
Expand All @@ -477,7 +475,7 @@ def query_jobs(body, **kwargs):
next_page_token = page_tokens.encode_offset(offset + query_page_size)
return QueryJobsResponse(results=jobs_list,
total_size=total_results,
next_page_token=next_page_token)
next_page_token=next_page_token.decode())


def get_last_page(total_results, page_size):
Expand Down Expand Up @@ -596,7 +594,6 @@ def get_operation_details(job, operation, **kwargs):

if response.status_code != 200:
handle_error(response)

return JobOperationResponse(id=job, details=response.text)


Expand Down Expand Up @@ -758,8 +755,11 @@ def _is_call_cached(metadata):

def _get_response_message(response):
logger.error('Response error: {}'.format(response.content))
if is_jsonable(response) and response.json().get('message'):
return response.json().get('message')
if is_jsonable(response):
try:
return response.json().get('message')
except:
return str(response.json())
return str(response)


Expand Down
4 changes: 2 additions & 2 deletions servers/jm_utils/jm_utils/page_tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ def _decode(token):
"""Decodes the pagination token.

Args:
token: (string) Base64 encoded JSON pagination token
token: (string) JSON pagination token

Returns:
(dict) The token dictionary representing a page of items.
"""
if token is None:
return None
# Pad the token out to be divisible by 4.
padded_token = token + '='.encode() * (4 - (len(token) % 4))
padded_token = bytes(token, 'utf8') + '='.encode() * (4 - (len(token) % 4))
decoded_token = base64.urlsafe_b64decode(padded_token)
token_dict = json.loads(decoded_token)
if not token_dict or not isinstance(token_dict, dict):
Expand Down
16 changes: 8 additions & 8 deletions servers/jm_utils/jm_utils/test/test_page_tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class TestJmUtils(unittest.TestCase):
""" jm_utils unit tests """

def test_encode_decode_offset(self):
encoded = page_tokens.encode_offset(12)
encoded = page_tokens.encode_offset(12).decode()
decoded = page_tokens.decode_offset(encoded)
self.assertEqual(decoded, 12)

Expand All @@ -25,9 +25,9 @@ def test_encode_offset_zero(self):
str(context.exception))

def test_decode_offset_zero(self):
encoded = page_tokens._encode({'of': 0})
token = page_tokens._encode({'of': 0}).decode()
with self.assertRaises(ValueError) as context:
page_tokens.decode_offset(encoded)
page_tokens.decode_offset(token)
self.assertIn('Invalid offset token JSON', str(context.exception))

def test_decode_offset_none(self):
Expand All @@ -36,7 +36,7 @@ def test_decode_offset_none(self):
def test_encode_decode_create_time_max(self):
now = datetime.datetime.now().replace(microsecond=0).replace(
tzinfo=pytz.utc)
encoded = page_tokens.encode_create_time_max(now, 'offset-id')
encoded = page_tokens.encode_create_time_max(now, 'offset-id').decode()
decoded_create_time, decoded_offset_id = page_tokens.decode_create_time_max(
encoded)
self.assertEqual(decoded_create_time, now)
Expand All @@ -53,14 +53,14 @@ def test_encode_create_time_max_invalid(self):
str(context.exception))

def test_decode_create_time_max_invalid(self):
encoded = page_tokens._encode({"cb": "not-a-date"})
token = page_tokens._encode({"cb": "not-a-date"}).decode()
with self.assertRaises(ValueError) as context:
page_tokens.decode_create_time_max(encoded)
page_tokens.decode_create_time_max(token)
self.assertIn("Invalid created before in token JSON",
str(context.exception))
encoded = page_tokens._encode({'cb': 10, 'oi': 123})
token = page_tokens._encode({'cb': 10, 'oi': 123}).decode()
with self.assertRaises(ValueError) as context:
page_tokens.decode_create_time_max(encoded)
page_tokens.decode_create_time_max(token)
self.assertIn("Invalid offset ID in token JSON",
str(context.exception))

Expand Down
43 changes: 24 additions & 19 deletions ui/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,49 +24,54 @@ import { SignInRedirectComponent } from './sign-in/sign-in-redirect.component';
// UI Components to the <router-outlet> element in the main AppComponent.
const routes: Routes = [
{
path: '',
component: PagenotfoundComponent
path: "",
redirectTo: "jobs",
pathMatch: "full",
},
{
path: 'sign_in',
path: "sign_in",
component: SignInComponent,
canActivate: [CapabilitiesActivator]
canActivate: [CapabilitiesActivator],
},
{
path: 'projects',
path: "projects",
component: ProjectsComponent,
canActivate: [CapabilitiesActivator]
canActivate: [CapabilitiesActivator],
},
{
path: 'dashboard',
path: "dashboard",
component: DashboardComponent,
//TODO: (zach) if the projectId param is missing, it gives a 400 error which is not desired.
// Should be redirect to the home page.
canActivate: [CapabilitiesActivator],
runGuardsAndResolvers: 'always',
runGuardsAndResolvers: "always",
resolve: {
aggregations: DashboardResolver
}
aggregations: DashboardResolver,
},
},
{
path: 'jobs',
component: PagenotfoundComponent
path: "jobs",
component: JobListComponent,
canActivate: [CapabilitiesActivator],
resolve: {
stream: JobListResolver,
},
},
{
path: 'jobs/:id',
path: "jobs/:id",
component: JobDetailsComponent,
canActivate: [CapabilitiesActivator],
resolve: {
job: JobDetailsResolver
}
job: JobDetailsResolver,
},
},
{
path: 'redirect-from-oauth',
component: SignInRedirectComponent
path: "redirect-from-oauth",
component: SignInRedirectComponent,
},
{
path: '**',
component: PagenotfoundComponent
path: "**",
component: PagenotfoundComponent,
},
];

Expand Down
1 change: 1 addition & 0 deletions ui/src/app/job-details/job-details.component.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<div class="details-container">
<jm-panels [job]="job"
[primaryLabels]="primaryLabels"
(close)="handleClose()"
(navUp)="handleNavUp()"></jm-panels>
<jm-resources *ngIf="hasResources()" [job]="job"></jm-resources>
<!-- Only show the tasks table if there is at least 1 task. -->
Expand Down
21 changes: 21 additions & 0 deletions ui/src/app/job-details/job-details.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,27 @@ describe('JobDetailsComponent', () => {
expect(de.query(By.css('#job-id')).nativeElement.value).toContain(jobId);
}));

it("navigates to jobs table on close", fakeAsync(() => {
const q = URLSearchParamsUtils.encodeURLSearchParams({
extensions: { projectId: "foo" },
});
router.navigate(["jobs/" + jobId], { queryParams: { q } });
tick();

fixture.detectChanges();
tick();

const de: DebugElement = fixture.debugElement;
de.query(By.css(".close")).nativeElement.click();
fixture.detectChanges();
tick();

const fakeJobs = fixture.debugElement.query(By.css(".fake-jobs"));
expect(fakeJobs).toBeTruthy();
const fakeJobsRoute = fakeJobs.componentInstance.route;
expect(fakeJobsRoute.snapshot.queryParams["q"]).toEqual(q);
}));

@Component({
selector: 'jm-test-app',
template: '<router-outlet></router-outlet>'
Expand Down
90 changes: 62 additions & 28 deletions ui/src/app/job-details/job-details.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import {CapabilitiesResponse} from "../shared/model/CapabilitiesResponse";
import {objectNotEmpty} from '../shared/common';

@Component({
selector: 'jm-job-details',
templateUrl: './job-details.component.html',
styleUrls: ['./job-details.component.css'],
selector: "jm-job-details",
templateUrl: "./job-details.component.html",
styleUrls: ["./job-details.component.css"],
})
export class JobDetailsComponent implements OnInit {
@ViewChild(JobTabsComponent) taskTabs;
Expand All @@ -29,29 +29,47 @@ export class JobDetailsComponent implements OnInit {
private readonly router: Router,
private readonly route: ActivatedRoute,
private readonly settingsService: SettingsService,
private readonly capabilitiesService: CapabilitiesService) {
private readonly capabilitiesService: CapabilitiesService
) {
this.capabilities = capabilitiesService.getCapabilitiesSynchronous();
}

ngOnInit(): void {
this.job = this.route.snapshot.data['job'];
const req = URLSearchParamsUtils.unpackURLSearchParams(this.route.snapshot.queryParams['q']);
this.projectId = req.extensions.projectId || '';
this.job = this.route.snapshot.data["job"];
const req = URLSearchParamsUtils.unpackURLSearchParams(
this.route.snapshot.queryParams["q"]
);
this.projectId = req.extensions.projectId || "";

// if the user has saved settings for display columns, use that
// otherwise, go with default list from capabilities
if (this.settingsService.getSavedSettingValue('displayColumns', this.projectId)) {
this.primaryLabels = this.settingsService.getSavedSettingValue('displayColumns', this.projectId).filter(field => field.match('labels.')).map(field => field.replace('labels.',''));
if (
this.settingsService.getSavedSettingValue(
"displayColumns",
this.projectId
)
) {
this.primaryLabels = this.settingsService
.getSavedSettingValue("displayColumns", this.projectId)
.filter((field) => field.match("labels."))
.map((field) => field.replace("labels.", ""));
} else if (this.capabilities.displayFields) {
this.primaryLabels = this.capabilities.displayFields.map((df) => df.field).filter(field => field.match('labels.')).map(field => field.replace('labels.',''));
this.primaryLabels = this.capabilities.displayFields
.map((df) => df.field)
.filter((field) => field.match("labels."))
.map((field) => field.replace("labels.", ""));
} else if (this.job.labels) {
this.primaryLabels = Object.keys(this.job.labels);
}
}

hasTabs(): boolean {
if (objectNotEmpty(this.job.inputs) || objectNotEmpty(this.job.outputs) || objectNotEmpty(this.job.failures)) {
return true;
if (
objectNotEmpty(this.job.inputs) ||
objectNotEmpty(this.job.outputs) ||
objectNotEmpty(this.job.failures)
) {
return true;
}
if (this.job.extensions) {
let tasks: TaskMetadata[] = this.job.extensions.tasks || [];
Expand All @@ -61,45 +79,61 @@ export class JobDetailsComponent implements OnInit {

handleNavUp(): void {
if (this.job.extensions.parentJobId) {
this.router.navigate(['/jobs/' + this.job.extensions.parentJobId], {
this.router
.navigate(["/jobs/" + this.job.extensions.parentJobId], {
queryParams: {
q: this.route.snapshot.queryParams["q"],
},
replaceUrl: true,
skipLocationChange: false,
})
.then((result) => {
this.handleNav();
});
}
}

handleNavDown(id: string): void {
this.router
.navigate(["/jobs/" + id], {
queryParams: {
'q': this.route.snapshot.queryParams['q']
q: this.route.snapshot.queryParams["q"],
},
replaceUrl: true,
skipLocationChange: false
skipLocationChange: false,
})
.then(result => {
.then((result) => {
this.handleNav();
});
}
}

handleNavDown(id: string): void {
this.router.navigate(['/jobs/' + id], {
handleClose(): void {
this.router.navigate(["jobs"], {
queryParams: {
'q': this.route.snapshot.queryParams['q']
q: this.route.snapshot.queryParams["q"],
},
replaceUrl: true,
skipLocationChange: false
})
.then(result => {
this.handleNav();
});
}

hasResources(): boolean {
return (this.job.extensions && (this.job.extensions.sourceFile || this.job.extensions.logs));
return (
this.job.extensions &&
(this.job.extensions.sourceFile || this.job.extensions.logs)
);
}

private handleNav() {
this.job = this.route.snapshot.data['job'];
this.job = this.route.snapshot.data["job"];
this.jobPanels.job = this.job;
this.jobPanels.setUpExtensions();
if (this.taskTabs.failuresTable) {
this.taskTabs.failuresTable.dataSource = this.job.failures;
}
if (this.jobPanels.jobFailures) {
this.jobPanels.jobFailures.dataSource = this.job.failures.slice(0, this.jobPanels.numOfErrorsToShow);
this.jobPanels.jobFailures.dataSource = this.job.failures.slice(
0,
this.jobPanels.numOfErrorsToShow
);
}
if (this.job.extensions.tasks) {
this.taskTabs.timingDiagram.buildTimelineData(this.job.extensions.tasks);
Expand Down
Loading