@pipedream/redmine 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/actions/create-issue/create-issue.mjs +74 -0
- package/actions/delete-user/delete-user.mjs +45 -0
- package/actions/list-priority-id-options/list-priority-id-options.mjs +24 -0
- package/actions/list-status-id-options/list-status-id-options.mjs +24 -0
- package/actions/list-tracker-id-options/list-tracker-id-options.mjs +24 -0
- package/actions/update-project/update-project.mjs +80 -0
- package/common/constants.mjs +15 -0
- package/common/utils.mjs +43 -0
- package/package.json +1 -1
- package/redmine.app.mjs +220 -5
- package/sources/common/base.mjs +14 -0
- package/sources/common/polling.mjs +58 -0
- package/sources/issue-created/issue-created.mjs +34 -0
- package/sources/project-updated/project-updated.mjs +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Overview
|
|
2
|
+
|
|
3
|
+
The Redmine API allows for integration with the Redmine project management tool, enabling the automation of tasks like issue tracking, project management, and time tracking. Using Pipedream, you can create workflows that trigger on specific Redmine events or perform actions in Redmine based on triggers from other apps. By harnessing the Redmine API on Pipedream, you can connect Redmine with a multitude of other apps and services to streamline your project management processes.
|
|
4
|
+
|
|
5
|
+
# Example Workflows
|
|
6
|
+
|
|
7
|
+
- **Sync Redmine Issues to Google Sheets**: Automatically export newly created or updated Redmine issues to a Google Sheets spreadsheet for advanced reporting or data analysis. This workflow can be set up to run periodically, ensuring your Sheets are always up-to-date with the latest issue data.
|
|
8
|
+
|
|
9
|
+
- **Slack Notifications for Redmine Updates**: Set up a workflow where updates to issues or projects in Redmine trigger notifications in a designated Slack channel. This keeps your team informed in real-time about important changes, like status updates or newly assigned tasks.
|
|
10
|
+
|
|
11
|
+
- **GitHub Commit Linked to Redmine Issues**: Whenever a new commit is pushed to a GitHub repository, search for Redmine issue IDs in the commit message and update the corresponding issue with the commit details. This helps maintain a clear link between code changes and project tasks.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import app from "../../redmine.app.mjs";
|
|
2
|
+
import utils from "../../common/utils.mjs";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
key: "redmine-create-issue",
|
|
6
|
+
name: "Create Issue",
|
|
7
|
+
description: "Creates a new issue in Redmine. [See the documentation](https://www.redmine.org/projects/redmine/wiki/rest_issues#creating-an-issue)",
|
|
8
|
+
version: "0.0.2",
|
|
9
|
+
annotations: {
|
|
10
|
+
destructiveHint: false,
|
|
11
|
+
openWorldHint: true,
|
|
12
|
+
readOnlyHint: false,
|
|
13
|
+
},
|
|
14
|
+
type: "action",
|
|
15
|
+
props: {
|
|
16
|
+
app,
|
|
17
|
+
projectId: {
|
|
18
|
+
propDefinition: [
|
|
19
|
+
app,
|
|
20
|
+
"projectId",
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
trackerId: {
|
|
24
|
+
propDefinition: [
|
|
25
|
+
app,
|
|
26
|
+
"trackerId",
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
subject: {
|
|
30
|
+
type: "string",
|
|
31
|
+
label: "Subject",
|
|
32
|
+
description: "The subject of the issue",
|
|
33
|
+
},
|
|
34
|
+
description: {
|
|
35
|
+
type: "string",
|
|
36
|
+
label: "Description",
|
|
37
|
+
description: "The description of the issue",
|
|
38
|
+
},
|
|
39
|
+
statusId: {
|
|
40
|
+
propDefinition: [
|
|
41
|
+
app,
|
|
42
|
+
"statusId",
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
priorityId: {
|
|
46
|
+
propDefinition: [
|
|
47
|
+
app,
|
|
48
|
+
"priorityId",
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
methods: {
|
|
53
|
+
createIssue(args = {}) {
|
|
54
|
+
return this.app.post({
|
|
55
|
+
path: "/issues.json",
|
|
56
|
+
...args,
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
run({ $: step }) {
|
|
61
|
+
const {
|
|
62
|
+
createIssue,
|
|
63
|
+
...issue
|
|
64
|
+
} = this;
|
|
65
|
+
|
|
66
|
+
return createIssue({
|
|
67
|
+
step,
|
|
68
|
+
data: {
|
|
69
|
+
issue: utils.transformProps(issue),
|
|
70
|
+
},
|
|
71
|
+
summary: (response) => `Successfully created issue with ID: \`${response.issue?.id}\``,
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import app from "../../redmine.app.mjs";
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
key: "redmine-delete-user",
|
|
5
|
+
name: "Delete User",
|
|
6
|
+
description: "Deletes a user from the Redmine platform. [See the documentation](https://www.redmine.org/projects/redmine/wiki/rest_users#delete)",
|
|
7
|
+
version: "0.0.2",
|
|
8
|
+
annotations: {
|
|
9
|
+
destructiveHint: true,
|
|
10
|
+
openWorldHint: true,
|
|
11
|
+
readOnlyHint: false,
|
|
12
|
+
},
|
|
13
|
+
type: "action",
|
|
14
|
+
props: {
|
|
15
|
+
app,
|
|
16
|
+
userId: {
|
|
17
|
+
propDefinition: [
|
|
18
|
+
app,
|
|
19
|
+
"userId",
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
methods: {
|
|
24
|
+
deleteUser({
|
|
25
|
+
userId, ...args
|
|
26
|
+
} = {}) {
|
|
27
|
+
return this.app.delete({
|
|
28
|
+
path: `/users/${userId}.json`,
|
|
29
|
+
...args,
|
|
30
|
+
});
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
run({ $: step }) {
|
|
34
|
+
const {
|
|
35
|
+
deleteUser,
|
|
36
|
+
userId,
|
|
37
|
+
} = this;
|
|
38
|
+
|
|
39
|
+
return deleteUser({
|
|
40
|
+
step,
|
|
41
|
+
userId,
|
|
42
|
+
summary: () => "Successfully deleted user",
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import redmine from "../../redmine.app.mjs";
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
key: "redmine-list-priority-id-options",
|
|
5
|
+
name: "List Priority ID Options",
|
|
6
|
+
description: "Retrieves available options for the Priority ID field.",
|
|
7
|
+
version: "0.0.1",
|
|
8
|
+
type: "action",
|
|
9
|
+
annotations: {
|
|
10
|
+
destructiveHint: false,
|
|
11
|
+
openWorldHint: true,
|
|
12
|
+
readOnlyHint: true,
|
|
13
|
+
},
|
|
14
|
+
props: {
|
|
15
|
+
redmine,
|
|
16
|
+
},
|
|
17
|
+
async run({ $ }) {
|
|
18
|
+
const options = await redmine.propDefinitions.priorityId.options.call(this.redmine);
|
|
19
|
+
$.export("$summary", `Successfully retrieved ${options.length} option${options.length === 1
|
|
20
|
+
? ""
|
|
21
|
+
: "s"}`);
|
|
22
|
+
return options;
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import redmine from "../../redmine.app.mjs";
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
key: "redmine-list-status-id-options",
|
|
5
|
+
name: "List Status ID Options",
|
|
6
|
+
description: "Retrieves available options for the Status ID field.",
|
|
7
|
+
version: "0.0.1",
|
|
8
|
+
type: "action",
|
|
9
|
+
annotations: {
|
|
10
|
+
destructiveHint: false,
|
|
11
|
+
openWorldHint: true,
|
|
12
|
+
readOnlyHint: true,
|
|
13
|
+
},
|
|
14
|
+
props: {
|
|
15
|
+
redmine,
|
|
16
|
+
},
|
|
17
|
+
async run({ $ }) {
|
|
18
|
+
const options = await redmine.propDefinitions.statusId.options.call(this.redmine);
|
|
19
|
+
$.export("$summary", `Successfully retrieved ${options.length} option${options.length === 1
|
|
20
|
+
? ""
|
|
21
|
+
: "s"}`);
|
|
22
|
+
return options;
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import redmine from "../../redmine.app.mjs";
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
key: "redmine-list-tracker-id-options",
|
|
5
|
+
name: "List Tracker ID Options",
|
|
6
|
+
description: "Retrieves available options for the Tracker ID field.",
|
|
7
|
+
version: "0.0.1",
|
|
8
|
+
type: "action",
|
|
9
|
+
annotations: {
|
|
10
|
+
destructiveHint: false,
|
|
11
|
+
openWorldHint: true,
|
|
12
|
+
readOnlyHint: true,
|
|
13
|
+
},
|
|
14
|
+
props: {
|
|
15
|
+
redmine,
|
|
16
|
+
},
|
|
17
|
+
async run({ $ }) {
|
|
18
|
+
const options = await redmine.propDefinitions.trackerId.options.call(this.redmine);
|
|
19
|
+
$.export("$summary", `Successfully retrieved ${options.length} option${options.length === 1
|
|
20
|
+
? ""
|
|
21
|
+
: "s"}`);
|
|
22
|
+
return options;
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import app from "../../redmine.app.mjs";
|
|
2
|
+
import utils from "../../common/utils.mjs";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
key: "redmine-update-project",
|
|
6
|
+
name: "Update Project",
|
|
7
|
+
description: "Updates an existing project in Redmine. [See the documentation](https://www.redmine.org/projects/redmine/wiki/rest_projects#updating-a-project)",
|
|
8
|
+
version: "0.0.2",
|
|
9
|
+
annotations: {
|
|
10
|
+
destructiveHint: true,
|
|
11
|
+
openWorldHint: true,
|
|
12
|
+
readOnlyHint: false,
|
|
13
|
+
},
|
|
14
|
+
type: "action",
|
|
15
|
+
props: {
|
|
16
|
+
app,
|
|
17
|
+
projectId: {
|
|
18
|
+
propDefinition: [
|
|
19
|
+
app,
|
|
20
|
+
"projectId",
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
name: {
|
|
24
|
+
type: "string",
|
|
25
|
+
label: "Name",
|
|
26
|
+
description: "The name of the project",
|
|
27
|
+
optional: true,
|
|
28
|
+
},
|
|
29
|
+
description: {
|
|
30
|
+
type: "string",
|
|
31
|
+
label: "Description",
|
|
32
|
+
description: "The description of the project",
|
|
33
|
+
optional: true,
|
|
34
|
+
},
|
|
35
|
+
homepage: {
|
|
36
|
+
type: "string",
|
|
37
|
+
label: "Homepage",
|
|
38
|
+
description: "The homepage of the project",
|
|
39
|
+
optional: true,
|
|
40
|
+
},
|
|
41
|
+
isPublic: {
|
|
42
|
+
type: "boolean",
|
|
43
|
+
label: "Is Public",
|
|
44
|
+
description: "Whether the project is public",
|
|
45
|
+
optional: true,
|
|
46
|
+
},
|
|
47
|
+
inheritMembers: {
|
|
48
|
+
type: "boolean",
|
|
49
|
+
label: "Inherit Members",
|
|
50
|
+
description: "Whether the project should inherit members from its parent",
|
|
51
|
+
optional: true,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
methods: {
|
|
55
|
+
updateProject({
|
|
56
|
+
projectId, ...args
|
|
57
|
+
} = {}) {
|
|
58
|
+
return this.app.put({
|
|
59
|
+
path: `/projects/${projectId}.json`,
|
|
60
|
+
...args,
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
run({ $: step }) {
|
|
65
|
+
const {
|
|
66
|
+
updateProject,
|
|
67
|
+
projectId,
|
|
68
|
+
...project
|
|
69
|
+
} = this;
|
|
70
|
+
|
|
71
|
+
return updateProject({
|
|
72
|
+
step,
|
|
73
|
+
projectId,
|
|
74
|
+
data: {
|
|
75
|
+
project: utils.transformProps(project),
|
|
76
|
+
},
|
|
77
|
+
summary: () => "Successfully updated project",
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const SUMMARY_LABEL = "$summary";
|
|
2
|
+
const DOMAIN_PLACEHOLDER = "{domain}";
|
|
3
|
+
const BASE_URL = `https://${DOMAIN_PLACEHOLDER}`;
|
|
4
|
+
const LAST_CREATED_AT = "lastCreatedAt";
|
|
5
|
+
const DEFAULT_MAX = 600;
|
|
6
|
+
const DEFAULT_LIMIT = 60;
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
SUMMARY_LABEL,
|
|
10
|
+
DOMAIN_PLACEHOLDER,
|
|
11
|
+
BASE_URL,
|
|
12
|
+
DEFAULT_MAX,
|
|
13
|
+
DEFAULT_LIMIT,
|
|
14
|
+
LAST_CREATED_AT,
|
|
15
|
+
};
|
package/common/utils.mjs
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
function toSnakeCase(str) {
|
|
2
|
+
return str?.replace(/([A-Z])/g, "_$1").toLowerCase();
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function keysToSnakeCase(data = {}) {
|
|
6
|
+
return Object.entries(data)
|
|
7
|
+
.reduce((acc, [
|
|
8
|
+
key,
|
|
9
|
+
value,
|
|
10
|
+
]) => ({
|
|
11
|
+
...acc,
|
|
12
|
+
[toSnakeCase(key)]: value,
|
|
13
|
+
}), {});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function transformProps(props) {
|
|
17
|
+
if (!props) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return keysToSnakeCase(
|
|
22
|
+
Object.fromEntries(
|
|
23
|
+
Object.entries(props)
|
|
24
|
+
.filter(([
|
|
25
|
+
key,
|
|
26
|
+
value,
|
|
27
|
+
]) => typeof(value) !== "function" && key !== "app"),
|
|
28
|
+
),
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function iterate(iterations) {
|
|
33
|
+
const items = [];
|
|
34
|
+
for await (const item of iterations) {
|
|
35
|
+
items.push(item);
|
|
36
|
+
}
|
|
37
|
+
return items;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default {
|
|
41
|
+
transformProps,
|
|
42
|
+
iterate,
|
|
43
|
+
};
|
package/package.json
CHANGED
package/redmine.app.mjs
CHANGED
|
@@ -1,11 +1,226 @@
|
|
|
1
|
+
import {
|
|
2
|
+
axios, ConfigurationError,
|
|
3
|
+
} from "@pipedream/platform";
|
|
4
|
+
import constants from "./common/constants.mjs";
|
|
5
|
+
import utils from "./common/utils.mjs";
|
|
6
|
+
|
|
1
7
|
export default {
|
|
2
8
|
type: "app",
|
|
3
9
|
app: "redmine",
|
|
4
|
-
propDefinitions: {
|
|
10
|
+
propDefinitions: {
|
|
11
|
+
projectId: {
|
|
12
|
+
type: "integer",
|
|
13
|
+
label: "Project ID",
|
|
14
|
+
description: "The ID of the project",
|
|
15
|
+
async options() {
|
|
16
|
+
const response = await this.listProjects();
|
|
17
|
+
console.log(response);
|
|
18
|
+
const { projects } = response;
|
|
19
|
+
return projects.map(({
|
|
20
|
+
id: value, name: label,
|
|
21
|
+
}) => ({
|
|
22
|
+
label,
|
|
23
|
+
value,
|
|
24
|
+
}));
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
trackerId: {
|
|
28
|
+
type: "integer",
|
|
29
|
+
label: "Tracker ID",
|
|
30
|
+
description: "The ID of the tracker",
|
|
31
|
+
async options() {
|
|
32
|
+
const { trackers } = await this.listTrackers();
|
|
33
|
+
return trackers.map(({
|
|
34
|
+
id: value, name: label,
|
|
35
|
+
}) => ({
|
|
36
|
+
label,
|
|
37
|
+
value,
|
|
38
|
+
}));
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
statusId: {
|
|
42
|
+
type: "integer",
|
|
43
|
+
label: "Status ID",
|
|
44
|
+
description: "The ID of the status",
|
|
45
|
+
async options() {
|
|
46
|
+
const { issue_statuses: statuses } = await this.listIssueStatuses();
|
|
47
|
+
return statuses.map(({
|
|
48
|
+
id: value, name: label,
|
|
49
|
+
}) => ({
|
|
50
|
+
label,
|
|
51
|
+
value,
|
|
52
|
+
}));
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
priorityId: {
|
|
56
|
+
type: "integer",
|
|
57
|
+
label: "Priority ID",
|
|
58
|
+
description: "The ID of the priority",
|
|
59
|
+
async options() {
|
|
60
|
+
const { issue_priorities: priorities } = await this.listIssuePriorities();
|
|
61
|
+
return priorities.map(({
|
|
62
|
+
id: value, name: label,
|
|
63
|
+
}) => ({
|
|
64
|
+
label,
|
|
65
|
+
value,
|
|
66
|
+
}));
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
userId: {
|
|
70
|
+
type: "integer",
|
|
71
|
+
label: "User ID",
|
|
72
|
+
description: "The ID of the user",
|
|
73
|
+
async options() {
|
|
74
|
+
const { users } = await this.listUsers();
|
|
75
|
+
return users.map(({
|
|
76
|
+
id: value, login: label,
|
|
77
|
+
}) => ({
|
|
78
|
+
label,
|
|
79
|
+
value,
|
|
80
|
+
}));
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
5
84
|
methods: {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
85
|
+
exportSummary(step) {
|
|
86
|
+
if (!step?.export) {
|
|
87
|
+
throw new ConfigurationError("The summary method should be bind to the step object aka `$`");
|
|
88
|
+
}
|
|
89
|
+
return (msg = "") => step.export(constants.SUMMARY_LABEL, msg);
|
|
90
|
+
},
|
|
91
|
+
getUrl(path) {
|
|
92
|
+
const baseUrl = constants.BASE_URL
|
|
93
|
+
.replace(constants.DOMAIN_PLACEHOLDER, this.$auth.hostname);
|
|
94
|
+
return `${baseUrl}${path}`;
|
|
95
|
+
},
|
|
96
|
+
async makeRequest({
|
|
97
|
+
step = this, path, headers, summary, ...args
|
|
98
|
+
} = {}) {
|
|
99
|
+
const {
|
|
100
|
+
getUrl,
|
|
101
|
+
exportSummary,
|
|
102
|
+
$auth: { api_key: apiKey },
|
|
103
|
+
} = this;
|
|
104
|
+
|
|
105
|
+
const config = {
|
|
106
|
+
...args,
|
|
107
|
+
url: getUrl(path),
|
|
108
|
+
headers: {
|
|
109
|
+
...headers,
|
|
110
|
+
"X-Redmine-API-Key": apiKey,
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const response = await axios(step, config);
|
|
115
|
+
|
|
116
|
+
if (typeof(summary) === "function") {
|
|
117
|
+
exportSummary(step)(summary(response));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return response || {
|
|
121
|
+
success: true,
|
|
122
|
+
};
|
|
123
|
+
},
|
|
124
|
+
post(args = {}) {
|
|
125
|
+
return this.makeRequest({
|
|
126
|
+
method: "post",
|
|
127
|
+
...args,
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
put(args = {}) {
|
|
131
|
+
return this.makeRequest({
|
|
132
|
+
method: "put",
|
|
133
|
+
...args,
|
|
134
|
+
});
|
|
135
|
+
},
|
|
136
|
+
delete(args = {}) {
|
|
137
|
+
return this.makeRequest({
|
|
138
|
+
method: "delete",
|
|
139
|
+
...args,
|
|
140
|
+
});
|
|
141
|
+
},
|
|
142
|
+
listIssues(args = {}) {
|
|
143
|
+
return this.makeRequest({
|
|
144
|
+
path: "/issues.json",
|
|
145
|
+
...args,
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
listProjects(args = {}) {
|
|
149
|
+
return this.makeRequest({
|
|
150
|
+
path: "/projects.json",
|
|
151
|
+
...args,
|
|
152
|
+
});
|
|
153
|
+
},
|
|
154
|
+
listTrackers(args = {}) {
|
|
155
|
+
return this.makeRequest({
|
|
156
|
+
path: "/trackers.json",
|
|
157
|
+
...args,
|
|
158
|
+
});
|
|
159
|
+
},
|
|
160
|
+
listIssueStatuses(args = {}) {
|
|
161
|
+
return this.makeRequest({
|
|
162
|
+
path: "/issue_statuses.json",
|
|
163
|
+
...args,
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
listIssuePriorities(args = {}) {
|
|
167
|
+
return this.makeRequest({
|
|
168
|
+
path: "/enumerations/issue_priorities.json",
|
|
169
|
+
...args,
|
|
170
|
+
});
|
|
171
|
+
},
|
|
172
|
+
listUsers(args = {}) {
|
|
173
|
+
return this.makeRequest({
|
|
174
|
+
path: "/users.json",
|
|
175
|
+
...args,
|
|
176
|
+
});
|
|
177
|
+
},
|
|
178
|
+
async *getIterations({
|
|
179
|
+
resourceFn,
|
|
180
|
+
resourceFnArgs,
|
|
181
|
+
resourceName,
|
|
182
|
+
max = constants.DEFAULT_MAX,
|
|
183
|
+
}) {
|
|
184
|
+
let offset = 0;
|
|
185
|
+
let resourcesCount = 0;
|
|
186
|
+
|
|
187
|
+
while (true) {
|
|
188
|
+
const response =
|
|
189
|
+
await resourceFn({
|
|
190
|
+
...resourceFnArgs,
|
|
191
|
+
params: {
|
|
192
|
+
offset,
|
|
193
|
+
limit: constants.DEFAULT_LIMIT,
|
|
194
|
+
...resourceFnArgs?.params,
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const nextResources = resourceName && response[resourceName] || response;
|
|
199
|
+
|
|
200
|
+
if (!nextResources?.length) {
|
|
201
|
+
console.log("No more resources found");
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
for (const resource of nextResources) {
|
|
206
|
+
yield resource;
|
|
207
|
+
resourcesCount += 1;
|
|
208
|
+
|
|
209
|
+
if (resourcesCount >= max) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (nextResources.length < constants.DEFAULT_LIMIT) {
|
|
215
|
+
console.log("Less resources than the limit found, no more resources to fetch");
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
offset += constants.DEFAULT_LIMIT;
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
paginate(args = {}) {
|
|
223
|
+
return utils.iterate(this.getIterations(args));
|
|
9
224
|
},
|
|
10
225
|
},
|
|
11
|
-
};
|
|
226
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ConfigurationError } from "@pipedream/platform";
|
|
2
|
+
import app from "../../redmine.app.mjs";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
props: {
|
|
6
|
+
app,
|
|
7
|
+
db: "$.service.db",
|
|
8
|
+
},
|
|
9
|
+
methods: {
|
|
10
|
+
generateMeta() {
|
|
11
|
+
throw new ConfigurationError("generateMeta is not implemented");
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ConfigurationError,
|
|
3
|
+
DEFAULT_POLLING_SOURCE_TIMER_INTERVAL,
|
|
4
|
+
} from "@pipedream/platform";
|
|
5
|
+
import common from "./base.mjs";
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
...common,
|
|
9
|
+
props: {
|
|
10
|
+
...common.props,
|
|
11
|
+
timer: {
|
|
12
|
+
type: "$.interface.timer",
|
|
13
|
+
label: "Polling schedule",
|
|
14
|
+
description: "How often to poll the API",
|
|
15
|
+
default: {
|
|
16
|
+
intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL,
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
methods: {
|
|
21
|
+
...common.methods,
|
|
22
|
+
getResourceName() {
|
|
23
|
+
throw new ConfigurationError("getResourceName is not implemented");
|
|
24
|
+
},
|
|
25
|
+
getResourceFn() {
|
|
26
|
+
throw new ConfigurationError("getResourceFn is not implemented");
|
|
27
|
+
},
|
|
28
|
+
getResourceFnArgs() {
|
|
29
|
+
throw new ConfigurationError("getResourceFnArgs is not implemented");
|
|
30
|
+
},
|
|
31
|
+
processEvent(resource) {
|
|
32
|
+
const meta = this.generateMeta(resource);
|
|
33
|
+
this.$emit(resource, meta);
|
|
34
|
+
},
|
|
35
|
+
async processResources(resources) {
|
|
36
|
+
Array.from(resources)
|
|
37
|
+
.reverse()
|
|
38
|
+
.forEach(this.processEvent);
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
async run() {
|
|
42
|
+
const {
|
|
43
|
+
app,
|
|
44
|
+
getResourceFn,
|
|
45
|
+
getResourceFnArgs,
|
|
46
|
+
getResourceName,
|
|
47
|
+
processResources,
|
|
48
|
+
} = this;
|
|
49
|
+
|
|
50
|
+
const resources = await app.paginate({
|
|
51
|
+
resourceFn: getResourceFn(),
|
|
52
|
+
resourceFnArgs: getResourceFnArgs(),
|
|
53
|
+
resourceName: getResourceName(),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
processResources(resources);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import common from "../common/polling.mjs";
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
...common,
|
|
5
|
+
key: "redmine-issue-created",
|
|
6
|
+
name: "New Issue Created",
|
|
7
|
+
description: "Emit new event when a new issue is created in Redmine",
|
|
8
|
+
version: "0.0.1",
|
|
9
|
+
type: "source",
|
|
10
|
+
dedupe: "unique",
|
|
11
|
+
methods: {
|
|
12
|
+
...common.methods,
|
|
13
|
+
getResourceName() {
|
|
14
|
+
return "issues";
|
|
15
|
+
},
|
|
16
|
+
getResourceFn() {
|
|
17
|
+
return this.app.listIssues;
|
|
18
|
+
},
|
|
19
|
+
getResourceFnArgs() {
|
|
20
|
+
return {
|
|
21
|
+
params: {
|
|
22
|
+
sort: "created_on:desc",
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
generateMeta(resource) {
|
|
27
|
+
return {
|
|
28
|
+
id: resource.id,
|
|
29
|
+
summary: `New Issue: ${resource.subject}`,
|
|
30
|
+
ts: Date.parse(resource.created_on),
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import common from "../common/polling.mjs";
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
...common,
|
|
5
|
+
key: "redmine-project-updated",
|
|
6
|
+
name: "Project Updated",
|
|
7
|
+
description: "Emits an event whenever a project is updated in Redmine",
|
|
8
|
+
version: "0.0.1",
|
|
9
|
+
type: "source",
|
|
10
|
+
dedupe: "unique",
|
|
11
|
+
methods: {
|
|
12
|
+
...common.methods,
|
|
13
|
+
getResourceName() {
|
|
14
|
+
return "projects";
|
|
15
|
+
},
|
|
16
|
+
getResourceFn() {
|
|
17
|
+
return this.app.listProjects;
|
|
18
|
+
},
|
|
19
|
+
getResourceFnArgs() {
|
|
20
|
+
return {
|
|
21
|
+
params: {
|
|
22
|
+
sort: "updated_on:desc",
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
generateMeta(resource) {
|
|
27
|
+
const ts = Date.parse(resource.updated_on);
|
|
28
|
+
return {
|
|
29
|
+
id: `${resource.id}-${ts}`,
|
|
30
|
+
summary: `Project Updated: ${resource.name}`,
|
|
31
|
+
ts,
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|