Skip to content

Commit

Permalink
Auto merge pull request #504 from atomist/sdm
Browse files Browse the repository at this point in the history
* Allow goals to take implementations, side effects and callbacks

This requires more polish

* Autofix: TypeScript header

[atomist:generated] [atomist:autofix=TypeScript header]

* Minor update

* Autofix: TypeScript header

[atomist:generated] [atomist:autofix=TypeScript header]

* Fix registration lifecycle

* Autofix: TypeScript header

[atomist:generated] [atomist:autofix=TypeScript header]

* Autofix: tslint

[atomist:generated] [atomist:autofix=tslint]

* Not quite yet

* Autofix: TypeScript header

[atomist:generated] [atomist:autofix=TypeScript header]

* Autofix: tslint

[atomist:generated] [atomist:autofix=tslint]

* Clean out registrables that got registered

* Allow to reset the registrable tracking for tests

* Don't pass entire SDM; only the config

* Add well known goals to not break backwards compatibility

* Remove well known goals

* Fix approvalRequired message not being used

* Fix spelling error
  • Loading branch information
cdupuis authored and atomist-bot committed Sep 2, 2018
1 parent 68016ea commit 707bb8f
Show file tree
Hide file tree
Showing 34 changed files with 890 additions and 117 deletions.
8 changes: 4 additions & 4 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"@atomist/automation-client": "*"
},
"devDependencies": {
"@atomist/automation-client": "1.0.0-master.20180901185845",
"@atomist/automation-client": "1.0.0-master.20180902103822",
"@types/mocha": "^5.2.5",
"@types/power-assert": "^1.4.29",
"axios-mock-adapter": "^1.15.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,51 +16,47 @@

import { Goal } from "../../api/goal/Goal";
import { SdmGoalEvent } from "../../api/goal/SdmGoalEvent";
import { IsolatedGoalLauncher } from "../../api/goal/support/IsolatedGoalLauncher";
import {
GoalFulfillment,
GoalFulfillmentCallback,
GoalImplementation,
GoalImplementationMapper,
GoalSideEffect,
SdmGoalImplementationMapper,
} from "../../api/goal/support/SdmGoalImplementationMapper";
} from "../../api/goal/support/GoalImplementationMapper";
import { PushListenerInvocation } from "../../api/listener/PushListener";

