diff --git a/confluence-mdx/bin/reverse_sync/sidecar.py b/confluence-mdx/bin/reverse_sync/sidecar.py index 5e10af338..4ca96d6d2 100644 --- a/confluence-mdx/bin/reverse_sync/sidecar.py +++ b/confluence-mdx/bin/reverse_sync/sidecar.py @@ -3,8 +3,7 @@ Block-level sidecar (schema v3): RoundtripSidecar, SidecarBlock, DocumentEnvelope, build_sidecar, verify_sidecar_integrity, - write_sidecar, load_sidecar, sha256_text, - find_block_by_identity + write_sidecar, load_sidecar, sha256_text Mapping lookup (mapping.yaml v3 기반): SidecarChildEntry, SidecarEntry, load_sidecar_mapping, build_mdx_to_sidecar_index, @@ -14,6 +13,7 @@ from __future__ import annotations from dataclasses import dataclass, field +from collections import defaultdict import hashlib import json from pathlib import Path @@ -23,6 +23,7 @@ from reverse_sync.mapping_recorder import BlockMapping from reverse_sync.block_diff import NON_CONTENT_TYPES +from reverse_sync.xhtml_normalizer import extract_plain_text # --------------------------------------------------------------------------- @@ -31,9 +32,6 @@ ROUNDTRIP_SCHEMA_VERSION = "3" -# v2 스키마도 로드 허용 (하위 호환) -_COMPATIBLE_SCHEMA_VERSIONS = frozenset({"2", "3"}) - def sha256_text(text: str) -> str: return hashlib.sha256(text.encode("utf-8")).hexdigest() @@ -49,12 +47,7 @@ class DocumentEnvelope: @dataclass class SidecarBlock: - """Individual XHTML block + metadata. - - schema v3에서 reconstruction 필드가 추가됨: - - reconstruction: dict | None — block 재구성에 필요한 metadata - kind, old_plain_text, anchors, items(list), child_blocks 등을 포함 - """ + """Individual XHTML block + metadata.""" block_index: int xhtml_xpath: str @@ -64,6 +57,29 @@ class SidecarBlock: lost_info: dict = field(default_factory=dict) reconstruction: Optional[dict] = None + def to_dict(self) -> dict: + return { + "block_index": self.block_index, + "xhtml_xpath": self.xhtml_xpath, + "xhtml_fragment": self.xhtml_fragment, + "mdx_content_hash": self.mdx_content_hash, + "mdx_line_range": list(self.mdx_line_range), + "lost_info": self.lost_info, + "reconstruction": self.reconstruction, + } + + @staticmethod + def from_dict(data: dict) -> "SidecarBlock": + return SidecarBlock( + block_index=data["block_index"], + xhtml_xpath=data["xhtml_xpath"], + xhtml_fragment=data["xhtml_fragment"], + mdx_content_hash=data.get("mdx_content_hash", ""), + mdx_line_range=tuple(data.get("mdx_line_range", (0, 0))), + lost_info=data.get("lost_info", {}), + reconstruction=data.get("reconstruction"), + ) + @dataclass class RoundtripSidecar: @@ -89,25 +105,12 @@ def reassemble_xhtml(self) -> str: def to_dict(self) -> dict: """JSON 직렬화.""" - blocks = [] - for b in self.blocks: - d: dict = { - "block_index": b.block_index, - "xhtml_xpath": b.xhtml_xpath, - "xhtml_fragment": b.xhtml_fragment, - "mdx_content_hash": b.mdx_content_hash, - "mdx_line_range": list(b.mdx_line_range), - "lost_info": b.lost_info, - } - if b.reconstruction is not None: - d["reconstruction"] = b.reconstruction - blocks.append(d) return { "schema_version": self.schema_version, "page_id": self.page_id, "mdx_sha256": self.mdx_sha256, "source_xhtml_sha256": self.source_xhtml_sha256, - "blocks": blocks, + "blocks": [b.to_dict() for b in self.blocks], "separators": self.separators, "document_envelope": { "prefix": self.document_envelope.prefix, @@ -117,19 +120,8 @@ def to_dict(self) -> dict: @staticmethod def from_dict(data: dict) -> "RoundtripSidecar": - """JSON 역직렬화. v2/v3 모두 지원.""" - blocks = [ - SidecarBlock( - block_index=b["block_index"], - xhtml_xpath=b["xhtml_xpath"], - xhtml_fragment=b["xhtml_fragment"], - mdx_content_hash=b.get("mdx_content_hash", ""), - mdx_line_range=tuple(b.get("mdx_line_range", (0, 0))), - lost_info=b.get("lost_info", {}), - reconstruction=b.get("reconstruction"), - ) - for b in data.get("blocks", []) - ] + """JSON 역직렬화.""" + blocks = [SidecarBlock.from_dict(b) for b in data.get("blocks", [])] env = data.get("document_envelope", {}) return RoundtripSidecar( schema_version=data.get("schema_version", ROUNDTRIP_SCHEMA_VERSION), @@ -145,6 +137,43 @@ def from_dict(data: dict) -> "RoundtripSidecar": ) +def build_sidecar_identity_index( + blocks: List[SidecarBlock], +) -> Dict[str, List[SidecarBlock]]: + """Group sidecar blocks by content hash in deterministic line-range order.""" + grouped: Dict[str, List[SidecarBlock]] = defaultdict(list) + for block in blocks: + if not block.mdx_content_hash: + continue + grouped[block.mdx_content_hash].append(block) + for content_hash, content_blocks in grouped.items(): + grouped[content_hash] = sorted( + content_blocks, + key=lambda block: (tuple(block.mdx_line_range), block.block_index), + ) + return dict(grouped) + + +def find_sidecar_block_by_identity( + blocks: List[SidecarBlock], + mdx_content_hash: str, + mdx_line_range: tuple[int, int] | None = None, + occurrence_index: int = 0, +) -> Optional[SidecarBlock]: + """Resolve a block using hash first, then line range, then stable order.""" + candidates = build_sidecar_identity_index(blocks).get(mdx_content_hash, []) + if not candidates: + return None + if mdx_line_range is not None: + ranged = [ + block for block in candidates + if tuple(block.mdx_line_range) == tuple(mdx_line_range) + ] + if ranged: + return ranged[occurrence_index] if occurrence_index < len(ranged) else None + return candidates[occurrence_index] if occurrence_index < len(candidates) else None + + def verify_sidecar_integrity( sidecar: RoundtripSidecar, expected_xhtml: str, @@ -170,54 +199,6 @@ def verify_sidecar_integrity( ) -def _build_reconstruction_metadata( - fragment: str, - xhtml_type: str, -) -> Optional[dict]: - """XHTML fragment에서 reconstruction metadata를 생성한다. - - 현재 지원하는 kind: - - paragraph: old_plain_text + anchors - - heading: old_plain_text - - list: old_plain_text + items (placeholder) - - code: (None — clean block) - - table: (None — clean block) - - html_block: kind + old_plain_text - - Phase 3에서 anchor 분석이 추가될 예정. - """ - from reverse_sync.xhtml_normalizer import extract_plain_text - - # code, table은 clean block — reconstruction metadata 불필요 - if xhtml_type in ("code", "table"): - return None - - plain_text = extract_plain_text(fragment) - - kind_map = { - "heading": "heading", - "paragraph": "paragraph", - "list": "list", - "html_block": "container", - } - kind = kind_map.get(xhtml_type, xhtml_type) - - meta: dict = { - "kind": kind, - "old_plain_text": plain_text, - } - - # list는 items placeholder (Phase 3에서 실제 item 분석) - if xhtml_type == "list": - meta["items"] = [] - - # paragraph/heading은 anchors placeholder - if xhtml_type in ("heading", "paragraph"): - meta["anchors"] = [] - - return meta - - def build_sidecar( page_xhtml_text: str, mdx_text: str, @@ -225,8 +206,7 @@ def build_sidecar( ) -> RoundtripSidecar: """Block-level sidecar를 생성한다. - Fragment 추출 → MDX alignment → reconstruction metadata 빌드 → - 무결성 검증 → RoundtripSidecar 반환. + Fragment 추출 → MDX alignment → 무결성 검증 → RoundtripSidecar 반환. """ from reverse_sync.fragment_extractor import extract_block_fragments from reverse_sync.mapping_recorder import record_mapping @@ -236,6 +216,7 @@ def build_sidecar( xhtml_mappings = record_mapping(page_xhtml_text) frag_result = extract_block_fragments(page_xhtml_text) mdx_blocks = parse_mdx_blocks(mdx_text) + id_to_mapping = {mapping.block_id: mapping for mapping in xhtml_mappings} # 2. top-level mapping 필터링 child_ids = set() @@ -250,14 +231,12 @@ def build_sidecar( sidecar_blocks: List[SidecarBlock] = [] for i, fragment in enumerate(frag_result.fragments): xpath = top_mappings[i].xhtml_xpath if i < len(top_mappings) else f"unknown[{i}]" - xhtml_type = top_mappings[i].type if i < len(top_mappings) else "" # 순차 1:1 대응 (향후 block alignment로 개선) mdx_block = mdx_content_blocks[i] if i < len(mdx_content_blocks) else None mdx_hash = sha256_text(mdx_block.content) if mdx_block else "" mdx_range = (mdx_block.line_start, mdx_block.line_end) if mdx_block else (0, 0) - - reconstruction = _build_reconstruction_metadata(fragment, xhtml_type) + mapping = top_mappings[i] if i < len(top_mappings) else None sidecar_blocks.append( SidecarBlock( @@ -266,7 +245,7 @@ def build_sidecar( xhtml_fragment=fragment, mdx_content_hash=mdx_hash, mdx_line_range=mdx_range, - reconstruction=reconstruction, + reconstruction=_build_reconstruction_metadata(fragment, mapping, id_to_mapping), ) ) @@ -288,6 +267,39 @@ def build_sidecar( return sidecar +def _build_reconstruction_metadata( + fragment: str, + mapping: BlockMapping | None, + id_to_mapping: Dict[str, BlockMapping], +) -> Optional[dict]: + if mapping is None: + return None + + metadata: dict[str, Any] = { + "kind": mapping.type, + "old_plain_text": extract_plain_text(fragment), + } + if mapping.type == "paragraph": + metadata["anchors"] = [] + elif mapping.type == "list": + metadata["ordered"] = mapping.xhtml_xpath.startswith("ol[") + metadata["items"] = [] + elif mapping.children: + child_plain_texts = [ + id_to_mapping[child_id].xhtml_plain_text.strip() + for child_id in mapping.children + if child_id in id_to_mapping and id_to_mapping[child_id].xhtml_plain_text.strip() + ] + if child_plain_texts: + metadata["old_plain_text"] = " ".join(child_plain_texts) + metadata["child_xpaths"] = [ + id_to_mapping[child_id].xhtml_xpath + for child_id in mapping.children + if child_id in id_to_mapping + ] + return metadata + + def write_sidecar(sidecar: RoundtripSidecar, path: Path) -> None: """RoundtripSidecar를 JSON 파일로 저장한다.""" path.parent.mkdir(parents=True, exist_ok=True) @@ -298,18 +310,14 @@ def write_sidecar(sidecar: RoundtripSidecar, path: Path) -> None: def load_sidecar(path: Path) -> RoundtripSidecar: - """JSON 파일에서 RoundtripSidecar를 로드한다. - - v2와 v3 스키마를 모두 지원한다. v2 파일은 reconstruction=None으로 로드된다. - """ + """JSON 파일에서 RoundtripSidecar를 로드한다.""" data: Any = json.loads(path.read_text(encoding="utf-8")) if not isinstance(data, dict): raise ValueError("invalid sidecar payload") - version = data.get("schema_version") - if version not in _COMPATIBLE_SCHEMA_VERSIONS: + if data.get("schema_version") != ROUNDTRIP_SCHEMA_VERSION: raise ValueError( - f"expected schema_version in {sorted(_COMPATIBLE_SCHEMA_VERSIONS)}, " - f"got {version}" + f"expected schema_version={ROUNDTRIP_SCHEMA_VERSION}, " + f"got {data.get('schema_version')}" ) return RoundtripSidecar.from_dict(data) @@ -597,53 +605,3 @@ def find_mapping_by_sidecar( if entry is None: return None return xpath_to_mapping.get(entry.xhtml_xpath) - - -# --------------------------------------------------------------------------- -# Block identity — hash + line_range 기반 disambiguation -# --------------------------------------------------------------------------- - -@dataclass -class _IdentityKey: - """Internal identity lookup key.""" - mdx_content_hash: str - mdx_line_range: tuple - - -def build_block_identity_index( - sidecar: RoundtripSidecar, -) -> Dict[str, List[SidecarBlock]]: - """mdx_content_hash → SidecarBlock 리스트 인덱스를 구축한다. - - 동일 hash를 가진 블록이 여러 개일 때 line_range로 disambiguation할 수 있도록 - 리스트로 저장한다. - """ - index: Dict[str, List[SidecarBlock]] = {} - for block in sidecar.blocks: - if not block.mdx_content_hash: - continue - index.setdefault(block.mdx_content_hash, []).append(block) - return index - - -def find_block_by_identity( - mdx_content_hash: str, - mdx_line_range: tuple, - identity_index: Dict[str, List[SidecarBlock]], -) -> Optional[SidecarBlock]: - """hash + line_range로 SidecarBlock을 찾는다. - - 1. hash가 유일하면 바로 반환 - 2. 같은 hash가 여러 개면 line_range가 일치하는 블록을 반환 - 3. line_range도 일치하지 않으면 None - """ - candidates = identity_index.get(mdx_content_hash) - if not candidates: - return None - if len(candidates) == 1: - return candidates[0] - # line_range로 disambiguation - for block in candidates: - if block.mdx_line_range == mdx_line_range: - return block - return None diff --git a/confluence-mdx/tests/test_reverse_sync_sidecar_v2.py b/confluence-mdx/tests/test_reverse_sync_sidecar_v2.py deleted file mode 100644 index 6de14f205..000000000 --- a/confluence-mdx/tests/test_reverse_sync_sidecar_v2.py +++ /dev/null @@ -1,197 +0,0 @@ -"""reverse_sync/sidecar.py block-level sidecar 스키마 유닛 테스트.""" - -import json -from pathlib import Path - -import pytest - -from reverse_sync.sidecar import ( - DocumentEnvelope, - RoundtripSidecar, - SidecarBlock, - build_sidecar, - load_sidecar, - sha256_text, - verify_sidecar_integrity, - write_sidecar, -) - - -class TestSidecarSchema: - def test_create_sidecar(self): - sidecar = RoundtripSidecar( - page_id="test", - blocks=[ - SidecarBlock( - block_index=0, - xhtml_xpath="h1[1]", - xhtml_fragment="

Title

", - mdx_content_hash="abc123", - mdx_line_range=(1, 1), - ) - ], - separators=[], - document_envelope=DocumentEnvelope(prefix="", suffix="\n"), - ) - assert sidecar.schema_version in ("2", "3") - assert sidecar.page_id == "test" - assert len(sidecar.blocks) == 1 - - def test_to_dict_roundtrip(self): - original = RoundtripSidecar( - page_id="123", - mdx_sha256="mdx_hash", - source_xhtml_sha256="xhtml_hash", - blocks=[ - SidecarBlock(0, "h2[1]", "

A

", "hash_a", (1, 1)), - SidecarBlock(1, "p[1]", "

B

", "hash_b", (3, 3)), - ], - separators=["\n"], - document_envelope=DocumentEnvelope(prefix="", suffix="\n"), - ) - d = original.to_dict() - restored = RoundtripSidecar.from_dict(d) - - assert restored.schema_version in ("2", "3") - assert restored.page_id == "123" - assert len(restored.blocks) == 2 - assert restored.blocks[0].xhtml_fragment == "

A

" - assert restored.blocks[1].mdx_line_range == (3, 3) - assert restored.separators == ["\n"] - assert restored.document_envelope.suffix == "\n" - - def test_json_serializable(self): - sidecar = RoundtripSidecar( - page_id="test", - blocks=[ - SidecarBlock(0, "p[1]", "

한글

", "hash", (1, 1)), - ], - ) - json_str = json.dumps(sidecar.to_dict(), ensure_ascii=False) - data = json.loads(json_str) - assert data["blocks"][0]["xhtml_fragment"] == "

한글

" - - def test_reassemble_xhtml(self): - sidecar = RoundtripSidecar( - blocks=[ - SidecarBlock(0, "h1[1]", "

A

", "", (1, 1)), - SidecarBlock(1, "p[1]", "

B

", "", (3, 3)), - ], - separators=["\n"], - document_envelope=DocumentEnvelope(prefix="", suffix="\n"), - ) - assert sidecar.reassemble_xhtml() == "

A

\n

B

\n" - - -class TestVerifySidecarIntegrity: - def test_passes_when_equal(self): - original = "

A

\n

B

\n" - sidecar = RoundtripSidecar( - blocks=[ - SidecarBlock(0, "h1[1]", "

A

", "", (1, 1)), - SidecarBlock(1, "p[1]", "

B

", "", (3, 3)), - ], - separators=["\n"], - document_envelope=DocumentEnvelope(prefix="", suffix="\n"), - ) - verify_sidecar_integrity(sidecar, original) - - def test_fails_when_fragment_wrong(self): - original = "

A

\n

B

\n" - sidecar = RoundtripSidecar( - blocks=[ - SidecarBlock(0, "h1[1]", "

A

", "", (1, 1)), - SidecarBlock(1, "p[1]", "

WRONG

", "", (3, 3)), - ], - separators=["\n"], - document_envelope=DocumentEnvelope(prefix="", suffix="\n"), - ) - with pytest.raises(ValueError, match="integrity check failed"): - verify_sidecar_integrity(sidecar, original) - - def test_fails_when_separator_wrong(self): - original = "

A

\n

B

" - sidecar = RoundtripSidecar( - blocks=[ - SidecarBlock(0, "h1[1]", "

A

", "", (1, 1)), - SidecarBlock(1, "p[1]", "

B

", "", (3, 3)), - ], - separators=[" "], # wrong separator - document_envelope=DocumentEnvelope(prefix="", suffix=""), - ) - with pytest.raises(ValueError, match="integrity check failed"): - verify_sidecar_integrity(sidecar, original) - - -class TestWriteAndLoadSidecar: - def test_roundtrip(self, tmp_path): - sidecar = RoundtripSidecar( - page_id="100", - mdx_sha256="mhash", - source_xhtml_sha256="xhash", - blocks=[ - SidecarBlock(0, "h2[1]", "

Title

", "bhash", (1, 1)), - ], - separators=[], - document_envelope=DocumentEnvelope(prefix="", suffix=""), - ) - path = tmp_path / "sidecar.json" - write_sidecar(sidecar, path) - - loaded = load_sidecar(path) - assert loaded.page_id == "100" - assert loaded.blocks[0].xhtml_fragment == "

Title

" - - def test_load_rejects_wrong_version(self, tmp_path): - path = tmp_path / "bad.json" - path.write_text('{"schema_version": "1"}', encoding="utf-8") - with pytest.raises(ValueError, match="expected schema_version in"): - load_sidecar(path) - - -class TestBuildSidecar: - def test_simple_case(self): - xhtml = "

Title

\n

Body text

" - mdx = "## Title\n\nBody text\n" - sidecar = build_sidecar(xhtml, mdx, page_id="test") - - assert sidecar.schema_version in ("2", "3") - assert sidecar.page_id == "test" - assert sidecar.mdx_sha256 == sha256_text(mdx) - assert sidecar.source_xhtml_sha256 == sha256_text(xhtml) - assert len(sidecar.blocks) == 2 - assert sidecar.blocks[0].xhtml_fragment == "

Title

" - assert sidecar.blocks[1].xhtml_fragment == "

Body text

