+
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
49085d2
Use a suggestion of displaying the YAML file much better
kienstra Mar 16, 2022
bb5833e
Use yq to pretty-print YAML, thanks to suggestion
kienstra Mar 17, 2022
e7e6df3
Fix the file name to be result.log
kienstra Mar 17, 2022
01fa645
Fix failed unit tests for getResultFilePath()
kienstra Mar 17, 2022
1b8f1c2
Save the job log as a timestamp
kienstra Mar 18, 2022
23213b5
Create a directory in /tmp/local-ci/ for each repo that is run
kienstra Mar 18, 2022
33055e4
Rename file to getLogFilePath.ts
kienstra Mar 18, 2022
437b240
Fix failed unit tests
kienstra Mar 18, 2022
7188daa
Acknowledge yq in README.md
kienstra Mar 18, 2022
0119eda
Merge branch 'develop' into update/cleaner-terminal-output
kienstra Mar 20, 2022
fe54af0
Change how logs are saved
kienstra Apr 9, 2022
ec48619
Do npm audit fix to update some dependencies
kienstra Apr 9, 2022
622c7f8
Merge branch 'develop' into update/cleaner-terminal-output
kienstra Apr 9, 2022
389372b
Change CircleCI build job to package
kienstra Apr 9, 2022
fe6362b
Correct a failed unit test
kienstra Apr 9, 2022
3f9e0fe
Remove needless browser-tools orb
kienstra Apr 9, 2022
a7a0d71
Bump the bundled CircleCI binary with this
kienstra Apr 11, 2022
ee12f2c
Bump the CircleCI binary to the latest
kienstra Apr 12, 2022
c8f4513
Commit 2 files I forgot to commit
kienstra Apr 12, 2022
c0d9131
Move CircleCI binary back to a slightly older version
kienstra Apr 14, 2022
1523369
Remove 2 files that aren't needed in this PR
kienstra Apr 14, 2022
50d2d9b
Remove another file that't not needed in this PR
kienstra Apr 14, 2022
f389a7e
Show job logs in the job TreeView
kienstra Apr 15, 2022
79dc316
Add Log and util function I forgot to commit
kienstra Apr 15, 2022
724615d
Allow refreshing the job TreeView
kienstra Apr 21, 2022
f54b5c3
Remove getParent(), as it shouldn't be needed without calling jobProv…
kienstra Apr 21, 2022
92aee9e
Remove needless optional argument from logFilePath()
kienstra Apr 21, 2022
4c88643
Clarify a JSDoc comment
kienstra Apr 21, 2022
475aa3f
Move init() to the top of JobProvider
kienstra Apr 21, 2022
5accb0c
Pass jobTreeView directly to subscriptions
kienstra Apr 21, 2022
405315a
Add back in the hardRefresh() function
kienstra Apr 22, 2022
ce2f83a
Change refresh() back to hardRefresh()
kienstra Apr 22, 2022
7b3da0c
Correct the type for getChildren()
kienstra Apr 22, 2022
8f131b6
Handle the log in the success/failure of a job
kienstra Apr 22, 2022
35d60b2
Capitalize enum keys, as this seems the convention
kienstra Apr 22, 2022
3e8a515
Add unit tests for 2 util functions
kienstra Apr 22, 2022
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
6 changes: 2 additions & 4 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,16 @@ version: 2.1

orbs:
node: circleci/node@5.0.0
browser-tools: circleci/browser-tools@1.1.0

jobs:
test:
docker:
- image: cimg/node:16.8.0-browsers
steps:
- checkout
- browser-tools/install-browser-tools
- node/install-packages
- run: npm test
build:
package:
docker:
- image: cimg/node:16.8.0-browsers
steps:
Expand All @@ -28,4 +26,4 @@ workflows:
test-lint:
jobs:
- test
- build
- package
4 changes: 2 additions & 2 deletions node/binary.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ const supportedPlatforms = [
{
type: "Linux",
architecture: "x64",
url: "https://github.com/CircleCI-Public/circleci-cli/releases/download/v0.1.16947/circleci-cli_0.1.16947_linux_amd64.tar.gz",
url: "https://github.com/CircleCI-Public/circleci-cli/releases/download/v0.1.17087/circleci-cli_0.1.17087_linux_amd64.tar.gz",
},
{
type: "Darwin",
architecture: "x64",
url: "https://github.com/CircleCI-Public/circleci-cli/releases/download/v0.1.16947/circleci-cli_0.1.16947_darwin_amd64.tar.gz",
url: "https://github.com/CircleCI-Public/circleci-cli/releases/download/v0.1.17087/circleci-cli_0.1.17087_darwin_amd64.tar.gz",
},
];