/**
* Concrete implementation of SdmGoalImplementationMapper
* Concrete implementation of GoalImplementationMapper
*/
export class SdmGoalImplementationMapperImpl implements SdmGoalImplementationMapper {
export class DefaultGoalImplementationMapper implements GoalImplementationMapper {

private readonly implementations: GoalImplementation[] = [];
private readonly sideEffects: GoalSideEffect[] = [];
private readonly callbacks: GoalFulfillmentCallback[] = [];

constructor(private readonly goalLauncher: IsolatedGoalLauncher) {
}

public async findImplementationBySdmGoal(goal: SdmGoalEvent, inv: PushListenerInvocation): Promise<GoalImplementation> {
public findImplementationBySdmGoal(goal: SdmGoalEvent): GoalImplementation {
const matchedNames = this.implementations.filter(m =>
m.implementationName === goal.fulfillment.name &&
m.goal.context === goal.externalKey);

const matchedGoalImplementations = [];
for (const implementation of matchedNames) {
if (await implementation.pushTest.mapping(inv)) {
matchedGoalImplementations.push(implementation);
}
}

if (matchedGoalImplementations.length > 1) {
if (matchedNames.length > 1) {
throw new Error("Multiple mappings for name " + goal.fulfillment.name);
}
if (matchedGoalImplementations.length === 0) {
if (matchedNames.length === 0) {
throw new Error(`No implementation found with name '${goal.fulfillment.name}': ` +
`Found ${this.implementations.map(impl => impl.implementationName)}`);
}
return matchedGoalImplementations[0];
return matchedNames[0];
}

public addImplementation(implementation: GoalImplementation): this {
if (this.implementations.some(i =>
i.implementationName === implementation.implementationName &&
i.goal.name === implementation.goal.name &&
i.goal.environment === implementation.goal.environment)) {
throw new Error(`Implementation with name '${implementation.implementationName
}' already registered for goal '${implementation.goal.name}'`);
}
this.implementations.push(implementation);
return this;
}
Expand Down Expand Up @@ -100,8 +96,4 @@ export class SdmGoalImplementationMapperImpl implements SdmGoalImplementationMap
(c.goal.definition.environment.slice(0, -1) === g.environment
|| c.goal.definition.environment === g.environment));
}

public getIsolatedGoalLauncher(): IsolatedGoalLauncher {
return this.goalLauncher;
}
}
23 changes: 12 additions & 11 deletions src/api-helper/goal/chooseAndSetGoals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,14 @@ import { ExecuteGoal } from "../../api/goal/GoalInvocation";
import { Goals } from "../../api/goal/Goals";
import {
SdmGoalFulfillment,
SdmGoalFulfillmentMethod,
SdmGoalMessage,
} from "../../api/goal/SdmGoalMessage";
import {
GoalImplementationMapper,
isGoalImplementation,
isSideEffect,
SdmGoalImplementationMapper,
} from "../../api/goal/support/SdmGoalImplementationMapper";
isGoalSideEffect,
} from "../../api/goal/support/GoalImplementationMapper";
import {
GoalsSetListener,
GoalsSetListenerInvocation,
Expand Down Expand Up @@ -73,7 +74,7 @@ export interface ChooseAndSetGoalsRules {

goalSetter: GoalSetter;

implementationMapping: SdmGoalImplementationMapper;
implementationMapping: GoalImplementationMapper;
}

/**
Expand Down Expand Up @@ -121,7 +122,7 @@ export async function determineGoals(rules: {
projectLoader: ProjectLoader,
repoRefResolver: RepoRefResolver,
goalSetter: GoalSetter,
implementationMapping: SdmGoalImplementationMapper,
implementationMapping: GoalImplementationMapper,
},
circumstances: {
credentials: ProjectOperationCredentials,
Expand Down Expand Up @@ -155,7 +156,7 @@ export async function determineGoals(rules: {

}

async function sdmGoalsFromGoals(implementationMapping: SdmGoalImplementationMapper,
async function sdmGoalsFromGoals(implementationMapping: GoalImplementationMapper,
repoRefResolver: RepoRefResolver,
pli: PushListenerInvocation,
determinedGoals: Goals,
Expand All @@ -173,19 +174,19 @@ async function sdmGoalsFromGoals(implementationMapping: SdmGoalImplementationMap
}

async function fulfillment(rules: {
implementationMapping: SdmGoalImplementationMapper,
implementationMapping: GoalImplementationMapper,
}, g: Goal, inv: PushListenerInvocation): Promise<SdmGoalFulfillment> {
const { implementationMapping } = rules;
const plan = await implementationMapping.findFulfillmentByPush(g, inv);
if (isGoalImplementation(plan)) {
return constructSdmGoalImplementation(plan);
}
if (isSideEffect(plan)) {
return { method: "side-effect", name: plan.sideEffectName };
if (isGoalSideEffect(plan)) {
return { method: SdmGoalFulfillmentMethod.SideEffect, name: plan.sideEffectName };
}

logger.warn("FYI, no implementation found for '%s'", g.name);
return { method: "other", name: "unspecified-yo" };
logger.warn("No implementation found for '%s'", g.name);
return { method: SdmGoalFulfillmentMethod.Other, name: "unknown" };
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/api-helper/goal/executeGoal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ export function markStatus(parameters: {
}) {
const { context, sdmGoal, goal, result, error, progressLogUrl } = parameters;
const newState = result.code !== 0 ? SdmGoalState.failure :
result.requireApproval ? SdmGoalState.waiting_for_approval : SdmGoalState.success;
(result.requireApproval || goal.definition.approvalRequired ? SdmGoalState.waiting_for_approval : SdmGoalState.success);

return updateGoal(
context,
Expand Down
9 changes: 5 additions & 4 deletions src/api-helper/goal/storeGoals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,12 @@ import { SdmGoalEvent } from "../../api/goal/SdmGoalEvent";
import {
GoalRootType,
SdmGoalFulfillment,
SdmGoalFulfillmentMethod,
SdmGoalKey,
SdmGoalMessage,
SdmProvenance,
} from "../../api/goal/SdmGoalMessage";
import { GoalImplementation } from "../../api/goal/support/SdmGoalImplementationMapper";
import { GoalImplementation } from "../../api/goal/support/GoalImplementationMapper";
import {
OnAnyRequestedSdmGoal,
OnPushToAnyBranch,
Expand Down Expand Up @@ -100,7 +101,7 @@ export function goalCorrespondsToSdmGoal(goal: Goal, sdmGoal: SdmGoalKey): boole

export function constructSdmGoalImplementation(gi: GoalImplementation): SdmGoalFulfillment {
return {
method: "SDM fulfill on requested",
method: SdmGoalFulfillmentMethod.Sdm,
name: gi.implementationName,
};
}
Expand All @@ -115,8 +116,8 @@ export function constructSdmGoal(ctx: HandlerContext, parameters: {
url?: string,
fulfillment?: SdmGoalFulfillment,
}): SdmGoalMessage {
const {goalSet, goal, goalSetId, state, id, providerId, url} = parameters;
const fulfillment = parameters.fulfillment || {method: "other", name: "unspecified"};
const { goalSet, goal, goalSetId, state, id, providerId, url } = parameters;
const fulfillment = parameters.fulfillment || { method: SdmGoalFulfillmentMethod.Other, name: "unknown" };

if (!id.branch) {
throw new Error(sprintf("Please provide a branch in the RemoteRepoRef %j", parameters));
Expand Down
10 changes: 3 additions & 7 deletions src/api-helper/listener/executeAutoInspects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,26 +34,22 @@ import {
ReviewerError,
} from "../../api/registration/ReviewerError";
import { ReviewListenerRegistration } from "../../api/registration/ReviewListenerRegistration";
import { ProjectLoader } from "../../spi/project/ProjectLoader";
import { createPushImpactListenerInvocation } from "./createPushImpactListenerInvocation";
import { relevantCodeActions } from "./relevantCodeActions";

/**
* Execute auto inspections and route or react to review results using review listeners
* @param {ProjectLoader} projectLoader
* @param autoInspectRegistrations
* @param {ReviewListener[]} reviewListeners
* @return {ExecuteGoal}
*/
export function executeAutoInspects(projectLoader: ProjectLoader,
autoInspectRegistrations: Array<AutoInspectRegistration<any, any>>,
export function executeAutoInspects(autoInspectRegistrations: Array<AutoInspectRegistration<any, any>>,
reviewListeners: ReviewListenerRegistration[]): ExecuteGoal {
return async (goalInvocation: GoalInvocation) => {
const { credentials, id, addressChannels } = goalInvocation;
const { configuration, credentials, id, addressChannels } = goalInvocation;
try {
if (autoInspectRegistrations.length > 0) {
logger.info("Planning inspection of %j with %d AutoInspects", id, autoInspectRegistrations.length);
return projectLoader.doWithProject({ credentials, id, readOnly: true }, async project => {
return configuration.sdm.projectLoader.doWithProject({ credentials, id, readOnly: true }, async project => {
const cri = await createPushImpactListenerInvocation(goalInvocation, project);
const relevantAutoInspects = await relevantCodeActions(autoInspectRegistrations, cri);
logger.info("Executing review of %j with %d relevant AutoInspects: [%s] of [%s]",
Expand Down
16 changes: 5 additions & 11 deletions src/api-helper/listener/executeAutofixes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ import {
import { PushImpactListenerInvocation } from "../../api/listener/PushImpactListener";
import { AutofixRegistration } from "../../api/registration/AutofixRegistration";
import { ProgressLog } from "../../spi/log/ProgressLog";
import { ProjectLoader } from "../../spi/project/ProjectLoader";
import { RepoRefResolver } from "../../spi/repo-ref/RepoRefResolver";
import { confirmEditedness } from "../command/transform/confirmEditedness";
import { toScalarProjectEditor } from "../machine/handlerRegistrations";
import { createPushImpactListenerInvocation } from "./createPushImpactListenerInvocation";
Expand All @@ -41,24 +39,20 @@ import { relevantCodeActions } from "./relevantCodeActions";
/**
* Execute autofixes against this push
* Throw an error on failure
* @param projectLoader use to load projects
* @param {AutofixRegistration[]} registrations
* @param repoRefResolver RepoRefResolver
* @return GoalExecutor
* @return ExecuteGoal
*/
export function executeAutofixes(projectLoader: ProjectLoader,
registrations: AutofixRegistration[],
repoRefResolver: RepoRefResolver): ExecuteGoal {
export function executeAutofixes(registrations: AutofixRegistration[]): ExecuteGoal {
return async (goalInvocation: GoalInvocation): Promise<ExecuteGoalResult> => {
const { sdmGoal, credentials, context, progressLog } = goalInvocation;
const { configuration, sdmGoal, credentials, context, progressLog } = goalInvocation;
progressLog.write(sprintf("Executing %d autofixes", registrations.length));
try {
if (registrations.length === 0) {
return Success;
}
const push = sdmGoal.push;
const editableRepoRef = repoRefResolver.toRemoteRepoRef(sdmGoal.push.repo, { branch: push.branch });
const editResult = await projectLoader.doWithProject<EditResult>({
const editableRepoRef = configuration.sdm.repoRefResolver.toRemoteRepoRef(sdmGoal.push.repo, { branch: push.branch });
const editResult = await configuration.sdm.projectLoader.doWithProject<EditResult>({
credentials,
id: editableRepoRef,
context,
Expand Down
63 changes: 63 additions & 0 deletions src/api-helper/listener/executeFingerprinting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright © 2018 Atomist, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {
logger,
Success,
} from "@atomist/automation-client";
import { Fingerprint } from "@atomist/automation-client/project/fingerprint/Fingerprint";
import {
ExecuteGoal,
GoalInvocation,
} from "../../api/goal/GoalInvocation";
import { FingerprintListener } from "../../api/listener/FingerprintListener";
import { FingerprinterRegistration } from "../../api/registration/FingerprinterRegistration";
import { computeFingerprints } from "./computeFingerprints";
import { createPushImpactListenerInvocation } from "./createPushImpactListenerInvocation";
import { relevantCodeActions } from "./relevantCodeActions";

/**
* Execute fingerprinting and send fingerprints to Atomist
* @param {FingerprinterRegistration} fingerprinters
* @param listeners listeners to fingerprints
*/
export function executeFingerprinting(fingerprinters: FingerprinterRegistration[],
listeners: FingerprintListener[]): ExecuteGoal {
return async (goalInvocation: GoalInvocation) => {
const { configuration, id, credentials, context } = goalInvocation;
if (fingerprinters.length === 0) {
return Success;
}

logger.debug("About to fingerprint %j using %d fingerprinters", id, fingerprinters.length);
await configuration.sdm.projectLoader.doWithProject({ credentials, id, readOnly: true }, async project => {
const cri = await createPushImpactListenerInvocation(goalInvocation, project);
const relevantFingerprinters: FingerprinterRegistration[] = await relevantCodeActions(fingerprinters, cri);
logger.info("Will invoke %d eligible fingerprinters of %d to %j",
relevantFingerprinters.length, fingerprinters.length, cri.project.id);
const fingerprints: Fingerprint[] = await computeFingerprints(cri, relevantFingerprinters.map(fp => fp.action));
await Promise.all(listeners.map(l =>
Promise.all(fingerprints.map(fingerprint => l({
id,
context,
credentials,
addressChannels: cri.addressChannels,
fingerprint,
})))));
});
return Success;
};
}
7 changes: 3 additions & 4 deletions src/api-helper/listener/executePushReactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,14 @@ import { relevantCodeActions } from "./relevantCodeActions";
* @param {PushImpactListenerRegistration[]} registrations
* @return {ExecuteGoal}
*/
export function executePushReactions(projectLoader: ProjectLoader,
registrations: PushImpactListenerRegisterable[]): ExecuteGoal {
export function executePushReactions(registrations: PushImpactListenerRegisterable[]): ExecuteGoal {
return async (goalInvocation: GoalInvocation) => {
if (registrations.length === 0) {
return Success;
}

const {credentials, id, context} = goalInvocation;
return projectLoader.doWithProject({credentials, id, context, readOnly: true}, async project => {
const { configuration, credentials, id, context } = goalInvocation;
return configuration.sdm.projectLoader.doWithProject({ credentials, id, context, readOnly: true }, async project => {
const cri: PushImpactListenerInvocation = await createPushImpactListenerInvocation(goalInvocation, project);
const regs = registrations.map(toPushReactionRegistration);
const relevantCodeReactions: PushImpactListenerRegistration[] = await relevantCodeActions<PushImpactListenerRegistration>(regs, cri);
Expand Down
Loading

0 comments on commit 707bb8f

Please sign in to comment.