" - assert sidecar.separators == ["\n"] - - -class TestBuildSidecarRealTestcases: - """실제 testcase 파일에 대한 build + integrity 테스트.""" - - @pytest.fixture - def testcases_dir(self): - return Path(__file__).parent / "testcases" - - def test_all_testcases_build_and_verify(self, testcases_dir): - if not testcases_dir.is_dir(): - pytest.skip("testcases directory not found") - - ok = 0 - for case_dir in sorted(testcases_dir.iterdir()): - if not case_dir.is_dir(): - continue - xhtml_path = case_dir / "page.xhtml" - mdx_path = case_dir / "expected.mdx" - if not xhtml_path.exists() or not mdx_path.exists(): - continue - - xhtml = xhtml_path.read_text(encoding="utf-8") - mdx = mdx_path.read_text(encoding="utf-8") - sidecar = build_sidecar(xhtml, mdx, page_id=case_dir.name) - - assert sidecar.schema_version in ("2", "3") - assert len(sidecar.blocks) > 0 - assert len(sidecar.separators) == len(sidecar.blocks) - 1 - ok += 1 - - assert ok >= 21, f"Expected at least 21 testcases, got {ok}" diff --git a/confluence-mdx/tests/test_reverse_sync_sidecar_v3.py b/confluence-mdx/tests/test_reverse_sync_sidecar_v3.py index 448b89353..31f882132 100644 --- a/confluence-mdx/tests/test_reverse_sync_sidecar_v3.py +++ b/confluence-mdx/tests/test_reverse_sync_sidecar_v3.py @@ -1,12 +1,4 @@ -"""reverse_sync/sidecar.py schema v3 — reconstruction metadata 및 identity helper 테스트. - -Phase 1 게이트: -- SidecarBlock.reconstruction 필드 직렬화/역직렬화 -- build_sidecar가 reconstruction metadata를 생성 -- v2 파일 하위 호환 로드 -- hash + line_range 기반 identity helper -- 기존 21개 testcase build + integrity 유지 -""" +"""reverse_sync/sidecar.py schema v3 유닛 테스트.""" import json from pathlib import Path @@ -14,350 +6,262 @@ import pytest from reverse_sync.sidecar import ( - DocumentEnvelope, ROUNDTRIP_SCHEMA_VERSION, + DocumentEnvelope, RoundtripSidecar, SidecarBlock, - build_block_identity_index, build_sidecar, - find_block_by_identity, + build_sidecar_identity_index, + find_sidecar_block_by_identity, load_sidecar, sha256_text, + verify_sidecar_integrity, write_sidecar, ) -TESTCASES_DIR = Path(__file__).parent / "testcases" - - -# --------------------------------------------------------------------------- -# Schema v3 기본 동작 -# --------------------------------------------------------------------------- -class TestSchemaV3: - def test_schema_version_is_3(self): - assert ROUNDTRIP_SCHEMA_VERSION == "3" - - def test_sidecar_block_reconstruction_field(self): - block = SidecarBlock( - block_index=0, - xhtml_xpath="p[1]", - xhtml_fragment="

text

", - reconstruction={ - "kind": "paragraph", - "old_plain_text": "text", - "anchors": [], - }, - ) - assert block.reconstruction is not None - assert block.reconstruction["kind"] == "paragraph" - - def test_sidecar_block_reconstruction_none(self): - block = SidecarBlock( - block_index=0, - xhtml_xpath="macro-code[1]", - xhtml_fragment="", - ) - assert block.reconstruction is None - - def test_to_dict_includes_reconstruction(self): +class TestSidecarSchema: + def test_create_sidecar(self): sidecar = RoundtripSidecar( page_id="test", blocks=[ SidecarBlock( block_index=0, - xhtml_xpath="p[1]", - xhtml_fragment="

A

", - reconstruction={"kind": "paragraph", "old_plain_text": "A", "anchors": []}, - ), + xhtml_xpath="h1[1]", + xhtml_fragment="

Title

", + mdx_content_hash="abc123", + mdx_line_range=(1, 1), + ) ], + separators=[], + document_envelope=DocumentEnvelope(prefix="", suffix="\n"), ) - d = sidecar.to_dict() - assert "reconstruction" in d["blocks"][0] - assert d["blocks"][0]["reconstruction"]["kind"] == "paragraph" - - def test_to_dict_omits_reconstruction_when_none(self): - sidecar = RoundtripSidecar( - page_id="test", + assert sidecar.schema_version == ROUNDTRIP_SCHEMA_VERSION + assert sidecar.page_id == "test" + assert len(sidecar.blocks) == 1 + + def test_to_dict_roundtrip_preserves_reconstruction(self): + original = RoundtripSidecar( + page_id="123", + mdx_sha256="mdx_hash", + source_xhtml_sha256="xhtml_hash", blocks=[ + SidecarBlock(0, "h2[1]", "

A

", "hash_a", (1, 1)), SidecarBlock( - block_index=0, - xhtml_xpath="macro-code[1]", - xhtml_fragment="x", + 1, + "p[1]", + "

B

", + "hash_b", + (3, 3), + reconstruction={"kind": "paragraph", "old_plain_text": "B", "anchors": []}, ), ], + separators=["\n"], + document_envelope=DocumentEnvelope(prefix="", suffix="\n"), ) - d = sidecar.to_dict() - assert "reconstruction" not in d["blocks"][0] - - def test_from_dict_with_reconstruction(self): - data = { - "schema_version": "3", - "page_id": "test", - "blocks": [ - { - "block_index": 0, - "xhtml_xpath": "p[1]", - "xhtml_fragment": "

A

", - "reconstruction": { - "kind": "paragraph", - "old_plain_text": "A", - "anchors": [ - { - "anchor_id": "p[1]/ac:image[1]", - "raw_xhtml": "", - "old_plain_offset": 2, - "affinity": "after", - } - ], - }, - } - ], - "separators": [], - "document_envelope": {"prefix": "", "suffix": ""}, - } - sidecar = RoundtripSidecar.from_dict(data) - block = sidecar.blocks[0] - assert block.reconstruction is not None - assert len(block.reconstruction["anchors"]) == 1 - assert block.reconstruction["anchors"][0]["old_plain_offset"] == 2 - - def test_from_dict_without_reconstruction(self): - """v2 형식 데이터는 reconstruction=None으로 로드된다.""" - data = { - "schema_version": "2", - "page_id": "test", - "blocks": [ - { - "block_index": 0, - "xhtml_xpath": "p[1]", - "xhtml_fragment": "

A

", - } - ], - "separators": [], - "document_envelope": {"prefix": "", "suffix": ""}, + + restored = RoundtripSidecar.from_dict(original.to_dict()) + + assert restored.schema_version == ROUNDTRIP_SCHEMA_VERSION + assert restored.page_id == "123" + assert len(restored.blocks) == 2 + assert restored.blocks[0].xhtml_fragment == "

A

" + assert restored.blocks[1].mdx_line_range == (3, 3) + assert restored.blocks[1].reconstruction == { + "kind": "paragraph", + "old_plain_text": "B", + "anchors": [], } - sidecar = RoundtripSidecar.from_dict(data) - assert sidecar.blocks[0].reconstruction is None + assert restored.separators == ["\n"] + assert restored.document_envelope.suffix == "\n" - def test_json_roundtrip_with_reconstruction(self): + def test_json_serializable(self): sidecar = RoundtripSidecar( page_id="test", blocks=[ - SidecarBlock( - block_index=0, - xhtml_xpath="ul[1]", - xhtml_fragment="", - reconstruction={ - "kind": "list", - "old_plain_text": "X", - "items": [{"item_xpath": "ul[1]/li[1]", "old_plain_text": "X"}], - }, - ), + SidecarBlock(0, "p[1]", "

한글

", "hash", (1, 1)), ], ) json_str = json.dumps(sidecar.to_dict(), ensure_ascii=False) - restored = RoundtripSidecar.from_dict(json.loads(json_str)) - assert restored.blocks[0].reconstruction["kind"] == "list" - assert len(restored.blocks[0].reconstruction["items"]) == 1 - - -# --------------------------------------------------------------------------- -# v2 하위 호환 로드 -# --------------------------------------------------------------------------- - -class TestV2Compatibility: - def test_load_v2_file(self, tmp_path): - """v2 schema 파일이 정상 로드된다.""" - data = { - "schema_version": "2", - "page_id": "compat", - "blocks": [ - { - "block_index": 0, - "xhtml_xpath": "p[1]", - "xhtml_fragment": "

Old

", - "mdx_content_hash": "h", - "mdx_line_range": [1, 1], - "lost_info": {}, - } - ], - "separators": [], - "document_envelope": {"prefix": "", "suffix": ""}, - } - path = tmp_path / "v2.json" - path.write_text(json.dumps(data), encoding="utf-8") - - sidecar = load_sidecar(path) - assert sidecar.schema_version == "2" - assert sidecar.blocks[0].reconstruction is None - - def test_load_v3_file(self, tmp_path): - """v3 schema 파일이 정상 로드된다.""" - data = { - "schema_version": "3", - "page_id": "new", - "blocks": [ - { - "block_index": 0, - "xhtml_xpath": "p[1]", - "xhtml_fragment": "

New

", - "reconstruction": {"kind": "paragraph", "old_plain_text": "New"}, - } - ], - "separators": [], - "document_envelope": {"prefix": "", "suffix": ""}, - } - path = tmp_path / "v3.json" - path.write_text(json.dumps(data), encoding="utf-8") + data = json.loads(json_str) + assert data["blocks"][0]["xhtml_fragment"] == "

한글

" - sidecar = load_sidecar(path) - assert sidecar.schema_version == "3" - assert sidecar.blocks[0].reconstruction is not None + def test_reassemble_xhtml(self): + sidecar = RoundtripSidecar( + blocks=[ + SidecarBlock(0, "h1[1]", "

A

", "", (1, 1)), + SidecarBlock(1, "p[1]", "

B

", "", (3, 3)), + ], + separators=["\n"], + document_envelope=DocumentEnvelope(prefix="", suffix="\n"), + ) + assert sidecar.reassemble_xhtml() == "

A

\n

B

\n" - def test_load_v1_rejected(self, tmp_path): - """v1은 거부된다.""" - path = tmp_path / "v1.json" - path.write_text('{"schema_version": "1"}', encoding="utf-8") - with pytest.raises(ValueError, match="expected schema_version in"): - load_sidecar(path) - def test_write_load_roundtrip_v3(self, tmp_path): +class TestVerifySidecarIntegrity: + def test_passes_when_equal(self): + original = "

A

\n

B

\n" sidecar = RoundtripSidecar( - page_id="rt", blocks=[ - SidecarBlock( - block_index=0, - xhtml_xpath="p[1]", - xhtml_fragment="

RT

", - mdx_content_hash="h", - mdx_line_range=(5, 5), - reconstruction={"kind": "paragraph", "old_plain_text": "RT", "anchors": []}, - ), + SidecarBlock(0, "h1[1]", "

A

", "", (1, 1)), + SidecarBlock(1, "p[1]", "

B

", "", (3, 3)), ], - separators=[], - document_envelope=DocumentEnvelope(), + separators=["\n"], + document_envelope=DocumentEnvelope(prefix="", suffix="\n"), ) - path = tmp_path / "sidecar.json" - write_sidecar(sidecar, path) - loaded = load_sidecar(path) - assert loaded.blocks[0].reconstruction == {"kind": "paragraph", "old_plain_text": "RT", "anchors": []} - + verify_sidecar_integrity(sidecar, original) -# --------------------------------------------------------------------------- -# Block identity helper -# --------------------------------------------------------------------------- + def test_fails_when_fragment_wrong(self): + original = "

A

\n

B

\n" + sidecar = RoundtripSidecar( + blocks=[ + SidecarBlock(0, "h1[1]", "

A

", "", (1, 1)), + SidecarBlock(1, "p[1]", "

WRONG

", "", (3, 3)), + ], + separators=["\n"], + document_envelope=DocumentEnvelope(prefix="", suffix="\n"), + ) + with pytest.raises(ValueError, match="integrity check failed"): + verify_sidecar_integrity(sidecar, original) -class TestBlockIdentity: - @pytest.fixture - def sidecar_with_duplicates(self): - return RoundtripSidecar( + def test_fails_when_separator_wrong(self): + original = "

A

\n

B

" + sidecar = RoundtripSidecar( blocks=[ - SidecarBlock(0, "p[1]", "

A

", "hash_a", (1, 1)), - SidecarBlock(1, "p[2]", "

B

", "hash_b", (3, 3)), - SidecarBlock(2, "p[3]", "

A

", "hash_a", (5, 5)), # duplicate hash - SidecarBlock(3, "p[4]", "

C

", "hash_c", (7, 7)), + SidecarBlock(0, "h1[1]", "

A

", "", (1, 1)), + SidecarBlock(1, "p[1]", "

B

", "", (3, 3)), ], + separators=[" "], + document_envelope=DocumentEnvelope(prefix="", suffix=""), ) + with pytest.raises(ValueError, match="integrity check failed"): + verify_sidecar_integrity(sidecar, original) - def test_unique_hash_found(self, sidecar_with_duplicates): - index = build_block_identity_index(sidecar_with_duplicates) - result = find_block_by_identity("hash_b", (3, 3), index) - assert result is not None - assert result.block_index == 1 - - def test_unique_hash_found_regardless_of_line_range(self, sidecar_with_duplicates): - """hash가 유일하면 line_range가 달라도 찾는다.""" - index = build_block_identity_index(sidecar_with_duplicates) - result = find_block_by_identity("hash_b", (999, 999), index) - assert result is not None - assert result.block_index == 1 - - def test_duplicate_hash_disambiguated_by_line_range(self, sidecar_with_duplicates): - index = build_block_identity_index(sidecar_with_duplicates) - result1 = find_block_by_identity("hash_a", (1, 1), index) - result2 = find_block_by_identity("hash_a", (5, 5), index) - assert result1 is not None and result1.block_index == 0 - assert result2 is not None and result2.block_index == 2 - - def test_duplicate_hash_no_matching_line_range(self, sidecar_with_duplicates): - index = build_block_identity_index(sidecar_with_duplicates) - result = find_block_by_identity("hash_a", (99, 99), index) - assert result is None - - def test_nonexistent_hash(self, sidecar_with_duplicates): - index = build_block_identity_index(sidecar_with_duplicates) - result = find_block_by_identity("nonexistent", (1, 1), index) - assert result is None - - def test_empty_hash_skipped(self): + +class TestWriteAndLoadSidecar: + def test_roundtrip(self, tmp_path): sidecar = RoundtripSidecar( - blocks=[SidecarBlock(0, "p[1]", "

A

", "", (1, 1))], + page_id="100", + mdx_sha256="mhash", + source_xhtml_sha256="xhash", + blocks=[ + SidecarBlock(0, "h2[1]", "

Title

", "bhash", (1, 1)), + ], + separators=[], + document_envelope=DocumentEnvelope(prefix="", suffix=""), ) - index = build_block_identity_index(sidecar) - assert len(index) == 0 + path = tmp_path / "sidecar.json" + write_sidecar(sidecar, path) - def test_identity_index_groups_correctly(self, sidecar_with_duplicates): - index = build_block_identity_index(sidecar_with_duplicates) - assert len(index["hash_a"]) == 2 - assert len(index["hash_b"]) == 1 - assert len(index["hash_c"]) == 1 + loaded = load_sidecar(path) + assert loaded.page_id == "100" + assert loaded.blocks[0].xhtml_fragment == "

Title

" + def test_load_rejects_wrong_version(self, tmp_path): + path = tmp_path / "bad.json" + path.write_text('{"schema_version": "1"}', encoding="utf-8") + with pytest.raises(ValueError, match=f"expected schema_version={ROUNDTRIP_SCHEMA_VERSION}"): + load_sidecar(path) -# --------------------------------------------------------------------------- -# build_sidecar reconstruction metadata -# --------------------------------------------------------------------------- -class TestBuildSidecarReconstructionMetadata: - def test_simple_case_has_reconstruction(self): +class TestBuildSidecar: + def test_simple_case(self): xhtml = "

Title

\n

Body text

" mdx = "## Title\n\nBody text\n" sidecar = build_sidecar(xhtml, mdx, page_id="test") - assert sidecar.schema_version == "3" - # heading block - h_block = sidecar.blocks[0] - assert h_block.reconstruction is not None - assert h_block.reconstruction["kind"] == "heading" - assert h_block.reconstruction["old_plain_text"] == "Title" - assert h_block.reconstruction["anchors"] == [] - # paragraph block - p_block = sidecar.blocks[1] - assert p_block.reconstruction is not None - assert p_block.reconstruction["kind"] == "paragraph" - assert p_block.reconstruction["old_plain_text"] == "Body text" - - def test_code_block_no_reconstruction(self): + assert sidecar.schema_version == ROUNDTRIP_SCHEMA_VERSION + assert sidecar.page_id == "test" + assert sidecar.mdx_sha256 == sha256_text(mdx) + assert sidecar.source_xhtml_sha256 == sha256_text(xhtml) + assert len(sidecar.blocks) == 2 + assert sidecar.blocks[0].xhtml_fragment == "

Title

" + assert sidecar.blocks[1].xhtml_fragment == "

Body text

" + assert sidecar.blocks[1].reconstruction == { + "kind": "paragraph", + "old_plain_text": "Body text", + "anchors": [], + } + assert sidecar.separators == ["\n"] + + def test_list_block_includes_reconstruction_metadata(self): + xhtml = "" + mdx = "- Parent\n - Child\n" + sidecar = build_sidecar(xhtml, mdx, page_id="test") + + assert sidecar.blocks[0].reconstruction == { + "kind": "list", + "old_plain_text": "ParentChild", + "ordered": False, + "items": [], + } + + def test_container_block_records_child_xpaths(self): xhtml = ( - '' - 'python' - '' - '' + '' + "" + "

First

" + "

Second

" + "
" + "
" + ) + mdx = ( + "import { Callout } from 'nextra/components'\n\n" + "\n" + "First\n\n" + "Second\n" + "\n" ) - mdx = "```python\nx = 1\n```\n" sidecar = build_sidecar(xhtml, mdx, page_id="test") - assert sidecar.blocks[0].reconstruction is None - def test_list_block_has_reconstruction(self): - xhtml = "" - mdx = "- Item 1\n- Item 2\n" - sidecar = build_sidecar(xhtml, mdx, page_id="test") - block = sidecar.blocks[0] - assert block.reconstruction is not None - assert block.reconstruction["kind"] == "list" - assert "items" in block.reconstruction + assert sidecar.blocks[0].reconstruction == { + "kind": "html_block", + "old_plain_text": "First Second", + "child_xpaths": ["macro-info[1]/p[1]", "macro-info[1]/p[2]"], + } + + +class TestSidecarIdentityHelpers: + def test_build_sidecar_identity_index_groups_by_hash_in_line_order(self): + blocks = [ + SidecarBlock(0, "p[1]", "

A

", "same", (8, 8)), + SidecarBlock(1, "p[2]", "

A

", "same", (3, 3)), + SidecarBlock(2, "p[3]", "

B

", "other", (5, 5)), + ] + index = build_sidecar_identity_index(blocks) -# --------------------------------------------------------------------------- -# 실제 testcase에서 build + integrity + reconstruction 검증 -# --------------------------------------------------------------------------- + assert [block.xhtml_xpath for block in index["same"]] == ["p[2]", "p[1]"] + + def test_find_sidecar_block_by_identity_prefers_line_range(self): + blocks = [ + SidecarBlock(0, "p[1]", "

Repeated

", "same", (10, 10)), + SidecarBlock(1, "p[2]", "

Repeated

", "same", (20, 20)), + ] + + result = find_sidecar_block_by_identity(blocks, "same", (20, 20)) + + assert result is blocks[1] + + def test_find_sidecar_block_by_identity_uses_occurrence_when_range_missing(self): + blocks = [ + SidecarBlock(0, "p[1]", "

Repeated

", "same", (10, 10)), + SidecarBlock(1, "p[2]", "

Repeated

", "same", (20, 20)), + ] + + result = find_sidecar_block_by_identity(blocks, "same", None, occurrence_index=1) + + assert result is blocks[1] + + +class TestBuildSidecarRealTestcases: + """실제 testcase 파일에 대한 build + integrity 테스트.""" -class TestBuildSidecarRealTestcasesV3: @pytest.fixture def testcases_dir(self): - return TESTCASES_DIR + return Path(__file__).parent / "testcases" def test_all_testcases_build_and_verify(self, testcases_dir): - """21개 testcase 모두 schema v3로 build + integrity pass.""" if not testcases_dir.is_dir(): pytest.skip("testcases directory not found") @@ -374,50 +278,9 @@ def test_all_testcases_build_and_verify(self, testcases_dir): mdx = mdx_path.read_text(encoding="utf-8") sidecar = build_sidecar(xhtml, mdx, page_id=case_dir.name) - assert sidecar.schema_version == "3" + assert sidecar.schema_version == ROUNDTRIP_SCHEMA_VERSION assert len(sidecar.blocks) > 0 assert len(sidecar.separators) == len(sidecar.blocks) - 1 ok += 1 assert ok >= 21, f"Expected at least 21 testcases, got {ok}" - - def test_reconstruction_metadata_present(self, testcases_dir): - """실제 testcase에서 reconstruction이 생성되는지 확인.""" - case_dir = testcases_dir / "544113141" - if not case_dir.exists(): - pytest.skip("testcase 544113141 not found") - - xhtml = (case_dir / "page.xhtml").read_text(encoding="utf-8") - mdx = (case_dir / "expected.mdx").read_text(encoding="utf-8") - sidecar = build_sidecar(xhtml, mdx, page_id="544113141") - - # heading block은 reconstruction 있어야 함 - heading_blocks = [b for b in sidecar.blocks if b.xhtml_xpath.startswith("h")] - assert len(heading_blocks) > 0 - for b in heading_blocks: - assert b.reconstruction is not None - assert b.reconstruction["kind"] == "heading" - assert len(b.reconstruction["old_plain_text"]) > 0 - - def test_identity_index_from_real_testcase(self, testcases_dir): - """실제 testcase에서 identity index가 올바르게 구축된다.""" - case_dir = testcases_dir / "544113141" - if not case_dir.exists(): - pytest.skip("testcase 544113141 not found") - - xhtml = (case_dir / "page.xhtml").read_text(encoding="utf-8") - mdx = (case_dir / "expected.mdx").read_text(encoding="utf-8") - sidecar = build_sidecar(xhtml, mdx, page_id="544113141") - - index = build_block_identity_index(sidecar) - - # 모든 hash가 있는 block이 인덱스에 있어야 함 - hashed_blocks = [b for b in sidecar.blocks if b.mdx_content_hash] - total_in_index = sum(len(v) for v in index.values()) - assert total_in_index == len(hashed_blocks) - - # 각 block을 identity로 다시 찾을 수 있어야 함 - for b in hashed_blocks: - found = find_block_by_identity(b.mdx_content_hash, b.mdx_line_range, index) - assert found is not None, f"Failed to find block {b.block_index} by identity" - assert found.block_index == b.block_index diff --git a/confluence-mdx/tests/testcases/1454342158/expected.roundtrip.json b/confluence-mdx/tests/testcases/1454342158/expected.roundtrip.json index 7ec3fecd6..b620396ff 100644 --- a/confluence-mdx/tests/testcases/1454342158/expected.roundtrip.json +++ b/confluence-mdx/tests/testcases/1454342158/expected.roundtrip.json @@ -1,5 +1,5 @@ { - "schema_version": "2", + "schema_version": "3", "page_id": "1454342158", "mdx_sha256": "7a634e202c8134d7f0520c9730e9ae4e8cdbf58d3128227c1355d372d0f071a4", "source_xhtml_sha256": "94e38b8adec78c652bec25688477c8ba2e1e15b5a7c16a7c7112ec7b9af3b8d7", @@ -13,7 +13,11 @@ 8, 8 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 1, @@ -24,7 +28,11 @@ 10, 10 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Overview" + } }, { "block_index": 2, @@ -35,7 +43,12 @@ 12, 13 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "QueryPie의 사용자 인증 방식은 QueryPie 사용자 계정(ID)과 암호를 사용하는 방식과 외부 Identity Provider(IdP)와 통합하는 방식 크게 두가지가 있습니다. 관리자는 IdP와의 Integration 설정을 통해 QueryPie가 Single-Sign-On을 통해 쉽게 사용자 인증을 처리하고 계정을 동기화 할 수 있습니다.", + "anchors": [] + } }, { "block_index": 3, @@ -46,7 +59,12 @@ 15, 15 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "현재 지원되는 IdP와의 연동 설정은 다음과 같습니다.", + "anchors": [] + } }, { "block_index": 4, @@ -57,7 +75,13 @@ 17, 22 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "LDAPOktaSAML 2.0OneLoginSwivel SecureCustom Identity Provider (특수 목적 사용)", + "ordered": false, + "items": [] + } }, { "block_index": 5, @@ -68,7 +92,14 @@ 24, 24 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "현재 여러개의 Identity Provider를 동시에 지원하지 않습니다. Internal database (QueryPie 사용자 ID/PW) 인증외에 하나의 IdP를 사용해야 합니다.MFA (Multi-factor Authentication) 지원은 Internal DB, LDAP, Custom Identity Provider에서만 가능합니다. 11.5.0 부터 Internal DB와 LDAP 또는 Internal DB와 Custom Identity Provider를 설정해서 사용할 경우 각각의 설정에서 MFA를 사용할 수 있습니다.예) LDAP에서 MFA를 설정하고 Internal DB에 MFA를 설정한 경우 사용자 계정이 LDAP 계정이면 LDAP의 MFA 설정에 의해 제어되고 Internal DB의 사용자 계정은 Internal DB의 MFA 설정에 의해 제어됩니다.Schedule에 의한 주기적 동기화는 LDAP, Okta, One Login, Custom Identity Provider에서만 가능합니다.", + "child_xpaths": [ + "macro-info[1]/ul[1]" + ] + } }, { "block_index": 6, @@ -79,7 +110,12 @@ 25, 28 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "Admin > General > System > Integration 에서 Authentication 하위 항목으로 Identity Providers 가 있습니다. 항목을 클릭하여 상세페이지로 이동하면 Identity Providers에 관련된 설정을 할 수 있습니다. 기본적으로 사용자 인증은 QueryPie의 Internal database에 저장된 ID / Password를 사용해서 처리되므로 목록에 Internal database 항목이 존재합니다. 이 항목은 삭제 할 수 없습니다.", + "anchors": [] + } }, { "block_index": 7, @@ -90,7 +126,11 @@ 29, 29 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 8, @@ -101,7 +141,11 @@ 31, 34 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "기본 인증의 상세 설정" + } }, { "block_index": 9, @@ -112,7 +156,13 @@ 36, 38 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Name : 목록에 표시되는 식별 가능한 이름입니다.Type : QueryPie에서 지원하는 IdP 유형 중 하나가 표시됩니다.Multi-Factor Authentication Setting : MFA 설정을 할 수 있습니다. Google Authenticator, Email을 지원합니다.", + "ordered": false, + "items": [] + } }, { "block_index": 10, @@ -123,7 +173,11 @@ 40, 40 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Internal database의 상세 설정" + } }, { "block_index": 11, @@ -134,7 +188,14 @@ 42, 44 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "주의: 설정 후 한번이라도 사용자를 동기화 했다면, 설정한 IdP를 삭제(삭제후 다른 IdP로 변경)하는 것이 불가능합니다. IdP를 변경하거나 삭제를 해야하는 경우 Customer Portal 을 통해 문의 부탁드립니다.", + "child_xpaths": [ + "macro-note[1]/p[1]" + ] + } }, { "block_index": 12, @@ -145,7 +206,11 @@ 46, 51 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "LDAP 연동" + } }, { "block_index": 13, @@ -156,7 +221,12 @@ 53, 55 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "목록 우측 상단의 + Add 버튼을 누르고 팝업되는 화면에서 Type을 LDAP으로 선택하면 LDAP을 IdP로 추가 할 수 있습니다.", + "anchors": [] + } }, { "block_index": 14, @@ -167,7 +237,13 @@ 56, 56 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Name : 식별에 용이하도록 적합한 IdP의 이름을 입력합니다.Type: LDAP를 선택합니다.Server URL : ldap://ldap.example.com:389 과 같은 형식으로 LDAP server의 주소를 입력합니다. LDAPS의 경우 scheme을 ldaps:// 로 입력합니다.BindDN : LDAP 서버에 접속(바인드)할 때 사용할 서비스 계정의 고유 이름(Distinguished Name, DN)을 입력합니다. 이 계정은 최소한 사용자 정보를 검색(Read)할 수 있는 권한이 필요합니다.예시: cn=admin,ou=Services,dc=example,dc=comBind Password : BindDN의 암호를 입력합니다.", + "ordered": false, + "items": [] + } }, { "block_index": 15, @@ -178,7 +254,11 @@ 58, 58 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "LDAP 상세설정" + } }, { "block_index": 16, @@ -189,7 +269,12 @@ 60, 60 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "QueryPie 사용자 계정과 LDAP의 사용자 계정을 동기화하여 매핑할 내용을 입력합니다. ", + "anchors": [] + } }, { "block_index": 17, @@ -200,7 +285,11 @@ 62, 66 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "table", + "old_plain_text": "Attribute필수 여부DescriptionUser Base DN필수LDAP 트리 내에서 사용자 계정들을 검색할 시작 위치입니다. 이 경로 하위에 있는 사용자들만 로그인 대상으로 간주됩니다.예시: ou=People,dc=example,dc=com 또는 cn=Users,dc=example,dc=comUser Search Filter필수사용자가 로그인 시 입력한 아이디를 기반으로 LDAP에서 해당 사용자를 찾는 데 사용할 쿼리(필터)를 입력합니다. 예시: (objectClass=inetOrgPerson)User Name필수LDAP 서버에서 사용자의 로그인 아이디로 사용되는 속성(attribute)의 이름을 입력합니다. QueryPie와 동기화 할 때 QuerPie 사용자의 Login ID에 매핑됩니다.예시: uidEmail필수LDAP 서버에서 사용자의 Email 주소 항목으로 사용되는 속성(attribute)의 이름을 입력합니다. QueryPie와 동기화 할 때 QueryPie 사용자의 Email에 매핑됩니다.예시: emailDisplay Name-QueryPie 사용자의 Display Name과 매핑할 LDAP 서버에서 사용하는 속성을 입력합니다.예시: cn 또는 displayName 등." + } }, { "block_index": 18, @@ -211,7 +300,12 @@ 68, 73 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "LDAP에서 사용자 그룹 정보 및 소속 정보를 동기화하려면 Use Group 옵션을 활성화(체크)하고 필수로 지정된 정보들을 입력합니다. ", + "anchors": [] + } }, { "block_index": 19, @@ -222,7 +316,11 @@ 75, 75 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 20, @@ -233,7 +331,12 @@ 77, 83 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 21, @@ -244,7 +347,11 @@ 85, 85 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "table", + "old_plain_text": "Attribute필수 여부DescriptionGroup Base DN필수LDAP 서버의 그룹 Base DN 값을 입력합니다. 이 경로의 하위에 있는 그룹만 Group으로 동기화합니다. 예: dc=example,dc=comGroup Search Filter필수LDAP 서버의 그룹을 가져오기 위한 필터 값을 입력합니다.예: objectclass=posixGroupGroup ID필수그룹의 식별자로 사용할 속성 값을 입력합니다.예: gidNumberMembership Type필수사용자에 그룹 정보가 포함된 경우, Include group information in user entries 를 선택하고, 하단 필드에 참조할 Attribute를 입력합니다.예: member, uniqueMember, memberUid 등그룹에 사용자 정보가 포함된 경우, Include user information in group entries 를 선택하고, 하단 필드에 참조할 Attribute를 입력합니다.예: gidNumber" + } }, { "block_index": 22, @@ -255,7 +362,12 @@ 87, 89 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "LDAP 서버로부터 사용자 정보 동기화를 실행하려는 경우 Use Synchronization with the Authentication System 옵션을 활성화합니다.", + "anchors": [] + } }, { "block_index": 23, @@ -266,7 +378,11 @@ 92, 166 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 24, @@ -277,7 +393,12 @@ 168, 168 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 25, @@ -288,7 +409,13 @@ 170, 172 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Replication Frequency : 자동 동기화 기능 사용 여부를 설정합니다.Manual : 수동으로만 동기화를 수행합니다. 현재 페이지에서 Synchronize 버튼을 클릭할 때에만 LDAP 서버로부터 사용자 정보를 불러옵니다.Scheduling : 주기적으로 동기화를 수행합니다. 하단 Use cron expression 필드가 활성화됩니다.Additional SettingsMake New Users Inactive by Default : 동기화 시 새로운 사용자를 비활성화 상태로 추가할지 여부를 선택합니다.동기화할 사용자 수가 많거나, 사용자의 LDAP 인증을 통한 QueryPie 접근을 개별적으로 관리하고자 하는 경우 해당 옵션을 활성화하시기 바랍니다.Use an Attribute for Privilege Revoke : 동기화 시 특정 Attribute에 따라 Privilege를 회수할지 여부를 선택합니다.특정 LDAP Attribute의 변경에 의해 자동으로 DAC Privilege를 회수하고자 하는 경우 이 옵션을 활성화하세요.LDAP Attribute 입력 필드에 활성화 변경을 감지하려는 Attribute 이름을 입력합니다.Enable Attribute Synchronization : LDAP 사용자 속성과 QueryPie 사용자 속성을 매핑하여 동기화할지 여부를 선택합니다.LDAP에서 관리 중인 사용자 속성을 QueryPie 내 Attribute와 자동으로 연동하고자 하는 경우, 해당 옵션을 활성화하시기 바랍니다.옵션 활성화 시, 하단에 LDAP Attribute Mapping UI가 표시되며 매핑 작업을 통해 연동할 LDAP Attribute와 QueryPie Attribute를 지정할 수 있습니다.단, 해당 기능은 Profile Editor(Admin> General > User Management > Profile Editor)에서 Source Priority가 Inherit from profile source로 설정된 Attribute에 한해 적용됩니다.Allowed User Deletion Rate Threshold : 동기화시 기존 유저가 이 값의 비율 이상으로 삭제되었을 경우에는 동기화를 실패하도록 하는 기능입니다.0.0 ~ 1.0 사이의 값을 입력합니다. (기본값은 0.1)예) 기존 유저가 100명이고, Allowed User Deletion Rate Threshold 0.1 인 경우, 다시 동기화 하였을 때, 삭제된 유저가 10명 이상이면 동기화가 실패합니다.11.3.0 이전 버전에서 동기화 설정된 상태에서, 11.3.0으로 제품을 업그레이드하면 이 값이 1.0 으로 기본 설정됩니다.", + "ordered": false, + "items": [] + } }, { "block_index": 26, @@ -299,7 +426,17 @@ 175, 192 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "LDAP Attribute Mapping LDAP에서 관리 중인 사용자 속성을 QueryPie 내 Attribute와 매핑하여 동기화하려면, Enable Attribute Synchronization 옵션을 활성화하고 아래 정보를 입력합니다. 우측 상단의 Add Row 버튼을 클릭하면 새로운 매핑 행이 추가되며, 각 행마다 LDAP Attribute와 대응되는 QueryPie Attribute를 지정할 수 있습니다. 해당 기능은 Admin > General > User Management > Profile Editor에서 Source Priority가 Inherit from profile source로 설정된 QueryPie Attribute에 한해 적용됩니다.QueryPie Attribute인 Username (loginId), Primary Email (email) 항목은 LDAP 연동 설정 시 별도로 입력되므로, 해당 항목은 LDAP–QueryPie Attribute Mapping UI에는 노출되지 않습니다. 매핑 행 삭제 또는 변경 시, Save 버튼을 클릭해야 UI 상에서 변경 사항이 반영되며, Synchronize를 추가로 클릭해야 LDAP과 실제 동기화가 수행됩니다. 즉, Save는 화면상 변경, Synchronize는 시스템 반영을 의미합니다.", + "child_xpaths": [ + "macro-info[2]/p[1]", + "macro-info[2]/p[2]", + "macro-info[2]/p[3]", + "macro-info[2]/ol[1]" + ] + } }, { "block_index": 27, @@ -310,7 +447,14 @@ 194, 195 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Active Directory를 LDAP 연동할 경우 Anonymous 항목을 반드시 false로 설정해야합니다. AD는 기본적으로 익명 바인드 상태에서의 검색 작업을 허용하지 않습니다.", + "child_xpaths": [ + "ac:adf-extension[1]/p[1]" + ] + } }, { "block_index": 28, @@ -321,7 +465,11 @@ 197, 197 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Okta 연동" + } }, { "block_index": 29, @@ -332,7 +480,11 @@ 199, 199 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Okta에서 QueryPie 를 애플리케이션으로 추가" + } }, { "block_index": 30, @@ -343,7 +495,11 @@ 201, 203 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Okta Admin > Applications > Applications > Browse App Catalog > QueryPie 검색" + } }, { "block_index": 31, @@ -354,7 +510,13 @@ 204, 204 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Okta 서비스에 접속하여 관리자 계정으로 로그인합니다.우측 상단의 프로필을 클릭하여 Your Org로 접속합니다.Okta 관리자 페이지의 좌측 패널에서 Applications > Applications 메뉴로 이동합니다.Browse App Catalog 버튼을 클릭하여 QueryPie 를 검색합니다.QueryPie 애플리케이션 페이지로 들어가 Add Integration 버튼을 클릭합니다.Application Label에 QueryPie 로 입력된 것을 확인 후, Done 버튼을 클릭하여 애플리케이션을 추가합니다.", + "ordered": false, + "items": [] + } }, { "block_index": 32, @@ -365,7 +527,11 @@ 206, 208 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Okta 계정 연동을 위한 Profile 설정" + } }, { "block_index": 33, @@ -376,7 +542,11 @@ 209, 212 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Okta Admin > Directory > Profile Editor > QueryPie User > Add Attribute" + } }, { "block_index": 34, @@ -387,7 +557,13 @@ 214, 214 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Okta 관리자 페이지의 좌측 패널에서 Directory > Profile Editor 메뉴로 이동합니다.Profile 목록 중 ‘QueryPie User’ 를 클릭합니다.Attributes 설정에서 Add Attribute 버튼을 클릭합니다.Attribute 추가 화면에서 아래 4가지 항목을 차례대로 입력 후 저장합니다.Display name : firstName / Variable name : firstName 항목 입력 후 Save and Add AnotherDisplay name : lastName / Variable name : lastName 항목 입력 후 Save and Add AnotherDisplay name : email / Variable name : email 항목 입력 후 Save and Add AnotherDisplay name : loginId / Variable name : loginId 항목 입력 후 Save 클릭", + "ordered": false, + "items": [] + } }, { "block_index": 35, @@ -398,7 +574,11 @@ 216, 216 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Okta Admin > Directory > Profile Editor > QueryPie User > Mappings" + } }, { "block_index": 36, @@ -409,7 +589,13 @@ 218, 223 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "4가지 Attribute 가 추가된 것을 확인 후 Mappings 버튼을 클릭합니다.Okta User Profile Attribute 항목을 아래와 같이 QueryPie User Profile의 Attribute 와 연결합니다.user.firstName ↔︎ firstNameuser.lastName ↔︎ lastNameuser.email ↔︎ emailuser.email ↔︎ loginId (Okta 의 email 항목을 QueryPie 의 로그인 Id 로 사용합니다.)Save Mappings 버튼을 클릭하여 저장합니다.", + "ordered": false, + "items": [] + } }, { "block_index": 37, @@ -420,7 +606,12 @@ 225, 230 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 38, @@ -431,7 +622,11 @@ 232, 232 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Okta에 추가된 QueryPie 애플리케이션에 사용자 할당" + } }, { "block_index": 39, @@ -442,7 +637,11 @@ 234, 239 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Okta Admin > Applications > Applications > QueryPie App" + } }, { "block_index": 40, @@ -453,7 +652,13 @@ 241, 248 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Okta 관리자 페이지의 좌측 패널에서 Applications > Applications 메뉴로 이동합니다.리스트에서 QueryPie 애플리케이션을 클릭합니다.Assignments 탭으로 이동한 뒤 Assign 버튼을 클릭하여 Assign to People 또는 Assign to Group을 선택합니다.Okta 계정으로 QueryPie 접근을 허용할 사용자 또는 그룹을 할당한 뒤 Done 버튼을 클릭합니다.People 할당시 사용자 정보 확인 후 Sava and Go Back 버튼을 클릭합니다.Group 할당시 loginId 항목을 빈 칸으로 두고 Save and Go Back 버튼을 클릭합니다.사용자 또는 그룹이 QueryPie 애플리케이션에 할당되어 추가된 내역을 확인하실 수 있습니다.", + "ordered": false, + "items": [] + } }, { "block_index": 41, @@ -464,7 +669,12 @@ 250, 255 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 42, @@ -475,7 +685,11 @@ 257, 263 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Okta에서 QueryPie 애플리케이션 연동 정보 설정" + } }, { "block_index": 43, @@ -486,7 +700,11 @@ 266, 266 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Okta Admin > Applications > Applications > QueryPie App" + } }, { "block_index": 44, @@ -497,7 +715,13 @@ 268, 273 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Okta 내의 QueryPie 애플리케이션 페이지에서 Sign On 탭으로 이동합니다.Settings 영역의 Edit 버튼을 클릭하여 QueryPie 가 설치된 도메인 주소를 Base URL 항목에 입력하고 저장합니다.Metadata URL에 표기된 주소로 별도 탭에서 접근하여 표시되는 XML 정보를 복사합니다.", + "ordered": false, + "items": [] + } }, { "block_index": 45, @@ -508,7 +732,12 @@ 275, 281 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 46, @@ -519,7 +748,11 @@ 284, 284 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "최소 권한의 Okta API 토큰 발급" + } }, { "block_index": 47, @@ -530,7 +763,12 @@ 286, 291 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "QueryPie-Okta 간 사용자 및 그룹, 그룹 멤버십의 동기화를 위해 Okta Admin API 토큰 발급이 필요합니다. 일반적인 방법으로는 이용하고 계신 Okta 최고관리자(Super Administrator)/읽기권한관리자(Read-Only Administrator) 계정으로 API 토큰을 이하의 방법으로 발급하여 적용하는 방법이 있습니다:", + "anchors": [] + } }, { "block_index": 48, @@ -541,7 +779,13 @@ 293, 295 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Okta 관리자 페이지 좌측 패널에서 Security > API 메뉴로 이동합니다.API 메뉴에서 Tokens 탭으로 이동합니다.Create Token 버튼을 클릭하여 인증 토큰을 생성할 수 있습니다.", + "ordered": false, + "items": [] + } }, { "block_index": 49, @@ -552,7 +796,12 @@ 298, 298 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "다만, 보안 수준 향상을 위해 Okta API 토큰의 권한을 최소한으로 부여하도록 조정해야 하는 경우, 이하의 권한 및 방법에 따라 API 토큰을 생성하실 것을 권장드립니다.", + "anchors": [] + } }, { "block_index": 50, @@ -563,7 +812,11 @@ 300, 301 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Okta Admin Console > Security > Administrators > Roles > Create new role" + } }, { "block_index": 51, @@ -574,7 +827,13 @@ 303, 305 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Okta 관리자 페이지 좌측 패널에서 Directory > People 메뉴로 이동하여 Add Person을 눌러 전용 시스템 연동용 계정을 생성합니다.이미 쿼리파이 연동용으로 사용 가능한 계정이 있다면 본 단계를 넘어갑니다.Okta 관리자 페이지 좌측 패널에서 Security > Administrators 메뉴로 이동하여 Roles탭으로 이동합니다.Create new role을 선택합니다.Role name (예. MinimumAdminRole) 및 Role description을 정의한 뒤, Select Permisions에서 이하의 권한만 체크합니다.UserView users and their detailsGroupView groups and their detailsApplicationView application and their detailsSave role을 눌러 커스텀 롤을 저장합니다.Resources 탭으로 이동합니다.Create new resource set을 선택합니다.이미 할당할 권한 범위 지정을 위해 만들어두신 resource set이 있다면 본 단계를 넘어가 10번 단계를 진행합니다.Name (예. MinimumResources) 및 Description을 정의한 뒤 이하의 범위를 검색하여 지정합니다.User : 쿼리파이 사용자 전부 선택Group : 쿼리파이 사용 그룹 전부 선택Application : 쿼리파이 앱으로 한정Create를 선택하여 생성합니다.Admins 탭으로 이동하여 쿼리파이 연동용 계정에 이하의 권한을 할당합니다.Role: MinimumAdminRole | Resource: MinimumResourcesRole: Read-Only AdministratorAPI 토큰 생성메뉴 접근을 위한 임시 부여쿼리파이 연동용 계정으로 옥타 관리자 콘솔 페이지로 인증 후 접근합니다.Security > API 메뉴에서 Tokens 탭으로 이동합니다.Create Token 버튼을 클릭하여 인증 토큰을 생성하여 이를 보관합니다.이후 다시 초기 작업하였던 관리자 계정으로 접속하여 Security > Administrators > Admins 탭에서 연동용 계정을 편집하여 Read-Only Adminstrator 권한을 회수합니다.", + "ordered": false, + "items": [] + } }, { "block_index": 52, @@ -585,7 +844,11 @@ 307, 307 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "QueryPie에서 Okta 연동 및 동기화 설정" + } }, { "block_index": 53, @@ -596,7 +859,12 @@ 309, 314 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "목록 우측 상단의 + Add 버튼을 누르고 팝업되는 화면에서 Type을 Okta로 선택하면 Okta를 IdP로 추가 할 수 있습니다.", + "anchors": [] + } }, { "block_index": 54, @@ -607,7 +875,11 @@ 316, 343 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Okta 상세설정 (1)" + } }, { "block_index": 55, @@ -618,7 +890,13 @@ 345, 345 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Name : 식별에 용이하도록 적합한 IdP의 이름을 입력합니다.Type: Okta를 선택합니다.Identity Provider Metadata : https://querypie.atlassian.net/wiki/spaces/QM/pages/1454342158/Identity+Providers#Okta%EC%97%90%EC%84%9C-QueryPie-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EC%97%B0%EB%8F%99-%EC%A0%95%EB%B3%B4-%EC%84%A4%EC%A0%95 단계에서 복사한 XML 정보를 Identity Provider Metadata 항목에 붙여넣기합니다.Use SAML Assertion Consumer Service Index : Service Provider에서 여러개의 Endpoint 를 사용하는 경우 ACS Index를 사용하여 endpoint를 각각 지정할 수 있습니다.Entity ID : https://your-domain.com/saml/sp/metadata 의 형식으로 입력합니다. Okta SAML 2.0 설정의 Audience URI (SP Entity ID) 값입니다. ACS Index : 0 ~ 2,147,483,647 사이의 값을 입력합니다. 기본값은 0 입니다.", + "ordered": false, + "items": [] + } }, { "block_index": 56, @@ -629,7 +907,27 @@ 347, 347 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Assertion Consumer Service (ACS): SP(Service provider)에 위치한 특정 Endpoint(URL)로, IdP로부터 SAML Assertion을 수신하여 검증하고 사용자의 로그인을 처리하는 역할을 합니다. Assertion Consumer Service Index의 필요성 및 역할 하나의 SP는 다양한 이유로 여러 개의 ACS URL을 가질 수 있습니다. 이때AssertionConsumerServiceIndex가 필수적인 역할을 합니다. SP가 인증을 요청하는 SAML 메시지(AuthnRequest)에 이 인덱스 값을 포함하여 IdP에 보내면, IdP는 해당 인덱스에 매핑된 ACS URL로 정확하게 SAML 어설션을 전송합니다. 만약 이 인덱스가 명시되지 않으면, 일반적으로 미리 약속된 기본(Default) ACS URL로 어설션을 보내게 됩니다. 이러한 인덱스 기반의 라우팅은 다음과 같은 구체적인 상황에서 매우 유용합니다. 주요 사용 사례 다양한 프로토콜 바인딩(Binding) 지원: SAML 어설션은 HTTP POST, HTTP-Artifact 등 여러 방식으로 전송될 수 있습니다. SP는 각 바인딩 방식에 따라 별도의 ACS URL을 운영할 수 있습니다. 예를 들어, index=\"0\"은 HTTP POST를 위한 ACS URL을, index=\"1\"은 HTTP-Artifact를 위한 ACS URL을 가리키도록 설정할 수 있습니다. 다중 테넌트(Multi-tenant) 아키텍처 지원: 하나의 SaaS 애플리케이션이 여러 고객사(테넌트)를 지원하는 경우, 각 테넌트별로 고유한 ACS URL을 할당할 수 있습니다. 이를 통해 각 고객사의 인증 흐름을 격리하고 맞춤형으로 관리할 수 있습니다. 애플리케이션 내 다른 인증 흐름 구분: 같은 애플리케이션이라도 사용자의 역할이나 접근 경로에 따라 다른 인증 후 처리가 필요할 수 있습니다. 예를 들어, 일반 사용자와 관리자의 로그인 후 리디렉션 페이지를 다르게 설정하고 싶을 때, 각각 다른 ACS URL을 사용하고 이를 인덱스로 구분할 수 있습니다. 동적 또는 특수한 ACS URL 처리: 특정 상황이나 클라이언트의 요구에 따라 동적으로 생성된 ACS URL로 어설션을 받아야 할 때, 인덱스를 통해 정적으로 정의된 여러 URL 중 하나를 선택하도록 유도할 수 있습니다. okta app 설정의 Audience URI(SP Entity ID) Other Requestable SSO URLs 의 Index", + "child_xpaths": [ + "macro-info[3]/p[1]", + "macro-info[3]/p[2]", + "macro-info[3]/p[3]", + "macro-info[3]/p[4]", + "macro-info[3]/p[5]", + "macro-info[3]/p[6]", + "macro-info[3]/ul[1]", + "macro-info[3]/p[7]", + "macro-info[3]/ul[2]", + "macro-info[3]/p[8]", + "macro-info[3]/ul[3]", + "macro-info[3]/p[9]", + "macro-info[3]/ul[4]", + "macro-info[3]/p[10]" + ] + } }, { "block_index": 57, @@ -640,7 +938,11 @@ 349, 354 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Okta 상세설정 (2)" + } }, { "block_index": 58, @@ -651,7 +953,12 @@ 356, 361 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 59, @@ -662,7 +969,13 @@ 363, 364 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "동기화를 설정하고자 하는 경우, “Use Synchronization with the Authentication System” 옵션을 활성화(체크)합니다.API URL: Okta 관리자 페이지 우측 상단의 프로필을 클릭하면 {domain}.okta.com 형식의 URL 을 확인할 수 있습니다.API Token: Okta 관리자 API 토큰을 기입합니다.Application ID: Okta 내에서 2개 이상의 QueryPie App 을 사용할 경우 입력합니다.주기적인 동기화 기능을 사용하고자 할 경우 Replication Frequency 항목에서 Scheduling 을 설정합니다.Additional SettingsMake New Users Inactive by Default : 동기화 시 새로운 사용자를 비활성화 상태로 추가할지 여부를 선택합니다.동기화할 사용자 수가 많거나, 사용자의 인증을 통한 QueryPie 접근을 개별적으로 관리하고자 하는 경우 해당 옵션을 활성화하시기 바랍니다.Use an Attribute for Privilege Revoke : 동기화 시 특정 Attribute에 따라 Privilege를 회수할지 여부를 선택합니다.특정 Attribute의 변경에 의해 자동으로 DAC Privilege를 회수하고자 하는 경우 이 옵션을 활성화하세요.Attribute 입력 필드에 활성화 변경을 감지하려는 Attribute 이름을 입력합니다.Enable Attribute Synchronization : IdP의 사용자 속성과 QueryPie 사용자 속성을 매핑하여 동기화할지 여부를 선택합니다.IdP에서 관리 중인 사용자 속성을 QueryPie 내 Attribute와 자동으로 연동하고자 하는 경우, 해당 옵션을 활성화하시기 바랍니다.옵션 활성화 시, 하단에 Attribute Mapping UI가 표시되며 매핑 작업을 통해 연동할 IdP Attribute와 QueryPie Attribute를 지정할 수 있습니다.단, 해당 기능은 Profile Editor(Admin> General > User Management > Profile Editor)에서 Source Priority가 Inherit from profile source로 설정된 Attribute에 한해 적용됩니다.Allowed User Deletion Rate Threshold : 동기화시 기존 유저가 이 값의 비율 이상으로 삭제되었을 경우에는 동기화를 실패하도록 하는 기능입니다.0.0 ~ 1.0 사이의 값을 입력합니다. (기본값은 0.1)예) 기존 유저가 100명이고, Allowed User Deletion Rate Threshold 0.1 인 경우, 다시 동기화 하였을 때, 삭제된 유저가 10명 이상이면 동기화가 실패합니다.11.3.0 이전 버전에서 동기화 설정된 상태에서, 11.3.0으로 제품을 업그레이드하면 이 값이 1.0 으로 기본 설정됩니다.Dry Run 버튼을 클릭하여 연동 정보가 정상적으로 입력되었는지 확인합니다.Save 버튼을 눌러 저장합니다.", + "ordered": false, + "items": [] + } }, { "block_index": 60, @@ -673,7 +986,15 @@ 366, 366 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Application ID 확인하는 방법 2개 이상의 QueryPie Application 을 사용하는 경우, Okta Admin > Applications 으로 이동하여 QueryPie 앱의 디테일 화면으로 들어가면 상단 URL 에서 위의 스크린샷에 표시된 것과 같은 Application ID 를 확인하실 수 있습니다.", + "child_xpaths": [ + "macro-info[4]/p[1]", + "macro-info[4]/p[2]" + ] + } }, { "block_index": 61, @@ -684,7 +1005,11 @@ 368, 369 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Okta Admin > Applications > QueryPie App 상단 URL" + } }, { "block_index": 62, @@ -695,7 +1020,12 @@ 371, 372 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 63, @@ -706,7 +1036,11 @@ 374, 374 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "QueryPie에서 Okta 로그인" + } }, { "block_index": 64, @@ -717,7 +1051,13 @@ 376, 376 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "General Settings > Users 또는 Groups 메뉴에서 동기화된 사용자 및 그룹을 확인할 수 있습니다.이제 로그인 페이지에서 Login with Okta 버튼을 통해 Okta 계정으로 QueryPie에 로그인할 수 있습니다.", + "ordered": true, + "items": [] + } }, { "block_index": 65, @@ -728,7 +1068,11 @@ 378, 378 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 66, @@ -739,7 +1083,15 @@ 380, 382 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "해당 연동 방식으로는 사용자 및 그룹은 Okta → QueryPie로의 단방향 동기화를 지원합니다. SCIM 프로비저닝 연동까지 구현하고자 하는 경우, [Okta] 프로비저닝 연동 가이드 내 절차대로 대신 진행하여 주시기 바랍니다.", + "child_xpaths": [ + "macro-info[5]/p[1]", + "macro-info[5]/p[2]" + ] + } }, { "block_index": 67, @@ -750,7 +1102,11 @@ 384, 384 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "One Login 연동" + } }, { "block_index": 68, @@ -761,7 +1117,12 @@ 386, 387 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "목록 우측 상단의 + Add 버튼을 누르고 팝업되는 화면에서 Type을 One Login으로 선택하면 One Login를 IdP로 추가 할 수 있습니다.", + "anchors": [] + } }, { "block_index": 69, @@ -772,7 +1133,13 @@ 389, 389 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Name : 식별에 용이하도록 적합한 IdP의 이름을 입력합니다.Type: One Login을 선택합니다.", + "ordered": false, + "items": [] + } }, { "block_index": 70, @@ -783,7 +1150,16 @@ 391, 392 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "One Login SAML Custom Connector 설정 및 Metadata XML 다운로드 OneLogin에 접속한 후 화면 상단의 Applications > Applications 메뉴를 클릭합니다.Add App 버튼을 클릭합니다.검색 영역에 'SAML Custom Connector (Advanced)'을 입력한 후 검색 결과를 클릭합니다.Display Name에 QueryPie에서 확인된 Application Name to be used in OneLogin의 내용을 복사해서 붙여넣고 Audiance (Entity ID), Recipient, ACS (Consumer) URL Validator, ACS (Consumer) URL도 각각 정보를 복사하여 One Login 설정에 붙여 넣습니다.Save 버튼을 눌러 저장합니다. 화면 좌측의 Configuration 메뉴를 선택한 뒤 화면 우측 상단의 More Actions > SAML Metadata를 클릭합니다.다운로드된 XML 파일을 확인합니다. One Login SAML Custom Connector 설정에 대한 자세한 내용은 https://onelogin.service-now.com/support?id=kb_article&sys_id=8a1f3d501b392510c12a41d5ec4bcbcc&kb_category=de885d2187372d10695f0f66cebb351f 의 내용을 참고 바랍니다.", + "child_xpaths": [ + "macro-info[6]/p[1]", + "macro-info[6]/ul[1]", + "macro-info[6]/p[2]" + ] + } }, { "block_index": 71, @@ -794,7 +1170,13 @@ 394, 394 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Identity Provider Metadata : One Login에서 다운로드한 XML 파일의 내용을 복사해서 붙여넣습니다.", + "ordered": false, + "items": [] + } }, { "block_index": 72, @@ -805,7 +1187,11 @@ 396, 396 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "One Login 상세설정 (1)" + } }, { "block_index": 73, @@ -816,7 +1202,13 @@ 398, 403 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "동기화를 설정하고자 하는 경우, “Use Synchronization with the Authentication System” 옵션을 활성화(체크)합니다.주기적인 동기화 기능을 사용하고자 할 경우 Replication Frequency 항목에서 Scheduling 을 설정합니다.", + "ordered": false, + "items": [] + } }, { "block_index": 74, @@ -827,7 +1219,11 @@ 405, 411 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "One Login 상세설정 (2)" + } }, { "block_index": 75, @@ -838,7 +1234,13 @@ 413, 418 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Additional SettingsMake New Users Inactive by Default : 동기화 시 새로운 사용자를 비활성화 상태로 추가할지 여부를 선택합니다.동기화할 사용자 수가 많거나, 사용자의 인증을 통한 QueryPie 접근을 개별적으로 관리하고자 하는 경우 해당 옵션을 활성화하시기 바랍니다.Use an Attribute for Privilege Revoke : 동기화 시 특정 Attribute에 따라 Privilege를 회수할지 여부를 선택합니다.특정 Attribute의 변경에 의해 자동으로 DAC Privilege를 회수하고자 하는 경우 이 옵션을 활성화하세요.Attribute 입력 필드에 활성화 변경을 감지하려는 Attribute 이름을 입력합니다.Enable Attribute Synchronization : IdP의 사용자 속성과 QueryPie 사용자 속성을 매핑하여 동기화할지 여부를 선택합니다.IdP에서 관리 중인 사용자 속성을 QueryPie 내 Attribute와 자동으로 연동하고자 하는 경우, 해당 옵션을 활성화하시기 바랍니다.옵션 활성화 시, 하단에 Attribute Mapping UI가 표시되며 매핑 작업을 통해 연동할 IdP Attribute와 QueryPie Attribute를 지정할 수 있습니다.단, 해당 기능은 Profile Editor(Admin> General > User Management > Profile Editor)에서 Source Priority가 Inherit from profile source로 설정된 Attribute에 한해 적용됩니다.Allowed User Deletion Rate Threshold : 동기화시 기존 유저가 이 값의 비율 이상으로 삭제되었을 경우에는 동기화를 실패하도록 하는 기능입니다.0.0 ~ 1.0 사이의 값을 입력합니다. (기본값은 0.1)예) 기존 유저가 100명이고, Allowed User Deletion Rate Threshold 0.1 인 경우, 다시 동기화 하였을 때, 삭제된 유저가 10명 이상이면 동기화가 실패합니다.11.3.0 이전 버전에서 동기화 설정된 상태에서, 11.3.0으로 제품을 업그레이드하면 이 값이 1.0 으로 기본 설정됩니다.", + "ordered": false, + "items": [] + } }, { "block_index": 76, @@ -849,7 +1251,11 @@ 421, 436 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "SAML 2.0 연동 (주기적 동기화 없는 1회용)" + } }, { "block_index": 77, @@ -860,7 +1266,12 @@ 438, 440 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "주기적인 동기화 없이 1회만 SAML 연동하는 경우 SAML metadata 만 입력하여 설정합니다.", + "anchors": [] + } }, { "block_index": 78, @@ -871,7 +1282,13 @@ 441, 441 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Name : 식별에 용이하도록 적합한 IdP의 이름을 입력합니다.Type : SAML 을 선택합니다.Identity Provider Metadata : IdP에서 확인한 SML metadata XML의 내용을 복사해서 붙여 넣습니다. ", + "ordered": false, + "items": [] + } }, { "block_index": 79, @@ -882,7 +1299,11 @@ 443, 448 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 80, @@ -893,7 +1314,12 @@ 451, 451 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "< 참고 > ", + "anchors": [] + } }, { "block_index": 81, @@ -904,7 +1330,11 @@ 453, 454 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Custom Identity Provider" + } }, { "block_index": 82, @@ -915,7 +1345,12 @@ 456, 458 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "Custom Identity Provider는 인증 API서버를 사용하는 특수한 경우에만 사용합니다.", + "anchors": [] + } }, { "block_index": 83, @@ -926,7 +1361,13 @@ 460, 462 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Name : 식별에 용이하도록 적합한 IdP의 이름을 입력합니다.Type : Custom Identity Provider 를 선택합니다.API URL : API서버의 End-point URL을 입력합니다. 사용자 정보 동기화를 실행하려는 경우 Use Synchronization with the Authentication System 옵션을 활성화합니다.", + "ordered": false, + "items": [] + } }, { "block_index": 84, @@ -937,7 +1378,11 @@ 463, 463 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 85, @@ -948,7 +1393,13 @@ 465, 465 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Additional SettingsMake New Users Inactive by Default : 동기화 시 새로운 사용자를 비활성화 상태로 추가할지 여부를 선택합니다.동기화할 사용자 수가 많거나, 사용자의 인증을 통한 QueryPie 접근을 개별적으로 관리하고자 하는 경우 해당 옵션을 활성화하시기 바랍니다.Use an Attribute for Privilege Revoke : 동기화 시 특정 Attribute에 따라 Privilege를 회수할지 여부를 선택합니다.특정 Attribute의 변경에 의해 자동으로 DAC Privilege를 회수하고자 하는 경우 이 옵션을 활성화하세요.Attribute 입력 필드에 활성화 변경을 감지하려는 Attribute 이름을 입력합니다.Enable Attribute Synchronization : IdP의 사용자 속성과 QueryPie 사용자 속성을 매핑하여 동기화할지 여부를 선택합니다.IdP에서 관리 중인 사용자 속성을 QueryPie 내 Attribute와 자동으로 연동하고자 하는 경우, 해당 옵션을 활성화하시기 바랍니다.옵션 활성화 시, 하단에 Attribute Mapping UI가 표시되며 매핑 작업을 통해 연동할 IdP Attribute와 QueryPie Attribute를 지정할 수 있습니다.단, 해당 기능은 Profile Editor(Admin> General > User Management > Profile Editor)에서 Source Priority가 Inherit from profile source로 설정된 Attribute에 한해 적용됩니다.Allowed User Deletion Rate Threshold : 동기화시 기존 유저가 이 값의 비율 이상으로 삭제되었을 경우에는 동기화를 실패하도록 하는 기능입니다.0.0 ~ 1.0 사이의 값을 입력합니다. (기본값은 0.1)예) 기존 유저가 100명이고, Allowed User Deletion Rate Threshold 0.1 인 경우, 다시 동기화 하였을 때, 삭제된 유저가 10명 이상이면 동기화가 실패합니다.11.3.0 이전 버전에서 동기화 설정된 상태에서, 11.3.0으로 제품을 업그레이드하면 이 값이 1.0 으로 기본 설정됩니다.", + "ordered": false, + "items": [] + } }, { "block_index": 86, @@ -959,7 +1410,12 @@ 467, 467 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } } ], "separators": [ diff --git a/confluence-mdx/tests/testcases/1844969501/expected.roundtrip.json b/confluence-mdx/tests/testcases/1844969501/expected.roundtrip.json index 51b709cdb..e36e6d537 100644 --- a/confluence-mdx/tests/testcases/1844969501/expected.roundtrip.json +++ b/confluence-mdx/tests/testcases/1844969501/expected.roundtrip.json @@ -1,5 +1,5 @@ { - "schema_version": "2", + "schema_version": "3", "page_id": "1844969501", "mdx_sha256": "61415a45c9159e6763e2fb494acd8562fd96d3862d85d3d4b79f648374ae28da", "source_xhtml_sha256": "94e15b644935393ee8893708987d235462108a6e137afbf178f44f1411ac08a3", @@ -13,7 +13,11 @@ 6, 6 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 1, @@ -24,7 +28,11 @@ 8, 8 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "공통 리소스" + } }, { "block_index": 2, @@ -35,7 +43,12 @@ 10, 10 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "모든 QueryPie 고객이 이용할 수 있는 자료입니다.", + "anchors": [] + } }, { "block_index": 3, @@ -46,7 +59,11 @@ 12, 12 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Documentation" + } }, { "block_index": 4, @@ -57,7 +74,12 @@ 14, 14 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "이 웹사이트, docs.querypie.com 에서 다음 자료를 확인할 수 있습니다:", + "anchors": [] + } }, { "block_index": 5, @@ -68,7 +90,13 @@ 16, 19 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "이용자 매뉴얼관리자 매뉴얼API Reference릴리스 노트", + "ordered": false, + "items": [] + } }, { "block_index": 6, @@ -79,7 +107,12 @@ 21, 21 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "기술지원을 위한 에서 상세한 기술지원 자료를 확인할 수 있습니다.", + "anchors": [] + } }, { "block_index": 7, @@ -90,7 +123,13 @@ 23, 27 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "", + "ordered": false, + "items": [] + } }, { "block_index": 8, @@ -101,7 +140,11 @@ 29, 29 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Product Demo - YouTube" + } }, { "block_index": 9, @@ -112,7 +155,12 @@ 31, 31 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "Product Demo - YouTube 채널에서 QueryPie ACP 제품의 여러 기능을 영상으로 살펴볼 수 있습니다.", + "anchors": [] + } }, { "block_index": 10, @@ -123,7 +171,13 @@ 33, 36 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "DAC: 데이터베이스 접근 제어 기능 튜토리얼SAC: 서버 접근 제어 기능 튜토리얼KAC: Kubernetes 접근 제어 기능 튜토리얼General: SSO 통합, 감사 로그, BI 도구 연동 (Tableau, Redash)", + "ordered": false, + "items": [] + } }, { "block_index": 11, @@ -134,7 +188,11 @@ 38, 38 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "커뮤니티 지원" + } }, { "block_index": 12, @@ -145,7 +203,12 @@ 40, 40 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "대상: Community Edition, Standard Edition 을 이용하는 고객", + "anchors": [] + } }, { "block_index": 13, @@ -156,7 +219,11 @@ 42, 42 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "GitHub Discussion" + } }, { "block_index": 14, @@ -167,7 +234,12 @@ 44, 44 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "설치 및 사용 중 궁금한 점을 GitHub Discussion 에서 문의하거나, 다른 사용자들과 경험을 공유할 수 있습니다.", + "anchors": [] + } }, { "block_index": 15, @@ -178,7 +250,12 @@ 0, 0 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } } ], "separators": [ diff --git a/confluence-mdx/tests/testcases/1911652402/expected.roundtrip.json b/confluence-mdx/tests/testcases/1911652402/expected.roundtrip.json index 522174d8f..993aa5b41 100644 --- a/confluence-mdx/tests/testcases/1911652402/expected.roundtrip.json +++ b/confluence-mdx/tests/testcases/1911652402/expected.roundtrip.json @@ -1,5 +1,5 @@ { - "schema_version": "2", + "schema_version": "3", "page_id": "1911652402", "mdx_sha256": "e619817bf71ddde9c08b75428dabfd343fff13c100d88ed41f1135fc1e76e5ea", "source_xhtml_sha256": "dd7e858544cbe4c994c4844b5d55812b3effdaeb21538e4ff12e0f014e86cbc9", @@ -13,7 +13,12 @@ 6, 6 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "QueryPie ACP 제품을 설치하는 방법은 Container 실행방식, 설치 도우미 프로그램의 버전에 따라, 안내 문서가 구분되어 제공됩니다.", + "anchors": [] + } }, { "block_index": 1, @@ -24,7 +29,11 @@ 8, 8 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Compose Tool 로 Container 를 실행하기" + } }, { "block_index": 2, @@ -35,7 +44,12 @@ 10, 10 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "일반적인 설치, 실행 방법이며, 제조사에서 권장하는 설치 방법입니다. Docker Compose 를 활용하여, Container Image 를 내려받고, 실행, 종료하는 방식입니다.", + "anchors": [] + } }, { "block_index": 3, @@ -46,7 +60,12 @@ 12, 13 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "이 경우, 설치 도우미 프로그램의 버전에 따라, 두 가지 설치 방식이 제공됩니다.", + "anchors": [] + } }, { "block_index": 4, @@ -57,7 +76,11 @@ 15, 15 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "setup.v2.sh 로 설치하기" + } }, { "block_index": 5, @@ -68,7 +91,12 @@ 17, 17 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "2025년 8월 출시된 QueryPie ACP Community Edition 의 출시와 함께 최종 고객사와 파트너 엔지니어에게 제공되는 설치 도우미 프로그램입니다. Docker Hub 에 공개된 Container Image 를 내려받아 설치하는 방식입니다.", + "anchors": [] + } }, { "block_index": 6, @@ -79,7 +107,12 @@ 19, 20 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "설치 과정이 자동화되어, 처음 설치하는 이용자가 제품을 쉽게 설치할 수 있습니다. setup.v2.sh 가 실제로 실행하는 설치 과정은 setup.sh 의 설치 방식과 동등합니다. setup.v2.sh 는 그 과정이 자동화되었고, Version 10 등 구버전의 설치를 지원하지 않는다는 차이가 있습니다. 더 상세한 차이점은 이 문서를 참조하세요: ", + "anchors": [] + } }, { "block_index": 7, @@ -90,7 +123,12 @@ 22, 25 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "Apple Silicon 을 사용하는 macOS 환경의 설치를 지원합니다.", + "anchors": [] + } }, { "block_index": 8, @@ -101,7 +139,12 @@ 27, 27 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "자세한 설치 방법은 이 문서를 참조하세요: ", + "anchors": [] + } }, { "block_index": 9, @@ -112,7 +155,11 @@ 29, 29 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "setup.sh 로 설치하기" + } }, { "block_index": 10, @@ -123,7 +170,12 @@ 31, 31 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "QueryPie ACP Version 9 시절부터 파트너 엔지니어에게 제공된 설치 도우미 프로그램입니다. Harbor 라는 Container Registry 에 계정을 발급받아 Container Image 를 내려받을 수 있습니다.", + "anchors": [] + } }, { "block_index": 11, @@ -134,7 +186,12 @@ 33, 34 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "자세한 설치 방법은 이 문서를 참조하세요: ", + "anchors": [] + } }, { "block_index": 12, @@ -145,7 +202,11 @@ 36, 36 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Kubernetes 환경에 설치하기" + } }, { "block_index": 13, @@ -156,7 +217,12 @@ 38, 38 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "QueryPie ACP 는 Container 환경에서 작동하는 소프트웨어 제품입니다. Compose Tool 을 이용해 설치, 실행하는 것이 일반적이지만, Kubernetes 환경에서도 매끄럽게 작동합니다.", + "anchors": [] + } }, { "block_index": 14, @@ -167,7 +233,12 @@ 40, 41 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "Kubenetes 환경에서 제품을 설치하는 경우, AWS EKS, GCP GKE 등 Managed Kubernetes Cluster 와 Self-Hosted Cluster, 모두 사용 가능합니다.", + "anchors": [] + } }, { "block_index": 15, @@ -178,7 +249,13 @@ 43, 43 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "AWS EKS 환경 : Helm Chart : https://github.com/chequer-io/querypie-deployment ", + "ordered": false, + "items": [] + } }, { "block_index": 16, @@ -189,7 +266,12 @@ 45, 46 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } } ], "separators": [ diff --git a/confluence-mdx/tests/testcases/544112828/expected.roundtrip.json b/confluence-mdx/tests/testcases/544112828/expected.roundtrip.json index 00d2ff613..0c3b4a639 100644 --- a/confluence-mdx/tests/testcases/544112828/expected.roundtrip.json +++ b/confluence-mdx/tests/testcases/544112828/expected.roundtrip.json @@ -1,5 +1,5 @@ { - "schema_version": "2", + "schema_version": "3", "page_id": "544112828", "mdx_sha256": "1944af0092b956a4eb535f915e514027ebe336c343a52dc02ee389828e799699", "source_xhtml_sha256": "b31f4552fd924f4299dfd7dc6edea8f00bfefbf403f5f96011e17a553b24ff67", @@ -13,7 +13,11 @@ 8, 8 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Overview" + } }, { "block_index": 1, @@ -24,7 +28,12 @@ 10, 10 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "QueryPie Agent를 설치하면, DataGrip, DBeaver와 같은 SQL Client, iTerm/SecureCRT와 같은 SSH Client, Lens, k9s와 같은 3rd Party 애플리케이션을 사용할 수 있습니다.", + "anchors": [] + } }, { "block_index": 2, @@ -35,7 +44,11 @@ 12, 12 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 3, @@ -46,7 +59,12 @@ 15, 15 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 4, @@ -57,7 +75,11 @@ 17, 17 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "에이전트 앱 다운로드 및 실행하기" + } }, { "block_index": 5, @@ -68,7 +90,12 @@ 19, 24 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "1. QueryPie 로그인 후 우측 상단 프로필을 클릭하여 Agent Download 버튼을 클릭합니다. ", + "anchors": [] + } }, { "block_index": 6, @@ -79,7 +106,11 @@ 26, 26 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "QueryPie Web > 프로필 메뉴" + } }, { "block_index": 7, @@ -90,7 +121,12 @@ 28, 33 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "2. QueryPie Agent Downloads 팝업창이 실행되면 Step 1에서 사용 중인 PC 운영체제에 맞는 설치 파일을 다운로드한 후 Step 3에 있는 QueryPie URL을 복사해 둡니다. ", + "anchors": [] + } }, { "block_index": 8, @@ -101,7 +137,11 @@ 35, 36 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "QueryPie Web > Agent Downloads 팝업창" + } }, { "block_index": 9, @@ -112,7 +152,14 @@ 37, 37 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "QueryPie Agent는 Mac, Windows, Linux OS를 지원합니다.", + "child_xpaths": [ + "macro-info[1]/p[1]" + ] + } }, { "block_index": 10, @@ -123,7 +170,12 @@ 39, 39 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "3. 다운로드받은 QueryPie Agent 설치 프로그램을 실행하여 설치를 완료합니다. ", + "anchors": [] + } }, { "block_index": 11, @@ -134,7 +186,11 @@ 41, 46 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Mac OS 설치 프로그램" + } }, { "block_index": 12, @@ -145,7 +201,12 @@ 48, 48 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "4. 설치 완료된 QueryPie Agent를 실행합니다. QueryPie Host 입력란에 미리 복사해뒀던 QueryPie URL을 입력하고 Next 버튼을 클릭하면 로그인 화면으로 진입하게 됩니다. ", + "anchors": [] + } }, { "block_index": 13, @@ -156,7 +217,11 @@ 49, 49 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Agent > QueryPie Host 입력" + } }, { "block_index": 14, @@ -167,7 +232,12 @@ 51, 56 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 15, @@ -178,7 +248,11 @@ 59, 59 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "QueryPie Agent에 로그인하기" + } }, { "block_index": 16, @@ -189,7 +263,12 @@ 61, 61 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "1. Agent 앱 내 로그인 화면에서 Login 버튼을 클릭합니다.", + "anchors": [] + } }, { "block_index": 17, @@ -200,7 +279,11 @@ 63, 65 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 18, @@ -211,7 +294,12 @@ 67, 67 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "2. 웹 브라우저가 열리면, 로그인 페이지에서 인증정보를 입력하고, Continue 버튼을 클릭합니다.", + "anchors": [] + } }, { "block_index": 19, @@ -222,7 +310,11 @@ 69, 74 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "QueryPie Web > Agent Login Page" + } }, { "block_index": 20, @@ -233,7 +325,12 @@ 76, 76 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "3. 로그인을 성공하면 아래와 같이 로그인 성공 화면이 표시되며 이후 Agent로 돌아갑니다.", + "anchors": [] + } }, { "block_index": 21, @@ -244,7 +341,11 @@ 78, 83 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "QueryPie Web > Agent Login Success Page" + } }, { "block_index": 22, @@ -255,7 +356,12 @@ 85, 85 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "4. Agent 열기를 명시적으로 수행하여 인증정보를 Agent로 전달합니다.", + "anchors": [] + } }, { "block_index": 23, @@ -266,7 +372,11 @@ 87, 92 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Chrome - Agent App 열기 모달" + } }, { "block_index": 24, @@ -277,7 +387,11 @@ 94, 94 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "에이전트로 데이터베이스 접속하기" + } }, { "block_index": 25, @@ -288,7 +402,12 @@ 96, 96 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "1. 로그인이 정상적으로 완료되면 Agent 앱 내 Databases 탭에서 권한 있는 커넥션들의 접속 정보를 확인할 수 있습니다.접속할 커넥션에 할당된 Port 를 클릭하면, 해당 커넥션의 Proxy Credentials 정보를 확인할 수 있습니다.", + "anchors": [] + } }, { "block_index": 26, @@ -299,7 +418,11 @@ 98, 103 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Agent > DB Connection Information" + } }, { "block_index": 27, @@ -310,7 +433,12 @@ 105, 105 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "2. 위의 접속 정보를 3rd Party 클라이언트에 입력하면 DB 커넥션 접속이 가능합니다.", + "anchors": [] + } }, { "block_index": 28, @@ -321,7 +449,11 @@ 107, 112 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "3rd Party Client를 이용한 DB 커넥션 접속" + } }, { "block_index": 29, @@ -332,7 +464,12 @@ 115, 115 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 30, @@ -343,7 +480,11 @@ 117, 117 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "에이전트를 통한 서버 접속" + } }, { "block_index": 31, @@ -354,7 +495,12 @@ 119, 119 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "로그인이 정상적으로 완료되면 Agent 앱 내 Server 탭에서 권한 있는 서버를 확인할 수 있습니다.", + "anchors": [] + } }, { "block_index": 32, @@ -365,7 +511,11 @@ 121, 122 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "1. 서버 역할 선택하기" + } }, { "block_index": 33, @@ -376,7 +526,13 @@ 124, 129 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "사용자 프로필 영역 하단의 Role 버튼을 클릭하여 원하는 역할을 고르고 OK 버튼을 클릭하세요.Default 역할 선택시, Workflow > Server Access Request 요청에 의해 할당받은 서버 권한을 사용합니다. ", + "ordered": false, + "items": [] + } }, { "block_index": 34, @@ -387,7 +543,11 @@ 131, 132 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Agent > Server > Select a Role" + } }, { "block_index": 35, @@ -398,7 +558,14 @@ 133, 133 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "역할이 두 개 이상이라면, Agent 로그인 후 Server 기능 사용을 위해 역할 선택을 먼저 완료해야 합니다.", + "child_xpaths": [ + "macro-info[2]/p[1]" + ] + } }, { "block_index": 36, @@ -409,7 +576,11 @@ 135, 135 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "2. Agent로 서버 접속하기" + } }, { "block_index": 37, @@ -420,7 +591,13 @@ 137, 137 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "접속할 서버를 우클릭 후 Open Connection with 메뉴를 선택하여, 사용하려는 터미널 툴을 선택합니다.", + "ordered": false, + "items": [] + } }, { "block_index": 38, @@ -431,7 +608,11 @@ 139, 144 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Agent > Server > Open Connection with" + } }, { "block_index": 39, @@ -442,7 +623,13 @@ 146, 147 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "이후 해당 서버에 접속 가능한 계정이 여러개라면, Account 선택창이 열립니다. 사용하려는 계정을 선택하고 필요시 비밀번호를 입력한 뒤, OK 버튼을 클릭하여 세션을 엽니다.", + "ordered": false, + "items": [] + } }, { "block_index": 40, @@ -453,7 +640,11 @@ 149, 154 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Agent > Server > Open New Session" + } }, { "block_index": 41, @@ -464,7 +655,11 @@ 156, 156 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "3. Seamless SSH 설정하기" + } }, { "block_index": 42, @@ -475,7 +670,12 @@ 158, 159 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "Seamless SSH란 기존 터미널 사용성을 그대로 유지하면서 QueryPie를 통해 서버에 접속할 수 있는 기능입니다. 다음음의 방법으로 .ssh 폴더에 config 파일을 생성하여 손쉽게 seamless SSH 설정이 가능합니다.", + "anchors": [] + } }, { "block_index": 43, @@ -486,7 +686,12 @@ 161, 161 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "1) 터미널을 열고, .ssh 폴더로 이동합니다.", + "anchors": [] + } }, { "block_index": 44, @@ -497,7 +702,11 @@ 162, 164 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "code", + "old_plain_text": "wide760" + } }, { "block_index": 45, @@ -508,7 +717,12 @@ 166, 166 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "2) ssh 폴더에서 config 파일을 생성하기 위해 vi 에디터를 엽니다.", + "anchors": [] + } }, { "block_index": 46, @@ -519,7 +733,11 @@ 167, 169 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "code", + "old_plain_text": "wide760" + } }, { "block_index": 47, @@ -530,7 +748,12 @@ 171, 171 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "3) 아래의 내용을 입력 후, wq 키를 입력하여 vi 에디터를 나옵니다.", + "anchors": [] + } }, { "block_index": 48, @@ -541,7 +764,11 @@ 172, 177 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "code", + "old_plain_text": "wide760" + } }, { "block_index": 49, @@ -552,7 +779,14 @@ 179, 181 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "config 파일 작성 시 Seamless SSH 설정하고자 하는 서버마다 서버 이름, URL, 포트를 입력함으로써 서버를 특정합니다. 서버 간에 URL, 포트가 겹치지 않는 경우 아래와 같이 입력하여도 접속이 가능합니다.", + "child_xpaths": [ + "macro-info[3]/p[1]" + ] + } }, { "block_index": 50, @@ -563,7 +797,12 @@ 182, 185 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "4) 이상으로 설정이 완료됩니다. Agent > Server 탭에서 역할을 선택하면 기존 ssh 명령어로 서버에 접속할 수 있습니다.", + "anchors": [] + } }, { "block_index": 51, @@ -574,7 +813,11 @@ 186, 186 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "code", + "old_plain_text": "wide760" + } }, { "block_index": 52, @@ -585,7 +828,12 @@ 188, 189 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 53, @@ -596,7 +844,11 @@ 190, 192 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "에이전트를 통한 쿠버네티스 접속 " + } }, { "block_index": 54, @@ -607,7 +859,11 @@ 195, 195 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 55, @@ -618,7 +874,13 @@ 197, 199 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "권한을 부여 받은 사용자는 에이전트 실행 시 현재 정책에 따른 kubeconfig 파일이 자동으로 수신됩니다.이를 통해 Kubernetes Client(kubectl, lens, k9s 등) 툴로 Kubernetes API 리소스에 접근할 수 있습니다.에이전트에서는 접근 가능한 클러스트 리스트를 표시하며, 각 클러스터에 적용된 정책을 확인할 수 있습니다.또한 설정 메뉴를 통해 kubeconfig파일의 위치를 확인 및 수정할 수 있습니다.", + "ordered": false, + "items": [] + } }, { "block_index": 56, @@ -629,7 +891,11 @@ 201, 204 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "1. Kubernetes 역할 선택하기" + } }, { "block_index": 57, @@ -640,7 +906,12 @@ 206, 206 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "사용자 프로필 영역 하단의 Role 버튼을 클릭하면 역할 선택 모달이 열립니다. 원하는 역할을 고르고 OK 버튼을 클릭하세요.", + "anchors": [] + } }, { "block_index": 58, @@ -651,7 +922,11 @@ 208, 209 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Agent > Kubernetes > Select a Role" + } }, { "block_index": 59, @@ -662,7 +937,14 @@ 211, 216 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "역할이 두 개 이상이라면, Agent 로그인 후 Kubernetes 기능 사용을 위해 역할 선택을 먼저 완료해야 합니다.", + "child_xpaths": [ + "macro-info[4]/p[1]" + ] + } }, { "block_index": 60, @@ -673,7 +955,12 @@ 218, 219 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 61, @@ -684,7 +971,11 @@ 220, 220 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "2. Policy 조회하기" + } }, { "block_index": 62, @@ -695,7 +986,12 @@ 223, 223 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "선택된 역할에 따라 접근 가능한 리소스가 표시됩니다. 각 클러스터 우측의 🔍 버튼을 누르면 Policy Information 팝업창이 나타나며 이곳에서 해당 클러스터에 적용된 정책 목록을 자세히 확인할 수 있습니다.", + "anchors": [] + } }, { "block_index": 63, @@ -706,7 +1002,11 @@ 225, 226 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Agent > Policy Information" + } }, { "block_index": 64, @@ -717,7 +1017,11 @@ 228, 233 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "3. Kubeconfig 경로 설정하기" + } }, { "block_index": 65, @@ -728,7 +1032,12 @@ 235, 235 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "Agent 설정 메뉴에서 KubeConfig Path 버튼을 클릭하여 Kubeconfig 경로 설정 모달을 엽니다.", + "anchors": [] + } }, { "block_index": 66, @@ -739,7 +1048,11 @@ 237, 237 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Agent > Settings > Configure Kubeconfig Path" + } }, { "block_index": 67, @@ -750,7 +1063,13 @@ 239, 244 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Path Configuration : Kubeconfig 파일 저장 경로를 사용자가 지정할 수 있습니다.기본값은 $HOME/.kube/querypie-kubeconfig 입니다.경로 필드 우측 Upload 버튼을 누르면 로컬 파일 경로를 지정할 수 있는 팝업이 나타납니다. ", + "ordered": false, + "items": [] + } }, { "block_index": 68, @@ -761,7 +1080,12 @@ 246, 248 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 69, @@ -772,7 +1096,11 @@ 251, 256 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "경로 지정 팝업" + } }, { "block_index": 70, @@ -783,7 +1111,13 @@ 258, 260 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Where : 우측의 🔽 버튼을 클릭하여 원하는 폴더를 지정합니다.Save As : Kubeconfig 파일의 이름을 입력합니다. 변경하지 않으면 기본값으로 적용됩니다.Command Line : KUBECONFIG 환경 변수를 선언하기 위한 커맨드 라인을 제공합니다.", + "ordered": false, + "items": [] + } }, { "block_index": 71, @@ -794,7 +1128,12 @@ 262, 262 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "1) KUBECONFIG 환경 변수를 최초 설정하는 경우, 명령 줄 내의 디폴트 \"${KUBECONFIG}\" 값을 사용 전에 \"${HOME}/.kube/config\"로 변경해야 합니다. ", + "anchors": [] + } }, { "block_index": 72, @@ -805,7 +1144,11 @@ 263, 265 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "code", + "old_plain_text": "wide760" + } }, { "block_index": 73, @@ -816,7 +1159,12 @@ 267, 267 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "2) QueryPie에서 제공된 사용자 지정 kubeconfig 경로를 변경한 경우 새 경로로 환경 변수를 다시 선언해야 합니다.", + "anchors": [] + } }, { "block_index": 74, @@ -827,7 +1175,11 @@ 268, 270 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "code", + "old_plain_text": "wide760" + } }, { "block_index": 75, @@ -838,7 +1190,12 @@ 272, 272 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "3) 보다 자세한 kubeconfig에 대한 내용은 다음 링크를 참조해주시기 바랍니다.", + "anchors": [] + } }, { "block_index": 76, @@ -849,7 +1206,13 @@ 274, 275 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "참고: kubeconfig 파일 병합참고: KUBECONFIG 환경 변수 설정", + "ordered": false, + "items": [] + } }, { "block_index": 77, @@ -860,7 +1223,12 @@ 277, 277 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "4) Copy 로 커맨드라인을 복사하여 클라이언트에 선언하고 컨텍스트를 접근 가능한 클러스터로 스위칭할 수 있습니다 ", + "anchors": [] + } }, { "block_index": 78, @@ -871,7 +1239,11 @@ 278, 280 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "code", + "old_plain_text": "wide760" + } }, { "block_index": 79, @@ -882,7 +1254,12 @@ 283, 283 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 80, @@ -893,7 +1270,11 @@ 285, 285 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "QueryPie Agent 세팅 초기화하기" + } }, { "block_index": 81, @@ -904,7 +1285,12 @@ 286, 286 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "세팅을 초기화하면 입력하였던 QueryPie Host 정보가 초기화되어 다시 입력할 수 있게 됩니다. ", + "anchors": [] + } }, { "block_index": 82, @@ -915,7 +1301,11 @@ 288, 288 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Agent 내 설정 메뉴에서 초기화" + } }, { "block_index": 83, @@ -926,7 +1316,12 @@ 290, 295 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "프로필 영역 우측 ⚙️ 버튼을 클릭하여 설정 메뉴를 엽니다. Reset All Settings 버튼을 클릭합니다.", + "anchors": [] + } }, { "block_index": 84, @@ -937,7 +1332,11 @@ 298, 298 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Agent > Settings" + } }, { "block_index": 85, @@ -948,7 +1347,12 @@ 300, 300 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 86, @@ -959,7 +1363,11 @@ 302, 307 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "(Mac) 메뉴 막대 내 앱 메뉴에서 초기화" + } }, { "block_index": 87, @@ -970,7 +1378,12 @@ 0, 0 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "메뉴 막대에서 QueryPie Agent 아이콘을 클릭하여 앱 메뉴를 엽니다. Reset All Settings 버튼을 클릭합니다.", + "anchors": [] + } }, { "block_index": 88, @@ -981,7 +1394,11 @@ 0, 0 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Agent > App menu" + } }, { "block_index": 89, @@ -992,7 +1409,12 @@ 0, 0 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } } ], "separators": [ diff --git a/confluence-mdx/tests/testcases/544113141/expected.roundtrip.json b/confluence-mdx/tests/testcases/544113141/expected.roundtrip.json index 4cd39cd35..14004223e 100644 --- a/confluence-mdx/tests/testcases/544113141/expected.roundtrip.json +++ b/confluence-mdx/tests/testcases/544113141/expected.roundtrip.json @@ -1,5 +1,5 @@ { - "schema_version": "2", + "schema_version": "3", "page_id": "544113141", "mdx_sha256": "f525580771fff8d47c148392987504b03903936fe26c5ecfe747dcfc4d6257c2", "source_xhtml_sha256": "4ae9cf549fddbc8f6fa3a9164f51a99fb22474f470f2c8f8a6a71b48830927f6", @@ -13,7 +13,11 @@ 6, 6 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Overview" + } }, { "block_index": 1, @@ -24,7 +28,12 @@ 8, 8 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "조직에서 관리하는 DB 커넥션 연결 및 연결해제 이력을 기록합니다.", + "anchors": [] + } }, { "block_index": 2, @@ -35,7 +44,11 @@ 10, 10 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "DB Access History 조회하기 " + } }, { "block_index": 3, @@ -46,7 +59,11 @@ 12, 12 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Administrator > Audit > Databases > DB Access History" + } }, { "block_index": 4, @@ -57,7 +74,13 @@ 14, 19 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Administrator > Audit > Databases > DB Access History 메뉴로 이동합니다.현재 월 기준으로 로그가 내림차순으로 조회됩니다.테이블 좌측 상단의 검색란을 통해 이하의 조건으로 검색이 가능합니다.Name : 사용자 이름Error Message : 접근 에러 메시지 내 구문 Client IP : 사용자 IP검색 필드 우측 필터 버튼을 클릭하여 AND/OR 조건으로 이하의 필터링이 가능합니다.Connection Name : 접속한 DB 커넥션명Database Type : 데이터베이스 유형Action Type : Connect / Disconnect 여부Result : 접속 성공/실패 여부Action At : 접속 시점테이블 우측 상단의 새로고침 버튼을 통해 로그 목록을 최신화할 수 있습니다.테이블에서 이하의 컬럼 정보를 제공합니다:No : 이벤트 식별 번호Action At : 서버 접속 시도 일시Action Type : Connect / Disconnect 여부Result : 접속 성공/실패 여부:check_mark: Success:cross_mark: FailureName : 대상 사용자 이름Email : 대상 사용자 이메일Cloud Provider : 대상 클라우드 프로바이더명Connection Name : 대상 DB 커넥션명Database Type : 대상 데이터베이스 유형Privilege Name : 사용자 접속 권한 Client IP : 사용자 클라이언트 IP 주소Replication Type : DB Replication 유형DB Host : 접속한 DB 호스트DB User : DB 사용자 IDDB Name : DB명Client Name : 이용 클라이언트명 (DataGrip 등)Error Message : 접속 실패 등 특이사항에 대한 기록Connected From : 접속 방식", + "ordered": true, + "items": [] + } }, { "block_index": 5, @@ -68,7 +91,11 @@ 21, 57 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "DB Access History 상세 내역 조회하기" + } }, { "block_index": 6, @@ -79,7 +106,12 @@ 59, 59 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "각 행을 클릭하면 세부 정보 조회가 가능합니다. ", + "anchors": [] + } }, { "block_index": 7, @@ -90,7 +122,11 @@ 61, 61 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Administrator > Audit > Databases > DB Access History > DB Access History Details" + } }, { "block_index": 8, @@ -101,7 +137,13 @@ 63, 68 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "우측 드로워에는 이하의 정보를 노출합니다:Result : 접속 성공/실패 여부:check_mark: Success:cross_mark: FailureName : 대상 사용자 이름Action At : 접속 시점Action Type : Connect / Disconnect 여부Privilege Name : 사용자 접속 권한 Connection Name : 대상 DB 커넥션명Host Name : 접속 실행 호스트명Connected From : 접속 방식Client Name : 이용 클라이언트명 (DataGrip 등)Client IP : 사용자 클라이언트 IP 주소Replication Type : DB Replication 유형Database Type : 대상 데이터베이스 유형DB Host : 접속한 DB 호스트DB Name : DB명DB User : DB 사용자 IDError Message : 접속 실패 등 특이사항에 대한 기록", + "ordered": false, + "items": [] + } } ], "separators": [ diff --git a/confluence-mdx/tests/testcases/544145591/expected.roundtrip.json b/confluence-mdx/tests/testcases/544145591/expected.roundtrip.json index e700194a9..9373566b8 100644 --- a/confluence-mdx/tests/testcases/544145591/expected.roundtrip.json +++ b/confluence-mdx/tests/testcases/544145591/expected.roundtrip.json @@ -1,6 +1,6 @@ { - "schema_version": "2", - "page_id": "", + "schema_version": "3", + "page_id": "544145591", "mdx_sha256": "b6183884380b9e270dca5ed67ccfb8f144052850236c7c2b1ad291b2acf5dee7", "source_xhtml_sha256": "cbc830bacea9231f83d9d4414a69f79e32a0fb414596a648fd9326688b5598c3", "blocks": [ @@ -13,7 +13,11 @@ 8, 8 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Overview" + } }, { "block_index": 1, @@ -24,7 +28,12 @@ 10, 10 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "General 페이지에서는 QueryPie 전체의 기본 설정을 변경할 수 있습니다. ", + "anchors": [] + } }, { "block_index": 2, @@ -35,7 +44,11 @@ 12, 12 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Administrator > General > Company Management > General" + } }, { "block_index": 3, @@ -46,7 +59,11 @@ 14, 19 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 4, @@ -57,7 +74,11 @@ 21, 21 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "기본 설정" + } }, { "block_index": 5, @@ -68,7 +89,13 @@ 23, 29 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Company Name : 프로필 영역 등에 표시할 회사 이름을 입력합니다.Web Base URL : QueryPie 서비스에 접근하기 위해 사용하는 기본 도메인 주소Timezone : QueryPie 전체에서 표시될 날짜 및 시각 정보에 적용될 타임존을 설정합니다.Date Format : 날짜 표기 방식을 지정합니다.Default Display Language : QueryPie의 기본 언어 설정을 설정합니다. (디폴트: 영어) Audit Log Retention Period : 감사로그 보존 주기를 설정합니다. 현재는 5년으로 고정되어 있습니다.Go to System Properties : 클릭 시 시스템 설정값 내역을 확인할 수 있습니다. ", + "ordered": false, + "items": [] + } }, { "block_index": 6, @@ -79,7 +106,12 @@ 32, 32 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 7, @@ -90,7 +122,11 @@ 34, 34 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "System Properties" + } }, { "block_index": 8, @@ -101,7 +137,12 @@ 36, 41 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "System Properties 페이지에서는 QueryPie 시스템 컴포넌트들의 설정값을 확인하고, 파일로 저장할 수 있습니다.", + "anchors": [] + } }, { "block_index": 9, @@ -112,7 +153,11 @@ 44, 44 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Administrator > General > Company Management > General > System Properties" + } }, { "block_index": 10, @@ -123,7 +168,12 @@ 46, 46 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 11, @@ -134,7 +184,11 @@ 48, 50 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Workflow Configuration" + } }, { "block_index": 12, @@ -145,7 +199,12 @@ 52, 52 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "Workflow Configurations에서는 결재 상신 및 승인 관련 설정을 변경합니다.", + "anchors": [] + } }, { "block_index": 13, @@ -156,7 +215,11 @@ 54, 54 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 14, @@ -167,7 +230,11 @@ 56, 57 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "최대 승인 기간 설정 " + } }, { "block_index": 15, @@ -178,7 +245,12 @@ 59, 59 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "모든 워크플로우 요청의 승인 만료일을 일괄적으로 제한하는 최대 기간을 설정합니다.", + "anchors": [] + } }, { "block_index": 16, @@ -189,7 +261,12 @@ 61, 61 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "관리자는 Maximum Approval Duration 항목에서 승인 대기 요청이 자동으로 만료될 때까지의 기간을 1일에서 최대 14일까지 설정할 수 있습니다. 이곳에 설정된 기간은 사용자가 워크플로우를 상신할 때 선택하는 Approval Expiration Date (승인 만료일)의 최대값으로 적용됩니다.", + "anchors": [] + } }, { "block_index": 17, @@ -200,7 +277,13 @@ 63, 64 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Admin > Databases > Configurations의 Maximum Access Duration 또는 Admin > Servers > Configurations의 Maximum Access Duration이 Maximum Approval Duration보다 짧게 설정된 경우, 권한이 이미 만료된 후에 승인이 처리되는 상황을 방지하기 위해 Approval Expiration Date은 더 짧은 Access Expiration Date (권한 만료일)을 따라갑니다. ", + "ordered": false, + "items": [] + } }, { "block_index": 18, @@ -211,7 +294,11 @@ 66, 69 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "워크플로우 사유입력 글자 수 제한 설정" + } }, { "block_index": 19, @@ -222,7 +309,12 @@ 71, 71 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "워크플로우의 요청 사유Reason for Request와 승인, 거부, 취소, 확인 시 입력하는 Comment의 최소 및 최대 글자 수를 설정하는 기능입니다. 이 설정을 통해 관리자는 조직의 정책에 맞춰 입력 글자 수를 관리할 수 있습니다.", + "anchors": [] + } }, { "block_index": 20, @@ -233,7 +325,13 @@ 73, 73 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "관리자는 Workflow Configurations 페이지에서 다음 항목에 대한 글자 수 범위를 지정할 수 있습니다.Reason for Request: 워크플로우 상신 시 사유 입력에 대한 최소, 최대 글자 수를 설정합니다. (기본값: 최소 3자, 최대 1000자)Comments: 워크플로우 승인, 거부, 실행 취소, 리뷰 확인 시 입력값에 대한 최소, 최대 글자 수를 설정합니다. (기본값: 최소 3자, 최대 300자)글자 수 범위는 최소 3자에서 최대 1000자 사이에서만 설정할 수 있습니다.", + "ordered": false, + "items": [] + } }, { "block_index": 21, @@ -244,7 +342,11 @@ 75, 77 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "참조자 지정 활성화" + } }, { "block_index": 22, @@ -255,7 +357,12 @@ 79, 79 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "Activate Review Step to collaborate with others 토글로 활성화합니다.", + "anchors": [] + } }, { "block_index": 23, @@ -266,7 +373,13 @@ 81, 81 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Workflow 요청을 상신할 때 참조자를 지정하도록 허용할지 결정합니다.토글을 켜면 요청 생성 화면의 결재 규칙 지정 영역에서 참조자 지정 버튼이 표시됩니다.토글을 끄면 해당 버튼이 제거됩니다. (기존에 지정한 내역은 유지되며, 여전히 목록을 확인할 수 있습니다.)", + "ordered": false, + "items": [] + } }, { "block_index": 24, @@ -277,7 +390,11 @@ 83, 87 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "사후 승인 허용" + } }, { "block_index": 25, @@ -288,7 +405,12 @@ 89, 89 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "Allow users to submit requests in Urgent mode 토글로 활성화합니다.", + "anchors": [] + } }, { "block_index": 26, @@ -299,7 +421,13 @@ 91, 92 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Workflow 요청을 상신할 때 사후 승인 모드(Urgent Mode)를 허용할지 결정합니다.토글을 켜면 다음의 영역에서 사후 승인 관련 토글이 표시됩니다.Approval Rules : 결재 규칙 생성 및 수정Submit Request : 요청 생성 화면의 결재 규칙 지정 영역토글을 끄면 위 영역에서 사후 승인 관련 토글이 더 이상 표시되지 않습니다. 단, 기존에 이미 사후 승인 모드로 요청한 내역은 그대로 남아 있으며, 실행 가능합니다.", + "ordered": false, + "items": [] + } }, { "block_index": 27, @@ -310,7 +438,11 @@ 93, 94 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Attribute 기반 승인자 지정 기능" + } }, { "block_index": 28, @@ -321,7 +453,15 @@ 95, 95 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "11.4.0부터 속성 기반 승인자 지정시 여러개의 속성을 지정할 수 있도록 개선되었습니다. Admin > General > Company Management > General > Workflow Configurations의 Use User Attribute-Based Approval 옵션 스위치가 제거되었습니다.기존 Attribute 기반 승인자 지정시 하나의 Attribute만 지정할 수 있었으나 다른 Attribute를 선택할 수 있도록 하여 각 승인 단계별 각각 다른 속성의 승인자가 매핑되도록 개선되었습니다.", + "child_xpaths": [ + "macro-info[1]/p[1]", + "macro-info[1]/ul[1]" + ] + } }, { "block_index": 29, @@ -332,7 +472,12 @@ 97, 97 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "사용자 프로필의 특정 Attribute(예: 팀 리더, 부서장)를 기준으로 승인자를 자동으로 지정할 수 있습니다. ", + "anchors": [] + } }, { "block_index": 30, @@ -343,7 +488,13 @@ 99, 123 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "중요: 선택된 Attribute의 값으로는 승인자의 QueryPie Login ID가 입력되어 있어야 합니다.Administrator > General > Workflow Management > Approval Rules 페이지에서 새로운 승인 규칙을 추가하거나 기존 규칙을 수정할 때, 'Assignee for Approval' 항목에 'Allow Assignee selection (Attribute-Based)'를 선택한 뒤 승인자와 매핑하기 위한 Attribute (예: teamLeader)를 지정합니다.자가 승인 비활성화 시 연동: 만약 Approval Rules 설정에서 'Self Approval (자가 승인)' 옵션이 비활성화된 경우, Attribute 기반으로 결정된 승인자가 요청자 자신일 경우에는 해당 요청자를 승인자로 자동 지정할 수 없습니다. 이 경우, 워크플로우 설정 또는 시스템 정책에 따라 승인자 지정이 실패하거나 다른 경로로 처리될 수 있으니 주의가 필요합니다.Attribute 값 부재 시 알림만약 상신자 User Profile에 해당 Attribute 값이 비어 있는 경우(즉, 승인자의 Login ID가 지정되지 않은 경우), 워크플로우 상신 시 다음과 같은 내용의 알림 모달이 표시되며 요청 제출이 중단됩니다. 상신자는 관리자에게 문의하여 프로필의 Attribute 값을 설정해야 합니다.에러 메시지 예시: Attribute 값은 있지만 해당 사용자가 비활성화된 경우만약 상신자 User Profile에 해당 Attribute 값으로 지정된 승인자가 비활성화된 경우, 워크플로우 상신 시 Approver란에 지정된 승인자가 표기되지 않으며 아래와 같은 내용의 알림 모달이 표시되며 요청 제출이 중단됩니다.에러 메세지 예시:", + "ordered": false, + "items": [] + } }, { "block_index": 31, @@ -354,7 +505,11 @@ 125, 125 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Attribute 기반 워크플로우 메뉴 숨김 기능 (Use User Attribute to Hide Workflow Menu)" + } }, { "block_index": 32, @@ -365,7 +520,12 @@ 127, 127 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "Use User Attribute to Hide Workflow Menu 토글을 활성화합니다.", + "anchors": [] + } }, { "block_index": 33, @@ -376,7 +536,11 @@ 129, 131 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 34, @@ -387,7 +551,12 @@ 133, 133 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "특정 사용자 속성(User Attribute)을 기준으로 사용자 대시보드에서 Workflow 메뉴를 보이지 않도록 설정하는 기능입니다.", + "anchors": [] + } }, { "block_index": 35, @@ -398,7 +567,13 @@ 135, 137 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "토글을 활성화하면 User Attribute의 Key와 Value를 한 쌍으로 선택하고 입력할 수 있습니다.여기에 설정된 Key:Value 조건과 일치하는 사용자는 사용자 대시보드에 Workflow 메뉴가 표시되지 않습니다.예시: User Attribute Key로 '부서코드'를 선택하고, Value로 '부서 A'를 입력하면 해당 부서코드 소속의 사용자에게는 Workflow 메뉴가 보이지 않습니다.", + "ordered": false, + "items": [] + } }, { "block_index": 36, @@ -409,7 +584,11 @@ 139, 139 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "대리결재 기능 활성화 / 비활성화 옵션 (Allow Delegated Approval)" + } }, { "block_index": 37, @@ -420,7 +599,14 @@ 141, 143 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "대리결재 기능 활성화 / 비활성화 옵션은 11.3.0에 추가되었습니다.11.4.0 부터 사용자의 대리결재자 지정 / 해제 이벤트에 대해 감사로그를 기록합니다. 해당 로그는 External API ( /api/external/v2/workflows/approval-delegations/logs) 를 사용해서 조회 할 수 있습니다.", + "child_xpaths": [ + "macro-info[2]/p[1]" + ] + } }, { "block_index": 38, @@ -431,7 +617,12 @@ 144, 144 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "승인자 역할을 일정기간 다른 사용자에게 위임하는 기능인 대리결재 기능을 관리자가 활성화 또는 비활성화 할 수 있는 옵션입니다. 만약 비활성화 할 경우 사용자 preference 화면에서 Approver setting 메뉴가 노출되지 않습니다. ", + "anchors": [] + } }, { "block_index": 39, @@ -442,7 +633,11 @@ 146, 147 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Allow Delegated Approval 스위치" + } }, { "block_index": 40, @@ -453,7 +648,11 @@ 149, 154 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "대리결재 기능을 비활성화한 상태의 preference" + } }, { "block_index": 41, @@ -464,7 +663,11 @@ 156, 161 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "SQL 요청 실행 시 쓰로틀링 활성화 (Use throttling when submitting SQL Request in Workflow)" + } }, { "block_index": 42, @@ -475,7 +678,12 @@ 163, 163 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "토글을 켜면 Workflow에서 상신된 SQL 요청 실행시 쓰로틀링을 적용합니다.", + "anchors": [] + } }, { "block_index": 43, @@ -486,7 +694,11 @@ 165, 165 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 44, @@ -497,7 +709,12 @@ 167, 169 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 45, @@ -508,7 +725,12 @@ 172, 173 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "이 옵션을 활성화하는 경우, 명시된 갯수만큼 쿼리를 연속적으로 실행한 뒤, 명시된 간격(ms)만큼 대기합니다. 이를 통해 대용량 쿼리 실행에 따르는 DB 부하를 방지할 수 있습니다.", + "anchors": [] + } }, { "block_index": 46, @@ -519,7 +741,13 @@ 175, 177 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Query chunk size : 1~999,999 사이의 값으로 설정되며, 다수의 쿼리를 해당 개수만큼 나누어 순차적으로 실행하는 기능예) 100개의 쿼리가 있을 때, Query chunk size가 10으로 설정되어 있다면 한번에 10개의 Query를 실행합니다. Query throttle interval : 연속 쿼리 실행 후, 다음 연속 쿼리 실행 간의 간격 (1 ~ 9,999 ms)", + "ordered": false, + "items": [] + } }, { "block_index": 47, @@ -530,7 +758,11 @@ 179, 179 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Ledger 테이블 DML 워크플로우 커스텀 URL 리디렉션 기능" + } }, { "block_index": 48, @@ -541,7 +773,12 @@ 181, 181 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "Redirect to custom page for SQL Requests 토글을 활성화합니다.", + "anchors": [] + } }, { "block_index": 49, @@ -552,7 +789,11 @@ 183, 185 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 50, @@ -563,7 +804,12 @@ 187, 188 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "이 옵션을 통해 User Agent에서 Ledger 테이블 대상으로 INSERT, UPDATE, DELETE 쿼리 실행으로 인해 워크플로 승인 요청이 발생할 경우, Send Request 버튼 클릭 시 사용자를 사전에 정의된 커스텀 URL 페이지로 리디렉션할지 여부를 결정합니다. 이를 통해 고객사는 자체 운영 중인 외부 워크플로 시스템과의 연동을 강화할 수 있습니다.", + "anchors": [] + } }, { "block_index": 51, @@ -574,7 +820,13 @@ 190, 195 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "토글을 활성화한 경우Administrator > General > Workflow Configurations 페이지에 커스텀 URL을 입력할 수 있는 필드가 나타납니다.User Agent 사용자가 Ledger 테이블에 대해 변경 쿼리를 실행하고 승인 요청을 보낼 경우, Send Request 클릭 시 해당 커스텀 URL로 리디렉션되며, 이때 쿼리나 사용자 정보는 전달되지 않습니다.토글을 비활성화한 경우Administrator > General > Workflow Configurations 페이지에 커스텀 URL을 입력할 수 있는 필드가 표시되지 않습니다.User Agent 사용자가 Ledger 테이블에 대해 INSERT, UPDATE, DELETE 쿼리를 실행하여 워크플로 승인 요청 모달이 표시될 때, Send Request 버튼을 클릭하면 기존과 같이 QueryPie 내부의 표준 워크플로 페이지로 이동합니다", + "ordered": false, + "items": [] + } }, { "block_index": 52, @@ -585,7 +837,11 @@ 197, 197 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "워크플로우 Request 타입별 사용자 노출 제어 기능" + } }, { "block_index": 53, @@ -596,7 +852,12 @@ 199, 199 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "Select Request Types Allowed for Users 토글을 활성화합니다. ", + "anchors": [] + } }, { "block_index": 54, @@ -607,7 +868,11 @@ 201, 203 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 55, @@ -618,7 +883,12 @@ 205, 205 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "이 옵션을 통해 관리자가 사용자가 접근하고 요청할 수 있는 워크플로우의 요청(Request) 타입을 선택적으로 제어할 수 있도록 합니다. ", + "anchors": [] + } }, { "block_index": 56, @@ -629,7 +899,13 @@ 207, 221 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "토글을 활성화(On)한 경우:Administrator > General > Workflow Configurations 페이지에서:Select Request Types Allowed for Users 토글 아래에 제품별(Databases (DAC), Servers (SAC), Kubernetes (KAC), Web Apps (WAC))로 분류된 요청 타입 목록이 체크박스 형태로 표시됩니다.관리자는 이 체크박스를 사용하여 사용자에게 노출할 요청 타입을 선택적으로 지정할 수 있습니다. 기본적으로 모든 요청 타입이 선택된 상태로 표시됩니다.사용자 워크플로우 상신 화면:관리자가 선택한(체크한) 요청 타입만 사용자에게 표시되며 요청 가능합니다.Access Role Request의 특별 동작:Servers (SAC), Kubernetes (KAC), Web Apps (WAC) 중 하나 이상의 제품에서 Access Role Request가 선택되어야 사용자에게 Access Role Request 항목이 노출됩니다.Access Role Request 요청 시, 내부 Service 항목에는 관리자가 선택한 제품(예: \"Server Access Control\")만 표시됩니다.토글을 비활성화(Off)한 경우 (기본값):Administrator > General > Workflow Configurations 페이지에서:요청 타입 선택을 위한 체크박스 목록이 표시되지 않습니다.사용자 워크플로우 상신 화면:기존과 동일하게, 사용자에게 할당된 라이선스에 따라 모든 관련 제품의 워크플로우 요청 타입이 노출되고 요청 가능합니다.Access Role Request 내의 Service 항목도 라이선스에 따라 모두 노출됩니다.", + "ordered": false, + "items": [] + } }, { "block_index": 57, @@ -640,7 +916,11 @@ 223, 223 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "DB access 요청 권한 별 결재선 지정 기능" + } }, { "block_index": 58, @@ -651,7 +931,12 @@ 225, 225 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "Workflow를 통해 DB Access Request 를 상신할 경우 요청하는 권한에 따라 결재선 (Approval Rule)을 특정할 수 있도록 하려면 “Use DB Privilege Type Based Approval” 토글을 활성화합니다.", + "anchors": [] + } }, { "block_index": 59, @@ -662,7 +947,11 @@ 227, 232 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Use DB Privilege Type Based Approval" + } }, { "block_index": 60, @@ -673,7 +962,12 @@ 234, 234 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "+Routing Rule 버튼을 누르면 privilege 에 대한 조건을 지정하여 결재선에 대한 라우팅을 지정할 수 있습니다.", + "anchors": [] + } }, { "block_index": 61, @@ -684,7 +978,11 @@ 236, 238 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 62, @@ -695,7 +993,13 @@ 240, 245 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Routing Rule Name : 식별하기 쉬운 라우팅 규칙의 이름을 입력합니다.Condition : Privilege에 대한 조건을 지정합니다.All Match : 사용자가 DB Access Request 를 상신할 때 지정한 Privilege Type이 모두 같은 종류인 경우 동작하는 조건입니다.Contains : 사용자가 DB Access Request 를 상신할 때 지정한 Privilege Type이 하나라도 지정한 종류를 포함 했을 때 동작하는 조건입니다. Requested Privilege Type : Administrator > Databases > DB Access Control > Privilege Type에 정의 되어 있는 Privilege 유형 중 하나를 선택합니다.Approval Rule : 위 조건이 만족했을 경우 강제할 결재선(Approval Rule)을 지정합니다.", + "ordered": false, + "items": [] + } }, { "block_index": 63, @@ -706,7 +1010,11 @@ 247, 247 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Routing Rule 지정 예시" + } }, { "block_index": 64, @@ -717,7 +1025,13 @@ 249, 256 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "만약 사용자가 Workflow의 DB Access Request를 통해 요청한 대상 커넥션에 지정한 privilege가 모두 “Read-Only” 인 경우 특정 Approval rule을 사용하도록 강제하기 원한다면, 아래와 같이 설정하고 Add 버튼을 누릅니다.Condition: All Match 선택.Requested Privilege Type : Read-Only 선택.Approval Rule : 강제하기 원하는 Approval Rule 선택. 만약 사용자가 Workflow의 DB Access Request를 통해 요청한 대상 커넥션에 지정한 privilege가 하나라도 Read/Write가 포함되어 있는 경우 특정 Approval rule을 사용하도록 강제하기 원한다면, 아래와 같이 설정하고 Add 버튼을 누릅니다.Condition: Contains 선택.Requested Privilege Type : Read/Write 선택.Approval Rule : 강제하기 원하는 Approval Rule 선택.", + "ordered": false, + "items": [] + } }, { "block_index": 65, @@ -728,7 +1042,11 @@ 258, 258 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "SQL Request에서 실행계획 첨부기능 활성 / 비활성 옵션 " + } }, { "block_index": 66, @@ -739,7 +1057,12 @@ 260, 260 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "Workflow의 SQL Request를 통해 수행할 쿼리를 상신할 때 실행 계획 결과를 첨부하는 “Attach Explain Results” 기능을 활성 또는 비활성화 할 수 있도록 옵션을 제공합니다.", + "anchors": [] + } }, { "block_index": 67, @@ -750,7 +1073,11 @@ 262, 264 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 68, @@ -761,7 +1088,12 @@ 266, 266 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "관리자가 이 옵션을 활성화 하더라도 Workflow의 SQL Request 양식에서 “Attach Explain Results” 는 Off로 되어 있으므로 사용자가 필요시 명시적으로 “Attatch Explain Results” 토글 스위치를 On으로 변경해야 실행계획 결과가 첨부됩니다.", + "anchors": [] + } } ], "separators": [ diff --git a/confluence-mdx/tests/testcases/544178405/expected.roundtrip.json b/confluence-mdx/tests/testcases/544178405/expected.roundtrip.json index 1c5377b6a..42dacafea 100644 --- a/confluence-mdx/tests/testcases/544178405/expected.roundtrip.json +++ b/confluence-mdx/tests/testcases/544178405/expected.roundtrip.json @@ -1,5 +1,5 @@ { - "schema_version": "2", + "schema_version": "3", "page_id": "544178405", "mdx_sha256": "40d0ad62c41d15a6298ad998674e1ac6a4c0f6bebdc56aa3e55a2cea7425bf6a", "source_xhtml_sha256": "4fe7ffbbcee2a5a22ee29bc64cd3f4eaac99a48ae71bae70ed58b41e562e246e", @@ -13,7 +13,11 @@ 6, 6 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "환영합니다." + } }, { "block_index": 1, @@ -24,7 +28,12 @@ 8, 8 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "QueryPie의 관리자는 QueryPie Web 또는 API를 통하여 리소스 및 권한, 솔루션 보안, 알림 설정 등 쿼리파이 전반 솔루션 관리를 진행할 수 있습니다. 공급된 라이선스 및 관리자 역할 권한에 따라 관리자에게 보여지는 설정 메뉴가 상이합니다.", + "anchors": [] + } }, { "block_index": 2, @@ -35,7 +44,12 @@ 10, 11 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "관리자 페이지를 진입하기 위해서는 먼저 QueryPie 로그인을 진행합니다. 로그인이 정상적으로 완료되었다면 사용자 대시보드 이동하게 되며 만약 QueryPie 관리자 역할을 할당받은 사용자일 경우 우측 상단에 Go to Admin Page 버튼이 표시됩니다. 이 버튼을 클릭하여 관리자 페이지를 새 탭으로 열 수 있습니다. ", + "anchors": [] + } }, { "block_index": 3, @@ -46,7 +60,11 @@ 13, 15 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "GNB 내 관리자 페이지 진입점" + } }, { "block_index": 4, @@ -57,7 +75,12 @@ 17, 22 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 5, @@ -68,7 +91,11 @@ 25, 25 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "QueryPie 관리자 설정이 처음이신가요?" + } }, { "block_index": 6, @@ -79,7 +106,12 @@ 27, 28 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "Databases, Servers, Kubernetes 등 각 서비스 설정을 진행하기 전에 기본적으로 설정해야하는 항목들이 있습니다. 특히 보안 설정, 사용자 설정, 관리자 역할 할당은 필수적으로 설정하시는 것을 권장해 드립니다. ", + "anchors": [] + } }, { "block_index": 7, @@ -90,7 +122,11 @@ 30, 136 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "table", + "old_plain_text": "설정 순서설정 항목1기본 정보 설정하기 회사 이름, 타임존, 날짜 표기 방식 등2보안 정보 설정하기 잠금 정책, 비밀번호 변경 주기, 타임아웃 등 허용할 IP 대역 생성 및 관리3사용자 및 사용자 그룹 생성하기Authentication Users 4관리자 역할 할당하기 5결재 규칙 설정하기6알림 설정하기7외부 보안 솔루션 연동하기 " + } }, { "block_index": 8, @@ -101,7 +137,12 @@ 139, 139 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 9, @@ -112,7 +153,11 @@ 141, 144 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "자, 이제 기본 설정을 마쳤습니다. 이제 서비스별 설정을 해볼까요?" + } }, { "block_index": 10, @@ -123,7 +168,12 @@ 146, 216 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "기본 설정을 마쳤다면 서비스별 설정이 남았습니다. QueryPie의 세분화된 접근 제어 기능을 통해 강력한 권한 관리를 경험해 보세요. 서비스별 설정이 끝난 후에는 관리자/사용자의 접속 및 실행 이력들을 Audit 메뉴에서 자세히 확인하실 수 있습니다. 뿐만 아니라 민감 정보를 식별할 수 있는 Discovery 기능 또한 제공하니 차세대 디스커버리 기능을 경험해 보세요.", + "anchors": [] + } }, { "block_index": 11, @@ -134,7 +184,11 @@ 0, 0 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "table", + "old_plain_text": "설정 순서설정 항목1Databased Access Control 설정하기2Server Access Control 설정하기3Kubernetes Access Control 설정하기4감사 로그 확인하기" + } }, { "block_index": 12, @@ -145,7 +199,12 @@ 0, 0 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 13, @@ -156,7 +215,12 @@ 0, 0 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } } ], "separators": [ diff --git a/confluence-mdx/tests/testcases/544211126/expected.roundtrip.json b/confluence-mdx/tests/testcases/544211126/expected.roundtrip.json index bfcbfef6c..e59102f36 100644 --- a/confluence-mdx/tests/testcases/544211126/expected.roundtrip.json +++ b/confluence-mdx/tests/testcases/544211126/expected.roundtrip.json @@ -1,5 +1,5 @@ { - "schema_version": "2", + "schema_version": "3", "page_id": "544211126", "mdx_sha256": "dc392f1d1969be71110411580eb638e2c5f916c90811de2a534288cc247e2ad9", "source_xhtml_sha256": "dbe191b85ab99880b209776f4e806bdcb333550e2c1d71325b701536a8cef73f", @@ -13,7 +13,11 @@ 6, 6 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "환영합니다." + } }, { "block_index": 1, @@ -24,7 +28,12 @@ 8, 8 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "이곳에서 QueryPie의 사용자가 어떻게 QueryPie Web에 접속하여 리소스(DB, System, Cluster)에 대한 접근 권한을 받고, 원하는 리소스에 안전하게 접속할 수 있는지에 대한 가이드를 제공합니다. ", + "anchors": [] + } }, { "block_index": 2, @@ -35,7 +44,12 @@ 10, 10 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 3, @@ -46,7 +60,11 @@ 13, 13 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "QueryPie가 처음이신가요?" + } }, { "block_index": 4, @@ -57,7 +75,12 @@ 15, 16 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "아래 순서를 따라 사용해 보세요. 각 항목을 클릭하면 자세한 사용 방법을 확인하실 수 있습니다.", + "anchors": [] + } }, { "block_index": 5, @@ -68,7 +91,11 @@ 18, 123 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "table", + "old_plain_text": "사용 순서기능1로그인 2대시보드 둘러보기 3Workflow로 권한 받기 4QueryPie Web에서 접속하기 Database Access Control Server Access Control Kubernetes Access ControlWeb Access Control5QueryPie Agent로 접속하기6개인 설정 둘러보기" + } }, { "block_index": 6, @@ -79,7 +106,12 @@ 0, 0 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } } ], "separators": [ diff --git a/confluence-mdx/tests/testcases/544375741/expected.roundtrip.json b/confluence-mdx/tests/testcases/544375741/expected.roundtrip.json index db88d6355..d989a1c40 100644 --- a/confluence-mdx/tests/testcases/544375741/expected.roundtrip.json +++ b/confluence-mdx/tests/testcases/544375741/expected.roundtrip.json @@ -1,5 +1,5 @@ { - "schema_version": "2", + "schema_version": "3", "page_id": "544375741", "mdx_sha256": "05b2b6812db44117380677b47f514d94a1ea7c93c3d1580f2a99ac53578b5e96", "source_xhtml_sha256": "89b833aa5c960cf06b8c5f9a803f3d30b076dad7dc82ec8ac2eb709abb67f3e9", @@ -13,7 +13,11 @@ 6, 6 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 1, @@ -24,7 +28,11 @@ 8, 8 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 2, @@ -35,7 +43,11 @@ 10, 10 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "API Docs Json File" + } }, { "block_index": 3, @@ -46,7 +58,12 @@ 12, 14 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 4, @@ -57,7 +74,11 @@ 16, 16 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 5, @@ -68,7 +89,11 @@ 18, 18 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "1. Access Approval API" + } }, { "block_index": 6, @@ -79,7 +104,11 @@ 20, 20 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "(GET) List" + } }, { "block_index": 7, @@ -90,7 +119,11 @@ 22, 22 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Request" + } }, { "block_index": 8, @@ -101,7 +134,13 @@ 24, 24 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "변경점 없음", + "ordered": false, + "items": [] + } }, { "block_index": 9, @@ -112,7 +151,11 @@ 26, 41 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Response" + } }, { "block_index": 10, @@ -123,7 +166,11 @@ 43, 49 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "table", + "old_plain_text": "변경 전변경 후id" + } }, { "block_index": 11, @@ -134,7 +181,13 @@ 51, 51 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "id 가 추가되었습니다.", + "ordered": false, + "items": [] + } }, { "block_index": 12, @@ -145,7 +198,11 @@ 52, 52 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 13, @@ -156,7 +213,11 @@ 54, 54 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "2. Approval API" + } }, { "block_index": 14, @@ -167,7 +228,11 @@ 56, 56 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "(GET) List" + } }, { "block_index": 15, @@ -178,7 +243,11 @@ 58, 58 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Request" + } }, { "block_index": 16, @@ -189,7 +258,13 @@ 60, 60 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "변경점 없음", + "ordered": false, + "items": [] + } }, { "block_index": 17, @@ -200,7 +275,11 @@ 62, 62 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Response" + } }, { "block_index": 18, @@ -211,7 +290,11 @@ 64, 79 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "table", + "old_plain_text": "변경 전변경 후id" + } }, { "block_index": 19, @@ -222,7 +305,13 @@ 81, 87 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "id 가 추가되었습니다.", + "ordered": false, + "items": [] + } }, { "block_index": 20, @@ -233,7 +322,11 @@ 89, 89 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 21, @@ -244,7 +337,11 @@ 90, 90 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "3. Audit Log API" + } }, { "block_index": 22, @@ -255,7 +352,11 @@ 92, 92 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "(GET) List of Approval" + } }, { "block_index": 23, @@ -266,7 +367,12 @@ 94, 94 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "/api/external/audit-logs", + "anchors": [] + } }, { "block_index": 24, @@ -277,7 +383,11 @@ 96, 96 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Request" + } }, { "block_index": 25, @@ -288,7 +398,13 @@ 98, 98 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Query Parameter/api/docs 에 잘못 표기 되어 있던 cursor 에 대한 내용이 수정되었습니다.", + "ordered": false, + "items": [] + } }, { "block_index": 26, @@ -299,7 +415,11 @@ 100, 101 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Response" + } }, { "block_index": 27, @@ -310,7 +430,13 @@ 103, 103 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "/api/docs 에 잘못 표기 되어 있던 nextCursor 에 대한 내용이 수정되었습니다.", + "ordered": false, + "items": [] + } }, { "block_index": 28, @@ -321,7 +447,11 @@ 105, 105 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 29, @@ -332,7 +462,11 @@ 106, 106 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "4. Notification Channels API" + } }, { "block_index": 30, @@ -343,7 +477,11 @@ 108, 108 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "(GET) List" + } }, { "block_index": 31, @@ -354,7 +492,12 @@ 110, 110 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "/api/external/notification-channels", + "anchors": [] + } }, { "block_index": 32, @@ -365,7 +508,11 @@ 112, 112 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Request" + } }, { "block_index": 33, @@ -376,7 +523,13 @@ 114, 114 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Query Parameter", + "ordered": false, + "items": [] + } }, { "block_index": 34, @@ -387,7 +540,11 @@ 116, 116 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "table", + "old_plain_text": "변경 전변경 후dataFlowRequest.filterKeydataFlowRequest.filterValuedataFlowRequest.sortKeydataFlowRequest.sortTypefilterKeyfilterValuesortKeysortType" + } }, { "block_index": 35, @@ -398,7 +555,13 @@ 118, 147 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "이름이 변경되었습니다.", + "ordered": false, + "items": [] + } }, { "block_index": 36, @@ -409,7 +572,11 @@ 149, 149 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Response" + } }, { "block_index": 37, @@ -420,7 +587,13 @@ 151, 151 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "변경점 없음", + "ordered": false, + "items": [] + } } ], "separators": [ diff --git a/confluence-mdx/tests/testcases/544377869/expected.roundtrip.json b/confluence-mdx/tests/testcases/544377869/expected.roundtrip.json index c13b36fba..d9adb9e3f 100644 --- a/confluence-mdx/tests/testcases/544377869/expected.roundtrip.json +++ b/confluence-mdx/tests/testcases/544377869/expected.roundtrip.json @@ -1,5 +1,5 @@ { - "schema_version": "2", + "schema_version": "3", "page_id": "544377869", "mdx_sha256": "62f5ca1faca232e7cbcd2d265fe47252843fb16a9655be8a1a54d9571eda1d2c", "source_xhtml_sha256": "a74628f8a53d804ffd124b133efc5d9fbf3b623262f07cf2e824b9d7e0f69946", @@ -13,7 +13,11 @@ 8, 8 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Overview" + } }, { "block_index": 1, @@ -24,7 +28,12 @@ 10, 10 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "데이터베이스 커넥션 접속시 Proxy 연결을 지원합니다. 기본적으로 QueryPie 에서 제공하는 웹 SQL 에디터를 통해 커넥션에 접속할 수 있고, QueryPie 에서 생성한 Proxy Credential 정보 또는 Agent 를 통한 기존의 DB username/password로 쓰시던 툴에서 커넥션에 접속할 수 있습니다. 웹 SQL 에디터와 함께 Proxy 연결을 지원하여 다양한 사용자 환경에서도 문제없이 커넥션 접근을 제어하고, 정책을 적용할 수 있으며, 로그를 남길 수 있습니다. 현재 Proxy 연결은 MySQL, MariaDB, Oracle, PostgreSQL, Redshift, Trino, SQLServer, MongoDB 를 지원합니다.", + "anchors": [] + } }, { "block_index": 2, @@ -35,7 +44,12 @@ 12, 15 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 3, @@ -46,7 +60,11 @@ 18, 18 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Proxy 사용 옵션 활성화" + } }, { "block_index": 4, @@ -57,7 +75,12 @@ 20, 21 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "기본적으로는 Proxy 사용 옵션이 비활성화되어 있습니다. 접속을 허용할 커넥션에서 Proxy 사용 여부를 활성화합니다.", + "anchors": [] + } }, { "block_index": 5, @@ -68,7 +91,11 @@ 23, 28 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Administrator > Databases > Connection Management > DB Connections > Connection Details > Proxy Usage" + } }, { "block_index": 6, @@ -79,7 +106,13 @@ 30, 37 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Database 설정 메뉴에서 DB Connections 메뉴로 이동합니다.등록한 커넥션을 하나 클릭합니다.하단의 Additional Information 항목으로 이동합니다.Proxy Usage 옵션을 체크하여 활성화합니다.두 가지 Proxy 인증 방식 중 하나를 선택합니다.Use QueryPie registered account : 관리자가 커넥션 정보 페이지 내에 저장한 DB username / password 기준으로 Proxy 접속 정보를 생성하는 방식입니다. 사용자는 Agent 또는 별도로 생성된 Proxy Credential 정보를 이용해 Proxy 로 접속할 수 있습니다.Use existing database account with Agent : 사용자가 기존에 사용하던 DB username / password 를 사용할 수 있는 방식으로 Proxy 접속 정보가 생성됩니다. 사용자는 Agent 에서 실행 후 localhost 와 port 정보를 이용해 Proxy 로 접속할 수 있습니다.※ 사용자의 DB username / password 인증을 사용할 경우, Agent 를 통해서만 접속 가능합니다.Network ID : Reverse SSH 기능을 사용하는 경우에 필요한 설정 값입니다.", + "ordered": true, + "items": [] + } }, { "block_index": 7, @@ -90,7 +123,12 @@ 39, 40 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "Proxy Usage 옵션을 활성화하면 Proxy 로 접속할 수 있는 Port 가 해당 커넥션에 할당됩니다. 사용자는 해당 옵션이 활성화되면 Proxy 접속 정보를 확인할 수 있고, 관리자만 해당 Proxy 옵션을 설정할 수 있습니다.", + "anchors": [] + } }, { "block_index": 8, @@ -101,7 +139,12 @@ 43, 43 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 9, @@ -112,7 +155,11 @@ 45, 47 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Proxy 사용 활성화된 커넥션 확인하기" + } }, { "block_index": 10, @@ -123,7 +170,14 @@ 48, 48 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "10.3.0 부터 Databases > Connection Management > Proxy Management 는 Databases > Monitoring > Proxy Management 로 이동되었습니다. 10.3.0 이후 버전 사용자는 를 참고하시기 바랍니다.", + "child_xpaths": [ + "macro-info[1]/p[1]" + ] + } }, { "block_index": 11, @@ -134,7 +188,11 @@ 50, 55 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Administrator > Databases > Connection Management > DB Connections > Proxy Management" + } }, { "block_index": 12, @@ -145,7 +203,13 @@ 57, 59 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Database 설정 메뉴에서 Proxy Manangement 메뉴로 이동합니다.클러스터 기준으로 Proxy 사용이 활성화된 커넥션과 Port 정보를 확인할 수 있습니다.Proxy 접속을 통해 커넥션에 연결 중인 사용자를 확인할 수 있고, 필요한 경우 해당 세션을 Kill할 수 있습니다.", + "ordered": true, + "items": [] + } }, { "block_index": 13, @@ -156,7 +220,12 @@ 0, 0 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } } ], "separators": [ diff --git a/confluence-mdx/tests/testcases/544379140/expected.roundtrip.json b/confluence-mdx/tests/testcases/544379140/expected.roundtrip.json index 7c7c13ee0..7c6874327 100644 --- a/confluence-mdx/tests/testcases/544379140/expected.roundtrip.json +++ b/confluence-mdx/tests/testcases/544379140/expected.roundtrip.json @@ -1,5 +1,5 @@ { - "schema_version": "2", + "schema_version": "3", "page_id": "544379140", "mdx_sha256": "9505bae1a9f8e94e484dfc1fc631255c2982b0d1e336cb5f3a6513be1de88edc", "source_xhtml_sha256": "4afc192738be9e19c9b78f324aab5800dd7bad4490d49520f6ff2c0e964840c5", @@ -13,7 +13,11 @@ 8, 8 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Overview" + } }, { "block_index": 1, @@ -24,7 +28,12 @@ 10, 10 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "QueryPie에서 생성된 각종 감사 로그를 추출하고 csv 파일로 내려받을 수 있는 기능입니다. 장기간에 걸친 로그 등 대용량 파일인 경우에도 안정적인 추출 및 다운로드가 가능합니다. 한 번 생성된 로그 추출 파일은 30일 동안 다운로드가 가능합니다. 추출 지원 로그는 Query Audit, Workflow SQL Request 등 21종입니다. ", + "anchors": [] + } }, { "block_index": 2, @@ -35,7 +44,14 @@ 12, 15 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Audit Log Export 기능 추가에 따라, 각 로그 화면에서 제공되던 ‘Excel File Download’ 버튼은 QueryPie v9.15.0부터 지원이 중단되었습니다.", + "child_xpaths": [ + "macro-note[1]/p[1]" + ] + } }, { "block_index": 3, @@ -46,7 +62,12 @@ 17, 18 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "추출 지원 로그 종류 (QueryPie v11.0.0 기준)", + "anchors": [] + } }, { "block_index": 4, @@ -57,7 +78,11 @@ 19, 19 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "table", + "old_plain_text": "Log TypeRelease VersionFormatWorkflow DB Access RequestCSVWorkflow SQL RequestCSVWorkflow SQL Request for Query DetailsCSVWorkflow SQL Export RequestCSVWorkflow Restricted Data Access Request11.0.0JSONWorkflow DB Policy Exception Request11.0.0JSONWorkflow Unmasking Request11.0.0JSONWorkflow Decryption Request10.3.0JSONWorkflow Server Access RequestJSONWorkflow Server Privilege Request11.3.0JSONWorkflow Access Role Request10.0.0CSVIP Registration Request11.0.0JSONUser Access HistoryCSVAdmin Role HistoryCSVActivity LogsJSONAlert Logs10.3.0JSONQuery AuditCSVDB Access HistoryCSVDB Account Lock HistoryCSVDB Access Control LogsCSVDML SnapshotJSONRestricted Data Access Logs (Legacy)10.3.0JSONMasked Data Access Logs (Legacy)10.3.0JSONSensitive Data Access Logs (Legacy)10.3.0JSONDAC Policy Audit Log11.3.0JSONServer Access HistoryJSONCommand AuditJSONSession LogsJSONServer Access Control LogsJSONServer Role HistoryJSONServer Account Lock HistoryJSONRequest AuditJSONKubernetes Role HistoryJSONWeb App Access History11.0.0CSVWeb App Role History11.0.0CSVWeb App JIT Access Control Log11.0.0CSVWeb App Event Audit11.0.0CSV" + } }, { "block_index": 5, @@ -68,7 +93,11 @@ 21, 21 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Audit Log Export 목록 조회하기" + } }, { "block_index": 6, @@ -79,7 +108,11 @@ 23, 61 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Administrator > Audit > General > Audit Log Export" + } }, { "block_index": 7, @@ -90,7 +123,13 @@ 63, 63 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Administrator > Audit > General > Audit Log Export 메뉴로 이동합니다.현재까지 생성된 Audit Log Export 태스크 목록을 확인할 수 있습니다.Status 에서는 태스크의 진행 상태를 확인할 수 있습니다.Processing : 감사 로그 추출이 진행 중입니다.Completed : 감사 로그 추출이 완료되었으며, 파일 다운로드가 가능합니다. (추출 완료 후 30일이 경과하였다면, 파일이 만료되어 다운로드가 불가능합니다.)Failed : 감사 로그 추출이 실패하였습니다. QueryPie Customer Support 를 통해 문의 바랍니다.", + "ordered": true, + "items": [] + } }, { "block_index": 8, @@ -101,7 +140,11 @@ 65, 70 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "로그 추출 작업 생성하기" + } }, { "block_index": 9, @@ -112,7 +155,12 @@ 72, 77 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "감사 로그 추출 및 다운로드는 크게 다음과 같은 단계로 진행됩니다. (1) 관련 메뉴 진입 → (2) 로그 추출 작업 생성 → (3) 로그 추출 완료까지 대기 → (4) 추출 완료시 파일 다운로드", + "anchors": [] + } }, { "block_index": 10, @@ -123,7 +171,12 @@ 79, 79 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "감사 로그 추출을 시작하기 위해서는 ‘Audit > General > Audit Log Export’ 메뉴 접근 후, 우측 상단의 Create Task 버튼을 클릭해야 합니다. 해당 버튼 클릭시 아래와 같은 화면이 나타납니다. ", + "anchors": [] + } }, { "block_index": 11, @@ -134,7 +187,11 @@ 81, 82 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Administrator > Audit > General > Audit Log Export > Create New Task" + } }, { "block_index": 12, @@ -145,7 +202,13 @@ 84, 85 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Task Name : 로그 추출 작업의 이름을 입력합니다.Log Type : 추출 대상 로그의 종류를 선택합니다. Query Audit 등 추출하기 원하는 로그 타입을 선택합니다.로그 종류 선택 후 See Log Template and Description을 클릭하시면 각 로그의 key 등 상세 정보를 조회할 수 있습니다.Download File Format : 로그 출력 파일 형식을 지정합니다. (9.17.0 기준, 각 로그마다 다운로드 가능한 형식이 단일로 제한됩니다. 상단의 ‘추출 지원 로그 종류’을 참고해주시기 바랍니다.) For Text Exceeding 32,000 Characters : CSV 파일의 경우 32,000자를 초과하는 글자를 잘라낼지 여부를 선택할 수 있습니다.Trim Overflowing Text - 32,000자를 초과하는 글자를 잘라냅니다. Excel로 파일을 직접 열어야 하는 경우에 셀 당 최대 글자수 초과로 표가 깨지는 현상을 방지합니다.No Action - 32,000자를 초과하는 글자를 그대로 포함시킵니다.From : 추출 시작 날짜를 지정합니다.To : 추출 대상 마지막 날짜를 지정합니다. 11.2.0부터 당일 날짜를 선택할 수 있도록 개선되었습니다. Filter Expression : 필터 표현식을 지정합니다. 조건 입력 방법 및 예시는 하단의 ‘필터 표현식’을 참고해주시기 바랍니다.Generate Preview : 미리보기를 생성합니다. 미리보기는 필수 단계입니다.미리보기 결과를 확인해야 다음 단계인 ‘Create’가 가능합니다.Create 버튼 : 로그 추출 작업을 생성합니다. ", + "ordered": true, + "items": [] + } }, { "block_index": 13, @@ -156,7 +219,15 @@ 87, 92 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "유의 사항 당일 날짜를 선택하여 로그를 추출하는 경우, 실시간으로 데이터가 쌓이기 때문에 Preview 시점의 결과와 실제 Create를 통해 생성된 파일의 내용이 다를 수 있습니다.", + "child_xpaths": [ + "macro-info[1]/p[1]", + "macro-info[1]/p[2]" + ] + } }, { "block_index": 14, @@ -167,7 +238,35 @@ 94, 109 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "필터 표현식 (1) 필터 표현식을 사용하시기 위해 'See Log Template and Description'을 통해 로그별 key와 각각의 type 및 포함하는 value를 참고해주시기 바랍니다. (2) 사용 가능한 필터 표현식은 데이터의 종류에 따라 아래와 같이 나뉩니다. - Number Type 지원하는 표현식 : >, <, <=, >=, ==, !=예 : x > `10`, x == `10` - String Type 지원하는 표현식 : == (equals), != (not equals), contains예 : x == 'abc', x != 'abc', contains(x, 'ab') - Boolean Type 지원하는 표현식 : == (equals), != (not equals), && (and), || (or)예 : x == `true`, x && y, (x > `0`) && (y == `0`) - Array Type 예 : x[? @ == 'value'], list[? @ > `10`] (3) 여러 조건을 함께 사용하기 위해 아래의 문자를 활용할 수 있습니다. - AND 조건 : && 연산자를 활용합니다. - OR 조건 : || 연산자를 활용합니다. - 복합 조건 : 함께 처리해야하는 조건은 괄호(( ))로 묶어서 활용합니다. (4) 예시 - Query Audit 중 쿼리 실행 로그만 추출하려는 경우 필요한 표현식 : actionType == 'SQL_EXECUTION' - Query Audit 중 웹에디터에 수행한 쿼리 실행 로그만 추출하려는 경우 필요한 표현식 : actionType == 'SQL_EXECUTION' && executedFrom == 'WEB_EDITOR' - DB Access History 중 특정 데이터베이스 2종류 대해서만 추출하려는 경우 필요한 표현식 : connectionName == 'database1' || connectionName == 'database2' - DB Access History 중 특정 데이터베이스 2종류이면서 Replication Type이 SINGLE인 경우에 대해서만 추출하려는 경우 필요한 표현식 : (connectionName == 'database1' || connectionName == 'database2') && replicationType == 'SINGLE'", + "child_xpaths": [ + "ac:adf-extension[1]/p[1]", + "ac:adf-extension[1]/p[2]", + "ac:adf-extension[1]/p[3]", + "ac:adf-extension[1]/p[4]", + "ac:adf-extension[1]/p[5]", + "ac:adf-extension[1]/p[6]", + "ac:adf-extension[1]/p[7]", + "ac:adf-extension[1]/p[8]", + "ac:adf-extension[1]/p[9]", + "ac:adf-extension[1]/p[10]", + "ac:adf-extension[1]/p[11]", + "ac:adf-extension[1]/p[12]", + "ac:adf-extension[1]/p[13]", + "ac:adf-extension[1]/p[14]", + "ac:adf-extension[1]/p[15]", + "ac:adf-extension[1]/p[16]", + "ac:adf-extension[1]/p[17]", + "ac:adf-extension[1]/p[18]", + "ac:adf-extension[1]/p[19]", + "ac:adf-extension[1]/p[20]", + "ac:adf-extension[1]/p[21]", + "ac:adf-extension[1]/p[22]" + ] + } }, { "block_index": 15, @@ -178,7 +277,12 @@ 111, 113 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 16, @@ -189,7 +293,16 @@ 114, 114 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Query Audit의 내보내기 파일의 Privilege Type 명시 기준 내보내기 파일의 ‘Privilege Type’ 컬럼에는 실행시점에는 어느 권한이 필요했는지가 기록됩니다. 아래와 같이 작동합니다. 기본 권한으로 실행되는 명령어(SET, SHOW 등)는 해당 컬럼의 값이 공백입니다.INSERT 등 권한에 따라 수행된 로그에는 SQL Type이 명시됩니다. Redis의 경우에는 커맨드명이 명시됩니다.", + "child_xpaths": [ + "ac:adf-extension[2]/p[1]", + "ac:adf-extension[2]/p[2]", + "ac:adf-extension[2]/ol[1]" + ] + } }, { "block_index": 17, @@ -200,7 +313,12 @@ 116, 117 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 18, @@ -211,7 +329,11 @@ 119, 119 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "추출 작업 완료시 파일 내려받기" + } }, { "block_index": 19, @@ -222,7 +344,12 @@ 121, 121 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "별도의 필터링 조건이 없는 단기간 조회의 경우에는 로그 추출이 몇 분 이내에 완료됩니다. 장기간에 걸친 로그이거나 필터링 조건의 복잡도가 높은 경우 등에서는 로그 추출까지 다소 시간이 소요될 수 있습니다.", + "anchors": [] + } }, { "block_index": 20, @@ -233,7 +360,12 @@ 123, 123 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "추출 작업이 완료된 이후에는 두 가지 방법으로 로그 파일을 내려받을 수 있습니다. ", + "anchors": [] + } }, { "block_index": 21, @@ -244,7 +376,13 @@ 125, 125 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "목록 페이지에서 다운로드: 목록 페이지에서 Download 버튼을 클릭합니다.상세 페이지에서 다운로드: 목록 페이지에서 태스크를 클릭하여 상세 페이지로 진입, 우측 상단 Download 버튼을 클릭합니다.", + "ordered": true, + "items": [] + } }, { "block_index": 22, @@ -255,7 +393,16 @@ 127, 127 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "다운로드 파일에 암호 포함하기 다운로드 대상 파일은 ‘*.csv 또는 *.json 파일을 압축한 *.zip 파일’입니다. 해당 압축 파일에 대한 암호를 지정하기 위해서는 ‘General Setting > Security’ 메뉴에서 Export a file with Encryption 옵션을 ‘Required’로 지정해야 합니다.", + "child_xpaths": [ + "ac:adf-extension[3]/p[1]", + "ac:adf-extension[3]/p[2]", + "ac:adf-extension[3]/p[3]" + ] + } }, { "block_index": 23, @@ -266,7 +413,12 @@ 129, 129 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 24, @@ -277,7 +429,11 @@ 131, 131 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "로그 추출 파일 보관 기간 정책 안내" + } }, { "block_index": 25, @@ -288,7 +444,12 @@ 133, 133 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "추출이 완료된 로그 파일은 작업 생성일로부터 30일간 보관되며, 해당 기간 이내에는 횟수 제한 없이 내려받기가 가능합니다. 하지만 30일이 경과하면 파일이 만료됩니다. 만료된 로그 파일이 필요해진 경우, 로그 추출 태스크를 다시 생성해주시기 바랍니다.", + "anchors": [] + } } ], "separators": [ diff --git a/confluence-mdx/tests/testcases/544381877/expected.roundtrip.json b/confluence-mdx/tests/testcases/544381877/expected.roundtrip.json index d06c724d7..2361721b2 100644 --- a/confluence-mdx/tests/testcases/544381877/expected.roundtrip.json +++ b/confluence-mdx/tests/testcases/544381877/expected.roundtrip.json @@ -1,5 +1,5 @@ { - "schema_version": "2", + "schema_version": "3", "page_id": "544381877", "mdx_sha256": "b19a8dbd987ba7ea6ebfa52d4b09cdee4fa49e1bcba5725b3a87e9dcd5425fba", "source_xhtml_sha256": "cc44184af8a58c67adea622946171a262d266f7a8371352323a96d047b79e3e4", @@ -13,7 +13,11 @@ 6, 6 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Overview" + } }, { "block_index": 1, @@ -24,7 +28,12 @@ 8, 8 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "QueryPie에서는 접근 제어를 적용할 온프레미스 등에 위치한 쿠버네티스 클러스터를 수동으로 등록할 수 있습니다. ", + "anchors": [] + } }, { "block_index": 2, @@ -35,7 +44,12 @@ 10, 10 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 3, @@ -46,7 +60,11 @@ 13, 13 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "수동으로 클러스터 등록하기" + } }, { "block_index": 4, @@ -57,7 +75,12 @@ 15, 15 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "개별 서버를 수동으로 등록하기 위해서는 서버의 기본적인 정보 입력이 필요합니다. ", + "anchors": [] + } }, { "block_index": 5, @@ -68,7 +91,11 @@ 17, 19 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 6, @@ -79,7 +106,13 @@ 21, 61 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Administrator > Kubernetes > Connection Management > Clusters 메뉴로 이동합니다.우측 상단의 + Create Cluster 버튼을 클릭합니다.Information : 클러스터 수동 등록을 위한 다음의 정보들을 입력합니다Name : 해당 클러스터를 식별할 수 있는 이름을 입력합니다. (필수) 해당 정보는 향후 수정이 불가한 항목입니다. Version : 클러스터의 버전을 기입합니다. (선택) 이후 크리덴셜 인증 테스트 절차를 통해 자동 기입 예정인 항목입니다. API URL : Kubernetes API를 수신할 클러스터의 API URL을 기입합니다. Credential : 해당 클러스터의 Kubernetes API 서버에 액세스 권한을 부여하려면 서비스 계정 토큰 및 CA인증서를 해당 클러스터에서 가져와야 합니다. 자세한 내용은 파란색 정보 박스 안 내용을 확인해 주세요.Service Account Token : QueryPie Proxy에서 사용자 Kubernetes API 호출 시 사용할 쿠버네티스 클러스터의 서버스 계정 토큰 값을 기입합니다. Certificate Authority : QueryPie에서 Kubernetes API 서버 인증서를 검증할 CA 인증서를 기입합니다.Verify Credential : 서비스 계정 토큰 및 CA인증서를 모두 기입 시 해당 버튼이 활성화됩니다. 버튼을 클릭하면 정상 연결이 가능한지 여부를 체크할 수 있습니다. 수행 결과에 따라 다음과 같이 결과가 표시됩니다.:check_mark: Verified : 클러스터 연결 성공으로 서비스 계정 토큰 및 CA 인증서가 정상 기입되었음을 의미합니다. :cross_mark: Verification Failed : 클러스터 연결 실패로 서비스 계정 토큰 및 CA인증서 중 값의 오류가 있거나, 네트워크 연결에 실패하였을 가능성이 있음을 의미합니다. Logging Options : 해당 클러스터에 대한 로깅 옵션을 선택합니다. Request Audit : 해당 클러스터에 대한 Kubernetes API 호출 이력에 대한 로깅 활성화 옵션으로, Default는 On입니다. 해당 기능을 Off로 전환 시, 해당 클러스터를 대상으로 호출되는 Kubernetes API 이력이 남지 않습니다. 하위의 Request Audit Types 및 Pod Session Recording 모두 일괄 비활성화 처리됩니다. Request Audit Types : 해당 클러스터의 감사할 대상 Verb를 관리자가 선택할 수 있습니다. Default는 이하의 기본 verb 전부를 선택하고 있습니다. Verb 종류: get list watch createupdate patchdeletedeletecollection ✅ Select All : 모든 API 호출에 대해 감사를 진행합니다. Pod Session Recording : 해당 클러스터 내 Pod exec 명령에 의해 오픈된 세션에 대한 레코딩 활성화 옵션으로, Default는 On입니다. 해당 기능은 이하의 조건을 충족하지 못하면 Off로 전환됩니다: Request Audit이 On으로 활성화되어야 합니다. Request Audit Types에 이하의 verb가 선택되어 있어야 합니다:createget Tags : 필요 시 개별 클러스터에 Tag를 수동으로 입력할 수 있으며 Cloud Provider를 통해 동기화된 클러스터의 경우 동기화해 온 태그도 함께 표시됩니다. (단, 동기화를 통해 불러온 태그는 삭제 및 수정 불가합니다.) + Add Tag 버튼을 클릭하여 새 행을 추가하고 원하는 태그 값을 기입할 수 있으며 태그는 key-value 형태로 기입해야 합니다. Key : 태그를 구분할 수 있는 Key 값을 512자 이내로 입력합니다. Key 값을 필수로 입력해야 하며, 이미 등록된 키는 중복 입력이 불가합니다. 중복은 대소문자를 구분하여 체크합니다. Value : 필터링에 사용할 Value 값을 256자 이내로 입력합니다.위 과정을 거친 후 최종 Save 버튼을 클릭하면 클러스터가 성공적으로 등록됩니다. ", + "ordered": true, + "items": [] + } }, { "block_index": 7, @@ -90,7 +123,12 @@ 64, 64 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 8, @@ -101,7 +139,11 @@ 66, 68 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "쿠버네티스 클러스터 연동용 스크립트 이용 안내 " + } }, { "block_index": 9, @@ -112,7 +154,11 @@ 70, 71 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 10, @@ -123,7 +169,13 @@ 72, 75 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "관리자는 사전에 해당 대상 쿠버네티스 클러스터에 접속이 가능하여야 합니다.관리자는 Administrator > Kubernetes > Connection Management > Clusters > Create Cluster > Credential 안내 박스 내 “download and run this script”의 링크를 클릭하여 스크립트를 다운로드할 수 있습니다. ", + "ordered": false, + "items": [] + } }, { "block_index": 11, @@ -134,7 +186,11 @@ 77, 77 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "generate_kubepie_sa.sh 스크립트 컨텐츠 " + } }, { "block_index": 12, @@ -145,7 +201,13 @@ 79, 80 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "스크립트 다운로드 이후 해당 경로에서 이하의 명령어를 수행하여 실행 권한을 부여한 뒤 사용합니다. ", + "ordered": false, + "items": [] + } } ], "separators": [ diff --git a/confluence-mdx/tests/testcases/544382364/expected.roundtrip.json b/confluence-mdx/tests/testcases/544382364/expected.roundtrip.json index 2e2874e1d..975b577a1 100644 --- a/confluence-mdx/tests/testcases/544382364/expected.roundtrip.json +++ b/confluence-mdx/tests/testcases/544382364/expected.roundtrip.json @@ -1,5 +1,5 @@ { - "schema_version": "2", + "schema_version": "3", "page_id": "544382364", "mdx_sha256": "7167b9565306b542b575d17e84f01cf5443e97c546862ff82396f5cd820744be", "source_xhtml_sha256": "672aae0060c85c2dff3cc80ef243fe69dcb915a52e2117a181acfef5733be0c7", @@ -13,7 +13,14 @@ 8, 8 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "해당 문서는 QueryPie Enterprise 9.20.0 기준으로 작성되었습니다.", + "child_xpaths": [ + "macro-info[1]/p[1]" + ] + } }, { "block_index": 1, @@ -24,7 +31,11 @@ 10, 11 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 2, @@ -35,7 +46,12 @@ 12, 12 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 3, @@ -46,7 +62,11 @@ 15, 15 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "쿼리파이 접근 제어 정책 개요 " + } }, { "block_index": 4, @@ -57,7 +77,12 @@ 17, 20 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "쿼리파이에서 정의하는 접근 제어 정책은 EABAC(Enhanced Attribute Based Access Control)을 기반으로 동작합니다. EABAC은 쿼리파이 사용자의 역할(Role)과 속성(Attribute)을 기반으로 쿼리파이에 등록된 리소스에 대한 접근을 제어하고 권한 관리를 수행할 수 있는 정책의 통제 방식을 의미합니다. RBAC(Role-Based Access Control) 및 ABAC(Attribute-Based Access Control)의 기능을 결합하여 더욱 유연하고 정교한 접근 제어를 제공합니다. 모든 정책은 All Deny를 전제로 동작합니다. ", + "anchors": [] + } }, { "block_index": 5, @@ -68,7 +93,12 @@ 22, 23 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "EABAC 은 Role, Policy 두 가지로 동작합니다. Role 설정은 GUI 로 하며, Policy 정의는 코드(YAML)로 관리합니다.", + "anchors": [] + } }, { "block_index": 6, @@ -79,7 +109,12 @@ 25, 25 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "쿠버네티스 정책의 경우, 실제 쿠버네티스에 존재하는 네임스페이스에 국한되는 Role과 네임스페이스 범위 외 리소스를 지원하는 ClusterRole 양측 모두를 수용할 수 있는 종합적 모델로 스펙이 제작되었습니다. ", + "anchors": [] + } }, { "block_index": 7, @@ -90,7 +125,12 @@ 28, 28 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 8, @@ -101,7 +141,11 @@ 30, 30 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "쿠버네티스 정책 YAML 기본 구조" + } }, { "block_index": 9, @@ -112,7 +156,12 @@ 32, 117 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "일부 항목을 제외한 전반 정책 코드에서 와일드카드(“*”) 및 정규표현식(RE2 형식: ^$ 명시 필수) 패턴을 지원합니다.", + "anchors": [] + } }, { "block_index": 10, @@ -123,7 +172,11 @@ 119, 135 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "table", + "old_plain_text": "CategoryPropertyRequiredDescriptionValid ValuesapiVersion-O작성된 YAML Code의 버전입니다. 시스템에서 관리하는 값으로, 수정이 불필요합니다.kubernetes.rbac.querypie.com/v1kind-O작성된 YAML Code의 종류입니다. 시스템에서 관리하는 값으로, 수정이 불필요합니다.KacPolicyspec: -O정책의 구체적인 규칙의 허용 또는 거부 여부 지정합니다. 각 정책은 이하의 스펙만 허용합니다: 단일 Allow 단일 Deny단일 Allow & 단일 DenyDeny가 Allow보다 우선순위가 높습니다.allow, denyresourcesO접근을 허용/거부할 리소스를 지정합니다. 와일드카드 및 정규표현식 허용Blue - \"cluster:*\"- \"cluster:^eks-.*$\"subjectsO쿠버네티스 명령을 impersonate할 쿠버네티스 사용자/그룹을 지정합니다.kubernetesGroups : 쿼리파이 Proxy가 이용할 kubernetes group을 지정합니다.impersonation : 클라이언트 단에서 impersonation을 허용할 대상 사용자/그룹을 지정합니다. 와일드카드 허용GreenkubernetesGroups: - \"system:masters\" - $user.groupsimpersonation: users: - \"system:user\" groups: - \"system:admin\"actionsO쿠버네티스 클러스터 API 서버 내 허용/거부할 Resource API를 명시합니다. 와일드카드 및 정규표현식 허용BlueapiGroups : 쿠버네티스 API 그룹 리스트를 정의합니다. 와일드카드 허용Greenresources : 쿠버네티스 리소스를 정의합니다. 와일드카드 허용Greennamespace : 대상 네임스페이스를 정의합니다. 와일드카드 및 정규표현식 허용Bluename : 대상 리소스명을 정의합니다. 와일드카드 및 정규표현식 허용Blueverbs : 작업을 허용하거나 거부할 kubernetes 내의 권한을 명시합니다. 와일드카드 허용Green- apiGroups: [\"*\"] resources: - \"*\" namespace: \"*\" name: \"*\" verbs: [\"*\"]conditions리소스 접근 정책 적용 여부를 조건으로 적용 대상을 세부 제어합니다.resourceTags : 리소스 태그의 키와 값으로 필터링할 수 있습니다. Value 와일드카드 및 정규표현식 허용Purple userAttributes : 사용자의 attribute을 조건으로 권한 사용을 제한합니다. Value 와일드카드 및 정규표현식 허용PurpleipAddresses : 리소스에 대한 IP 접근 통제 조건 리스트를 단일IP, CIDR 형태으로 정의합니다. 와일드카드 허용GreenresourceTags: - \"Owner\": \"Daniel\"userAttributes: - \"department\": \"DevOps\" ipAddresses: - \"10.10.10.0/24\"" + } }, { "block_index": 11, @@ -134,7 +187,12 @@ 137, 155 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 12, @@ -145,7 +203,11 @@ 157, 177 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Resources 명시 방법" + } }, { "block_index": 13, @@ -156,7 +218,12 @@ 179, 183 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "resources는 쿠버네티스의 리소스가 아닌 쿼리파이에 등록된 쿠버네티스 클러스터에 대한 리소스를 정의합니다.", + "anchors": [] + } }, { "block_index": 14, @@ -167,7 +234,13 @@ 185, 197 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "해당 resources 필드에 대한 정의는 필수입니다. requiredRed 쿠버네티스 클러스터명을 바탕으로 입력합니다.\"cluster:{Kubernetes Cluster Name in QueryPie}\"쿠버네티스 클러스터명은 이하의 기준으로 정립됩니다. 문자 길이 100자 제한알파벳 대소문자 (case-sensitive), 숫자 및 이하 특수기호만 허용underscore (_)hyphen (-)이름 시작과 끝은 알파벳 대소문자 또는 숫자로 제한클러스터명은 중복을 제한쿠버네티스 클러스터는 단일 정책에서 다중으로 지정이 가능합니다. - \"cluster:kubepie-dev-1\"- \"cluster:kubepie-dev-2\"[\"cluster:kubepie-dev-1\", \"cluster:kubepie-dev-2\"]경로는 와일드카드를 허용합니다.\"cluster:kubepie-dev-*\"경로는 정규표현식을 허용합니다.\"cluster:^kubepie-dev-.*$\"", + "ordered": true, + "items": [] + } }, { "block_index": 15, @@ -178,7 +251,12 @@ 200, 200 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 16, @@ -189,7 +267,11 @@ 202, 202 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Subjects 명시 방법" + } }, { "block_index": 17, @@ -200,7 +282,12 @@ 204, 220 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "subjects는 쿠버네티스 명령을 impersonate할 쿠버네티스 내 사용자/그룹을 정의합니다. subjects는 spec:allow에 한해 적용이 필요한 필드로, spec:deny에서는 문법적으로 허용되지 않습니다. ", + "anchors": [] + } }, { "block_index": 18, @@ -211,7 +298,13 @@ 223, 223 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "kubernetesGroups: requiredRed리소스에 대한 API 수행을 위해 쿼리파이 Proxy가 impersonate할 kubernetes 그룹 계정을 정의합니다.해당 subjects는 Policy-wide 레벨에서 정의할 수 있어야 하며, 최소 하나가 배정되도록 정의되어야 합니다.Impersonation을 위한 해당 subjects는 다른 Policy들과 하나의 Role 내에서 중첩이 가능합니다.Resources에 명시된 리소스에 대한 권한 수행이 가능한 kubernetes 측 그룹(단일 또는 복수)을 정의합니다.kubernetesGroups에는 Querypie User의 Group을 예약어 형태로 입력할 수 있습니다. 이 때, User의 Group은 쌍따옴표(“) 없이 입력 입력되어야 하며, 사용자가 Policy를 볼 땐 실제 user Group의 값이 표시됩니다.impersonation: OPTIONALYellow사용자가 클라이언트 단에서 kubernetes 내 특정 serviceaccount 등의 권한으로 kubectl 명령에 impersonation(--as, --as-group)을 시도하는 경우, 이에 대한 호출 허용 목록을 정의합니다.users: 쿠버네티스 내 존재하는 사용자/서비스 계정으로 \"--as\" 파라미터에 허용 가능한 값을 나열합니다.groups: 쿠버네티스 내 존재하는 그룹 계정으로 \"--as-group\" 파라미터에 허용 가능한 값을 나열합니다.와일드카드를 허용합니다. (\"*\")", + "ordered": true, + "items": [] + } }, { "block_index": 19, @@ -222,7 +315,12 @@ 225, 225 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 20, @@ -233,7 +331,11 @@ 227, 253 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Actions 명시 방법" + } }, { "block_index": 21, @@ -244,7 +346,12 @@ 256, 256 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "클러스터 API 서버 내 허용할 Resource API 리스트를 명시합니다. 각 action에는 apiGroups, resources, namespace, name, verbs 기입이 필요합니다. ", + "anchors": [] + } }, { "block_index": 22, @@ -255,7 +362,13 @@ 258, 259 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "apiGroups : 쿠버네티스 API 그룹 리스트를 정의합니다. API 그룹을 정의함으로써 더욱 세부적인 권한 통제가 API 그룹별로 가능합니다. 커스텀 리소스에 대한 통제가 필요 시, apiGroups에 명시하여 커스텀 리소스에 한하여 통제도 가능합니다. 참고: API Groups 안내 API Group 목록 와일드카드를 허용하여 API 그룹 전체를 포괄적으로 지정하는 것이 가능합니다.apiGroups: [\"*\"]resources : 쿠버네티스 리소스 리스트를 정의합니다.일반적으로 많이 쓰이는 리소스는 이하와 같습니다:pods, pods/exec, pods/log, pods/portforward, services, ingresses, deployments, replicasets, statefulsets, daemonsets, configmaps, secrets, namespaces, nodes, persistentvolumes, persistentvolumeclaims, jobs, cronjobs, serviceaccounts, endpoints, roles, rolebindings, clusterroles, clusterrolebindings위에 표기되지 않은 리소스 또한 정책에서 수용이 가능합니다. 네임스페이스 하위 리소스만 지정하여 권한을 부여하더라도, Namespace에 대한 ReadOnly 권한이 함께 부여됩니다. 따라서, 리소스 호출 시 매번 네임스페이스 명시해주어야 하는 사례가 발생되지 않습니다. 특정 네임스페이스에 대한 권한 부여를 방지하고자 하는 경우, spec:deny 에서 네임스페이스 리소스에 대한 접근 거부를 설정할 수 있습니다. 단일 정책 액션에서 다중으로 리소스 지정이 가능합니다. 와일드카드를 허용합니다. (\"*\")namespace : 대상 네임스페이스를 정의합니다. 네임스페이스를 액션에서 정의하여 클러스터 내 허용 대상 네임스페이스를 리소스별로 제어할 수 있습니다. 와일드카드와 정규표현식 모두 허용합니다.namespace: \"*\"namespaces 외 네임스페이스에 존속되지 않는 리소스의 경우, 해당 namespace를 작성하지 않아도 됩니다.대상 예) persistentvolumes, persistentvolumeclaims, serviceaccounts, customresourcedefinitions, endpoints, nodes, clusterroles, clusterrolebindings 등name : 대상 리소스명을 정의하여 클러스터 내 허용 리소스 중 네이밍을 바탕으로 제어가 가능합니다. 와일드카드와 정규표현식 모두 허용합니다.name: \"pods-*\"verbs : 작업을 허용하거나 거부할 kubernetes 내의 API 권한을 명시합니다.일반적으로 호출되는 verb는 아래와 같습니다: get: 리소스 정보 검색list: 리소스 목록 나열watch: 리소스 변경사항 주시 create: 새로운 리소스 생성update: 기존 리소스 업데이트patch: 리소스 부분 수정delete: 리소스 삭제 deletecollection: 단일 작업에서 여러 자원 삭제위에 표기되지 않은 특수한 verb가 있더라도 정책에 명시하여 적용 가능합니다. 3rd-Party 클라이언트 지원 시 이하의 verb가 필수로 요구되며 안내합니다: View 권한 : get, list, watchEdit 권한 : get, list, watch + create, update, patch, delete, deletecollectionpod exec명령을 통한 컨테이너 쉘 접속 세션 생성을 위해서는 일반적으로 create verb 명시가 필요합니다. 와일드카드를 허용합니다. verbs: [\"*\"]", + "ordered": true, + "items": [] + } }, { "block_index": 23, @@ -266,7 +379,12 @@ 261, 307 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 24, @@ -277,7 +395,11 @@ 310, 310 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Conditions 명시 방법" + } }, { "block_index": 25, @@ -288,7 +410,12 @@ 312, 312 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "conditions 항목에는 resourceTags, userAttributes, ipAddresses를 조건으로 정의할 수 있습니다. OPTIONALYellow", + "anchors": [] + } }, { "block_index": 26, @@ -299,7 +426,13 @@ 314, 329 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "resourceTags : 태그 항목에서는 리소스 태그의 키와 값으로 필터링할 수 있습니다. userAttributes : 사용자의 속성(attribute)을 조건으로 지정하고 값이 매칭되는 사용자에 한해서만 해당 정책에서 정의된 권한의 사용을 제한합니다. ipAddresses : 리소스에 대한 IP 접근 통제 조건 리스트를 단일IP, CIDR 형태으로 정의합니다. ", + "ordered": false, + "items": [] + } }, { "block_index": 27, @@ -310,7 +443,12 @@ 332, 332 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 28, @@ -321,7 +459,11 @@ 334, 376 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "KAC Policy 예시" + } }, { "block_index": 29, @@ -332,7 +474,11 @@ 0, 0 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "code", + "old_plain_text": "" + } } ], "separators": [ diff --git a/confluence-mdx/tests/testcases/544384417/expected.roundtrip.json b/confluence-mdx/tests/testcases/544384417/expected.roundtrip.json index 0e958715d..323744022 100644 --- a/confluence-mdx/tests/testcases/544384417/expected.roundtrip.json +++ b/confluence-mdx/tests/testcases/544384417/expected.roundtrip.json @@ -1,5 +1,5 @@ { - "schema_version": "2", + "schema_version": "3", "page_id": "544384417", "mdx_sha256": "1eba2d5c618f216f3a17cb9babfa381d33c57f22420745575d3e8c03b0804d4d", "source_xhtml_sha256": "a4e2f99d4cec940a78d4fec41ca182195bcca8a70edd3085f72d5e778c47d4c0", @@ -13,7 +13,14 @@ 8, 8 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "11.2.0 현재 Reports 기능은 Beta 버전으로 제공 중입니다.", + "child_xpaths": [ + "macro-note[1]/p[1]" + ] + } }, { "block_index": 1, @@ -24,7 +31,11 @@ 10, 11 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Overview" + } }, { "block_index": 2, @@ -35,7 +46,12 @@ 12, 12 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "QueryPie 보고서는 감사 대응에 필요한 데이터를 보고서 형태로 출력하는 기능입니다. ", + "anchors": [] + } }, { "block_index": 3, @@ -46,7 +62,12 @@ 14, 14 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "사용자 및 관리자 현황 및 내역, 보안 설정 내역, DB/서버에 주어진 사용자별 권한 현황 및 이력, DB/서버 접속 및 쿼리/명령어 사용 내역 등을 출력할 수 있습니다.", + "anchors": [] + } }, { "block_index": 4, @@ -57,7 +78,14 @@ 16, 16 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "11.2.0 버전 현재 보고서는 영어로만 출력 가능합니다.", + "child_xpaths": [ + "macro-info[1]/p[1]" + ] + } }, { "block_index": 5, @@ -68,7 +96,11 @@ 18, 18 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 6, @@ -79,7 +111,11 @@ 20, 21 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "보고서 출력 항목 및 필터 지원 사항" + } }, { "block_index": 7, @@ -90,7 +126,12 @@ 22, 22 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "11.2.0 버전 현재 지원하는 보고서 항목 및 필터는 아래의 표를 참고하시기 바랍니다.", + "anchors": [] + } }, { "block_index": 8, @@ -101,7 +142,11 @@ 24, 24 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "table", + "old_plain_text": "보고서 유형대분류소분류User / GroupResource기타 필터QueryPie SystemUsersUser Account Status 사용자 계정 현황OXUser StatusUser Account Management History 사용자 계정 관리 이력OX-User Account Activation History 사용자 계정 활성화/비활성화 이력OX-Group Status added-10.2.2Green그룹 현황XX-Group Membership History added-10.2.2Green그룹 멤버 추가/삭제 이력XX-AdministratorAdministrator Status 관리자 현황OX-Admin Role History 관리자 권한 부여/회수 이력OX-Admin Activity History 관리자 행위 이력OX-LoginUser Login History 사용자 로그인 이력OX-Login IPs by User 사용자 별 로그인 IP 이력OX-Security SettingsAuthentication Method 사용자 인증 방식XX-Security Configuration Stauts updated-10.2.2Blue보안 설정 현황XX-QueryPie DACDatabases added-10.2.2Green DB Connection StatusDB 커넥션 현황XO-DB Connection Management HistoryDB 커넥션 추가/삭제 이력XO-DB Access ControlDB Access Control - Assigned Status DB 접근 권한 부여 현황OO-DB Access Control Logs DB 접근 권한 부여/회수 이력OO-DB Access & QueriesDB Access History DB 접근 이력OOTime RangeQuery Execution History 쿼리 실행 이력OO-Workflow Logs - DB added-10.2.2GreenDAC Type RequestsWorkflow를 통해 진행된 DAC 유형 요청XO-DAC Unmasking Requests added-11.2.0GreenWorkflow를 통해 진행된 Unmasking 요청OX-QueryPie SACServers added-10.2.2GreenServer Status서버 현황XO-Server Management History서버 추가/삭제/변경 이력XO-Server Groups added-10.2.2GreenServer Group Status서버 그룹 현황XX-Server Group Management History서버 그룹 추가/삭제/변경 이력XX-Server Account & Password added-10.2.2GreenProvisioned Server Account Status프로비저닝된 서버 계정 현황XO-Server Account Provisioning History서버 계정 프로비저닝 이력XO-Server Access ControlDirect Permission Status by User/Group updated-10.2.2Blue사용자/그룹별 Direct Permission 현황OO-Direct Permission Change History Direct Permission 변경 이력OO-Server Role Status by User deprecated-10.2.2Red사용자별 서버 역할 현황OO-Role-Granted Users by Role added-10.2.2Green역할별 역할 부여 사용자 현황OX-Accessible Servers by Role added-10.2.2Green역할별 접근 가능 서버 현황XO-Server Role & Policy Management History added-10.2.2Green서버 역할 및 정책 변경 이력XX-Server Role Grant/Revocation History 서버 역할 부여/회수 이력OO-Server Sessions and CommandsServer Access History 서버 접근 이력OOTime RangeResultAction TypeRoleCommand History 명령어 이력OOKeywordCommand Template Status 명령어 템플릿 현황XX-Command Template Management History 명령어 템플릿 관리 이력XX-Command Whitelist Grant/Revocation History 명령어 일시 허용 권한 부여/회수 이력OO-Restricted Commands 차단 명령어 이력OOKeywordWorkflow Logs - Servers added-10.2.2GreenSAC Type RequestsSAC 유형 요청XO-" + } }, { "block_index": 9, @@ -112,7 +157,11 @@ 26, 26 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "보고서 목록 조회" + } }, { "block_index": 10, @@ -123,7 +172,12 @@ 28, 584 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "Audit 메뉴에서 Reports > Reports 메뉴로 진입하면 보고서 생성 태스크 목록을 확인할 수 있습니다.", + "anchors": [] + } }, { "block_index": 11, @@ -134,7 +188,14 @@ 586, 655 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "리포트 메뉴에 접근하기 위해서는 계정에 Report Audit Full Access 관리자 권한이 부여되어야 합니다.", + "child_xpaths": [ + "macro-info[2]/p[1]" + ] + } }, { "block_index": 12, @@ -145,7 +206,12 @@ 657, 677 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 13, @@ -156,7 +222,11 @@ 679, 679 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Administrator > Audit > Reports > Reports" + } }, { "block_index": 14, @@ -167,7 +237,12 @@ 681, 681 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 15, @@ -178,7 +253,13 @@ 683, 684 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "검색 : Task Name으로 검색이 가능합니다.Create Task : 리포트 생성 페이지로 진입합니다.새로고침 : 목록을 다시 불러옵니다.Type : 보고서 생성시 지정한 유형 정보로, 다음의 세 가지가 있습니다.QueryPie SystemQueryPie DACQueryPie SACTask Name : 보고서 생성 시 입력한 태스크의 이름입니다.Scheduling : 보고서 생성 시 입력한 태스크의 반복 주기 정보로, 다음과 같은 값을 가질 수 있습니다.None : 반복 없음 (일회성으로 생성)Daily : 매일 반복Weekly : 매주 반복Monthly : 매 월 반복Quarterly : 매 분기 반복Created By : 보고서 생성 태스크를 만든 사람의 정보입니다.Created At : 보고서 생성 태스크를 만든 시점입니다.Last Task At : 마지막으로 보고서가 생성된 시점입니다. (생성된 적이 없을 경우 비어 있음)Last Task Status : 마지막 보고서 생성 태스크의 상태입니다. 다음과 같은 값을 가집니다.Succeeded : 성공In Progress : 진행 중Failed : 실패Last Report File : 버튼을 클릭하면, 마지막으로 생성된 보고서 파일을 다운로드 받을 수 있습니다.생성된 파일이 없거나, 이미 만료된 경우 비활성화 처리됩니다.파일 다운로드 시 비밀번호 지정 여부는 Security 설정에서 지정할 수 있습니다.비밀번호 지정 활성화 시, 파일을 다운로드 할 때 비밀번호를 설정합니다. 이후 파일의 압축을 해제할 때 설정한 비밀번호를 입력해야 합니다.", + "ordered": true, + "items": [] + } }, { "block_index": 16, @@ -189,7 +270,11 @@ 685, 685 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "보고서 상세 조회하기" + } }, { "block_index": 17, @@ -200,7 +285,12 @@ 688, 693 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "보고서 목록에서 상세 내용을 조회하고자 하는 항목을 선택하면 Drawer가 열리고, 상세 정보를 확인할 수 있습니다.", + "anchors": [] + } }, { "block_index": 18, @@ -211,7 +301,12 @@ 696, 720 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 19, @@ -222,7 +317,11 @@ 722, 722 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Administrator > Audit > Reports > Reports > Report Detail" + } }, { "block_index": 20, @@ -233,7 +332,12 @@ 724, 724 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 21, @@ -244,7 +348,13 @@ 727, 732 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Unschedule Task : 반복 생성하는 보고서의 경우, 스케줄링 해제가 가능합니다.스케줄이 없거나 이미 해제된 태스크의 버튼은 비활성화되어 있으며, Task Unscheduled로 표시됩니다.Type : 보고서 유형 정보를 표시합니다. (QueryPie General, DAC, SAC) 돋보기 버튼을 클릭하면 선택된 출력 항목 및 필터 정보를 확인할 수 있습니다.Scheduling : 설정된 반복 주기 정보를 표시합니다. (None, Daily, Weekly, Quarterly)Date Range : 일회성으로 생성하는 보고서의 경우 기간 범위를 표시합니다.Start Date : 반복 생성하는 보고서의 경우 태스크 시작일을 표시합니다.Next Task At : 다음으로 예정된 태스크의 일시를 표시합니다. (반복하지 않는 경우 비워둡니다)User Scope : 사용자 Scope 정보를 표시합니다.Resource Scope : 리소스 Scope 정보를 표시합니다.Report List 탭 : 생성된 보고서의 목록을 표시합니다. Report No. : 보고서 태스크 내 보고서 일련번호Created At : 보고서의 생성일시Expired At : 보고서 파일의 만료(예정)일시Status : 보고서 태스크의 상태 (Succeeded, Failed 중 하나)Report File : 파일 다운로드 링크 생성된 파일이 없거나, 이미 만료된 경우 비활성화 처리됩니다.파일 다운로드 시 비밀번호 지정 여부는 Security 설정에서 지정할 수 있습니다.비밀번호 지정 활성화 시, 파일을 다운로드 할 때 비밀번호를 설정합니다. 이후 파일의 압축을 해제할 때 설정한 비밀번호를 입력해야 합니다.Action History 탭 : 보고서에 대한 사용자의 작업 내역을 표시합니다.Action At : 작업 수행 일시Action Type : 작업 유형 (Report Download, Report Unschedule 중 하나)Target Report No. : 대상 리포트 일련번호 (없을 경우 비워져 있음)Action By : 작업자", + "ordered": true, + "items": [] + } }, { "block_index": 22, @@ -255,7 +365,11 @@ 735, 758 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "보고서 생성하기" + } }, { "block_index": 23, @@ -266,7 +380,12 @@ 760, 760 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "보고서 목록 화면에서 + Create Task 버튼을 클릭하여 보고서 생성 페이지로 진입합니다.", + "anchors": [] + } }, { "block_index": 24, @@ -277,7 +396,12 @@ 762, 762 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "태스크 이름, 보고서 유형, 스케줄링 관련 내용을 입력하고, 섹션 단위로 보고서 출력 항목 및 적용할 필터를 지정할 수 있습니다. + Add Section 버튼을 클릭하여 섹션을 추가할 수 있습니다.", + "anchors": [] + } }, { "block_index": 25, @@ -288,7 +412,11 @@ 764, 764 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Administrator > Audit > Reports > Reports > Create" + } }, { "block_index": 26, @@ -299,7 +427,13 @@ 766, 771 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Task Name : 보고서 생성 태스크를 식별할 수 있는 이름을 입력합니다.Type : 보고서 유형을 선택합니다. (QueryPie General, DAC, SAC)Scheduling : 보고서 생성 태스크의 반복 주기를 입력합니다.None : 태스크의 반복 없이 일회성으로 생성할 때 선택합니다.Daily : 매일 반복 생성하고자 할 때 선택합니다.Weekly : 매주 반복 생성하고자 할 때 선택합니다.Monthly : 매 월 반복 생성하고자 할 때 선택합니다.Quarterly : 매 분기 반복 생성하고자 할 때 선택합니다.Date Range : 이력 데이터의 기간 범위Scheduling을 None 으로 지정한 경우, 이력 데이터의 출력 범위를 지정합니다.현황 데이터는 Date Range 입력 값과 무관하게 태스크 실행 시점을 기준으로 출력됩니다.제약사항: Date Range는 최대 3개월치의 데이터만 선택할 수 있습니다. 10.2.8 버전 현재, 오늘로부터 최대 3개월 전의 데이터만 선택할 수 있습니다. 이는 10.2.9 버전에서 수정될 예정입니다.Start Date : 반복 태스크의 시작일Scheduling을 Daily, Weekly, Monthly, 또는 Quarterly 로 지정한 경우, 태스크 시작일을 지정합니다.Data : 보고서 출력 항목항목 선택 시 하단에서 데이터 기간 범위 정보를 확인할 수 있으며, Scheduling 지정 시 최초로 출력되는 보고서에 적용될 내용을 표시해줍니다.Reference Time : 현황 데이터에 적용되는 기준 시점 안내Date Range : 이력 데이터에 적용되는 기간 범위 안내Filter : 섹션에 적용할 필터 입력보고서 출력 항목 선택에 따라 적용 가능한 필터가 표시됩니다.하단 필터 항목 참고", + "ordered": true, + "items": [] + } }, { "block_index": 27, @@ -310,7 +444,12 @@ 773, 794 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "원하는 정보를 모두 입력 후 Preview 버튼을 클릭 시 출력될 보고서를 미리보기할 수 있습니다.", + "anchors": [] + } }, { "block_index": 28, @@ -321,7 +460,12 @@ 796, 796 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "Save 버튼을 클릭하여 보고서 태스크를 저장하고 목록으로 돌아갑니다.", + "anchors": [] + } }, { "block_index": 29, @@ -332,7 +476,11 @@ 798, 798 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "보고서 복제하기 10.2.2 " + } }, { "block_index": 30, @@ -343,7 +491,11 @@ 800, 800 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Administrator > Audit > Reports > Reports - Duplicate Task" + } }, { "block_index": 31, @@ -354,7 +506,12 @@ 802, 807 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "복제하려는 보고서를 목록에서 선택한 뒤 :copy: Duplicate Task 버튼을 클릭하면, 기존에 생성했던 보고서를 복제하여 새로운 보고서 태스크를 생성할 수 있습니다. ", + "anchors": [] + } }, { "block_index": 32, @@ -365,7 +522,12 @@ 809, 809 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "태스크 복제 시에 복제되는 정보는 다음과 같습니다.", + "anchors": [] + } }, { "block_index": 33, @@ -376,7 +538,13 @@ 811, 811 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Task Name - 기존 태스크 이름 뒤에 Copy가 추가됨Type - 기존 태스크에서 선택한 보고서 유형이 유지됨Scheduling - 기존 태스크에서 선택한 반복 주기가 유지됨단, Date Range 및 Start Date 는 초기화되며, 재입력이 필요함Data - 기존 태스크에서 선택한 출력 항목이 보존됨Filter - Data별로 입력한 필터가 보존됨단, 기존에 선택한 Role, User, Group이 더 이상 존재하지 않는 경우에는 필터값에서 제거됨", + "ordered": false, + "items": [] + } }, { "block_index": 34, @@ -387,7 +555,11 @@ 813, 819 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "필터" + } }, { "block_index": 35, @@ -398,7 +570,12 @@ 821, 821 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "보고서에서 지원하는 필터의 상세 스펙을 안내합니다.", + "anchors": [] + } }, { "block_index": 36, @@ -409,7 +586,13 @@ 823, 823 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "User/Group : 보고서 출력 데이터를 사용자 또는 그룹으로 필터링합니다.예: PM팀에 대해서 부여된 DB 접근 권한 보고서 출력을 원하는 경우 다음과 같이 선택Type: QueryPie DACData: DB Access Control - Assigned StatusFilter > User/Group: PM (그룹)Resource : 보고서 출력 데이터를 리소스에 부여된 태그로 필터링합니다.예: Service:Homepage 태그가 달린 DB에 대한 접근 권한 보고서 출력을 원하는 경우Type: QueryPie DACData: DB Access Control - Assigned StatusFilter > Resource: Service:Homepage태그 연산 로직은 다른 QueryPie 기능과 동일하며, 다음과 같습니다.Key가 동일한 태그 필터를 여러 개 입력하는 경우 OR 조건으로 합쳐집니다.Key가 다른 태그 필터를 여러 개 입력하는 경우 AND 조건으로 합쳐집니다.Workflow의 경우 한 번에 여러 개의 리소스에 대해 요청이 가능합니다. 이 때 요청 내에 조건을 만족하는 리소스가 한 개 이상 포함되었다면 검색결과에 포함됩니다.User Status : User Account Status 출력 시 User Account의 상태로 필터링합니다.필터값: Active, Locked, Expired, Locked ManuallyTime Range : DB Access History 또는 Server Access History 선택 시 시간 범위로 필터링합니다.시작 시간 및 종료 시간을 입력합니다.입력된 시간 범위 내의 결과를 제외할 지 여부를 선택합니다. 선택 시, 입력된 시간 범위 외의 로그만 출력됩니다.Result : Server Access History 선택 시 결과를 기준으로 필터링합니다.필터값: Success, FailureAction Type : Server Access History 선택 시 행위 유형(Action Type)을 기준으로 필터링합니다.필터값: Connect, DisconnectRole : Server Access History 선택 시 사용한 서버 역할을 기준으로 필터링합니다.복수 개의 Role을 입력하는 경우 OR 조건으로 합쳐집니다.Keyword : Command History 또는 Restricted Commands 선택 시 입력한 커맨드 기준으로 필터링합니다.키워드 입력 후 키보드에서 Enter 키를 입력하여 검색 조건을 적용합니다.입력한 키워드가 포함된 커맨드 이력만 추출됩니다. (like 검색)대소문자를 구분하지 않습니다.복수의 키워드를 입력하는 경우 OR 조건으로 합쳐집니다.", + "ordered": true, + "items": [] + } } ], "separators": [ diff --git a/confluence-mdx/tests/testcases/568918170/expected.roundtrip.json b/confluence-mdx/tests/testcases/568918170/expected.roundtrip.json index d21259b9f..8a6491958 100644 --- a/confluence-mdx/tests/testcases/568918170/expected.roundtrip.json +++ b/confluence-mdx/tests/testcases/568918170/expected.roundtrip.json @@ -1,5 +1,5 @@ { - "schema_version": "2", + "schema_version": "3", "page_id": "568918170", "mdx_sha256": "6778238a00f143a586f78c1236adc4ef83034887018ec996e1bb32023ff6c244", "source_xhtml_sha256": "dc295c914735b806617a4d44cec180f0db014894623a9e43cbb178d334c1ea16", @@ -13,7 +13,11 @@ 8, 8 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Overview" + } }, { "block_index": 1, @@ -24,7 +28,12 @@ 10, 10 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "Workflow 사용 시 편리함을 더해주는 부가 기능을 소개합니다. ", + "anchors": [] + } }, { "block_index": 2, @@ -35,7 +44,11 @@ 12, 12 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 3, @@ -46,7 +59,12 @@ 15, 15 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 4, @@ -57,7 +75,11 @@ 17, 22 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "참조된 요청 확인 처리하기" + } }, { "block_index": 5, @@ -68,7 +90,11 @@ 24, 29 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Workflow > Reviews > All Reviews > Review Details" + } }, { "block_index": 6, @@ -79,7 +105,13 @@ 33, 33 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Reviews > All Reviews 목록에서 참조 완료 처리하려는 요청을 클릭하여 상세 페이지로 진입합니다. 참조 문서는 결재 승인 상태와 관계없이(승인 전/후) 모두 열람할 수 있습니다. Review Status 가 Unread 인 요청들에 대해서만 Confirm이 가능합니다. 요청 상세 페이지 우측 상단의 Confirm 버튼을 클릭하여 Comment 입력할 수 있는 팝업창을 띄웁니다. 검토 의견을 입력 후 OK 버튼을 눌러 확인을 완료합니다. All Reviews 목록으로 돌아가면 Review Status 값이 Confirmed로 변경된 것을 확인할 수 있습니다.", + "ordered": true, + "items": [] + } }, { "block_index": 7, @@ -90,7 +122,12 @@ 35, 36 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 8, @@ -101,7 +138,12 @@ 38, 38 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 9, @@ -112,7 +154,11 @@ 40, 45 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "대리 결재 사용하기" + } }, { "block_index": 10, @@ -123,7 +169,12 @@ 47, 51 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "대리 결재란 승인자 역할을 일정 기간 동안 다른 사용자에게 위임하는 기능입니다. 사용 방법은 다음과 같습니다.", + "anchors": [] + } }, { "block_index": 11, @@ -134,7 +185,11 @@ 53, 53 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "1. 대리 결재 사용 설정하기" + } }, { "block_index": 12, @@ -145,7 +200,11 @@ 55, 55 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "User > 상단 메뉴바에서 프로필 클릭 > Preferences" + } }, { "block_index": 13, @@ -156,7 +215,13 @@ 56, 71 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Delegate Approval to : 대리 결재자 입력 Start Date : 대리 결재 시작일자End Date : 대리 결재 종료일자Delegation Reason : 대리 결재 설정 사유 입력 Save : Save 버튼을 입력하면 대리 결재 설정이 저장됩니다. 설정 시작이 당일인 경우 Save 즉시 적용됩니다.", + "ordered": false, + "items": [] + } }, { "block_index": 14, @@ -167,7 +232,11 @@ 73, 73 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "2. 대리 결재 설정 상태 확인하기" + } }, { "block_index": 15, @@ -178,7 +247,12 @@ 75, 78 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "대리 결재 설정 기간에는 원 결재자와 대리 결재자의 접속 시 상단 메뉴바에 다음과 같이 표시됩니다.", + "anchors": [] + } }, { "block_index": 16, @@ -189,7 +263,11 @@ 80, 82 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "원 결재자 기준 표시 내용" + } }, { "block_index": 17, @@ -200,7 +278,11 @@ 84, 84 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "대리 결재자 기준 표시 내용" + } }, { "block_index": 18, @@ -211,7 +293,14 @@ 86, 86 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "대리 결재 설정 기간 중 기안 요청자에게는 별도의 안내가 나타나지 않습니다. 요청자는 평소와 마찬가지로 Approval Rule 및 Approver를 선택하면 됩니다.", + "child_xpaths": [ + "macro-info[1]/p[1]" + ] + } }, { "block_index": 19, @@ -222,7 +311,11 @@ 87, 87 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "3. 위임받은 대리 결재 요청 처리하기" + } }, { "block_index": 20, @@ -233,7 +326,13 @@ 88, 104 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "대리 결재 설정 기간 동안, 대리 결재자가 확인할 수 있는 요청은 다음과 같습니다. Received Requests > To Do : 원 결재자가 수신한 미처리 요청 목록 (설정 기간 이전에 수신한 요청 포함Received Requests > Done : 대리 결재 설정 기간 내 대리 결재자가 처리한 요청을 볼 수 있습니다.상세 페이지에서 승인 또는 반려할 수 있으며, Comment 입력 시에는 대리 결재 중임을 알리는 문구가 표시됩니다. ", + "ordered": false, + "items": [] + } }, { "block_index": 21, @@ -244,7 +343,11 @@ 106, 106 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 22, @@ -255,7 +358,11 @@ 108, 108 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "4. 대리 결재 처리된 요청 건 확인하기 " + } }, { "block_index": 23, @@ -266,7 +373,13 @@ 111, 111 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "다음과 같이 요청 상세페이지 내 승인 현황에서 원 결재자 또는 대리 결재자가 처리한 것인지를 확인할 수 있습니다. ", + "ordered": false, + "items": [] + } }, { "block_index": 24, @@ -277,7 +390,12 @@ 113, 113 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "원 결재자가 처리한 경우", + "anchors": [] + } }, { "block_index": 25, @@ -288,7 +406,11 @@ 115, 120 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "대리 결재 관련 문구 없음" + } }, { "block_index": 26, @@ -299,7 +421,12 @@ 122, 122 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "대리 결재자가 처리한 경우", + "anchors": [] + } }, { "block_index": 27, @@ -310,7 +437,11 @@ 124, 125 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "대리 결재자가 Done by the delegate와 함께 표기됨" + } }, { "block_index": 28, @@ -321,7 +452,14 @@ 126, 126 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Slack DM을 통한 요청 알림을 사용 중이라면, 대리 결재자에게도 Workflow 단계별 알림이 발송됩니다. Slack DM 알림 관련 자세한 내용은 문서를 참고해주세요.", + "child_xpaths": [ + "macro-info[2]/p[1]" + ] + } }, { "block_index": 29, @@ -332,7 +470,11 @@ 128, 131 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "5. 대리 결재 설정 종료 이후의 기안 조회 방법" + } }, { "block_index": 30, @@ -343,7 +485,13 @@ 0, 0 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "대리 결재 설정 기간이 종료되었다 하더라도 Received Requests > Done 목록에서, 대리 결재 설정 기간 내 대리 결재자가 처리한 요청을 확인할 수 있습니다. 그 이외의 요청들은 더 이상 조회하거나 처리할 수 없습니다. ", + "ordered": false, + "items": [] + } }, { "block_index": 31, @@ -354,7 +502,12 @@ 0, 0 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 32, @@ -365,7 +518,11 @@ 0, 0 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "기존 요청 건 재상신하기 " + } }, { "block_index": 33, @@ -376,7 +533,12 @@ 0, 0 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "동일한 내용으로 반복적인 결재 요청을 하거나 반려 처리된 결재 건에 대해 다음과 같이 재상신할 수 있습니다. ", + "anchors": [] + } }, { "block_index": 34, @@ -387,7 +549,11 @@ 0, 0 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Workflow > Sent Requests > Done > List Details" + } }, { "block_index": 35, @@ -398,7 +564,13 @@ 0, 0 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Sent Requests > Done 목록에서 재상신을 원하는 요청 건을 클릭하여 상세 페이지로 진입합니다. ", + "ordered": true, + "items": [] + } }, { "block_index": 36, @@ -409,7 +581,14 @@ 0, 0 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "이때 이미 삭제된 데이터(결재 규칙, 승인자, 서버, 계정 등)는 Deleted로 표시되는 것을 확인할 수 있으며, 삭제된 값은 재상신 시 제외하고 수정된 값은 현재 기준으로 불러오게 됩니다.", + "child_xpaths": [ + "macro-info[3]/p[1]" + ] + } }, { "block_index": 37, @@ -420,7 +599,13 @@ 0, 0 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "우측 상단에 있는 Resubmit 버튼을 클릭하여 재상신 페이지로 진입합니다. 재상신 페이지 내에서 수정을 원하는 값이 있을 경우 수정을 합니다. Save 버튼을 통해 저장합니다.Workflow > Sent Request > In Progress 메뉴에서 본인의 요청 내역과 승인 현황을 확인할 수 있습니다.", + "ordered": true, + "items": [] + } } ], "separators": [ diff --git a/confluence-mdx/tests/testcases/692355151/expected.roundtrip.json b/confluence-mdx/tests/testcases/692355151/expected.roundtrip.json index 185a2ce32..f699983c3 100644 --- a/confluence-mdx/tests/testcases/692355151/expected.roundtrip.json +++ b/confluence-mdx/tests/testcases/692355151/expected.roundtrip.json @@ -1,5 +1,5 @@ { - "schema_version": "2", + "schema_version": "3", "page_id": "692355151", "mdx_sha256": "1397eacd6a21942abccc6cf0df0378d17cfb442ca88b40451bb37d82c3da6e20", "source_xhtml_sha256": "b9e0646039c7414bbc75057fcec55a2d80783ca2b6767f0fb6147dde5920d6dd", @@ -13,7 +13,15 @@ 8, 8 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "10.2.0 부터 추가된 기능입니다. (10.2.0 기준 MySQL만 지원합니다.) 11.2.0 부터 관리자가 이 기능을 비활성화 할 수 있도록 변경되었습니다. 사용자가 이 기능을 사용할 수 없는 경우 관리자가 정책적으로 비활성화한 것입니다.", + "child_xpaths": [ + "macro-info[1]/p[1]", + "macro-info[1]/p[2]" + ] + } }, { "block_index": 1, @@ -24,7 +32,11 @@ 10, 14 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Overview" + } }, { "block_index": 2, @@ -35,7 +47,12 @@ 15, 15 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "사용자가 Workflow를 통해 SQL Request를 요청할 때 해당 쿼리에 대해 DBMS에서 제공하는 실행 계획(Explain 구문)을 사용하여 쿼리에 대한 제한적인 절차 정보를 확인할 수 있습니다. 이 기능은 DBMS에 종속된 기능이므로 Workflow의 SQL Request 작성할 때 커넥션에 해당하는 DBMS 마다 차이가 있을 수 있습니다. ", + "anchors": [] + } }, { "block_index": 3, @@ -46,7 +63,14 @@ 17, 17 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "실행계획이란?데이터베이스의 옵티마이저가 가장 최적의 쿼리를 수행하기 위해 생성하는 일련의 작업세트 또는 작업 절차입니다. 단계적으로 수행 단계를 볼 수 있고 단계별 비용을 볼 수 있습니다.", + "child_xpaths": [ + "macro-info[2]/p[1]" + ] + } }, { "block_index": 4, @@ -57,7 +81,11 @@ 19, 20 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "SQL Request 작성 및 실행계획(Explain) 수행하기" + } }, { "block_index": 5, @@ -68,7 +96,12 @@ 22, 24 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "QueryPie Workflow에서 SQL Request를 작성하는 기본 방법은 동일합니다. (참고 : )", + "anchors": [] + } }, { "block_index": 6, @@ -79,7 +112,13 @@ 25, 25 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": " MySQL Connection 및 Database 선택 쿼리를 실행할 MySQL Connection 과 Database를 선택하고 쿼리를 입력합니다. ", + "ordered": true, + "items": [] + } }, { "block_index": 7, @@ -90,7 +129,14 @@ 27, 27 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Content Type은 “Text”만 지원합니다. “File”을 선택하면 실행 계획(Explain)기능을 사용할 수 없습니다.", + "child_xpaths": [ + "macro-note[1]/p[1]" + ] + } }, { "block_index": 8, @@ -101,7 +147,11 @@ 29, 30 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 9, @@ -112,7 +162,13 @@ 32, 32 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "실행계획(Explain) 옵션 및 실행계획 수행쿼리 에디터 하단에 있는 옵션을 선택하고 Explain 버튼을 누릅니다.Attach Explain Results : 실행계획의 결과를 SQL Request 에 첨부 여부를 선택하는 스위치입니다. (기본값은 OFF 입니다. 필요한 경우 스위치를 ON으로 전환하여 실행 계획 결과를 첨부해야 합니다.) 결과 표시 방식 선택Table : 테이블 형태로 결과가 표시됩니다.JSON : JSON 형태로 결과가 표시됩니다. ", + "ordered": true, + "items": [] + } }, { "block_index": 10, @@ -123,7 +179,11 @@ 34, 36 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 11, @@ -134,7 +194,14 @@ 37, 37 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "실행계획 대상이 되는 쿼리(세미콜론으로 끝나는 구문)는 100개로 제한됩니다. MySQL은 5.6 이후 부터 Explain의 결과를 JSON 포맷으로 출력할 수 있습니다. 따라서 5.6 이전 버전을 대상으로 Explain을 수행시 JSON으로 출력할 수 없습니다.", + "child_xpaths": [ + "macro-note[2]/ul[1]" + ] + } }, { "block_index": 12, @@ -145,7 +212,13 @@ 39, 41 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "실행계획(Explain) 결과 확인Table 형태의 결과 확인아래 그림과 같은 결과가 표시됩니다. 각 필드의 의미는 MySQL reference 문서를 참고 바랍니다.JSON 형태의 결과 확인JSON 을 선택하고 Explain 버튼을 누르면 아래 그림과 같은 결과가 표시됩니다. {JSON} 을 클릭하면 전체 JSON 내용을 확인 할 수 있습니다. 내보내기(Export)를 할 때 관리자 설정에 따라 암호 입력을 요구하는 팝업이 출력될 수 있습니다.", + "ordered": true, + "items": [] + } }, { "block_index": 13, @@ -156,7 +229,11 @@ 43, 47 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "기타 참고사항 " + } }, { "block_index": 14, @@ -167,7 +244,13 @@ 49, 51 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "결재자(Approver)의 실행계획(Explain)결과 확인결재자는 Explain Results 탭에서 첨부된 실행계획(Explain)결과를 확인하고 승인의 참고자료로 활용할 수 있습니다.", + "ordered": true, + "items": [] + } }, { "block_index": 15, @@ -178,7 +261,14 @@ 53, 53 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "결재자가 볼 수 있는 실행계획(Explain) 결과는 상신자가 실행계획을 수행할 때 선택한 포맷으로만 볼 수 있습니다. 즉, 상신자가 실행계획을 JSON 형태로 출력하도록 하고 첨부하여 상신했다면 결재자는 JSON 형태의 결과만 볼 수 있고 출력 형태를 테이블로 바꿀 수 없습니다.", + "child_xpaths": [ + "macro-note[3]/p[1]" + ] + } }, { "block_index": 16, @@ -189,7 +279,13 @@ 54, 55 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "MySQL에서 실행계획(Explain)을 통해 검토 가능한 구문MySQL은 버전별로 실행계획을 통해 분석하는 지원 구문이 다릅니다. ", + "ordered": true, + "items": [] + } }, { "block_index": 17, @@ -200,7 +296,11 @@ 56, 59 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "table", + "old_plain_text": "MySQL 5.6.3 이전MySQL 5.6.3 이후SELECTSELECT , DELETE , INSERT , REPLACE , UPDATE" + } }, { "block_index": 18, @@ -211,7 +311,14 @@ 61, 78 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "optimizer_trace, analyze 는 SQL Request 에서 제공하는 실행계획(Explain) 수행 기능에서 지원하지 않습니다.", + "child_xpaths": [ + "macro-note[4]/p[1]" + ] + } } ], "separators": [ diff --git a/confluence-mdx/tests/testcases/793608206/expected.roundtrip.json b/confluence-mdx/tests/testcases/793608206/expected.roundtrip.json index e1d60f40e..b33169897 100644 --- a/confluence-mdx/tests/testcases/793608206/expected.roundtrip.json +++ b/confluence-mdx/tests/testcases/793608206/expected.roundtrip.json @@ -1,5 +1,5 @@ { - "schema_version": "2", + "schema_version": "3", "page_id": "793608206", "mdx_sha256": "e09ae89468435eeaee1732434a330f5bf2def6f875feec5d5d65adc6b7a83243", "source_xhtml_sha256": "1fe717056eda284c8e41946fa98b25a00dae0e421798cccd34abcd2c0ae80c3c", @@ -13,7 +13,12 @@ 6, 6 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "Alert Type을 New Request로 선택한 경우, Request Type에 따른 템플릿 변수 지원 내용은 다음과 같습니다.", + "anchors": [] + } }, { "block_index": 1, @@ -24,7 +29,11 @@ 8, 8 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "공통 변수" + } }, { "block_index": 2, @@ -35,7 +44,12 @@ 10, 10 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "모든 요청 타입에서 지원하는 변수 목록입니다.", + "anchors": [] + } }, { "block_index": 3, @@ -46,7 +60,12 @@ 12, 12 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "Request Type을 전체 선택한 경우에도 공통 변수는 사용 가능합니다.", + "anchors": [] + } }, { "block_index": 4, @@ -57,7 +76,11 @@ 14, 14 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "table", + "old_plain_text": "변수명변수설명추가 시점Request Title {{requestTitle}} 요청 제목예: 서비스 DB 접근 요청-Assignees {{assignees}} 요청 승인 담당자예: STEP 1(Katie Wells), STEP 2(Kenny Park), STEP 3(Brant Hwang)-Comment {{comment}} 요청 코멘트예: 서비스 운영 목적으로 DB 접근 권한 요청드립니다.-User Name {{userName}} 요청자 이름 예: Keira Yoon-Email {{email}} 요청자 이메일예: keira@email.com-Access Type {{accessType}} 요청 유형DB Access RequestSQL RequestSQL Export RequestUnmasking RequestServer Access RequestAccess Role Request-Link {{link}} 요청 상세 페이지 링크예: https://{querypie_domain}/workflow/received-requests/to-do/detail/{workflow_uuid}-Workflow Uuid {{workflowUuid}} 요청 UUID 예: b63f9f94-11e5-4095-a399-be7d96e9ce06-Workflow Id {{id}} 요청 ID예: 12-Urgent {{urgent}} 긴급 요청 여부true 또는 false10.2.0" + } }, { "block_index": 5, @@ -68,7 +91,11 @@ 16, 194 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "DB Access Request" + } }, { "block_index": 6, @@ -79,7 +106,11 @@ 196, 196 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "table", + "old_plain_text": "변수명변수설명추가 시점Expiration Date {{expirationDate}} 만료 일자10.2.211.2.0 - DeprecatedApproval Expiration Date{{approvalExpirationDate}}승인 만료일자11.2.0Access Expiration Date{{accessExpirationDate}}권한 만료일자11.2.0Requested Privilege per Connection {{connectionPrivilegeNames}} 커넥션 별 요청된 Privilege를 목록으로 표시예:Connection A : Read/WriteConnection B : Read-Only10.2.2" + } }, { "block_index": 7, @@ -90,7 +121,11 @@ 198, 281 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "SQL Request" + } }, { "block_index": 8, @@ -101,7 +136,11 @@ 283, 283 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "table", + "old_plain_text": "변수명변수설명추가 시점SQL Text {{sqlText}} SQL 구문최대 100자 (TEXT, FILE 동일)10.2.2Content Type {{contentType}} SQL 구문 입력 방식TEXT 또는 FILE10.2.2Cluster Group Uuid {{clusterGroupUuid}} DB 커넥션 UUID10.2.2Cluster Group Name {{clusterGroupName}} DB 커넥션 이름10.2.2Cluster Id {{clusterId}} DB 커넥션 ID10.2.2Database Type {{databaseType}} 데이터베이스 유형10.2.2Database Name {{databaseName}} 데이터베이스 이름10.2.2Use Ledger Approval Rule {{useLedgerApprovalRule}}원장 승인 규칙 사용 여부10.2.2Target Date {{targetDate}} 실행 목표 일자10.2.211.2.0 - DeprecatedPreferred Execution Date{{preferredExecutionDate}}실행 목표 일자11.2.0Due Date {{dueDate}} 승인/실행 만료 일자10.2.211.2.0 - DeprecatedApproval Expiration Date{{approvalExpirationDate}}승인 만료 일자11.2.0Execution Expiration Date{{executionExpirationDate}}실행 만료 일자11.2.0" + } }, { "block_index": 9, @@ -112,7 +151,11 @@ 285, 494 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "SQL Export Request" + } }, { "block_index": 10, @@ -123,7 +166,11 @@ 496, 496 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "table", + "old_plain_text": "변수명변수설명추가 시점Cluster Group Uuid {{clusterGroupUuid}} DB 커넥션 UUID10.2.2Cluster Group Name {{clusterGroupName}} DB 커넥션 이름10.2.2Cluster Id {{clusterId}} DB 커넥션 ID10.2.2Database Type {{databaseType}} 데이터베이스 유형10.2.2Database Name {{databaseName}} 데이터베이스 이름10.2.2Export Type {{exportType}} 내보내기 유형QUERY 또는 TABLE10.2.2Export Detail {{exportDetail}} 내보내기 세부 정보Query인 경우, 최대 100자의 쿼리Table인 경우, 테이블 이름10.2.2Target Date {{targetDate}} 실행 목표 일자10.2.2Preferred Execution Date{{preferredExecutionDate}}실행 목표 일자11.2.0Due Date {{dueDate}} 승인/실행 만료 일자10.2.2Approval Expiration Date{{approvalExpirationDate}}승인 만료 일자11.2.0Execution Expiration Date{{executionExpirationDate}}실행 만료 일자11.2.0" + } }, { "block_index": 11, @@ -134,7 +181,11 @@ 498, 692 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Unmasking Request" + } }, { "block_index": 12, @@ -145,7 +196,11 @@ 694, 694 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "table", + "old_plain_text": "변수명변수설명추가 시점Cluster Group Uuid {{clusterGroupUuid}} DB 커넥션 UUID10.2.2Cluster Group Name {{clusterGroupName}} DB 커넥션 이름10.2.2Database Type {{databaseType}} 데이터베이스 유형10.2.2Database Name {{databaseName}} 데이터베이스 이름10.2.2Table Name {{tableName}} 테이블 이름10.2.2Column Names {{columnNames}} 컬럼 이름을 목록으로 표시예: [actor_id, first_name, last_name, last_update]10.2.2Unmasking Expiration {{unmaskingExpiration}} 마스킹 해제 만료 시점 예: 10 Minutes2024-12-03T14:59:00 10.2.2Approval Expiration Date{{approvalExpirationDate}}승인 만료 일자11.2.0" + } }, { "block_index": 13, @@ -156,7 +211,11 @@ 696, 835 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Restricted Data Access Request" + } }, { "block_index": 14, @@ -167,7 +226,11 @@ 837, 837 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "table", + "old_plain_text": "변수명변수설명추가 시점Cluster Group Uuid {{clusterGroupUuid}} DB 커넥션 UUID11.0.0Cluster Group Name {{clusterGroupName}} DB 커넥션 이름11.0.0Database Type {{databaseType}} 데이터베이스 유형11.0.0Database Name {{databaseName}} 데이터베이스 이름11.0.0Table Name {{tableName}} 테이블 이름11.0.0Column Names {{columnNames}} 컬럼 이름을 목록으로 표시예: [actor_id, first_name, last_name, last_update]11.0.0Restricted Data Access Expiration{{restrictedDataAccessExpiration}} 접근 허용 만료 시점 예: 10 Minutes2024-12-03T14:59:00 11.0.0Approval Expiration Date{{approvalExpirationDate}}승인 만료 일자11.2.0" + } }, { "block_index": 15, @@ -178,7 +241,11 @@ 839, 978 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "DB Policy Exception Request" + } }, { "block_index": 16, @@ -189,7 +256,11 @@ 980, 980 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "table", + "old_plain_text": "변수명변수설명추가 시점Cluster Group Uuid {{clusterGroupUuid}} DB 커넥션 UUID11.0.0Cluster Group Name {{clusterGroupName}} DB 커넥션 이름11.0.0Data Scope Type{{dataScopeType}}Data Path와 Tag중 하나를 선택11.0.0Database Type {{databaseType}} 데이터베이스 유형11.0.0Database Name {{databaseName}} 데이터베이스 이름11.0.0Table Name {{tableName}} 테이블 이름11.0.0Column Names {{columnNames}} 컬럼 이름을 목록으로 표시예: [actor_id, first_name, last_name, last_update]11.0.0Expiration{{expiration}} 예외 허용 만료 시점 예: 10 Minutes2024-12-03T14:59:00 11.0.0Approval Expiration Date{{approvalExpirationDate}}승인 만료 일자11.2.0" + } }, { "block_index": 17, @@ -200,7 +271,11 @@ 982, 1135 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Server Access Request" + } }, { "block_index": 18, @@ -211,7 +286,11 @@ 1137, 1137 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "table", + "old_plain_text": "변수명변수설명추가 시점Server Group Name {{serverGroupName}} 서버 그룹 이름10.2.2Server Names {{serverNames}} 서버 이름을 목록으로 표시예: Server AServer B10.2.2Server Accounts {{serverAccounts}} 서버 계정을 목록으로 표시예: devec2-user10.2.2Expiration Date {{expirationDate}} 만료 일자10.2.211.2.0 - DeprecatedApproval Expiration Date{{approvalExpirationDate}}승인 만료 일자11.2.0Access Expiration Date{{accessExpirationDate}}권한 만료일자11.2.0Trigger Condition{{triggerCondition}}권한 부여 시작 조건11.2.0Duration{{duration}}분(Minutes) 단위 서버 접근 권한이 유효한 시간11.2.0" + } }, { "block_index": 19, @@ -222,7 +301,11 @@ 1139, 1281 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Server Privilege Request" + } }, { "block_index": 20, @@ -233,7 +316,11 @@ 1283, 1283 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "table", + "old_plain_text": "변수명변수설명추가 시점Server Group Name {{serverGroupName}} 서버 그룹 이름10.2.2Server Names {{serverNames}} 서버 이름을 목록으로 표시예: Server AServer B10.2.2Server Accounts {{serverAccounts}} 서버 계정을 목록으로 표시예: devec2-user10.2.2Expiration Date {{expirationDate}} 만료 일자10.2.211.2.0 - DeprecatedApproval Expiration Date{{approvalExpirationDate}}승인 만료 일자11.2.0Privilege Expiration Date{{accessExpirationDate}}권한 만료일자11.2.0Trigger Condition{{triggerCondition}}권한 부여 시작 조건11.2.0Duration{{duration}}분(Minutes) 단위 서버 특권이 유효한 시간11.2.0" + } }, { "block_index": 21, @@ -244,7 +331,11 @@ 1285, 1427 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Access Role Request" + } }, { "block_index": 22, @@ -255,7 +346,11 @@ 1429, 1429 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "table", + "old_plain_text": "변수명변수설명추가 시점Service {{service}} 역할 요청 대상 서비스SAC, KAC, WAC10.2.2Role Name {{roleName}} 역할 이름10.2.2Expiration Date {{expirationDate}} 만료 일자10.2.211.2.0 - DeprecatedApproval Expiration Date{{approvalExpirationDate}}승인 만료 일자11.2.0Role Expiration Date{{roleExpirationDate}}권한 만료 일자 11.2.0" + } }, { "block_index": 23, @@ -266,7 +361,11 @@ 1431, 1526 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Web APP Just-In-Time Access Request" + } }, { "block_index": 24, @@ -277,7 +376,11 @@ 1528, 1528 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "table", + "old_plain_text": "변수명변수설명추가 시점Approval Expiration Date{{approvalExpirationDate}}승인 만료 일자11.2.0Web App Name{{webAppName}}요청 대상 웹앱11.0.0Access Duration{{accessDuration}} 분(Minutes) 단위 웹앱 접근 권한이 유효한 시간11.0.0" + } }, { "block_index": 25, @@ -288,7 +391,11 @@ 1530, 1534 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "IP Registration Request" + } }, { "block_index": 26, @@ -299,7 +406,11 @@ 1536, 1536 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "table", + "old_plain_text": "변수명변수설명추가 시점Approval Expiration Date{{approvalExpirationDate}}승인 만료 일자11.2.0IP Addresses {{ipAddresses}} IP 주소 11.0.0" + } } ], "separators": [ diff --git a/confluence-mdx/tests/testcases/880181257/expected.roundtrip.json b/confluence-mdx/tests/testcases/880181257/expected.roundtrip.json index 5b3676786..2c4ff9917 100644 --- a/confluence-mdx/tests/testcases/880181257/expected.roundtrip.json +++ b/confluence-mdx/tests/testcases/880181257/expected.roundtrip.json @@ -1,5 +1,5 @@ { - "schema_version": "2", + "schema_version": "3", "page_id": "880181257", "mdx_sha256": "405d6c970e8c71f6de383207fa8d39dbb24eee622d251f0e07a3065fbc5131d1", "source_xhtml_sha256": "dd6df6dbfae4e50c7b53823a9bbfbc3d1bc4119240c4155d9c4b3a276a28e3b9", @@ -13,7 +13,11 @@ 8, 8 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 1, @@ -24,7 +28,11 @@ 10, 10 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Custom Data Source 접근 요청" + } }, { "block_index": 2, @@ -35,7 +43,13 @@ 12, 25 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "사용자 화면 상단 Workflow 메뉴로 이동합니다.좌측 상단 Submit Request 버튼 클릭후 DB Access Request 항목을 선택합니다.Approval Rule을 상황에 맞게 설정하고 Title도 작성합니다.DB Connection 목록에서 Type이 'Custom Data Source'인 항목을 선택합니다.다른 벤더들과 다르게 Privilege를 선택할 수 없기 때문에 “-”로 표기됩니다. Expiration Date과 Reason for Request 항목도 상황에 맞게 설정 및 작성합니다.작성을 완료했다면 하단 Submit버튼을 클릭하여 신청을 완료합니다. 관리자의 승인을 받았다면 좌측 Workflow > Sent Request > Done 메뉴에서 확인할 수 있습니다.", + "ordered": true, + "items": [] + } }, { "block_index": 3, @@ -46,7 +60,15 @@ 27, 28 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "Custom Data Source Type 외 다중 요청 시 주의사항 Custom Data Source와 다른 벤더를 함께 선택하는 경우 권한 타입이 다르기 때문에 일괄적으로 선택할 수 없습니다. 이 경우 다음 메시지가 표시됩니다: \"Privileges cannot be applied across different types at once (e.g., General, Redis, Custom Data Source). Please assign privileges separately for each connection.\"같은 권한 타입의 커넥션끼리는 일괄 적용이 가능합니다.", + "child_xpaths": [ + "macro-note[1]/p[1]", + "macro-note[1]/ul[1]" + ] + } }, { "block_index": 4, @@ -57,7 +79,11 @@ 29, 31 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Agent를 통한 접속" + } }, { "block_index": 5, @@ -68,7 +94,13 @@ 32, 32 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "QueryPie Agent 실행QueryPie Agent를 실행합니다.Database 항목에서 Custom Data Source를 확인할 수 있습니다.Connection Guide 확인Agent에서 Custom Data Source 커넥션 행을 클릭하면 Port 정보를 담은 행이 보입니다.위 행을 우클릭을 한뒤 Connection Guide를 클릭하면 접속 정보를 확인할 수 있습니다.UID/PW는 수정할 수 없는 db username, db password로 표시됩니다.Privileges는 \"-\"로 표시됩니다.SQL Tool에서 접속DataGrip이나 기타 SQL Tool을 실행합니다.Host와 Port는 Connection Guide에 명시되어있는 정보를 입력해야합니다. 벤더에 따라 추가 정보를 입력합니다.", + "ordered": true, + "items": [] + } }, { "block_index": 6, @@ -79,7 +111,15 @@ 34, 34 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "주의사항 관리자가 설정한 접근 가능 시간 외에 접속 시도 시 에러 메시지가 표시됩니다.", + "child_xpaths": [ + "macro-note[2]/p[1]", + "macro-note[2]/ul[1]" + ] + } }, { "block_index": 7, @@ -90,7 +130,11 @@ 36, 56 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "웹으로 접속 시도하는 경우" + } }, { "block_index": 8, @@ -101,7 +145,13 @@ 58, 59 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "웹 커넥션 목록 확인사용자 화면 상단 Databases를 클릭합니다. 좌측 커넥션 목록에서 QueryPie Connections를 클릭하면 접근 권한이 있는 Custom Data Source가 표시됩니다.접속 불가 안내Custom Data Source 선택 시 Connect 버튼이 비활성화됩니다.아래와 같은 툴팁이 표시되며 웹으로는 접속이 불가합니다. \"Access is only possible through a proxy and cannot be accessed through the web. Please open the QueryPie Agent to connect to the Custom Data Source.\"", + "ordered": true, + "items": [] + } } ], "separators": [ diff --git a/confluence-mdx/tests/testcases/883654669/expected.roundtrip.json b/confluence-mdx/tests/testcases/883654669/expected.roundtrip.json index 454b0aeef..41b0a4b4a 100644 --- a/confluence-mdx/tests/testcases/883654669/expected.roundtrip.json +++ b/confluence-mdx/tests/testcases/883654669/expected.roundtrip.json @@ -1,5 +1,5 @@ { - "schema_version": "2", + "schema_version": "3", "page_id": "883654669", "mdx_sha256": "59e5ebfe4b348dc33ff27d394f61940996f58303cb23dd73fc90411e8436879e", "source_xhtml_sha256": "4c79226b0a847e7cc4faf0b39127a7b02510347c4653b0e18129de8c59e7e1f7", @@ -13,7 +13,11 @@ 8, 8 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Overview" + } }, { "block_index": 1, @@ -24,7 +28,12 @@ 10, 10 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "Slack App을 QueryPie에 연동하고, QueryPie로부터 Direct Message 알림을 수신할 수 있습니다.", + "anchors": [] + } }, { "block_index": 2, @@ -35,7 +44,15 @@ 12, 12 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "본 문서는 10.2.6 또는 그 이상의 버전에 적용됩니다. 10.2.5 또는 그 이하 버전의 Slack 연동 방법은 10.1.0 버전 매뉴얼 문서를 참고해주세요.", + "child_xpaths": [ + "macro-info[1]/p[1]", + "macro-info[1]/p[2]" + ] + } }, { "block_index": 3, @@ -46,7 +63,11 @@ 14, 16 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 4, @@ -57,7 +78,11 @@ 17, 17 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "구성 시 사전에 필요한 권한" + } }, { "block_index": 5, @@ -68,7 +93,13 @@ 19, 19 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Slack Workspace 관리자 권한 계정 (Slack Workspace에 App을 설치하기 위해 필요)QueryPie Owner 및 Approval Admin 권한 계정", + "ordered": false, + "items": [] + } }, { "block_index": 6, @@ -79,7 +110,11 @@ 21, 22 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Slack API 에서 DM App 생성 (via Manifest Files)" + } }, { "block_index": 7, @@ -90,7 +125,12 @@ 24, 24 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "App Manifest를 이용하여 QueryPie DM 연동 전용 Slack App을 생성합니다.", + "anchors": [] + } }, { "block_index": 8, @@ -101,7 +141,12 @@ 26, 26 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 9, @@ -112,7 +157,13 @@ 29, 75 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "https://api.slack.com/apps 으로 이동하여 Create an App 을 클릭합니다.Create an app 모달에서, App 생성 방식을 선택합니다. From a manifest 를 클릭합니다.Pick a workspace 모달에서 QueryPie와 연동할 Slack Workspace를 선택한 뒤 다음 단계로 진행합니다.Create app from manifest 모달에서 JSON 형식의 App Manifest를 입력합니다. 미리 채워져 있는 내용들을 삭제하고 아래의 App Manifest를 붙여넣은 뒤 다음 단계로 진행합니다.:light_bulb_on: {{..}} 안의 값은 원하는 값으로 변경해 주세요.설정 내용을 검토하고 Create 버튼을 클릭하여 App 생성을 완료합니다.", + "ordered": true, + "items": [] + } }, { "block_index": 10, @@ -123,7 +174,11 @@ 77, 77 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Slack Workspace에 Slack App 설치" + } }, { "block_index": 11, @@ -134,7 +189,13 @@ 79, 87 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Settings > Install App에서 Install to Workspace 버튼을 클릭하여 생성된 앱을 Slack Workspace에 설치합니다. 권한 요청 페이지에서, 허용을 클릭합니다.설정한 Slack Workspace에서 Slack DM 전용 App이 생성된 것을 확인할 수 있습니다.", + "ordered": true, + "items": [] + } }, { "block_index": 12, @@ -145,7 +206,11 @@ 89, 89 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Bot User OAuth Token 획득" + } }, { "block_index": 13, @@ -156,7 +221,12 @@ 91, 91 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "Features > OAuth & Permissions 메뉴 내 OAuth Tokens for Your Workspace 섹션에서, Bot User OAuth Token 을 복사하여 기록해 둡니다.", + "anchors": [] + } }, { "block_index": 14, @@ -167,7 +237,11 @@ 93, 95 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 15, @@ -178,7 +252,12 @@ 98, 98 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 16, @@ -189,7 +268,11 @@ 100, 101 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "App-Level Token 생성" + } }, { "block_index": 17, @@ -200,7 +283,15 @@ 102, 103 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "App manifest로 Slack app을 생성할 때 Socket Mode와 관련 권한들을 활성화할 수는 있지만, App Level Token(xapp-)은 자동으로 생성되지 않습니다. App Level Token은 보안 자격 증명(security credential)이기 때문에 manifest로 자동 생성/노출되면 보안상 위험App manifest는 앱의 구성(configuration)만 정의하고, 실제 인증 토큰들은 별도로 생성/관리됨", + "child_xpaths": [ + "macro-info[2]/p[1]", + "macro-info[2]/ul[1]" + ] + } }, { "block_index": 18, @@ -211,7 +302,12 @@ 104, 104 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "다음 단계를 수행하여 App-Level Token을 수동으로 생성합니다.", + "anchors": [] + } }, { "block_index": 19, @@ -222,7 +318,13 @@ 106, 106 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "앱 설정 페이지의 Settings > Basic Information 메뉴의 App-Level Tokens 섹션으로 이동하고, Generate Token and Scope 버튼을 클릭합니다.Generate an app-level token 모달에서, Add Scope 버튼을 클릭한 뒤 connections:write 를 추가합니다.생성된 App-Level Token 모달에서, 앱 토큰을 복사하여 따로 기록해 둡니다. App-Level Token은 xapp- 으로 시작합니다.", + "ordered": true, + "items": [] + } }, { "block_index": 20, @@ -233,7 +335,11 @@ 108, 116 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "QueryPie에서 Slack DM 설정하기" + } }, { "block_index": 21, @@ -244,7 +350,13 @@ 118, 118 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Admin > General > System > Integrations > Slack 메뉴로 진입한 뒤, Configure 버튼을 클릭하여 설정 모달을 엽니다.설정 모달에 아까 기록해둔 App Token과 Bot User OAuth Token을 입력합니다.추가 설정 값은 다음과 같습니다. DM으로 Workflow 알림을 받고 메시지 안에서 승인/거절을 수행하려면 모든 설정 토글을 활성화 합니다.Send Workflow Notification via Slack DM : 워크플로우 요청에 대한 Slack DM 전송 활성화Allow Users to approve or reject on Slack DM : Slack DM 내에서 승인 또는 거절 기능 활성화Save 버튼을 누르고 설정을 완료합니다.", + "ordered": true, + "items": [] + } }, { "block_index": 22, @@ -255,7 +367,11 @@ 120, 137 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Slack DM 설정 관리하기" + } }, { "block_index": 23, @@ -266,7 +382,13 @@ 139, 139 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "Slack Configuration을 등록한 뒤, 현재 설정 상태를 화면에서 확인할 수 있습니다.Edit 버튼을 클릭하여 입력한 설정을 변경할 수 있습니다.", + "ordered": true, + "items": [] + } }, { "block_index": 24, @@ -277,7 +399,12 @@ 141, 154 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } }, { "block_index": 25, @@ -288,7 +415,11 @@ 157, 157 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Workflow 요청 시 Slack DM 테스트" + } }, { "block_index": 26, @@ -299,7 +430,12 @@ 159, 159 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "DB Access Request를 예시로, Slack DM 기능이 정상적으로 작동하는지 테스트를 수행합니다.", + "anchors": [] + } }, { "block_index": 27, @@ -310,7 +446,13 @@ 161, 172 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "QueryPie User > Workflow 페이지에서 Submit Request 버튼을 클릭한 뒤, DB Access Request를 선택하여 요청 작성 화면으로 진입합니다. 요청 작성 화면에서 필요한 정보를 입력하고, 요청을 상신합니다.승인자는 앞에서 추가한 Slack App과의 DM으로 승인해야 할 요청 알림을 수신할 수 있습니다.Allow Users to approve or reject on Slack DM 설정이 켜져 있으므로, DM에서 직접 사유를 입력하고 요청을 승인 또는 거절할 수 있습니다.DM에서 Details 버튼을 클릭 시, QueryPie Admin에서 결재 요청에 대한 상세 내용을 확인하고 승인 또는 거절할 수 있습니다.", + "ordered": true, + "items": [] + } } ], "separators": [ diff --git a/confluence-mdx/tests/testcases/lists/expected.roundtrip.json b/confluence-mdx/tests/testcases/lists/expected.roundtrip.json index 54dfed3e7..e5c61f3d2 100644 --- a/confluence-mdx/tests/testcases/lists/expected.roundtrip.json +++ b/confluence-mdx/tests/testcases/lists/expected.roundtrip.json @@ -1,5 +1,5 @@ { - "schema_version": "2", + "schema_version": "3", "page_id": "lists", "mdx_sha256": "67e914c155a6e962cae05eb72f50df55353bade9ea51740c5f4c15a260a0d33e", "source_xhtml_sha256": "9d0d9a133f46555437bd107ea630d34d06edff8520f0c8cb5af825cfb42d1233", @@ -13,7 +13,11 @@ 1, 1 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Unordered List" + } }, { "block_index": 1, @@ -24,7 +28,13 @@ 3, 17 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "\n\none\n\n\neleven\n\ntwelve\n\n\none twenty one\n\none twenty two\n\none twenty three\n\ntwo\n\n\ntwenty one\n\n\ntwo eleven\n\ntwo twelve\n\ntwenty two\n\nthree\n\n\nthirty one\n\nthirty two\n\nfour", + "ordered": false, + "items": [] + } }, { "block_index": 2, @@ -35,7 +45,11 @@ 19, 19 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Ordered List" + } }, { "block_index": 3, @@ -46,7 +60,13 @@ 21, 31 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "\n\nhello 1\n\n\nworld 1\n\nworld 2\n\n\nlove 1\n\nlove 2\n\n\npeace 1\n\npeace 2\n\nlove 3\n\nworld 3\n\nhello 2\n\nhello 3", + "ordered": true, + "items": [] + } }, { "block_index": 4, @@ -57,7 +77,11 @@ 33, 33 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "heading", + "old_plain_text": "Mixed List" + } }, { "block_index": 5, @@ -68,7 +92,13 @@ 35, 47 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "list", + "old_plain_text": "\n\nhello 1\n\nhello 2\n\n\none\n\n\nworld 1\n\nworld 2\n\n\neleven\n\ntwelve\n\nthirteen\n\nworld 3\n\ntwo\n\nthree\n\nhello 3\n\nhello 4", + "ordered": true, + "items": [] + } } ], "separators": [ diff --git a/confluence-mdx/tests/testcases/panels/expected.roundtrip.json b/confluence-mdx/tests/testcases/panels/expected.roundtrip.json index 114eb11b6..65e714e44 100644 --- a/confluence-mdx/tests/testcases/panels/expected.roundtrip.json +++ b/confluence-mdx/tests/testcases/panels/expected.roundtrip.json @@ -1,5 +1,5 @@ { - "schema_version": "2", + "schema_version": "3", "page_id": "panels", "mdx_sha256": "4e4d1870a5a1a31fdc00643d1209fddc22646f27cbf2dcc39c9d9409fe4a86f1", "source_xhtml_sha256": "ac1ec9ed15afad6a00c3a4337fc7393cd4bb7a899662a6ddc71f293b84465c0e", @@ -13,7 +13,14 @@ 3, 4 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "This is an info panel.", + "child_xpaths": [ + "macro-info[1]/p[1]" + ] + } }, { "block_index": 1, @@ -24,7 +31,11 @@ 5, 6 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 2, @@ -35,7 +46,14 @@ 8, 9 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "This is a note panel.", + "child_xpaths": [ + "ac:adf-extension[1]/p[1]" + ] + } }, { "block_index": 3, @@ -46,7 +64,11 @@ 10, 11 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 4, @@ -57,7 +79,14 @@ 13, 14 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "This is an error panel.", + "child_xpaths": [ + "macro-warning[1]/p[1]" + ] + } }, { "block_index": 5, @@ -68,7 +97,11 @@ 15, 16 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 6, @@ -79,7 +112,14 @@ 18, 19 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "This is a success panel.", + "child_xpaths": [ + "macro-tip[1]/p[1]" + ] + } }, { "block_index": 7, @@ -90,7 +130,11 @@ 20, 21 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 8, @@ -101,7 +145,14 @@ 23, 24 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "This is a warning panel.", + "child_xpaths": [ + "macro-note[1]/p[1]" + ] + } }, { "block_index": 9, @@ -112,7 +163,11 @@ 25, 26 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 10, @@ -123,7 +178,14 @@ 28, 29 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "This is a custom panel.", + "child_xpaths": [ + "macro-panel[1]/p[1]" + ] + } }, { "block_index": 11, @@ -134,7 +196,11 @@ 30, 31 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "html_block", + "old_plain_text": "" + } }, { "block_index": 12, @@ -145,7 +211,12 @@ 33, 33 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "Done.", + "anchors": [] + } }, { "block_index": 13, @@ -156,7 +227,12 @@ 0, 0 ], - "lost_info": {} + "lost_info": {}, + "reconstruction": { + "kind": "paragraph", + "old_plain_text": "", + "anchors": [] + } } ], "separators": [