mirror of
https://github.com/alamion/obsidian-jira-sync.git
synced 2026-07-22 05:43:04 +00:00
Tested and fixed issues from last update
This commit is contained in:
parent
67308c0fec
commit
108fdc502a
18 changed files with 70 additions and 40 deletions
|
|
@ -79,4 +79,6 @@ The auth middleware needs to...
|
|||
`jira-sync-line-openLink` [Open in Jira](http://jira/browse/DEV-42)
|
||||
```
|
||||
|
||||
|
||||
|
||||
Docs with more detailed info and examples: [GitHub](https://github.com/Alamion/obsidian-jira-sync/tree/master/docs)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "jira-sync",
|
||||
"name": "Jira Issue Manager",
|
||||
"version": "1.1.9",
|
||||
"version": "1.2.0",
|
||||
"minAppVersion": "1.7.7",
|
||||
"description": "Get Jira issues, create and update them. Issue status and worklog management.",
|
||||
"author": "Alamion",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "obsidian-sample-plugin",
|
||||
"version": "1.1.9",
|
||||
"version": "1.2.0",
|
||||
"description": "This is a sample plugin for Obsidian (https://obsidian.md)",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ export async function baseRequest(
|
|||
throw: false,
|
||||
body
|
||||
}
|
||||
debugLog(requestParams)
|
||||
debugLog("Request:\n", requestParams)
|
||||
const response = await requestUrl(requestParams);
|
||||
debugLog(response);
|
||||
debugLog("Response:\n", response);
|
||||
if (response.status < 200 || response.status >= 300) {
|
||||
if (response.status === 401 && retries > 0) {
|
||||
await authenticate(plugin);
|
||||
|
|
@ -31,7 +31,7 @@ export async function baseRequest(
|
|||
${response.text || "Unknown error"}
|
||||
${body && '\nbody:' + body || ""}`);
|
||||
}
|
||||
debugLog({"url": `${plugin.settings.jiraUrl}/rest/api/2${additional_url_path}`,
|
||||
debugLog("Additional request info:\n", {"url": `${plugin.settings.jiraUrl}/rest/api/2${additional_url_path}`,
|
||||
"method": method, "body": body});
|
||||
return response.status === 204 ? null : response.json;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
import JiraPlugin from "../main";
|
||||
import {baseRequest} from "./base";
|
||||
import {JiraIssueType, JiraProject} from "../interfaces";
|
||||
|
||||
export async function fetchProjects(plugin: JiraPlugin): Promise<void> {
|
||||
export async function fetchProjects(plugin: JiraPlugin): Promise<JiraProject[]> {
|
||||
const result = await baseRequest(plugin, 'get', '/project');
|
||||
plugin.temp_vars.projects = result.map((project: any) => ({
|
||||
return result.map((project: any) => ({
|
||||
id: project.key,
|
||||
name: project.name,
|
||||
}));
|
||||
})) as JiraProject[];
|
||||
}
|
||||
|
||||
export async function fetchIssueTypes(plugin: JiraPlugin, projectKey: string): Promise<void> {
|
||||
export async function fetchIssueTypes(plugin: JiraPlugin, projectKey: string): Promise<JiraIssueType[]> {
|
||||
const result = await baseRequest(plugin, 'get', `/issue/createmeta/${projectKey}/issuetypes`);
|
||||
plugin.temp_vars.issueTypes = result.map((type: any) => ({
|
||||
return result.values.map((type: any) => ({
|
||||
name: type.name,
|
||||
}));
|
||||
})) as JiraIssueType[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,10 +33,13 @@ async function processFrontmatterWorkLogs(plugin: JiraPlugin, _: TFile, jira_wor
|
|||
throw new TypeError(`jira_worklog_batch has an invalid type: ${typeof jira_worklog_batch}`);
|
||||
}
|
||||
} catch (error) {
|
||||
new Notice("Failed to parse jira_worklog_batch");
|
||||
console.error("Failed to parse jira_worklog_batch:", error);
|
||||
}
|
||||
|
||||
if (!foundData || workLogData.length === 0) {
|
||||
new Notice("No work log data to process");
|
||||
console.warn("No work log data to process");
|
||||
return;
|
||||
}
|
||||
await processWorkLogBatch(plugin, workLogData);
|
||||
|
|
@ -109,10 +112,17 @@ function addFailure(results: { failures: { reason: string; issueKeys: string[] }
|
|||
|
||||
function convertToStandardDate(dateString: string): string | null {
|
||||
try {
|
||||
const [datePart, timePart] = dateString.split(' ');
|
||||
const [day, month, year] = datePart.split('-');
|
||||
const [hours, minutes] = timePart.split(':');
|
||||
return `${year}-${month}-${day}T${hours}:${minutes}:00.000+0000`;
|
||||
// Try to parse the date string directly
|
||||
const date = new Date(dateString);
|
||||
|
||||
if (isNaN(date.getTime())) {
|
||||
throw new Error("Invalid date format");
|
||||
}
|
||||
|
||||
// Format to ISO string and adjust to desired format
|
||||
const isoString = date.toISOString();
|
||||
return isoString.replace('Z', '+0000');
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error converting date:", error);
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -28,15 +28,15 @@ export async function createIssue(plugin: JiraPlugin, file: TFile): Promise<void
|
|||
try {
|
||||
|
||||
// Fetch projects to show in selection modal
|
||||
await fetchProjects(plugin);
|
||||
const projects = await fetchProjects(plugin);
|
||||
|
||||
// Open project selection modal
|
||||
new ProjectModal(plugin.app, plugin.temp_vars.projects, async (projectKey: string) => {
|
||||
new ProjectModal(plugin.app, projects, async (projectKey: string) => {
|
||||
// Fetch issue types for the selected project
|
||||
await fetchIssueTypes(plugin, projectKey);
|
||||
const issueTypes = await fetchIssueTypes(plugin, projectKey);
|
||||
|
||||
// Open issue type selection modal
|
||||
new IssueTypeModal(plugin.app, plugin.temp_vars.issueTypes, async (issueType: string) => {
|
||||
new IssueTypeModal(plugin.app, issueTypes, async (issueType: string) => {
|
||||
try {
|
||||
// Create the issue with selected project and issue type
|
||||
const issueKey = await createIssueFromFile(plugin, file, projectKey, issueType);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
manual:
|
||||
name: Вести журнал работы в Jira вручную
|
||||
name: Вести журнал работы в Jira вручную (Update work log in Jira manually)
|
||||
batch:
|
||||
name: Вести журнал работы в Jira массово
|
||||
name: Вести журнал работы в Jira массово (Update work log in Jira by batch)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
name: Создать задачу в Jira
|
||||
name: Создать задачу в Jira (Create issue in Jira)
|
||||
success: Задача {issueKey} успешно создана
|
||||
error: Ошибка при создании задачи
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
with_key: Получить данные задачи из Jira по указанному ключу
|
||||
without_key: Получить данные текущей задачи из Jira
|
||||
with_key: Получить данные задачи из Jira по номеру (Get issue from Jira with custom key)
|
||||
without_key: Получить данные текущей задачи из Jira (Get current issue from Jira)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
name: Обновить задачу в Jira
|
||||
name: Обновить задачу в Jira (Update issue in Jira)
|
||||
success: Задача {issueKey} успешно обновлена
|
||||
error: Ошибка при обновлении задачи
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
name: Обновить статус задачи в Jira
|
||||
name: Обновить статус задачи в Jira (Update issue status in Jira)
|
||||
success: Статус задачи {issueKey} успешно обновлён
|
||||
error: Ошибка при обновлении статуса задачи
|
||||
|
|
|
|||
|
|
@ -21,4 +21,4 @@ reset_confirm_title: Сбросить сопоставление полей
|
|||
reset_confirm_text: Сопоставление полей будет очищено. Вы уверены?
|
||||
reload_defaults_tooltip: Обновить
|
||||
reload_confirm_title: Обновить сопоставления полей по умолчанию
|
||||
reload_confirm_text: Текущие сопоставления полей будут перезаписаны сопоставлениями. Вы уверены?
|
||||
reload_confirm_text: Текущие сопоставления полей будут перезаписаны сопоставлениями по умолчанию. Вы уверены?
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import {transform_string_to_functions_mappings} from "./tools/convertFunctionStr
|
|||
*/
|
||||
export default class JiraPlugin extends Plugin {
|
||||
settings: JiraSettings;
|
||||
temp_vars = {projects: [] as JiraProject[], issueTypes: [] as JiraIssueType[]};
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
|
|
|
|||
|
|
@ -26,8 +26,19 @@ export class IssueStatusModal extends SuggestModal<JiraTransitionType> {
|
|||
}
|
||||
|
||||
renderSuggestion(type: JiraTransitionType, el: HTMLElement) {
|
||||
el.createEl("div", {text: type.action && type.status ? `${type.action} -> ${type.status}` :
|
||||
type.action ? `${type.action}` : `${type.status}`});
|
||||
let result = "";
|
||||
if (type.action && type.status) {
|
||||
if (type.action === type.status) {
|
||||
result = type.action;
|
||||
} else {
|
||||
result = `${type.action} (${type.status})`;
|
||||
}
|
||||
} else if (type.action) {
|
||||
result = type.action;
|
||||
} else if (type.status) {
|
||||
result = type.status;
|
||||
}
|
||||
el.createEl("div", {text: result});
|
||||
}
|
||||
|
||||
onChooseSuggestion(type: JiraTransitionType) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import {Notice, TFile} from "obsidian";
|
|||
import JiraPlugin from "../main";
|
||||
import {extractAllJiraSyncValuesFromContent, updateJiraSyncContent} from "./sectionTools";
|
||||
import {FieldMapping, obsidianJiraFieldMappings} from "../default/obsidianJiraFieldsMapping";
|
||||
import {debugLog} from "./debugLogging";
|
||||
|
||||
|
||||
export function localToJiraFields(
|
||||
|
|
@ -65,6 +66,7 @@ export async function updateJiraToLocal(
|
|||
let updatedContent = fileContent;
|
||||
for (const [fieldName, fieldValue] of Object.entries(syncSections)) {
|
||||
const markdownValue = jiraToMarkdown(fieldValue);
|
||||
debugLog(`Updating sync section: ${fieldName}: ${markdownValue}`);
|
||||
updatedContent = updateJiraSyncContent(updatedContent, fieldName, markdownValue);
|
||||
}
|
||||
|
||||
|
|
@ -104,6 +106,7 @@ function updateFieldFromJira(
|
|||
if (key in fieldMappings) {
|
||||
value = fieldMappings[key].fromJira(issue, targetObject);
|
||||
}
|
||||
// debugLog(`Updating field: ${key}: ${issue.fields[key]}`);
|
||||
|
||||
// Only update if value exists
|
||||
if (value !== null && value !== undefined) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
import {debugLog} from "./debugLogging";
|
||||
|
||||
const getSectionRegex = (sectionName: string) => new RegExp(`\`jira-sync-section-${sectionName}\`([\\s\\S]*?)\\n([\\s\\S]*?)(?=\\n#+ |\\n\`jira-sync-|$)`, 'g');
|
||||
const getLineRegex = (sectionName: string) => new RegExp(`\`jira-sync-line-${sectionName}\` *(.*?)(?=\\n|$)`, 'g');
|
||||
// ([w-]+)
|
||||
|
||||
/**
|
||||
* Updates or adds a Jira sync section in the file content
|
||||
* @param fileContent - The current content of the file
|
||||
|
|
@ -23,17 +27,16 @@ export function updateJiraSyncContent(fileContent: string, sectionName: string,
|
|||
* @returns The updated file content
|
||||
*/
|
||||
function updateJiraSyncSection(fileContent: string, sectionName: string, markdownContent: string, force: boolean = false): string {
|
||||
const sectionRegex = new RegExp(`(\`jira-sync-section-${sectionName}\` [\\s\\S]*?)\\n([\\s\\S]*?)(?=\\n##|\`jira-sync-|$)`, 'g');
|
||||
const sectionRegex = getSectionRegex(sectionName);
|
||||
|
||||
// If section exists, update it, otherwise append it
|
||||
// debugLog(`Trying to find field ${sectionName} in section`)
|
||||
if (sectionRegex.test(fileContent)) {
|
||||
// Reset regex lastIndex
|
||||
// debugLog(`Found a field ${sectionName} in section`)
|
||||
sectionRegex.lastIndex = 0;
|
||||
return fileContent.replace(
|
||||
sectionRegex,
|
||||
(match, group1) => `${group1}\n${markdownContent}\n`
|
||||
(_, group1) => `\`jira-sync-section-${sectionName}\`${group1}\n${markdownContent}`
|
||||
);
|
||||
} else if (force) {
|
||||
// No section found, append it
|
||||
|
|
@ -52,7 +55,7 @@ function updateJiraSyncSection(fileContent: string, sectionName: string, markdow
|
|||
* @returns The updated file content
|
||||
*/
|
||||
function updateJiraSyncLine(fileContent: string, lineName: string, lineContent: string, force: boolean = false): string {
|
||||
const lineRegex = new RegExp(`\`jira-sync-line-${lineName}\`.*?(?=\\n|$)`, 'g');
|
||||
const lineRegex = getLineRegex(lineName);
|
||||
const newLine = `\`jira-sync-line-${lineName}\` ${lineContent.split('\n')[0]}`;
|
||||
|
||||
// If line exists, update it, otherwise append it
|
||||
|
|
@ -88,10 +91,11 @@ export function extractAllJiraSyncValuesFromContent(fileContent: string): Record
|
|||
* @returns Object mapping section names to their content
|
||||
*/
|
||||
function extractAllJiraSyncValuesFromSections(fileContent: string): Record<string, string> {
|
||||
const syncSectionRegex = /`jira-sync-section-([\w-]+)` ([\s\S]*?)\n([\s\S]*?)(?=\n##|\n`jira-sync-|$)/g;
|
||||
const sectionRegex = getSectionRegex(`([\\w-]+)`);
|
||||
debugLog(sectionRegex);
|
||||
const sections: Record<string, string> = {};
|
||||
let match;
|
||||
while ((match = syncSectionRegex.exec(fileContent)) !== null) {
|
||||
while ((match = sectionRegex.exec(fileContent)) !== null) {
|
||||
// debugLog(`Found match ${JSON.stringify(match)} in section`)
|
||||
const sectionName = match[1];
|
||||
sections[sectionName] = match[3].trim();
|
||||
|
|
@ -106,11 +110,11 @@ function extractAllJiraSyncValuesFromSections(fileContent: string): Record<strin
|
|||
* @returns Object mapping line names to their content
|
||||
*/
|
||||
function extractAllJiraSyncValuesFromLines(fileContent: string): Record<string, string> {
|
||||
const syncLineRegex = /`jira-sync-line-([\w-]+)`\s+(.*?)(?=\n|$)/g;
|
||||
const lineRegex = getLineRegex(`([\\w-]+)`);
|
||||
debugLog(lineRegex);
|
||||
const lines: Record<string, string> = {};
|
||||
|
||||
let match;
|
||||
while ((match = syncLineRegex.exec(fileContent)) !== null) {
|
||||
while ((match = lineRegex.exec(fileContent)) !== null) {
|
||||
// debugLog(`Found match ${JSON.stringify(match)} in line`)
|
||||
const lineName = match[1];
|
||||
lines[lineName] = match[2].trim();
|
||||
|
|
|
|||
|
|
@ -566,7 +566,7 @@ code.hljs {
|
|||
}
|
||||
|
||||
/* Main display container */
|
||||
.all-periods-display {
|
||||
.jira-copyable {
|
||||
margin-top: 1.5rem;
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
|
|
|
|||
Loading…
Reference in a new issue