Expand Down
24 changes: 12 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions src/classes/Job.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,8 @@ export default class Job extends vscode.TreeItem {
setIsFailure(): void {
this.description = '❌';
}

setExpanded(): void {
this.collapsibleState = vscode.TreeItemCollapsibleState.Expanded;
}
}
105 changes: 74 additions & 31 deletions src/classes/JobProvider.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import * as fs from 'fs';
import * as path from 'path';
import * as vscode from 'vscode';
import TelemetryReporter from '@vscode/extension-telemetry';
import Command from './Command';
import Job from './Job';
import Log from './Log';
import Warning from './Warning';
import getAllConfigFilePaths from '../utils/getAllConfigFilePaths';
import getAllJobs from '../utils/getAllJobs';
import getConfigFilePath from '../utils/getConfigFilePath';
import getLogFilesDirectory from '../utils/getLogFilesDirectory';
import getTrialLength from '../utils/getTrialLength';
import isDockerRunning from '../utils/isDockerRunning';
import isLicenseValid from '../utils/isLicenseValid';
Expand All @@ -23,11 +26,11 @@ import {
} from '../constants';

enum JobError {
dockerNotRunning,
licenseKey,
noConfigFilePathInWorkspace,
noConfigFilePathSelected,
processFile,
DockerNotRunning,
LicenseKey,
NoConfigFilePathInWorkspace,
NoConfigFilePathSelected,
ProcessFile,
}

export default class JobProvider
Expand All @@ -42,25 +45,29 @@ export default class JobProvider
private jobErrorMessage: string | undefined;
private runningJob: string | undefined;
private jobDependencies: Map<string, string[] | null> | undefined;
private logs: Record<string, string[]> = {};

constructor(
private readonly context: vscode.ExtensionContext,
private readonly reporter: TelemetryReporter
) {}

/**
* Refreshes the TreeView, without processing the config file.
*/
async init() {
await this.loadJobs();
await this.loadLogs();
}

/** Refreshes the TreeView, without processing the config file. */
async refresh(job?: Job, skipMessage?: boolean): Promise<void> {
await this.loadJobs(true, skipMessage);
await this.loadLogs();
this._onDidChangeTreeData.fire(job);
}

/**
* Processes the config file(s) in addition to refreshing.
*/
/** Processes the config file(s) in addition to refreshing. */
async hardRefresh(job?: Job, suppressMessage?: boolean): Promise<void> {
await this.loadJobs(false, suppressMessage);
await this.loadLogs();
this._onDidChangeTreeData.fire(job);
}

Expand All @@ -73,25 +80,25 @@ export default class JobProvider
this.jobErrorMessage = undefined;
this.runningJob = undefined;

let processedConfig;
let processError;

const configFilePath = await getConfigFilePath(this.context);
if (!configFilePath || !fs.existsSync(configFilePath)) {
this.reporter.sendTelemetryEvent('configFilePath');

const doExistConfigPaths = !!(await getAllConfigFilePaths(this.context))
.length;
if (doExistConfigPaths) {
this.jobErrorType = JobError.noConfigFilePathSelected;
this.jobErrorType = JobError.NoConfigFilePathSelected;
} else {
this.reporter.sendTelemetryErrorEvent('noConfigFile');
this.jobErrorType = JobError.noConfigFilePathInWorkspace;
this.jobErrorType = JobError.NoConfigFilePathInWorkspace;
}

return;
}

let processedConfig;
let processError;

if (!skipConfigProcessing) {
const configResult = prepareConfig(
configFilePath,
Expand All @@ -111,19 +118,19 @@ export default class JobProvider
);

if (!shouldEnableExtension) {
this.jobErrorType = JobError.licenseKey;
this.jobErrorType = JobError.LicenseKey;
return;
}

if (!isDockerRunning()) {
this.reporter.sendTelemetryErrorEvent('dockerNotRunning');
this.jobErrorType = JobError.dockerNotRunning;
this.jobErrorType = JobError.DockerNotRunning;
this.jobErrorMessage = getDockerError();
return;
}

if (processError) {
this.jobErrorType = JobError.processFile;
this.jobErrorType = JobError.ProcessFile;
this.jobErrorMessage = processError;
return;
}
Expand All @@ -138,11 +145,24 @@ export default class JobProvider
}
}

async loadLogs() {
const configFilePath = await getConfigFilePath(this.context);

for (const jobName of this.jobDependencies?.keys() ?? []) {
const logDirectory = getLogFilesDirectory(configFilePath, jobName);
if (fs.existsSync(logDirectory)) {
this.logs[jobName] = fs.readdirSync(logDirectory).map((logFile) => {
return path.join(logDirectory, logFile);
});
}
}
}

getTreeItem(treeItem: vscode.TreeItem): vscode.TreeItem {
return treeItem;
}

getChildren(parentElement: vscode.TreeItem): vscode.TreeItem[] {
getChildren(parentElement: Job | Log): vscode.TreeItem[] {
if (!parentElement) {
return this.jobs.length
? this.getJobTreeItems(
Expand All @@ -154,15 +174,18 @@ export default class JobProvider
}

const jobNames = this.jobDependencies?.keys();
if (!jobNames) {
if (
!jobNames ||
('getJobName' in parentElement && !parentElement.getJobName())
) {
return [];
}

const children = [];
for (const jobName of jobNames) {
const jobDependencies = this?.jobDependencies?.get(jobName) ?? [];
const dependencyLength = jobDependencies?.length;
// This element's children should be the jobs that list it as their last dependency in the requires array.
// This element's children include the jobs that list it as their last dependency in the requires array.
if (
dependencyLength &&
parentElement.label === jobDependencies[dependencyLength - 1]
Expand All @@ -171,43 +194,56 @@ export default class JobProvider
}
}

return this.getJobTreeItems(children);
return [
...this.getLogTreeItems(
'getJobName' in parentElement ? parentElement.getJobName() : ''
),
...this.getJobTreeItems(children),
];
}

getLogTreeItems(jobName: string): Log[] {
return (
this.logs[jobName]?.map(
(logFile) => new Log(path.basename(logFile), logFile)
) ?? []
);
}

getJobTreeItems(jobs: string[]): Job[] {
return jobs.map(
(jobName) =>
new Job(jobName, jobName === this.runningJob, this.hasChildJob(jobName))
new Job(jobName, jobName === this.runningJob, this.hasChild(jobName))
);
}

getErrorTreeItems(): vscode.TreeItem[] {
const errorMessage = this.getJobErrorMessage();

switch (this.jobErrorType) {
case JobError.dockerNotRunning:
case JobError.DockerNotRunning:
return [
new Warning('Error: is Docker running?'),
new vscode.TreeItem(errorMessage),
new Command('Try Again', `${JOB_TREE_VIEW_ID}.refresh`),
];
case JobError.licenseKey:
case JobError.LicenseKey:
return [
new Warning('Please enter a Local CI license key.'),
new Command('Get License', GET_LICENSE_COMMAND),
new Command('Enter License', ENTER_LICENSE_COMMAND),
];
case JobError.noConfigFilePathInWorkspace:
case JobError.NoConfigFilePathInWorkspace:
return [
new Warning('Error: No .circleci/config.yml found'),
new Command('Create a config for me', CREATE_CONFIG_FILE_COMMAND),
];
case JobError.noConfigFilePathSelected:
case JobError.NoConfigFilePathSelected:
return [
new Warning('Error: No jobs found'),
new Command('Select repo', 'localCiJobs.selectRepo'),
];
case JobError.processFile:
case JobError.ProcessFile:
return [
new Warning('Error processing the CircleCI config:'),
new vscode.TreeItem(
Expand All @@ -233,9 +269,16 @@ export default class JobProvider
}

/**
* A job has a child job if any other job has it as the last value in its requires array.
* A job has a child if either:
*
* 1. It has a log
* 2. It's a dependency of another job (another job has it as the last value in its requires array)
*/
hasChildJob(jobName: string): boolean {
hasChild(jobName: string): boolean {
if (this.logs[jobName]) {
return true;
}

for (const [, dependecies] of this?.jobDependencies ?? []) {
if (
dependecies?.length &&
Expand Down
18 changes: 18 additions & 0 deletions src/classes/Log.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as vscode from 'vscode';

export default class Log extends vscode.TreeItem {
constructor(public readonly label: string, public readonly filePath: string) {
super(label);
const tooltip = 'Log for CircleCI® job';
this.collapsibleState = vscode.TreeItemCollapsibleState.None;
this.command = {
title: label,
command: 'vscode.open',
tooltip,
arguments: [filePath],
};

this.iconPath = new vscode.ThemeIcon('output');
this.tooltip = tooltip;
}
}
2 changes: 0 additions & 2 deletions src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@ export const DYNAMIC_CONFIG_PATH_IN_CONTAINER = path.join(
DYNAMIC_CONFIG_FILE_NAME
);
export const HOST_TMP_DIRECTORY = '/tmp/local-ci'; // Also hard-coded in node/uninstall.js, change that if this changes. Be careful changing this, as there's an rm -rf for it.
export const PROCESS_FILE_DIRECTORY = path.join(HOST_TMP_DIRECTORY, 'process');
export const LOCAL_VOLUME_DIRECTORY = path.join(HOST_TMP_DIRECTORY, 'volume');
export const RUN_JOB_COMMAND = 'local-ci.job.run';
export const CREATE_CONFIG_FILE_COMMAND = 'local-ci.create.config';
export const CONTINUE_PIPELINE_STEP_NAME = 'Continue the pipeline';
Expand Down
Loading
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载